[BUG] `M-mode PMP`: lower-priority locked entry overrides higher-priority unlocked entry

Placeholder hjw_arch
2026-01-06T09:29:37Z

### Description
We have observed a discrepancy between the CVA6 RTL execution and the Spike simulation regarding PMP priority handling in `M-Mode`.

According to the RISC-V Privileged Specification:
> PMP entries are statically prioritized. The lowest-numbered PMP entry that matches any byte of an access determines whether that access succeeds or fails. The matching PMP entry must match all bytes of an access, or the access fails, irrespective of the L, R, W, and X bits. For example, if a PMP entry is configured to match the four-byte range 0xC–0xF, then an 8-byte access to the range 0x8–0xF will fail, assuming that PMP entry is the highest-priority entry that matches those addresses.
>
> If a PMP entry matches all bytes of an access, then the L, R, W, and X bits determine whether the access succeeds or fails. If the L bit is clear and the privilege mode of the access is M, the access succeeds. Otherwise, if the L bit is set or the privilege mode of the access is S or U, then the access succeeds only if the R, W, or X bit corresponding to the access type is set.

### The Issue
In our test case, we configure:

- PMP Entry 2: Covers the entire address range (-1), **Unlocked**, **RWX permissions** (L=0, R=1, W=1, X=1, cfg=0x1f).
- PMP Entry 7: Covers the entire address range (-1), **Locked**, **No permissions** (L=1, R=0, W=0, X=0, cfg=0x98).

#### Expected Behavior (Spike)
The memory access (ld) should match PMP Entry 2 first. Since it is Unlocked and we are in M-Mode, the access should be allowed. PMP Entry 7 **should** be ignored due to lower priority.

#### Actual Behavior (CVA6)
CVA6 triggers an `LD_ACCESS_FAULT`. It appears to ignore the match on `PMP Entry 2` and enforces the restrictions of `PMP Entry 7` (Locked, No Access), effectively prioritizing the Locked attribute over the entry index order.

### Steps to Reproduce

1. Initialize environment and enter `M-Mode`.
2. Set pmpaddr2 and pmpaddr7 to -1 (covering full address space).
3. Write pmpcfg0 with 0x98000000001f0000:
- `PMP[2]`: 0x1f (NAPOT, RWX, Unlocked).
- `PMP[7]`: 0x98 (TOR, No Access, Locked).

4. Execute a load instruction (ld) targeting a valid memory address (e.g., 0x80101000).

```
main:
li s9, 0x98000000001f0000
csrw pmpcfg0, s9
csrr x1, pmpcfg0

li t0, -1
csrw pmpaddr2, t0
csrw pmpaddr7, t0

ld t1, (t6)
ld t2, (t6)
```

### Logs
#### spike
```
ore 0: 0x0000000080003028 (0x000fb303) ld t1, 0(t6)
core 0: 3 0x0000000080003028 (0x000fb303) x6 0x0000000000000000 mem 0x0000000080101000
core 0: 0x000000008000302c (0x000fb383) ld t2, 0(t6)
core 0: 3 0x000000008000302c (0x000fb383) x7 0x0000000000000000 mem 0x0000000080101000
```

#### cva6
```
LD_ACCESS_FAULT exception @ 0x0000000080003028 (0x000fb303)
......
core 0: 0x0000000080001010 (0x30200073) mret
3 0x0000000080001010 (0x30200073)
LD_ACCESS_FAULT exception @ 0x000000008000302c (0x000fb383)
```

### Attachments
ELF binary and full execution logs are attached.

[bug_report.zip](https://github.com/user-attachments/files/24449677/bug_report.zip)