All work
2026/ Computer architecture · Verilog/ EECE 321 · 15-day build

Dual-Issue RISC-V CPU

Taking a working 5-stage pipelined RISC-V processor and extending it to issue two instructions per cycle — in Verilog, strictly in-order. The interesting part isn't the second ALU; it's the six rules that decide when issuing two instructions together would silently produce a wrong answer.

Interactive demo

The real test program, cycle by cycle

iverilog — cpu_debug.v / cpu_dual_issue.v

Cycle 0 — ready.

// hazard and issue events appear here

At a glance

Language
Verilog-2005
Extension
727 lines, 2-wide in-order
Verification
7 targeted testbench cases
Course
EECE 321 Computer Organization

Two lanes, one gatekeeper

Where the second instruction can and cannot go

Fetch ×2 PC, PC+4 Issue checker 6 blocking rules Lane A always issues Lane B or NOP bubble EX / MEM 2 ALUs WB ×2 A wins RAW · memory · WB · branch ×2 · load-use
Lane A always advances. Lane B is nullified to a NOP whenever any rule fires.

Code

The gatekeeper, and the forwarding case it creates

cpu_dual_issue.v every reason to refuse the second slot

wire dual_issue_ok = !b_reads_a_rd   &&   // B reads what A is about to write
                     !mem_structural &&   // both want the memory port
                     !wb_conflict    &&   // both write the same rd
                     !branch_in_a    &&   // branch resolves in EX — stay conservative
                     !branch_in_b    &&
                     !pipeline_hazard;     // load-use anywhere in the pair

Six independent reasons, all of which must be false. Any one of them and lane B becomes a bubble.

cpu_dual_issue.v a forwarding path the baseline never needed

// Encoding: 2'b11 = forward from A's EX result (intra-pair, A -> B)
//           2'b10 = forward from EX/MEM (one cycle back)
//           2'b01 = forward from MEM/WB  (two cycles back)
always @(*) begin
    forward_a_b = 2'b00;
    // Highest priority: A's result in the SAME EX cycle
    if (id_ex_regwrite_a && (id_ex_rd_a != 5'd0) && (id_ex_rd_a == id_ex_rs1_b))
        forward_a_b = 2'b11;
    else if (ex_mem_regwrite_a && (ex_mem_rd_a == id_ex_rs1_b))
        forward_a_b = 2'b10;
    else if (mem_wb_regwrite_a && (mem_wb_rd_a == id_ex_rs1_b))
        forward_a_b = 2'b01;
end

Two instructions in EX at once creates a dependency distance of zero — a case a single-issue pipeline cannot produce.

Why it never reaches 2×

The honest result

A 2-wide machine has a theoretical CPI of 0.5. This one does not get close, and the report says why rather than hiding it: branches force single issue every time, load-use hazards stall both lanes at once, adjacent instructions are very often RAW-dependent, and a load/store pair collides on the single memory port.

That gap between the theoretical number and the measured one is the lesson of the project — ILP is limited by the program, not just the hardware.

Inside the repo

Structure

  • cpu_debug.vprovided 5-stage baseline — 460 lines
  • tb_cpu_debug.vprovided baseline testbench
  • cpu_dual_issue.vthe extension — 727 lines
  • tb_cpu_dual_issue.v7 targeted test cases — 281 lines
  • report.mdtechnical report, 412 lines
  • README.mdbuild + GTKWave signal guide

Skills, in context

Where each one actually showed up

Verilog / RTL design Duplicated decode, a second ALU, dual-write register file, and the issue checker above.
Computer architecture Hazard classes — RAW, structural, control — and why each one blocks the second slot.
Verification Seven testbench cases written to force each issue rule to fire, plus GTKWave waveform inspection.
Technical writing A 412-line report justifying every design decision — the deliverable was the reasoning, not just the RTL.
Next project 3ayn