← Índice del curso

Circuitos Secuenciales y Memoria · Tema 30

Memoria Flash

Es la memoria no volátil que hay en casi todo: el programa de un microcontrolador, el almacenamiento de un teléfono, un pendrive, una tarjeta SD, un SSD. La Flash es una EEPROM que cambió la facilidad de escribir un byte por una enorme densidad y un costo muy bajo: se programa en unidades chicas pero se borra en bloques grandes. Esa asimetría condiciona todo lo que se hace con ella, desde cómo se guarda un dato hasta cómo se organiza el sistema de archivos.

01 · Punto de partida

Por qué existe la Flash

La EEPROM del tema 29 permite reescribir un byte sin tocar los demás. Para lograrlo, cada celda necesita un transistor de selección extra, y por eso es grande y cara por bit. La Flash toma la misma celda de compuerta flotante y renuncia a esa selección individual: el borrado se hace de a muchas celdas a la vez, un bloque entero. A cambio, la celda es mínima, se pueden fabricar miles de millones por chip y el costo por bit cae en picada.

El nombre alude a esa forma de borrar: «en un destello». El resultado es una memoria que ocupa el centro de la jerarquía de almacenamiento no volátil actual.

EEPROM frente a Flash.
AspectoEEPROMFlash
Unidad de borradoByte (o página)Sector o bloque (kilobytes a megabytes)
Unidad de programaciónByte o páginaByte, palabra o página (según el tipo)
Celda2 transistores1 transistor
Densidad y costo por bitBaja, caraAlta, barata
Capacidad típicaBytes a decenas de KiBKiB a cientos de GiB
Ciclos de escritura por celda105 a 106103 a 105 de borrado
Uso típicoConfiguración, calibraciónPrograma, datos, almacenamiento masivo

02 · Celda y tipos

Celda, NOR y NAND

La celda es un transistor de compuerta flotante (temas 28 y 29): la carga atrapada cambia el umbral y eso se lee como 0 o 1. Lo que distingue a las dos grandes familias es cómo se conectan las celdas:

  • NOR: las celdas se conectan en paralelo a la línea de bit, como las entradas de una compuerta NOR. Cada celda se puede leer individualmente: acceso aleatorio rápido, a nivel de byte. Se puede ejecutar código directamente desde ella (execute in place, XIP).
  • NAND: las celdas se conectan en serie, en cadenas de 32 o 64. Ocupan mucho menos área, pero para leer una hay que hacer conducir a todas las demás de la cadena, y el acceso es por páginas enteras (2 a 16 KiB), no por bytes.
NOR frente a NAND.
AspectoFlash NORFlash NAND
LecturaAleatoria, por byte, ~50–100 nsPor página (25–100 µs), luego salida serie
ProgramaciónPor byte o palabra, µsPor página (2–16 KiB), 200–700 µs
BorradoSector (4–128 KiB), decenas a cientos de msBloque (128 KiB–4 MiB), 2–5 ms
Ejecutar código directamenteSíNo (se copia a RAM)
Densidad y costo por bitMenor, más caraMayor, mucho más barata
FiabilidadAlta, sin bloques defectuosos de fábricaRequiere corrección de errores (ECC) y gestión de bloques defectuosos
Ciclos de borrado104 a 105SLC ~105; MLC ~3 000; TLC ~1 000
Uso típicoFirmware, microcontroladoresSSD, SD, pendrives, eMMC, teléfonos

Los microcontroladores traen Flash tipo NOR (o similar) para el programa: la CPU lee instrucciones de ella con acceso aleatorio. Los dispositivos de almacenamiento masivo usan NAND, con un controlador que se ocupa de la corrección de errores, del desgaste y de los bloques defectuosos, y que presenta al sistema un disco «normal» (tema 31 y más allá).

La NAND moderna guarda más de un bit por celda distinguiendo varios niveles de carga: dos niveles (SLC, 1 bit), cuatro (MLC, 2 bits), ocho (TLC, 3 bits) o dieciséis (QLC, 4 bits). Más bits por celda significan más capacidad por chip, pero niveles más apretados: la lectura es más lenta, hace falta más ECC y los ciclos de borrado bajan de unos cien mil a unos pocos miles o menos.

03 · Las reglas

Las tres reglas: leer, programar, borrar

Toda Flash obedece las mismas tres reglas, heredadas de la EPROM (tema 28), y programar con ellas en mente es lo que separa un uso correcto de uno que se rompe:

Operaciones de una Flash.
OperaciónUnidadQué haceDesgasta
LeerByte (NOR) o página (NAND)Devuelve el contenidoNo
ProgramarPalabra o páginaLleva bits de 1 a 0 (nuevo = anterior AND dato)Poco
BorrarSector o bloque completoLleva todos sus bits a 1 (0xFF)Sí: un ciclo de borrado

De ahí salen consecuencias directas:

  • No se puede sobrescribir. Cambiar un byte de 0x3C a 0x7E exige subir un bit de 0 a 1, y eso solo se logra borrando todo el sector. Programar 0x7E encima de 0x3C deja 0x3C AND 0x7E = 0x3C: no cambia.
  • Cambiar un byte es caro. La única forma general es leer el sector entero, modificar el byte en RAM, borrar el sector y reprogramarlo completo (leer-modificar-borrar-escribir, RMW). Hace falta tener RAM para el sector, y el costo es el de un borrado más la reescritura de todo.
  • Un borrado interrumpido es peligroso. Si se corta la energía durante el borrado o la programación, el sector queda con datos indefinidos, y todo lo que hubiera en él (aunque no se quisiera cambiar) se pierde.
  • Antes de programar, hay que estar seguro de que está borrado. Programar sobre datos viejos produce el AND de ambos, un valor que no es ninguno de los dos.

04 · Tiempos

Tiempos y amplificación de escritura

Leer es rápido. Programar y sobre todo borrar son lentos, porque requieren tensiones altas y tiempos de túnel: el borrado de un sector NOR lleva decenas a cientos de milisegundos. Esto deriva en un concepto central: la amplificación de escritura (write amplification), la relación entre los bytes que realmente se escriben en la Flash y los que el programa quiso cambiar.

Cambiar un byte en un sector NOR de 4 KiB (borrado 45 ms, programación 10 µs por byte).
MétodoTiempo por actualizaciónBorradosBytes programados por 1 útil
A. Reescribir el sector (RMW)≈ 86 ms1 por actualización4096
B. Agregar una entrada de 8 bytes a un registro≈ 0,08 ms1 cada 512 actualizaciones8

El método A es mil veces más lento y gasta el sector mil veces más rápido. El método B (registro o log) aprovecha lo que la Flash sí hace bien: programar áreas vacías sin borrar. Se verá en la sección 7.

05 · Desgaste

Desgaste y nivelación

Cada borrado degrada un poco el óxido que rodea la compuerta flotante, exactamente como en la EEPROM (tema 29), de modo que cada bloque soporta una cantidad finita de borrados: de 103 a 105, según el tipo. La retención de los datos (más de 10 a 20 años en condiciones normales) también se reduce con el uso y el calor.

El problema es que las escrituras no se reparten por igual: una carpeta de registro, una tabla de asignación de archivos o un contador se reescriben mil veces más que un archivo de datos. Sin precauciones, esos pocos bloques «calientes» se gastan mucho antes que el resto. La respuesta es la nivelación del desgaste (wear leveling):

  • Se mantiene un mapa entre las direcciones lógicas que ve el sistema y los bloques físicos.
  • Cuando se reescribe un dato, no se borra y reescribe el mismo bloque: se escribe en un bloque físico poco usado y se actualiza el mapa.
  • Los bloques viejos se borran más tarde, en segundo plano (garbage collection).
  • Se lleva un contador de borrados por bloque y se mueven de vez en cuando los datos que casi no cambian a los bloques más gastados, para liberar los jóvenes.

Esa capa de software se llama FTL (Flash Translation Layer) y vive en el controlador de un SSD o una tarjeta SD, y en sistemas de archivos específicos para Flash en equipos embebidos. Junto con la corrección de errores (ECC) y el manejo de los bloques defectuosos, es lo que hace que una NAND de consumo, cuyas celdas fallan con frecuencia, se comporte como un disco confiable.

Una consecuencia curiosa para el usuario: el espacio sobrante («over-provisioning») de un SSD no es un desperdicio sino la reserva con la que la FTL mueve datos y nivela el desgaste. Un SSD casi lleno se vuelve más lento y se gasta más rápido.

06 · En un microcontrolador

La Flash en un microcontrolador

Un microcontrolador guarda su programa en Flash NOR interna, dividida en sectores o páginas. Algunas prácticas y trampas del programador de firmware:

  • Mapa típico: al comienzo, la tabla de vectores de interrupción y el bootloader (que puede reescribir el resto de la Flash); luego la aplicación. El bootloader suele protegerse contra escritura y borrado para que un error de actualización no «ladrille» el equipo.
  • Programación en la aplicación (IAP): el programa mismo borra y reescribe sectores de Flash (para guardar datos o actualizar firmware). La secuencia típica: desbloquear el controlador de Flash con claves, esperar que no esté ocupado, borrar el sector, programar las palabras, verificar y volver a bloquear.
  • Leer mientras se escribe: si el código en ejecución está en la misma Flash que se borra, la CPU se detiene. Soluciones: ejecutar las rutinas de escritura desde la RAM, usar una Flash de dos bancos (mientras uno se borra, el otro sigue ejecutando código) o aceptar la pausa en un momento seguro.
  • Granularidad de programación: muchas Flash exigen programar por palabras de 16, 32 o 64 bits alineadas, y a veces no admiten reprogramar la misma palabra sin borrar (por el ECC interno). Hay que respetarlo.
  • Opciones de arranque y protección: bytes de configuración que fijan, por ejemplo, la protección de lectura del firmware (para que no se pueda copiar) o el origen del arranque.
  • Tensión baja: borrar o programar con la alimentación por debajo del mínimo es riesgoso. Muchos controladores bloquean la operación si detectan tensión insuficiente.
Dónde va cada cosa en una Flash típica de microcontrolador.
ZonaContenidoProtección habitual
Sectores inicialesVectores y bootloaderProtegidos contra escritura
Zona de aplicaciónCódigo y constantesSe reescriben solo al actualizar
Últimos sectoresDatos de usuario (emulación de EEPROM)Escritura frecuente: registro con rotación
Bytes de opcionesProtección de lectura, arranqueBloqueo con claves

07 · Datos en Flash

Guardar datos en Flash: el registro

Muchos microcontroladores no tienen EEPROM y se usa un par de sectores de Flash para guardar configuración. Como cambiar un valor reescribiendo el sector entero es lento (86 ms) y gasta rápido el sector, la técnica habitual es el registro (log) sobre dos sectores:

  1. Cada actualización se agrega como una entrada nueva al final del sector activo: número de secuencia, dato y CRC. Programar un área vacía (0xFF) es rápido y no requiere borrar.
  2. Para leer el valor actual se recorre el sector y se toma la entrada más nueva con CRC válido.
  3. Cuando el sector activo se llena, se copia la última entrada al otro sector (que se borra antes) y se pasa a usar ese.
  4. El sector viejo se borra la próxima vez que haga falta. Solo hay un borrado cada N actualizaciones, con N = entradas por sector.

Con entradas de 8 bytes en sectores de 4 KiB, N = 512: el desgaste cae unas 500 veces. Una entrada escrita a medias por un corte de energía queda con un CRC inválido y se descarta, igual que la ranura de dos copias del tema 29: es el mismo diseño, adaptado a la asimetría de la Flash (programar es barato y se hace en lugar vacío; borrar es caro y se difiere).

08 · La Flash en código

Borrar, medir el costo, nivelar y llevar un registro

Los bloques modelan las reglas de la Flash, calculan el costo de cambiar un byte con y sin registro, simulan la nivelación del desgaste con una carga concentrada en pocos bloques y verifican una emulación de EEPROM sobre dos sectores con CRC.

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: las reglas y el leer-modificar-borrar-escribir

class Flash:
    """Flash NOR simplificada: se programa por byte (solo 1 -> 0) y se borra por SECTOR (todo a 0xFF)."""

    def __init__(self, sectores=4, tam_sector=16):
        self.tam = tam_sector
        self.datos = [0xFF] * (sectores * tam_sector)
        self.borrados = [0] * sectores

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

    def programar(self, a, v):
        self.datos[a] &= v                       # solo se pueden bajar bits
        return self.datos[a]

    def borrar_sector(self, s):
        for i in range(s * self.tam, (s + 1) * self.tam):
            self.datos[i] = 0xFF
        self.borrados[s] += 1


f = Flash()
print("virgen:", f.leer(5) == 0xFF)
print("programo 0x3C en 5:", hex(f.programar(5, 0x3C)))
print("quiero cambiarlo a 0x7E: programo 0x7E encima ->", hex(f.programar(5, 0x7E)), "(no es 0x7E: había un 0 que no vuelve a 1)")


def modificar_byte(flash, a, v):
    """Leer-modificar-borrar-escribir: lo único que se puede hacer para cambiar un byte a cualquier valor."""
    s = a // flash.tam
    copia = flash.datos[s * flash.tam:(s + 1) * flash.tam]
    copia[a % flash.tam] = v
    flash.borrar_sector(s)
    for i, b in enumerate(copia):
        if b != 0xFF:
            flash.programar(s * flash.tam + i, b)


modificar_byte(f, 5, 0x7E)
print("con leer-modificar-borrar-escribir:", hex(f.leer(5)), "| borrados del sector 0:", f.borrados[0])
print("el resto del sector se conserva:", all(f.leer(i) == 0xFF for i in range(16) if i != 5))

Programar 0x7E encima de 0x3C deja 0x3C: ningún bit pasa de 0 a 1. Solo el leer-modificar-borrar-escribir logra el cambio: el sector se copia, se modifica el byte en la copia, se borra el sector (un ciclo de borrado) y se reprograma lo que no sea 0xFF. El resto de los bytes del sector se conserva. Cambiar un byte costó un borrado entero.

Paso 2: el costo de cambiar un byte

# Costo de cambiar UN byte en una Flash NOR con sectores de 4 KiB.
t_borrado_ms = 45.0           # borrado de un sector
t_prog_us = 10.0              # programación de un byte (aprox., depende del chip)
sector = 4096
print(f"sector de {sector} bytes: borrar {t_borrado_ms} ms, programar un byte {t_prog_us} us\n")

# A) leer-modificar-borrar-escribir el sector completo
t_a = t_borrado_ms + sector * t_prog_us / 1000
print(f"A) cambiar 1 byte reescribiendo el sector: {t_a:.1f} ms, 1 borrado, {sector} bytes programados")
print(f"   amplificación de escritura: se programaron {sector} bytes para guardar 1 -> x{sector}")

# B) registro (log): se agrega una entrada de 8 bytes en un sector hasta llenarlo
entrada = 8
por_sector = sector // entrada
t_b = entrada * t_prog_us / 1000
borrados_por_actualizacion = 1 / por_sector
print(f"\nB) agregar una entrada de {entrada} bytes a un registro: {t_b:.2f} ms por actualización")
print(f"   {por_sector} actualizaciones caben en un sector -> {borrados_por_actualizacion:.5f} borrados por actualización")
print(f"   el borrado de {t_borrado_ms} ms se reparte: {t_borrado_ms / por_sector:.3f} ms por actualización")

endurance = 10_000            # ciclos de borrado por sector
print(f"\nvida útil con {endurance:,} borrados por sector y una actualización por segundo:")
print(f"   A (siempre el mismo sector):  {endurance / 3600:.1f} horas")
print(f"   B (registro en un sector):    {endurance * por_sector / 86400:.0f} días ({endurance * por_sector / 86400 / 365:.1f} años)")

Reescribir el sector para cambiar un byte tarda unos 86 ms (45 de borrado y 41 de reprogramar 4096 bytes) y programa 4096 bytes para guardar 1: amplificación de 4096. Con un registro de entradas de 8 bytes, cada actualización cuesta 0,08 ms y el sector se borra una vez cada 512. Con 10 000 borrados por sector y una actualización por segundo, el método A dura 2,8 horas y el B, 59 días: unas 500 veces más.

Paso 3: nivelación del desgaste

import random

rnd = random.Random(30)
BLOQUES = 64
ESCRITURAS = 20_000


def simular(nivelar):
    borrados = [0] * BLOQUES
    mapa = list(range(BLOQUES))                      # bloque lógico -> bloque físico
    for _ in range(ESCRITURAS):
        # 80 % de las escrituras van a solo 4 bloques lógicos ("calientes")
        logico = rnd.randrange(4) if rnd.random() < 0.8 else rnd.randrange(BLOQUES)
        if nivelar:
            # elegir el bloque físico menos gastado y llevar ahí el bloque lógico
            fisico = min(range(BLOQUES), key=lambda b: borrados[b])
            mapa[logico] = fisico
        else:
            fisico = mapa[logico]
        borrados[fisico] += 1
    return borrados


for nivelar in (False, True):
    b = simular(nivelar)
    prom = sum(b) / BLOQUES
    print(f"{'con' if nivelar else 'sin'} nivelación: borrados por bloque  máx {max(b):5d}  mín {min(b):4d}  promedio {prom:6.1f}  máx/promedio {max(b) / prom:5.2f}")

endurance = 3_000
print()
for nivelar in (False, True):
    b = simular(nivelar)
    vida = endurance / max(b) * ESCRITURAS
    print(f"{'con' if nivelar else 'sin'} nivelación: con {endurance:,} ciclos por bloque, la primera falla llega tras ~{vida:,.0f} escrituras")

Con 80 % de las escrituras sobre solo 4 bloques, sin nivelación el bloque más usado recibe 4081 borrados (13 veces el promedio de 312,5) mientras otros reciben 43. Con nivelación, todos quedan en 312 o 313. La vida útil, medida como escrituras hasta la primera falla con 3000 ciclos por bloque, pasa de unas 14 000 a unas 192 000 (aproximadamente 13 veces más). El modelo es ideal: una FTL real paga el costo de mover datos de larga duración.

Paso 4: emulación de EEPROM con un registro

from binascii import crc_hqx

SECTOR = 4096
ENTRADA = 8                                    # [secuencia (2), dato (4), CRC (2)]
POR_SECTOR = SECTOR // ENTRADA


class LogFlash:
    """Emulación de EEPROM sobre dos sectores de Flash: se agregan entradas; al llenarse un sector, se copia el último dato al otro y se borra el viejo."""

    def __init__(self):
        self.sectores = [bytearray([0xFF] * SECTOR) for _ in range(2)]
        self.activo = 0
        self.libre = 0
        self.seq = 0
        self.borrados = [0, 0]
        self.escrituras = 0

    def _entrada(self, seq, dato):
        cuerpo = seq.to_bytes(2, "big") + dato.to_bytes(4, "big")
        return cuerpo + crc_hqx(cuerpo, 0xFFFF).to_bytes(2, "big")

    def _agregar(self, dato):
        self.seq = (self.seq + 1) & 0xFFFF
        e = self._entrada(self.seq, dato)
        a = self.libre * ENTRADA
        for i, b in enumerate(e):
            self.sectores[self.activo][a + i] &= b           # programar: solo 1 -> 0
        self.libre += 1
        self.escrituras += 1

    def guardar(self, dato):
        if self.libre == POR_SECTOR:                         # sector lleno: pasar al otro
            ultimo = self.cargar()                           # el último dato válido, leído ANTES de cambiar de sector
            nuevo = 1 - self.activo
            self.borrados[nuevo] += 1                        # se borra el sector que va a pasar a ser el activo
            self.sectores[nuevo] = bytearray([0xFF] * SECTOR)
            self.activo, self.libre = nuevo, 0
            self._agregar(ultimo)                            # copiarlo al comienzo del sector nuevo
        self._agregar(dato)

    def cargar(self):
        mejor = None
        for i in range(self.libre):
            e = self.sectores[self.activo][i * ENTRADA:(i + 1) * ENTRADA]
            if crc_hqx(bytes(e[:6]), 0xFFFF) == int.from_bytes(e[6:], "big"):
                seq, dato = int.from_bytes(e[:2], "big"), int.from_bytes(e[2:6], "big")
                if mejor is None or seq >= mejor[0]:
                    mejor = (seq, dato)
        return mejor[1] if mejor else None


log = LogFlash()
N = 10_000
for k in range(1, N + 1):
    log.guardar(k)
print(f"{N:,} actualizaciones de un valor de 4 bytes en dos sectores de {SECTOR} bytes:")
print(f"  entradas por sector: {POR_SECTOR}")
print(f"  borrados de sector realizados: {sum(log.borrados)}   (sin registro habría sido {N:,}, uno por actualización)")
print(f"  reducción del desgaste: x{N / max(1, sum(log.borrados)):.0f}")
print(f"  valor recuperado al 'reiniciar': {log.cargar()} (esperado {N})")

# corte de energía a mitad de la escritura de una entrada: la entrada tiene un CRC inválido y se ignora
log.sectores[log.activo][log.libre * ENTRADA:log.libre * ENTRADA + 3] = bytearray([0x00, 0x7F, 0x12])    # entrada a medias
log.libre += 1
print(f"  tras un corte a medio guardar: {log.cargar()} (la entrada incompleta se descarta por su CRC)")

En 10 000 actualizaciones solo hay 19 borrados de sector, contra los 10 000 que haría falta con reescritura del sector: el desgaste cae unas 526 veces (cada sector aloja 512 entradas, una de ellas ocupada por la copia al cambiar de sector). Al reiniciar, el valor recuperado es 10 000, el último. Una entrada escrita a medias (3 bytes) tiene un CRC inválido y se descarta, y se sigue cargando 10 000. Es el diseño de un registro sobre Flash.

Los cuatro bloques muestran el hilo conductor: la Flash es barata y densa a cambio de una regla asimétrica (programar es fácil, borrar es caro y por bloque), y todo el arte de usarla está en sortear esa asimetría.

09 · Exploración interactiva

El laboratorio: sectores que se borran

El simulador es una Flash NOR de 4 sectores de 8 bytes (32 bytes), con una resistencia de entre 5 y 50 borrados por sector (muy reducida para que el desgaste sea visible). Podés leer, programar un byte (la regla del AND), borrar un sector entero y cambiar un byte con leer-modificar-borrar-escribir. Dos botones llevan un contador: el método A lo reescribe siempre en el sector 0 (borrándolo en cada incremento), y el método B agrega entradas a un registro en el sector 2 y solo borra cuando se llena.

Flash NOR de 4 sectores × 8 bytes

Sectores de la Flash simulada con su desgaste Cuatro sectores, cada uno con ocho bytes en hexadecimal y una barra de ciclos de borrado usados sobre el límite. Un sector gastado se marca con borde naranja.
Tiempo acumulado0 ms
Borrados de sector0
Bytes programados0
Operaciones fallidas0
Valor real del contador0
Contador A (dirección 0)255
Contador B (último del registro)—
Mayor número de borrados en un sector—

Flash nueva con 10 ciclos de borrado por sector. Programá un byte o probá los dos contadores.

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

Recorrido guiado

  1. Con la dirección 0 y el dato 3C, presioná «Programar byte»: la celda toma 0x3C (AND con 0xFF). «Leer» no gasta nada.
  2. Cambiá el dato a 7E y presioná «Programar byte» sobre la misma dirección: queda 0x3C, no 0x7E (programar solo baja bits). El simulador lo marca como fallo y avisa que hay que borrar.
  3. Presioná «Borrar el sector de esa dirección»: los 8 bytes del sector vuelven a FF y aparece un borrado en la barra. Reprogramá 7E: ahora sí.
  4. Presioná «Cambiar byte (leer-modificar-borrar-escribir)» con un nuevo dato: tarda 50 ms de borrado más la reprogramación y consume un ciclo de borrado, aunque solo hayas cambiado un byte.
  5. Reiniciá y presioná «Contador A ×10» dos veces: con 10 ciclos de resistencia, el sector 0 soporta diez borrados; en el undécimo incremento ya no se puede borrar y el contador deja de avanzar. «Contador A» queda en 10 mientras el real llega a 20.
  6. Reiniciá y presioná «Contador B ×10» ocho veces (80 incrementos): el registro del sector 2 se llena cada 8 entradas y solo entonces se borra; los 10 ciclos alcanzan para unos 80 incrementos. Los contadores A y B del mismo recorrido muestran 8 veces más vida para B.
  7. Subí la resistencia a 50 y repetí: A resiste 50 incrementos; B, unos 400.
  8. Fijate en «Tiempo acumulado» entre los dos métodos: el borrado de 50 ms domina el costo de A, mientras que B casi no gasta tiempo.
Desafío: el costo de los datos que no cambian

En el método A de este laboratorio, cada incremento reescribe todo el sector 0, incluidos los 7 bytes que no cambian. En un sistema real, ese sector podría contener datos importantes que se borran y reescriben en cada actualización: si se corta la energía a mitad, todo el sector se pierde, no solo el contador. Es una razón más para preferir el registro y para no mezclar datos de distinta frecuencia de cambio en un mismo sector.

10 · Comprensión

Confusiones frecuentes

«Para cambiar un byte de Flash se lo sobrescribe»

No se puede sobrescribir. Hay que borrar el sector entero (todo a 0xFF) y reprogramar. Programar encima solo produce el AND del valor viejo y el nuevo.

«Se puede borrar un byte suelto»

El borrado es siempre por sector o bloque. Por eso la Flash es barata y por eso su uso para datos pequeños necesita estrategias como el registro.

«Leer de la Flash la desgasta»

Solo los borrados (y, en menor medida, las programaciones) desgastan. Leer es ilimitado.

«NOR y NAND son lo mismo con otro nombre»

NOR permite leer por byte y ejecutar código desde ella; NAND lee por páginas, es mucho más densa y barata, y exige ECC y gestión de bloques defectuosos.

«Un SSD casi lleno funciona igual de bien»

No: la FTL necesita espacio libre para mover datos y nivelar el desgaste. Con poco espacio libre, el rendimiento cae y el desgaste sube.

«Mientras se borra la Flash del microcontrolador, el programa sigue corriendo»

Si el código se ejecuta desde esa misma Flash, la CPU se detiene hasta que termina la operación. Hay que ejecutar desde la RAM o usar Flash de dos bancos.

«Borrar es seguro si se corta la energía a mitad»

El sector queda indefinido. Los datos importantes deben estar duplicados, con número de secuencia y CRC, en sectores distintos.

«Más bits por celda es siempre mejor»

Más capacidad y menor costo por bit, pero menos ciclos de borrado, más lentitud y más corrección de errores. TLC y QLC no son adecuadas para datos que se reescriben todo el tiempo.

11 · Práctica guiada

Predecí antes de simular

1. Tamaño de bloque NAND

Una NAND tiene páginas de 4 KiB y 128 páginas por bloque. ¿De qué tamaño es un bloque (la unidad de borrado) y cuántos bloques tiene un chip de 8 GiB?

Ver solución

Bloque: 128 · 4 KiB = 512 KiB. Chip de 8 GiB: 8 · 1024 · 1024 KiB / 512 KiB = 16 384 bloques.

2. Qué queda

Una posición de Flash vale 0x5A. Se la programa con 0xF5. ¿Qué queda? ¿Qué hay que hacer para llegar a 0xF5?

Ver solución

0x5A AND 0xF5 = 0x50. Para llegar a 0xF5 hay que borrar el sector (queda 0xFF) y luego programar 0xF5.

3. Costo de cambiar un byte

En un sector de 4 KiB, borrar lleva 45 ms y programar un byte 10 µs. Cambiar un byte reescribiendo el sector, ¿cuánto tarda y cuál es la amplificación de escritura (bytes programados por byte útil)?

Ver solución

45 ms + 4096 · 10 µs = 45 + 40,96 ≈ 86 ms. Se programan hasta 4096 bytes para guardar 1 útil: amplificación de 4096.

4. Vida de un registro

Un registro de entradas de 16 bytes en un sector de 4 KiB se actualiza cada 10 segundos. Con 10 000 ciclos de borrado por sector, ¿cuánto dura un sector (sin rotar entre sectores)?

Ver solución

Entradas por sector: 4096 / 16 = 256. Actualizaciones hasta gastarlo: 10 000 · 256 = 2 560 000. En tiempo: 2 560 000 · 10 s = 25 600 000 s ≈ 296 días. Alternando dos sectores o más, la vida se multiplica.

5. Nivelación ideal

Una memoria tiene 100 bloques de 10 000 ciclos. Todas las escrituras recaen en un solo bloque lógico. ¿Cuántas escrituras aguanta sin nivelación y con nivelación perfecta?

Ver solución

Sin nivelación: 10 000 escrituras (el bloque caliente se gasta). Con nivelación perfecta, repartidas entre los 100 bloques: 100 · 10 000 = 1 000 000 escrituras, cien veces más.

6. Diagnóstico

Un equipo guarda su configuración en una página de la Flash interna del microcontrolador. Cada vez que el usuario cambia algo, el equipo se «congela» unos 100 ms y a veces pierde pulsos de un sensor. ¿Qué ocurre y cómo se corrige?

Ver solución razonada

El borrado y la reprogramación del sector tienen lugar en la misma Flash de la que se ejecuta el programa, y la CPU queda detenida durante la operación (y las interrupciones no se atienden, de ahí los pulsos perdidos). Se corrige ejecutando la rutina de escritura desde RAM con las interrupciones críticas bajo control, usando una Flash de dos bancos o una EEPROM externa, y reduciendo la frecuencia de escritura con un registro, que evita el borrado en casi todas las actualizaciones.

12 · Síntesis

Ideas para recordar

  • La Flash es una EEPROM que se borra por bloques: la celda de un solo transistor da altísima densidad y bajo costo a cambio de no poder borrar un byte suelto.
  • Tres reglas: leer (rápido, sin desgaste), programar (solo de 1 a 0, nuevo = anterior AND dato) y borrar (por sector o bloque, todo a 0xFF; es lo que desgasta).
  • No se puede sobrescribir: cambiar un byte exige leer-modificar-borrar-escribir el sector, con una gran amplificación de escritura (4096 bytes para 1 útil en un sector de 4 KiB).
  • NOR: lectura por byte, ejecución en el lugar, ideal para firmware. NAND: lectura y programación por páginas, borrado por bloques, mucho más densa, con ECC y bloques defectuosos.
  • Cada bloque aguanta de 103 a 105 borrados (menos con más bits por celda). La nivelación del desgaste (FTL) reparte los borrados entre los bloques.
  • En un microcontrolador, borrar o programar la Flash donde se ejecuta el código detiene la CPU; se resuelve con rutinas en RAM, Flash de dos bancos o ventanas seguras.
  • Para guardar datos en Flash se usa un registro: se agregan entradas con secuencia y CRC en un sector vacío y se borra solo cuando se llena (≈ 500 veces menos borrados).
  • Un borrado o una programación interrumpidos dejan el sector indefinido: los datos críticos van duplicados y verificados.

¿Listo para el tema 31?

Antes de avanzar, explicá de memoria por qué cambiar un byte de Flash cuesta tanto, qué diferencia hay entre NOR y NAND y cómo se guarda un dato que cambia seguido sin gastar la memoria. Si podés justificar las tres respuestas, ya conocés todas las familias de memoria del curso.

En el próximo tema armaremos el taller del mapa de memoria y la selección: cómo decodificar las direcciones para repartir el espacio entre una ROM, una RAM y los periféricos, y cómo coordinar CS̅, OE̅ y WE̅ para que nunca haya dos chips hablando a la vez.