A RISC-V core, plus an instruction of my own
A single-cycle RV32I core in Verilog, with a custom multiply-accumulate added in the custom-0 opcode space. Dot products are most of inference math, and MACC does each step in one instruction instead of two.
- ISA
- RV32I plus MACC: rd = rd + rs1 × rs2 in one cycle
- Checks
- 8 self-checking testbenches and a golden cross-check, all passing
- Reference
- A Python ISA model written from the spec, not from the RTL
The hard part
A processor can be nearly right for a long time. A core with a broken sign-extension path still runs most programs, and one that mis-decodes a rare immediate format passes every test that never uses it. So every module has its own self-checking testbench, the full core runs six hand-assembled programs, and MACC results are cross-checked against an independent Python model of the ISA.
MACC also needed a third register read. rd is both the accumulator and the destination, and a standard register file only has two read ports.
What testing caught
The bug wasn't in the RTL. My assembler encoded the halt pseudo-op, jal x0, 0, as an absolute address instead of a relative offset, so halt jumped back to address 0 and reran the program. Every existing test passed by luck, because a deterministic program recomputes the same values. The golden model expected a true self-loop, never saw one, and flagged it. That's the case for a second implementation in one example.
At a glance
- ISA
- RV32I without FENCE, ECALL or CSRs, plus custom-0 MACC
- Integration
- 6 hand-assembled programs, 7 core-level checks
- Toolchain
- Icarus Verilog, Python
- Before this
- A 32-bit 5-stage pipelined MIPS in Verilog with forwarding and hazard detection: RAW hazards resolved in under 3 cycles, 100% instruction coverage in simulation