实验目的:
本实验通过设计和实现一个基于MIPS指令集的单周期CPU,深入理解MIPS指令集架构和单周期CPU的工作原理,理解MIPS指令集的格式,编码以及寻址模式,包括R型,I型和J型指令的特点,利用硬件描述语言Verilog建立单周期CPU内部组成的模型,包括寄存器堆,算术逻辑单元,数据存储器和程序计数器,并描述他们在执行指令过程中的作用和数据流动。
关于计算机体系结构处理器部分内容可参考B站视频:
实验原理:
MIPS指令:
MIPS(Microprocessor without Interlocked Pipeline Stages)是一种精简指令集计算机(RISC)架构,它以其简洁的指令集、高效的流水线设计和高性能而著称。MIPS指令集的设计哲学强调了指令的简单性和规则性,这有助于简化处理器的设计和提高指令的执行速度。
MIPS指令集的所有指令均为固定字长,通常为32位(4字节),分为三类:R型指令、I型指令和J型指令。
R型指令:R型指令用于寄存器之间的操作,如加减乘除等,指令格式中的操作码为000000。这种指令涉及到寄存器之间的操作,使用3个寄存器进行操作。这些指令的操作码字段为6位,rs、rt、rd、shamt和funct字段的长度为5位。其中,rs、rt分别表示源寄存器1和源寄存器2,rd表示目标寄存器,shamt字段用于移位操作,funct字段指定具体的操作类型。
表1 R型指令格式
6位操作码 | 5位rs | 5位rt | 5位rd | 5位shamt | 6位功能码 |
000000 | $rs | $rt | $rd | shamt | funct |
I型指令:I型指令用于取数和存数操作,立即数可以是有符号数或无符号数,立即数(immediate value)是指在指令中直接给出的数值,而不是从寄存器或内存中读取的数据。操作码根据不同指令而不同。这种指令涉及到立即数的操作,使用2个寄存器和一个立即数进行操作。这些指令的操作码字段为6位,rs、rt字段的长度为5位,immediate字段的长度为16位。其中,rs表示源寄存器,rt表示目标寄存器,immediate表示立即数。此外I型指令还包括计算类指令。I型计算类指令是指立即数计算类指令,其操作码为6位,rs字段代表第一个操作数寄存器,rt字段代表第二个操作数寄存器,立即数字段表示立即数操作数。这类指令执行时,先将第一个操作数从寄存器中取出,再将立即数或者第二个操作数从寄存器中取出,经过计算后将结果存储到目标寄存器中。
表2 I型指令格式
6位操作码 | 5位rs | 5位rt | 16位立即数 |
lw:6’d43 sw:6’d4 addi:6’d08 ori:6’d13 | $rs | $rt | immediat |
分支指令:涉及到条件分支的操作,使用1个寄存器和一个偏移量进行操作。这些指令的操作码字段为6位,rs、rt字段的长度为5位,offset字段的长度为16位。其中,rs表示源寄存器,offset表示偏移量,根据rs中的值进行条件分支操作。
表3 J型指令格式
6位操作码 | 5位rs | 5位rt | 16位立即数 |
000100 | $rs | $rt | immediat |
单周期CPU工作原理:
单周期(Single Cycle)CPU是指CPU从取出1条指令到执行完该指令只需1个时钟周期。一条指令的执行过程包括:取指令→指令译码→执行指令→存储器访问→结果写回。
下图是包含控制单元的简单数据通路:
以执行R型指令(add $t1,$t2,$t3)为例:
1)从指令存储器中取出指令,PC自增。
2)从寄存器堆中读出寄存器$t2和$t3。同时,主控制单元计算出各控制信号的状态。
3)ALU根据funct字段(指令的5:0位)确定ALU的功能,对从寄存器堆读出的数据进行操作。
4)将ALU的结果写入寄存器堆,根据指令的15:11位选择目标寄存器($t1)。
单周期CPU的具体实现:
本实验所设计的单周期CPU整体框架主要包括程序计数器(PC),指令存储器(InstMem),寄存器堆(Register_Files),数据存储器(DataMem),算数逻辑单元(ALU)以及控制单元(ControlUnit),此外还有四个多选器用于控制数据流向,加法器用于地址运算,扩展单元用于将指令的[15:0]位扩展至32位用于数据偏移计算。单周期CPU设计的整体数据通路如下图:
程序计数器PC:
PC为时序逻辑,在时钟上升沿到来时,将地址传递出去
module PC (
input sys_clk,
input sys_rst_n,
input [31:0] Address_in,
output reg [31:0] Address
);
initial begin
Address <= 32'b0;
end
always @(posedge sys_clk or negedge sys_rst_n) begin
if(~sys_rst_n)
Address <= 32'b0;
else Address <= Address_in;
end
endmodule
指令存储器InstMem:
这部分参考了https://blog.csdn.net/Accelerato/article/details/86546751的代码,方便后续的仿真验证,在这一模块设置32x32的rom将地址与指令对应;
module InstMeM (
input [31:0] Address,
output [31:0] Inst
);
wire [31:0] Rom [31:0];
assign Rom[5'h00]=32'h20010008;//addi $1,$0,8 $1=8
assign Rom[5'h01]=32'h3402000C;//ori $2,$0,12 $2=12
assign Rom[5'h02]=32'h00221820;//add $3,$1,$2 $3=20
assign Rom[5'h03]=32'h00412022;//sub $4,$2,$1 $4=4
assign Rom[5'h04]=32'h00222824;//and $5,$1,$2
assign Rom[5'h05]=32'h00223025;//or $6,$1,$2
assign Rom[5'h06]=32'h14220002;//bne $1,$2,2
assign Rom[5'h07]=32'hXXXXXXXX;
assign Rom[5'h08]=32'hXXXXXXXX;
assign Rom[5'h09]=32'h10220002;// beq $1,$2,2
assign Rom[5'h0A]=32'h0800000D;// J 0D
assign Rom[5'h0B]=32'hXXXXXXXX;
assign Rom[5'h0C]=32'hXXXXXXXX;
assign Rom[5'h0D]=32'hAD02000A;// sw $2 10($8) memory[$8+10]=12
assign Rom[5'h0E]=32'h8D04000A;//lw $4 10($8) $4=12
assign Rom[5'h0F]=32'h10440003;//beq $2,$4,3
assign Rom[5'h10]=32'hXXXXXXXX;
assign Rom[5'h11]=32'hXXXXXXXX;
assign Rom[5'h12]=32'hXXXXXXXX;
assign Rom[5'h13]=32'h30470009;//andi $2,9,$7
assign Rom[5'h14]=32'hXXXXXXXX;
assign Rom[5'h15]=32'hXXXXXXXX;
assign Rom[5'h16]=32'hXXXXXXXX;
assign Rom[5'h17]=32'hXXXXXXXX;
assign Rom[5'h18]=32'hXXXXXXXX;
assign Rom[5'h19]=32'hXXXXXXXX;
assign Rom[5'h1A]=32'hXXXXXXXX;
assign Rom[5'h1B]=32'hXXXXXXXX;
assign Rom[5'h1C]=32'hXXXXXXXX;
assign Rom[5'h1D]=32'hXXXXXXXX;
assign Rom[5'h1E]=32'hXXXXXXXX;
assign Rom[5'h1F]=32'hXXXXXXXX;
assign Inst=Rom[Address[6:2]];
endmodule
寄存器堆Register_Files:
RegisterFile中定义了一个含有32个寄存器的寄存器组,设计逻辑包括两部分:一方面,读寄存器组为组合逻辑,当输入一个5位长的寄存器编号时,输出对应寄存器中的数值。另一方面,写寄存器组是时序逻辑,在时钟下降沿到来时触发写入。
module Register_Files (
input sys_clk,
input sys_rst_n,
input RegWrite,
input [4:0] ReadReg1,
input [4:0] ReadReg2,
input [4:0] WriteReg,
input [31:0] WriteData,
output [31:0] ReadData1,
output [31:0] ReadData2
);
reg [31:0] regfile [31:0];
integer i;
assign ReadData1 = regfile[ReadReg1];
assign ReadData2 = regfile[ReadReg2];
always @(posedge sys_clk or negedge sys_rst_n) begin
if(~sys_rst_n)begin
for(i = 0;i < 32;i = i + 1)
regfile[i] <= 0;
end
else if((RegWrite == 1) && (WriteReg != 0)) begin
regfile[WriteReg] <= WriteData;
end
end
endmodule
控制单元ControlUnit:
根据指令的操作码[31:26]位确定控制信号,其中
RegDst:写寄存器目标寄存器号是RT还是RD
ALUSrc:ALU第二个操作数来自读数readdata2还是16位扩充数据
PCsrc:由Branch与zero信号进行与操作下次PC地址是PC+4还是分支指令
ALUop:ALU计算单元的控制号
MemToreg:写入寄存器的数据来源是ALU结果还是存储器数据
对应参数如下:
操作码 | 指令 | RegDst | Branch | MemRead | MemToReg | ALUop | MemWrite | ALUSc | RegWrite |
0 | add | 1 | 0 | 0 | 0 | 10 | 0 | 0 | 1 |
sub | 1 | 0 | 0 | 0 | 10 | 0 | 0 | 0 | |
and | 1 | 0 | 0 | 0 | 10 | 0 | 0 | 0 | |
or | 1 | 0 | 0 | 0 | 10 | 0 | 0 | 0 | |
slt | 1 | 0 | 0 | 0 | 10 | 0 | 0 | 0 | |
35 | lw | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 1 |
43 | sw | 0 | 0 | 0 | x | 0 | 1 | 1 | 0 |
4 | beq | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 |
8 | addi | 0 | 0 | 0 | 0 | 00 | 0 | 1 | 1 |
13 | ori | 0 | 0 | 0 | 0 | 11 | 0 | 1 | 1 |
module ControlUnit (
input [5:0] opcode,
output reg RegDst,
output reg Branch,
output reg MemRead,
output reg MemToreg,
output reg [1:0] ALUop,
output reg MemWrite,
output reg ALUSrc,
output reg RegWrite
);
//根据操作码确定控制信号;
//RegDst:写寄存器目标寄存器号是RT还是RD
//ALUSrc:ALU第二个操作数来自读数readdata2还是16位扩充数据
//PCsrc:由Branch与zero信号进行与操作下次PC地址是PC+4还是分支指令
//ALUop:ALU计算单元的控制号
//MemToreg:写入寄存器的数据来源是ALU结果还是存储器数据
always @(opcode) begin
RegDst = 0;
Branch = 0;
MemRead = 0;
MemToreg = 0;
ALUop = 0;
MemWrite = 0;
ALUSrc = 0;
RegWrite = 0;
case(opcode)
6'd0: begin
RegDst = 1;
Branch = 0;
MemRead = 0 ;
MemToreg = 0 ;
ALUop = 2'b10 ;
MemWrite = 0 ;
ALUSrc = 0 ;
RegWrite = 1 ;
end //R型运算
6'd8: begin
RegDst = 0;
Branch = 0;
MemRead = 0 ;
MemToreg = 0 ;
ALUop = 2'b00 ;
MemWrite = 0 ;
ALUSrc = 1 ;
RegWrite = 1 ;
end //addi运算
6'd13: begin
RegDst = 0;
Branch = 0;
MemRead = 0 ;
MemToreg = 0 ;
ALUop = 2'b11 ;
MemWrite = 0 ;
ALUSrc = 1 ;
RegWrite = 1 ;
end //ori运算
6'd35: begin
RegDst = 0;
Branch = 0;
MemRead = 1 ;
MemToreg = 1 ;
ALUop = 2'b00 ;
MemWrite = 0 ;
ALUSrc = 1 ;
RegWrite = 1 ;
end //lw指令
6'd43: begin
RegDst = 0;
Branch = 0;
MemRead = 0 ;
MemToreg = 0 ;
ALUop = 2'b00 ;
MemWrite = 1 ;
ALUSrc = 1 ;
RegWrite = 0 ;
end //sw指令
6'd4: begin
RegDst = 0;
Branch = 1;
MemRead = 0 ;
MemToreg = 0 ;
ALUop = 2'b01 ;
MemWrite = 0 ;
ALUSrc = 0 ;
RegWrite = 0 ;
end //分支指令
endcase
end
endmodule
ALU控制单元:
该模块功能是根据ALUop以及指令的[5:0]功能位确定ALU进行的运算操作:
module ALUCtrl (
input [5:0] Inst5_0,
input [1:0] ALUop,
output reg [3:0] ALUopcode
);
//根据操作码及功能码确定ALU操作码
always @(Inst5_0 or ALUop) begin
ALUopcode = 4'b0000;
case(ALUop)
2'b00: ALUopcode = 4'b0010;//存取指令
2'b01: ALUopcode = 4'b0110;//分支指令
2'b10:begin //R型指令
case(Inst5_0)
6'b100_000:ALUopcode = 4'b0010;//加法
6'b100_010:ALUopcode = 4'b0110;//减法
6'b100_100:ALUopcode = 4'b0000;//与操作
6'b100_101:ALUopcode = 4'b0001;//或操作
6'b101_010:ALUopcode = 4'b0111;//小于则置位
endcase
end
2'b11:ALUopcode = 4'b0001;
endcase
end
endmodule
ALU运算单元:
根据上述ALU控制单元输出的控制信号确定当前进行的运算;
module ALU(
input [3:0] ALUopcode,
input [31:0] rega,
input [31:0] regb,
output reg [31:0] result,
output zero
);//根据ALU操作码,确定ALU运算
assign zero = (result == 0) ? 1 : 0;
always @(ALUopcode or rega or regb) begin
result = 32'b0;
case(ALUopcode)
4'b0000: result = rega & regb;//与操作
4'b0001: result = rega | regb;//或操作
4'b0010: result = rega + regb;//加法运算
4'b0110: result = rega - regb;//减法运算
4'b0111: result = (rega < regb) ? 1 : 0;//小于置位操作
4'b1100: result = ~(rega | regb) ;//或非操作
endcase
end
endmodule
数据存储器DataMem:
DataMemory中包含32x32的ram,设计逻辑包括两部分:一方面,读存储器为组合逻辑,当输入一个32位长的数据地址时,输出对应地址中的32位数据。另一方面,写存储器是时序逻辑,在时钟上升沿到来时触发写入。
module DataMeM (
input [31:0] MEM_Address,
input [31:0] Din,
input sys_clk,
input MemRead,
input MemWrite,
output [31:0] Dout
);
reg [31:0] Ram [31:0];
assign Dout = Ram[MEM_Address[6:2]];
always @(posedge sys_clk) begin
if(MemWrite)
Ram[MEM_Address[6:2]] <= Din;
end
integer i;
initial begin
for(i = 0;i < 32; i = i + 1)
Ram[i] = 0;
end
endmodule
除了上述几个模块之外,还有四个多选器,
muxAluSrc:确定ALU的其中一位输入(另一位输入是寄存器堆读数据1)是来自寄存器读数据2(R型指令)还是指令[15:0]经过扩展成32位的数据(I型指令);
muxPCSrc:下一指令地址是PC+4还是PC+4+偏移量(分支指令);
muxRegDst:写入寄存器号rs;1:写入寄存器号rd;
muxMemtoReg:将存储器输出或者ALU计算结果写入寄存器堆中;
上述多选器实现简单,使用三元运算符 ”?:" 即可实现;
顶层模块:
module single_cpu_top(
input sys_clk,
input sys_rst_n,
input [31:0] Address_in,
output [31:0] NowIns,//当前指令
output [31:0] next_PC,//下一条指令地址
output [31:0] DataMeM_Dout,//数据存储器输出数据
output [31:0] ALU_Dout//ALU运算结果输出
);
wire [31:0] Address;
PC PC_inst(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.Address_in(Address_in),
.Address(Address)
);
wire [31:0] Inst;
InstMeM InstMeM_inst(
.Address(Address),
.Inst(Inst)
);
assign NowIns = Inst;
wire RegDst;
wire Branch;
wire MemRead;
wire MemToreg;
wire [1:0] ALUop;
wire MemWrite;
wire ALUSrc;
wire RegWrite;
ControlUnit ControlUnit_inst(
.opcode(Inst[31:26]),
.RegDst(RegDst),
.Branch(Branch),
.MemRead(MemRead),
.MemToreg(MemToreg),
.ALUop(ALUop),
.MemWrite(MemWrite),
.ALUSrc(ALUSrc),
.RegWrite(RegWrite)
);
wire [31:0] ReadData1;
wire [31:0] ReadData2;
wire [4:0] muxRegDst_dout;
wire [31:0] muxMemtoReg_dout;
muxRegDst muxRegDst_inst(
.Inst20_16(Inst[20:16]),
.Inst15_11(Inst[15:11]),
.RegDst(RegDst),
.muxRegDst_dout(muxRegDst_dout)
);
Register_Files Register_Files_inst(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.RegWrite(RegWrite),
.ReadReg1(Inst[25:21]),
.ReadReg2(Inst[20:16]),
.WriteReg(muxRegDst_dout),
.WriteData(muxMemtoReg_dout),
.ReadData1(ReadData1),
.ReadData2(ReadData2)
);
wire [31:0] data_ex;
sign_ex sign_ex_inst(
.data_in(Inst[15:0]),
.data_ex(data_ex)
);
wire [31:0] muxAluSrc_dout;
muxAluSrc muxAluSrc_inst(
.ReadData2(ReadData2),
.data_ex(data_ex),
.ALUSrc(ALUSrc),
.muxAluSrc_dout(muxAluSrc_dout)
);
wire [3:0] ALUopcode;
ALUCtrl ALUCtrl_inst(
.Inst5_0(Inst[5:0]),
.ALUop(ALUop),
.ALUopcode(ALUopcode)
);
wire [31:0] result;
wire zero;
ALU ALU_inst(
.ALUopcode(ALUopcode),
.rega(ReadData1),
.regb(muxAluSrc_dout),
.result(result),
.zero(zero)
);
wire [31:0] Dout;
DataMeM DataMeM_inst(
.MEM_Address(result),
.Din(ReadData2),
.sys_clk(sys_clk),
.MemRead(MemRead),
.MemWrite(MemWrite),
.Dout(Dout)
);
muxMemtoReg muxMemtoReg_inst(
.Dout(Dout),
.result(result),
.MemToreg(MemToreg),
.muxMemtoReg_dout(muxMemtoReg_dout)
);
wire [31:0] Address_Add;
PC_Add4 PC_Add4_inst(
.Address(Address),
.Address_Add(Address_Add)
);
wire [31:0] data_ex_left2;
assign data_ex_left2 = data_ex << 2;
wire [31:0] PC_ALU;
PC_ADD_ADD PC_ADD_ADD_inst(
.Address_Add(Address_Add),
.data_ex(data_ex_left2),
.PC_ALU(PC_ALU)
);
assign PCSrc = Branch & zero;
wire [31:0] muxPCSrc_dout;
muxPCSrc muxPCSrc_inst(
.Address_Add(Address_Add),
.PC_ALU(PC_ALU),
.PCSrc(PCSrc),
.muxPCSrc_dout(muxPCSrc_dout)
);
assign next_PC = muxPCSrc_dout;
assign DataMeM_Dout = muxMemtoReg_dout;
assign ALU_Dout = result;
endmodule