Introducción
En el capítulo anterior definimos la interfaz de botones y leds de nuestra CPU de 4 bits, que nos permitirá controlar a la CPU. En este capítulo definiremos el conjunto de instrucciones que soportará nuestra CPU.
Primeramente, hay que considerar que el tamaño de las instrucciones es también de 4 bits, y, por lo tanto, solo podemos definir 16 instrucciones de un solo nibble. La cantidad de operandos sí es variable, pero trataremos de que que sea lo más reducida posible.
Si bien cada instrucción es solo un número de 4 bits (Opcode o Código de operación), nos referiremos a este número con un mnemónico, como es costumbre en el mundo de los microprocesadores.
Aunque tenemos libertad para elegir el mnemónico, pudiendo usar incluso nombres en español, usaremos los clásicos nombres en inglés, como LDA, STA, INC o DEC, al estilo de los antiguos microprocesadores de 8 bits, y en especial de mi favorito, el 6502.
Elección de las instrucciones
Podríamos desear disponer de un conjunto amplio de instrucciones en ensamblador para facilitar la programación de la CPU, pero tenemos dos limitaciones importantes:
- Disponemos de 4 bits para el OpCode y eso nos permite disponer de 16 instrucciones como máximo, incluyendo los distintos modos de direccionamiento que pueda tener cada instrucción.
- Implementar muchas instrucciones complica el hardware de la CPU y la implementación física de dicho dispositivo. No es nuestro caso, porque no implementaremos el hardware, pero podría ser un problema si luego nos decidimos a diseñar la CPU físicamente.
Bajo estas limitaciones, debemos elegir un juego reducido de instrucciones, que incluya solo las instrucciones más básicas y necesarias. Por lo tanto, debemos centrarnos, únicamente, en las instrucciones que nos permitan abarcar los temas que deseamos cubrir con esta CPU educativa, y que serán:
- Movimiento de datos.
- Operaciones aritméticas.
- Operaciones lógicas.
- Condiciones.
- Bucles e iteraciones.
Incluir solo estas características en una CPU, no significa que no puedan realizarse funciones avanzadas como el manejo de pila o direccionamiento indexado. Es solo que el hardware de la CPU no se presta para estas funciones avanzadas, pero se podrían realizar con esfuerzo adicional de programación.
En el diseño de nuestra CPU, hemos decidido prescindir de instrucciones de pila, y de la pila misma, para simplificar el hardware y el manejo del microprocesador.
Al centrarnos solo en instrucciones importantes, dejaremos de lado algunas instrucciones comunes en microprocesadores más potentes, como por ejemplo:
- JSR -> Salto a subrutina
- RTS -> Retorno de subrutina.
- PHA -> Pone acumulador en la pila
- PLA -> Recupera acumulador de la pila
- CLR -> Pone el acumulador en cero.
- ASL -> Desplazamiento aritmético del acumulador a la izquierda.
- NEG->Convierte a negativo el valor del acumulador
No necesitaremos instrucciones de subrutinas, porque no tenemos soporte para subprogramas en nuestra CPU, además, este mecanismo trabaja formalmente con una estructura de pila como apoyo, que tampoco implementamos. En consecuencia, tampoco necesitaremos de instrucciones de pila.
Por otro lado, instrucciones como CLR, NEG o ASL, pueden implementarse a partir de instrucciones más simples como las que pensamos definir en nuestra CPU.
Formato de las instrucciones
El formato de nuestras instrucciones tendrá una de estas tres posibles formas:

Las instrucciones que no manejan operandos son aquellas que no necesitan ningún dato adicional para funcionar, como:
- HLT -> Detención de la CPU (HALT).
- NOT -> Realiza la operación NOT sobre los bits del acumulador.
- NOP -> No realiza ninguna operación.
A estas instrucciones, que no necesitan más información para ejecutarse, se les llama también «de direccionamiento implícito».
Las instrucciones con un solo operando de 4 bits son las que cargan un valor en el registro, y en principio, solo tendríamos una:
LDA# <valor de 4 bits>
Esta instrucción solo necesita un operando de 4 bits, porque ese es el tamaño del acumulador. La especificación directa del valor a usar por una instrucción, como operando, es conocida como direccionamiento inmediato, y se identifica por el carácter «#» después del mnemónico.
Las instrucciones que utilizan dos operandos de 4 bits serán las que accedan a memoria (la mayoría), sea para lectura y escritura. Esto es cierto, porque, recordaremos que la memoria física que puede direccionar nuestra CPU es de 256 nibbles, de modo que necesitamos 8 bits para un direccionamiento completo.
Un ejemplo clásico sería la instrucción de carga desde memoria:
LDA <dirección de 8 bits>
Pero, pensando en la facilidad de ingreso de datos manuales desde el entrenador, he decidido que las instrucciones que accedan a datos, sean de solo 4 bits, de modo que, se reduce la cantidad de nibbles que se requieren para ingresar una instrucción de este tipo:
LDA <dirección de 4 bits>
Como ya habremos deducido, con 4 bits de dirección solo podemos abarcar hasta 16 celdas de memoria, así que este tipo de direccionamiento solo podrá acceder a las primeras 16 posiciones del total de 256 a las que puede acceder el contador de programa PC.
¿Se restringirá el tamaño de los programas a 16 nibbles?
No. Simplemente que restringiremos a que los datos puedan ubicarse en los primero 16 nibbles de la memoria, pero el programa completo puede extenderse hasta los 256 nibbles que soporta el registro PC.
Para ello, debemos hacer que las instrucciones de salto, como JMP, sí incluyan una dirección completa de 8 bits:
JMP <dirección de 8 bits>
Y solo las instrucciones que manejen datos (la mayoría), estarán restringidas a direcciones de 4 bits.
Este tipo de direccionamiento es llamado de «página cero» y es una técnica usada por microprocesadores antiguos como el 6502, con la diferencia de que en nuestro caso, el tamaño de la página es de 16 nibbles.
Disponer de solo 16 nibbles, para datos, puede parecer algo limitante, pero no pretendemos escribir programas de tipo videojuegos o bases de datos en nuestra CPU elemental. Sino, simplemente, aprender los principios de la programación en código máquina.
Puede ser reconfortante, y revelador, recordar que existen microcontroladores más modernos, como los AVR o PIC, que manejan precisamente este tamaño de memoria reducida, porque en las aplicaciones más básicas para IOT no se necesita más que eso.
Las instrucciones
Movimiento de datos
El movimiento de datos debe cubrir el movimiento de datos entre la memoria y el acumulador, en ambos sentidos. Para ello implementaremos solo estas tres instrucciones:
- LDA# ->Carga un valor literal de 4 bits al acumulador.
- LDA -> Carga un valor de memoria al acumulador.
- STA -> Guarda el valor del acumulador a la memoria.
Operaciones aritméticas
Las operaciones aritméticas que cubriremos con nuestra CPU, serán las más básicas que incluyen a las sumas, restas, incremento y decremento. Para nuestro caso, implementaremos las siguientes instrucciones:
- ADD -> Suma un valor de la memoria al acumulador
- SUB -> Resta, al acumulador, un valor de la memoria.
- INC -> Incrementa el acumulador.
- DEC -> Decrementa el acumulador.
Operaciones lógicas
Las operaciones lógicas serán casi todas las soportadas en CPU modernas. :
- AND -> Operación lógica AND de la memoria al acumulador.
- OR -> Operación lógica OR de la memoria al acumulador.
- XOR -> Operación lógica XOR de la memoria al acumulador.
- NOT -> Negación lógica del acumulador.
Saltos y condiciones
Solo se implementarán dos saltos condicionales y uno incondicional:
- JZ -> Saltar si el resultado de la última instrucción es cero.
- JC -> Saltar si el resultado de la última instrucción tuvo acarreo.
- JMP -> Salto incondicional.
Control y sistema
Se necesita aún, una instrucción más para el control de la ejecución de un programa y una más que es la instrucción NOP:
- HLT -> Detiene la ejecución del programa.
- NOP -> No realiza ninguna operación. Funciona más como relleno.
Tabla de instrucciones
Resumiendo las instrucciones anteriores, y dándoles un código de operación binario, tendremos la siguiente tabla:
| N | Mnemó nico | Descripción | Acción | Nº de Nibbles | Nible 1 | Nibble 2 | Nibble 3 |
| 0 | HLT | Detener CPU | – | 1 | 0000 | ||
| 1 | LDA# | Cargar en A un valor | A←Value | 2 | 0001 | <valor> | |
| 2 | LDA | Cargar en A de memoria | A←[addr] | 2 | 0010 | <direccion> | |
| 3 | STA | Almacenar A | A←[addr] | 2 | 0011 | <direccion> | |
| 4 | ADD | Sumar memoria a A | A←A+[addr] | 2 | 0100 | <direccion> | |
| 5 | SUB | Restar memoria de A | A←A-[addr] | 2 | 0101 | <direccion> | |
| 6 | AND | AND de memoria a A | A←A & [addr] | 2 | 0110 | <direccion> | |
| 7 | OR | OR de memoria a A | A←A | [addr] | 2 | 0111 | <direccion> | |
| 8 | INC | Incrementar acumular | A←A+1 | 1 | 1000 | ||
| 9 | DEC | Decrementar acumulador | A←A-1 | 1 | 1001 | ||
| 10 | XOR | XOR de memoria a A | A←A ^ [addr] | 2 | 1010 | <direccion> | |
| 11 | NOP | Sin operación | – | 1 | 1011 | ||
| 12 | NOT | NOT de A | A←NOT A | 1 | 1100 | ||
| 13 | JMP | Salto a memoria | PC←addr | 3 | 1101 | <dir_alta> | <dir_baja> |
| 14 | JZ | Saltar si Z=1 | PC←addr | 3 | 1110 | <dir_alta> | <dir_baja> |
| 15 | JC | Saltar si C=1 | PC←addr | 3 | 1111 | <dir_alta> | <dir_baja> |
El orden en que se han listado las instrucciones no es arbitrario. Se les ha puesto así, priorizando a las instrucciones más usadas, de modo que tengan un OpCode más bajo, y sean así, más fáciles de recordar.
De cualquier forma, el tener un conjunto reducido de instrucciones (RISC) y solo 4 bits de tamaño, nos ayudará a memorizar los códigos binarios correspondientes.
También se ha buscado una disposición ordenada juntando a cada instrucción con su complementaria o asociada. Así, tenemos:
- Carga de memoria (2) y Almacenamiento a memoria (3)
- Suma (4) y Resta (5)
- Operación AND (6) y Operación OR (7)
Las operaciones de salto ocupan las posiciones más altas para ser fácilmente decodificables por hardware y para recordarlas más fácilmente también.
Se puede notar que solo las instrucciones de salto incluyen 8 bits de la dirección, porque se refieren a posiciones del código, mientras que las instrucciones de acceso a datos, solo usan 4 bits de dirección, porque, como ya hemos decidido, solo usaremos los primeros 16 nibbles de memoria para los datos.
En un inicio consideré usar desplazamiento relativo para las instrucciones de salto, y así usar solo un nibble como operando de JZ y JC, pero no es el objetivo de este proyecto complicarse con el cálculo de los desplazamientos negativos. Sin embargo, queda como una posible modificación.
Como se puede apreciar, todos los 16 códigos de operación se han usado. Si se quisiera ampliar a más instrucciones, habría que eliminar alguna instrucción existente. La principal candidata a eliminación puede ser la instrucción NOP, pero también puede usarse la instrucción lógica XOR que es poco usada.
¿Cómo citar este artículo?
- En APA: Hinostroza, T. (29 de abril de 2026). Entrenador de CPU de 4 bits – Parte 3. Blog de Tito. https://blogdetito.com/2026/04/29/entrenador-de-cpu-de-4-bits-parte-3/
- En IEEE: T. Hinostroza. (2026, abril 29). Entrenador de CPU de 4 bits – Parte 3. Blog de Tito. [Online]. Available: https://blogdetito.com/2026/04/29/entrenador-de-cpu-de-4-bits-parte-3/
- En ICONTEC: HINOSTROZA, TIto. Entrenador de CPU de 4 bits – Parte 3 [blog]. Blog de Tito. Lima Perú. 29 de abril de 2026. Disponible en: https://blogdetito.com/2026/04/29/entrenador-de-cpu-de-4-bits-parte-3/
Dejar una contestacion