Les 10 ans de Cocotb était l’occasion rêvée pour sortir la version 1.8 😉
Archives par mot-clé : cocotb
Et voila, ça fait 10 ans que Cocotb existe. On remercie toute l’équipe du projet qui a ainsi ré-enchanté la validation VHDL/Verilog.
Longue vie à Cocotb \o/
Un timeout dans cocotb
Avec Cocotb nous avons parfois des coroutines qui sont susceptible de rester «coincées» dans une boucle d’attente infinie. Si l’on y prête pas garde, on a vite fait de remplir son disque dur de traces totalement inutile.
# Une coroutine qui attend bien trop longtemps
async def too_long_coroutine(self):
await Timer(1, units="sec")
Pour éviter ce problème, l’idéal serait de pouvoir ajouter un «timeout» à l’appel de la coroutine susceptible de bloquer.
Ça tombe bien, cocotb a prévu un trigger pour ça : with_timeout()
from cocotb.triggers import with_timeout
await with_timeout(testcls.too_long_coroutine(), 100, "ns")
Sauf que python n’a pas trop l’air d’accord pour exécuter notre coroutine comme un trigger.
TypeError: All triggers must be instances of Trigger! Got: coroutine
C’est dommage, on perd beaucoup de l’intérêt de ce trigger !
La solution donnée par marlonjames est d’«empaquetter» la coroutine dans la fonction start_soon()
comme ceci :
await with_timeout(
cocotb.start_soon(testcls.too_long_coroutine()),
100, "ns")
De cette manière, le test s’interromps sur une levé d’interruption SimTimeoutError
et le test est marqué FAIL
sans ruiner notre disque dur.
raise cocotb.result.SimTimeoutError
cocotb.result.SimTimeoutError
CocoTB 1.4.0, la maturité
[Dépêche publiée initialement sur LinuxFR]
C’est dans la soirée du 8 juillet que l’annonce est tombée : la version 1.4.0 de CocoTB est sortie. Cette nouvelle version est une belle évolution de Cocotb avec une bonne intégration dans le système de paquets de Python ainsi que l’abandon de la prise en charge de Python 2. On peut aujourd’hui dire que CocoTB est une alternative sérieuse pour écrire ses bancs de test HDL.
Sommaire
Mais qu’est‑ce que c’est ?
CocoTB est une bibliothèque de cosimulation permettant d’écrire (en Python) des bancs de test pour la simulation numérique HDL (Hardware Description Language). Historiquement, les deux langages de descriptions HDL que sont Verilog et VHDL embarquent tout le nécessaire pour écrire des stimuli permettant de tester le composant en simulation. Cela permet d’avoir un seul langage pour décrire le composant et le tester. Le simulateur exécutera tout cela sans problème.
Mais cela amène beaucoup de confusion entre la partie du langage utilisable pour la simulation uniquement et la partie « description du matériel ». Dans le cas de la partie « matériel » on parle alors de code « synthétisable ». Cette confusion entre du code synthétisable ou non est source de grandes frustrations au moment de passer à la synthèse. En effet, cette belle structure de données que l’on aura développée et testée aux petits oignons s’écroulera au moment de la synthèse quand on se rendra compte que le code n’est pas synthétisable. Il faudra tout reprendre.
Une des idées derrière CocoTB est donc de changer de langage pour la simulation, comme cela les choses sont claires : on utilise le VHDL ou Verilog pour la partie du composant qui est synthétisable, et on passe au Python pour le banc de test. Ce n’est pas le seul logiciel à proposer ce genre d’approche. Avec Verilator, par exemple, on va écrire toute la partie banc de test en C++ ou en SystemC. La partie synthétisable sera écrite en Verilog et convertie en un objet C++ par Verilator.
La seconde idée de CocoTB est de ne pas réinventer la roue en réécrivant un énième simulateur HDL. Ce qui évite également d’avoir à choisir son camp entre Verilog et VHDL ou les deux (simulation mixte). Non, CocoTB va se contenter de piloter les simulateurs disponibles sur le marché. Les simulateurs libres que sont GHDL, Icarus et Verilator sont naturellement pris en charge, même si dans le cas de Verilator c’est très récent. La plupart des simulateurs commerciaux le sont également, ce qui est un argument pour l’introduire dans son bureau d’étude. En effet, les managers sont en général moyennement chauds pour virer un logiciel acquis à grands frais. Et l’on peut continuer à profiter des interfaces proposées par notre simulateur habituel pour exécuter le simulateur, visionner les chronogrammes, faire de la couverture de tests, etc.
La version 1.4 de CocoTB introduit la gestion complète du simulateur Aldec Active HDL qui vient s’ajouter aux classiques de Cadence et de Mentor, Modelsim…
Les changements dans le code
Un gros changement initié depuis quelque versions déjà est l’utilisation du mot clef async
en lieu et place du yield
et du décorateur @coroutine
. Python 3 gérant désormais l’asynchronisme, CocoTB l’utilise et le prend désormais complètement en charge. L’exemple donné dans le courriel de la publication est le suivant :
@cocotb.test()
async def my_first_test(dut):
"""Try accessing the design."""
dut._log.info("Running test!")
for cycle in range(10):
dut.clk <= 0
await Timer(1, units='ns')
dut.clk <= 1
await Timer(1, units='ns')
dut._log.info("Running test!")
Qui se serait écrit comme cela dans « l’ancien système » :
@cocotb.test()
def my_first_test(dut):
"""Try accessing the design."""
dut._log.info("Running test!")
for cycle in range(10):
dut.clk <= 0
yield Timer(1, units='ns')
dut.clk <= 1
yield Timer(1, units='ns')
dut._log.info("Running test!")
Cette écriture restant cependant valable.
Le gros avantage de cette nouvelle écriture est de ne plus avoir a réinventer la roue avec des décorateurs inutiles. Avec async
et await
, on utilise des interfaces intégrées à Python 3, ce qui évite tout un travail de gestion.
Installation
CocoTB est, depuis maintenant un certain temps, partie intégrante du système de gestion de paquets de Python pip. Et vous pouvez dès à présent l’installer sur votre système via la commande pip install
:
$ python -m pip install cocotb
# Pour celles et ceux qui ont installé la version précédente n’oubliez pas le --upgrade
$ python -m pip install --upgrade cocotb
Et on peut vérifier la version grâce à la commande cocotb-config
suivante :
cocotb-config --version
1.4.0
En plus de votre composant écrit en VHDL ou Verilog, deux fichiers supplémentaires sont nécessaires pour tester avec CocoTB : le Makefile et le script Python de test proprement dit.
Avec cette nouvelle version, le Makefile a encore été simplifié puisqu’il n’est plus nécessaire d’intégrer les en‑têtes C++. Ces en‑têtes sont nécessaires pour compiler les interfaces VPI/VHPI/FLI qui permettent de piloter les simulateurs. On compile désormais cette partie à l’installation de CocoTB. Dans les précédentes version, cette compilation ce faisait à chaque fois que l’on relançait les tests.
Si l’on prend l’exemple de l’antirebond en Verilog du Blinking Led Project, nous avons le Makefile suivant :
SIM=icarus # Nom du simulateur
export COCOTB_REDUCED_LOG_FMT=1 # Pour avoir des traces de log qui rentre dans l’écran
VERILOG_SOURCES = $(PWD)/../src/button_deb.v # Inclusion des fichiers HDL
TOPLEVEL=button_deb # Nom de l’entité
MODULE=test_$(TOPLEVEL) # Nom du script Python de test
include $(shell cocotb-config --makefile)/Makefile.sim
L’exemple est un composant permettant de ne pas compter les rebonds d’un bouton comme des appuis successifs.
Le script de test en Python se trouve dans le dépôt Git du projet et se nomme test_buton_deb.py
. Pour le lancer, il suffit de se rendre dans le répertoire blp/verilog/cocotb/
et de taper make
:
$ make
[...]
0.00ns INFO Running test!
0.00ns INFO freq value : 95000 kHz
0.00ns INFO debounce value : 20 ms
0.00ns INFO Period clock value : 10000 ps
0.02ns INFO Reset complete
Un fichier de traces (chronogrammes) button_deb.vcd
au format VCD est créé. Il peut être visionné en « temps réel » alors même que la simulation n’est pas terminée, grâce au visualiseur gtkwave
:
$ gtkwave button_deb.vcd
Une organisation qui tourne
Le projet CocoTB est chapeauté par la FOSSi foundation qui fournit le « chef de projet » Philipp Wagner ainsi que des moyens financiers pour faire tourner des machines virtuelles de tests ainsi que pour payer les licences des simulateurs commerciaux.
Les statistiques de modification de cette version sont les suivantes :
- 346 fichiers modifiés, 14 012 insertions (+), 10 356 suppressions (−) ;
- 554 commits ;
- 31 contributeurs ;
- 2 nouveaux mainteneurs : Colin Marquardt et Kaleb Barrett.
Ces chiffres montrent que CocoTB est un projet qui fédère désormais une grosse communauté. C’est un projet mature qui compte dans le paysage des logiciels libres pour le matériel (FPGA et ASIC).
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
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
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.
Cocotb Tips
Some tips for python HDL test module Cocotb.
Read and Write signal
Write:
clk.value = 1
dut.input_signal <= 12
dut.sub_block.memory.array[4] <= 2
Read:
count = dut.counter.value
print(count.binstr)
print(count.integer)
print(count.n_bits)
print(int(dut.counter))
See it under the official documentation.
Yielding a coroutine in a select list fashion
Question asked on stackoverflow.
Using latest python version with virtualenv
If you compile python yourself, don’t forget to add option --enable-shared
at configure time (./configure --enable-shared
)
$ virtualenv --python=/usr/local/bin/python3.7 ~/envp37
$ source ~/envp37/bin/activate
$ python -m pip install cocotb
Do not forget to re-source your environnement each time you open a new terminal :
$ source ~/envp37/bin/activate
Logging messages and main test class template
This is a template for declaring a class used for test in function @cocotb.test() :
import logging
from cocotb import SimLog
...
class MyDUTNameTest(object):
""" Test class for MyDUTName"""
LOGLEVEL = logging.INFO
# clock frequency is 50Mhz
PERIOD = (20, "ns")
def __init__(self):
if sys.version_info[0] < 3: # because python 2.7 is obsolete
raise Exception("Must be using Python 3")
self._dut = dut
self.log = SimLog("RmiiDebug.{}".format(self.__class__.__name__))
self.log.setLevel(self.LOGLEVEL)
self._dut._log.setLevel(self.LOGLEVEL)
self.clock = Clock(self._dut.clock, self.PERIOD[0], self.PERIOD[1])
self._clock_thread = cocotb.fork(self.clock.start())
# ....
@cocotb.test()
def my_test(dut):
mdutn = MyDUTNameTest()
mdutn.log.info("Launching my test")
Conférence Cocotb ORCONF 2015
Une présentation de Cocotb à la conférence ORCONF 2015 du CERN.