MIPI DPHY 在 ZYNQ 上做接收,很多人第一反应是找现成的 IP 核。但现实情况是,Xilinx 官方并没有提供一个开箱即用的 MIPI DPHY 接收 IP,尤其在 ZYNQ-7000 系列上,你得自己动手用 SelectIO 资源去搭。我前后做过三个基于 ZYNQ 的 MIPI 摄像头采集项目,从最初的天真以为"配个 IP 就行",到后来老老实实啃 UG471 和 DPHY 规范,中间踩的坑足够写一本小册子。这篇文章不讲空泛的理论,只聊手动实现 MIPI DPHY 接收时最容易翻车的五个关键细节,每个都附带我的实际排查过程和最终方案。
如果你正在用 ZYNQ 做 MIPI 摄像头、显示屏或者其他高速差分信号的接收,并且打算走手动实现这条路,那这些经验应该能帮你省下不少调试时间。前提是你对 FPGA 的 SelectIO 原语、IDELAY、ISERDES 有基本了解,至少知道它们大概能干什么。如果完全没接触过,建议先翻一翻 UG471 的 SelectIO 章节再回来。
1. 时钟通道的 DDR 接收与字节对齐,远比想象中麻烦
MIPI DPHY 的时钟通道是 DDR 的,也就是说数据在时钟的上下沿都会翻转。很多新手在这里的第一个误区是:以为用一个普通的 IBUFDS 把差分时钟转成单端,然后直接当系统时钟用就行了。实际上,DPHY 的时钟通道频率等于数据通道的比特率除以 2,比如你跑 800Mbps 的 lane rate,时钟通道就是 400MHz。这个频率直接扔给 FPGA 的逻辑资源,时序压力非常大,而且你还需要对它做进一步的 MMCM 倍频来采样数据。
1.1 为什么不能直接把 DPHY 时钟当系统时钟
DPHY 时钟通道在 LP 模式下会进入低功耗状态,时钟会停止翻转。如果你把它直接接到 MMCM 或者 BUFG 上,一旦时钟停止,整个时钟网络就会挂掉,后续的复位和重新锁定会变得极其麻烦。我最初的做法就是直接把 IBUFDS 的输出接到 BUFG,结果摄像头一进入 LP 模式,FPGA 这边就彻底失联了,必须重新配置才能恢复。
正确的做法是:DPHY 时钟通道经过 IBUFDS 后,不直接进 BUFG,而是先送到一个 BUFIO 或者区域时钟缓冲,再通过 ISERDES 做 DDR 接收,把时钟的边沿信息转换成数据和使能信号。这样即使时钟停止,你也能通过状态机检测到 LP 状态,而不是让整个时钟树崩溃。
1.2 用 ISERDES 做时钟通道接收的具体配置
在 Vivado 中实例化 ISERDESE2 时,有几个参数必须注意。首先是DATA_RATE要设为DDR,DATA_WIDTH设为 4 或者 8,取决于你后续怎么处理。INTERFACE_TYPE设为NETWORKING还是MEMORY其实影响不大,但IOBDELAY建议设为IFD,这样你可以同时使用 IDELAY 和 ISERDES 的内部延迟。
ISERDESE2 #( .DATA_RATE("DDR"), .DATA_WIDTH(4), .INTERFACE_TYPE("NETWORKING"), .IOBDELAY("IFD"), .DYN_CLKDIV_INV_EN("FALSE"), .DYN_CLK_INV_EN("FALSE"), .NUM_CE(1), .OFB_USED("FALSE"), .SERDES_MODE("MASTER") ) iserdes_clk ( .Q1(q1), .Q2(q2), .Q3(q3), .Q4(q4), .SHIFTIN1(), .SHIFTIN2(), .SHIFTOUT1(), .SHIFTOUT2(), .BITSLIP(bitslip), .CE1(1'b1), .CE2(1'b1), .CLK(clk_hi), .CLKB(~clk_hi), .CLKDIV(clk_div), .D(d_clk_in), .DDLY(d_clk_delayed), .RST(rst), .OCLK(), .OCLKB() );这里clk_hi是 DPHY 时钟经过 BUFIO 后的高频时钟,clk_div是经过 BUFR 后的分频时钟。注意 BUFIO 和 BUFR 必须成对使用,且它们只能驱动同一时钟区域内的资源。如果你的 DPHY 时钟引脚和 ISERDES 不在同一个 bank,那就得换引脚或者用 BUFG 加 MMCM 的方案,但那样会引入额外的抖动。
1.3 字节对齐的 Bitslip 操作逻辑
ISERDES 输出的并行数据是乱序的,因为 DPHY 的时钟和数据之间没有固定的相位关系。你需要通过 BITSLIP 来调整对齐。具体做法是:发送一个已知的同步序列,比如 DPHY 规范里的 Sync 码 0xB8,然后不断执行 BITSLIP,直到接收到的数据等于 0xB8。
我通常的做法是写一个状态机,每次 BITSLIP 后检查一次数据,最多尝试 8 次。如果 8 次都没对齐,说明时钟或者数据有问题,需要检查 IDELAY 的延迟值。这里有个细节:BITSLIP 操作必须在 CLKDIV 时钟域下进行,而且每次 BITSLIP 后要等至少两个 CLKDIV 周期才能检查数据,否则可能采到不稳定的值。
注意:BITSLIP 不是随便滑的,它每次滑动一个比特位。如果你发现滑了 8 次还是不对,不要继续滑,而是回头检查 IDELAY 的 tap 值是否合适。我遇到过因为 IDELAY 延迟太大导致数据眼图完全关闭的情况,这时候再怎么滑都是白费。
2. IDELAY 的 tap 值不是拍脑袋定的,得算
IDELAY 是手动实现 MIPI DPHY 接收时最核心也最玄学的部分。它的作用是对输入的数据信号进行延迟调整,使得采样点落在数据眼图的中心。Xilinx 7 系列的 IDELAYE2 有 31 个 tap,每个 tap 的延迟大约是 78ps(具体值取决于器件速度和温度)。很多人调 IDELAY 就是随便写个值然后看能不能收到数据,这种做法在实验室可能碰巧能跑,但一到现场就各种不稳定。
2.1 先搞清楚你的数据眼图有多宽
假设你的 MIPI lane rate 是 800Mbps,那么一个比特周期是 1.25ns。理论上数据眼图的宽度就是 1.25ns 减去上升下降时间。实际上由于 PCB 走线、连接器、ESD 器件的影响,眼图会缩水很多。我的经验是,在 800Mbps 下,如果 PCB 设计得当,眼图宽度大概在 0.8ns 到 1.0ns 之间。IDELAY 的总延迟范围是 31 个 tap,按 78ps 算就是 2.4ns 左右,足够覆盖一个比特周期。
但问题是,你需要把采样点放在眼图中心。如果 IDELAY 的初始值设为 0,采样点就在数据跳变沿附近,误码率会很高。所以你需要扫描 IDELAY 的 tap 值,找到误码率最低的那个点。
2.2 实际扫描 IDELAY 的方法
我通常会在 FPGA 里做一个简单的误码率测试逻辑:发送端持续发送已知的伪随机序列,接收端统计错误比特数。然后通过 AXI 或者 UART 接口,逐个 tap 值进行测试,每个 tap 值跑个几秒钟,记录误码数。
// 简化的 IDELAY 控制逻辑 IDELAYE2 #( .IDELAY_TYPE("VAR"), .IDELAY_VALUE(0), .DELAY_SRC("IDATAIN"), .SIGNAL_PATTERN("DATA"), .REFCLK_FREQUENCY(200.0), .HIGH_PERFORMANCE_MODE("TRUE") ) idelay_data ( .CNTVALUEOUT(cntvalueout), .DATAOUT(data_delayed), .C(data_clk), .CE(ce), .INC(inc), .IDATAIN(data_in), .LD(ld), .LDPIPEEN(1'b0), .REGRST(1'b0) );扫描的时候,从 tap 0 开始,每次加 1,记录误码数。你会看到一个类似浴盆曲线的结果:tap 值太小或太大时误码率高,中间有一段误码率为零的区域。取这个区域的中心点作为最终的 tap 值。
2.3 温度漂移和电压变化的影响
IDELAY 的延迟值会随温度和电压变化。Xilinx 的 IDELAYE2 有一个 REFCLK 输入,通常接 200MHz 的参考时钟,用来补偿 PVT 变化。但即便如此,在极端温度下,延迟值还是会有几个 tap 的漂移。如果你的应用场景温度变化很大,建议在系统运行过程中定期重新扫描 IDELAY,或者至少留出足够的余量。
我做过一个车载项目,夏天车内温度能到 70 度,冬天零下 20 度。实验室调好的 tap 值,到了现场就偶尔出误码。后来改成每次上电都重新扫描一次 IDELAY,问题才解决。虽然麻烦,但可靠性上去了。
提示:IDELAYE2 的 HIGH_PERFORMANCE_MODE 设为 TRUE 时,延迟值的线性度更好,但功耗会略高。如果你的系统对功耗不敏感,建议开启。
3. ISERDES 的位宽和时钟分频比,必须和 DPHY 的 lane 速率匹配
ISERDES 的 DATA_WIDTH 和时钟分频比决定了你最终输出的并行数据宽度。这个参数如果设错了,轻则数据错位,重则完全收不到数据。很多人在这里犯的错误是:只根据 FPGA 的时钟频率去算分频比,而忽略了 DPHY 的 lane rate 和时钟通道频率之间的关系。
3.1 从 lane rate 反推 ISERDES 配置
假设你的 MIPI 摄像头输出 2 个 lane,每个 lane 的速率是 800Mbps,时钟通道是 400MHz。那么对于每个数据 lane,ISERDES 需要以 800Mbps 的速率采样。如果你用 ISERDES 的 DDR 模式,那么 ISERDES 的 CLK 频率就是 400MHz,CLKDIV 频率就是 400MHz 除以 DATA_WIDTH。
如果你设 DATA_WIDTH 为 8,那么 CLKDIV 就是 50MHz。这个频率对于 FPGA 的逻辑资源来说很友好,但你需要确保 MMCM 能同时产生 400MHz 和 50MHz 的时钟,并且它们之间的相位关系是确定的。
3.2 时钟分频比的计算表格
| Lane Rate (Mbps) | 时钟通道频率 (MHz) | ISERDES DATA_WIDTH | CLKDIV 频率 (MHz) | 备注 |
|---|---|---|---|---|
| 400 | 200 | 4 | 50 | 低速场景,逻辑资源压力小 |
| 800 | 400 | 8 | 50 | 最常用的配置 |
| 1000 | 500 | 8 | 62.5 | 需要 MMCM 支持非整数分频 |
| 1200 | 600 | 8 | 75 | 高速场景,时序压力大 |
| 1500 | 750 | 8 | 93.75 | 需要仔细约束时序 |
这个表格里的 CLKDIV 频率是我实际项目中用过的,基本覆盖了常见的 MIPI 摄像头速率。注意 1000Mbps 和 1500Mbps 这两行,CLKDIV 频率不是整数,MMCM 配置起来会稍微麻烦一点,但 Vivado 的 Clocking Wizard 可以处理。
3.3 多 lane 之间的 skew 问题
如果你的 MIPI 有多个 lane,比如 2 lane 或者 4 lane,那么每个 lane 的 ISERDES 都需要独立的 IDELAY 和 BITSLIP。但问题是,不同 lane 之间的走线长度不可能完全一致,导致它们之间的 skew 可能达到几百皮秒。这个 skew 如果不处理,会导致多 lane 数据拼接时出错。
我的做法是:每个 lane 独立做 IDELAY 扫描和 BITSLIP 对齐,然后在数字域里用一个简单的 FIFO 或者移位寄存器来补偿 lane 之间的 skew。具体补偿多少,可以通过发送一个已知的多 lane 同步序列来测量。比如 DPHY 规范里的 Sync 码在每个 lane 上同时发送,你可以在 FPGA 里比较不同 lane 收到 Sync 码的时间差,然后调整 FIFO 的读延迟。
4. LP 状态检测和高速模式切换,状态机不能写得太简单
MIPI DPHY 有 LP 和 HS 两种模式。LP 模式下,信号是单端的,电平是 0V 到 1.2V;HS 模式下,信号是差分的,共模电压是 200mV,差分摆幅是 200mV。FPGA 的 SelectIO 资源需要能够在这两种模式之间切换,而且切换的时机必须准确,否则会丢失数据或者误触发。
4.1 LP 模式的电平检测
在 LP 模式下,DPHY 的每条 lane 有两根线,通常称为 P 和 N。LP 状态有四种组合:LP-00、LP-01、LP-10、LP-11。其中 LP-11 是空闲状态,LP-00 是进入 HS 模式的前导码的一部分。FPGA 需要能够检测这些状态,通常用普通的 IBUF 加上比较器来实现。
但这里有个坑:FPGA 的 IO 标准必须支持 1.2V 的 LVCMOS 或者类似的标准。如果你用的是 HP bank,VCCO 必须设为 1.2V 或者更低。如果是 HR bank,VCCO 可以设为 1.2V 到 3.3V 之间的值,但需要确认输入阈值是否满足要求。我遇到过因为 VCCO 设成了 1.8V,导致 LP 模式的 1.2V 电平被误判为低电平的情况。
4.2 HS 模式的使能时序
从 LP 模式切换到 HS 模式时,DPHY 会先发送一个 LP-00 到 LP-01 到 LP-00 的序列,然后进入 HS 模式。FPGA 需要在检测到这个序列后,及时使能 ISERDES 和 IDELAY,并且切换到 HS 模式的差分输入。这个切换的延迟必须控制在一个比特周期以内,否则会丢失 HS 模式的前几个比特。
我的做法是用一个高速状态机,时钟用 DPHY 时钟通道的 LP 模式下的自由振荡时钟,或者用一个独立的 200MHz 时钟。状态机检测到 LP-00 序列后,立即拉高 HS_EN 信号,这个信号控制 ISERDES 的 RST 和 IDELAY 的 LD。同时,IO 的差分输入使能也要切换。
4.3 状态机的常见错误
最常见的错误是状态机在 LP 模式下没有正确复位,导致进入 HS 模式时 ISERDES 还处于复位状态。另一个错误是 HS_EN 信号的时序不对,太早或太晚。太早会导致 ISERDES 在数据还没稳定时就开始采样,太晚则会丢失数据。
我建议在状态机里加一个计数器,对 LP-00 序列的长度进行计数。如果 LP-00 持续的时间超过某个阈值,才认为是有效的 HS 进入请求。这样可以避免噪声或者毛刺误触发。
注意:LP 模式的信号速率虽然低,但也不能忽略。有些摄像头的 LP 模式信号速率能达到 10Mbps 以上,如果你的状态机时钟太慢,可能会漏掉一些状态变化。
5. PCB 走线和端接电阻,硬件上的坑比逻辑上的更难填
说了这么多逻辑上的细节,但很多时候问题根本不在 FPGA 内部,而在 PCB 上。MIPI DPHY 的 HS 模式差分信号对走线要求很高,如果走线阻抗不匹配、端接电阻不对、或者走线长度差异太大,逻辑上再怎么调都是白搭。
5.1 差分走线的阻抗控制
MIPI DPHY 的差分阻抗要求是 100 欧姆。如果你的 PCB 走线阻抗是 90 欧姆或者 110 欧姆,信号反射会导致眼图恶化。我见过一个项目,因为 PCB 厂家把差分阻抗做成了 85 欧姆,导致 800Mbps 下误码率居高不下,最后只能降速到 400Mbps 才能稳定工作。
差分走线的阻抗取决于线宽、线间距、介质厚度和介电常数。在画 PCB 的时候,一定要让 PCB 厂家提供阻抗计算报告,并且在制板后用 TDR 实测确认。如果阻抗偏差超过 10%,建议重新设计叠层。
5.2 端接电阻的位置和阻值
MIPI DPHY 的接收端通常需要端接电阻,一般是 100 欧姆的差分端接。有些 FPGA 的 SelectIO 内部有可配置的端接电阻,但精度和线性度可能不如外部电阻。如果走线比较长,建议使用外部端接电阻,并且尽量靠近 FPGA 的接收引脚。
端接电阻的阻值不是随便选的。如果阻值偏大,信号幅度会变小,眼图高度降低;如果阻值偏小,信号幅度变大,但功耗增加,而且可能超出 FPGA 输入的共模范围。我通常会用 100 欧姆的差分端接,然后在调试时用示波器观察信号幅度,必要时微调。
5.3 走线长度匹配和过孔处理
多 lane 之间的走线长度差异要控制在 5mil 以内,最好是 2mil 以内。这个要求听起来很苛刻,但对于 800Mbps 以上的速率来说是必须的。如果走线长度差异太大,lane 之间的 skew 会超过一个比特周期,导致数据无法对齐。
过孔也是一个大问题。每个过孔都会引入寄生电容和电感,导致阻抗不连续。如果差分对必须换层,建议在过孔旁边加接地过孔,并且尽量让两根线对称换层。我见过一个设计,因为差分对换层时不对称,导致共模噪声很大,最后不得不重新改板。
5.4 电源和地的处理
MIPI DPHY 的 HS 模式信号摆幅很小,只有 200mV 差分。如果电源或者地平面噪声太大,很容易淹没信号。建议在 FPGA 的 IO 电源引脚旁边放足够多的去耦电容,并且确保地平面完整。如果可能的话,给 MIPI 接收部分单独用一个 LDO 供电,避免被其他数字电路干扰。
我在一个项目里遇到过因为 DDR 内存的开关噪声耦合到 MIPI 电源上,导致误码率随内存访问模式变化的情况。后来在 MIPI 电源上加了 LC 滤波,问题才解决。所以如果你发现误码率和某些数字电路的活动相关,不妨查一查电源噪声。
6. 调试手段和工具链的配合,别只盯着 Vivado 的 ILA
手动实现 MIPI DPHY 接收,调试手段非常关键。Vivado 的 ILA 很好用,但它只能看数字信号,看不到模拟层面的问题。很多时候你需要结合示波器、协议分析仪和逻辑分析仪一起排查。
6.1 用示波器看眼图和信号质量
在调试初期,我强烈建议用高带宽示波器(至少 1GHz 以上)观察 MIPI 差分信号的眼图。通过眼图你可以直观地看到信号幅度、上升下降时间、抖动和噪声。如果眼图已经闭合了,那逻辑上再怎么调都没用,必须先解决硬件问题。
示波器的探头也很重要。普通的无源探头带宽不够,而且寄生电容大,会严重影响信号。建议使用差分有源探头,带宽至少是信号速率的 2 倍以上。比如 800Mbps 的信号,探头带宽至少 1.6GHz。
6.2 ILA 的触发和采样深度设置
当硬件信号没问题后,就可以用 ILA 来调试逻辑了。ILA 的采样深度和触发条件设置很关键。对于 MIPI DPHY 接收,我通常会设置触发条件为:检测到 LP-00 序列后,捕获 HS 模式的前几百个比特。这样可以看到 HS 模式进入时的时序是否正确。
ILA 的采样时钟要用 CLKDIV 时钟,而不是高速时钟。因为高速时钟频率太高,ILA 的采样深度会非常有限。用 CLKDIV 时钟可以捕获更多的数据,而且对于分析字节对齐和状态机跳转来说足够了。
6.3 协议分析仪的辅助作用
如果你手头有 MIPI 协议分析仪,那调试会轻松很多。协议分析仪可以直接解码 MIPI 的 CSI-2 或者 DSI 协议,告诉你每一帧的数据内容。这样你可以对比 FPGA 接收到的数据和协议分析仪解码的数据,快速定位是物理层的问题还是协议层的问题。
不过协议分析仪通常比较贵,不是每个团队都有。如果没有,也可以用一个已知的 MIPI 信号源,比如 FPGA 自己产生一个简单的 MIPI 信号,然后自己接收,形成一个闭环测试。这样可以排除摄像头或者显示屏的问题,专注于 FPGA 接收逻辑的调试。
6.4 常见问题排查表格
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 完全收不到数据 | 时钟通道没起来 | 用示波器检查 DPHY 时钟是否有翻转 |
| 数据偶尔出错 | IDELAY tap 值不合适 | 扫描 IDELAY,找误码率最低点 |
| 多 lane 数据错位 | lane 之间 skew 太大 | 检查 PCB 走线长度,调整 FIFO 延迟 |
| LP 模式检测不到 | IO 标准或 VCCO 设置错误 | 检查 bank 电压和 IO 标准 |
| HS 模式进入失败 | 状态机时序不对 | 用 ILA 抓 LP-00 到 HS 的切换过程 |
| 误码率随温度变化 | IDELAY 温漂 | 定期重新扫描 IDELAY 或留足余量 |
这个表格是我在实际项目中总结的,基本上覆盖了 80% 的常见问题。每次遇到新问题,我都会先对照这个表格排查一遍,往往能快速定位方向。
7. 一些零散但重要的经验补充
除了上面五个关键细节,还有一些零散的经验值得分享。这些经验不一定适用于所有项目,但至少在我做过的项目里都发挥过作用。
7.1 复位策略要统一
MIPI DPHY 接收逻辑涉及多个时钟域:LP 模式检测时钟、HS 模式的高速时钟、CLKDIV 时钟、以及 AXI 或者系统时钟。复位信号必须同步到每个时钟域,否则会出现亚稳态或者复位释放不同步的问题。我通常会用一个小模块,把外部复位同步到各个时钟域,并且确保复位释放的顺序是:先释放高速时钟域,再释放 CLKDIV 域,最后释放系统域。
7.2 时钟切换的毛刺问题
从 LP 模式切换到 HS 模式时,时钟源可能会切换。如果切换过程中产生毛刺,会导致 ISERDES 误触发。建议使用 BUFGMUX 或者 BUFGCTRL 来做时钟切换,并且确保切换时两个时钟都是稳定的。如果可能的话,尽量让 LP 和 HS 模式使用同一个时钟源,只是分频比不同,这样可以避免切换带来的问题。
7.3 仿真验证不能省
虽然 MIPI DPHY 的仿真模型不太好找,但至少可以对 ISERDES 和 IDELAY 的行为进行仿真。Xilinx 提供了这些原语的仿真模型,你可以写一个简单的测试平台,模拟 DPHY 的时钟和数据输入,验证你的接收逻辑是否正确。仿真虽然不能完全替代实测,但可以帮你排除很多逻辑上的低级错误。
7.4 代码的可配置性
MIPI 的 lane rate 和 lane 数量可能因项目而异。建议把 ISERDES 的 DATA_WIDTH、IDELAY 的 tap 值、时钟分频比等参数做成可配置的,通过 AXI 寄存器或者参数化模块来设置。这样同一个代码框架可以复用到不同的项目,省去很多重复劳动。
7.5 和摄像头厂商的沟通
如果你用的是某个特定型号的摄像头,建议直接找厂商要一份 DPHY 的时序参数,包括 LP 模式的电平范围、HS 模式的进入时序、时钟通道的抖动要求等。这些参数在数据手册里可能写得不详细,但厂商的 FAE 通常能提供更具体的信息。我遇到过因为摄像头厂商的 LP 模式电平和标准略有差异,导致 FPGA 检测不到的情况,后来找 FAE 确认了实际电平范围,调整了比较器阈值才解决。
7.6 留足调试余量
最后一点,也是最重要的一点:留足调试余量。无论是 IDELAY 的 tap 值、时钟的相位、还是 PCB 的走线长度,都不要卡在临界值上。IDELAY 的 tap 值要选在浴盆曲线的中心,而不是边缘;时钟相位要留出至少 90 度的余量;PCB 走线长度差异要控制在规格的一半以内。这样即使温度、电压或者批次有变化,系统仍然能稳定工作。
我在一个量产项目里,因为 IDELAY 的 tap 值选得太靠近边缘,导致小批量试产时有 5% 的板子出现误码。后来把 tap 值往中心调了 3 个 tap,良率直接到了 100%。这个教训让我明白,调试的时候不能只追求"能跑",还要追求"跑得稳"。
手动实现 MIPI DPHY 接收确实比用现成的 IP 核麻烦很多,但好处是你对每一个环节都有完全的控制权,遇到问题也知道从哪里下手。而且一旦调通,这套逻辑可以复用到不同的项目和不同的速率上,长期来看是值得的。希望这些经验能帮你少走一些弯路。