简介:面向FPGA与高速数字设计学习者的DDR3仿真实战教程包,围绕V7架构平台讲解如何借助Ibis模型和Sigrity完成DDR3接口的信号完整性分析。内容从PCB布局、走线匹配与原理图连接入手,覆盖仿真时间范围、采样频率、电源地平面、激励源设置,以及眼图、抖动、上升下降时间和串扰等结果判读,并延伸到布局拓扑与I/O缓冲参数的迭代优化,适合希望系统掌握高速内存仿真流程、补齐SI分析思路的初中级工程师与学生。压缩包共100个文件,约23.9MB,包含brd布局文件、ibs模型、cfg仿真配置、spc与dat结果数据、pdf说明和log日志等,便于按仿真准备、运行与结果核查逐步查阅。已有3175人学习,可作为DDR3仿真入门与实操对照资料,帮助读者理解时序和电压阈值要求,形成从建模、参数设置到结果优化的完整分析路径。
1. 一块 DDR3-1600 的板子被打回两次:信号完整性仿真到底在验证什么
某开发者拿着一块八层板来找我,DDR3-1600,颗粒贴在控制器背面,等长做到 ±5mil,电源分割也画了。打样回来常温跑 memtest 一整天不出错,进高低温箱到 65℃ 开始偶发读写失败。示波器抓 DQ 眼图,看着是开的,可一算建立保持余量只剩十几皮秒,规范要求的一半都不到。DDR3 的时序裕量在布线定型那一刻基本就固定了,后期靠改电容、调 ODT 能救回来的余量非常有限。
DDR3 信号完整性仿真配合 IBIS 模型要回答的其实就三件事:眼图开不开、时序余量够不够、串扰和同步开关噪声吃掉多少。这份手把手流程从模型校验、叠层拓扑,写到写读时序、地址命令总线和批处理扫描,新手能照着复现一遍,熟手能对着参数边界挑毛病。
2. IBIS 模型到手先别急着跑:三个检查点和一张叠层表
拿到颗粒厂和控制器厂给的两个 .ibs 文件,很多人的第一反应是直接拖进仿真器点 Run。我在头两年也这么干过,结果一次仿真跑出来的眼图漂亮得离谱,投板后实测眼高只有仿真值的六成。后来才发现模型里 Package 的 RLC 是空的,封装寄生完全没算进去。模型校验这一步花半小时,能省掉后面几天的返工。
2.1 先看 IBIS 的五个字段,别被“能加载”骗了
仿真器能加载模型,只说明语法没大错,不代表模型能用。DDR3 场景下我固定看这几项:
| 字段 | 含义 | DDR3 场景下要看什么 |
|---|---|---|
| [Model] Type | 模型类型 | 控制器侧是 Output,颗粒侧是 Bidirectional 或 Input;类型选错,方向直接反 |
| [Pullup]/[Pulldown] | I/V 曲线 | 单调性、是否过零点、Typ/Min/Max 三个角的差异是否合理 |
| [Ramp] | 上升下降斜率 | 换算成 tr/tf 后和 datasheet 对一遍,差太多说明模型是估的 |
| [C_comp] | 芯片焊盘电容 | 影响回波和边沿,DDR3 的 DQ 一般在 1~2pF 量级 |
| [Package] 或 [Pin] 的 RLC | 封装寄生 | 空的时候仿真结果偏乐观,这一点最容易漏 |
| [Model Selector] | 多档驱动/ODT | 有几个档位就得扫几个档位,不能只跑默认那一个 |
一段典型的 DDR3 DQ 模型长这样,字段含义我标在注释里:
[Model] DDR3_DQ_ODT60 Model_type I/O Vinl 0.675 # 0.9 * Vref,SSTL-15 的输入低门限 Vinh 0.825 # 1.1 * Vref C_comp 1.80pF 1.65pF 2.10pF # typ / min / max 三列 [Pullup] | voltage I(typ) I(min) I(max) -1.5 -32.1m -28.4m -35.9m -0.75 -18.6m -16.1m -20.8m 0.0 0.0 0.0 0.0 0.75 18.9m 16.3m 21.2m 1.5 33.5m 29.7m 37.1m表格里是示意数值,实际以厂商模型为准。这里要盯的是 0.0V 那一行:Pullup 和 Pulldown 在 0V 处的电流都应该接近 0,如果不为 0,说明模型内部把某个直流偏置算进去了,仿真出来的共模电平会整体漂移。
I/V 曲线的单调性可以用脚本批量查,比人眼看快得多:
import re def parse_iv(text, keyword): """抓取 [Pullup] / [Pulldown] 段里的 I/V 表""" m = re.search(r'\[%s\](.*?)(?=\n\s*\[|\Z)' % keyword, text, re.S) if not m: return [] rows = [] for line in m.group(1).splitlines(): line = line.strip() if not line or line.startswith('|'): continue parts = line.split() if len(parts) >= 2: try: rows.append((float(parts[0]), float(parts[1].rstrip('m')))) except ValueError: continue return rows def check_curve(rows, name): """相邻段斜率变号即视为非单调,返回变号次数""" if len(rows) < 3: print(f'{name}: 数据点不足,跳过') return slopes = [] for (v0, i0), (v1, i1) in zip(rows, rows[1:]): dv = v1 - v0 if abs(dv) < 1e-12: continue slopes.append((i0 - i1) / dv) flips = sum(1 for a, b in zip(slopes, slopes[1:]) if a * b < 0 and abs(b) > 1e-6) print(f'{name}: {len(rows)} 个点,斜率变号 {flips} 次') if flips: print(' -> 曲线非单调,SPICE 可能收敛困难或出现假振铃,回查厂商模型') with open('ddr3_dq.ibs', 'r', errors='ignore') as f: text = f.read() for kw in ('Pullup', 'Pulldown'): check_curve(parse_iv(text, kw), kw)parse_iv用正则按[关键字]切段,遇到下一个[开头就停,避免把后面的 [Ramp] 也吃进来。电流值末尾的m是毫安,需要剥掉再转 float。check_curve判的是斜率变号,一两次轻微变号属于模型量化误差,连续多次变号基本可以判定模型有问题。这套脚本我一般放在模型入库前跑一遍,三十秒出结果,比仿真跑完发现波形发散再回头查省事得多。
2.2 叠层与阻抗:把仿真的边界条件固定下来
仿真结果的可信度,一半取决于叠层参数填得对不对。DDR3 单端走线一般控 50Ω±10%,差分对(CK/CK#、DQS/DQS#)控 100Ω±10%。我习惯先把叠层整理成一张表再动手:
| 层 | 用途 | 介质厚度 | 介电常数 | 备注 |
|---|---|---|---|---|
| L1 | 信号(DQ/DQS) | 3.5mil | 4.2 | 表层微带,阻抗敏感度高 |
| L2 | GND 完整平面 | — | — | 不许开槽,DQ 的参考层 |
| L3 | 信号(ADDR/CMD) | 4.0mil | 4.4 | 带状线,两侧都有参考 |
| L4 | 电源 1.5V | — | — | 与 L3 之间介质要够厚 |
| L5 | 电源 0.75V VTT | — | — | VTT 平面要能承住端接电流 |
介电常数别直接用板厂给的 4.2 就完事,频率高了以后 Dk 会往下掉,Df 会往上走。DDR3-1600 的奈奎斯特频率在 800MHz 附近,常见做法是按 1GHz 点的 Dk/Df 填,或者用板厂提供的宽频模型拟合。我用过几次只填单一 Dk 的做法,结果眼图宽度比实测宽了约 8%,后面的裕量评估就全飘了。
还有一个容易糊弄的地方是过孔。DQ 换层过孔的 stub 在 1600MT/s 下已经开始咬人,仿真里要么把过孔建成短传输线,要么至少在叠层里把焊盘电容和反焊盘直径写进去。完全忽略过孔,跑出来的眼图是理想值,没有参考意义。
2.3 拓扑与端接:Fly-by、ODT 和 VTT 怎么落到网表里
DDR3 的地址、命令、控制、时钟走 Fly-by 菊花链,末端用 VTT(0.75V)端接;数据线 DQ/DQS 是点对点,控制器到颗粒一对一。这个拓扑在仿真里必须如实建出来,尤其是多颗粒的地址总线,中间那颗颗粒的支线 stub 长度会直接影响反射。
ODT 配置是最容易拍脑袋的部分。颗粒侧的 Rtt_nom 有 40/60/120Ω 几档,Rtt_wr 有 60/120Ω 几档,控制器侧的驱动阻抗也有 34/40/48Ω 之类的分档。我一般的做法是先按下面的组合建一个扫描矩阵,让仿真去挑,而不是凭经验拍一个:
| 场景 | 控制器驱动 | 颗粒 Rtt_nom | 颗粒 Rtt_wr | 说明 |
|---|---|---|---|---|
| 写操作 | 34Ω | 关闭 | 60Ω | 写的时候用动态 ODT,减少反射 |
| 读操作 | 高阻 | 60Ω | 关闭 | 读的时候控制器端接,颗粒输出 |
| 空闲 | 高阻 | 120Ω | 关闭 | 降低静态功耗,抑制反射 |
| 地址总线 | 40Ω | — | — | 末端 VTT 75Ω 到 0.75V |
注意 Rtt_wr 生效的前提是模式寄存器里开了动态 ODT,仿真里如果只在颗粒模型上挂了个固定电阻,写时序的波形和实际会差一截。这一点我在一个项目上翻过车:仿真里全程用 60Ω 固定端接,实测波形在写突发的前两个 UI 有明显过冲,最后是靠打开动态 ODT 才把过冲压下去。
3. 写时序仿真:从拓扑建模到 DQ 眼图的完整链路
写时序是 DDR3 仿真里最常被要求出报告的一项。控制器把 DQ 和 DQS 一起发出去,DQS 和 DQ 中心对齐,颗粒在 DQS 的上下沿采样。整个链路里,控制器的驱动、走线的损耗与串扰、颗粒的输入电容和 ODT,任何一环没建模,眼图都不是真实的眼图。
3.1 激励码型:PRBS 不够,得用真实业务码型
PRBS 适合看通道的宽带响应,但 DDR3 的 DQ 是一组八根线同时翻转,最坏情况往往出现在特定的码型组合上。我一般准备三组激励:
- 全翻转:0x55/0xAA 交替,用来压 DQS 和 DQ 的边沿对齐;
- 走 1:0x01 循环左移,用来激发同一字节组内的串扰;
- 真实业务码型:从控制器侧抓一段实际写突发的数据,回灌到仿真里,用来验证 SSN。
仿真时长也要够。只跑十几个 UI 看不出低频的电源噪声累积,一般至少跑 200 个 UI 以上,地址总线因为有 Fly-by 的多点反射,最好跑到 500 个 UI。时间步长按 UI 的 1/50 取,DDR3-1600 的 UI 是 0.625ns,步长就是 12.5ps 左右,再细就是浪费算力。
3.2 采样点、Vref 与眼图掩模怎么定
写时序的采样点在颗粒端,采样时钟是 DQS。Vref 取 0.75V,也就是 VDD/2。这里有两个细节经常被搞错:
第一,DQS 是差分对,采样时刻应该取 DQS 和 DQS# 的交叉点,而不是 DQS 单端过 Vref 的时刻。差分交叉点对共模漂移不敏感,单端过零在共模偏移时会提前或滞后。
第二,眼图掩模要用 JEDEC 对应的速率档,不能拿一个通用模板套所有速率。DDR3-1333 和 DDR3-1600 的眼图模板在时间和电压两个维度上都不一样,拿错模板,本来不合格的波形会被判成合格。
我习惯在仿真输出里同时导出眼图和浴盆曲线。眼图看形状,浴盆曲线看误码率随采样点移动的退化速度。浴盆的开口宽度比眼宽更能反映真实的时序裕量,因为它把抖动算进去了。
3.3 从波形到裕量:把眼高眼宽换算成 tDS/tDH
仿真器自带的眼图测量有时候和 JEDEC 的定义对不上,尤其是眼高的算法,有的按最大最小值,有的按 1e-12 误码率外推。我一般自己写脚本复算一遍,保证报告里的数字是可追溯的:
import numpy as np import pandas as pd def write_timing_margin(csv_path, ui=0.625, vref=0.75): """csv 列: time, dq, dqs_p, dqs_n;返回眼高、眼宽和采样点数量""" df = pd.read_csv(csv_path) t = df['time'].values dq = df['dq'].values dqs = df['dqs_p'].values - df['dqs_n'].values # 差分交叉点 dt = t[1] - t[0] # DQS 差分上升沿穿越 0V 的时刻即颗粒采样时刻 idx = np.where((dqs[:-1] < 0) & (dqs[1:] >= 0))[0] samp_v = np.interp(t[idx], t, dq) # 眼高:采样点前后各 0.25UI 窗口内的电压摆幅,取最差 win = max(1, int(0.25 * ui / dt)) hi = [dq[max(0, i - win): i + win].max() for i in idx] lo = [dq[max(0, i - win): i + win].min() for i in idx] eye_h = np.min(np.array(hi) - np.array(lo)) # 眼宽:DQ 穿越 Vref 的相邻间隔最小值,粗略代表最差眼宽 cross = np.where((dq[:-1] - vref) * (dq[1:] - vref) < 0)[0] eye_w = np.min(np.diff(cross)) * dt if len(cross) > 1 else 0.0 tds = np.min(samp_v) - vref # 采样点相对 Vref 的最低电压余量 return dict(eye_h=eye_h, eye_w=eye_w, tds_mv=tds * 1000, n_sample=len(idx))ui按速率换,1600MT/s 是 0.625ns,1333 是 0.75ns。vref取 0.75V。win控制眼高统计窗口,取 0.25UI 是行业里比较常用的折中,太宽会把相邻 bit 的干扰算进来,太窄又体现不出抖动。眼宽这里用了最差的相邻穿越间隔,是个保守估计,严谨做法是累积多次仿真做浴盆,但日常筛查够用了。返回的tds_mv是电压维度的余量,时间维度的 tDS/tDH 还要结合 DQS 和 DQ 的相对位置再算一遍,公式是实测采样点到 DQ 跳变沿的距离减去规范值。
我一般把规范值做成一张表,脚本直接读,避免手抄:
| 参数 | 含义 | 取值来源 |
|---|---|---|
| tDS(base) | 数据建立时间 | 颗粒 datasheet 的 AC 时序表 |
| tDH(base) | 数据保持时间 | 同上 |
| tDQSS | DQS 相对 CK 的偏差 | 规范范围约 ±0.25 tCK |
| tDQSQ | DQ 相对 DQS 的偏斜 | 随速率变化,1600 下在百皮秒量级 |
| tQH | 读时序保持 | 颗粒输出有效窗口 |
derating 别忘,工作电压和温度偏离标称值时,tDS/tDH 要按 datasheet 的降额曲线修正,直接拿 base 值对比会虚高。
3.4 一个可复现的批处理流程
单点仿真跑通只是开始,真正花时间的是扫描。ODT 三档、驱动三档、三个工艺角,就是 27 组,手工点 27 次不现实。我一般用 shell 把参数塞进网表模板里批量跑:
#!/usr/bin/env bash # run_ddr3_sweep.sh —— ODT 与驱动强度的二维扫描 # 用法: ./run_ddr3_sweep.sh ddr3_top ./netlist set -euo pipefail PROJ=${1:?需要工程名} NETDIR=${2:-./netlist} OUT=./results mkdir -p "$OUT" for corner in typ min max; do for rtt in 40 60 120; do for drv in 34 40 48; do tag="${corner}_rtt${rtt}_drv${drv}" sed -e "s/__CORNER__/${corner}/g" \ -e "s/__RTT__/${rtt}/g" \ -e "s/__DRV__/${drv}/g" \ "$NETDIR/ddr3_top.sp" > "$OUT/$tag.sp" sim_cli -batch -f "$OUT/$tag.sp" -o "$OUT/$tag.csv" \ > "$OUT/$tag.log" 2>&1 echo "[done] $tag" done done doneset -euo pipefail保证任何一条命令失败就整体退出,不会出现“跑了 20 组,剩下 7 组静默失败”的情况。sed的三个占位符对应网表里的工艺角、端接电阻和驱动阻抗,网表模板里写成__RTT__这种形式,跑之前替换掉,原始模板不动。sim_cli换成你手上仿真器的批处理命令,不同工具的写法差别很大,有的用-b,有的要写配置文件,这一行按实际情况改。
跑完以后每个 tag 对应一个 csv 和一个 log。log 里要重点看两类信息:一是收敛警告,二是模型越界警告。出现收敛警告的结果不能直接用,得先调仿真器的收敛参数或者把时间步长再缩小。我吃过一次亏,27 组里有两组收敛失败但没报错,脚本照样往下走,最后汇总的时候才发现那两组的眼图是发散的。
4. 读时序与地址命令总线:四个容易搞反的方向和参数
写时序跑通以后,很多人会顺手把读时序也按同样的配置再跑一遍,然后发现结果怎么调都不对。读和写的方向是反的,采样点、端接、甚至参考电压的用法都不一样。
4.1 读时序的采样时钟在控制器端,方向反了全错
写是控制器发、颗粒收,采样点在颗粒端;读是颗粒发、控制器收,采样点在控制器端。这个方向一换,好几件事跟着变:
控制器端的接收模型要用 Input 类型,而不是 Output;端接从颗粒的 Rtt_wr 换成了控制器的 Rtt_nom 或者内部可变端接;DQS 的相位关系也从“控制器发出的 DQS 与 DQ 中心对齐”变成了“颗粒发出的 DQS 与 DQ 有固定偏斜”。
读时序里最关键的三个参数是 tDQSCK、tDQSQ 和 tQH。tDQSCK 是颗粒输出的 DQS 相对 CK 的延迟,规范给的是一个范围,不是一个固定值,仿真里如果只取 typ 值,读眼图会偏乐观。tDQSQ 是同一字节组内 DQ 相对 DQS 的最大偏斜,这个值和颗粒的制造工艺强相关,必须用 max 角的模型跑。tQH 是读数据的保持时间,容易被忽略,但它在高温低压角下会明显变差。
4.2 地址命令总线是单速率,但 SSN 最严重
地址、命令、控制总线跑的是单速率,DDR3-1600 下就是 800MT/s,看起来比 DQ 轻松。但这组线的扇出多、走线长、又走 Fly-by,同步开关噪声反而是整个接口里最严重的。
我一般这样处理:地址总线单独建一个仿真工程,把所有颗粒的输入模型都挂上,末端 VTT 端接建准,激励用“全 0 到全 1”的跳变,让所有位同时翻转,这是最坏情况。然后看中间那颗颗粒和末端那颗颗粒的波形差异,末端的过冲通常最大,中间的振铃最厉害。
VTT 端接的建模有个细节:VTT 不是一个理想电压源,它是有内阻的,常见做法是串一个几十毫欧到一百毫欧的电阻再接到 0.75V。如果直接接理想电压源,端接点的反射会被吸收得过于干净,仿真结果比实际好。
4.3 tDQSS、tDQSCK、tDQSQ 别混用
这三个参数名字像,含义完全不同,我在带新人的时候见过把它们写进同一张表里对比的:
| 参数 | 适用方向 | 含义 | 典型用法 |
|---|---|---|---|
| tDQSS | 写 | 控制器发出的 DQS 相对 CK 的偏差 | 判断 Write Leveling 是否收敛 |
| tDQSCK | 读 | 颗粒输出的 DQS 相对 CK 的延迟 | 读时序采样窗口的定位 |
| tDQSQ | 读写都有 | DQ 相对 DQS 的偏斜 | 眼图宽度的主要贡献项 |
tDQSS 是 DDR3 引入 Write Leveling 的直接原因。Fly-by 拓扑下,CK 到每颗颗粒的飞行时间不一样,控制器发出去的 DQS 到颗粒时,和 CK 的相位关系已经偏了。Write Leveling 就是让控制器逐颗调整 DQS 的延迟,直到颗粒采样到 CK 的跳变。仿真里如果不建 Write Leveling 的过程,直接给一个理想相位,写时序的结论是不成立的。
4.4 把规范值和仿真值对起来
报告里我一般放一张对照表,左列是 datasheet 的规范值,右列是仿真实测值,中间是余量。这张表比眼图更能说服评审:
| 项目 | 规范值 | 仿真值(max 角) | 余量 | 判定 |
|---|---|---|---|---|
| 写建立 tDS | 按 datasheet | 实测 | 实测减规范 | 通过/不通过 |
| 写保持 tDH | 按 datasheet | 实测 | 同上 | 通过/不通过 |
| 读 DQS 有效窗口 | 按 datasheet | 实测 | 同上 | 通过/不通过 |
| 地址建立 tIS | 按 datasheet | 实测 | 同上 | 通过/不通过 |
| 地址保持 tIH | 按 datasheet | 实测 | 同上 | 通过/不通过 |
余量我一般要求至少留 20%,低于这个数就得回头看是布线的问题还是端接配置的问题。有些团队会把余量压到 5% 以内还放行,除非你对模型精度非常有把握,否则不建议。
5. 避坑排查:仿真跑通但结论不可信的五个现场
仿真跑出波形不难,难的是波形能信。下面这五条是我这些年真实踩过或者帮别人排查过的,按“现象、原因、解决”写,遇到类似情况可以直接对照。
5.1 眼图很漂亮,实测眼高只有六成
现象:仿真报告眼高 600mV,投板后实测不到 400mV。
原因:IBIS 模型里的 [Package] 段是空的,封装 RLC 完全没算。另外走线的过孔也按理想直通处理了。
解决:先查模型文件里有没有 [Package] 或者 [Pin] 的 RLC 数据,没有就找厂商要带封装的版本;过孔至少在叠层里补上焊盘电容和反焊盘尺寸。补完重跑,眼高一般会掉 15%~25%,这个量级才是正常的。
5.2 只跑了 typ 角,结论写成“满足规范”
现象:报告只给了一组 typ 角的眼图,结论写着“时序裕量充足”。
原因:DDR3 的时序最差情况往往出现在 min 或 max 角,尤其是高温低压的组合,颗粒输出延迟变大,建立时间被吃掉。
解决:typ/min/max 三个角必须都跑,报告里给最差角。如果算力有限,至少跑 min 和 max 两个角,typ 只作为参考。
5.3 地址总线波形振荡,调端接怎么调都好不了
现象:地址总线末端颗粒的波形有很明显的高频振铃,改 VTT 电阻值、改驱动强度都没明显改善。
原因:Fly-by 拓扑下,中间颗粒的支线 stub 太长,形成了四分之一波长的谐振。这种情况改端接没用,问题在拓扑。
解决:缩短支线 stub,或者把颗粒的菊花链顺序调一下,让 stub 最短的那颗放在中间。仿真里可以先扫一遍 stub 长度,找到振铃最小的那个值再改布局。
5.4 写时序眼图开,但 Write Leveling 没收敛
现象:写眼图看着是开的,但仿真的 Write Leveling 过程里,DQS 的延迟调了很多档位才勉强对上。
原因:CK 和 DQS 的飞行时间差太大,超出了控制器的延迟调节范围。Fly-by 拓扑下,第一颗和最后一颗颗粒的 CK 到达时间差可能超过 1ns。
解决:先算一下 CK 到各颗粒的飞行时间,如果差值超过控制器 Write Leveling 的调节范围(具体看控制器手册),就得调拓扑,比如把 CK 改成点对点分发,或者把颗粒数量减下来。
5.5 仿真收敛了,但结果是发散的
现象:仿真正常结束,没有报错,但波形在某几个时间点突然跳到很大的电压值。
原因:IBIS 模型的 I/V 曲线非单调,或者 [Ramp] 数据和 [Pullup]/[Pulldown] 对不上,导致求解器在某一步走错方向。
解决:用 2.1 节的脚本先查单调性,再对比 [Ramp] 换算出的 tr/tf 和 datasheet。模型本身有问题的话,找厂商换模型,不要在仿真器里硬调收敛参数。
6. 把单次仿真变成参数扫描:用脚本减少重复劳动
单次仿真跑通以后,真正的效率差距在参数扫描上。我见过有团队做 DDR3 仿真,一个速率档扫六组端接,靠人手工改网表、点运行、存结果,两天才能出一版报告。用脚本把这个流程固定下来,同样的工作量压到一个下午,而且不会漏组。
做法是把网表里的可变参数全部抽成占位符,工艺角、端接电阻、驱动强度、走线长度各留一个。第 3.4 节的 shell 脚本负责批量跑,跑完用一段 Python 汇总:
import glob, os import pandas as pd rows = [] for csv_path in glob.glob('./results/*.csv'): tag = os.path.basename(csv_path).replace('.csv', '') corner, rtt, drv = tag.split('_') m = write_timing_margin(csv_path) # 复用 3.3 节的函数 rows.append({'corner': corner, 'rtt': rtt, 'drv': drv, **m}) df = pd.DataFrame(rows).sort_values('eye_h') df.to_csv('./summary.csv', index=False) print(df.head(10)) # 最差的十组,优先看这些tag的命名规则要和 shell 脚本里保持一致,否则拆参数会出错。sort_values('eye_h')把最差的排前面,评审的时候先看这十组,如果最差的都过,整批就都过。这份汇总表还能直接画成热力图,横轴端接、纵轴驱动,一眼就能看出哪个组合是平坦区。
我现在的习惯是:任何一次 DDR3 仿真,只要参数超过三个,就先写扫描脚本再动手,绝不手工点第二次。早年有一次赶进度,手工跑了十几组,最后汇总时发现有两组的工艺角标错了,报告发出去又撤回,那次之后我就把“参数必须脚本化”写进了自己的检查清单。
扫描脚本和模型校验脚本放在同一个目录下,跑之前先跑校验,跑完自动汇总,中间不插手工步骤。这套流程稳定下来以后,新板子进来,改一下网表和叠层参数,半天就能出一版可信的时序报告。
希望帮到你。
本文还有配套的精品资源,点击获取