nickg / nvc

VHDL compiler and simulator
https://www.nickg.me.uk/nvc/
GNU General Public License v3.0
636 stars 80 forks source link

Feature request: gtkw file output #526

Closed amb5l closed 2 years ago

amb5l commented 2 years ago

A command line option e.g. --gtkw [filename] to output a gtkw file as a very useful starting point for viewing waveforms with ports/signals grouped by design unit, and listed in the order that they are declared in the source.

A barebones gtkw file can simply comprise a list of signals with dots separating hierarchy levels:

tbr6522.regclk
tbr6522.regclken
tbr6522.regrst
tbr6522.regcs
tbr6522.regrs[3:0]
tbr6522.regdw[7:0]
tbr6522.regdr[7:0]
tbr6522.regirq
tbr6522.uut_1.ddra[7:0]

Everything is lower case.

A useful upgrade would be to change the waveform colour for each design unit.

amb5l commented 2 years ago

Regarding waveform colour, the syntax is simply [color] n where 0 <= n <= 7. For example:

[color] 1
tbr6522.regclk
tbr6522.regclken
tbr6522.regrst
tbr6522.regcs
tbr6522.regrs[3:0]
tbr6522.regdw[7:0]
tbr6522.regdr[7:0]
tbr6522.regirq
[color] 2
tbr6522.uut_1.ddra[7:0]
nickg commented 2 years ago

You can dump VCD at the moment with --format=vcd. It's perhaps not as convenient as gktw but it is textual.

amb5l commented 2 years ago

OK that was a good suggestion. I've written a short Python script - below - which pulls the relevant data from the vcd file and writes a gtkw file. Because NVC preserves the source declared order of ports and signals when it writes the VCD (thankyou!) the result is really useful. I find the alphabetic listing of signals in the gtkwave UI really unhelpful. I can live with my script but the logic is simple, maybe it might make its way into NVC one day?!

vcd2gtkw

# vhdl2gtkw.py

import sys

if len(sys.argv) < 2 or len(sys.argv) > 3:
    print('usage: vcd2gtkw.py filename [level]')
    print('  filename = VCD file to process')
    print('  level = hierarchy levels to descend (0 = all, default)')
    sys.exit(1)
file_name = sys.argv[1]
level = 0
if len(sys.argv) == 3:
    level = int(sys.argv[2])
print("processing: "+file_name)
hier = []
scope = ""
scope_waves = []
color = 1
![vcd2gtkw](https://user-images.githubusercontent.com/33783239/186771871-7e4c0cd9-05f4-43e4-8b50-55a321ba718c.png)

with open(file_name, 'r') as f:
    while True:
        l = f.readline()
        if "$" in l:
            ll = l.split()
            cmd = ll[0][1:]
            if cmd == "enddefinitions":
                break
            elif cmd == "scope":
                if scope_waves:
                    print("-"+"/".join(hier))
                    color = (color%7)+1
                    for w in scope_waves:
                        print("[color] "+str(color))
                        print(w) 
                scope_waves = []
                scope = ll[2]
                hier.append(scope)
            elif cmd == "upscope":
                if scope_waves:
                    print("-"+"/".join(hier))
                    color = (color%7)+1
                    for w in scope_waves:
                        print("[color] "+str(color))
                        print(w) 
                scope_waves = []
                hier.pop()
                scope = hier[-1] if hier else ""
            elif cmd == "var":
                if level == 0 or len(hier) <= level:
                    scope_waves.append(".".join(hier)+"."+ll[4] if hier else ll[4])
amb5l commented 2 years ago

I've put the script in its own repo after a bit of tidying.

amb5l commented 2 years ago

Here's the same functionality in a bash script. It might fit - perhaps if the infrastructure you have for "nvc install" to run scripts was extended to support other (contributed) scripts? e.g. with "script" as an alias for "install".

#!/bin/bash
#
# vcd2gtkw.sh
#
# This script creates a .gtkw (waveform save file) from a .vcd (value change
# dump). Waves are added in the order they are found in the VCD, which normally
# matches the order of port, signal declarations etc in the design source. Wave
# colours are cycled as the hierarchy levels are traversed.
#
# Arguments:
#  $1 : vcd filename
#  $2 : gtkw filename
#  $3 : (optional) hierarchy levels to descend, 0 = all (default if omitted)

vcd_filename=$1
gtkw_filename=$2
if [ -z "$3" ]; then
    levels=0;
else
    levels=$3
fi
echo "vcd2gtkw.sh: $1 -> $2 (levels = $3)"
echo "[\*] vcd2gtkw.sh: $1 -> $2 (levels = $3)" > $gtkw_filename
declare -a hlevel=() # current hierarchy level
scope="" # current scope name
declare -a waves=() # waves gathered for current scope
color=7
wave_name=""
function join_with { local IFS="$1"; shift; echo "$*"; }
while read -r line; do # read file line by line
    if [[ ${line:0:1} == "\$" ]]; then
        IFS=' ' read -ra line_tokens <<< "$line" # split line into tokens
        cmd=${line_tokens[0]} # cmd = 1st token
        cmd="${cmd:1}" # strip $ from cmd
        if [ "$cmd" = "enddefinitions" ]; then # marks end of defs section of VCD
            break
        fi
        if [[ "$cmd" == *"scope"* ]]; then # change of scope
            if [ ${#waves[@]} -ne 0 ]; then # there are some waves to dump
                echo "-$(join_with // ${hlevel[*]})" >> $gtkw_filename # comment: current hierarchy level
                color=$(( (color%7)+1 )) # cycle colour
                # dump waves
                dn=""            # deferred wave name      } for building vectors
                di=""            #          wave index     }  from bits
                df=""            #          wave 1st index }
                declare -a dw=() #          wave list      }
                for w in ${waves[*]}; do # dump waves
                    if [ ${w:0-1} = "]" ]; then # wave is vector (whole or bit)
                        IFS='[' read -ra w_parts <<< "${w::-1}"
                        n=${w_parts[0]} # name
                        i=${w_parts[1]} # index
                        if [[ "$i" == *":"* ]]; then # whole vector
                            n=$w
                            i=""
                        fi
                    else # scalar
                        n=$w
                        i=""
                    fi
                    if [ "$di" != "" ]; then # previous wave was vector bit
                        if [ "$dn" != "$n" ]; then # this wave is not part of that vector
                            # dump previous wave vector
                            echo "#{$dn[$df:$di]} ${dw[*]}" >> $gtkw_filename
                            dw=()
                        else # this wave is part of that vector
                            dw+=("$w")
                        fi
                    else # no previous wave to consider
                        df=$i
                        dw=()
                    fi
                    if [ "$i" = "" ]; then # this wave is not vector bit
                        echo "[color] $color" >> $gtkw_filename
                        echo "$w" >> $gtkw_filename
                    fi
                    dn=$n
                    di=$i
                done
                if [ "$di" != "" ]; then # deal with final deferred wave
                    echo "#{$dn[$df:$di]} ${dw[*]}" >> $gtkw_filename
                    dw=()
                fi
                waves=() # reset wave list
            fi
        fi
        if [ "$cmd" = "scope" ]; then
            scope=${line_tokens[2]} # new scope name (descend hierarchy)
            hlevel+=("$scope")
        elif [ "$cmd" = "upscope" ]; then # ascend hierarchy
            unset 'hlevel[$(( ${#hlevel[@]}-1 ))]' # pop last element
            if [ ${#hlevel[@]} -ne 0 ]; then
                scope=${hlevel[-1]}
            else
                scope=""
            fi
        elif [ "$cmd" = "var" ]; then
            wave_name=${line_tokens[4]}
            if [[ "${line_tokens[5]}" == "["*"]" ]]; then # bus wire - concatenate index
                wave_name="$wave_name${line_tokens[5]}"
            fi
            # add signal if not too far down in hierarchy
            if [ $levels -eq 0 ] || [ ${#hlevel[@]} le $levels]; then
                waves+=("$(join_with . ${hlevel[*]}).$wave_name")
            fi
        fi
    fi
done <"$vcd_filename"
nickg commented 2 years ago

Thanks for the script. There's a contrib/ directory in the source which is meant for this kind of user-contributed script, but actually having tried it, I think this is useful enough to be implemented in the main program. I've added a --gtkw (alias -g) run option that does basically the same as the above. Could you try it?

amb5l commented 2 years ago

I tried the latest commit against UVVM's bitvis_uart example. Analysis and elaboration OK, but the run ended with this error:

nvc --std=08 --work=bitvis_uart -r uart_vvc_demo_tb --format=vcd --wave=wave.vcd
...
=======================================================
UVVM:      >> Simulation SUCCESS: No mismatch between counted and expected serious alerts
UVVM:      ====================================================================================================================================================================
UVVM:
UVVM:
UVVM:
UVVM:
UVVM: ID_LOG_HDR                    371672.5 ns  TB seq.                        SIMULATION COMPLETED
UVVM: -------------------------------------------------------------------------------------------------------------------------------------------------------------------------
** Note: STOP called
** Fatal: wave_dumper_free called before end of simulation
[00007ff7dab76440]
[00007ff7dac001f0]
[00007ff7dab72a12]
[00007ff7dac53832] vhpi_put_data+0x23942
[00007ff7dab713ae]
[00007ff7dab714e6]
[00007ffc210854e0] BaseThreadInitThunk+0x10
[00007ffc229e485b] RtlUserThreadStart+0x2b
nickg commented 2 years ago

@amb5l could you test again with the latest master? That should be fixed in d6170a2f.

amb5l commented 2 years ago

I checked out a slightly more recent master commit (a4a61c) and reran this test successfully - thankyou for incorporating this useful feature.