Módulo TimeMachine

Proyecto: Kmila-9s

Este módulo es un componente central de Kmila-9s, un proyecto titulado oficialmente "Aplicación Multiplataforma para el Aprendizaje y Depuración de Código VHDL sin Requerimiento de Hardware Físico" (Multiplatform Application for Learning and Debugging VHDL Code without Requiring Physical Hardware).

Autor: Ulrich Tamayo Daniel Correo: [email protected] Correo 2: [email protected] Versión: 1.14.1 Última actualización: 3 de agosto de 2026


1. Descripción general del proyecto

El objetivo principal de este proyecto es desarrollar una aplicación de escritorio libre, de código abierto y multiplataforma para la depuración interactiva de código VHDL. La aplicación busca facilitar el aprendizaje del diseño digital al proporcionar un entorno unificado para la simulación y la visualización en tiempo real de señales digitales, ciclos de reloj y estados de puertos, eliminando por completo la necesidad de hardware físico como las FPGA.

Motivaciones clave

  • Accesibilidad: Superar la barrera económica de las costosas tarjetas FPGA y las licencias de software propietario.
  • Mejora pedagógica: Ofrecer una interfaz gráfica intuitiva e integrada que simplifica el proceso de depuración, a diferencia de los flujos de trabajo fragmentados de línea de comandos de herramientas como GHDL y GTKWave.
  • Flexibilidad: Proporcionar una aplicación autónoma que se ejecuta localmente en múltiples sistemas operativos (Windows, macOS, Linux, Android, iOS) sin requerir conexión a internet.

Este proyecto está construido con .NET y MAUI, lo que garantiza una base de código moderna y mantenible con amplio soporte de plataformas.

2. El módulo TimeMachine

Este módulo en particular, el TimeMachine, funciona como el corazón del motor de simulación. Su responsabilidad principal es proporcionar una fuente de tiempo discreta y controlable que impulsa toda la simulación de VHDL. Simula el paso del tiempo en "ticks" discretos, lo que permite al motor de simulación ejecutar procesos de VHDL y actualizar los valores de las señales en una secuencia determinista, imitando el reloj de un circuito digital real.

El módulo está diseñado para ser altamente flexible, ofreciendo un control preciso sobre la ejecución de la simulación (iniciar, pausar, detener) y proporcionando retroalimentación a través de eventos.

Responsabilidades principales

  • Simulación de tiempo discreto: Genera un flujo continuo de ticks de tiempo que representan la unidad de tiempo más pequeña de la simulación.
  • Control del ciclo de vida de la simulación: Proporciona una API sencilla para Start, Pause, Continue y Stop del flujo de tiempo, habilitando la depuración interactiva.
  • Comunicación basada en eventos: Notifica al motor de simulación principal y a la interfaz de usuario sobre eventos importantes relacionados con el tiempo, como las actualizaciones de progreso (OnProgressAdvance) y la finalización de la simulación (OnSimulationEnded).
  • Desacoplamiento: Separa la lógica de la gestión del tiempo de la lógica de la simulación de VHDL, promoviendo una arquitectura más limpia y modular.

3. Arquitectura del módulo TimeMachine

El módulo está construido en torno a una clase central Timer que gestiona el estado de la simulación y el bucle de temporización. Está diseñado para ejecutarse en un hilo en segundo plano con el fin de garantizar que la interfaz de usuario de la aplicación permanezca receptiva.

Componentes clave

  • Timer.cs: La clase central del módulo. Orquesta la simulación de tiempo, ejecutando un bucle en un Task en segundo plano. Gestiona el estado de la simulación (por ejemplo, IsRunning, IsPaused) y expone métodos (Start, Pause, etc.) para controlar su ciclo de vida. Utiliza un CancellationTokenSource para pausar y detener de manera eficiente.

  • TimeClock.cs: El reloj de simulación global que proporciona el "tick" fundamental que impulsa todos los eventos basados en el tiempo. Lleva el conteo del tick actual, maneja el desbordamiento de ticks para simulaciones largas y define la constante de resolución de tiempo (NS_PER_TICK = 10 nanosegundos por tick). Dispara los eventos estáticos OnTickChanged y ResetCounter.

  • Clock.cs: Representa una única señal de reloj dentro de la simulación. Se suscribe a los eventos TimeClock.OnTickChanged y alterna su estado en función de su frecuencia. Admite cualquier frecuencia especificada en MHz.

  • Input.cs: Una clase de marcador de posición para la futura implementación del manejo de entradas de la simulación. Actualmente solo contiene el nombre de la entrada.

Modelos de datos principales

  • Eventos (Action<T>): La clase Timer utiliza varios delegados Action como eventos para comunicarse con otras partes de la aplicación:
    • OnPaused (estático): Indica que la simulación se ha pausado o reanudado.
    • OnSimulationEnded: Señala que una simulación de duración fija ha finalizado.
    • OnProgressAdvance: Informa el progreso de la simulación como un porcentaje.
    • OnSimulationTick (estático): Se dispara en cada tick de la simulación para el procesamiento de ciclos delta.

4. Cómo usar este módulo

Para usar el TimeMachine, se instancia la clase Timer y se suscribe a sus eventos. El motor de simulación o la interfaz de usuario pueden luego llamar a sus métodos de control en respuesta a las acciones del usuario.

using TimeMachine.Repositories;
using System;
using System.Threading.Tasks;

public class SimulationHost
{
    private readonly Timer _timeMachine;

    public SimulationHost()
    {
        // 1. Instantiate the TimeMachine.
        _timeMachine = new Timer();

        // 2. Subscribe to its events to receive updates.
        _timeMachine.OnProgressAdvance += (percent) => Console.WriteLine($"Progress: {percent}%");
        _timeMachine.OnSimulationEnded += (finished) => Console.WriteLine("Simulation Finished.");
        // Note: OnPaused and OnSimulationTick are static events on the Timer class
        Timer.OnPaused += (isPaused) => Console.WriteLine(isPaused ? "Simulation Paused." : "Simulation Resumed.");
    }

    public void RunSimulation()
    {
        // 3. Start the simulation for a defined duration (e.g., 1 microsecond).
        Console.WriteLine("Starting simulation...");
        _timeMachine.Start(TimeSpan.FromMicroseconds(1));

        // The simulation now runs on a background thread.
    }

    public void Pause() => _timeMachine.Pause();
    public void Continue() => _timeMachine.Continue();
    public void Stop() => _timeMachine.Stop();
}

5. Dependencias

Este módulo es autónomo y no tiene dependencias de bibliotecas externas más allá del entorno de ejecución estándar de .NET 9.

6. Flujo del TimeMachine y relaciones entre clases

Para comprender mejor el funcionamiento interno del módulo, los siguientes diagramas ilustran el flujo de datos y las relaciones entre clases.

6.1. Flujo de simulación de alto nivel

Este diagrama muestra cómo el TimeMachine interactúa con el motor de simulación en su conjunto.

graph TD
    A["Simulation Engine / UI"] -->|Instantiates & Controls| B(TimeMachine);
    B -->|Runs on a background thread| C{Time Simulation Loop};
    C -->|Reads| D[TimeClock Constants];
    C -->|Fires Events| A;
    
    subgraph "TimeMachine Module"
        B; C; D;
    end

    style B fill:#ccf,stroke:#333,stroke-width:2px
    style A fill:#bbf,stroke:#333,stroke-width:2px

6.2. Diagrama de clases detallado

Este diagrama detalla las clases clave dentro del módulo TimeMachine.

classDiagram
    direction LR

    class Timer {
        -TimeClock _clock
        -CancellationTokenSource _cts
        -CancellationTokenSource _pauseCts
        +TimeElapsed : TimeSpan
        +CurrentTick : double
        +Paused : volatile bool
        +OnPaused : Action~bool~$
        +OnSimulationEnded : Action~bool~
        +OnProgressAdvance : Action~double~
        +OnSimulationTick : Action~ulong~$
        +Start(TimeSpan? duration)
        +Pause()
        +Continue()
        +Stop()
        +Restart(TimeSpan? duration)
        +Dispose()
        +GetSimulationTicks(TimeSpan, int)$ ulong
        +GetTimeFromTicks(ulong, int)$ TimeSpan
    }

    class TimeClock {
        +Tick : ulong
        +Multiplier : ulong
        +Ns : int
        +NS_PER_TICK : int$
        +OnTickChanged : Action~ulong~$
        +ResetCounter : EventHandler~bool~$
        +Next()
        +Reset()
        +Dispose()
    }

    class Clock {
        +Name : string
        +State : bool
        +Period : ulong
        +OnStatusChanged : EventHandler~bool~
        +Clock(bool, double, string)
        +Dispose()
    }

    class Input {
        +Name : string
    }

    Timer o-- TimeClock : uses internally
    Clock ..> TimeClock : subscribes to OnTickChanged
    Timer ..|> IDisposable : implements
    Clock ..|> IDisposable : implements
    TimeClock ..|> IDisposable : implements

6.3. Diagrama de secuencia: Inicio y pausa

Este diagrama de secuencia muestra las interacciones en tiempo de ejecución cuando un usuario inicia y luego pausa la simulación.

sequenceDiagram
    participant User
    participant SimulationEngine
    participant Timer
    participant BackgroundThread

    User->>SimulationEngine: ClickStartButton()
    SimulationEngine->>Timer: Start(duration)
    activate Timer
    Timer->>BackgroundThread: Task.Run(SimulationLoop)
    deactivate Timer
    activate BackgroundThread

    loop Simulation Loop
        BackgroundThread->>BackgroundThread: Increment CurrentTick
        BackgroundThread->>Timer: OnProgressAdvance(percent)
        Timer-->>SimulationEngine: Fires event
    end

    User->>SimulationEngine: ClickPauseButton()
    SimulationEngine->>Timer: Pause()
    activate Timer
    Timer->>Timer: Paused = true
    Timer-->>SimulationEngine: OnPaused(true)
    deactivate Timer
    BackgroundThread-->>BackgroundThread: Enters Task.Delay(Infinite, _pauseCts)

    User->>SimulationEngine: ClickResumeButton()
    SimulationEngine->>Timer: Continue()
    activate Timer
    Timer->>Timer: Paused = false, _pauseCts.Cancel()
    Timer-->>SimulationEngine: OnPaused(false)
    deactivate Timer
    BackgroundThread-->>BackgroundThread: Resumes loop

6.4. Diagrama de módulos

graph TD

    user["User<br>[External]"]
    subgraph kmila9s_boundary["Kmila-9s Application<br>[External]"]
        subgraph userInterface_boundary["User Interface<br>[External]"]
            codeEditor["Code Editor<br>[External]"]
            waveformViewer["Waveform Viewer<br>[External]"]
            controlPanel["Control Panel<br>[External]"]
        end
        subgraph simulationEngine_boundary["Simulation Engine<br>[External]"]
            vhdlParser["VHDL Parser<br>[External]"]
            simulatorCore["Simulator Core<br>[External]"]
            signalManager["Signal Manager<br>[External]"]
            %% Edges at this level (grouped by source)
            simulatorCore["Simulator Core<br>[External]"] -->|"Updates | Manages signal values during simulation"| signalManager["Signal Manager<br>[External]"]
            vhdlParser["VHDL Parser<br>[External]"] -->|"Provides | Parsed VHDL logic"| simulatorCore["Simulator Core<br>[External]"]
        end
        subgraph timeMachineModule_boundary["TimeMachine Module<br>[External]"]
            timerClass["Timer<br>/Repositories/Timer.cs"]
            timeClockClass["TimeClock<br>/Models/TimeClock.cs"]
            clockModel["Clock Model<br>/Models/Clock.cs"]
            inputModel["Input Model<br>/Models/Input.cs"]
            %% Edges at this level (grouped by source)
            timerClass["Timer<br>/Repositories/Timer.cs"] -->|"Uses | Tick advancement and time resolution"| timeClockClass["TimeClock<br>/Models/TimeClock.cs"]
            clockModel["Clock Model<br>/Models/Clock.cs"] -->|"Subscribes to | OnTickChanged events"| timeClockClass["TimeClock<br>/Models/TimeClock.cs"]
            timerClass["Timer<br>/Repositories/Timer.cs"] -->|"Uses | To manage simulation inputs"| inputModel["Input Model<br>/Models/Input.cs"]
        end
        %% Edges at this level (grouped by source)
        controlPanel["Control Panel<br>[External]"] -->|"Controls | Starts, pauses, continues, and stops simulation time"| timerClass["Timer<br>/Repositories/Timer.cs"]
        timerClass["Timer<br>/Repositories/Timer.cs"] -->|"Drives | Generates time ticks for simulation execution"| simulatorCore["Simulator Core<br>[External]"]
        signalManager["Signal Manager<br>[External]"] -->|"Provides | Signal data for visualization"| waveformViewer["Waveform Viewer<br>[External]"]
    end
    %% Edges at this level (grouped by source)
    user["User<br>[External]"] -->|"Uses | Interacts with"| userInterface_boundary["User Interface<br>[External]"]

7. Limitaciones actuales

Las siguientes limitaciones existen en la implementación actual:

  • Marcador de posición de la clase Input: La clase Input.cs es un marcador de posición con una implementación mínima. Las versiones futuras agregarán propiedades para el valor, el tipo y los cambios de valor basados en el tiempo.
  • Retardo de tick fijo: El bucle de simulación utiliza un retardo fijo de 10 ms entre ticks, lo cual puede no ser adecuado para todos los escenarios de simulación.
  • Seguridad entre hilos: Solo la propiedad Paused está marcada como volatile. Otros campos pueden necesitar sincronización para el acceso multihilo.

8. Detalles técnicos

Resolución de tiempo

El TimeMachine opera con una resolución de 10 nanosegundos por tick (NS_PER_TICK = 10). Este valor se eligió para proporcionar suficiente precisión para los circuitos digitales típicos, manteniendo al mismo tiempo un rendimiento de simulación razonable.

Conversión de frecuencia de reloj

Las frecuencias de reloj especificadas en MHz se convierten a ticks de simulación mediante la fórmula:

Period (in ticks) = 1,000,000,000 ns / (frequency_MHz * 1,000,000) / NS_PER_TICK
                  = 1000 / frequency_MHz / NS_PER_TICK

Por ejemplo:

  • Reloj de 50 MHz: Period = 1000 / 50 / 10 = 2 ticks por semiperiodo
  • Reloj de 100 MHz: Period = 1000 / 100 / 10 = 1 tick por semiperiodo

Mecanismo de pausa/reanudación

El mecanismo de pausa utiliza Task.Delay(Timeout.Infinite) con un token de cancelación, lo que permite que el bucle de simulación espere de manera eficiente sin consumir ciclos de CPU.

8.bis. Diagramas de auditoría (2026-05-09)

La auditoría identificó que la máquina de estados del temporizador y la ruta de ejecución dual rápida/interactiva no estaban representadas en los diagramas de la tesis. El bloque de abajo es la vista canónica; el mismo origen se encuentra en Documentacion/TT1/diagrams/fig_5_11_11_state_timer.mmd.

fig_5_11_11_state_timer — Máquina de estados del temporizador

stateDiagram-v2
    [*] --> Idle
    Idle --> Running : Start(Duration?)
    Running --> Paused : Pause()<br/>OnPaused(true)
    Paused --> Running : Continue()<br/>OnPaused(false), cancel _pauseCts
    Running --> Stopped : Stop()<br/>cancel _cts
    Paused --> Stopped : Stop()
    Running --> Done : Duration alcanzada<br/>OnSimulationEnded
    Stopped --> Idle : Reset interno
    Done --> Idle : Reset interno

    state Running {
        [*] --> Tick
        Tick --> EmitProgress : OnSimulationTick<br/>(consumido por DeltaCycleEngine)
        EmitProgress --> Wait : Task.Delay(10ms)
        Wait --> Tick : !Paused y !_cts cancelado
    }

    state Paused {
        [*] --> AwaitResume : Task.Delay(Infinite, _pauseCts.Token)
        AwaitResume --> [*] : token cancelado
    }

    note right of Running
      DeltaCycleEngine.ConnectToTimeMachine()
      subscribe a OnPaused (estatico)
      para sincronizar pausa/resume.
    end note

    note left of Idle
      Path dual:
      - Fast: SimulationRunner sin TimeMachine.
      - Interactivo: TimeMachine + DeltaCycleEngine.
    end note

9. Registro de cambios

Versión 1.14.1 (2026-05-01)

Verificación de la documentación

  • README.md: Se verificaron todas las firmas de clases, eventos y comportamientos contra la base de código actual. Se confirmó:
    • Timer expone Start, Pause, Continue, Stop, Restart, Dispose, además de TimeElapsed, CurrentTick, Paused (volatile) y los cuatro eventos (OnPaused y OnSimulationTick son static; OnSimulationEnded y OnProgressAdvance son eventos de instancia).
    • TimeClock.Next() incrementa Tick, dispara OnTickChanged y avanza a Multiplier con ResetCounter en caso de desbordamiento. NS_PER_TICK = 10 ns permanece sin cambios.
    • Clock deriva su semiperiodo a partir de frequencyMHz respecto a TimeClock.NS_PER_TICK, se suscribe a OnTickChanged y ResetCounter, y se cancela la suscripción en Dispose.
    • Input sigue siendo un marcador de posición que solo contiene Name.
  • Diagrama de clases: Se marcó Paused como volatile bool para coincidir con la declaración del campo.

Versión 0.5.2 (2026-02-21)

Correcciones de la documentación

  • Clock.cs: Se agregó documentación XML para los miembros privados (_halfPeriod, _currentTick, OnResetGlobalClock)
  • README.md: Se corrigió el diagrama de secuencia para reflejar con precisión el mecanismo de pausa/reanudación (utiliza la bandera Paused + el token _pauseCts, no _cts.Cancel())
  • README.md: Se corrigió el ejemplo de uso para referenciar correctamente Timer.OnPaused como un evento estático en lugar de un evento de instancia

Versión 0.5.1 (2025-12-13)

Actualizaciones de diagramas

  • Diagrama de clases (6.2): Actualizado para incluir todas las clases del módulo:
    • Se agregó la clase Clock con propiedades (Name, State, Period, OnStatusChanged) y métodos (Tick, Dispose)
    • Se agregó la clase Input (marcador de posición para las entradas de simulación)
    • Se actualizó TimeClock para mostrar los miembros estáticos reales (eventos NS_PER_TICK, OnTickChanged, ResetCounter)
    • Se agregó la relación que muestra que Clock se suscribe a TimeClock.OnTickChanged
    • Se agregó el indicador de implementación de IDisposable para Clock

Versión 0.5.0 (2025-12-13)

Mejoras de la documentación

  • Models/Input.cs: Se agregó documentación XML exhaustiva que incluye:

    • Documentación a nivel de clase que explica el estado de marcador de posición
    • Casos de uso futuros para el manejo de señales de entrada
    • Documentación de propiedades con ejemplos
    • Referencias cruzadas a clases relacionadas
  • Program.cs: Se agregó documentación XML completa que incluye:

    • Documentación a nivel de clase que explica la configuración de prueba
    • Documentación de métodos con una tabla de comandos disponibles
    • Comentarios en línea que explican la elección de las frecuencias de reloj
    • Notas sobre el comportamiento del bucle infinito
  • README.md: Se agregó información de versión, la sección de limitaciones actuales, la sección de detalles técnicos y este registro de cambios


Versión 1.14.0 (2026-04-19)

  • Higiene de compilación — se agregó <NoWarn> para el ruido restante de documentación XML / referencias anulables, de modo que el proyecto compila sin advertencias junto con el resto de la solución Kmila.
  • No hay cambios de comportamiento en tiempo de ejecución en esta versión; el motor de ticks del reloj del TimeMachine es estable y se consume sin cambios por el sistema de reloj híbrido predeterminado para FPGA / con anulación por proyecto agregado en Kmila.Shared.Services.SimulationParameters.

Novedades en la v1.15 (2 de mayo de 2026)

  • Ejecutor de pruebas automatizado (#6). Program.cs era anteriormente un REPL interactivo que se bloqueaba en Console.ReadLine() y hacía imposible validar el módulo de forma no interactiva. Se reemplazó por un ejecutor de 23 pruebas que finaliza con el código 0/1 según el resultado aprobado/fallido. Casos:
    • 8 pruebas de la semántica de Clock (cálculo del periodo a 50 / 100 MHz, estado inicial, alternancia, relación de frecuencias, dispose cancela la suscripción a OnTickChanged).
    • 4 pruebas unitarias de TimeClock (Next, OnTickChanged, Reset, constante NS_PER_TICK).
    • 8 pruebas del ciclo de vida de Timer (Start con duración finita, Pause detiene el avance, Continue reanuda, Stop reinicia, Restart, OnProgressAdvance monótono).
    • 3 pruebas de esfuerzo (100 relojes concurrentes, ejecución de 50k ticks, 20 ciclos rápidos de Start/Stop).
  • GC de estación de trabajo habilitado a nivel de proyecto (#7); el RSS máximo durante la ejecución de las 23 pruebas es de ~37 MB.

Notas de versión completas: ../Documentacion/CHANGELOG_v1.15.md.


Última actualización: 2026-08-03