Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
9e4f5f8
Fix ACT4 mtval/stval config mismatch that was failing ExceptionsZc-00
ranaumarnadeem Aug 17, 2026
73df11f
Add bus-error trapping: instruction/load/store/AMO access faults
ranaumarnadeem Aug 17, 2026
66f83a9
Fix LR/SC reservation gap found by code review of bus-error trapping
ranaumarnadeem Aug 17, 2026
8af3ae7
Add Zifencei (FENCE.I) -- closes the I$/D$ self-modifying-code gap
ranaumarnadeem Aug 17, 2026
d9fd0ab
Add CLINT module (mtime/mtimecmp/mtip_o) -- standalone, Milestone 3
ranaumarnadeem Aug 17, 2026
0cdf262
Bus-wire CLINT into wb_addr_decoder/soc.sv -- Milestone 4
ranaumarnadeem Aug 18, 2026
1adda11
CSR-side CLINT/interrupt plumbing in csr_file.sv -- Milestone 5
ranaumarnadeem Aug 18, 2026
7f10dcf
Real interrupt-taking in core.sv + final soc.sv CLINT wiring -- Miles…
ranaumarnadeem Aug 18, 2026
78d5caa
Vendor taxi as a submodule, add Verilator toolchain fork for AXI work
ranaumarnadeem Aug 18, 2026
db07215
Add Wishbone-to-AXI4 bridge + behavioral DRAM timing model over real …
ranaumarnadeem Aug 19, 2026
e38dc7f
Bus-wire DRAM into wb_addr_decoder.sv -- 4-way decode (RAM/UART/CLINT…
ranaumarnadeem Aug 19, 2026
078052c
Wire up rvfi_intr for real + repair broken WSL riscv-formal toolchain
ranaumarnadeem Aug 19, 2026
2868dfe
Make EBREAK a real spec-compliant trap, not a permanent sim-only halt
ranaumarnadeem Aug 19, 2026
507f6ca
Trap write-attempts to read-only CSRs as illegal-instruction
ranaumarnadeem Aug 20, 2026
69cb4d0
Add UART RX peripheral (Milestone 2 of EBREAK/JTAG staged plan)
ranaumarnadeem Aug 20, 2026
7be47f4
Refresh README to reflect actual current architecture and status
ranaumarnadeem Aug 20, 2026
71dbdb7
Add Debug-mode CSRs: dcsr/dpc/dscratch0/dscratch1 (Milestone 3)
ranaumarnadeem Aug 20, 2026
589921f
Note Debug-mode CSRs (dcsr/dpc/dscratch0/1) as shipped in README
ranaumarnadeem Aug 20, 2026
31bf7ef
Round out README with a TOC, Contributing, and License section
ranaumarnadeem Aug 20, 2026
8b4feca
Ignore docs/internal/ for local-only reference documentation
ranaumarnadeem Aug 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,7 @@ dkms.conf

#Iverilog Simulation Files
*.vcd
*a.out
*a.out

# Internal-only reference documentation (not for the public repo)
docs/internal/
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[submodule "third_party/taxi"]
path = third_party/taxi
url = https://github.com/fpganinja/taxi.git
shallow = true
208 changes: 149 additions & 59 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,71 +4,122 @@ A RISC-V SoC, built collaboratively from scratch in SystemVerilog.

Join on [Discord](https://discord.gg/sQjhBvWXjF) if you're interested in the project!

**Contents:** [Current state](#current-state) · [Architecture](#architecture) ·
[Verification](#verification) · [Building and simulating](#building-and-simulating) ·
[Roadmap](#roadmap) · [Contributing](#contributing) · [License](#license)

---

## Current state

**RV64I base ISA + Zicsr (CSR instructions)**, non-pipelined, single-hart, fully
verified. The core is a multi-cycle Wishbone-master FSM (fetch → execute →
memory, one instruction fully retires before the next begins -- no forwarding,
no hazards to design around yet) driving a real Wishbone bus out to two real
peripherals.
**RV64IMAC + Zicsr, full M/S/U privilege modes, real timer interrupts**,
non-pipelined, single-hart, fully verified. The core is a multi-cycle
Wishbone-master FSM (fetch → execute → memory, one instruction fully retires
before the next begins -- no forwarding, no hazards to design around yet)
driving a real Wishbone bus out through an L1 instruction/data cache pair to
a real memory-mapped peripheral set.

### Architecture

- `design/decoder.sv`, `design/alu.sv`, `design/register_file.sv`,
`design/csr_file.sv` -- the datapath: instruction decode, ALU (full RV64I
arithmetic/logic/shift ops including the `*W` word-width family), a 32-entry
general-purpose register file, and the 8 machine-mode CSRs this milestone
backs (`misa`, `mvendorid`/`marchid`/`mimpid`/`mhartid`, `mscratch`,
`mcycle`, `minstret`).
- `design/core.sv` -- ties the above together as a Wishbone bus master. No
private instruction/data memory of its own; every fetch and load/store goes
out over the bus.
- `design/wb4_sram.sv`, `design/uart_tx.sv`, `design/wb_addr_decoder.sv` --
the two real Wishbone slaves (a flat 64-bit-word memory, and a
simulation-only UART that transmits via `$write`) plus the address decoder
routing between them.
- `design/soc.sv` -- top-level integration: `core` + `wb_addr_decoder` +
`wb4_sram` + `uart_tx`, `clk`/`rst` are its only ports.

Privilege modes, traps/interrupts, and virtual memory (Sv39) don't exist yet --
every access currently runs unconstrained, `ECALL` is a no-op, and the CSRs
backed today are exactly the ones meaningful without that infrastructure. See
`design/csr_file.sv`'s own header for the full scope note.
`design/divider.sv`, `design/c_expand.sv` -- the datapath: instruction
decode, ALU (full RV64I arithmetic/logic/shift ops including the `*W`
word-width family plus RV64M multiply/divide), a 32-entry general-purpose
register file, a standalone multi-cycle divider, and the RV64C
compressed-instruction decompressor.
- `design/csr_file.sv` -- every CSR the current privilege/interrupt/cache/
debug feature set needs: the M-mode base set (`misa`, `mvendorid`/
`marchid`/`mimpid`/`mhartid`, `mscratch`, `mcycle`, `minstret`), the full
M/S trap stack (`mstatus`/`sstatus`, `mtvec`/`stvec`, `mepc`/`sepc`,
`mcause`/`scause`, `mtval`/`stval`, `medeleg`/`mideleg`), the real
interrupt CSRs (`mie`/`mip`, spliced live against the CLINT's
timer-pending signal), and the Debug-mode CSRs (`dcsr`/`dpc`/
`dscratch0`/`dscratch1`) -- storage and access-control exist, but
nothing can legally enter Debug Mode yet (see below).
- `design/core.sv` -- ties the above together as a Wishbone bus master,
including real synchronous-trap and timer-interrupt-taking logic, atomic
memory operations (LR/SC/AMO), bus-error-to-access-fault trapping, and
FENCE.I. No private instruction/data memory of its own; every fetch and
load/store goes out over the bus (through the cache, see below).
- `design/icache.sv`, `design/dcache.sv`, `design/cache_complex.sv` -- a
direct-mapped, physically-indexed/physically-tagged, write-through L1
instruction/data cache pair sitting between `core` and the address
decoder. FENCE.I (Zifencei) flushes the I$ for self-modifying-code
coherence; the D$ never needs an equivalent flush (write-through keeps a
store hit's cached copy and the backing SRAM in lockstep).
- `design/wb4_sram.sv`, `design/uart_tx.sv`, `design/uart_rx.sv`,
`design/clint.sv`, `design/wb_addr_decoder.sv` -- the real Wishbone
slaves (a flat 64-bit-word memory; a simulation-only UART, transmit and
receive; an `mtime`/`mtimecmp` timer driving real machine-timer
interrupts) plus the address decoder routing between them.
- `design/soc.sv` -- top-level integration: `core` + cache + address
decoder + `wb4_sram` + `uart_tx`/`uart_rx` + `clint`. `clk`/`rst` are its
only ports (no real serial pins exist yet -- the UART model transmits via
`$write` and receives via a testbench-only backdoor task).

Sv39 virtual memory doesn't exist yet -- every address currently runs
untranslated. A hardware Debug Module (JTAG/DMI, in the spirit of the
RISC-V External Debug Support spec) is in progress: `EBREAK` is already a
real, resumable synchronous trap, UART RX is wired up as its planned
transport, and the Debug-mode CSRs (`dcsr`/`dpc`/`dscratch0`/`dscratch1`)
exist with real access control -- any access from anywhere currently
traps, since there's no Debug Mode to legally be in yet. The halt/resume
FSM and the DM/JTAG stack itself are still ahead. See `design/csr_file.sv`'s
and `design/core.sv`'s own header comments for the exact current scope.

`verification/taxi/` also carries a standalone Wishbone-to-AXI4 bridge and
behavioral DRAM timing model, built on a vendored `taxi` AXI4 IP submodule
-- proven independently via its own Verilator-only test flow, but not yet
wired into `design/soc.sv` (that RTL is Verilator-only, since it
instantiates a SystemVerilog `interface`, which Icarus cannot parse; `soc.sv`
itself must stay 100% Icarus-compatible).

### Verification

17 testbenches (unit-level for the ALU/register file/CSR file in isolation,
integration-level driving the real Wishbone bus, one running a real
`riscv64-unknown-elf`-assembled program), all passing. Shared infrastructure
lives in `testbench/`: `check_lib.sv` (a `check()` primitive), `wb_driver.sv`
(a Wishbone bus-cycle task), `halt_wait.sv` (timeout-guarded halt waiting),
`pc_trigger_sample_monitor.sv` and `core_wb4_sram_harness.sv` (reusable
monitor/harness modules) -- pulled into new testbenches via `` `include ``
rather than hand-rolled each time.

Real code coverage has been measured (line/branch/toggle/expression, via
Verilator) across every live design file, not just claimed. Current whole-design
coverage: 97.6% line, 99.0% branch, 100% expression -- what's left uncovered
is understood and benign (a couple of structurally-unreachable default arms,
one genuinely unused ALU op, one buffer-full guard that'd need over 256 writes
in a single test to trigger).
A large testbench suite (unit-level for every datapath/cache/peripheral
module in isolation, integration-level driving the real Wishbone bus and
the real cache hierarchy, several running real `riscv64-unknown-elf`
-assembled/toolchain-built programs), all passing, run through a
non-committed regression script (there's no single top-level build script
checked in yet -- see *Building and simulating* below). Shared
infrastructure lives in `testbench/`: `check_lib.sv` (a `check()`
primitive), `wb_driver.sv` (a Wishbone bus-cycle task), `halt_wait.sv`
(timeout-guarded halt waiting), and several reusable harness/monitor
modules -- pulled into new testbenches via `` `include `` rather than
hand-rolled each time.

Beyond the project's own testbenches, every ISA feature is additionally
cross-checked against two independent, external references:

- **`verification/riscv-arch-test/`** -- the official RISC-V Architecture
Test (ACT4) compliance suite, run as real self-checking ELFs through a
dedicated runner. The large majority pass; the handful that don't are
understood, documented, spec-legal config/UDB mismatches, not RTL bugs.
- **`verification/riscv-formal/`** -- formal, unbounded-cycle-count
verification via SymbiYosys/RVFI taps on `design/core.sv`, covering the
base RV64I integer pipeline, the A-extension (atomics), and the
C-extension (compressed instructions).

Real code coverage has also been measured (line/branch/toggle/expression,
via Verilator) across the design, not just claimed -- see individual
milestone notes for current numbers, which move as new features land.

---

## Building and simulating

Everything here is developed and verified against **Icarus Verilog**
(`iverilog`/`vvp`) and **Verilator**, run through WSL on Windows. There is no
single top-level build script yet -- compile the specific file set a given
testbench needs directly, e.g.:
(`iverilog`/`vvp`) and **Verilator**, run through WSL on Windows. There is
no single top-level build script checked into the repo yet -- compile the
specific file set a given testbench needs directly, e.g.:

```sh
iverilog -g2012 -I design -I testbench -o /tmp/soc_tb.out \
design/alu.sv design/decoder.sv design/register_file.sv design/csr_file.sv \
design/core.sv design/wb4_sram.sv design/uart_tx.sv design/wb_addr_decoder.sv \
design/soc.sv testbench/soc_tb.sv
design/decoder.sv design/alu.sv design/c_expand.sv design/csr_file.sv \
design/divider.sv design/register_file.sv design/uart_tx.sv \
design/uart_rx.sv design/wb4_sram.sv design/wb_addr_decoder.sv \
design/icache.sv design/dcache.sv design/cache_complex.sv \
design/clint.sv design/core.sv design/soc.sv testbench/soc_tb.sv
cd design && vvp /tmp/soc_tb.out
```

Expand All @@ -80,17 +131,22 @@ its `$readmemh` of `../firmware/crt0.hex` resolves.
A Verilator lint pass over the full SoC:

```sh
verilator --lint-only -Wall -Idesign -Itestbench --top-module soc \
design/alu.sv design/decoder.sv design/register_file.sv design/csr_file.sv \
design/core.sv design/wb4_sram.sv design/uart_tx.sv design/wb_addr_decoder.sv \
design/soc.sv
verilator --lint-only -Wall -Idesign -Idesign/defaults --top-module soc \
design/decoder.sv design/alu.sv design/c_expand.sv design/csr_file.sv \
design/divider.sv design/register_file.sv design/uart_tx.sv \
design/uart_rx.sv design/wb4_sram.sv design/wb_addr_decoder.sv \
design/icache.sv design/dcache.sv design/cache_complex.sv \
design/clint.sv design/core.sv design/soc.sv
```

(Verilator wants `-Idesign`, no space; Icarus accepts either form.)

`firmware/` holds a real C toolchain build (`riscv64-unknown-elf-gcc`/`-as`/
`-ld`) producing the hex images some testbenches load -- see
`firmware/Makefile`.
`firmware/Makefile`. `verification/taxi/` (the Wishbone-to-AXI4 bridge and
DRAM model) is Verilator-only and has its own separate test runner --
never add a `taxi`-touching testbench to the file lists above, it will not
compile under Icarus.

---

Expand All @@ -100,12 +156,46 @@ RV64**IMAC** + Zicsr + U/S/M privilege + Sv39, non-pipelined and in-order,
before any pipelining/OoO work starts -- deliberately, so out-of-order
correctness has a trusted in-order reference to debug against.

RV64IMAC + Zicsr + full U/S/M privilege modes are done, each verified via a
4-pillar pattern (unit test, hand-assembled end-to-end core testbench,
real-toolchain encoder cross-check, real-toolchain end-to-end firmware test)
and cross-checked against the official riscv-arch-test (ACT4) compliance
suite. Sv39 virtual memory is next -- a different teammate's work, built on
top of the U/S/M privilege seams (`fetch_paddr`/`mem_paddr`, `satp`,
`mstatus.MPRV/SUM/MXR`) this core already carries specifically for that
handoff. Bus protocol stays Wishbone at the core; AXI4 is a future fabric
concern at the edge, not a core-level one.
RV64IMAC + Zicsr + full U/S/M privilege modes + real timer interrupts are
done, each verified via a multi-pillar pattern (unit test, hand-assembled
end-to-end core testbench, real-toolchain encoder cross-check, real-
toolchain end-to-end firmware test) and cross-checked against both the
official riscv-arch-test compliance suite and formal (riscv-formal)
verification. Sv39 virtual memory is next on the privilege/memory side --
a different teammate's work, built on top of the U/S/M privilege seams
(`fetch_paddr`/`mem_paddr`, `satp`, `mstatus.MPRV/SUM/MXR`) this core
already carries specifically for that handoff.

In parallel, a hardware Debug Module (JTAG/DMI) is being built out in
staged milestones: `EBREAK` is now a real, resumable synchronous trap,
UART RX exists as its planned transport, and the Debug-mode CSRs
(`dcsr`/`dpc`/`dscratch0`/`dscratch1`) exist with real access control;
the halt/resume FSM and the JTAG TAP/DMI/Program-Buffer stack itself are
still ahead.

Bus protocol stays Wishbone at the core; AXI4 is a fabric-edge concern (see
the standalone bridge/DRAM model under `verification/taxi/`), not a
core-level one.

---

## Contributing

This is a collaborative, from-scratch build -- [Discord](https://discord.gg/sQjhBvWXjF)
is where design decisions get discussed and work gets coordinated before a PR
shows up, not an afterthought support channel. Come say what you're
interested in; the *Roadmap* section above and the per-module header
comments throughout `design/` are the best starting map of what's settled,
what's in flight, and what's still open.

A few conventions worth knowing before sending a change: every new RTL
feature ships with real tests, not just a claim it works (see
*Verification* above) -- a full local regression plus a Verilator lint
pass, both clean, is the bar every prior milestone has held itself to, and
new privilege/CSR/interrupt logic additionally gets cross-checked against
riscv-arch-test and, where practical, riscv-formal. Module header comments
explain *why*, not just *what* -- keep that up when you add or change one.

## License

[MIT](LICENSE).
15 changes: 14 additions & 1 deletion design/cache_complex.sv
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,19 @@ module cache_complex #(
output logic ack_o,
output logic err_o,

/*
* Zifencei: passed straight through to icache0.flush_i UNCONDITIONALLY
* -- load-bearing to get right, not a style choice. ifetch_i is LOW
* throughout S_EXEC (FENCE.I's own commit cycle, where core.sv pulses
* icache_flush_o -- see that port's own comment), so gating this on
* ifetch_i the way cyc_i/stb_i are routed above would silently make
* FENCE.I a permanent no-op: the exact class of bug this signal exists
* to prevent, not just an edge case. D$ has no equivalent flush path
* (or need for one) -- write-through already keeps a store hit's
* cached copy and SRAM in lockstep.
*/
input logic flush_i,

// Memory-facing port -- this module is a Wishbone MASTER from
// wb4_sram.sv's side. Shared by both sub-caches; see this module's
// own header for why no arbitration is needed.
Expand All @@ -82,7 +95,7 @@ module cache_complex #(
icache #(.num_lines(num_lines), .line_words(line_words)) icache0 (
.clk(clk), .rst(rst),
.addr_i(addr_i), .dat_o(ic_dat_o), .cyc_i(cyc_i && ifetch_i), .stb_i(stb_i && ifetch_i),
.ack_o(ic_ack), .err_o(ic_err),
.ack_o(ic_ack), .err_o(ic_err), .flush_i(flush_i),
.mem_addr_o(ic_mem_addr), .mem_dat_i(ic_mem_dat_i), .mem_sel_o(ic_mem_sel),
.mem_we_o(ic_mem_we), .mem_cyc_o(ic_mem_cyc), .mem_stb_o(ic_mem_stb),
.mem_ack_i(ic_mem_ack), .mem_err_i(ic_mem_err)
Expand Down
Loading