尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

VC Spyglass Lint检查TCL脚本自动化实践指南

VC Spyglass Lint检查TCL脚本自动化实践指南 做数字IC设计这一行的对VC Spyglass应该都不陌生。每次提到Lint检查它基本是默认选项尤其是RTL编码阶段。但很多人用VC Spyglass都停留在“跑一下看报告改代码”这种手工流程几十个文件还能应付一旦模块多了、版本迭代快了手工打开GUI点按钮的效率就太低了。这篇博文记录了我在实际项目中把VC Spyglass的Lint检查从手工操作改造成TCL脚本自动化的完整过程包括脚本怎么写、报告怎么解析、怎么集成到回归流程里以及踩过的几个坑。适合做数字前端、验证、以及负责流程集成的人参考。要说清楚这件事得先理清VC Spyglass和Lint之间的关联。VC Spyglass是Synopsys旗下专门做RTL静态检查的工具Lint只是它的一个goal也就是目标检查项。其他还有CDC跨时钟域、DFT、功耗分析等goal。但Lint是日常最常用、也是最好自动化的一个因为它的本质是拿代码去和一组静态规则做对比不涉及仿真激励跑得快、结果稳定非常适合脚本化。1. Lint检查在数字IC设计流程中的定位别把它当成可有可无的辅助很多刚毕业、或者从写软件转到写RTL的工程师对Lint检查的第一印象是“这玩意就是查语法错误”。这个理解不能说错但严重低估了它的价值。Lint检查的内容远不止语法它还包括编码风格、可综合性、跨模块引用、位宽匹配、时钟处理、异步复位同步释放等等。这些规则往往是芯片流片前最容易出问题、最容易被仿真漏掉的部分。举个最典型的例子仿真器对未初始化变量的默认值处理在RTL仿真和真实芯片行为之间可能存在差异。仿真时信号初始化为0可能掩盖了某个路径上寄存器没有复位的问题但Lint规则会有专门一项检查寄存器是否在复位阶段赋值。这类问题不是在功能仿真里能轻松发现的而且越是复杂的逻辑越容易漏。也就是说Lint是芯片质量保证里比较靠前、但不可替代的一层过滤网。在流程定位上Lint检查一般放在综合之前甚至要在模块级仿真之前就做掉。因为综合和仿真的成本都比Lint高很多如果等到芯片综合完了才发现代码里有可综合性问题改动的代价会大很多。真正合理的流程应该是RTL写完一版立刻跑LintLint干净之后再进仿真、综合、后端。实际上大公司里这一关往往是PRPull Request合并的门禁之一代码不过Lint根本不允许合入主干。正是因为这个定位Lint要被反复跑、频繁跑而且每个版本迭代都要跑。手工一次两次还能忍一旦每天来回几十个模块自动化的需求就非常强烈了。后面说的TCL脚本自动化本质上就是把这个频率高、重复性强的静态检查过程彻底变成一个无人值守的流程。2. VC Spyglass的Lint检查机制理解规则、工程文件和Goal的关系2.1 Lint检查的规则分类VC Spyglass的Lint检查说到底是基于一组规则集。这些规则会被归类到不同的goal里面Lint goal里包含的规则覆盖了RTL设计的常见问题领域。我大致梳理一下平时接触最多的规则类别有语法与可综合性检查比如敏感列表不完整、阻塞赋值与非阻塞赋值混用、不可综合的语法结构initial、fork/join、动态数组等。位宽与类型检查多比特总线之间赋值位宽不匹配有符号和无符号混用。跨模块检查顶层实例引用的端口对齐、未声明的信号、悬空输入输出。编码风格规范统一的命名总线风格、对齐方式、命名规范等。时钟与复位相关时钟信号定义、门控时钟处理、异步复位的使用。这些规则分布在不同的policy文件里。Policy可以理解为一套规则和约束条件的集合文件每个policy文件包含了某一个特定目标场景下最常用的一组规则。2.2 工程文件与Goal的关系VC Spyglass运行时依赖的最小单元是project也就是工程文件。一个工程文件里你需要写明RTL文件列表、设计顶层名字、约束文件比如SDC路径、引脚定义等。然后在工程基础上跑goal。一个goal其实就是一组规则的用户组合比如lint/goals/top.lint。Goal定义的粒度可以很细也可以很粗实际项目中一般会基于官方提供的goal模板定制一套属于自己项目的规则集。这也解释了为什么纯粹用GUI跑Lint很方便但很消耗精力因为工程文件、目标goal、policy关系需要维护。每次换项目都要重新配置手工操作容易漏。我第一次用TCL脚本跑VC Spyglass主要就是想解决这个配置维护的问题。2.3 命令行运行和数据流解释命令行之前先看一下整个数据流。VC Spyglass执行Lint检查时会经历几个阶段读入RTL源码构建层次化、信息完整的中间表示。基于policy中的规则在RTL上生成检查器任务。执行检查任务然后收集告警信息。更新数据库生成报告。命令行模式下VC Spyglass执行的方式是spyglass -project project_file -goal -batch其中-project指定工程文件-goal指定目标goal-batch表示以非GUI的批量模式执行。这个命令是Script Automation的基础因为有了批量模式才能把它放到后台、CI流程里反复跑。注意一点project文件在VC Spyglass里一般既可以是GUI产生的配置文件也可以是由TCL脚本动态生成的甚至可以两者同时存在。我的做法基本上不在GUI里建工程完全通过TCL脚本在每次运行前动态创建工程文件和相关的约束信息这样整个项目可以被脚本完全掌控。3. TCL脚本自动化从手工配置到脚本驱动的完整过程3.1 为什么TCL是VC Spyglass自动化里绕不开的语言VC Spyglass原生支持TCL脚本语言作为接口控制。你可能问为什么不用Perl或者Python其实主要原因在于VC Spyglass内部许多命令都是TCL外壳的TCL能够直接调用这些命令来操作工程、获取数据、设置选项不需要再通过子进程去调用系统命令行。而且VC Spyglass本身的GUI也是完全基于TCL命令构建的所以想在GUI里做的一切操作基本上都能用TCL命令实现。我见过不少工程师尝试用Perl包一层系统调用通过解析输出文本来控制流程。这种方式也可以做但效率很低因为VC Spyglass的输出文本是给人类阅读的格式变化会导致脚本解析失效维护成本很高。直接用TCL脚本在工具内部调用API是更稳定、更官方的方向。3.2 一个最小的自动运行脚本拆解一个完整的TCL自动化脚本通常包含以下部分创建project、读取RTL源文件、读取SDC约束、设置顶层、选择goal、执行goal、输出报告、退出。拿我之前一个模块来做示例脚本结构大致如下# create_prj.tcl # 用于创建项目并运行Lint检查的TCL脚本 project new # 读取文件列表通常放在外部配置文件里 read_file -type rtl {../rtl/osi_wrap.v ../rtl/osi_fifo.v ../rtl/osi_top.v} # 读取SDC约束文件 read_file -type sdc ../sdc/osi_top.sdc # 设置设计顶层 set_option -top osi_top # 选择lint目标 goal lint # 运行目标 run_goal # 生成报告 report_summary这段脚本在GUI里输入和在batch模式下执行都一样有效。运行方式也很简单直接在命令行里执行spyglass -tcl create_prj.tcl -batch这条命令进入batch模式后VC Spyglass会进入TCL解释器依次执行脚本中的每条命令。执行完之后自动退出。我在实际项目中还会在脚本开头加入一些日志和环境设置set log_file spyglass_lint.log config -set logging $log_file这样所有信息都会写入日志文件方便回归跟踪。3.3 用变量和循环将脚本从工具变成了流程硬编码文件名和执行步骤是一个起步但真正要面向项目来维护时脚本需要更结构化。比如处理一大批文件时一般会把文件列表维护在一个外部的filelist.txt中TCL脚本去动态读取它。这里有一个很实用的技巧VC Spyglass支持我们用read_file -type rtl一次读取一个文件也支持读取文件列表。我通常用一个外部配置文件把设计相关的参数集中管理# config.tcl set PROJECT_NAME osi_controller set TOP_MODULE osi_controller_top set RTL_FILE_LIST ../rtl/rtl_list.txt set SDC_FILE ../sdc/osi_controller.sdc然后在主脚本里source这个配置文件# run_lint.tcl source ../scripts/config.tcl project new set file_list [open $RTL_FILE_LIST r] while {[gets $file_list file] 0} { if {$file ne } { read_file -type rtl $file } } close $file_list read_file -type sdc $SDC_FILE set_option -top $TOP_MODULE goal lint run_goal report_summary这个结构比硬编码好维护得多。换设计、换版本的时候只需要改config.tcl中的参数不用动脚本主体。刚开始自动化的朋友我强烈建议一上来就用这种参数分离的方式不然以后改脚本很痛苦。3.4 动态生成目标和结束码便于接入上层流程在真正自动化时还有一个容易被忽略的点检查完成后的返回码。如果脚本跑完但不返回正确状态上层调度系统比如Jenkins或者Makefile就没法判断结果是否成功。所以我在脚本结尾会显式地检查告警数量然后设置退出模式。思路是这样的先跑完Lint通过report命令获取所有告警列表然后再决定是返回success还是failure。示例如下# 获取告警计数 set warnings [get_design $TOP_MODULE -messages -type info] set errors [get_design $TOP_MODULE -messages -type error] # 写入统计文件 puts Lint errors: [llength $errors] puts Lint warnings: [llength $warnings] if {[llength $errors] 0} { exit 1 } else { exit 0 }这样可以保证当Lint出现严重错误时CI系统会把这个任务标记为失败这比人工看报告然后再手动触发后续步骤要可靠很多。4. 报告解析与告警清理自动化不是终点复现闭环才是4.1 报告格式与输出方式的选择VC Spyglass的报告可以有很多种形式包括SRPSpyglass Report、文本、CSV、HTML等。我最常用的是SRP和文本输出。SRP是工具自带的格式GUI里可以打开便于交互查阅。文本输出则适合被脚本解析和归档。报告中最重要的信息主要是每个告警的severity严重级别、message、发生的文件、行号、以及对应的规则ID。这部分才是你真正要去解决的东西。report_summary -severity error,warning -type summary上面命令会生成一份汇总把error和warning的数量按规则分类统计。4.2 按严重级别分类是定位问题最快的路径拿到告警列表后眼尖的人会注意到告警有不同严重级别常见的有info、warning、error。有一部分warning其实是项目里明确允许存在的。比如某些代码是跨多个模块的公共逻辑会产生位宽警告但设计上本身就是故意这么做的。这种告警如果直接当作失败标准会让脚本没法收敛。我用过比较有效的汇报策略是把报告解析出来后先做一个分类聚合把同类规则的告警数量统计出来再结合项目历史数据设定一个“允许告警基线”。跑完Lint之后只比较新增告警数量而不是要求所有告警必需为零。这部分逻辑可以在TCL里实现也可以把报告导成CSV后丢到Python里做二次处理。考虑到VC Spyglass原生支持TCL我一般会用脚本递归遍历告警对象把它导出到一个结构化文件再统一汇总。4.3 快速定位高频告警集中攻坚在项目初期第一次对老代码跑Lint通常会爆出一堆告警几千条都是正常的。这时候千万不要试图一次性全部修完效率极低。我的经验是按规则分类找出出现频率最高、影响面最大的十类问题先集中解决其中最容易普适的规则。举个例子我遇到最多的告警类别是位宽不匹配。因为芯片内部数据总线有时故意截断或者扩展但RTL编码风格并不统一。这时可以借助VC Spyglass提供的规则说明了解这个警告是否属于“结构性警告”如果是就可以通过修改代码实现来消除或者通过约束配置来豁免。既然报告解析的核心是给后续“修复”和“回归”提供依据那么我通常会在工程目录下保存上一次的告警基线然后在每次运行后执行diff。把新增告警和消失告警分列出来输出到两个文本文件。这样代码评审的时候新增的都是显眼的必须得到解释。4.4 使用TCL调用GUI界面做进一步调查虽然自动化脚本跑完之后不需要打开GUI但报告中出现某个非常诡异的告警时我还是倾向于打开GUI去看规则定义。比如命令gui会打开GUI界面但如果已经batch运行过再回到GUI里所有数据都需要重新加载所以我才把GUI调查放在最后只在人工排查时用。5. 集成到项目流程让Lint自动化在团队里真正落地5.1 在Makefile里加入Lint目标脚本化之后下一步就是把它接入项目常规流程。最直接也最简单的方式是写一个Makefile让团队里每个人都能一键运行Lintlint: spyglass -tcl scripts/run_lint.tcl -batch这种方式其实不需要每个人都懂TCL只需要会执行make lint。一键化能够统一大家使用的规则和配置比每个人手工起GUI各自跑一份要规范得多。我当时对这个事的体验很深以前团队里三个人做同一个模块每个人本地的策略、规则、警告处理方式都不一样导致模块级检查结果出现分歧。后来改成让所有人共用一套脚本和配置情况好了很多。5.2 接入CI/CD对MR做质量门禁仅仅本地跑一次并不能保证以后的代码都符合规范所以接着要做的就是把它放到CI流水线里。在实际项目里我会在Merge Request触发时执行lint脚本通过后才会进入后续仿真验证。把脚本接到CI时有两个容易被坑的点。第一个是CI环境里通常没有GUI库依赖所以运行时一定要用-batch模式同时还要确认VC Spyglass的License在CI节点上可以正常获取。第二个是文件路径问题CI执行环境的工作目录可能与本地不一致脚本里的相对路径要处理好最好用绝对路径或环境变量动态确定目录。流动到这一步脚本的意义就远远超出了“取代手工操作”它本质上变成了一套编码质量约束。每一次代码修改都必须过这一关从源头阻止了不规范的RTL代码进入主干。这段用脚本建立质量门禁的过程对团队的影响是最明显的。同事们开始主动关注自己提交的代码有没有Lint问题因为CI会在十分钟甚至几分钟内给出明确结果这比代码评审时人肉检查更客观、更即时。5.3 个人经验三个让我栽过跟头的细节讲了挺多脚本原理和流程最后挑几个我实际踩过的坑希望对你有点帮助。第一个坑是RTL文件列表的排序问题。Lint检查的结果依赖编译顺序绝大多数情况下没问题但如果有同名模块在不同文件里的情景编译器可能会用后加载的定义。这种问题比较阴。后来我在脚本里固定了文件列表顺序并且在工程里强制检查重名模块凡是出现两次声明就直接报error。第二个坑是SDC约束文件缺失。很多RTL工程师跑Lint时觉得不需要SDC结果导致时钟信号识别不完整报出一堆毫无意义的时钟约束告警。更麻烦的是这些误报会掩盖真正的问题。后来我规定Lint执行前必须提供SDC哪怕只有一个create_clock也行让工具能正确区分时钟和普通信号。第三个坑是TCL脚本的异常退出。默认的脚本在执行命令遇到严重错误时并不会自动终止它会继续往下跑最后可能整个脚本看似跑完了但实际上项目没有正确生成。后来我在关键步骤之后加入了断言检查比如跑完goal之后判断有没有生成报告文件如果没有就输出错误信息并强制exit。5.4 从单模块到全芯片的扩展脚本自动化最大的好处在于它几乎可以无缝地从模块级扩展到全芯片级别。单个模块的RTL文件列表通常只有几个到几十个但整个芯片可能有几千个。如果全靠手工配置不现实但使用TCL脚本只需在同一个框架里面调整filelist和顶层模块名称剩下的事情交给脚本自动完成。我在脚本里加了一个switch参数根据不同的设计阶段选择不同的filelist文件和goal配置文件。这样既可以跑模块级快速Lint也能在版本发布前执行一次整个芯片的完整Lint。脚本从一套逻辑出发适配了不同粒度的检查场景。这里有一个核心经验写TCL脚本时尽量定义清晰的抽象层。比如所有设计参数集中放在config文件公共逻辑放在common.tcl然后具体任务脚本再source这些公共部分。模块级和芯片级脚本之间的差异只在顶层配置不在执行逻辑上。这样维护成本最低也最容易让后来接手的人理解整个流程。我实际做下来一个芯片后端环境里的完整Lint运行时间在30到60分钟而这还包含了生成报告的时间。把这么长时长的任务手工跑一次是痛苦的但脚本跑起来基本无感。每天夜里自动跑一次早上过来看报告这种节奏才真正健康。全文从VC Spyglass的Lint定位、TCL脚本自动化、报告解析到流程集成这个闭环走通之后我才体会到“静态检查自动化”不仅仅是省力它让代码质量变回了一种可度量、可追踪、可门禁的状态。以后再去接新项目我第一时间做的事情之一就是先把这套自动运行框架搭起来。
返回列表