From e8631501e8130e58c3b116990b025c128ecd4bee Mon Sep 17 00:00:00 2001 From: Renat Nurgaliyev Date: Tue, 28 Apr 2026 13:23:03 +0200 Subject: [PATCH] Add BIOS and tiny kernel roadmap phases --- CLAUDE.md | 38 ++-- README.md | 8 +- ROADMAP.md | 181 ++++++++++++++---- Tutorial/README.md | 22 ++- .../phase-04-01-fetch-datapath-integration.md | 2 +- .../phase-08-02-first-gcc-program.md | 2 +- Tutorial/phase-09-bios-monitor/phase-09.md | 49 +++++ Tutorial/phase-10-elf-loader/phase-10.md | 51 +++++ Tutorial/phase-11-tiny-kernel/phase-11.md | 50 +++++ .../phase-12.md} | 2 +- .../phase-13.md} | 2 +- .../phase-14.md} | 2 +- .../phase-15.md} | 2 +- .../phase-16.md} | 2 +- .../phase-17.md} | 2 +- .../phase-18.md} | 2 +- .../phase-19.md} | 2 +- .../phase-20.md} | 2 +- 18 files changed, 344 insertions(+), 77 deletions(-) create mode 100644 Tutorial/phase-09-bios-monitor/phase-09.md create mode 100644 Tutorial/phase-10-elf-loader/phase-10.md create mode 100644 Tutorial/phase-11-tiny-kernel/phase-11.md rename Tutorial/{phase-09-csrs-traps/phase-09.md => phase-12-csrs-traps/phase-12.md} (97%) rename Tutorial/{phase-10-timer/phase-10.md => phase-13-timer/phase-13.md} (98%) rename Tutorial/{phase-11-interrupt-controller/phase-11.md => phase-14-interrupt-controller/phase-14.md} (98%) rename Tutorial/{phase-12-atomics/phase-12.md => phase-15-atomics/phase-15.md} (98%) rename Tutorial/{phase-13-pipeline/phase-13.md => phase-16-pipeline/phase-16.md} (98%) rename Tutorial/{phase-14-flash-dram/phase-14.md => phase-17-flash-dram/phase-17.md} (98%) rename Tutorial/{phase-15-privilege-sv32/phase-15.md => phase-18-privilege-sv32/phase-18.md} (97%) rename Tutorial/{phase-16-linux-boot-contract/phase-16.md => phase-19-linux-boot-contract/phase-19.md} (98%) rename Tutorial/{phase-17-linux/phase-17.md => phase-20-linux/phase-20.md} (98%) diff --git a/CLAUDE.md b/CLAUDE.md index eb9eac9..5ad8f51 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -29,8 +29,8 @@ See ROADMAP.md for the full phased plan. - 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` + - Phase 12+ (CSRs/Zifencei decoded): `-march=rv32im_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 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: - 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 +- Zicsr (CSR instructions) + M-mode trap handling — Phase 12 +- Zifencei (`fence.i`) — decoded but NOP until caches exist (Phase 17+) +- A (atomics: LR/SC + AMO) — Phase 15 (required for mainline Linux) +- S-mode + U-mode + Sv32 — Phase 18 ## 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. - "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 @@ -82,10 +82,10 @@ Extensions land incrementally: ``` 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 + mapped at 0x0000_0000–0x00FF_FFFF in Phase 17 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 +0x8000_0000 – 0x8000_FFFF data BRAM (64 KB) → grows to DRAM 0x8000_0000–0x8FFF_FFFF in Phase 17 ``` 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: -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 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 [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+) + logic [3:0] amo; // AMO op encoding (D-bus only, Phase 15+) } mem_req_t; // Payload — slave → master @@ -153,7 +153,7 @@ Handshake notes: 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 +- `amo` is unused (drive `4'h0`) until Phase 15; 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. @@ -173,8 +173,8 @@ separately. Per-phase progression: | 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) | +| 12-15 | unchanged | + timer (Phase 13), PLIC (Phase 14) | +| 17 | I-bus → arbiter → DRAM | D-bus → arbiter → DRAM (or MIG dual-port) | Both buses can fault independently; classification happens in the masters (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 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) | +| Instruction page fault | 12 | I-side MMU (Phase 18) | +| Load page fault | 13 | D-side MMU (Phase 18) | +| Store/AMO page fault | 15 | D-side MMU (Phase 18) | 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, +- Page faults (Phase 18) are raised by the MMU during translation, 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 a separate bus. Single-hart reservation tracking lives in the LSU. diff --git a/README.md b/README.md index 65b0069..794fd29 100644 --- a/README.md +++ b/README.md @@ -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 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 -pipelined later (Phase 13, optional). +pipelined later (Phase 16, optional). ## Memory Map | Address Range | Region | |---------------------------|----------------------------------------------| -| `0x0000_0000–0x0FFF_FFFF` | reserved boot aperture (Phase 14: 16 MB SPI flash at low end) | +| `0x0000_0000–0x0FFF_FFFF` | reserved boot aperture (Phase 17: 16 MB SPI flash at low end) | | `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) → DRAM in Phase 14 | +| `0x8000_0000–0x8000_FFFF` | data BRAM (64 KB) → DRAM in Phase 17 | Reset PC = `0x2000_0000`. @@ -46,7 +46,7 @@ Requires: - Vivado 2025.2 or later - RISC-V GCC toolchain — multilib `riscv64-unknown-elf-gcc`. March string 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) - Serial terminal (minicom/picocom/PuTTY) at 115200 8N1 diff --git a/ROADMAP.md b/ROADMAP.md index 539d28b..6adf0fb 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -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 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; actual SPI flash is 16 MB on Arty A7, - mapped at 0x0000_0000–0x00FF_FFFF in Phase 14) + mapped at 0x0000_0000–0x00FF_FFFF in Phase 17) 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) → DRAM 0x8000_0000–0x8FFF_FFFF in Phase 14 +0x8000_0000 – 0x8000_FFFF data BRAM (64 KB) → DRAM 0x8000_0000–0x8FFF_FFFF in Phase 17 ``` 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, 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. --- @@ -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). 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 +uses. Defining it once means Phase 6.2 (sub-word), Phase 7 (MMIO), Phase 12 +(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 spec) is two payload structs (`mem_req_t`, `mem_rsp_t`) plus loose 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 instruction word — catching the bug visibly rather than NOPing through it. (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 -land, and `rv32ima_zicsr_zifencei` in Phase 12 once atomics land. +is not involved.) Switch to `rv32im_zicsr_zifencei` in Phase 12 once CSRs +land, and `rv32ima_zicsr_zifencei` in Phase 15 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` @@ -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` 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, +real exception in Phase 12 (`mcause = 2` illegal, `mcause = 3` ebreak, `mcause = 11` ecall-from-M). 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 addresses), recursive functions. -### 8.4 — Milestone: Meaningful C Program -What: Write something real — a serial monitor that accepts commands over UART -and responds. Or a tiny Forth interpreter. Something interactive that proves -the core is solid. +### 8.4 — Milestone: Meaningful Standalone C Program +What: Write something real but still self-contained — a checksum demo, tiny +benchmark, string/array exercise, or simple UART-driven calculator. Keep the +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 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 official suite catches the ones you didn't — corner cases in shifts, sign 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. 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 ` / `poke ` — inspect and edit memory-mapped state +- `dump ` — hex-dump memory +- `fill ` — 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 ` — 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, 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 register). When mtime >= mtimecmp, a timer interrupt fires. 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 -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 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 / 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| 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 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. +`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 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 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`. This is a real subproject, not a footnote inside "port Linux". @@ -676,16 +791,16 @@ Required deliverables: 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 +during Phase 20 bring-up, where the failure mode is "kernel hangs silently +in early boot" with no console. Doing it explicitly turns Phase 20 into a 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 -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. 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) - RISC-V GCC toolchain (`riscv64-unknown-elf-gcc`, multilib build). March - string evolves: `rv32im` (Phase 8) → `rv32im_zicsr_zifencei` (Phase 9) → - `rv32ima_zicsr_zifencei` (Phase 12). + string evolves: `rv32im` (Phase 8) → `rv32im_zicsr_zifencei` (Phase 12) → + `rv32ima_zicsr_zifencei` (Phase 15). - `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) + Phase 15) +- Device-tree compiler `dtc` (Phase 19+) +- OpenSBI source (Phase 19+, only if you choose the SBI boot path) - Terminal program (minicom, picocom, or PuTTY) for UART, 115200 8N1 - Text editor you like for SystemVerilog - The RISC-V ISA spec (Volume 1: Unprivileged, Volume 2: Privileged) — free PDFs diff --git a/Tutorial/README.md b/Tutorial/README.md index 6a01e68..a634979 100644 --- a/Tutorial/README.md +++ b/Tutorial/README.md @@ -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 7 - Memory-Mapped UART](phase-07-uart-mmio/phase-07.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 10 - Timer](phase-10-timer/phase-10.md) -- [Phase 11 - Interrupt Controller](phase-11-interrupt-controller/phase-11.md) -- [Phase 12 - Atomics](phase-12-atomics/phase-12.md) -- [Phase 13 - Pipeline](phase-13-pipeline/phase-13.md) -- [Phase 14 - SPI Flash + DRAM](phase-14-flash-dram/phase-14.md) -- [Phase 15 - M/S/U + Sv32](phase-15-privilege-sv32/phase-15.md) -- [Phase 16 - Linux Boot Contract](phase-16-linux-boot-contract/phase-16.md) -- [Phase 17 - Linux](phase-17-linux/phase-17.md) - +- [Phase 9 - GCC-Built BIOS / Serial Monitor](phase-09-bios-monitor/phase-09.md) +- [Phase 10 - Minimal ELF Loader](phase-10-elf-loader/phase-10.md) +- [Phase 11 - Tiny Kernel + Command Shell](phase-11-tiny-kernel/phase-11.md) +- [Phase 12 - CSRs + M-Mode Traps](phase-12-csrs-traps/phase-12.md) +- [Phase 13 - Timer](phase-13-timer/phase-13.md) +- [Phase 14 - Interrupt Controller](phase-14-interrupt-controller/phase-14.md) +- [Phase 15 - Atomics](phase-15-atomics/phase-15.md) +- [Phase 16 - Pipeline](phase-16-pipeline/phase-16.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) diff --git a/Tutorial/phase-04-first-cpu/phase-04-01-fetch-datapath-integration.md b/Tutorial/phase-04-first-cpu/phase-04-01-fetch-datapath-integration.md index f34560c..9bc386e 100644 --- a/Tutorial/phase-04-first-cpu/phase-04-01-fetch-datapath-integration.md +++ b/Tutorial/phase-04-first-cpu/phase-04-01-fetch-datapath-integration.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 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. ## Pitfalls diff --git a/Tutorial/phase-08-gcc-toolchain/phase-08-02-first-gcc-program.md b/Tutorial/phase-08-gcc-toolchain/phase-08-02-first-gcc-program.md index 5a899d8..b636fa1 100644 --- a/Tutorial/phase-08-gcc-toolchain/phase-08-02-first-gcc-program.md +++ b/Tutorial/phase-08-gcc-toolchain/phase-08-02-first-gcc-program.md @@ -27,7 +27,7 @@ every emitted instruction is implemented or fails loudly. - Compare C source to generated assembly. - 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 diff --git a/Tutorial/phase-09-bios-monitor/phase-09.md b/Tutorial/phase-09-bios-monitor/phase-09.md new file mode 100644 index 0000000..da86280 --- /dev/null +++ b/Tutorial/phase-09-bios-monitor/phase-09.md @@ -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 `. +- 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 diff --git a/Tutorial/phase-10-elf-loader/phase-10.md b/Tutorial/phase-10-elf-loader/phase-10.md new file mode 100644 index 0000000..c98a235 --- /dev/null +++ b/Tutorial/phase-10-elf-loader/phase-10.md @@ -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 diff --git a/Tutorial/phase-11-tiny-kernel/phase-11.md b/Tutorial/phase-11-tiny-kernel/phase-11.md new file mode 100644 index 0000000..d12e803 --- /dev/null +++ b/Tutorial/phase-11-tiny-kernel/phase-11.md @@ -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/ diff --git a/Tutorial/phase-09-csrs-traps/phase-09.md b/Tutorial/phase-12-csrs-traps/phase-12.md similarity index 97% rename from Tutorial/phase-09-csrs-traps/phase-09.md rename to Tutorial/phase-12-csrs-traps/phase-12.md index 4517c72..aea1689 100644 --- a/Tutorial/phase-09-csrs-traps/phase-09.md +++ b/Tutorial/phase-12-csrs-traps/phase-12.md @@ -1,4 +1,4 @@ -# Phase 9 - CSRs + M-Mode Trap Handling +# Phase 12 - CSRs + M-Mode Trap Handling ## Context diff --git a/Tutorial/phase-10-timer/phase-10.md b/Tutorial/phase-13-timer/phase-13.md similarity index 98% rename from Tutorial/phase-10-timer/phase-10.md rename to Tutorial/phase-13-timer/phase-13.md index 10d29e9..e7123a6 100644 --- a/Tutorial/phase-10-timer/phase-10.md +++ b/Tutorial/phase-13-timer/phase-13.md @@ -1,4 +1,4 @@ -# Phase 10 - Timer +# Phase 13 - Timer ## Context diff --git a/Tutorial/phase-11-interrupt-controller/phase-11.md b/Tutorial/phase-14-interrupt-controller/phase-14.md similarity index 98% rename from Tutorial/phase-11-interrupt-controller/phase-11.md rename to Tutorial/phase-14-interrupt-controller/phase-14.md index 3b90f26..65730d1 100644 --- a/Tutorial/phase-11-interrupt-controller/phase-11.md +++ b/Tutorial/phase-14-interrupt-controller/phase-14.md @@ -1,4 +1,4 @@ -# Phase 11 - Interrupt Controller +# Phase 14 - Interrupt Controller ## Context diff --git a/Tutorial/phase-12-atomics/phase-12.md b/Tutorial/phase-15-atomics/phase-15.md similarity index 98% rename from Tutorial/phase-12-atomics/phase-12.md rename to Tutorial/phase-15-atomics/phase-15.md index cdbc05d..f09af11 100644 --- a/Tutorial/phase-12-atomics/phase-12.md +++ b/Tutorial/phase-15-atomics/phase-15.md @@ -1,4 +1,4 @@ -# Phase 12 - A Extension +# Phase 15 - A Extension ## Context diff --git a/Tutorial/phase-13-pipeline/phase-13.md b/Tutorial/phase-16-pipeline/phase-16.md similarity index 98% rename from Tutorial/phase-13-pipeline/phase-13.md rename to Tutorial/phase-16-pipeline/phase-16.md index 5ba2ad4..ca2c265 100644 --- a/Tutorial/phase-13-pipeline/phase-13.md +++ b/Tutorial/phase-16-pipeline/phase-16.md @@ -1,4 +1,4 @@ -# Phase 13 - Pipeline +# Phase 16 - Pipeline ## Context diff --git a/Tutorial/phase-14-flash-dram/phase-14.md b/Tutorial/phase-17-flash-dram/phase-17.md similarity index 98% rename from Tutorial/phase-14-flash-dram/phase-14.md rename to Tutorial/phase-17-flash-dram/phase-17.md index 1a234ad..f0d01d1 100644 --- a/Tutorial/phase-14-flash-dram/phase-14.md +++ b/Tutorial/phase-17-flash-dram/phase-17.md @@ -1,4 +1,4 @@ -# Phase 14 - SPI Flash Boot + DRAM +# Phase 17 - SPI Flash Boot + DRAM ## Context diff --git a/Tutorial/phase-15-privilege-sv32/phase-15.md b/Tutorial/phase-18-privilege-sv32/phase-18.md similarity index 97% rename from Tutorial/phase-15-privilege-sv32/phase-15.md rename to Tutorial/phase-18-privilege-sv32/phase-18.md index bffe244..851616b 100644 --- a/Tutorial/phase-15-privilege-sv32/phase-15.md +++ b/Tutorial/phase-18-privilege-sv32/phase-18.md @@ -1,4 +1,4 @@ -# Phase 15 - S-Mode, U-Mode, Sv32 Virtual Memory +# Phase 18 - S-Mode, U-Mode, Sv32 Virtual Memory ## Context diff --git a/Tutorial/phase-16-linux-boot-contract/phase-16.md b/Tutorial/phase-19-linux-boot-contract/phase-19.md similarity index 98% rename from Tutorial/phase-16-linux-boot-contract/phase-16.md rename to Tutorial/phase-19-linux-boot-contract/phase-19.md index 44c37f7..437216f 100644 --- a/Tutorial/phase-16-linux-boot-contract/phase-16.md +++ b/Tutorial/phase-19-linux-boot-contract/phase-19.md @@ -1,4 +1,4 @@ -# Phase 16 - Linux Boot Contract +# Phase 19 - Linux Boot Contract ## Context diff --git a/Tutorial/phase-17-linux/phase-17.md b/Tutorial/phase-20-linux/phase-20.md similarity index 98% rename from Tutorial/phase-17-linux/phase-17.md rename to Tutorial/phase-20-linux/phase-20.md index 34656e4..d1e946e 100644 --- a/Tutorial/phase-17-linux/phase-17.md +++ b/Tutorial/phase-20-linux/phase-20.md @@ -1,4 +1,4 @@ -# Phase 17 - Linux +# Phase 20 - Linux ## Context