VHDL: afficher du texte au moment de la synthèse vivado

Lorsque l’on essait de faire des modules VHDL génériques, on utilise massivement les paramètres generic map des modules.

Ces paramètres sont utilisés ensuite à la synthèse pour calculer des tailles de tableaux, des constantes et autre structures matérielles générées.

Pour déverminer cette partie du code on a besoin de «voir» les valeurs qui sont calculées. En VHDL il n’y a pas de printf pour ça, mais il y a report utilisé en simulation dans un process :

process is
begin
    report "Affichons des trucs" severity note;
    wait;
end process;

Sauf que nous ne somme pas en simulation, ce process ne sera même pas traité à la synthèse.

Pour pouvoir afficher du texte on peut utiliser les assertions en les mettant systématiquement à faux pour être sur qu’elles soient affichées :

assert false report "Affichons des trucs à la synthèse" severity note;

Mais rien ne s’affiche dans les log à la synthèse Vivado. Ça n’est pas un bug, c’est juste que Xilinx a désactivé ces messages par défaut. Pour les réactiver il suffit de taper cette ligne magique dans la console tcl avant de lancer la synthèse :

set_param synth.elaboration.rodinMoreOptions {rt::set_parameter ignoreVhdlAssertStmts false}

Le message est ensuite visible dans les log:

INFO: [Synth 8-63] RTL assertion: "Affichons des trucs à la synthèse" [plop.vhd:108]

Merci à brimdavis pour le truc.

Test d’une carte Gowin avec LiteX

Après avoir déballé le kit gowin proposé par trenz micro. Il faut trouver quelques chose à faire de plus avancé qu’un simple clignotement de LED. Pour le clignotement de LED et la prise en main des outils, le lecteur se référera à l’article Hackable 32.

Pourquoi ne pas tenter le Linux des FPGA, j’ai nommé LiteX (Prononcez Lahïtixe ) ? LiteX est un framework HDL basé sur Migen pour construire des systèmes matériel facilement en python.

LiteX inclut un langage de description matériel, mais également tous les outils permettant de faire des simulations, la synthèses et générer les bitstreams pour la plupart des FPGA du marché. Bien évidement, en ce qui concerne la synthèse et les bitstreams, LiteX pilote les outils propriétaires des constructeurs. Ce pilotage ne pose généralement pas trop de problème, car tous les outils constructeurs proposent des interfaces en ligne de commande.

La carte que nous allons tenter de faire fonctionner avec LiteX est donc la TEC0117-1 produite par Trenz electronic et munie d’un FPGA gowin .

La carte TEC0117-1 de trenz micro se pilote intégralement avec le port série.

D’après le wiki, la carte n’est pas encore officiellement supporté par LiteX puisqu’elle ne se trouve pas dans la liste du projet LiteX-Boards

Pas dans le tableau de la documentation du moins, car en fouillant dans le code du projet, il semble qu’il y ait déjà un embryon de quelques chose ici. Voila qui est très engageant pour tester la carte.

Voyons donc voir les étapes nous permettant de construire un système avec LiteX.

Installation de LiteX

Le plus simple pour installer LiteX sur son ordinateur est d’aller suivre le guide officiel d’installation.

$ wget https://raw.githubusercontent.com/enjoy-digital/litex/master/litex_setup.py
$ chmod +x litex_setup.py
$ ./litex_setup.py init install --user (--user to install to user directory)

Attention, si comme moi vous avez un pc qui commence à prendre sérieusement de l’âge, sachez que le script litex_setup.py va descendre beaucoup de projets annexes de LiteX. Ça va prendre quelques minutes.

On aura besoin également de gcc compilé pour RISC-V :

$ ./litex_setup.py gcc

Il faudra bien penser à l’exporter à chaque fois qu’on en aura besoin :

$ export PATH=$PATH:$(echo $PWD/riscv64-*/bin/)

Construire le système pour TEC0117

Pour construire un système pour la carte il faut ensuite se rendre dans le répertoire contenant la carte puis lancer le script python correspondant:

$ export PATH=$PATH:$(echo $PWD/riscv64-*/bin/)
$ cd litex-boards/litex_boards/targets
$ python3 trenz_tec0117.py

Pour synthétiser et générer le bitstream il faut d’abord ajouter le lien vers l’ide (1.9.7 minimum) de gowin :

$ export PATH=$PATH:/opt/gowin/1_9_7/IDE/bin/

Puis lancer le build:

$ python litex-boards/litex_boards/targets/trenz_tec0117.py --build
INFO:SoC:        __   _ __      _  __  
INFO:SoC:       / /  (_) /____ | |/_/  
INFO:SoC:      / /__/ / __/ -_)>  <    
INFO:SoC:     /____/_/\__/\__/_/|_|  
INFO:SoC:  Build your hardware, easily!
INFO:SoC:--------------------------------------------------------------------------------

...

Running timing analysis......
[95%] Timing analysis completed
Bitstream generation in progress......
Bitstream generation completed
Running power analysis......
[100%] Power analysis completed
Generate file "/home/fabienm/myapp/litex/build/trenz_tec0117/gateware/impl/pnr/project.power.html" completed
Generate file "/home/fabienm/myapp/litex/build/trenz_tec0117/gateware/impl/pnr/project.pin.html" completed
Generate file "/home/fabienm/myapp/litex/build/trenz_tec0117/gateware/impl/pnr/project.rpt.html" completed
Generate file "/home/fabienm/myapp/litex/build/trenz_tec0117/gateware/impl/pnr/project.rpt.txt" completed
Generate file "/home/fabienm/myapp/litex/build/trenz_tec0117/gateware/impl/pnr/project.tr.html" completed
Fri Jul  9 13:18:07 2021

Le bitstream généré au format *.fs se trouve ensuite dans le répertoire

./build/trenz_tec0117/gateware/impl/pnr/project.fs

On pourra configurer le FPGA directement avec openFPGALoader :

$ openFPGALoader ./build/trenz_tec0117/gateware/impl/pnr/project.fs
Parse ./build/trenz_tec0117/gateware/impl/pnr/project.fs: 
checksum 0x15d3
Done
erase SRAM Done
Flash SRAM: [==================================================] 100.000000%
Done
SRAM Flash: Success

Ou avec la bonne option litex (qui fait appel à openFPGALoader de toute manière):

$ python3 ../litex-boards/litex_boards/targets/trenz_tec0117.py --load
INFO:SoC:        __   _ __      _  __  
INFO:SoC:       / /  (_) /____ | |/_/  
INFO:SoC:      / /__/ / __/ -_)>  <    
INFO:SoC:     /____/_/\__/\__/_/|_|  
INFO:SoC:  Build your hardware, easily!
INFO:SoC:--------------------------------------------------------------------------------
INFO:SoC:Creating SoC... (2021-07-12 19:05:10)
INFO:SoC:--------------------------------------------------------------------------------
INFO:SoC:FPGA device : GW1NR-LV9QN88C6/I5.
INFO:SoC:System clock: 25.00MHz.
INFO:SoCBusHandler:Creating Bus Handler...

...

INFO:SoC:Initializing ROM rom with contents (Size: 0x51cc).
INFO:SoC:Auto-Resizing ROM rom from 0x6000 to 0x51cc.
Parse /home/user/myapp/litex/myapp/build/trenz_tec0117/gateware/impl/pnr/project.fs: 
checksum 0xa3a3
Done
erase SRAM Done
Flash SRAM: [==================================================] 100.000000%
Done
SRAM Flash: Success

La confirmation de la bonne configuration est donnée par le message de la console, mais également par le chenillard de LED rouge.

Console litex>

Le convertisseur USB-uart de la carte possède deux interfaces ttyUSB, la première vient d’être utilisée par openFPGALoader pour charger le bitstream, la seconde permet de se connecter à la console litex :

$ screen /dev/ttyUSB1 115200
litex> reboot


        __   _ __      _  __
       / /  (_) /____ | |/_/
      / /__/ / __/ -_)>  <
     /____/_/\__/\__/_/|_|
   Build your hardware, easily!

 (c) Copyright 2012-2021 Enjoy-Digital
 (c) Copyright 2007-2015 M-Labs

 BIOS built on Jul  9 2021 13:17:06
 BIOS CRC passed (855636f6)

 Migen git sha1: 3ffd64c
 LiteX git sha1: 2b49430f

--=============== SoC ==================--
CPU:            VexRiscv_Lite @ 25MHz
BUS:            WISHBONE 32-bit @ 4GiB
CSR:            32-bit data
ROM:            24KiB
SRAM:           4KiB
L2:             0KiB
SDRAM:          8192KiB 16-bit @ 25MT/s (CL-2 CWL-2)

--========== Initialization ============--
Initializing SDRAM @0x40000000...
Switching SDRAM to software control.
Switching SDRAM to hardware control.
Memtest at 0x40000000 (2.0MiB)...
  Write: 0x40000000-0x40200000 2.0MiB     
   Read: 0x40000000-0x40200000 2.0MiB     
Memtest OK
Memspeed at 0x40000000 (2.0MiB)...
  Write speed: 5.6MiB/s
   Read speed: 6.2MiB/s

--============== Boot ==================--
Booting from serial...
Press Q or ESC to abort boot completely.
sL5DdSMmkekro
             Timeout
No boot medium found

--============= Console ================--

litex> 

La commande help nous donnes les commandes disponibles :

litex> help


LiteX BIOS, available commands:

leds                     - Set Leds value
flush_l2_cache           - Flush L2 cache
flush_cpu_dcache         - Flush CPU data cache
crc                      - Compute CRC32 of a part of the address space
ident                    - Identifier of the system
help                     - Print this help

serialboot               - Boot from Serial (SFL)
reboot                   - Reboot
boot                     - Boot from Memory

mem_speed                - Test memory speed
mem_test                 - Test memory access
mem_copy                 - Copy address space
mem_write                - Write address space
mem_read                 - Read address space
mem_list                 - List available memory regions

sdram_test               - Test SDRAM

Photo traditionnelle

Avec LiteX, une tradition «twitter» veux que l’on fasse une photo du kit démarrant LiteX avec la console démarrée.

Photo trophée de la tec0117 avec LiteX

Et voila \o/

L’entrée en matière est incroyablement facile et fait honneur au slogan «Build your hardware, easily!».

Nous regarderons dans de futur articles ce que l’on peut faire avec.

Utiliser UNISIM avec GHDL et CocoTB

Les constructeurs de FPGA fournissent des modèles VHDL de leurs primitives. Chez Xilinx cela se présente sous la forme d’une librairie nommée UNISIM.

Le logiciel libre de simulation GHDL n’inclut pas les sources VHDL de cette librairie dans son dépôt officiel car ça n’est pas sa propriété. Cependant, GHDL fourni des scripts permettant de les pré-compiler pour son projet.

Nous allons voir ici comment se servir des primitives disponible dans vivado avec une simulation utilisant CocoTB.

Les sources des primitives se trouvent dans le répertoire d’installation de Vivado

data/vhdl/src/unisims/

Pour les retrouver on peut utiliser la commande fd-find comme ceci :

$ fd unisim -e vhd
Vitis/2021.2/scripts/rt/data/unisim_VCOMP.vhd
Vitis/2021.2/scripts/rt/data/unisim_VCOMP_8h.vhd
Vitis/2021.2/scripts/rt/data/unisim_VCOMP_diablo.vhd
Vitis/2021.2/scripts/rt/data/unisim_VCOMP_e.vhd
Vitis/2021.2/scripts/rt/data/unisim_VPKG.vhd
Vivado/2021.2/data/vhdl/src/unisims/unisim_VCOMP.vhd
Vivado/2021.2/data/vhdl/src/unisims/unisim_VPKG.vhd
Vivado/2021.2/data/vhdl/src/unisims/unisim_retarget_VCOMP.vhd
Vivado/2021.2/ids_lite/ISE/vhdl/src/unisims/unisim_VCOMP.vhd
Vivado/2021.2/ids_lite/ISE/vhdl/src/unisims/unisim_VPKG.vhd
Vivado/2021.2/scripts/rt/data/unisim_VCOMP.vhd
Vivado/2021.2/scripts/rt/data/unisim_VCOMP_8h.vhd
Vivado/2021.2/scripts/rt/data/unisim_VCOMP_diablo.vhd
Vivado/2021.2/scripts/rt/data/unisim_VCOMP_e.vhd

# et pour unimacro :
$ fd unimacro -e vhd
Vitis/2021.2/scripts/rt/data/unimacro_VCOMP.vhd
Vivado/2021.2/data/vhdl/src/unimacro/unimacro_VCOMP.vhd
Vivado/2021.2/ids_lite/ISE/vhdl/src/unimacro/unimacro_VCOMP.vhd
Vivado/2021.2/scripts/rt/data/unimacro_VCOMP.vhd$ fd unimacro -e vhd
Vitis/2021.2/scripts/rt/data/unimacro_VCOMP.vhd
Vivado/2021.2/data/vhdl/src/unimacro/unimacro_VCOMP.vhd
Vivado/2021.2/ids_lite/ISE/vhdl/src/unimacro/unimacro_VCOMP.vhd
Vivado/2021.2/scripts/rt/data/unimacro_VCOMP.vhd
$ fd unimacro -e vhd
Vitis/2021.2/scripts/rt/data/unimacro_VCOMP.vhd
Vivado/2021.2/data/vhdl/src/unimacro/unimacro_VCOMP.vhd
Vivado/2021.2/ids_lite/ISE/vhdl/src/unimacro/unimacro_VCOMP.vhd
Vivado/2021.2/scripts/rt/data/unimacro_VCOMP.vhd

On s’assure d’avoir la variable d’environnement XILINX_VIVADO bien configurée :

$ export XILINX_VIVADO=/opt/Xilinx/Vivado/2021.2/Vivado/2021.2/
$ echo $XILINX_VIVADO
/opt/Xilinx/Vivado/2021.2/Vivado/2021.2/

Il peut être intéressant de mettre les librairies compilé à cet endroit pour y faire référence depuis tous ses projets ensuite :

cd data/vhdl
mkdir ghdl
cd ghdl
/usr/local/lib/ghdl/vendors/compile-xilinx-vivado.sh -all --vhdl2008
[...]

Pour compiler la librairie unisim pour vivado on utilisera le script suivant :

/usr/local/lib/ghdl/vendors/compile-xilinx-vivado.sh -a --vhdl2008
Loading environment...
Not all Xilinx primitives are VHDL-2008 compatible! Setting CONTINUE_ON_ERROR to TRUE.
Analyzing library 'unisim'...
Creating VHDL Library 'unisim'...
Analyzing files into library 'unisim'...
  WARNING: /opt/Xilinx/Vivado/2020.2/data/vhdl/src/unisims/primitive/SYSMONE4.vhd:1536:44:warning: prefix of array attribute must be an object name [-Wattribute]
SCRIPT ERROR: Unfiltered line
v_str_time_length := time'image(now)'length;
SCRIPT ERROR: Unfiltered line
^
  Warnings detected by filtering script.
Analyzing library 'secureip'...
Creating VHDL Library 'secureip'...
Analyzing files into library 'secureip'...
Analyzing library 'unimacro'...
Creating VHDL Library 'unimacro'...
Analyzing files into library 'unimacro'...
Analyzing library 'unifast'...
Creating VHDL Library 'unifast'...
Analyzing files into library 'unifast'...
Analyzing library 'secureip'...
Creating VHDL Library 'secureip'...
Analyzing files into library 'secureip'...
  WARNING: /opt/Xilinx/Vivado/2020.2/data/vhdl/src/unifast/secureip/GTHE2_CHANNEL.vhd:29:1:warning: entity "gthe2_channel" was also defined in file "/opt/Xilinx/Vivado/2020.2/data/vhdl/src/unisims/secureip/GTHE2_CHANNEL.vhd" [-Wlibrary]
SCRIPT ERROR: Unfiltered line
library IEEE;
SCRIPT ERROR: Unfiltered line
^
  Warnings detected by filtering script.
  WARNING: /opt/Xilinx/Vivado/2020.2/data/vhdl/src/unifast/secureip/GTXE2_CHANNEL.vhd:34:1:warning: entity "gtxe2_channel" was also defined in file "/opt/Xilinx/Vivado/2020.2/data/vhdl/src/unisims/secureip/GTXE2_CHANNEL.vhd" [-Wlibrary]
SCRIPT ERROR: Unfiltered line
library IEEE;
SCRIPT ERROR: Unfiltered line
^
  Warnings detected by filtering script.
--------------------------------------------------------------------------------
Compiling Xilinx Vivado libraries [SUCCESSFUL]

La compilation prend un temps infini, compter au moins 5 minutes sur un PC muni de 16 cœurs. Il suffira ensuite d’ajouter les lignes suivantes à son makefile CocoTB :


EXTRA_ARGS+=--std=08
EXTRA_ARGS+=-frelaxed-rules

UNISIMDIR=$(VIVADODIR)/data/vhdl/ghdl/xilinx-vivado/unisim/v93
EXTRA_ARGS=-P$(UNISIMDIR)
...
EXTRA_ARGS=-P$(UNIMACRODIR)

Et roulez jeunesse.

Les outils open source pour le FPGA – Live embedded event 2021

Le jeudi 3 juin 2021 a eu lieu, pour la seconde fois, le Live embedded event. Un ensemble de conférences totalement en ligne sur le sujet des systèmes embarqués et de l’open source.

J’ai eu l’honneur de pouvoir présenter une courte présentation sur les outils open-source pour le FPGA. La vidéo sera peut-être disponible dans quelques temps sur la chaîne youtube de l’événement. L’événement ayant été un succès, la technique a été relativement compliquée, avec peu de retour des «spectateurs» et quelque déboires filmatoires.

Pour ceux qui souhaitent retrouver les liens et les références de la présentation, je met le PDF de la présentation en ligne ici.

Compteur de «build» en Chisel

Ais-je bien téléchargé la dernière version de mon gateware dans ce montage ?

C’est un problème classique quand on fait de l’embarqué, entre l’éditeur de code, la génération du code bas niveau (Verilog dans le cas de Chisel) le logiciel de synthèse/placement-routage et le téléchargement du bitstream, il y a plein de façon de se tromper de version du logiciel s’exécutant réellement dans le montage. Et l’on peu passer des heures à chercher un bug dans son système en modifiant des lignes de code alors que le problème vient d’une même (vieille) version téléchargée éternellement.

Pour s’assurer que le gateware qui se trouve dans le FPGA soit bien le dernier généré il faut trouver une manière d’y enregistrer une valeur qui sera différente à chaque fois.

On pourrait mettre une version «en dur» dans nos sources, mais l’expérience montre que l’on oublie 4 fois sur 5 de l’incrémenter avant de générer le fichier. Toute bonne développeuse et tout bon développeur versionne son projet avec un gestionnaire de version. On pourrait du coup mettre le numéro de commit. Sauf que l’on ne commit pas toujours la modification avant de la tester.

Une autre astuce consiste à mettre la date EPOCH. De cette manière nous avons la date et l’heure précise de la synthèse du gateware. Cette solution est intéressante mais elle pose rapidement un problème d’occupation du FPGA. En effet, le temps en secondes filant assez rapidement nous avons besoin de registres de 32 bits minimum voir même 64bits. Ce qui est plutôt inutile pour compter les builds …

Compter les builds ?

La voila la solution toute simple, il suffit d’enregistrer un compteur dans un fichier texte. À chaque build, une fonction vient lire la valeur et l’incrémente. Pour rester à jour il nous suffit ensuite de versionner ce fichier avec le reste du code. Même si le nombre de build est grand, on n’aura rarement besoin d’en faire plus de quelques millier, ce qui rentrera très bien dans un registre de 16 bits voir moins.

Voici comment faire en Scala avec Chisel.

Dans un package avec les utilitaires «personnel» :

package myutil

On importe les packages d’entrées sorties pour lire/écrire dans les fichiers textes:

// Pour fromFile()
import scala.io.Source
// Pour PrintWriter()
import java.io._

Puis on crée une fonction de lecture écriture dans un objet personnel MyUtility :


  def genCountVers(className: String = null): Option[Int] = {
    if(className == null){
      return None
    }
    println("generate counter for class " + className)
    val filename = "src/main/scala/util/gen_count_vers.txt"
    val versmap = scala.collection.mutable.Map[String,Int]()

    /* read file to hashmap*/
    val fp = Source.fromFile(filename)
    for(v <- fp.getLines.map(_.split(",").map(_.trim))){
      versmap(v(0)) = v(1).toInt
    }
    fp.close()
  
    /* get and increment version */
    val version = versmap.get(className)
    if(version == None){
      versmap(className) = 0
    } else {
      versmap(className) = version.get.toInt + 1
    }

    /* write back file*/
    val fpw = new PrintWriter(new File(filename))
    for ((hname, hvalue) <- versmap) {
      fpw.write(hname + "," + hvalue + "\n")
    }
    fpw.close()

    /* return version */
    version match {
      case Some(s) => Some(s.toInt + 1)
      case None => Some(0)
    }
  }

La fonction genCountVers() va ouvrir le fichier texte gen_count_vers.txt, qui est un «CSV» composé du nom de classes et d’un numéro. Si le nom de la classe passé en paramètre n’existe pas la fonction va l’ajouter avec un compteur à zero.

Nous n’avons plus qu’a appeler la fonction genCounterVers() avec le nom de la classe (ou autre) de notre choix pour la fourrer dans un registre lisible via notre interface (spi, wisbone, i2c, jtag, …) directement sur le FPGA :

val buildnum = MyUtility.genCounterVers("MyTopClasse").get

Les interface Decoupled() en Chisel

La présentation de Jonathan Bachrach donne les bases d’utilisation des interfaces Decoupled() en Chisel. Cependant, elle date (2013). Il est donc nécessaire de se mettre un peu à jour.

Tout d’abord, il ne faut pas oublier d’inclure le package :

import chisel3.util._

Ensuite, l’interface possède en elle même l’information de direction. Il ne faut donc pas l’inclure dans un Output()/Input() lorsqu’on le déclare dans un IO(new Bundle {}) :

// Producteur
val data = Decoupled(UInt(10.W))
// Consommateur
val data = Flipped(Decoupled(UInt(10.W)))

Et pour finir, on ne déclare plus de Reg() comme donné à la fin de la présentation. Il faut utiliser RegNext() à la place :

results.valid := isResult
results.bits := result
isResult := RegNext(results.ready) & haveNewResult

Tout cela évitera les horribles traces Java assez illisible.

Sortie de GHDL version 1.0.0

[Dépêche initialement parue sur LinuxFR]

GHDL est un logiciel écrit en ADA permettant de faire l’analyse, la compilation, la simulation ainsi que la synthèse du VHDL. Le VHDL, quant à lui, est un langage de description matériel très utilisé dans le développement sur FPGA ou ASIC. À l’origine, GHDL est un « side-project » de Tristan Gingold lui permettant de se faire la main avec ADA.

GHDL est devenu l’outil indispensable pour faire de la simulation VHDL aujourd’hui. Après presque 20 ans de développement, voici que sort en version 1.0.0 le logiciel de simulation VHDL nommé GHDL. En prime, GHDL s’offre un nouveau logo:

logo GHDL

Peu d’informations ont filtré sur cette sortie pour le moment. On peut soupçonner que ce soit une sortie anniversaire pour marquer les 20 ans du logiciel. Il n’en reste pas moins que GHDL est devenu un maillon indispensable dans l’écosystème opensource du monde FPGA et des ASIC.

Cette version 1.0.0 supporte désormais complètement les standards 1987, 1993 et 2002 du langage défini par l’IEEE. Le support de VHDL 2008 est noté comme partiel pour le moment.

Depuis quelques années, le développement du projet s’est accéléré et supporte de mieux en mieux les projets tierces comme Yosys bien sûr mais également CocoTB pour les testbenchs écrit en Python ainsi que les standards de vérification comme UVVM, OSVVM, VUnit issues du standard d’accelera.

Un support partiel d’un langage de PSL (Properties Specification Language) est également inclus. Il permet de décrire les propriétés du système pour faire de la vérification formelle.

Et surtout, il est possible de faire de la Synthèse. Certes, l’extension ghdl-yosys-plugin est encore en développement, mais l’exemple TapTempo l’a montré : Il est tout à fait possible de faire de la synthèse VHDL avec.

Cette version mature est surtout une occasion de mettre en valeur cet outil indispensable dans le monde du développement numérique (gateware).

Aller plus loin