第二章:版本控制(Git)在RTL项目中的使用

版本控制这事儿,很多做RTL的兄弟一开始不太当回事。我刚开始带项目的时候,见过有人用final.v、final_v2.v、final_really_final.v这种命名法……嗯,后来仿真出问题查了三天,才发现是版本搞混了。

说白了,Git就是你的后悔药和时光机。在CI流水线里,它更是整个自动化的起点。没有规范的Git管理,流水线跑得再快也是乱的。

核心要点:RTL项目的Git管理,跟纯软件不一样。我们关心的是:仓库结构怎么搭?什么该提交什么不该提交?分支怎么走?commit怎么写?
RTL项目Git仓库结构 & 分支策略总览 远程仓库 (origin) main (主分支) develop (开发分支) release/* (发布分支) 本地开发 (Local) feature/* fix/* hotfix/* 标签 (Tags) - 发布里程碑 v1.0.0 v1.1.0 v2.0.0-rc1 → 对应流片/发布点

仓库结构怎么搭

我建议一个RTL项目对应一个Git仓库。别把几十个IP核塞到一个仓库里,后期CI跑起来你会哭的。

典型的RTL仓库结构长这样:

project_name/
├── rtl/                # 设计源代码
│   ├── top/
│   ├── module_a/
│   └── module_b/
├── sim/                # 仿真相关
│   ├── tests/          # 测试用例
│   ├── tb/             # testbench
│   └── waves/          # 波形(一般不提交)
├── scripts/            # 编译、仿真、综合脚本
├── constraints/        # SDC约束文件
├── doc/                # 设计文档
├── .gitignore
└── README.md

这里有个坑。我曾经见过一个团队把sim/waves/下的vcd波形文件全部commit了,仓库活生生撑到2个G。每次clone都要等半天。

小提示:rtl/目录下只放.v、.sv源文件。IP核的预编译库、仿真产物,全部放到.gitignore里。

.gitignore配置——绕不开的学问

一个设计良好的.gitignore文件,能帮你省很多烦心事。我习惯从一开始就配置好,免得后面有人不小心把中间文件推上去。

RTL项目常用的忽略规则:

# 仿真生成文件
*.vcd
*.vpd
*.fsdb
*.wlft*
*.log
*.dump

# 综合/实现产物
*.ddc
*.mdd
*.sdc.out
*.vg         # 网表
*.svf        # 形式验证文件
*.syn*

# 编译中间文件
*.o
*.obj
work/        # ModelSim/VCS工作库
xsim.dir/    # Vivado仿真目录
.cache/

# IDE临时文件
*.swp
*.swo
.DS_Store
Thumbs.db

你想想看,要是一不小心把综合出来的网表提交了,review时大家对着几千行的gate-level代码看差异……那画面太美我不敢看。

注意:.gitignore配置好之后,先commit它。不然已跟踪的文件不会自动忽略,得用git rm --cached手动处理。

分支策略——选对路子,少走弯路

在CI流水线里,分支策略决定了自动化触发的时机。我推荐基于Trunk-based或Git Flow的变体,适合RTL项目。核心原则就一条:main分支永远是可流片/可发布的。

常用的分支角色:

分支类型命名示例谁来用CI触发什么
功能开发feature/spi-master设计师Lint + 基本仿真
Bug修复fix/timing-violation设计师回归测试
发布准备release/v2.1项目组全量回归 + 综合
紧急修复hotfix/reset-glitch资深工程师快速Lint + 全量仿真

我个人习惯用develop作为日常集成分支。每个feature分支开发完后,向develop发起Pull Request。PR通过后,CI自动触发回归测试。main分支只接受release或hotfix的合并。

有一次项目赶工期,有人直接往main上push了未验证的代码。流水线一跑,Lint报了两百多个warning,仿真直接挂掉。从那以后,我强制开启了main分支的protected规则——没有review和CI通过,谁都别想合并。

避坑指南:我曾经因为hotfix没走review,直接把改动的模块从main合并到feature分支上,结果feature分支的代码被覆盖了大半。所以hotfix也要走PR,区别只是Review可以加速但不是跳过。

代码提交规范——写commit也是一门手艺

很多RTL工程师写commit message写得很随意,比如fix bug、update。过两个月回来看,根本不知道改了啥。在CI流水线的上下文里,好的commit message能帮助自动生成变更日志,甚至影响自动化工具的决策。

我推荐使用Conventional Commits规范,稍作调整适配RTL场景:

类型(范围): 一句话描述

详细说明(可选,包括为什么改、怎么改、影响哪些模块)

Closes #123

类型可以用这些:

  • feat —— 新功能模块
  • fix —— Bug修复
  • perf —— 时序/面积优化
  • refactor —— 代码重构,功能不变
  • test —— 增加测试
  • doc —— 文档更新
  • ci —— CI配置变更

举个例子:

feat(axi_slave): 实现AXI4-Lite从设备接口

- 添加读写通道FSM
- 支持burst长度1~16
- 增加地址对齐检查

Closes #45

为什么要这么写?因为在CI流水线里,工具可以自动解析commit message,决定是否触发特定环节。比如commit类型是feat,就触发全量回归;类型是doc,就只同步文档到wiki。

我的习惯:每次commit的变更量不要太大。一个commit只做一件事,改一个模块的几行。这样review好做,出了问题时git bisect也能精确定位到具体修改。

嗯,最后说一点。Git不是存代码的仓库,它更是团队的沟通工具。好的分支策略和commit规范,能让CI流水线跑得更聪明,也能让合作更顺畅。