Debugger 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.14.1 Last Updated: August 3, 2026
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 Debugger Module
This specific module, the Debugger, is a thin façade that bridges raw VHDL source files with the Interpreter data models. In the current state of the codebase it does two jobs:
- Run a fast structural pass to extract top-level
entity/architectureblocks and hydrateInterpreter.Services.Entity/Interpreter.Services.Architectureobjects from them. - Run a lightweight source-level lint pass (
RunDiagnostics) that emits structuredParser.Models.Diagnosticentries (missing;,begin/endimbalance) for the editor surface to render as Monaco markers — before the structural parse runs, so warnings still appear on broken sources.
It does not orchestrate the live simulation or manage breakpoints; those responsibilities live in Kmila.Shared.Services (SimulationParameters, SimulationCoordinator, etc.) and the Interpreter simulation runner. Debugger.csproj still ProjectReferences both Interpreter and TimeMachine because the structural pass hands the results back to Interpreter types and downstream consumers (Kmila.Shared) rely on TimeMachine snapshots being reachable from the same assembly graph — but nothing inside Debugger itself calls into TimeMachine. Earlier README revisions described a wider "central orchestrator" role that never materialised here; this module stayed pre-processing-only.
Core Responsibilities
- VHDL File Processing: Reading raw
.vhdlsource files from the filesystem (File.ReadAllTextin the constructor). - Structural Analysis: Walking the file line-by-line with a 3-state state machine (default → entity → architecture) to identify top-level blocks.
- Data Model Hydration: Constructing
EntityandArchitectureobjects (from theInterpretermodule) and attaching the architecture to its declaring entity viaentity.SetArchitecture(arch). - Source-level Diagnostics: Collecting non-fatal warnings about missing semicolons and
begin/endbalance inDiagnostics, suitable for direct mapping to Monaco'ssetModelMarkers. - Resilient parsing: The lint pass runs before the structural pass so that even if the structural pass throws, the editor still shows diagnostics for the user to act on.
3. Architecture of the Debugger Module
The Debugger module is designed as a high-level coordinator that prepares data for the other core components of the Kmila-9s project.
Key Components
Program.cs: A small console test harness that scans theCodes/directory for.vhdlfiles and parses each one, printing the entity count it found per file. Useful for smoke-testing the parser bridge in isolation.Repositories/Parser.cs: The only real class in the module. Reads a.vhdlfile in the constructor, exposes:string FileContent { get; private set; }— raw file text.List<Entity> Entities { get; private set; }— entities discovered (with their architectures already attached after parsing).List<Parser.Models.Diagnostic> Diagnostics { get; private set; }— structured warnings produced by the lint pass.void ParseFile()— runs the lint pass first, then walks the file with the 3-state state machine to populateEntities. Forwards toParseFile(CancellationToken.None).void ParseFile(CancellationToken cancellationToken)— cancellable overload added in Session 22 pass 4 (F-G-08). Threads the token into bothentity.Syntetize(rawEntity, ct)andarch.Syntetize(rawEntity, ct)so the wall-timeout hook (KMILA_SIM_WALL_TIMEOUT_MSinHeadlessSimulator) can actually interrupt a stuck synthesis descent instead of waiting for the HTTP client to disconnect. Prior to pass 4 the parser had no cancellation surface.private void RunDiagnostics(string source)— the lint pass itself.private static bool LooksLikeStatement(string lower)— heuristic that decides which lines should require a trailing;.
Core Data Models
This module uses data models defined in two upstream modules:
Interpreter.Services.Entity— represents a VHDL entity, including its ports and generics.Interpreter.Services.Architecture— represents a VHDL architecture;Syntetize(string[] lines)is invoked here once the architecture body has been collected.Parser.Models.Diagnostic+Parser.Models.DiagnosticSeverity— structured diagnostic record used to surface lint findings to the Editor page. The Debugger never instantiatesParser.Models.ParserExceptiondirectly; that is the job of the upstreamParserproject, but the Editor maps both into the same diagnostic stream.
Lint diagnostics emitted
| Code | Severity | Trigger |
|---|---|---|
VHD-MISSING-SEMICOLON |
Warning | Line looks like a statement (<=, :=, =>, or starts with library/use/signal/variable/constant) but does not end with ;, ,, is, then, else, loop, begin, generate, (, or ). |
VHD-BLOCK-BALANCE |
Warning | Total begin keyword count does not match total end keyword count across the whole file. Reported once at the last line. |
Errors thrown by the upstream Parser project propagate as Parser.Models.ParserException; callers (e.g. the Editor page) typically convert them with Diagnostic.FromException(...) so they appear in the same Monaco marker stream.
4. How to Use This Module
To use the debugger's parsing functionality, you provide the path to a VHDL file to the Debugger.Repositories.Parser. The ParseFile() method then processes the file, runs the lint pass, and populates the Entities and Diagnostics lists.
using Debugger.Repositories;
using System;
// This example demonstrates the current file processing capability.
var files = Directory.GetFiles("Codes");
foreach (var file in files)
{
try
{
// 1. Initialize the Debugger's Parser with a file path.
Parser vhdlFileParser = new(file);
// 2. Process the file to identify entities and architectures.
// The lint pass runs first, even if the structural pass later throws.
vhdlFileParser.ParseFile();
// 3. The parsed entities are now available for the Interpreter.
Console.WriteLine($"File: {file}, Entities found: {vhdlFileParser.Entities.Count}");
// 4. Surface non-fatal lint warnings (mirrors what the Editor page does
// when wiring Monaco's setModelMarkers).
foreach (var d in vhdlFileParser.Diagnostics)
{
Console.WriteLine($" [{d.Severity}] L{d.Line}:{d.StartColumn} {d.Code} — {d.Message}");
}
}
catch (Exception ex)
{
// Structural failures (e.g. "Entity not found") propagate. The editor
// surface typically catches these and maps them to error-level
// diagnostics via Diagnostic.FromException(...).
Console.WriteLine($"An error occurred while parsing {file}: {ex.Message}");
}
}
5. Dependencies
This module integrates other key parts of the Kmila-9s project and relies on:
InterpreterModule: For its data models (Entity,Architecture) and the architecture-synthesis entry point (Architecture.Syntetize).ParserModule: For two things — theTokenizer(currently instantiated but reserved for future structural rewrites) and theParser.Models.Diagnostic/Parser.Models.DiagnosticSeveritytypes used by the lint pass.- .NET 9 BCL only otherwise — no
Microsoft.Extensions.Logging, noTimeMachinedependency. The Debugger is a pre-processing layer; the live simulation is driven from theKmila.SharedEditor stack, not from this project.
6. Debugger Flow and Class Relationships
The following diagrams illustrate the actual data flow and class relationships of the module as it stands today. Earlier revisions of this README depicted a "State Manager / Time Travel Logic / Simulation Orchestrator" subsystem inside the Debugger that never existed in the source — it has been removed.
6.1. High-Level Data Flow
graph TD
A["VHDL Files (.vhdl)"] --> B["Debugger.Repositories.Parser"]
B -->|RunDiagnostics first| D["List<Parser.Models.Diagnostic><br/>(warnings)"]
B -->|Structural state machine| C["Interpreter.Services.Entity<br/>+ Architecture (with body synthesised)"]
D --> E["Editor (Monaco markers)<br/>[External — Kmila.Shared]"]
C --> F["Interpreter Simulation<br/>[External]"]
style B fill:#ccf,stroke:#333,stroke-width:2px
style D fill:#fec,stroke:#333,stroke-width:1px
style F fill:#bbf,stroke:#333,stroke-width:2px
6.2. Detailed Class Diagram
classDiagram
direction LR
class Program {
+static Main(args : string[])
}
class DbgParser["Debugger.Repositories.Parser"] {
+FileContent : string
+Entities : List~Entity~
+Diagnostics : List~Diagnostic~
-_excludedEnds : List~string~
+Parser(filePath : string)
+ParseFile() void
-RunDiagnostics(source : string) void
-LooksLikeStatement(lower : string)$ bool
}
class IntEntity["Interpreter.Services.Entity"] {
<<External>>
+Name : string
+Ports
+Generics
+SetArchitecture(arch)
}
class IntArch["Interpreter.Services.Architecture"] {
<<External>>
+Syntetize(lines : string[])
}
class Tok["Parser.Repositories.Tokenizer"] {
<<External, reserved>>
}
class Diag["Parser.Models.Diagnostic"] {
<<External>>
+Severity : DiagnosticSeverity
+Line : int
+StartColumn : int
+EndColumn : int
+Code : string
+Message : string
}
Program --> DbgParser : creates and uses
DbgParser --> IntEntity : instantiates
DbgParser --> IntArch : instantiates and Syntetize()
DbgParser ..> Tok : instantiates (currently unused)
DbgParser --> Diag : appends warnings
6.3. Sequence Diagram: Parsing a File
sequenceDiagram
participant Main
participant DbgParser as "Debugger.Repositories.Parser"
participant FileIO as "File IO"
participant Arch as "Interpreter.Services.Architecture"
Main->>DbgParser: new(filePath)
activate DbgParser
DbgParser->>FileIO: ReadAllText(filePath)
FileIO-->>DbgParser: file content
deactivate DbgParser
Main->>DbgParser: ParseFile()
activate DbgParser
DbgParser->>DbgParser: RunDiagnostics(FileContent)
Note right of DbgParser: emits VHD-MISSING-SEMICOLON,<br/>VHD-BLOCK-BALANCE warnings
loop For each non-empty line
DbgParser->>DbgParser: Detect entity / architecture starts
DbgParser->>DbgParser: Collect lines until "end ...;"
end
DbgParser->>DbgParser: new Entity(rawLines)
DbgParser->>Arch: new Architecture(ports, generics)
DbgParser->>Arch: Syntetize(rawLines)
DbgParser->>DbgParser: entity.SetArchitecture(arch)
deactivate DbgParser
DbgParser-->>Main: Entities + Diagnostics populated
6.4. Module Diagram
graph TD
vhdl_code["VHDL Code Files<br>/Codes/*.vhdl"]
subgraph debugger_boundary["Debugger Project"]
program["Program.cs<br>(test harness)"]
dbg_parser["Repositories/Parser.cs<br>(structural pass + lint pass)"]
program -->|invokes| dbg_parser
end
subgraph parser_project["Parser Project [External]"]
diagnostic["Parser.Models.Diagnostic<br>+ DiagnosticSeverity"]
tokenizer["Parser.Repositories.Tokenizer<br>(instantiated, reserved)"]
end
subgraph interpreter_project["Interpreter Project [External]"]
entity["Interpreter.Services.Entity"]
architecture["Interpreter.Services.Architecture<br>.Syntetize(lines)"]
end
subgraph kmila_shared["Kmila.Shared [External — actual UI consumer]"]
editor_page["Pages/Editor.razor<br>(Monaco markers)"]
sim_coord["Services/SimulationParameters,<br>SimulationCoordinator"]
end
dbg_parser -->|reads| vhdl_code
dbg_parser -->|emits warnings into| diagnostic
dbg_parser -.->|references| tokenizer
dbg_parser -->|hydrates| entity
dbg_parser -->|hydrates and synthesises| architecture
editor_page -->|consumes Diagnostics + maps via Diagnostic.FromException| dbg_parser
sim_coord -->|drives the actual simulation, NOT the Debugger| architecture
7. Current Limitations
The following limitations exist in the current implementation:
- Tokenizer Not Actively Used: The
Tokenizerclass from the Parser module is instantiated insideParseFile()but its tokens are not consumed. The structural pass still uses line-level string matching. Reserved for a future rewrite. - VHDL Comments: The lint pass strips
--line comments before analysis, but multiline / block comment forms are not specially handled (VHDL itself does not have/* ... */). - Simple Parsing Approach: Structure detection relies on
comparer.StartsWith("entity")/"architecture"— no real AST is built in this project. AST-style parsing happens in the upstreamParserproject. - Single Entity-Architecture Assumption: Architectures are matched to entities declared earlier in the same file, by name. Out-of-order declarations and cross-file lookups raise
Exception("Entity not found"). - Single Diagnostic Pass:
RunDiagnosticsonly emits two codes today (VHD-MISSING-SEMICOLON,VHD-BLOCK-BALANCE). Richer rules (port direction, type mismatches, unused signals) live in the simulation pipeline, not here. - No Simulation, No TimeMachine: Despite the project name, this module does not run a simulation or talk to
TimeMachine. Live debugging is driven fromKmila.Shared.Servicesagainst theInterpreter. - Stray
test_output.vcdin the module root: ADebugger/test_output.vcdfile sits at the module root. It is a leftover artifact, not produced by this module (which runs no simulation and emits no VCD — see above). Treat it as safe to ignore/remove during cleanup; it plays no part in the Debugger's pre-processing pass.
7.bis. Audit diagrams (2026-05-09)
These diagrams complement sections 6.1–6.4. They were added during the cross-module
audit because the existing TT1 diagrams either misrepresented this module
(fig_5_2_c4_contenedores claimed Debugger delegates tokenization to Parser and
coordinates with TimeMachine — neither is true) or did not depict the lint-first
pipeline at all. Sources also live in Documentacion/TT1/diagrams/.
fig_5_5_12_clases_debugger — Debugger class detail
classDiagram
direction TB
class DbgParser {
+string FileContent
+List~Entity~ Entities
+List~Diagnostic~ Diagnostics
-List _excludedEnds
-int funcProcDepth
+ParseFile() void
+RunDiagnostics(content) void
-LooksLikeStatement(line) bool
}
class Tokenizer {
<<from Parser>>
+ReadFile(path) void
+Evaluate(token) void
}
class Diagnostic {
<<from Parser.Models>>
+Severity Severity
+int Line
+int StartColumn
+int EndColumn
+string Code
+string Message
+FromException(ex)$ Diagnostic
}
class Entity {
<<from Interpreter.Services>>
+Syntetize(lines: List~tuple~) void
+SetArchitecture(arch) void
}
class Architecture {
<<from Interpreter.Services>>
+Syntetize(rawEntity: List~tuple~) void
}
DbgParser ..> Tokenizer : instancia (no consume tokens)
DbgParser "1" *-- "*" Entity : produce
DbgParser "1" *-- "*" Diagnostic : emite
Entity "1" *-- "1" Architecture : compone
fig_5_11_12_act_debugger_lint_resilience — Lint-first pipeline
flowchart TD
A([DbgParser.ParseFile]) --> B[Cargar FileContent desde disco]
B --> C[**Lint pass**: RunDiagnostics FileContent]
C --> D[Por cada linea]
D --> E{LooksLikeStatement?}
E -->|Si y falta `;`| F[Diagnostic VHD-MISSING-SEMICOLON]
E -->|No| G[Skip]
D --> H[Contar begin / end por bloque]
H --> I{Balance?}
I -->|No| J[Diagnostic VHD-BLOCK-BALANCE]
F --> K[Diagnostics.Add]
J --> K
G --> L
K --> L[Continuar siguiente linea]
L --> M{Quedan lineas?}
M -->|Si| D
M -->|No| N[Lint pass completo: Diagnostics persistentes]
N --> O[**Structural pass**: maquina line-based<br/>default -> in-entity -> in-architecture]
O --> P{Excepcion en parsing?}
P -->|Si| Q[Capturar y agregar a Diagnostics]
Q --> R([Retornar: Entities parciales + Diagnostics completos])
P -->|No| S[Crear Entity / Architecture<br/>con tuplas List int string]
S --> T[Entity.Syntetize / Architecture.Syntetize]
T --> U[Entity.SetArchitecture]
U --> V([Retornar: Entities completas + Diagnostics])
8. Changelog
Version 1.14.1 (2026-05-01)
Documentation Re-alignment
- Section 2 / "Core Responsibilities": Removed the long-standing claim that the Debugger module orchestrates
Parser+Interpreter+TimeMachine. The current code is pre-processing only (structural extraction + lint diagnostics). Live simulation has always been driven byKmila.Shared.Services. - Section 3 / Key Components: Added the
Diagnosticsproperty, theRunDiagnostics(string)lint pass, and theLooksLikeStatement(string)helper. Added an explicit "Lint diagnostics emitted" subsection enumerating the two codes (VHD-MISSING-SEMICOLON,VHD-BLOCK-BALANCE). - Section 4 / Usage example: Updated the snippet to surface
Diagnostics(the property the editor actually consumes), and clarified thatParser.Models.ParserExceptionis mapped viaDiagnostic.FromException(...)upstream. - Section 5 / Dependencies: Removed the bogus
TimeMachineandMicrosoft.Extensions.Loggingdependencies — the Debugger referencesInterpreterandParseronly, on plain .NET 9. - Section 6 / Diagrams — all four regenerated:
- §6.1 now shows the lint pass running before the structural pass and feeding the editor independently.
- §6.2 lists the real public surface (
Diagnostics,_excludedEnds, theRunDiagnostics/LooksLikeStatementhelpers) and adds theParser.Models.Diagnosticexternal class. - §6.3 inserts the
RunDiagnosticsstep and the call toArchitecture.Syntetize. - §6.4 removed the fictional "State Manager" and "Time Travel Logic" boundaries and replaced them with the real consumer surface (
Kmila.Shared.Pages.EditorandSimulationCoordinator).
- Section 7 / Limitations: Refined the comment-handling note (line comments are stripped, no block-comment form exists in VHDL anyway), added "Single Diagnostic Pass" + "No Simulation, No TimeMachine" to set correct expectations.
Version 0.6.1 (2026-02-21)
Documentation Fixes
- README.md: Fixed module diagram to correctly reference
Parser.Repositories.Tokenizerinstead ofInterpreter.Services.Tokenizer - README.md: Updated VHDL Models node to reference both
Interpreter.ModelsandInterpreter.Servicesnamespaces (Entity is in Services, models in Models)
Version 0.6.0 (2025-12-13)
Documentation Improvements
Program.cs: Added comprehensive XML documentation including:
- Class-level summary explaining the test harness purpose
- Method documentation with processing steps
- Expected directory structure diagram
- Exception documentation
Repositories/Parser.cs: Added complete XML documentation including:
- Class-level documentation explaining the state machine algorithm
- Detailed remarks about parsing limitations
- Property documentation for
FileContentandEntities - Private field documentation for
_excludedEnds - Constructor and method documentation with step-by-step processing details
- Inline comments explaining state transitions and entity/architecture association
README.md: Added version information, last updated date, current limitations section, and this changelog
Version 1.14.0 (2026-04-19)
Repositories/Parser.cs— diagnostics collection. Added aDiagnosticsproperty (typed asglobal::Parser.Models.Diagnosticto dodge the namespace collision between this file'sParserclass and theParserproject) plus a lightweightRunDiagnostics(source)lint pass that catches missing;at statement ends andbegin/endimbalance before falling through to the full parser. Feeds directly into Monaco'ssetModelMarkersfrom the editor.ParserExceptioninstances are also coerced viaDiagnostic.FromException(...)into the same list, so surfacing in the editor is uniform regardless of whether the error came from the lint pass or the parser throwing.- Build hygiene —
<NoWarn>added for XML-doc ripple so the Debugger project builds cleanly alongside the rest of the solution.
What's new in v1.15 (2 May 2026)
- Source-line-accurate diagnostics (#21).
ParseFilenow collects each non-blank line as(lineIndex, text)and feeds those tuples to the newEntity.Syntetize(IReadOnlyList<(int, string)>)/Architecture.Syntetize(IReadOnlyList<(int, string)>)overloads, which callTokenizer.TokenizeLineAt. Error messages reference the user's actual file lines instead of the tokenizer's internal call counter. - Lint diagnostics surface even on parse failure.
Program.MainwrapsParseFile()in try/catch and prints everyDiagnostic(e.g.VHD-MISSING-SEMICOLON,VHD-BLOCK-BALANCE) before re-emitting the structural error. Previously a throw masked all upstream lint output. - Architecture-slicing bug fixed. The substring-match fallback
comparer.Contains(currentArchName.ToLower())no longer mistakesend maxv;for arch end ofarchitecture A of …. Exact-name matching only, plus a control-flow keyword exclusion list (end if/loop/case/for/while/generate/record). - Workstation GC enabled at the project level (#7).
Full release notes: ../Documentacion/CHANGELOG_v1.15.md.
Last Updated: 2026-08-03