Align the roadmap with a Linux-capable RV32IMA target

Broaden the documented end target from RV32IM plus machine-mode support to a
Linux-capable RV32IMA core with Zicsr, Zifencei, M/S/U privilege, and Sv32.

Add atomics as a required Phase 12 milestone, move the optional pipeline and
memory-system work later, and introduce explicit Linux bring-up preparation for
boot ABI, device tree, UART driver compatibility, OpenSBI versus direct M-mode
boot, and kernel/initramfs handoff.

Tighten compiler guidance so the advertised -march string follows the hardware
that is actually decoded: rv32im for early bare-metal work, then
rv32im_zicsr_zifencei after CSR/fence.i support, and rv32ima_zicsr_zifencei once
atomics land. The roadmap also calls out loud illegal-instruction halts instead
of silently treating unsupported operations as NOPs.
This commit is contained in:
2026-04-28 11:51:22 +02:00
parent e98b3694ab
commit bcbf1fa616
3 changed files with 259 additions and 68 deletions
+60 -12
View File
@@ -26,9 +26,12 @@ See ROADMAP.md for the full phased plan.
active-high.
- BRAM init files: `mem/*.mem` in hex format, one 32-bit word per line.
- Firmware source: `fw/` directory. Built with `riscv64-unknown-elf-gcc`
(multilib build required), `-march=rv32im_zicsr_zifencei -mabi=ilp32`. The
full march string is required by GCC 11+ even before CSRs are implemented;
`Zifencei` is a NOP until caches exist (Phase 13+).
(multilib build required). March string evolves with the implemented ISA:
- Phase 8 (pre-CSR bare-metal): `-march=rv32im -mabi=ilp32`
- Phase 9+ (CSRs/Zifencei decoded): `-march=rv32im_zicsr_zifencei -mabi=ilp32`
- Phase 12+ (atomics): `-march=rv32ima_zicsr_zifencei -mabi=ilp32`
Don't advertise an extension before its ops are decoded — the compiler is
free to emit them, and "trap as illegal" early is much better than NOPing.
## Directory Structure
@@ -47,16 +50,19 @@ docs/ — block diagrams, notes
## ISA Target
RV32IM base. Extensions added incrementally:
- Zicsr (CSR instructions) — Phase 9
- Zifencei (fence.i) — implemented as NOP until caches exist
- M-mode privileged — Phase 9
- S-mode + U-mode + Sv32 — Phase 14
End target: **RV32IMA + Zicsr + Zifencei + M/S/U privilege + Sv32** (Linux-capable).
Extensions land incrementally:
- RV32IM base — Phases 1-8
- Zicsr (CSR instructions) + M-mode trap handling — Phase 9
- Zifencei (`fence.i`) — decoded but NOP until caches exist (Phase 14+)
- A (atomics: LR/SC + AMO) — Phase 12 (required for mainline Linux)
- S-mode + U-mode + Sv32 — Phase 15
## Key Design Decisions
- Single-cycle first, pipeline later (Phase 12). Stages are separated in the code
even without pipeline registers between them.
- Single-cycle first, pipeline later (Phase 13, optional). Stages are separated
in the code even without pipeline registers between them.
- "Single-cycle" is a logical model, not a literal one cycle per instruction.
BRAM has a 1-cycle read latency, so fetch is registered and loads take 2
cycles. The datapath stalls fetch while a multi-cycle operation is in flight.
@@ -73,10 +79,12 @@ RV32IM base. Extensions added incrementally:
## Memory Map
```
0x0000_0000 0x0FFF_FFFF reserved (future SPI flash boot region, Phase 13)
0x0000_0000 0x0FFF_FFFF reserved boot aperture (256 MB decode region)
actual SPI flash device is 16 MB (Arty A7),
mapped at 0x0000_00000x00FF_FFFF in Phase 14
0x1000_0000 0x1000_0FFF MMIO (UART; later: timer, PLIC)
0x2000_0000 0x2000_FFFF instruction BRAM (64 KB)
0x8000_0000 0x8000_FFFF data BRAM (64 KB) → grows to DRAM 0x8000_00000x8FFF_FFFF in Phase 13
0x8000_0000 0x8000_FFFF data BRAM (64 KB) → grows to DRAM 0x8000_00000x8FFF_FFFF in Phase 14
```
Reset PC = `0x2000_0000`. Linker script anchors text/rodata at `0x2000_0000`
@@ -89,6 +97,46 @@ UART register layout (split, not 16550-style):
0x1000_0008 status (R: bit0 = tx_busy, bit1 = rx_valid)
```
This is fine for bare-metal firmware but is *not* directly Linux-compatible:
the in-tree `8250/16550` driver expects a different register layout. Phase 16
(Linux Boot Contract) revisits this — either add a 16550-compatible wrapper
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.
```
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)
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
```
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
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.
## When Helping With This Project
- Always check rv32_pkg.sv first to understand current struct definitions and enums.