本博文希望对于OpenMIPS第一条指令ori加以实现并总结。会加入一些基本的理论以及博主的学习记录。

流水与五级流水

什么是流水:拆分,并行。将多条指令的执行相互重叠起来。就构成了流水,这样充分利用了执行部件的并行性,提高了CPU的效率。如下图

由于各个指令需要的时钟周期可能是不一样的,因此这里使用五级流水,解决相关流水线停滞的问题。下面是书中对于每一级的功能解释

这里博主理解的是。

  • 取指阶段首先读取一个指令,为了保证程序可以执行下一条,必须准备下一条指令的地址。
  • 译码阶段是对于读取到的数据进行解析。
  • 执行阶段是对于解析结果的执行。
  • 访存阶段判断是否需要进行仿存(load/Store)指令需要。否则直接将上一级的数据传递到下一级。–后面对于这两个指令进行解释
  • 回写阶段是将运算的结果(根据上一级传回来的数据以及地址)存入寄存器中。

这样就完成了一条简单的指令。后面代码中依旧会对于具体的代码进行解释。(计划是每一条相类似的代码进行一次分析)

这里只实现一个ori指令。书中给出的数据流图如下:

指令的说明

  • 补充介绍(扩展、通用寄存器)

这里地址求取需要对于立即数进行无符号的扩展以及无符号的扩展。下面给出解释:

执行过程

具体的代码实现分为组合逻辑与时序逻辑部分。组合逻辑进行计算取值等,在时钟到来的时候,将数据传到下一级进行下一级的运算。下图是一个数据数据流图的概括。需要心里有一个大概的框架。

下图是各个模块的接口图,需要对每一根信号及其作用有一个概念,才能明确具体代码的具体含义。下面给出一个大体的框架。博主理解画Viso图去表明具体数据的流向。

附图 or 链接

模块代码分析及实现

代码宏定义

这里的宏定义博主的建议是仔细看几遍,然后直接复制粘贴文件中所给的代码宏定义。对于宏定义本身就是用于方便代码书写以及可读性的,因此只需要对于代码中的宏定义进行理解即可。李思思定义如下图所示

指令获取(Instruction fetch)

取指阶段分为两个模块,一个是PC模块,一个是IF/ID模块,下面对于取指阶段的两个模块进行介绍。

PC模块

PC模块是时序逻辑,书中解释的很清楚,就是在复位结束后,PC的值每一个时钟周期加4。这里加4的原因是因为如果仔细看前面的指令的话,会发现指令的长度为32。而且所有的指令都是定长的,因此我们这里从上一条指令到下一个指令需要四个字节(4byte为32位)
将地址指向下一个指令所在的位置,等待下一个时钟到来读出。注意这里的PC仅仅是产生一个地址。而具体的指令则从对应地址中取出。这里使用的方式是读inst_rom.data中的数据。

`timescale 1ns / 1ps
`include "defines.v"
module pc_reg(input         wire                        clk     ,input      wire                        rst     ,output         reg[`InstAddrBus]          pc      ,output         reg                         ce);
always @ (posedge clk )
beginif(rst == `RstEnable)ce <= `Chipdisbale;else ce <= `ChipEnable;
end
always @ (posedge clk )
beginif(ce == `Chipdisbale)pc <= 32'h0000_0000;else     pc <= pc + 4'h4;      //按字节寻址,一个指令四个字节,因此每个时钟周期加4,跳转到下一个指令
end
endmodule
IF/ID模块

根据前面得到的地址,从对应地址取值同时将得到的指令传递给译码阶段,这里给的教程将当前指令的地址也传递给了下一个模块。个人觉得不是那么必要。至此得到指令。

`timescale 1ns / 1ps
`include "defines.v"
module if_id(input          wire                    clk         ,input          wire                    rst         ,//来自取址阶段的信号,其中宏定义InstBus表示指令宽度,为32input          wire    [`InstAddrBus] if_pc       ,input          wire    [`InstBus]     if_inst     ,//对应译码阶段的信号output          reg     [`InstAddrBus] id_pc       ,output             reg     [`InstBus]     id_inst     );
always @ (posedge clk )
beginif(rst == `RstEnable)beginid_pc <= `ZeroWord;          //复位时pc是0id_inst <= `ZeroWord;     //复位是指令为空endelse beginid_pc <= if_pc;               //其余时刻向下传递取址阶段的值id_inst <= if_inst;end
end
endmodule

这里的指令从指令存储器中得到。接下来就是译码阶段了。

译码阶段

译码阶段需要对IF/ID给出的指令进行翻译。从上边的数据流图中可以看出,得到的指令直接进入了ID模块。有寄存器堆模块,需要对于指令中的地址数据进行读取,同时还需要将后面计算的结果数据存入寄存器堆,照理还有将数据与数据处理的方式送入下一个模块处理的ID/EX模块。

ID模块

可以看出来ID模块都是组合逻辑。其主要功能及代码理解如下:首先对于上一个模块传输过来的指令进行译码。对于ori指令

其指令格式再次给出。依据指令用特征字段对指令进行区分。对于最高的6bit数据,001101表明是ORI指令,其需要读取的寄存器rs所指向的数值中,作为与立即数向或的运算数。其还会依据最高的6bit信息,得到运算类型以及运算的子类型,与数据一起传输到下一个模块用于数据的处理计算。
其主要与两个模块相连接,第一个模块是寄存器堆模块,其数据需要从寄存器堆中读取,其次是ID/EX模块,需要将得到的数据传输到下一个模块中用于执行。因此其接口也分别与这两个模块向连接。
其代码如下,输入输出接口都有标记。

`timescale 1ns / 1ps
`include "defines.v"
module id(input     wire                        rst             ,input  wire    [`InstAddrBus]     pc_i            ,input  wire    [`InstBus]         inst_i          ,//读取regfile的值input     wire    [`InstBus]         reg1_data_i     ,input  wire    [`InstBus]         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指令只需要判断21-31 bit的值,即可判断是否是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 @ (*)
beginif(rst == `RstEnable)beginaluop_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              ;endelse beginaluop_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读取端口1的寄存器地址reg2_addr_o        <=  inst_i[20:16]       ;//默认通过Regfile读取端口2的寄存器地址   imm             <=  `ZeroWord          ;case(op)`EXE_ORI:     //判断op的值是进行opi指令begin                       wreg_o      <= `WriteEnable        ;                   //ori 指令需要将结果写入目的寄存器,所以wreg_o 为 WriteEnable                      aluop_o     <= `EXE_OR_OP          ;                   //运算的子类型是逻辑“或”运算                        alusel_o    <= `EXE_RES_LOGIC      ;                   //运算类型是逻辑运算reg1_read_o <= 1'b1                 ;                   //需要通过Regfile的读端口1读取寄存器reg2_read_o <= 1'b0                 ;                   //不需要通过Regfile的读端口2读取寄存器imm         <= {16'h0,inst_i[15:0]} ;                  //指令执行需要的立即数                        wd_o        <= inst_i[20:16]        ;                   //执行指令要写入的目的寄存器地址                       instvalid   <= `InstValid          ;                   //ori指令是有效的enddefault:beginendendcaseend
end/*********************************************************************************
*************************** 第二阶段:确定进行运算的源操作数1***************************
*********************************************************************************/
//regfile读端口1的输出值
always @ (*)
beginif(rst == `RstEnable)reg1_o <= `ZeroWord;else if(reg1_read_o == 1'b1)reg1_o <= reg1_data_i;else if(reg1_read_o == 1'b0)reg1_o <= imm;                //立即数else reg1_o <= `ZeroWord;
end/*********************************************************************************
*************************** 第三阶段:确定进行运算的源操作数2***************************
*********************************************************************************/
//regfile读端口2的输出值
always @ (*)
beginif(rst == `RstEnable)reg2_o <= `ZeroWord;else if(reg2_read_o == 1'b1)reg2_o <= reg2_data_i;else if(reg2_read_o == 1'b0)reg2_o <= imm;                //立即数else reg2_o <= `ZeroWord;
end
endmodule
regfile模块

这个模块模拟的就是MIPS中的通用寄存器,32个,每一个都有固定给的用处。之前有过介绍,代码如下。

`timescale 1ns / 1ps
`include "defines.v"
module regfile(input            wire                    clk         ,input          wire                    rst         ,//write port input             wire                    we          ,input          wire    [`RegAddrBus]  waddr       ,input          wire    [`RegBus]      wdata       ,//read port 1 input            wire                    re1         ,input          wire    [`RegAddrBus]  raddr1      ,output             reg     [`RegBus]      rdata1      ,//read port 2 input            wire                    re2         ,input          wire    [`RegAddrBus]  raddr2      ,output             reg     [`RegBus]      rdata2      );
/*********************************************************************************
*************************** 第一阶段:定义32个32位寄存器***************************
*********************************************************************************/reg   [`RegBus]  regs[0:`RegNum - 1];/*********************************************************************************
*************************** 第二阶段:写操作         ***************************
*********************************************************************************/always @ (posedge clk )if((rst == `RstDisable) && (we == `WriteEnable) && (waddr != `RegNumLog2'h0) ) regs[waddr] <= wdata;else regs[waddr] <= regs[waddr];/*********************************************************************************
*************************** 第三阶段:读端口1的读操作    ***************************
*********************************************************************************/always @ (*)
beginif(rst == `RstEnable)rdata1 <= `ZeroWord;else if(raddr1 == `RegNumLog2'h0 )rdata1 <= `ZeroWord;else if( (raddr1 == waddr) && (we == `WriteEnable) && (re1 == `ReadEnable))rdata1 <= wdata;else if(re1 == `ReadEnable)rdata1 <= regs[raddr1];else rdata1 <= `ZeroWord;
end/*********************************************************************************
*************************** 第四阶段:读端口2的读操作    ***************************
*********************************************************************************/always @ (*)
beginif(rst == `RstEnable)rdata2 <= `ZeroWord;else if(raddr2 == `RegNumLog2'h0 )rdata2 <= `ZeroWord;else if((raddr2 == waddr) && (we == `WriteEnable) && (re2 == `ReadEnable))rdata2 <= wdata;else if(re2 == `ReadEnable)rdata2 <= regs[raddr2];else rdata2 <= `ZeroWord;
end
endmodule
id_ex模块

把需要计算的数据与数据处理的方式送入下一个模块处理的ID/EX模块。送出去的数据如下图。代码如下:

`timescale 1ns / 1ps
`include "defines.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     ,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 )
beginif(rst == `RstEnable)beginex_aluop  <= `EXE_NOP_OP;ex_alusel   <= `EXE_RES_NOP;ex_reg1    <= `ZeroWord;ex_reg2   <= `ZeroWord;ex_wd         <= `NOPRegAddr;ex_wreg     <= `WriteDisable;endelse beginex_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

执行阶段

执行部分分为两个部分,一个是组合逻辑,对数据处理,这里只是最简单的或运算,作为理解整个流程。分为ex模块以及EX/MEM模块

ex模块

代码如下。直接根据得到运算类型对于数据进行运算即可。

`timescale 1ns / 1ps
`include "defines.v"
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 @ (*)
beginif( rst == `RstEnable)logicout <= `ZeroWord;else begincase(aluop_i)`EXE_OR_OP:beginlogicout <= reg1_i | reg2_i;enddefault:logicout <= `ZeroWord;endcase;end
end/*********************************************************************************
*************** 第二阶段:依据alusel_i指示的运算类型,确定wdata_o的值
*********************************************************************************/always @ (*)
beginwd_o <= wd_i;              //wd_o等于wd_i,要写入的寄存器地址wreg_o <= wreg_i;          //wreg_o等于wreg_i,表示是否要写入目的寄存器case(alusel_i)`EXE_RES_LOGIC:beginwdata_o <= logicout;        //wdata_o中存放逻辑运算运算结果enddefault:wdata_o <= `ZeroWord;endcase
end
endmodule

接下来就是熟悉的时序逻辑部分。

ex/mem模块

对运算的结果传输到下一个模块里,传输的数据为运算的结果以及写入的地址以及是否有写入的寄存器。

`timescale 1ns / 1ps
`include "defines.v"
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 )
beginif(rst == `RstEnable)beginmem_wd        <= `NOPRegAddr         ;mem_wreg   <= `WriteDisable       ;mem_wdata  <= `ZeroWord           ;   endelse beginmem_wd     <= ex_wd                ;mem_wreg   <= ex_wreg              ;mem_wdata  <= ex_wdata             ;end
end
endmodule

访存阶段

由于ori不需要访问数据寄存器,因此在访存阶段不做任何事情,使用组合将接收的数据直接存入写一个阶段即可。包含两个模块。

mem模块
`timescale 1ns / 1ps
`include "defines.v"
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 @ (*)
beginif(rst == `RstEnable)beginwd_o  <=  `NOPRegAddr        ;wreg_o     <=  `WriteDisable  ;wdata_o <=  `ZeroWord     ;endelse beginwd_o  <=  wd_i            ;wreg_o     <=  wreg_i          ;wdata_o <= wdata_i         ;end
end
endmodule
mem/wb模块

将数据传输到回写阶段

`timescale 1ns / 1ps
`include "defines.v"
module mem_wb(input             wire                    clk         ,input          wire                    rst         ,//访存阶段的结果input             wire [`RegAddrBus]     mem_wb      ,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 )
beginif(rst == `RstEnable)beginwb_wd     <=  `NOPRegAddr        ;wb_wreg    <=  `WriteDisable  ;wb_wdata   <=  `ZeroWord      ;endelse beginwb_wd     <=  mem_wb          ;wb_wreg    <=  mem_wreg        ;wb_wdata   <=  mem_wdata       ;end
end
endmodule

回写阶段

至此ori指令的值可以直接写入寄存器堆中。前面的模块中代码也有展示。至此。所有的模块已经梳理结束。

顶层实现与例化

顶层的书写

直接将所有的模块连接起来即可,代码如下:

`timescale 1ns / 1ps
`include "defines.v"
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    [`RegAddrBus]  id_wd_o      ;
wire                    id_wreg_o    ;//连接EX与EX/MEM之间的连线
wire    [`RegAddrBus]  ex_wd_o      ;
wire                    ex_wreg_o    ;
wire    [`RegBus]      ex_wdata_o   ;//连接EXM与输出之间的连线
wire    [`RegAddrBus]  mem_wd_i     ;
wire                    mem_wreg_i   ;
wire    [`RegBus]      mem_wdata_i  ;//连接MEM_wb与回写阶段(寄存器堆)的输入变量
wire    [`RegAddrBus]  wb_wd_i      ;
wire                    wb_wreg_i    ;
wire    [`RegBus]      wb_wdata_i   ;//连接ID与寄存器regfile之间的连线
wire    [`RegBus]      reg1_data    ;
wire    [`RegBus]      reg2_data    ;
wire                    reg1_read    ;
wire                    reg2_read    ;
wire    [`RegAddrBus]  reg1_addr    ;
wire    [`RegAddrBus]  reg2_addr    ;//ID/EX 与EX之间的联系
wire    [`AluOpBus]     ex_aluop_i   ;
wire    [`AluSelBus]    ex_alusel_i  ;
wire    [`RegBus]       ex_reg1_i    ;
wire    [`RegBus]       ex_reg2_i    ;
wire    [`RegAddrBus]   ex_wd_i      ;
wire                    ex_wreg_i    ;// 访存与回写阶段的连线,MEM与MEM_WB
wire    [`RegAddrBus]   mem_wb_o     ;
wire                    mem_wreg_o   ;
wire    [`RegBus]       mem_wdata_o  ;pc_reg pc_reg0 (.clk     (clk        ), .rst     (rst        ), .pc          (pc         ), .ce          (rom_ce_o   ));
assign rom_addr_o = pc  ;      //指令寄存器的输入地址就是pc的值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 id0 (.rst     (rst        ), //与模块if_id之间的连线.pc_i     (id_pc_i    ), .inst_i      (id_inst_i  ), //来自regfile模块的输入.reg1_data_i(reg1_data   ), .reg2_data_i(reg2_data   ), .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 regfile0 (.clk       (clk        ), .rst     (rst        ), //与mem_wb之间的连线.we        (wb_wreg_i  ), .waddr       (wb_wd_i    ), .wdata       (wb_wdata_i ), //与ID之间的连线.re1       (reg1_read  ), .re2         (reg2_read  ), .raddr1      (reg1_addr  ), .rdata1      (reg1_data  ), .raddr2      (reg2_addr  ), .rdata2      (reg2_data  ));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 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_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 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_wb_o   ),       //要写入的目的寄存器地址 .wreg_o  (mem_wreg_o ),       //是否要写入目的寄存器.wdata_o   (mem_wdata_o)        //要写入的数据);mem_wb mem_wb (.clk      (clk        ), .rst     (rst        ),//与mem之间的连线.mem_wb    (mem_wb_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

指令寄寄存器的实现

`timescale 1ns / 1ps
`include "defines.v"
module inst_rom(input           wire                        ce      ,input          wire    [`InstAddrBus]     addr    ,output             reg     [`InstBus]             inst    );
//定义一个数组,大小是InstMemNum,元素宽度是InstBus
reg [`InstBus]     inst_mem [0:`InstMemNum - 1];
// 使用文件初始化数组
initial $readmemh ("inst_rom.data",inst_mem);
// always @ (*)
// begin
//  inst_mem [0] = 32'h3401_1100;
//  inst_mem [1] = 32'h3402_0020;
//  inst_mem [2] = 32'h3403_ff00;
//  inst_mem [3] = 32'h3404_ffff;
// end
// 当复位信号无效时,依据输入的地址,给出指令寄存器中ROM对应的元素
always@(*)
beginif(ce == `Chipdisbale)inst <= `ZeroWord;else inst <= inst_mem[addr[`InstMemNumLog2 + 1 : 2]];
end
endmodule
搭建最小系统
`timescale 1ns / 1ps
`include "defines.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_data_i(inst), .rom_addr_o(inst_addr), .rom_ce_o(rom_ce));inst_rom inst_rom0 (.ce(rom_ce), .addr(inst_addr), .inst(inst));
endmodule

这里对于数据的读取不进行分析,后期会出一个专门的博文用来总结常常用来仿真的语法以及用法。

验证与仿真

仿真文件的书写

这里仿真文件只需要给出一个是时钟信号以及复位信号即可,将复位信号拉低,处理器开始工作。

`timescale 1ns / 1ps
`include "defines.v"
module tb_openmips_min_sopc;reg clk;reg rst;openmips_min_sopc openmips_min_sopc0 (.clk(clk), .rst(rst));
always #10 clk = ~clk;initial beginclk = 0;rst = 1;#100;rst = 0;end
endmodule
代码波形的分析

这里我们对每一个模块进行时序波形图的分析,推导出一个理想波形最后验证。这里验证四条指令。

首先是PC计算器,其应该在每一个时钟上升沿,进行+4。波形如下

其次是得地址后,应该在指令寄存器中找到响应的指令然后送入译码阶段进行译码。这里蓝色的是指令寄存器中的数据,橙色的内容是送指令到译码阶段的延拍,因此有一个时钟周期的延时。

接下来就是对指令进行译码了。即为ID模块的操作。下图中对于每一个数据进行了详细的说明。这里传输下去的数据为执行阶段的数据以及向寄存器堆中读取的数据。从寄存器堆中读取的数据这里直接参与运算,传递给执行的数据是运算的类型以及子类型。参与运算的数据以及运算结果的存入地址。

还有寄存器堆模块在读使能的时候,读取的数据始终是0,寄存器中的数据,以及写回的数据,依次存放于1234号寄存器中。(这里是写回的数据)


接下来就是运算(ALU)模块。对于得到的数据依据得到的处理方式进行处理。处理的数据与结果进行标记。

最后将数据在传输一拍进行访存,最后写回即可。写回在寄存器中,前面已经进行验证。

分析总结


最后自己画了一个数据流图用于总结思路。代码中将指令寄存器放在了单独一个模块并且是所有模块的外部。至此,笔者完成了第一条指令ori的书写以及指令验证。
后面需要添加的还很多,这里先简单总结下思路。首先需要明确一些基本的概念,例如PC+4的原因,指令的格式与种类,寄存器堆的概念等…还是比较繁琐的,对于数据的流向需要有一个清楚的认识。

verilog实现多周期处理器之——(二)第一条指令ori的实现相关推荐

  1. 自己动手写CPU(1)五级流水线及CPU第一条指令ori

    自己动手写CPU(1)五级流水线及CPU第一条指令ori 动机 不知为何研一的自由时间突然多起来,可能人一闲下来就容易焦虑吧,hhhhhh.正好之前看到一本<自己动手写CPU>,就按照此书 ...

  2. 第4章:第一条指令ori的实现

    4.1 ori指令说明 ori指令格式: 索引为rs的通用寄存器的值与扩展后的立即数进行or运算,结果存储到rt (1) 符号扩展 (2)通用寄存器 32个通用寄存器,使用某个通用寄存器只需要给出相应 ...

  3. 【自己动手写CPU】第一条指令ori的实现

    验证过程 实现ori指令->建立最小SOPC->验证ori指令是否正确 ori指令说明 ori是进行逻辑"或"的运算指令 ori指令的指令码是6'b001101.处理器 ...

  4. verilog实现多周期处理器之——目录及总述

    本系列博文将使用verilog语言,实现兼容MIPS32指令集架构的处理器--OpenMIPS,MIPS是典型的RSIC处理器.主要依据雷思磊老师的<自己动手写CPU>,袁春风老师主编的& ...

  5. 对[我所认识的BIOS]系列 -- CPU的第一条指令 一文扩充(III):从源代码到 FFS 文件

    我们可以在\Build\NT32IA32\DEBUG_VS2013\IA32\MdeModulePkg\Universal\BdsDxe\BdsDxe\OUTPUT\ 目录下,找到编译生成的 BdsD ...

  6. 【基础】ARM芯片上电取第一条指令流程

    转载:ARM上电启动及Uboot代码分析 网上关于ARM的bootloader(以Uboot为例)的启动顺序的资料有好多,但是对于Uboot的地址映射.体系结构级操作介绍很少,都是直接开始Start. ...

  7. 对[我所认识的BIOS]系列 -- CPU的第一条指令 一文扩充(II):从FDF到Bios Rom image

    在"对[我所认识的BIOS]系列 -- CPU的第一条指令 一文扩充(I)"一文中,我用EFITool工具加载了BIOS Rom,发现Reset Vector位于BIOS Rom ...

  8. 【我所認知的BIOS】--第一条指令

    [我所認知的BIOS]-->第一条指令 By LightSeed 2009-10-26 其实早就想写这样一篇文章了,今天才着手写了下.说来也惭愧关于CPU的第一条指令的问题,在一开始study的 ...

  9. 计算机开机执行的第一条指令是什么?

    第一条指令的位置在FFFF:0000,也就是物理地址FFFF0.第一条指令是跳转到F000:EO5B. 接下来准备由实模式进入保护模式.加载GDT,置PE位为1,清指令预取队列并真正进入保护模式. 那 ...

最新文章

  1. Unity 类似FingerGestures 的相机跟随功能
  2. 多媒体视频知识入门贴zt(一)
  3. clickhouse 的mysql表引擎
  4. swift:创建滚动视图的图片轮播器
  5. Field ‘id‘ doesn‘t have a default value错误解决方法
  6. 阿里云:已有10000家企业在云上构建数据湖
  7. java反编译工具jad安装
  8. 汽车称重软件系统配置(一)
  9. 如何确定硕士毕业论文选题?
  10. python opencv颜色通道_【Python+OpenCV之五】 分离颜色通道多通道图像混合
  11. json schema php解析,php的json校验json-schema
  12. 微信也能鉴别山寨iPhone【微信高级教程2】
  13. 手动搭建一个https服务器,并颁发证书
  14. 封装实现电子宠物系统的企鹅类正确输入健康值和亲密度
  15. 电子商务宝盒PRIMO
  16. mysql中高阶玩法系列(六)
  17. linux kvm 命令行安装Windows xp虚拟机
  18. 助力新冠抗原检测产品规模化、智能化生产,慧灵科技推出整体解决方案
  19. css如何实现字体为10px
  20. 面试题:说一说es6新增方法

热门文章

  1. springboot Cacheable(redis),解决key乱码问题
  2. SVN工作副本已经锁定错误的解决方法
  3. Android App自动更新解决方案(DownloadManager)
  4. 解决sublime text 3使用Install Package时出现There are no packages available for installation问题
  5. 如何制作圆角布局..?
  6. 如何将JavaScript日期转换为UTC?
  7. 为什么null为对象,并且null和undefined有什么区别?
  8. 用java来实现FIFO先进先出的队列
  9. Pattern类正则表达式的编译表示形式
  10. MTK:串口调试方法|MTK串口工具