跳转到内容
新建笔记

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。

分支较多时,用过程式组合逻辑更清晰:

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 提供确定回退值。

因此不需要保存旧值,不会推导锁存器。

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 位。要求:

  1. 使用 always @(*) 和 case;
  2. 块开始先给 y 默认值;
  3. testbench 穷举四个选择值;
  4. 解释它为什么不会生成锁存器。

上一篇:运算符 · 返回目录 · 下一篇:时序逻辑