P3 CPU设计文档
设计草稿
设计要求
处理器为 32 位单周期处理器,应支持的指令集为:
add, sub, ori, lw, sw, beq, lui, nop
,其中:nop
为空指令,机器码0x00000000
,不进行任何有效行为(修改寄存器等)。add, sub
按无符号加减法处理(不考虑溢出)。
需要采用模块化和层次化设计。顶层有效的驱动信号要求包括且仅包括异步复位信号 reset(clk 请使用内置时钟模块)。
可以将微体系结构分为两个相互作用的部分:数据通路(datapath)和控制(control)。
先根据应实现的指令对功能部件进行设计与搭建,然后再对各个功能部件进行连接,形成完整的数据通路。
数据通路搭建完毕后,再构造控制器,以根据读取的指令产生相应的控制信号。
数据通路
1 数据通路模块定义
先从包含状态元件的硬件开始,然后在这些存储组件之间增加组合逻辑基于当前状态计算新的状态。
1.1 程序计数器(PC)
32 位寄存器。输出 PC 指向当前指令,输入 PC’表示下一条指令的地址。
PC 用寄存器实现,应具有异步复位功能,复位值为起始地址(0x00003000)。
地址范围:0x00003000 ~ 0x00006FFF。
1.2 NPC
- 计算下一个 PC 值。
1.3 指令存储器(IM)
- 将 32 位指令地址输入 A,并在这个地址中读取 32 位数据(指令)并传送到读数据输出 RD 上。
- 用 ROM 实现,容量为 4096 × 32bit。
- ROM 内部的起始地址是从 0 开始的,即 ROM 的 0 位置存储的是 PC 为 0x00003000 的指令,每条指令是一个 32bit 常数。
- 经过以上分析,不难发现 ROM 实际地址宽度仅需 12 位。又由于 PC 值一定为 4 的倍数,即 PC 的低 2 位始终为 0,故只需将 PC[13:2]同 IM 联系起来。
1.4 寄存器堆(GRF)
信号名 | 方向 | 描述 |
---|---|---|
A1[4:0] | I | 指定 32 个寄存器中的一个作为源操作数 |
A2[4:0] | I | 指定 32 个寄存器中的一个作为源操作数 |
RD1[31:0] | O | 从指定的 32 位寄存器中读取的值 |
RD2[31:0] | O | 从指定的 32 位寄存器中读取的值 |
A3[4:0] | I | 需要写入的寄存器的地址 |
WD3[31:0] | I | 需要写入的数据 |
WE3 | I | 写使能信号 |
CLK | I | 时钟信号 |
注意 0 号寄存器值始终为 0。
1.5 数据存储器(DM)
若写使能 WE=1,则在时钟上升沿将数据 WD 写入地址 A;若写使能 WE=0,则从地址 A 将数据读到 RD。
使用 RAM 实现,容量为 3072 × 32bit,应具有异步复位功能,复位值为 0x00000000。
起始地址:0x00000000。
地址范围:0x00000000 ~ 0x00002FFF(一个地址 1 个字节,1 个字节 8 位)。
RAM 应使用双端口模式,即设置 RAM 的 Data Interface 属性为 Separate load and store ports。
由于只实现
lw,sw
,故 A 的值为 4 的倍数,即 A 的 32 位二进制低 2 位始终为 0,且 3072 个数据地址 12 位即可,所以只要 A[13:2]。lb,sb
怎么处理?数据地址位宽为 12,共有 4096 个地址,如何处理地址超出 3072 的情况?
1.6 算数逻辑单元(ALU)
提供 32 位加、减、或运算、大小比较以及移位功能。
加减法按无符号处理(不考虑溢出)。
端口定义:
信号名 方向 描述 SrcA[31:0] I 第一个操作数 SrcB[31:0] I 第二个操作数 ALUOp[2:0] I 运算选择 Result[31:0] O 运算结果 Zero O SrcA == SrcB ? 1 : 0 Shift[4:0] I 移位量 功能定义:
功能名 ALUOp 功能描述 ADD 3’b000 SrcA + SrcB SUB 3’b001 SrcA - SrcB OR 3’b010 SrcA | SrcB SHIFT 3’b011 SrcB << Shift
1.7 扩展单元(EXT)
- 对 16 位立即数进行符号扩展或无符号扩展。
2 构建数据通路
通过一次增加一个状态元件,逐步构成单周期的数据路径。
2.1 从指令存储器中读取指令
PC 将32 位指令地址传入 A,并从指令存储器中取出相应地址处的32 位指令输出到 RD,标记为Instr。
由于处理器的动作取决于取出的具体指令,故先针对lw
指令构造数据路径连接,然后再泛化处理该数据路径来处理其他指令。
2.2 从寄存器堆中读源操作数
lw
指令的格式为
lw rt, offset(base)
,操作为
Addr ← GPR[base] + sign_ext(offset)
GPR[rt] ← memory[Addr]
故读包含基地址的源寄存器,该寄存器由 Instr[25:21]的 rs 字段指定。
Instr[25:21]连接到 GRF 的 A1 口,GRF 读出源寄存器的基地址值到 RD1 口。
2.3 符号扩展立即数
偏移量 offset 由 Instr[15:0]字段指定,且需要符号扩展,记为 SignImm。
2.4 计算存储器地址
基地址值还要再和偏移量 offset 相加,故 Instr[15:0]经符号扩展后与基地址相加。
基地址值连接到 ALU 的 SrcA,SignImm 连接到 ALU 的 SrcB。
Addr 由 ALU 计算结果得到,ALUResult 连接到 DM 的 A 口,以获取相应内存中的值。
2.5 向寄存器堆写入数据
将内存读取到的值写入 GPR。
DM 的 RD 口连接到 GRF 的 WD3 口,同时写入的寄存器由 Instr[20:16]字段指定,故 Instr[20:16]连接到 A3 口,同时 GRF 的写使能信号 RegWrite 连接到 WE3 口。
2.6 确定 PC 的下一个指令地址
操作执行完毕后,需要由 NPC
计算出下一个地址值。lw
指令中,NPC = PC + 4。
就此完成了lw
的数据通路。新增其它指令时仿照上述流程构建相应的数据通路。
控制
控制器的设计,从最基本的层面来说,是一个译码的过程,将每一条机器指令中包含的信息,转化为给 CPU 各部分的控制信号(RegDst, ALUSrc 等等)。我们把解码逻辑分解为和逻辑和或逻辑两部分:和逻辑的功能是识别,将输入的机器码识别为相应的指令;或逻辑的功能是生成,根据输入的指令的不同,产生不同的控制信号。
而在设计这两套逻辑的过程中,和逻辑是非常自然的。而或逻辑则需要我们建立从指令到控制信号的映射,为了避免错误的产生,我们希望使用真值表来完成相应的设计任务,并希望通过真值表可以简化相应的逻辑。一个典型的真值表如下:
func | 10 0000 | 10 0010 | n/a | ||||
op | 00 0000 | 00 0000 | 00 1101 | 10 0011 | 10 1011 | 00 0100 | 00 1111 |
add | sub | ori | lw | sw | beq | lui | |
RegDst | 1 | 1 | 0 | 0 | X | X | 0 |
ALUSrc | 0 | 0 | 1 | 1 | 1 | 0 | 1 |
MemtoReg | 0 | 0 | 0 | 1 | X | X | 0 |
RegWrite | 1 | 1 | 1 | 1 | 0 | 0 | 1 |
MemWrite | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
nPC_sel | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
ExtOp | X | X | 0 | 1 | 1 | 1 | 0 |
ALUctr | ADD | SUB | OR | ADD | ADD | SUB | SHIFT |
ALUctr[0] | 0 | 1 | 0 | 0 | 0 | 1 | 1 |
ALUctr[1] | 0 | 0 | 1 | 0 | 0 | 0 | 1 |
ALUctr[2] | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
ShiftOp | X | X | X | X | X | X | 1 |
控制单元端口定义如下:
端口名 | 方向 | 描述 |
---|---|---|
Op[5:0] | I | 指令的操作类型 |
Funct[5:0] | I | R 型指令通过 funct 字段来决定 ALU 的操作 |
RegDst | O | GRF 的写入寄存器地址选择。 |
ALUSrc | O | ALU 的 SrcB 选择。0 为 GRF 的 RD2,1 为扩展立即数。 |
MemtoReg | O | GRF 的写入数据选择。0 为 ALU 运算结果,1 为 DM 读取的数据。 |
RegWrite | O | GRF 的写使能信号。 |
MemWrite | O | DM 的写使能信号。 |
nPC_sel | O | NPC 的值选择。0 为 PC+4,1 且 ALU 的 Zero 信号生效时为 PC+4+sign_extend(immediate || 02)。 |
ExtOp | O | 立即数扩展方式选择。0 为无符号扩展,1 为有符号扩展。 |
ShiftOp | O | 移位量选择。1 为 LUI 移位(左移 16 位)。 |
ALUctr[2:0] | O | ALU 运算类型选择。 |
总体预览
指令集
add
add rd, rs, rt
sub
sub rd, rs, rt
ori
ori rt, rs, immediate
操作:GPR[rt] ← GPR[rs] OR zero_extend(immediate)
lw
lw rt, offset(base)
sw
sw rt, offset(base)
beq
beq rs, rt ,offset
操作:
if (GPR[rs] == GPR[rt]) PC ← PC + 4 + sign_extend(offset || 02)
else PC ← PC + 4
lui
lui rt, immediate
操作:GPR [rt] ← immediate || 016
nop
测试方案
通过 asm 文件随机生成,以保证测试数据的任意性,然后与 MARS 或同学的结果对拍,以确定正确性。(随机生成程序与对拍程序均借助讨论区工具)。
同时,适当修改随机数,产生一些极端情况的数据以对电路进行压力测试。
1 | lw $17,692($0) |
计算类指令功能测试
寄存器数据方面,可以考虑以下情况:
- 0 及附近的数:−2,−1,0,1,2
- 32 位数边界附近的数: −2147483648,−2147483647,2147483646,2147483647
- 32 位数范围内的一些随机数:−1000786109,1919156834,…
无符号立即数方面,可以考虑以下情况:
- 0 及附近的数:0,1,2,3
- 16 位无符号数边界附近的数:65533,65534,65535
- 16 位无符号数范围内的一些随机数:25779,42528,…
符号立即数 (P3 不涉及) 方面,可以考虑以下情况:
- 0 及附近的数:−2,−1,0,1,2
- 16 位符号数边界附近的数:−32768,−32767,32766,32767
- 16 位符号数范围内的一些随机数:−5329,25299,…
特别的,可注意测试目标寄存器是 $0 的情况。
存取类指令功能测试
- offset 方面,可以考虑以下情况:
- offset 是正数
- offset 是零
- offset 是负数
$base
寄存器方面,可以考虑以下情况:$base
寄存器中的值是正数$base
寄存器中的值是零$base
寄存器中的值是负数
- 特别的,对于
sw
指令,建议存入的 word 中,每个 byte 都不是零。 - 特别的,对于
lw
指令,可注意测试目标寄存器是$0
的情况。
跳转类指令功能测试
- 对于非比较相关的部分,可以考虑以下情况:
- 跳转,且目标在此跳转指令之前
- 跳转,且目标是此跳转指令
- 跳转,且目标在此跳转指令之后
- 不跳转,且目标在此跳转指令之前
- 不跳转,且目标是此跳转指令
- 不跳转,且目标在此跳转指令之后
- 对于比较相关的部分,本质上依旧是构造寄存器数据,处理类似 “计算类指令功能测试”。
思考题
1.上面我们介绍了通过 FSM 理解单周期 CPU 的基本方法。请大家指出单周期 CPU 所用到的模块中,哪些发挥状态存储功能,哪些发挥状态转移功能。
状态存储功能:PC,GRF,DM
状态转移功能:IM,Control Unit,EXT,ALU,NPC
2.现在我们的模块中 IM 使用 ROM, DM 使用 RAM, GRF 使用 Register,这种做法合理吗? 请给出分析,若有改进意见也请一并给出。
部分合理。IM 为 ROM 存在过度简化,因为在大多数实际存储器中,指令存储器必须可写,从而使操作系统可以载入一个新的程序到存储器中。DM 为 RAM 可读可写,满足 DM 对读写的要求;GRF 使用寄存器,满足寄存器堆较高读写速度的要求。
3.在上述提示的模块之外,你是否在实际实现时设计了其他的模块?如果是的话,请给出介绍和设计的思路。
是。NPC 用于计算下一个 PC 值。根据目前实现的指令,当指令为 beq 时,NPC = PC + 4 + sign_extend(offset || 02),其余情况 NPC = PC + 4;故使用一个 DMX 并通过信号控制 NPC 的取值。
4.事实上,实现 nop
空指令,我们并不需要将它加入控制信号真值表,为什么?
空指令机器码为 0x00000000,不需要执行任何操作。
5.阅读 Pre 的 “MIPS 指令集及汇编语言” 一节中给出的测试样例,评价其强度(可从各个指令的覆盖情况,单一指令各种行为的覆盖情况等方面分析),并指出具体的不足之处。
sw
指令仅测试了$base$
为 0 的情况。
beq
指令仅考虑了跳转/不跳转,且目标在跳转指令之后的情况。