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:
2026-04-28 12:00:39 +02:00
parent bcbf1fa616
commit 8edfc86027
2 changed files with 154 additions and 62 deletions
+103 -30
View File
@@ -2,10 +2,11 @@
## Project Overview
This is a custom RISC-V RV32IM CPU core written in SystemVerilog, targeting the
Digilent Arty A7 100T (xc7a100tcsg324-1) with Vivado 2025.2 or later.
This is a custom RISC-V CPU core written in SystemVerilog, targeting the
Digilent Arty A7 100T (xc7a100tcsg324-1) with Vivado 2025.2 or later. It
starts as RV32IM and grows toward **RV32IMA + Zicsr + Zifencei + M/S/U
privilege + Sv32** for a Linux-capable SoC.
The goal is incremental development from a single-cycle core to a Linux-capable SoC.
See ROADMAP.md for the full phased plan.
## Conventions
@@ -104,38 +105,110 @@ or write a custom Linux serial driver + device-tree binding.
## Memory Bus Contract
Single channel, valid/ready handshake, defined once in `rv32_pkg.sv` and
reused by every master/slave (CPU, instruction BRAM, data BRAM, MMIO,
DRAM later). Master drives a request, slave drives a response.
Single channel shape, valid/ready handshake, defined once in `rv32_pkg.sv`.
Each channel has two parts: a **payload struct** (single-direction) and a
loose **valid/ready handshake pair** (one signal in each direction). The
payload structs are packed and directional; valid/ready stays outside the
struct to avoid mixing directions.
```
Request (master → slave):
req_valid : logic — request is valid this cycle
req_ready : logic — slave accepts (driven by slave)
req_addr : logic [31:0] — byte address
req_we : logic — 1 = write, 0 = read
req_size : logic [1:0] — 00=byte, 01=halfword, 10=word
req_wdata : logic [31:0] — write data (lane-aligned per wstrb)
req_wstrb : logic [3:0] — byte enables (writes only)
The same channel shape is **instanced twice** — once for the I-bus
(fetch ↔ instruction memory) and once for the D-bus (LSU ↔ data memory and
MMIO). Harvard at the bus level, not just at the BRAM level.
Response (slave → master):
rsp_valid : logic — response is valid this cycle
rsp_ready : logic — master accepts (driven by master)
rsp_rdata : logic [31:0] — read data (reads only)
rsp_err : logic — fault: unmapped, misaligned, access violation
```systemverilog
// Payload — master → slave
typedef struct packed {
logic [31:0] addr;
logic we; // 1 = write, 0 = read (always 0 on I-bus)
logic [1:0] size; // 00=byte, 01=halfword, 10=word
logic [31:0] wdata; // lane-aligned per wstrb
logic [3:0] wstrb; // byte enables (writes only)
logic [3:0] amo; // AMO op encoding (D-bus only, Phase 12+)
} mem_req_t;
// Payload — slave → master
typedef struct packed {
logic [31:0] rdata; // valid on reads only
logic err; // slave-reported access fault
} mem_rsp_t;
// Per-channel signals (instance once for I-bus, once for D-bus):
// logic <ch>_req_valid; // master → slave
// logic <ch>_req_ready; // slave → master
// mem_req_t <ch>_req; // master → slave
//
// logic <ch>_rsp_valid; // slave → master
// logic <ch>_rsp_ready; // master → slave
// mem_rsp_t <ch>_rsp; // slave → master
```
Notes:
- `req_valid`/`req_ready` and `rsp_valid`/`rsp_ready` are independent
handshakes; a slave may take multiple cycles between accepting a request
and producing a response (BRAM = 1 cycle, DRAM = many).
- `req_wstrb` is mandatory from Phase 6.2 onward (sub-word stores). For
If you later prefer a SystemVerilog `interface`, use master/slave modports
to enforce direction. Until then, loose signals + payload structs are the
simplest path that survives every Vivado quirk.
Handshake notes:
- Standard valid/ready: a beat transfers when both `valid` and `ready` are
high in the same cycle. Request and response handshakes are independent;
a slave may take multiple cycles between accepting a request and
producing a response (BRAM = 1 cycle, DRAM = many).
- `wstrb` is mandatory from Phase 6.2 onward (sub-word stores). For
Phase 6.1 (word-only), tie strobes to `4'b1111` on stores.
- `rsp_err` lets the bus signal misalignment, unmapped MMIO, and (later)
page faults uniformly. The CPU's trap logic (Phase 9) consumes it as
load/store access-fault exceptions.
- Atomics (Phase 12) extend this with an `req_amo` opcode field rather than
a new bus.
- `amo` is unused (drive `4'h0`) until Phase 12; on the I-bus it is always
unused.
- `err` is **only** raised by a slave to report access faults it owns:
unmapped address, peripheral-access violation, eventually DRAM ECC fault.
It does NOT carry misalignment (the LSU detects that locally and never
issues), illegal instructions (decode event, not bus), or page faults
(MMU generates them before the bus). Keeping `err` to one bit is fine
because the master that issued the transaction has all the context
needed to classify it.
### I-bus / D-bus instance plan
Both buses use the same `mem_req_t` / `mem_rsp_t` struct, instanced
separately. Per-phase progression:
| Phase | I-bus | D-bus |
|-------|--------------------------------|---------------------------------------------|
| 4 | CPU fetch ↔ instruction BRAM | (none yet) |
| 6 | unchanged | LSU ↔ data BRAM (single slave) |
| 7 | unchanged | LSU ↔ decoder → {data BRAM, UART MMIO} |
| 9-12 | unchanged | + timer (Phase 10), PLIC (Phase 11) |
| 14 | I-bus → arbiter → DRAM | D-bus → arbiter → DRAM (or MIG dual-port) |
Both buses can fault independently; classification happens in the masters
(see below).
### Trap cause classification (who owns which mcause)
`rsp.err` is one bit on the wire. The **master** that issued the
transaction adds context to produce the precise architectural cause:
| Source | mcause | Detected by |
|-------------------------------------|--------|-----------------------------------|
| Instruction address misaligned | 0 | fetch unit (PC[1:0] != 0) |
| Instruction access fault | 1 | fetch unit (I-bus `rsp.err`) |
| Illegal instruction | 2 | decoder (`illegal_instr`) |
| Breakpoint (`ebreak`) | 3 | decoder |
| Load address misaligned | 4 | LSU (size + addr LSBs, pre-issue) |
| Load access fault | 5 | LSU (D-bus `rsp.err` on read) |
| Store/AMO address misaligned | 6 | LSU (size + addr LSBs, pre-issue) |
| Store/AMO access fault | 7 | LSU (D-bus `rsp.err` on write) |
| Ecall from M-mode | 11 | decoder |
| Instruction page fault | 12 | I-side MMU (Phase 15) |
| Load page fault | 13 | D-side MMU (Phase 15) |
| Store/AMO page fault | 15 | D-side MMU (Phase 15) |
Two consequences:
- The LSU detects misalignment **before** issuing a bus request — never
send a request you already know will trap.
- Page faults (Phase 15) are raised by the MMU during translation,
before the (translated) request hits the bus.
### Atomics (Phase 12)
LR/SC and AMO use the `amo` field of `mem_req_t` (D-bus only) rather than
a separate bus. Single-hart reservation tracking lives in the LSU.
## When Helping With This Project