Source code for pyGHDL.dom.NonStandard

# =============================================================================
#               ____ _   _ ____  _          _
#  _ __  _   _ / ___| | | |  _ \| |      __| | ___  _ __ ___
# | '_ \| | | | |  _| |_| | | | | |     / _` |/ _ \| '_ ` _ \
# | |_) | |_| | |_| |  _  | |_| | |___ | (_| | (_) | | | | | |
# | .__/ \__, |\____|_| |_|____/|_____(_)__,_|\___/|_| |_| |_|
# |_|    |___/
# =============================================================================
# Authors:
#   Patrick Lehmann
#
# Package module:   DOM: Elements not covered by the VHDL standard.
#
# License:
# ============================================================================
#  Copyright (C) 2019-2022 Tristan Gingold
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <gnu.org/licenses>.
#
# SPDX-License-Identifier: GPL-2.0-or-later
# ============================================================================
"""
This module implements the non-standard classes :class:`Design`, :class:`Library` and :class:`Document`.

These three are not VHDL language constructs, but the entry points of :mod:`pyGHDL.dom`: a document reads
and parses a source file through *libghdl*, a library groups documents, and a design owns the libraries.
"""

import ctypes
import time
from pathlib import Path
from typing import Any, Optional as Nullable, List

from pyTooling.Decorators import export, InheritDocString, readonly
from pyTooling.Warning import WarningCollector

from pyVHDLModel import VHDLVersion, IEEEFlavor
from pyVHDLModel import Design as VHDLModel_Design
from pyVHDLModel import Library as VHDLModel_Library
from pyVHDLModel import Document as VHDLModel_Document

from pyGHDL.libghdl import (
    ENCODING,
    initialize as libghdl_initialize,
    finalize as libghdl_finalize,
    set_option as libghdl_set_option,
    analyze_init_status as libghdl_analyze_init_status,
    name_table,
    files_map,
    errorout_memory,
    LibGHDLException,
    flags,
    utils,
    files_map_editor,
)
from pyGHDL.libghdl.flags import Flag_Gather_Comments
from pyGHDL.libghdl.vhdl import nodes, sem_lib
from pyGHDL.libghdl.vhdl.parse import Flag_Parse_Parenthesis
from pyGHDL.dom import DOMException, Position
from pyGHDL.dom._Utils import GetIirKindOfNode, CheckForErrors, GetNameOfNode, GetDocumentationOfNode
from pyGHDL.dom.Name import SimpleName
from pyGHDL.dom.Symbol import LibraryReferenceSymbol
from pyGHDL.dom.DesignUnit import (
    Entity,
    Architecture,
    Package,
    PackageBody,
    Context,
    Configuration,
    PackageInstantiation,
    LibraryClause,
    UseClause,
    ContextReference,
)
from pyGHDL.dom.PSL import VerificationUnit, VerificationProperty, VerificationMode


[docs] @export @InheritDocString(VHDLModel_Design, merge=True) class Design(VHDLModel_Design): """ This class implements a :mod:`pyGHDL.dom` object derived from :class:`pyVHDLModel.Design`. """ _loadDefaultLibraryTime: Nullable[float] #: :meth:`LoadDefaultLibraries` duration in seconds, ``None`` if unused. _analyzeTime: Nullable[float] #: :meth:`Analyze` duration in seconds, ``None`` if not called. _vhdlVersion: VHDLVersion #: The VHDL version this design is analyzed with. _warnings: List #: Warnings collected from *libghdl* while the design's documents are analyzed. #: VHDL versions currently supported by this class. Older revisions (87, 93, 2000, 2002) are #: not planned to be supported for now. _SUPPORTED_VHDL_VERSIONS = (VHDLVersion.VHDL2008, VHDLVersion.VHDL2019) #: Mapping from a supported VHDLVersion to GHDL's '--std=' option value. _VHDL_VERSION_TO_STD_OPTION = { VHDLVersion.VHDL2008: "08", VHDLVersion.VHDL2019: "19", }
[docs] def __init__(self, name: str = None, vhdlVersion: VHDLVersion = VHDLVersion.VHDL2008) -> None: """ Initialize a VHDL design. :param name: Name of the design. :param vhdlVersion: The VHDL version used to analyze this design. """ super().__init__(name) if vhdlVersion not in self._SUPPORTED_VHDL_VERSIONS: supported = ", ".join(str(v) for v in self._SUPPORTED_VHDL_VERSIONS) ex = DOMException(f"VHDL version '{vhdlVersion}' is not supported by pyGHDL.dom.") ex.add_note(f"Supported versions: {supported}.") raise ex self._vhdlVersion = vhdlVersion self._loadDefaultLibraryTime = None self._analyzeTime = None self._warnings = [] self.__ghdl_init()
@readonly def VHDLVersion(self) -> VHDLVersion: """ Read-only property to access the VHDL version this design is analyzed with (:attr:`_vhdlVersion`). The version is checked against :attr:`_SUPPORTED_VHDL_VERSIONS` when the design is created and is translated to GHDL's ``--std=`` option value via :attr:`_VHDL_VERSION_TO_STD_OPTION`. :returns: The design's VHDL version. """ return self._vhdlVersion def __ghdl_init(self): """Initialization: set options and then load libraries.""" # Initialize libghdl libghdl_finalize() libghdl_initialize() # Collect error messages in memory errorout_memory.Install_Handler() stdOption = self._VHDL_VERSION_TO_STD_OPTION[self._vhdlVersion] libghdl_set_option(f"--std={stdOption}") Flag_Gather_Comments.value = True Flag_Parse_Parenthesis.value = True # Finish initialization. This will load the standard package. if libghdl_analyze_init_status() != 0: raise DOMException("Error initializing 'pyGHDL.dom'.") from LibGHDLException( "Error initializing 'libghdl'." )
[docs] def LoadDefaultLibraries(self, flavor: Nullable[IEEEFlavor] = None): """ Loads the ``std`` and ``ieee`` libraries into the design. How long this took is measured and kept in :attr:`_loadDefaultLibraryTime`. :param flavor: The IEEE library flavor to load, or ``None`` for the default. """ t1 = time.perf_counter() super().LoadStdLibrary() super().LoadIEEELibrary(flavor) self._loadDefaultLibraryTime = time.perf_counter() - t1
[docs] def Analyze(self): """ Analyzes all documents of this design. How long this took is measured and kept in :attr:`_analyzeTime`, and the warnings *libghdl* raised during the analysis are collected in :attr:`_warnings`. """ t1 = time.perf_counter() with WarningCollector(self._warnings) as warnings: super().Analyze() self._analyzeTime = time.perf_counter() - t1
[docs] @export @InheritDocString(VHDLModel_Library, merge=True) class Library(VHDLModel_Library): """ This class implements a :mod:`pyGHDL.dom` object derived from :class:`pyVHDLModel.Library`. """ pass
[docs] @export @InheritDocString(VHDLModel_Document, merge=True) class Document(VHDLModel_Document): """ This class implements a :mod:`pyGHDL.dom` object derived from :class:`pyVHDLModel.Document`. """ _filename: Path #: The source file this document was read from. _warnings: List #: Warnings collected from *libghdl* while this document is translated. __ghdlFileID: Any #: *libghdl*'s name table identifier for :attr:`_filename`. __ghdlSourceFileEntry: Any #: *libghdl*'s source file entry holding this document's source code. __ghdlFile: Any #: The IIR design file node returned by *libghdl* when the source code was parsed. __ghdlProcessingTime: float #: Duration of *libghdl*'s parsing in seconds, unset if ``dontParse`` was ``True``. __domTranslateTime: float #: Duration of the IIR to DOM translation in seconds, unset if it was skipped.
[docs] def __init__( self, path: Path, sourceCode: str = None, vhdlVersion: VHDLVersion = VHDLVersion.VHDL2008, dontParse: bool = False, dontTranslate: bool = False, ) -> None: """ Initializes a VHDL document. :param path: Path to the document. ``None`` if in-memory document. :param sourceCode: The source code to analyze, or ``None`` to read it from ``path``. :param vhdlVersion: VHDL version used for analyzing this source file. :param dontParse: ``True`` to skip parsing the source code. :param dontTranslate: ``True`` to parse the source code, but skip translating it to the DOM. """ super().__init__(path, parent=None) self._filename = path self._warnings = [] if sourceCode is None: self.__loadFromPath() else: self.__loadFromString(sourceCode) if not dontParse: # Parse input file t1 = time.perf_counter() if vhdlVersion.IsAMS: flags.AMS_Vhdl.value = True self.__ghdlFile = sem_lib.Load_File(self.__ghdlSourceFileEntry) CheckForErrors() if vhdlVersion.IsAMS: flags.AMS_Vhdl.value = False self.__ghdlProcessingTime = time.perf_counter() - t1 if not dontTranslate: t1 = time.perf_counter() with WarningCollector(self._warnings) as warnings: self.translate() self.__domTranslateTime = time.perf_counter() - t1
def __loadFromPath(self): """ Reads the source file named by :attr:`_filename` and hands it to *libghdl*. :raises DOMException: If the source file does not exist. """ try: with self._filename.open("r", encoding=ENCODING) as file: self.__loadFromString(file.read()) except FileNotFoundError as ex: raise DOMException(f"Sourcefile '{self._filename}' not found.") from ex def __loadFromString(self, sourceCode: str): """ Hands the given source code to *libghdl* under the name in :attr:`_filename`. .. hint:: The source file itself is not read, which is how a document can be analyzed *in-memory* from a string. :param sourceCode: The source code to analyze. """ sourcesBytes = sourceCode.encode(ENCODING) sourceLength = len(sourcesBytes) bufferLength = sourceLength + 128 dirId = name_table.Null_Identifier self.__ghdlFileID = name_table.Get_Identifier(str(self._filename)) if files_map.Find_Source_File(dirId, self.__ghdlFileID) == files_map.No_Source_File_Entry: self.__ghdlSourceFileEntry = files_map.Reserve_Source_File(dirId, self.__ghdlFileID, bufferLength) files_map_editor.Fill_Text(self.__ghdlSourceFileEntry, ctypes.c_char_p(sourcesBytes), sourceLength) CheckForErrors() else: raise DOMException(f"Source file '{self._filename}' already loaded.")
[docs] def translate(self): """ Translates the design units of the parsed IIR tree to :mod:`pyGHDL.dom` objects. :raises DOMException: If a design unit's kind is not handled. """ firstUnit = nodes.Get_First_Design_Unit(self.__ghdlFile) self._documentation = GetDocumentationOfNode(firstUnit) for unit in utils.chain_iter(firstUnit): libraryUnit = nodes.Get_Library_Unit(unit) nodeKind = GetIirKindOfNode(libraryUnit) contextItems = [] contextNames = [] context = nodes.Get_Context_Items(unit) if context is not nodes.Null_Iir: for item in utils.chain_iter(context): itemKind = GetIirKindOfNode(item) if itemKind is nodes.Iir_Kind.Library_Clause: libraryIdentifier = GetNameOfNode(item) contextNames.append(LibraryReferenceSymbol(item, SimpleName(item, libraryIdentifier))) if nodes.Get_Has_Identifier_List(item): continue contextItems.append(LibraryClause(item, contextNames)) contextNames = [] elif itemKind is nodes.Iir_Kind.Use_Clause: contextItems.append(UseClause.parse(item)) elif itemKind is nodes.Iir_Kind.Context_Reference: contextItems.append(ContextReference.parse(item)) else: pos = Position.parse(item) raise DOMException( f"Unknown context item kind '{itemKind.name}' in context at line {pos.Line}." ) if nodeKind == nodes.Iir_Kind.Entity_Declaration: entity = Entity.parse(libraryUnit, contextItems) self._AddEntity(entity) elif nodeKind == nodes.Iir_Kind.Architecture_Body: architecture = Architecture.parse(libraryUnit, contextItems) self._AddArchitecture(architecture) elif nodeKind == nodes.Iir_Kind.Package_Declaration: package = Package.parse(libraryUnit, contextItems) self._AddPackage(package) elif nodeKind == nodes.Iir_Kind.Package_Body: packageBody = PackageBody.parse(libraryUnit, contextItems) self._AddPackageBody(packageBody) elif nodeKind == nodes.Iir_Kind.Package_Instantiation_Declaration: package = PackageInstantiation.parse(libraryUnit, contextItems) self._AddPackage(package) elif nodeKind == nodes.Iir_Kind.Context_Declaration: context = Context.parse(libraryUnit) self._AddContext(context) elif nodeKind == nodes.Iir_Kind.Configuration_Declaration: configuration = Configuration.parse(libraryUnit, contextItems) self._AddConfiguration(configuration) elif nodeKind == nodes.Iir_Kind.Vunit_Declaration: vunit = VerificationUnit.parse(libraryUnit) self._AddVerificationUnit(vunit) elif nodeKind == nodes.Iir_Kind.Vprop_Declaration: vprop = VerificationProperty.parse(libraryUnit) self._AddVerificationProperty(vprop) elif nodeKind == nodes.Iir_Kind.Vmode_Declaration: vmod = VerificationMode.parse(libraryUnit) self._AddVerificationMode(vmod) else: raise DOMException(f"Unknown design unit kind '{nodeKind.name}'.")
@readonly def LibGHDLProcessingTime(self) -> float: """ Read-only property to access the time *libghdl* spent parsing this document (:attr:`__ghdlProcessingTime`). The duration is measured while the document is constructed, so it is set only if ``dontParse`` was ``False``. :returns: The parse duration in seconds. :raises AttributeError: If the document was constructed with ``dontParse=True``. """ return self.__ghdlProcessingTime @readonly def DOMTranslationTime(self) -> float: """ Read-only property to access the time spent translating the IIR tree to the DOM (:attr:`__domTranslateTime`). The duration is measured while the document is constructed, so it is set only if neither ``dontParse`` nor ``dontTranslate`` was ``True``. :returns: The translation duration in seconds. :raises AttributeError: If the document was constructed with ``dontParse=True`` or ``dontTranslate=True``. """ return self.__domTranslateTime