【CO】P4课下——单周期CPU(Verilog实现)
本文为P4课下单周期CPU设计思路与具体细节,仅供参考
P4课下–单周期CPU的Verilog实现
总体设计方案
指令集合
课下提交要求实现的指令包括add(u),sub(u),ori,lui,beq,lw,sw,nop,j,jal,jr。在此基础上添加了移位指令sll。分类如下:
R型指令 | I型指令 | J型指令 |
---|---|---|
add(u),sub(u),sll(nop),jr | ori,lui,beq,lw,sw | j,jal |
模块设计
从模块划分上看,P4与P3基本相同,包括PC,NPC,IM,GRF,ALU,DM,EXT,CTRL等模块。这些模块分别作为项目下的一个.v文件。为了将这些模块能更清晰地进行实例化,添加了datapath.v和mips.v作为上层模块来进行实例化。
整体架构
具体模块设计
PC
端口说明
端口 | 功能 |
---|---|
next_pc | 接收来自NPC模块指示的下一条指令地址,并在时钟上升沿更新 |
clk | 时钟信号 |
reset | 异步复位信号,为1’b1时将当前指令设置为起始位置 |
pc | 输出当前pc |
实现
时序逻辑实现对寄存器exe_pc
的更新,assign对pc输出端口进行连接。
NPC
端口说明
端口 | 功能 |
---|---|
pc | 接收当前指向的指令地址 |
pc_add_four | 输出当前pc+4的值,便于jal指令跳转时将pc+4的值存入$ra中 |
IMM | 接收当前J型指令的26位转移地址 |
beq_offset | 接收beq指令中16位偏移量 |
RA | 接收读出的jr指令转移至$31指向的地址(仅设计了jr $ra) |
zero | 接收ALU返回的rs rt寄存器值是否相等的信号,用于beq |
NPCOp | 接收CTRL返回的下一个PC所指向的位置类型控制信号 |
npc | 向PC传递经NPC模块得到的下一条执行指令的位置 |
其中,控制信号NPCOp的功能具体如下:
信号 | 功能 |
---|---|
2’b00 | npc为pc+4 |
2’b01 | npc为j/jal指向的指令 |
2’b10 | npc为$ra指向的指令 |
2’b11 | npc为pc+4/beq分支的指令,取决于zero的输入 |
实现
利用多目运算符和assign语句对端口进行赋值即可。
注意:
- 使用某中间变量(不准确的称呼)时,首先应声明为wire型,同时要注意端口位宽的匹配。
- 此处pc接收的应未减0x00003000,并且输出npc前减去0x00003000以保持与pc的配合。
IM
利用initial块中的$readmemh("code.txt",rom);
语句即可实现从当前目录下读取文件并放入指定位置。
注意:
在声明寄存器阵列作为IM时,应当从低到高,如:reg [31:0] rom [0:4096];
。这样做可以保证ISE和VCS仿真结果是相同的。
GRF
端口说明
端口 | 功能 |
---|---|
A1 | 读取的第一个寄存器编号(rs) |
A2 | 读取的第二个寄存器编号(rt) |
WR | 写入数据的目标寄存器(rd/rt/$ra) |
WD | 写入目标寄存器的数据 |
RD1 | A1中的数据 |
RD2 | A2中的数据 |
clk | 时钟信号 |
reset | 异步复位信号 |
WE | 写入信号 |
pc | 当前pc,以便于输出信息 |
实现
在时序逻辑中实现对内容的更新。注意不要对零寄存器进行更新,也不要输出信息。在判断条件中应当注意这一点。
ALU
端口说明
端口 | 功能 |
---|---|
A | 进行运算的第一个数 |
B | 进行运算的第二个数 |
sll | 进行sll操作需要左移的位数 |
ALUOp | 控制信号,选择ALU进行的操作 |
zero | 输出两个寄存器中的值是否相等,为beq指令时的NPC提供是否分支的依据 |
ans | 输出运算结果 |
其中,控制信号ALUOp的功能具体如下:
信号 | 功能 |
---|---|
3’b000 | A+B |
3’b001 | A-B |
3’b010 | A |
3’b011 | B<<sll |
3’b100 | B<<16 |
实现电路
同样使用assign和多目运算符配合实现。Verilog中默认为无符号数,所以这里不需要考虑符号带来的问题。Verilog中4’b0认为是无符号数,但是0认为是有符号数。
DM
和IM几乎相同。P3中我的DMOp非常别扭,DM出的问题(只接了ld没有接str)也导致了P3课上挂掉:sob:,P4中对DMOp对应功能进行了更改,使其不再那么别扭。
信号 | 功能 |
---|---|
1’b0 | 读出数据 |
1’b1 | 写入数据 |
EXT
选择0扩展或符号扩展,一条简单的assign语句。
信号 | 功能 |
---|---|
1’b0 | 0扩展 |
1’b1 | 符号扩展 |
CTRL
端口说明
端口 | 功能 |
---|---|
OpCode | 获取指令的操作码 |
FuncCode | 获取指令的功能码 |
NPCOp | 模块控制信号,控制NPC操作 |
ALUOp | 模块控制信号,控制ALU操作 |
WE | 模块控制信号,控制GRF读写 |
DMOp | 模块控制信号,控制DM读写 |
WRSlt | 数据通路控制信号,控制写入的寄存器来源 |
WDSlt | 数据通路控制信号,控制写入的数据来源 |
ALUSlt | 数据通路控制信号,控制进入ALU的第二个数来源 |
EXTOp | 模块控制信号,控制EXT扩展方式 |
实现
和P3中的设计思路一样,分为与逻辑(识别)和或逻辑(赋值)。这里要比Logisim中简单的多。
数据通路分析
和P3的设计一样,下面再次给出:
WRSlt对应的选择器选择进行写入操作的寄存器编号:
WRSlt | 来源 |
---|---|
2’b00 | rd |
2’b01 | rt |
2’b10 | $ra |
WDSlt对应的选择器选择写入寄存器的数据:
WDSlt | 来源 |
---|---|
2’b00 | pc+4 |
2’b01 | ALU计算得到的ans |
2’b10 | DM读取的数据 |
ALUSlt对应的选择器选择进入ALU模块的第二个数:
ALUSlt | 来源 |
---|---|
1’b0 | RD2 |
1’b1 | EXT扩展后的结果 |
综上,给出每条指令对应的各个控制信号:
指令 | NPCOp | WE | ALUOp | DMOp | WRSlt | WDSlt | ALUSlt | EXTOp |
---|---|---|---|---|---|---|---|---|
add(u) | 2’b00 | 1’b1 | 3’b000 | 1’b0 | 2’b00 | 2’b01 | 1’b0 | 1’b0 |
sub(u) | 2’b00 | 1’b1 | 3’b001 | 1’b0 | 2’b00 | 2’b01 | 1’b0 | 1’b0 |
sll | 2’b00 | 1’b1 | 3’b011 | 1’b0 | 2’b00 | 2’b01 | 1’b0 | 1’b0 |
ori | 2’b00 | 1’b1 | 3’b010 | 1’b0 | 2’b01 | 2’b01 | 1’b1 | 1’b0 |
lui | 2’b00 | 1’b1 | 3’b100 | 1’b0 | 2’b01 | 2’b01 | 1’b1 | 1’b0 |
beq | 2’b11 | 1’b0 | 3’b001 | 1’b0 | 2’b00 | 2’b00 | 1’b0 | 1’b0 |
lw | 2’b00 | 1’b1 | 3’b000 | 1’b0 | 2’b01 | 2’b10 | 1’b1 | 1’b1 |
sw | 2’b00 | 1’b0 | 3’b000 | 1’b1 | 2’b00 | 2’b00 | 1’b1 | 1’b1 |
j | 2’b01 | 1’b0 | 3’b000 | 1’b0 | 2’b10 | 2’b00 | 1’b0 | 1’b0 |
jal | 2’b01 | 1’b1 | 3’b000 | 1’b0 | 2’b00 | 2’b00 | 1’b0 | 1’b0 |
jr | 2’b10 | 1’b0 | 3’b000 | 1’b0 | 2’b10 | 2’b00 | 1’b0 | 1’b0 |
datapath和mips
实例化以上模块即可,注意位宽的匹配和实例化过程中变量的对应。
思考题
阅读下面给出的 DM 的输入示例中(示例 DM 容量为 4KB,即 32bit × 1024字),根据你的理解回答,这个 addr 信号又是从哪里来的?地址信号 addr 位数为什么是 [11:2] 而不是 [9:0] ?
答:addr信号来自于ALU模块,位数是[11:2]因为DM中按字存储,每个字有4个字节,所以左移两位才对应着DM中储存的字的位置。
思考上述两种控制器设计的译码方式,给出代码示例,并尝试对比各方式的优劣。
答:
指令对应的控制信号如何取值:1
2
3
4
5
6
7
8
9always@(*) begin
case(OpCode)
6’b101011: begin
DMOp=1’b1
...
end
...
endcase
end控制信号每种取值所对应的指令:
1
2
3
4
5assign add=(OpCode==6'b000000&&FuncCode==6'b100000)?1:0;
assign NPCOp=(add|sub|sll|ori|lui|lw|sw)?2'b00:
(j|jal)?2'b01:
(jr)?2'b10:
2'b11;记录指令对应的控制信号如何取值便于观察每条指令的运作,与其他指令可以独立开来,添加指令时更方便。
记录控制信号每种取值所对应的指令便于对控制信号进行观察,检查控制信号是否按照预期的设计输出。在相应的部件中,复位信号的设计都是同步复位,这与 P3 中的设计要求不同。请对比同步复位与异步复位这两种方式的 reset 信号与 clk 信号优先级的关系。
答:同步复位中reset信号优先级低于clk信号;
异步复位中reset信号优先级高于clk信号。C 语言是一种弱类型程序设计语言。C 语言中不对计算结果溢出进行处理,这意味着 C 语言要求程序员必须很清楚计算结果是否会导致溢出。因此,如果仅仅支持 C 语言,MIPS 指令的所有计算指令均可以忽略溢出。 请说明为什么在忽略溢出的前提下,addi 与 addiu 是等价的,add 与 addu 是等价的。提示:阅读《MIPS32® Architecture For Programmers Volume II: The MIPS32® Instruction Set》中相关指令的 Operation 部分。
答:add和addi都会对溢出进行检查,如果溢出,则抛出
SignalException(IntegerOverflow)
异常,而addu和addiu不进行溢出检查,所以在忽略溢出的前提下,两种指令是等价的。
测试方案
此次自动化测试相对比较简单,原因在于ISE可以通过命令行操作编译项目后直接获得控制台输出,同时在模块设计过程中已实现了向控制台的输出。我们只需要捕获这些输出并且与编译后的C程序输出进行对拍即可。