URL: https://linuxfr.org/news/taptempo-en-verilog Title: TapTempo en Verilog Authors: martoni Yves Bourguignon, Ysabeau đ§¶, Davy Defaud, BAud, Xavier Teyssier, BenoĂźt Sibaud, FrĂ©dĂ©ric Massot, palm123, claudex, bobble bubble et mzf Date: 2020ćčŽ09æ13æ„T14:25:36+02:00 License: CC By-SA Tags: open_hardware, taptempo, matĂ©riel, hdl et verilog Score: 79 Le projet [TapTempo](https://linuxfr.org/tags/taptempo/public) semble faiblir depuis quelques mois maintenant. En panne de langage informatique pour en faire une dĂ©pĂȘche ? N. D. M. â TapTempo est un _dĂ©tecteur de tempo_ oĂč _lâutilisateur frappe une touche en cadence rĂ©guliĂšre et le programme en dĂ©duit le tempo correspondant._ Il a Ă©tĂ© dĂ©clinĂ© en de multiples langages de programmation. Laissezâmoi vous prĂ©senter un langage assez particulier puisquâil ne sert pas Ă faire de la programmation. Ce langage permet de dĂ©crire le comportement numĂ©rique dâun composant Ă©lectronique (on parle alors de langage de description de matĂ©riel â [HDL](https://fr.wikipedia.org/wiki/Langage_de_description_de_mat%C3%A9riel "hardware description language")) : le [Verilog](https://en.wikipedia.org/wiki/Verilog). Câest aussi un langage utilisĂ© pour faire de la synthĂšse numĂ©rique sur les circuits logiques programmables (FPGA). Dans cet exemple, nous utiliserons la carte de dĂ©veloppement Ă bas coĂ»t [ColorLight 5Aâ75B](https://github.com/q3k/chubby75/blob/master/5a-75b/hardware_V7.0.md).  ---- [Le projet TapTempoASIC dĂ©crit en Verilog](https://github.com/Martoni/TapTempoASIC) [La vidĂ©o de TapTempo en action sur une carte Colorlight (ECP5)](https://www.youtube.com/watch?v=03z5ehVXYvc) [ColorLight 5A-75B](https://github.com/q3k/chubby75/blob/master/5a-75b/hardware_V7.0.md) ---- Le Verilog ========== Le Verilog est un langage conçu Ă lâorigine pour rĂ©diger des spĂ©cifications de circuits logiques en Ă©lectronique numĂ©rique. Le langage permet de dĂ©crire le comportement de sortie par rapport Ă des entrĂ©es logiques. Un peu comme les logiciels de saisie de schĂ©ma Ă©lectronique, le Verilog est trĂšs hiĂ©rarchique, on dĂ©crit des modules avec leurs entrĂ©esâsorties. Que lâon assemble ensuite dans dâautres modules pour finir dans un module « top » qui dĂ©crit le composant final. Dans le cas de TapTempo, le module « top » est dĂ©clarĂ© [comme ceci](https://github.com/Martoni/TapTempoASIC/blob/master/hdl/taptempo.v) : ```Verilog module taptempo #( parameter CLK_PER_NS = 40, // 25Mhz clock (ns) parameter TP_CYCLE = 5120, // timepulse cycle period (ns) parameter BPM_MAX = 250 // BPM max (bpm) )( input clk_i, input btn_i, output pwm_o ); //corps du module endmodule ``` Le module possĂšde deux entrĂ©es : lâhorloge (`clk_i`) et le bouton (`btn_i`) ainsi quâune sortie pwm (`pwm_o`) pour lâaffichage. Les paramĂštres seront vus comme des constantes au moment de la simulation, ils permettent de configurer les composants en fonction de la cible. Le changement de valeur des signaux se fait dans des processus qui sont dĂ©clenchĂ©s sur Ă©vĂ©nement. Ces processus sont dĂ©crits au moyen du mot clef `always@()` en Verilog. Par exemple, dans le code suivant : ```Verilog /* Detect rising edge*/ reg btn_old, btn_rise; always@(posedge clk_i) begin btn_old <= btn_i; if(btn_old == 0 && btn_i == 1) btn_rise <= 1; else btn_rise <= 0; end ``` LâĂ©vĂ©nement dĂ©clencheur du process est le front montant de lâhorloge `clk_i`. Ă chaque fois quâun front montant dâhorloge se prĂ©sente, le processus est exĂ©cutĂ© de maniĂšre sĂ©quentielle. LâopĂ©rateur `<=` est lâopĂ©rateur dâaffectation dit « non bloquant ». Cela signifie que la valeur ne sera effectivement appliquĂ©e quâĂ la fin de lâexĂ©cution du process. Donc, la valeur du signal `btn_old` ne sera pas nĂ©cessairement Ă©gale Ă `btn_i` Ă la ligne du `if()` comme on aurait pu instinctivement le croire. Le langage Verilog a beaucoup de succĂšs dans le monde du logiciel libre. En effet, il est relativement peu verbeux et ressemble au C pour de nombreux aspects. Il est par exemple possible de dĂ©crire des macros de la mĂȘme maniĂšre quâen C, il suffit de remplacer le symbole `#` par ` pour crĂ©er des constantes qui seront remplacĂ©es par le prĂ©processeur : ```Verilog /* count tap period */ `define MIN_NS 60_000_000_000 `define BTN_PER_MAX (`MIN_NS/TP_CYCLE) `define BTN_PER_SIZE ($clog2(1 + `BTN_PER_MAX)) ``` Le Verilog reprend Ă©galement les opĂ©rateurs boolĂ©en et binaire `&`, `&&`, `|`, `||`, etc., du C. Câest le langage HDL le mieux pris en charge par les diffĂ©rents logiciels libres. Si lâon souhaite se lancer dans le domaine des FPGA et/ou des ASIC, il est prĂ©fĂ©rable de commencer par lui. Câest Ă©galement le langage « de sortie » de quasiment tous les gĂ©nĂ©rateurs de code HDL. Architecture de TapTempo ======================== Lâoutil indispensable pour commencer un projet en Verilog est... le papier et le crayon. Il est en effet indispensable dâavoir une vue dâensemble assez claire de ce que lâon souhaite rĂ©aliser avant de se lancer dans le code. Voici donc lâarchitecture gĂ©nĂ©rale du composant TapTempo :  MĂȘme si lâon doit revenir plusieurs fois (ce qui est le cas ici puisque les constantes ne sont pas Ă jour) sur ce schĂ©ma gĂ©nĂ©ral en cours de dĂ©veloppement, cette partie est trĂšs importante. Si elle est bien pensĂ©e, le reste coule de source. Le composant va nĂ©cessiter quelques compteurs, mais lâhorloge utilisĂ©e ici Ă©tant trĂšs rapide nous allons dâabord factoriser le comptage au moyen du module nommĂ© `timepulse`, ce module va distribuer une pulsation qui servira de base aux autres compteurs pour leur fonctionnement interne. LâentrĂ©e utilisateur se compose dâun bouton (touche tĂ©lĂ©graphique « morse »). Les fronts montant et descendant de cette entrĂ©e nâĂ©tant pas synchronisĂ©s sur lâhorloge du systĂšme nous allons devoir le faire au moyen de deux bascules en sĂ©rie pour Ă©viter la [mĂ©tastabilitĂ©](https://en.wikipedia.org/wiki/Metastability_(electronics)). ```Verilog /* Synchronize btn_i to avoid metastability*/ reg btn_old, btn_s; always@(posedge clk_i or posedge rst) begin if(rst) begin btn_old <= 1'b0; btn_s <= 1'b0; end else begin btn_old <= btn_i; btn_s <= btn_old; end end ``` Le second problĂšme que pose notre entrĂ©e est que lâappui sur le bouton ne gĂ©nĂšre pas des changements francs de son Ă©tat. Chaque « appui et relĂąche » gĂ©nĂšre une sĂ©rie de rebonds et donc une sĂ©rie de 0 et de 1 avant de se stabiliser. Pour lisser le signal il va donc falloir faire passer le signal dans le bloc « antirebond » `debounce`. Le bloc `percount` va ensuite se charger de mesurer le temps entre deux appuis sur le bouton. Cette pĂ©riode va devoir ĂȘtre transformĂ©e en frĂ©quence « BPM » (_Beat Per Minute_) via le module `per2bpm`, puis en une valeur pseudoâanalogique (PWM) grĂące au module `pwmgen`. La carte cible ne possĂ©dant pas de bouton « _reset_ », il va falloir le gĂ©nĂ©rer grĂące au module `rstgen` de maniĂšre Ă sâassurer de lâĂ©tat de dĂ©part de notre systĂšme au dĂ©marrage. EntrĂ©eâsortie ------------- La plupart des programmes TapTempo proposĂ©s jusquâici supposaient â en plus dâun processeur â la prĂ©sence dâun clavier et dâune console texte de sortie (avec toute la pile de pilotes et de systĂšme dâexploitation associĂ©s). Ici, nous allons devoir tout dĂ©finir dans le « [portegramme](http://www.fabienm.eu/flf/et-pourquoi-pas-portegramme/) » â dans lâindustrie on va parler dâIP pour _Intellectual Property_, quel horrible nom. LâidĂ©e est donc de simplifier au maximum lâentrĂ©e « clavier » et la sortie histoire de pouvoir les dĂ©crire simplement. Pour lâentrĂ©e nous allons nous contenter dâun contact de type bouton, ou dâune touche de type tĂ©lĂ©graphe « morse » :  Comme on peut le voir dans le schĂ©ma ciâdessus, quand la touche est appuyĂ©e, lâentrĂ©e « bouton » est mise Ă la masse et donne un niveau logique Ă 0 sur notre systĂšme. Lorsque lâon relĂąche le bouton, la rĂ©sistance de tirage ramĂšne le niveau de tension Ă Vcc pour avoir un niveau 1 sur lâentrĂ©e. Pour la sortie, lâidĂ©e de mettre un Ă©cran complexifie Ă©normĂ©ment le systĂšme. En effet, il est nĂ©cessaire de faire une machine dâĂ©tat assez complexe pour initialiser lâĂ©cran puis rafraĂźchir lâaffichage. Il est souvent nĂ©cessaire dâajouter un processeur « soft » rien que pour ça dâailleurs (bon, il est vrai que le VGA nâest pas si compliquĂ©, mais il reste plus complexe que la solution proposĂ©e ici). Non, lâidĂ©e ici est dâutiliser les graduations de lâantique voltmĂštre Ă aiguille trouvĂ© dans une cave et qui gradue de 0 Ă 300 comme on peut le voir sur la photo :  Et comme un systĂšme numĂ©rique ne sort que des 0 et des 1 sur ses broches, on va « simuler » une valeur analogique au moyen dâune modulation de largeur dâimpulsion, en anglais _Pulse Width Modulation_ â PWM. Il suffit de changer le rapport cyclique entre le temps haut et le temps bas de notre signal pour faire varier la tension moyenne qui sera vue par le voltmĂštre. Si on lâajuste correctement avec une rĂ©sistance en sĂ©rie, il est relativement facile de forcer la valeur maximale (5 V) Ă 250.  La pĂ©riode de la modulation de largeur dâimpulsion sera configurĂ©e suffisamment rapide pour que lâaiguille nâoscille pas. Pulsation de temporisation ([timepulse](https://github.com/Martoni/TapTempoASIC/blob/master/hdl/timepulse.v)) -------------------------------------- Le module ne prend pas de valeur dâentrĂ©e hormis lâhorloge et le _reset_ qui sont de rigueur dans tout le projet. Son signal de sortie `tp_o` est une pulsation de lâhorloge Ă©mise toutes les 5 120 ns : ```Verilog module timepulse #( parameter CLK_PER_NS = 40, parameter PULSE_PER_NS = 5120 )( /* clock and reset */ input clk_i, input rst_i, /* output */ output tp_o); ``` Pour pouvoir compter des pĂ©riodes de 5 120 ns on dĂ©finit un registre de comptage : ```Verilog `define MAX_COUNT (PULSE_PER_NS/CLK_PER_NS) `define MAX_COUNT_SIZE ($clog2(`MAX_COUNT)) reg [`MAX_COUNT_SIZE-1:0] counter = 0; ``` Puis on compte de maniĂšre synchronisĂ©e avec lâhorloge : ```Verilog always@(posedge clk_i or posedge rst_i) begin if(rst_i) begin counter <= 0; end else begin if (counter < `MAX_COUNT) begin counter <= counter + 1'b1; end else begin counter <= 0; end end end ``` La pulsation est Ă©mise lorsque le compteur passe par 0 : ```Verilog assign tp_o = (counter == 0); ``` Gestion des rebonds ([debounce](https://github.com/Martoni/TapTempoASIC/blob/master/hdl/debounce.v)) ------------------------------ LâentrĂ©e de ce module est le signal de bouton prĂ©alablement synchronisĂ© avec lâhorloge du systĂšme `btn_s`. Le compteur utilisera la pulsation `tp_i` gĂ©nĂ©rĂ© par le module timepulse dĂ©crit ciâavant. La sortie du module est un signal btn_o proprement lissĂ©. La pĂ©riode de temporisation de 20 ms est donnĂ© ici en paramĂštre `DEBOUNCE_PER_NS` : ```Verilog module debounce #( parameter PULSE_PER_NS = 5120, parameter DEBOUNCE_PER_NS = 20_971_520 )( /* clock and reset */ input clk_i, input rst_i, /* inputs */ input tp_i, input btn_i, /* output */ output btn_o ); ``` La gestion des rebonds est rĂ©alisĂ©e au moyen dâun compteur utilisĂ© pour temporiser : ```Verilog `define MAX_COUNT ((DEBOUNCE_PER_NS/PULSE_PER_NS)-1'b1) `define MAX_COUNT_SIZE ($clog2(`MAX_COUNT)) /* Counter */ reg [`MAX_COUNT_SIZE-1:0] counter = 0; ``` Ainsi que dâune machine dâĂ©tats Ă quatre Ă©tats : ```Verilog /* State machine */ localparam [1:0] s_wait_low = 2'h0, s_wait_high = 2'h1, s_cnt_high = 2'h2, s_cnt_low = 2'h3; reg [1:0] state_reg, state_next; ``` Les transitions de la machine dâĂ©tats sont donnĂ©es dans le code ciâdessous dans un processus dit « combinatoire » (`always@*`) par opposition Ă un processus « synchrone ». ```Verilog always@* begin case(state_reg) s_wait_low: if(btn_i) state_next = s_cnt_high; else state_next = s_wait_low; s_wait_high: if(!btn_i) state_next = s_cnt_low; else state_next = s_wait_high; s_cnt_high: /* verilator lint_off WIDTH */ if(counter == `MAX_COUNT) /* verilator lint_on WIDTH */ state_next = s_wait_high; else state_next = s_cnt_high; s_cnt_low: /* verilator lint_off WIDTH */ if(counter == `MAX_COUNT) /* verilator lint_on WIDTH */ state_next = s_wait_low; else state_next = s_cnt_low; endcase; end ``` LâĂ©tat de la machine est tout de mĂȘme synchronisĂ© dans un second processus : ```Verilog always@(posedge clk_i or posedge rst_i) if(rst_i) state_reg <= s_wait_low; else state_reg <= state_next; ``` Le principe de « lissage » des rebonds est donc le suivant : dans lâĂ©tat initial `s_wait_low`, on attend que le bouton passe Ă la valeur 1, et lorsque le signal passe Ă 1, on change dâĂ©tat pour `s_cnt_high`. Le passage dans lâĂ©tat `s_cnt_high` a pour effet de faire passer le signal de sortie Ă 1 et dĂ©clencher le compteur. Tant que le compteur compte et nâa pas atteint la valeur `MAX_COUNT`, on reste dans cet Ă©tat quelles que soient les variations du signal dâentrĂ©e. Lorsque le compteur atteint la valeur maximale, la machine dâĂ©tat passe dans lâĂ©tat `s_wait_high` (en attente de valeurs hautes). Dans lâĂ©tat `s_wait_high` on surveille la valeur du bouton dâentrĂ©e, si elle passe Ă 0 on change dâĂ©tat pour `s_cnt_low`. De maniĂšre symĂ©trique Ă `s_cnt_high`, on dĂ©clenche donc le compteur en ignorant la valeur dâentrĂ©e, et, lorsquâelle atteint son maximum, on passe Ă lâĂ©tat initial `s_wait_low`. La valeur « lissĂ©e » du bouton en sortie est donnĂ©e par lâĂ©tat de la machine dâĂ©tat : ```Verilog assign btn_o = (state_reg == s_cnt_high) || (state_reg == s_wait_high); ``` Mesure de la pĂ©riode de tempo ([percount](https://github.com/Martoni/TapTempoASIC/blob/master/hdl/percount.v)) ---------------------------------------- Lâinterface du module `percount` se compose des entrĂ©es habituelles dâhorloge `clk_i`, de reset `rst_i`, ainsi que de la pulsation `tp_i`. Le signal de mesure en entrĂ©e est `btn_i` et la sortie est un vecteur `btn_per_o` donnant la valeur mesurĂ©e. La valeur est considĂ©rĂ©e comme valide uniquement lorsque la sortie `btn_per_valid` est Ă 1. Cette astuce permet dâĂ©conomiser un registre si la sauvegarde de la valeur mesurĂ©e est inutile comme câest le cas ici. ```Verilog `define MIN_NS 60_000_000_000 `define BTN_PER_MAX (`MIN_NS/TP_CYCLE) `define BTN_PER_SIZE ($clog2(1 + `BTN_PER_MAX)) module percount #( parameter CLK_PER_NS = 40, parameter TP_CYCLE = 5120, parameter PULSE_PER_NS = 5120, )( /* clock and reset */ input clk_i, input rst_i, /* time pulse */ input tp_i, /* input button */ input btn_i, /* output period */ output [(`BTN_PER_SIZE-1):0] btn_per_o, output btn_per_valid); ``` Maintenant que nous avons un signal de bouton `btn_b` propre et lissĂ©, nous pouvons entamer la mesure de la pĂ©riode entre deux appuis au moyen de... devinez quoi ? Dâun compteur pardi ! ```Verilog reg [($clog2(`BTN_PER_MAX+1)-1):0] counter = 0; reg counter_valid = 0; assign btn_per_valid = counter_valid; assign btn_per_o = counter; ``` Il nous faut tout dâabord dĂ©tecter le front descendant du bouton : ```Verilog reg btn_old; wire btn_fall = btn_old & (!btn_i); always@(posedge clk_i or posedge rst_i) begin if(rst_i) btn_old <= 1'b0; else btn_old <= btn_i; end ``` Le signal `btn_fall` sert de remise Ă zĂ©ro du compteur ainsi que de validation de la valeur de sortie : ```Verilog always@(posedge clk_i or posedge rst_i) begin if(rst_i) begin counter <= 0; end else begin if(btn_fall) begin counter_valid <= 1'b1; end else if(counter_valid) begin counter <= 0; counter_valid <= 1'b0; end else begin /* stop counting if max, count tp_i */ if(tp_i && counter < `BTN_PER_MAX) counter <= counter + 1'b1; end end end ``` Le compteur compte le nombre de pulsations de `tp_i` jusquâĂ atteindre la saturation `BTN_PER_MAX`. Si un front montant du bouton se prĂ©sente avec `btn_fall`, on valide le compteur avec `counter_valid`. Et si le signal de validation passe Ă 1 (donc, le coup dâhorloge suivant), on remet le compteur Ă zĂ©ro et on recommence Ă compter. Calcul de la frĂ©quence en Beat Per Minute ([per2bpm](https://github.com/Martoni/TapTempoASIC/blob/master/hdl/per2bpm.v)) --------------------------------------------------- Avec le module `per2bpm` on arrive dans la partie critique du projet, car il va nous falloir faire une division. On entre une pĂ©riode dans le module : ```Verilog /* inputs */ input [(`BTN_PER_SIZE-1):0] btn_per_i, input btn_per_valid, ``` Et on doit en ressortir une frĂ©quence (BPM) : ```Verilog /* outputs */ output [`BPM_SIZE - 1:0] bpm_o, output bpm_valid ``` Suivant la formule : $$ bpm = \frac{\frac{MIN\_NS}{TP\_NS}}{btn\_per\_i}$$ Il faut donc diviser la constante $\frac{MIN\_NS}{TP\_NS}$ par la variable $btn\_per\_i$ La division (tout comme la multiplication) est un point sensible en Verilog. En effet, lâopĂ©rateur de division existe bien dans le langage et il se peut que cela simule parfaitement. Câest lorsque arrivera lâĂ©tape de la synthĂšse que lâon risque dâavoir quelques surprises. Il est possible que certains logiciels de synthĂšse rĂ©ussiront Ă faire quelque chose en un coup dâhorloge, mais il est certain que cela se fera au prix de trĂšs mauvaises performances en matiĂšre de ressources utilisĂ©es et de frĂ©quence dâhorloge. Il est surtout probable que votre logiciel de synthĂšse jette lâĂ©ponge. Pour rĂ©aliser cette division, nous allons donc en revenir aux fondamentaux appris au primaire et poser la division. Une division, câest la recherche du quotient et du reste de lâĂ©quation suivante : $Dividend = Quotient \times Divisor + Remainder$ ```Verilog reg [(`REGWIDTH-1):0] divisor; reg [(`REGWIDTH-1):0] remainder; reg [(`REGWIDTH-1):0] quotient; ``` La taille des registres sera celle de la pĂ©riode en entrĂ©e `BTN_PER_SIZE` additionnĂ©e Ă la constante Ă diviser : ```Verilog `define DIVIDENTWITH ($clog2(1 + `MIN_NS/(TP_CYCLE))) `define REGWIDTH (`BTN_PER_SIZE + `DIVIDENTWITH) ``` La division sâeffectue avec une sĂ©rie de soustraction du reste (`remainder`) et de dĂ©calage du diviseur. Ă lâĂ©tape initiale, on place le diviseur Ă gauche du registre `divisor` et le dividende dans le reste `remainder` : ```Verilog divisor <= {btn_per_i, (`DIVIDENTWITH)'h0}; remainder <= `MIN_NS/TP_CYCLE; // le rĂ©sultat est initialisĂ© Ă 0: quotient <= 0; ``` Puis, on effectue une sĂ©rie de comparaisonâsoustractionâdĂ©calage avec lâalgorithme comme dĂ©crit ciâdessous : - si le diviseur (`divisor`) infĂ©rieur ou Ă©gal au reste (`remainder`), on soustrait le reste avec le diviseur et on dĂ©cale le quotient Ă gauche en ajoutant 1 : ```Verilog if(divisor <= remainder) begin remainder <= remainder - divisor; quotient <= {quotient[(`DIVIDENTWITH-2):0], 1'b1}; ``` - si le diviseur (`divisor`) est supĂ©rieur au reste, on dĂ©cale le quotient Ă gauche en ajoutant 0. On ne touche pas au reste : ```Verilog quotient <= {quotient[(`DIVIDENTWITH-2):0], 1'b0}; ``` - dans tous les cas, on dĂ©cale le diviseur Ă droite : ```Verilog divisor <= {1'b0, divisor[(`REGWIDTH-1):1]}; ``` La division est orchestrĂ©e par une machine Ă trois Ă©tats : ```Verilog localparam [1:0] s_init = 2'h0, s_compute = 2'h1, s_result = 2'h2; reg [1:0] state_reg, state_next; ``` Et le rĂ©sultat est disponible en sortie quand `state_reg` est dans lâĂ©tat `s_result` : ```Verilog assign bpm_o = quotient[(`BPM_SIZE-1):0]; assign bpm_valid = (state_reg == s_result); ``` GĂ©nĂ©ration de la tension de sortie ([pwmgen](https://github.com/Martoni/TapTempoASIC/blob/master/hdl/pwmgen.v)) ------------------------------------------- La gĂ©nĂ©ration du signal pseudoâanalogique dĂ©crite en introduction est presque la partie la plus simple. On compte (oui encore) de 0 Ă 250 (BPM_MAX) : ```Verilog /* count */ always@(posedge clk_i or posedge rst_i) begin if(rst_i) count <= BPM_MAX; else begin if(tp_i) begin if (count == 0) count <= BPM_MAX; else count <= count - 1'b1; end end end ``` Et on passe le signal de sortie `pwm_o` Ă 1 lorsque le compteur est infĂ©rieur Ă la frĂ©quence demandĂ©e : ```Verilog assign pwm_o = (count <= pwmthreshold); ``` Il y a juste une subtilitĂ© consistant Ă sauvegarder la valeur de la frĂ©quence donnĂ©e en entrĂ©e dans deux registres `pwmthreshold` et `bpm_reg` : ```Verilog reg [($clog2(BPM_MAX+1)-1):0] bpm_reg; reg [($clog2(BPM_MAX+1)-1):0] pwmthreshold; /* Latching bpm_i on bpm_valid */ always@(posedge clk_i or posedge rst_i) begin if(rst_i) begin bpm_reg <= 0; pwmthreshold <= 0; end else begin if(bpm_valid) bpm_reg <= bpm_i; if(count == BPM_MAX) pwmthreshold <= bpm_reg; end end ``` Le premier registre `bpm_reg` est mis Ă jour lorsque le signal dâentrĂ©e `bpm_valid` est Ă 1. Pour mĂ©moriser la valeur dâentrĂ©e et pouvoir lâutiliser au moment oĂč lâon en a besoin. Le second `pwmthreshold` est rafraĂźchi en fin de cycle dâune pĂ©riode de la modulation de largeur dâimpulsions. Pour Ă©viter dâavoir un changement de valeur en cours de pĂ©riode, et donc un rapport cyclique faux. Simulation de lâensemble avec Cocotb ==================================== Jusquâici nous avons dĂ©crit le comportement du composant final en Verilog. Toutes les dĂ©veloppeuses ou dĂ©veloppeurs HDL le savent trĂšs bien, il est impossible de rĂ©aliser un projet Verilog (ou autre HDL) sans faire un minimum de simulation. Pour simuler le composant, il est nĂ©cessaire de dĂ©crire les stimuli en entrĂ©e du composant et de lire/valider les sorties. On va gĂ©nĂ©ralement crĂ©er un composant hiĂ©rarchiquement auâdessus du top de notre composant appelĂ© « testbench », dans lequel nous dĂ©crirons les changements de valeurs des entrĂ©es au cours du temps. Cette partie peut tout Ă fait se faire en Verilog. Cependant, lâidĂ©e de mĂ©langer la partie banc de test et composant « synthĂ©tisable » nâest pas terrible. En effet, on va trĂšs vite confondre les deux parties et mĂ©langer les codes. Lâexemple de la division est criant : lâopĂ©rateur diviser « / » fonctionne trĂšs bien dans la partie `testbench` mais il pose de gros problĂšmes dans la partie « synthĂ©tisable ». Pour Ă©viter ce mĂ©lange des genres, une solution radicale consiste Ă utiliser un autre langage pour la partie banc de test. Le C++ et le [SystemC](https://accellera.org/downloads/standards/systemchttps://accellera.org/downloads/standards/systemc) sont utilisĂ©s depuis longtemps pour cela. Sâils sont utilisĂ©s en conjonction avec [Verilator](https://linuxfr.org/news/verilator-4-002), ils permettent dâatteindre des puissance et rapiditĂ© de simulation inĂ©galĂ©es par les simulateurs « propriĂ©taires ». Une autre mĂ©thode consiste Ă piloter le simulateur Verilog avec un autre programme, on parle alors de cosimulation. Câest le cĆur du fonctionnement du module Python [CocoTB](https://linuxfr.org/news/cocotb-1-4-0-la-maturite). LâidĂ©e ici est dâĂ©crire son banc de test en Python, ce qui est nettement plus confortable que du Verilog ou mĂȘme du C++ (SystemC est Ă©galement une librairie C++). Le test pour simuler lâensemble du projet TapTempo se trouve dans le rĂ©pertoire cocotb/test_taptempo. Pour le simuler, il suffit de sây rendre et dây exĂ©cuter un `make` ; Ă condition cependant dâavoir installĂ© cocotb (en Python 3) et [Icarus](https://github.com/steveicarus/iverilog) pour la partie simulateur (on laissera lâapprĂ©ciation de lâinstallation au lecteur en fonction de ses affinitĂ©s linuxdistributive). [La simulation](https://github.com/Martoni/TapTempoASIC/blob/master/cocotb/test_taptempo/test_taptempo.py) consiste Ă tester trois appuis sur le bouton Ă des intervalles diffĂ©rents : ```python @cocotb.test() async def debounce_upanddown(dut): td = TestTapTempo(dut) td.log.info("Running test!") await td.reset() td.log.info("System reseted!") await Timer(1000, units="us") td.log.info("up") await td.bounce_up(10, bounce_per=(10000, "ns")) await Timer(24, units="ms") td.log.info("down") await td.bounce_down(10, bounce_per=(10000, "ns")) await Timer(300, units="ms") td.log.info("up") await td.bounce_up(10, bounce_per=(10000, "ns")) await Timer(30, units="ms") td.log.info("down") await td.bounce_down(10, bounce_per=(10000, "ns")) await Timer(800, units="ms") td.log.info("up") await td.bounce_up(10, bounce_per=(10000, "ns")) await Timer(30, units="ms") td.log.info("Wait stable") await Timer(1000, units="us") ``` Cela gĂ©nĂšre un fichier de « traces » au format VCD particuliĂšrement volumineux de 2,3 Gio (qui se compresse Ă 70 Mio avec xz !) permettant de visionner les signaux au cours du temps grĂące Ă [GTKWave](https://linuxfr.org/news/simplifier-la-visualisation-de-chronogrammes) : ```Shell $ gtkwave -g taptempo.vcd ``` Et donne la trace suivante :  Cette simulation est particuliĂšrement longue (il mâa fallu environ une heure et demie sur mon vieux T430) et gĂ©nĂšre un fichier de trace monstrueux. En phase de dĂ©veloppement, on va gĂ©nĂ©ralement lancer de petites simulations par modules, comme on peut le voir pour le module `debounce` dans le rĂ©pertoire cocotb/test_debounce. On changera Ă©galement certaines constantes de temps pour limiter les « pas » de simulation consommant inutilement du calcul processeur. Il est Ă©galement possible de laisser lâordinateur Ă©crire les stimuli grĂące Ă la mĂ©thode de preuve formelle. Câest la mĂ©thode qui a Ă©tĂ© utilisĂ©e ici pour les modules. Les fichiers de configuration se trouvent dans le rĂ©pertoire `formal/`. SynthĂšse sur ColorLight ======================= La [Colorlight](https://fr.aliexpress.com/item/32281130824.html) nâest pas initialement une carte de dĂ©veloppement pour les FPGA. Câest une carte permettant de piloter des panneaux de LED qui nous agressent un peu partout dans les rues commerçantes. Cependant, [un petit malin](https://github.com/q3k/chubby75/tree/master/5a-75b) sâest rendu compte quâelle Ă©tait munie dâun FPGA de chez Lattice : lâ[ECP5](https://www.latticesemi.com/en/Products/FPGAandCPLD/ECP5). Ce FPGA possĂšde deux gros avantages : - il est relativement gros, suffisamment pour possĂ©der des multiplieurs cĂąblĂ©s, des sĂ©rialiseurs-dĂ©sĂ©rialiseurs... - on peut dĂ©velopper dessus avec une chaĂźne de dĂ©veloppement intĂ©gralement _open source_ ! JusquâĂ la Colorlight, les kits de dĂ©veloppement ECP5 nâĂ©taient pas donnĂ©s puisque [les premiĂšres cartes dĂ©butaient Ă 100 US$ minimum](https://www.latticesemi.com/en/Solutions/Solutions/SolutionsDetails01/CommunitySourced). Mais avec la Colorlight, on tombe Ă 15 US,ăă« ce qui en fait un kit de dĂ©veloppement ultra bon marchĂ© pour se faire la main avec des FPGA. Et comme tout est _open source_, il est aisĂ© dâaller installer les logiciels permettant de synthĂ©tiser TapTempo sur sa distribution GNU/Linux prĂ©fĂ©rĂ©e. Lâexplication de lâinstallation des outils est hors de propos de cet article (un article dĂ©taillĂ© sur la Colorlight est disponible dans le [_Hackable_ n^(o) 35](https://www.hackable.fr/)), mais une fois les outils installĂ©s, il suffit de se rendre dans le rĂ©pertoire `synthesis/colorlight` du projet et de faire `make` : ```Shell $ make [...] Info: Device utilisation: Info: TRELLIS_SLICE: 328/12144 2% Info: TRELLIS_IO: 3/ 197 1% Info: DCCA: 1/ 56 1% Info: DP16KD: 0/ 56 0% Info: MULT18X18D: 0/ 28 0% Info: ALU54B: 0/ 14 0% Info: EHXPLLL: 0/ 2 0% Info: EXTREFB: 0/ 1 0% Info: DCUA: 0/ 1 0% Info: PCSCLKDIV: 0/ 2 0% Info: IOLOGIC: 0/ 128 0% Info: SIOLOGIC: 0/ 69 0% Info: GSR: 0/ 1 0% Info: JTAGG: 0/ 1 0% Info: OSCG: 0/ 1 0% Info: SEDGA: 0/ 1 0% Info: DTR: 0/ 1 0% Info: USRMCLK: 0/ 1 0% Info: CLKDIVF: 0/ 4 0% Info: ECLKSYNCB: 0/ 10 0% Info: DLLDELD: 0/ 8 0% Info: DDRDLL: 0/ 4 0% Info: DQSBUFM: 0/ 8 0% Info: TRELLIS_ECLKBUF: 0/ 8 0% Info: ECLKBRIDGECS: 0/ 2 0% [...] ecppack --svf taptempo.svf taptempo_out.config taptempo.bit ``` On voit ici que les ressources utilisĂ©es pour TapTempo sont ridicules par rapport au FPGA utilisĂ©. La curieuse ou le curieux qui voudra « voir » le placementâroutage dans le FPGA utilisera lâoption `--gui` dans la commande NextPnR pour avoir lâinterface graphique : ```Shell $ nextpnr-ecp5 --25k --package CABGA256 --speed 6 --json taptempo.json --textcfg taptempo_out.config --lpf taptempo.lpf --freq 25 --gui ``` Ce qui donne un autre aperçu du remplissage du FPGA :  Pour tĂ©lĂ©charger le _bitstream_ dans le FPGA, on pourra utiliser [openFPGALoader](https://github.com/trabucayre/openFPGALoader) en donnant simplement le nom du _bitstream_ : ```Shell $ openFPGALoader taptempo.bit ``` Exercices de travaux pratiques ============================== Pour celles et ceux qui ont suivi jusquâici et qui voudraient se faire la main avec ce projet, voici quelques propositions de « sujet de TP » :) : - utilisation dâun multiplieur cĂąblĂ© de lâECP5 pour faire la division dans per2bpm ; - ajout un module de moyennage sur cinq Ă©chantillons pour coller Ă la spĂ©cification initiale de TapTempo ; - utilisation dâautres platesâformes FPGA Ă bas coĂ»t : [QuickFeather](https://www.crowdsupply.com/quicklogic/quickfeather), [FireAnt](http://www.fabienm.eu/flf/fireant-un-petit-nouveau-dans-le-monde-du-fpga-a-bas-cout/), [Tang Nano](https://tangnano.sipeed.com/en/)... NâhĂ©sitez pas Ă me proposer des demandes dâintĂ©gration Git pour amĂ©liorer le projet. Conclusion ========== On voit que dĂšs que lâon passe dans le domaine de lâembarquĂ© les choses se compliquent et prennent plus de temps. Alors que sur un PC on aurait pu faire ça en [une ligne de code](https://linuxfr.org/users/zerodeux/journaux/taptempo-en-une-ligne), quand [on embarque ça dans un microcontrĂŽleur](https://linuxfr.org/users/belegar--2/journaux/taptempo-sur-stm32f469i-discovery), câest dĂ©jĂ plus compliquĂ©. Mais si lâon passe dans le monde des FPGA et des ASIC, le projet prend une toute autre dimension. Câest la raison pour laquelle il faut toujours se demander si un FPGA est bien Ă propos pour notre projet, non seulement cela coĂ»tera plus cher en composant quâune solution sur Ă©tagĂšre, mais en plus le temps de dĂ©veloppement (et donc le coĂ»t) sera nettement supĂ©rieur. LâidĂ©e dâutiliser une touche de tĂ©lĂ©graphe pour mesurer le tempo nâĂ©tait peutâĂȘtre pas la meilleure, compte tenu des rebonds qui sont relativement violents. MĂȘme avec le module lisseur de rebond ([debounce](https://github.com/Martoni/TapTempoASIC/blob/master/hdl/debounce.v)), il subsiste quelques rebonds trop longs. Un tempo maximum Ă 250 nâest pas si rapide et lâon est vite frustrĂ© de lâatteindre alors quâon pourrait mesurer des tempos de musiques plus... rythmĂ©es. On peut facilement passer Ă 300, mais ça reste lent. Si lâon veut un tempo plus rapide, il faut tout dâabord changer la graduation sur le voltmĂštre, puis modifier le paramĂštre `BPM_MAX` dans le code. On a ici un modĂšle de projet qui est facile Ă synthĂ©tiser sur nâimporte quel petit FPGA. Câest un projet qui peut ĂȘtre intĂ©ressant si lâon souhaite se sortir un peu les doigts des LED qui clignotent. La dĂ©monstration Ă©tant faite du fonctionnement de lâarchitecture globale, il est aisĂ© de sâen servir pour la réécrire dans dâautres langages de description de matĂ©riel comme le VHDL, Chisel (mĂȘme sâil y en a [dĂ©jĂ une](https://linuxfr.org/users/martoni/journaux/integration-de-taptempo-chisel-sur-apf27) pour taptempo), Migen/Litex, MyHDL, Clash (en plus, ça permettrait de dĂ©bloquer [la dĂ©pĂȘche _LinuxFr.org_](https://linuxfr.org/redaction/news/sortie-de-c-ash-1-0) sur le sujet)... Pour le curieux, ou la curieuse, qui sera allĂ© voir le code sur le projet GitHub, ce projet a Ă©tĂ© dĂ©veloppĂ© avec une dose de [preuves formelles](https://fr.wikipedia.org/wiki/M%C3%A9thode_formelle_(informatique)#Preuves_formelles) grĂące au logiciel libre [Yosys-SMTBMC](http://www.clifford.at/papers/2017/smtbmc-sby/slides.pdf).