302 lines
13 KiB
Plaintext
302 lines
13 KiB
Plaintext
<<<
|
|
:sectnums:
|
|
== Debugging using the On-Chip Debugger
|
|
|
|
The NEORV32 on-chip debugger ("OCD") allows _online_ in-system debugging via an external JTAG access port from a
|
|
host machine. The general flow is independent of the host machine's operating system. However, this tutorial uses
|
|
Windows and Linux (Ubuntu on Windows / WSL) in parallel running the upstream version of OpenOCD and the
|
|
RISC-V _GNU debugger_ `gdb`.
|
|
|
|
.TLDR
|
|
[TIP]
|
|
You can start a pre-configured debug session (using default `main.elf` as executable and
|
|
`target extended-remote localhost:3333` as gdb connection configuration) by using the **gdb** makefile target
|
|
(i.e. `make gdb`).
|
|
|
|
.OCD Hardware Implementation
|
|
[NOTE]
|
|
See datasheet section https://stnolting.github.io/neorv32/#_on_chip_debugger_ocd[On Chip Debugger (OCD)]
|
|
for more information regarding the actual hardware.
|
|
|
|
.OCD CPU Requirements
|
|
[NOTE]
|
|
The on-chip debugger is only implemented if the _ON_CHIP_DEBUGGER_EN_ generic is set _true_. Furthermore, it requires
|
|
the `Zicsr` and `Zifencei` CPU extension, which are always enabled by the CPU.
|
|
|
|
|
|
:sectnums:
|
|
=== Hardware Requirements
|
|
|
|
Make sure the on-chip debugger of your NEORV32 setup is implemented (`ON_CHIP_DEBUGGER_EN` generic = true). This
|
|
tutorial uses `gdb` to **directly upload an executable** to the processor. If you are using the default
|
|
processor setup _with_ internal instruction memory (IMEM) make sure it is implemented as RAM
|
|
(`INT_BOOTLOADER_EN` generic = true).
|
|
|
|
Connect a JTAG adapter to the NEORV32 `jtag_*` interface signals. If you do not have a full-scale JTAG adapter, you can
|
|
also use a FTDI-based adapter like the "FT2232H-56Q Mini Module", which is a simple and inexpensive FTDI breakout board.
|
|
|
|
.JTAG pin mapping
|
|
[cols="^3,^2,^2"]
|
|
[options="header",grid="rows"]
|
|
|=======================
|
|
| NEORV32 top signal | JTAG signal | FTDI port
|
|
| `jtag_tck_i` | TCK | D0
|
|
| `jtag_tdi_i` | TDI | D1
|
|
| `jtag_tdo_o` | TDO | D2
|
|
| `jtag_tms_i` | TMS | D3
|
|
| `jtag_trst_i` | TRST | D4
|
|
|=======================
|
|
|
|
[TIP]
|
|
The low-active JTAG tap reset `jtag_trst_i` signals is _optional_ as a reset can also be triggered via the TAP controller
|
|
issuing special commands. If `jtag_trst_i` is not connected make sure to pull the signal _high_.
|
|
|
|
|
|
:sectnums:
|
|
=== OpenOCD
|
|
|
|
The NEORV32 on-chip debugger can be accessed using the upstream version of OpenOCD. A pre-configured OpenOCD configuration
|
|
file is provided (`sw/openocd/openocd_neorv32.cfg`) that allows an easy access to the NEORV32 CPU.
|
|
|
|
[NOTE]
|
|
You might need to adapt `ftdi vid_pid`, `ftdi channel` and `ftdi layout_init` in `sw/openocd/openocd_neorv32.cfg`
|
|
according to your interface chip and your operating system.
|
|
|
|
[TIP]
|
|
If you want to modify the JTAG clock speed (via `adapter speed` in `sw/openocd/openocd_neorv32.cfg`) make sure to meet
|
|
the clock requirements noted in https://stnolting.github.io/neorv32/#_debug_module_dm[Documentation: Debug Transport Module (DTM)].
|
|
|
|
To access the processor using OpenOCD, open a terminal and start OpenOCD with the pre-configured configuration file.
|
|
|
|
.Connecting via OpenOCD (on Windows) using the default `openocd_neorv32.cfg` script
|
|
[source, bash]
|
|
--------------------------
|
|
N:\Projects\neorv32\sw\openocd>openocd -f openocd_neorv32.cfg
|
|
Open On-Chip Debugger 0.11.0 (2021-11-18) [https://github.com/sysprogs/openocd]
|
|
Licensed under GNU GPL v2
|
|
libusb1 09e75e98b4d9ea7909e8837b7a3f00dda4589dc3
|
|
For bug reports, read
|
|
http://openocd.org/doc/doxygen/bugs.html
|
|
Info : clock speed 1000 kHz
|
|
Info : JTAG tap: neorv32.cpu tap/device found: 0x00000000 (mfg: 0x000 (<invalid>), part: 0x0000, ver: 0x0)
|
|
Info : datacount=1 progbufsize=2
|
|
Info : Disabling abstract command reads from CSRs.
|
|
Info : Examined RISC-V core; found 1 harts
|
|
Info : hart 0: XLEN=32, misa=0x40901107
|
|
Info : starting gdb server for neorv32.cpu.0 on 3333
|
|
Info : Listening on port 3333 for gdb connections
|
|
Target HALTED.
|
|
Ready for remote connections.
|
|
Info : Listening on port 6666 for tcl connections
|
|
Info : Listening on port 4444 for telnet connections
|
|
--------------------------
|
|
|
|
OpenOCD has successfully connected to the NEORV32 on-chip debugger and has examined the CPU (showing the content of
|
|
the `misa` CSRs). The processor is halted and OpenOCD waits fot `gdb` to connect via port 3333.
|
|
|
|
|
|
:sectnums:
|
|
=== Debugging with GDB
|
|
|
|
.GDB + SVD
|
|
[TIP]
|
|
Together with a third-party plugin the processor's SVD file can be imported right into GDB to allow comfortable
|
|
debugging of peripheral/IO devices (see https://github.com/stnolting/neorv32/discussions/656).
|
|
|
|
This guide uses the simple "blink example" from `sw/example/demo_blink_led` as simplified test application to
|
|
show the basics of in-system debugging.
|
|
|
|
At first, the application needs to be compiled. We will use the minimal machine architecture configuration
|
|
(`rv32i`) here to be independent of the actual processor/CPU configuration.
|
|
Navigate to `sw/example/demo_blink_led` and compile the application:
|
|
|
|
.Compile the test application
|
|
[source, bash]
|
|
--------------------------
|
|
.../neorv32/sw/example/demo_blink_led$ make MARCH=rv32i USER_FLAGS+=-g clean_all all
|
|
--------------------------
|
|
|
|
.Adding debug symbols to the executable
|
|
[NOTE]
|
|
`USER_FLAGS+=-g` passes the `-g` flag to the compiler so it adds debug information/symbols
|
|
to the generated ELF file. This is optional but will provide more sophisticated debugging information
|
|
(like source file line numbers).
|
|
|
|
This will generate an ELF file `main.elf` that contains all the symbols required for debugging.
|
|
Furthermore, an assembly listing file `main.asm` is generated that we will use to define breakpoints.
|
|
|
|
Open another terminal in `sw/example/demo_blink_led` and start `gdb`.
|
|
|
|
.Starting GDB (on Linux (Ubuntu on Windows))
|
|
[source, bash]
|
|
--------------------------
|
|
.../neorv32/sw/example/demo_blink_led$ riscv32-unknown-elf-gdb
|
|
GNU gdb (GDB) 10.1
|
|
Copyright (C) 2020 Free Software Foundation, Inc.
|
|
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
|
|
This is free software: you are free to change and redistribute it.
|
|
There is NO WARRANTY, to the extent permitted by law.
|
|
Type "show copying" and "show warranty" for details.
|
|
This GDB was configured as "--host=x86_64-pc-linux-gnu --target=riscv32-unknown-elf".
|
|
Type "show configuration" for configuration details.
|
|
For bug reporting instructions, please see:
|
|
<https://www.gnu.org/software/gdb/bugs/>.
|
|
Find the GDB manual and other documentation resources online at:
|
|
<http://www.gnu.org/software/gdb/documentation/>.
|
|
|
|
For help, type "help".
|
|
Type "apropos word" to search for commands related to "word".
|
|
(gdb)
|
|
--------------------------
|
|
|
|
Now connect to OpenOCD using the default port 3333 on your machine.
|
|
We will use the previously generated ELF file `main.elf` from the `demo_blink_led` example.
|
|
Finally, upload the program to the processor and start debugging.
|
|
|
|
[NOTE]
|
|
The executable that is uploaded to the processor is **not** the default NEORV32 executable (`neorv32_exe.bin`) that
|
|
is used for uploading via the bootloader. Instead, all the required sections (like `.text`) are extracted from `mail.elf`
|
|
by GDB and uploaded via the debugger's indirect memory access.
|
|
|
|
.Running GDB
|
|
[source, bash]
|
|
--------------------------
|
|
(gdb) target extended-remote localhost:3333 <1>
|
|
Remote debugging using localhost:3333
|
|
warning: No executable has been specified and target does not support
|
|
determining executable automatically. Try using the "file" command.
|
|
0xffff0c94 in ?? () <2>
|
|
(gdb) file main.elf <3>
|
|
A program is being debugged already.
|
|
Are you sure you want to change the file? (y or n) y
|
|
Reading symbols from main.elf...
|
|
(gdb) load <4>
|
|
Loading section .text, size 0xd0c lma 0x0
|
|
Loading section .rodata, size 0x39c lma 0xd0c
|
|
Start address 0x00000000, load size 4264
|
|
Transfer rate: 43 KB/sec, 2132 bytes/write.
|
|
(gdb)
|
|
--------------------------
|
|
<1> Connect to OpenOCD
|
|
<2> The CPU was still executing code from the bootloader ROM - but that does not matter here
|
|
<3> Select `mail.elf` from the `demo_blink_led` example
|
|
<4> Upload the executable
|
|
|
|
After the upload, GDB will make the processor jump to the beginning of the uploaded executable
|
|
(by default, this is the beginning of the instruction memory at `0x00000000`) skipping the bootloader
|
|
and halting the CPU right before executing the `demo_blink_led` application.
|
|
|
|
[IMPORTANT]
|
|
After gdb has connected to the CPU, it is recommended to disable the CPU's global interrupt flag
|
|
(`mstatus.mie`, = bit #3) to prevent unintended calls of potentially outdated trap handlers. The global
|
|
interrupt flag can be cleared using the following gdb command:
|
|
`set $mstatus = ($mstatus & ~(1<<3))`. Interrupts can be enabled globally again by the following command:
|
|
`set $mstatus = ($mstatus | (1<<3))`.
|
|
|
|
|
|
:sectnums:
|
|
==== Software Breakpoints
|
|
|
|
The following steps are just a small showcase that illustrate a simple debugging scheme.
|
|
|
|
While compiling `demo_blink_led`, an assembly listing file `main.asm` was generated.
|
|
Open this file with a text editor to check out what the CPU is going to do when resumed.
|
|
|
|
The `demo_blink_led` example implements a simple counter on the 8 lowest GPIO output ports. The program uses
|
|
"busy wait" to have a visible delay between increments. This waiting is done by calling the `neorv32_cpu_delay_ms`
|
|
function. We will add a _breakpoint_ right at the end of this wait function so we can step through the iterations
|
|
of the counter.
|
|
|
|
.Cut-out from `main.asm` generated from the `demo_blink_led` example
|
|
[source, assembly]
|
|
--------------------------
|
|
00000688 <__neorv32_cpu_delay_ms_end>:
|
|
688: 01c12083 lw ra,28(sp)
|
|
68c: 02010113 addi sp,sp,32
|
|
690: 00008067 ret
|
|
--------------------------
|
|
|
|
The very last instruction of the `neorv32_cpu_delay_ms` function is `ret` (= return)
|
|
at hexadecimal `690` in this example. Add this address as _breakpoint_ to GDB.
|
|
|
|
[NOTE]
|
|
The address might be different if you use a different version of the software framework or
|
|
if different ISA options are configured.
|
|
|
|
.Adding a GDB software breakpoint
|
|
[source, bash]
|
|
--------------------------
|
|
(gdb) b * 0x690 <1>
|
|
Breakpoint 1 at 0x690
|
|
--------------------------
|
|
<1> `b` is an alias for `break`, which adds a _software_ breakpoint.
|
|
|
|
.How do _software_ breakpoints work?
|
|
[TIP]
|
|
Software breakpoints are used for debugging programs that are accessed from read/write memory (RAM) like IMEM. The debugger
|
|
temporarily replaces the instruction word of the instruction, where the breakpoint shall be inserted, by a `ebreak` / `c.ebreak`
|
|
instruction. Whenever execution reaches this instruction, debug mode is entered and the debugger restores the original
|
|
instruction at this address to maintain original program behavior. +
|
|
+
|
|
When debugging programs executed from ROM _hardware-assisted_ breakpoints using the core's trigger module have to be used.
|
|
See section <<_hardware_breakpoints>> for more information.
|
|
|
|
Now execute `c` (= continue). The CPU will resume operation until it hits the break-point.
|
|
By this we can move from one counter increment to another.
|
|
|
|
.Iterating from breakpoint to breakpoint
|
|
[source, bash]
|
|
--------------------------
|
|
Breakpoint 1 at 0x690
|
|
(gdb) c
|
|
Continuing.
|
|
|
|
Breakpoint 1, 0x00000690 in neorv32_cpu_delay_ms ()
|
|
(gdb) c
|
|
Continuing.
|
|
|
|
Breakpoint 1, 0x00000690 in neorv32_cpu_delay_ms ()
|
|
(gdb) c
|
|
Continuing.
|
|
--------------------------
|
|
|
|
.Hardcoded EBREAK Instructions In The Program Code
|
|
[TIP]
|
|
If your original application code uses the BREAK instruction (for example for some OS calls/signaling) this
|
|
instruction will cause an enter to debug mode when executed. These situation cannot be continued using gdb's
|
|
`c` nor can they be "stepped-over" using the single-step command `s`. You need to declare the `ebreak` instruction
|
|
as breakpoint to be able to resume operation after executing it. See https://sourceware.org/pipermail/gdb/2021-January/049125.html
|
|
|
|
|
|
:sectnums:
|
|
==== Hardware Breakpoints
|
|
|
|
Hardware-assisted breakpoints using the CPU's trigger module are required when debugging code that is executed from
|
|
read-only memory (ROM) as GDB cannot temporarily replace instructions by BREAK instructions.
|
|
|
|
From a user point of view hardware breakpoints behave like software breakpoints. GDB provides a command to setup
|
|
a hardware-assisted breakpoint:
|
|
|
|
.Adding a GDB hardware breakpoint
|
|
[source, bash]
|
|
--------------------------
|
|
(gdb) hb * 0x690 <1>
|
|
Breakpoint 1 at 0x690
|
|
--------------------------
|
|
<1> `hb` is an alias for `hbreak`, which adds a _hardware_ breakpoint.
|
|
|
|
[NOTE]
|
|
The CPU's trigger module only provides a single _instruction address match_ type trigger. Hence, only
|
|
a single `hb` hardware-assisted breakpoint can be used.
|
|
|
|
|
|
:sectnums:
|
|
=== Segger Embedded Studio
|
|
|
|
Software for the NEORV32 processor can also be developed and debugged _in-system_ using Segger Embedded Studio
|
|
and a Segger J-Link probe. The following links provide further information as well as an excellent tutorial.
|
|
|
|
* Segger Embedded Studio: https://www.segger.com/products/development-tools/embedded-studio
|
|
* Segger notes regarding NEORV32: https://wiki.segger.com/J-Link_NEORV32
|
|
* Excellent tutorial: https://www.emb4fun.com/riscv/ses4rv/index.html
|