3. Verilog HDL基础回顾(二):组合逻辑与时序逻辑、阻塞与非阻塞赋值、有限状态机
好,我们接着往下聊。上一章我们把Verilog的基本语法和数据类型过了一遍,这一章要聊的,是真正决定你写的代码是“能用的电路”还是“仿真能跑但综合就崩”的关键。说白了,就是组合逻辑、时序逻辑、赋值方式,还有状态机。
我见过不少新手,写出来的代码仿真波形漂亮得很,一上综合器就报一堆latch警告。为什么?就是这几个概念没理清楚。咱们一个一个来。
3.1 组合逻辑 vs 时序逻辑
先问个问题:你写的always块,综合出来是组合电路还是时序电路?答案其实就一句话——看敏感列表。
- 组合逻辑:敏感列表里只有电平信号(比如
@(*)或@(a or b))。输出只依赖当前输入,没有记忆。 - 时序逻辑:敏感列表里有时钟边沿(比如
@(posedge clk))。输出不仅依赖当前输入,还依赖之前的状态。
核心区别一句话:组合逻辑是“即时响应”,时序逻辑是“边沿触发,有记忆”。
我在项目中遇到过一位同事,把组合逻辑的敏感列表写成了 @(posedge clk),结果综合出来一堆奇怪的触发器。嗯,仿真能过,但实际电路完全不是他想要的样子。
3.1.1 组合逻辑写法示例
// 组合逻辑:多路选择器
always @(*) begin
if (sel)
out = a;
else
out = b;
end
3.1.2 时序逻辑写法示例
// 时序逻辑:D触发器
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
q <= 1'b0;
else
q <= d;
end
避坑指南:我曾经在组合逻辑里漏写了某个条件分支,结果综合出了latch。后来养成习惯——组合逻辑的case和if-else一定要写全,或者给default/else兜底。
3.2 阻塞赋值 vs 非阻塞赋值
这个坑,几乎每个Verilog初学者都踩过。我当年也栽过跟头。阻塞赋值用 =,非阻塞赋值用 <=。它们的行为完全不同,用错了,仿真和综合结果可能天差地别。
| 特性 | 阻塞赋值 = |
非阻塞赋值 <= |
|---|---|---|
| 执行顺序 | 立即执行,阻塞后续语句 | 右值先计算,块结束后统一更新 |
| 典型用途 | 组合逻辑 | 时序逻辑 |
| 综合结果 | 组合电路 | 触发器/寄存器 |
| 仿真行为 | 顺序执行,类似软件语言 | 并行更新,模拟硬件行为 |
你想想看,如果在一个always块里混用两种赋值方式,仿真器会怎么处理?我告诉你,大概率会出问题。所以我的习惯是:一个always块里只用一种赋值方式。组合逻辑用阻塞,时序逻辑用非阻塞,泾渭分明。
个人经验:写时序逻辑时,我永远用 <=。写组合逻辑时,我永远用 =。这个习惯帮我避免了很多莫名其妙的仿真-综合不一致问题。
3.2.1 错误示例:混用赋值方式
// 不推荐:混用阻塞和非阻塞
always @(posedge clk) begin
a = b; // 阻塞
c <= a; // 非阻塞
end
// 仿真结果可能和你想的不一样!
3.2.2 正确示例:分开写
// 时序部分
always @(posedge clk) begin
a <= b;
c <= a;
end
// 组合部分
always @(*) begin
d = a & b;
end
3.3 有限状态机(FSM)
状态机是数字设计的核心技能之一。去噪算法里,状态机用来控制数据流的处理阶段:采样、滤波、输出。说白了,就是让电路知道“现在该干什么,下一步该干什么”。
FSM分两类:
- Moore型:输出只取决于当前状态。
- Mealy型:输出取决于当前状态和输入。
我个人更常用Moore型,因为输出稳定,时序好分析。但Mealy型在某些场景下更省状态数,看需求选。
3.3.1 三段式状态机写法
我强烈推荐三段式写法。为什么?因为代码清晰,综合工具也好优化。三段分别是:
- 第一段:时序逻辑,状态跳转。
- 第二段:组合逻辑,次态判断。
- 第三段:组合逻辑,输出赋值。
// 三段式状态机示例
// 第一段:状态寄存器
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
state <= IDLE;
else
state <= next_state;
end
// 第二段:次态逻辑
always @(*) begin
case (state)
IDLE: if (start) next_state = RUN;
else next_state = IDLE;
RUN: if (done) next_state = DONE;
else next_state = RUN;
DONE: next_state = IDLE;
default: next_state = IDLE;
endcase
end
// 第三段:输出逻辑
always @(*) begin
case (state)
IDLE: out = 2'b00;
RUN: out = 2'b01;
DONE: out = 2'b10;
default: out = 2'b00;
endcase
end
为什么用三段式?因为组合逻辑和时序逻辑分开了,仿真容易调,综合结果也干净。我见过有人把状态跳转和输出写在一个always块里,代码一长就乱成一团。
3.4 本章知识体系
下面这张图把本章的核心概念串起来了。你看一眼,心里就有谱了。
3.5 本章小结
这一章的内容,说白了就是三个核心点:
- 组合逻辑 vs 时序逻辑:看敏感列表,电平还是边沿。
- 阻塞 vs 非阻塞赋值:组合用
=,时序用<=,别混用。 - 有限状态机:三段式写法,清晰又好调。
我的建议:刚开始学的时候,多写几个小模块练手。比如写一个简单的计数器,再用状态机控制它。写多了,这些概念就变成肌肉记忆了。
嗯,这一章就到这里。记住这些基础,后面去噪算法的RTL设计才能写得顺手。