P8 Design documents
Verilog流水线CPU设计文档
一、 CPU设计方案综述
(一) 总体设计概述
使用Verilog开发一个简单的流水线CPU,总体概述如下:
-
此CPU为32位CPU
-
此CPU为流水线设计
-
此CPU支持的指令集为:
mips-c指令集所有指令
(二) 关键模块定义
宏的定义
Folding coding
1 |
|
CPU模块
<一>.F级流水线
1. PC
(1) 端口说明
序号 | 信号名 | 方向 | 描述 |
---|---|---|---|
1 | clk | I | 时钟信号 |
2 | reset | I | 同步复位信号,将PC值置为0x0000_3000: 0:无效 1:复位 |
3 | en | I | 使能信号,决定是否阻塞 |
4 | NPC[31:0] | I | 下一周期PC的地址 |
6 | PC[31:0] | O | 当前执行的PC |
(2) 功能定义
序号 | 功能 | 描述 |
---|---|---|
1 | 复位 | reset有效时,PC置为0x00003000 |
2 | 更新PC的值 | 将PC赋值为NPC |
<二>.D级流水线
1. GRF
(1) 端口说明
序号 | 信号名 | 方向 | 描述 |
---|---|---|---|
1 | clk | I | 时钟信号 |
2 | reset | I | 同步复位信号,将32个寄存器中全部清零 1:清零 0:无效 |
3 | WE | I | 写使能信号 1:可向GRF中写入数据 0:不能向GRF中写入数据 |
4 | A1[4:0] | I | 5位地址输入信号,指定32个寄存器中的一个,将其中存储的数据读出到RD1 |
5 | A2[4:0] | I | 5位地址输入信号,指定32个寄存器中的一个,将其中存储的数据读出到RD2 |
6 | A3[4:0] | I | 5位地址输入信号,指定32个寄存器中的一个,作为RD的写入地址 |
7 | WD[31:0] | I | 32位写入数据 |
8 | WPC[31:0] | I | 当前写入GRF的PC |
9 | RD1[31:0] | O | 输出A1指定的寄存器的32位数据 |
10 | RD2[31:0] | O | 输出A2指定的寄存器的32位数据 |
(2) 功能定义
序号 | 功能 | 描述 |
---|---|---|
1 | 同步复位 | reset为1时,将所有寄存器清零 |
2 | 读数据 | 将A1和A2地址对应的寄存器的值分别通过RD1和RD2读出 |
3 | 写数据 | 当WE为1且时钟上升沿来临时,将WD写入到A3对应的寄存器内部 |
4 | 内部转发 | 当A1和A2之一与A3相等且写入信号为1时,用WD代替RD1或RD2的输出 |
2. EXT
(1) 端口说明
序号 | 信号名 | 方向 | 描述 |
---|---|---|---|
1 | imm16[15:0] | I | 代扩展的16位信号 |
2 | sign[2:0] | I | 无符号或符号扩展选择信号 3’b001:无符号扩展 3’b010:符号扩展 3’b100: 寄存到高位 |
3 | imm32[31:0] | O | 扩展后的32位的信号 |
(2) 功能定义
序号 | 功能 | 描述 |
---|---|---|
1 | 无符号扩展 | 当sign为3’b001时,将imm16无符号扩展输出 |
2 | 符号扩展 | 当sign为3‘b010时,将imm16符号扩展输出 |
3 | 存储到高位 | 当sign为3’100时,将imm16存在高16位 |
3. Controller
(1) 端口说明
序号 | 信号名 | 方向 | 描述 |
---|---|---|---|
1 | op[5:0] | I | instr[31:26]6位控制信号 |
2 | func[5:0] | I | instr[5:0]6位控制信号 |
3 | rt[4:0] | I | instr[20:16]5位控制信号 |
4 | AluOp[11:0] | O | ALU的控制信号 |
5 | WeGrf | O | GRF写使能信号 0:禁止写入 1:允许写入 |
6 | WeDm | O | DM的写入信号 0:禁止写入 1:允许写入 |
7 | branch[3:0] | O | PC转移位置选择信号 4’b0001:其他情况 4’b0010:beq 4’b0100:j||jal 4’b1000:jr; |
8 | AluSrc1[3:0] | O | 参与ALU运算的第一个数 4’b0001:RD1 4’b0010:RD2 |
9 | AluSrc2[3:0] | O | 参与ALU运算的第二个数,来自GRF还是imm 4’b0001:RD2 4’b0010:imm32 4’b0100: offset |
10 | WhichtoReg[7:0] | O | 将何种数据写入GRF? 8’b0000_0001:ALU计算结果 8’b0000_0010:DM读出信号 8’b0000_0100:upperImm 8’b0000_1000: PC+4 |
11 | RegDst[3:0] | O | 写入GRF的哪个寄存器? 4’b0001:rd 4’b0010:rt 4’b0100:31号寄存器 |
12 | SignExt[2:0] | O | 拓展方式: 3’b001:0拓展 3’b010:符号拓展 3’b100:存储到高位 |
13 | B_change[3:0] | O | B转移的类型 4’b0001:beq 4’b0010:slt 4’b0100:blez |
14 | DM_type[5:0] | O | 存取指令类型: 6’b000001:lw/sw 6’b000010:lh/sh 6’b000100:lb/sb 6’b001000:lhu 6’b010000:lbu |
15 | MDop[3:0] | O | 乘除指令选择信号: 4‘b0000:无操作 4’b0001:mult 4’b0010:multu 4’b0011:div 4’b0100:divu 4’b0101:mfhi 4’b0110:mflo 4’b0111:mthi 4’b1000:mtlo |
16 | D_Tuse_rs[1:0] | O | 指令的rs寄存器的值从第一次进入D级到被使用的周期数 |
17 | D_Tuse_rt[1:0] | O | 指令的rt寄存器的值从第一次进入D级到被使用的周期数 |
18 | D_Tnew[1:0] | O | 指令从进入D开始到产生运算结果需要的周期数 |
4.NPC
NPC(下一指令计算单元)
该模块根据当前pc(包括D级和F级)和其他控制信号(NPCOp,CMP输出信息),计算出下一指令所在的地址npc,传入IFU模块。
- 端口定义
信号名 | 方向 | 位宽 | 描述 |
---|---|---|---|
F_pc | I | 32 | F级指令地址 |
D_pc | I | 32 | D级指令地址 |
offset | I | 32 | 地址偏移量,用于计算B类指令所要跳转的地址 |
imm26 | I | 26 | 当前指令数据的前26位(0~25),用于计算jal和j指令所要跳转的地址 |
ra | I | 32 | 储存在寄存器($ra或是jalr指令中存储“PC+4”的寄存器)中的地址数据,用于实现jr和jalr指令 |
ALU_change | I | 1 | B类指令判断结果 1:说明当前B类指令的判断结果为真 0:说明判断结果为假 |
branch | I | 4 | NPC功能选择 0x000:顺序执行 0x001:B类指令跳转 0x010: jal/j跳转 0x011: jr/jalr跳转 |
npc | O | 32 | 输出下一指令地址 |
PC8 | O | 32 | jr指令时存储PC+8的值 |
5.B_transfer(B类指令比较单元)
该单元根据输入的CMPOp信号对当前B指令的类型进行判断,进而对当前输入的数值进行相应比较,最后输出结果。
- 端口定义
信号名 | 方向 | 位宽 | 描述 |
---|---|---|---|
A | I | 32 | 输入B_transfer单元的第一个数据 |
B | I | 32 | 输入B_transfer单元的第二个数据 |
Type | I | 4 | Type功能选择信号 0x0001:beq判断 0x0010:slt判断 0x0100:blez判断 |
ALU_change | O | 1 | 判断结果输出 1: 判断结果为真 0:判断结果为假 |
<三>.E级流水线
1. ALU
(1) 端口说明
序号 | 信号名 | 方向 | 描述 |
---|---|---|---|
1 | A[31:0] | I | 参与运算的第一个数 |
2 | B[31:0] | I | 参与运算的第二个数 |
3 | ALUop[11:0] | I | 决定ALU做何种操作 12’b0000_0000_0001:无符号加 12’b0000_0000_0010:无符号减 12’b0000_0000_0100:与 12’b0000_0000_1000:或 12‘b0000_0001_0000:异或 12‘b0000_0010_0000:或非 12‘b0000_0100_0000:sll 12‘b0000_1000_0000:sra 12‘b0001_0000_0000:srl 12‘b0010_0000_0000:slt 12‘b0100_0000_0000:sltu |
4 | res | O | A与B做运算后的结果 |
(2) 功能定义
序号 | 功能 | 描述 |
---|---|---|
1 | 加运算 | res = A + B |
2 | 减运算 | res = A - B |
3 | 与运算 | res = A & B |
4 | 或运算 | res = A | B |
5 | 左移位运算 | Res=A<<B |
2.MD_Unit
- 端口定义
方向 | 信号名 | 位宽 | 描述 | 输入来源 |
---|---|---|---|---|
I | clk | 1 | 时钟信号 | mips.v中的clk |
I | reset | 1 | 同步复位信号 | mips.v中的reset |
I | en | 1 | MD_Unit使能信号 | 判断是否为乘除计算 |
I | MDop | 4 | 指令选择信号 | Controller |
I | A | 32 | 第一个计算数 | e_A |
I | B | 32 | 第二个计算数 | e_B |
O | HI | 32 | HI寄存器输出 | |
O | LO | 32 | LO寄存器输出 | |
O | out | 32 | 取出HI或LO的值 | |
O | busy | 1 | 是否在进行乘除计算 |
<四>.M级流水线
1. DM_In
- 端口定义
方向 | 信号名 | 位宽 | 描述 | 输入来源 |
---|---|---|---|---|
I | low2 | 2 | 时钟信号 | Address低两位 |
I | WD | 31 | 处理前写入结果 | 32位写入数据 |
I | DM_type | 6 | 决定存取指令类型 | 存取指令类型: 6’b000001:lw/sw 6’b000010:lh/sh 6’b000100:lb/sb 6’b001000:lhu 6’b010000:lbu |
O | data_byteen | 4 | 字节使能信号 | |
O | DM_input | 32 | 处理后的写入结果 |
2.DM_Out
- 端口定义
方向 | 信号名 | 位宽 | 描述 | 输入来源 |
---|---|---|---|---|
I | low2 | 2 | 时钟信号 | Address低两位 |
I | DM_output | 32 | 处理前读入信号 | 32位读入数据 |
I | DM_type | 6 | 决定存取指令类型 | 存取指令类型: 6’b000001:lw/sw 6’b000010:lh/sh 6’b000100:lb/sb 6’b001000:lhu 6’b010000:lbu |
O | RD | 32 | 处理后的读入结果 |
3.CP0
- 端口定义
方向 | 信号名 | 位宽 | 描述 | 输入来源 |
---|---|---|---|---|
I | clk | 1 | 时钟信号 | cpu主时钟 |
I | reset | 1 | 复位信号 | cpu同步复位信号 |
I | WE | 1 | 写入使能 | 前一级相同信号输入 |
I | Address | 5 | CP0 寄存器编号 | 前一级相同信号输入 |
I | WD | 32 | CP0 寄存器的写入数据 | 前一级相同信号输入 |
I | BD_in | 1 | 分支延迟槽指令标志 | 前一级相同信号输入 |
I | VPC | 32 | 中断/异常时的 PC | 前一级相同信号输入 |
I | ExcCode_in | 5 | 中断/异常的类型 | 前一级相同信号输入 |
I | HWInt | 6 | 6 个设备中断 | 前一级相同信号输入 |
I | EXLClr | 1 | 置 0 SR 的EXL 位 | m_eret控制信号输入 |
O | req | 1 | 异常/中断请求 | |
O | EPC | 32 | 处理后的读入结果 | |
O | D_out | 32 | CP0 寄存器的输出数据 |
<五>.各级流水线寄存器
1.IF_ID
D_Reg(IF/ID流水寄存器)
- 端口定义
方向 | 信号名 | 位宽 | 描述 | 输入来源 |
---|---|---|---|---|
I | clk | 1 | 时钟信号 | mips.v中的clk |
I | reset | 1 | 同步复位信号 | mips.v中的reset |
I | en | 1 | D级寄存器使能信号 | stall信号取反 |
I | clr | 1 | D级寄存器清空信号 | 是否清空延迟槽决定 |
I | req | 1 | D级寄存器异常/中断请求信号 | CP0 |
I | F_instr | 32 | F级instr输入 | IFU_instr |
I | F_pc | 32 | F级pc输入 | IFU_pc |
I | F_ExcCode | 5 | F级ExcCode输入 | 前一级相同信号 |
I | F_BD | 1 | F级BD输入 | 前一级相同信号 |
O | D_instr | 32 | D级instr输出 | |
O | D_pc | 32 | D级pc输出 | |
O | D_ExcCode | 5 | D级ExcCode输出 | |
O | D_BD | 1 | D级BD输出 |
2.ID_EX
E_Reg(ID/EX流水寄存器)
端口定义
方向 | 信号名 | 位宽 | 描述 | 输入来源 |
---|---|---|---|---|
I | clk | 1 | 时钟信号 | mips.v中的clk |
I | reset | 1 | 同步复位信号 | mips.v中的reset |
I | clr | 1 | E级寄存器清空信号 | HazardUnit中stall信号 |
I | req | 1 | ||
I | D_RD1 | 32 | D级GRF输出RD1 | 通过B_transfer_D1转发的数据 |
I | D_RD2 | 32 | D级GRF输出RD2 | 通过B_transfer_D2转发的数据 |
I | D_instr_s | 5 | D级instr的shamt | D_instr的s域数据 |
I | D_A1 | 5 | D级A1输入 | D_instr的rs域数据 |
I | D_A2 | 5 | D级A2输入 | D_instr的rt域数据 |
I | D_A3 | 5 | D级A3输入 | 通过MUX_A3选择出的数据 |
I | D_imm32 | 32 | D级imm32输入 | 通过EXT模块扩展出的数据 |
I | D_PC | 32 | D级PC输入 | 前一级相同信号 |
I | D_PC8 | 32 | D级PC8输入 | 前一级相同信号 |
I | Tnew_D | 2 | D级指令的Tnew输入 | 前一级相同信号 |
I | D_Wegrf | 1 | D级控制信号输入 | 前一级相同信号 |
I | D_WeDm | 1 | D级控制信号输入 | 前一级相同信号 |
I | D_ALUop | 7 | D级控制信号输入 | 前一级相同信号 |
I | D_AluSrc1 | 4 | D级控制信号输入 | 前一级相同信号 |
I | D_AluSrc2 | 4 | D级控制信号输入 | 前一级相同信号 |
I | D_WhichtoReg | 8 | D级控制信号输入 | 前一级相同信号 |
I | D_RegDst | 4 | D级控制信号输入 | 前一级相同信号 |
I | D_DM_type | 6 | D级控制信号输入 | 前一级相同信号 |
I | D_ALU_change | 1 | D级ALU_change输入 | 前一级相同信号 |
I | D_MDop | 4 | D级MDop输入 | 前一级相同信号 |
I | D_branch | 5 | D级branch输入 | 前一级相同信号 |
I | D_ExcCode | 5 | D级ExcCode输入 | 前一级相同信号 |
I | D_BD | 1 | D级BD输入 | 前一级相同信号 |
I | D_mfc0 | 1 | D级mfc0输入 | 前一级相同信号 |
I | D_mtc0 | 1 | D级mtc0输入 | 前一级相同信号 |
I | D_eret | 1 | D级eret输入 | 前一级相同信号 |
I | D_syscall | 1 | D级syscall输入 | 前一级相同信号 |
O | E_RD1 | 32 | E级RD1输出 | |
O | E_RD2 | 32 | E级RD2输出 | |
O | E_instr_s | 5 | 移位指令的位移数 | |
O | E_A1 | 5 | E级A1输出 | |
O | E_A2 | 5 | E级A2输出 | |
O | E_A3 | 5 | E级A3输出 | |
O | E_imm32 | 32 | E级imm32输出 | |
O | E_PC | 32 | E级PC输出 | |
O | E_PC8 | 32 | E级PC8输出 | |
O | E_Tnew | 2 | E级指令的Tnew输出 | |
O | E_Wegrf | 1 | E级控制信号输出 | |
O | E_WeDm | 1 | E级控制信号输出 | |
O | E_ALUop | 12 | E级控制信号输出 | |
O | E_AluSrc1 | 4 | E级控制信号输出 | |
O | E_AluSrc2 | 4 | E级控制信号输出 | |
O | E_WhichtoReg | 11 | E级控制信号输出 | |
O | E_RegDst | 3 | E级控制信号输出 | |
O | E_DM_type | 6 | E级控制信号输出 | |
O | E_ALU_change | 1 | E级ALU_change输出 | |
O | E_MDop | 4 | E级MDop输出 | |
O | E_branch | 5 | E级branch输出 | |
O | E_ExcCode | 5 | E级ExcCode输出 | |
O | E_BD | 1 | E级BD输出 | |
O | E_mfc0 | 1 | E级mfc0输出 | |
O | E_mtc0 | 1 | E级mtc0输出 | |
O | E_eret | 1 | E级eret输出 | |
O | E_syscall | 1 | E级syscall输出 |
-
运算功能
Tnew_E = (Tnew_D > 0) ? Tnew_D - 1: 0Tnew_E=(Tnew_D>0)?Tnew_D−1:0
3.EX_MEM
M_Reg(EX/MEM流水寄存器)
- 端口定义
方向 | 信号名 | 位宽 | 描述 | 输入来源 |
---|---|---|---|---|
I | clk | 1 | 时钟信号 | mips.v中的clk |
I | reset | 1 | 同步复位信号 | mips.v中的reset |
I | E_A2 | 5 | E级A2输入 | ALU_out数据 |
I | E_A3 | 5 | E级A3输入(转发值) | MUX_ALU选择出来的数据 |
I | E_RD2 | 32 | E级RD2输入 | 前一级相同信号 |
I | E_ALUout | 32 | E级res输入 | 前一级相同信号 |
I | E_PC | 32 | E级PC输入 | 前一级相同信号 |
I | E_PC8 | 32 | E级PC8输入 | 前一级相同信号 |
I | E_Tnew | 2 | E级Tnew输入 | 前一级相同信号 |
I | E_Wegrf | 1 | E级控制信号输入 | 前一级相同信号 |
I | E_WeDm | 1 | E级控制信号输入 | 前一级相同信号 |
I | E_WhichtoReg | 8 | E级控制信号输入 | 前一级相同信号 |
I | E_RegDst | 4 | E级控制信号输入 | 前一级相同信号 |
I | E_DM_type | 6 | E级控制信号输入 | 前一级相同信号 |
I | E_ALU_change | 1 | E级ALU_change输入 | 前一级相同信号 |
I | E_imm32 | 32 | E级imm32输入 | 前一级相同信号 |
I | E_branch | 5 | E级branch输入 | 前一级相同信号 |
I | E_ExcCode | 5 | E级ExcCode输入 | 前一级相同信号 |
I | E_BD | 1 | E级BD输入 | 前一级相同信号 |
I | E_mfc0 | 1 | E级mfc0输入 | 前一级相同信号 |
I | E_mtc0 | 1 | E级mtc0输入 | 前一级相同信号 |
I | E_eret | 1 | E级eret输入 | 前一级相同信号 |
I | E_syscall | 1 | E级syscall输入 | 前一级相同信号 |
O | M_A2 | 5 | M级A2输出 | |
O | M_A3 | 5 | M级A3输出 | |
O | M_RD2 | 32 | M级RD2输出 | |
O | M_ALUout | 32 | M级res输出 | |
O | M_PC | 32 | M级PC输出 | |
O | M_PC8 | 32 | M级PC8输出 | |
O | M_Tnew | 2 | M级Tnew输出 | |
O | M_Wegrf | 1 | M级Tnew输出 | |
O | M_WeDm | 1 | M级控制信号输出 | |
O | M_WhichtoReg | 8 | M级控制信号输出 | |
O | M_RegDst | 4 | M级控制信号输出 | |
O | M_DM_type | 6 | M级控制信号输出 | |
O | M_ALU_change | 1 | M级ALU_change输出 | |
O | M_imm32 | 32 | M级imm32输出 | |
O | M_branch | 5 | M级branch输出 | |
O | M_ExcCode | 5 | M级ExcCode输出 | |
O | M_BD | 1 | M级BD输出 | |
O | M_mfc0 | 1 | M级mfc0输出 | |
O | M_mtc0 | 1 | M级mtc0输出 | |
O | M_eret | 1 | M级eret输出 | |
O | M_syscall | 1 | M级syscall输出 |
-
运算功能
Tnew_M = (Tnew_E > 0) ? Tnew_E - 1: 0Tnew_M=(Tnew_E>0)?Tnew_E−1:0
4.MEM_WB
W_Reg(MEM/WB流水寄存器)
- 接口定义
方向 | 信号名 | 位宽 | 描述 | 输入来源 |
---|---|---|---|---|
I | clk | 1 | 时钟信号 | mips.v中的clk |
I | reset | 1 | 同步复位信号 | mips.v中的reset |
I | M_A3 | 5 | M级A3输入 | 前一级相同信号 |
I | M_RD | 32 | M级RD输入 | 前一级相同信号 |
I | M_PC | 32 | M级PC输入 | 前一级相同信号 |
I | M_PC8 | 32 | M级PC8输入 | 前一级相同信号 |
I | M_Wegrf | 1 | M级控制信号输入 | 前一级相同信号 |
I | M_WhichtoReg | 1 | M级控制信号输入 | 前一级相同信号 |
I | M_RegDst | 4 | M级控制信号输入 | 前一级相同信号 |
I | M_imm32 | 32 | M级imm32输入 | 前一级相同信号 |
I | M_ALU_change | 1 | M级ALU_change输入 | 前一级相同信号 |
I | M_Tnew | 2 | M级Tnew输入 | 前一级相同信号 |
O | W_A3 | 5 | W级A3输出 | |
O | W_ALUout | 32 | W级res输出 | |
O | W_RD | 32 | W级RD输出 | |
O | W_PC | 32 | W级PC输出 | |
O | W_PC8 | 32 | W级PC8输出 | |
O | W_Wegrf | 1 | W级控制信号输出 | |
O | W_WhichtoReg | 8 | W级控制信号输出 | |
O | W_RegDst | 4 | W级控制信号输出 | |
O | W_imm32 | 32 | W级imm32输出 | |
O | W_ALU_change | 1 | W级ALU_change输出 | |
O | W_Tnew | 2 | W级Tnew输出 |
<六>.暂停、转发处理及相关多路选择器
(一).冲突综合单元(HazardUnit)
方向 | 信号名 | 位宽 | 描述 |
---|---|---|---|
I | D_A1 | 5 | D级A1端输入 |
I | D_A2 | 5 | D级A2端输入 |
I | E_A1 | 5 | E级A1端输入 |
I | E_A2 | 5 | E级A2端输入 |
I | M_A2 | 5 | M级A2端输入 |
I | E_A3 | 5 | E级A3端输入 |
I | M_A3 | 5 | M级A3端输入 |
I | W_A3 | 5 | W级A3端输入 |
I | D_Tuse_rs | 2 | D_Tuse_rs输入 |
I | D_Tuse_rt | 2 | D_Tuse_rt输入 |
I | E_Tnew | 2 | E级Tnew输入 |
I | M_Tnew | 2 | M级Tnew输入 |
I | W_Tnew | 2 | W级Tnew输入 |
I | E_Wegrf | 1 | E级Wegrf输入 |
I | M_Wegrf | 1 | M级Wegrf输入 |
I | W_Wegrf | 1 | W级Wegrf输入 |
I | E_WhichtoReg | 8 | E级WhichtoReg输入 |
I | M_WhichtoReg | 8 | M级WhichtoReg输入 |
I | start | 1 | 乘除开始信号 |
I | busy | 1 | 乘除忙碌信号 |
I | MDU_en | 1 | D级将进行乘除运算信号 |
I | D_eret | 1 | D级eret输入 |
I | E_mtc0 | 1 | E级mtc0输入 |
I | M_mtc0 | 1 | M级mtc0输入 |
O | SelB_D1 | 2 | B_transfer的D1输入转发信号 |
O | SelB_D2 | 2 | B_transfer的D2输入转发信号 |
O | SelALU_A | 2 | ALU输入A转发信号 |
O | SelALU_B | 2 | ALU输入B转发信号 |
O | SelDM | 1 | DM写入WD转发信号 |
O | stall | 1 | 冲突信号 |
(二).控制和冒险简述
-
对于控制冒险,本实验要求大家实现比较过程前移至 D 级,并采用延迟槽。
-
对于数据冒险,两大策略及其应用:
- 假设当前我需要的数据,其实已经计算出来,只是还没有进入寄存器堆,那么我们可以用转发( Forwarding )来解决,即不引用寄存器堆的值,而是直接从后面的流水级的供给者把计算结果发送到前面流水级的需求者来引用。如果我们需要的数据还没有算出来。则我们就只能暂停( Stall ),让流水线停止工作,等到我们需要的数据计算完毕,再开始下面的工作。
(三).冒险处理
冒险处理我们均通过“A_T”法实现——
转发(forward)
当前面的指令要写寄存器但还未写入,而后面的指令需要用到没有被写入的值时,这时候会产生数据冒险,我们首先考虑进行转发。我们假设所有的数据冒险均可通过转发解决。也就是说,当某一指令前进到必须使用某一寄存器的值的流水阶段时,这个寄存器的值一定已经产生,并存储于后续某个流水线寄存器中。
在这一阶段,我们不管需要的值有没由计算出,都要进行转发,即暴力转发。为实现这一机制,我们要清楚哪些模块需要转发后的数据(需求者)和保存着写入值的流水寄存器(供应者)
- 供应者及其产生的数据
流水级 | 产生数据 | MUX名&选择信号名 | MUX输出名 |
---|---|---|---|
E | E_imm32, E_PC8 |
直接流水线传递 | 直接流水线传递 |
M | M_ALUout, M_PC8 |
直接流水线传递 | 直接流水线传递 |
W | w_res, w_RD, w_imm32, W_PC8 |
w_WhichtoReg | WD |
注:当M级指令为读hi和lo的指令时, M_AO中的结果是从上一周期在乘除槽中读取的hi或lo的值;如果是其他指令,M_AO是上一周期ALU的计算结果。
- 需求者及其产生的数据
接收端口 | 选择数据 | HMUX名&选择信号名 | MUX输出名 |
---|---|---|---|
B_transfer_D1 | D_V1, M_out, E_out |
SelB_D1 | d_b_transfer1 |
B_transfer_D2 | d_RD2, m_res, e_res |
SelB_D2 | d_b_transfer2 |
ALU_A | e_RD1, WD, m_res |
SelALU_A | e_A |
ALU_B | e_RD2, WD, m_res |
SelALU_B | e_B |
DM_WD | m_RD2, WD |
SelDM | M_WD_f |
NPC_ra | D_V1_f , E_PC8 , M_PC8 |
SelJr | ra |
从上表可以看出,W级中的数据没有转发到D级,原因是我们在GRF内实现了内部转发机制,将GRF输入端的数据(还未写入)及时反映到RD1或这RD2,判断条件为A3 == A2
或者A3 == A1
。
此时为了生成HMUX的选择信号,我们需要向HCU(冒险控制器)输入”A”数据,然后进行选择信号的计算,执行转发的条件为——
暂停(stall)
接下来,我们来处理通过转发不能处理的数据冒险。在这种情况下,新的数据还未来得及产生。我们只能暂停流水线,等待新的数据产生。为了方便处理,我们仅仅为D级的指令进行暂停处理。
我们把Tuse和Tnew作为暂停的判断依据——
- Tuse:指令进入 D 级后,其后的某个功能部件再经过多少时钟周期就必须要使用寄存器值。对于有两个操作数的指令,其每个操作数的 Tuse 值可能不等(如 store 型指令 rs、rt 的 Tuse 分别为 1 和 2 )。
- Tnew:位于 E 级及其后各级的指令,再经过多少周期就能够产生要写入寄存器的结果。在我们目前的 CPU 中,W 级的指令Tnew 恒为 0;对于同一条指令,Tnew@M = max(Tnew@E - 1, 0)、
在这一阶段,我们找到D级生成的Tuse_rs和Tuse_rt和在E,M,W级寄存器中流水的Tnew_D,Tnew_M,Tnew_W,如下表所示
- Tuse表和计算表达式
指令类型 | Tuse_rs | Tuse_rt |
---|---|---|
calc_R | 1 | 1 |
calc_I | 1 | X |
shift | X | 1 |
shiftv | 1 | 1 |
load | 1 | X |
store | 1 | 2 |
md | 1 | 1 |
mt | 1 | X |
mf | X | X |
branch | 0 | 0 |
j / jr | X | X |
jal / jalr | 0 | X |
lui | X | X |
- Tnew表和计算表达式
指令类型 | Tnew_D | Tnew_E | Tnew_M | Tnew_W |
---|---|---|---|---|
calc_R | 2 | 1 | 0 | 0 |
calc_I | 2 | 1 | 0 | 0 |
shift | 2 | 1 | 0 | 0 |
shiftv | 2 | 1 | 0 | 0 |
load | 3 | 2 | 1 | 0 |
store | X | X | X | X |
md | X | X | X | X |
mt | X | X | X | X |
mf | 2 | 1 | 0 | 0 |
branch | X | X | X | X |
jal / jalr | 0 | 0 | 0 | 0 |
j / jr | X | X | X | X |
lui | 1 | 0 | 0 | 0 |
然后我们Tnew和Tuse传入HCU(冒险控制器中),然后进行stall信号的计算。如果满足以下条件则stall有效——
-
Tnew > Tuse
-
前位点的读取寄存器地址和某转发输入来源的写入寄存器地址相等且不为 0
-
写使能信号有效
-
当E级延迟槽在进行运算(
start | busy
)时,D级为md、mt、mf指令 -
阻塞的构造(D级)
Folding coding
1
2
3
4
5
6
7
8
9
10
11
12assign stall_rs = (D_A1!= 0)&&((D_Tuse_rs<E_Tnew)&&(D_A1 == E_A3)&&E_Wegrf||
(D_Tuse_rs<M_Tnew)&&(D_A1 == M_A3)&&M_Wegrf||
(D_Tuse_rs<W_Tnew)&&(D_A1 == W_A3)&&W_Wegrf);
//the pre is the condition
assign stall_rt = (D_A2!= 0)&&((D_Tuse_rt<E_Tnew)&&(D_A2 == E_A3)&&E_Wegrf||
(D_Tuse_rt<M_Tnew)&&(D_A2 == M_A3)&&M_Wegrf||
(D_Tuse_rt<W_Tnew)&&(D_A2 == W_A3)&&W_Wegrf);
assign stall_md=(start||busy)&&(MDU_en);
assign stall_eret=D_eret&((E_mtc0&(E_A3==5'd14))||(M_mtc0&&(M_A3==5'd14)));
assign stall = (stall_rs||stall_rt||stall_md||stall_eret)&&(~Is_nop);
真值表
端口 | addu | subu | ori | lw | sw | lui | beq |
---|---|---|---|---|---|---|---|
op | 000000 | 000000 | 001101 | 100011 | 101011 | 001111 | 000100 |
func | 100001 | 100011 | |||||
AluOp | 0000001 | 0000010 | 0001000 | 0000000 | 0000000 | 0000000 | 0000000 |
WeGrf | 1 | 1 | 1 | 1 | 0 | 1 | 0 |
WeDm | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
branch | 0001 | 0001 | 0001 | 0001 | 0001 | 0001 | 0010 |
AluSrc1 | 0001 | 0001 | 0001 | 0001 | 0001 | 0001 | 0001 |
AluSrc2 | 0001 | 0001 | 0010 | 0010 | 0010 | 0001 | 0001 |
WhichtoReg | 0001 | 0001 | 0001 | 0010 | 0001 | 0100 | 0001 |
RegDst | 0001 | 0001 | 0010 | 0010 | 0010 | 0010 | 1010 |
SignExt | 0 | 0 | 0 | 1 | 1 | 0 | 1 |
端口 | andi | jal | j | jr | sll | add | sub |
op | 001100 | 000011 | 000010 | 000000 | 000000 | 000000 | 000000 |
func | 001000 | 000000 | 100000 | 100010 | |||
AluOp | 0000100 | 0000000 | 0000000 | 0000000 | 0010000 | 0000000 | 0000001 |
WeGrf | 1 | 1 | 0 | 0 | 1 | 1 | 1 |
WeDm | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
branch | 0001 | 0100 | 0100 | 1000 | 0001 | 0001 | 0001 |
AluSrc1 | 0001 | 0001 | 0001 | 0001 | 0010 | 0001 | 0001 |
AluSrc2 | 0010 | 0001 | 0001 | 0001 | 0100 | 0001 | 0001 |
WhichtoReg | 0001 | 1000 | 0001 | 0001 | 0001 | 0001 | 0001 |
RegDst | 0010 | 0100 | 0001 | 0001 | 0001 | 0001 | 0001 |
SignExt | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
桥和计数器
1.Bridge
- 端口定义
方向 | 信号名 | 位宽 | 描述 | 输入来源 |
---|---|---|---|---|
I | Address_in | 32 | 写入/读取的外设单元的地址 | 前一级相同信号 |
I | WD_in | 32 | 写入外设单元的数据 | 前一级相同信号 |
I | byteen | 4 | 写入外设单元的使能 | 前一级相同信号 |
I | DM_RD | 32 | DM读取值的输入 | 前一级相同信号 |
I | T0_RD | 32 | Timer1读取值的输入 | 前一级相同信号 |
I | T1_RD | 32 | Timer0读取值的输入 | 前一级相同信号 |
O | DM_WE | 4 | DM写入使能 | |
O | T0_WE | 1 | Timer1写入使能 | |
O | T1_WE | 1 | Timer2写入使能 | |
O | Address_out | 32 | 写入/读取的外设单元的地址 | |
O | WD_out | 32 | 写入外设单元的数据 | |
O | RD_out | 32 | 外设单元的读取值输出 |
2.TC
- 端口定义
方向 | 信号名 | 位宽 | 描述 | 输入来源 |
---|---|---|---|---|
I | clk | 1 | 时钟信号 | 前一级相同信号 |
I | reset | 1 | 同步复位信号 | 前一级相同信号 |
I | Addr | 30 | Timer写入地址 | 前一级相同信号 |
I | WE | 1 | Timer写入使能 | 前一级相同信号 |
I | Din | 32 | Timer写入数据 | 前一级相同信号 |
O | D_out | 32 | Timer读取数据 | |
O | IRQ | 1 | 中断请求 |
重要机制实现方法
CP0响应机制
CP0是检测异常和中断的重要部件,异常和中断通过以下接口传入——
- 中断信息通过 HWInt[7:2] 接口进入,其中HWInt[2]连接Timer0的终端请求,HWInt[3]连接Timer1的终端请求,HWInt[4]连接外部的中断请求。
- 异常信息通过ExcCode_in和BD_in两个接口传入,ExcCode_in传入的是异常代码(ADEL,ADES,RI,OV),BD_in传入的是延迟槽指令标志(有效则表示当前指令为延迟槽指令)。
检测到中断或者异常时,CP0会进行判断并响应,决定是否将req(CP0向CPU发出的中断请求)置1。判断逻辑如下——
1 | wire inter_req = (|(HWInt & IM)) & IE & (!EXL);//中断有效判断 |
当req有效,CP0还需要完成以下任务——
-
EXL置1
-
将M级PC存入EPC(延迟槽指令中存入EPC-4)
-
如果当前响应中断,ExcCode寄存器写入0;若响应异常,ExcCode寄存器写入外部传入的异常代码ExcCode_in
-
BD寄存器写入外部传入的BD_in信号
-
此外,无论是否发出中断请求,在每一周期均需要将HWInt[6:2]写入Cause寄存器中的的IP域
系统桥设计
系统桥其实是充当一个“交换机”的角色,将CPU传来的地址写入相应的外设(DM、Timer0、Timer1),只需要组合逻辑便可实现。
异常识别
P7中我们考虑的异常情况有以下几种——
- F级异常:
- PC地址没有字对齐(AdEL)
- PC地址超过0x3000 ~ 0x6ffc(AdEL)
- D级异常:
- 未知的指令码(RI)
- E级异常:
- addi、add、sub计算溢出(Ov)
- load类指令计算地址时加法溢出(AdEL)
- store类指令计算地址时加法溢出(AdES)
- M级异常:
- lw取数地址未 4 字节对齐(AdEL)
- lh、lhu取数地址未与 2 字节对齐(AdEL)
- lh、lhu、lb、lbu取 Timer 寄存器的值(AdEL)
- load型指令取数地址超出 DM、Timer0、Timer1 的范围(AdEL)
- sw存数地址未 4 字节对齐(AdES)
- sh存数地址未 2 字节对齐(AdES)
- sh、sb存 Timer 寄存器的值(AdES)
- sw向计时器的 Count 寄存器存值(AdES)
- store型指令存数地址超出 DM、Timer0、Timer1 的范围(AdES)
针对以上列出的异常情况,我们在每一个流水级对异常进行检测。由于教程提出了以下要求——
- 发生取指异常后视为
nop
直至提交到 CP0。 - 发生
RI
异常后视为nop
直至提交到 CP0。 load
与store
类算址溢出按照AdEL
与AdES
处理。
因此不会出现一个指令在多级出现异常的情况。如果某个流水级出现了新的异常,我们将这个异常流水到下一级即可,而不是流水上一级传来的异常;如果这个流水级没有出现新的异常,则将上一级传来的异常继续流水给下一级即可。
P8工程模块
fpga_top(顶层模块)
该模块替代P7中的mips模块,增加了和外设(数码管、LED等等)通信的IO接口。
- F级异常:
信号名 | 方向 | 位宽 | 描述 |
---|---|---|---|
clk_in | I | 1 | 时钟信号 |
sys_rstn | I | 1 | 低电平同步复位信号 |
dip_swithc0 | I | 8 | 第0组拨码开关控制信号 |
dip_swithc1 | I | 8 | 第1组拨码开关控制信号 |
dip_swithc2 | I | 8 | 第2组拨码开关控制信号 |
dip_swithc3 | I | 8 | 第3组拨码开关控制信号 |
dip_swithc4 | I | 8 | 第4组拨码开关控制信号 |
dip_swithc5 | I | 8 | 第5组拨码开关控制信号 |
dip_swithc6 | I | 8 | 第6组拨码开关控制信号 |
dip_switch7 | I | 8 | 第7组拨码开关控制信号 |
user_key | I | 8 | 用户按键开关控制信号 |
led_light | O | 31 | LED灯控制信号 |
digital_tube2 | O | 8 | 第2组四段数码管驱动信号 |
digital_tube_sel2 | O | 1 | 第2组四段数码管片选信号 |
digital_tube1 | O | 8 | 第1组四段数码管驱动信号 |
digital_tube_sel1 | O | 3 | 第1组四段数码管片选信号 |
digital_tube0 | O | 8 | 第0组四段数码管驱动信号 |
digital_tube_sel0 | O | 3 | 第0组四段数码管片选信号 |
uart_rxd | I | 1 | UART接收信号 |
uart_txd | O | 1 | UART发送信号 |
Bridge(系统桥)
信号名 | 方向 | 位宽 | 描述 |
---|---|---|---|
clk | I | 1 | 时钟信号 |
reset | I | 1 | 高电平同步复位信号 |
A_in | I | 32 | 写入/读取的外设单元的地址 |
WD_in | I | 32 | 写入外设单元的数据 |
byteen | I | 4 | 写入外设单元的使能 |
DM_RD | I | 32 | DM读取值的输入 |
Timer_RD | I | 32 | Timer读取值的输入 |
UART_RD | I | 32 | UART读取值的输入 |
Tube_RD | I | 32 | Tube读取值的输入 |
GPIO_RD | I | 32 | GPIO读取值的输入 |
A_out | O | 32 | 写入/读取的外设单元的地址 |
WD_out | O | 32 | 写入外设单元的数据 |
RD_out | O | 32 | 外设单元的读取值输出 |
DM_WE | O | 4 | DM写入使能 |
Timer_WE | O | 1 | Timer写入使能 |
UART_WE | O | 1 | UART写入使能 |
Tube_WE | O | 4 | Tube写入使能 |
GPIO_WE | O | 4 | GPIO写入使能 |
UART_STB | O | 1 | UART片选信号 |
Tube(数码管驱动模块)
信号名 | 方向 | 位宽 | 描述 |
---|---|---|---|
clk | I | 1 | 时钟信号 |
reset | I | 1 | 高电平同步复位信号 |
A | I | 32 | 读/写数码管的地址 |
WD | I | 32 | 写入数码管的数据 |
WE | I | 1 | 数码管写入使能 |
RD | I | 32 | 数码管读取数据 |
digital_tube2 | O | 8 | 第2组四段数码管驱动信号 |
digital_tube_sel2 | O | 1 | 第2组四段数码管片选信号 |
digital_tube1 | O | 8 | 第1组四段数码管驱动信号 |
digital_tube_sel1 | O | 3 | 第1组四段数码管片选信号 |
digital_tube0 | O | 8 | 第0组四段数码管驱动信号 |
digital_tube_sel0 | O | 3 | 第0组四段数码管片选信号 |
GPIO(通用IO驱动模块)
该模块位于CPU模块中,主要用于获取外部中断信息和内部异常信息,进行判断后输出异常/中断请求。同时该模块中设有四个寄存器——SR,Cause,EPC和PRIP。
信号名 | 方向 | 位宽 | 描述 |
---|---|---|---|
clk | I | 1 | 时钟信号 |
reset | I | 1 | 复位信号 |
A | I | 32 | 读GPIO的地址 |
WD | I | 32 | 写入GPIO的数据 |
WE | I | 1 | GPIO写入使能 |
dip_swithc0 | I | 8 | 第0组拨码开关控制信号 |
dip_swithc1 | I | 8 | 第1组拨码开关控制信号 |
dip_swithc2 | I | 8 | 第2组拨码开关控制信号 |
dip_swithc3 | I | 8 | 第3组拨码开关控制信号 |
dip_swithc4 | I | 8 | 第4组拨码开关控制信号 |
dip_swithc5 | I | 8 | 第5组拨码开关控制信号 |
dip_swithc6 | I | 8 | 第6组拨码开关控制信号 |
dip_switch7 | I | 8 | 第7组拨码开关控制信号 |
user_key | I | 8 | 用户按键开关控制信号 |
RD | O | 8 | GPIO的读取数据 |
led_light | O | 32 | LED灯控制信号 |
UART模块
该模块为系统桥,实际上是区分地址的组合逻辑模块,用于CPU和DM、Timer1、Timer0之间的的数据交换。
信号名 | 方向 | 位宽 | 描述 |
---|---|---|---|
CLK_I | I | 1 | 时钟信号 |
RST_I | I | 1 | 高电平同步复位信号 |
ADD_I | I | 3([4:2]) | 读/写UART的地址 |
WE_I | I | 1 | UART写使能信号(和CPU交互) |
DAT_I | I | 32 | UART写入数据(和CPU交互) |
STB_I | I | 1 | UART片选信号输入 |
RxD | I | 1 | UART接收数据(和外设交互) |
DAT_O | O | 32 | UART读取数据(和CPU交互) |
ACK_O | O | 1 | UART片选信号输出 |
TxD | O | 1 | UART发送数据(和外设交互) |
inter | O | 1 | 中断请求信号 |
IP Core 的使用和相关调整
因为在P8实验中,为了使得我们的微系统是“可综合的”,我们需要将用常规方法描述的DM和IM删去,同时用FPGA内封装好的特殊功能部件——“块储存器”(“IP核”的一种)来替换。我们在ISE中生成后,直接在我们的微系统中调用即可。调用模板如
Folding coding
1 | IM u_IM(//input |
与我们之前使用的IM和DM不同,P8中使用的块储存器是 “同步读出”,这种行为差异造成了数据读出的时机不同,为了适应这一变化,需要对流水线的结构作出一定的修改。笔者采用的是修改流水寄存器的方法,即短接法。教程中的描述为——
修改流水级寄存器。由于 Block RAM/ROM 的读端口可以抽象成组合逻辑与寄存器的连接,而 P6/P7 的流水线则是将组合逻辑读出的值输入 M/W 级流水寄存器;因此若将 M/W 级流水寄存器中的流水m_data_rdata的寄存器短接,就等价于将 Block RAM 读出端的"寄存器"移到了 M/W 流水寄存器上。这样修改后,流水线的结构与修改前几乎是相同的。
此外,我们该需要对D级流水寄存器和Bridge进行相应调整
- D级寄存器的调整:为保证D 级发生阻塞时,指令被正确地 stall 在 D 级,我们需要D级流水寄存器中增加一个l临时寄存器来存储上一次在D级的instruction。然后再通过一个选择信号来选择当前进入D级的instruction是IM同步读出的instrcution还是临时寄存器中的instruction。清空延迟槽时将D级instruction置0的操作也类似。
Folding coding
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20//IF流水寄存器
reg stall_tag;
reg clr_tag;
reg [31:0] last_instr_reg;
assign D_Instr = clr_tag ? 32'd0 :
stall_tag ? last_instr_reg :
F_Instr;
always @(posedge clk) begin
if(reset)begin
last_instr_reg <= 0;
stall_tag <= 0;
clr_tag <= 1'b1;
end
else begin
last_instr_reg <= D_Instr;
stall_tag <= (~en) ? 1'b1 : 1'b0;
clr_tag <= (req|clr) ? 1'b1 : 1'b0;
end
end - Bridge的调整:由于Bridge沟通的外设既有同步读的DM,也有异步读的Timer,Tube,UART,GPIO等设备,这样在Bridge向CPU传数据的时候会产生冲突。因此笔者将他们的读方式统一成“同步读”,方法是——在Bridge为每一个外设设置一个数据寄存器(DM除外),每个时钟上升沿更新从这些外设中异步读出的值。此外,我们还需要将被访问的外设的地址存进一个临时寄存器,每次根据这个临时寄存器的地址从若干数据寄存器中选择相应的值,返回到CPU中。
Folding coding
1 | reg [31:0] Timer_RD_reg; |
数码管驱动设计
数码管驱动中有两个寄存器,和对其他外设的操作一样,我们需要实现CPU对寄存器的读写。代码比较简单,如下所示
Folding coding
1 | reg [15:0] tube_0_data; |
在数码管驱动设计中,我们不仅要能够向驱动中的寄存器读写数据,还要根据驱动内部寄存器的值向真正的数码管传递对应的电平信号,来控制数码管的亮灭。四段数码管为一组,每组数码管由一个驱动信号、一个片选信号控制。原理如下
一段数码管显示的16进制数与控制信号的关系如下表所示(注意:实验中采用的是共阳极数码管,低电平时亮,高电平时灭)
数码管显示的16进制数 | 数码管控制信号{DP,G,F,E,D,C,B,A} |
---|---|
0 | 0xC0 |
1 | 0xF9 |
2 | 0xA4 |
3 | 0xB0 |
4 | 0x99 |
5 | 0x92 |
6 | 0x82 |
7 | 0xF8 |
8 | 0x80 |
9 | 0x90 |
A | 0x88 |
B | 0x83 |
C | 0xC6 |
D | 0xA1 |
E | 0x86 |
F | 0x8E |
为实现该功能,笔者设计了单独的模块digital_tube
来对一组数码管的亮灭进行控制,直接在Tube
模块中调用即可。
Folding coding
1 | /* |
通用IO驱动设计
通用IO包括三部分——64 位用户输入微动开关、 8 个通用按键开关和LED、LED。前两个是输入设备,只有LED是输出设备。
- 对于输入设备(64 位用户输入微动开关、 8 个通用按键开关)而言,外部传来的数据只有在CPU读取该设备数据的时候才是有效的,也就是说,在CPU读其他外设时,无论该输入设备输进什么值,都是无效值,也就没有必要用寄存器将外界读取的值存储下来,只需要将输入设备的引脚直接连接到控制模块的读数据端口,使 CPU 将它们当作一个只读的寄存器。
- 对于输出设备(LED)而言,每次CPU写进设备驱动的数据都应该保存下来,这样才能保证CPU读写其他外设时,该驱动仍然向真正的输出外设稳定地输出数据。
整体写法也比较简单,如下所示——
Folding coding
1 | reg [31:0] led_reg; |
UART的修改
为了实现UART中断功能,即接收到一次完整的数据后向CPU产生中断信号,CPU对其进行响应,我们需要对UART进行一定的修改,并使用教程里的源码模仿即可。其实也比较简单。
Folding coding
1 | output inter; |
- Warning:一定要修改UART的头文件中有关时钟频率的宏定义(根据21级教程来,时钟频率改为25MHz)!否则会导致波特率不匹配!
二、 MIPS软件代码
Folding coding
1 | .text |
自动化生成coe文件
当我们写完软件部分的代码时,需要将其转换成机器码,同时还需要根据coe文件格式进行一定处理——文件开头加相应前缀,每行机器码后面需要加逗号。如果手动进行修改的话费时费力,可以通过下面的python脚本自动生成coe文件。
Folding coding
1 | import os |
- Title: P8 Design documents
- Author: Charles
- Created at : 2022-12-26 21:12:55
- Updated at : 2023-11-05 21:36:02
- Link: https://charles2530.github.io/2022/12/26/p8-design-documents/
- License: This work is licensed under CC BY-NC-SA 4.0.