逻辑、移位操作和空指令的实现
1. 流水线数据相关的问题
流水线上经常会有一些被称为“相关”的情况发生,它使得指令序列中下一条指令无法按照设计的时钟周期执行,这些“相关”会降低流水线的性能。
1.1 流水线相关
流水线中的相关可分为:
- 结构相关:指令在执行时,由于硬件资源满足不了指令执行的要求,发生硬件资源冲突而产生的相关。如:指令和数据都共享一个寄存器,在某个时钟周期,流水线既要完成某条指令对寄存器中数据的访问操作,又要完成后续的取指令操作,这样就会发生存储器访问冲突,产生结构相关。
- 数据相关:在流水线中执行的几条指令中,一条指令依赖于前面指令的执行结果。
- 控制相关:流水线中的分支指令或则其他需要改写PC的指令造成的相关。
1.2 数据相关
本节只讨论数据相关的问题:
- RAW,即Read After Write。假设指令j在指令i后执行,RAW表示指令i将数据写入寄存器后,指令j才能从这个寄存器读取数据。如果指令j在指令i写入前读取数据,则指令j得到不正确的数据。
- WAR,即Write After Read。假设指令j在指令i后执行,WAR表示在指令i读出寄存器数据后,指令j才能向这个寄存器写入数据。如果指令j在指令i读取前写入数据,则指令i得到不正确的数据。
- WAW,即Write After Write。假设指令j在指令i后执行,RAW表示指令i将数据写入寄存器后,指令j才能向这个寄存器写入数据。如果指令j在指令i写入前写入数据,则该寄存器的值不是最新的值。
对目前的OpenMIPS结构而言,只有流水线写回阶段才会写寄存器,因此不存在WAW。又因为只能在流水线译码阶段读寄存器、写回阶段写寄存器,不存在WAR相关。
RAW相关有三种情况:
- 相邻指令存在数据相关
- 相隔1条指令的指令间存在数据相关
- 相隔2条指令的指令间存在数据相关
1.3 如何避免数据冒险
对于相隔2条指令的指令间存在数据相关(译码、写回阶段存在数据相关),在Regfile模块中已经得到了解决:
...if((raddr1 == waddr) && (we == `WriteEnable) && (re1 == `ReadEnable)) begin rdata1 <= wdata; ...if((raddr2 == waddr) && (we == `WriteEnable) && (re2 == `ReadEnable)) begin rdata2 <= wdata;...
对于相邻指令间存在数据相关、相隔1条指令的指令间存在数据相关的情况:
-
用暂停来避免数据相关(数据冒险)
暂停时,处理器会停止流水线上一条或多条指令,直到冒险不再满足。
让一条指令停在译码阶段,直到产生它的源操作数的指令进入写回阶段,这样就可以避免数据冒险。
弊端:使流水线暂停了多个周期,降低了整体的吞吐量。
-
用数据前递(转发)来避免数据冒险
实现数据前递需要在基本的硬件结构中添加一些额外的数据连接和控制逻辑。
将新计算的结果从执行阶段转发到译码阶段。当访存阶段有对寄存器为进行的写使,也可以使用数据转发。 -
加载互锁:用暂停来处理加载/使用冒险
在周期4,mrmovq指令访存,addq指令要进行计算,即便是数据前递也来不及。这就需要addq指令至少暂停一个周期,再进行数据前递。 -
编译器调度:编译器检测到相关后,可以改变部分指令的执行顺序
2. OpenMIPS对数据相关问题的解决措施
采用数据前递的方法处理数据相关。将执行阶段的结果、访存阶段的结果前递到译码阶段,使其参与译码阶段选择源操作数的过程。
在原有的基础上改动:
将执行阶段的结果 ex_wreg_o、ex_wd_o 和 ex_wdata_o,访存阶段的结果 mem_wreg_o、mem_wd_o 和 mem_wdata_o 前递到译码阶段ID模块。
2.1 ID模块添加输入接口和控制逻辑
module id(...// 处于执行阶段的指令的运算结果// 数据转发 data forwarding 来避免数据冒险input wire ex_wreg_i, // 处于执行阶段的指令是否要写寄存器input wire[`RegAddrBus] ex_wd_i, // 处于执行阶段的指令写的目的寄存器的地址input wire[`RegBus] ex_wdata_i, // 处于执行阶段的指令写的目的寄存器的数据// 处于访存阶段的指令的运算结果// 数据转发 data forwarding 来避免数据冒险input wire mem_wreg_i, // 处于访存阶段的指令是否要写寄存器input wire[`RegAddrBus] mem_wd_i, // 处于访存阶段的指令写的目的寄存器的地址input wire[`RegBus] mem_wdata_i, // 处于访存阶段的指令写的目的寄存器的数据...
);...
/**************** 第二段:确定进行运算的源操作数1 ****************/
/* 给reg1_o的赋值的过程增加了两种情况1. 如果Regfile模块读端口1要读取的寄存器就是要执行阶段要写的目的寄存器,那么直接把执行阶段的结果ex_wdata_i作为reg1_o的值;2. 如果Regfile模块读端口1要读取的寄存器就是要访存阶段要写的目的寄存器,那么直接把访存阶段的结果mem_wdata_i作为reg1_o的值;
*/
always @(*) beginif (rst == `RstEnable) beginreg1_o <= `ZeroWord;end else if((reg1_read_o == `ReadEnable) && (ex_wreg_i == `WriteEnable)&& (reg1_addr_o == ex_wd_i)) begin reg1_o <= ex_wdata_i; // 读入执行阶段的结果end else if((reg1_read_o == `ReadEnable) && (mem_wreg_i == `WriteEnable)&& (reg1_addr_o == mem_wd_i)) begin reg1_o <= mem_wdata_i; // 读入访存阶段的结果end else if (reg1_read_o == `ReadEnable) beginreg1_o <= reg1_data_i; // Regfile读端口1的输出值end else if (reg1_read_o == `ReadDisable) beginreg1_o <= imm; // 读立即数end else beginreg1_o <= `ZeroWord;end
end/**************** 第三阶段:确定进行运算的源操作数2 ****************/
always @(*) beginif (rst == `RstEnable) beginreg2_o <= `ZeroWord;end else if((reg2_read_o == `ReadEnable) && (ex_wreg_i == `WriteEnable)&& (reg2_addr_o == ex_wd_i)) begin reg2_o <= ex_wdata_i; // 读入执行阶段的结果end else if((reg2_read_o == `ReadEnable) && (mem_wreg_i == `WriteEnable)&& (reg2_addr_o == mem_wd_i)) begin reg2_o <= mem_wdata_i; // 读入访存阶段的结果end else if (reg2_read_o == `ReadEnable) beginreg2_o <= reg2_data_i; // Regfile读端口2的输出值end else if (reg2_read_o == `ReadDisable) beginreg2_o <= imm; // 读立即数end else beginreg2_o <= `ZeroWord;end
endendmodule
2.2 在OpenMIPS顶层模块中添加数据连接关系
...
// 译码阶段ID模块实例化id id0(...// 来自正处于执行阶段的指令的结果.ex_wreg_i(ex_wreg_o),.ex_wd_i(ex_wd_o),.ex_wdata_i(ex_wdata_o),// 来自正处于访存阶段的指令的结果.mem_wreg_i(mem_wreg_o),.mem_wd_i(mem_wd_o),.mem_wdata_i(mem_wdata_o),...);
...
3. 测试数据相关问题的解决效果
更改指令存储器内容:
34011100 // ori $1,$0,0x1100
34210020 // ori $1,$1,0x0020
34214400 // ori $1,$1,0x4400
34210044 // ori $1,$1,0x0044
创建工程,仿真:
$1的变化符合预期,修改后的OpenMIPS结构正确解决了数据相关问题。
4. 逻辑、移位操作和空指令的说明
OpenMIPS中所有指令的格式均为立即数型(I-Type)、跳转型(J-Type)和寄存器型(R-Type)三种类型中的一种。
MIPS32指令集架构中定义:
- 逻辑操作:and、andi、or、ori、xor、xori、nor、lui
- 移位操作:sll、sllv、sra、srav、srl、srlv
- 空指令:nop、ssnop
ssnop是一种特殊类型的空操作,在每个周期发射多条指令的CPU中,使用ssnop指令可以确保单独占用一个周期。OpenMIPS为标量处理器,每个周期只发射一条指令,所以在OpenMIPS中可以按照 nop 指令的处理方式来处理ssnop指令。
sync(用于保证加载、存储操作的顺序)、pref(用于缓存预取),对于OpenMIPS而言,是顺序执行、加载和存储的,没有实现缓存,故将上述两条指令当成 nop 指令处理。
4.1 and、or、xor、nor指令
这四条指令都是R类型指令,指令码都是6’b000000,也就是MIPS32指令集架构中定义的SPECIAL类。第6~10bit都为0,需要根据功能码判断是哪种指令。
-
function:100100,AND
汇编格式:AND rd, rs, rt
功能描述:寄存器 rs 中的值与寄存器 rt 中的值按位逻辑与,结果写入寄存器 rd 中。
操作定义:GPR[rd] ← GPR[rs] & GPR[rt] -
function:100101,OR
汇编格式:OR rd, rs, rt
功能描述:寄存器 rs 中的值与寄存器 rt 中的值按位逻辑或,结果写入寄存器 rd 中。
操作定义:GPR[rd] ← GPR[rs] or GPR[rt] -
function:100110,XOR
汇编格式:XOR rd, rs, rt
功能描述:寄存器 rs 中的值与寄存器 rt 中的值按位逻辑异或,结果写入寄存器 rd 中。
操作定义:GPR[rd] ← GPR[rs] xor GPR[rt] -
function:100111,NOR
汇编格式:NOR rd, rs, rt
功能描述:寄存器 rs 中的值与寄存器 rt 中的值按位逻辑或非,结果写入寄存器 rd 中。
操作定义:GPR[rd] ← GPR[rs] nor GPR[rt]
4.2 andi、xori指令
andi、xori都是I类型指令,依据第26~31bit指令码判断是哪种指令。
-
opcode:001100,ANDI
汇编格式:ANDI rt, rs, imm
功能描述:寄存器 rs 中的值与 0 扩展至 32 位的立即数 imm 按位逻辑与,结果写入寄存器 rt 中。
操作定义:GPR[rt] ← GPR[rs] and Zero_extend(imm) -
opcode:001110,XORI
汇编格式:XORI rt, rs, imm
功能描述:寄存器 rs 中的值与 0 扩展至 32 位的立即数 imm 按位逻辑异或,结果写入寄存器 rt 中。
操作定义:GPR[rt] ← GPR[rs] xor Z ero_ext end(imm)
4.3 lui指令
lui指令是I类型指令,依据第26~31bit指令码是否为6’b001111判断是否是lui指令。
汇编格式:LUI rt, imm
功能描述:将 16 位立即数 imm 写入寄存器 rt 的高 16 位,寄存器 rt 的低 16 位置 0 。
4.4 sll、sllv、sra、srav、stlv、stlv指令
这六条指令都是R类型指令,指令码都为6’b000000,SPECIAL类指令,需要根据0~5bit的功能码判断是那种指令。
-
function:000000,SLL
汇编格式:SLL rd, rt, sa
功能描述:由立即数 sa 指定移位量,对寄存器 rt 的值进行逻辑左移,结果写入寄存器 rd 中。 -
function:000010,SRL
汇编格式:SRL rd, rt, sa
功能描述:由立即数 sa 指定移位量,对寄存器 rt 的值进行逻辑右移,结果写入寄存器 rd 中。 -
function:000011,SRA
汇编格式:SRA rd, rt, sa
功能描述:由立即数 sa 指定移位量,对寄存器 rt 的值进行算术右移,结果写入寄存器 rd 中。 -
function:000100,SLLV
汇编格式:SLLV rd, rt, rs
功能描述:由寄存器 rs 中的值指定移位量,对寄存器 rt 的值进行逻辑左移,结果写入寄存器 rd 中。 -
function:000110,SRLV
汇编格式:SRLV rd, rt, rs
功能描述:由寄存器 rs 中的值指定移位量,对寄存器 rt 的值进行逻辑右移,结果写入寄存器 rd 中。 -
function:000111,SRAV
汇编格式:SRAV rd, rt, rs
功能描述:由寄存器 rs 中的值指定移位量,对寄存器 rt 的值进行算术右移,结果写入寄存器 rd 中。
总结一下,指令的助记符最后有“v",表示移位位数由寄存器确定;指令的助记符最后没有“v",表示移位位数由指令中6~10Bit的sa确定
4.5 nop、ssnop、sync、pref指令
nop、ssnop指令的指令码、功能码与SLL指令一致,这样译码的时候会造成冲突吗?
nop = sll $0,$0,0
ssnop = sll $0,$0,1
其实两者是等价的,sll指令向$0寄存器保存移位结果,实际上不会由任何效果,无论向$0写任何数,$0始终为0,等同于什么都没做,这正是空指令的效果。所以nop、ssnop指令不用特意实现,完全可以当作特殊的逻辑左移指令。
5. 修改OpenMIPS以实现移位、逻辑操作与空指令
5.1 修改译码阶段的ID模块
首先在defines.v文件中添加宏定义:
`define EXE_AND 6'b100100 // 指令and的功能码
`define EXE_OR 6'b100101 // 指令or的功能码
`define EXE_XOR 6'b100110 // 指令xor的功能码
`define EXE_NOR 6'b100111 // 指令nor的功能码
`define EXE_ANDI 6'b001100 // 指令andi的指令码
`define EXE_ORI 6'b001101 // 指令ori的指令码
`define EXE_XORI 6'b001110 // 指令xori的指令码
`define EXE_LUI 6'b001111 // 指令lui的指令码`define EXE_SLL 6'b000000 // 指令sll的功能码
`define EXE_SLLV 6'b000100 // 指令sllv的功能码
`define EXE_SRL 6'b000010 // 指令srl的功能码
`define EXE_SRLV 6'b000110 // 指令srlv的功能码
`define EXE_SRA 6'b000011 // 指令sra的功能码
`define EXE_SRAV 6'b000111 // 指令srav的功能码`define EXE_SYNC 6'b001111 // 指令sync的功能码
`define EXE_PREF 6'b110011 // 指令pref的功能码`define EXE_NOP 6'b000000 // 指令nop的功能码
`define SSNOP 32'b00000000000000000000000001000000`define EXE_SPECIAL_INST 6'b000000 //SPECIAL类指令的指令码
对指令进行译码的前提是能判断出指令种类,如下图:
修改后的ID模块:
`include "defines.v"
`timescale 1ns/1ps
module id(
......
);// 取得指令的指令码,功能码
// 对于ori指令只需判断26~31bit的值,即可判断是否是ori指令
wire[5:0] op = inst_i[31:26]; // 取指令码opcode
wire[4:0] op2 = inst_i[10:6]; // 用来判断指令种类
wire[5:0] op3 = inst_i[5:0]; // 取功能码function
wire[4:0] op4 = inst_i[20:16]; // 取 rt// 保存指令执行需要的立即数
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 <= `ReadDisable;reg2_read_o <= `ReadDisable;reg1_addr_o <= `NOPRegAddr;reg2_addr_o <= `NOPRegAddr;imm <= `ZeroWord;end else beginaluop_o <= `EXE_NOP_OP;alusel_o <= `EXE_RES_NOP;wd_o <= inst_i[15:11]; // 默认目的寄存器地址wd_owreg_o <= `WriteDisable;instvalid <= `InstInvalid;reg1_read_o <= `ReadDisable;reg2_read_o <= `ReadDisable;reg1_addr_o <= inst_i[25:21]; // 默认的reg1_addr_oreg2_addr_o <= inst_i[20:16]; // 默认的reg2_addr_oimm <= `ZeroWord;case (op)`EXE_SPECIAL_INST: begincase (op2)5'b000000: begincase (op3)`EXE_OR: beginwreg_o <= `WriteEnable;aluop_o <= `EXE_OR_OP;alusel_o <= `EXE_RES_LOGIC;reg1_read_o <= `ReadEnable;reg2_read_o <= `ReadEnable;instvalid <= `InstValid;end`EXE_AND: beginwreg_o <= `WriteEnable;aluop_o <= `EXE_AND_OP;alusel_o <= `EXE_RES_LOGIC;reg1_read_o <= `ReadEnable;reg2_read_o <= `ReadEnable;instvalid <= `InstValid;end`EXE_XOR: beginwreg_o <= `WriteEnable;aluop_o <= `EXE_XOR_OP;alusel_o <= `EXE_RES_LOGIC;reg1_read_o <= `ReadEnable;reg2_read_o <= `ReadEnable;instvalid <= `InstValid;end`EXE_NOR: beginwreg_o <= `WriteEnable;aluop_o <= `EXE_NOR_OP;alusel_o <= `EXE_RES_LOGIC;reg1_read_o <= `ReadEnable;reg2_read_o <= `ReadEnable;instvalid <= `InstValid;end`EXE_SLLV: beginwreg_o <= `WriteEnable;aluop_o <= `EXE_SLL_OP;alusel_o <= `EXE_RES_SHIFT;reg1_read_o <= `ReadEnable;reg2_read_o <= `ReadEnable;instvalid <= `InstValid;end`EXE_SRLV: beginwreg_o <= `WriteEnable;aluop_o <= `EXE_SRL_OP;alusel_o <= `EXE_RES_SHIFT;reg1_read_o <= `ReadEnable;reg2_read_o <= `ReadEnable;instvalid <= `InstValid;end`EXE_SRAV: beginwreg_o <= `WriteEnable;aluop_o <= `EXE_SRA_OP;alusel_o <= `EXE_RES_SHIFT;reg1_read_o <= `ReadEnable;reg2_read_o <= `ReadEnable;instvalid <= `InstValid;end`EXE_SYNC: beginwreg_o <= `WriteDisable;aluop_o <= `EXE_NOP_OP;alusel_o <= `EXE_RES_NOP;reg1_read_o <= `ReadDisable;reg2_read_o <= `ReadEnable;instvalid <= `InstValid;enddefault: beginendendcase //op3enddefault: beginendendcase // op2end`EXE_ORI: beginwreg_o <= `WriteEnable; aluop_o <= `EXE_OR_OP; alusel_o <= `EXE_RES_LOGIC; reg1_read_o <= `ReadEnable; reg2_read_o <= `ReadDisable; imm <= {16'h0, inst_i[15:0]}; wd_o <= inst_i[20:16]; instvalid <= `InstValid; end `EXE_ANDI: beginwreg_o <= `WriteEnable;aluop_o <= `EXE_AND_OP;alusel_o <= `EXE_RES_LOGIC;reg1_read_o <= `ReadEnable;reg2_read_o <= `ReadDisable;imm <= {16'h0,inst_i[15:0]};wd_o <= inst_i[20:16];instvalid <= `InstValid;end`EXE_XORI: beginwreg_o <= `WriteEnable;aluop_o <= `EXE_XOR;alusel_o <= `EXE_RES_LOGIC;reg1_read_o <= `ReadEnable;reg2_read_o <= `ReadDisable;imm <= {16'h0,inst_i[15:0]};wd_o <= inst_i[20:16];instvalid <= `InstValid;end`EXE_LUI: beginwreg_o <= `WriteEnable;aluop_o <= `EXE_OR_OP; // 将lui指令转为ori指令执行alusel_o <= `EXE_RES_LOGIC;reg1_read_o <= `ReadEnable;reg2_read_o <= `ReadDisable;imm <= {inst_i[15:0],16'h0};wd_o <= inst_i[20:16];instvalid <= `InstValid;end`EXE_PREF: beginwreg_o <= `WriteDisable;aluop_o <= `EXE_NOP_OP;alusel_o <= `EXE_RES_NOP;reg1_read_o <= `ReadDisable;reg2_read_o <= `ReadDisable;instvalid <= `InstValid;enddefault: beginendendcase // case opif (inst_i[31:21] == 11'b0) beginif (op3 == `EXE_SLL) beginwreg_o <= `WriteEnable;aluop_o <= `EXE_SLL_OP;alusel_o <= `EXE_RES_SHIFT;reg1_read_o <= `ReadDisable;reg2_read_o <= `ReadEnable;imm[4:0] <= inst_i[10:6];wd_o <= inst_i[15:11];instvalid <= `InstValid;end else if (op3 == `EXE_SRL) beginwreg_o <= `WriteEnable;aluop_o <= `EXE_SRL_OP;alusel_o <= `EXE_RES_SHIFT;reg1_read_o <= `ReadDisable;reg2_read_o <= `ReadEnable;imm[4:0] <= inst_i[10:6];wd_o <= inst_i[15:11];instvalid <= `InstValid;end else if (op3 == `EXE_SRA) beginwreg_o <= `WriteEnable;aluop_o <= `EXE_SRA_OP;alusel_o <= `EXE_RES_SHIFT;reg1_read_o <= `WriteDisable;reg2_read_o <= `WriteEnable;imm <= inst_i[10:6];wd_o <= inst_i[15:11];instvalid <= `InstValid;endendend // else
end // always/************** 第二段:确定进行运算的源操作数1 **************/
/* 给reg1_o的赋值的过程增加了两种情况1. 如果Regfile模块读端口1要读取的寄存器就是要执行阶段要写的目的寄存器,那么直接把执行阶段的结果ex_wdata_i作为reg1_o的值;2. 如果Regfile模块读端口1要读取的寄存器就是要访存阶段要写的目的寄存器,那么直接把访存阶段的结果mem_wdata_i作为reg1_o的值;
*/
always @(*) beginif (rst == `RstEnable) beginreg1_o <= `ZeroWord;end else if((reg1_read_o == `ReadEnable) && (ex_wreg_i == `WriteEnable)&& (reg1_addr_o == ex_wd_i)) begin reg1_o <= ex_wdata_i; // 读入执行阶段的结果end else if((reg1_read_o == `ReadEnable) && (mem_wreg_i == `WriteEnable)&& (reg1_addr_o == mem_wd_i)) begin reg1_o <= mem_wdata_i; // 读入访存阶段的结果end else if (reg1_read_o == `ReadEnable) beginreg1_o <= reg1_data_i; // Regfile读端口1的输出值end else if (reg1_read_o == `ReadDisable) beginreg1_o <= imm; // 读立即数end else beginreg1_o <= `ZeroWord;end
end/************** 第三阶段:确定进行运算的源操作数2 **************/
always @(*) beginif (rst == `RstEnable) beginreg2_o <= `ZeroWord;end else if((reg2_read_o == `ReadEnable) && (ex_wreg_i == `WriteEnable)&& (reg2_addr_o == ex_wd_i)) begin reg2_o <= ex_wdata_i; // 读入执行阶段的结果end else if((reg2_read_o == `ReadEnable) && (mem_wreg_i == `WriteEnable)&& (reg2_addr_o == mem_wd_i)) begin reg2_o <= mem_wdata_i; // 读入访存阶段的结果end else if (reg2_read_o == `ReadEnable) beginreg2_o <= reg2_data_i; // Regfile读端口2的输出值end else if (reg2_read_o == `ReadDisable) beginreg2_o <= imm; // 读立即数end else beginreg2_o <= `ZeroWord;end
endendmodule
5.2 修改执行阶段的EX模块
`include "defines.v"
`timescale 1ns/1ps
module ex(
......
);reg[`RegBus] logicout; // 保存逻辑运算的结果
reg[`RegBus] shiftres; // 保存移位运算的结果// 逻辑运算
always @(*) beginif (rst == `RstEnable) beginlogicout <= `ZeroWord;end else begincase (aluop_i)`EXE_OR_OP: beginlogicout <= reg1_i | reg2_i;end`EXE_AND_OP: beginlogicout <= reg1_i & reg2_i;end`EXE_NOR_OP: beginlogicout <= ~(reg1_i | reg2_i);end`EXE_XOR_OP: beginlogicout <= reg1_i ^ reg2_i;enddefault: beginlogicout <= `ZeroWord;endendcaseend // else
end // always// 移位运算
always @(*) beginif (rst == `RstEnable) beginshiftres <= `ZeroWord;end else begincase (aluop_i)`EXE_SLL_OP: beginshiftres <= reg2_i << reg1_i[4:0];end`EXE_SRL_OP: beginshiftres <= reg2_i >> reg1_i[4:0];end`EXE_SRA_OP: beginshiftres <= ({32{reg2_i[31]}} << (6'd32-{1'b0,reg1_i[4:0]})) | reg2_i >> reg1_i[4:0];enddefault: beginshiftres <= `ZeroWord;endendcaseend
end// 依据aluop_i选择最终的运算结果
always @(*) beginwd_o <= wd_i;wreg_o <= wreg_i;case (alusel_i)`EXE_RES_LOGIC: beginwdata_o <= logicout;end`EXE_RES_SHIFT: beginwdata_o <= shiftres;enddefault: beginwdata_o <= `ZeroWord;endendcase
endendmodule
6. 测试程序
6.1 逻辑运算功能测试
.org 0x0
.global _start.set noat
_start:lui $1,0x0101ori $1,$1,0x0101ori $2,$1,0x1100 # $2 = $1 | 0x1100 = 0x01011101or $1,$1,$2 # $1 = $1 | $2 = 0x01011101andi $3,$1,0x00fe # $3 = $1 & 0x00fe = 0x00000000and $1,$3,$1 # $1 = $3 & $1 = 0x00000000xori $4,$1,0xff00 # $4 = $1 ^ 0xff00 = 0x0000ff00xor $1,$4,$1 # $1 = $4 ^ $1 = 0x0000ff00nor $1,$4,$1 # $1 = $4 ~^ $1 = 0xffff00ff nor is "not or"
编译得到:
3c010101
34210101
34221100
00220825
302300fe
00610824
3824ff00
00810826
00810827
6.2 移位运算和空指令功能测试
.org 0x0.set noat.global _start
_start:lui $2,0x0404ori $2,$2,0x0404ori $7,$0,0x7ori $5,$0,0x5ori $8,$0,0x8syncsll $2,$2,8 # $2 = 0x40404040 sll 8 = 0x04040400sllv $2,$2,$7 # $2 = 0x04040400 sll 7 = 0x02020000srl $2,$2,8 # $2 = 0x02020000 srl 8 = 0x00020200srlv $2,$2,$5 # $2 = 0x00020200 srl 5 = 0x00001010nopsll $2,$2,19 # $2 = 0x00001010 sll 19 = 0x80800000ssnopsra $2,$2,16 # $2 = 0x80800000 sra 16 = 0xffff8080srav $2,$2,$8 # $2 = 0xffff8080 sra 8 = 0xffffff80
编译得到:
3c020404
34420404
34070007
34050005
34080008
0000000f
00021200
00e21004
00021202
00a21006
00000000
000214c0
00000040
00021403
01021007
建立工程,仿真:
参考资料
- 《自己动手写CPU》
- 《MIPS基准指令集手册_v1.00》
- 《深入理解计算机系统》