Back in 2019, I built a MIPS single-cycle processor in Verilog, extended it into a pipeline, and ran it on an FPGA.
Here, I will be going through the things I did to make a single-cycle MIPS processor in Verilog HDL, perform tests on Intel Quartus Prime’s Modelsim, and implement a BNE instruction.
This is a 3-part series, where the 2nd part will talk about extending this MIPS processor into a 5-stage pipeline, and the 3rd part will talk about running the MIPS 5-stage pipeline on a DE10 Nano FPGA.
Part 1: Building a MIPS single-cycle processor in Verilog
Part 2: Building a MIPS 5-stage Pipeline processor in Verilog
Part 3: Running the MIPS 5-stage Pipeline processor on a DE10-Nano FPGA
Table of contents
- Some prerequisites
- Making a single-cycle MIPS processor in Verilog HDL
- Testing the single-cycle MIPS processor on Modelsim
- Implementing a BNE instruction on the single-cycle MIPS processor
- Testing the single-cycle MIPS processor with the BNE instruction on ModelSim
Some prerequisites
I’ve included some prerequisites for building a single-cycle MIPS processor in Verilog HDL in this section.
To get started with Intel Quartus Prime, please check out the following blog post,
To create FPGA projects using Verilog HDL, please check out the following blog post,
Digital Design and Computer Architecture by David Harris, Sarah L. Harris contains a walkthrough of building a single-cycle MIPS using Verilog HDL or VHDL.
Computer Organization and Design by David A. Patterson, John L. Hennessy talks in detail about the MIPS design and computer architecture.
Making a single-cycle MIPS processor in Verilog HDL
The MIPS design used in this blog post is based on the design found in Computer Organization and Design by David A. Patterson, John L. Hennessy.
The design of the Single-cycle MIPS processor that implements a MIPS subset can be seen below.
In this section, I’ll provide some example of the Verilog codes that were used in the MIPS single-cycle processor.
In my previous blog post, I went into detail about how to build an adder using Verilog HDL, at the very low-layer level, where I took into consideration each wire, gates, flip flops, etc.
In Verilog, you can program in a higher level, without having to consider each wire, gates, flip flops. This is especially useful when creating complex circuitry like a CPU.
For example, the MIPS processor contains an adder, so we’ll be using a 32-bit adder module. Two 32-bit operand inputs, a
and b
will be added together to output a 32-bit y
. The adder module looks like the following,
module adder(input logic [31:0] a, b,
output logic [31:0] y);
assign y = a + b;
endmodule
The ALU, which stands for the Arithmetic Logic Unit, is responsible for carrying out the arithmetic and logical operations. There are two 32-bit operand inputs, a
and b
, and a 3-bit ALU control signal input alucontrol
that specifies which ALU operation is to be done. It outputs a 32-bit result
. The ALU module looks like the following,
module alu(input logic [31:0] a, b,
input logic [2:0] alucontrol,
output logic [31:0] result);
logic [31:0] condinvb, sum;
assign condinvb = alucontrol[2] ? ~b : b;
assign sum = a + condinvb + alucontrol[2];
always_comb
case (alucontrol[1:0])
2'b00: result = a & b;
2'b01: result = a | b;
2'b10: result = sum;
2'b11: result = sum[31];
endcase
endmodule
The instruction memory module looks like the following, where it reads a file called instr.dat
(contains the instruction in hex), and puts it into RAM
. This module takes a 6-bit input a
, and outputs a 32-bit instruction rd
.
module imem(input logic [5:0] a, output logic [31:0] rd);
logic [31:0] RAM[63:0];
initial
$readmemh("instr.dat", RAM);
assign rd = RAM[a]; // word aligned
endmodule
There are many more components to the MIPS processor. The book Digital Design and Computer Architecture covers in detail how to implement the Single-cycle MIPS processor using Verilog HDL or VHDL.
Testing the single-cycle MIPS processor on Modelsim
In order to see the contents of registers and memory, I used $display()
to output the contents of the register and memory on Modelsim whenever a register or memory value is updated.
In the register file module, I put $display()
to output the contents of the register upon update.
module regfile(input logic clk,
input logic we3,
input logic [4:0] ra1, ra2, wa3,
input logic [31:0] wd3,
output logic [31:0] rd1, rd2);
...
...
rf[wa3] <= wd3;
case(wa3)
5'b10000: $display("content of $s0 = %h", wd3);
5'b10001: $display("content of $s1 = %h", wd3);
5'b10010: $display("content of $s2 = %h", wd3);
5'b10011: $display("content of $s3 = %h", wd3);
5'b10100: $display("content of $s4 = %h", wd3);
5'b01000: $display("content of $t0 = %h", wd3);
5'b01001: $display("content of $t1 = %h", wd3);
//default: $display("no");
endcase
end
end
...
endmodule
In the dmem
module, I put $display()
to output the address and the contents at that address upon memory update.
module dmem(input logic clk, we,
input logic [31:0] a, wd,
output logic [31:0] rd);
logic [31:0] RAM[63:0];
assign rd = RAM[a[31:2]]; // word aligned
always_ff @(posedge clk)
if (we) begin
RAM[a[31:2]] <= wd;
$display("address %h now has data %h", a[31:0], wd);
end
endmodule
I prepared the following assembly instructions to test the MIPS single-cycle processor,
ADDI $s1 $zero 0x0001 //a is $s1, has 1
ADDI $s2 $zero 0x0002 //b is $s2, has 2
ADDI $s3 $zero 0x0003 //c is $s3, has 3
ADDI $s4 $zero 0x0004 //d is $s4, has 4
ADD $t0 $s1 $s2 //put a+b in $t0
ADD $t1 $s3 $s4 //put c+d in $t1
SUB $s0 $t1 $t0 //put (c+d)-(a+b) in $s0
SW $s1 0x0($zero)
SW $s2 0x4($zero)
SW $s3 0x8($zero)
SW $s4 0xC($zero) //this is 12($zero) in decimal
SW $s0 0x10($zero) //this is 16($zero) in decimal
LW $s1 0x8($zero)
As instr.dat
required the instructions to be in hex, I converted the above assembly into the following and put it inside instr.dat
.
20110001
20120002
20130003
20140004
02324020
02744820
01288022
AC110000
AC120004
AC130008
AC14000C
AC100010
8C110008
The following shows the output on Modelsim after running,
As shown in the output, the register and memory contents are correct and show that the single-cycle MIPS processor is working as expected.
Implementing a BNE instruction on the single-cycle MIPS processor
In order to implement a BNE (Branch Not Equal) instruction to the Single-cycle MIPS processor, I modified the circuitry as shown below,
I added a new wire called BeqBne
, which uses the 27:26 bit of the instruction to control a new multiplexor called muxBEQBNE
. This muxBEQBNE
takes in an input zero
and NotZero
from the ALU and outputs it as zeroNzero
.
module muxBEQBNE(input logic BeqBne,
input logic zero,
input logic notzero,
output logic zeroNzero);
assign zeroNzero = BeqBne ? notzero : zero;
//always_ff @(zeroNzero) $display("BeqBne=%b, zero=%b, notzero=%b, zeroNzero=%b", BeqBne, zero, notzero, zeroNzero);
endmodule
The OP code of BEQ is 000100
, while the OP code of BNE is 000101
.
module maindec(input logic [5:0] op,
output logic memtoreg, memwrite,
output logic branch, alusrc,
output logic regdst, regwrite,
output logic jump,
output logic [1:0] aluop);
...
...
always_comb
case(op)
6'b000000: controls <= 9'b110000010; // RTYPE
6'b100011: controls <= 9'b101001000; // LW
6'b101011: controls <= 9'b001010000; // SW
6'b000100: begin
controls <= 9'b000100001; // BEQ
$display("BEQ");
end
6'b000101: begin
controls <= 9'b000100001; // BNE has same controls as BEQ
$display("BNE");
end
6'b001000: controls <= 9'b101000000; // ADDI
6'b000010: controls <= 9'b000000100; // J
default: controls <= 9'bxxxxxxxxx; // illegal op
endcase
endmodule
The full Verilog code can be found in my Github repo.
Testing the single-cycle MIPS processor with the BNE instruction on ModelSim
To test the single-cycle MIPS processor with the BNE instruction, I prepared the following assembly instructions,
addi $s0 $zero 0x1
addi $s1 $zero 0x2
bne $s0 $s1 0x1 //taken
addi $s3 $zero 0x5 //skipped
addi $s4 $zero 0x4 //come here from bne, $s4 should be 4
addi $t0 $zero 0x2
beq $t0 $s1 0x2 //taken
addi $s2 $zero 0x8 //skipped
add $s1 $s0 $s1 //skipped
addi $t1 $zero 0x9 //come here from beq, $t1 should be 9
bne $s1 $t0 0x1 //not taken
add $s1 $s1 $s0 //$s1 should be 3
beq $s1 $s0 0x1 //not taken
add $s1 $s1 $s0 //$s1 should be 4
Then converted into hex,
20100001
20110002
16110001
20130005
20140004
20080002
11110002
20120008
02118820
20090009
16280001
02308820
12300001
02308820
Then I ran the tests on ModelSim to confirm that the outputs are as expected.
This post focused on what I did for implementing the single-cycle MIPS processor on Verilog, as well as implementing the BNE instruction.
In the next blog post, I will be talking about how I extended this single-cycle MIPS processor into a 5-stage pipeline.
Part 1: Building a MIPS single-cycle processor in Verilog
Part 2: Building a MIPS 5-stage Pipeline processor in Verilog
Part 3: Running the MIPS 5-stage Pipeline processor on a DE10-Nano FPGA