From e98b3694ab4e75aee1b8ca9ba2488c3c1e486eb6 Mon Sep 17 00:00:00 2001 From: Renat Nurgaliyev Date: Tue, 28 Apr 2026 11:29:01 +0200 Subject: [PATCH] Refine Arty A7 bring-up and memory-map assumptions Replace the first broad project sketch with more concrete hardware assumptions for implementation on the Arty A7 100T. Document the 50 MHz MMCM-derived clock, synchronous active-high reset after the board reset synchronizer, and a split instruction/data BRAM map with reset PC at 0x2000_0000. Update the UART layout to separate TX, RX, and status registers, and make the README/roadmap agree on the address ranges that firmware and the future linker script will use. Also split the M extension out of the combinational ALU into a dedicated multi-cycle unit with start/busy/done handshaking, describe BRAM latency and stall behavior in the single-cycle logical model, and add riscv-tests as the planned compliance check once GCC-generated programs are running. --- CLAUDE.md | 69 ++++++++++++---- README.md | 43 ++++++---- ROADMAP.md | 231 +++++++++++++++++++++++++++++++++++++---------------- 3 files changed, 241 insertions(+), 102 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index d52723d..b940f76 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -3,7 +3,7 @@ ## Project Overview This is a custom RISC-V RV32IM CPU core written in SystemVerilog, targeting the -Digilent Arty A7 100T (xc7a100tcsg324-1) with Vivado 2025.2.1. +Digilent Arty A7 100T (xc7a100tcsg324-1) with Vivado 2025.2 or later. The goal is incremental development from a single-cycle core to a Linux-capable SoC. See ROADMAP.md for the full phased plan. @@ -18,25 +18,31 @@ See ROADMAP.md for the full phased plan. - Module naming: `rv32_` (e.g., `rv32_alu`, `rv32_decode`, `rv32_regfile`). - File naming: one module per file, filename matches module name. - Testbenches: `tb/tb_.sv`. Use Vivado simulator. -- Clock: single clock domain, active rising edge, signal named `clk`. -- Reset: synchronous active-high, signal named `rst`. +- Clock: single clock domain, active rising edge, signal named `clk`. Target + frequency: 50 MHz (derived from the Arty's 100 MHz oscillator via MMCM/2). +- Reset: synchronous active-high, signal named `rst`. The Arty's `CK_RST` button + is active-low; the top-level wraps it through a 2-FF synchronizer and inverts + to produce the internal `rst`. The rest of the design only sees synchronous + active-high. - BRAM init files: `mem/*.mem` in hex format, one 32-bit word per line. -- Firmware source: `fw/` directory. Built with riscv64-unknown-elf-gcc, - -march=rv32im -mabi=ilp32. +- Firmware source: `fw/` directory. Built with `riscv64-unknown-elf-gcc` + (multilib build required), `-march=rv32im_zicsr_zifencei -mabi=ilp32`. The + full march string is required by GCC 11+ even before CSRs are implemented; + `Zifencei` is a NOP until caches exist (Phase 13+). ## Directory Structure ``` -rtl/ — synthesizable source -pkg/ — packages (rv32_pkg.sv) -core/ — CPU core modules (ALU, decoder, regfile, datapath) -periph/ — peripherals (UART, timer, etc.) -top/ — top-level and SoC integration -tb/ — testbenches -mem/ — BRAM init files (.mem) -fw/ — firmware source (C, assembly, linker scripts) -constraints/ — Vivado XDC constraint files -docs/ — block diagrams, notes +rtl/ — synthesizable source + pkg/ — packages (rv32_pkg.sv) + core/ — CPU core modules (ALU, M unit, decoder, regfile, datapath) + periph/ — peripherals (UART, timer, PLIC, etc.) + top/ — top-level and SoC integration +tb/ — testbenches (tb_.sv) +mem/ — BRAM init files (.mem) +fw/ — firmware source (C, assembly, linker scripts) +constraints/ — Vivado XDC constraint files +docs/ — block diagrams, notes ``` ## ISA Target @@ -51,12 +57,38 @@ RV32IM base. Extensions added incrementally: - Single-cycle first, pipeline later (Phase 12). Stages are separated in the code even without pipeline registers between them. +- "Single-cycle" is a logical model, not a literal one cycle per instruction. + BRAM has a 1-cycle read latency, so fetch is registered and loads take 2 + cycles. The datapath stalls fetch while a multi-cycle operation is in flight. - Memory bus uses valid/ready handshake from day one, even though BRAM always responds in one cycle. This is for future DRAM compatibility. -- M extension (multiply/divide) is in the ALU from the start to avoid rework. +- M extension (multiply/divide) lives in a dedicated multi-cycle M unit + alongside the combinational ALU, with a `start`/`busy`/`done` handshake. + Multiply is 2-3 cycles via DSP48s; divide is iterative (~33 cycles). The + datapath stalls when the M unit is busy. Building this from day one avoids + retrofitting stall logic later. - Harvard architecture (separate instruction and data memory) initially. Unified memory when DRAM is added. +## Memory Map + +``` +0x0000_0000 – 0x0FFF_FFFF reserved (future SPI flash boot region, Phase 13) +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 13 +``` + +Reset PC = `0x2000_0000`. Linker script anchors text/rodata at `0x2000_0000` +and data/bss/stack at `0x8000_0000` from Phase 8 onward. + +UART register layout (split, not 16550-style): +``` +0x1000_0000 TX data (W: byte to send) +0x1000_0004 RX data (R: pops one byte from RX FIFO) +0x1000_0008 status (R: bit0 = tx_busy, bit1 = rx_valid) +``` + ## When Helping With This Project - Always check rv32_pkg.sv first to understand current struct definitions and enums. @@ -65,7 +97,10 @@ RV32IM base. Extensions added incrementally: before integration. - Don't add Verilog-style code (reg, wire, always @). Use SV equivalents. - When suggesting fixes, consider that the design is single-cycle — there are no - pipeline hazards yet. + pipeline hazards yet, but the M unit and BRAM both introduce stalls. +- ISA correctness is verified against `riscv-tests` (rv32ui, rv32um) starting at + Phase 8.5. Hand-written testbenches are for module-level checks; the official + suite is the source of truth for instruction behavior. - Vivado quirks: use (* dont_touch = "true" *) for signals that Vivado optimizes away during debug. ILA/VIO probes need to be on nets that survive synthesis. - The Arty A7 100T has: 101,440 logic cells, 4,860 Kbit BRAM, 240 DSP slices, diff --git a/README.md b/README.md index d2cc742..9757264 100644 --- a/README.md +++ b/README.md @@ -4,33 +4,48 @@ Custom RV32IM RISC-V CPU core built from scratch in SystemVerilog. ## Target -- Board: Digilent Arty A7 100T -- Toolchain: Vivado 2025.2.1 +- Board: Digilent Arty A7 100T (xc7a100tcsg324-1) +- Toolchain: Vivado 2025.2 or later - Language: SystemVerilog - ISA: RV32IM + Zicsr + Zifencei + M-mode privileged (extending toward Linux) +- Clock: 50 MHz (100 MHz xtal ÷ 2 via MMCM) ## Architecture -Single-cycle, non-pipelined, Harvard architecture (separate instruction/data BRAM). -Designed for incremental extension — clean stage separation via typed structs so -pipelining and new instructions can be added without structural rework. +"Single-cycle" logical model (no pipeline registers), Harvard architecture +(separate instruction/data BRAM). The combinational ALU and a multi-cycle M +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 12). ## Memory Map -| Address Range | Peripheral | -|-----------------------|--------------------| -| 0x00000000-0x0FFFFFFF | BRAM (instr + data)| -| 0x10000000 | UART TX/RX data | -| 0x10000004 | UART status | +| Address Range | Region | +|---------------------------|----------------------------------------------| +| `0x0000_0000–0x0FFF_FFFF` | reserved (SPI flash boot region, Phase 13) | +| `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 13 | -(Will expand as peripherals are added) +Reset PC = `0x2000_0000`. + +UART registers (split layout): + +| Address | Access | Meaning | +|---------------|--------|----------------------------------------------| +| `0x1000_0000` | W | TX data — byte to send | +| `0x1000_0004` | R | RX data — pops one byte from RX FIFO | +| `0x1000_0008` | R | status — bit0 = `tx_busy`, bit1 = `rx_valid` | ## Building Requires: -- Vivado 2025.2.1 -- RISC-V GCC toolchain (`riscv64-unknown-elf-gcc`) -- Serial terminal (minicom/picocom/PuTTY) at 115200 baud +- Vivado 2025.2 or later +- RISC-V GCC toolchain — multilib `riscv64-unknown-elf-gcc` supporting + `-march=rv32im_zicsr_zifencei -mabi=ilp32` +- `riscv-tests` (for compliance verification from Phase 8.5) +- Serial terminal (minicom/picocom/PuTTY) at 115200 8N1 Open `FPGA-Core.xpr` in Vivado. Synthesis and implementation target the xc7a100tcsg324-1. diff --git a/ROADMAP.md b/ROADMAP.md index 7485b0a..2c1565f 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,9 +1,24 @@ # RV32IM CPU Core — Build Roadmap -## Target: Digilent Arty A7 100T / Vivado 2025.2.1 / SystemVerilog +## Target: Digilent Arty A7 100T / Vivado 2025.2+ / SystemVerilog +## Clock: 50 MHz (100 MHz xtal ÷ 2 via MMCM) ## End goal: Boot Linux on a custom RISC-V core --- +## Memory Map (target — fixed in Phase 0, evolves through Phase 13) + +``` +0x0000_0000 – 0x0FFF_FFFF reserved (SPI flash boot region, Phase 13) +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 13 +``` + +Reset PC = `0x2000_0000`. Locking this in now keeps the linker script and crt0 +stable across phases. + +--- + ## Phase 0 — Architecture Contract ### 0.1 — SystemVerilog Package @@ -32,43 +47,55 @@ source of truth when things get complex. --- -## Phase 1 — ALU +## Phase 1 — ALU + M Unit -### 1.1 — ALU Module + Simulation -What: Build a combinational ALU that handles all RV32I operations (add, sub, and, -or, xor, slt, sltu, shifts) AND the M extension operations (mul, mulh, mulhsu, -mulhu, div, divu, rem, remu). Inputs: two 32-bit operands + operation select from -your enum. Outputs: 32-bit result. +### 1.1 — Combinational ALU + Simulation +What: Build a combinational ALU that handles all RV32I arithmetic/logic ops +(add, sub, and, or, xor, slt, sltu, shifts). Inputs: two 32-bit operands + +operation select from your enum. Outputs: 32-bit result. -Why: The ALU is the computational heart. Building it first gives you a self-contained -module to practice your testbench workflow. Including M extension now costs almost -nothing (a few extra case statements) but saves you from touching this module again -later. - -Why M extension from day one: GCC emits multiply/divide constantly. Without it, the -compiler falls back to software emulation via libgcc — slow and painful. Baking it in -now means your first GCC-compiled program just works. +Why: The ALU is the computational heart. Building it first gives you a +self-contained module to practice your testbench workflow. Testbench focus: signed vs unsigned comparisons (SLT vs SLTU), arithmetic shift -right vs logical shift right, signed overflow, division by zero (RISC-V spec says -specific results, not an exception). +right vs logical shift right, signed overflow. Future role: This module is final. It goes into the finished core unchanged. -### 1.2 — ALU on FPGA with VIO -What: Synthesize the ALU on the Arty. Attach Vivado VIO (Virtual I/O) cores to the -inputs and outputs. Use the Vivado hardware manager to feed operands and read results -in real time. +### 1.2 — Multi-cycle M Unit + Simulation +What: A separate module for the M extension (mul, mulh, mulhsu, mulhu, div, +divu, rem, remu) with a `start` / `busy` / `done` handshake. Multiply uses +DSP48s and completes in 2-3 cycles; divide is iterative (one bit per cycle, +~33 cycles for 32-bit). Outputs a 32-bit result on `done`. -Why: Confirms the ALU works in real hardware, not just simulation. Gets you -comfortable with the VIO workflow — you'll use it again. Also catches synthesis -issues early (e.g., if your multiply path is too slow for the clock). +Why M extension early: GCC emits mul/div constantly; without hardware, libgcc +emulation is slow and painful. Why a separate module: a 32-bit combinational +divider will not meet timing on Artix-7 at any reasonable Fmax — divide must +be iterative. Baking the stall handshake in from day one means the datapath +(Phase 4) is built around it from the start, not retrofitted. -What is VIO: A Vivado IP that lets you poke values into signals and read signals out -through JTAG, right from the Vivado GUI. Think of it as virtual switches and LEDs -but with 32-bit width and no board wiring. +Testbench focus: division by zero (RISC-V spec gives specific results, not an +exception — `div(x,0) = -1`, `rem(x,0) = x`), signed overflow on division +(`INT_MIN / -1` returns `INT_MIN`), `mulh*` upper-half results, back-to-back +operations without dropping `busy`. -Future role: VIO familiarity pays off throughout the project. The ALU itself is final. +Future role: Final. The handshake interface is reused unchanged when the core +is pipelined. + +### 1.3 — ALU + M Unit on FPGA with VIO +What: Synthesize both on the Arty. Attach Vivado VIO cores to inputs, outputs, +and the M unit handshake. Use the Vivado hardware manager to drive operands +and read results. + +Why: Confirms behavior in real hardware and catches synthesis issues early +(timing on the multiply path, divide unit FSM state encoding). Also gets you +comfortable with the VIO workflow. + +What is VIO: A Vivado IP that lets you poke values into signals and read them +out through JTAG, right from the Vivado GUI. Think virtual switches and LEDs +with 32-bit width and no board wiring. + +Future role: VIO familiarity pays off throughout the project. --- @@ -126,19 +153,27 @@ decoder and a new field to the struct. The structure of the decoder doesn't chan ## Phase 4 — First CPU ("It's Alive") ### 4.1 — Fetch + Datapath Integration -What: Create an instruction BRAM initialized from a .mem file. Add a PC (program -counter) register that starts at 0 and increments by 4 each cycle. Wire the chain: -BRAM[PC] → decoder → ALU → register file writeback. Support only R-type and I-type -arithmetic for now (add, sub, addi, and, or, xor, slt, slti, lui, auipc, shifts). +What: Create an instruction BRAM initialized from a .mem file, mapped at +`0x2000_0000`. Add a PC register that resets to `0x2000_0000` and advances +by 4 when the next instruction is ready. Wire the chain: +BRAM[PC] → decoder → ALU/M unit → register file writeback. Support R-type and +I-type arithmetic for now (add, sub, addi, and, or, xor, slt, slti, lui, +auipc, shifts), plus the M-extension ops via the M unit's handshake. Why: This is the first time all your modules work together as a CPU. It can't branch, it can't access data memory, but it executes a sequence of arithmetic instructions correctly. The wiring is where most bugs live — wrong bit ranges, swapped operands, forgetting to connect a signal. +Important — "single-cycle" is a logical model: BRAM has a 1-cycle read +latency, so fetch is registered (PC → address one cycle, instruction available +the next). Each instruction takes 2 cycles end-to-end at minimum, and stalls +extend that whenever the M unit is busy. Plan the control FSM around this: +states like FETCH → EXECUTE, with a stall in EXECUTE while `m_busy` is high. + How to test: Hand-assemble 10-20 instructions into a .mem file. Calculate the -expected register state after each instruction by hand. Compare against what the -CPU actually produces. +expected register state after each instruction by hand. Compare against what +the CPU actually produces. What is a .mem file: A text file with hex values, one per line. Vivado can initialize BRAMs from these. Each line is one 32-bit instruction. @@ -169,10 +204,12 @@ What: Add a branch comparator (separate from the ALU — it checks rs1 vs rs2 fo equality, less-than, etc.) and a mux that selects between PC+4 and the branch target. Implement all B-type branches: beq, bne, blt, bge, bltu, bgeu. -Why: Without branches, the CPU can only run straight-line code. Branches give you -loops and conditionals — the core of any real program. Keeping the branch comparator -separate from the ALU is a design choice that pays off when you pipeline later (branch -decision can be made in the decode stage without waiting for ALU). +Why: Without branches, the CPU can only run straight-line code. Branches give +you loops and conditionals — the core of any real program. Keeping the branch +comparator separate from the ALU is mostly a clarity choice: branches and +arithmetic have different semantics, and the comparator is small. (Whether you +resolve branches in decode or execute when you pipeline later is a separate +trade-off involving forwarding paths — don't lock that decision in now.) Test: A loop that increments a register from 0 to 10, then falls through. Verify with ILA that the branch is taken exactly 10 times and the final register value is 10. @@ -199,9 +236,15 @@ Future role: Final. These instructions don't change. ## Phase 6 — Load/Store (Data Memory) ### 6.1 — Word Load/Store (lw, sw) -What: Add a data BRAM and a load/store unit. For now, only 32-bit aligned access -(lw and sw). Define a simple memory bus interface with address, write data, read data, -write enable, and valid/ready signals. +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 +interface with address, write data, read data, write enable, and valid/ready +signals. + +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 → +WRITEBACK for loads. This is exactly why valid/ready is in the bus from +day one. Why: A CPU without data memory can only work with 32 values (the registers). Load/ store connects the CPU to the outside world. Starting with word-only access keeps the @@ -244,8 +287,9 @@ errors). ILA on the bus lets you see exactly what's happening each cycle. ## Phase 7 — Memory-Mapped UART ### 7.1 — UART TX Module -What: A standalone UART transmitter. Fixed baud rate (115200 is standard), 8 data -bits, no parity, 1 stop bit (8N1). Interface: input byte, send signal, busy flag. +What: A standalone UART transmitter. Baud rate 115200, 8 data bits, no parity, +1 stop bit (8N1). At 50 MHz the baud divisor is `50_000_000 / 115_200 ≈ 434` +clocks per bit. Interface: input byte, send signal, busy flag. Why: This is your CPU's mouth. Once connected, the CPU can print to a serial terminal. This replaces ILA as your primary debug tool for software — you can printf-debug your @@ -272,25 +316,37 @@ deliberate timing skew and verify the receiver handles it. Future role: Same as TX — becomes the console UART. ### 7.3 — Bus Decoder + Memory-Mapped I/O -What: Add an address decoder to your memory bus. Address range 0x00000000-0x0FFFFFFF -routes to BRAM (instruction + data), address 0x10000000 routes to UART TX (write the -byte to send), 0x10000000 routes to UART RX (read the received byte). Add a status -register at 0x10000004 (TX busy flag, RX data available flag). +What: Add an address decoder to your memory bus. Routing: -Why: Memory-mapped I/O is how CPUs talk to peripherals in the real world. The CPU -doesn't know it's talking to a UART — it just does a store to an address, and the bus -decoder routes it to the right place. This is the same pattern used by every SoC ever. +``` +0x2000_0000 – 0x2000_FFFF instruction BRAM +0x8000_0000 – 0x8000_FFFF data BRAM +0x1000_0000 UART TX data (W: byte to send) +0x1000_0004 UART RX data (R: pops one byte from RX FIFO) +0x1000_0008 UART status (R: bit0 = tx_busy, bit1 = rx_valid) +``` -Test: Write a program that stores ASCII bytes to 0x10000000 in a loop. See the -message in your terminal. This is the most important milestone in the project — your -CPU is now a computer that communicates with the outside world through software. +Note this is a split register layout (separate TX/RX/status addresses), not +a 16550-style shared data register. Cleaner for a learning core; can be +reshaped later if you want a 16550-compatible model. -Future role: The bus decoder grows as you add peripherals (timer, interrupt controller, -DRAM) but the structure stays. UART mapping stays at this address. +Why: Memory-mapped I/O is how CPUs talk to peripherals in the real world. The +CPU doesn't know it's talking to a UART — it just does a store to an address, +and the bus decoder routes it to the right place. This is the same pattern +used by every SoC ever. + +Test: Write a program that stores ASCII bytes to `0x1000_0000` in a loop, +polling `0x1000_0008` bit 0 between writes. See the message in your terminal. +This is the most important milestone in the project — your CPU is now a +computer that communicates with the outside world through software. + +Future role: The bus decoder grows as you add peripherals (timer, PLIC, DRAM) +but the structure stays. UART mapping stays at these addresses. ### 7.4 — "Hello World" (Hand-Assembled) What: Hand-write an assembly program that prints "hello from rv32" to the UART -address. Assemble it, load into BRAM, run. +address (`0x1000_0000`), polling status bit 0 to wait for `tx_busy` to clear. +Assemble it, load into BRAM, run. Why: Pure emotional milestone. Your CPU, your UART, your program, your message on screen. @@ -300,9 +356,11 @@ screen. ## Phase 8 — GCC Toolchain Integration ### 8.1 — Linker Script + Startup Code -What: Write a linker script that tells GCC where your instruction memory, data memory, -and stack live. Write crt0.S (C runtime startup): set the stack pointer, zero out the -BSS section (uninitialized global variables), call main. +What: Write a linker script that tells GCC where your instruction memory, data +memory, and stack live. Anchor `.text`/`.rodata` at `0x2000_0000`, +`.data`/`.bss` at `0x8000_0000`, stack growing down from the top of data BRAM +(`0x8001_0000`). Write crt0.S (C runtime startup): set the stack pointer, +zero out the BSS section (uninitialized global variables), call main. Why: GCC doesn't just compile C to instructions — it expects a runtime environment. The linker script defines the memory layout, and crt0 sets up the minimal environment @@ -312,10 +370,19 @@ Future role: The linker script evolves as your memory map grows (adding DRAM, fl crt0 grows when you add CSRs (setting up trap vectors in startup). ### 8.2 — First GCC Program -What: Write a trivial main() that prints a string to the UART by writing bytes to -0x10000000. Compile with: riscv64-unknown-elf-gcc -march=rv32im -mabi=ilp32 -nostdlib --T linker.ld -o firmware.elf. Convert: objcopy -O binary firmware.elf firmware.bin. -Convert binary to .mem format. Load into BRAM. Run. +What: Write a trivial main() that prints a string to the UART by writing bytes +to `0x1000_0000`. Compile with: +``` +riscv64-unknown-elf-gcc -march=rv32im_zicsr_zifencei -mabi=ilp32 \ + -nostdlib -T linker.ld -o firmware.elf crt0.S main.c +``` +Convert: `objcopy -O binary firmware.elf firmware.bin`. Convert binary to +`.mem` format. Load into BRAM. Run. + +Why the long march string: GCC 11+ split `Zicsr` and `Zifencei` out of the +RV32I base. `-march=rv32im` alone fails or warns. `Zifencei` is harmless +(`fence.i` is a NOP until you have caches); `Zicsr` will only be emitted by +code that uses CSR ops, so it's safe to advertise even before Phase 9. 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 @@ -337,12 +404,32 @@ usage, switch statements (these generate jump tables — exercises jalr with com 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. +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. -Why: Confidence builder. You now have a working RISC-V computer that runs compiled C -and talks over serial. Everything after this is enrichment. +Why: Confidence builder. You now have a working RISC-V computer that runs +compiled C and talks over serial. Everything after this is enrichment. + +### 8.5 — riscv-tests Compliance +What: Clone `riscv-software-src/riscv-tests`. Build the `rv32ui-p-*` and +`rv32um-p-*` tests (the "p" variant assumes physical addressing, no virtual +memory — perfect for this stage). Each test is a tiny program that exercises +one or more instructions and writes a pass/fail code to a known address. +Write a small testbench harness that loads each test ELF into instruction +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 +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 +on when rv32ui and rv32um pass clean. + +Future role: Re-run the suite after every meaningful change to the core. It +becomes regression coverage for the rest of the project. --- @@ -436,8 +523,10 @@ Why: This is the summit. A Linux shell running on a CPU you built from scratch. ## Quick Reference: What You Need Installed -- Vivado 2025.2.1 (synthesis, simulation, ILA, VIO) -- RISC-V GCC toolchain (riscv64-unknown-elf-gcc) -- Terminal program (minicom, picocom, or PuTTY) for UART +- Vivado 2025.2 or later (synthesis, simulation, ILA, VIO) +- RISC-V GCC toolchain (`riscv64-unknown-elf-gcc`, multilib build — needs to + support `rv32im_zicsr_zifencei` / `ilp32`) +- `riscv-tests` repo cloned and buildable (Phase 8.5 onward) +- 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