4. MIPI D-PHY物理层信号质量测试与眼图分析

任务目标

用高速示波器采集 MIPI D-PHY 的差分时钟/数据对,重建眼图并量化眼高、眼宽、抖动,再根据结果调整 SoC 端 D-PHY 驱动能力(drive strength)与预加重,直到眼图满足信号完整性要求。本案例给出可复现的测量脚本与驱动参数调整代码。

环境与依赖

  • 示波器:带宽 ≥ 2 GHz,支持差分探头与眼图/模板测试(如 Keysight、Tektronix、R&S)。
  • 探头:差分探头,带宽 ≥ 2 GHz,负载电容尽量小。
  • 被测板:MIPI CSI-2 摄像头模组 + SoC,D-PHY 工作在 HS 模式(如 1.5 Gbps/lane)。
  • 软件:Python 3.8+,numpy、matplotlib;示波器支持 SCPI 远程控制(VISA)。

测量与眼图生成流程

D-PHY 差分对 CLK / DATA 差分探头 ≥2GHz 高速示波器 SCPI 采集 眼图重建 眼高/眼宽/抖动 驱动能力 参数调整 迭代:眼图不达标 → 调整驱动能力 → 重新测量

核心代码:眼图重建与指标评估

下面脚本从示波器导出的 CSV(时间、差分电压)重建眼图,计算眼高、眼宽与峰峰抖动,并给出是否满足 D-PHY HS 眼图模板的判定。CSV 由示波器 SCPI 采集后导出,每行 time,voltage。

import numpy as np
import matplotlib.pyplot as plt

# ---- 参数 ----
UI = 1.0 / 1.5e9          # 单位间隔,1.5 Gbps -> 666.7 ps
EYE_SPAN = 2 * UI         # 眼图窗口
BINS = 400

def load_waveform(path):
    data = np.loadtxt(path, delimiter=",", skiprows=1)
    return data[:, 0], data[:, 1]

def build_eye(t, v, ui, span):
    """按 UI 折叠波形,重建眼图矩阵。"""
    t0 = t[0]
    phase = ((t - t0) % ui) / ui          # 归一化相位 [0,1)
    # 每个 UI 内采样点映射到眼图横轴
    x = (phase * ui) / span               # 0~0.5 对应一个 UI
    # 用两个 UI 拼接成完整眼图
    x = np.where(x < 0.5, x, x - 0.5)
    hist, xe, ye = np.histogram2d(x, v, bins=[BINS, BINS])
    return hist, xe, ye

def eye_metrics(hist, xe, ye):
    """从眼图直方图提取眼高、眼宽。"""
    # 眼高:中心相位处电压分布的最大间隙
    center = BINS // 2
    col = hist[:, center]
    occupied = np.where(col > 0)[0]
    if len(occupied) < 2:
        return 0.0, 0.0
    v_low = ye[occupied[0]]
    v_high = ye[occupied[-1]]
    # 找中间最大空白区
    gaps = np.diff(occupied)
    if len(gaps) == 0:
        return 0.0, 0.0
    gi = np.argmax(gaps)
    eye_h = ye[occupied[gi + 1]] - ye[occupied[gi]]
    # 眼宽:中心电压处时间分布的最大间隙
    vc = BINS // 2
    row = hist[vc, :]
    occ = np.where(row > 0)[0]
    if len(occ) < 2:
        return eye_h, 0.0
    gaps_t = np.diff(occ)
    ti = np.argmax(gaps_t)
    eye_w = xe[occ[ti + 1]] - xe[occ[ti]]
    return eye_h, eye_w

def jitter_pp(t, v, ui):
    """用过零点估计峰峰抖动。"""
    cross = np.where(np.diff(np.sign(v)) != 0)[0]
    if len(cross) < 2:
        return 0.0
    tc = t[cross]
    # 相对最近 UI 边界的偏差
    phase = (tc % ui) / ui
    dev = np.abs(phase - np.round(phase))
    return dev.max() * ui

if __name__ == "__main__":
    t, v = load_waveform("dphy_hs.csv")
    hist, xe, ye = build_eye(t, v, UI, EYE_SPAN)
    eye_h, eye_w = eye_metrics(hist, xe, ye)
    jit = jitter_pp(t, v, UI)

    print(f"眼高 = {eye_h*1000:.1f} mV")
    print(f"眼宽 = {eye_w*1e12:.1f} ps")
    print(f"峰峰抖动 = {jit*1e12:.1f} ps")

    # D-PHY HS 眼图模板(简化):眼高 > 100 mV,眼宽 > 0.3 UI
    ok = eye_h > 0.1 and eye_w > 0.3 * UI
    print("眼图判定:", "PASS" if ok else "FAIL")

    plt.imshow(hist.T, origin="lower", aspect="auto",
               extent=[xe[0], xe[-1], ye[0], ye[-1]], cmap="hot")
    plt.xlabel("Phase (UI)"); plt.ylabel("Differential Voltage (V)")
    plt.title("MIPI D-PHY Eye Diagram")
    plt.colorbar(label="Hit Count")
    plt.tight_layout(); plt.savefig("eye.png", dpi=150)

驱动能力参数调整

眼图不达标时,通过 SoC 的 D-PHY 寄存器调整驱动能力与预加重。下面给出一个可复现的寄存器写入示例(以常见 Linux I2C/regmap 接口为例),并说明如何与上面的测量脚本配合迭代。

# 假设 SoC D-PHY 驱动能力寄存器地址 0x1A,位[3:0] 为 drive strength
# 位[7:4] 为 pre-emphasis,取值 0~15
DRV_REG = 0x1A

def set_drive_strength(bus, addr, strength, pre_emph=0):
    """strength: 0~15, pre_emph: 0~15"""
    val = (pre_emph << 4) | (strength & 0x0F)
    bus.write_byte_data(addr, DRV_REG, val)
    print(f"写入 0x{val:02X}: drive={strength}, pre_emph={pre_emph}")

# 迭代流程:
# 1. 初始 drive=8, pre_emph=0
# 2. 运行眼图脚本,若 FAIL 则增大 drive 或 pre_emph
# 3. 重复直到 PASS

运行与验证

  1. 用差分探头连接 D-PHY 时钟/数据对,示波器设置 HS 模式触发,采集至少 1 万个 UI 的波形。
  2. 导出 CSV 为 dphy_hs.csv(两列:time, voltage)。
  3. 运行 python eye_analysis.py,观察输出的眼高、眼宽、抖动与 PASS/FAIL。
  4. 若 FAIL,调用 set_drive_strength 调整驱动能力,重新采集并运行脚本,直到 PASS。
  5. 通过条件:眼高 > 100 mV,眼宽 > 0.3 UI,峰峰抖动 < 0.2 UI,且眼图无闭合。

常见问题

  • 眼图闭合:先检查探头接地与端接,再增大驱动能力或预加重。
  • 抖动偏大:确认参考时钟质量,必要时启用 SSC 或调整 PLL 带宽。
  • CSV 时间列单位:确保与脚本中 UI 计算一致(秒)。