本页是关于如何成为专业 FPGA 设计师的系列五篇文章中的第四篇。这次我要讲一些在入门阶段不那么重要的技能。先解释一下,为什么值得专门写这些东西。
为什么要提不重要的事?
专门写一页讲技能,同时又说“嗯,这个不太重要”,听起来有点怪。但这个系列不是在告诉你该做什么。我是在尽量解释每个主题为什么重要(或者为什么没那么重要)。那么对不那么重要的主题也照此办理,就很合理了。
有些实用技能常常有用,却被高估了。这些技能你也许在某个时候需要掌握,但也可能一辈子都用不上。一个主题之所以会被高估,是因为它经常出现在开发板的示例设计里。不是因为重要,而是因为用某项特定技术很容易做出漂亮又唬人的演示。
先把话说清楚:下面提到的所有技能都有用。只不过它们通常是在项目需要时才去学的,而且是和那个项目需求所特有的一大堆其他技术主题一起学的。
I2C 与 SPI
你完全可以做一辈子 FPGA 设计师,对这两个东西一无所知。但现实一点说,你很可能在实际工作中很快就撞上它们。
I2C(IIC,Inter-Integrated Circuit)、与它非常相似的若干协议,以及 SPI(Serial Peripheral Interface),是 PCB 上与某个电子元件之间传输数据时最常用的标准。它们基于主 / 从(master / slave)结构:主设备发起一次读或写操作,从设备则可能给出响应。大多数情况下,主设备是一个处理器或一个相对复杂的元件,从设备则是电子设计中承担某种外设功能的较简单元件。
I2C 以及由它派生的协议数据率非常低(通常约 100 kbit/s),主要用于配置元件的参数。比如,如果这个元件是 A/D 转换器,可以用 I2C 选择它采用哪个电压基准,以及它的输出应该是带符号还是无符号整数。这个协议的主要优点是连接只需要两根线:时钟(SCL)和数据(SDA)。此外还要接地(GND),但通常走 PCB 的公共地,几乎从不需要单独拉一根线。
这个协议其实是一条总线,所以每个数据交换周期都包含一个 7 位地址。因此可以把多个元件并行接在这一对线上。这种情况下,每个元件必须有不同的地址。
I2C 标准最初由飞利浦申请了专利。由于它太实用了,很多极为相似的变体被广泛使用,比如 SMBus、PMBus 和 DDC2。这些标准沿用了 I2C 的主要思路,但参数(尤其是数据率)和主要使用场景略有不同。比如,今天卖出的所有电脑显示器里都有一小块闪存,存放着它支持哪些图形模式的信息。电脑显卡与显示器之间的线缆中有两根线接到这块闪存上。这样显卡就能用 DDC2 协议读取这块存储器里的数据,而 DDC2 实际上和 I2C 是一回事。
所以,只要你懂了 I2C,你就知道了一大堆不同元件之间是怎么对话的。
接下来是 SPI。这个协议通常需要主从元件之间四根线(SCLK、MOSI、MISO、SSn)。有时它像 I2C 及其各种变体一样,用于配置元件。不过 SPI 的主要用途是以更高的数据率传输数据。元件支持 20–50 MHz 的 SCLK 频率并不少见,所以它是个适合传输应用数据的实用协议。比如,一个用于两个音频通道的 A/D 转换器产生的数据率是 48,000 Hz × 2 × 16 bit = 1.536 Mbit/s。这个数据率对 SPI 来说轻而易举。事实上,确实有好几款音频芯片用 SPI 传输采样数据。
还值得一提的是,FPGA 与它读取比特流(bitstream)的那块闪存之间的连接通常是 QSPI。这个接口和普通 SPI 一样,只不过数据线是四根而不是一根,因此数据传输更快。
那么回到结论性的问题:作为成为 FPGA 设计师的一部分,你应该学 I2C 和 SPI 吗?我的看法是,它们本身和 FPGA 设计没什么关系。但如果你在一个开发电子产品的小团队里工作,多半需要用 I2C 去配置某个元件。或者接到 FPGA 上的某个元件用 SPI 通信。又或者直接从你自己的逻辑里去访问保存 FPGA 比特流的那块闪存。
这两个协议有很多使用场景,但最重要的是:当它们在数据手册里出现时,你要认得出来,包括它们的各种变体。元件常常用了这类协议,却不明确写出协议的名字。如果你懂 I2C 背后的原理,那么当数据手册描述一个类似的接口时,你绝不会看漏。SPI 也一样。
而且,如果你在求职面试里自称是有经验的 FPGA 设计师,却对这两个协议一无所知,那可不太加分。
另外,给一个用来积累经验的项目建议:很容易就能买到便宜的、使用 I2C 或其某种变体的温度传感器或其他简单元件。买一个这样的元件,用杜邦线或者其他土办法接到你的 FPGA 开发板上。然后从零开始写出通过 I2C 与它交互所需的一切。如果你有示波器,就用它来监视信号。这可以成为你第一个自己动手的项目。
框图设计(Block Design)
大多数 FPGA 设计工具都提供了用图形界面连接逻辑设计模块的功能。原则上,用这个界面和用 Verilog 实例化(instantiation)模块差不多是一回事。图形化画出的连线就像 Verilog 里的端口连接。框图设计工具往往还提供一些额外能力,比如自动插入小段逻辑,确保在图形界面里做出的连接能达到用户想要的效果。
当所有或大部分模块都由 FPGA 厂商提供(以 IP 块的形式)时,框图设计的方法最能发挥优势。尤其是涉及处理器时,框图设计通常是把处理器与实现外设功能的 IP 块连接起来的自然方式:中断控制器、DMA 控制器、总线仲裁器等等。还有一些更“经典”的外设,比如以太网控制器。
除了明显与处理器相关的模块之外,还有大量 IP 块实现了 FPGA 中常见的各种功能。从时钟资源、紧邻 I/O 引脚的逻辑这类 FPGA 单元,经过简单的算术单元,一直到数字滤波器乃至更复杂的逻辑,应有尽有。看起来它的初衷是让只用框图设计就能做出完整的 FPGA 设计。
话虽如此,我从没听说有人只用这些现成的 IP 块做出过一个真正有用的 FPGA 项目。除非那个项目只有处理器和它的外设——可如果你只想要一个处理器,干嘛要用 FPGA 呢?那要贵得多,也复杂得多。
不过,整个框图设计可以、而且通常会被实例化在一个 Verilog 模块里,就像任何别的 Verilog 模块或 IP 一样。因此,框图设计通常是一个更大的、基于 Verilog 的项目的一部分。在这个语境下,一个只含处理器及其外设的框图设计是说得通的:它只是更大项目里的一个模块。
那么,就你应该学、或者也许不该学的技能而言,这意味着什么?
最简单的技能是创建框图设计、添加模块并把它们连起来。有大量教程告诉你什么时候点什么。既然这些教程这么容易照着做完,何不跑上一两个?做的时候,不必费心去搞懂过程中每一步、每一个选择。重点是找找框图设计是怎么回事的感觉。等真有必要了,再深入细节。
接下来的技能是把框图设计纳入项目,并在一个 Verilog 模块里实例化它。这也很简单,例子很多。和在你项目里使用任何 IP 没有区别。如果你会在一份 Verilog 项目里使用 FIFO IP,那你也会对框图设计做同样的事。
最关键的技能,是把你用 Verilog 写的东西变成一个能在框图设计里使用的模块。更进一步,是让它能用 Vivado 自带的图形界面(或者你用的任何开发套件)来配置。除非有直接目的,否则我不建议去学这个。大多数 FPGA 设计师一辈子都用不着做这类事。
所以结论是什么?和任何图形界面工具一样,先随便玩玩,然后再根据完成某项任务的需要逐步学习。尤其是,别指望用框图设计搞定一切,哪怕那些现成模块的集合会让你误以为就该这么做。
与 FPGA 内部的处理器打交道
很多项目,尤其是独立的电子产品,里面都跑着一些软件,因而涉及处理器。这个处理器可以是 FPGA 内部的一个模块,也可以是 FPGA 之外的一个独立物理元件。这两种情况下,挑战完全不同。
先说处理器在 FPGA 内部的情况。这可以是一个“硬处理器”,比如 AMD Zynq 系列里那种,把 ARM 处理器做进硅片中的。硬处理器和 FPGA 内部任何别的逻辑单元没什么两样,可以和算术单元、PLL、块存储器相提并论。只不过处理器块相对较大,而且有一大堆引脚。
如果一款 FPGA 没有硬处理器,它仍然可以在“软处理器”上运行软件。比如 AMD 的 Microblaze 和 Altera 的 Nios 处理器。区别在于,处理器是用 FPGA 的普通逻辑单元(也就是“逻辑结构”,logic fabric)实现的。这种方式更慢、更耗电,还要占用逻辑资源,但往往够用,而且是划算的选择。
原则上,硬处理器和软处理器都像任何 Verilog 模块一样,像别的 IP 那样与 FPGA 的逻辑相连。因此很自然会把它们看作 FPGA 的一部分,从而认为 FPGA 设计师要对它们负责。
在逻辑设计里把处理器搭起来,通常是相对轻松的任务,因为例子和模板很多。但事情很少到此为止。处理器需要有一些外设,而这些外设应当能在处理器的存储空间中以已知地址被软件访问。这些外设常常带有中断请求输出,需要正确地接到处理器上并配置正确。
软件团队通常期望由别人负责那段在处理器上电或收到复位信号时运行的软件。这段软件由若干例程组成,除了别的以外,它们要写处理器自己的硬件寄存器,使它按配置工作。这并没有听起来那么难,因为开发工具会为此生成 C 代码,供纳入更大的软件项目使用。但谁负责生成这些文件、并确保它们与 FPGA 设计的其余部分保持同步?在小团队里,那就是 FPGA 设计师。
如果这还不够,FPGA 设计师可能还要为处理器创建实现产品专用逻辑的外设。用 C 为这些逻辑写驱动,通常非常受欢迎。
那么——这是不是作为 FPGA 设计师应该开始学的东西?如果你想在软件和硬件的交叉地带工作,我会说也许是的。如果你本来就是 C 程序员,而且喜欢底层编程,这也许适合你。尤其是,如果你不介意隔三差五地翻那本厚厚的处理器用户手册。你想问的“我怎么才能要两个 X 外设、三个 Y 外设?”的答案就在里面。
那该学哪些主题?我会说,先对处理器如何运行软件、如何访问存储器、中断如何工作、上电时处理器如何启动有个基本理解。看看处理器的地址映射,弄清存储区域是怎样划分成不同段的(片上 RAM、外部 RAM、内部寄存器、外部总线访问段等等),并理解整套东西是怎么运作的。
我还建议理解 AMBA 协议(AXI3、AXI4、AXI4 Lite 等)的原理,尤其是 VALID / READY 握手。如果你以后要设计外设,多半得实现一个 AXI 从设备。即使你用的处理器原生不使用 AXI(比如 Altera 的处理器),原理也是一样的。
处理器上电和复位时运行的那段软件,大概也会由你负责。所以熟悉这段软件是怎么生成的、它和处理器的硬件寄存器是什么关系,可能会有帮助。而如果由你来为你可能被要求设计的外设写访问驱动例程,也会更方便。出于这些原因,不擅长用 C 编程,你走不了太远。即使你用 AI 替你写代码,你也必须准确理解这段代码在做什么。
但最重要的是,要明白:跑完一个冗长的示例项目——你配置好一个处理器,点、点、点,最后板子上出现了非常唬人的效果——你并学不到多少东西。所有有价值的东西别人都已经替你做完了,而你在点向最后一步的过程中跳过了重要的部分。如果这种示例项目里真有什么价值,那也在于你做完之后:你从示例里理解了些什么?你能在项目里改动什么?你自己能试些什么?
与 FPGA 外部的处理器打交道
在涉及 FPGA 的项目里,处理器常常是一个独立元件,或者属于一块单独的板子。整个产品的核心是一台完整的 PC 也不罕见,不管是普通台式机还是基于 x86 的工业主板。在这些场景里,通常把 FPGA 视为一个外设。虽然 FPGA 的用途因项目而异,但处理器(或 PC)通常被视为项目的中心,而 FPGA(以及它周围的电子电路)则是由软件控制和管理的部件。
由于处理器是独立的物理部件,通常有单独的团队负责它的一切,包括软件。FPGA 设计师与处理器相关的任务,主要是与它对接。如果处理器只用于控制 FPGA 的行为,那么通信可能只包含命令,也许就是读写寄存器。这种情况下往往使用更简单的协议,尤其是 I2C 和 SPI。这两个我上面已经讲过了。SPI 也可能被选用于相对较低数据率的数据交换。
值得一提的是,I2C 和 SPI 通常只和嵌入式处理器搭配使用。当项目中涉及 PC 主板时,这些协议在项目专用外设上就较少见了。虽然 SMBus 常被用来控制风扇、读取温度,但用这类协议与自己的外设通信并不常见。
对于嵌入式处理器(以及 DSP),也常见到与 FPGA 的接口走的是某个处理器(或某家厂商某一系列处理器)专有的接口。比如,处理器可能有大量物理引脚接到 FPGA,以便用地址 / 数据总线接口访问它。实现与这条总线对接的逻辑,需要准确理解处理器厂商定义的(并不总是设计得高明的)协议。此外还有必须满足的时序要求。不过,为这类任务提前做准备没什么意义,因为它和与任何其他具有复杂 I/O 协议的外部元件对接并无区别。
与 PC 和高端嵌入式处理器的对接,通常走 PCIe(PCI Express)接口。这是一条稳健、支持良好的通信通道,最简单的配置下就能达到 200 MB/s 的(有效负载)数据率,但上限远不止于此:PCIe 协议的新版本定期推出,每一版数据率都更高。实际的数据率上限往往取决于处理器本身能处理多少。
PCIe 的缺点是协议复杂,主要是为计算机外设芯片设计的。它默认假设:如果你在做 PCIe 相关的东西,那你已经专门投入了人手开发与电脑对接的逻辑,还有软件团队来开发驱动。如果用 Xillybus,这个任务会容易得多,因为这套方案把两边的复杂度都处理掉了。
那么为了应对有外部处理器的场景,该学哪些技能?首先,如果你擅长 C 编程,会有很大帮助,这样你就能写从处理器访问 FPGA 的驱动例程,或者至少提供示例代码。AI 可以替你写这段代码,但如果你不准确理解它做了什么,最后可能得到一个看起来像是 FPGA 引起的、实际却是 C 里的 bug。
除此之外,我不太建议提前学太多。所需的技术技能很大程度上取决于处理器和 FPGA 是怎么连接的,而这因项目而异。
其他接口标准
如果你翻几块市面上能买到的 FPGA 开发板,会发现某些特定元件和连接器出现的频率明显高于其他。这可以解读为 FPGA 项目中常用技术的某种指示。这有一定道理,我挑几个说说。
HDMI
FPGA 开发板上常常有 HDMI 连接器。目的通常是让 FPGA 产生视频输出,在电脑显示器上显示。连接器的线往往直接连到 FPGA,因为借助 I/O 块的 SERDES,FPGA 有能力产生所需的高速信号。有些板子上,FPGA 与 HDMI 连接器之间会有一个独立元件(“视频编码器”)。
这个连接器如此普遍,确实反映了现实:很多 FPGA 项目涉及某种视频处理和输出。把 FPGA 开发板接到电脑显示器上,用这套组合做实验,将来会派上用场。尤其是要学 VGA 的基础知识,弄清屏幕是如何水平、垂直扫描的,以及存在哪些标准显示模式。如果 HDMI 连接器直连 FPGA,你可以试着实现产生这些信号的逻辑,但我不确定这值得花力气。它不是个简单易学的协议,而且一旦不工作,这类项目还很难调试:线上的数据率非常高,而电脑显示器拒绝响应 FPGA 的输出时,它也不会告诉你到底哪里不对。有现成的 IP 块可以用。我建议直接用它们,把精力放在产生视频数据上。
还有个小提示:你大概是想往显示器送 RGB 像素。那么请遵循 DVI 协议(它和 VGA 有关),而不是 HDMI。DVI 和 HDMI 的信号是可以互换的。但 HDMI 是个更严格的协议,是为标准高清电视设计的,显示模式集合很窄。常用显示模式下的像素用 YCbCr 格式表示,这是个没必要的麻烦。之所以用 HDMI 连接器,是因为 DVI 连接器及其线缆又大又笨。但送往电脑显示器的信号几乎总是遵循 DVI 标准,而不是 HDMI。
如果你试做一个视频输出项目,很快就会发现在 FPGA 自带的块 RAM 里往往装不下一帧图像。这就引出了下一个话题。
DDR 存储器
FPGA 自带的 RAM 是相对稀缺的资源。当项目需要处理几兆、几吉字节的数据时,就需要外部存储器。这在涉及视频的项目中很常见,其他应用里也一样,比如协处理 / 硬件加速、网络交换等等。
到目前为止,最常用的外部 RAM 是 DDR SDRAM,和电脑里用的是同一类。所以它们常出现在 FPGA 开发板上,有时是 SODIMM 条,更多的是直接焊在板上。它们价格低、带宽出色,但设计时是以计算机为出发点的。因此,当访问请求是连续地址范围上的长突发时,它们的带宽效率很高。关于它们,一个不那么广为人知的事实是:当访问模式不那么规整时,它们的性能会变得极差。虽然它们叫“随机访问存储器”(RAM),但在其他访问模式下,其带宽性能会急剧下降。比如,如果每次只取一个数据,而且每次的地址都和上一次毫无关系,这些存储器的表现会非常糟糕。
与 DDR 存储器之间的接口协议非常复杂,不过 FPGA 设计师很少需要了解太多:每一家上点档次的 FPGA 厂商都会免费提供一款可靠且相当高效的 DDR 存储器控制器 IP 核(IP core),供自己的 FPGA 使用。因此 FPGA 设计师只需要用 AXI4 或类似协议与这个 IP 对接。
DDR 存储器值得学吗?我会说有个相当充分的理由:它们在各个领域的 FPGA 项目中都用得很多。一个涉及 DDR 存储器、也许还能产生视频输出的项目就是个不错的练习。也建议通读一下某个 DDR 存储器的数据手册,理解存储阵列的结构,以及为什么必须先选行才能访问其中数据。还值得看看不同操作之间(CAS、RAS、刷新等)的延迟要求,以便领会它们如何拉低带宽效率。关于这类存储器,最重要的一点是知道什么时候不该用它们。
SFP+ 笼子
很多开发板,尤其是厂商的官方板,都带有一个 SFP+ 笼子。这个部件在视觉上很显眼,因为它是板边一块相对较大的金属件。只有当 FPGA 带有多千兆位收发器(Multi-Gigabit Transceiver,MGT,在 AMD FPGA 上叫 GTX、GTH、GTY 等)时,才会有这个连接器。笼子内部的连接器直接连到 FPGA 的一个或多个 MGT。
MGT 是一个能进行千兆级(通常 1 Gbit/s 及以上)双向通信的功能单元。它是若干著名协议背后的主力,尤其是 PCIe、SuperSpeed USB、SATA、千兆 / 万兆以太网和 DisplayPort。本网站上有一整组关于 MGT 的文章,从一篇总体介绍 MGT 的文章开始。
SFP+ 笼子的主要用途是往里插一个光纤模块。这样就可以用光纤跳线连接两块 FPGA 开发板,或者把 FPGA 开发板接到另一个具有类似接口的设备上,比如光纤网络路由器。光纤模块通常不含在 FPGA 开发板套件里,但也不算很贵。也有可以直接连接两个 SFP+ 连接器的线缆,不需要光纤。
SFP+ 笼子在 FPGA 开发板上这么普遍,是不是意味着你该尽快成为 MGT 专家?我不这么认为。它们在板子上频繁出现,原因之一是板上这个部件便宜,不需要额外元件,也不需要很多连接。而且和替代方案(通常是为每个 MGT 接四根射频电缆)相比,它是连接两块 FPGA 开发板的一种优雅方式。
再者,MGT 并不好伺候:它们在某些方面和数字无线电信道很像。链路上会有误码,发送端和接收端的时钟频率常常不完全一致,接收端需要在数据通道里找到数据帧的开头,如此等等。
因此,MGT 通常与处理通信协议的 IP 块配合使用。尤其是,几乎所有带 MGT 的 FPGA 都带有一个实现 PCIe 协议的硬 IP 块。还有针对其他几种与计算机搭配使用的著名协议的 IP 核。至于两块 FPGA 之间的连接,Xillyp2p 提供了一个简单接口。
所以,尽管 MGT 无处不在,这个主题也未必是该最先学的东西。
以太网
很多 FPGA 开发板上有以太网连接器。它背后的原因取决于 FPGA 的种类。
最好解释的情况是 FPGA 内部带有处理器,比如 AMD 的 Zynq 器件。在这类板子上,以太网连接器几乎总是接到处理器专为此设的引脚上。这就像任何带嵌入式处理器的板子上的以太网连接器一样。
那带的是没有处理器的 FPGA 的板子呢?首先,即使是这样的 FPGA 也能装一个“软处理器”(比如 MicroBlaze 或 Nios)。这样的处理器同样可以很好地使用以太网连接器。这不一定是常见的使用场景,但很长一段时间里,FPGA 厂商都在努力推销把 FPGA 用于数据中心的构想。他们真的很想在人们的头脑里把 FPGA 和计算机联系起来。以太网连接器就是其中的一环。
如果 FPGA 上根本没有处理器,以太网连接器可以用来与计算机通信。TCP/IP 也许是第一个浮上脑海的,但这个协议是为用软件实现而量身定制的。在逻辑里实现这个协议既复杂,功能又受限。协议栈还需要响应 ARP 请求,最好也能响应 ICMP 包。
因此,以太网用来把(不带处理器的)FPGA 开发板连到计算机的唯一实用方式,就是使用广播包:FPGA 开发板和计算机点对点相连。线缆上传输的以太网帧全都有广播 MAC 地址。这通常通过使用广播 UDP/IP 包来实现。所以,这和我们平常把计算机接入以太网的方式差得远了。
除了不优雅之外,这个方案还有个明显缺点:以太网协议不保证包的送达。如果以太网包里有一位出错,它就会被悄悄丢弃。计算机的网卡也可能无缘无故随机丢包。这在正常使用中不会被察觉。
因此,如果数据丢失不被允许,FPGA 就必须把它发送的所有数据保存在缓冲区里,以便重传。需要采用带有差错检测机制的协议来请求这种重传。在以太网上把这件事做对,会变得非常复杂。
另一种做法是接受数据丢失的可能。或者,正如学生和爱好者项目中经常发生的那样,干脆无视这种可能性,因为系统测试时它没发生。当项目不要求专业性时,这也够用了。
结论:如果你的板子上跑着处理器,尤其是跑 Linux,那当然要用以太网连接器。但我不建议在这上面钻得更深。
这就是本系列第四页的结尾,也到此结束了关于专业技能(professional skills)的讨论。下一页会转向一个完全不同的方向:这个职业更青睐什么样的性格?