第四章:时序逻辑与状态机——让FPGA拥有“记忆”和“思维”
各位同学,欢迎来到第四章。前面我们聊了不少组合逻辑,说白了就是“输入一变,输出立马跟着变”。但现实世界哪有这么简单?你想想看,一个交易系统判断“是否要下单”,它得记住上一笔订单的状态、当前的价格、还有时间戳。这些“记忆”从哪来?
答案就是——时序逻辑。这一章,我们来啃这块硬骨头。
4.1 触发器与寄存器:FPGA的“记忆细胞”
时序逻辑的核心元件,就是触发器(Flip-Flop)。在FPGA里,我们最常用的是D触发器。它的行为很简单:在每个时钟上升沿,把输入D的值“拍”到输出Q上。
我刚开始学的时候,总觉得这玩意儿不就是个“延迟一拍”吗?没错,但这一拍,就是时序逻辑的根基。
多个D触发器并排放在一起,共享同一个时钟,就构成了寄存器。比如一个32位的寄存器,就是32个D触发器排排坐。
看个最简单的例子——一个带同步复位的D触发器:
always @(posedge clk) begin
if (rst) begin
q <= 1'b0;
end else begin
q <= d;
end
end
这里有个细节:<=是非阻塞赋值。在时序逻辑里,永远用非阻塞赋值。这是无数前辈用血泪换来的教训。我曾经在项目里混用了阻塞和非阻塞,结果仿真怎么都对,上板子就乱跳。查了三天,最后发现是赋值方式的问题。
4.2 同步复位 vs 异步复位:一个关于“安全感”的选择
复位,就是把电路恢复到初始状态。FPGA里主要有两种复位方式:同步复位和异步复位。
| 特性 | 同步复位 | 异步复位 |
|---|---|---|
| 复位条件 | 时钟沿 + 复位信号有效 | 复位信号有效(立即) |
| 优点 | 抗毛刺能力强,时序分析简单 | 响应快,不需要时钟也能复位 |
| 缺点 | 需要时钟存在才能复位 | 容易受毛刺影响,存在亚稳态风险 |
| 代码风格 | if (rst) 在 always 内部 | always @(posedge clk or negedge rst_n) |
我个人习惯,在高速交易系统里,尽量用同步复位。为什么?因为异步复位的释放时间如果离时钟沿太近,会产生亚稳态。高频交易里,一个亚稳态可能就意味着几百万的损失。
但有些场景,比如上电初始化,异步复位确实更方便。我的建议是:外部引脚用异步复位,内部逻辑全部转成同步复位。具体做法就是用一个两级寄存器打拍,把异步信号同步化。
4.3 有限状态机:让电路“按剧本走”
状态机,是时序逻辑的“大脑”。它让电路不再是简单的“输入→输出”,而是有了“状态”这个概念。你想想看,一个TCP连接有CLOSED、SYN_SENT、ESTABLISHED等状态,这不就是状态机吗?
FSM分为两类:
- Moore型:输出只取决于当前状态
- Mealy型:输出取决于当前状态和输入
在高频交易中,我更喜欢用Moore型。为什么?因为它的输出更稳定,不会因为输入毛刺而抖动。你总不希望交易状态机因为一个毛刺,从“等待”直接跳到“已成交”吧?
下面是一个简单的状态机示例——一个检测“101”序列的检测器:
// 状态编码
localparam IDLE = 2'b00;
localparam S1 = 2'b01;
localparam S10 = 2'b10;
localparam S101 = 2'b11;
reg [1:0] current_state, next_state;
// 第一段:状态转移
always @(posedge clk or posedge rst) begin
if (rst)
current_state <= IDLE;
else
current_state <= next_state;
end
// 第二段:次态逻辑
always @(*) begin
case (current_state)
IDLE: next_state = (data_in) ? S1 : IDLE;
S1: next_state = (data_in) ? S1 : S10;
S10: next_state = (data_in) ? S101 : IDLE;
S101: next_state = (data_in) ? S1 : S10;
default: next_state = IDLE;
endcase
end
// 第三段:输出逻辑
assign detected = (current_state == S101);
4.4 三段式状态机:工程界的“标准答案”
上面这个写法,就是经典的三段式状态机。我强烈建议你,从一开始就养成这个习惯。
三段式把状态机拆成三个部分:
- 第一段:时序逻辑,描述状态寄存器——就是那个
current_state <= next_state - 第二段:组合逻辑,描述次态转移条件——用
case根据当前状态和输入,算出下一个状态 - 第三段:输出逻辑——可以是组合逻辑,也可以是时序逻辑
为什么我推崇三段式?因为它在工程上最清晰、最不容易出错。我曾经接手过一个项目,前任用了一段式状态机,所有逻辑揉在一个always块里。那代码,说实话,我看了一周才敢改。
三段式的另一个好处是:便于维护和调试。如果状态跳转错了,你只需要看第二段;如果输出不对,你只需要看第三段。各司其职,互不干扰。
知识体系总览
为了让你更直观地理解本章的知识结构,我画了一张图:
这张图把本章的知识点串起来了。你从最底层的触发器开始,到复位策略的选择,再到状态机的设计,最后汇聚到三段式写法。每一步都是上一层的基石。
好了,这一章的内容就到这里。记住:时序逻辑是FPGA设计的灵魂,状态机是时序逻辑的骨架。把这两样东西吃透了,你就能写出稳定、高效、可维护的硬件代码。
公众号:蓝海资料掘金营,微信deep3321