URL: https://linuxfr.org/users/killruana/journaux/conception-d-un-circuit-integre-avec-qflow Title: Conception d’un circuit intégré avec Qflow Authors: jtremesay Date: 2019年11月14日T22:07:56+01:00 License: CC By-SA Tags: qflow, ic, microélectronique et ada Score: 76 Cher journal, aujourd’hui nous allons voir comment concevoir un circuit intégré avec des outils libres. Je ne vais pas faire de toi un expert en microélectronique, mais ça devrait déjà te permettre d’un peu mieux comprendre ce que je t’ai [baragouiné](https://linuxfr.org/users/killruana/journaux/k1g1-le-premier-fpga-libre) la dernière fois. # ~~Partie relou~~ Cours magistral ## RTL La première étape de la conception d’un circuit intégré consiste à en décrire le comportement avec un langage de la famille des HDL (_Hardware Description Langage_). Cette description est généralement appelé « RTL » (_Register Transfert Level_) car, historiquement, les langages utilisés décrivaient les circuits en termes de transfert de données entre registres, et les opérations logiques à effectuer dessus. Le terme est utilisé même si la description est faite avec un plus haut niveau d’abstraction. Le RTL est opposé à la _netlist_ qui est une représentation très bas niveau du circuit utilisant uniquement des portes logiques (voire directement des transistors, résistances, condensateurs et autres inductances). Pour faire un parallèle avec l’informatique, on peut dire que le RTL est le code source avant compilation et la _netlist_, le code objet. Il existe divers HDL. Les plus vieux et connus sont le [Verilog](https://fr.wikipedia.org/wiki/Verilog) et le [VHDL](https://fr.wikipedia.org/wiki/VHDL), tous les deux créés dans les années 80. Verilog a une syntaxe librement inspirée du C et plutôt simple, et est relativement permissif (→ c’est rapide de coder des trucs avec, mais c’est aussi facile de faire du caca). De son côté, VHDL est fortement influencé par Ada et en a récupéré les ~~côtés chiants~~ caractéristiques, ce qui fait la force du langage : une syntaxe très explicite et un compilateur intransigeant afin d’attraper le plus tôt possible les erreurs de conception (oui, j’ai une nette préférence pour Verilog). Parmi les outils libres, VHDL m’a l’air moins bien pris en charge que Verilog, mais je n’ai pas creusé la question plus que ça. Il existe aussi des HDL basés sur des langages de programmation tel que [SystemC](https://fr.wikipedia.org/wiki/SystemC), qui se présente sous la forme de classes et macros C++, [Chisel](https://www.chisel-lang.org/), basé sur Scala, ou [MyHDL](http://www.myhdl.org/) en Python. Voici à quoi peut ressembler un additionneur 32 bits synchronisé (un truc qui fait l’addition de nombres de 32 bits quand il voit passer un coup d’horloge) dans les différents langages mentionnés ci‐dessus : ### Verilog ```verilog module Adder(s, a, b, clk); output reg [31:0] s; input [31:0] a, b; input clk; always @(posedge clk) begin s <= a + b; end endmodule ``` ### VHDL ```VHDL library ieee; use ieee.std_logic_1164.all; entity Adder is port( s : out std_logic_vector(31 downto 0); a, b : in std_logic_vector(31 downto 0); clk : in std_logic ); end entity Adder; architecture Behavioral of Adder is begin process (clk) begin wait until clk'event and clk='1'; s <= a + b; end process; end architecture Behavioral; ``` ### SystemC ```c++ #ifndef __ADDER_H__ #define __ADDER_H__ #include SC_MODULE(Adder) { sc_out s; sc_in a, b; sc_in_clk clk; SC_CTOR(Adder) { SC_METHOD(do_add); sensitive << clk.pos(); } void do_add() { s = a + b; } }; #endif // ifndef __ADDER_H__ ``` ### Chisel ```scala import Chisel._ class Adder extends Module { val io = new Bundle { val a = Input(UInt(32.W)) val b = Input(UInt(32.W)) val s = Output(UInt(32.W)) } val reg = RegInit(32.W); reg := io.a + io.b io.s := reg } ``` ### MyHDL ```python from myhdl import * @block def Adder(s, a, b, clk): @always(clk.posedge): def process(): s.next = a + b return instances() ``` Bref, il y en a pour tous les goûts, chacun avec ses avantages et inconvénients. Aujourd’hui, nous allons surtout faire du Verilog car c’est le seul langage nativement pris en charge par Qflow. ## Implémentation Maintenant que nous avons décrit notre circuit, qu’est‐ce qu’on en fait quoi ? Déjà, on peut le simuler pour vérifier qu’il fonctionne correctement. [![Chronogramme d’un additionenur](https://i.zcraft.fr/mini_7217861573764693.png)](https://i.zcraft.fr/7217861573764693.png) `01 + 1 = 2` ? OK, c’est bon, notre circuit fonctionne. Mais, nous, ce qu’on veut, c’est pas juste un bout de code qui décrit le comportement de notre circuit. Non. Nous, on veut un vrai circuit avec lequel s’amuser, faire clignoter des diodes et tout et tout. [![Additionneur en action](https://i.zcraft.fr/mini_8148271573764733.jpeg)](https://i.zcraft.fr/8148271573764733.jpeg) Il va donc nous falloir faire l’implémentation du circuit. Nous pouvons choisir soit de cibler un [FPGA](https://fr.wikipedia.org/wiki/Circuit_logique_programmable "Field‐Programmable Gate Array — circuit logique programmable"), soit fabriquer un [ASIC](https://fr.wikipedia.org/wiki/Application-specific_integrated_circuit "Circuit intégré propre à une application") (_Application Specific Integrated Circuit_), c’est‐à‐dire de créer un véritable circuit intégré avec de touuuuuuut petits transistors gravés dans le silicium. Les deux solutions ont leurs avantages et inconvénients, que l’on peut résumer de la manière suivante : ### FPGA * avantages : * rapidité de développement (une fois le RTL écrit et testé, il est prêt à être déployé), * faible coût de développement, * solution évolutive (possibilité de reconfigurer le FPGA) ; * inconvénients : * fort coût de production (un FPGA, c’est pas donné, alors 100 000...), * moins optimisé (en termes de consommation électrique, vitesse d’exécution et surface occupée). ### ASIC * avantages : * meilleurs résultats en consommation, vitesse d’exécution et surface occupée, * faible coût unitaire si production de masse ; * inconvénients : * plus long à développer, * très fort coût initial (notamment la création du [masque lithographique](https://fr.wikipedia.org/wiki/Photolithographie) ou les licences des logiciels proprios), * pas évolutif (une fois le silicium gravé, on ne peut pas vraiment le modifier). Le sujet du jour concerne la création d’un ASIC. Il existe deux façons de concevoir un ASIC : ~~la mauvaise et la bonne~~ l’implémentation custom et l’implémentation basée sur des _standard cells_. ### Implémentation custom L’implémentation _custom_ est la solution historiquement utilisée et consiste à dessiner directement les transistors tels qu’ils devront être sur le silicium. Deux transistors sont cachés dans cette vue de dessus. Sauras‐tu les trouver ? [![Porte « ET » logique](https://i.zcraft.fr/mini_7526691573764762.png)](https://i.zcraft.fr/7526691573764762.png) Non ? Ben, moi non plus. :-/ Chaque couleur correspond à une matière différente, tel que l’on peut l’apercevoir sur cette [vue en coupe](https://commons.wikimedia.org/wiki/File:Coupe_MOSFET.svg). L’implémentation _custom_ permet d’obtenir d’excellentes performances, mais il est plus difficile de concevoir un gros circuit avec, pour ne pas dire impossible. De plus, elle n’est pas portable : le dessin réalisé ne fonctionne qu’avec le nœud technologique (« taille de gravure », par ex. [32 nm](https://en.wikipedia.org/wiki/32_nanometer)) du fondeur choisi. Si l’on veut changer de fondeur ou de nœud technologique, il faut tout redessiner. De nos jours, cette méthode n’est utilisée que pour réaliser certaines parties d’un ASIC ayant des besoins critiques d’optimisation. Nous pouvons dire que l’implémentation _custom_ est l’équivalent microélectronique de l’écriture de code en assembleur : c’était la norme à une époque quand il n’y avait pas vraiment le choix, mais, de nos jours, on peut passer toute une carrière sans en croiser. ### Implémentation basée sur les standard cells Les _standard cells_ sont des bibliothèques de portes logiques généralement fournies par le fondeur, mais qui peuvent aussi être fournies par des parties tierces, telles que [celles](https://vlsiarch.ecen.okstate.edu/flows/MOSIS_SCMOS/latest/cadence/lib/ami035/html/) de l’Oklahoma State University que nous utiliserons dans ce cours. Les _standard cells_ sont donc les briques de base utilisées pour réaliser l’implémentation du circuit. L’utilisation de _standard cells_ permet de simplifier grandement la conception des circuits intégrés, car après la modification du RTL ou le changement de bibliothèque de _standard cells_, « yaka » relancer le flot de conception (équivalent microélectronique d’une chaîne de compilation) pour générer les nouveaux plans de la puce, tout comme en C on lance une nouvelle compilation après avoir modifié le code, ou quand on arrive sur une nouvelle plate‐forme (sauf que, là, les compilations peuvent durer plusieurs semaines...). Il s’agit du type d’implémentation que nous allons voir. ## Flot de conception Pour réaliser une implémentation, il faut utiliser ce qu’on appelle un [flot de conception](https://fr.wikipedia.org/wiki/Flot_de_conception) qui consiste en une suite de logiciels réalisant chacun une tâche. Les grandes étapes d’un flot de conception sont les suivantes : * synthèse logique ; * placement ; * routage ; * vérifications ; * GDS II. ### Synthèse logique La [synthèse logique](https://en.wikipedia.org/wiki/Logic_synthesis) est l’étape qui consiste à prendre le RTL et à le transformer en _netlist_, c’est‐à‐dire en une série de portes logiques. [![Netlist de l’additionneur](https://i.zcraft.fr/mini_3450511573764792.png)](https://i.zcraft.fr/3450511573764792.png) _Ci‐dessus une représentation graphique d’une_ netlist _partielle de l’additionneur 32 bits présenté plus haut._ À partir de la _netlist_, il est possible d’estimer les caractéristiques du circuit final, telles que sa surface, sa consommation ou sa fréquence de fonctionnement, car les caractéristiques des portes logiques sont connues. Par exemple, pour l’estimation de surface, le circuit ne pourra pas être plus petit que la somme des surfaces des portes logiques le composant. En jouant avec les paramètres de l’outil de synthèse logique, il est possible d’améliorer les caractéristiques de surface, de vitesse ou de consommation, mais généralement au détriment des deux autres. Il faut donc faire des compromis qui dépendront surtout de l’utilité finale du circuit : si l’on conçoit une carte accélératrice de calcul pour un serveur, on va plutôt chercher à améliorer la fréquence de fonctionnement, tandis que si l’on vise l’Internet des objets, on aura plutôt tendance à vouloir diminuer la consommation électrique. ### Placement Le [placement](https://en.wikipedia.org/wiki/Placement_(electronic_design_automation)) va consister à placer intelligemment les portes logiques composant la _netlist_ de manière à minimiser la quantité de fils nécessaire pour les interconnecter entre elles et aux entrées‐sorties du circuit. Voici un exemple de placement pour l’additionneur : [![Additionneur après le placement](https://i.zcraft.fr/mini_6399251573764816.png)](https://i.zcraft.fr/6399251573764816.png) Chaque rectangle correspond à une porte logique ou à du métal de remplissage (car, contrairement à l’emmenthal, on ne veut pas de trous dans notre circuit). ### Routage Le [routage](https://en.wikipedia.org/wiki/Routing_(electronic_design_automation)) est l’étape où l’on connecte les portes logiques entre elles via des fils métalliques aux entrées‐sorties du circuit, ainsi qu’aux rails d’alimentation. Le routage a la particularité d’être effectué en trois dimensions en alternant des couches de métal où les fils sont dessinés uniquement horizontalement et des couches de métal où les fils sont uniquement verticaux. Des « vias » s’occupent de faire la jonction entre deux couches de métal. Sur cette jolie _[nimage](https://commons.wikimedia.org/wiki/File:Silicon_chip_3d.png)_ représentant un bout de circuit en 3D, vous pouvez voir le routage en doré : [![Additionneur après routage](https://i.zcraft.fr/mini_1283361573764843.png)](https://i.zcraft.fr/1283361573764843.png) Voici notre additionneur après le routage. Les traits violets correspondent aux fils de la deuxième couche de métal (verticale), les roses à la troisième (horizontale) et les gris à la quatrième (verticale), et, enfin, les petits carrés sont les différents _vias_. La première couche de métal n’a pas été représentée car elle est utilisée par les portes logiques pour réaliser leur routage interne. Notez les deux énormes traits gris verticaux correspondant aux rails d’alimentation. ### Vérification L’étape de vérification consiste à faire toutes les vérifications nécessaires pour valider la possibilité de fabriquer le circuit, mais aussi son fonctionnement. #### STA La [STA](https://en.wikipedia.org/wiki/Design_rule_checking) (_Static Timing Analysis_) correspond à l’analyse des délais du circuit, et donc à vérifier si le circuit atteint les contraintes de délais demandées (notamment les fréquences de fonctionnement visées). La STA est typiquement effectuée après la synthèse logique, le placement et le routage, car si elle n’est pas validé à une étape, elle a peu de chance de l’être après. Et, vu la durée du placement et du routage, mieux vaut se rendre compte le plus tôt possible dans l’exécution du flot que les délais ne sont pas respectés. #### LVS Le [LVS](https://en.wikipedia.org/wiki/Layout_Versus_Schematic) (_Layout Versus Schematic_) consiste à vérifier si le circuit dessiné (_Layout_) correspond bien à la _netlist_ demandée (_Schematic_). #### DRC Le [DRC](https://en.wikipedia.org/wiki/Design_rule_checking) (_Design Rules Checking_) consiste à vérifier si le circuit respecte les règles de dessin du fondeur, telles que l’espacement entre les fils ou leur largeur minimale. Ne pas respecter les règles de dessin, c’est prendre le risque que le circuit soit mal gravé et donc non fonctionnel. #### Simulation En plus de faire la simulation du circuit au niveau RTL, il peut être utile d’en faire une sur la _netlist_ issue de la synthèse (voire de celle issue du routage), car toutes les descriptions de RTL ne sont pas synthétisables (ou, parfois, l’outil de synthèse peut faire de la merde, mais c’est généralement dû à un ingénieur qui fait n’importe quoi avec les options de l’outil) et il peut donc y avoir des différences fonctionnelles entre le RTL et la _netlist_. De plus, la simulation sur la _netlist_ permet de simuler le circuit avec ses délais réels, et donc permet d’attraper d’autres erreurs fonctionnelles. #### Autres Il existe pléthore d’autres tests, tels que l’[ATPG](https://en.wikipedia.org/wiki/Automatic_test_pattern_generation) (_Automatic Test Pattern Generation_) ou la [vérification formelle](https://en.wikipedia.org/wiki/Formal_equivalence_checking). ### GDS II Le [GDS II](https://fr.wikipedia.org/wiki/GDSII) (_Graphic Database System 2_) est le format de fichier utilisé dans le milieu de la microélectronique pour transférer des dessins de circuits, et désigne par métonymie le plan final du circuit à communiquer au fondeur pour qu’il puisse graver la puce. L’article de Wikipédia sur [[photolithographie]] résume comment les fondeurs gravent les circuits. [![GDS II](https://i.zcraft.fr/mini_5382301573764878.png)](https://i.zcraft.fr/5382301573764878.png) Ci‐dessus le GDS II de l’additionneur. ## Qflow Qflow est un outil libre permettant l’exécution d’un flot d’implémentation physique basé sur des outils libres. Il est fourni avec quelques bibliothèques de _standard cells_ libres. Bref, de quoi faire une conception de circuit libre de A à Z. :) Il a même servi à fabriquer un [circuit libre](https://linuxfr.org/news/un-asic-concu-integralement-avec-des-logiciels-libres) ! Qflow est, selon moi, adapté à l’introduction à la microélectronique, car il est très simple d’utilisation : trois boutons à cliquer et roulez jeunesse, il s’occupe de tout. En revanche, les outils de placement et de routage utilisés ont des temps d’exécution assez catastrophiques comparés à ce que proposent les principaux éditeurs de microélectronique. Il y a aussi un gros manque de documentation et de tutoriels pour faire des configurations un peu plus avancées. :-/ # ~~Partie fun~~ Travaux pratiques Aujourd’hui, nous allons concevoir une [unité arithmétique et logique](https://fr.wikipedia.org/wiki/Unit%C3%A9_arithm%C3%A9tique_et_logique) 32 bits à deux entrées `A` et `B`, et une sortie `Y` synchrone, qui sera capable de réaliser les opérations suivantes : * addition (`Y := A + B`) ; * soustraction (`Y := A - B`) ; * incrémentation (`Y := A + 1`) ; * décrémentation (`Y := A - 1`) ; * complément à 1 (`Y := not A`) ; * `ET` binaire (`Y := A and B`) ; * `OU` binaire (`Y := A or B`) ; * `OU exclusif` binaire (`Y := A xor B`). Pas de drapeaux, pas de gestion des retenues, pas de fioritures, on reste sur du [simple, basique](https://www.youtube.com/watch?v=2bjk26RwjyU). ## Installation des outils Il va vous falloir les outils suivants : * [Yosys](http://www.clifford.at/yosys/) (synthèse logique) ; * [Graywolf](https://github.com/rubund/graywolf) (placement) ; * [Qrouter](http://opencircuitdesign.com/qrouter/index.html) (routage) ; * [OpenSTA](https://github.com/The-OpenROAD-Project/OpenSTA) (STA) ; * [NetGen](http://opencircuitdesign.com/netgen/index.html) (LVS) ; * [Magic](http://opencircuitdesign.com/magic/index.html) (_layout_) ; * [Qflow](http://opencircuitdesign.com/qflow/index.html) (flot d’implémentation). Si vous avez de la chance, ils sont disponibles dans votre distribution. Sinon, il va falloir compiler. J’ai écrit le tutoriel en utilisant Qflow 1.4 (branche de développement), mais ça devrait marcher également avec la 1.3 (branche stable). Pour simplifier l’installation, [j’ai commis un petit truc](https://git.slaanesh.org/eda/eda-tools) qui permet de compiler plus ou moins auto‐magiquement tout le bazar d’un coup à partir des dépots Git. En revanche, le script part du principe que toutes les dépendances nécessaires sont installées et s’arrête à la première erreur rencontrée. Voir les journaux dans le dossier `logs` pour voir ce qui a foiré. Bref, c’était prévu pour mon seul usage, du coup je n’ai pas trop cherché à faire ergonomique. ^^' ## Écriture du RTL en Verilog Ça y est, vous avez installé tout le nécessaire ? Parfait, on va pouvoir passer aux choses sérieuses. Commencez par créer l’espace de travail : ```sh $ mkdir -p alu32/{layout,source,synthesis,tb} $ cd alu32 ``` Utilisez le RTL suivant (ou créez le vôtre) : ``` verilog // source/ALU32.v `timescale 1ns / 1ps /** * A 32 bits registered ALU * @param y: output registered of the ALU * @param a: first input * @param b: second input * @param selector: operation selector * - 0b000: Y := A + B * - 0b001: Y := A - B * - 0b010: Y := A + 1 * - 0b011: Y := A - 1 * - 0b100: Y := not A * - 0b101: Y := A and B * - 0b110: Y := A or B * - 0b111: Y := A xor B */ module ALU32(y, a, b, selector, clk); output reg [31:0] y; input [31:0] a, b; input [2:0] selector; input clk; always @(posedge clk) begin case (selector) 3'b000: y <= a + b; 3'b001: y <= a + b; 3'b010: y <= a + 1; 3'b011: y <= a - 1; 3'b100: y <= !a; 3'b101: y <= a & b; 3'b110: y <= a | b; 3'b111: y <= a ^ b; endcase end endmodule ``` ## Mise en place d’un banc de test avec cocotb Habituellement, les bancs de test (_testbench_, en anglais, d’où l’abréviation « tb » couramment utilisée) sont écrits en Verilog (ou VHDL). Mais comme disent les anglophones, c’est un vrai un « [pain dans le c**](https://idioms.thefreedictionary.com/pain+in+the+ass) ». En effet, les langages de description matérielle ne sont pas vraiment adaptés à l’écriture de tests, et c’est rapidement rébarbatif. C’est pourquoi j’ai choisi d’utiliser [cocotb](https://cocotb.readthedocs.io/en/latest/), une bibliothèque qui permet d’écrire les tests en Python et qui génère un fichier `results.xml` au format [[JUnit]] exploitable par votre système d’intégration continue préféré — mais si vraiment vous voulez voir à quoi ressemble l’écriture d’un [_testbench_ en Verilog](https://verilogguide.readthedocs.io/en/latest/verilog/testbench.html). [![Un design testé par Jenkins](https://i.zcraft.fr/mini_1156261573764908.png)](https://i.zcraft.fr/1156261573764908.png) Pour installer cocotb, référez‐vous à la [documentation](https://cocotb.readthedocs.io/en/latest/quickstart.html#installing-cocotb). Écriture du test : ```Makefile # tb/Makefile VERILOG_SOURCES = $(PWD)/../source/ALU32.v # TOPLEVEL is the name of the toplevel module in your Verilog or VHDL file: TOPLEVEL=ALU32 # MODULE is the name of the Python test file: MODULE=test_ALU32 include $(shell cocotb-config --makefiles)/Makefile.inc include $(shell cocotb-config --makefiles)/Makefile.sim ``` ```python # tb/test_alu32.py import cocotb from cocotb.clock import Clock from cocotb.triggers import Timer import itertools import random @cocotb.test() def test_addition(dut): """Test the addition operation""" # Create a 10ns period clock cocotb.fork(Clock(dut.clk, 10, units='ns').start()) # Select the addition operation dut.selector = 0b000 # Do an hundred of random addition for _ in range(100): # Generate the operands a = random.randint(0, 1000) b = random.randint(0, 1000) # Assign the value to the DUT dut.a = a dut.b = b yield Timer(10, 'ns') # Wait a clock period y_expected = a + b assert dut.y == y_expected, "a={}, b={}, expected={}, found={}".format(a, b, y_expected, int(dut.y)) @cocotb.test() def test_substraction(dut): """Test the substraction operation""" # Create a 10ns period clock cocotb.fork(Clock(dut.clk, 10, units='ns').start()) # Select the addition operation dut.selector = 0b001 # Do an hundred of random addition for _ in range(100): # Generate the operands a = random.randint(0, 1000) b = random.randint(0, 1000) # Assign the value to the DUT dut.a = a dut.b = b yield Timer(10, 'ns') # Wait a clock period y_expected = a - b assert dut.y == y_expected, "a={}, b={}, expected={}, found={}".format(a, b, y_expected, int(dut.y)) # and others tests ``` Exécution du test : ```sh $ cd tb $ make ``` Et là, c’est le drame... [![Exécution des tests](https://i.zcraft.fr/mini_8551231573765442.png)](https://i.zcraft.fr/8551231573765442.png) La correction de la grossière erreur sur l’implémentation est laissée en exercice au lecteur. ## Implémentation physique avec Qflow Maintenant que nous avons fini d’écrire notre code et que nous avons validé son fonctionnement, nous allons pouvoir réaliser son implémentation. Pour cela, on revient à la racine du projet et on lance Qflow : ```sh $ cd .. $ qflow gui ``` Cliquez sur le bouton « _Settings_ » de la ligne « _Preparation_ » et choisissez la technologie « osu035 », puis cliquez sur le bouton « _Run_ » : [![Configuration du flot](https://i.zcraft.fr/mini_6243551573764967.png)](https://i.zcraft.fr/6243551573764967.png) Ensuite, cliquez sur le bouton « _Run_ » de la ligne « _Synthesis_ ». Qflow s’occupe du reste : [![Exécution du flot](https://i.zcraft.fr/mini_4224051573764990.png)](https://i.zcraft.fr/4224051573764990.png) Une fois que tout est « _Okay_ », vous pouvez cliquer sur « _Edit layout_ » pour admirer votre magnifique circuit intégré : [![GDS II de l’ALU](https://i.zcraft.fr/mini_3003751573765015.png)](https://i.zcraft.fr/3003751573765015.png) Tadam ! Quand je disais que c’était facile. :) ## Exercice Réaliser l’implémentation physique de TapTempo. Si vous demandez gentiment à _Martoni<_, il devrait pouvoir vous générer le RTL à partir de sa version [Chisel](https://github.com/Martoni/TapTempoChisel). À rendre la semaine prochaine. Compte pour la moitié de la note du semestre.

AltStyle によって変換されたページ (->オリジナル) /