A real, synthesizable RV32I RISC-V CPU in SystemVerilog, compiled to WebAssembly, running your own assembly live in the browser. Third project in the series after tiny-gpu and tiny-tpu. It reuses tiny-tpu's toolchain one to one:
rtl/*.sv → Verilator → C++ harness → Emscripten → web/public/tiny_cpu.wasm → React island → SVG.
Same rule as tiny-tpu: nothing on screen is faked. Every pipeline register, forwarding
path, stall and flush the UI draws comes from a dbg_* port on the compiled RTL.
- Two real cores, one ISA. A single-cycle reference core and a 5-stage pipelined core, both RV32I, sharing the same ALU, decoder and register file. Running the same program on both makes CPI and hazards concrete.
- Verified for real. The core passes the official
riscv-testsrv32ui-p-*suite, matches a Python golden ISS in lockstep on every retired instruction, and survives randomized programs. - Write code, watch it run. The browser has an assembly editor, an in-browser assembler, step/run/rewind, and four views: datapath, pipeline, hazard lab and programs.
- Synthesizable. Builds with Yosys + nextpnr for an iCE40 or ECP5 FPGA. The LUT count and fmax go in the README.
- No privileged spec: no traps, interrupts, MMU or S/U modes.
ecall/ebreaksimply halt. - No caches or branch predictor in v1. Both are stretch goals (§12).
- No compressed instructions (C), no M extension in v1. M is the first stretch goal.
- No
fence.ior self-modifying code (Harvard memories).
| Region | Base | Size | Notes |
|---|---|---|---|
| IMEM | 0x0000_0000 |
4 KiB (1024 words) | Loaded by the harness; read-only to the core |
| DMEM | 0x0001_0000 |
4 KiB | Byte-addressable, LB/LH/LW/SB/SH/SW |
MMIO CONSOLE |
0x0002_0000 |
1 word | Writing a byte sends it to the browser console panel |
MMIO TOHOST |
0x0002_0004 |
1 word | Writing here halts the core. 1 = PASS, anything else = FAIL code (the riscv-tests convention) |
MMIO CYCLE |
0x0002_0008 |
1 word | Read-only cycle counter (also mapped as CSR cycle) |
All three sizes are parameters in tiny_cpu_pkg.sv. 4 KiB each keeps the WASM small and
fits iCE40 BRAM.
┌────┐ IF/ID ┌────┐ ID/EX ┌────┐ EX/MEM ┌─────┐ MEM/WB ┌────┐
│ IF │────────▶│ ID │────────▶│ EX │─────────▶│ MEM │─────────▶│ WB │
└────┘ └────┘ └────┘ └─────┘ └────┘
▲ pc_next │ regfile read │ ALU, branch │ DMEM / MMIO │ regfile write
│ │ imm gen │ resolve │ │
└── redirect ◀──┼───────────────┘ │ │
│ forwarding ◀── EX/MEM ───┘◀── MEM/WB ───────┘
└── hazard unit (load-use stall)
Design decisions (record each one in docs/ARCHITECTURE.md):
| Decision | Choice | Why |
|---|---|---|
| Memory read timing | Synchronous read on both IMEM and DMEM | Maps straight to FPGA BRAM. IMEM is addressed with pc_next, so the instruction is ready in IF/ID. DMEM is addressed in EX, so data is ready in MEM. |
| Branch / JAL / JALR resolution | In EX, predict not-taken | Simplest correct design. A taken branch flushes IF/ID and ID/EX (2-cycle penalty), which is very visible in the UI. |
| Forwarding | EX/MEM→EX and MEM/WB→EX, for both rs1 and rs2 | Standard design. Store data (rs2) is forwarded as well. |
| Register-file write/read in the same cycle | Write-first bypass inside regfile.sv, parameter BYPASS (1 in the pipeline, 0 in the single-cycle core) |
Removes the WB→ID hazard without an extra forwarding path. In the single-cycle core the writer is the reader, so a bypass is wrong and a combinational loop (found in Phase 2). |
| Load-use hazard | 1-cycle stall: freeze PC and IF/ID, insert a bubble into ID/EX | The only stall in v1. |
x0 |
Hard-wired to zero in the register file. Forwarding ignores rd == 0 |
A common bug source; covered by tests. |
Runtime knob cfg_forward_en |
Top-level input pin. When 0, forwarding is disabled and the hazard unit stalls on every RAW hazard | Lets the Hazard Lab toggle forwarding live with one WASM build. |
| Halt | ecall, ebreak or a TOHOST write raises halted. The pipeline drains, and done asserts after the halting instruction retires |
Clear end condition for run(). |
| Misaligned load/store | Not supported. The address is forced into alignment and dbg_misaligned flags it |
rv32ui-p-ma_data is excluded from the suite. |
core_single.sv uses the same alu, decoder, imm_gen, branch_unit and regfile, with
combinational reads from a separate mem_comb.sv model (simulation only; it is not built for
the FPGA). CPI is exactly 1. It exists to teach and to compare, and it is also the first core you
build (Phase 2).
Each core outputs one retire record per cycle. This is the backbone of all verification and of the UI's "retired" log:
rvfi_valid, rvfi_order, rvfi_pc, rvfi_insn,
rvfi_rd_addr, rvfi_rd_wdata,
rvfi_mem_addr, rvfi_mem_rmask, rvfi_mem_wmask, rvfi_mem_rdata, rvfi_mem_wdata,
rvfi_halt
Phase 1 additions (implemented in sim/golden/iss.py): pc_wdata (next PC), misaligned (load/store was forced into alignment) and cycle_dependent (rd value depends on timing: rdcycle/rdtime/CYCLE MMIO; lockstep masks it). mem_addr is the word-aligned address, masks are byte lanes within it, and mem_rdata/mem_wdata are lane-positioned words.
tiny-cpu/
├── AGENTS.md / CLAUDE.md / CONTEXT.md # same conventions as tiny-tpu
├── README.md LICENSE .editorconfig
├── .github/workflows/
│ ├── rtl.yml # lint + cocotb unit + riscv-tests + lockstep + random
│ ├── wasm.yml # Verilator + emsdk build, artifact size check
│ ├── web.yml # lint, typecheck, unit tests, build
│ └── fpga.yml # yosys/nextpnr synth, posts LUT/fmax (Phase 9)
├── rtl/
│ ├── tiny_cpu_pkg.sv # opcodes, ALU ops, params, enums
│ ├── alu.sv
│ ├── regfile.sv # 32×32, x0=0, write-first bypass, dbg read-all port
│ ├── imm_gen.sv # I/S/B/U/J immediates
│ ├── decoder.sv # instr → control bundle (struct)
│ ├── branch_unit.sv # BEQ/BNE/BLT/BGE/BLTU/BGEU
│ ├── lsu.sv # byte/half/word align, sign-extend, wmask
│ ├── forward_unit.sv
│ ├── hazard_unit.sv
│ ├── mem_sync.sv # BRAM-style sync memory (IMEM/DMEM)
│ ├── mem_comb.sv # sim-only comb memory for single-cycle core
│ ├── mmio.sv # CONSOLE / TOHOST / CYCLE
│ ├── csr_counters.sv # rdcycle / rdinstret (Zicntr subset)
│ ├── core_single.sv
│ ├── core_pipe.sv
│ ├── tiny_cpu_top.sv # parameter CORE = "PIPE" | "SINGLE"; dbg_* ports
│ └── spike/ # toolchain smoke test (counter.sv), as in tiny-tpu
├── sim/
│ ├── Makefile conftest.py
│ ├── golden/
│ │ ├── iss.py # Python RV32I ISS (the golden model)
│ │ ├── decode.py # shared decode tables (also used to gen TS tables)
│ │ └── test_iss.py # ISS self-tests (pytest, no RTL)
│ ├── test_alu.py test_regfile.py test_imm_gen.py test_decoder.py
│ ├── test_branch_unit.py test_lsu.py test_hazards.py
│ ├── test_core_lockstep.py # RTL vs ISS on every retire
│ ├── test_riscv_tests.py # parametrized over rv32ui-p-*
│ ├── test_random.py # random program generator + lockstep
│ └── programs/ # .S sources: fib, sort, memcpy, mandelbrot, hazards_*.S
├── sw/
│ ├── link.ld # memory map from §2.1
│ ├── crt0.S # set sp, call main, write TOHOST
│ ├── riscv-tests/ # git submodule (riscv-software-src/riscv-tests)
│ ├── env/tinycpu/riscv_test.h # custom test env for our memory map
│ └── Makefile # builds programs + riscv-tests → .hex/.bin
├── wasm/
│ ├── harness.cpp bindings.cpp build.sh README.md
├── fpga/
│ ├── ice40/ (pcf, Makefile) ecp5/
├── docs/
│ ├── STATE_SCHEMA.md # per-cycle contract (§6)
│ ├── ARCHITECTURE.md # decisions table + diagrams
│ └── VERIFICATION.md
└── web/ # Astro + React + Tailwind + shadcn (copy tiny-tpu config)
├── public/tiny_cpu.mjs tiny_cpu.wasm
└── src/
├── lib/
│ ├── state-schema.ts # mirrors STATE_SCHEMA.md
│ ├── wasm-loader.ts # copied from tiny-tpu, renamed
│ ├── isa.ts # generated from sim/golden/decode.py
│ ├── assembler.ts # RV32I + pseudo-ops + labels
│ ├── disassembler.ts
│ └── programs.ts # bundled example programs
├── hooks/useTinyCpu.ts # sim lifecycle, step/run/rewind, history buffer
├── components/ (see §7)
└── pages/ index.astro app.astro docs/*.astro
| Tool | Version | Used for |
|---|---|---|
| Verilator | ≥ 5.x (same as tiny-tpu) | lint, cocotb sim, WASM model |
| cocotb + pytest + numpy | same venv as tiny-tpu (~/.venvs/tinycpu) |
all RTL tests |
riscv64-unknown-elf-gcc (or riscv32-…) |
any recent; -march=rv32i_zicsr -mabi=ilp32 |
building riscv-tests and C/asm programs in CI |
| Emscripten | same as tiny-tpu | WASM |
| Yosys + nextpnr + icestorm/trellis | OSS CAD Suite | Phase 9 synthesis |
| pnpm + Astro + React + Vitest | same as tiny-tpu | web |
The in-browser assembler is TypeScript, not GNU as. The browser can't run gcc, so
assembler.ts handles labels, .text/.data/.word/.byte/.ascii/.align, and the pseudo-ops
li, la, mv, nop, j, jr, ret, call, beqz, bnez, bgt, ble, not, neg, seqz.
CI checks it against GNU: every file in sim/programs/*.S is assembled by both, and the
bytes must match exactly.
Four layers. Each phase below says which layers must be green.
- Unit tests (cocotb), one per module: ALU (all ops, exhaustive shifts, random 10k vectors
checked against numpy), regfile (x0, write-first bypass), imm_gen (every format, sign edges),
decoder (every RV32I opcode → expected control bundle, generated from
decode.py), branch_unit, lsu (every size × sign × byte offset). - Golden ISS lockstep:
iss.pyexecutes the same program. On everyrvfi_validthe test comparespc, insn, rd, rd_wdata, mem_*and stops at the first divergence, printing a readable diff with the disassembly of the last 8 retired instructions. - Official compliance: all
rv32ui-p-*tests fromriscv-testson both cores, using a customenv/tinycpu(ourlink.ld, withRVTEST_PASS/FAILwritingTOHOST). Excluded:fence_i,ma_data. Verified in Phase 1: 40 tests (42 sources minus those two) on riscv-tests3df703c; the largest,ld_st, is 3732 B and fits the 4 KiB IMEM. - Random programs:
test_random.pygenerates N programs (seeded) of 200–2000 instructions, weighted toward hazards: back-to-back RAW, load-use, branch after load, store after load, writes to x0, JALR to computed targets. Every program runs in lockstep, withcfg_forward_en∈ {0, 1}. CI runs 200 seeds; nightly runs 5000.
Directed hazard tests (sim/programs/hazards_*.S + test_hazards.py) check exact
cycle counts, not just results. Example: lw x1,0(x2); add x3,x1,x1 takes exactly 1
extra cycle with forwarding on and 2 with it off (Phase 4 measured: the write-first regfile bypass saves the third). These numbers are the ones the Hazard Lab
displays, so they get pinned.
Performance assertions: fib(20) and bubble sort on 32 elements have pinned cycle counts per
core in sim/perf_baseline.json. If one changes unexpectedly, CI fails.
As in tiny-tpu: the harness emits one CycleState per clock, and the two files must stay in sync.
type StageName = "IF" | "ID" | "EX" | "MEM" | "WB";
interface StageState {
valid: boolean; // false = bubble
pc: number;
insn: number; // raw 32-bit; disassembly is derived in JS (documented as derived)
}
interface CycleState {
cycle: number;
core: "PIPE" | "SINGLE";
stages: Record<StageName, StageState>; // SINGLE: only IF populated
hazard: {
stall: boolean; // dbg_stall
flushIFID: boolean; // dbg_flush_ifid
flushIDEX: boolean; // dbg_flush_idex
loadUse: boolean; // dbg_load_use
fwdA: "NONE" | "EXMEM" | "MEMWB"; // dbg_fwd_a (2 bits)
fwdB: "NONE" | "EXMEM" | "MEMWB";
};
ex: { aluOp: string; aluA: number; aluB: number; aluY: number;
branchTaken: boolean; target: number };
mem: { addr: number; wdata: number; rdata: number;
wmask: number; rmask: number; mmio: boolean };
wb: { rd: number; wdata: number; we: boolean };
regs: number[]; // 32 entries, dbg_regs (read-all port)
retired: Retire | null; // RVFI-lite record from §2.4
console: number | null; // byte written to CONSOLE this cycle
counters: { cycle: number; instret: number };
halted: boolean;
done: boolean;
}Every field lists its dbg_* source in the Markdown, exactly as tiny-tpu does.
class TinyCpuSim {
void reset(std::string core /* "PIPE" | "SINGLE" */);
void setForwarding(bool on); // drives cfg_forward_en
void loadImem(val uint32Array); // words from 0x0000_0000
void loadDmem(val uint8Array); // bytes from 0x0001_0000
val step(); // one clock → CycleState
val run(int maxCycles); // CycleState[] until done or limit
val runFast(int maxCycles); // no per-cycle states; returns summary only
val getRegs(); val readDmem(int addr, int len);
int getCycleCount(); bool isDone();
};- Two cores, one WASM: Verilate
tiny_cpu_toptwice (-DCORE_PIPEand-DCORE_SINGLE, with separate--prefix) and link both models into one module.reset(core)picks one. - Rewind is replay. The design is deterministic, so
useTinyCpukeeps a ring buffer of the last 4096CycleStates. Seeking further back re-runs from reset withrunFastup to the target cycle, then switches tostep. No snapshot code is needed in C++. - Size budget: under 400 KB
.wasmgzipped.wasm.ymlfails the build above that.
Copy tiny-tpu's Astro/React/Tailwind/shadcn setup and visual language, so the three projects look like one family.
/— landing page: hero GIF of the pipeline running fib, then "Open the CPU" button./app— the visualizer (one React island)./docs/how-it-works,/docs/architecture,/docs/the-pipeline,/docs/hazards,/docs/verification.
┌─────────────────────────────────────────────────────────────────────┐
│ [Program ▾ fib.S] [Core: PIPE|SINGLE] [Forwarding ●] ⏮ ◀ ▶ ⏭ ▶▶ speed │
├──────────────────────┬──────────────────────────────────────────────┤
│ Editor (CodeMirror) │ LevelTabs: L1 Datapath | L2 Pipeline | │
│ + assemble errors │ L3 Hazard Lab | L4 Programs │
│ current PC line ▶ │ ┌──────────────────────────────────────┐ │
│ │ │ active view │ │
├──────────────────────┤ └──────────────────────────────────────┘ │
│ Registers x0–x31 │ Console (MMIO) │ Stats: cycles, instret, │
│ (flash on write) │ │ CPI, stalls, flushes │
└──────────────────────┴──────────────────────────────────────────────┘
| Level | Component | What it shows |
|---|---|---|
| L1 Datapath | DatapathView.tsx |
The single-cycle core drawn as an SVG datapath: PC → IMEM → decoder → regfile → ALU → DMEM → WB mux. Active wires light up with the values on them. |
| L2 Pipeline | PipelineView.tsx + PipelineChart.tsx |
The 5 stages as boxes holding the instruction in each, pipeline registers between them, and forwarding arrows when fwdA/B are active. Below that, the classic cycle × instruction chart (rows = instructions, columns = cycles, cells = IF/ID/EX/MEM/WB, stalls shown as bubbles, flushes crossed out). |
| L3 Hazard Lab | HazardLab.tsx |
Preset snippets (RAW, load-use, branch taken/not taken, store-after-load). Runs the same snippet with forwarding on and off side by side (two sim instances) and shows the cycle difference with an explanation of why. |
| L4 Programs | ProgramsView.tsx |
Runs whole programs (fib, bubble sort with a live memory heatmap, memcpy, ASCII Mandelbrot on the console, primes) and compares SINGLE vs PIPE: cycles, CPI, and, using the FPGA fmax from Phase 9, estimated time to finish. |
AsmEditor.tsx (CodeMirror 6 with an RV32I language mode, error gutter, PC highlight),
RegisterFile.tsx, MemoryView.tsx (hex + heatmap), ConsolePanel.tsx, StatsBar.tsx,
Controls.tsx (adapted from tiny-tpu), LevelTabs.tsx (reused).
assembler.test.ts: golden encodings for every instruction and pseudo-op; label and forward-reference resolution; error messages with line numbers.disassembler.test.ts: assemble → disassemble round trip over the whole ISA.state-schema.test.ts: a recordedCycleState[]fixture from the harness validates against the TS types (zod).- A Playwright smoke test: load
/app, run fib, and assert the console shows6765.
Estimates assume one person working part-time, roughly at the pace of tiny-tpu.
- Create the repo with the §3 layout. Copy
.editorconfig, the CI skeletons and the web config from tiny-tpu. WriteCLAUDE.md,AGENTS.mdandCONTEXT.md. rtl/spike/counter.sv→wasm/spikebuilds and runs in the browser (proves the toolchain).- Exit: all three workflows are green on an empty project; the spike page counts in the browser.
sim/golden/iss.py: full RV32I, the §2.1 memory map, MMIO, and RVFI-style trace output.sw/link.ld,crt0.S,sw/Makefile; riscv-tests submodule with theenv/tinycpuport.- The ISS must pass every
rv32ui-p-*test itself before any RTL exists. - Generate
web/src/lib/isa.tsfromdecode.py, so Python and TS share one opcode table. - Exit:
pytest sim/golden -qis green; the ISS passes riscv-tests;sim/programs/*.Sprint the expected console output under the ISS.
tiny_cpu_pkg,alu,regfile,imm_gen,decoder,branch_unit,lsu,mem_comb,mmio,csr_counters→core_single→tiny_cpu_top(CORE="SINGLE").- A unit test for every module (§5 layer 1).
- Exit:
verilator --lint-only -Wallis clean; all unit tests pass;core_singlepasses lockstep on every program and all included riscv-tests.
mem_sync,core_pipewith IF/ID/EX/MEM/WB registers (packed structs),dbg_*ports per stage.- Data hazards are avoided by construction (NOP padding via
golden/progbuild.py);hazard_free.pyholds the program set. The taken-branch flush is included here because lockstep cannot match the ISS without it. - Exit: the hazard-free programs pass lockstep; the waveform in GTKWave shows the 5-stage overlap.
forward_unit,hazard_unit(load-use stall, branch flush),cfg_forward_en.- Directed tests with exact cycle counts (§5) and pinned
perf_baseline.json. - Exit:
core_pipepasses all included riscv-tests and 200 random lockstep seeds with forwarding both on and off; the hazard cycle counts are pinned.
harness.cpp(dual-model),bindings.cpp,build.sh,docs/STATE_SCHEMA.md.- A Node test script (
wasm/test.mjs) that loads the.mjs, runs fib on both cores, and checks the console output and cycle count againstperf_baseline.json. The WASM gets the same check as the RTL. - Exit:
wasm.ymlis green; the size budget is met; the Node test passes.
assembler.ts,disassembler.tswith Vitest suites and the CI comparison against GNU as.useTinyCpu(lifecycle, history ring, replay-rewind),Controls,AsmEditor,RegisterFile,MemoryView,ConsolePanel,StatsBar.- L2 Pipeline view comes first. It's the signature visual.
- Exit: you can type a program, assemble it, step it forward and backward, and watch the pipeline view and registers update. The Playwright smoke test passes.
- L1 Datapath (SVG), L3 Hazard Lab (two sim instances side by side), L4 Programs (heatmap, SINGLE vs PIPE comparison).
- Exit: every level works on desktop and at phone width (375 px), in light and dark themes.
- Landing page + hero GIF/MP4 (reuse tiny-tpu's capture approach and
gen-og-image.mjs). - The five docs pages, each with an embedded live mini-visualizer (as tiny-tpu's
LandingEmbed). - README in tiny-tpu's style: badges, live demo link, the verification table (riscv-tests N/N passing, random seeds, the size budget).
- Deploy to Vercel (
tiny-cpu.vercel.app). - Exit: public URL live; README complete; all CI green.
fpga/ice40(e.g. iCEBreaker) and/orfpga/ecp5(ULX3S): a top wrapper with a UART replacingCONSOLEand LEDs showingTOHOST.fpga.ymlruns Yosys + nextpnr and posts the LUT/FF/BRAM count and fmax to the job summary. These numbers feed L4's "estimated real time".- Exit: synthesis is clean with no latches; the numbers are in the README. If you have a board, fib prints over UART.
Total: roughly 12–14 weeks part-time. Phases 0–5 (about 7 weeks) give you a fully verified CPU that is already worth publishing on its own.
| Workflow | Trigger | Jobs |
|---|---|---|
rtl.yml |
push/PR touching rtl/ sim/ sw/ |
lint → ISS tests → unit tests → riscv-tests (both cores) → lockstep programs → random (200 seeds × fwd on/off) → perf baseline |
rtl-nightly.yml |
cron | random 5000 seeds; uploads the failing seed + FST waveform as an artifact |
wasm.yml |
push touching rtl/ wasm/ |
build → size budget → wasm/test.mjs |
web.yml |
push touching web/ |
lint → typecheck → vitest → assembler-vs-GNU → build → Playwright smoke |
fpga.yml |
push touching rtl/ fpga/ |
yosys + nextpnr → job summary |
Full pre-PR check (goes in CLAUDE.md):
verilator --lint-only -Wall rtl/*.sv
pytest sim/golden -q
cd sim && make unit && make riscv-tests && make lockstep && make random SEEDS=50
bash wasm/build.sh && node wasm/test.mjs
cd web && pnpm lint && pnpm typecheck && pnpm test && pnpm build| Risk | Mitigation |
|---|---|
riscv-tests assume 0x8000_0000 and their own env |
Custom env/tinycpu/riscv_test.h + link.ld; the ISS proves the port in Phase 1, before any RTL |
| Off-by-one timing from synchronous memories | Write the timing diagram in ARCHITECTURE.md before coding Phase 3; dedicated directed tests for IF after a redirect and for loads in MEM |
| Forwarding bugs that only show in rare sequences | Random generator weighted toward hazards + lockstep stops at the first divergence with a readable diff |
| Two Verilated models in one WASM collide on symbols | Separate --prefix Vcpu_pipe / Vcpu_single and separate obj dirs; verify in the Phase 0 spike |
Long programs → millions of CycleState objects |
runFast for cycles you don't render; 4096-entry ring buffer; replay to rewind |
| TS assembler disagrees with GNU | Byte-exact comparison in CI over the whole program corpus |
| Scope creep (caches, predictor, M) | All in §12; nothing from it starts before Phase 8 ships |
- M extension (
mul/div/rem): a multi-cycle divider shows a new kind of stall in the UI. Add therv32um-p-*tests. - Branch prediction: static BTFN, then a 2-bit BHT + BTB. L3 gets a predictor-accuracy panel.
- Instruction/data cache: a direct-mapped I$ and D$ with a slow backing memory. A new "L5 Memory" view shows hits and misses (and sets up a future tiny-cache project).
- Traps + Zicsr (
mtvec,mepc,mcause, timer interrupt): enough to run a tiny scheduler. - C compiler in CI: build C programs with gcc and ship them as presets (precompiled
.hex). - Tiny Tapeout submission of the single-cycle core.
- tiny-soc: put tiny-cpu, tiny-tpu and a ported tiny-gpu on one bus, with the CPU sending matmul work to the TPU through MMIO.
- Create
deaneeth/tiny-cpu; copy tiny-tpu's.editorconfig,web/config and the three workflow skeletons; writeCLAUDE.md/CONTEXT.mdwith §2's decisions table. - Port the spike (
counter.sv→ WASM → page) and get CI green. - Write
iss.py+test_iss.py(start with ADDI/ADD/LUI/JAL/BEQ/LW/SW and grow from there). - Add the riscv-tests submodule +
env/tinycpu; getrv32ui-p-addpassing on the ISS. - Write
docs/ARCHITECTURE.mdwith the pipeline timing diagram for synchronous IMEM/DMEM.