3、FPGA基础:FPGA架构简介、Verilog/VHDL基础、开发工具链(Vivado/Quartus)
好,咱们进入正题。FPGA 这东西,说白了就是一块可以让你随心所欲定义逻辑的芯片。不像 CPU 那样指令一条条跑,FPGA 是硬件级别的并行执行。你想想看,在金融行情解码这种场景下,纳秒级的延迟差异可能就是几百万的盈亏,FPGA 的并行能力简直就是为这个量身定做的。
3.1 FPGA 架构简介
我第一次接触 FPGA 是在做高频交易网关的时候。当时团队用 CPU 软解码,延迟始终压不进 10 微秒。后来换成 FPGA,直接降到了几百纳秒。嗯,差距就是这么夸张。
FPGA 内部主要由这几块构成:
- 逻辑单元(Logic Elements, LE):这是最基础的积木。每个 LE 包含一个查找表(LUT)和一个触发器(Flip-Flop)。LUT 实现组合逻辑,触发器实现时序逻辑。
- 可编程互连(Programmable Interconnect):把各个逻辑单元连起来的“高速公路”。布线的好坏直接影响时序。
- 块 RAM(Block RAM, BRAM):片上的存储资源。我习惯用它来做 FIFO 或者小容量的查找表。注意,BRAM 的读写延迟是固定的,这点在低延迟设计中很关键。
- DSP 切片(DSP Slice):专门做乘加运算的硬核。行情解码里做校验和或者价格计算时,DSP 比用 LUT 拼出来的快得多。
- 高速收发器(Transceiver):用来接 10G/25G/100G 以太网的。组播分发全靠它。
核心要点:FPGA 的并行性来源于它的“空间换时间”思想。你写的每一行代码,最终都会映射到芯片上的一块物理区域。多个模块同时运行,互不干扰。
下面这张图是我自己画的 FPGA 内部结构简图,帮你快速建立整体印象:
3.2 Verilog/VHDL 基础
我个人更偏爱 Verilog,语法简洁,上手快。VHDL 更严谨,适合大型团队。但在低延迟领域,两者都能用,关键看你怎么写。
3.2.1 模块化设计
每个 FPGA 设计都是从模块开始的。一个模块就是一个硬件单元。举个例子,一个简单的 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
注意看,always @(posedge clk) 这个写法,它描述的是一个时序逻辑。在 FPGA 里,它会映射到一个触发器和一些组合逻辑。我曾经见过新手把组合逻辑和时序逻辑混在一起写,结果时序跑不过,板子调了三天。
3.2.2 阻塞赋值 vs 非阻塞赋值
这是个经典坑。我刚开始学的时候也栽过。
- 阻塞赋值 (=):用于组合逻辑。赋值立即生效。
- 非阻塞赋值 (<=):用于时序逻辑。所有赋值在 always 块结束时同时生效。
避坑指南:我曾经在一个项目中,把组合逻辑和时序逻辑写在同一个 always 块里,用了混合赋值方式。结果仿真没问题,上板后数据全乱套。后来老老实实分开写,一个 always 块只做一件事。
3.2.3 状态机
行情解码里,状态机是核心。比如解析一个以太网帧,你需要经历:前导码 -> 帧起始 -> 目的 MAC -> 源 MAC -> 类型/长度 -> 载荷 -> FCS。每个状态对应一个硬件阶段。
localparam IDLE = 3'd0;
localparam MAC_DST = 3'd1;
localparam MAC_SRC = 3'd2;
localparam ETH_TYPE = 3'd3;
localparam PAYLOAD = 3'd4;
localparam FCS = 3'd5;
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 = (valid_in) ? MAC_DST : IDLE;
MAC_DST: next_state = (byte_cnt == 5) ? MAC_SRC : MAC_DST;
// ... 其他状态
default: next_state = IDLE;
endcase
end
你想想看,这种三段式状态机写法,综合出来的电路清晰,时序也好收敛。我习惯把状态转移和输出逻辑分开,调试的时候一目了然。
3.3 开发工具链(Vivado / Quartus)
Xilinx 用 Vivado,Intel 用 Quartus。两者流程大同小异,但细节上各有千秋。
| 环节 | Vivado | Quartus |
|---|---|---|
| 综合(Synthesis) | 支持多种综合策略,默认性能优先 | 综合速度较快,但优化选项略少 |
| 实现(Implementation) | 布局布线可精细控制,支持增量编译 | 布局布线相对自动化,手动干预空间小 |
| 时序分析 | 报告详细,支持多时钟域分析 | 时序报告直观,但跨时钟域分析稍弱 |
| 调试 | 集成 ILA(逻辑分析仪),方便抓信号 | Signal Tap 逻辑分析仪,功能类似 |
3.3.1 综合与实现
综合是把 Verilog/VHDL 代码转换成网表(门级电路)。实现则是把网表映射到具体的 FPGA 资源上。我个人的习惯是:
- 先跑一次综合,看看资源占用和警告。
- 再跑实现,重点看时序报告。
- 如果时序不满足,回头改代码,而不是盲目调工具选项。
小技巧:在 Vivado 里,我常用 report_timing_summary 命令快速查看最差路径。如果 setup slack 是负的,说明你的逻辑路径太长了。试试在关键路径上插入流水线寄存器。
3.3.2 时序约束
没有约束的 FPGA 设计,就像没有导航的船。你永远不知道它能不能跑到目标频率。
# 创建时钟约束
create_clock -name sys_clk -period 5.000 [get_ports clk] # 200 MHz
# 输入延迟约束
set_input_delay -clock sys_clk -max 2.0 [get_ports data_in]
set_input_delay -clock sys_clk -min 0.5 [get_ports data_in]
# 输出延迟约束
set_output_delay -clock sys_clk -max 1.5 [get_ports data_out]
set_output_delay -clock sys_clk -min 0.2 [get_ports data_out]
嗯,这里要注意:输入输出延迟的值,需要根据你的外部芯片数据手册来填。我曾经因为偷懒没填输出延迟,结果板级信号眼图惨不忍睹。
3.3.3 仿真与调试
仿真分功能仿真和时序仿真。功能仿真看逻辑对不对,时序仿真看实际跑起来会不会出问题。我一般流程是:
- 写 testbench,覆盖正常和异常情况。
- 跑功能仿真,确认行为正确。
- 综合后跑门级仿真,检查时序问题。
- 上板后用 ILA/Signal Tap 抓实际信号。
说白了,仿真能发现 90% 的问题,剩下 10% 靠上板调试。但上板调试成本高,所以仿真阶段多花点时间绝对值得。
总结一下:FPGA 基础这块,架构是骨架,Verilog/VHDL 是血肉,工具链是手脚。三者缺一不可。在金融行情解码这个场景里,你不仅要会写代码,还要理解代码怎么映射到硬件,怎么优化时序,怎么用工具定位问题。这些经验,都是在一次次流片和上板调试中积累出来的。
好,这一章就到这里。记住,FPGA 设计没有银弹,多动手、多踩坑,自然就熟了。