0% found this document useful (0 votes)
17 views45 pages

Pipelined RISC-V Project

This document details the design and implementation of a RISC-V RV32I processor, progressing from a single-cycle architecture to a pipelined architecture to enhance performance. The project utilized FPGA tools Quartus and Vivado for implementation and verification, achieving stable operation at 100 MHz with potential for higher frequencies. It emphasizes the practical application of open-source architectures in hardware design, demonstrating successful functional verification and performance improvements through extensive testing.

Uploaded by

4592shivsharma
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
17 views45 pages

Pipelined RISC-V Project

This document details the design and implementation of a RISC-V RV32I processor, progressing from a single-cycle architecture to a pipelined architecture to enhance performance. The project utilized FPGA tools Quartus and Vivado for implementation and verification, achieving stable operation at 100 MHz with potential for higher frequencies. It emphasizes the practical application of open-source architectures in hardware design, demonstrating successful functional verification and performance improvements through extensive testing.

Uploaded by

4592shivsharma
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

VLSI DESIGN AND

IMPLEMENTATION FLOW
DIGITAL IC DESIGN
PIPELINED RISC-V
PROCESSOR

Prepared by:
Omar Khaled Mohammed

JUL 4, 2025

i
ABSTRACT
This project presents the design and implementation of a RISC-V RV32I processor,
developed in two stages: a single-cycle architecture and an enhanced pipelined
architecture. The single-cycle implementation served as the foundation for
functional verification and performance evaluation, providing a clear baseline for
further improvements. Once validated, the design was extended into a pipelined
structure to increase instruction throughput and better utilize hardware resources.
The processor was implemented using two industry-standard FPGA development
tools, Quartus and Vivado, to ensure compatibility and verify portability across
different platforms. The final design targeted the Xilinx FPGA device
XC7A200TFFG1156-3, chosen for its high performance and resource capacity.
Functional verification was carried out using detailed simulation test benches,
ensuring the correct execution of all RV32I instructions and confirming the integrity
of both the single-cycle and pipelined designs.
Extensive simulations and synthesis run demonstrated that the pipelined
implementation not only met all functional requirements but also delivered
significant performance improvements compared to the single-cycle version. The
design achieved stable timing closure for a 100 MHz operating frequency and
demonstrated potential to operate at 125 MHz, although 125 MHz was not a primary
target. The timing and resource utilization reports confirmed that the design
remained within acceptable limits for the chosen FPGA.
Overall, this work validates the successful implementation of an RV32I processor
from initial single-cycle design to optimized pipelined architecture, backed by
thorough functional verification and timing analysis. By demonstrating reliable
operation on high-performance FPGA hardware, the project highlights a complete
design cycle, from architectural concept through simulation, synthesis, and
implementation, while ensuring the processor meets both functional correctness and
practical performance goals.

ii
TABLE OF CONTENTS
ABSTRACT ................................................................................................................................................. ii
1.0 INTRODUCTION................................................................................................................................. 1
2.0 SINGLE CYCLE PROCESSOR ......................................................................................................... 2
3.0 FETCH BLOCK IMPLEMENTATION ............................................................................................. 2
3.1 Verilog Code ........................................................................................................................3
3.2 Fetch Block Elaboration Using Quartus ...................................................................................4
3.3 Fetch Block Elaboration Using Vivado ....................................................................................5
4.0 DECODE BLOCK IMPLEMENTATION.......................................................................................... 5
4.1 Verilog Code ........................................................................................................................6
4.2 Decode Block Elaboration Using Quartus .............................................................................. 10
4.3 Decode Block Elaboration Using Vivado ............................................................................... 11
5.0 EXECUTE BLOCK IMPLEMENTATION ..................................................................................... 11
5.1 Verilog Code ...................................................................................................................... 11
5.2 Execute Block Elaboration Using Quartus ............................................................................. 13
5.3 Execute Block Elaboration Using Vivado .............................................................................. 14
6.0 MEMORY BLOCK IMPLEMENTATION ...................................................................................... 14
6.1 Verilog Code ...................................................................................................................... 14
6.2 Memory Block Elaboration Using Quartus ............................................................................ 15
6.3 Memory Block Elaboration Using Vivado.............................................................................. 16
7.0 DESIGN SIMULATION .................................................................................................................... 16
7.1 Top Module ....................................................................................................................... 16
7.2 Test Bench Code................................................................................................................. 17
7.3 Top Module Schematic ........................................................................................................ 18
7.4 Program Used for Testing .................................................................................................... 18
7.5 Simulation ......................................................................................................................... 19
8.0 DESIGN IMPLEMENTATION ......................................................................................................... 20
8.1 Design Elaboration ............................................................................................................. 21
8.2 Design Synthesis ................................................................................................................ 21
8.3 Design Implementation ....................................................................................................... 23
9.0 PIPELINED PROCESSOR ............................................................................................................... 24
9.1 Verilog Code for Extra Hardware .......................................................................................... 26
9.2 Top Module ....................................................................................................................... 30

iii
10.0 PIPELINED PROCESSOR SIMULATION................................................................................... 32
10.1 Test Bench Code ............................................................................................................... 32
10.2 Simulation ....................................................................................................................... 33
11.0 PIPELINED PROCESSOR IMPLEMENTATION ....................................................................... 35
11.1 Design Elaboration ............................................................................................................ 35
11.2 Design Synthesis............................................................................................................... 36
11.3 Design Implementation ...................................................................................................... 37
12.0 CONCLUSION ................................................................................................................................. 39
REFERENCES .......................................................................................................................................... 40

iv
1.0 INTRODUCTION
The RISC-V RV32I architecture is a 32-bit integer instruction set that has quickly
become popular in both academic research and commercial use. Its open-source
design, modular structure, and flexibility make it a strong choice compared to
proprietary ISAs. Because anyone can implement and customize it freely, RISC-V
is widely adopted across various fields, from small embedded devices to more
powerful computing systems. Its straightforward design and growing industry
support have made RV32I a solid foundation for both learning and real-world
applications.
This report walks through the step-by-step process of building an RV32I processor,
starting with a simple single-cycle design. The work covers creating the data path,
adding control logic, and developing a detailed testbench to verify that the processor
executes all instructions correctly. Through simulation, the single-cycle design is
fully tested to ensure it meets the RISC-V specifications before moving forward.
Next, the design is upgraded to a pipelined processor, which divides instruction
execution into stages which are fetch, decode, execute, memory access, and write-
back to improve performance. The pipelined version undergoes thorough testing and
timing analysis to ensure it runs efficiently. Implementation on an FPGA shows that
the design reliably operates at 100 MHz, with potential to run even faster, although
higher speeds were beyond the initial project goals.
Beyond just building the processor, the report explores key engineering challenges
faced when turning theoretical architecture into a working hardware design. It
discusses trade-offs in design choices, methods for handling hazards in pipelining,
and strategies to meet timing requirements during FPGA synthesis. This approach
underlines the importance of careful verification and iterative improvement in
creating robust, high-performance hardware.
In conclusion, this project highlights the practical benefits of open-source
architectures like RISC-V in modern hardware design. It demonstrates how
academic projects can reach commercial-quality performance and reliability,
reinforcing RISC-V’s position as a strong candidate for future industry standards.
2.0 SINGLE CYCLE PROCESSOR
The single-cycle processor will be implemented following the provided block
diagram, where each instruction is executed entirely within one clock cycle. The
design integrates the main functional units, which are Instruction Memory, Program
Counter, Register File, ALU, Data Memory, and control unit. All components
operate in parallel during each cycle, allowing the fetch, decode, execute, memory
access, and write-back stages to occur sequentially within the same clock period.
This approach simplifies the control structure and ensures predictable execution
timing, making it ideal for demonstrating fundamental processor operations.

3.0 FETCH BLOCK IMPLEMENTATION


This section presents the RTL code for the Fetch block, which includes the Program
Counter (PC), Instruction Memory, and PC Adder, forming the essential components
responsible for retrieving instructions and updating the PC for subsequent execution.

2
3.1 Verilog Code
module instructionMemory #(
parameter MEM_WIDTH = 32,
parameter MEM_DEPTH = 128
) (
input [31:0] PC,
output reg [MEM_WIDTH - 1 : 0] instruction
);
reg [MEM_WIDTH - 1 : 0] instructionMem [0 : MEM_DEPTH - 1];

initial begin
instructionMem[0] = 32'h00500113; instructionMem[4] =
32'h00C00193; instructionMem[8] = 32'hFF718393;
instructionMem[12] = 32'h0023E233; instructionMem[16] =
32'h0041F2B3; instructionMem[20] = 32'h004282B3;
instructionMem[24] = 32'h02728863; instructionMem[28] =
32'h0041A233; instructionMem[32] = 32'h00020463;
instructionMem[36] = 32'h00000293; instructionMem[40] =
32'h0023A233; instructionMem[44] = 32'h005203B3;
instructionMem[48] = 32'h402383B3; instructionMem[52] =
32'h0471AA23; instructionMem[56] = 32'h06002103;
instructionMem[60] = 32'h005104B3; instructionMem[64] =
32'h008001EF; instructionMem[68] = 32'h00100113;
instructionMem[72] = 32'h00910133; instructionMem[76] =
32'h0221A023; instructionMem[80] = 32'h00210063;
end

always @(*) begin


instruction = instructionMem[PC];
end
endmodule

module programCounter (
input clk,
input [31:0] PCin,
output reg [31:0] PCout
);
initial PCout = 32'b0;
always @(posedge clk) begin
PCout <= PCin;
end
endmodule

3
module adder (
input [31:0] A1, A2,
output reg [31:0] O1
);
always @(*) begin
O1 = A1 + A2;
end
endmodule

module fetchBlock (
input clk,
input [31:0] PC_next,
output [31:0] instruction, PC_plus4, PC
);
parameter MEM_WIDTH = 32;
parameter MEM_DEPTH = 128;

programCounter instance1 (.clk(clk), .PCin(PC_next), .PCout(PC));


instructionMemory #(.MEM_WIDTH(MEM_WIDTH), .MEM_DEPTH(MEM_DEPTH))
instance2 (.PC(PC), .instruction(instruction));
adder instance3 (.A1(PC), .A2(32'd4), .O1(PC_plus4));

endmodule

3.2 Fetch Block Elaboration Using Quartus

4
3.3 Fetch Block Elaboration Using Vivado

4.0 DECODE BLOCK IMPLEMENTATION


This section presents the RTL code and elaboration for the Decode stage, which
includes the Control Unit, Register File, Immediate Extension unit, and an Adder for
calculating the PC target address. In the RV32I architecture, the Immediate
Extension unit generates different immediate formats for load, store, branch, and
JAL instructions, each requiring distinct control signals. A detailed table illustrating
the control signals and the immediate types and their corresponding control signals
is provided below.

ImmSrc Immediate Ext Type Description

00 {{20{Instr[31]}}, Instr[31:20]} I 12-bit signed immediate

01 {{20{Instr[31]}}, Instr[31:25], S 12-bit signed immediate


Instr[11:7]}

10 {{20{Instr[31]}}, Instr[7], B 13-bit signed immediate


Instr[30:25], Instr[11:8], 1’b0}

11 {{12{Instr[31]}}, Instr[19:12], J 21-bit signed immediate


Instr[20], Instr[30:21], 1’b0}

5
4.1 Verilog Code
module regFile #(
parameter width = 32,
parameter depth = 32
) (
input writeEn, clk,
input [4:0] rs1, rs2, rd,
input [width - 1:0] writeData,
output reg [width - 1:0] readData1, readData2
);
reg [width - 1:0] registers [0:depth - 1];

initial begin
registers[0] = 32'd0; registers[1] = 32'd1; registers[2] = 32'd2;
registers[3] = 32'd3;
registers[4] = 32'd4; registers[5] = 32'd5; registers[6] = 32'd6;
registers[7] = 32'd7;
registers[8] = 32'd8; registers[9] = 32'd9; registers[10] =
32'd10; registers[11] = 32'd11;
registers[12] = 32'd12; registers[13] = 32'd13; registers[14] =
32'd14; registers[15] = 32'd15;
registers[16] = 32'd16; registers[17] = 32'd17; registers[18] =
32'd18; registers[19] = 32'd19;
registers[20] = 32'd20; registers[21] = 32'd21; registers[22] =
32'd22; registers[23] = 32'd23;
registers[24] = 32'd24; registers[25] = 32'd25; registers[26] =
32'd26; registers[27] = 32'd27;
registers[28] = 32'd28; registers[29] = 32'd29; registers[30] =
32'd30; registers[31] = 32'd31;
end
always @(*) begin
readData1 = registers[rs1];
readData2 = registers[rs2];
end
always @(posedge clk) begin
if (writeEn && rd) begin
registers[rd] <= writeData;
end else begin
registers[rd] <= registers[rd];
end
end
endmodule
module signExtend #(
6
parameter WIDTH = 32
) (
input [WIDTH - 8:0] imm,
input [1:0] immSrc,
output reg [WIDTH - 1:0] immExt
);
always @(*) begin
case (immSrc)
2'b00: immExt = {{20{imm[WIDTH - 8]}}, imm[WIDTH - 8: WIDTH -
19]};
2'b01: immExt = {{20{imm[WIDTH - 8]}}, imm[WIDTH - 8: WIDTH -
14], imm[WIDTH - 28:0]};
2'b10: immExt = {{20{imm[WIDTH - 8]}}, imm[0], imm[WIDTH - 9:
WIDTH - 14], imm[WIDTH - 28:1], 1'b0};
2'b11: immExt = {{12{imm[WIDTH - 8]}}, imm[WIDTH - 20: WIDTH -
27], imm[WIDTH - 19], imm[WIDTH - 9: WIDTH - 18], 1'b0};

default: immExt = 0;
endcase
end
endmodule

module mainDecoder (
input [6:0] OpCode,
output reg branch, jump, memWrite, ALUSrc, regWrite,
output reg [1:0] resultSrc, immSrc, ALUOp
);
always @(*) begin
case (OpCode)
// Load Control Signals
7'b0000011: begin
regWrite = 1; immSrc = 2'b00; ALUSrc = 1; memWrite = 0;
resultSrc = 2'b01;
branch = 0; ALUOp = 2'b00; jump = 0;
end
// Store Control Signals
7'b0100011: begin
regWrite = 0; immSrc = 2'b01; ALUSrc = 1; memWrite = 1;
resultSrc = 2'b00;
branch = 0; ALUOp = 2'b00; jump = 0;
end
// R-Type Control Signals

7
7'b0110011: begin
regWrite = 1; immSrc = 2'b00; ALUSrc = 0; memWrite = 0;
resultSrc = 2'b00;
branch = 0; ALUOp = 2'b10; jump = 0;
end
// Branch Control Signals
7'b1100011: begin
regWrite = 0; immSrc = 2'b10; ALUSrc = 0; memWrite = 0;
resultSrc = 2'b00;
branch = 1; ALUOp = 2'b01; jump = 0;
end
// I-Type ALU Control Signals
7'b0010011: begin
regWrite = 1; immSrc = 2'b00; ALUSrc = 1; memWrite = 0;
resultSrc = 2'b00;
branch = 0; ALUOp = 2'b10; jump = 0;
end
// Jump Control Signals
7'b1101111: begin
regWrite = 1; immSrc = 2'b11; ALUSrc = 0; memWrite = 0;
resultSrc = 2'b10;
branch = 0; ALUOp = 2'b00; jump = 1;
end
default: begin
regWrite = 0; immSrc = 2'b00; ALUSrc = 0; memWrite = 0;
resultSrc = 2'b00;
branch = 0; ALUOp = 2'b00; jump = 0;
end
endcase
end
endmodule

module ALUDecoder (
input [1:0] ALUOp, funct7,
input [2:0] funct3,
output reg [2:0] ALUControl
);
always @(*) begin
case (ALUOp)
2'b00: ALUControl = 3'b000;
2'b01: ALUControl = 3'b001;
2'b10: begin

8
case (funct3)
3'b000: begin
if (funct7 == 3) begin
ALUControl = 3'b001;
end else begin
ALUControl = 3'b000;
end
end
3'b010: ALUControl = 3'b101;
3'b110: ALUControl = 3'b011;
3'b111: ALUControl = 3'b010;
default: ALUControl = 3'b000;
endcase
end
default: ALUControl = 3'b000;
endcase
end
endmodule

module controlUnit (
input zero, funct7,
input [6:0] OpCode,
input [2:0] funct3,
output PCSrc, memWrite, ALUSrc, regWrite,
output [1:0] resultSrc, immSrc,
output [2:0] ALUControl
);
wire branch, jump;
wire [1:0] ALUOp;

mainDecoder instance1 (.OpCode(OpCode), .branch(branch), .jump(jump),


.memWrite(memWrite), .ALUSrc(ALUSrc), .regWrite(regWrite),
.resultSrc(resultSrc), .immSrc(immSrc), .ALUOp(ALUOp));
ALUDecoder instance2 (.ALUOp(ALUOp), .funct7({OpCode[5], funct7}),
.funct3(funct3), .ALUControl(ALUControl));

assign PCSrc = (zero & branch) | jump;


endmodule

9
module decodeBlock (
input [31:0] instruction, writeData, PC,
input zero, clk,
output PCSrc, memWrite, ALUSrc,
output [1:0] resultSrc,
output [2:0] ALUControl,
output [31:0] readData1, readData2, immExt, PC_target
);
parameter width = 32;
parameter depth = 32;
wire [1:0] immSrc;
wire regWrite;

regFile #(.width(width), .depth(depth)) instance1 (.writeEn(regWrite),


.clk(clk), .rs1(instruction[19:15]), .rs2(instruction[24:20]),
.rd(instruction[11:7]), .writeData(writeData), .readData1(readData1),
.readData2(readData2));
signExtend #(.WIDTH(width)) instance2 (.imm(instruction[31:7]),
.immSrc(immSrc), .immExt(immExt));
controlUnit instance3 (.zero(zero), .funct7(instruction[30]),
.OpCode(instruction[6:0]), .funct3(instruction[14:12]),
.PCSrc(PCSrc), .memWrite(memWrite),
.ALUSrc(ALUSrc), .regWrite(regWrite),
.resultSrc(resultSrc), .immSrc(immSrc),
.ALUControl(ALUControl));
adder instance4 (.A1(PC), .A2(immExt), .O1(PC_target));
endmodule

4.2 Decode Block Elaboration Using Quartus

10
4.3 Decode Block Elaboration Using Vivado

5.0 EXECUTE BLOCK IMPLEMENTATION


The execute stage includes the Arithmetic Logic Unit (ALU) and a multiplexer that
selects between the second operand from the register file or the immediate value
generated by the immediate extension unit. This stage performs all required
arithmetic and logical operations as dictated by the control signals generated during
the decode stage. The selection of operands and the specific ALU operation depend
on the instruction type and its corresponding control configuration, ensuring correct
execution of operations such as addition, subtraction, bitwise logic, and
comparisons.
5.1 Verilog Code
module ALU (
input [2:0] ALUControl,
input [31:0] A1, A2,
output reg zero,
output reg [31:0] O1
);
always @(*) begin
case (ALUControl)
3'b000: O1 = A1 + A2;
3'b001: O1 = A1 - A2;
3'b010: O1 = A1 & A2;
3'b011: O1 = A1 | A2;
3'b101: O1 = (A1 < A2) ? 1 : 0;
default: O1 = 0;
endcase
11
if (A1 - A2 == 0) begin
zero = 1;
end else begin
zero = 0;
end
end
endmodule

module mux #(
parameter WIDTH = 32
) (
input sel,
input [WIDTH - 1:0] A1, A2,
output reg [WIDTH - 1:0] O1
);
always @(*) begin
O1 = sel ? A2 : A1;
end
endmodule

module executeBlock (
input [31:0] readData1, readData2, immExt,
input ALUSrc,
input [2:0] ALUControl,
output zero,
output [31:0] ALUResult
);
wire [31:0] SrcB;
mux instance1 (.sel(ALUSrc), .A1(readData2), .A2(immExt), .O1(SrcB));
ALU instance2 (.ALUControl(ALUControl), .A1(readData1), .A2(SrcB),
.zero(zero), .O1(ALUResult));
endmodule

12
5.2 Execute Block Elaboration Using Quartus

The two snippets above are extracted from the ALU instantiation to provide a
closer, more detailed view of the gates inferred by the synthesis tool.

13
5.3 Execute Block Elaboration Using Vivado

Vivado optimized the design by inferring a single 6-to-1 multiplexer, rather


than cascading multiple 2-to-1 multiplexers. This approach produces a more
efficient and accurate implementation, directly selecting the ALU output
based on the control signal while reducing logic depth and potential delay.

6.0 MEMORY BLOCK IMPLEMENTATION


6.1 Verilog Code
module dataMem #(
parameter WIDTH = 32,
parameter DEPTH = 128
) (
input clk, writeEn,
input [WIDTH - 1:0] address, writeData,
output reg [WIDTH - 1:0] readData
);
reg [WIDTH - 1:0] memory [0:DEPTH - 1];

always @(posedge clk) begin


if (writeEn) begin
memory[address[6:0]] <= writeData;
end
readData <= memory[address[6:0]];
end
endmodule

14
module memoryBlock (
input clk, memWrite,
input [31:0] ALUResult, writeData,
output [31:0] readData
);
parameter WIDTH = 32;
parameter DEPTH = 128;
dataMem #(.WIDTH(WIDTH), .DEPTH(DEPTH)) instance1 (.clk(clk),
.writeEn(memWrite), .address(ALUResult), .writeData(writeData),
.readData(readData));
endmodule

6.2 Memory Block Elaboration Using Quartus

15
6.3 Memory Block Elaboration Using Vivado

7.0 DESIGN SIMULATION


7.1 Top Module
module topModule (
input clk
);
parameter WIDTH = 32;
wire [31:0] instruction, PC_plus4, PC_target, PC, PC_next, readData1,
readData2, immExt, ALUResult, memReadData, Result;
wire PCSrc, memWrite, ALUSrc, zero;
wire [1:0] resultSrc;
wire [2:0] ALUControl;

mux #(.WIDTH(WIDTH)) instance0 (.sel(PCSrc), .A1(PC_plus4),


.A2(PC_target), .O1(PC_next));
fetchBlock instance1 (.clk(clk), .PC_next(PC_next),
.instruction(instruction),
.PC_plus4(PC_plus4), .PC(PC));

decodeBlock instance2 (.instruction(instruction), .writeData(Result),


.PC(PC), .zero(zero), .clk(clk), .PCSrc(PCSrc),
.memWrite(memWrite),
.ALUSrc(ALUSrc), .resultSrc(resultSrc),
.ALUControl(ALUControl),
.readData1(readData1), .readData2(readData2),
.immExt(immExt),
.PC_target(PC_target));

16
executeBlock instance3 (.readData1(readData1), .readData2(readData2),
.immExt(immExt),
.ALUSrc(ALUSrc), .ALUControl(ALUControl),
.zero(zero),
.ALUResult(ALUResult));

memoryBlock instance4 (.clk(clk), .memWrite(memWrite),


.ALUResult(ALUResult), .writeData(readData2), .readData(memReadData));
mux3x1 #(.WIDTH(WIDTH)) instance5 (.sel(resultSrc), .A1(ALUResult),
.A2(memReadData), .A3(PC_plus4), .O1(Result));

endmodule

7.2 Test Bench Code


`timescale 1ns/1ps
module topModuletb (
);
reg clk;

topModule uut (.clk(clk));


initial begin
clk = 0;
forever begin
#1 clk = ~clk;
end
end

initial begin
#50;
$stop;
end
endmodule

17
7.3 Top Module Schematic

The ALUResult signal was taken as an output wire to prevent synthesis


optimization, ensuring that the tool retains the signal and displays the actual
computed result.
7.4 Program Used for Testing

18
The code shown above serves as a test program for verifying the processor’s
functionality. It will also be applied to the pipelined version to ensure
consistent and correct behavior. The program concludes by writing the value
25 to memory address 100, which serves as the verification point indicating
that the processor is functioning correctly. These instructions are preloaded
into the instruction memory within an initial block (not synthesizable), only
for simulation purposes to validate the processor’s operation.
7.5 Simulation

19
As shown above, memory address 100 contains the value 0x19 (25 in
decimal), confirming that the program executed successfully on the processor.
With this verification complete, the design is now ready for implementation
and for evaluation against the timing and power requirements.

8.0 DESIGN IMPLEMENTATION


The ALUResult signal was taken as an output wire to prevent synthesis optimization,
ensuring that the tool retains the signal and displays the actual computed result.

20
8.1 Design Elaboration

8.2 Design Synthesis

21
22
8.3 Design Implementation

23
The implemented single-cycle design meets timing requirements at the target
operating frequency of 100 MHz with a positive slack margin. Further
analysis indicates the design can also operate at 125 MHz, achieving a positive
slack of 0.113 ns, which is within acceptable limits for stable operation. While
the current timing closure is satisfactory, introducing additional pipeline
registers along the critical path could further improve timing margin and
reliability, particularly under process, voltage, and temperature (PVT)
variations. The detailed timing report for the 125 MHz implementation is
provided below.

9.0 PIPELINED PROCESSOR


The pipelined processor will be implemented following the provided block diagram,
where instruction execution is divided into multiple stages—Instruction Fetch (IF),
Instruction Decode (ID), Execute (EX), Memory Access (MEM), and Write Back
(WB) with each stage handled by dedicated hardware in parallel. Pipeline registers
between stages preserve intermediate results and control signals, enabling the
processor to start executing a new instruction every clock cycle after the pipeline is
filled. This design improves throughput by overlapping instruction execution,
allowing multiple instructions to be in different stages simultaneously. While the
control logic becomes more complex, which requires hazard detection, data
forwarding, and pipeline flushing for branch handling, the pipelined structure
enables a higher clock frequency and greater instruction throughput compared to the
single-cycle design, making it more suitable for performance-oriented applications.

24
According to the block diagram, the processor requires both additional hardware and
certain modifications. First, the adder responsible for updating the PC should be
moved to the execute stage. Two 3×1 multiplexers must also be added before the
ALU inputs to enable the required data selection. Regarding additional hardware, a
hazard detection unit should be included, along with pipeline registers, which are
essential for storing data at each clock cycle.
Furthermore, the PCSrc signal should be calculated in the execute stage. This means
that branch and jump control signals are passed through the ID/EX registers, and
PCSrc is generated from there. For branch handling, a static prediction strategy is
applied, assuming branches are always not taken. When a branch is taken, a 2-cycle
stall is introduced as a penalty. Finally, the register file is designed to perform writes
on the negative clock edge, which eliminates the need for an additional register after
the WB stage.

25
9.1 Verilog Code for Extra Hardware
module IFRegs (
input clk, stall, flush,
input [31:0] instruction, PC, PC_plus4,
output reg [31:0] instruction_out, PC_out, PC_plus4_out
);
initial begin
instruction_out = 0;
PC_out = 0;
PC_plus4_out = 0;
end

always @(posedge clk) begin


if (flush) begin
instruction_out <= 0;
PC_out <= 0;
PC_plus4_out <= 0;
end else if (!stall) begin
instruction_out <= instruction;
PC_out <= PC;
PC_plus4_out <= PC_plus4;
end
end
endmodule

module IDRegs (
input clk, flush, regWriteD, memWriteD, jumpD, branchD, ALUSrcD,
input [1:0] resultSrcD,
input [2:0] ALUControlD,
input [31:0] readData1, readData2, PCD, PC_plus4D, immExtD,
input [4:0] Rs1D, Rs2D, RdD,
output reg regWriteD_out, memWriteD_out, jumpD_out, branchD_out,
ALUSrcD_out,
output reg [1:0] resultSrcD_out,
output reg [2:0] ALUControlD_out,
output reg [31:0] readData1_out, readData2_out, PCD_out,
PC_plus4D_out, immExtD_out,
output reg [4:0] Rs1D_out, Rs2D_out, RdD_out
);

always @(posedge clk) begin


if (flush) begin
regWriteD_out <= 0;
26
memWriteD_out <= 0;
jumpD_out <= 0;
branchD_out <= 0;
ALUSrcD_out <= 0;
resultSrcD_out <= 0;
ALUControlD_out <= 0;
readData1_out <= 0;
readData2_out <= 0;
PCD_out <= 0;
PC_plus4D_out <= 0;
immExtD_out <= 0;
Rs1D_out <= 0;
Rs2D_out <= 0;
RdD_out <= 0;
end else begin
regWriteD_out <= regWriteD;
memWriteD_out <= memWriteD;
jumpD_out <= jumpD;
branchD_out <= branchD;
ALUSrcD_out <= ALUSrcD;
resultSrcD_out <= resultSrcD;
ALUControlD_out <= ALUControlD;
readData1_out <= readData1;
readData2_out <= readData2;
PCD_out <= PCD;
PC_plus4D_out <= PC_plus4D;
immExtD_out <= immExtD;
Rs1D_out <= Rs1D;
Rs2D_out <= Rs2D;
RdD_out <= RdD;
end
end
endmodule

module EXRegs (
input clk, reset, regWriteE, memWriteE,
input [1:0] resultSrcE,
input [4:0] RdE,
input [31:0] ALUResult, writeDataE, PC_plus4E,
output reg regWriteE_out, memWriteE_out,
output reg [1:0] resultSrcE_out,
output reg [4:0] RdE_out,
output reg [31:0] ALUResult_out, writeDataE_out, PC_plus4E_out

27
);
always @(posedge clk) begin
if (reset) begin
regWriteE_out <= 0;
memWriteE_out <= 0;
resultSrcE_out <= 0;
RdE_out <= 0;
ALUResult_out <= 0;
writeDataE_out <= 0;
PC_plus4E_out <= 0;
end else begin
regWriteE_out <= regWriteE;
memWriteE_out <= memWriteE;
resultSrcE_out <= resultSrcE;
RdE_out <= RdE;
ALUResult_out <= ALUResult;
writeDataE_out <= writeDataE;
PC_plus4E_out <= PC_plus4E;
end
end
endmodule

module MEMRegs (
input clk, reset, regWriteM,
input [1:0] resultSrcM,
input [31:0] ALUResultM, readDataM, PC_plus4M,
input [4:0] RdM,
output reg regWriteM_out,
output reg [1:0] resultSrcM_out,
output reg [31:0] ALUResultM_out, readDataM_out, PC_plus4M_out,
output reg [4:0] RdM_out
);
always @(posedge clk) begin
if (reset) begin
regWriteM_out <= 0;
resultSrcM_out <= 0;
ALUResultM_out <= 0;
readDataM_out <= 0;
PC_plus4M_out <= 0;
RdM_out <= 0;
end else begin
regWriteM_out <= regWriteM;
resultSrcM_out <= resultSrcM;

28
ALUResultM_out <= ALUResultM;
readDataM_out <= readDataM;
PC_plus4M_out <= PC_plus4M;
RdM_out <= RdM;
end
end
endmodule

module hazardUnit (
input [4:0] Rs1E, Rs2E, Rs1D, Rs2D, RdM, RdW, RdE,
input [1:0] resultSrcE,
input regWriteM, regWriteW, PCSrc,
output reg stallF, stallD, flushD, flushE,
output reg [1:0] forwardA, forwardB
);
// Data Hazards Block
always @(*) begin
if ((Rs1E == RdM) && regWriteM && Rs1E) begin
forwardA = 2'b10;
end else if ((Rs1E == RdW) && regWriteW && Rs1E) begin
forwardA = 2'b01;
end else begin
forwardA = 2'b00;
end

if ((Rs2E == RdM) && regWriteM && Rs2E) begin


forwardB = 2'b10;
end else if ((Rs2E == RdW) && regWriteW && Rs2E) begin
forwardB = 2'b01;
end else begin
forwardB = 2'b00;
end

// LW & Branch hazards


if ((resultSrcE == 1) && ((Rs1D == RdE) || (Rs2D == RdE))) begin
stallF = 1;
stallD = 1;
flushE = 1;
flushD = 0;
end else if (PCSrc) begin
flushD = 1;
flushE = 1;
stallF = 0;

29
stallD = 0;
end else begin
stallF = 0;
stallD = 0;
flushD = 0;
flushE = 0;
end
end
endmodule

9.2 Top Module


module topPipelined (
input clk, reset,
output [31:0] ALU_Result
);
parameter WIDTH = 32;

wire stallF, stallD, flushD, flushE, regWriteD, memWriteD, jumpD,


branchD, ALUSrcD, regWriteW,
regWriteE, memWriteE, jumpE, branchE, ALUSrcE, PCSrc, regWriteM,
memWriteM;
wire [1:0] resultSrcD, resultSrcE, resultSrcM, resultSrcW, forwardA,
forwardB;
wire [2:0] ALUControlD, ALUControlE;
wire [4:0] Rs1D, Rs2D, Rs1E, Rs2E, RdE, RdD, RdM, RdW;
wire [31:0] PCF, PC, PCD, PCE, instructionF, instructionD,
PC_plus4F, PC_plus4D, PC_plus4E, PC_plus4M, PC_plus4W,
readData1, readData2, immExtD, immExtE, readData1E,
readData2E, ResultW, ALUResultM,
ALUResultE, writeDataE, PC_targetE, writeDataM, readDataM,
readDataW, ALUResultW;

mux #(.WIDTH(WIDTH)) instance8 (.sel(PCSrc), .A1(PC_plus4F),


.A2(PC_targetE), .O1(PCF));
fetchBlock instance0 (.clk(clk), .stall(stallF), .PC_next(PCF),
.instruction(instructionF),
.PC_plus4(PC_plus4F), .PC(PC));
IFRegs instance1 (.clk(clk), .stall(stallD), .flush(flushD),
.instruction(instructionF), .PC(PC),
.PC_plus4(PC_plus4F),
.instruction_out(instructionD), .PC_out(PCD),
.PC_plus4_out(PC_plus4D));

30
decodeBlock instance2 (.instruction(instructionD),
.writeData(ResultW),
.RdW(RdW), .clk(clk), .regWriteW(regWriteW),
.regWriteD(regWriteD), .memWrite(memWriteD),
.ALUSrc(ALUSrcD),
.jumpD(jumpD), .branchD(branchD),
.resultSrc(resultSrcD),
.ALUControl(ALUControlD),
.Rs1D(Rs1D), .Rs2D(Rs2D), .RdD(RdD),
.readData1(readData1), .readData2(readData2),
.immExt(immExtD));
IDRegs instance3 (.clk(clk), .flush(flushE), .regWriteD(regWriteD),
.memWriteD(memWriteD),
.jumpD(jumpD), .branchD(branchD), .ALUSrcD(ALUSrcD),
.resultSrcD(resultSrcD), .ALUControlD(ALUControlD),
.readData1(readData1), .readData2(readData2),
.PCD(PCD), .PC_plus4D(PC_plus4D), .immExtD(immExtD),
.Rs1D(Rs1D), .Rs2D(Rs2D), .RdD(RdD),
.regWriteD_out(regWriteE),
.memWriteD_out(memWriteE),
.jumpD_out(jumpE), .branchD_out(branchE),
.ALUSrcD_out(ALUSrcE),
.resultSrcD_out(resultSrcE),
.ALUControlD_out(ALUControlE),
.readData1_out(readData1E),
.readData2_out(readData2E), .PCD_out(PCE),
.PC_plus4D_out(PC_plus4E), .immExtD_out(immExtE),
.Rs1D_out(Rs1E), .Rs2D_out(Rs2E), .RdD_out(RdE));
executeBlock instance4 (.readData1(readData1E),
.readData2(readData2E),
.immExt(immExtE), .PCE(PCE),
.ResultW(ResultW),
.ALUResultM(ALUResultM), .ALUSrc(ALUSrcE),
.jumpE(jumpE),
.branchE(branchE), .forwardA(forwardA),
.forwardB(forwardB),
.ALUControl(ALUControlE),
.ALUResult(ALUResultE), .writeDataE(writeDataE),
.PC_targetE(PC_targetE), .PCSrc(PCSrc));
EXRegs instance5 (.clk(clk), .reset(reset), .regWriteE(regWriteE),
.memWriteE(memWriteE),
.resultSrcE(resultSrcE), .RdE(RdE),
.ALUResult(ALUResultE),
.writeDataE(writeDataE), .PC_plus4E(PC_plus4E),
31
.regWriteE_out(regWriteM),
.memWriteE_out(memWriteM),
.resultSrcE_out(resultSrcM),
.RdE_out(RdM), .ALUResult_out(ALUResultM),
.writeDataE_out(writeDataM),
.PC_plus4E_out(PC_plus4M));
memoryBlock instance6 (.clk(clk), .memWrite(memWriteM),
.ALUResult(ALUResultM), .writeData(writeDataM),
.readData(readDataM));
MEMRegs instance7 (.clk(clk), .reset(reset), .regWriteM(regWriteM),
.resultSrcM(resultSrcM), .ALUResultM(ALUResultM),
.readDataM(readDataM), .PC_plus4M(PC_plus4M),
.RdM(RdM), .regWriteM_out(regWriteW),
.resultSrcM_out(resultSrcW),
.ALUResultM_out(ALUResultW),
.readDataM_out(readDataW),
.PC_plus4M_out(PC_plus4W), .RdM_out(RdW));
mux3x1 #(.WIDTH(WIDTH)) instance9 (.sel(resultSrcW), .A1(ALUResultW),
.A2(readDataW),
.A3(PC_plus4W), .O1(ResultW));
hazardUnit instance10 (.Rs1E(Rs1E), .Rs2E(Rs2E), .Rs1D(Rs1D),
.Rs2D(Rs2D), .RdM(RdM), .RdW(RdW), .RdE(RdE),
.resultSrcE(resultSrcE), .regWriteM(regWriteM),
.regWriteW(regWriteW), .PCSrc(PCSrc),
.stallF(stallF), .stallD(stallD),
.flushD(flushD), .flushE(flushE),
.forwardA(forwardA), .forwardB(forwardB));

assign ALU_Result = ALUResultW;


endmodule

10.0 PIPELINED PROCESSOR SIMULATION


The pipelined processor uses the same program as the single-cycle processor, with
the instruction memory preloaded with the program code to facilitate simulation.
10.1 Test Bench Code
`timescale 1ns/1ps
module topPipelinedtb (
);
reg clk, reset;
topPipelined uut (.clk(clk), .reset(reset));

32
initial begin
$readmemh("[Link]", [Link]);
end

initial begin
clk = 0;
forever begin
#1 clk = ~clk;
end
end

initial begin
reset = 1;
#1;
reset = 0;
#100;
$stop;
end
endmodule

10.2 Simulation

33
34
11.0 PIPELINED PROCESSOR IMPLEMENTATION
11.1 Design Elaboration

35
11.2 Design Synthesis

36
11.3 Design Implementation

37
38
The pipelined processor maintained sufficient positive slack, enabling safe
operation at 125 MHz with improved timing margin compared to the single-
cycle design.

12.0 CONCLUSION
In summary, this project successfully demonstrates the full implementation of the
RISC-V RV32I processor, starting from a basic single-cycle design and advancing
to a more efficient pipelined architecture. The careful verification at each stage
ensured that the processor operates correctly according to the RISC-V specification,
providing a solid foundation for further enhancements or customization.
The transition from a theoretical design to a practical hardware implementation
highlighted important engineering challenges, including managing pipeline hazards
and meeting strict timing constraints. Overcoming these challenges requires a
combination of thoughtful design decisions and rigorous testing, which are critical
skills in real-world processor development.
Testing the design on an FPGA platform validated that the processor could achieve
stable operation at the target frequency of 100 MHz, confirming the viability of the
approach for practical applications. This successful hardware realization shows how
open-source ISAs like RISC-V can bridge the gap between academic concepts and
commercial-grade products.
Ultimately, this work reinforces the value of RISC-V as a flexible and scalable
architecture suitable for a wide range of uses. It serves as an example of how open
standards can empower engineers to innovate efficiently while maintaining high
performance and reliability, paving the way for future advancements in processor
design.

39
REFERENCES
[1] S. L. Harris and D. M. Harris, Digital Design and Computer
Architecture. Morgan Kaufmann, 2012.

40

You might also like