IP Core: vector_source (Fuente de Muestras SSA)¶
1. Resumen¶
La IP vector_source es el módulo generador e inyector de muestras del receptor digital bajo la especificación SSA (Sample Stream Architecture). Proporciona una interfaz unificada e intercambiable de muestras I/Q en banda base o FI para alimentar la cadena de procesado DDC (drx_ddc_quad_top).
Su diseño garantiza la regla fundamental del sistema: nada aguas abajo depende de la fuente concreta de muestras.
Implementa un solo modo, BRAM-Loop (D-01): reproducción continua en bucle de una forma de onda almacenada en bloques de memoria BRAM dedicados, uno por canal. Desde el 4-oct-2026 (mapa de registros v11) el contenido de esos bancos es escribible en tiempo de ejecución por el PS a través de una ventana AXI-Lite, de modo que el vector deja de quedar fijado en el bitstream sin dejar de entregarse a una muestra por ciclo de reloj.
Modo Throttled DDR: existió entre Z4.3 y el 4-oct-2026, y ya no está
Entre el 1 y el 4 de octubre de 2026 esta IP tuvo un segundo modo, seleccionable con CTRL bit 1: las muestras llegaban desde la DDR del PS por un AXI4-Stream de 16 bits alimentado por el canal MM2S de un AXI DMA. Resolvía el mismo problema —elegir el vector en caliente— pero entregaba una muestra cada \(N_{CH}\) ciclos.
Eso rompía el dato por una razón que no está en esta IP: el DDS Compiler no tiene aclken y avanza un paso de fase por ciclo de reloj mire o no valid, así que el DDC mezclaba a \(\approx N_{CH}\) veces el IF y la salida caía al suelo de ruido (−87,3 dBFS medidos contra −9,02 dBFS del modelo dorado). Ver P-16.
La ventana de carga de v11 elimina la fuente con huecos en lugar de arreglar el NCO. La causa raíz sigue en el diseño para cualquier fuente con huecos futura.
2. Características Principales¶
- Arquitectura Multicanal: Generación paralela parametrizable para \(N_{CH}\) canales (\(N_{CH} = 3\) o \(4\)), con un banco de BRAM independiente por canal.
- Tasa plena: una muestra por ciclo y por canal mientras el consumidor acepte, sin huecos estructurales.
- Carga en tiempo de ejecución: el PS escribe los bancos por
VEC_ADDR/VEC_DATA/VEC_LEN(mapa v11), bajo la protección deCTRLbit 2 (vector_load). - Difusión o escritura por banco:
vec_bank = 7escribe los \(N_{CH}\) bancos a la vez;vec_bank = cescribe solo el canal \(c\). Los bancos son independientes, así que un estímulo distinto por canal (Config A: Tx + H + V) es escribir tres veces, no un cambio de diseño. - Dos longitudes separadas:
DEPTHes la capacidad física del banco;vec_lenes lo que se reproduce en bucle, y manda sobreDEPTH. - Cumplimiento Estricto SSA: interfaz con soporte de contrapresión (backpressure) por canal.
- Inferencia Limpia de BRAM: un proceso de lectura y un proceso de escritura separados, sin sentencias compartidas y sin reset asíncrono sobre la matriz de memoria.
- Inicialización parametrizada: precarga hexadecimal mediante
$readmemh(INIT_FILE), conDEPTHlíneas exactas.
3. Diagrama de Bloques¶
graph TD
subgraph Controls ["Control & Reset"]
CLK["clk"]
RST["rst_n"]
end
subgraph Load ["Ventana de carga (mapa v11)"]
VLOAD["vec_load (CTRL bit 2)"]
VWE["vec_we / vec_addr / vec_bank / vec_data"]
VLEN["vec_len"]
DEC["Decodificador de banco (bank_hit)"]
end
subgraph BRAM_Path ["Bancos de BRAM"]
MEM0["BRAM Canal 0 (mem)"]
MEM1["BRAM Canal 1 (mem)"]
MEM2["BRAM Canal 2 (mem)"]
PTR["Contador de Lectura rd_ptr"]
LAST["last_idx = vec_len - 1"]
end
subgraph Outputs ["Interfaz SSA hacia DDC"]
VAL["valid[0..N_CH-1]"]
DAT["data[0..N_CH-1]"]
LST["last[0..N_CH-1]"]
RDY["ready[0..N_CH-1]"]
end
VWE --> DEC
VLOAD --> DEC
DEC --> MEM0
DEC --> MEM1
DEC --> MEM2
VLEN --> LAST
LAST --> PTR
VLOAD --> PTR
RDY --> PTR
PTR --> MEM0
PTR --> MEM1
PTR --> MEM2
MEM0 --> DAT
MEM1 --> DAT
MEM2 --> DAT
PTR --> VAL
LAST --> LST
4. Descripción de Interfaces y Señales¶
4.1. Interfaces de Reloj y Control¶
| Nombre de Puerto | Dirección | Ancho (bits) | Protocolo | Descripción |
|---|---|---|---|---|
clk |
Entrada | 1 | Clock | Reloj principal del dominio de procesado PL (FS_HZ). |
rst_n |
Entrada | 1 | Reset | Reset global asíncrono activo en bajo. No alcanza la matriz de memoria. |
4.2. Ventana de Carga (desde drx_axil_regs, mapa v11)¶
| Nombre de Puerto | Dirección | Ancho (bits) | Descripción |
|---|---|---|---|
vec_load |
Entrada | 1 | CTRL bit 2. En alto: congela el lector y habilita las escrituras. Al bajar, deja rd_ptr en 0. |
vec_we |
Entrada | 1 | Pulso de un ciclo por muestra. vec_addr, vec_bank y vec_data son válidos en ese mismo ciclo. |
vec_addr |
Entrada | $clog2(DEPTH) |
Dirección de escritura dentro del banco, en muestras. |
vec_bank |
Entrada | 3 | Banco de destino: 0…N_CH-1 un canal, 7 difusión a todos. Un valor entre \(N_{CH}\) y 6 no escribe en ninguno. |
vec_data |
Entrada | SAMPLE_BITS (16) |
Muestra con signo. |
vec_len |
Entrada | $clog2(DEPTH) |
Longitud del bucle en muestras. 0 se trata como 1. |
4.3. Interfaz de Salida SSA Multicanal (Hacia DDC)¶
| Nombre de Puerto | Dirección | Ancho (bits) | Protocolo | Descripción |
|---|---|---|---|---|
ready[N_CH] |
Entrada | \(N_{CH}\) | SSA Stream | Señal de contrapresión desde la cadena DDC por canal. |
valid[N_CH] |
Salida | \(N_{CH}\) | SSA Stream | Indicador de muestra válida por canal. |
data[N_CH] |
Salida | \(N_{CH} \times \text{SAMPLE\_BITS}\) | SSA Stream | Bus de datos de muestra real de entrada \(x[n]\) por canal. |
last[N_CH] |
Salida | \(N_{CH}\) | SSA Stream | Marcador de fin de ventana por canal: pulsa en la muestra vec_len - 1. |
5. Parámetros de Configuración (Generics)¶
| Parámetro | Tipo | Valor por Defecto | Descripción |
|---|---|---|---|
N_CH |
integer |
3 |
Número de canales paralelos de procesamiento (\(N_{Rx}\)). |
DEPTH |
integer |
4000 |
Capacidad física de cada banco, en muestras. drx_top lo fija a VECTOR_BRAM_DEPTH (8192). |
INIT_FILE |
string |
"" |
Ruta del fichero hexadecimal consumido por $readmemh. Sin valor por defecto a propósito: la plataforma la elige drx_top (D-02). |
AddrBits |
localparam |
$clog2(DEPTH) |
Ancho de vec_addr, vec_len y rd_ptr. Está en la lista de parámetros, no en el cuerpo, porque los puertos lo necesitan. |
6. Detalles Operativos¶
6.1. Las dos longitudes, y por qué no pueden ser el mismo número¶
DEPTH es capacidad; vec_len es lo que suena. El puntero de lectura envuelve en vec_len - 1, no en DEPTH - 1.
La distinción no es un lujo: la longitud reproducida tiene que ser múltiplo de IF_NORM_DEN (25 en ZedBoard) para que el tono complete un número entero de periodos y no haya salto de fase al envolver, y una potencia de dos no lo es. Con VECTOR_BRAM_DEPTH = 8192 y VECTOR_LOOP_LEN = 4000 (ambos en config/params.toml, con el gate de tools/gen_params.py comprobando las dos condiciones), el banco son 4 RAMB36 exactos por canal y el bucle precargado ocupa la mitad.
El máximo valor útil de vec_len es DEPTH - 1: con DEPTH potencia de dos exacta, el propio DEPTH no cabe en $clog2(DEPTH) bits. Es la misma trampa que documenta rtl/drx_range_gate.sv:152, y DrxReg_VecSetLen() la rechaza en vez de dejarla truncar.
6.2. Reproducción¶
- Un contador
rd_ptrpor canal recorre cíclicamente de0avec_len - 1. - El envolvente compara con
>=y no con==. Sivec_lense acorta con el puntero ya más allá del nuevo final, una igualdad estricta obligaría a dar la vuelta entera al banco —recorriendo la parte no cargada— antes de resincronizar. Con>=el bucle nuevo entra en el ciclo siguiente, al precio de una muestra fuera de rango. - La lectura está registrada (
out_data_r), y solo avanza cuando el dato anterior ya se consumió: nunca pisa una muestra no leída. last[gc]se activa en la muestravec_len - 1. Es un marcador artificial de fin de ventana para ejercitar el framing dedrx_range_gate, no el PRF real.
6.3. Carga¶
vec_loaden alto congela el lector (can_advance = 0) y lo rearma:rd_ptra 0 yvaliden bajo. Así una carga nueva empieza siempre en la muestra 0 y el rayo que se compara contra el modelo dorado arranca igual en cada selección.- Las escrituras se cualifican con
vec_loadtambién dentro de esta IP, no solo en quien generavec_we. Con el lector corriendo, una escritura dejaría medio vector viejo y medio nuevo sonando a la vez: sin error, sin bandera, solo un espectro raro. - Como con
vec_loaden alto no hay lecturas, los dos puertos de la memoria nunca coinciden.
stateDiagram-v2
[*] --> REPRODUCIENDO
REPRODUCIENDO --> REPRODUCIENDO : can_advance [rd_ptr = (rd_ptr >= vec_len-1) ? 0 : rd_ptr+1]
REPRODUCIENDO --> CARGANDO : vec_load = 1
state CARGANDO {
[*] --> Congelado : rd_ptr = 0, valid = 0
Congelado --> Congelado : vec_we & bank_hit [mem[vec_addr] <= vec_data]
}
CARGANDO --> REPRODUCIENDO : vec_load = 0 [arranca en la muestra 0]
6.4. Inferencia de BRAM: por qué dos procesos y ninguno con reset¶
Vivado solo reconoce el patrón como BRAM cuando cada memoria tiene su propio proceso de lectura y su propio proceso de escritura, sin ninguna sentencia compartida. Con un if/else compartido —y hasta con (* ram_style = "block" *)— Vivado real contestó Infeasible... trying to implement using LUTRAM, hallazgo medido sobre drx_range_gate.sv el 19-sep-2026.
Ninguno de los dos procesos lleva reset asíncrono en su sensibilidad: ninguna BRAM real de Xilinx tiene borrado asíncrono de contenido, y el síntoma es RAM is sensitive to asynchronous reset signal. out_data_r viaja con la lectura por el mismo motivo; su valor tras reset no se observa nunca, porque out_valid_r —que sí se resetea— se lo impide a cualquier consumidor.
6.5. Precarga y arranque en frío¶
$readmemh(INIT_FILE, mem) precarga los DEPTH valores del fichero que escribe tools/gen_vector_source_init.py: las primeras VECTOR_LOOP_LEN muestras son el tono con fase cerrada y el resto ceros. El fichero tiene DEPTH líneas exactas a propósito — uno más corto dejaría palabras indefinidas que un vec_len cargado en caliente sí podría llegar a leer.
Con vec_len reseteando a VECTOR_LOOP_LEN, un sistema que nunca escriba la ventana reproduce exactamente el bucle de v10 y anteriores, que es la condición para que los −9,03 dBFS ya medidos en placa sigan siendo la línea base de comparación.
El $readmemh solo corre al cargar el bitstream. No hay forma de volver al contenido de síntesis por software: para recuperar el tono nominal hay que cargar el vector tono_if del catálogo, que es el mismo.
6.6. Lo que esta IP no puede hacer¶
Releer su propia memoria. No hay camino de lectura desde la BRAM hacia AXI-Lite, y añadirlo costaría un puerto más por banco. La verificación de que una carga es correcta es el espectro del rayo contra el modelo dorado, no un volcado de la memoria.
Sostener un estímulo no repetitivo de la longitud de un rayo. Un rayo en modo fino son 3689 bins × \(R_1\) = 208 416 muestras por canal, o 102 tiles de BRAM por canal sobre los 140 de la pieza entera. Lo que esta IP reproduce es, y seguirá siendo, un bucle: no admite un perfil que dependa del rango.
7. Verificación¶
L0 (sim/tb/tb_vector_source.py, vía el shim sim/tb_sv/vector_source_tb_dut.sv): carga y reproducción en orden, vec_len mandando sobre DEPTH, last en vec_len-1, difusión contra escritura por banco, un banco inexistente que no escribe en ninguno, una escritura sin vec_load que no entra, el rearme del puntero al soltar vec_load, vec_len = 0 como bucle de una muestra, y el reenganche inmediato al acortar vec_len en caliente.
La alineación del strobe con la dirección y el dato se verifica del lado del productor, en sim/tb/tb_drx_axil_regs.py, y está comprobada por mutación.
Esta IP no tiene caso L1: no hay IP de Xilinx en su camino y su salida es el estímulo, no un resultado que comparar contra el modelo dorado.