自己动手写处理器之第四阶段(1)——第一条指令ori的实现
将陆续上传本人写的新书《自己动手写处理器》(尚未出版),今天是第11篇,我尽量每周四篇
第4章 第一条指令ori的实现
前面几章介绍了非常多预备知识,也描绘了即将要实现的OpenMIPS处理器的蓝图,各位读者是不是早已摩拳擦掌,迫切希望一展身手了,好吧,本章我们将实现OpenMIPS处理器的第一条指令ori,为什么选择这条指令作为我们实现的第一条指令呢?答案就两个字——简单。指令ori用来实现逻辑“或”运算,选择一条简单的指令有助于我们排除干扰。将注意力集中在流水线结构的实现上。当然也能够选择其他类似的指令。仅仅要简单就可以。通过这条简单指令的实现,本章在4.2节将初步建立OpenMIPS的五级流水线结构。当我们在后面章节中实现其余指令的时候,都是在这个初步建立的流水线结构上进行扩充。
在ori指令实现后,要验证事实上现是否正确。所以在4.3节建立了最小SOPC。只包括OpenMIPS、指令存储器,用于验证ori指令是否实现正确,兴许章节验证其余指令的时候,都是在这个最小SOPC或者其改进模型上进行验证。
本章最后介绍了MIPS编译环境的建立。
4.1 ori指令说明
ori是进行逻辑“或”运算的指令,其指令格式如图4-1所看到的。
从指令格式中能够知道,这是一个I类型的指令。ori指令的指令码是6'b001101,所以当处理器发现正在处理的指令的高6bit是6'b001101时,就知道当前正在处理的是ori指令。
指令使用方法为:ori rs, rt, immediate。作用是将指令中的16位马上数immediate进行无符号扩展至32位,然后与索引为rs的通用寄存器的值进行逻辑“或”运算。运算结果保存到索引为rt的通用寄存器中。这里须要说明下面两点。
(1)无符号扩展
在MIPS32指令集架构中,常常会有指令须要将当中的马上数进行符号扩展。或者无符号扩展,一般都是将n位马上数扩展为32位,当中。符号扩展是将n位马上数的最高位拷贝到扩展后的32位数据的高(32-n)位,无符号扩展则是将扩展后的32位数据的高(32-n)位都置为0。
以将指令中的16位马上数扩展为32位为例,表4-1给出了当16位马上数各自是0x8000、0x1000时的符号扩展、无符号扩展的结果。
(2)通用寄存器
在MIPS32指令集架构中定义了32个通用寄存器$0-$31,OpenMIPS实现了这32个通用寄存器,使用某一个通用寄存器仅仅须要给出对应索引,这个索引占用5bit。ori指令中的rs、rt就是通用寄存器的索引。比如:当rs为5'b00011时,就表示通用寄存器$3。
4.2 流水线结构的建立
4.2.1 流水线的简单模型
数字电路有组合逻辑、时序逻辑之分,当中时序逻辑最主要的器件是寄存器,此处的寄存器不是在4.1节中提到的MIPS架构规定的通用寄存器$0-$31,后者是一个更高层面的概念。前者是类似于D触发器这种数字电路的基本器件。寄存器依照给定时间脉冲来进行时序同步操作,其使得时序逻辑电路具有记忆功能。而组合逻辑电路则由逻辑门组成,提供电路的全部逻辑功能。实际的数字电路通常是组合逻辑与时序逻辑的结合。
假设寄存器的输出端和输入端存在环路,这种电路称为“状态机”。
如图4-2所看到的。
假设寄存器之间有连接。而没有上述环路。这种电路结构称为“流水线”。
假设4-3所看到的。
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbGVpc2hhbmd3ZW4=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">
在流水线结构中。信号在寄存器之间传递,每传递到一级都会引起对应的组合逻辑电路变化。对这样的模型进行抽象描写叙述就是寄存器传输级(RTL:Register Transfer Level)。本节接下来要实现的原始的OpenMIPS五级流水线结构就是图4-3的扩充。
4.2.2 原始的OpenMIPS五级流水线结构
扩充图4-3。能够得到OpenMIPS的原始数据流图如图4-4所看到的,这个数据流图还非常不完整,在兴许章节中会随着实现指令的添加而丰富,但这个原始的数据流图已经能够表达本节要实现的ori指令在流水线中的处理过程了。
图中深色部分相应的是图4-3中的D触发器。深色部分之间的部分相应的是图4-3中的组合逻辑。各个阶段完毕的主要工作例如以下。
- 取指:取出指令存储器中的指令。PC值递增,准备取下一条指令。
- 译码:对指令进行译码,根据译码结果。从32个通用寄存器中取出源操作数,有的指令要求两个源操作数都是寄存器的值,比方or指令,有的指令要求当中一个源操作数是指令中马上数的扩展,比方ori指令,所以这里有两个复用器,用于根据指令要求,确定參与运算的操作数。终于确定的两个操作数会送到运行阶段。
- 运行阶段:根据译码阶段送入的源操作数、操作码,进行运算。对于ori指令而言。就是进行逻辑“或”运算,运算结果传递到訪存阶段。
- 訪存阶段:对于ori指令。在訪存阶段没有不论什么操作,直接将运算结果向下传递到回写阶段。
- 回写阶段:将运算结果保存到目的寄存器。
图4-5是为实现上述数据流图而设计的OpenMIPS系统结构。图中显示了各个模块的接口、连接关系。每一个模块上方是模块名,下方是相应的Verilog HDL程序文件名称。
本节接下来将分别实现图中各个模块。
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbGVpc2hhbmd3ZW4=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">
4.2.3 一些宏定义
在正式開始介绍流水线结构实现之前,须要给出一些宏定义。由于在OpenMIPS的实现过程中,为了提高代码的可读性和易懂性,使用了较多的宏。所有的宏都在文件defines.v中定义。此处列举在本章中会使用到的一部分宏。后面随着OpenMIPS功能的不断完好。会有很多其它的宏加入进来,届时会对新添加的宏进行说明。
- //******************* 全局的宏定义 ***************************
- `define RstEnable 1'b1 //复位信号有效
- `define RstDisable 1'b0 //复位信号无效
- `define ZeroWord 32'h00000000 //32位的数值0
- `define WriteEnable 1'b1 //使能写
- `define WriteDisable 1'b0 //禁止写
- `define ReadEnable 1'b1 //使能读
- `define ReadDisable 1'b0 //禁止读
- `define AluOpBus 7:0 //译码阶段的输出aluop_o的宽度
- `define AluSelBus 2:0 //译码阶段的输出alusel_o的宽度
- `define InstValid 1'b0 //指令有效
- `define InstInvalid 1'b1 //指令无效
- `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
4.2.4 取指阶段的实现
取指阶段取出指令存储器中的指令。同一时候。PC值递增,准备取下一条指令,包含PC、IF/ID两个模块。
1、PC模块
PC模块的作用是给出指令地址,其接口描写叙述如表4-2所看到的。
PC模块相应的源文件是pc_reg.v,代码例如以下,能够在本书附带光盘的Code\Chapter4\文件夹下找到源文件。读者能够使用不论什么文本编辑工具编辑该文件。笔者习惯使用UltraEdit,全部的代码都是使用它编辑的。当然也能够使用Windows自带的记事本。
- module pc_reg(
- input wire clk,
- input wire rst,
- output reg[`InstAddrBus] pc
- );
- always @ (posedge clk) begin
- if (rst == `RstEnable) begin
- ce <= `ChipDisable; // 复位的时候指令存储器禁用
- end else 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
当中使用到了一些define.v中定义的宏,InstAddrBus宏表示指令地址线的宽度,此处定义为32,RstEnable宏表示复位信号有效,定义为1'b1,也就是当输入rst为高电平时,表示复位信号有效。
在复位的时候,输出的指令存储器使能信号为ChipDisable,表示指令存储器禁用,其余时刻指令存储器使能信号为ChipEnable,表示指令存储器使能。
当指令存储器禁用时。PC的值保持为0。当指令存储器使能时。PC的值会在每时钟周期加4,表示下一条指令的地址。由于一条指令是32位。而我们设计的OpenMIPS是能够依照字节寻址,一条指令相应4个字节。所以PC加4指向下一条指令地址。
读者须要注意区分:在2.7节设计的简单取指电路是依照字寻址的,所以每时钟周期PC加1。
2、IF/ID模块
IF/ID模块的作用是临时保存取指阶段取得的指令,以及相应的指令地址,并在下一个时钟传递到译码阶段。
其接口描写叙述如表4-3所看到的。
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbGVpc2hhbmd3ZW4=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">
IF/ID模块相应的源码文件是if_id.v,代码例如以下,读者能够在本书附带光盘的Code\Chapter4\文件夹下找到源文件。
- module if_id(
- input wire clk,
- input wire rst,
- //来自取指阶段的信号,当中宏定义InstBus表示指令宽度,为32
- 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; // 复位的时候pc为0
- id_inst <= `ZeroWord; // 复位的时候指令也为0,实际就是空指令
- end else begin
- id_pc <= if_pc; // 其余时刻向下传递取指阶段的值
- id_inst <= if_inst;
- end
- end
- endmodule
从代码能够知道,当中仅仅有一个时序电路,IF/ID模块仅仅是简单地将取指阶段的结果在每一个时钟周期的上升沿传递到译码阶段。
4.2.5 译码阶段的实现
參考图4-5可知。IF/ID模块的输出连接到ID模块,好了。我们的指令此时已经进入了译码阶段,在此阶段,将对取到的指令进行译码:给出要进行的运算类型,以及參与运算的操作数。译码阶段包含Regfile、ID和ID/EX三个模块。
1、Regfile模块
Regfile模块实现了32个32位通用整数寄存器,能够同一时候进行两个寄存器的读操作和一个寄存器的写操作。其接口描写叙述如表4-4所看到的。
Regfile模块相应的源码文件是regfile.v。代码例如以下,能够在本书附带光盘的Code\Chapter4\文件夹下找到regfile.v文件。
- module regfile(
- input wire clk,
- input wire rst,
- // 写port
- input wire we,
- input wire[`RegAddrBus] waddr,
- input wire[`RegBus] wdata,
- // 读port1
- input wire re1,
- input wire[`RegAddrBus] raddr1,
- output reg[`RegBus] rdata1,
- // 读port2
- input wire re2,
- input wire[`RegAddrBus] raddr2,
- output reg[`RegBus] rdata2
- );
- /****************************************************************
- *********** 第一段:定义32个32位寄存器 *********
- *****************************************************************/
- reg[`RegBus] regs[0:`RegNum-1];
- /****************************************************************
- *********** 第二段:写操作 *********
- *****************************************************************/
- always @ (posedge clk) begin
- if (rst == `RstDisable) begin
- if((we == `WriteEnable) && (waddr != `RegNumLog2'h0)) begin
- regs[waddr] <= wdata;
- end
- end
- end
- /****************************************************************
- *********** 第三段:读port1的读操作 *********
- *****************************************************************/
- always @ (*) begin
- if(rst == `RstEnable) begin
- rdata1 <= `ZeroWord;
- end else if(raddr1 == `RegNumLog2'h0) begin
- rdata1 <= `ZeroWord;
- 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;
- end
- end
- /****************************************************************
- *********** 第四段:读port2的读操作 *********
- *****************************************************************/
- 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
Regfile模块能够分为四段进行理解。
(1)第一段:定义了一个二维的向量。元素个数是RegNum,这是在defines.v中的一个宏定义,为32,每一个元素的宽度是RegBus,这也是在defines.v中的一个宏定义,也为32,所以此处定义的就是32个32位寄存器。
(2)第二段:实现了写寄存器操作,当复位信号无效时(rst为RstDisable),在写使能信号we有效(we为WriteEnable),且写操作目的寄存器不等于0的情况下,能够将写输入数据保存到目的寄存器。
之所以要推断目的寄存器不为0,是由于MIPS32架构规定$0的值仅仅能为0,所以不要写入。WriteEnable是defines.v中定义的宏,表示写使能信号有效,这些宏定义的含义十分明显,从名称上就能够知道详细含义。所以本书后面对宏定义不再作出说明,除非这个宏定义的含义从名称上不易明确。
(3)第三段:实现了第一个读寄存器port,分下面几步依次推断。
- 当复位信号有效时。第一个读寄存器port的输出始终为0
- 当复位信号无效时,假设读取的是$0,那么直接给出0
- 假设第一个读寄存器port要读取的目标寄存器与要写入的目的寄存器是同一个寄存器。那么直接将要写入的值作为第一个读寄存器port的输出
- 上述情况都不满足。那么给出第一个读寄存器port要读取的目标寄存器地址相应寄存器的值
- 第一个读寄存器port没有使能时,直接输出0
(4)第四段:实现了第二个读寄存器port,详细过程与第三段是相似的,不再反复解释。
注意一点:读寄存器操作是组合逻辑电路,也就是一旦输入的要读取的寄存器地址raddr1或者raddr2发生变化,那么会马上给出新地址相应的寄存器的值,这样能够保证在译码阶段取得要读取的寄存器的值,而写寄存器操作是时序逻辑电路,写操作发生在时钟信号的上升沿。
2、ID模块
ID模块的作用是对指令进行译码,得到终于运算的类型、子类型、源操作数1、源操作数2、要写入的目的寄存器地址等信息,当中运算类型指的是逻辑运算、移位运算、算术运算等。子类型指的是更加具体的运算类型。比方:当运算类型是逻辑运算时,运算子类型能够是逻辑“或”运算、逻辑“与”运算、逻辑“异或”运算等。ID模块的接口描写叙述如表4-5所看到的。
ID模块相应的代码文件是id.v。其内容例如以下,能够在本书附带光盘的Code\Chapter4\文件夹下找到源文件。
- module id(
- input wire rst,
- input wire[`InstAddrBus] pc_i,
- input wire[`InstBus] inst_i,
- // 读取的Regfile的值
- 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
- );
- // 取得指令的指令码,功能码
- // 对于ori指令仅仅需通过推断第26-31bit的值,就可以推断是否是ori指令
- 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]; // 默认通过Regfile读port1读取的寄存器地址
- reg2_addr_o <= inst_i[20:16]; // 默认通过Regfile读port2读取的寄存器地址
- imm <= `ZeroWord;
- case (op)
- `EXE_ORI: begin // 根据op的值推断是否是ori指令
- // ori指令须要将结果写入目的寄存器。所以wreg_o为WriteEnable
- wreg_o <= `WriteEnable;
- // 运算的子类型是逻辑“或”运算
- aluop_o <= `EXE_OR_OP;
- // 运算类型是逻辑运算
- alusel_o <= `EXE_RES_LOGIC;
- // 须要通过Regfile的读port1读取寄存器
- reg1_read_o <= 1'b1;
- // 不须要通过Regfile的读port2读取寄存器
- reg2_read_o <= 1'b0;
- // 指令运行须要的马上数
- imm <= {16'h0, inst_i[15:0]};
- // 指令运行要写的目的寄存器地址
- wd_o <= inst_i[20:16];
- // ori指令是有效指令
- instvalid <= `InstValid;
- end
- default: begin
- end
- endcase //case op
- end //if
- end //always
- /****************************************************************
- *********** 第二段:确定进行运算的源操作数1 *********
- *****************************************************************/
- always @ (*) begin
- if(rst == `RstEnable) begin
- reg1_o <= `ZeroWord;
- end else if(reg1_read_o == 1'b1) begin
- reg1_o <= reg1_data_i; // Regfile读port1的输出值
- end else if(reg1_read_o == 1'b0) begin
- reg1_o <= imm; // 马上数
- end else begin
- reg1_o <= `ZeroWord;
- end
- end
- /****************************************************************
- *********** 第三段:确定进行运算的源操作数2 *********
- *****************************************************************/
- always @ (*) begin
- if(rst == `RstEnable) begin
- reg2_o <= `ZeroWord;
- end else if(reg2_read_o == 1'b1) begin
- reg2_o <= reg2_data_i; // Regfile读port2的输出值
- end else if(reg2_read_o == 1'b0) begin
- reg2_o <= imm; // 马上数
- end else begin
- reg2_o <= `ZeroWord;
- end
- end
- endmodule
ID模块中的电路都是组合逻辑电路,另外。从图4-5可知ID模块与Regfile模块也有接口连接。其代码能够分为三段进行理解。
(1)第一段:实现了对指令的译码。根据指令中的特征字段区分指令,对指令ori而言,仅仅需通过识别26-31bit的指令码是否是6'b001101。就可以推断是否是ori指令,当中的宏定义EXE_ORI就是6'b001101,op就是指令的26-31bit。所以当op等于EXE_ORI时,就表示是ori指令。此时会有下面译码结果。
- 要读取的寄存器情况:ori指令仅仅须要读取rs寄存器的值。默认通过Regfile读port1读取的寄存器地址reg1_addr_o的值是指令的21-25bit。參考图4-1可知。正是ori指令中的rs,所以设置reg1_read_o为1。通过图4-5能够reg1_read_o连接Regfile的输入re1,reg1_addr_o连接Regfile的输入raddr1,结合对Regfile模块的介绍可知,译码阶段会读取寄存器rs的值。
指令ori须要的还有一个操作数是马上数,所以设置reg2_read_o为0,表示不通过Regfile读port2读取寄存器,这里暗含使用马上数作为运算的操作数。imm就是指令中的马上数进行零扩展后的值。
- 要运行的运算:alusel_o给出要运行的运算类型,对于ori指令而言就是逻辑操作,即EXE_RES_LOGIC。aluop_o给出要运行的运算子类型,对于ori指令而言就是逻辑“或”运算,即EXE_OR_OP。这两个值会传递到运行阶段。
- 要写入的目的寄存器:wreg_o表示是否要写目的寄存器,ori指令要将计算结果保存到寄存器中,所以wreg_o设置为WriteEnable。wd_o是要写入的目的寄存器地址,此时就是指令的16-20bit,參考图4-1可知,正是ori指令中的rt。
这两个值也会传递到运行阶段。
(2)第二段:给出參与运算的源操作数1的值,假设reg1_read_o为1,那么就将从Regfile模块读port1读取的寄存器的值作为源操作数1,假设reg1_read_o为0,那么就将马上数作为源操作数1,对于ori而言,此处选择从Regfile模块读port1读取的寄存器的值作为源操作数1。该值将通过reg1_oport被传递到运行阶段。
(3)第三段:给出參与运算的源操作数2的值,假设reg2_read_o为1,那么就将从Regfile模块读port2读取的寄存器的值作为源操作数2,假设reg2_read_o为0,那么就将马上数作为源操作数2。对于ori而言,此处选择马上数imm作为源操作数2。
该值将通过reg2_oport被传递到运行阶段。
3、ID/EX模块
參考图4-5可知,ID模块的输出连接到ID/EX模块。后者的作用是将译码阶段取得的运算类型、源操作数、要写的目的寄存器地址等结果,在下一个时钟传递到流水线运行阶段。其接口描写叙述如表4-6所看到的。
ID/EX模块相应的代码文件是id_ex.v,其内容例如以下。能够在本书附带光盘的Code\Chapter4\文件夹下找到源文件。
- module id_ex(
- input wire clk,
- input wire rst,
- // 从译码阶段传递过来的信息
- input wire[`AluOpBus] id_aluop,
- input wire[`AluSelBus] id_alusel,
- input wire[`RegBus] id_reg1,
- input wire[`RegBus] id_reg2,
- input wire[`RegAddrBus] id_wd,
- input wire id_wreg,
- // 传递到运行阶段的信息
- output reg[`AluOpBus] ex_aluop,
- output reg[`AluSelBus] ex_alusel,
- output reg[`RegBus] ex_reg1,
- output reg[`RegBus] ex_reg2,
- 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
代码十分清晰。当中仅仅有一个时序电路,ID/EX模块仅仅是简单地将译码阶段的结果在时钟周期的上升沿传递到运行阶段。
运行阶段将根据这些值进行运算。
4.2.6 运行阶段的实现
如今,指令已经进入流水线的运行阶段了,在此阶段将根据译码阶段的结果,对源操作数1、源操作数2,进行指定的运算。运行阶段包含EX、EX/MEM两个模块。
1、EX模块
观察图4-5中ID/EX与EX模块的port连接关系可知,EX模块会从ID/EX模块得到运算类型alusel_i、运算子类型aluop_i、源操作数reg1_i、源操作数reg2_i、要写的目的寄存器地址wd_i。EX模块会根据这些数据进行运算,其接口描写叙述如表4-7所看到的。
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbGVpc2hhbmd3ZW4=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">
EX模块相应的代码文件为ex.v。其内容例如以下,能够在本书附带光盘的Code\Chapter4\文件夹下找到源文件。
- module ex(
- input wire rst,
- // 译码阶段送到运行阶段的信息
- 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,
- // 运行的结果
- output reg[`RegAddrBus] wd_o,
- output reg wreg_o,
- output reg[`RegBus] wdata_o
- );
- // 保存逻辑运算的结果
- reg[`RegBus] logicout;
- /******************************************************************
- ** 第一段:根据aluop_i指示的运算子类型进行运算,此处仅仅有逻辑“或”运算 **
- *******************************************************************/
- always @ (*) begin
- if(rst == `RstEnable) begin
- logicout <= `ZeroWord;
- end else begin
- case (aluop_i)
- `EXE_OR_OP: begin
- logicout <= reg1_i | reg2_i;
- end
- default: begin
- logicout <= `ZeroWord;
- end
- endcase
- end //if
- end //always
- /****************************************************************
- ** 第二段:根据alusel_i指示的运算类型,选择一个运算结果作为终于结果 **
- ** 此处仅仅有逻辑运算结果 **
- *****************************************************************/
- always @ (*) begin
- wd_o <= wd_i; // wd_o等于wd_i,要写的目的寄存器地址
- wreg_o <= wreg_i; // wreg_o等于wreg_i。表示是否要写目的寄存器
- case ( alusel_i )
- `EXE_RES_LOGIC: begin
- wdata_o <= logicout; // wdata_o中存放运算结果
- end
- default: begin
- wdata_o <= `ZeroWord;
- end
- endcase
- end
- endmodule
EX模块中都是组合逻辑电路,上述代码能够分为两段理解。
(1)第一段根据输入的运算子类型进行运算,这里仅仅有一种,就是逻辑“或”运算,运算结果保存在logicout中,这个变量专门用来保存逻辑操作的结果。以后还会加入算术运算、移位运算等,届时,会定义一些新的变量保存相应的运算结果。
(2)第二段给出终于的运算结果,包含:是否要写目的寄存器wreg_o、要写的目的寄存器地址wd_o、要写入的数据wdata_o。当中wreg_o、wd_o的值都直接来自译码阶段,不须要改变,wdata_o的值要根据运算类型进行选择,假设是逻辑运算,那么将logicout的值赋给wdata_o。此处实际上是为以后扩展做准备,当加入其他类型的指令时,仅仅须要改动这里的case情况就可以。
2、EX/MEM模块
參考图4-5可知,EX模块的输出连接到EX/MEM模块,后者的作用是将运行阶段取得的运算结果。在下一个时钟传递到流水线訪存阶段。
其接口描写叙述如表4-8所看到的。
EX/MEM模块相应的代码文件是ex_mem.v。内容例如以下,能够在本书附带光盘的Code\Chapter4\文件夹下找到源文件。
- module ex_mem(
- input wire clk,
- input wire rst,
- // 来自运行阶段的信息
- input wire[`RegAddrBus] ex_wd,
- input wire ex_wreg,
- input 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
十分简单,当中仅仅有一个时序逻辑电路,在时钟上升沿,将运行阶段的结果传递到訪存阶段。
4.2.7 訪存阶段的实现
如今,ori指令进入訪存阶段了,可是因为ori指令不须要訪问数据存储器。所以在訪存阶段,不做不论什么事,仅仅是简单的将运行阶段的结果向回写阶段传递就可以。
流水线訪存阶段包含MEM、MEM/WB两个模块。
1、MEM模块
MEM模块的接口描写叙述如表4-9所看到的。
MEM模块的代码位于文件mem.v,内容例如以下,能够在本书附带光盘的Code\Chapter4\文件夹下找到源文件。
- module mem(
- input wire rst,
- // 来自运行阶段的信息
- input wire[`RegAddrBus] wd_i,
- input wire wreg_i,
- input wire[`RegBus] wdata_i,
- // 訪存阶段的结果
- output reg[`RegAddrBus] wd_o,
- output reg wreg_o,
- output reg[`RegBus] wdata_o
- );
- always @ (*) begin
- if(rst == `RstEnable) begin
- wd_o <= `NOPRegAddr;
- wreg_o <= `WriteDisable;
- wdata_o <= `ZeroWord;
- end else begin
- wd_o <= wd_i;
- wreg_o <= wreg_i;
- wdata_o <= wdata_i;
- end
- end
- endmodule
MEM模块中仅仅有一个组合逻辑电路,将输入的运行阶段的结果直接作为输出。參考图4-5可知,MEM模块的输出连接到MEM/WB模块。
2、MEM/WB模块
MEM/WB模块的作用是将訪存阶段的运算结果,在下一个时钟传递到回写阶段。其接口描写叙述如表4-10所看到的。
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbGVpc2hhbmd3ZW4=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">
MEM/WB模块的代码位于mem_wb.v文件。其主要内容例如以下,能够在本书附带光盘的Code\Chapter4\文件夹下找到源文件。
- module mem_wb(
- input wire clk,
- input wire rst,
- // 訪存阶段的结果
- input wire[`RegAddrBus] mem_wd,
- input wire mem_wreg,
- input wire[`RegBus] mem_wdata,
- // 送到回写阶段的信息
- 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模块的代码十分相似,都是将输入信号传递到相应输出port,可是MEM/WB模块中的是时序逻辑电路,即在时钟上升沿才发生信号传递。而MEM模块中的是组合逻辑电路。MEM/WB模块将訪存阶段指令是否要写目的寄存器mem_wreg、要写的目的寄存器地址mem_wd、要写入的数据mem_wdata等信息传递到回写阶段相应的接口wb_wreg、wb_wd、wb_wdata。
4.2.8 回写阶段的实现
经过上面的传递,ori指令的运算结果已经进入回写阶段了。这个阶段实际是在Regfile模块中实现的,从图4-5可知,MEM/WB模块的输出wb_wreg、wb_wd、wb_wdata连接到Regfile模块,分别连接到写使能portwe、写操作目的寄存器portwaddr、写入数据portwdata,所以会将指令的运算结果写入目的寄存器。详细代码能够參考Regfile模块。
4.2.9 顶层模块OpenMIPS的实现
顶层模块OpenMIPS在文件openmips.v中实现,主要内容就是对上面实现的流水线各个阶段的模块进行例化、连接。连接关系就如图4-5所看到的。在本章实现的OpenMIPS的接口如图4-6所看到的,还是採用左边是输入接口。右边是输出接口的方式绘制,便于理解,各接口的说明如表4-11所看到的。
可见与第3章的系统蓝图还有较大差距。非常多接口都没有,在兴许章节随着OpenMIPS实现指令的增多,会逐步完好,终于实现第3章的系统蓝图。
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbGVpc2hhbmd3ZW4=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">
代码例如以下,能够在本书附带光盘的Code\Chapter4\文件夹下找到源文件。
- module openmips(
- input wire clk,
- input wire rst,
- input wire[`RegBus] rom_data_i,
- output 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; // 指令存储器的输入地址就是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),
- // 来自Regfile模块的输入
- .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),
- // 从ID/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/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
至此,ori指令的流水线之旅已经结束了,一个原始而简单的五级流水线结构也已经建立了,有读者可能会怀疑区区百十行代码就实现了流水线。是不是太简单了?有这种怀疑是正常的,的确非常easy,可是简单并不代表简陋,不代表错误,流水线实际并没有大家想的那么复杂,下一节。将验证本节实现的流水线能不能正确工作。能不能正确运行ori指令。
好了,第一条指令就实现了。五级流水线也初步建立了,下一次将进行仿真验证。未完待续!
自己动手写处理器之第四阶段(1)——第一条指令ori的实现的更多相关文章
- 自己动手写CPU之第四阶段(3)——MIPS编译环境的建立
将陆续上传本人写的新书<自己动手写CPU>(尚未出版).今天是第13篇.我尽量每周四篇 4.4 MIPS编译环境的建立 OpenMIPS处理器在设计的时候就计划与MIPS32指令集架构兼容 ...
- 自己动手写处理器之第一阶段(3)——MIPS32指令集架构简单介绍
将陆续上传本人写的新书<自己动手写处理器>(尚未出版).今天是第四篇.我尽量每周四篇 1.4 MIPS32指令集架构简单介绍 本书设计的处理器遵循MIPS32 Release 1架构,所以 ...
- 自己动手写处理器之第一阶段(2)——MIPS指令集架构的演变
将陆续上传本人写的新书<自己动手写处理器>(尚未出版),今天是第三篇.我尽量每周四篇 MIPS指令集架构自上世纪80年代出现后.一直在进行着更新换代,从最初的MIPS I到MIPS V,发 ...
- 自己动手写处理器之第二阶段(1)——可编程逻辑器件与PLD电路设计流程
将陆续上传本人写的新书<自己动手写处理器>(尚未出版),今天是第五篇,我尽量每周四篇 通过上一章的介绍,读者应该知道CPU内部有一些主要的电路,比方:译码电路.运算电路.控 ...
- 自己动手写CPU之第七阶段(7)——乘累加指令的实现
将陆续上传本人写的新书<自己动手写CPU>.今天是第30篇.我尽量每周四篇 亚马逊的销售地址例如以下.欢迎大家围观呵! http://www.amazon.cn/dp/b00mqkrlg8 ...
- 自己动手写CPU之第五阶段(1)——流水线数据相关问题
将陆续上传本人写的新书<自己动手写CPU>(尚未出版),今天是第15篇,我尽量每周四篇 上一章建立了原始的OpenMIPS五级流水线结构,可是仅仅实现了一条ori指令,从本章開始,将逐步完 ...
- 自己动手写CPU之第六阶段(2)——移动操作指令实现思路
将陆续上传本人写的新书<自己动手写CPU>(尚未出版),今天是第21篇,我尽量每周四篇 6.2 移动操作指令实现思路 6.2.1 实现思路 这6条移动操作指令能够分为两类:一类是不涉及特殊 ...
- 自己动手写处理器之第二阶段(2)——Verilog HDL简单介绍
将陆续上传本人写的新书<自己动手写处理器>(尚未出版),今天是第六篇.我尽量每周四篇 2.3 Verilog HDL简单介绍 本书实现的OpenMIPS处理器是使用Verilog HDL编 ...
- 自己动手写CPU之第五阶段(2)——OpenMIPS对数据相关问题的解决措施
将陆续上传本人写的新书<自己动手写CPU>(尚未出版).今天是第16篇.我尽量每周四篇 5.2 OpenMIPS对数据相关问题的解决措施 OpenMIPS处理器採用数据前推的方法来解决流水 ...
随机推荐
- Alice and Bob(mutiset容器)
Alice and Bob Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) T ...
- cocos2dx 3.1从零学习(三)——Touch事件(回调,反向传值)
第三讲 Touch 前面两篇我们学习的内容,足够我们做一款简单的小游戏.也能够说,我们已经入门了,能够蹒跚的走路了. 本篇将解说cocos2dx中非常重要的touch回调机制.你肯定记得第一章做定时器 ...
- 实战Lucene,初始Lucene
实战 Lucene,第 1 部分: 初识 Lucene 本文首先介绍了 Lucene 的一些基本概念,然后开发了一个应用程序演示了利用 Lucene 建立索引并在该索引上进行搜索的过程. 10 评论: ...
- iOS中通知的添加和移除
我们都知道viewWillAppear:方法是在控制器的view将要显示的时候调用的,而viewWillDisappear:方法是在控制器的view将要隐藏的时候调用.很多时候我们根据自身需要将相关代 ...
- [转载]Matlab中fft与fftshift命令的小结与分析
http://blog.sina.com.cn/s/blog_68f3a4510100qvp1.html 注:转载请注明出处——by author. 我们知道Fourier分析是信号处理里很重要的技术 ...
- OC学习中遇到的问题总结
1.不要在初始化的时候用self.方法,因为在初始化的时候self.,此时系统还没有alloc开辟空间,这样做违背了系统流程. 2.善于运用宏定义来控制程序中的变量,这样程序可修改性高. 3.obje ...
- java基础之 第一步 :jdk安装配置
Java 开发环境配置 在本章节中我们将为大家介绍如何搭建Java开发环境. window系统安装java 下载JDK 首先我们需要下载java开发工具包JDK,下载地址:http://www.ora ...
- USACO 1.3... 虫洞 解题报告(搜索+强大剪枝+模拟)
这题可真是又让我找到了八数码的感觉...哈哈. 首先,第一次见题,没有思路,第二次看题,感觉是搜索,就这样写下来了. 这题我几乎是一个点一个点改对的(至于为什么是这样,后面给你看一个神奇的东西),让我 ...
- SDP协议
会话描述协议(SDP)为会话通知.会话邀请和其它形式的多媒体会话初始化等目的提供了多媒体会话描述.它只是用来描述,而不是一种传输协议.举例,在SIP协议的Message Header的Content- ...
- LogBoy logo