单周期CPU设计与实现
一. 实验目的
-
理解MIPS常用的指令系统并掌握单周期CPU的工作原理与逻辑功能实现。
-
通过对单周期CPU的运行状况进行观察和分析,进一步加深理解。
二. 实验内容
实验的具体内容与要求。
- 利用 HDL 语言,基于 Xilinx FPGA basys3 实验平台,用 Verilog HDL 语言或 VHDL 语言来编写,实现单周期CPU 的设计,这个单周期 CPU 能够完成 多于16 条MIPS 指令,至少包含以下指令:
- 支持基本的内存操作如 lw,sw 指令
- 支持基本的算术逻辑运算如 add,sub,and,ori,slt,addi 指令
- 支持基本的程序控制如 beq,j 指令
-
掌握各个指令的相关功能并输出仿真结果进行验证,并最后在 FGPA 上实现,将其中的 运算结果在开发板数码管上显示出来。
- 拓展添加其他指令及功能。
要求:
1、PC和寄存器组写状态使用 时钟边缘触发。
2、指令存储器存储单元宽度使用32位,数据存储器存储单元宽度使用8位,(便于添加字节存取)即一个字节的存储单位。深度的选择满足指令要求即可(测试代码的长度)。
3、控制器部分要学会用控制信号真值表方法分析问题并写出逻辑表达式;或者用case语句方法逐个产生各指令控制信号。
4、基本实现必须按统一测试用的汇编程序段进行测试所设计的CPU,增加功能设计可自行编写测试代码,包含所设计的所有指令。
5、必须注意,实验报告中,对每条指令必须有指令执行的波形(截图),且图上必须包含关键信号,同时还要对关键信号以文字说明,这样才能说明该指令的正确性。
三. 实验原理
简述实验原理和方法,必须有数据通路图及相关图。
1. 单时钟周期CPU
单周期 CPU 的特点是每条指令的执行只需要一个时钟周期,一条指令执行完再执行下一条指令。再这一个周期中,完成更新地址,取指,解码,执行,内存操作以及寄存器操作。由于每个时钟上升沿时更新地址,因此要在上升沿到来之前完成所有运算,而这所有的运算除可以利用一个下降沿外,只能通过组合逻辑解决。这给寄存器和存储器RAM的制作带来了些许难度。且因为每个时钟周期的时间长短必须统一,因此在确定时钟周期的时间长度时,要依照最长延迟的指令时间来定,这也限制了它的执行效率。
单周期CPU在每个CLK上升沿时更新PC,并读取新的指令。此指令无论执行时间长短,都必须在下一个上升沿到来之前完成。其时序示意如图Ⅰ。
下图是一个单周期 CPU 的顶层结构实现。主要器件有程序计数器PC、程序存储器、寄存器堆、ALU、数据存储器和控制部件等。所有的控制信号简单地说明如下:
其中,控制单元(Ctrl Unit)定义如下:
(1)regDst:目的地址,为1时,选择rd;为0时,选择rt。
(2)aluSrc:ALU操作数B的选择,为1时,选择扩展的立即数;为0时,选择寄存器数据;
(3)memToReg:为1时,选择存储器数据;为0时,选择ALU 输出的数据;
(4)regWrite:为1时写入寄存器堆,目的寄存器号是由RegDst选出的rt或rd,写入数据是由MemToReg选出的存储器数据或ALU的输出结果;
(5)memRead
(6)memWrite:为1时写入存储器。存储器地址由ALU的输出决定,写入数据为寄存器rt的内容;
(7)branch:为1时,选择转移目标地址;为0时,选择PC +4(图中的 NextPC);
(8)ExtOp:符号扩展。为1时,符号扩展;为0时,0扩展;
(9)aluop:ALU控制码;(具体表示见控制器的设计)
(10)jmp:为1时,选择跳转目标地址;为 0时,选择由Branch选出的地址;
(11)jal:为1时,写入地址选择31号寄存器,写入内容为PC+4
2. MIPS指令集
本次实验共涉及三种类型的MIPS指令,分别为R型、I型和J型,三种类型的MIPS指令格式定义如下:
-
R(register)类型的指令从寄存器堆中读取两个源操作数,计算结果写回寄存器堆;
-
I(immediate)类型的指令使用一个 16位的立即数作为一个源操作数;
-
J(jump)类型的指令使用一个 26位立即数作为跳转的目标地址(target address);
一条指令的执行过程一般有下面的五个阶段,指令的执行过程就是这五个状态的重复过程:
在本次实验中,完成了下列31条指令的功能,包括R型、I型和J型。
助记符 | 指 令 格 式 | 示 例 | 示例含义 | 操作及解释 | |||||
BIT # | 31..26 | 25..21 | 20..16 | 15..11 | 10..6 | 5..0 | |||
R-类型 | op | rs | rt | rd | shamt | func | |||
add | 000000 | rs | rt | rd | 00000 | 100000 | add $1,$2,$3 | $1=$2+S3 | (rd)←(rs)+(rt); rs=$2,rt=$3,rd=$1 |
addu | 000000 | rs | rt | rd | 00000 | 100001 | addu $1,$2,$3 | $1=$2+S3 | (rd)←(rs)+(rt); rs=$2,rt=$3,rd=$1,无符号数 |
sub | 000000 | rs | rt | rd | 00000 | 100010 | sub $1,$2,$3 | $1=$2-S3 | (rd)←(rs)-(rt); rs=$2,rt=$3,rd=$1 |
subu | 000000 | rs | rt | rd | 00000 | 100011 | subu $1,$2,$3 | $1=$2-S3 | (rd)←(rs)-(rt); rs=$2,rt=$3,rd=$1,无符号数 |
and | 000000 | rs | rt | rd | 00000 | 100100 | and $1,$2,$3 | $1=$2&S3 | (rd)←(rs)&(rt); rs=$2,rt=$3,rd=$1 |
or | 000000 | rs | rt | rd | 00000 | 100101 | or $1,$2,$3 | $1=$2|S3 | (rd)←(rs) | (rt); rs=$2,rt=$3,rd=$1 |
xor | 000000 | rs | rt | rd | 00000 | 100110 | xor $1,$2,$3 | $1=$2^S3 | (rd)←(rs)^(rt); rs=$2,rt=$3,rd=$1 |
nor | 000000 | rs | rt | rd | 00000 | 100111 | nor $1,$2,$3 | $1= ~($2 | S3) | (rd)←~((rs) | (rt)); rs=$2,rt=$3,rd=$1 |
slt | 000000 | rs | rt | rd | 00000 | 101010 | slt $1,$2,$3 | if($2<$3) $1=1 else $1=0 | if (rs< rt) rd=1 else rd=0;rs=$2,rt=$3, rd=$1 |
sltu | 000000 | rs | rt | rd | 00000 | 101011 | sltu $1,$2,$3 | if($2<$3) $1=1 else $1=0 | if (rs< rt) rd=1 else rd=0;rs=$2,rt=$3, rd=$1, 无符号数 |
sll | 000000 | 00000 | rt | rd | shamt | 000000 | sll $1,$2,10 | $1=$2<<10 | (rd)←(rt)<<shamt,rt=$2,rd=$1,shamt=10 |
srl | 000000 | 00000 | rt | rd | shamt | 000010 | srl $1,$2,10 | $1=$2>>10 | (rd)←(rt)>>shamt, rt=$2, rd=$1, shamt=10, (逻辑右移) |
sra | 000000 | 00000 | rt | rd | shamt | 000011 | sra $1,$2,10 | $1=$2>>10 | (rd)←(rt)>>shamt, rt=$2, rd=$1, shamt=10, (算术右移,注意符号位保留) |
sllv | 000000 | rs | rt | rd | 00000 | 000100 | sllv $1,$2,$3 | $1=$2<<$3 | (rd)←(rt)<<(rs), rs=$3,rt=$2,rd=$1 |
srlv | 000000 | rs | rt | rd | 00000 | 000110 | srlv $1,$2,$3 | $1=$2>>$3 | (rd)←(rt)>>(rs), rs=$3,rt=$2,rd=$1, (逻辑右移) |
srav | 000000 | rs | rt | rd | 00000 | 000111 | srav $1,$2,$3 | $1=$2>>$3 | (rd)←(rt)>>(rs), rs=$3,rt=$2,rd=$1, (算术右移,注意符号位保留) |
jr | 000000 | rs | 00000 | 00000 | 00000 | 001000 | jr $31 | goto $31 | (PC)←(rs) |
I-类型 | op | rs | rt | immediate | |||||
addi | 001000 | rs | rt | immediate | addi $1,$2,10 | $1=$2+10 | (rt)←(rs)+(sign-extend)immediate,rt=$1,rs=$2 | ||
addiu | 001001 | rs | rt | immediate | addiu $1,$2,10 | $1=$2+10 | (rt)←(rs)+(sign-extend)immediate,rt=$1,rs=$2 | ||
andi | 001100 | rs | rt | immediate | andi $1,$2,10 | $1=$2&10 | (rt)←(rs)&(zero-extend)immediate,rt=$1,rs=$2 | ||
ori | 001101 | rs | rt | immediate | ori $1,$2,10 | $1=$2|10 | (rt)←(rs)|(zero-extend)immediate,rt=$1,rs=$2 | ||
xori | 001110 | rs | rt | immediate | xori $1,$2,10 | $1=$2^10 | (rt)←(rs)^(zero-extend)immediate,rt=$1,rs=$2 | ||
lui | 001111 | 00000 | rt | immediate | lui $1,10 | $1=10*65536 | (rt)←immediate<<16 & 0FFFF0000H,将16位立即数放到目的寄存器高16位,目的寄存器的低16位填0 | ||
lw | 100011 | rs | rt | offset | lw $1,10($2) | $1=Memory[ $2+10] | (rt)←Memory[(rs)+(sign_extend)offset], rt=$1,rs=$2 | ||
sw | 101011 | rs | rt | offset | sw $1,10($2) | Memory[ $2+10] =$1 | Memory[(rs)+(sign_extend)offset]←(rt), rt=$1,rs=$2 | ||
beq | 000100 | rs | rt | offset | beq $1,$2,40 | if($1=$2) | if ((rt)=(rs)) then (PC)←(PC)+4+( (Sign-Extend) offset<<2), rs=$1, rt=$2 | ||
bne | 000101 | rs | rt | offset | bne $1,$2,40 | if($1≠$2) | if ((rt)≠(rs)) then (PC)←(PC)+4+( (Sign-Extend) offset<<2) , rs=$1, rt=$2 | ||
slti | 001010 | rs | rt | immediate | slti $1,$2,10 | if($2<10) | if ((rs)<(Sign-Extend)immediate) then (rt)←1; else (rt)←0, rs=$2, rt=$1 | ||
sltiu | 001011 | rs | rt | immediate | sltiu $1,$2,10 | if($2<10) | if ((rs)<(Zero-Extend)immediate) then (rt)←1; else (rt)←0, rs=$2, rt=$1 | ||
J-类型 | op | address | |||||||
j | 000010 | address | j 10000 | goto 10000 | (PC)←( (Zero-Extend) address<<2), address=10000/4 | ||||
jal | 000011 | address | jal 10000 | $31=PC+4 goto 10000 | ($31)←(PC)+4; (PC)←( (Zero-Extend) address<<2), address=10000/4 |
add和addu区别是有无溢出检测
四. 实验器材
电脑一台,Xilinx Vivado 软件一套,Basys3板一块。
五. 实验过程与结果
1. CPU的设计
该MIPS subset单周期分五个执行阶段,可分功能模块设计,各个子模块分别设计其特定的功能,最终利用一个总的模块进行子模块间连接,使得整个CPU能连贯执行指令,在仿真结果中观察设计结果,最终进行硬件下载,验证设计。其中各个模块简单功能如下:
(1)指令存储器模块(IM):具备基本的读写功能,用于存放指令。
(2)寄存器堆模块(RegFile):由32个32位的寄存器组成,提供较大的存储空间,用于存放暂存数据和指令。
(3)算术逻辑运算器模块(ALU):执行加减法等算术运算,与非或等逻辑运算,以及比较移位传送等操作的功能部件,是该CPU的设计核心部分,存在不同的运算处理功能,是体现实验设计结果正确性的模块。
(4)立即数扩展模块(signext):执行I型指令时需要立即数扩展,该模块用于MIPS符号扩展,将16位数据扩展为32位数据。
(5)主控制模块(ctr):用于控制各个模块之间的分工运行,产生不同数据通路的控制信号,保证指令顺序执行不发生紊乱。
(6)ALU控制模块(aluctr):用于生成ALU执行各种功能的控制信号,使ALU内部运行不发生紊乱。
(7)数据存储器模块(DM),用于LW/SW指令数据存取。
(8)取指模块(next_PC):进行指令的取出及译码,同时包括程序计数器PC运行设计。
(9)显示模块DISPLAY (display):basys3板子上数码管显示
2.各模块具体设计
(1)指令存储器(IM)
调用系统的ip核,用Mars编写汇编程序后,转为16进制的指令,然后写到coe文件中,导入ip核。需要注意的是,IP核中Port A Options-Port A Optional Output Registers-Primitives Output Register不要勾选,因为勾选后会使得IP核的Latency为2,使得PC无法从0开始。
(2)寄存器堆(RegFile)
寄存器组是指令操作的主要对象,MIPS 处理器里一共有 32 个 32 位的寄存器,故可以声明一个包含 32 个 32 位的寄存器数组。读寄存器时需要 Rs,Rd 的地址,得到其数据。写寄存器 Rd 时需要所写地址,所写数据,同时需要写使能。以上所有操作需要在时钟和复位信号控制下操作。故寄存器组设计如下:
(3)控制器
根据指令中的指令码(op)和功能码(funct)的不同组合输出相应的控制信号。
主控 | aluop 设计图 ALU 译码单元 |
---|---|
![]() |
![]() |
主控Main Control(ctr)
input Op opcode[5:0] | 00 0000 | 00 1101 | 10 0011 | 10 1011 | 00 0100 | 00 0101 | 00 1000 | 00 1010 | 00 1110 | 00 1111 | 00 0010 | 00 0011 | 00 1100 | 00 1001 | 00 1011 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
output | Rtype | ori | lw | sw | beq | bne | addi | slti | xori | lui | jump | Jal | andi | addiu | sltiu |
regDst | 1 | 0 | 0 | X | X | X | 0 | 0 | 0 | 0 | X | X | 0 | 0 | 0 |
aluSrc | 0 | 1 | 1 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | X | X | 1 | 1 | 1 |
memToReg | 0 | 0 | 1 | X | X | X | 0 | 0 | 0 | 0 | X | X | 0 | 0 | 0 |
regWrite | 1 | 1 | 1 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 1 | 1 |
memRead | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
memWrite | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
branch | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
ExtOp | X | 0 | 1 | 1 | X | X | 1 | 1 | 0 | 1 | X | X | 1 | 0 | 0 |
aluop[3:0] | 1111 | 0010 | 0000 | 0000 | 0001 | 0110 | 0000 | 0011 | 1100 | 1011 | xxxx | xxxx | 0101 | 0111 | 0100 |
jmp | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
jal | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
ALU控制译码模块(aluctr)
ALUctr。(拓展了lui指令和移位指令,增加输入lui和Asel输出,jr指令加一个控制信号jr)
上表中31条指令指令与 ALU 操作码及功能码的对应关系如下:(17R+12I+2J)
input | output | ||||||||
ALUop | func | lui | shift | ALU operation | ALUctr | Asel {lui,shift} | jr | ||
1001 | j-type | xxxxxx | 0 | 0 | xxx | xxxx | 00 | 0 | j |
1010 | j-type | xxxxxx | 0 | 0 | xxx | xxxx | 00 | 0 | jal |
0000(0) | I-type | xxxxxx | 0 | 0 | add | 0010 (2) | 00 | 0 | lw/sw |
0000(0) | I-type | xxxxxx | 0 | 0 | add | 0010 (2) | 00 | 0 | addi |
0111(7) | xxxxxx | 0 | 0 | add | 0010 (2) | 00 | 0 | addiu | |
0101(5) | xxxxxx | 0 | 0 | and | 0000 (0) | 00 | 0 | andi | |
0001(1) | xxxxxx | 0 | 0 | sub | 0110 (6) | 00 | 0 | beq | |
0010(2) | I-type | xxxxxx | 0 | 0 | or | 0001 (1) | 00 | 0 | ori |
1000(8) | xxxxxx | 0 | 0 | xor | 1100(12) | 00 | 0 | xori | |
0011(3) | I-type | xxxxxx | 0 | 0 | slt | 0111 (7) | 00 | 0 | slti |
0100(4) | xxxxxx | 0 | 0 | sltu | 1001(9) | 00 | 0 | sltiu | |
0110(6) | xxxxxx | 0 | 0 | sub | 0110(6) | 00 | 0 | bne | |
1011(11) | I-type | xxxxxx | 1 | 0 | sll | 0011 (3) | 10 | 0 | lui |
1111 | R-type | 100000 | 0 | 0 | add | 0010 (2) | 00 | 0 | add |
1111 | 100001 | 0 | 0 | addu | 0010 (2) | 00 | 0 | addu | |
1111 | 100010 | 0 | 0 | sub | 0110 (6) | 00 | 0 | sub | |
1111 | 100011 | 0 | 0 | subu | 0110 (6) | 00 | 0 | subu | |
1111 | 100100 | 0 | 0 | and | 0000 (0) | 00 | 0 | and | |
1111 | 100101 | 0 | 0 | or | 0001 (1) | 00 | 0 | or | |
1111 | 100110 | 0 | 0 | xor | 1100(12) | 00 | 0 | xor | |
1111 | 100111 | 0 | 0 | nor | 1000 (8) | 00 | 0 | nor | |
1111 | 101010 | 0 | 0 | slt | 0111 (7) | 00 | 0 | slt | |
1111 | 101011 | 0 | 0 | sltu | 1001 (9) | 00 | 0 | sltu | |
1111 | 000000 | 0 | 1 | Shift left | 0011 (3) | 01 | 0 | sll | |
000100 | 0 | 0 | Shift left | 0011 (3) | 00 | 0 | sllv | ||
1111 | 000011 | 0 | 1 | Shift A right | 0100 (4) | 01 | 0 | sra | |
1111 | 000111 | 0 | 0 | Shift A right | 0100 (4) | 00 | 0 | srav | |
1111 | 000010 | 0 | 1 | Shift right | 0101 (5) | 01 | 0 | srl | |
1111 | 000110 | 0 | 0 | Shift right | 0101 (5) | 00 | 0 | srlv | |
1111 | 001000 | 0 | 0 | xxxx | 00 | 1 | jr |
Asel={lui,shift}
指导书控制器只实现了部分指令的译码, 如果如图加入移位指令、lui指令,需要添加控制信号shift, lui(左移16位),按照提供的表格或自己编辑添加实现更多指令的控制逻辑。
加入jal指令,需译码jal信号
Shift and lui 增加控制信号ASEL
红色输入为 shamt:<10:6>
1
2
3
assign ALUSrcA =(Asel==2’b00)?RsData:
(Asel==2b01)?instruction[10:6]:
(Asel==2’b10?5’h10:RsData;
(4)ALU运算模块
ALU 主要执行 12种操作:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
4'b0000:begin F=A&B; end //按位与 0 and
4'b0001:begin F=A|B; end //按位或 1 or
4'b0010:begin {C,F}=A+B; end //加法 2 add
4'b0011:begin F=B<<A; end //将B左移shamt位 or rs位 3 sll sllv lui
4'b0100:begin F=( $signed(B) ) >>> A; end //算术右移 4 sra srav
4'b0101:begin F=B>>A; end //将B右移shamt位 5 srl srlv
4'b0110:begin {C,F}=A-B; end //减法 6 sub
4'b0111:begin F=A[31]!=B[31]?A[31]>B[31]:A<B; end //比较A<B带符号 7 slt slti
4'b1000:begin F=~(A|B); end // 或非 8 nor
4'b1001:begin F=A<B; end//A<B则F=1,否则F=0 9 sltu sltiu
4'b1100:begin F=A^B; end //按位异或 12 xor
4'b1111:begin F=32'h0; end // 15 j和jal,jr不需要ALU
add和addu、addi和addiu的区别是有无溢出检测,slt和sltu、slti和sltiu区别是有符号数和无符号数比较
(5)符号扩展模块(signext)
包括了符号扩展和0扩展,如果ExtOp是0,则为0扩展,若ExtOp是1,则为符号扩展
(6)数据存储器模块(DM)
只有lw和sw指令需要用到
(7)数码管显示模块(display)
七段译码显示的内容是16位的,而ALU的运算结果是32位的,将运算结果分为高十六位和低十六位,分别传进七段译码模块;在顶层模块可用一个开关,来选择是显示高16位还是低16位;sm_wei 选择哪一个数码管亮,sm_duan 选择数码管的哪一段亮,sm_wei 变换的速度是 1 秒 1000 次,使人眼看起来数码管是同时显示数值的。
display输入的时钟是高频时钟,在模块内部需要分频使得容易看
(8)PC取指模块(next_PC)
根据控制信号和ALU运算结果等产生下一条PC的值,若该指令为跳转指令,则若jmp为1,则说明要发生跳转,同时根据jr的值来选择进行寄存器跳转还是立即数跳转;若jmp为0则根据branch来决定是否跳转,若branch为1且ALU运算结果符合跳转条件(用zero来标志)则发生跳转,同时利用beq和ben只有指令的第二十六位不同的性质,来区分是否需要跳转,bne且零标志为1则跳转,beq且零标志为0则跳转,否则不进行跳转,下条指令为PC+4。
(9)顶层模块
1.用板子上的按键输入clkORstep来区分是连续运行还是单步运行
1
2
3
wire clkin;
assign clkin=(clkORstep==1'b1)?clk_cpu:step;
2.同时,判断是否是jr指令,来确定寄存器堆A口读出来的是rs的值,还是31号寄存器的值(因为要将A口读出来的值传给PC模块)
1
2
3
wire [4:0]ReadAddrA;
assign ReadAddrA=(jr==1'b1)?5'b11111:instruction[25:21];
3.为了在bsysy3 上便于观看,取指令的时钟需要分频成约T=1.33s的时钟,这样才可以慢慢增加PC(在顶层模块中分频,所有的时钟都用分频后的时钟,只有display模块用分频前高频时钟,因为在display模块中有分频且是基于高频时钟的分频)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//-------------------分频,PC更新需要慢一点的时钟---------------------
integer clk_cnt=0;
reg clk_cpu=0;//1.33秒一个时钟周期,以此为cpu的时钟周期 10^8/0.75*10^8=4/3=1.33 always @(posedge clk)
always @(posedge clk)
if(clk_cnt==32'd75000000)
begin
clk_cnt <= 1'b0;
clk_cpu <= ~clk_cpu;
end
else
clk_cnt <= clk_cnt + 1'b1;
4.因为basys3上数码管只有4个,只能同时显示16位,所以用SW[2]来控制高16位和低16位
1
2
3
wire [31:0] display_content;
assign display_content = (SW[1:0] == 2'b00)? instruction:(SW[1:0] == 2'b01)? PC:(SW[1:0] == 2'b10)? aluRes: memreaddata;
5.显示的内容可以是指令、PC、ALU运算结果、DM中取出来的值,用SW[1:0]来区分
00为ins
01为PC
10为ALU
11为MEM
6.写回寄存器堆的地址有3个:rt、rd、ra。写回寄存器的值有3种:alu计算结果、DM中取出来的值、PC+4,由以下语句区分(WB_addr和WB_data是最终输入到寄存器堆的值)。
1
2
3
4
5
6
7
8
9
10
11
assign regWriteAddr = reg_dst ? instruction[15:11] : instruction[20:16];
assign WB_addr = (jal==1'b1)?5'b11111:regWriteAddr;
assign regWriteData = memtoreg ? memreaddata : aluRes;
reg [31:0]next_PC;
always@(posedge clkin) next_PC=PC+4;
assign WB_data=(jal==1'b1)?next_PC:regWriteData;
7.ALU中A口的输入值有三个,RsData(Rs寄存器的值)或者shamt(R型指令的移位码)或者16(lui要左移16位)
ALU中B口的输入值有两个,RtData(Rt寄存器的值)或者立即数扩展后的值。
1
2
3
4
5
assign input2 = alu_src ? expand : RtData;
assign Asel={lui,shift};
assign input1 =(Asel==2'b00)?RsData:(Asel==2'b01)?shamt:(Asel==2'b10)?5'b10000:RsData;
8.给指令寄存器中输入的是PC[9:2],因为指令存储器IM中存的是指令的字地址且是设计的MIPS是大端存储,只需要输入字地址就可以,而输入PC[9:2]相当于除了4,也就是字地址了。
9.IM在时钟沿下降沿写入,才能使得PC从0开始
3.CPU正确性验证
表格中为理论值,图中为仿真波形图
经过比对,图中的仿真得到的数据与理论值符合,说明CPU设计正确,需要特别注意的是跳转指令
0x400064为jal指令,应该要跳转到0x400070的位置,跳转成功,并且看jr指令能否跳转回到0x400068的位置
0x40008c为jr指令,而下一条指令的地址为0x400068,是jal指令的下一条,说明jal指令成功跳转的同时也将PC+4的地址记录到了31号寄存器中,jr指令可以成功从31号寄存器读取地址并跳转
0x400070为bne指令:bne $1,$2,notequal #jal here
此时$1的值为6,$2的值为3,要发生跳转,到0x40007c的位置,图中可以看出下一条指令地址为0x40007c,说明成功跳转且可以分辨bne和零标志,因为bne是零标志位1时跳转
0x400080为beq指令:beq $1,$2,equal
,此时$1=6,$2=6,会发生跳转,跳转到0x400088的位置,而图中可看出beq下一条指令地址为0x40088,说明成功跳转,也说明了可以区分beq和零标志。只有beq和零标志为0的才会发生跳转。
0x40006c是j指令,需要跳转到0x400090的位置,图中下一条指令位置为0x400090,说明跳转成功
还有需要注意的是slti和sltiu,slt和sltu的区别,就是有符号数比较和无符号数比较,这里以slti和sltiu为例:
0x400088处为slti:slti $9,$0,-2 #beq here
,此时运算结果为0,说明0>-2, 所以不小于
0x4000a4处为sltiu:sltiu $16,$0,-2
,此时运算结果为1,说明0<-2成立,这是因为-2以补码形式储存,实际存储的值是fffe,有因为是无符号比较,所以不会认为是-2,而是fffe,即0<fffe,所以可以实现有符号比较和无符号比较
汇编程序 | 运行结果 | |||||||
16进制 | Next_PC | ALU运算结果 | DM输出结果(LW) | WB_data写回数据 | WB_addr写回地址 | |||
0x00400000 | lui $1,4097 | 3c011001 | 0x00400004 | 268500992 | x | 268500992 | 1 | |
0x00400004 | ori $11,$1,0 | 342b0000 | 0x00400008 | 268500992 | x | 268500992 | 11 | |
0x00400008 | addi $1,$0,1 | 20010001 | 0x0040000C | 1 | x | 1 | 1 | |
add $2,$1,$1 | 00211020 | 0x00400010 | 2 | x | 2 | 2 | ||
0x00400010 | sub $3,$2,$1 | 00411822 | 0x00400014 | 1 | x | 1 | 3 | |
0x00400014 | add $1,$2,1 | 20410001 | 0x00400018 | 3 | x | 3 | 1 | |
0x00400018 | addi $2,$0,6 | 20020006 | 0x0040001C | 6 | x | 6 | 2 | |
0x0040001C | and $4,$1,$2 | 00222024 | 0x00400020 | 2 | x | 2 | 4 | |
0x00400020 | or $4,$1,$2 | 00222025 | 0x00400024 | 7 | x | 7 | 4 | |
0x00400024 | xor $4,$1,$2 | 00222026 | 0x00400028 | 5 | x | 5 | 4 | |
0x00400028 | nor $4,$1,$2 | 00222027 | 0x0040002C | -8 | x | -8 | 4 | |
0x0040002C | slt $5,$1,$2 | 0022282a | 0x00400030 | 1 | x | 1 | 5 | |
0x00400030 | sll $5,$2,2 | 00022880 | 0x00400034 | 24 | x | 24 | 5 | |
0x00400034 | srl $5,$2,1 | 00022842 | 0x00400038 | 3 | x | 3 | 5 | |
0x00400038 | sra $5,$2,1 | 00022843 | 0x0040003C | 3 | x | 3 | 5 | |
0x0040003C | sllv $5,$2,$1 | 00222804 | 0x00400040 | 48 | x | 48 | 5 | |
0x00400040 | addi $10,$0,1 | 200a0001 | 0x00400044 | 1 | x | 1 | 10 | |
0x00400044 | srlv $5,$2,$10 | 01422806 | 0x00400048 | 3 | x | 3 | 5 | |
0x00400048 | srav $5,$2,$10 | 01422807 | 0x0040004C | 3 | x | 3 | 5 | |
0x0040004C | andi $6,$1,6 | 30260006 | 0x00400050 | 2 | x | 2 | 6 | |
0x00400050 | ori $6,$1,6 | 34260006 | 0x00400054 | 7 | x | 7 | 6 | |
0x00400054 | xori $6,$1,6 | 38260006 | 0x00400058 | 5 | x | 5 | 6 | |
0x00400058 | lui $7,1 | 3c070001 | 0x0040005c | 65536 | x | 65536 | 7 | |
0x0040005c | sw $2,0($11) | ad620000 | 0x00400060 | xx | x | x | x | |
0x00400060 | lw $8,0($11) | 8d680000 | 0x00400064 | xx | 6 | 6 | 8 | |
0x00400064 | jal BranchTest | 0c10001c, | 0x00400070 | xx | x | x | x | |
0x00400068 | addi $17,$0,17 | 20110011 | 0x0040006c | 17 | x | 17 | 17 | |
0x0040006c | j end | 08100024 | 0x00400090 | xx | x | x | x | |
0x00400070 | bne $1,$2,notequal #jal here | 14220002 | 0x0040007c | -3 | x | x | x | |
0x00400074 | addi $18,$0,18 #不运行 | 20120012 | xx | xx | x | x | xx | |
0x00400078 | addi $19,$0,20 #不运行 | 20130014 | xx | xx | x | x | xx | |
0x0040007c | addi $2,$0,3 #bne here | 20020003 | 0x00400080 | 3 | x | 3 | 2 | |
0x00400080 | beq $1,$2,equal | 10220001 | 0x00400088 | 0 | x | x | x | |
0x00400084 | addi $19,$0,19#不运行 | 20130013 | xx | xx | x | x | xx | |
0x00400088 | slti $9,$0,-2 #beq here | 2809fffe | 0x0040008c | 0 | x | 0 | 9 | |
0x0040008c | jr $31 #回到jal branch的下一条 | 03e00008 | 0x00400068 | xx | x | x | x | |
0x00400090 | addi $1,$0,2 #j here | 20010002 | 0x00400094 | 2 | x | 2 | 1 | |
0x00400094 | addiu $12,$0,-2 | 240cfffe | 0x00400098 | -2 | x | -2 | 12 | |
0x00400098 | addu $13,$0,$12 | 000c6821 | 0x0040009c | -2 | x | -2 | 13 | |
0x0040009c | subu $14,$0,$1#$1=2 | 00017023 | 0x004000a0 | -2 | x | -2 | 14 | |
0x004000a0 | sltu $15,$0,$12 | 000c782b | 0x004000a4 | 1 | x | 1 | 15 | |
0x004000a4 | sltiu $16,$0,-2 | 2c10fffe | 0x00400098 | 1 | x | 1 | 16 |
4.Basys 3板操作方法
顶层文件输入有6个,
clk接板子上的时钟
step接板子上的SW15,模拟一个上升沿和下降沿
clkORstep接板子上的SW14,为1则是连续运行,时钟为板子上时钟clk,为0则为单步运行,用step驱动
reset接板子上的SW13,为1时,在上升沿来到时清零,为0时,则正常运行
SW2来选择显示数据的高16位和低16位,为1则显示高16位,为0则显示低16位
SW1和SW0同时选择显示的数据:00则为instruction,01则为PC,10则为ALU计算结果,11则为DM中取出来的值
输出有7个
sm_duna选择一个数码管的显示,sm_wei选择是哪一个数据管,数据在4个数码管上显示,显示高16位或者低16位
而输出标志这有ZF(零标志)、OF(溢出标志)、SF(符号位)、CF(进/借位)、PF(奇偶标志位)
ZF,连接板子上LD15 //0标志位, 运算结果为0(全零)则置1, 否则置0
OF,连接板子上LD14 //溢出标志位,对有符号数运算有意义,溢出则OF=1,否则为0
SF,连接板子上LD13 //符号标志位,与F的最高位相同
CF,连接板子上LD12 //进借位标志位, 取最高位进位C,加法时C=1则CF=1表示有进位,减法时C=0则CF=1表示有借位
PF,连接板子上LD11 //奇偶标志位,F有奇数个1,则PF=1,否则为0
5.Basys 3板运行CPU
##
六. 实验心得
- 在做CPU的时候,我是一类指令一类指令的写的,在开始之前并没有总体规划,所以导致其实很多可以单独用到MUX的地方,都是在顶层文件通过两步来完成的,例如:
1
2
assign regWriteAddr = reg_dst ? instruction[15:11] : instruction[20:16];
assign WB_addr = (jal==1'b1)?5'b11111:regWriteAddr;
本来是可以用3选一选择器使得CPU的结构更加的清晰,但是由于没有增加jal指令之前写回地址只有rt和rd,所以只用了一个三元运算符,但是增加jal指令后就要增加31号寄存器,但是要是临时增加一个3选一的MUX又要改很多地方,所以就保留了上述这种不利于阅读的方法,自己看还好,但是不利于其他人的理解。 所以在开始设计CPU的时候最好先花一点时间看看自己都需要哪一些部件,能抽象成为一个模块的最好都抽象出来,便于阅读,也便于自己的修改。
2.添加移位指令时可以传入ALU的参数其实包含shamt,即移动位数,所以sll和sllv可以分成两条指令来写,但是我觉得这样同样操作的指令分成两条写不太优雅,所以ALU的A口传入的值有3种,rs的值或者是shamt或者是16(因为lui指令是要左移16位的),这样sll和sllv京可以用同一条指令完成,区别只是传入A口的值不同,这样只需要添加一个选择器即可,而不用在ALU中添加一条指令,但在软件层面上来说,在ALU中添加一条指令显然是更容易的,但是不符合简洁的设计初衷,所以我选择了添加选择器。
1
assign input1 =(Asel==2'b00)?RsData:(Asel==2'b01)?shamt:(Asel==2'b10)?5'b10000:RsData;
3.添加jal指令的时候需要存PC+4到$31中,但是如果直接写的话,会有时钟影响导致PC的值改变,因此写入的值将不是正确的值,因为verilog语言是一旦变化就会写入的,因此需要限定next_PC的值,保证下一个上升沿到来前next_PC的值不会改变:
1
2
3
reg [31:0]next_PC;
always@(posedge clkin) next_PC=PC+4;
assign WB_data=(jal==1'b1)?next_PC:regWriteData;
4.传入IM的值是PC[9:2],因为IM中根据字地址寻找指令,而我们规定了数据深度为256位,因此只需要2^8=256,第二位到第九位这8位数就可以了
5.仿真遇到zzzz(高阻态),一般是没有在仿真激励文件中例化。遇到xxxx是因为没有取到值。
6.在设计控制器的时候一定要先分配好自己的aluop对应的操作。