Courte introduction pratique à FPGA, Verilog et Verilator et quelques mots à propos de SystemVerilog

→ English version here

Attention, Je suis un débutant de moins d’un mois en Verilog, Verilator et FPGA, que j’ai étudié comme un loisir, si il y a des erreurs je les corrigerais. Vous pouvez me contacter sur le Fediverse.

Sommaire


* Introduction
** Process complet dans le monde réel
** Choses à connaître et comprendre
** Comment se programme un FPGA
* Verilog
** Valeurs
** Types
** Portes
** Modules
** Exemple simple, écrire une porte « et »
** Blocs initial et always
* Exemple simple en Verilator
** Effectuer le test avec Verilator
** Tracer l’exemple avec GTKWave
** À propos des exemples de Verilator
** Exemple pratique de base avec Verilator
* Pour aller plus loin

Le texte mis en gras ici, est utilisé afin d’aider à lire en diagonale.

Introduction

Je continue de descendre dans les couches avec le mode des FPGA (en anglais, « Field Processors Gateway Array », il existe différentes traductions). Ce sont des circuits itnégrés programmables, permettant de réaliser des circuilts logiques comme des processeurs. Ils sont notamment utilisés pour développer et tester des processeurs. Lorsque le processeur est validé en FPGA, ils peuvent être produit en ASIC (en anglais : « Application-specific integrated circuit »), processeur finaux, avec circuits cablés en durs et n’étant plus modifiables, tels qu’on les trouves dans les ordinateurs et périphériques informatiques de tous les jours. Les FPGA sont également utilisés tels quels dans différentes applications industrielles (avionique, traitement audio, vidéo, etc…) pour leur parallélisme et rapidité supérieure à un ASIC comportant un processeur général interprétent un logiciel, dans ces domaines et la possibilité de les mettre à jour facilement en cas de problème. Ce billet est une petite intrduction aux FPGA, au langage HDL (en anglais : « Hardware description language », signifiant, « langage de description de matériel ») standard IEEE, appelé Verilog, et la façon de le tester avec un logiciel libre à sources ouvertes (FOSS), le simulateur Verilator. Si vous désirez utiliser VHDL, GHDL est un simulateur libre équivalant VHDL.

Process complet dans le monde réel

Les étapes de l’implémentation d’un circuit sur un FGPA après sa conceptions son (des logiciels libres sont donnés comme outils de référence à chaque étape) :
* Implémentation de la logique (dans un HDL), donc Verilog ici, n’importe quel éditeur de text ou de programme peut être utilisé (j’utilise les très ampibien VIM et NeoVIM et parfois Geany, un environnement de programmation à interface très léger, mais également très puissant.
* SimulationVerilator ici, permettant de faire des bancs d’essai (anglais : testbench, parfois traduit en banc de test). C’est également un bon premier pas dans la vérification.
* Vérification formelleYoSYS est un système de vérification formelle et de synthèse de circuit. C’est une vérification plus formelle que celle de Verilator (vérification du temps d’execution et de la cohérence notamment). On ne va pas aborder ce point ici.
, Synthesis — créer les route et placer les blocs logiques, avec les contraites physiques et de temps (voir les principes de base des blocs logiques (CLB) et implémentations aujourd’hui) blocs depuis les sources en HDL et sortie d’un bitstream au format spécifique au FPGA utilisé, YoSYS de nouveau.
* Programmation/flashage du FPGAopenFPGAloader fait le travail sur la plupart des FPGA (utiliser la version Git si votre carte n’est pas autodetectée).
* Test dans le monde réel avec le FPGA.

On va se content de tester sur simulateur ici, pour comprendre les bases. Lorsque vous les aurez compris, vous pourrez aller plus loin, chosir un FPGA qui vous convient, et commencer à travailler avec.

Choses à connaître et comprendre

Les principaux concepts à comprendre et à savoir manipuler pour produire un circuit de FPGA de base sont:
* 0, 1 : Électricité passant par un circuit, ou non, pouvant être interprété en numérique par 1 ou 0, vrai ou faux, etc…
* Opéraeurs booléens (pour les portes logiques), qui est l’outil principal dans leurs manipulations. Vous n’avez pas besoin de notion d’électronique plus poussées. Les portes les plus basiques sont ou (OR), et (AND), non (NOT), et ou exclusif (XOR). Vous pouvez réaliser toutes ces portes en chaînant uniquement la porte non-et (NAND == AND+NOT), mais ça n’est probablement pas la façon la plus efficace de le faire. Si vous maîtrisez les propriétés algébriques des booléens cela pourra vous aider à simplifier les circuits, et donc de les rendre plus efficace, maiss ça n’est pas indispensable pour réaliser un circuit.
* Additionneur (ALU) (arithmetics logical unit), permettant des simples additions
* Bascule D (flip-flop) : Circuit spécial utilisé pour mémoriser les infromations (états) et les changer en fonction du signal d’enetrée et des changements d’état de l’horloge.
* Verrou (Latch) : Circuit logique séquentiel ne dépendant pas de l’horloge
* Horloge (Clock) : Donnant le rythme du temps. Les opérations sont dépendantes du temps, et ont besoin d’être synchronisées pour avoir un comportement cohérent. Les horloges sont comme un percussionniste dans un groupe de musique, Ils donne le rythme de base nécessaire à toute construction relative au temps. Les autres instruments, les chanteurs et les danceurs suivent son signal de temps. Appellés signaux d’horloge ici.
* Multipléxeurs (Multiplexers or MUX) qui peuvent aggréger différents signaux en un seul, et les démultipléxeurs (demultiplexer) qui font l’opposé.
Toutes ces fonctions sont intégrées dans une chaque bloc logique d’un FPGA.

Les autres parties importantes d’un FPGA sont :
* Un a quelques PLL (anglais : « Phase-locked loop », signifiant boucle, vérouillé sur la phase). Ils permettent d’avoir des signaux d’horloge secondaires, soit à des multiples de la fréquence de l’horloge principale, soit pour se synchroniser à des horloges (et donc signaux) externes.
* A Mémoire flash permettant de garder le bitstream (circuit) sur la carte après sa mise hors tension. Le bitstream sera automatiquement chargé au démarrage.
* De nombres blocs d’entrées/sorties (I/O blocks) périphériques à la puce contenant le FPGA, pour communiquer avec le monde extérieur.

Comment se programme un FPGA

Il existe trois principales méthodes pour développer un circuit de FPGA, et elles peuvent être mélangées, selon les besoins:
* Tracer le schéma avec outil graphique representant les opérateurs logiques et autres éléments de base (bascules, MUX, etc,) donnés plus haut.
* En utilisant un HDL (hardware description language), comme Verilog ou VHDL pour les plus connus. Les synthétiseurs sont là pour construire le circuit correspondant au programme en HDL, à la façon d’un assembleur pour transformer un du langage assembleur, facilement lisible par un humain en langage machine, ou comme les compilateurs ou interpréteurs des langages de plus haut niveau (C/C++, Python, Lua, JavaScript, FORTRAN, BASIC, Pascal, Camel, LISP, etc) qui les convertissent également en langage machine. Il existe également des langage de plus haut niveau pour les FPGA, tels que Chisel, un lanage HDL de très haut niveau qui est transpilé en Verilog ou VHDL, pour être ensuite synthétisé par les outils habituels.
* En utilisant OpenCL (Open Computing Language). Un langage conçu à la base pour le calcul intensif, pouvant être réparti simultanément sur les GPGPU et CPU .

Lors de l’utilisation de HDL, des outils sont donc là pour calculer automatiquement le routage du circuit pour vous. Des outils tels que VTR (Verilog To Routing) et NextPNR, optimisent également leur placement pour vous.

Symbiyosys est un outil de vérification.

OpenFPGAloader est un logiciel libre pour programmer/flasher le bitstream ainsi généré sur le FPGA lui même.

Il est possible de d’obtenir des résultats intéressants quelques heures après avoir commencé à étudier un HDL, Il permet de crée un circuit simple avec du code, proche de certains langages de haut niveau, avec des variables, constantes, registres, tests conditionnels, ainsi que des calculs de booléens des additions et des manipulations de bits et champs de bits. Il faut comprendre qu’un registre est déjà lui même un circuit de plusieurs portes logiques. Ce type d’outil est souvent construit sous forme d’ASIC au sein des blocs logiqus des FGPA, afin de gagner en performances et consommation électrique.

Verilog

Nous choisissons donc ici Verilog (Specifications officielles IEEE Std 1364-2001), c’est un langage très répandu avec de nombreux outils, VHDL est également un langage très répandu. Quoi qu’il en soit, il est toujours intéressant de regarder à des alternatives qui pourraient avoir des aspects intéressants. Je l’ai choisi comme langage de démarrage, parce qu’il est utilisé dans les quelques projets et exemples qui m’intéressent, et parce qu’il y a tous les outils en Logiciels libres et open source (FOSS) nécessaire pour écrire, simuler, vérifier, ainsi que différentes implémentation légères du processeur RISC-V pour l’embarqué (SERV and PicoRV32 (Github) (dont l’implémentation pour Sipeed Lichee Tang (carte FPGA à 20 €) implementation (J’ai personnellement choisi la Sipeed Tang Nano 4K (à 12 €) comme première carte, la Tang Nano coûte 3 € mais n’a que 1000 blocs logiques). Il y a également le très pratique et efficace simulateur Verilator et l’optimiseur de route VTR (Verilog to Routing.

Valeurs

Par défaut, les nombres sont des entiers en Verilog. Il est possible de forcer le nombre de bits (en le précisant avant le caractère apostrophe ') et de le choisir signé (caractère s, signé signifiant pouvant voir un signe négatif) ou non-signé (c’est le cas par défaut). En binaire b, octal o, hexadécimal h, ou encore de forcer décimal d (c’est déjà le cas par défaut). Les données peuvent également être données sous forme de chaînes de caractères.

32'hFFFF00FF  // Valeur RVBA 32 bits non signée en hexadécimal, correspondant à du jaune opaque ( rouge+vert+alpha à 100 %)
8'b00101010   // 42 (binaire non-signé sur 8 bits)
8'b0010_1010  // 42 (binaire non-signé sur 8 bits) version plus facile à lire
8'sb1010_1010 // -86 (binaire signé sur 8 bits)
-8'sd42       // -42 (décimal signé sur 8 bits)
12'o0754      // -rwxr-x-r-- pour les droits (octal non signé sur 12 bits) sur les systèmes de fichiers compatible Unix.
"Hello"       // Une chaine de caractères

Les constantes peuvent être définies en les précédent par une apostrophe suivie du terme define : 'define:

'define mavaleur -8'sd42;

Types

Il existe deux classes différentes de types réseau et logique. Nous ne couvrons ici que :
* wire, qui est de type réseau, implémente juste un fil électrique (wire) pour lier deux composants.
* reg, qui est de type logique, qui peut être défini ou indéifini pour mémoriser des valeurs (comme un registre).

wire w; // un simple fil, appelé w, entre 2 composants
reg r0; // Une mémoire (registre) de 1 bit appelé r0 (registre 0)

La valeur d’un wire est modifiée dynamiquement par les composants qui lui sont liés, mais il est également possible de leur donner une valeur permanente en dur avec la fonction assign :

assign a = 1;

Il existe également des types de plus haut niveau qui agglomèrent plusieurs des éléments précédents, comme par exemple :

integer i,j; // Deux entiers 32 bis signés, appelés i et j
real num;    // Un nombre flottant 64 bits appelé num
time t;      // Un nombre entier non-signé 64 bits représentant le temps (en signaux d'horloge)
realtime rt; // Un nombre flottant 64 bits représentant le temps

À propos du temps, il est intéressant de savoir que le terme #entier peut être utiliser pour attendre n signaux d’horloge. Il ne faut pas mettre ici de point-virgule (;) après la valeur, mais à la place, sur la même ligne, l’instruction qui sera executée après le délai définit. Par exemple :

#20 out = a & b;// Attend 20 signaux, puis calcule la valeur de a ET b

Les éléments de bit simples, wire et reg peuvent être organisés en vecteurs, les bits sont alors notés par convention, du MSB (anglais : « most significant bit » traduit par, « bit de plus fort poids » ) au LSB (anglais : « less significant bit », traduit par : « bit de plus faible poids »).

wire [7:0] Bus0; // Un bus 8 bits appelé Bus0 et connecté au système
reg [31:0] R0;   // un registre 32 bits appelé R0

Il est possible d’utiliser des expressions arithmétiques pour les définire, dans ce cas, l’utilisation de tableaux (voir ci-dessous) est plus pertinent:

reg[8*256:1] texte;   // Une chaîne de caractères de 256 octets. Pour simplfier son utilisation nous commencons à 1, comme nous n'y accéderons pas bits à bits
reg[16*16-1:0] sprite; // un sprite monochrome de 16×16 pixels, il est mieux d'utiliser -1 et de commencer par 0 dans ce cas

Après ces déclarations on peut accéder à des bits en particuliers ou des groupes de bits de la façon suivante :

Bus0[0];   // Accès au LSB de Bus0
Bus0[7:5]; // Accès aux 3 MSB de Bus0 (défini comme 8 bits de 0 à 7)

Verilog permet également de concaténer plusieurs ensembles de bits vers un autre. Par example, ici, on convertir une valeur 16 bit encodée au format de donnée Little Endian en valaur 16 bit au format Big Endian:

BigEndian[15:0] = {LittleEndian[7:0], LittleEndian[15,8]};

Si le bus de destination est de 16 bits ici [15:0] n’est alors pas nécessaire, puisque cela correspond à l’ensemble des bits fournis en entrée (8 + 8).

Et tous ces types peuvent être organisés en tableaux.

Tous les types complexes (dans le sens évolués, pas dans le sens mathématique) et vecteurs peuvent être organsiés en tableaux.

reg[7:0] curseur[0:7];            // Bitmap nonochrome d'un curseur de 8x8 pixels. Ici, curseur est le nom donné
reg[15:0] sprite[0:15];          // Le bitmap monochrome d'un sprite de 16*16 pixels
reg[31:0] palette[0:15];         // Palettes de 16 couleus RGBA8 (RVBA8)
reg[31:0] FrameBuffer[0:307199]; // Un tampon graphique d'écran de 640×480 pixels RGBA8

Les tableaux, contrairement aux vecteurs de bits, sont notés de leur adresse la plus basse à leur plus haute.

Portes

Voicil les portes (et opérateurs) logiques disponibles dans ce langage, ils opèrent sur un bit, mais on peut l’appliquer aux bits d’un vecteur en parallèle:

~a     // NON (NOT)
a & b  // ET (AND)
a | b  // OU (OR)
a ^ b  // OU-x (XOR)
a ~^ b // X-N-OU (XNOR), peut également être écrit ^~

tables de vérité des portes logiques

Il exxiste également deux opérateurs de décalage de bits :

>> // Décalage à droite
<< // Décalage à gauche

Modules

Verilog est organisé par module (un peu comme des classes en programmation objet, ou bien un circuit intégré sur un circuit imprié). Un module comporte :
* Un nom de module
* Son interface entre l’intérieur et l’extérieur du module (penser aux variables ou méthodes publiques en programmation objet, ou aux branches d’un CI en électronique), est une liste de ports définis entre parenthèses comme pour les fonctions dans les langages fonctionnels module module_name (wire a, wire b, wire out); ... endmodule.
* Par défaut, les déclarations sont des blocs logiques exécutés en parallèle.
* Pour éxecuter du code séquentiellement et conditionnellement, les sous-blocs always (et initial dans les bancs d’essai) doivent être utilisés (vir plus bas pour d’avantage de détails).

Exemple simple, écrire une porte « et »

AND
Les opérateurs de bases sont déjà présent en Verilog, mais voici comment ils pourraient être écrits. Dans les fonctions booléennes de Verilog, l’argument de gauche est la sortie (valeur retournée). Une porte « et » (AND), peut donc être réimplémentée de la façon suivante :

module and(f,a,b); // Le caractère ; n'est pas une erreur ici
  output f;
  input a,b;

  assign f = a & b; // ^ est une porte AND
endomdule

Cet exemple devrait être mis dans un fichier nommé "and.v".
* Le type par défaut est wire
* Depius Verilog 2005, les entrées (input) et sorties (output) peuvent être placer dans l’entête du module comme ceci, vous trouverez les 2 versions selon les goûts des développeurs :

module and(output f, input a,b);

Cet exemple est une porte permanente inconditionnelle, il est possible de les utiliser pour faire des circuits complets, mais Verilog est une langage de haut niveau, permettant de programmer des choes plus complexes de façon simple. Les blocs always pour des porticiels (anglais : gatewares) plus complexes.

Blocs initial et always

Il existe deux types de blocs à connaître pour commencer Verilog :
* Les blocs always (toujours) peuvent être synthétisés, et donc utilisés dans les circuits et sont executés à chaque fois que les conditions sont remplies, donc déclenchés par un évenement (en anglais : event), donné entre parenthèses always @(event).
* Les blocs initial (initial) ne peuvent être synthétisés, ils sont donc utilisés uniquement dans les bancs d’essai et exécués une seule fois et inconditionnellement. Ils permettent de donner des condtions et valeurs initiales au test. Il est possible d’avoir plusieurs blocs initial. Cela permet également de tester ou prototyper une fonction simple rapidement grace à la sortie texte du vérificateur/simulateur, avant de le mettre dans un bloc always d’un circuit.

Ils permenttent tous deux de :Front montant (Posedge) et descendant (negedge) sur un signal d'horloge
* exécuter un code séquentiel
* des conditionnels, if else
* des boucles while, for, etc

Un élément typique pour déclencher un always peut être par exemple :
* Un front montant d’horloge (positive clock edge (posedge)
* Un front descendant d’horloge negative clock edge (negedge)

always @(posedge clk)
  ...
end

Mais peut également être un test logique conditionnel :

always @(a or b)
  ...
end

Exemple simple en Verilator

Un exemple simple fourni avec Verilator (j’ai traduit les commentaires) :

module our (clk);
  input clk;  // L'horloge est requise pour obtenir une activation initiale
  always @(posedge clk)
    begin $display("Hello World!"); $finish;
  end
endmodule

Ici, le bloc always est déclench au premier front montant (posedge), il sort à l’écran ($display) une chaîne de caractères « Hello World! », puis termine ($finish) la simulation.

Il est posssible de tester et valider de nombreuses choses avec Verilator avant d’aller plus loin dans le processus de création du FPGA (vérification formelle, conversion en bitstream (qui peut être très long) et écriture sur le FPGA). Une bonne façon de démarrer est de faire une copie de l’exemple donné ci-dessus, puis de modifier le fichier top.v en jouant avec.

Effectuer le test avec Verilator

Les exemples sont installés sur :
* Arch Linux ou Manjaro, dans : /usr/share/verilator/examples/
* Debian ou Ubuntu, dans : /usr/share/doc/verilator/examples/

Donc pour les tester, copier simple le dossier dans votre répertoir personnel ou de travail, où vous avez un accès en écriture, puis construisez le modèle et executez le avec un simple make (Verilator le transpile en C puis le compile, ce qui permet d’avoir une simulation très rapide):

Sur les distributions basés Arch Linux :

cp -a /usr/share/verilator/examples/make_hello_c ~/

Sur les distributions basées sur Debian:

cp -a /usr/share/doc/verilator/examples/make_hello_c ~/

Puis, sur ma distribution:

cd ~/make_hello_c
make

make permet de reconstruire depuis les sources si nécessaire, et exécute immédiatement le banc d’essai.

La sortie du banc est de ce type:

-- RUN ---------------------
obj_dir/Vtop
Hello World!
- top.v:11: Verilog $finish
-- DONE --------------------

Le reste du texte est à propos de la compilation et une proposition d’uitliser le tuto suivant.

Tracer l’exemple avec GTKWave

GTKwave est un outil compagnon à Verilator. Il permet de voir et vérifier le chronographe résultant de la simulation, afin de comprendre le fonctionnement temporel du circuit, et de visualiser ce que pourrait être la source d’un problème possible. Il utilise le fichier .vcd (Value Change Dump), pour tracer. Ce fichier est utilisé comme source pour tracer le chronogramme (Le résultat de sortie de make_tracing_c se trouve dans le sous-répertoire logs/. Le fichier est logs/vlt_dump.vcd, crée après avoir utilisé la commande make).

Pour utiliser l’exemple de tracé avec GTKwave, vous devez avoir installé le paquet GTKwave, puis, comme dans le premier exemple, avoir dupliqué le dossier dans un répertoire dans lequel vous avez les droits en écriture:
* Sur les distributions basées sur Arch Linux :

cp -a /usr/share/verilator/examples/make_tracing_c ~/

* Sur les distributions basées sur Debian :

cp -a /usr/share/doc/verilator/examples/make_tracing_c ~/

Puis, dans tous les cas :

cd ~/make_tracing_c
make
gktwave logs/vlt_dump.vcd

GTKwave
La sortie texte pendant la phase make fournit également des informations intéressantes :

-- RUN ---------------------
obj_dir/Vtop +trace
[1] Tracing to logs/vlt_dump.vcd...

[1] Model running...

[1] clk=1 rstl=1 iquad=1234 -> oquad=1235 owide=3_22222222_11111112
[2] clk=0 rstl=0 iquad=1246 -> oquad=0 owide=0_00000000_00000000
[3] clk=1 rstl=0 iquad=1246 -> oquad=0 owide=0_00000000_00000000
[4] clk=0 rstl=0 iquad=1258 -> oquad=0 owide=0_00000000_00000000
[5] clk=1 rstl=0 iquad=1258 -> oquad=0 owide=0_00000000_00000000
[6] clk=0 rstl=0 iquad=126a -> oquad=0 owide=0_00000000_00000000
[7] clk=1 rstl=0 iquad=126a -> oquad=0 owide=0_00000000_00000000
[8] clk=0 rstl=0 iquad=127c -> oquad=0 owide=0_00000000_00000000
[9] clk=1 rstl=0 iquad=127c -> oquad=0 owide=0_00000000_00000000
[10] clk=0 rstl=1 iquad=128e -> oquad=128f owide=3_22222222_11111112
[11] clk=1 rstl=1 iquad=128e -> oquad=128f owide=3_22222222_11111112
[12] clk=0 rstl=1 iquad=12a0 -> oquad=12a1 owide=3_22222222_11111112
[13] clk=1 rstl=1 iquad=12a0 -> oquad=12a1 owide=3_22222222_11111112
[14] clk=0 rstl=1 iquad=12b2 -> oquad=12b3 owide=3_22222222_11111112
[15] clk=1 rstl=1 iquad=12b2 -> oquad=12b3 owide=3_22222222_11111112
[16] clk=0 rstl=1 iquad=12c4 -> oquad=12c5 owide=3_22222222_11111112
*-* All Finished *-*
- sub.v:29: Verilog $finish
[17] clk=1 rstl=1 iquad=12c4 -> oquad=12c5 owide=3_22222222_11111112

On peut voir ici, chaque étape de l’exécution avec l’horloge (clk) oscillant entre l’état 0 et l’état 1.

Le temps est en tic d’horloge, et celle du banc d’essai peut être défini par la directive timescale, permettant un temps granulaire de plus grande précision. Cela ne nous est pas utile pour le moment.

Le fichier de trace .vcd (Value Change Dump) est défini dans top.v:

$dumpfile("logs/vlt_dump.vcd");

Toutes les étapes se trouvent dans le dossier logs/annotated/ et le fichier de rapport (coverage) dans logs/coverage.dat:

-- COVERAGE ----------------
verilator_coverage --annotate logs/annotated logs/coverage.dat
Total coverage (2/31) 6.00%
See lines with '%00' in logs/annotated

-- DONE --------------------

Le dernier est définit dans sim_main.cpp.

À propos des exemples de Verilator

Dans chaque exemple, il y a un petit code d’interface en C++. La modification du code C++ n’est pas nécessaire pour les tests de base. Lorsque les concepts de bases sont acquis, la partie C++ peut être adaptée pour s’interfacer avec des bibliothèques système, et permetter de simuler les échanges avec des périphériques de communications, audio, graphisme, vidéo etc.

Dans ces simples exemples d’introduction, les plus intéressant pour débuter sont tous disponible en Verilog :
* make_hello_c Hellow World Verilog avec Makefile
* make_hello_sc Hello World Verilog utilisant SystemC et un Makefile
* cmake_hello_c Hello World Verilog avec cmake.
* cmake_hello_sc Hello World Verilog utilisant SystemC et cmake.

SystemC est un ensemble de classes et macros C++ fournissant une interface de simulation pilotée par les évenements, simule les processus concurrents dans un environnement temps-réel.

Les autres exemples sont (avec leurs variantes cmake|make et c|sc) :
* make_tracing_c pour le tracage de l’execution (comme dit au dessus avec l’exemple GTKWave).
* make_protect_lib pour créer une bibliothèque protégée DPI (Direct Programming Interface), une interface entre SystemVerilog et des fonctions dans un langage comme C ou C++.

System Verilog est une évolution de Verilog, c’est donc un HDL avec d’avantage de fonctionnalités, et également classé comme un HVL (Hardware Verification Language), il ajoute différents types et blocs séquenciels, et permet la programmation orientée objet avec une syntaxe proche de C sur certains points.

Quelques extensions de fichiers que vous trouverez dans le domaine de Verilog, System Verilog et simulateurs liés sont :
* .v comme Verilog.
* .vc (verilog ???) ou .f comme File, utilisé pour les projets importants, comportant des argiments à passer à Verilator (ou un autre simulateur) tels que les drapeaux (flags), des dossiers d’include, et des bibliothèques liées pour la simulation.
* .vcd comme Value Change Dump file, contient les sorties de traces de la simulation pour leur analyse (voir au dessus).
* .vo comme Verilog Output file.
* .sv comme System Verilog,

Exemple pratique de base avec Verilator


Nous allons donc réutiliser le simple exemple avec Make (vous pouvez choisirles exemples avec cmake à la place, dans ce cas, vous n’aurez qu’à invoquer la commande cmake à la place de la commande make.

J’ai dans ce cas, copié l’exemple make_hello_c dans un repertoire avec droits en écriture :

cp -a /usr/share/verilator/examples/make_hello_c verilator_test
cd verilator_test

puis ai édité top.v ( vim, emacs, gedit, ou n’importe quel éditeur de texte de votre choix fait l’affaire), puis remplacé son contenu.

Voici un simple exemple que j’ai utilisé pour mes tests. Je voulais comprendre comment fonctionnait les accès aux registres Verilog simples, et à différent types de données, dont un sprite (celui affiché à côté du titre de cette section), vous povuez décharger cette version de top.v ici. Je traduis ici les commentaires :

module top;
 reg [8*11:1] str1;
 reg [8*25:1] str2;  // remplit à gauche par des espaces
 reg a,b,c;
 reg [7:0] sprite[0:7];  // utilise un octet par ligne
 reg [15:0] sprite2;  // sprite sous forme de champs de bits
 integer i;

 initial begin
   str1 = "Hello World"; // tests d'initialisations de chaîne de caractère
   str2 = "Hello World";

   a = 1'b1; // Tests d'initialisation de bits
   b = a^1;
   c = a^0;

   sprite[0] = 8'b10011000; // Tests d'initialisation de sprites
   sprite[1] = 8'b00100100;
   sprite[2] = 8'b01000010;
   sprite[3] = 8'b10011001;
   sprite[4] = 8'b10011001;
   sprite[5] = 8'b01000010;
   sprite[6] = 8'b00100100;
   sprite[7] = 8'b00011000;

   sprite2[ 7:0] = 8'b10011000;
   sprite2[15:8] = 8'b00100100;

   $display ("str1 = %s", str1); // Affichage des chaînes de caractères
   $display ("str2 = %s", str2);

   $display ("a = %d", a);       // Affichage des bits
   $display ("b = a^1 = %d", b);
   $display ("c = a^0 = %d", c);

   for ( i=0; i<8; i=i+1) begin  // Affichage des sprites
    $display ("sprite[%2d] = %b",i,sprite[i]);
   end
   for ( i=0; i<2; i=i+1) begin  // essaie de lire des rangées de bits, en tantant des sorties du domaine
    $display ("sprite2[8*i(%1d) +: 8] = %8b",i,sprite2[8*i +:8]);
    $display ("sprite2[8*i(%1d) -: 8] = %8b",i,sprite2[8*i -:8]);
    $display ("sprite2[4*i(%1d) -: 8] = %8b",i,sprite2[4*i -:8]);
   end
 end 
endmodule

Pour le tester, lancer juste la commande make. Ici, les commentaires en bleu sont des commentaires de la sorti uniquement dans ce billet de blog, il ne seront pas visible à l’execution:

make
-- VERILATE & BUILD -------- 
[...]  Cette partie est le log de compilation, je ne l'ai pas collé
-- RUN ---------------------   C'est ici que c'est intéressant
obj_dir/Vtop
str1 = Hello World
str2 =               Hello World    str2 de 25 caractères rempli par des espaces
a = 1
b = a^1 = 0
c = a^0 = 1
sprite[ 0] = 10011000    Huits lignes du sprite
sprite[ 1] = 00100100
sprite[ 2] = 01000010
sprite[ 3] = 10011001
sprite[ 4] = 10011001
sprite[ 5] = 01000010
sprite[ 6] = 00100100
sprite[ 7] = 00011000
sprite2[8*i(0) +: 8] = 10011000      Quelques acces de tests à des plages de bits
sprite2[8*i(0) -: 8] = x0010010      Nous sortons des domaines du registre ici, des x sont affichés
sprite2[4*i(0) -: 8] = x0010010
sprite2[8*i(1) +: 8] = 00100100
sprite2[8*i(1) -: 8] = 01001100
sprite2[4*i(1) -: 8] = xxxxx001

Pour aller plus loin

Documentation relativement complètes en ligne :

À propos de Verilog :
* Anglais : Un manuel Verilog complet sur Chip Verify
* Anglais : Tutorial Verilog pour les débutants sur ReferenceDesigner.com
* Anglais : Survol de Verilog en quelques écrans de présentation (format PDF) sur euler.ecs.umass.edu
* Anglais : Verilog Quick Reference Card.pdf
* Anglais :
Verilog HDL Quick reference Guide
* Français : Syntaxe de Verilog à hdl.telecom-paristech.fr

Bancs d’essai en Verilog, SystemC et System Verilog:
* Anglais : Comment écrire un banc d’essai de base en Verilog, en utilisant un template en C++. Ce site, FPGA tutorial, à trois sujets principaux, VHDL, Verilog & System Verilog.
* Anglais : Tutoriels sur Systemverilog et SystemC.
* Anglais : Quelques tutoriels sur System Verilog.
* Français : SystemVerilog en 13 minutes sur sen.enst.fr
* Verilog Simulation with Verilator and SDL Utilisation de la bibliothèque SDL pour simuler une sortie graphique VGA, avec Verilator. Utilise Verilog et un peu de SystemVerilog.

Quelques projets FPGA intéressants:
* Anglais : From Nand To Tetris (De Nand à Tetris), toutes les étapes pour faire, en quelques exercices, un ordinateur basé sur un CPU 16 bits, en n’utilisant que des portes NAND (jeu intéressant et formateur), puis construction d’un jeu Tetris avec ce petit système. Ils utilisent un HDL développé par cette université, mais il existe déjà des ports, comme exemples, comme celui-ci en Verilog utilisant Ikarus Verilog simulator (également logiciel libre de simulation, c’était la référence avnat Verilator), un autre en Verilog pour le Lattice ICE40 FPGA, dont tous les outils pour la programmation sont libres (testé avec une carte en matériel libre/ouvert Olimex) et des outils open source (YoSYS comme synthétiseur), encore en Verilog pour la carte De0-nano (utilisant Altera Cyclone IV FPGA).
*
Implementation de UART/port série/interface RS-232 pour FPGA (versions Verilog et VHDL).
* Consolite, une console de jeu légère en Verilog pour FPGA, programmable en assembleur. Un émulateur est également disponible.
* FloPoCoUnité de caclculs arithmétique à virgule flottante pour FPGA dévelopée par l’INRIA.
* Anglais : Tang nano MIDI Sounder, un synthétiseur MIDI, à la sonorité 8 bits sympa.
* Anglais : Speech256, un synthétiseur vocal.
* Japonais : Processor RISC-V SERV pour Tang Nano
* Japonais : Tetris pour Tang Nano en Verilog
* ZipCPU Un blog pour le CPU ZipCPU avec un des exemples de banc d’ssai Verilator intéressant, utilisant des patrons C++ pour des bancs d’essai génériques.

Pour les hackers amoureux des logiciels libres :
* Apicula project, un projet d’ouvertur d’ouverture des bitstream des FPGA Gow1n.