Guide for RTL design and SystemVerilog development on the RISC-V CPU. Use when asked about CPU architecture, instruction set, FSM states, memory interface, or SystemVerilog coding conventions.
Different instruction types require different numbers of cycles:
Instruction Class
Base Cycles
States
R-type (ADD, SUB, etc.)
4
FETCH → DECODE → EXECUTE → WRITEBACK
I-type Arithmetic
4
FETCH → DECODE → EXECUTE → WRITEBACK
Load (LW, LH, LB)
5
FETCH → DECODE → MEM_ADDR → MEM_READ → WRITEBACK
Store (SW, SH, SB)
4
FETCH → DECODE → MEM_ADDR → MEM_WRITE
Branch
3
FETCH → DECODE → BRANCH
Jump (JAL/JALR)
4
FETCH → DECODE → EXECUTE → WRITEBACK
Upper Immediate
4
FETCH → DECODE → EXECUTE → WRITEBACK
M-Extension (MUL/DIV)
4
FETCH → DECODE → EXECUTE → WRITEBACK
System (FENCE)
2
FETCH → DECODE
System (ECALL/EBREAK)
2
FETCH → DECODE → HALT
CSR Operations
4
FETCH → DECODE → CSR → WRITEBACK
Note: Memory latency adds additional cycles. For example, with 3-cycle memory latency, a load instruction takes 5 base cycles + 3 cycles in FETCH + 3 cycles in MEM_READ = 11 total cycles.
Memory Interface Signals
The multi-cycle design adds handshaking signals:
Instruction Memory:
imem_req (output): CPU requests instruction fetch
imem_ready (input): Memory has valid instruction data
Multi-cycle execution: Instructions take 3-5+ base cycles plus variable memory latency
FSM-based control: 12-state finite state machine
Variable-latency memory: Ready/valid handshaking on instruction and data memory interfaces
Exposed memory ports: Instruction and data memory are external (managed by testbench)
Register x0 hardwired to zero: Hardware enforcement (not just software convention)
Separate branch unit: Dedicated branch comparison logic (not ALU-based)
CSR support: Full Control and Status Register implementation (Zicsr extension)
FIFO-based debug: MMIO FIFO at 0x40000000 for host communication with packet protocol
Staging registers: Flip-flop based intermediate storage for multi-cycle operation (FPGA-safe, no latches)
Coding Conventions
Signal Naming
Use snake_case for signal names
Prefix with purpose: imem_, dmem_, alu_, etc.
Keep ports consistent with RISC-V naming: rs1, rs2, rd, funct3, etc.
Reset Conventions
Use synchronous resets only in project RTL modules.
Default to active-high reset ports as rst for internal RTL modules. The supported FPGA flows map these resets efficiently without extra inversion logic, so active-high remains the project default.
For sequential logic, use always_ff @(posedge clk) (or the local clock domain) and perform reset inside the block with if (rst).
Reserve active-low resets for special cases, usually external board or device signals that already arrive active-low. Convert those signals to the internal active-high convention as close to the boundary as practical.
When a datapath payload register has a separate valid, pending, or similar control bit, reset the control bit rather than the payload register itself. Write or refresh the payload whenever you capture new data, typically in the same branch where you set/assert the control bit, and downstream logic must ignore the payload whenever that control bit is low.
Avoiding resets on payload-only registers reduces reset fanout and routing congestion on FPGA hardware without changing functional behavior.
Timing and Frequency Priority
Maximizing achievable Fmax and timing margin is a repo priority. Prefer registered signals and shorter combinational cones to improve timing closure against the documented target constraint.
Default to multi-cycle staging when needed instead of forcing large arithmetic, mux, compare, or control cones into a single cycle for Fmax.
Prefer registering intermediate values and outputs at natural boundaries (register -> logic -> register) whenever that does not require major architectural changes.
Signals that cannot be registered without major architectural changes—especially ready/valid-style handshake returns such as *_ready—are exempt from this guidance.
When an unregistered path is kept for architectural reasons, document the exemption clearly so the timing trade-off is explicit.
default_nettype Guards (MANDATORY)
Every .sv file must begin with `default_nettype none and end with `default_nettype wire:
The opening `default_nettype none turns implicit net declarations into compile errors, catching undeclared signals before they silently become 1-bit wires.
The closing `default_nettype wire restores the default so the guard does not bleed into other files included after this one.
All project .sv files already carry these guards. Any new file added under rtl/ must include them.
Linting
# Lint SystemVerilog files before committing (RTL files are in subdirectories)
find rtl/common -name '*.sv' -exec verilator --lint-only --Wno-MULTITOP {} +
All SystemVerilog code should pass Verilator linting before being committed.
FPGA Synthesis Verification
# Verify RTL can be synthesized to FPGA (whenever SystemVerilog is modified)
(cd rtl/fpga && make)
Important: CI automatically runs FPGA synthesis verification on all SystemVerilog changes. The design must successfully synthesize to the default ECP5 target (ecp5_icepi_zero) using Yosys/nextpnr-ecp5.
The default open-source target keeps ENABLE_F_EXT=0; check current build reports for the latest resource/timing headroom
Debugging Hardware
CRITICAL RULE: When debugging hardware, NEVER rely heavily on abstract reasoning about what signals "should" be doing.
Correct Debugging Approach
Add $display() statements to observe actual signal values
Print state transitions to see FSM behavior
Observe timing with cycle-by-cycle output
Base hypotheses on concrete data from simulation
Verify assumptions with additional instrumentation
Example Debug Instrumentation
always_ff @(posedge clk) begin
if (state == S_FETCH) begin
$display("FETCH: pc=%h instr=%h imem_ready=%b", pc, imem_data, imem_ready);
end
if (state == S_EXECUTE) begin
$display("EXECUTE: alu_op=%h rs1_data=%h rs2_data=%h result=%h",
alu_op, rs1_data, rs2_data, alu_result);
end
end
What NOT to Do
❌ Assuming signal values without checking them
❌ Predicting FSM state transitions without observation
❌ Guessing timing relationships
❌ Reasoning through complex logic without concrete data
Key Principle: Treat hardware debugging like experimental science - observe first, then reason based on evidence.