Tarea 7: Mejora del rendimiento

Tarea 7: Mejora del rendimiento

Información de la Tarea

Estudiante: Andrés Cruz Chipol

Curso: Arquitectura De Computadoras

Fecha de entrega: 16 de abril de 2026


Tarea 7: Mejora del Rendimiento del RISC0 — Multiplicador en Hardware

Objetivo

Reemplazar el algoritmo de multiplicación del procesador RISC0 (implementación secuencial de 32 ciclos) por código Verilog combinacional de 1 ciclo, tal como se usa en el hardware dedicado del oscilador caótico. Medir el nuevo throughput y la frecuencia de reloj resultante y compararlos con los obtenidos en la Tarea 6.


1. Implementación de una nueva forma combinacional

Problemas con el diseño de la tarea anterior

En la tarea anterior usábamos un módulo que implementaba la multiplicación mediante un proceso iterativo que tomaba 32 ciclos de reloj. Esto detenía toda la ejecución del procesador durante ese tiempo cada vez que se llamaba la instrucción MUL. Además, la lógica generaba un atraso muy grande en el flujo, por lo que la placa iCE40 no lograba sincronizarse para correr a los 100 MHz normales y por eso tuvimos que jugar con un divisor de reloj a 25 MHz.

Modo de solucionarlo

Para el diseño actual armamos otro módulo completamente combinacional usando de forma directa el operador * que ya viene nativo en Verilog:

module Multiplier_comb(
input clk, run, u, // misma interfaz que Multiplier.v
output stall, // siempre 0 — sin ciclos de espera
input [31:0] x, y,
output [63:0] z);
wire signed [63:0] sp = $signed(x) * $signed(y);
wire [63:0] up = x * y;
assign z = u ? sp : up; // u=1: signed, u=0: unsigned
assign stall = 1'b0; // nunca genera stall
endmodule

Al compilar y realizar la síntesis, las herramientas de yosys mapean este símbolo a un bloque multiplicador real de la FPGA, muy similar a lo que hizo cuando programamos puramente en hardware en la Tarea 5.

Como mantuvimos los mismos nombres en las entradas y salidas, la vinculación dentro de nuestro procesador principal RISC0.v fue directa, logrando quitar el antiguo módulo e ingresando la nueva forma manteniendo el mismo cableado; el único cambio notable fue que la señal de stall queda anclada siempre a un 0:

// EN RISC0.v (Tarea 7)
// =========================================================================
// Reemplazado Multiplier (32 estados, 32 ciclos de stall)
// por el nuevo multiplicador (combinacional, 0 ciclos de stall — 1 ciclo).
// stallM queda cableado igual; el nuevo módulo siempre retorna stall=0.
// =========================================================================
Multiplier_comb mulUnit (.clk(clk), .run(MUL), .stall(stallM),
.u(~u), .x(B), .y(C1), .z(product));

Dado que liberamos el procesador y quitamos el cuello de botella que teníamos en iterar los ciclos, quitamos también el viejo divisor de reloj del RISC0Top.v.

Este era el código que eliminamos:

reg [1:0] clk_div = 0;
wire clk25;
always @(posedge clk) begin
clk_div <= clk_div + 1;
end
assign clk25 = clk_div[1];

Y conectamos la señal directamente a la placa principal a 100 MHz, además de adaptar los receptores y transmisores para esa tasa de reloj:

// RISC0 corre directamente a 100 MHz (clk en lugar de clk25)
RISC0 riscx(.clk(clk), .rst(rst), .iord(iord), .iowr(iowr),
.ioadr(ioadr), .inbus(inbus), .outbus(outbus));
// ...
// Parametros explícitos de la UART ajustados a 100 MHz
uart_rx #(.CLK_FREQ(100000000), .BAUD(1000000)) receiver(
.clk(clk), .rst(rst), .rx(usb_rx), .data(dataRx), .new_data(rdyRx));
uart_tx #(.CLK_FREQ(100000000), .BAUD(1000000)) transmitter(
.clk(clk), .rst(rst), .tx(usb_tx), .block(block), .busy(rdyTx),
.data(dataTx), .new_data(startTx));

2. Evidencia del Número de Ciclos (Simulación)

Reutilizamos el banco de pruebas de simulación que monitorizaba el acceso a la memoria para extraer la cantidad de esperas y capturar el tiempo:

// Interceptar la escritura a la memoria en la dirección 16300 que contiene el conteo
// micro.riscx.dmin contiene el valor del registro proveniente del contador TICS.
if (micro.riscx.dmwr && micro.riscx.dmadr == 14'd16300) begin
$display("\n============================================================");
$display(" THROUGHPUT (Ciclos de reloj por iteracion): %d", micro.riscx.dmin);
$display("============================================================\n");
$finish();
end

Al simular nuestro algoritmo junto con los nuevos archivos de la consola se obtuvo lo siguiente:

#!/bin/bash
# ...
iverilog -o sim_monitor.vvp RISC0Top_tb_monitor.v RISC0Top.v RISC0.v Divider.v DRAM.v Multiplier_comb.v PROM.v uart_rx.v uart_tx.v
vvp sim_monitor.vvp > uart_sim_log.txt

Resultado de la simulación Tarea 7

============================================================
THROUGHPUT (Ciclos de reloj por iteracion): 66
============================================================

El resultado imprimió apenas 66 ciclos, lo cual es asombroso si recordamos que antes nos llevaba 190 terminar solo una iteración.

La principal razón de esta mejora es el ahorro de 160 esperas muertas: antes, cada una de las 5 multiplicaciones de la ecuación tardaba 32 ciclos en resolverse. Al hacerlas instantáneas, los 66 ciclos restantes corresponden únicamente a las sumas y demás pasos normales del algoritmo.


3. Frecuencia de Reloj — Reporte de Síntesis FPGA

Resultados de nextpnr (apio build)

"fmax": {
"clk$SB_IO_IN_$glb_clk": {
"achieved": 28.655759,
"constraint": 12.0
}
}
"utilization": {
"ICESTORM_LC": { "available": 7680, "used": 6537 },
"ICESTORM_RAM": { "available": 32, "used": 32 }
}

La validación nos comprueba que la máxima frecuencia para ese circuito sintetizado es de unos 28.65 MHz tras armarlo de manera física.

Comparativa de frecuencias

MétricaTarea 6Tarea 7
Frecuencia configurada25 MHz (÷4)100 MHz (directo)
Frecuencia máxima — nextpnr~38 MHz~28.65 MHz
Frecuencia de operación real25 MHz28.65 MHz

4. Comparativa de Rendimientos (Throughput)

Fórmula: Throughput = Frecuencia (Hz) / Ciclos_por_iteración

Tarea 6 (Baseline)

Tarea 7 (Con Multiplicador Combinacional)

Tabla Comparativa Final

MétricaTarea 5 (HW puro)Tarea 6 (RISC0 original)Tarea 7 (Nueva Forma)
Frecuencia de operación100 MHz25 MHz~28.65 MHz
Ciclos por iteración119066
Throughput (resultados/seg)100,000,000~131,578~434,090
Mejora vs Tarea 6~3.3×
Mejora vs HW dedicado1/7601/230

Conclusión

Al cambiar a la nueva forma combinacional obtuvimos un rendimiento más de tres veces mayor. Los ciclos bajaron de 190 a 66 porque nos ahorramos todos los tiempos muertos de la instrucción de multiplicar, lo que también nos dio paso a aumentar levemente el margen de nuestra frecuencia teórica rozando los 28.65 MHz. Aunque hoy estamos alcanzando aproximadamente los 434,000 cálculos por segundo con estas optimizaciones, seguimos algo rezagados en comparación con la construcción del hardware dedicado analógico, pues nos vemos muy limitados por el estilo de procesamiento en secuencia que tiene nuestro procesador y las dependencias de datos que arrastra.