diff options
-rw-r--r-- | .coveragerc | 12 | ||||
-rw-r--r-- | .gitignore | 42 | ||||
-rw-r--r-- | pyGHDL/README.md | 12 | ||||
-rw-r--r-- | pyGHDL/cli/__init__.py | 0 | ||||
-rw-r--r-- | pyGHDL/dom/Common.py | 48 | ||||
-rw-r--r-- | pyGHDL/dom/DesignUnit.py | 100 | ||||
-rw-r--r-- | pyGHDL/dom/InterfaceItem.py | 33 | ||||
-rw-r--r-- | pyGHDL/dom/Misc.py | 96 | ||||
-rw-r--r-- | pyGHDL/dom/__init__.py | 4 | ||||
-rw-r--r-- | requirements.txt | 2 | ||||
-rw-r--r-- | testsuite/pyunit/dom/Instantiate.py | 25 | ||||
-rw-r--r-- | testsuite/pyunit/libghdl/Initialize.py | 20 | ||||
-rw-r--r-- | testsuite/requirements.txt | 4 |
13 files changed, 389 insertions, 9 deletions
diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 000000000..0ebb5bd64 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,12 @@ +[run] +branch = true + +[report] +skip_covered = True +skip_empty = True + +[html] +directory = .cov + +[xml] +output = coverage.xml diff --git a/.gitignore b/.gitignore index 897fd0904..2a290d646 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,8 @@ +# Ada outputs *~ *.ali *.o b~*.ad? -*.v87 -*.v93 -*.v08 -*.cf -.gdb_history -*.pyc - -testsuite/get_entities # Generated files on windows. /build/ @@ -19,7 +12,6 @@ testsuite/get_entities # Generated files on Linux config.status default_paths.ads -doc/_build/ elf_arch.ads ghdl1-* ghdl.gpr @@ -44,3 +36,35 @@ run-bind.ads src/version.ads version.ads version.tmp + +# GDB outputs +.gdb_history + +# GHDL outputs +*.v87 +*.v93 +*.v08 +*.cf + +# GHDLs testsuite +testsuite/get_entities + +# Python cache and object files +__pycache__/ +*.py[cod] + +# Python installation packages +dist/ + +# Coverage.py +.coverage +.cov +coverage.xml + +# Sphinx +doc/_build/ +doc/pyGHDL/**/*.* +!doc/pyGHDL/index.rst + +# IntelliJ project files +/.idea/workspace.xml diff --git a/pyGHDL/README.md b/pyGHDL/README.md new file mode 100644 index 000000000..deb45b8d8 --- /dev/null +++ b/pyGHDL/README.md @@ -0,0 +1,12 @@ +# pyGHDL + +Python binding for GHDL and high-level APIs. + +## Provided Packages + +* `pyGHDL.libghdl` - Low-level Python bindings to GHDL's `libghdl` shared library. + Auto generated API from Ada sources. +* `pyGHDL.cli` - Command line interface tools. +* `pyGHDL.lsp` - Language Server Protocol (LSP) implementation for VHDL. +* `pyGHDL.dom` - Document Object Model (DOM) for VHDL parsed by `libghdl`. +* `pyGHDL.xtools` - *tbd* diff --git a/pyGHDL/cli/__init__.py b/pyGHDL/cli/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/pyGHDL/cli/__init__.py diff --git a/pyGHDL/dom/Common.py b/pyGHDL/dom/Common.py new file mode 100644 index 000000000..8baa6537e --- /dev/null +++ b/pyGHDL/dom/Common.py @@ -0,0 +1,48 @@ +from pydecor import export + +from pyVHDLModel.VHDLModel import Modes + +from libghdl.thin import name_table +from libghdl.thin.vhdl import nodes + +__all__ = [] +__api__ = __all__ + + +@export +class GHDLBaseException(Exception): + pass + + +@export +class LibGHDLException(GHDLBaseException): + pass + + +@export +class GHDLException(GHDLBaseException): + pass + + +@export +class GHDLMixin: + _MODE_TRANSLATION = { + nodes.Iir_Mode.In_Mode: Modes.In, + nodes.Iir_Mode.Out_Mode: Modes.Out, + nodes.Iir_Mode.Inout_Mode: Modes.InOut, + nodes.Iir_Mode.Buffer_Mode: Modes.Buffer, + nodes.Iir_Mode.Linkage_Mode: Modes.Linkage + } + + @classmethod + def _ghdlNodeToName(cls, node): + """Return the python string from node :param:`node` identifier""" + return name_table.Get_Name_Ptr(nodes.Get_Identifier(node)).decode("utf-8") + + @classmethod + def _ghdlPortToMode(cls, port): + """Return the mode of a port.""" + try: + return cls._MODE_TRANSLATION[nodes.Get_Mode(port)] + except KeyError: + raise LibGHDLException("Unknown mode.") diff --git a/pyGHDL/dom/DesignUnit.py b/pyGHDL/dom/DesignUnit.py new file mode 100644 index 000000000..894651c2c --- /dev/null +++ b/pyGHDL/dom/DesignUnit.py @@ -0,0 +1,100 @@ +from pydecor import export + +from pyVHDLModel.VHDLModel import Entity as VHDLModel_Entity +from pyVHDLModel.VHDLModel import Architecture as VHDLModel_Architecture +from pyVHDLModel.VHDLModel import Package as VHDLModel_Package +from pyVHDLModel.VHDLModel import PackageBody as VHDLModel_PackageBody +from pyVHDLModel.VHDLModel import Context as VHDLModel_Context +from pyVHDLModel.VHDLModel import Configuration as VHDLModel_Configuration + +from libghdl.thin.vhdl import nodes, pyutils + +from pyGHDL.dom.Common import GHDLMixin + +__all__ = [] +__api__ = __all__ + +from pyGHDL.dom.InterfaceItem import GenericConstantInterfaceItem, PortSignalInterfaceItem + + +@export +class Entity(VHDLModel_Entity, GHDLMixin): + + @classmethod + def parse(cls, libraryUnit): + name = cls._ghdlNodeToName(libraryUnit) + entity = cls(name) + + cls.__parseGenerics(libraryUnit, entity) + cls.__parsePorts(libraryUnit, entity) + + return entity + + @classmethod + def __ghdlGetGenerics(cls, entity): + return pyutils.chain_iter(nodes.Get_Generic_Chain(entity)) + + @classmethod + def __ghdlGetPorts(cls, entity): + return pyutils.chain_iter(nodes.Get_Port_Chain(entity)) + + @classmethod + def __parseGenerics(cls, libraryUnit, entity): + for generic in cls.__ghdlGetGenerics(libraryUnit): + genericConstant = GenericConstantInterfaceItem.parse(generic) + entity.GenericItems.append(genericConstant) + + @classmethod + def __parsePorts(cls, libraryUnit, entity): + for port in cls.__ghdlGetPorts(libraryUnit): + signalPort = PortSignalInterfaceItem.parse(port) + entity.PortItems.append(signalPort) + +@export +class Architecture(VHDLModel_Architecture, GHDLMixin): + def __init__(self, name: str, entityName: str): + super().__init__(name) + + self.__entityName = entityName + + @classmethod + def parse(cls, libraryUnit): + name = cls._ghdlNodeToName(libraryUnit) + entityName = cls._ghdlNodeToName(nodes.Get_Entity_Name(libraryUnit)) + + return cls(name, entityName) + + def resolve(self): + pass + +@export +class Package(VHDLModel_Package, GHDLMixin): + + @classmethod + def parse(cls, libraryUnit): + name = cls._ghdlNodeToName(libraryUnit) + return cls(name) + +@export +class PackageBody(VHDLModel_PackageBody, GHDLMixin): + + @classmethod + def parse(cls, libraryUnit): + name = cls._ghdlNodeToName(libraryUnit) + return cls(name) + +@export +class Context(VHDLModel_Context, GHDLMixin): + + @classmethod + def parse(cls, libraryUnit): + name = cls._ghdlNodeToName(libraryUnit) + return cls(name) + +@export +class Configuration(VHDLModel_Configuration, GHDLMixin): + + @classmethod + def parse(cls, libraryUnit): + name = cls._ghdlNodeToName(libraryUnit) + return cls(name) diff --git a/pyGHDL/dom/InterfaceItem.py b/pyGHDL/dom/InterfaceItem.py new file mode 100644 index 000000000..0833c2547 --- /dev/null +++ b/pyGHDL/dom/InterfaceItem.py @@ -0,0 +1,33 @@ +from pydecor import export + +from pyVHDLModel.VHDLModel import PortSignalInterfaceItem as VHDLModel_PortSignalInterfaceItem +from pyVHDLModel.VHDLModel import GenericConstantInterfaceItem as VHDLModel_GenericConstantInterfaceItem + +from pyGHDL.dom.Common import GHDLMixin + +__all__ = [] +__api__ = __all__ + + +@export +class GenericConstantInterfaceItem(VHDLModel_GenericConstantInterfaceItem, GHDLMixin): + @classmethod + def parse(cls, generic): + name = cls._ghdlNodeToName(generic) + mode = cls._ghdlPortToMode(generic) + + generic = cls(name, mode) + + return generic + + +@export +class PortSignalInterfaceItem(VHDLModel_PortSignalInterfaceItem, GHDLMixin): + @classmethod + def parse(cls, port): + name = cls._ghdlNodeToName(port) + mode = cls._ghdlPortToMode(port) + + port = cls(name, mode) + + return port diff --git a/pyGHDL/dom/Misc.py b/pyGHDL/dom/Misc.py new file mode 100644 index 000000000..ab47576fc --- /dev/null +++ b/pyGHDL/dom/Misc.py @@ -0,0 +1,96 @@ +from pathlib import Path +from typing import Any + +from pydecor import export + +from pyVHDLModel.VHDLModel import Design as VHDLModel_Design +from pyVHDLModel.VHDLModel import Library as VHDLModel_Library +from pyVHDLModel.VHDLModel import Document as VHDLModel_Document + +import libghdl +from libghdl.thin import name_table, files_map, errorout_console +from libghdl.thin.vhdl import nodes, sem_lib + +from pyGHDL.dom.Common import LibGHDLException, GHDLException +from pyGHDL.dom.DesignUnit import Entity, Architecture, Package, PackageBody, Context, Configuration + +__all__ = [] +__api__ = __all__ + + +@export +class Design(VHDLModel_Design): + def __init__(self): + super().__init__() + + self.__ghdl_init() + + def __ghdl_init(self): + """Initialization: set options and then load libraries""" + + # Print error messages on the console + errorout_console.Install_Handler() + + libghdl.set_option(b"--std=08") + libghdl.analyze_init() + +@export +class Library(VHDLModel_Library): + pass + + +@export +class Document(VHDLModel_Document): + __ghdlFileID: Any + __ghdlSourceFileEntry: Any + __ghdlFile: Any + + def __init__(self, path : Path = None): + super().__init__(path) + + self.__ghdl_init() + + def __ghdl_init(self): + # Read input file + self.__ghdlFileID = name_table.Get_Identifier(str(self.Path).encode("utf_8")) + self.__ghdlSourceFileEntry = files_map.Read_Source_File(name_table.Null_Identifier, self.__ghdlFileID) + if self.__ghdlSourceFileEntry == files_map.No_Source_File_Entry: + raise LibGHDLException("Cannot load file '{!s}'".format(self.Path)) + + # parse + self.__ghdlFile = sem_lib.Load_File(self.__ghdlSourceFileEntry) + + def parse(self): + unit = nodes.Get_First_Design_Unit(self.__ghdlFile) + while unit != nodes.Null_Iir: + libraryUnit = nodes.Get_Library_Unit(unit) + nodeKind = nodes.Get_Kind(libraryUnit) + + if (nodeKind == nodes.Iir_Kind.Entity_Declaration): + entity = Entity.parse(libraryUnit) + self.Entities.append(entity) + + elif (nodeKind == nodes.Iir_Kind.Architecture_Body): + architecture = Architecture.parse(libraryUnit) + self.Architectures.append(architecture) + + elif (nodeKind == nodes.Iir_Kind.Package_Declaration): + package = Package.parse(libraryUnit) + self.Packages.append(package) + + elif (nodeKind == nodes.Iir_Kind.Package_Body): + packageBody = PackageBody.parse(libraryUnit) + self.PackageBodies.append(packageBody) + + elif (nodeKind == nodes.Iir_Kind.Context_Declaration): + context = Context.parse(libraryUnit) + self.Contexts.append(context) + + elif (nodeKind == nodes.Iir_Kind.Configuration_Declaration): + configuration = Configuration.parse(libraryUnit) + self.Configurations.append(configuration) + + else: + raise GHDLException("Unknown design unit kind.") + + unit = nodes.Get_Chain(unit) diff --git a/pyGHDL/dom/__init__.py b/pyGHDL/dom/__init__.py new file mode 100644 index 000000000..df2dfb868 --- /dev/null +++ b/pyGHDL/dom/__init__.py @@ -0,0 +1,4 @@ +from pydecor import export + +__all__ = [] +__api__ = __all__ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..6f7932078 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +pydecor>=2.0.1 +pyVHDLModel>=0.7.1 diff --git a/testsuite/pyunit/dom/Instantiate.py b/testsuite/pyunit/dom/Instantiate.py new file mode 100644 index 000000000..429c1a5a8 --- /dev/null +++ b/testsuite/pyunit/dom/Instantiate.py @@ -0,0 +1,25 @@ +from pathlib import Path +from unittest import TestCase + +from pyVHDLModel.VHDLModel import Design, Library, Document, Entity + + +if __name__ == "__main__": + print("ERROR: you called a testcase declaration file as an executable module.") + print("Use: 'python -m unitest <testcase module>'") + exit(1) + + +class Instantiate(TestCase): + def test_Design(self): + design = Design() + + def test_Library(self): + library = Library() + + def test_Document(self): + path = Path("tests.vhdl") + document = Document(path) + + def test_Entity(self): + entity = Entity("entity_1") diff --git a/testsuite/pyunit/libghdl/Initialize.py b/testsuite/pyunit/libghdl/Initialize.py new file mode 100644 index 000000000..b7e370650 --- /dev/null +++ b/testsuite/pyunit/libghdl/Initialize.py @@ -0,0 +1,20 @@ +from unittest import TestCase + + +if __name__ == "__main__": + print("ERROR: you called a testcase declaration file as an executable module.") + print("Use: 'python -m unitest <testcase module>'") + exit(1) + +class Instantiate(TestCase): + def test_InitializeGHDL(self): + pass + + def test_ReadSourceFile(self): + pass + + def test_ParseFile(self): + pass + + def test_ListDesignUnits(self): + pass diff --git a/testsuite/requirements.txt b/testsuite/requirements.txt new file mode 100644 index 000000000..f7196c036 --- /dev/null +++ b/testsuite/requirements.txt @@ -0,0 +1,4 @@ +-r ../requirements.txt + +# Coverage collection +Coverage>=5.3 |