3. 搭建最小调试系统:创建Vivado工程、例化RISC-V核、添加ILA探针

好,咱们进入实战环节。这一章,我们要亲手搭建一个最小调试系统。说白了,就是让RISC-V核能在FPGA上跑起来,同时我们还能用ILA盯着它看。

我个人习惯,做任何调试系统之前,先画个框图。脑子里想清楚信号怎么流,再动手。不然代码写一半发现少了个关键信号,那才叫头疼。

最小调试系统结构图 时钟生成 MMCM/PLL 复位逻辑 异步复位同步释放 RISC-V 核心 RV32IM 五级流水线 指令/数据总线 ILA 探针 深度: 1024 采样: clk 触发: pc[31:0] JTAG 调试桥 → Vivado HW Manager

3.1 创建Vivado工程——别急着点Next

打开Vivado,选好器件型号。我建议你直接用xc7a35ticsg324-1L或者你板子上对应的型号。创建工程时,记得勾上「RTL Project」,别选「Post-synthesis」——我们后面还要加ILA呢。

小技巧: 工程命名别用中文,路径也别带空格。Vivado对中文支持不太好,我曾经因为路径里有个「调试」两个字,折腾了半天编译不过。

创建好工程后,第一件事是添加约束文件。哪怕你还没写任何RTL代码,先把时钟引脚约束写好。我个人习惯用.xdc文件,清晰明了。

# 时钟约束 - 50MHz输入
create_clock -period 20.000 [get_ports clk_i]

# 复位引脚 - 低有效
set_property PACKAGE_PIN R4 [get_ports rstn_i]
set_property IOSTANDARD LVCMOS33 [get_ports rstn_i]

# 串口引脚(后续调试用)
set_property PACKAGE_PIN U12 [get_ports uart_tx_o]
set_property IOSTANDARD LVCMOS33 [get_ports uart_tx_o]

3.2 例化RISC-V核——别被那么多端口吓到

我们用的RISC-V核是VexRiscv,一个用SpinalHDL写的可配置核。你第一次看到它的端口列表可能会有点懵——怎么这么多信号?

其实核心就几类:

  • 时钟复位clk_irstn_i
  • 指令总线iBus_cmd_validiBus_cmd_readyiBus_cmd_payload_pc
  • 数据总线dBus_cmd_validdBus_cmd_readydBus_cmd_payload_wr
  • 调试接口debug_resetdebug_rundebug_pc

嗯,这里要注意:调试接口不是必须连的。如果你只是想让核跑起来,把debug_reset拉低就行。但我们要加ILA,所以debug_pc这个信号一定要引出来。

// RISC-V 核例化模板
vexriscv_wrapper u_cpu (
    .clk_i              (sys_clk),
    .rstn_i             (sys_rstn),
    
    // 指令总线 - 接BRAM
    .iBus_cmd_valid     (ibus_cmd_valid),
    .iBus_cmd_ready     (ibus_cmd_ready),
    .iBus_cmd_payload_pc(ibus_pc),
    .iBus_rsp_valid     (ibus_rsp_valid),
    .iBus_rsp_payload_inst(ibus_inst),
    
    // 数据总线 - 接BRAM
    .dBus_cmd_valid     (dbus_cmd_valid),
    .dBus_cmd_ready     (dbus_cmd_ready),
    .dBus_cmd_payload_wr(dbus_wr),
    .dBus_cmd_payload_address(dbus_addr),
    .dBus_cmd_payload_data(dbus_wdata),
    .dBus_rsp_valid     (dbus_rsp_valid),
    .dBus_rsp_payload_data(dbus_rdata),
    
    // 调试信号 - 引出给ILA
    .debug_pc           (cpu_pc),
    .debug_instruction  (cpu_inst),
    .debug_stall        (cpu_stall)
);
关键点: 调试信号debug_pcdebug_instruction是ILA的核心观测对象。我建议你至少把这俩信号引出来,不然你都不知道CPU跑到哪条指令了。

3.3 添加ILA探针——别贪多,够用就行

ILA(Integrated Logic Analyzer)是Vivado自带的逻辑分析仪。添加ILA有两种方式:

  1. GUI方式:在Netlist窗口右键,选择「Debug」→「Mark Debug」
  2. HDL方式:直接在RTL代码里例化ILA IP核

我推荐用第二种方式。为什么?因为可控性更强。GUI方式有时候会莫名其妙地优化掉一些信号,尤其是跨时钟域的信号。

添加ILA IP核的步骤:

  • 在IP Catalog里搜索「ILA」
  • 配置采样深度:1024够用,别选太大,否则BRAM消耗惊人
  • 配置探针数量:我一般选4-8个,每个32位
  • 采样时钟:直接用CPU的主时钟
避坑指南: 我曾经在调试一个多周期指令时,把ILA采样深度设成了65536。结果综合报告显示BRAM用了80%,整个工程布局布线都过不了。后来改成1024,配合触发条件,一样能抓到问题。

ILA的触发条件设置也很关键。比如你想看CPU执行到地址0x80001000时的行为,可以这样配:

// ILA 触发条件设置(在Vivado Hardware Manager中)
// 触发端口: probe0 (cpu_pc)
// 触发条件: == 0x80001000
// 触发位置: 中间(便于看前后波形)

3.4 连接存储——别忘了程序放哪

RISC-V核需要指令和数据存储器。最简单的做法是用Block RAM。我建议例化一个双端口BRAM,一个端口给指令总线,一个端口给数据总线。

存储器 地址范围 大小 说明
指令BRAM 0x80000000 - 0x80003FFF 16KB 存放程序代码
数据BRAM 0x80004000 - 0x80007FFF 16KB 存放堆栈和数据

你想想看,如果BRAM初始化内容为空,CPU上电后第一条指令都取不到。所以我们需要用.coe文件或者.mem文件来初始化BRAM。这个我们下一章再细讲。

3.5 综合与实现——第一次跑通

所有代码写完后,点击「Run Synthesis」。Vivado会开始综合。这个过程可能需要几分钟,取决于你的工程大小。

综合完成后,打开综合报告,重点关注:

  • 时序报告:看WNS(最差负时序裕量)是不是正数
  • 资源使用:LUT、FF、BRAM用了多少
  • 警告信息:有没有未连接的端口或者跨时钟域问题
个人经验: 第一次综合如果出现时序违例,别慌。先检查时钟约束是不是写对了。我遇到过好几次,都是因为create_clock的周期写错了,导致Vivado以为我们要跑200MHz。

综合通过后,点击「Run Implementation」。布局布线完成后,生成比特流。这一步如果报错,多半是引脚约束冲突或者BRAM不够用。

最后,把比特流下载到FPGA里。打开Vivado Hardware Manager,你应该能看到ILA的窗口了。点击运行,ILA开始采样——如果CPU在跑,你应该能看到cpu_pc在变化。

嗯,到这里,最小调试系统就搭好了。你可能会问:「为什么CPU没跑起来?」别急,下一章我们就要解决这个问题——程序加载和启动流程。


公众号:蓝海资料掘金营,微信deep3321