用斐波那契数列手把手调试你的第一个LoongArch单周期CPU(Vivado仿真+上板验证)
用斐波那契数列手把手调试你的第一个LoongArch单周期CPU(Vivado仿真+上板验证)
第一次看到自己设计的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 # 循环变量i=0 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指令将结果输出到LED
3. 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] | 拨码开关输入 | 8'h05 (计算f(5)) |
| led[7:0] | LED输出结果 | 8'h05 (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文件到FPGA
4.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 1024 | 0x00000400 | 拨码开关输入 |
| st.w 1028 | 0x00000404 | LED输出 |
调试时可添加以下监测代码:
always @(posedge clk) begin if (data_sram_we && data_sram_addr == 32'h404) begin $display("LED输出更新为: %h", data_sram_wdata); end end6. 性能优化与扩展思考
虽然我们的单周期CPU已经能正确运行,但仍有改进空间:
关键路径分析:
- 使用Vivado的Timing Report工具识别关键路径
- 常见瓶颈在寄存器文件访问和ALU计算
扩展建议:
- 添加更多指令(如sub.w、and.w等)
- 实现流水线结构提升性能
- 添加中断支持
调试过程中最让我印象深刻的是第一次看到LED正确显示f(7)=13的时刻——那一刻所有的调试艰辛都得到了回报。建议初学者在每验证成功一条指令后都做一个标记,这种渐进式的成就感能有效保持学习动力。
