用斐波那契数列手把手调试你的第一个LoongArch单周期CPU(Vivado仿真+上板验证)
用斐波那契数列手把手调试你的第一个LoongArch单周期CPUVivado仿真上板验证第一次看到自己设计的CPU在开发板上运行并计算出正确结果那种成就感是难以言喻的。本文将带你通过一个有趣的斐波那契数列计算项目从零开始调试一个支持5条基础指令的LoongArch单周期CPU。不同于枯燥的理论讲解我们会聚焦于如何通过拨码开关输入、LED输出观察这种直观方式一步步验证CPU的每条指令是否正常工作。1. 实验环境与准备工作在开始之前确保你已经准备好以下环境Vivado设计套件建议使用2019.1或更高版本LoongArch开发板如无实体硬件可仅进行仿真验证实验代码包包含miniCPU工程文件和测试程序实验目录结构关键文件说明minicpu_env/ ├── func/ # 测试程序目录 │ └── inst_ram.coe # 斐波那契数列计算程序 ├── soc_verify/ │ ├── run_vivado/ # Vivado工程目录 │ └── testbench/ │ └── minicpu_tb.v # 测试激励文件提示初次使用时建议先阅读实验包中的README文件了解环境配置的特殊要求。2. 理解斐波那契测试程序我们的测试程序是一个精简的斐波那契数列计算器其汇编代码如下1c000000: addi.w $t0,$zero,0x0 # 初始化f(0)0 1c000004: addi.w $t1,$zero,0x1 # 初始化f(1)1 1c000008: addi.w $s0,$zero,0x0 # 循环变量i0 1c00000c: addi.w $s1,$zero,0x1 # 循环步长1 1c000010: ld.w $a0,$zero,1024 # 读取拨码开关输入值n loop: 1c000014: add.w $t2,$t0,$t1 # f(i)f(i-2)f(i-1) 1c000018: addi.w $t0,$t1,0x0 # 更新f(i-2) 1c00001c: addi.w $t1,$t2,0x0 # 更新f(i-1) 1c000020: add.w $s0,$s0,$s1 # i 1c000024: bne $s0,$a0,loop # 循环控制 1c000028: st.w $t2,$zero,1028 # 输出结果到LED end: 1c00002c: bne $s1,$zero,end # 程序终止程序工作原理通过ld.w指令读取拨码开关设置的输入值n计算斐波那契数列第n项f(n)通过st.w指令将结果输出到LED3. Vivado仿真调试技巧3.1 基础仿真设置启动Vivado后按以下步骤操作打开minicpu_env/soc_verify/run_vivado/miniCPU.xpr工程确认inst_ramIP核已关联正确的coe文件在Flow Navigator中点击Run Simulation → Run Behavioral Simulation关键仿真信号观察点信号名称作用描述预期值示例switch[7:0]拨码开关输入8h05 (计算f(5))led[7:0]LED输出结果8h05 (f(5)5)pc[31:0]程序计数器按指令顺序变化inst[31:0]当前执行指令对应指令编码3.2 调试常见问题当结果不符合预期时可按以下步骤排查指令执行顺序验证在波形窗口观察pc值变化确认是否按预期顺序执行指令特别注意bne指令的跳转是否正确寄存器文件检查// 添加这些信号到波形窗口观察寄存器值 wire [31:0] t0_value u_regfile.regs[8]; // $t0 wire [31:0] t1_value u_regfile.regs[9]; // $t1 wire [31:0] t2_value u_regfile.regs[10]; // $t2内存访问验证确认ld.w正确读取了switch值检查st.w是否将结果写入正确地址注意每次修改switch值后必须重新运行仿真才能看到更新后的结果。4. 上板验证实战4.1 比特流生成与下载通过仿真验证后按以下步骤进行硬件验证在Vivado中点击Generate Bitstream连接开发板打开硬件管理器下载bit文件到FPGA4.2 物理调试技巧实际硬件调试与仿真不同需要注意输入设置拨码开关对应关系参考开发板手册通常switch[0]对应最低位输入值n建议从3开始f(3)2输出观察LED显示模式二进制直接显示如f(5)5 → 00000101可能需要转换为十六进制理解常见硬件问题排查时钟信号是否稳定可用示波器检查复位信号是否正确释放I/O约束是否正确定义5. 指令级调试详解让我们深入分析每条指令的验证方法5.1 addi.w指令验证验证步骤在仿真波形中找到第一条指令addi.w $t0,$zero,0x0检查以下信号rf_waddr应为8$t0寄存器编号rf_wdata应在指令执行后变为0gr_we信号应在指令周期内拉高5.2 add.w指令验证关键观察点// 在testbench中添加以下监测代码 always (posedge clk) begin if (inst_add_w) begin $display(ADD.W: %d %d %d, rj_value, rkd_value, alu_result); end end预期输出示例ADD.W: 0 1 1 ADD.W: 1 1 2 ADD.W: 1 2 35.3 内存访问指令验证ld.w/st.w指令的地址映射关系指令地址对应物理设备ld.w 10240x00000400拨码开关输入st.w 10280x00000404LED输出调试时可添加以下监测代码always (posedge clk) begin if (data_sram_we data_sram_addr 32h404) begin $display(LED输出更新为: %h, data_sram_wdata); end end6. 性能优化与扩展思考虽然我们的单周期CPU已经能正确运行但仍有改进空间关键路径分析使用Vivado的Timing Report工具识别关键路径常见瓶颈在寄存器文件访问和ALU计算扩展建议添加更多指令如sub.w、and.w等实现流水线结构提升性能添加中断支持调试过程中最让我印象深刻的是第一次看到LED正确显示f(7)13的时刻——那一刻所有的调试艰辛都得到了回报。建议初学者在每验证成功一条指令后都做一个标记这种渐进式的成就感能有效保持学习动力。