8edfc86027
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.
229 lines
11 KiB
Markdown
229 lines
11 KiB
Markdown
# CLAUDE.md
|
||
|
||
## Project Overview
|
||
|
||
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.
|
||
|
||
See ROADMAP.md for the full phased plan.
|
||
|
||
## Conventions
|
||
|
||
- Language: SystemVerilog (not Verilog). Use SV features: packages, structs, enums,
|
||
always_comb, always_ff, logic (not reg/wire).
|
||
- All inter-stage signals are defined as structs in `rtl/pkg/rv32_pkg.sv`. Always
|
||
import this package. When adding new functionality, extend the existing structs
|
||
rather than adding loose wires.
|
||
- Module naming: `rv32_<block>` (e.g., `rv32_alu`, `rv32_decode`, `rv32_regfile`).
|
||
- File naming: one module per file, filename matches module name.
|
||
- Testbenches: `tb/tb_<module>.sv`. Use Vivado simulator.
|
||
- Clock: single clock domain, active rising edge, signal named `clk`. Target
|
||
frequency: 50 MHz (derived from the Arty's 100 MHz oscillator via MMCM/2).
|
||
- Reset: synchronous active-high, signal named `rst`. The Arty's `CK_RST` button
|
||
is active-low; the top-level wraps it through a 2-FF synchronizer and inverts
|
||
to produce the internal `rst`. The rest of the design only sees synchronous
|
||
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 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
|
||
|
||
```
|
||
rtl/ — synthesizable source
|
||
pkg/ — packages (rv32_pkg.sv)
|
||
core/ — CPU core modules (ALU, M unit, decoder, regfile, datapath)
|
||
periph/ — peripherals (UART, timer, PLIC, etc.)
|
||
top/ — top-level and SoC integration
|
||
tb/ — testbenches (tb_<module>.sv)
|
||
mem/ — BRAM init files (.mem)
|
||
fw/ — firmware source (C, assembly, linker scripts)
|
||
constraints/ — Vivado XDC constraint files
|
||
docs/ — block diagrams, notes
|
||
```
|
||
|
||
## ISA Target
|
||
|
||
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 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.
|
||
- Memory bus uses valid/ready handshake from day one, even though BRAM always
|
||
responds in one cycle. This is for future DRAM compatibility.
|
||
- M extension (multiply/divide) lives in a dedicated multi-cycle M unit
|
||
alongside the combinational ALU, with a `start`/`busy`/`done` handshake.
|
||
Multiply is 2-3 cycles via DSP48s; divide is iterative (~33 cycles). The
|
||
datapath stalls when the M unit is busy. Building this from day one avoids
|
||
retrofitting stall logic later.
|
||
- Harvard architecture (separate instruction and data memory) initially. Unified
|
||
memory when DRAM is added.
|
||
|
||
## Memory Map
|
||
|
||
```
|
||
0x0000_0000 – 0x0FFF_FFFF reserved boot aperture (256 MB decode region)
|
||
actual SPI flash device is 16 MB (Arty A7),
|
||
mapped at 0x0000_0000–0x00FF_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_0000–0x8FFF_FFFF in Phase 14
|
||
```
|
||
|
||
Reset PC = `0x2000_0000`. Linker script anchors text/rodata at `0x2000_0000`
|
||
and data/bss/stack at `0x8000_0000` from Phase 8 onward.
|
||
|
||
UART register layout (split, not 16550-style):
|
||
```
|
||
0x1000_0000 TX data (W: byte to send)
|
||
0x1000_0004 RX data (R: pops one byte from RX FIFO)
|
||
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 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.
|
||
|
||
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.
|
||
|
||
```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
|
||
```
|
||
|
||
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.
|
||
- `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
|
||
|
||
- Always check rv32_pkg.sv first to understand current struct definitions and enums.
|
||
- Reference the RISC-V ISA spec for instruction encoding and behavior.
|
||
- Prefer simulation-testable solutions. Every module should be verifiable standalone
|
||
before integration.
|
||
- Don't add Verilog-style code (reg, wire, always @). Use SV equivalents.
|
||
- When suggesting fixes, consider that the design is single-cycle — there are no
|
||
pipeline hazards yet, but the M unit and BRAM both introduce stalls.
|
||
- ISA correctness is verified against `riscv-tests` (rv32ui, rv32um) starting at
|
||
Phase 8.5. Hand-written testbenches are for module-level checks; the official
|
||
suite is the source of truth for instruction behavior.
|
||
- Vivado quirks: use (* dont_touch = "true" *) for signals that Vivado optimizes
|
||
away during debug. ILA/VIO probes need to be on nets that survive synthesis.
|
||
- The Arty A7 100T has: 101,440 logic cells, 4,860 Kbit BRAM, 240 DSP slices,
|
||
256MB DDR3L SDRAM, USB-UART bridge, 4 LEDs, 4 switches, 4 buttons.
|