Simplifier la visualisation de chronogrammes

Posté par  (site web personnel) . Édité par Davy Defaud, palm123 et gUI. Modéré par Nÿco. Licence CC By‑SA.
Étiquettes :
35
13
nov.
2019
Matériel

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

AST

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

Suivre le flux des commentaires

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