Archives par mot-clé : truc

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

Synchronisation d’un signal externe avec Chisel

Quand on récupère un signal extérieur au fpga dont on ne maitrise pas le timing d’arrivée des fronts — typiquement un bouton ou une interruption — il est nécessaire de le synchroniser par rapport à l’horloge qui cadence le FPGA.

Il faut donc utiliser le montage classique des deux bascules. En Verilog et en VHDL cela se traduit par la déclaration d’un signal intermédiaire permettant la connection entre les deux bascules:

reg tmp, sig_s;

always @(posedge clk)
 begin
    tmp <= sig;
    sig_s <= tmp;
 end

Avec Chisel l’exemple donné dans le tutorial (page 14) est assez décevant car il impose aussi l’utilisation d’un signal intermédiaire :

s1 :=  signalA
s2 := s1;
signalB := s2

Pourtant il existe une méthode un peu plus élégante ne nécessitant qu’une seule ligne de code si on regarde du coté de la classe ChiselUtil : ShiftRegister()

Cette fonction permet de décaler un signal du nombre de coup d’horloge donné en argument, la synchronisation d’un signal par une double bascule n’étant rien de plus qu’un registre à décalage de deux coups d’horloge il suffit donc de l’utiliser avec un décalage de 2 :

val  sig_s = ShiftRegister(sig, 2)

Si on regarde le Verilog généré par le backend on obtient bien les deux bascules souhaitées:

always @(posedge clk) begin
[...]
sig_s <= R23;
R23 <= sig;
[...]
end

Voila qui nous simplifie grandement les design, et évite l’empilement des copier/coller de signaux temporaire pour synchroniser des grappes de signaux.