CASE STUDY / 01 / COMPUTER ARCHITECTURE
The processor development archive.
A five-stage RV32I-subset processor with independent instruction/data ports and opt-in fault-stop diagnostics. The latest verification checks instruction-fetch bounds, older-instruction completion and speculative fault suppression at the 4 KiB modeled memory boundary.
RECRUITER BRIEF / WHAT THE EVIDENCE SUPPORTS
My engineering work
Built and iterated on the five-stage RTL, tagged I/D handshakes and fault-ordering behavior, with testbenches and review. AI-assisted development and review were part of this iteration; inspect the linked source rather than assuming unaided authorship of every line.
Inspect first
72/72 fetch-boundary scenarios across six timing setups, with archived output, independent assertions and a reproduction script. Current RTL and testbench are linked directly.
Verification boundary
Educational RV32I subset with modeled 4 KiB fetch bounds. Not full ISA compliance, architectural traps, formal proof, synthesis or FPGA performance.
How does an instruction become hardware behavior?
Studying processor architecture in a diagram is one thing. Translating instruction fields into data paths, control decisions and state transitions forces every assumption to become explicit. This project follows the journey from a base RISC-V instruction to the modules that process it.
The earlier integrated core learned to wait for delayed internal data memory. The new question is whether both instruction fetch and data memory can live outside the processor, each with independent request acceptance and response timing. The current external-port mode tests that boundary with separate I/D handshakes while retaining architectural program checks.
The latest milestone addresses an additional pipeline problem: what happens when an instruction is invalid, its taken target is misaligned, or it accesses an unsupported memory address? With FAULT_HALT=1, the core stops precisely at the faulting instruction, exposes a stable diagnosis, and waits for reset. It does not execute a RISC-V machine-mode trap.
The next challenge is a fetch just beyond the modeled instruction memory: does it truly fault, or can an older taken branch prove that fetch was speculative? The new implementation carries an invalid fetch as a squashable token to EX so the pipeline preserves instruction age rather than treating every attempted fetch as an immediate fault.
Five stages, one data path.
Fetch, decode, execute, memory and writeback form one in-order pipeline. In EXTERNAL_PORTS=1 mode, instruction and data memories are supplied by separate ports outside the core. Each channel allows one outstanding request and uses rising-edge valid && ready transfers; the two channels can overlap. The older self-contained mode remains for historical regressions.
Opt-in, architecturally ordered fault-stop
With FAULT_HALT=1, the EX stage recognizes illegal/unsupported encodings, misaligned taken branch/JAL/JALR targets, and misaligned or out-of-range loads/stores. Any older memory operation finishes before the younger fault is reported; older writeback can retire. The faulting instruction never enters memory, and wrong-path or younger instructions cannot modify architectural state. A pending external fetch may still be accepted and drained without execution.
Four outputs retain the first diagnosis until reset: fault_valid (sticky), fault_cause (0 control-target alignment; 1 modeled instruction-fetch range; 2 illegal instruction; 4/6 load/store misalignment; 5/7 modeled load/store range), fault_pc (faulting instruction or fetch PC), and fault_tval (illegal instruction bits, invalid fetch PC, bad target or effective address). The default FAULT_HALT=0 preserves prior behavior.
A bad fetch is not always an architectural fault
When the next fetch PC falls outside 0x00000000–0x00000fff, FAULT_HALT=1 creates one synthetic IF-to-EX fault token instead of reading an aliased internal word or issuing an invalid external I request. If an older taken BEQ, JAL or JALR at 0x00000ffc redirects in range, it flushes the wrong-path token. If the fetch is real, EX reports sticky cause=1 with fault_pc=fault_tval equal to the invalid fetch PC.
An aligned JAL/JALR to 0x00001000 is itself valid: the link register retires, then the *younger fetch* at that address faults. Earlier loads or stores drain first, even with independent I/D delays and held responses. Reset clears the sticky diagnosis. This checks only the fixed local teaching memory; external hardware bus errors and architectural instruction-access trap handling remain unsupported.
Fetch buffers a returned instruction until decode can accept it. An older data transaction holds younger pipeline stages until its response handshake; earlier writeback retires once, and redirects cannot overtake an unfinished store.
Match fetched instructions to their PC
Instruction requests carry an address and 8-bit tag; matching responses are buffered until IF/ID can advance. Redirects invalidate the old fetch epoch, drain obsolete replies, and request the new branch target without executing wrong-path instructions.
Wait for completion without double commits
EX/MEM holds the data address, write strobes, payload and destination until its tagged response handshakes. An older MEM/WB instruction retires once, and a held decode operand is refreshed if its producer writes back. DATA_RESPONSE_DELAY intentionally withholds d_rsp_ready to test stable responses.
Keep branches and stores in order
Younger redirects wait behind pending data accesses; stale instruction replies are discarded after a taken branch. The same little-endian byte-lane logic supports LB/LBU/LH/LHU/LW and SB/SH/SW, with no claim of out-of-order memory or MMIO support.
Two ports, independently stalled
The test adapter varies instruction and data latency independently and can hold either req_ready low. A response remains valid, with the same tag and payload, until the core raises rsp_ready. Fetch checks both the echoed PC and tag; data checks its tag. The adapter commits stores at the response handshake once. This is a bounded, single-outstanding educational protocol on one core clock, not AXI or a physical SRAM timing model.
Earlier latency-controlled internal memory
Before external I/D ports, the core used an internal behavioral memory adapter. That prior milestone tested base delays of 1, 2 or 4 cycles plus deterministic extra delays of up to three cycles; its trace remains below as historical evidence.
Implementation with receipts.
The newest instruction-fetch boundary implementation and independent tests come first. Earlier fault-stop, external I/D bus, internal latency-memory and integration sources remain linked below at their original verified revisions.
Instruction-fetch range and ordered fault token / new milestone
Earlier implementation milestonesPrecise fail-stop, external I/D ports, memory latency and initial pipeline · original revision links
Earlier precise fail-stop diagnostics
Independent instruction/data ports
Earlier memory-latency milestone
Earlier integrated and module evidence
wire [31:0] forward_a = ex_match1 ? ex_mem_alu_res :
wb_match1 ? rf_wd : id_ex_rd1;
wire [31:0] forward_b = ex_match2 ? ex_mem_alu_res :
wb_match2 ? rf_wd : id_ex_rd2;
Newer EX/MEM wins over MEM/WB. The hazard detector holds back dependent instructions when a load result is not yet available; this prevents forwarding unready memory data.
Fetch faults. In instruction order.
The independent fetch-boundary bench executes 12 distinct scenarios under six internal/external I/D timing configurations. Each run compares 32 registers and 1,024 modeled RAM words, yielding 72 scenario runs and 76,032 final-state comparisons—not 76,032 different instructions or tests. The checks verify that valid instructions finish, speculative fetch faults are squashed, and no invalid external instruction request is offered. The I/D adapter also exercises response backpressure.
72 scenarios · 76,032 architectural state comparisons
Across six configurations, 48 sticky cause-1 fetch faults and 24 valid or reset scenarios passed. The tests cover sequential crossing after 0xffc, older load/store completion, valid redirects at the final instruction word, JAL/JALR link writes before a younger fetch fault at 0x1000, and reset recovery. Four separate opt-out tests confirmed expected simulator rejections; four original integrated programs also passed their existing 4,224 historical state checks. These compatibility checks are not part of the 76,032 new comparisons.
One boundary. Three different correct outcomes.
Examples from the captured passing simulator log and the checking testbench ↗, not illustrative performance numbers.
0xffc redirects inwardInvalid speculative fetch is discarded; no sticky fault; expected marker 41 observed.0xffcSequential fetch at 0x1000 reports cause 1, with fault PC and tval both 0x1000.0xffc targets 0x1000The older link write retires; the younger invalid fetch produces cause 1 at 0x1000.Ordering under load: a separate SW→LW case must finish two older data transactions before the invalid fetch stops the core. The captured external-memory runs report D=2, cause=1 and pc=tval=0x1000. The exact test sequence ↗ and runner output ↗ let a reviewer check the causal claim; these are modeled, not physical bus faults.
Earlier illegal instruction and data fault-stop checks
The preceding opt-in fail-stop milestone established EX-stage illegal-instruction, misaligned control target, and invalid data-access diagnostics. Its 90 scenarios and 95,040 architectural comparisons remain separate from the new instruction-fetch boundary regression.
The fault-halt suite runs 12 invalid-instruction or access scenarios and three non-fault cases under each of six configurations: three internal-memory and three independently timed external I/D combinations. Every run checks 32 registers and 1,024 data-memory words, including whether younger instructions were suppressed, valid older work survived and the diagnosis remains stable until reset.
90 scenarios · 95,040 architectural state comparisons
Across the six timing configurations, 72 fault cases and 18 no-fault cases passed. Checks cover illegal encodings, misaligned or out-of-range memory accesses, misaligned taken control targets, wrong-path suppression, sticky fault outputs, and the absence of faulting data requests. Four additional expected simulator-fatal cases confirm FAULT_HALT=0 preserves the earlier behavior. These checks do not implement or verify RISC-V privilege, machine-mode traps or recovery.
Earlier external I/D bus verification
The preceding port-level regression separately passed 42 directed runs and 12 interpreter-reference runs, totaling 57,024 state comparisons, plus two expected corrupted-tag rejections. Its independently timed instruction/data traces and source remain linked below. None of those checks are counted as part of the new 95,040 fault-stop comparisons.
The external regression uses a separate adapter with independently varied instruction and data timing. Six configurations run seven self-checking programs each: mixed load/store hazards, branch redirects and wrong-path suppression, backward branches with delayed fetch, signed subword loads and masked stores, reset cancellation of an outstanding store, a 300-redirect loop that wraps fetch tags, and a 301-instruction straight-line test. Every completed case checks all 32 registers and 1,024 modeled memory words against explicit expected state.
42 scenario runs · 44,352 state comparisons
Request and response counters catch duplicate or missing data transactions and repeated stores. The testbench also asserts that instruction and data request/response payloads remain stable while ready is low. Timed configurations exercise independent ready stalls, fetch redirects and held data responses. Two negative tests deliberately corrupt I/D response tags and must terminate with identity errors.
12 program runs · 12,672 additional state comparisons
Three seeded instruction programs are independently decoded and interpreted by Python to calculate architectural register and byte-addressed memory results. Each executes under four independent instruction/data timing combinations, then checks every register and all 1,024 modeled memory words. These are self-checking simulation results, not formal ISA conformance or unique instruction-count measurements.
bash scripts/run_external_regression.sh /tmp/rv32i-external
The runner prints per-case instruction/data request and response counts, store counts, ready-stall cycles and held-response cycles, and captures the complete raw VCD. The site provides a compact, unmodified signal-value excerpt from it for quick inspection. These are finite simulation scenarios, not a formal proof of all protocol interleavings.
Earlier internal memory-latency verification
The previous internal-memory adapter was checked across six delay configurations and against a separate Python sequential interpreter. Those 50,688 architectural comparisons and four expected-fault tests are earlier, distinct evidence; they are not included in the external-port totals above.
36 directed runs · 38,016 state comparisons
Cases cover back-to-back loads and stores, load-use and store-data dependencies, branches and wrong-path suppression, reset cancellation, and little-endian LB/LBU/LH/LHU/LW and SB/SH/SW. Checks fail if data, control flow, memory contents or transaction counts are wrong.
12 interpreter-based runs · 12,672 comparisons
Three deterministic seeds generate programs and expected results with a separate Python instruction interpreter, then run each program at four memory configurations. A byte-addressed reference memory and all 32 expected registers are compared with RTL final state. Four additional expected-failure tests confirm the simulator rejects unsupported addresses and alignments; they do not establish an architectural trap handler.
bash scripts/run_memory_regression.sh /tmp/rv32i-memory-regression
The runner writes directed logs, interpreter expectations, four expected rejection logs and the raw handshake VCD. These are simulation comparisons, not proof of complete RV32I compliance, formal exhaustiveness or physical performance.
Earlier one-cycle integrated milestone
Before the delayed-memory design, the first integrated regression passed four programs with 4,224 state checks, plus a separate jump/control-flow test. Its captured trace is retained as historical evidence of how the processor developed.
4 programs, 4,224 state checks passed
The run observed 7 load-use stalls and 11 taken redirects across arithmetic, load/store, branch and load-to-branch scenarios. An additional JAL/JALR control-flow program and all three standalone module benches passed. These are simulation observations, not silicon performance or proof of exhaustive correctness.
bash scripts/run_regression.sh /tmp/rv32i-evidence
The runner executes standalone and integrated testbenches, captures simulator logs, and writes the raw VCD into the selected output directory.
The earlier module-level foundation
Standalone self-checking tests for the ALU, register file and control unit still pass. The original captured ALU waveform remains below as a separate, narrower piece of evidence.
ALU, register file and control unit passed
These three benches test their respective modules in isolation, with direct assertions. They complement the new integrated instruction programs rather than substituting for them.
Arithmetic and edge cases
The bench checks arithmetic, logical operations, shifts, signed/unsigned comparisons and zero-flag behavior.
Read the ALU checks ↗Architectural state
Checks cover reset behavior, read/write paths, disabled writes and the special behavior of x0.
Inspect the register testbench ↗Instruction decoding
Directed cases exercise instruction decode and control outputs, including an illegal/default opcode path.
Inspect control tests ↗Verification boundary. FAULT_HALT=1 checks only the modeled local 4 KiB instruction/data ranges and reports sticky fail-stop diagnostics, including modeled fetch cause 1. It does not save machine-mode CSR state, jump to mtvec, execute a trap handler or MRET, or resume without reset. There is no physical/external bus-error response, memory protection, MMIO, AXI/AHB, interrupts, privilege, cache coherence, full ISA compliance, formal proof, synthesis or FPGA evidence. FAULT_HALT=0 retains prior legacy simulation behavior, including $fatal for invalid accepted data requests. External-memory and core reset must still cancel outstanding transactions together.
Development provenance. AI-assisted development and review were used in this iteration. The listed results describe actual, reproducible simulator runs, not a claim that every RTL and test line was independently handwritten. The linked source and verification procedures are provided for direct inspection.
The interesting problems aren't finished.
The objective is to understand processor design deeply enough to explain the choices, show the RTL, and demonstrate behavior with evidence.
Discuss the verification work