%Warning-SYNCASYNCNET 排查:Verilator 同一信号同步异步混用修复
更新时间:2026-10-29。FAQ 速查页,属于 EDA 报错排查系列。
%Warning-SYNCASYNCNET: Signal flopped as both synchronous and async是 Verilator 对「同一根信号被两种身份使用」的告警——它一路进了negedge敏感表当异步复位,另一路又在always @(posedge clk)里被当普通数据同步采样。复位结构引起的另一类专项问题是复位域穿越,见 RDC 复位域穿越。
首屏答案:SYNCASYNCNET 的意思是某个网络(net)在设计里既是异步控制(出现在敏感表边沿里)又是同步数据(在时钟沿块内被 if/赋值读取)。告警文案会同时给出 async usage 和 sync usage 两处行号,对照一看便知。修法永远是把两种身份拆成两根信号:原始复位专职走异步复位敏感表;需要被同步逻辑读到的「复位是否有效」状态,先经复位同步器打拍成本时钟域的 rst_n_sync 再使用。绝不要用 lint_off SYNCASYNCNET 消音——混用背后是真实的亚稳态与复位释放时序风险。
本文要回答的问题
- SYNCASYNCNET 报错串里的两处行号(async usage / sync usage)分别指向什么?
- 同一根复位信号既异步用又同步读,硬件上到底危险在哪?
- 拆成同步/异步两路的标准写法长什么样?
- 修完怎么确认告警真的闭环?
一、报错原文与现象
最小复现是一个典型的「顺手读了一下复位」:rst_n 在一个 always 块里当异步复位,又在另一个 always 块里被当时钟使能/状态位读取:
module syncasync_bad (
input wire clk,
input wire rst_n,
input wire d,
output reg q,
output reg active
);
// 用法一:异步复位——rst_n 出现在敏感表边沿里
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
q <= 1'b0;
else
q <= d;
end
// 用法二:同步读取——同一 rst_n 被当普通数据采样
always @(posedge clk) begin
if (rst_n)
active <= 1'b1;
else
active <= 1'b0;
end
endmodule对这个文件跑 verilator --lint-only -Wall,真实告警与修复后复跑结果如下:
读这段输出抓三处:第一,主行 Signal flopped as both synchronous and async: 'rst_n' 直接点名肇事信号;第二,两行 Location of async usage / Location of sync usage 把两种用法各钉到具体行——syncasync_bad.v:12:10 是敏感表里的异步复位引用,:20:9 是另一个时序块里的同步读取,排查时不用自己猜;第三,末尾 %Error: Exiting due to 1 warning(s) 表示 lint 模式下告警提升为退出错误,CI 直接卡构建。修复版把同步侧改读打拍后的 rst_n_sync 再复跑同一命令,零输出、退出码 0。
二、工具与版本
本篇实验在 Linux 容器环境用 Verilator 5.020(2024-01-01,Debian 打包版)实测,命令统一为 verilator --lint-only -Wall <file>.v。SYNCASYNCNET 在默认告警集里就启用,不依赖 -Wall;告警的「信号名 + 双行号(async/sync usage)」三要素从 4.x 到 5.x 保持一致。注意主行行号指向的是信号声明处(本例 5:17 即 rst_n 端口声明),真正的使用点要看下面两行 Location 标注,别对着声明行发呆。其他工具的对照:Yosys 综合不对此告警(它会照单综合出你写的结构),问题要靠 Verilator lint 或专门的 CDC/RDC 检查工具前置拦截。
三、根本原因:一根信号两种身份为什么危险
先厘清两种用法的硬件语义。异步复位:negedge rst_n 进敏感表,意味着 rst_n 拉低的瞬间触发器立刻清零,不等待时钟沿——这是「随时生效」的通路。同步读取:always @(posedge clk) 里 if (rst_n),意味着 rst_n 被当普通数据在时钟沿采样——这是「跟着节拍走」的通路。一根物理信号同时挂在这两种通路上,麻烦有三:
- 复位释放的亚稳态:异步复位拉低没问题,但释放(拉高)那一刻如果不满足触发器的恢复/移除时间(recovery/removal),输出可能亚稳。同步侧直接采样原始 rst_n,等于把同一个亚稳窗口又暴露给第二组触发器,且两路的判定结果可能不一致——一个块认为复位已结束、另一个还认为在复位,电路进入自相矛盾的状态。
- 工具实现分叉:综合器和布局布线对「异步复位脚」和「普通数据脚」走完全不同的物理通路(专用复位网络 vs 通用布线),一根网同时是两种身份,时序约束和时钟树综合都容易被误导。
- 意图模糊:读代码的人(和 lint 工具)无法判断你到底是想要异步复位还是想要一个同步状态位,后续维护改错一边的概率很高。
排查收敛路径一张图看全:
这张图的读法是:SYNCASYNCNET 不需要「找」——两处行号已经给你,真正要判断的是「哪一路是笔误、哪一路是意图」。九成情况同步那一路是顺手写的(想读「复位是否有效」),标准解法就是把这一路的读取对象换成经过同步器打拍的版本。
四、排查清单
拿到 SYNCASYNCNET 告警后按这份清单机械执行:
- 核对两处行号:打开 async usage 行确认是敏感表边沿用法(
negedge/posedge),打开 sync usage 行确认是块内if/赋值里的采样用法。 - 全库搜该信号:
grep -rn rst_n(或被点名的信号名)把所有引用列出来,确认除告警两处外没有更多混用点;多模块设计里同步读取常藏在别的文件。 - 判断意图:敏感表那一路几乎总是设计本意(异步复位);同步那一路问一句「我到底想读什么」——想读「复位有没有效」就改用同步后的状态位,想纯当使能就说明复位策略本身要重审。
- 检查复位策略一致性:同一模块里其他触发器是否都是同一套异步复位写法;混用有时是「一半寄存器异步复位、一半不复位」重构改到一半的残留。
- 收口复验:改完重跑同一条 lint 命令,退出码 0、无 SYNCASYNCNET 字样才算闭环;顺手搜一遍
lint_off SYNCASYNCNET,历史消音残留一并评估拆除。
五、修复代码与复跑确认
修法是「一信号一身份」:rst_n 只走异步复位敏感表;同步逻辑想读复位状态,先过经典的两级触发器复位同步器打拍,读打拍后的 rst_n_sync:
module syncasync_fixed (
input wire clk,
input wire rst_n,
input wire d,
output reg q,
output reg active
);
reg rst_ff1, rst_n_sync;
// 复位同步器:异步拉低、同步释放,两级打拍
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rst_ff1 <= 1'b0;
rst_n_sync <= 1'b0;
end else begin
rst_ff1 <= 1'b1;
rst_n_sync <= rst_ff1;
end
end
// rst_n 专职异步复位
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
q <= 1'b0;
else
q <= d;
end
// 同步侧只读 rst_n_sync,不再碰原始 rst_n
always @(posedge clk) begin
if (rst_n_sync)
active <= 1'b1;
else
active <= 1'b0;
end
endmodule对修复后的文件重跑同一命令,零告警、退出码 0(见第一节 termshot 末尾两行)。这个同步器本身就是业界标准的「异步复位、同步释放」结构:拉低仍然瞬时生效(安全侧),释放经过两级触发器与时钟对齐,亚稳态概率被压到可忽略。反面做法仍是 /* verilator lint_off SYNCASYNCNET */ 圈掉了事——告警消了,复位释放窗口和工具通路分叉的风险一分没少。
实验环境
- 机器/系统:Linux 容器环境(docker 镜像 geek-eda:0.2)
- 工具版本:Verilator 5.020 2024-01-01 rev (Debian 5.020-1)
- 日期:2026-10-29
- 完整命令:
verilator --lint-only -Wall syncasync_bad.v(报错复现,退出码 1);verilator --lint-only -Wall syncasync_fixed.v(修复后复验,退出码 0)
代码获取
复现用源码(syncasync_bad.v 报错版、syncasync_fixed.v 修复版)与完整 lint log 在公开仓 https://github.com/derekzhuo/geek-doc.cn/tree/main/experiments/verilator-warn-syncasyncnet。装好 Verilator 后跑 verilator --lint-only -Wall syncasync_bad.v 复现告警,再对 syncasync_fixed.v 跑同一命令验证修复。
这一篇在知识体系里的位置
本篇是 EDA 报错排查系列中 Verilator lint 告警簇的一员:一根信号被多处驱动看 %Warning-MULTIDRIVEN 排查;时序块里误用阻塞赋值的姊妹告警看 %Warning-BLKSEQ 排查;复位释放后跨域的专项问题看 RDC 复位域穿越。整个系列的目录在 EDA 报错排查系列。
总结
%Warning-SYNCASYNCNET 就是同一根信号(几乎总是复位)一路进 negedge 敏感表当异步控制、另一路又在时序块里被同步采样;告警把 async/sync 两处行号都钉给你,排查只需确认同步那一路是顺手写的读取,然后把它改读经两级触发器同步器打拍后的 rst_n_sync,原始 rst_n 专职异步复位,复跑 lint 零告警才算闭环;这一拆同时消灭了复位释放亚稳态与工具通路分叉两个隐患,永远不要用 lint_off 消音代替修复。