4. Quartus工程文件版本化实践

Quartus工程进入多人协作或长期维护阶段后,真正棘手的往往不是RTL代码,而是那些“半自动生成、半手工维护”的工程文件。其中.qsf是核心:它同时承载器件型号、引脚分配、时序约束、IP实例化路径、编译选项等大量信息。如果直接把整个.qsf丢进Git,任何一次编译都可能产生大量无意义差异;如果完全忽略它,工程又无法复现。本节围绕.qsf、IP核文件、引脚与时序约束,给出可落地的版本化方法与冲突解决策略。

一、先分清哪些文件该进版本库

Quartus工程目录中,文件可按“是否可重建”分为三类。第一类是必须版本化的源文件:.v/.sv/.vhd、.qsf、.sdc、.qpf、IP的.qip与用户参数文件、.tcl脚本。第二类是可重建但建议保留的:.qsys/.ip(IP配置源)、.sopcinfo。第三类是禁止入库的中间产物:db/、incremental_db/、greybox_tmp/、*.rpt、*.sof、*.pof、*.pin、*.qws、*.qpf中自动生成的路径信息。判断标准很简单:能否由源文件通过一次编译完整重建。能重建的就不入库,否则仓库会被二进制中间文件迅速撑大,且每次编译都产生冲突。

建议在仓库根目录放置 .gitignore,至少包含:db/、incremental_db/、greybox_tmp/、*.rpt、*.sof、*.pof、*.pin、*.qws、*.jdi、*.ddb。

二、.qsf的版本化:拆分与稳定化

直接维护单一.qsf的最大问题是“自动生成内容”与“人工约束”混在一起。Quartus在编译、引脚编辑、IP升级时会自动改写.qsf,导致diff噪声极大。推荐做法是按职责拆分.qsf,再用一个顶层.qsf通过source或直接由Quartus的“Assignment Groups”机制组织。典型拆分:

project.qsf          # 顶层,仅含器件、版本、source引用
device.qsf           # 器件型号、速度等级、封装
pin_assignments.qsf  # set_location_assignment 引脚分配
timing.sdc           # 时序约束(独立SDC,不写进qsf)
ip_instances.qsf     # IP实例化路径与参数
compile_options.qsf  # 编译、优化、Fitter选项

拆分后,引脚变更只改pin_assignments.qsf,时序调整只改timing.sdc,冲突面大幅缩小。同时,在顶层.qsf中用set_global_assignment -name SOURCE_FILE ...或直接source pin_assignments.qsf引入。注意:Quartus GUI保存时仍可能把内容写回顶层,因此建议约束文件由脚本生成或手工维护,GUI仅用于查看,避免自动回写破坏拆分结构。

另一个稳定化手段是固定自动生成段。Quartus会在.qsf末尾追加类似set_global_assignment -name LAST_QUARTUS_VERSION "23.1std.0"的版本标记,以及IP升级产生的路径。可在提交前用脚本剥离这些行,或约定这些行单独放在一个auto_generated.qsf中并加入.gitignore。这样每次编译不会因为版本号变化产生diff。

三、IP核文件的版本管理

IP核是冲突高发区。以Nios II、PCIe、DDR控制器为例,IP目录通常包含:.qsys(系统描述)、.qip(Quartus引用)、.sopcinfo、以及生成的HDL。版本化原则是:只提交.qsys与.qip,不提交生成HDL。生成HDL由qsys-generate或ip-generate在编译前重建。若团队使用Platform Designer,建议在CI中加入生成步骤:

qsys-generate soc_system.qsys --synthesis=VERILOG --output-directory=./generated

IP参数变更(如DDR位宽、PLL频率)会同时影响.qsys和.qip。冲突解决时,以.qsys为准,.qip可由工具重新生成。若两人同时修改同一IP参数,应先在Platform Designer中打开.qsys对比参数差异,而不是直接合并文本。对于第三方加密IP,只提交其.qip和授权文件,不提交加密后的网表。

四、引脚分配与时序约束的冲突解决

引脚分配冲突最常见于两人同时改同一bank或同一差分对。解决策略是按bank或按功能模块划分文件:例如pin_ddr.qsf、pin_ethernet.qsf、pin_debug.qsf,每人只改自己负责的文件,合并时几乎不冲突。若必须改同一文件,合并后务必用quartus_sh --qsf或编译一次验证引脚是否重复、是否与器件bank电压冲突。

时序约束建议全部写在独立.sdc中,而不是.qsf的set_global_assignment -name SDC_FILE里混写。SDC按时钟、输入输出延迟、例外分段,每段一个文件,用source组织。冲突时优先保留更严格的约束,并用TimeQuest的report_sdc检查是否有重复或矛盾的create_clock、set_false_path。特别注意:set_false_path与set_max_delay同时作用于同一路径时,后者可能被忽略,合并后必须重新跑一次时序分析确认。

Quartus工程文件版本化:入库边界与冲突处理 必须版本化(源) RTL:.v / .sv / .vhd 约束:.qsf / .sdc IP:.qsys / .qip 工程:.qpf / .tcl 授权:.lic / 加密IP 原则:可由源重建者不入库 .qsf 拆分结构 project.qsf(顶层) device.qsf pin_assignments.qsf timing.sdc(独立) ip_instances.qsf compile_options.qsf auto_generated.qsf (版本号/自动路径,.gitignore) 冲突解决策略 引脚:按bank/功能拆文件 合并后编译验证重复 时序:SDC分段source 保留更严格约束 IP:以.qsys为准 .qip由工具重生成 自动段:剥离或忽略 避免版本号噪声 合并后必做: 编译 + TimeQuest复检 源文件 → 拆分约束 → 冲突处理 → 编译验证

五、合并后的验证闭环

无论采用哪种拆分,合并后必须执行一次完整验证,否则冲突可能以“编译通过但功能异常”的形式潜伏。建议的验证顺序是:

  1. 语法与重复检查:用quartus_sh -t check_qsf.tcl或直接编译,观察是否有Duplicate assignment、Conflicting location警告。
  2. 引脚复检:打开Pin Planner,确认无红色冲突、无未分配关键引脚,差分对正负未颠倒。
  3. 时序复检:运行TimeQuest,执行report_clocks、report_timing,确认时钟定义唯一、无被覆盖的false path。
  4. IP复检:确认.qsys参数与.qip一致,生成HDL无缺失。
常见陷阱:两人分别修改同一.qsf的不同行,Git自动合并成功,但其中一人把set_location_assignment写到了错误的bank,编译不报错却导致引脚电压不匹配。因此“合并成功”不等于“约束正确”,必须编译并检查Pin Planner。

六、可直接使用的提交前检查清单

检查项判断标准处理动作
db/、incremental_db/是否入库不应出现加入.gitignore并移除
.qsf是否含自动版本号行不应出现在主文件剥离到auto_generated.qsf
引脚是否重复分配编译无Duplicate警告按bank拆分文件
SDC是否有重复create_clockreport_clocks唯一合并为单一时钟定义
IP生成HDL是否入库不应入库由qsys-generate重建
合并后是否编译验证编译通过且时序收敛未通过则回退重解

把上述方法固化为团队约定后,Quartus工程的版本管理就从“每次合并都提心吊胆”变成可预测的例行操作。核心只有两条:让可重建的东西不进入版本库,让必须人工维护的约束按职责拆开。做到这两点,引脚、时序与IP的冲突就会从“灾难”降级为“可定位、可复检的普通合并”。