Greboca  

LinuxFr.org : les journaux  -  Conception d’un circuit intégré avec Qflow

 -  17 novembre - 

Sommaire

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é 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 et le 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, qui se présente sous la forme de classes et macros C++, Chisel, basé sur Scala, ou MyHDL 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

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

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

#ifndef __ADDER_H__
#define __ADDER_H__

#include 

SC_MODULE(Adder) {
    sc_out<uint> s;
    sc_in<uint> 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

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

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

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

Il va donc nous falloir faire l’implémentation du circuit. Nous pouvons choisir soit de cibler un FPGA, soit fabriquer un ASIC (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 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

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.

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) 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 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 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 Ⅱ.

Synthèse logique

La synthèse logique 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’additionneurCi‑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 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

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 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 représentant un bout de circuit en 3D, vous pouvez voir le routage en doré :

Additionneur après routage

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 (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 (Layout Versus Schematic) consiste à vérifier si le circuit dessiné (Layout) correspond bien à la netlist demandée (Schematic).

DRC

Le DRC (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 (Automatic Test Pattern Generation) ou la vérification formelle.

GDS Ⅱ

Le GDS Ⅱ (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 Ⅱ  Ci‑dessus le GDS Ⅱ 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 !

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 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.

Installation des outils

Il va vous falloir les outils suivants :

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 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 :

$ mkdir -p alu32/{layout,source,synthesis,tb}
$ cd alu32

Utilisez le RTL suivant (ou créez le vôtre) :

// 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** ». 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, 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.

Un design testé par Jenkins

Pour installer cocotb, référez‐vous à la documentation.

Écriture du test :

# 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
# 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 :

$ cd tb
$ make

Et là, c’est le drame…
Exécution des tests

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 :

$ 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

Ensuite, cliquez sur le bouton « Run » de la ligne « Synthesis ». Qflow s’occupe du reste :

Exécution du flot

Une fois que tout est « Okay », vous pouvez cliquer sur « Edit layout » pour admirer votre magnifique circuit intégré :

GDS Ⅱ de l’ALU

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. À rendre la semaine prochaine. Compte pour la moitié de la note du semestre.

Commentaires : voir le flux atom ouvrir dans le navigateur

par killruana

LinuxFr.org : les journaux

LinuxFr.org : Journaux

31 collectivités récompensées par le label Territoire Numérique Libre 2019 décerné par l'ADULLACT

 -  2 décembre - 

Communiqué de l'April Liste des communes récompensées, sur le site Territoire Numérique Libre De quoi s'agit-il ? Je vous renvoie au communiqué de (...)


NoComprendo, la commande vocale pour Linux

 -  28 novembre - 

Sommaire Un contexte douloureux Trouver un outil Ça marche ce truc ? A quoi ça ressemble ? Principes générauxVocabulaire Enoncé Les différentes (...)


kFPGA et DEL qui clignotent

 -  27 novembre - 

Ceci est une copie du billet publié initialement sur mon blog sous licence CC SA-BY 4.0. Dans l'épisode précédent, j'avais annoncé avoir validé le (...)


CPU Ex0121 The Mother Of All Demos, l'autre révolution de 1968

 -  21 novembre - 

Dans cette release de CPU : Une souris, des liens hyper, un piano à 5 touches et une audience conquise. The Mother Of All Demos, l'autre révolution (...)


Le bloboscope

 -  16 novembre - 

Cher Nal', Si tu t'intéresses à l'actualité scientifique, tu as sans doute déjà entendu parlé du blob. Pourvu de 720 sexes et dépourvu de système (...)