Préambule

Objectifs

La sémantique de Verilog n'est pas la même si on souhaite effectuer seulement une simulation ou bien une synthèse.

L'objectif de ce chapitre est de séparer le langage Verilog en deux parties :

Plan du chapitre


Back to Top

Tâches systèmes

Certaines construction ne sont typiquement pas synthétisable : celles qui s'adressent directement à un simulateur (pour garder une trace des chronogrammes, lancer ou stoper une simulation, etc...). Ces constructions sont appelées tâches systèmes. Elle commencent toutes par le symbole $. Nous présentons ici les plus communes, mais il y en bien d'autres !...

Tâche d'affichage
$display, $write
Equivalent du printf de C. $display ajoute automatiquement un retour à la ligne en fin de chaîne, alors que $write non.
Exemple : $display("La valeur hexadecimale de a est %h, sa valeur binaire est %b", a, a);
$monitor
Cette directive prend en argument des variables, des noeuds ou des chaînes de caractères.
A chaque changement d'état d'un des paramètres, l'ensemble des arguments est affiché.
Cette directive n'est évaluée qu'à la toute fin de chaque cycle de simulation, même après les affectations non bloquantes.
Exemple : $monitor($time, ", a=%b, \t b=%b, \t c=%h", a, b, c);
Il ne peut y avoir qu'une seule liste de variable monitorée. $monitor est donc appelée dans un bloc initial seulement. Un appel successif à $monitor remplace le précédent.
$strobe
Cette directive est similaire à $monitor : elle affiche le contenu de ses arguments à la toute fin du cycle de simulation. Mais on doit l'appeler manuellement, elle ne surveille pas les changements des arguments.
Dans chacune de ces fonctions, le spécificateurs de format sont les suivants :
Spécificateur de format Description
%h ou %H Affiche en hexadécimal
%d ou %D Affiche  en décimal
%oou %O Affiche en octal
%b ou %B Affiche en binaire
%c ou %C Affiche un caractère
%s ou %S Affiche une chaîne de caractères
%l ou %L Affiche la bibliothèque où est stockée le module courant
%m ou %M Affiche le nom du module / processus / bloc nommé courant
%t ou %T Affiche un temps
%u ou %U Affiche une valeur  logique bivaluée (0 ou 1)
%z ou %Z Affiche une valeur logique multivaluée (0, 1, X, ou Z)
%e ou %E Affiche un flottant sous forme exponentielle
%f ou %F Affiche un flottant sous forme décimale
%g ou %G Choisit automatiquement entre %f et %e la forme produisant l'affichage le plus court
 
Tâches de contrôle de simulation
$stop
Permet d'arrêter temporairement la simulation. Elle place le simulateur en mode interactif (si celui-ci le supporte). Le concepteur peut alors faire du pas-à-pas, examiner les variables, les modifier, ...
$finish
Permet d'arrêter définitivement la simulation. Généralement, $finish ferme aussi le simulateur...
Tâches de génération de chronogrammes
Ces fonctions ne sont utiles qu'avec un visionneur de chronogrammes externe (GtkWave par exemple). Dans notre cas, Modelsim utilise le sien, et dump automatiquement les variables qu'on lui spécifie !
$dumpfile
Spécifie le nom du fichier de chronogrammes à générer. Le nom du fichier doit généralement se terminer par .vcd
Cette instruction est spécifiée en début de simulation, dans un bloc initial.
Exemple : $dumpfile("test_counter.vcd");
$dumpvars
Permet de spécifier les variables dont il faut garder une trace.
Sans argument : toutes les variable du circuit. Sinon, il y a deux arguments : la profondeur à atteindre et le nom d'un module. Toutes les variables de ce module, jusqu'à la profondeur spécifiée sont tracées. Une profondeur de 0 signifie profondeur illimitée.
Exemple : $dumpvars(1,top); // trace toutes les variable de top, pas celles des modules instanciés dedans.
Entrées-sorties sur fichier
$fopen, $fclose, $fmonitor, $fdisplay
Ouverture / fermeture de fichier, et équivalents de $monitor et $display mais sur fichier. Les descripteurs de fichiers sont des bits stockés dans un integer. Conséquence, on ne peut avoir que 32 fichiers ouverts en même temps (dont stdout, qui a le descripteur 1). Il est possible de faire des sorties sur plusieurs fichiers en même temps en ORant bit à bit les descrpteurs de fichiers.
$readmemb, $readmemh
Permet la lecture d'un fichier ASCII, contenant des mots binaires ou hexadécimaux, pour initialiser une mémoire.

Exemples :

module testmemory;
  reg [7:0] memory [9:0];
  integer index;

  initial begin
    $readmemb("mem.dat", memory);
    for(index = 0; index < 10; index = index + 1)
      $display("memory[%d] = %b", index[4:0], memory[index]);
  end
endmodule 
initial begin
  integer outfile;
  outfile = $fopen("res.dat");
  $fmonitor(outfile, $time, , S);
end
Le format des fichiers utilisés par readmemh / readmemb est le suivant :
// mem.dat
// Les commentaires sont acceptés
AF   // Contenu de l'adresse 0
23   // Contenu de l'adresse 1
@ 87 // On saute directement à l'adresse 87
1A   // Contenu de l'adresse 87
FE   // Contenu de l'adresse 88

$fgetc(fd), $fgets(str, fd), $ungetc(c, fd)
Equivalents aux fonctions du même nom de C.
$fscanf(fd, format, args)
Equivament du fscanf de C. Retourne le nombre d'items convertis. En cas d'erreur, retourne 0. En cas de fin de fichiers, retourne EOF.
Attention : les arguments de $fscanf se voient affectés une valeur de façon immédiate (par une affectation bloquante).
 
$fread(reg, fd)
$fread(mem, fd, start, count)
Permet de lire le contenu binaire d'un fichier et de le stocker dans un reg ou une mémoire. Si la mémoire est plus large que 8 bits, alors elle est remplie en big-endian.
start est optionnel, et indique l'index du premier élément de la mémoire à écrire. S'il n'est pas présent, l'index de départ vaut 0.
count est optionnel , et indique le nombre d'emplacements de la mémoire à charger. Par défaut, c'est autant d'emplacements que possible.
Cette fonction renvoie le nombre d'éléments effectivement lus/écrits.
On dispose aussi des fonction $ftell, $fseek, $rewind, $fflush, $ferror, ... Pour plus de détails on se rapportera à la norme Verilog.

Récupération du temps
$time, $realtime
Retourne le temps actuel de la simulation, arrondi à l'unité  (timescale) du module où on se trouve.
$time renvoie une entier sur 64 bits, $realtime renvoie un flottant.
 
$printtimescale
Affiche la timescale du module courant.
 
$timeformat
Permet de spécifier comment seront affichés les temps dans le cas d'un $display("%t") par exemple. On se rapportera à la norme pour plus de détails.

Back to Top

Sémantique de synthèse

L'ensemble de Verilog étant utilisable pour la simulation, nous allons pencher sur ce qui est spécifique à la synthèse.

La synthèse permet d'aboutir à une représentation structurelle de bas niveau d'un circuit. C'est un processus automatisé, effectué par des logiciels appelés synthétiseurs (!).
Les synthétiseurs actuels acceptent comme langage d'entrée VHDL et Verilog, et SystemC pour certains d'entre eux.
Le problème est qu'ils n'acceptent pas tout Verilog (ni tout VHDL) :

Le processus de synthèse faisant intervenir des problèmes algorithmiques les plus complexes, il faut aider le synthétiseur à effectuer son travail

Même si un code vous semble synthétisable (transformable en matériel par la pensée), le synthétiseur n'en sera peut-être pas capable : des restrictions fortes existent sur le langage synthétisable, ces restrictions dépendant souvent du synthétiseur.

Verilog synthétisable

Le Verilog synthétisable est un sous-ensemble de Verilog ne décrivant que du matériel :

Les ressources matérielles doivent être

Il faut avant tout penser en terme de matériel. Sinon, dans le meilleur des cas le synthétiseur refusera de synthétiser, et dans le pire il synthétisera n'importe quoi sans vous le dire.

Exemples : les codes ci-dessous vous semblent-t-ils synthétisables ? Attention, la réponse n'est pas forcément "non" ! Si c'est "oui", expliquer ce qui sera synthétisé.

// ressources identifiées...
module top(out, a, b);
  input a, b;
  output out;

  reg out;

  always@(b)
    out = a;

endmodule
// ressources allouées...
module top(out, in);
  input [3:0] in;
  output [15:0] out;

  assign out = 2**in;

endmodule
// ressources ordonancées...
module top(out, a, b, c, d);
  input a, b, c, d;
  output out;

  reg out;

  always@(posedge a)
    begin
     if(b)
       @(posedge c) out = d;
     else
       out = d;
    end

endmodule
// ressources ordonancées...
module top(clk, in, out);
  input in, clk;
  output out;

  reg out, in1;

  always@(posedge clk)
    in1 = in;

  always@(posedge clk)
    out = in1;

endmodule

 

Boucles

On peut utiliser les boucles, mais elles sont déroulées avant la synthèse :

Exemples :

Exemple Commentaire
 
parameter max = 8;
for(i=0; i<max; i=i+1)
   <blabla i>





synthétisable, mais tranformé lors de la synthèse en :

<blabla 0>
<blabla 1>
<blabla 2>
<blabla 3>
<blabla 4>
<blabla 5>
<blabla 6>
<blabla 7>
reg max;
for(i=0; i<max; i=i+1)
<blabla i>

non synthétisable

while(0)
synthétisable
reg a;
while(a)
généralement non synthétisable

 

Synchronisation

Les clauses wait sont parfois acceptées mais avec de fortes restrictions, comme par exemple

Les synchronisations événementielles sont acceptées. Attention alors à l'inférence accidentelle de latchs !

Exemple :

reg [2:0] curr_state, next_state;

always @(curr_state);
  begin
    case (curr_state)
      0: next_state <= 3'd4;
      1: next_state <= 3'd6;
      2: next_state <= 3'd1;
      4: next_state <= 3'd5;
      5: next_state <= 3'd2;
      6: next_state <= 3'd0;
    endcase
  end

Exercice :

 
Blocs initial

Les blocs initial sont rarement synthétisés.
Il est préférable d'initialiser les registres à la main (sur un signal de reset ou de preset par exemple)

Back to Top

Conseils d'écriture

  1. Attention aux registres involontaires (cf. ci-dessus)
    Evaluez le nombre de registres avant la synthèse, puis étudiez les messages du synthétiseur. S'il vous signale plus de registres ou latch que prévu, c'est qu'il y a un problème.
    Pour éviter les listes de sensibilité incomplètes, utilisez les listes implicites @*
     
  2. Attention aux cycles combinatoires
    Certains synthétiseurs acceptent de synthétiser de la logique combinatoire comportant des boucles...
     
  3. Attention aux signes lors des affectations ou opérations arithmétiques
    Préférez travailler avec des types simples, et gérez les notions de signe vous-même.
     
  4. Même pour du code non synthétisable (testbench), évitez les délais d'attente. Synchronisez-vous plutôt sur une horloge.
    Même si un #10; et un repeat(10) @(posedge clk); ne sont pas équivalents (pourquoi ?), la seconde forme est bien préférable, et vous évitera bien des déboires.
     
  5. Séparez la logique séquentielle de la logique combinatoire.
    Notament dans les machines à états :
    • un processus pour la mémorisation de l'état courant (séquentiel)
    • un autre processus pour le calcul de l'état futur (combinatoire)
    • un autre processus pour le calcul des sorties (combinatoire)
  6. Evitez les situations de course
    • dans les processus, utilisez toujours les affectation différées ( <= )
    • sauf pour les variables locales, qui utilisent alors des affectation immédiates ( = )
       
  7. N'utilisez pas les pilotes multiples, sauf si vous savez parfaitement ce que vous faites.
    Réservez leur utilisation aux sorties de portes 3-états.
     
  8. Certains synthétiseurs demandent un style d'écriture spécial pour inférer les bons éléments mémorisant.
    Lisez donc la documentation de votre synthétiseur...
     
  9. Attention à la portabilité
    Certaines constructions sont synthétisables par certains synthétiseurs, pas par d'autres. Faites simple, et pensez au matériel avant tout !
    N'oubliez pas que les outils propriétaires sont... propriétaires !
     
  10. Faites simple : un code simple et propre est portable et se synthétisera bien plus vite qu'un code complexe.
    N'oubliez l'adage : "si ça a l'air compliqué, c'est que c'est compliqué".
     
  11. Enfin, la consigne habituelle : mettez des commentaire !
    Si ce n'est pas vous, faites le au moint pour les autres (profs, collègues de travail, ...)
    Et, pitié, mettez des commentaires intelligents...
Back to Top

Conclusion


On a vu dans ce chapitre quelles sont les constructions synthétisables. Pour être synthétisable, les ressources doivent être :

Ce chapitre clôt la formation à Verilog.

Vous savez maintenant comment écrire un modèle complet, le simuler et le débugger. Si vous avez des problèmes, n'hésitez à consulter les enseignants (tous...), à vous reporter aux différents documents officiels de Verilog, aux ressources en ligne, ou même à Google...

Enfin, toute remarque ou critique sur ce cours sera la bienvenue !

 

Back to Top