diff --git a/pyEDAA/ProjectModel/Xilinx/Vivado.py b/pyEDAA/ProjectModel/Xilinx/Vivado.py index 760fe36e..13c193ed 100644 --- a/pyEDAA/ProjectModel/Xilinx/Vivado.py +++ b/pyEDAA/ProjectModel/Xilinx/Vivado.py @@ -30,13 +30,46 @@ # ============================================================================ # from pathlib import Path +from typing import Iterable -from lxml import etree +from xml.dom import minidom, Node from pyVHDLModel import VHDLVersion from pydecor import export -from pyEDAA.ProjectModel import ConstraintFile, ProjectFile, XMLFile, XMLContent, SDCContent, Project, FileSet, \ - VHDLSourceFile, File, VerilogSourceFile +from pyEDAA.ProjectModel import ProjectFile, XMLFile, XMLContent, SDCContent, Project, FileSet, File, Attribute +from pyEDAA.ProjectModel import File as Model_File +from pyEDAA.ProjectModel import ConstraintFile as Model_ConstraintFile +from pyEDAA.ProjectModel import VerilogSourceFile as Model_VerilogSourceFile +from pyEDAA.ProjectModel import VHDLSourceFile as Model_VHDLSourceFile + + +@export +class UsedInAttribute(Attribute): + KEY = "UsedIn" + VALUE_TYPE = Iterable[str] + + def __init__(self): + super().__init__() + + +@export +class File(Model_File): + pass + + +@export +class ConstraintFile(Model_ConstraintFile): + pass + + +@export +class VerilogSourceFile(Model_VerilogSourceFile): + pass + + +@export +class VHDLSourceFile(Model_VHDLSourceFile): + pass @export @@ -54,33 +87,33 @@ def Parse(self): raise Exception(f"Vivado project file '{self._path!s}' not found.") from FileNotFoundError(f"File '{self._path!s}' not found.") try: - with self._path.open(encoding="utf-8") as fileHandle: - content = fileHandle.read() - content = bytes(bytearray(content, encoding="utf-8")) - except OSError as ex: + root = minidom.parse(str(self._path)).documentElement + except Exception as ex: raise Exception(f"Couldn't open '{self._path!s}'.") from ex - XMLParser = etree.XMLParser(remove_blank_text=True, encoding="utf-8") - root = etree.XML(content, XMLParser) - self._xprProject = Project(self._path.stem, rootDirectory=self._path.parent) self._ParseRootElement(root) def _ParseRootElement(self, root): - filesetsNode = root.find("FileSets") - for filesetNode in filesetsNode: - self._ParseFileSet(filesetNode) + for rootNode in root.childNodes: + if rootNode.nodeName == "FileSets": + for fileSetsNode in rootNode.childNodes: + if fileSetsNode.nodeType == Node.ELEMENT_NODE and fileSetsNode.tagName == "FileSet": + self._ParseFileSet(fileSetsNode) def _ParseFileSet(self, filesetNode): - filesetName = filesetNode.get("Name") + filesetName = filesetNode.getAttribute("Name") fileset = FileSet(filesetName, design=self._xprProject.DefaultDesign) - for fileNode in filesetNode: - if fileNode.tag == "File": - self._ParseFile(fileNode, fileset) + for fileNode in filesetNode.childNodes: + if fileNode.nodeType == Node.ELEMENT_NODE: + if fileNode.tagName == "File": + self._ParseFile(fileNode, fileset) + elif fileNode.nodeType == Node.ELEMENT_NODE and fileNode.tagName == "Config": + self._ParseFileSetConfig(fileNode, fileset) def _ParseFile(self, fileNode, fileset): - croppedPath = fileNode.get("Path").replace("$PPRDIR/", "") + croppedPath = fileNode.getAttribute("Path").replace("$PPRDIR/", "") filePath = Path(croppedPath) if filePath.suffix in (".vhd", ".vhdl"): self._ParseVHDLFile(fileNode, filePath, fileset) @@ -96,12 +129,24 @@ def _ParseFile(self, fileNode, fileset): def _ParseVHDLFile(self, fileNode, path, fileset): vhdlFile = VHDLSourceFile(path) fileset.AddFile(vhdlFile) + usedInAttr = [] - if fileNode[0].tag == "FileInfo": - if fileNode[0].get("SFType") == "VHDL2008": - vhdlFile.VHDLVersion = VHDLVersion.VHDL2008 - else: - vhdlFile.VHDLVersion = VHDLVersion.VHDL93 + for childNode in fileNode.childNodes: + if childNode.nodeType == Node.ELEMENT_NODE and childNode.tagName == "FileInfo": + if childNode.getAttribute("SFType") == "VHDL2008": + vhdlFile.VHDLVersion = VHDLVersion.VHDL2008 + else: + vhdlFile.VHDLVersion = VHDLVersion.VHDL93 + + for fileAttribute in childNode.childNodes: + if fileAttribute.nodeType == Node.ELEMENT_NODE and fileAttribute.tagName == "Attr": + if fileAttribute.getAttribute("Name") == "Library": + libraryName = fileAttribute.getAttribute("Val") + vhdlFile.VHDLLibrary = fileset.GetOrCreateVHDLLibrary(libraryName) + elif fileAttribute.getAttribute("Val") == "UsedIn": + usedInAttr.append(fileAttribute.getAttribute("Val")) + + vhdlFile[UsedInAttribute] = usedInAttr def _ParseDefaultFile(self, _, path, fileset): File(path, fileSet=fileset) @@ -115,6 +160,12 @@ def _ParseVerilogFile(self, _, path, fileset): def _ParseXCIFile(self, _, path, fileset): IPCoreInstantiationFile(path, fileSet=fileset) + def _ParseFileSetConfig(self, fileNode, fileset): + for option in fileNode.childNodes: + if option.nodeType == Node.ELEMENT_NODE and option.tagName == "Option": + if option.getAttribute("Name") == "TopModule": + fileset.TopLevel = option.getAttribute("Val") + @export class XDCConstraintFile(ConstraintFile, SDCContent): diff --git a/pyEDAA/ProjectModel/__init__.py b/pyEDAA/ProjectModel/__init__.py index 2dc53e51..b10550a7 100644 --- a/pyEDAA/ProjectModel/__init__.py +++ b/pyEDAA/ProjectModel/__init__.py @@ -42,6 +42,13 @@ __version__ = "0.1.1" +@export +class Attribute: + KEY: str + VALUE_TYPE: typing_Any + + +@export class FileType(type): """ A :term:`meta-class` to construct *FileType* classes. @@ -87,11 +94,12 @@ class File(metaclass=FileType): :arg fileSet: Fileset the file is associated with. """ - _path: Path - _fileType: 'FileType' - _project: Nullable['Project'] - _design: Nullable['Design'] - _fileSet: Nullable['FileSet'] + _path: Path + _fileType: 'FileType' + _project: Nullable['Project'] + _design: Nullable['Design'] + _fileSet: Nullable['FileSet'] + _attributes: Dict[Attribute, typing_Any] = {} def __init__( self, @@ -183,6 +191,16 @@ def FileSet(self, value: 'FileSet') -> None: self._fileSet = value value._files.append(self) + def __getitem__(self, key: Attribute): + try: + return self._attributes[key] + except KeyError: + return self._fileSet[key] + + def __setitem__(self, key: Attribute, value: typing_Any): + x = key.VALUE_TYPE + self._attributes[key] = value + FileTypes = File @@ -293,6 +311,7 @@ def VHDLLibrary(self) -> 'VHDLLibrary': @VHDLLibrary.setter def VHDLLibrary(self, value: 'VHDLLibrary') -> None: self._vhdlLibrary = value + value._files.append(self) @property def VHDLVersion(self) -> VHDLVersion: @@ -449,24 +468,25 @@ class FileSet: :arg svVersion: Default SystemVerilog version for files in this fileset, if not specified for the file itself. """ - _name: str - _project: Nullable['Project'] - _design: Nullable['Design'] - _directory: Nullable[Path] - _parent: Nullable['FileSet'] - _fileSets: Dict[str, 'FileSet'] - _files: List[File] - + _name: str + _topLevel: Nullable[str] + _project: Nullable['Project'] + _design: Nullable['Design'] + _directory: Nullable[Path] + _parent: Nullable['FileSet'] + _fileSets: Dict[str, 'FileSet'] + _files: List[File] + _attributes: Dict[Attribute, typing_Any] + _vhdlLibraries: Dict[str, 'VHDLLibrary'] _vhdlLibrary: 'VHDLLibrary' _vhdlVersion: VHDLVersion _verilogVersion: VerilogVersion _svVersion: SystemVerilogVersion - # TODO: link parent fileset for relative path calculations - def __init__( self, name: str, + topLevel: str = None, directory: Path = Path("."), project: 'Project' = None, design: 'Design' = None, @@ -477,6 +497,7 @@ def __init__( svVersion: SystemVerilogVersion = None ): self._name = name + self._topLevel = topLevel if project is not None: self._project = project self._design = design @@ -494,6 +515,9 @@ def __init__( if design is not None: design._fileSets[name] = self + self._attributes = {} + self._vhdlLibraries = {} + # TODO: handle if vhdlLibrary is a string self._vhdlLibrary = vhdlLibrary self._vhdlVersion = vhdlVersion @@ -504,6 +528,18 @@ def __init__( def Name(self) -> str: return self._name + @Name.setter + def Name(self, value: str) -> None: + self._name = value + + @property + def TopLevel(self) -> str: + return self._topLevel + + @TopLevel.setter + def TopLevel(self, value: str) -> None: + self._topLevel = value + @property def Project(self) -> Nullable['Project']: return self._project @@ -593,6 +629,23 @@ def AddFiles(self, files: Iterable[File]) -> None: self._files.append(file) file._fileSet = self + def __getitem__(self, key): + try: + return self._attributes[key] + except KeyError: + return self._fileSet[key] + + def __setitem__(self, key, value): + self._attributes[key] = value + + def GetOrCreateVHDLLibrary(self, name): + if name in self._vhdlLibraries: + return self._vhdlLibraries[name] + else: + library = VHDLLibrary(name, design=self._design, vhdlVersion=self._vhdlVersion) + self._vhdlLibraries[name] = library + return library + @property def VHDLLibrary(self) -> 'VHDLLibrary': if self._vhdlLibrary is not None: @@ -755,10 +808,13 @@ class Design: """ _name: str + _topLevel: Nullable[str] _project: Nullable['Project'] _directory: Nullable[Path] _fileSets: Dict[str, FileSet] _defaultFileSet: Nullable[FileSet] + _attributes: Dict[Attribute, typing_Any] + _vhdlLibraries: Dict[str, VHDLLibrary] _vhdlVersion: VHDLVersion _verilogVersion: VerilogVersion @@ -768,6 +824,7 @@ class Design: def __init__( self, name: str, + topLevel: str = None, directory: Path = Path("."), project: 'Project' = None, vhdlVersion: VHDLVersion = None, @@ -775,12 +832,14 @@ def __init__( svVersion: SystemVerilogVersion = None ): self._name = name + self._topLevel = topLevel self._project = project if project is not None: project._designs[name] = self self._directory = directory self._fileSets = {} self._defaultFileSet = FileSet("default", project=project, design=self) + self._attributes = {} self._vhdlLibraries = {} self._vhdlVersion = vhdlVersion self._verilogVersion = verilogVersion @@ -791,6 +850,18 @@ def __init__( def Name(self) -> str: return self._name + @Name.setter + def Name(self, value: str) -> None: + self._name = value + + @property + def TopLevel(self) -> str: + return self._topLevel + + @TopLevel.setter + def TopLevel(self, value: str) -> None: + self._topLevel = value + @property def Project(self) -> Nullable['Project']: return self._project @@ -841,9 +912,6 @@ def DefaultFileSet(self, value: Union[str, FileSet]) -> None: else: raise ValueError("Unsupported parameter type for 'value'.") - def __getitem__(self, name: str): - return self._fileSets[name] - # TODO: return generator with another method @property def FileSets(self) -> Dict[str, FileSet]: @@ -866,6 +934,15 @@ def Files(self, fileType: FileType = FileTypes.Any, fileSet: Union[str, FileSet] for file in fileSet.Files(fileType): yield file + def __getitem__(self, key): + try: + return self._attributes[key] + except KeyError: + return self._fileSet[key] + + def __setitem__(self, key, value): + self._attributes[key] = value + @property def VHDLLibraries(self) -> List[VHDLLibrary]: return self._vhdlLibraries.values() @@ -955,6 +1032,8 @@ class Project: _rootDirectory: Nullable[Path] _designs: Dict[str, Design] _defaultDesign: Design + _attributes: Dict[Attribute, typing_Any] + _vhdlVersion: VHDLVersion _verilogVersion: VerilogVersion _svVersion: SystemVerilogVersion @@ -971,6 +1050,7 @@ def __init__( self._rootDirectory = rootDirectory self._designs = {} self._defaultDesign = Design("default", project=self) + self._attributes = {} self._vhdlVersion = vhdlVersion self._verilogVersion = verilogVersion self._svVersion = svVersion @@ -995,9 +1075,6 @@ def ResolvedPath(self) -> Path: else: return path.relative_to(Path.cwd()) - def __getitem__(self, name: str): - return self._designs[name] - # TODO: return generator with another method @property def Designs(self) -> Dict[str, Design]: @@ -1007,6 +1084,15 @@ def Designs(self) -> Dict[str, Design]: def DefaultDesign(self) -> Design: return self._defaultDesign + def __getitem__(self, key): + try: + return self._attributes[key] + except KeyError: + return self._fileSet[key] + + def __setitem__(self, key, value): + self._attributes[key] = value + @property def VHDLVersion(self) -> VHDLVersion: # TODO: check for None and return exception diff --git a/tests/requirements.txt b/tests/requirements.txt index 172211b8..8db23708 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -9,4 +9,3 @@ pytest-cov>=2.12.1 # Static Type Checking mypy>=0.910 -lxml>=4.6.3 diff --git a/tests/unit/Design.py b/tests/unit/Design.py index d1478c35..2b96e1c3 100644 --- a/tests/unit/Design.py +++ b/tests/unit/Design.py @@ -63,7 +63,7 @@ def test_WithProject(self): design = Design(designName, project=project) self.assertIs(project, design.Project) - self.assertIs(design, project[designName]) +# self.assertIs(design, project[designName]) def test_WithVersions(self): vhdlVersion = VHDLVersion.VHDL2019 @@ -98,8 +98,8 @@ def test_ResolveDirectory(self): projectDirectoryPath = Path.cwd() / "project" designDirectory = "designA" - project = Project("project", projectDirectoryPath) - design = Design("design", Path(designDirectory), project=project) + project = Project("project", rootDirectory=projectDirectoryPath) + design = Design("design", directory=Path(designDirectory), project=project) self.assertEqual(f"{projectDirectoryPath.as_posix()}/{designDirectory}", design.ResolvedPath.as_posix()) diff --git a/tests/unit/File.py b/tests/unit/File.py index 5c572db9..37faa04e 100644 --- a/tests/unit/File.py +++ b/tests/unit/File.py @@ -138,7 +138,7 @@ def test_ResolveDirectory(self): filePath = "file_A1.vhdl" project = Project("project", projectDirectoryPath) - design = Design("design", Path(designDirectory), project=project) + design = Design("design", directory=Path(designDirectory), project=project) file = File(Path(filePath), design=design) self.assertEqual(f"{projectDirectoryPath.as_posix()}/{designDirectory}/{filePath}", file.ResolvedPath.as_posix()) diff --git a/tests/unit/FileSet.py b/tests/unit/FileSet.py index 27d35b8e..37898d5b 100644 --- a/tests/unit/FileSet.py +++ b/tests/unit/FileSet.py @@ -62,7 +62,7 @@ def test_WithDesign(self): self.assertIsNotNone(fileset) self.assertEqual(filesetName, fileset.Name) self.assertIs(design, fileset.Design) - self.assertIs(fileset, design[filesetName]) +# self.assertIs(fileset, design[filesetName]) self.assertEqual(0, len(fileset._files)) def test_WithProject(self):