A 5-Stage Pipelined RISC-V Processor in Verilog: Hazards and Forwarding

Sunhong··Updated Sep 15, 2026·7 min read·340 views·Translated from Korean
ProjectverilogC

-Stage Pipelined Processor (RISC-V)

The figure above is a diagram of the 5-stage pipeline that I drew myself. (Please credit the source if you use it :))

Project overview

In this project I designed and implemented, in Verilog, a 5-stage pipelined processor that supports the entire RISC-V RV32I instruction set. Rather than a simple single-cycle processor, it applies the pipelining used in real high-performance CPUs, and it resolves the data hazards and control hazards that pipelining introduces with a hazard detection unit and a forwarding unit.

Simulation was done with iverilog + GTKWave (Linux), and I converted the test programs into machine code by hand and loaded them into the instruction memory.

The RV32I instruction set

RV32I is RISC-V's 32-bit base integer instruction set. It consists of 37 instructions, grouped into the following categories.

  • R-Type: register-register operations (ADD, SUB, AND, OR, XOR, SLL, SRL, SRA, SLT, SLTU)
  • I-Type: immediate operations and loads (ADDI, ANDI, ORI, LW, LB, LH, JALR)
  • S-Type: stores (SW, SB, SH)
  • B-Type: conditional branches (BEQ, BNE, BLT, BGE, BLTU, BGEU)
  • U-Type: upper immediate loads (LUI, AUIPC)
  • J-Type: jump (JAL)

Every instruction is a fixed 32 bits long, and the operation is determined by the combination of the opcode (7 bits), funct3 (3 bits), and funct7 (7 bits) fields. Thanks to the simplicity of the RISC-V specification, the hardware decoder is relatively simple.

The 5-stage pipeline

A pipeline splits instruction execution into five stages so that several instructions are processed at once:

IF (Instruction Fetch)

Fetches the instruction from the instruction memory at the address held in the PC (program counter). Each cycle the PC either increments by 4 or, on a branch, is updated to the branch target.

ID (Instruction Decode)

Decodes the instruction. The opcode determines the instruction type, and the rs1/rs2 register values are read from the register file. The immediate is generated by sign-extending according to the instruction type. The control unit also generates the control signals each stage needs (RegWrite, MemRead, MemWrite, ALUOp, etc.).

EX (Execute)

The ALU performs the actual operation. It handles everything from basic operations such as ADD/SUB/AND/OR/XOR to comparison and shift operations such as SLT (set less than) and SLL (shift left logical). For branch instructions, the condition is evaluated and the branch target computed here.

MEM (Memory Access)

Only load and store instructions use this stage. It sends an address to the data memory and performs the read or write. A load passes the value read from memory to the next stage, while a store writes the rs2 value to memory and is done.

WB (Write Back)

Writes the ALU result or the value read from memory into the rd register of the register file. Only after this stage finishes can other instructions safely read that register.

A single-cycle processor needs the time of all five stages to process one instruction, but with pipelining one instruction completes every cycle, so the theoretical throughput is 5x. In practice hazards keep it from reaching that, but it is still a large performance gain.

Hazard detection unit

The biggest problem in pipelining is hazards. Data hazards are the classic example:

ADD x1, x2, x3   # x1 = x2 + x3
ADD x4, x1, x5   # x4 = x1 + x5 (tries to read x1, but it hasn't been written back yet)

The second instruction can only read the value written to x1 once the first instruction reaches the WB stage. If it reaches EX before then, it reads the stale value.

A load-use hazard is a case that even forwarding cannot resolve:

LW x1, 0(x2)     # x1 = Mem[x2] (value is known in the MEM stage)
ADD x4, x1, x5   # needs x1 (in the EX stage)

In this case, the hazard detection unit checks whether the instruction after a load uses the load's rd as rs1/rs2 and, if so, inserts a one-cycle stall. Specifically, it holds the IF/ID register and the PC at their current values and inserts a NOP (bubble) into the ID/EX register.

Forwarding unit

Most data hazards can be resolved by forwarding, without a stall. Forwarding takes the result of an earlier instruction from where it becomes available (the end of the EX stage or the end of the MEM stage), before it is written to the register file, and sends it directly to the ALU input of the next instruction.

The forwarding unit checks the following conditions to generate the ForwardA and ForwardB signals:

  • EX → EX forwarding: RegWrite in the EX/MEM register is 1, EX/MEM.rd != 0, and EX/MEM.rd == ID/EX.rs1 (or rs2)
  • MEM → EX forwarding: RegWrite in the MEM/WB register is 1, MEM/WB.rd != 0, the condition above does not apply, and MEM/WB.rd == ID/EX.rs1 (or rs2)

Based on ForwardA/B, the ALU input MUX selects the register value, the EX/MEM result, or the MEM/WB result.

Control hazards

Whether a branch is taken is decided at the end of the EX stage. By then, two instructions have already entered the IF/ID stages, and if the branch is taken, these two are on the wrong path and must be flushed.

In the implementation, when a branch resolves, all control signals in the IF/ID and ID/EX registers are set to 0 so the instructions become NOPs. This costs a 2-cycle penalty per branch. Going further would require branch prediction, but this project simply uses not-taken prediction plus flush.

Waveform analysis

Checking the data in the registers and memory shows that the processor executes the RISC-V instructions correctly. The instructions were converted to machine code and stored in the instruction memory.

Waveform analysis

Waveform analysis (2)

Waveform analysis (3)

Waveform analysis (4)

Waveform analysis (5)

Waveform analysis (6)

In each waveform I checked the following: that the PC increments correctly every cycle, that instructions pass through the five stages correctly, that the correct value reaches the ALU when forwarding occurs, and that stalls and flushes happen properly when a hazard occurs.

Takeaways

  • The complexity of pipelining: The moment you go from single-cycle to pipelined, most of the design effort goes into handling hazards. Performance gains clearly come at a cost.
  • The importance of modularity: Building each stage as an independent module and connecting them with pipeline registers made debugging much easier.
  • The strength of the RISC-V specification: The instruction formats are consistent, so the decoder is compact. It is in a different league from a variable-length CISC like x86.
  • Simulation tools: The iverilog + GTKWave combination is free yet powerful enough. You can learn plenty without the commercial Vivado Simulator.

This project was a great chance to go from start to finish through the process of turning computer architecture theory into real hardware. Watching the textbook five-stage Fetch-Decode-Execute-Memory-Writeback diagram come alive in actual Verilog code and waveforms turned abstract concepts into concrete understanding.

Similar posts

Hardware Project

Zynq-7000 FPGA AI Accelerator: CNN/FC Cores and AXI CDMA Data Movement

I built an AI accelerator on a Zynq-7000 FPGA by designing CNN and fully connected compute cores and an AXI CDMA data mover. This post covers the Vivado and Vitis work: the design flow, the memory map, verifying the cores and movers, and building a 5-stage AI model.

Hardware Project

Cortex-M0 SoC Design: A Multifunction Robot Control System on AHB-Lite

Designed an SoC on a Basys3 board that combines a Cortex-M0 with AHB-Lite peripherals. Covers the HW and SW of a multifunction system that controls a small robot over UART and runs an up counter, a stopwatch, a random number generator/multiplier, and an adder/subtractor, along with the results.

Hardware Project

Designing a Verilog Slot Machine: LFSR Random Numbers and a Timer Seed (Basys3)

I designed a slot machine for the Basys3 board in Verilog. A timer seeds an LFSR to produce three BCD random numbers, and the output is 2x, 5x, or 10x the input depending on how many digits match. Covers the block diagram, sub-block definitions, simulation waveforms, and discussion.

Hardware Project

Wallace Tree Multiplier in Verilog: 8-Bit Parallel Multiply with Adders

I designed an 8-bit parallel multiplier in Verilog using the Wallace tree structure, which reduces partial sums stage by stage with half adders and full adders. Covers the computation that produces the 16-bit product, the design flow, the testbench, and the full code.