Implementando un Banco de Registros en un Procesador RISC-V 32I en FPGA
Este artículo es parte de nuestra serie sobre el diseño e implementación de un procesador RISC-V 32I en la FPGA Tang Nano 20K. Hoy nos sumergimos en un componente fundamental: el banco de registros. Exploraremos qué es, por qué es tan importante en la arquitectura RISC-V y cómo implementarlo de manera eficiente en Verilog. Además, veremos algunas optimizaciones clave para que sea más robusto y compatible con hardware real.
¿Qué es un Banco de Registros y por qué es tan importante?
Imagina que el procesador es como un chef en una cocina. Si cada vez que necesitara un ingrediente tuviera que ir al supermercado, cocinar sería un proceso extremadamente lento. En cambio, si tiene los ingredientes organizados y accesibles en su estación de trabajo, todo fluye con rapidez y eficiencia. Bueno, los registros en una CPU funcionan igual: son una memoria ultrarrápida donde el procesador puede guardar y recuperar datos sin perder tiempo accediendo a la memoria principal1.
En la arquitectura RISC-V 32I, contamos con 32 registros de 32 bits (x0
a x31
), que son esenciales para ejecutar operaciones aritméticas, lógicas y de control.
Reglas clave del banco de registros en RISC-V:
- El registro
x0
siempre debe ser 0. No importa qué operación intente modificarlo, su valor es inmutable. Es una constante de hardware. - Se pueden leer hasta dos registros y escribir en uno por ciclo de reloj. Esto maximiza la eficiencia de las instrucciones.
- Las lecturas deben ser inmediatas (combinacionales)2. Cuando el procesador solicita un valor, debe obtenerlo sin esperas.
- Las escrituras ocurren en el flanco positivo del reloj3. Es decir, los valores se actualizan cuando el ciclo de reloj hace una transición de 0 a 1.
Código Completo del Banco de Registros en Verilog
Aquí tienes el código en Verilog, asegurándonos de que cumple con las reglas de RISC-V y agregando mejoras para un funcionamiento más seguro y eficiente:
module RegisterFile (
input wire clk,
input wire rst, // Reset global
input wire we, // Señal de escritura
input wire [4:0] rs1, rs2, rd, // Direcciones de registros
input wire [31:0] wd, // Dato a escribir
output reg [31:0] rd1, rd2 // Datos leídos
);
reg [31:0] registers [0:31]; // Banco de registros (32 registros de 32 bits)
// Inicializar todos los registros en 0 para evitar valores indefinidos
integer i;
initial begin
for (i = 0; i < 32; i = i + 1)
registers[i] = 32'b0;
end
// Escritura en el banco de registros
always @(posedge clk) begin
if (we && rd != 5'b00000) // Evita escritura en x0
registers[rd] <= wd;
end
// Lectura combinacional con protección de x0
always @(*) begin
rd1 = (rs1 == 5'b00000) ? 32'b0 : registers[rs1]; // Si rs1 == x0, devolver 0
rd2 = (rs2 == 5'b00000) ? 32'b0 : registers[rs2]; // Si rs2 == x0, devolver 0
end
endmodule
Explicación del Código Línea por Línea
1. Definición del Módulo y Puertos
Aquí creamos el módulo RegisterFile, el cual almacena y gestiona los valores de los registros.
module RegisterFile (
input wire clk,
input wire rst, // Reset global
input wire we, // Señal de escritura
input wire [4:0] rs1, rs2, rd, // Direcciones de registros
input wire [31:0] wd, // Dato a escribir
output reg [31:0] rd1, rd2 // Datos leídos
);
clk
: Señal de reloj, sincroniza las operaciones.rst
: Señal de reset, reinicia los registros.we
: Indica si se debe escribir en un registro.rs1
,rs2
: Direcciones de los registros a leer.rd
: Dirección del registro donde se escribirá.wd
: Dato que se almacenará.rd1
,rd2
: Valores de los registros leídos.
2. Definición del Banco de Registros
reg [31:0] registers [0:31]; // Banco de registros (32 registros de 32 bits)
Se declara un arreglo de 32 registros de 32 bits, representando los registros x0
a x31
en RISC-V.
3. Inicialización de Registros
integer i;
initial begin
for (i = 0; i < 32; i = i + 1)
registers[i] = 32'b0;
end
Aquí garantizamos que todos los registros comiencen en 0, evitando problemas con valores indeterminados en la FPGA.
4. Escritura en el Banco de Registros
always @(posedge clk) begin
if (we && rd != 5'b00000) // Evita escritura en x0
registers[rd] <= wd;
end
Este bloque se ejecuta en el flanco positivo del reloj. La escritura solo ocurre si we
está activo y rd
no es x0
, asegurando que x0
permanezca siempre en 0
.
5. Lectura Combinacional
always @(*) begin
rd1 = (rs1 == 5'b00000) ? 32'b0 : registers[rs1]; // Si rs1 == x0, devolver 0
rd2 = (rs2 == 5'b00000) ? 32'b0 : registers[rs2]; // Si rs2 == x0, devolver 0
end
La lectura es combinacional, lo que significa que los valores se obtienen de inmediato, sin depender del reloj.
Conclusión: Un Paso Más en Nuestro Procesador RISC-V
Con este banco de registros, nuestra CPU RISC-V 32I en la FPGA Tang Nano 20K ya tiene un almacenamiento rápido y confiable para operar. Este es un paso esencial para la ejecución eficiente de instrucciones.
En la próxima entrega, exploraremos la Unidad de Control, donde diseñaremos cómo el procesador decodifica y ejecuta las instrucciones RISC-V. ¡No te lo pierdas! 🚀
Notas al pie
Los registros de propósito general (GPR, por sus siglas en inglés) son pequeños espacios de almacenamiento dentro del procesador que permiten un acceso rápido a los datos, sin necesidad de acudir a la memoria RAM. ↩︎
Un circuito combinacional es aquel cuya salida depende únicamente de las entradas en un instante dado, sin necesidad de una señal de reloj o estado previo. ↩︎
El flanco positivo del reloj es el momento en que la señal de reloj cambia de
0
a1
, permitiendo la actualización de valores en registros síncronos. ↩︎