支持27条指令的单周期架构设计
实验任务
在前面实现的23条指令单周期数据通路的基础上,扩充以下4条指令,实现单周期架构的27条指令。
(1)U型格式的LUI指令;
(2)U型格式的AUIPC指令;
(3)J型格式的JAL指令;
(4)I型格式的JALR指令。
本实验首先在远程虚拟实验平台调试验证设计,成功后再移植到线下实验板实速运行。
从开源项目托管网站下载实验材料,下载方法见下载实验材料。 实验材料的文件组织如下。
📂 riscv 📂 RV-00 📂 project_de2-115 (1) 📂 RV-27 📁 diagram (2) 📂 real-speed (3) 📁 testbench (4)
1 | 用于线下实验板的文件 |
2 | 用于设计虚拟面板的文件 |
3 | 用于实速运行测试的文件 |
4 | 用于仿真的testbench文件 |
任务一、微架构设计与验证
1. 数据通路设计
通过前面实验的逐步扩展数据通路,已经理解了数据通路与指令之间的关系。本实验不再给定数据通路,而是将设计数据通路作为一个主要的实验任务。
通过查阅RISC-V手册了解指令功能,综合分析后确定最优的数据通路,使其简洁、高效。修改后的数据通路不仅能执行新增加的4条指令,也应能执行之前已经完成的23条指令。
设计步骤如下。
(1)设计数据通路;
(2)列出控制信号的输入输出真值表;
(3)编写代码实现。
2. 虚拟面板设计
根据自己设计的数据通路,绘图作为虚拟面板的背景;添加必要的观察数据和信号,同时在设计代码中添加相应的观察变量。 所需实验材料如下所示。
📂 riscv 📂 RV-27 📁 diagram 📄 RV-23.drawio 📄 RV-27_golden.jvp
(1)绘制虚拟面板背景图
根据自己设计的数据通路,绘图作为虚拟面板的背景。可使用远程实验验证流程中推荐的开源软件diagrams.net(又名draw.io)。
为了减少绘制背景图的工作量,实验材料diagram文件夹下提供了前面23条指令的单周期数据通路图RV-23.drawio,可在此基础上修改。
绘制完成的背景图需导出为PNG格式,具体方法见远程实验验证流程中的相关说明。 如果PNG图片在虚拟面板上的显示尺寸过大或过小,可以改变dpi重新导出。
(2)打开虚拟面板
和逻辑电路实验不同,CPU实验目前尚不支持从空白面板交互式地设计虚拟面板,可以在一个已有面板的基础上进行交互式修改。 实验材料提供了一个基本的虚拟面板文件,位于diagram文件夹下,在实验系统中打开其中的RV-27_golden.jvp。
(3)导入背景图
用上面自己绘制的背景图替换虚拟面板的背景图。操作方法可参考远程实验验证流程中的相关说明。
替换背景图通常会引起信息流显示(绿线)的错位,实验平台目前还没有交互式定义信息流绿线的功能。为避免错位后影响外观,实验材料中的RV-27_golden.jvp未包含信息流绿线的定义。如果希望自己的虚拟面板也能够显示信息流绿线,需要手工修改jvp文件,可在硬件设计调试成功之后向老师咨询。 |
(4)添加数据框和信号框
添加必要的观察数据和信号,同时在设计代码中添加相应的观察变量。关于代码与面板元件的对应方法,可参阅实现ADDI指令。
RV-27_golden.jvp面板中已经包含了一些数据框和信号框,与其对应的ws和wd结构体定义如下。
//送给调试器的观察信号,需要与虚拟面板的信号框相对应 struct packed{ logic WS1 ; //MemWrite logic WS0 ; //RegWrite }ws; //送入扫描链的观察数据,需要与虚拟面板的数据框相对应 struct packed{ logic [31:0] WD3; //regReadData1 logic [4:0] WD2; //ra1 logic [31:0] WD1; //instruction logic [31:0] WD0; //pc }wd;
设计代码中必须包含上面的观察变量,它们将用于电路测试。除此之外,可追加需要的观察变量;提交时无需删除增加的观察变量,它们不会影响电路测试。 |
3. 验证
编写测试程序,在实验平台运行,分析运行结果是否正确。测试程序要求如下。
(1)包含本实验新增的4条指令。
(2)转移指令需测试向下转移和向上转移两种情况。
(3)jalr指令需测试rd和rs1的寄存器号相同的情况,以及计算出的转移地址不为偶数的情况。
(4)测试新修改的数据通路能否正确执行前面实验完成的分支指令、访存指令和运算指令。
在访存指令的测试程序中,使用了如下两条指令设置数据存储器的基地址。 addi x3, x0, 0x100 slli x3, x3, 20 现在实现了lui指令,用一条指令就可以完成。如下。 lui x3, 0x10000 |
任务二、线下实验板实速运行
实速运行是指脱离远程虚拟实验平台的调试环境,在线下实验板运行测试程序,通过板载的拨动开关、LED指示灯、七段数码管等输入程序运行需要的数据、输出运行结果。
本课程支持的线下实验板有基于Intel/Altera FPGA的DE2-115和基于AMD/Xilinx FPGA的Nexys4DDR,其他实验板需学员自己适配。下面以DE2-115为例说明移植过程。
1. 创建工程
Quartus支持在同一个工程中创建多个“修订”(Revision),并且可以复制一个现有的Revision设置,在此基础上创建新的Revision。 所需实验材料如下。
📂 riscv 📂 RV-00 📁 project_de2-115 📄 create_revision_rv_DE2115.tcl 📄 DE2-115.sdc 📄 DE2-115.tcl 📄 RV_DE2_115.v 📄 用TCL文件自动创建revision.md
创建Revision的具体方法见线上线下混合模式的Quartus工程设置,不同之处如下。
(1)基础工程不同
本实验已有工程是RISC-V工程,并且已有的Rivision名称应为 RV_Pocket,顶层文件为 RV_Pocket.v。
(2)顶层文件不同
用于DE2-115开发板的CPU实验的顶层文件为 RV_DE2_115.v。
(3)脚本文件不同
本实验所用脚本文件为 create_revision_rv_DE2115.tcl。
也可参阅实验材料中包含的文档,该文档具体说明了用TCL脚本自动创建Revision的步骤。 |
2. 设计代码移植
实速运行脱离调试环境独立运行,设计代码的移植主要是屏蔽片上调试器(On-chip debuger,OCD)模块,将原来经过OCD连接的信号改为直接连接;由于指令存储器原来需要OCD写入,所以实际是RAM,屏蔽OCD之后需要改为ROM,建议用IP核实现指令存储器的ROM模块。此外,存储器的时钟也是一个需要考虑的问题。
(1)存储器的类型和时钟
通过前面的存储器实验我们已经知道,FPGA内部的存储器有两种类型:同步读和异步读。“同步读”使用FPGA内部的专用RAM资源,不占用逻辑资源,但是锁存地址占用了一个周期,所以需要两个时钟周期才能读出数据。而“异步读”不锁存地址,可以在给出地址的同时读出数据。理论上,单周期CPU应该采用“异步读”存储器与其配合,才能一个周期执行一条指令。但是在调试环境下,OCD采用“同步读”存储器与其配合,为了能够在“单周期”内执行完一条指令,存储器的时钟采用了连续的10MHz时钟,而CPU时钟由OCD提供,仅在用户通过实验软件执行指令时产生一个脉冲;简单来说,存储器采用了比CPU更高频率的时钟,在一个CPU时钟周期内有若干次存储器读出,从而保证了“单周期”执行完一条指令。
实速运行时,CPU时钟不再由OCD提供,而是采用10MHz时钟,这时存储器模块就有两种选择。如果仍然采用“同步读”存储器,存储器时钟就要采用更高频率的时钟,以满足“单周期”的要求;或者采用“异步读”存储器,存储器时钟采用与CPU相同的10MHz时钟。
前面存储器实验中已经学习了同步读和异步读RAM的Verilog描述方法,而ROM用Verilog描述只能生成异步ROM,同步ROM必须使用IP核[1]。IP核的生成方法见附录。
(2)SoC模块的移植
首先替换OCD。将JuTAG_CPU模块的实例化注释掉,添加以下代码,替换原来由JuTAG模块产生的信号,以及经过JuTAG_CPU内部连接的信号。
assign cpuReset = RESET; assign cpuClk = CLK; assign memWR = cpuWR; assign memRD = cpuRD; assign memAB = cpuAB; assign memWriteData = cpuWriteData; assign bsc_sw = vSWITCH; assign bsc_btn = vBUTTON; assign JTDO = JTDI;
然后添加指令存储器模块。注意时钟的连接,如果采用同步ROM,连接SoC模块CLOCK端口提供的50MHz时钟;如果采用异步ROM,连接和CPU模块相同的10MHz CLK时钟。指令存储器的容量设计为16K×32(64KB),因为指令存储器按字编址,而CPU输出的是字节地址,所以要将CPU输出的地址右移两位送给指令存储器。
最后还要修改数据存储器的实例化。前面实验使用的数据存储器是同步读RAM,时钟连接的是10MHz CLK时钟,现在要将它连接到CLOCK端口提供的50MHz时钟。另外,数据存储器的容量也需要加大,以满足后面编写的应用程序和测试程序的需要;建议数据存储器的容量也设置为16K×32(64KB),至少设置为1K×32(4KB),可在实例化时通过参数进行设置。目前设计的CPU尚未支持存储器的字节访问,数据存储器仍然是按字编址,所以地址也要右移两位。
3. 应用程序设计和实速运行测试
遵循从简单到复杂的原则,编写一些包含输入输出的RISC-V汇编语言程序,用来测试移植是否成功以及所设计的CPU能否正确运行。下面通过一个例子说明过程。
(1)编写汇编语言程序并用汇编器翻译为机器指令
例 1是一个简单的测试程序,读取开关状态输出到LED指示灯和数码管显示。
00000000: 80000413 addi s0, x0, -2048 (1) 00000004: 00042903 LOOP: lw s2, 0x00(s0) (2) 00000008: 01242823 sw s2, 0x10(s0) (3) 0000000C: 01042983 lw s3, 0x10(s0) (4) 00000010: 03342023 sw s3, 0x20(s0) (5) 00000014: FF1FF0EF jal LOOP
1 | IO基地址0xFFFFF800 |
2 | 读开关 |
3 | 写LED输出寄存器 |
4 | 读LED输出寄存器 |
5 | 写数码管输出寄存器 |
(2)将机器指令转换为ROM初始化文件
如果指令存储器采用的是IP核,初始化文件格式有两种:Memory Initialization File(.mif)格式和Hexadecimal Intel-Format File(.hex)格式,详见附录介绍。上述程序的mif文件内容如下,该文件在实验材料的real-speed目录下。
WIDTH = 32; DEPTH = 16384; ADDRESS_RADIX = HEX; DATA_RADIX = HEX; CONTENT BEGIN 00000000:80000413; 00000001:00042903; 00000002:01242823; 00000003:01042983; 00000004:03342023; 00000005:FF1FF0EF; END;
注意,mif文件中的地址是字地址,是例 1程序清单中的指令地址除4。
如果指令存储器是写代码实现的异步ROM,将指令编码写入一文本文件作为存储器的初始化文件,文件格式在存储器实验学习过。
当程序比较长时,如果从例 1格式的反汇编清单中逐个复制机器指令到mif文件,不但耗时还容易出错。很多优秀的代码编辑器(如VS CODE)都有列选择和复制功能,能够极大地提高效率。 |
上述文件在实验材料中提供,如下所示。
📂 riscv 📂 RV-27 📂 real-speed 📄 IM.txt 📄 test-io_instr.mif 📄 test-io.dump
(3)编译并运行
在Quartus的存储器IP核设置中,指定上述mif文件作为初始内容文件,编译工程,将电路文件加载到DE2-115开发板的FPGA中。有关Quartus软件为DE2-115加载电路的操作说明,见“实验工具和环境”中的“本地实验验证流程-JULAB版”。
从例 1可以看出,该程序的功能是读出拨动开关的值,输出到LED指示灯和数码管进行显示。拨动开关,检查指示灯和数码管的显示是否正确。
指示灯的连接顺序和逻辑电路实验不同。
在逻辑电路实验的DE2_115_TOP.v中,LED[0]连接到DE2-115开发板的 |
例 1涉及的指令很少,仅仅做了最基本的输入输出测试,通过以后,还要再编写一些更复杂的程序,更全面地测试所设计的CPU能否正常工作。可以对前面汇编语言实验做过的程序稍加修改,如斐波那契数列计算程序,可以通过开关输入循环次数,数码管显示计算的结果;再如位操作的程序,在汇编语言实验中是通过控制台显示结果,现在可以改用LED显示输出结果。
4. 实速运行的调试
实速运行出现问题,如何排查呢?假如上面举例的简单程序,加载到FPGA运行时拨动开关没反应,数码管显示全0,可能会让人一筹莫展。其实这类看起来很严重的问题,往往是一些小的疏忽造成的,比如在移植过程中,某些信号连接错了。
如果加载FPGA后没反应,首先检查SW[17]的位置。由于DE2-115开发板没有专用的复位按键,CPU实验框架临时将SW[17](最左边一个开关)用作复位;当SW[17]拨向上方时复位,拨向下方时正常运行。之所以没有用按键作为复位按钮,是考虑以后可以将按键扩展为外设。如果在以后的设计中,SW[17]也要作为外设,需要修改RV_DE2_115.v中的下面这行代码。 wire rst_btn = SW[17]; 改为 wire rst_btn = 1'b0; |
通过仔细检查代码,有些错误可以被发现,但还有些错误可能隐藏的比较深,没那么容易看出来。即使解决了小疏忽,基本输入输出正常了,但是更复杂的测试程序出现问题,就难以通过看代码找出问题。
在计数器实验中曾经介绍过仿真工具,对于像CPU这样的复杂电路来说,仿真是一种比较高效的调试方法,也是工业界通行的验证方法。例 2给出了一个testbench代码,可以用它对自己的设计进行仿真。仿真工具的使用方法,可参阅计数器实验中的实验指导ModelSim仿真入门,更多资料可阅读《Intel Quartus Prime用户指南: 第三方仿真》。
`timescale 1ns / 100ps module tb_soc(); reg rst; reg fpga_clk; reg sys_clk; SoC uut( .RESET(rst), .CLOCK(fpga_clk), .CLK(sys_clk) ); initial begin fpga_clk = 0; sys_clk = 0; rst = 1; #10 rst = 0; end initial begin forever #10 fpga_clk = ~fpga_clk; //50Mhz end initial begin forever #50 sys_clk = ~sys_clk; //10Mhz end endmodule
注:本文件在实验材料的testbench文件夹中提供。
利用仿真通常可以查出大部分设计中存在的问题,但是仿真毕竟不是在真实FPGA上运行的结果,如果仿真没有发现错误,实际运行又不正确,有没有其他调试工具呢?Quartus 提供了Signal Tap片上逻辑分析仪,可以观察FPGA芯片上的实际运行结果。Signal Tap的具体用法请参阅《Intel Quartus Prime用户指南: 调试工具》。