Original Info
- Source: RISC-V Developer Community
- Author / ID: zevorn
- Original: https://ruyisdk.cn/t/topic/919
- Publication date: 2025-08-02
Abstract
Against the backdrop of RISC-V Debug Specification 1.0, the article explains concepts such as Debug Mode, DCSR, and Debug Module, and discusses why hardware debugging capabilities matter in conjunction with QEMU’s work to support the spec.
It is especially suitable for archiving under topics such as the RISC-V debug specification, QEMU debug support, and bare-metal/firmware debugging infrastructure.
Archival Note
This is an index for an externally published article; the full text has been imported below.
Main Text
The RISC-V Debug Specification 1.0 was finalized on February 21, 2025. I have recently been working on support for this spec in QEMU, so this is a good time to talk about it.
Overview
When a design moves from simulation to hardware implementation, users lose a significant amount of control over and visibility into system state, so built-in hardware debugging support becomes critical for low-level software and hardware debugging.
In general, when running a mature OS on RISC-V hardware, software can handle most debugging tasks. But during the boot stage, in no-OS environments such as bare-metal firmware, or when hardware fails, hardware debugging support is indispensable.
RISC-V’s debug specification adds a new mode for each Hart called Debug Mode, controlled by the DCSR register to determine which privilege modes can enter Debug Mode. To make it easier to manage a Hart’s debug state (such as halt/step/resume), the specification introduces a new hardware module called the DM (Debug Module). Its key characteristic is that it controls Hart behavior through non-intrusive debugging methods and exposes a unified debugging interface externally.
The DM connects to an external debugger through the DTM (Debug Transport Module), either over JTAG or via direct support for the GDB debugging protocol.
Below is the overall architecture of the RISC-V debug specification:
RISC-V debug architecture (text summary): the debugger or translator on the debug host talks to the target through the DTM. Inside the RISC-V platform, the DM exposes a DMI, drives reset/halt control, abstract commands, bus access, and the program buffer, while the hart side provides Debug Mode and optional trigger logic.
As shown, the basic debug flow is that the external debugger sends DMI requests through the DTM. After the DM responds, it executes the corresponding operation and abstract command. The abstract command is translated into a set of RISC-V instructions and executed by one Hart or a group of Harts, for example to access a Hart’s CSRs, and then returns a status code (if it times out or errors, the DTM must retry or notify the debugger).
The following diagram compares Hart state transitions in normal operation and in debug mode:
RISC-V debug state machine (text summary): normal execution can transition into halting or halted debug state through a halt request, ebreak, or a trigger event; resumereq or an external resume event returns the hart to running. Debug-module reset and hart reset reinitialize state, while abstract commands cycle through idle, running, busy, wait, and error states.
The modules discussed above are the required pieces of the debug spec. There are also optional modules, such as the Trigger Module shown in the Hart diagram, which is used to support hardware breakpoints and watchpoints.
Next, we will discuss the important modules. For register configuration, please read the manual for a more accurate reference; below, I mainly explain the workflow and principles. At the end, I will also talk about what to watch for when implementing this spec in QEMU.
Hart Debug Mode (Sdext Extension)
The Sdext extension provides the necessary support for an external debugger: Debug Mode and dedicated CSRs. This extension must work together with the DM; implementing it by itself is meaningless (so when implementing this extension in QEMU, you should check whether DM support is present).
Let’s start with Debug Mode. Its privilege is higher than M-mode and it is not constrained by PMP/PMA. Entry conditions are:
executing the
ebreakinstruction (triggered by configuringdcsr.ebreakm/s/u);an external debug request (the DM asserts
haltreq);a trigger-module event (such as a hardware breakpoint).
Executing dret returns to the original mode (restoring from the dpc CSR).
The new CSRs introduced by the extension are as follows:
| CSR | Address | Purpose |
|---|---|---|
dcsr | 0x7b0 | Debug control and status: cause, step, and ebreakm/s/u |
dpc | 0x7b1 | Program counter saved on entry to Debug Mode |
dscratch0/1 | 0x7b2-0x7b3 | Scratch registers used by the park loop |
Let’s begin with the dscratch0/1 CSRs.
Because the DM mainly uses non-intrusive debugging, controlling a Hart requires the DM to translate its intent into the corresponding RISC-V instruction sequence, such as halt and resume. A halt is implemented with a set of RISC-V instructions forming a park loop (you can think of it as a while loop that keeps checking a flag to decide whether to resume or execute debugger commands).
To avoid corrupting the Hart’s context while the park loop runs in Debug Mode, dscratch0/1 can be used to temporarily save the registers used by the park loop, and then restore them when exiting Debug Mode.
How is single-step execution implemented?
By setting dcsr.step, when the DM executes dret and the Hart returns to its normal state, the Hart will execute dcsr.step instructions and then enter Debug Mode again.
How are breakpoints implemented?
The external debugger can patch the instruction at the Hart’s debug address to ebreak. When the Hart executes ebreak, it will no longer enter a normal exception; instead, it enters Debug Mode. Whether a breakpoint triggers debug entry can be configured per privilege level through dcsr.ebreak<u/s/m>.
How are interrupts handled?
Debug Mode has higher priority than all interrupts (including NMI). During debugging, interrupts are suspended; after leaving debug mode they are handled according to priority. The dcsr.nmip flag indicates that an NMI occurred while debugging.
How does Debug Mode interact with privilege modes?
| Scenario | Behavior |
|---|---|
| Exception in Debug Mode | The exception is suppressed and recorded in dcsr.cause when applicable |
| Interrupt in Debug Mode | The interrupt is masked or deferred; mip/sip bits remain pending until exit |
| Nested debug request | Not supported; a new debug request is ignored while already in Debug Mode |
Debug Module
The DM is independent of the instruction-set architecture. It uses memory-mapped registers instead of instructions to provide control. Based on the abstract commands configured by the external debugger, the DM generates the corresponding RISC-V instruction sequences. In addition to the basic halt/resume execution commands, it must also support access to GPRs, FPRs, CSRs, and memory (with results written to the data0/1 registers).
Besides abstract commands, the DM also allows the external debugger to provide a custom instruction sequence after the abstract command finishes, by writing to the program buffer, which offers higher flexibility and extensibility.
Some important registers are listed below:
| Register | Address | Purpose |
|---|---|---|
dmcontrol | 0x10 | Global control: activate DM (dmactive), reset hart (ndmreset), and select hart (hartsel/hasel) |
dmstatus | 0x11 | Status: whether harts are halted (allhalted) and whether the debugger is authenticated (authenticated) |
abstractcs | 0x16 | Abstract-command state: busy flag and error code (cmderr) |
command | 0x17 | Writes an abstract command, such as a register or memory access |
data0-11 | 0x04-0x0F | Input/output data for abstract commands |
progbuf0-15 | 0x20-0x2F | Custom instruction buffer, up to 16 instructions |
sbaddress/sbdata | 0x39-0x3C | System-bus address/data registers |
A minimal DM implementation may support only GPR and basic CSR access (no memory access or program buffer), but it still needs to declare its compatibility (dmstatus.version identifies this).
When the DM issues a debug signal to a Hart, it generates a special interrupt telling the Hart to enter Debug Mode. This special interrupt is similar to exception handling, but it is not an exception.
After receiving the interrupt from the DM, the Hart needs to write the PC of the next instruction in the current context into the DPC register, update DCSR with the reason for entering Debug Mode, and then update the PC register so that it points to the start of the park-loop instruction block.
The park loop is implemented by the DM and is usually placed at the beginning of the address space.
When entering the park loop, the first step is to save context. Typically, the registers used inside the park loop are saved into dcsrach0/1, and then a loop repeatedly checks a flag to decide whether to go on executing abstract commands or to resume the pre-debug context.
How are abstract instructions executed?
Abstract commands must end with an ebreak instruction so that control returns to the park loop after execution.
How is Program Buffer executed?
The external debugger can configure the DM registers to choose whether the abstract command runs first and then the Program Buffer, or whether only the Program Buffer runs. Likewise, the last instruction in the Program Buffer must also be ebreak, but it is allowed to include a jump instruction that exits externally. The DM can also be configured to automatically execute ebreak after the Program Buffer finishes, which saves one instruction slot for the user.
If the end of the instruction sequence generated by the abstract command, or the end of the Program Buffer, is not ebreak, the DM loses control of the Hart and the behavior becomes unpredictable.
The DM also supports automatically triggering the previously configured abstract command when the external debugger accesses the DM’s data0/1 registers.
Simulating the RISC-V Debug Specification in QEMU
QEMU itself supports remote Hart debugging through gdbstub. If the QEMU accelerator uses TCG, the signals received by gdbstub are handled as TCG interrupts/exceptions. If gdbstub needs to pause a Hart, you can set cs->halted to 1.
Because the DM implementation is non-intrusive, it can be designed to be compatible with gdbstub. However, the following points need attention:
- When the DM issues an abstract command, it must notify the Hart to switch into Debug Mode through
qemu_irq; - The RISC-V instruction sequence corresponding to the abstract command in DM ROM changes every time, which is self-modifying code and requires the TB cache to be cleared;
- The DM state machine must be maintained carefully so that it matches Hart state;
- The DM’s
hartselmust be contiguous, but a Hart’smhartidmay be non-contiguous, so the mapping between the two needs attention.