news 2026/8/25 19:37:00

验证自己的处理器——基于riscv-tests

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
验证自己的处理器——基于riscv-tests

在使用riscv-tests之前,我们需要安装riscv-tool-chain 编译链,并将 RISCV 环境变量设置为 RISC-V 工具 install 路径。可以参考之前的文章:ubuntu20.04 riscv-gnu-toolchain编译链极简安装_ubuntu安装risv-gun-tools-CSDN博客

安装好编译链后,构建最新的riscv-tests:

$ git clone https://github.com/riscv/riscv-tests $ cd riscv-tests $ git submodule update --init --recursive $ autoconf $ ./configure --prefix=$RISCV/target $ make $ make install

riscv-tests的链接脚本link.ld在riscv-tests/env/p文件夹中,可以根据自己的处理器内存映射进行修改,不修改则默认连续。

在/riscv-tests/isa路径下找到Makefile文件,由于处理器目前只支持RV32I指令集,因此对Makefile文件进行了修改:

#======================================================================= # Makefile for riscv-tests/isa #----------------------------------------------------------------------- XLEN ?= 64 src_dir := . include $(src_dir)/rv64ui/Makefrag include $(src_dir)/rv32ui/Makefrag default: all #-------------------------------------------------------------------- # Build rules #-------------------------------------------------------------------- RISCV_PREFIX ?= riscv64-unknown-elf- RISCV_GCC ?= $(RISCV_PREFIX)gcc RISCV_GCC_OPTS ?= -static -mcmodel=medany -fvisibility=hidden -nostdlib -nostartfiles RISCV_OBJDUMP ?= $(RISCV_PREFIX)objdump --disassemble-all --disassemble-zeroes --section=.text --section=.text.startup --section=.text.init --section=.data RISCV_SIM ?= spike vpath %.S $(src_dir) #------------------------------------------------------------ # Build assembly tests %.dump: % $(RISCV_OBJDUMP) $< > $@ %.out: % $(RISCV_SIM) --isa=rv64gch_ziccid_zfh_zicboz_svnapot_zicntr_zba_zbb_zbc_zbs --misaligned $< 2> $@ %.out32: % $(RISCV_SIM) --isa=rv32gc_ziccid_zfh_zicboz_svnapot_zicntr_zba_zbb_zbc_zbs --misaligned $< 2> $@ define compile_template $$($(1)_p_tests): $(1)-p-%: $(1)/%.S $$(RISCV_GCC) $(2) $$(RISCV_GCC_OPTS) -I$(src_dir)/../env/p -I$(src_dir)/macros/scalar -T$(src_dir)/../env/p/link.ld $$< -o $$@ $(1)_tests += $$($(1)_p_tests) $$($(1)_v_tests): $(1)-v-%: $(1)/%.S $$(RISCV_GCC) $(2) $$(RISCV_GCC_OPTS) -DENTROPY=0x$$(shell echo \$$@ | md5sum | cut -c 1-7) -std=gnu99 -O2 -I$(src_dir)/../env/v -I$(src_dir)/macros/scalar -T$(src_dir)/../env/v/link.ld $(src_dir)/../env/v/entry.S $(src_dir)/../env/v/*.c $$< -o $$@ $(1)_tests += $$($(1)_v_tests) $(1)_tests_dump = $$(addsuffix .dump, $$($(1)_tests)) $(1): $$($(1)_tests_dump) .PHONY: $(1) COMPILER_SUPPORTS_$(1) := $$(shell $$(RISCV_GCC) $(2) -c -x c /dev/null -o /dev/null 2> /dev/null; echo $$$$?) ifeq ($$(COMPILER_SUPPORTS_$(1)),0) tests += $$($(1)_tests) endif endef $(eval $(call compile_template,rv32ui,-march=rv32i -mabi=ilp32)) tests_dump = $(addsuffix .dump, $(tests)) tests_hex = $(addsuffix .hex, $(tests)) tests_out = $(addsuffix .out, $(filter rv64%,$(tests))) tests32_out = $(addsuffix .out32, $(filter rv32%,$(tests))) run: $(tests_out) $(tests32_out) junk += $(tests) $(tests_dump) $(tests_hex) $(tests_out) $(tests32_out) #------------------------------------------------------------ # Default all: $(tests_dump) #------------------------------------------------------------ # Clean up clean: rm -rf $(junk)

保存后退出,输入make命令,将在当前路径下生成rv32i指令集中指令的.hex文件和.dump文件,如图所示:

将它们分别保存到两个不同的文件夹中,方便后续使用:

进入hex文件夹,创建hex2bin.py文件:

#!/usr/bin/env python3 import os import subprocess import glob def batch_generate_bin(): # 设置工具链路径 objcopy = "riscv64-unknown-elf-objcopy" # 检查 objcopy 是否可用 try: subprocess.run([objcopy, "--version"], capture_output=True, check=True) except (subprocess.CalledProcessError, FileNotFoundError): print(f"错误: 找不到 {objcopy},请检查工具链路径") return False # 创建输出目录 - 使用 ../bin output_dir = "../bin" os.makedirs(output_dir, exist_ok=True) # 查找所有 rv32ui-p-* 文件(无后缀) test_files = [f for f in glob.glob("rv32ui-p-*") if os.path.isfile(f)] # 过滤掉可能误匹配的文件 test_files = [f for f in test_files if not f.endswith(('.bin', '.dump', '.S', '.s', '.c'))] if not test_files: print("未找到任何 rv32ui-p-* 测试文件") return False success_count = 0 failed_files = [] print(f"找到 {len(test_files)} 个测试文件") for test_file in test_files: # 生成 bin 文件名 - 使用 ../bin 目录 bin_file = os.path.join(output_dir, f"{test_file}.bin") try: # 执行转换命令 result = subprocess.run( [objcopy, "-O", "binary", test_file, bin_file], capture_output=True, text=True, check=True ) if os.path.exists(bin_file) and os.path.getsize(bin_file) > 0: success_count += 1 else: print(f" ✗ 生成的文件为空或不存在") failed_files.append(test_file) except subprocess.CalledProcessError as e: print(f" ✗ 转换失败: {e.stderr.strip()}") failed_files.append(test_file) # 输出总结 print("\n" + "="*50) print(f"批量转换完成!") print(f"成功: {success_count}/{len(test_files)}") if failed_files: print(f"失败的文件:") for f in failed_files: print(f" - {f}") return len(failed_files) == 0 if __name__ == "__main__": batch_generate_bin()

运行,生成的.bin文件在/MyCPU_test/bin目录下。

接下来进行编译与仿真,建议安装iverilog与gtkwave:

$ git clone https://github.com/steveicarus/iverilog.git $ sudo apt-get install autoconf gperf flex bison build-essential $ sh autoconf.sh $ ./configure $ make $ make install $ sudo ln -s /usr/bin/python3.8 /usr/bin/python $ sudo apt-get install gtkwave

在当前目录下添加rtl文件夹,将所有.v文件放入文件夹中,并添加compile_rtl.py文件:

import sys import subprocess import os def main(): rtl_dir = './rtl' if not os.path.exists(rtl_dir): print(f"错误: RTL 目录不存在: {rtl_dir}") return 1 # 构建 iverilog 命令 iverilog_cmd = ['iverilog', '-o', 'out.vvp', '-I', rtl_dir, '-D', 'OUTPUT="signature.output"'] # 添加你的testbench 文件 tb_file = os.path.join(rtl_dir, 'Top_tb.v') if not os.path.exists(tb_file): print(f"错误: 找不到 testbench 文件: {tb_file}") return 1 iverilog_cmd.append(tb_file) # RTL 文件列表 rtl_files = [ 'ALU.v', 'IDecoder.v', 'Regfile.v', 'cpu.v' #...你的rtl文件 ] # 添加所有 RTL 文件 for rtl_file in rtl_files: full_path = os.path.join(rtl_dir, rtl_file) if os.path.exists(full_path): iverilog_cmd.append(full_path) else: print(f"警告: 文件不存在: {full_path}") # 执行编译 try: print("开始编译...") result = subprocess.run(iverilog_cmd, capture_output=True, text=True, timeout=30) if result.returncode == 0: print("编译成功!") return 0 else: print("编译失败!") print("STDERR:", result.stderr) return result.returncode except subprocess.TimeoutExpired: print("编译超时!") return 1 except Exception as e: print(f"编译过程中出错: {e}") return 1 if __name__ == '__main__': sys.exit(main())

在dump文件中找到add.dump文件,观察到该指令通过测试的标志为:

gp(x3)=1,a7(x17)=93(0x5d),a0(x10)=0;

并注意到最新版测试文件中包含了CSR指令,因此建议测试前进行相关实现或处理:

tb文件如下,需要修改对应的寄存器和存储器以适配你的设计,并注意指令存储器和数据存储器都需要用inst.data初始化,inst.data将在后面生成。

`timescale 1ns / 1ps `define TEST_PROG 1 module tb (); reg clk; reg rst_n; //需要修改 wire [31:0] x3 = u1.u_Regfile.regs[3]; wire [31:0] x10 = u1.u_Regfile.regs[10]; wire [31:0] x17 = u1.u_Regfile.regs[17]; integer r; initial begin `ifdef TEST_PROG wait (x3 == 32'd1) //测试结束 #100; if (x10 == 0) begin //通过测试 $display("~~~~~~~~~~~~~~~~~~~ TEST_PASS ~~~~~~~~~~~~~~~~~~~"); $display("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); $display("~~~~~~~~~ ##### ## #### #### ~~~~~~~~~"); $display("~~~~~~~~~ # # # # # # ~~~~~~~~~"); $display("~~~~~~~~~ # # # # #### #### ~~~~~~~~~"); $display("~~~~~~~~~ ##### ###### # #~~~~~~~~~"); $display("~~~~~~~~~ # # # # # # #~~~~~~~~~"); $display("~~~~~~~~~ # # # #### #### ~~~~~~~~~"); $display("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); end else begin $display("~~~~~~~~~~~~~~~~~~~ TEST_FAIL ~~~~~~~~~~~~~~~~~~~~"); $display("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); $display("~~~~~~~~~~###### ## # # ~~~~~~~~~~"); $display("~~~~~~~~~~# # # # # ~~~~~~~~~~"); $display("~~~~~~~~~~##### # # # # ~~~~~~~~~~"); $display("~~~~~~~~~~# ###### # # ~~~~~~~~~~"); $display("~~~~~~~~~~# # # # # ~~~~~~~~~~"); $display("~~~~~~~~~~# # # # ######~~~~~~~~~~"); $display("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); $display("fail testnum = %2d", x3); for (r = 0; r < 32; r = r + 1) $display("x%2d = 0x%x", r, u1.u_Regfile.regs[r]); end #100 $finish; `endif end initial begin rst_n=0; clk=0; for (integer i = 0; i < 8191; i = i + 1) begin u1.u_data_ram.u_dram.mem_array[i] = 32'd0; u1.u_IFetch.u_irom.mem_array[i]=32'd0; end //需要修改成你的存储器对应的reg数组 $readmemh("inst.data", u1.u_data_ram.u_dram.mem_array); $display("Initialize dram with dram.data"); //指令存储器和数据存储器的初始化文件相同 $readmemh("inst.data", u1.u_IFetch.u_irom.mem_array); $display("Initialize iram with inst.data"); #100 rst_n=1; end always #5 clk = ~clk; // sim timeout initial begin #5000000 $display("Time Out."); $finish; end //例化你的处理器 riscv_mcu u1 ( .clk (clk), .rst_n (rst_n) ); //生成vcd文件,使用gtkwave观察 initial begin $dumpfile("test.vcd"); $dumpvars(0, Top_tb); end endmodule

运行:

编译完成后进行仿真工作的准备,在MyCPU_tests目录下新增BinToMem.py和sim.py如下:

BinToMem.py

# BinToMem.py import sys import os def bin_to_mem(infile, outfile): try: with open(infile, 'rb') as binfile: binfile_content = binfile.read() with open(outfile, 'w') as datafile: # 确保字节数是4的倍数,不足则补0 remainder = len(binfile_content) % 4 if remainder != 0: binfile_content += b'\x00' * (4 - remainder) print(f"警告: 文件字节数不是4的倍数,已填充 {4-remainder} 个零字节") # 按32位字处理 for i in range(0, len(binfile_content), 4): word = binfile_content[i:i+4] # 转换为大端序(反转字节顺序) big_endian_word = word[::-1] hex_string = big_endian_word.hex() datafile.write(hex_string + '\n') print(f"成功转换: {infile} -> {outfile}") print(f"生成 {len(binfile_content)//4} 条指令") except FileNotFoundError: print(f"错误: 文件 {infile} 不存在") return False except Exception as e: print(f"转换过程中出错: {e}") return False return True if __name__ == '__main__': if len(sys.argv) == 3: success = bin_to_mem(sys.argv[1], sys.argv[2]) sys.exit(0 if success else 1) else: print('Usage: python BinToMem_CLI.py <binfile> <datafile>') sys.exit(1)

sim.py:

将文件编译集成到了sim.py中,因此每次修改.v文件后仿真只需运行sim.py文件。

import sys import filecmp import subprocess import sys import os # 主函数 def main(): #print(sys.argv[0] + ' ' + sys.argv[1] + ' ' + sys.argv[2]) # 1.编译rtl文件 cmd = r'python compile_rtl.py' f = os.popen(cmd) f.close() # 2.将bin文件转成mem文件 cmd = r'python BinToMem.py' + ' ' + sys.argv[1] + ' ' + sys.argv[2] f = os.popen(cmd) f.close() # 3.运行 vvp_cmd = [r'vvp'] vvp_cmd.append(r'out.vvp') process = subprocess.Popen(vvp_cmd) try: process.wait(timeout=10) except subprocess.TimeoutExpired: print('!!!Fail, vvp exec timeout!!!') if __name__ == '__main__': sys.exit(main())

对add指令进行仿真:

$ python sim.py ./bin/rv32ui-p-add.bin inst.data

仿真通过。

对sh指令进行仿真:

$ python sim.py ./bin/rv32ui-p-sh.bin inst.data

可以用gtkwave查看test.vcd文件,可以看到x3寄存器保存了test num的值;如果通过测试,则x3的最终值为1,x10的最终值为0。

也可以将rtl文件、tb文件和待测指令的inst.data文件导入modelsim工作目录下,使用modelsim进行编译与仿真:

最后,我们可以利用test_all_isa脚本对所有指令进行测试:

test_all_isa.py

import os import subprocess import sys import signal # 处理 BrokenPipeError signal.signal(signal.SIGPIPE, signal.SIG_DFL) # 找出path目录下的所有bin文件 def list_binfiles(path): files = [] list_dir = os.walk(path) for maindir, subdir, all_file in list_dir: for filename in all_file: apath = os.path.join(maindir, filename) if apath.endswith('.bin'): files.append(apath) return files def run_single_test(file): """运行单个测试文件""" try: # 使用 subprocess.run 替代 os.popen cmd = ['python', 'sim.py', file, 'inst.data'] result = subprocess.run(cmd, capture_output=True, text=True, timeout=60) # 检查输出中是否包含 TEST_PASS if 'TEST_PASS' in result.stdout: return True, result.stdout else: return False, result.stdout + result.stderr except subprocess.TimeoutExpired: return False, "TIMEOUT" except Exception as e: return False, f"ERROR: {str(e)}" # 主函数 def main(): bin_files = list_binfiles('./bin') if not bin_files: print("错误: 在 ./bin 目录中未找到任何 .bin 文件") return 1 anyfail = False passed_count = 0 total_count = len(bin_files) # 对每一个bin文件进行测试 for i, file in enumerate(bin_files, 1): print(f"[{i}/{total_count}] 测试: {os.path.basename(file)}", end="", flush=True) success, output = run_single_test(file) if success: print(" -> PASS") passed_count += 1 else: print(" -> !!!FAIL!!!") print(f" 失败原因: {output.split(chr(10))[0] if output else 'Unknown'}") anyfail = True # 如果希望一个失败就停止,取消下面的注释 # break print("=" * 60) if not anyfail: print(f'恭喜,所有测试通过! ({passed_count}/{total_count})') return 0 else: print(f'测试完成,有测试失败。通过: {passed_count}/{total_count}') return 1 if __name__ == '__main__': sys.exit(main())

运行:

测试结束。


2026.3.3 更新

处理器通过RV32IM指令集测试:


2026.3.12 更新

处理器通过RV32IMF指令集测试:


参考资料:

tinyriscv: tiny开源riscv

Deepseek

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/14 16:54:55

移动端API文档革命:3步打造完美适配的apidoc响应式界面

移动端API文档革命&#xff1a;3步打造完美适配的apidoc响应式界面 【免费下载链接】apidoc RESTful web API Documentation Generator. 项目地址: https://gitcode.com/gh_mirrors/ap/apidoc apidoc是一款强大的RESTful web API文档生成工具&#xff0c;能够帮助开发者…

作者头像 李华
网站建设 2026/7/14 16:54:57

终极Maccy瘦身指南:5个高效方法减小macOS剪贴板管理器体积

终极Maccy瘦身指南&#xff1a;5个高效方法减小macOS剪贴板管理器体积 【免费下载链接】Maccy Lightweight clipboard manager for macOS 项目地址: https://gitcode.com/gh_mirrors/ma/Maccy Maccy作为一款轻量级macOS剪贴板管理器&#xff0c;其小巧的体积是吸引用户的…

作者头像 李华
网站建设 2026/7/14 16:55:11

算 法 笔 记

力扣两个栈实现先进先出队列class MyQueue{ private: stack<int>inStack,outStack; void in2out(){//in栈元素全部移动到out栈中while (!inStack.empty()) {outStack.push(inStack.top());inStack.pop();} } public:MyQueue() {}void push(int x) {//入队inStack.push(x)…

作者头像 李华
网站建设 2026/7/14 16:54:58

如何用粤语编写函数与面向对象程序?Cantonese进阶教程

如何用粤语编写函数与面向对象程序&#xff1f;Cantonese进阶教程 【免费下载链接】Cantonese 粤语編程語言.The Cantonese programming language. 项目地址: https://gitcode.com/gh_mirrors/ca/Cantonese Cantonese编程语言是一款创新的粤语编程工具&#xff0c;让开发…

作者头像 李华