SDRAM/DDR是怎么寻址的?
为了读取特定单元格的数据,在寻址时要首先确定是哪一个bank,然后在这个选定的bank中进行行列的寻址。在实际工作中,bank的地址与相应的行地址是同时发出的,此时这个命令称之为"行有效"或者“行激活”。在此之后,发送列地址寻址命令和具体的操作命令(读或写),这两个命令也是同时发送的。行列地址是可以复用的,一般来说DDR芯片的地址线为A0~A15,低地址线会被行列复用。以K4B4G1646B 4Gbit 256MB x 16bit内存芯片为例,A0~A14用做行地址,A0~A9用做列地址,这款芯片同时含有B0~B2用来选择bank。
在实际工作中,Bank地址与相应的行地址是同时发出的,此时这个命令称之为“行激活”(Row Active)。在此之后,将发送列地址寻址命令与具体的操作命令(是读还是写),这两个命令也是同时发出的,所以一般都会以“读/写命令”来表示列寻址。根据相关的标准,从行有效到读/写命令发出之间的间隔被定义为tRCD,即RAS to CAS Delay(RAS至CAS延迟,RAS就是行地址选通脉冲,CAS就是列地址选通脉冲),我们可以理解为行选通周期。tRCD是DDR的一个重要时序参数,广义的tRCD以时钟周期(tCK,Clock Time)数为单位,比如tRCD=3,就代表延迟周期为两个时钟周期,具体到确切的时间,则要根据时钟频率而定,DDR3-800,tRCD=3,代表30ns的延迟。
图中显示的是tRCD=3
接下来,相关的列地址被选中之后,将会触发数据传输,但从存储单元中输出到真正出现在内存芯片的 I/O 接口之间还需要一定的时间(数据触发本身就有延迟,而且还需要进行信号放大),这段时间就是非常著名的 CL
下面来看一个具体例子。
该例子中,用两个16bit的DDR3内存拼成了一个32bit的DDR3.
每块16bit DDR3的大小为512M Bytes.
看看硬件上连接,我们这儿只关心地址线和数据线,CS, CK, CKE, CAS, RAS等暂时不考虑。
第一片16bit DDR3的BA0, BA1, BA2连接到了CPU的BA0, BA1, BA2。
第二片16bit DDR3的BA0, BA1, BA2也连接到了CPU的BA0, BA1, BA2。
第一片16bit DDR3的A0~A14连接到了CPU的A0~A14。
第二片16bit DDR3的A0~A14连接到了CPU的A0~A14。
第一片16bit DDR3的D0~D15连接到了CPU的D0~D15。
第二片16bit DDR3的D0~D15连接到了CPU的D16~D31。
分析下该实例。
bank address有三个bit,所以单个16bit DDR3内部有8个bank.
表示行的有A0~A14,共15个bit,说明一个bank中有2^15个行。
表示列的有A0~A9,共10个bit,说明一个bank中有2^10个行。
来看看单块16bit DDR3容量:
2^3*2^15*2^10=2^28=256M
我们的内存是512M,到这儿怎么变成256M了?被骗了?
呵呵,当然没有。
忘了我们前面一直提到的16bit。
16bit是2个byte对吧。
访问一个地址,内存认为是访问16bit的数据,也就是两个字节的数据。
256M个地址,也就是对应512M的数据了。
真相大白。卖内存的没骗俺。
再来看看两个16bit是如何组成一个32bit的。
有一个概念一定要清楚,这儿所说的两个16bit组成一个32bit,指的是数据,与地址没有关系。
我开始这一块没搞清楚,一直认为是两个16bit的地址组成了一个32bit的地址。然后高位地址,地位地址,七七八八。。。
之后没一点头绪。
将16bit/32bit指的是数据宽度之后,就非常明了了。
每一块16bit DDR3中有8个bank,2^15个row,2^10个column。也就是有256M个地址。
看前面的连线可知,两块16bit DDR3的BA0~BA2和D0~D14其实是并行连接到CPU。
也就是说,CPU其实认为只有一块内存,访问的时候按照BA0~BA2和D0~D14给出地址。
两块16bit DDR3都收到了该地址。
它们是怎么响应的呢?
两块内存都是16bit,它们收到地址之后,给出的反应是要么将给定地址上2个字节送到数据线上,要么是将数据线上的两个字节写入到指定的地址。
再看数据线的连接,第一片的D0~D15连接到了CPU的D0~D15,第二片的D0~D15连接到了CPU的D16~D31。
CPU认为自己访问的是一块32bit的内存,所以CPU每给出一个地址,将访问4个字节的数据,读取/写入。
这4字节数据对应到CPU的D0~D31,又分别被连接到两片内存的D0~D15,这样一个32bit就被拆成了两个16bit.
反过来,也就是两个16bit组成了一个32bit.
CPU访问的内存地址有256M个,每访问一个地址,将访问4个字节,这样CPU能访问的内存即为1G.
DDR内存子系统常见硬件错误及Uboot中检测流程
在 U-Boot中,Denx(U-Boot的开发商)针对常见的DDR内存故障进行了严格的检测处理,下图描述了该检测处理过程的三个步骤:检测数据线、地址线和DDR物理存储部件,主要涉及这三个步骤的处理过程和方法,对于DDR子系统,是很容易出故障并且是很难debug检测出来的,而Denx所针对 DDR内存故障设计的检测方法是非常严谨,值得学习研究的。
下面主要是相关的检测处理思路及问题:
1、为什么先检测数据线?
因为如果数据线是断开的,那么一切无从谈起!接下来是检测地址线,只有数据线和地址线都通过,检测内存的存储单元才有意义,这样的流程也利于分割定位问题。上面testing sequence框图将整个检测过程分成三大步,用三个虚线方框表示。
2、数据线的连接错误
数据线的连接可能存在两种错误,一种是被断开,另一种布线或生产造成互相短路。
3、如何检测数据线的连接错误
Denx 设计的数据线检测算法还是很Tricky和精秒的,整个处理流程如下例子:如果是两根数据线,只需要写入并读出一个pattern=0b01(0b开头表示二进制数)就能判断它们是否短路或断开。很明显,大部分的嵌入式平台不止两根数据线,我们以64位地址线为例,pattern = 0b101010101010101010.... 能检测出奇偶位之间的数据错误。如果这个错误被排除,每两根数据线组成一组(这是理解下一个pattern的关键),再用相同的办法,检测每相邻两组之间是否有短路,就得到第二个pattern,就是 0b110011001100...... 依次类推,以4根数据线为一组,8根线为一组,相继得到共6个pattern,分别是 0xaaaaaaaaaaaaaaaa,0xcccccccccccccccc,0xf0f0f0f0f0f0f0f0,0xff00ff00ff00ff00,0xffff0000ffff0000,0xffffffff00000000。只要相继写入并读出这6个pattern就能验证是否存在数据线交叉短路错误。
4、如何检测数据线与板上其它信号线交叉短路或断路
取以上6个paatern的反码,总共12个pattern就能检测到每一位都可以写入和读出0和1。
5、什么是floating buses错误
floating buses会“欺骗”测试软件,如果测试软件写入并很快读出一个值的时候,写操作会跟数据线上的电容充电,总线会短暂的保持它的状态。当测试软件读操作时,总线会返回刚写入的值,即使实际上该数据线是断路的。
6、如何检测数据线的floating buses错误
检测floating buses错误的算法不复杂,在写入和读回之间再插入一次对不同地址写入不同值的操作。例如,X写入X1位置,Y写入Y1位置,再从X1位置读出X值则表示floating buses错误不存在。
7、地址线的错误
如果地址线存在错误,其症状是地址空间中的两个不同位置被映射到同一物理存储位置。更通俗地讲,就是写一个位置却“改变”了另一个位置。
8、地址线的错误检测
地址线的错误检测相对简单,其算法是:
1)、将地址的值作为内容写入该地址处,汇编的表示方法是 (addr) = addr。即将地址值写到地址对应的空间里,这样确保每一个位置的内容不同。
2)、依次将内存基地址的某一根地址线的值翻转(flip/toggle)得到某个地址,从该地址取值,如果该值和基地址的值相等,则表示某一位地址线有问题。
这个算法的特点是每次只检测一根地址线,方法简单有效。
9、存储单元的错误
以上数据线和地址线的检测都是检测布线或工厂生产的错误,而存储单元的检测则是真正对DDR内存芯片的检测。内存芯片的常见错误是bit-stuck,简而言之,就是让它是0,它偏为1,让它为1,它偏为0,检测方法也很简单,就是用不同的pattern去写尽可能所有的地址并读回比较。有一些常用的 pattern如0x5555, 0xAAAA等。
10、几个简单的检测DDR故障的方法
上面的DDR检测算法,虽然全面,但是耗时比较长,常常需要好几个小时,在Uboot命令行下也有几个简单的命令可以检测常见内存故障,如下所示:
1)、mtest addr lenth pattern
这个命令需要注意,DDR在Uboot启动后被映射到了0地址,但是uboot的代码和堆、栈空间0x10000000处开始,这些空间是不能被刷的,否则就挂死了。
2)、复制NOR flash的内容到内存中,如 cp.b 0x20080000 0x7fc0 20000,然后比较 cmp.b 0x20080000 0x7fc0 20000。
3)、下载kernel image到内存中,copy NOR flash 或tftp都行,然后调用iminfo LOAD_ADDR 检测CRC错误。
第一种方法是用特定的pattern去刷DDR的空闲空间,第二种和第三种方法可以说Pattern的随机性更大一些。
当然最彻底的检测方法当然是长时间跑Linux系统,上面的方法更适用于系统不稳定时定位错误。
内存检测方法 针对常见的DDR内存故障进行了严格的检测处理,下图描述了该检测处理过程的三个步骤:检测数据线、地址线和DDR物理存储部件,主要涉及这三个步骤的处理过程和方法。
下面主要是相关的检测处理思路及问题: 1、为什么先检测数据线? 因为如果数据线是断开的,那么一切无从谈起!接下来是检测地址线,只有数据线和地址线都通过,检测内存的存储单元才有意义,这样的流程也利于分割定位问题。上面testing sequence框图将整个检测过程分成三大步,用三个虚线方框表示。 2、数据线的连接错误 数据线的连接可能存在两种错误,一种是被断开,另一种布线或生产造成互相短路。 3、如何检测数据线的连接错误 Denx 设计的数据线检测算法还是很Tricky和精秒的,整个处理流程如下例子:如果是两根数据线,只需要写入并读出一个pattern=0b01(0b开头表示二进制数)就能判断它们是否短路或断开。很明显,大部分的嵌入式平台不止两根数据线,我们以64位地址线为例,pattern= 0b101010101010101010.... 能检测出奇偶位之间的数据错误。如果这个错误被排除,每两根数据线组成一组(这是理解下一个pattern的关键),再用相同的办法,检测每相邻两组之间是否有短路,就得到第二个pattern,就是0b110011001100...... 依次类推,以4根数据线为一组,8根线为一组,相继得到共6个pattern,分别是0xaaaaaaaaaaaaaaaa,0xcccccccccccccccc,0xf0f0f0f0f0f0f0f0,0xff00ff00ff00ff00,0xffff0000ffff0000,0xffffffff00000000。只要相继写入并读出这6个pattern就能验证是否存在数据线交叉短路错误。 4、如何检测数据线与板上其它信号线交叉短路或断路 取以上6个pattern的反码,总共12个pattern就能检测到每一位都可以写入和读出0和1。 5、什么是floating buses错误 floating buses会“欺骗”测试软件,如果测试软件写入并很快读出一个值的时候,写操作会对数据线上的电容充电,总线会短暂的保持它的状态。当测试软件进行读操作时,总线会返回刚写入的值,即使实际上该数据线是断路的。 6、如何检测数据线的floating buses错误 检测floating buses错误的算法不复杂,在写入和读回之间再插入一次对不同地址写入不同值的操作。例如,a写入A位置,b写入B位置,再从A位置读出a值则表示floating buses错误不存在。 7、地址线的错误 如果地址线存在错误,其症状是地址空间中的两个不同位置被映射到同一物理存储位置。更通俗地讲,就是写一个位置却“改变”了另一个位置。 8、地址线的错误检测 地址线的错误检测相对简单,其算法是: 1)将地址的值作为内容写入该地址处,汇编的表示方法是*addr = addr。即将地址值写到地址对应的空间里,这样确保每一个位置的内容不同。 2)依次将内存基地址的某一根地址线的值翻转(flip/toggle)得到某个地址,从该地址取值,如果该值和基地址的值相等,则表示某一位地址线有问题。 这个算法的特点是每次只检测一根地址线,方法简单有效。 9、存储单元的错误 以上数据线和地址线的检测都是检测布线或工厂生产的错误,而存储单元的检测则是真正对DDR内存芯片的检测。内存芯片的常见错误是bit-stuck,简而言之,就是让它是0,它偏为1,让它为1,它偏为0,检测方法也很简单,就是用不同的pattern去写尽可能所有的地址并读回比较。有一些常用的pattern如0x5555,0xAAAA等。 10、几个简单的检测DDR故障的方法 上面的DDR检测算法,虽然全面,但是耗时比较长,常常需要好几个小时,在Uboot命令行下也有几个简单的命令可以检测常见内存故障,如下所示: 1)mtest addr lenth pattern 这个命令需要注意,DDR在Uboot启动后被映射到了0地址,但是uboot的代码和堆、栈空间0x10000000处开始,这些空间是不能被刷的,否则就挂死了。 2)复制NOR flash的内容到内存中,如cp.b 0x20080000 0x7fc0 20000,然后比较cmp.b 0x20080000 0x7fc0 20000。 3)下载kernel image到内存中,copy NOR flash 或tftp都行,然后调用iminfo LOAD_ADDR 检测CRC错误。 第一种方法是用特定的pattern去刷DDR的空闲空间,第二种和第三种方法可以说Pattern的随机性更大一些。 当然最彻底的检测方法当然是长时间跑Linux系统,上面的方法更适用于系统不稳定时定位错误。 具体代码实现如下:
static void move64(unsigned long long *src, unsigned long long *dest) { *dest = *src;
}
/* * This is 64 bit wide test patterns. Note that they reside in ROM * (which presumably works) and the tests write them to RAM which may * not work. * * The "otherpattern" is written to drive the data bus to values other * than the test pattern. This is for detecting floating bus lines. * */ const static unsigned long long pattern[] = { 0xaaaaaaaaaaaaaaaaULL, 0xccccccccccccccccULL, 0xf0f0f0f0f0f0f0f0ULL, 0xff00ff00ff00ff00ULL, 0xffff0000ffff0000ULL, 0xffffffff00000000ULL, 0x00000000ffffffffULL, 0x0000ffff0000ffffULL, 0x00ff00ff00ff00ffULL, 0x0f0f0f0f0f0f0f0fULL, 0x3333333333333333ULL, 0x5555555555555555ULL }; const unsigned long long otherpattern = 0x0123456789abcdefULL;
/* 数据线检测 */ static int memory_post_dataline(unsigned long long * pmem) { unsigned long long temp64 = 0; int num_patterns = sizeof(pattern)/ sizeof(pattern[0]); int i; unsigned int hi, lo, pathi, patlo; int ret = 0;
for ( i = 0; i < num_patterns; i++) { move64((unsigned long long *)&(pattern[i]), pmem++); /* * Put a different pattern on the data lines: otherwise they * may float long enough to read back what we wrote. */ /* 预防floating buses错误 */ move64((unsigned long long *)&otherpattern, pmem--); move64(pmem, &temp64);
#ifdef INJECT_DATA_ERRORS temp64 ^= 0x00008000; #endif
if (temp64 != pattern[i]) { pathi = (pattern[i]>>32) & 0xffffffff; patlo = pattern[i] & 0xffffffff;
hi = (temp64>>32) & 0xffffffff; lo = temp64 & 0xffffffff;
post_log ("Memory (date line) error at %08x, " "wrote %08x%08x, read %08x%08x !\n", pmem, pathi, patlo, hi, lo); ret = -1; } } return ret; }
/* 地址线检测 */ static int memory_post_addrline(ulong *testaddr, ulong *base, ulong size) { ulong *target; ulong *end; ulong readback; ulong xor; int ret = 0;
end = (ulong *)((ulong)base + size);/* pointer arith! */ xor = 0;
for(xor = sizeof(ulong); xor > 0; xor <<= 1) { /* 对测试的地址的某一根地址线的值翻转 */ target = (ulong *)((ulong)testaddr ^ xor);
if((target >= base) && (target < end)) { /* 由于target是testaddr某一根地址线的值翻转得来 故testaddr != target,下面赋值操作后 应有*testaddr != *target */ *testaddr = ~*target; readback = *target;
#ifdef INJECT_ADDRESS_ERRORS if(xor == 0x00008000) { readback = *testaddr; } #endif /* 出现此种情况只有testaddr == target,即某根地址线翻转无效 */ if(readback == *testaddr) { post_log ("Memory (address line) error at %08x<->%08x, " "XOR value %08x !\n", testaddr, target, xor); ret = -1; } } } return ret; }
static int memory_post_test1 (unsigned long start, unsigned long size, unsigned long val) { unsigned long i; ulong *mem = (ulong *) start; ulong readback; int ret = 0;
for (i = 0; i < size / sizeof (ulong); i++) { mem[i] = val; if (i % 1024 == 0) WATCHDOG_RESET (); }
for (i = 0; i < size / sizeof (ulong) && ret == 0; i++) { readback = mem[i]; if (readback != val) { post_log ("Memory error at %08x, " "wrote %08x, read %08x !\n", mem + i, val, readback);
ret = -1; break; } if (i % 1024 == 0) WATCHDOG_RESET (); }
return ret; }
static int memory_post_test2 (unsigned long start, unsigned long size) { unsigned long i; ulong *mem = (ulong *) start; ulong readback; int ret = 0;
for (i = 0; i < size / sizeof (ulong); i++) { mem[i] = 1 << (i % 32); if (i % 1024 == 0) WATCHDOG_RESET (); }
for (i = 0; i < size / sizeof (ulong) && ret == 0; i++) { readback = mem[i]; if (readback != (1 << (i % 32))) { post_log ("Memory error at %08x, " "wrote %08x, read %08x !\n", mem + i, 1 << (i % 32), readback);
ret = -1; break; } if (i % 1024 == 0) WATCHDOG_RESET (); }
return ret; }
static int memory_post_test3 (unsigned long start, unsigned long size) { unsigned long i; ulong *mem = (ulong *) start; ulong readback; int ret = 0;
for (i = 0; i < size / sizeof (ulong); i++) { mem[i] = i; if (i % 1024 == 0) WATCHDOG_RESET (); }
for (i = 0; i < size / sizeof (ulong) && ret == 0; i++) { readback = mem[i]; if (readback != i) { post_log ("Memory error at %08x, " "wrote %08x, read %08x !\n", mem + i, i, readback);
ret = -1; break; } if (i % 1024 == 0) WATCHDOG_RESET (); }
return ret; }
static int memory_post_test4 (unsigned long start, unsigned long size) { unsigned long i; ulong *mem = (ulong *) start; ulong readback; int ret = 0;
for (i = 0; i < size / sizeof (ulong); i++) { mem[i] = ~i; if (i % 1024 == 0) WATCHDOG_RESET (); }
for (i = 0; i < size / sizeof (ulong) && ret == 0; i++) { readback = mem[i]; if (readback != ~i) { post_log ("Memory error at %08x, " "wrote %08x, read %08x !\n", mem + i, ~i, readback);
ret = -1; break; } if (i % 1024 == 0) WATCHDOG_RESET (); }
return ret; }
static int memory_post_tests (unsigned long start, unsigned long size) { int ret = 0;
if (ret == 0) ret = memory_post_dataline ((unsigned long long *)start); WATCHDOG_RESET (); if (ret == 0) ret = memory_post_addrline ((ulong *)start, (ulong *)start, size); WATCHDOG_RESET (); if (ret == 0) ret = memory_post_addrline ((ulong *)(start + size - 8), (ulong *)start, size); WATCHDOG_RESET (); if (ret == 0) ret = memory_post_test1 (start, size, 0x00000000); WATCHDOG_RESET (); if (ret == 0) ret = memory_post_test1 (start, size, 0xffffffff); WATCHDOG_RESET (); if (ret == 0) ret = memory_post_test1 (start, size, 0x55555555); WATCHDOG_RESET (); if (ret == 0) ret = memory_post_test1 (start, size, 0xaaaaaaaa); WATCHDOG_RESET (); if (ret == 0) ret = memory_post_test2 (start, size); WATCHDOG_RESET (); if (ret == 0) ret = memory_post_test3 (start, size); WATCHDOG_RESET (); if (ret == 0) ret = memory_post_test4 (start, size); WATCHDOG_RESET ();
return ret; } |