Five EmbedDev logo Five EmbedDev

An Embedded RISC-V Blog

GTKWave can be used for firmware debugging. This is obviously useful for co-simulation, but is also useful for debugging timing and integration problems.

In a previous post I showed how to create a vcd trace of a firmware run via a fork of the RISC-V ISA simulator.

In this post using https://github.com/five-embeddev/riscv-gtkwave I add:

There are a few methods to decode via GTKWave. The decoders in https://github.com/five-embeddev/riscv-gtkwave are using 3 methods:

Generated Translate Filter File.

The generate_csrs.py script uses pyvcd to generate enumeration files such as mcause.exception_code.gtkw from YAML CSR data csr.yaml.

Filter Process

RISC-V ISA Disassembly for GTKWave

The decode_inst program uses the disassembler defined by the RISC-V ISA Simulator in disasm.h to disassemble instruction words.

It also uses isa_parser to determine the ISA to dissassemble. The filename determines the ISA (via argv[0], e.g. decode_inst-rv32ic or decode_inst-rv64g.

e.g. the opcodes.hex file:

20010ae2
20010ae6
20010ae8
20010aea
20010aee
20010af0
20010af2
20010af4
20010af6
20010afa

Piped into decode_inst gives the following result.

$ cat opcodes.hex | ./decode_inst 
auipc   t0, 0x0
c.add   a3, t0
c.mv    t0, ra
jalr    ra, a3, -88
c.mv    ra, t0
c.addi  a5, -16
c.sub   a4, a5
c.add   a2, a5
bgeu    t1, a2, pc - 120
c.j     pc - 152

ELF Symbol Lookup for GTKWave

The decode_addr program converts hex address values to symbols names. It uses the global variable symbols or function symbols.

It is derived from elfloader.cc in the RISC-V ISA simulator.

As GTKWave cannot pass command line arguments DECODE_ELF must be set as an environment variable with the path to the ELF file to use.

$ cd src/
$ DECODE_ELF=../example/main.elf ./decode_addr
0
-
0000
-
0000000020010000
_enter
0000000080000008
mti_count

Signal Grouping and Vector Expansion and Combination

Registers signal vectors are expanded into bits, and fields combined as groups of bits.

The pyvcd class GTKWSave provides trace_bits for this. However it only provides a single bit function trace_bit.

The generate_csrs.py script subclasses GTKWSave and adds trace_bit_group for multibit fields. These can be interpreted via translate filter files.

Added trace_bit_group:

class UpdateGTKWSave(vcd.gtkw.GTKWSave):
    def trace_bit_group(
        self,
        lsb: int, msb: int,
        name: str,
        alias: str,
        color: Optional[Union[vcd.gtkw.GTKWColor, str, int]] = None,
        translate_filter_file: Optional[str] = None,
    ) -> None:

Iterate over fields in a register and add the trace_bit or trace_bit_group. There is one save file per CSR.

   gtkw = UpdateGTKWSave(rout)
   gtkw.comment(f"Fields for register {reg_name}")
   gtkw.begin_group(reg_name)
   with gtkw.trace_bits(reg_path):
       for fkey, fdata in reg_data['fields'].items():
           gtkw.comment(f"Field {reg_name}_{fkey}")
           fwidth=get_field_width(fdata)
           msb,lsb=get_field_range(fdata)
           if fwidth==1:
               gtkw.trace_bit(reg_width-lsb-1, 
                   name=f"{reg_path}",
                   alias=f"{reg_name}_{fkey}")
           else:
               translate_filter_file=...
               gtkw.trace_bit_group(
                   reg_width-msb-1, 
                   reg_width-lsb, 
                   name=f"{reg_path}",
                   alias=f"{reg_name}_{fkey}",
                   translate_filter_file=translate_filter_file)
   gtkw.end_group(reg_name)

Firmware Trace Example

Example of firmware trace

TODO

There are many limitations to these programs.

In a real world application other decoders that are useful are: