Coverage for pyEDAA/OutputFilter/Xilinx/Common2.py: 88%

332 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 datetime import datetime 

33from re import Pattern, compile as re_compile 

34from typing import ClassVar, Optional as Nullable, Generator, List, Dict, Tuple, Type 

35 

36from pyTooling.Decorators import export, readonly 

37from pyTooling.MetaClasses import ExtendedType 

38from pyTooling.Versioning import YearReleaseVersion 

39 

40from pyEDAA.OutputFilter.Xilinx import Line, LineKind, VivadoMessage 

41from pyEDAA.OutputFilter.Xilinx import VivadoInfoMessage, VivadoWarningMessage, VivadoCriticalWarningMessage, VivadoErrorMessage 

42from pyEDAA.OutputFilter.Xilinx.Exception import ProcessorException 

43 

44 

45@export 

46class VivadoMessagesMixin(metaclass=ExtendedType, mixin=True): 

47 _infoMessages: List[VivadoInfoMessage] 

48 _warningMessages: List[VivadoWarningMessage] 

49 _criticalWarningMessages: List[VivadoCriticalWarningMessage] 

50 _errorMessages: List[VivadoErrorMessage] 

51 _toolIDs: Dict[int, str] 

52 _toolNames: Dict[str, int] 

53 _messagesByID: Dict[int, Dict[int, List[VivadoMessage]]] 

54 

55 def __init__(self) -> None: 

56 self._infoMessages = [] 

57 self._warningMessages = [] 

58 self._criticalWarningMessages = [] 

59 self._errorMessages = [] 

60 self._toolIDs = {} 

61 self._toolNames = {} 

62 self._messagesByID = {} 

63 

64 @readonly 

65 def ToolIDs(self) -> Dict[int, str]: 

66 return self._toolIDs 

67 

68 @readonly 

69 def ToolNames(self) -> Dict[str, int]: 

70 return self._toolNames 

71 

72 @readonly 

73 def MessagesByID(self) -> Dict[int, Dict[int, List[VivadoMessage]]]: 

74 return self._messagesByID 

75 

76 @readonly 

77 def InfoMessages(self) -> List[VivadoInfoMessage]: 

78 return self._infoMessages 

79 

80 @readonly 

81 def WarningMessages(self) -> List[VivadoWarningMessage]: 

82 return self._warningMessages 

83 

84 @readonly 

85 def CriticalWarningMessages(self) -> List[VivadoCriticalWarningMessage]: 

86 return self._criticalWarningMessages 

87 

88 @readonly 

89 def ErrorMessages(self) -> List[VivadoErrorMessage]: 

90 return self._errorMessages 

91 

92 def _AddMessage(self, message: VivadoMessage) -> None: 

93 if isinstance(message, VivadoInfoMessage): 

94 self._infoMessages.append(message) 

95 elif isinstance(message, VivadoWarningMessage): 

96 self._warningMessages.append(message) 

97 elif isinstance(message, VivadoCriticalWarningMessage): 97 ↛ 99line 97 didn't jump to line 99 because the condition on line 97 was always true

98 self._criticalWarningMessages.append(message) 

99 elif isinstance(message, VivadoErrorMessage): 

100 self._errorMessages.append(message) 

101 

102 if message._toolID in self._messagesByID: 

103 sub = self._messagesByID[message._toolID] 

104 if message._messageKindID in sub: 

105 sub[message._messageKindID].append(message) 

106 else: 

107 sub[message._messageKindID] = [message] 

108 else: 

109 self._toolIDs[message._toolID] = message._toolName 

110 self._toolNames[message._toolName] = message._toolID 

111 self._messagesByID[message._toolID] = {message._messageKindID: [message]} 

112 

113 

114@export 

115class BaseParser(VivadoMessagesMixin, metaclass=ExtendedType, slots=True): 

116 def __init__(self) -> None: 

117 super().__init__() 

118 

119 

120@export 

121class Parser(BaseParser): 

122 _processor: "Processor" 

123 

124 def __init__(self, processor: "Processor"): 

125 super().__init__() 

126 

127 self._processor = processor 

128 

129 @readonly 

130 def Processor(self) -> "Processor": 

131 return self._processor 

132 

133 

134@export 

135class Preamble(Parser): 

136 _toolVersion: Nullable[YearReleaseVersion] 

137 _startDatetime: Nullable[datetime] 

138 

139 _VERSION: ClassVar[Pattern] = re_compile(r"""# Vivado v(\d+\.\d(\.\d)?) \(64-bit\)""") 

140 _STARTTIME: ClassVar[Pattern] = re_compile(r"""# Start of session at: (\w+ \w+ \d+ \d+:\d+:\d+ \d+)""") 

141 

142 def __init__(self, processor: "BaseProcessor"): 

143 super().__init__(processor) 

144 

145 self._toolVersion = None 

146 self._startDatetime = None 

147 

148 @readonly 

149 def ToolVersion(self) -> YearReleaseVersion: 

150 return self._toolVersion 

151 

152 @readonly 

153 def StartDatetime(self) -> datetime: 

154 return self._startDatetime 

155 

156 def Generator(self, line: Line) -> Generator[Line, Line, Line]: 

157 if line.StartsWith("#----"): 157 ↛ 160line 157 didn't jump to line 160 because the condition on line 157 was always true

158 line._kind = LineKind.SectionDelimiter 

159 else: 

160 line._kind |= LineKind.ProcessorError 

161 

162 line = yield line 

163 

164 while True: 

165 if (match := self._VERSION.match(line._message)) is not None: 

166 self._toolVersion = YearReleaseVersion.Parse(match[1]) 

167 line._kind = LineKind.Normal 

168 elif (match := self._STARTTIME.match(line._message)) is not None: 

169 self._startDatetime = datetime.strptime(match[1], "%a %b %d %H:%M:%S %Y") 

170 line._kind = LineKind.Normal 

171 elif line.StartsWith("#----"): 

172 line._kind = LineKind.SectionDelimiter 

173 break 

174 else: 

175 line._kind = LineKind.Verbose 

176 

177 line = yield line 

178 

179 nextLine = yield line 

180 return nextLine 

181 

182@export 

183class Task(BaseParser, VivadoMessagesMixin, metaclass=ExtendedType, slots=True): 

184 # _START: ClassVar[str] 

185 # _FINISH: ClassVar[str] 

186 _TIME: ClassVar[str] = "Time (s):" 

187 

188 _PARSERS: ClassVar[Tuple[Type["Phase"], ...]] = tuple() 

189 

190 _command: "Command" 

191 _duration: float 

192 _phases: Dict[Type["Phase"], "Phase"] 

193 

194 def __init__(self, command: "Command"): 

195 super().__init__() 

196 VivadoMessagesMixin.__init__(self) 

197 

198 self._command = command 

199 self._phases = {p: p(self) for p in self._PARSERS} 

200 

201 @readonly 

202 def Command(self) -> "Command": 

203 return self._command 

204 

205 @readonly 

206 def Phases(self) -> Dict[Type["Phase"], "Phase"]: 

207 return self._phases 

208 

209 def __getitem__(self, key: Type["Phase"]) -> "Phase": 

210 return self._phases[key] 

211 

212 def _TaskStart(self, line: Line) -> Generator[Line, Line, Line]: 

213 if not line.StartsWith(self._START): 213 ↛ 214line 213 didn't jump to line 214 because the condition on line 213 was never true

214 raise ProcessorException() 

215 

216 line._kind = LineKind.TaskStart 

217 nextLine = yield line 

218 return nextLine 

219 

220 def _TaskFinish(self, line: Line) -> Generator[Line, Line, Line]: 

221 if not line.StartsWith(self._FINISH): 221 ↛ 222line 221 didn't jump to line 222 because the condition on line 221 was never true

222 raise ProcessorException() 

223 

224 line._kind = LineKind.TaskEnd 

225 line = yield line 

226 while self._TIME is not None: 226 ↛ 233line 226 didn't jump to line 233 because the condition on line 226 was always true

227 if line.StartsWith(self._TIME): 

228 line._kind = LineKind.TaskTime 

229 break 

230 

231 line = yield line 

232 

233 line = yield line 

234 return line 

235 

236 def Generator(self, line: Line) -> Generator[Line, Line, Line]: 

237 line = yield from self._TaskStart(line) 

238 

239 while True: 

240 if line._kind is LineKind.Empty: 

241 line = yield line 

242 continue 

243 elif self._FINISH is not None and line.StartsWith("Ending"): 243 ↛ 244line 243 didn't jump to line 244 because the condition on line 243 was never true

244 break 

245 elif isinstance(line, VivadoMessage): 245 ↛ 246line 245 didn't jump to line 246 because the condition on line 245 was never true

246 self._AddMessage(line) 

247 elif line.StartsWith(self._TIME): 247 ↛ 252line 247 didn't jump to line 252 because the condition on line 247 was always true

248 line._kind = LineKind.TaskTime 

249 nextLine = yield line 

250 return nextLine 

251 

252 line = yield line 

253 

254 nextLine = yield from self._TaskFinish(line) 

255 return nextLine 

256 

257 def __str__(self) -> str: 

258 return self._NAME 

259 

260 

261@export 

262class Phase(BaseParser, VivadoMessagesMixin, metaclass=ExtendedType, slots=True): 

263 # _START: ClassVar[str] 

264 # _FINISH: ClassVar[str] 

265 _TIME: ClassVar[str] = "Time (s):" 

266 

267 _task: Task 

268 _duration: float 

269 

270 def __init__(self, task: Task): 

271 super().__init__() 

272 VivadoMessagesMixin.__init__(self) 

273 

274 self._task = task 

275 

276 @readonly 

277 def Task(self) -> Task: 

278 return self._task 

279 

280 def _PhaseStart(self, line: Line) -> Generator[Line, Line, Line]: 

281 if not line.StartsWith(self._START): 281 ↛ 282line 281 didn't jump to line 282 because the condition on line 281 was never true

282 raise ProcessorException() 

283 

284 line._kind = LineKind.PhaseStart 

285 nextLine = yield line 

286 return nextLine 

287 

288 def _PhaseFinish(self, line: Line) -> Generator[Line, Line, None]: 

289 if not line.StartsWith(self._FINISH): 289 ↛ 290line 289 didn't jump to line 290 because the condition on line 289 was never true

290 raise ProcessorException() 

291 

292 line._kind = LineKind.PhaseEnd 

293 line = yield line 

294 

295 if self._TIME is not None: 295 ↛ 305line 295 didn't jump to line 305 because the condition on line 295 was always true

296 while self._TIME is not None: 296 ↛ 303line 296 didn't jump to line 303 because the condition on line 296 was always true

297 if line.StartsWith(self._TIME): 

298 line._kind = LineKind.PhaseTime 

299 break 

300 

301 line = yield line 

302 

303 line = yield line 

304 

305 return line 

306 

307 def Generator(self, line: Line) -> Generator[Line, Line, Line]: 

308 line = yield from self._PhaseStart(line) 

309 

310 while True: 

311 if line._kind is LineKind.Empty: 

312 line = yield line 

313 continue 

314 elif isinstance(line, VivadoMessage): 

315 self._AddMessage(line) 

316 elif line.StartsWith(self._FINISH): 

317 break 

318 

319 line = yield line 

320 

321 nextLine = yield from self._PhaseFinish(line) 

322 return nextLine 

323 

324@export 

325class SubPhase(BaseParser, VivadoMessagesMixin, metaclass=ExtendedType, slots=True): 

326 # _START: ClassVar[str] 

327 # _FINISH: ClassVar[str] 

328 

329 _phase: Phase 

330 _duration: float 

331 

332 def __init__(self, phase: Phase): 

333 super().__init__() 

334 VivadoMessagesMixin.__init__(self) 

335 

336 self._phase = phase 

337 

338 def _SubPhaseStart(self, line: Line) -> Generator[Line, Line, Line]: 

339 if not line.StartsWith(self._START): 339 ↛ 340line 339 didn't jump to line 340 because the condition on line 339 was never true

340 raise ProcessorException() 

341 

342 line._kind = LineKind.SubPhaseStart 

343 nextLine = yield line 

344 return nextLine 

345 

346 def _SubPhaseFinish(self, line: Line) -> Generator[Line, Line, None]: 

347 if not line.StartsWith(self._FINISH): 347 ↛ 348line 347 didn't jump to line 348 because the condition on line 347 was never true

348 raise ProcessorException() 

349 

350 if self._TIME is None: 

351 line._kind = LineKind.SubPhaseTime 

352 else: 

353 line._kind = LineKind.SubPhaseEnd 

354 

355 line = yield line 

356 while self._TIME is not None: 356 ↛ 363line 356 didn't jump to line 363 because the condition on line 356 was always true

357 if line.StartsWith(self._TIME): 

358 line._kind = LineKind.SubPhaseTime 

359 break 

360 

361 line = yield line 

362 

363 nextLine = yield line 

364 return nextLine 

365 

366 def Generator(self, line: Line) -> Generator[Line, Line, Line]: 

367 line = yield from self._SubPhaseStart(line) 

368 

369 while True: 

370 if line._kind is LineKind.Empty: 

371 line = yield line 

372 continue 

373 elif line.StartsWith(self._FINISH): 

374 break 

375 elif isinstance(line, VivadoMessage): 

376 self._AddMessage(line) 

377 

378 line = yield line 

379 

380 nextLine = yield from self._SubPhaseFinish(line) 

381 return nextLine 

382 

383 

384@export 

385class SubSubPhase(BaseParser, VivadoMessagesMixin, metaclass=ExtendedType, slots=True): 

386 # _START: ClassVar[str] 

387 # _FINISH: ClassVar[str] 

388 

389 _subphase: SubPhase 

390 _duration: float 

391 

392 def __init__(self, subphase: SubPhase): 

393 super().__init__() 

394 VivadoMessagesMixin.__init__(self) 

395 

396 self._subphase = subphase 

397 

398 def _SubSubPhaseStart(self, line: Line) -> Generator[Line, Line, Line]: 

399 if not line.StartsWith(self._START): 399 ↛ 400line 399 didn't jump to line 400 because the condition on line 399 was never true

400 raise ProcessorException() 

401 

402 line._kind = LineKind.SubSubPhaseStart 

403 nextLine = yield line 

404 return nextLine 

405 

406 def _SubSubPhaseFinish(self, line: Line) -> Generator[Line, Line, None]: 

407 if not line.StartsWith(self._FINISH): 407 ↛ 408line 407 didn't jump to line 408 because the condition on line 407 was never true

408 raise ProcessorException() 

409 

410 line._kind = LineKind.SubSubPhaseEnd 

411 line = yield line 

412 

413 while self._TIME is not None: 413 ↛ 420line 413 didn't jump to line 420 because the condition on line 413 was always true

414 if line.StartsWith(self._TIME): 

415 line._kind = LineKind.SubSubPhaseTime 

416 break 

417 

418 line = yield line 

419 

420 nextLine = yield line 

421 return nextLine 

422 

423 def Generator(self, line: Line) -> Generator[Line, Line, Line]: 

424 line = yield from self._SubSubPhaseStart(line) 

425 

426 while True: 

427 if line._kind is LineKind.Empty: 

428 line = yield line 

429 continue 

430 elif line.StartsWith(self._FINISH): 

431 break 

432 elif isinstance(line, VivadoMessage): 

433 self._AddMessage(line) 

434 

435 line = yield line 

436 

437 nextLine = yield from self._SubSubPhaseFinish(line) 

438 return nextLine 

439 

440 

441@export 

442class SubSubSubPhase(BaseParser, VivadoMessagesMixin, metaclass=ExtendedType, slots=True): 

443 # _START: ClassVar[str] 

444 # _FINISH: ClassVar[str] 

445 

446 _subsubphase: SubSubPhase 

447 _duration: float 

448 

449 def __init__(self, subsubphase: SubSubPhase): 

450 super().__init__() 

451 VivadoMessagesMixin.__init__(self) 

452 

453 self._subsubphase = subsubphase 

454 

455 def _SubSubSubPhaseStart(self, line: Line) -> Generator[Line, Line, Line]: 

456 if not line.StartsWith(self._START): 456 ↛ 457line 456 didn't jump to line 457 because the condition on line 456 was never true

457 raise ProcessorException() 

458 

459 line._kind = LineKind.SubSubSubPhaseStart 

460 nextLine = yield line 

461 return nextLine 

462 

463 def _SubSubSubPhaseFinish(self, line: Line) -> Generator[Line, Line, None]: 

464 if not line.StartsWith(self._FINISH): 464 ↛ 465line 464 didn't jump to line 465 because the condition on line 464 was never true

465 raise ProcessorException() 

466 

467 line._kind = LineKind.SubSubSubPhaseEnd 

468 line = yield line 

469 

470 while self._TIME is not None: 470 ↛ 477line 470 didn't jump to line 477 because the condition on line 470 was always true

471 if line.StartsWith(self._TIME): 

472 line._kind = LineKind.SubSubSubPhaseTime 

473 break 

474 

475 line = yield line 

476 

477 nextLine = yield line 

478 return nextLine 

479 

480 def Generator(self, line: Line) -> Generator[Line, Line, Line]: 

481 line = yield from self._SubSubSubPhaseStart(line) 

482 

483 while True: 

484 if line._kind is LineKind.Empty: 

485 line = yield line 

486 continue 

487 elif line.StartsWith(self._FINISH): 

488 break 

489 elif isinstance(line, VivadoMessage): 

490 self._AddMessage(line) 

491 

492 line = yield line 

493 

494 nextLine = yield from self._SubSubSubPhaseFinish(line) 

495 return nextLine