Tous les articles par Fabien Marteau

A propos Fabien Marteau

À la recherche des outils libre pour le FPGA.

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/

Il peut être intéressant de mettre les librairies compilé à cette 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 -a
[...]

Il y a une erreur dans le VHDL de xilinx qu’il faut corriger à la main avant de lancer le script de compilation. Dans le fichier `BUFG_GT_SYNC.vhd`, à la ligne 73 il faut remplacer :

    wait for 1ps;
    wait for 1 ps; -- notez l'espace

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 cores. Il suffira ensuite d’ajouter les lignes suivantes à son makefile CocoTB :

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

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.

Non non, vhd2vl n’est pas mort, contrairement à ce qui a été dit dans l’article de description du projet. Son auteur Larry Doolittle a publié une nouvelle version 2.5 l’été dernier (2015).

Et pour simplifier le développement collaboratif de ce programme un github a été ouvert. Comme il le dit sur sa page personnel du projet, Larry souhaite simplement intégrer son outils dans icarus (vhdlpp) de manière à pouvoir faire de la simulation VHDL avec. Mais pour faire une simple conversion VHDL->Verilog, vhd2vl est toujours d’actualité.