Skip to content

Latest commit

 

History

55 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Extensión Vectorial — Documentación del Proyecto


1. Descripción General

Este proyecto implementa un subconjunto mínimo de la especificación de extensión vectorial de RISC-V (RVV 1.0) como un co-procesador acoplado a un procesador escalar RISC-V host. El objetivo es una implementación en hardware funcionalmente correcta que cubra las operaciones vectoriales aritméticas y de acceso a memoria.

En este archivo se puede ver un resumen de las partes importantes de la especificación RVV para este proyecto.

1.1 Parámetros Fijos

Para nuestra implementación los siguientes parámetros seran fijos y no se podran modificar en tiempo de ejecución.

Parámetro Valor Descripción
VLEN 128 bits Ancho de cada registro vectorial
SEW 32 bits Ancho de elemento estándar — solo se soportan elementos de 32 bits
LMUL 1 Agrupamiento de registros — cada instrucción opera sobre exactamente un registro
VLMAX 4 Número máximo de elementos por instrucción (VLEN / SEW)
Registros vectoriales 32 v0 – v31, cada uno de 128 bits

Dado que SEW y LMUL son fijos, no existe configuración de vtype en tiempo de ejecución. Todas las instrucciones operan implícitamente sobre 4 elementos de 32 bits cada uno.

La razón principal de esta elección de parámetros fijos es que el procesador es de 32 bits.

Distribución de VLEN y elementos

La imagen anterior muestra cómo un registro vectorial de 128 bits aloja 4 elementos independientes de 32 bits.

1.2 Funcionalidades e instrucciones implementadas

  • Aritmética vectorial-vectorial (ADD, SUB, AND, OR, XOR, SLL, SRL, SRA, SLT, SLTU)
  • Aritmética vectorial-escalar (modo VX): el valor escalar se replica en los 4 carriles
  • Acceso vectorial a memoria: cargas y almacenamientos en modalidad unit-stride, strided e indexed
  • Carga y almacenamiento del registro de máscara (VLM/VSM) sobre v0

1.3 Limitaciones Conocidas

Las siguientes características de la especificación RVV 1.0 no están implementadas:

  • Registros CSR: vtype, vl, vstart, vxrm, vxsat y vcsr no existen en el diseño. No hay configuración de tipo ni de longitud de vector en tiempo de ejecución.
  • vl variable: La implementación siempre procesa los 4 elementos (VLMAX). No existe ejecución de vector parcial.
  • Enmascaramiento por elemento: La semántica de elementos activos/inactivos (vma) no se aplica. El flag de operación de máscara solo habilita o deshabilita el acceso a DCache para las instrucciones VLM/VSM; no controla la ejecución condicional en operaciones aritméticas.
  • Instrucciones de segmento: Las variantes vlseg/vsseg no están implementadas.
  • Cargas fault-only-first: vle<eew>ff.v no está implementado.
  • Carga/almacenamiento de registro completo: vl1r/vs1r y sus variantes no están implementados.
  • SEW < 32 o SEW = 64: Solo se soportan elementos de 32 bits.
  • LMUL ≠ 1: El agrupamiento de registros está fijo en LMUL=1.

2. Arquitectura de Alto Nivel

2.1 Contexto del Sistema

Esta extensión vectorial funciona como co-procesador junto al procesador escalar RISC-V host. El procesador host es responsable del fetch de instrucciones, la decodificación y la lectura de registros escalares. Cuando el decodificador identifica una instrucción vectorial, envía señales de control pre-decodificadas y valores de operandos escalares directamente a la extensión vectorial a través de la interfaz de ve_top — la extensión vectorial no posee decodificador de instrucciones propio.

Distribución de VLEN y elementos

El decodificador del host provee dos caminos de entrada independientes a la extensión vectorial:

  • Camino ALU: campos de instrucción (funct7, funct3, rs1, rs2, rd), una bandera que indica modo vectorial-escalar (i_is_vx) y el valor del operando escalar.
  • Camino LSU: banderas de operación de memoria decodificadas (carga, almacenamiento, strided, indexed, operación de máscara), la dirección base (valor de rs1 del banco de registros escalar) y el stride (valor de rs2).

La extensión vectorial aserta o_stall de regreso al decodificador del host cuando no puede aceptar una nueva instrucción — ya sea por un conflicto en el bus de la caché de datos o por un hazard RAW. Ambas causas se describen en la Sección 2.4.

2.2 Descripción del Pipeline

La extensión vectorial utiliza un pipeline de 4 etapas. Una instrucción recorre las cuatro etapas antes de que su resultado sea confirmado en el banco de registros vectoriales.

Etapa Módulo Responsabilidad
Issue issue Lee operandos del VRF, captura campos de la instrucción en registros de pipeline
Execute execute Calcula el resultado ALU; emite el primer acceso a DCache (elementos 0–1) para LSU
MEM mem Emite el segundo acceso a DCache (elementos 2–3) para LSU; ensambla el resultado de 128 bits en cargas
Writeback writeback Escribe el resultado de vuelta al VRF (omitido en almacenamientos)

2.3 Lista de Módulos

Módulo Archivo Función
ve_integrated rtl_ve/ve_integrated.v Sistema completo: procesador host + extensión vectorial + memorias
ve_top rtl_ve/ve_top.v Módulo raíz de la extensión vectorial: instancia todos los submódulos, lógica de stall y mux de DCache
vregisters rtl_ve/vregfile/vregisters.v Banco de registros vectoriales de 32 × 128 bits
alu rtl_ve/alu/alu.v Carril aritmético/lógico individual de 32 bits
alu_array rtl_ve/alu/alu_array.v 4 carriles ALU en paralelo operando sobre un vector de 128 bits
vlsu rtl_ve/lsu/vlsu.v Generador combinacional de direcciones y señales de control para acceso a memoria
issue rtl_ve/pipeline/issue.v Etapa 1 del pipeline
execute rtl_ve/pipeline/execute.v Etapa 2 del pipeline
mem rtl_ve/pipeline/mem.v Etapa 3 del pipeline
writeback rtl_ve/pipeline/writeback.v Etapa 4 del pipeline
hazard_unit rtl_ve/pipeline/hazard_unit.v Detección de hazards RAW entre etapas del pipeline vectorial
dcache risc-v_RV32I/memory/DCache.v Caché de datos de doble puerto (128 palabras de 32 bits)

2.4 Mecanismo de Stall

El pipeline vectorial puede detenerse por dos razones, y la combinación de ambas se reporta al host mediante o_stall:

  1. Conflicto de DCache: una instrucción LSU vectorial ocupa el bus de DCache durante dos etapas consecutivas del pipeline (Execute y MEM), por lo que dos instrucciones LSU consecutivas generarían un conflicto. La condición es:
dcache_stall = s1_is_lsu AND s2_is_lsu

Cuando se aserta:

  • La etapa Issue mantiene sus registros sin cambios.
  • La etapa Execute inserta una burbuja en MEM (fuerza o_valid = 0, o_is_lsu = 0).
  • La etapa MEM continúa normalmente con la instrucción que ya está en vuelo.
  1. Hazard RAW: la instrucción entrante necesita leer un registro vectorial que una instrucción todavía en vuelo aún no ha escrito. En este caso Issue le niega la entrada al pipeline insertando burbujas hasta que el dato esté disponible. La detección se describe en detalle en el capítulo de control de hazards.

En ambos casos o_stall se aserta alto hacia el decodificador del host para que no emita una nueva instrucción ese ciclo.

2.5 Arbitraje del Bus de DCache

La DCache tiene dos puertos (A y B). Tanto Execute como MEM generan señales hacia la DCache, pero el mecanismo de stall garantiza que nunca estén ambas activas para LSU al mismo tiempo. Un mux combinacional en ve_top selecciona cuál etapa maneja el bus:

sel_mem = s2_is_lsu AND NOT s2_is_mask_op

Cuando sel_mem = 1, las señales ACCESS_23 de MEM se enrutan hacia la DCache. Cuando sel_mem = 0, se usan las señales ACCESS_01 de Execute. Las operaciones de máscara quedan fuera de esta selección (ACCESS_23 se deshabilita para VLM/VSM ya que solo se accede al elemento 0).


3. Banco de Registros Vectoriales (VRF)

El banco de registros vectoriales almacena el estado de los 32 registros vectoriales v0v31. Es el punto de partida y destino de toda operación vectorial: las instrucciones leen sus operandos de aquí al inicio del pipeline y escriben su resultado de vuelta al final.

Distribución de VLEN y elementos

Para el empaquetado de los elementos dentro de cada registro de 128 bits, ver spec_overview.md — Sección 3.

3.1 Estructura

El VRF implementa un arreglo de 32 registros de 128 bits cada uno. Todas las lecturas son combinacionales — los datos están disponibles el mismo ciclo en que se presenta la dirección. Las escrituras son síncronas.

En reset, todos los registros se inicializan a cero.

3.2 Puertos de Lectura

El VRF expone cuatro puertos de lectura independientes para permitir que una sola instrucción acceda a todos los operandos que necesita simultáneamente, sin necesidad de ciclos adicionales.

Puerto Dirección Dato Uso
A addr_a data_a Primer operando de la ALU (vs1)
B addr_b data_b Segundo operando de la ALU (vs2)
C addr_c data_c Dato a escribir en memoria en almacenamientos (vs3)
D addr_d data_d Offsets de dirección en modo indexed (vs2 offsets)

Los puertos C y D son necesarios por el modo indexed de la VLSU: en ese modo vs2 cumple el rol de registro de offsets (puerto D), y el dato que se almacena en memoria viene del campo rd de la instrucción reinterpretado como vs3 (puerto C). La especificación los trata como operandos distintos, por eso requieren puertos separados. Para más detalle ver spec_overview.md — Sección 4.3.

3.3 Puerto de Escritura

El VRF tiene un único puerto de escritura síncrono:

Señal Descripción
we Habilitación de escritura
addr_w Registro destino (0–31)
data_in Dato a escribir (128 bits)

La etapa Writeback es la única que conduce este puerto. Solo escribe cuando la instrucción es válida y no es un almacenamiento — los stores escriben en memoria, no en el VRF.

3.4 Registro de Máscara v0

El registro v0 no tiene tratamiento especial en el hardware del VRF: puede leerse y escribirse a través de los mismos puertos que cualquier otro registro. Su rol especial como registro de máscara es una convención de la especificación que se para usar las instrucciones VLM y VSM, las cuales apuntan siempre a v0 como destino o fuente.

4. ALU Vectorial

La ALU vectorial ejecuta operaciones aritméticas y lógicas elemento a elemento sobre vectores de 128 bits. Está compuesta por cuatro carriles de 32 bits que operan en paralelo, produciendo los cuatro resultados en un único ciclo combinacional.

El array de las ALUs

4.1 Estructura de Carriles

El módulo alu_array instancia cuatro unidades alu idénticas de 32 bits. Cada carril recibe el slice de 32 bits correspondiente de los operandos de entrada y produce su resultado de 32 bits de forma independiente. La misma operación (alu_op) se aplica a los cuatro carriles simultáneamente.

alu_array (128 bits)
├── carril 0: in_a[31:0]   op in_b[31:0]   → out[31:0]
├── carril 1: in_a[63:32]  op in_b[63:32]  → out[63:32]
├── carril 2: in_a[95:64]  op in_b[95:64]  → out[95:64]
└── carril 3: in_a[127:96] op in_b[127:96] → out[127:96]

4.2 Operaciones Soportadas

La operación a ejecutar se codifica en 4 bits formados por {funct7[5], funct3}, tomados directamente de la instrucción RISC-V. Este encoding es el mismo que usa la ALU escalar del procesador host para instrucciones de tipo R.

alu_op funct7[5] funct3 Operación Descripción
4'b0000 0 000 ADD Suma
4'b1000 1 000 SUB Resta
4'b0001 0 001 SLL Desplazamiento lógico a la izquierda
4'b0010 0 010 SLT Comparación con signo (resultado 0 o 1)
4'b0011 0 011 SLTU Comparación sin signo (resultado 0 o 1)
4'b0100 0 100 XOR XOR bit a bit
4'b0101 0 101 SRL Desplazamiento lógico a la derecha
4'b1101 1 101 SRA Desplazamiento aritmético a la derecha
4'b0110 0 110 OR OR bit a bit
4'b0111 0 111 AND AND bit a bit

Para los desplazamientos (SLL, SRL, SRA) solo se usan los 5 bits menos significativos del operando in_b, siguiendo la misma convención que la instrucción escalar equivalente.

4.3 Modos de Operando: VV y VX

Las operaciones aritméticas soportan dos formas de obtener sus operandos, distinguidas por el sufijo del mnemónico (.vv o .vx):

Modo VV — Vectorial-Vectorial. Es el caso base: ambos operandos provienen de registros vectoriales. vs1 se lee por el puerto A del VRF y vs2 por el puerto B, y cada carril de la ALU opera sobre el par de elementos correspondiente:

resultado[n] = vs1[n] op vs2[n]      // para n = 0, 1, 2, 3

Modo VX — Vectorial-Escalar. El segundo operando no proviene de un registro vectorial sino de un registro escalar del procesador host. Para que la alu_array pueda operar sin modificaciones, la etapa Issue replica el valor escalar en los cuatro slots de 32 bits del operando vs2 antes de capturarlo en el registro de pipeline:

vs2_replicado = { escalar, escalar, escalar, escalar }  // 4 × 32 bits
resultado[n]  = vs1[n] op escalar                        // para n = 0, 1, 2, 3

La selección entre ambos modos ocurre únicamente en Issue. Desde la perspectiva de Execute y de la alu_array, una instrucción VX es indistinguible de una VV — ambas llegan con dos operandos vectoriales de 128 bits.


5. Unidad de Carga/Descarga Vectorial (VLSU)

La VLSU es el módulo encargado de traducir una instrucción de memoria vectorial en accesos individuales a la DCache. Es un módulo puramente combinacional y recibe los parámetros de la operación (dirección base, stride, offsets, modo de acceso), luego genera las direcciones y señales de control para los puertos de la DCache.

Los modos de direccionamiento que implementa están descritos en spec_overview.md — Sección 4.1.

El array de las ALUs

5.1 Cálculo de Direcciones

La VLSU calcula siempre las direcciones de los 4 elementos en paralelo. La fórmula depende del modo de acceso:

Modo Dirección del elemento n
Unit-stride base + n × 4
Strided base + n × stride
Indexed base + offset[n]

En modo strided el salto proviene del registro escalar rs2 y puede ser cualquier valor, incluso negativo o cero. En modo indexed los 4 offsets provienen del registro vectorial vs2, leído por el puerto D del VRF.

5.2 Acceso en Dos Fases

Un vector completo requiere 4 accesos a memoria, pero la DCache solo tiene 2 puertos. La solución es dividir la operación en dos fases de 2 elementos cada una:

Fase Elementos Puerto A Puerto B Etapa del pipeline
ACCESS_01 0 y 1 elemento 0 elemento 1 Execute
ACCESS_23 2 y 3 elemento 2 elemento 3 MEM

La fase no es una decisión interna de la VLSU: llega como entrada desde el pipeline. La instancia de la VLSU en Execute siempre opera en fase ACCESS_01, y la instancia en MEM siempre en fase ACCESS_23. Así, una instrucción de memoria completa sus 4 accesos en 2 ciclos consecutivos conforme avanza por el pipeline.

5.3 Ruteo de Salidas

Con las 4 direcciones ya calculadas, la fase determina cuáles se envían a los puertos de la DCache. Junto con cada dirección se generan las señales de habilitación (read_en o write_en según sea carga o almacenamiento) y, en almacenamientos, el slice de 32 bits correspondiente del dato vs3.

Cuando la entrada de habilitación general está desactivada — por ejemplo durante un stall — ambos puertos quedan inactivos.

5.4 Operaciones de Máscara (VLM/VSM)

Las instrucciones de máscara son un caso especial: solo transfieren 1 byte (los 4 bits de máscara caben en el byte 0 de v0, ver spec_overview.md — Sección 3). Para esto la VLSU:

  • Solo accede al elemento 0 durante ACCESS_01; el puerto B queda desactivado.
  • Reduce el byte-enable del puerto A a 4'b0001 — solo el byte 0 de la palabra.
  • La fase ACCESS_23 se omite por completo: la instancia de MEM no habilita ningún acceso.

5.5 Ensamblado del Resultado en Cargas

Como los 4 elementos llegan en dos ciclos distintos, el resultado de 128 bits se construye por partes:

  1. En Execute, los datos de los elementos 0 y 1 (fase ACCESS_01) se capturan en un registro intermedio de 64 bits.
  2. En MEM, los datos de los elementos 2 y 3 (fase ACCESS_23) se concatenan con ese registro para formar los 128 bits completos.

Para VLM solo se conserva el byte 0 del primer acceso; el resto del resultado se rellena con ceros.


6. Pipeline

Este capítulo describe en detalle cada etapa del pipeline de la extensión vectorial: qué submódulos instancia, qué información captura en su registro de salida y cómo se comporta ante un stall. Los submódulos funcionales (VRF, ALU, VLSU) ya fueron descritos en los capítulos 3–5.

Pipeline de la extensión vectorial

6.1 Registros de Pipeline

Entre cada par de etapas existe un registro de pipeline que captura toda la información de la instrucción y las señales de estos siguen la siguiente convención de nombres:

Prefijo Frontera Contenido principal
s1_* Issue → Execute Operandos leídos del VRF, operación ALU, banderas LSU
s2_* Execute → MEM Resultado ALU, banderas LSU, datos de ACCESS_01
s3_* MEM → Writeback Resultado final (ALU o carga ensamblada), registro destino

Cada registro lleva además una bandera valid que indica si contiene una instrucción real o una burbuja, y una bandera is_lsu que distingue el camino de memoria del camino aritmético.

6.2 Etapa Issue

Issue es la puerta de entrada del pipeline. Sus responsabilidades:

  • Lectura del VRF (combinacional): con los campos de la instrucción conduce las direcciones de los 4 puertos de lectura — rs1 al puerto A, rs2 a los puertos B y D, y rd al puerto C. Los datos quedan disponibles el mismo ciclo.
  • Modo VX en instrucciones aritméticas: si la instrucción es vectorial-escalar, replica el valor escalar en los 4 slots del operando vs2 (ver Sección 4.3).
  • Captura en el registro de pipeline: en el flanco de reloj, todos los campos de la instrucción y los operandos leídos se capturan en las señales s1_*.

Issue lee siempre los 4 puertos del VRF sin importar el tipo de instrucción; las etapas siguientes simplemente ignoran los datos que no necesitan.

6.3 Etapa Execute

Execute contiene los dos submódulos de ejecución, que operan en paralelo sobre la instrucción que entra:

  • alu_array: calcula el resultado aritmético de los 4 carriles a partir de s1_vs1_data y s1_vs2_data. El resultado se captura en s2_result.
  • vlsu en fase ACCESS_01: si la instrucción es de memoria, genera los accesos a DCache de los elementos 0 y 1. En cargas, los datos devueltos por la DCache (que responde de forma combinacional) se capturan en el registro s2_asm_lo de 64 bits para su ensamblado posterior.

Solo uno de los dos caminos produce información útil por instrucción, la bandera is_lsu determina cuál. La ALU computa siempre, sin importar el tipo de instrucción; el VLSU también calcula sus direcciones siempre, pero sus habilitaciones hacia la DCache solo se activan cuando la instrucción es LSU y no hay stall.

6.4 Etapa MEM

MEM completa la mitad restante de los accesos a memoria:

  • vlsu en fase ACCESS_23: genera los accesos de los elementos 2 y 3. Se habilita solo si la instrucción es LSU válida y no es operación de máscara.
  • Ensamblado del resultado de carga: concatena los datos de la DCache de este ciclo (elementos 2–3) con s2_asm_lo (elementos 0–1) formando los 128 bits completos. Para VLM el resultado es {120'b0, byte_0}.
  • Selección del resultado: el registro s3_result captura el vector ensamblado si la instrucción es una carga, o s2_result (resultado ALU) en caso contrario.

6.5 Etapa Writeback

Writeback es puramente combinacional — no tiene registro de salida propio, conduce directamente el puerto de escritura del VRF:

we      = s3_valid AND NOT s3_is_store
addr_w  = s3_rd
data_in = s3_result

Los almacenamientos llegan hasta esta etapa con su bandera is_store activa, lo que suprime la escritura: su efecto ya ocurrió en las etapas Execute y MEM al escribir en la DCache.

6.6 Comportamiento ante Stalls

El pipeline tiene dos fuentes de stall independientes, cuya combinación (OR) se reporta al host como o_stall:

Fuente Condición Efecto en Issue Efecto en Execute
Conflicto de DCache s1_is_lsu AND s2_is_lsu Congela su registro de salida (la instrucción LSU espera) Inserta burbuja en MEM
Hazard RAW Instrucción entrante lee un registro aún no escrito Inserta burbuja (la instrucción no entra al pipeline) Avanza normalmente

La diferencia clave: en el conflicto de DCache la instrucción ya está dentro del pipeline y debe esperar en su lugar, mientras que en el hazard RAW la instrucción todavía no ha entrado y simplemente se le niega la entrada hasta que el productor termine. La detección del hazard RAW la realiza el módulo hazard_unit, que compara los registros fuente de la instrucción entrante (rs1, rs2, y rd en almacenamientos) contra el registro destino de las instrucciones en vuelo en las tres etapas siguientes.


7. Integración con el Host

El módulo ve_integrated es el nivel más alto de la jerarquía: conecta el pipeline escalar RV32I de 5 etapas (Fetch → Decode → Execute → MEM → Writeback) con la extensión vectorial (ve_top), compartiendo un único decodificador, un banco de registros escalar y la caché de datos.

7.1 El Decodificador Compartido

El sistema tiene completo tiene un solo decodificador (Modified_DecodeUnit), que pertenece al pipeline escalar pero fue extendido para reconocer también las instrucciones vectoriales. Cuando decodifica una instrucción vectorial, emite las señales pre-decodificadas hacia ve_top (los caminos ALU y LSU descritos en la Sección 2.1) y la instrucción no produce ningún efecto en el pipeline escalar.

Además de los campos de la instrucción, el decodificador captura en ese momento los valores escalares que la instrucción vectorial necesita, leyéndolos del banco de registros escalar:

Valor Registro fuente Uso en la extensión vectorial
vec_scalar rs1 Operando escalar en modo VX
vec_base_addr rs1 Dirección base para cargas/almacenamientos
vec_stride rs2 Salto entre elementos en modo strided

7.2 Arbitraje de la DCache

La DCache tiene dos puertos, repartidos así entre los dos pipelines:

  • Puerto B: exclusivo de la extensión vectorial.
  • Puerto A: compartido — un mux combinacional decide quién lo controla:
vec_port_a_active = vext_read_en OR vext_write_en

Cuando la extensión vectorial tiene un acceso activo, controla el puerto A; en caso contrario lo controla la etapa MEM del pipeline escalar. No se necesita arbitraje más sofisticado porque el stall vectorial (descrito en el Capítulo 8) garantiza que el pipeline escalar está congelado mientras una instrucción vectorial accede a memoria, por esto nunca hay competencia real por el puerto.

7.3 Carga de Programa y Ejecución

El sistema expone una interfaz mínima para los testbenches:

  • Durante el reset (rst = 1), el testbench escribe el programa en la ICache mediante las señales i_imem_wen, i_imem_addr (indexada por palabra) e i_imem_data.
  • Al liberar el reset, la ICache pasa a modo solo-lectura y el pipeline comienza a ejecutar desde INITIAL_PC.
  • Por convención, los programas usan el registro x20 como sentinel de finalización: x20 = 0 mientras el programa corre, y la última instrucción ejecuta addi x20, x0, 1. El testbench monitorea este registro para detectar la terminación y medir ciclos.

8. Control de Hazards y Forwarding

Un hazard RAW (Read After Write) ocurre cuando una instrucción necesita leer un registro que una instrucción anterior — todavía dentro del pipeline — aún no ha terminado de escribir. Si no se hace nada, la instrucción leería el valor viejo:

add x1, x2, x3    # escribe x1 (el resultado llega al RF varios ciclos después)
add x4, x1, x5    # lee x1 — ¿valor nuevo o viejo?

Cada uno de los dos pipelines del sistema resuelve este problema con una estrategia distinta.

8.1 Manejo en el Host: Forwarding

El pipeline escalar resuelve los hazards RAW sin detener el pipeline: en lugar de esperar a que el dato llegue al banco de registros, lo toma directamente de la etapa donde ya está calculado. Un mux en la etapa Decode selecciona, para cada operando, el valor más reciente disponible entre cuatro fuentes:

Prioridad Ruta Fuente del dato
1 EX→EX Resultado de la ALU en Execute (instrucción a 1 de distancia)
2 MEM→EX Resultado en la etapa MEM — de la ALU, o del dato leído de la DCache con extensión de signo
3 WB→EX Dato que Writeback está por escribir al RF (a 3 de distancia)
4 RF Lectura normal del banco de registros (sin dependencia)

Existe un único caso que el forwarding no puede resolver: el hazard load-use, cuando la instrucción inmediatamente siguiente a una carga usa el registro cargado. El dato aún no ha salido de la DCache cuando el consumidor llega a Execute. Para este caso se inserta automáticamente un ciclo de stall: Fetch y Decode se congelan, Execute recibe una burbuja, y al ciclo siguiente la ruta MEM→EX ya puede entregar el dato.

8.2 Manejo en la Extensión Vectorial: Stall

El pipeline vectorial usa la estrategia opuesta: detección y espera, sin caminos de forwarding. El módulo hazard_unit compara los registros fuente de la instrucción que intenta entrar al pipeline contra el registro destino de las instrucciones en vuelo en las tres etapas siguientes:

raw_stall = entrante.valid AND (
    (s1 escribe vd == rs1/rs2/rd_entrante) OR
    (s2 escribe vd == rs1/rs2/rd_entrante) OR
    (s3 escribe vd == rs1/rs2/rd_entrante) )

Los almacenamientos en vuelo se excluyen de la comparación (no escriben al VRF), pero el campo rd de un almacenamiento entrante sí se compara — porque en un store rd es en realidad vs3, el registro que se va a leer.

Mientras raw_stall esté activo, la etapa Issue inserta burbujas: la instrucción entrante no ingresa al pipeline mientras la instrucción productora avanza normalmente hasta escribir el VRF. En el peor caso (dependencia con la instrucción inmediatamente anterior) la espera es de 3 ciclos.

Ahora bien, detener el pipeline vectorial no es suficiente: como el host es quien emite las instrucciones, también hay que detenerlo. Para esto la extensión vectorial reporta su stall al host, donde se combina con el stall load-use del propio pipeline escalar — si cualquiera de los dos está activo, las etapas Fetch y Decode del host se congelan: el PC no avanza y la instrucción que estaba en decode se queda esperando. Como el decode congelado sigue presentando la instrucción vectorial en la entrada de la extensión, esta no se pierde: en cuanto el stall baja, entra al pipeline vectorial y el host continúa con la instrucción siguiente.


9. Verificación

La verificación del proyecto sigue una metodología jerárquica con pruebas dirigidas, organizada en cuatro niveles según qué parte del sistema se ejercita: primero los módulos individuales en de manera independiente, luego cada uno de los dos pipelines por separado (el escalar del host y el vectorial), y finalmente el sistema integrado donde ambos conviven. Todos los testbenches usan casos de prueba definidos manualmente con verificación automática — cada caso compara el resultado obtenido contra el esperado y acumula contadores de PASS/FAIL que se reportan al final de la simulación.

Niveles de verificación

Las herramientas utilizadas son Icarus Verilog (iverilog/vvp) para compilación y simulación, y GTKWave para inspección de formas de onda. Cada testbench genera un archivo .vcd con las señales de la simulación.

9.1 Niveles de Verificación

Nivel 1 — Módulos individuales: Cada bloque funcional se prueba de manera aislada, aplicando estímulos directamente sobre sus puertos: tb_alu (todas las operaciones de la ALU), tb_vregfile (escritura síncrona, lectura dual, escritura inhibida, reset) y tb_vlsu_integration (direcciones, enables y rutas de datos para todos los modos de acceso, en ambas fases).

Nivel 2 — Pipeline escalar del host: Programas puramente escalares (lw/add/sw) donde la extensión vectorial nunca se activa. Verifican el pipeline escalar — en particular sus caminos de forwarding — y producen las referencias escalares de rendimiento: tb_scalar_perf (N=4, sin aprovechar forwarding, NOPs manuales), tb_scalar_fwd (N=4, optimizado con 0 NOPs en el camino crítico), tb_scalar_n8 (N=8) y tb_vector_add_scalar (N=16).

Nivel 3 — Extensión vectorial: tb_ve_top prueba el pipeline vectorial completo aislado del pipeline escalar: el testbench hace el papel del host, simulando el banco de registros escalar y la DCache, e ingresando instrucciones vectoriales codificadas.

Nivel 4 — Sistema integrado: Programas ensamblados reales ejecutados sobre ve_integrated, ejercitando la interacción completa entre ambos pipelines — decodificador compartido, vec_stall congelando el host, y la DCache compartida: tb_ve_integrated (verificación funcional con programas mixtos), tb_vector_perf (rendimiento, N=4) y tb_vector_n8 (rendimiento, N=8, con instrucciones vectoriales consecutivas que ejercitan el stall de DCache y la hazard_unit).

9.2 Medición de Rendimiento

Los testbenches de rendimiento de los niveles 2 y 4 forman una familia comparativa: todos resuelven el mismo problema — la suma elemento a elemento de dos arreglos, C[i] = A[i] + B[i] — con los mismos datos, midiendo los ciclos de reloj hasta que el sentinel x20 indica la finalización del programa. Esto permite comparar directamente el pipeline escalar sin forwarding, el escalar con forwarding y el vectorial, para distintos tamaños de problema (N = 4, 8, 16).

Los resultados de estas mediciones se presentan en el Capítulo 10.

9.3 Ejecución de los Testbenches

El Makefile del proyecto define un target por testbench. Cada target compila el RTL necesario, ejecuta la simulación y reporta el archivo de ondas generado:

make tb_alu               # verificación de la ALU
make tb_ve_integrated     # verificación del sistema completo
make tb_vector_perf       # medición de rendimiento vectorial
make all                  # ejecuta todos los testbenches

Para inspeccionar las formas de onda de una simulación:

gtkwave tb_ve_integrated.vcd

10. Resultados

Las mediciones se obtuvieron ejecutando el benchmark descrito en la Sección 9.2 — la suma elemento a elemento C[i] = A[i] + B[i] — sobre las tres configuraciones del sistema, contando los ciclos de reloj desde el inicio del programa hasta que el sentinel indica la finalización.

Resultados

10.1 Mediciones

Configuración N = 4 N = 8
Escalar sin forwarding (baseline) 59 75*
Escalar con forwarding 23 39
Vectorial SIMD (VLEN=128, 4 carriles) 57 60

* Valor estimado mediante un modelo lineal; las demás celdas son mediciones directas de simulación RTL.

Para pocos elementos el resultado muestra varias cosas: en N=4 el pipeline vectorial (57 ciclos) apenas supera al baseline (59 ciclos, ×1.04) y pierde claramente contra el escalar con forwarding (23 ciclos). La razón es el costo fijo de la interfaz escalar→vectorial: cada instrucción vectorial congela el pipeline del host mientras se ejecuta, y el programa necesita instrucciones escalares de preparación (direcciones base) entre las instrucciones vectoriales. Para 4 elementos, ese costo fijo domina sobre el beneficio del paralelismo.

En N=8 la tendencia empieza a invertirse: el vectorial solo agrega 3 ciclos (de 57 a 60) mientras el escalar con forwarding agrega 16 (de 23 a 39). El auemnto de rendimiento contra el baseline sube a ×1.25.

10.2 Escalabilidad

Ajustando un modelo lineal a las mediciones se obtienen las pendientes de crecimiento:

Configuración Costo incremental
Escalar sin forwarding +4 ciclos por elemento
Escalar con forwarding +4 ciclos por elemento
Vectorial SIMD +0.75 ciclos por elemento

Las dos configuraciones escalares crecen a la misma tasa — el forwarding reduce el costo fijo pero no cambia la naturaleza secuencial del procesamiento: cada elemento requiere su propia carga, suma y almacenamiento. El vectorial, en cambio, procesa 4 elementos por instrucción, por lo que su pendiente es unas 5 veces más pequeña.

Observando los modelo, las rectas del vectorial y del escalar con forwarding se cruzan en N ≈ 14 elementos (~65 ciclos): a partir de ese tamaño de problema, la extensión vectorial supera incluso a la versión escalar optimizada, y la ventaja crece indefinidamente con N — en N=32 el vectorial ya es ×1.7 veces más rápido.

De esta implementacion y pruebas se puede concluir que la extensión vectorial no muestra un mejor rendimiento para operaciones aisladas sobre una pequeña cantidad de elementos, donde el costo fijo de la interfaz domina, pero se vuelve la opción claramente superior conforme crece el volumen de datos — que es precisamente el caso de uso para el que las extensiones vectoriales existen.

About

Proyecto Electrico

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages