Coverage for pyEDAA/OutputFilter/Xilinx/Common.py: 91%

265 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 enum import Flag 

33from pathlib import Path 

34from re import Pattern, compile as re_compile 

35from typing import Optional as Nullable, Self, ClassVar, Tuple, Union, Any 

36 

37from pyTooling.Decorators import export, readonly 

38from pyTooling.MetaClasses import ExtendedType 

39 

40 

41@export 

42class LineKind(Flag): 

43 Unprocessed = 0 

44 ProcessorError = 2** 0 

45 Empty = 2** 1 

46 Delimiter = 2** 2 

47 

48 Success = 2** 3 

49 Failed = 2** 4 

50 

51 Verbose = 2**10 

52 Normal = 2**11 

53 Info = 2**12 

54 Warning = 2**13 

55 CriticalWarning = 2**14 

56 Error = 2**15 

57 Fatal = 2**16 

58 

59 Start = 2**20 

60 End = 2**21 

61 Header = 2**22 

62 Content = 2**23 

63 Time = 2**24 

64 Footer = 2**25 

65 

66 Last = 2**29 

67 

68 Message = 2**30 

69 InfoMessage = Message | Info 

70 WarningMessage = Message | Warning 

71 CriticalWarningMessage = Message | CriticalWarning 

72 ErrorMessage = Message | Error 

73 

74 Task = 2**31 

75 TaskStart = Task | Start 

76 TaskEnd = Task | End 

77 TaskTime = Task | Time 

78 

79 Phase = 2**32 

80 PhaseDelimiter = Phase | Delimiter 

81 PhaseStart = Phase | Start 

82 PhaseEnd = Phase | End 

83 PhaseTime = Phase | Time 

84 PhaseFinal = Phase | Footer 

85 

86 SubPhase = 2**33 

87 SubPhaseStart = SubPhase | Start 

88 SubPhaseEnd = SubPhase | End 

89 SubPhaseTime = SubPhase | Time 

90 

91 SubSubPhase = 2**34 

92 SubSubPhaseStart = SubSubPhase | Start 

93 SubSubPhaseEnd = SubSubPhase | End 

94 SubSubPhaseTime = SubSubPhase | Time 

95 

96 SubSubSubPhase = 2**35 

97 SubSubSubPhaseStart = SubSubSubPhase | Start 

98 SubSubSubPhaseEnd = SubSubSubPhase | End 

99 SubSubSubPhaseTime = SubSubSubPhase | Time 

100 

101 Section = 2**36 

102 SectionDelimiter = Section | Delimiter 

103 SectionStart = Section | Start 

104 SectionEnd = Section | End 

105 

106 SubSection = 2**37 

107 SubSectionDelimiter = SubSection | Delimiter 

108 SubSectionStart = SubSection | Start 

109 SubSectionEnd = SubSection | End 

110 

111 Paragraph = 2**38 

112 ParagraphHeadline = Paragraph | Header 

113 

114 Hierarchy = 2**39 

115 HierarchyStart = Hierarchy | Start 

116 HierarchyEnd = Hierarchy | End 

117 

118 XDC = 2**40 

119 XDCStart = XDC | Start 

120 XDCEnd = XDC | End 

121 

122 Table = 2**41 

123 TableFrame = Table | Delimiter 

124 TableHeader = Table | Header 

125 TableRow = Table | Content 

126 TableFooter = Table | Footer 

127 

128 TclCommand = 2**42 

129 GenericTclCommand = TclCommand | 2**0 

130 VivadoTclCommand = TclCommand | 2**1 

131 

132 

133@export 

134class Line(metaclass=ExtendedType, slots=True): 

135 """ 

136 This class represents any line in a log file. 

137 """ 

138 _lineNumber: int 

139 _kind: LineKind 

140 _message: str 

141 _previousLine: "Line" 

142 _nextLine: "Line" 

143 

144 def __init__(self, lineNumber: int, kind: LineKind, message: str) -> None: 

145 self._lineNumber = lineNumber 

146 self._kind = kind 

147 self._message = message 

148 self._previousLine = None 

149 self._nextLine = None 

150 

151 @readonly 

152 def LineNumber(self) -> int: 

153 return self._lineNumber 

154 

155 @readonly 

156 def Kind(self) -> LineKind: 

157 return self._kind 

158 

159 @readonly 

160 def Message(self) -> str: 

161 return self._message 

162 

163 @property 

164 def PreviousLine(self) -> "Line": 

165 return self._previousLine 

166 

167 @PreviousLine.setter 

168 def PreviousLine(self, line: "Line") -> None: 

169 self._previousLine = line 

170 if line is not None: 

171 line._nextLine = self 

172 

173 @readonly 

174 def NextLine(self) -> "Line": 

175 return self._nextLine 

176 

177 def Partition(self, separator: str) -> Tuple[str, str, str]: 

178 return self._message.partition(separator) 

179 

180 def StartsWith(self, prefix: Union[str, Tuple[str, ...]]): 

181 return self._message.startswith(prefix) 

182 

183 def __getitem__(self, item: slice) -> str: 

184 return self._message[item] 

185 

186 def __eq__(self, other: Any): 

187 return self._message == other 

188 

189 def __ne__(self, other: Any): 

190 return self._message != other 

191 

192 def __str__(self) -> str: 

193 return self._message 

194 

195 def __repr__(self) -> str: 

196 return f"{self._lineNumber}: {self._message}" 

197 

198 

199@export 

200class InfoMessage(metaclass=ExtendedType, mixin=True): 

201 pass 

202 

203 

204@export 

205class WarningMessage(metaclass=ExtendedType, mixin=True): 

206 pass 

207 

208 

209@export 

210class CriticalWarningMessage(metaclass=ExtendedType, mixin=True): 

211 pass 

212 

213 

214@export 

215class ErrorMessage(metaclass=ExtendedType, mixin=True): 

216 pass 

217 

218 

219@export 

220class VivadoMessage(Line): 

221 """ 

222 This class represents an AMD/Xilinx Vivado message. 

223 

224 The usual message format is: 

225 

226 .. code-block:: text 

227 

228 INFO: [Synth 8-7079] Multithreading enabled for synth_design using a maximum of 2 processes. 

229 WARNING: [Synth 8-3332] Sequential element (gen[0].Sync/FF2) is unused and will be removed from module sync_Bits_Xilinx. 

230 

231 The following message severities are defined: 

232 

233 * ``INFO`` 

234 * ``WARNING`` 

235 * ``CRITICAL WARNING`` 

236 * ``ERROR`` 

237 

238 .. seealso:: 

239 

240 :class:`VivadoInfoMessage` 

241 Representing a Vivado info message. 

242 

243 :class:`VivadoWarningMessage` 

244 Representing a Vivado warning message. 

245 

246 :class:`VivadoCriticalWarningMessage` 

247 Representing a Vivado critical warning message. 

248 

249 :class:`VivadoErrorMessage` 

250 Representing a Vivado error message. 

251 """ 

252 # _MESSAGE_KIND: ClassVar[str] 

253 # _REGEXP: ClassVar[Pattern] 

254 

255 _toolID: int 

256 _toolName: str 

257 _messageKindID: int 

258 

259 def __init__(self, lineNumber: int, kind: LineKind, tool: str, toolID: int, messageKindID: int, message: str) -> None: 

260 super().__init__(lineNumber, kind, message) 

261 self._toolID = toolID 

262 self._toolName = tool 

263 self._messageKindID = messageKindID 

264 

265 @readonly 

266 def ToolName(self) -> str: 

267 return self._toolName 

268 

269 @readonly 

270 def ToolID(self) -> int: 

271 return self._toolID 

272 

273 @readonly 

274 def MessageKindID(self) -> int: 

275 return self._messageKindID 

276 

277 @classmethod 

278 def Parse(cls, lineNumber: int, kind: LineKind, rawMessage: str) -> Nullable[Self]: 

279 if (match := cls._REGEXP.match(rawMessage)) is not None: 

280 return cls(lineNumber, kind, match[1], int(match[2]), int(match[3]), match[4]) 

281 

282 return None 

283 

284 def __str__(self) -> str: 

285 return f"{self._MESSAGE_KIND}: [{self._toolName} {self._toolID}-{self._messageKindID}] {self._message}" 

286 

287 

288@export 

289class VivadoInfoMessage(VivadoMessage, InfoMessage): 

290 """ 

291 This class represents an AMD/Xilinx Vivado info message. 

292 """ 

293 

294 _MESSAGE_KIND: ClassVar[str] = "INFO" 

295 _REGEXP: ClassVar[Pattern] = re_compile(r"""INFO: \[(\w+) (\d+)-(\d+)\] (.*)""") 

296 

297 @classmethod 

298 def Parse(cls, lineNumber: int, rawMessage: str) -> Nullable[Self]: 

299 return super().Parse(lineNumber, LineKind.InfoMessage, rawMessage) 

300 

301 

302@export 

303class VivadoIrregularInfoMessage(VivadoMessage, InfoMessage): 

304 """ 

305 This class represents an irregular AMD/Xilinx Vivado info message. 

306 """ 

307 

308 _MESSAGE_KIND: ClassVar[str] = "INFO" 

309 _REGEXP: ClassVar[Pattern] = re_compile(r"""INFO: \[(\w+)-(\d+)\] (.*)""") 

310 

311 @classmethod 

312 def Parse(cls, lineNumber: int, rawMessage: str) -> Nullable[Self]: 

313 if (match := cls._REGEXP.match(rawMessage)) is not None: 

314 return cls(lineNumber, LineKind.InfoMessage, match[1], None, int(match[2]), match[3]) 

315 

316 return None 

317 

318 def __str__(self) -> str: 

319 return f"{self._MESSAGE_KIND}: [{self._toolName}-{self._messageKindID}] {self._message}" 

320 

321 

322@export 

323class VivadoWarningMessage(VivadoMessage, WarningMessage): 

324 """ 

325 This class represents an AMD/Xilinx Vivado warning message. 

326 """ 

327 

328 _MESSAGE_KIND: ClassVar[str] = "WARNING" 

329 _REGEXP: ClassVar[Pattern] = re_compile(r"""WARNING: \[(\w+) (\d+)-(\d+)\] (.*)""") 

330 

331 @classmethod 

332 def Parse(cls, lineNumber: int, rawMessage: str) -> Nullable[Self]: 

333 return super().Parse(lineNumber, LineKind.WarningMessage, rawMessage) 

334 

335 

336@export 

337class VivadoIrregularWarningMessage(VivadoMessage, WarningMessage): 

338 """ 

339 This class represents an AMD/Xilinx Vivado warning message. 

340 """ 

341 

342 _MESSAGE_KIND: ClassVar[str] = "WARNING" 

343 _REGEXP: ClassVar[Pattern] = re_compile(r"""WARNING: (.*)""") 

344 

345 @classmethod 

346 def Parse(cls, lineNumber: int, rawMessage: str) -> Nullable[Self]: 

347 if (match := cls._REGEXP.match(rawMessage)) is not None: 

348 return cls(lineNumber, LineKind.WarningMessage, None, None, None, match[1]) 

349 

350 return None 

351 

352 def __str__(self) -> str: 

353 return f"{self._MESSAGE_KIND}: {self._message}" 

354 

355 

356@export 

357class VivadoCriticalWarningMessage(VivadoMessage, CriticalWarningMessage): 

358 """ 

359 This class represents an AMD/Xilinx Vivado critical warning message. 

360 """ 

361 

362 _MESSAGE_KIND: ClassVar[str] = "CRITICAL WARNING" 

363 _REGEXP: ClassVar[Pattern] = re_compile(r"""CRITICAL WARNING: \[(\w+) (\d+)-(\d+)\] (.*)""") 

364 

365 @classmethod 

366 def Parse(cls, lineNumber: int, rawMessage: str) -> Nullable[Self]: 

367 return super().Parse(lineNumber, LineKind.CriticalWarningMessage, rawMessage) 

368 

369 

370@export 

371class VivadoErrorMessage(VivadoMessage, ErrorMessage): 

372 """ 

373 This class represents an AMD/Xilinx Vivado error message. 

374 """ 

375 

376 _MESSAGE_KIND: ClassVar[str] = "ERROR" 

377 _REGEXP: ClassVar[Pattern] = re_compile(r"""ERROR: \[(\w+) (\d+)-(\d+)\] (.*)""") 

378 

379 @classmethod 

380 def Parse(cls, lineNumber: int, rawMessage: str) -> Nullable[Self]: 

381 return super().Parse(lineNumber, LineKind.ErrorMessage, rawMessage) 

382 

383 

384@export 

385class VHDLReportMessage(VivadoInfoMessage): 

386 _REGEXP: ClassVar[Pattern ] = re_compile(r"""RTL report: "(.*)" \[(.*):(\d+)\]""") 

387 

388 _reportMessage: str 

389 _sourceFile: Path 

390 _sourceLineNumber: int 

391 

392 def __init__(self, lineNumber: int, tool: str, toolID: int, messageKindID: int, rawMessage: str, reportMessage: str, sourceFile: Path, sourceLineNumber: int): 

393 super().__init__(lineNumber, LineKind.InfoMessage, tool, toolID, messageKindID, rawMessage) 

394 

395 self._reportMessage = reportMessage 

396 self._sourceFile = sourceFile 

397 self._sourceLineNumber = sourceLineNumber 

398 

399 @classmethod 

400 def Convert(cls, line: VivadoInfoMessage) -> Nullable[Self]: 

401 if (match := cls._REGEXP.match(line._message)) is not None: 401 ↛ 404line 401 didn't jump to line 404 because the condition on line 401 was always true

402 return cls(line._lineNumber, line._toolName, line._toolID, line._messageKindID, line._message, match[1], Path(match[2]), int(match[3])) 

403 

404 return None 

405 

406 

407@export 

408class VHDLAssertionMessage(VHDLReportMessage): 

409 _REGEXP: ClassVar[Pattern ] = re_compile(r"""RTL assertion: "(.*)" \[(.*):(\d+)\]""") 

410 

411 

412@export 

413class TclCommand(Line): 

414 _command: str 

415 _args: Tuple[str, ...] 

416 

417 def __init__(self, lineNumber: int, command: str, arguments: Tuple[str, ...], tclCommand: str) -> None: 

418 super().__init__(lineNumber, LineKind.GenericTclCommand, tclCommand) 

419 

420 self._command = command 

421 self._args = arguments 

422 

423 @readonly 

424 def Command(self) -> str: 

425 return self._command 

426 

427 @readonly 

428 def Arguments(self) -> Tuple[str]: 

429 return self._args 

430 

431 @classmethod 

432 def FromLine(cls, line: Line) -> Nullable[Self]: 

433 args = line._message.split() 

434 

435 return cls(line._lineNumber, args[0], tuple(args[1:]), line._message) 

436 

437 def __str__(self) -> str: 

438 return f"{self._command} {' '.join(self._args)}" 

439 

440 

441@export 

442class VivadoTclCommand(TclCommand): 

443 _PREFIX: ClassVar[str] = "Command:" 

444 

445 @classmethod 

446 def Parse(cls, lineNumber: int, rawMessage: str) -> Nullable[Self]: 

447 command = rawMessage[len(cls._PREFIX) + 1:] 

448 args = command.split() 

449 

450 command = cls(lineNumber, args[0], tuple(args[1:]), command) 

451 command._kind = LineKind.VivadoTclCommand 

452 return command 

453 

454 def __str__(self) -> str: 

455 return f"{self._PREFIX} {self._command} {' '.join(self._args)}"