自己动手写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指令
由指令格式可以看出這六條指令指令碼都是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
2.3 ov_sum
計算是否溢出,加法指令(add和addi)、減法指令(sub)執行的時候,需要判斷是否溢出,滿足一下兩種情況時,有溢出:
- reg1_i為正數,reg2_i_mux為正數,但是兩者之和為負數
- reg1_i為負數,reg2_i_mux為負數,但是兩者之和為正數
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
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 = 0x8000sll $1,$1,16 # $1 = 0x80000000ori $1,$1,0x0010 # $1 = 0x80000010 給$1賦值ori $2,$0,0x8000 # $2 = 0x8000sll $2,$2,16 # $2 = 0x80000000ori $2,$2,0x0001 # $2 = 0x80000001 給$2賦值ori $3,$0,0x0000 # $3 = 0x00000000addu $3,$2,$1 # $3 = 0x00000011 $1加$2,無符號加法ori $3,$0,0x0000 # $3 = 0x00000000add $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 = 0x00000000addiu $3,$3,0x8000 # $3 = 0xffff8000 $3加0xffff8000 無符號加法2. 測試slt、sltu、slti、sltiu
or $1,$0,0xffff # $1 = 0xffffsll $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 = 0xffff0000ori $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,16ori $1,$1,0xfffb # $1 = -5 給$1賦值ori $2,$0,6 # $2 = 6 給$2賦值mul $3,$1,$2 # $3 = -30 = 0xffffffe2 $1 乘以$2,有符號乘法,結果低32位保存到$3mult $1,$2 # hi = 0xffffffff # lo = 0xffffffe2# $1 乘以$2,有符號乘法,結果低32位保存到HI LOmultu $1,$2 # hi = 0x5# lo = 0xffffffe2# $1 乘以$2,無符號乘法,結果低32位保存到HI LOnopnop實驗心得
1、ov_sum是否溢出信號需在執行階段最后選擇運算結果時進行判斷,如果發生溢出行為則將寫寄存器堆的信號取消使能;
2、mult和multu指令均不對寄存器堆進行回寫操作,僅改變特殊寄存器HI和LO的值,所以譯碼階段這兩個指令的alusel_o信號均需設置成EXE_RES_NOP,不對寄存器堆進行回寫操作;
3、由仿真的波形圖發現譯碼階段的EXE_MUL指令的回寫地址填錯了,已修正;
項目鏈接
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的自己动手写CPU(5)简单算术操作指令实现_1的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 自己动手写CPU(4)移动操作指令的实现
- 下一篇: 自己动手写CPU(6)流水线暂停、乘累加