Eléments de base
Il y a deux sortes de blocs en Verilog :
- modules
- ce sont les blocs qui sont conçus (écrits) par le
concepteur. Le circuit à décrire est donc un module,
et, généralement, ses sous-blocs sont aussi des modules.
primitives
- ce
sont des blocs qui font déjà partie du langage. Comme leur nom
l'indique, ces blocs sont primitifs. Ce sont les portes ET, OR, XOR,
NOT, ... On peut les considérer comme des modules dont le code a déjà
été écrit quelque part.
On notera qu'on peut rajouter des primitives au langage, qui seront
alors appelées primitives utilisateur.
Les fondeurs fournissent généralement, dans les bibliothèques pour la
synthèses, un ensemble de primitives utilisateurs qui modélisent le
comportement de leur portes de base (qui peuvent être un peu plus
complexes que des portes basiques.
Exemple : un NAND à 4 entrée dont
l'une des entrée est complémentée, ou un mutiplexeur). Nous
n'étudierons pas les primitives utilisateurs, elles ne sont pas
utilisées par les concepteurs habituels.
Comme indiqué plus haut, un module peut contenir :
- d'autres modules
- des fils électriques servant de connexions entre les
modules. On les appelle noeuds (wire),
- des instructions, pour modéliser un bout simple du module
sans prendre la peine de créer un sous-module,
- des tâches et des fonctions.
Les modules ont aussi généralement des entrées et des sorties,
qu'on appelera des ports.
Un cas particulier est le cas de la simulation d'un système :
pour
simuler un circuit, on aura besoin au moins du module à simuler et d'un
générateur de stimulis. Ces deux objets seront instanciés dans un même
module, généralement appelé top (mais ce n'est pas une obligation) qui
est autonome : il n'aura pas d'entrées ni de
sorties.
Exemple
: le module top, servant
à simuler le module a
doit être autonome : il n'a pas d'entrée ni de sortie.
Cas
particulier de la simulation : module sans entrée ni sortie
Déclaration d'un
module
La déclaration d'un module se fait comme suit :
module NOM (LISTE DES PORTS);
DECLARATION DES PORTS
DECLARATION DES PARAMETRES
`include "NOM DE FICHIER";
DECLARATIONS DE VARIABLES
AFFECTATIONS
INSTANCIATIONS DE SOUS-MODULES
BLOCS initial ET always
TACHES ET FONCTIONS
endmodule
La déclaration se décompose donc en deux parties :
- l'interface : qui déclare les ports
(entrées - sorties), et d'éventuels paramètres
(servant à faire des modules génériques).
- le corps du module : qui décrit
l'intérieur du module
- les sous-modules
- les noeuds
- des instructions éventuelles, ..
L'interface
On déclare un module par le
mot-clef module suivi du nom de ce
module, puis, entre parenthèses, le nom des éventuelles
entrées-sorties. Et on termine la ligne par un point-virgule.
Exemple
:
module toto(a, b, c, d); // pour l'instant rien ne dit si a, b, c et d sont des entrées ou des sorties
L'ordre de déclaration des
entrées et sortie est laissé à
l'appréciation du concepteur. Mais il vaut mieux adopter une convention
et s'y tenir (par exemple, les entrées en premier, puis les sorties,
...)
Il faut ensuite déclarer :
- le type des ports :si ce sont des
entrées (input), des
sorties (output) ou des
ports bidirectionnels (inout)
- la largeur des ports : s'ils sont sur
un bit ou plusieurs bits.
Exemple :
module toto(a, b, c, d, e);
input a, b; // a et b sont des entrées sur 1 bit
input [7:3] c; // c est un bus en entrée sur 5 bits
output d; // d est une sortie sur 1 bit
inout [31:0] e; // e est un bus en entrée-sortie sur 32 bits
Règles de typage :
- Par défaut, les ports déclarent un noeud (wire) implicite du même nom à
l'intérieur du module.
- input
- intérieur
du module
: une entrée (ou un port bidirectionnel) ne fait que refléter les
changements du signal à l'extérieur du module auquel elle est
connectée. Dans l'intérieur d'un module, une entrée est donc forcément
de type wire.
- extérieur
du module : à l'extérieur du module, cette entrée peut
être connectée à un wire
ou un reg.
- output
- intérieur
du module : dans l'intérieur d'un module, une sortie peut
être de type reg ou un wire. Tout dépend de la façont
dont la sortie est générée (structurel ou comportemental).
- extérieur
du module : à l'extérieur du module, cette sortie ne peut
être connectée qu'à un wire,
qui reflètera ses changements.
- inout
- les
inout étant des ports bidirectionnels, donc à la fois des entrées et
des sorties, ils ne font que refléter les changements extérieurs ou
intérieurs.
- intérieur
du module : dans l'intérieur d'un module, un inout est
donc forcément un wire.
- extérieur
du module : à l'extérieur du module, un inuot ne peut être
connectée qu'à un wire.
Pour q'une sortie soit de type reg
à l'intérieur d'un module, il faut le spécifier manuellement. Sinon le
type wire est implicite.
Remarque
: même si le type wire
est implicite, il est quand même préférable de le préciser !
Exemple
:
module toto(a, b, c, d, e);
input a, b; // a et b sont des entrées sur 1 bit
input [7:3] c; // c est un bus en entrée sur 5 bits
output d; // d est une sortie sur 1 bit
inout [31:0] e; // e est un bus en entrée-sortie sur 32 bits
wire a, b; // superflu, cette déclaration est implicite
wire [7:3] c; // superflu, cette déclaration est implicite
wire [31:0] e; // superflu, cette déclaration est implicite
reg d; // cette declaration est NECESSAIRE si on veut que d soit une variable (reg).
Cette forme de déclaration est celle de Verilog 1995, et
ressemble à
l'ancienne forme du C. Une nouvelle forme plus condensée se trouve
ci-dessous
module toto(
input wire a, // "wire" est superflu
input b,
input wire [7:3] c, // "wire" est superflu
output reg d,
inout wire [31:0] e // dernier port : pas de virgule !
);
Le corps
Dans le corps du module on
trouve tout ce qui en décrit l'intérieur
(les instances des sous-blocs, des noeuds, des instructions, ...). Nous
nous attachons ici à la description structurelle des modules, donc nous
ne verrons dans ce chapitre-ci que la façon d'instancier d'autres
modules, des primitives et des noeuds.
Imaginons qu'on ait créé un
module bascule_D. (module
bascule_D(reset, clk, d, q); ...). On peut vouloit
utiliser plusieurs bascule D dans un autre module. Il faudra donc instancier
plusieurs fois le module bascule_D, en donnant un nom
différent à chaque instance.
L'instanciation de module se
fait ainsi : nom_du_module
nom_de_l_instance (connexion des ports);
La connexion des ports peut
se faire de deux façons :
- connexion par position
- on spécifie la liste des signaux qui vont relier le module
instancié au reste du monde. C'est la position
des signaux dans la liste qui déterminera le port auquel ils seront
reliés. L'ordre des signaux dans la liste doit être le même que celui
de la déclaration des ports : le premier signal sera connecté au
premier port, le deuxième signal au deuxième port, etc.
Si on veut laisser des ports non connectés, ce seront alors
obligatoirement des ports en fin de liste.
- connexion par nom
- pour
des modules ayant beaucoup de ports, il n'est pas pratique de se
souvenir de l'ordre dans lequel les ports on été déclarés. On spécifie
alors pour chaque signal le nom du port auquel on
le
connecte. Cette façon de faire est plus verbeuse, mais permet de
s'affranchir de l'ordre de déclaration des ports. De plus, on peut
choisir de laisser n'importe quel port non connecté.
Exemple :
module toto(a, b, c, d);
input a, b;
output c, d;
...
endmodule
module top();
wire a1, b1, c1, d1;
wire a2, b2, d2;
/* on instancie un premier module toto, qu'on appelle toto1
Les noeud sont connectés par position :
le port a au noeud a1
le port b au noeud b1
le port c au noeud c1
le port d au noeud d1
*/
toto toto1 (a1, b1, c1, d1);
/* on instancie un deuxième module toto, qu'on appelle toto2
Les noeud sont connectés par nom :
le port a au noeud a2
le port b au noeud b2
le port c a rien (ce sera une sortie de toto2 non utilisée)
le port d au noeud d2
*/
toto toto2 (.d(d2), .a(a2), .b(b2));
endmodule
Remarques :
- Il est possible de laisser des ports (sorties)
non connectés. Ce seront juste des sorties non utilisées.
Même si, syntaxiquement, on peut laisser des entrées
non connectées, ce n'est pas une bonne chose (une
entrée de porte ne doit jamais être flottante).
- Il
est possible de connecter des ports vectoriels (bus) à des signaux ou à
des noeuds (bus) de largeur différente. La connexion s'effectue alors
bit à bit en commençant par la droite. C'est, de toutes façons, une
mauvaise habitude !
Exemple structurel
et exercices
Nous avons vu :
- comment déclarer un module
- comment instancier un module ou une primitive
- comment les relier (en déclarant des wire,
et en connectant ces wire
aux ports des modules)
Faisons tout de suite un exemple ! Voici le schéma d'un
additionneur complet 1 bit. Les noms des noeuds internes ont été
représentés.
Additionneur
complet 1 bit
Une description structurelle possible de ce module serait :
module additionneur(
input Ai,
input Bi,
input Ci,
output Si,
output Cout);
wire a, f, e;
xor xor1(a, Ai, Bi);
xor xor2(Si, Ci, a);
and and1(e, Ai, Bi);
and and2(f, Ci, a);
or or1(Cout, f, e);
endmodule
Exercice
:
- A l'aide des testbench du chapitre précédent, testez ce
module par simulation. Est-il correct ?
- Donnez une
description structurelle d'un multiplexeur 4 entrées.
- entrées de sélection : Sel[1:0]
- entrées à commuter : E3, E2, E1, E0
- sortie : S
Afficher
la réponse
Réponse :
En partant sur le schéma suivant,
Multiplexeur
4 entrées
une réponse possible serait :
module mux4(
input E0, E1, E2, E3,
input [1:0] Sel,
output S);
wire S0, S1, S2, S3;
wire not_sel0, not_sel1;
not not0(not_sel0, Sel[0]);
not not1(not_sel1, Sel[1]);
and and0(S0, not_sel0, not_sel1, E0);
and and1(S1, Sel[0], not_sel1, E1);
and and2(S2, not_sel0, Sel[1], E2);
and and3(S3, Sel[0], Sel[1], E3);
or(S, S0, S1, S2, S3);
endmodule // mux4