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.
GHDL + cocotb + planta Python
sistema_adcs_tcp.py. Simula el VHDL y inyecta SEU.MATLAB/Simulink:
cliente_tcp.m + fiil_tcp.m. Simula la planta del satélite.CSV, mensajes JSON, señales
.mat y figuras VHDL vs Simulink.2. Los dos repositorios
| Repositorio | Papel | Contiene |
|---|---|---|
fiil/test_seu | Entorno genérico de inyección de fallos | run_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-CUBESAT | Un modelo concreto | El 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 referenciaadcs_simulink.slx. - Controlador: el PID desacoplado y el desacoplo por jacobiano del modelo (
Subsystem2yMATLAB 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
| Estado | Qué hace |
|---|---|
REPOSO | Espera ena = 1 (un pulso por muestra) y registra las entradas. |
PID | Los PID actualizan sus registros; u_des queda válido. |
COS_SEN | Espera a que los dos seno_coseno den tvalid. |
ACOPLAMIENTO | Espera a tvalid del acoplador y guarda tau saturado. |
SALIDA | tvalid = 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.
| Puerto | Dir. | Contenido |
|---|---|---|
clk, rst | in | Reloj y reset asíncrono. |
ena | in | Pulso de disparo por muestra (puerto_disparo de la planta). |
ref2_x/y/z, error_x/y/z | in | Segunda derivada de la referencia y error phi_0 − ref_0, Q0.23. |
kp, kd, ki, neg_alpha_x/y/z | in | Ganancias y constantes (en entradas_iniciales). |
phi, theta | in | Ángulos divididos por π (ángulo binario). |
tvalid, u_x/y/z | out | Validez 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.pyTCP / JSON
Cliente
cliente_tcp(...yml) → adcs_simulink_vhdl.slx (sincronizado) → adcs_simulink.slx (referencia) → comparar_modelosmake simarranca el servidor, que espera al cliente hastatimeout_conexion_s(300 s) en elhost:puertodel.ymlde la planta.- El cliente (
cliente_tcpomake cliente-matlab) simulaadcs_simulink_vhdl.slxsin ritmo de tiempo real: cada TS envía los sensores, Simulink se detiene, espera la respuesta y aplica el parts_act_sdespués. - Guarda los mensajes (
en_cli_/re_cli_) y los To Workspace (senales_cosim_<sim>.mat) ensim/results/. - Como el
.ymltienemodelo_referencia, después simulaadcs_simulink.slxy dibujaphi_0,tauyECM: arriba Simulink, abajo VHDL. - Opcional:
make comparar-tcpcompara 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_seu | Para 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.py | Lanzador: lee config_sim_controlador_adcs.yml y ejecuta cada campaña sim_X con GHDL + cocotb. |
tb/tb_seu_eventos.py | Testbench 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.m | Cliente: 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_modelos | Compara el VHDL en cosimulación con el modelo de referencia en Simulink. |
utils/comparar_tcp.py, app_grafica | Compara mensajes del servidor y del cliente; visor de los CSV (make graficas). |
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.



7. Modo muestreado y modo continuo
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”.
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.


8. SEU, estabilización y cálculos colgados
- Dónde inyecta: en los biestables de la lista generada desde el netlist de Yosys (transitorio o permanente).
- Cuándo termina el cálculo: el testbench da flancos de reloj tras cada muestra o SEU hasta que considera que el circuito se ha estabilizado; entonces salta sin simular hasta el siguiente suceso. El criterio se elige con la clave
fin_de_calculo(cuatro modos, en la tabla siguiente). - Cálculo colgado: un SEU puede causar que el circuito no se estabilice. Se detecta cuando el cálculo no termina en
max_ciclos_estableciclos o antes del siguiente suceso, en cualquiera de los cuatro modos. Entonces se lanzaCalculoColgado, se llama aabortar(), el servidor envíafincon error y la referencia se simula solo hasta el último instante válido. La campaña figura comoABORTADA, un resultado válido con sus CSV guardados. - Tiempo: el testbench distingue tiempo virtual y tiempo de GHDL.
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.
| Modo | Cómo detecta que ha terminado | Cuándo usarlo |
|---|---|---|
combinacional | Para 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. |
biestables | Las 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_valida | Una 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_estados | El 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. |
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, listassigned/unsigned(también las usaapp_grafica) yfin_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 claveseu, selección de biestables,generics,entradas_iniciales(ganancias con su escalado) ymodelo_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,sensoresyactuadores(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_variablesymodelo_referencia. - Por campaña: solo lo que cambia (p. ej.
modo_actuador: continuo). Se empareja con el otro fichero por el nombresim_X.
Elegir cómo y dónde caen los SEU
- Clave
seu:aparicion(siempre|nunca|aleatoria|periodica),probabilidad_muestra,patron(lista de 0/1 que se repite) y, solo en muestreado,probabilidad_ciclo. Como mucho hay un bit-flip por muestra; sin la clave no se inyecta ninguno. - Qué biestables pueden fallar:
biestables_incluidos/excluidosycomponentes_incluidos/excluidos(por entidad o por instancia:controlador_pid,seno_coseno,calc_sen_cos_phi,acoplador…). Gana la regla más específica: biestable > instancia > entidad. El controlador completo tiene 1408 bits. - Otros:
modelo_fallo(transitorioopermanente),max_ciclos_establey unfin_de_calculopropio por campaña. - Comprobaciones: si falta una clave obligatoria o un valor no es válido,
run_sim.pyse detiene al arrancar, antes de simular, y dice qué clave está mal. Una errata en una clave opcional se avisa con la clave más parecida.
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
- Con
--plantatambién generasim/config_planta_<top>.ymlcon los parámetros de la planta y deja el.ymldel rtl contestbench: tb_seu_eventos, porque la planta definerespuesta(). - Si los ficheros ya existen no los toca; solo los sobrescribe con
FORZAR=1.
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
- Agregar gráfica abre la ventana de selección con la lista de CSV de la carpeta (y la casilla Representar todo).
- 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(listassigned/unsigned) y de los valores del CSV. - 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).
- Desde (ns) y Hasta (ns) fijan la ventana de tiempo (Intro o Aplicar). Al principio se ven las 200 primeras muestras.
Herramientas
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.
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.*_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.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
| Variable | Valor por defecto | Para qué sirve |
|---|---|---|
TOP | controlador_adcs | Entidad top_level. |
VHDL | 5 ficheros de rtl/ | Orden de compilación: paquete, entidades hijas y top al final. |
GENERICS | -gdata_width=24 | Generics del netlist; deben coincidir con los del .yml. |
CONFIG / NOMBRE | rtl/config_sim_$(TOP).yml | Fichero de campañas. |
PLANTA | sim/sistema_adcs_tcp.py | Servidor TCP que hace de planta. |
YML_PLANTA | sim/config_planta_adcs_tcp.yml | Lo que lee el cliente MATLAB. |
FIIL_DIR, FIIL_RAMA, TAG | fiil junto a fiil-examples, main | Dónde está fiil, su rama o versión. |
MATLAB, MATLAB_MIN, TOOLBOX_MATLAB, PYTHON_MATLAB, CON_MATLAB | matlab, 24.1, Simulink | Cliente MATLAB y su comprobación. |
PYTHON, YOSYS, GIT | python3, yosys, git | Herramientas. |
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
| Objetivo | Qué hace |
|---|---|
make fiil / fiil-actualizar / fiil-checkout | Clona 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 comprobar | Comprueba GHDL, Yosys con plugin ghdl, cocotb y pyyaml; con CON_MATLAB=1, también MATLAB (versión y toolboxes). |
make netlist | Yosys sintetiza los 5 .vhd y escribe rtl/netlist.json (celdas genéricas, sin flatten para conservar la jerarquía). |
make biestables | bit_selector.py lee el netlist y genera sim/results/lista_biestables_controlador_adcs.json. |
make planta | Comprueba la configuración del servidor TCP sin GHDL ni cliente. |
make sim | Regenera 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-matlab | matlab -batch con cliente_tcp(...) para una campaña; log en log_cliente_matlab.txt. |
make comparar-tcp | Compara los mensajes TCP del servidor y del cliente (en_*, re_*). |
make graficas | Abre app_grafica (RUTA= para otra carpeta). |
make config | Genera el .yml en blanco (NOMBRE=, FORZAR=1). |
make clean / clean-resultados / clean-todo | Borra temporales / además CSV y capturas / deja solo código y configuración (también netlist y lista de biestables). |
make zip-results | Empaqueta 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
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.