Archives par mot-clé : verilog

De la synthèse et du test FPGA grace au fediverse

[publié en premier sur Linuxfr]

Je pratique pas mal la conception sur ces drôles de bêtes que l’on appelle des FPGA. Les deux langages principaux pour décrire un composant FPGA sont le Verilog et le VHDL.

Pour développer son composant il est préférable de le simuler pour ensuite le synthétiser pour une cible et enfin le configurer et le tester «sur table».

S’il est aujourd’hui courant de simuler ses composants avec des logiciels libres comme GHDL, Icarus, NVC ou Verilator. Il est déjà plus compliqué de trouver des cibles compatibles avec les rares logiciels libre de synthèse comme Yosys ou abc.

Et ne parlons même pas des logiciels libre de placement routages comme nextpnr, vpr ou arachne-pnr (abandonné).

Mais une fois que l’on a trouvé une chaîne complètement libre pour son développement, il n’en reste pas moins qu’il faudra quand même acquérir une carte de développement pour faire ses tests réels.

On n’est pas à l’age du Claude ici, on fait pas ça sur un serveur installé on ne sais où et qui pompe toute l’eau et l’électricité des villes d’à coté…

Et bien si ! JCM sur le net (un allemand ? bon le nom de domaine est à la Réunion, allez savoir …) s’est amusé à brancher une carte de développement Icepi Zero sur un ordinateur avec sa sortie vidéo (HDMI). Sur cet ordinateur tourne un service de synthèse/placementroutage/configuration/exécution du composant en VHDL/Verilog/Amaranth que vous lui envoyez et enregistre 30 secondes d’exécution de la vidéo qui sort sur le HDMI.

Il suffit pour cela de lui demander gentiment par message mastodon privé. Il synthétisera le design, postera la vidéo et les résultats de synthèse/P&R (nombre de cellules utilisés, vitesse max de l’horloge, …) sur son compte mastodon.

Le problème c’est que les instances mastodon limitent la taille des pouets. Alors il est bien sûr possible de copier coller chaque partie du code (par blocs de 666 caractères dans le cas de piaille.fr) mais c’est fastidieux et sujet à par mal d’erreur.

C’est pourquoi, mon cher journal, je te propose un petit script python qui se sert du fantastique client en console toot pour envoyer directement le fichier hdl que tu donneras en paramètre. Tu n’auras plus qu’à attendre quelques minutes pour voir apparaître la vidéo en gif sur ta timeline mastodon.

Le script se nomme masto_synth.py et se contente du nom du fichier à envoyer comme paramètre obligatoire.

$ python masto_synth.py -h
usage: masto_synth [-h] [-M MAX_CHAR] filename

Send an HDL file to @icepi-zero-bot@wafrn.jcm.re via mastodon

positional arguments:
  filename              HDL filename

options:
  -h, --help            show this help message and exit
  -M MAX_CHAR, --max_char MAX_CHAR
                        Mastodon toot maximum chars size

need toot for mastodon posting

Bien sur on peut adapter la taille des pouets à son instance mastodon, ici c’est configuré à 500 par défaut.

Il faut avoir configuré toot au préalable pour être loggé :

$ toot login

Un fichier d’exemple est donné dans le profil de jcm que l’on peu modifier à sa guise (voir le fichier pif.vhd dans le dépôt du client).

L’envoi se fait simplement en ajoutant le chemin du fichier en unique paramètre :

$ python masto_synth.py pif.vhd

On attend quelques minutes (~3 minutes pour cet exemple) et …

On récupère les résultats avec la vidéo du fonctionnement de notre design.

La vidéo est totalement statique dans cet exemple, mais elle ouvre plein de perspectives de nuits blanches à jouer avec le kit sans même l’avoir acheté 🙂

Comment générer du SystemVerilog et/ou du Verilog avec Chisel 6

Avec les dernières version de chisel le «backend» de génération du verilog a changé.

SystemVerilog

Avec circt on peut générer en systemVerilog avec la méthode emitSystemVerilogFile() inclue dans le package:

import circt.stage.ChiselStage

//...

class MonModule extends Module {
 //...
}

object MonModule extends App {
    ChiselStage.emitSystemVerilogFile( new MonModule(),
      firtoolOpts = Array("-disable-all-randomization",
                          "--lowering-options=disallowLocalVariables",
                          "-strip-debug-info"))

}

Le fichier généré se nommera MonModule.sv

Verilog

Pour générer du Verilog il faut utiliser l’ancienne méthode que l’on trouve dans le package chisel3.

object MonModule extends App {
    val filename = "MonModule.v"
    println("Generate verilog source for ChisNesPad Module")
    val verilog_src = chisel3.emitVerilog(new MonModule)
    val filepath = os.pwd / filename
    if (os.exists(file)) os.remove(file)
    os.write(filepath, verilog_src)
}

Le choix

Et l’on peut s’ajouter une option à la ligne de commande si l’on veut avoir le choix.

import circt.stage.ChiselStage

//...

class MonModule extends Module {
 //...
}

object MonModule extends App {
  if (args.length == 0) {
  } else if (args(0) == "--systemVerilog") {
    ChiselStage.emitSystemVerilogFile( new MonModule(),
      firtoolOpts = Array("-disable-all-randomization",
                          "--lowering-options=disallowLocalVariables",
                          "-strip-debug-info"))
  }
}

On lancera les commandes suivantes pour générer l’un ou l’autre :

# générer le verilog
sbt "runMain MonModule"
# générer le systemVerilog
sbt "runMain MonModule --systemVerilog"

Getting started with FPGAS

Russell Merrick est un ingénieur en électronique qui travail sur des FPGA depuis plus de 15 ans. C’est l’auteur du site internet Nandland qui propose toute une série de tutoriels pour débuter et s’amuser avec des FPGA.

Russell vient de sortir un livre chez No Starch Press pour débuter avec un FPGA.

En seulement 280 pages, on peut dire que l’auteur couvre bien le sujet. Les deux langages HDL du «marché» sont décrits et tous les exemples sont donnés en Verilog ainsi qu’en VHDL.

C’est la première fois que, dans un livre, je vois un vrai comparatif des deux HDL. En effet, l’un est souvent balayé au profit de l’autre avec un «si vous connaissez l’un vous saurez vous servir de l’autre». Même si l’accent est mis sur le ICE40 de Lattice (Célèbre FPGA lowcost reversé dans le projet icestorm), on sent bien qu’il existe d’autres constructeurs et que l’auteur a travaillé avec.

Le livre n’est pas si gros et pourtant il traite vraiment de tout ce qu’il faut savoir pour bien commencer (et avancer) dans le FPGA.

Un chapitre entier est consacré aux bascule D (FlipFlop) et à la problématique de conception synchrone. La notion de domaines d’horloge et son franchissement, les machines d’états, les macro classique (RAM, PLL, DSP) ne sont pas en reste.

Et avant d’aborder les entrées sorties (I/O, LVDS, SerDes) un chapitre particulièrement intéressant sur l’arithmétique est abordé. Tout est dit pour additionner, soustraire, multiplier et diviser (enfin surtout les méthodes de contournement de la division) des entiers mais également des nombres en virgules fixe (Qn.m) dans un FPGA.

C’est un livre que j’aurais adoré avoir pour débuter en FPGA, mais qui fera tout de même un très bon livre de référence au besoin.

Découverte du FPGA européen, le GateMate de CologneChip

Nous l’attendions depuis au moins deux ans, le FPGA européen GateMate des allemands de CologneChip est désormais disponible dans votre crémerie habituelle.

Ça y est il est arrivé, le kit de développement GateMate !

La dimension européenne de ce FPGA n’est pas la seule nouveauté, c’est également un des premier (mais pas le premier) à privilégier les outils open source pour son utilisation. Que cela soit pour la simulation ou pour la synthèse tous les exemples donnés dans la documentation utilisent des logiciels libres (Icarus, GHDL et surtout Yosys). Même pour la visualisation des chronogrammes, gtkwave est utilisé par défaut en exemple.

[À noter que le kit de développement m’a été offert gracieusement par CologneChip]

Caractéristiques du GateMate

Les caractéristiques du GateMate le positionne au niveau d’un petit Spartan7 de Xilinx ou d’un Trion T20 de Efinix.

Le composant est gravé par GlobalFounderies en 28nm.

Architecture générale (DS1001)

La cellule de base est nommée CPE pour «Central Programming Element».

Structure du CPE (DS1001)

On notera l’absence remarqué de blocs multiplieurs. Cette absence est compensée par le «fast signal path routing» qui permet de chaîner les CPE afin de construire un multiplieur de dimension voulue.

Caractéristiques du kit

Pour le moment, seuls les trois petit FPGA de la gamme GateMate semblent être en production. CologneChip propose un kit de développement muni du plus petit GateMate : le CCGM1A1

Le schéma blocs de la carte de développement (source pdf officiel)

La carte de développement possède deux entrées USB:

  • une pour l’alimentation
  • et une pour la programmation et la communication avec le FPGA (FTDI 2232), mais qui peut également servir d’alimentation.

La magie du logiciel libre openFPGALoader permet de détecter le FPGA directement au branchement:

$ openFPGALoader --detect
Jtag frequency : requested 6.00MHz   -> real 6.00MHz  
index 0:
	idcode 0x20000001
	manufacturer colognechip
	family GateMate Series
	model  GM1Ax
	irlength 6

En effet, avant même la sortie du gatemate, CologneChip avait déjà proposé le support du composant sur le dépot openFPGALoader.

Toolchain

CologneChip fourni un guide d’installation de la chaîne de développement sur son site internet.

Hormis le logiciel de placement routage (GateMate), tous les outils sont des logiciels libre bien connus du monde du FPGA (source UG1002).

Tous les outils sont connus du monde du FPGA opensource et leurs installations sont intensivement décrites dans les différents dépôts des projets.

Si l’on souhaite éviter la case compilation, l’entreprise fournie même des versions binaires. Ces binaires ne sont téléchargeable que via un compte enregistré sur leur site pour le moment. Deux paquets de logiciels sont nécessaires :

  • Yosys compilé pour le gatemate: pour la synthèse Verilog
  • p_r: le logiciel de placement routage.

Pour le moment, la version binaire proposée en téléchargement sur le site n’est disponible que pour windows. Ces «.exe» s’exécutent cependant parfaitement sous Linux au moyen de l’émulateur wine bien connu des Linuxiens.

L’archive téléchargée se décompresse simplement avec unzip :

$ unzip cc-toolchain-win.zip 
Archive:  cc-toolchain-win.zip
 extracting: cc-toolchain-win/openFPGALoader-mingw64-v.0.8.0+80eeaef.zip  
 extracting: cc-toolchain-win/p_r-2022.04-001.zip  
  inflating: cc-toolchain-win/ug1002-toolchain-install-2022-04.pdf  
 extracting: cc-toolchain-win/yosys-win32-mxebin-0.15+57.zip  

La version windows de openFPGALoader ne fonctionne pas bien en émulation wine, il est préférable d’en compiler une version à jour à partir des sources officiels.

Pour le reste, on peut s’affranchir de compiler yosys en utilisant celle fournie. Et pour p_r, les sources n’étant pas fournies pour le moment, cette version est la seule que nous pourrons utiliser.

Pour les installer, il suffit de les décompresser :

$ cd cc-toolchain-win/
$ unzip p_r-2022.04-001.zip
$ unzip yosys-win32-mxebin-0.15+57.zip

La notice d’utilisation des commandes est données dans le pdf de l’archive nommé ug1002-toolchain-install-2022-04.pdf.

C’est l’installation la plus simple que j’ai pu avoir à faire pour des outils de développement FPGA. La place occupée sur le disque dur de son ordinateur est plusieurs milliers de fois plus petites que les logiciels habituels :

$ cd p_r-2022.04-001/
$ du -sh .
24M	.
$ cd yosys-win32-mxebin-0.15+57/
$ du -sh .
32M	.

Évidemment, c’est pour seulement un modèle de FPGA, mais cela reste beaucoup plus petit.

Clignotons

Il est temps de rentrer dans le vif du sujet et de faire clignoter les LED. L’exemple donné dans le document ug1002 est trop rapide pour voir les LED clignoter. Nous allons donc faire un clignoteur plus traditionnel comme visible ci-dessous en Verilog :

`timescale 1ns / 1ps

module blink(
		input wire clk,
		input wire rst,
		output reg led
	);

	localparam MAX_COUNT = 10_000_000;
	localparam CNT_TOP = $clog2(MAX_COUNT);

	wire i_clk;
	reg [CNT_TOP-1:0] counter;

	assign i_clk = clk;

	always @(posedge i_clk)
	begin
		if (!rst) begin
			led <= 0;
			counter <= 0;
		end else begin
			if(counter < MAX_COUNT/2)
				led <= 1;
			else
				led <= 0;

			if (counter >= MAX_COUNT)
				counter <= 0;
			else
				counter <= counter + 1'b1;
		end
	end

endmodule

Contrairement à beaucoup de FPGA, le GateMate ne définit pas d’états initial à 0 de ses registres. Une entrée reset est donc nécessaire.

Le pinout est décrit au moyen d’un fichier «ccf» :

## blink.ccf

Pin_in   "clk"  Loc = "IO_SB_A8" | SCHMITT_TRIGGER=true;
Pin_in   "rst"  Loc = "IO_EB_B0"; # SW3
Pin_out  "led"  Loc = "IO_EB_B1"; # D1

Une fois que ces deux fichiers sont prêt il suffit de lancer yosys pour la synthèse :

$ wine ../../cc-toolchain-win/yosys-win32-mxebin-0.15+57/yosys.exe -l yosys.log -p 'read_verilog blink.v; synth_gatemate -top blink -vlog blink_synth.v'

Wine génère tout un tas d’erreurs mais fini par nous lancer la synthèse tout de même

$ wine ../../cc-toolchain-win/yosys-win32-mxebin-0.15+57/yosys.exe -l yosys.log -p 'read_verilog blink.v; synth_gatemate -top blink -vlog blink_synth.v'
wine: created the configuration directory '/home/oem/.wine'
0012:err:ole:marshal_object couldn't get IPSFactory buffer for interface {00000131-0000-0000-c000-000000000046}
0012:err:ole:marshal_object couldn't get IPSFactory buffer for interface {6d5140c1-7436-11ce-8034-00aa006009fa}
0012:err:ole:StdMarshalImpl_MarshalInterface Failed to create ifstub, hres=0x80004002
0012:err:ole:CoMarshalInterface Failed to marshal the interface {6d5140c1-7436-11ce-8034-00aa006009fa}, 80004002
0012:err:ole:get_local_server_stream Failed: 80004002
0014:err:ole:marshal_object couldn't get IPSFactory buffer for interface {00000131-0000-0000-c000-000000000046}
0014:err:ole:marshal_object couldn't get IPSFactory buffer for interface {6d5140c1-7436-11ce-8034-00aa006009fa}
0014:err:ole:StdMarshalImpl_MarshalInterface Failed to create ifstub, hres=0x80004002
0014:err:ole:CoMarshalInterface Failed to marshal the interface {6d5140c1-7436-11ce-8034-00aa006009fa}, 80004002
0014:err:ole:get_local_server_stream Failed: 80004002
Could not find Wine Gecko. HTML rendering will be disabled.
Could not find Wine Gecko. HTML rendering will be disabled.
wine: configuration in L"/home/oem/.wine" has been updated.

On ne va pas recopier toute la trace de synthèse ici mais juste la partie ressources utilisées donnée en fin de synthèse :

2.49. Printing statistics.

=== blink ===

   Number of wires:                 49
   Number of wire bits:            294
   Number of public wires:           5
   Number of public wire bits:      28
   Number of memories:               0
   Number of memory bits:            0
   Number of processes:              0
   Number of cells:                159
     CC_ADDF                        65
     CC_BUFG                         1
     CC_DFF                         25
     CC_IBUF                         2
     CC_LUT1                        24
     CC_LUT2                         5
     CC_LUT4                        36
     CC_OBUF                         1

2.50. Executing CHECK pass (checking for obvious problems).
Checking module blink...
Found and reported 0 problems.

2.51. Executing OPT_CLEAN pass (remove unused cells and wires).
Finding unused cells or wires in module \blink..

2.52. Executing Verilog backend.

2.52.1. Executing BMUXMAP pass.

2.52.2. Executing DEMUXMAP pass.
Dumping module `\blink'.

End of script. Logfile hash: dcf7a4084e
Yosys 0.15+57 (git sha1 207417617, i686-w64-mingw32.static-g++ 11.2.0 -Os)
Time spent: 1% 19x opt_clean (0 sec), 1% 18x opt_expr (0 sec), ...

Le fichier de sortie blink_synth.v est au format … Verilog également! C’était bien la peine de lancer Yosys tient !

Mais non, Verilog est un excellent format pour décrire une netlist autant que du RTL. Et de fait, le code Verilog généré n’est pas hyper lisible. Il est constitué d’une série d’instanciations et de connections des primitives du FPGA :

//...
 
 CC_LUT2 #(
    .INIT(4'h8)
  ) _051_ (
    .I0(_041_[0]),
    .I1(_041_[1]),
    .O(_043_[2])
  );
  CC_LUT4 #(
    .INIT(16'h0001)
  ) _052_ (
    .I0(counter[15]),
    .I1(counter[20]),
    .I2(counter[23]),
    .I3(counter[13]),
    .O(_041_[0])
  );

//...

Code qui reste parfaitement simulable avec Icarus pour faire de la simulation post-synthèse comme expliqué dans la documentation officielle.

Maintenant que nous avons notre netlist passons aux choses sérieuses avec le placement routage :

$ wine ../../cc-toolchain-win/p_r-2022.04-001/p_r.exe -i blink_synth.v -o blink -lib ccag
GateMate (c) Place and Route
Version 4.0 (4 April 2022)
All Rights Reserved (c) Cologne Chip AG

...

Comme pour la synthèse, nous n’allons pas mettre tous les messages ici. Une des information qui nous intéresse en priorité pour le placement routage est la performance en vitesse.

...
Static Timing Analysis
Longest Path from Q of Component 25_1 to D-Input of Component 33/1 Delay: 18215 ps
Maximum Clock Frequency on CLK 230/3:   54.90 MHz
...

La vitesse d’horloge maximale est donc de 54.90Mhz. Cela peut sembler ridicule mais il faut prendre en compte que :

  • L’architecture du «clignoteur» avec un énorme compteur pour diviser l’horloge n’est absolument pas optimisée. Pour faire bien il faudrait pipeliner le compteur mais ça n’est pas le sujet ici. Ces mauvais résultats sont cohérent avec ce qu’on pourrait obtenir avec un autre FPGA «mainstream».
  • Ces performances sont «conservatrice» c’est le pire cas quand le FPGA est très chaud.

Bref, pour faire clignoter une LED, on peut raisonnablement doubler cette fréquence d’horloge si on veut 🙂 Mais comme nous n’utilisons pas de PLL ici, la fréquence d’entrée de 10Mhz rentre dans la specification.

Les statistiques d’utilisation du FPGA sont données à la fin de la synthèse :

CPE_USAGE_INPUT - CPE_COMBSEQ         1/8 :     0 /     21   (  0.0%)
CPE_USAGE_INPUT - CPE_COMBSEQ         2/8 :    11 /     21   ( 52.4%)
CPE_USAGE_INPUT - CPE_COMBSEQ         3/8 :     0 /     21   (  0.0%)
CPE_USAGE_INPUT - CPE_COMBSEQ         4/8 :     0 /     21   (  0.0%)
CPE_USAGE_INPUT - CPE_COMBSEQ         5/8 :     0 /     21   (  0.0%)
CPE_USAGE_INPUT - CPE_COMBSEQ         6/8 :    10 /     21   ( 47.6%)
CPE_USAGE_INPUT - CPE_COMBSEQ         7/8 :     0 /     21   (  0.0%)
CPE_USAGE_INPUT - CPE_COMBSEQ         8/8 :     0 /     21   (  0.0%)

CPE_USAGE_INPUT - CPE_COMB            1/8 :     3 /      3   ( 100.0%)
CPE_USAGE_INPUT - CPE_COMB            2/8 :     0 /      3   (  0.0%)
CPE_USAGE_INPUT - CPE_COMB            3/8 :     0 /      3   (  0.0%)
CPE_USAGE_INPUT - CPE_COMB            4/8 :     0 /      3   (  0.0%)
CPE_USAGE_INPUT - CPE_COMB            5/8 :     0 /      3   (  0.0%)
CPE_USAGE_INPUT - CPE_COMB            6/8 :     0 /      3   (  0.0%)
CPE_USAGE_INPUT - CPE_COMB            7/8 :     0 /      3   (  0.0%)
CPE_USAGE_INPUT - CPE_COMB            8/8 :     0 /      3   (  0.0%)

CPE_USAGE_INPUT - CPE COMBSEQ+COMB    1/8 :     3 /     24   ( 12.5%)
CPE_USAGE_INPUT - CPE COMBSEQ+COMB    2/8 :    11 /     24   ( 45.8%)
CPE_USAGE_INPUT - CPE COMBSEQ+COMB    3/8 :     0 /     24   (  0.0%)
CPE_USAGE_INPUT - CPE COMBSEQ+COMB    4/8 :     0 /     24   (  0.0%)
CPE_USAGE_INPUT - CPE COMBSEQ+COMB    5/8 :     0 /     24   (  0.0%)
CPE_USAGE_INPUT - CPE COMBSEQ+COMB    6/8 :    10 /     24   ( 41.7%)
CPE_USAGE_INPUT - CPE COMBSEQ+COMB    7/8 :     0 /     24   (  0.0%)
CPE_USAGE_INPUT - CPE COMBSEQ+COMB    8/8 :     0 /     24   (  0.0%)

IO_USAGE                         :      3 /   144    (  2.1%) of all IOs
IO_USAGE_TYPE - IBF              :      2 /   144    (  1.4%) of all IOs    ( 66.7%) of used IOs
IO_USAGE_TYPE - OBF              :      1 /   144    (  0.7%) of all IOs    ( 33.3%) of used IOs
IO_USAGE_TYPE - TOBF             :      0 /   144    (  0.0%) of all IOs    (  0.0%) of used IOs
IO_USAGE_TYPE - IOBF             :      0 /   144    (  0.0%) of all IOs    (  0.0%) of used IOs

CPE_USAGE_PHYS - CPE_COMB_ONLY   :     73 / 20480    (  0.4%) of all CPEs   ( 60.3%) of occupied CPEs
CPE_USAGE_PHYS - CPE_SEQ_ONLY    :      5 / 20480    (  0.0%) of all CPEs   (  4.1%) of occupied CPEs
CPE_USAGE_PHYS - CPE_BRIDGE_ONLY :      0 / 20480    (  0.0%) of all CPEs   (  0.0%) of occupied CPEs
CPE_USAGE_PHYS - CPE_CARRY_ONLY  :      0 / 20480    (  0.0%) of all CPEs   (  0.0%) of occupied CPEs
CPE_USAGE_PHYS - CPE_COMB+SEQ    :     10 / 20480    (  0.0%) of all CPEs   (  8.3%) of occupied CPEs
CPE_USAGE_PHYS - CPE_COMB+BRIDGE :      0 / 20480    (  0.0%) of all CPEs   (  0.0%) of occupied CPEs
CPE_USAGE_PHYS - CPE_COMBSEQ     :     21 / 20480    (  0.1%) of all CPEs   ( 17.4%) of occupied CPEs

CPE_USAGE_LOGIC - CPE_COMB       :      8 / 20480    (  0.0%) of all CPEs   (  6.6%) of occupied CPEs
CPE_USAGE_LOGIC - CPE_SEQ        :     15 / 20480    (  0.1%) of all CPEs   ( 12.4%) of occupied CPEs
CPE_USAGE_LOGIC - CPE_COMBSEQ    :     21 / 20480    (  0.1%) of all CPEs   ( 17.4%) of occupied CPEs
CPE_USAGE_LOGIC - CPE_BRIDGE     :      0 / 20480    (  0.0%) of all CPEs   (  0.0%) of occupied CPEs

CPE_USAGE_OVERALL                :    121 / 20480    (  0.6%) of all CPEs occupied
CPE_USAGE_LOGIC                  :     94 / 20480    (  0.5%) of all CPEs used for customer logic

Component Statistics:
         AND        32        19%
        ADDF         2         1%
       ADDF2        60        37%
        C_OR        48        29%
      Route1         4         2%
    CP_route        15         9%
              --------
Sum of COMB:       161

           D        25        92%
       C_0_1         2         7%
              --------
Sum of SEQ:         27

Sum of all:        188

Et le bitstream généré est au format *.cfg (ascii) ou *.cfg.bit (binaire).

$ ls -lha blink_00.*
-rw-rw-r-- 1 oem oem 121K May  1 08:38 blink_00.cdf
-rw-rw-r-- 1 oem oem 1.4M May  1 08:38 blink_00.cfg
-rw-rw-r-- 1 oem oem  48K May  1 08:38 blink_00.cfg.bit
-rw-rw-r-- 1 oem oem  465 May  1 08:38 blink_00.pin
-rw-rw-r-- 1 oem oem 6.0K May  1 08:38 blink_00.place
-rw-rw-r-- 1 oem oem  65K May  1 08:38 blink_00.SDF
-rw-rw-r-- 1 oem oem  42K May  1 08:38 blink_00.used
-rw-rw-r-- 1 oem oem  79K May  1 08:38 blink_00.V

Pour configurer le FPGA nous utiliserons openFPGALoader en «natif» :

$ openFPGALoader -b gatemate_evb_jtag blink_00.cfg.bit
Jtag frequency : requested 6.00MHz   -> real 6.00MHz  
Load SRAM via JTAG: [==================================================] 100.00%
Done
Wait for CFG_DONE DONE

Et la LED clignote :

Ressources

sv2chisel, le convertisseur (System)Verilog vers Chisel

Le monde du FPGA (et de l’ASIC) regorge aujourd’hui de langages de description matériel. Au Verilog et VHDL s’ajoute maintenant tout un tas de langages comme Migen, Clash, BlueSpec, Amaranth, Chisel, SpinalHDL, Silice, … et j’en oublie plein. Tous ces langages permettent de générer du Verilog. Les possibilité de conversion de VHDL vers Verilog ne sont maintenant plus une utopie grâce aux évolutions de GHDL et de Yosys.

Bref, il existe désormais toujours une possibilité de convertir l’intégralité du projet en Verilog de manière à le simuler et synthétiser avec les outils conçus pour lui.

C’est quelque chose de très appréciable pour faire de la réutilisation de code. Il n’est plus nécessaire de re-concevoir un composant en VHDL tout ça parce que le contrôleur open source qui nous est nécessaire est codé en Verilog.

L’homogénéité de langage des sources d’un projet peut cependant être appréciable dans certain cas. Notamment quand le langage de description possède ses propres librairies de simulation comme c’est le cas en Chisel.

On peut certes instancier les «sous»-modules Verilog au moyen de BlackBox, mais ils ne seront pas simulable avec ChiselTest par exemple car treadle se cassera les dents dessus.

C’est là qu’intervient le nouveau projet nommé sv2chisel pour convertir du (system)Verilog en chisel.

Je vous propose ici de tester ensemble l’utilitaire dans un cas pratique. Je souhaite convertir le module Verilog fake_differential du projet d’exemple de l’ulx3s qui permet de générer un signal HDMI différentiel pour l’intégrer dans le projet HdmiCore écrit en Chisel. L’objectif étant de porter le projet HdmiCore sur la plate-forme ulx3s en restant dans du pure Chisel.

Installation de l’outil

Toutes les caractéristiques et limitations du convertisseur sont données sur le wiki. Pour l’installer nous allons cloner le projet github. Le projet n’en est pas à sa version 1.0, il est sans doute préférable de «travailler» sur le main du git plutôt que sur une release.

$ git clone https://github.com/ovh/sv2chisel.git
$ cd sv2chisel

Les étapes d’installations depuis les sources sont données dans le readme. Il faut publier localement sv2chisel ainsi que les «helpers» :

$ sbt publishLocal

Cette commande fonctionne pas chez moi, je pensais naïvement que c’était la même commande que celle consistant à lancer sbt puis taper «publishLocal» mais non 😉

Donc pour publier localement, on fera plutôt comme recommandé dans le readme :

$ sbt
sbt:sv2chisel> publishLocal
sbt:sv2chisel> helpers/publishLocal
sbt:sv2chisel> assembly

La commande «assembly» génère le fichier jar exécutable.

Le binaire de l’utilitaire est généré dans le répertoire utils/bin/ et se nomme tout simplement sv2chisel

$ ./sv2chisel -help
sv2chisel [Options] sv_files... or sv2chisel [Options] -c config_file

Commons Options:
    -l, --log-level <error|warn|struct|info|debug|trace>
                                     Logger verbosity level
    -L, --class-log-level CLASS_NAME:<error|warn|struct|info|debug|trace>
                                     Logger verbosity level within given CLASS_NAME (useful for transforms)
    -o, --emission-path PATH         Base emission path

Config File (prio over manually specified files):
    -c, --config-file FILE           Yaml Configuration File

Manual command-line configuration
    -i, --base-path PATH             Base path for files
    -n, --name NAME                  Project name
    -h, --help                       Show this message

Si l’on regarde dans le fichier on trouve un simple lien vers l’archive «jar» se trouvant dans le même répertoire:

$ ls 
sv2chisel  sv2chisel.jar

$ cat sv2chisel
#!/bin/bash

path=`dirname "$0"`
cmd="java -cp ${path}/sv2chisel.jar sv2chisel.Main ${@:1}"

Conversion

Pour convertir en Chisel, on peut simplement donner les noms des fichiers sources en arguments:

$ ./sv2chisel fake_differential.v
[log] ---- Processing project ----
[log] ############# Parsing fake_differential.v #############
[log] ######### Elapsed time for fake_differential.v #########
[log] # Lexing+Parsing Time : 335.206505 ms
[log] # Mapping to IR Time : 126.985877 ms
[log] ######### Executing 18 transforms #########
[log] ####### sv2chisel.transforms.CheckUseBeforeDecl #######
[log] # Elapsed time : 26.214516 ms
[log] ####### sv2chisel.transforms.CheckScopes #######
[log] # Elapsed time : 7.222317 ms
[log] ####### sv2chisel.transforms.CheckBlockingAssignments #######
[log] # Elapsed time : 1.448437 ms
[log] ####### sv2chisel.transforms.InferDefLogicClocks #######
[info] Registering a new clock clk_shift for module fake_differential (non blocking assignment) at fake_differential.v:3:0>>54:0
[warn] Unable to find module module ODDRX1F instanciated as ddr_p_instance in current module fake_differential for clock inference processing. at fake_differential.v:33:10>>41:11
[warn] Unable to find module module ODDRX1F instanciated as ddr_n_instance in current module fake_differential for clock inference processing. at fake_differential.v:42:10>>50:11
[log] # Elapsed time : 41.125895 ms
[log] ####### sv2chisel.transforms.PropagateClocks #######
[warn] Module ODDRX1F referenced by instance ddr_p_instance cannot be found in current design. Clock & Reset management might be inaccurate. at fake_differential.v:33:10>>41:11
[warn] Module ODDRX1F referenced by instance ddr_n_instance cannot be found in current design. Clock & Reset management might be inaccurate. at fake_differential.v:42:10>>50:11
[log] # Elapsed time : 1.878423 ms
[log] ####### sv2chisel.transforms.FlowReferences #######
[info] Declaring actual port directions for module fake_differential at fake_differential.v:5:2->8
[info] Running FlowReference Transform another time on module fake_differential at fake_differential.v:3:0>>54:0
[log] # Elapsed time : 28.311422 ms
[log] ####### sv2chisel.transforms.InferUInts #######
[info] Converting in_clock to UInt based on its usage in the module at fake_differential.v:7:9->11
[info] Converting tmds[_] to UInt based on its usage in the module at fake_differential.v:11:10->12
[log] # Elapsed time : 30.181078 ms
[log] ####### sv2chisel.transforms.InferParamTypes #######
[log] # Elapsed time : 5.803376 ms
[log] ####### sv2chisel.transforms.TypeReferences #######
[critical] Unsupported Type 'Bool' for subindex expression 'out_n[i]' at fake_differential.v:47:15->22
[log] # Elapsed time : 18.082934 ms
[log] ####### sv2chisel.transforms.LegalizeExpressions #######
[warn] Unknown remote type for port #2 (Q) of instance ddr_p_instance of module ODDRX1F: casting by reference by default at fake_differential.v:38:12->23
[critical] Unsupported Type 'Bool' for subindex expression 'out_n[i]' at fake_differential.v:47:15->22
[warn] Unknown remote type for port #2 (Q) of instance ddr_n_instance of module ODDRX1F: casting by reference by default at fake_differential.v:47:12->23
[log] # Elapsed time : 29.292823 ms
[log] ####### sv2chisel.transforms.FixFunctionImplicitReturns #######
[log] # Elapsed time : 1.314473 ms
[log] ####### sv2chisel.transforms.NameInstancePorts #######
[log] # Elapsed time : 2.628666 ms
[log] ####### sv2chisel.transforms.RemovePatterns #######
[log] # Elapsed time : 4.862502 ms
[log] ####### sv2chisel.transforms.RemoveConcats #######
[log] # Elapsed time : 3.143862 ms
[log] ####### sv2chisel.transforms.AddDontCare #######
[log] # Elapsed time : 1.28653 ms
[log] ####### sv2chisel.transforms.LegalizeParamDefaults #######
[warn] Cannot find module ODDRX1F in current project at fake_differential.v:33:10>>41:11
[warn] Cannot find module ODDRX1F in current project at fake_differential.v:42:10>>50:11
[log] # Elapsed time : 4.072062 ms
[log] ####### sv2chisel.transforms.FixReservedNames #######
[log] # Elapsed time : 4.126536 ms
[log] ####### sv2chisel.transforms.ToCamelCase #######
[log] # Elapsed time : 0.830815 ms
[log] # Total Elapsed time running transforms : 216.675871 ms
[log] ######### EMISSION #########
[log] ######### CHISELIZING fake_differential.v #########
[info] At fake_differential.v:11: Emitting unpacked for node tmds
[info] At fake_differential.v:18: Emitting unpacked for node R_tmds_p
[info] At fake_differential.v:18: Emitting unpacked for node R_tmds_n
[log] # Elapsed time : 21.267262 ms
[log] ######### EMITTING to /fake_differential.scala #########
Exception in thread "main" java.io.FileNotFoundException: /fake_differential.scala (Permission denied)
at java.base/java.io.FileOutputStream.open0(Native Method)
at java.base/java.io.FileOutputStream.open(FileOutputStream.java:291)
at java.base/java.io.FileOutputStream.(FileOutputStream.java:234)
at java.base/java.io.FileOutputStream.(FileOutputStream.java:123)
at java.base/java.io.FileWriter.(FileWriter.java:66)
at sv2chisel.Emitter$.$anonfun$emitChisel$9(Emitter.scala:174)
at sv2chisel.Utils$.time(Utils.scala:185)
at sv2chisel.Emitter$.$anonfun$emitChisel$1(Emitter.scala:163)
at scala.collection.TraversableLike.$anonfun$map$1(TraversableLike.scala:286)
at scala.collection.mutable.ResizableArray.foreach(ResizableArray.scala:62)
at scala.collection.mutable.ResizableArray.foreach$(ResizableArray.scala:55)
at scala.collection.mutable.ArrayBuffer.foreach(ArrayBuffer.scala:49)
at scala.collection.TraversableLike.map(TraversableLike.scala:286)
at scala.collection.TraversableLike.map$(TraversableLike.scala:279)
at scala.collection.AbstractTraversable.map(Traversable.scala:108)
at sv2chisel.Emitter$.emitChisel(Emitter.scala:134)
at sv2chisel.Driver$.emitChisel(Driver.scala:66)
at sv2chisel.Main$.$anonfun$new$10(Main.scala:159)
at scala.collection.immutable.List.foreach(List.scala:431)
at sv2chisel.Main$.delayedEndpoint$sv2chisel$Main$1(Main.scala:157)
at sv2chisel.Main$delayedInit$body.apply(Main.scala:55)
at scala.Function0.apply$mcV$sp(Function0.scala:39)
at scala.Function0.apply$mcV$sp$(Function0.scala:39)
at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:17)
at scala.App.$anonfun$main$1$adapted(App.scala:80)
at scala.collection.immutable.List.foreach(List.scala:431)
at scala.App.main(App.scala:80)
at scala.App.main$(App.scala:78)
at sv2chisel.Main$.main(Main.scala:55)
at sv2chisel.Main.main(Main.scala)

Après de multiple messages plus ou moins critiques, la commande se termine sur une étonnante erreur java de fichier non trouvé. Visiblement il faut lui fournir le path complet en argument (sans doute un bug) :

 ./sv2chisel /opt/chiselmod/sv2chisel/utils/bin/fake_differential.v 
[log]  ---- Processing project  ---- 
[log] ############# Parsing /opt/chiselmod/sv2chisel/utils/bin/fake_differential.v #############
[log] ######### Elapsed time for /opt/chiselmod/sv2chisel/utils/bin/fake_differential.v #########
[log] # Lexing+Parsing Time : 337.962186 ms
[log] # Mapping to IR Time  : 123.022396 ms
[log] ######### Executing 18 transforms #########
[log]    ####### sv2chisel.transforms.CheckUseBeforeDecl #######
[log]    # Elapsed time : 24.704003 ms
[log]    ####### sv2chisel.transforms.CheckScopes #######
[log]    # Elapsed time : 6.588446 ms
[log]    ####### sv2chisel.transforms.CheckBlockingAssignments #######
[log]    # Elapsed time : 1.838544 ms
[log]    ####### sv2chisel.transforms.InferDefLogicClocks #######
[info] Registering a new clock `clk_shift` for module fake_differential (non blocking assignment) at /opt/chiselmod/sv2chisel/utils/bin/fake_differential.v:3:0>>54:0
[warn] Unable to find module module ODDRX1F instanciated as ddr_p_instance in current module fake_differential for clock inference processing. at /opt/chiselmod/sv2chisel/utils/bin/fake_differential.v:33:10>>41:11
[warn] Unable to find module module ODDRX1F instanciated as ddr_n_instance in current module fake_differential for clock inference processing. at /opt/chiselmod/sv2chisel/utils/bin/fake_differential.v:42:10>>50:11
[log]    # Elapsed time : 37.088031 ms
[log]    ####### sv2chisel.transforms.PropagateClocks #######
[warn] Module ODDRX1F referenced by instance ddr_p_instance cannot be found in current design. Clock & Reset management might be inaccurate. at /opt/chiselmod/sv2chisel/utils/bin/fake_differential.v:33:10>>41:11
[warn] Module ODDRX1F referenced by instance ddr_n_instance cannot be found in current design. Clock & Reset management might be inaccurate. at /opt/chiselmod/sv2chisel/utils/bin/fake_differential.v:42:10>>50:11
[log]    # Elapsed time : 1.73635 ms
[log]    ####### sv2chisel.transforms.FlowReferences #######
[info] Declaring actual port directions for module fake_differential at /opt/chiselmod/sv2chisel/utils/bin/fake_differential.v:5:2->8
[info] Running FlowReference Transform another time on module fake_differential at /opt/chiselmod/sv2chisel/utils/bin/fake_differential.v:3:0>>54:0
[log]    # Elapsed time : 25.221955 ms
[log]    ####### sv2chisel.transforms.InferUInts #######
[info] Converting in_clock to UInt based on its usage in the module at /opt/chiselmod/sv2chisel/utils/bin/fake_differential.v:7:9->11
[info] Converting tmds[_] to UInt based on its usage in the module at /opt/chiselmod/sv2chisel/utils/bin/fake_differential.v:11:10->12
[log]    # Elapsed time : 29.58874 ms
[log]    ####### sv2chisel.transforms.InferParamTypes #######
[log]    # Elapsed time : 5.646461 ms
[log]    ####### sv2chisel.transforms.TypeReferences #######
[critical] Unsupported Type 'Bool' for subindex expression 'out_n[i]' at /opt/chiselmod/sv2chisel/utils/bin/fake_differential.v:47:15->22
[log]    # Elapsed time : 18.257447 ms
[log]    ####### sv2chisel.transforms.LegalizeExpressions #######
[warn] Unknown remote type for port #2 (Q) of instance ddr_p_instance of module ODDRX1F: casting by reference by default at /opt/chiselmod/sv2chisel/utils/bin/fake_differential.v:38:12->23
[critical] Unsupported Type 'Bool' for subindex expression 'out_n[i]' at /opt/chiselmod/sv2chisel/utils/bin/fake_differential.v:47:15->22
[warn] Unknown remote type for port #2 (Q) of instance ddr_n_instance of module ODDRX1F: casting by reference by default at /opt/chiselmod/sv2chisel/utils/bin/fake_differential.v:47:12->23
[log]    # Elapsed time : 21.168114 ms
[log]    ####### sv2chisel.transforms.FixFunctionImplicitReturns #######
[log]    # Elapsed time : 0.631086 ms
[log]    ####### sv2chisel.transforms.NameInstancePorts #######
[log]    # Elapsed time : 1.467731 ms
[log]    ####### sv2chisel.transforms.RemovePatterns #######
[log]    # Elapsed time : 5.09245 ms
[log]    ####### sv2chisel.transforms.RemoveConcats #######
[log]    # Elapsed time : 2.749951 ms
[log]    ####### sv2chisel.transforms.AddDontCare #######
[log]    # Elapsed time : 1.251281 ms
[log]    ####### sv2chisel.transforms.LegalizeParamDefaults #######
[warn] Cannot find module ODDRX1F in current project at /opt/chiselmod/sv2chisel/utils/bin/fake_differential.v:33:10>>41:11
[warn] Cannot find module ODDRX1F in current project at /opt/chiselmod/sv2chisel/utils/bin/fake_differential.v:42:10>>50:11
[log]    # Elapsed time : 3.092851 ms
[log]    ####### sv2chisel.transforms.FixReservedNames #######
[log]    # Elapsed time : 4.132691 ms
[log]    ####### sv2chisel.transforms.ToCamelCase #######
[log]    # Elapsed time : 0.890969 ms
[log] # Total Elapsed time running transforms : 195.82181 ms
[log] ######### EMISSION #########
[log] ######### CHISELIZING /opt/chiselmod/sv2chisel/utils/bin/fake_differential.v #########
[info] At /opt/chiselmod/sv2chisel/utils/bin/fake_differential.v:11: Emitting unpacked for node tmds
[info] At /opt/chiselmod/sv2chisel/utils/bin/fake_differential.v:18: Emitting unpacked for node R_tmds_p
[info] At /opt/chiselmod/sv2chisel/utils/bin/fake_differential.v:18: Emitting unpacked for node R_tmds_n
[log] # Elapsed time : 25.374274 ms
[log] ######### EMITTING to //opt/chiselmod/sv2chisel/utils/bin/fake_differential.scala #########
[log] # Elapsed time : 17.429649 ms

Il y a sans doute beaucoup de chose a parametrer aux petits oignons pour bien utiliser l’outils, mais le résultat «en l’état» est déjà assez intéressant (commentaires FLF ajoutés):

package // FLF: à rajouter à la main ?
// FLF: on comprend pourquoi il voulait un path complet,
//      il le prend pour le nom du package
package .opt.chiselmod.sv2chisel.utils.bin

import chisel3._
// FLF: commentaires gardés, bien.
// DDR mode uses Lattice ECP5 vendor-specific module ODDRX1F

class fake_differential() extends Module { // used only in DDR mode
  // [1:0]:DDR [0]:SDR TMDS
  val in_clock = IO(Input(UInt(2.W)))
  val in_red = IO(Input(Bool()))
  val in_green = IO(Input(Bool()))
  val in_blue = IO(Input(Bool()))
  // [3]:clock [2]:red [1]:green [0]:blue 
  val out_p = IO(Output(Vec(4, Bool())))
  val out_n = IO(Output(Bool()))
// FLF: hmm, j'aurais pas traduit un tableau «wire» par une Mem() 
//      chisel, pas sûr que ça marche bien cette affaire.
  val tmds = Mem(4,UInt(2.W)) 
  tmds(3) := in_clock
  tmds(2) := in_red
  tmds(1) := in_green
  tmds(0) := in_blue

  // register stage to improve timing of the fake differential
  val R_tmds_p = Mem(4,Vec(2, Bool())) 
  val R_tmds_n = Mem(4,Vec(2, Bool())) 
  // genvar i;
  for(i <- 0 until 4){
    R_tmds_p(i) := tmds(i).asBools
    R_tmds_n(i) := ( ~tmds(i)).asBools
  }

  // output SDR/DDR to fake differential

  // FLF: les generate sont détecté et traduit également.
  // genvar i;
  for(i <- 0 until 4){
    // FLF: connexion des primitives sans broncher
    //      Il faudra tout de même inclure la définition
    //      de la blackbox à la main par la suite (import)
    val ddr_p_instance = Module(new ODDRX1F)
    ddr_p_instance.D0 := R_tmds_p(i)(0)
    ddr_p_instance.D1 := R_tmds_p(i)(1)
    out_p(i) := ddr_p_instance.Q.asTypeOf(out_p(i))
    ddr_p_instance.SCLK := clock
    ddr_p_instance.RST := 0.U
    val ddr_n_instance = Module(new ODDRX1F)
    ddr_n_instance.D0 := R_tmds_n(i)(0)
    ddr_n_instance.D1 := R_tmds_n(i)(1)
    out_n(i) := ddr_n_instance.Q.asTypeOf(out_n(i))
    ddr_n_instance.SCLK := clock
    ddr_n_instance.RST := 0.U
  }

}

Conclusion

Visiblement, le code généré doit être passé en revue par un ou une humaine histoire de corriger quelques imprécisions.

Mais cette relecture est facile, le code est très lisible et bien indenté. On retrouve les noms des signaux, variables, registres, modules Verilog. On est loin des bouillies de conversion où le code généré ressemble plus à un binaire compilé qu’à un code source «versionnable». Et cette saine relecture est de toute manière indispensable si l’on souhaite se reposer sur ce nouveau code dans la suite de son projet.

C’est un outil qui va vite devenir indispensable lorsque le besoin de convertion de code open-source se fera sentir. Et c’est une belle passerelle pour tous les habitués du Verilog qui souhaiteraient se lancer dans ce langage de haut niveau qu’est Chisel.

Des dire de l’équipe, l’outil a été testé avec succès sur le code du processeur RISC-V PicoRV32 développé par Claire Clifford (autrice de Yosys) que l’on retrouve un peu partout dans les projets open-source hardware.

C’est également une surprise de voir que ce projet est né au sein du laboratoire OVHCloud. Où l’on découvre que le fleurons du Claude français (cocorico) finance la recherche sur le matériel libre. Ceux qui ont besoin d’un article plus académique pour découvrir l’outil iront lire le papier de l’équipe ici.

Une LED qui clignote sur ICEStick vite vite vite !

Historiquement le ICE40 soudé sur la carte icestick est le premier supporté par des outils libres.

Le célèbre icestick qui a libéré les FPGA

Il est maintenant possible d’utiliser plusieurs programmes open-source pour développer dessus. Voici une méthode avec yosys, nextpnr, icestorm et openFPGALoader.

Dans un premier temps, allez donc cloner, compiler makeInstaller les 4 programmes cités ci-avant :

  • Yosys: Logiciel de synthèse Verilog couteau suisse du monde du FPGA.
  • nextpnr: Logiciel de placement routage supportant de plus en plus de famille de FPGA
  • icestorm: La tempête à l’origine de la libération des ICE40 de Lattice.
  • openFPGALoader: Le configurateur universel pour FPGA.

N’oubliez pas l’option de compilation «ICE40» quand elle est requise, mais c’est expliqué dans les différents tutos de compilation des outils.

Une fois que tout est installé on peut prendre le source Verilog «Blinking Led Project» et le modifier comme ci-dessous :

module blink (
    // Horloge
    input clock,
    output led
);

// Icestick clock : 12Mhz
parameter clock_freq = 12_000_000; // clock frequency
localparam MAX_COUNT = clock_freq;
localparam MAX_COUNT_UPPER = $clog2(MAX_COUNT) - 1;

reg [MAX_COUNT_UPPER:0] counter;
reg led_reg;

assign led = led_reg;

always@(posedge clock)
begin
    if(counter < MAX_COUNT/2)
        led_reg <= 1;
    else
        led_reg <= 0;

    if(counter >= MAX_COUNT)
        counter <= 0;
    else
        counter <= counter + 1;
end

endmodule

Il faut ensuite ajouter les informations de pinout pour l’horloge et la LED dans un fichier pcf que nous nommerons blink.pcf:

set_io clock  21
set_io led 98

Puis enfin, lancer les différentes commande de synthèse/pnr/bitstream :

$ PROJECTNAME=blink
$ VERILOGS="$PROJECTNAME.v"
  • Synthèse avec yosys:
$ yosys -q -p "synth_ice40 -top $PROJECTNAME -json $PROJECTNAME.json" $VERILOGS
  • Placement routage avec nextpnr:
$ nextpnr-ice40 --force --json $PROJECTNAME.json --pcf $PROJECTNAME.pcf --asc $PROJECTNAME.asc --freq 12 --hx1k --package tq144 $1
  • Vérification des timings avec icetime:
$ icetime -p $PROJECTNAME.pcf -P tq144 -r $PROJECTNAME.timings -d hx1k -t $PROJECTNAME.asc
// Reading input .pcf file..
// Reading input .asc file..
// Reading 1k chipdb file..
// Creating timing netlist..
// Timing estimate: 6.12 ns (163.28 MHz)
  • Packaging du bitstream avec icepack :
$ icepack $PROJECTNAME.asc $PROJECTNAME.bin
  • Configuration du fpga avec openFPGALoader:
$ openFPGALoader -b ice40_generic blink.bin 
write to ram
Jtag frequency : requested 6.00MHz   -> real 6.00MHz  
Parse file DONE
00
Detail: 
Jedec ID          : 20
memory type       : ba
memory capacity   : 16
EDID + CFD length : 10
EDID              : 0000
CFD               : 
Erasing: [==================================================] 100.00%
Done
Writing: [==================================================] 100.00%
Done
Wait for CDONE DONE

Et voila, la LED clignote.

Convertir du VHDL en Verilog librement avec Yosys et GHDL

Il y a quelques années, nous parlions de l’utilitaire vhdl2vl sur ce blog. Cette solution est intéressante mais limitée car le projet est relativement au point mort.

Depuis quelques mois une solution beaucoup plus «hype» est disponible, alliant le couteau suisse du Verilog Yosys, la référence en simulation libre en VHDL GHDL et le plugin ghdl-yosys-plugin. Cette solution permet dès à présent de convertir la plupart des codes VHDL en Verilog.

Voyons comment faire avec le module de réception uart proposé par nandland : UART_RX.vhd.

$ mkdir vhdlconv
$ cd vhdlconv
$ ls
UART_RX.vhd

Il faut tout d’abord compiler et installer Yosys et GHDL selon la procédure donnée sur les sites respectif.

Un fois fait il faut installer et compiler le plugin ghdl-yosys-plugin comme expliqué sur le dépot. Dans notre cas, cette compilation sera faite dans le répertoire /opt/ghdl-yosys-plugin.

Un fois l’installation effectuée nous pouvons nous lancer dans la conversion avec le plugin :

$ export GHDL_YOSYS_PLUGIN=/opt/ghdl-yosys-plugin/ghdl.so

On élabore le vhdl avec ghdl :

$ ghdl -a UART_RX.vhd 
$ ls
UART_RX.vhd  work-obj93.cf

On lance yosys avec le module ghdl :

$ yosys -m $GHDL_YOSYS_PLUGIN 
...
 |  yosys -- Yosys Open SYnthesis Suite                                       
 |  Copyright (C) 2012 - 2020  Claire Xenia Wolf <claire@yosyshq.com>         
...
 Yosys 0.9+4081 (git sha1 862e84eb, clang 10.0.0-4ubuntu1 -fPIC -Os)

On lit le module fraîchement élaboré:

...
yosys> ghdl UART_RX
1. Executing GHDL.
Importing module UART_RX.

Puis on lance la synthèse :

yosys> proc; opt; fsm; opt; memory; opt;

2. Executing PROC pass (convert processes to netlists).

2.1. Executing PROC_CLEAN pass (remove empty switches from decision trees).
Cleaned up 0 empty switches.

2.2. Executing PROC_RMDEAD pass (remove dead branches from decision trees).
Removed a total of 0 dead cases.

[...]

7.8. Executing OPT_EXPR pass (perform const folding).
Optimizing module UART_RX.

7.9. Finished OPT passes. (There is nothing left to do.)

Et enfin, on peut écrire le Verilog du module converti :

yosys> write_verilog UART_RX.v

8. Executing Verilog backend.
Dumping module `\UART_RX'.

yosys> exit

Le module verilog ainsi généré possède les même noms d’interfaces:

$ head -n 20 UART_RX.v
/* Generated by Yosys 0.9+4081 (git sha1 862e84eb, clang 10.0.0-4ubuntu1 -fPIC -Os) */

module UART_RX(i_Clk, i_RX_Serial, o_RX_DV, o_RX_Byte);
  (* unused_bits = "7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31" *)
  wire [31:0] _00_;
  wire [2:0] _01_;
  wire [6:0] _02_;
  wire _03_;
  wire _04_;
  wire _05_;
  (* unused_bits = "3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31" *)
  wire [31:0] _06_;
  wire [2:0] _07_;
  wire [2:0] _08_;
  wire [2:0] _09_;
  wire [6:0] _10_;
  wire [2:0] _11_;
  wire _12_;
  wire [2:0] _13_;
  wire _14_;

$ head -n 20 UART_RX.vhd
library ieee;
use ieee.std_logic_1164.ALL;
use ieee.numeric_std.all;
 
entity UART_RX is
  generic (
    g_CLKS_PER_BIT : integer := 115     -- Needs to be set correctly
    );
  port (
    i_Clk       : in  std_logic;
    i_RX_Serial : in  std_logic;
    o_RX_DV     : out std_logic;
    o_RX_Byte   : out std_logic_vector(7 downto 0)
    );
end UART_RX;
 
 
architecture rtl of UART_RX is
 
  type t_SM_Main is (s_Idle, s_RX_Start_Bit, s_RX_Data_Bits,

Et même si le code n’est pas très lisible on retrouve ses petits avec le nom des signaux interne du module.

Ce qui est vraiment intéressant ici c’est que le code verilog généré est parfaitement synthétisable avec n’importe quel logiciel de synthèse verilog, on peut également utiliser Verilator pour accélérer nos simulation et enfin il est possible de faire de la preuve formelle avec Yosys.

Plus d’excuse pour ne pas mixer du code VHDL avec du Verilog maintenant puisque tout est convertible en Verilog !