计组P6课下作业总结

P6新增功能元件

乘除模块

乘除模块位于E级,和ALU并列,主要是为了解决和HI/LO寄存器相关的计算,这些计算列举如下:mult,multu,div,divu,mfhi,mflo,mthi,mtlo。而这些计算又分为两种:一种是运算指令,我们为了模拟运算需要让乘法经历五回合,除法经历十回合后才将内容输出到HI/LO寄存器中,而另一种是对这两个寄存器的存取操作,单周期即可完成。我的端口定义如下:

端口 方向 功能
CLK I 时钟信号输入
Reset I 同步复位信号输入
E_MulDiv_RD1[31:0] I 读入的GPR[rs]中的内容
E_MulDiv_RD2[31:0] I 读入的GPR[rt]中的内容
E_MulDiv_Op[3:0] I 用来表示当前正在执行的指令
E_MulDiv_Stall O 用来表示当前的乘除模块是否繁忙,需要后续相关指令暂停
E_MulDiV_HILO[31:0] O 用来获取HI或LO寄存器中的值

对于计算指令mult,multu,div,divu,我的计算方法如下:

1
2
3
4
5
6
else if(busy==0) begin
if(op==mult) begin
{hi,lo}<=$signed(E_MulDiv_RD1)*$signed(E_MulDiv_RD2);
busy<=5;
end
//else if...

首先,我定义了一个busy寄存器变量用来存储当前乘除模块是否繁忙,和繁忙的持续时间。只有当其不在繁忙时才能读入计算指令。(不用像教程中一样设定start变量,因为Op本身就可以表示start)
其次,由于我们第五个上升沿才需要将结果写入HI/LO,但是又不能在第五个上升沿才开始计算(RD1,2会改变),因此我们需要用hi,lo寄存器当作中间变量存储,在第五回合再写入HI/LO寄存器。写入的代码如下:

1
2
3
4
5
6
7
else if(busy!=0) begin
busy<=busy-1;
if(busy==1) begin
HI<=hi;
LO<=lo;
end
end

在每一个繁忙周期都将繁忙周期减一,而且可以类比mealy型有限状态机,我们需要提前预判下一个周期的busy值来没有延迟的写入HI/LO,所以我们在busy==1时就需要写入HI/LO,这样就可以和busy==0的周期同步。

最后我们需要进行暂停的考虑。其实这也很简单,乘除板块的暂停信号只要在busy!=0时就需要置1:assign E_MulDiv_Stall=(busy!=0||op==mult||op==multu||op==div||op==divu)?1:0;

但是,如果后续是不需要使用乘除模块的指令,那么我们不需要将它们暂停。所以我们在HCU中真正处理暂停信号时,只有乘除模块发出暂停信号,并且D级的指令是乘除相关指令时,我们才需要进行暂停。写法如下:

1
2
3
PauseFlag=//...
(HCU_E_MulDiv_Stall==1&&HCU_D_MulDivOp!=0)?1:
0;

DM相关

由于P6要求DM外置,并且出现了lh,lb,sh,sb这样的不以字为单位的存取命令,因此我们的DM相关操作也需要更新。

写入

首先考虑写入操作,包括sw,sh,sb。
本P提示我们的做法是改变DMWr的位数。之前的DMWr都只有1位,用来表示是否写入。但是此时我们可以把DMWr改成4位。这样就可以用1所在的位数标记需要写入的字节。例如要存入这一个字的第三字节,那么就设定为0100;如果要存入第一个半字(前两字节),那么就设定为0011。
但是,我们单纯通过指令的机器码,只能确定存入的大小是字,半字还是字节(也即1的个数),但存入的是哪个字节却需要addr的后两位给出。因此我的做法是先在CU中表明该指令的取数据长度,而真正取数据位置在M级再进行确定。
CU中的操作如下:

1
2
3
4
assign D_CU_DMWr=(op==sw)?4'b1111:
(op==sb)?4'b0001:
(op==sh)?4'b0011:
0;

而在M级中确定真正要输出位置的选择如下:

1
2
3
4
5
6
7
8
9
assign m_data_byteen=(M_M_DMWr__DM_Wr==4'b0000)?4'b0000: //else
(M_M_DMWr__DM_Wr==4'b0001&&M_M_ALUAns__DM_Addr[1:0]==2'b00)?4'b0001: //lb
(M_M_DMWr__DM_Wr==4'b0001&&M_M_ALUAns__DM_Addr[1:0]==2'b01)?4'b0010:
(M_M_DMWr__DM_Wr==4'b0001&&M_M_ALUAns__DM_Addr[1:0]==2'b10)?4'b0100:
(M_M_DMWr__DM_Wr==4'b0001&&M_M_ALUAns__DM_Addr[1:0]==2'b11)?4'b1000:
(M_M_DMWr__DM_Wr==4'b0011&&M_M_ALUAns__DM_Addr[1]==0)?4'b0011: //lh
(M_M_DMWr__DM_Wr==4'b0011&&M_M_ALUAns__DM_Addr[1]==1)?4'b1100:
(M_M_DMWr__DM_Wr==4'b1111)?4'b1111: //lw
0;

最后,要注意本题的外置DM的写入方法,和正常想当然的方法是不同的,题面如下:

所以说,我们正常的理解是:例如sw,那么无论是存入低位还是存入高位,都是用GPR[rt]数据的低位进行存入。但是本题是不同的,我们输出的数据需要对齐,就是如果存入高位,就要将数据的高位取出存入高位。
综上,我们需要对GPR[rt]的数据在M级作出处理:

1
2
3
4
assign m_data_wdata=(M_M_DMWr__DM_Wr==4'b0001)?{4{M_MUX_RD2[7:0]}}:
(M_M_DMWr__DM_Wr==4'b0011)?{2{M_MUX_RD2[15:0]}}:
(M_M_DMWr__DM_Wr==4'b1111)?M_MUX_RD2:
0;

这样进行输出就能正确的存入外置的虚拟内存中。

读取

然后我们再来考虑读取问题,包括lw,lh,lb。在这里我们需要用一个新的模块来完成。接口如下:

端口 方向 功能
M_DMEXT_LSel[2:0] I 读取方式的控制信号
M_DMEXT_A[1:0] I 读取地址的后两位,用来确定读取位置
M_DMEXT_Din[31:0] I 利用读取地址的前11位读出的字
M_DMEXT_Dout[31:0] O 经过处理之后读出的字/半字/字节

这个我们只需要根据LSel和A对Din进行处理即可,代码如下:

1
2
3
4
5
6
assign M_DMEXT_Dout=(M_DMEXT_LSel==3'b001&&M_DMEXT_A==2'b00)?{{24{M_DMEXT_Din[7]}},{M_DMEXT_Din[7:0]}}:(M_DMEXT_LSel==3'b001&&M_DMEXT_A==2'b01)?{{24{M_DMEXT_Din[15]}},{M_DMEXT_Din[15:8]}}:
(M_DMEXT_LSel==3'b001&&M_DMEXT_A==2'b10)?{{24{M_DMEXT_Din[23]}},{M_DMEXT_Din[23:16]}}:
(M_DMEXT_LSel==3'b001&&M_DMEXT_A==2'b11)?{{24{M_DMEXT_Din[31]}},{M_DMEXT_Din[31:24]}}:
(M_DMEXT_LSel==3'b010&&M_DMEXT_A[1]==0)?{{16{M_DMEXT_Din[15]}},{M_DMEXT_Din[15:0]}}:
(M_DMEXT_LSel==3'b010&&M_DMEXT_A[1]==1)?{{16{M_DMEXT_Din[31]}},{M_DMEXT_Din[31:16]}}:
M_DMEXT_Din;

要注意,由于取出的可能是半字或者字节,为了匹配32位的输出需要进行扩展。此处要进行的是符号扩展。

以上就是P5的构建和P6增加的模块。P6增加的指令集虽然有很多新指令,但是并没有超出这个框架。只需要根据其指令内容进行测试添加即可,在此不再赘述。
P6相对于P5其实改进并不太多,希望P6上机一切顺利。