计算机系统基础实验
实验目标
实验目的
实验要求
(1)单周期实验
1. 设计单周期 CPU,支持以下 26 条指令
• add/sub/and/or/slt/sltu/addu/subu
• addi/ori/lw/sw/beq
• j/jal
• sll/nor/lui/slti/bne/andi/srl/sllv/srlv/jr/jalr
2. 在 Nexys 4 DDR 开发板上正确实现学号排序。
(2)多周期实验
1. 设计多周期 CPU,支持以下 26 条指令
• add/sub/and/or/slt/sltu/addu/subu
• addi/ori/lw/sw/beq
• j/jal
• sll/nor/lui/slti/bne/andi/srl/sllv/srlv/jr/jalr
2. 能够正确处理多周期CPU中的状态机
3. 指令和数据放在同一个存储器
4. 在 Nexys 4 DDR2 开发板上正确实现学号排序
实验结论
1. 利用Verilog HDL编写的各个模块的.v文件成功地在ModelSim环境通过仿真测试,在 Vivado 开发板导入文件后实现了正确的学号排序,证明了该方法的可行性。
2. 在使用同一程序的条件下,多周期CPU比单周期处理速度快,进一步证明了设计CPU结构的重要性。
3. 运用软件进行硬件设计,比单纯人力设计要快得多。Verilog HDL语言在设计CPU时有极大的便利性,EDA技术可大幅提升设计 CPU的效率,与此同时,我对CPU结构的认识也得到了非常多的提升。
实验目的
本实验旨在融会贯通计算机组成原理所授的知识,通过对知识的综合应用,加深对CPU系统各模块的工作原理及其相互联系的认识。
学习采用EDA(Electronic Design Automation)技术设计MIPS单周期CPU与多周期CPU的技术与方法。能够培养科学研究的独立工作能力,取得CPU设计与仿真的实践和经验。并且进一步了解SOC系统,并在FPGA开发板上实现简单的SOC系统,体会计算机硬件的开发过程。
实验环境
Verilog HDL
(1)Verilog HDL 是一种硬件描述语言,以文本形式来描述数字系统硬件的结构和行为的语言,用它可以表示逻辑电路图、逻辑表达式,还可以表示数字逻辑系统所完成的逻辑功能,可对算法级、门级、开关级等多种抽象设计层次进行建模。
(2)Verilog HDL 中有两类数据类型:网线 wire 数据类型和寄存器 reg 数据类型。网线类型表示构件间的物理连线,而寄存器类型表示抽象的数据存储元件。
(3)Verilog HDL 具有内置逻辑函数,例如(按位与)和 |(按位或),为设计提供了方便。
(4)Verilog HDL 中的模块类似 C 语言中的函数,它能够提供输入、输出端口,可以实例调用其他模块,也可以被其他模块实例调用。模块中可以包括组合逻辑部分、过程时序部分。
MARS
(1)MARS是一个轻量级的交互式开发环境(IDE),用于使用MIPS汇编语言进行编程,旨在与Patterson和Hennessy的计算机组织和设计一起用于教育级别。
(2)MARS 是一个用于 MIPS 汇编语言程序设计的 IDE,需要 Java 环境的支持。
ModelSim
-
Mentor 公司的 ModelSim 是业界最优秀的 HDL 语言仿真软件,它能提供友好的仿真环境,是业界唯一的单内核支持 VHDL 和 Verilog 混合仿真的仿真器。
-
ModelSim 采用直接优化的编译技术、Tcl/Tk 技术、和单一内核仿真技术,编译仿真速度快,编译的代码与平台无关,便于保护 IP 核,个性化的图形界面 和用户接口,为用户加快调错提供强有力的手段,是 FPGA/ASIC 设计的首选 仿真软件。
-
ModelSim 安装路径不能含有中文,它会直接新建一个 work 文件夹,需要注意源文件的拷贝或者
Vivado
-
Vivado 是 FPGA 厂商赛灵思公司 2012 年发布的集成设计环境,把各类可编程技术结合在一起,能够扩展多达 1 亿个等效 ASIC 门的设计。
-
Vivado 是 FPGA 厂商 Xilinx 公司发布的集成设计环境。该安装软件解压后约20GB+,安装所需空间约 30GB。
-
Vivado 包括高度集成的设计环境和新一代从系统到 IC 级的工具,这些均建立在共享的可扩展数据模型和通用调试环境基础上。也是一个基于 AMBA AXI4互联规范、IP-XACT IP封装元数据、工具命令语言 (TCL)、Synopsys系统约束 (SDC) 以及其它有助于根据客户需求量身定制设计流程并符合业界标准的开放式环境。
-
Vivado 是业界首个采用模块化设计的图形化 IP 流程
Nexys4-DDR
(1)Nexys4-DDR是一款即用型数字电路开发平台,采用了 Xilinx Artix-7 FPGA 芯片,它是一款简单易用的数字电路开发平台,可以支持在课堂环境中来设计一些行业应用。
(2)Nexys4-DDR 开发板上集成的加速度、温度传感器、MEMs 数字麦克风、扬声器放大器以及大量的 I/O 设备,让 Nexys4-DDR 不需要增添额外组件而用于各种各样的设计。
(3)Nexys4-DDR具有的大规模、高容量的FPGA(Xilinx 零件编号 XC7A100T 1CSG324C), 大容量外部存储, 各种 USB、以太网、以及其它接口, 让Nexys4-DDR能够满足从入门级组合逻辑电路到强大的嵌入式系统的设计。
单周期CPU设计
总体设计
单周期CPU指的是一条指令的执行在一个时钟周期内完成,然后开始下一条指令的执行,即一条指令用一个时钟周期完成。此时,完成一条指令需要经过以下五个步骤:
(1) 取指令(IF):根据程序计数器PC中的指令地址,从存储器中取出一条指令,同时,根据指令字长度自动递增产生下一条指令所需要的指令地址,在本实验的MIPS汇编条件下,即PC+4,但遇到“地址转移”指令时,则控制器把转移地址经过扩展,送入PC。
(2) 指令译码(ID):对取指令操作中得到的指令进行分析并译码,确定这条指令需要完成的操作,从而产生相应的操作控制信号,用于驱动执行状态中的各种操作。
(3) 指令执行(EXE):根据指令译码得到的操作控制信号,具体地执行指令动作,然后转移到结果写回状态。
(4) 存储器访问(MEM):所有需要访问存储器的操作都将在这个步骤中执行,该步骤给出存储器的数据地址,把数据写入到存储器中数据地址所指定的存储单元或者从存储器中得到数据地址单元中的数据。
(5) 结果写回(WB):指令执行的结果或者访问存储器中得到的数据写回相应的目的寄存器中。
整体结构如下图所示(图片来源:老师PPT)。

图2-1
据此,设计总体任务有:
1. 单周期 CPU,支持以下 26 条指令
• add/sub/and/or/slt/sltu/addu/subu
• addi/ori/lw/sw/beq
• j/jal
• sll/nor/lui/slti/bne/andi/srl/sllv/srlv/jr/jalr
2. 构造包括多个模块,各个模块之间相互连接实现一条指令从取指到译码再到执行的过程。
3. 以功能部件为基础构造。
4、模块组成:
alu.v,crtl.v,crtl_encode_def.v, im.v, dm.v, EXT.v, mux.v, NPC. v, PC.v, RF.v, sccomp.v, sccomp_tb.v, sccpu.v
5. 各个模块的简要介绍
• alu.v:CPU的计算ALU模块,负责各个指令在EX段的算术操作。
• ctrl.v:用于生成处理器的各个控制信号。
• ctrl_encode_def.v:存放ALU不同运算操作的数字编码。
• im.v:指令存储器模块。
• dm.v:数据存储器。
• EXT.v:符号扩展和零扩展的模块。
• mux.v:多选器模块,包含多种选择方式来用于不同的操作。
• NPC.v:确定下一条指令的模块。
• PC.v:确定该条指令,对PC进行赋值的模块。
• RF.v:实现寄存器的读出和写入的模块。
• sccpu.v:将上述模块连接到一起的次顶层模块,用于一次ALU操作。
• sccomp.v:在sccpu组合了各个模块的基础上,加入了数据存储器DM和指令存储器IM。
• sccomp_tb.v:测试模块。
总体结构:
(图片来自网络:https://blog.csdn.net/Accelerato/article/details/86546751)

图2-2
PC(程序计数器)
功能描述
PC单元以时钟信号clk、重置标志rst以及NPC(next PC)为输入,输出当前PC地址。PCWr决定写入(单周期CPU每个周期都写入PC,该信号可省略或为1)。
模块接口
| 信号名 | 方向 | 描述 |
|---|---|---|
| clk | 输入 | clk上升沿时:if PCWr then PC <- NPC |
| rst | 输入 | rst上升沿时: if rst then PC <- 32’h00000000 |
| NPC[31:0] | 输入 | 下一条执行指令的地址 |
| PC[31:0] | 输出 | 新的PC值 |
代码和描述说明
当rst=1时,初始化PC,将PC置为0,否则为新地址。
PC.v代码实现如下:

图2-3
RF(寄存器文件)
功能描述
寄存器文件单元的功能是接收instructionMemory中的rs,rt,rd作为输入,输出对应寄存器的数据,从而达到取寄存器里的数据的目的。
模块接口
| 信号名 | 方向 | 描述 |
|---|---|---|
| clk | 输入 | 时钟周期 |
| rst | 输入 | 清零信号 |
| A1[4:0] | 输入 | 读取寄存器编号1 |
| A2[4:0] | 输入 | 读取寄存器编号2或立即数 |
| A3[4:0] | 输入 | 写入寄存器编号3 |
| RFWr | 输入 | 写使能信号,为0的时候不能写入,WD值不更新,为1的时候能写入,WD值更新,输入信号。 |
| WD | 输入 | 寄存器3更新值 |
| RD1[31:0] | 输出 | 输出寄存器1的值 |
| RD2[31:0] | 输出 | 输出寄存器2的值 |
| reg_sel | 输入 | 需要读取数据的寄存器地址 |
| reg_data | 输出 | 从寄存器中读取的数据 |
代码和描述说明
RF.v实现代码如下:

图2-4
ALU(算数运算单元)
功能描述
模块ALU接收寄存器的数据和控制信号作为输入,进行运算和结果输出。
模块接口
| 信号名 | 方向 | 描述 |
|---|---|---|
| A[31:0] | 输入 | 需要运算的数A |
| B[31:0] | 输入 | 需要运算的数B |
| ALUOp[3:0] | 输入 | 控制信号,表示计算的方式 |
| C[31:0] | 输出 | 运算的结果 |
| Zero | 输出 | 零信号,为跳转指令beq和bne服务 |
代码和描述说明
在ctrl_encode_def.v文件中准备如下:

图2-5
规定指令和对应的运算:
| ALUOp | 功能 | 操作 |
|---|---|---|
| 0000 | NOP | C=A |
| 0001 | ADD | C=A + B |
| 0010 | SUB | C=A - B |
| 0011 | AND/ANDI | C=A & B |
| 0100 | OR/ORI | C=A | B |
| 0101 | SLT/SLTI | C=(A<B) ? 32’d1 : 32’d0 |
| 0110 | SLTU | C=({1’b0,A}<{1’b0,B}) ? 32’d1 : 32’d0 |
| 0111 | NOR | C=~A & ~B |
| 1000 | SLL/SLLV | C=B << A |
| 1001 | SRL/SRLV | C=B >> A |
| 1010 | LUI | C=B << 16 |
| others | NOP | C=A |
| —— | A==B? | Zero=(C==32’b0) |
alu.v代码实现如下:

图2-6
CTRL(控制单元)
功能描述
控制单元的功能是接收一个6位的操作码和一个标志符作为输入,输出PCWr、ALUSrcB等控制信号,各控制信号的作用见实验原理的控制信号作用表(表1),从而达到控制各指令的目的。
具体来说,ctrl模块接收到指令的funct和op字段的值以及判断beq和bne指令是否跳转的Zero值,之后对指令进行译码,并输出控制信号。
控制逻辑如下:
首先判断是什么指令,因为R型指令op字段为全0,因此便于区分。然后R型指令匹配funct字段的值,I型和J型指令匹配op字段的值,若匹配上,相应指令的值被赋为1,否则为0。然后根据当前周期执行的指令的执行过程,生成对应的控制信号:寄存器写信号 (RegWrite)、数据存储器写信号 (MemWrite)、ALU的B端输入数据的二路选择器选择信号 (ALUSrc)、ALU的A端输入数据的二路选择器选择信号 (ARegSel)、符号扩展单元控制信号(EXTOp)、寄存器堆写数据四路选择器选择信号(WDSel)、 NPC 控制信号 (NPCOp)、ALU 控制信号 (ALUOp) 、生成寄存器写入寄存器编号、四路选择器选择信号 (GPRSel)。其中 NPCOp 信号的生成需要结合bne和beq指令是否跳转以及当前周期的指令类型来生成。
模块接口
| 信号名 | 方向 | 描述 |
|---|---|---|
| Op[5:0] | 输入 | 指令op段,控制信号 |
| Funct[5:0] | 输入 | 指令funct段,控制信号 |
| Zero | 输入 | 零信号,为跳转指令beq和bne服务 |
| RegWrite | 输出 | 写寄存器信号 |
| MemWrite | 输出 | 写存储器信号 |
| EXTOp | 输出 | 符号扩展信号 |
| ALUOp[3:0] | 输出 | alu控制信号 |
| NPCOp[1:0] | 输出 | op选择信号 |
| ALUSrc | 输出 | ALU sourceB信号 |
| ALU_A | 输出 | ALU sourceA信号 |
| GPRSel[1:0] | 输出 | 写寄存器选择信号 |
| WDSel[1:0] | 输出 | 写数据选择信号 |
代码和描述说明
在ctrl_encode_def.v中准备好NPC控制信号:

图2-7
编写ctrl模块如下:

图2-8

图2-9
EXT(符号扩展单元)
功能描述
EXT模块根据输入的16位立即数和5位的偏移量,在控制模块得出的扩展信号EXTOp 控制下,对立即数进行相应的扩展,得到 32 位立即数。如果EXTOp为00,对立即数进行 0 扩展,高16位补0,;如果EXTOp为01,对立即数进行符号扩展,高16位补第15位的符号位。
模块接口
| 信号名 | 方向 | 描述 |
|---|---|---|
| Imm16 | 输入 | 需要扩展的16位立即数 |
| EXTOp | 输入 | 控制信号,选择是零扩展还是符号扩展 |
| Imm32 | 输出 | 扩展后的32位立即数 |
代码和描述说明
编写符号扩展模块EXT如下:

图2-10
MUX(多路选择器)
功能描述
对输入的多个信号进行选择。
模块接口
设N为选择器的选择个数。
| 信号名 | 方向 | 描述 |
|---|---|---|
| di(0<=i<N), | 输入 | 需要选择的信号 |
| s | 输入 | 选择控制信号 |
| y | 输出 | 选择输出的信号 |
代码和描述说明
二路选择器:

图2-11
四路选择器:

图2-12
八路选择器:

图2-13
十六路选择器:

图2-14
NPC
功能描述
NPC模块用以产生下一周期单周期CPU的指令地址。根据输入的当前PC指令地址、跳转指令的26位立即数以及指定寄存器内的值对应的地址,在ctrl控制模块生成的NPCOp 信号的控制下,生成下一周期的指令地址NPC,包括bne,beq,j,jr,jal和jalr指令。
如果NPCOp为01,则以PC+4为基地址,加上分支指令的跳转偏移量,作为下一周期的指令地址;如果NPCOp为10,则取PC+4的高四位,拼接上跳转指令的26位立即数以及最后两位的字节地址,作为下一周期的指令地址;如果NPCOp为11,则取jr指令指定的寄存器内的值对应的地址作为下一周期的指令地址;其余情况下,下一周期的指令地址均为 PC+4。
模块接口
| 信号名 | 方向 | 描述 |
|---|---|---|
| PC[31:0] | 输入 | 当前地址 |
| NPCOp[1:0] | 输入 | 控制选择信号 |
| IMM[25:0] | 输入 | 立即数,用于分支指令的跳转 |
| PCJR[31:0] | 输入 | jr和jalr的跳转地址 |
| NPC[31:0] | 输出 | 下一条执行指令的地址 |
代码和描述说明
在“ctrl_encode_def.v”准备好控制信号和指令的对应关系:

图2-15
编写NPC模块:

图2-16
dm(数据存储器)
功能描述
存储数据。定义128个32位数据内存。取输入地址处的数据,赋值给dout;如果是写操作,在DMWR为1的情况下,根据输入地址addr[8:2]选择需要写入的地址,将数据din写入对应地址的内存中。
模块接口
| 信号名 | 方向 | 描述 |
|---|---|---|
| clk | 输入 | 时钟周期 |
| DMWr | 输入 | 写控制信号 |
| addr | 输入 | 地址 |
| din | 输入 | 输入数据 |
| dout | 输出 | 输出数据 |
代码和描述说明

图2-17
im(指令存储器)
功能描述
im模块用于程序指令的读取。定义128个32位指令内存。输入需取指令的地址,将内存中相应地址处的指令赋值给dout输出。
模块接口
| 信号名 | 方向 | 描述 |
|---|---|---|
| addr | 输入 | ROM地址 |
| dout | 输出 | 将要执行的指令 |
代码和描述说明

图2-18
SCCPU
功能描述
组合PC,NPC,RF,ALU,ctrl,EXT,mux等逻辑单元,由PC在指令存储器中取址,运算的结果如果要写入存储器的话就送入数据存储器。各个器件之间如果有多个信号就加多路选择器,形成CPU数据通路。
模块接口
| 信号名 | 方向 | 描述 |
|---|---|---|
| clk | 输入 | 时钟周期 |
| rst | 输入 | 清零信号 |
| reg_sel[4:0] | 输入 | 选择寄存器 |
| instr[31:0] | 输入 | 运行的指令 |
| readdata[31:0] | 输入 | 数据存储器中的数据 |
| PC[31:0] | 输出 | 当前指令的地址 |
| MemWrite | 输出 | 写存储器控制信号 |
| aluout[31:0] | 输出 | alu的输出 |
| writedata[31:0] | 输出 | 写入数据存储器的数据 |
| reg_data[31:0] | 输出 | 写入寄存器的数据 |
代码和描述说明

图2-19

图2-20

图2-21
SCCOMP
功能描述
在sccpu组合了各个模块的基础上,加入了数据存储器DM和指令存储器IM,构成完整的单周期cpu。
模块接口
| 信号名 | 方向 | 描述 |
|---|---|---|
| clk | 输入 | 时钟控制信号 |
| rstn | 输入 | 异步上升沿复位信号,wire rst=~rstn |
| reg_sel | 输入 | 寄存器选择信号 |
| reg_data | 输出 | 寄存器数据 |
代码和描述说明

图2-22
ctrl_encode_def
控制信号编码定义文件如下:

图2-23
单周期CPU测试及结果分析
仿真代码及分析
仿真测试文件(testbench):

图2-24
用于测试的汇编代码:
- mipstest_extloop.asm:

图2-25
生成的dat文件:

图2-26
(2)学号排序代码:

图2-27

图2-28

图2-29
生成的dat文件:

图2-30
仿真测试结果
- mipstest_extloop:
波形图:

图2-31
存储器内结果:

图2-32
-
学号排序
波形图:

图2-33
存储器:

图2-34
下载测试代码及分析
coe 文件通过先前生成的Hex文件编辑而成
文件开头加上:
• memory_initialization_radix=16;
• memory_initialization_vector=
然后从第三行开始每行其余各行加, 最后一行加;
生成的 coe 文件如下:

图2-35
下载测试结果

图2-36
生成IP核,添加xdc约束文件,生成二进制流文件,加载到Nexys4 DDR开发板上,得到以下学号排序结果:
排序前:

图2-37
排序后:

图2-38
多周期CPU设计
总体设计
整体流程:
1. 取指令 (IF):根据程序计数器pc中的指令地址,从存储器中取出一条指令,同时,pc根据指令字长度自动递增产生下一条指令所需要的指令地址,但遇到“地址转移”指令时,控制器需把相应转换得到的“转移地址”送入pc。
2. 指令译码 (ID):对取指令操作中得到的指令进行分析并译码,确定这条指令需要完成的操作,从而产生相应的操作控制信号,用于驱动执行状态中的各种操作。
3. 指令执行 (EXE):根据指令译码得到的操作控制信号,具体地执行指令动作,然后转移到结果写回状态。
4. 存储器访问 (MEM):所有需要访问存储器的操作都将在这个步骤中执行,该步骤给出存储器的数据地址,把数据写入到存储器中数据地址所指定的存储单元或者从存储器中得到数据地址单元中的数据。
5. 结果写回 (WB):指令执行的结果或者访问存储器中得到的数据写回相应的目的寄存器中。
整体结构如下图所示(图片来源:老师PPT)。

图2-39
单周期的特点是每一条指令用一个周期完成,即每条指令所用的总时长一致。而多周期每一条指令由多个周期完成,周期长度相同,但是不同的指令需要的周期数目不同,完成指令总时长不一定相同,从而使时间利用率提高,而且执行指令时每一个步骤使用一个周期,可阅读性也大大提高。需要注意的是多周期中同样必须一条指令完成才开始执行下一条指令。这和单周期是一致的,而流水线设计则是多条指令执行。
多周期各指令具体执行的阶段如下(图片来源:老师ppt):

图3-1
模块介绍:
• alu.v:CPU的计算ALU模块,负责各个指令在EX段的算术操作。
• ctrl.v:用于生成处理器的各个控制信号。
• ctrl_encode_def.v:存放ALU不同运算操作的数字编码。
• dm.v:数据存储器。
• flopr.v:用于数据延迟,使得数据正常输入输出。
• flopenr.v:用于数据延迟,使得数据正常输入输出,与上一模块区别为在写入
时需要写信号。
• EXT.v:符号扩展和零扩展的模块。
• mux.v:多选器模块,包含多种选择方式来用于不同的操作。
• RF.v:实现寄存器的读出和写入的模块。
• mccpu.v:将上述模块连接到一起的次顶层模块,用于一次ALU操作。
• mccomp.v:在sccpu组合了各个模块的基础上,加入了数据存储器DM。
• mccomp_tb.v:测试模块。

图3-2
RF(寄存器文件)
功能描述
寄存器文件单元的功能是接收指令中的rs,rt,rd作为输入,输出对应寄存器的数据,从而达到取寄存器里的数据的目的。
模块接口
| 信号名 | 方向 | 描述 |
|---|---|---|
| clk | 输入 | 时钟周期 |
| rst | 输入 | 清零信号 |
| A1[4:0] | 输入 | 读取寄存器编号1 |
| A2[4:0] | 输入 | 读取寄存器编号2或立即数 |
| A3[4:0] | 输入 | 写入寄存器编号3 |
| RFWr | 输入 | 写使能信号,为0的时候不能写入,WD值不更新,为1的时候能写入,WD值更新,输入信号。 |
| WD | 输入 | 寄存器3更新值 |
| RD1[31:0] | 输出 | 输出寄存器1的值 |
| RD2[31:0] | 输出 | 输出寄存器2的值 |
| reg_sel | 输入 | 需要读取数据的寄存器地址 |
| reg_data | 输出 | 从寄存器中读取的数据 |
代码和描述说明
RF.v实现代码如下:

图3-3
ALU(算数运算单元)
功能描述
算术运算单元, 在EXE阶段实现运算,模块根据送入的ALUOp决定ALU的行为,同时产生zero信号,用于对bne和beq指令的判断。
模块接口
| 信号名 | 方向 | 描述 |
|---|---|---|
| A[31:0] | 输入 | 需要运算的数A |
| B[31:0] | 输入 | 需要运算的数B |
| ALUOp[3:0] | 输入 | 控制信号,表示计算的方式 |
| C[31:0] | 输出 | 运算的结果 |
| Zero | 输出 | 零信号,为跳转指令beq和bne服务 |
代码和描述说明
在ctrl_encode_def.v文件中准备如下:

图3-4
规定指令和对应的运算:
| ALUOp | 功能 | 操作 |
|---|---|---|
| 0000 | NOP | C=A |
| 0001 | ADD | C=A + B |
| 0010 | SUB | C=A - B |
| 0011 | AND/ANDI | C=A & B |
| 0100 | OR/ORI | C=A | B |
| 0101 | SLT/SLTI | C=(A<B) ? 32’d1 : 32’d0 |
| 0110 | SLTU | C=({1’b0,A}<{1’b0,B}) ? 32’d1 : 32’d0 |
| 0111 | NOR | C=~A & ~B |
| 1000 | SLL/SLLV | C=B << A |
| 1001 | SRL/SRLV | C=B >> A |
| 1010 | LUI | C=B << 16 |
| others | NOP | C=A |
| —— | A==B? | Zero=(C==32’b0) |
alu.v代码实现如下:

图3-5
CTRL(控制单元)
功能描述
根据指令进行译码,产生控制信号。并在此处进行状态机的实现。多周期控制模块与单周期不同, 多周期cpu控制模块增加了指令的执行的状态,对于指令的五个阶段分别进行相应的操作。根据指令的特征选择指令具有的阶段。
模块接口
| 信号名 | 方向 | 描述 |
|---|---|---|
| clk | 输入 | 时钟控制信号 |
| rst | 输入 | 复位控制信号 |
| Op[5:0] | 输入 | 指令op段,控制信号 |
| Funct[5:0] | 输入 | 指令funct段,控制信号 |
| Zero | 输入 | 零信号,为跳转指令beq和bne服务 |
| RegWrite | 输出 | 写寄存器控制信号 |
| MemWrite | 输出 | 写存储器控制信号 |
| PCWrite | 输出 | PC写控制信号 |
| IRWrite | 输出 | IR写控制信号 |
| EXTOp | 输出 | 符号扩展控制信号 |
| ALUSrcA[1:0] | 输出 | ALU sourceA信号 |
| ALUSrcB[1:0] | 输出 | ALU sourceB信号 |
| ALUOp[3:0] | 输出 | ALU控制信号 |
| PCSource[1:0] | 输出 | PC source信号 |
| GPRSel[1:0] | 输出 | 通用目标寄存器选择信号 |
| WDSel[1:0] | 输出 | 写数据选择信号 |
| IorD | 输出 | 指令和数据的选择信号 |
代码和描述说明
在ctrl_encode_def.v中准备好NPC控制信号:

图3-6
编写ctrl模块如下:

图3-7

图3-8

图3-9

图3-10

图3-11
EXT(符号扩展单元)
功能描述
EXT模块根据输入的16位立即数和5位的偏移量,在控制模块得出的扩展信号EXTOp 控制下,对立即数进行相应的扩展,得到32位立即数。如果EXTOp为00,对立即数进行 0 扩展,高16位补0,;如果EXTOp为01,对立即数进行符号扩展,高16位补第15位的符号位,如 ori、andi 等指令使用零拓展,而 addi、subi 等指令使用符号扩展。
模块接口
| 信号名 | 方向 | 描述 |
|---|---|---|
| Imm16 | 输入 | 需要扩展的16位立即数 |
| EXTOp | 输入 | 控制信号,选择是零扩展还是符号扩展 |
| Imm32 | 输出 | 扩展后的32位立即数 |
代码和描述说明
编写符号扩展模块EXT如下:

图3-12
MUX(多路选择器)
功能描述
对输入的多个信号进行选择。
模块接口
设N为选择器的选择个数。
| 信号名 | 方向 | 描述 |
|---|---|---|
| di(0<=i<N), | 输入 | 需要选择的信号 |
| s | 输入 | 选择控制信号 |
| y | 输出 | 选择输出的信号 |
代码和描述说明
二路选择器:

图3-13
四路选择器:

图3-14
八路选择器:

图3-15
十六路选择器:

图3-16
dm(数据存储器)
功能描述
存储数据。定义128个32位数据内存。取输入地址处的数据,赋值给dout;如果是写操作,在DMWR为1的情况下,根据输入地址addr[8:2]选择需要写入的地址,将数据din写入对应地址的内存中。
模块接口
| 信号名 | 方向 | 描述 |
|---|---|---|
| clk | 输入 | 时钟周期 |
| DMWr | 输入 | 写控制信号 |
| addr | 输入 | 地址 |
| din | 输入 | 输入数据 |
| dout | 输出 | 输出数据 |
代码和描述说明

图3-17
im(指令存储器)
功能描述
im模块用于程序指令的读取。定义128个32位指令内存。输入需取指令的地址,将内存中相应地址处的指令赋值给dout输出。
模块接口
| 信号名 | 方向 | 描述 |
|---|---|---|
| addr | 输入 | ROM地址 |
| dout | 输出 | 将要执行的指令 |
代码和描述说明

图3-18
MCCPU
功能描述
组合PC,NPC,RF,ALU,ctrl,EXT,mux等逻辑单元,由PC在存储器中取址,运算的结果如果要写入存储器的话就送入存储器。各个器件之间如果有多个信号就加多路选择器,形成CPU数据通路。
模块接口
| 信号名 | 方向 | 描述 |
|---|---|---|
| clk | 输入 | 时钟周期 |
| rst | 输入 | 清零信号 |
| reg_sel[4:0] | 输入 | 选择寄存器 |
| instr[31:0] | 输入 | 运行的指令 |
| readdata[31:0] | 输入 | 数据存储器中的数据 |
| PC[31:0] | 输出 | 当前指令的地址 |
| MemWrite | 输出 | 写存储器控制信号 |
| writedata[31:0] | 输出 | 写入数据存储器的数据 |
| reg_data[31:0] | 输出 | 写入寄存器的数据 |
| adr[31:0] | 输出 | 内存地址 |
代码和描述说明

图3-19

图3-20

图3-21
MCCOMP
功能描述
在sccpu组合了各个模块的基础上,加入了数据存储器DM和指令存储器IM,构成完整的单周期cpu。
模块接口
| 信号名 | 方向 | 描述 |
|---|---|---|
| clk | 输入 | 时钟控制信号 |
| rstn | 输入 | 异步上升沿复位信号,wire rst=~rstn |
| reg_sel[4:0] | 输入 | 寄存器选择信号 |
| reg_data[31:0] | 输出 | 寄存器数据 |
代码和描述说明

图3-22
ctrl_encode_def
控制信号编码定义文件如下:

图3-23
flopenr
flopenr和flopr这两个模块用于切割数据通路,将大组合逻辑切分为若干个小组合逻辑,
大延迟变为多个分段。

图3-24
flopr
和flopenr一起用于切割数据通路,将大组合逻辑切分为若干个小组合逻辑,
大延迟变为多个分段。

图3-25
多周期CPU测试及结果分析
仿真代码及分析
仿真测试文件(testbench)

图3-26
用于测试的汇编代码

图3-27
对应的机器指令

图3-28
仿真测试结果
波形图:

图3-29
存储器内的结果:

图3-30
下载测试代码并分析
coe 文件通过先前生成的Hex文件编辑而成。
文件开头加上:
• memory_initialization_radix=16;
• memory_initialization_vector=
然后从第三行开始每行其余各行加, 最后一行加;
生成的 coe 文件如下:

图3-31
下载测试结果

图3-32
生成 IP 核,添加xdc约束文件,生成二进制流文件,加载到Nexys4 DDR开发板上,得到以下学号排序结果:
排序前:

图3-33
排序后:

图3-34
实验心得
在这样一个CPU设计实验中,我关于计算机架构和硬件开发有了更深的理解,同时也提高了编程和测试的综合能力。
在实验中,我首先学习了Mars的使用。老师的教程很清晰,我很顺利地完成了下载和使用。不过在后来,关于Mars,或者说是关于用来检验cpu设计的汇编语言方面,我也遇到了一些问题。首先是我的代码能力不太够,在阅读学号排序的代码时,我就非常头疼,盯着代码一行一行地看,但还是会在loop中迷失方向,捋不清楚里面的关系,更不敢自己尝试去写代码,但通过同学的帮助,我明白了代码的含义,也尝试自己去写,不过因为课业繁忙,没有在最后一次课前完成从而能在开发板上进行测试,还有些遗憾,但在这个过程中,我发现自己阅读代码的能力得到了很大的提升,这也是一个非常令人高兴的收获。
其次,我进行了对Verilog语言的学习和cpu相关知识的复习。由于疫情原因,eda的课程排在了这学期,所以在计组实验的时候我没有学习Verilog的相关知识,并且构建cpu的很多细节在记忆里已经模糊了,因此在前两周的时间里,我主要是在学习理解Verilog语言的语法及单周期CPU构建的主要思想和细节。期间,通过在网上查阅资料、请教朋友等方式,我能使用Verilog语言进行独立编程。也能描绘出单周期cpu的运行方式,为后续的代码撰写与修改打下了牢固的基础。
之后,我终于有底气开始添加指令,在这个过程中,由于原工程已经实现了若干指令,我只需要向其中添加新指令即可,于是我在理解各个模块的作用和连接关系后,根据 sccpu.v/mccpu.v文件跟踪每条指令的实现过程,并加入我想要实现的指令,在这个过程中,我又对整个CPU的工作流程有了更深的认识。
这个过程也不是一帆风顺的,我受到最大的教训是,在添加指令时一定要认真仔细,不能为了追求速度而忽视质量,也不能为了完美而过于焦虑。在第一次尝试添加指令时,我一次添加了3条指令,虽然通过了编译,但在测试时,却出现了程序执行到这些指令时输出XXXXXXXX的错误信息,但由于我的指令添加过多,无法确定哪里出了问题,在反复检查无果后,我不得不重新开始实验,而在第二次添加指令时,我又因为太过小心,整个过程心情非常焦虑,思绪反而不清醒,加了2条指令后同样也失败了。
然后,在实验的过程中,我也深刻体会到了沟通学习的重要性。我习惯于自己查找资料来解决问题,但当我反反复复检查不出错误的时候,我的朋友却能很快察觉到问题所在,很多时候,其实就是例如寄存器的位数之类的小问题,我自己看得多了,反而会忽略这样的细节,但“旁观者清”,有时候来自另一个视角的建议,能给我们带来极大的帮助。
单周期CPU的实现共计花费了两个星期,不过在这个过程中的积累,使我对于verilog语言的写法更加熟悉,使用更加熟练,在测试的时候,也能更加得心应手地处理波形,从而更快地debug,历经一个礼拜,我也完成了多周期cpu的实现。在第二部分中,我不仅锻炼了代码编写和测试能力,而且也学到了如何在代码中使用状态机来管理CPU的状态。
最后,通过vivado在开发板上进行测试的时候,因为阅读教程不够仔细,也犯了一些错误,我对此切身体会到了,学一样新东西的时候要保持平和冷静的心态去慢慢仔细地操作,欲速则不达。
总而言之,这次CPU设计实验让我对计算机架构有了更深入的理解,同时也提高了我的硬件设计能力和代码开发能力。我相信这些知识和技能在我的未来职业生涯中将非常有用,并有助于我更好地进行后续的学习。
参考文献
[1] 李亚民.计算机原理与设计——Verilog HDL版[B].清华大学出版社,2011年6月
[2] 蔡觉平.Verilog HDL 数字集成电路设计原理和应用 [B B]. 西安电子科技大学出版社,2016 年8月
[3] 潘松、黄继业–EDA 技术实用教程 2010 年 6 月
[4] Ace Cheney.Verilog单周期CPU设计(超详细).
https://blog.csdn.net/Accelerato/article/details/86546751 ,2019年1月19日



