Documente Academic
Documente Profesional
Documente Cultură
Si alguna vez hubo una pieza de software incrustado maduro para su reutilización
es la prueba de memoria. En este artículo se muestra cómo probar los problemas
de memoria más comunes con un conjunto de tres funciones de prueba de memoria
de dominio público, portátiles y eficientes.
Introducción
Una pieza de software que casi todos los desarrolladores integrados deben escribir
en algún momento de su carrera es una prueba de memoria. A menudo, una vez
que el hardware del prototipo está listo, el diseñador de la placa desea cierta
seguridad de que ha conectado la dirección y las líneas de datos correctamente, y
que los diversos chips de memoria están funcionando correctamente. Y, incluso si
ese no es el caso, es deseable probar cualquier RAM integrada al menos tan a
menudo como el sistema se restablece. Depende del desarrollador de software
incrustado, entonces, para averiguar lo que puede salir mal y diseñar un conjunto
de pruebas que descubrirán problemas. A primera vista, escribir una prueba de
memoria puede parecer una tarea bastante simple. Sin embargo, al mirar el
problema más de cerca se dará cuenta de que puede ser difícil detectar problemas
de memoria sutiles con una simple prueba. De hecho, como resultado de la
ingenuidad del programador, muchos sistemas integrados incluyen pruebas de
memoria que detectarían solo los fallos de memoria más catastróficos. Tal vez
increíblemente, algunos de estos pueden ni siquiera notar que los chips de memoria
han sido eliminados de la placa! El propósito de una prueba de memoria es
confirmar que cada ubicación de almacenamiento en un dispositivo de memoria está
funcionando. En otras palabras, si almacena el número 50 en una dirección
determinada, espera encontrar ese número almacenado allí hasta que se escriba
otro número en esa misma dirección. La idea básica detrás de cualquier prueba de
memoria, entonces, es escribir algún conjunto de datos en cada dirección en el
dispositivo de memoria y verificar los datos leyéndolos de nuevo. Si todos los
valores leídos son los mismos que los que se escribieron, se dice que el dispositivo
de memoria pasa la prueba. Como verá, es sólo a través de una cuidadosa
selección del conjunto de valores de datos que puede estar seguro de que un
resultado de paso es significativo. Por supuesto, una prueba de memoria como la
que acabamos de describir es necesariamente destructiva. En el proceso de probar
la memoria, debe sobrescribir su contenido anterior. Dado que generalmente no es
práctico sobrescribir el contenido de memorias no volátiles, las pruebas descritas
en este artículo se utilizan generalmente solo para las pruebas de RAM. Sin
embargo, si el contenido de un dispositivo de memoria no volátil, como flash, no es
importante, ya que lo son durante la etapa de desarrollo del producto, estos mismos
algoritmos también se pueden utilizar para probar esos dispositivos.
Problemas comunes de memoria
Antes de implementar cualquiera de los posibles algoritmos de prueba, debe estar
familiarizado con los tipos de problemas de memoria que es probable que se
produzcan. Un concepto erróneo común entre los ingenieros de software es que la
mayoría de los problemas de memoria ocurren dentro de los propios chips. Aunque
un problema importante en un momento (hace unas décadas), problemas de este
tipo son cada vez más raros. Los fabricantes de dispositivos de memoria realizan
una variedad de pruebas de postproducción en cada lote de chips. Si hay un
problema con un lote en particular, es extremadamente poco probable que uno de
los chips defectuosos se abre camino en su sistema. El único tipo de problema de
chip de memoria que podría encontrar es un fallo catastrófico. Esto suele ser
causado por algún tipo de daño físico o eléctrico al chip después de la fabricación.
Las fallas catastróficas son poco frecuentes y generalmente afectan a grandes
porciones del chip. Puesto que un área grande se ve afectada, es razonable asumir
que la falla catastrófica será detectada por cualquier algoritmo de prueba decente.
En mi experiencia, la fuente más común de problemas de memoria real es la placa
de circuito. Los problemas típicos de la placa de circuito son problemas con el
cableado entre el procesador y el dispositivo de memoria, los chips de memoria que
faltan y los chips de memoria insertados incorrectamente. Estos son los problemas
que un buen algoritmo de prueba de memoria debe ser capaz de detectar. Tal
prueba también debe ser capaz de detectar errores catastróficos de memoria sin
buscarlos específicamente. Por lo tanto, vamos a discutir los problemas de la placa
de circuito con más detalle.
Lo primero que queremos probar es el cableado del bus de datos. Tenemos que
confirmar que cualquier valor colocado en el bus de datos por el procesador es
recibido correctamente por el dispositivo de memoria en el otro extremo. La forma
más obvia de probar eso es escribir todos los valores de datos posibles y comprobar
que el dispositivo de memoria almacena cada uno correctamente. Sin embargo, esa
no es la prueba más eficiente disponible. Un método más rápido es probar el bus
un bit a la vez. El bus de datos pasa la prueba si cada bit de datos se puede
establecer en 0 y 1, independientemente de los demás bits de datos. Una buena
manera de probar cada bit de forma independiente es realizar la llamada "prueba
de caminar 1". La Tabla 1 muestra los patrones de datos utilizados en una versión
de 8 bits de esta prueba. El nombre de esta prueba, caminando 1, proviene del
hecho de que un solo bit de datos se establece en 1 y "caminado" a través de toda
la palabra de datos. El número de valores de datos que se va a probar es el mismo
que el ancho del bus de datos. Esto reduce el número de patrones de prueba de 2n
a n, donde n es el ancho del bus de datos.
00000001
00000010
00000100
00001000
00010000
00100000
01000000
10000000
Puesto que estamos probando solo el bus de datos en este punto, todos los valores
de datos se pueden escribir en la misma dirección. Cualquier dirección dentro del
dispositivo de memoria servirá. Sin embargo, si el bus de datos se divide a medida
que se dirige a más de un chip de memoria, tendrá que realizar la prueba de bus de
datos en varias direcciones, una dentro de cada chip. Para realizar la prueba de
caminar 1, simplemente escriba el primer valor de datos en la tabla, verifíquelo
leyéndolo de nuevo, escriba el segundo valor, verifique, etc. Cuando llegue al final
de la tabla, la prueba se completará. Está bien hacer la lectura inmediatamente
después de la escritura correspondiente esta vez porque todavía no estamos
buscando fichas que faltan. De hecho, esta prueba proporciona resultados
significativos incluso si los chips de memoria no están instalados! La función
memTestDataBus(), en el listado 1, muestra cómo implementar la prueba de walking
1 en C. Se supone que el autor de la llamada seleccionará la dirección de prueba y
probará todo el conjunto de valores de datos en esa dirección. Si el bus de datos
funciona correctamente, la función devolverá 0. De lo contrario, devolverá el valor
de datos para el que se produjo un error en la prueba. El bit que se establece en el
valor devuelto corresponde a la primera línea de datos defectuosa, si existe.
1. typedef unsigned char datum; /* Set the data bus width to 8 bits. */
2. /**********************************************************************
3. *
4. * Function: memTestDataBus()
5. *
6. * Description: Test the data bus wiring in a memory region by
7. * performing a walking 1's test at a fixed address
8. * within that region. The address (and hence the
9. * memory region) is selected by the caller.
10. *
11. * Notes:
12. *
13. * Returns: 0 if the test succeeds.
14. * A non-zero result is the first pattern that failed.
15. *
16. **********************************************************************/
17. datum
18. memTestDataBus(volatile datum * address)
19. {
20. datum pattern;
21. /*
22. * Perform a walking 1's test at the given address.
23. */
24. for (pattern = 1; pattern != 0; pattern <<= 1)
25. {
26. /*
27. * Write the test pattern.
28. */
29. *address = pattern;
30. /*
31. * Read it back (immediately is okay for this test).
32. */
33. if (*address != pattern)
34. {
35. return (pattern);
36. }
37. }
38. return (0);
39. } /* memTestDataBus() */
1. /**********************************************************************
2. *
3. * Function: memTestAddressBus()
4. *
5. * Description: Test the address bus wiring in a memory region by
6. * performing a walking 1's test on the relevant bits
7. * of the address and checking for aliasing. This test
8. * will find single-bit address failures such as stuck
9. * -high, stuck-low, and shorted pins. The base address
10. * and size of the region are selected by the caller.
11. *
12. * Notes: For best results, the selected base address should
13. * have enough LSB 0's to guarantee single address bit
14. * changes. For example, to test a 64-Kbyte region,
15. * select a base address on a 64-Kbyte boundary. Also,
16. * select the region size as a power-of-two--if at all
17. * possible.
18. *
19. * Returns: NULL if the test succeeds.
20. * A non-zero result is the first address at which an
21. * aliasing problem was uncovered. By examining the
22. * contents of memory, it may be possible to gather
23. * additional information about the problem.
24. *
25. **********************************************************************/
26. datum *
27. memTestAddressBus(volatile datum * baseAddress, unsigned long nBytes)
28. {
29. unsigned long addressMask = (nBytes/sizeof(datum) - 1);
30. unsigned long offset;
31. unsigned long testOffset;
32. datum pattern = (datum) 0xAAAAAAAA;
33. datum antipattern = (datum) 0x55555555;
34. /*
35. * Write the default pattern at each of the power-of-two offsets.
36. */
37. for (offset = 1; (offset & addressMask) != 0; offset <<= 1)
38. {
39. baseAddress[offset] = pattern;
40. }
41. /*
42. * Check for address bits stuck high.
43. */
44. testOffset = 0;
45. baseAddress[testOffset] = antipattern;
46. for (offset = 1; (offset & addressMask) != 0; offset <<= 1)
47. {
48. if (baseAddress[offset] != pattern)
49. {
50. return ((datum *) &baseAddress[offset]);
51. }
52. }
53. baseAddress[testOffset] = pattern;
54. /*
55. * Check for address bits stuck low or shorted.
56. */
57. for (testOffset = 1; (testOffset & addressMask) != 0; testOffset <<= 1)
58. {
59. baseAddress[testOffset] = antipattern;
60. if (baseAddress[0] != pattern)
61. {
62. return ((datum *) &baseAddress[testOffset]);
63. }
64. for (offset = 1; (offset & addressMask) != 0; offset <<= 1)
65. {
66. if ((baseAddress[offset] != pattern) && (offset != testOffset))
67. {
68. return ((datum *) &baseAddress[testOffset]);
69. }
70. }
71. baseAddress[testOffset] = pattern;
72. }
73. return (NULL);
74. } /* memTestAddressBus() */
1. /**********************************************************************
2. *
3. * Function: memTestDevice()
4. *
5. * Description: Test the integrity of a physical memory device by
6. * performing an increment/decrement test over the
7. * entire region. In the process every storage bit
8. * in the device is tested as a zero and a one. The
9. * base address and the size of the region are
10. * selected by the caller.
11. *
12. * Notes:
13. *
14. * Returns: NULL if the test succeeds. Also, in that case, the
15. * entire memory region will be filled with zeros.
16. *
17. * A non-zero result is the first address at which an
18. * incorrect value was read back. By examining the
19. * contents of memory, it may be possible to gather
20. * additional information about the problem.
21. *
22. **********************************************************************/
23. datum *
24. memTestDevice(volatile datum * baseAddress, unsigned long nBytes)
25. {
26. unsigned long offset;
27. unsigned long nWords = nBytes / sizeof(datum);
28. datum pattern;
29. datum antipattern;
30. /*
31. * Fill memory with a known pattern.
32. */
33. for (pattern = 1, offset = 0; offset < nWords; pattern++, offset++)
34. {
35. baseAddress[offset] = pattern;
36. }
37. /*
38. * Check each location and invert it for the second pass.
39. */
40. for (pattern = 1, offset = 0; offset < nWords; pattern++, offset++)
41. {
42. if (baseAddress[offset] != pattern)
43. {
44. return ((datum *) &baseAddress[offset]);
45. }
46. antipattern = ~pattern;
47. baseAddress[offset] = antipattern;
48. }
49. /*
50. * Check each location for the inverted pattern and zero it.
51. */
52. for (pattern = 1, offset = 0; offset < nWords; pattern++, offset++)
53. {
54. antipattern = ~pattern;
55. if (baseAddress[offset] != antipattern)
56. {
57. return ((datum *) &baseAddress[offset]);
58. }
59. }
60. return (NULL);
61. } /* memTestDevice() */
Putting It All Together
Para que nuestra discusión sea más concreta, consideremos un ejemplo práctico.
Supongamos que queríamos probar un fragmento de 64 kilobytes de SRAM en la
dirección 0x0000000. Para ello, llamamos a cada una de las tres rutinas de prueba
en el orden correcto, como se muestra en el listado 4. En cada caso, el primer
parámetro es la dirección base del bloque de memoria. Si el ancho del bus de datos
es mayor que 8 bits, se requieren un par de modificaciones. Si cualquiera de las
rutinas de prueba de memoria individuales devuelve un valor distinto de cero (o no
NULL), puede activar un LED rojo para indicar visualmente el error. De lo contrario,
después de que las tres pruebas se hayan completado correctamente, es posible
que active un LED verde. En caso de error, la rutina de prueba que ha fallado
devolverá información sobre el problema encontrado. Esta información puede ser
útil al comunicarse con el diseñador de hardware o el técnico sobre la naturaleza
del problema. Sin embargo, solo es visible si estamos ejecutando el programa de
prueba en un depurador o emulador. En la mayoría de los casos, simplemente
descargaría toda la suite y la dejaría ejecutar. A continuación, si y sólo si se
encuentra un problema de memoria, ¿necesitaría usar un depurador para recorrer
el programa y examinar los códigos de retorno de la función individual y el contenido
del dispositivo de memoria para ver qué prueba ha fallado y por qué.
1. /**********************************************************************
2. *
3. * Function: memTest()
4. *
5. * Description: Test a 64-k chunk of SRAM.
6. *
7. * Notes:
8. *
9. * Returns: 0 on success.
10. * Otherwise -1 indicates failure.
11. *
12. **********************************************************************/
13. int
14. memTest(void)
15. {
16. #define BASE_ADDRESS (volatile datum *) 0x00000000
17. #define NUM_BYTES 64 * 1024
18. if ((memTestDataBus(BASE_ADDRESS) != 0) ||
19. (memTestAddressBus(BASE_ADDRESS, NUM_BYTES) != NULL) ||
20. (memTestDevice(BASE_ADDRESS, NUM_BYTES) != NULL))
21. {
22. return (-1);
23. }
24. else
25. {
26. return (0);
27. }
28.
29. } /* memTest() */
Copyright (c) 2000 de Michael BarrEste artículo está adaptado del material del
capítulo 6 del libro Programming Embedded Systems in C and C++ (ISBN 1-56592-
354-5). Se imprime aquí con el permiso de O'Reilly & Associates, Inc.