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
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
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 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 : << , >> .
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.
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
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
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
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 $ :
on va reprendre quelque exemples de circuit classique comme le codeur et le 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
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
// 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
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
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
// 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
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
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
// 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
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.
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.
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
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
// 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.