P5_L0_document
P5
设计草稿
设计要求
使用Verilog语言设计一个支持add、sub、ori、lw、sw、beq、lui、jal、jr、nop
指令集的五级流水线设计。
1.对于b类和j类指令,流水线设计必须支持延迟槽,需要注意使用PC@D+8或PC@F+4(跳转指令在D阶段时)。
2.为了解决数据冒险而设计的转发数据来源应是是某一级流水线寄存器,不允许对功能部件的输出直接进行转发。
3.IM:容量为 16KiB(4096 × 32bit)。DM:容量为 12KiB(3072 × 32bit)。
大致思路
先不考虑数据冒险问题,将正常运行的流水线CPU框架搭起来,再根据相关指令添加相应的冒险解决通路(如重定向、阻塞和刷新等方式),并且并不把因冒险导致增加的相关数据通路(除冲突判断信号的产生外)模块化,减轻课上加指令时的压力。
数据通路
通过将单周期处理器分解成由5个流水线阶段(F(取指)、D(译码)、E(运算)、M(访存)、W(回写))构成的流水线处理器来提高CPU的吞吐量。
为保证每一个流水线阶段相应的指令时间下的状态值正确,需要在不同阶段间增加流水线寄存器堆来维持相应阶段的状态,以提供数据的流水级的简称命名,如D级流水线寄存器堆前一级为F级,后一级为D级。
流水线寄存器堆
不同类型指令与不同流水线寄存器堆中需要添加的寄存器的对应关系表:
R型算术运算指令:add、sub | 带有立即数的I型算数、逻辑运算指令:ori、lui | 内存访问指令:lw | 内存写入指令:sw | 跳转b型指令:beq | 跳转J型指令:jal | 跳转R型指令:jr | |
---|---|---|---|---|---|---|---|
D级流水线寄存器堆 | IRF(在指令的F阶段将指令存入,在相应指令的D阶段取出去译码) | IRF | IRF | IRF | IRF、PCF(存储当前在F阶段指令的PC值) | IRF、PCF | IRF、PCF |
E级流水线寄存器堆 | RSD(存储当前在D阶段指令的第一个操作数)、RTD、WAD(存储当前在D阶段指令的写入地址) | RSD、RTD、WAD、IMD | RSD、RTD、WAD、IMD | RSD、RTD、IMD | PCD(取出rs、rt寄存器值后进行比较决定是否跳转) | PCD(存储当前在D阶段指令的PC值,在该阶段需要通过控制信号完成跳转) | |
M级流水线寄存器堆 | AOE、WAE | AOE、WAE | WAE、AOE | AOE、WDE(处于E阶段指令的写入内存的数据) | PCE | ||
W级流水线寄存器堆 | AOM(存储当前在M阶段指令的ALU运算结果)、WAM(存储当前在M阶段指令的写入地址) | AOM、WAM | RDM(存储处于M阶段指令的访存数据)、WAM | AOM(通过当前指令控制信号ifJump_M写入从PCE取出指令地址值+8的结果,考虑延迟槽的效果)、WAM(通过当前指令控制信号ifJump_M将31写入) |
除此之外,在D阶段通过Controller产生的所有控制信号(采用通过一次产生控制信号,并传递到每个阶段的译码方式)需要在对应需要发挥作用阶段前的每一级流水线寄存器堆进行相应的存储,因此有如下的4块流水线寄存器堆(还未考虑数据冒险的情况):
D_Regs(D级流水线寄存器堆)
端口定义:
端口名称 | 方向 | 描述 |
---|---|---|
clk | I | 时钟信号 |
reset | I | 同步复位信号 |
stall | I | 1位,冻结信号 |
PC_F[31:0] | I | F阶段输入的指令地址 |
IR_F[31:0] | I | F阶段存入的指令 |
PC_D[31:0] | O | D阶段使用的指令地址 |
IR_D[31:0] | O | D阶段使用的指令 |
E_Regs(E级流水线寄存器堆)
端口定义:
端口名称 | 方向 | 描述 |
---|---|---|
clk | I | 时钟信号 |
reset | I | 同步复位信号 |
clear | I | 1位,刷新信号当阻塞进行时输出空指令,在reset不有效时将寄存器都清零 |
RegWrite_D | I | 当前处于D阶段指令的写入信号 |
MemWrite_D | I | 当前处于D阶段指令的写入内存信号 |
MemToReg_D | I | 当前处于D阶段指令的内存回写信号 |
ALUctr_D[2:0] | I | 当前处于D阶段指令的ALU操作信号 |
ALUsec_D | I | 当前处于D阶段指令的ALU第二个操作数的选择信号 |
PC_D[31:0] | I | 当前处于D阶段指令的地址 |
Rs_D[31:0] | I | 当前处于D阶段指令的第一个操作数 |
Rt_D[31:0] | I | 当前处于D阶段指令的第二个操作数 |
rs_D[4:0] | I | 当前处于D阶段的rs地址 |
rt_D[4:0] | I | |
IM_D[31:0] | I | 当前处于D阶段指令的立即数 |
WA_D[4:0] | I | 当前处于D阶段指令的回写地址 |
ifJump_D | I | 当前处于D阶段指令是否进行jal跳转 |
Type_D[2:0] | I | 当前处于D阶段指令的指令种类 |
RegWrite_E | O | 当前处于E阶段指令的写入信号 |
MemWrite_E | O | 当前处于E阶段指令的写入内存信号 |
MemToReg_E | O | 当前处于E阶段指令的内存回写信号 |
ALUctr_E[2:0] | O | 当前处于E阶段指令的ALU操作信号 |
ALUsec_E | O | 当前处于E阶段指令的ALU第二个操作数的选择信号 |
PC_E[31:0] | O | 当前处于E阶段指令的地址 |
Rs_E[31:0] | O | 当前处于E阶段指令的第一个操作数 |
Rt_E[31:0] | O | 当前处于E阶段指令的第二个操作数 |
rs_E[4:0] | O | 当前处于E阶段的rs地址 |
rt_E[4:0] | O | |
IM_E[31:0] | O | 当前处于E阶段指令的立即数 |
WA_E[4:0] | O | 当前处于E阶段指令的回写地址 |
ifJump_E | O | |
Type_E[2:0] | O |
M_Regs(M级流水线寄存器堆)
端口名称 | 方向 | 描述 |
---|---|---|
clk | I | 时钟信号 |
reset | I | 同步复位信号 |
RegWrite_E | I | 当前处于E阶段指令的回写信号 |
MemWrite_E | I | 当前处于E阶段指令的写入内存信号 |
MemToReg_E | I | |
AO_E[31:0] | I | 当前处于E阶段指令的ALU计算结果 |
ifJump_E | I | |
WA_E[4:0] | I | 当前处于E阶段指令的写入寄存器地址 |
WD_E[31:0] | I | 当前处于E阶段指令的写入内存的数据 |
PC_E[31:0] | I | 当前处于E阶段指令的地址 |
rs_E[4:0] | I | 当前处于E阶段的rs地址 |
rt_E[4:0] | I | |
Type_E[2:0] | I | |
RegWrite_M | O | |
MemWrite_M | O | |
MemToReg_M | O | |
AO_M[31:0] | O | |
ifJump_M | O | |
WA_M[4:0] | O | |
WD_M[31:0] | O | |
PC_M[31:0] | O | |
rs_M[4:0] | O | 当前处于M阶段的rs地址 |
rt_M[4:0] | O | |
Type_M[2:0] | O |
W_Regs(W级流水线寄存器堆)
端口定义:
端口名 | 方向 | 描述 |
---|---|---|
clk | I | 时钟信号 |
reset | I | 同步复位信号 |
RegWrite_M | I | 当前处于M阶段指令的回写信号 |
MemToReg_M | I | |
ifJump_M | I | 当前处于M阶段指令是否是jal指令 |
AO_M[31:0] | I | 当前处于M阶段指令的ALU运算结果 |
PC_M[31:0] | I | 当前处于M阶段指令的PC值 |
MD_M[31:0] | I | 当前处于M阶段指令的内存读取值 |
WA_M[4:0] | I | 当前处于M阶段指令的回写地址 |
Type_M[2:0] | I | |
RegWrite_W | O | |
MemToReg_W | O | |
ifJump_W | O | |
AO_W[31:0] | O | |
PC_W[31:0] | O | |
MD_W[31:0] | O | |
WA_W[4:0] | O | |
Type_W[2:0] | O |
功能模块
(尚未考虑冒险情况的发生)
IFU(取指令单元)
模块端口定义:
信号名 | 方向 | 描述 |
---|---|---|
clk | I | 时钟信号 |
reset | I | 同步复位信号,将PC寄存器的值复位为起始地址0x00003000 |
NPC[31:0] | I | 32位,代表下一条指令的地址,读入后用PC寄存器存储起来 |
stall | I | 1位,冻结信号 |
PC[31:0] | O | 32位,代表当前指令的地址,输出给NPC模块来生成下一条指令的地址 |
Data[31:0] | O | 32位,当前指令的机器码 |
模块功能定义:
序号 | 功能名称 | 描述 |
---|---|---|
1 | 同步复位 | 当时钟上升沿到来且reset有效时,将PC寄存器的值复位为起始地址0x00003000,PC寄存器通过一个内部的32位reg 型变量存储。 |
2 | 取指令 | 通过当前PC值读取ROM中相应位置的指令并输出当前指令,而ROM则通过对reg 型变量建立数组的方式来表示,例如reg [31:0] ROM [0:4095] ,根据实验要求一开始使用initial 块通过$readmemh 将code.txt 中的十六进制指令读入存储器ROM中。 |
3 | 阻塞PC | 当stall有效时,需要使PC值停在原地,冻结住 |
GRF(寄存器单元)
模块端口定义:
信号名 | 方向 | 描述 |
---|---|---|
clk | I | 时钟信号 |
reset | I | 同步复位信号,将32个寄存器中的值全都清零。 |
RegWrite | I | 写使能信号,1:可向GRF中写入数据;0:不可写入 |
rs[4:0] | I | 5为地址输入信号,指定32个寄存器中一个,将其中存储的数据读出到RD1 |
rt[4:0] | I | 5为地址输入信号,指定32个寄存器中一个,将其中存储的数据读出到RD2 |
RegAddr[4:0] | I | 5为地址输入信号,指定32个寄存器中一个作为写入的目标寄存器 |
RegData[31:0] | I | 32位输入数据 |
PC[31:0] | I | 32位,表示相应指令的存储地址 |
Rs[31:0] | O | 输出rs寄存器中的32位数据 |
Rt[31:0] | O | 输出rt寄存器中的32位数据 |
模块功能定义:
序号 | 功能名称 | 描述 |
---|---|---|
1 | 同步复位 | 时钟上升沿到来且reset信号有效时,所有寄存器存储的数据清零。 |
2 | 读数据 | 读出rs,rt地址对应寄存器中所存储的数据到Rs,Rt中。寄存器堆的实现方式同IFU模块中的ROM器件类似。 |
3 | 写数据 | 当RegWrite有效且时钟上升沿来临时,将RegData写入RegAddr寄存器中。 |
4 | 输出数据,用于评测 | 每个时钟上升沿到来时若要写入数据(即写使能信号为 1 且非 reset 时)则输出写入的位置及写入的值 |
CMP(比较器)
端口定义:
信号名 | 方向 | 描述 |
---|---|---|
Rs_D[31:0] | I | 32位输入,B类跳转指令的第一个比较数 |
Rt_D[31:0] | I | 32位输入,B类跳转指令的第二个比较数 |
CMPOp_D[2:0] | I | 3位,决定B类指令的比较方式 |
b_jump_D | O | 1位,输出是否进行B类型跳转 |
NPC(指令转移单元)
模块端口定义:
信号名 | 方向 | 描述 |
---|---|---|
PC_F[31:0] | I | 32位,当前处于F阶段指令的地址 |
PC_D[31:0] | I | 32位,当前处于D阶段指令的地址 |
NPCOp[2:0] | I | 3位,地址转移的选择信号 |
zero_D | I | 1位,表示当前处于D阶段指令的两个操作数是否满足相等关系 |
imm_D[15:0] | I | 16位,当前处于D阶段指令的16位立即数 |
offset_D[25:0] | I | 26位,当前处于D阶段指令的26位立即数 |
Rs_D[31:0] | I | 32位,当前处于D阶段指令的rs寄存器的值,作为jr 指令成立时的跳转地址 |
ifJump_D | I | 1位,表示当前处于D阶段指令是否为jal 5 |
NPC[31:0] | O | 32位,下一条指令的地址 |
模块功能定义:
序号 | 功能名称 | 描述 |
---|---|---|
1 | 非跳转指令的PC自增 | 对于非跳转指令,需要通过PC自加4完成向下一条指令的转移 |
2 | 跳转指令的PC跳转 | 针对类如beq 等跳转指令的在PC自增功能的基础下的PC地址偏移即将再加上立即数左移两位后符号扩展成32位的结果,进而实现PC的跳转;针对jr 指令需要将输入的寄存器存储的值赋值给PC即可实现相应的指令地址的转移。 |
DM(数据寄存器)
模块端口定义:
信号名 | 方向 | 描述 |
---|---|---|
MemData[31:0] | I | 32位,写入数据 |
MemAddr[31:0] | I | 32位,地址选择信号 |
MemToReg_M | I | 1位,是否读出信号 |
MemWrite_M | I | 1位,是否写入信号 |
clk | I | 时钟信号 |
reset | I | 复位信号 |
PC_M[31:0] | I | 32位,当前处于D阶段指令的地址 |
RD[31:0] | O | 32位,输出数据 |
模块功能定义:
序号 | 功能名称 | 描述 |
---|---|---|
1 | 读数据 | MemToReg_M有效时,地址选择信号MemAddr_M选择的存储字放在RD输出总线上输出 |
2 | 写数据 | MemWrite_M有效且时钟信号上升沿到来时,MemData_M上数据被写入地址MemAddr_M选择的存储单元中 |
3 | 同步复位 | 时钟上升沿到来且reset有效时,将RAM地址复位为起始地址0x00000000 |
4 | 输出数据,用于评测 | 每个时钟上升沿到来时若要写入数据(即写使能信号为 1 且非 reset 时)则输出写入的位置及写入的值 |
ALU(运算单元)
模块端口定义:
信号名 | 方向 | 描述 |
---|---|---|
ALUctr_E[2:0] | I | 3位,进行I1和I2的相关运算,课下目前只需要2位就可以了,但是多留一位给P4课上做准备() |
I1[31:0] | I | 32位,操作数一 |
I2[31:0] | I | 32位,操作数二 |
Out[31:0] | O | 32位,ALU计算结果 |
模块功能定义:
序号 | 功能名称 | 描述 |
---|---|---|
1 | 加操作 | ALUctr为00 时进行I1和I2的加操作 |
2 | 减操作 | ALUctr为01 时进行I1-I2的操作 |
3 | 或操作 | ALUctr为10 时进行I1|I2的操作 |
4 | 加载到高位的操作 | ALUctr为11 时进行将imm加载到高位 |
Ext(扩展单元)
模块端口定义:
信号名 | 方向 | 描述 |
---|---|---|
imm_D[15:0] | I | 处于译码(D)阶段指令的16位立即数输入 |
ifSignExt_D | I | 1位,表示是否进行符号位的扩展 |
immediate_D[31:0] | O | 32位,立即数扩展后的结果 |
模块功能定义:
序号 | 功能名称 | 描述 |
---|---|---|
1 | 0扩展 | 将16位立即数进行0扩展 |
2 | 符号扩展 | 将16位立即数进行符号位扩展 |
Controller(控制器单元)
模块端口定义:
信号名 | 方向 | 描述 |
---|---|---|
op[5:0] | I | 6位,区分非R型指令的标识 |
func[5:0] | I | 6位,区分R型指令的标识 |
RegWrite_D | O | 1位,表示是否要将结果写回寄存器中 |
MemWrite_D | O | 1位,表示是否要将结果写入DM中 |
ifSignExt_D | O | 1位,表示立即数是否进行符号扩展 |
ALUctr_D[2:0] | O | 3位,作为ALU模块的运算控制器 |
CMPOp_D[2:0] | O | 3位,作为比较器的控制器 |
NPCOp[2:0] | O | 3位,表示指令跳转的选择,000:正常读入下一条指令,001:在判断成立的前提下进行B指令跳转,010:进行Jjump类跳转指令,011:进行Rjump类跳转指令。 |
MemToReg_D | O | 1位,表示在写使能端有效前提下判断是否从DM读取数据写入寄存器中,其余情况均是从ALU运算得到结果写入寄存器中 |
RegDst_D | O | 1位,表示回写的寄存器是否为rd |
ALUsec_D | O | 1位,表示ALU进行操作的第二个操作数是否是立即数 |
ifJump_D | O | 1位,表示是否执行jal 的跳转指令 |
Type[2:0] | O | 3位,表示当前指令的类型 |
输入与输出之间的关系表:
func | 100000 | 100010 | n/a | n/a | n/a | n/a | n/a | 000000 | n/a | 001000 |
---|---|---|---|---|---|---|---|---|---|---|
op | 000000 | 000000 | 001101 | 001111 | 100011 | 101011 | 000100 | 000000 | 000011 | 000000 |
variate | add | sub | ori | lui | lw | sw | beq | nop | jal | jr |
RegDst_D | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
ALUsec_D | 0 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
MemToReg_D | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
RegWrite_D | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 1 | 0 |
MemWrite_D | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
NPCOp[0] | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 |
NPCOp[1] | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
NPCOp[2] | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
ifSignExt_D | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 |
ALUctr_D[0] | (add(00))0 | (sub(01))1 | (or(10))0 | (lui(11))1 | (add(00))0 | (add(00))0 | (sub(01))1 | 0 | 0 | 0 |
ALUctr_D[1] | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
ALUctr_D[2] | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
CMPOp_D[2:0] | 000 | 000 | 000 | 000 | 000 | 000 | 000 | 000 | 000 | 000 |
ifJump_D | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
Type | 000 | 000 | 001 | 001 | 011 | 010 | 100 | 000 | 101 | 110 |
冒险解决
由于冒险导致而添加的信号将以斜体的方式添加到数据通路中,添加的模块将添加在冒险解决中。
种类
结构冒险
不同指令同时需要用同一资源的情况。由于实验采用的是哈佛体系结构,是将存储器和数据存储器分开的,因此考虑寄存器文件需要在D级和W级同时被使用(D级读、W级写)时并且读和写的寄存器为同一个寄存器时的情况(采用从W级到D级的转发来解决,或者采取此时改为下跳沿写入,使写入寄存器在时钟周期内完成,这样刚好可以满足读寄存器所需要的数据稳定时间)。
控制冒险
分支指令的判断结果会影响接下来指令的执行流情况,因为设计均在D级就进行对于分支指令是否跳转的判断,因此可以通过插入空泡,即默认所有跳转指令后都跟着一条空指令nop
,来解决相关的控制冲突,因为当跳转不成立时,直接进行下一条指令即可,而跳转成立时改变地址,进入跳转后的指令,而beq
和跳转后指令间仅隔了一条空指令,不影响程序的进行。
数据冒险
后面指令需求的数据恰好是前面指令提供的数据,因此在后面指令需要用数据时,前面指令可能并被存入寄存器堆中,从而使后面的指令并不能正常地读取正确的数据。
冒险的解决
阻塞
即将寄存器使能端置0,不运转即阻塞,注意:阻塞的同时向下移流水级传递的只能是空指令,也很好理解,不运转肯定不能再进行下一个阶段,可以用来解决Tuse<Tnew的情况。
延迟槽
相当于不考虑跳转指令的下一条是什么指令都进行执行命令,相关指令由编码者决定(最好是空指令)。不考虑F级指令的作废(刷新)就是实现了延迟槽,但是对于jal
指令而言,写入$ra寄存器的地址应该是跳过延迟槽的下一条指令,即延迟槽中指令的地址的下一条。
转发
当Tuse>=Tnew时可以采取转发的方式,直接改变即将从寄存器中读出的值。
A-T模型:
A模型
转发时需要检验需求者和供给者两者对应得地址值相等,且不能为0,因为对于0号寄存器,供给者提供的很可能是非0的值,而由于0号寄存器的特性,需求者提取的值只能是0, 因此此时是不需要转发的,因为0号寄存器的值始终为零。
T模型
需求时间-供给时间模型。
Tuse定义:一条指令在D级时再经过多少个时间周期就必须要使用相应的数据。
Tuse两条性质:1.值为定值。2.一类指令对应的rs、rt可以是两个不同的值,形如sw
类的写入内存型指令,其rs_Tuse=1,而rt_Tuse=2.
注意:对于某些指令并不是所有的rs和rt都是操作数,比如对于I类型指令,rt并不是需要使用的数据,但是不能因此把其Tuse记为0,因为0是代表在D级就使用的意思,与本意截然相反,如此一来对于连续的都写入同一个寄存器的指令可能会发生阻塞的情况导致一些不必要的阻塞进而导致可能的TLE
的发生。最好是记为3,比后续的Tnew的最大值更大来避免触发不必要的阻塞,同理:对于jal
和jr
等指令也是一样的道理。
Tnew定义:位于某个流水级的某类指令,经过多少个时钟周期可算出结果并存储到流水级寄存器中。即运算产生的结果大致多少个周期后被流水级寄存器保持下来了。
两条性质:1.其为动态值,跟指令的类别和处在的流水级有关。2.一种指令在一个时刻至多有一个Tnew值(前提:一个指令至多写一个寄存器)。
因此当Tuse>=Tnew即要用的时间不小于存入寄存器保持稳定的时间时,可以采用转发解决。
反之,立马就要使用且仍为存入寄存器保持稳定时,只能采取阻塞的方法来解决了。
解决冒险的流水线实现
译码器的实现
为了方便不同指令但是操作相似的Tuse和Tnew的计算,新定义一个变量Type[2:0]
来表示当前指令的种类:NorR(3’b000)、I(3’b001)、Save(3’b010)、Load(3’b011)、Bjump(3’b100)、Jjump(3’b101)、Rjump(jr,3’b110)。因此需要需要在译码后每一级传递相应的寄存器地址值以及相应的指令种类Type。
实现阻塞
因为实验要求要在D级就完成阻塞,因此在阻塞时需要冻结F级PC的值与D级流水线寄存器堆的值,将E级流水线寄存器堆的值清零(相当于产生一个空指令),因此需要增加冻结PC值和D级寄存器的变量PC_F_En
和Regs_F_D_En
,以及将E级寄存器清零的变量Regs_D_E_Clr
.
实现转发
有一个比较暴力的方法就是只要不阻塞,且发生冲突就进行转发,但是这里要注意一点,就是因为阻塞是发生在D级的,所以上述的说法可能还不太对,应该是对于D级的GRF模块的输出Rs和Rt只要不阻塞且发生冲突就转发,但是对于E级的ALU模块的输入Rs和Rt和M级的DM的输入都是不管阻塞与否的,原因很简单,阻塞只在D级进行,因此当阻塞发生时,E级和M级应该是要继续进行下去的,继续流水,尽管阻塞后E级或M级可能是空泡,但是阻塞的瞬间,E级和M级都是要正常运转的,因此对于E级和M级来说只要发生冲突就转发。
那么,什么情况算发生冲突呢?对于D级的GRF模块的输出数据,可能来自M级ALU计算结果、M级的PC+8(特殊的jal)、W级的回写寄存器堆的数据,对于来自M级的ALU计算结果,只有NorR
、I
两种类型的指令在M级就可以返回结果了,因此需要特判一下M阶段指令的类型,M级的PC+8显然也需要特判,而对于W级的数据,NorR
、I
、Load
、Jjump
类型的指令都可以返回结果,因此要针对不同的指令进行相应的判断。此外对于Rs和Rt也有不同,因为不是所有的指令都是将rt作为操作数,因此在D级的转发和E级的转发判断中需要注意只有NorR
、Save
、Bjump
(只在D级发挥作用)指令会使用rt作为操作数,因此需要进行相关对于接收数据方的指令类型的特判。
模块设计
Stall_Forward
端口名称 | 方向 | 描述 |
---|---|---|
Type_D[2:0] | I | 当前处于D阶段指令的种类 |
rs_D[4:0] | I | |
rt_D[4:0] | I | |
Type_E[2:0] | I | |
rs_E[4:0] | I | |
rt_E[4:0] | I | |
WA_E[4;0] | I | |
Type_M[2:0] | I | |
rs_M[4:0] | I | |
rt_M[4:0] | I | |
WA_M[4:0] | I | |
RegAddr[4:0] | I | 这里由于jal指令最后写入的时候是特判该指令改写入寄存器地址的因此,需要传入的是最后的写入地址而不是从M级流水寄存器堆传来的回写地址 |
stall | O | 阻塞信号,对于处于D阶段的指令,比较对于同一寄存器后面每一级的T |
clear | O | 置空信号,为了实现阻塞后,D级和F级的指令无法继续流水下去,需要置空泡,也就是将E级流水寄存器堆清零。 |
forward_Rs_D[1:0] | O | D级的GRF的Rs_D输出的选择信号 |
forward_Rt_D[1:0] | O | D级的GRF的Rt_D输出的选择信号 |
forward_Rs_E[1:0] | O | E级 |
forward_Rt_E[1:0] | O | E级 |
forward_MWD_M | O | M级,由W级到M级的转发情况,存在于lw 指令后紧跟sw 指令,且发生数据冲的情况 |
测试方案
手搓的测试数据:
1 |
|
经排查,对于从W级进行转发的PC+8的jal数据的判断应该是rs_D == 5'd31 & (Type_W == Jjump)
(以D_W转发为例)却写成了或运算,导致W级只要是jal
指令就转发,显然是不正确的,忽视了发生冲突的前提。再者就是在阻塞发生时所有转发的暂停显然与E级和M级仍要正常进行相违背。
1 |
|
再经过P5课上de课下bug的痛苦后,痛定思痛手搓了如上的测试代码,大致是依照教程平台提供的覆盖率测试进行修改添加得到的。
思考题
1.我们使用提前分支判断的方法尽早产生结果来减少因不确定而带来的开销,但实际上这种方法并非总能提高效率,请从流水线冒险的角度思考其原因并给出一个指令序列的例子。
以beq
指令为例,其rs_Tuse=rt_Tuse = 0,而因为当Tnew>Tuse时需要阻塞流水线,因此在A-T方法下,该指令有很大概率是要阻塞流水线的,因此该方法可能会在效率方面与提前判断分支带来的提高的部分效率相抵消。
实例代码如下:
1 |
|
2.因为延迟槽的存在,对于 jal 等需要将指令地址写入寄存器的指令,要写回 PC + 8,请思考为什么这样设计?
因为延迟槽的存在,跳转指令后会跟着一条nop
指令或与数据无关的指令,回写PC+8,相当于跳过延迟槽,写入的是延迟槽后的指令地址值,使程序正常运行,而不必再经过一次延迟槽。
3.我们要求所有转发数据都来源于流水寄存器而不能是功能部件(如 DM、ALU),请思考为什么?
因为流水寄存器的输出值在当前时钟周期时是保持稳定的,而功能部件的输出端是会实现变化,因此若使用功能部件转发数据有可能在同一周期内先写入正确的数据,然后写入错误的数据最终导致写入错误的数据,导致数据写入的出错。
4.我们为什么要使用 GPR 内部转发?该如何实现?
为了解决先写后读的冒险问题,实现GPR的输出永远正确的效果。将M级或W级的相应寄存器数据输出接入GPR模块内,通过转发判断选择正确的值输出反映到Rs和Rt端。
5.我们转发时数据的需求者和供给者可能来源于哪些位置?共有哪些转发数据通路?
需求者:D级中GPR模块的输出即处于D级指令的寄存器数据、E级ALU模块的操作数输入即处于E级指令的ALU操作数、M级DM模块的写入数据即当前处于M级指令的写入内存的数据。
供给者:M流水线寄存器得到的稳定的ALU运算结果,一般是需要回写回寄存器的值、W流水线寄存器堆得到的稳定的回写回寄存器的数据。
数据通路:M级流水线寄存器堆输出到D级GPR模块输出、M级流水线寄存器堆输出到E级ALU模块输入、W级流水线寄存器堆输出到D级GPF模块输出、W级流水线寄存器堆输出到E级ALU模块输入、W级流水线寄存器堆输出到M级DM模块输入。
6.在课上测试时,我们需要你现场实现新的指令,对于这些新的指令,你可能需要在原有的数据通路上做哪些扩展或修改?提示:你可以对指令进行分类,思考每一类指令可能修改或扩展哪些位置。
对于R型算术运算指令形如add
、sub
等,可能需要在寄存器堆单元的写入数据选择和写入地址选择等方面进行相应的扩展,相应增加的新的运算方式也需要在ALU模块中进行扩展。如果出现操作数增加的情况,可能还需要对寄存器堆单元与ALU模块进行端口数量上的修改。
对于带有立即数的I型算数、逻辑运算指令形如ori
、lui
等,可能也需要进行R型算术运算指令的相应扩展,还有对于立即数的扩展方式发生改变。
对于访问内存并回写的指令形如lw
等,可能需要进行写入数据和写入地址的修改或扩展。
对于内存写入的指令形如sw
等,可能需要进行写入内存地址和字对齐等要求的修改与扩展,写入数据也有可能需要进行扩展。
对于b类型的跳转指令形如beq
等,可能需要对指令跳转的模块进行扩展,对于跳转条件的判断也可能需要进行扩展。
对于跳转j类型的跳转指令形如jal
等,可能需要对指令跳转的模块进行扩展,同时存入的数据和存入的寄存器地址可能需要扩展相应的选择器。
对于跳转R类型的跳转指令形如jr
等,一般应该不需要做出相应修改和扩展。
所有新增指令,均可能需要扩展控制信号和转发信号产生的模块。
7.简要描述你的译码器架构,并思考该架构的优势以及不足。
我采用了集中式译码的方式,在D级阶段,将所有的控制信号全部解析出来,然后让其随着流水线向后逐级传递,每一级取出指令在当前阶段所需的控制信号后减少取出过的控制信号,避免大量的向后传递导致过多的寄存器开销。
优势在于只需要在译码阶段对指令进行一次译码即可,减少了后续流水级的逻辑复杂度,同时也避免了每一流水级指令的传递。
不足:流水级间需要传递的信号数量十分大,且在解决冒险冲突问题时,并不能很好的判单当前流水级的具体指令,感觉不如分布式,但是已经采用集中式写了很多了。
8.[P5 选做] 请详细描述你的测试方案及测试数据构造策略。
测试方案大致就是依照教程平台提供的覆盖率测试进行对应测试数据的针对性加强,如下:
1 |
|
再根据讨论区里和别人分享的测试数据进行测试。
9.[P5、P6 选做] 请评估我们给出的覆盖率分析模型的合理性,如有更好的方案,可一并提出。
由于是选做,所以你懂的。