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系統,上面的方法更適用於系統不穩定時定位錯誤。
記憶體檢測方法 2011年07月14日 星期四 20:59
記憶體檢測方法 針對常見的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; } |