Category Archives: Uncategorized

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

The text in strong are here to help diagonal reading.

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++.

fSystem 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.
* 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.

Linux syscall and RISC-V assembly

Sample of RISC-V assembly code

Syscall in Linux kernel, is an interface to access to kernel basic functions. They are described in section 2 of man pages. The introduction is in man 2 syscall (indirect system call), and the list of functions are described in man 2 syscalls.  »’Update: »’ System Calls in lectures of official Linux kernel documentation including « Linux system calls implementation », « VDSO and virtual syscalls » and « Accessing user space from system calls »

This article follow previous one about RISC-V overall progress and available tools to play with, I will try to make a short article here about Linux syscall usage and the RISC-V assembly case.

Table of Content

* Description section of the man page
* Getting the list of function and how to access them
* Passing parameters
* Function number and registers of return values
* Return values and error code
* Compiling and executing on virtual environment
* Update: Bronzebeard assembler and its baremetal environment for real hardware

Description section of the man page

* syscall() is a small library function that invokes the system call whose assembly language interface has the specified number with the specified arguments. Employing
* syscall() is useful, for example, when invoking a system call that has no wrapper function in the C library.
* syscall() saves CPU registers before making the system call, restores the registers upon return from the system call, and stores any error returned by the system call in errno(3).
* Symbolic constants for system call numbers can be found in the header file .

You can find here function, like access to files open/close/read/write/flush, access to sockets, ioctl, uid, gid, pid, messages, ptrace, restart system, etc…

Getting the list of function and how to access them

As far I know, now only a part of syscall functions are accessible easily in assembly, they are defined in /usr/include/unistd.h, and function numbers assigned in ABI are defined in /usr/include/asm-generic/unistd.h.

The more practical match I found is using /usr/include/asm-generic/unistd.h to see which function are available and there respective manpage for the function header definition. For example:
* asm-generic: #define __NR_read 63
* man 2 read: ssize_t read(int fd, void *buf, size_t count);

The ABI with RISC_V as defined in man 2 syscall in section Architecture calling conventions use the registers following 2 tables rules.

Passing parameters

The second table in this section of the man page shows the registers used to pass the system call arguments.

Arch/ABI      arg1  arg2  arg3  arg4  arg5  arg6  arg7  Notes
──────────────────────────────────────────────────────────────
riscv         a0    a1    a2    a3    a4    a5    -

Here are the arguments in the order of the function definition, for example, in read (63) function:

ssize_t      read  ( int fd, void *buf, size_t count );
a0(result) = a7(63)( a0(fd),  a1(*buf),    a2(count) )

For remember, 3 standard I/O file descriptors are STDIN=0, STDOUT=1, STDERR=2, the other are used when opened a file with open and closed by close.

So we set the arguments as this. x0 is the always 0 register:

        addi  a0, x0, 0       # Set STDIN as sources
        la    a1, buffer_addr # load address of helloworld
        addi  a2, x0, 3       # reaad 3 bytes

Function number and registers of return values

And the first one give the register in which put the function number, that will receive return value and errno (error value)

Arch/ABI    Instruction           System  Ret  Ret  Error    Notes
                                  call #  val  val2
───────────────────────────────────────────────────────────────────
riscv       ecall                 a7      a0   a1   -

So for the read function, we need to put 63 (as found in /usr/include/asm-generic/unistd.h in register a7, and registers and following registers will receive the return values, a0 will receive system call result, and a1 an error message (the errno value).

ssize_t read(int fd, void *buf, size_t count);
a0    =  63 (    a0,       a1,         a2)

So can set them as this:

        addi  a7, x0, 63     # set called function as read()
        ecall                # call the function

Return values and error code

After the man page of read(2):
* On success, the number of bytes read is returned
* On error, -1 is returned, and errno is set to indicate the error.

So we can test first the return value of a0 and if a0 < 0 then we jump to part for display the error message, else we can simply display a OK message. So for the branching part, RISC-V in its super reduced set only have < (lt) and <= (le) comparators, you just need to swap registers to compute > (gt) and >= (ge), but this avoid lots more of transistors.

    addi a3,x0,0           # x3=0
    blt  a1,a3, error_seq  # if x1<0 branch to error_seq

We so use here the syscall write function (64) defined as:
* asm-generic: #define __NR_write 64
* man 2 write: ssize_t write(int fd, const void *buf, size_t count);
* So, registers: a0=1 (STDOUT), a1=*buf, a2=count, a7=64 (function number)

And we will finish with exit() syscall, defined as:
* asm-generic: #define __NR_exit 93
* man 2 exit: noreturn void _exit(int status);
* So, registers: a0=return code, a7=93 (function number)

    la    a1, ok           # load address (pseudo code) of ok string
    addi  a2, x0, 3        # set length of text to 3 (O + K + \n)
    addi  a7, x0, 64       # set ecall to write function
    ecall                  # Call the function

    addi  a0, x0, 0        # set return code to 0 (OK) for exit (93) function
    j     end              # unconditional jump to end before quit

error_seq:
    la    a1, error        # load address (pseudo code) of error string
    addi  a2, a2, 0x30     # add 0x30 (0 ASCII code) to the error code
    sb    a2, 7(a1)        # put the (byte) value at position 7 of Error string (before \n)
    addi  a2, x0, 10       # set now length of our string
    addi  a7, x0, 64       # set ecall to write function
    ecall                  # Call the function

    addi  a0, x0, -1       # set return code to -1 (error) for exit (93) function
end:
    addi    a7, x0, 93     # set ecall to exit (93) funciton
    ecall                  # Call linux to terminate the program

.data:
ok:     .ascii "OK\n"
error:  .ascii "Error:  \n"

RISC-V Longan nano

Compiling and executing on virtual environment

If you don't have a RISC-V hardware (can be found as low as 3€ now), you need to have a cross compiler and qemu for emulating instructions, or a whole system installed.

Packages needed for compiling on ArchLinux x86 or ARM for example.

sudo pacman -S riscv64-linux-gnu-gcc  riscv64-linux-gnu-glibc riscv64-elf-binutils riscv64-elf-binutils riscv64-elf-gcc riscv64-elf-gdb

Newlib is a lightweight RV32 (RISC-V 32bits) lightweight library for bare metal that can be used instead of a whole GNU system on embedded devices with low memory capacity (as Longan nano, less than 8€ with screen, see picture below, or 3€ Sipeed RV): riscv32-elf-newlib.

I made a simple shell script to don't have to remember the commands to assemble the code from an x86 platform (work also on ARM or RISC-V one) that take the .s as argument:

name=$1
riscv64-linux-gnu-as -march=rv64imac -o ${name}.o ${name}.s
riscv64-linux-gnu-ld -o ${name} ${name}.o

You can add a strip but better to avoid it if you need to debug it:

riscv64-elf-strip --strip-all ${name}

And it can be executed on non RISC-V platforms by using qemu-riscv64, if it doesn't depend on libraries or if you have them installed, it allow you to test it without having a full RISC-V system installed, qemu is so fantastic. On ArchLinux it is available in package qemu-arch-extra:

qemu-riscv64 ${name}

And can be disassembled (will probably use different instruction than your assembly code, due to RISC-V assembly pseudo-instructions:

riscv64-linux-gnu-objdump -d ${name}

Bronzebeard assembler and its baremetal environment for real hardware

RISC-V Longan nano

Update: Bronzebeard is an assembler with light baremetal environment builder for RISC-V, GD32VF103 as Longan Nano (about 8€ with a screen, as pictured on this article pictures) and Wio (similar board with an added ESP8266 SoC). I made an AUR package of Bronzebeard, and someone made a Mandelbrot set demo in 918 bytes pure RISC-V assembly. You can find some other example in the source of Bronzebeard. gd32vf103inator is a set of tools for GD32V, to manage from a simple random text editor.

Bugs in firefox 52=>53 on ArchLinuxARM 32 bits (ARMv7h) and how to still use it

Firefox 53 currently doesn’t compile on ARMv7h, so only firefox 52 works on ALARM/armv7h, but as the 52 package is no more in git current version, ALARM compiling sysyem doesn’t compile it with updated dependencies (ICU moved from 58 to 59 and hunspell updated too. I didn’t managed to compile firefox-esr.

I compiled former version of this too libs to be able to make firefox 52 works again.
You can find both icu-58 and hunspell 1.5.4 packages here. PLEASE DON’T INSTALL THEM, you can reinstall firefox package itself if needed with pacman -U firefox-52.0.2-1-armv7h.pkg.tar.xz
instead unarc them in a directory like this :

cd /tmp
mkdir unarc; cd unarc
wget https://popolon.org/depots/ArchLinuxARM/firefox/52/hunspell-1.5.4-1-armv7h.pkg.tar.xz
wget https://popolon.org/depots/ArchLinuxARM/firefox/52/icu-58.2-1-armv7h.pkg.tar.xz
tar xf hunspell-1.5.4-1-armv7h.pkg.tar.xz # lot of errors with SCHILY.fflags will be displayed
tar xf icu-58.2-1-armv7h.pkg.tar.xz # lot of errors with SCHILY.fflags will be displayed
cd usr/lib
sudo rsync -a libhunspell-1.5.so* libicu.so.58* /usr/lib/
sudo rsync -a icu/58.2 /usr/lib/icu/
ldconfig

That’s done. You can now type firefox to launch it :)

Créer des partitions disque alignées pour améliorer les performances, et réduit l’usure des SSD

Aligner les partitions d’un disque sur ses cylindres, c’est, sachant que les données les plus fréquentes tiennent sur un cylindre :

* Éviter de lire deux cylindres lorsqu’un seul est utile => gain de temps à la lecture, réduction de l’utilisation inutile des caches, gain de bande passante.
* Éviter d’écrire deux cylindres lorsqu’un seul est suffisant => gain de temps, réduction de l’utilisation inutile des caches, gain de bande passante et moins d’usure
* Pour la raison précédente, permet de prolonger grandement la vie d’un SSD (le nombre de cycle est limité sur un SLC, encore plus sur un MLC, et beaucoup plus sur un TLC, mais le prix de ce dernier est le plus bas au Go).

Un moyen simple de savoir si vos partitions sont alignés sur les cylindres

Lancer cfdisk sur le disque qui doit être optimisé (remplacez /dev/sda, par le disque à optimiser : /dev/sdb, /dev/sdc…) :

cfdisk /dev/sda

Si vous voyez une astérisque (ou étoile) tout à fait à droite, la partition n’est pas alignée sur les cylindres. Dans mon cas, ce SSD est à refaire complètement (sauf sda3) :

                           cfdisk (util-linux 2.20.1)

                           Unité disque : /dev/sda
                   Taille : 240057409536 octets,  240.0 Go
          Têtes : 255   Secteurs par piste : 63  Cylindres : 29185

    Nom         Ind.       Partition  S. Fic.          [Étiq.]        Taille (Mo
)------------------------------------------------------------------------------
                            Pri/Log   Espace libre                         1,05*
    sda1        Amorçage    Primaire  ext4                             51158,98*
                            Pri/Log   Espace libre                         1,22*
    sda2        Amorçage    Primaire  ext4                             53686,01*
                            Pri/Log   Espace libre                         0,41*
    sda3                    Primaire  ext4                            135207,16
                            Pri/Log   Espace libre                         2,62*





     [   Aide   ]  [ Nouvelle ]  [ Afficher ]  [ Quitter  ]  [ Unités  ]
     [ Écrire  ]

           Créer une nouvelle partition à partir de l'espace libre

Comment crée une seule partition, utilisant tout le disque, et bien alignée

Grâce à des anciens mode de fonctionnement de fdisk. J’ai trouvé l’astuce sur une doc d’ubuntu.

Remplacer /dev/sdX par le disque sur lequel vous désirez créer la partition alignée :

fdisk /dev/sdX
c
u
p
n
[touche entrée]
[touche entrée]
[touche entrée]
[touche entrée]
w

Et voilà, vous verrez le beau résultat sur cfdisk

Pour créer plusieurs partitions alignées : Choisissez les bonnes options où j'ai mis quatre fois [touche entrée] et recommencer n à chaque partition.

Problème de date avec WordPress + qTranslate résolu

J’ai trouvé ici la solution pour le problème de dates mal affichées, lors de l’utilisation de WordPress avec qTranslate, un greffon pour l’utilisation des billets en plusieurs langues.

* Avant : %A %e %B %Y
* Après : jeudi 1 août 2013

Il semble y avoir encore un problème avec le chinois qui est affiché en anglais.

Il faut donc remplacer le double % dans le fichier <code>wp-content/plugins/qtranslate-*/qtranslate_utils.php</code>

$strftime_parameters[] = '%%';

par un simple %

$strftime_parameters[] = '%';