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 三段式状态机写法

我强烈推荐三段式写法。为什么?因为代码清晰,综合工具也好优化。三段分别是:

  1. 第一段:时序逻辑,状态跳转。
  2. 第二段:组合逻辑,次态判断。
  3. 第三段:组合逻辑,输出赋值。
// 三段式状态机示例
// 第一段:状态寄存器
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 本章知识体系

下面这张图把本章的核心概念串起来了。你看一眼,心里就有谱了。

Verilog HDL基础回顾(二)知识体系 组合逻辑 vs 时序逻辑 阻塞 vs 非阻塞赋值 有限状态机 (FSM) 电平敏感 @(*) 输出只依赖当前输入 边沿敏感 posedge 有记忆,依赖历史状态 = 立即执行 用于组合逻辑 <= 块结束更新 用于时序逻辑 Moore型 Mealy型 核心原则 组合逻辑用阻塞赋值,时序逻辑用非阻塞赋值,FSM推荐三段式

3.5 本章小结

这一章的内容,说白了就是三个核心点:

  • 组合逻辑 vs 时序逻辑:看敏感列表,电平还是边沿。
  • 阻塞 vs 非阻塞赋值:组合用 =,时序用 <=,别混用。
  • 有限状态机:三段式写法,清晰又好调。

我的建议:刚开始学的时候,多写几个小模块练手。比如写一个简单的计数器,再用状态机控制它。写多了,这些概念就变成肌肉记忆了。

嗯,这一章就到这里。记住这些基础,后面去噪算法的RTL设计才能写得顺手。