一、FPGA 内部结构:这些基本单元你得心里有数
做运动控制,说白了就是跟“快”和“准”较劲。FPGA 为什么适合干这活?因为它内部那些基本单元,天生就是为并行计算和低延迟设计的。我刚开始接触 FPGA 时,总觉得它就是个“大号 CPLD”,后来踩了不少坑才明白——理解内部结构,是写好代码的第一步。
1.1 LUT(查找表)—— 最灵活的“逻辑拼图”
LUT 的全称是 Look-Up Table,你可以把它想象成一个“真值表”。输入几个信号,输出一个结果。比如一个 4 输入 LUT,内部其实就是一个 16x1 的 SRAM,输入信号作为地址,直接查表输出。
关键点:LUT 可以实现任意组合逻辑。你写的 assign y = a & b | c;,综合后就是某个 LUT 的配置。
我在项目中遇到过一个问题:同样的逻辑,用 LUT 实现和用门电路实现,延迟差很多。后来我养成了一个习惯——写代码前先估算一下 LUT 级数,尤其是高速运动控制中的位置比较逻辑,LUT 级数多了,时序就容易崩。
1.2 FF(触发器)—— 时序的“心跳”
FF(Flip-Flop)是 FPGA 里最基础的时序单元。每个 FF 都有一个时钟输入端、一个数据输入端 D、一个输出端 Q。时钟上升沿来的时候,D 的值被“锁”到 Q 上。
你想想看,运动控制里的位置计数器、速度累加器,哪个离得开 FF?我建议你把 FF 理解成“拍子”——每个时钟周期拍一下,数据就往前传一步。
个人习惯:我写 Verilog 时,所有时序逻辑都只用 always @(posedge clk),从不混合使用电平触发。这样综合出来的 FF 结构最干净,时序分析也最清晰。
1.3 BRAM(块 RAM)—— 数据缓存的好帮手
BRAM 是 FPGA 内部的专用存储资源。跟 LUT 搭出来的分布式 RAM 不同,BRAM 是硬核,速度快、容量大、功耗低。在运动控制里,BRAM 常用来存位置曲线表、电子凸轮表、或者 FIFO 数据缓冲。
我记得有一次做多轴插补,需要存 1024 个点的速度曲线。如果用 LUT 搭,资源直接爆了。换成 BRAM,只用了一个块,还省了布线资源。嗯,这里要注意:BRAM 有固定的端口宽度和深度,用之前一定要查手册。
| 资源类型 | 典型容量 | 运动控制常见用途 |
|---|---|---|
| LUT | 4~6 输入 | 组合逻辑、状态机 |
| FF | 1 bit | 寄存器、计数器 |
| BRAM | 36 Kb(典型) | 曲线表、FIFO、数据缓存 |
| DSP | 25x18 乘法器 | PID 运算、速度/位置计算 |
1.4 DSP(数字信号处理单元)—— 算得快才是硬道理
DSP 单元是 FPGA 里的“计算加速器”。它内部集成了乘法器、加法器、累加器。运动控制里的 PID 运算、速度前馈、位置插补,全是乘加运算。用 LUT 搭乘法器?太慢了,还费资源。直接用 DSP 硬核,一个时钟周期出结果。
我曾经在做一个高速追剪项目时,PID 运算需要在一个 10ns 的时钟周期内完成。用 LUT 搭的乘法器延迟太大,时序收敛不了。换成 DSP 硬核后,一拍搞定。所以说,DSP 资源是运动控制 FPGA 设计的“命根子”。
避坑指南:我曾经以为 DSP 单元可以无限使用,结果综合时发现资源不够。后来我学会了在代码里手动例化 DSP 原语,而不是依赖综合工具自动推断。这样资源利用率更高,时序也更好控制。
二、Vivado/Vitis 开发流程:从代码到比特流
工欲善其事,必先利其器。Vivado 是 Xilinx 的 FPGA 开发工具,Vitis 是它的软件配套。我个人的开发习惯是:Vivado 管硬件,Vitis 管软件。两者配合,才能做出完整的运动控制系统。
2.1 基本流程
- 创建工程:选芯片型号,比如 XC7Z020。我建议一开始就选对器件,不然后面改很麻烦。
- 编写 RTL 代码:Verilog 或 VHDL。运动控制部分我习惯用 Verilog,因为简洁。
- 仿真验证:写 testbench,跑功能仿真。这一步不能省,我见过太多人直接上板调,结果定位问题花了一天。
- 综合:把 RTL 代码转成网表。综合后可以看资源利用率,提前发现瓶颈。
- 实现:包括布局布线和生成比特流。这一步最耗时,也是时序收敛的关键。
- 下载调试:用 JTAG 下载比特流,然后用 ILA(集成逻辑分析仪)抓信号。
2.2 我常用的几个技巧
- 增量编译:每次只改一小部分代码时,用增量编译能省 70% 的时间。
- 时序报告:实现后一定要看时序报告。我习惯先看 setup 和 hold 的 slack,如果为负,说明时序有问题。
- Vitis 里的软件调试:如果用了 Zynq 系列,Vitis 里可以跑 ARM 代码。运动控制的上层协议栈,我一般放在 ARM 里跑,FPGA 只做实时计算。
小提示:Vivado 的 Tcl 脚本非常强大。我写了一个脚本,一键完成综合、实现、生成比特流。这样每次改完代码,跑一遍脚本就行,不用手动点来点去。
三、Verilog 基础语法回顾:够用就行
Verilog 语法很多,但做运动控制,常用的就那么几个。我挑重点说。
3.1 模块与端口
module motion_controller (
input wire clk,
input wire rst_n,
input wire [31:0] target_pos,
output reg [31:0] current_pos
);
// 逻辑代码
endmodule
模块就是 FPGA 里的“黑盒子”。端口分 input、output、inout。我习惯把所有输入都声明成 wire,输出根据情况用 reg 或 wire。
3.2 组合逻辑与时序逻辑
组合逻辑用 assign 或 always @(*),时序逻辑用 always @(posedge clk)。这个区分很重要,我见过有人把组合逻辑和时序逻辑混在一起写,结果综合出来的电路乱七八糟。
// 组合逻辑
assign next_pos = current_pos + step;
// 时序逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
current_pos <= 32'd0;
else
current_pos <= next_pos;
end
3.3 阻塞赋值与非阻塞赋值
这是新手最容易搞混的地方。简单说:
=(阻塞赋值):用在组合逻辑里,立即生效。<=(非阻塞赋值):用时序逻辑里,在时钟沿统一更新。
我曾经在写一个位置累加器时,用了阻塞赋值,结果仿真波形完全不对。查了半天才发现是赋值方式用错了。嗯,这个坑我替你们踩过了。
四、时序约束入门:让 FPGA 跑得稳
时序约束,说白了就是告诉工具:“我的时钟跑多快,哪些路径要优先优化。” 没有约束,综合工具就像没头苍蝇,布局布线全凭运气。
4.1 创建时钟约束
最基础的约束是创建时钟:
create_clock -name clk -period 10.000 [get_ports clk]
这条命令告诉 Vivado:时钟周期是 10ns,也就是 100MHz。如果运动控制需要更高的速度,比如 200MHz,就把 period 改成 5.000。
4.2 输入输出延迟约束
运动控制里,FPGA 经常要跟外部 ADC、编码器接口通信。这时候需要约束输入输出延迟:
set_input_delay -clock clk -max 2.000 [get_ports data_in]
set_output_delay -clock clk -max 2.000 [get_ports data_out]
这个值怎么定?我一般看外部器件的 datasheet,算一下 PCB 走线延迟和器件内部延迟,然后留 20% 的余量。
4.3 时序收敛的实战经验
我做过一个项目,运动控制时钟要求 150MHz,但综合后时序报告显示 slack 为负。怎么办?
- 加流水线:把长组合逻辑拆成多级,每级插一个 FF。
- 减少扇出:一个信号驱动太多 FF 时,复制几份。
- 调整布局:在 Vivado 里用 Pblock 把关键路径的模块固定到特定区域。
注意:时序约束不是越多越好。我见过有人加了上百条约束,结果工具优化不过来,反而更慢。我的原则是:只约束关键路径和时钟,其他交给工具自动处理。
本章知识体系
下面这张图,是我自己总结的 FPGA 运动控制知识框架。你可以把它当成一张“地图”,学完本章后,你应该能看懂每个模块的作用。
这张图把本章内容串起来了。硬件结构是基础,开发流程是工具,Verilog 和时序约束是技能。三者缺一不可。你学完这一章,应该能回答一个问题:一个运动控制算法,怎么在 FPGA 里落地?
公众号:蓝海资料掘金营,微信deep3321