37. Circuitos secuenciales

Los circuitos secuenciales incorporan memoria: sus salidas dependen de las entradas actuales y del estado almacenado. El reloj, los flip-flops, los registros y los contadores permiten construir sistemas digitales que evolucionan con el tiempo.

37.1 Introducción

Un circuito combinacional calcula una salida a partir de las entradas presentes. Si las entradas vuelven a ser iguales, la salida también; no conserva información del pasado.

Muchos sistemas necesitan recordar: un contador debe conocer su valor anterior, un teclado debe registrar una pulsación y un protocolo debe saber en qué etapa se encuentra. Los circuitos secuenciales añaden elementos de almacenamiento para representar ese estado.

37.2 Estado y memoria

El estado es la información interna que resume lo relevante del pasado para determinar el comportamiento futuro. En un circuito secuencial, el estado suele almacenarse en uno o varios bits.

Circuito combinacional:
salida = f(entradas actuales).

Circuito secuencial:
salida = f(entradas actuales, estado actual).
estado siguiente = g(entradas actuales, estado actual).

La función g actualiza la memoria. Esta separación entre estado actual, entrada, salida y estado siguiente también aparece en programas con variables, objetos y máquinas de estados.

37.3 El reloj

Un reloj o clock es una señal periódica que coordina cuándo se actualizan los elementos de memoria. En muchos diseños síncronos, los cambios de estado ocurren solo en un flanco de subida o de bajada.

Entre flancos: las entradas y la lógica combinacional pueden estabilizarse.
En el flanco activo: los flip-flops capturan el nuevo valor.
Después del flanco: el nuevo estado alimenta al resto del circuito.

El reloj no hace que un circuito sea correcto por sí solo. Deben respetarse tiempos de propagación y de estabilidad para que cada elemento capture datos confiables.

37.4 Latches y flip-flops

Un latch es normalmente sensible a un nivel de habilitación: mientras está habilitado puede reflejar cambios en su entrada. Un flip-flop captura un valor en un instante asociado al flanco del reloj.

Latch: sensible a nivel.
Flip-flop: sensible a flanco.

Ambos almacenan información, pero su control temporal es diferente.

En explicaciones introductorias a veces se usan como sinónimos, pero distinguirlos es importante al diseñar hardware. Los sistemas síncronos modernos usan con frecuencia flip-flops controlados por reloj.

37.5 Latch SR

El latch SR es uno de los elementos de memoria más simples. Tiene entradas S (set, establecer) y R (reset, borrar), y una salida Q que almacena un bit.

SRQ siguienteAcción
00Q actualmantener
101set
010reset
11no válidoestado prohibido en la versión NOR activa en alto

La última combinación no debe utilizarse en el latch SR construido con puertas NOR. Puede dejar las salidas complementarias en una condición inconsistente y producir un estado posterior impredecible.

37.6 Latch D

El latch D evita la combinación prohibida del SR. Tiene una entrada de datos D y una habilitación E. Cuando E está activa, Q sigue a D; cuando E no está activa, Q conserva su valor.

Si E = 1: Q siguiente = D.
Si E = 0: Q siguiente = Q actual.

La señal complementaria de D alimenta internamente la entrada opuesta del latch SR.

El latch D es útil cuando se desea que la memoria sea transparente durante una fase controlada. Para capturar datos en un único instante se prefiere un flip-flop D.

37.7 Flip-flop D

El flip-flop D captura el valor de D en el flanco activo del reloj y lo mantiene hasta el próximo flanco activo. Es el bloque básico de muchos registros.

En el flanco del reloj: Q siguiente = D.
Fuera del flanco: Q conserva su valor.

D representa el dato que se desea almacenar;
Q representa el dato ya almacenado.

La regla parece simple, pero permite separar un cálculo en etapas. La lógica combinacional prepara el dato y el flip-flop lo registra de manera coordinada.

37.8 Simular un flip-flop D

class FlipFlopD {
  constructor(valorInicial = false) {
    this.q = valorInicial;
  }

  flancoDeReloj(d) {
    this.q = Boolean(d);
    return this.q;
  }

  salida() {
    return this.q;
  }
}

const memoria = new FlipFlopD();
console.log(memoria.salida());              // false
console.log(memoria.flancoDeReloj(true));   // true
console.log(memoria.salida());              // true

El método flancoDeReloj representa el único momento en que se actualiza el estado. Cambiar una variable de entrada entre llamadas no altera Q hasta simular otro flanco.

37.9 Flip-flop JK

El flip-flop JK generaliza la idea del SR y resuelve su combinación prohibida. Con J = K = 1, la salida cambia al valor opuesto, es decir, conmuta.

JKQ siguienteAcción
00Q actualmantener
010reset
101set
11¬Q actualconmutar

La capacidad de conmutar hace útil al JK en contadores y divisores de frecuencia. En diseños modernos suele describirse el comportamiento con flip-flops D y lógica adicional.

37.10 Flip-flop T

El flip-flop T tiene una entrada T de toggle. Si T es 0, mantiene el estado; si T es 1, invierte la salida en cada flanco de reloj.

T = 0 ⟹ Q siguiente = Q actual.
T = 1 ⟹ Q siguiente = ¬Q actual.

Un flip-flop T puede construirse a partir de un JK con J = K = T.

Un T que conmuta en cada flanco produce una señal con la mitad de frecuencia que el reloj. Encadenar varios permite construir contadores binarios sencillos.

37.11 Tiempos de setup y hold

Para que un flip-flop capture correctamente D, la entrada debe permanecer estable durante una ventana temporal alrededor del flanco del reloj.

Tiempo de setup: D debe estar estable antes del flanco.
Tiempo de hold: D debe mantenerse estable después del flanco.

Si se violan, el resultado puede ser incorrecto o tardar en estabilizarse.

Estas restricciones vinculan la frecuencia máxima del reloj con el retraso de la lógica combinacional. Son propiedades físicas del componente, no reglas visibles en una tabla de verdad ideal.

37.12 Metastabilidad y sincronización

Si una entrada cambia demasiado cerca del flanco, un flip-flop puede entrar temporalmente en metastabilidad: no se comporta de inmediato como un 0 o un 1 estable. No se puede eliminar por completo, pero se puede reducir su riesgo.

Una señal asíncrona no debe asumirse sincronizada con el reloj.

Una técnica común es pasarla por varios flip-flops sincronizadores antes de usarla en la lógica principal.

Los botones, sensores y señales provenientes de otro dominio de reloj requieren atención especial. El modelo booleano por sí solo no representa estos problemas de tiempo.

37.13 Registros

Un registro es un conjunto de flip-flops que almacena varios bits a la vez. Un registro de 8 bits tiene ocho flip-flops D que capturan un byte en el mismo flanco de reloj.

Registro de n bits:
Dn-1 ... D0 → n flip-flops → Qn-1 ... Q0.

Todos los bits se actualizan de manera coordinada.

Los registros guardan operandos, direcciones, instrucciones y resultados intermedios. Son componentes fundamentales de procesadores y controladores digitales.

37.14 Registro de desplazamiento

Un registro de desplazamiento conecta la salida de un flip-flop con la entrada del siguiente. En cada pulso, los bits se desplazan una posición y puede entrar un nuevo bit por un extremo.

Estado inicial de 4 bits: 1 0 1 0.
Nuevo bit de entrada: 1.
Tras un pulso: 1 1 0 1, según la dirección elegida.

La convención de orden debe documentarse.

Estos registros se usan para convertir datos serie/paralelo, retrasar señales y generar secuencias. La dirección y el orden de bits son detalles críticos del diseño.

37.15 Contadores

Un contador recorre una secuencia de estados con cada pulso de reloj. Un contador binario de tres bits recorre de 000 a 111 y luego vuelve a 000.

Contador módulo 8:
000 → 001 → 010 → 011 → 100 → 101 → 110 → 111 → 000.

Un contador módulo m tiene exactamente m estados útiles.

Los contadores se usan en temporizadores, direcciones de memoria, generadores de secuencia y divisores de frecuencia. El módulo puede ser una potencia de dos o un valor arbitrario con lógica de reinicio adecuada.

37.16 Simular un contador modular

class ContadorModulo {
  constructor(modulo) {
    if (!Number.isInteger(modulo) || modulo <= 0) throw new Error("módulo inválido");
    this.modulo = modulo;
    this.valor = 0;
  }

  flancoDeReloj(reset = false) {
    this.valor = reset ? 0 : (this.valor + 1) % this.modulo;
    return this.valor;
  }
}

const contador = new ContadorModulo(4);
console.log(contador.flancoDeReloj()); // 1
console.log(contador.flancoDeReloj()); // 2
console.log(contador.flancoDeReloj()); // 3
console.log(contador.flancoDeReloj()); // 0

La clase representa el estado como un número, mientras que el hardware real lo codifica con varios flip-flops. La operación módulo expresa el retorno al estado inicial.

37.17 Máquinas de estados finitos

Una máquina de estados finitos tiene un número limitado de estados y reglas de transición. Los circuitos secuenciales son una forma de implementar estas máquinas.

Estado actual + entrada → estado siguiente.

Ejemplo de puerta automática:
Cerrada + credencial válida → Abierta.
Abierta + cierre → Cerrada.

En una máquina de Moore las salidas dependen solo del estado; en una máquina de Mealy dependen del estado y de las entradas. Esta distinción ayuda a diseñar controladores y protocolos.

37.18 Simular una máquina de estados

class PuertaAutomatica {
  constructor() {
    this.estado = "cerrada";
  }

  flancoDeReloj({ credencialValida, cerrar }) {
    if (this.estado === "cerrada" && credencialValida) this.estado = "abierta";
    else if (this.estado === "abierta" && cerrar) this.estado = "cerrada";
    return this.estado;
  }
}

const puerta = new PuertaAutomatica();
console.log(puerta.flancoDeReloj({ credencialValida: true, cerrar: false })); // abierta
console.log(puerta.flancoDeReloj({ credencialValida: false, cerrar: true })); // cerrada

Esta simulación modela una transición por llamada, como si cada llamada fuera un pulso de reloj. Un sistema real también debe definir qué ocurre con señales simultáneas, fallos y entradas no previstas.

37.19 Diseño síncrono

Un diseño síncrono separa la lógica en dos partes: lógica combinacional que calcula el próximo estado y flip-flops que registran ese estado en el flanco del reloj.

Estado actual → lógica combinacional → estado siguiente.

flip-flops en el reloj.

Las salidas pueden derivarse del estado, de las entradas o de ambos.

Esta organización facilita razonar sobre el tiempo: hay un ciclo completo de reloj para que la lógica combinacional produzca un valor estable antes de que el estado vuelva a capturarse.

37.20 Circuitos secuenciales y programación

Variables que persisten entre iteraciones, objetos con campos mutables y máquinas de estados de una interfaz son análogos de memoria en software. Su resultado depende de llamadas anteriores, no solo de los argumentos actuales.

  • Un contador de vistas conserva un total previo.
  • Un flujo de compra conoce en qué paso está el usuario.
  • Un parser conserva el estado de lectura de una entrada.
  • Un protocolo de red registra el estado de una conexión.

Hacer explícitos los estados y transiciones mejora la prueba y el mantenimiento. Es la misma disciplina que exige un circuito secuencial bien definido.

37.21 Errores frecuentes

  • Confundir un circuito combinacional con uno que almacena estado.
  • Usar la combinación prohibida de un latch SR sin analizar su implementación.
  • Ignorar tiempos de setup, hold y propagación al hablar de hardware real.
  • Suponer que una señal externa llega sincronizada con el reloj.
  • No definir el estado inicial o la señal de reset de un circuito.
  • Mezclar el orden de bits o la dirección de desplazamiento en registros.

37.22 Qué debes recordar y conclusión

  • Un circuito secuencial depende de entradas actuales y de estado almacenado.
  • El reloj coordina cuándo se capturan y actualizan los datos.
  • Latches y flip-flops almacenan bits con mecanismos temporales diferentes.
  • Registros guardan varios bits; registros de desplazamiento y contadores construyen secuencias.
  • Las máquinas de estados finitos modelan transiciones controladas por entradas.
  • El modelo lógico debe complementarse con consideraciones físicas de temporización y sincronización.

Los circuitos secuenciales añaden tiempo y memoria a la lógica booleana. En el próximo tema iniciaremos la teoría de grafos, otra herramienta esencial para modelar conexiones, redes y dependencias en programación.