A fully structural, single-cycle RV32I RISC-V 32-bit processor core implemented in SystemVerilog, featuring complete datapath-control unit integration, custom toolchain (assembler & memory generator), and verified via execution of a Bubble Sort program on 20 signed integers.
π€ Authors: Amirali Dehghani (
810102443) Β· Nazhin Nikkhahbahrami (810102530)
π Course: Computer Architecture β Fall 1404 (2025β2026)
π« University: University of Tehran
π Hardware Datapath & Control Report Reference: ComputerArchitecture-CA2-810102443-810102530.pdf
π Assignment Specification: CA#02.pdf
- CA2: Single-Cycle RISC-V (RV32I) Processor β Hardware Implementation βοΈπ
- π Table of Contents
- π Overview & Processor Core Details
- π― Instruction Set Architecture (ISA) & Encoding
- ποΈ Hardware System Architecture
- ποΈ Datapath Component Specifications
- πΉοΈ Control Unit Design
- π» Software & Custom Toolchain Pipeline
- π§ͺ Simulation & Verification
- π Compliance with Design Guidelines
- π Directory Structure
The Single-Cycle RISC-V Processor is a classic Harvard-architecture implementation of the 32-bit RISC-V base integer instruction set (RV32I). In a single-cycle design, the execution of every instruction β including instruction fetch, decode, operand fetch, ALU execution, memory access, and write-back β occurs entirely within one single clock cycle.
-
Single-Cycle Execution:
$CPI = 1.0$ across all instruction types. - Support for 16 Instructions: Covers R-type, I-type, S-type, B-type, J-type, and U-type formats.
-
Word-Addressed Memory Interface: Memory units use word-aligned addressing (
$A[31:2]$ ) for 32-bit access. -
Dedicated Register
$x0$ Guard: Registerx0is hardwired to zero, preventing accidental writes. -
Custom Toolchain: Self-contained assembler written in Python translates assembly directly to machine code hex files (
instructions.mem), while a data-generator prepares memory initialization files (data.mem).
The core supports 16 essential instructions from the RV32I instruction set:
| Instruction | Type | Opcode (op) |
funct3 |
funct7 / Imm Format |
Operation Summary |
|---|---|---|---|---|---|
add |
R-Type | 0110011 |
000 |
0000000 |
|
sub |
R-Type | 0110011 |
000 |
0100000 |
|
and |
R-Type | 0110011 |
111 |
0000000 |
|
or |
R-Type | 0110011 |
110 |
0000000 |
|
slt |
R-Type | 0110011 |
010 |
0000000 |
|
addi |
I-Type | 0010011 |
000 |
I-Immediate | |
xori |
I-Type | 0010011 |
100 |
I-Immediate | |
ori |
I-Type | 0010011 |
110 |
I-Immediate | |
slti |
I-Type | 0010011 |
010 |
I-Immediate | |
lw |
I-Type | 0000011 |
010 |
I-Immediate | |
jalr |
I-Type | 1100111 |
000 |
I-Immediate | |
sw |
S-Type | 0100011 |
010 |
S-Immediate | |
beq |
B-Type | 1100011 |
000 |
B-Immediate | |
bne |
B-Type | 1100011 |
001 |
B-Immediate | |
jal |
J-Type | 1101111 |
β | J-Immediate | |
lui |
U-Type | 0110111 |
β | U-Immediate |
The top-level module RiscV.sv orchestrates the communication between the Datapath.sv and ControlUnit.sv:
βββββββββββββββββββββββββββββββββββββββββββ
β RiscV (Top Module) β
β β
clk βββββββββββββββββΊβ ββββββββββββββββ βββββββββββββββ β
rst βββββββββββββββββΊβ β β β β β
β β ControlUnit ββββββ Datapath β β
β β βββββΊβ β β
β ββββββββ¬ββββββββ βββββββββββββββ β
β β β
ββββββββββββΌβββββββββββββββββββββββββββββββ
βΌ
Ready
The hardware structure implemented in Datapath.sv and illustrated in Page 1 of ComputerArchitecture-CA2-810102443-810102530.pdf is detailed below:
| Module Name | File | Description & Sub-components |
|---|---|---|
RiscV |
RiscV.sv | Top-level entity instantiating Datapath and ControlUnit |
Datapath |
Datapath.sv | Interconnects PC, IM, RF, ALU, DM, ImmExtend, Adders, MUXes |
ControlUnit |
ControlUnit.sv | Combinational decoder generating 7 main control signals |
Register_32 |
register.sv | 32-bit D-register used as Program Counter (PC) |
InstructionMemory |
InstructionMemory.sv | 8192-word ROM, loaded from instructions.mem
|
Adder_32 |
Adder.sv | 32-bit signed adder for PC+4 and PC+Imm calculations |
RegisterFile |
RegisterFile.sv | 32 |
ImmediateExtend |
ImmediateExtend.sv | Sign-extension logic for I, S, B, J, and U instruction types |
ALU |
ALU.sv | 32-bit multi-function arithmetic logic unit |
DataMemory |
DataMemory.sv | 8192-word RAM, reloads from data.mem on reset |
Mux_4to1_32 |
Mux.sv | 4-channel 32-bit multiplexer for PC select & Write-Back select |
Mux_2to1_32 |
Mux.sv | 2-channel 32-bit multiplexer for ALU input B selection |
The Register File (RegisterFile.sv) provides dual asynchronous read ports (RD1, RD2) and a single synchronous write port (WD3):
- Asynchronous Read: Outputs
RD1 = registers[A1]andRD2 = registers[A2]instantly. - Synchronous Write: Writes on
posedge clkwhenWE3 == 1. - Zero Register Lock: Writing to register
x0(A3 == 5'd0) is explicitly blocked (A3 != 5'd0), ensuringx0remains constant32'b0.
The Immediate Extension Unit (ImmediateExtend.sv) constructs sign-extended 32-bit immediates based on ImmSrc:
ImmSrc |
Format | Bit-Slicing & Sign-Extension Logic |
|---|---|---|
3'b000 |
I-Type | {{20{immediate[31]}}, immediate[31:20]} |
3'b001 |
S-Type | {{20{immediate[31]}}, immediate[31:25], immediate[11:7]} |
3'b010 |
B-Type | {{20{immediate[31]}}, immediate[7], immediate[30:25], immediate[11:8], 1'b0} |
3'b011 |
J-Type | {{12{immediate[31]}}, immediate[19:12], immediate[20], immediate[30:21], 1'b0} |
3'b100 |
U-Type | {immediate[31:12], 12'b00} |
The ALU (ALU.sv) executes arithmetic and logical operations controlled by ALUControl:
ALUControl |
Code Name | Operation | Description |
|---|---|---|---|
3'b000 |
ALU_ADD |
Signed 32-bit Addition | |
3'b001 |
ALU_SUB |
Signed 32-bit Subtraction | |
3'b010 |
ALU_AND |
Bitwise AND | |
3'b011 |
ALU_OR |
Bitwise OR | |
3'b100 |
ALU_XOR |
Bitwise XOR | |
3'b101 |
ALU_SLT |
Set Less Than (signed comparison) |
- Zero Flag Output: Asserted (
zero = 1) wheneverresult == 0.
The Control Unit (ControlUnit.sv) decodes instruction opcodes and function fields to generate all processor control signals:
| Instr | Format | op |
funct3 |
funct7 |
ImmSrc |
ALUSrc |
ResultSrc |
PCSrc |
MemWrite |
RegWrite |
ALUControl |
|---|---|---|---|---|---|---|---|---|---|---|---|
add |
R-Type | 0110011 |
000 |
0000000 |
β | 0 |
00 |
00 |
0 |
1 |
000 (ADD) |
sub |
R-Type | 0110011 |
000 |
0100000 |
β | 0 |
00 |
00 |
0 |
1 |
001 (SUB) |
and |
R-Type | 0110011 |
111 |
0000000 |
β | 0 |
00 |
00 |
0 |
1 |
010 (AND) |
or |
R-Type | 0110011 |
110 |
0000000 |
β | 0 |
00 |
00 |
0 |
1 |
011 (OR) |
slt |
R-Type | 0110011 |
010 |
0000000 |
β | 0 |
00 |
00 |
0 |
1 |
101 (SLT) |
addi |
I-Type | 0010011 |
000 |
β | 000 (I) |
1 |
00 |
00 |
0 |
1 |
000 (ADD) |
xori |
I-Type | 0010011 |
100 |
β | 000 (I) |
1 |
00 |
00 |
0 |
1 |
100 (XOR) |
ori |
I-Type | 0010011 |
110 |
β | 000 (I) |
1 |
00 |
00 |
0 |
1 |
011 (OR) |
slti |
I-Type | 0010011 |
010 |
β | 000 (I) |
1 |
00 |
00 |
0 |
1 |
101 (SLT) |
lw |
I-Type | 0000011 |
010 |
β | 000 (I) |
1 |
01 |
00 |
0 |
1 |
000 (ADD) |
jalr |
I-Type | 1100111 |
000 |
β | 000 (I) |
1 |
10 |
10 |
0 |
1 |
000 (ADD) |
sw |
S-Type | 0100011 |
010 |
β | 001 (S) |
1 |
00 |
00 |
1 |
0 |
000 (ADD) |
beq |
B-Type | 1100011 |
000 |
β | 010 (B) |
0 |
00 |
Zero?01:00 |
0 |
0 |
001 (SUB) |
bne |
B-Type | 1100011 |
001 |
β | 010 (B) |
0 |
00 |
!Zero?01:00 |
0 |
0 |
001 (SUB) |
jal |
J-Type | 1101111 |
β | β | 011 (J) |
0 |
10 |
01 |
0 |
1 |
000 (ADD) |
lui |
U-Type | 0110111 |
β | β | 100 (U) |
1 |
00 |
00 |
0 |
1 |
000 (ADD) |
PCSrc: Selects PC source (00= PC+4,01= TargetPC+ImmExt,10=ALUResultforjalr).ResultSrc: Selects Write-Back data to Register File (00= ALU,01= Memory,10=PC+4,11=PC).MemWrite: Asserts memory write enable forsw.ALUSrc: Selects ALU B operand (0= RegisterRD2,1=ImmExt).RegWrite: Asserts register file write enable.Ready: Asserts high when an unhandled opcode is reached (signals simulation termination).
Located in Assembly/sort.c, sorting 20 signed integers:
#include <stdint.h>
#define N 20
int32_t arr[N] = {
12, -5, 33, 7, 0, 19, -12, 44, 8, -1,
3, 27, 15, 2, -8, 6, 9, -3, 25, 1
};
int main() {
for (int i = 0; i < N - 1; i++) {
for (int j = 0; j < N - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int32_t temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
return 0;
}Located in Assembly/sort.s:
addi x1, x0, 0 # x1 = base array pointer (0)
addi x2, x0, 0 # x2 = outer loop counter (i = 0)
addi x8, x0, 20 # x8 = N = 20
addi x4, x0, 19 # x4 = N-1 = 19
LOOP1:
slt x12, x2, x4 # x12 = (i < N-1) ? 1 : 0
beq x12, x0, DONE # if i >= N-1 exit
addi x3, x0, 0 # x3 = inner loop counter (j = 0)
addi x7, x1, 0 # x7 = current element address &arr[j]
sub x9, x4, x2 # x9 = N-1-i (inner loop bound)
LOOP2:
slt x10, x3, x9 # x10 = (j < N-1-i) ? 1 : 0
beq x10, x0, END_LOOP2 # if j >= bound exit inner
lw x5, 0(x7) # x5 = arr[j]
lw x6, 4(x7) # x6 = arr[j+1]
slt x11, x6, x5 # x11 = (arr[j+1] < arr[j]) ? 1 : 0
beq x11, x0, NO_SWAP # if arr[j] <= arr[j+1], skip swap
sw x6, 0(x7) # arr[j] = arr[j+1]
sw x5, 4(x7) # arr[j+1] = arr[j] (swap)
NO_SWAP:
addi x3, x3, 1 # j++
addi x7, x7, 4 # pointer += 4 (next word)
jal x0, LOOP2 # repeat inner loop
END_LOOP2:
addi x2, x2, 1 # i++
jal x0, LOOP1 # repeat outer loop
DONE:
# Program finished: hits default opcode to assert ReadyThe self-contained Python script Assembly/assembly_to_machine.py translates .s files into 32-bit hexadecimal machine code (instructions.mem):
# Execute Assembler
python Assembly/assembly_to_machine.py
# Generated Machine Code (instructions.mem):
00000093
00000113
01400413
01300213
00412633
04060263
00000193
...The testbench RiscV_TB.sv verifies processor execution:
`timescale 1ns/1ns
module RiscV_TB();
logic clk = 1'b0, rst = 1'b1;
wire Ready;
RiscV UUT(clk, rst, Ready);
always #20 clk = ~clk;
always @(posedge Ready) #10 $stop;
initial #10 rst = 1'b0;
endmoduleAs illustrated on Page 3 of ComputerArchitecture-CA2-810102443-810102530.pdf, 20 signed integers are loaded into memory:
Memory[0..9] : 12, -5, 33, 7, 0, 19, -12, 44, 8, -1
Memory[10..19]: 3, 27, 15, 2, -8, 6, 9, -3, 25, 1
Upon completion when Ready == 1, memory dump confirms perfect ascending order:
Memory[0..9] : -12, -8, -5, -3, -1, 0, 1, 2, 3, 6
Memory[10..19]: 7, 9, 12, 15, 19, 25, 27, 33, 44
π― Result: The array is completely sorted in memory, confirming correct execution of all R, I, S, B, and J instructions.
The implementation satisfies all guidelines from CA#02.pdf:
- Single-Cycle RISC-V Architecture: Complete instruction cycle executed in one clock period.
- RV32I Base Subset Support: Complete support for 16 key instructions across all six format types (R, I, S, B, J, U).
-
Word-Aligned Memory Access: Addresses mapped using
A[31:2]. -
Hardwired Zero Register (
$x0$ ):x0register write-protected. - Structural & Combinational Design: Modular SystemVerilog code with combinational Control Unit decoding.
CA2/
βββ RiscV.sv # Top-level module (wires Datapath + ControlUnit)
βββ Datapath.sv # Structural Datapath module
βββ ControlUnit.sv # Combinational Control Unit decoder
βββ ALU.sv # 32-bit Signed ALU
βββ RegisterFile.sv # 32x32-bit Register File (x0 hardwired to zero)
βββ ImmediateExtend.sv # Sign-extension logic for I/S/B/J/U immediates
βββ InstructionMemory.sv # 8192-word ROM (loads instructions.mem)
βββ DataMemory.sv # 8192-word RAM (loads data.mem on reset)
βββ Mux.sv # 2-to-1 and 4-to-1 32-bit Multiplexers
βββ Adder.sv # 32-bit Adder
βββ register.sv # 32-bit PC Register
βββ RiscV_TB.sv # Processor Testbench
βββ instructions.mem # Hex machine code (22 instructions)
βββ data.mem # Hex data memory (20 signed integers)
βββ Assembly/
β βββ sort.c # Reference C implementation of bubble sort
β βββ sort.s # Hand-crafted RISC-V assembly code
β βββ assembly_to_machine.py # Custom Python RISC-V Assembler
β βββ make_data_mem.py # Python script generating data.mem
β βββ array_of_integers.txt # Raw input array (20 signed integers)
βββ CA#02.pdf # Project specification PDF
βββ ComputerArchitecture-CA2-810102443-810102530.pdf # Report PDF with datapath diagram & results
π Computer Architecture Course β Fall 1404 (2025)
Department of Electrical and Computer Engineering β University of Tehran
