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
- Project Overview
- The Interpreter Module
- Architecture
- Critical Design Decisions
- Key Components
- How to Use
- Testing
- Common Issues and Solutions
- Developer Guide
- 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,afterclause 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 viaLayeredLayoutfor 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 aSimulationConfig(entity + total ticks + auto-togglingClockConfiglist +StimulusEventlist), withPause/Resume/Stop,OnProgressChanged,OnSimulationEnded, andOnErrorevents. - Custom types:
TypeDefinition+TypeRegistrycover enumerations, arrays, records, and subtypes.TypeRegistryis consulted byVariableSyntetizer,Operation,FunctionExecutor, andResources. - FPGA resource estimation:
Resourcesproduces 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;CancellationTokenis threaded through the synthesis and run pipeline. - Test status: Two independent test surfaces exist today (see §7):
- The historic console corpus driven by
Program.cs— now 54test*.vhdlfixtures (numbered throughtest41, 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-runProgram.csagainst the corpus to obtain a current number. - A first-class xUnit suite under
Interpreter/Tests/(DeltaCycleEngineTests,SynthesisTests,NestedIEEECallTests,IEEEPrimitivesTests,IEEELibraryLoaderTests,IEEELoweringEndToEndTests,LibraryCompilerTests,PortStrictnessTests,PracticeExerciseTests,VcdComparison) run viadotnet test. This is the authoritative regression surface; the console corpus is a smoke/exploration harness.
- The historic console corpus driven by
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/architecturesEntity.cs: Parses entity declarations (ports, generics), coordinates architecture and resource estimationArchitecture.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 SignalSchedulerExecuter.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 structuresCaseStructure.cs: Handles case-when statements with multiple branchesForLoopStructure.cs: Handles for loops with ascending/descending ranges and expression-based boundsWhileLoopStructure.cs: Handles while loops with condition evaluationSimpleLoopStructure.cs: Handles infinite loops with exit conditionsExitStatement.cs: Handles VHDLexitstatements with optional label and conditionNextStatement.cs: Handles VHDLnextstatements with optional label and conditionNullStatement.cs: Handles VHDLnullno-op statementsReturnStatement.cs: Handles VHDLreturnstatements for functions and proceduresProcedureCallStatement.cs: Handles procedure calls with argument evaluation and out-parameter write-backConditionalAssignment.cs: Handles concurrent conditional signal assignments (when...else)SelectedAssignment.cs: Handles concurrent selected signal assignments (with...select)GenerateBlock.cs: Handlesfor-generateandif-generatestatements with loop unrolling
Expression Evaluation:
Operation.cs: Orchestrates expression parsing, synthesis to RPN, and evaluationShuntingYard.cs: Implements Shunting-yard algorithm for operator precedenceOperator.cs: Executes individual operations (arithmetic, logic, comparison, shift/rotate)ScheduledOperation.cs: Wraps Operation for delta cycle signal scheduling semanticsLiteralsFinder.cs: Identifies and classifies literal values (integers, std_logic, vectors, hex, boolean) using regexOperationDetector.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 anif/elsifbranch is taken or acasearm 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 (afterclause support)ProcessScheduler.cs: Manages process execution scheduling based on sensitivity listsSignalHistory.cs: Records signal value changes over time for waveform export (VCD, CSV, Text). Timestamps arelong(tick*1000+delta); widened frominton 2026-09-13 so seconds-scale runs no longer overflow at ~21 ms. Storage-only — theSignalAttributeHandlersemantics path keeps its owninttimestamp, 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 supportPackageRegistry.cs: Singleton registry for VHDL package constants, functions, and proceduresEntityRegistry.cs: Singleton registry for multi-file entity discovery and component hierarchy trackingTypeRegistry.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 valuesFunctionDefinition.cs: Represents user-defined function signatures with lazy body synthesisProcedureDefinition.cs: Represents user-defined procedure signatures with out/inout parameter supportParameterDefinition.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 flowConstansts.cs: Operator precedence enums and operation regex patterns (the file name keeps the historical typo)SimulationConfig.cs: Composition ofrequired Entity Entity,ulong TotalTicks,List<ClockConfig> Clocks,List<StimulusEvent> Stimuli. Plus the auxiliaryClockConfig(Signal,HalfPeriodTicks,InitialHigh) andStimulusEvent(Tick,Signal,Value) types.
Run-loop Façade:
SimulationRunner.cs(Services/):IDisposablefaçade withRunAsync(SimulationConfig),Pause(),Resume(),Stop(), plus theOnProgressChanged(double),OnSimulationEnded(bool), andOnError(string)events. It is the entry point used byKmila.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 inVariableSyntetizer,TypeRegistry, andTypeDefinition.
Synthesis Namespace (Interpreter.Synthesis):
Synthesizer.cs: Static entry point —Netlist Synthesize(Entity entity)first callsComponentSynthesizer.SynthesizeAllfor 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 exposesGetOrCreateNet,NewInternal,Constant,AddCell,AddPort,AddSignal,AddDiagnostic, and finallyBuild()which returns a frozenNetlist.ComponentSynthesizer.cs:internal static;SynthesizeAll(parentEntityName, ctx)emits oneCellKind.BLACKBOX_COMPONENTcell per component instantiation the parser registered against the entity inEntityRegistry.Instance. Deliberately shallow — it does not descend into the referenced component's own architecture (that is covered bySourceElaboratorat 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, includingSynthesize(Operation, ctx)andSynthesizeInto(Operation, outputNet, ctx)overloads +SynthesizeExpression(expr, scope, ctx, targetWidth)for sub-expression reuse. ResolvesDeferredFunctionargs (including nested calls) and lowers IEEE calls throughIEEEPrimitives.TryLower.ConditionalAssignSynthesizer.cs: Lowerswhen…elsechains.SelectedAssignSynthesizer.cs: Lowerswith…selectblocks.ProcessSynthesizer.cs: Lowers aProcessto 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, …). TheIEEEPrimitiveArgs/IEEEPrimitiveResult/IIEEEPrimitive/IEEEPrimitivestypes areinternal(they reference the internalSynthesisContext); hosts extend the table via the publicIEEEPrimitives.Registerstatic.TryLowerreturns the emitted output net or a placeholder +UnknownIEEEFunctiondiagnostic on miss.SelectionFilter.cs: Public filter that trims aNetlist+LaidOutNetlistdown to a user-selected subset for export —SelectionPicks(basketed cell / net / port-net ids + optionalFocusedSourceLine) →FilteredLayout(same coordinate space, with aBoundsbox). 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 enumsCellKind,NetOrigin,NetKind,PortDir,DiagnosticSeverity.Synthesis/Layout/LayeredLayout.cs: Sugiyama-style layout pass —Layout(Netlist) → LaidOutNetlist(withRectboxes andWirePathpolylines). 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:
- Synthesis Phase: Convert infix notation to postfix using Shunting-yard algorithm
- 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:
- Tokens:
(,A,+,B,),*,C,-,D,/,2 - Postfix:
A B + C * D 2 / - - 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:
- 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));
}
- I/O Pin Counting:
// Sums bit widths of all entity ports
ioPins = entity.Ports.Sum(p => p.Size);
- 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:
- 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' */
- 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:
- Parse signal declarations
- Identify and parse processes
- Handle concurrent signal assignments
- Skip component instantiations (structural, not behavioral)
- 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
CaseWhenBranchrecords the 1-based source line of itswhenkeyword (ConditionLine); the engine firesOnConditionEvaluated(line)when an arm matches so a breakpoint on awhenarm pauses the live run (parallel toIfBranch.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 cyclesignal'last_value- Previous value before changesignal'stable- True if signal has not changedsignal'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.ResourceUsageproperty - 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:
- CalculateIOPins() - Sums port bit widths (100% accurate)
- CalculateClocks() - Detects clock signals from process sensitivity lists (100% accurate)
- CalculateFlipFlops() - Recursively scans clocked processes for assigned signals (~95% accurate)
- CalculateDSPSlices() - Counts multiplication operations (TODO: implementation pending)
- CalculateBRAM() - Detects array/memory patterns (TODO: implementation pending)
- 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,Completedevents - 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 CancellationTokenSourceStartCancellableOperation(CancellationTokenSource)- Uses provided CTSRequestCancellation()- Triggers cancellation and raises eventThrowIfCancellationRequested()- Throws if cancelled (for use in loops)CheckCancellation()- Returns bool without throwingIsCancellationRequested- Property to check cancellation stateCancellationToken- 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
FastSimulationRunner↔SimulationRunnerbyte-for-byte equivalence is covered by VCD-diff / equivalence tests (see theFast modenotes 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 surface —
Program.cs.1(a stale copy ofProgram.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 underInterpreter/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_ROWSused 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:
- Component instantiation not properly skipped
- Labeled statement not recognized
- Function/procedure end mistaken for architecture end
Solution (v0.13.0):
- Added labeled component detection:
if (label : IDENTIFIER) - Added
SkipComponentInstantiation()method - Added
funcProcDepthtracking 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:
- Keywords (downto, to, when) treated as identifiers
- Generic used in port expression before generics evaluated
- 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: addedComponentSynthesizer,IEEEPrimitives, andSelectionFilterto theInterpreter.Synthesisnamespace listing; added theReportStatement/AssertStatement/WaitForStatementcontrol-statement files (allISControl) to the class hierarchy; and added the newerServices/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,SimulationRunnerfaç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.Synthesisnamespace (Synthesizer,SynthesisContext,ConcurrentAssignSynthesizer,ConditionalAssignSynthesizer,SelectedAssignSynthesizer,ProcessSynthesizer,Models.csrecords / enums,Layout/LayeredLayout), plus the newSimulationRunnerfaçade and theSimulationConfig/ClockConfig/StimulusEventmodel trio. MarkedServices/DataStructure.csexplicitly 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, andLayeredLayout. Noted thatSimulationRunneris the entry point used byKmila.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
Executerunder ISControl interface implementations - Added
IFinderinterface withLiteralsFinderimplementation - Added Utility Classes section with
OperationDetectorandShuntingYard
- Added
Key Components Summary: Added missing component descriptions:
Executer.cs- Task orchestration with pause/resume/cancel supportLiteralsFinder.cs- Literal value identification and classificationOperationDetector.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 declarationsTypeDefinitionmodel supporting Enumeration, Array, Record, and Subtype kindsTypeRegistryfor 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;
- Enumeration:
- 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.Instancefor global access - Events:
ProgressChanged,ErrorOccurred,Completed,CancellationRequested - Progress tuple output:
(float Progress, string Message)viae.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/--verbosefor detailed progress,-p/--progressfor compact bar - Integrated into Entity, Architecture, and Process parsing
- Singleton
CancellationToken Support: Graceful operation cancellation throughout the parsing pipeline
ProgressReportermethods:StartCancellableOperation(),RequestCancellation(),ThrowIfCancellationRequested(),CheckCancellation()CancellationRequestedevent for notification when cancellation is triggered- Ctrl+C handling in
Program.csfor console application cancellation CancellationTokenparameter 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
OperationCanceledExceptionhandling 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 operatorssll- 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 Leftror- Rotate Right- Added to
PrecedencesLogicOperatorsenum inModels/Constansts.cs - Added precedence definitions in
Operator.DefinePrecedence() - Implemented
SolveShiftOperation()for vector operations
Constant Declaration Handling (
Services/VariableSyntetizer.cs): Improved constant parsingHasFunctionCallInConstant()- lookahead detection for function calls in constant initializationSkipConstantDeclaration()- 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 liketype big_mem_t is array (0 to 199999) of STD_LOGIC;were causing infinite loops and memory exhaustion. Added:parenDepthtracking for array boundsMAX_ITERATIONS = 10000safety limit- Only break on
;whenparenDepth == 0
Fixed LoggerFactory memory leak (Parser/Repositories/TokenStream.cs): Each
TokenStreaminstance was creating a newLoggerFactory. Changed to static shared instance.Fixed Regex allocation in Operation class (Services/Operation.cs): Each
Operationinstance was creating 4 newRegexobjects. Changed to static shared compiled instances.Fixed list allocation in loop (Services/Architecture.cs):
List<Variable> comparatorswas being allocated on every iteration ofSyntetizeBehaviour()loop. Moved outside loop and changed toHashSet<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)toif (_value == null)to avoid Value getter returningnew()during object initializationFixed STD_LOGIC port sizing (Services/VariableSyntetizer.cs): Added default
VectorSize = 1for 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
endstatements (supports bothend behavioral;andend 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 setLOGIC_OPERATORS: Changed from"(and|or|xor|nand|nor|not)"to@"^(and|or|xor|nand|nor|not|xnor|abs)$"- Added anchors to prevent partial matchesARITH_OPERATORS: Changed from@"(\+|\-|\*\*|\*|\/|mod)"to@"^(\+|\-|\*\*|\*|\/|mod)$"- Added anchors
Fixed unary NOT operator detection (Services/Operation.cs): Added proper type check to ensure
**(power) operator withPrecedencesArithOperators.UNARYis not treated asPrecedencesLogicOperators.NOTFixed ShuntingYard precedence casting (Repositories/ShuntingYard.cs:47): Changed from direct cast
(int)operation.PrecedencetoConvert.ToInt32()to handle enum-to-int conversion when Precedence is stored asobjectAdded labeled process detection (Services/Architecture.cs): Added support for
label : process(...)syntax where the label comes before the process keywordFixed 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, andelsekeywords during expression synthesisFixed function/procedure parsing (Program.cs): Added
funcProcDepthtracking to preventend FunctionName;from being mistaken for architecture endImproved ToInteger safety (Services/StandardFunctions.cs): Added checks for empty, 'U', 'X', 'Z' values and proper error handling
Fixed
buskeyword handling (Services/VariableSyntetizer.cs): Added support for guarded signal declarations withbuskeyword (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 parsingFixed VHDL
nullstatement handling (Repositories/CaseStructure.cs): Added explicit handling fornull;no-operation statements in case branchesFixed 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 likeTREE_DEPTH - 1, not just simple integersFixed 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 iterationsOperation.Syntetize()- 5000 iterationsIfStructure.SyntetizeBranchBody()- 5000 iterationsForLoopStructure.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 branchesComponent instantiation skipping (Services/Architecture.cs): Added
SkipComponentInstantiation()method to handlelabel : ComponentName [generic map (...)] port map (...);syntaxPort/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-elsesupport with multiple branches IfBranchhelper 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.csbut implementation pending
Error Messages
"Expected type: KEYWORDS, found: -> at Line:"
- This error typically occurs with unsupported VHDL constructs:
- Custom
typedeclarations (type memory_t is array...) - Generate blocks (
for i in 0 to N generate) - Aggregates (
(others => '0')) - Signal range attributes (
signal'range)
- Custom
- 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
reportstatements execute + surface via API log. NewInterpreter/Services/ReportBus.cs(singleton event sink);Interpreter/Repositories/ReportStatement.csimplementsISControlwith parse-time expression capture (trackinginsideStringso a;inside a string literal doesn't prematurely terminate) and runtime rendering viaReportBus.Emit.Process.SyntetizeBehaviour'scase "report"now creates aReportStatementtask instead of skipping to;.SimulationRunner.RunAsyncbridgesReportBus.OnReportto an instance-scopedOnReportevent for the run's lifetime, tearing down infinallyso the static bus doesn't accumulate stale listeners.HeadlessSimulator.RunAsyncsubscribes and appends[report] <msg>to the response log. Before F-G-11 every VHDLreportwas silently dropped bySkipToSemicolonInclusive.F-G-11b — nested
reportexecution. The five sub-structure body dispatchers (WhileLoop/ForLoop/SimpleLoop/If/Case) also createReportStatementtasks now instead of skipping.assertstill skips in all six (F-G-16 candidate).F-G-12 — procedure-call inlining MVP.
Interpreter/Services/VariableSyntetizer.cs: rewroteSkipProcedureDeclarationto capture the parameter list (via the existingTryParseParameterList) and the body tokens betweenbeginand a two-tokenend procedure/end <name>match. The two-token match is naturally string-safe — bareendinside areport "…"fragment is followed by more string fragments, notprocedure/<name>. Registers a fullFunctionDefinition(Parameters + BodyRaw,IsPure=false) inFunctionRegistry.Interpreter/Repositories/ProcedureCallStatement.cs(pre-existing scaffold, now wired):Syntetizecaptures positional argument tokens (respecting string state so,and)inside a literal don't fire arg-boundary logic).ExecuteAsyncwalksBodyRaw, substitutes parameter names with argument tokens for everyreport … ;, and emits viaReportBus. 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'selsebranch dispatches viaProcedureCallStatement.IsProcedureCallbefore falling through toSyntetizeAssignment. Also fixedSourceElaborator.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.
Parser/Repositories/Tokenizer.csTokenizeSpaceswas string-blind:if (line.Contains("--")) line = line.Split("--")[0];truncatedreport "-------- "at the first--inside the string. NewFindTrailingLineCommentStarthelper walks the line tracking a"-toggledinsideStringflag and returns the byte index of a--outside any literal.- Same tokenizer's
TokenizeSpecialCharactershad a per-tokenif (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. ProcedureCallStatement.ExecuteAsync's inner-walk break wasi++; break;while the outerforalsoi++ed, skipping one token past every;. Every OTHERreportin a multi-report body was missed. Fixed by leavingiAT the;.
F-G-15 —
to_string(sig)/to_string(sig(hi downto|to lo))evaluation in report expressions. New shared staticInterpreter/Services/ParseTrace.RenderReportTokens(tokens, variables)consolidates the render logic previously duplicated acrossReportStatement.ExecuteAsyncandProcedureCallStatement. InlineTryEvalToStringpattern 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 asraw.Substring(size-1-high, high-low+1)(Kmila storesstd_logic_vectorvalues 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'send_caseLCD 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-13 —
wait for N nsscheduling. 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 hascols_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
assertexecution. NewInterpreter/Repositories/AssertStatement.csmirrorsReportStatement's shape.Syntetizecaptures three optional segments: condition tokens (untilreport/severity/;respectinginsideString), report expression tokens (untilseverity/;), and severity level (defaulterror).ExecuteAsyncevaluates the condition viaOperation(same idiom asExitStatement'swhenclause); when the condition is FALSE — or when evaluation throws — emits[assert-<sev>] <msg>viaReportBus. Default message when the source omitsreport:"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/Casebodies). - Consecutive-same-char run collapse in report render. The
fragmented-string path in
ParseTrace.RenderReportTokenstracks 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, sowait for N ns;inside a nestedwhile ... loopfell throughdefault: _streamer.MoveNext(); the parser then dispatched the trailingforas a for-loop header,ForLoopStructure.ParseRangeValueswallowed tokens until the nextloop, andnew Operation(...)on the giant malformed range string hung indefinitely. Fix addscase "wait"/case "report"/case "assert"to all five dispatchers, delegating to new shared static helpersInterpreter.Services.ParseTrace.SkipWaitStatementand.SkipToSemicolonInclusive.Process.SkipWaitStatement/Process.SkipToSemicolonInclusivenow delegate to the same helpers. Latent bonus fix in the shared helper: the tokenizer splits"TB: end of stimulus"on whitespace, so the oldendearly-exit safety net fired on the wordendinside the fragmented literal — the shared helper toggles aninsideStringflag via unmatched-quote counting so it stays opaque to the walk.F-G-09 (elaborator string-literal blindness) —
SourceElaborator.SplitArchitecturemasks 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|...)\bused to false-match onendinsidereport "TB: end of stimulus";, decrementinginnerDepthfrom 1 (insidestim_proc) to 0 and prematurely terminatingbodyTextat the realend process;. The emitter then wroteend architecture;right afterwait;with no closingend process;for the last process. Fix: newMaskStringLiteralshelper returns a length-preserving copy of the source with"..."contents replaced by.(quotes preserved);SplitArchitectureuses the masked copy for regex +IndexOf(';')and the original for substring returns, soreportstrings survive untouched in the elaborated output.F-G-10 (elaborator artefact shadowing) — scanners skip
_elaborated_*.vhd.SourceElaborator.ScanProjectandHeadlessSimulator's scan loop both enumerate every.vhd*file in the sandbox. A leftover_elaborated_<entry>.vhdfrom 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,ElaborateOrNullreturned 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 setretain: trueon its sandbox projects, so each case starts empty.Cancellation surface through parse + synth descent.
Debugger.Repositories.Parser.ParseFile(CancellationToken)overload added;SimulationRunner.RunAsyncregisters_cts.Token.Register(() => _engine?.Stop())afterInitializeso the runner-side cancel actually breaks the engine's delta loop.HeadlessSimulator.RunAsyncbuilds a request-scoped CTS atPhase("start")(before parse) instead of inside SimulationRunner (after parse), soKMILA_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. AddedParseTrace.MAX_SKIP_ITER = 100_000cap + throw-with-context via the newParser.Models.ParserSkipOverflowExceptionon:VariableSyntetizer.Skip{Function,Component,Subtype,Alias,Procedure,Attribute,Disconnect,ToSemicolon}DeclarationplusTryParseParameterList; 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 outerVariableSyntetizerloop + entry-of every sub-structureSyntetize.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 butreport-based sweeps still work. Always-on: the responselog[]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.csInterpreter/Models/{Deferred,Dynamic}Index.cs+Interpreter/Services/VariableSyntetizer.cs+Interpreter/Services/Operation.cs.** Positional aggregates (("10101010", "10111011", …)) now parse into a clean concatenated bitstring ofElementSize × ArrayLengthbits;Variablecarries the newElementSize/ArrayLengthfields;DeferredIndex/DynamicIndexslice element-wise when the source hasElementSize > 1. New expression-modeDynamicIndexre-evaluatesrom_init(to_integer(unsigned(addr)))-shaped indices each delta.ParseIndexedAccessDeferredis paren-aware. Trap: Variable overloadsoperator==to compare names — useis null/is not nullfor reference checks.
Named-association aggregates + user-defined function bodies (Bench bug #19 follow-up) —
VariableSyntetizer.cs,Operation.cs,FunctionExecutor.cs,FunctionDefinition.cs.TryParseNamedAggregatehandles(0 => pack(170), …, others => …)placing each value atkey × ElementSizeoffset.SkipFunctionDeclarationnow parses signature + body and registers a realFunctionDefinitioninPackageRegistry.ParseUserDefinedFunctionCallinvokesFunctionExecutorwhen the function has a body. NewCachedScopeonFunctionDefinition— subsequent calls reuse the same parameterVariableinstances and only refresh.Value(20× perf win on heavy ROM init).Case-statement identifier + slice selector (Bench bugs #20, #21) —
DeltaCycleEngine.cs.ExecuteCaseStructureWithSchedulingAsyncnow extracts the slice whenHasSelectorSliceis true;ResolveChoiceForCaseresolves eachwhenchoice — hex literals, quoted bit strings, single-quoted bits, and identifier lookups against_allSignalssowhen OP_LOAD =>matches the binary value of OP_LOAD.P2A LOAD pipeline + HALT detect (Phase E).
Operation.ParseFunctionCall::TryConsumeSliceArgemits aDeferredIndexwhen an arg looks like<var>(N)/<var>(H downto L);StandardFunctions.GetStringValue/GetIntValuelearned to evaluate it;Operator.SolveComparationINTEGER branch falls back to ordinal string equality when both sides aren't numeric (enum literals live as their name strings);*operator widened toleftW + rightWper 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 treatedend ifas decrement + re-increment on the next keyword, truncating bodies at the first innerend. Now consumesend <closer>as a single match; loop-opener prefixfor X in Y loop/while X loopskips past the trailingloopkeyword. P4'skey_indexfunction was the trigger.Array attributes (
'length,'high,'low,'left,'right) —Operation.TryParseSignalAttribute. P4 neededto_unsigned(N, slot_cnt'length)which would otherwise corrupt the function-call arg list. Boolean edge attributes (event,last_value,stable,active) untouched.SourceElaboratorgeneric-default substitution —SourceElaborator.cs. NewExtractGenericDefaultsreads:=defaults from a child entity's generic clause; ordering: defaults first, then explicitgeneric map(...)overrides. Port-type texts are substituted too (soSTD_LOGIC_VECTOR(WIDTH-1 downto 0)aliases reach the parent asSTD_LOGIC_VECTOR(16-1 downto 0)).Engine perf: concurrent-task sensitivity gating (Phase H.3) —
DeltaCycleEngine.cs. New per-Operation cache_operationInputsrecords the static input-signal set (Variable,DeferredIndex.Source,DynamicIndex.Source+IndexSource; null = "always run" forDeferredFunction/ expression-modeDynamicIndex)._lastDeltaChangedSignalssnapshots 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 inParser/Models/Variable.cs:135only fires when_valueis already non-null. The object-initializer pattern at the signal-construction site setsSizefirst (no value yet) andValuesecond, so the marker landed inValueverbatim — 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 aVectorSize-wide bitstring before construction, gated onVectorSize > 0(sounsigned/signed/bit_vector/std_logic_vectorare 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 anAggregate_others_XVariable via the 4-arg constructor. PassingSize = 0to that constructor triggeredVariable.Size's own marker-expansion path which produced a zero-length string — the Aggregate Variable'sValuecollapsed 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)ParseOthersAggregatenow uses the object-initializer construction so the Size setter is never called, preserving the marker string inValue; (b) new helperSignalScheduler.ExpandAggregateMarker(newValue, target)runs at every queue-drained write inApplyCurrentUpdatesWithLines— if the payload is an(others=>X)marker andtarget.Size > 0, it expands to atarget.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_regrom_data_wget real values.** The remaining ACC stall on P3 is a practica content bug (eq_selROM-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 EleftW + rightWwidening; 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
subentity is inlined twice with different architecture-scope signal values (k_sigdriven from input portkin = 3andkin = 7), and a functionadd_offdeclared insub's architecture readsk_sigdirectly. The handoff hypothesis was thatCachedScope's reuse of parameter Variable instances would bind the function body's Operations to the first instance'sk_sigand 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 distinctFunctionDefinitionentries inPackageRegistry, 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'becausexnorwasn't in the case list. Nowa xnor b = '1' when a = b else '0'. Matches the hand-derivednot (a xor b)form, which had been the user's workaround.Services/Resources.cs—CalculateLUTsimplemented. Walks the statement tree (Tasks / Then / Else / Body / Cases / Branches) via reflection and sums operator weights: logic ops1 × bits, arithmetic+/−at3 × bits, comparators atmax(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 workaroundStimulusBuilderused to pre-seed tick 0. (2) Threaded adirectlyChangedHashSet 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 DEBUGguards 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 holdingFunctionDefinitionstubs for every function and procedure the parser has skipped. Cleared between fixtures alongsideEntityRegistry/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 inProcessVhdlCoderuns.case <slice>selectors (#10) —Process.SyntetizeBehaviournow recognisescase op(3 downto 0) is; bit indices are stored onCaseStructure.SelectorSliceHigh/Lowfor downstream synthesis.<sig>'rangeand<sig>'reverse_rangeinforloops (#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) —
ParseInitializationValueconstant-folds+ - * /chains soTOTAL : integer := BASE * TIMESresolves correctly. - Architecture-end detection bug fixed (#15/#18):
funcProcDepthno longer decrements on control-flow ends (end if,end loop,end case); arch-end matching is exact-name only.
Error reporting (#21):
Entity.SyntetizeandArchitecture.SyntetizeacceptIReadOnlyList<(int sourceLine, string text)>so diagnostics reference the user's source line numbers.- Missing-
;between port declarations now produces an explicitMissing ';' before '<name>' at Line N:Minstead 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 defaultLibraryCompiler(no host callbacks, embedded-resource fallback) and forwardsEnsureLoaded().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— addedCellKind.TO_INTEGER,TO_VECTOR,RESIZE,EDGE_DETECT. ExistingSHL/SHR/ROL/ROR/BUFreused 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 viaIEEEPrimitives.Register.Synthesis/ConcurrentAssignSynthesizer.cs— when the Shunting-yard walker encounters aDeferredFunction, it now resolves the call's args to nets and callsIEEEPrimitives.TryLower. On hit the emitter's output net joins the operand stack; on miss a placeholder constant +UnknownIEEEFunctiondiagnostic.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
Services/Operation.cs::ParseFunctionCall— when walking depth-1 arg tokens, if the current token is a known function name (viaStandardFunctions.IsStandardFunctionorFunctionRegistry.IsKnown) and the next token is(, recursively callParseFunctionCallto consume the nested call. The recursive call uses its own localparenDepth, so paren tracking stays correct.Synthesis/ConcurrentAssignSynthesizer.cs::ResolveDeferredArgs— addedcase DeferredFunction nestedthat recursively resolves the nested call's args and lowers it throughIEEEPrimitives.TryLower. Result: properly wired cell graphs.Models/DeferredFunction.cs::Evaluate— recursively evaluates nested DeferredFunctions to theirVariableresults before passing them toStandardFunctions.EvaluateFunction.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.RawHistoryandWaveformData.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 theWaveformTransition.Labelsemantics will visibly degrade the replay.- Phase B (line-highlight metadata per executed statement) will require instrumenting
DeltaCycleEngineto emit a frame log alongsideSignalHistory. SameExecutionTimelineDocconsumer; the contract is fixed inApp/Kmila/Kmila.Shared/Models/ExecutionTimeline.cs(TimelineStepshape withT,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]