UN MINI-PROCESSEUR Projet de VHDL Gregory Heinrich/Maël Le Berre Historique du document Version Date Modification V0.01 26.10.2002 Document de référence initial V0.02 29.10.2002 Addition d’informations sur l’architecture et définition des entités V0.03 10.11.2002 Addition des codes source et chronogrammes de simulation V1.01 12.11.2002 Document rendu Projet de VHDL – un mini-processeur -1- TABLE DES MATIERES UN MINI-PROCESSEUR...................................................................................... 1 TABLE DES MATIERES ............................................................................................................... 2 CONTEXTE ..................................................................................................................................... 4 1 OBJECTIFS............................................................................................................................... 5 1.1 1.2 1.3 UN CODE REUTILISABLE ET COMMENTE ................................................................................. 5 UN CODE MODULAIRE POUR FACILITER LE TRAVAIL EN EQUIPE ............................................. 5 L’AVANTAGE DE L’OUTIL INFORMATIQUE ............................................................................. 5 2 CAHIER DES CHARGES ....................................................................................................... 6 3 METHODOLOGIE POUR LE CODAGE EN VHDL ....................................................... 10 3.1 NOMENCLATURE DES SYMBOLES ......................................................................................... 10 3.1.1 Noms de fichiers ........................................................................................................... 10 3.1.2 Noms de types et de signaux ........................................................................................ 10 3.2 TYPES DE DONNEES UTILISES ............................................................................................... 10 Fichier upp_util_types.vhdl.conf ............................................................................................... 11 3.3 PACKAGES ........................................................................................................................... 13 Fichier upp_util_tools.vhdl ....................................................................................................... 13 4 ARCHITECTURE DU COMPOSANT ................................................................................ 14 4.1 4.2 4.3 5 L’UNITE ARITHMETIQUE ET LOGIQUE (UPP_ALU) ................................................................. 14 LE DECODEUR D’INSTRUCTIONS (UPP_DECODER) ................................................................ 15 LE PILOTE DE REGISTRES (UPP_REGDRIVER) ........................................................................ 15 CODAGE EN VHDL .............................................................................................................. 17 Projet de VHDL – un mini-processeur -2- 5.1 OUTILS POUR L’AIDE A LA COMPILATION ............................................................................. 17 5.1.1 Script pour le paramétrage des constantes du système ............................................... 17 5.1.2 Fichier Makefile ........................................................................................................... 19 5.2 ENTITES UTILISEES .............................................................................................................. 19 5.2.1 Upp............................................................................................................................... 19 5.2.2 Upp_alu........................................................................................................................ 19 5.2.3 Upp_decoder ................................................................................................................ 19 5.2.4 Upp_regdriver.............................................................................................................. 20 5.3 SOURCES VHDL DES COMPOSANTS MODELISES .................................................................. 20 5.3.1 Upp.vhdl ....................................................................................................................... 20 5.3.2 Upp_alu.vhdl ................................................................................................................ 22 5.3.3 Upp_decoder.vhdl ........................................................................................................ 23 5.3.4 Upp_regdriver.vhdl ...................................................................................................... 25 6 SIMULATIONS ...................................................................................................................... 29 6.1 6.2 6.3 6.4 7 SIMULATION DU CIRCUIT DE DECODAGE (UPP_DECODER) .................................................... 29 SIMULATION DU CIRCUIT D’OPERATIONS LOGIQUES ET ARITHMETIQUES (UPP_ALU)............ 29 SIMULATION DU PILOTE DE REGISTRES (UPP_REGDRIVER) ................................................... 30 SIMULATION DU COMPOSANT PRINCIPAL UPP....................................................................... 31 SYNTHESE ............................................................................................................................. 32 7.1 UPP_ALU ............................................................................................................................. 33 7.2 UPP_DECODER ..................................................................................................................... 34 7.3 UPP_REGDRIVER .................................................................................................................. 35 7.4 UPP ...................................................................................................................................... 36 7.4.1 Upp, avec sa hiérarchie de composants conservée ..................................................... 36 7.4.2 Upp, hiérarchie dissoute .............................................................................................. 36 8 POUR ALLER PLUS LOIN… .............................................................................................. 38 8.1 8.2 8.3 PROGRAM COUNTER ............................................................................................................ 38 MEMOIRE CACHE ................................................................................................................. 38 PIPELINE .............................................................................................................................. 38 Projet de VHDL – un mini-processeur -3- CONTEXTE Le VHDL (Very High speed integrated circuits Description Language) est un langage de description matériel permettant de décrire la structure d’un design électronique, en la décomposant en sous-blocs et en spécifiant comment ces sous-blocs sont reliés. D’autre part, il permet de décrire le corps d’un bloc par l’intermédiaire de fonctions usuelles utilisées dans des langages de programmation informatique. Décrire un composant en VHDL permet de réduire le cycle de conception, en offrant un design facilement simulable et plus facile à valider. D’autre part, les composants créés sont facilement réutilisables et modifiables. Dans le cadre de l’unité VHDL de I4, les étudiants doivent réaliser un projet en VHDL. Ce rapport décrit la façon dont le projet a été mené. Projet de VHDL – un mini-processeur -4- 1 OBJECTIFS Lorsque l’on commence un projet, il faut avoir des objectifs clairs. Nous nous sommes fixés ici l’objectif de montrer aussi clairement que possible comment le VHDL a changé la donne en matière de conception électronique. En particulier, il nous est paru important d’insister sur les points suivants : 1.1 Un code réutilisable et commenté Le code VHDL que nous écrirons sera paramétrable autant que possible. Nous maintiendrons un fichier de constantes permettant de changer toutes les constantes du composant : taille des registres, nombre de registres, nombre d’instructions, etc. D’autre part, le code sera lisible et commenté, de manière à en faciliter la compréhension pour une personne étrangère au projet. 1.2 Un code modulaire pour faciliter le travail en équipe En VHDL, il est très facile de décomposer un composant en sous-composants. Chacun de ces souscomposants devient un composant à part entière et doit répondre à un sous-cahier des charges. Si chacun des sous-composants est fidèle à son sous-cahier des charges, alors les membres du groupe de projet peuvent travailler indépendamment les uns des autres et rassembler leurs descriptions une fois leur mission accomplie. 1.3 L’avantage de l’outil informatique Les fichiers VHDL sont des fichiers texte à part entière. On peut donc profiter de l’avantage offert par l’environnement de développement pour programmer des scripts informatiques qui traitent les fichiers sources au préalable. Dans notre exemple, nous utiliserons par exemple un script Perl qui permet de remplacer dans le code VHDL des constantes spécifiées dans un fichier principal de constantes et de faire certains calculs comme des calculs de logarithmes (utilisés notamment pour déterminer le nombre de fils nécessaires pour coder un nombre compris dans un intervalle de valeurs). Nous aurons aussi un fichier makefile qui contiendra toutes les informations sur la compilation du code et les dépendances entre fichiers. Projet de VHDL – un mini-processeur -5- 2 CAHIER DES CHARGES Projet de VHDL – un mini-processeur -6- Projet de VHDL – un mini-processeur -7- Projet de VHDL – un mini-processeur -8- Projet de VHDL – un mini-processeur -9- 3 METHODOLOGIE POUR LE CODAGE EN VHDL 3.1 Nomenclature des symboles Afin de s’y retrouver dans un projet, il est souvent utile de se fixer des règles pour la nomenclature des symboles utilisés. Dans la suite, on notera que « upp » est l’acronyme du sujet du projet : « un petit processeur ». 3.1.1 Noms de fichiers Les noms de fichiers VHDL utilisés sont de la forme : - Upp.vhdl, pour le fichier contenant l’entité et l’architecture du composant principal, - Upp_util_*.vhdl pour les fichiers contenant les packages utilisés, - Upp_*.vhdl pour les fichiers contenant les sous-composants. Enfin, nous utilisons un fichier générique de constantes nommé project.conf, et un fichier modèle pour le remplacement automatique par script des constantes appelé upp_util_types.vhdl.conf. Ces deux fichiers sont décrits dans la section suivante. 3.1.2 Noms de types et de signaux Pour tous les symboles, le caractère « _ » permet de lier les mots composant le nom du symbole lorsque c’est nécessaire. Les signaux et les types sont écrits en minuscule. Les types définis dans le projet comportent tous la mention « upp_ » au début. Les constantes génériques du système sont notées en majuscules. Le mot clé « accumulateur » désignera le registre utilisé pour stocker les résultats d’une opération sur des registres opérandes souvent notés « operande1 » et « opérande2 ». 3.2 Types de données utilisés Il est très important de se mettre d’accord sur les types de données utilisés car cela permet ensuite d’échanger des informations homogènes. Nous avons souhaité dans ce projet emprunter une approche proche de celle que l’on pourrait utiliser pour programmer dans un langage de programmation. Par exemple, nous utilisons des structures de données (types record) pour modéliser des instructions. Nous pensons que ceci permet d’abstraire beaucoup mieux le problème, sans pour autant augmenter la complexité du circuit synthétisé. Les structures de données ne sont Projet de VHDL – un mini-processeur - 10 - rien d’autre que des fils au niveau synthétisé, mais au niveau du source VHDL, elles nous ont permis d’écrire un code plus concis et plus proche du modèle. D’autre part, pour obtenir un circuit synthétisable, nous avons veillé à ce que tous les signaux d’entrée/sortie du composant soient des std_logic, ou des std_logic_vector. Quelques informations sur les principaux types utilisés : - upp_reg représente un registre, dont la taille est paramétrée par REG_SIZE, - upp_reg_array est un tableau de registres (le nombre de registres est paramétré par REG_COUNT). C’est la banque de registres utilisés dans le composant upp_regdriver pour stocker les données, - upp_reg_addr_bus, un registre contenant suffisamment de bits pour permettre d’adresser tous les registres du composant, - upp_opcode, une énumération sur les stypes d’instructions supportés (ex : op_null, op_add, …), - upp_instruction est une structure de données contenant l’opcode de l’instruction (upp_opcode) et les adresses des deux registres opérandes, - upp_regdrivingmode, une structure de données utilisée dans upp_regdriver pour déterminer le mode de transport des registres. Cette structure contient l’index du registre, ainsi qu’un flag boolean permettant de déterminer si les bus data_in, data_out du composant principal sont impliqués dans le transport, - upp_overalldrivingmode, une structure contenant les informations sur le transport de chacun des registres accumulateur, operande1 et opérande2 utilisés pour communiquer entre upp_regdriver et upp_alu. Fichier upp_util_types.vhdl.conf -- ----------- ESIEE I4 --------------- EL411 : Projet de VHDL -- Un Petit Processeur ( upp ) -- Gregory HEINRICH / Mael LE BERRE --------------------------------------- upp_type : -- contantes et types generaux utilises dans le processeur upp. -- Ces types permettent de controler entierement la taille de ses attributs. library ieee ; use ieee.std_logic_1164.all, ieee.numeric_std.all ; package upp_types is ------------ CONSTANTES : -- Taille des registres en bit : constant REG_SIZE : integer := 16 ; -- Nombre de registres : Projet de VHDL – un mini-processeur - 11 - constant REG_COUNT : integer := 8 ; -- Largeur du bus d'adresse des registre : -- ( typiquement : ADDR_BUS_SIZE = LOG2( REG_COUNT ) ) constant ADDR_BUS_SIZE : integer := 3 ; -- Taille des codes operations : -- ( typiquement : OPCODE_BIT_COUNT = LOG2( nombre de code operation ) ) constant OPCODE_BIT_COUNT : integer := 3 ; -- Taille d'une instruction en bit : constant INSTRUCTION_SIZE : integer := OPCODE_BIT_COUNT + 2*ADDR_BUS_SIZE ; ------------ TYPES : -- Type d'un registre : subtype upp_reg is signed( REG_SIZE-1 downto 0 ) ; -- Type de la banque de registre : type upp_reg_array is array( REG_COUNT-1 downto 0 ) of upp_reg ; -- Type d'un bus d'adresse de registre : subtype upp_reg_addr_bus is signed( ADDR_BUS_SIZE-1 downto 0 ) ; -- Type d'un code operation : type upp_opcode is ( op_null, op_load, op_move, op_add, op_sub, op_read, op_unsupported ) ; -- Type d'une instruction : type upp_instruction is record opcode : upp_opcode ; operande1 : upp_reg_addr_bus ; operande2 : upp_reg_addr_bus ; end record ; -- Type d'un mode de 'driving' d'un bus : type upp_regdrivingmode is record index : upp_reg_addr_bus ; -- Soit l'index d'un registre flag : boolean ; -- Soit vrai si e/s sur bus e/s du processeur ( prioritaire ) end record ; -- Type du 'driving' global du processeur : type upp_overalldrivingmode is record accumulator_mode : upp_regdrivingmode ; operande1_mode : upp_regdrivingmode ; operande2_mode : upp_regdrivingmode ; end record ; end upp_types ; Projet de VHDL – un mini-processeur - 12 - 3.3 packages Nous avons utilisé deux packages dans ce projet : - un package upp_util_types.vhdl, dont le contenu est ci-dessus. Ce package contient les types et constantes utilisés dans le projet, - un package upp_util_tools.vhdl, dans lequel on a placé une fonction utile qui nous permet de convertir des integer en signed. Dans cette fonction, nous avons simplement « hardcodé » les correspondances (ex : « 5 » -> « 101 »). Fichier upp_util_tools.vhdl 1. library ieee ; use ieee.std_logic_1164.all ; use ieee.numeric_std.all ; use work.upp_types.all ; package upp_tools is function int2signed ( a : integer ) return signed ; end upp_tools ; package body upp_tools is function int2signed ( a : integer ) return signed is variable u : signed( ADDR_BUS_SIZE-1 downto 0 ) ; begin case a is when 0 => u:="000" ; when 1 => u:="001" ; when 2 => u:="010" ; when 3 => u:="011" ; when 4 => u:="100" ; when 5 => u:="101" ; when 6 => u:="110" ; when 7 => u:="111" ; when others => u:="000" ; end case ; return u ; end function int2signed ; end package body upp_tools ; Projet de VHDL – un mini-processeur - 13 - 4 ARCHITECTURE DU COMPOSANT Nous avons choisi d’utiliser l’architecture suivante : Le composant Un Petit Processeur (UPP) est la réunion de 3 modules : 4.1 L’unité arithmétique et logique (upp_alu) upp_alu est l’unité qui exécute les instructions. Afin de limiter la surface du composant, elle opère sur deux registres opérandes et place le résultat en sortie sur l’accumulateur. Ainsi, dans le cas d’une addition par exemple, le résultat de l’addition entre les deux opérandes est placé dans l’accumulateur en sortie. Afin de n’avoir à utiliser qu’un seul additionneur pour le circuit, nous Projet de VHDL – un mini-processeur - 14 - avons implémenté de la manière suivante : le soustracteur est complémenté à deux, puis placé en entrée de l’additionneur. Lorsque le résultat est calculé, upp_alu inverse le signal accumulator_update pour signifier aux autres composants que la valeur de l’accumulateur a été actualisée. Entrées : - operande1, operande2, de type upp_reg, servant de données pour effectuer l’opération, - opcode, de type upp_opcode, l’opcode de l’instruction a réaliser (ex : op_add, op_move, …). Sorties : - s, de type upp_reg, le résultat de l’opération, - accumulator_update, de type std_logic, inversé lorsque le résultat est disponible. 4.2 Le décodeur d’instructions (upp_decoder) upp_decoder est le module qui décode les instructions. Lors de chaque front d’horloge, l’instruction est placée en entrée de ce module, qui va rechercher l’opcode de l’instruction, les registres mis en cause et le mode de transport à opérer sur les registres. Lorsque l’instruction est décodée, la valeur du signal operande_update est inversée pour signifier aux autres composants que les opérations de l’alu et les opérations de transport sur les reigstres peuvent commencer. Entrées : - instruction, de type std_logic_vector, l’instruction donnée en entrée du composant principal, - h, l’horloge, utiliser pour la synchronisation. Sorties : - drivingmode, de type upp_overalldrivingmode, le mode de transport à opérer sur les registres, - opcode, de type upp_opcode, le type d’instruction (op_add, op_sub, …), - operande_update, de type std_logic, inversé lorsque le décodage est fini. 4.3 Le pilote de registres (upp_regdriver) upp_regdriver est le composant qui va positionner les valeurs correctes sur les registres d’opérandes transmis à upp_alu, et qui recopiera la valeur de retour de upp_alu sur l’emplacement adhoc, en fonction de l’instruction qui aura été effectuée. C’est aussi dans ce module que les registres de données R0 à Rx seront déclarés. C’est le seul composant qui peut lire/écrire sur les registres. Entrées : - raz, le signal de remise à zéro. Ceci pilote la remise à zéro des registres de données, Projet de VHDL – un mini-processeur - 15 - - accumulator_update, operande_update, de type std_logic, les signaux venant respectivement de upp_alu et upp_decoder, utilisés pour synchroniser le repositionnement des registres accumulateur et opérandes, - data_in, de type upp_reg, l’entrée de données du composant principal, - reg_accumulator, de type upp_reg, le registre accumulateur, c’est le registre sur lequel upp_alu sort le résultat de l’instruction en cours d’exécution, - mode, de type upp_overalldrivingmode, la structure de données codant pour le mode de transmission des données entre registres et entrées/sorties du composant principal. Sorties : - reg_operande1, reg_operande2, de type upp_reg, les deux registres opérandes utilisés par upp_alu, - data_out, de type upp_reg, la sortie de données du composant principal, - data_valid, de type std_logic, positionné à 1 lorsque la valeur sur data_out est correcte. Projet de VHDL – un mini-processeur - 16 - 5 5.1 5.1.1 CODAGE EN VHDL Outils pour l’aide à la compilation Script pour le paramétrage des constantes du système Nous avons mis au point un script Perl qui permet facilement de modifier les constantes spécifiées dans les fichiers VHDL en modifiant seulement un fichier de constantes. Le fichier de constantes prend la forme suivante (fichier project.conf): REG_SIZE=16 REG_COUNT=8 OPCODE_COUNT=8 Ensuite, nous écrivons un fichier de configuration qui définit le modèle à suivre pour le remplacement des constantes. Ci-dessous, un extrait du fichier upp_util_types.vhdl.conf : constant constant constant constant REG_SIZE : integer := $(REG_SIZE); REG_COUNT : integer := $(REG_COUNT); ADDR_BUS_SIZE : integer := LOG($(REG_COUNT)); OPCODE_BIT_COUNT : integer := LOG($(OPCODE_COUNT)); Après exécution du script, un fichier upp_util_types.vhdl est généré. En voici un extrait : constant constant constant constant REG_SIZE : integer := 16; REG_COUNT : integer := 8; ADDR_BUS_SIZE : integer := 3; OPCODE_BIT_COUNT : integer := 3; Voici le script Perl que l’on a écrit pour le projet : #!/usr/esiee/bin/perl #fichier configure.pl # Ce fichier permet de remplacer les valeurs specifiees # dans le fichier de configuration dans le code VHDL # des fichiers de constantes sub LoadFile { local($line); open(FILE1,$_[0]) || die "Can't open file $_[0]"; $_[1]=''; while($line=<FILE1>) { Projet de VHDL – un mini-processeur - 17 - $_[1].=$line; } close(FILE1); } $MASTER=$ARGV[0]; $PATTERN=$ARGV[1]; $SLAVE=$ARGV[2]; open(MASTERFILE,"$MASTER") || die "Can't open file $MASTER"; &LoadFile($PATTERN,$pattern); while (<MASTERFILE>) { chop; if (/=/) { $SYMBOL=$`; $VALUE=$'; $pattern =~ s/\${$SYMBOL}/$VALUE/g; } } while ( $pattern =~ /LOG\((.*)\)/ ) { $ARGUMENT=$&; $ARGUMENT =~ s/LOG\(//; $ARGUMENT =~ s/\)//; $ARGUMENT =~ s/ //g; $VALUE=(log($ARGUMENT)/log(2)); $pattern =~ s/LOG\([ ]*$ARGUMENT[ ]*\)/$VALUE/g; } $pattern =~ s/LOG\((.*)\)/(\1)/g; close(MASTERFILE); open(SLAVEFILE,">$SLAVE") || die "Can't write to $SLAVE"; print SLAVEFILE "$pattern"; close(SLAVEFILE); Le script s’utilise de la manière suivante : # ./configure.pl project.conf upp_util_types.vhdl.conf upp_util_types.vhdl Projet de VHDL – un mini-processeur - 18 - L’intérêt d’un script comme celui-ci est qu’il permet de définir un unique fichier de constantes. D’autre part, on peut facilement procéder à des opérations du type logarithme de base 2, puissance, etc. Enfin, il est facile à encapsuler dans les commandes d’un fichier makefile. 5.1.2 Fichier Makefile Pour compiler l’ensemble des fichiers nécessaires au projet, il faut taper un certain nombre de lignes de commandes, regroupant : - la ligne de commandes qui fait appel au script Perl chargé de traiter les constantes du système, notées dans le fichier project.conf, - les lignes de commandes pour la compilation des fichiers VHDL. Cet ensemble de lignes de commandes dépend de l’état du système : par exemple, si l’on ne modifie que le fichier contenant l’architecture du composant principal, il n’est pas nécessaire de recompiler les autres fichiers. Si l’on modifie le package de constantes et de types, il faut tout recompiler. Si l’on modifie seulement un sous-composant, il ne faut recompiler que le sous-composant et le composant principal. On a ainsi des dépendances entre les fichiers sources, parfaitement gérées par un fichier d’informations sur la compilation de projets du type Makefile. 5.2 Entités utilisées 5.2.1 Upp 5.2.2 Upp_alu entity upp_alu_entity is port ( operande1, operande2 : in upp_reg; opcode : in upp_opcode; s : out upp_reg; ); end upp_alu_entity; 5.2.3 Upp_decoder entity upp_decoder_entity is port ( instruction: in std_logic_vector(INSTRUCTION_SIZE-1 downto 0); drivingmode: out upp_overalldrivingmode; opcode: out upp_opcode ); Projet de VHDL – un mini-processeur - 19 - end upp_decoder_entity; 5.2.4 Upp_regdriver entity upp_regdriver_entity is port ( raz : in std_logic; reg_accumulator : in upp_reg; data_in : in upp_reg; mode: in upp_overalldrivingmode; reg_operande1 : out upp_reg; reg_operande2 : out upp_reg; data_out: out upp_reg ); end upp_regdriver_entity; 5.3 5.3.1 Sources VHDL des composants modélisés Upp.vhdl ----------- ESIEE I4 --------------- EL411 : Projet de VHDL -- Un Petit Processeur ( upp ) -- Gregory HEINRICH / Mael LE BERRE --------------------------------------- upp : -- Un Petit Processeur -- Suivant le cahier des charge fourni pour le projet. library ieee ; use ieee.std_logic_1164.all, ieee.numeric_std.all ; library work ; use work.upp_types.all ; entity upp_entity is port ( enable, h, raz : in std_logic ; data_in: in std_logic_vector ( REG_SIZE-1 downto 0 ) ; instruction: in std_logic_vector ( INSTRUCTION_SIZE-1 downto 0 ) ; data_out: out std_logic_vector ( REG_SIZE-1 downto 0 ) ; data_valid: out std_logic ) ; Projet de VHDL – un mini-processeur - 20 - end upp_entity ; architecture upp_arch of upp_entity is signal instruction_reg : std_logic_vector ( INSTRUCTION_SIZE-1 downto 0 ) := ( others => '0' ) ; -- Registre contenant l'instruction en cours de traitement. ( utilise par 'decoder' ) signal drivingmode : upp_overalldrivingmode ; -- Ligne de 'driving' des bus. ( entre 'decoder' et 'regdriver' ) signal opcode_reg : upp_opcode ; -- Code operation de l'instruction en cours. ( utilise par 'alu' ) signal operande1_bus, operande2_bus, accumulator_bus : upp_reg ; -- Les bus principaux. ( entre 'alu' et 'regdriver' ) signal data_out_signal : signed( REG_SIZE-1 downto 0 ) := ( others => '0' ) ; - Le signal data_out. signal accumulator_update,operande_update : std_logic ; -- Les signaux de mise a jour des bus. begin -- Decoder : Decode l'instruction pour en extraire l'opcode et les modes de 'driving' des bus. decoder : entity work.upp_decoder_entity( upp_decoder_arch ) port map( instruction_reg, h, drivingmode, opcode_reg,operande_update ) ; -- Alu : Organe de calcule central. alu : entity work.upp_alu_entity( upp_alu_arch ) port map( operande1_bus, operande2_bus, opcode_reg, accumulator_bus, accumulator_update ) ; -- Regdriver : Organe qui oriente les donnee entre les bus, les registres et les e/s. ( contient les registres ) regdriver: entity work.upp_regdriver_entity( upp_regdriver_arch ) port map( raz,accumulator_update,operande_update,accumulator_bus, signed( data_in ), drivingmode, operande1_bus, operande2_bus, data_out_signal,data_valid ) ; data_out <= std_logic_vector ( data_out_signal ) ; process ( raz,H ) -- Chargement de l'instruction a chaque coup d'horloge : begin if ( raz='0' ) or ( enable='0' ) then instruction_reg <= ( others => '0' ) ; elsif ( rising_edge( H ) ) then instruction_reg <= instruction ; end if ; end process ; end architecture upp_arch ; Projet de VHDL – un mini-processeur - 21 - 5.3.2 Upp_alu.vhdl ----------- ESIEE I4 --------------- EL411 : Projet de VHDL -- Un Petit Processeur ( upp ) -- Gregory HEINRICH / Mael LE BERRE --------------------------------------- upp_alu : -- Unite Arithmetique et Logique : -- Realise une fonction combinatoire entre 2 operandes. ( addition, soustraction ou simple copie. ) -- Ecrit le resultat dans l'accumulateur. library ieee ; use ieee.std_logic_1164.all, ieee.numeric_std.all ; library work ; use work.upp_types.all ; entity upp_alu_entity is port ( operande1, operande2 : in upp_reg ; -- Les 2 operandes. opcode : in upp_opcode ; -- Le code operation a realiser. accumulator : out upp_reg := ( others => '0' ) ; -L'accumulateur. accumulator_update : out std_logic -- signal de mise a jour de l'accumulateur. ) ; end upp_alu_entity ; architecture upp_alu_arch of upp_alu_entity is signal accumulator_update_intern : std_logic:='0' ; signal accumulator_intern : upp_reg := ( others => '0' ) ; -- signal intermmediaire resultat de l'operation. signal operande2_intern : upp_reg ; -- operande2 eventuellement complemente a 2 dans le cas d'une soustraction. begin process ( operande1, operande2_intern, opcode ) begin -- Process de calcul principale : realise une addition ou une simple copie. case opcode is when op_add|op_sub => accumulator_intern <= ( operande1 + operande2_intern ) ; when op_move|op_load|op_read => accumulator_intern <= operande1 ; when others => accumulator_intern <= ( others => '0' ) ; -- pour les operation non suportée : on met 0 dans l'accumulateur. end case ; end process ; process ( accumulator_intern ) begin -- Process de mise a jour : Si l'accumulateur change, on le met a Projet de VHDL – un mini-processeur - 22 - jour et on transmet l'information par le signal de mise a jour. : accumulator <= accumulator_intern ; accumulator_update_intern <= not accumulator_update_intern ; accumulator_update <= accumulator_update_intern ; end process ; with opcode select -- Dans le cas d'une soustraction, operande 2 est complemente a 2. operande2_intern <= ( not operande2 ) + 1 when op_sub, -- complement a 2. operande2 when others ; end architecture upp_alu_arch ; 5.3.3 Upp_decoder.vhdl -- ----------- ESIEE I4 --------------- EL411 : Projet de VHDL -- Un Petit Processeur ( upp ) -- Gregory HEINRICH / Mael LE BERRE --------------------------------------- upp_decoder : -- decodeur d'instruction : -- extrait de l'instruction : -- Le code operation. -- La maniere de 'driver' les bus. library ieee ; use ieee.std_logic_1164.all, ieee.numeric_std.all ; library work ; use work.upp_types.all ; entity upp_decoder_entity is port ( instruction: in std_logic_vector ( INSTRUCTION_SIZE-1 downto 0 ) ; -- Instruction. h: in std_logic ; -- Horloge. drivingmode: out upp_overalldrivingmode ; -- mode de 'driving' : cf upp. opcode: out upp_opcode ; -- code operation. operande_update : out std_logic -- signal de mise a jour des operandes. ) ; end upp_decoder_entity ; architecture upp_decoder_arch of upp_decoder_entity is signal opcode_intern : upp_opcode ; -- Signal de code operation interne : reutilise pour le 'driving'. signal drivingmode_intern : upp_overalldrivingmode ; -- signal de 'driving' interne : reutilise pour la mise a jour des operandes. Projet de VHDL – un mini-processeur - 23 - signal operande_update_intern,operande_update_intern1,operande_update_intern2 : std_logic := '0' ; -- Signaux de remise a jour. begin with instruction( instruction'length-1 downto instruction'length OPCODE_BIT_COUNT ) select opcode_intern <= -- Description des opcodes : op_null when "000", op_load when "001", op_move when "010", op_add when "011", op_sub when "100", op_read when "101", op_unsupported when others ; opcode <= opcode_intern ; -- GESTION DES REGISTRES -- les premiers bits de l'instructions apres le code operation indiquent le registre -- source et/ou destination de toute operation sur un registre. -- ils sont donc les bits qui vont 'driver' l'operande1 et l'accumulateur avec les registres : drivingmode_intern.accumulator_mode.index <= signed( instruction( instruction'length-OPCODE_BIT_COUNT-1 downto ADDR_BUS_SIZE ) ) ; drivingmode_intern.operande1_mode.index <= signed( instruction( instruction'length-OPCODE_BIT_COUNT-1 downto ADDR_BUS_SIZE ) ) ; -- Les bits suivants indiquent le registre source du deuxieme operande quand il est necessaire. -- ils sont donc les bits qui vont 'driver' l'operande2 avec les registres : drivingmode_intern.operande2_mode.index <= signed( instruction( ADDR_BUS_SIZE-1 downto 0 ) ) ; -- le deuxieme operande n'est jamais charge par data_in donc son flag est toujours a zero : drivingmode_intern.operande2_mode.flag <= false ; -- GESTION ENTREE/SORTIE -- Dans la partie precedente, le 'driving' des registres n'a pas tenu compte -- du code operation : l'information de 'driving' avec les registres est systematique, -- mais ne sera pas toujours pris en compte car le 'flag' d'entree/sortie est considere -- comme prioritaire. with opcode_intern select -- l'accumulateur est dirige vers la sortie ( data_out ) pour l'operation 'op_read' : drivingmode_intern.accumulator_mode.flag <= true when op_read, false when others ; with opcode_intern select -- l'entree ( data_in ) est dirigee vers l'operateur 1 pour l'operation 'op_load' : drivingmode_intern.operande1_mode.flag <= Projet de VHDL – un mini-processeur - 24 - true when op_load, false when others ; process( drivingmode_intern ) begin -- A chaque modification de 'drivingmode' : les operandes doivent etre recharges : operande_update_intern1 <= not operande_update_intern1 ; end process ; process( h ) begin -- A chaque coup d'orloge, les operandes doivent etre recharges : -- ( pour le cas ou une meme instruction serait repetee plusieurs fois auquel cas 'drivingmode' n'est pas modifié. ) if rising_edge( h ) then operande_update_intern2 <= not operande_update_intern2 ; end if ; end process ; -- la mise a jours des operandes dependant de 2 conditions, on realise un xor entre celles-ci -- pour prendre en compte la variation de chacune. operande_update_intern <= operande_update_intern1 xor operande_update_intern2 ; process( operande_update_intern ) begin -- Chaque modification entraine le rechargement des sorties de 'driving'. drivingmode <= drivingmode_intern ; operande_update <= operande_update_intern ; end process ; end ; -- architecture upp_decoder_arch ; 5.3.4 Upp_regdriver.vhdl ----------- ESIEE I4 --------------- EL411 : Projet de VHDL -- Un Petit Processeur ( upp ) -- Gregory HEINRICH / Mael LE BERRE --------------------------------------- upp_regdriver : -- Contient l'instance des registres et 'Drive' les registres avec les bus : -- Recoit le mode de 'driving' du decodeur d'instruction. -- Charge les operandes ( d'un registre ou de data_in ). -- Copie l'accumulateur la ou il doit etre copie ( dans un registre ou dans data_out ). -- Se charge d'emettre le signal data_valid. Projet de VHDL – un mini-processeur - 25 - library ieee ; use ieee.std_logic_1164.all, ieee.numeric_std.all ; library work ; use work.upp_types.all ; use work.upp_tools.all ; entity upp_regdriver_entity is port ( raz : in std_logic ; -- Remise a zero generale. accumulator_update,operande_update : in std_logic ; -- signaux de mise a jour des bus. reg_accumulator : in upp_reg ; -- Le bus accumulateur. data_in : in upp_reg ; mode: in upp_overalldrivingmode ; -- Le mode de 'driving'. reg_operande1 : out upp_reg ; -- Les bus operande. reg_operande2 : out upp_reg ; data_out: out upp_reg := ( others => '0' ) ; data_valid : out std_logic := '0' ) ; end upp_regdriver_entity ; architecture upp_regdriver_arch of upp_regdriver_entity is -- Instance des registres : signal reg_bank : upp_reg_array := ( others =>( others => '0' ) ) ; begin process( mode.accumulator_mode,accumulator_update,raz ) begin -- Process gerant l'ecriture dans les registres. -- Sensibilites : -- si l'accumulateur change ( accumulator_update ), -- son mode de 'driving'( mode.accumulator_mode ) -- ou enfin en cas de remise a zero ( raz ). if ( raz = '0' ) then -- Si raz = 0 : on remet tous les registres a zero : for i in 0 to REG_COUNT-1 loop reg_bank( i ) <=( others => '0' ) ; end loop ; data_out <= ( others => '0' ) ; -- data _out et data_valid aussi. data_valid <= '0' ; else if ( mode.accumulator_mode.flag=false ) then data_valid <= '0' ; -- si l'accumulateur doit etre ecrit dans un registre : data_valid = 0 -- Dans ce cas : chaque registre recoit sa valeur si son index -- est egale a celui indique dans le mode de driving : Projet de VHDL – un mini-processeur - 26 - for i in 0 to REG_COUNT-1 loop if ( mode.accumulator_mode.index = int2signed( i ) ) then reg_bank( i ) <= reg_accumulator ; end if ; end loop ; else -- si l'accumulateur doit etre ecrit dans data_out : data_out <= reg_accumulator ; -- On l'y ecrit. data_valid <= '1' ; -- On met data_valid a 1. end if ; end if ; end process ; process( data_in,operande_update,mode.operande1_mode ) begin -- Process gerant la lecture dans les registres. -- Sensibilites : -- si data_in change ( data_in ), -- ou si le decodeur demande la mse a jour des operandes. ( operande_update ) if ( raz = '1' ) then if ( mode.operande1_mode.flag=false ) then -- si un registre doit etre ecrit dans l'operande1 : -- chaque registre ecrit sa valeur si son index -- est egale a celui indique dans le mode de driving : for i in 0 to REG_COUNT-1 loop if ( mode.operande1_mode.index = int2signed( i ) ) then reg_operande1 <= reg_bank( i ) ; end if ; end loop ; else -- si data_in doit etre ecrit dans operande1 : reg_operande1 <= data_in ; end if ; end if ; end process ; process( data_in,operande_update ) begin -- Process gerant la lecture dans les registres. -- Sensibilites : -- si data_in change ( data_in ), -- ou si le decodeur demande la mse a jour des operandes. ( operande_update ) if ( raz = '1' ) then if ( mode.operande2_mode.flag=false ) then -- si un registre doit etre ecrit dans l'operande1 : -- chaque registre ecrit sa valeur si son index -- est egale a celui indique dans le mode de driving : for i in 0 to REG_COUNT-1 loop Projet de VHDL – un mini-processeur - 27 - if ( mode.operande2_mode.index=int2signed( i ) ) then reg_operande2 <= reg_bank( i ) ; end if ; end loop ; else -- si data_in doit etre ecrit dans operande1 : reg_operande2 <= data_in ; end if ; end if ; end process ; end ; -- architecture upp_regdriver_arch; Projet de VHDL – un mini-processeur - 28 - 6 SIMULATIONS 6.1 Simulation du circuit de décodage (upp_decoder) 6.2 Simulation du circuit d’opérations logiques et arithmétiques (upp_alu) Projet de VHDL – un mini-processeur - 29 - 6.3 Simulation du pilote de registres (upp_regdriver) Projet de VHDL – un mini-processeur - 30 - 6.4 Simulation du composant principal upp Projet de VHDL – un mini-processeur - 31 - 7 SYNTHESE Un des impératifs du cahier des charges était d’obtenir un circuit synthétisable. Nous allons voir dans les sections suivantes pour chaque composant comment les circuits peuvent être synthétisés, et quelles en sont les caractéristiques (surface, …). Projet de VHDL – un mini-processeur - 32 - 7.1 Upp_alu Projet de VHDL – un mini-processeur - 33 - 7.2 Upp_decoder Projet de VHDL – un mini-processeur - 34 - 7.3 Upp_regdriver Projet de VHDL – un mini-processeur - 35 - 7.4 Upp 7.4.1 Upp, avec sa hiérarchie de composants conservée Surface avant dissolution de la hiérarchie : Module upp_entity upp_alu_entity upp_decoder_entity upp_regdriver_entity AWDP_ADD_partition_0 AWDP_ADD_partition_1 7.4.2 Wireload B0X0 B0X0 B0X0 B0X0 B0X0 B0X0 Cell Area 1809.00 329.00 8.00 1398.00 123.00 74.00 Net Area 0.00 0.00 0.00 0.00 0.00 0.00 Total Area 1809.00 329.00 8.00 1398.00 123.00 74.00 Cell Area 287.00 Net Area 0.00 Total Area 287.00 Upp, hiérarchie dissoute 7.4.2.1 Caractéristiques Surface après dissolution de la hiérarchie : Module upp_entity Wireload not mapped Projet de VHDL – un mini-processeur - 36 - 7.4.2.2 Schéma Projet de VHDL – un mini-processeur - 37 - 8 POUR ALLER PLUS LOIN… Comment aurait-on pu aller plus loin dans ce projet ? Les microprocesseurs d’aujourd’hui sont des machines constituées de dizaines de millions de transistors, toujours plus rapides et complexes. Voici quelques unes des améliorations que l’on pourrait apporter à notre upp. La plupart d’entre elles nécessiteraient une refonte radicale de notre architecture, ainsi que des milliers de jours*homme supplémentaires sur le projet, mais toutes sont « standard » dans les processeurs de nos jours : 8.1 Program Counter Dans notre architecture, on voit bien qu’il est impossible de rajouter des instructions de branchement vers une autre instruction. Ceci signifie donc qu’il n’est pas possible avec upp d’exécuter des algorithmes impliquant des prises de décision du type si… alors… sinon. Avec un Program Counter qui procède à un fetch des instructions dans la mémoire, il serait possible de simuler des prises de décision. Un Program Counter impliquerait d’ajouter un circuit supplémentaire pour la gestion/protection de la mémoire (MMU/MPU), 8.2 Mémoire cache Ajouter de la mémoire cache à un processeur n’est pas en soi une tâche compliquée (bien qu’elle soit coûteuse en termes de place sur le weafer de silicone). Ce qui ensuite complique la logique d’un processeur, c’est ensuite les méthodes utilisées pour : - choisir quelle section de mémoire sera en cache (une méthode typiquement utilisée est celle du Round Robin, où la stratégie est semblable à une pile de données, - déterminer si une zone de mémoire cachée a été modifiée depuis sa mise en cache. Dans la pratique, on distingue alors souvent le cache d’instructions (censées ne jamais être altérées) et la cache de données (qui peuvent être modifiées en cours d’exécution), pour lequel il faut implémenter des méthodes du type WriteBack, WriteThrough, etc. Lorsqu’une boucle tient entièrement dans le cache processeur, l’exécution de celle-ci peut être 20 à 25 fois plus rapides que s’il faut recupérer les instructions de la mémoire RAM. 8.3 Pipeline L’idée du Pipeline est celle de la chaîne de montage d’une automobile : plutôt que de faire les voitures une par une, on décompose le montage en étapes et lorsqu’une étape est accomplie, la voiture passe à l’étape suivante et une autre la remplace. Projet de VHDL – un mini-processeur - 38 - Ici, on décompose le traitement d’une instruction en étapes, de manière à simplifier chacune des étapes et être à même d’augmenter la cadence de l’horloge du processeur. Le temps de traitement des instructions est multiplié par le nombre d’étapes, mais dans la pratique, à chaque cycle d’horloge (ou presque), une instruction est effectuée. Ceci rend le processeur beaucoup plus complexe, car en plus de la synchronisation entre les étapes de traitement (typiquement au minimum fetch, decode, execute), il faut tenir compte des cas où une donnée utilisée dans le pipeline est modifiée à l’instruction précédente, ainsi que des instructions de branchement pour lesquelles il faut vider le pipeline pour sauter à l’instruction suivante (sauf dans le cas où les prédictions de branchement ont permis de « prédire » l’instruction suivante). Projet de VHDL – un mini-processeur - 39 -