Repository navigation
[Bug] hedeleg writes are masked to zero in H-enabled Rocket #3815
Description
Bug Description
Official Rocket Chip master with the H extension enabled accepts an HS-mode write of 0x000c0000 to hedeleg, but reads back 0. These are hedeleg bits 18 and 19.
The minimal PoC starts in M-mode, opens a permissive PMP entry so HS-mode can execute from the loaded image, enters HS-mode with mret, writes hedeleg[18:19], reads hedeleg, and fails if those two bits are not preserved.
Core HS-mode sequence:
li t0, 0x000c0000
csrw hedeleg, t0
csrr s0, hedeleg
and s0, s0, t0
bne s0, t0, fail_maskedThis is a direct CSR write/readback issue in an H-enabled Rocket configuration. It is not a reset-profile issue and not a BootROM false positive.
Upstream version confirmation: this issue was validated against the official Rocket Chip master line at revision v1.6-819-g55bcad0f5, confirmed at commit 55bcad0f59436de98ea510334121de8546b9e9d7, using freechips.rocketchip.system.HypervisorConfig. No fix revision has been identified yet.
RISC-V Specification Requirement
The RISC-V Privileged ISA, Section 15.1.2. Hypervisor and Virtual Supervisor CSRs, states that HS-mode provides additional CSRs, including hedeleg, to control VS-mode guest behavior.
Section 15.1.2.2. Hypervisor Trap Delegation (hedeleg and hideleg) Registers defines hedeleg as a 64-bit read/write register used to further delegate synchronous traps from HS-mode to VS-mode. The same section says each hedeleg bit is either writable or read-only zero, and Table 2 enumerates the required behavior.
For this PoC, the relevant Table 2 entries are:
| Bit | Attribute | Corresponding exception |
|---|---|---|
| 18 | Writable | Software check |
| 19 | Writable | Hardware error |
Reference: RISC-V Privileged ISA v20260120, Hypervisor Trap Delegation Registers
An H-enabled implementation should therefore preserve these two writable bits on readback after HS-mode software writes them.
Steps to Reproduce
Essential source:
li t0, -1
csrw pmpaddr0, t0
li t0, 0x1f
csrw pmpcfg0, t0
la t0, hs_entry
csrw mepc, t0
li t1, 0x1800
csrc mstatus, t1
li t1, 0x0800
csrs mstatus, t1
mret
hs_entry:
li t0, 0x000c0000
csrw hedeleg, t0
csrr s0, hedeleg
and s0, s0, t0
bne s0, t0, fail_maskedExpected Result
After HS-mode writes 0x000c0000 to hedeleg, bits 18 and 19 should read back as set:
hedeleg & 0x000c0000 == 0x000c0000
Actual Result
Official Rocket Chip HypervisorConfig reads back zero for those bits at the failing CSR read:
16552:C0: 75272 [1] pc=[0000000080000050] W[r 5=00000000000c0000][1] ... inst=[000c02b7]
16553:C0: 75273 [1] pc=[0000000080000054] ... R[r 5=00000000000c0000] ... inst=[60229073]
16554:C0: 75278 [1] pc=[0000000080000058] W[r 8=0000000000000000][1] ... inst=[60202473]
So software prepares 0x000c0000, writes hedeleg, and the following csrr hedeleg still returns zero.
The PoC reaches fail_masked and exits with code 61:
*** FAILED *** (tohost = 61)
*** FAILED *** via dtm (code = 61, seed 1) after 97394 cycles
Activity
hiSandog commentedon Jul 11, 2026
A directed CSR test could cover both writable and unsupported
hedelegbits, rather than only checking one nonzero write. Writing all ones and checking the architectural mask, followed by a mixed pattern, would catch both the current zero-mask failure and accidental exposure of reserved bits.jf-cc727 commentedon Jul 11, 2026
Thanks, that makes sense. I added a supplemental directed CSR mask test for hedeleg. The test writes all ones and then a mixed pattern, checking both the required-writable bits and the required-zero bits from the architectural mask. The result still shows the current zero-mask failure: both the all-ones write and the mixed-pattern write miss hedeleg[18:19] (missing mask 0x000c0000). The required-zero bits checked by the test remained zero, so this run confirms the writable-bit failure without showing accidental exposure of reserved/unsupported bits.