1. 背景:lint脚本在数字流程里到底解决什么问题
先交代一下我为什么会对“spyglass lint脚本”这种不起眼的题目专门写一篇东西。SpyGlass这个工具,做数字IC前端的人应该都不陌生,Synopsys家的静态检查工具,原来Atrenta的招牌产品,被收编之后在RTL检查领域依然是主流选择之一。Lint检查本身也不复杂:读入RTL代码,按规则扫描,报出代码里可能存在的功能隐患、编码风格问题、跨时钟域风险,然后输出一堆报告。听起来就像是“代码规范检查器”,但真正在项目里跑过的人都知道,lint这事想干好并不容易,尤其是当设计到一定规模之后。
我刚接触SpyGlass那会儿,用的是纯GUI操作,打开工具、建工程、添加文件、选顶层、选规则集、点执行、看报告,整个过程大概十分钟能走完。等RTL代码更新了一版,又得重新把这些步骤按一遍,偶尔忘了加某个文件,或者选了不一样的法典库,报告结果就和上次对不上了,一次回归下来老觉得不踏实。后来部门要求把所有检查都脚本化,方便回归到CI里统一跑,我才意识到一个问题:lint检查本身不是难点,难的是围绕lint检查搭一套稳定的、可复用的脚本流程。
这篇内容就是把我在实际项目中整理SpyGlass lint脚本的完整过程拆开讲,包括环境怎么配、RTL怎么读、规则怎么定、违规报告怎么解读,以及后来怎么把它接到回归流程里。适合正在入门数字前端的新人,也适合已经有几个项目经验、想把手动操作沉淀成脚本的工程师。你会看到一份能直接改一改用起来的脚本骨架,也会看到我在使用过程中踩过的一些坑。
1.1 为什么选择SpyGlass而不是其他lint工具
数字IC行业里能跑lint检查的工具其实不少,Cadence的Hal、Synopsys的nLint、开源的一些Verilog lint工具也都在用。但SpyGlass在静态验证这个细分市场上用得确实很广,原因倒不是因为它的lint比别家厉害多少,而是它把lint、CDC、约束检查、功耗意图检查这些都整合在了一个框架里,规则体系非常庞大,签核级别的检查能力比一般开源工具完整得多。
在实际项目里,lint只是SpyGlass流程的第一站。RTL写完先做Lint检查,之后做CDC检查、约束检查,再往后还可以接Formality或者VCS做动态仿真。工具入口统一,规则配置、学问库、豁免文件都可以共用一套,脚本也就不用每个环节重新写一遍。对做模块级签核的工程师来说,同一个框架下能把静态检查链路拉通,价值就体现出来了。
还有一点是SpyGlass的规则可定制性很强。默认规则集覆盖了绝大多数RTL问题,但你完全可以基于项目需要裁剪规则、调整严重级别、把某条规则对某个信号豁免。这种灵活性只有脚本化才能最大化,图形界面点来点去也能配置,但配置完没法在代码评审里审查,没法用diff看谁改了什么,出了问题回溯起来很被动。
1.2 脚本化与GUI操作的本质差别
我见过不少工程师用SpyGlass的习惯是打开GUI、点鼠标、看报告、关掉。这种工作方式如果只是临时看看一个模块有没有明显问题,倒也没什么不行,但一旦进入项目流程,问题就来了。我举个例子:你跑完一次lint,发现上面报了200条violation,你想改几行代码再跑一遍对比一下,这时候在GUI里重新执行一次其实也能做,但它不会自动告诉你这次和上次相比新增了哪些violation、消除了哪些violation。脚本化之后,你可以把每次报告存成带时间戳的文件,用diff命令一对比,改动的影响一目了然。
脚本化最关键的价值还是可重复性和可审查性。一个RTL项目从代码形成到流片签核,lint检查至少要跑几十轮,每轮用的规则应该是确定的,不能这个工程师用一套规则、那个工程师又改了另外一套。脚本把它固定下来之后,谁提交的脚本都一样,结果自然一致。再加上脚本本身可以放在Git仓库里管理,每一行配置改动都有迹可循,这在团队协作里是刚需。
可以这样对比一下GUI和脚本的区别:
| 维度 | GUI手动操作 | 脚本化运行 |
|---|---|---|
| 回归次数 | 适合单次检查,重复跑效率低 | 可反复执行,适合每日回归 |
| 配置一致性 | 依赖操作者记忆 | 脚本统一,结果可复现 |
| 规则变更管理 | 改动不透明难追溯 | 代码评审即可审查变更 |
| 多模块并行 | GUI一次只能看一个视图 | 脚本可以批量并发跑 |
| 持续集成 | 基本不可能接 | 天然适合CI流水线 |
所以我的结论很明确:lint检查一定要脚本化,这件事应该在项目流程建立初期就做掉,不要等到RTL规模大了才补。
2. 写脚本之前必须准备的环境和工程目录
脚本这个东西,很多人以为就是写几行命令让工具跑起来就行,真正落地的时候才发现,环境、目录、签名文件这些“基本功”没做扎实,脚本写再漂亮也跑不通。我在这个项目里面把准备工作分成三块:工具环境、目录规范、SGDC约束文件。
2.1 工具环境的初始化
SpyGlass本身是个商业EDA工具,安装流程各个公司都有自己的管理方式,这里不展开太多,只说脚本启动时必须要处理好的几件事。首先是工具的安装路径和版本:先在shell里加载工具的环境变量,通常是通过source一个setup脚本或者module load的方式实现。比如在bash环境下,常见的做法是:
# 加载SpyGlass运行环境 source /opt/synopsys/scripts/setup_spyglass.sh # 确认版本 spyglass -version在脚本里加上版本检查是个好习惯。不同版本的SpyGlass在命令语法和规则名称上会有细微差异,如果某一天你从2020版本升到2023版本,很多原来写好的命令可能就废弃了。我在实际项目里就遇到过,老脚本里用的new_project在新版本里被换成了project new,如果不做版本检查,脚本在升级后的环境下可能完全跑不起来。
除了工具本身,还要确认许可证环境没有问题,在批量跑回归的时候,如果多个job同时启动,License额度不够会导致部分任务等待甚至失败。我习惯在脚本里加一个简单的前置检查,用lmstat看一下对应feature是否可获取,不行就提前报错,而不是等到SpyGlass跑了一半才告诉你License获取超时。
2.2 工程目录结构怎么规划
一个lint脚本项目,表面上只有一个tcl脚本,但它依赖的其他文件如果东放一个西放一个,后面维护起来就是一场灾难。我推荐用下面这种目录结构:
lint_project/ ├── rtl/ # RTL源文件(或者这里的软链接指向真实代码目录) ├── sgdc/ # SpyGlass设计约束文件 ├── scripts/ # 存放主脚本和辅助脚本 ├── waiver/ # 豁免文件 ├── rpt/ # 输出报告 ├── log/ # 运行日志 └── work/ # 工具运行产生的中间文件这里有几点值得说一下。rtl目录我一般用软链接而不是拷贝,因为RTL代码每天都在更新,拷贝一份意味着你去维护同步逻辑,没意义。sgdc目录放的是SpyGlass的设计约束文件,类似SDC但格式是SpyGlass自己的,里面会定义时钟、复位、端口属性这些信息,lint和CDC检查都要用到。waiver目录存放豁免文件,这个后面专门讲。
work目录是个容易忽略的地方。SpyGlass跑起来会产生大量中间数据库文件和缓存,如果不隔离到一个单独目录,每次跑完工作区会乱成一团。直接指定一个可清理的work目录,脚本开头清一次、结尾清一次,整个工作区干干净净。
2.3 SGDC约束文件千万别漏掉
我最早用SpyGlass的时候犯过一个错误:觉得lint检查就是读代码、跑规则,不需要什么约束。结果一跑,报告里出现大量莫名其妙的违规,比如把复位信号当成普通数据信号分析、把跨时钟域的同步寄存器报成异步问题。原因很简单,工具不知道哪些信号是时钟、哪些信号是复位、哪些端口之间有约束关系,它只能按默认逻辑去猜。
SGDC文件就是用来把这些信息告诉工具的。一个基础的SGDC文件类似这样:
# top.sgdc set_clock -domain clk_a -name clk set_clock -domain clk_b -name clk_b set_async_reset -name rst_n set_sync_reset -name rst_sync set_port -name rst_n -reset set_port -name clk -clock set_clock_group -async -group clk_a -group clk_b这份文件告诉SpyGlass:设计里有两组异步时钟,rst_n是异步复位信号,等等。写完之后用read_file -type sgdc或者add_design_constraints命令把SGDC读进去,lint检查就会基于这些信息做分析。
千万不要图省事不写SGDC直接跑lint,那样出来的报告参考价值大打折扣。CDC相关的检查比如跨时钟域路径没有打同步器、异步信号直接接入组合逻辑,这些检查必须依赖时钟域的约束信息才能判断,没有SGDC的话SpyGlass根本不知道哪两个时钟属于不同域。
3. Lint脚本的核心结构:从读文件到输出报告
前面准备工作做完,终于到了核心环节:写SpyGraph lint脚本本身。我把整个脚本的执行流程概括成六步,这也是SpyGlass lint检查的标准工作流,几乎每个项目的脚本都跳不出这个结构。
3.1 第一步:清理工作目录和旧结果
脚本第一步一定是清理。如果不清理,上次运行的数据库、报告就会残留在work目录里,一方面影响运行速度,另一方面可能让新报告混入旧数据。我习惯在脚本开头显式删除work目录和rpt目录,然后用file mkdir重建:
# 清理并创建输出目录 file delete -force work rpt log file mkdir work rpt log这一步看起来不起眼,但它保证了每次结果都是干净的,尤其是接入回归后每天自动跑,旧数据污染这个问题会严重影响你可信度。回归结果一旦飘忽不定,排查起来非常耗时,不如一开始就把环境清干净。
3.2 第二步:创建工程并读入RTL设计
接下来是创建SpyGlass工程,然后读入所有的RTL文件。这里我给出一个常见版本的命令写法,新版本项目里可能会用project new替代new_project,基本思路一致,你需要根据自己工具版本微调:
# 创建工程,指定顶层模块 new_project -name lint_top -top top_module -force # 读入RTL文件 read_file -type verilog -y [list \ ../rtl/top.v \ ../rtl/ctrl.v \ ../rtl/datapath.v \ ../rtl/fifo_wrapper.v \ ]-type verilog -y表示读入的源文件格式和对应的库目录,如果你的设计还用到了其他格式比如SystemVerilog或者加密的源文件,这里的type也要相应调整。读文件这里有两个常见坑。
第一个坑是文件顺序问题。SpyGraph读文件时如果某个文件里引用了另一个模块,而那个模块还没读进来,就会报“module not found”之类的错误。有些老版本的SpyGraph依赖用户手动保证文件顺序,后来的版本有了自动解析,但为了稳定起见,我在脚本里仍然会按照依赖顺序排列文件列表,模块先从叶子节点读起。
第二个坑是物理文件路径很长的项目,Windows环境下路径太长会触发系统限制,Linux下情况好一些但也要注意。我一般用相对路径,并且把脚本固定存放在scripts目录,这样RTL路径就可以统一写成../rtl/xxx.v,不需要在多个地方维护绝对路径。
3.3 第三步:设置顶层和关键选项
读入文件之后,需要明确这个工程的顶层模块是什么,另外还有一些选项需要一并设置。这里的new_project命令创建工程时其实已经指定了顶层,但为了保险起见,我习惯再调用一次set_option显式设置:
# 显式指定顶层模块 set_option top top_module # 设置语言版本和优化选项(选填) set_option sverilog 1 set_option stop -no_elaborate 0set_option sverilog按你的设计实际情况决定要不要开,如果设计里用了SystemVerilog语法,这里必须打开,否则语法解析阶段就会大量报错。stop和no_elaborate这两个选项我是从工具文档里看来的,它们控制elaboration阶段的行为,设计规模大的时候可以用no_elaborate加快运行速度,但也可能导致某些需要完整展开才能报出的检查结果丢失,所以我的原则是小模块全量编译,大模块按需调整。
3.4 第四步:选择lint规则和规则集
这是整个脚本里最核心也最有讲究的一步。SpyGraph自带非常多的规则,按功能可以分成:RTL lint规则、CDC规则、功耗规则、跨时钟约束规则。lint脚本里你当然可以不加选择地全部跑一遍,但那样报告会非常庞大,有效信息淹没在海量违规里,反而影响判断。
我通常会把规则分成两类来配置。第一类是代码结构和风格检查,比如未使用信号、多驱动、锁存器推断、位宽不匹配这类,这些属于“必查项”;第二类是CDC相关的专项检查,比如异步时钟域之间的路径分析、同步器是否存在、复位信号是否正常同步,这些通常在单独的CDC流程里跑,但lint阶段也可以打开一部分快速筛选。
选择规则的命令在不同版本里也有差异,常见写法如下:
# 按规则集中执行lint检查 set_goal -task lint # 也可以指定规则列表,精确控制执行范围 set_option -enable_rule {W415 W302 W305}按照我的经验,项目早期可以跑全量规则,让工具把所有疑似问题都暴露出来,然后根据报告一件件判断哪些需要处理、哪些可以豁免。到了项目后期,规则集就要收敛,只跑固定一套经过评审的规则,这样每次跑出来的报告才有增量意义。
规则名的差异问题这里也提一下:不同版本SpyGlass对规则命名不完全一致,W415、W302这类编号规则在老版本里常见,新版本可能换成了更语义化的名字。所以脚本里写规则名之前,最好先到工具自带的Rule Reference文档里确认一下当前版本的命名。
3.5 第五步:执行检查
规则配好之后,真正执行检查的命令反而是最没技术含量的一句:
perform_lint这个名字在多数版本里都能用,少数新版本里会改成run_lint或者check_lint,具体以你手头工具为准。执行过程中,工具会先对设计做elaboration,然后运行规则引擎,整个过程会输出大量日志信息到控制台。在写自动化脚本时,我建议把这一步产生的日志重定向到log目录,方便事后排查:
perform_lint > ../log/lint.log3.6 第六步:输出报告
最后一步是生成报告。SpyGraph的报告输出格式很丰富,文本、HTML、XML都有,还可以和已有waiver合并后重新生成报告。我最常使用的是文本报告,因为方便直接diff,也方便集成到邮件通知或者网页展示里。
# 生成违规报告 report_violations -txt ../rpt/lint_violations.rpt # 生成豁免报告(当前自动豁免和手动豁免的清单) report_waiver -txt ../rpt/lint_waiver.rpt到这里,一个最原始的lint脚本就成型了。执行完这六步,输出目录里已经有了违规报告,接下来要做的工作就是解读报告、处理违规、维护豁免文件。
4. 一份可以直接参考的完整Lint脚本示例
光说不练假把式,这节我把一套在自己项目中实践过的完整SpyGraph lint脚本骨架贴出来,再逐段解释关键逻辑。你可以直接拷贝然后根据自己的设计改名、补文件列表,基本就能跑起来。
4.1 主脚本文件:run_lint.tcl
#!/usr/bin/env spyglass # ============================================================ # run_lint.tcl # 功能:基于SpyGlass执行RTL Lint检查 # 用法:spyglass -batch -f scripts/run_lint.tcl # ============================================================ # 0. 清理旧数据 file delete -force work rpt log file mkdir work rpt log # 1. 创建工程 new_project -name lint_top -top top_module -force # 2. 读入RTL源文件 read_file -type verilog -y [list \ ../rtl/sub_module_a.v \ ../rtl/sub_module_b.v \ ../rtl/top_module.v \ ] # 3. 设置选项 set_option top top_module set_option sverilog 1 # 4. 读入SGDC约束 add_design_constraints ../sgdc/top.sgdc # 5. 配置规则并执行lint检查 set_goal -task lint set_option -enable_rule {W415 W302 W305} perform_lint > ../log/lint.log # 6. 输出报告 report_violations -txt ../rpt/lint_violations.rpt report_waiver -txt ../rpt/lint_waiver.rpt # 7. 收集概要信息到log puts "Lint finish, report saved to ../rpt/lint_violations.rpt"这段脚本其实没有高级技巧,就是老老实实把前面六步串了起来。需要注意的一点是脚本开头的#!/usr/bin/env spyglass,在实际运行中不一定需要通过把脚本置为可执行来调用,更多时候是配合外壳脚本用命令行的方式执行,这行只是标注用途,方便读者理解。
4.2 壳层脚本:run_lint.sh
光有一个Tcl脚本还不够,我一般会再包一层shell脚本,用来加载工具环境、调起SpyGlass、处理退出码。这样后续接CI的时候,只需要调用这个shell脚本即可,不用关心内部细节。
#!/bin/bash # run_lint.sh - 通过shell包装脚本执行spyglass lint # ============================================== set -e cd "$(dirname "$0")/scripts" # 加载工具环境(按现场环境调整) source /opt/synopsys/scripts/setup_spyglass.sh # 执行lint,传递tcl脚本路径 spyglass -batch -f scripts/run_lint.tcl # 检查报告是否生成 if [ ! -f ../rpt/lint_violations.rpt ]; then echo "[ERROR] lint report not generated!" exit 1 fi echo "[INFO] lint completed successfully" exit 0这里用到的spyglass -batch -f是最常用的命令行模式,意思是后台非交互方式运行,直接执行指定Tcl脚本。set -e会让shell在中间步骤出错时立即退出,避免后续拿着一个残缺报告继续跑。
4.3 运行过程和日志的观察要点
脚本跑起来之后,你至少需要盯三个指标:第一,elaboration阶段是否有编译错误,有错误后面规则再多也是白跑;第二,perform_lint结束是否报出“0 error”类似的信息,这个信息告诉你规则引擎有没有内部异常;第三,报告文件是否生成、文件行数是否合理,如果报告是空的,反过来要怀疑是不是顶层选错了。
我习惯在脚本里额外加一行命令,把退出码传出来:
# 用exit code让外层shell判断结果 if {[file size ../rpt/lint_violations.rpt] > 0} { exit 0 } else { exit 1 }这样一个简单的文件大小检查,就能让稍后接入的CI系统对失败结果做出不同反应。
5. 报告解读和Violation处理实战
脚本跑通了只是第一步,真正的挑战在后面:打开报告,面对几百上千条violation,怎么判断哪些是真问题、哪些可以豁免、哪些需要改代码。这节分享我处理lint报告的一套方法。
5.1 怎么用速度最快的方式读一遍违规报告
一份典型的文本报告长这样:
Rule: W415 Severity: Warning Message: Signal 'data_valid' in module 'top_module' is not used. File: ../rtl/top_module.v Line: 87 Hierarchy: top_module/u_ctrl你需要关注的字段其实就四个:规则名、严重级别、信号名/实例层次、代码位置。我看报告的流程是先按严重级别排序,error级别的问题最优先处理,因为这些通常意味着设计功能性错误;然后看warning,重点判断是否会导致综合阶段出现问题;最后是一些info级别的提示,可以直接归档。
5.2 常见的Violation类型和对应处理方式
我在项目里碰得最多的一类违规是“信号未使用”和“信号未驱动”。这类问题通常不是严重bug,但它往往意味着代码里存在多余的逻辑,或者某个信号应该接到别的地方但漏接了。这时候要靠层次定位去看具体实例,如果确实是设计里用不到的信号,我建议连同代码里的声明一起清理掉,而不是在waiver里直接豁免。因为一个“Unused信号”背后可能藏着一个漏了连线的模块,你不处理它,模块间的独立设计和连接关系就可能出问题。
第二类是锁存器推断,报告里通常会显示类似“Latch inferred in combinational block”的信息。这个问题在组合逻辑里出现时,往往是因为if或者case分支没有写全。虽然有些设计是故意用锁存器,但大多数情况下它不是设计意图。建议先检查代码分支完整性,用默认分支把未覆盖情况处理掉。我早期处理这类问题就吃过亏,想着直接把锁存器豁免了,后来综合的时候工艺库提示时序收敛困难,才知道锁存器的时序建模很麻烦,最好还是在RTL阶段就消除。
第三类是位宽不匹配。SpyGraph会检查模块端口和信号连接时两边位宽不一致的地方,这类问题在仿真里通常不会报错,但综合时会自动补位或截断,可能导致功能错误。看到这类violation不要懒,老老实实把位宽对齐,尤其是跨模块连接的信号。
第四类是多驱动问题,多个驱动源同时给一个信号赋值。这个问题如果出现,等于告诉你有两个模块在打架,谁抢到总线谁说了算,在仿真时可能看不出问题,但FPGA上板或者流片后行为不可预期。遇到多驱动报告,我的建议是把它当成最高优先级bug处理,绝不能放进豁免清单。
下面这个表是我根据项目经验整理的常见violation速查表:
| Violation类型 | 常见规则号示例 | 风险等级 | 处理建议 |
|---|---|---|---|
| 未使用信号 | W415 | 低 | 清理代码,若意义不明需要确认后再删 |
| 未驱动信号 | W416 | 中 | 检查是否漏接,确认后补连接或改正逻辑 |
| 锁存器推断 | W305 | 中 | 补全if/case分支,消除组合锁存 |
| 位宽不匹配 | W528 | 中 | 对齐位宽,使用拼接和截位时显式说明 |
| 多驱动信号 | W302 | 高 | 必须解决,两个driver同时赋值会有致命隐患 |
| 跨时钟域无同步 | CDC类 | 高 | 加同步器或用GRC标准单元,规范约束 |
这份表格不是死的,不同项目不同工艺对每类问题的容忍度完全不同,但处理原则一致:风险等级高的violation一条都不能放过,宁可多花时间改代码,也不要留在waiver里。
5.3 Waiver豁免文件:合理使用而不是滥用
一个lint流程如果没有豁免机制,那它基本没法在真实项目里落地。因为RTL代码里总会有些故意违反规则的地方,比如为了性能优化手动驱动的信号、为了和IP对接表现一些非标准时序约束等。这些情况属于“已知且合理”,应该通过waiver文件让工具在后续报告中不再重复报出,从而让报告只反映“新出现的问题”。
写waiver文件最常见的格式是在工具里通过命令设置,然后再导出成waiver文件保存:
# 在脚本中增加的豁免示例 set_rule_status -rule W415 -signal {data_valid} -status waive report_waiver -txt ../rpt/lint_waiver.rpt这里的关键是:豁免一定要写清楚理由。我在实际项目中见过一种情况,一个模块的waiver文件积累了上百条记录,谁也不知道当年为什么豁免,后来代码重构时这些豁免记录带来大量困扰。所以我的建议是每一批waiver都附带注释,并且在代码评审阶段就同步审查waiver变更,不能只看RTL代码变更却忽视了waiver的变动。
有时候豁免申请会来自其他组,例如IP供应商提供的代码为了性能做了特殊处理,他们给出的waiver总是覆盖了很多规则。这时我的做法是:把IP相关的waiver单独放一个文件,不做全局豁免,这样在报告里IP部分的violation几乎没有,而自有代码的violation依然全部暴露。
6. 后续演进:脚本集成、并行回归与团队协作
一套SpyGraph lint脚本在单机单任务上跑通之后,还有一个很重要的阶段是把它放进团队的持续集成里,让每次代码提交都自动触发lint检查。
6.1 集成到回归流水线的几个要点
最基础的集成方式是在代码管理平台的流水线脚本里加一个“Lint”阶段,调用前面写好的run_lint.sh。跑完如果返回非零退出码,流水线失败,提交被拦下;返回零则继续后续仿真阶段。
为了处理好大量的报告,我建议不只保留一份最终报告,而是把当天和昨天的报告放到同一个目录下做个diff,这样提交者收到的信息就是“新增了多少violation、修复了多少violation”,一眼能找到自己那笔改动带来的影响。这个diff逻辑可以放在shell脚本里,不需要复杂的工具,一条diff -u命令就能解决。
另外要注意的是并发问题。如果项目里模块很多,同一时间可能有几十个lint任务在跑,License资源必定紧张。此时要在流水线层面控制并发数量,我在团队里是把lint任务按模块分组,一个job处理一个模块,并发数控制在License可用数以下,避免任务排队太久。
6.2 增量检查的策略
RTL代码量大了之后,每次都全量跑lint确实太浪费时间。我在项目中期就开始使用增量模式,让工具只分析变更过的模块和与之有依赖关系的模块。实现方式可以在脚本里传入一个参数,列出本次修改的文件列表,然后只读入这些文件及其上下游文件。
增量模式的实现细节依赖工具的版本支持,不同版本差异很大。这里我只说通用原则:增量检查适合在每日回归里使用,不能替代全量检查。建议项目每周至少做一次全量lint,用来兜底那些增量模式覆盖不到的问题。
6.3 一套脚本多人协作的注意事项
最后聊聊团队协作。脚本本身也要有版本管理,这不用多说,真正需要注意的是脚本配置的“集中化”和“差异需求”之间的平衡。不同模块可能对某条规则有不同需求,如果各自改脚本,长期下去脚本就发散得没法维护了。
我的做法是维护一个公共的规则集配置文件,所有人默认用同一套规则,个别模块需要放宽规则时通过当前模块自己的waiver或者局部配置来解决,不改动公共文件。这样既照顾了不同模块的特殊性,又保证了公共流程的一致性。
具体到脚本文件,我会把“哪些文件参与检查、顶层是哪个、使用哪些SGDC”这种因人而异的部分单独放到一个config文件里,主脚本只用读config,不做硬编码。团队里其他人接手的时候,只需要改config就行,不用深入理解tcl代码本身。
7. 一点个人经验总结
SpyGraph lint脚本这件事,做起来门槛不高,但想做到稳定、可维护、对团队有价值,需要下不少功夫。我自己的体会是,脚本中最容易被忽视、也最值得多花时间去打磨的,不是那些花哨的Tcl技巧,而是规则集的取舍和waiver的管理。规则太严,每天报告几百条,团队迟早会麻木,对违规提示视而不见;规则太松,漏掉真问题,签核的时候又得回头补课。最好的状态是让报告里出现的每一条都值得看,这样项目组成员才会认真对待lint输出。
另外,lint脚本永远要跟着设计流程一起演进。设计从模块级到芯片级,约束脚本和waiver都在变,没有一份脚本能一劳永逸。我每隔一段时间就会把现有的waiver文件重新审视一遍,删除那些已经失去意义的豁免,保留真正需要的,保证报告的“信噪比”不会越来越差。
写这篇内容的时候,我把自己之前踩过的坑和处理思路都翻了出来,希望对你搭自己的SpyGraph lint脚本有点帮助。真要动手做起来,并不复杂,先搭个能用的最小闭环,再逐步丰富规则集和waiver,你也会走进“跑lint靠脚本、看报告看增量、收尾有归档”的稳定节奏里。