Skip to content

Commit

Permalink
Add some support for automatically displaying enums in gtkwave.
Browse files Browse the repository at this point in the history
The flow is basically like this:
1. An enum is written by the developer in a .sv file. The developer must
use verilator directives to generate its equivalent in C++.
2. The generated enum is parsed in Python. The names of the states and
corresponding values are extracted.
3. The names and values are written to a .gtkw save file. This save
file can be used by the developer to display an enum's strings
instead of values.

Add a README.md which explains how to use this in a step-by-step guide.
  • Loading branch information
magnmaeh committed Feb 16, 2024
1 parent 30c95d3 commit e0729ee
Showing 9 changed files with 198 additions and 4 deletions.
72 changes: 72 additions & 0 deletions enumparse.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import pycparser
import os
import argparse
import math

argparser = argparse.ArgumentParser()
argparser.add_argument('projectname', help='The name of the project')
argparser.add_argument('-f', '--project-folder', help='Project folder location', default='.')
args = argparser.parse_args()

INPUTFILE = args.project_folder + '/obj_dir/V' + args.projectname + '.h'
OUTPUTFOLDER = args.project_folder + '/enums'
try:
with open(INPUTFILE, 'r') as f:
lines = f.readlines()
except FileNotFoundError:
print('Not found:', INPUTFILE)
print('Verilator not run?')
exit(1)



in_enum = False
enumfile = ""

for line in lines:
if not in_enum and "enum" in line:
in_enum = True
if in_enum:
enumfile += line
if in_enum and "};" in line:
in_enum = False

parser = pycparser.c_parser.CParser()
ast = parser.parse(enumfile, filename='<none>')

def get_enums():
return ast.ext

def get_enum_statename_only(name):
# Verilator names the states like so:
# Top__DOT__State
# We only want the 'State' part
return name.split('__DOT__')[-1]

def get_enum_highest_value(enum):
return int(
enum.type.values.enumerators[-1].value.value[:-1]
)

def get_enum_value_binary_encoded(value, bits):
value = int(value[:-1])
return "{0:b}".format(value).zfill(bits)

def enum2txt(enum, bits):
txt = ""
name = get_enum_statename_only(enum.type.name)
for e in enum.type.values.enumerators:
txt += get_enum_value_binary_encoded(e.value.value, bits) + ' ' + e.name + '\n'
return txt, name

enums = get_enums()

if not os.path.exists(OUTPUTFOLDER):
os.mkdir(OUTPUTFOLDER)

for enum in enums:
highest = get_enum_highest_value(enum)
bits = int(math.log2(highest)) + 1
(enumcfgfile, name) = enum2txt(enum, bits)
with open (OUTPUTFOLDER + '/' + name + '.gtkw', 'w') as f:
f.write(enumcfgfile)
1 change: 1 addition & 0 deletions examples/States/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
enums/
13 changes: 13 additions & 0 deletions examples/States/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# How to add states to waveform

1. In your design (.sv file), create an enum in the same way as in the `States.sv` file. Remember the `/* verilator public */` comment, as `Verilator` will pick up on this and generate a C++ equivalent enum for you.
2. Run the `enumparse.py` Python script. This parses the C++ enum and generates a file that adheres to gtkwave's configuration files. The script will output one file per enum in your code under the `enums` folder. (In this project, the python script is automatically run with `make`.)
3. Select the signal you want to correlate with your enums. Make sure it is *blue*! `Data format` -> `Translate Filter File` -> `Enable and Select`. See Figure 1.
4. `Add filter to list` -> Add correct `.gtkw` file -> Make sure it is *blue* -> `Ok`. See Figure 2.
5. Once this is done, you can save the configuration (CTRL + S) and reuse it later (`gtkwave <tracefile> -a <config file>`). It is fine to track the config file on git if the signals included are somewhat stable.

Figure 1:
![Figure1](./docs/gtkwave-1.png)

Figure 2:
![Figure2](./docs/gtkwave-2.png)
22 changes: 22 additions & 0 deletions examples/States/States.sv
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module States (
input[31:0] in,
output[1:0] state_out
);
typedef enum logic [1:0] { ZERO, LOW, HIGH, MAX } HighLow /* verilator public */;
typedef enum logic [2:0] { INIT, DATA_RX, DATA_TX, STOPBIT, RESET } UART /* verilator public */;
HighLow state = ZERO;

always @(in) begin
if (in == 0)
state = ZERO;
else if (in <= 'h80000000)
state = LOW;
else if (in == 'hFFFFFFFF)
state = MAX;
else
state = HIGH;
end

assign state_out = state;

endmodule
Binary file added examples/States/docs/gtkwave-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/States/docs/gtkwave-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
66 changes: 66 additions & 0 deletions examples/States/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#include "VStates.h"
#include "verilated.h"

// Needed for waveform generation
#include <verilated_vcd_c.h>

#include <cassert>
#include <vector>
#include <iostream>

int main(int argc, char** argv) {
VStates* top = new VStates;
Verilated::commandArgs(argc, argv);

// For waveform generation
unsigned long tickcount = 0;
Verilated::traceEverOn(true);

// Initialization of trace
VerilatedVcdC *trace = new VerilatedVcdC;
top->trace(trace, 99);
trace->open("trace.vcd");

struct inout {
const uint32_t in;
VStates::States__DOT__HighLow out;
} inout[] = {
/**
* The enum is generated by Verilator from using
*
* /* verilator public *\/
*
* behind the typedef enum declaration. See the States.sv code.
*
*/
{ .in = 0x00000000, .out = VStates::States__DOT__HighLow::ZERO, },
{ .in = 0x00000001, .out = VStates::States__DOT__HighLow::LOW, },
{ .in = 0x00000010, .out = VStates::States__DOT__HighLow::LOW, },
{ .in = 0x7FFFFFFF, .out = VStates::States__DOT__HighLow::LOW, },
{ .in = 0x80000000, .out = VStates::States__DOT__HighLow::LOW, },
{ .in = 0x80000001, .out = VStates::States__DOT__HighLow::HIGH, },
{ .in = 0x80000002, .out = VStates::States__DOT__HighLow::HIGH, },
{ .in = 0x87236211, .out = VStates::States__DOT__HighLow::HIGH, },
{ .in = 0xFFFFFFFE, .out = VStates::States__DOT__HighLow::HIGH, },
{ .in = 0xFFFFFFFF, .out = VStates::States__DOT__HighLow::MAX, },
};

for (int i = 0; i < sizeof(inout) / sizeof(inout[0]); i++) {
if (Verilated::gotFinish()) break;

top->in = inout[i].in;

top->eval();
assert(top->state_out == inout[i].out);

trace->dump(10*tickcount++);
}

trace->dump(10*tickcount++);

trace->close();
delete trace;

delete top;
return 0;
}
15 changes: 15 additions & 0 deletions examples/States/makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
VERILATOR_ROOT := ../..
PROJECT_NAME := States
SOURCES := *.sv
SIMFILES := *.cpp
ENUMPARSE := ../../enumparse.py

include $(VERILATOR_ROOT)/verilator.mk

all:: enums

clean::
rm -rf enums

enums:
python3 $(ENUMPARSE) $(PROJECT_NAME)
13 changes: 9 additions & 4 deletions verilator.mk
Original file line number Diff line number Diff line change
@@ -10,6 +10,9 @@ HASHTAG := \#
# Just doing ${MODULE_FOLDER}/${PROJECT_NAME} is not sufficient, because there
# might be folders between the module folder and project folder. Like in the
# Hierachy example.
#
# The cryptographic code is extracted from:
# https://unix.stackexchange.com/questions/392528/extract-sub-directory-path-from-partially-known-directory
PATH_FROM_ROOT_TO_SRC = $(shell p=$(shell pwd); g=$${p${HASHTAG}${HASHTAG}*/${MODULE_FOLDER}}; echo $$g)

VERILATOR := $(VERILATOR_ROOT)/verilator-docker.sh
@@ -20,7 +23,7 @@ VERILATOR_ARGS_EMULATOR := --cc --build --exe --trace
VERILATOR_EMULATOR := obj_dir/V$(PROJECT_NAME)
PARSER ?= $(VERILATOR_ROOT)/default_parser.sh

all: run
all:: run

lint: $(SOURCES) $(GENERATED)
./$(VERILATOR) $(VERILATOR_ARGS) $(VERILATOR_ARGS_LINT) $(SOURCES) --top-module $(PROJECT_NAME)
@@ -29,12 +32,14 @@ $(VERILATOR_EMULATOR): lint | $(SOURCES) $(GENERATED)
./$(VERILATOR) $(VERILATOR_ARGS) $(VERILATOR_ARGS_EMULATOR) $(SIMFILES) $(SOURCES) > /dev/null

# https://stackoverflow.com/questions/17757039/equivalent-of-pipefail-in-dash-shell
run: $(VERILATOR_EMULATOR)
@mkfifo named_pipe
run:: $(VERILATOR_EMULATOR)
@if [[ ! -e named_pipe ]]; then \
mkfifo named_pipe; \
fi
@tee output.txt < named_pipe &
@./$(VERILATOR_EMULATOR) > named_pipe; ./$(PARSER) $(PROJECT_NAME) $$? output.txt
@rm named_pipe

clean:
clean::
@rm -rf obj_dir
@rm -f named_pipe

0 comments on commit e0729ee

Please sign in to comment.