Archives mensuelles : mars 2020

Apicula : lancement de la libération du FPGA Gowin GW1N

[Dépèche écrite initialement pour LinuxFR]

Le lecteur assidu de LinuxFr.org sait déjà sans doute ce qu’est un FPGA. Rappelons‑en cependant brièvement la définition.

Les FPGA sont des composants constitués de « champs de portes programmables ». L’idée est de graver un certain nombre d’éléments logiques simples sous forme de matrice et de laisser au développeur le loisir de reconfigurer à l’infini les connexions entre ces portes. Une fois les connexions configurées, on se retrouve avec un composant numérique sur mesure qui ne ressemble à aucun composant disponible chez les fournisseurs classiques. C’est très pratique quand on a besoin d’architectures bâtardes, ou quand justement on développe un composant numérique : ça permet de reconfigurer à l’infini pour déverminer et évaluer les performances.

Pepijn de Vos a effectué un stage pour Symbiotic EDA. Et l’ingénierie inverse du GW1N était son sujet de stage. Il a rendu son rapport avant Noël sur GitHub et a publié le code source du projet Apicula.

Vous voulez en savoir plus, lisez la suite…

Sommaire

Parlons maintenant du grand drame des FPGA : ils sont complètement verrouillés car, pour reconfigurer ces fameuses connexions, il faut leur téléverser un fichier nommé bitstream qu’aucun constructeur de FPGA ne documente. Ce drame conduit les libristes que nous sommes à se taper des installations d’outils propriétaires particulièrement volumineux (surtout dans le cas de Xilinx et Altera) et pas toujours très stables. Tout ça pour générer le fameux bitstream.

Le format du bitstream n’est pourtant pas chiffré. Depuis plus d’une décennie, on sait qu’il est parfaitement possible d’en faire l’ingénierie inverse. Mais rien n’avait bougé jusqu’en 2015 quand Clifford (qui se prénomme maintenant Claire) Wolf a sorti sa suite open source pour le Lattice iCE40 : IceStorm. Le projet IceStorm avait pour but d’analyser toute la gamme des FPGA iCE40 pour en documenter le format du bitstream. Projet qui a parfaitement abouti et essaimé : toute la gamme des iCE40 est désormais accessible au moyen d’outils open source, ainsi que la gamme des ECP5 avec le Projet Trellis.

Presque tous les modèles de FPGA ont leur projet d’ingénierie inverse aujourd’hui. Tous ne sont pas terminés, loin s’en faut. Néanmoins, quelques‑uns avancent, comme le projet Apicula de Pepijn de Vos ciblant les FPGA du constructeur chinois Gowin. Dire que le projet Apicula est abouti serait un raccourci un peu rapide, il manque encore en effet un certain nombre de blocs à décoder. Cependant, il est possible aujourd’hui de réaliser un projet très simple (UNE LED QUI CLIGNOTE \o/) grâce au travail effectué.

Le Gowin GW1N

Voici l’architecture du GW1N donnée dans la documentation du composant :

Architecture global du GW1N

Oui, toi aussi, cher lecteur, tu te demandes bien l’intérêt de ce zoom. ;)

On voit différents éléments dans ce schéma, de la mémoire vive, des PLL pour générer les horloges, des multiplieurs câblés (DSP), des blocs d’entrées‑sorties (IOB) et surtout — ce que l’on voit le plus — des CFU (Configurable Function Unit).

Chez Gowin, le CFU est l’élément de base constitué ainsi :

Schéma d’architecture CFU

Les deux éléments qui nous intéressent ici sont : la LUT (Look Up Table) et le REG (registre). Ces deux éléments sont la base de la logique synchrone.

Le registre recopie son entrée sur la sortie au front montant de l’horloge, la sortie restant stable le reste du temps.

La LUT, comme son nom anglais l’indique, est une table de vérité, qui, dans le cas de ce petit FPGA, comporte quatre entrées binaires et une sortie. La configuration du FPGA viendra remplir cette table de vérité pour réaliser une fonction logique. Le registre se trouvant derrière se charge ensuite de verrouiller le résultat au rythme de l’horloge.

Si l’on arrive à déchiffrer le bitstream pour pouvoir configurer ces LUT, ces registres ainsi que les blocs d’entrées‑sorties (IOB), alors on a (presque) gagné : on peut réaliser un composant simple mais fonctionnel.

Et c’est ce qu’a réalisé Pepijn de Vos, on peut donc dire qu’il a amorcé la libération du GW1N de Gowin !

Le projet n’est cependant pas terminé, il faut encore déchiffrer les autres blocs pour permettre l’utilisation complète du FPGA. Il faut également réussir à documenter les temps de propagation des signaux entre les différents blocs. L’information de ces temps de propagation permet au logiciel de placement routage (nextpnr, pour ne citer que le plus célèbre en libre) de faire son travail correctement.

Mais alors, cette LED clignotante, elle vient ?

Minute, papillon ! Dans un premier temps, il va falloir trouver une carte électronique munie d’un FPGA de Gowin. Nous allons ensuite nous servir des données déjà produites par le projet Apicula pour faire la synthèse avec Yosys, puis le placement‐routage avec nextpnr.

Pour le moment, deux kits de développement ont été utilisés dans le projet Apicula :

Nous allons tester avec le kit allemand TEC0117, mais le Tang Nano ne change pas grand’chose à la procédure (et il est moins cher).

On doit installer :

  • Yosys, le logiciel de synthèse Verilog. Pour le détail des dépendances et autres subtilités d’installation, voir le site officiel. Sinon, pour résumer :
git clone https://github.com/YosysHQ/yosys.git
cd yosys make sudo make install
  • nextpnr, le logiciel de placement routage. Il faut le compiler avec le paramètre «generic». Les deux autres paramètres possible à ma connaissance sont ice40 et ecp5 (les deux gammes de FPGA vraiment prises en charge par nextpnr) : git clone https://github.com/YosysHQ/nextpnr.git cd nextpnr/ cmake -DARCH=generic . make sudo make install Dans mon cas, j’ai dû forcer l’utilisation de Python 3.7 dans le fichier CMakeLists.txt : find_package(PythonInterp 3.7 REQUIRED); pour pouvoir exécuter les scripts en argument avec Python 3.7.
  • Apicula, dont il faut cloner le dépôt Git : git clone https://github.com/pepijndevos/apicula.git cd apicula
  • pour l’instant la « base de données » des éléments trouvés par ingénierie inverse n’est pas « commité » dans le projet Apicula, il faut donc avoir une installation de l’EDI officiel de Gowin ; les scripts du projet Apicula iront fouiller dans les données de l’IDE pour générer un fichier intelligible en JSON et pickle (Python), il n’est cependant pas nécessaire d’avoir la licence ;
  • il faut également aller chercher le « pinout » de nos FPGA sur le site officiel de Gowin et le mettre dans son répertoire local nommé ~/Documents/gowinsemi :

Maintenant que nous avons les outils installés, lançons l’exemple générique du projet Apicula :

  • d’abord, on génère la base de données des éléments du FPGA : export GOWINHOME=/chemin/de/linstallation/gowin/IDE/ export DEVICE="GW1NR-9" # TEC0117 python dat19_h4x.py # makes $DEVICE.json python tiled_fuzzer.py # makes $DEVICE.pickle
  • muni de cette base de données, on peut se lancer dans la synthèse du (pas si) simple porte‑gramme de clignotement de LED donné en Verilog ci‑dessous : module top; wire clk; (* BEL="R29C29_IOBA", keep *) GENERIC_IOB #(.INPUT_USED(1), .OUTPUT_USED(0)) clk_ibuf (.O(clk)); wire [7:0] leds; (* BEL="R1C8_IOBA", keep *) GENERIC_IOB #(.INPUT_USED(0), .OUTPUT_USED(1)) led7_obuf (.I(leds[7])); (* BEL="R1C8_IOBB", keep *) GENERIC_IOB #(.INPUT_USED(0), .OUTPUT_USED(1)) led6_obuf (.I(leds[6])); (* BEL="R1C10_IOBA", keep *) GENERIC_IOB #(.INPUT_USED(0), .OUTPUT_USED(1)) led5_obuf (.I(leds[5])); (* BEL="R1C10_IOBB", keep *) GENERIC_IOB #(.INPUT_USED(0), .OUTPUT_USED(1)) led4_obuf (.I(leds[4])); (* BEL="R1C11_IOBA", keep *) GENERIC_IOB #(.INPUT_USED(0), .OUTPUT_USED(1)) led3_obuf (.I(leds[3])); (* BEL="R1C11_IOBB", keep *) GENERIC_IOB #(.INPUT_USED(0), .OUTPUT_USED(1)) led2_obuf (.I(leds[2])); (* BEL="R1C12_IOBA", keep *) GENERIC_IOB #(.INPUT_USED(0), .OUTPUT_USED(1)) led1_obuf (.I(leds[1])); (* BEL="R1C12_IOBB", keep *) GENERIC_IOB #(.INPUT_USED(0), .OUTPUT_USED(1)) led0_obuf (.I(leds[0])); reg [25:0] ctr; always @(posedge clk) ctr <= ctr + 1'b1; assign leds = ctr[25:18]; endmodule Un habitué des LED qui clignotent en Verilog reconnaîtra tout de suite le compteur dans les dernières lignes (always, reg…) mais sera peut‑être perturbé par la déclaration des entrées‑sorties. Il faut juste avoir en tête que le projet n’est pas terminé et qu’il faut se taper le placement‐routage des entrées‑sorties « à la main », d’où les directives (* ... *) et les modules GENERIC_IOB().
  • le script pour la synthèse et le placement routage est donné dans l’exemple : $ cd generic $ bash simple.sh blinky.v # TEC0117 $ cd .. $ python gowin_pack.py generic/pnrblinky.json $ python gowin_unpack.py pack.fs $ yosys -p "read_verilog -lib +/gowin/cells_sim.v; clean -purge; show" unpack.v
  • on doit se retrouver avec un bitstream nommé pack.fs que l’on peut téléverser dans le FPGA au moyen de l’utilitaire libre openFPGALoader maintenu par Trabucayre (Gwenhael Goavec‑Merou) : openFPGALoader -m -b littleBee pack.fs # FOSS programmer Parse pack.fs: Done erase SRAM Done Flash SRAM: [==================================================] 100.000000% Done SRAM Flash: FAIL Le FAIL est connu et vient d’une sombre histoire de somme de contrôle que openFPGALoader ne sait pas encore calculer et qu’Apicula ne fournit pas. Un ticket est ouvert sur le sujet dans le projet, Trabucayre sera ravi d’accepter des correctifs.

Et les huit LED doivent clignoter. Enfin, disons plutôt qu’elles comptent en binaire. Pour voir la vidéo des LED clignotantes, c’est sur YouTube.

Architectures de développement hétéroclites

Comme nous avons des outils open source, il est possible de développer sur des ordinateurs à base d’architectures différentes de x86. Voici un petit exemple de téléchargement d’un bitstream sur Tang Nano au moyen d’un Raspberry Pi (architecture ARM). Le bitstream en question permet de piloter un écran à cristaux liquides.

Chose impossible à faire avec les Vivado, Diamond et autres Quartus.

LCD sur Tang Nano

C’est encore très expérimental

Comme nous venons de le voir, le projet Apicula est encore très expérimental. Cependant, tous les ingrédients sont là et la preuve de fonctionnement est faite. Donc, à condition de mettre un minimum les mains dans le cambouis, on peut désormais générer des bitstreams pour les GW1Nx avec des outils open source.

Super ! Comment je fais pour contribuer ?

Autant d’enthousiasme fait plaisir à voir. Pour contribuer, le mieux est d’acquérir l’une des deux cartes citées dans cette dépêche et d’installer le logiciel officiel de Gowin.
Ensuite, différentes commandes permettant de mettre le pied à l’étrier de l’ingénierie inverse sont données sur le README.md du projet Apicula.

Sinon, n’hésitez pas à laisser une issue sur le projet GitHub ou d’interpeller Pepijn directement sur le « silo social Twitter ». Pepijn est super content de voir des gens s’intéresser au projet et répond très vite.

Aller plus loin

OrangeCrab

Ça y est, la carte à base d’ECP5 tant attendu est enfin disponible sur le site groupsget.

La carte qui est utilisable avec une chaîne de développement intégralement libre est constituée de:

  • 24kLut
  • 1008 Kb – Embedded Block RAM
  • 194 Kb – Distributed RAM
  • 28 – 18×18 Multipliers
  • PLLs: 2
  • Internal oscillator
  • 1Gbits DDR
  • Full-speed (12Mbit) USB with a direct connection to the FPGA
  • 128Mbit QSPI FLASH Memory
  • MicroSD socket
  • SAR ADC, external RC / input comparator of FPGA
  • Battery voltage sensing

Le tout pour des dimensions rikiki de 22.86mm x 50.8mm (0.9″ x 2.0″)

Ça fait quelques mois déjà que tout le monde l’attendait. Elle est disponible pour $99 à l’achat dès aujourd’hui.

Test your Chisel design in python with CocoTB

Chisel is a hardware description language embedded in Scala language. Compared to VHDL/Verilog Chisel it’s a high-level language. With Chisel it’s easier to parametrize and to abstract your hardware. It’s the language used for all SiFive RISC-V cores and for Google Edge TPU.

What’s great with chisel that it generate Verilog sources for synthesis. And we can use this Verilog generated design for simulation or formal prove.

Simulation can be done in Scala with chisel.testers. But this tester is mostly under development project for the moment. And there is no test library for common busses and devices like SPI, Wishbone, AXI, PWM, …

CocoTB is a cosimulation testbench framework written in Python. Main advantage of CocoTB is that you write your testbench stimulis in python language. Python is really comfortable programming language. The other advantage of using CocoTB is that there is a growing library of modules available to test devices like SPI, Wishbone, USB, uart, … And its easier to use a library than to reinvent the wheel.

Then, let’s write Chisel testbench with CocoTB !

As an example we will use the ChisNesPad project (yes, same as formal prove article).

The directory structure is following :

/
|-- build.sbt  <- scala build configuration
|-- src/   <- all chisel sources
|   |-- main/
|       |-- scala/
|           |-- chisnespad.scala <- Chisel module we will test
|           |-- snespadled.scala <- "top" module to test with
|                                    tang nano (gowin)
|-- formal/   <- formal directory 
|-- platform/ <- some usefull files for synthesis with 
|                final platform (gowin).
|-- cocotb/   <- python cocotb tests
    |-- chisnespad/ <- test for chisnespad core
    |   |-- Makefile    <- makefile to compile and launch simulation
    |   |-- test_chisnespad.py <- cocotb stimulis
    |-- snespadled/ <- test for «top» project that toggle leds
        |              when push buttons
        |-- Makefile
        |-- test_snespadled.py

To launch tests we needs following dependencies :

  • Icarus Verilog : for simulation
  • Python 3: This example use python 3.7. It can be compiled and used with virtualenv
  • sbt: The scala build system
  • Chisel : The official website explain how to use it
  • CocoTB: and of course it need CocoTB that can be installed with python (python -m pip install cocotb)
  • cocotbify: python package included in repository chisverilogutil. Its used to inject some Verilog code in top module to generate VCD traces.
  • fpgamacro: it’s a Chisel library used to instantiate some fpga-specific RawModule. In our case it’s for ResetGen block.
  • gtkwave: VCD waveforms viewer.

Once all dependencies installed we can clone the chisNesPad project :

$ git clone https://github.com/Martoni/chisNesPad.git

Then launch simulation :


$ cd chisNesPad/cocotb/chisnespad/
$ make 
make[1]: Entering directory '/home/fabien/myapp/chisNesPad/cocotb/chisnespad'

[...] lots of compilation lines [...]

/myapp/chisNesPad/cocotb/chisnespad/build/libs/x86_64:/usr/local/lib:/usr/local/lib:/usr/local/lib:/usr/local/lib:/usr/local/lib MODULE=test_chisnespad \
        TESTCASE= TOPLEVEL=ChisNesPad TOPLEVEL_LANG=verilog COCOTB_SIM=1 \
        /usr/local/bin/vvp -M /home/fabien/myapp/chisNesPad/cocotb/chisnespad/build/libs/x86_64 -m gpivpi sim_build/sim.vvp   
     -.--ns INFO     cocotb.gpi                                  gpi_embed.c:103  in embed_init_python               Using virtualenv at /home/fabien/pyenv/pyenv37/bin/python.
     -.--ns INFO     cocotb.gpi                                GpiCommon.cpp:91   in gpi_print_registered_impl       VPI registered
     0.00ns INFO     Running tests with Cocotb v1.2.0 from /home/fabien/pyenv/pyenv37/lib/python3.7/site-packages
     0.00ns INFO     Seeding Python random module with 1583180134
     0.00ns INFO     Found test test_chisnespad.always_ready
     0.00ns INFO     Found test test_chisnespad.double_test
     0.00ns INFO     Found test test_chisnespad.simple_test
     0.00ns INFO     Running test 1/3: always_ready
     0.00ns INFO     Starting test: "always_ready"
                     Description: None
VCD info: dumpfile ChisNesPad.vcd opened for output.
401300.00ns INFO     Test Passed: always_ready
401300.00ns INFO     Running test 2/3: double_test
401300.00ns INFO     Starting test: "double_test"
                     Description: None
436000.00ns INFO     Value read CAFE
470420.00ns INFO     Value read DECA
471440.00ns INFO     Test Passed: double_test
471440.00ns INFO     Running test 3/3: simple_test
471440.00ns INFO     Starting test: "simple_test"
                     Description: None
506140.00ns INFO     Value read CAFE
507160.00ns INFO     Test Passed: simple_test
507160.00ns INFO     Passed 3 tests (0 skipped)
507160.00ns INFO     *************************************************************************
                     ** TEST                          PASS/FAIL  SIM TIME(NS)  REAL TIME(S)  RATIO(NS/S) **
                     *************************************************************************
                     ** test_chisnespad.always_ready    PASS       401300.00          2.78    144519.92  **
                     ** test_chisnespad.double_test     PASS        70140.00          0.49    143736.56  **
                     ** test_chisnespad.simple_test     PASS        35720.00          0.25    144120.85  **
                     **************************************************************************************
                     
507160.00ns INFO     *************************************************************************************
                     **                                 ERRORS : 0                                      **
                     *************************************************************************************
                     **                               SIM TIME : 507160.00 NS                           **
                     **                              REAL TIME : 3.52 S                                 **
                     **                        SIM / REAL TIME : 144276.59 NS/S                         **
                     *************************************************************************************
                     
507160.00ns INFO     Shutting down...
make[1]: Leaving directory '/home/fabien/myapp/chisNesPad/cocotb/chisnespad'

(Note : I can’t find how to change width of code text in this #*/% wordpress )

And we can see wave form with gtkwave:

$ gtkwave ChisNesPad.vcd
Waveform of CocoTB stimulis for NES Pad controller

Let’s see what happen

All commands are described in the Makefile in directory chisNesPad/cocotb/chisnespad/.

Chisel Module

The Chisel Module we test here is in directory src/main/scala/chisnespad/ and is named chisnespad.scala with following interfaces :

class ChisNesPad (val mainClockFreq: Int = 100,
                  val clockFreq: Int = 1,
                  val regLen: Int = 16) extends Module {
  val io = IO(new Bundle{
    /* SNES Pinout */
    val dclock = Output(Bool())
    val dlatch = Output(Bool())
    val sdata  = Input(Bool())
    /* read/valid output */
    val data = Decoupled(Output(UInt(16.W)))
  })
//...
}

The scala verilog generator driver is given at the end of file :

object ChisNesPad extends App {
  println("Generating Verilog sources for ChisNesPad Module")
  chisel3.Driver.execute(Array[String](), () => new ChisNesPad)
}

This object will be called by SBT following command:

$ sbt "runMain chisnespad.ChisNesPad"

Generated Verilog

This will generate the Verilog file named ChisNesPad.v in root directory. With following interfaces :

module ChisNesPad(
  input         clock,
  input         reset,
  output        io_dclock,
  output        io_dlatch,
  input         io_sdata,
  input         io_data_ready,
  output        io_data_valid,
  output [15:0] io_data_bits
);
//...
endmodule

As we can see, all bundled ports are kept but with little modification : dot ‘.’ are replaced by underscore ‘_’. clock and reset has been added and we can retrieve our decoupled signal io.data.{ready, valid, bits} -> io_data_{ready, valid, bits} .

CocoTB testbench

With these changes in mind, we can read/write our chisel ports signals with CocoTB.

CocoTB tests are described in file test_chisnespad.py. This file describe a class to store all method and data for testing ChisNesPad Module then list cocotb test function :

# main class for all test
class ChisNesPadTest(object):
    """
    """
    LOGLEVEL = logging.INFO
    PERIOD = (20, "ns")
    SUPER_NES_LEN = 16
    NES_LEN = 8

    def __init__(self, dut, reg_init_value=0xcafe, reg_len=16):
        if sys.version_info[0] < 3:
            raise Exception("Must be using Python 3")
        self._dut = dut
#...
# all tests
@cocotb.test()
def simple_test(dut):
    cnpt = ChisNesPadTest(dut)
    yield cnpt.reset()
    yield Timer(1, units="us")
    dut.io_data_ready <= 1
#...

@cocotb.test()#skip=True)
def double_test(dut):
    cnpt = ChisNesPadTest(dut)
    yield cnpt.reset()
#...

@cocotb.test()
def always_ready(dut):
    cnpt = ChisNesPadTest(dut)
    yield cnpt.reset()
#...

Here we see tree tests decorated with @cocotb.test(). The our module ChisNesPad is the Device Under Test (DUT) and is passed in test function arguments : dut.

To access input/output ports we just have to use dot on our dut object.

  • set io.data.ready to logic level ‘1’ :
    dut.io_data_ready <= 1
  • read io.data.bits
    vread = int(dut.io_data_bits)
  • We can also read register under the module or a submodule :
    countvalue = int(dut.countReg)

It’s also possible to write register under the module, but be careful of the race condition when you doing that. It can be re-written by simulation with 0-delay.

Get Waveform

All tests can be done with procedure describe above. But with Icarus as simulator we don’t get the waveforms.

It’s not easy to develop HDL without any waveform. To get waveform we can use another simulator that will generate the traces (mainly in VCD format) but Icarus is mature and free then it’s cheaper to use it.

The solution given in CocoTB documentation is to add following verilog code in top module :

`ifdef COCOTB_SIM
initial begin
  $dumpfile ("ChisNesPad.vcd");
  $dumpvars (0, ChisNesPad);
  #1;
end
`endif

With this $dumpX() function we will records all signals under the file named ChisNesPad.vcd. If we had to add this code by hand each time we re-generate verilog from Chisel module, it would quickly become painful.

That why we use the tool cocotbify, included in package chisverilogutils.

$ cocotbify
Usages:
cocotbify.py [options]
-h, --help             print this help
-v, --verilog          verilog filename to modify (filename is used 
                       as module name)
-o, --output filename  output filename

This tool will take a verilog source as input and generate an output with dumpvars code added for cocotb. In the example makefile the output name will be ChisNesPadCocotb.v. This file will be used by CocoTB and Icarus for simulation. VCD file can then be view with gtkwave:

$ gtkwave ChisNesPad.vcd
Simulation traces

Conclusion

As we can see, it’s perfectly possible to use CocoTB framework for testing Chisel components. CocoTB has more library test modules available than chisel.tester and we can code in Python. Python is used by lots of peoples through the world and is less scary than Scala or SystemVerilog for hardware engineers that develop digital hardware.