Interpreter Module

Project: Kmila-9s

This module is a core component of Kmila-9s, a project officially titled "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).

Author: Ulrich Tamayo Daniel Email: [email protected] Email 2: [email protected] Version: 1.2.0 (branch v1.2.0) Last Updated: 2026-08-03


Table of Contents

  1. Project Overview
  2. The Interpreter Module
  3. Architecture
  4. Critical Design Decisions
  5. Key Components
  6. How to Use
  7. Testing
  8. Common Issues and Solutions
  9. Developer Guide
  10. Changelog

1. Project Overview

The primary goal of this project is to develop a free, open-source, and multiplatform desktop application for the interactive debugging of VHDL code. The application aims to facilitate the learning of digital design by providing a unified environment for simulation and real-time visualization of digital signals, clock cycles, and port states, completely eliminating the need for physical hardware like FPGAs.

Key Motivations

  • Accessibility: Overcoming the financial barrier of expensive FPGA boards and proprietary software licenses.
  • Pedagogical Improvement: Offering an intuitive, integrated graphical interface that simplifies the debugging process, unlike the fragmented command-line workflows of tools like GHDL and GTKWave.
  • Flexibility: Providing a self-contained application that runs locally on multiple operating systems (Windows, macOS, Linux, Android, iOS) without requiring an internet connection.

This project is built using .NET and MAUI, ensuring a modern and maintainable codebase with broad platform support.


2. The Interpreter Module

This specific module, the VHDL Interpreter, is the execution engine of the simulation pipeline. Its primary responsibility is to take the structured, in-memory representation of VHDL code (generated by the Parser Module) and simulate its behavior over time.

Current Status (v1.14.x)

  • Supported VHDL Features: Entities, architectures, processes, signals, variables, ports, generics, if/elsif/else, case/when, for/while/simple loops, exit/next/null/return, functions, procedures, IEEE standard functions, custom type definitions (enum / array / record / subtype), packages, generate blocks, conditional and selected concurrent assignments, component instantiations.
  • Simulation engine: Full delta-cycle scheduler (DeltaCycleEngine + SignalScheduler + ProcessScheduler), signal history recording (SignalHistory) with VCD / CSV / Text export, after clause support, sensitivity-list driven process activation, IEEE attribute tracking ('event / 'last_value / 'stable / 'active).
  • Synthesis pass (Synthesis/ namespace): Behavioral VHDL is lowered to a structural netlist (Netlist, Net, Cell, Pin, PortPin) by per-construct synthesizers and laid out via LayeredLayout for the schematic view in the Editor. This is independent from the simulation pipeline and feeds the Schematic component.
  • Run-loop façade (SimulationRunner + SimulationConfig): Single entry point used by the Editor page to run a SimulationConfig (entity + total ticks + auto-toggling ClockConfig list + StimulusEvent list), with Pause / Resume / Stop, OnProgressChanged, OnSimulationEnded, and OnError events.
  • Custom types: TypeDefinition + TypeRegistry cover enumerations, arrays, records, and subtypes. TypeRegistry is consulted by VariableSyntetizer, Operation, FunctionExecutor, and Resources.
  • FPGA resource estimation: Resources produces flip-flop, LUT, DSP, BRAM, I/O, and clock-domain estimates per entity; consumed by the FPGA fit chip in the Editor's status bar.
  • Progress / cancellation: ProgressReporter (singleton, event-based) drives Editor build-status UI; CancellationToken is threaded through the synthesis and run pipeline.
  • Test status: Two independent test surfaces exist today (see §7):
    1. The historic console corpus driven by Program.cs — now 54 test*.vhdl fixtures (numbered through test41, plus named variants) — of which the old 80 % (16/20) figure quoted in the changelog is a v0.15.0 snapshot over the first 20 fixtures only and has not been re-measured since; re-run Program.cs against the corpus to obtain a current number.
    2. A first-class xUnit suite under Interpreter/Tests/ (DeltaCycleEngineTests, SynthesisTests, NestedIEEECallTests, IEEEPrimitivesTests, IEEELibraryLoaderTests, IEEELoweringEndToEndTests, LibraryCompilerTests, PortStrictnessTests, PracticeExerciseTests, VcdComparison) run via dotnet test. This is the authoritative regression surface; the console corpus is a smoke/exploration harness.

Core Responsibilities

  • Expression Evaluation: Calculating results of arithmetic, logical, and relational operations using the Shunting-yard algorithm with proper operator precedence
  • Statement Execution: Processing sequential statements like signal assignments, variable assignments, and control structures within VHDL processes
  • Control Flow Management: Implementing conditional execution (if-then-else, case-when), loops (for, while, loop), and nested control structures
  • State Simulation: Managing state of all signals, variables, and ports, updating values as simulation progresses
  • Event Handling: Simulating signal changes triggering process execution (event-driven simulation model)
  • Function Support: IEEE standard functions (rising_edge, falling_edge, type conversions, vector operations) and user-defined functions/procedures

3. Architecture

Parsing Pipeline

VHDL Source Code
    ↓
[Parser Module] - Tokenization
    ↓
Token Stream
    ↓
[Entity] - Entity/Port/Generic parsing
    ↓
[Architecture] - Signal declarations, behavior synthesis
    ↓
[Process] - Sequential statement parsing
    ↓
[Control Structures] - If/Case/Loop synthesis
    ↓
[Operation] - Expression evaluation
    ↓
[ShuntingYard] - Infix to postfix conversion
    ↓
[Operator] - Actual computation
    ↓
Result (Updated signal/variable values)

Class Hierarchy

ISControl (Interface) - Sequential control structures with async execution
├── IfStructure              - if-elsif-else conditional execution
├── CaseStructure            - case-when statement with multiple branches
├── ForLoopStructure         - for loop with ascending/descending ranges
├── WhileLoopStructure       - while loop with condition evaluation
├── SimpleLoopStructure      - infinite loop with exit conditions
├── ExitStatement            - exit statement (throws ExitException)
├── NextStatement            - next statement (throws NextException)
├── NullStatement            - null no-op statement
├── ReturnStatement          - return statement (throws ReturnException)
├── ProcedureCallStatement   - procedure call with argument evaluation
├── ReportStatement          - VHDL `report` statement, renders + emits via ReportBus
├── AssertStatement          - VHDL `assert` statement, emits on false condition via ReportBus
├── WaitForStatement         - `wait for N ns` / `wait` statement handling
├── ConditionalAssignment    - concurrent when...else assignment
├── SelectedAssignment       - concurrent with...select assignment
├── GenerateBlock            - for-generate and if-generate statements
├── Process                  - VHDL process with sensitivity list (also IControl)
├── Architecture             - architecture synthesis and execution (also IControl)
├── Entity                   - entity synthesis and execution (also IControl)
├── VariableSyntetizer       - variable/signal/port declaration parser
└── Executer                 - task list orchestrator with pause/resume (also IDisposable)

IOperation (Interface) - Expression evaluation
├── IArithOperation
│   └── Operator (arithmetic: +, -, *, /, **, mod)
└── ILogicOperation
    └── Operator (logic: and, or, xor, nand, nor, xnor, not, sll, srl, sla, sra, rol, ror)

IFinder (Interface) - Literal identification
└── LiteralsFinder

Utility / Singleton Services
├── Operation               - Expression parsing and RPN evaluation (IOperation)
├── ShuntingYard            - Infix to postfix conversion (ISYard)
├── OperationDetector       - Operation type classification
├── ScheduledOperation      - Delta cycle signal scheduling wrapper
├── StandardFunctions       - IEEE standard library functions (static)
├── FunctionExecutor        - User-defined function/procedure executor (singleton)
├── SignalAttributeHandler  - Signal attribute tracking (singleton)
├── SignalScheduler         - Delta cycle signal update scheduling
├── ProcessScheduler        - Process execution scheduling by sensitivity
├── DeltaCycleEngine        - Delta cycle simulation coordinator (IDisposable)
├── SignalHistory           - Signal value recording for waveforms
├── PackageRegistry         - Package constants/functions/procedures (singleton)
├── EntityRegistry          - Multi-file entity discovery (singleton)
├── FunctionRegistry        - FunctionDefinition stubs for skipped funcs/procs (singleton)
├── PackageSubtypeScanner   - Cross-file `subtype` scanner → PackageRegistry
├── TypeRegistry            - Custom VHDL type definitions
├── SourceElaborator        - Component-instance elaboration + string-safe source splitting
├── ReportBus               - Singleton event sink for `report` / `assert` output
├── ParseTrace              - Shared static skip/render helpers + skip-iteration cap
├── SkipDiagnosticLog       - Records silent-skip events with stable codes
├── IEEELibraryLoader       - Thin façade delegating to the LibraryCompiler module
├── ProgressReporter        - Event-based progress tracking (singleton)
└── Resources               - FPGA resource estimation

Models
├── TypeDefinition          - Custom type metadata (enum, array, record, subtype)
├── FunctionDefinition      - User-defined function signature and body
├── ProcedureDefinition     - User-defined procedure signature and body
├── ParameterDefinition     - Function/procedure parameter metadata
├── DeferredFunction        - Runtime-evaluated IEEE/known function call
├── DeferredUserFunction    - Runtime-evaluated *user-defined* function call
├── DeferredExpression      - Sub-expression whose evaluation is deferred to run time
├── DeferredAttribute       - Signal/array attribute resolved at run time (Tier 4.1)
├── DeferredIndex           - Deferred array/vector index resolution
├── DynamicIndex            - Run-time-computed index into an array/vector
├── IProcessFrame           - Interface for the sequential-execution frame stack
├── BranchFrame             - Frame for an in-flight if/elsif/case branch
├── LoopFrame               - Frame for an in-flight for/while/loop iteration
├── ProcedureFrame          - Frame for an in-flight procedure call
├── ControlExceptions       - ExitException, NextException, ReturnException
├── SimulationConfig        - Entity + ticks + clocks + stimuli (input to SimulationRunner)
├── ClockConfig             - Auto-toggling clock spec (signal + half-period + initial state)
├── StimulusEvent           - "set Signal to Value at Tick" stimulus record
└── Constansts              - Operator precedence enums, operation regex (sic — historical typo)

Synthesis Namespace (Interpreter.Synthesis) - behavioral → structural lowering
├── Synthesizer                  - Static façade: Synthesize(Entity) → Netlist
├── SynthesisContext             - Per-entity build context (nets, cells, ports, diagnostics)
├── ComponentSynthesizer         - Emits BLACKBOX_COMPONENT cells for component instantiations
│                                  registered in EntityRegistry (shallow; honours pin direction)
├── ConcurrentAssignSynthesizer  - Lowers concurrent signal assignments
├── ConditionalAssignSynthesizer - Lowers `when…else` concurrent assignments
├── SelectedAssignSynthesizer    - Lowers `with…select` concurrent assignments
├── ProcessSynthesizer           - Lowers VHDL processes (registered FFs, combinational paths)
├── IEEEPrimitives               - Table-driven IEEE-function → primitive-cell emitter
│                                  (internal IIEEEPrimitive registry; extensible via Register)
├── SelectionFilter              - Filters a Netlist + LaidOutNetlist for export
│                                  (SelectionPicks → FilteredLayout for the Schematic basket)
├── Models.cs                    - Records: Net, Pin, Cell, PortPin, Netlist, Diagnostic
│                                  Enums: CellKind, NetOrigin, NetKind, PortDir, DiagnosticSeverity
└── Layout/LayeredLayout         - Sugiyama-style placement → LaidOutNetlist (rects + wire paths)

Façade
├── ISimulationRunner            - Shared surface of the two interchangeable run drivers
│                                  (Engine, IsRunning/IsPaused, Pause/Resume/Stop, the
│                                  OnProgressChanged/OnSimulationEnded/OnError/OnEngineReady/
│                                  OnReport events). Lets callers (e.g. ProgramBuilder) pick a
│                                  runner at run time without branching on the concrete type.
├── SimulationRunner             - "Visual/Slow mode" IDisposable runner: visits every tick
│                                  0..totalTicks. RunAsync(SimulationConfig), Pause/Resume/Stop.
└── FastSimulationRunner         - "Fast mode" sibling: drives the SAME DeltaCycleEngine but
                                   jumps straight to the next tick where something can happen
                                   (clock edge / stimulus / scheduled update / process wake),
                                   skipping idle ticks. Same public surface as SimulationRunner;
                                   verified byte-identical to it by VCD diff across a corpus.

Placeholder
└── Services/DataStructure       - Reserved for future complex data-structure ops
                                   (currently empty class — operations live in
                                    VariableSyntetizer / TypeRegistry / TypeDefinition)

Key Components Summary

Core Execution Pipeline:

  • Program.cs: Test runner that processes test*.vhdl files, parses entities/architectures
  • Entity.cs: Parses entity declarations (ports, generics), coordinates architecture and resource estimation
  • Architecture.cs: Parses architecture declarations (signals) and behavioral region (processes, concurrent statements, generate blocks)
  • Process.cs: Manages sequential processes with sensitivity lists, supports delta cycle execution via SignalScheduler
  • Executer.cs: Orchestrates task execution with pause/resume/cancel support, integrates with TimeMachine

Control Structures (ISControl implementations):

  • IfStructure.cs: Handles if-elsif-else conditional statements with nested structures
  • CaseStructure.cs: Handles case-when statements with multiple branches
  • ForLoopStructure.cs: Handles for loops with ascending/descending ranges and expression-based bounds
  • WhileLoopStructure.cs: Handles while loops with condition evaluation
  • SimpleLoopStructure.cs: Handles infinite loops with exit conditions
  • ExitStatement.cs: Handles VHDL exit statements with optional label and condition
  • NextStatement.cs: Handles VHDL next statements with optional label and condition
  • NullStatement.cs: Handles VHDL null no-op statements
  • ReturnStatement.cs: Handles VHDL return statements for functions and procedures
  • ProcedureCallStatement.cs: Handles procedure calls with argument evaluation and out-parameter write-back
  • ConditionalAssignment.cs: Handles concurrent conditional signal assignments (when...else)
  • SelectedAssignment.cs: Handles concurrent selected signal assignments (with...select)
  • GenerateBlock.cs: Handles for-generate and if-generate statements with loop unrolling

Expression Evaluation:

  • Operation.cs: Orchestrates expression parsing, synthesis to RPN, and evaluation
  • ShuntingYard.cs: Implements Shunting-yard algorithm for operator precedence
  • Operator.cs: Executes individual operations (arithmetic, logic, comparison, shift/rotate)
  • ScheduledOperation.cs: Wraps Operation for delta cycle signal scheduling semantics
  • LiteralsFinder.cs: Identifies and classifies literal values (integers, std_logic, vectors, hex, boolean) using regex
  • OperationDetector.cs: Detects operation types (assignment, etc.) from raw string input

Declaration Parsing:

  • VariableSyntetizer.cs: Parses variable/signal/port/generic/constant declarations with custom type support

Simulation Engine:

  • DeltaCycleEngine.cs: Coordinates VHDL delta cycle simulation with process scheduling, signal updates, and time advancement. Live-replay hooks: OnFrameEmitted(FrameEmittedArgs) (per-delta changed signals + their source lines), Pause()/Resume()/StepOneDelta(), History, and — 2026-05-27 — OnConditionEvaluated(int line) which fires when an if/elsif branch is taken or a case arm matches, so breakpoints can pause on a condition line that has no signal assignment.
  • SignalScheduler.cs: Manages signal update scheduling for current and future delta cycles (after clause support)
  • ProcessScheduler.cs: Manages process execution scheduling based on sensitivity lists
  • SignalHistory.cs: Records signal value changes over time for waveform export (VCD, CSV, Text). Timestamps are long (tick*1000+delta); widened from int on 2026-09-13 so seconds-scale runs no longer overflow at ~21 ms. Storage-only — the SignalAttributeHandler semantics path keeps its own int timestamp, so short-run output is byte-identical.

Services and Registries:

  • StandardFunctions.cs: Implements IEEE standard library functions (edge detection, type conversions, vector operations)
  • SignalAttributeHandler.cs: Singleton tracking signal attributes ('event, 'last_value, 'stable, 'active)
  • FunctionExecutor.cs: Singleton executor for user-defined functions/procedures with local scope and recursion support
  • PackageRegistry.cs: Singleton registry for VHDL package constants, functions, and procedures
  • EntityRegistry.cs: Singleton registry for multi-file entity discovery and component hierarchy tracking
  • TypeRegistry.cs: Registry for custom VHDL type definitions (enumeration, array, record, subtype)
  • Resources.cs: Estimates FPGA hardware resource usage (flip-flops, LUTs, DSP slices, BRAM, I/O, clocks)
  • ProgressReporter.cs: Event-based progress reporting system for UI integration with cancellation support

Models:

  • TypeDefinition.cs: Represents custom type declarations with bounds, fields, and default values
  • FunctionDefinition.cs: Represents user-defined function signatures with lazy body synthesis
  • ProcedureDefinition.cs: Represents user-defined procedure signatures with out/inout parameter support
  • ParameterDefinition.cs: Represents function/procedure parameter metadata with mode (in/out/inout)
  • DeferredFunction.cs: Represents function calls deferred to execution time (e.g., rising_edge)
  • ControlExceptions.cs: ExitException, NextException, ReturnException for control flow
  • Constansts.cs: Operator precedence enums and operation regex patterns (the file name keeps the historical typo)
  • SimulationConfig.cs: Composition of required Entity Entity, ulong TotalTicks, List<ClockConfig> Clocks, List<StimulusEvent> Stimuli. Plus the auxiliary ClockConfig (Signal, HalfPeriodTicks, InitialHigh) and StimulusEvent (Tick, Signal, Value) types.

Run-loop Façade:

  • SimulationRunner.cs (Services/): IDisposable façade with RunAsync(SimulationConfig), Pause(), Resume(), Stop(), plus the OnProgressChanged(double), OnSimulationEnded(bool), and OnError(string) events. It is the entry point used by Kmila.Shared.Services.SimulationCoordinator (no other call site re-implements the pipeline).
  • Services/DataStructure.cs: Reserved placeholder class — currently empty constructor only. Real type operations live in VariableSyntetizer, TypeRegistry, and TypeDefinition.

Synthesis Namespace (Interpreter.Synthesis):

  • Synthesizer.cs: Static entry point — Netlist Synthesize(Entity entity) first calls ComponentSynthesizer.SynthesizeAll for the entity's instantiations, then walks the architecture body and dispatches each concurrent construct (Operation / conditional / selected / Process) to a per-construct synthesizer.
  • SynthesisContext.cs: Build context per entity. Owns nets/cells lists and exposes GetOrCreateNet, NewInternal, Constant, AddCell, AddPort, AddSignal, AddDiagnostic, and finally Build() which returns a frozen Netlist.
  • ComponentSynthesizer.cs: internal static; SynthesizeAll(parentEntityName, ctx) emits one CellKind.BLACKBOX_COMPONENT cell per component instantiation the parser registered against the entity in EntityRegistry.Instance. Deliberately shallow — it does not descend into the referenced component's own architecture (that is covered by SourceElaborator at simulation time); it only makes the top-level hierarchy visible in the Schematic. Honours formal-pin direction when the referenced entity is resolved, otherwise emits every formal as an input pin.
  • ConcurrentAssignSynthesizer.cs: Lowers concurrent signal assignments, including Synthesize(Operation, ctx) and SynthesizeInto(Operation, outputNet, ctx) overloads + SynthesizeExpression(expr, scope, ctx, targetWidth) for sub-expression reuse. Resolves DeferredFunction args (including nested calls) and lowers IEEE calls through IEEEPrimitives.TryLower.
  • ConditionalAssignSynthesizer.cs: Lowers when…else chains.
  • SelectedAssignSynthesizer.cs: Lowers with…select blocks.
  • ProcessSynthesizer.cs: Lowers a Process to a mix of registered (clocked) and combinational cells.
  • IEEEPrimitives.cs: Table-driven lowering of IEEE function calls to real cells (EDGE_DETECT, TO_INTEGER, TO_VECTOR, RESIZE, SHL/SHR/ROL/ROR, BUF, …). The IEEEPrimitiveArgs / IEEEPrimitiveResult / IIEEEPrimitive / IEEEPrimitives types are internal (they reference the internal SynthesisContext); hosts extend the table via the public IEEEPrimitives.Register static. TryLower returns the emitted output net or a placeholder + UnknownIEEEFunction diagnostic on miss.
  • SelectionFilter.cs: Public filter that trims a Netlist + LaidOutNetlist down to a user-selected subset for export — SelectionPicks (basketed cell / net / port-net ids + optional FocusedSourceLine) → FilteredLayout (same coordinate space, with a Bounds box). Backs the SchematicViewer's export basket and Naive-view focus.
  • Synthesis/Models.cs: Hosts the record types that make up a netlist — Net, Pin, Cell, PortPin, Netlist, Diagnostic — and the enums CellKind, NetOrigin, NetKind, PortDir, DiagnosticSeverity.
  • Synthesis/Layout/LayeredLayout.cs: Sugiyama-style layout pass — Layout(Netlist) → LaidOutNetlist (with Rect boxes and WirePath polylines). Consumed by the Editor's Schematic component.

4. Critical Design Decisions

4.1. Regex Pattern Anchoring (v0.13.0)

One of the most critical fixes in v0.13.0 was properly anchoring operator regex patterns. Unanchored patterns caused severe token misclassification:

Problem Example:

// WRONG - Unanchored pattern
LOGIC_OPERATORS = "(and|or|xor|nand|nor|not)"

// This matches "or" in "memory" -> "mem[or]y"
// This matches "or" in "std_logic_vector" -> "std_logic_vect[or]"
// Result: Identifiers incorrectly classified as operators

Solution:

// CORRECT - Anchored pattern with ^ and $
LOGIC_OPERATORS = @"^(and|or|xor|nand|nor|not|xnor|abs)$"

// Now only exact matches work
// "or" matches, but "memory" and "std_logic_vector" don't

Impact: This fix alone resolved 9 test failures, improving pass rate from 35% to 80%.

4.2. Expression Evaluation Strategy

The interpreter uses a two-phase approach for expression evaluation:

  1. Synthesis Phase: Convert infix notation to postfix using Shunting-yard algorithm
  2. Execution Phase: Evaluate postfix expression using stack-based approach

Benefits:

  • Correct operator precedence handling (**, *, /, +, -)
  • Support for parenthesized sub-expressions
  • Clear separation between parsing and evaluation
  • Efficient evaluation with O(n) complexity

Example:

result <= (A + B) * C - D / 2;

Processing:

  1. Tokens: (, A, +, B, ), *, C, -, D, /, 2
  2. Postfix: A B + C * D 2 / -
  3. Evaluation: Stack-based with proper precedence

4.3. Port/Generic Declaration Handling

Port and generic declarations may contain complex expressions using other generics or functions:

generic (
    NUM_INPUTS : integer := 8;
    DATA_WIDTH : integer := 16
);
port (
    inputs : in std_logic_vector((NUM_INPUTS * DATA_WIDTH) - 1 downto 0);
    sum : out std_logic_vector(DATA_WIDTH + clog2(NUM_INPUTS) - 1 downto 0)
);

Strategy (v0.13.0): The interpreter skips evaluation of these expressions during declaration because:

  • Functions like clog2() may be defined later in the architecture
  • Generics are not yet assigned values during entity parsing
  • Type information is sufficient for syntax validation
  • Actual values computed at runtime when needed

4.4. Hardware Resource Estimation (v0.13.0)

The interpreter includes an automatic FPGA resource estimator that analyzes synthesized VHDL entities to predict hardware usage.

Resource Estimation Accuracy:

Resource Accuracy Method
Flip-Flops ~95% Count signals assigned in clocked processes (rising_edge/falling_edge)
I/O Pins 100% Exact count from entity port declarations
Clock Domains 100% Detect rising_edge/falling_edge calls in processes
DSP Slices ~80-90% Count multiplication operations and wide arithmetic
BRAM ~85% Detect array types and memory patterns
LUTs ~60-80% Estimate from combinational logic (synthesis-dependent)

Example Output:

Resources: FF=8, LUT=0, DSP=0, BRAM=0.0, IO=18, CLK=1

How It Works:

  1. Flip-Flop Counting:
// Scans all processes with clock sensitivity
// Counts signals assigned within rising_edge(clk) blocks
foreach (var process in clocked_processes)
{
    flipFlops += Sum(assigned_signals.Select(s => s.Size));
}
  1. I/O Pin Counting:
// Sums bit widths of all entity ports
ioPins = entity.Ports.Sum(p => p.Size);
  1. Clock Detection:
// Finds signals used in rising_edge() or falling_edge() calls
// Identifies clock domains by unique clock signal names

Example:

entity example is
    port (
        clk : in STD_LOGIC;         -- 1 I/O pin
        reset : in STD_LOGIC;       -- 1 I/O pin
        data_in : in STD_LOGIC_VECTOR(7 downto 0);   -- 8 I/O pins
        data_out : out STD_LOGIC_VECTOR(7 downto 0)  -- 8 I/O pins
    );
end example;

architecture behavioral of example is
begin
    process(clk, reset)
    begin
        if reset = '1' then
            data_out <= "00000000";
        elsif rising_edge(clk) then
            data_out <= data_in;    -- 8 flip-flops for data_out
        end if;
    end process;
end behavioral;

Resource Estimation Result:

  • Flip-Flops: 8 (data_out is 8-bit register)
  • I/O Pins: 18 (1+1+8+8)
  • Clock Domains: 1 (clk)
  • LUTs: 0 (no combinational logic)
  • DSP: 0 (no multiplications)
  • BRAM: 0 (no arrays)

4.5. Operator Precedence Design

Challenge: VHDL has two separate operator type systems:

  • Arithmetic operators (+, -, *, /, **, mod)
  • Logical operators (and, or, xor, nand, nor, not)

Solution:

// Precedence stored as 'object' to accommodate both enum types
public object Precedence { get; private set; }

// v0.13.0 Fix: Use Convert.ToInt32() for proper enum unboxing
Convert.ToInt32(operator.Precedence)

Precedence Values (lower = higher precedence):

  • 0: UNARY (**, abs, not)
  • 1: MULTIPLICATION/DIVISION, AND/NAND
  • 2: ADD/SUBTRACT, OR/NOR
  • 3: XOR/XNOR
  • 4: PARENTHESIS
  • 5: COMPARATION, ASSIGNATION

5. Key Components

5.1. ShuntingYard - Expression Evaluator

File: Repositories/ShuntingYard.cs

Purpose: Converts infix expressions to postfix notation using Dijkstra's Shunting-yard algorithm.

Algorithm:

Input: [A, +, B, *, C]

Process:
1. A (operand) → output
2. + (operator) → stack
3. B (operand) → output
4. * (higher precedence than +) → stack
5. C (operand) → output
6. End: pop all operators to output

Output: [A, B, C, *, +]

v0.13.0 Critical Fix:

// Changed from direct cast to Convert.ToInt32()
Convert.ToInt32(_operators.Peek().Precedence) <= Convert.ToInt32(operation.Precedence)

Why: Precedence is object type containing enum. Direct (int) cast fails for enums.

5.2. Operation - Expression Handler

File: Services/Operation.cs

Purpose: Orchestrates expression parsing, synthesis, and evaluation.

Key Methods:

Syntetize() - Processes raw expression into evaluable form:

// Input: "A + B * C"
// Output: List<object> { Variable(A), Operator(+), Variable(B), Operator(*), Variable(C) }

Execute() - Evaluates expression and returns result:

// Creates ShuntingYard, evaluates postfix, returns Variable with result

v0.13.0 Improvements:

  1. Keyword Skipping:
// Skip 'downto'/'to' keywords in range expressions
// Example: signal(7 downto 0) → process "signal" and indices, skip "downto"
if (currentItem.ToLower() == "downto" || currentItem.ToLower() == "to")
    continue;

// Skip 'when...else' in conditional assignments
// Example: output <= input1 when sel='0' else input2
if (currentItem.ToLower() == "when")
    /* skip to 'else' */
  1. Unary Operator Type Checking:
// v0.13.0 Fix: Prevent ** (power) from being treated as NOT
bool isUnaryNot = op.OperatorType == LOGIC_OPERATION &&
                  op.Precedence is PrecedencesLogicOperators &&
                  (PrecedencesLogicOperators)op.Precedence == NOT;

Why: Both PrecedencesArithOperators.UNARY and PrecedencesLogicOperators.NOT have value 0.

5.3. Architecture - Behavioral Synthesizer

File: Services/Architecture.cs

Purpose: Parses architecture declarative region and behavioral statements.

Responsibilities:

  1. Parse signal declarations
  2. Identify and parse processes
  3. Handle concurrent signal assignments
  4. Skip component instantiations (structural, not behavioral)
  5. Manage labeled statements

v0.13.0 Features:

1. Labeled Statement Detection:

-- Labeled process
timer_proc : process(clk, reset)
begin
    -- statements
end process;

-- Labeled component instantiation
cpu_inst : CPU port map (clk => clk, data => data);

-- Labeled generate
gen_adders : for i in 0 to 7 generate
    -- structure
end generate;

Implementation:

if (_streamer.Current?.Type == IDENTIFIERS)
{
    // Check for label : keyword/component pattern
    if (nextToken == ":")
    {
        if (followingToken is KEYWORDS)
            // Handle process, for, if generate
        else if (followingToken is IDENTIFIERS)
            // Handle component instantiation
            SkipComponentInstantiation();
    }
}

2. Component Instantiation Skipping:

private void SkipComponentInstantiation()
{
    // Skip: label : ComponentName [generic map (...)] port map (...);
    int parenDepth = 0;
    while (current != ";\" || parenDepth > 0)
    {
        if (current == "(") parenDepth++;
        else if (current == ")") parenDepth--;
        moveNext();
    }
}

5.4. IfStructure - Conditional Handler

File: Repositories/IfStructure.cs

Purpose: Manages if-elsif-else conditional execution.

Supported Syntax:

if condition1 then
    statements;
elsif condition2 then
    statements;
else
    statements;
end if;

Design: Uses flat list of IfBranch objects (not recursive) for better maintainability.

2026-05-27 — condition breakpoints (IfBranch.ConditionLine): each branch records the 1-based source line of its if / elsif keyword (captured from _streamer.Current.Line before the keyword is consumed). At runtime DeltaCycleEngine fires OnConditionEvaluated(line) when a branch is taken (condition true), so the live-replay layer can pause on a breakpoint set on a condition line — the signal-transition path can't catch these because the condition itself produces no signal change. See DeltaCycleEngine.OnConditionEvaluated.

v0.13.0 Enhancement - Nested Case Statements:

case "case":
    // Nested case statement within if/elsif/else branch
    CaseStructure nestedCase = new(_variables, _streamer);
    nestedCase.Syntetize();
    branch.Tasks.Add(nestedCase);
    ConsumeEndKeyword("case");
    break;

Example Usage:

if button_flag = '1' then
    case selector is
        when "00" => opcode <= ADD;
        when "01" => opcode <= SUB;
        when others => opcode <= NOP;
    end case;
elsif timeout = '1' then
    opcode <= RESET;
end if;

5.5. CaseStructure - Case/When Handler

File: Repositories/CaseStructure.cs

Purpose: Manages VHDL case statements with multiple when branches.

Supported Syntax:

case selector is
    when "0000" => statements;
    when "0001" | "0010" => statements;  -- Multiple choices
    when others => statements;           -- Default branch
end case;

Features:

  • Multiple choice values per branch using | separator
  • when others => default/fallback branch
  • Nested control structures (if, case, loops) within branches
  • v0.13.0: Can be nested within if/elsif/else branches
  • 2026-05-27: each CaseWhenBranch records the 1-based source line of its when keyword (ConditionLine); the engine fires OnConditionEvaluated(line) when an arm matches so a breakpoint on a when arm pauses the live run (parallel to IfBranch.ConditionLine).

5.6. VariableSyntetizer - Declaration Parser

File: Services/VariableSyntetizer.cs

Purpose: Parses signal, variable, port, and generic declarations.

Handles:

  • Simple types: signal counter : integer := 0;
  • Vectors: signal data : std_logic_vector(7 downto 0);
  • Ports: port (clk : in std_logic; data : out std_logic_vector(7 downto 0));
  • Generics: generic (WIDTH : integer := 8);

v0.13.0 Critical Fix - Port Expression Skipping:

// For ports/generics with complex expressions, skip evaluation
if (_isPort || _isGeneric)
{
    // Skip: std_logic_vector((NUM_INPUTS * DATA_WIDTH) - 1 downto 0)
    int parenDepth = 1;
    while (parenDepth > 0) {
        if (current == "(") parenDepth++;
        else if (current == ")") parenDepth--;
        if (parenDepth > 0) moveNext();
    }
    VectorSize = 8; // Default for complex expressions
}

Why This Matters:

  • Port: inputs : in std_logic_vector((NUM_INPUTS * DATA_WIDTH) - 1 downto 0)
  • Expression uses generics NUM_INPUTS and DATA_WIDTH
  • Also uses function clog2(NUM_INPUTS) which may be defined later
  • Cannot evaluate during entity parsing - skip and use defaults

5.7. StandardFunctions - IEEE Library

File: Services/StandardFunctions.cs

Purpose: Implements IEEE std_logic_1164 and numeric_std functions.

Implemented Functions:

Category Functions
Edge Detection rising_edge(signal), falling_edge(signal)
Type Conversion to_integer(slv), to_unsigned(val, width), to_signed(val, width), conv_integer(slv), std_logic_vector(val, width)
Vector Operations resize(vec, width), shift_left(vec, amt), shift_right(vec, amt), rotate_left(vec, amt), rotate_right(vec, amt)
Arithmetic AddVectors(a, b), SubtractVectors(a, b)
Synthesis clog2(val) - ceiling of log base 2

v0.13.0 ToInteger() Enhancement:

public static int ToInteger(string slv)
{
    string cleaned = slv.Replace("\"", "").Replace("'", "").Trim();

    // v0.13.0: Safety checks for VHDL meta-values
    if (string.IsNullOrEmpty(cleaned)) return 0;
    if (cleaned.Contains('U') || cleaned.Contains('X') || cleaned.Contains('Z')) return 0;

    // Try decimal first (e.g., integer literals)
    if (int.TryParse(cleaned, out int dec)) return dec;

    // Try binary (e.g., std_logic_vector)
    try { return Convert.ToInt32(cleaned, 2); }
    catch { return 0; }
}

VHDL Meta-Values:

  • 'U' - Uninitialized
  • 'X' - Unknown/forcing
  • 'Z' - High impedance
  • 'W' - Weak unknown
  • '-' - Don't care

These are treated as 0 for simulation purposes.

5.8. SignalAttributeHandler - Attribute Tracker

File: Services/SignalAttributeHandler.cs

Purpose: Singleton service tracking signal state for attribute queries.

Supported Attributes:

  • signal'event - True if signal changed this cycle
  • signal'last_value - Previous value before change
  • signal'stable - True if signal has not changed
  • signal'active - True if signal had transaction

Usage in rising_edge():

public static bool RisingEdge(Variable signal)
{
    // Check if signal had an event
    if (!SignalAttributeHandler.Instance.GetEvent(signal))
        return false;

    // Check if current value is '1'
    string currentValue = signal.Value?.ToString()?.Replace("'", "") ?? "";
    return currentValue == "1";
}

5.9. Resources - FPGA Resource Estimator

File: Services/Resources.cs

Purpose: Estimates hardware resource usage for synthesized VHDL entities.

Features:

  • Attached to each Entity instance via entity.ResourceUsage property
  • Automatically calculated after architecture synthesis
  • Provides accurate flip-flop and I/O counts, good estimates for DSP/BRAM/LUTs

Properties:

public class Resources
{
    public int FlipFlops { get; }        // ~95% accuracy
    public int LUTs { get; }             // ~60-80% accuracy
    public int DSPSlices { get; }        // ~80-90% accuracy
    public double BRAMBlocks { get; }    // ~85% accuracy
    public int IOPins { get; }           // 100% exact
    public int ClockDomains { get; }     // 100% exact
    public HashSet<string> ClockSignals { get; }
}

Usage:

// Resources are automatically calculated when architecture is attached
entity.SetArchitecture(architecture);

// Access resource estimates
Console.WriteLine($"Flip-Flops: {entity.ResourceUsage.FlipFlops}");
Console.WriteLine($"I/O Pins: {entity.ResourceUsage.IOPins}");
Console.WriteLine($"Clocks: {string.Join(", ", entity.ResourceUsage.ClockSignals)}");

// Or print formatted report
entity.ResourceUsage.PrintReport("Xilinx 7-Series");

Calculation Methods:

  1. CalculateIOPins() - Sums port bit widths (100% accurate)
  2. CalculateClocks() - Detects clock signals from process sensitivity lists (100% accurate)
  3. CalculateFlipFlops() - Recursively scans clocked processes for assigned signals (~95% accurate)
  4. CalculateDSPSlices() - Counts multiplication operations (TODO: implementation pending)
  5. CalculateBRAM() - Detects array/memory patterns (TODO: implementation pending)
  6. CalculateLUTs() - Estimates combinational logic (TODO: implementation pending)

5.10. ProgressReporter - Progress Tracking System

File: Services/ProgressReporter.cs

Purpose: Event-based progress reporting for UI integration and debugging.

Features:

  • Singleton pattern for global access
  • Event-driven updates via ProgressChanged, ErrorOccurred, Completed events
  • Progress as tuple (float, string) for easy UI binding
  • ASCII progress bar visualization
  • Processing phase tracking
  • Scoped reporters for sub-operations

Processing Phases:

public enum ProcessingPhase
{
    Initializing, Tokenizing, ParsingEntity, ParsingPorts, ParsingGenerics,
    ParsingArchitecture, ParsingSignals, ParsingTypes, SynthesizingBehavior,
    ParsingProcess, ParsingIfStructure, ParsingCaseStructure, ParsingLoop,
    EvaluatingExpression, CalculatingResources, Completed, Error
}

Usage - Subscribe to Progress Updates:

// Subscribe to progress events
ProgressReporter.Instance.ProgressChanged += (sender, e) =>
{
    // Get progress as tuple (float, string)
    var (progress, message) = e.Update.ToTuple();

    // Or access individual properties
    float percent = e.Update.Progress;           // 0.0 to 1.0
    string status = e.Update.Message;            // "Parsing entity"
    string detail = e.Update.Detail;             // "AND_Gate"
    ProcessingPhase phase = e.Update.Phase;      // ProcessingPhase.ParsingEntity

    // Update your UI
    progressBar.Value = percent * 100;
    statusLabel.Text = $"{status} {detail}";
};

// Subscribe to errors
ProgressReporter.Instance.ErrorOccurred += (sender, e) =>
{
    ShowError(e.Update.Message, e.Update.Detail);
};

// Subscribe to completion
ProgressReporter.Instance.Completed += (sender, e) =>
{
    ShowSuccess("Parsing completed!");
};

Usage - Report Progress (from parsing code):

var progress = ProgressReporter.Instance;

// Report with phase-relative progress (0-1 within phase)
progress.ReportPhase(0.5f, "Parsing ports", ProcessingPhase.ParsingPorts, "clk");

// Report absolute progress
progress.Report(0.75f, "Almost done", ProcessingPhase.Completed);

// Report error
progress.ReportError("Failed to parse entity", "Unexpected token at line 42");

// Report completion
progress.ReportCompleted("Successfully parsed 5 entities");

Cancellation Support:

The progress reporter integrates with CancellationToken for graceful operation cancellation:

// Setup cancellation (typically in Program.cs)
var cts = new CancellationTokenSource();
var progress = ProgressReporter.Instance;

// Register CTS with progress reporter
progress.StartCancellableOperation(cts);

// Handle Ctrl+C for console applications
Console.CancelKeyPress += (sender, e) =>
{
    e.Cancel = true; // Prevent immediate termination
    progress.RequestCancellation();
};

// Subscribe to cancellation events
progress.CancellationRequested += (sender, e) =>
{
    Console.WriteLine($"Cancelled: {e.Update.Message}");
};

// Pass token to parsing methods
try
{
    entity.Syntetize(rawData, cts.Token);
    architecture.Syntetize(rawData, cts.Token);
}
catch (OperationCanceledException)
{
    Console.WriteLine("Operation was cancelled by user.");
}

Cancellation Methods:

  • StartCancellableOperation() - Creates new CancellationTokenSource
  • StartCancellableOperation(CancellationTokenSource) - Uses provided CTS
  • RequestCancellation() - Triggers cancellation and raises event
  • ThrowIfCancellationRequested() - Throws if cancelled (for use in loops)
  • CheckCancellation() - Returns bool without throwing
  • IsCancellationRequested - Property to check cancellation state
  • CancellationToken - Property to get the current token

Integrated Components: All major parsing components support CancellationToken:

  • Entity.Syntetize(string[], CancellationToken)
  • Architecture.Syntetize(string[], CancellationToken)
  • Process.Syntetize(CancellationToken)
  • VariableSyntetizer.Syntetize(CancellationToken)

Cancellation is checked at each loop iteration in parsing methods, ensuring responsive cancellation even for large VHDL files.

Progress Bar Visualization:

[████████████░░░░░░░░]  62.0% | Parsing process variables [counter]

Console Output with Icons (verbose mode):

📝 [█░░░░░░░░░░░░░░░░░░░]   5.0% | Tokenizing entity
📦 [████░░░░░░░░░░░░░░░░]  20.0% | Found entity [AND_Gate]
🔌 [█████░░░░░░░░░░░░░░░]  25.0% | Parsed 3 port(s)
🏗️ [██████░░░░░░░░░░░░░░]  33.0% | Found architecture [behavioral of AND_Gate]
📡 [███████░░░░░░░░░░░░░]  38.0% | Parsed 5 signal(s)
⚡ [██████████████░░░░░░]  73.0% | Synthesizing behavior
📊 [█████████████████░░░]  87.5% | Calculating resource usage
✅ [████████████████████] 100.0% | Processing completed

6. How to Use

Running the Test Suite

cd /mnt/ssd1tb/Codigos/Kmila-9s/Interpreter

# Standard output
dotnet run --framework net9.0

# Verbose mode with progress details
dotnet run --framework net9.0 -- -v
dotnet run --framework net9.0 -- --verbose

# Compact progress bar mode
dotnet run --framework net9.0 -- -p
dotnet run --framework net9.0 -- --progress

Standard Output:

Found 20 test file(s)
============================================================

[TEST] Processing: test1.vhdl
----------------------------------------
  Entities parsed: 1
    - TestEntity: 5 ports, 2 generics
      Architecture tasks: 3
  [PASS] test1.vhdl - Synthesized successfully!

...

Test Results: 16 passed, 4 failed, 20 total

Verbose Output (-v):

[TEST] Processing: test1.vhdl
----------------------------------------
    📝 [█░░░░░░░░░░░░░░░░░░░]   5.0% | Tokenizing entity
    📦 [████░░░░░░░░░░░░░░░░]  20.0% | Found entity [AND_Gate]
    🔌 [█████░░░░░░░░░░░░░░░]  25.0% | Parsed 3 port(s)
  Entities parsed: 1
    - AND_Gate: 3 ports, 0 generics
  [PASS] test1.vhdl - Synthesized successfully!

Programmatic Usage

using Interpreter.Services;
using Parser.Repositories;

// 1. Parse VHDL source
string vhdlCode = File.ReadAllText("design.vhdl");
var entities = ParseVhdlCode(vhdlCode);

// 2. Access parsed structure
foreach (var entity in entities)
{
    Console.WriteLine($"Entity: {entity.Name}");
    Console.WriteLine($"  Ports: {entity.Ports.Count}");
    Console.WriteLine($"  Generics: {entity.Generics.Count}");

    if (entity.Architecture != null)
    {
        Console.WriteLine($"  Signals: {entity.Architecture.Signals.Count}");
        Console.WriteLine($"  Tasks: {entity.Architecture.Tasks.Count}");
    }
}

// 3. Execute simulation (future implementation)
// await entity.Architecture.ExecuteAsync();

7. Testing

There are two test surfaces today:

7.1. xUnit regression suite (Interpreter/Tests/) — authoritative

Run with dotnet test. This is the surface to trust for regressions:

Test file Focus
DeltaCycleEngineTests Delta-cycle scheduling, signal updates, time advancement
SynthesisTests Behavioral → structural lowering (netlist correctness)
NestedIEEECallTests Nested IEEE function calls (e.g. to_integer(unsigned(...)))
IEEEPrimitivesTests IEEE-function → primitive-cell lowering table
IEEELibraryLoaderTests / IEEELoweringEndToEndTests IEEE library load + end-to-end lowering
LibraryCompilerTests Cross-module LibraryCompiler resolution/build
PortStrictnessTests Port direction / connectivity strictness
PracticeExerciseTests Practice-grader integration cases
VcdComparison VCD golden comparison (waveform equivalence)

The FastSimulationRunnerSimulationRunner byte-for-byte equivalence is covered by VCD-diff / equivalence tests (see the Fast mode notes in §2).

7.2. Console corpus (test*.vhdl, driven by Program.cs) — smoke/exploration

The corpus has grown to 54 test*.vhdl fixtures (numbered through test41, plus named variants). It is a smoke/exploration harness, not a pass/fail gate. Rough coverage of the first fixtures:

Repository hygiene: the module root also carries leftover scratch artifacts that are not part of the build or either test surfaceProgram.cs.1 (a stale copy of Program.cs), run_delta_tests.cs, and several loose golden/scratch VCDs (counter_sim.vcd, reference_*.vcd, test_*.vcd). They can be ignored/removed during cleanup; the authoritative VCD goldens used by tests live under Interpreter/Tests/.

Tests Coverage
test1-test7 Basic entities, signals, processes, simple control structures
test8-test10 Complex control flow with nested case statements
test11 Generate statements with component instantiation
test12-test15 Functions, procedures, signal attributes
test16 Port expressions with generic parameters
test17-test20 Advanced features (components, buses, GPIO controllers)
test21-test41 + variants Later fixtures added alongside engine/synthesis work (not individually catalogued here)
Historic snapshot (v0.15.0) — NOT re-measured since

The figures below are a v0.15.0 snapshot over the first 20 fixtures only and have not been re-measured; treat them as historical, not current. Re-run Program.cs to obtain a fresh number.

✓ Passing: 16/20 tests (80%)  [v0.15.0, first-20 corpus, historical]
✗ Failing: 4/20 tests (20%)

Passing tests: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 15, 17, 18, 19
Failing tests: 11, 13, 16, 20

Failure Analysis:

  • test11: Complex generate statements with clog2() function - causes parsing timeout (needs optimization)
  • test13: Multiple architectures with Booth algorithm case statements - causes parsing timeout
  • test16: Package constants like MAX_ROWS used in complex expressions - causes parsing timeout
  • test20: Complex SoC design with GPIO controllers - causes parsing timeout

Note: Tests 11, 13, 16, 20 involve advanced VHDL constructs that trigger deep parsing paths. The interpreter handles these gracefully (no crashes), but they timeout during complex structure parsing. Cancellation support (v0.15.0) allows users to abort long-running parses via Ctrl+C.

Debug Mode

Enable detailed debug output:

# Build in Debug configuration
dotnet build -c Debug
dotnet run -c Debug --framework net9.0

Debug Output Includes:

  • Token stream positions during parsing
  • Function/procedure skipping details ([SkipFunction] prefix)
  • Entity synthesis state
  • Variable syntetizer operations

8. Common Issues and Solutions

Issue 1: "Expected KEYWORDS, found IDENTIFIERS"

Symptom: Error during entity or architecture parsing

Example:

Error: Expected type: KEYWORDS, found: IDENTIFIERS -> ComponentName at Line: 1:4

Common Causes:

  1. Component instantiation not properly skipped
  2. Labeled statement not recognized
  3. Function/procedure end mistaken for architecture end

Solution (v0.13.0):

  • Added labeled component detection: if (label : IDENTIFIER)
  • Added SkipComponentInstantiation() method
  • Added funcProcDepth tracking in Program.cs

Issue 2: "Instance not in this scope"

Symptom: Variable/signal not found during expression evaluation

Example:

Error: Instance not in this scope: 'downto' in expression 'signal 7 downto 0'
Error: Instance not in this scope: 'ELEMENT_WIDTH' in expression 'ELEMENT_WIDTH - 1'

Common Causes:

  1. Keywords (downto, to, when) treated as identifiers
  2. Generic used in port expression before generics evaluated
  3. Local variable declared but scope check fails

Solutions (v0.13.0):

  • Skip downto/to/when keywords in Operation.Syntetize()
  • Skip evaluation of port expressions with generics in VariableSyntetizer
  • Removed erroneous scope check preventing local variable declarations

Issue 3: "Unable to cast object to Int32"

Symptom: ShuntingYard fails during precedence comparison

Example:

Error: Unable to cast object of type 'System.Object' to type 'System.Int32'
at ShuntingYard.OrderPrecedenceList() line 47

Cause: Operator.Precedence is object type, direct (int) cast fails for enum values

Solution (v0.13.0):

// WRONG
(int)_operators.Peek().Precedence

// CORRECT
Convert.ToInt32(_operators.Peek().Precedence)

Issue 4: Power Operator (**) Treated as NOT

Symptom: Expression like 2 ** 8 evaluates incorrectly

Example:

Expected: 256 (2^8)
Actual: Logic NOT applied to 2

Cause:

  • PrecedencesArithOperators.UNARY = 0 (for ** operator)
  • PrecedencesLogicOperators.NOT = 0 (for not operator)
  • Without type check, both match when cast to int

Solution (v0.13.0):

// Added type check before applying unary NOT logic
bool isUnaryNot = op.OperatorType == LOGIC_OPERATION &&
                  op.Precedence is PrecedencesLogicOperators &&
                  (PrecedencesLogicOperators)op.Precedence == NOT;

Issue 5: Identifiers Matching "or"/"and" Operators

Symptom: Variables like "memory" or "factor" treated as operators

Example:

Error: "memoryDirection1" classified as operator (contains "or")
Error: "std_logic_vector" classified as operator (contains "or")

Cause: Unanchored regex pattern matches substrings

Solution (v0.13.0):

// WRONG
LOGIC_OPERATORS = "(and|or|xor|nand|nor|not)"

// CORRECT - Anchored with ^ and $
LOGIC_OPERATORS = @"^(and|or|xor|nand|nor|not|xnor|abs)$"

9. Developer Guide

9.1. Adding a New Standard Function

Step 1: Add to function registry

public static bool IsStandardFunction(string name)
{
    return name.ToLower() switch
    {
        "my_new_function" => true,
        // ... existing functions
        _ => false
    };
}

Step 2: Implement the function

/// <summary>
/// Description of what the function does.
/// </summary>
/// <param name="arg1">First argument</param>
/// <param name="arg2">Second argument</param>
/// <returns>Result value</returns>
public static int MyNewFunction(int arg1, string arg2)
{
    // Implementation
    return result;
}

Step 3: Add to dispatcher

public static object EvaluateFunction(string functionName, object[] arguments, HashSet<Variable> variables)
{
    switch (functionName.ToLower())
    {
        case "my_new_function":
            if (arguments.Length >= 2)
            {
                int val1 = GetIntValue(arguments[0], variables);
                string val2 = GetStringValue(arguments[1], variables);
                return MyNewFunction(val1, val2);
            }
            return 0;
        // ... existing cases
    }
}

9.2. Adding a New Control Structure

Step 1: Create ISControl implementation

public class MyControlStructure : ISControl
{
    private readonly HashSet<Variable> _variables;
    private readonly TokenStream _streamer;

    public List<object> Tasks { get; private set; } = new();

    public MyControlStructure(HashSet<Variable> variables, TokenStream streamer)
    {
        _variables = variables;
        _streamer = streamer;
    }

    public void Syntetize()
    {
        _streamer.Expect(KEYWORDS); // consume structure keyword
        // Parse structure-specific syntax
    }

    public async Task ExecuteAsync()
    {
        // Execute the structure's tasks
        Executer executer = new(Tasks);
        await executer.ExecuteAsync();
    }

    public void Syntetize(string[] data) => throw new NotImplementedException();
}

Step 2: Add to Architecture and/or Process

// In Architecture.SyntetizeBehaviour() or Process.SyntetizeBehaviour()
case "mystructure":
    MyControlStructure myStruct = new(comparators.ToHashSet(), _streamer);
    myStruct.Syntetize();
    Tasks.Add(myStruct);
    // Consume 'end mystructure;' if applicable
    break;

9.3. Debugging Techniques

1. Token Stream Debugging:

#if DEBUG
Console.WriteLine($"[ComponentName] Current: '{_streamer.Current?.Value}' " +
                  $"Type: {_streamer.Current?.Type} " +
                  $"Line: {_streamer.Current?.Line}");
#endif

2. Expression Debugging:

// In Operation.cs, enable DEBUG_OPERATION
#define DEBUG_OPERATION

// Shows:
// - Raw expression string
// - Tokenized data array
// - Available variables
// - Syntetized items before ShuntingYard

3. Parenthesis Depth Tracking:

int depth = 0;
while (_streamer.Current != null)
{
    if (current == "(") {
        depth++;
        Console.WriteLine($"[DEBUG] Open paren, depth now {depth}");
    }
    else if (current == ")") {
        depth--;
        Console.WriteLine($"[DEBUG] Close paren, depth now {depth}");
    }
}

4. Common Debugging Checkpoints:

  • After Expect() calls: Verify Current token advanced correctly
  • Before while loops: Verify exit condition will eventually be met
  • After synthesis methods: Verify token stream at expected position
  • In switch statements: Add default case with debug output

9.4. Best Practices

1. Regex Patterns:

  • Always anchor: Use ^ and $ for exact matches
  • Use raw strings: Prefix with @ for verbatim strings
  • Test edge cases: Empty strings, single characters, similar words

2. Token Stream Usage:

  • Use Expect(): For tokens that must be present
  • Use Match(): For optional tokens (returns bool, advances if matched)
  • Set rollback points: Before speculative parsing with SetRollBack()
  • Track depth: For nested structures (parentheses, begin/end pairs)

3. Error Handling:

  • Fail fast: Throw meaningful exceptions early
  • Provide context: Include token value, line number in error messages
  • Add safety limits: Maximum iterations to prevent infinite loops

4. Testing:

  • Minimal test cases: Start with simplest code that reproduces issue
  • Incremental fixes: Fix one issue at a time, verify tests pass
  • Regression testing: Ensure fixes don't break previously passing tests

10. Changelog

Documentation re-sync (2026-08-03, branch v1.2.0)

  • Updated the header stamp to the current branch (v1.2.0) and date. Re-checked the §3 structure lists against the on-disk tree and closed the drift the synthesis work had opened: added ComponentSynthesizer, IEEEPrimitives, and SelectionFilter to the Interpreter.Synthesis namespace listing; added the ReportStatement / AssertStatement / WaitForStatement control-statement files (all ISControl) to the class hierarchy; and added the newer Services/ entries (FunctionRegistry, PackageSubtypeScanner, SourceElaborator, ReportBus, ParseTrace, SkipDiagnosticLog, IEEELibraryLoader) to the services listing. No source changes — documentation only; the detailed feature narratives below (Ring 5 report/assert, v1.15–v1.19 IEEE lowering, practica-hardening) were already current.

v1.14.1 - Documentation Re-alignment (2026-05-01)

Documentation Synchronisation

  • §2 Current Status block: Replaced the v0.15.0 snapshot (which still claimed an 80 % / 16-of-20 pass rate as if it were live) with a feature inventory that reflects the actual surface today: full delta-cycle scheduler, Synthesis/ namespace, SimulationRunner façade, custom-type infrastructure, FPGA resource estimation, progress + cancellation pipeline. The historical pass-rate number is preserved in this changelog but no longer fronted as the current status.
  • §3 Class Hierarchy diagram: Added the entire Interpreter.Synthesis namespace (Synthesizer, SynthesisContext, ConcurrentAssignSynthesizer, ConditionalAssignSynthesizer, SelectedAssignSynthesizer, ProcessSynthesizer, Models.cs records / enums, Layout/LayeredLayout), plus the new SimulationRunner façade and the SimulationConfig / ClockConfig / StimulusEvent model trio. Marked Services/DataStructure.cs explicitly as a placeholder so its presence in the file tree does not mislead.
  • §3 Key Components Summary: Added matching one-liners for SimulationRunner, SimulationConfig (+ aux types), all six synthesizers, SynthesisContext, Synthesis/Models.cs, and LayeredLayout. Noted that SimulationRunner is the entry point used by Kmila.Shared.Services.SimulationCoordinator.
  • Constansts.cs: Documented the historical filename typo so future readers don't try to "fix" it and break references.
  • No source-level changes in this release; the public Interpreter surface from v1.14.0 is unchanged.

v0.15.1 - Comprehensive Code Documentation (2026-02-21)

Documentation Enhancements

  • Interfaces: Added XML documentation to all 7 interface files (IArithOperation, IControl, IFinder, ILogicOperation, IOperation, ISControl, ISYard)
  • Models: Added XML documentation to all 8 model files (TypeDefinition, Constansts, ControlExceptions, ParameterDefinition, FunctionDefinition, ProcedureDefinition, SimulationConfig, DeferredFunction)
  • Repositories: Added XML documentation to all 17 repository files including Executer, ShuntingYard, all control structures, and statement handlers
  • Services: Added XML documentation to all 21 service files including DeltaCycleEngine, SimulationRunner, Architecture, Entity, Process, and all registries/handlers
  • Tests: Added XML documentation to VcdComparison, DeltaCycleEngineTests, and run_delta_tests

v0.15.0 - Documentation & Stability Release (2026-01-17)

Documentation Improvements

  • README Updates:

    • Updated version to 0.15.0 and date to January 17, 2026
    • Revised Current Status section to reflect 80% test pass rate
    • Updated test results section with current passing/failing tests
    • Added note about cancellation support for long-running parses
  • Code Documentation Enhancements:

    • Enhanced XML documentation across Repository classes
    • Added comprehensive class-level remarks with VHDL syntax examples
    • Documented v0.13.0+ improvements in control structure classes

Stability & Maintenance

  • Version Alignment: Branch version v0.15.0 now matches documentation
  • Documentation Consistency: All changelog entries properly dated
  • Test Coverage Documentation: Updated to reflect current 16/20 (80%) pass rate

v0.16.1 - Diagram Updates (2025-12-13)

Diagram and Documentation Updates

  • Class Hierarchy: Updated to include all Repository classes:

    • Added Executer under ISControl interface implementations
    • Added IFinder interface with LiteralsFinder implementation
    • Added Utility Classes section with OperationDetector and ShuntingYard
  • Key Components Summary: Added missing component descriptions:

    • Executer.cs - Task orchestration with pause/resume/cancel support
    • LiteralsFinder.cs - Literal value identification and classification
    • OperationDetector.cs - Operation type detection from raw input

v0.16.0 - Documentation Enhancement Release (2025-12-13)

Documentation Improvements

  • DataStructure.cs: Added comprehensive XML documentation explaining:

    • The class's role as a placeholder for future VHDL data structure operations
    • Potential use cases (record types, array aggregates, custom type operations)
    • Current status and relationships to other classes
    • Cross-references to related classes (TypeDefinition, TypeRegistry, VariableSyntetizer)
  • README Updates:

    • Updated version number and last updated date
    • Added this changelog entry documenting the documentation improvements
    • Version history properly maintained

Component Interaction Documentation

The following interactions are now better documented in the codebase:

Component Interacts With Purpose
DataStructure TypeDefinition, TypeRegistry Reserved for complex data structure operations
TypeRegistry TypeDefinition, VariableSyntetizer Stores and retrieves custom VHDL type definitions
TypeDefinition TypeRegistry Represents enumeration, array, record, and subtype definitions
VariableSyntetizer All type-related classes Parses signal/variable declarations with custom types

Design Notes

This release focuses on improving code maintainability through better documentation. The DataStructure class has been explicitly documented as a placeholder to prevent confusion about its empty implementation. Future versions may implement this class when advanced data structure operations (record field access, array manipulation) are needed beyond what existing classes provide.


v0.13.0 - Regex Fixes, Resource Estimation, Custom Types, Cancellation & Memory Fixes (2025-12-09)

New Features

  • Hardware Resource Estimation (Services/Resources.cs): NEW module for FPGA resource usage estimation

    • Flip-flop counting from clocked processes (~95% accuracy)
    • I/O pin counting from entity ports (100% exact)
    • Clock domain detection from rising_edge/falling_edge usage (100% exact)
    • Framework for DSP, BRAM, LUT estimation (implementation in progress)
    • Automatic calculation when architecture is attached to entity
    • Example output: Resources: FF=8, LUT=0, DSP=0, BRAM=0.0, IO=18, CLK=1
  • Custom Type Support (Models/TypeDefinition.cs, Services/TypeRegistry.cs): NEW infrastructure for VHDL custom type declarations

    • TypeDefinition model supporting Enumeration, Array, Record, and Subtype kinds
    • TypeRegistry for storing and looking up custom types within architecture scope
    • Full parsing of type declarations in VariableSyntetizer.ParseTypeDeclaration()
    • Supported syntax:
      • Enumeration: type state_t is (IDLE, RUN, STOP);
      • Array: type memory_t is array (0 to 255) of std_logic;
      • Record: type packet_t is record valid: std_logic; end record;
    • Automatic size calculation and default value generation
    • Signals using custom types are now properly sized and initialized
  • Progress Reporting System (Services/ProgressReporter.cs): NEW event-based progress tracking for UI integration

    • Singleton ProgressReporter.Instance for global access
    • Events: ProgressChanged, ErrorOccurred, Completed, CancellationRequested
    • Progress tuple output: (float Progress, string Message) via e.Update.ToTuple()
    • ASCII progress bar visualization with emoji icons
    • Processing phases: Tokenizing, ParsingEntity, ParsingPorts, ParsingArchitecture, etc.
    • Scoped reporters for sub-operations (CreateScope(), CreateItemScope())
    • Command line options: -v/--verbose for detailed progress, -p/--progress for compact bar
    • Integrated into Entity, Architecture, and Process parsing
  • CancellationToken Support: Graceful operation cancellation throughout the parsing pipeline

    • ProgressReporter methods: StartCancellableOperation(), RequestCancellation(), ThrowIfCancellationRequested(), CheckCancellation()
    • CancellationRequested event for notification when cancellation is triggered
    • Ctrl+C handling in Program.cs for console application cancellation
    • CancellationToken parameter added to all major parsing methods:
      • Entity.Syntetize(string[], CancellationToken)
      • Architecture.Syntetize(string[], CancellationToken)
      • Process.Syntetize(CancellationToken)
      • VariableSyntetizer.Syntetize(CancellationToken)
    • Cancellation checks at each loop iteration for responsive cancellation
    • Graceful OperationCanceledException handling prevents crashes
    • Useful for stopping processing of large VHDL files (e.g., FPGA_KILLER)
  • Shift/Rotate Operator Support (Services/Operator.cs): Full implementation of VHDL shift and rotate operators

    • sll - Shift Left Logical (fills with '0')
    • srl - Shift Right Logical (fills with '0')
    • sla - Shift Left Arithmetic (fills with rightmost bit)
    • sra - Shift Right Arithmetic (fills with sign bit)
    • rol - Rotate Left
    • ror - Rotate Right
    • Added to PrecedencesLogicOperators enum in Models/Constansts.cs
    • Added precedence definitions in Operator.DefinePrecedence()
    • Implemented SolveShiftOperation() for vector operations
  • Constant Declaration Handling (Services/VariableSyntetizer.cs): Improved constant parsing

    • HasFunctionCallInConstant() - lookahead detection for function calls in constant initialization
    • SkipConstantDeclaration() - safely skips constants with function call initializations
    • Simple constants (e.g., constant X := "0101";) continue to be parsed normally
    • Complex constants with function calls (e.g., constant Y := clog2(N);) are skipped to prevent parse errors
    • Test19 now passes (was failing on constant with aggregate initialization)

Memory Leak Fixes

  • Fixed infinite loop in SkipTypeDeclaration() (Services/VariableSyntetizer.cs): Array type declarations like type big_mem_t is array (0 to 199999) of STD_LOGIC; were causing infinite loops and memory exhaustion. Added:

    • parenDepth tracking for array bounds
    • MAX_ITERATIONS = 10000 safety limit
    • Only break on ; when parenDepth == 0
  • Fixed LoggerFactory memory leak (Parser/Repositories/TokenStream.cs): Each TokenStream instance was creating a new LoggerFactory. Changed to static shared instance.

  • Fixed Regex allocation in Operation class (Services/Operation.cs): Each Operation instance was creating 4 new Regex objects. Changed to static shared compiled instances.

  • Fixed list allocation in loop (Services/Architecture.cs): List<Variable> comparators was being allocated on every iteration of SyntetizeBehaviour() loop. Moved outside loop and changed to HashSet<Variable>.

Critical Bug Fixes

  • Fixed Variable.Type setter (Parser/Models/Variable.cs): Removed premature _value = "U" initialization for STD_LOGIC_VECTOR that was causing Size setter to miscalculate vector widths (Size was being set to 1 instead of actual bit width like 8)

  • Fixed Variable.Size setter (Parser/Models/Variable.cs): Changed if (Value == null) to if (_value == null) to avoid Value getter returning new() during object initialization

  • Fixed STD_LOGIC port sizing (Services/VariableSyntetizer.cs): Added default VectorSize = 1 for STD_LOGIC and BIT types (single-bit signals were showing Size=0)

  • Fixed architecture end detection (Program.cs): Now checks both architecture name AND entity name in end statements (supports both end behavioral; and end example;)

  • Fixed property initialization order (Services/VariableSyntetizer.cs): Set Size before Value in Variable object initializer to prevent Value setter from interfering with Size calculation

Parser Bug Fixes

  • Fixed operator regex patterns (Parser/Models/Constants.cs):

    • ASSIGNATION_OPERATORS: Changed from @"^[(\<\=)|\:\=]" to @"^((\<\=)|\:\=)$" - Fixed bracket creating character class that included (
    • COMPARATION_OPERATORS: Changed from @"^[=|<|>|!=]" to @"^(=|!=|/=|<|>|<=|>=)$" - Fixed char class and completed operator set
    • LOGIC_OPERATORS: Changed from "(and|or|xor|nand|nor|not)" to @"^(and|or|xor|nand|nor|not|xnor|abs)$" - Added anchors to prevent partial matches
    • ARITH_OPERATORS: Changed from @"(\+|\-|\*\*|\*|\/|mod)" to @"^(\+|\-|\*\*|\*|\/|mod)$" - Added anchors
  • Fixed unary NOT operator detection (Services/Operation.cs): Added proper type check to ensure ** (power) operator with PrecedencesArithOperators.UNARY is not treated as PrecedencesLogicOperators.NOT

  • Fixed ShuntingYard precedence casting (Repositories/ShuntingYard.cs:47): Changed from direct cast (int)operation.Precedence to Convert.ToInt32() to handle enum-to-int conversion when Precedence is stored as object

  • Added labeled process detection (Services/Architecture.cs): Added support for label : process(...) syntax where the label comes before the process keyword

  • Fixed variable scope check (Services/VariableSyntetizer.cs): Removed erroneous check that prevented local variable declarations in processes

  • Added keyword handling in expressions (Services/Operation.cs): Added explicit handling to skip downto, to, when, and else keywords during expression synthesis

  • Fixed function/procedure parsing (Program.cs): Added funcProcDepth tracking to prevent end FunctionName; from being mistaken for architecture end

  • Improved ToInteger safety (Services/StandardFunctions.cs): Added checks for empty, 'U', 'X', 'Z' values and proper error handling

  • Fixed bus keyword handling (Services/VariableSyntetizer.cs): Added support for guarded signal declarations with bus keyword (e.g., signal x : std_logic_vector bus := ...;)

  • Fixed custom type signal declarations (Services/VariableSyntetizer.cs): Custom types (e.g., reg_array_t) are now properly recognized as IDENTIFIERS rather than KEYWORDS during signal declaration parsing

  • Fixed VHDL null statement handling (Repositories/CaseStructure.cs): Added explicit handling for null; no-operation statements in case branches

  • Fixed undeclared identifier handling (Services/Operation.cs): Undeclared identifiers now create stub variables instead of throwing exceptions, allowing parsing to continue gracefully

  • Fixed type mismatch handling (Services/Operator.cs): Operations involving stub variables (undeclared identifiers) now skip strict type checking, returning default values

  • Fixed for-loop range parsing (Repositories/ForLoopStructure.cs): ParseRangeValue() now handles expressions like TREE_DEPTH - 1, not just simple integers

  • Fixed Variable null-safety (Parser/Models/Variable.cs): Added null checks to all operator overloads to prevent NullReferenceException

  • Added iteration guards: Added maximum iteration limits to prevent infinite loops in:

    • Process.SyntetizeBehaviour() - 10000 iterations
    • Operation.Syntetize() - 5000 iterations
    • IfStructure.SyntetizeBranchBody() - 5000 iterations
    • ForLoopStructure.SyntetizeLoopBody() - 5000 iterations
    • Various assignment parsing methods - 1000 iterations

New Features

  • Case statement support in If branches (Repositories/IfStructure.cs): Added case handling in SyntetizeBranchBody() - case statements can now be nested inside if/elsif/else branches

  • Component instantiation skipping (Services/Architecture.cs): Added SkipComponentInstantiation() method to handle label : ComponentName [generic map (...)] port map (...); syntax

  • Port/Generic expression skipping (Services/VariableSyntetizer.cs): Added special handling for ports and generics with complex expressions (e.g., (NUM_INPUTS * DATA_WIDTH) - 1) - skips evaluation and uses default size

Test Results

  • Passing Tests: 13/20 base tests (65%) + resource estimation demo
  • Newly passing: test8, test9, test10, test12, test99_example (resource demo)
  • Regressions: test13, test14, test16, test19 (new errors from expression parsing changes - requires additional fixes)
  • Remaining failures: test11, test17, test18, test20 (entity token positioning issues)

Note: Test count decreased from 16→13 due to stricter vector expression parsing revealing edge cases with shift operators and complex expressions that were previously parsed incorrectly.

Documentation

  • Comprehensive inline code documentation with v0.13.0 annotations
  • Enhanced README with design decisions, testing guide, and troubleshooting
  • All documentation consolidated into single README for better maintainability
  • Added Known Limitations section with specific error message explanations
  • Documented unsupported VHDL constructs: custom types, aggregates, generate blocks, signal range attributes

Previous Versions

v0.12.0 - If Structure Implementation (2025-11-26)

Features:

  • Complete if-elsif-else support with multiple branches
  • IfBranch helper class for branch representation
  • Nested if statements within any branch

Bug Fixes:

  • Fixed Architecture.SyntetizeAssingations() termination condition
  • Fixed architecture end detection in Program.cs
  • Added ConsumeEndIf() method for proper token consumption

Test Results: 7/20 passing (35%)


Dependencies

Required Modules

  • Parser Module (/mnt/ssd1tb/Codigos/Kmila-9s/Parser/) - Provides tokenization, entity/architecture models, and constants
  • .NET 9.0 - Target framework with C# 13 language features

Optional Dependencies

  • TimeMachine.dll - For time-based simulation and clock cycle management

Known Limitations

The following VHDL features require additional development:

Parser-Level:

  • Signal attributes with 'range - for i in inputs'range loop
  • Array slicing with complex expressions

Fully Supported (Parser + Interpreter):

  • VHDL aggregates - (others => '0'), (others => '1') ✓ Supported
  • Custom type declarations - Enumeration, array, record, and subtype types ✓ Supported via TypeDefinition, TypeRegistry
  • Time literal support - fs, ps, ns, us, ms, sec, min, hr ✓ Parser supported

Interpreter-Level (current module):

  • Generate statement execution - for-generate and if-generate are parsed but may timeout on complex designs
  • Complete structural hierarchy simulation - component instantiation is detected but not fully simulated
  • Bus resolution functions - affects test18
  • DSP/BRAM/LUT estimation - framework exists in Resources.cs but implementation pending

Error Messages

"Expected type: KEYWORDS, found: -> at Line:"

  • This error typically occurs with unsupported VHDL constructs:
    • Custom type declarations (type memory_t is array...)
    • Generate blocks (for i in 0 to N generate)
    • Aggregates ((others => '0'))
    • Signal range attributes (signal'range)
  • Workaround: Simplify VHDL code to use supported constructs or wait for future parser updates

Future Roadmap

Next Release (Planned)

  • Fix remaining 4 test failures (test11, test13, test16, test20)
  • Optimize deep parsing paths that cause timeouts
  • Improve error messages with better context
  • Full generate statement execution support

v1.0.0 (Long-term)

  • Complete VHDL-93 coverage
  • Partial VHDL-2008 support
  • Interactive debugger integration
  • Full structural hierarchy simulation
  • Complete FPGA resource estimation (DSP, BRAM, LUT implementations)

Audit diagrams (2026-05-09)

The Interpreter README previously had no Mermaid diagrams (only ASCII trees). The audit identified that several behaviors of the simulation engine were either misrepresented in TT1 (fig_5_5_10, fig_4_5) or undocumented entirely. Sources also live in Documentacion/TT1/diagrams/.

fig_5_5_10_clases_motor_simulacion — Engine class diagram (rewritten)

classDiagram
    direction TB

    class SimulationRunner {
        +DeltaCycleEngine Engine
        +bool IsRunning
        +bool IsPaused
        +RunAsync(SimulationConfig) Task
        +Pause() void
        +Resume() void
        +Stop() void
        +OnProgressChanged EventHandler
        +OnSimulationEnded EventHandler
        +OnError EventHandler
    }

    class DeltaCycleEngine {
        -int _currentTick
        -int _currentDelta
        -int MAX_DELTA_CYCLES
        +SignalHistory History
        +Initialize(signals, tasks) void
        +RunTimeStepAsync() Task~bool~
        +RunDeltaCycleAsync() Task~bool~
        +AdvanceTime(tick) void
        +ConnectToTimeMachine(timer) void
    }

    class ProcessScheduler {
        -Dictionary _sensitivityMap
        -List _allProcesses
        -Queue _pending
        +RegisterProcess(p) void
        +InitializeAllProcesses() void
        +NotifySignalChanges(changed) void
        +GetPendingProcesses() IEnumerable
    }

    class SignalScheduler {
        -Dictionary _currentDeltaUpdates
        -Dictionary _futureUpdates
        +Schedule(signal, value) void
        +ScheduleAfter(signal, value, ticks) void
        +ApplyCurrentUpdates() ISet~Variable~
        +AdvanceTime(tick) void
    }

    class SignalHistory {
        +RegisterSignal(signal, time, record) void
        +RecordChanges(signals, time) void
        +ExportToVcd(module, ns) string
        +SaveToVcd(path, module, ns) void
        +ExportToCsv() string
    }

    class SignalAttributeHandler {
        <<singleton>>
        +RegisterSignal(signal) void
        +UpdateAllPreviousValues() void
        +HasEvent(signal) bool
        +LastValue(signal) object
    }

    class ProcessSynthesizer {
        +Synthesize(entity) Netlist
    }

    class SimulationConfig {
        +Entity Entity
        +int TotalTicks
        +List Clocks
        +List Stimuli
    }

    SimulationRunner "1" *-- "1" DeltaCycleEngine : compone
    DeltaCycleEngine "1" *-- "1" SignalHistory : registra en
    DeltaCycleEngine "1" *-- "1" ProcessScheduler : delega procesos
    DeltaCycleEngine "1" *-- "1" SignalScheduler : delega senales
    DeltaCycleEngine ..> SignalAttributeHandler : consulta atributos
    SimulationRunner ..> SimulationConfig : recibe
    ProcessSynthesizer ..> SignalHistory : independiente

fig_5_5_11_clases_planificadores — Schedulers detail (new)

classDiagram
    direction TB

    class ProcessScheduler {
        -Dictionary~Variable, List~Process~~ _sensitivityMap
        -List~Process~ _allProcesses
        -HashSet~Process~ _pending
        +RegisterProcess(p: Process) void
        +InitializeAllProcesses() void
        +NotifySignalChanges(changed: ISet) void
        +GetPendingProcesses() IEnumerable~Process~
        +HasPendingProcesses bool
    }

    class SignalScheduler {
        -Dictionary~Variable, object~ _currentDeltaUpdates
        -SortedDictionary~int, Dictionary~ _futureUpdates
        +Schedule(signal, value) void
        +ScheduleAfter(signal, value, ticks) void
        +ApplyCurrentUpdates() ISet~Variable~
        +AdvanceTime(tick) void
        +HasPendingUpdates bool
    }

    class ScheduledOperation {
        +IOperation Source
        +Execute() void
    }

    class Process {
        +List~Variable~ SensitiveList
        +List~Variable~ SignalsPorts
        +List~ITask~ Tasks
        +SyntentizeSensitiveList(tokens) void
    }

    class Variable {
        +string Name
        +DATATYPES Type
        +object Value
        +event OnValueChange
    }

    ProcessScheduler "1" o-- "*" Process : registra
    ProcessScheduler "1" --> "*" Variable : indexa por sensibilidad
    SignalScheduler "1" --> "*" Variable : programa updates
    SignalScheduler "1" *-- "*" ScheduledOperation : cachea
    Process "1" o-- "*" Variable : SensitiveList

fig_5_8_4_seq_resolucion_sensitivity — Sensitivity-list resolution (new)

sequenceDiagram
    participant DCE as DeltaCycleEngine
    participant P as Process
    participant SP as SignalsPorts (scope)
    participant PS as ProcessScheduler
    participant SAH as SignalAttributeHandler

    Note over DCE: Elaboracion (Initialize)
    DCE->>P: VariableSyntetizer (declaraciones)
    DCE->>P: SyntentizeSensitiveList(rawTokens)
    loop Por cada nombre en lista de sensibilidad
        P->>SP: Buscar Variable por nombre
        SP-->>P: Variable referencia (o stub si no existe)
        P->>P: Agregar a SensitiveList
    end
    DCE->>SAH: RegisterSignal por cada senal
    DCE->>PS: RegisterProcess(P)
    PS->>PS: Por cada signal en P.SensitiveList:<br/>_sensitivityMap[signal].Add(P)

    Note over DCE: Tiempo t=0
    DCE->>PS: InitializeAllProcesses
    PS-->>DCE: Todos los Process en pendientes
    DCE->>P: ExecuteProcessWithSchedulingAsync
    P-->>DCE: Cambios de senal (en SignalScheduler)

    Note over DCE: Cambio de senal mas tarde
    DCE->>PS: NotifySignalChanges(changedSignals)
    PS->>PS: Para cada signal:<br/>marcar Process en _sensitivityMap[signal]<br/>como pending para siguiente delta

fig_5_11_10_state_delta_cycle — Delta-cycle state machine (new)

stateDiagram-v2
    [*] --> Idle
    Idle --> Initializing : RunAsync(config)
    Initializing --> TimeStep : Initialize completo
    TimeStep --> DeltaCycle : InitializeAllProcesses (t=0)<br/>or procesos pendientes

    state DeltaCycle {
        [*] --> ExecPending
        ExecPending --> ExecConcurrent : pending procesados
        ExecConcurrent --> ApplyUpdates : asignaciones encoladas
        ApplyUpdates --> RecordHistory : SignalScheduler.ApplyCurrentUpdates
        RecordHistory --> NotifyScheduler : SignalHistory.RecordChanges
        NotifyScheduler --> [*] : ProcessScheduler.NotifySignalChanges
    }

    DeltaCycle --> DeltaCycle : HasPendingProcesses<br/>or HasPendingUpdates<br/>(_currentDelta < MAX)
    DeltaCycle --> Aborted : _currentDelta > MAX_DELTA_CYCLES<br/>(bucle combinacional)
    DeltaCycle --> AdvanceTick : Estable (sin pendientes)
    AdvanceTick --> TimeStep : tick < TotalTicks
    AdvanceTick --> Done : tick = TotalTicks
    Done --> [*] : OnSimulationEnded
    Aborted --> [*] : OnError

    Idle --> Paused : OnPaused(true) (TimeMachine)
    Paused --> Idle : OnPaused(false)

fig_5_10_1_seq_vcd_id_alloc — VCD identifier allocation (new)

sequenceDiagram
    participant SR as SimulationRunner
    participant SH as SignalHistory
    participant Map as signalIds (Dict)
    participant FS as Filesystem

    SR->>SH: ExportToVcd(moduleName, timescaleNs)
    SH->>SH: Escribir $date / $version / $timescale
    SH->>Map: idCounter = 0
    loop Por cada Senal ordenada por nombre
        SH->>SH: GenerateVcdId(idCounter)
        Note right of SH: ASCII 33..126 con<br/>wraparound: !, ", #, ...,<br/>~, !!, "!, ...
        SH->>Map: signalIds[signal] = id
        SH->>SH: Emitir $var wire/reg width id name $end
        SH->>SH: idCounter++
    end
    SH->>SH: Escribir $dumpvars (valor inicial por senal)
    loop Por cada tiempo unico en el historial
        SH->>SH: Emitir #tiempo (en timescaleNs)
        loop Senales con cambio en este tiempo
            SH->>Map: Lookup id por senal
            Map-->>SH: id
            SH->>SH: Emitir nuevoValor + id
        end
    end
    SH-->>SR: string VCD
    SR->>FS: SaveToVcd(path) escribe string

fig_4_5_dominio_simulacion — Simulation domain (rewritten)

classDiagram
    direction TB

    class ConfigSimulacion {
        +Int totalTicks
        +Int timescaleNs
        +List clocks
        +List stimuli
    }
    class DispositivoFPGA {
        +String familia
        +String fabricante
        +Int celdasLogicas
    }
    class Senal {
        +String nombre
        +Tipo tipo
        +Int ancho
    }
    class HistorialDeSenales {
        +Map~Senal, List~ transiciones
        +Int tickActual
    }
    class FormaDeOnda {
        +List valoresTemporales
    }
    class VisualizadorDeOndas {
        +List senalesMostradas
        +Int zoom
    }
    class ArchivoVCD {
        +String contenido
        +Int timescaleNs
    }

    ConfigSimulacion "1" --> "0..1" DispositivoFPGA : se calcula sobre
    ConfigSimulacion "1" --> "0..*" Senal : estimula
    HistorialDeSenales "1" --> "0..*" Senal : muestrea
    Senal "1" --> "1" FormaDeOnda : produce
    VisualizadorDeOndas "1" ..> "0..*" FormaDeOnda : presenta
    HistorialDeSenales "1" --> "1" ArchivoVCD : exporta a

Changelog

Ring 5 sweep observability — report / procedure / to_string (2026-07-18)

Follow-on session that made VHDL testbench reports actually execute and their case-marker sweep readable via /api/v1/simulate response log[]. Every fix is additive: default paths unchanged, 121/121 SimulatorBench byte-identical.

  • F-G-11 — VHDL report statements execute + surface via API log. New Interpreter/Services/ReportBus.cs (singleton event sink); Interpreter/Repositories/ReportStatement.cs implements ISControl with parse-time expression capture (tracking insideString so a ; inside a string literal doesn't prematurely terminate) and runtime rendering via ReportBus.Emit. Process.SyntetizeBehaviour's case "report" now creates a ReportStatement task instead of skipping to ;. SimulationRunner.RunAsync bridges ReportBus.OnReport to an instance-scoped OnReport event for the run's lifetime, tearing down in finally so the static bus doesn't accumulate stale listeners. HeadlessSimulator.RunAsync subscribes and appends [report] <msg> to the response log. Before F-G-11 every VHDL report was silently dropped by SkipToSemicolonInclusive.

  • F-G-11b — nested report execution. The five sub-structure body dispatchers (WhileLoop / ForLoop / SimpleLoop / If / Case) also create ReportStatement tasks now instead of skipping. assert still skips in all six (F-G-16 candidate).

  • F-G-12 — procedure-call inlining MVP. Interpreter/Services/VariableSyntetizer.cs: rewrote SkipProcedureDeclaration to capture the parameter list (via the existing TryParseParameterList) and the body tokens between begin and a two-token end procedure / end <name> match. The two-token match is naturally string-safe — bare end inside a report "…" fragment is followed by more string fragments, not procedure/<name>. Registers a full FunctionDefinition (Parameters + BodyRaw, IsPure=false) in FunctionRegistry. Interpreter/Repositories/ProcedureCallStatement.cs (pre-existing scaffold, now wired): Syntetize captures positional argument tokens (respecting string state so , and ) inside a literal don't fire arg-boundary logic). ExecuteAsync walks BodyRaw, substitutes parameter names with argument tokens for every report … ;, and emits via ReportBus. Signal assignments, wait, nested calls, and function evaluation inside reports are NOT modelled — full inlining is F-G-14 + F-G-13 territory. Process.SyntetizeBehaviour's else branch dispatches via ProcedureCallStatement.IsProcedureCall before falling through to SyntetizeAssignment. Also fixed SourceElaborator.StripLineComments (was blind to string literals — report "-------- " & name & … had its content truncated at the first -- inside the message).

  • F-G-12a — three tightly-coupled tokenizer / walk bugs. Surfaced by finally running procedure bodies for real.

    1. Parser/Repositories/Tokenizer.cs TokenizeSpaces was string-blind: if (line.Contains("--")) line = line.Split("--")[0]; truncated report "-------- " at the first -- inside the string. New FindTrailingLineCommentStart helper walks the line tracking a "-toggled insideString flag and returns the byte index of a -- outside any literal.
    2. Same tokenizer's TokenizeSpecialCharacters had a per-token if (line.StartsWith("--")) return new(); safety net that fired during the recursive per-char split of "--------", dropping the intermediate 7-dash substring. Removed now that line-level stripping is quote-aware.
    3. ProcedureCallStatement.ExecuteAsync's inner-walk break was i++; break; while the outer for also i++ed, skipping one token past every ;. Every OTHER report in a multi-report body was missed. Fixed by leaving i AT the ;.
  • F-G-15 — to_string(sig) / to_string(sig(hi downto|to lo)) evaluation in report expressions. New shared static Interpreter/Services/ParseTrace.RenderReportTokens(tokens, variables) consolidates the render logic previously duplicated across ReportStatement.ExecuteAsync and ProcedureCallStatement. Inline TryEvalToString pattern matcher recognises the 4-token (to_string ( name )) and 9-token (to_string ( name ( hi downto lo ) )) forms; resolves the signal via variable scope; extracts the slice as raw.Substring(size-1-high, high-low+1) (Kmila stores std_logic_vector values as strings with index 0 = leftmost/MSB char). Falls back to source-text emission on any unknown signal / out-of-bounds slice / unrecognised direction. Ring 5's end_case LCD reports now render actual bit values instead of the source expression text.

Reference: report statement execution end-to-end

Source .vhd:                        report "TB: reset released";
      │
      ▼
Parser.ParseFile                 (line-based state machine)
      │
      ▼
Architecture.Syntetize           (per-arch)
      │
      ▼
VariableSyntetizer.Syntetize     (declarative region)
      │
      ▼
Architecture.SyntetizeBehaviour  (body)
      │
      ├─ case "process" ──► Process.Syntetize
      │                          │
      │                          ▼
      │                     Process.SyntetizeBehaviour
      │                          │
      │                          ├─ case "report" ──► new ReportStatement + Syntetize + Tasks.Add
      │                          ├─ case "procedure_call" ──► new ProcedureCallStatement + Syntetize + Tasks.Add
      │                          └─ …
      │
      ▼
DeltaCycleEngine.RunTimeStep     (loops until stable)
      │
      ▼
ExecuteProcessWithSchedulingAsync
      │
      ▼
foreach task in process.Tasks:
   task.ExecuteAsync()  ──► ReportStatement / ProcedureCallStatement
                                 │
                                 ▼
                            ParseTrace.RenderReportTokens()
                                 │
                                 ▼
                            ReportBus.Instance.Emit(msg)
                                 │
                                 ▼
                            SimulationRunner.OnReport (event, per-run bridge)
                                 │
                                 ▼
                            HeadlessSimulator: log.Add($"[report] {msg}")
                                 │
                                 ▼
                            /api/v1/simulate response `log[]`

Known limitations tracked as followups:

  • F-G-13wait for N ns scheduling. Kmila doesn't model process suspend/resume across simulation time. All statements in a process body currently fire at delta 0 in code order.
  • F-G-14 — signal assignments inside procedure bodies. Ring 5's press_key(KEY_1) procedure body has cols_in <= … writes; F-G-12 MVP ignores them, so the DUT never sees inputs.

Also shipped 2026-07-18 — F-G-16 (assert execution) + report render polish:

  • F-G-16 — VHDL assert execution. New Interpreter/Repositories/AssertStatement.cs mirrors ReportStatement's shape. Syntetize captures three optional segments: condition tokens (until report / severity / ; respecting insideString), report expression tokens (until severity / ;), and severity level (default error). ExecuteAsync evaluates the condition via Operation (same idiom as ExitStatement's when clause); when the condition is FALSE — or when evaluation throws — emits [assert-<sev>] <msg> via ReportBus. Default message when the source omits report: "Assertion violation". Failure-biased on eval-throw so students diagnosing broken designs get noise rather than silence. Wired into all six dispatchers (Process + While / For / SimpleLoop / If / Case bodies).
  • Consecutive-same-char run collapse in report render. The fragmented-string path in ParseTrace.RenderReportTokens tracks the previous emitted char; when the current token is a single char equal to the previous, join WITHOUT a space. ======== (source) was rendering as = = = = = = = = because the tokenizer splits string content on special chars — now renders as ========. Same for --------. Multi-char tokens (lcd, line0) keep the space separator so word boundaries survive. Heterogeneous single-char runs also keep the space.

Ring 5 unblock — parser + elaborator (2026-07-17 → 2026-07-18)

Session 22 shipped four related fixes plus the diagnostic scaffolding that found them. All four are additive: default paths unchanged, byte-identical SimulatorBench VCDs across 121 cases.

  • F-G-08 (parser hang) — sub-structure body dispatchers now handle wait / report / assert. WhileLoopStructure, ForLoopStructure, IfStructure, CaseStructure, SimpleLoopStructure — none of their body switch statements had cases for testbench-only keywords, so wait for N ns; inside a nested while ... loop fell through default: _streamer.MoveNext(); the parser then dispatched the trailing for as a for-loop header, ForLoopStructure.ParseRangeValue swallowed tokens until the next loop, and new Operation(...) on the giant malformed range string hung indefinitely. Fix adds case "wait" / case "report" / case "assert" to all five dispatchers, delegating to new shared static helpers Interpreter.Services.ParseTrace.SkipWaitStatement and .SkipToSemicolonInclusive. Process.SkipWaitStatement / Process.SkipToSemicolonInclusive now delegate to the same helpers. Latent bonus fix in the shared helper: the tokenizer splits "TB: end of stimulus" on whitespace, so the old end early-exit safety net fired on the word end inside the fragmented literal — the shared helper toggles an insideString flag via unmatched-quote counting so it stays opaque to the walk.

  • F-G-09 (elaborator string-literal blindness) — SourceElaborator.SplitArchitecture masks string-literal contents before its keyword regex. Same shape as F-G-08's fragmented-string bug, one level up. The regex \b(architecture|process|...|end|...)\b used to false-match on end inside report "TB: end of stimulus";, decrementing innerDepth from 1 (inside stim_proc) to 0 and prematurely terminating bodyText at the real end process;. The emitter then wrote end architecture; right after wait; with no closing end process; for the last process. Fix: new MaskStringLiterals helper returns a length-preserving copy of the source with "..." contents replaced by . (quotes preserved); SplitArchitecture uses the masked copy for regex + IndexOf(';') and the original for substring returns, so report strings survive untouched in the elaborated output.

  • F-G-10 (elaborator artefact shadowing) — scanners skip _elaborated_*.vhd. SourceElaborator.ScanProject and HeadlessSimulator's scan loop both enumerate every .vhd* file in the sandbox. A leftover _elaborated_<entry>.vhd from a previous run of the same project would get scanned too, and its post-elaboration record (with all component instances inlined into signals) would overwrite the source's record in the entity dict. Downstream, HasComponentInstance(top.ArchitectureBody) returned false, ElaborateOrNull returned null, HeadlessSimulator fell back to the original source, and the parser saw only the outer testbench shell (11 signals for the Ring 5 capstone, missing all 174 DUT internals). Fix: both scanners now skip file names starting with _elaborated_. Reproducer was invisible to SimulatorBench because bench doesn't set retain: true on its sandbox projects, so each case starts empty.

  • Cancellation surface through parse + synth descent. Debugger.Repositories.Parser.ParseFile(CancellationToken) overload added; SimulationRunner.RunAsync registers _cts.Token.Register(() => _engine?.Stop()) after Initialize so the runner-side cancel actually breaks the engine's delta loop. HeadlessSimulator.RunAsync builds a request-scoped CTS at Phase("start") (before parse) instead of inside SimulationRunner (after parse), so KMILA_SIM_WALL_TIMEOUT_MS=<n> now covers the whole pipeline — parse included — not just the sim loop.

  • Parse-time safety nets on every unbounded Skip* loop. Added ParseTrace.MAX_SKIP_ITER = 100_000 cap + throw-with-context via the new Parser.Models.ParserSkipOverflowException on: VariableSyntetizer.Skip{Function,Component,Subtype,Alias,Procedure,Attribute,Disconnect,ToSemicolon}Declaration plus TryParseParameterList; historically these had no cap and could hang the parser indefinitely on pathological input. A cap trip now surfaces via the response as [engine] ParserSkipOverflowException in <site> at line <N>, tokens=[...] — under 100 ms instead of "curl times out after 10 min."

  • Env-var-gated parse-time diagnostics.

    • KMILA_PARSER_STEP_TRACE=1 — stderr [kmila-parse t=...ms line=...] {caller}: {tag} at Skip* entries + per-declaration in the outer VariableSyntetizer loop + entry-of every sub-structure Syntetize.
    • KMILA_SIM_PHASE_TRACE=1 — stderr [kmila-phase t=...ms] {tag} at each pipeline boundary (start / registries-cleared / scan / subscan / elaborate / parse / stimulus / runner).
    • KMILA_SIM_PROGRESS_EVERY_N_DELTAS=<n> — stderr [kmila-diag t=...ms] tick=... delta=... cumDeltas=... every N deltas.
    • KMILA_SIM_TRUNCATE_DELTA_CAP=1 — turn the delta-cap throw into a break-and-continue so a run past the first cap is observable end-to-end.
    • KMILA_SIM_NO_HISTORY=1 (pass 1 legacy) — skip VCD history recording; response VCD is empty but report-based sweeps still work. Always-on: the response log[] gets a [diag] line at the end with tick / delta / cap / initial-elab counters.

Practica-hardening sweep (2026-06-22 → 2026-06-23)

Driven by the user's 5 ESCOM Arquitectura de Computadoras practicas (P1–P4). Each bug below was locked in as a SimulatorBench case (120–132) before being fixed; full per-case narrative lives in Documentacion/HANDOFF_practicas_2026-06-23.md. The rule throughout: Kmila adapts to handle the codes, never the other way around. No practica source was modified for sim-only compatibility.

  • **Constant-array element access (Bench bug #19) — Parser/Models/Variable.cs

    • Interpreter/Models/{Deferred,Dynamic}Index.cs + Interpreter/Services/VariableSyntetizer.cs + Interpreter/Services/Operation.cs.** Positional aggregates (("10101010", "10111011", …)) now parse into a clean concatenated bitstring of ElementSize × ArrayLength bits; Variable carries the new ElementSize / ArrayLength fields; DeferredIndex / DynamicIndex slice element-wise when the source has ElementSize > 1. New expression-mode DynamicIndex re-evaluates rom_init(to_integer(unsigned(addr)))-shaped indices each delta. ParseIndexedAccessDeferred is paren-aware. Trap: Variable overloads operator== to compare names — use is null / is not null for reference checks.
  • Named-association aggregates + user-defined function bodies (Bench bug #19 follow-up) — VariableSyntetizer.cs, Operation.cs, FunctionExecutor.cs, FunctionDefinition.cs. TryParseNamedAggregate handles (0 => pack(170), …, others => …) placing each value at key × ElementSize offset. SkipFunctionDeclaration now parses signature + body and registers a real FunctionDefinition in PackageRegistry. ParseUserDefinedFunctionCall invokes FunctionExecutor when the function has a body. New CachedScope on FunctionDefinition — subsequent calls reuse the same parameter Variable instances and only refresh .Value (20× perf win on heavy ROM init).

  • Case-statement identifier + slice selector (Bench bugs #20, #21) — DeltaCycleEngine.cs. ExecuteCaseStructureWithSchedulingAsync now extracts the slice when HasSelectorSlice is true; ResolveChoiceForCase resolves each when choice — hex literals, quoted bit strings, single-quoted bits, and identifier lookups against _allSignals so when OP_LOAD => matches the binary value of OP_LOAD.

  • P2A LOAD pipeline + HALT detect (Phase E). Operation.ParseFunctionCall::TryConsumeSliceArg emits a DeferredIndex when an arg looks like <var>(N) / <var>(H downto L); StandardFunctions.GetStringValue/GetIntValue learned to evaluate it; Operator.SolveComparation INTEGER branch falls back to ordinal string equality when both sides aren't numeric (enum literals live as their name strings); * operator widened to leftW + rightW per numeric_std unsigned-multiply. P2A end-to-end: acc_reg = 56, halted = 1 — matches GHDL bit-for-bit and the MachXO2 FPGA.

  • Function-body capture for any if/case/loop-nested body (Bench bug #24) — VariableSyntetizer.SkipFunctionDeclaration. The legacy depth counter treated end if as decrement + re-increment on the next keyword, truncating bodies at the first inner end. Now consumes end <closer> as a single match; loop-opener prefix for X in Y loop / while X loop skips past the trailing loop keyword. P4's key_index function was the trigger.

  • Array attributes ('length, 'high, 'low, 'left, 'right) — Operation.TryParseSignalAttribute. P4 needed to_unsigned(N, slot_cnt'length) which would otherwise corrupt the function-call arg list. Boolean edge attributes (event, last_value, stable, active) untouched.

  • SourceElaborator generic-default substitution — SourceElaborator.cs. New ExtractGenericDefaults reads := defaults from a child entity's generic clause; ordering: defaults first, then explicit generic map(...) overrides. Port-type texts are substituted too (so STD_LOGIC_VECTOR(WIDTH-1 downto 0) aliases reach the parent as STD_LOGIC_VECTOR(16-1 downto 0)).

  • Engine perf: concurrent-task sensitivity gating (Phase H.3) — DeltaCycleEngine.cs. New per-Operation cache _operationInputs records the static input-signal set (Variable, DeferredIndex.Source, DynamicIndex.Source + IndexSource; null = "always run" for DeferredFunction / expression-mode DynamicIndex). _lastDeltaChangedSignals snapshots committed changes per delta; the concurrent-task loop skips Operations whose inputs don't overlap. Reset / unioned at each time-step boundary so external-stimulus changes still propagate. P22 12× faster on signal-heavy designs.

Continuation 2026-06-24 — handoff §6 follow-up

Closed §6 items 1, 3, 5, 6 from HANDOFF_practicas_2026-06-23.md. Items 2 (P22 ROM rewrite) and 4 (P3/P22 ROM content) were re-confirmed deferred — both are practica-edit work and stay paused until the user re-authorizes. Zero practica VHDL was modified.

  • (others => 'X') initial-value eager expansion (§6 item 1) — VariableSyntetizer.cs, signal-construction site. The Size setter's (others=>X) branch in Parser/Models/Variable.cs:135 only fires when _value is already non-null. The object-initializer pattern at the signal-construction site sets Size first (no value yet) and Value second, so the marker landed in Value verbatim — VCD writers then emitted the literal 11-char string (others=>0) (non-bit chars map to 'x') for every signal initialised this way, until the first user write. Now: detect the marker on the initializer value, expand to a VectorSize-wide bitstring before construction, gated on VectorSize > 0 (so unsigned / signed / bit_vector / std_logic_vector are all covered).

  • (others => 'X') RHS aggregate write-time expansion (§6 item 3, P3 root cause) — Operation.cs::ParseOthersAggregate + SignalScheduler.cs::ExpandAggregateMarker. When (others => '0') appears on the RHS of a clocked reset branch (pc_reg <= (others => '0') etc.), the engine created an Aggregate_others_X Variable via the 4-arg constructor. Passing Size = 0 to that constructor triggered Variable.Size's own marker-expansion path which produced a zero-length string — the Aggregate Variable's Value collapsed to "". The scheduler then wrote "" into the target signal, and the VCD writer rendered it back as the original 11-char marker. Fix has two parts: (a) ParseOthersAggregate now uses the object-initializer construction so the Size setter is never called, preserving the marker string in Value; (b) new helper SignalScheduler.ExpandAggregateMarker(newValue, target) runs at every queue-drained write in ApplyCurrentUpdatesWithLines — if the payload is an (others=>X) marker and target.Size > 0, it expands to a target.Size-wide bitstring using the fill char from the marker. **P3 result: all internal CPU registers now initialise to clean zeros at t = 0; PC walks normally; mbr_reg

    • rom_data_w get real values.** The remaining ACC stall on P3 is a practica content bug (eq_sel ROM-address mux OR-masks data fetches — same pattern as P22 Phase G.1) and is out of scope per the rule.
  • * width tripwire (§6 item 5) — no code change, locked via bench case 135. Auditing showed no existing bench case directly exercised the Phase E leftW + rightW widening; P1 was the only consumer, and only indirectly. Added a focused tripwire so any drift back to narrow-multiply (max(leftW, rightW)) fails the sweep immediately: unsigned(4..0) * unsigned(4..0)unsigned(9..0)std_logic_vector(8..0); 12 * 11 = 132 = "010000100" loses the MSB under narrow-multiply.

  • Function-body symbol-resolution rebind (§6 item 6) — no code change, locked via bench case 136. Built a tripwire where the same sub entity is inlined twice with different architecture-scope signal values (k_sig driven from input port kin = 3 and kin = 7), and a function add_off declared in sub's architecture reads k_sig directly. The handoff hypothesis was that CachedScope's reuse of parameter Variable instances would bind the function body's Operations to the first instance's k_sig and yield stale reads for the second instance. The case passes today (o1 = 5 + 3 = 8, o2 = 10 + 7 = 17) — the elaborator's per-instance signal renaming creates distinct FunctionDefinition entries in PackageRegistry, so each instance's function body parses against its own scope and there is no cache collision. Workaround proven correct; tripwire stays in the bench as a permanent regression net.

End-of-continuation state:

  • SimulatorBench: 119 / 119 PASS (115 baseline + 4 new locks 133–136). ~26 cumulative simulator/bench bugs fixed.
  • P1: 5 / 5 PASS.
  • P2A: ACC = 56, halted = 1 — bit-for-bit GHDL match, FPGA-verified state preserved.
  • P3 / P22 / P4: parse + simulate cleanly. P3 internal-register marker leak fixed; remaining ACC stalls are practica-content issues, not Kmila bugs.

Files modified in this continuation (uncommitted, user manages git):

Interpreter/Services/VariableSyntetizer.cs            §6 item 1 eager-expansion at construction
Interpreter/Services/Operation.cs                     §6 item 3 no-Size construction of Aggregate_others_X
Interpreter/Services/SignalScheduler.cs               §6 item 3 ExpandAggregateMarker write-time helper
SimulatorBench/Cases/133-others-init-readback/        §6 item 1 lock
SimulatorBench/Cases/134-others-rhs-clocked-reset/    §6 item 3 lock
SimulatorBench/Cases/135-mul-width-tripwire/          §6 item 5 lock
SimulatorBench/Cases/136-fn-multi-caller-scope/       §6 item 6 lock (no Kmila change)

Version 1.14.0 (2026-04-19)

  • Services/Operator.cs — xnor fixed. The evaluator fell through to 'U' because xnor wasn't in the case list. Now a xnor b = '1' when a = b else '0'. Matches the hand-derived not (a xor b) form, which had been the user's workaround.
  • Services/Resources.csCalculateLUTs implemented. Walks the statement tree (Tasks / Then / Else / Body / Cases / Branches) via reflection and sums operator weights: logic ops 1 × bits, arithmetic +/− at 3 × bits, comparators at max(1, bits/2). abs, not, *, /, mod, ** stay 0 because they map to DSP / BRAM and are accounted for in their own calculators. Removes the TODO stub.
  • Services/SimulationRunner.cs — tick-0 stimulus + history. Two related fixes: (1) dropped the .Where(s => s.Tick > 0) filter so stimulus at tick 0 reaches the engine; this removes the workaround StimulusBuilder used to pre-seed tick 0. (2) Threaded a directlyChanged HashSet through the main loop and now call _engine.History.RecordChanges(directlyChanged, tick * 1000), so direct .Value = writes in the interpreter are recorded — previously only delta-cycle driven changes made it into the waveform.

Contributing

Code Style

  • Use XML documentation comments for all public methods
  • Add // v0.x.x Fix: comments for bug fixes with explanation
  • Use #if DEBUG guards for debug output
  • Follow existing naming conventions (PascalCase for public, _camelCase for private)

Testing

  • Add test cases for new features
  • Ensure existing tests still pass
  • Document any new test files in this README

Documentation

  • Update CHANGELOG section for all changes
  • Add inline comments explaining non-obvious logic
  • Include code examples in method documentation

What's new in v1.15 (2 May 2026)

New services in Interpreter.Services:

  • FunctionRegistry (#18) — process-wide singleton holding FunctionDefinition stubs for every function and procedure the parser has skipped. Cleared between fixtures alongside EntityRegistry / PackageRegistry.
  • SkipDiagnosticLog (#19) — every silent-skip event (function body, procedure body, alias, attribute, subtype, disconnect spec, constant-with-fn-call) is logged with a stable code (VHD-FUNC-BLACKBOX, VHD-ALIAS-PLACEHOLDER, …). The test runner drains and prints it after each fixture so users see what's missing from the netlist.

Parsing improvements:

  • JoinContinuationLines (#11) collapses multi-physical-line VHDL statements before the line-by-line state machine in ProcessVhdlCode runs.
  • case <slice> selectors (#10) — Process.SyntetizeBehaviour now recognises case op(3 downto 0) is; bit indices are stored on CaseStructure.SelectorSliceHigh / Low for downstream synthesis.
  • <sig>'range and <sig>'reverse_range in for loops (#14) resolve to the signal's declared bit range.
  • Alias declarations (#17) now register the alias name + size (from explicit subtype → target slice → default) instead of being silently dropped.
  • Generic computed defaults (#16) — ParseInitializationValue constant-folds + - * / chains so TOTAL : integer := BASE * TIMES resolves correctly.
  • Architecture-end detection bug fixed (#15/#18): funcProcDepth no longer decrements on control-flow ends (end if, end loop, end case); arch-end matching is exact-name only.

Error reporting (#21):

  • Entity.Syntetize and Architecture.Syntetize accept IReadOnlyList<(int sourceLine, string text)> so diagnostics reference the user's source line numbers.
  • Missing-; between port declarations now produces an explicit Missing ';' before '<name>' at Line N:M instead of the generic "expected ( or )" message.

Memory (#7): result = default after each fixture, non-blocking Gen-2 GC every 8 fixtures, source string nulled immediately after parsing. Workstation GC enabled at the project level. Peak RSS during the 54-fixture sweep: ~101 MB.

Full release notes: ../Documentacion/CHANGELOG_v1.15.md.


What's new in v1.16 — LibraryCompiler module (2 May 2026)

Library compilation moved out of the Interpreter into a dedicated sibling module: ../LibraryCompiler/. Owns the IEEE builtins (std_logic_1164.vhd, numeric_std.vhd), exposes a portable host-driven API (OpenLibrarySource + IBlobStore), and produces compiled artifacts that feed the rest of the pipeline.

The old Interpreter.Services.IEEELibraryLoader now delegates to the new module — its public API is unchanged so existing callers keep working.

What changed inside Interpreter/:

  • Services/IEEELibraryLoader.cs — rewritten as a thin facade that constructs a default LibraryCompiler (no host callbacks, embedded-resource fallback) and forwards EnsureLoaded().
  • Tests/LibraryCompilerTests.cs — 17 new test cases covering the full module surface (resolver / builder / compiler / orchestrator / 3 policy modes / blob stores / project compilation).
  • Interpreter.csproj+ProjectReference LibraryCompiler.

Host contract: LibraryCompiler makes zero filesystem decisions. Hosts pass Func<string, CancellationToken, Task<Stream?>> OpenLibrarySource and an IBlobStore? for persistence. The lib only ships InMemoryBlobStore and NullBlobStore — concrete FileSystemBlobStore / MauiAppDataBlobStore live in the host (Kmila.Shared/Services/).

Full release notes: ../Documentacion/CHANGELOG_v1.16.md.


What's new in v1.17 — IEEE primitive lowering at synth time

The synthesizer used to emit a single Constant("@FunctionName", 1) placeholder net for every IEEE function call. Now it emits real cells:

User VHDL Cell emitted
q <= rising_edge(clk) EDGE_DETECT (Edge=rising)
idx <= to_integer(unsigned(addr)) BUF (cast) → TO_INTEGER
widen <= resize(small, 16) RESIZE (FromWidth=4, ToWidth=16)
shifted <= shift_left(v, n) SHL
rotated <= rotate_right(v, n) ROR

Key files

  • Synthesis/Models.cs — added CellKind.TO_INTEGER, TO_VECTOR, RESIZE, EDGE_DETECT. Existing SHL / SHR / ROL / ROR / BUF reused for shifts / rotates / casts.
  • Synthesis/IEEEPrimitives.cs — table-driven lookup mapping each IEEE function name to a primitive emitter (IIEEEPrimitive). 13 emitters seeded by default; vendor libraries can add more via IEEEPrimitives.Register.
  • Synthesis/ConcurrentAssignSynthesizer.cs — when the Shunting-yard walker encounters a DeferredFunction, it now resolves the call's args to nets and calls IEEEPrimitives.TryLower. On hit the emitter's output net joins the operand stack; on miss a placeholder constant + UnknownIEEEFunction diagnostic.
  • Tests/IEEEPrimitivesTests.cs — 18 cases covering canonical-name lookup, alias lookup, case-insensitive lookup, unknown-name fallback, and per-emitter cell-shape assertions.

The IEEEPrimitiveArgs, IEEEPrimitiveResult, IIEEEPrimitive, and IEEEPrimitives types are all internal because they reference the internal SynthesisContext. Tests in the same assembly drive them directly; vendor packs that want to register from outside need InternalsVisibleTo (or live as a sub-namespace inside Interpreter).

Full release notes: ../Documentacion/CHANGELOG_v1.17.md.


What's new in v1.18 — primitive lowering reaches user code

v1.17 wired the table; v1.18 made it actually fire for typical user expressions.

Models/DeferredFunction.cs::ShouldDefer extended from {rising_edge, falling_edge} to {rising_edge, falling_edge, to_integer, conv_integer, to_unsigned, to_signed, resize, shift_left, shift_right, rotate_left, rotate_right}. Type-name overloads (unsigned, signed, std_logic_vector) deferred separately in v1.19.

The runtime path keeps working through DeferredFunction.Evaluate() (which still calls StandardFunctions.EvaluateFunction internally), so simulation semantics are preserved. The synth path now sees structured calls instead of inline-evaluated literals.

Tests/IEEELoweringEndToEndTests.cs — 6 cases that drive a hand-crafted Operation items list through the synthesizer and assert the resulting netlist contains the expected CellKind. Proves the wiring from DeferredFunction through IEEEPrimitives.TryLower to a real Cell actually fires.

Full release notes: ../Documentacion/CHANGELOG_v1.18.md.


What's new in v1.19 — nested IEEE calls in arguments

A pre-existing parser bug: to_integer(unsigned(addr)) produced args == ["unsigned"] and the addr operand was silently dropped. Every nested IEEE call simulated as if its inner operand were 0. Hidden by tests because synth-only fixtures only validate "did it throw," not "did the values match."

Fix — four pieces

  1. Services/Operation.cs::ParseFunctionCall — when walking depth-1 arg tokens, if the current token is a known function name (via StandardFunctions.IsStandardFunction or FunctionRegistry.IsKnown) and the next token is (, recursively call ParseFunctionCall to consume the nested call. The recursive call uses its own local parenDepth, so paren tracking stays correct.
  2. Synthesis/ConcurrentAssignSynthesizer.cs::ResolveDeferredArgs — added case DeferredFunction nested that recursively resolves the nested call's args and lowers it through IEEEPrimitives.TryLower. Result: properly wired cell graphs.
  3. Models/DeferredFunction.cs::Evaluate — recursively evaluates nested DeferredFunctions to their Variable results before passing them to StandardFunctions.EvaluateFunction.
  4. Models/DeferredFunction.cs::ShouldDefer — added type-name overloads (unsigned, signed, std_logic_vector, conv_std_logic_vector). Deferring these was the original blocker for v1.18; the parser-side recursion finally made it safe.

Bonus bug killed

StandardFunctions.ToInteger("01010101") was returning 1,010,101 decimal instead of 85 binary because int.TryParse was tried before binary parsing. Reordered: when input is multi-character all 0/1, parse as binary first; decimal interpretation falls through for actual integer literals.

This was a separate pre-existing bug — but it would have hidden the parser fix, so it shipped together. Old code computed to_integer(unsigned("01010101")) as 1,010,101 instead of 85 even after the parser was fixed.

Real-world impact

Designs that index memory through to_integer(unsigned(addr)) (which is the canonical idiom — every UART / SPI / cache / register-file design in the test suite uses it) now compute the correct address. Pre-v1.19 those designs simulated as if every access landed at location 0.

Tests/NestedIEEECallTests.cs — 7 cases covering parser nesting, runtime evaluation (to_integer(unsigned("01010101")) == 85), and synth wiring topology (BUF.outputId == TO_INTEGER.inputPinId).

Full release notes: ../Documentacion/CHANGELOG_v1.19.md.


Test counts after v1.19

Interpreter sweep (test*.vhdl) ............... 54 / 54
Interpreter --test-delta:
  DeltaCycleEngine ........................... 12 / 12
  Synthesis .................................. 16 / 16
  IEEELibraryLoader ...........................  7 /  7
  LibraryCompiler ............................ 21 / 21
  IEEEPrimitives ............................. 19 / 19
  IEEELoweringEndToEnd ........................  7 /  7
  NestedIEEECall ..............................  8 /  8
  PortStrictness ..............................  5 /  5
  PracticeExercise ............................  1 /  1
─────────────────────────────────────────────────
                                              150 / 150

Plus 40 (Parser) + 31 (Debugger) + 23 (TimeMachine) for a grand total of 230 / 230 across the whole simulator stack.


Quick architecture pointer

If you're wiring the Interpreter into a host, the integration guide is at ../Documentacion/IEEE_PIPELINE_GUIDE.md — walks through the full flow from a user q <= to_integer(unsigned(addr)) line down to a BUF + TO_INTEGER cell graph, with code samples at each layer.


Step-replay downstream consumer (2026-05-17, App polish pass)

SignalHistory and the WaveformData.FromSignalHistory factory now feed a new App-side step-replay pipeline. After a simulation completes, App/Kmila/Kmila.Shared/Services/ReplayBuilder.cs walks the union of all signals' transition times into ≤200 frames and emits an ExecutionTimelineDoc consumed by the Concepts-track ExecutionPlayer component. The App's Editor renders this as a new "Replay" dock tab — tape transport + live signal table per frame, no source-line highlights yet (that's Phase B).

Implications for this module:

  • SignalHistory.RawHistory and WaveformData.FromSignalHistory(history, timescaleNs) are now load-bearing for the App's pedagogical playback, not just the Practice grader / VCD export / Runs persistence. Any future changes that drop transitions, reorder them, or change the WaveformTransition.Label semantics will visibly degrade the replay.
  • Phase B (line-highlight metadata per executed statement) will require instrumenting DeltaCycleEngine to emit a frame log alongside SignalHistory. Same ExecutionTimelineDoc consumer; the contract is fixed in App/Kmila/Kmila.Shared/Models/ExecutionTimeline.cs (TimelineStep shape with T, HighlightLines, Signals, Note). No App-side changes needed when Phase B lands — only Interpreter-side instrumentation.
  • See Documentacion/CHANGELOG_concepts_module_2026-05-17.md (Task 5 section) for the full pipeline diagram and the design-decisions table.

License

This project is part of academic research at Instituto Politécnico Nacional (IPN).

Contact

Project Author: Ulrich Tamayo Daniel Email: [email protected] Institutional Email: [email protected]