验证过程
实现ori指令->建立最小SOPC->验证ori指令是否正确
ori指令说明
ori是进行逻辑“或”的运算指令
ori指令的指令码是6’b001101。处理器发现正在处理的指令的高6bit是6’b001101时,说明当时处理的指令是ori指令
根据格式可以得出这是一个I型指令,
指令的用法为ori rs,rt,immediate
作用为将指令中的16位立即数immediate进行无符号扩展至32位,然后与索引为rs的通用寄存器的值进行逻辑“或”运算,运算结果保存在索引为rt的通用寄存器中。
符号扩展和无符号扩展
符号扩展
将n位立即数的最高位复制到扩展后的32位数据的高(32-n)位
无符号扩展
将扩展后的32位数据的高(32-n)位都置为0
举例
以指令中16位立即数扩展位32位为例(加上一个知识点16进制转为2进制)
通用寄存器
MIPS指令集架构中定义了32个通用寄存器$0-$31,OpenMIPS实现了这32个通用寄存器,对于某一个通用寄存器只要给出相应索引,这个索引占用5bit,ori指令中rs,rt就是通用寄存器的索引
当rs=5’b00011,表示通用寄存器$3
流水线结构的建立
流水线的简单模型
寄存器按照给定的时间脉冲来进行时序同步操作,使时序逻辑电路具有记忆功能
组合逻辑电路由逻辑门组成,提供电路所有的逻辑功能,如果寄存器的输出端和输入端存在环路,这样的电路称为状态机,
如果寄存器之间有连接而无上述环路,这样的电路结构称为“流水线”
流水线结构中,信号在寄存器中传递,每传递到一级都会引起相应组合逻辑的变化,这种模型的抽象描述就是寄存器传输级
原始的OpenMIPS五级流水线结构
原始数据流图:
下图是为实现上述数据流图设计的五级流水线系统结构图(看不清)
一些宏定义
在OpenMIPS的实现过程中,为了提高代码的可读性和易懂性,使用了较多的宏,将所有文件定义在define.v中。
此处仅列举在ori指令编写中所需要的宏,随着OpenMIPS的功能不断完善,会有更多的宏添加进来,届时再说明及添加。
/************全局的宏定义***********/
`define RstEnable 1'b1
`define RstDisable 1'b0
`define ZeroWord 32'h00000000
`define WriteEnable 1'b1
`define WriteDisable 1'b0
`define ReadEnable 1'b1
`define ReadDisable 1'b0
`define AluOpBus 7:0
`define AluSelBus 2:0
`define InstValid 1'b0
`define InstInvalid 1'b1
`define Stop 1'b1
`define NoStop 1'b0
`define InDelaySlot 1'b1
`define NotInDelaySlot 1'b0
`define Branch 1'b1
`define NotBranch 1'b0
`define InterruptAssert 1'b1
`define InterruptNotAssert 1'b0
`define TrapAssert 1'b1
`define TrapNotAssert 1'b0
`define True_v 1'b1
`define False_v 1'b0
`define ChipEnable 1'b1
`define ChipDisable 1'b0
/*********与具体指令有关的宏定义*******/
`define EXE_ORI 6'b001101 //指令ori的指令码
`define EXE_NOP 6'b000000
//AluOp
`define EXE_OR_OP 8'b00100101
`define EXE_NOP_OP 8'b00000000
//AluSel
`define EXE_RES_LOGIC 3'b001
`define EXE_RES_NOP 3'b000
/******与指令存储器ROM相关的宏定义******/
`define InstAddrBus 31:0 //ROM的地址总线长度
`define InstBus 31:0 //ROM的数据总线长度
`define InstMemNum 131071 //ROM的实际大小128KB
`define InstMemNumLog2 17 //ROM实际使用地址线宽度
/******与通用寄存器Regfile有关的宏定义*****/
`define RegAddrBus 4:0 //Regfile模块地址线宽度
`define RegBus 31:0 //Regfile模块的数据线宽度
`define RegWidth 32 //通用寄存器宽度
`define DoubleRegWidth 64 //两倍的通用寄存器宽度
`define DoubleRegBus 63:0 //两倍的通用寄存器数据线宽度
`define RegNum 32 //通用寄存器数量
`define RegNumLog2 5 //寻址通用寄存器使用的地址位数
`define NOPRegAddr 5'b00000
取指阶段的实现
取指阶段取出指令存储器中的指令,同时PC值递增,准备下一条指令,包括PC、IF/ID两个模块
PC模块
作用:给出指令地址
接口描述
对应文件
pc_reg.v
代码
`include "define.v"
//PC模块:给出指令地址
module pc_reg(
input wire clk,//时钟信号
input wire rst,//复位信号
output reg[`InstAddrBus]pc,//[31:0]要读取的指令地址,指令地址线宽度为32
output reg ce//指令存储器使能信号
);
always @(posedge clk)
begin
if(rst==`RstEnable)//rst=1,复位信号有效,当输入rst为高电平时,复位信号有效
begin
ce<= `ChipDisable;//指令存储器禁用
end
else //rst=0
begin
ce<=`ChipEnable;//复位结束后,指令存储器使能
end
end
always @ (posedge clk)
begin
if(ce==`ChipDisable)//指令存储器禁用
begin
pc<=32'h00000000;//指令存储器禁用时,PC为0
end
else
begin
pc<=pc+4'h4;//指令存储器使能时,PC的值每时钟周期加4
end
end
endmodule
复位时,指令存储器禁用
其余时刻表示指令存储器使能
当指令存储器禁用时,PC为0
当指令存储器使用时,PC值在每时钟周期加4,表示下一条指令地址(一条指令32位,OpenMIPS按字节寻址,一条指令对应4字节,PC加4指向下一条地址)
IF/ID模块
作用:暂时保存取指阶段取得的指令,以及对应指令的地址,并在下一个时钟传递到译码阶段
接口描述
对应文件
if_id.v
代码
//IF/ID模块
`include "define.v"
module if_id(
input wire clk,
input wire rst,
input wire[`InstAddrBus] if_pc,
input wire[`InstBus] if_inst,
output reg[`InstAddrBus] id_pc,
output reg[`InstBus] id_inst
);
always @ (posedge clk) begin
if (rst == `RstEnable) begin
id_pc <= `ZeroWord;
id_inst <= `ZeroWord;
end else begin
id_pc <= if_pc;
id_inst <= if_inst;
end
end
endmodule
IF/ID模块只是简单地将取指阶段的结果在每个时钟周期上升沿传递到译码阶段
至此,以上的框架图如下
译码阶段的实现
1:Regfile模块
实现了32个32位通用寄存器,可以同时进行两个寄存器的读操作和一个寄存器的写操作
接口描述
================================================================================================
分割线:隔了一个星期,我看不懂了,我累了
================================================================================================
分割线 一个学期过去了,我来继续补这个。
回写阶段实际是在Regfile模块中实现的。MEM/WB模块输出wb_wreg,wb_wd,wb_wdata连接到Regfile模块,分别连接到写使能端口we,写操作目的寄存器端口waddr,写入数据端口wdata,所以会将指令的运算结果写入目的寄存器,具体代码可以参考Regfile模块。
代码文件
refile.v
代码:
`include "define.v"
//回写阶段的实现实际上是在Regfile模块中实现的
//Regfile模块 寄存器堆
module regfile(
input wire clk,//时钟信号
input wire rst,//复位信号,高电平有效
//写端口
input wire we,//写使能信号
input wire[`RegAddrBus] waddr,//要写入的寄存器地址
input wire[`RegBus] wdata,//要写入的数据
//读端口1
input wire re1,//第一个读寄存器端口读使能信号
input wire[`RegAddrBus] raddr1,//第一个读寄存器端口要读取的寄存器的地址
output reg[`RegBus] rdata1,//第一个读寄存器端口输出寄存器的值
//读端口2
input wire re2,//第二个读寄存器端口读使能信号
input wire[`RegAddrBus] raddr2,//第二个读寄存器端口要读取的寄存器地址
output reg[`RegBus] rdata2//第二个读寄存器端口要输出的寄存器的值
);
/*******第一段: 定义32个32位寄存器*******/
/*******第二段:写操作******************/
/*******第三段:读端口1的读操作********/
/*******第四段:读端口2的读操作*******/
//第一段:定义32个32位寄存器
reg[`RegBus] regs[0:`RegNum-1];
//第二段:实现写寄存器操作
always @ (posedge clk)
begin
if(rst==`RstDisable)//rst==1,复位信号无效
begin
if((we==`WriteEnable)&&(waddr!=`RegNumLog2'h0))//写使能信号we有效 且 写操作目的寄存器不等于0的情况下
begin//MIPS32架构规定 $0的值只能为0 所以不需要写入
regs[waddr]<=wdata;//将写输入数据保存到目的寄存器
end
end
end
//第三段:读端口1的读操作 实现第一个读寄存器端口
always @(*)
begin
if(rst==`RstEnable)//当复位信号有效时
begin
rdata1 <= `ZeroWord;//第一个读寄存器端口的输出始终为0
end
else if(raddr1==`RegNumLog2'h0)//当复位信号无效时 如果读取的是$0
begin
rdata1<=`ZeroWord;//直接给出0
end
else if((raddr1==waddr)&&(we==`WriteEnable)&&(re1==`ReadEnable))//如果第一个读寄存器端口要读取的目标寄存器与要写入的目的寄存器是同一个
begin
rdata1<=wdata;//直接将要写入的值作为第一个寄存器端口的输出
end
else if(re1==`ReadEnable)//上述情况都不满足时
begin
rdata1<=regs[raddr1];//给出第一个读寄存器端口要读取的目标寄存器地址对应寄存器的值
end
else//当第一个寄存器端口不能使用时
begin
rdata1<=`ZeroWord;//直接输出0
end
end
//第四段 读端口2的操作 实现第二个寄存器端口 具体过程和第三段相似 注意地址的变化
always @ (*)
begin
if(rst==`RstEnable)
begin
rdata2<=`ZeroWord;
end
else if(raddr2==`RegNumLog2'h0)
begin
rdata2<=`ZeroWord;
end
else if((raddr2==waddr)&&(we==`WriteEnable)&&(re2==`ReadEnable))
begin
rdata2<=wdata;
end
else if(re2==`ReadEnable)
begin
rdata2<=regs[raddr2];
end
else
begin
rdata2<=`ZeroWord;
end
end
endmodule
2:ID模块
对指令进行译码,得到最终运算的类型、子类型、源操作数1、源操作数2、要写入的目的寄存器地址等信息,其中运算类型指的是逻辑运算、移位运算、算术运算等,子类型指的是更加详细的运算类型
比如:当运算类型是逻辑运算时,运算子类型可以是逻辑运算“或”“与”“异或”运算等。
接口描述
代码文件
id.v
代码
`include "define.v"
module id(
input wire rst,
input wire[`InstAddrBus] pc_i,
input wire[`InstBus] inst_i,
input wire[`RegBus] reg1_data_i,
input wire[`RegBus] reg2_data_i,
//送到regfile的信息
output reg reg1_read_o,
output reg reg2_read_o,
output reg[`RegAddrBus] reg1_addr_o,
output reg[`RegAddrBus] reg2_addr_o,
//送到执行阶段的信息
output reg[`AluOpBus] aluop_o,
output reg[`AluSelBus] alusel_o,
output reg[`RegBus] reg1_o,
output reg[`RegBus] reg2_o,
output reg[`RegAddrBus] wd_o,
output reg wreg_o
//处于执行阶段的指令的运算结果
input wire ex_wreg_i,
input wire[`RegBus] ex_wdata_i,
input wire[`RegAddrBus] ex_wd_i,
//处于访存阶段的指令的运算结果
input wire mem_wreg_i,
input wire[`RegBus] mem_wdata_i,
input wire[`RegAddrBus] mem_wd_i
);
wire[5:0] op = inst_i[31:26];
wire[4:0] op2 = inst_i[10:6];
wire[5:0] op3 = inst_i[5:0];
wire[4:0] op4 = inst_i[20:16];
reg[`RegBus] imm;
reg instvalid;
always @ (*) begin
if (rst == `RstEnable) begin
aluop_o <= `EXE_NOP_OP;
alusel_o <= `EXE_RES_NOP;
wd_o <= `NOPRegAddr;
wreg_o <= `WriteDisable;
instvalid <= `InstValid;
reg1_read_o <= 1'b0;
reg2_read_o <= 1'b0;
reg1_addr_o <= `NOPRegAddr;
reg2_addr_o <= `NOPRegAddr;
imm <= 32'h0;
end else begin
aluop_o <= `EXE_NOP_OP;
alusel_o <= `EXE_RES_NOP;
wd_o <= inst_i[15:11];
wreg_o <= `WriteDisable;
instvalid <= `InstInvalid;
reg1_read_o <= 1'b0;
reg2_read_o <= 1'b0;
reg1_addr_o <= inst_i[25:21];
reg2_addr_o <= inst_i[20:16];
imm <= `ZeroWord;
case (op)
`EXE_ORI: begin //ORI指令
wreg_o <= `WriteEnable; aluop_o <= `EXE_OR_OP;
alusel_o <= `EXE_RES_LOGIC; reg1_read_o <= 1'b1; reg2_read_o <= 1'b0;
imm <= {16'h0, inst_i[15:0]}; wd_o <= inst_i[20:16];
instvalid <= `InstValid;
end
default: begin
end
endcase //case op
end //if
end //always
/*给reg1_o赋值过程增加了两种情况
1:如果Regfile模块读端口1要读取的寄存器就是执行阶段要写的目的寄存器,那么直接把执行阶段的结果ex_wdata_i作为reg1_o的值
2:如果Regfile模块读端口1要读取的寄存器就是访存阶段要写的目的寄存器,那么直接把访存阶段的结果mem_wdata_i作为reg1_o的值*/
always @ (*) begin
if(rst == `RstEnable) begin
reg1_o <= `ZeroWord;
end else if(reg1_read_o == 1'b1) begin
reg1_o <= reg1_data_i;
end else if(reg1_read_o == 1'b0) begin
reg1_o <= imm;
end else if((reg1_read_o == 1'b1)&&(ex_wreg_i == 1'b1)&&(ex_wd_i == reg1_addr_o)) begin
reg1_o <= ex_wdata_i;
end else if((reg1_read_o == 1'b1)&&(mem_wreg_i == 1'b1)&&(mem_wd_i == reg1_addr_o)) begin
reg1_o <= mem_wdata_i;
end else begin
reg1_o <= `ZeroWord;
end
end
always @ (*) begin
if(rst == `RstEnable) begin
reg2_o <= `ZeroWord;
end else if(reg2_read_o == 1'b1) begin
reg2_o <= reg2_data_i;
end else if(reg2_read_o == 1'b0) begin
reg2_o <= imm;
end else if((reg2_read_o == 1'b1)&&(ex_wreg_i == 1'b1)&&(ex_wd_i == reg2_addr_o)) begin
reg2_o <= ex_wdata_i;
end else if((reg2_read_o == 1'b1)&&(mem_wreg_i == 1'b1)&&(mem_wd_i == reg2_addr_o)) begin
reg2_o <= mem_wdata_i;
end else begin
reg2_o <= `ZeroWord;
end
end
endmodule
3:id_ex模块
代码
`include "define.v"
//将译码阶段取得的运算类型、源操作数、要写的目的寄存器等结果在下一个时钟递到流水线执行阶段
module id_ex(
input wire clk,//复位信号
input wire rst,//时钟信号
//从译码阶段传递过来的信息
input wire[`AluOpBus] id_aluop,//译码阶段指令要执行的运算的子类型
input wire[`AluSelBus] id_alusel,//译码阶段要执行的运算的类型
input wire[`RegBus] id_reg1,//译码阶段指令要进行的运算的源操作数1
input wire[`RegBus] id_reg2,//译码阶段指令要执行的运算的源操作数2
input wire[`RegAddrBus] id_wd,//译码阶段的指令要写入的目的寄存器的地址
input wire id_wreg,//译码阶段的指令是否有要写入的目的寄存器
//传递到执行阶段的信息
output reg[`AluOpBus] ex_aluop,//执行阶段的指令要进行的运算的子类型
output reg[`AluSelBus] ex_alusel,//执行阶段要进行的类型
output reg[`RegBus] ex_reg1,//执行阶段的指令要进行运算的源操作数1
output reg[`RegBus] ex_reg2,//执行阶段的指令要进行运算的源操作数2
output reg[`RegAddrBus] ex_wd,//执行阶段的指令要写入的目的寄存器地址
output reg ex_wreg //执行阶段的指令是否要写入的目的寄存器
);
always @(posedge clk)
begin
if(rst == `RstEnable)//复位有效
begin
ex_aluop <= `EXE_NOP_OP;
ex_alusel <= `EXE_RES_NOP;
ex_reg1 <= `ZeroWord;
ex_reg2 <= `ZeroWord;
ex_wd <= `NOPRegAddr;
ex_wreg <= `WriteDisable;
end
else
begin
ex_aluop <= id_aluop;
ex_alusel <= id_alusel;
ex_reg1 <= id_reg1;
ex_reg2 <= id_reg2;
ex_wd <= id_wd;
ex_wreg <= id_wreg;
end
end
endmodule
//在时钟周期的上升沿,将译码阶段的结果传递到执行阶段
执行阶段的实现
1:ex模块
代码:
`include "define.v"
//ex.v 执行模块
module ex(
//译码阶段送到执行阶段的信息
input wire[`AluOpBus] aluop_i,
input wire[`AluSelBus] alusel_i,
input wire[`RegBus] reg1_i,
input wire[`RegBus] reg2_i,
input wire[`RegAddrBus] wd_i,
input wire wreg_i,
input wire rst,
//执行的结果
output reg[`RegAddrBus] wd_o,
output reg wreg_o,
output reg[`RegBus] wdata_o
);
//保存逻辑运算的结果
reg[`RegBus] logicout;
/*******************************************************************
**第一段:依据aluop_i指示的运算子类型进行运算,此处只有逻辑“或”运算**
*******************************************************************/
always @(*)
begin//1
if(rst == `RstEnable)
begin//2
logicout <= `ZeroWord;
end//2
else
begin//3
case(aluop_i)//4
`EXE_OR_OP:begin//5
logicout <= reg1_i|reg2_i;
end//5
default:begin//6
logicout <= `ZeroWord;
end//6
endcase//4
end//3
end//1
/******************************************************************
**第二段:依据alusel_i指示的运算类型,选择一个运算结果作为最终结果**
********************此处只有逻辑运算结果***************************/
always @ (*)
begin//10
wd_o <= wd_i;//wd_o等于wd_i 为要写的目的寄存器地址
wreg_o <= wreg_i;//wreg_o等于wreg_i 表示是否要写目的寄存器
case(alusel_i)//9
`EXE_RES_LOGIC:begin//7
wdata_o <= logicout;//wdata_o中存放运算结果
end//7
default:begin//8
wdata_o <= `ZeroWord;
end//8
endcase//9
end//10
endmodule
2:ex_mem模块
代码:
`include "define.v"
//ex_mem.v 将执行阶段取得的运算结果 在下一个时钟传递到流水线访存阶段
module ex_mem(
input wire clk,//时钟信号
input wire rst,//复位信号
//来自执行阶段的信息
input wire[`RegAddrBus] ex_wd,//执行阶段指令执行后要写入的寄存器地址
input wire ex_wreg,//执行阶段指令执行后是否要写入的目的寄存器
inout wire[`RegBus] ex_wdata,//执行阶段的指令执行后要写入的目的寄存器的值
//送到访存阶段的信息
output reg[`RegAddrBus] mem_wd,//访存阶段的指令要写入的目的寄存器的地址
output reg mem_wreg,//访存阶段的指令是否有要写入的目的寄存器
output reg[`RegBus] mem_wdata//访存阶段的指令要写入的目的寄存器的值
);
always @ (posedge clk)//在时钟上升沿将执行阶段的结果传递到访存阶段
begin
if(rst==`RstEnable)
begin
mem_wd <= `NOPRegAddr;
mem_wreg <= `WriteDisable;
mem_wdata <= `ZeroWord;
end
else
begin
mem_wd <= ex_wd;
mem_wreg <= ex_wreg;
mem_wdata <= ex_wdata;
end
end
endmodule
访存阶段的实现
1:mem模块
`include "define.v"
//mem.v 访存
//将输入的执行阶段的结果直接作为输出
module mem(
input wire rst,
input wire clk,
//来自执行阶段的信息
input wire[`RegAddrBus] wd_i,
input wire wreg_i,
input wire wdata_i,
//访存阶段的结果
output reg[`RegAddrBus] wd_o,
output reg wreg_o,
output reg[`RegBus] wdata_o
);
always @ (posedge clk)
begin
if(rst == `RstEnable)
begin
wd_o <= `NOPRegAddr;
wreg_o <= `WriteEnable;
wdata_o <= `ZeroWord;
end
else
begin
wd_o <= wd_i;
wreg_o <= wreg_i;
wdata_o <= wdata_i;
end
end
endmodule
2:mem_wb模块
`include "define.v"
//mem_wb.v
//将访存阶段的运算结果在下一个时钟传递到回写阶段
module mem_wb(
input wire clk,
input wire rst,
//访存阶段的结果
input wire[`RegAddrBus] mem_wd,
input wire[`RegBus] mem_wdata,
input wire mem_wreg,
//送到回写阶段信息
output reg[`RegAddrBus] wb_wd,
output reg wb_wreg,
output reg[`RegBus] wb_wdata
);
always @ (posedge clk)
begin
if(rst==`RstEnable)
begin
wb_wd <= `NOPRegAddr;
wb_wreg <= `WriteDisable;
wb_wdata <= `ZeroWord;
end
else
begin
wb_wd <= mem_wd;
wb_wreg <= mem_wreg;
wb_wdata <= mem_wdata;
end
end
endmodule
//mem_wb代码与mem模块代码相似,将输入信号传递到对应的输出端口,mem_wb是时序电路
//在时钟上升沿才发生信号传递,而mem模块中的是组合逻辑电路
回写阶段的实现
参考regfile模块。
顶层模块OpenMIPS的实现
该模块是对上面实现的流水线各个阶段的模块进行例化、连接。
接口描述
序号 | 接口名 | 宽度(bit) | 输入/输出 | 作用 |
---|---|---|---|---|
1 | rst | 1 | 输入 | 复位信号 |
2 | clk | 1 | 输入 | 时钟信号 |
3 | rom_data_i | 32 | 输入 | 从指令存储器取得的指令 |
4 | rom_addr_o | 32 | 输出 | 输出到指令存储器的地址 |
5 | rom_ce_o | 1 | 输出 | 指令存储器使能信号 |
代码
`include "define.v"
//openmips.v
module openmips(
input wire clk,
input wire rst,
input wire[`RegBus] rom_data_i,
input wire[`RegBus] rom_addr_o,
output wire rom_ce_o
);
//连接IF/ID模块与译码阶段ID模块的变量
wire[`InstAddrBus] pc;
wire[`InstAddrBus] id_pc_i;
wire[`InstBus] id_inst_i;
//连接译码阶段ID模块输出与ID/EX模块的输入变量
wire[`AluOpBus] id_aluop_o;
wire[`AluSelBus] id_alusel_o;
wire[`RegBus] id_reg1_o;
wire[`RegBus] id_reg2_o;
wire id_wreg_o;
wire[`RegAddrBus] id_wd_o;
//连接ID/EX模块与执行阶段EX模块的变量
wire[`AluOpBus] ex_aluop_i;
wire[`AluSelBus] ex_alusel_i;
wire[`RegBus] ex_reg1_i;
wire[`RegBus] ex_reg2_i;
wire ex_wreg_i;
wire[`RegAddrBus] ex_wd_i;
//连接执行阶段EX模块输出与EX/MEM模块的输入变量
wire ex_wreg_o;
wire[`RegAddrBus] ex_wd_o;
wire[`RegBus] ex_wdata_o;
//连接EX/MEM模块与访存阶段MEM模块的变量
wire mem_wreg_i;
wire[`RegAddrBus] mem_wd_i;
wire[`RegBus] mem_wdata_i;
//连接访存阶段MEM模块的输出和MEM/WB模块的输入变量
wire mem_wreg_o;
wire[`RegAddrBus] mem_wd_o;
wire[`RegBus]mem_wdata_o;
//连接MEM/WB模块的输出和回写阶段的输入的变量
wire wb_wreg_i;
wire[`RegAddrBus] wb_wd_i;
wire[`RegBus] wb_wdata_i;
//连接译码阶段ID模块与通用寄存器Regfile模块的变量
wire reg1_read;
wire reg2_read;
wire[`RegBus] reg1_data;
wire[`RegBus] reg2_data;
wire[`RegAddrBus] reg1_addr;
wire[`RegAddrBus] reg2_addr;
//pc_reg??
pc_reg pc_reg0(
.clk(clk),
.rst(rst),
.pc(pc),
.ce(rom_ce_o)
);
assign rom_addr_o = pc;
//IF/ID????
if_id if_id0(
.clk(clk),
.rst(rst),
.if_pc(pc),
.if_inst(rom_data_i),
.id_pc(id_pc_i),
.id_inst(id_inst_i)
);
//????ID??
id id0(
.rst(rst),
.pc_i(id_pc_i),
.inst_i(id_inst_i),
.reg1_data_i(reg1_data),
.reg2_data_i(reg2_data),
//??regfile???
.reg1_read_o(reg1_read),
.reg2_read_o(reg2_read),
.reg1_addr_o(reg1_addr),
.reg2_addr_o(reg2_addr),
//??ID/EX?????
.aluop_o(id_aluop_o),
.alusel_o(id_alusel_o),
.reg1_o(id_reg1_o),
.reg2_o(id_reg2_o),
.wd_o(id_wd_o),
.wreg_o(id_wreg_o)
);
//?????Regfile??
regfile regfile1(
.clk (clk),
.rst (rst),
.we (wb_wreg_i),
.waddr (wb_wd_i),
.wdata (wb_wdata_i),
.re1 (reg1_read),
.raddr1 (reg1_addr),
.rdata1 (reg1_data),
.re2 (reg2_read),
.raddr2 (reg2_addr),
.rdata2 (reg2_data)
);
//ID/EX??
id_ex id_ex0(
.clk(clk),
.rst(rst),
//?????ID???????
.id_aluop(id_aluop_o),
.id_alusel(id_alusel_o),
.id_reg1(id_reg1_o),
.id_reg2(id_reg2_o),
.id_wd(id_wd_o),
.id_wreg(id_wreg_o),
//???????EX?????
.ex_aluop(ex_aluop_i),
.ex_alusel(ex_alusel_i),
.ex_reg1(ex_reg1_i),
.ex_reg2(ex_reg2_i),
.ex_wd(ex_wd_i),
.ex_wreg(ex_wreg_i)
);
//EX??
ex ex0(
.rst(rst),
//??????EX?????
.aluop_i(ex_aluop_i),
.alusel_i(ex_alusel_i),
.reg1_i(ex_reg1_i),
.reg2_i(ex_reg2_i),
.wd_i(ex_wd_i),
.wreg_i(ex_wreg_i),
//EX??????EX/MEM????
.wd_o(ex_wd_o),
.wreg_o(ex_wreg_o),
.wdata_o(ex_wdata_o)
);
//EX/MEM??
ex_mem ex_mem0(
.clk(clk),
.rst(rst),
//??????EX?????
.ex_wd(ex_wd_o),
.ex_wreg(ex_wreg_o),
.ex_wdata(ex_wdata_o),
//??????MEM?????
.mem_wd(mem_wd_i),
.mem_wreg(mem_wreg_i),
.mem_wdata(mem_wdata_i)
);
//MEM????
mem mem0(
.rst(rst),
//??EX/MEM?????
.wd_i(mem_wd_i),
.wreg_i(mem_wreg_i),
.wdata_i(mem_wdata_i),
//??MEM/WB?????
.wd_o(mem_wd_o),
.wreg_o(mem_wreg_o),
.wdata_o(mem_wdata_o)
);
//MEM/WB??
mem_wb mem_wb0(
.clk(clk),
.rst(rst),
//??????MEM?????
.mem_wd(mem_wd_o),
.mem_wreg(mem_wreg_o),
.mem_wdata(mem_wdata_o),
//?????????
.wb_wd(wb_wd_i),
.wb_wreg(wb_wreg_i),
.wb_wdata(wb_wdata_i)
);
endmodule
验证OpenMIPS实现效果
指令存储器ROM的实现
主要验证两个部分
1:流水线
2:ori指令
在验证之前,首先实现指令存储器,以便OpenMIPS从中读取指令。指令存储器ROM模块是只读的。
接口描述
序号 | 接口名 | 宽度(bit) | 输入/输出 | 作用 |
---|---|---|---|---|
1 | ce | 1 | 输入 | 使能信号 |
2 | addr | 32 | 输入 | 要读取的指令地址 |
3 | inst | 32 | 输出 | 读出的指令 |
代码
inst_rom.v
`include "define.v"
//inst_rom.v
module inst_rom(
input wire ce,
input wire[`InstAddrBus] addr,
output reg[`InstBus] inst
);
//定义一个数组,大小是InstMemNum,元素宽度是InstBus
reg[`InstBus] inst_mem[0:`InstMemNum-1];
//使用文件inst_rom.data 初始化指令存储器
initial $readmemh ("D:\cpu\AsmTest\inst_rom.data",inst_mem);
always @ (*)
begin
if(ce == `ChipDisable)//当复位信号无效时,依据输入的地址,给出指令存储器ROM中对应的元素
begin
inst <= `ZeroWord;
end
else begin
inst <= inst_mem[addr[`InstMemNumLog2+1:2]];
end
end
endmodule
(1)初始化指令存储器时,使用initial语句,initial语句只执行一次,通常用于仿真模块中对激励向量的描述,或用于给变量赋初值,是面向模拟仿真的过程语句。
(2)初始化指令存储器时,使用了系统函数$readmemh,从inst_rom_data文件中读取数据,初始化数组inst_mem。
inst_rom.data是一个文本文件,里面存储的是指令,每行存储一条32位宽度的指令,用16进制表示, $readmemh会将inst_rom.data中的数据依次填写到inst_mem数组中。
(3)OpenMIPS按字节寻址,此处定义的指令存储器的每个地址是一个32bit的字,所以将OpenMIPS给出的指令地址除以4再使用。
e.g:读取0xC处的指令 C在十进制中表示12 将12/4=3 实际对应的就是ROM的inst_mem[3]。
除以4也就是将指令地址右移2位,所以在读取的时候给出地址就是addr[`InstMemNumLog2+1:2],InstMemNumLog2指的是指令存储器的实际地址宽度。比如如果inst_mem有1024个元素,InstMemNum=1024 InstMemNumLog2=10 则实际地址宽度为10。
我不理解
最小SOPC的实现
为了验证建立一个SOPC,其中只包含OpenMIPS、指令存储器ROM,所以是一个最小SOPC。OpenMIPS从ROM中读取指令。指令进入OpenMIPS开始执行。
代码:
`include "define.v"
//openmips_min_sopc.v
module openmips_min_sopc(
input wire clk,
input wire rst
);
//连接指令存储器
wire[`InstAddrBus] inst_addr;
wire[`InstBus] inst;
wire rom_ce;
//例化处理器
openmips openmips0(
.clk(clk), .rst(rst),
.rom_addr_o(inst_addr), .rom_data_i(inst),
.rom_ce_o(rom_ce)
);
//例化指令存储器
inst_rom inst_rom0(
.ce(rom_ce), .inst(inst)
);
endmodule
测试
`include "define.v"
//建立TestBench文件
//时间单位是1ns,精度是1ps
`timescale 1ns/1ps
module openmips_min_sopc_tb();
reg CLOCK_50;
reg rst;
//每隔10ns,CLOCK_50信号翻转一次,所以下一个时钟周期是20ns,对应50MHz
initial begin
CLOCK_50 = 1'b0;
forever #10 CLOCK_50 = ~CLOCK_50;
end
//最初时刻,复位信号有效,在第195ns复位信号无效 最小SOPC开始运行
//运行1000ns后,暂停仿真
initial begin
rst = `RstEnable;
#195 rst = `RstDisable;
#1000 $stop;
end
//例化最小SOPC
openmips_min_sopc openmips_min_sopc0(
.clk(CLOCK_50),
.rst(rst)
);
endmodule
写在最后
1:这些代码全部来自《自己动手写cpu》这本书。
2:这些代码在编译上没有问题(id模块加了数据前推,openmips模块没有加),但是没有通过仿真,大概是我还没有弄透怎么仿真,但是我又能看懂书上和别人正确的仿真。代码写一天半,仿真弄两天也没弄出来,拿书上的源代码还是不行。只能等之后慢慢试了。
3:第一次看这一部分是九月份,这个博客开头也是九月份。。。一晃已经过去四个多月了,该忘的不该忘的全忘了,再看着一部分比之前又明白了一点,许是我太笨了,还是没有完完全全记住每一个端口,每一个信号,时间原因只能通过看后面的章节再慢慢理解了。