移动操作指令的实现
1. 移动操作指令说明
MIPS32指令集架构中定义的移动操作指令共有六条:movn、movz、mfhi、mthi、mflo、mtlo,后四条指令涉及对特殊寄存器HI、LO的读写操作。
HI、LO寄存器用于保存乘法、除法结果。当用于保存乘法结果时,HI寄存器保存结果的高32位,LO寄存器保存低32位;当用于保存乘法结果时,HI寄存器保存余数,LO寄存器保存商。
这六条指令都是R类型指令,指令码都是6’b000000,都为SPECIAL类型指令;同时第6~10bit都是5’b00000,可以根据功能码判断是哪条指令。
-
function:001011;MOVN(Move Conditional on Not Zero)
汇编格式:MOVN rd, rs, rt
功能描述:if rt != 0 then rd = rs -
function:001010;MOVZ(Move Conditional on Zero)
汇编格式:MOVZ rd, rs, rt
功能描述:if rt = 0 then rd = rs -
function:010000;MFHI(Move From HI)
汇编格式:MFHI rd
功能描述:将 HI 寄存器的值写入到寄存器 rd 中
操作定义:GPR[rd] ← HI -
function:010010;MFLO(Move From LO)
汇编格式:MFLO rd
功能描述:将 LO 寄存器的值写入到寄存器 rd 中
操作定义:GPR[rd] ← LO -
function:010001;MTHI(Move To HI)
汇编格式:MTHI rs
功能描述:将寄存器 rs 的 值 写入到 HI 寄存器中
操作定义:GPR[rd] ←HI -
function:010011;MTLO(Move To LO)
汇编格式:MTLO rs
功能描述:将寄存器 rs 的 值 写入到 LO 寄存器中
操作定义:GPR[rd] ← LO
2. 移动操作指令实现思路
2.1 movn、movz指令实现思路
与第五章实现思路类似,在译码阶段给出运算类型alusel_o、运算子类型aluop_o等信号的值,同时读取地址为rs rt的通用寄存器的值,新增:依据rt寄存器的值,判断是否要将rs中的值写入目的寄存器中。将结果送入执行阶段。
2.2 mthi、mtlo指令实现思路
在译码阶段,读出地址为rs的通用寄存器的值。由于mthi、mtlo指令不写通用寄存器,所以wreg_o为WriteDisable,wd_o为ZeroWord。
在执行阶段,确定要写HI、LO寄存器的情况,以及要写入的值
写回阶段才去修改HI、LO寄存器的值
2.3 mfhi、mflo指令实现思路
这两条指令需要读HI、LO寄存器的值,设计在执行阶段才能读取到。
2.4 改变数据流
- 添加了HILO模块,并且该模块添加到了写回阶段
- 将HI、LO寄存器的值前递到执行模块,在执行模块添加了一个选择模块,用于选择参与运算的数据。如果是mfhi、mflo指令,那就会选择传递过来的HI、LO寄存器的值
2.5 如何解决新的数据相关问题
进一步考虑mfhi、mflo指令的处理过程,这两条指令会在流水线的执行阶段读取HI、LO寄存器的值,如果直接采用HILO模块给出的HI、LO寄存器的值,可能不会程序希望的正确值。因为此时处于访存、写回阶段的指令可能会修改HI、LO寄存器的值。
lui $1,0x0000 // $1 = 0x00000000
lui $2,0xffff // $2 = 0xffff0000
mthi $0 // hi = 0x00000000
mthi $1 // hi = 0x00000000
mthi $2 // hi = 0xffff0000
mfhi $4 // $4 = 0xffff0000
指令3 4 5都要改变HI寄存器的值,当指令6处于执行阶段时,指令5处于访存阶段,指令4处于写回阶段,此时HI寄存器的值是指令3刚刚写入的0x00000000,HILO模块将该值前递给执行阶段,从而得到错误结果,正确的值应该是处于访存阶段的指令5要写入的数据。
似曾相识?这就是第5章介绍的数据相关问题,解决方法还是使用数据前递。将处于访存阶段、写回阶段的指令对HI、LO寄存器的操作信息反馈到执行阶段。
2.6 系统结构的修改
- 在写回阶段添加了HILO模块,用于实现HI、LO寄存器
- 执行阶段的EX模块中添加了与HI、LO寄存器有关的输入接口,包括为了解决HI、LO寄存器的数据相关问题而引入的接口。
- 执行阶段的EX模块中添加了whilo_o、hi_o、lo_o输出接口,分别表示是否要写HILO、要写入HI寄存器的值、要写入LO寄存器的值。这三个接口传递出来的信息会一直通过后面的模块到达写回阶段的HILO模块。
3. 修改OpenMIPS
3.1 HILO寄存器的实现
`include "defines.v"
`timescale 1ns/1ps
module hilo_reg (
input wire rst,
input wire clk,
input wire we, // HI、LO寄存器写使能
input wire [`RegBus] hi_i,
input wire [`RegBus] lo_i,
output reg [`RegBus] hi_o,
output reg [`RegBus] lo_o
);
always @(*) begin
if (rst == `RstEnable) begin
hi_o <= `ZeroWord;
lo_o <= `ZeroWord;
end else if (we == `WriteEnable) begin
hi_o <= hi_i;
lo_o <= lo_i;
end
end
endmodule
3.2 修改ID模块
这六条指令都是SPECIAL类指令,只需要功能码便可判断是哪条指令。
首先在宏定义中添加这六条指令的功能码。
`define EXE_MOVZ 6'b001010 // 指令movz的指令码
`define EXE_MOVN 6'b001011 // 指令movn的指令码
`define EXE_MFHI 6'b010000 // 指令mfhi的指令码
`define EXE_MTHI 6'b010001 // 指令mthi的指令码
`define EXE_MFLO 6'b010010 // 指令mflo的指令码
`define EXE_MTLO 6'b010011 // 指令mtlo的指令码
//AluOp
`define EXE_MOVZ_OP 8'b00001010
`define EXE_MOVN_OP 8'b00001011
`define EXE_MFHI_OP 8'b00010000
`define EXE_MTHI_OP 8'b00010001
`define EXE_MFLO_OP 8'b00010010
`define EXE_MTLO_OP 8'b00010011
//AluSel
`define EXE_RES_MOVE 3'b011 // 移动操作
修改ID模块:
case (op3)
......
`EXE_MFHI: begin
wreg_o <= `WriteEnable;
aluop_o <= `EXE_MFHI_OP;
alusel_o <= `EXE_RES_MOVE;
reg1_read_o <= `ReadDisable;
reg2_read_o <= `ReadDisable;
instvalid <= `InstValid;
end
`EXE_MFLO: begin
wreg_o <= `WriteEnable;
aluop_o <= `EXE_MFLO_OP;
alusel_o <= `EXE_RES_MOVE;
reg1_read_o <= `ReadDisable;
reg2_read_o <= `ReadDisable;
instvalid <= `InstValid;
end
`EXE_MTHI: begin
wreg_o <= `WriteDisable;
aluop_o <= `EXE_MTHI_OP;
reg1_read_o <= `ReadEnable;
reg2_read_o <= `ReadDisable;
instvalid <= `InstValid;
end
`EXE_MTLO: begin
wreg_o <= `WriteDisable;
aluop_o <= `EXE_MTLO_OP;
reg1_read_o <= `ReadEnable;
reg2_read_o <= `ReadDisable;
instvalid <= `InstValid;
end
`EXE_MOVN: begin
aluop_o <= `EXE_MOVN_OP;
alusel_o <= `EXE_RES_MOVE;
reg1_read_o <= `ReadEnable;
reg2_read_o <= `ReadEnable;
instvalid <= `InstValid;
// reg2_o的值就是地址为rt的通用寄存器的值
if (reg2_o != `ZeroWord) begin
wreg_o <= `WriteEnable;
end else begin
wreg_o <= `WriteDisable;
end
end
`EXE_MOVZ_OP: begin
aluop_o <= `EXE_MOVZ_OP;
alusel_o <= `EXE_RES_MOVE;
reg1_read_o <= `ReadEnable;
reg2_read_o <= `ReadEnable;
instvalid <= `InstValid;
// reg2_o的值就是地址为rt的通用寄存器的值
if (reg2_o == `ZeroWord) begin
wreg_o <= `WriteEnable;
end else begin
wreg_o <= `WriteDisable;
end
end
default: begin
end
endcase //op3
几点说明:
- 除mthi、mvlo指令外的其余4条移动操作指令的运算类型alusel_o均为EXE_RES_MOVE
- mthi、mvlo指令需要修改HI、LO寄存器,但是不需要改变通用寄存器,所以在其译码阶段wreg_o为WriteDisable。设置reg1_read_o为1,表示需要通过Regfile模块读端口1读取通用寄存器的值,默认为指令第21~25bit,即rs
- movz指令的译码阶段需要读取rs、rt寄存器的值,要设置reg1_read_o、reg2_read_o均为ReadEnable。
3.3 修改执行阶段
3.3.1 修改EX模块
module ex (
......
// HILO模块给出的HI、LO寄存器的值
input wire [`RegBus] hi_i,
input wire [`RegBus] lo_i,
// 访存阶段的指令是否要写HI、LO,用于检测HI、LO寄存器带来的数据相关问题
input wire mem_whilo_i,
input wire [`RegBus] mem_hi_i,
input wire [`RegBus] mem_lo_i,
// 写回阶段的指令是否要写HI、LO,用于检测HI、LO寄存器带来的数据相关问题
input wire wb_whilo_i,
input wire [`RegBus] wb_hi_i,
input wire [`RegBus] wb_lo_i,
// 处于执行阶段的指令对HI、LO寄存器的写操作请求
output reg whilo_o,
output reg [`RegBus] hi_o, // 执行阶段的指令要写入HI寄存器的值
output reg [`RegBus] lo_o, // 执行阶段的指令要写入LO寄存器的值
......
);
......
/************************** 移动操作 **************************/
// 得到HILO寄存器的值,此处要解决数据相关问题
always @(*) begin
if (rst == `RstEnable) begin
{HI,LO} <= {`ZeroWord,`ZeroWord};
end else if (mem_whilo_i == `WriteEnable) begin
{HI,LO} <= {mem_hi_i,mem_lo_i};
end else if (wb_whilo_i == `WriteEnable) begin
{HI,LO} <= {wb_hi_i,wb_lo_i};
end else begin
{HI,LO} <= {hi_i,lo_i};
end
end
// MFHI、MFLI、MOVZ、MOVN指令
always @(*) begin
if (rst == `RstEnable) begin
moveres <= `ZeroWord;
end else begin
moveres <= `ZeroWord;
case (aluop_i)
`EXE_MFHI_OP: begin
moveres <= HI;
end
`EXE_MFLO_OP: begin
moveres <= LO;
end
`EXE_MOVZ_OP: begin
moveres <= reg1_i; // reg1_i默认为标号rs的寄存器的值
end
`EXE_MOVN_OP: begin
moveres <= reg1_i;
end
default: begin
end
endcase
end
end
/************************** 依据aluop_i选择最终的运算结果 **************************/
always @(*) begin
wd_o <= wd_i;
wreg_o <= wreg_i;
case (alusel_i)
`EXE_RES_LOGIC: begin
wdata_o <= logicout;
end
`EXE_RES_SHIFT: begin
wdata_o <= shiftres;
end
`EXE_RES_MOVE: begin
wdata_o <= moveres;
end
default: begin
wdata_o <= `ZeroWord;
end
endcase
end
/********** 如果是MTHI、MTLO指令,那么需要给出whilo_o、hi_o和lo_o的值 **********/
always @(*) begin
if (rst == `RstEnable) begin
whilo_o <= `WriteDisable;
hi_o <= `ZeroWord;
lo_o <= `ZeroWord;
end else if (aluop_i == `EXE_MTHI_OP) begin
whilo_o <= `WriteEnable;
hi_o <= reg1_i;
lo_o <= LO; // 写HI寄存器,LO寄存器不变
end else if (aluop_i == `EXE_MTLO_OP) begin
whilo_o <= `WriteEnable;
hi_o <= HI; // 写LO寄存器,HI寄存器不变
lo_o <= reg1_i; // 注意是reg1_i
end else begin
whilo_o <= `WriteDisable;
hi_o <= `ZeroWord;
lo_o <= `ZeroWord;
end
end
.......
几点说明:
- 在获取HILO寄存器值时,判断正处于访存阶段、写回阶段的指令是否要写HILO寄存器。如果要写,则就将要写入的值前递;如果不需要写入,就取HILO寄存器现存的值
- movz、movn指令的结果movres都赋值reg1_i(rs标号寄存器的值),在译码阶段就已根据rt标号寄存器的值设置了wreg_o来选择是否将结果写入rd
- 如果是MTHI、MTLO指令,那么需要给出whilo_o、hi_o和lo_o的值。写HI寄存器时,LO寄存器不变;写LO寄存器时,HI寄存器不变。两者都是利用reg1_i(rs标号寄存器的值)写入
3.3.2 修改EX/MEM模块
module ex_mem (
input wire clk,
input wire rst,
// 来自执行阶段的信息
input wire[`RegBus] ex_wdata,
input wire[`RegAddrBus] ex_wd,
input wire ex_wreg,
input wire ex_whilo,
input wire[`RegBus] ex_hi,
input wire[`RegBus] ex_lo,
// 传递到访存阶段的信息
output reg[`RegBus] mem_wdata,
output reg[`RegAddrBus] mem_wd,
output reg mem_wreg,
output reg mem_whilo,
output reg[`RegBus] mem_hi,
output reg[`RegBus] mem_lo
);
always @(posedge clk) begin
if (rst == `RstEnable) begin
mem_wdata <= `ZeroWord;
mem_wd <= `NOPRegAddr;
mem_wreg <= `WriteDisable;
mem_whilo <= `WriteDisable;
mem_hi <= `ZeroWord;
mem_lo <= `ZeroWord;
end else begin
mem_wdata <= ex_wdata;
mem_wd <= ex_wd;
mem_wreg <= ex_wreg;
mem_whilo <= ex_whilo;
mem_hi <= ex_hi;
mem_lo <= ex_lo;
end
end
endmodule
3.4 修改访存阶段
3.4.1 修改MEM模块
`include "defines.v"
`timescale 1ns/1ps
module mem (
input wire rst,
// 来自执行阶段的信息
input wire[`RegBus] wdata_i,
input wire[`RegAddrBus] wd_i,
input wire wreg_i,
input wire whilo_i,
input wire[`RegBus] hi_i,
input wire[`RegBus] lo_i,
// 访存阶段的结果
output reg[`RegBus] wdata_o,
output reg[`RegAddrBus] wd_o,
output reg wreg_o,
output reg whilo_o,
output reg[`RegBus] hi_o,
output reg[`RegBus] lo_o
);
always @(*) begin
if (rst == `RstEnable) begin
wdata_o <= `ZeroWord;
wd_o <= `NOPRegAddr;
wreg_o <= `WriteDisable;
whilo_o <= `WriteDisable;
hi_o <= `ZeroWord;
lo_o <= `ZeroWord;
end else begin
wdata_o <= wdata_i;
wd_o <= wd_i;
wreg_o <= wreg_i;
whilo_o <= whilo_i;
hi_o <= hi_i;
lo_o <= lo_i;
end
end
endmodule
3.4.2 修改MEM/WB模块
`include "defines.v"
`timescale 1ns/1ps
module mem_wb(
input wire rst,
input wire clk,
// 访存阶段的结果
input wire[`RegBus] mem_wdata,
input wire[`RegAddrBus] mem_wd,
input wire mem_wreg,
input wire mem_whilo,
input wire[`RegBus] mem_hi,
input wire[`RegBus] mem_lo,
// 传递到写回阶段的信息
output reg[`RegBus] wb_wdata,
output reg[`RegAddrBus] wb_wd,
output reg wb_wreg,
output reg wb_whilo,
output reg[`RegBus] wb_hi,
output reg[`RegBus] wb_lo
);
always @(*) begin
if (rst == `RstEnable) begin
wb_wdata <= `ZeroWord;
wb_wd <= `NOPRegAddr;
wb_wreg <= `WriteDisable;
wb_whilo <= `WriteDisable;
wb_hi <= `ZeroWord;
wb_lo <= `ZeroWord;
end else begin
wb_wdata <= mem_wdata;
wb_wd <= mem_wd;
wb_wreg <= mem_wreg;
wb_whilo <= mem_whilo;
wb_hi <= mem_hi;
wb_lo <= mem_lo;
end
end
endmodule
3.5 修改OpenMIPS顶层结构
在原先的文件基础上,根据上面的修改过后的结构图将各接口新增的接口连接起来。
......
// 连接执行阶段EX模块的输出与EX/MEM模块输入的变量
wire ex_whilo_o;
wire[`RegBus] ex_hi_o;
wire[`RegBus] ex_lo_o;
// 连接EX/MEM模块的输出与访存阶段MEM模块的输入的变量
wire mem_whilo_i;
wire[`RegBus] mem_hi_i;
wire[`RegBus] mem_lo_i;
// 连接访存阶段MEM模块输出与MEM/WB模块输入的变量
wire mem_whilo_o;
wire[`RegBus] mem_hi_o;
wire[`RegBus] mem_lo_o;
// 连接MEM/WB模块输出与写回阶段的输入的变量
wire wb_whilo_i;
wire[`RegBus] wb_hi_i;
wire[`RegBus] wb_lo_i;
// 连接HILO寄存器与EX模块
wire[`RegBus] hi;
wire[`RegBus] lo;
......
// EX模块实例化
ex ex0(
......
.hi_i(hi),
.lo_i(lo),
.mem_whilo_i(mem_whilo_o),
.mem_hi_i(mem_hi_o),
.mem_lo_i(mem_lo_o),
.wb_whilo_i(wb_whilo_i),
.wb_hi_i(wb_hi_i),
.wb_lo_i(wb_lo_i),
.whilo_o(ex_whilo_o),
.hi_o(ex_hi_o),
.lo_o(ex_lo_o)
);
// EX/MEM模块实例化
ex_mem ex_mem0(
......
.ex_hi(ex_hi_o),
.ex_lo(ex_lo_o),
.ex_whilo(ex_whilo_o),
.mem_hi(mem_hi_i),
.mem_lo(mem_lo_i),
.mem_whilo(mem_whilo_i)
);
// MEM模块实例化
mem mem0(
......
.hi_i(mem_hi_i),
.lo_i(mem_lo_i),
.whilo_i(mem_whilo_i),
.hi_o(mem_hi_o),
.lo_o(mem_lo_o),
.whilo_o(mem_whilo_o)
);
// MEM/WB模块实例化
mem_wb mem_wb0(
......
.mem_hi(mem_hi_o),
.mem_lo(mem_lo_o),
.mem_whilo(mem_whilo_o),
.wb_hi(wb_hi_i),
.wb_lo(wb_lo_i),
.wb_whilo(wb_whilo_i)
);
// HILO寄存器实例化
hilo_reg hilo_reg0(
.clk(clk),
.rst(rst),
// 来自MEM/WB模块的信息
.we(wb_whilo_i),
.hi_i(wb_hi_i),
.lo_i(wb_lo_i),
// 传递到EX模块的信息
.hi_o(hi),
.lo_o(lo)
);
......
4. 测试程序
.org 0x0
.set noat
.global _start
_start:
lui $1,0x0000 # $1 = 0x00000000
lui $2,0xffff # $2 = 0xffff0000
lui $3,0x0505 # $3 = 0x05050000
lui $4,0x0000 # $4 = 0x00000000
movz $4,$2,$1 # $4 = 0xffff0000
movn $4,$3,$1 # $4 = 0xffff0000
movn $4,$3,$2 # $4 = 0x05050000
movz $4,$2,$3 # $4 = 0x05050000
mthi $0 # hi = 0x00000000
mthi $2 # hi = 0xffff0000
mthi $3 # hi = 0x05050000
mfhi $4 # $4 = 0x05050000
mtlo $3 # li = 0x05050000
mtlo $2 # li = 0xffff0000
mtlo $1 # li = 0x00000000
mflo $4 # $4 = 0x00000000
3c010000
3c02ffff
3c030505
3c040000
0041200a
0061200b
0062200b
0043200a
00000011
00400011
00600011
00002010
00600013
00400013
00200013
00002012
参考资料
- 《自己动手写CPU》
- 《MIPS基准指令集手册_v1.00》