单周期CPU设计方案
概述
本次课下主要依靠P3的logisim进行翻译,不过我也对P3设计的一系列不合理之处进行了调整。我首先根据logisim电路图搭建各个模块,随后在mips.v文件中把各个模块合理地连接起来,最后添加controller模块产生控制信号。
指令说明
本文实现的CPU在P3基础上添加了2条指令,即jal和jr。
R型指令
addsubjr
实际上实现的指令相当于
addu和subu,因为题目明确指出不考虑溢出
I型指令
orilwswbeqlui
J型指令
jal
空指令
nop
功能模块设计
IFU
将PC和IM合二为一。IM使用$readmehn语句读取code.txt文件里的指令。
| 信号名 | 方向 | 描述 | 位宽 |
|---|---|---|---|
| clk | input | 时钟信号 | 1 |
| reset | input | 复位信号 | 1 |
| next_pc | input | 下一个指令地址 | 32 |
| PC | output | 当前指令地址寄存器 | 32 |
| instr | output | 当前指令 | 32 |
NPC
用组合逻辑计算出下一个指令的地址,通常是变为PC+4。根据控制信号不同会执行beq/jal/jr三种跳转。
| 信号名 | 方向 | 描述 | 位宽 |
|---|---|---|---|
| PC | input | 当前指令地址 | 32 |
| offset | input | 分支偏移量 | 16 |
| instr_26 | input | 指令中26位目标地址 | 26 |
| ra_data | input | 从GRF返回的地址数据 | 32 |
| zero | input | 分支条件标志 | 1 |
| NPCOp | input | 下一地址选择操作码 | 3 |
| next_pc | output | 计算后的下一指令地址 | 32 |
| pc_add_4 | output | 当前地址加4的值 | 32 |
GRF
寄存器堆,同样是翻译即可。注意要display否则无法评测。
| 信号名 | 方向 | 描述 | 位宽 |
|---|---|---|---|
| clk | input | 时钟信号 | 1 |
| reset | input | 复位信号 | 1 |
| RegWrite | input | 寄存器写入使能信号 | 1 |
| RA1 | input | 读寄存器1地址 | 5 |
| RA2 | input | 读寄存器2地址 | 5 |
| WA | input | 写寄存器地址 | 5 |
| WD | input | 写入数据 | 32 |
| RD1 | output | 读寄存器1数据 | 32 |
| RD2 | output | 读寄存器2数据 | 32 |
| PC | input | 当前程序计数器地址 | 32 |
DM
数据存储器,朴实无华的读写功能。同样需要display。
| 信号名 | 方向 | 描述 | 位宽 |
|---|---|---|---|
| PC | input | 当前程序计数器地址 | 32 |
| clk | input | 时钟信号 | 1 |
| reset | input | 复位信号 | 1 |
| MemWrite | input | 内存写入使能信号 | 1 |
| WA | input | 写地址 | 32 |
| WD | input | 写入数据 | 32 |
| RD | output | 读取数据 | 32 |
ALU
本文需要的指令只需要四个运算:加、减、或。相等的判断用减法实现。
| 信号名 | 方向 | 描述 | 位宽 |
|---|---|---|---|
| ALUOp | input | ALU操作选择信号 | 3 |
| A | input | ALU第一个操作数 | 32 |
| B | input | ALU第二个操作数 | 32 |
| zero | output | 结果是否为零标志 | 1 |
| ALU_res | output | ALU运算结果 | 32 |
MUX
这个模块的设计和每个人自身的电路设计高度相关,我认为先建立mips.v顶层文件再构造更好。具体内容在下一部分讲解。
| 信号名 | 方向 | 描述 | 位宽 |
|---|---|---|---|
| rt | input | 源寄存器1地址 | 5 |
| rd | input | 源寄存器2地址 | 5 |
| WAOp | input | 写地址选择操作码 | 2 |
| MemRD | input | 从内存读取的数据 | 32 |
| offset | input | 偏移量 | 16 |
| ALU_res | input | ALU运算结果 | 32 |
| pc_add_4 | input | 当前PC加4的值 | 32 |
| WDOp | input | 写数据选择操作码 | 2 |
| RD2 | input | 第二个读寄存器的数据 | 32 |
| Ext_imm16 | input | 扩展的16位立即数 | 32 |
| BOp | input | B选择信号 | 1 |
| B | output | 选择的第二个操作数 | 32 |
| WD | output | 写入的数据 | 32 |
| A3 | output | 写入的目标寄存器地址 | 5 |
组建数据通路
verilog组建数据通路的难度远大于logisim,因为不够直观,很容易漏接某些线。我认为比较不容易出错的设计方式如下:
- 将除MUX外的所有模块放入
mips.v文件中并接上所有的I/O端口。为防出错,所有的接口都用同名wire连接,可以先连好再定义这些wire型变量。 - 观察不同模块的接口,根据自己整理的表格/P3的电路图确定哪些端口之间是一对一连接的。使用assign语句把连接到对应端口的
wire变量链接在一起。 - 对于需要用到多路选择器的端口,我们建立一个统一的
MUX模块,这个模块负责处理所有需要进行信号选择的端口。我们将所有可能的输入信号和一会用Controller.v产生的选择信号全部作为MUX的输入,所有输出信号作为输出,内部使用条件判断语句为各个输出信号赋上对应的值。随后将其添加到mips.v中。
控制信号
建立一个新的Controller.v模块,因为CPU操作只跟指令有关,所以输入只需要opcode和funct。对于其内部结构,我喜欢先为每一条指令单独定义一个wire型变量,再以这些变量为条件去编写控制信号的生成逻辑。
| 信号名 | 方向 | 描述 | 位宽 |
|---|---|---|---|
| opcode | input | 指令操作码 | 6 |
| funct | input | 功能码,用于区分操作类型 | 6 |
| NPCOp | output | 下一地址选择操作码 | 3 |
| RegWrite | output | 寄存器写使能信号 | 1 |
| ALUOp | output | ALU操作码 | 3 |
| MemWrite | output | 内存写使能信号 | 1 |
| ExtOp | output | 立即数扩展操作选择信号 | 1 |
| WAOp | output | 写地址选择操作码 | 2 |
| WDOp | output | 写数据选择操作码 | 2 |
| BOp | output | 第二操作数选择信号 | 1 |
思考题
- 阅读下面给出的 DM 的输入示例中(示例 DM 容量为 4KB,即 32bit × 1024字),根据你的理解回答,这个
addr信号又是从哪里来的?地址信号addr位数为什么是 [11:2] 而不是 [9:0] ?
答:addr是ALU通过将寄存器值与偏移量offset相加得到的地址信号,之所以取[11:2]是因为DM的存储是以32bit为单位,而地址信号是以8bit为单位,故略去后两位使地址信号与DM内的存储地址一致。 - 思考上述两种控制器设计的译码方式,给出代码示例,并尝试对比各方式的优劣
答:指令对应的控制信号如何取值:
控制信号每种取值时对应的指令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27case (opcode) 6'b000000: begin // R型指令,funct决定操作 case (funct) 6'b100000: begin // add RegWrite = 1; ALUOp = 3'b000; WAOp = 2'b01; NPCOp = 3'b0; MemWrite = 0; ExtOp = 0; WDOp = 2'b0; BOp = 0; end 6'b100010: begin // sub RegWrite = 1; ALUOp = 3'b001; WAOp = 2'b01; NPCOp = 3'b0; MemWrite = 0; ExtOp = 0; WDOp = 2'b0; BOp = 0; end //略 endcase //略 endcase1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19assign NPCOp = beq ? 3'b001: jal ? 3'b010: jr ? 3'b011: 3'b0; assign RegWrite = add || sub || ori || lw || lui || jal; assign ALUOp = add || lw || sw ? 3'b000: sub || beq ? 3'b001: ori ? 3'b010: 3'b111; assign MemWrite = sw; assign ExtOp = ori; assign WAOp = add || sub || jr ? 2'b01: jal ? 2'b10: 2'b0; assign WDOp = lw ? 2'b01: lui? 2'b10: jal? 2'b11: 2'b0; assign BOp = ori || lw || sw; - 在相应的部件中,复位信号的设计都是同步复位,这与 P3 中的设计要求不同。请对比同步复位与异步复位这两种方式的
reset信号与clk信号优先级的关系。 答:同步复位中clk信号优先级更高,只有clk上升沿到来时reset信号才有影响,而异步复位二者地位相当,二者都可以随时生效。 - C 语言是一种弱类型程序设计语言。C 语言中不对计算结果溢出进行处理,这意味着 C 语言要求程序员必须很清楚计算结果是否会导致溢出。因此,如果仅仅支持 C 语言,MIPS 指令的所有计算指令均可以忽略溢出。 请说明为什么在忽略溢出的前提下,
addi与addiu是等价的,add与addu是等价的。 答:以add与addu为例,从RTL描述中可以看出,addu就是在add的基础上增加了如果运算结果最高位和次高位不相等就抛出报错,所以如果忽略这个溢出,二者就是等价的。addi和addiu同理。