☰
FPGA PL端直驱DisplayPort TX:从IP配置到链路训练实战
2026/10/7 6:49:18 网站建设 项目流程

1. 项目缘起与整体设计思路

1.1 为什么要在PL端直接做DP TX

先说说这个项目的来由。手上有一块Xilinx的FPGA开发板,需要在没有外部GPU、没有独立视频处理芯片的前提下,把FPGA内部产生的视频数据直接推送到显示器上。最直接的想法是用HDMI,但项目对分辨率、刷新率和色彩深度有比较高的要求,DP接口在带宽和协议灵活性上更合适,所以最终选定了DisplayPort TX这条路线。

这里有个关键决策点:视频源到底走PS端还是PL端。如果走PS端,可以用处理器去组织帧数据,再通过AXI总线搬到外设,但这样会引入额外的总线延迟和CPU占用,而且PS端的视频通路往往受限于DDR带宽和缓存策略。把视频生成和DP TX全部放在PL端,好处是数据流完全在FPGA逻辑内部闭环,时序可控、延迟确定,特别适合对实时性敏感的场景,比如工业相机、医疗影像前端、雷达显示终端这类应用。

另一个考量是IP核的选型。Xilinx提供了DisplayPort 1.4 TX Subsystem这类集成度较高的IP,也提供了更底层的DisplayPort TX IP。集成Subsystem的好处是帮你把AUX通道、热插拔检测、链路训练这些繁琐的状态机都封装好了,你只需要喂视频流和配置参数;缺点是灵活性受限,某些非标准分辨率或者特殊时序不好改。底层IP则相反,控制粒度细,但需要自己实现链路训练和AUX通信,工作量大。这个项目里我选择了Subsystem方案,原因是项目周期紧,而且目标分辨率是标准的1080p和4K,Subsystem完全覆盖。

1.2 整体数据通路是怎么搭的

整个PL端的数据通路可以拆成四段:视频源生成、视频时序处理、DP协议封装、物理层发送。

视频源生成部分,我用的是自定义的Pattern Generator,产生彩条、渐变、棋盘格等测试图案,方便调试阶段验证链路。实际项目中这里会替换成真实的视频输入,比如来自MIPI CSI、SDI或者内部算法模块的输出。

视频时序处理部分,核心是把像素数据按照标准视频时序(HSYNC、VSYNC、DE)组织好,同时完成像素格式转换。DP TX Subsystem通常接受AXI4-Stream格式的视频流,像素格式可以是RGB、YCbCr 444、YCbCr 422等。如果源数据是RGB888,而Subsystem配置的是YCbCr422,就需要在这里做色彩空间转换。

DP协议封装部分由Subsystem内部完成,它会把视频流打包成DP的传输单元,插入时序参数、MISC信息、音频流(如果启用)等。这一层对用户基本透明,但需要正确配置视频时序参数,否则会出现画面偏移、闪烁甚至无显示。

物理层发送部分,Subsystem输出的是并行视频数据和时钟,需要经过一个Serializer(比如Xilinx的DisplayPort TX PHY或者第三方SerDes)转换成高速串行信号,再驱动到DP连接器。这一步对PCB走线、参考时钟质量、电源噪声非常敏感,硬件设计不到位的话,后面软件怎么调都没用。

1.3 方案选型的几个关键取舍

第一个取舍是参考时钟来源。DP TX的PHY需要一个高精度的参考时钟,通常是27MHz或者108MHz。如果板上没有专用晶振,可以用FPGA的MMCM从系统时钟分频得到,但抖动性能会差一些。实测下来,1080p60用MMCM分频的时钟也能稳定工作,但4K60就建议用专用晶振,否则链路训练容易失败。

第二个取舍是AUX通道的实现方式。AUX是DP的双向低速通道,用于读取显示器EDID、进行链路训练、调整驱动参数。Subsystem内部集成了AUX控制器,但需要外部电平转换电路把FPGA的1.8V或3.3V电平转换成DP要求的差分AUX电平。这个电路如果设计不好,会出现EDID读取失败、链路训练反复重试的问题。

第三个取舍是视频缓存的深度。如果视频源和DP TX的时钟域不同,或者源数据有突发性,就需要FIFO做跨时钟域缓冲。FIFO太浅会导致欠载,画面出现撕裂;太深则浪费BRAM资源。我的经验是至少缓冲两行像素,对于4K分辨率,一行是3840像素,两行就是7680个像素,按RGB888算需要约23KB的BRAM,这个开销是可以接受的。

2. 核心配置细节与实操要点

2.1 IP核的关键参数怎么填

打开Vivado的IP Catalog,找到DisplayPort 1.4 TX Subsystem,双击进入配置界面。第一个要填的是Link Rate和Lane Count。Link Rate有RBR(1.62Gbps)、HBR(2.7Gbps)、HBR2(5.4Gbps)、HBR3(8.1Gbps)几档,Lane Count可以是1、2、4。这两个参数决定了总带宽,计算公式是:

总带宽 = Link Rate × Lane Count × 编码效率

DP用的是8b/10b编码,编码效率是80%。比如HBR2配4 Lane,总带宽是5.4G × 4 × 0.8 = 17.28Gbps。1080p60 RGB888需要的带宽是1920 × 1080 × 60 × 24 = 2.98Gbps,绰绰有余。4K60 RGB888需要1920 × 1080 × 4 × 60 × 24 = 11.94Gbps,HBR2 4 Lane也够用。但如果要跑4K120或者8K30,就得考虑HBR3了。

第二个要填的是Maximum Resolution,这个参数会影响Subsystem内部FIFO的深度和时序参数的范围。填的时候要留一定余量,比如实际用1080p就填1920x1080,不要填成4K,否则会浪费资源。

第三个是Pixel Format。如果视频源是RGB,就选RGB;如果是YCbCr,就选对应的格式。注意DP协议里RGB和YCbCr的时序参数略有不同,选错了会导致颜色异常或者画面偏移。

第四个是Audio选项。如果不需要音频,直接关掉,可以省不少逻辑资源。需要音频的话,还要配置音频采样率、通道数等参数。

2.2 视频时序参数的精确计算

视频时序参数是DP TX配置里最容易出错的地方。以1080p60为例,标准参数是:

参数数值说明
水平有效像素1920每行有效像素数
水平前肩88HSYNC之前的空白像素
水平同步宽度44HSYNC的宽度
水平后肩148HSYNC之后的空白像素
水平总像素22001920+88+44+148
垂直有效行1080每帧有效行数
垂直前肩4VSYNC之前的空白行
垂直同步宽度5VSYNC的宽度
垂直后肩36VSYNC之后的空白行
垂直总行数11251080+4+5+36
像素时钟148.5MHz2200×1125×60

这些参数必须和显示器的EDID匹配,否则显示器可能不认这个时序。Subsystem里有一个Timing Configuration页面,把这些数值填进去就行。注意像素时钟是算出来的,不是随便填的,Vivado会根据你填的时序参数自动计算,但你要确保这个时钟在PHY的支持范围内。

对于非标准分辨率,比如1920x1200,参数会不一样,需要查VESA标准或者显示器的EDID。我一般用EDID解析工具读出显示器的详细时序,然后照着填。

2.3 跨时钟域处理与FIFO配置

视频源时钟和DP TX的像素时钟往往不同。比如视频源来自一个100MHz的摄像头接口,而DP TX的像素时钟是148.5MHz,这两个时钟域之间必须做跨时钟域处理。

我的做法是在视频源和Subsystem之间插入一个AXI4-Stream FIFO。FIFO的写时钟是视频源时钟,读时钟是DP TX的像素时钟。FIFO深度设置为两行像素,对于1080p RGB888,一行是1920×3=5760字节,两行就是11520字节。Vivado的AXI4-Stream FIFO IP可以配置数据宽度和深度,我一般选32位数据宽度,深度设为4096,这样能缓冲约4096×4=16384字节,足够两行多一点。

这里有个坑:FIFO的almost_full和almost_empty阈值要设好。如果almost_full设得太高,视频源会频繁暂停;设得太低,FIFO容易溢出。我的经验是almost_full设在深度的75%,almost_empty设在25%。另外,FIFO的复位要同步到各自的时钟域,否则会出现亚稳态。

2.4 链路训练与AUX通信的调试要点

链路训练是DP TX启动时最关键的步骤。Subsystem会自动发起链路训练,但前提是AUX通道能正常工作。AUX通道的调试可以从几个方面入手:

第一,检查AUX的物理连接。DP连接器的AUX+和AUX-是差分信号,需要正确的终端电阻和电平转换。用示波器看AUX波形,正常应该是差分摆幅在0.3V到0.6V之间,共模电压在0.5V左右。

第二,读取EDID。如果AUX能通,Subsystem应该能读到显示器的EDID。在Vivado的ILA里抓AXI4-Lite总线,看EDID读取的返回数据是否正确。如果读不到,先检查I2C地址(通常是0x50),再检查AUX的时钟频率(1MHz)。

第三,看链路训练的状态寄存器。Subsystem有一个Link Training Status寄存器,会显示训练是否成功、当前Link Rate和Lane Count、以及失败原因。常见的失败原因包括:AUX超时、时钟恢复失败、通道间偏斜过大。

我遇到过一次链路训练反复失败的情况,最后发现是参考时钟的抖动太大。换了一个专用的27MHz晶振后,问题解决。所以硬件设计阶段一定要重视时钟质量。

3. 完整实操流程与关键环节实现

3.1 Vivado工程的搭建步骤

第一步,创建Vivado工程,选好目标器件。我用的是一块Zynq UltraScale+的板子,具体型号就不说了,反正支持DP TX的器件都行。

第二步,添加DisplayPort 1.4 TX Subsystem IP。在IP Catalog里搜索"DisplayPort",找到对应的Subsystem,双击配置。按照前面说的参数填好Link Rate、Lane Count、分辨率、像素格式等。

第三步,添加视频源。我用的是自己写的Pattern Generator,用Verilog实现,产生彩条图案。核心是一个计数器和几个比较器,根据当前像素坐标决定输出什么颜色。代码不复杂,但要注意时序要严格符合视频标准。

第四步,添加AXI4-Stream FIFO,连接视频源和Subsystem。FIFO的写接口接视频源的输出,读接口接Subsystem的视频输入。

第五步,添加AXI4-Lite互联。Subsystem的配置接口是AXI4-Lite,需要接到PS端或者MicroBlaze上,方便软件读写寄存器。我用的是PS端的M_AXI_HPM0_LPD接口,通过AXI Interconnect连接到Subsystem。

第六步,添加时钟和复位。Subsystem需要几个时钟:AXI4-Lite时钟(通常100MHz)、视频像素时钟(148.5MHz)、PHY参考时钟(27MHz或108MHz)。用Clock Wizard生成这些时钟,注意像素时钟要和视频时序匹配。

第七步,添加约束文件。主要是引脚约束和时序约束。引脚约束把DP TX的差分对分配到正确的管脚,时序约束告诉Vivado哪些路径需要做时序分析。

3.2 关键代码片段与配置寄存器

Pattern Generator的核心代码大概长这样:

module pattern_gen ( input wire pix_clk, input wire rst_n, output reg [23:0] pixel_data, output reg hsync, output reg vsync, output reg de ); reg [11:0] h_cnt; reg [11:0] v_cnt; localparam H_ACTIVE = 1920; localparam H_FRONT = 88; localparam H_SYNC = 44; localparam H_BACK = 148; localparam H_TOTAL = 2200; localparam V_ACTIVE = 1080; localparam V_FRONT = 4; localparam V_SYNC = 5; localparam V_BACK = 36; localparam V_TOTAL = 1125; always @(posedge pix_clk or negedge rst_n) begin if (!rst_n) begin h_cnt <= 0; v_cnt <= 0; end else begin if (h_cnt == H_TOTAL - 1) begin h_cnt <= 0; if (v_cnt == V_TOTAL - 1) v_cnt <= 0; else v_cnt <= v_cnt + 1; end else begin h_cnt <= h_cnt + 1; end end end always @(*) begin hsync = ~((h_cnt >= H_ACTIVE + H_FRONT) && (h_cnt < H_ACTIVE + H_FRONT + H_SYNC)); vsync = ~((v_cnt >= V_ACTIVE + V_FRONT) && (v_cnt < V_ACTIVE + V_FRONT + V_SYNC)); de = (h_cnt < H_ACTIVE) && (v_cnt < V_ACTIVE); end always @(*) begin if (de) begin if (h_cnt < H_ACTIVE / 3) pixel_data = 24'hFF0000; else if (h_cnt < 2 * H_ACTIVE / 3) pixel_data = 24'h00FF00; else pixel_data = 24'h0000FF; end else begin pixel_data = 24'h000000; end end endmodule

这段代码产生一个三色彩条,红绿蓝各占三分之一。实际项目中可以改成渐变、棋盘格或者从ROM读图片数据。

Subsystem的配置寄存器通过AXI4-Lite访问。关键寄存器包括:

  • 0x0000:Link Training Control,写1启动训练
  • 0x0004:Link Training Status,读回训练结果
  • 0x0008:Video Timing Configuration,配置时序参数
  • 0x000C:Pixel Format,配置像素格式

在PS端用C代码读写这些寄存器:

#define DP_TX_BASE 0xA0000000 void dp_tx_start_training(void) { Xil_Out32(DP_TX_BASE + 0x0000, 0x1); while ((Xil_In32(DP_TX_BASE + 0x0004) & 0x1) == 0) { // 等待训练完成 } u32 status = Xil_In32(DP_TX_BASE + 0x0004); if (status & 0x2) { xil_printf("Link training succeeded\n"); } else { xil_printf("Link training failed, status = 0x%x\n", status); } }

3.3 上板调试的完整记录

第一次上板,我先把Pattern Generator的输出接到ILA上,确认视频时序正确。ILA抓到的波形显示HSYNC、VSYNC、DE的时序和预期一致,像素数据也在正确的时间窗口内变化。

然后连接显示器,启动链路训练。第一次训练失败了,状态寄存器显示AUX超时。用示波器检查AUX信号,发现差分摆幅只有0.1V,远低于正常的0.3V到0.6V。检查电路图,发现AUX的电平转换芯片供电电压不对,应该是3.3V,实际只有1.8V。改过来之后,AUX波形正常了。

第二次训练,AUX通了,但链路训练还是失败,状态显示时钟恢复失败。检查参考时钟,发现用的是MMCM分频出来的27MHz,抖动比较大。换了一个专用的27MHz晶振,重新训练,这次成功了。显示器上出现了彩条图案,颜色正确,没有闪烁。

但仔细看,画面左边有一条细黑边,大约几个像素宽。这是时序参数不匹配导致的。查显示器的EDID,发现它的水平前肩是88,我填的是88,没问题;但水平同步宽度是44,我填的是44,也没问题。后来发现是Subsystem内部的时序配置寄存器没有正确写入,重新写了一遍,黑边消失。

最后跑了一个长时间测试,连续运行24小时,画面稳定,没有出现掉链路或者花屏。温度也在正常范围内,说明功耗和散热设计没问题。

4. 常见问题与排查技巧实录

4.1 链路训练失败的原因与对策

链路训练失败是最常见的问题,表现是显示器无信号或者反复闪烁。根据我的经验,原因可以归为几类:

现象可能原因排查方法解决措施
AUX超时AUX电平不对、AUX走线断裂示波器看AUX波形检查电平转换电路、补焊
时钟恢复失败参考时钟抖动大、频率偏差频谱仪测时钟相位噪声换专用晶振、调整MMCM
通道间偏斜过大PCB走线不等长时域反射计测走线长度重新布线或加延迟补偿
训练成功但无画面视频时序不匹配、像素格式错误读EDID、对比时序参数修正时序参数、改像素格式

我踩过最深的坑是参考时钟。一开始为了省事,用MMCM从100MHz系统时钟分频出27MHz,理论上频率是对的,但抖动性能很差。1080p60还能勉强工作,4K60就完全不行。后来换了一个专用的27MHz晶振,抖动从几十皮秒降到几皮秒,问题彻底解决。所以如果你的项目要跑高分辨率高刷新率,参考时钟一定不要省。

4.2 画面异常问题的快速定位

画面异常有很多种表现,定位思路是从后往前查。

如果显示器完全无信号,先查链路训练是否成功。训练成功但无信号,查视频时序是否匹配。时序匹配但画面偏移,查时序参数的精确值。画面颜色不对,查像素格式和色彩空间转换。

如果画面闪烁或者撕裂,通常是FIFO欠载或溢出。用ILA抓FIFO的almost_empty和almost_full信号,看是否有频繁触发。如果有,要么加深FIFO,要么调整视频源的输出节奏。

如果画面有噪点或者花屏,可能是高速串行链路的信号完整性问题。检查PCB走线的阻抗匹配、终端电阻、电源滤波。用眼图仪看发送端的信号质量,眼高和眼宽是否满足要求。

我遇到过一次画面随机出现彩色噪点的情况,查了很久才发现是电源噪声。DP PHY的供电电源纹波太大,导致串行信号的抖动增加。在电源引脚旁边加了一个10uF的钽电容和几个0.1uF的陶瓷电容,问题解决。所以电源设计一定要留足余量,不要只看平均电流,要看瞬态响应。

4.3 资源占用与时序收敛的优化

DP TX Subsystem会占用不少逻辑资源,尤其是BRAM和DSP。如果FPGA资源紧张,可以做几件事来优化:

第一,降低Lane Count。如果带宽够用,用2 Lane代替4 Lane,可以省一半的PHY资源。

第二,关闭音频功能。音频模块会占用额外的BRAM和逻辑,不需要就关掉。

第三,优化FIFO深度。如果视频源和DP TX的时钟是同源的,可以大幅减小FIFO深度,甚至不用FIFO。

第四,复用时钟资源。如果板上已经有合适的时钟,不要再用MMCM生成,直接拿来用。

时序收敛方面,DP TX的高速接口通常需要额外的时序约束。Vivado的Subsystem会自动生成一些约束,但可能需要手动补充。关键是参考时钟的输入延迟、PHY输出的输出延迟、以及跨时钟域路径的false path设置。如果时序不收敛,先看报告里哪些路径违例,再针对性地加约束或者改逻辑。

4.4 与PS端协同工作的注意事项

如果DP TX的配置接口接到PS端,有几个地方要注意:

第一,AXI4-Lite的时钟域。PS端的M_AXI_HPM0_LPD通常是100MHz,而Subsystem的AXI4-Lite接口可能要求不同的时钟。如果不同,需要加一个AXI Clock Converter。

第二,地址映射。在Vivado的Address Editor里,确保Subsystem的寄存器地址映射到PS端可以访问的空间。如果地址冲突,PS端读写会失败。

第三,中断处理。Subsystem可以产生中断,比如链路训练完成、热插拔事件等。如果要用中断,需要在PS端配置GIC,并注册中断处理函数。

第四,DMA传输。如果视频数据要从PS端的DDR搬到PL端的DP TX,需要用DMA。Xilinx提供了AXI VDMA IP,可以配置成读模式,从DDR读视频帧送到DP TX。VDMA的配置比较复杂,要注意帧缓存的数量、地址对齐、以及同步信号的生成。

我在一个项目里用VDMA从DDR读4K视频帧,一开始画面总是撕裂,后来发现是VDMA的帧缓存数量不够,只有两个,导致读写冲突。改成三个帧缓存后,问题解决。所以VDMA的帧缓存数量要根据视频分辨率和DDR带宽来算,不能随便设。

5. 几个容易被忽略的硬件设计细节

5.1 DP连接器的选型与布局

DP连接器有标准型和Mini型两种,选哪种取决于产品形态。标准型更牢固,但占板面积大;Mini型小巧,但插拔寿命短一些。不管选哪种,都要注意连接器的阻抗是100欧姆差分,PCB走线要按这个阻抗设计。

布局上,DP连接器尽量靠近FPGA,走线越短越好。差分对要等长,误差控制在5mil以内。差分对之间要保持足够的间距,避免串扰。如果走线要过孔,尽量少换层,换层时要加回流地过孔。

5.2 电源滤波与去耦

DP PHY的电源噪声直接影响信号质量。我的做法是在每个电源引脚旁边放一个0.1uF的陶瓷电容,再在电源入口放一个10uF的钽电容。如果空间允许,再加一个1uF的电容。电容要尽量靠近引脚,走线要短而粗。

另外,DP PHY的供电最好用LDO而不是DC-DC,因为LDO的噪声更小。如果必须用DC-DC,要选低纹波的型号,并在输出端加LC滤波。

5.3 ESD防护与热插拔

DP接口是外部接口,容易受到ESD冲击。在DP连接器的信号线上要加ESD防护器件,比如TVS二极管。选TVS的时候要注意结电容,太大会影响高速信号。一般选结电容小于0.5pF的型号。

热插拔方面,DP协议支持热插拔检测(HPD)。FPGA端要能检测HPD信号的变化,并在显示器插入或拔出时重新进行链路训练。Subsystem内部有HPD检测逻辑,但需要外部电路把HPD信号电平转换到FPGA能接受的范围。

6. 实际项目中的经验体会

这个项目做下来,最大的体会是:DP TX的调试,三分靠逻辑,七分靠硬件。逻辑部分只要按部就班配置IP、写时序、做跨时钟域处理,一般不会有大问题。但硬件部分,时钟质量、电源噪声、信号完整性、ESD防护,任何一个环节出问题,都会导致链路训练失败或者画面异常。

另一个体会是,EDID的读取和解析非常重要。显示器的EDID里包含了它支持的所有分辨率和时序参数,照着EDID配置,成功率最高。如果自己瞎填时序,即使链路训练成功,显示器也可能不认,或者画面偏移。

最后分享一个小技巧:在调试阶段,可以先用低分辨率(比如720p)和低Link Rate(比如RBR)跑通链路,确认AUX和基本时序没问题,再逐步提高分辨率和Link Rate。这样可以把问题隔离,避免一上来就面对一堆变量。等720p跑通了,再上1080p,再上4K,每一步都确认稳定后再进行下一步。这个方法帮我省了很多调试时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询