简单算术操作指令的实现
1. 代码模块
1.1 宏定义defines
新增关于具体指令的宏定义
- 需要注意的是EXE_ADD = EXE_CLZ、EXE_ADDU = EXE_CLO,但由于指令码不同,他们在译码阶段是可区分的,但在执行阶段是靠译码阶段给的aluop子类型区分具体操作,所以不能简单把6位的功能码补0扩展到8位
`define EXE_ADD 6'b100000
`define EXE_ADDU 6'b100001
`define EXE_CLZ 6'b100000
`define EXE_CLO 6'b100001
`define EXE_ADD_OP 8'b00100000
`define EXE_ADDU_OP 8'b00100001
`define EXE_CLZ_OP 8'b10100000
`define EXE_CLO_OP 8'b10100001
// 与具体指令有关宏定义
`define EXE_SLTI 6'b001010 // op_slti compare_signed
`define EXE_SLTIU 6'b001011 // op_sltiu compare_unsigned
`define EXE_ADDI 6'b001000 // op_addi add_overflow_check
`define EXE_ADDIU 6'b001001 // op_addiu add_no_overflow_check
`define EXE_NOP 6'b000000
`define EXE_SPECIAL_INST 6'b000000 // instruction code of SPECIAL
`define EXE_SPECIAL2_INST 6'b011100 // instruction code of SPECIAL2
`define EXE_SLT 6'b101010 // func_slt compare_signed
`define EXE_SLTU 6'b101011 // func_sltu compare_unsigned
`define EXE_ADD 6'b100000 // func_add add_overflow_check
`define EXE_ADDU 6'b100001 // func_addu add_no_overflow_check
`define EXE_SUB 6'b100010 // func_sub sub_overflow_check
`define EXE_SUBU 6'b100011 // func_subu sub__no_overflow_check
`define EXE_CLZ 6'b100000 // func_clz count_0
`define EXE_CLO 6'b100001 // func_clo count_0
`define EXE_MULT 6'b011000 // func_mult mul_signed_result_in_hilo
`define EXE_MULTU 6'b011001 // func_multu mul_unsigned_result_in_hilo
`define EXE_MUL 6'b000010 // func_mul mul_signed_result_in_rd
// AluOp 子类型
`define EXE_SLT_OP 8'b00101010
`define EXE_SLTU_OP 8'b00101011
`define EXE_ADD_OP 8'b00100000
`define EXE_ADDU_OP 8'b00100001
`define EXE_ADDI_OP 8'b00001000
`define EXE_ADDIU_OP 8'b00001001
`define EXE_SUB_OP 8'b00100010
`define EXE_SUBU_OP 8'b00100011
`define EXE_CLZ_OP 8'b10100000
`define EXE_CLO_OP 8'b10100001
`define EXE_MULT_OP 8'b00011000
`define EXE_MULTU_OP 8'b00011001
`define EXE_MUL_OP 8'b00000010
// AluSel 类型
`define EXE_RES_NOP 3'b000
`define EXE_RES_LOGIC 3'b001 // logic operation
`define EXE_RES_SHIFT 3'b010 // shift operation
`define EXE_RES_MOVE 3'b011 // move operation
`define EXE_RES_ARITHMETIC 3'b100 // arithmetc operation
`define EXE_RES_MUL 3'b101 // mul_signed_result_in_rd
1.2 译码id
添加新的算术指令add、addu、sub、subu、slt、sltu、addi、addiu、slti、sltiu、clz、clo、mul、mult、multu的case分支
- slti、sltiu、addi、addiu都是立即数符号扩展
- mult、multu写入HILO寄存器,所以wreg_o = `WriteDisable
- mul是需要特殊对待的乘法乘法运算
case (op)
`EXE_SPECIAL_INST: begin
case (op2) // [10:6]sa为0
5'b00000: begin
case (op3) // [5:0]func
... ...
`EXE_SLT: begin // slt compare_signed
wreg_o <= `WriteEnable;
aluop_o <= `EXE_SLT_OP;
alusel_o <= `EXE_RES_ARITHMETIC;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b1;
instvalid <= `InstValid;
end
`EXE_SLTU: begin // sltu compare_unsigned
wreg_o <= `WriteEnable;
aluop_o <= `EXE_SLTU_OP;
alusel_o <= `EXE_RES_ARITHMETIC;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b1;
instvalid <= `InstValid;
end
`EXE_ADD: begin // add add_overflow_check
wreg_o <= `WriteEnable;
aluop_o <= `EXE_ADD_OP;
alusel_o <= `EXE_RES_ARITHMETIC;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b1;
instvalid <= `InstValid;
end
`EXE_ADDU: begin // addu add_no_overflow_check
wreg_o <= `WriteEnable;
aluop_o <= `EXE_ADDU_OP;
alusel_o <= `EXE_RES_ARITHMETIC;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b1;
instvalid <= `InstValid;
end
`EXE_SUB: begin // sub sub_overflow_check
wreg_o <= `WriteEnable;
aluop_o <= `EXE_SUB_OP;
alusel_o <= `EXE_RES_ARITHMETIC;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b1;
instvalid <= `InstValid;
end
`EXE_SUBU: begin // subu sub_no_overflow_check
wreg_o <= `WriteEnable;
aluop_o <= `EXE_SUBU_OP;
alusel_o <= `EXE_RES_ARITHMETIC;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b1;
instvalid <= `InstValid;
end
`EXE_MULT: begin // mult mul_signed_result_in_hilo
wreg_o <= `WriteDisable;
aluop_o <= `EXE_MULT_OP;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b1;
instvalid <= `InstValid;
end
`EXE_MULTU: begin // multu mul_unsigned_result_in_hilo
wreg_o <= `WriteDisable;
aluop_o <= `EXE_MULTU_OP;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b1;
instvalid <= `InstValid;
end
default: begin
end
endcase // case (op3)
end
default: begin
end
endcase // case(op2)
end
... ...
`EXE_SLTI: begin // slti compare_signed
wreg_o <= `WriteEnable;
aluop_o <= `EXE_SLT_OP;
alusel_o <= `EXE_RES_ARITHMETIC;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b0;
imm <= { {16{inst_i[15]}}, inst_i[15:0] };
wd_o <= inst_i[20:16];
instvalid <= `InstValid;
end
`EXE_SLTIU: begin // sltiu compare_unsigned
wreg_o <= `WriteEnable;
aluop_o <= `EXE_SLTU_OP;
alusel_o <= `EXE_RES_ARITHMETIC;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b0;
imm <= { {16{inst_i[15]}}, inst_i[15:0] };
wd_o <= inst_i[20:16];
instvalid <= `InstValid;
end
`EXE_ADDI: begin // addi add_singned
wreg_o <= `WriteEnable;
aluop_o <= `EXE_ADDI_OP;
alusel_o <= `EXE_RES_ARITHMETIC;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b0;
imm <= { {16{inst_i[15]}}, inst_i[15:0] };
wd_o <= inst_i[20:16];
instvalid <= `InstValid;
end
`EXE_ADDIU: begin // addiu add_unsigned
wreg_o <= `WriteEnable;
aluop_o <= `EXE_ADDIU_OP;
alusel_o <= `EXE_RES_ARITHMETIC;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b0;
imm <= { {16{inst_i[15]}}, inst_i[15:0] };
wd_o <= inst_i[20:16];
instvalid <= `InstValid;
end
`EXE_SPECIAL2_INST: begin
case (op3)
`EXE_CLZ: begin // clz count_0
wreg_o <= `WriteEnable;
aluop_o <= `EXE_CLZ_OP;
alusel_o <= `EXE_RES_ARITHMETIC;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b0;
instvalid <= `InstValid;
end
`EXE_CLO: begin // clo count_1
wreg_o <= `WriteEnable;
aluop_o <= `EXE_CLO_OP;
alusel_o <= `EXE_RES_ARITHMETIC;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b0;
instvalid <= `InstValid;
end
`EXE_MUL: begin // mul mul_signed_result_in_rd
wreg_o <= `WriteEnable;
aluop_o <= `EXE_MUL_OP;
alusel_o <= `EXE_RES_MUL;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b1;
instvalid <= `InstValid;
end
default: begin
end
endcase // EXE_SPECIAL2_INST case
end
endcase // case(op)
1.3 执行ex
1.3.1. 添加一些新的变量
- arithmeticres保存算术运算类型的运算结果
reg [`RegBus] arithmeticres; // save the result of arithmetic operation
// new variables for arithmetic
wire ov_sum; // save overflow
wire reg1_eq_reg2; // if operand1 = operand2
wire reg1_lt_reg2; // if operand1 < operand2
wire [`RegBus] reg2_i_mux; // the complement of reg2_i
wire [`RegBus] reg1_i_not; // the not of reg1_i
wire [`RegBus] result_sum; // the result of add operation
wire [`RegBus] opdata1_mult; // save the multiplier1
wire [`RegBus] opdata2_mult; // save the multiplier2
wire [`DoubleRegBus] hilo_temp; // save the result of multiplcation 64bits temporary
reg [`DoubleRegBus] mulres; // save the result of multiplication
1.3.2. 计算五个变量的值
- (1) 如果是减法运算或者有符号比较运算,那么reg2_i_mux等于第二个操作数reg2_i的补码,否则等于第二个操作数
- (2) 分三种情况:
A. 如果是加法运算,reg2_i_mux就是第二个操作数reg2_i,
所以result_sum就是加法运算的结果
B. 如果是减法运算,reg2_i_mux就是第二个操作数reg2_i的补码,
所以result_sum就是减法运算的结果
C. 如果是有符号比较运算,reg2_i_mux就是第二个操作数reg2_i的补码,
所以result_sum也是减法运算的结果,
可以通过判断减法的结果是否小于0,进而判断第一个操作数reg1_i是否小于第二个操作数reg2_i - (3) 计算是否溢出,加法指令(add和addi)、减法指令(sub)执行的时候,需要判断溢出
满足以下情况之一,有溢出:
A. reg1_i是正数,reg2_i_mux是正数,两者之和为负数
B. reg1_i是负数,reg2_i_mux是负数,两者之和为正数 - (4) 计算操作数1是否小于操作数2,分两种情况:
A. aluop_i为EXE_SLT_OP表示有符号比较运算,分三种情况:
A1. reg1_i为负数,reg2_i为正数,显然reg1_i小于reg2_i
A2. reg1_i为正数,reg2_i为正数,并且reg1_i减去reg2_i小于0
即result_sum为负,此时也有reg1_i小于reg2_i
A3. reg1_i为负数,reg2_i为负数,并且reg1_i减去reg2_i小于0
即result_sum为负,此时也有reg1_i小于reg2_i
B. 无符号数比较的时候,世界使用比较运算符比较reg1_i和reg2_i - (5) 对操作数1逐位取反,赋给reg1_i_not
// (1) if (sub or comp_signed) reg2_i_mux = reg2_i's complement
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) A. if (add) reg2_i_mux = reg2_i
// so result_sum = add's result
// B. if (sun) reg2_i_mux = reg2_i's complement
// so result_sum = sub's result
// C. if (comp_signed) reg2_i_mux = reg2_i's complement
// so result_sum = sub's result
// the result can be utilized for judging the size of values' relationship
assign result_sum = reg1_i + reg2_i_mux;
// (3) if (add or addi or sub) need to judge overflow
// A. reg1_i is positive, reg2_i_mux is positive, the result_sum is negative
// B. reg1_i is negative, reg2_i_mux is negative, the result_sum is positive
assign ov_sum = ( !reg1_i[31] && !reg2_i_mux[31] && result_sum[31] ) || ( reg1_i[31] && reg2_i_mux[31] && !result_sum[31] );
// (4) judge operand1 < operand2
// A. aluop_i == EXE_SLT_OP (comp_signed)
// A1. reg1_i is negative, reg2_i is positive
// so reg1_i < reg2_i(negative < positive)
// A2. reg1_i is positive, reg2_i is positvie, reg1_i-reg2_i < 0(result_sum < 0)
// so reg1_i < reg2_i(positive < positive)
// A3. reg1_i is negative, reg2_i is negative, reg1_i-reg2_i < 0(result_sum < 0)
// so reg1_i < reg2_i(negative < negative)
// B. aluop_i == EXE_SLTU_OP (comp_unsigned)
// use comparison operator
assign reg1_lt_reg2 = ( (aluop_i == `EXE_SLT_OP) ) ?
( (reg1_i[31] && !reg2_i[31]) || (!reg1_i[31] && !reg2_i[31] && result_sum) || (reg1_i[31] && reg2_i[31] && result_sum) ) :
( reg1_i < reg2_i );
// (5) reverse the operand1
assign reg1_i_not = ~reg1_i;
1.3.3. 依据aluop_i,给arithmeticres赋值
// ********** arithmetic operation **********
always @ (*) begin
if (rst == `RstEnable) begin
arithmeticres <= `ZeroWord;
end else begin
case (aluop_i)
`EXE_SLT_OP, `EXE_SLTU: begin // compare operation
arithmeticres <= reg1_lt_reg2;
end
`EXE_ADD_OP, `EXE_ADDU_OP, `EXE_ADDI_OP, `EXE_ADDIU_OP: begin // add operation
arithmeticres <= result_sum;
end
`EXE_SUB_OP, `EXE_SUBU_OP: begin // sub operation
arithmeticres <= result_sum;
end
`EXE_CLZ_OP: beg // clz count_0
arithmeticres <= reg1_i[31] ? 0 : reg1_i[30] ? 1 :
reg1_i[29] ? 2 : reg1_i[28] ? 3 :
reg1_i[27] ? 4 : reg1_i[26] ? 5 :
reg1_i[25] ? 6 : reg1_i[24] ? 7 :
reg1_i[23] ? 8 : reg1_i[22] ? 9 :
reg1_i[21] ? 10 : reg1_i[20] ? 11 :
reg1_i[19] ? 12 : reg1_i[18] ? 13 :
reg1_i[17] ? 14 : reg1_i[16] ? 15 :
reg1_i[15] ? 16 : reg1_i[14] ? 17 :
reg1_i[13] ? 18 : reg1_i[12] ? 19 :
reg1_i[11] ? 20 : reg1_i[10] ? 21 :
reg1_i[9] ? 22 : reg1_i[8] ? 23 :
reg1_i[7] ? 24 : reg1_i[6] ? 25 :
reg1_i[5] ? 26 : reg1_i[4] ? 27 :
reg1_i[3] ? 28 : reg1_i[2] ? 29 :
reg1_i[1] ? 30 : reg1_i[0] ? 31 : 32 ;
end
`EXE_CLO_OP: begin // clo count_1
arithmeticres <= (reg1_i_not[31] ? 0 : reg1_i_not[30] ? 1 :
reg1_i_not[29] ? 2 : reg1_i_not[28] ? 3 :
reg1_i_not[27] ? 4 : reg1_i_not[26] ? 5 :
reg1_i_not[25] ? 6 : reg1_i_not[24] ? 7 :
reg1_i_not[23] ? 8 : reg1_i_not[22] ? 9 :
reg1_i_not[21] ? 10 : reg1_i_not[20] ? 11 :
reg1_i_not[19] ? 12 : reg1_i_not[18] ? 13 :
reg1_i_not[17] ? 14 : reg1_i_not[16] ? 15 :
reg1_i_not[15] ? 16 : reg1_i_not[14] ? 17 :
reg1_i_not[13] ? 18 : reg1_i_not[12] ? 19 :
reg1_i_not[11] ? 20 :reg1_i_not[10] ? 21 :
reg1_i_not[9] ? 22 : reg1_i_not[8] ? 23 :
reg1_i_not[7] ? 24 : reg1_i_not[6] ? 25 :
reg1_i_not[5] ? 26 : reg1_i_not[4] ? 27 :
reg1_i_not[3] ? 28 : reg1_i_not[2] ? 29 :
reg1_i_not[1] ? 30 : reg1_i_not[0] ? 31 : 32) ;
end
endcase
end // else
end // always
1.3.4. 进行乘法运算
- (1) 取得乘法运算的被乘数,如果是有符号乘法并且被乘数是负数,那么取补码
- (2) 取得乘法运算的乘数,如果是有符号乘法并且乘数是负数,那么取补码
- (3) 得到临时的乘法结果,保存在变量hilo_temp中
- (4) 对临时乘法结果进行修正,最终的乘法结果保存在变量mulres中
A. 如果是有符号乘法指令mul、mult,那么需要修正临时乘法结果,如下:
A1. 如果被乘数和乘数一正一负,
需要对临时乘法结果hilo_temp求补码,作为最终的乘法结果,赋给变量mulres
A2. 如果被乘数和乘数同号,
hilo_temp的值就作为最终乘法结果,赋给变量mulres
B. 如果是无符号乘法指令multu,那么hilo_temp的值就作为最终乘法结果,赋给变量mulres
// ********** multiplicative operation **********
// (1) acquire operand1
// if ( (mul_signed || mult_signed) && reg1_i is negative) complement reg1_i
assign opdata1_mult = ( ( (aluop_i == `EXE_MUL_OP) || (aluop_i == `EXE_MULT_OP) ) && (reg1_i[31] == 1'b1) ) ? ( ~reg1_i + 1 ) : reg1_i;
// (2) acquire operand2
// if ( (mul_signed || mult_signed) && reg2_i is negative) complement reg2_i
assign opdata2_mult = ( ( (aluop_i == `EXE_MUL_OP) || (aluop_i == `EXE_MULT_OP) ) && (reg2_i[31] == 1'b1) ) ? ( ~reg2_i + 1 ) : reg2_i;
// (3) acquire the result of multiplication temporary
assign hilo_temp = opdata1_mult * opdata2_mult;
// (4) modify the result of multiplication temporary, the final value is stored in mulres
// A. if (mul_signed || mult_signed) need to modify
// A1. if (two operands have different sign)
// take the complement of temporary value as the final value
// A2. if (two operands have same sign) no need to modify
// the temporary value is the final value
// B. if (multu_unsigned)
// the temporary value is the final value
always @ (*) begin
if (rst == `RstEnable) begin
mulres <= {`ZeroWord, `ZeroWord};
end else if ( (aluop_i == `EXE_MUL_OP) || (aluop_i == `EXE_MULT_OP) ) begin
if ( reg1_i[31] ^ reg2_i[31] == 1'b1 ) begin
mulres <= ~hilo_temp + 1;
end else begin
mulres <= hilo_temp;
end
end else begin
mulres <= hilo_temp;
end
end
1.3.5. 依据alusel_i确定最终要写入目的寄存器的值
- 如果是add、addi、sub、subi指令,且发生了溢出,那么设置wreg_o为WriteDisable,表示不写入目的寄存器
always @ (*) begin
wd_o <= wd_i; // destination register address
// if ( (add || addi || sub || subi) && overflow ) WriteDisable
if ( ( (aluop_i == `EXE_ADD_OP) || (aluop_i == `EXE_ADDI_OP) || (aluop_i == `EXE_SUB_OP) ) && (ov_sum == 1'b1) ) begin
wreg_o <= `WriteDisable;
end else begin
wreg_o <= `WriteEnable;
end
case (alusel_i)
... ...
`EXE_RES_ARITHMETIC: begin // simple arithmetic operation but multiplication
wdata_o <= arithmeticres;
end
`EXE_RES_MUL: begin // store the result of multiplication
wdata_o <= mulres;
end
default: begin
wdata_o <= `ZeroWord;
end
endcase // case
end // always
1.3.6. 确定对HILO寄存器的操作信息
- mult、multu分别是有符号数相乘和无符号数相乘,结果低32bit存在LO寄存器中,高32bit存在HI寄存器中
always @ (*) begin
if (rst == `RstEnable) begin
whilo_o <= `WriteEnable;
hi_o <= `ZeroWord;
lo_o <= `ZeroWord;
end else if ( (aluop_i == `EXE_MULT_OP) || (aluop_i == `EXE_MULTU_OP) ) begin
whilo_o <= `WriteEnable;
hi_o <= mulres[63:32];
lo_o <= mulres[31:0];
end else if (aluop_i == `EXE_MTHI_OP) begin // hi <- rs
whilo_o <= `WriteEnable;
hi_o <= reg1_i;
lo_o <= LO;
end else if (aluop_i == `EXE_MTLO_OP) begin // lo <- rs
whilo_o <= `WriteEnable;
hi_o <= HI;
lo_o <= reg1_i;
end
end
结构框图
2. 测试模块
2.1 第一段 add addi addiu addu sub subu
2.2 第二段 slt sltu slti sltiu
2.3 第三段 clo clz
2.4 第四段 mul、mult、multu
先照着书上的写了一遍,暂时发布,后面再完善
希望等回头的时候,一切都明白了
从第一条ori指令写到现在的简单算数指令,已经开始看不明白代码了
书上有个别错误已经修改,但是关于简单算术的取补、判断溢出已经迷惑了
关于slti有符号数比较指令
reg1 = 0xffff_0000
reg2 = 0xffff_8000
进行有符号数比较,实际是reg1 > reg2,reg1_lt_reg2 = 0,wdata_o = 0
但是仿真结果是1
根据reg2_i_mux、result_sum、ov_sum,三者判断reg1_lt_reg2的逻辑关系还需要改进一点
在对reg2进行求补运算的时候,应该是reg2_mux = { reg2[31], ~reg2[30:0]+1 }
对reg2求补要分别考虑正数(补码就是源码)和负数(除符号位的其他位按位取反末位加1),确定reg2_i_mux的分支情况需要增加新的分支
自己想推理一下add和addu指令结果的由来,推着推着就不懂了
给寄存器中写入数据的形式应该是源码,否则在进行减法运算的时候就不需要对reg2判断求补
reg1 $1 = 32’h 8000_0001
reg2 $2 = 32’h 8000_0010
ADDU: 无符号加法,不进行溢出检查,直接写入结果
不用对reg2求补,result_sum = reg1+reg2 = 32’h 0000_0011
最高位都是1,相加结果最高位为0,结果有溢出
ADD: 有符号加法,进行溢出检查,结果溢出不写入
不用对reg2求补,负数在寄存器存储时是以补码形式存放的
[reg1]补 = 32’h 8000_0001 reg1 = [[reg1]补]补 = 32’h ffff_ffff
[reg2]补 = 32’h 8000_0010 reg2 = [[reg2]补]补 = 32’h ffff_fff0
result_sum = reg1+reg2 = 32’h 0000_0011
负 + 负 = 正 有溢出,结果不写入(满足ov_sum分支的条件)
。
数据已经写入寄存器,
进行无符号数操作就直接操作,没有符号位
进行有符号数操作就把它当成有符号数操作,最高位是1就是负数,最高位是0就是正数
但是作者博客下的评论又说“负数在寄存器存储时,以补码形式存放”,“指令中就是补码,不需要自己转”,啥意思啊这是
12.27 好像明白了一点,关于“负数在寄存器存储时,以补码形式存放”
第四段测试:
ori $1, $1, 0xfffb # $1 = -5
该指令进行完后,$1 = oxffff_fffb 作者提示这是-5
如果最高位是1的话,直接默认这个数是负数,符号为没有表示在这个32位数中,实际是这样的 1_1111_1111_1111_1011
而在regs存储器中直接以补码形式存放,并且默认省略掉符号位“1”,1111_1111_1111_1011
对她求原码,1_0000_0000_0000_0101,刚好就是-5
流水线的乘累加、乘累减可以了
12.29 除法指令好难啊 想给dividend赋值,一直写不进去
12.30 除法指令测试通过了,奈斯,谢谢奥姐给我检查