P6课上测试

P6课上测试内容与P5课上测试内容大概只有第一题计算类型上的区别,变为考察乘除槽的指令

一.增添指令一般步骤

  • 读懂RTL语言,注意opcode与funct位置,是什么类型的指令,会不会与已经添加过的指令发生冲突(例如P5上机第一题中计算指令与slt指令opcode重复QAQ)
  • 使用课上提供的.class文件在MARS中进行模拟
  • 计算指令是否可以进行归类,cal_R,cal_I;条件跳转指令和条件存储指令一般直接使用check信号对新信号进行特判。
  • 在MCU中从上到下观察是否将新指令加入有效列表
  • 明确新指令的T_use,T_new

二.题型分析

1.涉及到乘除槽的计算类指令

​ P6的计算指令会涉及到乘除模块,以madd,maddu,msub,msubu等指令为例。

  • madd : 将两个有符号数相乘,计算结果与之前的HI,LO寄存器中的值相加,而不是覆盖
  • mddu:将两个无符号数相乘,计算结果与之前的HI,LO寄存器中的值相加,而不是覆盖
  • msub:将两个有符号数相乘,之前HI,LO中的值减去当前结果
  • msubu:将两个无符号数相乘,之前HI,LO中的值减去当前结果

1.verilog中的符号性问题

$signed()关键字的功能为数据如何进行补位,根据递归确定外层+向内传播的表达式符号确定规则,例如在P1中遇到的三目表达式中有无符号数导致整个表达式变为无符号的情况。

  • 算数(符号)右移表达式中移位立即数不必要声明为有符号 :$signed(A) »> B

  • 为避免$singed()出现的问题,可以使用位扩展进行代替

    1
    
    {{16{imm[15]}},imm}//16位imm符号扩展为32位
    

2.计算指令中的符号乘除

  • mult 使用位拼接运算

    1
    
    {tmpHI,tmpLO} <= $signed(A) * $signed(B)
    
  • madd

    • 错误写法1

      1
      
      {tmpHI,tmpLO} <= {tmpHI,tmpLO} + $signed(A)*$signed(B)
      

      位拼接运算{tmpHI,tmpLO}默认被当作无符号数,向内传播导致$signed(A)*$signed(B)变为无符号的

      • 修改为:

        1
        
        {tmpHI,tmpLO} <= $signed({tmpHI,tmpLO}) + $signed(A)*$signed(B)
        
    • 错误写法2

      1
      
      {tmpHI,tmpLO} <= {tmpHI.tmpLO} + $signed($signed(A)*$signed(B))
      

      使用$signed()屏蔽了外界符号性的传入,同时屏蔽了位宽信息的传入,由于$signed(A),$signed(B)为32位,则$signed($signed(A)*$signed(B))为32位

      • 修改为:

        1
        2
        
        {tmpHI,tmpLO} <= {tmpHI,tmpLO} + $signed($signed(64'd0) + $signed(A)*$signed(B))// 补充位宽信息
        {tmpHI,tmpLO} <= {tmpHI,tmpLO} + $signed({{32{A[31]}},A}*{{32{B[31]}},B})//手动符号位扩展
        

    ps:循环移位计算

    ​ P5课前预习到了循环移位,结果课上第一题就考了循环移位,虽然再次出现概率不大,但是有必要重复一下

    1
    2
    
    //循环右移
    E_AO <= (src_B >> shamt) | (src_B << (32'd32 - shamt));
    

3.题目实例分析

  • madd,maddu,msub,msubu等可以直接归结为md类指令,乘法延迟为5,只需注意符号性的问题。

  • shl : 交换HI和LO寄存器中的值

    • 由于非阻塞赋值的特性,无需设置中间变量,直接交换即可
    1
    2
    
    HI <= LO;
    LO <= HI;
    
  • bds : 使用rs和rt寄存器中的值,用较大的数除以较小的数,注意是进行无符号除法

    1
    2
    3
    4
    5
    6
    7
    8
    
    if(A > B)begin
        tmpLO = A / B;
        tmpHI = A % B;
    end
    else begin
        tmpLO = B / A;
        tmpHI = B % A;
    end
    
  • crt : 将GPR[rs]循环移位得到的32个结果无符号求和,结果记为temp1,将GPR[rt]循环移位得到的32个结果无符号求和,结果记为temp2,比较这两个结果

    if temp1 < temp2 then
    	GPR[rd] = -1
    else if temp1 > temp2 then
    	GPR[rd] = 1
    else then
    	GPR[rd] = 0
    

    这题算是把循环移位考到头了,循环移位的32种结果即移位0(32)~31位,实现细节上无论左移还是右移得到的和都是相等的

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    	temp1 = 32'b0;
    	temp2 = 32'b0;
    	for(i=0;i<32;i=i+1)begin
    		temp1 = temp1 + (src_A>>i)|(src_A << (32'd32 - i));
    	end
    	for(i=0;i<32;i=i+1)begin
    		temp2 = temp2 + (src_B>>i)|(src_B << (32'd32 - i)); 
    	end
    	if(temp1 < temp2)begin
    		E_AO = -1;
    	end
    	else if(temp1 > temp2)begin
    		E_AO = 1; 
    	end
    	else begin
    		E_AO = 0;
    	end
    

2.条件跳转类指令

1.条件跳转+无条件链接

  • 在CMP模块中添加判断,RegWrite置为1,SelA3选择31号寄存器,SelEMout/SelWout转发PC8数据

2.条件跳转+条件链接

  • 在CMP模块中添加判断,新增信号RegWrite_new,check,对该新增指令进行特判,流水新的RegWrite信号

    1
    
    assign RegWrite_D_new = (check_D) ? (b_result_D ? 1'b1 : 1'b0) : RegWrite_D;
    

3.条件跳转+不跳转清空延迟槽

  • 在CMP模块中添加判断、特判新信号、当前未暂停(若当前处于暂停则CMP判断结果无效)、当前不满足跳转条件,清空D级流水线寄存器

  • 为D级流水线寄存器添加clr端口,默认置为0

    1
    
    assign clr = (check_D)&(!stall)&(!b_result_D);
    

4.题目实例分析

  • bezal : 若GPR[rt] = 0,则跳转到GPR[rs],并且链接到$31

    • 条件跳转+条件链接,加入branch类,check_D,NPCOp,CMPOp,使用RegWrite_new
  • btheq : 若GPR[rs]最高位1与GPR[rt]的相同,则跳转,注意GPR[rs]==0||GPR[rt]==0时不用跳转

    • 条件跳转+不链接,与bne/beq指令类似,在CMP中新增判断逻辑

      最初的错误写法

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      
      		for(flag1 = 31;flag1 >= 0;flag1 = flag1 - 1)begin
      			if(D_V1[flag1] == 1'b1)begin
      				break;
      			end
      		end
      		for(flag2 = 31;flag2 >= 0;flag2 = flag2 - 1)begin
      			if(D_V2[flag2] == 1'b1)begin
      				break;
      			end
      		end
      b_result = (flag1 == 0 || flag2 == 0) ? 1'b0 : (flag1 == flag2) ? 1'b1 : 1'b0;
      
    • 错误点1 :verilog中不能使用break来跳出for循环!

      • 修改方法1:可以将跳出条件移到括号内

        1
        2
        3
        
        for(flag1 = 31;flag1 >= 0 && (D_V1[flag1] != 1'b1);flag1 = flag1 - 1)begin
        for(flag2 = 31;flag2 >= 0 && (D_V2[flag2] != 1'b1);flag2 = flag2 - 1)begin
        b_result = (D_V1 == 0 || D_V2 == 0) ? 1'b0 : (flag1 == flag2) ? 1'b1 : 1'b0;
        
      • 修改方法2:使用disable语句跳出for循环,在使用disable跳出循环时,需要为循环起名字

         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        
        for(flag1 = 31;flag1 >= 0;flag1 = flag1 - 1)begin : one
        	if(D_V1[flag1] == 1'b1)begin
        		disable one;
        	end
        end
        for(flag2 = 31;flag2 >= 0;flag2 = flag2 - 1)begin : two
        	if(D_V2[flag2] == 1'b1)begin
        		disable two;
        	end
        end
        b_result = (D_V1 == 0 || D_V2 == 0) ? 1'b0 : (flag1 == flag2) ? 1'b1 : 1'b0;
        
    • 错误点2 : 一开始写法中判断(GPR[rs] ==0 || GPR[rt] == 0)时使用(flag1 == 0 | flag2 == 0)判断,这是错误的,若两个数均为0跳出循环时flag1=flag2=-1

3.条件存储类

​ 对于条件存储类load指令,只有到了M级才能确定写入的寄存器,需要对暂停逻辑进行修改。如果D级要读的寄存器后续可能被写入,就要暂停

1.满足condition写入A,不满足condition写入B

​ 如满足条件向rt号(load类rt即为rd,即为load类原本的rd)写,不满足条件向31号写(流水到M级只剩下rt(rd)号了,所以一般来说一定会出现一个写入常数寄存器,当然也可以从D级增加流水rs段)

HCU

1
2
3
4
 	assign   stall_rs_E = (D_A1 != 5'd0) & (check_E ? (D_A1 == E_A3 | D_A1 == 5'd31) : D_A1 == E_A3) & (RFWrite_E) & (Tuse_rs < Tnew_E);
    assign   stall_rs_M = (D_A1 != 5'd0) & (check_M ? (D_A1 == M_A3 | D_A1 == 5'd31) : D_A1 == M_A3) & (RFWrite_M) & (Tuse_rs < Tnew_M);
    assign   stall_rt_E = (D_A2 != 5'd0) & (check_E ? (D_A2 == E_A3 | D_A2 == 5'd31) : D_A2 == E_A3) & (RFWrite_E) & (Tuse_rt < Tnew_E);
    assign   stall_rt_M = (D_A2 != 5'd0) & (check_M ? (D_A2 == M_A3 | D_A2 == 5'd31) : D_A2 == M_A3) & (RFWrite_M) & (Tuse_rt < Tnew_M);

MUX

1
 wire M_A3_new = check_M ? (condition ? `rt : 5'd31) : M_A3;

将M_A3_new传入W_reg和HCU

2.满足condition写入A,不满足condition不写入

​ 可以将不写入理解为写入0号寄存器

HCU

1
2
3
4
5
//按照第一种题型以写成  (check_M ? (D_A2 == 5'd31 | D_A2 == 5'd0): D_A2 == M_A3),因为前面有条件 D_A2 != 5'd0,所以可以简化 	
assign   stall_rs_E = (D_A1 != 5'd0) & (check_E ? (D_A1 == E_A3 ) : D_A1 == E_A3) & (RFWrite_E) & (Tuse_rs < Tnew_E);
assign   stall_rs_M = (D_A1 != 5'd0) & (check_M ? (D_A1 == M_A3 ) : D_A1 == M_A3) & (RFWrite_M) & (Tuse_rs < Tnew_M);
assign   stall_rt_E = (D_A2 != 5'd0) & (check_E ? (D_A2 == E_A3 ) : D_A2 == E_A3) & (RFWrite_E) & (Tuse_rt < Tnew_E);
assign   stall_rt_M = (D_A2 != 5'd0) & (check_M ? (D_A2 == M_A3 ) : D_A2 == M_A3) & (RFWrite_M) & (Tuse_rt < Tnew_M);

MUX

1
  wire M_A3_new = check_M ? (condition ? 5'd31 : 5'd0) : M_A3;

3.写入完全取决于DM中取出的数据

​ 取决于DM中取出的数据则不能确定到底是哪一个,即所有寄存器都有可能被写入

HCU

1
2
3
4
assign   stall_rs_E = (D_A1 != 5'd0) & (check_E ? 1'b1 : D_A1 == E_A3) & (RFWrite_E) & (Tuse_rs < Tnew_E);
assign   stall_rs_M = (D_A1 != 5'd0) & (check_M ? 1'b1 : D_A1 == M_A3) & (RFWrite_M) & (Tuse_rs < Tnew_M);
assign   stall_rt_E = (D_A2 != 5'd0) & (check_E ? 1'b1 : D_A2 == E_A3) & (RFWrite_E) & (Tuse_rt < Tnew_E);
assign   stall_rt_M = (D_A2 != 5'd0) & (check_M ? 1'b1 : D_A2 == M_A3) & (RFWrite_M) & (Tuse_rt < Tnew_M);

MUX

1
 wire M_A3_new = check_M ? DM_out[4:0] : M_A3;