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
endmoduleAl 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;endassign 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 MHzuart_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();endAl 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.vvvp sim_monitor.vvp > uart_sim_log.txtResultado 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étrica | Tarea 6 | Tarea 7 |
|---|---|---|
| Frecuencia configurada | 25 MHz (÷4) | 100 MHz (directo) |
| Frecuencia máxima — nextpnr | ~38 MHz | ~28.65 MHz |
| Frecuencia de operación real | 25 MHz | 28.65 MHz |
4. Comparativa de Rendimientos (Throughput)
Fórmula: Throughput = Frecuencia (Hz) / Ciclos_por_iteración
Tarea 6 (Baseline)
- Frecuencia: 25,000,000 Hz
- Ciclos: 190
- Throughput: 25,000,000 / 190 = ~131,578 resultados/seg
Tarea 7 (Con Multiplicador Combinacional)
- Frecuencia: 28,650,000 Hz
- Ciclos: 66
- Throughput: 28,650,000 / 66 = ~434,090 resultados/seg
Tabla Comparativa Final
| Métrica | Tarea 5 (HW puro) | Tarea 6 (RISC0 original) | Tarea 7 (Nueva Forma) |
|---|---|---|---|
| Frecuencia de operación | 100 MHz | 25 MHz | ~28.65 MHz |
| Ciclos por iteración | 1 | 190 | 66 |
| Throughput (resultados/seg) | 100,000,000 | ~131,578 | ~434,090 |
| Mejora vs Tarea 6 | — | 1× | ~3.3× |
| Mejora vs HW dedicado | 1× | 1/760 | 1/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.