Préambule
Objectifs
La description structurelle d'un circuit complexe en vhdl
présente de nombreux avantages :
- Une architecture hiérarchique
compréhensible : il est plus simple de
séparer un circuit en un ensemble de blocs plus petits, ayant des
fonctions bien
identifiées. Ces blocs pourront alors être
décrits sous forme
comportementale, ou bien à leur tour
être séparés en blocs encore plus
simples.
- Une synthèse logique efficace : la
synthèse est un
processus lent (en terme de temps de calcul). Plus un bloc est gros et
complexe, plus sa synthèse prendra du temps. Il vaut donc
mieux
travailler sur des blocs plus petits, plus simples à
synthétiser, et
rassembler le tout à la fin.
L'objectif de ce chapitre est de voir
précisément comment
coder une représentation structurelle d'un circuit,
autrement
dit :
- comment déclarer des blocs (qu'on appellera
composant)
- comment utiliser un composant (qui devient une "instance")
et déclarer la façon dont il est
connecté.
- comment choisir l'architecture d'un composant quand il y en
a plusieurs (configurations)
Plan du chapitre
- Eléments de base
qu'est-ce qu'un composant, que contient-il ?
- Déclaration et instanciation des composants
comment déclare-t-on un composant
(déclaration) et comment l'appeler pour le
connecter
(instanciation) ?
- Composants
génériques
VHDL offre la possibilité d'avoir des
composants génériques comme par exemple un
compteur sur N bits où N est un paramètre qu'il
faut indiquer au moment de l'instanciation. Comment fait on pour passer
des paramètres à ce type de composants
- Configurations
VHDL offre la possibilité d'avoir
plusieurs
scénarios d'architecture pour un même composant.
Comment
configurer le composant ?
- Exercices
Eléments de base
- VHDL permet l'assemblage de "composants"
ce qui constitue une description structurelle. Ce composant peut
être appelé plusieurs fois dans un même
circuit.
Pour différencier ces mêmes composants, il est
nécessaire de leur donner un nom d'"instance".
L'appel
d'un composant se dit aussi "instanciation"
De façon à instancier un
composant il est nécessaire de connaître :
- Le prototype du composant (ses ports d'entrée et
de sortie). La directive component
peut être utilisée à cette fin.
- A quelle entité et architecture est
lié chaque instance de composant. Ce lien peut être connu grâce à
l'unité de configuration.
Il est important de noter :
- La déclaration du composant (directive component ) est redondante
textuellement avec celle de l'entité associée
mais permet :
- Une compilation indépendante entre
l'entité associée au composant et le circuit
utilisant le composant.
- La conception descendante. Le composant peut
être déclaré avant l'entité
associée.
- La configuration est une unité de compilation optionnelle,
très
utile pour les gros circuits. Par exemple pour
accélérer
la simulation , un même composant peut être
associé
à un couple entité/architecture détaillé et
synthétisable ou un
autre couple plus abstrait et plus
rapide à simuler. Pour ne pas utiliser de
configuration, une règle fréquente
est
d'utiliser le même nom pour le composant et
l'entité associée, c'est le cas pour
ModelSim et les outils de synthèse FPGA.
La description structurelle est nécessaire pour
simuler un circuit dont les vecteurs stimulis sont eux mêmes
issus d'un modèle VHDL. Le modèle de plus haut niveau fait donc appel
au circuit à tester (Device Under Test) et d'un
générateur de stimulis. Ces deux objets sont
instanciés dans un même circuit,
généralement appelé "testbench" (mais
ce n'est pas une obligation) qui
est autonome : il n'aura pas d'entrées
ni de
sorties.
Exemple
: le circuit top, servant
à simuler le circuit "module a"
doit être autonome : son entité n'a pas
d'entrée ni de
sortie.
Cas
particulier de la simulation : circuit "top" sans entrée ni
sortie
Déclaration ET INSTANCIATION dES COMPOSANTS
Déclaration
Le mot clé component sert
à déclarer le prototype d'interconnexion. La syntaxe est presque
identique à
celle de l'entité :
component AND_2 port ( a : in bit; b : in bit; s : out bit); end component;
|
Pour
créer rapidement un
composant, une opération copier/coller de l'entité en enlevant
le litéral "IS" suffit.
Instanciation :
L'instanciation d'un composant se fait dans le corps de l'architecture
de cette façon :
<NOM_INSTANCE>:<NOM_COMPOSANT> port map(LISTE DES CONNEXIONS);
Exemple:
entity AND_3 is port( e1 : in bit; e2 : in bit; e3 : in bit; s : out bit ); end entity; -- architecture arc of AND_3 is -- signal z : bit; component and2 port ( a : bit; b : bit; s : bit); end component; -- begin inst1 : and2 port map (a=>e1, b=>e2 , s=>z); inst2 : and2 port map (z, e3, s); end arc
|
Dans cet exemple , 2 instances de composant "and2" sont appelées pour créer une porte ET à 3 entrées.
L'association des ports du composants aux signaux de l'instance se fait à l'aide de la clause port map.
La syntaxe des associations est soit
-
par nom où chaque broche du composant est associée à un signal : cas de inst_1
-
positionnelle où l'ordre des signaux correspond à l'ordre des broches : cas de inst_2
|
GENERICITE
Déclaration
Un composant peut être
générique en définissant
les paramètres qui seront vus comme des constantes
à
chaque instance de composant. Il est ainsi possible de n'avoir qu'un
seul composant pour différentes instances ayant
des
paramètres différents. Dans la
déclaration du
composant, la clause generic
sert à passer les paramètres au composant. Dans
l'exemple
suivant, l'entier positif N indique le nombre de bits de
l'additionneur.
component ADD generic ( N : positive range 0 to 16 ); port ( A: in std_logic_vector(N-1 downto 0); B: in std_logic_vector(N-1 downto 0); S: out std_logic_vector(N-1 downto 0) ); end component;
|
Le paramètre N permet de dimensionner la taille de l'additionneur, il est déclaré avec la clause generic
|
De même l'entité associée au
composant doit comporter la clause generic pour
déclarer le(s) paramètre(s)
entity ADD is generic ( N : positive range 0 to 16 ); port ( A: in std_logic_vector(N-1 downto 0); B: in std_logic_vector(N-1 downto 0); S: out std_logic_vector(N-1 downto 0) ); end entity ADD;
|
Instanciation :
L'instanciation d'un composant se fait dans le corps de l'architecture
de cette façon :
architecture arc of mult is component ADD generic ( N : positive range 0 to 16); port ( A: in std_logic_vector(N-1 downto 0); B: in std_logic_vector(N-1 downto 0); S: out std_logic_vector(N-1 downto 0)); end component; signal OP1,OP2,S std_logic_vector(N-1 downto 0); . . . -- begin inst_ADD : ADD generic map(N=>12); port map(A=>OP1, B=>OP2,S=>S); . . . end arc;
|
La clause generic map dans l'instanciation du composant ADD permet de fixer la valeur du paramètre.
|
Instanciation de multiples composants
Les paramètres ne sont parfois pas suffisants pour
écrire un
code générique : on peut aussi vouloir
instancier
un nombre variable de composants (en fonction d'un
paramètre, par exemple).
Ceci est fait au moyen des mots-clef
for
generate..et if generate.
Exemple 1
: on veut décrire un multiplieur
générique, tel que :
- L'architecture Carry Lookhead (CLA) est utilisée s'il doit
manipuler des nombres de largeur
inférieure à 8
bits,
- L'architecture en arbre de
Wallace est utilisée sinon.
L'exemple ci-dessous fait appel à la clause
IF
GENERATE en testant le
paramètre
width.
Notez que :
- le ELSE
n'existe pas (oubli de VHDL ?) et qu'il
faut refaire un 2ème IF.
- l'instruction IF GENERATE
a besoin obligatoirement d'une
étiquette
use work.pack.all;
entity multiplier is generic( width : positive :=8;); port( a : in signed(width-1 downto 0); b : in signed(width-1 downto 0); product : out (2*width-1 downto 0)); end entity; -- architecture arc of multiplier is -- begin -- CLA_gen : if width < 8 generate inst_cla : CLA_multiplier generic map (width) port map(a, b, product); -- end generate CLA_gen; -- WAL_gen : if width >= 8 generate inst_wal : WAL_multiplier generic map (width) port map(a, b, product); end generate WAL_gen; -- end arc; --
|
L'exemple ci-contre fait appel à la clause IF GENERATE en testant le paramètre width.
Notez que :
-
le ELSE n'existe pas (oubli de VHDL ?) et qu'il faut refaire un 2ème IF.
-
l'instruction IF GENERATE a besoin obligatoirement d'une étiquette
|
Exemple
2
: l'exemple classique de l'additionneur n bits... On utilise ici une
boucle FOR GENERATE
pour
instancier automatiquement les différentes primitives
ainsi que les noeuds
les connectant entre elles. Notez qu'il n'est pas nécessaire
de
déclarer la variable de boucle mais que l'instruction FOR GENERATE nécessite
une étiquette.
entity Nbit_adder is generic( SIZE = 4); port ( a,b : in unsigned(SIZE-1 downto 0); ci : in std_logic; sum : out unsigned(SIZE-1 downto 0); co : out std_logic); end entity; -- component FA port(A,B,Cin : in std_logic; S, Cout : out std_logic); end component; -- architecture arc of Nbit_adder is -- signal c :unsigned(SIZE downto 0);; -- begin -- C(0) <= Cin; co <= C(SIZE); -- G: for I in 0 to N-1 generate inst: FA port map(A(I), B(I), C(I), sum(I), C(I+1)); end generate G; -- end arc;
|
LA
CONFIGURATION
VHDL dispose d'un mécanisme appelé
"configuration" permettant d'associer une instance de composant
à un couple entité/architecture. La configuration
est une
unité de compilation à part, tout comme
l'entité
et l'architecture. Pour la plupart des outils , la configuration est optionnelle.
Le lien composant/entité s'effectue
généralement
en imposant un nom de l'entité identique à celui
du
composant, et le lien entité/architecture s'effectue soit en
considérant la dernière architecture
compilée,
soit l'outil impose une architecture unique.
La configuration est donc l'unité de compilation de
plus haut
niveau car elle permet l'élaboration de l'ensemble
(l'édition de liens) pour pouvoir simuler. Son
intérêt apparaît pour
gérer des gros
circuits où plusieurs architectures sont possibles pour une
même entité. C'est la cas quend il existe des
modèles d'abstraction différentes, ce qui est
très
utilisé pour les méthodes de conception "top
down" .
Par exemple une équipe peut avoir des modèles
abstraits
de tous les blocs de façon à
accélérer les
temps de simulation et travailler sur plusieurs architectures
du bloc à concevoir.
Il existe plusieurs façons de configurer un projet :
- configuration hiérarchique
- configuration à plat
- configuration immédiate
- instanciation de configuration
Configuration hiérarchique
Pour chaque entité, une configuration est
créée. Dans chaque configuration, la clause use
configuration est utilisée pour les instances
de composant de
niveau inférieur.
configuration CF_AND2 of AND_2 is for arc end for; end configuration CF_AND2;
|
La
configuration
est vide
car
l'entité AND_2
n'a pas d'instances de composant.
La
configuration existe toutefois car tous les composants
utilisés
doivent avoir une configuration dans cette méthode.
|
configuration CF_AND3 of AND_3 is for arc for all : AND2 use configuration work.CF_AND2; end for; end for; end configuration CF_AND3;
|
Le composant AND2
est instancié dans AND_3.
A chaque
instance la configuration CF_AND2
est utilisée.
|
Pour les gros circuits, il
est fortement recommandé
d'utiliser ce type de configuration car c'est la plus simple
à
maintenir.
Configuration à plat
Elle peut être unique et indique les liens
entité/architecture explicitement avec la clause
use
entity(architecture). Elle est utilisée pour
des circuits simples car il n'est pas nécessaire d'avoir des fichiers
de configuration au niveau hiérarchique inférieur.
configuration CF_AND3_APLAT of AND_3 is for arc for i1 : AND2 use entity work.AND2(a1); end for; for i2 : AND2 use entity work.AND2(a2); end for; end for; end configuration CF_AND3_APLAT;
|
Configuration immédiate
Les associations entité/architecture des composants peuvent
être déclarés directement dans
l'architecture avant
le corps de l'architecture. Elle est réservée aux
petits
circuits pour des essais de différentes architectures.
architecture arc of TOP is for all : AND2 use configuration work.CF_AND2; end for; for inst_block1 : compo use entity work.ctr(arc); end for; begin I1 : AND2 port map (...); I2 : AND2 port map (...); inst_block1 : compo port map (...); end arc;
|
Instanciation de configuration
Dans ce cas , il n'est pas nécessaire de déclarer
de
composant et l'instanciation se fait en spécifiant le couple entité/architecture. La conception dans ce cas est nécessairement "bottom-up" et non "top-down" car il faut obligatoirement avoir concçu les entités appelées au niveau le plus haut.
architecture arc of TOP is begin I1 : entity work.and2(arc1) port map (...); I2 : entity work.ctrl(arch) port map (...); end arc;
|
Cette méthode ne
nécessite pas de configuration. Elle est la plus simple mais peut être
aussi moins adaptée aux gros circuits du fait de la conception "bottom
up" uniquement. |
exercices
Nous avons vu :
- comment déclarer un composant
- comment instancier un composant
- comment instancier un composant générique
- comment les relier (en utilisant la clause port map et generic map)
Faisons tout de suite des exemples !
Exercices:
- Codez un additionneur générique de 2 nombres non signés sur n bits. Afficher la réponse
Réponse Additionneur générique :
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
--
entity GEN_ADD is
generic
(
n : postive :=8
);
port
(
A : in unsigned(n-1 downto 0);
B : in unsigned(n-1 downto 0);
S : out unsigned(n-1 downto 0)
);
end GEN_ADD;
--
architecture RTL of GEN_ADD is
--
begin
--
S <= A + B;
--
end RTL;
--
--
|
Notez l'utilisation de la bibliothèque IEEE, permettant d'utiliser le type std_logic (paquetage std_logic_1164 et le sous type unsigned (paquetage numeric_std). |
- Codez un registre générique de n bits. Afficher la réponse
Réponse Registre générique
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
--
entity GEN_REG is
generic
(
n : integer :=8
);
port
(
CLK : in std_logic;
NRST : in std_logic;
EN : in std_logic;
D : in unsigned(n-1 downto 0);
Q : out unsigned(n-1 downto 0)
);
end GEN_REG;
--
architecture RTL of GEN_REG is
--
begin
--
SYNC : process(CLK,NRST)
begin
if NRST='0' then
Q <= (others => '0');
elsif (CLK'event and CLK='1')then
if en='1' then
Q <= D;
end if;
end if;
end process sync;
--
end rtl;
--
|
Notez l'utilisation de la bibliothèque IEEE, permettant d'utiliser le type std_logic (paquetage std_logic_1164 et le sous type unsigned (paquetage numeric_std). |
- Codez en VHDL structurel un accumulateur n bits à l'aide du registre générique et de l'additionneur générique. Afficher la réponse
Réponse Accumulateur générique
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
--
entity ACCU is
port
(
CLK : in std_logic;
NRST : in std_logic;
EN : in std_logic;
D : in unsigned(7 downto 0);
Q : out unsigned(7 downto 0)
);
end ACCU;
--
architecture RTL of ACCU is
--
constant N : integer := 8;
signal Q_int : unsigned(7 downto 0);
signal D_int : unsigned(7 downto 0);
--
--
begin
--
INST_REG : entity work.GEN_REG(RTL) generic map (N=>n) port map(CLK=>CLK,NRST=>NRST,EN=>EN,D=>D_int,Q=>Q_int);
INST_ADD : entity work.GEN_ADD(RTL) generic map (N=>n) port map(A=>D,B=>Q_int,S=>D_int);
Q <= Q_int;
--
end RTL;
--
|
Notez l'utilisation de la bibliothèque IEEE, permettant d'utiliser le type std_logic (paquetage std_logic_1164 et le sous type unsigned (paquetage numeric_std). |
- Ecrivez un testbench simple pour tester l'accumulateur par simulation.Afficher la réponse
Réponse Test de l'accumulateur générique
--
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
--
use std.textio.all;
--
--
entity ACCU_TEST is
end ACCU_TEST;
--
--
architecture ARCHI of ACCU_TEST is
--
constant N : integer :=8;
signal CLK : std_logic;
signal N_RESET : std_logic;
signal EN : std_logic;
signal D : signed(N-1 downto 0);
signal Q : signed(N-1 downto 0);
file VECT : text open READ_MODE is "./simu/accu_test_in.dat";
file EXP : text open READ_MODE is "./simu/accu_test_exp.dat";
file RESULT : text open WRITE_MODE is "./simu/accu_test_out.dat";
--
--
--
begin
--
--
--
EN <= '1';
--
horloge :process
begin
CLK <='1';
wait for 50 ns;
CLK <= '0';
wait for 50 ns;
end process horloge;
--
RESET : process
begin
N_RESET <= '0';
wait until CLK'event and CLK='0';
N_RESET <= '1';
wait;
end process RESET;
--
DATA :process
variable L1 : line;
variable L2 : line;
variable L3 : line;
variable D_in : integer;
variable D_exp : integer;
variable D_out : integer;
begin
write(L3,string'(" temps entrée attendu resultat"));
write(L3,LF);
writeline(RESULT,L3);
loop
wait until CLK'event and CLK='0';
readline(VECT,L1);
read(L1,D_in);
readline(EXP,L2);
read(L2,D_exp);
D <= to_signed(D_in,N);
write(L3,now,field=>10);
write(L3,D_in,field=>8);
write(L3,D_exp,field=>8);
write(L3,to_integer(Q),field=>9);
if to_integer(Q) /= D_exp then
write(L3,string'(" => erreur à cette ligne"));
end if;
writeline(RESULT,L3);
assert (to_integer(Q)=D_exp) report "comparaison fausse" severity failure;
assert (endfile(VECT)=FALSE) report "fin de simulation" severity failure;
end loop;
end process;
--
--
INST_ACCU : entity WORK.ACCU(RTL) port map (CLK=>CLK,NRST=>N_RESET,EN=>EN, D=>D,Q=>Q);
--
end ARCHI;
|
|
- Compilez les circuits et simulez le tesbench avec ModelSim. Déboguez si nécessaire
Réponse :
Testbench de
l'accumulateur
une réponse
possible serait :
Notez l'utilisation de la bibliothèque IEEE, permettant d'utiliser le
type std_logic (paquetage std_logic_1164 et le sous type unsigned
(paquetage numeric_std).
En
résumé
On a vu dans ce chapitre comment déclarer, instancier et
relier les composants entre eux.
L'objectif du prochain chapitre est de savoir comment
décrire
le contenu d'un composant, son fonctionnement.