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”三种处置路径。它不替代正文,而是帮助你在实际工程中快速判断每个文件该走哪条路。
把上图落到实际工程,第一步是写 .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 那样的指针机制,大文件会直接进入仓库,因此更依赖“目录分离”:把源码目录和制品目录拆开,制品目录单独用文件服务器或制品库管理。
第三步是处理 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 的选择不再是“二选一”的站队,而是根据团队分支模型、大文件比例和交付流程做出的工程决策。