思考题
1 单独乘除法部件
由于实际上乘除法计算需要不止一个时钟周期,如果放进ALU,在整个乘除法计算过程中就需要将整个CPU阻塞许多个时钟周期,而如果独立出来乘除法部件,就可以在进行乘除法计算的过程中“并行”地执行其他指令,而不会因为乘除法的计算延迟其他指令(比如add beq等)的进行,有助于提高CPU的效率(指降低总体运行周期数)
设置独立的HI、LO寄存器也是出于类似的考量。首先,如果将乘除法放进ALU,结果按照正常计算指令存入GRF,除了前文提到的会阻塞整个CPU之外,还会遇到另一个问题——乘除法需要同时写入两个寄存器HI和LO,而我们设计的GRF只有一个写入数据端口(也只有一个写入使能端),而且如果要更改为支持同时写入两个数据,不仅GRF需要更改,整个转发逻辑都要更改,会需要对整个CPU进行许多“只对乘除法有用”的修改。
而如果将乘除法模块独立出来,但不设置HI和LO寄存器,除了也会遇到上文中同时写入两个值的情况,还可能在乘除法结束计算时和别的指令“撞车”,就需要处理一系列复杂的阻塞情况。
而如果独立出来HI和LO寄存器,就不会遇到上述一系列问题,是在保持原CPU设计不进行大幅更改而能支持乘除法且保持高效的最好做法。
2 真实的CPU实现乘除法
首先是有无符号性,无符号情况直接运算即可,有符号情况下先记录原数符号,再将原数取绝对值后运算,运算结束后根据结果的符号来处理。
具体运算方面,类似实际运算中的竖式计算。
乘法采用一个64位寄存器(初始为0)记录结果,对于乘数A从小到大遍历每一位,如果是0就直接将结果寄存器的数左移一位,如果是1就将乘数B加进结果寄存器后再左移一位。
除法采用32位商寄存器和65位余数寄存器,首先将余数寄存器填充为被除数的绝对值(的零扩展),商寄存器填充0,每次将商寄存器左移一位,最低位填充1,余数寄存器减去除数寄存器内容,判断余数是否小于0:如果是则将商寄存器最低位置1并将余数寄存器减去的内容加回来。
乘除法均为完成32次迭代后输出结果。
3 结合实现分析如何处理Busy带来的周期阻塞
添加一个Busy信号,同时乘除法相关操作指令(mfhi mflo mthi mtlo)有对应的MNDUsage控制信号或MNDWE控制信号会置为非默认值(有对应宏定义)。当F/D流水线寄存器中的指令通过这两个信号判断是这类指令时,如果Busy为真,则进行阻塞。
4 采用字节使能信号的好处
清晰性:四个位每位对应一个字节,只要看信号的二进制表示就能清晰地看出需要写入哪几个字节,很明确也很简单。相比之下如果对不同位置sb/sh/sw进行编码,则需要4+2+1=7种信号,虽然只需要三位,但是读起来很不清楚,需要查信号的定义方法才能确定是怎么读信号,不够清晰。
统一性:如果采用上述7种信号3位编码,需要单独判断,单独处理sb/sh/sw三种情况,如果出现其他写内存方式还需要另行扩展,十分不便。而采用字节使能信号则可以直接判断每一字节对应信号是否是1,如果是1就写入对应数据即可,十分统一。
5 使用字节读写的高效情况
我们在进行lw和sw指令时,很多时候都是范围较小的数据,一个字长的范围有大部分内容在写入前后都是0(或读取时高位都是0,读不读不影响结果),因此“实际上”从DM取得的有效数据和向DM写入的有效数据不足一字长。在需要读取的数据能确定同一字长其他字节均为0的情况下,或需要写入的数据同字长其他字节均为0且写入内存同字其他部分也都为0的情况下,则不需要按字读写,只需要按字节读写(或类似地,按半字读写)能获得更高的效率。
6 对抗复杂性的抽象和规范手段
采用了分类译码的方式,在集中式译码器中通过多重case语句,先将同类语句分类,设置这一类语句共同的控制信号值,再用内层case对每一种不同语句设置特定控制信号值。
同时对于乘除法相关指令采用了四个控制信号的方式,虽然这样控制信号较多,但逻辑清晰便于使用整理。
同时所有的值都采用宏定义的方式统一命名,减少出错情况。下面是译码器中乘除法部分,作为示例:
1 |
7 不同指令类型组合的冲突和解决方式
在P5的基础上,计算指令、访存指令和跳转分支指令除了对应的ALU计算方式和CMP比较方式有所区分之外,并没有明显区别,实际的冲突类型和冲突方式和P5类似,不再赘述。
因此主要新增问题集中在乘除法指令上。主要有以下几个问题:
- 乘除法指令在busy时的阻塞问题,在mult/multu/div/divu后不同周期后跟mfhi/mflo/mthi/mtlo进行测试
- mfhi/mflo需要写入GRF,因此后跟jr/beq/bne时需要阻塞,分别举例阻塞即可
- mult/multu/div/divu/mthi/mtlo需要在E级使用对应寄存器的值,需要考虑相关的阻塞和转发问题,如跟在lw后等(如果采用A-T法,将对应的Tuse值设置为和计算类指令相同即可,实际上如果P5没有问题的情况下应该不容易出问题)
实际问题集中在增量开发上,只要前期CPU设计符合规范,抽象方法合适,基本上不容易出大问题。
8 测试策略
首先我们将指令分为如下几个大类:(含扩展指令)(实际在译码器中也是类似的分类)
- R型 跳转到寄存器 jr jalr
- R型计算 add addu sub subu and or xor nor slt sltu sll sllv srl srlv sra srav
- R型 乘除法相关 mult multu div divu mfhi mflo mthi mtlo
- lui
- I型计算 ori andi addi addiu slti sltiu xori
- 分支 beq bne bgez bgtz blez bltz
- 读内存 lw lh lhu lb lbu
- 写内存 sw sh sb
- J型指令 jal j
测试预处理
首先随机生成大量计算指令,将1~31号寄存器填充好不同的值
然后随机生成大量写内存指令,尽量将内存填入不同内容
转发测试
- W-E转发,隔一条指令分别运行同寄存器的读内存指令和(R型/I型计算,乘除法相关)指令
- M-E转发,连续运行同寄存器递增的(R型/I型计算,乘除法相关)指令
- W-M转发,读内存后加写内存操作,同寄存器
- GPR内部转发,隔两条指令运行的同寄存器指令,第一条有写操作,第二条用到同寄存器即可
- M-D转发,隔一条指令分别运行同寄存器指令,第一条有写操作,第二条用到同寄存器即可
将上述不同转发情况用生成程序+修改操作数进行多次叠加修改,测试多重转发,随机生成多组连续内容。
阻塞测试
- 连续运行同寄存器的读内存指令和(R型/I型计算,乘除法相关)指令
- 在分支指令或跳转寄存器指令前加任何写寄存器指令
综合测试
- 首先对计算指令进行测试,随机生成大量不含跳转或分支指令的各类计算指令,考虑按照五条进行分类,不同类别排列组合进行测试
- 对于每种指令,测试分支类和跳转类指令分别在紧跟、隔一条、隔两条、隔三条的位置跳转的情况,分别对向前跳转和向后跳转进行测试
- 先向某寄存器写入对应跳转目标的指令位置,然后分别测试前述写入指令和跳转到寄存器指令间隔0、1、2、3条指令的情况,再在相隔更远的情况下用类似分支和跳转类测试的方式测试,注意不能更改跳转目标寄存器。
指令集综述
简要说明
包含指令集如下:
1 | add sub and or slt sltu lui |
R I J 型指令说明
R型
包括
1 | add sub and or slt sltu mult multu div divu mfhi mflo mthi mtlo jr |
位置 (位数) | 31~26 (6) | 25~21 (5) | 20~16 (5) | 15~11 (5) | 10~6 (5) | 5~0 (6) |
---|---|---|---|---|---|---|
类型 | 操作码 | 寄存器地址 | 寄存器地址 | 寄存器地址 | 五位立即数 | 操作码 |
名称 | OP | rs | rt | rd | shamt (sa) | func |
解释 | 恒为0x0 | 第一参 | 第二参 | 目的参 | 移位量 | 指示操作类型 |
25~21 (5) | 20~16 (5) | 15~11 (5) | 10~6 (5) | 5~0 (6) | ||
---|---|---|---|---|---|---|
寄存器地址 | 寄存器地址 | 寄存器地址 | 五位立即数 | 操作码 | ||
指令名 | 指令解释 | rs | rt | rd | shamt (sa) | func |
add | GPR[rd] <= GPR[rs] + GPR[rt] | rs | rt | rd | 00000 | 10_0000 |
sub | GPR[rd] <= GPR[rs] - GPR[rt] | rs | rt | rd | 00000 | 10_0010 |
and | GPR[rd] <= GPR[rs] & GPR[rt] | rs | rt | rd | 00000 | 10_0100 |
or | GPR[rd] <= GPR[rs] | GPR[rt] | rs | rt | rd | 00000 | 10_0101 |
slt | GPR[rd] <= (GPR[rs] < GPR[rt]) ? 32’b1 : 32’b0 | rs | rt | rd | 00000 | 10_1010 |
sltu | GPR[rd] <= (GPR[rs] < GPR[rt]) ? 32’b1 : 32’b0 (u) | rs | rt | rd | 00000 | 10_1011 |
mult | (HI,LO) <= GPR[rs] * GPR[rt] | rs | rt | 00000 | 00000 | 01_1000 |
multu | (HI,LO) <= GPR[rs] * GPR[rt] (u) | rs | rt | 00000 | 00000 | 01_1001 |
div | (HI,LO) <= GPR[rs] / GPR[rt] | rs | rt | 00000 | 00000 | 01_1010 |
divu | (HI,LO) <= GPR[rs] / GPR[rt] (u) | rs | rt | 00000 | 00000 | 01_1011 |
mfhi | GPR[rd] <= HI | 00000 | 00000 | rd | 00000 | 01_0000 |
mflo | GPR[rd] <= LO | 00000 | 00000 | rd | 00000 | 01_0010 |
mthi | HI <= GPR[rs] | rs | 00000 | 00000 | 00000 | 01_0001 |
mtlo | LO <= GPR[rs] | rs | 00000 | 00000 | 00000 | 01_0011 |
jr | PC <= GPR[rs] | rs | 00000 | 00000 | 00000 | 00_1000 |
I型
包括
1 | lui addi andi ori lb lh lw sb sh sw beq bne |
位置 (位数) | 31~26 (6) | 25~21 (5) | 20~16(5) | 15~0 (16) |
---|---|---|---|---|
类型 | 操作码 | 寄存器地址 | 寄存器地址 | 立即数 |
名称 | OP | rs | rt | imm |
解释 | 指示操作类型 | 第一参 | 目的参 | 立即数 |
31~26 (6) | 25~21 (5) | 20~16(5) | 15~0 (16) | ||
---|---|---|---|---|---|
操作码 | 寄存器地址 | 寄存器地址 | 立即数 | ||
指令名 | 指令解释 | OP | rs | rt | imm |
lui | GPR[rt] <= {imm, {16{1’b0}}} | 00_1111 | 00000 | rt | imm |
addi | GPR[rt] <= GPR[rs] + sign_ext(imm) | 00_1000 | rs | rt | imm |
andi | GPR[rt] <= GPR[rs] & zero_ext(imm) | 00_1100 | rs | rt | imm |
ori | GPR[rt] <= GPR[rs] | zero_ext(imm) | 00_1101 | rs | rt | imm |
lb | GPR[rt] <= memory[GPR[base] + offset] (byte) | 10_0000 | base | rt | offset |
lh | GPR[rt] <= memory[GPR[base] + offset] (half byte) | 10_0001 | base | rt | offset |
lw | GPR[rt] <= memory[GPR[base] + offset] (word) | 10_0011 | base | rt | offset |
sb | memory[GPR[base] + offset] <= GPR[rt] (byte) | 10_1000 | base | rt | offset |
sh | memory[GPR[base] + offset] <= GPR[rt] (half byte) | 10_1001 | base | rt | offset |
sw | memory[GPR[base] + offset] <= GPR[rt] (word) | 10_1011 | base | rt | offset |
beq | PC <= PC + 4 + sign_ext({offset, 2’b0}) if (GPR[rs] == GPR[rt]) | 00_0100 | rs | rt | offset |
bne | PC <= PC + 4 + sign_ext({offset, 2’b0}) if (GPR[rs] != GPR[rt]) | 00_0101 | rs | rt | offset |
J型
包括
1 | jal |
位置 (位数) | 31~26 (6) | 25~0 (26) |
---|---|---|
类型 | 操作码 | 指令地址 |
名称 | OP | address |
解释 | 指示操作类型 | 目标跳转位置 |
IFU 取指令单元 (F)
功能要求
- 内部包括 PC(程序计数器)、IM(指令存储器)及相关逻辑。
- PC 同步复位,复位值为起始地址。
- 起始地址:0x00003000。
- IM 容量为 32bit × 4096字(按字取的地址宽度12位)。
- 支持冻结
- 可能出现问题,不确定,目前未考虑,后续debug可以测试
输入端口
- clk 时钟信号
- Reset 同步复位信号
- Froze 冻结信号,收到该信号时忽略时钟上沿
- PCSrc [1:0] 决定下条PC指令使用何处输入
- CMPOut 对应CMP的输出,代表是否允许branch,1代表允许
- PCImm [15:0] 符号扩展后的立即数(对应beq给定的立即数)
- PCInstrIndex [25:0] J型指令的立即数值(对应jal给定的立即数)
- PCJumpReg [31:0] 来自寄存器的跳转地址(对应jr指令对应地址读取到的值)
输出端口
- Inst [31:0] 取得的指令
- PCNxt [31:0] 对应PC+4,用于jal
行为逻辑
- 支持同步复位,复位到0x3000
- 任何时刻,输出当前PC寄存器对应地址中的指令
- 收到冻结信号时忽略clk上沿,PC <= PC
- 每个clk上沿更新PC的值:
1 | if (PCSrc == 2'b00 || (PCSrc == 2'b01 && ALUZero == 0)) begin // 无b/j指令或b指令无效,直接PC+=4 |
Controller 集中译码器 (F)
功能要求
将输入的OPCode和func转化为CPU各部分对应信号输出,各信号随流水线逐级传递。
实现方式
分类判断后指定各项输出的值。
输入端口
- OPCode [5:0] 标识命令种类的指令,按MIPS汇编标准执行
- func [5:0] 标识R型指令具体种类的指令,按MIPS汇编标准执行
输出端口
-
CmpType [3:0] 分支指令比较方法 ->CMP
-
ExtType 决定扩展器扩展类型,0零扩展,1符号扩展 ->EXT
-
PCSrc [1:0] 决定下条PC指令使用何处输入,00代表PC+=4,01代表branch,10代表jal,11代表jr ->IFU
-
ALUCtrl [3:0] 决定当前ALU执行何种运算,具体见ALU说明
-
ALUSrc [1:0] 决定ALU的第二参来自GRF的RD2(对应值0)还是立即数(对应值1)还是shamt(对应值2)
-
MNDType [3:0] 计算方式 0代表有符号乘法 1代表无符号乘法 2代表有符号除法 3代表无符号除法
-
MNDUsage [2:0] 决定是否使用HI或LO作为E级计算结果的输出,0用ALU,1用HI,2用LO
-
MNDWE [1:0] 写使能信号,0不写,1写HI,2写LO
-
MNDStart 决定是否开始乘除运算
-
MemWrite 决定DM是否允许写入
-
MemType [3:0] 向寄存器存储数据的类型(具体说明参考DM)
-
RegDataSrc [1:0] 决定GRF写入数据来源,00代表ALU输出,01代表DM输出,10代表PCNxt
-
RegDest [1:0]决定GRF写入地址来源是Inst[20:16](rt, 对应值00,I型)还是Inst[15:11](rd, 对应值01,R型),还是31即$ra(对应值10,jal)
-
RegWrite 决定GRF是否允许写入
-
Tnew [3:0] 对应A-T法的Tnew,为D端值,每经过一级寄存器Tnew = max{Tnew-1, 0}
-
TuseE [3:0] 对应A-T法的Tuse,对应A1参数的Tuse
-
TuseM [3:0] 对应A-T法的Tuse,对应A2参数的Tuse
CMP 比较模块 (D)
功能要求
- 实现单周期CPU中ALU为分支指令实现的比较功能
- 纯组合逻辑
输入端口
- RD1 [31:0] 第一参
- RD2 [31:0] 第二参
- CmpType [3:0] 比较方法
输出端口
- CmpOut 输出是否可以进行分支
行为描述
CmpType | 比较行为描述 |
---|---|
0 | 输出是否相等,即 RD1 == RD2 |
1 | 暂不定义 |
2 | 暂不定义 |
… | … |
EXT 扩展器 (D)
功能要求
- 将输入的立即数进行扩展
输入端口
- Imm [15:0] Inst指令中的立即数部分
- ExtType 扩展方式
输出端口
- ExtOut [31:0] 扩展后的结果
行为描述
ExtType | 扩展行为描述 |
---|---|
0 | 进行零扩展,即返回 , Imm} |
1 | 进行符号扩展,即返回 , Imm} |
GRF 通用寄存器组 (D)
功能要求
- 用具有写使能的寄存器实现,寄存器总数为 32 个,应具有同步复位功能。
- 0 号寄存器的值始终保持为 0。其他寄存器初始值(复位后)均为 0,无需专门设置。
- 支持GPR内部转发,即读取地址等于写入地址时,直接输出写入数据。
输入端口
- clk 时钟信号
- Reset同步复位信号
- WE 写使能信号
- A1 [4:0] 第一读取地址
- A2 [4:0] 第二读取地址
- A3 [4:0] 写入地址
- WD [31:0] 写入数据输入
输出端口
- RD1 [31:0] 第一读取输出 输出A1地址的寄存器中的数据
- RD2 [31:0] 第二读取输出 输出A2地址的寄存器中的数据
功能说明
- 任何时刻,RD1和RD2都输出A1和A2对应地址的寄存器中的数据
- Reset同步复位,将所有寄存器复位为0
- 0号寄存器永远保持为0
- 当WE为真时,将WD的数据在clk上沿写入A3对应地址的寄存器
- 读取地址等于写入地址时,直接输出写入数据。
ALU 算术逻辑单元 (E)
功能要求
- 提供 32 位加、减、或运算及大小比较等功能。
- 加减法按无符号处理(不考虑溢出)。
输入端口
- SrcA [31:0] 第一个参数输入
- SrcB [31:0] 第二个参数输入
- ALUCtrl [3:0] ALU操作类型控制码
输出端口
- ALUResult [31:0] 计算结果
控制信号说明
ALUCtrl | 计算行为描述 |
---|---|
0 | 按位或 |
1 | 按位与 |
2 | 不判断溢出的加法 |
3 | 不判断溢出的减法 |
4 | 立即数置高位 |
MND 乘除法模块(E)
功能要求
- 自 Start 信号有效后的第 1 个时钟上升沿开始,乘除法部件开始执行运算,同时将 Busy 置位为 1。
- 在运算结果保存到 HI 寄存器和 LO 寄存器后,Busy 位清除为 0。
- 当 Busy 信号或 Start 信号为 1 时,
mult, multu, div, divu, mfhi, mflo, mthi, mtlo
等乘除法相关的指令均被阻塞在 D 流水级。 - 数据写入 HI 寄存器或 LO 寄存器,均只需 1 个时钟周期。
输入端口
- clk 时钟信号
- Reset 同步复位信号
- start 开始计算信号
- MNDWE [1:0] 写使能信号,0不写,1写HI,2写LO
- MNDType [3:0] 计算方式 0代表有符号乘法 1代表无符号乘法 2代表有符号除法 3代表无符号除法
- RD1 [31:0] 计算第一参
- RD2 [31:0] 计算第二参
输出端口
- busy 运算时指示信号
- HI [31:0] HI寄存器值
- LO [31:0] LO寄存器值
HH 冒险处理模块
功能要求
- 判断是否进行阻塞/转发操作并执行
- 组合逻辑
输入端口
重要说明
下面提到的A1和A2均指第一个读取值和第二个读取值!!!比如对R型指令就是rs和rt,对于I型指令可能是rs和rt(如beq)也可能是rs和无(如ori)!!!
而A3指的是写入值!!不一定是rd!!!对R型指令是rd,对有写入内容的I型指令是rt!!!
F/D流水线寄存器
- FD_TuseE [3:0] 此时F/D流水线寄存器中的TuseE
- FD_TuseM [3:0] 此时F/D流水线寄存器中的TuseM
- FD_A1 [4:0] F/D流水线寄存器中的A1地址
- FD_A2 [4:0] F/D流水线寄存器中的A2地址
- FD_RD1 [31:0] F/D流水线寄存器中的RD1
- FD_RD2 [31:0] F/D流水线寄存器中的RD2
- FD_MNDUsage [2:0] F/D流水线寄存器中的MNDUsage
D/E流水线寄存器
- DE_TuseE [3:0] 此时D/E流水线寄存器中的TuseE
- DE_TuseM [3:0] 此时D/E流水线寄存器中的TuseM
- DE_A1 [4:0] D/E流水线寄存器中的A1地址
- DE_A2 [4:0] D/E流水线寄存器中的A2地址
- DE_A3 [4:0] D/E流水线寄存器中的A3地址
- DE_RD1 [31:0] D/E流水线寄存器中的RD1
- DE_RD2 [31:0] D/E流水线寄存器中的RD2
- DE_Tnew [3:0] 此时D/E流水线寄存器中的Tnew
- DE_RegWrite 此时D/E流水线寄存器中的RegWrite
MND模块
- MND_Busy MND的Busy信号,代表正在运算乘除法
E/M流水线寄存器
- EM_TuseM [3:0] 此时E/M流水线寄存器中的TuseM
- EM_A2 [4:0] E/M流水线寄存器中的A2地址
- EM_A3 [4:0] E/M流水线寄存器中的A3地址
- EM_RD2 [31:0] E/M流水线寄存器中的RD2
- EM_Tnew [3:0] 此时E/M流水线寄存器中的Tnew
- EM_RegWrite E/M流水线寄存器中代表是否可写的指令
- EM_RegData [31:0] E/M流水线寄存器中GRF写入数据
M/W流水线寄存器
- MW_Tnew [3:0] 此时E/M流水线寄存器中的Tnew
- MW_RegWrite M/W流水线寄存器中代表是否可写的指令,同时指示当前是否有写入操作
- MW_A3 [4:0] M/W流水线寄存器中的A3地址
- MW_RD2 [31:0]
- MW_RegData [31:0] M/W流水线寄存器中GRF写入数据
输出端口
- Froze 向IFU,F/D和D/E流水线寄存器传递的冻结信号,代表输出默认值而非寄存器值
D级
- RD1ForwardD [31:0] 寄存器读取的第一参结果
- RD2ForwardD [31:0] 寄存器读取的第二参结果
E级
- RD1ForwardE [31:0] 寄存器读取的第一参结果
- RD2ForwardE [31:0] 寄存器读取的第二参结果
M级
- WDForward [31:0] DM写入的数据结果