4. 硬件描述语言速通:Verilog/VHDL核心语法、组合逻辑与时序逻辑、状态机设计
好,咱们直接切入正题。FPGA 开发,说白了就是用硬件描述语言(HDL)去“画电路”。你写的每一行代码,最终都会变成实实在在的门电路、触发器和连线。这和写软件完全是两码事——软件是顺序执行的,硬件是并行工作的。这个思维转变,是入门的第一道坎。
我个人习惯用 Verilog,因为它语法更灵活,在金融交易领域用得也最多。但 VHDL 在军工、航天领域依然很强势。咱们这章以 Verilog 为主线,同时我会点出 VHDL 的关键差异。你只要精通一门,另一门看语法表就能上手。
4.1 核心语法:从“线”到“寄存器”
先看最基本的元素。在 FPGA 里,数据流动靠的是“线”(wire)和“寄存器”(reg)。
- wire:组合逻辑的输出,相当于物理连线。你不能在 always 块里给它赋值。
- reg:时序逻辑的存储单元,可以理解为 D 触发器。注意,reg 不一定是寄存器,在组合逻辑的 always 块里它也能用,但我不建议这么干——容易把自己绕晕。
举个例子,一个简单的与门:
// Verilog
module and_gate (
input wire a,
input wire b,
output wire y
);
assign y = a & b;
endmodule
对应的 VHDL 写法:
-- VHDL
entity and_gate is
port (
a : in std_logic;
b : in std_logic;
y : out std_logic
);
end entity;
architecture rtl of and_gate is
begin
y <= a and b;
end architecture;
看到区别了吗?Verilog 用 assign 连续赋值,VHDL 用 <= 信号赋值。本质上都是描述组合逻辑。
assign 或 always @(*)。千万别把组合逻辑和时序逻辑混在一个 always 块里,否则综合工具会给你一堆警告,甚至综合出意想不到的锁存器(latch)。我曾经在一个高频交易项目里,就因为少写了一个 else 分支,综合出了一个 latch,导致整个流水线时序崩溃……排查了整整两天。
4.2 组合逻辑 vs 时序逻辑:核心区别
这是 FPGA 设计的灵魂。你想想看,为什么交易系统需要 FPGA?因为我们要在 一个时钟周期内 完成复杂的计算。组合逻辑负责“算”,时序逻辑负责“存”和“同步”。
| 特性 | 组合逻辑 | 时序逻辑 |
|---|---|---|
| 输出依赖 | 仅当前输入 | 当前输入 + 历史状态 |
| 存储能力 | 无 | 有(寄存器) |
| 时钟 | 不需要 | 需要(边沿触发) |
| 典型代码 | assign / always @(*) |
always @(posedge clk) |
| 延迟 | 门延迟(ns级) | 时钟周期延迟 |
举个实际例子。在交易系统中,我们需要计算买卖价差(spread)。这个计算本身是组合逻辑:
// 组合逻辑:计算价差
wire [31:0] bid_price, ask_price;
wire [31:0] spread;
assign spread = ask_price - bid_price;
但如果你要把这个结果传给下一级模块,就必须用寄存器打一拍:
// 时序逻辑:寄存结果
reg [31:0] spread_reg;
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
spread_reg <= 32'd0;
else
spread_reg <= spread;
end
为什么要多此一举?因为组合逻辑的输出可能有毛刺(glitch),而且路径延迟不确定。寄存器可以消除毛刺,让数据在时钟边沿稳定采样。嗯,这里要注意:在高速交易系统里,每一拍延迟都意味着真金白银。所以我们会尽量压缩流水线级数,但绝不能为了省一级寄存器而牺牲时序收敛。
always @(posedge clk) 里只放时序逻辑,always @(*) 里只放组合逻辑。这是铁律。
4.3 状态机设计:交易系统的“大脑”
状态机(FSM)是控制逻辑的核心。在交易系统中,状态机用来管理订单生命周期:从“空闲”到“发送订单”,再到“等待确认”,最后到“成交”或“撤销”。
我习惯用三段式状态机。为什么?因为代码清晰,综合工具优化得好,而且时序容易收敛。
三段式包括:
- 状态转移:时序逻辑,更新当前状态。
- 次态逻辑:组合逻辑,根据当前状态和输入决定下一状态。
- 输出逻辑:组合逻辑或时序逻辑,根据当前状态产生输出。
看一个简单的例子:一个交易订单状态机。
// 状态定义
localparam IDLE = 2'b00;
localparam SEND_ORD = 2'b01;
localparam WAIT_ACK = 2'b10;
localparam DONE = 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: begin
if (start_trade)
next_state = SEND_ORD;
else
next_state = IDLE;
end
SEND_ORD: begin
next_state = WAIT_ACK;
end
WAIT_ACK: begin
if (ack_received)
next_state = DONE;
else if (timeout)
next_state = IDLE; // 超时重试
else
next_state = WAIT_ACK;
end
DONE: begin
next_state = IDLE;
end
default: next_state = IDLE;
endcase
end
// 第三段:输出逻辑(组合逻辑)
reg send_enable;
reg clear_timeout;
always @(*) begin
// 默认值
send_enable = 1'b0;
clear_timeout = 1'b0;
case (state)
SEND_ORD: send_enable = 1'b1;
WAIT_ACK: clear_timeout = 1'b1;
// 其他状态保持默认
endcase
end
你看,三段式把“状态跳转”和“输出”分开了。这样调试的时候,你一眼就能看出状态机在哪个状态卡住了。我在做低延迟交易系统时,经常用逻辑分析仪抓状态机的状态信号,配合三段式代码,定位问题非常快。
4.4 知识体系总览
为了让你对本章内容有个整体印象,我画了一张图。它把 Verilog/VHDL 的核心语法、组合逻辑、时序逻辑和状态机设计串在了一起。
这张图把本章的核心脉络理清了。你从左上角的核心语法出发,掌握 wire/reg 和 assign/always。然后分两条路:组合逻辑负责计算,时序逻辑负责存储。最后,状态机把两者结合起来,实现复杂的控制逻辑。在交易系统里,这三者就像齿轮一样咬合在一起。
公众号:蓝海资料掘金营,微信deep3321