Verilog语言设计单周期CPU
设计草稿及模块安排
整体架构
mips
PC
IM
GRF
ALU
EXT
DM
Controlled
MUX
模块安排
mips
管脚名 | 位数 | in/out | 功能解释 |
---|---|---|---|
clk | 1 | in | 系统时钟信号 |
reset | 1 | in | 复位信号 |
mips模块是整个系统的主模块,其主要承担整体架构中各分模块的线路连接任务。
PC
管脚名 | 位数 | in/out | 功能解释 |
---|---|---|---|
clk | 1 | in | 时钟信号 |
reset | 1 | in | 复位信号,置1时pc = 0x00003000 |
next_pc | 32 | in | 下一条指令的地址 |
pc | 32 | out | 当前指令的地址 |
pc是程序执行指令的地址提供者,传入其的next_pc受跳转指令信号的影响。
IM
管脚名 | 位数 | in/out | 功能解释 |
---|---|---|---|
pc | 32 | in | 当前指令的地址 |
instr | 32 | out | 根据pc取出的指令,注意0x00003000的地址偏移量 |
IM是指令存储器,读取code.txt文件中的多条32位指令,根据传入的pc取指令。
GRF
管脚名 | 位数 | in/out | 功能解释 |
---|---|---|---|
clk | 1 | in | 系统时钟信号 |
reset | 1 | in | 复位信号,置1时,32个寄存器存储值归0 |
RegWrite | 1 | in | 写寄存器使能信号 |
A1 | 5 | in | 需要读取的存储器1的地址 |
A2 | 5 | in | 需要读取的存储器2的地址 |
A3 | 5 | in | 需要写入的存储器的地址 |
WD | 32 | in | 需要写入寄存器的数据 |
pc | 32 | in | 当前指令的地址 |
RD1 | 32 | out | 读取的寄存器1中存储的32位数据 |
RD2 | 32 | out | 读取的寄存器2中存储的32位数据 |
GRF是寄存器文件,具有读写功能。
ALU
管脚名 | 位数 | in/out | 功能解释 |
---|---|---|---|
A | 32 | in | 操作数1 |
B | 32 | in | 操作数2 |
ALUCtrl | 3 | in | ALU运算操作选择信号:0–和;1–或;2–加;3–减 |
ALUResult | 32 | out | 运算结果 |
ifequal | 1 | out | 操作数1和操作数2相等时置1 |
ALU为算数运算单元,ALUCtrl指令控制执行何种运算。
EXT
管脚名 | 位数 | in/out | 功能解释 |
---|---|---|---|
imm16 | 16 | in | 带扩展的16位立即数 |
extop | 1 | in | 扩展操作选择信号:0–0扩展;1–符号扩展 |
imm32 | 32 | out | 扩展完毕后的32位立即数 |
EXT为数据位扩展模块,主要服务于I指令。
DM
管脚名 | 位数 | in/out | 功能解释 |
---|---|---|---|
clk | 1 | in | 系统时钟信号 |
reset | 1 | in | 复位信号,置1时,数据存储器中数据全部归0 |
MemRead | 1 | in | 读数据存储器使能信号 |
MemWrite | 1 | in | 写数据存储器使能信号 |
pc | 32 | in | 当前指令的地址 |
MemAddress | 32 | in | 读出/写入数据的地址 |
dataIn | 32 | in | 写入数据存储器的数据 |
dataOut | 32 | out | 从数据存储器读出的数据 |
DM是数据存储器,具有读写功能。
Controller
管脚名 | 位数 | in/out | 功能解释 |
---|---|---|---|
op | 6 | in | 32位指令的26-31位 |
funct | 6 | in | 32位指令的0-5位 |
RegDst | 2 | out | 写寄存器地址的选择信号:0–rt;1–rd;2–$ra |
ALUSrc | 1 | out | ALU操作数2选择信号:0–RD2;1–imm32 |
WD_sel | 1 | out | 写入寄存器的数据选择信号:0–ALUResult;1–加载至高位的imm16;2–数据存储器的读出;3–pc+4 |
ALUCtrl | 3 | out | ALU运算操作选择信号:0–和;1–或;2–加;3–减 |
RegWrite | 1 | out | 写寄存器使能信号 |
MemRead | 1 | out | 读数据存储器使能信号 |
MemWrite | 1 | out | 写数据存储器使能信号 |
extop | 1 | out | 扩展操作选择信号:0–0扩展;1–符号扩展 |
branch_beq | 1 | out | beq指令识别信号 |
branch_j_jal | 1 | out | j,jal指令识别信号 |
branch_jr | 1 | out | jr指令识别信号 |
Controller是控制(译码)器,根据 op 和 funct 识别是MIPS指令集中的何指令,并依此生成各模块和通路的控制信号。
MUX
管脚名 | 位数 | in/out | 功能解释 |
---|---|---|---|
rt | 5 | in | 32位指令的16-20位 |
rd | 5 | in | 32位指令的11-15位 |
RD2 | 32 | in | 寄存器2中存储的数据 |
imm32 | 32 | in | 32位立即数 |
imm16 | 16 | in | 16位立即数 |
ALUResult | 32 | in | ALU运算结果 |
dataOut | 32 | in | 数据存储器读出的数据 |
pc | 32 | in | 当前指令的地址 |
RegDst | 2 | in | 写寄存器地址(A3_grf)的选择信号:0–rt;1–rd;2–$ra |
ALUSrc | 1 | in | ALU操作数2(B_alu)选择信号:0–RD2;1–imm32 |
WD_sel | 1 | in | 写入寄存器的数据(WD_grf)选择信号:0–ALUResult;1–加载至高位的imm16;2–数据存储器的读出;3–pc+4 |
pc4 | 32 | in | pc+4 |
pc_beq | 32 | in | beq指令将会跳转到的指令地址 |
pc_j_jal | 32 | in | j,jal指令将会跳转到的指令地址 |
ifequal | 1 | in | 来自ALU的判等信号 |
branch_beq | 1 | in | beq指令识别信号 |
branch_j_jal | 1 | in | j,jal指令识别信号 |
branch_jr | 1 | in | jr指令识别信号 |
A3_grf | 5 | out | 经选择后的写寄存器地址 |
B_alu | 32 | out | 经选择后的ALU操作数2 |
WD_grf | 32 | out | 经选择后的存储器写入数据 |
next_pc | 32 | out | 根据跳转信号选择后的下一条指令地址 |
MUX是集成的选择模块,根据Controller等模块传入的控制信号和选择数据来选择各模块的某些输入信号。
指令及控制信号
指令名称 | op | funct | RegDst | ALUSrc | WD_sel | ALUCtrl | RegWrite | MemRead | MemWrite | extop | branch_beq | branch_j_jal | branch_jr |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
add | 000000 | 100000 | 1 | 0 | 0 | 2 | 1 | 0 | 0 | 1 | 0 | 0 | 0 |
sub | 000000 | 100010 | 1 | 0 | 0 | 3 | 1 | 0 | 0 | 1 | 0 | 0 | 0 |
jr | 000000 | 001000 | 0 | 0 | 0 | 2 | 0 | 0 | 0 | 1 | 0 | 0 | 1 |
ori | 001101 | |0 | 1 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | |
lw | 100011 | |0 | 1 | 2 | 2 | 1 | 1 | 0 | 1 | 0 | 0 | 0 | |
sw | 101011 | |0 | 1 | 0 | 2 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | |
beq | 000100 | |0 | 0 | 0 | 3 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | |
lui | 001111 | |0 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | |
jal | 000011 | |2 | 0 | 3 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 |
设计思路
由于单个模块的控制已经在各个模块的安排中充分介绍,此思路主要概述如何在顶层模块mips中将单个模块组织起来。
首先应当明确,Controller 主要起到译码作用,MUX 根据Controller输出的译码结果选择控制指令。
而需要被选择的端口在当前支持的指令下有以下4个
- GRF的写入地址:A3,由RegDst控制
- GRF的写入数据:WD,由WD_sel控制
- ALU的操作数2:B,由ALUSrc控制
- 下一条指令地址:next_pc,由branch_beq ifequal,branch_j_jal,branch_jr联合控制
故MUX的输出只为GRF,ALU,PC提供输入。
而其他的通路值得注意的有
- GRF的A1连接rs,A2连接rt
- ALU的A连接GRF的RD1
- DM的MemAddress连接ALU的ALUResult
- DM的dataIn连接GRF的RD2
其余的控制指令和数据传输可根据名称对应互联。
测试方案
只要使 CPU 的 IM 读入十六进制汇编程序(存储在code.txt中),运行之后查看其存储器和寄存器中的数据,和 Mars 运行结果比较,就可以判定程序的对错。
###测试程序
-
ori指令
ori $0, $0, 1 ori $t1, $t1, 1 ori $t1, $t1, 0 ori $t2, $t2, 65535 ori $t3, $t3, 65534 ori $t4, $t4, 25779
-
lui指令
lui $t1, 1 lui $t2, 65535 lui $t2, 65533 lui $t3, 42538
-
sw指令
ori $t1, $t1, 1 lui $t2, 65535 sw $t1, 0($0) sw $t2, 4($0) ori $t3, $t3, 28381 sw $t3, 0($0) ori $t4, $t4, 4 sw $t3, 120($t4)
-
lw指令
ori $t1, $t1, 1 lui $t2, 65535 sw $t1, 0($0) sw $t2, 4($0) ori $t3, $t3, 28381 ori $t4, $t4, 4 sw $t3, 120($t4) ori $s0, $s0, 8 lw $s1, 0($0) lw $s2, 0($t4) lw $s3, 116($s0)
-
add指令
ori $s1, $s1, 1 lui $s2, 65535 add $t0, $s1, $s2 add $t0, $t0, $s2 lui $t1, 65535 ori $t1, $t1, 65535 add $s3, $s1, $t1
-
sub指令
lui $t1, 65535 ori $t1, $t1, 65535 lui $t2, 65535 ori $t2, $t2, 60000 sub $s1, $t1, $t2 ori $t3, 7227 sub $s1, $s1, $t3
-
beq指令
ori $t0, $t0, 1 ori $t3, $t3, 2 tag1: ori $t1, $t1, 63333 add $t0, $t0, $t0 beq $t0, $t3, tag1 ori $s0, $s0, 4 beq $t0, $s0, tag2 ori $s0, $s0, 0 tag2: lui $s0, 4 addi $s0, $t0, $s0 beq $s0, $t0, tag3 ori $s0, $s0, 0 ori $t0, $t0, 0 tag3: nop
-
j指令
ori $t1, $t1, 123 j tag1 ori $t1, $t2, 1 tag1: add $t2, $t1, $0 tag2: add $t2, $t2, 1 j tag2
-
jal指令 jr指令
ori $s0, $s0, 1 ori $s1, $s1, 13828 jal func ori $s0, $s0, 2 ori $s1, $s1, 3 func: add $s0, $s0, $s1 lui $s1, 123 jr $ra
-
综合测试
ori $a0,$0,1999
ori $a1,$a0,111
lui $a2,12345
lui $a3,0xffff
nop
ori $a3,$a3,0xffff
addu $s0,$a0,$a1
addu $s1,$a3,$a3
addu $s2,$a3,$s0
beq $s2,$s3,eee
subu $s0,$a0,$s2
subu $s1,$a3,$a3
eee:
subu $s2,$a3,$a0
subu $s3,$s2,$s1
ori $t0,$0,0x0000
sw $a0,0($t0)
nop
sw $a1,4($t0)
sw $s0,8($t0)
sw $s1,12($t0)
sw $s2,16($t0)
sw $s5,20($t0)
lw $t1,20($t0)
lw $t7,0($t0)
lw $t6,20($t0)
sw $t6,24($t0)
lw $t5,12($t0)
jal end
ori $t0,$t0,1
ori $t1,$t1,1
ori $t2,$t2,2
beq $t0,$t2,eee
lui $t3,1111
jal out
end:
addu $t0,$t0,$t7
jr $ra
out:
addu $t0,$t0,$t3
ori $t2,$t0,0
beq $t0,$t2,qqq
lui $v0,10
qqq:
lui $v0,11
- 机器码
340407cf
3485006f
3c063039
3c07ffff
00000000
34e7ffff
00858021
00e78821
00f09021
12530002
00928023
00e78823
00e49023
02519823
34080000
ad040000
00000000
ad050004
ad100008
ad11000c
ad120010
ad150014
8d090014
8d0f0000
8d0e0014
ad0e0018
8d0d000c
0c000c22
35080001
35290001
354a0002
110affec
3c0b0457
0c000c24
010f4021
03e00008
010b4021
350a0000
110a0001
3c02000a
3c02000b
思考题解答
阅读下面给出的 DM 的输入示例中(示例 DM 容量为 4KB,即 32bit × 1024字),根据你的理解回答,这个 addr 信号又是从哪里来的?地址信号 addr 位数为什么是 [11:2] 而不是 [9:0] ?
答:
如设计思路中所说,DM的Addr来自ALU的ALUResult,因为现有支持的指令仅lw和sw需访问DM存储器,其地址由ALU模块计算地址地址为rs的寄存器存储的值和符号扩展后的32位立即数之和得到。位数为[11:2]是因为DM中数据按字寻址,而计算得到地址是以字节为一个地址单位的,32位系统下1字为4字节,故取地址除以4即为32位数据的存储地址。
思考上述两种控制器设计的译码方式( 1.指令对应的控制信号如何取值;2.控制信号每种取值所对应的指令),给出代码示例,并尝试对比各方式的优劣。
答:
本设计方案采用的是第1种译码方式(见Controller模块);现给出第2种译码方式的部分示例(以ALUCtrl为例):
wire add,sub,jr,ori,lw,sw,beq,lui,jal;
assign add = (op==6'b000000 && funct==6'b100000) ? 1 : 0;
assign sub = (op==6'b000000 && funct==6'b100010) ? 1 : 0;
assign jr = (op==6'b000000 && funct==6'b001000) ? 1 : 0;
assign ori = (op==6'b001101) ? 1 : 0;
assign lw = (op==6'b100011) ? 1 : 0;
assign sw = (op==6'b101011) ? 1 : 0;
assign beq = (op==6'b000100) ? 1 : 0;
assign lui = (op==6'b001111) ? 1 : 0;
assign jal = (op==6'b000011) ? 1 : 0;
assign ALUCtrl = (add | lw | sw |jr) ? 2 :
(sub) ? 3 :
(ori) ? 1 : 0;
第1中译码方式在添加指令的时候较为方便,只需添加case中的一种情况,集中地选择控制信号,但指令与控制信号的整体对应关系不易看出,可读性一般。第2种译码方式虽然在修改时需要分散地添加指令识别信号,但写起来十分简洁,可读性较好。
在相应的部件中,复位信号的设计都是同步复位,这与 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 部分 。
答:
根据RTL语言描述:addi、add与addiu、addu的区别在于当出现溢出时,addiu、addu不检查溢出,只取32位结果;addi、add开辟额外一个位的空间来检查溢出,若溢出则报告SignalException(IntegerOverflow)。