Systèmes électroniques
Fermer ×

Conception des ASICs

Initiation à Verilog

Documentation et outils

L'objectif n'est pas de décrire complètement verilog, mais de donner les informations pour comprendre le code verilog qui sera utilisé pour la conception de l'ASIC.

Le langage Verilog se compose de modules qui décrivent un circuit ou un composant. Chaque module fournit la liste des signaux qui communiquent avec l'extérieur du circuit ou composant et la description du fonctionnement du composant ou circuit

Pour compléter cette introduction, on peut se référer à ce cours en ligne de verilog ou encore à ce tutorial sur verilog.

Pour tester les codes, on utilisera les outils libres iverilog disponible dans les paquetages des distributions linux et en version windows, pour la compilation (commande iverilog) et la simulation (commande vvp), et gtkwave pour la visualisation des résultats de la simulation

Structure du module

module nom (liste des signaux séparés par des virgule)
// declaration du type et du sens des signaux de la liste input ou output
// declaration des variables de stockage registre : reg
// déclaration des connexions entre circuits : wire
// equations combinatoires avec assign
// processus combinatoires ou
// permanent avec always ou non permanent avec initial
endmodule

module nom (
liste des signaux avec les types et sens
);
// declaration des variables de stockage registre : reg
// déclaration des connexions entre circuits : wire
// equations combinatoires avec assign
// processus combinatoires ou
// permanent avec always ou non permanent avec initial
endmodule

Registre connexion et valeurs

Les valeurs s'écrivent avec la syntaxe :[nombre de bits]'[type][valeur] ce qui donne pour positionner un bit à 0 1'b0 ou à 1 1'b1, Une valeur de bus ou vecteur peut s'écrire en décimal avec le type d, en binaire avec le type b, ou en hexadécimal avec le type h ou encore en octal avec le type o.
Exemples :
4'd15
8'd128

Pour gérer les signaux, on trouve les type registre avec le mot clé reg qui est une mémorisation et le type wire qui est une connexion entre deux composants. Pour les entrées on a soit le sens input ou output. La déclaration d'un signal externe s'écrit donc [sens] [bus eventuel] [reg|wire] nom suivi d'une virgule si on le déclare dans les parenthèses du module ou suivi d'un point virgule si déclare à l'extérieur des parenthèses.
Exemples
input [3:0] e;
output [0:3] reg S;

La déclaration des signaux internes est du même genre à l'exclusion des mots clés input ou output.
Exemples
wire [7:0] bus;
reg clk;

Les opérateurs

Les opérateurs logiques sur les bits utilisent les symboles : ~ (non) , & (et) , | (ou), ^ (Ou exclusif). Les opérateurs logiques sur les booléens (expressions booléennes) sont : ! (non), && (et), || (ou)

Les opérateurs arithmétiques sont : +, -, *, /, ** (puissance).

Les opérateurs de comparaisons sont : ==, !=, <, <=, >, >= .

Les opérateurs de décalage sont : << , >> .

L'affectation combinatoire

Elle utilise le mot clé assign suivi de l'expression logique ou arithmétique.
Exemples
assign S = a | b;
assign S = (Q == 9); affecte la valeur 1 à S si le vecteur Q est égal à 9.

Le processus

Il peut être activé une seule fois au démarrage ou en permanence

La syntaxe du processus activé une seule fois est
initial
begin
//traitement lie au processus
end

Le processus permanent est
always
begin
// traitement lie au processus
end

La liste de sensibilité est une liste de signaux entre parenthèse précédé du symbole @ et reliés par l'opérateur or et éventuellement précédé du mot clé posedge pour la détection d'un changement d'état en séquentiel

Les structures de contrôle

L'alternative simple est
if (expression booléenne) begin
// traitement si vrai
end
else begin
// traitement si faux
end

L'alternative multiple est
case (signal)
valeur1 : traitement1
valeur2 : traitement2
// ... endcase

La boucle :
for (valeur initiale;sortie de boucle;évolution de la valeur) begin
// traitement
end

La répétition spatiale ou duplication de circuit

En dehors du processus, c'est une structure de contrôle encadrée d'un bloc generate comme par exemple avec la boucle :

genvar i;
generate
for (i=0;i < Nmax ;i+=1) begin
// traitement
end
endgenerate

Le testbench

En plus de la syntaxe décrite auparavant, on va voir quelques fonctions utiles pour un fichier testbench, fonctions qui commencent toutes par le symbole $ :

Exemples

on va reprendre quelque exemples de circuit classique comme le codeur et le décodeur

Voir le code verilog et le code du testbench du codeur

Code Verilog du décodeur


module codeur (e,S);
// signaux externes
// entree vecteur de 8 bits
input [0:7] e;
// sortie vecteur de 2 bits
output reg [2:0] S;
// processus premanent
always @(e)
begin
   case (e)
      8'd1    : S = 3'd7 ;
      8'd2    : S = 3'd6 ;
      8'd4    : S = 3'd5 ;
      8'd8    : S = 3'd4 ;
      8'd16   : S = 3'd3 ;
      8'd32   : S = 3'd2 ;
      8'd64   : S = 3'd1 ;
      8'd128  : S = 3'd0 ; 
   endcase
end
endmodule
					

Affichage de la simulation

vvp -n codeur_tb
VCD info: dumpfile codeur.vcd opened for output.
on démarre
e0	e1	e2	e3	e4	e5	e6	e7	S
1	0	0	0	0	0	0	0	0
0	1	0	0	0	0	0	0	1
0	0	1	0	0	0	0	0	2
0	0	0	1	0	0	0	0	3
0	0	0	0	1	0	0	0	4
0	0	0	0	0	1	0	0	5
0	0	0	0	0	0	1	0	6
0	0	0	0	0	0	0	1	7
                 800 on arrête					
					

Code verilog du testbench


// inclure le circuit à tester
`include "codeur.v"
// definir l'échelle de simulation
// 1ns avec une précision de 10ps
`timescale 1 ns/ 10 ps
// module testbench pas de signaux externes
module codeur_tb ();
// stimulis pour tester le composant
reg [0:7] tbe;
// signaux de connexions pour la sortie
wire [2:0] tbS;
// instanciation du ciccuit à tester
codeur chipcoder (
      .e (tbe),
      .S (tbS)
);
// processus de gestion des stimulis
// une seule fois au démarrage
initial
begin
// fichier vcd transmis en paramètre
   $dumpfile(`VCDFILENAME);
// affichage des variables du composant à tester
   $dumpvars(0, chipcoder) ;
// affichage écran
   $display ("on démarre");
   $display ("e0\te1\te2\te3\te4\te5\te6\te7\tS");
   $monitor("%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d",tbe[0],tbe[1],tbe[2],tbe[3],tbe[4],tbe[5],tbe[6],tbe[7],tbS);
   tbe = 8'd128 ;
   #100 // temps en ns relatif au dernier temps
   tbe = 8'd64 ;
   #100
   tbe = 8'd32 ;
   #100
   tbe = 8'd16 ;
   #100
   tbe = 8'd8 ;
   #100
   tbe = 8'd4 ;
   #100
   tbe = 8'd2 ;
   #100
   tbe = 8'd1 ;
   #100
    $display ($time, " on arrête");
// termine la simulation et quitte
    $finish;
end
    
endmodule
					
Voir le code verilog et le code du testbench en version 2 du codeur

Code verilog du codeur


module codeur (e,S);
// signaux externes
// entree vecteur de 8 bits
input [0:7] e;
// sortie vecteur de 2 bits
output reg [2:0] S;
// processus permanent
integer i;
always @(e)
begin
   case (e)
      8'd1    : S = 3'd7 ;
      8'd2    : S = 3'd6 ;
      8'd4    : S = 3'd5 ;
      8'd8    : S = 3'd4 ;
      8'd16   : S = 3'd3 ;
      8'd32   : S = 3'd2 ;
      8'd64   : S = 3'd1 ;
      8'd128  : S = 3'd0 ; 
   endcase
end
endmodule
					

Affichage de la simulation

vvp -n codeur_tb
VCD info: dumpfile codeur.vcd opened for output.
on démarre
e0	e1	e2	e3	e4	e5	e6	e7	S
1	0	0	0	0	0	0	0	0
0	1	0	0	0	0	0	0	1
0	0	1	0	0	0	0	0	2
0	0	0	1	0	0	0	0	3
0	0	0	0	1	0	0	0	4
0	0	0	0	0	1	0	0	5
0	0	0	0	0	0	1	0	6
0	0	0	0	0	0	0	1	7
                 800 on arrête
					

Code verilog du testbench


// inclure le circuit à tester
`include "codeur.v"
// definir l'échelle de simulation
// 1ns avec une précision de 10ps
`timescale 1 ns/ 10 ps
// module testbench pas de signaux externes
module codeur_tb ();
// stimulis pour tester le composant
reg [0:7] tbe;
// signaux de connexions pour la sortie
wire [2:0] tbS;
// instanciation du ciccuit à tester
codeur chipcoder (
      .e (tbe),
      .S (tbS)
);

parameter Nbitsmax=8; 
integer i;
// une seule fois au démarrage
initial
begin
// fichier vcd transmis en paramètre
   $dumpfile(`VCDFILENAME);
// affichage des variables du composant à tester
   $dumpvars(0, chipcoder) ;
// affichage écran
   $display ("e0\te1\te2\te3\te4\te5\te6\te7\tS");
   $monitor("%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d",tbe[0],tbe[1],tbe[2],tbe[3],tbe[4],tbe[5],tbe[6],tbe[7],tbS);
   i=1;
end
    
//  processus permanent qui s'arrête avec Nbitsmax
always
begin
   if (i<=Nbitsmax) begin
      tbe = 2**(Nbitsmax-i);
   end
   else
   begin
      $display ($time, " on arrête");
      $finish;
   end
   #100
   i = i + 1;
end
endmodule
					
Voir le code verilog et le code du testbench du décodeur

Code verilog du décodeur


module decodeur (e,S);
// signaux externes
// entree vecteur de 8 bits
input [2:0] e;
// sortie vecteur de 2 bits
output reg [0:7] S;

// structure combinatoire
genvar i;
generate
   for(i=0;i<8;i=i+1) begin
      assign S[i] = (e == i) ? 1 : 0; 
   end
endgenerate
endmodule
					

Affichage de la simulation

vvp -n decodeur_tb
VCD info: dumpfile decodeur.vcd opened for output.
on démarre
E	S0	S1	S2	S3	S4	S5	S6	S7
0	1	0	0	0	0	0	0	0
1	0	1	0	0	0	0	0	0
2	0	0	1	0	0	0	0	0
3	0	0	0	1	0	0	0	0
4	0	0	0	0	1	0	0	0
5	0	0	0	0	0	1	0	0
6	0	0	0	0	0	0	1	0
7	0	0	0	0	0	0	0	1
                 800 on arrête
					

Code verilog du testbench


// inclure le circuit à tester
`include "decodeur.v"
// definir l'échelle de simulation
// 1ns avec une précision de 10ps
`timescale 1 ns/ 10 ps
// module testbench pas de signaux externes
module decodeur_tb ();
// stimulis pour tester le composant
reg [2:0] tbe;
// signaux de connexions pour la sortie
wire [0:7] tbS;
// instanciation du ciccuit à tester
decodeur chipdecoder (
      .e (tbe),
      .S (tbS)
);

parameter Nbitsmax=8; 
integer i;
// une seule fois au démarrage
initial
begin
// fichier vcd transmis en paramètre
   $dumpfile(`VCDFILENAME);
// affichage des variables du composant à tester
   $dumpvars(0, chipdecoder) ;
// affichage écran
   $display ("on démarre");
   i=0;
   $display ("E\tS0\tS1\tS2\tS3\tS4\tS5\tS6\tS7");
   $monitor("%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d",tbe,tbS[0],tbS[1],tbS[2],tbS[3],tbS[4],tbS[5],tbS[6],tbS[7]);
end
//  processus permanent qui s'arrête avec Nbitsmax
always
begin
   if (i < Nbitsmax) begin
      tbe = i ;
   end
   else
   begin
      $display ($time, " on arrête");
      $finish;
   end
   #100
   i = i + 1;
end
endmodule
					

Conception

Documents et Outils

Cette page a été inspirée par cette description de la conception d'un processeur RISC à l'aide de logiciels libres.

Il s'agit de la suite logicielle qflow disponible sur opencircuitdesign. Tous ces logiciels sont inclus dans les distributions linux.

Cette suite logicielle utilise un logiciel par étape de conception :

Cette suite logicielle utilise uniquement la description verilog, mais il est possible d'utiliser VHDL, en installant la dernière version de GHDL et le plugin pour yosys. Cette solution permet d'utiliser yosys pour convertir les codes sources VHDL en verilog avant d'utiliser la suite qflow.

Exemple de conception

On va concevoir un ASIC à partir du compteur déjà étudié en VHDL.

On ne va pas utiliser la conversion vhdl vers verilog, mais écrire directement le code verilog de ce compteur, puis on soumettra ce code verilog à la suite qflow pour concevoir le circuit.

Voir le code verilog et le code du testbench du compteur

Code verilog du compteur


module compteur (
// signaux externes
input clk,
input reset,
input en,
output tc,
// sortie vecteur de 4 bits
output [3:0] Q
);
// signal interne
reg [3:0] cpt;
// traitement combinatoire
assign Q = cpt;
// tc = 1 si cpt = 9 
assign tc = (cpt == 4'd9);
// processus qui se déroule en permanence
// la liste de sensibilité est clk et reset
always @(posedge clk or posedge reset)
begin
   if (reset) begin
         cpt <= 4'd0;
   end
    else begin
      if (en) begin
         if (cpt < 9)
            cpt <= cpt + 4'd1;
         else
            cpt <= 4'd0 ;
      end
   end
end 
endmodule
					

Résultat de la simulation

vvp -n compteur_tb
VCD info: dumpfile compteur.vcd opened for output.
t	rst	en	Q3	Q2	Q1	Q0	Q
  0	1	0	0	0	0	0	 0
 30	0	0	0	0	0	0	 0
 50	0	1	0	0	0	0	 0
 60	0	1	0	0	0	1	 1
100	0	1	0	0	1	0	 2
140	0	1	0	0	1	1	 3
180	0	1	0	1	0	0	 4
220	0	1	0	1	0	1	 5
260	0	1	0	1	1	0	 6
300	0	1	0	1	1	1	 7
340	0	1	1	0	0	0	 8
380	0	1	1	0	0	1	 9
420	0	1	0	0	0	0	 0
                 450 on arrête
					

code du tesybench


// inclure le circuit à tester
`include "compteur.v"
// definir l'échelle de simulation
// 1ns avec une précision de 10ps
`timescale 1 ns/ 10 ps
// module testbench pas de signaux externes
module compteur_tb ();
// stimulis pour tester le composant
reg tbclk = 1'b0; // horloge initialise à 0
reg tbreset;
reg tben;
// signaux de connexions pour la sortie
wire [3:0] tbQ;
wire tbtc;
// instanciation du ciccuit à tester
compteur chipcpt (
      .clk (tbclk),
      .reset (tbreset),
      .en (tben),
      .tc (tbtc),
      .Q (tbQ)
);
// processus permanent qui génère l'horloge
// il contient une boucle infinie : l'horloge
always
begin
    #20
   tbclk = ~tbclk;
end
// processus de gestion des stimulis
// une seule fois au démarrage
initial
begin
// fichier vcd transmis en paramètre
   $dumpfile(`VCDFILENAME);
// affichage des variables du composant à tester
   $dumpvars(0, chipcpt) ;
// affichage écran
   $display ("t\trst\ten\tQ3\tQ2\tQ1\tQ0\tQ");
   $monitor("%3d\t%d\t%d\t%d\t%d\t%d\t%d\t%d",$time,tbreset,tben,tbQ[3],tbQ[2],tbQ[1],tbQ[0],tbQ);
   tbreset = 1'b1;
   tben = 1'b0 ;
   #30 
   tbreset = 1'b0;
   #20
   tben = 1'b1;
   #400
   $display ($time, " on arrête");
// termine la simulation et quitte
   $finish;
end
    
endmodule
					

Après avoir traiter le code verilog avec qflow, on obient un affichage du circuit avec magic

Parmi toutes ces informations, il y a celle qui précise que le fonctionnement de la puce est garanti pour une fréquence maximale de l'horloge est de 765MHz.