BUAA-CO-P5-verilog五层流水线CPU(简化指令)
co-系列:
verilog五层流水线CPU(简化指令)
模块设计
整体视图:
1. GRF(寄存器堆)
| 端口名 | 输入\输出 | 位宽 | 功能 |
|---|---|---|---|
| clk | Input | 1 | 时钟信号 |
| reset | Input | 1 | 复位信号 |
| WE | Input | 1 | 使能信号 |
| PC | Input | 31:0 | pc |
| A1 | Input | 4:0 | 输入寄存器地址端口1 |
| A2 | Input | 4:0 | 输入寄存器地址端口2 |
| A3 | Input | 4:0 | 输入寄存器地址端口3,写寄存器地址 |
| EXTRA | Input | 4:0 | 输入寄存器地址端口EX,读寄存器地址 |
| WD | Input | 31:0 | 数据输入端口,输入一个32位数据,存入编码为A3的寄存器中 |
| RD1 | Output | 31:0 | 输出编码为A1中输入的寄存器中的值 |
| RD2 | Output | 31:0 | 输出编码为A2中输入的寄存器中的值 |
| RDEXTRA | Output | 31:0 | 输出编码为EXTRA中输入的寄存器中的值 |
初始化!!
1 | module GRF ( |
2. DM
| 端口名 | 输入\输出 | 位宽 | 功能 |
|---|---|---|---|
| clk | Input | 1 | 时钟信号 |
| reset | Input | 1 | 复位信号 |
| PC | Input | 31:0 | pc |
| memwrite | Input | 1 | 内存写使能 |
| memaddr | Input | 31:0 | 内存地址 |
| memdata | Input | 31:0 | 写入的内存数据 |
| outdata | Output | 31:0 | 输出的内存数据 |
使用寄存器数组实现。初始化!!
1 | module DM ( |
DM中一个字是一个地址,按字节为14位(16K),按字为12位地址(32bit*4096),端口应该连接ALU输出端的2~13位。
3. NPC
| 端口名 | 输入\输出 | 位宽 | 功能 |
|---|---|---|---|
| clk | Input | 1 | 时钟信号 |
| reset | Input | 1 | 复位信号 |
| beq_judge | Input | 1 | beq指令pc选择 |
| j_if | Input | 1 | j指令pc选择 |
| jr_if | Input | 1 | jr指令pc选择 |
| jal_if | Input | 1 | jal指令pc选择 |
| imm | Input | 31:0 | 位扩展后的立即数 |
| j_addr | Input | 25:0 | j指令跳转地址 |
| jr_addr | Input | 31:0 | jr指令跳转地址 |
| NPC | Output | 31:0 | 输出的真实pc值 |
| NPC_4 | Output | 31:0 | pc+4(用于jal指令写入$ra) |
外部引入两个对应位置的寄存器通过ALU的减法结果zero,通过与beq_if(判断指令是否为beq)连接,实现beq(if$[rs]==$[rt]): PC=PC+offest+4。
1 | module NPC ( |
4.IM
通过ROM元件存储和读入指令代码,PC在内部为0x00000000起始,而外部为0x00003000,需要减去一个偏移量
ROM中一个字是一个地址,按字为12位地址端口应该连接ALU输出端的2~13位。
| 端口名 | 输入\输出 | 位宽 | 功能 |
|---|---|---|---|
| pc | Input | 31:0 | pc |
| inster | Output | 31:0 | 指令机器码 |
1 | module IM( |
使用$readmemh(“code.txt”, mem)指令读取文件,初始化im。
5.ALU
| 端口名 | 输入\输出 | 位宽 | 功能 |
|---|---|---|---|
| ALUOp | Input | 3:0 | ALU功能选择 |
| A | Input | 31:0 | 待处理数字1 |
| B | Input | 31:0 | 待处理数字2 |
| result | Output | 31:0 | 计算结果 |
| overflow | Output | 1 | 溢出判断 |
1 |
|
ALUOp:(留一位给剩下的)
| 0000 | 0001 | 0010 | 0011 | 0100 | 0101 | 0110 | 0111 |
|---|---|---|---|---|---|---|---|
| add | sub | mul | div | and | or | sll | srl |
6.EXT
| 端口名 | 输入\输出 | 位宽 | 功能 |
|---|---|---|---|
| imm | Input | 15:0 | 16位立即数 |
| extp | Input | 1 | 位扩展选择功能 |
| extresult | Output | 31:0 | 位扩展计算结果 |
1 | module EXT ( |
| EXTOp: | 00 | 01 | 10 | 11 |
|---|---|---|---|---|
| 功能 | 0扩展 | 符号扩展 | 高位加载 | 空余 |
7.CTRL
| 端口名 | 输入\输出 | 位宽 | 功能 |
|---|---|---|---|
| opcode | Input | 5:0 | 高六位opcode |
| func | Input | 5:0 | 低六位opcode |
| instr | Input | 31:0 | 指令码 |
| regwrite | Output | 1 | reg写使能 |
| regwritedst | Output | 1:0 | 寄存器写选择 |
| alusrc | Output | 1 | alu选择imm入B口 |
| memwrite | Output | 1 | mem写使能 |
| memtoreg | Output | 1 | mem写入reg选择 |
| jr_if | Output | 1 | jr判断 |
| j_if | Output | 1 | j判断 |
| jal_if | Output | 1 | jal判断 |
| beq_if | Output | 1 | beq判断 |
| extop | Output | 1:0 | ext功能选择 |
| aluop | Output | 3:0 | alu功能选择 |
通过输入的指令码各部分,进行操作状态输出。
| 执行指令 | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 信号名 | add | sub | andi | lui | ori | j | jal | jr | beq | sw | lw | nop |
| opcode | 000000 | 000000 | 001100 | 001111 | 001101 | 000010 | 000011 | 000000 | 000100 | 101011 | 100011 | 000000 |
| func | 100000 | 100010 | 001000 | 000000 | ||||||||
| regwrite | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 0 | 0 | 0 | 1 | 0 |
| regwritedst(2) | 01(rd) | 01 | 00 | 00 | 00(rt) | 00 | 10($ra) | 00 | 00 | 00 | 00 | 01 |
| alusrc(imm) | 0 | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 1 | 1 | 0 |
| memwrite | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
| memtoreg | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
| extop(2) | 00 | 00 | 00 | 10 | 00 | 00 | 00 | 00 | 01 | 01 | 01 | 00 |
| beq_if | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
| j_if | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
| jal_if | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
| jr_if | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
| ALU_ctr3 | 0 | 0 | 1 | 0(add$0) | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| ALU_ctr2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| ALU_ctr1 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 1(sub验证) | 0 | 0 | 0 |
1 |
|
各信号注意点:
- add、sub、jr指令需要先判断opcode再判断func。
8.HCTRL
| 端口名 | 输入\输出 | 位宽 | 批注 |
|---|---|---|---|
| IR_F | Input | 31:0 | / |
| IR_D | Input | 31:0 | / |
| RS_D | Input | 4:0 | / |
| RT_D | Input | 4:0 | / |
| RS_E | Input | 4:0 | / |
| RT_E | Input | 4:0 | / |
| WA_E | Input | 4:0 | / |
| WA_M | Input | 4:0 | / |
| WA_W | Input | 4:0 | / |
| memtoreg_E | Input | 1 | / |
| memtoreg_M | Input | 1 | / |
| regwrite_E | Input | 1 | / |
| regwrite_M | Input | 1 | / |
| regwrite_W | Input | 1 | / |
| memwrite_M | Input | 1 | / |
| D_CLEAR | Output | 1 | D流水寄存器清空 |
| F_BLOCK | Output | 1 | F流水寄存器保持 |
| PC_BLOCK | Output | 1 | PC保持 |
| rd1_sel | Output | 1:0 | R1_D_IN选择 |
| rd2_sel | Output | 1:0 | R2_D_IN选择 |
| frd1_sel | Output | 1:0 | aluA选择 |
| frd1_sel | Output | 1:0 | aluB选择 |
| memdata_sel | Output | 1:0 | mem写入WD选择 |
1 |
|
9.流水线REG
F:
1 |
|
D:
1 | module D_REG ( |
E:
1 |
|
M:
1 | module M_REG ( |
注意一下特殊的清空或者阻塞信号即可。
10.mips(顶层模块)
注意流水寄存器中为pc+4,使用时需要减去4,而jal的pc+8需要加4。
添加了31号和pc+8的选择。
x_D_IN表示一个名为x的信号,IN表示输入,D表示输入的流水线寄存器
x_E 表示一个名为x的信号,处于E时期,可能为D_OUT或者E_IN。
M_REG表示其前一阶段为M阶段。
转发表:
| 供给者序号\需求者 | D级grf的输出 | E级alu的输入 | M级mem的内存写入数据WD |
|---|---|---|---|
| 0 | AO_E_OUT | r1_D_OUT/r2_D_OUT | WD_E_OUT |
| 1 | rd1/rd2 | AO_E_OUT | realgrfdata(真实值) |
| 2 | / | grfwritedata(包括memout和AO_M_OUT) | / |
mux暂时不用,改为assign选择。
MUX选择信号表:
| 序号 \ 需求者/控制信号 | aluB/alusrc | AO_E_IN/jal_if | WA_E_IN/regdst_E | grfwritedata/memtoreg_W |
|---|---|---|---|---|
| 0 | 转发后的rd2 | aluresult | rt | AO_M_OUT(alu结果) |
| 1 | imm | pc+8(jal使用0) | rd | MD_M_OUT(内存读取) |
| 2 | / | / | 31 | / |
1 | module mips ( |
AT法详表:
| 指令 | T_use | E_T_new | M_T_new | W_T_new |
|---|---|---|---|---|
| ADD/SUB/ORI | 1 | 1 | 0 | 0 |
| LW | 1 | 2 | 1 | 0 |
| SW | 1(特殊) | 0 | 0 | 0 |
| LUI | INF | 1 | 0 | 0 |
| BEQ | 0 | 0 | 0 | 0 |
| JR | 0 | 0 | 0 | 0 |
| J | INF | 0 | 0 | 0 |
| JAL | INF | 1(用aluresult传入pc+8) | 0 | 0 |
| NOP | INF | 0 | 0 | 0 |
对于sw:
如果sw处于E级,lw处于M级,会产生wd无法正确转发的错误。
其读取rt的值写入内存时所需的rt是有可能在后方的先前指令修改,用grf魔改端口实现实时输出真实值,通过对E级memwrite判断是否需要写入内存,来选择真实值,避免对sw进行错误的判断。
测试方案
通过mars编写汇编程序,编写相关测试代码,将mars生成的机器码通过文件导入到verilog,通过向输出中间数据,和mars进行对拍,以此验证各代码是否运行正确。
思考题
1.
例如:
1 | add $t1,$t1,$t2 |
提前分支判断会导致其阻塞一周期,但是如果在M级分支判断可以通过转发解决。
2.
延迟槽会导致跳转指令下一条指令也会被执行,但是如果使用pc+4会导致跳转回来时候重复执行,所以需要pc+8。
3.
这样可以保证时序的准确性,如果直接接到元器件上,可能会产生一些毛刺数据造成潜在的转发错误。
4.
因为要实现在D级跳转,如果此时W级有写入数据,那么需要在判断时转发数据保证正确性,而此时转发的接收者和发送者都是grf,因此可以内部转发。
实现:
1 | assign RD1 = (A1 == 5'b0) ? 32'b0 : ((A1 == A3) && WE) ? WD : registers[A1]; |
值得注意的是需要对$0特判。
5.
| 供给者序号\需求者 | D级grf的输出 | E级alu的输入 | M级mem的内存写入数据WD |
|---|---|---|---|
| 0 | AO_E_OUT | r1_D_OUT/r2_D_OUT | WD_E_OUT |
| 1 | rd1/rd2 | AO_E_OUT | realgrfdata(真实值) |
| 2 | / | grfwritedata(包括memout和AO_M_OUT) | / |
6.
可能的修改:
alu内部计算逻辑、alu计算数选择、grf的读取写入寄存器选择、冒险控制元件判断逻辑、PC增加逻辑、imm预处理、
7.
通过对opcode和func的分层判断,以此来判断命令类型译码,采用命令控制的信号驱动,对每一条指令产生的所有信号进行判断,这种好处是如果产生的新的命令,可以方便地加入,缺点是如果同时加入大量指令,可能有大部分无效添加的低电平信号,而且不便于管理单个信号的命令控制。
附录:
题目测试代码翻译:
1 | 0x0000000000000000: 34 1C 00 00 ori $gp, $zero, 0 |
1 | @00003000: $28 <= 00000000 |
所涉及的指令的手册解释












