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)。
测量与眼图生成流程
核心代码:眼图重建与指标评估
下面脚本从示波器导出的 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
运行与验证
- 用差分探头连接 D-PHY 时钟/数据对,示波器设置 HS 模式触发,采集至少 1 万个 UI 的波形。
- 导出 CSV 为
dphy_hs.csv(两列:time, voltage)。 - 运行
python eye_analysis.py,观察输出的眼高、眼宽、抖动与 PASS/FAIL。 - 若 FAIL,调用
set_drive_strength调整驱动能力,重新采集并运行脚本,直到 PASS。 - 通过条件:眼高 > 100 mV,眼宽 > 0.3 UI,峰峰抖动 < 0.2 UI,且眼图无闭合。
常见问题
- 眼图闭合:先检查探头接地与端接,再增大驱动能力或预加重。
- 抖动偏大:确认参考时钟质量,必要时启用 SSC 或调整 PLL 带宽。
- CSV 时间列单位:确保与脚本中 UI 计算一致(秒)。