Préambule

Objectifs

La description en Verilog d'un circuit complexe se fait souvent de façon d'abord structurelle :

L'objectif de ce chapitre est de voir précisément comment coder une représentation structurel d'un circuit, autrement dit

Plan du chapitre


Back to Top

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 :

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.

Module particulier sans entrées ni sorties (toplevel)

Cas particulier de la simulation : module sans entrée ni sortie



Back to Top

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

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 :

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 :

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 :



Back to Top

Les primitives

Verilog définit un certain nombre de portes de base, dont le nombre de port est variable. Ces portes ne peuvent être connectées que par liste ordonnée.

A ces détails près, les primitives sont instanciées comme des modules normaux.


Back to Top

Exemple structurel et exercices

Nous avons vu :

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

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 :

  1. A l'aide des testbench du chapitre précédent, testez ce module par simulation. Est-il correct ?
  2. 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

 

 

Back to Top

En résumé

On a vu dans ce chapitre comment déclarer, instancier et relier les modules entre eux.

L'objectif du prochain chapitre est de savoir comment décrire le contenu du module, son fonctionnement.

Back to Top