{"id":677,"date":"2018-04-14T16:20:07","date_gmt":"2018-04-14T15:20:07","guid":{"rendered":"http:\/\/www.fabienm.eu\/flf\/?p=677"},"modified":"2018-04-14T16:20:07","modified_gmt":"2018-04-14T15:20:07","slug":"un-composant-electronique-taptempo-avec-chisel3","status":"publish","type":"post","link":"http:\/\/www.fabienm.eu\/flf\/un-composant-electronique-taptempo-avec-chisel3\/","title":{"rendered":"Un composant \u00e9lectronique TapTempo avec Chisel3"},"content":{"rendered":"<p>Le <a href=\"https:\/\/duckduckgo.com\/?q=TapTempo+site%3Alinuxfr.org&amp;ia=web\">\u00abd\u00e9fi TapTempo\u00bb<\/a> a \u00e9t\u00e9 lanc\u00e9 sur <a href=\"https:\/\/linuxfr.org\/users\/mzf\/journaux\/un-tap-tempo-en-ligne-de-commande\">LinuxFr<\/a> il y a quelques semaines. L&rsquo;objectif est de r\u00e9aliser la mesure du tempo de l&rsquo;appui sur une touche et de l&rsquo;afficher simplement dans la console le r\u00e9sultat. La mesure du tempo s&rsquo;effectue par d\u00e9faut sur 5 appuis cons\u00e9cutif et affiche une moyenne en <a href=\"https:\/\/fr.wikipedia.org\/wiki\/Battement_par_minute\">bpm<\/a> (Beats Per Minute). L&rsquo;id\u00e9e est de r\u00e9aliser la fonction dans divers langages informatiques pour que chacun puisse promouvoir son langage favoris. Beaucoup de langages ont \u00e9t\u00e9 repr\u00e9sent\u00e9 jusqu&rsquo;\u00e0 pr\u00e9sent, mais aucun langages de description mat\u00e9riel n&rsquo;avait encore \u00e9t\u00e9 propos\u00e9.<\/p>\n<p>Pour palier ce gros manquement dans les langages repr\u00e9sent\u00e9 je vous propose ici de r\u00e9aliser TapTempo en Chisel (version 3).<\/p>\n<p><strong>Architecture g\u00e9n\u00e9rale<br \/>\n<\/strong><\/p>\n<p>L&rsquo;id\u00e9e ici n&rsquo;est donc plus d&rsquo;\u00e9crire un programme pour calculer le tempo mais de d\u00e9crire l&rsquo;architecture d&rsquo;un composant mat\u00e9riel permettant de r\u00e9aliser la fonction. Le mat\u00e9riel vis\u00e9 sera un FPGA, nous laissons de cot\u00e9 le d\u00e9veloppement sur ASIC. M\u00eame si une fois termin\u00e9 il ne devrait pas y avoir de probl\u00e8me pour \u00eatre port\u00e9 sur un ASIC, si quelqu&rsquo;un a suffisamment d&rsquo;argent pour le claquer dans ce genre d&rsquo;\u00e2nerie \ud83d\ude09<\/p>\n<p>Le bloc fonctionnel de notre composant sera donc constitu\u00e9 d&rsquo;une entr\u00e9e <em>button<\/em> recevant le signal de l&rsquo;appui sur un bouton permettant de faire le tempo. Dans un premier temps nous laisserons de cot\u00e9 les probl\u00e8mes de m\u00e9tastabilit\u00e9 ainsi que de rebond. L&rsquo;impl\u00e9mentation r\u00e9el dans un FPGA n\u00e9cessitera obligatoirement l&rsquo;ajout d&rsquo;un \u00e9tage de synchronisation du signal d&rsquo;entr\u00e9e avec l&rsquo;horloge ainsi que d&rsquo;un bloc \u00abanti-rebond\u00bb, aucun bouton r\u00e9el n&rsquo;\u00e9tant capable de faire un signal vraiment propre.<\/p>\n<p><a href=\"http:\/\/www.fabienm.eu\/flf\/wp-content\/uploads\/2018\/03\/general-1.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-689 size-full\" src=\"http:\/\/www.fabienm.eu\/flf\/wp-content\/uploads\/2018\/03\/general-1.jpg\" alt=\"\" width=\"704\" height=\"192\" srcset=\"http:\/\/www.fabienm.eu\/flf\/wp-content\/uploads\/2018\/03\/general-1.jpg 704w, http:\/\/www.fabienm.eu\/flf\/wp-content\/uploads\/2018\/03\/general-1-300x82.jpg 300w\" sizes=\"auto, (max-width: 704px) 100vw, 704px\" \/><\/a><\/p>\n<p>La sortie du bloc sera constitu\u00e9 d&rsquo;un entier non sign\u00e9 bpm dont nous allons discuter la taille ci-dessous.<\/p>\n<p>Et comme nous somme dans un FPGA il est indispensable de concevoir notre fonction synchrone d&rsquo;une horloge, et souhaitable d&rsquo;avoir un reset g\u00e9n\u00e9ral.<\/p>\n<p><strong>Structure interne<\/strong><\/p>\n<p>La structure interne de TapTempo est donn\u00e9e ci-dessous:<\/p>\n<p><a href=\"http:\/\/www.fabienm.eu\/flf\/wp-content\/uploads\/2018\/03\/Struct-2.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-688 size-full\" src=\"http:\/\/www.fabienm.eu\/flf\/wp-content\/uploads\/2018\/03\/Struct-2.jpg\" alt=\"\" width=\"1408\" height=\"512\" srcset=\"http:\/\/www.fabienm.eu\/flf\/wp-content\/uploads\/2018\/03\/Struct-2.jpg 1408w, http:\/\/www.fabienm.eu\/flf\/wp-content\/uploads\/2018\/03\/Struct-2-300x109.jpg 300w, http:\/\/www.fabienm.eu\/flf\/wp-content\/uploads\/2018\/03\/Struct-2-768x279.jpg 768w, http:\/\/www.fabienm.eu\/flf\/wp-content\/uploads\/2018\/03\/Struct-2-1024x372.jpg 1024w\" sizes=\"auto, (max-width: 1408px) 100vw, 1408px\" \/><\/a><\/p>\n<p>L&rsquo;id\u00e9e est de compter des ticks g\u00e9n\u00e9r\u00e9s par timepulse au moyen du compteur count. Quand un appui sur le bouton est d\u00e9tect\u00e9, le compteur se remet \u00e0 z\u00e9ro et la valeur est enregistr\u00e9e dans le tableau countx. \u00c0 chaque coup d&rsquo;horloge l&rsquo;addition des 4 valeurs count est r\u00e9alis\u00e9e puis on divise par 4. La division par 4 est r\u00e9alisable dans un FPGA au moyen d&rsquo;un simple d\u00e9calage \u00e0 droite de 2. Vient la partie la plus compliqu\u00e9 : se servir de cette p\u00e9riode moyenne pour diviser TMINUTE et obtenir la valeur du tempo en bmp.<\/p>\n<p><strong>Un peu de dimensionnement<\/strong><\/p>\n<p>On ne fonctionne pas dans un FPGA comme on fonctionne avec un pc, quand on fait des op\u00e9rations sur des nombres il faut les dimensionner. Et il est fortement recommander d&rsquo;utiliser des entiers, car le calcul flottant n\u00e9cessite tout de suite une quantit\u00e9 de ressources ph\u00e9nom\u00e9nale.<\/p>\n<p>Notre objectif est de mesurer une cadence musicale en bpm que l&rsquo;on puisse \u00abtaper \u00e0 la main\u00bb, si l&rsquo;on regarde l&rsquo;article wikipedia consacr\u00e9 au Tempo, on se rend vite compte qu&rsquo;attendre les 200bpm est d\u00e9j\u00e0 pas mal. Disons que pour prendre une tr\u00e8s large marge nous mettons la marge sup\u00e9rieur \u00e0 270bpm. Nous aurons donc en sortie une variable enti\u00e8re sur 9bits ( int(ln2(270))+1).<\/p>\n<p>\u00c0 270bpm, le temps entre deux tempos est de ~222ms ce qui nous donnerais une fr\u00e9quence de pulse de 4.5Hz. Cependant, si nous voulons une pr\u00e9cision de 1bpm il va falloir augmenter cette fr\u00e9quence , pour avoir un chiffre rond nous prendrons un temps de 1ms, soit une fr\u00e9quence de 1kHz. Ce qui est un peu juste pour 270bpm, mais conviendra \u00e0 la d\u00e9monstration.<\/p>\n<p><strong>D\u00e9composons le code<\/strong><\/p>\n<p>Le code se trouve sur le d\u00e9p\u00f4t github <a href=\"https:\/\/github.com\/Martoni\/TapTempoChisel\">suivant<\/a>.\u00a0 Nous allons d\u00e9crire la description du module \u00e0 proprement parl\u00e9 qui se trouve <a href=\"https:\/\/github.com\/Martoni\/TapTempoChisel\/blob\/master\/src\/main\/scala\/taptempo\/TapTempo.scala\">ici<\/a>.<\/p>\n<p>Dans l&rsquo;ent\u00eate du module nous allons retrouver le port d&rsquo;entr\u00e9e button ainsi que le port de sortie bpm. Point d&rsquo;horloge ni de reset ici puisqu&rsquo;en Chisel ces signaux sont implicite.<\/p>\n<pre>\/\/ default clock 100Mhz -&gt; T = 10ns\r\nclass TapTempo(tclk_ns: Int, bpm_max: Int = 270) extends Module {\r\nval io = IO(new Bundle {\r\n\/\/ val bpm = Output(UInt(8.W))\r\nval bpm = Output(UInt(9.W))\r\nval button = Input(Bool())\r\n})\r\n<\/pre>\n<p>Quelques constantes qui nous servirons ensuite :<\/p>\n<pre>  \/* Constant parameters *\/\r\n  val MINUTE_NS = 60*1000*1000*1000L\r\n  val PULSE_NS = 1000*1000\r\n  val TCLK_NS = tclk_ns\r\n  val BPM_MAX = bpm_max\r\n\r\n  \/* usefull function *\/\r\n  def risingedge(x: Bool) = x &amp;&amp; !RegNext(x)\r\n<\/pre>\n<p>Pour notre g\u00e9n\u00e9rateur de pulses il suffit d&rsquo;utiliser une classe pr\u00e9sente dans la biblioth\u00e8que \u00abutil\u00bb de chisel : Counter. Qui comme son nom l&rsquo;indique &#8230; compte !<\/p>\n<pre>import chisel3.util.Counter\r\n[...]\r\n  val (pulsecount, timepulse) = Counter(true.B, PULSE_NS\/tclk_ns)\r\n[...]\r\n<\/pre>\n<p>Ce compteur prend en param\u00e8tre un signal de comptage (ici true.B -&gt; compte tout le temps) ainsi que la valeur max \u00e0 atteindre.<br \/>\nL&rsquo;instanciation de l&rsquo;objet retourne un compteur ainsi qu&rsquo;un signal qui passe \u00e0 &lsquo;1&rsquo; quand le compteur se remet \u00e0 zero (quand il d\u00e9passe la valeur max).<\/p>\n<p>Ce signal timepulse sera ensuite utilis\u00e9 par un deuxi\u00e8me compteur 16 bits tp_count que nous allons \u00e9crire \u00ab\u00e0 la main\u00bb cette fois.<br \/>\nOn d\u00e9fini d&rsquo;abord le registre de 16bits que l&rsquo;ont initialise \u00e0 0 (0.asUInt(16.W))<\/p>\n<pre>  val tp_count = RegInit(0.asUInt(16.W))\r\n<\/pre>\n<p>Puis on \u00e9crit le code d\u00e9crivant le \u00abcomptage\u00bb:<\/p>\n<pre>  when(timepulse) {\r\n    tp_count := tp_count + 1.U\r\n  }\r\n  when(risingedge(io.button)){\r\n    \/\/ enregistrement de la valeur du compteur.\r\n    countx(count_mux) := tp_count\r\n    count_mux := Mux(count_mux === 3.U, 0.U, count_mux + 1.U)\r\n    \/\/ remise \u00e0 z\u00e9ro du compteur\r\n    tp_count := 0.U\r\n  }\r\n<\/pre>\n<p>Ce deuxi\u00e8me compteur compte les pulse \u00abtimepulse\u00bb et se remet \u00e0 0 lorsqu&rsquo;un front montant est d\u00e9tect\u00e9 sur le bouton (quand on appuis sur le bouton).<\/p>\n<p>Pour stocker les 4 valeurs permettant de r\u00e9aliser une valeurs nous d\u00e9clarons un vecteur de registres de 19 bits (pour g\u00e9rer les retenues quand nous ferons l&rsquo;addition):<\/p>\n<pre>  val countx = RegInit(Vec(Seq.fill(4)(0.asUInt(19.W))))\r\n<\/pre>\n<p>Ainsi que le \u00abpointeur\u00bb :<\/p>\n<pre>  val count_mux = RegInit(0.asUInt(2.W))\r\n<\/pre>\n<p>La gestion de l&rsquo;incr\u00e9mentation du pointeur ainsi que de l&rsquo;enregistrement du compteur se trouve dans le code du \u00abcompteur de pulse\u00bb que nous avons vu plus haut:<\/p>\n<pre>    \/\/ enregistrement de la valeur du compteur.\r\n    countx(count_mux) := tp_count\r\n    count_mux := Mux(count_mux === 3.U, 0.U, count_mux + 1.U)\r\n<\/pre>\n<p>Pour faire la somme rien de plus simple, il suffit de faire &lsquo;+&rsquo; :<\/p>\n<pre>  val sum = Wire(UInt(19.W))\r\n[...]\r\n  sum := countx(0) + countx(1) + countx(2) + countx(3)\r\n<\/pre>\n<p>Et la division par 4 se r\u00e9sume \u00e0 un d\u00e9calage:<\/p>\n<pre>  val sum_by_4 = sum(18, 2)\r\n<\/pre>\n<p>Et nous arrivons \u00e0 la partie la plus compliqu\u00e9e du design : <strong>diviser. <\/strong>Diviser est quelque chose de tr\u00e8s compliqu\u00e9 dans un FPGA, on peut r\u00e9aliser un design permettant d&rsquo;effectuer une divisions en plusieurs cycles d&rsquo;horloge, mais c&rsquo;est tout de suite une tr\u00e8s grosse usine \u00e0 gaz.<strong><br \/>\n<\/strong><\/p>\n<p>Pour la division permettant de faire la moyenne des \u00e9chantillons nous nous en \u00e9tions sortie en faisant une division par une puissance de 2, qui se r\u00e9sume de fait \u00e0 un simple d\u00e9calage. Mais cette fois on ne pourra pas s&rsquo;en sortir avec ce genre de pirouette. Car notre division \u00e0 r\u00e9aliser est la suivante :<\/p>\n<pre>   Nombre de pulse dans une minute\r\n-------------------------------------  = valeur en bpm\r\nmoyenne des pulses mesur\u00e9 (sum_by_4)\r\n<\/pre>\n<p>Pour nous en sortir la premi\u00e8re id\u00e9e serait de faire une table de valeurs pr\u00e9-calcul\u00e9e pour chaque valeurs de\u00a0 <em>sum_by_4. <\/em>Ce qui se fait tr\u00e8s simplement en scala avec une s\u00e9quence :<em><br \/>\n<\/em><\/p>\n<pre>val x = Seq.tabulate(pow(2,16).toInt-1)(n =&gt; ((MINUTE_NS\/PULSE_NS)\/(n+1)).U)\r\n<\/pre>\n<p>Sauf que le nombre de valeurs est particuli\u00e8rement grand (2^16) et qu&rsquo;on pourrait certainement factoriser un peut tout \u00e7a.<\/p>\n<p>Pour r\u00e9duire la taille ne notre tableau il faut prendre le probl\u00e8me \u00e0 l&rsquo;envers: combien de valeurs possible puis-je avoir en sortie ?<\/p>\n<p>La r\u00e9ponse est simplement 269 puisque je peux aller de 1 \u00e0 270bpm. Nous allons donc r\u00e9aliser un vecteur de 270 valeurs contenant la valeurs minimum du compteurs permettant d&rsquo;atteindre notre r\u00e9sultat. Et nous mettrons ces valeurs dans 270 registres.<\/p>\n<pre>  val bpm_calc = RegInit(Vec(x(0) +: Seq.tabulate(bpm_max)(n =&gt; x(n))))\r\n<\/pre>\n<p>Pour obtenir la valeur finale il faut ensuite g\u00e9n\u00e9rer 270 in\u00e9quations ayant pour r\u00e9sultat un vecteur de 270 bits :<\/p>\n<pre>  for(i &lt;- 0 to (bpm_max-1)) {\r\n    bpm_ineq(i) := Mux(sum_by_4 &lt; bpm_calc(i), 1.U, 0.U)\r\n  }\r\n<\/pre>\n<p>Le r\u00e9sultat correspondra ensuite \u00e0 l&rsquo;index du dernier bit \u00e0 1 dans ce vecteur.<\/p>\n<p>Pour r\u00e9cup\u00e9rer cet index, une fonction tr\u00e8s utile est fournie dans la biblioth\u00e8que \u00abutil\u00bb de chisel : le <a href=\"https:\/\/chisel.eecs.berkeley.edu\/api\/index.html#chisel3.util.PriorityEncoder$\">PriorityEncoder<\/a>. Qui permet d&rsquo;obtenir l&rsquo;index du plus petit bit \u00e0 1 dans un vecteur&#8230; sauf que nous on veut le plus grand !<\/p>\n<p>Mais ce n&rsquo;est pas tr\u00e8s grave, il suffit de retourner le vecteur avec <a href=\"https:\/\/chisel.eecs.berkeley.edu\/api\/index.html#chisel3.util.Reverse$\">Reverse<\/a> puis de faire une soustraction pour avoir le r\u00e9sultat :<\/p>\n<pre>  io.bpm := bpm_max.U - PriorityEncoder(Reverse(bpm_ineq.asUInt()))\r\n<\/pre>\n<p><strong>Simulation<\/strong><\/p>\n<p>Le test permettant de simuler le design se trouve dans le fichier TapTempoUnitTest.scala<\/p>\n<p>Il peut \u00eatre lanc\u00e9 avec la commande sbt suivante :<\/p>\n<pre>sbt 'test:runMain taptempo.TapTempoMain --backend-name verilator'\r\n<\/pre>\n<p>Et les traces VCD g\u00e9n\u00e9r\u00e9es sont disponible dans le r\u00e9pertoire .\/test_run_dir\/taptempo.TapTempoMain962904038\/TapTempo.vcd<\/p>\n<p>Visualisable simplement avec <a href=\"http:\/\/gtkwave.sourceforge.net\/\">gtkwave<\/a>.<\/p>\n<pre>gtkwave .\/test_run_dir\/taptempo.TapTempoMain962904038\/TapTempo.vcd\r\n<\/pre>\n<figure id=\"attachment_704\" aria-describedby=\"caption-attachment-704\" style=\"width: 1359px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/www.fabienm.eu\/flf\/wp-content\/uploads\/2018\/04\/gtkwave_taptempo.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-704\" src=\"http:\/\/www.fabienm.eu\/flf\/wp-content\/uploads\/2018\/04\/gtkwave_taptempo.png\" alt=\"\" width=\"1359\" height=\"446\" srcset=\"http:\/\/www.fabienm.eu\/flf\/wp-content\/uploads\/2018\/04\/gtkwave_taptempo.png 1359w, http:\/\/www.fabienm.eu\/flf\/wp-content\/uploads\/2018\/04\/gtkwave_taptempo-300x98.png 300w, http:\/\/www.fabienm.eu\/flf\/wp-content\/uploads\/2018\/04\/gtkwave_taptempo-768x252.png 768w, http:\/\/www.fabienm.eu\/flf\/wp-content\/uploads\/2018\/04\/gtkwave_taptempo-1024x336.png 1024w\" sizes=\"auto, (max-width: 1359px) 100vw, 1359px\" \/><\/a><figcaption id=\"caption-attachment-704\" class=\"wp-caption-text\">Trace vcd de la simulation TapTempo<\/figcaption><\/figure>\n<p>\u00c0 suivre<\/p>\n<p>Pour \u00eatre vraiment complet il faudrait ajouter la gestion des rebonds ainsi que la synchronisation du signal d&rsquo;entr\u00e9e puis tester la synth\u00e8se. Mais ce sont de nouvelles aventure qui continuerons peut-\u00eatre dans un prochaine \u00e9pisode \ud83d\ude42 .<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Le \u00abd\u00e9fi TapTempo\u00bb a \u00e9t\u00e9 lanc\u00e9 sur LinuxFr il y a quelques semaines. L&rsquo;objectif est de r\u00e9aliser la mesure du tempo de l&rsquo;appui sur une touche et de l&rsquo;afficher simplement dans la console le r\u00e9sultat. La mesure du tempo s&rsquo;effectue par d\u00e9faut sur 5 appuis cons\u00e9cutif et affiche une moyenne en bpm (Beats Per Minute). &hellip; <a href=\"http:\/\/www.fabienm.eu\/flf\/un-composant-electronique-taptempo-avec-chisel3\/\" class=\"more-link\">Continuer la lecture de <span class=\"screen-reader-text\">Un composant \u00e9lectronique TapTempo avec Chisel3<\/span> <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_uag_custom_page_level_css":"","footnotes":""},"categories":[2,4,3,27],"tags":[],"class_list":["post-677","post","type-post","status-publish","format-standard","hentry","category-chisel","category-chisel-langages","category-langages","category-verilator"],"uagb_featured_image_src":{"full":false,"thumbnail":false,"medium":false,"medium_large":false,"large":false,"1536x1536":false,"2048x2048":false,"post-thumbnail":false},"uagb_author_info":{"display_name":"Fabien Marteau","author_link":"http:\/\/www.fabienm.eu\/flf\/author\/martoni\/"},"uagb_comment_info":1,"uagb_excerpt":"Le \u00abd\u00e9fi TapTempo\u00bb a \u00e9t\u00e9 lanc\u00e9 sur LinuxFr il y a quelques semaines. L&rsquo;objectif est de r\u00e9aliser la mesure du tempo de l&rsquo;appui sur une touche et de l&rsquo;afficher simplement dans la console le r\u00e9sultat. La mesure du tempo s&rsquo;effectue par d\u00e9faut sur 5 appuis cons\u00e9cutif et affiche une moyenne en bpm (Beats Per Minute).\u2026","_links":{"self":[{"href":"http:\/\/www.fabienm.eu\/flf\/wp-json\/wp\/v2\/posts\/677","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/www.fabienm.eu\/flf\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.fabienm.eu\/flf\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.fabienm.eu\/flf\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"http:\/\/www.fabienm.eu\/flf\/wp-json\/wp\/v2\/comments?post=677"}],"version-history":[{"count":16,"href":"http:\/\/www.fabienm.eu\/flf\/wp-json\/wp\/v2\/posts\/677\/revisions"}],"predecessor-version":[{"id":705,"href":"http:\/\/www.fabienm.eu\/flf\/wp-json\/wp\/v2\/posts\/677\/revisions\/705"}],"wp:attachment":[{"href":"http:\/\/www.fabienm.eu\/flf\/wp-json\/wp\/v2\/media?parent=677"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.fabienm.eu\/flf\/wp-json\/wp\/v2\/categories?post=677"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.fabienm.eu\/flf\/wp-json\/wp\/v2\/tags?post=677"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}