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

167 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-07-16 06:19 +0000

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

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

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

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

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

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

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

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

9# Authors: # 

10# Patrick Lehmann # 

11# # 

12# License: # 

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

14# Copyright 2025-2025 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): 

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 :return: 

95 

96 .. info:: 

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: 118 ↛ 119line 118 didn't jump to line 119 because the condition on line 118 was never true

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: 123 ↛ 124line 123 didn't jump to line 124 because the condition on line 123 was never true

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): 131 ↛ 132line 131 didn't jump to line 132 because the condition on line 131 was never true

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: 143 ↛ 144line 143 didn't jump to line 144 because the condition on line 143 was never true

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: 156 ↛ 157line 156 didn't jump to line 157 because the condition on line 156 was never true

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: 176 ↛ 177line 176 didn't jump to line 177 because the condition on line 176 was never true

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: 220 ↛ 225line 220 didn't jump to line 225 because the condition on line 220 was always true

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 def Parse(self) -> None: 

259 with Stopwatch() as sw: 

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

261 content = f.read() 

262 

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

264 for rawLine in content.splitlines(): 

265 generator.send(rawLine) 

266 

267 self._duration = sw.Duration