FPGA 入门
知识储备
FPGA 概览
FPGA 是 CLD(Configurable Logic Device,可编程逻辑设备)的一种,一般用于高质量低延迟的视频流处理,硬件加速等,其工作方式与 GPU 类似,将部分在 CPU 上由软件执行的工作 offload 到硬件电路上进行。
FPGA 的结构由可编程输入/输出单元、基本可编程逻辑单元、嵌入式块 RAM、布线资源和 IP 内核单元几部分组成。CLB(Configurable Logic Block,可配置逻辑块) 是 FPGA 的编程部分,也称逻辑单元,配置时可以只对芯片中的部分 CLB 编程,因此一块芯片可以同时执行不同的功能,或以流水线的形式在不同时刻对数据做不同的处理,故 FPGA 拥有很高的并行度。同样由于这一特性,FPGA 的设计目标之一,就是用最少的 CLB 实现需要的逻辑函数。
CLB 内部由查找表 LUT(Lookup Table)和触发器 FF(Flip-Flops)构成。LUT 是存储真值表的介质,也可用作分布式 RAM 作为快速高效的 on-chip memory。LUT 通过 SRAM 实现编程,可以反复编程且不用刷新电子。今天的 FPGA,内部除了集成了 LUT 和 FF 外,还集成了 RAM(存储资源)、PLL(高速的时钟信号)、DSP(乘法操作、滤波器、数字信号处理)、SERDES(实现高速接口)、CPU(硬核处理器)、NPU(硬核 AI 推理)等硬核 IP 来满足特定应用场景的需求,这样的架构是解决实际问题和成本效率综合考量后的高度优化的结果。
开发流程
FPGA 开发流程大致可以分为以下步骤:
- 设计(Design):用 HDL 对硬件电路进行建模描述,需要定义电路功能实现、设计约束和测试用例。对于许多常用的功能,有现成的 IP 核作为轮子可以直接使用。测试用例是针对功能仿真的,由.sim 文件管理;设计约束主要由.xdc 文件管理。
 - 功能仿真(Simulation):又称前仿真,指在计算机的仿真器上运行 Verilog 代码,在不考虑硬件电路实现和绝大部分约束的情况下,仅验证逻辑功能是否正确。功能仿真是可选步骤。
 - 分析和综合(Systhesis):检查 HDL 代码是否符合综合规则,并由 EDA 在厂家提供的标准单元库和设计阶段中的约束下,编译出由 HDL 描述的逻辑网表并进行逻辑优化。逻辑网表是指一个标准的逻辑门或查找表的集合,是一个逻辑层面的结构,可以包括时序触发器、寄存器等时序元素,是逻辑门级别的描述。另外,注意不是所有的 HDL 语句都可以综合出相应的逻辑电路。硬核 IP 已经在芯片流片(制造)时,用晶体管电路实现并固化在硅片中了,不需要综合。
 - 布局布线(Implementation):分配引脚并确定内部电路的连接关系,进行 layout 和 IO planning,根据设计阶段的约束文件和综合出的逻辑网表,利用厂家提供的标准元件库对门级电路进行布局,在考虑各种约束和优化的情况下,将设计中的门级网表映射到 FPGA 的物理布局上并进行物理优化。完成这一步后就将 HDL 描述的模型电路转化为标准元件库组成的数字电路,且此时的电路已经包含了时延信息。
 - 时序分析(Analysis):指时序仿真,又称后仿真,用于检查信号延迟是否满足要求。时序分析有静态时序分析(STA)和动态时序分析(DTA),静态时序分析是通过计算每条路径的延迟来检查是否满足设计的时序约束;动态时序分析是模拟设计的运行,动态检查信号的传播延迟,用于验证时序边界。时序分析是可选步骤。
 - 生成比特流和板级调试(Bitstream):生成烧录到板子中的二进制文件.bit,并进行实际硬件的调试。生成比特流之前需要完成设计、综合和布局布线。比特流文件是一种专门为 FPGA 硬件配置而设计的格式,文件内容是经过优化的、以二进制形式存储的配置信息,包含了逻辑元件的映射、时序约束、硬件资源配置等所有信息,能够直接在 FPGA 上加载和执行。
 
设计约束包括:
- 引脚约束:又称 IO 约束、引脚绑定,将逻辑信号与物理引脚进行对应
 - 时序约束:保证各信号按预期的时序顺序传输,在规定时间内到达正确的位置,主要类型包括时钟约束、输入输出延迟约束、时序路径约束等
 - 资源约束:又称面积约束,限制 FPGA 资源的使用量来适配目标 FPGA 的可用资源
 - 布局约束:用于控制设计中各逻辑单元的物理布局,确保逻辑单元被放置到 FPGA 的指定资源区域内,从而提高时序性能、减少信号延迟并优化资源使用
 
FPGA / ASIC 的设计常分层为行为级、RTL 级和门级。行为级只写算法逻辑,不考虑硬件细节;RTL 级描述寄存器之间的逻辑运算和数据流,合成工具能直接将 RTL 转换成门级电路;门级为门电路和触发器的连接,是 RTL 综合之后的结果。RTL(Register Transfer Level,寄存器传输级)是硬件描述语言 HDL 对电路行为的一种抽象层级,主要描述寄存器之间的数据传输和逻辑运算,不关注底层由多少与非门、触发器组成,而是更高一层的“时钟边沿+数据流动”。
网表是电路连接清单,描述了由哪些逻辑单元(门电路、触发器、LUT、RAM、DSP 等),以及这些单元之间的连线关系(net)。FPGA 芯片就像一张巨大的电路板,除了基本的逻辑单元(LUT、触发器),大部分面积都被布线资源占据,比如可编程导线和开关,用于连接这些基本逻辑单元。
布线工具是 EDA 的自动布线算法,读取网表(逻辑单元和连接关系)并决定每个逻辑单元放在 FPGA 上的哪个位置(Place),决定用哪些具体的导线 + 哪些开关,把它们连起来(Route)。实现的效果是把抽象的“逻辑连接” → 映射到具体的 物理导线和开关配置。输出的结果是一个 bitstream 配置文件,用来配置 FPGA 内部的 SRAM 控制每个开关的开关状态。
ZYNQ 架构简介
FPGA 和 SoC 是 Xilinx 的两大主要产品系列。
FPGA 即纯 FPGA 芯片,按照工艺节点分为 UltraScale+(16nm)、UltraScale(20nm)、7 Series(28nm)三大类,类似于 CPU 中第几代的概念。在每个类别中,又分为 Spartan、Artex、Kintex、Virtex 四个子系列,面向不同的应用场景和市场定位,性能依次提升。
除了最新的 Versal ACAP(Adaptive Compute Acceleration Platform,自适应计算加速平台)之外,Xilinx 将 SoC 系列命名为 ZYNQ 计算架构,是 FPGA + Arm 的多处理器系统,集成了 FPGA 的可编程逻辑(PL)与 ARM 处理器核心(PS),两者之间通过 AXI(Advanced eXtensible Interface)总线实现低延迟数据传输。PS 具有固定的架构,包含了处理器和系统的存储器,适合控制或具有串行执行特性的部分以及浮点计算等;而 PL 是完全灵活的,适合并行流处理。
ZYNQ 的 PS 内集成了很多外设控制器模块,如 UART、SPI、I2C、CAN、USB、Ethernet 等,本质上是固化在 ZYNQ 芯片中的硬核外设 IP,是 PS 区域内的逻辑电路。它们存在于 SoC 的 PS 端,与 ARM CPU 核、DDR 控制器、时钟系统等都在同一个芯片中,通过 AXI 总线与 CPU 内核连接,CPU 可以读写寄存器来控制外设。
ZYNQ 架构分为 ZYNQ-7000 SoC、ZYNQ UltraScale+ MPSoC、ZYNQ UltraScale+ RFSoC 三类。ZYNQ-7000 SoC 的 PS 端为 Cortex A9 的 Arm 核心,为 ARMv7-A 架构,32 位;PL 端为 7 Series 的 FPGA。ZYNQ UltraScale+ MPSoC(MultiProcessor)的 PS 端为 Cortex A53 的 Arm 核心,为 ARM-v8-A 架构,64 位,另外还配备 Cortex-R5F 作为协处理器;PL 端为 UltraScale+的 FPGA。ZYNQ UltraScale+ MPSoC 再往下又可以细分为 CG、EG、EV 三类,面向的硬冲场景不同。ZYNQ UltraScale+ RFSoC(Radio Frequency)与 ZYNQ UltraScale+ MPSoC 的区别仅在于配备了高速 ADC 和 DAC 通道,用于射频领域。
IP 和 Module
Module 一般指用户写的 RTL 代码,是纯粹的 Verilog / VHDL / SystemVerilog 模块,完全掌握源码,可综合可仿真;IP(Intellectual Property Core)一般指 Xilinx 或第三方提供的与构建电路,经过了验证和优化,打包成黑盒,在工程中表现为 xcl 配置文件,vivado 根据 xcl 文件生成 HDL 包装器.v 文件,有的 IP 只有 netlist(加密,看不到源码,不能直接修改逻辑),IP 可以配置,底层实现可能包含厂商专用的宏元件,用户不能直接手写,有的 IP 甚至包含仿真模型和约束文件。从使用上说,IP 是一个特殊的 Module。可以自定义 ip 或添加第三方 ip。
在 FPGA/SoC 设计语境中,IP 核(Intellectual Property Core)指的是一种可复用的逻辑模块实现,可以是 HDL 描述的、可综合到 FPGA 逻辑中的软核 IP;也可以是直接固化在硅片中、不需要综合的硬核 IP;还可以是固件宏块,类似于硬核 IP 但是核 FPGA 可编程逻辑共享工艺。
例如:PS UART 在硬件上是 ZYNQ 芯片 PS 区域的一块 UART 控制电路(硬核 IP);在软件上,它暴露一组寄存器地址,CPU 通过 AXI 总线访问这些寄存器就能控制 UART;在引脚上,它把 TX 和 RX 信号通过 MIO 和 EMIO 引脚输出,连接到板上外设,比如 USB-UART 转换芯片。
AXI 协议
AXI 全称 Advanced eXtensible Interface,是 ARM 提出的 AMBA 协议标准的一部分,Xilinx 从 6 系列的 FPGA 开始引入这一协议。
AXI 属于片上总线协议,描述主设备和从设备之间的数据传输方式,通过 VALID 和 READY 信号的握手机制建立主从设备之间的连接(VALID 来自主设备,READY 来自从设备)。任意通道上完成一次数据交互都称为一个传输 transfer,当 VALID 和 READY 信号均位高电平时到来时钟上升沿,就会发生传输。一个通道内 VALID 和 READY 信号的建立顺序无关紧要。VALID/READY 机制使得数据的发送和接受方都有能力控制传输速率。
ZYNQ 中使用的主要是 AXI4 协议族,包括 AXI4、AXI4-Lite、AXI4-Stream 三种协议。(注:burst 指一个地址中可发生多次数据传输的传输事务,burst 操作只需要提供首地址)
AXI4:存储器映射总线,采用内存映射控制,支持 burst 传输,高带宽,适合访问块式内存,常用于 DDR 等大量数据读写。
- AXI4-Lite:存储器映射总线,采用内存映射控制,仅支持单数据传输,常用于访问和配置状态寄存器。
 - AXI4-Stream:连续流式接口,没有地址的概念,支持 burst 传输,适合访问流式内存,常用于视频流、FFT 数据等流式数据通路。
 
块式内存和流式内存是两种数据传输模型,块式数据的数据按照地址排列,一次访问一大块数据,典型的访问方式是内存地址+长度,如 DDR / PS 内存存储、CPU 内存搬运等。流式数据的数据不需要地址,一次访问一个元素,一个接一个,典型的方式是按时间按拍数,如 FPGA 内部视频像素流、FFT 数据流等流水线模块处理。
AXI4 和 AXI4-Lite 中,将一次传输事务分为五个独立通道,分别为读地址 AR、读数据 R、写地址 AW、写数据 W 和写响应 B。每个通道各有一组独立的信号线(各信号线的具体含义这里不一一列出),有一个独立的 AXI 握手协议,因此 AXI 协议支持同时进行读写操作。AXI4-Stream 由于没有地址的概念,因此也仅定义了一条通道,完成握手和数据传输。
ZYNQ 中在 PS 和 PL 之间用硬件实现了九个 AXI 物理接口,包含 AXI-GP0~AXI-GP3、AXI-HP0~AXI-HP3、AXI-ACP 共九个。GP 为 32 位接口,理论带宽 600MB/s,两个 PL 主 PS 从,两个 PS 主 PL 从;HP 接口和 ACP 接口可配置为 32 或 64 位接口,理论带宽 1200MB/s,均为 PL 主 PS 从。一个接口的方向性是固定的,要么能发起事务,要么只能响应事务。每个物理接口支持的协议也是确定的,HP 和 ACP 接口支持 AXI4 协议,GP 接口支持 AXI4 和 AXI4-Lite 协议,而 AXI4-Stream 协议是纯 PL 内部的连接方式,需要用 AXI-DMA 或其它类似模块在 PL 内部实现 AXI4 到 AXI4-Stream 的转换。ACP 与 HP 接口的区别在于 ACP 可以保证与 CPU Cache 的一致性。
ZYNQ 中,PS 端的 ARM 直接有硬件支持的 AXI 接口,而 PL 端需要使用逻辑实现相应的 AXI 协议,Xilinx 提供的现成 IP 如 AXI-DMA、AXI-GPIO、AXI-Dataover、AXI-Stream 都实现了这一接口。
虽然 AXI 协议的五个通道是独立并行的,但并非完全没有逻辑时序约束。AXI 协议要求读通道中读数据位于读地址之后,具体来说是 AR 通道地址有效之后才会返回 R 通道 VALID;类似地,要求写响应位于写通道中地最后一次写入传输之后,具体来说是 B 通道 VALID 只能在最后一个 W 通道地数据拍结束之后才出现。不过,这样的约束是通过 READY 和 VALID 信号实现的,设计时无需考虑地址和数据的先后顺序,较为灵活,只要保证符合 READY 和 VALID 信号的握手机制即可。
AXI 协议的一些特性如下:
- AXI 允许读数据返回之前,又发起一次新的读请求,类似于流水线的概念。延迟较大的情况下,可以做到不影响带宽,将延迟掩盖起来。
 - 支持非对齐传输:传输的地址和数据宽度一致时称为对齐传输,地址是字节地址的情况下,假设总线数据宽度为 32bit(4 字节)每次访问的起始地址是 4 的倍数,那么就是对齐的。而 AXI 协议通过 WSTRB 的掩码机制,每一位对应一个字节,可以控制只写入部分字节,因此写传输可以起始于任意地址,不必是 4 字节边界。
 - ARSIZE 和 ARBURST 分别规定了单次传输的数据宽度(如 4 字节、8 字节等)和突发类型(FIXED 地址固定、INCR 地址按固定值递增、WRAP 地址递增但循环)。写通道同样有这样的信号。
 - 支持不同 ID 之间的乱序传输,同一个 ID 内则是保序的。AXI4 中已经取消了 WID 信号的使用,不再支持写乱序。
 - AXI 协议是一个点对点的主从接口,当多个外设需要相互交互数据时,需要加入 AXI Interconnect 模块(Xilinx 提供了相应的 IP),本质上是一个实现交换机制的互联矩阵
 
DMA 是使用 AXI 总线来读写内存或寄存器的一种方式,也是由 PL 端的逻辑或相应的 IP 实现的。DMA 通过 AXI HP 总线访问内存或 PL,CPU 只负责告诉 DMA 要搬多少数据、源和目的地址,数据搬运完全由 DMA + AXI 高速通道完成。当然,完全可以不用 DMA 机制而直接读写 AXI 总线的数据。
时钟资源
锁相环(PLL)是 FPGA 中的时钟资源资源,通过 PLL 可以由晶振的频率引出许多不同的频率供各种外设使用。FPGA 使用专用的全局和局部 IO 以及时钟资源来管理各种时钟需求。FPGA 中的时钟不能直接裸信号使用,也不能走普通的布线逻辑,而需要经过专用的 buffer 电路驱动到全局或区域的时钟网络,这称为时钟 buffer。buffer 的作用主要是提高驱动能力和保证延迟均衡。
FPGA 内部有大量可编程的互连资源,逻辑单元之间的信号通过这些线段和开关连接,称为布线逻辑 (routing fabric)。普通布线逻辑指这些通用的互连通道,用来传数据/控制信号。它们延迟大、路径长短不一、受布线工具优化影响。时钟如果走普通布线逻辑 → 延迟和 skew 无法保证,会导致严重的同步问题。时钟倾斜 skew 是指同一个时钟信号到达不同触发器的时间差。如果 skew 太大,可能导致 setup/hold 时间被破坏 → 数据寄存错误。因此需要使用专用的时钟 buffer + 全局时钟网络(时钟布线网络),把 skew 控制在几十皮秒的范围内。
全局时钟资源指 FPGA 内部专门布设的低延迟、低偏斜的长距离布线网络,保证一个时钟信号可以同时驱动全芯片的逻辑,对应的 buffer 为 BUFG(Global Clock Buffer),用于驱动全局时钟,如系统主时钟、DDR 时钟等。
区域时钟资源限定在芯片的一部分,对应的 buffer 为 BUFH(Horizontal buffer)和 BUFR(Regional buffer),用于驱动局部时钟,减少功耗和浪费。FPGA 不允许随便用普通布线逻辑来传时钟,因为普通布线延迟大且不均衡,会导致时钟偏移,因此 Xilinx 给时钟单独做了专用的全局和局部 IO 与时钟资源。
CMT(Clock Management Tiles)分布在 FPGA 中的不同位置,提供了时钟合成、倾斜矫正和过滤抖动等功能,每个 CMT 包括一个 MMCM(Mixed Mode Clock Manager)和一个 PLL(Phase Lock Loop)。PLL 通过锁相机制使输出时钟和输入时钟相位一致,可实现分频和倍频,缺点是相位控制粒度有限。MMCM 比 PLL 功能更强,可实现分数分频/倍频和更精准的相移。CMT 的输入可以是 BUFR、IBUFG、BUFG、GT、BUFH 和本地布线,MMCM 或 PLL 输出的是原始时钟信号,要驱动 FPGA 内部逻辑,还需要接入专用时钟网络(BUFG/BUFH)。
时钟 buffer/网络说明:
- IBUFG (Input BUFG):把外部引脚的时钟(从 IO pin 来的时钟)引入到 FPGA 内部,送入全局/区域时钟网络。
 - BUFG (Global BUFG):驱动全局时钟网络,确保整个芯片时钟到达几乎同时。
 - BUFH (Horizontal Buffer):区域时钟 buffer,只在本 region(一般是一个 clock region,高度 50 CLB 左右)内部传播。
 - BUFR (Regional BUFR):区域时钟 buffer,常用于 I/O 时钟(比如串行接口的本地时钟)。
 - GT (Gigabit Transceiver):高速收发器自带的时钟输出,可作为 CMT 的时钟源。
 - 本地布线 (Local routing):如果你把时钟当普通信号布线,会走 LUT/普通 interconnect,延迟和偏斜不可控。
 
存储结构
FPGA 中常用的逻辑存储结构有 RAM、ROM 和 FIFO,三种结构 Xilinx 都提供了成熟的 IP,具体使用查阅文档即可。RAM 和 ROM 的 IP 有 Distributed Memory Generator 和 Block Memory Generator 两个,区别在于生成的 Core 所占用的 FPGA 资源不一样,从 Distributed Memory Generator 生成的 RAM/ROM Core 占用的资源是 LUT;而从 Block Memory Generator 生成的 RAM/ROM Core 占用的资源是 Block Memory。
Block Memory 是 FPGA 中片上块状存储资源的统称,是逻辑上的概念。Block Memory 主要由 BRAM 实现,少数型号的 FPGA 也有 URAM 和 HBM。BRAM 即 Block RAM,是 FPGA 中固化的硬件存储单元,固定容量为 18K bits(18432 bits),可以单独使用也可以两个相邻的 18k BRAM 组合成双口 36k BRAM。设计中调用的资源以 BRAM 为最小单位,向上取整。例如存储位宽为 16 位,数据深度为 512,总共占用 16*512=8192 bits = 1KBytes。
RAM 和 ROM 中任意一个存储单元都可以在相同时间内被访问,硬件上由存储单元阵列 + 地址译码器 + 读写电路构成;ROM 由 RAM 配合要预先写入的 coe 文件实现。FIFO 是先进先出队列存储器,内部常用环形缓冲区的结构,由 RAM+读写指针实现。根据读写时钟,可以分为同步 FIFO 和异步 FIFO,同步 FIFO 的读写时钟相同,异步 FIFO 的读写时钟不同。
FIFO 不像 RAM 那样可以随意访问地址,而是写入只能写到队尾,读出只能从队首读取,硬件自动维护顺序,读写顺序固定。FIFO 的容量一般比较小,广泛用于数据的缓存、平衡异步时钟域之间的速度差等。读写对象为读写指针指向的存储单元,读写时操作指针位置,FIFO 内部逻辑负责防止读空或写满。
数据宽度指一个数据占用的 bit 位数,数据深度指可以存放的数据个数。数据深度与地址线宽度有关,如数据深度为 512,则对应的地址线为 9 位,\(2^9=512\)。
ILA 逻辑分析仪
ILA 工作原理:ILA IP 被综合进 PL 逻辑电路中,本质上是片上逻辑分析仪,每个时钟沿采样一次所监控的信号;点击 run trigger for this ILA core 时,背后执行操作是从现在开始采样,直到触发条件满足,就把触发点前后的一段波形存下来(不设置触发条件的情况下,将点击 run 的时候视为触发)。ILA 抓取到的数据是运行中 PL 内部信号的采样结果,相当于捕获了那一瞬间的硬件状态,是对信号状态的一次快照,而不是仿真或重启程序。
MIO 和 EMIO
FPGA 芯片的管脚被划分为多个 IO Bank,每个 Bank 有一组外部供电的电源引脚,PS 的 IO 和 PS 的 MIO 都分布在若干个 Bank 中,每个 Bank 有独立的电平标准。
MIO (Multiplexed IO)是 PS 端提供的物理 IO 管脚,可以连接诸如 UART、SPI、I2C、GPIO 等,通过 vivado 软件设置可以将信号通过 MIO 导出。也可以将信号通过 EMIO 连接到 PL 端的 FPGA fabric,再从 PL 的 I/O Bank 的管脚引出。EMIO 是 PS 端的信号(注意不是来自 PS 封装的 MIO 引脚)通过 AXI 接口暴露到 PL 侧的一组逻辑 GPIO 接口,来源是 PS 内部信号,是直接逻辑线,不是直接的物理引脚。这样的逻辑线接入 IOBUF 之后,才成为 PL 端可以实际操作的物理引脚。
可以使用 AXI GPIO 的 IP 核,通过 AXI 总线控制 PL 端信号。AXI GPIO 是 xilinx 提供的 PL 侧的外设 IP 核,作用是让 PS 通过 AXI 总线访问 PL 端的 GPIO,因此使得 PS 端可以访问到 PL 端的信号。AXI GPIO 来源是 PL 内部逻辑,PS 通过 AXI 寄存器访问 PL GPIO,读写过程需要 AXI 总线。
XDC 编写
普通 IO 口只需约束引脚号和电压,注意大小写,端口名称是数组的话用{ }括起来,端口名称必须和源代码中的名字一致,且不能和关键字一样。
管脚约束:set_property PACKAGE_PIN 引脚编号 [get_ports 端口名称] 
电平信号约束:set_property IOSTANDARD 电平标准 [get_ports 端口名称]
1
2
3
4
5
6
7
8
9
10
11
12
13
set_property PACKAGE_PIN J16 [get_ports {led[3]}]
set_property PACKAGE_PIN K16 [get_ports {led[2]}]
set_property PACKAGE_PIN M15 [get_ports {led[1]}]
set_property PACKAGE_PIN M14 [get_ports {led[0]}]
set_property PACKAGE_PIN N15 [get_ports rstn]
set_property PACKAGE_PIN U18 [get_ports clk]
set_property IOSTANDARD LVCMOS33 [get_ports {led[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {led[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {led[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {led[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports rstn]
set_property IOSTANDARD LVCMOS33 [get_ports clk]
PS 端开发
PS 端的工作大致可以分为两部分,一是与 PL 端的交互接口,这部分主要是基于 AXI 协议的开发;二是各种外设的接口,这部分根据例程调用相应的 API 即可,类似于 HAL 库的开发方式。
Generate Output Products 是为 Block Design 内的各个 IP Core 生成 HDL 底层实现文件,修改 BD 中的 IP 时需要重新执行。Create HDL Wrapper 是给 BD 生成一个顶层 HDL 文件,让综合器将 BD 当作一个普通 IP 来使用,生成的 Wrapper 就是综合/仿真时的入口文件,生成时可以选择让 Vivado 自动维护,修改 BD 时自动更新。通常先 Generate Output Products,再 Create HDL Wrapper。绑定引脚时以 Wrapper 中的声明为准。
配置完 ZYNQ 核中的 PS 部分之后导出端口、连线;之后 Generate Output Products,生成 block 输出文件,包括 IP、例化模板、RTL 源文件、XDC 约束、第三方综合源文件等,再用 Block Design(bd 文件)Create HDL Wrapper,最后导出硬件信息生成 sdk 文件夹,这就包含了 PS 端的配置信息。SDK 部分通过导出的 hdf 文件启动并建立 App 工程,完成 App 的软件开发后,通过 Run 或 Debug 在板子上运行。
system.hdf 中包含了导出的硬件平台描述,用 SDK 打开后可以看到一张表格,其中 cell 一列表示 block design 中相应的硬件 IP 实例名,用于在软件层面标识。CPU 访问外设时,是通过 AXI 总线以内存映射寄存器的方式进行的,表格中 base address 和 high address 是分配给该外设的一段统一编址后的物理地址空间,每个外设都有一段寄存器空间。
bsp 的 include 目录下包含了 xilinx 的各种头文件,其中包含各种宏定义和函数声明。xparameters.h 中定义了各个外设的基地址、器件 ID 和中断等。libsrc 目录下包含了外设函数定义和使用注释说明。lscript.ld 中定义了可用 memory 空间,栈和堆空间大小等,可根据需要修改。
Petalinux
为了在 Xilinx 的硬件平台上运行 Linux,需要使用 Petalinux 工具。Petalinux 不是 Linux 内核,而是一套配置开发环境的工具,降低 uboot、内核、根文件系统的配置的工作量,可以从 Vivado 导出的硬件信息自动完成相关软件的配置。Petalinux 本身基于 Yocto Project(嵌入式 linux 定制框架)构建,内置了针对赛灵思硬件的交叉编译工具链(如 arm-xilinx-linux-gnueabi),支持在 x86 主机上编译针对 ARM 架构的 Linux 内核、驱动和应用程序。Petalinux 编译后会生成嵌入式系统的核心镜像,包括 Linux 内核镜像(定制化的 Linux 内核)、设备树 Device Tree Blob(描述硬件拓扑)、根文件系统 rootfs(包含系统库、命令行工具、应用程序等,python 就在这里面)、启动加载器 bootloader(默认使用针对 Xilinx 硬件优化的 u-boot)
下面简单记录按照 Alinx 厂家的教用 Petalinux 制作板子镜像并固化到 SD 卡的过程,详细步骤见教程。由于 Petalinux 对系统版本和设置有严格要求,这里按照 Alinx 厂家的教程,在 PC 的虚拟机上安装 ubuntu16.04(或者双系统也行)并在 ubuntu 上面安装 Petalinux2017.4。用 Petalinux 定制 Linux 系统涉及 Vivado 工程和 petalinux 工程,在 Vivado 中编译生成 bit 文件,导出硬件信息并得到包含硬件信息的 hdf 文件,Petalinux 根据 hdf 文件配置 uboot ,内核、文件系统等。
1
2
3
4
5
6
7
8
9
10
11
12
# 创建Petalinux工程
petalinux-create --type project --template zynq --name ax_peta
# 基于Vivado导出的hdf文件,由配置界面配置硬件信息
petalinux-config --get-hw-description ../linux_base.sdk
# 由配置界面配置内核
petalinux-config -c kernel
# 由配置界面配置根文件系统
petalinux-config -c rootfs
# 编译
petalinux-build
# 生成BOOT文件
petalinux-package --boot --fsbl ./images/linux/zynq_fsbl.elf --fpga --u-boot --force
在 PC 的 Linux 上用 disk 工具,分区出 FAT 和 EXT,此时可以把文件放入 SD 卡的 EXT 分区中(Windows 系统是不显示 EXT 分区的,Linux 可以,所以放文件要在 PC 端的 Linux 下操作);然后将工程目录 images –> linux 目录中的 BOOT.BIN 和 image.ub 复制到 SD 卡的 FAT 分区即可。如果需要打包成 img 镜像,使用 imageUSB 工具即可。
Petalinux 的版本和 python 的版本是绑定的,但 Xilinx 的官方文档中并没有给出对应关系,目前已知 Petalinux2023.1 对应 python3.10.6。如果需要改 python 版本就需要尝试安装不同的 Petalinux 版本并完成整套的 Linux 系统定制,在板子上运行起来编译出的镜像之后,由 python3 –version 才能查到这个 Petalinux 版本对应的 python 版本是否符合要求。
另外再多说一句图形界面相关的问题(完全可以不用图形界面拥抱命令行,不过已经问过了相关的情况,这里就一起记录下来了),Petalinux 自带桌面系统 matchbox,但这个系统与 ZYNQ 7000 架构的适配有 bug;Alinx 厂家是自己移特制 Linux 内核和经过移植的 Debian 桌面文件系统,理论上应该也可以自己移植 Linux 其它发行版如 ubuntu 的桌面系统,但移植时涉及文件系统,python 版本和库也要在此时一并作好处理;不过移植 Linux 在没有接触过的情况下工作量太大坑太多,并且图形界面也不是刚需,这里就不配置了。
Debug 经验
- xdc 文件中约束的端口需要与顶层模块中的一致。
 - 在实例化端口连接时,inout 不能接 output
 - PS 端的 IO 分配是固定的,自然也不需要在 vivado 中分配管脚,但需要建立 vivado 工程中配置 PS 管脚,也需要将 ARM 添加到工程中才能使用。
 - IOBUF 的 IO 引脚不能被 FPGA 内部逻辑当作信号源。不能把 IOBUF 的 IO 端口(即 .IO)连接到一个非顶层的 inout 信号,哪怕那个信号最终又连接到顶层的 inout。只有顶层 inout 才能合法地驱动 IOBUF 的 IO 端。
 - 打开了 deign 还 implementation 失败就重新 synthesis 一下,即使提示 up-to-date。(重新 setup debug 之后会这样)
 - FatFs 库函数编译时报错未定义:read_ddr/Debug 目录下的 objects.mk 文件,在 LIBS 中加上-lxilffs。或者换一个有 xiff 库的 bsp 然后再换回来(这样可以刷新一下)
 - DONE pin is not high on target FPGA:这是从 SDK 启动时有时会出现的提示,需要在 SDK 中为所启动程序的 run configuration 中配置 program fpga,让板子在启动时先把 bitstream 加载进去再启动程序。(这是 vivado 2017 版本的解决方案)。DONE 引脚本身表示 PL 加载成功,FPGA 被正确配置完成后,DONE 引脚电平会被拉高。
 - verilog默认以unsigned处理数据,对于有符号数需要用signed声明,否则会影响对符号位的解释。另外,即使信号声明为 
signed,一旦参与运算的表达式中有 unsigned 或默认推断为无符号的部分,Verilog 会 自动把整个表达式转为 unsigned 计算。写$signed()是为了“锁死表达式为有符号运算”,防止 Verilog 自行提升为无符号算术。 - 乘法运算要以两倍扩宽处理,结果要做截断判断处理,否则可能发生溢出。
 - DUT(Device Under Test)和testbench中的信号类型对应关系:DUT中的input需要tb来驱动,因此用可以在过程块中赋值的reg;DUT中的output不由tb赋值,只连接wire来接受即可;常量也可以用wire,在声明的时候就设定好值。
 - 仿真中可以使用real的数据类型和task结构,但不能直接综合到FPGA中,因此不能用在设计中。
 - AXI 规范要求 AR/R 事务一旦开始,不允许中途中止。一旦在 R 通道阶段突然把状态打回 IDLE,不再给 RREADY(在 IDLE 状态默认 RREADY=0),但从端仍可能继续送数据。这会导致 RVALID 有数据了,但状态机认为自己“没请求”/“不该收了”,现象是数据在 S_RD_WAIT 就出现,这是因为 slave 正常返回了 RDATA,但主机状态机不在正常处理路径。
 - 状态机的state_next是时序逻辑赋值,否则会延后两个周期,甚至卡死在某一个状态。
 





.png)
