Intégration de TapTempo-Chisel sur APF27

Dans un premier article je décrivais le «core» de TapTempo en Chisel. Mais si nous souhaitons tester en réel il faut choisir une plate-forme sur laquelle le synthétiser. Ce choix implique nécessairement d’ajouter du code pour «packager» notre composant.

La carte APF27 et son kit de développement conçus par Armadeus Systems sont parfaitement indiqués. En effet la carte possède un FPGA de taille plutôt raisonnable de chez Xilinx : le spartan3A. Ce FPGA est couplé à un microprocesseur i.MX27 permettant de communiquer directement via un OS «évolué» (ici U-Boot). Et … comble du perfectionnement, le kit de développement est muni d’un bouton poussoir, qui nous servira de «touche tempo» !

L’idée est donc d’utiliser le bouton du kit pour la tempo et de venir lire le résultat mesuré par TapTempoChisel au moyen d’une lecture sur le bus de communication du processeur qui est connecté au FPGA.

Architecture du «packaging» de TapTempo
Architecture du «packaging» de TapTempo

On trouvera le code du packaging sur le github du projet. L’interface du Top est donc relativement simple, et se résume à deux signaux :

  • Le signal d’entrée (bouton)
  • Le signal de sortie (data)

Coté processeur,  il suffira de faire une lecture sur le bus pour pouvoir avoir la valeur en temps réel:

BIOS> md.w C8000000

Nous verrons plus tard que le design présenté ici est beaucoup trop simpliste et bloque le bus de l’apf27 ce qui entraîne une impossibilité de lancer Linux sur la carte.

Les différents éléments de notre architecture

Tout d’abord, pour éviter au maximum la métastabilité, il est nécessaire de synchroniser le signal d’entrée avec l’horloge du système. Pour cela nous devons faire passer le signal bouton par deux bascules D.

synchronization d'un signal externe par deux bascules
synchronisation d’un signal externe par deux bascules

Pour réaliser cela, dans un premier temps nous aurions tendance à déclarer deux signaux :

  • Un signal temporaire tmp
  • le signal synchronisé button_s

En chisel cela donnerait un truc dans le genre:

val tmp = RegNext(io.button)
val button_s = RegNext(tmp)

On déclare le registre en même temps que l’on connecte sa valeur d’entrée.

Pourtant à y regarder de plus près, ce montage de la double bascules n’est qu’un registre à décalage de 2 ! Et il existe une fonction pour ça dans la librairie «util» de chisel : ShiftRegister(sig, n)

Du coup nous pouvons réduire notre synchronisation en une simple ligne :

val button_s = ShiftRegister(io.button, 2)

Notre signal est maintenant synchronisé, mais nous n’avons pas filtré les rebonds. Or avec le genre de boutons que nous trouvons sur ces kits de développement c’est indispensable. Le FPGA étant cadencé à une fréquence élevé de 100Mhz nous allons «voir» tous les rebonds, et fausser par la même occasion notre mesure du tempo.

La plupart des «montages FPGA» permettant de faire de l’anti-rebond se basent sur des compteurs. Le tout étant de bien les dimensionner.

  val clk_freq_khz = 100000
  val debounce_per_ms = 20
  val MAX_COUNT = (clk_freq_khz * debounce_per_ms) + 1
  val debcounter = RegInit(MAX_COUNT.U)

La remise à zéro du compteur sera déclenchée par un front (montant ou descendant) du signal d’entrée. Nous déclarerons pour cela deux fonctions très commodes:

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

Permettant de détecter respectivement le front montant et le front descendant du signal d’entrée.

Tant que le compteur debcounter n’a pas atteint sa valeur maximal, on ne fait que compter. Si le compteur est à sa valeur max et que l’on a un front sur le signal d’entrée, alors on remet le compteur à zero et on recopie la valeur du signal d’entrée.

  when(debcounter =/= MAX_COUNT.U) {
    debcounter := debcounter + 1.U
  }.otherwise {
    when(risingedge(button_s) || fallingedge(button_s)){
      debcounter := 0.U
      button_deb := button_s
    }
}

De cette manière on répercute rapidement un changement du signal d’entrée sans s’encombrer des multiples changement de valeurs rapide inhérentes aux rebonds.

Synthèse

Chisel est «vendu» à la base comme un langage HDL synthétisable, du coup nous allons le synthétiser, et avec un logiciel du marché s’il vous plaît : ISE.

Avant la synthèse nous avons besoin du code verilog généré. Pour le générer nous appellerons le ‘Driver’ déclaré dans le top:

object APF27TapTempoDriver extends App {
  chisel3.Driver.execute(args, () => new APF27TapTempo)
}

Au moyen de la commande sbt :

sbt 'runMain taptempo.APF27TapTempoDriver'

Le code verilog ainsi généré se retrouve dans le répertoire courant avec le nom APF27TapTempo.v

Notre projet comportant deux modules verilog (APF27TapTempo et TapTempo) leurs déclaration dans le fichier source se fait en partant de la fin -> le «top» est à la fin du fichier et le «core» au début:

...
module APF27TapTempo( // @[:@2517.2]
  input         clock, // @[:@2518.4]
  input         reset, // @[:@2519.4]
  output [15:0] io_data, // @[:@2520.4]
  input         io_button // @[:@2520.4]
);
...

Il ne nous reste plus qu’à intégrer ce source à un projet ISE en y ajoutant la description des signaux d’entrées sorties et leurs placement sur les pins du FPGA. Ce qui peut-être fait en intégrant le fichier de description APF27TapTempoChisel.ucf

# clock
NET "clock" LOC="N9" | IOSTANDARD=LVCMOS18;# CLK0
NET "clock" TNM_NET = "clock";
TIMESPEC "TS_clock" = PERIOD "clock" 10.4167 ns HIGH 50 %;
# data bus
NET "io_data<0>"  LOC="T5" | DRIVE=8 | IOSTANDARD=LVCMOS18; # DATA0
NET "io_data<1>"  LOC="T6" | DRIVE=8 | IOSTANDARD=LVCMOS18; # DATA1
NET "io_data<2>"  LOC="P7" | DRIVE=8 | IOSTANDARD=LVCMOS18; # DATA2
NET "io_data<3>"  LOC="N8" | DRIVE=8 | IOSTANDARD=LVCMOS18; # DATA3
NET "io_data<4>"  LOC="P12"| DRIVE=8 | IOSTANDARD=LVCMOS18; # DATA4
NET "io_data<5>"  LOC="T13"| DRIVE=8 | IOSTANDARD=LVCMOS18; # DATA5
NET "io_data<6>"  LOC="R13"| DRIVE=8 | IOSTANDARD=LVCMOS18; # DATA6
NET "io_data<7>"  LOC="T14"| DRIVE=8 | IOSTANDARD=LVCMOS18; # DATA7
NET "io_data<8>"  LOC="P5" | DRIVE=8 | IOSTANDARD=LVCMOS18; # DATA8
NET "io_data<9>"  LOC="N6" | DRIVE=8 | IOSTANDARD=LVCMOS18; # DATA9
NET "io_data<10>" LOC="T3" | DRIVE=8 | IOSTANDARD=LVCMOS18; # DATA10
NET "io_data<11>" LOC="T11"| DRIVE=8 | IOSTANDARD=LVCMOS18; # DATA11
NET "io_data<12>" LOC="T4" | DRIVE=8 | IOSTANDARD=LVCMOS18; # DATA12
NET "io_data<13>" LOC="R5" | DRIVE=8 | IOSTANDARD=LVCMOS18; # DATA13
NET "io_data<14>" LOC="M10"| DRIVE=8 | IOSTANDARD=LVCMOS18; # DATA14
NET "io_data<15>" LOC="T10"| DRIVE=8 | IOSTANDARD=LVCMOS18; # DATA15
# Button
NET "io_button" LOC="C15" | DRIVE=12 | IOSTANDARD=LVCMOS33; # IO_L24N_1

Et nous pouvons lancer la synthèse/placement&routage/bitstream d’ISE. Une fois le bitstream généré il faut le transférer dans la mémoire de l’apf27 avec U-Boot :

BIOS> tftpboot ${loadaddr} APF27TapTempo.bit

Puis configurer le FPGA.

BIOS> fpga load 0 ${loadaddr}

Nous pouvons enfin lire la valeur du tempo avec la commande de lecture dans l’espace mémoire du bus fpga (WEIM) :

BIOS> md.w C8000000
c8000000: 010e 010e 010e 010e 010e 010e 010e 010e ................
c8000010: 010e 010e 010e 010e 010e 010e 010e 010e ................
c8000020: 010e 010e 010e 010e 010e 010e 010e 010e ................

La valeur est lue en hexadécimal. Et comme l’adresse n’est pas gérée, tant que ça reste dans la zone du bus FPGA, la même valeur se répète.

Ici nous avons donc un tempo de 0x10e soit 270bpm. Pour le calibrer, j’ai pris le chronomètre et tenté d’appuyer sur le bouton toutes les secondes, ce qui doit logiquement donner 60bpm -> 0x3c.

Nous n’en somme pas trop loin :
Test de la calibration de TapTempoChisel sur APF27

Il est désormais possible de l’utiliser dans le cas concret de la mesure du tempo du très mauvais «nuit de folie» du groupe «début de soirée» .

Mesure du tempo du très mauvais «nuit de folie» :

On obtient une valeur de 0x7B soit 123 coups par minute (bpm).

Ps: si vous voulez laver votre cerveau de cette horrible chanson pourquoi pas une petite guérilla ? À moins que vous soyez adepte du crou. Ne me remerciez pas, moi aussi j’ai beaucoup souffert à mesurer le tempo de cette horreur 😉

Une réflexion sur « Intégration de TapTempo-Chisel sur APF27 »

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

*