RISC-V Debug Register Reads: Recover the Abstract Command State Before Retrying
A debugger reads one CPU register successfully, encounters an unsupported CSR, and then reports that every later register read failed. The next requested general-purpose register may be perfectly accessible. A sticky error in the Debug Module can prevent the subsequent commands from starting at all.
The relevant contract is RISC-V Debug Specification 1.0, Abstract Commands, pinned to release commit 5695c0aa. This article follows its Debug Module register definitions and Access Register command. It describes a proposed debugger-driver review, not an executed probe session or a universal fix for transport failures.
Separate transaction transport from command completion
Writing the Debug Module’s command register launches an abstract operation. A successful transport write does not establish that the selected hart completed that operation. The debugger must observe abstractcs.busy and, after it becomes zero, inspect abstractcs.cmderr.
The specification makes cmderr valid only while busy=0. Reading the status register during execution is appropriate for polling, but interpreting its error field before completion can attribute a stale or invalid result to the current operation.
Results belong in the data registers before the module clears busy. For a 64-bit result, the least-significant word occupies the lower-numbered data register. A debugger must establish successful completion before treating those words as the requested register value. Retained data from an earlier command is not a fallback result.
Preserve the first failure instead of creating a second one
Submitting another command while busy=1 produces a busy error. The command already running is allowed to finish, but its own eventual error can be lost behind the busy condition. An eager retry therefore risks replacing the useful failure with evidence of the debugger’s sequencing mistake.
The busy restriction also applies beyond writing command. The register definitions identify writes to abstractcs or abstractauto, and reads or writes of data or progbuf, as operations that can set the busy error during command execution. A diagnostic routine that dumps all Debug Module registers indiscriminately can create the condition it is trying to investigate.
Poll the permitted status path until completion. Keep the hart selection fixed and do not issue the prohibited halt/resume or reset-acknowledgement request-bit changes during an abstract command. Those restrictions are explicit in the specification; selecting another hart is not a harmless way to collect parallel evidence.

Original debugger state diagram. It illustrates source-defined transitions, not captured Debug Module traffic.
A sticky error blocks later commands
The cmderr field is write-one-to-clear. Once it becomes nonzero, new abstract commands do not start until the field is cleared. Writing another command first merely repeats an inhibited request.
A useful recovery sequence is conceptual rather than a blind register constant:
wait until abstractcs.busy == 0, with a bounded timeout
record cmderr and the failed command's fields
if cmderr != 0:
write ones to all three cmderr bits to clear the field
preserve the intended values of other writable fields
read status again and confirm idle, error-free state
while idle, disable and verify applicable abstractauto autoexec bits
issue one deliberately supported diagnostic command
wait for completion; check cmderr before consuming data
Do not write abstractcs while still busy to “clear the hang.” That write can itself create a busy error. Also avoid an indiscriminate writeback that changes another writable policy field, such as the optional relaxedpriv control, while clearing the error.
The baseline also disables automatic command re-execution through abstractauto while idle. Otherwise, reading an enabled data or Program Buffer word can launch the command again after that access. A supposedly passive result read would then start an unplanned operation.
The proposed retry is intentionally narrow. For a selected, available, halted hart, the specification requires Access Register support for its general-purpose registers. Reading an appropriate GPR provides a stronger baseline than repeating an optional CSR access with unchanged settings.
Classify unsupported, exception and wrong-state outcomes
Error 2 means the requested command or option combination is unsupported; changing the hart’s running state does not make that same unsupported operation become supported. A debugger can try a different supported command form, but it should not turn “unsupported” into an endless timing retry.
Error 3 reports an exception during command execution. The Access Register description specifically requires that code when the requested register does not exist. Error 4 instead concerns a hart that is unavailable or in the wrong running/halted state. These categories call for different evidence and recovery decisions.
For Access Register, inspect regno, aarsize, transfer, write, postexec and any optional increment setting. A width larger than the actual register must fail. A command that unnecessarily enables Program Buffer execution adds another potential failure after the register transfer. Reduce the diagnostic to the operation actually needed rather than assuming every module implements every option.
Treat timeout recovery as a separate destructive decision
A hart becoming unavailable during a command can yield a halt/resume error, but the specification also permits the command to remain busy. A host-side timeout therefore needs to preserve the last valid status, hart selection and attempted operation; it cannot manufacture successful completion.
The specification discusses hart reset and then Debug Module reset as possible escalation paths. Those actions can destroy the failure state and affect the running product. They are not equivalent to clearing a completed command’s error. Require the laboratory’s approved recovery policy before taking them, and document what evidence will be lost.
No register, hart or reset control was accessed here. A useful acceptance test deliberately exercises one unsupported operation, verifies that its sticky error blocks later commands, and demonstrates an idle-state clear followed by a supported read. Keep that test separate from transport faults and genuine never-completing commands so that a debugger sequencing bug does not masquerade as inaccessible silicon.