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:
- Instruction Decoding according to the Spike disassembler.
- Bus Address decoding according to an elf file.
- CSR register decoding.
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.
- Translate Filter process.
- Signal Grouping and Vector Expansion and Combination
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
TODO
There are many limitations to these programs.
- 64bit/32bit distinctions are not
In a real world application other decoders that are useful are:
- MMIO Peripheral register decoding.
- External bus/device (I2C/SPI) register decoding.
- RTOS/OS instrumentation (e.g. current task, current memory map etc)
- Test name/assertion name.
- etc.