4. Verilog基础语法(下):过程块、时序逻辑、状态机
好,咱们接着聊。上一节我们把Verilog的组合逻辑和连续赋值讲完了,说白了就是那些assign和always @(*)的用法。但真正做交易系统,光有组合逻辑可不行——你得有记忆,得有状态,得知道“上一笔订单成交了没有”。这就引出了我们今天的主角:过程块、时序逻辑、状态机。
我个人习惯把这三样东西称为“FPGA设计的骨架”。你想想看,一个低延迟交易系统里,从行情解析到订单生成,哪个环节不需要状态机来调度?哪个环节不需要时序逻辑来锁存数据?嗯,今天我们就把它彻底讲透。
4.1 过程块:always的两种灵魂
Verilog里最核心的过程块就是always。它有两种写法,对应两种完全不同的硬件行为:
- 组合逻辑型:
always @(*)—— 敏感列表里没有时钟沿,输出只随输入变化。 - 时序逻辑型:
always @(posedge clk)—— 敏感列表里有时钟上升沿或下降沿,输出在时钟边沿更新。
我在项目中遇到过不少新手,把这两种混着写,结果综合出来一堆奇怪的锁存器。记住一个原则:如果你想让信号在时钟边沿变化,就用posedge clk;如果只是组合逻辑,就用@(*)。
重要提醒:always @(*)里的*是自动推导敏感列表,意思是“所有输入信号变化时都触发”。千万别手动写敏感列表,容易漏信号,导致仿真和综合结果不一致。
4.2 时序逻辑:让电路拥有记忆
时序逻辑的核心是寄存器(Flip-Flop)。在Verilog里,我们通过always @(posedge clk)来生成寄存器。看个最简单的例子:
// 一个带同步复位的D触发器
reg [7:0] data_reg;
always @(posedge clk) begin
if (rst) begin
data_reg <= 8'b0;
end else begin
data_reg <= data_in;
end
end
这里有个细节:非阻塞赋值<=。为什么时序逻辑里要用<=而不是=?说白了,非阻塞赋值模拟了硬件寄存器的并行行为——所有赋值在时钟沿同时发生。如果你用阻塞赋值=,那综合出来的结果可能跟你想象的不一样。
我的小技巧:写时序逻辑时,一律用<=。写组合逻辑时,一律用=。这个习惯帮我避免了很多仿真和综合不一致的坑。
4.3 状态机:交易系统的调度核心
状态机(FSM)是低延迟交易系统的灵魂。从TCP连接管理到订单生命周期,从行情快照解析到风险检查,几乎每个模块背后都有一个状态机在默默工作。
我常用的状态机写法是三段式。为什么是三段?因为清晰、好维护、时序好收敛。咱们直接看代码:
// 三段式状态机示例:一个简单的订单状态机
module order_fsm (
input wire clk,
input wire rst,
input wire new_order,
input wire ack_received,
output reg send_order,
output reg order_done
);
// 第一段:状态定义
localparam IDLE = 2'b00;
localparam SENDING = 2'b01;
localparam WAITING = 2'b10;
localparam DONE = 2'b11;
reg [1:0] state, next_state;
// 第二段:状态转移(时序逻辑)
always @(posedge clk) begin
if (rst) begin
state <= IDLE;
end else begin
state <= next_state;
end
end
// 第三段:次态逻辑和输出逻辑(组合逻辑)
always @(*) begin
// 默认值
next_state = state;
send_order = 1'b0;
order_done = 1'b0;
case (state)
IDLE: begin
if (new_order) begin
next_state = SENDING;
send_order = 1'b1;
end
end
SENDING: begin
next_state = WAITING;
end
WAITING: begin
if (ack_received) begin
next_state = DONE;
order_done = 1'b1;
end
end
DONE: begin
next_state = IDLE;
end
default: begin
next_state = IDLE;
end
endcase
end
endmodule
你看,三段式把状态寄存器、次态逻辑、输出逻辑分得清清楚楚。我在做低延迟交易系统时,所有状态机都这么写。为什么?因为调试方便——你可以在波形里直接看到当前状态,也能看到下一状态,定位问题快得很。
我曾经踩过的坑:有一次做订单路由模块,状态机里漏写了default分支。结果在某个极端情况下,状态跳到了一个未定义的值,整个模块死锁了。从那以后,我每个状态机都加上default,哪怕只是回到IDLE状态。
4.4 状态机设计中的常见陷阱
做交易系统,状态机出问题就是真金白银的损失。我总结几个常见陷阱:
- 状态编码选择:小状态机用二进制编码(节省寄存器),大状态机用独热码(节省组合逻辑,时序更好)。我个人习惯,状态数少于8个用二进制,多于8个用独热码。
- 输出信号毛刺:组合逻辑输出容易有毛刺。如果输出信号要驱动关键路径(比如发送网络包),建议用寄存器打一拍再输出。
- 状态机死锁:一定要有超时机制。比如等待ACK的状态,如果一直等不到,应该超时重发或报错。
4.5 本章知识体系
下面这张图把本章的核心逻辑串起来了。你看,从过程块出发,分支出组合逻辑和时序逻辑,而状态机则是两者的结合体:
嗯,这张图把今天的内容串起来了。你想想看,从always出发,分支出组合逻辑和时序逻辑,最后汇聚到状态机——这就是Verilog数字设计的核心脉络。
好了,今天的内容就到这里。记住,写状态机时多想想“如果这个状态永远等不到下一个事件怎么办”,多留个心眼,你的交易系统就会更稳健。