A single-cycle RV32I processor with a Wishbone B4 SoC fabric, built for
FPGA (Digilent Nexys4 DDR, Artix-7 XC7A100T). Verified against the
rv32ui compliance suite (37/37 passing). Bare-metal C runtime with
printf over UART.
- Phase 0 complete. Foundation hardened — RTL bug fixes, C
toolchain, Verilator lint, and CI. See
TIER1_ROADMAP.mdfor the plan anddocs/phase0_retrospective.mdfor what Phase 0 actually taught us. - Phase 1 next. CSRs, traps, and M-mode. Picks up the
bus_error_oandtimer_irqwires Phase 0 left dangling for it.
Single-cycle RV32I core (rtl/rv32i_core.v) with external instruction
and data bus ports. The data bus runs through a Wishbone B4
interconnect to four slaves:
wb_dmem— 4 KB data RAMwb_uart— 115200 8N1 TX/RXwb_gpio— 16 LEDs and 16 slide switcheswb_timer— CLINT-style 64-bitmtime/mtimecmpwith IRQ
Instruction fetch is a separate Harvard-style port; IMEM is BRAM-backed
with a pc_next pre-fetch path. The compliance testbench uses a
unified 16 KB memory model — see the header comment in
tb/tb_compliance.v and the split between sw/link.ld (synthesized
hardware) and tests/link.ld (testbench). Deep dive in
docs/datapath.md; bus architecture in
docs/phase1_wishbone.md (archived).
| Region | Address Range | Size | Module | Notes |
|---|---|---|---|---|
| IMEM | 0x00000000 - 0x0000FFFF | 64 KB | imem.v |
16K words, read-only, not on WB bus |
| DMEM (RAM) | 0x00010000 - 0x00010FFF | 4 KB | wb_dmem.v |
Byte-addressable, R/W |
| UART | 0x80000000 - 0x8000000F | 16 B | wb_uart.v |
TX data/status, RX data/status |
| GPIO | 0x80001000 - 0x80001007 | 8 B | wb_gpio.v |
Output = LEDs, input = switches |
| Timer | 0x80002000 - 0x8000200F | 16 B | wb_timer.v |
mtime_lo/hi, mtimecmp_lo/hi |
Accesses outside these ranges auto-ack with zero data and assert
wb_interconnect.bus_error_o (a combinational line currently
unconnected at fpga_top — consumed by Phase 1's load/store
access-fault trap).
- Compliance: 37/37 rv32ui tests passing. Cycle counts in
docs/compliance_results.md. - Unit tests: 16 self-checking testbenches under
tb/, parsed strictly forALL PASSEDin CI. - Lint:
verilator --lint-only -Wallclean with 29 documented waivers — seedocs/lint_waivers.md. - CI: GitHub Actions runs lint + unit + compliance jobs in parallel on every push and PR.
Last measured at commit 6edc15c (2026-03-29, pre-Phase-0): ~1,969
LUTs / 275 FFs / 0.5 BRAM at 50 MHz on Artix-7 speed grade -1
(WNS +0.301 ns). Phase 0.1's RTL changes (reset-style conversions,
stall_o / bus_error_o ports, tightened peripheral decodes) are
expected to move utilisation by a handful of LUTs; synthesis will be
re-run the next time Vivado is opened. History in
docs/synth_results.md; build procedure in
docs/synth_guide.md. Core is ~3% of the
XC7A100T — enormous headroom for Phase 4+ work.
Requires Icarus Verilog and
Verilator for lint. Assembly
flow uses riscv64-unknown-elf-gcc; C flow requires a purpose-built
rv32i/ilp32 toolchain with newlib-nano — see
docs/toolchain.md for why Ubuntu packages and
the Vivado bundle both fall short.
# Module testbench
make sim MOD=alu
# Full-core integration
make sim-top
# FPGA top-level — assembly "Hello, RISC-V!"
make sim-fpga
# FPGA top-level — C "Hello from C!"
make sim-fpga-c
# Build an assembly program (sw/link.ld → sim/<name>.hex)
make asm PROG=test_basic
# Build a C program (sw/c_link.ld → sim/<name>.hex + sim/<name>_dmem.hex)
make c PROG=hello_c
# Run all 37 rv32ui compliance tests
cd tests && make run-all
# Verilator lint
verilator --lint-only -Irtl -Wall rtl/*.vAny sw/*.c file builds to an IMEM+DMEM hex pair with
make c PROG=<name>. Constraints:
- newlib-nano only.
printfworks;%fdeliberately disabled. - No heap.
_sbrkreturns -1. Keep programs stack-only. .rodatalives in DMEM, not IMEM. Harvard-bus constraint — a load cannot reach IMEM BRAM.sw/c_link.ldenforces this. A unified-memory rework is deferred to Phase 4.
Canonical example: sw/hello_c.c. ELF sizes (rv32i/ilp32,
-specs=nano.specs, -Os): .text 4294 B / .data 92 B / .bss
328 B.
Vivado 2025.2, Nexys4 DDR (Artix-7 XC7A100T), 50 MHz core clock (100
MHz input / 2). Pin assignments in constraints/; step-by-step in
docs/synth_guide.md.
| File | Purpose |
|---|---|
TIER1_ROADMAP.md |
Active planning doc (Phases 0–6) |
docs/datapath.md |
Architecture reference |
docs/phase1_wishbone.md |
Wishbone bus architecture (archived) |
docs/phase0_changelog.md |
Phase 0 commit log |
docs/phase0_retrospective.md |
Phase 0 lessons learned |
docs/toolchain.md |
C toolchain build-from-source |
docs/lint_waivers.md |
Verilator waiver rationales |
docs/compliance_results.md |
rv32ui cycle counts |
docs/synth_results.md |
Synthesis utilisation history |
docs/synth_guide.md |
Vivado build procedure |
docs/tech_debt.md |
Tracked technical debt with triggers |
docs/rv32i_reference.md |
ISA quick reference |
After Phase 0 foundation, the work splits into:
- Phase 1 — CSRs, traps, M-mode
- Phase 2 — Interrupts (timer, UART-RX)
- Phase 3 — RVFI + formal verification
- Phase 4 — 5-stage pipeline refactor
- Phase 5 — I$ / D$ caches
- Phase 6 — Tier 1 wrap-up and capstone decision (ML accelerator, DSP/SDR, or OS/RTOS)
See TIER1_ROADMAP.md for phase gates and details.