3.1 assign 连续赋值:组合逻辑的直接证词 本节摘要:assign 建立的是「永远有效」的映射关系:右侧任何信号变化,左侧立即重新求值。它是数据流建模的主力设施,目标必须是 net 类型,综合成组合逻辑。写好 assign 的关键不是语法,而是确认你描述的逻辑函数没有隐藏的环路。 上一章把证物登记完毕,本章开始质证。第一份证词是 assign——Verilog 里最接近「逻辑表达式」本义的设施,也是三种建模范式中数据流的主场。 「连续」的确切含义 assign 语句不是「执行一次的动作」,而是一份长期有效的委托:右侧表达式中任何一个信号变化,就触发一次重新求值,结果立即送往左侧。仿真里这体现为事件驱动的连锁更新;综合里这直接对应一片组合逻辑。
本节摘要:assign 建立的是「永远有效」的映射关系:右侧任何信号变化,左侧立即重新求值。它是数据流建模的主力设施,目标必须是 net 类型,综合成组合逻辑。写好 assign 的关键不是语法,而是确认你描述的逻辑函数没有隐藏的环路。
上一章把证物登记完毕,本章开始质证。第一份证词是 assign——Verilog 里最接近「逻辑表达式」本义的设施,也是三种建模范式中数据流的主场。
assign 语句不是「执行一次的动作」,而是一份长期有效的委托:右侧表达式中任何一个信号变化,就触发一次重新求值,结果立即送往左侧。仿真里这体现为事件驱动的连锁更新;综合里这直接对应一片组合逻辑。两个视角的共同点是:它没有记忆,输出是输入的纯函数。
module alu_slice( input wire [7:0] a, b, input wire [1:0] op, output wire [7:0] y ); assign y = (op == 2'd0) ? (a + b) : (op == 2'd1) ? (a - b) : (op == 2'd2) ? (a & b) : (a | b); endmodule
这份四功能运算单元是 assign 的典型用法:条件运算符串联出选择结构,右值整体是一个纯函数。综合器会把它化简成由加法器、减法器、按位门与多路选择器组成的电路,逻辑与表达式严格等价。
几条语法边界要钉死。目标必须是 net 类型(通常就是 wire),上一章讲过这背后的驱动来源逻辑;一条 assign 只能有一个左侧目标,要给多位总线多处赋值就写多条语句;右侧可以调用 function,不能调用 task;assign 上可以挂延迟 assign #2 y = a & b;——这是仿真行为建模,综合器直接忽略,第四章讲延迟模型时会展开。
assign 到一个未声明的名字时,工具会隐式生成一条 wire——这个「便利」是把双刃剑:
module typo_trap( input wire [7:0] din, output wire [7:0] dout ); assign dout = din; // 这里手误写成 dount 的话 endmodule
把 dout 敲成 dount,工具不报错,默默生成一条新 wire dount,接住 din 的值;而端口 dout 无人驱动,输出恒为高阻 z。仿真波形上 dout 一片灰色,追查半天发现是拼写错误——这类案子在真实工程里每周都有人开庭。防线有两条:Lint 工具的隐式声明检查(第九章展开),以及编码时坚持「先声明后使用」的纪律。

assign 描述的是无记忆映射,一旦输出通过某条路径绕回自己的输入,就形成了组合环——数学上这可能无解,硬件上表现为振荡或亚稳态。语言层面它常常是无声的:
module ring_hazard( input wire [1:0] a, b, output wire [1:0] y ); wire [1:0] t1, t2; assign t1 = a ^ t2; assign t2 = b ^ t1; assign y = t1 & t2; // t1 与 t2 互为因果:组合环 endmodule
t1 依赖 t2,t2 又依赖 t1,仿真器按事件调度可能永不收敛(报迭代上限错误),综合器则可能直接报组合环,也可能「看漏」生成一个振荡电路——取决于工具的环检测严格度。纪律很简单:assign 的有向依赖图必须是无环的。需要反馈就经过寄存器,让时钟沿把环切开,这是第四章时序逻辑的题眼。
案例展开:优先编码器的两种证词。 背景:某中断控制器需要把多位请求线编码成中断号,高位优先。操作:先用嵌套条件运算符写——y = req[3] ? 2'd3 : req[2] ? 2'd2 : ...,仿真正确;改用 assign 配合与或表达式手写「高位屏蔽低位」的逻辑,仿真发现在只有一个请求时结果一致,多个请求同时到时两种写法结果不同。结果:回头审查发现手写版本漏了一层屏蔽项,修正后两者一致。解读:条件运算符天然表达优先级链,手写布尔式表达优先级容易漏项——组合逻辑越复杂,越应该把「人读得懂的陈述」交给条件运算符或 case,让化简交给综合器。变式:把编码器扩展到八位、十六位,手写布尔式很快不可维护,此时行为级 case 或循环辅助生成才是正路——具体写法在 3.3 节展开。
assign 与 always @(*) 都能写组合逻辑,选择标准是表达力:单个表达式能写清的用 assign——一眼见底;需要分支、中间变量、循环辅助的用 always 组合块——3.2 节展开。两者综合结果等价,风格上混用没有问题,但同一个信号只能有一种驱动方式,混驱是编译期错误。
工程里还有一条实用经验:关键路径上的复杂组合逻辑,用 assign 写出顶层函数关系,把子函数抽成 function 或独立模块,层次清楚之后做时序收敛时才知道该切哪一刀。这个习惯在第八章面积功耗速度三角里会再次用到。
以「一眼能看出逻辑关系」为限。超过三个条件运算符嵌套的表达式,读者要在脑子里压栈才能理清优先级——这段逻辑更适合搬进组合块用分支写。工程上有条经验线:一行 assign 描述一个函数,函数复杂到需要换行解释,就该给它一个名字(function)或一个结构(组合块)。
可以,多条 assign 各管一段是常见写法:低位段一条、高位段一条、状态位一条。这比一条大表达式拼接清晰得多,每段各自可读。要注意的仍是那条老规矩——各段互不重叠,重叠即多驱动。
简单映射类逻辑坚持用 assign:译码、选择、标志生成,一行表达式直接对上硬件形态,评审时一眼通过。组合块留给需要中间变量、分支结构、循环辅助的复杂逻辑。判断标准是「描述与电路的对应清晰度」:assign 与门的对应关系最直接,能用最直接的方式表达就不绕路。
assign 讲完了组合世界的陈述方式。下一节进入过程块:always 与 initial 的两种身份,以及全语言最凶险的一条分界线——阻塞与非阻塞。