第三章 自适应引擎:可编程逻辑(PL)架构详解
好,咱们进入正题。这一章聊的是 Versal 的「心脏」之一——可编程逻辑,也就是 PL 部分。很多人觉得 PL 不就是 FPGA 那点老本行吗?其实不然。Versal 的 PL 架构,跟传统的 7 系列、UltraScale 相比,做了不少「手术」。我刚开始接触时,也花了不少时间重新适应。
3.1 CLB:逻辑单元的基本盘
CLB(可配置逻辑块)是 PL 里最基础的单元。说白了,它就是一堆 LUT(查找表)和触发器的组合。Versal 的 CLB 跟上一代比,最大的变化是 LUT 的结构。
每个 CLB 包含 8 个 6 输入 LUT,以及 16 个触发器。嗯,这里要注意:LUT 可以拆成两个 5 输入 LUT 来用。我在项目中遇到过这种情况——当你的逻辑刚好是 5 输入时,这种拆分能省下不少资源。
关键点:Versal 的 LUT 支持「双输出」模式。同一个 LUT 可以同时输出两个不同的函数。这招在实现加法器、比较器时特别管用。
我个人习惯,在写 RTL 代码时,会刻意把一些组合逻辑控制在 5 输入以内。为什么?因为这样综合工具能更高效地映射到 LUT 上。你想想看,如果每个 LUT 只用了 4 个输入,那剩下的 2 个输入就浪费了。
3.2 DSP:不只是乘加器
DSP 模块,全称是 DSP slice。Versal 里用的是 DSP58,比上一代的 DSP48 多了 10 位宽度。这 10 位差别在哪?
- 精度提升:支持 58 位累加,适合高精度定点运算
- 模式丰富:支持复数乘法、浮点运算(需要配合逻辑)
- 级联能力:多个 DSP 可以无缝级联,不用走通用布线资源
我记得有一次做 FIR 滤波器,数据位宽是 24 位,系数也是 24 位。如果用 DSP48,一个乘加器只能处理 18x18 的乘法,得拆成两个。但 DSP58 直接搞定,省了一半资源。
小技巧:DSP58 的预加器(Pre-adder)可以用来实现对称 FIR 滤波器。把对称系数先加起来再乘,能省一个乘法器。我曾经用这个技巧,把 64 阶滤波器的 DSP 用量从 64 个降到了 32 个。
3.3 Block RAM 与 UltraRAM
Versal 的 PL 里有两种 RAM:Block RAM(BRAM)和 UltraRAM(URAM)。它们各有各的脾气。
| 特性 | Block RAM | UltraRAM |
|---|---|---|
| 容量 | 36 Kb | 288 Kb |
| 最大频率 | ~600 MHz | ~450 MHz |
| 支持双口 | 是(真双口) | 是(简单双口) |
| 级联方式 | 通过布线资源 | 专用级联路径 |
BRAM 是「万金油」。36Kb 的容量,可以配置成 32Kx1、16Kx2、8Kx4 等等。我建议你记住一个规律:BRAM 的深度和宽度是反比关系。深度越大,宽度越小。
URAM 就不一样了。它容量大,但速度稍慢。而且它只能做简单双口——也就是一个口只读,一个口只写。我在做视频缓存时特别喜欢用 URAM。一帧 1080p 的图像,用 URAM 来存行缓冲,比用 BRAM 省事多了。
避坑指南:我曾经在 URAM 上吃过亏。URAM 的级联路径是专用的,但如果你在级联路径上插了寄存器,时序反而会变差。为什么?因为专用路径的延迟是固定的,加了寄存器反而破坏了它的流水线结构。所以,URAM 级联时,尽量别在路径上加额外逻辑。
3.4 AI 引擎的接口与数据流
AI 引擎(AIE)是 Versal 的「杀手锏」。但 AIE 跟 PL 怎么通信?这是个关键问题。
AIE 和 PL 之间,主要有三种接口:
- AXI-Stream:最常用的数据通道。每个 AIE 阵列有 32 个 AXI-Stream 接口,每个接口带宽高达 128 Gbps。
- AXI-MM:用于配置和状态读取。说白了,就是 CPU 通过这个接口来读写 AIE 的寄存器。
- PL 到 AIE 的直接连接:通过 AIE 阵列边界的「PL 接口块」。这个接口延迟极低,适合实时性要求高的场景。
数据流怎么走?我画个简单的流程:
PL (数据源) → AXI-Stream → AIE 阵列 (处理) → AXI-Stream → PL (数据汇)
嗯,看起来简单。但实际项目中,你得考虑数据格式对齐。AIE 的数据宽度是 128 位(16 字节),而 PL 侧可能是 32 位或 64 位。这时候就需要做位宽转换。
经验之谈:我建议在 PL 侧做一个「数据打包器」。把多个小数据包拼成 128 位的字,再发给 AIE。这样能充分利用 AIE 的带宽。我曾经见过一个项目,因为没做打包,AIE 的利用率只有 30%。做了打包后,直接飙到 85%。
还有一个细节:AIE 的存储是分级的。每个 AIE 核心有本地存储器(Local Memory),大小是 64 KB。多个 AIE 核心之间可以通过「核心到核心」的 DMA 传输数据。这种传输不需要经过 PL,延迟极低。
我个人习惯,在写 AIE 程序时,会尽量把热点数据放在本地存储器里。如果数据量太大,再考虑用 PL 侧的 BRAM 或 DDR 做后备。你想想看,从本地存储器读数据,延迟只有几个时钟周期;从 DDR 读,延迟是几百个周期。这差距,不是一星半点。
最后,提一下 AIE 的时钟。AIE 阵列有自己的时钟域,跟 PL 的时钟是异步的。跨时钟域处理,得用 AXI-Stream 的握手信号。我曾经见过有人忘了做同步处理,结果数据传过去全是乱码。嗯,这种坑,踩过一次就记住了。
好了,这一章就聊到这。下一章,咱们看看「标量引擎」——也就是 Arm 处理器那边,又有什么新花样。