Archives par mot-clé : icarus

Un ASIC conçu intégralement avec des logiciels libres

Les FPGA sont très liés aux ASIC. En effet, la plupart des outils utilisés en FPGA pour la synthèse HDL, la preuve formel, le placement routage ou l’analyse des timings sont les même que ceux à destination des ASIC. Seuls les librairies et les configurations changent. La grosse différence (de taille) avec les FPGA c’est que l’ASIC n’est pas reconfigurable, et les «frais d’initialisations» sont très élevés. Les délais de productions sont très long également (on parle en trimestre voir en semestre de délais).

Avec de telles contraintes, on comprend pourquoi les développeurs ne se mouillent pas trop avec des logiciels exotiques et restent sur ceux qu’ils connaissent. Vu les tarif de production, le coût des licences des logiciels est assez négligeable. Pourquoi «grenouiller» avec des outils open-source dans ce cas ?

Vue «silicium» du Raven, un microcontrôleur Risc-V conçu avec des outils open-sources

Toutes ces contraintes n’ont pas découragé Tim Edwards de se lancer dans la conception et la fabrication d’un microcontrôleurs intégralement avec des outils open-sources.

Synoptique du Raven avec ses différents périphériques

C’est comme cela qu’est né le Raven, un microcontrôleur basé sur un cœur picoRV32 (conçu par Clifford Wolf) et réalisé principalement avec les outils qflow d’opencircuitdesign.com :

Grande surprise quand on se plonge dans ces outils open-source : Beaucoup sont très vieux. Les pages web de ses outils sont encore codé en web95 avec des frames et autre fonds hideux datant de l’époque frontpage.

Pourtant à y regarder de plus prêt, ces outils semblent toujours activement maintenus.

Mais alors pourquoi aucun fondeur FPGA ne les proposent dans leurs IDE ?

Une première série du microcontrôleur gravé en 180nm a été produite en mai 2018. Le composant est désormais fonctionnel avec les caractéristiques suivantes:

  • Cadencé à 100 MHz
  • 16 GPIO
  • 2 ADCs
  • 1 DAC
  • 1 Comparateur
  • Alarme de température
  • Oscillateur RC de 100 kHz
  • Fonction configurables pour les sorties GPIO
  • Interruptions configurable sur les entrées GPIO

Il n’est pas possible d’acheter le composant pour se faire un montage chez soit pour le moment. Par contre l’«IP» est disponible dans la bibliothèque du fondeur efabless et peut être utilisé comme base pour réaliser son propre composant selon les besoins.

Une nouvelle release d’icarus verilog est désormais disponible sur le ftp officiel.

Pour l’installer sous linux, rien de plus simple :

$ wget ftp://icarus.com/pub/eda/verilog/v10/verilog-10.0.tar.gz
$ tar -zxvf verilog-10.0.tar.gz
$ cd verilog-10.0/
$ ./configure
$ make
$ sudo make install

Vérifier la bonne installation avec :

$ iverilog -v
Icarus Verilog version 10.0 (stable) (v10_0)

Copyright 1998-2015 Stephen Williams

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License along
  with this program; if not, write to the Free Software Foundation, Inc.,
  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

iverilog: no source files.

Usage: iverilog [-ESvV] [-B base] [-c cmdfile|-f cmdfile]
                [-g1995|-g2001|-g2005|-g2005-sv|-g2009|-g2012] [-g]
                [-D macro[=defn]] [-I includedir]
                [-M [mode=]depfile] [-m module]
                [-N file] [-o filename] [-p flag=value]
                [-s topmodule] [-t target] [-T min|typ|max]
                [-W class] [-y dir] [-Y suf] source_file(s)

See the man page for details.

Icarus vs Verilator

Plusieurs solutions de simulations s’offrent à nous quand on développe un composant en Verilog. On peut écrire le testbench en Verilog, de manière à être compatible avec la plupart des simulateurs du marché. Dans ce cas, le simulateur libre le plus célèbre est Icarus.

Mais la solution du «tout Verilog» est relativement lente en temps de simulation, et l’utilisation du langage Verilog est restrictive,  en effet il n’est pas facile de s’approprier toutes les subtilités du langage.

C’est là que la solution de Verilator devient très intéressante. Verilator permet de convertir un modèle de composant écrit en Verilog synthétisable en un modèle C++. De cette manière, écrire le code du testbench revient à instancier notre composant dans un main() en C++ et à décrire nos tests avec toute la liberté qu’offre ce langage.

Mieux encore, il est possible d’écrire notre testbench en SystemC, et de profiter ainsi de cette librairie conçue pour la simulation de circuit numérique.

L’idée va être ici de mesurer le temps effectif de simulation de chacune de ses deux solutions. On se servira pour cela du composant d’antirebond du projet «blinking led project» (blp) disponible sur github.

Le composant synthétisable

Le composant button_deb commute sa sortie button_valid à chaque front montant du signal d’entrée button_in. Pour que le fonctionnement soit un peu plus complexe qu’une simple commutation, et surtout pour bien coller au fonctionnement réel, le composant est muni d’un antirebond de 20 ms (par défaut). Quand le premier front survient, un compteur est déclenché et aucun autre front n’est pris en compte pendant 20 ms.

Voici ce que cela donne avec gtkwave:

blp_button_deb
Chronogramme du testbench avec GTKwave

Le testbench verilog (Icarus)

Tout comme en VHDL, un testbench en Verilog se présente sous la forme d’un module sans entrée/sortie.

La première chose à définir est le temps, avec la directive `timescale:

`timescale 1ns/100ps;

Le premier chiffre indique le pas de simulation, cela correspondra au temps d’attente que l’on retrouvera tout au long du code avec l’«instruction» ‘#’.  Le deuxième nombre indique la précision maximum.

Par exemple, pour simuler l’horloge à 95Mhz on écrira :

/* Make a regular pulsing clock. */
always
    #5.263158 clk = !clk;

Mais comme la précision indiquée en début de code est de 100ps, seule le 5.2 sera pris en compte pour la simulation, ce qui dans notre cas est tout à fait suffisant.

La simulation de l’appui sur le bouton avec rebond se fait ensuite dans un process que l’on appel souvent «stimulis» par convention :

/* Stimulis */
initial begin
    $display("begin stimulis");
    $dumpfile("simu/button_deb_tb.vcd");
    $dumpvars(1, clk, rst, button_in, button_valid, button);
    $monitor("At time %t, value = %h (%0d)",
        $time, button_in, button_in);
[...]
end

Le Verilog fourni tout un tas de primitive permettant de simplifier le debuggage. Notamment $display() et $monitor(), pour afficher du texte pendant la simulation. $display() ne fait qu’afficher du texte au moment où la fonction est appelée, alors que monitor va afficher le texte à chaque changement d’état de ses paramètres.

Les functions $dump* permettent de définir le format de fichiers des traces ainsi que les signaux a dumper. Dans ce cas précis on choisira le format vcd qui est un format non compressé, de manière à améliorer la comparaison avec verilator qui lui ne sait faire que du vcd. Mais cela génère vite de très gros fichiers, il sera préférable d’utiliser le format compressé lxt2, fst ou fsdb pour des simulations plus longues.

Pour factoriser un peu de code d’attente on décrit des tâches d’attentes wait_ms et wait_us :


/* some usefull functions */
task wait_us;
    input integer another_time;
    begin
        repeat(another_time) begin
            # 1_000;
        end
    end
endtask

task wait_ms;
[...]

Tâches qui seront appelées lors de la simulation des rebonds :

[...]
        button_in = 1;
        wait_us(`DEBOUNCE_PER_MS * 8);
        button_in = 0;
        wait_us(`DEBOUNCE_PER_MS * 8);
        button_in = 1;
        wait_us(`DEBOUNCE_PER_MS * 10);
        button_in = 0;
        wait_us(`DEBOUNCE_PER_MS * 10);
        button_in = 1;

        wait_ms(`DEBOUNCE_PER_MS * 2);
[...]

Pour lancer notre testbench avec Icarus, la première chose à faire est de compiler le code au moyen de la commande suivante :

iverilog -o simu/button_deb test/test_button_deb.v src/button_deb.v

Icarus va ainsi créer un binaire exécutable nommé simu/button_deb que l’on lancera avec les arguments de dumps:

vvp simu/button_deb -lvcd

Le fichier de traces (VCD) généré fait une taille vénérable de 752Mo et peut être visualisé avec gtkwave en l’indiquant simplement en paramètre de la commande :

gtkwave simu/button_deb_tb.vcd

Sur un Lenovo T430, la simulation prend 1 minute et 17 secondes, ce qui est tout de même relativement lent pour une simulation d’appui sur un boutton 😉

Voyons maintenant ce que nous donne Verilator.

Le testbench C++

Un testbench Verilator se présente sous la forme d’un main() C++. Dans le main() du testbench nous instancierons l’objet correspondant au model verilog transformé par verilator.
Pour cela nous devons donc convertir notre bouton_deb.v en C++ au moyen de la commande Verilator suivante :

verilator -Wall -cc src/button_deb.v --trace --exe test/test_button_deb.cpp

Cette commande va nous créer un projet avec le code source du modèle ainsi que le makefile pour compiler. Il suffira donc de se rendre dans le répertoire obj_dir et de faire «make» pour compiler le modèle.

Il ne nous restera plus qu’à instancier notre bouton dans notre testbench test_button_deb.cpp :

    Vbutton_deb* top = new Vbutton_deb;

Ainsi que l’objet tfp pour les dump VCD:

    VerilatedVcdC* tfp = new VerilatedVcdC;

Pour simuler notre objet «top» il faut assigner des valeurs aux signaux d’entrées :

    top->rst = 1;
    top->button_in = 0;
    top->clk = 0;

Puis évaluer les sorties avec la méthode eval() :

        top->eval();

À chaque front d’horloge il faut donc changer la valeur de clk et évaluer :

        top->clk = !top->clk;
        top->eval();

Dans tout ce que nous venons de voir le temps n’intervient pas. En réalité, le temps n’est tout simplement pas géré dans les modèles Verilator, le modèle ne fait qu’évaluer les sorties en fonction des entrées, c’est à nous de gérer le temps comme nous le souhaitons.

Nous allons donc gérer le temps au moment du dump des signaux en lui indiquant le temps en argument:

        tfp->dump(10);

Pour éviter d’avoir à taper tout ça à chaque fois on pourra créer une fonction «time_pass» qui fait passer le temps, avec un compteur global pour incrémenter le temps:

#define BASE_TIME_NS ((1000*1000)/(CLK_FREQ*2))
int base_time = 0;

/* the time is passing */
void time_pass(VerilatedVcdC *tfp, Vbutton_deb *top) {
        top->clk = !top->clk;
        tfp->dump(base_time*BASE_TIME_NS);
        top->eval();
        base_time++;
}

À l’image du testbench verilog, on pourra aussi créer des fonctions d’attente wait_ms et wait_us:

/* wait for us */
void wait_us(VerilatedVcdC *tfp, Vbutton_deb *top, int timeus) {
    int wait_time = 0;
    while((wait_time * BASE_TIME_NS) < (timeus * 1000)) {
        wait_time++;
        time_pass(tfp, top);
    }
}

/* wait for ms */
void wait_ms(VerilatedVcdC *tfp, Vbutton_deb *top, int timems) {
    int wait_time = 0;
    while((wait_time * BASE_TIME_NS) < (timems * 1000 * 1000)) {
        wait_time++;
        time_pass(tfp, top);
    }
}

Le code simulant les rebonds du bouton ressemble ainsi à s’y méprendre au code Verilog:

[...]
        top->button_in = 1;
        wait_us(tfp, top, DEBOUNCE_PER_MS);
        top->button_in = 0;
        wait_us(tfp, top, DEBOUNCE_PER_MS);
        top->button_in = 1;
        wait_us(tfp, top, DEBOUNCE_PER_MS * 5);
        top->button_in = 0;
        wait_us(tfp, top, DEBOUNCE_PER_MS * 5);
        top->button_in = 1;
        wait_us(tfp, top, DEBOUNCE_PER_MS * 8);
        top->button_in = 0;
        wait_us(tfp, top, DEBOUNCE_PER_MS * 8);
        top->button_in = 1;
        wait_us(tfp, top, DEBOUNCE_PER_MS * 10);
        top->button_in = 0;
        wait_us(tfp, top, DEBOUNCE_PER_MS * 10);
        top->button_in = 1;

        wait_ms(tfp, top, DEBOUNCE_PER_MS * 2);
[...]

Pour lancer la simulation on va d’abord compiler le tout :

make -C obj_dir/ -j -f Vbutton_deb.mk Vbutton_deb

Puis le lancer simplement comme un vulgaire binaire exécutable :

./Vbutton_deb

La simulation crée un fichier VCD de 654Mo que nous pouvons visualiser avec gtkwave.
Sur le même Lenovo T430 la simulation ne dure cette fois que 17 secondes, ce qui est très nettement plus rapide qu’Icarus !

Mieux, si on optimise le code à la compilation avec l’option -O3 :

verilator -Wall -cc src/button_deb.v --trace -O3 -noassert --exe test/test_button_deb.cpp

Le temps de simulation tombe à 3 secondes !

La rapidité d’exécution est telle que certain réussissent à faire «tourner» un soft-core avec son programme, à des fréquences allant jusqu’à la centaine de kilohertz.

Formidable !

C’est un peu l’expression que l’on a lors des premiers tests de verilator, pouvoir faire de la simulation 20 fois plus rapidement qu’Icarus semble formidable. Surtout quand cela passe par de la simplicité d’écriture du testbench en C++ ou SystemC.

Néanmoins il faut relativiser un peu notre ferveur, Verilator a encore un gros point noir: il ne gère pas le temps. Verilator n’est capable que de gérer des modèle Verilog synthétisable. Mais si ce code synthétisable inclu des primitives du constructeurs : RAM, Multiplieur, … et surtout PLL. Verilator ne sera pas capable de les simuler.

En ce qui concerne les Ram ou les multiplieurs cela ne pose pas trop de problème dans la mesure ou il est assez simple de les inférer. Mais pour les PLL cela devient bien plus compliqué, et je ne parle même pas des composants spécifique au constructeur (Bus serie, transceiver, core, …).

Ce «bug» a été répertorié il y 5 ans déjà mais pour l’instant personne ne semble s’être attelé à la tâche. D’après un des auteur de Verilator ajouter cette fonctionnalité devrait prendre quelques mois de programmation et de test. Mais cela vaudrait quand même le coup !

Alors qui s’y colle ?