ADR-001 · fs, R₁ y R₂ — propuesta para sustituir a D-04¶
Estado: PROPUESTA. No aplicada. Nada de config/params.toml se ha tocado. Sustituir
D-04 exige firma de dirección técnica y de compra, que son los dueños de
P-08; esta página es el documento sobre el que firmar o rechazar, no el
registro de una decisión ya tomada.
Fecha de redacción: 30 de agosto de 2026 · Sustituiría a: D-04 · Cierra: P-01 y P-08 · Toca también: D-02, por el reloj de la plataforma de desarrollo.
Contexto¶
D-04 fijó R₁ = 208 y R₂ = 416 con fs = 250 MHz, que es el rate máximo de los convertidores del
FMC213. De ahí sale una celda de 249,427 m en el modo grueso. Dos hallazgos posteriores dicen que
esa combinación no sirve:
- P-01. ORPG no tolera un espaciado aproximado: lo valida contra una lista blanca. La
recombinación azimutal de super-res admite
{250, 1000}en reflectividad y solo 250 en Doppler, y la validación de moments dual-pol exige 250 exacto. Una celda de 249,427 m se declara como 249 y el radial se descarta, no se degrada. - P-08.
fses un grado de libertad. Los convertidores son de hasta 250 MSPS; nada obliga a operarlos ahí. Sifsse elige bien, la celda cae donde se quiere sin necesidad de remuestreo fraccionario.
El dato que faltaba para decidir ya está: el ADC del FMC213 son dos TI ADS42JB69, cuya tabla de condiciones recomendadas especifica 60–250 MSPS en modo 10x y 40–156,25 MSPS en modo 20x. 143,9 MHz está dentro de los dos.
Decisión propuesta¶
fs = 143,9 MHz, R₁ = 120, R₂ = 240, con el IF de 60 MHz muestreado directo en la primera zona
de Nyquist.
| R | celda | error acumulado a 460 km | bins a 460 km | |
|---|---|---|---|---|
| Modo fino | 120 | 125,000330 m | 1,21 m sobre 3679 bins | 3680 |
| Modo grueso | 240 | 250,000660 m | 1,21 m sobre 1839 bins | 1840 |
Contra los 1054 m de D-04 tal como está hoy. El valor que se declara a ORPG es 250 exacto, que es lo que exige la lista blanca de P-01, y 1840 bins son justo el tope duro de reflectividad del ORPG (1840 × 250 m = 460 km), no uno por encima.
Por qué esta y no otra de la tabla de alternativas de P-08:
- Reloj en rejilla de 100 kHz. 143,9 MHz sale por integer-N desde la referencia de 10 MHz de la
casa. Sin PLL fraccional-N y por tanto sin espurios fraccionales, que era la única objeción seria
a la variante de
fsexacta (249,427325 MHz). - Factores de diezmado limpios. 240 = 2⁴·3·5 y 120 = 2³·3·5, frente al 13 de 208 = 2⁴·13, que es un factor incómodo para una cadena CIC.
- R₂ = 2·R₁ intacto, así que se conserva la cadena única con etapa ÷2 y no hacen falta dos cadenas, que es lo que obligaría la opción B de P-01 (R₂ = 417).
- Cuatro transceptores en vez de ocho. A 250 MHz el modo 20x del ADS42JB69 queda fuera de rango y hay que usar 10x, o sea cuatro lanes por ADC. A 143,9 MHz el 20x sirve (2,878 Gbps, bajo el máximo de 3,125), y los cuatro canales de la tarjeta caben en cuatro MGTs. Entra en la selección de portadora de P-03.
Lo que esto obliga a cambiar y no estaba en P-08¶
El fs de la ZedBoard también se mueve. El paquete de parámetros exige que el IF caiga en un
número entero de hercios en las dos plataformas — lo comprueba drx_params_selftest.c con
IF_HZ · IF_NORM_DEN == FS_HZ · IF_NORM_NUM, y Params.if_hz levanta excepción si no. Con
fs = 143,9 MHz el IF normalizado pasa a ser 60/143,9 = 600/1439, y 1439 es primo, así que
cualquier fs de plataforma tiene que ser múltiplo de 1439 Hz. 100 MHz no lo es.
fs de ZedBoard |
IF | dilatación | nota |
|---|---|---|---|
| 99,998988 MHz | 41,6952 MHz | 1,439015 | Recomendada. 1439 × 69 492 |
| 100,000427 MHz | 41,6958 MHz | 1,438994 | Lo más cerca de 100 MHz por arriba |
| 100,73 MHz | 42 MHz | 1,428571 | Múltiplo redondo de 1439 |
| 71,95 MHz | 30 MHz | exactamente 2 | Dilatación legible, pero reloj irreal |
| ~~100 MHz~~ | — | — | Ilegal: el IF no cae en hercios enteros |
Se propone 99,998988 MHz, y se elige por realismo del reloj, no por legibilidad. Es el múltiplo de 1439 más cercano a los 100 MHz de hoy: se queda 10,1 ppm por debajo, así que el reloj de fabric de la ZedBoard no se mueve en la práctica y la plataforma de desarrollo sigue corriendo a la velocidad a la que va a doler el timing.
La alternativa de 71,95 MHz es más bonita sobre el papel —dilatación exactamente 2, IF de 30 MHz redondos— y se descarta a propósito: bajar el reloj de fabric a menos de la mitad del de destino cierra timing con holgura, pero por eso mismo esconde la presión de recursos y de rutado que sí va a aparecer en la ZU9. Una plataforma de desarrollo que no reproduce el problema no lo detecta. El precio de 99,998988 MHz es un número feo en el XDC y una dilatación que no se razona de cabeza; a cambio, el riesgo ya registrado de que «el 7Z020-1 no cierre a 100 MHz con 4 canales» se sigue midiendo en vez de esquivarse.
FS_HZ es el número del que se derivan los parámetros, no una promesa sobre el oscilador. Un
Zynq-7000 no sintetiza 99,998988 MHz exactos —los relojes de PL salen de divisores enteros del PLL
del PS— y no hace falta que lo haga: la comparación bit a bit contra el modelo dorado vive en el
dominio de la muestra, no en el del tiempo de pared, y nada en la ZedBoard se contrasta contra un
reloj externo. Lo que cambia con este valor es el paquete de parámetros; el cristal de la placa se
queda donde está. Los 10 ppm de diferencia se ven en los valores de pared derivados —el pulso de
200 ciclos pasa de 2,000000 µs a 2,000020 µs— y son irrelevantes en la plataforma de desarrollo.
La afirmación de P-08 de que la dilatación bajaría de 2,5 a 1,439 se sostiene con este fs, con la
corrección de que el valor exacto es 1,439015 y no 1,439.
Esto obliga a enmendar la frase de D-02 que dice que el reloj de fabric objetivo en ZedBoard es 100 MHz, y a anotar la entrada correspondiente en el registro delta.
El diff, si se aprueba¶
En config/params.toml:
[invariant]
if_norm_num = 600 # era 6
if_norm_den = 1439 # era 25
r1 = 120 # era 208
r2 = 240 # era 416
bins_mode_fine = 3680 # era 3689
bins_mode_coarse = 1840 # era 1844
[chain]
cic_rate = 30 # era 52; 30 × 4 = 120
[platform.zu9]
fs_hz = 143_900_000 # era 250_000_000
[platform.zedboard]
fs_hz = 99_998_988 # era 100_000_000; = 1439 × 69 492
fir_decim se queda en 4, así que los bordes de banda del FIR —normalizados a la frecuencia de
entrada del filtro— no se mueven. Después, make gen.
El SHA-256 del oráculo se va a mover, y es correcto que se mueva. Los coeficientes del
compensador de droop se diseñan contra la respuesta del CIC, y esa respuesta depende de cic_rate:
al pasar de R = 52 a R = 30 el droop es menor y el compensador sale distinto. make gen regenera
config/coeffs/, la huella de model/tests/test_chain.py cambia y hay que decir en el commit que
cambió por esto. Después, make figures, porque las gráficas del tutorial las dibuja el modelo.
Consecuencias¶
- Z1 puede empezar. La arquitectura de la cadena de diezmado deja de estar bloqueada, que es lo que P-01 y P-08 estaban reteniendo.
- Menos presión de recursos que con R = 208: los factores se descomponen en etapas más cómodas para una cadena CIC. En la ZU9 el reloj además baja de 250 a 143,9 MHz, que es holgura de timing real; en la ZedBoard no baja —99,998988 MHz son los 100 MHz de hoy menos 10 ppm— y eso es deliberado, por lo dicho arriba sobre el realismo del reloj.
- La tasa de línea JESD204B baja con
fs, lo que va a favor del margen; queda por confirmar que el modo elegido del convertidor admite ese rate y que SYSREF sigue siendo submúltiplo entero del LMFC. A 143,9 MHz en modo 20x con K = 32 y F = 2, SYSREF sale a 2,2484 MHz por división entera del reloj de dispositivo. - Nada del RTL cambia de estructura, si se cumple D-02: todo se deriva de
FS_HZ. Cambian parámetros, no arquitectura.
Riesgo que queda abierto, y no bloquea firmar¶
El PLL del FMC213 no está identificado. VadaTech describe un «wide-band PLL» con referencia por panel frontal, portadora o referencia de a bordo, pero no publica la pieza; su hoja de datos devuelve HTTP 403. Dato nuevo, 7-sep-2026: el sistema completo usa una referencia externa disciplinada por GPS (confirmado en sesión, ver P-03 y A9), casi con certeza la "referencia de 10 MHz de la casa" de este párrafo — pero sigue sin ficha técnica, así que el PFD de 100 kHz de abajo es todavía la mejor suposición, no un dato confirmado. Si es el LMK04828 del ejemplo de aplicación de TI, 143,9 MHz sale con el VCO a 2446,3 MHz (143,9 × 17, dentro del rango de VCO0) y N entero desde 10 MHz, pero con un PFD de 100 kHz, que sube el ruido en banda del lazo — y el presupuesto de jitter de apertura es de ~100 fs rms. Hay que pedir a VadaTech la pieza del PLL y su configuración.
Si resultara que la tarjeta no puede sintetizar 143,9 MHz, el plan B documentado en P-08 sigue en pie: 247 MHz con R₁ = 206 / R₂ = 412, que da 54 m de error acumulado, reloj en rejilla de 1 MHz y cadena única. Y en último caso la opción B de P-01, R₂ = 417 con cadena propia.
Revisión del 8-oct-2026: lo que el diff de arriba no contempla¶
El ADR se redactó el 30-ago-2026, antes de Z1–Z4. Desde entonces el repositorio ha cambiado en
cuatro puntos que el diff de arriba, aplicado tal cual, rompería o dejaría incoherentes. La
decisión de fondo (143,9 MHz, 120/240) no cambia; cambia lo que cuesta aplicarla y lo que hay que
incluir en el diff. Nada de config/params.toml se ha tocado con esta revisión.
1. El fs de la ZedBoard ya no es 100 MHz, y 99,998988 MHz ya no tiene sentido¶
El argumento de «realismo del reloj» sostenía que la ZedBoard debía quedarse cerca de 100 MHz para
seguir midiendo la presión de timing. Esa medida ya se hizo: el 19-sep-2026 el datapath completo
no cerró a 100 MHz (WNS = −1,209 ns) y fs_hz de zedboard bajó a 62,5 MHz
(config/params.toml). El valor vigente hoy es 62 500 000.
62,5 MHz no es múltiplo de 1439, así que con if_norm = 600/1439 es ilegal igual que lo era
100 MHz. El múltiplo de 1439 más cercano es 62 500 087 Hz (1439 × 43 433, +1,39 ppm). La
dilatación pasa de 4,0 hoy (250 / 62,5) a 143,9 / 62,500087 = 2,3024 contra el nuevo fs de
la ZU9. Ese número sustituye a la «dilatación 1,439» del cuerpo del ADR, que ya no es alcanzable
en esta placa: la propia razón de bajar fs de la ZedBoard fue que 100 MHz no cierra. La fila de 99,998988
MHz de la tabla de fs de ZedBoard queda sin efecto.
2. Las constantes de PRF y ancho de pulso dependen de fs y el diff no las toca¶
El diff solo cambia if_norm, r1, r2, bins_*, cic_rate y los dos fs_hz. Pero
config/params.toml calcula otras constantes contra el fs de la ZU9 de 250 MHz y las trata
como invariantes de registro. Con fs = 143,9 MHz siguen siendo números de registro, pero ya no
significan lo mismo:
| Constante | Hoy (250 MHz) | Con 143,9 MHz | Efecto si no se cambia |
|---|---|---|---|
prf_div_min |
208 333 (1200 Hz) | 119 917 | el techo de PRF baja de 1200 Hz a 690,7 Hz y el lote Doppler (~1300 Hz) deja de ser configurable |
prf_div_max |
1 250 000 (200 Hz) | 719 500 | el suelo de PRF sube de 200 Hz a 115 Hz |
prf_div_min_low (460 km) |
767 198 | 441 599 | D-09 exigiría 799 km de rango no ambiguo (PRF ≤ 187,6 Hz) y rechazaría el lote de vigilancia estándar de ~326 Hz |
prf_div_min_high (115 km) |
191 800 | 110 400 | exigiría PRF ≤ 750 Hz; hoy no vincula porque prf_div_min lo tapa, pero deja de ser coherente |
pulse_width_cycles |
200 / 415 / 825 | 115 / 239 / 475 | los pulsos durarían 1,39 / 2,89 / 5,73 µs en vez de 0,8 / 1,66 / 3,3 µs |
Medido el 8-oct-2026 en una copia aislada del repositorio (diff de arriba más los valores de esta
tabla, gen_params.py, gen_coeffs.py, gen_contract.py, gen_registers.py; sin Vivado, sin
Verilator, sin placa):
- Con el diff literal de este ADR,
gen_params.pyaborta: «vector_loop_len(4000) no es múltiplo deif_norm_den(1439)». Confirma el punto 3. - Con los valores de esta revisión (
vector_loop_len= 4317,fs_hzde ZedBoard = 62 500 087, y las cinco constantes de la tabla)gen_params.pycorre, ytools/check_hardcoded_clock.pypasa. Las cifras de la columna «Con 143,9 MHz» son las que usé a mano; el generador las acepta, pero no las recalcula, así que siguen siendo aritmética mía (ceil(r·2·fs/c)). - El firmware se deriva solo de esas constantes.
DrxTiming_DefaultConfig(fw/src/app/drx_timing_cfg.c) tomamax(PRF_DIV_MIN_LOW, PRF_DIV_MIN)y lo alinea a un múltiplo deDRX_R1. Con los valores nuevos saleprf_div_low= 441 600 (3680 bins, justo en el techoBINS_MODE_FINE, sin bin de guarda) yprf_div_high= 120 000 (1000 bins). Inferencia leída del código, no ejecutada. - Corrección a la versión anterior de este párrafo: dije que las constantes estaban «escritas a
mano en nueve ficheros». La mayoría son comentarios (
rtl/drx_ray_packer.sv,drx_timing_cfg.c,drx_control_boot.c) o cargas de prueba de ida y vuelta del contrato (fw/tests/*.py,drx_ray_frame_selftest.c), que siguen pasando con 767198 porque no están atadas aparams.toml; el riesgo ahí es que queden con valores de la era de 250 MHz, no que fallen. Lo que sí falla son 16 tests demodel/tests/(134 en total, 118 pasan; 14 tras el cambio del test de ganancia, ver abajo;fw/tests100 % verde):test_params.py(3 sobre dilatación, tamaño de celda y PRF de pared, más la comprobación de ficheros generados),test_timing.py(5, anchos de pulso 200 fuera de la tabla115/239/475),test_chain.py(3, tras el cambio del test de ganancia),test_bloques.py(1) ytest_gating_y_vectores.py(1).
Hallazgo previsto como regresión y resuelto como falso positivo:
test_la_ganancia_de_la_cadena_es_unidad_exacta_en_continua fallaba en los dos modos con
cic_rate = 30 (10 000 salía como 9999 en fino y 9997 en grueso). No era el ajuste de ganancia:
barriendo 43 amplitudes entre 500 y 32 000, el error de continua es de ±2 (fino) y ±3 (grueso) tanto
con R₁ = 208 como con R₁ = 120, sin sesgo (media −0,23 y −0,02 con los parámetros del ADR; 0,05 y
0,00 con los de hoy). Es el redondeo de los shift_round previos, amplificado por el propio ajuste
(entre ~2,6 y ~4,2). El test antiguo exigía igualdad exacta con amplitud 10 000 y pasaba por
casualidad; la propiedad «ganancia exacta» que Z1 daba por cumplida nunca se cumplió. Se
sustituyó por un barrido con cota derivada de la amplificación y comprobación de sesgo
(model/tests/test_chain.py); pasa con los parámetros de hoy y con los del ADR, y un mutante con
+3 % de ganancia lo hace fallar. No hay constante que reajustar y este punto deja de bloquear la
firma. Con eso, los fallos de model/tests/ con los parámetros del ADR bajan de 16 a 14. Quedan
test_chain y test_bloques con valores literales de la era R = 52 (growth_bits == 29, tamaños de
salida 192/96, la huella GOLDEN del oráculo, que el propio ADR ya anticipaba).
make gen no converge en una pasada. Tras cambiar cic_rate, gen_params.py --check seguía
fallando después de make gen hasta volver a correr gen_params.py, porque drx_params_pkg.sv y
compañía incluyen cantidades que salen del modelo y de los coeficientes, y gen_coeffs.py corre
después de gen_params.py en el Makefile. Inferido del orden de ejecución; no aislé qué campo
cambia. Al aplicar el ADR, correr make gen dos veces o corregir el orden.
No ejecutado: sim/tests/ (cocotb sobre Verilator), make lint-hdl*, toda la verificación de
RTL, xsim, P&R y placa. En particular, test_l0_timing_engine.py y tb_drx_timing_engine.py
mencionan 767198 en comentarios y no se ejecutaron contra los valores nuevos.
3. vector_loop_len tiene que cambiar¶
VECTOR_LOOP_LEN (hoy 4000) debe ser múltiplo de IF_NORM_DEN, y gen_params.py lo comprueba. Hoy
IF_NORM_DEN = 25 y 4000 = 160 × 25. Con 1439 el valor legal más cercano dentro de
vector_bram_depth (8192) es 4317 (3 × 1439); las alternativas son 1439, 2878, 5756 y 7195. Esto
cambia lo que suena en bucle y, con ello, el catálogo de ocho vectores de estímulo y las
referencias de espectro que ya están medidas en placa (−9,02/−9,03 dBFS): hay que remedir.
4. Qué verificación hay que repetir, y cuánto cuesta¶
Cambiar cic_rate de 52 a 30 regenera el CIC Compiler y los coeficientes del compensador FIR. Las
dos cosas invalidan evidencia ya cerrada:
- La matriz de 16 corridas de D-12 contra xsim real (8 casos × {fino, grueso}). Cada corrida son ~20–25 min y no hay caché; con tres en paralelo, 2–3 h.
NCO_LUT_OFFSET_K= 655 (tcl/gen_ip.tcl:180) se calibró contra el arranque del Phase Generator. El comentario del fichero dice que no se recalibra al cambiar la IP, pero eso se verificó con la matriz de D-12 para la frecuencia de IF de entonces; conif_norm= 600/1439 la palabra de fase es otra. Inferencia, no medida: lo esperable es que siga cancelando, pero solo la matriz lo dice.- P&R de
-top zynq, que hoy cierra con WNS ≈ 1,6 ns a 62,5 MHz. El cambio decic_ratemueve el camino crítico (FIFO del FIR Compiler →overflow_sticky_r). - Bring-up en placa: las cifras de Z4.4–Z4.6 (rayos/s, −9,03 dBFS, AFC cerrado con el DSP real)
se vuelven a medir con el nuevo
fs. El caudal en pared también cambia: la PRF de pared de la ZedBoard esfs / prf_div.
Estimación de esfuerzo si se aprueba: 1–2 días de trabajo con hardware, no «dos líneas y
make gen». La frase de substrato.md y de CLAUDE.md que dice que aplicarlo es «el diff más
gen_params.py» describe el cambio en params.toml, no el cierre de verificación, y está
desactualizada por Z1–Z4.
Firma¶
| Rol | Quién | Decisión | Fecha |
|---|---|---|---|
| Dirección técnica | |||
| Compra de hardware | |||
| Especialistas de dominio (P-01) |
Hasta que esta tabla esté rellena, config/params.toml no se toca y D-04 sigue vigente
provisionalmente.