自己动手写CPU(5)简单算术操作指令实现_1

自己动手写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指令

1.png

由指令格式可以看出这六条指令指令码都是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指令

2.png

我们由指令格式可以看出,依据指令码(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指令

3.png

由指令格式可以看出,这两条指令的指令码(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指令

4.png

由指令格式可以看出,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 无符号加法

算术指令仿真测试结果1.png

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,无符号比较

算术指令仿真测试结果2.png

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”的个数

算术指令仿真测试结果3.png

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

算术指令仿真测试结果4.png

实验心得

1、ov_sum是否溢出信号需在执行阶段最后选择运算结果时进行判断,如果发生溢出行为则将写寄存器堆的信号取消使能;

2、multmultu指令均不对寄存器堆进行回写操作,仅改变特殊寄存器HI和LO的值,所以译码阶段这两个指令的alusel_o信号均需设置成EXE_RES_NOP,不对寄存器堆进行回写操作;

3、由仿真的波形图发现译码阶段的EXE_MUL指令的回写地址填错了,已修正;

项目链接

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

日拱一卒_未来可期

若复习顺利望有闲钱的同学支持下

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值