第四章:时序逻辑与状态机——寄存器、计数器、FSM设计、时钟分频

大家好,我是你们的FPGA讲师。今天咱们聊聊时序逻辑,这是FPGA设计的核心中的核心。说白了,组合逻辑负责“算”,时序逻辑负责“记”和“排”。没有时序逻辑,你的电路就是一盘散沙。

我个人习惯把时序逻辑比作“流水线上的工人”——每个时钟沿到来时,工人看一眼手里的活,然后做出反应。这个比喻虽然简单,但能帮你理解后面所有复杂的东西。

4.1 寄存器:时序逻辑的基石

寄存器,说白了就是一个带时钟的存储单元。它只在时钟沿(上升沿或下降沿)采样输入,其他时间输出保持不变。

我在项目中遇到过最典型的坑:有人把寄存器当组合逻辑用,结果仿真对了,上板子就乱跳。为什么?因为寄存器有建立时间和保持时间的要求,你输入变化太快,它采样到的就是“亚稳态”——既不是0也不是1,而是中间值。

⚠️ 避坑指南
我曾经在一个高频交易项目中,因为寄存器输入信号跨时钟域没做同步处理,导致整个交易引擎偶尔报出错误价格。排查了三天,最后发现是亚稳态在作怪。记住:跨时钟域信号必须用两级寄存器同步!

一个基本的D触发器(寄存器)的Verilog代码长这样:

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

注意这里的 always @(posedge clk or negedge rst_n),这是时序逻辑的标准写法。敏感列表里有时钟和复位,复位是异步的——也就是说,不管时钟在不在,复位一拉低,输出立刻清零。

4.2 计数器:从0到N的循环

计数器是寄存器最直接的应用。你想想看,一个寄存器加一个加法器,不就是计数器吗?

计数器在交易系统里太常见了:

  • 生成交易时钟的使能信号
  • 统计报文数量
  • 实现超时检测
  • 产生时间戳

一个简单的N位二进制计数器:

module counter #(
    parameter WIDTH = 8
) (
    input  wire                clk,
    input  wire                rst_n,
    input  wire                en,
    output reg [WIDTH-1:0]     count
);
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            count <= {WIDTH{1'b0}};
        else if (en)
            count <= count + 1'b1;
    end
endmodule

嗯,这里要注意:en信号是使能,它必须持续一个时钟周期的高电平,计数器才会加1。如果你想让计数器每N个时钟加一次,那就需要配合分频或者使能信号生成逻辑。

💡 个人经验
我在做低延迟交易系统时,计数器经常用来做“时间戳计数器”。系统上电后计数器从0开始,每个时钟加1。当交易事件发生时,直接锁存当前计数值作为时间戳。这样做比读系统时间快得多——少了几十纳秒的延迟。

4.3 有限状态机(FSM):控制逻辑的灵魂

FSM是时序逻辑的集大成者。它把电路的行为抽象成“状态”和“转移”,非常适合描述复杂的控制流程。

FSM分两种:

  • Moore型:输出只取决于当前状态
  • Mealy型:输出取决于当前状态和输入

我个人偏好Moore型,因为它的输出更稳定,不会因为输入毛刺而抖动。但在交易系统中,Mealy型有时能省一个时钟周期——这对低延迟来说很关键。

一个简单的FSM例子:检测串行数据中的“101”序列。

module seq_detector (
    input  wire clk,
    input  wire rst_n,
    input  wire data_in,
    output reg  detected
);
    // 状态编码
    localparam IDLE = 2'b00,
               S1   = 2'b01,
               S10  = 2'b10,
               S101 = 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
        next_state = state;
        case (state)
            IDLE: if (data_in) next_state = S1;
            S1:   if (!data_in) next_state = S10;
                  else next_state = S1;
            S10:  if (data_in) next_state = S101;
                  else next_state = IDLE;
            S101: next_state = data_in ? S1 : S10;
        endcase
    end

    // 输出逻辑
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            detected <= 1'b0;
        else
            detected <= (state == S101);
    end
endmodule

这段代码用了三段式FSM写法:状态寄存器、次态逻辑、输出逻辑分开写。我强烈建议你养成这个习惯——它让代码可读性高,也方便后期修改。

🔑 关键点
三段式FSM的好处:
1. 状态寄存器用时序逻辑,次态逻辑用组合逻辑,分工明确
2. 输出可以单独处理,方便加流水线寄存器
3. 修改状态转移不影响输出逻辑,反之亦然

4.4 时钟分频:从高频到低频

时钟分频,就是把一个高频时钟变成低频时钟。最简单的办法是用计数器。

比如,要把100MHz时钟变成50MHz(2分频):

module clk_div2 (
    input  wire clk_in,
    input  wire rst_n,
    output reg  clk_out
);
    always @(posedge clk_in or negedge rst_n) begin
        if (!rst_n)
            clk_out <= 1'b0;
        else
            clk_out <= ~clk_out;
    end
endmodule

但我要提醒你:在FPGA里,尽量不要用分频产生的时钟作为全局时钟。为什么?因为分频时钟走的是普通逻辑资源,不是专用的时钟网络,会导致时钟偏斜(skew)和抖动(jitter)问题。

更好的做法是:用时钟使能信号代替分频时钟。

// 生成时钟使能,每2个clk_in周期产生一个脉冲
reg clk_en;
always @(posedge clk_in or negedge rst_n) begin
    if (!rst_n)
        clk_en <= 1'b0;
    else
        clk_en <= ~clk_en;
end

// 在clk_en有效时执行操作
always @(posedge clk_in) begin
    if (clk_en) begin
        // 这里相当于在50MHz下工作
    end
end

这样做的好处是:所有逻辑仍然在同一个时钟域下,时序约束简单,也不会有跨时钟域的问题。

⚠️ 重要提醒
如果你非要用分频时钟(比如驱动外部芯片),请使用FPGA内部的PLL或MMCM资源。这些专用模块产生的时钟质量远好于逻辑分频。我在一个10G以太网项目中吃过亏——用逻辑分频的时钟驱动SerDes,结果眼图惨不忍睹。

知识体系总览

下面这张图概括了本章的核心内容,你可以把它当作学习路线图:

时序逻辑与状态机知识体系 时序逻辑 寄存器 计数器 有限状态机 寄存器关键点 • D触发器结构 • 建立/保持时间 • 亚稳态与同步 计数器应用 • 二进制计数器 • 使能信号生成 • 时间戳生成 FSM设计要点 • Moore vs Mealy • 三段式写法 • 状态编码选择 时钟分频(计数器实现) 最佳实践:用时钟使能代替分频时钟

这张图把本章的知识点串起来了。你从顶层的“时序逻辑”出发,往下走就是寄存器、计数器、FSM三大块,最后落到时钟分频和最佳实践。每个模块之间都有联系——比如计数器本质上就是寄存器加组合逻辑,FSM的状态寄存器也是寄存器。

好了,这一章的内容就到这里。记住:时序逻辑是FPGA设计的骨架,寄存器是砖块,状态机是设计蓝图。把这三样东西吃透了,你就能搭建出任何复杂的数字系统。


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