Seed RISC-V core roadmap and project contracts
Establish the repository as a documentation-first plan for a custom SystemVerilog RISC-V CPU targeting the Digilent Arty A7 100T. Add the initial README, roadmap, and contributor guidance that define the starting RV32IM direction, Vivado/RISC-V toolchain expectations, basic SystemVerilog conventions, and the phased path from an architecture contract toward a Linux-capable SoC. This commit intentionally contains planning and interface direction only; RTL, firmware, testbenches, and Vivado project files are left for later phases.
This commit is contained in:
@@ -0,0 +1,72 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
The goal is incremental development from a single-cycle core to a Linux-capable SoC.
|
||||||
|
See ROADMAP.md for the full phased plan.
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
- Language: SystemVerilog (not Verilog). Use SV features: packages, structs, enums,
|
||||||
|
always_comb, always_ff, logic (not reg/wire).
|
||||||
|
- All inter-stage signals are defined as structs in `rtl/pkg/rv32_pkg.sv`. Always
|
||||||
|
import this package. When adding new functionality, extend the existing structs
|
||||||
|
rather than adding loose wires.
|
||||||
|
- Module naming: `rv32_<block>` (e.g., `rv32_alu`, `rv32_decode`, `rv32_regfile`).
|
||||||
|
- File naming: one module per file, filename matches module name.
|
||||||
|
- Testbenches: `tb/tb_<module>.sv`. Use Vivado simulator.
|
||||||
|
- Clock: single clock domain, active rising edge, signal named `clk`.
|
||||||
|
- Reset: synchronous active-high, signal named `rst`.
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
## 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
|
||||||
|
```
|
||||||
|
|
||||||
|
## ISA Target
|
||||||
|
|
||||||
|
RV32IM base. Extensions added incrementally:
|
||||||
|
- Zicsr (CSR instructions) — Phase 9
|
||||||
|
- Zifencei (fence.i) — implemented as NOP until caches exist
|
||||||
|
- M-mode privileged — Phase 9
|
||||||
|
- S-mode + U-mode + Sv32 — Phase 14
|
||||||
|
|
||||||
|
## Key Design Decisions
|
||||||
|
|
||||||
|
- Single-cycle first, pipeline later (Phase 12). Stages are separated in the code
|
||||||
|
even without pipeline registers between them.
|
||||||
|
- 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.
|
||||||
|
- Harvard architecture (separate instruction and data memory) initially. Unified
|
||||||
|
memory when DRAM is added.
|
||||||
|
|
||||||
|
## When Helping With This Project
|
||||||
|
|
||||||
|
- Always check rv32_pkg.sv first to understand current struct definitions and enums.
|
||||||
|
- Reference the RISC-V ISA spec for instruction encoding and behavior.
|
||||||
|
- Prefer simulation-testable solutions. Every module should be verifiable standalone
|
||||||
|
before integration.
|
||||||
|
- Don't add Verilog-style code (reg, wire, always @). Use SV equivalents.
|
||||||
|
- When suggesting fixes, consider that the design is single-cycle — there are no
|
||||||
|
pipeline hazards yet.
|
||||||
|
- Vivado quirks: use (* dont_touch = "true" *) for signals that Vivado optimizes
|
||||||
|
away during debug. ILA/VIO probes need to be on nets that survive synthesis.
|
||||||
|
- The Arty A7 100T has: 101,440 logic cells, 4,860 Kbit BRAM, 240 DSP slices,
|
||||||
|
256MB DDR3L SDRAM, USB-UART bridge, 4 LEDs, 4 switches, 4 buttons.
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
# FPGA-Core
|
||||||
|
|
||||||
|
Custom RV32IM RISC-V CPU core built from scratch in SystemVerilog.
|
||||||
|
|
||||||
|
## Target
|
||||||
|
|
||||||
|
- Board: Digilent Arty A7 100T
|
||||||
|
- Toolchain: Vivado 2025.2.1
|
||||||
|
- Language: SystemVerilog
|
||||||
|
- ISA: RV32IM + Zicsr + Zifencei + M-mode privileged (extending toward Linux)
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
## Memory Map
|
||||||
|
|
||||||
|
| Address Range | Peripheral |
|
||||||
|
|-----------------------|--------------------|
|
||||||
|
| 0x00000000-0x0FFFFFFF | BRAM (instr + data)|
|
||||||
|
| 0x10000000 | UART TX/RX data |
|
||||||
|
| 0x10000004 | UART status |
|
||||||
|
|
||||||
|
(Will expand as peripherals are added)
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
Requires:
|
||||||
|
- Vivado 2025.2.1
|
||||||
|
- RISC-V GCC toolchain (`riscv64-unknown-elf-gcc`)
|
||||||
|
- Serial terminal (minicom/picocom/PuTTY) at 115200 baud
|
||||||
|
|
||||||
|
Open `FPGA-Core.xpr` in Vivado. Synthesis and implementation target the xc7a100tcsg324-1.
|
||||||
|
|
||||||
|
## Roadmap
|
||||||
|
|
||||||
|
See `ROADMAP.md` for the full phased build plan.
|
||||||
|
|
||||||
|
## Current Phase
|
||||||
|
|
||||||
|
Phase 0 — Architecture contract (package definitions + block diagram)
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [RISC-V ISA Spec Vol 1 (Unprivileged)](https://riscv.org/technical/specifications/)
|
||||||
|
- [RISC-V ISA Spec Vol 2 (Privileged)](https://riscv.org/technical/specifications/)
|
||||||
|
- [Arty A7 Reference Manual](https://digilent.com/reference/programmable-logic/arty-a7/reference-manual)
|
||||||
+443
@@ -0,0 +1,443 @@
|
|||||||
|
# RV32IM CPU Core — Build Roadmap
|
||||||
|
## Target: Digilent Arty A7 100T / Vivado 2025.2.1 / SystemVerilog
|
||||||
|
## End goal: Boot Linux on a custom RISC-V core
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 0 — Architecture Contract
|
||||||
|
|
||||||
|
### 0.1 — SystemVerilog Package
|
||||||
|
What: Create a `.sv` package file with enums (ALU operations, opcode types, branch
|
||||||
|
types) and structs (fetch_out_t, decode_out_t, exec_out_t, mem_out_t) that define
|
||||||
|
the signals passed between pipeline stages.
|
||||||
|
|
||||||
|
Why: This is the "API" of your CPU. Every module you build will import this package.
|
||||||
|
If you get the struct definitions reasonable now, adding new instructions later means
|
||||||
|
adding a field to a struct — not re-plumbing wires across the whole design.
|
||||||
|
|
||||||
|
Future role: These structs stay forever. When you pipeline the core, the pipeline
|
||||||
|
registers are literally just these structs stored in flip-flops. When you add CSR
|
||||||
|
instructions, you add a field to decode_out_t. The package grows but never gets
|
||||||
|
replaced.
|
||||||
|
|
||||||
|
### 0.2 — Block Diagram (on paper)
|
||||||
|
What: Draw the major blocks (fetch, decode, ALU, register file, memory, writeback)
|
||||||
|
and the signals between them. Label signals with the struct names from 0.1.
|
||||||
|
|
||||||
|
Why: You need a physical reference to look at while coding. Screen diagrams get
|
||||||
|
buried in tabs. Paper on a wall stays visible.
|
||||||
|
|
||||||
|
Future role: You'll update this as the design grows. It becomes your architectural
|
||||||
|
source of truth when things get complex.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1 — ALU
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Future role: VIO familiarity pays off throughout the project. The ALU itself is final.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2 — Register File
|
||||||
|
|
||||||
|
### 2.1 — Register File Module + Simulation
|
||||||
|
What: Build the RISC-V register file — 32 registers, each 32 bits wide. Two read
|
||||||
|
ports (rs1, rs2), one write port (rd). Register x0 is hardwired to zero (writes to
|
||||||
|
it are ignored, reads always return 0).
|
||||||
|
|
||||||
|
Why: Every instruction reads from and/or writes to registers. This is the second
|
||||||
|
fundamental building block after the ALU. It's simple (it's basically a small RAM
|
||||||
|
with some special behavior on address 0) but getting the read/write timing right
|
||||||
|
matters.
|
||||||
|
|
||||||
|
Testbench focus: read-after-write in the same cycle (does the new value forward?),
|
||||||
|
write to x0 then read x0 (must be zero), read two different registers simultaneously.
|
||||||
|
|
||||||
|
Future role: This module is final. Might eventually get a third read port if you add
|
||||||
|
certain extensions, but for RV32IM it's done.
|
||||||
|
|
||||||
|
### 2.2 — Optional: VIO validation
|
||||||
|
What: Put it on FPGA with VIO if you want extra confidence. Usually sim is enough
|
||||||
|
for a register file since the logic is simple.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3 — Decoder
|
||||||
|
|
||||||
|
### 3.1 — Instruction Decoder + Simulation
|
||||||
|
What: A combinational module that takes a raw 32-bit instruction word and produces
|
||||||
|
your decode_out_t struct: ALU operation, source/destination register addresses,
|
||||||
|
immediate value (sign-extended), memory read/write flags, branch type, etc.
|
||||||
|
|
||||||
|
Why: The decoder is the "brain" that tells every other module what to do. RISC-V has
|
||||||
|
six instruction formats (R, I, S, B, U, J) and the immediate bits are scattered
|
||||||
|
differently in each. Getting the immediate extraction and sign extension right is the
|
||||||
|
main challenge.
|
||||||
|
|
||||||
|
How to test: Use the RISC-V toolchain as your reference. Write small assembly
|
||||||
|
snippets, assemble them with `riscv64-unknown-elf-as`, then `objdump -d` to get the
|
||||||
|
binary encoding. Feed those encodings to your testbench and verify every output field.
|
||||||
|
This also gets you familiar with the cross-compilation toolchain you'll need later.
|
||||||
|
|
||||||
|
Testbench focus: Every instruction format. Pay special attention to immediate sign
|
||||||
|
extension (bit 31 of the instruction is always the sign bit in RISC-V — that's a
|
||||||
|
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
|
||||||
|
decoder and a new field to the struct. The structure of the decoder doesn't change.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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).
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Future role: This is your core. Everything from here on adds capabilities to it.
|
||||||
|
|
||||||
|
### 4.2 — ILA Verification on FPGA
|
||||||
|
What: Synthesize the CPU on the Arty. Attach ILA (Integrated Logic Analyzer) probes
|
||||||
|
to the PC, register write port, and ALU output. Set a trigger (e.g., when PC reaches
|
||||||
|
the last instruction). Inspect the captured waveform.
|
||||||
|
|
||||||
|
Why: Confirms hardware behavior matches simulation. Gets you comfortable with ILA,
|
||||||
|
which becomes your primary debugging tool for all future phases.
|
||||||
|
|
||||||
|
What is ILA: A Vivado IP that captures signal values over time into on-chip memory,
|
||||||
|
like an oscilloscope for internal FPGA signals. You set trigger conditions and view
|
||||||
|
waveforms in the Vivado hardware manager.
|
||||||
|
|
||||||
|
Future role: You'll drop ILA probes on different signals throughout the project.
|
||||||
|
Knowing how to use it well is arguably the most important FPGA debug skill.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 5 — Branches and Jumps (Control Flow)
|
||||||
|
|
||||||
|
### 5.1 — Branch Instructions
|
||||||
|
What: Add a branch comparator (separate from the ALU — it checks rs1 vs rs2 for
|
||||||
|
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).
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Future role: When you pipeline, branch handling becomes the source of pipeline hazards
|
||||||
|
and you'll add branch prediction. But the comparator logic stays.
|
||||||
|
|
||||||
|
### 5.2 — Jump Instructions (jal, jalr)
|
||||||
|
What: Add jal (jump and link — PC-relative) and jalr (jump and link register —
|
||||||
|
absolute). Both store PC+4 into the destination register before jumping.
|
||||||
|
|
||||||
|
Why: jal/jalr are how RISC-V implements function calls and returns. `jal` calls a
|
||||||
|
function, `jalr` returns from it (by jumping to the address saved in the link
|
||||||
|
register). Without these, you can't have functions — and C is nothing but functions.
|
||||||
|
|
||||||
|
Test: Write a call/return sequence. Main calls a subroutine via jal, subroutine does
|
||||||
|
some work, returns via jalr. Verify the return address is correct and execution
|
||||||
|
resumes in the right place.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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
|
||||||
|
byte-lane logic simple while you verify the memory path works.
|
||||||
|
|
||||||
|
Why valid/ready now: BRAM responds in one cycle, so ready is always high. But when
|
||||||
|
you swap in DRAM later, responses take many cycles. If your interface already has
|
||||||
|
valid/ready handshaking, the swap is painless. If you hardwire it now, you'll have
|
||||||
|
to redesign the memory path later. Five minutes of future-proofing saves a weekend
|
||||||
|
of rework.
|
||||||
|
|
||||||
|
Test: Store values to various addresses, load them back, verify they match.
|
||||||
|
|
||||||
|
Future role: The bus interface is the foundation for your entire memory map (BRAM,
|
||||||
|
DRAM, UART, timer, interrupt controller — all hang off this bus).
|
||||||
|
|
||||||
|
### 6.2 — Byte and Halfword Access (lb, lbu, lh, lhu, sb, sh)
|
||||||
|
What: Add byte and halfword loads (signed and unsigned) and stores. This means byte
|
||||||
|
lane selection (which byte within a 32-bit word) and sign extension (lb sign-extends
|
||||||
|
to 32 bits, lbu zero-extends).
|
||||||
|
|
||||||
|
Why: C uses char (byte) and short (halfword) types constantly. String operations are
|
||||||
|
byte-by-byte. You can't run real C code without these.
|
||||||
|
|
||||||
|
Testbench focus: Sign extension (loading 0xFF as signed byte should give 0xFFFFFFFF,
|
||||||
|
as unsigned should give 0x000000FF). Unaligned access behavior (RISC-V base spec says
|
||||||
|
misaligned access can trap — decide if you want to support it or trap).
|
||||||
|
|
||||||
|
Future role: Final. These instructions don't change.
|
||||||
|
|
||||||
|
### 6.3 — ILA on the Memory Bus
|
||||||
|
What: Attach ILA to the memory bus signals. Run a program that computes Fibonacci
|
||||||
|
numbers and stores them into a data array. Verify the memory contents.
|
||||||
|
|
||||||
|
Why: Memory bugs are subtle (off-by-one addresses, wrong byte lane, sign extension
|
||||||
|
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.
|
||||||
|
|
||||||
|
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
|
||||||
|
C programs.
|
||||||
|
|
||||||
|
Test standalone: Wrap it in a tiny FSM that sends "hello\n" on reset. Synthesize,
|
||||||
|
connect to your terminal at 115200 baud. If you want, verify the waveform on your
|
||||||
|
scope — you'll see actual start bits, data bits, stop bits on the TX pin.
|
||||||
|
|
||||||
|
Future role: This exact module gets memory-mapped in 7.3 and eventually becomes the
|
||||||
|
console for your BIOS and Linux.
|
||||||
|
|
||||||
|
### 7.2 — UART RX Module
|
||||||
|
What: A standalone UART receiver. Same 8N1 format. Oversamples the input (typically
|
||||||
|
16x baud rate) to find bit centers. Outputs received byte + valid flag.
|
||||||
|
|
||||||
|
Why: This is your CPU's ear. Needed for any interactive console — typing commands,
|
||||||
|
sending data to the board.
|
||||||
|
|
||||||
|
Test: Send bytes from your terminal, verify they appear correctly. If you want to be
|
||||||
|
extra thorough, use your Eclypse + DAC zmod to generate serial waveforms with
|
||||||
|
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).
|
||||||
|
|
||||||
|
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 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.
|
||||||
|
|
||||||
|
Future role: The bus decoder grows as you add peripherals (timer, interrupt controller,
|
||||||
|
DRAM) but the structure stays. UART mapping stays at this address.
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
|
Why: Pure emotional milestone. Your CPU, your UART, your program, your message on
|
||||||
|
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.
|
||||||
|
|
||||||
|
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
|
||||||
|
that C code assumes exists (a stack, zeroed globals).
|
||||||
|
|
||||||
|
Future role: The linker script evolves as your memory map grows (adding DRAM, flash).
|
||||||
|
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.
|
||||||
|
|
||||||
|
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
|
||||||
|
combinations you never thought to hand-test.
|
||||||
|
|
||||||
|
Expect to iterate: GCC will probably emit an instruction you haven't implemented yet.
|
||||||
|
That's fine — check the illegal instruction, add it, resynthesize. This is normal.
|
||||||
|
|
||||||
|
### 8.3 — Fill Remaining RV32I Gaps
|
||||||
|
What: Implement any RV32I instructions you deferred. Common ones: all shift variants
|
||||||
|
(sll, srl, sra, slli, srli, srai), set-less-than variants (slti, sltiu), fence (NOP
|
||||||
|
for now — you have no cache), ecall/ebreak (NOP for now — you have no trap handling).
|
||||||
|
|
||||||
|
Why: GCC's output will exercise the full ISA. You need complete RV32I coverage for
|
||||||
|
any non-trivial C code to work.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Why: Confidence builder. You now have a working RISC-V computer that runs compiled C
|
||||||
|
and talks over serial. Everything after this is enrichment.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 9 — CSRs + M-Mode Trap Handling
|
||||||
|
|
||||||
|
What: Add Control and Status Registers (mstatus, mtvec, mepc, mcause, mtval, mie,
|
||||||
|
mip) and the CSR instructions (csrrw, csrrs, csrrc, csrrwi, csrrsi, csrrci). Add
|
||||||
|
mret (return from trap). Implement the trap entry mechanism: on an exception or
|
||||||
|
interrupt, save PC to mepc, jump to mtvec, set mcause.
|
||||||
|
|
||||||
|
Why: Traps and interrupts are how the CPU handles errors (illegal instruction, bad
|
||||||
|
memory access) and hardware events (timer fired, UART received a byte). No operating
|
||||||
|
system can function without this. M-mode (machine mode) is the highest privilege
|
||||||
|
level and the only one you need for bare-metal code and simple kernels.
|
||||||
|
|
||||||
|
Future role: This is the foundation for all OS-level functionality. Linux needs this
|
||||||
|
plus S-mode (supervisor mode), which you'll add later.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 10 — Timer
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 11 — Interrupt Controller
|
||||||
|
|
||||||
|
What: Build a minimal PLIC (Platform-Level Interrupt Controller) or a simplified
|
||||||
|
version. Connect UART RX as an interrupt source. Implement interrupt priority and
|
||||||
|
enable/disable.
|
||||||
|
|
||||||
|
Why: Polling the UART wastes CPU cycles. With interrupts, the CPU does other work
|
||||||
|
and only handles UART when data arrives. A real system has many interrupt sources —
|
||||||
|
the PLIC manages them. This is required for Linux.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 12 — Pipeline (Optional but educational)
|
||||||
|
|
||||||
|
What: Insert pipeline registers between your stages (fetch|decode|execute|memory|
|
||||||
|
writeback). Handle data hazards (forwarding/stalling) and control hazards (branch
|
||||||
|
prediction or pipeline flush).
|
||||||
|
|
||||||
|
Why: Your single-cycle core's clock speed is limited by the longest combinational
|
||||||
|
path (probably through the ALU). Pipelining lets each stage run in one short cycle.
|
||||||
|
This is the classic computer architecture exercise and deeply educational.
|
||||||
|
|
||||||
|
Why optional: A single-cycle core can run at maybe 50-80 MHz on the Artix-7. That's
|
||||||
|
plenty for booting Linux. Pipeline if you want to learn, not because you must.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 13 — SPI Flash Boot + DRAM
|
||||||
|
|
||||||
|
What: Add an SPI flash controller to boot from the on-board flash (instead of BRAM
|
||||||
|
initialization). Integrate AMD/Xilinx MIG IP for the DDR3 on the Arty A7. Update
|
||||||
|
your memory map: flash at boot, copy to DRAM, jump to DRAM.
|
||||||
|
|
||||||
|
Why: BRAM is tiny (a few hundred KB on Artix-7 100T). Linux needs megabytes. DRAM
|
||||||
|
gives you 256MB. Flash gives you persistent storage for the bootloader. This is
|
||||||
|
how real embedded systems boot.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 14 — S-Mode, U-Mode, Sv32 Virtual Memory
|
||||||
|
|
||||||
|
What: Add supervisor and user privilege modes. Implement Sv32 page table walking
|
||||||
|
(two-level page tables, 4KB pages). Add the satp CSR, page fault exceptions, and
|
||||||
|
sfence.vma.
|
||||||
|
|
||||||
|
Why: Linux runs the kernel in S-mode, user programs in U-mode. Virtual memory gives
|
||||||
|
each process its own address space and protects the kernel from user code. This is the
|
||||||
|
last major architectural piece before Linux.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 15 — Linux
|
||||||
|
|
||||||
|
What: Port a minimal Linux kernel (or use an existing RISC-V port). Write a device
|
||||||
|
tree for your SoC. Build an initramfs with BusyBox. Boot to a shell prompt.
|
||||||
|
|
||||||
|
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
|
||||||
|
- Text editor you like for SystemVerilog
|
||||||
|
- The RISC-V ISA spec (Volume 1: Unprivileged, Volume 2: Privileged) — free PDFs
|
||||||
Reference in New Issue
Block a user