13、工具链使用:Vivado部分重构流程、Tcl脚本自动化、工程管理技巧

说到部分重构,很多人第一反应是「这玩意儿太复杂,搞不定」。其实不然。我刚开始接触PR(Partial Reconfiguration)时,也被那一堆约束文件和配置步骤搞得头晕。但后来我发现,只要把工具链的套路摸清楚,这事儿就跟搭积木一样简单。

今天咱们就聊聊Vivado里怎么玩转部分重构。我会把流程拆开,一个个讲清楚。嗯,这里要注意,我说的都是实战经验,不是官方文档的复读。

13.1 Vivado部分重构的基本流程

说白了,部分重构就是让FPGA的某个区域在运行时换功能。比如你今天想让这个区域跑个FFT,明天想让它跑个滤波器,不用重新烧录整个bitstream。

Vivado里做PR,核心就三步:

  1. 定义重构分区——告诉工具哪块区域是「可变」的
  2. 生成多个重构模块——每个模块对应一种功能
  3. 编译并生成部分bitstream——只更新那个区域

我在项目中遇到过最坑的事,就是分区边界没对齐CLB列。Vivado要求重构分区必须按整列划分,你想想看,如果边界卡在中间,工具直接报错。所以第一步,一定要先看芯片的列结构。

关键点:重构分区必须对齐到CLB列的边界。建议先用 report_clock_utilization 看看资源分布,再画分区。

13.2 创建重构工程的步骤

Vivado里创建PR工程,有两种方式:

  • 方式一:用GUI界面,点来点去。适合新手,但效率低。
  • 方式二:用Tcl脚本,一键搞定。我个人习惯用脚本,因为重复劳动太烦了。

先看GUI的流程:

  1. 新建工程,添加静态模块(Static Region)的代码。
  2. 在IP Integrator或RTL中,把要重构的模块设为 HD.RECONFIGURABLE
  3. 运行 create_pblock 命令,划定物理区域。
  4. 添加多个重构模块(RM1, RM2, ...),每个模块对应不同的实现。
  5. 运行综合、布局布线,生成bitstream。

但说实话,GUI操作有个问题——你很难复现。换台电脑,换个版本,可能就找不到按钮了。所以我更推荐用Tcl脚本。

13.3 Tcl脚本自动化——这才是生产力

Vivado的Tcl脚本,说白了就是把你手动操作翻译成命令。我刚开始写脚本时,也是从GUI里复制命令开始的。Vivado有个好功能:在Tcl Console里,你每点一个操作,它都会打印对应的命令。你把这些命令攒起来,就是一个完整的脚本。

下面是我常用的一个PR工程脚本框架:

# 创建工程
create_project -part xc7k325tffg900-2 pr_demo ./pr_demo

# 添加源文件
add_files -norecurse ./src/static_top.v
add_files -norecurse ./src/reconfig_module.v

# 定义重构模块
set_property HD.RECONFIGURABLE 1 [get_cells u_reconfig]

# 创建物理分区
create_pblock pblock_reconfig
resize_pblock pblock_reconfig -add {CLOCKREGION_X0Y0:CLOCKREGION_X0Y1}

# 添加多个配置
create_run -parent_run synth_1 -flow "Vivado Synthesis 2022" rm_synth_1
set_property STEPS.SYNTHESIS.ARGS.MORE OPTIONS "-mode out_of_context" [get_runs rm_synth_1]

# 运行综合
launch_runs synth_1 rm_synth_1 -jobs 4
wait_on_runs synth_1 rm_synth_1

# 运行实现
launch_runs impl_1 -to_step write_bitstream -jobs 4
wait_on_runs impl_1

你看,就这么几行,一个PR工程的编译流程就搭好了。我一般还会加个循环,把多个RM模块的编译串起来:

# 批量编译多个重构模块
foreach rm {rm_fft rm_filter rm_encoder} {
    create_run -parent_run synth_1 -flow "Vivado Synthesis 2022" ${rm}_synth
    add_files -norecurse ./src/${rm}.v
    launch_runs ${rm}_synth -jobs 4
    wait_on_runs ${rm}_synth
}

小技巧:wait_on_runs 确保前一步完成再跑下一步。我曾经因为没加这个,脚本跑飞了,浪费了半天时间。

13.4 工程管理技巧——别让工程变成一团乱麻

做PR工程,最怕的就是版本混乱。一个工程里可能有十几个RM模块,每个模块又有多个版本。我见过有人把不同版本的RM文件全扔在一个文件夹里,结果编译时加载错了文件,查了两天bug。

我的管理原则就三条:

  • 目录结构要清晰——每个RM模块一个子目录,命名带版本号。
  • 脚本要版本控制——Tcl脚本和源文件一起提交到Git。
  • 编译结果要归档——每个版本的bitstream和报告单独保存。

举个例子,我的工程目录长这样:

pr_demo/
├── src/
│   ├── static/          # 静态模块代码
│   ├── rm_fft_v1/       # FFT模块 v1
│   ├── rm_fft_v2/       # FFT模块 v2
│   └── rm_filter_v1/    # 滤波器模块 v1
├── scripts/
│   ├── build_all.tcl    # 主编译脚本
│   └── utils.tcl        # 工具函数
├── constraints/
│   └── pr_demo.xdc      # 约束文件
└── outputs/
    ├── v1.0/
    └── v1.1/

另外,我强烈建议在XDC约束文件里,把重构分区的时序约束单独写一块。这样万一时序出问题,你一眼就能看到是静态部分还是重构部分的问题。

注意:重构模块的时序约束,必须和静态模块分开。否则Vivado会报冲突。我曾经因为偷懒,把约束写在一起,结果编译出来的bitstream跑起来直接死机。

13.5 生成部分bitstream与验证

编译完成后,Vivado会生成两种bitstream:

  • 完整bitstream(.bit)——包含静态区域和初始的重构模块。
  • 部分bitstream(.partial.bit)——只包含重构区域的内容。

实际部署时,你先烧录完整bitstream,然后通过ICAP或PCAP接口,动态加载部分bitstream。加载过程很简单,就是往ICAP寄存器里写数据。

我一般会在测试板上写个简单的状态机,模拟动态切换的过程。先加载RM1,跑一会儿,再加载RM2,看看功能是否正常。嗯,这一步不能省,因为部分重构的时序和静态时序不一样,有时候静态区域没问题,重构区域却会出毛刺。

13.6 知识体系总览

下面这张图,把整个PR工具链的流程串起来了。你可以把它当作一个检查清单,做工程时对着看,不容易漏步骤。

Vivado部分重构工具链流程 1. 工程准备 创建工程 · 添加源文件 设置器件型号 2. 分区定义 设置 HD.RECONFIGURABLE 创建 pblock · 划定区域 3. 多配置管理 添加 RM1, RM2, ... 创建多个 run 4. 综合 静态模块 + 每个RM模块分别综合(out_of_context) 5. 实现(布局布线) 静态 + 每个RM分别布局布线 6. 生成bitstream(完整 + 部分)

你看,整个流程其实就是一个流水线。从工程准备到最终生成bitstream,每一步都有对应的Tcl命令。只要把脚本写好,以后每次改RM模块,跑一遍脚本就行了。

最后说一句,部分重构这东西,上手确实有点门槛。但一旦你掌握了工具链的套路,就会发现它其实是个很强大的武器。我现在的项目里,几乎每个用到FPGA的地方都会考虑PR——能省不少重新编译的时间。

总结一下今天的核心:

  • PR流程分三步:定义分区、添加RM、编译生成bitstream。
  • Tcl脚本是自动化的关键,建议从GUI复制命令开始积累。
  • 工程管理要规范,目录结构、版本控制、结果归档缺一不可。
  • 部分bitstream通过ICAP接口加载,验证时注意时序问题。

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