Le développement avec des langages de description matériel, le (System)Verilog par exemple, nécessite très souvent de visualiser les chronogrammes afin de vérifier le comportement du composant en développement. Ces chronogrammes sont générés par un simulateur tel qu’Icarus et GHDL, pour les versions libres, ou bien encore par ModelSim, VCS et consorts, pour les versions propriétaires.
GTKWave est la référence dans le monde du logiciel libre pour afficher les chronogrammes, mais il existe également autant d’afficheurs que de simulateurs propriétaires. De très nombreux développeurs ont sans doute été confrontés au côté rébarbatif de l’insertion à la souris des signaux à observer. GTKWave, ainsi que les simulateurs propriétaires, embarquent un interpréteur de langage Tcl afin de faciliter l’édition des signaux à observer avec, bien sûr, une syntaxe différente à chaque fois…
J’ai donc décidé de créer le module Python Wavedisp permettant de décrire hiérarchiquement les signaux à observer, ainsi que de procéder à la génération de scripts d’affichage pour différents outils de visualisation, dont GTKWave.
Sommaire
Introduction
Wavedisp est un ensemble de classes Python pour vous aider à générer les scripts d’affichage pour différents outils de visualisation de chronogrammes. Au sein d’un fichier Wave, il vous permet de sélectionner facilement les signaux de vos composants à afficher.
En plus de déclarer les signaux, il permet également d’ajouter des groupes, de spécifier la hiérarchie des composants, d’ajouter des barres de division et aussi d’inclure d’autres fichiers Wave. Il prend en charge la génération des fichiers Tcl pour GTKWave, ModelSim (Mentor) et RivieraPro (Aldec).
Installation
Le module Wavedisp est disponible dans le dépôt PyPI :
python3 -m venv wavedisp-venv
pip install wavedisp
Description d’un fichier Wave
Généralement, les projets de description de matériel reposent sur une hiérarchie de modules et de tests. Supposons que nous avons réalisé une file d’attente (FIFO) qui utilise deux modules pour coder et décoder le code de Gray depuis et vers le code binaire.
La hiérarchie du projet est la suivante :
dclkfifolut_tb (test)
├── dclkfifolut (composant principal)
├── gray2bin (sous‑composant)
├── bin2gray (sous‑composant)
Un premier test
Les sous‐composants gray2bin et bin2gray disposent eux aussi de leurs propres fichiers de test et nous proposons de décrire dans un premier temps les fichiers Wave pour un de ces sous‑modules.
Le code ci-dessous montre la structure du fichier Wave (gray2bin.wave.py
) pour le composant gray2bin :
from wavedisp.ast import *
def generator():
blk = Block()
blk.add(Disp('gray', radix='hexadecimal'))
blk.add(Disp('bin', radix='hexadecimal'))
return blk
On remarque dans le code ci‑dessus qu’il faut en premier lieu importer les classes permettant d’instancier notre arbre de syntaxe (AST). Il faut également déclarer une fonction generator afin de retourner le point d’entrée de notre AST.
Le nœud Disp permet d’afficher un signal avec différentes propriétés :
- radix (hexadecimal, decimal, octal ou binary) ;
- color ;
- height.
Le nœud Block ne représentera rien dans le code généré mais permet de définir un ensemble de propriétés par défaut pour tous ses enfants. Charge à ces derniers de les redéfinir ou non. Tous les nœuds ayant des enfants rattachés ont le même comportement que Block.
Procédons maintenant à l’écriture du fichier Wave (gray2bin_tb.wave.py
) pour le test de notre module gray2bin :
from wavedisp.ast import *
def generator():
hier = Hierarchy('/gray2bin_tb')
inst = hier.add(Hierarchy('gray2bin_inst'))
inst.include('gray2bin_wave.py')
return hier
Dans ce dernier fichier nous avons ajouté un nœud déclarant la hiérarchie de notre test ainsi qu’une inclusion vers le fichier Wave gray2bin_wave.py
déclarant les signaux du module testé.
L’inclusion de fichiers Wave permet de séparer la déclaration des chronogrammes pour un module de test de ceux pour un module synthétisable. Cela permet donc de hiérarchiser nos fichiers Wave avec la même structure que celle du projet.
Nous pouvons maintenant produire un fichier dot représentant notre AST :
wavedisp -t dot -o wave.dot gray2bin_tb.wave.py
La commande suivante génère le script Tcl pour note visualiseur GTKWave :
wavedisp -t gtkwave -o wave.tcl gray2bin_tb.wave.py
Le contenu du fichier wave.tcl
est le suivant :
# Wavedisp generated gtkwave file
gtkwave::/Edit/Set_Trace_Max_Hier 0
gtkwave::addSignalsFromList [list {gray2bin_tb.gray2bin_inst.gray}]
gtkwave::/Edit/Data_Format/Hex {gray2bin_tb.gray2bin_inst.gray}
gtkwave::addSignalsFromList [list {gray2bin_tb.gray2bin_inst.bin}]
gtkwave::/Edit/Data_Format/Hex {gray2bin_tb.gray2bin_inst.bin}
gtkwave::/Edit/Set_Trace_Max_Hier 1
Un cas plus ambitieux
Nous pouvons maintenant décrire le fichier Wave pour notre projet complet, à savoir la file d’attente (FIFO) qui instancie plusieurs sous‑composants :
from wavedisp.ast import *
def generator():
block = Block()
block.add(Disp('rclk', radix='binary'))
block.add(Disp('rsrst', radix='binary'))
block.add(Disp('ren', radix='binary'))
block.add(Disp('rdata', radix='hexadecimal'))
block.add(Disp('rlevel', radix='hexadecimal'))
block.add(Disp('rempty', radix='binary'))
block.add(Disp('wclk', radix='binary'))
block.add(Disp('wsrst', radix='binary'))
block.add(Disp('wen', radix='binary'))
block.add(Disp('wdata', radix='hexadecimal'))
block.add(Disp('wlevel', radix='hexadecimal'))
block.add(Disp('wfull', radix='binary'))
read_ptr_gray = block.add(Hierarchy('read_ptr_gray')).add(Group('read_ptr_gray'))
read_ptr_gray.include('../../bin2gray/project/bin2gray_wave.py')
write_ptr_gray = block.add(Hierarchy('write_ptr_gray')).add(Group('write_ptr_gray'))
write_ptr_gray.include('../../bin2gray/project/bin2gray_wave.py')
read_ptr_bin = block.add(Hierarchy('read_ptr_bin')).add(Group('read_ptr_bin'))
read_ptr_bin.include('../../gray2bin/project/gray2bin_wave.py')
write_ptr_bin = block.add(Hierarchy('write_ptr_bin')).add(Group('write_ptr_bin'))
write_ptr_bin.include('../../gray2bin/project/gray2bin_wave.py')
return block
Le fichier Wave pour le test est séparé dans un fichier à part pour les raisons énoncées dans la section précédente :
from wavedisp.ast import *
def generator():
hier = Hierarchy('/dclkfifolut_tb', color='blue')
inst = hier.add(Hierarchy('DUT'))
inst.include('dclkfifolut_wave.py')
return hier
La commande suivante génère le script Tcl pour le simulateur ModelSim :
$ wavedisp -t modelsim -o wave.tcl dclkfifolut_tb_wave.py
Le contenu du fichier généré est le suivant :
# Wavedisp generated modelsim file
onerror {resume}
add wave -radix binary -color #0000ff {/dclkfifolut_tb/DUT/rclk}
add wave -radix binary -color #0000ff {/dclkfifolut_tb/DUT/rsrst}
add wave -radix binary -color #0000ff {/dclkfifolut_tb/DUT/ren}
add wave -radix hex -color #0000ff {/dclkfifolut_tb/DUT/rdata}
add wave -radix hex -color #0000ff {/dclkfifolut_tb/DUT/rlevel}
add wave -radix binary -color #0000ff {/dclkfifolut_tb/DUT/rempty}
add wave -radix binary -color #0000ff {/dclkfifolut_tb/DUT/wclk}
add wave -radix binary -color #0000ff {/dclkfifolut_tb/DUT/wsrst}
add wave -radix binary -color #0000ff {/dclkfifolut_tb/DUT/wen}
add wave -radix hex -color #0000ff {/dclkfifolut_tb/DUT/wdata}
add wave -radix hex -color #0000ff {/dclkfifolut_tb/DUT/wlevel}
add wave -radix binary -color #0000ff {/dclkfifolut_tb/DUT/wfull}
add wave -radix hex -color #0000ff -group {read_ptr_gray} {/dclkfifolut_tb/DUT/read_ptr_gray/bin}
add wave -radix hex -color #0000ff -group {read_ptr_gray} {/dclkfifolut_tb/DUT/read_ptr_gray/gray}
add wave -radix hex -color #0000ff -group {write_ptr_gray} {/dclkfifolut_tb/DUT/write_ptr_gray/bin}
add wave -radix hex -color #0000ff -group {write_ptr_gray} {/dclkfifolut_tb/DUT/write_ptr_gray/gray}
add wave -radix hex -color #0000ff -group {read_ptr_bin} {/dclkfifolut_tb/DUT/read_ptr_bin/gray}
add wave -radix hex -color #0000ff -group {read_ptr_bin} {/dclkfifolut_tb/DUT/read_ptr_bin/bin}
add wave -radix hex -color #0000ff -group {write_ptr_bin} {/dclkfifolut_tb/DUT/write_ptr_bin/gray}
add wave -radix hex -color #0000ff -group {write_ptr_bin} {/dclkfifolut_tb/DUT/write_ptr_bin/bin}
update
Conclusion
Le module Python Wavedisp permet donc simplement de gérer l’édition de vos chronogrammes dans des environnements complexes et variés, à la fois en termes de hiérarchie de composants, mais aussi en termes d’outils de simulation HDL.
Ce module est encore jeune et des retours sont les bienvenus, soit via des commentaires ou sur GitHub.
Aller plus loin
- Page GitHub du projet Wavedisp (126 clics)
- Page Web de Wavedisp (145 clics)
- Page Wikipédia anglaise de GTKWave (90 clics)
- Wavedisp sur PyPI (38 clics)
# J'adore!
Posté par ʭ ☯ . Évalué à 10.
C'est très plaisant ce retour de publications très techniques sur LinuxFr. J'y comprends pas grand chose, parce que c'est un domaine qui ne intéresse pas, mais ça ouvre mon esprit.
Et quelque part il y a certainement quelqu'un qui va devenir dans quelques années très bon sur ce sujet parce qu'il t'aura lu!
⚓ À g'Auch TOUTE! http://afdgauch.online.fr
# Parler de visualisation sans screenshot ?
Posté par benoar . Évalué à 6.
On veut un screenshot !
[^] # Re: Parler de visualisation sans screenshot ?
Posté par Christophe Clienti (site web personnel) . Évalué à 2.
Ce module ne fait pas de GUI :p
Mais voici un rendu de l'insertion des signaux grâce à wavedisp dans GTKWave: https://www.wavecruncher.net/static/images/gtkwave-example.png
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.