05. 连续赋值与组合逻辑
组合逻辑满足一个核心条件:输出只由当前输入决定,不需要记住过去。

写法一:assign 连续赋值
跳转到“写法一:assign 连续赋值”适合简短、直接的表达式:
module mux2_assign #( parameter WIDTH = 8) ( input wire [WIDTH-1:0] a, input wire [WIDTH-1:0] b, input wire sel, output wire [WIDTH-1:0] y);
assign y = sel ? b : a;
endmodule只要 a/b/sel 变化,y 就重新计算。y 由连续赋值驱动,所以声明为 wire。
写法二:always @(*)
跳转到“写法二:always @(*)”分支较多时,用过程式组合逻辑更清晰:
module alu_small ( input wire [7:0] a, input wire [7:0] b, input wire [1:0] op, output reg [7:0] y);
always @(*) begin case (op) 2'b00: y = a + b; 2'b01: y = a - b; 2'b10: y = a & b; 2'b11: y = a | b; default: y = 8'b0; endcase end
endmodule@(*) 让工具把块内读取的信号自动加入敏感列表。旧写法 always @(a or b or op) 容易漏信号,导致 RTL 仿真与综合硬件不一致。
组合过程使用阻塞赋值 =
跳转到“组合过程使用阻塞赋值 =”always @(*) begin temp = a + b; y = temp ^ mask;end第二行需要读取第一行刚算出的 temp,阻塞赋值自然表达组合计算步骤。推荐规则:
- 组合
always @(*)使用阻塞赋值=; - 时钟
always @(posedge clk)使用非阻塞赋值<=。
规则不是语法强制,而是为了减少竞态并让意图清楚。
锁存器是怎样意外出现的
跳转到“锁存器是怎样意外出现的”always @(*) begin if (enable) q = data;end当 enable=0 时,代码没有说明 q 应是什么。为了“保持旧 q”,综合器只能加入存储元件,这就是锁存器。

修正方法一,补全分支:
always @(*) begin if (enable) q = data; else q = 1'b0;end修正方法二,在块开始给默认值,再覆盖特殊情况:
always @(*) begin q = 1'b0; if (enable) q = data;end默认赋值法特别适合多个输出和复杂 case。
一个完整译码器
跳转到“一个完整译码器”module decoder2to4 ( input wire [1:0] code, input wire enable, output reg [3:0] one_hot);
always @(*) begin one_hot = 4'b0000; if (enable) begin case (code) 2'b00: one_hot = 4'b0001; 2'b01: one_hot = 4'b0010; 2'b10: one_hot = 4'b0100; 2'b11: one_hot = 4'b1000; default: one_hot = 4'b0000; endcase end end
endmodule检查所有路径:
enable=0:保留开头默认值;enable=1且code为正常二进制:每个分支赋值;- 仿真出现
x/z:default提供确定回退值。
因此不需要保存旧值,不会推导锁存器。
if 的优先级
跳转到“if 的优先级”always @(*) begin if (req_high) grant = 2'b10; else if (req_low) grant = 2'b01; else grant = 2'b00;end如果两个请求同时为 1,req_high 获胜。这是一条优先链,通常会综合为级联选择逻辑。若输入本应互斥,case 可能更清楚;若确实需要优先级,命名和注释应表达出来。
组合环路
跳转到“组合环路”assign a = ~b;assign b = ~a;这不是“两个变量互相算”,而是没有寄存器隔开的反馈网络。组合环路可能振荡、无法稳定或使时序分析失败。普通同步 RTL 中,反馈应经过寄存器。
仿真检查组合逻辑
跳转到“仿真检查组合逻辑”对输入组合较少的模块,可以穷举:
integer i;initial begin enable = 1'b0; code = 2'b00;
for (i = 0; i < 8; i = i + 1) begin {enable, code} = i[2:0]; #10; end
$finish;end在波形中重点看:输出是否有 x、每种输入是否得到期望结果、输入变化后输出是否及时变化。
组合逻辑审查清单
跳转到“组合逻辑审查清单”- 敏感列表是否用
@(*); - 所有输出是否在所有路径上赋值;
- 是否统一用阻塞赋值;
- 是否存在组合反馈;
- 位宽和符号是否匹配;
- 长优先链是否影响时序。
练习
跳转到“练习”编写一个 4 路 8 位多路选择器,sel 为 2 位。要求:
- 使用
always @(*)和case; - 块开始先给
y默认值; - testbench 穷举四个选择值;
- 解释它为什么不会生成锁存器。