Journal Un composant électronique TapTempo avec Chisel3

Posté par (page perso) . Licence CC by-sa
56
15
avr.
2018

Le «défi» TapTempo est un peu en train de faiblir je trouve. Du coup je vous propose un nouveau langage pour réaliser TapTempo : Chisel. Et pour être plus précis, la version 3 de Chisel.

Contrairement à tous les langages proposés jusqu'ici, Chisel ne permet pas de réaliser un programme qui sera exécuté par une machine réelle ou virtuelle. Chisel permet de décrire l'architecture de la machine elle-même !

C'est ce qu'on appelle un langage de description matériel ou HDL pour Hardware Description Langage. Ce sont des langages utilisés pour décrire les «IP» pour les FPGA, les CPLD mais aussi (et surtout en fait) les ASIC.

Les deux HDL les plus connus sont le VHDL et le Verilog. Cependant, ce sont de vieux langages extrêmement verbeux et pas très modulaires. Un design correct en simulation ne le sera pas forcément en synthèse.

C'est pourquoi plusieurs nouveaux langages de description de matériel sont en train de voir le jour aujourd'hui. Chisel est l'un d'eux. Lancé initialement par l'université de Berkley, Chisel est basé sur le langage Scala. Il permet de faire une description synchrone et synthétisable de son design. La même université utilise Chisel pour faire des «core» Risc-V.

Pour pouvoir être compatible avec la plupart des logiciels de synthèse du marché les IP écrites en Chisel sont ensuite converties en (en passant par un langage «netlist» intermédiaire nommé FIRRTL) Verilog, qui lui sera synthétisable et simulable avec tous les logiciels connus.

Dans cette «IP» TapTempo nous laisserons de côté la partie synchronisation du signal externe ainsi que la gestion des rebonds du bouton qui sont propres à l'intégration. Nous nous concentrerons sur l'essence de la fonctionnalité.

Vue «externe» de TapTempo

Plutôt que de taper une touche, l'idée ici est d'utiliser un bouton. Le résultat se présente ensuite sous la forme d'un signal binaire sur 9 bits. D'après la page wikipedia des battements par minute, 270bpm est déjà très rapide, pas la peine d'aller plus loin dans le dimensionnement. Par conséquent 9 bits suffisent pour présenter le résultat (entier positif).

Architecture de TapTempo

Le schéma d'architecture de TapTempo est un peu «à main levée» mais ça permet de comprendre l'esprit du composant. Un générateur de «ticks» génère des ticks à une fréquence de 1kHz. Ces ticks sont comptés par le compteur «count».
À chaque appui sur le bouton le résultat du compteur est stocké dans un registre «countx», le pointeur «mux» est incrémenté et le compteur est remis à zéro.

On choisira une architecture avec seulement 4 registres d'échantillons «countx» car il est incroyablement plus facile de faire une division par 4 (un simple décalage à droite de deux) que si nous avions eu 5 valeurs comme pour les autres programmes.

Les 4 valeurs «countx» sont donc additionnées puis divisées par 4 et vient le problème de la division (inversion) permettant de convertir une période moyenne en une fréquence.

Il est très compliqué de diviser dans un FPGA. On emprunte donc généralement des chemins détournés pour arriver à nos fins. Ici nous allons créer une table de registres avec toutes les valeurs «en dur» pré-calculées. Puis nous comparerons (inégalité) la valeur du compteur à tous les registres de manière parallèle (toutes les comparaisons se font en même temps), le résultat sera un vecteur de 270 bits rempli de '1' jusqu'à la valeur voulue.
Pour avoir une sortie sur 9 bits et non sur 270 (ça fait vraiment trop de LED à souder pour la maquette !) Chisel fournit une petite fonction bien utile permettant de sortir la valeur de l'index du bit de poids faible à '1' : le PriorityEncoder. Comme nous on veut le poids fort on retournera le vecteur et on fera une petite soustraction de 270.

Voila pour le paquet d'explications. Et voila pour le code :

package taptempo

import chisel3._
import chisel3.util.{Counter, PriorityEncoder, Reverse}
import scala.language.reflectiveCalls  //avoid reflective call warnings
import scala.math.pow

// 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(9.W))
    val button = Input(Bool())
  })
  /* 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)

  val tp_count = RegInit(0.asUInt(16.W))
  val (pulsecount, timepulse) = Counter(true.B, PULSE_NS/tclk_ns)

  val countx = RegInit(Vec(Seq.fill(4)(0.asUInt(19.W))))
  val count_mux = RegInit(0.asUInt(2.W))
  val sum = Wire(UInt(19.W))

  /* div array calculation */
  val x = Seq.tabulate(pow(2,16).toInt-1)(n => ((MINUTE_NS/PULSE_NS)/(n+1)).U)
  val bpm_calc = RegInit(Vec(x(0) +: Seq.tabulate(bpm_max)(n => x(n))))
  val bpm_ineq = RegInit(Vec(Seq.fill(270)(0.asUInt(1.W))))

  when(timepulse) {
    tp_count := tp_count + 1.U
  }
  when(risingedge(io.button)){
    countx(count_mux) := tp_count
    count_mux := Mux(count_mux === 3.U, 0.U, count_mux + 1.U)
    tp_count := 0.U
  }

  sum := countx(0) + countx(1) + countx(2) + countx(3)

  val sum_by_4 = sum(18, 2)

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

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

La totalité du code, des outils de simulation et de visualisation sont disponibles sur un dépôt github et accessible depuis la TapTempo Fédération.

Il est possible de simuler le composant avec la commande sbt suivante:

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

Un jour je ferais un vrai journal sur Chisel, mais là c'était surtout histoire d'être dans la course à TapTempo ;)

  • # un nouveau HDL !

    Posté par . Évalué à 5 (+2/-0).

    Enfin un HDL open source à la base :) Ce n'est pas si courant.

    Une question bête : est-ce qu'il est facile de faire une sorte de code "behavioral VHDL". En 2000, cela permettait de sortir l'horloge du design, mais au prix d'une synthèse lente. Cela permettait de faire générer des machines d'état avec des "wait" de signal. Cela permet aussi de redécouper un pipeline après coup (en VHDL, il faut ajouter des registres en sortie et faire confiance au synthétiseur).

    "La première sécurité est la liberté"

    • [^] # Re: un nouveau HDL !

      Posté par (page perso) . Évalué à 1 (+0/-0).

      Enfin un HDL open source à la base :) Ce n'est pas si courant.

      Ça se discute, il commence à y avoir pas mal de nouveau langages open-source intéressants aujourd'hui. j’essaie d'en maintenir une liste plus ou moins exhaustive avec mon schéma OpenSourceFPGAMap.

      Je pense notamment à Migen, MyHDL (python) ou à Clash (Haskell), sans compter les dérivés de scala (pourquoi cet amour immodéré pour scala ?) : SpinalHDL et DFiant. Tout ces nouveaux langage génère ensuite du Verilog et/ou du VHDL pour être accepter par les logiciels de synthèse/simulation du marché.

      Mais Chisel me semble le plus abouti «industriellement» dans la mesure où il sert de base à la société SiFive pour produire ses cœurs E310 et U500 basé sur le set d'instructions Risc-V.
      Je me suis également servi de Chisel pour réaliser un design client sans «trop» de problème.

      Après pour l'histoire du «behavioral», le principe de chisel est de faire un design synchrone et synthétisable. Du coup si c'est pas synthétisable ou synchrone alors le code n'est pas juste et doit provoquer une erreur. Il n'y a donc pas de séparation entre le comportementale (behavioral) et le synthétisable (RTL).

      • [^] # Re: un nouveau HDL !

        Posté par . Évalué à 3 (+0/-0).

        Je me doute qu'il existe plein de langage, mais entre un prof of concept et un un truc utilisable en production il y a un monde. SystemC était à la mode, mais la détection d'erreur n'était vraiment pas sympa.

        Après pour l'histoire du «behavioral», le principe de chisel est de faire un design synchrone et synthétisable. Du coup si c'est pas synthétisable ou synchrone alors le code n'est pas juste et doit provoquer une erreur. Il n'y a donc pas de séparation entre le comportementale (behavioral) et le synthétisable (RTL).

        De mémoire, le behavioral était synchrone, c'est juste que le synthétiseur générait sa clock. A l'époque (autour de 2000), il y avait 3 modes: sans clock, avec une metaclock (la vrai clock était un multiple de la metaclock), ou avec une horloge mais avec des constructions inhabituel comme des wait sur l'horloge qui induisait des machines d'états parfois plus simple à écrire que le gros switch.

        "La première sécurité est la liberté"

  • # HDL

    Posté par (page perso) . Évalué à 2 (+1/-0).

    Super cette version !

    Concrètement comment ça se passe pour transformer ce code en schéma électronique ? Est-ce qu'il faut le compiler puis le charger sur un FPGA ? Y a t'il une correspondance à faire entre les entrées/sorties physiques et les registres affectés ?

    • [^] # Re: HDL

      Posté par (page perso) . Évalué à 2 (+1/-0).

      En fait je n'ai présenté que le «core», si on intégrait que cette partie cela ne fonctionnerait pas bien du tout ;)

      Le passage du langage HDL au «schéma électronique» se nomme la Synthèse. Il permet de transformer un code «haut-niveau» en netlist.
      La netlist est ce qui correspond au schéma électronique, puisque c'est une liste des composants avec leurs connections.
      Pour être intégrer dans un FPGA il y a encore une étape de package puis placement-routage et enfin la génération du fichier de configuration -> le bitstream.

      Et oui, il faut également établir une correspondance entre les I/O (ici le bouton, les sorties bpm ainsi que l'horloge et le reset) sur le FPGA et le «core».

      Je pense faire tout ça, une fois que j'aurai trouver une plate-forme qui me convient et surtout le temps !

  • # Il manque une synchronisation

    Posté par . Évalué à 4 (+3/-0).

    Si je ne trompes pas (ça fait des années que je n'ai pas fait de Chisel, et encore moins du Chisel3), il manque une resynchronisation du signal button par rapport à l'horloge timepulse…
    Il y a gros risque de métastabilité…. (À moins que Chisel3 le gère automatiquement ?)

    • [^] # Re: Il manque une synchronisation

      Posté par (page perso) . Évalué à 3 (+1/-0).

      Il manque aussi un anti-rebond (à moins que ce soit ce que tu décris, dans ce cas je n'ai pas pigé).

    • [^] # Re: Il manque une synchronisation

      Posté par (page perso) . Évalué à 2 (+1/-0).

      Si je ne trompes pas (ça fait des années que je n'ai pas fait de Chisel, et encore moins du Chisel3), il manque une resynchronisation du signal button par rapport à l'horloge timepulse…

      Nope, pas de problème de resynchronisation entre timepulse et count, car timepulse n'est pas une horloge. C'est un signal qui passe à '1' quand le compteur déborde (et repasse à zero) :

        val (pulsecount, timepulse) = Counter(true.B, PULSE_NS/tclk_ns)

      La valeur de timpulse est ensuite utilisée avec la fonction risingedge() définie avant :

        def risingedge(x: Bool) = x && !RegNext(x)

      Cette fonction «sauvegarde» la valeurs précédente du signal 'x' et s'en sert pour détecter un front montant en regardant la valeur présente du même signal. La sauvegarde se fait avec un registre (RegNext()).

      Tout le code présenté est cadencé avec une seule horloge. Cette horloge est implicite, on ne la voit pas dans le code. Il n'y a donc pas de franchissement de domaine d'horloge … sauf pour le signal d'entrée mais comme dit dans l'article : c'est quelque chose qu'il faut ajouter à l'intégration dans le FPGA.

  • # Version 3.1.0 toute fraîche

    Posté par (page perso) . Évalué à 1 (+0/-0).

    Pour info, la version 3.1.0 vient de sortir ce soir : https://chisel.eecs.berkeley.edu/blog/?p=189

  • # fautes

    Posté par (page perso) . Évalué à 1 (+0/-1).

    Il permet faire une description synchrone

    de ?

    tout les logiciels connu

    tous et connus

    Ces tick sont comptés

    ticks

    à nos fin

    fins

    la valeurs voulue

    valeur

    sont disponible

    disponibles

    https://librazik.tuxfamily.org - http://linuxmao.org - https://liberapay.com/trebmuh

Envoyer un commentaire

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.