CRITICAL WARNING [Synth 8-3352] multi-driven net:一条 wire 被 assign 和子模块同时驱动怎么修
更新时间:2026-11-10。本篇属于 FPGA 入门排错线,讲 Vivado 综合阶段的多驱动告警在结构化网表级的形态。同一报错码在行为级(两个 always 块写同一根 reg)的场景已有专文 Vivado 多驱动综合报错:两个 always 块驱动同一 reg,开源工具侧的同类告警见 Yosys multiple drivers to net 警告排查,三篇对照着读能覆盖多驱动问题的完整形态。
首屏答案:CRITICAL WARNING: [Synth 8-3352] multi-driven net y_OBUF 的意思是——输出端口 y 背后的那张物理网被两个驱动源同时灌值。本例的两个驱动源不是两个 always 块,而是一根 wire y 一边被 assign y = a & b; 连续赋值驱动,一边被子模块实例 and_sub u_and ( ... .y(y)) 的输出端口驱动。Vivado 在 IO 插入完成后的网表检查阶段连发两条 CRITICAL WARNING,分别把驱动点钉到 net_bad.v:9 的 i_1/O 和 and_sub.v:7 的 i_0/O,Check Netlist 里 multi_driven_nets 判 Failed,但综合流程不中断,结尾是 0 errors, 2 critical warnings。修法是给子模块输出单开一根内部线 sub_y,实例只驱动 sub_y,再用一个带 sel 的连续赋值 assign y = sel ? sub_y : (a & b); 把两个来源收编成 y 的唯一驱动,重跑后 0 error、0 critical warning、0 warning。
本文要回答的问题
- 同样是 [Synth 8-3352],「assign 加实例输出共驱一根 wire」和「两个 always 块驱动同一 reg」在网表层有什么不同?
- 告警里的
y_OBUF、1st driver pin 'i_1/O'、2nd driver pin 'i_0/O'分别对应源码的哪一行? - 为什么这种写法仿真可能只是偶尔出 x,综合却直接判网表 Failed?
- 修复时为什么不能简单删掉其中一个驱动,内部线加选择器的写法各自解决了什么?
- 重跑综合后要核对哪几个锚点,才能确认多驱动真的清干净而不是被优化掩盖?
先给一张告警分诊全景,后面的章节沿着这张漏斗逐层展开:从综合结束时的 2 条 CRITICAL WARNING,一路收敛到 net_bad.v 第 9 行那处连续赋值与实例输出的接线冲突。
读这张漏斗抓住一个要点:两条 CRITICAL WARNING 不是两个独立故障,而是同一张多驱动网的两个驱动点,Vivado 按「第一驱动、第二驱动」分两行列出。所以关单标准不是逐条消告警,而是让 y 在源码层面只剩一个驱动源,根因一动,两行一起消失。
一、现象:综合跑完,网表检查抓到双驱动
最小复现只有两个文件。顶层 net_bad.v 声明输出 y 为 wire,先写了一个连续赋值,又在下面例化一个与门子模块,并把子模块输出也接到 y 上:
// net_bad.v
module net_bad (
input wire a,
input wire b,
input wire c,
output wire y
);
assign y = a & b;
and_sub u_and (
.a(c),
.b(a),
.y(y)
);
endmodule子模块本身平平无奇,内部也是一个连续赋值:
// and_sub.v
module and_sub (
input wire a,
input wire b,
output wire y
);
assign y = a & b;
endmoduleTcl 脚本只有读源码和综合两行,用 vivado.bat -mode batch -source v7_err.tcl 跑批处理。综合全程不中断,告警出现在 IO 插入完成之后,两条消息把两个驱动点和源码位置一次给全:
终端卡片受版面宽度限制会截断长行,两条告警的完整文本如下,措辞、引脚名和文件行号均原样摘自同一份日志:
CRITICAL WARNING: [Synth 8-3352] multi-driven net y_OBUF with 1st driver pin 'i_1/O' [C:/eda-lab/b1/ts-vivado-synth-8-3352-multi-driven-net/net_bad.v:9]
CRITICAL WARNING: [Synth 8-3352] multi-driven net y_OBUF with 2nd driver pin 'i_0/O' [C:/eda-lab/b1/ts-vivado-synth-8-3352-multi-driven-net/and_sub.v:7]紧接着的 Report Check Netlist 表格里,multi_driven_nets 一行状态为 Failed,计数是 0 error、1 warning——这里的 1 指的是「1 张多驱动网」,对应上面两条驱动点描述。综合总结行给出最终口径:
Synthesis finished with 0 errors, 2 critical warnings and 0 warnings.注意它是 CRITICAL WARNING 而不是 error:流程体面收场,exit code 仍为 0,但网表已经带着明确的结构性错误,后续实现阶段的时序收敛和上板行为都不可信。Cell Usage 表显示坏版综合出 LUT2 ×2、IBUF ×3、OBUF ×1——两个与门各自变成一个 LUT2,输出却只能接同一个 OBUF,这正是双驱动在资源层面的投影。
二、机理:一根物理导线为什么不能有两个驱动源
要理解这条告警,先把 Verilog 的 wire 语义和 FPGA 硅片的物理约束分开看。Verilog 语言里,wire 是一根可以被多个驱动源驱动的连线,仿真器靠值解析函数处理冲突:两个驱动值相同就取该值,不同(一个 0 一个 1)就解析成 x(未知态)。所以这段代码在仿真里不会报错,只是当 a & b 与 c & a 结果不一致时,y 会飘出 x,testbench 不仔细比对输出就很容易漏过去。
综合器面对的是真实器件。xc7a35t 里一张普通布线网在任一时刻只能被一个输出引脚驱动,否则两个 LUT 输出级互相灌电流,既没有确定的逻辑值,也可能损伤器件。唯一合法的例外是显式三态总线:多个三态缓冲挂同一根 inout 线,靠使能信号保证同一时刻只有一个缓冲导通。本例没有任何三态结构,于是综合在 IO 插入阶段把输出 y 配上输出缓冲,缓冲前的网命名为 y_OBUF,网表检查时发现这张网上挂着两个输出引脚,直接判多驱动。
这就解释了告警里的命名。综合器把顶层的连续赋值 assign y = a & b 展开成一个 LUT 单元,层级展平后编号为 i_1,它的输出引脚就是 i_1/O,回溯位置是 net_bad.v:9;子模块实例 u_and 展平后编号为 i_0,内部与门的输出引脚是 i_0/O,回溯到 and_sub.v:7 那行 assign y = a & b;。两个 O 引脚接在同一张 y_OBUF 网上,告警按驱动点分两行列出。
本篇与已有的行为级专文必须区分清楚,二者报错码相同、代码形态和修法不同:
| 对比维度 | 本篇:网表级双驱动 | 行为级双驱动(见互链专文) |
|---|---|---|
| 冲突载体 | 一根 wire 型输出 y | 一个 reg 型变量 y |
| 两个驱动源 | 顶层连续赋值 + 子模块实例输出端口 | 两个 always 块各自对 y 过程赋值 |
| 告警落点 | y_OBUF 网,driver pin 为 i_1/O、i_0/O | 同一 reg 综合出的网,driver pin 指向两个 always 块 |
| 仿真表现 | 驱动值冲突时 wire 解析为 x | 后执行的赋值覆盖先执行的,可能静默不报错 |
| 修复手法 | 实例输出落内部线,再加选择器收编 | 合并为单个 always 块,块内 if/else 表达分支 |
两类问题共享同一条硬件纪律:除三态总线外,一个信号只能有一个驱动源。开源工具链对同一纪律的执法方式不同,Yosys 在 check 阶段给出 multiple conflicting drivers 警告并缩进列出打架的驱动源,根因二分法和修法对照见 Yosys multiple drivers to net 警告排查;Verilator 的 MULTIDRIVEN lint 规则可以在综合之前就从源码层面拦住这类写法。仿真与综合语义分叉的系统性讨论见 仿真通过但上板失败的常见原因。
三、定位:从两条 driver pin 回溯到冲突接线
拿到带 8-3352 的日志,定位动作是一条单向收敛链:先读告警给出的网名和两个引脚,再按 file:line 回源码确认驱动语句,最后判断两个驱动源各自代表的设计意图。下图颜色按角色区分:红是根因入口,蓝是排查动作,橙是修复动作,绿是复验闭环,紫是防复发沉淀。
读这张图抓住 check 这个判断点:定位到两个驱动源之后,先别急着删代码,要回答「这两个驱动各自想表达什么功能」。本例里连续赋值代表 a & b 这条直通路,子模块代表 c & a 这条备选路,两者都是设计有意为之,冲突不在「多写了一个驱动」,而在「缺一个把两条路合流的选择结构」。如果不问意图直接删掉实例,告警确实消失,但功能也被删没了,这是多驱动排查最常见的错误关单方式。
批处理日志里可以直接 grep -E "Synth 8-3352|multi_driven_nets" vivado.log 把告警和网表检查结论一次捞出来。还有一个容易误判的点:告警行号指的是驱动语句所在文件,i_0/O 指向 and_sub.v:7 并不代表子模块写错了——子模块内部单驱动完全合法,真正的接线错误在顶层把它的输出端口连到了一张已有驱动的网上。排查视线要始终落在顶层的端口连接表 .y(y) 上。
四、修复:内部线加选择器恢复单驱动
修复版 net_fixed.v 做两件事。第一,新增一根内部线 wire sub_y;,子模块实例的输出端口改接 sub_y,实例只驱动这根内部线,不再触碰 y。第二,新增选择输入 sel,用唯一一个连续赋值在两个来源之间做选择,y 从此只有这一个驱动源:
// net_fixed.v
module net_fixed (
input wire a,
input wire b,
input wire c,
input wire sel,
output wire y
);
wire sub_y;
and_sub u_and (
.a(c),
.b(a),
.y(sub_y)
);
assign y = sel ? sub_y : (a & b);
endmodule这个改法的关键是每个信号都满足单驱动:sub_y 的唯一驱动是实例输出,y 的唯一驱动是选择器连续赋值,选择意图显式写在 RTL 里,综合器据此生成一个 LUT4 而不是两个互相冲突的 LUT2。如果设计本来就只需要其中一路,更简单的修法是删掉多余的那个驱动源;如果未来真的需要多个设备分时驱动同一根线,则要把端口改成 inout 并显式例化三态缓冲(OBUFT),用使能保证互斥,那是三态总线的合法多驱动形态,和本例不是一回事。
行为级场景的修法对照着记:两个 always 块驱动同一 reg 时,合并成一个 always 块、块内用 if/else 表达分支即可,不需要引入内部线。两类修法的共同点是把「谁驱动这个信号」变成源码里一眼可见的唯一结构,而不是依赖仿真器的覆盖顺序或线与解析。
五、验证:告警归零与网表结构变化
修好后用 v7_fix.tcl 重跑同一流程。这次日志里两条 8-3352 全部消失,Check Netlist 不再出现 Failed 行,总结行变成三个零:
这段输出有三个验证锚点,逐一对应坏版的症状。第一,总结行是 Synthesis finished with 0 errors, 0 critical warnings and 0 warnings.,日志末尾的工具统计同样是 13 Infos, 0 Warnings, 0 Critical Warnings and 0 Errors encountered.,多驱动告警没有残余。第二,Cell Usage 从坏版的 LUT2 ×2、IBUF ×3、OBUF ×1 变成 LUT4 ×1、IBUF ×4、OBUF ×1:两个打架的 LUT2 合成为一个四输入 LUT4,新增的 sel 让 IBUF 从 3 个变成 4 个,网表结构与「选择器二选一」的设计意图完全对上。第三,Check Netlist 表中 multi_driven_nets 的 Failed 行不再出现,网表检查整体通过。
修复前后的核对项整理成下表,数字均来自本次两次综合的真机日志:
| 核对项 | 坏版 net_bad | 修复版 net_fixed |
|---|---|---|
| Synth 8-3352 | 2 条,指向 net_bad.v:9 与 and_sub.v:7 | 0 条 |
| Check Netlist | multi_driven_nets 判 Failed | 无 Failed 项 |
| 综合总结行 | 0 error、2 critical warning、0 warning | 0 error、0 critical warning、0 warning |
| y 的驱动源数 | 2(连续赋值 + 实例输出) | 1(选择器连续赋值) |
| Cell Usage | LUT2 ×2、IBUF ×3、OBUF ×1 | LUT4 ×1、IBUF ×4、OBUF ×1 |
复验时不要只看 exit code——坏版的 exit code 同样是 0。资源报告的读法基础见 Vivado 资源利用率报告怎么看,信号被综合器优化删除这类「流程成功但电路不对」的通用排查见 Vivado 信号被综合优化消失排查。
六、预防清单
- 子模块输出一律先落内部线:实例端口不要直接连到已有其他驱动的外部网上,先接一根
wire sub_xxx;,再由顶层唯一个连续赋值或逻辑块收编,从接线上消灭双驱动。 - 画连接表时数驱动源:写完每个模块的实例化和 assign,顺着每个输出端口回数一遍它有几个驱动,超过一个且没有三态使能,就是设计错误。
- lint 前置拦截:提交前跑 Verilator 的 MULTIDRIVEN 规则,开源综合用 Yosys 的
check对照,把多驱动挡在 Vivado 综合之前,交叉跑最稳。 - 批处理日志机检:脚本里 grep
Synth 8-3352命中即判失败,同时把 Check Netlist 的 Failed 状态纳入红线,CRITICAL WARNING 不允许靠人眼翻日志兜底。 - 三态总线显式化:确实需要多驱动的场景只认显式三态结构,inout 端口加
OBUFT互斥使能,并在代码评审时单独说明,避免和普通多驱动混淆。 - 复验看网表不看退出码:重跑后核对 8-3352 归零、Check Netlist 无 Failed、Cell Usage 与设计意图一致,三个锚点齐了才算关单。
实验环境
- 机器/系统:Windows(64 位)
- 工具版本:Vivado v2017.4(SW Build 2086221,synth_design 批处理)
- 器件:xc7a35tcpg236-1
- 日期:2026-11-08
- 完整命令:
vivado.bat -mode batch -source v7_err.tcl(报错复现,Tcl 内容为read_verilog and_sub.v net_bad.v+synth_design -top net_bad -part xc7a35tcpg236-1);vivado.bat -mode batch -source v7_fix.tcl(修复复现,net_fixed.v 同流程综合)
代码获取:完整复现工程(net_bad.v / net_fixed.v / and_sub.v / 两份综合 Tcl / README,以及两份实测综合日志)在公开仓 https://github.com/derekzhuo/geek-doc.cn/tree/main/experiments/ts-vivado-synth-8-3352-multi-driven-net,装好 Vivado 2017.4(或任意含 synth_design 的版本)后按 README 依次跑两组批处理命令,即可复现两条 8-3352 告警与修复后的全零总结行。
这一篇在知识体系里的位置
本篇是 FPGA 入门排错线中 Vivado 多驱动告警的网表级分支,与行为级专文 Vivado 多驱动综合报错:两个 always 块驱动同一 reg 共用 Synth 8-3352 报错码而代码形态互补,两篇合起来覆盖多驱动问题的两大类写法。开源工具侧的同类执法见 Yosys multiple drivers to net 警告排查 与 Verilator 的 MULTIDRIVEN 规则,整个开源报错簇的入口见 开源 EDA 报错排查总览;仿真与综合语义分叉的背景见 仿真通过但上板失败的常见原因,复验所需的资源报告读法见 Vivado 资源利用率报告怎么看。
总结
Vivado 多驱动综合报错 [Synth 8-3352] multi-driven net 在网表级的典型形态,是一根 wire 输出同时被连续赋值和子模块实例输出驱动:实测在 Vivado 2017.4 下,IO 插入后的网表检查在 y_OBUF 上连发两条 CRITICAL WARNING,两个驱动点分别回溯到 net_bad.v:9 的 i_1/O 与 and_sub.v:7 的 i_0/O,Check Netlist 判 multi_driven_nets Failed,但综合不中断、exit code 仍为 0;它与两个 always 块驱动同一 reg 的行为级场景同码不同形,修法也不同——本例通过新增内部线 sub_y 承接实例输出、再用 sel 选择器把两条通路收编成 y 的唯一驱动,重跑后告警归零、两个 LUT2 合成为一个 LUT4;复验要同时核对 8-3352 消失、网表检查无 Failed、Cell Usage 符合单驱动意图,并把多驱动 lint 和日志 grep 写进提交红线,从源头保证「一个信号只有一个驱动源」。