Add BIOS and tiny kernel roadmap phases

This commit is contained in:
2026-04-28 13:23:03 +02:00
parent 07dd8e21f0
commit e8631501e8
18 changed files with 344 additions and 77 deletions
+19 -19
View File
@@ -29,8 +29,8 @@ See ROADMAP.md for the full phased plan.
- Firmware source: `fw/` directory. Built with `riscv64-unknown-elf-gcc` - Firmware source: `fw/` directory. Built with `riscv64-unknown-elf-gcc`
(multilib build required). March string evolves with the implemented ISA: (multilib build required). March string evolves with the implemented ISA:
- Phase 8 (pre-CSR bare-metal): `-march=rv32im -mabi=ilp32` - Phase 8 (pre-CSR bare-metal): `-march=rv32im -mabi=ilp32`
- Phase 9+ (CSRs/Zifencei decoded): `-march=rv32im_zicsr_zifencei -mabi=ilp32` - Phase 12+ (CSRs/Zifencei decoded): `-march=rv32im_zicsr_zifencei -mabi=ilp32`
- Phase 12+ (atomics): `-march=rv32ima_zicsr_zifencei -mabi=ilp32` - Phase 15+ (atomics): `-march=rv32ima_zicsr_zifencei -mabi=ilp32`
Don't advertise an extension before its ops are decoded — the compiler is 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. free to emit them, and "trap as illegal" early is much better than NOPing.
@@ -55,14 +55,14 @@ End target: **RV32IMA + Zicsr + Zifencei + M/S/U privilege + Sv32** (Linux-capab
Extensions land incrementally: Extensions land incrementally:
- RV32IM base — Phases 1-8 - RV32IM base — Phases 1-8
- Zicsr (CSR instructions) + M-mode trap handling — Phase 9 - Zicsr (CSR instructions) + M-mode trap handling — Phase 12
- Zifencei (`fence.i`) — decoded but NOP until caches exist (Phase 14+) - Zifencei (`fence.i`) — decoded but NOP until caches exist (Phase 17+)
- A (atomics: LR/SC + AMO) — Phase 12 (required for mainline Linux) - A (atomics: LR/SC + AMO) — Phase 15 (required for mainline Linux)
- S-mode + U-mode + Sv32 — Phase 15 - S-mode + U-mode + Sv32 — Phase 18
## Key Design Decisions ## Key Design Decisions
- Single-cycle first, pipeline later (Phase 13, optional). Stages are separated - Single-cycle first, pipeline later (Phase 16, optional). Stages are separated
in the code 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
@@ -82,10 +82,10 @@ Extensions land incrementally:
``` ```
0x0000_0000 0x0FFF_FFFF reserved boot aperture (256 MB decode region) 0x0000_0000 0x0FFF_FFFF reserved boot aperture (256 MB decode region)
actual SPI flash device is 16 MB (Arty A7), actual SPI flash device is 16 MB (Arty A7),
mapped at 0x0000_00000x00FF_FFFF in Phase 14 mapped at 0x0000_00000x00FF_FFFF in Phase 17
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_00000x8FFF_FFFF in Phase 14 0x8000_0000 0x8000_FFFF data BRAM (64 KB) → grows to DRAM 0x8000_00000x8FFF_FFFF in Phase 17
``` ```
Reset PC = `0x2000_0000`. Linker script anchors text/rodata at `0x2000_0000` Reset PC = `0x2000_0000`. Linker script anchors text/rodata at `0x2000_0000`
@@ -99,7 +99,7 @@ UART register layout (split, not 16550-style):
``` ```
This is fine for bare-metal firmware but is *not* directly Linux-compatible: 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 the in-tree `8250/16550` driver expects a different register layout. Phase 19
(Linux Boot Contract) revisits this — either add a 16550-compatible wrapper (Linux Boot Contract) revisits this — either add a 16550-compatible wrapper
or write a custom Linux serial driver + device-tree binding. or write a custom Linux serial driver + device-tree binding.
@@ -123,7 +123,7 @@ typedef struct packed {
logic [1:0] size; // 00=byte, 01=halfword, 10=word logic [1:0] size; // 00=byte, 01=halfword, 10=word
logic [31:0] wdata; // lane-aligned per wstrb logic [31:0] wdata; // lane-aligned per wstrb
logic [3:0] wstrb; // byte enables (writes only) logic [3:0] wstrb; // byte enables (writes only)
logic [3:0] amo; // AMO op encoding (D-bus only, Phase 12+) logic [3:0] amo; // AMO op encoding (D-bus only, Phase 15+)
} mem_req_t; } mem_req_t;
// Payload — slave → master // Payload — slave → master
@@ -153,7 +153,7 @@ Handshake notes:
producing a response (BRAM = 1 cycle, DRAM = many). producing a response (BRAM = 1 cycle, DRAM = many).
- `wstrb` is mandatory from Phase 6.2 onward (sub-word stores). For - `wstrb` is mandatory from Phase 6.2 onward (sub-word stores). For
Phase 6.1 (word-only), tie strobes to `4'b1111` on stores. 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 - `amo` is unused (drive `4'h0`) until Phase 15; on the I-bus it is always
unused. unused.
- `err` is **only** raised by a slave to report access faults it owns: - `err` is **only** raised by a slave to report access faults it owns:
unmapped address, peripheral-access violation, eventually DRAM ECC fault. unmapped address, peripheral-access violation, eventually DRAM ECC fault.
@@ -173,8 +173,8 @@ separately. Per-phase progression:
| 4 | CPU fetch ↔ instruction BRAM | (none yet) | | 4 | CPU fetch ↔ instruction BRAM | (none yet) |
| 6 | unchanged | LSU ↔ data BRAM (single slave) | | 6 | unchanged | LSU ↔ data BRAM (single slave) |
| 7 | unchanged | LSU ↔ decoder → {data BRAM, UART MMIO} | | 7 | unchanged | LSU ↔ decoder → {data BRAM, UART MMIO} |
| 9-12 | unchanged | + timer (Phase 10), PLIC (Phase 11) | | 12-15 | unchanged | + timer (Phase 13), PLIC (Phase 14) |
| 14 | I-bus → arbiter → DRAM | D-bus → arbiter → DRAM (or MIG dual-port) | | 17 | I-bus → arbiter → DRAM | D-bus → arbiter → DRAM (or MIG dual-port) |
Both buses can fault independently; classification happens in the masters Both buses can fault independently; classification happens in the masters
(see below). (see below).
@@ -195,17 +195,17 @@ transaction adds context to produce the precise architectural cause:
| Store/AMO address misaligned | 6 | LSU (size + addr LSBs, pre-issue) | | Store/AMO address misaligned | 6 | LSU (size + addr LSBs, pre-issue) |
| Store/AMO access fault | 7 | LSU (D-bus `rsp.err` on write) | | Store/AMO access fault | 7 | LSU (D-bus `rsp.err` on write) |
| Ecall from M-mode | 11 | decoder | | Ecall from M-mode | 11 | decoder |
| Instruction page fault | 12 | I-side MMU (Phase 15) | | Instruction page fault | 12 | I-side MMU (Phase 18) |
| Load page fault | 13 | D-side MMU (Phase 15) | | Load page fault | 13 | D-side MMU (Phase 18) |
| Store/AMO page fault | 15 | D-side MMU (Phase 15) | | Store/AMO page fault | 15 | D-side MMU (Phase 18) |
Two consequences: Two consequences:
- The LSU detects misalignment **before** issuing a bus request — never - The LSU detects misalignment **before** issuing a bus request — never
send a request you already know will trap. send a request you already know will trap.
- Page faults (Phase 15) are raised by the MMU during translation, - Page faults (Phase 18) are raised by the MMU during translation,
before the (translated) request hits the bus. before the (translated) request hits the bus.
### Atomics (Phase 12) ### Atomics (Phase 15)
LR/SC and AMO use the `amo` field of `mem_req_t` (D-bus only) rather than 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. a separate bus. Single-hart reservation tracking lives in the LSU.
+4 -4
View File
@@ -19,16 +19,16 @@ grows to RV32IMA + Sv32 + M/S/U privilege en route to booting Linux.
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 13, optional). pipelined later (Phase 16, optional).
## Memory Map ## Memory Map
| Address Range | Region | | Address Range | Region |
|---------------------------|----------------------------------------------| |---------------------------|----------------------------------------------|
| `0x0000_00000x0FFF_FFFF` | reserved boot aperture (Phase 14: 16 MB SPI flash at low end) | | `0x0000_00000x0FFF_FFFF` | reserved boot aperture (Phase 17: 16 MB SPI flash at low end) |
| `0x1000_00000x1000_0FFF` | MMIO (UART; later: timer, PLIC) | | `0x1000_00000x1000_0FFF` | MMIO (UART; later: timer, PLIC) |
| `0x2000_00000x2000_FFFF` | instruction BRAM (64 KB) | | `0x2000_00000x2000_FFFF` | instruction BRAM (64 KB) |
| `0x8000_00000x8000_FFFF` | data BRAM (64 KB) → DRAM in Phase 14 | | `0x8000_00000x8000_FFFF` | data BRAM (64 KB) → DRAM in Phase 17 |
Reset PC = `0x2000_0000`. Reset PC = `0x2000_0000`.
@@ -46,7 +46,7 @@ Requires:
- Vivado 2025.2 or later - Vivado 2025.2 or later
- RISC-V GCC toolchain — multilib `riscv64-unknown-elf-gcc`. March string - RISC-V GCC toolchain — multilib `riscv64-unknown-elf-gcc`. March string
evolves with the implementation: `rv32im` (Phase 8) → evolves with the implementation: `rv32im` (Phase 8) →
`rv32im_zicsr_zifencei` (Phase 9) → `rv32ima_zicsr_zifencei` (Phase 12). `rv32im_zicsr_zifencei` (Phase 12) → `rv32ima_zicsr_zifencei` (Phase 15).
- `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
+148 -33
View File
@@ -21,17 +21,22 @@ Recommended casual target: reach Phase 8, where GCC-built C runs on your CPU and
`riscv-tests` gives you confidence in the ISA implementation. Everything after `riscv-tests` gives you confidence in the ISA implementation. Everything after
that is enrichment or a long-term expedition. that is enrichment or a long-term expedition.
Recommended middle-game target: continue through Phases 9-11 and build your own
BIOS, ELF loader, and tiny kernel before attempting Linux. This gives you a
playable computer with your own firmware and command line without requiring the
full Linux boot contract.
--- ---
## Memory Map (target — fixed in Phase 0, evolves through Phase 14) ## Memory Map (target — fixed in Phase 0, evolves through Phase 17)
``` ```
0x0000_0000 0x0FFF_FFFF reserved boot aperture (256 MB decode region; 0x0000_0000 0x0FFF_FFFF reserved boot aperture (256 MB decode region;
actual SPI flash is 16 MB on Arty A7, actual SPI flash is 16 MB on Arty A7,
mapped at 0x0000_00000x00FF_FFFF in Phase 14) mapped at 0x0000_00000x00FF_FFFF in Phase 17)
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_00000x8FFF_FFFF in Phase 14 0x8000_0000 0x8000_FFFF data BRAM (64 KB) → DRAM 0x8000_00000x8FFF_FFFF in Phase 17
``` ```
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
@@ -165,7 +170,7 @@ extension (bit 31 of the instruction is always the sign bit in RISC-V — that's
deliberate design choice). Verify that the decoder handles all R-type, I-type, S-type, deliberate design choice). Verify that the decoder handles all R-type, I-type, S-type,
B-type, U-type, and J-type correctly. B-type, U-type, and J-type correctly.
Future role: When you add CSR instructions (Phase 9), you'll add a new case to the Future role: When you add CSR instructions (Phase 12), you'll add a new case to the
decoder and a new field to the struct. The structure of the decoder doesn't change. decoder and a new field to the struct. The structure of the decoder doesn't change.
--- ---
@@ -260,8 +265,8 @@ 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). For now, only 32-bit aligned access (lw and sw).
Define the full memory bus contract here, not just the subset Phase 6.1 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 uses. Defining it once means Phase 6.2 (sub-word), Phase 7 (MMIO), Phase 12
(access faults), Phase 12 (atomics), and Phase 14 (DRAM) all plug into the (access faults), Phase 15 (atomics), and Phase 17 (DRAM) all plug into the
same interface without rework. The contract (see `CLAUDE.md` for the full 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 spec) is two payload structs (`mem_req_t`, `mem_rsp_t`) plus loose
valid/ready signals on each channel: valid/ready signals on each channel:
@@ -442,8 +447,8 @@ 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 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. instruction word — catching the bug visibly rather than NOPing through it.
(Illegal instructions are a decode event, not a bus event; the memory bus (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 is not involved.) Switch to `rv32im_zicsr_zifencei` in Phase 12 once CSRs
land, and `rv32ima_zicsr_zifencei` in Phase 12 once atomics land. land, and `rv32ima_zicsr_zifencei` in Phase 15 once atomics land.
Note on GCC 11+: newer toolchains require `_zicsr` / `_zifencei` in the Note on GCC 11+: newer toolchains require `_zicsr` / `_zifencei` in the
march string only when source code actually uses CSR or `fence.i` march string only when source code actually uses CSR or `fence.i`
@@ -469,7 +474,7 @@ testbench-visible `illegal_instr` signal** with the offending PC and
instruction word latched. Do NOT decode them as NOPs — GCC emits `ebreak` instruction word latched. Do NOT decode them as NOPs — GCC emits `ebreak`
in `__builtin_trap`, abort paths, and some divide-by-zero configurations, in `__builtin_trap`, abort paths, and some divide-by-zero configurations,
and silently NOPing past those destroys debuggability. The halt becomes a and silently NOPing past those destroys debuggability. The halt becomes a
real exception in Phase 9 (`mcause = 2` illegal, `mcause = 3` ebreak, real exception in Phase 12 (`mcause = 2` illegal, `mcause = 3` ebreak,
`mcause = 11` ecall-from-M). `mcause = 11` ecall-from-M).
Why: GCC's output will exercise the full ISA. You need complete RV32I Why: GCC's output will exercise the full ISA. You need complete RV32I
@@ -480,10 +485,11 @@ 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
addresses), recursive functions. addresses), recursive functions.
### 8.4 — Milestone: Meaningful C Program ### 8.4 — Milestone: Meaningful Standalone C Program
What: Write something real — a serial monitor that accepts commands over UART What: Write something real but still self-contained — a checksum demo, tiny
and responds. Or a tiny Forth interpreter. Something interactive that proves benchmark, string/array exercise, or simple UART-driven calculator. Keep the
the core is solid. full serial monitor for Phase 9, where it becomes the BIOS instead of a one-off
test program.
Why: Confidence builder. You now have a working RISC-V computer that runs Why: Confidence builder. You now have a working RISC-V computer that runs
compiled C and talks over serial. Everything after this is enrichment. compiled C and talks over serial. Everything after this is enrichment.
@@ -499,7 +505,7 @@ BRAM, runs the core until the test signals completion, and reports pass/fail.
Why: Hand-written testbenches catch the bugs you thought to look for. The Why: Hand-written testbenches catch the bugs you thought to look for. The
official suite catches the ones you didn't — corner cases in shifts, sign official suite catches the ones you didn't — corner cases in shifts, sign
extension on byte loads, immediate decoding for every format, M-extension extension on byte loads, immediate decoding for every format, M-extension
overflow cases. Doing this *before* Phase 9 means you're building trap overflow cases. Doing this *before* Phase 12 means you're building trap
handling on a known-good ISA implementation, not stacking unknowns. handling on a known-good ISA implementation, not stacking unknowns.
Expect to find bugs. That's the point. Fix each one, re-run the suite, move Expect to find bugs. That's the point. Fix each one, re-run the suite, move
@@ -510,7 +516,116 @@ becomes regression coverage for the rest of the project.
--- ---
## Phase 9 — CSRs + M-Mode Trap Handling [Hard stretch] ## Phase 9 — GCC-Built BIOS / Serial Monitor [Easy/fun]
What: Build a small BIOS in freestanding C/assembly with its own linker script,
startup code, UART driver, and command loop. It runs from the existing BRAM
image and gives you an interactive prompt over serial.
Suggested commands:
- `help` — list commands
- `peek <addr>` / `poke <addr> <value>` — inspect and edit memory-mapped state
- `dump <addr> <len>` — hex-dump memory
- `fill <addr> <len> <value>` — initialize memory
- `regs` — print a software-maintained register/trap snapshot when available
- `memtest` — run a small RAM/BRAM test
- `load` — receive a raw binary or hex stream over UART
- `run <addr>` — jump to a loaded program
- `reboot` — return to reset or spin until manual reset
Important hardware contract: a UART-loaded program needs somewhere executable
to live. In the early Harvard design, instruction BRAM is fetch-only unless you
deliberately expose a writable path. Pick one simple learning-friendly option:
- make instruction BRAM dual-port, with the CPU fetch path on one port and a
D-bus write/debug port on the other;
- add a small "program RAM" window that is writable through the D-bus and
fetchable through the I-bus;
- or defer `run`/ELF execution until you add one of those executable write paths.
Why: This is the first point where the board feels like your own computer. It
also becomes a practical debug tool for later hardware and firmware work.
Test: Boot to a prompt, use `poke`/`peek` on UART registers and RAM, load a tiny
raw program, jump to it, and have it return or print a message.
Future role: The BIOS can remain as a recovery/debug monitor even after flash,
DRAM, and Linux enter the picture.
---
## Phase 10 — Minimal ELF Loader [Easy/fun]
What: Teach the BIOS to receive and run the simplest possible RISC-V ELF32
binaries. Support statically linked, non-relocatable, little-endian
`EM_RISCV` executables with `PT_LOAD` segments only. Copy each loadable segment
to its physical address, zero the `memsz - filesz` tail for BSS, set a known
stack pointer, and jump to `e_entry`.
Deliberate limits:
- no dynamic linking
- no relocations
- no virtual memory
- no privilege separation
- no demand paging
- no filesystem
ABI for tiny programs: start with a tiny fixed contract. For example, `a0`
receives a pointer to a BIOS call table, `sp` points at the top of data RAM,
and returning from `main` jumps back to the monitor with an integer status.
Before Phase 12 traps exist, programs are trusted firmware payloads, not isolated
user processes.
Why: ELF loading is a major confidence milestone. You are no longer baking every
program into the FPGA bitstream; you can compile a new binary, send it over
serial, and run it on your CPU.
Test: Compile several tiny programs with fixed link addresses: hello world,
integer arithmetic, memory copy, and a program that returns a status code to
the BIOS. Confirm the BIOS rejects malformed or unsupported ELF files loudly.
Future role: This loader becomes the conceptual ancestor of the later bootloader
that copies kernels from flash to DRAM.
---
## Phase 11 — Tiny Kernel + Command Shell [Easy/fun]
What: Build a tiny kernel as a separate GCC-built ELF loaded by the BIOS.
Before Phase 12, "kernel" means bare-machine firmware with a command shell, not
an isolated privileged OS. It owns a simple console, command parser, memory
allocator, and a table of services. Keep it intentionally small: no MMU, no
userspace isolation, no real filesystem.
Suggested kernel features:
- banner and prompt
- `help`, `uptime`, `mem`, `dump`, `loadelf`, `run`, `reboot`
- direct UART console driver or BIOS-backed console calls
- bump allocator for kernel data structures
- simple program table showing loaded ELF images
- trusted program launch with an agreed calling convention
- status return from launched programs back to the shell
Simple ELF binaries: compile tiny freestanding programs that use the kernel or
BIOS service table for `putchar`, `getchar`, and exit. The first binaries can be
as small as "print a line", "sum an array", and "echo typed characters". The
goal is not POSIX; the goal is a complete end-to-end loop: compile on your host,
send over UART, load as ELF, run on your CPU, return to your shell.
Why: This is the rewarding middle game between "bare-metal C works" and "Linux
boot hangs somewhere in early init." You get OS-shaped learning while the system
is still small enough to understand in one sitting.
Future role: Phase 12 turns `ecall`, exceptions, and trap vectors into real
architectural mechanisms. At that point, the ad hoc service table can evolve into
a syscall ABI and the kernel can start handling faults instead of trusting every
program.
---
## Phase 12 — CSRs + M-Mode Trap Handling [Hard stretch]
What: Add Control and Status Registers (mstatus, mtvec, mepc, mcause, mtval, mie, What: Add Control and Status Registers (mstatus, mtvec, mepc, mcause, mtval, mie,
mip) and the CSR instructions (csrrw, csrrs, csrrc, csrrwi, csrrsi, csrrci). Add mip) and the CSR instructions (csrrw, csrrs, csrrc, csrrwi, csrrsi, csrrci). Add
@@ -527,18 +642,18 @@ plus S-mode (supervisor mode), which you'll add later.
--- ---
## Phase 10 — Timer [Hard stretch] ## Phase 13 — Timer [Hard stretch]
What: Implement mtime (a free-running 64-bit counter) and mtimecmp (comparison What: Implement mtime (a free-running 64-bit counter) and mtimecmp (comparison
register). When mtime >= mtimecmp, a timer interrupt fires. register). When mtime >= mtimecmp, a timer interrupt fires.
Why: Every operating system needs a timer tick for scheduling. Even bare-metal Why: Every operating system needs a timer tick for scheduling. Even bare-metal
firmware needs delays and timeouts. This is your first interrupt source, which also firmware needs delays and timeouts. This is your first interrupt source, which also
validates that the trap handling from Phase 9 actually works end-to-end. validates that the trap handling from Phase 12 actually works end-to-end.
--- ---
## Phase 11 — Interrupt Controller [Hard stretch] ## Phase 14 — Interrupt Controller [Hard stretch]
What: Build a minimal PLIC (Platform-Level Interrupt Controller) or a simplified What: Build a minimal PLIC (Platform-Level Interrupt Controller) or a simplified
version. Connect UART RX as an interrupt source. Implement interrupt priority and version. Connect UART RX as an interrupt source. Implement interrupt priority and
@@ -550,7 +665,7 @@ the PLIC manages them. This is required for Linux.
--- ---
## Phase 12 — A Extension (Atomics) [Hard stretch] ## Phase 15 — A Extension (Atomics) [Hard stretch]
What: Implement the RV32A atomic instructions: `lr.w` / `sc.w` (load-reserved / What: Implement the RV32A atomic instructions: `lr.w` / `sc.w` (load-reserved /
store-conditional) and the AMO ops (`amoswap.w`, `amoadd.w`, `amoand.w`, store-conditional) and the AMO ops (`amoswap.w`, `amoadd.w`, `amoand.w`,
@@ -578,7 +693,7 @@ reservation set and bus become more involved.
--- ---
## Phase 13 — Pipeline (Optional but educational) [Hard stretch] ## Phase 16 — Pipeline (Optional but educational) [Hard stretch]
What: Insert pipeline registers between your stages (fetch|decode|execute| What: Insert pipeline registers between your stages (fetch|decode|execute|
memory|writeback). Handle data hazards (forwarding/stalling) and control memory|writeback). Handle data hazards (forwarding/stalling) and control
@@ -592,11 +707,11 @@ and deeply educational.
Why optional: A non-pipelined core can run at 50 MHz on the Artix-7. That's 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 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 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. `riscv-tests` (Phase 8.5 + 15) after pipelining catches regressions.
--- ---
## Phase 14 — SPI Flash Boot + DRAM [Overkill/hard] ## Phase 17 — SPI Flash Boot + DRAM [Overkill/hard]
What: Add an SPI flash controller to boot from the on-board flash (instead What: Add an SPI flash controller to boot from the on-board flash (instead
of BRAM initialization). The Arty A7-100T has a 16 MB Quad-SPI flash; map of BRAM initialization). The Arty A7-100T has a 16 MB Quad-SPI flash; map
@@ -619,7 +734,7 @@ keep.
--- ---
## Phase 15 — S-Mode, U-Mode, Sv32 Virtual Memory [Overkill/hard] ## Phase 18 — S-Mode, U-Mode, Sv32 Virtual Memory [Overkill/hard]
What: Add supervisor and user privilege modes. Implement Sv32 page table What: Add supervisor and user privilege modes. Implement Sv32 page table
walking (two-level page tables, 4 KB pages). Add the `satp` CSR, page-fault walking (two-level page tables, 4 KB pages). Add the `satp` CSR, page-fault
@@ -637,7 +752,7 @@ modes round-trip via `mret` / `sret`.
--- ---
## Phase 16 — Linux Boot Contract (SBI / Device Tree / ABI) [Overkill/hard] ## Phase 19 — Linux Boot Contract (SBI / Device Tree / ABI) [Overkill/hard]
What: Lock down everything Linux expects at the moment of `kernel_entry`. What: Lock down everything Linux expects at the moment of `kernel_entry`.
This is a real subproject, not a footnote inside "port Linux". This is a real subproject, not a footnote inside "port Linux".
@@ -676,16 +791,16 @@ Required deliverables:
surface area. surface area.
Why: Skipping this phase means hitting all of these problems simultaneously Why: Skipping this phase means hitting all of these problems simultaneously
during Phase 17 bring-up, where the failure mode is "kernel hangs silently during Phase 20 bring-up, where the failure mode is "kernel hangs silently
in early boot" with no console. Doing it explicitly turns Phase 17 into a in early boot" with no console. Doing it explicitly turns Phase 20 into a
debugging exercise on a known-good ABI surface. debugging exercise on a known-good ABI surface.
--- ---
## Phase 17 — Linux [Overkill/hard] ## Phase 20 — Linux [Overkill/hard]
What: Build a minimal RISC-V Linux kernel against the device tree and 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 boot path defined in Phase 19. Build an initramfs with BusyBox. Load
kernel + DTB + initramfs to flash. Boot to a shell prompt over UART. 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 Why: This is the summit. A Linux shell running on a CPU you built from
@@ -697,12 +812,12 @@ scratch.
- 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). March - RISC-V GCC toolchain (`riscv64-unknown-elf-gcc`, multilib build). March
string evolves: `rv32im` (Phase 8) → `rv32im_zicsr_zifencei` (Phase 9) → string evolves: `rv32im` (Phase 8) → `rv32im_zicsr_zifencei` (Phase 12) →
`rv32ima_zicsr_zifencei` (Phase 12). `rv32ima_zicsr_zifencei` (Phase 15).
- `riscv-tests` repo cloned and buildable (Phase 8.5 onward; rv32ua added at - `riscv-tests` repo cloned and buildable (Phase 8.5 onward; rv32ua added at
Phase 12) Phase 15)
- Device-tree compiler `dtc` (Phase 16+) - Device-tree compiler `dtc` (Phase 19+)
- OpenSBI source (Phase 16+, only if you choose the SBI boot path) - OpenSBI source (Phase 19+, 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
+12 -10
View File
@@ -27,13 +27,15 @@ is to explain what to learn, what to verify, and what mistakes to avoid.
- [Phase 6 - Load/Store](phase-06-load-store/phase-06.md) - [Phase 6 - Load/Store](phase-06-load-store/phase-06.md)
- [Phase 7 - Memory-Mapped UART](phase-07-uart-mmio/phase-07.md) - [Phase 7 - Memory-Mapped UART](phase-07-uart-mmio/phase-07.md)
- [Phase 8 - GCC Toolchain Integration](phase-08-gcc-toolchain/phase-08.md) - [Phase 8 - GCC Toolchain Integration](phase-08-gcc-toolchain/phase-08.md)
- [Phase 9 - CSRs + M-Mode Traps](phase-09-csrs-traps/phase-09.md) - [Phase 9 - GCC-Built BIOS / Serial Monitor](phase-09-bios-monitor/phase-09.md)
- [Phase 10 - Timer](phase-10-timer/phase-10.md) - [Phase 10 - Minimal ELF Loader](phase-10-elf-loader/phase-10.md)
- [Phase 11 - Interrupt Controller](phase-11-interrupt-controller/phase-11.md) - [Phase 11 - Tiny Kernel + Command Shell](phase-11-tiny-kernel/phase-11.md)
- [Phase 12 - Atomics](phase-12-atomics/phase-12.md) - [Phase 12 - CSRs + M-Mode Traps](phase-12-csrs-traps/phase-12.md)
- [Phase 13 - Pipeline](phase-13-pipeline/phase-13.md) - [Phase 13 - Timer](phase-13-timer/phase-13.md)
- [Phase 14 - SPI Flash + DRAM](phase-14-flash-dram/phase-14.md) - [Phase 14 - Interrupt Controller](phase-14-interrupt-controller/phase-14.md)
- [Phase 15 - M/S/U + Sv32](phase-15-privilege-sv32/phase-15.md) - [Phase 15 - Atomics](phase-15-atomics/phase-15.md)
- [Phase 16 - Linux Boot Contract](phase-16-linux-boot-contract/phase-16.md) - [Phase 16 - Pipeline](phase-16-pipeline/phase-16.md)
- [Phase 17 - Linux](phase-17-linux/phase-17.md) - [Phase 17 - SPI Flash + DRAM](phase-17-flash-dram/phase-17.md)
- [Phase 18 - M/S/U + Sv32](phase-18-privilege-sv32/phase-18.md)
- [Phase 19 - Linux Boot Contract](phase-19-linux-boot-contract/phase-19.md)
- [Phase 20 - Linux](phase-20-linux/phase-20.md)
@@ -27,7 +27,7 @@ more than minimizing cycles.
- Write a cycle-by-cycle timeline for a simple `addi` instruction. - Write a cycle-by-cycle timeline for a simple `addi` instruction.
- Write a cycle-by-cycle timeline for a multiply/divide instruction. - Write a cycle-by-cycle timeline for a multiply/divide instruction.
- Confirm the pre-Phase 9 illegal-instruction behavior: halt the core and expose - Confirm the pre-Phase 12 illegal-instruction behavior: halt the core and expose
the offending PC and instruction word to the testbench. the offending PC and instruction word to the testbench.
## Pitfalls ## Pitfalls
@@ -27,7 +27,7 @@ every emitted instruction is implemented or fails loudly.
- Compare C source to generated assembly. - Compare C source to generated assembly.
- Identify every load/store used for UART access. - Identify every load/store used for UART access.
- Confirm no CSR or `fence.i` instructions appear before Phase 9. - Confirm no CSR or `fence.i` instructions appear before Phase 12.
## Pitfalls ## Pitfalls
@@ -0,0 +1,49 @@
# Phase 9 - GCC-Built BIOS / Serial Monitor
## Context
This phase turns the GCC bring-up work into a persistent interactive firmware. The
CPU boots into a small monitor instead of a one-off test program.
## Goals
- Build a freestanding C/assembly BIOS with its own linker script and startup code.
- Provide a UART command prompt.
- Add simple commands for memory inspection, loading, and jumping to test payloads.
## New Concepts
- Monitor: small firmware that lets you inspect and control a machine interactively.
- Command parser: text interface that maps typed commands to firmware functions.
- Firmware ABI: the calling convention and data contract between loaded code and BIOS.
- Executable RAM window: memory that software can write and the fetch path can execute.
## How To Think About It
The BIOS is both a milestone and a tool. Keep it boring and reliable: UART in, UART out,
explicit commands, clear error messages, and no hidden dependencies on host tooling.
## Learning Tasks
- Decide where UART-loaded code can live and how the I-bus fetches it.
- Define a tiny BIOS call table for console I/O and returning to the monitor.
- Write down the exact register state expected by `run <addr>`.
- Add commands one at a time and test each on hardware.
## Pitfalls
- Loading code into data memory that the instruction fetch path cannot see.
- Letting a failed command corrupt the monitor's own stack or globals.
- Building a clever shell before the load/run/debug basics work.
## Tooling And Testing
- Test the monitor in simulation with scripted UART input where practical.
- Use a terminal program that can send raw files without changing line endings.
- Keep a known-good tiny payload that prints one line and returns to the monitor.
## References
- RISC-V ELF psABI: https://github.com/riscv-non-isa/riscv-elf-psabi-doc
- GNU linker scripts: https://sourceware.org/binutils/docs/ld/Scripts.html
- OSDev bare bones background: https://wiki.osdev.org/Bare_Bones
+51
View File
@@ -0,0 +1,51 @@
# Phase 10 - Minimal ELF Loader
## Context
This phase lets the BIOS receive GCC-built programs as ELF files and run them without
rebuilding the FPGA bitstream.
## Goals
- Parse enough ELF32 to recognize RISC-V executable files.
- Load `PT_LOAD` segments into memory and zero BSS.
- Jump to `e_entry` with a fixed tiny-program ABI.
## New Concepts
- ELF header: file-level metadata describing architecture, entry point, and tables.
- Program header: runtime load description used by loaders.
- `PT_LOAD`: segment that must be copied into memory before execution.
- BSS: zero-initialized memory represented by `memsz > filesz`.
## How To Think About It
Do not build a general-purpose dynamic loader. Build a strict loader for the exact
firmware shape you produce: static, little-endian, non-relocatable RV32 ELF files with
fixed physical addresses.
## Learning Tasks
- Read the ELF header fields needed to reject unsupported files.
- Map `p_paddr` or `p_vaddr` to your executable memory window.
- Define the stack pointer and argument registers at program entry.
- Decide how a loaded program returns a status code to the BIOS.
## Pitfalls
- Trusting malformed ELF offsets or sizes.
- Forgetting to zero the BSS tail of a loadable segment.
- Accidentally accepting relocatable or dynamically linked binaries.
- Loading a segment over the BIOS itself.
## Tooling And Testing
- Use `readelf -h -l` and `objdump -d` on every early payload.
- Start with one fixed link address and one loadable segment.
- Add loud rejection messages for unsupported ELF class, endianness, machine, or type.
## References
- ELF specification overview: https://refspecs.linuxfoundation.org/elf/elf.pdf
- RISC-V ELF psABI: https://github.com/riscv-non-isa/riscv-elf-psabi-doc
- GNU binutils `readelf` documentation: https://sourceware.org/binutils/docs/binutils/readelf.html
+50
View File
@@ -0,0 +1,50 @@
# Phase 11 - Tiny Kernel + Command Shell
## Context
This phase builds an OS-shaped firmware payload before the project moves into traps,
interrupts, privilege, DRAM, and Linux.
## Goals
- Load a separate GCC-built kernel ELF from the BIOS.
- Provide a kernel-owned command shell.
- Run tiny trusted ELF programs through a simple service table.
## New Concepts
- Kernel: central program that owns machine services. Before traps, this is still
trusted bare-machine firmware, not an isolated privileged OS.
- Service table: function-pointer ABI for console I/O, exit, and simple utilities.
- Bump allocator: simple allocator that hands out memory linearly.
- Program table: small kernel data structure tracking loaded payloads.
## How To Think About It
The goal is a complete loop, not a Unix clone: compile a tiny program on the host,
send it over UART, load it as ELF, run it on the CPU, and return to the shell.
## Learning Tasks
- Decide whether kernel console I/O calls the BIOS or drives UART directly.
- Define the tiny-program ABI: entry registers, stack, service table, and return path.
- Implement the smallest useful allocator.
- Add shell commands that expose real machine state, not decorative output.
## Pitfalls
- Pretending trusted payloads are isolated before traps and privilege modes exist.
- Growing the kernel into a second large project too early.
- Hiding loader or ABI bugs behind ad hoc special cases.
## Tooling And Testing
- Keep kernel and user-payload linker scripts separate.
- Build a tiny test suite of loaded programs: hello, echo, memory copy, return status.
- Run the same payloads after Phase 12 trap handling lands to catch ABI regressions.
## References
- OSDev kernel structure notes: https://wiki.osdev.org/Kernel
- RISC-V calling convention: https://github.com/riscv-non-isa/riscv-elf-psabi-doc
- Embedded Artistry first-fit allocator background: https://embeddedartistry.com/blog/2017/02/15/implementing-malloc-first-fit-free-list/
@@ -1,4 +1,4 @@
# Phase 9 - CSRs + M-Mode Trap Handling # Phase 12 - CSRs + M-Mode Trap Handling
## Context ## Context
@@ -1,4 +1,4 @@
# Phase 10 - Timer # Phase 13 - Timer
## Context ## Context
@@ -1,4 +1,4 @@
# Phase 11 - Interrupt Controller # Phase 14 - Interrupt Controller
## Context ## Context
@@ -1,4 +1,4 @@
# Phase 12 - A Extension # Phase 15 - A Extension
## Context ## Context
@@ -1,4 +1,4 @@
# Phase 13 - Pipeline # Phase 16 - Pipeline
## Context ## Context
@@ -1,4 +1,4 @@
# Phase 14 - SPI Flash Boot + DRAM # Phase 17 - SPI Flash Boot + DRAM
## Context ## Context
@@ -1,4 +1,4 @@
# Phase 15 - S-Mode, U-Mode, Sv32 Virtual Memory # Phase 18 - S-Mode, U-Mode, Sv32 Virtual Memory
## Context ## Context
@@ -1,4 +1,4 @@
# Phase 16 - Linux Boot Contract # Phase 19 - Linux Boot Contract
## Context ## Context
@@ -1,4 +1,4 @@
# Phase 17 - Linux # Phase 20 - Linux
## Context ## Context