时序逻辑与状态机:从触发器到序列检测器

大家好,我是你们的FPGA讲师。今天我们来聊聊时序逻辑和状态机——这两个东西,说白了就是数字电路的“记忆”和“决策”能力。没有它们,你的电路就只能做组合逻辑,像个没脑子的计算器。

我个人觉得,学FPGA设计,最难跨过的坎就是时序逻辑。为什么?因为组合逻辑是“即时响应”,输入一变,输出马上变。但时序逻辑不一样,它要“记住”过去的状态,还要“决定”未来的走向。嗯,这就像下棋,你不能只看当前这一步,得想好后面几步怎么走。

D触发器:数字电路的“记忆细胞”

先讲最基础的——D触发器。你可以把它想象成一个“单比特存储器”。它只有一个数据输入D,一个时钟CLK,一个输出Q。时钟上升沿来的时候,Q就变成D的值;时钟没来,Q就保持原样。

我在项目中遇到过一个问题:有个同事用D触发器做数据同步,结果发现数据老是丢。查了半天,原来是时钟域没处理好。D触发器虽然简单,但用不好会出大问题。

// 最简单的D触发器
module d_flip_flop (
    input  wire clk,
    input  wire rst_n,
    input  wire d,
    output reg  q
);
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            q <= 1'b0;
        else
            q <= d;
    end
endmodule

你看这个代码,敏感列表里既有clk又有rst_n。这就是我们下面要讲的——同步复位和异步复位的区别。

同步复位 vs 异步复位:一个老生常谈的话题

同步复位,意思是复位信号只在时钟上升沿才起作用。异步复位呢?复位信号一来,不管时钟在不在,立刻复位。

我个人习惯用异步复位、同步释放的方式。为什么?因为异步复位能保证复位信号及时生效,避免毛刺;同步释放又能防止复位撤除时出现亚稳态。你想想看,如果复位信号在时钟上升沿附近撤除,那D触发器可能进入“半复位半工作”的状态,数据就乱了。

避坑指南:我曾经在一个高速项目中用了纯异步复位,结果复位网络太大,导致复位信号到达各个触发器的时间不一致。后来改成异步复位同步释放,问题就解决了。

复位类型 优点 缺点 适用场景
同步复位 综合后面积小,时序易分析 复位信号宽度必须大于时钟周期 低速设计、复位信号干净
异步复位 复位响应快,不依赖时钟 易产生亚稳态,复位释放需同步 高速设计、需要快速复位

有限状态机FSM:数字电路的“大脑”

状态机,说白了就是让电路“记住自己走到哪一步了”。FSM分两种:Moore型和Mealy型。Moore型的输出只取决于当前状态,Mealy型的输出还取决于输入。

我建议初学者先从Moore型开始学,因为它的输出和输入是“隔离”的,不容易出错。等你熟练了,再用Mealy型——它更灵活,但调试起来也更头疼。

个人经验:写状态机时,我习惯把状态编码定义成localparam,而不是直接用数字。这样代码可读性高,也方便后期修改。另外,状态机最好分成三个always块:一个描述状态转移,一个描述次态逻辑,一个描述输出逻辑。这样结构清晰,综合工具也容易优化。

// 三段式状态机示例
module fsm_example (
    input  wire clk,
    input  wire rst_n,
    input  wire in,
    output reg  out
);
    localparam IDLE = 2'b00,
               S1   = 2'b01,
               S2   = 2'b10,
               S3   = 2'b11;

    reg [1:0] state, next_state;

    // 状态寄存器
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            state <= IDLE;
        else
            state <= next_state;
    end

    // 次态逻辑
    always @(*) begin
        case (state)
            IDLE: next_state = in ? S1 : IDLE;
            S1:   next_state = in ? S2 : IDLE;
            S2:   next_state = in ? S3 : IDLE;
            S3:   next_state = in ? IDLE : IDLE;
            default: next_state = IDLE;
        endcase
    end

    // 输出逻辑
    always @(*) begin
        case (state)
            S3:   out = 1'b1;
            default: out = 1'b0;
        endcase
    end
endmodule

序列检测器实战:从理论到代码

好了,理论讲完了,我们来做个实战——序列检测器。检测“1011”这个序列,检测到了就输出高电平。

为什么选这个例子?因为序列检测器是状态机最经典的应用之一。你想想看,通信协议里的帧头检测、数据包识别,本质上都是序列检测。

注意:序列检测器有两种:重叠检测和非重叠检测。重叠检测的意思是,检测到“1011”后,最后一个“1”可以作为下一个序列的起点。非重叠检测则不行。我这里用的是重叠检测,因为更实用。

// 序列检测器:检测1011(重叠)
module seq_detector (
    input  wire clk,
    input  wire rst_n,
    input  wire din,
    output reg  dout
);
    localparam IDLE = 3'b000,
               S1   = 3'b001,
               S2   = 3'b010,
               S3   = 3'b011,
               S4   = 3'b100;

    reg [2:0] state, next_state;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            state <= IDLE;
        else
            state <= next_state;
    end

    always @(*) begin
        case (state)
            IDLE: next_state = din ? S1 : IDLE;
            S1:   next_state = din ? S1 : S2;   // 收到1,继续等0
            S2:   next_state = din ? S3 : IDLE; // 收到0,等1
            S3:   next_state = din ? S4 : S2;   // 收到1,等下一个1
            S4:   next_state = din ? S1 : S2;   // 检测到1011,重叠
            default: next_state = IDLE;
        endcase
    end

    always @(*) begin
        dout = (state == S4) ? 1'b1 : 1'b0;
    end
endmodule

这段代码里,S4状态就是检测到“1011”的时刻。注意看S4的次态逻辑:如果输入是1,回到S1(因为最后一个1可以作为下一个序列的起点);如果输入是0,回到S2(因为“10”已经匹配了)。这就是重叠检测的精髓。

知识体系总览

下面这张图,是我花时间画的。它把本章的知识点串起来了——从D触发器到复位方式,再到状态机,最后到序列检测器。你把它存下来,学完本章后再回头看,会更有感觉。

时序逻辑与状态机知识体系 D触发器原理 同步复位 vs 异步复位 有限状态机 FSM Moore型状态机 Mealy型状态机 序列检测器实战

这张图从D触发器开始,一路延伸到复位方式、状态机类型,最后落到序列检测器。你跟着这个脉络学,就不会迷路。

好了,本章的内容就到这里。记住,时序逻辑和状态机是FPGA设计的核心,也是区分“会用”和“精通”的分水岭。多写代码、多仿真、多踩坑,你才能真正掌握它们。


公众号:蓝海资料掘金营,微信deep3321