iverilog reg 被原语或 assign 驱动:Variable cannot be driven 排查
更新时间:2026-10-31。FAQ 速查页,属于 EDA 报错排查系列。
iverilog reg 被原语或 assign 驱动常见于把模块输出、buf等门原语输出或assign左侧声明成了reg。它与 iverilog inout 端口连接错误 同源:都把只能由过程赋值的变量当成了可持续驱动的线网。
首屏答案:在 Verilog-2005 中,reg 是 variable,不是「物理寄存器」的代名词;它只能在 initial、always 等过程块里赋值。assign、门原语输出和模块输出端口需要的是 net,通常声明为 wire。因此,连续赋值报错时把目标改成 wire;若信号确实要由过程逻辑计算,就删除 assign 或原语实例,把同样逻辑放进 always @*,保留 reg。不要只把报错行当警告忽略,iverilog 会停止详细化,不会生成可运行的仿真文件。
本文要回答的问题
cannot be driven by a primitive和cannot be driven by a continuous assignment/module分别指哪里?- 为什么
reg名字里有「寄存器」,却不能表示一根被持续驱动的连线? - 同一根信号应该改
wire,还是改成always过程赋值? - 修复后怎样确认不是仅仅消除了编译报错?
一、iverilog reg 被原语或 assign 驱动的报错原文
下面的最小文件同时放入两类错误:continuous_target 是连续赋值左侧的 reg,primitive_target 是 buf 原语输出连接的 reg。source 只在 initial 中做过程赋值,这个声明本身没有问题。
module tb_bad;
reg source;
reg continuous_target;
reg primitive_target;
initial begin
source = 1'b0;
#1 source = 1'b1;
#1 $finish;
end
assign continuous_target = source;
buf primitive_buf(primitive_target, source);
endmodule使用 iverilog -g2005 编译,两种驱动方式会分别得到明确报错:
输出里有两组定位信息。第 14 行是 Variable 'continuous_target' cannot be driven by a continuous assignment/module,说明变量不能由连续赋值或模块输出驱动;第 15 行是 Variable 'primitive_target' cannot be driven by a primitive,后一行进一步指出原语输出表达式详细化失败。最终是 3 error(s) during elaboration,退出码为 3。错误发生在仿真运行之前,所以此时调整激励或增加延时没有任何作用。
This is allowed when SystemVerilog is enabled 只针对连续赋值那一项给出语言模式提示,不代表原语输出也能随意连接 reg,更不意味着当前 -g2005 工程可以忽略错误。对既有 Verilog 代码,先按驱动语义修正声明,比直接切换语言标准更能保持行为可预期。
二、先看驱动方式,再决定 wire 还是 reg
wire 与 reg 的区别不是「组合逻辑用 wire、时序逻辑用 reg」,而是谁来给它值。下面两栏把本例两条修复路线放在一起:颜色区分持续驱动与过程驱动,选择依据是赋值语句所在的位置。
读这张图只抓一个判断:看信号出现在哪种语句里,不看它最终是否综合成触发器。 assign y = a; 中的 y 用 wire;always @* y = a; 中的 y 用 reg。两种写法在这个一位缓冲示例里得到相同的仿真值,但赋值模型不同。reg 也不必然综合出寄存器,完整组合 always 块中的 reg 仍可综合成组合逻辑。
| 场景 | 正确声明 | 合法赋值形式 | 本例处理 |
|---|---|---|---|
| 连续赋值 | wire | assign target = source; | continuous_target 改为 wire |
| 门原语输出 | wire | buf (target, source); | 若保留原语,目标改 wire |
| 组合过程逻辑 | reg | always @* target = source; | primitive_target 保留 reg 并改过程赋值 |
| 时钟过程逻辑 | reg | always @(posedge clk) | 与连续驱动错误不是同一类写法 |
还要避免「两边都写」:把 reg 改成 wire 后,不能又在 always 块里给同一个 wire 赋值;反过来,保留 reg 改过程赋值时,要删除原来的原语实例。一个信号应选择一种清晰的驱动模型。多驱动线网的强度裁决又是另一个问题,双向端口可对照 iverilog inout 端口连接错误。
这里最容易过度修复的是看到 reg 就全局替换成 wire。例如测试台里的时钟和复位通常由 initial 或 always 产生,它们使用 reg 完全正确;状态机里的现态寄存器也由时序过程块赋值,同样不应改。报错只针对被持续驱动的那个目标,修改范围应限制在编译器指认的信号及其驱动语句。盲目全局替换会把原本合法的过程赋值变成一批新错误。
另一种误区是把 buf 当成可执行语句移进 always。门原语实例描述的是持续存在的硬件连接,always 块里应写行为表达式,而不是再实例化一个 buf。本例用 always @* primitive_target = source; 表达相同的值传递关系;更复杂的与、或、选择逻辑也可以放进组合过程块,但要保证所有路径都赋值,避免意外推断锁存器。
报错中的「详细化」阶段也值得区分。编译器此时已经读过语法,正在展开模块实例、连接端口并检查驱动目标是否合法。因此,这类错误通常能准确定位到例化或赋值行,却尚未进入事件调度与仿真。vvp 不是修复手段:详细化失败时没有新的可运行产物,旧目录里即使残留先前的 .vvp 文件,也只是上一次编译的结果,不能拿它证明当前源码可用。
三、两种修法与复跑结果
修复版故意同时展示两条路线,而不是机械地把所有 reg 都替换掉。连续赋值目标改为 wire;原来的 buf 则改写为 always @*,所以 primitive_target 继续声明为 reg。
module tb_fixed;
reg source;
wire continuous_target;
reg primitive_target;
assign continuous_target = source;
always @* primitive_target = source;
initial begin
source = 1'b0;
#1 source = 1'b1;
#1;
if (continuous_target === 1'b1 && primitive_target === 1'b1) begin
$display("PASS: both assignments work");
end
$finish;
end
endmodule这里的过程赋值采用阻塞赋值 =,符合组合 always @* 的常规写法;敏感表 @* 让 source 改变时重新执行。若原逻辑是带时钟的触发器,就应放回对应的 always @(posedge clk),而不是为了消错把它改成组合逻辑。
修复版编译成功后继续运行,两个目标都跟随 source 变为 1:
# command: iverilog -g2005 -o fixed.vvp fixed.v && vvp fixed.vvp
# date: 2026-10-31
# host-toolchain: MacBook Pro / macOS 26.5.2 / Icarus Verilog version 13.0 (stable) (v13_0)
---
continuous_target=1 primitive_target=1
PASS: wire continuous assignment and procedural assignment both work
fixed.v:18: $finish called at 2000 (1ps)这次验证包含两个层次:编译器不再拒绝信号声明,仿真中的两个输出也都等于预期值。实际工程复跑时还应检查其余报错,因为详细化提前停止可能遮住后面的独立问题;同时保留原有功能测试,避免把「能编译」误当成「逻辑正确」。
四、遇到同类报错的处理顺序
- 按信号名找声明:报错已经给出变量名和行号,先确认它是不是
reg、integer等 variable,不必先怀疑source或激励。 - 找全部驱动点:搜索该信号的
assign、原语实例、模块端口连接和过程块,确认是否存在多个相互冲突的驱动来源。 - 按语义二选一:外部持续驱动就改
wire;逻辑本应在过程块内计算就保留reg,删除持续驱动并补全always。 - 复跑编译与功能测试:确认详细化错误归零,再观察关键值。若改后出现新的多重驱动或敏感表问题,按新的报错继续收敛,不回退到错误声明。
预防上,可以在模块接口先区分「连线」与「过程变量」:子模块输出连接到上层时使用 wire,测试台主动在 initial 中变化的激励使用 reg。不要仅凭信号名叫 next_state 或 data_out 判断类型,赋值位置才是可靠依据。
实验环境
- 机器/系统:MacBook Pro / macOS 26.5.2
- 工具版本:Icarus Verilog version 13.0 (stable) (v13_0)
- 日期:2026-10-31
- 完整命令:
iverilog -g2005 -o bad.vvp bad.v(复现 3 条详细化错误,退出码 3);iverilog -g2005 -o fixed.vvp fixed.v && vvp fixed.vvp(修复版编译、运行并输出 PASS)
代码获取
最小复现、修复源码与运行脚本位于公开仓 https://github.com/derekzhuo/geek-doc.cn/tree/main/experiments/ts-iverilog-reg-driven-by-primitive。安装 iverilog 后进入该目录执行 bash run.sh,脚本会先确认 bad.v 编译失败,再验证 fixed.v 编译和仿真成功。
这一篇在知识体系里的位置
本篇处理 iverilog 簇中「变量被持续驱动」的类型错误。双向总线场景见 iverilog inout 端口连接错误,隐式声明导致的线网问题见 iverilog implicit wire 报错,其他开源工具报错可从 开源 EDA 报错排查导航 进入。
总结
iverilog reg 被原语或 assign 驱动 的根因是把 variable 当成 net:reg 只能在过程块内赋值,assign、门原语输出与模块输出则需要 wire。连续驱动场景将目标改为 wire;过程逻辑场景保留 reg,用 always 取代原来的持续驱动。Icarus Verilog 13.0 的复现中错误版以退出码 3 停止,按这两条路线修复后编译通过,两个目标均得到预期值 1。