RISC-V is an open standard instruction set architecture based on established RISC (Reduced Instruction Set Computer) principles.
Comparing to ARM and x86, a RISC-V CPU has the following advantages:
To create a minimum viable debugger on a RISC-V Processor, you need the following capabilities:
Memory Access: The ability to peek and poke memory. This means reading from and writing to specific memory locations. Alternatively, the ability to push instructions (through the program buffer) can also suffice if direct memory access is not possible or is restricted.
CPU Register Access: The ability to read and write CPU registers. Registers are special storage locations within the CPU that are used for calculations, control, and status information.
CPU Control: The ability to control the CPU. This involves several key actions:
Here’s a simplified diagram representing these core components:
+---------------------+ +-----------------------+
| Debugger Host | | Target Device |
+---------------------+ +-----------------------+
| |
| (Debug Interface) |
v v
+-----------------------+ +-----------------------+
| Memory Access |----->| Memory |
| (Read/Write) | | |
+-----------------------+ +-----------------------+
| |
| |
+-----------------------+ +-----------------------+
| CPU Register Access |----->| CPU Registers |
| (Read/Write) | | |
+-----------------------+ +-----------------------+
| |
| |
+-----------------------+ +-----------------------+
| CPU Control |----->| CPU (Halt, Step, etc.)|
| (Halt, Step, Reset) | | |
+-----------------------+ +-----------------------+
VexRiscv is an FPGA-friendly CPU core that implements the RISC-V instruction set architecture (ISA). Designed with flexibility and scalability in mind, it caters to a wide range of applications, from compact microcontroller-based systems to more complex multi-core configurations. The project is open-source under the MIT license, promoting community collaboration and adaptation.
VexRiscv is developed using SpinalHDL, a high-level hardware description language that enhances design modularity and reusability. This approach allows for a plugin-based architecture, enabling users to customize and extend the CPU’s capabilities to meet specific project requirements.
JTAG stands for Joint Test Action Group. JTAG support in VexRiscv is implemented through a combination of hardware and software components that adhere to the JTAG standard (IEEE 1149.1).
The diagram below describe how a host computer (with GDB) uses a JTAG interface to debug a VexRiscv CPU.
+-----------------+
| GDB (Host PC) |
+-------+---------+
|
v
+-----------------+ +--------------------+
| OpenOCD | --> | JTAG Dongle |
|(VexRiscv Driver)| | (Jtag Uart Cable) |
| (Host PC) | |(TCK, TMS, TDI, TDO)|
+-----------------+ | |
+--------------------+
|
v
+--------------------------------------------------------------+
| Target |
| +-----------------+ +-------------------+ |
| | JTAG Bridge |--->| VexRiscv Debug | |
| |(TAP, IR, DR, | | Plugin | |
| |State Machine) | |(SystemDebugBus) | |
| +-----------------+ +--------+----------+ |
| | (CPU Control, Registers) |
| v |
| +--------------------------+ |
| | VexRiscv CPU | |
| +--------------------------+ |
+--------------------------------------------------------------+
Debugging VexRiscv using JTAG involves connecting a JTAG dongle to the FPGA, and then using OpenOCD with a VexRiscv specific driver, to communicate with the JTAG bridge, which accesses the CPU debug module, all while GDB executes on the host machine.
Terminal 1 on Host :
openocd -f interface/ftdi/olimex-jtag-tiny.cfg -f target/riscv.cfg
Terminal 2 on Host :
riscv32-unknown-elf-gdb hello.elf
(gdb) target remote :3333
(gdb) load
(gdb) break main
(gdb) continue
(gdb) info registers
(gdb) next
(gdb) print message
(gdb) x/16x 0x80000000
The VexRiscv soft core supports minimal custom debugging by utilizing a debug plugin, which has two registers that allow for complete control of the CPU.
With these two registers, the core has all the necessary functions for a complete debugger, thus meeting the minimum requirements of a viable debugger.
The VexRiscv CPU has its own specific debugging implementation that is not compatible with the standard RISC-V Debug Specification (v1.0.0-rc4). Current deviation from the ratified RISC-V debug specification is listed below :
Current deviation from the ratified RISC-V debug specification is listed below :
| Ratified Debug Spec Feature | VexRiscv Support Status |
|---|---|
| Halt/Resume/Singlestep | ✅ Supported |
| Register (GPR/CSR) Read/Write | ✅ Via instruction injection |
| Memory Access via Abstract Commands | ❌ Not standard; done via instruction injection |
| Hardware Breakpoints / Triggers | ⚠️ Supports PC and load/store triggers, other modes not supported |
| Program Buffer | ✅ Supported |
| Multi-hart / Multi-DM support | ⚠️ Unable to halt/resume multiple harts with a single command |
| Alternate Transport Modules (non-JTAG) | ❌ Not implemented |
| DM/DTM layer per spec | ❌ Partial; custom version used, need to extend to supprt SWD |
Other than above features , below feature needs to be implemented :
SWD stands for Serial Wire Debug. The key differences between JTAG and SWD are :
The following conceptual diagram explains the general components and data flow involved in debugging via SWD:

In the diagram:
To add support for the RISC-V debug standard over SWD in VexRiscv below steps need to be followed:
Goal: Replace proprietary implementation with spec compliant implementation.
Approach: Build a new DebugModulePlugin alongside the existing DebugPlugin.scala (which remains as a fallback). The existing plugin’s register interface has zero overlap with the spec, so extending it is not viable. The useful pieces (~50 lines of halt/step/RVC handling) will be extracted and reused.
Suggested File Structure:
src/main/scala/vexriscv/plugin/
DebugPlugin.scala ← KEEP (existing, for backward compat)
DebugModulePlugin.scala ← NEW: DM registers, abstract commands
DebugTransportPlugin.scala ← NEW: JTAG DTM with standard dtmcs/dmi
DebugCsrPlugin.scala ← NEW: dcsr, dpc, dscratch CSRs on hart
TriggerPlugin.scala ← NEW: tselect, tdata1/2/3, tinfo, tcontrol
Dependency Chain:
Phase 1A: JTAG DTM + DMI Bus ← Foundation, no dependencies
│
▼
Phase 1B: Core DM (dmcontrol/dmstatus) ← Needs DMI bus
│
▼
Phase 1C: Abstract Register Access ← Needs DM registers
│
├──▼
│ Phase 1D: Debug CSRs (dcsr/dpc) ← Needs register access
│ │
│ ▼
│ Phase 1F: Trigger Module ← Needs dcsr for cause reporting
│
└──▼
Phase 1E: Memory Access ← Needs abstract command framework
| Phase | Milestone | GDB Capabilities | What Can be Verified |
|---|---|---|---|
| 1A | DTM + DMI bus | JTAG chain detected, nothing else | JTAG connectivity |
| 1B | DM control | Halt, resume, reset | Hello world UART output starts/stops with halt/resume |
| 1C | Register access | Read/write all GPRs and CSRs | See PC, SP, arguments; identify which function is executing |
| 1D | Debug CSRs | Single-step, halt cause, debug mode | Step through instructions one at a time; see why hart halted |
| 1E | Memory access | Full debug: load, break, step, print, backtrace, memory | Complete hello world debug session (software breakpoints) |
| 1F | Triggers | Hardware breakpoints, data watchpoints | Breakpoints on ROM/flash; watch variable changes |
Phases 1A-1D are incremental milestones that let you validate each layer before building the next. Phase 1E is the target for a complete hello world debugging experience with mainstream OpenOCD + GDB.
Detailed tasks and milestones planned for implementation of each Phase can be found at Standard Debug Spec Milestone.
Goal: Add Serial Wire Debug support alongside JTAG.
Tasks:
Goal: Integrate the new debug architecture into the top-level FPGA/System design.
Tasks:
Goal: Ensure the entire debug pipeline behaves per spec.
Tasks:
dmactive activation/deactivation sequencecmderr codes)dcsr.ebreakm/ebreaks/ebreaku combinationsdmode securityGoal: Make integration/usage clear for future development.
Tasks:
Goal: Add support for multiple cores and advanced features.
Tasks:
hartsel, hasel, hart arrays)dmcs2haltsum0-3 for efficient multi-hart halt pollingnextdm for multiple DMsitrigger, etrigger, tmexttrigger)mcontext/scontext/hcontext)