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

417 statements  

« prev     ^ index     » next       coverage.py v7.14.1, created at 2026-06-05 22:59 +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 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, Iterator, Generator, Callable 

36 

37from pyTooling.Decorators import export, readonly 

38from pyTooling.MetaClasses import ExtendedType 

39from pyTooling.Common import getFullyQualifiedName 

40 

41 

42@export 

43class LineKind(Flag): 

44 """ 

45 Classification of a log message line. 

46 """ 

47 Unprocessed = 0 

48 ProcessorError = 2** 0 

49 Empty = 2** 1 

50 Delimiter = 2** 2 

51 

52 Success = 2** 3 

53 Failed = 2** 4 

54 

55 Verbose = 2**10 

56 Normal = 2**11 

57 Info = 2**12 

58 Warning = 2**13 

59 CriticalWarning = 2**14 

60 Error = 2**15 

61 Fatal = 2**16 

62 

63 Start = 2**20 

64 End = 2**21 

65 Header = 2**22 

66 Content = 2**23 

67 Time = 2**24 

68 Footer = 2**25 

69 

70 Last = 2**29 

71 

72 Message = 2**30 

73 InfoMessage = Message | Info 

74 WarningMessage = Message | Warning 

75 CriticalWarningMessage = Message | CriticalWarning 

76 ErrorMessage = Message | Error 

77 

78 Task = 2**31 

79 TaskStart = Task | Start 

80 TaskEnd = Task | End 

81 TaskTime = Task | Time 

82 

83 Phase = 2**32 

84 PhaseDelimiter = Phase | Delimiter 

85 PhaseStart = Phase | Start 

86 PhaseEnd = Phase | End 

87 PhaseTime = Phase | Time 

88 PhaseFinal = Phase | Footer 

89 

90 SubPhase = 2**33 

91 SubPhaseStart = SubPhase | Start 

92 SubPhaseEnd = SubPhase | End 

93 SubPhaseTime = SubPhase | Time 

94 

95 SubSubPhase = 2**34 

96 SubSubPhaseStart = SubSubPhase | Start 

97 SubSubPhaseEnd = SubSubPhase | End 

98 SubSubPhaseTime = SubSubPhase | Time 

99 

100 SubSubSubPhase = 2**35 

101 SubSubSubPhaseStart = SubSubSubPhase | Start 

102 SubSubSubPhaseEnd = SubSubSubPhase | End 

103 SubSubSubPhaseTime = SubSubSubPhase | Time 

104 

105 NestedTask = 2**36 

106 NestedTaskStart = NestedTask | Start 

107 NestedTaskEnd = NestedTask | End 

108 

109 NestedPhase = 2**37 

110 NestedPhaseStart = NestedPhase | Start 

111 NestedPhaseEnd = NestedPhase | End 

112 

113 Section = 2**38 

114 SectionDelimiter = Section | Delimiter 

115 SectionStart = Section | Start 

116 SectionEnd = Section | End 

117 

118 SubSection = 2**39 

119 SubSectionDelimiter = SubSection | Delimiter 

120 SubSectionStart = SubSection | Start 

121 SubSectionEnd = SubSection | End 

122 

123 Paragraph = 2**40 

124 ParagraphHeadline = Paragraph | Header 

125 

126 Hierarchy = 2**41 

127 HierarchyStart = Hierarchy | Start 

128 HierarchyEnd = Hierarchy | End 

129 

130 XDC = 2**42 

131 XDCStart = XDC | Start 

132 XDCEnd = XDC | End 

133 

134 Table = 2**43 

135 TableFrame = Table | Delimiter 

136 TableHeader = Table | Header 

137 TableRow = Table | Content 

138 TableFooter = Table | Footer 

139 

140 TclCommand = 2**44 

141 GenericTclCommand = TclCommand | 2**0 

142 VivadoTclCommand = TclCommand | 2**1 

143 

144 

145@export 

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

147 """ 

148 This class represents any line in a log file. 

149 """ 

150 _lineNumber: int 

151 _kind: LineKind 

152 _message: str 

153 _previousLine: Nullable["Line"] 

154 _nextLine: Nullable["Line"] 

155 

156 def __init__(self, lineNumber: int, kind: LineKind, message: str, previousLine: Nullable["Line"] = None) -> None: 

157 self._lineNumber = lineNumber 

158 self._kind = kind 

159 self._message = message 

160 self._previousLine = previousLine 

161 self._nextLine = None 

162 

163 if previousLine is not None: 

164 previousLine._nextLine = self 

165 

166 @readonly 

167 def LineNumber(self) -> int: 

168 return self._lineNumber 

169 

170 @readonly 

171 def Kind(self) -> LineKind: 

172 return self._kind 

173 

174 @readonly 

175 def Message(self) -> str: 

176 return self._message 

177 

178 @property 

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

180 return self._previousLine 

181 

182 @PreviousLine.setter 

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

184 self._previousLine = line 

185 if line is not None: 

186 line._nextLine = self 

187 

188 @readonly 

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

190 return self._nextLine 

191 

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

193 return self._message.partition(separator) 

194 

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

196 return self._message.startswith(prefix) 

197 

198 def __iter__(self) -> Generator["Line", None, None]: 

199 """ 

200 Forward iteration from the successor of this node. 

201 

202 .. hint:: 

203 

204 Delegates to :meth:`GetIterator` with all defaults. 

205 

206 :returns: A generator yielding each successive :class:`Line` node. 

207 """ 

208 return self.GetIterator() 

209 

210 def GetIterator( 

211 self, 

212 stopPredicate: Nullable[Callable[["Line"], bool]] = None, 

213 *, 

214 reverse: bool = False, 

215 inclusive: bool = True, 

216 maxLines: Nullable[int] = None, 

217 ) -> Generator["Line", None, None]: 

218 """ 

219 Iterate consecutive lines starting from next line towards the end of the log. 

220 

221 If the order is reversed, iterate starting at the previous line towards the beginning of the log. The iteration ends 

222 either at the bounds of the log, by specifying a stop predicate or a maximum number of lines to return. When stopped 

223 this line is usually included in the iteration, but can be excluded. 

224 

225 :param stopPredicate: Optional, a callable receiving a :class:`Line` and returning ``True`` when iteration should 

226 stop at that line. 

227 :param reverse: Optional, reverse the iteration from previous line to the beginning of the log. 

228 :param inclusive: Optional, when ``True`` the line where ``stopPredicate`` or ``maxLines`` triggers, is 

229 included in the iteration, otherwise it's excluded. 

230 :param maxLines: Optional, maximum number of lines to yield. 

231 :returns: A generator yielding :class:`Line` in the requested direction, stopping at the log boundary, 

232 the predicate match, or the line limit — whichever comes first. 

233 :raises TypeError: When ``stopPredicate`` is not callable. 

234 :raises ValueError: When ``maxLines`` is not a positive integer. 

235 """ 

236 if stopPredicate is not None and not callable(stopPredicate): 

237 ex = TypeError("Parameter 'stopPredicate' is not a callable.") 

238 ex.add_note(f"Got type '{getFullyQualifiedName(stopPredicate)}'.") 

239 raise ex 

240 if not isinstance(reverse, bool): 

241 ex = TypeError("Parameter 'reverse' is not a boolean.") 

242 ex.add_note(f"Got type '{getFullyQualifiedName(reverse)}'.") 

243 raise ex 

244 if not isinstance(inclusive, bool): 

245 ex = TypeError("Parameter 'inclusive' is not a boolean.") 

246 ex.add_note(f"Got type '{getFullyQualifiedName(inclusive)}'.") 

247 raise ex 

248 if maxLines is not None: 

249 if not isinstance(maxLines, int): 

250 ex = TypeError("Parameter 'maxLines' is not a integer.") 

251 ex.add_note(f"Got type '{getFullyQualifiedName(maxLines)}'.") 

252 raise ex 

253 elif maxLines <= 0: 

254 ex = ValueError("Parameter 'maxLines' must be a positive integer.") 

255 ex.add_note(f"Got {maxLines!r}.") 

256 raise ex 

257 

258 current = self._previousLine if reverse else self._nextLine 

259 

260 if maxLines is None: 

261 if stopPredicate is None: 

262 if reverse: 

263 while current is not None: 

264 yield current 

265 current = current._previousLine 

266 else: 

267 while current is not None: 

268 yield current 

269 current = current._nextLine 

270 else: 

271 if reverse: 

272 while current is not None: 

273 if stopPredicate(current): 

274 if inclusive: 

275 yield current 

276 return 

277 yield current 

278 current = current._previousLine 

279 else: 

280 while current is not None: 

281 if stopPredicate(current): 

282 if inclusive: 

283 yield current 

284 return 

285 yield current 

286 current = current._nextLine 

287 

288 elif stopPredicate is None: 

289 remaining = maxLines 

290 if reverse: 

291 while current is not None and remaining > 0: 

292 yield current 

293 current = current._previousLine 

294 remaining -= 1 

295 else: 

296 while current is not None and remaining > 0: 

297 yield current 

298 current = current._nextLine 

299 remaining -= 1 

300 

301 else: 

302 remaining = maxLines 

303 if reverse: 

304 while current is not None and remaining > 0: 

305 if stopPredicate(current): 

306 if inclusive: 

307 yield current 

308 return 

309 yield current 

310 current = current._previousLine 

311 remaining -= 1 

312 else: 

313 while current is not None and remaining > 0: 

314 if stopPredicate(current): 

315 if inclusive: 

316 yield current 

317 return 

318 yield current 

319 current = current._nextLine 

320 remaining -= 1 

321 

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

323 return self._message[item] 

324 

325 def __eq__(self, other: Any): 

326 return self._message == other 

327 

328 def __ne__(self, other: Any): 

329 return self._message != other 

330 

331 def __str__(self) -> str: 

332 return self._message 

333 

334 def __repr__(self) -> str: 

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

336 

337 

338@export 

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

340 pass 

341 

342 

343@export 

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

345 pass 

346 

347 

348@export 

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

350 pass 

351 

352 

353@export 

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

355 pass 

356 

357 

358@export 

359class VivadoMessage(Line): 

360 """ 

361 This class represents an AMD/Xilinx Vivado message. 

362 

363 The usual message format is: 

364 

365 .. code-block:: text 

366 

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

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

369 

370 The following message severities are defined: 

371 

372 * ``INFO`` 

373 * ``WARNING`` 

374 * ``CRITICAL WARNING`` 

375 * ``ERROR`` 

376 

377 .. seealso:: 

378 

379 :class:`VivadoInfoMessage` 

380 Representing a Vivado info message. 

381 

382 :class:`VivadoWarningMessage` 

383 Representing a Vivado warning message. 

384 

385 :class:`VivadoCriticalWarningMessage` 

386 Representing a Vivado critical warning message. 

387 

388 :class:`VivadoErrorMessage` 

389 Representing a Vivado error message. 

390 """ 

391 # _MESSAGE_KIND: ClassVar[str] 

392 # _REGEXP: ClassVar[Pattern] 

393 

394 _toolName: Nullable[str] 

395 _toolID: Nullable[int] 

396 _messageKindID: Nullable[int] 

397 

398 def __init__( 

399 self, 

400 lineNumber: int, 

401 kind: LineKind, 

402 message: str, 

403 toolName: Nullable[str] = None, 

404 toolID: Nullable[int] = None, 

405 messageKindID: Nullable[int] = None, 

406 previousLine: Nullable[Line] = None 

407 ) -> None: 

408 super().__init__(lineNumber, kind, message, previousLine) 

409 self._toolName = toolName 

410 self._toolID = toolID 

411 self._messageKindID = messageKindID 

412 

413 @readonly 

414 def ToolName(self) -> Nullable[str]: 

415 return self._toolName 

416 

417 @readonly 

418 def ToolID(self) -> Nullable[int]: 

419 return self._toolID 

420 

421 @readonly 

422 def MessageKindID(self) -> Nullable[int]: 

423 return self._messageKindID 

424 

425 @classmethod 

426 def Parse(cls, lineNumber: int, kind: LineKind, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]: 

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

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

429 

430 return None 

431 

432 def __str__(self) -> str: 

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

434 

435 

436@export 

437class VivadoInfoMessage(VivadoMessage, InfoMessage): 

438 """ 

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

440 

441 .. rubric:: Example 

442 

443 .. code-block:: 

444 

445 INFO: [Common 17-83] 66-Releasing license: Synthesis 

446 """ 

447 

448 _MESSAGE_KIND: ClassVar[str] = "INFO" 

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

450 

451 @classmethod 

452 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]: 

453 return super().Parse(lineNumber, LineKind.InfoMessage, rawMessage, previousLine) 

454 

455 

456@export 

457class VivadoDRCInfoMessage(VivadoMessage, InfoMessage): 

458 """ 

459 This class represents an AMD/Xilinx Vivado Design Rule Check (DRC) info message. 

460 

461 .. rubric:: Example 

462 

463 .. code-block:: 

464 

465 INFO: [DRC AVAL-4] enum_USE_DPORT_FALSE_enum_DREG_ADREG_0_connects_CED_CEAD_RSTD_GND: i_system/xbip_dsp48_macro_0/U0/i_synth/i_synth_option.i_synth_model/opt_7series.i_uniwrap/i_primitive: DSP48E1 is not using the D port (USE_DPORT = FALSE). For improved power characteristics, set DREG and ADREG to '1', tie CED, CEAD, and RSTD to logic '0'. 

466 """ 

467 

468 _MESSAGE_KIND: ClassVar[str] = "INFO" 

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

470 

471 _drcRuleName: str 

472 

473 def __init__( 

474 self, 

475 lineNumber: int, 

476 kind: LineKind, 

477 drcRuleName: str, 

478 message: str, 

479 toolName: Nullable[str] = None, 

480 toolID: Nullable[int] = None, 

481 messageKindID: Nullable[int] = None, 

482 previousLine: Nullable[Line] = None 

483 ) -> None: 

484 super().__init__(lineNumber, kind, message, toolName, toolID, messageKindID, previousLine) 

485 

486 self._drcRuleName = drcRuleName 

487 

488 @readonly 

489 def DRCRuleName(self) -> str: 

490 return self._drcRuleName 

491 

492 @classmethod 

493 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]: 

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

495 return cls(lineNumber, LineKind.WarningMessage, match[1], match[3], toolName="DRC", toolID=None, 

496 messageKindID=int(match[2]), previousLine=previousLine) 

497 

498 return None 

499 

500 def __str__(self) -> str: 

501 return f"{self._MESSAGE_KIND}: [DRC {self._drcRuleName}-{self._messageKindID}] {self._message}" 

502 

503 

504@export 

505class VivadoIrregularInfoMessage(VivadoMessage, InfoMessage): 

506 """ 

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

508 

509 .. rubric:: Example 

510 

511 .. code-block:: 

512 

513 INFO: [runtcl-4] Executing : report_io -file system_top_io_placed.rpt 

514 """ 

515 

516 _MESSAGE_KIND: ClassVar[str] = "INFO" 

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

518 

519 @classmethod 

520 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]: 

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

522 return cls(lineNumber, LineKind.InfoMessage, match[3], toolName=match[1], messageKindID=int(match[2]), previousLine=previousLine) 

523 

524 return None 

525 

526 def __str__(self) -> str: 

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

528 

529 

530@export 

531class VivadoStuntedInfoMessage(VivadoMessage, InfoMessage): 

532 """ 

533 This class represents a stunted AMD/Xilinx Vivado info message. 

534 

535 .. rubric:: Example 

536 

537 .. code-block:: 

538 

539 INFO: Helper process launched with PID 29056 

540 """ 

541 

542 _MESSAGE_KIND: ClassVar[str] = "INFO" 

543 _REGEXP: ClassVar[Pattern] = re_compile(r"""INFO: ([^\[].*)""") 

544 

545 @classmethod 

546 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]: 

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

548 return cls(lineNumber, LineKind.InfoMessage, match[1], previousLine=previousLine) 

549 

550 return None 

551 

552 def __str__(self) -> str: 

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

554 

555 

556@export 

557class VivadoWarningMessage(VivadoMessage, WarningMessage): 

558 """ 

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

560 

561 .. rubric:: Example 

562 

563 .. code-block:: 

564 

565 WARNING: [Synth 8-7080] Parallel synthesis criteria is not met 

566 """ 

567 

568 _MESSAGE_KIND: ClassVar[str] = "WARNING" 

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

570 

571 @classmethod 

572 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]: 

573 return super().Parse(lineNumber, LineKind.WarningMessage, rawMessage, previousLine=previousLine) 

574 

575 

576@export 

577class VivadoDRCWarningMessage(VivadoMessage, WarningMessage): 

578 """ 

579 This class represents an AMD/Xilinx Vivado Design Rule Check (DRC) warning message. 

580 

581 .. rubric:: Example 

582 

583 .. code-block:: 

584 

585 WARNING: [DRC PDCN-1569] LUT equation term check: Used physical LUT pin 'A1' of cell ps/path/to/cell (pin ps/path/to/cell/I0) is not included in the LUT equation: 'O6=(A6+~A6)*((A3*A2)+(A3*(~A2)*A5)+((~A3)*A4*A5)+((~A3)*(~A4)*A2)+((~A3)*(~A4)*(~A2)*A5))'. If this cell is a user instantiated LUT in the design, please remove connectivity to the pin or change the equation and/or INIT string of the LUT to prevent this issue. If the cell is inferred or IP created LUT, please regenerate the IP and/or resynthesize the design to attempt to correct the issue. 

586 """ 

587 

588 _MESSAGE_KIND: ClassVar[str] = "WARNING" 

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

590 

591 _drcRuleName: str 

592 

593 def __init__( 

594 self, 

595 lineNumber: int, 

596 kind: LineKind, 

597 drcRuleName: str, 

598 message: str, 

599 toolName: Nullable[str] = None, 

600 toolID: Nullable[int] = None, 

601 messageKindID: Nullable[int] = None, 

602 previousLine: Nullable[Line] = None 

603 ) -> None: 

604 super().__init__(lineNumber, kind, message, toolName, toolID, messageKindID, previousLine) 

605 

606 self._drcRuleName = drcRuleName 

607 

608 @readonly 

609 def DRCRuleName(self) -> str: 

610 return self._drcRuleName 

611 

612 @classmethod 

613 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]: 

614 if (match := cls._REGEXP.match(rawMessage)) is not None: 614 ↛ 615line 614 didn't jump to line 615 because the condition on line 614 was never true

615 return cls(lineNumber, LineKind.WarningMessage, match[1], match[3], toolName="DRC", toolID=None, messageKindID=int(match[2]), previousLine=previousLine) 

616 

617 return None 

618 

619 def __str__(self) -> str: 

620 return f"{self._MESSAGE_KIND}: [DRC {self._drcRuleName}-{self._messageKindID}] {self._message}" 

621 

622 

623@export 

624class VivadoXPMWarningMessage(VivadoMessage, WarningMessage): 

625 """ 

626 This class represents an AMD/Xilinx Vivado XPM warning message. 

627 

628 .. rubric:: Example 

629 

630 .. code-block:: 

631 

632 WARNING: [XPM_CDC_GRAY: TCL-1000] The source and destination clocks are the same. 

633 """ 

634 

635 _MESSAGE_KIND: ClassVar[str] = "WARNING" 

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

637 

638 _xpmName: str 

639 

640 def __init__( 

641 self, 

642 lineNumber: int, 

643 kind: LineKind, 

644 xpmName: str, 

645 message: str, 

646 toolName: Nullable[str] = None, 

647 toolID: Nullable[int] = None, 

648 messageKindID: Nullable[int] = None, 

649 previousLine: Nullable[Line] = None 

650 ) -> None: 

651 super().__init__(lineNumber, kind, message, toolName, toolID, messageKindID, previousLine) 

652 

653 self._xpmName = xpmName 

654 

655 @readonly 

656 def XPMName(self) -> str: 

657 return self._xpmName 

658 

659 @classmethod 

660 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]: 

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

662 return cls(lineNumber, LineKind.WarningMessage, match[1], match[4], toolName=match[2], toolID=None, messageKindID=int(match[3]), previousLine=previousLine) 

663 

664 return None 

665 

666 def __str__(self) -> str: 

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

668 

669 

670@export 

671class VivadoStuntedWarningMessage(VivadoMessage, WarningMessage): 

672 """ 

673 This class represents a stunted AMD/Xilinx Vivado warning message. 

674 

675 .. rubric:: Example 

676 

677 .. code-block:: 

678 

679 WARNING: set_property ASYNC_REG could not find object (constraint file /path/to/sync_Bits_Xilinx.xdc, line 5). 

680 """ 

681 

682 _MESSAGE_KIND: ClassVar[str] = "WARNING" 

683 _REGEXP: ClassVar[Pattern] = re_compile(r"""WARNING: ([^\[].*)""") 

684 

685 @classmethod 

686 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]: 

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

688 return cls(lineNumber, LineKind.WarningMessage, match[1], previousLine=previousLine) 

689 

690 return None 

691 

692 def __str__(self) -> str: 

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

694 

695 

696@export 

697class VivadoCriticalWarningMessage(VivadoMessage, CriticalWarningMessage): 

698 """ 

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

700 

701 .. rubric:: Example 

702 

703 .. code-block:: 

704 

705 CRITICAL WARNING: [Constraints 18-1056] Clock 'RefClkA_SFP_Quad' completely overrides clock 'USRCLKA_SFP[P]'. 

706 """ 

707 

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

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

710 

711 @classmethod 

712 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]: 

713 return super().Parse(lineNumber, LineKind.CriticalWarningMessage, rawMessage, previousLine) 

714 

715 

716@export 

717class VivadoErrorMessage(VivadoMessage, ErrorMessage): 

718 """ 

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

720 

721 .. rubric:: Example 

722 

723 .. code-block:: 

724 

725 ERROR: [Memdata 28-96] Could not find a BMM_INFO_DESIGN property in the design. Could not generate the merged BMM file: C:/Users/username/git/design.runs/impl_1/system_top_bd.bmm 

726 """ 

727 

728 _MESSAGE_KIND: ClassVar[str] = "ERROR" 

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

730 

731 @classmethod 

732 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]: 

733 return super().Parse(lineNumber, LineKind.ErrorMessage, rawMessage, previousLine) 

734 

735 

736@export 

737class VHDLReportMessage(VivadoInfoMessage): 

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

739 

740 _reportMessage: str 

741 _sourceFile: Path 

742 _sourceLineNumber: int 

743 

744 def __init__( 

745 self, 

746 lineNumber: int, 

747 rawMessage: str, 

748 toolName: str, 

749 toolID: int, 

750 messageKindID: int, 

751 reportMessage: str, 

752 sourceFile: Path, 

753 sourceLineNumber: int, 

754 previousLine: Nullable[Line] = None 

755 ) -> None: 

756 super().__init__(lineNumber, LineKind.InfoMessage, rawMessage, toolName, toolID, messageKindID, previousLine) 

757 

758 self._reportMessage = reportMessage 

759 self._sourceFile = sourceFile 

760 self._sourceLineNumber = sourceLineNumber 

761 

762 @classmethod 

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

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

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

766 

767 return None 

768 

769 

770@export 

771class VHDLAssertionMessage(VHDLReportMessage): 

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

773 

774 

775@export 

776class TclCommand(Line): 

777 """ 

778 Represents a TCL command found in a Vivado log output. 

779 

780 Besides the full log message (:class:`Line`), this class splits the TCL command into the command name and its 

781 arguments. 

782 """ 

783 _command: str 

784 _arguments: Tuple[str, ...] 

785 

786 def __init__( 

787 self, 

788 lineNumber: int, 

789 command: str, 

790 arguments: Tuple[str, ...], 

791 rawMessage: str, 

792 previousLine: Nullable[Line] = None 

793 ) -> None: 

794 super().__init__(lineNumber, LineKind.GenericTclCommand, rawMessage, previousLine) 

795 

796 self._command = command 

797 self._arguments = arguments 

798 

799 @readonly 

800 def Command(self) -> str: 

801 return self._command 

802 

803 @readonly 

804 def Arguments(self) -> Tuple[str, ...]: 

805 return self._arguments 

806 

807 @classmethod 

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

809 args = line._message.split() 

810 

811 return cls(line._lineNumber, args[0], tuple(args[1:]), line._message, previousLine=line._previousLine) 

812 

813 def __str__(self) -> str: 

814 return f"{self._command} {' '.join(self._arguments)}" 

815 

816 

817@export 

818class VivadoTclCommand(TclCommand): 

819 """ 

820 Represents a Vivado specific TCL command. 

821 """ 

822 

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

824 

825 @classmethod 

826 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]: 

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

828 args = command.split() 

829 

830 vivadoCommand = cls(lineNumber, args[0], tuple(args[1:]), rawMessage, previousLine) 

831 vivadoCommand._kind = LineKind.VivadoTclCommand 

832 return vivadoCommand 

833 

834 def __str__(self) -> str: 

835 return f"{self._PREFIX} {self._command} {' '.join(self._arguments)}"