3. 版本管理基础与Quartus工程

Quartus工程天然是“文本+二进制”的混合体:.v、.sv、.vhd、.sdc、.qsf、.qpf 是文本,可以逐行 diff、逐行合并;而 .sof、.pof、.jic、.rbf、.qar、.ddb、.qdb、.cdb、.qws 以及 IP 生成的 .qip 关联库、.hex/.mif 初始化文件,往往是二进制或大体积文件。版本管理的第一件事,不是选 Git 还是 SVN,而是先给工程里的每个文件“定性”:哪些必须入库、哪些必须忽略、哪些必须走大文件通道。

Git 与 SVN 在 FPGA 项目中的差异,核心不在“谁更先进”,而在“分支模型”和“大文件处理方式”。Git 是分布式,每个克隆点都有完整历史,分支创建几乎零成本,适合“多版本并行开发、频繁实验、按需合并”的节奏;但 Git 默认对二进制文件不做增量存储,每次提交 .sof 都会把整个文件压进对象库,仓库体积会迅速膨胀。SVN 是集中式,二进制文件默认按增量存储(部分实现),对“大文件、少分支、线性推进”的团队更友好;但分支和合并成本高,离线提交困难。因此,FPGA 项目常见的结论是:源码、约束、脚本、工程配置文件用 Git 管理;编译产物、器件编程文件、大型 IP 库用 LFS 或独立制品库管理;若团队已有 SVN 且分支需求弱,可继续用 SVN,但必须配合忽略规则和目录约定。

下面这张图给出一个 Quartus 工程在版本管理中的文件分流关系:左侧是工程目录中的典型文件,中间是“文本/二进制”判定,右侧是“入库/忽略/LFS”三种处置路径。它不替代正文,而是帮助你在实际工程中快速判断每个文件该走哪条路。

Quartus 工程文件在版本管理中的分流判定 文本文件走 diff/merge,二进制文件走 LFS 或忽略,编译产物不入库 工程目录中的典型文件 .v / .sv / .vhd 源码 .sdc 时序约束 .qsf / .qpf 工程配置 .sof / .pof / .jic 编程文件 .qar / .qdb / .ddb 归档库 .hex / .mif 初始化文件 db/ incremental_db/ 中间产物 output_files/ 编译输出 .qws / .qpf 用户界面状态 文件性质判定 文本文件 可 diff / 可 merge 二进制文件 不可逐行 diff / 体积大 可再生成文件 由工具重新编译产生 判定依据:扩展名 + 是否可重建 版本管理处置路径 ① 直接入库(Git / SVN) .v .sv .vhd .sdc .qsf .qpf .tcl 正常提交、分支、合并、代码评审 ② 走 Git LFS / 制品库 .sof .pof .jic .qar .qdb .hex .mif 指针入库,实体存 LFS 或 Artifactory ③ 写入 .gitignore / svn:ignore db/ incremental_db/ output_files/ .qws *.rpt *.summary *.pin 等 原则:源码入库、产物出库、大文件走 LFS 保证克隆体积可控、可重建、可追溯

把上图落到实际工程,第一步是写 .gitignore。Quartus 工程中必须忽略的目录和文件包括:db/、incremental_db/、output_files/、greybox_tmp/、*.qws、*.rpt、*.summary、*.pin、*.smsg、*.jdi、*.qws、*.bak、*.tmp。这些文件要么是编译中间产物,要么是用户界面状态,要么是报告文件,都可以由 .qpf、.qsf 和源码重新生成。忽略它们之后,仓库里留下的就是“可重建工程”的最小集合。

# .gitignore 示例(Quartus 工程)
db/
incremental_db/
output_files/
greybox_tmp/
*.qws
*.rpt
*.summary
*.pin
*.smsg
*.jdi
*.bak
*.tmp
*.qar
*.sof
*.pof
*.jic
*.rbf
*.hex
*.mif

注意:.sof、.pof、.jic 这类编程文件是否忽略,取决于交付策略。如果团队需要“每个发布版本都保留可烧录文件”,就不应简单忽略,而应交给 Git LFS 或制品库;如果只是日常开发,编译产物不入库,发布时再归档到制品库更合理。同理,.hex/.mif 如果是手工维护的初始化数据,应入库;如果是脚本生成的,可忽略并保留生成脚本。

第二步是配置 Git LFS。LFS 的原理是:仓库中只保存一个文本指针,真实文件内容存放在 LFS 服务器。克隆时按需拉取,避免仓库体积爆炸。配置命令如下:

git lfs install
git lfs track "*.sof"
git lfs track "*.pof"
git lfs track "*.jic"
git lfs track "*.qar"
git lfs track "*.qdb"
git lfs track "*.hex"
git lfs track "*.mif"
git add .gitattributes
git commit -m "chore: 配置 Quartus 二进制文件 LFS 跟踪"

执行后,.gitattributes 中会出现类似 *.sof filter=lfs diff=lfs merge=lfs -text 的行。这里 -text 很关键,它告诉 Git 不要把这些文件当文本处理,避免换行符转换破坏二进制内容。对于 SVN,对应做法是设置 svn:mime-type 为 application/octet-stream,并利用 svn:ignore 忽略 db/、output_files/ 等目录。SVN 没有 LFS 那样的指针机制,大文件会直接进入仓库,因此更依赖“目录分离”:把源码目录和制品目录拆开,制品目录单独用文件服务器或制品库管理。

判断一个文件是否该入库,可以问三个问题:它是否由人编写或维护?它是否无法由其他文件重新生成?它是否小到可以接受每次提交都存一份?三个答案都是“是”,就入库;否则忽略或走 LFS。

第三步是处理 Quartus 工程文件的“可合并性”。.qsf 是文本,但它是按行排列的赋值语句,多人同时修改不同行时 Git 通常能自动合并;但如果两人同时修改同一行(例如同一个 set_global_assignment),就会冲突。建议把 .qsf 按功能拆成多个 .qsf 文件,用 source 语句在主 .qsf 中引入,例如 source ./constraints/timing.sdc、source ./assignments/pin_assignments.qsf。这样不同人改不同文件,冲突概率大幅下降。同理,.sdc 约束文件也建议按时钟域或功能拆分,避免一个几千行的巨型约束文件成为合并瓶颈。

第四步是建立“版本—交付”对应关系。每次发布给测试或生产,应打一个 Git tag,例如 v1.2.0-rc1,并在制品库中保存对应的 .sof、.pof、.jic 和 .qar。tag 指向源码树,制品库保存二进制,两者通过版本号关联。这样回溯时,既能找到当时的源码和约束,也能找到当时实际烧录的文件。对于 SVN,可以用 svn copy 打 tag 目录,但同样建议把大文件放到独立制品库,避免仓库膨胀。

不要把 db/ 和 incremental_db/ 提交到仓库。它们包含编译缓存和增量编译数据库,体积大、平台相关、无法跨机器复用,且会随每次编译变化,导致大量无意义 diff。忽略它们后,新克隆的工程第一次编译会慢一些,但换来的是干净、可重建的仓库。

最后给出一个可执行的检查清单,用于在项目初始化时逐项确认:

检查项正确做法常见错误
源码与约束入库,按功能拆分文件全部塞进一个 .qsf
编译中间产物写入 .gitignore / svn:ignore提交 db/、output_files/
编程文件LFS 或制品库,按发布版本归档直接提交 .sof 导致仓库膨胀
初始化文件手工维护的入库,脚本生成的可忽略把生成物当源码提交
版本标记Git tag / SVN tag 目录 + 制品库版本号只靠文件名区分版本

把以上策略落地后,Quartus 工程的版本管理会呈现清晰的分层:文本层负责逻辑与约束的演进,二进制层负责可烧录成果的归档,忽略层负责把可再生成内容挡在仓库之外。Git 与 SVN 的选择不再是“二选一”的站队,而是根据团队分支模型、大文件比例和交付流程做出的工程决策。