# CPU 设计文档及思考题
# 设计草稿
# 思路
- 在单周期 CPU 的基础上,加入流水线寄存器以分割 CPU 的 5 个执行阶段。为了处理冒险,需要加入暂停和转发的模块。为了使得思路更加清晰,先画出流水线 CPU 的设计示意图如下。其中正三角表示转发的供给者,倒三角表示转发的需求者。部分控制信号连线省略了。
# 工程化设计
- 数据通路的设计如下,其中标记橙色部分为转发的需求者,需要替换为转发需求者多路选择器的输出:
- 控制信号的设计如下:
- 暂停转发的设计如下:
- 暂停 / 转发的策略信号:rs-Tuse,rt-Tuse 是从存到 D 级开始,过多少周期用到。Tnew 是从存入当前寄存器开始,过多少周期更新数据。
- 暂停信号例如 (rs-Tuse < Tnew_E && rs == A3@E && RegWrite@E && rs != 0)。其他同理,将两个子公式析取起来得到 rs_Stall,将 rs_Stall 和 rt_Stall 析取得到 Stall。
- 转发信号的条件则是信号已经产生并且读写同一个寄存器、寄存器不为零号、写使能。由于采用集中式译码,因此 “信号已经产生” 的条件不是查看后面流水级寄存器得到的 T_new 为零,而是等到将要转发的那个周期中 T_new 为零。即 T_new_2D 为零,T_new_2E 小于等于一,T_new_2M 小于等于 2。
# 模块设计
PC
信号名 方向 描述 Clk Input 时钟 Reset Input 同步复位到 0x00003000 In Input[31:0] npc Out Output[31:0] pc IM
信号名 方向 描述 A[31:0] Input 指令的字节地址 Out[31:0] Output 指令 GRF
信号名 方向 描述 Clk Input 时钟 Reset Input 同步复位 WE Input 写使能 A1[4:0] Input 操作数 1 地址 A2[4:0] Input 操作数 2 地址 A3[4:0] Input 操作数 3 地址 WD[31:0] Input 写入数据 D1[31:0] Output 读出数据 1 D2[31:0] Output 读出数据 2 NPC
信号名 方向 描述 BLOCK_NPC Input 冻结信号 NPCMode[2:0] Input 运算模式 PC_Out[31:0] Input F 级 PC PC_D[31;0] Input D 级 PC Imm Input[25:0] 25 位立即数 ra Input[31:0] ra beq Input beq 两数是否相等 pc8 Output[31:0] PC_D+8(PC_Out+4) npc Output[31:0] NPC EXT
信号名 方向 描述 EXTMode[1:0] Input 模式 Imm16 Input 待扩展立即数 ext[31:0] Output 结果 Controller
信号名 方向 描述 Opcode[5:0] Input Funct[5:0] Input rs[4:0] Input rt[4:0] Input T_new_E[2:0] Input T_new_D[2:0] Input T_new_M[2:0] Input RegWrite_E Input RegWrite_D Input RegWrite_M Input NPCMode Output[2:0] NPC 模式 EXTMode Output EXT 使能和模式 A3Src[1:0] Output ALU/DM 回写 ALUSrc[1:0] Output B 为寄存器 / 立即数 ALUMode[3:0] Output ALU 模式 MemoryWrite Output DM 写使能 WDSrc[1:0] Ouput DM 选择 RegWrite Output GRF 写使能 Stall Output 暂停 DFV1_DSrc[2:0] Output 转发 DFV2_DSrc[2:0] Output 转发 DFV1_ESrc[1:0] Output 转发 DFV2_ESrc[1:0] Output 转发 DFData_MSrc[1:0] Output 转发 T_new_D[2:0] Output 产生结果时间 ALU
信号名 方向 描述 A[31:0] Input 操作数 1 B[31:0] Input 操作数 2 ALUMode[3:0] Input 模式 C[31:0] Output 运算结果 DM
信号名 方向 描述 Clk Input 时钟 Reset Input 同步复位 En Input 写使能 A[31:0] Input 读地址 WD[31:0] Input 写数据 RD[31:0] Output 读出数据 D
信号名 方向 描述 CLK Input 时钟信号 Reset Input 同步复位 BLOCK_D Input 冻结信号 PC[31:0] Input pc IR[31:0] Input 指令 PC_D Output IR_()_D Ouput 指令的各个域 E
M
W
# 测试方案
- 进行一定的手动测试。其中可以先进行单条指令的测试。这里使用了部分 P3,P4 的测试点。
- 多条指令之间的暂停转发的测试。为了进行充分覆盖,将指令分成不同的类型,类型内和类型之间进行测试。包括前一条指令与后一条指令数据相关,隔一条指令数据相关,隔两条指令数据相关。测试样例如下 (算数指令和立即数指令内和之间):
.data
.text
ori $1,$0,12
ori $2,$0,15
ori $3,$0,16
ori $4,$0,17
add $1,$2,$3
sub $2,$3,$1
add $1,$2,$3
sub $3,$1,$2
add $1,$2,$3
nop
sub $3,$1,$2
add $1,$2,$3
nop
sub $3,$2,$1
add $1,$2,$3
nop
nop
sub $3,$1,$2
add $1,$2,$3
nop
nop
sub $3,$2,$1
add $1,$2,$3
ori $3,$1,15
add $1,$2,$3
nop
ori $3,$1,13
add $1,$2,$3
nop
nop
ori $3,$1,15
add $1,$2,$3
ori $3,$1,15
add $1,$3,$2
ori $3,$1,15
nop
add $1,$3,$2
ori $3,$1,15
nop
add $1,$2,$3
ori $3,$1,15
nop
nop
add $1,$3,$2
ori $3,$1,15
nop
nop
add $1,$2,$3
lui $1,0xff
add $1,$2,$1
lui $1,0xff
add $1,$1,$1
lui $1,0xff
nop
add $1,$2,$1
lui $1,0x1
nop
add $2,$1,$2
lui $1,0x11
nop
nop
add $2,$1,$2
lui $1,0x1
nop
nop
add $2,$2,$1
# 思考题
- 思考题 1:当分支指令于先前的指令不会产生相关时,能够提高效率,但是如果产生数据相关,很有可能分支指令要被迫暂停等待数据更新。例如:
lw $1,0($0)
lw $2,0($3)
beq $1,$2,label
beq 指令要等待两个周期,直到 lw 将要更新的 $2 写入 W 级,才能执行;这和没有提前分支判断(需要等待一个周期)执行完所需要的时钟周期数量是相同的,且不提前判断还可以让延迟槽的指令进入 D 级开始译码。
思考题 2:jal 指令的效果是在 jal 之后所执行的 jr($ra 未被修改)能够跳转回接下来要执行的指令。由于延迟槽中的指令(即 jal 的 pc+4)已经在跳转前紧随 jr 执行完成,无需也不应该再执行一次,返回时应执行延迟槽的下一条指令,故应该保存 pc+8。
思考题 3:如果直接从功能部件进行转发,那么意味着接收者会先计算错误的信号,再计算正确信号。从本周期时钟上升沿开始,接收者接收到正确的输入信号并产生本级结果所经历的组合逻辑延时是提供级和接受级组合逻辑延时之和,这导致如果想要获得稳定正确的输出,则时钟周期被迫延长,这违反了流水线设计的初衷。因此应该在下一周期结果存到流水线寄存器当中再转发。
思考题 4:
- 这是因为由于流水线执行的特点,存在 GRF 同时被读和被写的冒险的情况,即在某上升沿到来之前,之前在 W 级的指令已经准备好数据和控制信号,而此时在 D 级的信号读出更新前数据,将要存到 E 级流水线寄存器当中。此时 D 级读出的信号并不是我们真正想要的。又因为新信号已经产生并传到 GRF,我们可以通过 GRF 的内部转发把 D 级读出的信号换成新的信号,以解决冒险。
- 实现方法:在 GRF 读出数据之前判断是否与将要写入的数据地址相同且不为零,若条件成立,则输出将要写入的值,否则输出寄存器中的值。即:
assign RD1 = ((A1 == A3) && (A1 != 0) && WE) ? WD : register[A1];
思考题 5:
- 需求者有 D 级的 GRF.RD1,GRF.RD2,E 级的 V1,V2 以及 M 级的 Data;而供给者有 E 级的 PC8,M 级别的 PC8 和 AO,以及 W 级的要写入的数据 WD。
- 假设各个接受方的多路选择器分别是 MFV1_D,MFV2_D,MFV1_E,MFV2_E,MFData_M, 分别对应输出相应的更新数据,而供给方只需要在 M 级有一多路选择器 MFBack_M, 其数据来自 ALUO_M 和 PC8_M。
- 转发数据流包括
- PC8_E->MFV1_D,
- PC8_E->MFV2_D,
- MFBack_M->MFV1_D,
- MFBack_M->MFV2_D,
- MFBack_M->MFV1_E,
- MFBack_M->MFV2_E,
- WD_W->MFV1_D,
- WD_W->MFV2_D,
- WD_W->MFV1_E,
- WD_W->MFV2_E,
- WD_W->MFData_M,
思考题 6:
- 指令的分类:目前可以分成如下几类:
- 寄存器算术逻辑运算类型。此类型的运算读两个寄存器,写一个寄存器,使用 ALU 算出结果,不用 Memory。因此 T_use_rs=T_use_rt=1,T_new_D=2。
- 寄存器 - 立即数算术逻辑运算类型。此类型的运算读一个个寄存器,用一个立即数,写一个寄存器,使用 ALU 算出结果,不用 Memory。因此 T_use_rs=1,T_new_D=2。
- 立即数算术逻辑运算类型。只用立即数算出结果,不用寄存器,T_new=2。
- load 类型。访存。用寄存器,立即数算出地址访问 Mem,修改 rt。T_use_rs=1,T_new_D=3。
- store 类型。T_use_rs=1,不修改寄存器。
- branch 类型。T_use_rs=T_use_rt=0,不修改寄存器。
- 立即数 j 类型。不使用寄存器,jal 修改寄存器且 T_new_D=1。
- 寄存器 j 类型。T_use_rs=0。
- 可以看出,在数据通路方面,存在使用寄存器和使用立即数的差别,以及是否经过 ALU 计算的差别,对相应的通路和算术类型,修改控制信号 ALUMode 和 ALUBSrc。
- 在暂停转发的控制上,可以将每个指令分成两方面,即读寄存器方面和写寄存器方面。
- 如果不读寄存器,则无暂停转发,在本人设计中即不需要改动 T_use_rs=T_use_rt,设置为默认值。如果需要读寄存器,那么需要把 T_use_rs,T_use_rt 设置到对应值上。这里还需要注意,可能存在指令使用的寄存器并不是 rs 和 rt 域对应的寄存器,这时候需要修改 D 级的数据通路,通过添加多路选择器将其整合到 rs/rt 中,或者新增加一个流水信号持续下传;同时设置好相应的 T_use_()值以及 MuxSrc。
- 写寄存器方面。如果不写,对转发暂停无影响,RegWrite 和 T_new 保持默认值即可。如果写,RegWrite 和 T_new 设置到相应值。同样的,如果写的寄存器不是 rd/rt/0x1f,那么要增加 A3Src 的来源和 A3Src 控制信号。这里特别需要注意的是,A3 不一定在 D 级就已经知道,在这种情况下,需要在后面的流水级多加 MUX 和 MUXSrc 信号,并且如果之后有读寄存器信号,如果 max {T_new_A3,T_new_Data}>T_use, 那么需要暂停。其余需要转发。
- 指令的分类:目前可以分成如下几类:
思考题 7:
- 本译码器为集中译码,在 D 级输入 Opcode 和 Funct,译码出指令类型,并以此译码形成模式控制信号,是能控制信号和多路选择器信号(记录每个信号取值对应的指令类型)。同时也输入了 EMW 级的寄存器写使能和 A3 信号,直接在 D 级判断了暂停和转发情况,产生暂停信号或转发选择信号。
- 优点是逻辑比较简单,写的代码行数较少;且集中在一个模块内,便于排查错误。不足之处则是需要传递大量的控制信号,流水线寄存器的规模比较大,端口众多且连线复杂,设计时比较麻烦,且不便于添加新的控制信号。