RISC-V Debug Memory Dumps: Stop the Last SBA Read from Fetching One Word Too Far
A debugger requests three words from a RISC-V target but the bus trace shows four reads. The fourth access can be harmless in ordinary RAM and destructive beside a read-to-clear peripheral register. The cause can be the System Bus Access interface’s read-ahead behavior rather than an incorrect requested byte count.
This discussion uses the RISC-V Debug Specification 1.0 System Bus Access contract and its SBA register definitions, pinned to release commit 5695c0aa. The addresses and values below are hypothetical. No target memory, peripheral or debug probe was accessed.
Establish which memory path is in use
System Bus Access, or SBA, is an optional Debug Module block that accesses physical addresses without involving a hart. It is distinct from executing a Program Buffer or using an abstract memory command through a hart. A debugger’s generic “read memory” operation does not reveal which path its driver selected.
Inspect sbcs.sbasize and the advertised access-size bits before constructing a transaction. An address width of zero means no SBA support. Selecting a width the block does not support prevents the bus access and sets the corresponding error; CPU XLEN alone does not establish which SBA widths exist.
SBA also need not observe memory coherently with every hart. The specification assigns the debugger responsibility for enforcing coherency when the implementation does not provide it, without defining one universal mechanism. A correctly sequenced dump can still contain stale data if the platform’s cache-visibility requirements were ignored.
Understand the two automatic read triggers
With sbreadonaddr=1, writing sbaddress0 starts a read using the newly assembled address. If the address spans several registers, populate the implemented upper parts before writing the low part that triggers the operation. Otherwise the read can use an unintended combination of address pieces.
With sbreadondata=1, reading sbdata0 first returns the already available data and then starts another bus read at the current address. If sbautoincrement=1, a successful access advances that address by the configured access size in bytes.
These are separate triggers. A read of sbdata0 is not always a passive extraction of the last result, and setting auto-increment does not by itself define when another read begins. Label each host operation by the target-bus access it can cause.

Original transaction worksheet. Three requested words require three bus reads, not an unrequested fourth fetch.
Drain a three-word pipeline deliberately
Assume a supported, aligned 32-bit access to ordinary RAM, no outstanding error and no other debugger client using the block. Let the first address be A. Configure read-on-address, read-on-data and auto-increment while the SBA manager is idle.
Writing the address triggers the read at A. Wait for completion and inspect the error fields. The buffer now contains word zero and the address has advanced to A+4. Reading sbdata0 returns word zero and starts the read at A+4.
After that read completes successfully, the next sbdata0 read returns word one and starts the read at A+8. Wait again and inspect status. The buffer contains word two; the address now identifies A+12.
Before extracting word two, clear sbreadondata while retaining the intended values of the other configuration fields. This control write is legal only after observing sbbusy=0. The final sbdata0 read then retrieves the existing result without initiating a bus read at A+12.
Leaving read-on-data enabled during that last extraction launches the extra access. Waiting for it afterward does not undo a read-to-clear side effect, and discarding the extra returned value does not make the extra bus transaction disappear.
Read wide results before triggering their replacement
For a supported 64-bit read, the result occupies sbdata1 and sbdata0. The specification directs debuggers to access the upper data registers before sbdata0, because only the low register has the transaction-triggering behavior.
If read-on-data is enabled and the debugger reads the low word first, it starts the next access before collecting the previous high word. The new transaction can make that later access busy, or replace the buffer before the debugger assembles the intended value. Reading the high parts first and the low trigger last keeps one result associated with one access.
This register ordering does not promise a coherent snapshot of a changing application structure. Even correctly assembled individual bus reads need a separate capture protocol when firmware can update the object concurrently.
Qualify exact bus footprints, not just returned bytes
The sbbusy bit describes the SBA manager’s outstanding operation. Accessing its data registers while busy can set sbbusyerror; writing sbcs while busy has undefined behavior. The proposed final-drain step must therefore wait rather than racing a configuration update against the last fetch.
Both sbbusyerror and sberror inhibit new accesses until explicitly cleared under their documented write-one-to-clear semantics. Retain an error before clearing it, and do not interpret a stale buffer as a successful current read. This is a different state machine from abstract-command cmderr.
A useful acceptance fixture provides safe RAM with a bus-access log. Test one-word, three-word and wide-result reads, then assert the exact addresses, widths and count. Deliberately delay transactions to expose busy sequencing. Only after that baseline should the driver be considered for documented peripheral reads, with automatic prefetch disabled unless every resulting access is explicitly safe.
No such fixture was executed here. The review outcome should identify the initial trigger, each pipeline advancement and the final drain, alongside physical-address and coherency assumptions. A byte-perfect returned buffer alone cannot establish that the debugger touched only the requested target locations.