1. 项目概述构建一个完整的视频测试与传输系统最近在调试一块基于Zynq的FPGA开发板上的视频输出接口遇到了一个典型问题屏幕上要么没信号要么图像错乱排查起来非常麻烦。传统的做法是用一个现成的视频源比如电脑HDMI输出来测试但这又需要额外的设备而且无法精确控制视频时序和像素内容来定位问题。于是我决定在PL可编程逻辑侧自己动手实现一个Test Pattern Generator并通过VDMA将生成的测试图像流送到PS处理器系统侧最终在Petalinux系统下通过Frame Buffer显示出来。整个项目基于最新的Vivado和Petalinux 2025.2工具链这不仅是一次对AXI4-Stream和VDMA的深入实践更是构建一个自包含、可复用的视频系统验证平台的绝佳案例。这个项目非常适合正在学习Zynq/Zynq MPSoC视频处理的工程师或爱好者。无论你是想验证自己的视频输出IP如HDMI TX、MIPI D-PHY是否工作正常还是想深入理解VDMA如何作为PL和PS之间高速数据搬运的“桥梁”亦或是想在Petalinux下编写一个简单的帧缓冲显示应用这个项目都能提供一个清晰的路径。通过它你不仅能得到一个实用的测试工具更能打通从FPGA逻辑设计到Linux驱动和应用的全流程理解其中每一个环节的关键配置和潜在陷阱。2. 系统架构与核心组件选型2.1 整体数据流设计思路整个系统的核心目标是在PL侧生成可编程的测试图像并通过高效的数据通道在PS侧的Linux用户空间显示出来。数据流需要跨越时钟域PL的像素时钟 vs PS的AXI总线时钟和内存域PL的BRAM/寄存器 vs PS的DDR因此架构设计至关重要。我设计的核心数据流路径如下TPG (Test Pattern Generator) 位于PL侧作为一个纯逻辑的IP核。它根据配置的时序参数如分辨率、帧率和图案类型颜色条、渐变、移动方块等持续产生AXI4-Stream格式的视频像素流。VDMA (Video Direct Memory Access) 这是连接PL和PS的关键枢纽。它负责将TPG产生的流数据通过AXI4总线写入PS侧DDR内存中的指定缓冲区同时也支持从DDR中读取数据流回PL本项目中主要用于显示通路。VDMA内部包含读写通道、帧缓冲管理和调度器能高效处理视频帧数据。AXI Interconnect 提供PS的AXI Master端口如HP或ACP与VDMA、其他IP之间的互联通路。PS (Processing System) 运行Petalinux。其作用包括通过AXI-Lite总线配置TPG和VDMA的寄存器在DDR中开辟帧缓冲区加载并运行VDMA的Linux驱动提供Frame Buffer设备供用户空间访问。Linux Frame Buffer 用户空间应用 Petalinux系统启动后VDMA驱动会注册一个Frame Buffer设备如/dev/fb0。用户可以通过标准的Frame Buffer接口如mmap直接访问DDR中的图像数据或者编写简单的应用将图像显示出来。选择TPGVDMA的组合而不是用PS端的处理器通过AXI总线直接向PL发送像素数据主要基于性能和实时性考虑。视频数据量巨大以1920x1080 30fps RGB888为例带宽约~180MB/s由PL侧的专用逻辑生成并流式传输效率远高于PS软件模拟。VDMA则提供了专业、稳定的DMA传输通道减轻了CPU负担并简化了驱动开发。2.2 关键IP核与接口详解1. Test Pattern Generator (TPG) IP在Vivado 2025.2中Xilinx提供了官方的Video Test Pattern Generator IP核位于Video Image Processing子目录下。这个IP功能强大我主要使用了它的以下特性多种图案 支持颜色条Color Bar、渐变Ramp、移动方块Moving Box、纯色等。颜色条图案对于验证色彩深度和通道顺序至关重要。可动态配置 通过AXI4-Lite接口可以在运行时从PS修改图案类型、颜色、移动速度等参数无需重新综合。标准视频时序输出 生成标准的AXI4-Stream Video协议信号包括TDATA像素数据、TVALID、TREADY、TUSER帧起始、TLAST行结束。这保证了与下游IP如VDMA的无缝对接。分辨率与帧率可调 需要在IP配置界面输入像素时钟频率、水平/垂直总数、同步脉冲宽度等参数以匹配你的显示设备。注意 如果Vivado IP Catalog中没有找到可能需要安装Vivado Design Suite - Video and Imaging相关的扩展包。也可以自己用HDL编写一个简单的TPG但使用官方IP能节省大量时间并保证时序正确性。2. AXI Video Direct Memory Access (VDMA) IP这是整个项目的核心。VDMA IP有几个关键配置选项直接影响系统行为和驱动兼容性Number of Read/Write Channels 本项目主要使用写通道将TPG数据写入DDR。但为了架构完整性和未来扩展例如从摄像头采集我通常使能一个写通道和一个读通道。Enable Frame Buffers 必须使能。我设置为3个帧缓冲区Frame Buffer。双缓冲2个是最低要求用于乒乓操作避免撕裂3个缓冲区可以提供更宽松的调度余量防止因DDR访问延迟导致的帧丢失。Memory Map Data Width Stream Data Width 这两个宽度必须匹配你的视频数据位宽。对于RGB888格式24位通常设置为32位MM2S和S2MM的TDATA宽度均设为32。VDMA会在内部处理数据打包32位总线能有效利用带宽。Line Buffer Depth 行缓冲深度。对于高清视频建议设置为最大行长度如1920像素以上以确保流畅传输。通常设为2048。GenLock Mode 生成锁模式。在简单的TPG到内存的写入场景中S2MMStream to Memory-Map即写通道通常设置为Master模式因为它由TPG的时序驱动。MM2S读通道可以设置为Slave模式但本例中读通道未使用可先关闭相关功能或设为自由运行。3. AXI Interconnect 与 Clocking Wizard时钟网络 TPG工作在像素时钟例如148.5 MHz for 1080p60下而VDMA的AXI4内存映射接口和PS的HP端口通常运行在更高的频率如150 MHz或200 MHz。需要使用Clocking Wizard生成这两个时钟并确保它们同源例如都来自PS的PL时钟输出。VDMA内部的异步FIFO会处理这两个时钟域之间的数据传递。互联 将PS的S_AXI_HP0接口高性能从端口连接到AXI Interconnect再将VDMA的M_AXI_MM2S和M_AXI_S2MM接口内存映射主接口连接到该Interconnect。同时将PS的M_AXI_GP0通用主端口连接到另一个AXI Interconnect用于通过AXI-Lite配置TPG、VDMA等IP的寄存器。3. Vivado 硬件平台搭建实操3.1 IP集成与Block Design绘制启动Vivado 2025.2创建RTL项目选择对应的Zynq器件。在Block Design中操作步骤如下添加并配置Zynq Processing System 双击Zynq IP根据开发板型号进行预设。关键步骤是启用PS-PL接口在PS-PL Configuration HP Slave AXI Interface中使能S AXI HP0接口。这将提供PS到PL的高性能数据通道。在PS-PL Configuration General Enable Clock Resets中使能FCLK_CLK0例如100MHz作为PL侧部分逻辑和AXI互联的参考时钟。在Clock Configuration PL Fabric Clocks中使能FCLK_CLK1并设置一个频率例如150MHz作为VDMA AXI总线时钟。在MIO Configuration中根据开发板确保UART等基础外设已正确配置用于后续Linux调试输出。添加并配置Clocking Wizard添加Clocking Wizard IP。设置一个主输入时钟例如FCLK_CLK0的100MHz。输出两个时钟clk_out1150MHz提供给VDMA的axi_aclk和AXI Interconnectclk_out2148.5MHz作为像素时钟提供给TPG和可能的后续显示IP。务必勾选reset和locked端口。添加并配置Video Test Pattern Generator添加Video Test Pattern GeneratorIP。在Basic标签页设置Samples Per Clock为1每个时钟一个像素Maximum Number of Columns和Rows至少大于你的目标分辨率。在Video Format标签页选择Video Format为RGBComponent Width通常为8即每通道8位。Total Columns/Rows、Active Columns/Rows、Sync Widths等参数需要根据你的目标视频模式精确计算填写。例如对于1920x1080p60常用参数如下表所示参数值说明Clock Frequency (MHz)148.5像素时钟频率Total Columns (Pixels)2200水平总像素数Active Columns (Pixels)1920水平有效像素数H Front Porch88水平前肩H Sync Width44水平同步脉宽H Back Porch148水平后肩Total Rows (Lines)1125垂直总行数Active Rows (Lines)1080垂直有效行数V Front Porch4垂直前肩V Sync Width5垂直同步脉宽V Back Porch36垂直后肩* 在Interface标签页确保Interface Mode为Native使用AXI4-Stream信号并勾选Enable s_axi_ctrl interface以允许PS动态配置。添加并配置AXI VDMA添加AXI Video Direct Memory AccessIP。Basic标签页使能Read Channel和Write Channel。Number of Frame Buffers设为3。Memory Map Data Width和Stream Data Width均设为32。Advanced标签页Line Buffer Depth设为2048。在S2MM Options下设置GenLock Mode为Master。Stream to Memory Map的Transfer Type保持为Increment。其他选项可暂时保持默认。添加AXI Interconnect和Processor System Reset添加两个AXI InterconnectIP。一个用于高性能数据路径连接HP0和VDMA的内存映射接口另一个用于低速控制路径连接GP0和各个IP的AXI-Lite接口。添加Processor System ResetIP用于生成各个时钟域对应的复位信号。将Clocking Wizard的locked信号连接到其aux_reset_in可以确保时钟稳定后才释放复位。连接Block Design 这是最需要细心的一步。连接关系可概括为下表信号组源目的说明时钟FCLK_CLK0Clocking Wizardclk_in1主时钟输入Clocking Wizardclk_out1(150MHz)VDMAs_axi_lite_aclk,m_axi_mm2s_aclk,m_axi_s2mm_aclk, AXI Interconnect (HP)ACLK等AXI总线时钟域Clocking Wizardclk_out2(148.5MHz)TPGap_clk像素时钟域复位Processor System Reset 输出的各时钟域复位对应IP的复位端口异步复位同步释放数据流TPGm_axis_videoVDMAS_AXIS_S2MMAXI4-Stream视频流内存映射VDMAM_AXI_MM2S和M_AXI_S2MMAXI Interconnect (HP)S00_AXI,S01_AXI数据通道到DDRAXI Interconnect (HP)M00_AXIZynqS_AXI_HP0控制通路ZynqM_AXI_GP0AXI Interconnect (Lite)S00_AXIPS配置IP寄存器AXI Interconnect (Lite)M00_AXITPGs_axi_ctrlAXI Interconnect (Lite)M01_AXIVDMAS_AXI_LITE中断VDMAmm2s_introut和s2mm_introutZynqIRQ_F2P[1:0]可选用于驱动中通知帧完成连接完成后使用Validate Design功能检查确保无错误。3.2 约束、综合与实现创建顶层HDL包装器 在Block Design上右键选择Create HDL Wrapper让Vivado自动生成顶层文件。引脚约束 本项目主要验证内部数据流暂时无需外部视频输出引脚。但需要为系统时钟、复位、UART等基础引脚添加约束。如果你的开发板有外部时钟输入需要创建.xdc文件约束sys_clk等引脚。综合与实现 直接运行Generate Bitstream。Vivado会自动执行综合、布局布线。这个过程耗时较长取决于设计复杂度。导出硬件 生成Bitstream后在File Export Export Hardware中选择Include bitstream导出.xsa文件。这个文件包含了硬件描述、IP配置和比特流是后续Petalinux工程的基础。实操心得 在连接Block Design时经常容易混淆时钟域。一个简单的检查方法是所有连接到同一个AXI Interconnect的ACLK信号必须来自同一个时钟源这里是150MHz。而TPG的ap_clk和VDMA的s_axis_s2mm_aclk必须连接像素时钟148.5MHz。混接时钟是导致VDMA无法正常传输数据的最常见原因之一。4. Petalinux 系统配置与驱动开发4.1 创建Petalinux工程与导入硬件# 假设工作目录为 ~/project source /opt/pkg/petalinux/2025.2/settings.sh # 设置Petalinux环境变量 petalinux-create -t project -n video_project --template zynq cd video_project petalinux-config --get-hw-description/path/to/your/exported.xsa执行petalinux-config命令时会启动配置菜单。关键配置位于Subsystem AUTO Hardware Settings Memory Settings 确认DDR容量配置正确。Image Packaging Configuration Root filesystem type 选择INITRAMFS或EXT4 (SD card)取决于你的启动方式。DTG Settings 内核设备树在此生成。我们后续需要手动修改。4.2 配置内核与设备树配置Linux内核petalinux-config -c kernel需要确保以下驱动被编译通常已默认内置或编译为模块Device Drivers Character devices Xilinx DMA engines support 这是VDMA和AXI DMA驱动的依赖。Device Drivers Graphics support Frame buffer Devices Support for frame buffer devices 使能Frame Buffer。Device Drivers Graphics support Xilinx framebuffer driver 这是Xilinx提供的基于DMA的Frame Buffer驱动非常适合VDMA。在Device Drivers Staging drivers下找到Xilinx Video IP Core support确保Xilinx Video Test Pattern Generator被启用作为模块m。修改设备树 Petalinux自动生成的设备树可能不包含我们的TPG和VDMA节点需要手动添加。找到project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi文件。在其中添加TPG和VDMA的设备节点。节点的地址reg属性可以在Vivado中Address Editor标签页找到。/ { amba_pl: amba_pl { #address-cells 1; #size-cells 1; compatible simple-bus; ranges; // VDMA 节点 vdmaa0000000 { compatible xlnx,axi-vdma-6.3, xlnx,axi-vdma-1.00.a; reg 0xa0000000 0x10000; #dma-cells 1; clocks clkc 15, clkc 15, clkc 16; // 第一个是s_axi_lite_aclk 第二个是m_axi_mm2s_aclk第三个是m_axi_s2mm_aclk需要根据实际时钟索引调整 clock-names s_axi_lite_aclk, m_axi_mm2s_aclk, m_axi_s2mm_aclk; xlnx,num-fstores 3; dma-channela0000000 { compatible xlnx,axi-vdma-mm2s-channel; interrupts 0 31 4; // 中断号需要根据PL到PS的连接调整 xlnx,datawidth 0x20; xlnx,genlock-mode 0; // 读通道模式 }; dma-channela0000030 { compatible xlnx,axi-vdma-s2mm-channel; interrupts 0 32 4; xlnx,datawidth 0x20; xlnx,genlock-mode 1; // 写通道为Master模式 }; }; // TPG 节点 tpga0010000 { compatible xlnx,v-tpg-5.0, xlnx,v-tpg; reg 0xa0010000 0x10000; clocks clkc 16; // 像素时钟 clock-names ap_clk; xlnx,max-width 1920; xlnx,max-height 1080; port { tpg_out: endpoint { remote-endpoint vdma_in; // 与VDMA的stream端口连接 }; }; }; }; };同时需要在amba节点下为VDMA的stream接口和TPG的stream接口添加连接关系在system-user.dtsi的amba块内或同级添加vdma_s2mm { dma-channela0000030 { xlnx,video-format 0; // 例如0代表RGB xlnx,video-width 32; // 数据位宽 port { vdma_in: endpoint { remote-endpoint tpg_out; }; }; }; };注意 设备树中的时钟索引clkc 15、中断号0 31 4、寄存器地址必须与Vivado设计完全一致。最可靠的方法是参考Vivado生成的*.sysdef文件或pl.dtsi文件中的内容进行修改。错误的设备树是导致驱动加载失败的主要原因。4.3 构建与启动系统petalinux-build petalinux-package --boot --fsbl images/linux/zynq_fsbl.elf --fpga images/linux/system.bit --u-boot images/linux/u-boot.elf将生成的BOOT.BIN和image.ub拷贝到SD卡FAT分区启动开发板。系统启动后通过串口登录。首先检查驱动是否加载成功dmesg | grep -E “vdma|tpg|xilinx”你应该能看到类似xilinx-vdma a0000000.vdma: Xilinx AXI VDMA driver probe successful和xilinx-vdma a0000000.vdma: Please ensure that AXIS and AXI MM interface clocks are synchronous这是一条警告在我们的异步时钟设计下是预期的的信息。然后检查Frame Buffer设备ls -l /dev/fb* cat /sys/class/graphics/fb0/name如果一切正常/dev/fb0应该存在并且name属性可能显示为xilinx-fb。5. 用户空间应用与测试验证5.1 编写简单的Frame Buffer测试程序驱动加载成功后VDMA会在/dev/fb0创建一个Frame Buffer设备。我们可以编写一个简单的C程序来验证图像是否正确写入。这个程序不需要绘制复杂图形因为它只是映射出VDMA从TPG写入DDR的缓冲区。程序的主要作用是打开设备映射内存然后可以读取前几个像素值来验证数据是否在变化对于移动图案或者直接使用ioctl获取固定颜色条图像。#include stdio.h #include stdlib.h #include unistd.h #include fcntl.h #include sys/ioctl.h #include sys/mman.h #include linux/fb.h int main() { int fbfd 0; struct fb_var_screeninfo vinfo; struct fb_fix_screeninfo finfo; long int screensize 0; char *fbp 0; // 打开Frame Buffer设备 fbfd open(“/dev/fb0”, O_RDWR); if (fbfd -1) { perror(“Error: cannot open framebuffer device”); exit(1); } printf(“The framebuffer device was opened successfully.\n”); // 获取固定屏幕信息 if (ioctl(fbfd, FBIOGET_FSCREENINFO, finfo)) { perror(“Error reading fixed information”); exit(2); } // 获取可变屏幕信息分辨率、位深等 if (ioctl(fbfd, FBIOGET_VSCREENINFO, vinfo)) { perror(“Error reading variable information”); exit(3); } printf(“%dx%d, %dbpp\n”, vinfo.xres, vinfo.yres, vinfo.bits_per_pixel); printf(“Line length: %d bytes\n”, finfo.line_length); // 计算映射内存的大小 screensize finfo.line_length * vinfo.yres; printf(“Screensize: %ld bytes\n”, screensize); // 将Frame Buffer内存映射到用户空间 fbp (char *)mmap(0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fbfd, 0); if ((void*)fbp MAP_FAILED) { perror(“Error: failed to map framebuffer device to memory”); exit(4); } printf(“The framebuffer device was mapped to memory successfully.\n”); // 此时fbp指向DDR中VDMA写入的帧缓冲区。 // 对于TPG生成的动态图案如移动方块我们可以定期读取特定位置的值来观察变化。 int x 100, y 100; // 观察坐标(100, 100)的像素 long int location 0; unsigned int pixel_value 0; for (int i 0; i 10; i) { location (x vinfo.xoffset) * (vinfo.bits_per_pixel / 8) (y vinfo.yoffset) * finfo.line_length; pixel_value *((unsigned int*)(fbp location)); // 假设32bpp printf(“Frame %d: Pixel at (%d, %d) 0x%08X\n”, i, x, y, pixel_value); sleep(1); // 等待1秒观察TPG图案变化 } // 清理 munmap(fbp, screensize); close(fbfd); return 0; }将此程序交叉编译使用petalinux-build -c myapp -x do_compile或在Petalinux工程中创建应用并拷贝到开发板运行。如果TPG配置为移动方块图案程序输出的像素值应该会周期性变化。5.2 使用现有工具直接显示更简单的方法是使用Linux下现有的Frame Buffer工具。fbset 查看和修改Frame Buffer显示模式。fbset -i可以查看详细信息。cat或dd 理论上你可以将图像文件如原始的RGB数据文件直接写入/dev/fb0来显示。但我们的fb0内容由VDMA从TPG持续写入直接写入会被覆盖。使用ioctl切换缓冲区 更高级的用法是利用VDMA多缓冲区的特性通过驱动提供的ioctl命令让显示端切换到一个由TPG写入完毕的缓冲区而TPG继续写入另一个缓冲区实现无撕裂显示。这需要对VDMA驱动有更深入的了解并可能编写更复杂的应用。对于快速验证最直观的方法是连接一个显示器。如果你的PL部分还连接了HDMI TX或DisplayPort IP并且正确驱动那么系统启动后屏幕上就应该直接显示TPG生成的测试图案。这是最直接的验证方式。6. 调试技巧与常见问题排查在实际操作中几乎不可能一次成功。以下是我在调试过程中遇到的一些典型问题及解决方法问题1Vivado实现时报错时序不满足。现象 在Implementation阶段出现Setup/Hold时间违例。排查检查时钟约束 确保所有时钟特别是像素时钟和VDMA的AXI时钟都创建了正确的约束create_clock。使用Report Clock Networks和Report Clock Interaction检查时钟关系。降低时钟频率 初次尝试时可以先将像素时钟和AXI时钟频率设低例如降到50MHz验证功能再逐步提高。优化流水线 检查TPG到VDMA之间的路径。可以在它们之间插入AXI4-Stream Register SliceIP来打拍提高时序性能。问题2Petalinux启动后/dev/fb0不存在或驱动加载失败。现象dmesg中无VDMA或Frame Buffer相关成功信息甚至报错。排查检查设备树 这是最常见的原因。使用petalinux-build --sdk生成SDK用其中的设备树编译器dtc检查你的system-user.dtsi语法是否正确dtc -I dts -O dtb -o test.dtb system-user.dtsi。核对地址与中断 仔细核对设备树中的reg地址、clocks索引、interrupts号是否与Vivado Address Editor和Zynq配置中完全一致。中断号错误会导致驱动probe失败。检查内核配置 确认Xilinx DMA engines,Xilinx framebuffer driver等必要驱动已编译进内核*或作为模块M。如果是模块需要手动modprobe。查看完整内核日志dmesg | grep -i error或dmesg | grep -i fail寻找更具体的错误信息。问题3程序读取/dev/fb0但像素数据不变或全为0。现象 Frame Buffer设备存在但映射出来的内存区域是静态的或全是0。排查确认TPG是否启动 在用户空间可以通过devmem工具或编写一个小程序通过AXI-Lite总线访问TPG的寄存器地址在设备树中定义检查其控制寄存器确保它没有被禁用例如ap_start位是否被置1。确认VDMA是否启动 同样检查VDMA的S2MM_DMACR寄存器确保RS运行状态位和RUN_STOP位被置1。VDMA需要被正确初始化并启动传输。检查时钟和复位 确认PL侧的像素时钟和AXI时钟是否真的在运行。可以在Vivado中生成ILA集成逻辑分析仪核插入到TPG和VDMA的时钟、复位、流接口信号上烧录后通过Vivado Hardware Manager观察实际信号。这是最强大的调试手段。检查数据流路径 使用ILA抓取TPG输出的TDATA、TVALID、TREADY信号。如果TVALID和TREADY从未同时有效说明流通道未建立。检查VDMA的S2MM通道是否已启动并准备好接收数据。问题4图像显示撕裂或不完整。现象 连接显示器后图像有撕裂感、闪烁或只有部分屏幕有内容。排查缓冲区数量 检查VDMA配置的帧缓冲区数量。至少需要2个双缓冲。如果只有一个当TPG写入速度与显示器读取速度不同步时必然撕裂。建议设置为3。时序参数 仔细核对TPG中配置的视频时序参数总像素/行、有效区域、同步脉宽等是否与你的显示器模式严格匹配。一个错误的参数就可能导致图像错位。DDR带宽与延迟 如果系统负载重DDR访问延迟可能导致VDMA无法及时写入/读取数据。尝试降低帧率或分辨率看问题是否改善。也可以尝试使用PS的ACP端口如果支持它缓存一致性更好延迟更低。这个项目从硬件设计到软件驱动的完整流程涵盖了Zynq混合系统开发的关键环节。最大的收获不是最终屏幕上显示的彩色条纹而是在解决上述一个个问题的过程中对AXI总线协议、VDMA工作机制、Linux设备驱动模型以及硬件调试方法有了更深刻的理解。当你第一次看到自己生成的测试图案稳定地显示在屏幕上时那种对系统级掌控感是无可替代的。后续你可以基于这个平台轻松地将TPG替换为真实的图像传感器接口IP或者将显示通路连接到更复杂的视频处理管线从而构建更复杂的视频应用系统。