思考题
1 提前分支判断
提前分支判断,就代表要提前判断对应寄存器值是否相等(或大于小于等),也就是说要提前用到这些值,也就是说,让 从 减少到了 ,这可能让本来可以通过转发解决的数据冒险必须通过阻塞一周期来解决,反而降低了效率。示例汇编代码如下:
1 | li $t0, 0 |
这里我们用汇编进行了一个 次的循环,每次循环结束后add都会对t0寄存器自增1,随后由bne指令判断是否结束循环。如果不采用提前分支判断,这里add计算出结果后刚好可以转发给来到E级进行分支判断的bne指令;但如果采用提前分支判断,就必须要使用阻塞才能保证bne使用了正确的数据。
2 跳转链接指令写回 PC + 8
对于jal等跳转并链接的指令,写回$ra寄存器的地址值对应着 “返回后继续执行的指令地址”,在单周期cpu中是PC + 4,但是由于延迟槽的存在,PC + 4 这条指令,即延迟槽内的指令应是在跳转指令的下一步执行,因此不应该在 “返回后” 被执行。举例来说就是,在使用函数时,jal这类指令一般用于进入函数,此时延迟槽内指令应代表进入函数前(因为实际上这条指令在跳转进入函数之前执行)的最后一条指令,显然不应该在函数返回后再被执行。因此应该写回的地址是 PC + 8,即延迟槽的下条指令。
3 转发数据的来源
如果转发来源于功能部件,目标是功能部件,那么这样将大幅延长关键路径。准确来说,会让原模块的关键路径长度增加转发源模块的关键路径长度,相当于拖慢了CPU一倍的时钟频率。
此外,如果转发来自功能部件,目标功能部件,这样仅能实现 “本周期运算的数据被本周期前面模块使用” 的数据冒险,如果数据在下一周期才被使用(但下一周期数据尚未写回)则无法实现转发。
4 GPR内部转发
使用GPR内部转发,本质上相当于解决W级的结果数据要被D、E级使用的数据冒险问题。如果使用外部转发,即其他部件的转发方式,应该是从W级的 “目标流水寄存器” 的输出端口连接到使用数据的E级(显然W级结果不可能和M级发生数据冒险)输入部分。但是W级没有 “目标流水寄存器” ,准确来说对应的目标寄存器是GRF模块,所以按照原来的方式,我们应该从GRF将数据连接到E级的MUX。但是GRF有固定的输出端口,如果这么做需要额外添加一个 “W级输出”,再将对应输出链接到数据冒险处理模块,麻烦且影响模块局部性。
而使用GPR内部转发则不需要再进行额外链接,只需要在GRF内部进行判断即可,方便且简洁。
5 转发通路
首先,供给者是W级的情况已经在4中讨论过了,使用GPR内部转发解决,这里不再讨论。
供给者是M级时,需求者可能来自于D或E级,分别对应分支指令和计算类指令。对于分支指令,指令从GRF中取数据时恰好M级对应数据写入,通过GPR内部转发即可解决。对于计算类指令,需要从M/W流水线寄存器转发到E级输入。
供给者是E级时,需求者可能来自于D或E级,对应分支指令和计算类指令。对于分支指令,需要从E/M流水线寄存器转发到D级。对于计算类指令,需要从E/M流水线寄存器转发到E级。
6 新指令扩展位置
计算指令
对于类似add/addi这样,由两个寄存器的值或一个寄存器值和一个立即数进行某种运算,再将运算结果存回到某个寄存器内的计算类指令,只需要做如下两处修改:
- 修改译码器:在对应R型指令部分或计算立即数部分增加分支,修改ALUCtrl的值(如有需要,还要修改ExtType的值)即可。
- 修改ALU:对于新增的ALUCtrl,在ALU中增加分支,进行相关计算
访存指令
对于类似lw/sw这样对内存进行访问的指令,进行如下修改:
- 修改译码器:对应读内存指令或者写内存指令部分修改对应的Memtype和ALUCtrl
- 修改ALU:对应不同的计算方式新增ALU计算方法的分支
- 修改DM:对应不同的Memtype修改访存方式
分支指令
对于类似beq这样新增的分支指令,进行如下修改:
- 修改译码器:对于分支指令一类的指令部分修改CMPType
- 修改比较器:对应新增CMPType新增比较方式
7 译码器架构
采用了指令驱动型的方式,在两重case语句下(第二重对应OPCode==6’00_0000的情况)对应不同类型的指令进行分类,总共分如下几类:
- 跳转到寄存器 jr
- R型计算 add sub
- I型计算 lui ori
- 分支 beq
- 读内存 lw
- 写内存 sw
- J型指令 jal
可以看到虽然按类别分,但是实际上每类只有一两个,这是因为指令集较小的缘故。当指令集增大时,除了乘除法指令需要再单开一类,其他指令均可以按类型分类到上述七个类别中。
对于每一类指令,大多数控制信号都是相同的,只需要单独修改某一两个控制信号即可。下面给出R型计算指令的示例:(有四个信号在OPCode == 6’b00_0000处已经统一赋值过了)
1 | else begin |
可以看到,省去了同类指令相似端口的赋值。
注:这里没有采用宏,因为个人认为,宏定义每一种指令判断还需要到前文(当代码较长时,翻上去很麻烦)寻找具体的OPCode和func值,如果在宏定义中有一处bug是不太方便查找到的。相反的,如果直接把判断值写在对应判断位置,用注释进行标识,同样能达成说明的效果,按指令进行查错时很方便。
优势:便于指令添加,不容易出现某条指令少添加了一个控制信号的情况(出现了也很容易发现,数每个分支的赋值数即可),且相对直观,对于每个指令对应什么信号都很一目了然。
劣势:代码冗长,不能直观体现出每个指令有决定性的信号在哪里,为了在always块中进行赋值加入了reg,综合后可能会影响效率等。
数据构造
转发测试
- W-E转发,隔一条指令分别运行同寄存器lw和add
- M-E转发,连续运行同寄存器递增的add
- W-M转发,add后加sw操作同寄存器
- GPR内部转发,隔两条指令运行的同寄存器add
- M-D转发,隔一条指令分别运行同寄存器add
将上述不同转发情况用生成程序+修改操作数进行多次叠加修改,测试多重转发,随机生成多组连续内容。
阻塞测试
- 连续运行lw和add(同寄存器)
- 在beq前加操作相关寄存器的add sub ori lw
- 在jr前加操作相关寄存器的add sub ori lw
综合测试
- 随机生成大量连续add、sub、ori、lui、lw、sw指令(地址按固定寄存器值+随机数指定)
- 随机生成beq在其中,跳跃到+3条指令位置
- 随机生成jal跳跃到+3条指令地址
- 为寄存器赋值后jr到+3条指令地址
测试方法
采用了TobyShi的修改版MARS生成对应汇编码,采用如下Python程序进行自动评测:
1 | import re |
通过简单的正则表达式提取输出内容并进行相关判断。
指令集综述
简要说明
包含指令集如下:
1 | add sub ori lw sw beq lui jal jr nop |
R I J 型指令说明
R型
包括
1 | add sub and or nor sll srl 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 | 第一参 | 第二参 | 目的参 | 移位量 | 指示操作类型 |
I型
包括
1 | addi andi ori xori lw sw beq bne lui |
位置 (位数) | 31~26 (6) | 25~21 (5) | 20~16(5) | 15~0 (16) |
---|---|---|---|---|
类型 | 操作码 | 寄存器地址 | 寄存器地址 | 立即数 |
名称 | OP | rs | rt | imm |
解释 | 指示操作类型 | 第一参 | 目的参 | 立即数 |
J型
包括
1 | j jal |
位置 (位数) | 31~26 (6) | 25~0 (26) |
---|---|---|
类型 | 操作码 | 指令地址 |
名称 | OP | address |
解释 | 指示操作类型 | 目标跳转位置 |
(无)符号加 add
按照要求,依照add的机器码处理,但加法依照无符号数加法完成。取两个寄存器中的数,无符号相加(不考虑溢出)后存入目标寄存器。为R型指令。
位置 (位数) | 31~26 (6) | 25~21 (5) | 20~16 (5) | 15~11 (5) | 10~6 (5) | 5~0 (6) |
---|---|---|---|---|---|---|
名称 | OP | rs | rt | rd | 0 | func |
解释 | 000000 | 第一加数地址 | 第二加数地址 | 结果存储地址 | 00000 | 100000 |
(无)符号减 sub
按照要求,依照sub的机器码处理,但减法依照无符号数加法完成。无符号相减(不考虑溢出)后存入目标寄存器。为R型指令。
位置 (位数) | 31~26 (6) | 25~21 (5) | 20~16 (5) | 15~11 (5) | 10~6 (5) | 5~0 (6) |
---|---|---|---|---|---|---|
名称 | OP | rs | rt | rd | 0 | func |
解释 | 000000 | 被减数地址 | 减数地址 | 结果存储地址 | 00000 | 100010 |
或立即数 ori
按照要求,即为将某寄存器中数或一个16位立即数,将得到的数存到目标寄存器中。为I型指令。
位置 (位数) | 31~26 (6) | 25~21 (5) | 20~16(5) | 15~0 (16) |
---|---|---|---|---|
名称 | OP | rs | rt | imm |
解释 | 001101 | 被或数地址 | 结果存储地址 | 立即数 |
加载字 lw
取内存中的一字长的内容存入目标寄存器。为I型指令。
1 | addr <- GPR[base] + sign_ext(offset) |
位置 (位数) | 31~26 (6) | 25~21 (5) | 20~16(5) | 15~0 (16) |
---|---|---|---|---|
名称 | OP | base | rt | offset |
解释 | 100011 | 内存地址所在寄存器 | 结果存储地址 | 内存地址偏移量(立即数) |
存储字 sw
将目标寄存器中的内容存入内存中一字长的位置。为I型指令。
1 | addr <- GPR[base] + sign_ext(offset) |
位置 (位数) | 31~26 (6) | 25~21 (5) | 20~16(5) | 15~0 (16) |
---|---|---|---|---|
名称 | OP | base | rt | offset |
解释 | 101011 | 内存地址所在寄存器 | 目标寄存器地址 | 内存地址偏移量(立即数) |
相等时转移 beq
当两个目标寄存器中存储的值相等时转移到目标指令处。为I型指令。
1 | if (GPR[rs] == GPR[rt]) |
位置 (位数) | 31~26 (6) | 25~21 (5) | 20~16(5) | 15~0 (16) |
---|---|---|---|---|
名称 | OP | rs | rt | offset |
解释 | 000100 | 第一目标寄存器 | 第二目标寄存器 | 转移目标指令地址 |
立即数加载至高位 lui
将立即数赋值到目标寄存器的高16位。为I型指令。
位置 (位数) | 31~26 (6) | 25~21 (5) | 20~16(5) | 15~0 (16) |
---|---|---|---|---|
名称 | OP | 0 | rt | imm |
解释 | 001111 | 00000 | 目标寄存器 | 立即数 |
跳转并链接 jal
将当前的PC+4存储在GPR[31]($ra)中,按照给定的地址进行跳转。
1 | PC <- {PC[31:28], instr_index, {2{0}}} |
位置 (位数) | 31~26 (6) | 25~0 (26) |
---|---|---|
名称 | OP | instr_index |
解释 | 000011 | 目标跳转位置 |
跳转至寄存器 jr
跳转到目标寄存器存储的地址指定的指令处
1 | PC <- GPR[rs] |
位置 (位数) | 31~26 (6) | 25~21 (5) | 20~16 (5) | 15~11 (5) | 10~6 (5) | 5~0 (6) |
---|---|---|---|---|---|---|
名称 | OP | rs | 0 | 0 | 0 | func |
解释 | 000000 | 目标寄存器地址 | 00000 | 00000 | 00000 | 001000 |
空指令 nop
机器码为0x00000000,不进行任何有效行为。
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)
-
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 | 立即数置高位 |
DM 数据存储器(M)
功能要求
- 使用 RAM 实现,容量为 32bit × 3072字,应具有同步复位功能,复位值为 0x00000000。
- 起始地址:0x00000000。
输入端口
- clk 时钟信号
- Reset 同步复位信号
- WE 写使能信号
- MemType [3:0] 向内存存储数据的类型
- Addr [31:0] 写入/读取地址
- WD [31:0] 写入数据
输出端口
- RD [31:0] 读取结果数据
功能说明
- 任何时刻,在RD输出Addr对应内存地址的内存数据
- 当WE为真时,将WD的数据在clk上沿写入Addr对应内存地址
- MemType类型对应存储类型格式说明如下:
MemType | 对应指令 | 说明 |
---|---|---|
4‘d0 | lw, sw | 以字为单位进行存取 |
… | … | … |
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
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
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写入数据
输出端口
F级
- IFUFroze IFU的冻结信号,代表禁止更新指令
F/D流水线寄存器
- FDFroze 向F/D流水线寄存器传递的冻结信号,代表输出nop而非寄存器值
D级
- RD1ForwardD [31:0] 寄存器读取的第一参结果
- RD2ForwardD [31:0] 寄存器读取的第二参结果
E级
- RD1ForwardE [31:0] 寄存器读取的第一参结果
- RD2ForwardE [31:0] 寄存器读取的第二参结果
M级
- WDForward [31:0] DM写入的数据结果
各控制信号说明图表
op | 00 0000 | 00 0000 | 00 1101 | 10 0011 | 10 1011 | 00 0100 | 00 1111 | 00 0011 | 00 0000 | default |
---|---|---|---|---|---|---|---|---|---|---|
func | 10 0000 | 10 0010 | - | - | - | - | - | - | 00 1000 | |
add | sub | ori | lw | sw | beq | lui | jal | jr | ||
CmpType | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
ExtType | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
PCSrc | 00 | 00 | 00 | 00 | 00 | 01 | 00 | 10 | 11 | 0 |
ALUCtrl | 2 | 3 | 5 | 2 | 2 | 3 | 4 | - | - | 0 |
(CalcMode) | add | sub | ori | add | add | sub | lui | - | - | |
ALUSrc | 0 | 0 | 1 | 1 | 1 | 0 | 1 | - | - | 0 |
MemWrite | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
RegDataSrc | 00 | 00 | 00 | 01 | - | - | 00 | 10 | - | 0 |
RegDest | 01 | 01 | 00 | 00 | - | - | 00 | 10 | - | 0 |
MWWrite | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 0 |
DEWrite | 0 | 0 | 0 | 0 | - | - | 0 | 1 | - | 0 |
EMWrite | 1 | 1 | 1 | 0 | - | - | 1 | 0 | - | 0 |
TuseE | 1 rs | 1 rs | 1 rs | 1 base | 1 base(rs) | 0 rs | - | - | 0 rs | 15 |
TuseM | 1 rt | 1 rt | - | - | 2 rt | 0 rt | - | - | - | 15 |
TnewD | 2 | 2 | 2 | 3 | - | - | 2 | 2 | - | 0 |
TnewE | 1 | 1 | 1 | 2 | - | - | 1 | 1 | - | |
TnewM | 0 | 0 | 0 | 1 | - | - | 0 | 0 | - | |
TnewW | 0 | 0 | 0 | 0 | - | - | 0 | 0 | - |