前言:本文均为作者原创,内容均来自本人的毕业设计。未经授权严禁转载、使用。里面的插图和表格均为作者本人制作,如需转载请联系我并标注引用参考。分享仅供大家学习和交流。
功能仿真,主要检查代码中的语法错误以及代码行为的正确性,以及对源代码进行逻辑功能的验证。完成功能仿真之前,需要要先建立testbench,仿真结果将会以波形图或者文本文件形式给出相关结果报告。通过功能仿真可以及时发现设计中存在的错误,一般是不考虑器件延时和布线延时的理想情况下,所以这个阶段的仿真也可以做到与器件无关。
本设计功能仿真所使用的数字仿真工具是Iverilog 12.0。在Ubuntu20.4的虚拟机系统环境下,首先安装仿真环境所需Linux工具包,如device-tree-compiler(设备树内核编译工具)、autotools-dev(交叉工具链编译)等。第二,安装RISC-V GNU工作链 (GNU Toolchain) ,是一组Linux操作系统内部的开发和编程工具,包括GCC、GNU Binutils和GNU make等,这些工具构成了一个完整的编程环境。第三,下载E203内核的源码并使用Iverilog12.0编译并综合网表,可以测试指令进行波形查看。
本设计的功能仿真利用了RISC-V官方的riscv-tests,能够检测指令集中的指令在处理器中运行结果是否成功,判断处理器的设计是否可以通过指令集架构定义的测试程序,仿真结果将会以波形图或者文本文件形式给出相关结果报告。所使用的数字仿真工具是Iverilog 12.0,具体实现步骤是做一个整体的testcase(测试用例),然后将处理器电路代码配置到Iverilog的仿真环境中,批量化运行所有准备好的测试用例都输入测试。而指令集功能仿真过程和测试结果,全部记录到日志文件(.log)中。
对处理器核进行功能验证,包含若干个测试子程序用于测试RISC-V处理器的指令是否正确实现,并且每个子程序都带有自检代码,可以检测运行的正确与否。具体测试步骤如图3-2所示:
测试程序的生成:下载RISC-V官网的riscv-tests测试文件。首先用宏定义文件test_macros.h和riscv_test.h完成指令集中各种指令测试所需汇编代码(例如测试ADD指令add.s)以及其特定的链接脚本。链接脚本一般用来将目标汇编代码的文件等链接在一起,可以确认汇编代码在处理器指令的起始地址和操作内存大小。链接脚本把数据段、地址以及堆栈段等分配在系统内存中不同的地址空间。
对汇编代码进行链接:将汇编代码进行汇编生成ELF格式的可重定向文件,此文件不能直接运行,需要通过链接脚本对生成的文件进行链接,在RISC-V编译工具gnu-mcu-eclipse-nono-gcc中调用Binutils中的链接器来链接包含启动代码的目标文件(.ELF),此过程需要依赖库文件、引导程序以及链接脚本。
生成可被Veilog的ReadMenh函数可读的文件xxx.veilog:使用Binutils中的pbject-copy对ELF目标文件进行反汇编生成包含指令和数据的dump文件和机器可读的二进制文件(xxx.verilog),如图3-3所示。
对处理器设计的verilog的RTL代码进行编译,选择Iverilog仿真器对verilog编写的RTL代码进行编译。对编写的指令测试代码xxx.verilog加载进去CPU中进行运行,因为在链接阶段己经将测试代码放在ITCM中,使得程序直接在ITCM中运行,并将运行结果打印在日志文件(xxx.log)中,输出批量化验证结果如图3-4所示。
riscv-tests是能够检测指令集中的指令在处理器中运行结果是否成功,判断处理器的设计是否可以通过指令集架构定义的测试程序。在运行批量化测试结束后可以判断出指令是否执行成功:在测试程序中设定了对检测目标寄存器的判断,如果符合这意味着指令测试完成,在日志文件(xxx.log)文件中打印”PASS”字样,否则打印”FALL”。第一列PASS表示指令成功通过测试。RV32I的所有指令测试结果如表3-1所示。
测试指令 | 指令用途 | 结果 | 测试指令 | 指令用途 | 结果 |
---|---|---|---|---|---|
add | 加操作 | 通过 | ld | 双字加载,读取八个字节 | 通过 |
addi | 先扩展的加操作 | 通过 | lh | 两字节有符号位的扩展指令 | 通过 |
and | 与操作 | 通过 | lhu | 两字节无符号位的扩展指令 | 通过 |
andi | 符号位扩展的立即数与操作 | 通过 | lui | 高位立即数加载 | 通过 |
auipc | PC加立即数指令 | 通过 | lw | 字加载并进行位扩展 | 通过 |
beq | 相等跳转指令 | 通过 | or | 或操作 | 通过 |
bge | 有符号大于跳转指令 | 通过 | ori | 先扩展的或操作 | 通过 |
bgeu | 无符号大于跳转指令 | 通过 | sb | rs2低8位存储到rs1+imm | 通过 |
blt | 有符号小于跳转指令 | 通过 | sh | rs2低16位存储到rs1+imm | 通过 |
bltu | 无符号小于跳转指令 | 通过 | sw | rs2一次1个字存到rs1+imm | 通过 |
bne | 数据跳转指令 | 通过 | sll | rs1逻辑左移rs2位 | 通过 |
csrrc | 读后清除控制状态寄存器 | 通过 | slli | rs1逻辑左移有符号imm位 | 通过 |
csrrci | 立即数读后设置控制状态寄存器 | 通过 | slt | 置位操作指令 | 通过 |
csrrs | 读后置位控制状态寄存器 | 通过 | slti | 置位指定操作指令 | 通过 |
csrw | 写控制状态寄存器指令 | 通过 | sltu | 无符号置位操作指令 | 通过 |
csrwi | 立即数写控制状态寄存器指令 | 通过 | sltiu | 无符号置位指定操作指令 | 通过 |
ebreak | 环境断点 | 通过 | sra | rs1算数右移imm位 | 通过 |
ecall | 环境调用 | 通过 | srai | rs1逻辑右移imm位 | 通过 |
fence | 同步内存和I/O | 通过 | srl | rs1算数右移rs2位 | 通过 |
fence_i | 同步指令数据流指令 | 通过 | srli | rs1逻辑右移rs2位 | 通过 |
jal | 无条件跳转指令 | 通过 | sub | 减操作 | 通过 |
jalr | 无条件指定跳转指令 | 通过 | xor | 异或操作 | 通过 |
lb | 有符号位的扩展指令 | 通过 | xori | 先扩展的加操作 | 通过 |
lbu | 无符号位的扩展指令 | 通过 |
以add指令在处理器中“流动”的过程,通过add加法运算的过程观察处理器各级流水线是否正常工作。对硬件电路各部分进行波形检测,完成verilog编写的RTL级电路各模块的功能验证,基于对指令测试代码中的反汇编指令运行情况进行分析。先验证add指令可以正常工作如下图所示打印“PASS”,再输出波形图进行观察。
取出add指令的过程在IFU取指单元中进行,该阶段主要完成PC地址的产生和读出指令编码信息,PC值根据指令类型完成自增。对应的汇编指令解析:把mhartid的值读入到a0实现取指。
16进制指令 对应汇编指令
f1402573 csrr a0,mhartid
图3-6为取指阶段的仿真波形,可以看到在测试中第一个时钟周期上,ifetch给ifu2biu模块发送的ifu_req_pc信号32位PC值800000134,ifu2biu模块根据PC值向biu总线发送ifu2biu_icb_cmd_addr信号所提取指令的PC值800000134,通过biu总线返回ifetch到ifu_rsp_instr信号提取了外部指令F1402573。IFU取指阶段的输出信号有三个:ifu_o_ir是当前从biu中提取的32位指令数据F1402573,ifu_o_pc是所提取指令的PC地址,inspect_pc是所提取的32位指令使得PC值+4。经测试能够正常完成第二个时钟周期PC地址自增和IFU单元取指令。
该阶段根据指令的低7位的操作码opcode[6:0]翻译出指令类型,然后通过3位寄存器索引cmt_dcause[2:0]从通用寄存器中读取操作数,对应的汇编指令解析:条件分支跳转指令若a0不为零则跳转8000013e。
16进制指令 对应汇编指令
e101 bnez a0,8000013e
4181 li gp,0
00000297 auipc t0,0x0
图3-7为译码阶段的仿真结果,可以从图中看出,在第一个周期,ifu_rsp_instr是e101,意味着下一周期需判断是否IFU有输入;在第二个周期,dec_bjp为高电平,读出立即数dec_imm并跳转。条件分支跳转指令是16位指令下一周期IFU提取指令部分完成PC+2,ifu_req_pc从80000013C加2等于80000013E。在第三个周期,看见ifu_rsp_instr[31:0]由02974181变为00000297,与反汇编指令一致。
主要完成对译码阶段后的操作数进行相关的运算,并将运算结果输出,如果是条件跳转需要先进行跳转判断,最后需要判断流水线内部是否需要对取指令阶段的PC生成模块进行流水冲刷[19]。先要打开寄存器编写模式:将8(二进制0111,经查询CSRs寄存器的状态0111代表使能)赋值给a0, 再给mstatus用于保存全局中断使能或其他的CSRs寄存器状态。然后打开mie和mtcev寄存器。操作CSR寄存器向寄存器写入数据。
16进制指令 对应汇编指令
ec228293 addi t0,t0,-318 <trap_vector>
4521 li a0,8
30052073 csrs mstatus,a0
fff00513 li a0,-1
30452073 csrs mie,a0
30529073 csrw mtvec,t0
执行阶段的仿真波形图如图3-8所示。从图中的波形可以看出执行阶段的将译码得到的操作数0x80000142与立即数0xFFFFFECC进行add指令加法运算操作,0x80000000 + 0x00000004结果为alu_req_alu_res[31:0]信号为0x80000004,与反汇编程序中的addi指令一致[11]。
下面的表述更清晰。为了查重,我就换了个表达方式。
图3-8为执行阶段的仿真波形图,从图中的波形可以看出此阶段的操作将译码阶段输出的操作数0x80000142与立即数0xFFFFFEC2进行加法运算,结果alu_req_alu_res[31:0]信号为0x80000004(溢出保留地位有效位),与反汇编程序中的addi指令一致。
经测试,流水线的取add指令、译码add指令、执行add指令的波形输出符合预期效果,预期产生的16位进制指令可以从波形图中找到对应的指令。