Hazard3/doc/sections/debug.adoc

76 lines
5.9 KiB
Plaintext

== Debug
Hazard3, along with its external debug components, implements version 0.13.2 of the RISC-V debug specification. It supports the following:
* Run/halt/reset control as required
* Abstract GPR access as required
* Program Buffer, 2 words plus `impebreak`
* Automatic trigger of abstract command (`abstractauto`) on `data0` or Program Buffer access for efficient memory block transfers from the host
* (TODO) Some minimum useful trigger unit -- likely just breakpoints, no watchpoints
=== Debug Topologies
Hazard3's Debug Module has the following interfaces:
* An upstream AMBA 3 APB port -- the "Debug Module Interface" -- for host access to the Debug Module
* A downstream Hazard3-specific interface to one or more cores _(multicore support is experimental)_
* Some reset request/acknowledge signals which require careful handshaking with system-level reset logic
This is shown in the example topology below.
image::diagrams/debug_topology.png[pdfwidth=50%]
The Debug Module _must_ be connected directly to the processors without intervening registers. This implies the Debug Module is in the same clock domain as the processors, so multiple processors on the same Debug Module must share a common clock.
Upstream of the Debug Module is at least one Debug Transport Module, which bridges some host-facing interface such as JTAG to the APB Debug Module Interface. Hazard3 provides an implementation of a standard RISC-V JTAG-DTM, but any APB master could be used. The Debug Module requires at least 7 bits of word addressing, i.e. 9 bits of byte address space.
An APB arbiter could be inserted here, to allow multiple transports to be used, provided the host(s) avoid using multiple transports concurrently. This also admits simple implementation of self-hosted debug, by mapping the Debug Module to a system-level peripheral address space.
The clock domain crossing (if any) occurs on the downstream port of the Debug Transport Module. Hazard3's JTAG-DTM implementation runs entirely in the TCK domain, and instantiates a bus clock-crossing module internally to bridge a TCK-domain internal APB bus to an external bus in the processor clock domain.
It is possible to instantiate multiple Debug Modules, one per core, and attach them to a single Debug Transport Module. This is not the preferred topology, but it does allow multiple cores to be independently clocked.
=== Implementation-defined behaviour
Features implemented by the Hazard3 Debug Module (beyond the mandatory):
* Halt-on-reset, selectable per-hart
* Program Buffer, size 2 words, `impebreak` = 1.
* A single data register (`data0`) is implemented as a per-hart CSR accessible by the DM
* `abstractauto` is supported on the `data0` register
* Up to 32 harts selectable via `hartsel`
Not implemented:
* Hart array mask selection
* Abstract access memory
* Abstract access CSR
* Post-incrementing abstract access GPR
* System bus access
The core behaves as follows:
* Branch, `jal`, `jalr` and `auipc` are illegal in debug mode, because they observe PC: attempting to execute will halt Program Buffer execution and report an exception in `abstractcs.cmderr`
* The `dret` instruction is not implemented (a special purpose DM-to-core signal is used to signal resume)
* The `dscratch` CSRs are not implemented
* External `data0` register is exposed as a dummy CSR mapped at `0x7b2` (the location of `dscratch0`), readable and writable by the DM.
** This is a debug mode CSR, so raises an illegal instruction exception when accessed in machine mode
** The DM ignores writes unless it is currently executing an abstract command on this core (`hartsel` = this core, `abstractcs.busy` = 1)
* `dcsr.stepie` is hardwired to 0 (no interrupts during single stepping)
* `dcsr.stopcount` and `dcsr.stoptime` are hardwired to 1 (no counter or internal timer increment in debug mode)
* `dcsr.mprven` is hardwired to 0
* `dcsr.prv` is hardwired to 3 (M-mode)
=== Debug Module to Core Interface
The DM can inject instructions directly into the core's instruction prefetch buffer. This mechanism is used to execute the Program Buffer, or used directly by the DM, issuing hardcoded instructions to manipulate core state.
The DM's `data0` register is exposed to the core as a debug mode CSR. By issuing instructions to make the core read or write this dummy CSR, the DM can exchange data with the core. To read from a GPR `x` into `data0`, the DM issues a `csrw data0, x` instruction. Similarly `csrr x, data0` will write `data0` to that GPR. The DM always follows the CSR instruction with an `ebreak`, just like the implicit `ebreak` at the end of the Program Buffer, so that it is notified by the core when the GPR read instruction sequence completes.
The debug host must use the Program Buffer to access CSRs and memory. This carries some overhead for individual accesses, but is efficient for bulk transfers: the `abstractauto` feature allows the DM to trigger the Program Buffer and/or a GPR tranfer automatically following every `data0` access, which can be used for e.g. autoincrementing read/write memory bursts. Program Buffer read/writes can also be used as `abstractauto` triggers: this is less useful than the `data0` trigger, but takes little extra effort to implement, and can be used to read/write a large number of CSRs efficiently.
Abstract memory access is not implemented because it offers no better throughput than Program Buffer execution with `abstractauto` for bulk transfers, and non-bulk transfers are still instantaneous from the perspective of the human at the other end of the wire.
The Hazard3 Debug Module has experimental support for multi-core debug. Each core possesses exactly one hardware thread (hart) which is exposed to the debugger. The RISC-V specification does not mandate what mapping is used between the Debug Module hart index `hartsel` and each core's `mhartid` CSR, but a 1:1 match of these values is the least likely to cause issues. Each core's `mhartid` can be configured using the `MHARTID_VAL` parameter during instantiation.