
简介Synopsys SpyGlass LintRules Reference GuideQ-2020.03-SP1是一份面向数字IC前端设计与验证工程师的权威静态检查参考手册专注Verilog/VHDL硬件描述的lint规则解读用于在RTL阶段拦截语法偏差、编码风格冲突、时序收敛隐患及功耗/接口类设计缺陷适合希望系统性提升设计质量的验证与设计团队。资源仅含1个PDF文档2.04MB随包无其他附件方便离线查阅和按需检索。目前已有4467人参与学习。文档完整收录SpyGlass lint产品全部规则集覆盖设计风格、时序分析、功耗优化、设计约束、接口检查等多个维度每条规则均给出规则ID、详细描述、示例代码、违例解决方案及严重性等级Warning/Error/Info并针对allow_clk_in_condition、avoid_seq_logic、checkblocking等关键参数展开专项说明还涉及阈值调整、例外添加等自定义配置方法。读者可由此深入理解各类规则意图建立可复用的lint检查策略提前发现并修复潜在问题从而减少后期设计改动加速整体IC设计流程。1. 先从规则参数入手拿到 SpyGlass lint 参考指南后先做什么RTL 综合前一夜仿真波形全绿lint 报告里却躺着几十条 latch 和总线宽度相关的消息这时候再逐条读规则定义已经来不及。SpyGlass lint 这类静态检查工具的价值就是把这类问题提前暴露在代码阶段而官方规则参考文档的打开方式不是从头到尾通读而是按参数去查。这份指南里每条规则背后挂着一串可选参数哪些规则能调严重级、哪些能用 ignore 系列缩小作用域、哪些配备了独立的分析开关全都写在这本参考里。适合做 RTL 设计、验证以及 flow 维护的人在接到一条陌生 lint 消息时按规则 ID 去文档里反查参数而不是凭经验猜。2. 把 lint 规则当成可调参数来用规则 ID、严重级与作用域SpyGlass lint 的规则参考文档前半部分是一份相当长的规则参数清单后半部分才是对规则的具体解释。我的经验是先看懂参数清单的排布方式再去看具体规则文本这样遇到报错时的处理速度会明显变快。文档里很多参数名与规则 ID 是同一个词比如 check_latch 既代表「检查锁存器推断」这条规则本身也出现在参数清单里而真正控制它检查强度的是一批独立参数例如 latch_effort_level、treat_latch_as_combinational。理解规则名与参数名之间的关系是正确使用这本手册的第一步。2.1 规则文档的第一层读法ID、描述与严重度每条 lint 规则条目通常包含规则 ID、目的描述、示例代码、解决方案和严重性等级。严重性等级一般分 Error、Warning、Info 三档。Error 多用于那些会改变硬件行为的写法例如时序逻辑里出现阻塞赋值Warning 表示疑似问题例如敏感列表不完整、推断出 latchInfo 是提示性质的例如信号未被使用、模块以黑盒形式被例化。这些级别并不是固定的项目可以用 set_message_severity 调整所以拿到一条消息先别急着改代码先确认它在当前规则集里是什么级别。先把高频规则与配套参数的关系列成一张表这是后续所有配置的基础。规则名检查对象常见级别配套参数check_latch推断锁存器Warninglatch_effort_level、treat_latch_as_combinationalcheckblocking时序块中的阻塞赋值Errorignore_nonBlockConditionchecknonblocking组合逻辑中的非阻塞赋值Warningignore_reinitializationcheck_implicit_senselist敏感列表不完整Warningsimplesensecheck_unsign_overflow无符号算术溢出Warningnocheckoverflowcheck_parameter参数定义与传递不一致Warningcheck_param_associationcheck_fullbus总线位宽不匹配Errorhandle_large_bus、concat_width_nfcheck_counter_assignment计数器赋值形式Warningcheck_counter_assignment_turbo表中的「常见级别」是项目里的常用配置不代表工具默认值。处理一条陌生报错的正确顺序在我的工作习惯里是这样的先看消息行里的规则 ID拿 ID 去文档参数清单里找它挂的参数确认这条规则的检查意图最后才决定是修代码、调参数还是加豁免。这个顺序用熟之后大部分误报不需要靠断点调试就能定位。不建议拿到报错就搜代码示例然后照抄修复因为同样的消息可能来自完全不同的代码结构。2.2 控制规则行为的参数从 allviol 到 set_message_severity参数按作用可以分成几类。第一类是全局报告行为控制包括 allviol、report_all_messages、report_all_connections、set_message_severity。allviol 控制一条规则匹配到多处违规时是否每一处都报告如果关闭日志里可能只保留第一条匹配结果回归环境里建议把它打开否则违规数量统计会失真。report_all_messages 决定工具是否把内部产生的所有消息写入日志调试阶段打开日常回归关掉避免日志被工具自身的运行信息占满。第二类是具体规则的细粒度开关比如 check_latch 下的 latch_effort_level、check_counter_assignment 下的 check_counter_assignment_turbo、check_parameter 下的 check_param_association。这类参数通常跟在规则名后面以键值对的形式写进规则配置。我一般会在规则集文件里用近似下面的写法具体字段拼写以自家流程为准# 规则集配置示意 set_rule_option {check_latch} {latch_effort_level} {2} set_rule_option {check_latch} {treat_latch_as_combinational} {on} set_rule_option {check_counter_assignment} {check_counter_assignment_turbo} {off} set_rule_option {check_parameter} {check_param_association} {on} set_rule_option {allviol} {on}这段配置的逻辑是先把需要重点盯的规则选项显式打开再把全局的 allviol 打开。latch_effort_level 的数值越大对组合分支补全性的检查越细报告中出现的 latch 消息数量也会上升treat_latch_as_combinational 打开后某些特定模式的 latch 会被当作组合逻辑处理适合那些「结构上像 latch但综合结果可以接受」的代码。check_counter_assignment_turbo 是加速计数类规则分析的开关设计里大量使用计数器时建议打开分析速度的提升比较明显。set_message_severity 这类消息级控制不改变规则内部逻辑只调整输出级别。设计准备交接给后端之前把关键规则提升到 Error、把提示类规则降级到 Info能让报告聚焦在真正需要处理的问题上。需要注意的是它和 ignore 系列是两回事ignore 系列直接不检查set_message_severity 是检查了但换一个等级输出。我在项目里一般先保持默认规则集跑一次确认哪些消息是真实的再决定降级不会一开始就把规则用 ignore 关掉否则规则集里很容易积累一堆没有理由的豁免。2.3 按模块施放规则ignore_* 家族与作用域限定规则参考文档里出现频率很高的一类参数是 ignore_*它们的作用是把检查范围从「全设计」收窄到「某个作用域」。典型成员包括 ignore_scope_names、ignore_typedefs、ignoreSeqProcess、ignore_in_ports、ignore_if_case_statement、ignore_local_variables、ignore_interface_locals。这些参数解决的其实是同一个问题lint 默认看到的是一整棵层级树但项目往往只对自己编写的 RTL 负责第三方的加密 IP、工艺库模型、仿真用的 wrapper编码风格可能跟项目规范不一致强行套同一套规则会产生大量噪音。实际配置里我习惯按模块路径去限定而不是全局打开忽略。例如某个复用模块的代码风格与项目不一致但综合结果长期稳定可以在规则集里这样写ignore_scope_names u_comp_sim, u_dft_wrapper ignoreSeqProcess u_core.u_fifo ignore_in_ports { *:u_comp_sim }这样比给每条规则手动加豁免要直观因为它是按层级路径精确圈定的后续其他人接手时一眼能看懂为什么这些模块被排除。需要注意每个 ignore 参数的匹配规则不同有的吃字符串前缀有的吃 glob 通配符改之前先查该参数在文档里的定义段落不要拿一个通配符直接套所有参数。我的习惯是先在单个模块的 lint run 里验证一遍范围再固化到正式流程避免豁免范围比预期大、把真实的跨模块问题也一起淹掉。3. 从规则名到修复动作阻塞赋值、锁存器与整数宽度规则文档的价值不在规则名本身而在于一条消息出现后能顺着规则思考代码为什么触发它。第二章讲的参数是「怎么查」的问题这一章讲「查到了怎么办」的问题。我从高频报错里挑三类展开时序赋值风格、锁存器推断、总线宽度与溢出。这三类消息在 RTL 综合前的 lint 报告里出现频率最高也是新手最容易直接 waive 掉的三类。3.1 时序行为检查checkblocking、checknonblocking、avoid_seq_logic在时序 always 块里使用阻塞赋值的代码仿真阶段通常还能看到正确结果因为事件调度恰好按顺序执行综合后的行为却可能发生变化尤其是赋值语句存在多个驱动来源或内部信号被反复读写时。checkblocking 盯的就是这一写法。下面的代码是典型触发场景module bad_dff ( input clk, rst_n, input d, output reg q ); reg tmp; always (posedge clk or negedge rst_n) begin if (!rst_n) begin q 1b0; end else begin tmp d; // 时序块里的阻塞赋值 q tmp; // 同样触发 checkblocking end end endmodule这段代码的问题在于 tmp 充当了中间寄存器的角色但赋值方式是阻塞赋值。仿真时 tmp 立即更新q 在同一时钟沿拿到的是新值综合工具对时序块中临时变量的建模方式与仿真器有差异前后仿真结果容易在边界时序上分叉。修复方式是只保留一个非阻塞赋值给 q临时计算放到组合 always 或 function 里always (posedge clk or negedge rst_n) begin if (!rst_n) q 1b0; else q d; // 去掉中间变量行为清晰 endchecknonblocking 检查的是反方向组合逻辑的 always 块里使用非阻塞赋值。组合电路本身没有存储单元非阻塞赋值会在综合时被推断成寄存器结果与代码意图不一致。如果确认某个非阻塞赋值必须存在又不想触发规则可以用 ignore_nonBlockCondition 按条件绕开但这种情况很少见大部分时候直接改成阻塞赋值并补全敏感列表即可。avoid_seq_logic 这条规则和上面两个是搭档关系它负责挑出零散分布在代码里的顺序逻辑写法提醒设计者改用统一的同步模板例如统一的复位方式、统一的时钟沿描述避免一个设计里混用多种时序风格。3.2 锁存器相关check_latch、latch_effort_level、treat_latch_as_combinational组合 always 块里条件分支不完备时综合器可能推断出 latchlint 在 RTL 阶段就能把这个潜在结构报告出来这正是 check_latch 的用途。最典型的代码是 if 没有 else或者 case 没有 default// 触发 check_latchen 为 0 时 y 没有任何赋值路径 always * begin if (en) y d; end修复方向有两个补完分支或者明确保持原值。补完分支让设计变成真正的组合逻辑明确保持原值相当于设计者显式声明「这里就是一个 latch」。两者在综合结果上完全不同所以 check_latch 不能简单 waive要先判断设计意图。latch_effort_level 决定这条规则在多大程度上识别间接的 latch 结构简单分支在低档位就能查出跨语句的读改写路径需要高档位档位开太高也会把部分组合逻辑误判成 latch所以一般从默认档开始误报多了再降。treat_latch_as_combinational 参数打开后工具会把特定模式的 latch 当作组合逻辑处理以降低误报打开前需要对涉及的代码模块做一次人工评估不能全设计统一开启。除了 check_latch文档里的 reportLibLatch 和 reportconstassign 也值得留意。reportLibLatch 报告库单元中推断出的锁存器对含有标准单元库仿真模型的项目很实用reportconstassign 报告常量赋值相关的结构。它们与 check_latch 的差别在于报告维度不同check_latch 偏向 RTL 写法分析另外两个偏向检查结果的结构化输出处理顺序通常是先修 check_latch 的 Error再看 reportLibLatch 的消息确认库单元没有被意外推断。3.3 宽度、符号与溢出check_fullbus、check_unsign_overflow、sign_extend_func_names总线宽度问题在综合前一般不会让仿真失败因为截断后的低比特位在大多数普通激励下数值相同但一旦数值超出目标位宽仿真结果与硬件行为就会出现分叉。check_fullbus 家族检查的正是位宽不匹配与赋值截断。看这段代码logic [7:0] a, b; logic [3:0] sum; assign sum a b; // 8 位相加结果赋给 4 位信号高位被截断如果 a、b 的业务范围确实不超过 15截断不算 bug否则应把 sum 扩到 8 位或在赋值前做显式饱和处理。lint 不知道业务语义它只报告可能的截断点由人来判断。所以正确解法不是无脑改位宽而是先把所有 check_fullbus 消息按模块分类确认溢出在业务上是否不可达再决定扩展位宽还是加注释说明。和它配套的 check_unsign_overflow 进一步专门检查无符号算术表达式是否可能溢出它有一个直接关闭开关 nocheckoverflow。不推荐全局关掉至少应该在日常回归里保留统计否则算术模块的溢出问题只能等仿真暴露。符号问题由 check_sign_extend、sign_extend_func_names 负责。函数返回值在 Verilog 里经常被隐式扩展如果函数内部做的是无符号运算而外部把它当有符号数用符号位会被错误扩展。遇到这类消息先去看函数原型里的 signed 关键字和调用处的类型确认运算意图后再改声明或加显式位宽转换。这类检查在数据通路模块里尤其重要忽略它们容易在定点化以后的数字信号处理模块里翻车。check_static_value、check_static_natural_width 这两个参数是用来做静态数值传播的配合 check_fullbus 一起用可以筛掉那些由常量代入引起的伪位宽问题减少人工核对工作量。4. 避坑手记lint 跑得动并不等于跑得对lint 能出报告不代表报告内容就有意义。模拟项目X里实际踩过的四个坑按「现象 → 原因 → 解决」整理每条后面附上对应的参数名方便在参考文档里反查。4.1 坑一宏展开把代码变得面目全非误报铺天盖地现象代码里加入了若干宏定义之后lint 报告突然多出几百条结构与接口相关消息check_parameter 和 check_param_association 的报错大部分指向宏体内部而这些宏在其他编译环境下一直正常。原因lint 先做宏展开再分析宏里的局部变量、typedef、参数列表在展开后失去了原始作用域信息工具把宏体当成独立结构变量被误判为未定义或参数误配。解决在规则配置里打开 ignore_macro_to_nonmacro专门处理宏展开导致的非宏代码误报再根据宏的使用范围决定是否打开 ignore_typedefs、ignore_scope_names。我一般会把来自宏的误报单独筛一遍确认是宏展开问题后才加入豁免。注意不要为了消除误报去改宏本身宏在综合工具里的展开过程与 lint 并不完全一致改了反而影响综合结果。4.2 坑二inout 端口被当成 output双向总线误报不断现象设计里存在标准的 inout 双向数据总线运行后 checkDriverInModule、reportsimilarassgn、check_bbox_driver 轮番报告驱动冲突指向同一个模块的同一组端口。原因lint 默认把 inout 当作纯 output 处理驱动方向判定错误导致同一端口出现在多个驱动源清单里驱动类规则随即报出大量冲突。解决设置 considerInoutAsOutput 参数让工具把双向端口作为输出参与驱动分析同时结合 report_all_connections 查看该端口的完整连接表确认是否真的存在多个驱动。经过这个处理后双向总线相关的误报基本消失。实际项目中 inout 总线大多来自存储器接口和模拟 IP 边界配置这个参数前先确认层级结构里的双向端口数量避免把真正的多驱动错误也一起掩盖。4.3 坑三信息级消息刷屏真正的 error 被淹没现象回归日志里 Info 级消息上万条Error 只有十几条但日志文件太大Error 消息被滚动到看不到的位置自动化脚本按关键字抓取时还会误抓 Info 行。原因规则集默认把检查覆盖到全设计的每个节点report_all_messages 开着大量 dump_array_bits 之类的数组位宽提示被写进日志信息密度过低。dump_array_bits 本身用于把数组按位展开输出调试单个大位宽信号时很好用放进日常回归就是噪音。解决用 set_message_severity 把不关心的规则统一降级到 Info 或关闭把 check_latch、check_fullbus 等核心规则提升到 Error回归时只对 Error 级别的消息做汇总统计。dump_array_bits 这类数组展开提示应该单独放到调试规则集里需要定位大位宽信号时再临时打开而不是常驻回归环境。4.4 坑四RTL 阶段干净综合后 netlist 又冒出一批新的位宽消息现象RTL 的 lint 全部清零综合后对网表再跑 lintcheck_fullbus 和 concat_width_nf 又报出一批位宽不一致的消息而且数量随综合工具优化选项变化。原因RTL 分析时工具对位宽的建模是理想化的综合工具做完优化和常数传播后位宽信息会变化同一段逻辑在两种视图下长度不同。handle_large_bus 和 handle_large_expr 两个参数控制大位宽总线与大表达式的拆分策略默认值在网表分析时不一定匹配综合器的资源分配。解决对网表分析单独建一个规则集把 handle_large_bus 的拆分上限与综合策略对齐必要时设置 new_flow_width 切换到新的位宽处理流程后重跑同时开启 report_only_from_one_hierarchy 把网表消息按层级分组逐模块核对哪些是综合优化引入的合法差异。从那以后我在做门级验证前都会先比一遍两套规则集的差异而不是直接复用 RTL 的配置。5. 把 lint 流程固化成回归规则集配置、降级与批量豁免lint 必须跑成回归才有长期价值否则每次都是人工翻日志规则配置会越积越乱。这一章把参数落实到流程上按阶段、按角色地把这本参考文档用起来。5.1 按设计阶段开规则不必一把全开常见做法是按设计成熟度把规则集拆成三档。第一档在模块刚写完时开重点查风格和接口check_parameter、check_param_association、check_implicit_senselist、check_case_type这些规则解决「代码写没写规范」的问题。第二档在模块集成时开重点查结构check_latch、checkblocking、checknonblocking、check_sequential、check_temporary_flop这类规则的报错直接影响综合结果。第三档在回归冻结前全开额外启用 allviol、report_all_connections、not_used_signal确保任何违规点都不会漏报。三档规则集对应关系如下阶段规则示例关注点模块单独检查check_parameter、check_implicit_senselist、check_case_type接口规范与编码风格模块集成阶段check_latch、checkblocking、checknonblocking、check_sequential结构与时序行为回归冻结阶段allviol、report_all_connections、not_used_signal覆盖完整性、未使用信号、连接正确性规则集的分档不意味着低档位跑完就完事而是在每个阶段只处理该阶段应该解决的问题避免模块还在改结构时被风格类消息干扰。等到冻结阶段再全开这时报告里出现的每一条消息都值得逐条过一遍。5.2 无法立即修的问题waive 也要讲证据规则报出来不等于当天就要修。项目中期经常存在一批已知但无法立即修复的 violation比如遗留 IP 接口处的位宽截断、宏展开产生的误报。对这类问题我一般不用 ignore 直接关规则而是用 set_message_severity 把消息降级并在项目管理文件里记录降级原因。实际操作时会把 strict 配合 no_strict 一起用no_strict 放宽部分规则的匹配条件strict 要求所有检查必须显式配置避免默认配置悄悄放过了某些规则。下面这段脚本的思路是把 lint 日志按规则统计数量用于每次回归之间的对比。它不依赖工具的 API任何 lint 日志都适用只要确认日志行的格式即可。#!/bin/bash # 从 lint 日志里统计各规则的消息数量 grep -E \[(E|W|I)\] run.log \ | sed -E s/.*\[(E|W|I)\].*\[([A-Za-z0-9_])\].*/\1 \2/ \ | sort \ | uniq -c \ | sort -k2 -nr这段脚本做的事情是筛出日志中的消息行提取严重级和规则 ID按规则 ID 分组计数最后按数量排序。配置变更后可以清楚看到哪条规则的消息数上升如果某条规则的数量突然翻倍要么是新代码引入了问题要么是规则参数被改宽或改严两种原因都需要人工确认。提示对回归中反复出现的同一条消息先看它是不是由宏展开、inout 方向、大位宽拆分这三类原因造成的优先调参数而不是加豁免。5.3 回归里盯住趋势not_used_signal 与 report_all_connectionslint 回归最怕的不是有 violation而是 violation 数量在两次运行之间没理由地暴涨或暴跌。not_used_signal 负责检查信号从未被读取或驱动的情况这类消息的数量能反映代码清理状况适合放在每周回归里看曲线report_all_connections 把信号连通的详细路径打印出来排错时打开它可以看到一条消息背后的完整传播链。两个参数都打开会增加运行时间我的处理方式是平时关掉 report_all_connections只在重点信号异常时临时打开not_used_signal 始终保持打开并把结果并入每日报告。6. 把黑匣子变透明一条规则到底在查什么规则文档最实用的用法是在收到一条看不懂的报错时用它自带的参数做一次「隔离实验」。我常用的三板斧是 report_only_from_one_hierarchy、flag_only_instance_ports、checkDriverInModule。第一步把报错定位到单个模块设置 report_only_from_one_hierarchy 只报告指定层级下的消息先把范围从全设计收敛到出问题的块。第二步如果消息来自实例端口用 flag_only_instance_ports 把分析限定在端口级别过滤掉实例内部逻辑的干扰。第三步遇到驱动类消息让 checkDriverInModule 输出该信号的驱动模块清单对照 show_connected_net 看实际连接一般三分钟内就能判断是真实问题还是配置差异。另外report_hierarchy 会打印完整层级结构处理跨模块问题时先把层级打出来再决定动哪一层。这几个参数的组合使用比单独看任何一条消息的描述文本都快。我拿到一个陌生模块的 lint 报告时通常先用 report_hierarchy 过一遍层级再挑报错最多的模块跑一次隔离分析确认规则触发点后才回到文档里核对参数语义。这个过程只需要在规则配置里临时加几行跑完就撤销不会污染正式规则集。说实话用 lint 这些年最大的教训是别在 freeze 前一天才开始读这类参考文档。从那以后我每次接入新工艺库或新 IP都会强制把规则参数核对一遍把这份文档当参数辞典用而不是当小说读。希望帮到你。本文还有配套的精品资源点击获取