Coverage for pyEDAA / OutputFilter / Xilinx / __init__.py: 96%

170 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-23 22:13 +0000

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

2# _____ ____ _ _ ___ _ _ _____ _ _ _ # 

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

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

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

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

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

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

9# Authors: # 

10# Patrick Lehmann # 

11# # 

12# License: # 

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

14# Copyright 2025-2026 Electronic Design Automation Abstraction (EDA²) # 

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"""Basic classes for outputs from AMD/Xilinx Vivado.""" 

32from pathlib import Path 

33from typing import Optional as Nullable, Dict, List, Generator, Union, Type 

34 

35from pyTooling.Decorators import export, readonly 

36from pyTooling.MetaClasses import ExtendedType 

37from pyTooling.Stopwatch import Stopwatch 

38 

39from pyEDAA.OutputFilter.Xilinx.Common import LineKind, Line 

40from pyEDAA.OutputFilter.Xilinx.Common import VivadoMessage, TclCommand, VivadoTclCommand 

41from pyEDAA.OutputFilter.Xilinx.Common import InfoMessage, VivadoInfoMessage, VivadoIrregularInfoMessage 

42from pyEDAA.OutputFilter.Xilinx.Common import WarningMessage, VivadoWarningMessage, VivadoIrregularWarningMessage 

43from pyEDAA.OutputFilter.Xilinx.Common import CriticalWarningMessage, VivadoCriticalWarningMessage 

44from pyEDAA.OutputFilter.Xilinx.Common import ErrorMessage, VivadoErrorMessage 

45from pyEDAA.OutputFilter.Xilinx.Common import VHDLReportMessage 

46from pyEDAA.OutputFilter.Xilinx.Commands import Command, SynthesizeDesign, LinkDesign, OptimizeDesign, PlaceDesign, \ 

47 PhysicalOptimizeDesign, RouteDesign, WriteBitstream, ReportDRC, ReportMethodology, ReportPower 

48from pyEDAA.OutputFilter.Xilinx.Common2 import Preamble, VivadoMessagesMixin 

49from pyEDAA.OutputFilter.Xilinx.Exception import ProcessorException, ClassificationException 

50 

51 

52ProcessedLine = Union[Line, ProcessorException] 

53 

54 

55@export 

56class Processor(VivadoMessagesMixin, metaclass=ExtendedType, slots=True): 

57 _duration: float 

58 

59 _lines: List[ProcessedLine] 

60 _preamble: Preamble 

61 _commands: Dict[Type[Command], Command] 

62 

63 def __init__(self) -> None: 

64 super().__init__() 

65 

66 self._duration = 0.0 

67 

68 self._lines = [] 

69 self._preamble = None 

70 self._commands = {} 

71 

72 @readonly 

73 def Lines(self) -> List[ProcessedLine]: 

74 return self._lines 

75 

76 @readonly 

77 def Preamble(self) -> Preamble: 

78 return self._preamble 

79 

80 @readonly 

81 def Commands(self) -> Dict[Type[Command], Command]: 

82 return self._commands 

83 

84 @readonly 

85 def Duration(self) -> float: 

86 return self._duration 

87 

88 def __getitem__(self, item: Type[Command]) -> Command: 

89 return self._commands[item] 

90 

91 def IsIncompleteLog(self) -> bool: 

92 """ 

93 

94 :returns: undocumented 

95 

96 .. note:: 

97 

98 ``INFO: [Common 17-14] Message 'Synth 8-3321' appears 100 times and further instances of the messages will be disabled. Use the Tcl command set_msg_config to change the current settings.`` 

99 """ 

100 return 17 in self._messagesByID and 14 in self._messagesByID[17] 

101 

102 def LineClassification(self) -> Generator[ProcessedLine, str, None]: 

103 # Instantiate and initialize CommandFinder 

104 next(cmdFinder := self.CommandFinder()) 

105 

106 # wait for first line 

107 lastLine = None 

108 rawMessageLine = yield 

109 lineNumber = 0 

110 _errorMessage = "Unknown processing error" 

111 

112 while rawMessageLine is not None: 112 ↛ exitline 112 didn't return from function 'LineClassification' because the condition on line 112 was always true

113 lineNumber += 1 

114 rawMessageLine = rawMessageLine.rstrip() 

115 errorMessage = _errorMessage 

116 

117 if rawMessageLine.startswith(VivadoInfoMessage._MESSAGE_KIND): 

118 if (line := VivadoInfoMessage.Parse(lineNumber, rawMessageLine)) is None: 

119 line = VivadoIrregularInfoMessage.Parse(lineNumber, rawMessageLine) 

120 

121 errorMessage = f"Line starting with 'INFO' was not a VivadoInfoMessage." 

122 elif rawMessageLine.startswith(VivadoWarningMessage._MESSAGE_KIND): 

123 if (line := VivadoWarningMessage.Parse(lineNumber, rawMessageLine)) is None: 

124 line = VivadoIrregularWarningMessage.Parse(lineNumber, rawMessageLine) 

125 

126 errorMessage = f"Line starting with 'WARNING' was not a VivadoWarningMessage." 

127 elif rawMessageLine.startswith(VivadoCriticalWarningMessage._MESSAGE_KIND): 

128 line = VivadoCriticalWarningMessage.Parse(lineNumber, rawMessageLine) 

129 

130 errorMessage = f"Line starting with 'CRITICAL WARNING' was not a VivadoCriticalWarningMessage." 

131 elif rawMessageLine.startswith(VivadoErrorMessage._MESSAGE_KIND): 

132 line = VivadoErrorMessage.Parse(lineNumber, rawMessageLine) 

133 

134 errorMessage = f"Line starting with 'ERROR' was not a VivadoErrorMessage." 

135 elif len(rawMessageLine) == 0: 

136 line = Line(lineNumber, LineKind.Empty, rawMessageLine) 

137 elif rawMessageLine.startswith("Command: "): 

138 line = VivadoTclCommand.Parse(lineNumber, rawMessageLine) 

139 errorMessage = "Line starting with 'Command:' was not a VivadoTclCommand." 

140 else: 

141 line = Line(lineNumber, LineKind.Unprocessed, rawMessageLine) 

142 

143 if line is None: 

144 line = Line(lineNumber, LineKind.ProcessorError, rawMessageLine) 

145 else: 

146 if line.StartsWith("Resolution:") and isinstance(lastLine, VivadoMessage): 

147 line._kind = LineKind.Verbose 

148 

149 line.PreviousLine = lastLine 

150 lastLine = line 

151 line = cmdFinder.send(line) 

152 

153 if isinstance(line, VivadoMessage): 

154 self._AddMessage(line) 

155 

156 if line._kind is LineKind.ProcessorError: 

157 line = ClassificationException(errorMessage, lineNumber, rawMessageLine) 

158 

159 self._lines.append(line) 

160 

161 rawMessageLine = yield line 

162 

163 def CommandFinder(self) -> Generator[Line, Line, None]: 

164 self._preamble = Preamble(self) 

165 cmd = None 

166 

167 tclProcedures = {"source"} 

168 

169 # wait for first line 

170 line = yield 

171 # process preable 

172 line = yield from self._preamble.Generator(line) 

173 

174 while True: 

175 while True: 

176 if line._kind is LineKind.Empty: 

177 line = yield line 

178 continue 

179 elif isinstance(line, VivadoTclCommand): 

180 if line._command == SynthesizeDesign._TCL_COMMAND: 

181 self._commands[SynthesizeDesign] = (cmd := SynthesizeDesign(self)) 

182 line = yield next(gen := cmd.SectionDetector(line)) 

183 break 

184 elif line._command == LinkDesign._TCL_COMMAND: 

185 self._commands[LinkDesign] = (cmd := LinkDesign(self)) 

186 line = yield next(gen := cmd.SectionDetector(line)) 

187 break 

188 elif line._command == OptimizeDesign._TCL_COMMAND: 

189 self._commands[OptimizeDesign] = (cmd := OptimizeDesign(self)) 

190 line = yield next(gen := cmd.SectionDetector(line)) 

191 break 

192 elif line._command == OptimizeDesign._TCL_COMMAND: 192 ↛ 193line 192 didn't jump to line 193 because the condition on line 192 was never true

193 self._commands[OptimizeDesign] = (cmd := OptimizeDesign(self)) 

194 line = yield next(gen := cmd.SectionDetector(line)) 

195 break 

196 elif line._command == PlaceDesign._TCL_COMMAND: 

197 self._commands[PlaceDesign] = (cmd := PlaceDesign(self)) 

198 line = yield next(gen := cmd.SectionDetector(line)) 

199 break 

200 elif line._command == PhysicalOptimizeDesign._TCL_COMMAND: 

201 self._commands[PhysicalOptimizeDesign] = (cmd := PhysicalOptimizeDesign(self)) 

202 line = yield next(gen := cmd.SectionDetector(line)) 

203 break 

204 elif line._command == RouteDesign._TCL_COMMAND: 

205 self._commands[RouteDesign] = (cmd := RouteDesign(self)) 

206 line = yield next(gen := cmd.SectionDetector(line)) 

207 break 

208 elif line._command == WriteBitstream._TCL_COMMAND: 

209 self._commands[WriteBitstream] = (cmd := WriteBitstream(self)) 

210 line = yield next(gen := cmd.SectionDetector(line)) 

211 break 

212 elif line._command == ReportDRC._TCL_COMMAND: 

213 self._commands[ReportDRC] = (cmd := ReportDRC(self)) 

214 line = yield next(gen := cmd.SectionDetector(line)) 

215 break 

216 elif line._command == ReportMethodology._TCL_COMMAND: 

217 self._commands[ReportMethodology] = (cmd := ReportMethodology(self)) 

218 line = yield next(gen := cmd.SectionDetector(line)) 

219 break 

220 elif line._command == ReportPower._TCL_COMMAND: 

221 self._commands[ReportPower] = (cmd := ReportPower(self)) 

222 line = yield next(gen := cmd.SectionDetector(line)) 

223 break 

224 

225 firstWord = line.Partition(" ")[0] 

226 if firstWord in tclProcedures: 

227 line = TclCommand.FromLine(line) 

228 

229 line = yield line 

230 

231 # end = f"{cmd._TCL_COMMAND} completed successfully" 

232 

233 while True: 

234 # if line.StartsWith(end): 

235 # # line._kind |= LineKind.Success 

236 # lastLine = gen.send(line) 

237 # if LineKind.Last in line._kind: 

238 # line._kind ^= LineKind.Last 

239 # line = yield lastLine 

240 # break 

241 

242 try: 

243 line = yield gen.send(line) 

244 except StopIteration as ex: 

245 line = ex.value 

246 break 

247 

248 

249@export 

250class Document(Processor): 

251 _logfile: Path 

252 

253 def __init__(self, logfile: Path) -> None: 

254 super().__init__() 

255 

256 self._logfile = logfile 

257 

258 @readonly 

259 def Logfile(self) -> Path: 

260 return self._logfile 

261 

262 def Parse(self) -> None: 

263 with Stopwatch() as sw: 

264 with self._logfile.open("r", encoding="utf-8") as f: 

265 content = f.read() 

266 

267 next(generator := self.LineClassification()) 

268 for rawLine in content.splitlines(): 

269 generator.send(rawLine) 

270 

271 self._duration = sw.Duration