Apuntes de Verilog
Tema 7. Arquitectura de Ordenadores. Procesador MIPS
1ª Parte: Módulos Individuales
1ª Parte: Módulos Individuales
5 de octubre 2025
En este capítulo describiremos los módulos simples que forman un procesador MIPS, sus características y funciones. Los interconectaremos paso a paso hasta montar el datapath mínimo para obtener un microprocesador.
El Program Counter es un registro especial que contiene la dirección de memoria de la siguiente instrucción a ejecutar.
Funciones
Lectura de instrucciones: El procesador usa el valor del PC para buscar la instrucción en la memoria.
Ejecución: Una vez obtenida la instrucción, el procesador la decodifica y ejecuta.
Actualización: Después de ejecutar la instrucción, el PC se incrementa en 4 unidades apuntar a la siguiente instrucción.
La memoria de un procesador MIPS es byte-addressable, sin embargo las instrucciones se organizan en palabras de 32 bits, de ahí la necesidad de incrementar el contador en 4 unidades para apuntar a la siguiente dirección.
El Program Counter consta por lo tanto de dos módulos: el Registro que almacena la dirección de la siguiente instrucción y un Sumador para incrementar dicha dirección en cuatro unidades y apuntar así a la dirección de la siguiente instrucción.
En la siguiente imagen podemos ver un esquema del Program Counter y cómo se interconecta el registro con el sumador.
Esquema del Program Counter
El código Verilog del Program Counter, su conexión en el módulo top y un testbench de prueba se pueden ver a continuación.
Ejecutamos la simulación y este es el resultado:
Vemos que efectivamente, con cada ciclo del reloj el contador se incrementa en cuatro unidades.
El módulo Instruction Memory en un procesador MIPS es una parte esencial del sistema. Se encarga de almacenar las instrucciones del programa que el procesador debe ejecutar.
Es una memoria de solo lectura (ROM) o una memoria RAM configurada para contener únicamente instrucciones de 32 bits codificadas en headecimal. El procesador accede a esta memoria usando el Program Counter para obtener la instrucción actual.
Funciones
Almacenar instrucciones en formato hexadecimal.
Entregar instrucciones: cuando el PC apunta a una dirección, el módulo Instruction Memory devuelve la instrucción correspondiente.
No se modifica en tiempo de ejecución: normalmente, las instrucciones se cargan al inicio y no cambian durante la ejecución del programa.
El módulo Sign Extend tiene la función de extender a 32 bits los valores inmediatos de 16 bits. Por ejemplo, algunas instrucciones como addi, lw, o sw, usan valores inmediatos de 16 bits. Pero como MIPS es una arquitectura de 32 bits, esos valores deben extenderse a 32 bits antes de ser usados en operaciones aritméticas o direcciones de memoria.
Si el bit más significativo del valor de 16 bits (bit 15) es 0, el número es positivo → se rellenan los 16 bits superiores con ceros.
Si el bit 15 es 1, el número es negativo → se rellenan los 16 bits superiores con unos.
Valor de 16 bits: 0xFFFC (binario: 1111 1111 1111 1100)
Sign Extend a 32 bits: 0xFFFF_FFFC
La siguiente imagen muestra los módulos Instruction Memory y Sign Extend conectados al Program Counter.
El código Verilog, su conexión en el módulo top y un testbench de prueba se pueden ver a continuación. Ambos Instruction Memory y Sign Extend son circuitos puramente combinacionales; la lectura de la ROM es asíncroma y la salida del SE se actualiza tan pronto como cambia el dato de entrada.
Ejecutamos la simulación y este es el resultado:
Comprobamos que con cada incremento del contador, el módulo Intruction Memory va poniendo en el bus la siguiente instrucción. Al mismo tiempo el módulo Sing Extend completa con ceros o unos el valor de la constante IMM (bits 15 a 0).
El Register File es uno de los bloques más importante de la unidad de procesamiento. Es una memoria pequeña y muy rápida que contiene los registros del procesador, es decir, un conjunto de celdas de almacenamiento interno donde se guardan datos e instrucciones temporales que la CPU necesita para operar.
En MIPS clásico, el Register File está compuesto por 32 registros de propósito general, cada uno de 32 bits de ancho.
Almacenamiento temporal de datos
Guarda operandos intermedios que la ALU (Unidad Aritmético-Lógica) necesita para realizar operaciones.
Evita el acceso constante a la memoria RAM, que es mucho más lenta.
Proporcionar registros fuente
Cada instrucción MIPS puede leer hasta dos registros fuente al mismo tiempo.
Escribir resultados
Almacenar el resultado de una operación en un registro destino.
Acceso rápido y paralelo
El diseño típico del Register File en MIPS permite dos lecturas simultáneas y una escritura en cada ciclo de reloj.
Esto se logra gracias a que tiene dos puertos de lectura y un puerto de escritura.
Características
El Register File tiene dos puertos de lectura RD1 y RD2 accesibles mediante las direcciones RA1 y RA2 y un puerto de escritura (WA/WD). Las direcciones de 5 bits, RA1, RA2 y WA, pueden acceder cada una a los 32 registros. Por lo tanto, se pueden leer dos registros y escribir uno simultáneamente.
Lectura de registros de forma combinacional; si las direcciones RA1 y RA2 cambian, los nuevos datos aparecen en RD1 y RD2. No interviene ningún reloj.
El puerto de escritura tiene una entrada de dirección de 5 bits WA (write address) ; una entrada de datos de escritura de 32 bits, WD (write data); una entrada de habilitación de escritura (write_enable) y la señal de reloj. Si write_enable = 1 el Register File escribe el dato presente en WD en la dirección WA en el flanco ascendente del reloj.
Register File, código Verilog y esquema
Para comprobar el funcionameinto del Register File, he añadido una ROM al circuito que irá metiendo datos en el bus W_DATA a medida que el PC se va incrementando. También habilita la señal wrire_enable para guardar los datos en las direcciones que vamos poniendo en WD.
La prueba consiste en guardar 4 datos distintos: AAAA_1111, BBBB_1111, CCCC_1111 y DDDD_1111 en cuatro posiciones: 00100, 00101, 00110 y 00111 y luego leer esas posiciones y verificar que los datos se han guardado correctamente, así en una sola simulación verificamos que el Register File guarda y lee los datos correctamente.
Ejecutamos la simulación y éste es el resultado:
Primero guardamos los datos AAAA_1111, BBBB_1111, CCCC_1111 y DDDD_1111 en las posiciones 00100, 00101, 00110 y 00111 durante los ciclos 4, 8, 12 y 16. Luego hacemos write_enable = 0 y ponemos en RA1 y RA2 las direcciones de los registros que queremos leer, como son cuatro registros lo hacemos en dos ciclos (20 y 24), primero leemos 00100, 00101 en el ciclo 20, y luego 00110 y 00111 en el ciclo 24. Los datos guardados aparecen en las salidas RA1 y RA2.
La ALU (Arithmetic Logic Unit) es uno de los bloques fundamentales, ya que se encarga de realizar las operaciones aritméticas y lógicas que requieren las instrucciones.
Funciones
Operaciones aritméticas básicas
Suma: utilizada en instrucciones como add, addi, y también en el cálculo de direcciones efectivas para lw, sw.
Resta: usada en sub, pero también para comparaciones en ramas (beq, bne).
Operaciones lógicas
AND, OR, XOR, NOR: usadas en instrucciones como and, andi, or, ori, etc.
Operaciones de comparación
Set less than (slt, slti): compara dos operandos y devuelve 1 si una condición se cumple (menor que), o 0 en caso contrario.
ALU, código Verilog y esquema
Hacemos un sencillo testbench para probar el funcionamiento de todas las operaciones y comprobamos que efectivamente la ALU se comporta como se espera.
El testbench no lo pego aquí para que no quede muy extenso; lo puedes descargar al final de la página.
el módulo Data Memory es el bloque encargado de gestionar la memoria de datos, es decir, el lugar donde se almacenan y leen los valores durante la ejecución de un programa. No guarda las instrucciones del programa (eso lo hace el Instruction Memory), sino únicamente los datos utilizados por las instrucciones.
Funciones
Leer datos de memoria (cuando se ejecutan instrucciones tipo load).
Escribir datos en memoria (cuando se ejecutan instrucciones de almacenamiento, store).
A nivel descriptivo es similar a una memoria RAM, con escritura síncrona en el flanco positivo del reloj si write_enable = 1 y lectura asíncorna, igual que el módulo Register File.
Data Memory, código Verilog y esquema
Pasamos un testbench de prueba para guardar cuatro datos AAAA_AAAA, BBBB_BBBB, CCCC_CCCC y DDDD_DDDD en cuatro posiciones distintas: 0000, 0010, 0100 y 1111, luego deshabilitamos la señal write_enable y vamos cambiando la dirección ADDR para ir leyendo las posiciones de memoria y ver el dato almacenado en la salida RD.
Nota: Por simplicidad en el ejemplo de arriba, se ha reducido el número de bits de la entrada ADDR de 32 a 4. En un procesador real, la entrada del módulo Data Memory tiene 32 bits, capaz de direccionar hasta 4 GB de memoria.
Hasta aquí hemos visto una descripción de cada uno de los módulos simples que forman un procesador MIPS-SAP, hemos interconectado algunos de ellos y simulado su funcionamiento. El siguiente paso es conectarlos todos juntos para formar el datapath.
Moamen Wael tiene en su GitHub un excelente tutorial dedicado al procesador MIPS descrito en Verilog. Thanks for sharing!
Descarga las fuentes y testbenches de los módulos individuales en el enlace de más abajo.