Tous les articles par Fabien Marteau

A propos Fabien Marteau

À la recherche des outils libre pour le FPGA.

ClaSH un HDL basé sur Haskell

La version 1.0 de CλaSH est sortie en septembre 2019. Profitons de cette sortie pour présenter ce langage de description matériel basé sur Haskell.

Logo de CλaSH

Mais qu’est-ce que CλaSH ? On pourrait commencer par le définir en décrivant ce qu’il n’est pas :

  • CλaSH (of cλaN ?) n’est pas un jeu vidéo se jouant en caressant un téléphone.
  • CλaSH n’est pas un groupe de musique se demandant s’il doit rester ou partir.
  • CλaSH n’est pas un HLS (High Level Synthesis). La fonction d’un logiciel HLS consiste à convertir un algorithme écrit dans un langage de programmation classique (très souvent du C) dans une architecture numérique en Verilog/VHDL.

Non.

CλaSH est un langage de description matériel basé sur le langage fonctionnel Haskell.

Le langage Haskell est né dans les années 90, fondé par une société secrète de mathématiciens qui voulait reprendre le contrôle des ordinateurs. En effet, vexés de voir tous ces geeks maîtriser cette machine mieux qu’eux ils décidèrent de se réunir de manière anonyme dans une cave (où il fallait entrer avec un mot de passe à base de développement limités sur un Algèbre commutatif isomorphe) pour fonder un nouveau langage compris par eux seuls. Le Haskell était né ! Enfin je crois 😉

Haskell est un langage fonctionnel, qui nous impose une autre façons de penser un programme informatique. Du dire des auteurs de CλaSH, le paradigme fonctionnel est plus à même de décrire du matériel que le paradigme impératif.

Un composant décrit en CλaSH est directement convertible en Verilog ou en VHDL (les deux sont gérés dans la version 1.0). Ce qui fait qu’un composant décrit en CλaSH est synthétisable avec n’importe quel logiciel de synthèse FPGA.

Dans le cas de ce type de langage on parle de générateur de code, un peu comme Chisel (basé sur Scala), Migen/Litex (basé sur Python) ou SpinalHDL (basé également sur Scala). Ces langages sont en fait des librairies ou des modules du langage auquel ils sont accolés.
Cette modularité permet de profiter d’un langage souvent bien établi avec énormément de fonctions optimisées (Scala, Python, Haskell…).

Le paradigme fonctionnel n’est pas du tout quelque chose qui coule de source pour un simple électronicien comme votre serviteur. Cette dépêche a donc été un peu différée le temps d’avaler les 100 premières pages de tutoriel permettant de faire un simple « hello world » en Haskell. Puis d’avaler le tutoriel CλaSH pour enfin faire clignoter cette fameuse LED sur un kit FPGA low cost de base.

Nous allons donc tenter un petit CλaSHtest permettant de faire clignoter une led.

Le type de base manipulé en CλaSH est le Signal. CλaSH ne permettant que des descriptions synchrones, un Signal représente une liste infinie de valeurs synchronisée sur une horloge et un reset pour la valeur initiale. L’horloge et le reset du signal sont décrit par le Domaine dom du signal. La forme d’un Signal est donc la suivante :

 Signal (dom :: Domain) a

En logique synchrone, le composant de base est le registre, nommé register:

register
     ( HiddenClockResetEnable dom dom
     , NFDataX a )
  => a
  -> Signal dom a
  -> Signal dom a

Son fonctionnement est assez basique : La valeur initiale (de reset) est donnée en premier argument, le second argument est le signal d’entrée qui est recopié sur le signal de sortie (valeur de retour de la fonction). On peut utiliser CλaSH en ligne de commande avec la commande clash.clashi.

$ clash.clashi
Clashi, version 1.0.1 (using clash-lib, version 1.0.1):
http://www.clash-lang.org/  :? for help
Clash.Prelude> 

Mais on préférera rapidement enregistrer notre module dans un fichiers source au format *.hs.

Pour faire clignoter une LED dans un FPGA, la première chose à mettre en place est un compteur :

Clash.Prelude> counter = register 0 counter + 1

Cette fonction counter que nous venons de créer retourne un registre initialisé à 0 et qui ajoute 1 à sa sortie à chaque coup d’horloge. La définition est récursive, c’est à dire qu’elle ajoute 1 à elle même. Et comme on commence à 0 et qu’on ajoute 1, la valeur initiale sera 1.
Cette fonction étant infinie, si nous voulons connaitre des valeurs il faut échantillonner :

Clash.Prelude> sampleN @System 10 counter
[1,1,2,3,4,5,6,7,8,9]

On constate que la première valeur est en double car c’est la valeur initiale, avant le premier coup d’horloge. @System désigne le domaine d’horloge/reset/enable utilisé. @System est le domaine général, nous pourrions appliquer un domaine (dom) spécifique à une architecture comme @XilinxSystem ou @IntelSystem.

Le problème avec ce compteur c’est qu’il incrémente à l’infini, nous on voudrait un compteur avec une limite et qui se remet à 0 à une certaine valeur comme ça :

Clash.Prelude> counter value = if(value < 100) then value + 1 else 0
Clash.Prelude> counter 10
11
Clash.Prelude> counter 0
1
Clash.Prelude> counter 101
0

[notes]
Je ne sais pas si je vais réussir à comprendre l’exemple de led qui clignote donné sur le blog de l’auteur de clash en fait.

Je suis parti un peu la fleur au fusil pour faire cette dépêche, mais je me rend compte que l’apprentissage de Clash (et surtout Haskell) est un loooong chemin.

Bref, je crois que je ne vais pas réussir à finir cette dépêche. Peut-être un jour aurais-je suffisamment de bouteille en Haskell pour comprendre Clash, mais là …

RapidSilicon va-t-elle renverser la table ?

Le FLF a été fondé a une époque obscure où il n’existait (quasiment) pas de solution open-source pour travailler sur des FPGA. Seul un petit village d’irréductibles simulateurs comme icarus, ghdl ou verilator maintenaient un semblant de liberté dans cet océan de sombre verrouillage. Il y avait bien VTR et Alliance, mais ils restaient très universitaire et un peu trop centré sur les ASIC.

Puis est arrivé Yosys le logiciel libre de synthèse Verilog accompagné du reverse engineering de l’ICE40 avec Icestorm. Ont suivis Apicula pour les Gowin, X-Ray pour les Xilinx série 7 ou Trellis pour les ECP5 (Voir le tableau des projets de reverse sur le FLF).

Et pour boucler la boucle de développement, le logiciel libre de placement routage nextpnr a émergé ainsi que le logiciel de configuration universelle openFPGALoader.

Toutes ces initiatives ont énormément gagnées en qualité et en crédibilité depuis 3 ou 4 ans et sont en passe de devenir les références dans le monde du FPGA.

À ces nouvelles très réjouissantes se sont ajouté les annonces de plusieurs mise en production de FPGA avec les outils open-source supportés officiellement par les constructeurs. On pense notamment à :

  • Quicklogic et son microcontrôleur EOS S3 basé sur un cœur Cortex-M4 couplé à une «zone» de FPGA utilisable intégralement avec des logiciels open source.
  • CLEAR: un projet plus anecdotique consistant à générer un FPGA à partir de l’outil open source openFpga et à l’intégrer à la Caravel de la société eFabless pour la graver en 130nm. Pour le coup les outils pour développer le FPGA sont opensource ainsi que les outils pour développer sur le fpga.
  • CologneChip et son GateMate: un FPGA à l’architecture assez original notamment pour la partie calcul/dsp mais qui se défend assez bien niveau taille.

Avec toutes ces nouvelles on aurait pensé que cela allait se calmer et que l’on aurait le temps de digérer et faire clignoter quelques LED avant de passer à une vitesse encore supérieure.

ERREUR !

C’était sans compter sur cette société créé en 2020 (et oui toute neuve) et son annonce de sortie du projet Gemini (un poil prétentieux tout de même pour le nom 😉 de faire un SoC haute performances en se basant sur des outils open sources, et oui même pour la fabrication du FPGA. En parallèle du développement silicium, RapidSilicon développe un IDE open source nommé Raptor se basant sur tout un tas de logiciels libres cité au début de cet article :

Un premier échantillonnage de composants en 16nm est déjà sorti des fonderies de TSMC et nous rassure sur la réalité du produit. Nous ne sommes pas dans le cas d’une société qui fait des annonces «vaporware» juste pour faire des levées de fond.

Hâte de voir la datasheet

L’architecture du prototype est donnée dans la figure suivante:

On est donc dans le cas d’un gros SoC-FPGA avec les caractéristiques suivantes :

  • Arm A53 Dual-Core
  • contrôleur de DDR4
  • «Petit» core Risc-V 32bit pour le temps réel et la supervision
  • Les ports habituels d’un SoC : UART, USB, SPI, I²C, …
  • Du PCIe Gen4, XGMII (pour le réseau)
  • Des liens serdes (très) rapide jusqu’à 16Gbits/s
  • La zone FPGA n’est pas énorme, mais se défend face aux petit Zynq par exemple :
    • 50-75K LUT (6 entrées)
    • Blocks DSP (combien ?)
    • Block de Ram double port 18kb (Combien ?)

Toutes ces parties sont liée ensemble au moyen d’un bloc d’interconnexion nommé FlexNOC.

Alors ?

Xilinx doit-elle trembler avec son Zynq ? et Microsemi avec son PolarFire SoC ?

L’avenir nous le dira, hâte de voir la suite 🙂

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.

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

Un CPU RISC-V avec des instructions «maison» reconfigurable à volonté

Le standard RISC-V permet l’ajout d’extension «maison» par le constructeur. Cela fait longtemps qu’on ne propose plus l’ajout de puce à coté du processeur pour ajouter des instructions (comme le coprocesseur arithmétique sur les 386), la perte en bande passante est vraiment trop grande.

Le chinois Andes technology et le Français Menta s’associe pour proposer une solution eFPGA permettant de synthétiser «à la demande» ces instructions maison.

La société Menta va-t-elle publier la spécification du «bitstream» permettant de configurer cette partie ? Rien n’est moins sûr, mais on l’encourage à faire comme quicklogic.

L’annonce de partenariat par Andes technologie et Menta.

ULX3S ou OrangeCrab ?

Deux cartes à base d’ECP5 ont été lancée coup sur coup le même weekend pour fêter le début du confinement : OrangeCrab et ULX3S. Ces deux cartes sont accessibles via un financement participatif, l’une avec groupsget et l’autre avec crowdsupply. Les deux cartes supportent à 100% toute la chaine de développement open source Yosys + NextPnR + Trellis.

Et les deux sont proposées au même prix de départ : $99. Vu qu’elles sont toutes les deux à base d’ECP5 et au même prix voyons voir un peu ce qu’elles ont dans le ventre.

OrangeCrab, de la DDR3 sur batterie.

Ce qui frappe avec l’OrangeCrab c’est sa capacité mémoire avec un chip DDR3 de 1Gbits. C’est également une toute petite carte qui tiens presque sur le doigt (à condition de choisir le bon).

Et elle possède un connecteur de batterie permettant de la rendre autonome et «portable».

Image provenant de groupgets

Le détails des caractéristiques est donné sur le site :

  • 24kLut
  • 1008 Kb –de blocs RAM
  • 194 Kb – RAM Distribuée
  • 28 – 18×18 Multiplieurs
  • PLLs: 2
  • oscillateur interne
  • 1Gbits DDR
  • Full-speed (12Mbit) USB avec connection direct sur le FPGA
  • 128Mbit QSPI de mémoire FLASH
  • Connecteur MicroSD
  • SAR ADC, external RC / input comparateur
  • Système de gestion de batterie

ULX3S la console de jeux à base de FPGA

L’ULX3S est nettement plus grosse que l’OrangeCrab sur beaucoup de points. Sur le nombre de composants ajoutés déjà. Puisque l’on peut noter la présence d’un connecteur microSD, de 8 leds, d’un port USB connecté au FPGA via un convertisseur FTDI plus un USB connecté en direct, d’un module Wifi/Bt, d’un port vidéo GPDI d’une sortie audio, de 6 boutons, de …. mais dites donc ne serait-ce pas là tous les élements nécessaire pour faire une console de jeu ?!

Image provenant de la page crowdsupply

Par contre, la version à 99$ démarre avec le plus petit FPGA de la gamme, le ECP5 12F, deux fois plus petit que celui de l’OrangeCrab. Il est cependant possible d’acquérir la carte avec des FPGA (nettement) plus gros comme le 45F (135$) et le 85F (155$).

Mais même avec le 12F, l’équipement de cette ULX3S tel que copié/collé ci-dessous reste impressionnant:

  • FPGA: Lattice ECP5
    • LFE5U-85F-6BG381C (84 K LUT)
    • LFE5U-45F-6BG381C (44 K LUT)
    • LFE5U-12F-6BG381C (12 K LUT)
  • USB: FTDI FT231XS (500 kbit JTAG and 3 Mbit USB-serial)
  • GPIO: 56 pins (28 differential pairs), PMOD-friendly with power out 3.3 V at 1 A or 2.5 V at 1.5 A
  • RAM: 32 MB SDRAM 166 MHz
  • Flash: 4-16 MB Quad-SPI Flash for FPGA config and user data storage
  • Mass Storage: Micro-SD slot
  • LEDs: 11 (8 user LEDs, 2 USB LEDs, 1 Wi-Fi LED)
  • Buttons: 7 (4 direction, 2 fire, 1 power button)
  • Audio: 3.5 mm jack with 4 contacts (analog stereo + digital audio or composite video)
  • Video: Digital video (GPDI General-Purpose Differential Interface) with 3.3 V to 5 V I²C bidirectional level shifter
  • Display: Placeholder for 0.96″ SPI COLOR OLED SSD1331
  • Wi-Fi & Bluetooth: ESP32-WROOM-32 supports a standalone JTAG web interface over Wi-Fi
  • Antenna: 27, 88-108, 144, 433 MHz FM/ASK onboard
  • ADC: 8 channels, 12 bit, 1 MS a/s MAX11125
  • Power: 3 Switching voltage regulators: 1.1 V, 2.5 V, and 3.3 V
  • Clock: 25 MHz onboard, external differential clock input
  • Low-Power Sleep: 5 µA at 5 V standby, RTC MCP7940N clock wake-up, power button, 32768 Hz quartz with CR1225 battery backup
  • Dimensions: 94 mm × 51 mm

Soyons sage, patientons avant de dégainer sa monnaie 😉

Célèbre mème Futurama

OrangeCrab

Ça y est, la carte à base d’ECP5 tant attendu est enfin disponible sur le site groupsget.

La carte qui est utilisable avec une chaîne de développement intégralement libre est constituée de:

  • 24kLut
  • 1008 Kb – Embedded Block RAM
  • 194 Kb – Distributed RAM
  • 28 – 18×18 Multipliers
  • PLLs: 2
  • Internal oscillator
  • 1Gbits DDR
  • Full-speed (12Mbit) USB with a direct connection to the FPGA
  • 128Mbit QSPI FLASH Memory
  • MicroSD socket
  • SAR ADC, external RC / input comparator of FPGA
  • Battery voltage sensing

Le tout pour des dimensions rikiki de 22.86mm x 50.8mm (0.9″ x 2.0″)

Ça fait quelques mois déjà que tout le monde l’attendait. Elle est disponible pour $99 à l’achat dès aujourd’hui.

Retour de Conférence ORConf 2019

Je remercie mon entreprise Armadeus Systems de m’avoir permit d’assister à cette septième conférence OpenRisc 2019.

Libérez vos flip-flop !

L’ORConf est organisée par la fondation FOSSi qui promeut la liberté dans le matériel, que l’on parle d’outils ou de composants matériel. L’objet de la première conférence fut justement sur l’histoire de cette organisation.

FOSSi foundation est une évolution de l’association opencore, les fondateurs de FOSSi n’étaient pas satisfait de cette structure et de l’organisation de la gestion des projets. La fondation FOSSi a pour but de promouvoir le logiciel libre et le matériel libre et de servir de support aux différents projets libres. Elle apporte un soutien logistique pour l’hébergement elle sert d’interface avec le projet google summer of code. Sa mission est également d’organiser des événements comme l’ORConf pour faciliter les rencontres entre les différents acteurs du matériel libre.

C’est la première fois que la conférence se déroulait en France, à Bordeaux dans les locaux de l’école d’ingénieur ENSEIRB-MATMECA. Une école que je connais bien puisque c’est l’école dans laquelle j’ai passé mon diplôme d’ingénieur 😉

Une fois l’introduction de la fondation passée, la journée du vendredi s’est enchaînée avec une présentation de la Chips Alliance pas Zvonimir Z bandic employé de Western Digital puis avec une discussion autour des licences open sources du CERN à destination spécifiquement du matériel.

Nous avons eu la chance d’avoir une présentation de la fondation RISC-V par Calista Redmond – récemment nommée CEO de l’organisation – pour nous parler de la révolution en cours.

Mais les conférences ne sont pas réservées au jeux d’instruction RISC-V, nous avons pu avoir un aperçu d’un processeur autour du jeux d’instructions OpenPower (de plus en plus libre) ainsi que du processeur OpenRisc (le samedi) développé sur le temps libre de Stafford Horne principalement (temps pas si libre que ça puisqu’il a des enfants;).

Après quelques discussions à propos des outils disponibles autour du VHDL pour la vérification de la syntaxe et des règles de codage la journée du vendredi s’est terminée par une présentation de l’avancée des outils libres pour le développement sur ASIC par Luis Eduardo Rueda Gruerrero de Symbiotic EDA. Luis participe au développement d’un processeurs RISC-V 32bits nommé ASICone en ayant – comme son nom l’indique– la fabrication d’un silicium avec le plus possible de logiciels libres comme objectif. Le développement intégralement open source est encore compliqué, notamment en ce qui concerne l’analyse de la consommation, l’arbre d’horloge ainsi que la description des librairies de composants.

Cette demi-journée fut bien chargée en informations annonçant bien la suite le samedi.

Beaucoup de choses à digérer de la journée de samedi. À titre personnel je retiens surtout les avancées de Cocotb version 1.2 dont le mainteneur est un membre de la fondation FOSSI. En plus du support complet de Python3 et les directives «async», cocotb 1.2 permet désormais d’être utilisé sans Makefile car intégré complètement dans le système de packaging Python.

Mais le futur de Cocotb semble très intéressant avec le support de verilator comme simulateur. Le travail pour le support de verilator était surtout à faire coté verilator et non Cocotb, mais un patch semble être sur les rails chez Wilson.

Malgré son nom très «vacances à la plage», cocotb est le nouveau système permettant d’écrire des testbenchs qui est de plus en plus utilisé en entreprise aujourd’hui. Il remplace allègrement les UVM, VUNIT qui font si mal à la tête.

Jeremy Bennett nous a présenté un nouveau banc de test nommée emBench en cours de définition pour que les différentes architectures de processeurs puissent comparer leurs zizi. L’objectif étant d’avoir un testbench libre et gratuit pour pouvoir l’exécuter sur toutes les plate-formes et faire de beaux tableaux comparatif.

L’après midi fut marqué par une série de «ligthning talks» de 3 minutes chacune. Avec les avancées du développement de SymbiFlow (impressionnantes) notamment pour le support de l’artix7 ainsi que par une présentation de Clash qui vient de passer à sa version 1.0. Sans oublier la présentation des cœurs RISC-V pour ASIC développés par la société russe syntacore et les avancées du langage Chisel3.

À noter aussi la remarquable performance de Pepin de Vos avec sa présentation intégralement réalisés sur un softCore tournant sur FPGA (GOWIN). Il est désormais possible grâce au travail de Tristan Gringold de synthétiser du VHDL avec Yosys. C’est ce qu’a utilisé Pepin pour réaliser son système à base de logique 7400. Cependant le nombre de composant étant trop important il s’est contenté d’une synthèse sur FPGA pour cette présentation.

Pour que toute la chaîne de développement sur FPGA soit libérée, un bon logiciel de placement routage est nécessaire. C’est le rôle du nouveau logiciel Nextpnr que David Shah nous a présenté. Le développement de nextpnr avance bien. N’hésitez pas à le soutenir sur patreon.

Enfin, la journée s’est terminé sur les berges de la Garonne par un dîner concert dans la guinguette «chez alriq». Cela qui m’a permis de passer de l’autre coté du fleuve, ce qui ne m’était jamais arrivé durant mes trois ans de scolarité à Bordeaux !

Pour le restaurant, c’est Google qui régale

Le dimanche ne fut pas sans repos non plus et fut marqué par une conférence très dynamique de Jose E. Marchesi et son nouveau logiciel d’édition de binaire (ELF, mp3, …) poke. Une présentation très vivante et passionnante, tout le monde achète 😉

Les interfaces (connecteurs) présenté par Alan J.Wood sont aussi très intéressantes. L’objectif des connecteurs mixMOD et Blackedge présentés est de pouvoir s’adapter aux PMOD très présent dans les kits de développement FPGA tout en ajoutant des pins analogique. L’idée est d’avoir un standard pour bricoler dans son garage et pour équiper les salles de TP pour l’éducation.

N’oublions pas la présentation de l’impressionnant travail abattu par l’université de Zurich avec leur projet PULP. Le travail de l’équipe PULP est de concevoir et produire des ASIC pour l’embarqué à base d’architectures parallèle. L’objectif est de publier en open source le plus possible les outils utilisés. Leur processeur nommé Arnold est particulièrement remarquable car il intègre une matrice FPGA nommée eFPGA fournie par QuickLogic. Pour l’instant les outils de synthèse et de placement routage sont en source fermés, mais il est prévu de fournir des outils libre pour cette matrice.

Todd Strader nous a parlé de son projet de protection d’IP Verilog à base de verilator permettant d’éviter l’horrible système de chiffrement des IP proposé habituellement par les constructeurs et empêchant l’utilisation de simulateur libre. Tout en ayant une sécurité très relative quand au piratage de la dite IP chiffrée.

Dan Gisselquist nous a démontré que la plupart des IP proposées par les fondeurs à base de bus AXI ne respectent pas le standard et sont souvent buggé ! Ces bug ressortent très facilement grâce à la vérification formelle.

Et enfin, n’oublions pas la présentation de la nouvelle entreprise local Hiventive et son système de coordination de simulateurs en ligne.

Pour conclure, cette conférence fut très intense en présentations. Beaucoup d’acteurs du matériel libre étaient présent. Un des grand intérêt de cette conférence était aussi de pouvoir rencontrer en personne des acteurs que l’on ne côtoyait avant qu’a travers des messagerie.

Une question reste sur toutes les langues : Où se passera l’ORConf 2020 ?

[Edit: 12 novembre 2019]

Les vidéos des conférences sont désormais disponible sur youtube.

Un composant électronique TapTempo avec Chisel3

Le «défi TapTempo» a été lancé sur LinuxFr il y a quelques semaines. L’objectif est de réaliser la mesure du tempo de l’appui sur une touche et de l’afficher simplement dans la console le résultat. La mesure du tempo s’effectue par défaut sur 5 appuis consécutif et affiche une moyenne en bpm (Beats Per Minute). L’idée est de réaliser la fonction dans divers langages informatiques pour que chacun puisse promouvoir son langage favoris. Beaucoup de langages ont été représenté jusqu’à présent, mais aucun langages de description matériel n’avait encore été proposé.

Pour palier ce gros manquement dans les langages représenté je vous propose ici de réaliser TapTempo en Chisel (version 3).

Architecture générale

L’idée ici n’est donc plus d’écrire un programme pour calculer le tempo mais de décrire l’architecture d’un composant matériel permettant de réaliser la fonction. Le matériel visé sera un FPGA, nous laissons de coté le développement sur ASIC. Même si une fois terminé il ne devrait pas y avoir de problème pour être porté sur un ASIC, si quelqu’un a suffisamment d’argent pour le claquer dans ce genre d’ânerie 😉

Le bloc fonctionnel de notre composant sera donc constitué d’une entrée button recevant le signal de l’appui sur un bouton permettant de faire le tempo. Dans un premier temps nous laisserons de coté les problèmes de métastabilité ainsi que de rebond. L’implémentation réel dans un FPGA nécessitera obligatoirement l’ajout d’un étage de synchronisation du signal d’entrée avec l’horloge ainsi que d’un bloc «anti-rebond», aucun bouton réel n’étant capable de faire un signal vraiment propre.

La sortie du bloc sera constitué d’un entier non signé bpm dont nous allons discuter la taille ci-dessous.

Et comme nous somme dans un FPGA il est indispensable de concevoir notre fonction synchrone d’une horloge, et souhaitable d’avoir un reset général.

Structure interne

La structure interne de TapTempo est donnée ci-dessous:

L’idée est de compter des ticks générés par timepulse au moyen du compteur count. Quand un appui sur le bouton est détecté, le compteur se remet à zéro et la valeur est enregistrée dans le tableau countx. À chaque coup d’horloge l’addition des 4 valeurs count est réalisée puis on divise par 4. La division par 4 est réalisable dans un FPGA au moyen d’un simple décalage à droite de 2. Vient la partie la plus compliqué : se servir de cette période moyenne pour diviser TMINUTE et obtenir la valeur du tempo en bmp.

Un peu de dimensionnement

On ne fonctionne pas dans un FPGA comme on fonctionne avec un pc, quand on fait des opérations sur des nombres il faut les dimensionner. Et il est fortement recommander d’utiliser des entiers, car le calcul flottant nécessite tout de suite une quantité de ressources phénoménale.

Notre objectif est de mesurer une cadence musicale en bpm que l’on puisse «taper à la main», si l’on regarde l’article wikipedia consacré au Tempo, on se rend vite compte qu’attendre les 200bpm est déjà pas mal. Disons que pour prendre une très large marge nous mettons la marge supérieur à 270bpm. Nous aurons donc en sortie une variable entière sur 9bits ( int(ln2(270))+1).

À 270bpm, le temps entre deux tempos est de ~222ms ce qui nous donnerais une fréquence de pulse de 4.5Hz. Cependant, si nous voulons une précision de 1bpm il va falloir augmenter cette fréquence , pour avoir un chiffre rond nous prendrons un temps de 1ms, soit une fréquence de 1kHz. Ce qui est un peu juste pour 270bpm, mais conviendra à la démonstration.

Décomposons le code

Le code se trouve sur le dépôt github suivant.  Nous allons décrire la description du module à proprement parlé qui se trouve ici.

Dans l’entête du module nous allons retrouver le port d’entrée button ainsi que le port de sortie bpm. Point d’horloge ni de reset ici puisqu’en Chisel ces signaux sont implicite.

// default clock 100Mhz -> T = 10ns
class TapTempo(tclk_ns: Int, bpm_max: Int = 270) extends Module {
val io = IO(new Bundle {
// val bpm = Output(UInt(8.W))
val bpm = Output(UInt(9.W))
val button = Input(Bool())
})

Quelques constantes qui nous servirons ensuite :

  /* Constant parameters */
  val MINUTE_NS = 60*1000*1000*1000L
  val PULSE_NS = 1000*1000
  val TCLK_NS = tclk_ns
  val BPM_MAX = bpm_max

  /* usefull function */
  def risingedge(x: Bool) = x && !RegNext(x)

Pour notre générateur de pulses il suffit d’utiliser une classe présente dans la bibliothèque «util» de chisel : Counter. Qui comme son nom l’indique … compte !

import chisel3.util.Counter
[...]
  val (pulsecount, timepulse) = Counter(true.B, PULSE_NS/tclk_ns)
[...]

Ce compteur prend en paramètre un signal de comptage (ici true.B -> compte tout le temps) ainsi que la valeur max à atteindre.
L’instanciation de l’objet retourne un compteur ainsi qu’un signal qui passe à ‘1’ quand le compteur se remet à zero (quand il dépasse la valeur max).

Ce signal timepulse sera ensuite utilisé par un deuxième compteur 16 bits tp_count que nous allons écrire «à la main» cette fois.
On défini d’abord le registre de 16bits que l’ont initialise à 0 (0.asUInt(16.W))

  val tp_count = RegInit(0.asUInt(16.W))

Puis on écrit le code décrivant le «comptage»:

  when(timepulse) {
    tp_count := tp_count + 1.U
  }
  when(risingedge(io.button)){
    // enregistrement de la valeur du compteur.
    countx(count_mux) := tp_count
    count_mux := Mux(count_mux === 3.U, 0.U, count_mux + 1.U)
    // remise à zéro du compteur
    tp_count := 0.U
  }

Ce deuxième compteur compte les pulse «timepulse» et se remet à 0 lorsqu’un front montant est détecté sur le bouton (quand on appuis sur le bouton).

Pour stocker les 4 valeurs permettant de réaliser une valeurs nous déclarons un vecteur de registres de 19 bits (pour gérer les retenues quand nous ferons l’addition):

  val countx = RegInit(Vec(Seq.fill(4)(0.asUInt(19.W))))

Ainsi que le «pointeur» :

  val count_mux = RegInit(0.asUInt(2.W))

La gestion de l’incrémentation du pointeur ainsi que de l’enregistrement du compteur se trouve dans le code du «compteur de pulse» que nous avons vu plus haut:

    // enregistrement de la valeur du compteur.
    countx(count_mux) := tp_count
    count_mux := Mux(count_mux === 3.U, 0.U, count_mux + 1.U)

Pour faire la somme rien de plus simple, il suffit de faire ‘+’ :

  val sum = Wire(UInt(19.W))
[...]
  sum := countx(0) + countx(1) + countx(2) + countx(3)

Et la division par 4 se résume à un décalage:

  val sum_by_4 = sum(18, 2)

Et nous arrivons à la partie la plus compliquée du design : diviser. Diviser est quelque chose de très compliqué dans un FPGA, on peut réaliser un design permettant d’effectuer une divisions en plusieurs cycles d’horloge, mais c’est tout de suite une très grosse usine à gaz.

Pour la division permettant de faire la moyenne des échantillons nous nous en étions sortie en faisant une division par une puissance de 2, qui se résume de fait à un simple décalage. Mais cette fois on ne pourra pas s’en sortir avec ce genre de pirouette. Car notre division à réaliser est la suivante :

   Nombre de pulse dans une minute
-------------------------------------  = valeur en bpm
moyenne des pulses mesuré (sum_by_4)

Pour nous en sortir la première idée serait de faire une table de valeurs pré-calculée pour chaque valeurs de  sum_by_4. Ce qui se fait très simplement en scala avec une séquence :

val x = Seq.tabulate(pow(2,16).toInt-1)(n => ((MINUTE_NS/PULSE_NS)/(n+1)).U)

Sauf que le nombre de valeurs est particulièrement grand (2^16) et qu’on pourrait certainement factoriser un peut tout ça.

Pour réduire la taille ne notre tableau il faut prendre le problème à l’envers: combien de valeurs possible puis-je avoir en sortie ?

La réponse est simplement 269 puisque je peux aller de 1 à 270bpm. Nous allons donc réaliser un vecteur de 270 valeurs contenant la valeurs minimum du compteurs permettant d’atteindre notre résultat. Et nous mettrons ces valeurs dans 270 registres.

  val bpm_calc = RegInit(Vec(x(0) +: Seq.tabulate(bpm_max)(n => x(n))))

Pour obtenir la valeur finale il faut ensuite générer 270 inéquations ayant pour résultat un vecteur de 270 bits :

  for(i <- 0 to (bpm_max-1)) {
    bpm_ineq(i) := Mux(sum_by_4 < bpm_calc(i), 1.U, 0.U)
  }

Le résultat correspondra ensuite à l’index du dernier bit à 1 dans ce vecteur.

Pour récupérer cet index, une fonction très utile est fournie dans la bibliothèque «util» de chisel : le PriorityEncoder. Qui permet d’obtenir l’index du plus petit bit à 1 dans un vecteur… sauf que nous on veut le plus grand !

Mais ce n’est pas très grave, il suffit de retourner le vecteur avec Reverse puis de faire une soustraction pour avoir le résultat :

  io.bpm := bpm_max.U - PriorityEncoder(Reverse(bpm_ineq.asUInt()))

Simulation

Le test permettant de simuler le design se trouve dans le fichier TapTempoUnitTest.scala

Il peut être lancé avec la commande sbt suivante :

sbt 'test:runMain taptempo.TapTempoMain --backend-name verilator'

Et les traces VCD générées sont disponible dans le répertoire ./test_run_dir/taptempo.TapTempoMain962904038/TapTempo.vcd

Visualisable simplement avec gtkwave.

gtkwave ./test_run_dir/taptempo.TapTempoMain962904038/TapTempo.vcd

Trace vcd de la simulation TapTempo

À suivre

Pour être vraiment complet il faudrait ajouter la gestion des rebonds ainsi que la synchronisation du signal d’entrée puis tester la synthèse. Mais ce sont de nouvelles aventure qui continuerons peut-être dans un prochaine épisode 🙂 .

De beaux chronogrammes avec Wavedrom

wavedrom est une librairie html/javascript permettant de faire de superbes rendu de chronogrammes.

La syntaxe en JSON est relativement simple et permet de créer très rapidement des chronogrammes dans sa page web.

<script type="WaveDrom">
{ signal : [
{ name: "clk", wave: "p......" },
{ name: "bus", wave: "x.34.5x", data: "head body tail" },
{ name: "wire", wave: "0.1..0." },
]}
</script>

Si l’on souhaite faire une utilisation hors ligne de wavedrom il est possible de télécharger l’éditeur sur le site. L’éditeur permet de faire des modifications du code json avec le rendu en live. Il permet également d’enregistrer le rendu sous un format image pour que l’on puisse incorporer le chronogramme dans son document.

wavedromeditor

Très pratique pour écrire ses documents de spécification.