neorv32/docs/userguide/debugging_with_ocd.adoc

302 lines
13 KiB
Plaintext
Raw Permalink Normal View History

2024-02-24 08:25:27 +00:00
<<<
: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