VHDL Parser 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.3.0 (branch v1.3.0)
Last Updated: 2026-08-03
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 Parser Module
This specific module, the VHDL Parser, serves as the foundational first step in the simulation pipeline. Its sole responsibility is to read raw VHDL source code (.vhdl files) and transform it into a structured, in-memory representation using C# classes. This structured data is then passed to the simulation engine for execution and analysis.
The parser is designed to be robust, handling complex VHDL syntax and providing clear error messages for invalid code.
Core Responsibilities
- Lexical Analysis: Breaking down the raw VHDL code into a stream of individual tokens (keywords, identifiers, operators, literals, etc.).
- Syntactic Analysis: Validating the sequence of tokens against the VHDL grammar rules to understand the code's structure.
- Abstract Syntax Tree (AST) Generation: Building a tree-like structure of C# objects that represents the VHDL design, including its entities, architectures, signals, processes, and sequential statements.
- Scope Management: Tracking all declared identifiers (signals, variables, ports, etc.) and the scope in which they are valid (e.g., entity, architecture, process).
3. Architecture of the Parser Module
The parser is built with a modular, object-oriented design where different classes are responsible for parsing specific parts of the VHDL language.
Key Components
Tokenizer.cs: The first stage of the pipeline. It reads a.vhdlfile or a raw string, removes comments, and uses regular expressions to split the code into a list of raw string tokens. It then evaluates these tokens to classify them into types (e.g.,KEYWORD,IDENTIFIER) and creates a list ofTokenobjects.TokenStream.cs: A wrapper around the list ofTokenobjects generated by theTokenizer. It provides a stream-like API (Current,Previous,MoveNext,Expect,Match,SetRollBack,RollBack) that allows the parser classes to easily consume and validate tokens in sequence. Also includesStreamerExceptionfor detailed token-related error reporting.Parser.cs: The main orchestrator. It initializes theTokenStreamand delegates the parsing of top-level VHDL design units (entity,architecture,package) to their specialized parser classes.ParseV2()additionally handleslibrary/useclauses anywhere between design units (multiple library declarations are tolerated) and walksconfigurationblocks in-place viaParseConfigurationBlock()/ParseConfigurationBody()— including thefor CompLabel : CompType use entity work.X(Arch);binding form.Structural Parsers (
IParseStructures):EntityParser.cs: Parses anentityblock, including its name and delegations forgenericandportblocks.ArchitectureParser.cs: Parses anarchitectureblock, including its header (signal/constant declarations) and body (processes, concurrent statements).PackageParser.cs: Parsespackageandpackage bodydeclarations.
Declaration Parsers (
IParseDeclarations):PortsParser.cs: Handles theport (...)block within an entity, creatingVariableobjects for each port.GenericParser.cs: Handles thegeneric (...)block, parsing generic constants.ArchitectureAssignationParser.cs: Parses declarations in an architecture's header, such assignalandconstant.
Statement & Expression Parsers:
DataStructuresParser.cs: A versatile parser for sequential statements found inside processes, functions, and architecture bodies. It can parseif-then-elsif-else,case-when,for-loop,while-loop,exit,wait,null,return, and signal/variable assignments. Supports both concurrent and sequential statement contexts.FunctionParser.cs: Parses function declarations including parameters, return types (with complex bounds), local variable declarations, and function bodies with sequential statements.FunctionAssignationParser.cs: Parses the parameter declarations within a function signature.VectorParser.cs&RangeParser.cs: Helper parsers that handle specific syntax for vector types (e.g.,std_logic_vector(7 downto 0),signed,unsigned) and ranges (e.g.,integer range 0 to 255). Also handles vector attributes like'length,'range,'high,'low.
Core Data Models
Token.cs: Represents a single lexical unit from the source code. It contains itsValue(e.g., "entity", "clk"),Type(e.g.,KEYWORDS), and location (Line,Col).Variable.cs: A crucial class that represents any named object in VHDL (a signal, port, variable, or constant). It stores itsName, dataType,Value, andSize. It also includes anOnValueChangeevent to notify the simulation engine of updates. 2026-06-22 addition:ElementSize+ArrayLengthcarry the per-element width and slot count for array-typed signals (e.g.signal mem : rom_twhererom_t = array(0 to 7) of std_logic_vector(7 downto 0)lands asElementSize=8,ArrayLength=8).DeferredIndex/DynamicIndexconsult these to slice element-wise onmem(N)reads — without them the indexer fell back to per-bit reads and returned garbage. Watch out:Variableoverloadsoperator==to compare names (not references), so always useis null/is not nullfor reference checks.Constants.cs: A static class that centralizes all constants used by the parser, including:enum TYPE: Defines all possible token types.enum DATATYPES: Defines all supported VHDL data types.- Regular expressions for identifying each token type.
ParserException.cs: A custom exception class used to report detailed syntax and semantic errors during parsing, including:- Line and column numbers (1-based) for error location
- Context information (the token value where the error occurred)
- Predefined error codes (
INITIALIZATION,DUPLICATE_DECLARATION,MISSING_STRUCTURE,DUPLICATE_STRUCTURE,MISSING_DECLARATION,SYNTAX_ERROR) - Factory methods for common errors:
UnexpectedToken(),UndeclaredVariable(),TypeMismatch()
Supported VHDL Constructs
The parser supports a comprehensive set of VHDL language features:
- Top-level Structures:
entity,architecture,package,package body,configuration - Declarations:
signal,constant,variable,attribute,alias,component,disconnect,type,subtype - Custom Types:
recordtypes (with field declarations),arraytypes (with index constraints), enumeration types, subtypes with constraints - Port and Generic: Full port/generic declarations with vector types, ranges, and default values
- Sequential Statements:
if-then-elsif-else,case-when,for-loop,while-loop,exit,wait,null,return - Concurrent Statements: Signal assignments, component instantiations with
port mapandgeneric map - Generate Statements:
for-generate,if-generate, nested generates with local signal/constant declarations - Block Statements: Full block statement support with guard expressions, local declarations, and nested blocks
- Labeled Constructs: Labeled processes, labeled generate statements, labeled component instantiations, labeled blocks
- Functions: Function declarations with parameters, local variables, return type bounds (including complex expressions), and complex expressions
- Expressions: Arithmetic (
+,-,*,/,mod,**), logical (and,or,xor,nand,nor,xnor,not,sll,srl,sla,sra,rol,ror), comparison (=,/=,<,>,<=,>=) - Vector Operations: Indexed access (e.g.,
signal(7 downto 0),signal(i)), vector attributes (e.g.,A'length,A'range,A'high,A'low,A'left,A'right,A'event,A'last_value,A'stable,A'quiet,A'active,A'transaction) - Data Types:
std_logic,std_logic_vector,integer,boolean,bit,bit_vector,natural,positive,signed,unsigned,std_ulogic,std_ulogic_vector,boolean_vector,integer_vector,character,string,time,real - Special Constructs:
rising_edge,falling_edge, aggregate expressions (e.g.,(others => '0')), function calls in bounds (e.g.,clog2(N)), concatenation operator (&),aftertiming specifications with time units (fs,ps,ns,us,ms,sec,min,hr)
Standard Library Functions
The parser recognizes the following IEEE standard library functions (these do not require explicit declaration):
| Category | Functions |
|---|---|
| IEEE.numeric_std Conversion | to_unsigned, to_signed, to_integer, unsigned, signed |
| IEEE.numeric_std Resize/Shift | resize, shift_left, shift_right |
| IEEE.std_logic_1164 | std_logic_vector, std_ulogic_vector, to_stdlogicvector, to_stdulogicvector, to_bit, to_bitvector, to_x01, to_x01z, to_ux01 |
| Math Functions | clog2, log2, ceil, floor, abs, minimum, maximum |
| Type Conversions | conv_integer, conv_unsigned, conv_signed, conv_std_logic_vector, integer, natural, positive, real, boolean, bit, character, string |
| Edge Detection | rising_edge, falling_edge |
| IEEE.math_real | sqrt, sin, cos, tan, exp, log, pow |
| Other | now, time |
Block Statement Support
Block statements provide a way to group concurrent statements with optional guard expressions and local declarations:
my_block : block (guard_signal = '1') is
signal local_sig : std_logic;
begin
guarded output <= input;
end block my_block;
Supported features:
- Guard expressions (optional)
- Local signal/constant declarations within blocks
- Nested blocks (blocks inside blocks)
- Labeled blocks with optional end labels
- Guarded signal assignments with the
guardedkeyword
Custom Type Support
The parser supports user-defined types including records, arrays, and enumerations:
-- Record type declaration
type ComplexNumber is record
re : signed(15 downto 0);
im : signed(15 downto 0);
end record;
-- Array type declaration
type MemoryArray is array (0 to 255) of std_logic_vector(7 downto 0);
-- Enumeration type declaration
type State is (IDLE, RUNNING, STOPPED, ERROR);
-- Subtype declaration
subtype SmallInt is integer range 0 to 100;
Supported features:
- Record types with multiple fields of various data types
- Array types with index constraints (using
toordownto) - Enumeration types with named constants (added to scope automatically)
- Subtype declarations with range constraints
Component Instantiation
The parser fully supports component instantiations with port and generic mappings:
-- Component declaration in architecture header
component Adder
generic (WIDTH : integer := 8);
port (
A, B : in std_logic_vector(WIDTH-1 downto 0);
Sum : out std_logic_vector(WIDTH-1 downto 0)
);
end component;
-- Component instantiation
U1 : Adder
generic map (WIDTH => 16)
port map (A => input_a, B => input_b, Sum => result);
Scope Naming Convention
Architecture scopes use the naming convention {entityName}_{archName} to support multiple architectures with the same name for different entities:
Entity "Adder" with architecture "Behavioral" → scope key: "Adder_Behavioral"
Entity "Counter" with architecture "Behavioral" → scope key: "Counter_Behavioral"
TokenStream Rollback Feature
The TokenStream class provides rollback capability for backtracking during parsing:
// Mark current position
stream.SetRollBack();
// Try to parse something
try {
ParseComplexConstruct();
} catch {
// Restore to marked position if parsing fails
stream.RollBack();
ParseAlternativeConstruct();
}
4. How to Use This Module
To use the parser, you instantiate the main Parser class, providing it with the path to a VHDL file. Then, you call the ParseV2() method to begin the process.
using Parser.Repositories;
using System;
try
{
// 1. The Tokenizer reads the file and performs lexical analysis.
Tokenizer tokenizer = new("path/to/your/design.vhdl");
// 2. The Parser class orchestrates the syntactic analysis.
Parser vhdlParser = new(tokenizer);
vhdlParser.ParseV2();
// 3. After parsing, the results are available in the Scopes dictionary.
// This dictionary contains all entities, architectures, and their declared variables.
var scopes = vhdlParser.GetScopes();
Console.WriteLine("Parsing completed successfully!");
// You can now inspect the parsed data structures.
foreach (var scope in scopes)
{
Console.WriteLine($"Scope: {scope.Key}");
foreach (var variable in scope.Value)
{
Console.WriteLine($" - Variable: {variable.Name}, Type: {variable.Type}");
}
}
}
catch (Exception ex)
{
Console.WriteLine($"An error occurred during parsing: {ex.Message}");
}
5. Dependencies
This module relies on the following .NET libraries:
Microsoft.Extensions.Logging: For logging information and errors during the parsing process.
All other code is self-contained within this project module.
6. Parser Flow and Class Relationships
To better understand the parser's internal workings, the following diagrams illustrate the data flow, class relationships, and sequence of operations.
6.1. High-Level Parsing Flow
This diagram shows the overall process, starting from the raw VHDL file and ending with the structured Scopes dictionary that the simulation engine can use.
graph TD
A["VHDL File (.vhdl)"] --> B(Tokenizer);
B --> C{"List<Token>"};
C --> D[TokenStream];
D --> E(Parser);
E --> F(EntityParser);
E --> G(ArchitectureParser);
E --> H(PackageParser);
subgraph "Parsing Execution"
F --> I{Scopes};
G --> I;
H --> I;
end
I --> J[Simulation Engine];
style B fill:#f9f,stroke:#333,stroke-width:2px
style E fill:#ccf,stroke:#333,stroke-width:2px
style J fill:#bbf,stroke:#333,stroke-width:2px
6.2. Detailed Class Diagram
This diagram details the composition and inheritance relationships between the key classes in the parser module. It highlights how specialized parsers are composed to handle different parts of the VHDL syntax.
classDiagram
direction LR
class Parser {
+ParseV2()
+Parse()
+GetScopes() : Dictionary~string, HashSet~Variable~~
}
class Tokenizer {
+ReadFile()
+ReadString(string)
+TokenizeLine(string)
+Evaluate(string) : Token
+GetDictionaryType() : List~Token~
}
class TokenStream {
+Current : Token
+Previous : Token
+MoveNext()
+Expect(TYPE)
+Match(TYPE) : bool
+SetRollBack()
+RollBack()
+EndStream() : bool
}
class EntityParser {
+Parse()
+ParseDeclaration()
+GetName() : string
+ExistsVariable(string) : bool
}
class ArchitectureParser {
+Parse()
+ParseDeclaration()
+ParseHeader()
+ParseBodyDeclaration()
+ParseGenerateStatement()
+ParseBlockStatement()
+ParseComponentInstantiation()
}
class PackageParser {
+Parse()
+ParseDeclaration()
+ParseBodyDeclaration()
+ParseProcedureBody()
}
class DataStructuresParser {
+Parse()
+ParseSequentialBody()
+ParseProcess()
+ParseIfStructure()
+ParseCaseStructure()
+ParseForStructure()
+ParseWhileStructure()
+ParseAsignations()
+ParseOperation()
}
class FunctionParser {
+Parse()
+ParseDeclaration()
+ParseBodyDeclaration()
+ParseReturnTypeBounds()
}
class GenericParser {
+Parse()
+ParseStructure()
+ParseAssignations()
+ParseAssignationsWithComas()
}
class PortsParser {
+Parse()
+ParseDeclarations()
+ParseTypeBounds()
}
class ArchitectureAssignationParser {
+Parse()
+ParseDeclarations()
+ParseAttributeDeclaration()
+ParseDisconnectSpecification()
}
class FunctionAssignationParser {
+Parse()
+ParseDeclarations()
}
class VectorParser {
+Parsed : bool
+Parse()
+ParseBody()
+ParseAttributes()
+ParseBoundExpression()
}
class RangeParser {
+Parse()
+ParseBody()
}
subgraph Models
class Token {
+Value : string
+Type : TYPE
+Line : int
+Col : int
}
class Variable {
+Name : string
+Type : DATATYPES
+Value : object
+Size : int
+IsPort : bool
+PortType : PortType
+IsStub : bool
+CustomTypeName : string?
+RecordFields : Dictionary?
+ElementSize : int
+ArrayLength : int
+OnValueChange : event
}
class Constants {
<<static>>
+TYPE enum
+DATATYPES enum
+ParseExceptions enum
+OperatorType enum
+PortType enum
}
class ParserException {
+Line : int
+Column : int
+ErrorCode : ParseExceptions
+Context : string
+UnexpectedToken()$ ParserException
+UndeclaredVariable()$ ParserException
+TypeMismatch()$ ParserException
}
class Diagnostic {
+Severity : DiagnosticSeverity
+Line : int
+StartColumn : int
+EndColumn : int
+Code : string
+Message : string
+FromException(ParserException)$ Diagnostic
}
class DiagnosticSeverity {
<<enumeration>>
Info
Warning
Error
}
end
Parser o-- TokenStream : uses
Parser o-- EntityParser : creates
Parser o-- ArchitectureParser : creates
Parser o-- PackageParser : creates
Tokenizer --> TokenStream : provides tokens for
EntityParser o-- GenericParser : creates
EntityParser o-- PortsParser : creates
ArchitectureParser o-- DataStructuresParser : creates
ArchitectureParser o-- FunctionParser : creates
ArchitectureParser o-- ArchitectureAssignationParser : creates
PackageParser o-- DataStructuresParser : creates
PackageParser o-- FunctionParser : creates
FunctionParser o-- DataStructuresParser : creates
FunctionParser o-- FunctionAssignationParser : creates
PortsParser o-- VectorParser : uses
GenericParser o-- VectorParser : uses
GenericParser o-- RangeParser : uses
VectorParser <|-- RangeParser : inherits from
6.3. Sequence Diagram: Parsing an Entity
This sequence diagram shows the runtime interactions between objects when the ParseV2() method encounters an entity block in the VHDL code.
sequenceDiagram
participant User
participant Parser
participant TokenStream
participant EntityParser
participant GenericParser
participant PortsParser
User->>Parser: ParseV2()
Parser->>EntityParser: new(stream, scopes)
Parser->>EntityParser: Parse()
activate EntityParser
EntityParser->>TokenStream: Expect(KEYWORD 'entity')
EntityParser->>TokenStream: Current (get entity name)
loop until 'is'
EntityParser->>TokenStream: MoveNext()
end
alt 'generic' keyword found
EntityParser->>GenericParser: new(stream, scopes, name)
EntityParser->>GenericParser: Parse()
activate GenericParser
loop Parse generic declarations
GenericParser->>TokenStream: Expect/MoveNext
end
deactivate GenericParser
end
alt 'port' keyword found
EntityParser->>PortsParser: new(stream, scopes, name)
EntityParser->>PortsParser: Parse()
activate PortsParser
loop Parse port declarations
PortsParser->>TokenStream: Expect/MoveNext
end
deactivate PortsParser
end
EntityParser->>TokenStream: Expect(KEYWORD 'end')
deactivate EntityParser
6.4. Module Diagram
graph TD
vhdl_files["VHDL Source Files<br>(.vhdl)"]
subgraph parser_system["VHDL Parser Module"]
subgraph repositories["Repositories"]
tokenizer["Tokenizer<br>/Repositories/Tokenizer.cs"]
token_stream["TokenStream<br>/Repositories/TokenStream.cs"]
parser_core["Parser<br>/Repositories/Parser.cs"]
structural_parsers["Structural Parsers<br>EntityParser, ArchitectureParser,<br>PackageParser"]
declaration_parsers["Declaration Parsers<br>PortsParser, GenericParser,<br>ArchitectureAssignationParser,<br>FunctionAssignationParser"]
statement_parsers["Statement Parsers<br>DataStructuresParser, FunctionParser"]
helper_parsers["Helper Parsers<br>VectorParser, RangeParser"]
end
subgraph models["Models"]
token_model["Token"]
variable_model["Variable"]
constants_model["Constants"]
exception_model["ParserException"]
diagnostic_model["Diagnostic<br>+ DiagnosticSeverity"]
end
subgraph interfaces["Interfaces"]
iparser["IParser, IParserBaseMethods"]
iparse_structures["IParseStructures"]
iparse_declarations["IParseDeclarations"]
iparse_vector["IParseVectorStructure"]
iparse_generic["IParseGeneric"]
end
end
scopes["Scopes Dictionary<br>Dictionary<string, HashSet<Variable>>"]
downstream["Downstream Modules<br>(Debugger / Interpreter)"]
editor_markers["Editor (Monaco markers)<br>Kmila.Shared.Pages.Editor"]
vhdl_files -->|"reads"| tokenizer
tokenizer -->|"produces List<Token>"| token_stream
token_stream -->|"consumed by"| parser_core
parser_core -->|"delegates to"| structural_parsers
structural_parsers -->|"delegates to"| declaration_parsers
structural_parsers -->|"delegates to"| statement_parsers
declaration_parsers -->|"uses"| helper_parsers
structural_parsers -.->|"implements"| interfaces
declaration_parsers -.->|"implements"| interfaces
statement_parsers -.->|"implements"| interfaces
helper_parsers -.->|"implements"| interfaces
repositories -->|"uses"| models
parser_core -->|"populates"| scopes
scopes -->|"consumed by"| downstream
diagnostic_model -->|"FromException(ParserException)"| editor_markers
exception_model -.->|"adapted via"| diagnostic_model
7. Changelog
Documentation re-sync (2026-08-03, branch v1.3.0)
- Updated the header stamp to the current branch (
v1.3.0) and date. Re-checked the class reference against the on-disk tree: addedParserSkipOverflowExceptionandDiagnosticto the Models table,SharedLoggingto the Parsers table, and a new Services table forUserTypeRegistry/ParseProfile. Refreshed the "Strictness pass" negative-fixture list to include the third fixture (test_negative_garbage_in_body.vhdl), whichProgram.csalready wires. Documentation only — no parser source or public API changes.
Version 1.14.1 (2026-05-01)
Documentation Verification
- README.md: Cross-checked the entire document against the current
Repositories/,Models/, andInterfaces/source. Confirmed that the orchestrator entry points are stillParser(Tokenizer)/Parser(string FilePath), withParseV2()as the recommended path andParse()retained as the legacy monolithic implementation. The fullIParserset documented here matches the*.csfiles on disk one-to-one (14 parser classes, 6 interfaces, 5 model files includingDiagnostic). - §2 Architecture /
Parser.cs: Clarified thatParseV2()also handles inlinelibrary/usedeclarations between design units and walksconfigurationblocks (with thefor CompLabel : CompType use entity work.X(Arch);binding form) viaParseConfigurationBlock/ParseConfigurationBody. Earlier versions of this document only mentionedentity/architecture/packageswitching. - §6.2 Class diagram: Added the
Diagnosticmodel (withFromException(ParserException)static adapter) and theDiagnosticSeverityenumeration to the Models cluster. - §6.4 Module diagram: Added the
Diagnostic/DiagnosticSeveritynode to the Models subgraph and an explicit edge into the editor surface (Kmila.Shared.Pages.Editor) showing howParserExceptionis adapted viaDiagnostic.FromException(...)to feed Monaco markers. - No public API or runtime behavior changes in this release; the parser surface remains backwards-compatible with the v1.14.0 release that introduced the diagnostics pipeline.
Version 0.13.1 (2026-02-21)
Documentation Enhancements
- Variable.cs: Added comprehensive XML documentation for all constructors, operators, backing fields (
_size,_value),IsPort,PortType,ToString(), implicit conversion, and equality/inequality operators - DataStructuresParser.cs: Added XML docs for
ParseDeclaration()(throws NotImplementedException), and documented private field groups - VectorParser.cs: Added XML docs for
CheckVariable()protected method andIsVector()private method - TokenizerTests.cs: Added XML documentation for test class and test method with parameter descriptions
- Program.cs: Added XML documentation for the test runner class and its entry point
Version 0.13.0 (2025-12-13)
This version includes comprehensive documentation improvements, bug fixes, and minor code enhancements.
Bug Fixes
- Memory Leak Fix in TokenStream: Fixed significant memory leak caused by each
TokenStreaminstance creating its ownLoggerFactory. Now uses a static sharedLoggerFactoryto prevent memory accumulation during repeated parsing operations.
Documentation Enhancements
README Updates: Added comprehensive documentation for previously undocumented features:
- Standard Library Functions: Complete table of 50+ recognized IEEE standard library functions
- Block Statement Support: Detailed explanation with VHDL code examples
- Component Instantiation: Full documentation with generic/port map examples
- Scope Naming Convention: Explanation of
{entityName}_{archName}pattern - TokenStream Rollback Feature: Usage documentation with code examples
Constants.cs Documentation: Added XML documentation to all enum values and regex patterns:
TYPEenum: Each token type now has detailed descriptionParseExceptionsenum: Each error code has explanation with examplesDATATYPESenum: Each data type has description of its VHDL semanticsOperatorTypeenum: Each operator category is documentedPortTypeenum: Input/output port descriptions- All regex patterns: Added examples and explanations for each pattern
Updates (from 2025-12-09)
- Shift/Rotate Operators in LOGIC_OPERATORS: Added
sll,srl,sla,sra,rol,rorto theLOGIC_OPERATORSregex pattern. These operators were already in KEYWORDS but were not classified as logical operators, causing issues in the Interpreter's expression evaluation.
Version 0.12.5 (2025-12-09)
This version adds shift and rotate operators to LOGIC_OPERATORS for proper classification by downstream interpreter.
Updates
- Shift/Rotate Operators in LOGIC_OPERATORS: Added
sll,srl,sla,sra,rol,rorto theLOGIC_OPERATORSregex pattern. These operators were already in KEYWORDS but were not classified as logical operators, causing issues in the Interpreter's expression evaluation.
Version 0.12.5 (2025-12-09)
This version fixes the remaining test failures (tests 18, 19, 20) and adds support for advanced VHDL constructs including block statements, guarded signals, signal attributes, and complex conditional generates.
New Features
- Block Statement Support: Full parsing of VHDL block statements with:
- Guard expressions (e.g.,
block (guard_signal = '1')) - Local signal/constant declarations within blocks
- Nested blocks (blocks inside blocks)
- Labeled blocks with optional end labels
- Guard expressions (e.g.,
- Guarded Signal Assignments: Support for guarded concurrent signal assignments with the
guardedkeyword - Signal Attributes with Arguments: Parsing of signal attributes with time arguments:
'stable(time)- signal stability check'delayed(time)- delayed signal value'last_value,'last_event,'eventin conditional expressions
- Type Conversion Functions in Conditionals: Support for IEEE numeric_std type conversion functions in if/when conditions:
unsigned(),signed(),to_integer(),to_unsigned(),to_signed()resize(),shift_left(),shift_right()std_logic_vector(),integer(),natural()
- Configuration Architecture Binding: Support for architecture specification in configuration blocks:
use entity work.Component(Architecture);syntax
- Enhanced Operator Support: Added
/=(not-equal) and>=(greater-equal) as properly tokenized operators - Package Constants in Generics: Generic default values can now reference package constants (identifiers), not just literals
- Custom Types in Ports: Port declarations now accept user-defined types (identifiers) in addition to standard VHDL types
Bug Fixes
- Fixed infinite loop in
ParseOperation(): Corrected while loop condition that prevented proper termination on semicolon - Fixed
QUALIFIED_IDENTIFIERSregex: Now allows digits in the first identifier segment (e.g.,s_axi_s2m.bvalidwheres2mcontains a digit) - Fixed nested block parsing:
ParseBlockBody()now properly recognizes and handles nested block statements - Fixed configuration block parsing: Added support for architecture binding syntax with parentheses
- Added fallback for unknown tokens:
ParseSingleStatement()now gracefully skips unknown tokens to the next semicolon instead of causing infinite loops
Test File Fixes
- test20.vhdl: Fixed VHDL identifier case conflicts by renaming state machine constants:
TX_DATA→TX_ST_DATA(to avoid conflict withtx_datasignal)RX_DATA→RX_ST_DATA(to avoid conflict withrx_datasignal)- Similar renames for
TX_IDLE,TX_START,TX_STOP,RX_IDLE,RX_START,RX_STOP
Test Coverage
- All 43 test cases now pass:
- test1-10: Basic to intermediate VHDL constructs
- test11-17: Generate statements, multi-entity designs, labeled processes
- test18: Advanced signal attributes, guarded signals, block statements, configuration
- test19: Complex conditional generates with type conversions
- test20: Full SoC-style design with packages, records, multiple entities, and configuration
Version 0.12.4 (2025-12-08)
This version includes significant improvements to the parser's handling of complex VHDL constructs, particularly around concurrent vs sequential statement parsing, scope management, and enhanced error reporting.
New Features
- Generate Statement Support: Full parsing of
for-generateandif-generatestatements, including:- Nested generate statements
- Local signal/constant declarations within generate blocks
- Iterator variable scoping (automatically removed after generate completes)
- Labeled Process Support: Parsing of processes with labels (e.g.,
my_proc : process(clk) begin ... end process my_proc;) - While Loop Support: Added
while condition loop ... end loop;parsing - Additional Sequential Statements: Support for
exit,wait, andnullstatements - Extended Keywords: Added support for many additional VHDL keywords including
nand,nor,xnor,sll,srl,sla,sra,rol,ror,abs,rem,transport,reject,inertial,unaffected, and more - Vector Attributes: Extended support for signal attributes like
'last_value,'last_event,'event,'stable,'quiet,'active,'transaction,'delayed,'now,'path_name,'instance_name,'base,'high,'low,'left,'right,'length,'range,'reverse_range,'ascending, etc. - Configuration Block Parsing: Added support for parsing VHDL
configurationblocks with nestedforstatements - Component Declarations: Full parsing of
componentdeclarations within architecture headers, including optionalgenericandportblocks - Component Instantiation: Support for component instantiations with
port mapandgeneric map - Process Variable Declarations: Parsing of
variableandconstantdeclarations within process bodies beforebegin - Aggregate Expressions: Support for aggregate expressions like
(others => '0')in signal/constant initializations - Function Call Parsing: Proper parsing of function calls in expressions (e.g.,
conv_integer(x),clog2(N)) - Indexed Signal Access: Parsing of indexed signal/variable access like
signal(7 downto 0)orsignal(i) - Attribute Declarations: Support for
attributedeclarations and specifications - Disconnect Specifications: Parsing of
disconnectspecifications for guarded signals
Enhanced Error Reporting
ParserException: Completely rewritten with detailed error messages including:- Line and column numbers (1-based)
- Context information (near which token the error occurred)
- Predefined error types:
INITIALIZATION,DUPLICATE_DECLARATION,MISSING_STRUCTURE,DUPLICATE_STRUCTURE,MISSING_DECLARATION,SYNTAX_ERROR - Factory methods:
UnexpectedToken(),UndeclaredVariable(),TypeMismatch()
StreamerException: Enhanced with detailed context:- Human-readable type descriptions (e.g., "keyword (e.g., 'process', 'signal', 'begin', 'end')")
- Tracks last valid token for better error location when hitting end-of-input
Key Architectural Changes
Concurrent vs Sequential Statement Parsing (
DataStructuresParser.cs)- Added
ParseSingleStatement()method for parsing exactly ONE concurrent statement (used in architecture body context) - Added
ParseSequentialBody()method for parsing ALL sequential statements untilendkeyword (used in process/function bodies) - The
Parse()method now usesParseSingleStatement()to prevent incorrect looping through architecture body constructs
- Added
Architecture Scope Naming (
ArchitectureParser.cs)- Architecture scopes now use
{entityName}_{archName}as the key - This allows multiple architectures with the same name for different entities (e.g., both
AdderandPipelineRegcan haveBehavioralarchitectures)
- Architecture scopes now use
Iterator Variable Scoping
for-loopandfor-generateiterator variables are now properly scoped- Variables are removed from scope after the loop/generate completes
- Prevents "duplicate declaration" errors when the same iterator name is reused
Function Body Parsing (
FunctionParser.cs)- Now uses
ParseSequentialBody()instead ofParse()to correctly handle multiple statements in function bodies - Support for return type bounds with complex expressions (e.g.,
std_logic_vector((A'length + B'length) - 1 downto 0)) - Parsing of local variable/constant declarations before
begin
- Now uses
Variable Model Enhancements (
Variable.cs)- Extended
GetDatatype()to support additional types:std_ulogic,signed,unsigned,bit_vector,boolean_vector,integer_vector,positive,string,time,real - Improved handling of
(others => 'X')aggregate patterns - Support for concatenation expressions
(concat:...) - Case-insensitive variable comparison (VHDL identifiers are case-insensitive)
- Extended
Bug Fixes
- Fixed issue where generate labels were incorrectly parsed as signal assignments
- Fixed issue where component instantiation labels caused "instance not in scope" errors
- Fixed duplicate structure errors when multiple architectures shared the same name
- Fixed function body parsing stopping after first statement
- Fixed
whileloop body parsing returning prematurely - Fixed vector type bounds not being parsed in function parameters
- Fixed return expression parsing in functions
Test Coverage
- All 20 test cases now pass, covering:
- Basic entity/architecture parsing (test1-5)
- Complex expressions and operators (test6-10)
- Generate statements with local declarations (test11-12)
- Multi-entity designs with shared architecture names (test11, test17)
- Labeled processes and nested control structures (test13-16)
- Component hierarchies and instantiations (test17-20)
8. Complete Class Reference
Models
| Class | Description |
|---|---|
Token |
Represents a lexical token with Value, Type, Line, and Col properties |
Variable |
Represents VHDL signals, ports, variables, and constants with properties: Name, Type, Value, Size, IsPort, PortType, IsStub, CustomTypeName, RecordFields, and reactive OnValueChange event. Includes static GetDatatype() for type string resolution. |
Constants |
Static class containing token types (TYPE enum), data types (DATATYPES enum), error codes (ParseExceptions enum), operator types (OperatorType enum: ASSIGNATION, LOGIC_OPERATION, ARITH_OPERATION, COMPARATION), port types (PortType enum: IN, OUT, INOUT, BUFFER), and regex patterns for token classification |
ParserException |
Custom exception with rich error context including line/column info |
ParserSkipOverflowException |
Subclass of ParserException thrown when a Skip* / Parse* loop runs past its iteration cap (carries the offending method name and the cap); lets pathological input fail fast instead of hanging the parser |
Diagnostic |
Structured diagnostic (DiagnosticSeverity + line/column/code/message); FromException(ParserException) adapts the exception flow into Monaco markers |
Interfaces
| Interface | Description |
|---|---|
IParser |
Base interface with Parse() method |
IParserBaseMethods |
Methods for scope/variable management: ExistsVariable(), GetScopes() |
IParseStructures |
For top-level structures (entity, architecture, package): GetName(), ParseDeclaration(), ParseBodyDeclaration() |
IParseDeclarations |
For declaration blocks: ParseDeclarations() |
IParseVectorStructure |
For vector types: ParseBody() |
IParseGeneric |
For generic blocks: ParseStructure(), ParseAssignations(), ParseAssignationsWithComas() |
Parsers
| Parser | Responsibility |
|---|---|
Tokenizer |
Lexical analysis - converts VHDL source to tokens |
TokenStream |
Token stream management with Current, MoveNext(), Expect(), Match() |
Parser |
Main orchestrator - delegates to specialized parsers |
EntityParser |
Parses entity blocks |
ArchitectureParser |
Parses architecture blocks including generate statements and component instantiations |
PackageParser |
Parses package and package body blocks |
DataStructuresParser |
Parses sequential statements (if, case, for, while, assignments) |
FunctionParser |
Parses function declarations and bodies |
FunctionAssignationParser |
Parses function parameter declarations |
GenericParser |
Parses generic blocks |
PortsParser |
Parses port blocks |
ArchitectureAssignationParser |
Parses architecture header declarations (signals, constants, attributes) |
VectorParser |
Parses vector types (std_logic_vector, etc.) |
RangeParser |
Parses range declarations (extends VectorParser) |
SharedLogging |
internal static helper exposing a single shared ILoggerFactory (console provider), reused by every parser to avoid per-instance LoggerFactory allocation |
Services
| Service | Responsibility |
|---|---|
UserTypeRegistry |
Process-wide catalogue of resolved user-defined VHDL type names; populated by the host from LibraryCompiler symbols before parsing, so the parser accepts package/library-declared types as valid type identifiers |
ParseProfile |
Lightweight per-run counters for the parse-side hot paths (e.g. Tokenizer.EvaluateToken), emitted by the host when KMILA_PARSE_PROFILE is set |
9. Audit diagrams (2026-05-09)
These diagrams were rewritten or created during the cross-module audit because the
existing TT1 diagrams modeled a single-state-machine Parse() that was replaced by
the explicit ParseV2() delegation (EntityParser / ArchitectureParser /
PackageParser). They live also in Documentacion/TT1/diagrams/.
fig_4_4_dominio_analisis_codigo — Domain model: code analysis (rewritten)
classDiagram
direction LR
class ArchivoHDL {
+String nombre
+String contenido
}
class Scope {
+String nombre
+TipoScope tipo
}
class Variable {
+String nombre
+Tipo tipo
+Object valor
+Int tamanio
+PortType direccion
}
class Diagnostico {
+Severidad severidad
+Int linea
+Int columnaInicio
+Int columnaFin
+String codigo
+String mensaje
}
ArchivoHDL "1" --> "0..*" Scope : produce
Scope "1" --> "0..*" Variable : agrupa
ArchivoHDL "1" --> "0..*" Diagnostico : genera
Variable "0..*" ..> "0..*" Variable : referencia (records, vectores)
fig_5_8_1_seq_validacion_vhdl — Validation sequence (rewritten)
sequenceDiagram
actor E as Estudiante
participant UI as ButtonCollapse
participant Ed as Editor.razor
participant PB as ProgramBuilder
participant Tk as Tokenizer
participant TS as TokenStream
participant Pa as Parser
E->>UI: Presiona "Validar programa"
UI->>Ed: OnBuildRequested
Ed->>PB: Build(ProjectFile)
PB->>PB: Validar extension .vhd/.vhdl
PB->>Pa: new Parser(filePath)
Pa->>Tk: ReadFile + TokenizeLine + Evaluate
Tk-->>Pa: List~Token~
Pa->>TS: new TokenStream(tokens)
PB->>Pa: ParseV2()
loop Mientras quedan tokens
Pa->>TS: Current / MoveNext
alt Token = entity
Pa->>Pa: EntityParser.ParseDeclaration
else Token = architecture
Pa->>Pa: ArchitectureParser.ParseDeclaration
else Token = package
Pa->>Pa: PackageParser.ParseDeclaration
else Token = library / use
Pa->>Pa: ParseLibrariesUsage
else Token = configuration
Pa->>Pa: ParseConfigurationBlock
end
end
Pa-->>PB: Scopes (Dictionary<string, HashSet~Variable~>)
PB-->>UI: OnBuildProgressChanged(100%)
UI-->>E: Muestra entidades y recursos detectados
fig_5_11_2_1_act_validacion_prep — Preparation activity (rewritten)
flowchart TD
A([Inicio: Validar programa]) --> B{Extension .vhd o .vhdl?}
B -->|No| C([Fin: archivo rechazado])
B -->|Si| D[Crear instancia de Parser con ruta del archivo]
D --> E[Tokenizer: ReadFile y eliminar comentarios]
E --> F[TokenizeLine: clasificar tokens por regex]
F --> G[Evaluate: asignar TYPE a cada token]
G --> H[Construir TokenStream sobre la lista de tokens]
H --> I([Continua en Figura 5.11.2.2])
fig_5_11_2_2_act_validacion_recorrido — Token-driven traversal (rewritten)
flowchart TD
A([TokenStream listo]) --> B[ParseV2: leer Current]
B --> C{Tipo de token?}
C -->|entity| D[EntityParser: nombre, generic, ports]
C -->|architecture| E[ArchitectureParser: nombre, entidad, header]
C -->|package| F[PackageParser: constantes, funciones, tipos]
C -->|library / use| G[ParseLibrariesUsage]
C -->|configuration| H[ParseConfigurationBlock]
C -->|otro| B
D --> I[Registrar Variables en Scopes]
E --> I
F --> I
G --> B
H --> I
I --> J{EndStream?}
J -->|No| B
J -->|Si| K{Hubo entidades / packages?}
K -->|No| L([Fin: error, sin unidades de diseno])
K -->|Si| M([Fin: Scopes disponibles + Diagnosticos])
fig_5_11_6_act_tokenizer_pipeline — Tokenizer pipeline (new)
flowchart TD
A([Inicio: Tokenizer.ReadFile]) --> B[Leer archivo VHDL crudo]
B --> C[Eliminar lineas que comienzan con --]
C --> D[Por cada linea: TokenizeLine]
D --> E[Aplicar regex a cada fragmento]
E --> F{Patron coincide?}
F -->|KEYWORDS| G[Token.Type = KEYWORDS]
F -->|OPERATORS| H[Token.Type = OPERATORS]
F -->|LITERALS| I[Token.Type = LITERALS]
F -->|IDENTIFIERS| J[Token.Type = IDENTIFIERS]
F -->|TIME / VECTORS_ATTRIBUTES /<br/>FUNCTIONS / SEPARATORS /<br/>PARENTHESIS| K[Token.Type = otro]
F -->|Sin coincidencia| L[Token.Type = UNKNOWN<br/>+ Diagnostico critico]
G --> M[Adjuntar Line, Col]
H --> M
I --> M
J --> M
K --> M
L --> M
M --> N{Quedan lineas?}
N -->|Si| D
N -->|No| O([Fin: List~Token~ + Diagnosticos])
fig_5_11_7_act_configuration_block — Configuration block parsing (new)
flowchart TD
A([Token = configuration]) --> B[ParseConfigurationBlock: leer nombre y entidad objetivo]
B --> C[Expect of, leer entityName]
C --> D[Expect is]
D --> E[ParseConfigurationBody]
E --> F{Siguiente token?}
F -->|for label : compType use| G[Registrar binding<br/>label -> entity work.X arch]
F -->|end for| H[Cerrar bloque interno]
F -->|end configuration| I[Validar nombre coincide]
F -->|otro| E
G --> E
H --> E
I --> J[Crear scope configuration_name]
J --> K[Registrar bindings como Variables<br/>en HashSet del scope]
K --> L([Fin: scope listo en Scopes])
fig_5_11_8_act_generate_scope — Generate statement scope lifecycle (new)
flowchart TD
A([Statement: for label in rango generate]) --> B[ArchitectureParser.ParseGenerateStatement]
B --> C[Crear scope hijo<br/>arch_label_generate]
C --> D[Registrar variable iteradora<br/>en el scope hijo]
D --> E[ParseSequentialBody hasta end generate]
E --> F{Statement tipo?}
F -->|signal_assignment| G[Resolver nombres en scope hijo<br/>+ scope padre]
F -->|process| H[Procesar Process anidado<br/>SyntentizeSensitiveList]
F -->|component_instantiation| I[Resolver entity ref con<br/>iterador como generic]
F -->|generate anidado| J[Recursion: nuevo scope hijo]
G --> E
H --> E
I --> E
J --> E
F -->|end generate| K[Validar nombre = label]
K --> L[Cerrar scope hijo,<br/>iterador liberado]
L --> M([Fin: bindings disponibles para Interpreter])
fig_5_11_9_act_record_types — Record types resolution (new)
flowchart TD
A([Declaracion: type T is record ...]) --> B[ArchitectureAssignationParser detecta record]
B --> C[Crear Variable typeDef con<br/>CustomTypeName=T y RecordFields={}]
C --> D[Por cada campo del record]
D --> E[Leer nombre del campo + tipo]
E --> F[RecordFields[name] = default por tipo]
F --> G{Mas campos?}
G -->|Si| D
G -->|No| H[Registrar tipo T en scope]
H --> I([Tipo disponible para declaraciones])
J([Asignacion: signal x : T := ...]) --> K[Crear Variable x con<br/>CustomTypeName=T]
K --> L[Copiar RecordFields del typeDef]
L --> M([Variable lista en scope])
N([Acceso: x.field <= value]) --> O[DataStructuresParser: detectar punto]
O --> P[Resolver x en Scopes]
P --> Q{Existe x.RecordFields[field]?}
Q -->|No| R[Diagnostico: campo desconocido]
Q -->|Si| S[Generar asignacion sobre<br/>RecordFields[field]]
S --> T([Operacion lista para Interpreter])
Changelog
Version 1.14.0 (2026-04-19)
- Structured diagnostics — new
Parser.Models.Diagnostictype withDiagnosticSeverity(Info / Warning / Error) and line / column / code / message.Diagnostic.FromException(ParserException)adapts the existing exception-based flow into the new collection without breaking callers. - Linter hook — downstream callers (see
Debugger.Repositories.Parser) can now run a non-fatal pass that reports missing;at statement ends andbegin/endblock imbalance before the full parser runs. Addresses the long-standing complaint that the parser silently accepted sources missing terminators at simulation time. - Editor integration — these diagnostics are mapped to Monaco's
setModelMarkersAPI byKmila.Shared/Pages/Editor.razor, so syntax errors now light up in the gutter instead of appearing only in a toast. - Build hygiene — added
<NoWarn>for the XML-doc ripple (CS1587 / CS1591 / CS1570) caused by the comma-separated const lists inModels/Constants.cs. The project builds warning-free without the churn of splitting each regex constant into its own declaration.
What's new in v1.15 (2 May 2026)
Parser surface now accepts:
procedure name(...) is ... end name;in the architecture header (#12).- Direct entity instantiation
label : entity work.Sub[(arch)] port map(...)(#13). - Generic defaults with arithmetic expressions, e.g.
TOTAL : integer := BASE * TIMES(#16). - Line numbers in error diagnostics now reference the user's source file (not the tokenizer's call counter) via the new
Tokenizer.TokenizeLineAt(text, sourceLineZeroBased)overload (#21).
Memory: Tokenizer.Evaluate() now releases the raw _tokens buffer once _typeDictionary is built. Workstation GC enabled at the project level for low-RAM device targeting (#7).
Full release notes: Documentacion/CHANGELOG_v1.15.md.
Strictness pass (2026-05-17)
PortsParser now rejects two classes of port-declaration sloppiness that previously slipped through silently. Both were filed by Daniel as part of the ripple-counter "Visible Wave" repro that built clean despite obvious typos — see Documentacion/CHANGELOG_concepts_module_2026-05-17.md addendum and the memory entry project_vhdl_parser_too_lenient.md.
Unknown port type → "did you mean …" error
When the type identifier in a port declaration is captured as an IDENTIFIER (not a recognised KEYWORD) AND its case-folded value is within Levenshtein-distance 2 of any canonical VHDL type name in Constants.KNOWN_TYPE_NAMES, the parser now throws a ParserException with a "Did you mean '
| Source token | Canonical | Distance | Behaviour |
|---|---|---|---|
STDLOGIC |
std_logic |
1 (missing _) |
rejected |
STD_LGIC |
std_logic |
1 (missing O) |
rejected |
coefficient_array_t |
none close | > 2 | accepted as user-defined type |
The Levenshtein helper lives at Constants.cs:LevenshteinDistance (private, rolling two-row buffer). Constants.FindTypoSuggestion(string) is the public entry — pass the source identifier, get back the closest canonical name or null when the identifier looks legitimate.
Missing ; between ports
The Parse() loop now detects the case where a port declaration finishes and the next token is another IDENTIFIER (rather than ; or )). It surfaces a ParserException reading "Missing ';' before port '
Negative test suite
Program.cs gained a second test pass for "must-fail" fixtures. Each entry is a (filename, mustContainSubstring) pair; a PASS means the parser surfaced the expected exception and the message contained the required substring. The runner's exit code is non-zero unless both the 40 positive AND every negative fixture pass — so regressions can't silently re-loosen the strictness.
Current negative coverage (Parser root, alongside the existing test1..test40.vhdl):
test_negative_unknown_type.vhdl— typoSTDLOGICin a port type (must containSTDLOGIC).test_negative_missing_port_semi.vhdl— port lacks;before the next port (must containMissing ';').test_negative_garbage_in_body.vhdl— stray$token in the architecture body (must containUnexpected token '$').
Adding more is one entry in the negativeTests array + a test_negative_*.vhdl file at the Parser root.
Last updated: 2026-08-03