ADCS-CUBESAT y cómo usa fiil/test_seu

Cosimulación Simulink–VHDL con inyección de SEU: qué es el sistema de control de actitud, cómo se simula su controlador en GHDL junto a una planta en Simulink y qué piezas del repositorio fiil/test_seu hacen posible el experimento.

1. Resumen

ADCS-CUBESAT es el control de actitud de un CubeSat 1U con tres ruedas de reacción (basado en Carmona-Bellido et al., Pädi 14, 2026). El controlador completo (PID por eje + desacoplo por jacobiano) está escrito en VHDL; la dinámica del satélite sigue en Simulink.

Para estudiar el efecto de los SEU (bit-flips por radiación) en el controlador, fiil simula el VHDL con GHDL + cocotb, inyecta los fallos y, mediante TCP, intercambia en lazo cerrado las muestras con Simulink. Al final, el mismo cliente MATLAB simula el modelo de referencia (controlador en Simulink) y compara ambos.

Servidor
GHDL + cocotb + planta Python sistema_adcs_tcp.py. Simula el VHDL y inyecta SEU.
Cliente
MATLAB/Simulink: cliente_tcp.m + fiil_tcp.m. Simula la planta del satélite.
Salida
CSV, mensajes JSON, señales .mat y figuras VHDL vs Simulink.

2. Los dos repositorios

RepositorioPapelContiene
fiil/test_seuEntorno genérico de inyección de fallosrun_sim.py, testbenches (tb_seu.py, tb_seu_eventos.py), utilidades (netlist, biestables, comparar_tcp.py, app_grafica), cliente MATLAB (cliente_tcp.m, fiil_tcp.m), plantillas.
fiil-examples/ADCS-CUBESATUn modelo concretoEl VHDL del controlador, el .yml de campañas, la planta TCP, los modelos Simulink y un Makefile. Nada genérico se duplica.
VHDL/
├── fiil/test_seu/            genérico: run_sim.py, tb/, utils/, matlab/
└── fiil-examples/
    └── ADCS-CUBESAT/
        ├── rtl/      controlador_adcs.vhd + config_sim_controlador_adcs.yml
        ├── sim/      sistema_adcs_tcp.py + config_planta_adcs_tcp.yml + results/
        ├── matlab/   adcs_simulink.slx, adcs_simulink_vhdl.slx, adcs_script.m
        ├── doc/      artículo y revisión del modelo
        └── Makefile  TOP=controlador_adcs  PLANTA=sim/sistema_adcs_tcp.py

Si fiil no está clonado junto al ejemplo, el Makefile lo clona (make fiil).

3. Qué hace el ADCS

Origen: el artículo en el que se basa

El sistema y el controlador parten del trabajo expuesto en el artículo «Sistema de control de actitud para un satélite tipo CubeSat» (Attitude control system for a CubeSat-like satellite), de M. T. Carmona-Bellido, O. Gutiérrez-Frias, A. Luviano-Juárez y N. Lozada-Castillo (Instituto Politécnico Nacional, UPIITA, México), publicado en Pädi, Boletín Científico de Ciencias Básicas e Ingenierías del ICBI, Vol. 14, No. Especial (2026), pp. 248–258.

  • Modelo Simulink: a partir del modelo del artículo se hizo una revisión (qué se cambió respecto al artículo y por qué, resultados y erratas), documentada en ADCS-CUBESAT/doc/. El resultado es el modelo de referencia adcs_simulink.slx.
  • Controlador: el PID desacoplado y el desacoplo por jacobiano del modelo (Subsystem2 y MATLAB Function4) se han llevado a VHDL. El PID implementa la ecuación 30 del artículo con su errata corregida.
  • Planta: la dinámica del CubeSat 1U con tres ruedas de reacción sigue en Simulink, como en el artículo.

Por cada muestra (cada TS = 10 ms) el controlador calcula el par de control que se aplica a las ruedas:

tau = −Jd .* (M(φ, θ) · a_des) = neg_alpha .* (M · a_des)

M = [1,  0,      −sin θ;
     0,  cos φ,   cos θ·sin φ;
     0, −sin φ,   cos θ·cos φ]

a_des es la aceleración angular de Euler que pide el PID de cada eje.

Arquitectura del controlador VHDL (controlador_adcs.vhd)

3 × controlador_pid

Un PID por eje → a_des. Registros up, ui, ud, error_ant. Coma fija Q0.23.
+

2 × seno_coseno

sen/cos de φ y θ sin tablas ni CORDIC: reducción a octante + polinomios minimax (Horner, 2 multiplicadores). Latencia fija de 6 ciclos.
→

acoplamiento_adcs

FSM con 9 multiplicadores: tau = neg_alpha · (M·a_des), saturado a [−1, 1).

Máquina de estados del top

EstadoQué hace
REPOSOEspera ena = 1 (un pulso por muestra) y registra las entradas.
PIDLos PID actualizan sus registros; u_des queda válido.
COS_SENEspera a que los dos seno_coseno den tvalid.
ACOPLAMIENTOEspera a tvalid del acoplador y guarda tau saturado.
SALIDAtvalid = 1 un ciclo y vuelta a REPOSO.

Los resultados u_x/y/z se actualizan unos 13 ciclos de reloj después de la muestra y se mantienen hasta la siguiente.

PuertoDir.Contenido
clk, rstinReloj y reset asíncrono.
enainPulso de disparo por muestra (puerto_disparo de la planta).
ref2_x/y/z, error_x/y/zinSegunda derivada de la referencia y error phi_0 − ref_0, Q0.23.
kp, kd, ki, neg_alpha_x/y/zinGanancias y constantes (en entradas_iniciales).
phi, thetainÁngulos divididos por π (ángulo binario).
tvalid, u_x/y/zoutValidez y par tau en Q0.23.

4. Cómo funciona una campaña

Servidor

make sim → run_sim.py → tb_seu_eventos.py → sistema_adcs_tcp.py
⇄
TCP / JSON

Cliente

cliente_tcp(...yml) → adcs_simulink_vhdl.slx (sincronizado) → adcs_simulink.slx (referencia) → comparar_modelos
  1. make sim arranca el servidor, que espera al cliente hasta timeout_conexion_s (300 s) en el host:puerto del .yml de la planta.
  2. El cliente (cliente_tcp o make cliente-matlab) simula adcs_simulink_vhdl.slx sin ritmo de tiempo real: cada TS envía los sensores, Simulink se detiene, espera la respuesta y aplica el par ts_act_s después.
  3. Guarda los mensajes (en_cli_/re_cli_) y los To Workspace (senales_cosim_<sim>.mat) en sim/results/.
  4. Como el .yml tiene modelo_referencia, después simula adcs_simulink.slx y dibuja phi_0, tau y ECM: arriba Simulink, abajo VHDL.
  5. Opcional: make comparar-tcp compara los mensajes del servidor y del cliente.

Cada campaña necesita lanzar el cliente otra vez.

5. Qué piezas de test_seu usa el ADCS

Pieza de fiil/test_seuPara qué la usa el ADCS
utils (Yosys + plugin ghdl)Sintetiza controlador_adcs con -gdata_width=24, genera el netlist y la lista de biestables donde se pueden inyectar SEU (make netlist, make biestables).
run_sim.pyLanzador: lee config_sim_controlador_adcs.yml y ejecuta cada campaña sim_X con GHDL + cocotb.
tb/tb_seu_eventos.pyTestbench por sucesos: avanza el tiempo solo cuando hay una muestra, simula los ciclos necesarios hasta que el circuito se estabiliza (fin_de_calculo) e inyecta el SEU.
Contrato de planta (sim/sistema_*.py)sistema_adcs_tcp.py implementa la “planta por ventanas”: modo_actuador, ts_act_ns, respuesta() y abortar(). Hace de servidor TCP en lugar de integrar la dinámica en Python.
matlab/cliente_tcp.m, fiil_tcp.mCliente: lee el .yml, abre adcs_simulink_vhdl.slx, y el bloque MATLAB Function “TCP” llama a fiil_tcp('paso', …) con un ZOH(TS_act).
comparar_modelosCompara el VHDL en cosimulación con el modelo de referencia en Simulink.
utils/comparar_tcp.py, app_graficaCompara mensajes del servidor y del cliente; visor de los CSV (make graficas).
El Makefile del ADCS solo define TOP=controlador_adcs y PLANTA=sim/sistema_adcs_tcp.py; tb/ y utils/ están vacíos porque se reutiliza todo de fiil.

6. Protocolo TCP y flujo del código

Se envía un JSON por línea. Mensajes: start (servidor→cliente), muestra (cliente→servidor), respuesta (servidor→cliente), fin y fin con error. Las magnitudes se escalan con un exponente en base 2 para pasarlas a coma fija. El cliente usa readline bloqueante con timeout_respuesta_s.

Ventanas: N = TS / ts_act_s; en el ADCS, TS = 10 ms y N = 20, es decir ts_act_s = 0,5 ms. La ventana 1 es la salida del cálculo; la ventana j se aplica en t_muestra + j·ts_act_s.

Flujo: inicio
Cliente y servidor: arranque, conexión y mensaje de inicio.
Flujo: bucle
Bucle por muestra: el cliente envía, el servidor simula con GHDL y responde.
Flujo: fin y errores
Cierre normal y gestión de errores (cálculo colgado, abortos).

7. Modo muestreado y modo continuo

Muestreado

Actuador con memoria propia: cada TS, con un retraso de TS_act respecto a la lectura de los sensores, guarda en su memoria la salida calculada por el VHDL. El SEU cae durante el cálculo (con probabilidad_ciclo): el tiempo en que el sistema digital de la FPGA procesa la nueva entrada y sus biestables aún cambian de valor entre ciclos. Una salida por muestra; el efecto se clasifica como “posible”.

Continuo

Actuador sin memoria propia: necesita leer continuamente la salida del VHDL para aplicarla; se modela como una lectura cada TS_act, con N = TS / TS_act lecturas por TS. El SEU cae en reposo: el tiempo posterior al cálculo, con el sistema de la FPGA estabilizado (ningún biestable cambia de valor entre ciclos y la salida permanece fija). Por eso j ∈ [2, N]: en la ventana 1 la FPGA no está en reposo, está calculando por el cambio en la entrada. El bit-flip se aplica sobre la salida en reposo; los efectos son enmascarado, latente o visible.

Modo muestreado
Modo muestreado: cada TS el cliente envía la muestra, el servidor simula con GHDL hasta estabilizarse y responde; Simulink actualiza tras TS_act.
Modo continuo
Modo continuo: además del cálculo, el SEU en una ventana de reposo modifica la salida que se aplica.

8. SEU, estabilización y cálculos colgados

Los cuatro modos de fin_de_calculo

Indican cómo decide el testbench que el circuito ya se ha estabilizado y se puede leer su salida.

ModoCómo detecta que ha terminadoCuándo usarlo
combinacionalPara tras un ciclo por cada entrada de la secuencia de la muestra (normalmente uno). Sin reloj, un “ciclo” es escribir las entradas y esperar periodo_clk_ns.Circuitos sin biestables. No admite SEU.
biestablesLas salidas y todos los biestables no cambian entre dos ciclos seguidos sin entradas nuevas ni SEU. Con entradas constantes, un circuito síncrono que ya no cambia no volverá a hacerlo. Un ciclo con SEU no cuenta.Circuitos síncronos sin máquina de estados.
senal_validaUna señal (senal, p. ej. tvalid) valía valor en el ciclo anterior y en el actual vale otra cosa. Opcionalmente (estado / valor_estado) se exige además que la FSM esté en el estado esperado, para que un SEU que baje la señal antes de tiempo no pare la simulación con el cálculo a medias.Diseños con FSM y salida de validez, como el controlador del ADCS (tvalid).
maquina_estadosEl registro de estado valía ultimo en el ciclo anterior y vale primero en el actual: la FSM ha completado una vuelta.Diseños con FSM sin señal de validez.
En senal_valida y maquina_estados las salidas enviadas a la planta son las del primer ciclo en que se cumple el criterio. Si un SEU provoca una segunda pasada, esa segunda salida no se envía.

Bit-flip en reposo: tras inyectarlo se da un ciclo. Si ha cambiado el estado vigilado, el SEU ha lanzado un cálculo y se sigue con el criterio de fin_de_calculo; si no, se sigue con el criterio biestables, de modo que un fallo en un registro de datos no se da por inofensivo tras un solo ciclo.

9. Configuración: los ficheros .yml y el generador de configuración

Una simulación no se programa: se describe en dos ficheros .yml. Cada uno contiene un bloque común y una entrada por campaña (sim_1, sim_2…), de modo que con un mismo circuito se definen tantas configuraciones como se quiera y make sim las lanza una detrás de otra.

rtl/config_sim_controlador_adcs.yml

Lo lee run_sim.py. Describe el circuito y el experimento.

  • Común: top_level, senal_reloj, senal_reset, testbench: tb_seu_eventos, listas signed/unsigned (también las usa app_grafica) y fin_de_calculo.
  • Por campaña: tiempos (tiempo_reset_ns, periodo_clk_ns, tiempo_simulacion_ns, periodo_muestra_ns = TS, tiempo_inicio_radiacion_ns), modelo_fallo, la clave seu, selección de biestables, generics, entradas_iniciales (ganancias con su escalado) y modelo_planta.script.

sim/config_planta_adcs_tcp.yml

Lo leen el servidor y el cliente MATLAB. Describe la conexión con Simulink.

  • Común: host, puerto, timeouts, sensores y actuadores (en el mismo orden que el Mux y el Demux de Simulink), puerto_disparo: ena, escalado (2−23), modo_actuador, ts_act_s = 0,0005, modelo_simulink, script_variables y modelo_referencia.
  • Por campaña: solo lo que cambia (p. ej. modo_actuador: continuo). Se empareja con el otro fichero por el nombre sim_X.

Elegir cómo y dónde caen los SEU

La “aplicación de configuración”: generar_config (make config)

Crea el .yml de campañas en blanco leyendo solo el texto de los .vhd (sin GHDL ni Yosys). Escribe una campaña sim_1 con todas las claves posibles: las obligatorias salen vacías y marcadas con # OBLIGATORIO, y las opcionales comentadas, listas para descomentar. En comentarios VHDL: añade lo que encuentra: puertos con tipo y ancho, generics con su valor por defecto, señales internas con la ruta de la instancia y una propuesta de reloj y reset.

make config                      # rtl/config_sim_controlador_adcs.yml en blanco
make config NOMBRE=config_sim_otro FORZAR=1
# equivale a:
python3 <fiil>/test_seu/utils/generar_config --top controlador_adcs \
        --planta sim/sistema_adcs_tcp.py rtl/*.vhd

10. Representar resultados con app_grafica

app_grafica es un visor Qt (PyQt5 o PySide6 + matplotlib + pyyaml) de los CSV que deja la simulación en sim/results/. A diferencia de dibujar un CSV suelto, combina varios CSV en la misma ventana, dibuja las señales binarias bit a bit y trabaja sobre una ventana de tiempo.

make graficas                       # lee sim/results
make graficas RUTA=otra/carpeta     # lee los CSV de otra carpeta
# equivale a: python3 <fiil>/test_seu/utils/app_grafica [carpeta]

Pasos

  1. Agregar gráfica abre la ventana de selección con la lista de CSV de la carpeta (y la casilla Representar todo).
  2. Por cada CSV marcado aparecen sus señales (sin la columna de tiempo). Se marcan las deseadas y, si hace falta, se cambia su tipo: binario, signed, unsigned o real. Por defecto se deduce del .yml (listas signed/unsigned) y de los valores del CSV.
  3. Representar dibuja una gráfica por señal, con el tiempo en ns en el eje x. Las binarias muestran una fila por bit (el más significativo arriba).
  4. Desde (ns) y Hasta (ns) fijan la ventana de tiempo (Intro o Aplicar). Al principio se ven las 200 primeras muestras.

Herramientas

Puntos de información
Clic izquierdo en la gráfica: etiqueta con el tiempo y el valor (en binarias, el bit pulsado y el valor completo). Se arrastran con el botón izquierdo; clic derecho sobre la etiqueta la borra, y clic derecho en la gráfica borra todas.
Escalado 2e
En señales signed/unsigned se dibuja entero × 2e. El valor inicial sale del escalado de la planta o de entradas_iniciales, así que u_x con e = −23 se ve en N·m.
CSV de fallos (*_seu.csv)
No se dibujan: se muestran como texto con el número de fallos, los aplicados y el efecto de cada uno (posible, enmascarado, latente, visible) y si algún cálculo se colgó. Sirve para localizar el tiempo_ns en las gráficas.
Capturar gráficas
Guarda un PNG por gráfica, tal como se ve (ventana de tiempo y puntos incluidos), en sim/results/capturas/ con nombre <csv>__<señal>__<desde>-<hasta>ns.png. Nunca sobrescribe.

Qué mirar en el ADCS: las señales de puertos u_x/y/z (el par tau) y tvalid, la señal binaria colgado (0/1) para ver cuándo se abortó una campaña, y el CSV de fallos para relacionar cada SEU con su efecto. La comparación completa Simulink vs VHDL (phi_0, tau, ECM) la dibuja MATLAB con comparar_modelos, no esta aplicación.

11. El Makefile del ADCS

Se ejecuta desde ADCS-CUBESAT/ (run_sim.py escribe en el sim/results/ del directorio de trabajo) y necesita un shell POSIX (Linux, WSL o Git Bash). Contiene solo lo propio del modelo y apunta al resto de fiil. make sin argumentos muestra la ayuda.

Variables principales

VariableValor por defectoPara qué sirve
TOPcontrolador_adcsEntidad top_level.
VHDL5 ficheros de rtl/Orden de compilación: paquete, entidades hijas y top al final.
GENERICS-gdata_width=24Generics del netlist; deben coincidir con los del .yml.
CONFIG / NOMBRErtl/config_sim_$(TOP).ymlFichero de campañas.
PLANTAsim/sistema_adcs_tcp.pyServidor TCP que hace de planta.
YML_PLANTAsim/config_planta_adcs_tcp.ymlLo que lee el cliente MATLAB.
FIIL_DIR, FIIL_RAMA, TAGfiil junto a fiil-examples, mainDónde está fiil, su rama o versión.
MATLAB, MATLAB_MIN, TOOLBOX_MATLAB, PYTHON_MATLAB, CON_MATLABmatlab, 24.1, SimulinkCliente MATLAB y su comprobación.
PYTHON, YOSYS, GITpython3, yosys, gitHerramientas.

Todas se pueden sobrescribir: make sim CONFIG=rtl/config_sim_otro.yml, make sim FIIL_DIR=/ruta/a/fiil.

Objetivos, en el orden del flujo

ObjetivoQué hace
make fiil / fiil-actualizar / fiil-checkoutClona fiil junto a este repo (primero por SSH y, si falla, por HTTPS), hace git pull o cambia de rama (FIIL_RAMA=) o versión (TAG=).
make comprobarComprueba GHDL, Yosys con plugin ghdl, cocotb y pyyaml; con CON_MATLAB=1, también MATLAB (versión y toolboxes).
make netlistYosys sintetiza los 5 .vhd y escribe rtl/netlist.json (celdas genéricas, sin flatten para conservar la jerarquía).
make biestablesbit_selector.py lee el netlist y genera sim/results/lista_biestables_controlador_adcs.json.
make plantaComprueba la configuración del servidor TCP sin GHDL ni cliente.
make simRegenera netlist y biestables si faltan y lanza todas las campañas con run_sim.py. Guarda la salida en sim/results/log_servidor_python.txt. Cada campaña espera al cliente.
make cliente-matlabmatlab -batch con cliente_tcp(...) para una campaña; log en log_cliente_matlab.txt.
make comparar-tcpCompara los mensajes TCP del servidor y del cliente (en_*, re_*).
make graficasAbre app_grafica (RUTA= para otra carpeta).
make configGenera el .yml en blanco (NOMBRE=, FORZAR=1).
make clean / clean-resultados / clean-todoBorra temporales / además CSV y capturas / deja solo código y configuración (también netlist y lista de biestables).
make zip-resultsEmpaqueta sim/results en un .zip con fecha en la carpeta padre del repo.

Dependencias

VHDL ──► netlist (rtl/netlist.json) ──► biestables (lista .json) ──► make sim
make sim ◄── TCP ──► make cliente-matlab         (una vez por sim_X)
sim/results/*.csv ──► make graficas · make comparar-tcp · make zip-results

Make solo rehace el netlist si cambia algún .vhd, y la lista de biestables si cambia el netlist. Si cambias un ancho (data_width), actualiza a la vez GENERICS y los generics del .yml.

12. Cómo lanzarlo

# Terminal 1: servidor, todas las campañas
make sim
# Terminal 2: cliente de UNA campaña
make cliente-matlab
# Opcional
make comparar-tcp
make graficas RUTA=sim/results
make comprobar CON_MATLAB=1
El servidor hace un único accept() por campaña: no sondees el puerto con nc ni /dev/tcp. El cliente reintenta conectar 300 s. El host del .yml es la IP de la VM si MATLAB está en otra máquina.