Construye una CPU desde cero en un simulador
Guía para una CPU didáctica de 8 bits con registros, ALU, bus compartido, RAM de bytes, instrucciones de 16 bits, banderas y control microsecuenciado.
Resumen: Esta guía define una arquitectura didáctica completa: ruta de datos y espacio de direcciones de 8 bits, RAM de bytes, instrucciones de 16 bits, cuatro registros, ALU, banderas, bus compartido y controlador microsecuenciado.
«Construir una CPU» puede significar desde conectar bloques ya preparados hasta diseñar transistores. Este tutorial se sitúa entre ambos extremos. Construirás un procesador a partir de bloques digitales —registros, multiplexores, decodificadores, una ALU, memoria y un almacén de control— y tratarás las puertas internas de cada bloque como un diseño que se puede probar por separado.
El objetivo no es imitar un núcleo comercial moderno. Es exponer el contrato que debe cumplir la implementación de cada instrucción:
- obtener los bytes correctos de la instrucción;
- conservarlos durante la decodificación;
- mover exactamente los operandos previstos;
- realizar la operación definida;
- actualizar únicamente el estado arquitectónico permitido; y
- seleccionar la dirección correcta de la siguiente instrucción.
1. Fija primero la arquitectura
Muchos proyectos de CPU fallan porque la anchura de memoria, la anchura de instrucción y el incremento del PC cambian a mitad de la construcción. Usa esta especificación de forma coherente:
| Propiedad | Definición de la CPU didáctica |
|---|---|
| Anchura de datos | 8 bits |
| Anchura de direcciones | 8 bits |
| Memoria | 256 posiciones direccionadas por byte, con instrucciones y datos unificados |
| Anchura de instrucción | 16 bits, almacenados como dos bytes consecutivos |
| Orden de bytes de instrucción | Byte alto primero en la dirección inicial de la instrucción |
| Avance secuencial del PC | Dos incrementos de un byte durante la obtención |
| Registros generales | Cuatro registros de 8 bits: R0–R3 |
| Registros de operandos | A y B, de 8 bits cada uno |
| Registro de instrucciones | 16 bits |
| Banderas | Z (cero), N (negativo), C (acarreo), V (desbordamiento con signo) |
| Interconexión | Un bus didáctico de 8 bits |
| Control | Contador de micro pasos más ROM de control |
Una dirección de 8 bits selecciona 256 posiciones; la memoria tiene 256 bytes porque esta arquitectura define explícitamente que cada posición ocupa un byte. Las direcciones iniciales de las instrucciones suelen ser pares porque cada instrucción ocupa dos bytes.
Diagrama de bloques
┌──────────────────────┐
│ control ROM + │
│ microstep sequencer │
└──────────┬───────────┘
│ control signals
▼
┌────┐ ┌─────┐ ┌────────────── 8-bit shared bus ───────────────┐
│ PC │──▶│ MAR │ │ │ │ │ │ │ │
└────┘ └──┬──┘ ┌▼──┐ ┌▼──┐ ┌▼──┐ ┌▼──┐ ┌▼──┐ ┌▼──┐ ┌▼───┐
│ │MDR│ │ R0│ │ R1│ │ R2│ │ R3│ │ A │ │ B │
▼ └─┬─┘ └───┘ └───┘ └───┘ └───┘ └┬──┘ └┬───┘
┌────────┐ │ └──┬───┘
│256 × 8 │ │ ┌────▼────┐
│ RAM │ │ │ 8-bit │
└────────┘ │ │ ALU │
│ └────┬────┘
└──────▶ 16-bit IR ◀────────────────────┘
│
▼
opcode / fields
El dibujo es conceptual. El IR carga dos bytes separados desde el bus, y el resultado de la ALU llega al bus a través de su propia ruta de salida seleccionada.
Abre el ejecutor secuencial de instrucciones como referencia, pero considera esta arquitectura escrita como la autoridad del ejercicio. Si la plantilla usa una convención interna distinta, documenta la diferencia en lugar de combinar ambas en silencio.
2. Define los formatos de instrucción
La ISA personalizada usa dos formatos de 16 bits.
Formato de registro
15 12 11 8 7 4 3 0
┌───────────────┬────────────────┬────────────────┬────────────────┐
│ opcode [3:0] │ RA [3:0] │ RB [3:0] │ RD [3:0] │
└───────────────┴────────────────┴────────────────┴────────────────┘
Formato inmediato/de dirección
15 12 11 8 7 0
┌───────────────┬────────────────┬─────────────────────────────────┐
│ opcode [3:0] │ RD [3:0] │ immediate / address [7:0] │
└───────────────┴────────────────┴─────────────────────────────────┘
En esta máquina de cuatro registros solo son legales los códigos de registro del 0 al 3. El decodificador debe rechazar o llevar de forma segura a una trampa los valores 4–15, en lugar de seleccionar accidentalmente una línea de control.
| Opcode | Mnemónico | Formato | Efecto definido |
|---|---|---|---|
0 | NOP | — | Sin cambio de estado arquitectónico, salvo que el PC ya avanzó |
1 | LDI | Inmediato | RD ← imm8 |
2 | LD | Inmediato | RD ← RAM[addr8] |
3 | ST | Inmediato | RAM[addr8] ← RD |
4 | ADD | Registro | RD ← RA + RB; actualiza banderas |
5 | SUB | Registro | RD ← RA − RB; actualiza banderas |
6 | JMP | Inmediato | PC ← addr8 |
7 | JZ | Inmediato | Si Z = 1, PC ← addr8 |
8 | HLT | — | Entra en el estado detenido del controlador |
Todos los demás opcodes son ilegales. Añade una ruta explícita illegal_instruction o asígnalos a una trampa/parada segura durante la depuración.
3. Construye registros sin bloquear el reloj
Un registro de 8 bits son ocho biestables D activados por flanco que comparten un reloj y una habilitación lógica de carga.
Para cada bit:
Implementa esa ecuación con un multiplexor 2:1 antes de la entrada D, o usa la función de habilitación de reloj específica del dispositivo. Mantén todos los elementos de estado en el mismo reloj global.
No crees un reloj local aplicando AND al reloj global con load. El bloqueo de reloj sin control puede introducir sesgo y glitches en el hardware. Las celdas integradas de bloqueo de reloj y los recursos de control de reloj de FPGA son mecanismos distintos y conscientes de la temporización.
Cada registro conectado al bus necesita:
- una entrada de 8 bits desde el bus;
- una salida almacenada de 8 bits;
- un control
load_x; y - una ruta
oe_xautorizada para controlar el bus didáctico.
El PC necesita además una ruta de incremento y una ruta de carga explícita. El IR necesita cargas independientes para el byte alto y el bajo. MAR contiene una dirección de memoria; MDR captura o proporciona un byte de datos de memoria.
Verifica un registro antes de clonarlo
Prueba estos casos:
load = 0: varios relojes dejan Q sin cambios.load = 1: Q captura el bus en el flanco activo.- Los datos cambian lejos del flanco: Q cambia solo en el siguiente flanco habilitado.
- Reinicio, si se proporciona: Q alcanza el estado documentado.
- Habilitación de salida desactivada: el registro no controla el bus.
Un simulador digital puede verificar la secuencia lógica de este modelo. No reproduce la metastabilidad analógica ni demuestra la temporización física de configuración y mantenimiento.
4. Construye la ALU y define cada bandera
Usa A y B como registros estables de operandos. La ALU es combinacional: no debe contener estado arquitectónico oculto.
alu_op | Operación | Resultado |
|---|---|---|
000 | ADD | A + B |
001 | SUB | A − B |
010 | AND | A & B |
011 | OR | `A |
100 | XOR | A ^ B |
101 | NOT | ~A |
110 | SHL | A << 1, relleno con ceros |
111 | SHR | A >> 1 lógico, relleno con ceros |
La guía de la ALU desarrolla en detalle las rutas y el multiplexor de salida. El diseño aritmético central es:
- una cadena de ocho etapas de sumadores completos;
- resta implementada como
A + (~B) + 1; - puertas bit a bit en paralelo;
- cableado fijo de desplazamiento de un bit; y
- un multiplexor de salida seleccionado por
alu_op.
Definiciones de las banderas
Define las banderas como parte de la ISA, incluido lo que hacen las instrucciones lógicas y de desplazamiento con C y V.
- Z: 1 cuando todos los bits del resultado son cero.
- N: bit 7 del resultado, interpretado como bit de signo solo en operaciones con signo.
- C en ADD: acarreo de salida del bit 7.
- C en SUB: elige y documenta «sin préstamo» o «préstamo». Esta guía usa sin préstamo, de acuerdo con la convención de acarreo de salida del sumador.
- V en ADD: los operandos tienen el mismo signo y el resultado tiene el signo contrario.
- V en SUB: los operandos tienen signos distintos y el signo del resultado difiere del de A.
La fórmula equivalente XOR entre acarreo de entrada y de salida se aplica a la suma y resta en complemento a 2 cuando la ruta de resta se implementa con el operando B invertido y un acarreo de entrada. Consulta Banderas de CPU para ver casos resueltos.
Esta ISA didáctica actualiza Z, N, C y V en ADD y SUB. Para operaciones lógicas y de desplazamiento, elige una política documentada —conservar C/V o definir nuevos significados— antes de escribir el microcódigo. «Lo que tengan los cables» no es una política válida.
Tabla de verificación de la ALU
Como mínimo, prueba:
| Caso | Aspecto esperado |
|---|---|
0x00 + 0x00 | Z |
0xFF + 0x01 | acarreo sin signo y cero |
0x7F + 0x01 | desbordamiento con signo y N |
0x80 + 0x80 | acarreo más desbordamiento con signo |
0x00 − 0x01 | convención de préstamo y N |
0x80 − 0x01 | desbordamiento con signo |
| Identidades AND/OR/XOR | rutas bit a bit |
desplazamientos de 0x81 | dirección, bit de relleno y política del bit desplazado |
5. Construye el bus compartido
El bus didáctico tiene ocho cables con varias fuentes posibles. Se aplica un invariante central:
Entre los controladores posibles están PC, MDR, R0–R3, A, B, un byte del IR y el resultado de la ALU. La lista exacta es más larga que «el número de registros», así que enumera cada fuente en la especificación de control.
En DigiSim, los buffers triestado pueden modelar un bus compartido:
- habilita exactamente una fuente durante una transferencia;
- activa una o más señales de carga de destino para el siguiente flanco activo; y
- nunca captures el bus cuando ninguna fuente lo controla.
La contención es un error de diseño. Un conflicto físico de salidas puede causar una tensión incorrecta y un exceso de corriente; la consecuencia depende de la tecnología y la duración, así que «el chip siempre se quema» no es una regla útil.
Las arquitecturas FPGA modernas suelen implementar los buses compartidos internos como multiplexores, no como cableado triestado interno real. Los estilos ASIC también varían. El bus triestado sigue siendo un modelo didáctico claro de propiedad y transferencia.
Prueba de arbitraje del bus
Coloca valores conocidos en R0 y R1:
- R0 =
0xAA - R1 =
0x55
Verifica R0 solo, R1 solo, ninguna fuente y ambas fuentes. Los dos últimos casos deben tratarse como estados inválidos del controlador. Después añade una aserción o un indicador visible para detectar inmediatamente errores posteriores del microcódigo.
6. Conecta la memoria de ancho de byte
Usa el componente RAM del simulador como modelo de memoria arquitectónica. No afirmes que la RAM física es literalmente una matriz de registros D: SRAM, DRAM, RAM de bloques y matrices de registros sintetizadas usan celdas de almacenamiento y temporizaciones distintas.
La interfaz de esta CPU es:
ADDR[7:0], controlada por MAR;DATA[7:0], transferida mediante MDR o la interfaz de bus controlada;mem_read;mem_write; y- una
readyopcional si el modelo admite latencia variable.
Este recorrido supone que una lectura puede solicitarse y capturarse en un único micro paso dedicado. Si la memoria elegida es síncrona o tiene una latencia visible, inserta un estado de espera y captura los datos solo cuando sean válidos.
Las instrucciones y los datos comparten los mismos 256 bytes. Un diseño Harvard con memorias separadas también es válido, pero sería una arquitectura y una secuencia de obtención diferentes.
Para distinguir entre las abstracciones de almacenamiento escribible y no volátil/constante, consulta RAM frente a ROM.
7. Construye el controlador microsecuenciado
El controlador convierte un opcode, un micro paso, las banderas y el estado de la máquina en:
- habilitaciones de salida del bus;
- habilitaciones de carga de registros;
- operación de la ALU;
- controles de lectura/escritura de memoria;
- controles de incremento/carga del PC;
- carga de banderas;
- carga del byte alto/bajo del IR; y
- control del siguiente micro paso o del fin de instrucción.
Una implementación usa una ROM de control:
control address = {opcode, microstep}
control word = {oe signals, load signals, alu_op, memory controls, next control}
Las banderas pueden calificar acciones seleccionadas —por ejemplo, load_pc = microcode_load_pc AND Z para JZ— o participar en la dirección del almacén de control. La lógica explícita de siguiente estado suele ser más clara que suponer que el contador de micro pasos siempre se incrementa.
Este es un controlador microcodificado didáctico. Los procesadores reales usan muchas combinaciones de decodificación combinacional, almacenes de control programables, máquinas de estados y secuenciadores. No uses la etiqueta RISC/CISC como atajo para describir la implementación.
Valores predeterminados seguros
Cada entrada no utilizada de la ROM de control debe:
- deshabilitar todos los controladores del bus;
- deshabilitar las escrituras de memoria;
- deshabilitar las escrituras de registros arquitectónicos; y
- entrar en una ruta definida de instrucción ilegal o parada.
Una palabra de control sin inicializar nunca debe convertirse en «escribir datos arbitrarios en memoria arbitraria».
8. Obtén siempre dos bytes de instrucción
Como la RAM y la ruta de datos tienen 8 bits de ancho, mientras cada instrucción tiene 16 bits, la obtención requiere dos lecturas de byte. El byte alto se almacena primero por definición.
Suponiendo que un micro paso basta para la lectura de RAM seleccionada:
| Paso | Transferencia de registro | Controles importantes |
|---|---|---|
| F0 | MAR ← PC | oe_pc, load_mar |
| F1 | MDR ← RAM[MAR]; PC ← PC + 1 | mem_read, load_mdr, inc_pc |
| F2 | IR[15:8] ← MDR | oe_mdr, load_ir_hi |
| F3 | MAR ← PC | oe_pc, load_mar |
| F4 | MDR ← RAM[MAR]; PC ← PC + 1 | mem_read, load_mdr, inc_pc |
| F5 | IR[7:0] ← MDR | oe_mdr, load_ir_lo |
Después de F5, el IR contiene una instrucción completa y el PC ya apunta a la siguiente instrucción. La decodificación selecciona la secuencia de ejecución.
Si la RAM necesita una espera entre dirección y datos, divide F1 y F4 en pasos de solicitud, espera y captura. Nunca ocultes una obtención de 16 bits dentro de una transferencia de ancho de byte.
9. Define las microoperaciones de ejecución
ADD RD, RA, RB
| Paso | Transferencia |
|---|---|
| E0 | A ← R[RA] |
| E1 | B ← R[RB] |
| E2 | R[RD] ← A + B; FLAGS ← ALU flags |
| E3 | Fin de instrucción; vuelve a F0 |
RA, RB y RD seleccionan uno de R0–R3 mediante habilitaciones decodificadas. Los códigos de registro inválidos deben entrar en la ruta de instrucción ilegal.
LDI RD, imm8
| Paso | Transferencia |
|---|---|
| E0 | R[RD] ← IR[7:0] |
| E1 | Fin de instrucción |
LD RD, addr8
| Paso | Transferencia |
|---|---|
| E0 | MAR ← IR[7:0] |
| E1 | MDR ← RAM[MAR] |
| E2 | R[RD] ← MDR |
| E3 | Fin de instrucción |
ST RD, addr8
| Paso | Transferencia |
|---|---|
| E0 | MAR ← IR[7:0] |
| E1 | MDR ← R[RD] |
| E2 | RAM[MAR] ← MDR |
| E3 | Fin de instrucción |
JMP y JZ
- JMP:
PC ← IR[7:0]y después termina. - JZ: carga el PC solo cuando Z es 1 y después termina en cualquier caso.
HLT
Entra en un estado estable del controlador que no emita escrituras. Detener manualmente el reloj del simulador es cómodo, pero el estado arquitectónico debe seguir bien definido aunque continúen los flancos de reloj.
10. Ensambla a mano y carga el programa de prueba
El programa carga 5 y 3, los suma en R2, almacena el resultado en la dirección 0x10 y se detiene.
| Dirección inicial | Instrucción | Palabra de 16 bits | Bytes en memoria |
|---|---|---|---|
0x00 | LDI R0, #5 | 0x1005 | 0x10 0x05 |
0x02 | LDI R1, #3 | 0x1103 | 0x11 0x03 |
0x04 | ADD R2, R0, R1 | 0x4012 | 0x40 0x12 |
0x06 | ST R2, 0x10 | 0x3210 | 0x32 0x10 |
0x08 | HLT | 0x8000 | 0x80 0x00 |
Carga esos diez bytes en las direcciones 0x00 a 0x09 y después reinicia:
- PC =
0x00 - micro paso del controlador = F0
- detenido = 0
- todos los controladores del bus deshabilitados salvo cuando la palabra de control seleccione uno
Avanza paso a paso la primera obtención y confirma:
- después de F2, el byte alto del IR es
0x10; - después de F5, IR es
0x1005; - PC es
0x02; - LDI coloca
0x05en R0; y - ningún otro registro o posición de memoria cambia.
Al detenerse:
- R0 =
0x05 - R1 =
0x03 - R2 =
0x08 - RAM[
0x10] =0x08 - PC =
0x0A
Esos valores finales son necesarios, pero no suficientes. Una CPU defectuosa podría corromper otra posición y aun así producirlos.
11. Verifica invariantes, no solo la respuesta final
Añade comprobaciones para:
- no más de una habilitación de salida del bus en cada momento;
- escritura de memoria activa solo durante el paso de escritura ST;
- bytes del IR cargados solo durante F2 y F5;
- PC incrementado exactamente dos veces por obtención normal;
- banderas escritas solo por las instrucciones definidas para actualizarlas;
- un opcode o código de registro inválido no causa ninguna escritura no prevista;
- el estado detenido no produce efectos secundarios arquitectónicos; y
- el reinicio alcanza un estado conocido del controlador.
Para cada instrucción, registra una traza de:
clock, microstep, PC, IR, bus owner, bus value,
register loads, memory read/write, ALU op, flags
Cuando falle una prueba, encuentra el primer ciclo que diverge de la traza esperada. Depurar a partir del valor final incorrecto suele llevarte varios pasos demasiado tarde.
La guía de depuración con osciloscopio explica cómo inspeccionar formas de onda modeladas sin confundirlas con la validación temporal física.
12. Fallos habituales
| Síntoma | Causa probable | Primera comprobación |
|---|---|---|
| El IR contiene bytes intercambiados | El orden de bytes difiere entre carga y obtención | Direcciones PC y PC+1 |
| PC avanza cuatro | El incremento ocurre dos veces por byte o se mezclaron las anchuras | Traza F0–F5 |
| El bus está en contención | Dos habilitaciones de salida comparten un bit de control | Palabra de control decodificada |
| El bus no está controlado al capturarlo | Falta habilitar la fuente | Aserción de propietario del bus |
| ADD escribe el registro incorrecto | Decodificación del campo RD u orden de códigos de registro | IR[3:0] y líneas de carga |
| Store corrompe la memoria de instrucciones | Temporización de dirección/inmediato o mem_write | MAR y ST E2 |
| El salto usa una Z antigua | No se escribieron las banderas o JZ muestrea el estado incorrecto | ADD/SUB E2 y JZ |
| El controlador nunca vuelve a obtener | Falta la entrada de fin/siguiente estado | Transición de micro paso |
| Un opcode ilegal ejecuta una operación | La ROM de control no utilizada no se inicializó de forma segura | Palabra de control predeterminada |
13. Qué enseña y qué no enseña esta CPU
Este diseño enseña transferencia de registros, codificación de instrucciones, propiedad del bus, banderas de la ALU, secuenciación de memoria y estado del controlador.
No modela:
- RAM o lógica a nivel de transistores;
- jerarquía de caché o memoria virtual;
- ejecución especulativa o fuera de orden;
- comportamiento analógico del reloj o metastabilidad;
- propagación, enrutamiento, potencia o límites térmicos físicos; ni
- una arquitectura de seguridad o excepciones de producción.
Su único bus compartido está diseñado para ser fácil de observar, no es una afirmación sobre la topología interna de una CPU moderna.
Extensiones en un orden seguro
- Añade pruebas automatizadas de instrucciones. Verifica cada opcode y codificación ilegal antes de añadir funciones.
- Añade AND, OR, XOR y desplazamientos. Define primero el comportamiento de las banderas y después añade las secuencias de control.
- Añade un salto condicional mediante comparación directa. Esto muestra la diferencia entre las ISA basadas en banderas y las de comparar y saltar.
- Añade un puntero de pila y llamada/retorno. Especifica el crecimiento de la pila, el orden de bytes y el comportamiento ante desbordamiento.
- Añade una máquina de estados de interrupciones. Guarda una dirección de retorno y las banderas antes de cargar un vector; define la prioridad y el enmascaramiento.
- Separa la memoria de instrucciones y datos. Compara la nueva secuencia de obtención con el diseño de memoria unificada.
- Segmenta solo después de que pase la referencia multiciclo. Después define la validez de las etapas, las esperas, el forwarding, los vaciados y las excepciones.
Consulta el análisis detallado de obtención–decodificación–ejecución y la guía del registro de instrucciones y la decodificación antes de intentar la extensión segmentada.
Try It
Usa el ejecutor secuencial de instrucciones para orientarte, el sistema ALU de 8 bits para verificar la aritmética por separado y el registro de banderas de CPU para probar el comportamiento del estado. Mantén una arquitectura escrita y una tabla de microoperaciones junto al esquema.
El hito importante no es ver 8 una vez. Es poder explicar por qué se autorizó cada cambio de estado, reproducirlo en una traza y demostrar que ningún otro estado cambió.



