Coverage for pyEDAA/ProjectModel/Xilinx/Vivado.py: 88%

133 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-10-18 17:08 +0000

1# ==================================================================================================================== # 

2# _____ ____ _ _ ____ _ _ __ __ _ _ # 

3# _ __ _ _| ____| _ \ / \ / \ | _ \ _ __ ___ (_) ___ ___| |_| \/ | ___ __| | ___| | # 

4# | '_ \| | | | _| | | | |/ _ \ / _ \ | |_) | '__/ _ \| |/ _ \/ __| __| |\/| |/ _ \ / _` |/ _ \ | # 

5# | |_) | |_| | |___| |_| / ___ \ / ___ \ _| __/| | | (_) | | __/ (__| |_| | | | (_) | (_| | __/ | # 

6# | .__/ \__, |_____|____/_/ \_\/_/ \_(_)_| |_| \___// |\___|\___|\__|_| |_|\___/ \__,_|\___|_| # 

7# |_| |___/ |__/ # 

8# ==================================================================================================================== # 

9# Authors: # 

10# Patrick Lehmann # 

11# # 

12# License: # 

13# ==================================================================================================================== # 

14# Copyright 2017-2025 Patrick Lehmann - Boetzingen, Germany # 

15# # 

16# Licensed under the Apache License, Version 2.0 (the "License"); # 

17# you may not use this file except in compliance with the License. # 

18# You may obtain a copy of the License at # 

19# # 

20# http://www.apache.org/licenses/LICENSE-2.0 # 

21# # 

22# Unless required by applicable law or agreed to in writing, software # 

23# distributed under the License is distributed on an "AS IS" BASIS, # 

24# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 

25# See the License for the specific language governing permissions and # 

26# limitations under the License. # 

27# # 

28# SPDX-License-Identifier: Apache-2.0 # 

29# ==================================================================================================================== # 

30# 

31"""Specific file types and attributes for Xilinx Vivado.""" 

32from pathlib import Path 

33from typing import Iterable, Optional as Nullable 

34from xml.dom import minidom, Node 

35 

36from pyTooling.Decorators import export 

37from pyTooling.MetaClasses import ExtendedType 

38from pyVHDLModel import VHDLVersion 

39 

40from pyEDAA.ProjectModel import ProjectFile, XMLFile, XMLContent, SDCContent, Project, FileSet, Attribute, Design 

41from pyEDAA.ProjectModel import File as Model_File 

42from pyEDAA.ProjectModel import ConstraintFile as Model_ConstraintFile 

43from pyEDAA.ProjectModel import VerilogSourceFile as Model_VerilogSourceFile 

44from pyEDAA.ProjectModel import VHDLSourceFile as Model_VHDLSourceFile 

45 

46 

47@export 

48class UsedInAttribute(Attribute): 

49 KEY = "UsedIn" 

50 VALUE_TYPE = Iterable[str] 

51 

52 

53@export 

54class ScopeToRefAttribute(Attribute): 

55 KEY = "ScopeToRef" 

56 VALUE_TYPE = Nullable[str] 

57 

58 

59@export 

60class ScopeToCellAttribute(Attribute): 

61 KEY = "ScopeToCell" 

62 VALUE_TYPE = Nullable[str] 

63 

64 

65@export 

66class File(Model_File): 

67 pass 

68 

69 

70@export 

71class VivadoFileMixIn(metaclass=ExtendedType, mixin=True): 

72 def _registerAttributes(self) -> None: 

73 self._attributes[UsedInAttribute] = [] 

74 

75 

76@export 

77class ConstraintFile(Model_ConstraintFile, VivadoFileMixIn): 

78 def _registerAttributes(self) -> None: 

79 super()._registerAttributes() 

80 VivadoFileMixIn._registerAttributes(self) 

81 

82 

83@export 

84class VerilogSourceFile(Model_VerilogSourceFile): 

85 def _registerAttributes(self) -> None: 

86 super()._registerAttributes() 

87 VivadoFileMixIn._registerAttributes(self) 

88 

89 

90@export 

91class VHDLSourceFile(Model_VHDLSourceFile): 

92 def _registerAttributes(self) -> None: 

93 super()._registerAttributes() 

94 VivadoFileMixIn._registerAttributes(self) 

95 

96 

97@export 

98class VivadoProjectFile(ProjectFile, XMLContent): 

99 """A Vivado project file (``*.xpr``).""" 

100 

101 _xprProject: Project 

102 

103 def __init__( 

104 self, 

105 path: Path, 

106 project: Nullable[Project] = None, 

107 design: Nullable[Design] = None, 

108 fileSet: Nullable[FileSet] = None 

109 ) -> None: 

110 super().__init__(path, project, design, fileSet) 

111 

112 self._xprProject = None 

113 

114 @property 

115 def ProjectModel(self) -> Project: 

116 return self._xprProject 

117 

118 def Parse(self) -> None: 

119 if not self._path.exists(): 119 ↛ 120line 119 didn't jump to line 120 because the condition on line 119 was never true

120 raise Exception(f"Vivado project file '{self._path!s}' not found.") from FileNotFoundError(f"File '{self._path!s}' not found.") 

121 

122 try: 

123 root = minidom.parse(str(self._path)).documentElement 

124 except Exception as ex: 

125 raise Exception(f"Couldn't open '{self._path!s}'.") from ex 

126 

127 self._xprProject = Project(self._path.stem, rootDirectory=self._path.parent) 

128 self._ParseRootElement(root) 

129 

130 def _ParseRootElement(self, root) -> None: 

131 for rootNode in root.childNodes: 131 ↛ exitline 131 didn't return from function '_ParseRootElement' because the loop on line 131 didn't complete

132 if rootNode.nodeName == "FileSets": 

133 self._ParseFileSets(rootNode) 

134 break 

135 

136 def _ParseFileSets(self, filesetsNode) -> None: 

137 for fileSetsNode in filesetsNode.childNodes: 

138 if fileSetsNode.nodeType == Node.ELEMENT_NODE and fileSetsNode.tagName == "FileSet": 

139 self._ParseFileSet(fileSetsNode) 

140 

141 def _ParseFileSet(self, filesetNode) -> None: 

142 filesetName = filesetNode.getAttribute("Name") 

143 fileset = FileSet(filesetName, design=self._xprProject.DefaultDesign) 

144 

145 for fileNode in filesetNode.childNodes: 

146 if fileNode.nodeType == Node.ELEMENT_NODE: 

147 if fileNode.tagName == "File": 

148 self._ParseFile(fileNode, fileset) 

149 elif fileNode.nodeType == Node.ELEMENT_NODE and fileNode.tagName == "Config": 

150 self._ParseFileSetConfig(fileNode, fileset) 

151 

152 def _ParseFile(self, fileNode, fileset) -> None: 

153 croppedPath = fileNode.getAttribute("Path").replace("$PPRDIR/", "") 

154 filePath = Path(croppedPath) 

155 if filePath.suffix in (".vhd", ".vhdl"): 

156 self._ParseVHDLFile(fileNode, filePath, fileset) 

157 elif filePath.suffix == ".xdc": 157 ↛ 159line 157 didn't jump to line 159 because the condition on line 157 was always true

158 self._ParseXDCFile(fileNode, filePath, fileset) 

159 elif filePath.suffix == ".v": 

160 self._ParseVerilogFile(fileNode, filePath, fileset) 

161 elif filePath.suffix == ".xci": 

162 self._ParseXCIFile(fileNode, filePath, fileset) 

163 else: 

164 self._ParseDefaultFile(fileNode, filePath, fileset) 

165 

166 def _ParseVHDLFile(self, fileNode, path, fileset) -> None: 

167 vhdlFile = VHDLSourceFile(path) 

168 fileset.AddFile(vhdlFile) 

169 usedInAttr = vhdlFile[UsedInAttribute] 

170 

171 for childNode in fileNode.childNodes: 

172 if childNode.nodeType == Node.ELEMENT_NODE and childNode.tagName == "FileInfo": 

173 if childNode.getAttribute("SFType") == "VHDL2008": 

174 vhdlFile.VHDLVersion = VHDLVersion.VHDL2008 

175 else: 

176 vhdlFile.VHDLVersion = VHDLVersion.VHDL93 

177 

178 for fileAttribute in childNode.childNodes: 

179 if fileAttribute.nodeType == Node.ELEMENT_NODE and fileAttribute.tagName == "Attr": 

180 if fileAttribute.getAttribute("Name") == "Library": 

181 libraryName = fileAttribute.getAttribute("Val") 

182 vhdlFile.VHDLLibrary = fileset.GetOrCreateVHDLLibrary(libraryName) 

183 elif fileAttribute.getAttribute("Val") == "UsedIn": 183 ↛ 184line 183 didn't jump to line 184 because the condition on line 183 was never true

184 usedInAttr.append(fileAttribute.getAttribute("Val")) 

185 

186 def _ParseDefaultFile(self, _, path, fileset) -> None: 

187 File(path, fileSet=fileset) 

188 

189 def _ParseXDCFile(self, _, path, fileset) -> None: 

190 XDCConstraintFile(path, fileSet=fileset) 

191 

192 def _ParseVerilogFile(self, _, path, fileset) -> None: 

193 VerilogSourceFile(path, fileSet=fileset) 

194 

195 def _ParseXCIFile(self, _, path, fileset) -> None: 

196 IPCoreInstantiationFile(path, fileSet=fileset) 

197 

198 def _ParseFileSetConfig(self, fileNode, fileset) -> None: 

199 for option in fileNode.childNodes: 

200 if option.nodeType == Node.ELEMENT_NODE and option.tagName == "Option": 

201 if option.getAttribute("Name") == "TopModule": 

202 fileset.TopLevel = option.getAttribute("Val") 

203 

204 

205@export 

206class XDCConstraintFile(ConstraintFile, SDCContent): 

207 """A Vivado constraint file (Xilinx Design Constraints; ``*.xdc``).""" 

208 

209 def _registerAttributes(self) -> None: 

210 super()._registerAttributes() 

211 self._attributes[ScopeToRefAttribute] = None 

212 self._attributes[ScopeToCellAttribute] = None 

213 

214 

215@export 

216class IPCoreDescriptionFile(XMLFile): 

217 pass 

218 

219 

220@export 

221class IPCoreInstantiationFile(XMLFile): 

222 """A Vivado IP core instantiation file (Xilinx IPCore Instance; ``*.xci``)."""