-
-
Notifications
You must be signed in to change notification settings - Fork 14
Debugger
commit: 7609f4075a9f5cd7a83c65efb35ddc270a03f425
In this commit, the debugger is based on RISC-V External Debug Support Version 0.13.2. The most of the function is implemented. However, the riscv-test/debug has not been passed yet. The remained issues will be fixed in the future.
The debug solution is based on the model in GDB -> OpenOCD -> FT2232 -> Debugger -> Core
Independent of MMode, SMode, UMode in Privilege Level, DMode is an inner user invisible register which flags whether the core is in Debug Mode.
DCSR is a register for debuging, whoes fields afface the behavior of debug
-
ebreakm,ebreaks,ebreakurespectively affect the behavior ofebreakinstruction in three privilege levels: 0:Cause BreakPiont Exception 1:Enter Debug Mode -
causerecords the reason why Debug Mode is entered. There are 5 reason
- The softwave breakpoint causes by ebreak
- The hardwave breakpoint causes by trigger
- The unmaskable interrupt cause by haltreq
- Re-enter debug mode by stepping
- he unmaskable interrupt cause by resethaltreq(can merge in 3)
-
stepThe control field of stepping. The debug mode will be re-entered once the instruction is committed, if this field is set and the core is not in debug mode. -
prvRecord the privilege level while entering the debug mode. The privilege level will be updated according to this field when return from debug mode.
Record the pc while entering the debug mode and re-direct pc when return from debug mode. It's very simular to mepc.
Be aware, the softwave breakpoint causes by ebreak is implemented by modifying the instruction to ebreak at the porper address by OpenOCD. The instruction will be modified back after the breakpoint is reached. As a result, the instruction is not actually executed when the breakpoint is triggered. And the dpc should be updated to the address of this instruction.
Dscratch works as a small stack, which save and resume the value in Regfile temply while executing the Abstract Command
The number of dscratch is implementation dependent with Abstract Command.
Simalar to MRET, the core will return from debug mode, update the pc with dpc, update the Privilege Level with prv, after the DRET is executed.
Depending on the configuration of dcsr and Privilege Level, either a softwave exception or sofewave breakpoint will be caused by ebreak.
An unmaskable interrupt input should be provided by the core. Once the unmaskable interrupt comes, the pc will be re-direct to a pre-set address of Debug ROM, and fetch instruction from that peripheral.
The Debug Interrupt will keep asserting until the debugger comfirms that the core is halted.
As a bridge between hardwave and debug logic, DTM can access registers of DM with the register access from hardwave interface. The DTM may work in an independent clock domain, for the frequence is different from the core. The connection between DTM and DM may be a self-defined bus.
Base on IEEE Std 1149.1-2013, JTAG works as finite-state-machine. The next state is depend on the TMS, while the TCK driving the clock.
With the driven by TCK, one bit is shifted-out to TDO or one bit is shifted-in from TDI. A serial mode it is.
DMI works as a CrossBar between DTM and a numbers of DM. Also the timing path may cross the clock domain at DMI.
The cores enter the debug mode with the lead of DM, and make a specific behavior to inquire or modify the current state of the SoC. The number of cores, that DM can debug with is 1~2^20.
A fully implemented DM has 3 sets of Bus Interface:
- Self-define DMI Bus Port as slave interface
- A Bus Port as slave interface for instruction fetching and data accessing from cores
- A System Bus Port as master interface to access the whole memeory region of the SoC
A number of registers in DM can be accessed by DTM. By writing these registers and reading back, the DTM can instrcut DM to operate and get result back.
Once entering the debug mode, the cores make a specific behavior by fetching instructions from these region and exchange information with DM by reading or writing the specific region.
-
DEBUG_ROM_HALTED A write-only region for cores. The halted cores will read out its hart id and write it into this region to info
DMthat it's halted. -
DEBUG_ROM_GOING A write-only region for cores. The halted cores will read out its hart id and write it into this region to info
DMthat it has received the next instruction and is moving to GOING state. -
DEBUG_ROM_RESUMING A write-only region for cores. The halted cores will read out its hart id and write it into this region to info
DMthat it's leaving the debug mode. -
DEBUG_ROM_EXCEPTION A write-only region for cores. The halted cores will read out its hart id and write it into this region to info
DMthat an exception has been caused during debuging. -
WhereTo
WhereTo is an address-variable jal instruction. When Abstract Command should be executed, the implementation modifies the target address and the instruction flow will be led to:
- Abstract Command ROM
- Program Buff
- Abstract Command ROM
Abstract Command ROM is a small instruction region that can vary limited by DM for function of Abstract Command. It works as followed:
- Reading/Writing the Register-Files: The values will be exchange with
DATAbyload/storeinstructions - Reading/Writing the CSR Registers: The values in Integer Register will be protected in
dscratchtemporarily. Then the Integer Register andload/storeinstructions will be used to exchange the values between the CSR andDATA - Post-Execution:
Abstract Command ROMmay lead the instruction flow toProgram Buffif the feature ofPost-Executionis required - Access Memory: The values in Integer Register will be protected in
dscratchtemporarily. Then the Integer Register andload/storeinstructions will be used to exchange the values between the specific address andDATA
- Program Buff
Program Buff is a small RAM that can be both accessed by Private Memory Region of DMI and Public Memory Region of SoC. The DM can modify the instructions inside the Program Buff, then the cores will make more complex debugging behavior by fetching instruction from Program Buff.
The last instruction in Program Buff must be ebreak. The impebreak feild in dmstatus infos OpenOCD that whether the ebreak should be written by OpenOCD.
The avivable length of Program Buff should be determined by the implementation. The projbufsize feild in abstractcs infos OpenOCD that how much space can be used during operating the Quick Access. The Quick Access may be splited or failed if the Program Buff is too small to used.
-
DATA The
DATAcan be both accessed by Private Memory Region of DMI and Public Memory Region of SoC. The infomation exchanges betweenDMand the Cores throughDATA. -
DEBUG_ROM_FLAGS
A read-only field for the cores. Each hart has one-Byte here, and they will poll this region. The DM will modify this field if the hart is needed to operate(GOING, RESUMING).
- Debug ROM Debug ROM is a implementation-dependent pre-compile code region. The softwave code can be refered at riscv-isa-sim. The linker file must be be in line with the implementation.
Once the hart entering the debug mode, the entry will be arrived firstly.
At the infinite loop of halt, the hart will read out its HartID and write it into to the write-only register DEBUG_ROM_HALTED, which infos DM that the hart is halted. The field belongs to hart in DEBUG_ROM_FLAGS will be read out then, the next command from DM will be gotten.
-
going The
HartIDwill be written into the write-only registerDEBUG_ROM_GOING, which infosDMthat the hart is entering the GOING state, and the hart will go towheretofinally, at where it will be led to the other debug region. -
resume The
HartIDwill be written into the write-only registerDEBUG_ROM_RESUMING,which infosDMthat the hart is leaving the debug mode,and the hart will go toDRETfinally and resume from debug mode.
similarly, if an exception is caused during debuging, the HartID will be written into the write-only register DEBUG_ROM_EXCEPTION, which infos DM that the exception is caused. Then the ebreak will be used to re-direct instruction flow to infinite loop of halt, and waiting for the following command from DM.
// See LICENSE.SiFive for license details.
#include "riscv/encoding.h"
#include "riscv/debug_rom_defines.h"
.option norvc
.global entry
.global exception
// Entry location on ebreak, Halt, or Breakpoint
// It is the same for all harts. They branch when
// their GO or RESUME bit is set.
entry:
jal zero, _entry
resume:
jal zero, _resume
exception:
jal zero, _exception
_entry:
// This fence is required because the execution may have written something
// into the Abstract Data or Program Buffer registers.
fence
csrw CSR_DSCRATCH, s0 // Save s0 to allow signaling MHARTID
// We continue to let the hart know that we are halted in order that
// a DM which was reset is still made aware that a hart is halted.
// We keep checking both whether there is something the debugger wants
// us to do, or whether we should resume.
entry_loop:
csrr s0, CSR_MHARTID
sw s0, DEBUG_ROM_HALTED(zero)
lbu s0, DEBUG_ROM_FLAGS(s0) // 1 byte flag per hart. Only one hart advances here.
andi s0, s0, (1 << DEBUG_ROM_FLAG_GO)
bnez s0, going
csrr s0, CSR_MHARTID
lbu s0, DEBUG_ROM_FLAGS(s0) // multiple harts can resume here
andi s0, s0, (1 << DEBUG_ROM_FLAG_RESUME)
bnez s0, resume
jal zero, entry_loop
_exception:
sw zero, DEBUG_ROM_EXCEPTION(zero) // Let debug module know you got an exception.
ebreak
going:
csrr s0, CSR_DSCRATCH // Restore s0 here
sw zero, DEBUG_ROM_GOING(zero) // When debug module sees this write, the GO flag is reset.
fence
fence.i
jalr zero, zero, %lo(whereto) // Debug module will put different instructions and data in the RAM,
// so we use fence and fence.i for safety. (rocket-chip doesn't have this
// because jalr is special there)
_resume:
csrr s0, CSR_MHARTID
sw s0, DEBUG_ROM_RESUMING(zero) // When Debug Module sees this write, the RESUME flag is reset.
csrr s0, CSR_DSCRATCH // Restore s0
dret
// END OF ACTUAL "ROM" CONTENTS. BELOW IS JUST FOR LINKER SCRIPT.
.section .whereto
whereto:
nop
// Variable "ROM" This is : jal x0 abstract, jal x0 program_buffer,
// or jal x0 resume, as desired.
// Debug Module state machine tracks what is 'desired'.
// We don't need/want to use jalr here because all of the
// Variable ROM contents are set by
// Debug Module before setting the OK_GO byte.
The basic function of debugger is forcing the specific harts to enter the debug mode, and resume from the debug mode.
-
One hart can be selected in the
hartselfeilds indmcontrol, and additional more harts can ben seleted withhawindowselandhawindow. For the address space may be comsumed a lot if 2^20 Boolean registers were maped, only a small 32-bits space is selected byhawindowsel, and their state are exposed byhawindowfor user accessing. -
DMcan force the seleted harts entering in or resuming from halt bydmcontrol, observe their state withdmstatus -
DWcan drive a global reset withndmresetfeild ofdmcontrol, which can be used for reseting the other module on the SoC.
Basically, one debugger must be able to read the Regiter Files when the hart halted. Furthermore, writing registers, accessing other registers, accessing without halting the harts are the enhancement feature.
Access Register is accomplished by transforming the instructions in the Abstract Command ROM, which can exchange values between hart registers and the DATA register.
Additionally, the Access Register command optionally supports complex functions such as (1) register auto-increment and (2) execution of code in Program Buff before completion.
This feature is optional and is used to assist debuggers in performing complex and highly flexible debugging functions. OpenOCD first writes instructions to Program Buff, then directs the instruction flow to Program Buff.
This feature is optional and is used to access memory from the hart’s perspective. Access Register is accomplished by transforming the instructions in the Abstract Command ROM, which can exchange values between memory and the DATA register.
Since this feature essentially uses Load/Store instructions to access memory, with bus requests entering through the LSU in the hart, it produces results that are not completely identical to those produced by requesting through System Bus Access from System Bus entry:
- Access to hart space
- Cache consistency
- Bus access permissions
Additionally, the Access Memory optionally supports complex functions such as (1) optional physical/virtual address access and (2) address auto-increment.
As an additional optional feature, the DM can act as a Master to access the System Bus under the control of another set of registers which can be accessed by DMI. Compared to accessing the bus through the hart, this feature does not require halting the hart. So it has higher efficiency than accessing the bus through LSU.
The debugger (OpenOCD) accesses the bus via DM in 3 ways:
- Read and write by filling instructions into the Program Buff through Quick Access or post-execution in Access Registers
- System Bus Access(mentioned in this section)
- Access Memory
It should be noted that currently (June 2022), OpenOCD’s functional code is not complete relative to specification 0.13.2, and designs that fully comply with specifications cannot be guaranteed to be supported by OpenOCD. The source code of OpenOCD needs to be analyzed and compared.
The feature of hardware breakpoint has not been implemented yet.
An adapter is required to connect from USB to JTAG. Compared to commercial adapter, using FT2232 to homebrew a USB-TO-JTAG circuit is an open-source and cheap solution.
The solution can be refer from an very old project
For a raw FT2232, FT Prog, which can be download from FTDI, is required to flash the EEPROM to store the configuration of FT2232
For the FT2232D, only channel-0 can be configed to JTAG mode:
- config function of port-A to 234FIFO
- config drivcer of port-A to D2XX
- zadig is used to modify the driver of FT2232. Replace the driver of interface-0 to libusbK ( or WinUSB, it depends on version )
- Comfirming the device
0403:6010 Future Techonlogy Devices Internationalis existed.
lsusb
- adding the device into the group
plugdev
sudo echo -e "SUBSYSTEM=="usb", ATTR{idVendor}=="0403", ATTR{idProduct}=="6010", MODE="664", GROUP="plugdev"\nSUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6010", MODE="664", GROUP="plugdev"" >> /etc/udev/rules.d/99-openocd.rules
- adding the user into the
plugdev
sudo usermod -a -G plugdev WHO-AM-I
To simulate in softwave environment, such as verilator and vcs, BitBang can be used.
-
connecting host of JTAG (SimJTAG.v) in to the environment
-
adding the driver:SimJTAG.cc, remote_bitbang.h, remote_bitbang.cc
This will open a socket after the softwave simulation starting . OpenOCD can connect to the debug environment through a specified port and operate the corresponding signal on the JTAG port. Due to speed mismatch, there will be a problem of slow simulation speed. During simulation:
- Use as simple configuration as possible, reduce the capacity of the cache and reduce the number of the channels in each pipeline.
- Reduce the IO, turn off unnecessary waveform output and log printing
- Compilation optimization
- Enable multi-threaded simulation
The OpenOCD access the inner register in DTM directly through JTAG interface. Using -d if logging is required.
Also the debug interface and configuration of harts should be declared in configuration
To use BitBang Mode, the configuration of --enable-remote-bitbang should be enabled during the compilation
The demo run-time configuration is as followed:
adapter speed 10
interface remote_bitbang
remote_bitbang_host localhost
remote_bitbang_port 16666
set _CHIPNAME riscv
jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x00001
set _TARGETNAME $_CHIPNAME.cpu
target create $_TARGETNAME riscv -chain-position $_TARGETNAME
riscv set_command_timeout_sec 20
init
halt
To use FT2232, the configuration of --enable-ftdi should be enabled during the compilation
The demo run-time configuration is as followed:
adapter_khz 1000
interface ftdi
ftdi_device_desc "Dual RS232-HS"
ftdi_vid_pid 0x0403 0x6010
ftdi_layout_init 0x0008 0x001b
#ftdi_layout_signal nSRST -oe 0x0020 -data 0x0020
set _CHIPNAME riscv
jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x00001
set _TARGETNAME $_CHIPNAME.cpu
set _TARGETNAME_0 $_CHIPNAME.cpu0
set _TARGETNAME_1 $_CHIPNAME.cpu1
set _TARGETNAME_2 $_CHIPNAME.cpu2
set _TARGETNAME_3 $_CHIPNAME.cpu3
target create $_TARGETNAME_0 riscv -chain-position $_TARGETNAME -coreid 0 -rtos hwthread
target create $_TARGETNAME_1 riscv -chain-position $_TARGETNAME -coreid 1 -rtos hwthread
target create $_TARGETNAME_2 riscv -chain-position $_TARGETNAME -coreid 2 -rtos hwthread
target create $_TARGETNAME_3 riscv -chain-position $_TARGETNAME -coreid 3 -rtos hwthread
target smp $_TARGETNAME_0 $_TARGETNAME_1 $_TARGETNAME_2 $_TARGETNAME_3
init
halt
The Port 3333 will be listened for GDB connection, and the Port 4444 will be listened for telnet connection.
As an open-source debugger, GDB can be easily used with following instruction to control OpenOCD:
-
target remote localhost::3333connect OpenOCD -
set remotetimeout 2000set the timeout limited to 20 seconds for the bitbang mode that runs slowly -
file YOUR-FILE-NAMEload the debug file into GDB -
loaddownload the debug file into the memory of the SoC -
compare-sectionscompare whether the memory completely identical to thefile -
b maininsert a breakpiont atmain -
nextstep -
info regsprint the state of registers -
ccontinue -
p $pc,p/x $pcprint the value ofpc(in hex) -
set $pc=0x80000000set the value ofpc
The command user send to GDB is abstract, the specific operation is determined by the OpenOCD. The lower level interface is provided by OpenOCD for detail debugging. To manually access the inner registers in the DM, an user-defined behavior can be operated.
The telnet instruction is as followed:
-
riscv dmi_read 0x00raedDMat 0x00 -
riscv dmi_write 0x00 0xAAwrite 0xAA intoDMat 0x00
For the completeness functionnal test of the debugger , the rscripts under riscv-test/debug can be used
- According to the memory map, define the linker file
xxx.ldsattarget/RISC-V/ - According to the feature of the harts, define the hart configuration file
xxx_test.pyattarget/RISC-V/ - Run the test script
python ./gdbserver.py targets/RISC-V/rift_test.pyinriscv-test/debug, and test the test cases in thetestlib.pyfile one by one - Check the logs under the
logfolder