← Índice del curso

Circuitos Secuenciales y Memoria · Tema 28

ROM y tablas de consulta

La RAM pierde sus datos al apagarse; una ROM los conserva. Una ROM (Read-Only Memory, memoria de solo lectura) guarda un contenido que se fija una vez y desde entonces solo se lee: el programa de arranque de un equipo, la fuente de letras de una pantalla, una tabla de senos. Hay otra manera de mirarla, más sorprendente: una ROM es una tabla de verdad. Si las entradas son las direcciones y las salidas son los datos, guarda cualquier función combinacional. Este tema recorre los tipos de ROM, cómo se programan y para qué sirven las tablas de consulta.

01 · Punto de partida

Memoria que no se olvida

Una ROM comparte con la RAM (tema 26) la organización de palabras y direcciones, y se lee con las mismas señales de dirección, datos y habilitación. Se diferencia en dos cosas:

  • No tiene entrada de escritura (no existe WE̅): el procesador no puede cambiar su contenido en funcionamiento normal.
  • Es no volátil: el contenido sobrevive sin alimentación, durante años.

Su rol natural es guardar lo que no cambia o cambia raramente: el firmware (el programa que arranca el equipo), las tablas de constantes, las fuentes de caracteres, el código de un videojuego de cartucho. Apenas se enciende una computadora, lo primero que ejecuta está en una ROM, porque la RAM todavía está vacía.

ROM frente a RAM.
AspectoRAMROM
LecturaSíSí
Escritura en funcionamientoSí, con WE̅No
Sin alimentaciónPierde el contenidoLo conserva
Contenido al encenderIndefinidoEl que se programó
Uso típicoVariables y datos de trabajoPrograma de arranque, constantes, tablas

02 · Estructura

La ROM por dentro: una tabla de verdad

Una ROM de n líneas de dirección y m de datos tiene dos bloques. Un decodificador de n a 2n activa, para cada dirección, una única línea de palabra. Una matriz de m columnas conecta cada línea de palabra con las columnas cuyo bit vale 1 (con un diodo o un transistor en el cruce); las columnas tienen una resistencia a masa, de modo que una columna sin conexión lee 0.

ROM de cuatro palabras de tres bits Un decodificador de 2 a 4 activa una de cuatro líneas de palabra. Tres columnas de datos tienen un diodo en cada cruce donde el bit vale 1: la palabra 0 vale 101, la 1 vale 011, la 2 vale 110 y la 3 vale 000. Decodificador2 a 4 D2D1D0 A1A0 W0 → 101W1 → 011W2 → 110W3 → 000

Cada punto es una conexión programada. Leer la dirección 01 (palabra W1) activa su línea; los puntos de D1 y D0 la conectan con esas columnas, que suben a 1, y D2 queda en 0: el dato es 011. La matriz es el contenido. Programar una ROM es decidir dónde hay conexión y dónde no.

Una ROM implementa cualquier función combinacional

Mirá la ROM de otra manera: las líneas de dirección son las entradas de un circuito combinacional y las columnas de datos son sus salidas. El decodificador genera todos los minitérminos de las entradas; la matriz es un conjunto de compuertas OR que suma los que correspondan a cada salida. Con la ROM de arriba:

La ROM como tabla de verdad de tres funciones de dos variables (A1 A0 son las entradas).
A1A0D2D1D0
00101
01011
10110
11000

D2 = ~A1·~A0 + A1·~A0 = ~A0; D1 = ~A1·A0 + A1·~A0 = A1 ⊕ A0; D0 = ~A1·~A0 + ~A1·A0 = ~A1. No hizo falta simplificar nada: se copió la tabla de verdad. Esa es la gran ventaja de implementar lógica con una ROM: el diseño es la tabla, y cambiar la función es cambiar los datos, no el cableado.

03 · Tipos

ROM de máscara, PROM y EPROM

Lo que distingue a las ROM es cuándo y cómo se fija el contenido:

Familias de ROM.
TipoCómo se programa¿Se puede cambiar?Cuándo conviene
ROM de máscaraEn la fábrica, con una máscara del proceso que define las conexionesNoProducción masiva de un contenido ya definitivo (costo inicial alto, costo por chip muy bajo)
PROM (Programmable ROM)Con un programador: se «queman» fusibles o anti-fusiblesNo: una sola vez (OTP, one-time programmable)Series chicas y medianas con contenido definitivo
EPROM (Erasable PROM)Con un programador, aplicando una tensión altaSí: se borra con luz ultravioleta y se reprogramaDesarrollo y prototipos, donde el contenido cambia
EEPROM y FlashEléctricamente, en el propio circuitoSí, eléctricamenteTemas 29 y 30

PROM: el fusible

En una PROM cada cruce de la matriz tiene una conexión con un fusible. De fábrica, todos los fusibles están intactos y todas las palabras valen 1 (o 0, según la tecnología). El programador hace pasar una corriente intensa por los fusibles de los bits que deben cambiar y los quema. Un fusible quemado no se repara: el chip es de un solo uso.

EPROM: la compuerta flotante

La celda de una EPROM es un transistor con una compuerta flotante, una isla de silicio aislada por óxido. Para programarla, el programador aplica una tensión alta (VPP, de unos 12 a 21 V) y fuerza electrones calientes a atravesar el óxido y quedar atrapados en la isla: la carga atrapada cambia el umbral del transistor, y esa celda se lee como 0. El aislante es tan bueno que la carga se conserva por más de una década. Para borrarla hace falta darles energía a los electrones para que salgan: se ilumina el chip con luz ultravioleta (unos 254 nm) durante 15 a 20 minutos a través de una ventana de cuarzo que tiene el encapsulado. Por eso se las reconoce a simple vista: una ventanita transparente sobre el chip, que se tapa con una etiqueta para que la luz del ambiente no la borre de a poco.

Las EPROM sin ventana (OTP EPROM) tienen la misma celda pero en un encapsulado plástico barato: no se pueden borrar y se comportan como PROM.

04 · Programación

Programar, borrar y leer

Hay una regla que explica casi todo el comportamiento de estas memorias: programar solo lleva bits de 1 a 0; borrar los devuelve a 1.

  • Una EPROM virgen o recién borrada contiene 0xFF en todas las posiciones (todos los bits en 1).
  • Programar una palabra con el dato D deja en esa palabra el resultado de (contenido anterior) AND D: los bits de D en 0 se ponen a 0; los bits de D en 1 dejan el bit como estaba.
  • Convertir un 0 en 1 es imposible por programación: hay que borrar toda la memoria con UV, no una palabra suelta.
Qué queda en una palabra de EPROM al programarla (solo se muestran 4 bits).
Contenido anteriorDato a programarResultado (AND)¿Lo esperado?
1111 (virgen)10101010Sí
101011111010No se «reescribe»: los 0 no vuelven a 1
101001100010No: el segundo bit pedido en 1 quedó en 0
101010001000Sí (solo se pidieron más ceros)

Antes de programar, el procedimiento correcto es un chequeo de borrado (blank check): comprobar que todas las palabras son 0xFF. Después, programar y verificar leyendo todo y comparando con el original.

Leer

La lectura es la de cualquier memoria asíncrona: se pone la dirección, se activan CE̅ (selección del chip) y OE̅ (salida), y después del tiempo de acceso (decenas a cientos de ns) aparece el dato. No hay WE̅ ni contención posible con el procesador: el único que maneja el bus durante una lectura es la memoria. Los pines extra (VPP y PGM, o como se llamen en el chip) sirven solo para programar; en funcionamiento normal se conectan a +5 V. Un 27C256 tiene 15 líneas de dirección y 8 de datos: 32 768 palabras de 8 bits (tema 26).

05 · Tablas de consulta

Tablas de consulta

Una tabla de consulta (LUT, lookup table) reemplaza un cálculo por una lectura: en vez de calcular f(x), se guarda f(x) para todos los valores posibles de x y se lee la dirección x. Es el mismo compromiso que conoce cualquier programador entre memoria y tiempo de cómputo.

Usos clásicos de una ROM como tabla de consulta.
AplicaciónEntradas (dirección)Salidas (dato)Tamaño
Decodificador a siete segmentos (tema 20)Dígito BCD o hexadecimal de 4 bitsSegmentos a b c d e f g16 × 7 bits
Generador de caracteresCódigo de letra + fila del carácterPuntos encendidos de esa filap. ej. 128 letras × 8 filas × 5 bits
Onda senoidal para un DACContador de fase de n bitsValor de la señal en esa fase2n × 8 bits
Conversión de códigos (BCD ↔ binario, Gray, etc.)Código de entradaCódigo de salida2entradas × salidas
Linealización de un sensorLectura del conversor A/DValor corregido (temperatura, presión)256 o 1024 palabras
Unidad de control microprogramadaCódigo de instrucción + estadoSeñales de controlCentenares de palabras anchas
Aritmética sin circuitoDos operandos juntosResultado22n × (n + 1) bits

Ejemplo: generar una senoide

Un contador de 6 bits recorre las direcciones 0 a 63 de una ROM que contiene 64 muestras de una onda seno, de 8 bits cada una. La salida de la ROM va a un conversor digital-analógico y se obtiene una senoide. Si el reloj del contador es de 64 kHz, la frecuencia de la señal es 64 000 / 64 = 1 kHz. Cambiar el reloj cambia la frecuencia; cambiar el contenido, la forma de onda (cuadrada, triangular, diente de sierra o cualquier otra). Un generador de funciones sencillo es solo un contador, una ROM y un DAC.

06 · Límites

Cuánto se puede meter en una ROM

Una ROM con n entradas tiene 2n palabras: el tamaño crece exponencialmente con el número de entradas. Implementar un sumador de dos números de n bits requiere 2n entradas y n + 1 salidas:

Bits de ROM para un sumador de dos números de n bits.
nEntradasPalabrasBitsAlternativa
241648Unas pocas compuertas
482561280Un 74HC283
612409628 672Dos 74HC283 en cascada
81665 536589 824Dos 74HC283 (ocho sumadores completos)

Para un sumador de 8 bits ya serían casi 600 000 bits, cuando bastan ocho sumadores completos. Regla práctica: la ROM conviene cuando hay pocas entradas (hasta unas 10 a 12) y muchas salidas o una función irregular que no se simplifica (un decodificador de siete segmentos, un conversor de código, una tabla de senos). Cuando la función es regular (sumar, comparar, contar), la lógica dedicada gana por mucho.

Las LUT de las FPGA

Las FPGA, que veremos más adelante en tu camino, están construidas sobre esta misma idea: cada bloque lógico contiene una pequeña tabla de consulta de 4 a 6 entradas (una memoria de 16 a 64 bits) que se programa con la tabla de verdad de la función deseada. Una FPGA es, en el fondo, un mar de ROM minúsculas conectadas entre sí.

Entre la ROM y las compuertas sueltas están los dispositivos de lógica programable de matriz fija (PAL, GAL, PLA), que programan la parte AND y/o la parte OR con menos desperdicio. Pero ninguno es tan fácil de entender como la ROM: la tabla de verdad es el programa.

07 · Integridad

Verificar el contenido

Una ROM puede tener un contenido equivocado por un error de programación, un bit que se descargó con los años o una copia defectuosa. Para detectarlo, el firmware lleva junto a los datos un código de verificación:

  • Checksum (suma de verificación): se suman todos los bytes módulo 256 y se guarda el complemento; al sumar todo el contenido más ese byte, el resultado debe ser 0. Es muy barato, pero débil: no ve dos errores que se compensen ni bytes intercambiados de lugar.
  • CRC (cyclic redundancy check): divide el contenido, tratado como un polinomio, por un polinomio generador y guarda el resto. Detecta todos los errores de un bit, casi todos los de pocos bits, ráfagas de errores e intercambios de posición. El costo es algo de cálculo (en un procesador, una tabla de 256 entradas, una LUT) o un pequeño circuito con un registro de desplazamiento realimentado, como los del tema 14.

Un bootloader serio verifica el CRC de la aplicación antes de ejecutarla y se niega a arrancar si no coincide. Es la versión de hardware de ver que un archivo bajó completo.

08 · La ROM en código

Programar, tabular, muestrear y verificar

Los bloques modelan la regla de programación de una EPROM, implementan lógica con tablas (siete segmentos y un sumador), construyen tablas de seno de distinto tamaño y comparan un checksum con un CRC-8 ante errores inyectados.

Python en tu navegador. Los bloques son editables: cambiá los valores y presioná «Ejecutar Python». La primera vez tarda unos segundos en cargar.

Paso 1: programar solo lleva de 1 a 0

class EPROM:
    """EPROM de 2^n palabras de 8 bits. Programar solo puede llevar bits de 1 a 0; borrar (UV) devuelve todo a 1."""

    def __init__(self, n=4):
        self.datos = [0xFF] * (2 ** n)
        self.borrados = 0

    def leer(self, a):
        return self.datos[a]

    def programar(self, a, valor):
        anterior = self.datos[a]
        self.datos[a] = anterior & valor              # un 0 ya programado no vuelve a 1
        return self.datos[a]

    def borrar_uv(self):
        self.datos = [0xFF] * len(self.datos)
        self.borrados += 1


m = EPROM(4)
print("virgen:      ", *(f"{m.leer(a):02X}" for a in range(8)), "...")
res = m.programar(3, 0xA5)
print(f"programo 0xA5 en 3: queda 0x{res:02X}")
res = m.programar(3, 0xFF)
print(f"intento 'programar' 0xFF encima: queda 0x{res:02X}   (no se puede volver a 1 sin borrar)")
res = m.programar(3, 0x0F)
print(f"programo 0x0F encima de 0xA5: queda 0x{res:02X}   (= 0xA5 AND 0x0F)")
m.borrar_uv()
print(f"tras borrar con UV: 0x{m.leer(3):02X}, ciclos de borrado: {m.borrados}")
res = m.programar(3, 0xA5)
print(f"reprogramo 0xA5: queda 0x{res:02X}")

La memoria virgen vale 0xFF. Programar 0xA5 deja 0xA5; querer «reescribir» 0xFF no cambia nada (queda 0xA5), y programar 0x0F encima da 0xA5 AND 0x0F = 0x05, no 0x0F. Solo el borrado con UV devuelve los bits a 1 y permite reprogramar. Es exactamente lo que hace el laboratorio de más abajo.

Paso 2: lógica en una ROM

# ROM de 16 x 7 que convierte un dígito hexadecimal en los segmentos a b c d e f g (cátodo común).
SEG = ["1111110", "0110000", "1101101", "1111001", "0110011", "1011011", "1011111", "1110000",
       "1111111", "1111011", "1110111", "0011111", "1001110", "0111101", "1001111", "1000111"]
ROM7 = [int(b, 2) for b in SEG]
print("ROM de 7 segmentos (16 x 7 = 112 bits):")
print("  dirección  a b c d e f g   byte")
for a in (0, 1, 7, 8, 10, 15):
    print(f"     {a:X}       {' '.join(SEG[a])}    0x{ROM7[a]:02X}")

# ROM de 256 x 5 que suma dos números de 4 bits: la dirección es (A << 4) | B
SUMA = [((addr >> 4) + (addr & 0xF)) for addr in range(256)]
errores = sum(1 for a in range(16) for b in range(16) if SUMA[(a << 4) | b] != a + b)
print()
print(f"ROM de suma: 256 palabras de 5 bits = {256 * 5} bits; errores contra la aritmética: {errores}")
print(f"  ejemplo: 9 + 8 -> dirección 0x{(9 << 4) | 8:02X}, contenido {SUMA[(9 << 4) | 8]} = 0b{SUMA[(9 << 4) | 8]:05b}")

# crecimiento: una ROM para sumar dos números de n bits cada uno
print()
print("bits de ROM para un sumador de dos números de n bits (2n entradas, n+1 salidas):")
for n in (2, 4, 6, 8):
    print(f"  n = {n}: 2^{2 * n} = {2 ** (2 * n):6d} palabras x {n + 1} bits = {2 ** (2 * n) * (n + 1):7d} bits")

La tabla de siete segmentos es una ROM de 112 bits: el 0 es 0x7E (todo menos g), el 8 es 0x7F (todos), la A es 0x77. La ROM de suma de 256 palabras de 5 bits reproduce las 256 sumas sin ningún error (9 + 8 = 17, 0b10001, en la dirección 0x98). Pero crece rápido: de 1280 bits para 4 bits a casi 590 000 para 8 bits. Por eso una ROM sirve para funciones de pocas entradas.

Paso 3: una tabla de seno

from math import sin, pi


def tabla_seno(n_bits_dir, ancho=8):
    N = 2 ** n_bits_dir
    amplitud = 2 ** ancho - 1
    return [round((sin(2 * pi * i / N) + 1) / 2 * amplitud) for i in range(N)]


def error_maximo(rom, n_bits_dir, ancho=8):
    N = 2 ** n_bits_dir
    amplitud = 2 ** ancho - 1
    peor = 0
    for k in range(4096):                              # recorremos la señal con más resolución que la tabla
        x = k / 4096
        ideal = (sin(2 * pi * x) + 1) / 2 * amplitud
        leido = rom[int(x * N) % N]                    # sin interpolar: tomamos el valor de la entrada anterior
        peor = max(peor, abs(ideal - leido))
    return peor


rom = tabla_seno(6)
print("tabla de seno de 64 puntos, 8 bits (primeros valores):", *rom[:10])
print("valores clave:  dir 0 ->", rom[0], "| dir 16 (90°) ->", rom[16], "| dir 32 (180°) ->", rom[32], "| dir 48 (270°) ->", rom[48])
print()
print("líneas de dirección | puntos | bits de ROM (8 bits por punto) | error máximo sin interpolar (LSB de 8 bits)")
for n in (4, 6, 8, 10):
    r = tabla_seno(n)
    print(f"        {n:2d}          | {2 ** n:5d}  | {2 ** n * 8:6d}                          | {error_maximo(r, n):6.1f}")

# generador: un contador de n bits recorre la ROM y un DAC convierte
f_reloj = 64_000                                       # Hz
n = 6
print()
print(f"con un contador de {n} bits y reloj de {f_reloj} Hz: frecuencia de la señal = {f_reloj / 2 ** n:.0f} Hz")

La tabla de 64 puntos va de 128 (el centro) a 255 en el máximo (dirección 16, 90°), vuelve a 128 en la 32 y llega al mínimo 0 en la 48. Duplicar los puntos reduce el error de cuantización por escalón: 49 niveles con 16 puntos, 13 con 64, 3 con 256 y 1 con 1024. Cuatro veces más puntos cuestan cuatro veces más ROM por cada paso. Con un contador de 6 bits y 64 kHz, la señal sale a 1 kHz.

Paso 4: checksum y CRC

import random


def suma_comprobacion(imagen):
    """Complemento a 2 de la suma módulo 256: sumar todo el contenido con este byte da 0."""
    return (-sum(imagen)) & 0xFF


def crc8(imagen, poly=0x07):
    crc = 0
    for byte in imagen:
        crc ^= byte
        for _ in range(8):
            crc = ((crc << 1) ^ poly) & 0xFF if crc & 0x80 else (crc << 1) & 0xFF
    return crc


rnd = random.Random(28)
imagen = [rnd.randint(0, 255) for _ in range(256)]
chk, crc = suma_comprobacion(imagen), crc8(imagen)
print(f"imagen de 256 bytes: checksum = 0x{chk:02X}, CRC-8 = 0x{crc:02X}")
print("verificación (suma total con el checksum):", (sum(imagen) + chk) & 0xFF, "-> 0 significa 'sin cambios'")

# inyectamos errores y vemos qué detecta cada método
n_pruebas, detecta_chk, detecta_crc = 2000, 0, 0
for _ in range(n_pruebas):
    mala = imagen[:]
    pos = rnd.randrange(256)
    mala[pos] ^= 1 << rnd.randrange(8)                  # un solo bit cambiado
    detecta_chk += suma_comprobacion(mala) != chk
    detecta_crc += crc8(mala) != crc
print(f"errores de 1 bit: checksum detecta {detecta_chk}/{n_pruebas}, CRC-8 detecta {detecta_crc}/{n_pruebas}")

# un error que el checksum NO ve: dos bytes que se compensan
mala = imagen[:]
mala[10] = (mala[10] + 5) & 0xFF
mala[200] = (mala[200] - 5) & 0xFF
print("dos bytes que se compensan (+5 y -5): checksum igual?", suma_comprobacion(mala) == chk, "| CRC-8 igual?", crc8(mala) == crc)

# intercambio de dos bytes
mala = imagen[:]
mala[3], mala[4] = mala[4], mala[3]
print("dos bytes intercambiados: checksum igual?", suma_comprobacion(mala) == chk, "| CRC-8 igual?", crc8(mala) == crc)

Con un error de un solo bit, tanto el checksum como el CRC-8 detectan los 2000 casos probados. La diferencia aparece en errores más sutiles: si dos bytes cambian en sentidos opuestos (+5 y −5), el checksum queda igual y no ve el problema, y si dos bytes se intercambian de lugar, tampoco. El CRC-8 sí detecta ambos. Por eso los bootloaders usan CRC y no una simple suma.

Los cuatro bloques cubren el ciclo de vida de una ROM: grabarla bajo sus reglas, usarla como lógica o como tabla, dimensionarla y verificar que lo grabado sea lo que se quiso grabar.

09 · Exploración interactiva

El laboratorio: programar una ROM

El simulador es una ROM de 16 palabras de 8 bits en tres versiones: de máscara (con la tabla de siete segmentos grabada de fábrica), PROM virgen (se programa una vez) y EPROM virgen (se programa y se borra con UV). Elegís una dirección y un dato en hexadecimal, y «Programar palabra» aplica la regla del AND: solo se pueden bajar bits. Con «Programar tabla de 7 segmentos» se carga la tabla completa, y «Leer» muestra el dato como segmentos de un display.

ROM de máscara, PROM o EPROM de 16 × 8

Contenido de la ROM simulada Las dieciséis palabras con su dirección en binario y hexadecimal, en dos columnas. A la derecha, un display de siete segmentos que muestra el dato leído o el contenido de la dirección elegida.
Tipo de memoria—
Contenido de la dirección elegida—
Palabras en blanco (0xFF)—
Operaciones de programación0
Fallos0
Ciclos de borrado0
Verificación contra la tabla de 7 segmentos—

EPROM virgen: todas las palabras en 0xFF.

Últimas 8 operaciones desde el inicio de la simulación.
N.ºOperaciónDirecciónDatoResultado
Sin operaciones.

Recorrido guiado

  1. Con la EPROM virgen, presioná «Leer» en la dirección 0: el dato es 0xFF y el display enciende todos los segmentos. Una memoria sin programar tiene todos los bits en 1.
  2. Presioná «Programar tabla de 7 segmentos»: las 16 palabras toman los códigos de 0 a F, y la verificación dice «coincide con la tabla». Recorré las direcciones con «Leer»: el display muestra el dígito de cada una.
  3. En la dirección 5 (0x5B, el dígito 5), programá 0xFF: el contenido no cambia (queda 0x5B, los ceros no vuelven a 1), pero el simulador marca que la verificación falla, porque se pedía 0xFF y quedó otra cosa.
  4. En la misma dirección, programá 0x30 (que sería el 1): queda 0x5B AND 0x30 = 0x10, no 0x30. Aparece «verificación falla» y la tabla ya no coincide.
  5. Presioná «Borrar con UV»: todo vuelve a 0xFF y el contador de borrados sube. Reprogramá la tabla: vuelve a coincidir.
  6. Cambiá a PROM: se reinicia virgen. Programá la tabla (ok) y luego intentá reprogramar una palabra con otro valor: falla. «Borrar con UV» responde «imposible»: los fusibles quemados son definitivos.
  7. Cambiá a ROM de máscara: ya trae la tabla grabada. «Programar palabra» y «Programar tabla» son rechazados, y la verificación coincide desde el principio.
  8. Con cualquiera de las tres, recorré las 16 direcciones con «Leer» y observá que el display forma 0 1 2 3 4 5 6 7 8 9 A b C d E F: la ROM es un decodificador de siete segmentos.
Desafío: programar un dato con dos intentos

Querés dejar 0xC3 en una dirección de una EPROM virgen, pero primero programás 0xF0 por error. Después de eso, ¿se puede llegar a 0xC3? 0xF0 AND 0xC3 = 0xC0, que no es 0xC3: los bits 1 y 0 de 0xC3 quedaron en 0 y no pueden subir. La única salida es borrar toda la memoria con UV y volver a programar. Es la razón de que un programador de EPROM siempre haga primero el chequeo de borrado, y de que un error en una palabra obligue a reprogramar el chip entero.

10 · Comprensión

Confusiones frecuentes

«ROM significa que nunca se puede cambiar»

Solo la ROM de máscara. La PROM se programa una vez, la EPROM se borra con UV y reprograma, y la EEPROM y la Flash se reescriben eléctricamente. «Solo lectura» describe el uso en funcionamiento normal.

«Se puede reprogramar una palabra de EPROM sin borrar»

Programar solo baja bits (de 1 a 0). Para subirlos hay que borrar todo el chip con luz ultravioleta.

«Una EPROM nueva viene en cero»

Viene en 0xFF: todos los bits en 1. El borrado también deja todo en 1.

«La ROM solo sirve para guardar datos»

También implementa lógica combinacional: las direcciones son entradas y los datos, salidas. Un decodificador de siete segmentos o un conversor de código son ROM.

«Una ROM siempre es mejor que las compuertas porque no hay que simplificar»

El tamaño de la ROM se duplica por cada entrada que se agrega. Para funciones regulares y de muchas entradas, la lógica dedicada ocupa mucho menos.

«El firmware en ROM no puede tener errores de memoria»

Puede tenerlos: un bit puede perderse con los años o la programación puede fallar. Por eso se agrega un checksum o un CRC.

«El checksum detecta cualquier cambio»

No ve dos errores que se compensen ni bytes intercambiados. El CRC es mucho más robusto, por un costo de cálculo pequeño.

«Las ventanitas de las EPROM son decorativas»

Son el camino de la luz ultravioleta que las borra. Por eso se tapan con una etiqueta una vez programadas: la luz solar o la de algunas lámparas fluorescentes puede borrarlas de a poco.

11 · Práctica guiada

Predecí antes de simular

1. Tamaño de una tabla

Una ROM convierte un número BCD de 8 bits (dos dígitos) en binario de 7 bits. ¿Cuántas líneas de dirección, de datos, palabras y bits tiene?

Ver solución

8 líneas de dirección (256 palabras), 7 de datos: 256 · 7 = 1792 bits. En realidad, solo 100 de las 256 direcciones son BCD válidas (00 a 99); las otras 156 pueden quedar sin usar o marcar error.

2. El AND de la programación

En una EPROM, una palabra vale 0xA5. Se programa con 0xF0 y después con 0x3C. ¿Qué valor queda?

Ver solución

0xA5 AND 0xF0 = 0xA0. Luego 0xA0 AND 0x3C = 0x20. Ninguno de los dos datos queda en la memoria: cada programación solo baja bits.

3. Un comparador en ROM

Un comparador de dos números de 4 bits tiene tres salidas (A > B, A = B, A < B). ¿Qué tamaño tiene su ROM? ¿Por qué un 74HC85 es mejor?

Ver solución

8 entradas → 256 palabras de 3 bits = 768 bits. El 74HC85 hace lo mismo con pocas compuertas y se cascadea fácilmente para más bits; con una ROM, cada bit de más duplica la tabla.

4. Un generador de función

Una ROM con 256 muestras de seno es recorrida por un contador de 8 bits con reloj de 25,6 kHz. ¿Cuál es la frecuencia de la onda? ¿Y si se recorre una muestra de cada dos?

Ver solución

25 600 / 256 = 100 Hz. Saltando de a dos muestras, el ciclo completo tiene 128 pasos: 25 600 / 128 = 200 Hz (el doble, con la mitad de resolución).

5. Checksum a mano

Un firmware tiene los bytes 0x10, 0x20 y 0x30. ¿Cuál es el byte de checksum que hace que la suma total sea 0 módulo 256?

Ver solución

La suma es 0x60. El complemento a dos módulo 256 es 0x100 − 0x60 = 0xA0. Comprobación: 0x60 + 0xA0 = 0x100 → 0x00 módulo 256.

6. Diagnóstico

Se reprogramó una EPROM que ya tenía una versión anterior del firmware, sin borrarla antes. El programador dio error de verificación en varias palabras. ¿Por qué y cómo se arregla?

Ver solución razonada

Cada palabra quedó con (contenido viejo) AND (contenido nuevo): los bits que ya estaban en 0 no pudieron volver a 1, aunque la versión nueva los necesitara. Se arregla con un borrado completo con UV, un chequeo de borrado (todo 0xFF) y recién entonces programar y verificar de nuevo.

12 · Síntesis

Ideas para recordar

  • Una ROM es una memoria no volátil que en funcionamiento normal solo se lee (no tiene WE̅). Se organiza como cualquier memoria: n líneas de dirección y m de datos.
  • Por dentro es un decodificador (todos los minitérminos) y una matriz de conexiones: una tabla de verdad. Con ella se implementa cualquier función combinacional de n entradas y m salidas.
  • ROM de máscara: contenido fijado en fábrica. PROM: se programa una vez quemando fusibles. EPROM: celda de compuerta flotante, se programa con tensión alta y se borra con luz ultravioleta.
  • Programar solo lleva bits de 1 a 0 (resultado = anterior AND dato); borrar devuelve todo a 1 (0xFF). Antes de programar se hace un chequeo de borrado y después una verificación.
  • Una tabla de consulta reemplaza un cálculo por una lectura: siete segmentos, senoides, conversiones de código, linealización de sensores; en software, un arreglo constante en Flash.
  • El tamaño de una ROM crece 2n con las entradas: conviene con pocas entradas (hasta unas 10 a 12) y función irregular. Las LUT de una FPGA son pequeñas ROM.
  • El contenido se verifica con un checksum (barato, débil) o un CRC (mucho más robusto); un bootloader serio comprueba el CRC antes de arrancar.
  • El procesador arranca ejecutando lo que hay en la ROM en su vector de reset; sin ese contenido, no arranca.

¿Listo para el tema 29?

Antes de avanzar, explicá de memoria qué le pasa a una palabra de EPROM cuando se la programa dos veces, cómo se implementa una función lógica con una ROM y por qué una ROM de 16 entradas no es práctica para un sumador. Si podés justificar las tres respuestas, ya podés pasar a las memorias que se reescriben eléctricamente.

En el próximo tema veremos la EEPROM: datos que permanecen sin energía y se reescriben byte a byte sin luz ultravioleta, su retención y sus límites de ciclos de escritura.