← Back to Blog
FPGAZynqVDMAHDMIVTC视频1080pAXIPetaLinuxV4L2

Zynq 实战 13|VDMA + VTC + HDMI:搭一条 1080p 视频流水线

This article was written in Chinese and auto-translated via Google Translate.
View Chinese Original →

Zynq 实战 13|VDMA + VTC + HDMI:搭一条 1080p 视频流水线

这是《Zynq FPGA 嵌入式系统设计实战》系列的第 13 篇。 板子:Pynq-Z2(XC7Z020)。工具链:Vivado / Vitis / PetaLinux 2023.2。 上一篇:《Zynq 实战 12|AXI DMA 引擎驱动》


0. 这一篇要解决什么问题

上一篇用 AXI DMA 把 PL 的任意数据流搬进 DDR。视频是 AXI-Stream 数据流的一个特殊子集:它有固定帧率、固定分辨率、严格的行列时序——普通 AXI DMA 管不了帧切换和显示时序,需要专用的 AXI VDMA(Video DMA)VTC(Video Timing Controller)

本篇做完之后,你会有:

  • 一条跑通 1920×1080@60Hz 的完整视频流水线,从 PL 内部的 Test Pattern Generator(TPG)输出到 HDMI 显示器
  • 清楚 VDMA 的两种工作模式和三帧缓冲配置
  • 能用裸机寄存器操作或 Linux 控制 VDMA 帧切换

本文不覆盖摄像头输入(那是 14、15 篇的内容),不覆盖 H.264 编码——只做显示输出这条路。


1. 整体 Block Design 拓扑

Zynq 1080p60 视频流水线 Block Design ① TPG Test Pattern Generator S-AXIS video ② AXI VDMA S2MM 写帧 DDR MM2S 读帧→PL HP0 64-bit PS DDR3 Frame Buffer × 3(Triple Buffer) S-AXIS video out ③ Color Conv. RGB24 → RGB24 (或 YCbCr 转换) ④ VTC Video Timing Controller HSYNC/VSYNC/DE Pixel Clock 148.5 MHz (MMCM) TMDS ⑤ HDMI Tx Subsystem (PHY + TMDS) 或直接用 HDMI 1.4 TX HDMI 显示器 1920×1080@60 PS — ARM Cortex-A9(控制平面) VDMA 寄存器控制 GP0 AXI-Lite 写帧地址 VTC / HDMI 配置 S_AXI_GP0 时序参数写入 Frame Buffer(DDR) 3 × 1920×1080×3 = 18.7 MB Linux V4L2 / 裸机 userspace 控制帧切换 带宽核算 1920 × 1080 × 60 fps × 3 bytes(RGB24) = 373 MB/s(读写各一份,双向共 746 MB/s) HP0 实测上限 ~800 MB/s,Triple Buffer 带宽占用率 ≈ 47%,有余量做其他 DMA 任务
图 1. Zynq 1080p60 视频流水线:TPG → VDMA → DDR → VDMA → VTC → HDMI Tx

完整 Block Design 里的 IP 清单:

IP版本功能
AXI4-Stream Test Pattern Generatorv8.2生成彩条 / 渐变 / 棋盘格测试图案
AXI Video DMAv6.3视频帧在 AXI-Stream 和 DDR 之间搬运
AXI4-Stream Subset Converterv1.1可选:做 AXI-Stream 位宽转换或颜色格式调整
Video Timing Controllerv6.2产生 HSYNC / VSYNC / DE / 行列计数
HDMI 1.4/2.0 Tx SubsystemRGB to DVI IPTMDS 编码输出
MMCM(Clocking Wizard)v6.0生成 148.5 MHz pixel clock

2. 带宽核算:为什么要用 HP 端口

1080p60 RGB24 的原始带宽:

1920 × 1080 × 60 Hz × 3 bytes = 373,248,000 B/s ≈ 356 MB/s

VDMA 在 Triple Buffer(三帧缓冲)模式下,需要:

  • S2MM 写通道:写入当前捕获帧(373 MB/s,若有摄像头)
  • MM2S 读通道:读出当前显示帧(373 MB/s)

两个方向加起来:~746 MB/s
即使只做显示(无摄像头输入),MM2S 读带宽 373 MB/s 也接近 GP0 端口(AXI-GP0 理论值约 600 MB/s,实际负载下更低)的极限。

结论:视频流水线必须用 HP 端口(S_AXI_HP0S_AXI_HP2,不能用 GP 端口。

HP0 实测能跑约 800 MB/s(见第 12 篇),单方向 373 MB/s 占用率约 47%,有余量。


3. 1080p60 时序参数:VTC 的每一个数字

VTC 需要精确的时序参数才能输出符合 HDMI 标准的 1920×1080@60Hz 信号。这些数字来自 VESA/CEA 标准:

参数水平(H)值垂直(V)值说明
Active(有效像素/行)19201080实际画面
Front Porch(前廊)884有效区结束到同步脉冲前
Sync Width(同步脉冲)445HSYNC / VSYNC 高脉冲宽度
Back Porch(后廊)14836同步脉冲结束到有效区开始
Total(总长)220011251920+88+44+148 / 1080+4+5+36
Pixel Clock148.5 MHz2200 × 1125 × 60 = 148.5 MHz

🚧 避坑:CEA-861 标准里 1080p60 的 pixel clock 是 148.5 MHz,不是 148 MHz 也不是 150 MHz。Zynq 的 MMCM 能精确锁到 148.5 MHz(通过合理设置 CLKFBOUT_MULT_F 和 CLKOUT_DIVIDE)。如果你用 150 MHz,显示器大概率能显示但画面会有轻微抖动,某些 HDMI 接收器会拒绝信号。Clocking Wizard 里设置目标频率 148.5 MHz,Vivado 会自动算分频参数。

Clocking Wizard 参数(PL 输入时钟 200 MHz)

CLKIN1_PERIOD = 5.000        # 200 MHz 输入
CLKFBOUT_MULT_F = 7.425      # VCO = 200 * 7.425 = 1485 MHz
CLKOUT0_DIVIDE_F = 10.0      # 1485 / 10 = 148.5 MHz
CLKOUT1_DIVIDE   = 2         # 1485 / 2 = 742.5 MHz(TMDS 5× 时钟,用于 HDMI 序列化)

在 VTC 寄存器里设置(裸机,偏移见 PG016)

/* VTC 基地址(Vivado Address Editor 里的分配值) */
#define VTC_BASE  0x43C20000

/* VTC 寄存器偏移 */
#define VTC_CTL   0x000   /* Control */
#define VTC_GASIZE 0x060  /* Generator Active Size:低16位=行有效像素-1,高16位=帧有效行-1 */
#define VTC_GFENC  0x068  /* Generator Frame Encoding(field/frame)*/
#define VTC_GHSIZE 0x070  /* H total size */
#define VTC_GVSIZE 0x074  /* V total size */
#define VTC_GHSYNC 0x078  /* H sync 起始/结束 */
#define VTC_GVSYNC 0x07C  /* V sync 起始/结束 */
#define VTC_GHBPST 0x080  /* H back porch 起始 */
#define VTC_GVBPST 0x084  /* V back porch 起始 */

/* 1920×1080@60Hz 参数写入 */
void vtc_config_1080p60(void __iomem *vtc_base)
{
    /* 先停止 VTC */
    iowrite32(0x00000000, vtc_base + VTC_CTL);

    /* Active area: 1920 cols, 1080 rows(均为值-1) */
    iowrite32((1079 << 16) | 1919, vtc_base + VTC_GASIZE);

    /* Total: H=2200, V=1125 */
    iowrite32(2199, vtc_base + VTC_GHSIZE);
    iowrite32(1124, vtc_base + VTC_GVSIZE);

    /* Hsync: 起始列 2008 (1920+88), 结束列 2052 (2008+44) */
    iowrite32((2051 << 16) | 2008, vtc_base + VTC_GHSYNC);
    /* Vsync: 起始行 1084 (1080+4),  结束行 1089 (1084+5) */
    iowrite32((1088 << 16) | 1084, vtc_base + VTC_GVSYNC);

    /* Back porch: H 从列 2052 到 2199(实际是 active 从 2200-148=2052 开始) */
    iowrite32(2052, vtc_base + VTC_GHBPST);
    iowrite32(1089, vtc_base + VTC_GVBPST);

    /* 启动 VTC,生成器模式 */
    iowrite32(0x00000003, vtc_base + VTC_CTL);  /* GEN_ENABLE | ENABLE */
}

4. VDMA 配置详解

4.1 IP 配置参数

在 Vivado 里双击 AXI VDMA IP:

配置项设置值说明
Enable Read ChannelMM2S(DDR → PL,用于显示输出)
Enable Write Channel✅(如需摄像头输入)S2MM(PL → DDR)
Frame Buffers3Triple Buffer,避免撕裂
Memory Map Data Width64对应 HP0 64-bit 接口
Stream Data Width32RGB24 每像素 3 字节,对齐到 32-bit(1 字节浪费,或用 24-bit 流)
Max Frame Store3最大帧缓冲数
Line Buffer Depth2048行缓冲深度,1920 像素以上必须 ≥ 2048
Video FormatRGB与 TPG 输出格式一致

🚧 避坑:Line Buffer Depth 必须 ≥ 水平分辨率。如果你把它设成 1024 然后跑 1920 列,VDMA 会在行末尾丢数据,症状是画面上出现规律性水平撕裂条纹,但 dmesg 里没有任何报错。

4.2 Free-running 模式 vs Frame-locked 模式

Free-running(自由运行)

VDMA 持续循环读取 DDR 帧缓冲,不和 VSYNC 同步。这是最简单的配置:

  • 帧地址寄存器(MM2S_Start_Address_1/2/3)在启动时写一次,之后 VDMA 自己循环
  • PS 端可以在 VDMA 读完一帧后更新下一帧的 DDR 内容,但不精确同步
  • 适合静态图像显示、单帧更新频率 < 30 fps 的场景
  • 风险:撕裂(VDMA 读到一半,PS 已经写新数据)

Frame-locked(帧同步)

VDMA 用 VTC 的 VSYNC 信号作为帧切换信号,只在 VSYNC 期间切换帧地址:

  • 需要把 VTC 的 vtiming_out 接到 VDMA 的 s_axis_vid_tvalid 时序输入
  • VDMA 的 PARKPTR 寄存器指向当前显示帧;PS 端向空闲帧写数据,写完后切 PARKPTR
  • 适合需要平滑显示的场景(视频播放、UI 渲染)

实际推荐:绝大多数有显示需求的场景,用 Frame-locked + Triple Buffer(3 帧:显示、备用、写入,三路轮换),完全消除撕裂。


5. VDMA 寄存器操作(裸机 + Linux 两种方式)

5.1 裸机寄存器直接写(Vitis / Standalone)

VDMA MM2S 通道关键寄存器(偏移来自 PG020):

偏移名称写入值说明
0x00MM2S_VDMACR0x00010003RS=1(运行),Circular_Park=1,FSYNC=1
0x18MM2S_PARK_PTR_REG0x00000000RdFramePtr=0(从帧0开始)
0x28MM2S_START_ADDR_10x10000000帧0 DDR 地址
0x2CMM2S_START_ADDR_20x10800000帧1 DDR 地址(偏移 8MB)
0x30MM2S_START_ADDR_30x11000000帧2 DDR 地址(偏移 16MB)
0x58MM2S_HSIZE1920 × 3 = 5760每行字节数
0x5CMM2S_FRMDLY_STRIDE5760stride = hsize(无对齐 padding)
0x60MM2S_VSIZE1080帧行数,写此寄存器触发传输开始
/*
 * vdma_start_display.c — 裸机启动 VDMA MM2S(Vitis Standalone)
 *
 * 假设:
 *   - VDMA 基地址 0x43000000
 *   - Frame Buffer 0 在 DDR 0x10000000(16MB 对齐)
 *   - 1920×1080 RGB24,stride = 5760 bytes/line
 */
#include "xaxivdma.h"
#include "xparameters.h"

#define VDMA_ID          XPAR_AXI_VDMA_0_DEVICE_ID
#define FB0_ADDR         0x10000000UL
#define FB1_ADDR         0x10780000UL   /* 1920*1080*3 = 6,220,800 ≈ 0x5F1000 → 对齐 0x600000 */
#define FB2_ADDR         0x10F00000UL
#define H_PIXELS         1920
#define V_LINES          1080
#define BYTES_PER_PIXEL  3
#define STRIDE           (H_PIXELS * BYTES_PER_PIXEL)   /* 5760 */

int vdma_start_display(void)
{
    XAxiVdma       vdma;
    XAxiVdma_Config *cfg;
    XAxiVdma_DmaSetup rd_cfg;
    int ret;

    cfg = XAxiVdma_LookupConfig(VDMA_ID);
    if (!cfg) return XST_FAILURE;

    ret = XAxiVdma_CfgInitialize(&vdma, cfg, cfg->BaseAddress);
    if (ret != XST_SUCCESS) return ret;

    /* 配置 MM2S(读通道 → 显示输出) */
    rd_cfg.VertSizeInput      = V_LINES;
    rd_cfg.HoriSizeInput      = STRIDE;
    rd_cfg.Stride             = STRIDE;
    rd_cfg.FrameDelay         = 0;
    rd_cfg.EnableCircularBuf  = 1;   /* 循环帧缓冲 */
    rd_cfg.EnableSync         = 1;   /* 帧同步模式 */
    rd_cfg.PointNum           = 0;
    rd_cfg.EnableFrameCounter  = 0;
    rd_cfg.FixedFrameStoreAddr = 0;

    rd_cfg.FrameStoreStartAddr[0] = FB0_ADDR;
    rd_cfg.FrameStoreStartAddr[1] = FB1_ADDR;
    rd_cfg.FrameStoreStartAddr[2] = FB2_ADDR;

    ret = XAxiVdma_DmaConfig(&vdma, XAXIVDMA_READ, &rd_cfg);
    if (ret != XST_SUCCESS) return ret;

    ret = XAxiVdma_DmaSetBufferAddr(&vdma, XAXIVDMA_READ, rd_cfg.FrameStoreStartAddr);
    if (ret != XST_SUCCESS) return ret;

    ret = XAxiVdma_DmaStart(&vdma, XAXIVDMA_READ);
    if (ret != XST_SUCCESS) return ret;

    xil_printf("VDMA MM2S 已启动,Triple Buffer 模式\r\n");
    return XST_SUCCESS;
}

帧切换(显示下一帧,裸机)

/* 把 FB1 填满新内容后,切换到 FB1 显示 */
void vdma_switch_to_frame(XAxiVdma *vdma, int frame_idx)
{
    /*
     * PARKPTR 寄存器低 5 位 = RdFramePtr,VDMA 在下一个 VSYNC 时切到此帧
     * 非 Circular 模式下需要重写 HSIZE/VSIZE 触发新帧
     */
    XAxiVdma_StartParking(vdma, frame_idx, XAXIVDMA_READ);
}

5.2 Linux 内核:通过 V4L2 或直接 MMIO

PetaLinux 2023.2 里有 xilinx-vdma 驱动,但 V4L2 子系统的完整支持需要额外的 xlnx_v_vdma_proc 设备驱动链。对于直接写 VDMA 帧地址的简单需求,用 /dev/mem 调试或 UIO(见第 11 篇 UIO 方法)最简单:

/* linux_vdma_ctrl.c — 通过 /dev/mem 控制 VDMA(仅调试,生产用 UIO 或内核驱动) */
#include <stdio.h>
#include <stdint.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>

#define VDMA_BASE      0x43000000UL
#define MAP_SIZE       0x10000

/* MM2S 寄存器偏移 */
#define MM2S_VDMACR    0x00
#define MM2S_PARK_PTR  0x18
#define MM2S_START_1   0x28
#define MM2S_START_2   0x2C
#define MM2S_START_3   0x30
#define MM2S_HSIZE     0x58
#define MM2S_STRIDE    0x5C
#define MM2S_VSIZE     0x60   /* 写这个触发帧传输 */

#define FB0 0x10000000UL
#define FB1 0x10780000UL
#define FB2 0x10F00000UL

int main(void)
{
    int   fd = open("/dev/mem", O_RDWR | O_SYNC);
    void *base = mmap(NULL, MAP_SIZE, PROT_READ|PROT_WRITE,
                      MAP_SHARED, fd, VDMA_BASE);

#define WR(off, val) (*(volatile uint32_t*)((uint8_t*)base+(off)) = (val))

    /* 停止 VDMA */
    WR(MM2S_VDMACR, 0x00000000);
    usleep(1000);

    /* 帧地址 */
    WR(MM2S_START_1, (uint32_t)FB0);
    WR(MM2S_START_2, (uint32_t)FB1);
    WR(MM2S_START_3, (uint32_t)FB2);

    /* 帧尺寸:stride 和 hsize(字节) */
    WR(MM2S_HSIZE,  1920 * 3);    /* 5760 */
    WR(MM2S_STRIDE, 1920 * 3);    /* 5760 */

    /* 启动(RS=1,Circular,GenLockSrc=0) */
    WR(MM2S_VDMACR, 0x00010003);

    /* 写 VSIZE 触发 → 开始传输 */
    WR(MM2S_VSIZE,  1080);

    printf("VDMA MM2S 已启动\n");
    munmap(base, MAP_SIZE);
    close(fd);
    return 0;
}

6. 常见故障排查

6.1 画面撕裂(Tearing)

症状:显示画面出现水平分界线,上下两部分属于不同帧的内容。
根因:使用 Free-running 模式,VDMA 读取帧缓冲时 PS 同时在写同一块 DDR。

修复

  1. 改用 Frame-locked 模式(FSYNC bit 设为 1)
  2. 使用 Triple Buffer(3 帧),确保 VDMA 读、PS 写、备用三帧互不冲突
  3. 确认 VTC 的 VSYNC 已正确连接到 VDMA 的同步输入

6.2 画面偏移(Image Shift / Pixel Drift)

症状:画面整体向右或向下偏移若干像素,有时伴随边缘绿色或黑色条纹。
根因 A:stride(行步进)配置错误。stride 是每行的 字节数(含对齐 padding),不是像素数。RGB24 + 1920 列时 stride = 5760,如果误填成 1920 会导致 VDMA 读行地址偏移。
根因 B:帧缓冲 DDR 起始地址不是 AXI DMA 要求的对齐倍数(通常要求 4 字节或 64 字节对齐,Zynq 上至少 4 字节对齐)。

🚧 避坑:VDMA 的 MM2S_HSIZE 填的是字节数(bytes per line),不是像素数(pixels per line)。RGB24 是 1920 × 3 = 5760。填错这个值是最常见的画面偏移原因。

6.3 帧缓冲数量不足

症状:视频播放时偶发闪烁,或高速场景(如滚动字幕)出现撕裂。
根因:双缓冲(2 帧)在 PS 写帧速度比 VDMA 读帧速度慢时,无法保证总有一帧可读且完整。
修复:改用三帧(Frame Buffers = 3),VDMA 的 FRMSTORE 寄存器设为 3,DDR 里预分配 3 块帧内存。

6.4 Genlock 相关问题

Genlock 用于多路 VDMA 之间的帧同步(例如捕获通道和显示通道同步):

Genlock 模式配置适用场景
Dynamic GenlockS2MM 为 Master,MM2S 为 Slave摄像头输入同步显示,帧率由输入决定
Static Genlock两通道独立固定帧率静态图像循环显示
Disabled无同步只有显示,无输入

常见 Genlock 问题:如果 MM2S 帧率(由 pixel clock 决定)和 S2MM 帧率(由摄像头时序决定)不完全匹配(哪怕差 1 ppm),长时间运行后会发生帧跳(drop 或 repeat)。解决方案是给 Clocking Wizard 加 MMCM 的动态相位调整,或接受偶发帧跳(对大多数应用可接受)。


7. HDMI Tx 子系统配置要点

Pynq-Z2 上的 HDMI 输出路径有两种常见方案:

方案 A:rgb2dvi IP(免费,简单)
来自 Digilent 的 IP(在 Vivado IP Catalog 或 GitHub 上获取),把 RGB 并行视频 + 时序信号转成 TMDS 编码输出,走 HDMI 连接器。Pynq-Z2 板子上的 HDMI Tx 连接器接的就是 PL IO,可以直接用 rgb2dvi

方案 B:Xilinx HDMI 1.4/2.0 Tx Subsystem(需要 license)
功能完整,支持 HDCP、音频嵌入,但需要付费 license。个人项目和学习用 rgb2dvi 足够。

rgb2dvi IP 连线

VTC 的 vtiming_out   ──→  rgb2dvi 的 vid_io_in_timing
AXI-Stream 视频数据  ──→  AXI4-Stream to Video Out IP ──→  rgb2dvi 的 vid_io_in (RGB 24-bit)
Pixel Clock(148.5M)──→  rgb2dvi 的 PixelClk
TMDS_Clk_p/n         ──→  HDMI 连接器差分时钟引脚
TMDS_Data_p/n[2:0]   ──→  HDMI 连接器 3 路差分数据引脚

XDC 引脚约束(Pynq-Z2 HDMI Tx,来自官方 Master XDC)

# HDMI TX(来自 Pynq-Z2 Master XDC)
set_property -dict {PACKAGE_PIN L17 IOSTANDARD TMDS_33} [get_ports {hdmi_tx_clk_p}]
set_property -dict {PACKAGE_PIN L18 IOSTANDARD TMDS_33} [get_ports {hdmi_tx_clk_n}]
set_property -dict {PACKAGE_PIN K17 IOSTANDARD TMDS_33} [get_ports {hdmi_tx_data_p[2]}]
set_property -dict {PACKAGE_PIN K18 IOSTANDARD TMDS_33} [get_ports {hdmi_tx_data_n[2]}]
set_property -dict {PACKAGE_PIN G17 IOSTANDARD TMDS_33} [get_ports {hdmi_tx_data_p[1]}]
set_property -dict {PACKAGE_PIN H17 IOSTANDARD TMDS_33} [get_ports {hdmi_tx_data_n[1]}]
set_property -dict {PACKAGE_PIN D19 IOSTANDARD TMDS_33} [get_ports {hdmi_tx_data_p[0]}]
set_property -dict {PACKAGE_PIN D20 IOSTANDARD TMDS_33} [get_ports {hdmi_tx_data_n[0]}]

🚧 避坑rgb2dvi IP 的 kGenerateSerialClk 参数默认是 true(内部生成 5× 串行时钟),如果你想用外部 MMCM 生成的 5× 时钟(更稳定),需要把它改为 false 并连接 SerialClk。用内部生成时,pixel clock 抖动略大,某些严格的显示器(特别是 4K 显示器做降频输入)可能拒绝信号。


8. 资源占用参考

以下数据来自 Vivado 2023.2,Pynq-Z2(XC7Z020),实现后:

IP 模块LUTFFBRAMDSP
AXI VDMA v6.3(读写均开,3帧)2,8473,91240
VTC v6.228431200
TPG v8.2(1080p)1,1561,02422
rgb2dvi(TMDS 编码)986400
AXI SmartConnect + Interconnect1,2451,08700
合计5,6306,39962
XC7Z020 可用53,200106,400140220
占用率10.6%6.0%4.3%0.9%

资源占用不多,还有充足空间加 HLS 算法或 DSP 模块。


9. 本篇 Checklist

  • Pixel clock 锁到 148.5 MHz(不是 148 或 150),Clocking Wizard 里验证 locked 信号
  • VTC H total = 2200,V total = 1125(CEA-861 标准值)
  • VDMA Line Buffer Depth ≥ 2048(≥ 水平分辨率)
  • VDMA MM2S_HSIZE 填字节数(1920 × 3 = 5760),不是像素数
  • Frame Buffers = 3(Triple Buffer),DDR 里预留 3 × 6.2 MB ≈ 18.7 MB
  • HP0 端口 Data Width = 64-bit,确认 VDMA 接的是 HP 端口不是 GP 端口
  • 用 Frame-locked 模式避免撕裂,VTC VSYNC 正确接到 VDMA 同步输入

10. 下一篇预告

下一篇 《Zynq 实战 14|AXI-Stream 实战:高速 ADC 采集与流式 DSP》 会深入 AXI4-Stream 协议本身:

  • 自己写一个 AXI4-Stream Master IP(产生 sin 波样本,模拟 ADC 数据流)
  • 接 FIFO + AXI DMA S2MM 进 DDR
  • 分析 100 Msps × 16-bit = 200 MB/s 的可行性,背压(backpressure)测试
  • 在流上串一个 FIR 滤波器 IP,做实时流式 DSP

参考资料

文档号名称用途
PG020AXI Video DMA v6.3 Product GuideVDMA 寄存器映射(MM2S/S2MM 偏移)、Genlock 配置、Frame Buffer 地址格式
PG016Video Timing Controller v6.2 Product GuideVTC 寄存器详解,1080p/720p/VGA 标准时序参数表
PG278AXI4-Stream to Video Out v4.0 Product GuideStream → 并行视频信号的时序转换,连接 VTC 的方式
CEA-861A DTV Profile for Uncompressed High Speed Digital Interfaces1080p60 标准时序(H total 2200 / V total 1125 / pixel clock 148.5 MHz)权威来源
UG585Zynq-7000 SoC TRMHP 端口带宽规格(第 9 章),PS DDR 地址映射
Digilent GitHubDigilent/vivado-library / ip / rgb2dvirgb2dvi IP 源码和例程,Pynq-Z2 HDMI Tx 连接示例

这是《Zynq FPGA 嵌入式系统设计实战》系列第 13 篇。 如果你在 VTC 时序参数、VDMA 撕裂或 Pixel Clock 锁相上遇到了问题,欢迎留言。