13、工具链使用:Vivado部分重构流程、Tcl脚本自动化、工程管理技巧
说到部分重构,很多人第一反应是「这玩意儿太复杂,搞不定」。其实不然。我刚开始接触PR(Partial Reconfiguration)时,也被那一堆约束文件和配置步骤搞得头晕。但后来我发现,只要把工具链的套路摸清楚,这事儿就跟搭积木一样简单。
今天咱们就聊聊Vivado里怎么玩转部分重构。我会把流程拆开,一个个讲清楚。嗯,这里要注意,我说的都是实战经验,不是官方文档的复读。
13.1 Vivado部分重构的基本流程
说白了,部分重构就是让FPGA的某个区域在运行时换功能。比如你今天想让这个区域跑个FFT,明天想让它跑个滤波器,不用重新烧录整个bitstream。
Vivado里做PR,核心就三步:
- 定义重构分区——告诉工具哪块区域是「可变」的
- 生成多个重构模块——每个模块对应一种功能
- 编译并生成部分bitstream——只更新那个区域
我在项目中遇到过最坑的事,就是分区边界没对齐CLB列。Vivado要求重构分区必须按整列划分,你想想看,如果边界卡在中间,工具直接报错。所以第一步,一定要先看芯片的列结构。
关键点:重构分区必须对齐到CLB列的边界。建议先用 report_clock_utilization 看看资源分布,再画分区。
13.2 创建重构工程的步骤
Vivado里创建PR工程,有两种方式:
- 方式一:用GUI界面,点来点去。适合新手,但效率低。
- 方式二:用Tcl脚本,一键搞定。我个人习惯用脚本,因为重复劳动太烦了。
先看GUI的流程:
- 新建工程,添加静态模块(Static Region)的代码。
- 在IP Integrator或RTL中,把要重构的模块设为
HD.RECONFIGURABLE。 - 运行
create_pblock命令,划定物理区域。 - 添加多个重构模块(RM1, RM2, ...),每个模块对应不同的实现。
- 运行综合、布局布线,生成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工具链的流程串起来了。你可以把它当作一个检查清单,做工程时对着看,不容易漏步骤。
你看,整个流程其实就是一个流水线。从工程准备到最终生成bitstream,每一步都有对应的Tcl命令。只要把脚本写好,以后每次改RM模块,跑一遍脚本就行了。
最后说一句,部分重构这东西,上手确实有点门槛。但一旦你掌握了工具链的套路,就会发现它其实是个很强大的武器。我现在的项目里,几乎每个用到FPGA的地方都会考虑PR——能省不少重新编译的时间。
总结一下今天的核心:
- PR流程分三步:定义分区、添加RM、编译生成bitstream。
- Tcl脚本是自动化的关键,建议从GUI复制命令开始积累。
- 工程管理要规范,目录结构、版本控制、结果归档缺一不可。
- 部分bitstream通过ICAP接口加载,验证时注意时序问题。