简介:write leveling training是一份聚焦DDR3与DDR4存储系统的写均衡专题文档,主要面向DDR控制器开发、硬件设计以及存储验证工程师,深入剖析fly-by封装布线造成的DQS与CLK飞行时间差异及其补偿方法;资源压缩包内共1个docx文件,大小仅221KB,内容精炼而完整,便于快速阅读与查阅。目前已有1601人学习下载,得到不少存储相关从业者的关注。文档从训练启动写起,描述了控制器发送CLK并通过DQS信号线连接内存设备、内存设备利用DQS对CLK采样后将结果经DQ反馈、控制器据此迭代调整delay值,直到检测到0到1跳变并锁定延迟的完整流程;同时明确说明read DQS在这一阶段并不参与,仅由控制器采样DQ并用DQS锁定时序。针对write leveling必须满足的时序约束,文档逐项解释了tWLMRD、tWLDQSEN、tWLS、tWLH、tWLO、tWLOE等参数的定义窗口与设计意义,并结合JESD79-3E规范第44页对常见图示T1、T2标注进行了辨析,能帮助读者避免理解陷阱、准确掌握DDR3+ write leveling的关键实现步骤。整体上,这份文档既适合快速入门,也可作为调试中的核对清单。
1. Write Leveling Training:内存初始化里最容易漏掉的那个训练
DDR4-3200 的板子上,内存颗粒焊上去之后,如果固件里把 Write Leveling Training 这一行直接注释掉,系统大概率只能跑在很低的等效频率,甚至开机自检直接报错。这不是玄学:写数据时 DQS 与 CK 到达颗粒的时间差只要超过规格里 tDQSS 的窗口,颗粒内部的时钟域交叉就无法保证,数据要么写错位,要么整条总线罢工。Write Leveling Training 就是控制器在每次初始化时,自动测量并补偿这个时间差的训练步骤。也有人看到 Write Leveling 先想到 NAND 的磨损均衡,那是另一条技术线;DDR 总线里的 Write Leveling 与寿命无关,管的是时序。这篇文章写给做固件初始化、DDR 控制器验证和硬件调试的人,目标只有一个:让你能独立跑通一次 Write Leveling 训练,并且知道结果到底靠不靠谱。
2. 训练原理明线:DQS 与 CK 的相位差为什么不能靠布线解决
2.1 广播的 CK 与逐 lane 的 DQS:天生的相位差
DDR 总线里,CK 是从控制器发向所有颗粒的一对差分时钟,走的是树形或菊花链拓扑;而 DQS 是每个字节通道独立的差分信号,从控制器 PHY 的某个 lane 出发,只到这一组颗粒。两者出发点不同、走线长度不同、末端负载也不同,到达同一颗颗粒的时间天然不相等。PCB 设计阶段能做的等长约束只能把静态偏差压小,压不掉控制器 PHY 内部输出延迟的差异,更压不掉温度变化引起的走线延迟漂移。
写操作时,控制器把 DQ 与 DQS 一起发出去,颗粒用 DQS 的边沿采样 DQ,这是源同步方式。但颗粒内部还有一条以 CK 为基准的写路径:写命令的解码、写 FIFO 的写入时钟,都以 CK 为准。DQS 与 CK 之间的相位差一旦超出 tDQSS 定义的窗口,命令与数据的对应关系就乱了。频率越高,一个时钟周期越短,这个相位差能容忍的绝对值越小,等长布线能兜住的余量也就越少。DDR3-1600 时代一个周期约 1.25ns,tDQSS 窗口还有大约正负 0.25 个周期;到了 DDR5-4800,周期缩到约 0.42ns,同样比例的窗口只有一百多 ps。布线等长解决不了这种量级的偏差,必须靠训练。
2.2 颗粒端反馈机制:DQ 引脚如何“说出”对齐结果
Write Leveling 的核心思想是让颗粒自己报告 DQS 与 CK 的相对位置。进入训练模式后,颗粒不再正常接收写数据,而是把内部比较器的结果驱动到 DQ 引脚上。具体行为是:颗粒观察 DQS 边沿相对 CK 边沿的早晚,如果 DQS 落在 CK 的某个采样窗口内,DQ 输出一个电平;如果落在窗口外,输出另一个电平。控制器在每个 DQS 延迟档位下采样 DQ,就能画出一条“通过/失败”随延迟变化的曲线。
DQS 延迟从小到大扫描时,反馈电平会在两个边界处翻转:第一次翻转表示 DQS 从“太早”进入“通过”区域,第二次翻转表示 DQS 从“通过”区域变成“太晚”。两个边界之间的区间就是 Write Leveling 的通过窗口。控制器最终要选的延迟值,不是第一个翻转点,也不是第二个翻转点,而是窗口中心。选中心是为了给温度漂移和电压纹波留出对称余量,选边界的话,稍微飘一点就直接出窗。
反馈引脚的具体位宽与颗粒类型有关。x8 颗粒通常用 DQ0 到 DQ7 反馈,x4 颗粒只占用其中一部分,x16 颗粒则有额外的约定,后面避坑章节会专门讲。这里的要点是:反馈信号不是一次采样就稳定,边界附近往往有毛刺,训练算法需要做多次采样表决,而不是简单读一次就下结论。
2.3 关键时序参数与控制器/颗粒的分工
与 Write Leveling 直接相关的时序参数有四个,训练代码里等待时间的设置、窗口宽度的判断都以它们为参考。
| 参数 | 全称 | 作用域 | 常见量级 |
|---|---|---|---|
| tDQSS | DQS 相对 CK 的建立保持窗口 | 正常写操作 | 正负 0.25 tCK 以内(高频时更紧) |
| tWLS | Write Leveling Setup Time | 训练模式下 DQS 边沿建立时间 | 约 0.5 到 0.7 tCK |
| tWLH | Write Leveling Hold Time | 训练模式下 DQS 边沿保持时间 | 约 0.5 到 0.7 tCK |
| tWLDQSEN | Write Leveling DQS Enable Time | 从进入训练模式到反馈可用的时间 | 数个 tCK 到数十个 tCK |
控制器与颗粒各自负责一部分延迟调整。控制器 PHY 的每个字节 lane 有一根可编程延迟线,步进通常是 1/64 或 1/128 tCK,这是训练时主要扫描的对象;颗粒内部也有一段延迟链,用于把 DQS 进一步微调,最终校准值通过模式寄存器写回颗粒。两边叠加才是完整的补偿量。固件里要清楚一点:控制器侧扫描到的最优延迟,不等于最终写入颗粒的值,通常控制器侧只写自己 PHY 的延迟寄存器,颗粒侧的值由控制器根据总延迟目标换算后写入。不同控制器的分割策略不一样,参考手册里写得很明确,不要拿一套公式套所有平台。
提示:训练开始前,先确认颗粒已经正确识别容量与密度,且 CK 已经稳定运行。Write Leveling 是在 CK 正常工作的前提下做的,时钟都没起来,训练结果没有意义。
3. 最小训练序列:寄存器操作与 DQ 反馈采样的可执行流程
3.1 开训前置:Vref、ODT 与训练顺序
Write Leveling 不是内存初始化里的第一步。常见做法是先完成颗粒的复位与模式寄存器初始配置,再把 Vref 训练做掉,然后才进入 Write Leveling。Vref 直接决定颗粒输入端判高判低的阈值,Vref 偏了,反馈信号本身可能就是错的,后面所有训练都建立在错误基础上。ODT 也要提前设好,训练期间总线上有持续的 DQS 翻转,如果没有合适的端接,信号反射会在反馈电平上叠加振铃,边界位置会被抬高或压低。
训练顺序上,我一般会这样排:Vref 训练 → Write Leveling → Read DQS Gate 训练 → Read Training → Write Training。Write Leveling 排在写训练之前,是因为后续的写训练需要在 DQS 与 CK 基本对齐的前提下做数据眼图搜索;顺序反过来,写训练眼图会被相位差带偏,搜出来的结果不是真实的。有些控制器的固件库会把 Write Leveling 合并到一段总训练流程里,但调试阶段建议单独剥离出来跑,方便定位问题。
3.2 训练主流程的 C 风格伪代码
下面是一段可运行的训练主流程伪代码,对应常见的 DDR4/LPDDR4 控制器。不同控制器的寄存器地址不同,但流程骨架一致。
// write_leveling_train 伪代码 // lane = 当前训练的字节通道,常见值为 0 到 (总线宽度/8 - 1) void write_leveling_train(uint8_t lane) { // 1. 前置检查:确认颗粒已退出复位、CK 稳定、Vref 训练已完成 if (check_ck_stable() != OK) return TRAIN_ERR_CK; // 2. 进入 Write Leveling 模式:置位模式寄存器的 WL 使能位 write_mr(1, 0x80); // 以常见控制器为例,MR1[7] = 1 wait_cycles(64); // 等待 tWLDQSEN,让颗粒反馈电路稳定 struct record records[256]; // 记录每个延迟步进的反馈结果 // 3. 扫描延迟:从最小到最大,逐级调整控制器侧 DQS 延迟 for (int step = 0; step < 256; step++) { phy_set_dqs_delay(lane, step); // 写入 PHY 的 per-lane 延迟寄存器 wait_cycles(16); // 等 DQS 相位稳定 uint8_t fb = read_dq_feedback(lane); // 采样颗粒 DQ 反馈 records[step].step = step; records[step].value = fb; } // 4. 分析反馈曲线:找到两个翻转边沿,计算窗口 int lo = find_first_edge(records, 256); // 从失败变通过 int hi = find_last_edge(records, 256); // 从通过变失败 if (hi - lo < MIN_WINDOW_STEPS) { training_error(lane, lo, hi); // 窗口过窄,直接报错 return TRAIN_ERR_WINDOW; } // 5. 取窗口中心作为目标延迟 int target = (lo + hi) / 2; phy_set_dqs_delay(lane, target); // 6. 把换算后的校准值写入颗粒模式寄存器 // 控制器侧延迟与颗粒内部延迟的分割比例,见控制器手册 uint8_t mr_value = encode_wl_value(target); write_mr(1, mr_value); // 7. 退出 Write Leveling 模式 write_mr(1, 0x00); wait_cycles(64); // 等颗粒退出训练模式 return TRAIN_OK; }逐段说明:第 2 步写 MR1 进入训练模式,这里的 0x80 是“WL 使能位置 1”的常见写法,实际地址与位偏移以所使控制器的寄存器手册为准。wait_cycles(64) 是为了满足 tWLDQSEN,即从发出 MR 命令到颗粒真正把反馈信号驱动到 DQ 上所需的稳定时间,取 64 个时钟周期是保守值,通常够用。第 3 步是核心扫描循环,phy_set_dqs_delay 每次只改当前 lane 的延迟值,改完必须等一小段时间再采样,因为延迟链切换后输出需要几个时钟周期才稳定。read_dq_feedback 读到的就是颗粒 DQ 引脚上的电平。
第 4 步的 find_first_edge 与 find_last_edge 是边沿查找函数:线性扫描整个 256 步的反馈数组,找到第一次与最后一次电平翻转的位置。理想情况下反馈是一个方波:一段低、一段高、再一段低,两个翻转点就是窗口边界。如果扫描曲线出现多次翻转,说明边界处存在振荡,需要回到第 3 步改用多数表决采样,或检查 ODT 与 Vref 设置。
第 6 步的 encode_wl_value 负责把目标延迟换算成颗粒模式寄存器里的编码值。注意:这个值通常不是直接写 target,因为控制器 PHY 的延迟线与颗粒内部延迟链是串联的,需要减去 PHY 侧已经设置的步进数,再换算成颗粒侧可编程的粒度。换算关系写错,训练结果会整体偏一个常数,但窗口形状看起来仍然正常,这也是这类错误难排查的原因。
3.3 训练结果的写回与退出序列
训练完成后,控制器侧每个 lane 的 DQS 延迟寄存器保留 target 值,颗粒侧模式寄存器写入换算后的校准值。如果只写 PHY 不写颗粒,或者只写颗粒不写 PHY,效果都是 DQS 总延迟与扫描时不一致,训练白做。退出训练模式时,先把 MR 的 WL 使能位清掉,再等一段时间,让颗粒恢复正常的写数据路径。
退出后的第一步验证,不是直接进下一项训练,而是做一次简短的写读检查:向刚才训练的 lane 写一组 0x5A 数据,读回对比。这个检查不追求眼图最优,只确认数据没有明显错位。如果连这组数据都对不上,说明 WL 训练结果本身不可信,回查扫描曲线与寄存器换算关系,而不是继续往下跑后面的训练步骤。
4. 延迟搜索与窗口中心:训练算法的两个关键参数
4.1 线性扫描还是二分:时间开销与风险对比
扫描范围与步进决定了 Write Leveling 训练的时间开销。一个典型 DDR4 控制器有 256 个延迟步进,每个步进约 15ps 到 30ps,总覆盖范围约 4ns 到 7ns,超过一个 tCK 周期。如果按每个步进等待 16 个时钟周期计算,线性扫描一轮需要 4096 个时钟周期,在 100MHz 的训练时钟下耗时约 40 微秒,一个 64bit 总线分割成 8 个 lane,总共也就几百微秒。这个开销对开机流程来说完全可以接受,所以很多固件直接采用线性扫描。
二分搜索可以把时间再缩短一个量级,但有一个前提:反馈曲线必须是单调的,即从失败变成通过、再变成失败,不能有多个翻转区间。边界毛刺会破坏单调性,二分容易收敛到错误的边沿。我的习惯是调测阶段用线性扫描,同时把曲线打印出来,肉眼看一遍窗口形态;确认形态稳定后,再换成二分或直接保留线性扫描。量产固件里用二分的多,但会在二分之前先用粗步进扫一遍,确认只有两个翻边沿才开始精调。
4.2 找边沿与取窗口中心:抗毛刺的三次多数表决
反馈信号在边界附近不是干净的电平跳变。颗粒内部比较器本身有亚稳态窗口,DQS 延迟链切换瞬间会产生毛刺,DQ 线上叠加的串扰也可能把高电平拉低。如果训练代码在某个步进上只采样一次,恰好采到毛刺,边沿位置就会偏移,窗口中心也随之偏移。
常见的解决方法是多数表决:同一个延迟步进下采样三次,取多数值作为该步进的结果。三次采样之间要稍微间隔几个时钟周期,让 PHY 延迟链的输出完全稳定,不能连续读三次,那样只是重复读到同一个毛刺。更稳妥的做法是三次采样时在延迟值上叠加一个极小的偏移,比如当前步进减 1、当前步进、当前步进加 1,三个值各采一次,再把三次结果合并成当前步进的判定。这样可以避免某个步进恰好卡在亚稳态窗口中间。
一个容易忽略的点:找边沿时,第一个翻转点与最后一个翻转点之间的“通过”区域里,理论上全部是反馈电平 A,失败区域全部是电平 B。如果实际曲线中间出现孤立的相反值,需要做滤波处理,比如把连续三个步进的采样值做中值滤波,而不是直接把它当成边沿。孤立点通常是噪声,真正的边沿一定伴随相邻步进的电平变化。
4.3 延迟搜索算法的伪代码与步进设置
下面是用线性扫描配合三次表决实现的边沿查找过程:
// 带三次表决的延迟扫描与窗口中心计算 #define VOTE_NUM 3 #define STEP_NUM 256 #define MIN_WINDOW 8 // 窗口至少 8 个步进,换算成皮秒需按实际步进计算 static int sample_with_vote(uint8_t lane, int step) { int sum = 0; for (int i = 0; i < VOTE_NUM; i++) { // 轻微偏移:减少卡在亚稳态窗口内的概率 int d = step + (i - 1); if (d < 0) d = 0; if (d > STEP_NUM - 1) d = STEP_NUM - 1; phy_set_dqs_delay(lane, d); wait_cycles(8); sum += read_dq_feedback(lane); } return (sum >= 2) ? 1 : 0; } int find_wl_window(uint8_t lane, int *lo, int *hi) { int prev = sample_with_vote(lane, 0); int first_edge = -1, last_edge = -1; for (int step = 1; step < STEP_NUM; step++) { int cur = sample_with_vote(lane, step); if (cur != prev) { if (first_edge < 0) first_edge = step; last_edge = step; prev = cur; } } *lo = first_edge; *hi = last_edge; if (first_edge < 0 || last_edge < 0 || (last_edge - first_edge) < MIN_WINDOW) return -1; // 窗口不存在或过窄 int center = (first_edge + last_edge) / 2; return center; }参数说明:VOTE_NUM 是表决次数,取 3 是性能与抗噪的折中;取值越大越稳,但每个步进耗时线性增长。STEP_NUM 对应 PHY 延迟线的最大档位,不同控制器可能只有 128 档,需要根据寄存器位宽调整。MIN_WINDOW 是窗口最小宽度判定阈值,8 个步进只是一个参考起点。实际设置时,先查出控制器每个延迟步进对应的皮秒数,再乘窗口步进数,看结果是否覆盖至少一个温度区间的漂移量。主流平台的经验值是窗口宽度不低于 300ps,低于这个值就要检查 ODT、Vref 或走线设计。
采样时的偏移量 i - 1 意味着每次表决实际覆盖了三个相邻延迟值,最终判定的是这三个值的多数电平。这样做的代价是边沿精度损失一个步进,换来的是亚稳态引起的误判大幅减少。对窗口中心计算来说,一个步进的精度损失完全可以接受,因为最后中心取整也会损失相同量级。
4.4 影响窗口大小的因素:从 Vref 到电源噪声
窗口宽度不是固定值,同一块板子在不同温度、不同 Vref 设置下窗口可以差出两倍。Vref 偏高或偏低时,颗粒对 DQS 边沿的判别点偏移,通过窗口整体平移,甚至变窄。固件里如果在 WL 训练完成之后又调整过 Vref,必须重新跑 WL。电源噪声是另一个主要因素,DQS 延迟链由模拟电路实现,电源纹波直接调制延迟时间,纹波大时边界抖动明显,三次表决也可能失效。训练期间应该避开总线上的大电流翻转,比如同时跑其他 lane 的训练,最好逐 lane 顺序执行,不要并行。
温度对窗口的影响表现为整体漂移,方向与 PCB 走线的温度系数一致。高温下走线延迟变大,窗口向一个大延迟方向移动。因此,训练时环境温度不同,最终中心值可能差 5 到 10 个步进。这也是为什么有些系统在常温下训练通过、进老化箱后开始报错——窗口余量不够,温度一漂就出界。解决思路有两个:一是把 MIN_WINDOW 阈值设大,留足漂移空间;二是做运行时周期重训,后面章节会展开。
5. Write Leveling 训练避坑:五次翻车换来的五条排查经验
5.1 跳过训练用固定延迟:冷机开机快,热机死得快
现象:某开发板为了缩短开机时间,把 Write Leveling 训练结果固定成一组常数,跳过训练流程直接写寄存器。冷机状态下能正常开机进系统,跑一段时间或环境温度升高后,内存读写随机报错,有时直接死机。
原因:固定延迟值是在某一温度、某一 Vref 条件下标定的,温度变化后 DQS 与 CK 的相对相位差已经漂移,固定的补偿量不再匹配实际偏差。冷机时恰好落在窗口边缘,热机后漂出窗口,错误就来了。
解决:每次冷启动都必须重新训练。开机时间如果紧张,可以缩短每步等待周期数,把 wait_cycles(16) 降为 wait_cycles(8),代价是边界采样稳定性略降,但对量产的常温环境通常够用。固定延迟只能作为训练失败后的降级方案,不能作为正常路径。
5.2 反馈 DQ 在边界附近抖动:训练窗口中心漂移
现象:训练算法找到的窗口中心每次开机都不一样,偏差最多能到十几个步进,但训练本身不报错,后续压力测试时有少量写错误。
原因:反馈电平在边界处不是干净翻转,而是类似振荡的反复跳变。线性扫描时,多个步进都读到了中间电平或交替电平,find_first_edge 与 find_last_edge 把振荡区域当成了通过窗口的一部分,窗口中心被拉偏。
解决:改用三次多数表决,并在边沿查找前做连续三个步进的中值滤波。另外,检查训练期间 ODT 是否已启用,未启用端接时 DQ 反馈线上的反射会更明显。先后端接,再调表决,最后再看窗口中心是否稳定。
5.3 高字节 lane 训练结果异常:x16 颗粒反馈位宽的坑
现象:使用 x16 颗粒的板卡,训练完成后低 8 位 lane 窗口正常,高 8 位 lane 窗口明显偏窄,有些步进采样值完全不变。
原因:x16 颗粒在 Write Leveling 模式下,反馈信号的承载方式与 x8 不同。部分颗粒只把反馈结果驱动到低字节对应的 DQ 线上,高字节反馈需要额外条件;另外 x16 颗粒内部把高字节与低字节看作两个半通道,训练时要分组处理,不能直接套用 x8 的反馈读取逻辑。
解决:先查控制器手册里 x16 颗粒的 WL 反馈约定,确认高字节 lane 对应的反馈引脚是哪些。如果颗粒只支持低字节反馈,就让高字节 lane 的训练跟随低字节的窗口中心,取同一个延迟值,再验证是否落在高字节的通过范围内。不要想当然地认为每个 lane 的反馈引脚就是它自己的 DQ0 到 DQ7,这个假设在 x16 上不成立。
5.4 镜像插槽训练值与参考值不对应:DDR4 镜像映射问题
现象:DIMM 插槽 0 训练出的各 lane 延迟值从左到右依次递增,插槽 1 的结果却是跳跃的,某些 lane 值明显异常,但单独测试每个 lane 又都能通过。
原因:DDR4 标准引入了颗粒镜像映射,插槽 1 的地址与数据引脚相对于插槽 0 做了镜像,DQ/DQS 的物理引脚与逻辑 lane 的对应关系发生了交换。控制器如果按默认映射读取反馈,就会用错的引脚关系去解释训练结果。
解决:训练之前先读取平台配置里的镜像标志位,根据插槽号把 lane 的逻辑映射关系做一次重排。不同厂商控制器的处理方式不同,有些在 PHY 里自动处理,有些需要固件调整。排查时把训练结果按插槽画出 per-lane 延迟曲线,正常应是一条平滑曲线,出现锯齿状跳变就要怀疑映射问题。
5.5 温度漂移把窗口推过边界:需要预留冗余与重训机制
现象:常温老化测试前 8 小时正常,第 10 小时开始出现零星写错误,频率逐渐升高,重启后恢复正常,过几小时又复发。
原因:老化温箱升温后,颗粒与 PCB 走线的延迟漂移量超过了 WL 训练的剩余余量。训练时 MIN_WINDOW 设得太小,中心值离边界太近,温度上升把工作点推出窗口。
解决:把 MIN_WINDOW 阈值从 8 个步进提到 12 到 16 个步进,或者在训练完成后做一次边界回退校验:确认中心值距离两个边界都不小于 N 个步进。对需要长期高温运行的设备,还要增加运行时重训机制,周期性地断电重训或在线重训。在线重训的窗口一般选在内存 idle 时,短时间封锁总线,几百微秒内完成一轮扫描,应用层基本无感。
6. 训练通过之后:三招验证 WL 余量的闭环习惯
第一招,把训练结果导出对照布线。训练完成后,读出每个 lane 的 DQS 延迟值,换算成皮秒,再对照 PCB 设计里 DQS 与 CK 的走线长度差。两者方向应该一致:DQS 走线比 CK 长,延迟值为正;短则为负。数值差在合理范围内,因为 PHY 内部延迟和封装延迟也占一部分。如果方向都不对,说明 PHY 的延迟极性设置反了,或者反馈逻辑解释反了。这个检查只要算一次就能堵住大部分配置错误。
第二招,用示波器实测颗粒端的 DQS 与 CK 相位。需要拆到颗粒引脚附近,用差分探头同时测 DQS 与 CK 的上升沿,相位差应接近零或落在你设定的目标偏置附近。受探头负载影响,实测值不会完全等于训练寄存器值,但偏差应在几个步进以内。如果实测差出几十个步进,回查反馈采样路径,大概率是反馈读错了线。示波器验证不需要每次开机都做,换板、换颗粒批次、改走线后各做一次即可。
第三招,做温度循环下的边界回测。训练完成后,把中心值分别偏移正负 5 个步进、正负 10 个步进,各跑一轮写读压力测试,记录通过区间。通过区间的宽度应该大于训练时扫描到的窗口宽度,否则说明训练时的窗口包含了一部分假通过区域。再让板子跑一轮升温降温循环,回到常温后重新训练,把两次训练的中心值做对比,偏差应小于 5 个步进。超过这个量,说明训练稳定性有问题,优先检查电源噪声与反馈毛刺。
我现在养成的习惯是,每次改完内存初始化相关代码,第一件事不是跑性能,而是把 WL 训练的窗口曲线导出来,用脚本画一张每个 lane 的通过窗口图。窗口宽度、中心位置、边界毛刺一目了然。换板子、换颗粒、改频率,先看这张图再决定要不要继续调别的参数。希望这套流程能帮你在 Write Leveling 上少走几个来回,这个训练步骤看起来不起眼,但它是后续所有写操作时序的地基,地基歪了,上面调得再精细也白搭。
本文还有配套的精品资源,点击获取