FPGA基础回顾:从LUT到时序收敛
各位同学,咱们开始第一节课。说实话,每次讲FPGA基础,我都觉得特别重要。你想想看,如果地基没打好,后面盖再高的楼也悬。我在低延迟交易系统里踩过的坑,十有八九都是基础不牢导致的。
今天咱们就聊聊FPGA内部到底长什么样,Verilog怎么写才靠谱,以及那个让无数人头疼的时序收敛问题。
FPGA内部结构:不只是逻辑门
很多人以为FPGA就是一堆门电路拼在一起。其实没那么简单。FPGA内部是一个高度结构化的阵列,我习惯把它想象成一个「乐高城市」——有住宅区、商业区、交通枢纽。
LUT(查找表)—— 逻辑的灵魂
LUT说白了就是一个「查字典」的电路。你给它几个输入,它直接输出对应的结果。现在的FPGA主流是6输入LUT,能实现任意6输入布尔函数。
我在做行情解析时,经常用LUT来做协议解码。比如以太网帧头的类型字段,用LUT查一下就知道是UDP还是TCP,比用逻辑门拼快多了。
FF(触发器)—— 时序的基石
触发器就是FPGA里的「记忆单元」。每个LUT后面基本都跟着一个FF,它们俩是黄金搭档。
我记得刚入行时,有个同事写代码忘了加always块的敏感列表,结果综合出来一堆透明锁存器。那板子调了三天才找到问题。嗯,从此我养成了写完整敏感列表的习惯。
BRAM(块RAM)—— 数据缓存专家
BRAM是FPGA里真正的「内存」。每个BRAM有36Kb,可以配置成单端口、双端口、甚至FIFO。
在交易系统里,我用BRAM做订单簿的深度缓存。双端口特性特别适合——一个端口写数据,另一个端口读数据,互不干扰。
| BRAM配置 | 位宽 | 深度 | 典型用途 |
|---|---|---|---|
| 单端口 | 1-36位 | 1024-32768 | 参数存储 |
| 真双端口 | 1-36位 | 1024-32768 | 跨时钟域 |
| 简单双端口 | 1-72位 | 512-16384 | FIFO |
DSP48(数字信号处理单元)—— 算力担当
DSP48是FPGA里的「计算器」。一个DSP48可以完成25x18位的乘法,还能级联做乘加运算。
做低延迟交易时,计算订单价格、风险敞口这些,我全用DSP48。比用LUT拼乘法器快一个数量级。
SerDes(串行收发器)—— 高速接口
SerDes是FPGA和外界通信的「高速公路」。现在的GTY收发器能跑到56Gbps,延迟只有几纳秒。
我曾经在项目中用SerDes直接连接交易所的10G网络,绕过了CPU和网卡,延迟从微秒级降到了纳秒级。这就是硬件加速的魅力。
Verilog基础语法:够用就行
我不喜欢讲太多语法细节。你想想看,写FPGA又不是写软件,核心就三个东西:组合逻辑、时序逻辑、状态机。
组合逻辑:assign和always@(*)
// 组合逻辑:用assign
assign sum = a + b;
// 组合逻辑:用always块
always @(*) begin
if (sel)
out = in1;
else
out = in2;
end
时序逻辑:always@(posedge clk)
// 时序逻辑:带同步复位
always @(posedge clk) begin
if (rst)
count <= 0;
else
count <= count + 1;
end
这里有个关键点:非阻塞赋值 <= 和阻塞赋值 = 的区别。我建议时序逻辑全用 <=,组合逻辑全用 =。混着用容易出问题。
状态机:三段式写法
// 状态编码
localparam IDLE = 2'b00;
localparam READ = 2'b01;
localparam WRITE = 2'b10;
// 第一段:状态转移
always @(posedge clk) begin
if (rst) state <= IDLE;
else state <= next_state;
end
// 第二段:次态逻辑
always @(*) begin
case (state)
IDLE: next_state = start ? READ : IDLE;
READ: next_state = done ? IDLE : READ;
default: next_state = IDLE;
endcase
end
// 第三段:输出逻辑
assign busy = (state != IDLE);
三段式状态机是我最推荐的写法。可读性强,时序好收敛,综合工具也喜欢。
FPGA开发流程:从代码到比特流
整个流程说白了就三步:写代码、跑工具、调时序。但每一步都有坑。
- 综合(Synthesis):把Verilog转成网表。我习惯先看综合报告,检查有没有意外生成的锁存器。
- 布局布线(Place & Route):把网表映射到FPGA物理资源上。这一步最耗时,也最考验耐心。
- 时序收敛(Timing Closure):检查所有路径是否满足时序要求。不满足?回去改代码。
时序收敛:硬着头皮也要过
时序收敛是FPGA开发里最磨人的环节。说白了就是检查你的设计能不能跑在目标频率上。
我刚开始做设计时,总觉得时序分析可有可无。直到有一次,板子调通了但跑着跑着就出错。查了三天,发现是一条关键路径的建立时间违例了。嗯,从那以后我再也不敢跳过时序分析了。
- 建立时间(Setup Time):数据必须在时钟沿之前稳定。违例了?降频或者加流水线。
- 保持时间(Hold Time):数据必须在时钟沿之后保持稳定。违例了?加延迟或者调整布局。
- 时钟偏斜(Clock Skew):时钟到达不同FF的时间差。用全局时钟资源可以缓解。
我的经验是:先看最差路径(Worst Negative Slack),从那里下手。通常加一级流水线就能解决大部分问题。
避坑指南:我踩过的那些雷
- 时钟域跨越:不同时钟域的信号一定要同步。我见过有人直接连,结果数据全乱了。
- 复位策略:尽量用同步复位,异步复位容易出毛刺。
- 资源浪费:别把BRAM当寄存器用,也别用LUT拼乘法器。用对资源,事半功倍。
好了,FPGA基础就回顾到这里。这些内容看着简单,但每个点都能展开讲一节课。咱们后面会反复用到这些知识,到时候再细聊。