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
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: Extend current implementation with spec compliant implementation.
Detailed tasks and milestones planned for implementation can be found at Standard Debug Spec Milestone.
Goal: Add Serial Wire Debug support alongside JTAG. Reference Specs: ARM Debug Interface Architecture Specification ADIv6.0
The following Architecture diagram explains the components and data flow involved in debugging VexRiscv via SWD, showing both existing (implemented) and new (to be built) components with their implementation phases:
+===========================+
| Debug Host PC |
| GDB + OpenOCD |
| (transport select swd) |
+=============+=============+
|
| USB (CMSIS-DAP / J-Link / ST-Link)
v
+===========================+
| Debug Adapter |
| USB-to-SWD Converter |
| (probe hardware) |
+=============+=============+
|
| SWCLK + SWDIO (2 wires)
v
+=============================================================+
| Target System (FPGA) |
| |
| +-------------------------------------------------------+ |
| | SWD Interface [Phase 2A] | |
| | | |
| | SWD Pins ──> SWD Protocol State Machine | |
| | (SWCLK, - 8-bit packet request parser | |
| | SWDIO) - 3-bit ACK generator (OK/WAIT/FAULT) | |
| | - Turnaround & parity handling | |
| | - Line reset detection (50+ clocks) | |
| +---------------------------+---------------------------+ |
| | |
| v |
| +-------------------------------------------------------+ |
| | SW-DP (Debug Port) [Phase 2B] | |
| | | |
| | DP Registers: | |
| | +--------+ +-----------+ +--------+ | |
| | | DPIDR | | CTRL/STAT | | SELECT | | |
| | | (ID) | | (sticky | | (AP/ | | |
| | | | | errors) | | bank) | | |
| | +--------+ +-----------+ +--------+ | |
| | +--------+ +-----------+ | |
| | | RDBUFF | | ABORT | | |
| | | (read | | (error | | |
| | | buf) | | clear) | | |
| | +--------+ +-----------+ | |
| +---------------------------+---------------------------+ |
| | |
| | AP Access |
| v |
| +-------------------------------------------------------+ |
| | DMI Bus Adapter [Phase 2C] | |
| | | |
| | AP addr + data ──> DebugBus.cmd (DebugCmd) | |
| | DebugBus.rsp (DebugRsp) ──> RDATA + ACK | |
| | (cross-clock-domain via ccToggle) | |
| +---------------------------+---------------------------+ |
| | |
| | DebugBus |
| | (DebugCmd / DebugRsp) |
| v |
| +---------------------------+---------------------------+ |
| | Debug Module (DM) [EXISTING - DebugModule.scala] | |
| | | |
| | dmcontrol (0x10) | dmstatus (0x11) | |
| | abstractcs (0x16) | command (0x17) | |
| | data0-N (0x04+) | progbuf0-N (0x20+) | |
| | sbcs (0x38) | sbaddress0/sbdata0 | |
| | hartinfo (0x12) | haltsum0 (0x40) | |
| +-------------+----------------+------------------------+ |
| | | |
| DebugHartBus| | SBA (System Bus Access) |
| (halt/resume| | |
| signals, | v |
| dmToHart, | +-----------------------------------+ |
| hartToDm) | | Memory System Bus | |
| | | (Tilelink / AXI / Wishbone) | |
| | +-----------------------------------+ |
| v |
| +-------------------------------------------------------+ |
| | VexRiscv CPU [EXISTING - CsrPlugin.scala] | |
| | | |
| | Debug CSRs: Trigger Module: | |
| | dcsr (0x7B0) tselect (0x7A0) | |
| | dpc (0x7B1) tdata1 (0x7A1) - type 2 | |
| | tdata2 (0x7A2) - PC match | |
| | Debug Mode: tinfo (0x7A4) | |
| | entry/exit, | |
| | step FSM, Instruction injection, | |
| | ebreakm/s/u data CSR (0x7B4) | |
| +-------------------------------------------------------+ |
| |
+=============================================================+
Legend:
[NEW - Phase 2x] = Components to be built for SWD support (Task 2)[EXISTING - filename] = Already implemented componentsDebugBus = Same internal bus interface used by both JTAG DTM and SWD DTMKey Insight:
Phase 2A (SWD Interface) + Phase 2B (SW-DP) together are the SWD equivalent of the existing JTAG DTM. They handle the wire protocol and produce DebugBus commands — the same DebugCmd/DebugRsp interface that the JTAG DTM already produces.
The Debug Module and VexRiscv CPU need NO changes — they only see DebugBus commands and have no knowledge of whether they came from JTAG or SWD.
SoC-level change: Only DebugModuleFiber.scala needs a new withSwdTransport() method alongside existing withJtagTap(), so the SoC builder can choose which transport to instantiate (or both, with a mux at the DebugBus level).
JTAG Path (EXISTING): SWD Path (NEW):
============================== ==============================
JTAG TAP + dtmcs/dmi SWD Interface (Phase 2A)
(DebugTransportModuleJtag.scala) + SW-DP registers (Phase 2B)
- JTAG state machine - SWD protocol state machine
- IR: IDCODE(0x01), dtmcs(0x10), - 8-bit packet parser
dmi(0x11) - 3-bit ACK generator
- DMI op/address/data in dmi DR - DP regs: DPIDR, CTRL/STAT,
- dmiStat busy/error handling SELECT, RDBUFF, ABORT
| |
| both produce the |
| same interface | DMI Bus Adapter
v | (Phase 2C)
DebugBus (DebugCmd/DebugRsp) v
| DebugBus (DebugCmd/DebugRsp)
| |
+──────────────► same bus ◄────────────────+
|
v
Debug Module (DM) ◄── NO CHANGES NEEDED
(DebugModule.scala) (transport-agnostic)
|
DebugHartBus
|
v
VexRiscv CPU ◄── NO CHANGES NEEDED
(CsrPlugin.scala) (transport-agnostic)
Why Phase 2C (DMI Bus Adapter) is needed: In the JTAG path, the dmi shift register directly carries DMI address+data+op, so the translation to DebugBus is straightforward. In SWD, access goes through DP registers — the debugger writes SELECT to pick an AP address, then issues AP read/write operations. The adapter translates this DP/AP register access model into DebugBus read/write commands.
Suggested File: DebugTransportModuleSwd.scala (alongside existing DebugTransportModuleJtag.scala)
Spec Reference: ADIv6.0 Sec B4.1-B4.2
Tasks:
Spec Reference: ADIv6.0 Sec B2.2 (DP Reference Information)
Tasks:
DPIDR register (DP read, A[3:2]=0b00) — read-only device ID with manufacturer, version, min/revision fieldsCTRL/STAT register (DP read/write, A[3:2]=0b01, SELECT.DPBANKSEL=0x0) — sticky error flags: STICKYERR, STICKYCMP, STICKYORUN, WDATAERR; power control: CDBGPWRUPREQ/ACK, CSYSPWRUPREQ/ACK; ORUNDETECT enableSELECT register (DP write, A[3:2]=0b10) — DPBANKSEL (4 bits) + ADDR (AP address selection)RDBUFF register (DP read, A[3:2]=0b11) — read buffer for previous AP read resultABORT register (DP write, A[3:2]=0b00) — DAPABORT, STKCMPCLR, STKERRCLR, WDERRCLR, ORUNERRCLRSpec Reference: Custom bridge layer
Tasks:
DebugBus.cmd (same DebugCmd interface used by JTAG DTM)SELECT.ADDR + A[3:2] to DMI address for DebugBus.cmd.addressDebugBus.rsp to RDATA output and ACK generationccToggle pattern from JTAG DTM)DebugBus.cmd is not ready (DM busy)DebugBus.rsp.error is setGoal: Integrate the new debug architecture into the top-level FPGA/System design.
JTAG Specific Tasks:
SWD Specific Tasks:
DebugTransportModuleSwd component with io.swclk (input) + io.swdio (bidirectional) pinswithSwdTransport() method to DebugModuleFiber (alongside existing withJtagTap())DebugBus as JTAG DTM (select one at SoC level)transport select swd, adapter driver config)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: