• Li chevron_right

      TapTempo pour Arduino Uno

      xulops · pubsub.eckmul.net / linuxfr_news · Wednesday, 23 December, 2020 - 11:01 · 25 minutes

    <div><p>Puisqu’elle n’existe pas encore, voici une version de TapTempo pour Arduino Uno, utilisant Arduino IDE sous Linux.</p> <p>Arduino est une marque italienne proposant des cartes de développement open-source basées sur des micro-contrôleurs AVR, ARM et Cortex-A3.</p> <p>L’Arduino Uno est la carte la plus connue et la plus accessible : compter environ deux euros en Chine pour des copies d’Arduino, et moins de dix euros en France. Elle est équipée d’un micro-contrôleur Atmel ATmega328P, dont les caractéristiques techniques sont : architecture Atmel AVR, 16MHz, 8bit, 32ko Flash, 1ko EEPROM, 2ko de SRAM. On est donc loin des PC avec CPU en GHz et RAM en Go.</p> </div><ul><li>lien nᵒ 1 : <a title="https://linuxfr.org/wiki/taptempo" hreflang="fr" href="https://linuxfr.org/redirect/107546">Le projet TapTempo</a></li><li>lien nᵒ 2 : <a title="https://linuxfr.org/news/taptempo-en-verilog" hreflang="fr" href="https://linuxfr.org/redirect/107547">TapTempo en Verilog</a></li><li>lien nᵒ 3 : <a title="https://linuxfr.org/news/portage-de-taptempo-en-vhdl" hreflang="fr" href="https://linuxfr.org/redirect/107548">Portage de TapTempo en VHDL</a></li><li>lien nᵒ 4 : <a title="https://fr.wikipedia.org/wiki/Arduino" hreflang="fr" href="https://linuxfr.org/redirect/107569">Page wikipédia Arduino</a></li><li>lien nᵒ 5 : <a title="https://www.arduino.cc/" hreflang="fr" href="https://linuxfr.org/redirect/107570">Site officiel Arduino</a></li><li>lien nᵒ 6 : <a title="https://www.arduino.cc/en/guide/linux" hreflang="en" href="https://linuxfr.org/redirect/107571">Installation d’Arduino IDE sous Linux</a></li><li>lien nᵒ 7 : <a title="https://randomnerdtutorials.com/" hreflang="en" href="https://linuxfr.org/redirect/107572">Site sur les IoT à base d’Arduino, ESP8266, ESP32 et leurs capteurs/accessoires</a></li></ul><div><h2 class="sommaire">Sommaire</h2> <ul class="toc"> <li><a href="#toc-les-avantages-de-la-carte">Les avantages de la carte</a></li> <li><a href="#toc-le-programme">Le programme</a></li> <li><a href="#toc-les-fonctions-setup-et-loop">Les fonctions setup et loop</a></li> <li><a href="#toc-usage-m%C3%A9moire">Usage mémoire</a></li> <li><a href="#toc-avec-dautres-micro-contr%C3%B4leurs">Avec d’autres micro-contrôleurs</a></li> </ul> <h2 id="toc-les-avantages-de-la-carte">Les avantages de la carte</h2> <p>Les avantages de cette carte sont :</p> <ul> <li>14 entrées/sorties numériques, dont 6 PWM ;</li> <li>6 entrées analogiques ;</li> <li>port série, bus I2C et SPI ;</li> <li>consommation électrique autour de 50mA sous 5V ;</li> <li>un nombre phénoménal de capteurs et accessoires en tous genres et pas chers (shield moteur, capteurs ultrason, température, pression, humidité, vibration…) ;</li> <li>un port USB pour transférer le programme dans le micro-contrôleur, et retourner des infos au PC via le port série de la carte ;</li> <li>un IDE (Arduino IDE sous GPL 2.0) qui fonctionne sous Linux et prend en charge toute la partie transfert du code. Il fait également office de moniteur série, et plotter/grapheur bien pratique pour suivre l’évolution de valeurs de capteurs dans le temps.</li> </ul> <p>C’est donc une carte simple et idéale pour apprendre à faire des robots, automatiser sa serre de jardin ou son aquarium, faire une serrure à carte sans contact, etc.</p> <p>Pour l’exemple TapTempo, j’utilise une « Funduino UNO », copie chinoise à bas prix, un écran LCD de deux lignes de 16 caractères avec interface I2C, et un bouton poussoir câblé entre l’entrée numérique n°2 et la masse.</p> <p><img src="//img.linuxfr.org/img/68747470733a2f2f78756c6f70732e6e65742f696d6167652f61757472652f61726475696e6f5f756e6f5f636f6d70617469626c652e6a7067/arduino_uno_compatible.jpg" alt="Copie chinoise d’Arduino Uno" title="Source : https://xulops.net/image/autre/arduino_uno_compatible.jpg"></p> <p>Comme je n’aime pas les câbles Dupont mâles, j’utilise ici une carte supplémentaire qui vient se poser sur l’Arduino Uno, qui ne fait que rendre plus accessible les pins de connexion (un sensor shield). C’est facultatif et ça marche très bien sans. Le câblage est d’ailleurs simple à réaliser.</p> <p><img src="//img.linuxfr.org/img/68747470733a2f2f78756c6f70732e6e65742f696d6167652f61757472652f736368656d615f6361626c6167652e706e67/schema_cablage.png" alt="Câblage du TapTempo sur Arduino Uno" title="Source : https://xulops.net/image/autre/schema_cablage.png"></p> <h2 id="toc-le-programme">Le programme</h2> <p>Le programme est écrit dans Arduino IDE qui permet de faire du C et C++. On peut utiliser d’autres IDE et d’autres langages (LUA, microPython). Voici le TapTempo basique :</p> <pre><code class="c"> <span class="cp">#include</span> <span class="cpf">&lt;LiquidCrystal_I2C.h&gt;</span><span class="cp"></span> <span class="cp">#include</span> <span class="cpf">&lt;Wire.h&gt;</span><span class="cp"></span> <span class="n">LiquidCrystal_I2C</span> <span class="nf">lcd</span><span class="p">(</span><span class="mh">0x27</span><span class="p">,</span><span class="mi">16</span><span class="p">,</span><span class="mi">2</span><span class="p">);</span> <span class="c1">// parametres de l'ecran à cristaux liquides</span> <span class="c1">// quelques constantes et variables globales</span> <span class="k">const</span> <span class="n">byte</span> <span class="n">TAP_PIN</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span> <span class="c1">// bouton poussoir connecté sur l'entrée 2</span> <span class="kt">bool</span> <span class="n">bouton_status</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">t</span><span class="p">[</span><span class="mi">6</span><span class="p">];</span> <span class="c1">// stocke 6 taps pour avoir 5 intervalles</span> <span class="n">byte</span> <span class="n">i</span><span class="p">;</span> <span class="n">byte</span> <span class="n">n</span><span class="p">;</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">tempo</span><span class="p">;</span> <span class="kt">void</span> <span class="nf">setup</span><span class="p">()</span> <span class="p">{</span> <span class="n">lcd</span><span class="p">.</span><span class="n">init</span><span class="p">();</span> <span class="n">lcd</span><span class="p">.</span><span class="n">backlight</span><span class="p">();</span> <span class="n">lcd</span><span class="p">.</span><span class="n">setCursor</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">);</span> <span class="n">lcd</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"TapTempo Arduino"</span><span class="p">);</span> <span class="n">lcd</span><span class="p">.</span><span class="n">setCursor</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">1</span><span class="p">);</span> <span class="n">lcd</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"Tempo : ...."</span><span class="p">);</span> <span class="n">pinMode</span><span class="p">(</span><span class="n">TAP_PIN</span><span class="p">,</span> <span class="n">INPUT_PULLUP</span><span class="p">);</span> <span class="c1">// on s'évite une résistance</span> <span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">6</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="n">t</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span> <span class="kt">void</span> <span class="nf">loop</span><span class="p">()</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span> <span class="n">bouton_status</span><span class="p">)</span> <span class="p">{</span> <span class="n">bouton_status</span> <span class="o">=</span> <span class="n">digitalRead</span><span class="p">(</span><span class="n">TAP_PIN</span><span class="p">);</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="n">bouton_status</span> <span class="o">=</span> <span class="n">digitalRead</span><span class="p">(</span><span class="n">TAP_PIN</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span> <span class="n">bouton_status</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// ok, un appui est detecté</span> <span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">5</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="n">t</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">t</span><span class="p">[</span><span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">];</span> <span class="n">t</span><span class="p">[</span><span class="mi">5</span><span class="p">]</span> <span class="o">=</span> <span class="n">millis</span><span class="p">();</span> <span class="k">if</span> <span class="p">(</span><span class="n">t</span><span class="p">[</span><span class="mi">5</span><span class="p">]</span> <span class="o">-</span> <span class="n">t</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span> <span class="o">&lt;</span> <span class="mi">3000</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// calcul de la moyenne des 5 derniers taps</span> <span class="n">tempo</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">n</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">6</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="n">t</span><span class="p">[</span><span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="n">tempo</span> <span class="o">+=</span> <span class="n">t</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">-</span> <span class="n">t</span><span class="p">[</span><span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">];</span> <span class="n">n</span><span class="o">++</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="n">tempo</span> <span class="o">=</span> <span class="mi">60000</span> <span class="o">/</span> <span class="p">(</span><span class="n">tempo</span> <span class="o">/</span> <span class="n">n</span><span class="p">);</span> <span class="c1">// affichage du resultat</span> <span class="n">lcd</span><span class="p">.</span><span class="n">setCursor</span><span class="p">(</span><span class="mi">8</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span> <span class="n">lcd</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="n">tempo</span><span class="p">);</span> <span class="n">lcd</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">" "</span><span class="p">);</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="c1">// premier tap, on attend le suivant</span> <span class="p">}</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="n">millis</span><span class="p">()</span> <span class="o">-</span> <span class="n">t</span><span class="p">[</span><span class="mi">5</span><span class="p">]</span> <span class="o">&gt;</span> <span class="mi">3000</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// rien depuis 3s (tempo &lt; 20), on reinitialise</span> <span class="n">lcd</span><span class="p">.</span><span class="n">setCursor</span><span class="p">(</span><span class="mi">8</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span> <span class="n">lcd</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"...."</span><span class="p">);</span> <span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">6</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="n">t</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> <span class="n">delay</span><span class="p">(</span><span class="mi">10</span><span class="p">);</span> <span class="c1">// pour ne pas se taper le rebond du bouton</span> <span class="p">}</span></code></pre> <h2 id="toc-les-fonctions-setup-et-loop">Les fonctions setup et loop</h2> <p>Arduino IDE impose la présence de deux fonctions : <em>setup</em> et <em>loop</em>.</p> <p>La fonction <em>setup</em> ne s’exécute qu’une seule fois, lorsque l’Arduino Uno est mis sous tension ou après un <em>reset</em> (le tranfert d’un nouveau code dans le micro-contrôleur est toujours automatiquement suivi d’un <em>reset</em>).</p> <p>Elle est assez facile à comprendre, elle initialise l’écran (facultatif), les entrées/sorties utilisées (ici uniquement le pin 2 pour le bouton poussoir), et le tableau des taps. On peut juste s’appesantir un peu sur le INPUT_PULLUP qui indique au micro-contrôleur qu’il doit activer la résistance interne de pullup de cette entrée. Si cette entrée est déclarée en INPUT seulement, il faut alors ajouter une résistance (10kOhm par exemple) entre l’entrée 2 et le 5V, afin de ramener l’entrée proche de 5 volts (HIGH, true) lorsque le bouton n’est pas pressé. Sinon l’entrée ne serait connectée à rien, donc avec un potentiel électrique indéterminé. Lorque le bouton est pressé, l’entrée est en connexion directe avec la masse, donc quasiment à 0V (LOW, false).</p> <p>La fonction <em>loop</em>, comme son nom l’indique, s’exécute en boucle.</p> <p>On commence par regarder si le bouton était pressé à la fin de la boucle précédente (bouton_status à false), si oui, on lit l’état du bouton jusqu’à relâchement. Sinon, le bouton était relâché, on peut donc regarder si maintenant il est pressé ou pas.</p> <p>Suit le classique calcul du tempo basé sur les 5 dernières mesures et l’affichage. L’affichage et le tableau se réinitialisent si pas de tap pendant plus de 3 secondes.</p> <p>La fonction <em>loop</em> se termine par un mystérieux délai de 10 ms, dont l’unique but est de ne pas capter le rebond du bouton. En effet, un bouton poussoir ne passe pas toujours franchement d’un état ouvert à l’état fermé, il rebondit, pouvant donner des séries d’ouvertures-fermetures perturbantes pendant quelques millisecondes. Sur ce bouton, la transition est normale (avec légère surtension pendant 2ms) au moins 9 fois sur 10, mais des rebonds sont présents sur environ un dixième des taps. Ces rebonds ne dépassent toutefois jamais les 3ms.</p> <p><img src="//img.linuxfr.org/img/68747470733a2f2f78756c6f70732e6e65742f696d6167652f61757472652f7265626f6e64732e6a7067/rebonds.jpg" alt="Rebonds du bouton" title="Source : https://xulops.net/image/autre/rebonds.jpg"></p> <p>Avec un délai de 10 ms, il n’y a donc pas de risques que les rebonds viennent perturber le programme.<br> Patienter un centième de seconde ne gêne pas à l’usage, j’ai mesuré avec précision (à l’unité près, je ne pense pas que descendre sous la virgule soit d’un grand intérêt musical) entre 20 BPM et 240 BPM, l’erreur la plus grande vient de mon doigt qui appuie sur le bouton (et du cerveau qui commande le doigt), bref je n’arrive pas à suivre, faudrait faire un robot (à base d’Arduino Uno ?) qui appuie sur le bouton à ma place.</p> <p><img src="//img.linuxfr.org/img/68747470733a2f2f78756c6f70732e6e65742f696d6167652f61757472652f74617074656d706f5f6d6f6e746167652e6a7067/taptempo_montage.jpg" alt="Montage en fonctionnement" title="Source : https://xulops.net/image/autre/taptempo_montage.jpg"></p> <h2 id="toc-usage-mémoire">Usage mémoire</h2> <p>En incluant les bibliothèques I2C (wire.h) et LiquidCrystal pour l’écran LCD, la compilation donne : </p> <ul> <li>4092 octets de FLASH utilisés sur les 32ko disponibles ;</li> <li>323 octets de SRAM utilisés sur les 2ko disponibles.</li> </ul> <p>En n’utilisant pas l’écran LCD et en envoyant le tempo mesuré sur le port série :</p> <ul> <li>2454 octets de FLASH utilisés ;</li> <li>218 octets de SRAM utilisés.</li> </ul> <p>Pour comparaison, pour un programme vide (juste les deux fonctions sans autre ligne de code), la compilation donne respectivement 444 octets de FLASH et 9 octets de SRAM utilisés. Ça peut sembler bizarre, mais le compilateur ajoute toujours un appel pour surveiller les événements sur le port série.<br> Pas de drame, tout cela reste léger.</p> <h2 id="toc-avec-dautres-micro-contrôleurs">Avec d’autres micro-contrôleurs</h2> <p>Les ESP8266 et ESP32, qui sont des micro-contrôleurs wifi de la société Expressif, ont aussi la cote en ce moment, principalement pour les remontées de mesures à distance, alertes par courriel… Pour ces micro-contrôleurs bien mieux équipés (160 MHz, 4 Mo RAM) qu’un Arduino Uno, le programme serait le même, Arduino IDE se chargeant de compiler et transférer le programme au micro-contrôleur. Il faudra penser cependant à ajouter une résistance de pullup suivant les modèles, tous les micro-contrôleurs ne sont pas pourvus de ces résistances internes. L’ESP32 est pourvu d’entrées tactiles (capacitive touch sensor), il serait intéressant de remplacer le bouton poussoir par une simple plaque métallique et regarder s’il est possible de s’affranchir des problèmes de rebonds.</p> </div><div><a href="https://linuxfr.org/news/taptempo-pour-arduino-uno.epub">Télécharger ce contenu au format EPUB</a></div> <p> <strong>Commentaires :</strong> <a href="//linuxfr.org/nodes/122644/comments.atom">voir le flux Atom</a> <a href="https://linuxfr.org/news/taptempo-pour-arduino-uno#comments">ouvrir dans le navigateur</a> </p>
    • Li chevron_right

      Portage de TapTempo en VHDL

      martoni · pubsub.eckmul.net / linuxfr_news · Thursday, 17 December, 2020 - 18:48 · 100 minutes

    <div><p>Ayant préparé tout le matériel pour faire du <a href="//linuxfr.org/news/taptempo-en-verilog">TapTempo en Verilog</a>, il était trop tentant de réaliser la même chose en <a href="https://fr.wikipedia.org/wiki/VHDL">VHDL</a>. L’occasion de se plonger dans ce langage de description matériel concurrent du Verilog.<br> L’occasion également de parler des avancées de GHDL, un simulateur libre du VHDL, et désormais également capable de faire la synthèse en conjonction avec Yosys.</p> <p>Pour comprendre TapTempo dans la culture <a href="//linuxfr.org/wiki/moule">moulesque</a> de LinuxFr.org, il est conseillé d’aller faire un petit tour sur la <a href="//linuxfr.org/wiki/taptempo">page wiki homonyme</a>.</p> </div><ul><li>lien nᵒ 1 : <a title="https://www.youtube.com/watch?v=heLm9gSVSTc" hreflang="fr" href="https://linuxfr.org/redirect/107436">La vidéo du projet en action.</a></li><li>lien nᵒ 2 : <a title="https://github.com/Martoni/TapTempoASIC" hreflang="en" href="https://linuxfr.org/redirect/107453">Le dépot du projet TapTempoASIC (contenant la version Verilog et VHDL)</a></li><li>lien nᵒ 3 : <a title="https://github.com/ghdl/ghdl-yosys-plugin" hreflang="en" href="https://linuxfr.org/redirect/107466">Le dépot du projet d&#39;extension à Yosys pour la synthèse avec GHDL</a></li><li>lien nᵒ 4 : <a title="https://github.com/ghdl/ghdl" hreflang="en" href="https://linuxfr.org/redirect/107467">Le dépot de GHDL, pour la synthèse et la simulation VHDL</a></li><li>lien nᵒ 5 : <a title="https://github.com/YosysHQ/yosys" hreflang="en" href="https://linuxfr.org/redirect/107468">Le dépot de Yosys, pour la synthèse Verilog</a></li></ul><div><h2 class="sommaire">Sommaire</h2> <ul class="toc"> <li><a href="#toc-le-vhdl">Le VHDL</a></li> <li> <a href="#toc-architecture-de-taptempo">Architecture de TapTempo</a><ul> <li> <a href="#toc-les-param%C3%A8tres-dans-un-package--taptempo_pkg">Les paramètres dans un package : </a><a href="https://github.com/Martoni/TapTempoASIC/blob/master/hdl/vhdl/taptempo_pkg.vhd">taptempo_pkg</a> </li> <li> <a href="#toc-un-cadenceur-timer-pour-tout-le-syst%C3%A8me--timepulse">Un cadenceur (timer) pour tout le système : </a><a href="https://github.com/Martoni/TapTempoASIC/blob/master/hdl/vhdl/timepulse.vhd">Timepulse</a> </li> <li> <a href="#toc-filtrage-des-rebonds--debounce">Filtrage des rebonds : </a><a href="https://github.com/Martoni/TapTempoASIC/blob/master/hdl/vhdl/debounce.vhd">debounce</a> </li> <li> <a href="#toc-mesure-de-la-p%C3%A9riode-dappuis--percount">Mesure de la période d’appuis : </a><a href="https://github.com/Martoni/TapTempoASIC/blob/master/hdl/vhdl/percount.vhd">percount</a> </li> <li> <a href="#toc-conversion-p%C3%A9riodefr%C3%A9quence-division--per2bpm">Conversion période/fréquence (division) : </a><a href="https://github.com/Martoni/TapTempoASIC/blob/master/hdl/vhdl/per2bpm.vhd">per2bpm</a> </li> <li> <a href="#toc-sortie-affichage-en-rapport-cyclique--pwmgen">Sortie «affichage» en rapport cyclique : </a><a href="https://github.com/Martoni/TapTempoASIC/blob/master/hdl/vhdl/pwmgen.vhd">pwmgen</a> </li> </ul> </li> <li> <a href="#toc-synth%C3%A8se-placement-routage-et-configuration">Synthèse, placement-routage et configuration</a><ul> <li><a href="#toc-ghdl--yosys-la-lune-de-miel">GHDL + Yosys, la lune de miel</a></li> <li><a href="#toc-placement-routage-avec-nextpnr">Placement routage avec NextPnR</a></li> <li><a href="#toc-en-avant-la-musique-avec-openfpgaloader">En avant la musique avec openFPGALoader</a></li> </ul> </li> <li><a href="#toc-conclusion">Conclusion</a></li> </ul> <p>Comme l’indique le titre de cette dépêche, nous allons effectuer un « portage » du projet TapTempo, qui était écrit en Verilog, vers le language VHDL. Nous aurons donc l’avantage de profiter d’une architecture déjà conçue. Et comme une partie des stimulus de simulation (banc de test) ont été écrit en Python avec <a href="https://github.com/cocotb/cocotb">CocoTB</a>, nous allons pouvoir les reprendre (presque) tels quels pour simuler notre version VHDL.</p> <p><img src="https://img.linuxfr.org/img/687474703a2f2f66616269656e6d2e65752f706172746167652f74617074656d706f636f6c6f726c696768742e4a5047/taptempocolorlight.JPG" alt="Vue d’ensemble du matériel pour TapTempo"></p> <h2 id="toc-le-vhdl">Le VHDL</h2> <p>Le VHDL – pour <a href="https://fr.wikipedia.org/wiki/Very_High_Speed_Integrated_Circuit"><strong>V</strong>HSIC</a> <strong>H</strong>ardware <strong>D</strong>escription <strong>L</strong>anguage – est un langage de description matérielle issu d’une commande du ministère de la Défense américaine, c’est donc en toute logique qu’il soit surtout populaire… en Europe !</p> <p>Le VHDL s’inspire fortement du langage <a href="http://https://fr.wikipedia.org/wiki/Ada_(langage)">ADA</a> pour décrire le comportement des circuits intégrés numériques. Cela permet de décrire un modèle que l’on peut ensuite simuler au moyen d’un simulateur. <br> Le simulateur VHDL OpenSource le plus connu est <a href="http://ghdl.free.fr/">GHDL</a>, initialement développé par le français <a href="//linuxfr.org/news/entretien-avec-tristan-gingold-auteur-de-ghdl">Tristan Gringold</a> comme une surcouche à GCC.<br> Il existe également un simulateur opensource nommé <a href="http://www.fabienm.eu/flf/nvc-lautre-simulateur-vhdl-libre/">nvc</a>, mais il est moins mature que GHDL. Les autres simulateurs OpenSource, comme <a href="http://freehdl.seul.org/">FreeHDL</a> ou VerilatorVHDL, sont plus anecdotiques.</p> <p>À partir d’une source VHDL il est également possible de générer un schéma de portes et de bascules logiques au moyen d’un logiciel de synthèse. Pendant longtemps, les seuls logiciels libres de synthèse HDL ciblaient le Verilog (<a href="https://symbiflow.readthedocs.io/projects/vtr/en/latest/odin/">OdinII</a> et <a href="http://www.clifford.at/yosys/">Yosys</a>). Mais depuis <a href="https://github.com/ghdl/ghdl-yosys-plugin">cette année</a> une extension GHDL est apparue pour Yosys. Si cette extension n’est pas encore vraiment stabilisée, elle n’en reste pas moins parfaitement utilisable comme nous allons le voir dans cet exemple.</p> <p>Le VHDL est très hiérarchique, on décrit des modules avec leurs entrées-sorties que l’on assemble ensuite à la manière d’un schéma bloc dans un composant «top».</p> <p>Dans le cas de TapTempo, l’interface du module «top» est déclarée dans une entité <a href="https://github.com/Martoni/TapTempoASIC/blob/master/hdl/vhdl/taptempo.vhd">comme ceci</a> :</p> <pre><code class="VHDL"><span class="k">entity</span> <span class="nc">taptempo</span> <span class="k">is</span> <span class="k">port</span> <span class="p">(</span> <span class="n">clk_i</span> <span class="o">:</span> <span class="k">in</span> <span class="kt">std_logic</span><span class="p">;</span> <span class="n">btn_i</span> <span class="o">:</span> <span class="k">in</span> <span class="kt">std_logic</span><span class="p">;</span> <span class="n">pwm_o</span> <span class="o">:</span> <span class="k">out</span> <span class="kt">std_logic</span> <span class="p">);</span> <span class="k">end</span> <span class="k">entity</span> <span class="nc">taptempo</span><span class="p">;</span></code></pre> <p>VHDL n’est pas sensible à la casse ! C’est quelque chose qui est très perturbant mais dans l’exemple ci-dessus <code>Entity</code> est exactement identique à <code>entity</code> ou <code>ENTITY</code>. En pratique, le simulateur mettra tout en minuscule.</p> <p>Le module possède deux entrées : l’horloge (<code>clk_i</code>) et le bouton (<code>btn_i</code>) ainsi qu’une sortie pwm (<code>pwm_o</code>) pour l’affichage. Il est possible de définir des paramètres pour la génération du module en utilisant le mot clef <a href="https://www.ics.uci.edu/%7Ejmoorkan/vhdlref/generics.html"><code>generic</code></a> de la même manière que le <code>port</code>. Pour plus de simplicité nous choisirons plutôt de mettre tous les paramètres dans un package séparé (nommé <a href="https://github.com/Martoni/TapTempoASIC/blob/master/hdl/vhdl/taptempo_pkg.vhd">taptempo_pkg.vhd</a>), à la manière des <code>include</code> du C/C++.</p> <p>Le type <code>std_logic</code> est utilisé pour représenter les états que peut prendre un signal. En simulation, il peut prendre 9 valeurs différentes :<br> - <code>0</code> : 0 logique;<br> - <code>1</code> : 1 logique;<br> - <code>Z</code> : Haute impédence;<br> - <code>X</code> : inconnue, plusieurs pilotes en conflit -&gt; erreur;<br> - <code>L</code> : 0 faible, peut-être vu comme une résistance de tirage à la masse (pull-down);<br> - <code>H</code> : 1 faible, peut-être vu comme une résistance de tirage à Vcc (pull-up);<br> - <code>W</code> : signal faible inconnu, quand il y a un conflit entre <code>L</code> et <code>H</code> mais qu’un troisième pilote fort (<code>0</code> ou <code>1</code>) pourrait tout de même mettre le signal à une valeur déterminable;<br> - <code>-</code> : pas d’importance;<br> - <code>U</code> : non initialisé.</p> <p>Dans la pratique on évitera d’utiliser toutes ces valeurs si on veut limiter les problèmes à la synthèse. Par exemple les valeurs 'L' et 'H' ne peuvent être synthétisées « à l’intérieur » d’un FPGA, les résistances de tirage ne sont disponibles que sur les entrées sorties.<br> Un bus de données bidirectionnel à l’extérieur du FPGA devra être séparé en deux bus dans le FPGA : un pour chaque direction.</p> <p>En réalité on se limite à '0' et '1'. Les valeurs 'U' et 'X' apparaissent dans les traces de simulation et nous avertissent d’un problème avec notre code (simulation ou synthèse).</p> <p>Le type <code>std_logic</code> peut être étendu en tableau avec <code>std_logic_vector</code> très pratique pour représenter des bus.</p> <h2 id="toc-architecture-de-taptempo">Architecture de TapTempo</h2> <p>L’architecture global de TapTempo qui est exactement la même que celle de la version Verilog est présenté dans le schéma ci-dessous:</p> <p><img src="https://img.linuxfr.org/img/687474703a2f2f66616269656e6d2e65752f706172746167652f736368656d615f74617074656d706f5f637261796f6e2e6a7067/schema_taptempo_crayon.jpg" alt="schema blocs taptempo"></p> <p>Le tempo en entrée est donné par une touche de morse</p> <p><img src="https://img.linuxfr.org/img/687474703a2f2f66616269656e6d2e65752f706172746167652f74617074656d706f5f656e747265655f6d6f7273655f6d6f6469662e6a7067/taptempo_entree_morse_modif.jpg" alt="Schéma de la touche morse"></p> <p>qui peut aisément être remplacé par un simple bouton poussoir (c’est d’ailleurs monté en parallèle du bouton de la colorlight). Cette entrée est synchronisée avec l’horloge du FPGA au moyen d’une double bascule en série pour éviter la métastabilité :</p> <pre><code class="VHDL"> <span class="c1">-- Synchronize btn</span> <span class="nc">btn_sync_p</span><span class="o">:</span> <span class="k">process</span> <span class="p">(</span><span class="n">clk_i</span><span class="p">,</span> <span class="n">rst</span><span class="p">)</span> <span class="k">begin</span> <span class="k">if</span> <span class="n">rst</span> <span class="o">=</span> <span class="sc">'1'</span> <span class="k">then</span> <span class="n">btn_s</span> <span class="o">&lt;=</span> <span class="sc">'0'</span><span class="p">;</span> <span class="n">btn_old</span> <span class="o">&lt;=</span> <span class="sc">'0'</span><span class="p">;</span> <span class="k">elsif</span> <span class="n">rising_edge</span><span class="p">(</span><span class="n">clk_i</span><span class="p">)</span> <span class="k">then</span> <span class="n">btn_s</span> <span class="o">&lt;=</span> <span class="n">btn_old</span><span class="p">;</span> <span class="n">btn_old</span> <span class="o">&lt;=</span> <span class="n">btn_i</span><span class="p">;</span> <span class="k">end</span> <span class="k">if</span><span class="p">;</span> <span class="k">end</span> <span class="k">process</span> <span class="nc">btn_sync_p</span><span class="p">;</span></code></pre> <p>Le module <code>debounce</code> se charge ensuite de filtrer les rebonds et transfert le signal au module de mesure de la période d’appuis.</p> <p>Cette période divise ensuite une constante de temps représentant le nombre de cycle de <code>timepulse</code> pour donner la fréquence en <strong>b</strong>attement <strong>p</strong>ar <strong>m</strong>inute (bpm).</p> <p>Pour être « affichée », cette valeur est transformée en un signal périodique avec un rapport cyclique proportionnel au battement <code>bpm</code>.</p> <p>Ce signal cyclique s’affiche ensuite très bien au moyen d’un voltmètre à aiguille comme celui-ci :</p> <p><img src="https://img.linuxfr.org/img/687474703a2f2f66616269656e6d2e65752f706172746167652f766f6c746d657472655f74617074656d706f2e6a7067/voltmetre_taptempo.jpg" alt="Photo du voltmètre à aiguille"></p> <p>La graduation qui nous intéresse ici est celle qui va de 0 à 250.</p> <h3 id="toc-les-paramètres-dans-un-package--taptempo_pkg">Les paramètres dans un package : <a href="https://github.com/Martoni/TapTempoASIC/blob/master/hdl/vhdl/taptempo_pkg.vhd">taptempo_pkg</a> </h3> <p>Pour configurer les différentes constantes et définir des fonctions utiles, on utilise un « package » nommé <code>taptempo_pkg.vhd</code> qui sera inclus dans tous les modules du projet :</p> <pre><code class="VHDL"><span class="k">use</span> <span class="nn">work.taptempo_pkg.</span><span class="k">all</span><span class="p">;</span></code></pre> <p>La déclaration d’un package en VHDL s’effectue en deux temps :</p> <ul> <li>on déclare d’abord les «objets» que l’on va utiliser dans le <code>package</code> :</li> </ul> <pre><code class="VHDL"><span class="c1">-- La déclaration de ce que le package rend visible</span> <span class="k">package</span> <span class="n">taptempo_pkg</span> <span class="k">is</span> <span class="k">constant</span> <span class="n">CLK_PER_NS</span> <span class="o">:</span> <span class="kt">natural</span><span class="p">;</span> <span class="k">constant</span> <span class="n">BPM_MAX</span> <span class="o">:</span> <span class="kt">natural</span><span class="p">;</span> <span class="k">constant</span> <span class="n">BPM_SIZE</span> <span class="o">:</span> <span class="kt">natural</span><span class="p">;</span> <span class="k">constant</span> <span class="n">TP_CYCLE</span> <span class="o">:</span> <span class="kt">natural</span><span class="p">;</span> <span class="k">constant</span> <span class="n">BTN_PER_MAX</span> <span class="o">:</span> <span class="kt">natural</span><span class="p">;</span> <span class="k">constant</span> <span class="n">BTN_PER_SIZE</span> <span class="o">:</span> <span class="kt">natural</span><span class="p">;</span> <span class="k">constant</span> <span class="n">BTN_PER_MIN</span> <span class="o">:</span> <span class="kt">natural</span><span class="p">;</span> <span class="k">constant</span> <span class="n">MIN_US</span> <span class="o">:</span> <span class="kt">natural</span><span class="p">;</span> <span class="k">constant</span> <span class="n">MAX_COUNT</span> <span class="o">:</span> <span class="kt">natural</span><span class="p">;</span> <span class="k">constant</span> <span class="n">DEBOUNCE_PER_US</span><span class="o">:</span> <span class="kt">natural</span><span class="p">;</span> <span class="k">constant</span> <span class="n">DEB_MAX_COUNT</span> <span class="o">:</span> <span class="kt">natural</span><span class="p">;</span> <span class="k">constant</span> <span class="n">DEB_MAX_COUNT_SIZE</span> <span class="o">:</span> <span class="kt">natural</span><span class="p">;</span> <span class="k">constant</span> <span class="n">ZEROS</span> <span class="o">:</span> <span class="kt">std_logic_vector</span><span class="p">(</span><span class="mi">31</span> <span class="k">downto</span> <span class="mi">0</span><span class="p">);</span> <span class="c1">-- Usefull function for register size</span> <span class="k">function</span> <span class="n">log2ceil</span><span class="p">(</span><span class="n">m</span> <span class="o">:</span> <span class="kt">integer</span><span class="p">)</span> <span class="k">return</span> <span class="kt">integer</span><span class="p">;</span> <span class="k">end</span> <span class="k">package</span> <span class="nc">taptempo_pkg</span><span class="p">;</span></code></pre> <ul> <li>Puis on définit leurs valeurs et comportement dans le corps (<code>body</code>) :</li> </ul> <pre><code class="VHDL"><span class="k">package</span> <span class="k">body</span> <span class="n">taptempo_pkg</span> <span class="k">is</span> <span class="k">constant</span> <span class="n">CLK_PER_NS</span> <span class="o">:</span> <span class="kt">natural</span> <span class="o">:=</span> <span class="mi">40</span><span class="p">;</span> <span class="k">constant</span> <span class="n">BPM_MAX</span> <span class="o">:</span> <span class="kt">natural</span> <span class="o">:=</span> <span class="mi">250</span><span class="p">;</span> <span class="c1">-- period of tp in ns</span> <span class="k">constant</span> <span class="n">TP_CYCLE</span> <span class="o">:</span> <span class="kt">natural</span> <span class="o">:=</span> <span class="mi">5120</span><span class="p">;</span> <span class="c1">-- Debounce period in us</span> <span class="k">constant</span> <span class="n">DEBOUNCE_PER_US</span><span class="o">:</span> <span class="kt">natural</span> <span class="o">:=</span> <span class="mi">50</span><span class="n">_000</span><span class="p">;</span> <span class="k">constant</span> <span class="n">DEB_MAX_COUNT</span> <span class="o">:</span> <span class="kt">natural</span> <span class="o">:=</span> <span class="p">(</span><span class="mi">1000</span> <span class="o">*</span> <span class="p">(</span><span class="n">DEBOUNCE_PER_US</span> <span class="o">/</span> <span class="n">TP_CYCLE</span><span class="p">));</span> <span class="k">constant</span> <span class="n">DEB_MAX_COUNT_SIZE</span> <span class="o">:</span> <span class="kt">natural</span> <span class="o">:=</span> <span class="n">log2ceil</span><span class="p">(</span><span class="n">DEB_MAX_COUNT</span><span class="p">);</span> <span class="c1">-- constant MIN_NS : natural := 60000000000;</span> <span class="k">constant</span> <span class="n">MIN_US</span> <span class="o">:</span> <span class="kt">natural</span> <span class="o">:=</span> <span class="mi">60</span><span class="n">_000_000</span><span class="p">;</span> <span class="k">constant</span> <span class="n">BPM_SIZE</span> <span class="o">:</span> <span class="kt">natural</span> <span class="o">:=</span> <span class="n">log2ceil</span><span class="p">(</span><span class="n">BPM_MAX</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span> <span class="k">constant</span> <span class="n">BTN_PER_MAX</span> <span class="o">:</span> <span class="kt">natural</span> <span class="o">:=</span> <span class="mi">1000</span> <span class="o">*</span> <span class="p">(</span><span class="n">MIN_US</span> <span class="o">/</span> <span class="n">TP_CYCLE</span><span class="p">);</span> <span class="k">constant</span> <span class="n">BTN_PER_SIZE</span> <span class="o">:</span> <span class="kt">natural</span> <span class="o">:=</span> <span class="n">log2ceil</span><span class="p">(</span><span class="n">BTN_PER_MAX</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span> <span class="k">constant</span> <span class="n">BTN_PER_MIN</span> <span class="o">:</span> <span class="kt">natural</span> <span class="o">:=</span> <span class="mi">1000</span> <span class="o">*</span> <span class="p">(</span><span class="n">MIN_US</span> <span class="o">/</span> <span class="n">TP_CYCLE</span><span class="p">)</span> <span class="o">/</span> <span class="n">BPM_MAX</span><span class="p">;</span> <span class="k">constant</span> <span class="n">MAX_COUNT</span> <span class="o">:</span> <span class="kt">natural</span> <span class="o">:=</span> <span class="n">TP_CYCLE</span> <span class="o">/</span> <span class="n">CLK_PER_NS</span><span class="p">;</span> <span class="k">constant</span> <span class="n">ZEROS</span> <span class="o">:</span> <span class="kt">std_logic_vector</span><span class="p">(</span><span class="mi">31</span> <span class="k">downto</span> <span class="mi">0</span><span class="p">)</span> <span class="o">:=</span> <span class="mh">x"00000000"</span><span class="p">;</span> <span class="k">function</span> <span class="n">log2ceil</span><span class="p">(</span><span class="n">m</span> <span class="o">:</span> <span class="kt">integer</span><span class="p">)</span> <span class="k">return</span> <span class="kt">integer</span> <span class="k">is</span> <span class="k">begin</span> <span class="k">for</span> <span class="n">i</span> <span class="k">in</span> <span class="mi">0</span> <span class="k">to</span> <span class="kt">integer</span><span class="na">'high</span> <span class="k">loop</span> <span class="k">if</span> <span class="mi">2</span> <span class="o">**</span> <span class="n">i</span> <span class="o">&gt;=</span> <span class="n">m</span> <span class="k">then</span> <span class="k">return</span> <span class="n">i</span><span class="p">;</span> <span class="k">end</span> <span class="k">if</span><span class="p">;</span> <span class="k">end</span> <span class="k">loop</span><span class="p">;</span> <span class="k">end</span> <span class="k">function</span> <span class="nc">log2ceil</span><span class="p">;</span> <span class="k">end</span> <span class="k">package</span> <span class="k">body</span><span class="p">;</span></code></pre> <p>Vous noterez la lourdeur d’avoir à déclarer le type de la constante dans le package avant de donner sa valeur dans le <code>body</code>. Les mauvaises langues diront que ça n’est pas beaucoup plus lourd que le C++ et ses doubles fichier (*.h et *.cpp) pour définir une classe.</p> <h3 id="toc-un-cadenceur-timer-pour-tout-le-système--timepulse">Un cadenceur (timer) pour tout le système : <a href="https://github.com/Martoni/TapTempoASIC/blob/master/hdl/vhdl/timepulse.vhd">Timepulse</a> </h3> <p>Nous allons avoir besoin de compter le temps dans plusieurs blocs du projet. L’horloge câblée sur la colorlight est de 25Mhz. Comme nous n’avons pas besoin de précisions à la quarantaine de nanoseconde nous allons utiliser un compteur global pour générer des « pulses » toutes les 5,12µs. Ces pulses seront utilisés en entrée des blocs ayant besoin de compter le temps. Cela réduira la taille des compteurs puisqu’ils n’auront pas à repartir de l’horloge globale.</p> <p><a href="https://github.com/Martoni/TapTempoASIC/blob/master/hdl/vhdl/timepulse.vhd">Le code</a> est suffisamment concis pour que nous puissions le reproduire ici dans son intégralité.</p> <pre><code class="VHDL"><span class="k">library</span> <span class="nn">ieee</span><span class="p">;</span> <span class="k">use</span> <span class="nn">ieee.std_logic_1164.</span><span class="k">all</span><span class="p">;</span> <span class="k">use</span> <span class="nn">ieee.numeric_std.</span><span class="k">all</span><span class="p">;</span> <span class="k">use</span> <span class="nn">work.taptempo_pkg.</span><span class="k">all</span><span class="p">;</span> <span class="k">entity</span> <span class="nc">timepulse</span> <span class="k">is</span> <span class="k">port</span> <span class="p">(</span> <span class="c1">-- clock and reset</span> <span class="n">clk_i</span> <span class="o">:</span> <span class="k">in</span> <span class="kt">std_logic</span><span class="p">;</span> <span class="n">rst_i</span> <span class="o">:</span> <span class="k">in</span> <span class="kt">std_logic</span><span class="p">;</span> <span class="c1">-- timepulse output</span> <span class="n">tp_o</span> <span class="o">:</span> <span class="k">out</span> <span class="kt">std_logic</span> <span class="p">);</span> <span class="k">end</span> <span class="k">entity</span> <span class="nc">timepulse</span><span class="p">;</span> <span class="k">architecture</span> <span class="nc">RTL</span> <span class="k">of</span> <span class="nc">timepulse</span> <span class="k">is</span> <span class="k">signal</span> <span class="n">counter</span> <span class="o">:</span> <span class="kt">natural</span> <span class="k">range</span> <span class="mi">0</span> <span class="k">to</span> <span class="n">MAX_COUNT</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span> <span class="k">begin</span> <span class="n">tp_o</span> <span class="o">&lt;=</span> <span class="sc">'1'</span> <span class="k">when</span> <span class="p">(</span><span class="n">counter</span> <span class="o">=</span> <span class="mi">0</span><span class="p">)</span> <span class="k">else</span> <span class="sc">'0'</span><span class="p">;</span> <span class="nc">counter_p</span><span class="o">:</span> <span class="k">process</span> <span class="p">(</span><span class="n">clk_i</span><span class="p">,</span> <span class="n">rst_i</span><span class="p">)</span> <span class="k">begin</span> <span class="k">if</span> <span class="n">rst_i</span> <span class="o">=</span> <span class="sc">'1'</span> <span class="k">then</span> <span class="n">counter</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">elsif</span> <span class="n">rising_edge</span><span class="p">(</span><span class="n">clk_i</span><span class="p">)</span> <span class="k">then</span> <span class="k">if</span> <span class="n">counter</span> <span class="o">&lt;</span> <span class="n">MAX_COUNT</span> <span class="k">then</span> <span class="n">counter</span> <span class="o">&lt;=</span> <span class="n">counter</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span> <span class="k">else</span> <span class="n">counter</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">end</span> <span class="k">if</span><span class="p">;</span> <span class="k">end</span> <span class="k">if</span><span class="p">;</span> <span class="k">end</span> <span class="k">process</span> <span class="nc">counter_p</span><span class="p">;</span> <span class="k">end</span> <span class="k">architecture</span> <span class="nc">RTL</span><span class="p">;</span></code></pre> <p>Tout module VHDL est constitué d’une partie entité <code>entity</code> pour décrire les interfaces d’entrées-sorties. Ici nous avons les deux entrées horloge <code>clk_i</code> et reset <code>rst_i</code> caractéristiques d’un système synchrone. L’unique sortie est le pulse généré toutes les 5,12µs.</p> <p>La description du fonctionnement du module est donnée ensuite dans le bloc « <em>architecture</em> ». En plus des signaux d’entrées-sorties, il est possible de déclarer des signaux interne au bloc. Nous avons déclaré ici le signal <code>counter</code> qui est un entier naturel borné de 0 à <code>MAX_COUNT + 1</code>. La possibilité de borner les signaux que l’on déclare en <em>integer</em> permet d’aider l’outil de synthèse à générer un bus de taille adaptée quand on ne souhaite pas déclarer un <code>std_logic_vector</code> avec une taille explicite.</p> <p>Attention, un <code>signal</code> n’a pas la même signification qu’une variable dans un programme procédural. Une variable prend une seule valeur qui changera au cours de l’exécution du programme. Un signal doit être vu comme une liste des différentes valeurs que prendra le signal au cours de la simulation. La première valeur de la liste sera son état initial, et à chaque événement sur le signal on ajoutera une valeur à la liste jusqu’à la fin de la simulation.</p> <p>Nous aurions également pu déclarer des constantes à cet endroit, mais comme dit dans l’introduction, une constante étant un paramètre modifiable nous avons préféré le mettre dans le package <code>taptempo_pkg</code>.</p> <p>Le corps de l’architecture de timepulse possède deux « process » concurrents qui s’exécutent en parallèle. Le premier est une assignation continue :</p> <pre><code class="VHDL"> <span class="n">tp_o</span> <span class="o">&lt;=</span> <span class="sc">'1'</span> <span class="k">when</span> <span class="p">(</span><span class="n">counter</span> <span class="o">=</span> <span class="mi">0</span><span class="p">)</span> <span class="k">else</span> <span class="sc">'0'</span><span class="p">;</span></code></pre> <p>Il fait passer <code>tp_o</code> à '1' quand le compteur <code>counter</code> atteint la valeur 0. Ce « process » est activé à chaque évenement/changement sur le signal <code>counter</code>.</p> <p>Le second process est plus étoffé :</p> <pre><code class="VHDL"> <span class="nc">counter_p</span><span class="o">:</span> <span class="k">process</span> <span class="p">(</span><span class="n">clk_i</span><span class="p">,</span> <span class="n">rst_i</span><span class="p">)</span> <span class="k">begin</span> <span class="c1">--...</span> <span class="k">end</span> <span class="k">process</span> <span class="nc">counter_p</span><span class="p">;</span></code></pre> <p>Ce process est exécuté à chaque événement surgissant sur les signaux déclarés dans la liste de sensibilité (ici <code>clk_i</code> et <code>rst_i</code>). Le contenu du <code>process</code> est quant à lui exécuté de manière séquentielle.</p> <pre><code class="VHDL"> <span class="k">if</span> <span class="n">rst_i</span> <span class="o">=</span> <span class="sc">'1'</span> <span class="k">then</span> <span class="c1">--...</span> <span class="k">elsif</span> <span class="n">rising_edge</span><span class="p">(</span><span class="n">clk_i</span><span class="p">)</span> <span class="k">then</span> <span class="c1">--...</span> <span class="k">end</span> <span class="k">if</span><span class="p">;</span></code></pre> <p>Cette structure décrit une <a href="https://fr.wikipedia.org/wiki/Bascule_(circuit_logique)#Bascule_D">bascule D</a> qui sera bien reconnue comme telle par le logiciel de synthèse.<br> Un reset asynchrone ré-initialise le compteur à 0:</p> <pre><code class="VHDL"> <span class="n">counter</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">;</span></code></pre> <p>Et sur un front montant de l’horloge</p> <pre><code class="VHDL"> <span class="k">elsif</span> <span class="n">rising_edge</span><span class="p">(</span><span class="n">clk_i</span><span class="p">)</span> <span class="k">then</span></code></pre> <p>on incrémente le compteur jusqu’à son maximum.</p> <pre><code class="VHDL"> <span class="k">if</span> <span class="n">counter</span> <span class="o">&lt;</span> <span class="n">MAX_COUNT</span> <span class="k">then</span> <span class="n">counter</span> <span class="o">&lt;=</span> <span class="n">counter</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span> <span class="k">else</span> <span class="n">counter</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">end</span> <span class="k">if</span><span class="p">;</span></code></pre> <p>Notez l’utilisation de l’opérateur d’affectation non bloquant <code>&lt;=</code> qui n’affectera la valeur qu’à la fin de l’exécution du process.</p> <p>Si par exemple nous avons le code suivant dans un process :</p> <pre><code class="VHDL"> <span class="n">counter</span> <span class="o">&lt;=</span> <span class="mi">10</span><span class="p">;</span> <span class="n">counter</span> <span class="o">&lt;=</span> <span class="mi">5</span><span class="p">;</span></code></pre> <p>La valeur effective de <code>counter</code> à la fin de l’exécution de process sera 5. Et à aucun moment la valeur 10 n’apparaîtra dans <code>counter</code>.</p> <p>Pour visualiser les signaux de sortie du module on pourra se rendre dans <a href="https://github.com/Martoni/TapTempoASIC/tree/master/cocotb/test_timepulse">le répertoire</a> <code>cocotb/test_timepulse</code> du projet et lancer le test en donnant ghdl comme simulateur:</p> <pre><code class="Shell">$ <span class="nb">cd</span> test_timepulse $ <span class="nv">SIM</span><span class="o">=</span>ghdl make</code></pre> <p>Le même principe pourra être appliqué pour simuler tous les autres modules de TapTempo.</p> <p>La simulation consiste à laisser passer le temps pendant une miliseconde :</p> <pre><code class="python"><span class="nd">@cocotb.test</span><span class="p">()</span> <span class="n">async</span> <span class="k">def</span> <span class="nf">double_push_test</span><span class="p">(</span><span class="n">dut</span><span class="p">):</span> <span class="n">trg</span> <span class="o">=</span> <span class="n">TestTimePulse</span><span class="p">(</span><span class="n">dut</span><span class="p">)</span> <span class="n">trg</span><span class="o">.</span><span class="n">display_config</span><span class="p">()</span> <span class="n">trg</span><span class="o">.</span><span class="n">log</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">"Running test!"</span><span class="p">)</span> <span class="n">await</span> <span class="n">trg</span><span class="o">.</span><span class="n">reset</span><span class="p">()</span> <span class="n">await</span> <span class="n">Timer</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">units</span><span class="o">=</span><span class="s2">"ms"</span><span class="p">)</span></code></pre> <p>La simulation génère un fichier nommé <code>timepulse.fst</code> visible avec <a href="https://github.com/gtkwave">gtkwave</a> :</p> <pre><code class="shell">$ gtkwave timepulse.fst</code></pre> <p>Comme visible dans l’image ci-dessous.</p> <p><img src="//img.linuxfr.org/img/687474703a2f2f7777772e66616269656e6d2e65752f6c696e757866722f74696d6570756c73655f776176652e6a7067/timepulse_wave.jpg" alt="Visualisation de la simulation timepulse avec gtkwave" title="Source : http://www.fabienm.eu/linuxfr/timepulse_wave.jpg"></p> <h3 id="toc-filtrage-des-rebonds--debounce">Filtrage des rebonds : <a href="https://github.com/Martoni/TapTempoASIC/blob/master/hdl/vhdl/debounce.vhd">debounce</a> </h3> <p>Le manipulateur morse génère des rebonds, le filtrage avait initialement été réglé à 20 ms sur la version Verilog. Pour être vraiment tranquille nous le monterons à 50 ms, mais c’est particulièrement long comme période. Comme pour le reste, cette configuration se trouve donc dans le package <code>taptempo_pkg.vhd</code>.</p> <p>Le module <code>debounce</code> va se charger de filtrer les rebonds au moyen d’une machine à états et d’un compteur.</p> <pre><code class="VHDL"><span class="k">entity</span> <span class="nc">debounce</span> <span class="k">is</span> <span class="k">port</span> <span class="p">(</span> <span class="c1">-- clock and reset</span> <span class="n">clk_i</span> <span class="o">:</span> <span class="k">in</span> <span class="kt">std_logic</span><span class="p">;</span> <span class="n">rst_i</span> <span class="o">:</span> <span class="k">in</span> <span class="kt">std_logic</span><span class="p">;</span> <span class="c1">-- inputs</span> <span class="n">tp_i</span> <span class="o">:</span> <span class="k">in</span> <span class="kt">std_logic</span><span class="p">;</span> <span class="n">btn_i</span> <span class="o">:</span> <span class="k">in</span> <span class="kt">std_logic</span><span class="p">;</span> <span class="c1">-- outputs</span> <span class="n">btn_o</span> <span class="o">:</span> <span class="k">out</span> <span class="kt">std_logic</span> <span class="p">);</span> <span class="k">end</span> <span class="k">entity</span><span class="p">;</span></code></pre> <p>Le comptage des pulsations du module timepulse s’effectue dans un process :</p> <pre><code class="VHDL"><span class="nc">counter_p</span><span class="o">:</span> <span class="k">process</span> <span class="p">(</span><span class="n">clk_i</span><span class="p">,</span> <span class="n">rst_i</span><span class="p">)</span> <span class="k">begin</span> <span class="k">if</span> <span class="n">rst_i</span> <span class="o">=</span> <span class="sc">'1'</span> <span class="k">then</span> <span class="n">counter</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">elsif</span> <span class="n">rising_edge</span><span class="p">(</span><span class="n">clk_i</span><span class="p">)</span> <span class="k">then</span> <span class="k">if</span> <span class="n">tp_i</span> <span class="o">=</span> <span class="sc">'1'</span> <span class="k">then</span> <span class="k">if</span> <span class="p">(</span><span class="n">state_reg</span> <span class="o">=</span> <span class="n">s_cnt_high</span><span class="p">)</span> <span class="k">or</span> <span class="p">(</span><span class="n">state_reg</span> <span class="o">=</span> <span class="n">s_cnt_low</span><span class="p">)</span> <span class="k">then</span> <span class="n">counter</span> <span class="o">&lt;=</span> <span class="n">counter</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span> <span class="k">else</span> <span class="n">counter</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">end</span> <span class="k">if</span><span class="p">;</span> <span class="k">end</span> <span class="k">if</span><span class="p">;</span> <span class="k">end</span> <span class="k">if</span><span class="p">;</span> <span class="k">end</span> <span class="k">process</span> <span class="nc">counter_p</span><span class="p">;</span></code></pre> <p>La machine à états est constituée de 4 états :</p> <pre><code class="VHDL"> <span class="k">type</span> <span class="n">t_state</span> <span class="k">is</span> <span class="p">(</span><span class="n">s_wait_low</span><span class="p">,</span> <span class="n">s_wait_high</span><span class="p">,</span> <span class="n">s_cnt_high</span><span class="p">,</span> <span class="n">s_cnt_low</span><span class="p">);</span> <span class="k">signal</span> <span class="n">state_reg</span> <span class="o">:</span> <span class="n">t_state</span><span class="p">;</span></code></pre> <p>Deux états d’attentes et deux états de comptages. Dans un état d’attente <code>s_wait_</code> on attend un front du bouton. Quand un front survient on passe dans un état de comptage <code>s_cnt_</code>.</p> <p>Dans un état de comptage, le compteur s’incrémente et passe à l’état d’attente lorsqu’il arrive à son maximum.</p> <p>De cette manière, seuls les fronts intervenant dans un état d’attente sont pris en compte. Une fois la machine à états dans un état de comptage, plus aucun front du bouton n’est « écouté ».</p> <h3 id="toc-mesure-de-la-période-dappuis--percount">Mesure de la période d’appuis : <a href="https://github.com/Martoni/TapTempoASIC/blob/master/hdl/vhdl/percount.vhd">percount</a> </h3> <p>Le module <code>percount</code> (<code>per</code>iod <code>count</code>er) va compter le temps d’une période entre deux appuis sur le bouton. </p> <p>Si on passe sur les signaux désormais bien connus <code>clk_i</code>, <code>rst_i</code> et <code>tp_i</code>; l’entrée du module est <code>btn_i</code> qui représente la valeur du bouton (appuyé ou non) sans rebond.</p> <p>Les deux signaux de sortie sont :<br> - Un vecteur <code>btn_per_o</code> représentant la valeur de la période mesurée<br> - Un signal <code>btn_per_valid</code> donnant la validité de la valeur précédente. La valeur de <code>btn_per_o</code> est considérée comme bonne uniquement si <code>btn_per_valid</code> est à '1'.</p> <p>Dans un premier <code>process</code> détecte les fronts descendants du bouton :</p> <pre><code class="VHDL"> <span class="n">btn_fall</span> <span class="o">&lt;=</span> <span class="n">btn_old</span> <span class="k">and</span> <span class="p">(</span><span class="k">not</span> <span class="n">btn_i</span><span class="p">);</span> <span class="nc">btn_p</span><span class="o">:</span> <span class="k">process</span> <span class="p">(</span><span class="n">clk_i</span><span class="p">,</span> <span class="n">rst_i</span><span class="p">)</span> <span class="k">begin</span> <span class="k">if</span> <span class="n">rst_i</span> <span class="o">=</span> <span class="sc">'1'</span> <span class="k">then</span> <span class="n">btn_old</span> <span class="o">&lt;=</span> <span class="sc">'0'</span><span class="p">;</span> <span class="k">elsif</span> <span class="n">rising_edge</span><span class="p">(</span><span class="n">clk_i</span><span class="p">)</span> <span class="k">then</span> <span class="n">btn_old</span> <span class="o">&lt;=</span> <span class="n">btn_i</span><span class="p">;</span> <span class="k">end</span> <span class="k">if</span><span class="p">;</span> <span class="k">end</span> <span class="k">process</span> <span class="nc">btn_p</span><span class="p">;</span></code></pre> <p>Puis dans un second process on compte le temps et on remet à 0 le compteur quand un front descendant du bouton surgit :</p> <pre><code class="VHDL"> <span class="c1">--...</span> <span class="k">elsif</span> <span class="n">rising_edge</span><span class="p">(</span><span class="n">clk_i</span><span class="p">)</span> <span class="k">then</span> <span class="k">if</span> <span class="n">btn_fall</span> <span class="o">=</span> <span class="sc">'1'</span> <span class="k">then</span> <span class="n">counter_valid</span> <span class="o">&lt;=</span> <span class="sc">'1'</span><span class="p">;</span> <span class="k">elsif</span> <span class="n">counter_valid</span> <span class="o">=</span> <span class="sc">'1'</span> <span class="k">then</span> <span class="n">counter</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">counter_valid</span> <span class="o">&lt;=</span> <span class="sc">'0'</span><span class="p">;</span> <span class="k">elsif</span> <span class="p">(</span><span class="n">tp_i</span> <span class="o">=</span> <span class="sc">'1'</span><span class="p">)</span> <span class="k">and</span> <span class="p">(</span><span class="n">counter</span> <span class="o">&lt;</span> <span class="n">BTN_PER_MAX</span><span class="p">)</span> <span class="k">then</span> <span class="n">counter</span> <span class="o">&lt;=</span> <span class="n">counter</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span> <span class="k">end</span> <span class="k">if</span><span class="p">;</span> <span class="k">end</span> <span class="k">if</span><span class="p">;</span> <span class="c1">-- ...</span></code></pre> <h3 id="toc-conversion-périodefréquence-division--per2bpm">Conversion période/fréquence (division) : <a href="https://github.com/Martoni/TapTempoASIC/blob/master/hdl/vhdl/per2bpm.vhd">per2bpm</a> </h3> <p>C’est la partie « dure » du système, il va falloir faire une division d’une constante par la période mesurée.</p> <svg viewbox="0 -1381.785489002541 13403 2302.0139780050818" style="width: 31.084ex; height: 5.301ex; vertical-align: -2.169ex; margin: 1px 0px; position: static;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs id="MathJax_SVG_glyphs"><path id="STIXWEBMAINI-62" stroke-width="10" d="M163 290h1c68 113 123 151 193 151c67 0 116 -43 116 -120c0 -158 -165 -332 -320 -332c-57 0 -130 23 -130 53v6l142 523c6 24 9 38 9 47c0 22 -5 23 -64 25v17c67 8 103 14 153 23l5 -5l-21 -82zM388 306c0 61 -26 86 -70 86c-48 0 -94 -42 -137 -115 c-44 -74 -72 -171 -72 -231c0 -23 15 -34 46 -34c43 0 86 21 123 59c60 62 110 161 110 235Z"></path><path id="STIXWEBMAINI-70" stroke-width="10" d="M215 428l-29 -100c54 81 111 113 171 113c74 0 115 -47 115 -125c0 -165 -151 -327 -297 -327c-23 0 -44 4 -69 17c-17 -70 -40 -152 -40 -166c0 -22 15 -29 62 -29v-16h-203v16c43 1 56 16 68 63l115 432c11 40 16 66 16 71c0 24 -28 27 -44 27h-22l-2 15l156 22 c3 0 5 -2 5 -4c0 0 -1 -5 -2 -9zM382 313c0 57 -17 85 -62 85c-53 0 -112 -54 -135 -114c-29 -75 -65 -204 -65 -238c0 -22 20 -38 48 -38c53 0 101 33 145 95c48 67 69 140 69 210Z"></path><path id="STIXWEBMAINI-6D" stroke-width="10" d="M704 105l-5 -7c-53 -74 -97 -107 -144 -107c-26 0 -40 16 -40 46c0 10 3 27 13 66l58 227c5 20 7 33 7 38c0 12 -9 21 -20 21c-30 0 -81 -47 -131 -117c-49 -69 -69 -118 -108 -272h-75l27 93c44 152 68 258 68 271c0 16 -9 25 -22 25c-34 0 -93 -58 -149 -143 c-34 -52 -48 -88 -96 -246h-75l43 144c40 133 55 216 55 228c0 15 -14 22 -41 22h-25v16l162 31l3 -2l-58 -209c89 145 159 211 220 211c40 0 60 -24 60 -60c0 -19 -13 -75 -40 -152c70 111 111 161 160 192c24 15 43 20 63 20c37 0 58 -27 58 -63c0 -7 -2 -23 -3 -28 l-68 -251c-6 -23 -10 -41 -10 -45c0 -11 4 -16 12 -16c17 0 35 16 65 53l21 26Z"></path><path id="STIXWEBMAIN-3D" stroke-width="10" d="M637 320h-589v66h589v-66zM637 120h-589v66h589v-66Z"></path><path id="STIXWEBMAIN-31" stroke-width="10" d="M394 0h-276v15c74 4 95 25 95 80v449c0 34 -9 49 -30 49c-10 0 -27 -5 -45 -12l-27 -10v14l179 91l9 -3v-597c0 -43 20 -61 95 -61v-15Z"></path><path id="STIXWEBMAIN-30" stroke-width="10" d="M476 330c0 -172 -63 -344 -226 -344c-171 0 -226 186 -226 350c0 177 69 340 230 340c131 0 222 -141 222 -346zM380 325c0 208 -44 325 -132 325c-83 0 -128 -118 -128 -321s44 -317 130 -317c85 0 130 115 130 313Z"></path><path id="STIXWEBMAIN-30" stroke-width="10" d="M476 330c0 -172 -63 -344 -226 -344c-171 0 -226 186 -226 350c0 177 69 340 230 340c131 0 222 -141 222 -346zM380 325c0 208 -44 325 -132 325c-83 0 -128 -118 -128 -321s44 -317 130 -317c85 0 130 115 130 313Z"></path><path id="STIXWEBMAIN-30" stroke-width="10" d="M476 330c0 -172 -63 -344 -226 -344c-171 0 -226 186 -226 350c0 177 69 340 230 340c131 0 222 -141 222 -346zM380 325c0 208 -44 325 -132 325c-83 0 -128 -118 -128 -321s44 -317 130 -317c85 0 130 115 130 313Z"></path><path id="STIXWEBMAIN-D7" stroke-width="10" d="M597 22l-46 -47l-231 230l-231 -230l-46 46l230 231l-230 231l47 46l230 -230l231 230l46 -45l-230 -232Z"></path><path id="STIXWEBMAINI-4D" stroke-width="10" d="M872 653v-16c-57 -9 -67 -23 -81 -74l-123 -443c-7 -25 -14 -46 -14 -62c0 -26 18 -40 81 -42l-1 -16h-271v16c65 7 85 24 102 86l126 459l-376 -561h-17l-62 546l-118 -428c-5 -18 -9 -43 -9 -54c0 -28 19 -44 70 -48v-16h-197v16c52 6 68 22 100 134l114 401 c8 27 12 44 12 52c0 19 -24 31 -74 34v16h181l56 -492l335 492h166Z"></path><path id="STIXWEBMAINI-49" stroke-width="10" d="M384 653v-16c-56 -7 -65 -16 -82 -77l-120 -429c-9 -32 -15 -56 -15 -76c0 -28 12 -32 69 -39v-16h-244v16c55 10 69 19 84 75l120 441c7 25 13 46 13 62c0 25 -16 40 -72 43v16h247Z"></path><path id="STIXWEBMAINI-4E" stroke-width="10" d="M727 653v-16c-63 -14 -65 -16 -102 -145l-146 -507h-18l-230 550l-114 -422c-6 -21 -9 -41 -9 -54c0 -28 18 -39 70 -43v-16h-198v16c56 8 70 24 106 152l117 415c-15 35 -39 54 -86 54v16h160l207 -499l106 388c6 21 8 32 8 44c0 36 -12 46 -69 51v16h198Z"></path><path id="STIXWEBMAIN-5F" stroke-width="10" d="M500 -125h-500v50h500v-50Z"></path><path id="STIXWEBMAINI-55" stroke-width="10" d="M765 653v-16c-62 -14 -66 -19 -104 -149l-83 -285c-22 -75 -54 -133 -97 -169c-43 -37 -93 -52 -158 -52c-131 0 -221 69 -221 164c0 54 18 121 44 216l52 189c5 18 9 33 9 45c0 26 -18 39 -80 41v16h272v-16c-68 -7 -85 -22 -104 -91l-63 -226 c-27 -95 -40 -157 -40 -179c0 -71 60 -118 138 -118c113 0 175 68 220 232l64 233c16 59 23 94 23 103c0 30 -17 41 -70 46v16h198Z"></path><path id="STIXWEBMAINI-53" stroke-width="10" d="M508 667l-40 -200l-18 3c0 102 -22 163 -119 163c-69 0 -112 -37 -112 -100c0 -49 8 -65 99 -156c91 -92 113 -135 113 -200c0 -114 -87 -195 -202 -195c-31 0 -58 7 -104 23c-27 10 -36 12 -47 12c-20 0 -36 -9 -43 -32h-18l34 224l20 -2c-2 -9 -2 -16 -2 -23 c0 -98 60 -167 144 -167c77 0 131 52 131 128c0 43 -14 73 -60 123l-39 42c-14 15 -26 29 -39 42c-60 64 -75 96 -75 150c0 105 82 164 178 164c31 0 64 -5 85 -14c21 -8 33 -11 46 -11c22 0 31 5 45 26h23Z"></path><path id="STIXWEBMAINI-62" stroke-width="10" d="M163 290h1c68 113 123 151 193 151c67 0 116 -43 116 -120c0 -158 -165 -332 -320 -332c-57 0 -130 23 -130 53v6l142 523c6 24 9 38 9 47c0 22 -5 23 -64 25v17c67 8 103 14 153 23l5 -5l-21 -82zM388 306c0 61 -26 86 -70 86c-48 0 -94 -42 -137 -115 c-44 -74 -72 -171 -72 -231c0 -23 15 -34 46 -34c43 0 86 21 123 59c60 62 110 161 110 235Z"></path><path id="STIXWEBMAINI-74" stroke-width="10" d="M296 428l-5 -32h-84l-87 -328c-1 -4 -2 -11 -2 -14c0 -13 6 -16 15 -16c16 0 32 16 81 79l13 -7c-60 -92 -91 -121 -143 -121c-27 0 -46 12 -46 37c0 11 8 42 16 74l78 296h-75c-1 3 -1 6 -1 6c0 11 9 20 33 25c32 7 97 56 133 110c4 6 10 9 14 9c6 0 9 -3 9 -8 c0 0 0 -5 -1 -7l-28 -103h80Z"></path><path id="STIXWEBMAINI-6E" stroke-width="10" d="M460 117l14 -13c-68 -93 -93 -113 -140 -113c-25 0 -47 16 -47 54c0 10 2 23 16 75l44 162c8 31 14 67 14 79c0 18 -9 29 -24 29c-40 0 -85 -49 -148 -142c-45 -67 -53 -90 -100 -248h-75l96 350c1 5 2 11 2 17c0 20 -14 26 -65 27v16c81 16 109 20 162 31l4 -2l-67 -218 c100 160 167 220 231 220c43 0 65 -25 65 -61c0 -18 -4 -39 -10 -60l-56 -203c-10 -36 -14 -53 -14 -61c0 -9 4 -18 16 -18c14 0 32 16 61 53c7 8 14 17 21 26Z"></path><path id="STIXWEBMAIN-5F" stroke-width="10" d="M500 -125h-500v50h500v-50Z"></path><path id="STIXWEBMAINI-70" stroke-width="10" d="M215 428l-29 -100c54 81 111 113 171 113c74 0 115 -47 115 -125c0 -165 -151 -327 -297 -327c-23 0 -44 4 -69 17c-17 -70 -40 -152 -40 -166c0 -22 15 -29 62 -29v-16h-203v16c43 1 56 16 68 63l115 432c11 40 16 66 16 71c0 24 -28 27 -44 27h-22l-2 15l156 22 c3 0 5 -2 5 -4c0 0 -1 -5 -2 -9zM382 313c0 57 -17 85 -62 85c-53 0 -112 -54 -135 -114c-29 -75 -65 -204 -65 -238c0 -22 20 -38 48 -38c53 0 101 33 145 95c48 67 69 140 69 210Z"></path><path id="STIXWEBMAINI-65" stroke-width="10" d="M358 109l12 -12c-57 -70 -121 -108 -204 -108c-81 0 -135 54 -135 137c0 155 149 315 299 315c53 0 82 -26 82 -68c0 -86 -105 -164 -284 -187c-10 -20 -10 -46 -10 -61c0 -55 37 -91 93 -91c44 0 75 16 147 75zM152 252l-17 -44c86 21 124 39 161 74c31 30 50 66 50 97 c0 24 -7 39 -35 39c-57 0 -125 -77 -159 -166Z"></path><path id="STIXWEBMAINI-72" stroke-width="10" d="M176 223l16 35c19 42 53 95 94 138c26 27 56 45 79 45c28 0 47 -21 47 -51s-17 -54 -47 -54c-18 0 -26 11 -35 26c-6 10 -9 14 -16 14c-18 0 -46 -33 -82 -94c-43 -74 -61 -114 -111 -282h-76l81 292c14 52 16 61 16 82s-18 26 -38 26c-8 0 -16 -1 -31 -3v17l155 27l3 -2 Z"></path><path id="STIXWEBMAIN-5F" stroke-width="10" d="M500 -125h-500v50h500v-50Z"></path><path id="STIXWEBMAINI-69" stroke-width="10" d="M264 599c0 -28 -23 -51 -49 -51c-29 0 -48 21 -48 53c0 31 20 53 47 53s50 -26 50 -55zM222 114l13 -11c-55 -84 -87 -114 -137 -114c-33 0 -49 18 -49 55c0 20 7 55 22 111l48 177c5 18 9 36 9 44c0 20 -10 23 -64 24v16c39 3 59 6 160 25l4 -3l-94 -343 c-5 -17 -10 -30 -10 -44c0 -10 6 -15 14 -15c17 0 41 21 84 78Z"></path><path id="STIXWEBMAIN-D7" stroke-width="10" d="M597 22l-46 -47l-231 230l-231 -230l-46 46l230 231l-230 231l47 46l230 -230l231 230l46 -45l-230 -232Z"></path><path id="STIXWEBMAINI-54" stroke-width="10" d="M633 653l-44 -164l-17 2c2 17 3 33 3 46c0 53 -36 81 -101 81h-58l-137 -490c-6 -21 -14 -44 -14 -66c0 -31 12 -39 53 -43l35 -3v-16h-288v16c69 6 92 21 107 75l143 527c-158 0 -190 -16 -238 -124l-18 4l42 155h532Z"></path><path id="STIXWEBMAINI-50" stroke-width="10" d="M146 653h241c147 0 218 -52 218 -148c0 -54 -27 -109 -68 -143c-46 -38 -116 -57 -205 -57c-42 0 -64 2 -90 8l-53 -193c-7 -27 -14 -48 -14 -63c0 -23 15 -35 69 -41v-16h-244v16c57 8 66 17 84 82l116 414c13 47 17 68 17 83c0 27 -14 35 -71 42v16zM320 592l-69 -245 c29 -5 34 -5 52 -5c62 0 97 6 128 24c44 25 71 69 71 134c0 89 -48 123 -130 123c-28 0 -46 -8 -52 -31Z"></path><path id="STIXWEBMAIN-5F" stroke-width="10" d="M500 -125h-500v50h500v-50Z"></path><path id="STIXWEBMAINI-43" stroke-width="10" d="M689 664l-37 -200l-18 3c-12 107 -61 163 -149 163c-74 0 -144 -38 -199 -97c-72 -77 -108 -191 -108 -301c0 -140 64 -205 175 -205c82 0 142 29 229 119l18 -15c-90 -106 -170 -149 -273 -149c-153 0 -261 96 -261 261c0 141 77 272 187 350c64 46 140 73 219 73 c33 0 75 -2 117 -17c20 -7 32 -7 41 -7c20 0 30 6 38 22h21Z"></path><path id="STIXWEBMAINI-59" stroke-width="10" d="M633 653v-16c-29 -10 -37 -20 -70 -61l-219 -270l-36 -123c-23 -79 -30 -106 -30 -127c0 -25 14 -34 53 -37l36 -3v-16h-289v16c66 4 89 17 107 81l59 205l-76 267c-15 52 -21 60 -77 68v16h239v-16c-60 -6 -72 -14 -72 -36c0 -15 12 -59 36 -144l31 -110l39 46 c105 124 149 186 149 213c0 17 -11 25 -40 28l-29 3v16h189Z"></path><path id="STIXWEBMAINI-43" stroke-width="10" d="M689 664l-37 -200l-18 3c-12 107 -61 163 -149 163c-74 0 -144 -38 -199 -97c-72 -77 -108 -191 -108 -301c0 -140 64 -205 175 -205c82 0 142 29 229 119l18 -15c-90 -106 -170 -149 -273 -149c-153 0 -261 96 -261 261c0 141 77 272 187 350c64 46 140 73 219 73 c33 0 75 -2 117 -17c20 -7 32 -7 41 -7c20 0 30 6 38 22h21Z"></path><path id="STIXWEBMAINI-4C" stroke-width="10" d="M559 180l-58 -180h-509v16c56 6 64 15 82 78l122 438c8 27 11 47 11 62c0 29 -15 39 -77 43v16h273v-16c-60 -4 -85 -20 -101 -77l-120 -429c-6 -23 -10 -40 -10 -54c0 -31 23 -41 105 -41c100 0 131 5 180 38c32 21 54 54 82 112Z"></path><path id="STIXWEBMAINI-45" stroke-width="10" d="M634 653l-31 -154l-22 2c2 17 3 33 3 46c0 60 -35 73 -192 73c-57 0 -67 -4 -73 -26l-66 -233h78c90 0 101 12 139 97l18 -4l-68 -232l-20 5c5 22 7 34 7 52c0 17 -2 26 -7 31c-11 10 -38 15 -78 15h-78l-32 -112c-25 -88 -36 -134 -36 -146c0 -24 23 -34 79 -34 c98 0 152 12 203 39c32 17 56 45 94 98l16 -8l-62 -162h-507v16c56 10 65 16 81 74l123 442c6 20 11 52 11 64c0 27 -15 35 -77 41v16h497Z"></path></defs><g stroke="black" fill="black" stroke-width="0" transform="matrix(1 0 0 -1 0 0)"><use xlink:href="#STIXWEBMAINI-62"></use><use xlink:href="#STIXWEBMAINI-70" x="505" y="0"></use><use xlink:href="#STIXWEBMAINI-6D" x="1014" y="0"></use><use xlink:href="#STIXWEBMAIN-3D" x="2018" y="0"></use><g transform="translate(3106,0)"><rect stroke="none" width="10176" height="60" x="0" y="220"></rect><g transform="translate(1640,676)"><use xlink:href="#STIXWEBMAIN-31"></use><use xlink:href="#STIXWEBMAIN-30" x="505" y="0"></use><use xlink:href="#STIXWEBMAIN-30" x="1010" y="0"></use><use xlink:href="#STIXWEBMAIN-30" x="1515" y="0"></use><use xlink:href="#STIXWEBMAIN-D7" x="2242" y="0"></use><use xlink:href="#STIXWEBMAINI-4D" x="3109" y="0"></use><use xlink:href="#STIXWEBMAINI-49" x="3986" y="0"></use><use xlink:href="#STIXWEBMAINI-4E" x="4375" y="0"></use><use xlink:href="#STIXWEBMAIN-5F" x="5107" y="0"></use><use xlink:href="#STIXWEBMAINI-55" x="5612" y="0"></use><use xlink:href="#STIXWEBMAINI-53" x="6382" y="0"></use></g><g transform="translate(60,-686)"><use xlink:href="#STIXWEBMAINI-62"></use><use xlink:href="#STIXWEBMAINI-74" x="505" y="0"></use><use xlink:href="#STIXWEBMAINI-6E" x="806" y="0"></use><use xlink:href="#STIXWEBMAIN-5F" x="1311" y="0"></use><use xlink:href="#STIXWEBMAINI-70" x="1816" y="0"></use><use xlink:href="#STIXWEBMAINI-65" x="2325" y="0"></use><use xlink:href="#STIXWEBMAINI-72" x="2774" y="0"></use><use xlink:href="#STIXWEBMAIN-5F" x="3191" y="0"></use><use xlink:href="#STIXWEBMAINI-69" x="3696" y="0"></use><use xlink:href="#STIXWEBMAIN-D7" x="4201" y="0"></use><use xlink:href="#STIXWEBMAINI-54" x="5068" y="0"></use><use xlink:href="#STIXWEBMAINI-50" x="5706" y="0"></use><use xlink:href="#STIXWEBMAIN-5F" x="6322" y="0"></use><use xlink:href="#STIXWEBMAINI-43" x="6827" y="0"></use><use xlink:href="#STIXWEBMAINI-59" x="7521" y="0"></use><use xlink:href="#STIXWEBMAINI-43" x="8159" y="0"></use><use xlink:href="#STIXWEBMAINI-4C" x="8853" y="0"></use><use xlink:href="#STIXWEBMAINI-45" x="9417" y="0"></use></g></g></g></svg> <p>Pour éviter d’avoir à utiliser les blocs multiplieurs dédiés de l’ECP5 et rester « portable » nous allons poser cette division comme on le ferait à la main.</p> <p>Pour ce faire, on va mettre le dividende dans un registre nommé <code>remainder</code> (pour le reste) :</p> <pre><code class="VHDL"><span class="n">remainder</span> <span class="o">&lt;=</span> <span class="kt">std_logic_vector</span><span class="p">(</span><span class="n">to_unsigned</span><span class="p">((</span><span class="n">MIN_US</span> <span class="o">/</span> <span class="n">TP_CYCLE</span><span class="p">)</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">,</span> <span class="n">remainder</span><span class="na">'length</span><span class="p">));</span></code></pre> <p>Et le diviseur dans un registre <code>divisor</code>:</p> <pre><code class="VHDL"><span class="k">if</span> <span class="n">to_integer</span><span class="p">(</span><span class="kt">unsigned</span><span class="p">(</span><span class="n">btn_per_i</span><span class="p">))</span> <span class="o">&lt;</span> <span class="n">BTN_PER_MIN</span> <span class="k">then</span> <span class="n">divisor</span> <span class="o">&lt;=</span> <span class="kt">std_logic_vector</span><span class="p">(</span><span class="n">to_unsigned</span><span class="p">(</span><span class="n">BTN_PER_MIN</span><span class="p">,</span> <span class="n">BTN_PER_SIZE</span><span class="p">))</span> <span class="o">&amp;</span> <span class="n">ZEROS</span><span class="p">(</span><span class="n">DIVIDENTWIDTH</span><span class="o">-</span><span class="mi">1</span> <span class="k">downto</span> <span class="mi">0</span><span class="p">);</span> <span class="k">else</span> <span class="n">divisor</span> <span class="o">&lt;=</span> <span class="n">btn_per_i</span> <span class="o">&amp;</span> <span class="n">ZEROS</span><span class="p">(</span><span class="n">DIVIDENTWIDTH</span><span class="o">-</span><span class="mi">1</span> <span class="k">downto</span> <span class="mi">0</span><span class="p">);</span> <span class="k">end</span> <span class="k">if</span><span class="p">;</span></code></pre> <p>Notez la la valeur minimum de <code>btn_per_i</code> qui nous évitera les problèmes de division par 0 ainsi que des valeurs de bpm trop élevées.</p> <p>La division est cadencée par une machine à états de 3 états :</p> <pre><code class="VHDL"><span class="k">type</span> <span class="n">t_state</span> <span class="k">is</span> <span class="p">(</span><span class="n">s_init</span><span class="p">,</span> <span class="n">s_compute</span><span class="p">,</span> <span class="n">s_result</span><span class="p">);</span> <span class="k">signal</span> <span class="n">state_reg</span> <span class="o">:</span> <span class="n">t_state</span><span class="p">;</span></code></pre> <p>Un état initial pour démarrer, un état de calcul et un état durant lequel la valeur de sortie est considérée comme bonne.</p> <p>L’algorithme de division consiste en une série de décalage à droite du diviseur :</p> <pre><code class="VHDL"><span class="n">divisor</span> <span class="o">&lt;=</span> <span class="s">"0"</span> <span class="o">&amp;</span> <span class="n">divisor</span><span class="p">(</span><span class="n">REGWIDTH</span><span class="o">-</span><span class="mi">1</span> <span class="k">downto</span> <span class="mi">1</span><span class="p">);</span></code></pre> <p>Et de décalages à gauche du quotient :</p> <pre><code class="VHDL"><span class="k">if</span> <span class="p">(</span><span class="kt">unsigned</span><span class="p">(</span><span class="n">divisor</span><span class="p">)</span> <span class="o">&lt;=</span> <span class="kt">unsigned</span><span class="p">(</span><span class="n">remainder</span><span class="p">))</span> <span class="k">then</span> <span class="n">remainder</span> <span class="o">&lt;=</span> <span class="kt">std_logic_vector</span><span class="p">(</span><span class="kt">unsigned</span><span class="p">(</span><span class="n">remainder</span><span class="p">)</span> <span class="o">-</span> <span class="kt">unsigned</span><span class="p">(</span><span class="n">divisor</span><span class="p">));</span> <span class="n">quotient</span> <span class="o">&lt;=</span> <span class="n">quotient</span><span class="p">(</span><span class="n">REGWIDTH</span><span class="o">-</span><span class="mi">2</span> <span class="k">downto</span> <span class="mi">0</span><span class="p">)</span> <span class="o">&amp;</span> <span class="s">"1"</span><span class="p">;</span> <span class="k">else</span> <span class="n">quotient</span> <span class="o">&lt;=</span> <span class="n">quotient</span><span class="p">(</span><span class="n">REGWIDTH</span><span class="o">-</span><span class="mi">2</span> <span class="k">downto</span> <span class="mi">0</span><span class="p">)</span> <span class="o">&amp;</span> <span class="s">"0"</span><span class="p">;</span> <span class="k">end</span> <span class="k">if</span><span class="p">;</span></code></pre> <p>Avec ajout d’un '1' ou d’un '0' en fonction de la valeur du diviseur et du reste. Lorsque le reste est supérieur au diviseur, on le soustrait du diviseur et on ajoute un bit '1' au moment du décalage du quotient. Sinon on ajoute le bit '0' sans toucher au registre de reste (<code>remainder</code>).</p> <p>Les registres <code>remainder</code>, <code>divisor</code> et <code>quotient</code> sont des <code>std_logic_vector</code> qui ne représente rien d’autre qu’un « paquet de bits » pour le langage. Si l’on souhaite effectuer des opérations de comparaison ou des additions soustraction dessus il faut donc dire au VHDL que ses bits représentent un nombre (ici non signé) en les « castant » avec <code>unsigned()</code>. Et si l’on souhaite assigner le résultat à un <code>std_logic_vector</code> il faut « caster » à nouveau avec <code>std_logic_vector</code>. Ces multiples conversion de type peuvent vite devenir un casse-tête quand on fait du VHDL.</p> <p>L’esperluette <code>&amp;</code> est ici un opérateur de concaténation de deux vecteurs <code>std_logic_vector</code>.</p> <p>Le résultat de la division est donné en continu par les bits de poids faible du registre <code>divisor</code> :</p> <pre><code class="VHDL"><span class="n">bpm_o</span> <span class="o">&lt;=</span> <span class="n">quotient</span><span class="p">(</span><span class="n">BPM_SIZE</span><span class="o">-</span><span class="mi">1</span> <span class="k">downto</span> <span class="mi">0</span><span class="p">);</span> <span class="n">bpm_valid</span> <span class="o">&lt;=</span> <span class="sc">'1'</span> <span class="k">when</span> <span class="n">state_reg</span> <span class="o">=</span> <span class="n">s_result</span> <span class="k">else</span> <span class="sc">'0'</span><span class="p">;</span></code></pre> <p><code>bpm_o</code> est considéré comme valide lorsque l’état de la machine à états est <code>s_result</code>.</p> <h3 id="toc-sortie-affichage-en-rapport-cyclique--pwmgen">Sortie «affichage» en rapport cyclique : <a href="https://github.com/Martoni/TapTempoASIC/blob/master/hdl/vhdl/pwmgen.vhd">pwmgen</a> </h3> <p>La sortie « d’affichage » de la valeur consiste à générer un signal périodique <code>pwm_o</code> dont le rapport cyclique est proportionnel à la valeur de <code>bpm_o</code>.</p> <p>On va pour cela compter de 0 à BPM_MAX :</p> <pre><code class="VHDL"><span class="c1">-- count</span> <span class="nc">count_p</span><span class="o">:</span> <span class="k">process</span> <span class="p">(</span><span class="n">clk_i</span><span class="p">,</span> <span class="n">rst_i</span><span class="p">)</span> <span class="k">begin</span> <span class="k">if</span> <span class="n">rst_i</span> <span class="o">=</span> <span class="sc">'1'</span> <span class="k">then</span> <span class="n">count</span> <span class="o">&lt;=</span> <span class="n">BPM_MAX</span><span class="p">;</span> <span class="k">elsif</span> <span class="n">rising_edge</span><span class="p">(</span><span class="n">clk_i</span><span class="p">)</span> <span class="k">then</span> <span class="k">if</span> <span class="n">tp_i</span> <span class="o">=</span> <span class="sc">'1'</span> <span class="k">then</span> <span class="k">if</span> <span class="n">count</span> <span class="o">=</span> <span class="mi">0</span> <span class="k">then</span> <span class="n">count</span> <span class="o">&lt;=</span> <span class="n">BPM_MAX</span><span class="p">;</span> <span class="k">else</span> <span class="n">count</span> <span class="o">&lt;=</span> <span class="n">count</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span> <span class="k">end</span> <span class="k">if</span> <span class="p">;</span> <span class="k">end</span> <span class="k">if</span> <span class="p">;</span> <span class="k">end</span> <span class="k">if</span><span class="p">;</span> <span class="k">end</span> <span class="k">process</span> <span class="nc">count_p</span><span class="p">;</span></code></pre> <p>Tant que la valeur du compteur se trouvera en dessous d’un seuil <code>pwmthreshold</code> elle sera à '1', une fois le seuil dépassé elle passera à '0':</p> <pre><code class="VHDL"><span class="c1">-- pwm output</span> <span class="n">pwm_o</span> <span class="o">&lt;=</span> <span class="sc">'1'</span> <span class="k">when</span> <span class="p">(</span><span class="n">count</span> <span class="o">&lt;</span> <span class="n">pwmthreshold</span><span class="p">)</span> <span class="k">else</span> <span class="sc">'0'</span><span class="p">;</span></code></pre> <p>Ce seuil ne doit pas changer de valeur pendant le comptage, on va donc devoir utiliser un registre intermédiaire <code>bpm_reg</code> pour stocker la valeur de <code>bpm_i</code> d’entrée :</p> <pre><code class="VHDL"><span class="c1">-- Latching bpm_i on bpm_valid</span> <span class="nc">bpm_register_p</span><span class="o">:</span> <span class="k">process</span> <span class="p">(</span><span class="n">clk_i</span><span class="p">,</span> <span class="n">rst_i</span><span class="p">)</span> <span class="k">begin</span> <span class="k">if</span> <span class="n">rst_i</span> <span class="o">=</span> <span class="sc">'1'</span> <span class="k">then</span> <span class="n">bpm_reg</span> <span class="o">&lt;=</span> <span class="n">BPM_RESET</span><span class="p">;</span> <span class="n">pwmthreshold</span> <span class="o">&lt;=</span> <span class="n">BPM_RESET</span><span class="p">;</span> <span class="k">elsif</span> <span class="n">rising_edge</span><span class="p">(</span><span class="n">clk_i</span><span class="p">)</span> <span class="k">then</span> <span class="k">if</span> <span class="n">bpm_valid</span> <span class="o">=</span> <span class="sc">'1'</span> <span class="k">then</span> <span class="n">bpm_reg</span> <span class="o">&lt;=</span> <span class="n">to_integer</span><span class="p">(</span><span class="kt">unsigned</span><span class="p">(</span><span class="n">bpm_i</span><span class="p">));</span> <span class="k">end</span> <span class="k">if</span><span class="p">;</span> <span class="k">if</span> <span class="p">(</span><span class="n">count</span> <span class="o">=</span> <span class="n">BPM_MAX</span><span class="p">)</span> <span class="k">then</span> <span class="n">pwmthreshold</span> <span class="o">&lt;=</span> <span class="n">bpm_reg</span><span class="p">;</span> <span class="k">end</span> <span class="k">if</span><span class="p">;</span> <span class="k">end</span> <span class="k">if</span><span class="p">;</span> <span class="k">end</span> <span class="k">process</span> <span class="nc">bpm_register_p</span><span class="p">;</span></code></pre> <p>Notez, à nouveau, la conversion d’un <code>std_logic_vector</code> (<code>bpm_i</code>) vers un entier naturel (<code>bpm_reg</code>) par le double « cast » <code>to_integer(unsigned())</code></p> <h2 id="toc-synthèse-placement-routage-et-configuration">Synthèse, placement-routage et configuration</h2> <p>Nous voici arrivé à la véritable information de cette dépêche : <strong>il est désormais possible de faire de la synthèse VHDL avec un logiciel libre</strong>.<br> Le VHDL est le langage avec lequel les étudiants français abordent le monde du FPGA en général. Je l’ai personnellement beaucoup pratiqué en utilisant ghdl pour la simulation et gtkwave pour visualiser les chronogrammes. Mais jusqu’à cette année, j’ai toujours dû passer sur les monstres propriétaires fournis gratuitement par les fabricants de FPGA pour la partie synthèse. Monstres de plusieurs dizaines de Giga-octets à télécharger, souvent précompilé pour une architecture 32 ou 64 bits mais pas les deux, incluant des machines virtuelles java et autres librairies graphique bugués quand on change la langue du système (coucou le <a href="https://forums.xilinx.com/t5/Memory-Interfaces-and-NoC/MIG-missing-input-clock-option/m-p/746423/highlight/true#M10242">MIG de Vivado</a>). Quand ils ne sont tout simplement pas compatibles Linux (de plus en plus rare cependant). Bref, la synthèse VHDL n’était pas une sinécure.</p> <p>La simulation n’était pas en reste non plus, car si GHDL fonctionnait plutôt bien, il n’était pas des plus rapides à l’époque. Et impossible de faire de la simulation mixte en mélangeant du VHDL et du Verilog.</p> <p>Impossible également d’utiliser la formule 1 de la simulation qu’est <a href="https://www.veripool.org/wiki/verilator">Verilator</a>, un simulateur un peu spécial qui converti son design en un objet C++, le testbench étant ensuite écrit comme du C++ «normal». Le rapport de performance est d’au moins <a href="http://www.fabienm.eu/flf/icarus-vs-verilator/">100 fois plus rapide</a>.</p> <p>Pour être honnête, signalons qu’il existe bien le logiciel français <a href="http://coriolis.lip6.fr/">Alliance</a>, mais c’est très orienté <a href="https://fr.wikipedia.org/wiki/Application-specific_integrated_circuit">ASIC</a>, et je n’ai jamais réussi à le faire fonctionner correctement. Peut-être que si des développeurs d’Alliance traînent sur LinuxFR, ils pourront nous en parler.</p> <p>Bref, la lumière est arrivée cette année avec le développement du greffon <a href="https://github.com/ghdl/ghdl-yosys-plugin">ghdl pour yosys</a> ainsi que les avancées à toute vapeur de la partie «synthèse» de <a href="https://github.com/ghdl/ghdl">ghdl</a>. Comme nous allons le voir dans la section suivante.</p> <h3 id="toc-ghdl--yosys-la-lune-de-miel">GHDL + Yosys, la lune de miel</h3> <p>Pour pouvoir synthétiser TapTempo il va falloir compiler et installer les trois projet suivant :</p> <ul> <li>GHDL : Le logiciel de simulation ainsi que de synthèse (ne pas oublier d’option de synthèse dans la configuration avant compilation) ;</li> <li>Yosys : le logiciel de synthèse Verilog, véritable couteau suisse pour le FPGA et plus si affinité ;</li> <li>ghdl-yosys-plugin : le greffon qui permet de compiler une librairie <code>ghdl.so</code> à copier dans le répertoire <code>YOSYS_PREFIX/share/yosys/plugins/</code> de yosys.</li> </ul> <p>Il est fortement conseillé de prendre les dernières versions du code des projets ci-dessus et de les compiler « à la main » car ces projets avancent vite et bougent beaucoup, les paquets des différentes distributions linux sont déjà largement obsolètes.</p> <p>Une fois installé on peut lancer yosys avec le greffon ghdl comme ceci :</p> <pre><code class="shell">$ yosys -m ghdl <span class="o">[</span>...<span class="o">]</span> Yosys <span class="m">0</span>.9+3686 <span class="o">(</span>git sha1 bc085761, clang <span class="m">8</span>.0.0-svn345496-1~exp1+0~20181029105533.852~1.gbpf10f36 -fPIC -Os<span class="o">)</span> yosys&gt;</code></pre> <p>Puis charger nos sources VHDL avec la commande ghdl, en précisant bien le nom du « top » que l’on souhaite élaborer avec l’option <code>-e</code>.</p> <pre><code class="shell">yosys&gt; ghdl --std<span class="o">=</span><span class="m">08</span> debounce.vhd per2bpm.vhd percount.vhd pwmgen.vhd rstgen.vhd taptempo_pkg.vhd taptempo.vhd timepulse.vhd -e taptempo <span class="m">1</span>. Executing GHDL. Importing module taptempo. Importing module rstgen. Importing module timepulse. Importing module debounce. Importing module percount. Importing module per2bpm. Importing module pwmgen_250.</code></pre> <p>Et c’est tout ! Maintenant on peut reprendre les commandes et la procédure que l’on avait utilisée dans le cas de <a href="//linuxfr.org/news/taptempo-en-verilog#toc-synth%C3%A8se-sur-colorlight">TapTempo en Verilog pour faire la synthèse</a>, le placement routage et le chargement.</p> <p>Notez que comme les sources ont été chargées et parsé dans yosys, il est parfaitement possible de le convertir en Verilog grâce à la commande <code>write_verilog</code>:</p> <pre><code>yosys&gt; write_verilog taptempo_converted.v 2. Executing Verilog backend. Dumping module `\debounce'. Dumping module `\per2bpm'. Dumping module `\percount'. Dumping module `\pwmgen_250'. Dumping module `\rstgen'. Dumping module `\taptempo'. Dumping module `\timepulse'. </code></pre> <p>La version Verilog ainsi générée pourra être simulée avec verilator ou icarus par exemple pour faire de la simulation mixte, ou tout simplement pour accélérer la simulation dans le cas de verilator.</p> <p>Pour synthétiser sur la colorlight qui est munie d’un FPGA ECP5 de chez Lattice, on utilisera la commande synth_ecp5 avec une sortie netlist au format json.</p> <pre><code>yosys&gt; synth_ecp5 -json taptempo.json [...] === taptempo === Number of wires: 540 Number of wire bits: 1515 Number of public wires: 540 Number of public wire bits: 1515 Number of memories: 0 Number of memory bits: 0 Number of processes: 0 Number of cells: 785 CCU2C 109 L6MUX21 9 LUT4 452 PFUMX 25 TRELLIS_FF 190 2.50. Executing CHECK pass (checking for obvious problems). Checking module taptempo... Found and reported 0 problems. 2.51. Executing JSON backend. </code></pre> <p>Arrivé à cette étape il peut être intéressant de comparer avec la version Verilog les ressources utilisées par le même projet :</p> <pre><code>$ yosys yosys&gt; read_verilog debounce.v per2bpm.v percount.v pwmgen.v rstgen.v taptempo.v timepulse.v yosys&gt; synth_ecp5 -json taptempo.json ... === taptempo === Number of wires: 487 Number of wire bits: 1435 Number of public wires: 487 Number of public wire bits: 1435 Number of memories: 0 Number of memory bits: 0 Number of processes: 0 Number of cells: 751 CCU2C 105 L6MUX21 1 LUT4 441 PFUMX 13 TRELLIS_FF 191 8.50. Executing CHECK pass (checking for obvious problems). Checking module taptempo... Found and reported 0 problems. 8.51. Executing JSON backend. </code></pre> <p>Nous obtenons un design légèrement plus petit avec la version Verilog, mais les ordres de grandeurs sont tout de même respecté.</p> <h3 id="toc-placement-routage-avec-nextpnr">Placement routage avec NextPnR</h3> <p><a href="https://github.com/YosysHQ/nextpnr">NextPnR</a> est un logiciel de placement routage qui prend le schéma (<a href="https://fr.wikipedia.org/wiki/Netlist">netlist</a>) de cellules «primitives» généré par le logiciel de synthèse et associe chaque cellule à une cellule disponible dans le FPGA. NextPnR effectue également le routage qui consiste à établir les connexions entre les différentes cellules.</p> <p>C’est également à cette étape que l’on va préciser la configuration du FPGA (taille, IO…). En plus du fichier <em>json</em> de synthèse nous donnerons un second fichier de configuration des IO nommé <code>taptempo.lpf</code> décrivant nos trois signaux d’entrées-sortie :</p> <pre><code>LOCATE COMP "clk_i" SITE "P6"; IOBUF PORT "clk_i" IO_TYPE=LVCMOS33; FREQUENCY PORT "clk_i" 25 MHZ; LOCATE COMP "btn_i" SITE "M13"; IOBUF PORT "btn_i" IO_TYPE=LVCMOS33; LOCATE COMP "pwm_o" SITE "P4"; IOBUF PORT "pwm_o" IO_TYPE=LVCMOS33; </code></pre> <p>Toutes les commandes de synthèse données ici sont bien évidemment disponibles dans un <a href="https://github.com/Martoni/TapTempoASIC/blob/master/synthesis/colorlight_vhdl/Makefile">Makefile</a> sur le dépot. Pour faire le placement routage nous pourrions taper la commande suivante :</p> <pre><code>$ nextpnr-ecp5 --25k --package CABGA256 --speed 6 --json taptempo.json --textcfg taptempo_out.config --lpf taptempo.lpf --freq 25 </code></pre> <p>Qui ne donnera <strong>pas</strong> le fichier de configuration nommé <strong>bitstream</strong> permettant de configurer le FPGA !</p> <p>Car les spécifications des FPGA sont gardées jalousement par les constructeurs, et il faut des heures et des heures d’ingénieries inverses pour venir à bout de ces informations. Travail qui a été effectué via le projet <a href="https://github.com/SymbiFlow/prjtrellis">Trellis</a> et qui nous permet de convertir la sortie texte précédente <code>taptempo_out.config</code> en un bitstream reconnu par l’EPC5 :</p> <pre><code>$ ecppack taptempo_out.config taptempo.bit </code></pre> <p>Et l’on décroche enfin le Saint Grââl permettant de configurer la colorlight : le bitstream <code>taptempo.bit</code>.</p> <h3 id="toc-en-avant-la-musique-avec-openfpgaloader">En avant la musique avec openFPGALoader</h3> <p>Arrivé à cette étape il serait vraiment dommage d’être contraint de relancer l’ide proprio du constructeur juste pour télécharger le bitstream dans le FPGA via une sonde USB-Jtag !</p> <p>C’est là que l’on peut dégainer le projet <a href="https://github.com/trabucayre/openFPGALoader">openFPGALoader</a> qui a pour ambition de permettre la configuration de tous les FPGA existant avec toutes les sondes disponibles sur le marché.</p> <pre><code>$ openFPGALoader taptempo.bit Open file taptempo.bit DONE Parse file DONE Enable configuration: DONE SRAM erase: DONE Loading: [==================================================] 100.000000% Done Disable configuration: DONE </code></pre> <p>Et voila, on peut maintenant taper taper taper <a href="https://www.youtube.com/watch?v=heLm9gSVSTc">jusqu’au bout de la nuit</a>.</p> <h2 id="toc-conclusion">Conclusion</h2> <p>Le VHDL est très verbeux, les évolutions du langage ont tenté de corriger un peu le tir mais cela reste tout de même verbeux. Certaine caractéristiques comme l’insensibilité à la casse font un peu penser à un langage d’un autre âge. Cependant, l’héritage du langage Ada fait de VHDL un langage très strict et déterministe de part sa conception contrairement au <a href="https://insights.sigasi.com/opinion/jan/verilogs-major-flaw/">Verilog</a>.</p> <p>Le typage fort peut-être considéré à première vu comme un défaut ralentissant l’écriture du code. Mais il n’en est rien, après avoir souffert de « compiler » votre porte-gramme pour la simulation, vous aurez l’agréable surprise de voir votre système fonctionner parfaitement sur le FPGA du (presque) premier coup.</p> <p>Le vocabulaire VHDL est très vaste et on se contente en général des structures de code connue dont on sait qu’elles « synthétiseront » correctement, ce qui donne une impression de ne jamais pouvoir atteindre la maîtrise du langage.</p> <p>Il y a quelques années je m’étais <a href="//linuxfr.org/users/martoni/journaux/le-vhdl-prend-il-l-eau">posé la question</a> de la popularité du VHDL par rapport au Verilog. En effet, même si le VHDL est presque aussi bien supporté que le Verilog par les outils des constructeurs, ça n’était pas le cas des logiciels libres. C’est encore largement le cas aujourd’hui, même certain logiciels non libre supportent en priorité le Verilog. Le constructeur Gowin par exemple ne permettait que la synthèse Verilog avec son outil maison. Il fallait installer le logiciel tier <a href="https://www.synopsys.com/implementation-and-signoff/fpga-based-design/synplify-pro.html">synplify</a> de synopsis pour pouvoir accéder à la synthèse VHDL.</p> <p>Cette extension de ghdl pour Yosys change la donne. Car, comme nous l’avons vu, il est possible de l’utiliser pour convertir son projet en Verilog et avoir accès à tous l’écosystème libre Verilog. Il est également possible de faire de la <a href="http://pepijndevos.nl/2019/08/15/open-source-formal-verification-in-vhdl.html">vérification formelle pour le VHDL</a>. </p> <p>Avoir la compétence VHDL dans son CV est une assez bonne idée car c’est souvent par ce mot que l’on résume le développement ASIC/FPGA/SoC. En Europe, le VHDL est très apprécié de l’industrie et particulièrement de l’industrie de défense.</p> <p>Mais si c’est juste pour mesurer le tempo, ce n’est peut-être pas la voie la plus rapide et la plus simple ;)</p> </div><div><a href="https://linuxfr.org/news/portage-de-taptempo-en-vhdl.epub">Télécharger ce contenu au format EPUB</a></div> <p> <strong>Commentaires :</strong> <a href="//linuxfr.org/nodes/122462/comments.atom">voir le flux Atom</a> <a href="https://linuxfr.org/news/portage-de-taptempo-en-vhdl#comments">ouvrir dans le navigateur</a> </p>