DDR颗粒的测试做到后期,你会发现它很像是“寻宝”——你永远不知道那颗判定为Pass的颗粒,会不会在三个月后的高温老化里突然翻车。我这些年接触的DDR相关项目,无论量产筛选、SiP封装前的KGD测试,还是失效分析,绕来绕去都会回到同一套底层算法:March系列。这套算法看起来只是一些“写0、读0、写1、读1”的基本动作,最多再换一下地址方向,但背后对故障模型的覆盖逻辑、对寻址方向的讲究、对时序细节的敏感程度,远不是写个循环那么简单。
这次我把March算法的原理、MATS/March C/March LR等经典变体的结构和选型、以及我在FPGA上用AXI接口扫DDR时攒下的经验教训一起整理出来。内容不端着,直接说结论,对正在做DDR量产测试程序、BIST逻辑设计,或者被FA搞得头大的硬件工程师,应该能有点用。
1. 为什么DDR测试离不开March算法
1.1 一颗DDR颗粒会怎么坏:先认识失效模型
DDR本质上是一片DRAM阵列,故障形态比一般逻辑芯片要丰富得多。我在项目里最常见的几类失效,基本可以归到下面这些模型里:
固定故障(SAF,Stuck-At Fault):某个存储单元永远只能读出0,或者只能读出1,不管写入什么值都无动于衷。这是最基础的失效,单片DRAM批量生产时偶发率不低。
转换故障(TF,Transition Fault):单元能存0也能存1,但从0变1或者从1变0这个跳变动作失败了。它比SAF隐蔽,因为单纯写一个固定值测不出来,必须让单元经历“先写反值、再写目标值”的过程。
耦合故障(CF,Coupling Fault):一个单元发生跳变时,影响到了邻近单元。比如第5行的单元从0跳变到1,把隔壁第6行某个本来好好的单元格强制拉成0,或者让它的值翻转了。这类故障在深亚微米工艺下尤其常见,和走线间距、衬底噪声都有关。
地址译码故障(AF,Address Decoder Fault):访问地址A时,实际选中的是地址B;或者一个地址同时选中了多个单元。这类故障用普通顺序读写很难发现,因为连续写一遍再连续读一遍,所有单元都访问过,单靠最终数据比对是能发现的,但定位效率很低,而且某些错选情况在随机模式下容易被掩盖。
读干扰故障(RDF,Read Disturb Fault):读这个单元的操作本身把它的值破坏了,读一次没问题,再读第二次就变了。在DDR里这类问题通常和灵敏放大器(Sense Amplifier)余量不足有关。
这些故障模型不是教科书里躺着不动的概念,它们是实实在在决定测试算法选型的依据。March算法家族之所以被广泛使用,就是因为每一类算法都在明确回答一个问题:我能检测哪些模型,检测不了哪些模型,测试成本多少。
1.2 为什么简单的读写测试不够
很多刚接触DDR测试的工程师,第一反应是先来个“全片写0、全片读0、全片写1、全片读1”,或者干脆用随机地址写随机数据,跑几个钟头看看稳不稳。这些做法的局限性,踩过坑的人都懂。
全片写/读只能抓SAF这类“卡死”故障。对TF,如果你从初始值0开始写1再读1,第一次写进去就读,还能碰巧抓到;但如果单元初始状态正好是1,那这次写入根本没有产生0→1跳变,故障就被跳过去了。随机地址读写的问题更麻烦:覆盖率完全靠运气,某个耦合故障是否被触发,取决于故障单元的邻居在那一刻是什么状态,跑一小时两小时不停,不代表覆盖了就一定稳。
March算法的核心价值在于“确定性”。它用固定顺序、固定数据、固定读写组合,把每个存储单元置于已知状态下,再施加特定操作,把各类故障模型的反应“逼”出来。算法完成后,能检测哪些模型、检测到什么程度,理论上是可以证明的,这套逻辑比一把梭的随机压力测试靠谱得多。
2. March算法的核心思想与符号系统
2.1 一个March元素到底在做什么
March算法由若干个March元素(March Element)串联而成,每个元素定义了寻址方向、操作序列以及执行次数。最通用的符号是类似这样的写法:
M0:↑ ( w0 ) M1:↑ ( r0 , w1 ) M2:↓ ( r1 , w0 )
箭头↑表示地址从低到高递增遍历,↓表示从高到低递减遍历。括号里的操作按逗号顺序依次执行,针对每一个地址单元做完一组操作后,再移动到下一个地址。这里要注意一个重点:括号内的所有操作,都是在“同一个地址上连续完成的”,然后才去下一个地址,很多初看March算法的人会在这里绕晕。
举个例子,M1写作↑ ( r0 , w1 ),实际执行过程是:从首地址开始,先读该地址,判断读出的值是否为0(即与之前写入的背景值比对),然后向该地址写入1,再递增地址,对下一个单元重复同样的操作。一次遍历结束后,这片存储区域的所有单元都已经经历过“读0→写1”的转变。
这种“读写绑定在同一个地址上”的设计,能保证每个单元在写入下一个值之前,它当前的状态是已知且可控的,从而为检测转换故障、耦合故障提供前提。同时,通过调整箭头方向,还能让故障检测覆盖到相邻地址之间的双向影响,这一步在后面讲耦合故障时尤其重要。
2.2 背景数据和寻址方向为什么不能随便选
March算法里写的w0和w1,在DDR大容量阵列里并不是简单对一个bit操作,而是对整个数据总线宽度的一次写入。背景数据模式(Background Pattern)的选择,直接影响故障检测效果。
理论上w0可以全片写0x00000000,w1全片写0xFFFFFFFF。但这种最朴素的写法有个问题:相邻bit之间永远没有逻辑电平差,检测不到bit线/位线之间的耦合漏电或串扰。实战里我会把背景数据改成棋盘格(0xAAAAAAAA和0x55555555交替),甚至用行条纹(同一行全1、相邻行全0)来覆盖不同的物理相邻关系。
寻址方向也很有讲究。DDR内部是Bank、Row、Column三级结构,物理上相邻的单元格和逻辑地址相邻并不总是一回事。如果算法按逻辑地址顺序遍历,实际访问的存储单元在物理上可能隔着几个Bank,本来该被检测的邻近耦合关系反而没触发。做总线和控制器验证时可以偷懒用逻辑地址,但做颗粒级测试,地址映射关系和遍历方向必须对着DDR手册的地址排列表推算。
3. 经典March算法家族逐代拆解
3.1 MATS到MATS++:最早期的实用算法
MATS(Modified Algorithmic Test Sequence)是March家族里最简朴的一个,结构大致是:
M0:↑ ( w0 ) M1:↑ ( r0 , w1 ) M2:↓ ( r1 )
它只要求先全片写0,然后从低到高读0写1,最后从高到低读1。操作次数大约为4N(N为存储单元数),复杂度很低,因此跑得非常快。但它能覆盖的故障模型也最有限:SAF可以查到,AF只能查一部分,TF和CF基本无能为力。
MATS+在MATS基础上把最后一个元素改成↓ ( r1 , w0 ),这样最后一个方向上也完成了一次读1写0的完整跳变,对TF有了一部分覆盖。MATS++再加了一个↑ ( r0 ),把首尾写读顺序彻底闭环。这三个算法现在几乎不会单独用于DDR量产,但在SRAM和寄存器堆的内建自测试(BIST)里仍然能看到身影,主要图它逻辑简单、门数开销低。
3.2 March C/C-/C+:工业界的绝对主力
March C是很多工程师接触到的第一个“高级”March算法,标准形式如下:
M0:↑ ( w0 ) M1:↑ ( r0 , w1 ) M2:↑ ( r1 , w0 ) M3:↓ ( r0 , w1 ) M4:↓ ( r1 , w0 ) M5:↓ ( r0 )
它把递增和递减方向组合进了同一个流程里,大大增强了对耦合故障的检测能力。March C的复杂度是10N,能覆盖SAF、AF、TF、CFin和CFid。这里说的CFin是“耦合反转故障”(一个单元跳变导致另一个单元逻辑值翻转),CFid是“幂等耦合故障”(一个单元跳变导致另一个单元被强制置成固定值0或1),这两种类型在DRAM中都很常见。
March C-和March C的唯一区别是把最后一个元素从↑(r0)改成了↓(r0)。这一改,整个算法的对称性变好了,让读写操作在序列中保持一致的顺序,也消除了某些操作之间不必要的时序依赖。实际应用里,我看过的ATE测试程序、BIST库,标准配置基本都是March C-,几乎没有直接用March C的。
March C+则是在March C-的基础上做了一些“读写操作合并”,主要目的是方便BIST硬件实现时减少状态机跳转,对最终故障覆盖率影响不大。如果你用现成IP做DDR BIST,见到March C+的选项也不用慌,跑出来的结果和March C-基本是同一档。
3.3 March B与March LR:针对耦合故障的进阶
March C-能覆盖单方向的耦合故障(即一个单元跳变影响另一个单元),但覆盖不了“链接故障”(Linked CF)。所谓链接故障,是指故障A触发之后,改变了故障B的初始状态,导致故障B无法被后续操作检测到,这是DRAM里一种很隐蔽的失效场景。
March B的出现解决了部分问题。March B的结构比March C-多了很多连续读写操作,例如:
M0:↑ ( w0 ) M1:↑ ( r0 , w1 , w1 , r1 , w0 , w0 ) M2:↑ ( r0 , w1 ) M3:↓ ( r1 , w0 , w0 , r0 , w1 ) M4:↓ ( r1 , w0 ) M5:↑ ( r0 )
它对链接故障的覆盖比March C-要好,但代价是复杂度上升到约17N,而且在很多实际生产测试里,March C-已经能筛掉绝大多数坏品,March B/D的边际收益并不高。
如果真的是冲着“最全面”去的,March LR值得研究。March LR的核心设计意图是检测链接耦合故障,它通过在不同March元素中反复对同一单元施加读和写,破坏耦合故障的传播条件。典型结构是6个元素交替使用↑和↓,总访问长度约14N。在专用DRAM测试机台和高端BIST方案里,March LR经常作为“出厂终极测试”的算法之一。它跑得比March C-慢,但能抓出来March C-漏掉的链路型故障,在FA分析里作用尤其大。
3.4 March SS:给复杂故障模型准备的“重武器”
March SS是学界针对更复杂故障模型(尤其是状态耦合故障SOF)提出的算法。所谓SOF,是指一个单元的状态本身就影响了另一个单元的行为,并不需要前一个单元跳变。这种故障在DRAM工艺尺寸越来越小之后,出现概率明显上升,传统March C-拿它没办法。
March SS的结构比前面所有算法都复杂,March元素内部包含多次读写交替,典型复杂度约22N。公式具体写法不同论文里略有差异,我参考较多的是Hamdioui在IEEE论文中给出的版本,6个March元素里有多个元素包含4次以上的读/写操作,比如:
M0:↑ ( w0 ) M1:↑ ( r0 , w1 , r1 , w0 ) M2:↑ ( r0 , w1 , r1 , w0 ) M3:↓ ( r0 , w1 , r1 , w0 ) M4:↓ ( r0 , w1 , r1 , w0 ) M5:↓ ( r0 )
March SS跑一轮要付出的时间成本相当高,所以它基本不会进量产测试流程,更多用在失效验证、工艺监控和研发阶段的样片验证里。工程上的建议是:先用March C-做快速筛,疑似问题再用March LR或March SS做细查,直接上来就跑22N的算法,时间不划算。
3.5 各算法核心参数速查表
| 算法 | 复杂度 | 可检测故障 | 工程定位 |
|---|---|---|---|
| MATS | 4N | SAF、部分AF | BIST轻量级自检 |
| MATS+ | 5N | SAF、AF、部分TF | 快速筛片 |
| March X | 6N | SAF、AF、TF、CFin | 低成本寄存器堆测试 |
| March C- | 10N | SAF、AF、TF、CFin、CFid | DDR量产/BIST主力 |
| March B | 17N | SAF、AF、TF、CFin、CFid、部分链接故障 | 更高覆盖率的场景 |
| March LR | 14N | SAF、AF、TF、CFin、CFid、链接故障 | FA深度定位 |
| March SS | 22N | SAF、AF、TF、CFin、CFid、SOF | 研发验证/工艺监控 |
4. 在真实DDR工程里落地March测试
4.1 用AXI接口扫DDR时,burst和地址映射怎么安排
DDR控制器外部接口现在几乎都是AXI总线,FPGA里做March测试,本质就是用一个AXI Master去读写DDR。这里有一个工程现实:如果直接按单地址读写,效率低到无法接受。
DDR本身按Burst访问,一个AXI burst至少对应DDR控制器里的几个内部burst,因此写March算法时不可能真的“一个地址一个地址”来。我建议的做法是,用AXI的INCR模式,设置Burst Length为8或16,让一次AXI事务连续覆盖一段地址。
但这就引出一个问题:March元素强调每个单元“读写绑定”。如果一次burst写一大段,然后下一次burst再读回来,读的时候数据其实来自一整段地址,想要在同一个AXI事务里对每个地址做“先读旧值、再写新值”,总线层面的操作时序会复杂得多。一个比较实用的折中方案是:把March元素拆成读阶段和写阶段,每次burst读回一段数据,在本地比对后,紧接着用另一个burst对同一段地址写入新数据。这样单个地址上“读旧→写新”的时序仍然成立,而总线利用率高出不少。
地址映射也需要特别注意。AXI地址是连续的,但DDR控制器会把逻辑地址拆成Bank、Row、Column。如果你跑的March算法是为了检测物理相邻单元的耦合故障,必须要知道控制器内部的地址交织规则,先把逻辑地址转换成物理Bank/Row/Column布局,再决定遍历顺序。我见过一个项目,直接用AXI地址按整数递增跑March,到最后FA分析时才发现,逻辑相邻的地址在物理上根本不相邻,故障定位完全偏了。
4.2 测试背景pattern怎么设计
很多人把March算法里的w0和w1理解成“写全0和全1”,这在芯片非常小、数据位宽很低的情况下勉强能用,但放到64bit甚至更宽的DDR总线上,全0全1根本不够。
我常用的背景数据模式有这么几种:
- Solid 0/1:全片写0x00或0xFF,对SAF检测最直接。
- Checkerboard:用0xAA和0x55交替填充,确保相邻bit之间永远是相反电平,可以暴露bit间漏电。
- Row Stripes:同一行写0xFFFF,下一行写0x0000,捕捉Word Line(字线)之间的干扰。
- Column Stripes:同一列写0x55,相邻列写0xAA,针对Bit Line(位线)之间的影响。
- 地址相关数据:每个地址写入的数据由地址低位计算得出,这样任何寻址错误(比如地址错位片选)都会立刻反映为读回数据异常。
实际测试程序里,March算法每执行一轮,背景数据模式换一次。比如第一轮用Checkerboard跑March C-,第二轮换成Column Stripes再跑一遍。这样整体时间翻了倍,但覆盖率显著提升。至于该跑几轮,我一般会根据产品的应用场景定,消费类跑2种模式,车规类跑4种以上。
4.3 测试时间与覆盖率如何权衡
测试时间是量产成本的核心,March算法的复杂度直接决定测试时间。假设DDR颗粒容量2Gbit,数据总线64bit,那么逻辑上要遍历的写读单位约为32M个(2Gbit / 64bit)。March C-复杂度10N,也就是大约3.2亿次读写操作,以DDR3-1600理论上单通道带宽约12.8GB/s来算,理想状态下几个秒级别能跑完,但实际工程里加上AXI总线协议开销、DDR刷新抢占、ECC计算消耗,以及多种背景pattern轮换,一轮跑下来要几十秒甚至数分钟都是正常的。
这个现实直接影响了生产测试方案的取舍。量厂里更常见的策略是“分两级”:第一级用MATS+或者March X这种低复杂度算法做快速初筛,把明显坏死的颗粒直接踢掉;第二级才上March C-甚至March LR做细致扫描。初筛多加一道虽然多花几分钟,但能大幅缩短复杂算法在坏片上的执行时间——坏片在读到第一个错误地址时往往可以被提前终止测试,没必要把整个算法跑完。
我在FPGA上实现的时候,会在March元素里加一个“首次错误即停”的开关。如果只是产线筛选,第一次读到错误数据直接结束并上报失败地址,省下大量时间。但如果是FA分析,就要关闭这个开关,记录所有错误地址,后面画Fault Bitmap用。
5. 配套参数与仿真对测试结论的影响
5.1 tWR/tREFI这些时序参数会把测试结果带偏吗
March算法在逻辑层面保证故障可测,但DDR本身还有一大串时序参数,处理不好会把逻辑上本来能测出来的故障掩盖掉,或者反过来制造一堆假故障。
先说tWR(Write Recovery Time)。在March的“写后读”元素里,写入数据后立刻去读,如果控制器没有等够tWR时间就发出读命令,写数据可能还没稳定锁定到存储单元里,读回来的值既不是旧值也不是可靠的新值。这种问题在低频下不明显,频率拉高后特别容易误报。解决办法有两个:要么在算法层面对“读旧值”和“写新值”之间插入适当的等待周期,这等于主动降低算法的访问速率;要么在控制器配置里把tWR参数严格遵守好,保证硬件层面不会提前发起下一次操作。
再说tREFI(Refresh Interval)。DDR颗粒需要定时刷新,刷新操作会抢占正常的读写带宽。March算法跑在DDR上时,刷新会插进来,导致某些单元的访问间隔被拉长。大多数情况下这不影响故障检测,但如果做数据保持测试(Data Retention Test),必须在March写完背景后停住刷新手动模式下的一段时间,再继续读取验证。这颗故障的机理是“存储单元电容漏电导致数据丢失”,它本身就依赖时间累积,不特意插入刷新暂停是测不出来的。
工程实践里有种常见误操作:为了让March算法跑得快,把自动刷新关掉,跑完再打开。这在短时间是可行的,但一旦总时长超过刷新周期太多,所有单元的电荷都会慢慢衰减,最后读到一大堆错误,分不清是单元坏了还是刷新被关了。我的建议是,如果是做功能故障扫描,保持自动刷新开启;如果是做数据保持特性验证,则要在严格可控的时长内单独关闭刷新并计时。
5.2 用IBIS模型做仿真,提前发现信号完整性问题
DDR测试结果里有一类非常让人头疼的假故障:颗粒本身没问题,但PCB走线、连接器、测试座引入的信号完整性问题,导致了误码。这类问题在DDR3/4的高速信号下尤其明显。要避免它,不能光靠跑测试,提前做SI仿真非常关键。
IBIS模型(I/O Buffer Information Specification)是芯片厂商提供的一种IO行为级模型,它描述了芯片引脚上的驱动强度、输入阈值、封装寄生参数等特性。我在设计DDR测试板或者选择测试夹具时,会用IBIS模型在Sigrity或HyperLynx里搭一个带拓扑的信号链路仿真,检查地址线、数据线、时钟线和读写DQS的时序余量。
一个典型的教训是:端接电阻选错,直接导致DQ信号眼图闭合。回板后测试发现某些地址单元总在固定数据位上出错,用逻辑分析仪抓波形又很难抓到,因为错误是间歇性的。后来把IBIS模型套上去仿真,才发现接收端的Vref基准设置和ODT配置不匹配,导致高电平幅度靠近判决门限,一旦供电稍有纹波就误判。修好端接后再跑March,错误全部消失。
所以,做DDR测试方案时,我强烈建议把“高速信号仿真”纳入流程。不需要仿真做得非常深,但至少要验证:走线阻抗是否受控、端接方案是否和芯片ODT配置匹配、Vref参考是否干净、走线长度差是否在时序预算内。这四项没验证前,直接跑March算法,出现故障时你无法判断是颗粒坏还是板子坏。
5.3 DRAM和DDR PSRAM的区别,对测试有什么提示
DDR PSRAM(Pseudo SRAM)这个词最近在嵌入式项目里出现频率很高。它内部内核仍然是DRAM单元,但对外接口做了全面封装,比DDR DRAM用起来简单得多,同时也带来了测试上的差异。
核心区别有两点。第一,PSRAM内部集成了刷新电路,外部接口不需要控制器每隔7.8us或者3.9us发一次刷新命令,这大幅简化了接口设计。第二,PSRAM一般容量比标准DDR小很多,接口时序简化,很多型号直接兼容SPI或者标准并行接口,并不全都走AXI/DDR协议。
这个区别对March测试的影响是:如果被测器件是DDR PSRAM,我仍然可以用March算法扫内部存储单元,但由于刷新控制权在芯片内部,数据保持测试就不能通过关闭外部刷新来做了,必须看芯片手册确认是否有测试模式可以控制内部刷新。另外,PSRAM内部可能还有地址重映射、坏块替换逻辑,这会在March扫描时把物理上相邻的单元映射到逻辑上不相邻的地址,从而影响耦合故障的检测效果。做测试前先摸清楚器件的内部仲裁机制,否则测试结果只代表“接口行为正确”,不代表“内部阵列健康”。
5.4 DDR规范与Pin脚细节:排查问题时最容易忽视的线索
做DDR测试时,很多人把精力全部放在算法上,却忽视了DDR规范里关于Pin脚时序和电气参数的细节。其实很多时候,March测试报错,问题恰恰出在引脚层面。
DDR的DQ、DQS、CLK、CS、CKE、ODT这些引脚各有严格的时序关系。比如DQ在读写时必须以DQS为参考对齐,如果PCB布线导致DQ和DQS的偏差过大,读操作时捕获到的数据就会出错。这种错误在March里表现为随机地址的随机bit错误,非常容易误导排查方向。
我的习惯是:当March测试出现多个随机地址错误时,不急着怀疑存储单元,先检查Pin脚的相关配置和时序约束。看CLK和DQS的相位关系是否正常、ODT配置是否合理、片选信号在访问过程中是否出现了毛刺。DDR规范和Pin脚说明文档里的时序图,这时候就是最好的排查手册。很多“疑难杂症”最后查下来都是引脚时序预算没算够。
6. 实战问题与排查经验
6.1 测试失败的典型定位流程
March测试一旦报错,我建议按下面的顺序排查,而不是一头扎进算法实现细节里:
第一步:确认错误是否可复现。单独重跑一遍出错的地址段,如果错误消失,大概率是刷新、温度、电源噪声或SI问题,而不是存储单元物理故障。
第二步:记录错误地址,检查是否成规律分布。比如是否集中在某一个Bank、某几个Row、或者数据总线的某一位。如果是固定bit位,多半是数据总线或焊点问题;如果是固定Bank/Row,多半是行译码或特定字线问题。
第三步:用不同背景pattern再跑一遍。如果换成反相数据后错误消失,可能是数据敏感故障;如果错误地址完全跟随背景数据变化,那耦合故障的可能性很大。
第四步:结合温度箱和电压拉偏测试。把温度升到85°C、电压降低5%再跑,故障率显著升高的话,说明颗粒时序裕量不足。
第五步:回到SI仿真和引脚时序排查。把IBIS仿真波形拿出来,看看出错地址附近的信号质量,确认是否属于板级问题。
6.2 高温低温下的失效差异怎么排查
DDR对温度极其敏感,March测试在不同温度下的表现差异很大。常见的情况是:常温下全部Pass,85°C高温箱里跑出一堆列故障,或者-20°C低温下出现数据保持失败。
高温下最容易暴露的是刷新问题。因为漏电随温度指数增长,tREFI即使在标准范围内,某些边缘单元电荷保持时间已经缩短到标准刷新周期以下,跑数据保持测试必然暴露。这种故障如果只在高温出现,基本可以断定是单元漏电或电容退化问题。
低温下常见的则是电压摆幅和时序问题。低温时晶体管阈值电压变化,信号边沿变慢,可能导致读写窗口不够。如果低温下出现固定地址的间歇性错误,优先检查时钟和DQS的建立保持时间,而不是怀疑存储单元。
工程建议是:量产测试至少做常温+高温两轮,DDR颗粒具体的测试温度和持续时间参考等级标准。车规器件的测试温度范围更宽,而且需要三温测试(低温、常温、高温)。所有温度测试背后的分析逻辑,要回到“这个故障是对存储单元本身的应力敏感,还是对接口时序敏感”,才能对症下药。
6.3 几个容易被忽略的工程细节
第一个细节,ECC和冗余行/列可能掩盖真实故障。很多DDR控制器默认开着ECC,March测试时读回的数据经过纠错后全部正确,但实际上存储单元已经出错了。做颗粒级测试前,务必通过模式寄存器关掉ECC、禁用硬件冗余替换,否则测出来的“Pass”没有任何意义。
第二个细节,写数据和读数据的比较方式。March测试必须比较的是“读回值”和“期望值”,但期望值不是简单地等于上次写入的背景值,因为中间可能隔了多个操作。我建议在测试代码里用一个状态变量记录当前March元素的期望数据,不要在硬件比较器里写死0或1。
第三个细节,测试完之后的清理。March测试会在DDR里留下大量非初始状态的数据,测试结束后如果不初始化DDR,直接启动应用,很可能触发ECC错误或者控制器异常。在所有测试线程退出后,做一遍全片写0或者写固定pattern,把DDR恢复成干净状态,这个小习惯能省很多线上调试时间。
第四个细节,测试地址范围不要笼统用“全部”。DDR颗粒有Training区域、Mode Register区域等不能随便读写的空间,还有部分保留地址。直接全地址遍历容易把测试命令打到保留区,产生意外结果。做地址遍历前,先把测试空间边界裁剪好。
第五个细节,AXI写响应和读数据的关联。用AXI VIP时,写数据和读数据是独立的通道,如果同时对多个事务进行outstanding操作,写完成时间和实际写入存储单元的时间可能并不一致。要保证March元素顺序严格,就得注意写响应通道的时序,或者干脆把outstanding能力限制为1,避免写读交错影响算法语义。
总的来说,March算法的原理并不复杂,真正考验人的地方在于把算法可靠地搬到真实DDR环境里,并且不被时序参数、SI问题和控制器行为干扰。我个人的习惯是把所有可能影响测试结论的因素列成一张检查表,每做一个项目先过一遍表再开始写算法代码。等踩过的坑足够多,你会发现大部分DDR失效问题其实都有规律可循,而March算法就是那把分辨失效率最强、而覆盖最精准的钥匙。