1.2 可综合RTL编码习惯


1.2 可综合 RTL 编码习惯

本节摘要:硬件描述语言不是按行执行的脚本,而是并发结构与时序闭包的契约。可综合 RTL 必须让综合器无歧义地抽出触发器、组合网络与三态,并与仿真语义一致。本节从语言角色、锁存器意外推断、时钟复位写法,讲到综合脚本将要消费的结构属性。

你能学到什么

阅读完本节,你应当能够:

  1. 说明 Verilog 或 SystemVerilog 相对软件语言的并发本质
  2. 识别会综合成锁存器或仿真综合不一致的写法
  3. 用边沿块与非阻塞赋值表达触发器,用连续赋值表达组合
  4. 把时钟域、复位和 keep 类属性写成工具读得懂的意图

一、语言是契约,不是日记

原文把 HDL 称作形式化契约语言,这个判断我同意,而且比“硬件版 C”准确得多。软件语言的主线是控制流:先执行哪一句决定结果。硬件语言的主线是结构:always 块之间同时存在,assign 之间同时存在,实例化就是连线。没有“主函数”。所谓执行,是时钟沿或电平变化触发的状态快照更新。

因此,你向三类主体同时承诺:

  • 向同事:分层清晰,端口能读懂
  • 向工具:能抽出 FSM、数据流图、触发器
  • 向物理实现:时钟域、复位、位宽这些约束有着落

Verilog 轻、语法糖多,历史上 reg 被误读成“一定是寄存器”——标准后来讲明,它只表示“能被过程赋值的变量”。VHDL 更强调类型与接口显式。SystemVerilog 把 assert propertycovergroup、约束随机拉进同一套语言,验证不再是设计的外挂。

你写下边沿采样,表面是 D 触发器,实质是一份时序闭包:每个上升沿,复位有效则清零,否则把输入搬到输出。这份声明已经在预判抖动、复位去毛刺、异步复位同步释放。后面综合脚本里的时钟组、假路径、最大延迟,都是在兑现或修补这份闭包。

二、可综合子集:什么能进网表

综合器吃的是可综合子集,不是语言标准的全集。延迟控制、绝对时间等待、文件读写、不定宽的动态切片,仿真里好用,综合要么忽略要么报错。更阴险的是它不报错、却推出你没要的结构。

最经典的是不完整 if:组合 always 块里某个分支没给输出赋值,综合器为了“记住旧值”,插入锁存器。仿真里因为你从未走到那个分支,波形看起来干净。硅上温度一变、扫描一移,锁存透明窗口把毛刺打进去。

// 意图是纯组合,缺 else 会变成锁存 always @(*) begin if (sel) y = a; end

正确态度不是背“所有输出都要赋默认值”当咒语,而是问:这个变量的时间语义是电平跟随,还是边沿采样?电平跟随就用连续赋值或完整 case;边沿采样就进 posedge 块,用非阻塞赋值。混用阻塞与非阻塞,是仿真综合不一致的温床:仿真按过程语句顺序更新,综合按触发器与云团连接。

原文还点过综合指令一类属性:保持节点、禁止翻译的区域。这些是给工具的旁注,不是给人类的情绪。乱打 keep,会挡住逻辑优化,面积莫名其妙涨一圈;该 keep 的异步口没打,优化器把同步器两级并成一级,CDC 形同虚设。

写法 综合通常推出 风险
边沿块加非阻塞 触发器 复位与使能优先级写反
完整组合块 组合云 默认值漏网变锁存
不完整 if 锁存器 仿真难发现
for 生成复制 并行结构 边界未约束则面积爆炸
函数里的静态变量 隐式存储 跨调用共享状态

⚠️ 常见坑:在边沿块里用阻塞赋值“顺便”算组合,再赋给寄存器。仿真顺序碰巧对,综合把计算挪到触发器前后,建立时间对不上你的心理模型。
💡 关键直觉:每个变量只选一种时间语义。跟随或采样,不要两种都沾一点。

三、时钟与复位:编码即约束

单时钟同步设计是综合和 STA 最省事的世界。现实里总会有配置域、常开域、接口域。编码习惯是:一个时序块只敏感一个时钟;跨域数据不在组合里直接运算后送出。

复位我更倾向异步断言、同步释放:掉电和毛刺来时能立刻把状态拽回已知值;释放时走两级触发器,避免复位树偏斜造成部分寄存器先醒。扫描测试还要考虑测试模式下降复位是否会切断移位。原文强调,这些答案直接决定后续 set_clock_groupsset_false_pathset_max_delay -datapath_only 怎么写。你在 RTL 里装糊涂,约束文件就会堆例外,例外一多,STA 的信任就垮。

时钟门控不要手写门去“与”时钟。综合器认 if (en) q <= d; 这种使能形式,能换成专用门控单元,并检查使能的建立保持。手写门容易做出毛刺时钟,保持违例会在硅上以极低概率出现,实验室很难抓。

图:可综合编码检查单

图:可综合编码检查单

四、验证不是后贴的贴纸

原文给过一个工程比例:大型 SoC 里验证吃掉前端大部分日历时间,功能验证又占其中一半以上量级。原因简单——掩膜改版以月和巨额费用计,RTL 改一行只是一次仿真。所以编码习惯包含“怎么被测”。

定向测试只能覆盖你能想到的路径。约束随机把合法输入空间写成约束,让引擎去填洞。覆盖率模型回答“所有指令类型是否出现过、缓存状态是否走全”。形式化则对属性做穷尽:比如“请求与应答最终配对”“非法状态进不去”。我的最低配是:复位属性、握手不丢拍、FSM 非法态、跨域口不直连。这些属性既是验证,也是设计文档,比注释抗衰老。

综合前还要跑 lint:组合环、未驱动、宽度截断、锁存推断。lint 通过不是荣誉,是进综合的门票。有人用 initial 给寄存器赋初值,仿真从已知态出发,综合后上电是 X,对拍失败还怪网表。可综合设计的已知态只能来自复位或扫描初始化。

物理感知综合会在前端就问线长。你的层次若切得太碎,工具看不到跨模块的公共子表达式;切得太肥,增量编译和形式等价检查都痛。层次是给人和工具同时用的:按时钟域和功能核切开,胶合逻辑单独放,不要在深层模块里偷偷打到顶层时钟。

问题:能不能写行为级、让 HLS 出 RTL?

控制浅、数据通路规则的核可以。带大量 CDC、扫描和自定义门控的核,HLS 出的代码往往在约束和可读性上更贵。即便 HLS,你仍要会读它吐出的 RTL 是否可综合、是否符合本节的时间语义。HLS 不是免考。

问题:SystemVerilog 接口会不会让综合变差?

接口能减少端口连线错误,但要保证综合看到的是静态连接,而不是运行时虚接口。验证用的 class 和 randomize 必须隔离在测试平台,不要漏进待综合文件列表。文件列表本身就是契约的一部分。

五、编码审查时我实际看什么

审查不从“整洁”开始,从时间语义开始。每个 always 块写上敏感列表或用 always_ff / always_comb 一类意图明确的形式,让工具和人少猜。发现组合块里有不完整赋值,不当风格问题,当缺陷。发现边沿块里调用了带静态存储的函数,当隐式锁存。

其次看时钟:一块是否只跟一个沿;是否出现数据当时钟;门控是否可被识别。再看复位:极性是否全芯片一致;释放链是否每域独立;扫描下复位是否可被关掉。最后才看命名和层次。层次按时钟域切,比按“算法步骤”切更利于约束和 CDC 审查。

仿真综合不一致的排查顺序:先关延迟语句和初值,再对比阻塞非阻塞,再看宽度截断。门级反标后对不上,先问复位后是否还有 X 在传播。X 乐观仿真会把问题藏到硅上。把未知态当失败,比把未知当 0 更接近制造。

综合文件列表要白名单。测试平台、断言绑定、调试打印不得漏进。一次把 interface 的 virtual 用法漏进待综合文件,工具可能报错,也可能推出奇怪逻辑。列表也是契约。

六、把 lint 当综合门票而不是羞辱

lint 报锁存、组合环、宽度截断、未驱动,不是风格偏好。锁存进网表会让 DFT 和 STA 一起痛;组合环可能是真振荡也可能是假敏感列表;宽度截断在仿真里用 32 位中间量藏起来。把 lint 清零当作进综合的门票,比把综合警告当门票更早、更便宜。

团队应维护一份允许的 waivers,每条有主人与到期日。无到期的 waiver 会变成永久伤疤。新增模块不允许继承别人的 waiver。审查时先看 lint 新增,再看功能波形。波形再绿,锁存仍在,硅上温度一变就会用毛刺给你上课。

编码习惯会过时,门票不过时。语言从 Verilog 换到 SystemVerilog,时间语义那一条仍成立:跟随或采样,不要两种都沾。

七、文件列表与断言绑定

待综合文件白名单用生成器从模块清单产出,禁止手工追加测试平台。断言用绑定到接口的方式放在验证文件,避免综合看到 property 后行为分裂。覆盖组同样隔离。谁把 class 声明漏进综合列表,构建应失败而不是警告。失败比事后对波形便宜。

审查清单可以钉在显示器边框:一块一时钟、一变量一时序语义、复位释放分域、使能留给门控、keep 只保护同步器、初值不进可综合、文件列表白名单。七条都过,才点综合。少一条就在下一站用更贵的方式偿还。仿真综合不一致时,把同一测试同时跑 RTL 和门级,从复位后第一拍开始对 X。X 当失败,不当 0。乐观仿真是把制造不确定性翻译成侥幸。语言标准允许的写法远多于可综合子集,工程上永远站在子集里,把全集留给测试平台。这不是保守,是让工具与人用同一份语法开会。

阻塞赋值与非阻塞混在同一时序块里,仿真事件队列会按工具心情排序,门级却按时钟边沿。习惯是:时序块只采样,组合块只跟随,两者不要在同一 always 里打架。这条比记住关键字更不容易在代码评审里漏掉。

本节速览

  • 并发结构:HDL 描述同时存在的块与连线,不是主函数顺序。
  • 一种语义:每个变量要么跟随要么采样,缺分支会变成锁存。
  • 复位与门控:异步断言同步释放;使能留给专用单元,不手与时钟。
  • 属性克制:keep 保护同步器,不拿来当优化开关的泄愤按钮。
  • 验证前置:断言、覆盖、lint 是编码习惯,不是项目末期补丁。
  • 已知态来自复位:仿真初值不是硅上初值。

下一节把组合云和算术通路写具体:进位链、编码与关键路径,往往藏在“只是算一下”里。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U