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:
@@ -26,9 +26,12 @@ See ROADMAP.md for the full phased plan.
|
|||||||
active-high.
|
active-high.
|
||||||
- BRAM init files: `mem/*.mem` in hex format, one 32-bit word per line.
|
- BRAM init files: `mem/*.mem` in hex format, one 32-bit word per line.
|
||||||
- Firmware source: `fw/` directory. Built with `riscv64-unknown-elf-gcc`
|
- Firmware source: `fw/` directory. Built with `riscv64-unknown-elf-gcc`
|
||||||
(multilib build required), `-march=rv32im_zicsr_zifencei -mabi=ilp32`. The
|
(multilib build required). March string evolves with the implemented ISA:
|
||||||
full march string is required by GCC 11+ even before CSRs are implemented;
|
- Phase 8 (pre-CSR bare-metal): `-march=rv32im -mabi=ilp32`
|
||||||
`Zifencei` is a NOP until caches exist (Phase 13+).
|
- 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
|
## Directory Structure
|
||||||
|
|
||||||
@@ -47,16 +50,19 @@ docs/ — block diagrams, notes
|
|||||||
|
|
||||||
## ISA Target
|
## ISA Target
|
||||||
|
|
||||||
RV32IM base. Extensions added incrementally:
|
End target: **RV32IMA + Zicsr + Zifencei + M/S/U privilege + Sv32** (Linux-capable).
|
||||||
- Zicsr (CSR instructions) — Phase 9
|
|
||||||
- Zifencei (fence.i) — implemented as NOP until caches exist
|
Extensions land incrementally:
|
||||||
- M-mode privileged — Phase 9
|
- RV32IM base — Phases 1-8
|
||||||
- S-mode + U-mode + Sv32 — Phase 14
|
- 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
|
## Key Design Decisions
|
||||||
|
|
||||||
- Single-cycle first, pipeline later (Phase 12). Stages are separated in the code
|
- Single-cycle first, pipeline later (Phase 13, optional). Stages are separated
|
||||||
even without pipeline registers between them.
|
in the code even without pipeline registers between them.
|
||||||
- "Single-cycle" is a logical model, not a literal one cycle per instruction.
|
- "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
|
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.
|
cycles. The datapath stalls fetch while a multi-cycle operation is in flight.
|
||||||
@@ -73,10 +79,12 @@ RV32IM base. Extensions added incrementally:
|
|||||||
## Memory Map
|
## 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_0000–0x00FF_FFFF in Phase 14
|
||||||
0x1000_0000 – 0x1000_0FFF MMIO (UART; later: timer, PLIC)
|
0x1000_0000 – 0x1000_0FFF MMIO (UART; later: timer, PLIC)
|
||||||
0x2000_0000 – 0x2000_FFFF instruction BRAM (64 KB)
|
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 13
|
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`
|
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)
|
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
|
## When Helping With This Project
|
||||||
|
|
||||||
- Always check rv32_pkg.sv first to understand current struct definitions and enums.
|
- Always check rv32_pkg.sv first to understand current struct definitions and enums.
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
# FPGA-Core
|
# FPGA-Core
|
||||||
|
|
||||||
Custom RV32IM RISC-V CPU core built from scratch in SystemVerilog.
|
Custom RISC-V CPU core built from scratch in SystemVerilog. Starts as RV32IM,
|
||||||
|
grows to RV32IMA + Sv32 + M/S/U privilege en route to booting Linux.
|
||||||
|
|
||||||
## Target
|
## Target
|
||||||
|
|
||||||
- Board: Digilent Arty A7 100T (xc7a100tcsg324-1)
|
- Board: Digilent Arty A7 100T (xc7a100tcsg324-1)
|
||||||
- Toolchain: Vivado 2025.2 or later
|
- Toolchain: Vivado 2025.2 or later
|
||||||
- Language: SystemVerilog
|
- Language: SystemVerilog
|
||||||
- ISA: RV32IM + Zicsr + Zifencei + M-mode privileged (extending toward Linux)
|
- ISA: end target **RV32IMA + Zicsr + Zifencei + M/S/U privilege + Sv32**
|
||||||
|
(Linux-capable). Built incrementally — see `ROADMAP.md`.
|
||||||
- Clock: 50 MHz (100 MHz xtal ÷ 2 via MMCM)
|
- Clock: 50 MHz (100 MHz xtal ÷ 2 via MMCM)
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
@@ -17,16 +19,16 @@ Custom RV32IM RISC-V CPU core built from scratch in SystemVerilog.
|
|||||||
unit (DSP-based multiply, iterative divide) sit side by side; the datapath
|
unit (DSP-based multiply, iterative divide) sit side by side; the datapath
|
||||||
stalls on M-unit and BRAM accesses. Inter-stage signals are typed structs in
|
stalls on M-unit and BRAM accesses. Inter-stage signals are typed structs in
|
||||||
`rv32_pkg.sv` — the same structs become pipeline registers when the core is
|
`rv32_pkg.sv` — the same structs become pipeline registers when the core is
|
||||||
pipelined later (Phase 12).
|
pipelined later (Phase 13, optional).
|
||||||
|
|
||||||
## Memory Map
|
## Memory Map
|
||||||
|
|
||||||
| Address Range | Region |
|
| Address Range | Region |
|
||||||
|---------------------------|----------------------------------------------|
|
|---------------------------|----------------------------------------------|
|
||||||
| `0x0000_0000–0x0FFF_FFFF` | reserved (SPI flash boot region, Phase 13) |
|
| `0x0000_0000–0x0FFF_FFFF` | reserved boot aperture (Phase 14: 16 MB SPI flash at low end) |
|
||||||
| `0x1000_0000–0x1000_0FFF` | MMIO (UART; later: timer, PLIC) |
|
| `0x1000_0000–0x1000_0FFF` | MMIO (UART; later: timer, PLIC) |
|
||||||
| `0x2000_0000–0x2000_FFFF` | instruction BRAM (64 KB) |
|
| `0x2000_0000–0x2000_FFFF` | instruction BRAM (64 KB) |
|
||||||
| `0x8000_0000–0x8000_FFFF` | data BRAM (64 KB) → DRAM in Phase 13 |
|
| `0x8000_0000–0x8000_FFFF` | data BRAM (64 KB) → DRAM in Phase 14 |
|
||||||
|
|
||||||
Reset PC = `0x2000_0000`.
|
Reset PC = `0x2000_0000`.
|
||||||
|
|
||||||
@@ -42,8 +44,9 @@ UART registers (split layout):
|
|||||||
|
|
||||||
Requires:
|
Requires:
|
||||||
- Vivado 2025.2 or later
|
- Vivado 2025.2 or later
|
||||||
- RISC-V GCC toolchain — multilib `riscv64-unknown-elf-gcc` supporting
|
- RISC-V GCC toolchain — multilib `riscv64-unknown-elf-gcc`. March string
|
||||||
`-march=rv32im_zicsr_zifencei -mabi=ilp32`
|
evolves with the implementation: `rv32im` (Phase 8) →
|
||||||
|
`rv32im_zicsr_zifencei` (Phase 9) → `rv32ima_zicsr_zifencei` (Phase 12).
|
||||||
- `riscv-tests` (for compliance verification from Phase 8.5)
|
- `riscv-tests` (for compliance verification from Phase 8.5)
|
||||||
- Serial terminal (minicom/picocom/PuTTY) at 115200 8N1
|
- Serial terminal (minicom/picocom/PuTTY) at 115200 8N1
|
||||||
|
|
||||||
|
|||||||
+189
-49
@@ -5,13 +5,15 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Memory Map (target — fixed in Phase 0, evolves through Phase 13)
|
## Memory Map (target — fixed in Phase 0, evolves through Phase 14)
|
||||||
|
|
||||||
```
|
```
|
||||||
0x0000_0000 – 0x0FFF_FFFF reserved (SPI flash boot region, Phase 13)
|
0x0000_0000 – 0x0FFF_FFFF reserved boot aperture (256 MB decode region;
|
||||||
|
actual SPI flash is 16 MB on Arty A7,
|
||||||
|
mapped at 0x0000_0000–0x00FF_FFFF in Phase 14)
|
||||||
0x1000_0000 – 0x1000_0FFF MMIO (UART; later: timer, PLIC)
|
0x1000_0000 – 0x1000_0FFF MMIO (UART; later: timer, PLIC)
|
||||||
0x2000_0000 – 0x2000_FFFF instruction BRAM (64 KB)
|
0x2000_0000 – 0x2000_FFFF instruction BRAM (64 KB)
|
||||||
0x8000_0000 – 0x8000_FFFF data BRAM (64 KB) → DRAM 0x8000_0000–0x8FFF_FFFF in Phase 13
|
0x8000_0000 – 0x8000_FFFF data BRAM (64 KB) → DRAM 0x8000_0000–0x8FFF_FFFF in Phase 14
|
||||||
```
|
```
|
||||||
|
|
||||||
Reset PC = `0x2000_0000`. Locking this in now keeps the linker script and crt0
|
Reset PC = `0x2000_0000`. Locking this in now keeps the linker script and crt0
|
||||||
@@ -237,9 +239,22 @@ Future role: Final. These instructions don't change.
|
|||||||
|
|
||||||
### 6.1 — Word Load/Store (lw, sw)
|
### 6.1 — Word Load/Store (lw, sw)
|
||||||
What: Add a data BRAM mapped at `0x8000_0000` (64 KB) and a load/store unit.
|
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 a simple memory bus
|
For now, only 32-bit aligned access (lw and sw).
|
||||||
interface with address, write data, read data, write enable, and valid/ready
|
|
||||||
signals.
|
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):
|
||||||
|
|
||||||
|
- `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`
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
Note: BRAM is 1-cycle read latency, so a load takes one extra cycle beyond
|
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 →
|
the EXECUTE state. The control FSM extends to FETCH → EXECUTE → MEM_WAIT →
|
||||||
@@ -269,9 +284,21 @@ to 32 bits, lbu zero-extends).
|
|||||||
Why: C uses char (byte) and short (halfword) types constantly. String operations are
|
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.
|
byte-by-byte. You can't run real C code without these.
|
||||||
|
|
||||||
Testbench focus: Sign extension (loading 0xFF as signed byte should give 0xFFFFFFFF,
|
Implementation: the load/store unit drives `req_size` (00=byte, 01=halfword,
|
||||||
as unsigned should give 0x000000FF). Unaligned access behavior (RISC-V base spec says
|
10=word) and `req_wstrb` (which byte lane within the 32-bit word is being
|
||||||
misaligned access can trap — decide if you want to support it or trap).
|
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.
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|
||||||
Future role: Final. These instructions don't change.
|
Future role: Final. These instructions don't change.
|
||||||
|
|
||||||
@@ -373,16 +400,24 @@ crt0 grows when you add CSRs (setting up trap vectors in startup).
|
|||||||
What: Write a trivial main() that prints a string to the UART by writing bytes
|
What: Write a trivial main() that prints a string to the UART by writing bytes
|
||||||
to `0x1000_0000`. Compile with:
|
to `0x1000_0000`. Compile with:
|
||||||
```
|
```
|
||||||
riscv64-unknown-elf-gcc -march=rv32im_zicsr_zifencei -mabi=ilp32 \
|
riscv64-unknown-elf-gcc -march=rv32im -mabi=ilp32 \
|
||||||
-nostdlib -T linker.ld -o firmware.elf crt0.S main.c
|
-nostdlib -T linker.ld -o firmware.elf crt0.S main.c
|
||||||
```
|
```
|
||||||
Convert: `objcopy -O binary firmware.elf firmware.bin`. Convert binary to
|
Convert: `objcopy -O binary firmware.elf firmware.bin`. Convert binary to
|
||||||
`.mem` format. Load into BRAM. Run.
|
`.mem` format. Load into BRAM. Run.
|
||||||
|
|
||||||
Why the long march string: GCC 11+ split `Zicsr` and `Zifencei` out of the
|
March string discipline: advertise only what the hardware decodes. `rv32im`
|
||||||
RV32I base. `-march=rv32im` alone fails or warns. `Zifencei` is harmless
|
is correct for Phase 8 because CSRs and `fence.i` aren't decoded yet. When
|
||||||
(`fence.i` is a NOP until you have caches); `Zicsr` will only be emitted by
|
the program (or crt0) uses CSR/fence.i ops before that, the bus will return
|
||||||
code that uses CSR ops, so it's safe to advertise even before Phase 9.
|
`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.
|
||||||
|
|
||||||
|
Note on GCC 11+: newer toolchains require `_zicsr` / `_zifencei` in the
|
||||||
|
march string only when source code actually uses CSR or `fence.i`
|
||||||
|
instructions (since those moved out of base RV32I in the 2019 spec). Pure
|
||||||
|
arithmetic + UART-poke C does not need them.
|
||||||
|
|
||||||
Why: This proves your CPU is compatible with a real compiler. Any bugs in your
|
Why: This proves your CPU is compatible with a real compiler. Any bugs in your
|
||||||
instruction implementation will surface here — GCC will use instructions in
|
instruction implementation will surface here — GCC will use instructions in
|
||||||
@@ -392,12 +427,23 @@ Expect to iterate: GCC will probably emit an instruction you haven't implemented
|
|||||||
That's fine — check the illegal instruction, add it, resynthesize. This is normal.
|
That's fine — check the illegal instruction, add it, resynthesize. This is normal.
|
||||||
|
|
||||||
### 8.3 — Fill Remaining RV32I Gaps
|
### 8.3 — Fill Remaining RV32I Gaps
|
||||||
What: Implement any RV32I instructions you deferred. Common ones: all shift variants
|
What: Implement any RV32I instructions you deferred. Common ones: all shift
|
||||||
(sll, srl, sra, slli, srli, srai), set-less-than variants (slti, sltiu), fence (NOP
|
variants (sll, srl, sra, slli, srli, srai), set-less-than variants (slti,
|
||||||
for now — you have no cache), ecall/ebreak (NOP for now — you have no trap handling).
|
sltiu), `fence` (decode as NOP — there is no cache yet, so memory ordering
|
||||||
|
is trivially satisfied).
|
||||||
|
|
||||||
Why: GCC's output will exercise the full ISA. You need complete RV32I coverage for
|
`ecall` / `ebreak` / unrecognized opcodes / `fence.i` / any CSR op all go
|
||||||
any non-trivial C code to work.
|
to a single illegal-instruction handler that **halts the core and asserts a
|
||||||
|
testbench-visible `illegal_instr` signal** with the offending PC and
|
||||||
|
instruction word latched. Do NOT decode them as NOPs — GCC emits `ebreak`
|
||||||
|
in `__builtin_trap`, abort paths, and some divide-by-zero configurations,
|
||||||
|
and silently NOPing past those destroys debuggability. The halt becomes a
|
||||||
|
real exception in Phase 9 (`mcause = 2` illegal, `mcause = 3` ebreak,
|
||||||
|
`mcause = 11` ecall-from-M).
|
||||||
|
|
||||||
|
Why: GCC's output will exercise the full ISA. You need complete RV32I
|
||||||
|
coverage for any non-trivial C code to work, and you need loud failures —
|
||||||
|
not silent ones — for the instructions you haven't implemented yet.
|
||||||
|
|
||||||
Test: Compile progressively more complex C programs. String manipulation, struct
|
Test: Compile progressively more complex C programs. String manipulation, struct
|
||||||
usage, switch statements (these generate jump tables — exercises jalr with computed
|
usage, switch statements (these generate jump tables — exercises jalr with computed
|
||||||
@@ -473,60 +519,154 @@ the PLIC manages them. This is required for Linux.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Phase 12 — Pipeline (Optional but educational)
|
## Phase 12 — A Extension (Atomics)
|
||||||
|
|
||||||
What: Insert pipeline registers between your stages (fetch|decode|execute|memory|
|
What: Implement the RV32A atomic instructions: `lr.w` / `sc.w` (load-reserved /
|
||||||
writeback). Handle data hazards (forwarding/stalling) and control hazards (branch
|
store-conditional) and the AMO ops (`amoswap.w`, `amoadd.w`, `amoand.w`,
|
||||||
prediction or pipeline flush).
|
`amoor.w`, `amoxor.w`, `amomin.w`, `amomax.w`, `amominu.w`, `amomaxu.w`).
|
||||||
|
Switch the GCC march string to `rv32ima_zicsr_zifencei`. Run `rv32ua` from
|
||||||
|
`riscv-tests`.
|
||||||
|
|
||||||
Why: Your single-cycle core's clock speed is limited by the longest combinational
|
Why now: Mainline Linux's RISC-V kernel is built against `rv32ima` /
|
||||||
path (probably through the ALU). Pipelining lets each stage run in one short cycle.
|
`rv64ima` — atomics are not optional for an unmodified kernel build. The
|
||||||
This is the classic computer architecture exercise and deeply educational.
|
kernel uses LR/SC and AMO for spinlocks, refcounts, and futexes; without
|
||||||
|
them you maintain a non-standard fork. Adding A before the Linux work means
|
||||||
|
you discover atomics-related bugs in a small, focused phase rather than
|
||||||
|
wedged inside a kernel boot.
|
||||||
|
|
||||||
Why optional: A single-cycle core can run at maybe 50-80 MHz on the Artix-7. That's
|
Single-hart implementation: the reservation set is just one register
|
||||||
plenty for booting Linux. Pipeline if you want to learn, not because you must.
|
(reserved address + valid bit). LR sets it; any store to that address (or
|
||||||
|
context switch) clears it; SC checks it and either commits or returns 1.
|
||||||
|
AMOs are "read, op, write" sequences that the bus must perform atomically —
|
||||||
|
on this single-master core that's free; the load/store unit just holds the
|
||||||
|
bus across the read-modify-write. This becomes meaningful only when DRAM
|
||||||
|
or DMA enters the picture.
|
||||||
|
|
||||||
|
Future role: Final for single-hart. If you ever go multi-hart, the
|
||||||
|
reservation set and bus become more involved.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Phase 13 — SPI Flash Boot + DRAM
|
## Phase 13 — Pipeline (Optional but educational)
|
||||||
|
|
||||||
What: Add an SPI flash controller to boot from the on-board flash (instead of BRAM
|
What: Insert pipeline registers between your stages (fetch|decode|execute|
|
||||||
initialization). Integrate AMD/Xilinx MIG IP for the DDR3 on the Arty A7. Update
|
memory|writeback). Handle data hazards (forwarding/stalling) and control
|
||||||
your memory map: flash at boot, copy to DRAM, jump to DRAM.
|
hazards (branch prediction or pipeline flush).
|
||||||
|
|
||||||
Why: BRAM is tiny (a few hundred KB on Artix-7 100T). Linux needs megabytes. DRAM
|
Why: Your single-cycle core's clock speed is limited by the longest
|
||||||
gives you 256MB. Flash gives you persistent storage for the bootloader. This is
|
combinational path (probably through the ALU). Pipelining lets each stage
|
||||||
how real embedded systems boot.
|
run in one short cycle. This is the classic computer architecture exercise
|
||||||
|
and deeply educational.
|
||||||
|
|
||||||
|
Why optional: A non-pipelined core can run at 50 MHz on the Artix-7. That's
|
||||||
|
plenty for booting Linux. Pipeline if you want to learn, not because you
|
||||||
|
must — and if you do, do it before DRAM/firmware work piles on. Re-running
|
||||||
|
`riscv-tests` (Phase 8.5 + 12) after pipelining catches regressions.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Phase 14 — S-Mode, U-Mode, Sv32 Virtual Memory
|
## Phase 14 — SPI Flash Boot + DRAM
|
||||||
|
|
||||||
What: Add supervisor and user privilege modes. Implement Sv32 page table walking
|
What: Add an SPI flash controller to boot from the on-board flash (instead
|
||||||
(two-level page tables, 4KB pages). Add the satp CSR, page fault exceptions, and
|
of BRAM initialization). The Arty A7-100T has a 16 MB Quad-SPI flash; map
|
||||||
sfence.vma.
|
it at `0x0000_0000–0x00FF_FFFF` inside the reserved boot aperture.
|
||||||
|
Integrate AMD/Xilinx MIG IP for the DDR3L on the Arty (256 MB). Map DRAM at
|
||||||
|
`0x8000_0000–0x8FFF_FFFF`, replacing the data BRAM in that range.
|
||||||
|
|
||||||
Why: Linux runs the kernel in S-mode, user programs in U-mode. Virtual memory gives
|
Boot flow: reset PC moves to `0x0000_0000` (flash). A small first-stage
|
||||||
each process its own address space and protects the kernel from user code. This is the
|
copies the kernel/firmware image from flash to DRAM, sets up the trap
|
||||||
last major architectural piece before Linux.
|
vector, and jumps to DRAM.
|
||||||
|
|
||||||
|
Why: BRAM is tiny (~600 KB total on Artix-7 100T). Linux needs megabytes.
|
||||||
|
DRAM gives you 256 MB; flash gives you persistent storage for the
|
||||||
|
bootloader. This is how real embedded systems boot.
|
||||||
|
|
||||||
|
Bus implications: DRAM via MIG has multi-cycle, variable-latency responses.
|
||||||
|
This is the first time `req_valid`/`req_ready`/`rsp_valid`/`rsp_ready`
|
||||||
|
actually matter — the handshake you wired in Phase 6.1 finally earns its
|
||||||
|
keep.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Phase 15 — Linux
|
## Phase 15 — S-Mode, U-Mode, Sv32 Virtual Memory
|
||||||
|
|
||||||
What: Port a minimal Linux kernel (or use an existing RISC-V port). Write a device
|
What: Add supervisor and user privilege modes. Implement Sv32 page table
|
||||||
tree for your SoC. Build an initramfs with BusyBox. Boot to a shell prompt.
|
walking (two-level page tables, 4 KB pages). Add the `satp` CSR, page-fault
|
||||||
|
exceptions (instruction/load/store page-fault), `sfence.vma`, and S-mode
|
||||||
|
CSRs (`sstatus`, `stvec`, `sepc`, `scause`, `stval`, `sie`, `sip`,
|
||||||
|
`sscratch`).
|
||||||
|
|
||||||
Why: This is the summit. A Linux shell running on a CPU you built from scratch.
|
Why: Linux runs the kernel in S-mode and user programs in U-mode. Virtual
|
||||||
|
memory gives each process its own address space and protects the kernel
|
||||||
|
from user code. This is the last big architectural piece before Linux.
|
||||||
|
|
||||||
|
Test: a hand-written M-mode "kernel" that maps a U-mode page, traps on
|
||||||
|
syscall, returns a value, then returns to U-mode. Verify all three privilege
|
||||||
|
modes round-trip via `mret` / `sret`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 16 — Linux Boot Contract (SBI / Device Tree / ABI)
|
||||||
|
|
||||||
|
What: Lock down everything Linux expects at the moment of `kernel_entry`.
|
||||||
|
This is a real subproject, not a footnote inside "port Linux".
|
||||||
|
|
||||||
|
Required deliverables:
|
||||||
|
- **Boot register state at kernel entry**: `a0` = hart ID (0 for single-hart),
|
||||||
|
`a1` = physical address of the device tree blob, `satp = 0`, all other
|
||||||
|
state per the [RISC-V Linux boot protocol](https://docs.kernel.org/arch/riscv/boot.html).
|
||||||
|
- **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.
|
||||||
|
- **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
|
||||||
|
offset 5, IER at offset 1, FCR/LCR mostly stubs). Then the in-tree
|
||||||
|
`8250_dw` or `of_serial` driver works with a stock binding.
|
||||||
|
- (b) Write a small custom Linux serial driver and define a
|
||||||
|
`compatible = "fpgacore,uart"` binding. More work, more learning.
|
||||||
|
- **Boot firmware**: choose between
|
||||||
|
- (a) **Direct M-mode Linux**: use the kernel's `CONFIG_RISCV_M_MODE`
|
||||||
|
path. Less portable, no SBI required, fewer moving pieces. Reasonable
|
||||||
|
for a learning bring-up.
|
||||||
|
- (b) **OpenSBI + S-mode Linux**: build OpenSBI as the M-mode firmware,
|
||||||
|
Linux runs in S-mode. Standard production path, more Vivado/Linux build
|
||||||
|
surface area.
|
||||||
|
|
||||||
|
Why: Skipping this phase means hitting all of these problems simultaneously
|
||||||
|
during Phase 17 bring-up, where the failure mode is "kernel hangs silently
|
||||||
|
in early boot" with no console. Doing it explicitly turns Phase 17 into a
|
||||||
|
debugging exercise on a known-good ABI surface.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 17 — Linux
|
||||||
|
|
||||||
|
What: Build a minimal RISC-V Linux kernel against the device tree and
|
||||||
|
boot path defined in Phase 16. Build an initramfs with BusyBox. Load
|
||||||
|
kernel + DTB + initramfs to flash. Boot to a shell prompt over UART.
|
||||||
|
|
||||||
|
Why: This is the summit. A Linux shell running on a CPU you built from
|
||||||
|
scratch.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Quick Reference: What You Need Installed
|
## Quick Reference: What You Need Installed
|
||||||
|
|
||||||
- Vivado 2025.2 or later (synthesis, simulation, ILA, VIO)
|
- Vivado 2025.2 or later (synthesis, simulation, ILA, VIO)
|
||||||
- RISC-V GCC toolchain (`riscv64-unknown-elf-gcc`, multilib build — needs to
|
- RISC-V GCC toolchain (`riscv64-unknown-elf-gcc`, multilib build). March
|
||||||
support `rv32im_zicsr_zifencei` / `ilp32`)
|
string evolves: `rv32im` (Phase 8) → `rv32im_zicsr_zifencei` (Phase 9) →
|
||||||
- `riscv-tests` repo cloned and buildable (Phase 8.5 onward)
|
`rv32ima_zicsr_zifencei` (Phase 12).
|
||||||
|
- `riscv-tests` repo cloned and buildable (Phase 8.5 onward; rv32ua added at
|
||||||
|
Phase 12)
|
||||||
|
- Device-tree compiler `dtc` (Phase 16+)
|
||||||
|
- OpenSBI source (Phase 16+, only if you choose the SBI boot path)
|
||||||
- Terminal program (minicom, picocom, or PuTTY) for UART, 115200 8N1
|
- Terminal program (minicom, picocom, or PuTTY) for UART, 115200 8N1
|
||||||
- Text editor you like for SystemVerilog
|
- Text editor you like for SystemVerilog
|
||||||
- The RISC-V ISA spec (Volume 1: Unprivileged, Volume 2: Privileged) — free PDFs
|
- The RISC-V ISA spec (Volume 1: Unprivileged, Volume 2: Privileged) — free PDFs
|
||||||
|
- The Linux RISC-V boot protocol doc (`Documentation/arch/riscv/boot.rst`)
|
||||||
|
|||||||
Reference in New Issue
Block a user