自己动手写CPU(5)简单算数操作指令实现_1
指令介绍
MIPS32指令集架构定义的所有算术操作指令,共有21条 共有三类,分别是:
- 简单算术指令
- 乘累加、乘累减指令
- 除法指令
算术指令操作介绍
一共有15条指令分别是:add、addi、addiu、addu、sub、subu、clo、clz、slt、slti、sltiu、sltu、mul、mult、multu
1.add、addu、sub、subu、slt、sltu指令
由指令格式可以看出这六条指令指令码都是6’b000000即SPECIAL类,而且指令的第610bit都是0,根据指令的功能码(05bit)来判断是哪一条指令
- add(功能码6’b100000):加法运算,用法:add rd,rs,rt;作用:rd <- rs+rt,将地址为rs的通用寄存器的值与地址为rt的通用寄存器的值进行加法运算,结果保存到地址为rd的通用寄存器中。如果加法运算溢出,那么会产生溢出异常,同时不保存结果。
- addu(功能码是6’b100001):加法运算,用法:addu rd,rs,rt; 作用:rd <-rs+rd,将地址为rs的通用寄存器的值与地址为rt的通用寄存器的值进行加法运算,结果保存到rd的通用寄存器中。不进行溢出检查,总是将结果保存到目的寄存器。
- SUB(功能码是6’b100010):减法运算,用法:sub rd,rs,rt; 作用:rd <- rs-rt,将地址为rs的通用寄存器的值与地址为rt的通用寄存器的值进行减法运算,结果保存到地址为rd的通用寄存器中。如果减法运算溢出,那么产生溢出异常,同时不保存结果。
- SUBU(功能码是6’b100011):减法运算,用法:subu rd,rs,rt; 作用:rd <- rs-rt将地址为rs的通用寄存器的值与地址为rt的通用寄存器的值进行减法运算,结果保存到地址为rd的通用寄存器中。不进行溢出检查,总是将结果保存到目的寄存器。
- SLT(功能码是6’b101010):比较运算,用法:slt rd,rs,rt; 作用:rd <- (rs<rt)将地址为rs的通用寄存器的值与地址为rt的通用寄存器的值按照有符号数进行比较,若前者小于后者,那么将1保存到地址为rd的通用寄存器,若前者大于后者,则将0保存到地址为rd的通用寄存器中。
- SLTU(功能码是6’b101011):比较运算,用法:sltu rd,rs,rt; 作用:rd <- (rs<rt)将地址为rs的通用寄存器的值与地址为rt的通用寄存器的值按照无符号数进行比较,若前者小于后者,那么将1保存到地址为rd的通用寄存器,若前者大于后者,则将0保存到地址为rd的通用寄存器中。
2. addi、addiu、slti、sltiu指令
我们由指令格式可以看出,依据指令码(26~31bit)判断是哪一种指令
- ADDI(指令码是6’b001000):加法运算,用法:addi rt,rs,immediate; 作用:rt <- rs+(sign_extended)immediate,将指令中16位立即数进行符号扩展,与地址为rs的通用寄存器进行加法运算,结果保存到地址为rt的通用寄存器。如果加法运算溢出,则产生溢出异常,同时不保存结果。
- ADDIU(指令码是6’b001001):加法运算,用法:addiu rt,rs,immediate; 作用:rt <- rs+(sign_extended)immediate,将指令中16位立即数进行符号扩展,与地址为rs的通用寄存器进行加法运算,结果保存到地址为rt的通用寄存器。不进行溢出检查,总是将结果保存到目的寄存器。
- SLTI(功能码是6’b001010):比较运算,用法:slti rt,rs,immediate; 作用:rt <- (rs<(sign_extended)immediate)将指令中的16位立即数进行符号扩展,与地址为rs的通用寄存器的值按照有符号数进行比较,若前者小于后者,那么将1保存到地址为rt的通用寄存器,若前者大于后者,则将0保存到地址为rt的通用寄存器中。
- SLTIU(功能码是6’b001011):比较运算,用法:sltiu rt,rs,immediate; 作用:rt <- (rs<(sign_extended)immediate)将指令中的16位立即数进行符号扩展,与地址为rs的通用寄存器的值按照无符号数进行比较,若前者小于后者,那么将1保存到地址为rt的通用寄存器,若前者大于后者,则将0保存到地址为rt的通用寄存器中。
3. clo、clz指令
由指令格式可以看出,这两条指令的指令码(2631bit)都是6’b011100,即是SPECIAL2类;而且第610bit都为0,根据指令中的功能码(0~5bit)判断是哪一条指令。
- CLZ(功能码是6’b100000):计数运算,用法:clz rd,rs; 作用:rd <- coun_leading_zeros rs,对地址为rs的通用寄存器的值,从最高位开始向最低位方向检查,直到遇到值为“1”的位,将该为之前“0”的个数保存到地址为rd的通用寄存器中,如果地址为rs的通用寄存器的所有位都为0(即0x00000000),那么将32保存到地址为rd的通用寄存器中。
- CLO(功能码是6’b100001):计数运算,用法:clo,rd,rs; 作用:rd <- coun_leading_zeros rs对地址为rs的通用寄存器的值,从最高位开始向最低位方向检查,直到遇到值为“0”的位,将该为之前“1”的个数保存到地址为rd的通用寄存器中,如果地址为rs的通用寄存器的所有位都为1(即0xFFFFFFFF),那么将32保存到地址为rd的通用寄存器中。
4. multu、mult、mul指令
由指令格式可以看出,mul指令的指令码(2631bit)都是6’b011100,即是SPECIAL2类,mult和multu这两条指令的指令码(2631bit)都是6’b000000,即是SPECIAL类;有着不同的功能码(0~5bit)
- mul(指令码是SPECIAL2,功能码是6’b000010):乘法运算,用法:mul,rd,rs,st; 作用:rd <- rs * rt,将地址为rs的通用寄存器的值与地址为rt的通用寄存器的值作为有符号数相乘,乘法结果的低32bit保存到地址为rd的通用寄存器中。
- mult(指令码是SPECIAL,功能码是6’b011000):乘法运算,用法:mult,rs,st; 作用:{hi,lo} <- rs * rt,将地址为rs的通用寄存器的值与地址为rt的通用寄存器的值作为有符号数相乘,乘法结果低32bit保存到LO寄存器中,高32bit保存到HI寄存器中。
- multu(指令码是SPECIAL,功能码是6’b011001):乘法运算,用法:mult,rs,st; 作用:{hi,lo} <- rs * rt,将地址为rs的通用寄存器的值与地址为rt的通用寄存器的值作为无符号数相乘,乘法结果低32bit保存到LO寄存器中,高32bit保存到HI寄存器中。
修改之处
译码阶段
- add、addu、sub、subu、slt、sltu:需要两个寄存器的值分别作为两个操作数,所以设置reg1_read_o和reg2_read_o都为1,运算完后结果需要写入目的寄存器,所以设置wreg_o为WriteEnable,写入目的寄存器地址wd_o是指令中16~20bit的值。
- addi、addiu、subi、subiu:只需要读取一个寄存器的值作为第一个操作数,即设置reg1_read_o为1,reg2_read_o为0,第二个操作数为立即数进行符号扩展后的值,运算完后结果需要写入目的寄存器,所以设置wreg_o为WriteEnable,写入目的寄存器地址wd_o是指令中16~20bit的值。
- mult、multu:需要两个寄存器的值分别作为两个操作数,所以设置reg1_read_o和reg2_read_o都为1,运算完后结果需要不需要写入通用寄存器,而是写入HI、LO寄存器所以设置wreg_o为WriteDisable。
- mul:需要两个寄存器的值分别作为两个操作数,所以设置reg1_read_o和reg2_read_o都为1,aluop_o为EXE_MUL_OP运算完后结果需要写入目的寄存器,所以设置wreg_o为WriteEnable,写入目的寄存器地址wd_o是指令中11~15bit的值。
- clo、clz:只需要读取一个寄存器的值作为第一个操作数,即设置reg1_read_o为1,reg2_read_o为0,运算完后结果需要写入目的寄存器,所以设置wreg_o为WriteEnable,写入目的寄存器地址wd_o是指令中11~15bit的值。
执行阶段
根据译码阶段的结果,来进行相关的执行操作
1.添加一些新的相关变量
reg[`RegBus] arithmeticres; //保存算术运算结果
wire ov_sum; //保存溢出情况
wire reg1_eq_reg2; //第一个操作数是否等于第二个操作数
wire reg1_lt_reg2; //第一个操作数是否小于第二个操作数
wire[`RegBus] reg2_i_mux; //保存输入的第二个操作reg2_i的补码
wire[`RegBus] reg1_i_not; //保存输入的第一个操作数reg1_i取反后的值
wire[`RegBus] result_sum; //保存加法结果
wire[`RegBus] opdata1_mult; //乘法操作中的被乘数
wire[`RegBus] opdata2_mult; //乘法操作中的乘数
wire[`DoubleRegBus] hilo_temp; //临时保存乘法结果,宽度为64位
reg[`DoubleRegBus] mulres; //保存乘法结果,宽度为64位
2.计算相关变量的值
2.1 reg2_i_mux
如果是减法或者有符号比较运算,那么reg2_i_mux等于第二个操作数reg2_i的补码,否则reg2_i_mux等于第二个操作数reg2_i
assign reg2_i_mux = ((aluop_i == `EXE_SUB_OP) || (aluop_i == `EXE_SUBU_OP) ||
(aluop_i == `EXE_SLT_OP)) ? (~reg2_i)+1 : reg2_i;
2.2 result_sum
- 如果是加法运算,此时reg2_i_mux就是第二个操作数reg2_i,所以result_sum就是加法运算的结果
- 如果是减法运算,此时reg2_i_mux是第二个操作数reg2_i的补码,所以result_sum就是减法运算的结果
- 如果是有符号比较运算,此时reg2_i_mux也是第二个操作数reg2_i的补码,所以result_sum也是减法运算的结果,可以通过判断减法结果是否小于零,进而判断第一个操作数reg1_i是否小于第二个操作数reg2_i
assign result_sum = reg1_i + reg2_i_mux;
2.3 ov_sum
计算是否溢出,加法指令(add和addi)、减法指令(sub)执行的时候,需要判断是否溢出,满足一下两种情况时,有溢出:
- reg1_i为正数,reg2_i_mux为正数,但是两者之和为负数
- reg1_i为负数,reg2_i_mux为负数,但是两者之和为正数
assign ov_sum = ((!reg1_i[31] && !reg2_i_mux[31]) && result_sum[31]) ||
((reg1_i[31] && reg2_i_mux[31]) && (!result_sum[31]));
2.4 reg1_lt_reg2
计算操作数1是否小于操作数2,分两种情况
-
aluop_i为EXE_SLT_OP表示有符号比较运算:
reg1_i为负数、reg2_i为正数,显然reg1_i小于reg2_i
reg1_i为正数、reg2_i为正数,并且reg1_i减去reg2_i的值小于0(即result_sum为负),此时也有reg1_i小于reg2_i
reg1_i为负数、reg2_i为负数,并且并且reg1_i减去reg2_i的值小于0(即result_sum为负),此时也有reg1_i小于reg2_i
-
无符号数比较的时候u,直接使用比较运算符比较reg1_i与reg2_i
assign reg1_lt_reg2 = ((aluop_i == `EXE_SLT_OP)) ? ((reg1_i[31] && !reg2_i[31])
|| (!reg1_i[31] && !reg2_i[31] && result_sum[31])
|| (reg1_i[31] && reg2_i[31] && result_sum[31])) : (reg1_i < reg2_i);
2.5 reg1_i_not
对操作数1逐位取反,赋给reg1_i_not
assign reg1_i_not = ~reg1_i;
仿真结果
1. 测试add、addi、addiu、addu、sub、subu指令
ori $1,$0,0x8000 # $1 = 0x8000
sll $1,$1,16 # $1 = 0x80000000
ori $1,$1,0x0010 # $1 = 0x80000010 给$1赋值
ori $2,$0,0x8000 # $2 = 0x8000
sll $2,$2,16 # $2 = 0x80000000
ori $2,$2,0x0001 # $2 = 0x80000001 给$2赋值
ori $3,$0,0x0000 # $3 = 0x00000000
addu $3,$2,$1 # $3 = 0x00000011 $1加$2,无符号加法
ori $3,$0,0x0000 # $3 = 0x00000000
add $3,$2,$1 # $2 加 $1,有符号加法,结果溢出,$3保持不变
sub $3,$1,$3 # $3 = 0x80000010 $1减去$3,有符号减法
subu $3,$3,$2 # $3 = 0xF $3减去$2,无符号减法
addi $3,$3,2 # $3 = 0x11 $3 加2,有符号加法
ori $3,$0,0x0000 # $3 = 0x00000000
addiu $3,$3,0x8000 # $3 = 0xffff8000 $3加0xffff8000 无符号加法
2. 测试slt、sltu、slti、sltiu
or $1,$0,0xffff # $1 = 0xffff
sll $1,$1,16 # $1 = 0xffff0000 给$1赋值
slt $2,$1,$0 # $2 = 1 比较$1与0x0,有符号比较
sltu $2,$1,$0 # $2 = 0 比较$1与0x0,无符号比较
slti $2,$1,0x8000 # $2 = 1 比较$1与0xffff8000,有符号比较
sltiu $2,$1,0x8000 # $2 = 1 比较$1与0xffff8000,无符号比较
3. 测试clo和clz指令
lui $1,0x0000 # $1 = 0x00000000 给$1赋值
clo $2,$1 # $2 = 0x00000000 统计$1中“1”之前“0”的个数
clz $2,$1 # $2 = 0x00000020 统计$1中“0”之前“1”的个数
lui $1,0xffff # $1 = 0xffff0000
ori $1,$1,0xffff # $1 = 0xffffffff 给$1赋值
clz $2,$1 # $2 = 0x00000000 统计$1中“1”之前“0”的个数
clo $2,$1 # $2 = 0x00000020 统计$1中“0”之前“1”的个数
lui $1,0xa100 # $1 = 0xa1000000 给$1赋值
clz $2,$1 # $2 = 0x00000000 统计$1中“1”之前“0”的个数
clo $2,$1 # $2 = 0x00000001 统计$1中“0”之前“1”的个数
lui $1,0x1100 # $1 = 0x11000000 给$1赋值
clz $2,$1 # $2 = 0x00000003 统计$1中“1”之前“0”的个数
clo $2,$1 # $2 = 0x00000000 统计$1中“0”之前“1”的个数
4. 测试mul、mult、multu指令
ori $1,$0,0xffff
sll $1,$1,16
ori $1,$1,0xfffb # $1 = -5 给$1赋值
ori $2,$0,6 # $2 = 6 给$2赋值
mul $3,$1,$2 # $3 = -30 = 0xffffffe2 $1 乘以$2,有符号乘法,结果低32位保存到$3
mult $1,$2 # hi = 0xffffffff
# lo = 0xffffffe2
# $1 乘以$2,有符号乘法,结果低32位保存到HI LO
multu $1,$2 # hi = 0x5
# lo = 0xffffffe2
# $1 乘以$2,无符号乘法,结果低32位保存到HI LO
nop
nop
实验心得
1、ov_sum
是否溢出信号需在执行阶段最后选择运算结果时进行判断,如果发生溢出行为则将写寄存器堆的信号取消使能;
2、mult
和multu
指令均不对寄存器堆进行回写操作,仅改变特殊寄存器HI和LO的值,所以译码阶段这两个指令的alusel_o
信号均需设置成EXE_RES_NOP
,不对寄存器堆进行回写操作;
3、由仿真的波形图发现译码阶段的EXE_MUL
指令的回写地址填错了,已修正;