首页 > 代码库 > 自己动手写CPU之第九阶段(9)——修改OpenMIPS以实现ll、sc指令
自己动手写CPU之第九阶段(9)——修改OpenMIPS以实现ll、sc指令
将陆续上传新书《自己动手写CPU》,今天是第48篇。
9.8 修改OpenMIPS以实现ll、sc指令
9.8.1 LLbit寄存器的实现
LLbit寄存器在LLbit模块中实现,模块接口如图9-30所示,各接口描述如表9-8所示。
LLbit寄存器的代码如下,源文件是本书光盘Code\Chapter9_2目录下的LLbit_reg.v文件。
module LLbit_reg( input wire clk, input wire rst, // 异常是否发生,为1表示异常发生,为0表示没有异常 input wire flush, // 写操作 input wire LLbit_i, input wire we, // LLbit寄存器的值 output reg LLbit_o ); always @ (posedge clk) begin if (rst == `RstEnable) begin LLbit_o <= 1'b0; end else if((flush == 1'b1)) begin //如果异常发生,那么设置LLbit_o为0 LLbit_o <= 1'b0; end else if((we == `WriteEnable)) begin LLbit_o <= LLbit_i; end end endmodule
当有异常发生时,会使得LLbit寄存器的值为0。所以此处有一个输入接口flush,当flush为1时,表示有异常发生(在第11章实现异常处理的时候会详细介绍),从而设置LLbit寄存器的值为0。
9.8.2 修改译码阶段的ID模块
在译码阶段的ID模块要增加对ll、sc指令的译码,根据图9-28给出的ll、sc指令格式可得,确定ll、sc指令的过程如图9-31所示。
其中涉及的宏定义如下,正是ll、sc指令的指令码,在本书附带光盘Code\Chapter9_2目录下的defines.v文件可以找到这些定义。
`define EXE_LL 6'b110000 `define EXE_SC 6'b111000
对译码阶段ID模块的代码做如下修改。完整代码位于本书附带光盘Code\Chapter9_2目录下的id.v文件。
module id( ....... ); ....... always @ (*) begin if (rst == `RstEnable) begin ...... end else begin aluop_o <= `EXE_NOP_OP; alusel_o <= `EXE_RES_NOP; wd_o <= inst_i[15:11]; // 默认目的寄存器地址wd_o wreg_o <= `WriteDisable; instvalid <= `InstInvalid; reg1_read_o <= 1'b0; reg2_read_o <= 1'b0; reg1_addr_o <= inst_i[25:21]; // 默认的reg1_addr_o reg2_addr_o <= inst_i[20:16]; // 默认的reg2_addr_o imm <= `ZeroWord; ...... case (op) ...... EXE_LL: begin // ll指令 wreg_o <= `WriteEnable; aluop_o <= `EXE_LL_OP; alusel_o <= `EXE_RES_LOAD_STORE; reg1_read_o <= 1'b1; reg2_read_o <= 1'b0; wd_o <= inst_i[20:16]; instvalid <= `InstValid; end ...... `EXE_SC: begin // sc指令 wreg_o <= `WriteEnable; aluop_o <= `EXE_SC_OP; alusel_o <= `EXE_RES_LOAD_STORE; reg1_read_o <= 1'b1; reg2_read_o <= 1'b1; wd_o <= inst_i[20:16]; instvalid <= `InstValid; alusel_o <= `EXE_RES_LOAD_STORE; end ...... endmodule
译码工作主要是确定要写的目的寄存器、要读取的寄存器情况、要执行的运算等三个方面。分别介绍如下。
(1)ll指令
- 要写的目的寄存器:链接加载指令ll需要将加载结果写入通用寄存器,所以设置wreg_o为WriteEnable,同时参考图9-28可知,要写的目的寄存器是指令中的16-20bit,所以设置wd_o为inst_i[20:16]。
- 要读取的寄存器情况:参考图9-28可知,计算加载目标地址需要使用到地址为base的寄存器值,所以设置reg1_read_o为1,表示通过Regfile模块的读端口1读取寄存器,默认读取的寄存器地址reg1_addr_o是指令的21-25bit,正是ll指令中的base。所以最终译码阶段的输出reg1_o就是地址为base的寄存器的值。
- 要执行的运算:设置alusel_o为EXE_RES_LOAD_STORE,表示运算类型是加载存储,设置aluop_o为EXE_LL_OP,表示运算子类型是ll。
(2)sc指令
- 要写的目的寄存器:条件存储指令sc也需要写通用寄存器,所以也设置wreg_o为WriteEnable。这一点是与其余的存储指令sb、sh、sw等的重要区别。要写的目的寄存器是指令中的16-20bit,所以设置wd_o为inst_i[20:16]。
- 要读取的寄存器情况:参考图9-28可知,计算存储目标地址需要使用到地址为base的寄存器值,所以设置reg1_read_o为1,表示通过Regfile模块的读端口1读取寄存器,默认读取的寄存器地址reg1_addr_o是指令的21-25bit,正是sc指令中的base。所以最终译码阶段的输出reg1_o就是地址为base的寄存器的值。另外,要存储的值是地址为rt的通用寄存器的值,所以设置reg2_read_o为1,表示通过Regfile模块的读端口2读取寄存器,默认读取的寄存器地址reg2_addr_o是指令的16-20bit,正是sc指令中的rt。所以最终译码阶段的输出reg2_o就是地址为rt的寄存器的值。
- 要执行的运算:设置alusel_o为EXE_RES_LOAD_STORE,表示运算类型是加载存储,设置aluop_o为EXE_SC_OP,表示运算子类型是sc。
9.8.3 修改访存阶段
1、修改MEM模块
参考图9-30可知,访存阶段的MEM模块要新增部分接口,新增接口的描述如表9-9所示。
MEM模块主要修改的代码如下,完整代码请参考本书附带光盘Code\Chapter9_2目录下的mem.v文件。
module mem( ...... // 新增的输入接口 input wire LLbit_i, input wire wb_LLbit_we_i, input wire wb_LLbit_value_i, ...... // 新增的输出接口 output reg LLbit_we_o, output reg LLbit_value_o, ...... ); reg LLbit; // 保存LLbit寄存器的最新值 ...... // 获取LLbit寄存器的最新值,如果回写阶段的指令要写LLbit,那么回写阶段要写入的 // 值就是LLbit寄存器的最新值,反之,LLbit模块给出的值LLbit_i是最新值 always @ (*) begin if(rst == `RstEnable) begin LLbit <= 1'b0; end else begin if(wb_LLbit_we_i == 1'b1) begin LLbit <= wb_LLbit_value_i; // 回写阶段的指令要写LLbit end else begin LLbit <= LLbit_i; end end end always @ (*) begin if(rst == `RstEnable) begin ...... LLbit_we_o <= 1'b0; LLbit_value_o <= 1'b0; end else begin ...... LLbit_we_o <= 1'b0; LLbit_value_o <= 1'b0;、 mem_ce_o <= `ChipDisable; mem_we <= `WriteDisable; case (aluop_i) ...... `EXE_LL_OP: begin // ll指令的访存输出 mem_addr_o <= mem_addr_i; mem_we <= `WriteDisable; wdata_o <= mem_data_i; LLbit_we_o <= 1'b1; LLbit_value_o <= 1'b1; mem_sel_o <= 4'b1111; mem_ce_o <= `ChipEnable; end ...... `EXE_SC_OP: begin // sc指令的访存输出 if(LLbit == 1'b1) begin LLbit_we_o <= 1'b1; LLbit_value_o <= 1'b0; mem_addr_o <= mem_addr_i; mem_we <= `WriteEnable; mem_data_o <= reg2_i; wdata_o <= 32'b1; mem_sel_o <= 4'b1111; mem_ce_o <= `ChipEnable; end else begin wdata_o <= 32'b0; end end ...... endmodule
MEM模块的代码增加了一个过程,以获得LLbit寄存器的最新值,然后针对ll、sc指令分别给出了对数据存储器的访问信息。
(1)ll指令
- 给出要访问的数据存储器地址mem_addr_o,其值就是执行阶段计算出来的地址mem_addr_i,参考9.3.2节。
- 因为是加载操作,所以设置mem_we_o为WriteDisable。
- 因为要访问数据存储器,所以设置mem_ce_o为ChipEnable。
- 因为是加载一个字,所以设置mem_sel_o为4‘b1111。
- 要写入通用寄存器rt的值就是从数据存储器加载到的数据mem_data_i,所以设置wdata_o为mem_data_i。
- 要置LLbit寄存器为1,所以设置LLbit_we_o为1,表示要写LLbit寄存器,同时,设置LLbit_value_o为1,表示要写入LLbit寄存器的值为1。
(2)sc指令
如果LLbit寄存器的值为1,表示之前已执行过ll指令,并且在ll指令执行后、当前sc指令执行前的这段时间内,没有异常发生,此时,sc指令的访存信息如下。
- 给出要访问的数据存储器地址mem_addr_o,其值就是执行阶段计算出来的地址mem_addr_i,参考9.3.2节。
- 因为是存储操作,所以设置mem_we_o为WriteEnable。
- 因为要访问数据存储器,所以设置mem_ce_o为ChipEnable。
- 因为是存储一个字,所以设置mem_sel_o为4‘b1111。
- 要存储的数据是reg2_i,是从译码阶段传递过来的,其值就是地址为rt的通用寄存器的值。
- 要置地址为rt的通用寄存器为1,所以设置wdata_o为1。
- 要置LLbit寄存器为0,所以设置LLbit_we_o为1,表示要写LLbit寄存器,同时,设置LLbit_value_o为0,表示要写入LLbit寄存器的值为0。
- 不修改数据存储器,所以mem_we_o保持默认值WriteDisable,mem_ce_o保持默认值ChipDisable。
- 不修改LLbit寄存器的值,所以LLbit_we_o保持默认值0。
- 要置地址为rt的通用寄存器为0,所以设置wdata_o为0。
2、修改MEM/WB模块
从图9-30可知,MEM/WB模块要新增部分接口,新增接口的描述如表9-10所示。
MEM/WB模块要修改的代码如下,作用很直白:在访存阶段没有暂停时,简单地将MEM给出的对LLbit寄存器的写信息传递到访存阶段,完整代码请读者参考本书附带光盘Code\Chapter9_2目录下的mem_wb.v文件。
module mem_wb( ...... input wire mem_LLbit_we, input wire mem_LLbit_value, ...... output reg wb_LLbit_we, output reg wb_LLbit_value ); always @ (posedge clk) begin if(rst == `RstEnable) begin ...... wb_LLbit_we <= 1'b0; wb_LLbit_value <= 1'b0; end else if(stall[4] == `Stop && stall[5] == `NoStop) begin ...... wb_LLbit_we <= 1'b0; wb_LLbit_value <= 1'b0; end else if(stall[4] == `NoStop) begin // 判断访存阶段是否暂停 ...... wb_LLbit_we <= mem_LLbit_we; wb_LLbit_value <= mem_LLbit_value; end end endmodule
9.8.4 修改OpenMIPS模块
因为一些模块增加了接口,所以要修改顶层模块OpenMIPS,以将这些新增加的接口按照图9-30所示的关系连接起来。具体修改代码不再给出,读者可以参考本书附带光盘Code\Chapter9_2目录下的openmips.v文件。
注意一点,因为目前还没有实现异常处理,所以可以直接设置LLbit模块的输入接口flush为0,表示没有异常发生,当后续章节实现异常处理后,再将其连接到正确的模块。
下一篇将验证OpenMIPS的ll、sc指令是否实现正确。
自己动手写CPU之第九阶段(9)——修改OpenMIPS以实现ll、sc指令