0%

计算机组成 P6 CPU 设计文档

思考题

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
2
3
4
5
add sub and or slt sltu lui
addi andi ori
lb lh lw sb sh sw
mult multu div divu mfhi mflo mthi mtlo
beq bne jal jr

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
2
3
4
5
6
7
8
9
10
11
12
if (PCSrc == 2'b00 || (PCSrc == 2'b01 && ALUZero == 0)) begin // 无b/j指令或b指令无效,直接PC+=4
PC <= PC + 4;
end
else if (PCSrc == 2'b01 && ALUZero == 1) begin // b类指令生效
PC <= PC + 4 + {{14{PCImm[15]}}, PCImm, {2{1'b0}}};
end
else if (PCSrc == 2'b10) begin // jal
PC <= {PC[31:28], PCInstrIndex, {2{1'b0}}};
end
else begin // jr
PC <= PCJumpReg;
end

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 进行零扩展,即返回 {{16{1b0}}\{\{16\{1'b0\}\}, Imm}
1 进行符号扩展,即返回 {{16{Imm[15]}}\{\{16\{Imm[15]\}\}, 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写入的数据结果