源代码放在7指令CPU设计
1. 文件结构说明
- mips为顶层模块,负责调用其他模块。完成各模块后,在mips统一实例化。
- DataPath为数据通路,主要实现计数器、存储器、寄存器堆、算术逻辑单元(ALU),符号扩展单元等基本单元。
- Control为控制器,主要是实现多周期CPU中状态的转移,以及各状态中控制信号的选择。
1.1 数据通路
1.2 指令分类
- R型指令
助记符 | op | rs | rt | rd | shamt | funct |
---|---|---|---|---|---|---|
addu | 000000 | rs | rt | rd | 00000 | 100001 |
subu | 000000 | rs | rt | rd | 00000 | 100011 |
- I型指令
助记符 | op | rs | rt | immediate |
---|---|---|---|---|
ori | 001101 | rs | rt | immediate |
lw | 100011 | base | rt | offset |
sw | 101011 | base | rt | offset |
beq | 000100 | rs | rt | offset |
- J型指令
助记符 | jal | instr_index |
---|---|---|
jal | 000011 | instr_index |
2. 模块定义
2.1 flopr(异步复位触发器)
基本描述
32位的异步复位触发器,主要用于暂存数据。
模块接口
信号名 | 方向 | 描述 |
---|---|---|
clk | I | 时钟信号 |
rst | I | 复位信号 |
flopr_data_in | I | 数据输入 |
Wr | I | 写使能信号 |
flopr_data_out | O | 数据输出 |
模块代码
module flopr(
input clk,
input rst,
input [31:0] flopr_data_in,
input Wr,
output [31:0] flopr_data_out
);
reg [31:0]flopr;
always @(posedge clk,posedge rst) begin
if(rst==1'b1)flopr<=32'h0000_0000;
else if(Wr==1'b1)flopr<=flopr_data_in;
else flopr<=flopr;
end
assign flopr_data_out=flopr;
endmodule
2.2 dm(数据存储器)
基本描述
用于读取和写入数据,当写使能有效时,按照地址写入数据,当读有效时,按照地址读取出数据。
模块接口
信号名 | 方向 | 描述 |
---|---|---|
clk | I | 时钟信号 |
addr | I | 地址 |
din | I | 数据输入 |
DMWr | I | 写使能信号 |
dout | O | 数据输出 |
功能定义
序号 | 功能名称 | 功能描述 |
---|---|---|
1 | 读数据寄存器 | 输出地址对应的数据 |
2 | 写数据寄存器 | 将数据写入对应地址 |
模块代码
module dm_4k( addr, din, DMWr, clk, dout );
input [11:2] addr;
input [31:0] din;
input DMWr;
input clk;
output [31:0] dout;
reg [31:0] dmem[1023:0];
always @(posedge clk) begin
if (DMWr)
dmem[addr] <= din;
end // end always
assign dout = dmem[addr];
endmodule
2.3 im(指令存储器)
基本描述
用于读取指令,在本程序中,指令存储器通过直接读取文本文件获取所需要执行的指令。
模块接口
信号名 | 方向 | 描述 |
---|---|---|
addr | I | 地址 |
dout | O | 指令 |
功能定义
序号 | 功能名称 | 功能描述 |
---|---|---|
1 | 读指令寄存器 | 输出地址对应的指令 |
模块代码
module im_4k( addr, dout );
input [11:2] addr;
output [31:0] dout;
reg [31:0] imem[1023:0];
assign dout = imem[addr];
endmodule
2.4 RF(寄存器堆)
基本描述
寄存器堆用于实现MIPS指令集中的32个寄存器。
模块接口
信号名 | 方向 | 描述 |
---|---|---|
clk | I | 时钟信号 |
Ra | I | 读寄存器1的地址 |
Rb | I | 读寄存器2的地址 |
Rw | I | 写寄存器的地址 |
WD | I | 写寄存器的内容 |
RFWr | I | 写使能信号 |
RD1 | O | 读寄存器1的内容 |
RD2 | O | 读寄存器2的内容 |
功能定义
序号 | 功能名称 | 功能描述 |
---|---|---|
1 | 读寄存器 | 输出对应寄存器的内容 |
2 | 写寄存器 | 将数据写入对应的寄存器 |
模块代码
module RF(
input [4:0]Ra, //读寄存器1
input [4:0]Rb, //读寄存器2
input [4:0]Rw, //写寄存器1
input [31:0]WD, //写入数据
input RFWr, //写使能信号
input clk, //时钟信号
output reg [31:0] RD1, //读寄存器1读到的数据
output reg [31:0] RD2 //读寄存器2读到的数据
);
reg [31:0]RF[31:0]; //寄存器堆
always @(posedge clk) begin
if(RFWr)
RF[Rw] <= WD;
else
RF[Rw] <= RF[Rw];
end
always @(posedge clk) begin
RF[0] <= 32'b0;
end
always @(Ra,RF) begin
RD1 = RF[Ra];
end
always @(Rb,RF) begin
RD2 = RF[Rb];
end
endmodule
2.5 ALU(算术逻辑单元)
基本描述
在7指令中用到的运算只有加法、减法、和或运算。
所以需要一个2位宽的操作码Opcode
。
模块接口
信号名 | 方向 | 描述 |
---|---|---|
Operand_X | I | 操作数1 |
Operand_Y | I | 操作数2 |
Opcode | I | 操作码 |
Result | O | 计算结果 |
zero | O | 零信号,用于分支指令 |
功能定义
序号 | 功能名称 | 功能描述 |
---|---|---|
1 | 加法 | 两个操作数相加 |
2 | 减法 | 两个操作数相减 |
3 | 或 | 两个操作数相或 |
模块代码
module ALU(
input [31:0] Operand_X,
input [31:0] Operand_Y,
input [1:0]Opcode,
output reg[31:0] Result,
output reg zero
);
/*
Opcode 字段:
00: add
01: sub
10: or
*/
always @(Result) begin
if(Result == 32'b0)
zero = 1;
else
zero = 0;
end
always @(Opcode,Operand_X,Operand_Y) begin
case(Opcode)
2'b00: Result = Operand_X + Operand_Y;
2'b01: Result = Operand_X - Operand_Y;
2'b10: Result = Operand_X | Operand_Y;
endcase
end
endmodule
2.6 ALUControl(算术逻辑控制单元)
基本描述
根据指令选择需要的操作码字段,进而控制ALU的运算结果。
模块接口
信号名 | 方向 | 描述 |
---|---|---|
ALUop | I | 控制器产生控制ALU的信号 |
funct | I | 指令的funct字段 |
Opcode | O | 产生使ALU |
模块代码
module ALUControl(
input [2:0]ALUop,
input [5:0]funct,
output reg[1:0]Opcode
);
parameter addu = 6'b100001;
parameter subu = 6'b100011;
always @(ALUop,funct) begin
case(ALUop)
3'b000: Opcode = 2'b00;
3'b001: Opcode = 2'b01;
3'b010: Opcode = 2'b10;
default:
case(funct)
addu:Opcode = 2'b00;
subu:Opcode = 2'b01;
default:Opcode = 2'b10;
endcase
endcase
end
endmodule
2.7 EXT(扩展器)
基本描述
根据是否需要符号扩展,将16位数扩展为32位数。
模块接口
信号名 | 方向 | 描述 |
---|---|---|
Imm16 | I | 16位立即数 |
ExtOp | I | 符号扩展信号 |
Imm32 | O | 32位立即数 |
模块代码
module EXT(
input [15:0] Imm16,
input [1:0]ExtOp,
output reg [31:0] Imm32
);
always @(Imm16,ExtOp) begin
case(ExtOp)
2'b00:Imm32 = {{16{0}},Imm16};
2'b01:Imm32 = {{16{Imm16[15]}},Imm16};
default: Imm32 = {{16{0}},Imm16};
endcase
end
endmodule
2.8 NPC(下一条指令地址产生逻辑)
基本描述
根据本指令的类型,计算出下一条指令地址。
模块接口
信号名 | 方向 | 描述 |
---|---|---|
PC | I | 当前指令地址 |
Imm32 | I | 扩展器得到的32位数 |
Imm26 | I | target字段 |
branch | I | 分支指令 |
jal | I | 跳转指令 |
clk | I | 时钟信号 |
zero | I | 零信号 |
NPC | O | 下一条指令地址 |
模块代码
module NPC(
input [31:0] PC, //当前指令地址
input [31:0] Imm32, //由扩展器扩展得到的32位数
input [25:0] Imm26, //jal指令的target字段
input branch, //branch指令
input jal, //jal指令
input clk,
input zero, //ALU计算出的跳转指令,若zero不为1,则不跳转
output reg [31:0]NPC//NPC组合逻辑的输出,下一条指令的地址
);
reg [31:0]PC_4;
always@(PC)
begin
PC_4 = PC + 4; //当指令计数器改变时,立即计算出该指令加4的值
end
reg [31:0]temp1; //jal计算出的地址
reg [31:0]temp2; //branch计算出的地址
always@(PC,Imm26)
begin
temp1 = {PC[31:28],Imm26,2'b00};
end
always@(PC,Imm32)
begin
temp2 = PC + {Imm32[29:0],2'b00};
end
always@(temp1,temp2,zero,PC_4,branch,jal)
begin
if(branch && zero)
NPC = temp2;
else if(jal)
NPC = temp1;
else
NPC = PC_4;
end
endmodule
2.9 PC(程序计数器)
基本描述
用与更新当前指令的地址,当使能信号有效时,将下一条地址加载进来。
模块接口
信号名 | 方向 | 描述 |
---|---|---|
clk | I | 时钟信号 |
rst | I | 复位信号 |
PCWr | I | 写使能信号 |
NPC | I | 下一条指令地址 |
PC | O | 当前指令地址 |
模块代码
module PC(
input clk,
input rst,
input PCWr, //PC计数器的写使能信号
input [31:0] NPC, //下一条指令的地址
output reg [31:0] PC //当前指令地址
);
always@(posedge clk,posedge rst)
begin
if(rst)
begin
PC <= 32'h0000_3000;
end
else
begin
if(PCWr)
PC <= NPC;
else
PC <= PC;
end
end
endmodule
2.10 ctrl(控制器)
控制器是多周期CPU的核心部件,它规定了多周期CPU所处的状态以及状态转移情况,同时给出了不同状态时各个控制信号的值。
基本描述
控制状态转移以及信号的值。
模块接口
信号名 | 方向 | 描述 |
---|---|---|
clk | I | 时钟信号 |
rst | I | 复位信号 |
zero | I | 零信号 |
Opcode | I | 操作码 |
RegDst | O | 选择寄存器的数据来源 |
ALUOp | O | ALU的控制信号 |
IRWr | O | 指令寄存器的写使能信号 |
PCWr | O | 指令计数器的写使能信号 |
RFWr | O | 寄存器堆的写使能信号 |
ALUSrc | O | ALU第二个操作数的来源 |
RFDataSel | O | 寄存器堆的数据选择信号 |
branch | O | 分支信号 |
jal | O | 跳转信号 |
ExtOp | O | 拓展信号 |
DMWr | O | 数据存储器的写使能信号 |
功能定义
七条指令的状态转移图如图所示,其中lw指令是五个状态都要经历的,其余指令都在4个周期以内完成。
针对每个状态,选择相应的控制信号。
IF(Instruction Fetch):取指阶段只需要程序计数器计算出下一条指令的地址,故此时PCWr = 1。
ID(Instruction Decode):译码阶段jal指令需要跳转故需要计算出下一条指令的地址故PCWr = 1,jal = 1,RFWr = 1(此时需要将返回地址保存在第31号寄存器中),并且下一个阶段为IF,其余指令不需要进行任何操作,进入EXE。
EXE(Execute):执行阶段对于r型指令,控制其ALUop = 100,并进入写回阶段。对于beq指令需要将ALUop = 001(减法)并且branch = 1和PCWr
= 1。对于ori指令需要将ALUop = 010(或运算),并且将ALUSrc = 1(以扩展器得到的32位数作为对象)。其余为lw和sw指令,ALUSrc = 1.
MEM(Memory Access):进入MEM阶段的只有lw和sw指令,sw需要将DMWr = 1,进而将数据写入数据寄存器中,并进入IF阶段。lw指令需要直接进入WB阶段。
WB(Write Back):将数据写回到寄存器中,其中r型指令和ori指令选择ALU的计算结果写入,lw选择内存的访问结果写入。
模块代码
module ctrl(
input clk,
input rst,
input zero,
input [5:0]Opcode,
output reg[1:0]RegDst, //寄存器堆入口处的多路选择器
output reg[2:0]ALUOp, //ALU的操作码
output reg IRWr, //指令寄存器的写使能信号
output reg PCWr, //PC计数器的写使能信号
output reg RFWr, //寄存器堆的写使能信号
output reg ALUSrc, //ALU的选择信号
output reg [1:0]RFDataSel,
output reg branch, //分支信号
output reg jal, //跳转信号
output reg [1:0]ExtOp, //扩展信号
output reg DMWr, //数据寄存器的写使能信号
output reg [2:0]CurrentState
);
parameter IF=3'b000; //取指
parameter ID=3'b001; //译码
parameter EXE=3'b010; //执行
parameter MEM=3'b011; //读取
parameter WB=3'b100; //写回
parameter j = 6'b000011; //jal
parameter r = 6'b000000; //R型指令
parameter lw =6'b100011; //lw
parameter sw =6'b101011; //sw
parameter beq =6'b000100;//beq
parameter ori =6'b001101;//ori
//reg [2:0]CurrentState;
reg [2:0]NextState;
/*时序部分*/
always @(posedge clk,posedge rst) begin
if(rst)
CurrentState <= IF;
else
CurrentState <= NextState;
end
/*组合逻辑部分*/
always @(CurrentState,zero,Opcode) begin
case (CurrentState)
IF:begin
NextState = ID; //下一个状态调整为ID
PCWr = 1'b1; //在第一个状态就计算出下一条PC的值
IRWr = 1'b1;
branch = 1'b0;
jal = 1'b0;
RFWr = 1'b0;
DMWr = 1'b0;
end
ID:begin
case(Opcode)
j:begin
NextState = IF;
PCWr = 1'b1;
IRWr = 1'b0;
branch=1'b0;
jal=1'b1;
RFWr=1'b1;
DMWr=1'b0;
RegDst=2'b00;
RFDataSel=2'b00;
end
default:begin
NextState=EXE;
PCWr=1'b0;
IRWr=1'b0;
branch=1'b0;
jal=1'b0;
RFWr=1'b0;
DMWr=1'b0;
end
endcase
if(Opcode == 6'b001101)
ExtOp = 1'b0;
else
ExtOp = 1'b1;
end
EXE:begin
case(Opcode)
r:begin
NextState = WB;
PCWr = 1'b0;
IRWr = 1'b0;
branch = 1'b0;
jal = 1'b0;
RFWr = 1'b0;
ALUSrc = 1'b0;
DMWr = 1'b0;
ALUOp = 3'b100;
end
beq:begin
NextState = IF;
PCWr = 1'b1;
IRWr = 1'b0;
RFWr = 1'b0;
jal = 1'b0;
DMWr = 1'b0;
ALUOp = 3'b001;
branch = 1'b1;
ALUSrc=1'b0;
end
ori:begin
NextState = WB;
PCWr = 1'b0;
IRWr = 1'b0;
RFWr = 1'b0;
DMWr = 1'b0;
ALUOp = 3'b010;
branch = 1'b0;
jal = 1'b0;
ALUSrc=1'b1;
end
default:begin
NextState = MEM;
PCWr = 1'b0;
IRWr = 1'b0;
RFWr = 1'b0;
DMWr = 1'b0;
ALUOp = 3'b000;
branch = 1'b0;
jal = 1'b0;
ALUSrc=1'b1;
end
endcase
end
MEM:begin
case(Opcode)
lw:begin
NextState = WB;
PCWr = 1'b0;
IRWr = 1'b0;
RFWr = 1'b0;
DMWr = 1'b0;
branch = 1'b0;
jal = 1'b0;
end
sw:begin
NextState = IF;
PCWr = 1'b0;
IRWr = 1'b0;
RFWr = 1'b0;
DMWr = 1'b1;
branch = 1'b0;
jal = 1'b0;
end
endcase
end
WB:begin
case(Opcode)
r:begin
NextState=IF;
PCWr = 1'b0;
IRWr = 1'b0;
branch = 1'b0;
jal = 1'b0;
DMWr = 1'b0;
RFWr = 1'b1;
RFDataSel = 2'b01;
RegDst = 2'b01;
end
ori:begin
NextState=IF;
PCWr=1'b0;
IRWr=1'b0;
branch=1'b0;
jal=1'b0;
DMWr=1'b0;
RFWr=1'b1;
RFDataSel=2'b01;//选择ALU数据
RegDst=2'b11;
end
default:begin
NextState=IF;
PCWr=1'b0;
IRWr=1'b0;
branch=1'b0;
jal=1'b0;
DMWr=1'b0;
RFWr=1'b1;
RegDst=2'b10;
RFDataSel=2'b10;//选择从内存中读取的数据
end
endcase
end
endcase
end
endmodule
mips(顶层模块)
基本描述
对上述所有模块做实例化。
模块接口
信号名 | 方向 | 描述 |
---|---|---|
clk | I | 时钟信号 |
rst | I | 复位信号 |
模块代码
module mips(
input clk,
input rst
);
wire PCWr;
wire [31:0]PC;
wire [31:0]NPC;
wire IRWr;
PC U_PC(.clk(clk),.rst(rst),.PCWr(PCWr),.NPC(NPC),.PC(PC)); //实例化指令计数器
wire [31:0]im_dout;
im_4k U_IM (.addr(PC[11:2]) , .dout(im_dout)); //实例化指令存储器
wire [31:0]instr;
flopr U_IR(.clk(clk),.rst(rst),.flopr_data_in(im_dout),.flopr_data_out(instr),.Wr(IRWr));
/*使用一个寄存器保存从指令寄存器取到的结果instr*/
wire zero;
wire jal;
wire branch;
wire [31:0]Imm32;
wire [1:0] ExtOp;
wire [31:0] Result_r;
EXT U_EXT(.Imm16(instr[15:0]),.ExtOp(ExtOp),.Imm32(Imm32)); //实例化符号扩展器
NPC U_NPC(.PC(PC),.Imm32(Imm32),.Imm26(instr[25:0]),.branch(branch),.jal(jal),.clk(clk),.zero(zero),.NPC(NPC));
/*实例化下一条指令地址的计算单元*/
wire [1:0]RegDst;
wire [2:0]ALUop;
reg [4:0]reg1;
always @(instr,RegDst) begin
if(RegDst==2'b00)reg1=5'b11111;
else if(RegDst==2'b01)reg1=instr[15:11];
else reg1=instr[20:16];
end
wire RFWr;
wire[31:0] RD1;
wire[31:0] RD2;
reg [31:0] reg2;
wire[31:0] mem_data_out;
wire[31:0] Result;
wire [1:0]RFDataSel;
always @(mem_data_out,PC,Result_r,RFDataSel) begin
if(RFDataSel==2'b00) reg2 = PC;
else if(RFDataSel==2'b01)reg2 = Result_r;
else reg2=mem_data_out;
end
RF U_RF(
.Ra(instr[25:21]),.Rb(instr[20:16]),.Rw(reg1),.WD(reg2),
.RFWr(RFWr),.clk(clk),.RD1(RD1),.RD2(RD2)
);
wire[31:0] data1;
wire[31:0] data2;
flopr U_rfr1(
.clk(clk),.rst(rst),.flopr_data_in(RD1),
.Wr(1'b1),.flopr_data_out(data1)
);
flopr U_rfr2(
.clk(clk),.rst(rst),.flopr_data_in(RD2),
.Wr(1'b1),.flopr_data_out(data2)
);
wire ALUSrc;
reg [31:0]reg3;
always @(data2,Imm32,ALUSrc)begin
if(!ALUSrc)
reg3 = data2;
else
reg3 = Imm32;
end
wire[1:0] Opcode;
ALU U_ALU(.Operand_X(data1), .Operand_Y(reg3), .Opcode(Opcode),.Result(Result),.zero(zero));
ALUControl U_ALUControl(.ALUop(ALUop),.funct(instr[5:0]),.Opcode(Opcode));
flopr U_rfr3(.clk(clk),.rst(rst),.flopr_data_in(Result),.Wr(1'b1),.flopr_data_out(Result_r));
wire DMWr;
wire[2:0] State;
ctrl U_Control(
.clk(clk),.rst(rst),.zero(zero),.Opcode(instr[31:26]),
.RegDst(RegDst), .ALUOp(ALUop),.IRWr(IRWr),.PCWr(PCWr),.RFWr(RFWr),.ALUSrc(ALUSrc),
.RFDataSel(RFDataSel),.branch(branch),.jal(jal),.ExtOp(ExtOp),.DMWr(DMWr),.CurrentState(State)
);
wire[31:0] data3;
flopr U_rfr5(.clk(clk),.rst(rst),.flopr_data_in(data2),.Wr(1'b1),.flopr_data_out(data3));
wire [31:0]dm_out;
dm_4k U_DM (
.addr(Result_r[11:2]), .din(data3), .DMWr(DMWr), .clk(clk), .dout(dm_out)
);
flopr U_rfr4(
.clk(clk),.rst(rst),.flopr_data_in(dm_out),
.Wr(1'b1),.flopr_data_out(mem_data_out)
);
endmodule
mips_tb(测试模块)
基本描述
将文本文件中的指令读入到指令寄存器中,然后开始执行。
模块代码
module mips_tb();
reg clk, rst;
mips U_MIPS(
.clk(clk), .rst(rst)
);
integer i;
initial begin
$readmemh( "code.txt" , U_MIPS.U_IM.imem ) ;
$monitor("PC = 0x%8X, IR = 0x%8X", U_MIPS.U_PC.PC, U_MIPS.instr );
clk = 1 ;
rst = 0 ;
for (i = 0;i<=31 ;i=i+1 ) U_MIPS.U_RF.RF[i]=32'h0000_0000;
#5 ;
rst = 1 ;
#20 ;
rst = 0 ;
end
always
#(50) clk = ~clk;
endmodule
3. 仿真结果
用Mars汇编器查看每条指令的执行结果。
结合Modelsim的时序仿真图查看结果。
最终得到正确的计算结果,完成7指令CPU的验证。