Tighten memory bus and trap ownership contracts
Convert the memory-bus guidance from a loose list of request/response fields into packed mem_req_t and mem_rsp_t payload structs carried by separate valid/ready handshakes. Spell out that the same channel shape is instantiated independently for the instruction bus and data bus so the design remains Harvard at the bus level while still sharing a single contract. Clarify fault ownership before RTL exists: slaves only raise rsp.err for access faults they can own, the LSU detects misaligned loads/stores before issuing a D-bus request, the decoder owns illegal instructions and ebreak/ecall decode events, and the future MMU owns page faults before translated requests reach the bus. Update the roadmap to use the structured bus field names, correct the misalignment tests and GCC illegal-instruction explanation, and prefer modern RISC-V device-tree ISA properties with an optional legacy riscv,isa string for older kernels.
This commit is contained in:
+51
-32
@@ -1,4 +1,4 @@
|
||||
# RV32IM CPU Core — Build Roadmap
|
||||
# RISC-V CPU Core — Build Roadmap
|
||||
## Target: Digilent Arty A7 100T / Vivado 2025.2+ / SystemVerilog
|
||||
## Clock: 50 MHz (100 MHz xtal ÷ 2 via MMCM)
|
||||
## End goal: Boot Linux on a custom RISC-V core
|
||||
@@ -241,20 +241,23 @@ Future role: Final. These instructions don't change.
|
||||
What: Add a data BRAM mapped at `0x8000_0000` (64 KB) and a load/store unit.
|
||||
For now, only 32-bit aligned access (lw and sw).
|
||||
|
||||
Define the full memory bus contract here, not just the subset Phase 6.1 uses.
|
||||
Defining it once means Phase 6.2 (sub-word), Phase 7 (MMIO), Phase 9 (access
|
||||
faults), Phase 12 (atomics), and Phase 14 (DRAM) all plug into the same
|
||||
interface without rework. Required signals (see `CLAUDE.md` for the full
|
||||
spec):
|
||||
Define the full memory bus contract here, not just the subset Phase 6.1
|
||||
uses. Defining it once means Phase 6.2 (sub-word), Phase 7 (MMIO), Phase 9
|
||||
(access faults), Phase 12 (atomics), and Phase 14 (DRAM) all plug into the
|
||||
same interface without rework. The contract (see `CLAUDE.md` for the full
|
||||
spec) is two payload structs (`mem_req_t`, `mem_rsp_t`) plus loose
|
||||
valid/ready signals on each channel:
|
||||
|
||||
- `req_valid` / `req_ready` — request handshake
|
||||
- `rsp_valid` / `rsp_ready` — response handshake (independent)
|
||||
- `req_addr`, `req_we`, `req_wdata`, `req_size`, `req_wstrb`
|
||||
- `rsp_rdata`, `rsp_err`
|
||||
- `req_valid` (master→slave) / `req_ready` (slave→master) + `mem_req_t req`
|
||||
- `rsp_valid` (slave→master) / `rsp_ready` (master→slave) + `mem_rsp_t rsp`
|
||||
|
||||
For Phase 6.1 specifically: `req_size` is always `2'b10` (word), `req_wstrb`
|
||||
is `4'b1111` on stores, `rsp_err` is unused (BRAM never faults). The signals
|
||||
exist in the bundle from day one even when tied off.
|
||||
`mem_req_t` carries `addr`, `we`, `size`, `wdata`, `wstrb`, `amo`.
|
||||
`mem_rsp_t` carries `rdata`, `err`.
|
||||
|
||||
For Phase 6.1 specifically: `req.size` is always `2'b10` (word),
|
||||
`req.wstrb` is `4'b1111` on stores, `req.amo` is `4'h0`, `rsp.err` is
|
||||
unused (BRAM never faults). The fields exist in the struct from day one
|
||||
even when tied off.
|
||||
|
||||
Note: BRAM is 1-cycle read latency, so a load takes one extra cycle beyond
|
||||
the EXECUTE state. The control FSM extends to FETCH → EXECUTE → MEM_WAIT →
|
||||
@@ -284,21 +287,30 @@ to 32 bits, lbu zero-extends).
|
||||
Why: C uses char (byte) and short (halfword) types constantly. String operations are
|
||||
byte-by-byte. You can't run real C code without these.
|
||||
|
||||
Implementation: the load/store unit drives `req_size` (00=byte, 01=halfword,
|
||||
10=word) and `req_wstrb` (which byte lane within the 32-bit word is being
|
||||
written). On load, the unit muxes the right byte/halfword out of `rsp_rdata`
|
||||
Implementation: the load/store unit drives `req.size` (00=byte, 01=halfword,
|
||||
10=word) and `req.wstrb` (which byte lane within the 32-bit word is being
|
||||
written). On load, the unit muxes the right byte/halfword out of `rsp.rdata`
|
||||
and applies sign/zero extension based on the opcode.
|
||||
|
||||
Decision for this project: **trap on misaligned access** rather than support
|
||||
it in hardware. Hardware support for misaligned word access on Artix-7 is
|
||||
expensive (two BRAM cycles + merge logic) and the kernel can emulate via
|
||||
trap. The bus signals misalignment via `rsp_err`, and Phase 9's trap logic
|
||||
turns that into a load/store address-misaligned exception.
|
||||
trap.
|
||||
|
||||
Critically, the LSU detects misalignment **locally, before issuing a bus
|
||||
request** — it has the opcode size and the low address bits available the
|
||||
moment the instruction is decoded. No misaligned request is ever placed on
|
||||
the D-bus. The LSU raises the architectural cause directly: mcause 4 for a
|
||||
load (`lh`/`lw` with bad alignment) and mcause 6 for a store/AMO. This
|
||||
keeps `rsp.err` reserved for slave-reported faults (unmapped, peripheral
|
||||
access violations) and matches how the trap classification table in
|
||||
`CLAUDE.md` divides responsibility.
|
||||
|
||||
Testbench focus: Sign extension (loading 0xFF as signed byte should give
|
||||
0xFFFFFFFF, as unsigned should give 0x000000FF). Byte lane selection (storing
|
||||
0xAB at address `0x8000_0001` must update only that byte). Misaligned access
|
||||
returns `rsp_err`.
|
||||
0xFFFFFFFF, as unsigned should give 0x000000FF). Byte lane selection
|
||||
(storing 0xAB at address `0x8000_0001` must update only that byte).
|
||||
Misaligned `lh`/`lw`/`sh`/`sw` produce the right mcause without ever
|
||||
touching the bus.
|
||||
|
||||
Future role: Final. These instructions don't change.
|
||||
|
||||
@@ -407,12 +419,13 @@ Convert: `objcopy -O binary firmware.elf firmware.bin`. Convert binary to
|
||||
`.mem` format. Load into BRAM. Run.
|
||||
|
||||
March string discipline: advertise only what the hardware decodes. `rv32im`
|
||||
is correct for Phase 8 because CSRs and `fence.i` aren't decoded yet. When
|
||||
the program (or crt0) uses CSR/fence.i ops before that, the bus will return
|
||||
`rsp_err` and Phase 8.3's illegal-instruction halt will catch it visibly —
|
||||
much better than NOPing through a bug. Switch to `rv32im_zicsr_zifencei` in
|
||||
Phase 9 once CSRs land, and `rv32ima_zicsr_zifencei` in Phase 12 once
|
||||
atomics land.
|
||||
is correct for Phase 8 because CSRs and `fence.i` aren't decoded yet. If the
|
||||
program (or crt0) emits a CSR or `fence.i` op before then, the **decoder**
|
||||
asserts `illegal_instr` and the Phase 8.3 halt latches the offending PC and
|
||||
instruction word — catching the bug visibly rather than NOPing through it.
|
||||
(Illegal instructions are a decode event, not a bus event; the memory bus
|
||||
is not involved.) Switch to `rv32im_zicsr_zifencei` in Phase 9 once CSRs
|
||||
land, and `rv32ima_zicsr_zifencei` in Phase 12 once atomics land.
|
||||
|
||||
Note on GCC 11+: newer toolchains require `_zicsr` / `_zifencei` in the
|
||||
march string only when source code actually uses CSR or `fence.i`
|
||||
@@ -618,11 +631,17 @@ Required deliverables:
|
||||
- **Image alignment**: rv32 kernel image base must be 4 MiB-aligned in
|
||||
physical memory (rv64 is 2 MiB). Reflect this in the linker layout for the
|
||||
loaded kernel and the bootloader's copy destination.
|
||||
- **Device tree**: hand-write a `.dts` describing CPU (with `riscv,isa =
|
||||
"rv32ima_zicsr_zifencei"`, `mmu-type = "riscv,sv32"`), memory (DRAM base
|
||||
+ size), CLINT (timer + soft IPI), PLIC (interrupt controller bindings),
|
||||
and the UART node. Compile with `dtc` to a `.dtb` and ship it in flash
|
||||
alongside the kernel.
|
||||
- **Device tree**: hand-write a `.dts` describing CPU, memory (DRAM base +
|
||||
size), CLINT (timer + soft IPI), PLIC (interrupt controller bindings),
|
||||
and the UART node. For the CPU node, prefer the **modern** ISA properties:
|
||||
```
|
||||
riscv,isa-base = "rv32i";
|
||||
riscv,isa-extensions = "i", "m", "a", "zicsr", "zifencei";
|
||||
mmu-type = "riscv,sv32";
|
||||
```
|
||||
Optionally include the legacy `riscv,isa = "rv32ima_zicsr_zifencei"`
|
||||
string for older kernels that don't yet parse the split form. Compile
|
||||
with `dtc` to a `.dtb` and ship it in flash alongside the kernel.
|
||||
- **UART driver/binding decision**: the split TX/RX/status UART from
|
||||
Phase 7 is *not* `8250/16550`-compatible. Pick one:
|
||||
- (a) Add a 16550-subset wrapper (THR/RBR shared at offset 0, LSR at
|
||||
|
||||
Reference in New Issue
Block a user