一、FPGA内部结构:从逻辑单元到计算引擎
说实话,我第一次接触FPGA时,觉得这东西就是个「万能芯片」。后来做量化交易的低延迟系统,才真正理解它的内部构造有多精妙。咱们先看看FPGA到底由什么组成。
1.1 基本逻辑单元:LUT与FF
FPGA最核心的两个元件,就是查找表(LUT)和触发器(FF)。
- LUT(查找表):说白了就是一个可编程的真值表。你给它输入,它直接输出结果。比如4输入LUT,内部有16个存储位,可以实现任意4输入逻辑函数。我在做行情解析时,就用LUT实现了协议头部的快速匹配,比CPU查表快了一个数量级。
- FF(触发器):用来存储1位数据。它只在时钟边沿采样输入,然后保持输出。所有时序逻辑都靠它。
嗯,这里有个关键点:LUT做组合逻辑,FF做时序逻辑。两者配合,就能实现任何数字电路。
1.2 存储与运算:BRAM与DSP
光有LUT和FF还不够,FPGA里还有两个重量级资源:
| 资源 | 用途 | 我踩过的坑 |
|---|---|---|
| BRAM(块RAM) | 存储数据,容量大,速度快 | 曾经用LUT搭FIFO,结果资源爆了。后来改用BRAM,省了80%的逻辑资源。 |
| DSP(数字信号处理单元) | 做乘法、加法、累加 | 做期权定价模型时,DSP的流水线特性让我少写了很多时序逻辑。 |
我个人习惯:能用BRAM就别用LUT存数据,能用DSP就别自己搭乘法器。这是FPGA设计的黄金法则。
1.3 知识体系总览
下面这张图,是我自己总结的FPGA内部结构关系。你看一眼就明白了:
核心要点:FPGA设计就是合理分配这四种资源。LUT和FF做控制逻辑,BRAM存数据,DSP做运算。别搞混了。
二、Verilog基础语法:写代码就像搭积木
Verilog这语言,说白了就是描述硬件连接关系的文本。跟C语言完全两码事——它是并行的,不是顺序执行的。
2.1 模块与端口
每个Verilog文件就是一个模块(module)。模块有输入输出端口,就像芯片的引脚。
module led_controller(
input wire clk, // 时钟信号
input wire rst_n, // 复位信号(低有效)
output reg led // LED输出
);
// 内部逻辑写在这里
endmodule
我个人习惯:端口声明时,input/output写在前面,wire/reg类型紧跟其后。这样代码可读性最好。
2.2 三种描述方式
Verilog有三种描述方式,你都得会:
- 结构化描述:直接例化门电路。比如
and u1(out, a, b);。实际项目中很少用,太底层了。 - 数据流描述:用assign语句。适合组合逻辑。比如
assign led = a & b;。 - 行为级描述:用always块。最常用,能描述时序逻辑和复杂组合逻辑。
我的建议:新手先掌握行为级描述。always块配合if-else、case语句,能解决90%的问题。
2.3 阻塞赋值与非阻塞赋值
这是新手最容易翻车的地方。我曾经在项目里用错了赋值方式,导致仿真和实际跑的结果不一样,查了两天才找到原因。
- 阻塞赋值(=):顺序执行,用于组合逻辑。
- 非阻塞赋值(<=):并行执行,用于时序逻辑。
记住一个原则:写时序逻辑(always @(posedge clk))用非阻塞赋值,写组合逻辑用阻塞赋值。别混用。
三、Vivado开发流程:从代码到比特流
Vivado是Xilinx的FPGA开发工具。说实话,刚开始用的时候我觉得它太笨重了,但用顺手后才发现功能确实强大。
3.1 标准开发流程
- 创建工程:选芯片型号,添加源文件。
- 编写代码:写Verilog,添加约束文件(XDC)。
- 综合(Synthesis):把Verilog转成网表。说白了就是翻译成LUT和FF的连接关系。
- 实现(Implementation):布局布线。把网表映射到实际芯片上。
- 生成比特流:生成下载文件(.bit)。
- 下载调试:烧录到FPGA,用ILA抓波形看结果。
注意:综合和实现阶段,一定要看警告信息。我曾经忽略了一个时序警告,结果板子跑起来数据全是错的。血的教训。
3.2 约束文件怎么写
约束文件(XDC)用来告诉工具,你的信号对应哪个引脚、时钟频率是多少。
# 时钟约束
create_clock -period 10.000 [get_ports clk]
# 引脚约束
set_property PACKAGE_PIN U18 [get_ports clk]
set_property IOSTANDARD LVCMOS33 [get_ports clk]
set_property PACKAGE_PIN J15 [get_ports led]
set_property IOSTANDARD LVCMOS33 [get_ports led]
嗯,这里要注意:时钟约束一定要写对。10ns对应100MHz,如果你板子上的晶振是50MHz,那就写20.000。
四、第一个LED闪烁程序
好了,理论说完了,咱们动手写个LED闪烁程序。这是FPGA界的「Hello World」。
4.1 代码实现
module led_blink(
input wire clk, // 50MHz时钟
input wire rst_n, // 复位
output reg led // LED输出
);
// 计数器:从0计数到最大值
reg [24:0] cnt;
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
cnt <= 25'd0;
else if (cnt == 25'd24_999_999)
cnt <= 25'd0;
else
cnt <= cnt + 1'b1;
end
// LED翻转:每0.5秒翻转一次
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
led <= 1'b0;
else if (cnt == 25'd24_999_999)
led <= ~led;
else
led <= led;
end
endmodule
4.2 代码解析
这段代码的逻辑很简单:
- 50MHz时钟,周期20ns。要闪烁周期1秒(亮0.5秒,灭0.5秒),需要计数2500万次。
- 计数器从0到24999999,每计满一次,LED翻转一次。
- 复位时,计数器清零,LED熄灭。
避坑指南:计数器位宽一定要算对。25位计数器最大值是33,554,431,够用。如果你用24位,最大值才16,777,215,计不到2500万。我曾经犯过这个错,LED死活不闪,查了半天才发现是计数器溢出了。
4.3 上板验证
写完代码后,在Vivado里走一遍综合、实现、生成比特流。下载到板子上,如果LED以1秒周期闪烁,恭喜你,成功了!
如果没闪,别慌。先检查:
- 时钟引脚对不对?
- 复位引脚有没有拉高?
- LED引脚有没有接反?(有些板子是低电平亮)
我个人习惯:第一次上板,先写个最简单的程序——让LED常亮。确认硬件连接没问题后,再写闪烁程序。这样能快速定位问题。
好了,FPGA的基础知识和开发环境就讲到这里。记住:理论是骨架,实践是血肉。赶紧打开Vivado,把LED点亮吧。