Journal Nouvelles de kFPGA, le FPGA libre

Posté par  (site web personnel) . Licence CC By‑SA.
Étiquettes :
30
12
avr.
2020

Sommaire

Bonjour' nal,

En novembre, je t'avais parlé de mon projet de FPGA libre, et je t'avais aussi montré une vidéo où il fait clignoter des LEDs.

Aujourd'hui, je te fais un petit point sur l'avancée du projet.

Déjà, comme tu peux le constater sur la heatmap ci-dessous, l'activité sur le projet a été assez faible. Cela est dû à une combinaison de facteurs. Il a fallu que je déménage suite à une rupture, je devais préparer une audience au tribunal pour début mars qui a finalement été reporté en septembre suite à la grève des avocats, j'ai reçu une mise en demeure de la part d'un concurrent après la publication de la dépêche parlant du projet, il faut que consacre du temps à ma recherche de taf pour faire plaisir à Pôle emploi et à mon banquier, le confinement à une influence assez néfaste sur ma motivation… Bref, j'avais pas trop la tête à bosser dessus.

Heatmap du projet

Mise de côté de OFP (Open FPGA Platform)

À la base j'avais décomposé mon idée en deux projets distincts : OFP et kFPGA. OFP devait être un framework gérant tout le cycle de vie des FPGA. Les concepteurs de FPGA s'en serait servis pour créer et mettre à disposition leur architectures FPGA, les intégrateurs pour réaliser les implémentations physiques et les utilisateurs pour programmer leurs FPGA. kFPGA aurait été une architecture parmi d'autres utilisable avec OFP. En cours de route, je me suis rendu compte que peut-être que j'avais vu un peu trop gros pour commencer et que la partie qui m'intéressai le plus était la conception de l'architecture FPGA. Du coup, j'ai mis de côté OFP et je me suis concentré sur kFPGA en y intégrant le travail sur OFP déjà effectué (essentiellement le module générant le code RTL des cœurs).

Simplification de l'architecture kFPGA

Concevoir une architecture FPGA, c'est bien beau, mais il faut encore pouvoir la tester. Et pour ça, il faut les outils pour la programmer : un synthétiseur logique, un placeur, un routeur, un générateur de bitstream et éventuellement un analyseur de timings. Tous ces outils existent déjà en opensource mais aucun n'est prévu pour fonctionner avec mon architecture :'(. Il me faut donc les adapter. Pour simplifier ce travail d’adaptation, j'ai décidé de partir de l'architecture FPGA la plus simple possible car comme l'aurait Lapalisse, moins il y a de fonctionnalités dans l'architecture, moins il y a de fonctionnalités à gérer dans les outils. Une fois que j'aurai une chaîne d'outils fonctionnelle, je pourrais complexifier mon architecture de manière incrémentale.

À quoi correspond une architecture la plus simple possible ? Juste des LUTs, des registres et de l’interconnexion. Un seul signal d'horloge en entrée, pas de signaux enable ou set, un seul signal de reset et déclenché uniquement sur un front montant, pas de génération interne de signaux d'horloge ou de reset. Bref, le minimum du minimum, voir même moins.

Un cœur kFPGA est constitué d'I/O en bordure permettant de communiquer avec l'extérieur, d'une grille de tuiles logiques implémentant les calculs et d'adapteurs entre les I/O et la grille. Le nombre d'IO par adaptateur, les dimensions de la grille et la largeur des canaux de routage sont configurables.

Vue d'ensemble de l'architecture

Chaque tuile logique contient un élément de routage (switch box) et des éléments logiques. Le nombre d'éléments logiques est configurable.

Vue d'une tuile logique

Chaque élément logique est composé d'une LUT et d'un registre by-passable. La taille des LUT est configurable.

Vue d'un élément logique

Synthèse logique

J'ai écrit un outil de synthèse logique basé sur yosys. Plutôt que de forker Yosys pour y inclure une commande de synthèse supportant spécifiquement kFPGA, j'ai préféré faire un wrapper qui génère à la volée le script de synthèse utilisé pour piloter Yosys. De cette manière, il m'est plus facile de supporter d'autres outils de synthèses. Et puis comme ça je n'ai pas à apprendre comment faire pour que python setup.py install compile un gros truc en c++ '.

Ça s'utilise comme ça :

$ kfpga-synthesize -o <netlist de sortie> <fichier de description du cœur cible> <fichiers RTL à synthetiser>

Exemple avec un additionneur 2 bits

module Adder2(z, a, b, clk);
    output reg [2:0] z;
    input [1:0] a, b;
    input clk;

    always @(posedge clk)
        z <= a + b;
endmodule

Synthèse à destination d'un cœur kFPGA avec des LUT de taille 2 :

$ kfpga-synthesize -o netlist_lut2.v kfpga_lut2.kcf Adder2.v

Netlist obtenue :

/* Generated by Yosys 0.9+2406 (git sha1 7c06cb61, gcc 9.3.0-1 -march=native -O3 -fno-plt -fPIC -Os) */

(* top =  1  *)
(* src = "Adder2.v:3.1-8.10" *)
module adder2(z, b, a);
  (* src = "Adder2.v:5.17-5.18" *)
  input [1:0] a;
  (* src = "Adder2.v:5.20-5.21" *)
  input [1:0] b;
  (* src = "Adder2.v:4.18-4.19" *)
  output [2:0] z;
  wire z_KTECH_LUT2_o_data_1_i_data;
  wire z_KTECH_LUT2_o_data_1_i_data_1;
  wire z_KTECH_LUT2_o_data_i_data;
  wire z_KTECH_LUT2_o_data_i_data_1;
  (* module_not_derived = 32'd1 *)
  (* src = "/tmp/tmp5zur_vdw/cells_map.v:44.15-47.14" *)
  KTECH_LUT2 #(
    .CONFIG(4'h8)
  ) a_KTECH_LUT2_i_data (
    .i_data({ b[0], a[0] }),
    .o_data(z_KTECH_LUT2_o_data_i_data_1)
  );
  (* module_not_derived = 32'd1 *)
  (* src = "/tmp/tmp5zur_vdw/cells_map.v:44.15-47.14" *)
  KTECH_LUT2 #(
    .CONFIG(4'h8)
  ) a_KTECH_LUT2_i_data_1 (
    .i_data({ b[1], a[1] }),
    .o_data(z_KTECH_LUT2_o_data_1_i_data)
  );
  (* module_not_derived = 32'd1 *)
  (* src = "/tmp/tmp5zur_vdw/cells_map.v:44.15-47.14" *)
  KTECH_LUT2 #(
    .CONFIG(4'h6)
  ) a_KTECH_LUT2_i_data_2 (
    .i_data({ b[1], a[1] }),
    .o_data(z_KTECH_LUT2_o_data_i_data)
  );
  (* module_not_derived = 32'd1 *)
  (* src = "/tmp/tmp5zur_vdw/cells_map.v:44.15-47.14" *)
  KTECH_LUT2 #(
    .CONFIG(4'h6)
  ) z_KTECH_LUT2_o_data (
    .i_data({ z_KTECH_LUT2_o_data_i_data, z_KTECH_LUT2_o_data_i_data_1 }),
    .o_data(z[1])
  );
  (* module_not_derived = 32'd1 *)
  (* src = "/tmp/tmp5zur_vdw/cells_map.v:44.15-47.14" *)
  KTECH_LUT2 #(
    .CONFIG(4'he)
  ) z_KTECH_LUT2_o_data_1 (
    .i_data({ z_KTECH_LUT2_o_data_1_i_data, z_KTECH_LUT2_o_data_1_i_data_1 }),
    .o_data(z[2])
  );
  (* module_not_derived = 32'd1 *)
  (* src = "/tmp/tmp5zur_vdw/cells_map.v:44.15-47.14" *)
  KTECH_LUT2 #(
    .CONFIG(4'h8)
  ) z_KTECH_LUT2_o_data_1_i_data_1_KTECH_LUT2_o_data (
    .i_data({ z_KTECH_LUT2_o_data_i_data, z_KTECH_LUT2_o_data_i_data_1 }),
    .o_data(z_KTECH_LUT2_o_data_1_i_data_1)
  );
  (* module_not_derived = 32'd1 *)
  (* src = "/tmp/tmp5zur_vdw/cells_map.v:44.15-47.14" *)
  KTECH_LUT2 #(
    .CONFIG(4'h6)
  ) z_KTECH_LUT2_o_data_2 (
    .i_data({ b[0], a[0] }),
    .o_data(z[0])
  );
endmodule

Même chose avec un FPGA possédant des LUTs de taille 6 :

$ kfpga-synthesize -o netlist_lut6.v kfpga_lut6.kcf Adder2.v
/* Generated by Yosys 0.9+2406 (git sha1 7c06cb61, gcc 9.3.0-1 -march=native -O3 -fno-plt -fPIC -Os) */

(* top =  1  *)
(* src = "Adder2.v:3.1-8.10" *)
module adder2(z, b, a);
  (* src = "Adder2.v:5.17-5.18" *)
  input [1:0] a;
  (* src = "Adder2.v:5.20-5.21" *)
  input [1:0] b;
  (* src = "Adder2.v:4.18-4.19" *)
  output [2:0] z;
  (* module_not_derived = 32'd1 *)
  (* src = "/tmp/tmp8gt2czyu/cells_map.v:60.15-63.14" *)
  KTECH_LUT6 #(
    .CONFIG(64'h0000000000008778)
  ) z_KTECH_LUT6_o_data (
    .i_data({ 2'h0, b[1], a[1], b[0], a[0] }),
    .o_data(z[1])
  );
  (* module_not_derived = 32'd1 *)
  (* src = "/tmp/tmp8gt2czyu/cells_map.v:60.15-63.14" *)
  KTECH_LUT6 #(
    .CONFIG(64'h000000000000e888)
  ) z_KTECH_LUT6_o_data_1 (
    .i_data({ 2'h0, b[0], a[0], b[1], a[1] }),
    .o_data(z[2])
  );
  (* module_not_derived = 32'd1 *)
  (* src = "/tmp/tmp8gt2czyu/cells_map.v:44.15-47.14" *)
  KTECH_LUT6 #(
    .CONFIG(64'h0000000000000006)
  ) z_KTECH_LUT6_o_data_2 (
    .i_data({ 4'h0, b[0], a[0] }),
    .o_data(z[0])
  );
endmodule

Système de tests automatisés

Pour me simplifier la vie et vérifier que je casse pas plus de trucs que je n'en répare, j'ai bricolé un système de tests automatisés. À partir de modèles relativement simples, le bouzin me génère une grande variété d'applications et les testbenchs associés, fait les synthèses logiques à destination de différents cœurs, exécute les tesbenchs et me génère un joli rapport au format HTML. La génération d'un rapport au format JUnit est prévu afin de faire tourner tout ça automatiquement dans Jenkins.

J'utilise python et le moteur de template Jinja pour générer les bouzins, make pour organiser l'orchestration et cocotb pour écrire les testbenchs.

Aperçu du rapport

Prochaine étape

Pouvoir réaliser le placement et le routage. Pour ça, je vais sauvagement gruiker nextpnr.

Liens

Le projet a déménagé depuis la dernière fois, il se trouve maintenant ici. Le README contient une description des autres outils fourni par le projet.

Mon espace de travail est ici. Il contient le bordel qui n'a pas sa place dans le dépôt principal mais qui improve grave ma productivity, tel que ma R&D, de la doc, des scripts divers…

  • # chapeau

    Posté par  . Évalué à 4.

    Franchement, je suis impressionné par la qualité et quantité de boulot qui se cache derrière ce projet.
    J'ai une petite remarque sur le reset de ton FPGA, pourquoi sur front ? En général les reset sont asynchrone et sur un état (de préférence actif à 0). En plus ça aura l'avantage quand tu voudra fondre ton circuit de trouver directement des bascules qui le font. En effet dans tous les cas en bibliothèque de cellules, les bascules sont à raz async et actif à 0, mais jamais sur un front.
    Pour finir une question : as tu déjà envisagé de faire fabriquer ton FPGA, si oui, chez qui (et avec quel budget car là ca parle de gros sous).
    En tout cas bonne continuation et encore une fois félicitations.

    • [^] # Re: chapeau

      Posté par  (site web personnel) . Évalué à 2.

      Franchement, je suis impressionné par la qualité et quantité de boulot qui se cache derrière ce projet.

      Merci :)

      J'ai une petite remarque sur le reset de ton FPGA, pourquoi sur front ? En général les reset sont asynchrone et sur un état (de préférence actif à 0). En plus ça aura l'avantage quand tu voudra fondre ton circuit de trouver directement des bascules qui le font. En effet dans tous les cas en bibliothèque de cellules, les bascules sont à raz async et actif à 0, mais jamais sur un front.

      Je ne sais pas si c'est parce qu'on veut dire la même chose avec des mots différents, mais dans tous les exemples de code verilog que j'ai pu trouver au cours de ma (courte) carrière dans la micro-élec, le reset des flip-flop était décrit comme ça

      // async
      always @(posedge clk, posedge rst)
          if (rst)
              stored_data <= 0;
          else
              stored_data <= input_data;
      
      // sync
      always @(posedge clk)
          if (rst)
              stored_data <= 0;
          else
              stored_data <= input_data;

      Comme l'indique de posedge ("positive edge", front montant en français), c'est le changement d'état (du signal d'horloge les FF synchrone, du signal de reset pour les FF asynchrone) qui déclenche la fonctionnalité.
      Là où je vais à contre courant de l'industrie, c'est d'utiliser le front montant plutôt que le descendant. Mais bon, ce n'est pas difficile de passer de l'un à l'autre. Pis de toute façon, à terme, je supporterai les deux (ainsi que le mode sync/async).

      Comme tu peux le constater, cela synthétise et mappe parfaitement vers une DFF de la bibliothèque de stdcell OSU035 :

      $ cat source/DFF.v 
      module DFF(output reg d, input d_in, clk, rst);
          always @(posedge clk, posedge rst)
              if (rst)
                  d <= 0;
              else
                  d <= d_in;
      endmodule
      $ qflow synth -t osu035 DFF
      $ cat synthesis/DFF_mapped.v
      /* Generated by Yosys 0.9+2406 (git sha1 93ef516d, gcc 9.3.0-1 -march=native -O3 -fno-plt -fPIC -Os) */
      
      (* top =  1  *)
      (* src = "/home/killruana/tmp/DFF/source/DFF.v:1.1-7.10" *)
      module DFF(d, d_in, clk, rst);
        wire _0_;
        wire _1_;
        (* src = "/home/killruana/tmp/DFF/source/DFF.v:1.38-1.41" *)
        input clk;
        (* src = "/home/killruana/tmp/DFF/source/DFF.v:1.23-1.24" *)
        output d;
        (* src = "/home/killruana/tmp/DFF/source/DFF.v:1.32-1.36" *)
        input d_in;
        (* src = "/home/killruana/tmp/DFF/source/DFF.v:1.43-1.46" *)
        input rst;
        INVX1 _2_ (
          .A(rst),
          .Y(_0_)
        );
        (* src = "/home/killruana/tmp/DFF/source/DFF.v:2.5-6.23" *)
        DFFSR _3_ (
          .CLK(clk),
          .D(d_in),
          .Q(_1_),
          .R(_0_),
          .S(1'h1)
        );
        (* keep = 32'd1 *)
        BUFX2 _4_ (
          .A(_1_),
          .Y(d)
        );
      endmodule
      

      Pour finir une question : as tu déjà envisagé de faire fabriquer ton FPGA, si oui, chez qui (et avec quel budget car là ca parle de gros sous).
      En tout cas bonne continuation et encore une fois félicitations.

      Oui et non. Une fois que le projet sera suffisamment avancé, je compte faire un financement participatif pour fabriquer des puces open hardware à destination des bidouilleurs «kFPGA, l'Arduino des FPGA !». J'ai repéré eFabless qui travaille avec la fonderie X-Fab et qui permet d'utiliser un flot libre basé sur QFlow. Sinon le classique Europractice qui permet d’accéder à toutes les fonderies ou presque, même si je crains qu'avec eux il ne me faille utiliser des outils proprios :'(

      À part ça, comme je cherche plus à me placer en tant que fournisseur d'IP et de service autour de la techno que en tant que vendeur de puces, je ne m'attends pas passer en fonderie régulièrement.

      • [^] # Re: chapeau

        Posté par  . Évalué à 3.

        Je connais plus le VHDL que verilog. Mais si je comprends cette description (if rst) teste bien le niveau, c'est bien un reset sur le niveau qui est fait. Donc oui, c'est bien une bascule normale. C'est le terme que tu avais employé (reset sur un front) qui était ambigu.
        Ton FPGA pourrait aussi avoir un intéret pour des labo qui cherchent parfois à faire des choses pas classiques (dans le sens pas dans le flot de conception classique d'un FPGA). L'intrêt ici est de tout connaitre et donc tout maitriser, ce qui n'est pas le cas avec des FPGA et outils commerciaux ou la partie bitstream est une boite noire. Et pour finir un soft libre serait aussi sans doute très intéressant pour des universités pour des aspect pédagogique, là ou encore une fois les outils commerciaux sont fermés.

        • [^] # Re: chapeau

          Posté par  (site web personnel) . Évalué à 3.

          Je connais plus le VHDL que verilog. Mais si je comprends cette description (if rst) teste bien le niveau, c'est bien un reset sur le niveau qui est fait. Donc oui, c'est bien une bascule normale. C'est le terme que tu avais employé (reset sur un front) qui était ambigu.

          Ouais, je voulais surtout dire qu'il fallait envoyer un 1 plutôt qu'un 0 pour réaliser le reset.

          L'intrêt ici est de tout connaitre et donc tout maitriser, ce qui n'est pas le cas avec des FPGA et outils commerciaux ou la partie bitstream est une boite noire.

          Les outils libres arrivent de mieux en mieux à supporter les FPGA proprios (voir le projet Symbiflow) donc les bistreams perdent de plus en plus leur côté boite noire. Je suis plus concerné par le fait que les FPGA eux-même soient des boîtes noires. Qu'est-ce qui nous dit que des backdoors n'ont pas été implémentés à la demande de la NSA pour par ex extraire facilement les clés cryptographique quand de l'AES est implémenté dessus ?

          Et pour finir un soft libre serait aussi sans doute très intéressant pour des universités pour des aspect pédagogique, là ou encore une fois les outils commerciaux sont fermés.

          Grave. D'ailleurs, je compte me rapprocher de mes anciens profs d'IUT membres du LIRMM pour exploiter des thésards :D

          • [^] # Re: chapeau

            Posté par  (site web personnel) . Évalué à 5.

            Le boulot est assez génial. J'imagine que l'idée suivante est de réussir à gérer plusieurs type de tile : La lut de base, une lut avec un peu de logique cablée plus rapide, des ALU simples, des mémoires de qq ko, des multiplieurs, des routeurs (pour aller plus vite d'aller d'un bout à l'autre de la puce).

            "La première sécurité est la liberté"

    • [^] # Re: chapeau

      Posté par  (site web personnel, Mastodon) . Évalué à 5.

      Bravo !

      De mon point de vu, ce projet est un grand coup de pied dans la fourmilière du monde des FPGA. En avançant sur ce projet tu casses le mythe d'un FPGA qui ne serait accessible qu'aux très grosses compagnies et ultra-verouillés (Il parait que pour lire la datasheet des librairies de fondeurs, il faut d'abord signer un premier accord de non divulgation (NDA) pour pouvoir lire l'accord principal de non-divulgation ;).
      Je connaissais Archipelago, mais il n'a jamais atteint aucune fonderie, et surtout il s'est visiblement cantonné à un seul projet/thèse à Berkley.

      Pour en revenir au reset asynchrone, c'est un vaste (et vieux) débat ;) Beaucoup de FPGA vont accepter les deux, par contre il faudra que ce soit homogène. Si on choisi asynchrone, il faut que tout le design soit en reset asynchrone.
      Si l'on veux plusieurs type de reset il faudra séparer en «domaine de reset» de la même manière que l'on parle de «domaine d'horloge». Et il faudra bien soigner les «franchissements» de domaines.

      En règle générale, dans le monde du FPGA on fait du reset asynchrone, dans le monde de l'ASIC du synchrone. Ça a d'ailleurs longtemps été un des points noir du langage Chisel, qui n'acceptait que le reset synchrone car ciblant les ASIC (pour produire les Risc-V à base de RocketChip).

      Le (un des) Problème du reset asynchrone c'est le «relâchement» du reset qui doit être synchrone. Le (un des) problème du reset synchrone c'est qu'il faut … une horloge pour faire le reset, et le franchissement des domaines d'horloges est (encore) plus compliqué.

      Bref, le choix reset synchrone/asynchrone est loin d'être une évidence. Mais s'il est vrai que «à l'école» on apprend tous à faire du VHDL avec un reset asynchrone ;)

      J'ai plus qu'une balle

      • [^] # Re: chapeau

        Posté par  (site web personnel) . Évalué à 2.

        Quand je lis ça, je me dis que je ferai mieux de partir sur du synchrone pour commencer.

        Et en preview, ma DFF qui gère le sync et l'async o/

        module KFPGA_DFF(
            output reg q,
            input d, clk, rst, is_rst_sync
        );
            wire rst_sync = rst && is_rst_sync;
            wire rst_async = rst && !is_rst_sync;
            always @(posedge clk, posedge rst_async)
                if (rst_async)
                    q <= 1'b0;
                else
                    if (rst_sync)
                        q <= 1'b0;
                    else
                        q <= d;
        endmodule

        (oui, j'ai appris le verilog par moi même, non, je ne suis pas allé loin dans mon apprentissage, et oui, j'ai conscience que c'est un peu pourri, mais c'est tout ce que j'ai trouvé)

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.