Coverage for pyEDAA/OutputFilter/Xilinx/Commands.py: 85%

461 statements  

« prev     ^ index     » next       coverage.py v7.10.4, created at 2025-08-22 22:11 +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 re import compile as re_compile 

34from typing import ClassVar, Generator, Union, List, Type, Dict, Iterator, Any, Tuple 

35 

36from pyTooling.Decorators import export, readonly 

37 

38from pyEDAA.OutputFilter.Xilinx import VivadoTclCommand 

39from pyEDAA.OutputFilter.Xilinx.Exception import ProcessorException 

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

41from pyEDAA.OutputFilter.Xilinx.Common2 import Parser 

42from pyEDAA.OutputFilter.Xilinx.SynthesizeDesign import Section, RTLElaboration, HandlingCustomAttributes 

43from pyEDAA.OutputFilter.Xilinx.SynthesizeDesign import ConstraintValidation, LoadingPart, ApplySetProperty 

44from pyEDAA.OutputFilter.Xilinx.SynthesizeDesign import RTLComponentStatistics, PartResourceSummary 

45from pyEDAA.OutputFilter.Xilinx.SynthesizeDesign import CrossBoundaryAndAreaOptimization, ROM_RAM_DSP_SR_Retiming 

46from pyEDAA.OutputFilter.Xilinx.SynthesizeDesign import ApplyingXDCTimingConstraints, TimingOptimization 

47from pyEDAA.OutputFilter.Xilinx.SynthesizeDesign import TechnologyMapping, IOInsertion, FlatteningBeforeIOInsertion 

48from pyEDAA.OutputFilter.Xilinx.SynthesizeDesign import FinalNetlistCleanup, RenamingGeneratedInstances 

49from pyEDAA.OutputFilter.Xilinx.SynthesizeDesign import RebuildingUserHierarchy, RenamingGeneratedPorts 

50from pyEDAA.OutputFilter.Xilinx.SynthesizeDesign import RenamingGeneratedNets, WritingSynthesisReport 

51from pyEDAA.OutputFilter.Xilinx.OptimizeDesign import Task, DRCTask, CacheTimingInformationTask, LogicOptimizationTask 

52from pyEDAA.OutputFilter.Xilinx.OptimizeDesign import PowerOptimizationTask, FinalCleanupTask, NetlistObfuscationTask 

53from pyEDAA.OutputFilter.Xilinx.PlaceDesign import PlacerTask 

54from pyEDAA.OutputFilter.Xilinx.PhysicalOptimizeDesign import PhysicalSynthesisTask, InitialUpdateTimingTask 

55from pyEDAA.OutputFilter.Xilinx.RouteDesign import RoutingTask 

56 

57 

58@export 

59class NotPresentException(ProcessorException): 

60 pass 

61 

62 

63@export 

64class SectionNotPresentException(NotPresentException): 

65 pass 

66 

67 

68@export 

69class HandlingCustomAttributes1(HandlingCustomAttributes): 

70 pass 

71 

72 

73@export 

74class HandlingCustomAttributes2(HandlingCustomAttributes): 

75 pass 

76 

77 

78@export 

79class ROM_RAM_DSP_SR_Retiming1(ROM_RAM_DSP_SR_Retiming): 

80 pass 

81 

82 

83@export 

84class ROM_RAM_DSP_SR_Retiming2(ROM_RAM_DSP_SR_Retiming): 

85 pass 

86 

87 

88@export 

89class ROM_RAM_DSP_SR_Retiming3(ROM_RAM_DSP_SR_Retiming): 

90 pass 

91 

92 

93@export 

94class Command(Parser): 

95 # _TCL_COMMAND: ClassVar[str] 

96 

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

98 if not (isinstance(line, VivadoTclCommand) and line._command == self._TCL_COMMAND): 98 ↛ 99line 98 didn't jump to line 99 because the condition on line 98 was never true

99 raise ProcessorException() 

100 

101 nextLine = yield line 

102 return nextLine 

103 

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

105 if line.StartsWith(f"{self._TCL_COMMAND} completed successfully"): 105 ↛ 108line 105 didn't jump to line 108 because the condition on line 105 was always true

106 line._kind |= LineKind.Success 

107 else: 

108 line._kind |= LineKind.Failed 

109 

110 line = yield line 

111 end = f"{self._TCL_COMMAND}: {self._TIME}" 

112 while self._TIME is not None: 

113 if line.StartsWith(end): 113 ↛ 117line 113 didn't jump to line 117 because the condition on line 113 was always true

114 line._kind = LineKind.TaskTime 

115 break 

116 

117 line = yield line 

118 

119 nextLine = yield line 

120 return nextLine 

121 

122 def SectionDetector(self, line: Line) -> Generator[Union[Line, ProcessorException], Line, None]: 

123 line = yield from self._CommandStart(line) 

124 

125 end = f"{self._TCL_COMMAND} " 

126 while True: 

127 if line._kind is LineKind.Empty: 

128 line = yield line 

129 continue 

130 elif isinstance(line, VivadoMessage): 

131 self._AddMessage(line) 

132 elif line.StartsWith(end): 

133 nextLine = yield from self._CommandFinish(line) 

134 return nextLine 

135 

136 line = yield line 

137 

138 

139@export 

140class CommandWithSections(Command): 

141 _sections: Dict[Type[Section], Section] 

142 

143 def __init__(self, processor: "Processor") -> None: 

144 super().__init__(processor) 

145 

146 self._sections = {p: p(self) for p in self._PARSERS} 

147 

148 @readonly 

149 def Sections(self) -> Dict[Type[Section], Section]: 

150 return self._sections 

151 

152 def __getitem__(self, key: Type[Section]) -> Section: 

153 return self._sections[key] 

154 

155 

156@export 

157class CommandWithTasks(Command): 

158 _tasks: Dict[Type[Task], Task] 

159 

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

161 super().__init__(processor) 

162 

163 self._tasks = {t: t(self) for t in self._PARSERS} 

164 

165 @readonly 

166 def Tasks(self) -> Dict[Type[Task], Task]: 

167 return self._tasks 

168 

169 def __getitem__(self, key: Type[Task]) -> Task: 

170 return self._tasks[key] 

171 

172 

173@export 

174class SynthesizeDesign(CommandWithSections): 

175 _TCL_COMMAND: ClassVar[str] = "synth_design" 

176 _PARSERS: ClassVar[List[Type[Section]]] = ( 

177 RTLElaboration, 

178 HandlingCustomAttributes1, 

179 ConstraintValidation, 

180 LoadingPart, 

181 ApplySetProperty, 

182 RTLComponentStatistics, 

183 PartResourceSummary, 

184 CrossBoundaryAndAreaOptimization, 

185 ROM_RAM_DSP_SR_Retiming1, 

186 ApplyingXDCTimingConstraints, 

187 TimingOptimization, 

188 ROM_RAM_DSP_SR_Retiming2, 

189 TechnologyMapping, 

190 IOInsertion, 

191 FlatteningBeforeIOInsertion, 

192 FinalNetlistCleanup, 

193 RenamingGeneratedInstances, 

194 RebuildingUserHierarchy, 

195 RenamingGeneratedPorts, 

196 HandlingCustomAttributes2, 

197 RenamingGeneratedNets, 

198 ROM_RAM_DSP_SR_Retiming3, 

199 WritingSynthesisReport, 

200 ) 

201 

202 @readonly 

203 def HasLatches(self) -> bool: 

204 if (8 in self._messagesByID) and (327 in self._messagesByID[8]): 

205 return True 

206 

207 return "LD" in self._sections[WritingSynthesisReport]._cells 

208 

209 @readonly 

210 def Latches(self) -> Iterator[VivadoMessage]: 

211 try: 

212 yield from iter(self._messagesByID[8][327]) 

213 except KeyError: 

214 yield from () 

215 

216 @readonly 

217 def HasBlackboxes(self) -> bool: 

218 return len(self._sections[WritingSynthesisReport]._blackboxes) > 0 

219 

220 @readonly 

221 def Blackboxes(self) -> Dict[str, int]: 

222 return self._sections[WritingSynthesisReport]._blackboxes 

223 

224 @readonly 

225 def Cells(self) -> Dict[str, int]: 

226 return self._sections[WritingSynthesisReport]._cells 

227 

228 @readonly 

229 def VHDLReportMessages(self) -> List[VHDLReportMessage]: 

230 if 8 in self._messagesByID: 

231 if 6031 in (synthMessages := self._messagesByID[8]): 

232 return [message for message in synthMessages[6031]] 

233 

234 return [] 

235 

236 @readonly 

237 def VHDLAssertMessages(self) -> List[VHDLReportMessage]: 

238 if 8 in self._messagesByID: 

239 if 63 in (synthMessages := self._messagesByID[8]): 

240 return [message for message in synthMessages[63]] 

241 

242 return [] 

243 

244 def __getitem__(self, item: Type[Parser]) -> Union[_PARSERS]: 

245 try: 

246 return self._sections[item] 

247 except KeyError as ex: 

248 raise SectionNotPresentException(F"Section '{item._NAME}' not present in '{self._parent.logfile}'.") from ex 

249 

250 def SectionDetector(self, line: Line) -> Generator[Union[Line, ProcessorException], Line, None]: 

251 if not (isinstance(line, VivadoTclCommand) and line._command == self._TCL_COMMAND): 251 ↛ 252line 251 didn't jump to line 252 because the condition on line 251 was never true

252 raise ProcessorException() 

253 

254 activeParsers: List[Parser] = list(self._sections.values()) 

255 

256 rtlElaboration = self._sections[RTLElaboration] 

257 # constraintValidation = self._sections[ConstraintValidation] 

258 

259 line = yield line 

260 if line == "Starting synth_design": 260 ↛ 263line 260 didn't jump to line 263 because the condition on line 260 was always true

261 line._kind = LineKind.Verbose 

262 else: 

263 raise ProcessorException() 

264 

265 line = yield line 

266 while True: 

267 while True: 

268 if line._kind is LineKind.Empty: 

269 line = yield line 

270 continue 

271 elif line.StartsWith("Start "): 

272 for parser in activeParsers: # type: Section 272 ↛ 278line 272 didn't jump to line 278 because the loop on line 272 didn't complete

273 if line.StartsWith(parser._START): 

274 line = next(section := parser.Generator(line)) 

275 line._previousLine._kind = LineKind.SectionStart | LineKind.SectionDelimiter 

276 break 

277 else: 

278 raise Exception(f"Unknown section: {line!r}") 

279 break 

280 elif line.StartsWith("Starting "): 

281 if line.StartsWith(rtlElaboration._START): 281 ↛ 304line 281 didn't jump to line 304 because the condition on line 281 was always true

282 parser = rtlElaboration 

283 line = next(section := parser.Generator(line)) 

284 line._previousLine._kind = LineKind.SectionStart | LineKind.SectionDelimiter 

285 break 

286 elif line.StartsWith(self._TCL_COMMAND): 

287 if line[len(self._TCL_COMMAND) + 1:].startswith("completed successfully"): 287 ↛ 304line 287 didn't jump to line 304 because the condition on line 287 was always true

288 line._kind |= LineKind.Success 

289 

290 # FIXME: use similar style like for _TIME 

291 line = yield line 

292 lastLine = yield line 

293 return lastLine 

294 elif line.StartsWith("Finished RTL Optimization Phase"): 

295 line._kind = LineKind.PhaseEnd 

296 line._previousLine._kind = LineKind.PhaseEnd | LineKind.PhaseDelimiter 

297 elif line.StartsWith("----"): 

298 if LineKind.Phase in line._previousLine._kind: 

299 line._kind = LineKind.PhaseEnd | LineKind.PhaseDelimiter 

300 elif not isinstance(line, VivadoMessage): 

301 pass 

302 # line._kind = LineKind.Unprocessed 

303 

304 line = yield line 

305 

306 line = yield line 

307 

308 while True: 

309 if line.StartsWith("Finished"): 

310 l = line[9:] 

311 if not (l.startswith("Flattening") or l.startswith("Final")): 

312 line = yield section.send(line) 

313 break 

314 

315 if isinstance(line, VivadoMessage): 

316 self._AddMessage(line) 

317 

318 line = yield section.send(line) 

319 

320 line = yield section.send(line) 

321 

322 activeParsers.remove(parser) 

323 

324 

325@export 

326class LinkDesign(Command): 

327 _TCL_COMMAND: ClassVar[str] = "link_design" 

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

329 

330 _ParsingXDCFile_Pattern = re_compile(r"""^Parsing XDC File \[(.*)\]$""") 

331 _FinishedParsingXDCFile_Pattern = re_compile(r"""^Finished Parsing XDC File \[(.*)\]$""") 

332 _ParsingXDCFileForCell_Pattern = re_compile(r"""^Parsing XDC File \[(.*)\] for cell '(.*)'$""") 

333 _FinishedParsingXDCFileForCell_Pattern = re_compile(r"""^Finished Parsing XDC File \[(.*)\] for cell '(.*)'$""") 

334 

335 _commonXDCFiles: Dict[Path, List[VivadoMessage]] 

336 _perCellXDCFiles: Dict[Path, Dict[str, List[VivadoMessage]]] 

337 

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

339 super().__init__(processor) 

340 

341 self._commonXDCFiles = {} 

342 self._perCellXDCFiles = {} 

343 

344 @readonly 

345 def CommonXDCFiles(self) -> Dict[Path, List[VivadoMessage]]: 

346 return self._commonXDCFiles 

347 

348 @readonly 

349 def PerCellXDCFiles(self) -> Dict[Path, Dict[str, List[VivadoMessage]]]: 

350 return self._perCellXDCFiles 

351 

352 def SectionDetector(self, line: Line) -> Generator[Union[Line, ProcessorException], Line, Line]: 

353 line = yield from self._CommandStart(line) 

354 

355 end = f"{self._TCL_COMMAND} " 

356 while True: 

357 if line._kind is LineKind.Empty: 

358 line = yield line 

359 continue 

360 elif isinstance(line, VivadoMessage): 

361 self._AddMessage(line) 

362 elif (match := self._ParsingXDCFile_Pattern.match(line._message)) is not None: 

363 line._kind = LineKind.Normal 

364 

365 path = Path(match[1]) 

366 self._commonXDCFiles[path] = (messages := []) 

367 

368 line = yield line 

369 while True: 

370 if line._kind is LineKind.Empty: 370 ↛ 371line 370 didn't jump to line 371 because the condition on line 370 was never true

371 line = yield line 

372 continue 

373 elif isinstance(line, VivadoMessage): 

374 self._AddMessage(line) 

375 messages.append(line) 

376 elif (match := self._FinishedParsingXDCFile_Pattern.match(line._message)) is not None and path == Path(match[1]): 

377 line._kind = LineKind.Normal 

378 break 

379 elif line.StartsWith("Finished Parsing XDC File"): 379 ↛ 380line 379 didn't jump to line 380 because the condition on line 379 was never true

380 line._kind = LineKind.ProcessorError 

381 break 

382 elif line.StartsWith(end): 382 ↛ 383line 382 didn't jump to line 383 because the condition on line 382 was never true

383 break 

384 

385 line = yield line 

386 elif (match := self._ParsingXDCFileForCell_Pattern.match(line._message)) is not None: 

387 line._kind = LineKind.Normal 

388 

389 path = Path(match[1]) 

390 cell = match[2] 

391 if path in self._perCellXDCFiles: 

392 self._perCellXDCFiles[path][cell] = (messages := []) 

393 else: 

394 self._perCellXDCFiles[path] = {cell: (messages := [])} 

395 

396 line = yield line 

397 while True: 

398 if line._kind is LineKind.Empty: 398 ↛ 399line 398 didn't jump to line 399 because the condition on line 398 was never true

399 line = yield line 

400 continue 

401 elif isinstance(line, VivadoMessage): 

402 self._AddMessage(line) 

403 messages.append(line) 

404 elif (match := self._FinishedParsingXDCFileForCell_Pattern.match(line._message)) is not None and path == Path(match[1]) and cell == match[2]: 

405 line._kind = LineKind.Normal 

406 break 

407 elif line.StartsWith("Finished Parsing XDC File"): 407 ↛ 408line 407 didn't jump to line 408 because the condition on line 407 was never true

408 line._kind = LineKind.ProcessorError 

409 break 

410 elif line.StartsWith(end): 410 ↛ 411line 410 didn't jump to line 411 because the condition on line 410 was never true

411 break 

412 

413 line = yield line 

414 

415 if line.StartsWith(end): 

416 nextLine = yield from self._CommandFinish(line) 

417 return nextLine 

418 

419 line = yield line 

420 

421 

422@export 

423class OptimizeDesign(CommandWithTasks): 

424 _TCL_COMMAND: ClassVar[str] = "opt_design" 

425 _TIME: ClassVar[str] = None 

426 

427 _PARSERS: ClassVar[Tuple[Type[Task], ...]] = ( 

428 DRCTask, 

429 CacheTimingInformationTask, 

430 LogicOptimizationTask, 

431 PowerOptimizationTask, 

432 FinalCleanupTask, 

433 NetlistObfuscationTask 

434 ) 

435 

436 def SectionDetector(self, line: Line) -> Generator[Union[Line, ProcessorException], Line, Line]: 

437 line = yield from self._CommandStart(line) 

438 

439 activeParsers: List[Task] = list(self._tasks.values()) 

440 

441 while True: 

442 while True: 

443 if line._kind is LineKind.Empty: 

444 line = yield line 

445 continue 

446 elif isinstance(line, VivadoMessage): 

447 self._AddMessage(line) 

448 elif line.StartsWith("Starting ") and not line.StartsWith("Starting Connectivity Check Task"): 

449 for parser in activeParsers: # type: Section 449 ↛ 454line 449 didn't jump to line 454 because the loop on line 449 didn't complete

450 if line.StartsWith(parser._START): 450 ↛ 449line 450 didn't jump to line 449 because the condition on line 450 was always true

451 line = yield next(task := parser.Generator(line)) 

452 break 

453 else: 

454 raise Exception(f"Unknown task: {line!r}") 

455 break 

456 elif line.StartsWith(self._TCL_COMMAND): 

457 if line[len(self._TCL_COMMAND) + 1:].startswith("completed successfully"): 457 ↛ 466line 457 didn't jump to line 466 because the condition on line 457 was always true

458 line._kind |= LineKind.Success 

459 

460 # FIXME: use similar style like for _TIME 

461 line = yield line 

462 lastLine = yield line 

463 return lastLine 

464 # line._kind = LineKind.Unprocessed 

465 

466 line = yield line 

467 

468 while True: 

469 # if line.StartsWith("Ending"): 

470 # line = yield task.send(line) 

471 # break 

472 

473 if isinstance(line, VivadoMessage): 

474 self._AddMessage(line) 

475 

476 try: 

477 line = yield task.send(line) 

478 except StopIteration as ex: 

479 task = None 

480 line = ex.value 

481 

482 if isinstance(line, VivadoMessage): 

483 line = yield line 

484 

485 break 

486 

487 if task is not None: 487 ↛ 488line 487 didn't jump to line 488 because the condition on line 487 was never true

488 line = yield task.send(line) 

489 

490 activeParsers.remove(parser) 

491 

492 

493@export 

494class PlaceDesign(CommandWithTasks): 

495 _TCL_COMMAND: ClassVar[str] = "place_design" 

496 _TIME: ClassVar[str] = None 

497 

498 _PARSERS: ClassVar[Tuple[Type[Task], ...]] = ( 

499 PlacerTask, 

500 ) 

501 

502 def SectionDetector(self, line: Line) -> Generator[Union[Line, ProcessorException], Line, Line]: 

503 line = yield from self._CommandStart(line) 

504 

505 activeParsers: List[Task] = list(self._tasks.values()) 

506 

507 while True: 

508 while True: 

509 if line._kind is LineKind.Empty: 

510 line = yield line 

511 continue 

512 elif isinstance(line, VivadoMessage): 

513 self._AddMessage(line) 

514 elif line.StartsWith("Starting "): 

515 for parser in activeParsers: # type: Section 515 ↛ 520line 515 didn't jump to line 520 because the loop on line 515 didn't complete

516 if line.StartsWith(parser._START): 516 ↛ 515line 516 didn't jump to line 515 because the condition on line 516 was always true

517 line = yield next(task := parser.Generator(line)) 

518 break 

519 else: 

520 raise Exception(f"Unknown task: {line!r}") 

521 break 

522 elif line.StartsWith(self._TCL_COMMAND): 

523 if line[len(self._TCL_COMMAND) + 1:].startswith("completed successfully"): 523 ↛ 532line 523 didn't jump to line 532 because the condition on line 523 was always true

524 line._kind |= LineKind.Success 

525 

526 # FIXME: use similar style like for _TIME 

527 line = yield line 

528 lastLine = yield line 

529 return lastLine 

530 # line._kind = LineKind.Unprocessed 

531 

532 line = yield line 

533 

534 while True: 

535 # if line.StartsWith("Ending"): 

536 # line = yield task.send(line) 

537 # break 

538 

539 if isinstance(line, VivadoMessage): 

540 self._AddMessage(line) 

541 

542 try: 

543 line = yield task.send(line) 

544 except StopIteration as ex: 

545 task = None 

546 line = ex.value 

547 

548 if isinstance(line, VivadoMessage): 548 ↛ 549line 548 didn't jump to line 549 because the condition on line 548 was never true

549 line = yield line 

550 

551 break 

552 

553 if task is not None: 553 ↛ 554line 553 didn't jump to line 554 because the condition on line 553 was never true

554 line = yield task.send(line) 

555 

556 activeParsers.remove(parser) 

557 

558 

559@export 

560class PhysicalOptimizeDesign(CommandWithTasks): 

561 _TCL_COMMAND: ClassVar[str] = "phys_opt_design" 

562 _TIME: ClassVar[str] = None 

563 

564 _PARSERS: ClassVar[Tuple[Type[Task], ...]] = ( 

565 InitialUpdateTimingTask, 

566 PhysicalSynthesisTask 

567 ) 

568 

569 def SectionDetector(self, line: Line) -> Generator[Union[Line, ProcessorException], Line, Line]: 

570 line = yield from self._CommandStart(line) 

571 

572 activeParsers: List[Task] = list(self._tasks.values()) 

573 

574 while True: 

575 while True: 

576 if line._kind is LineKind.Empty: 

577 line = yield line 

578 continue 

579 elif isinstance(line, VivadoMessage): 

580 self._AddMessage(line) 

581 elif line.StartsWith("Starting "): 

582 for parser in activeParsers: # type: Section 582 ↛ 587line 582 didn't jump to line 587 because the loop on line 582 didn't complete

583 if line.StartsWith(parser._START): 583 ↛ 582line 583 didn't jump to line 582 because the condition on line 583 was always true

584 line = yield next(task := parser.Generator(line)) 

585 break 

586 else: 

587 raise Exception(f"Unknown task: {line!r}") 

588 break 

589 elif line.StartsWith(self._TCL_COMMAND): 

590 if line[len(self._TCL_COMMAND) + 1:].startswith("completed successfully"): 590 ↛ 599line 590 didn't jump to line 599 because the condition on line 590 was always true

591 line._kind |= LineKind.Success 

592 

593 # FIXME: use similar style like for _TIME 

594 line = yield line 

595 lastLine = yield line 

596 return lastLine 

597 # line._kind = LineKind.Unprocessed 

598 

599 line = yield line 

600 

601 while True: 

602 # if line.StartsWith("Ending"): 

603 # line = yield task.send(line) 

604 # break 

605 

606 if isinstance(line, VivadoMessage): 

607 self._AddMessage(line) 

608 

609 try: 

610 line = yield task.send(line) 

611 except StopIteration as ex: 

612 task = None 

613 line = ex.value 

614 

615 if isinstance(line, VivadoMessage): 615 ↛ 618line 615 didn't jump to line 618 because the condition on line 615 was always true

616 line = yield line 

617 

618 break 

619 

620 if task is not None: 620 ↛ 621line 620 didn't jump to line 621 because the condition on line 620 was never true

621 line = yield task.send(line) 

622 

623 activeParsers.remove(parser) 

624 

625 

626@export 

627class RouteDesign(CommandWithTasks): 

628 _TCL_COMMAND: ClassVar[str] = "route_design" 

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

630 

631 _PARSERS: ClassVar[Tuple[Type[Task], ...]] = ( 

632 RoutingTask, 

633 ) 

634 

635 def SectionDetector(self, line: Line) -> Generator[Union[Line, ProcessorException], Line, Line]: 

636 line = yield from self._CommandStart(line) 

637 

638 activeParsers: List[Task] = list(self._tasks.values()) 

639 

640 while True: 

641 while True: 

642 if line._kind is LineKind.Empty: 

643 line = yield line 

644 continue 

645 elif isinstance(line, VivadoMessage): 

646 self._AddMessage(line) 

647 elif line.StartsWith("Starting "): 

648 for parser in activeParsers: # type: Section 648 ↛ 653line 648 didn't jump to line 653 because the loop on line 648 didn't complete

649 if line.StartsWith(parser._START): 649 ↛ 648line 649 didn't jump to line 648 because the condition on line 649 was always true

650 line = yield next(task := parser.Generator(line)) 

651 break 

652 else: 

653 raise Exception(f"Unknown task: {line!r}") 

654 break 

655 elif line.StartsWith(self._TCL_COMMAND): 

656 if line[len(self._TCL_COMMAND) + 1:].startswith("completed successfully"): 656 ↛ 665line 656 didn't jump to line 665 because the condition on line 656 was always true

657 line._kind |= LineKind.Success 

658 

659 # FIXME: use similar style like for _TIME 

660 line = yield line 

661 lastLine = yield line 

662 return lastLine 

663 # line._kind = LineKind.Unprocessed 

664 

665 line = yield line 

666 

667 while True: 

668 # if line.StartsWith("Ending"): 

669 # line = yield task.send(line) 

670 # break 

671 

672 if isinstance(line, VivadoMessage): 

673 self._AddMessage(line) 

674 

675 try: 

676 line = yield task.send(line) 

677 except StopIteration as ex: 

678 task = None 

679 line = ex.value 

680 

681 if isinstance(line, VivadoMessage): 681 ↛ 682line 681 didn't jump to line 682 because the condition on line 681 was never true

682 line = yield line 

683 

684 break 

685 

686 if task is not None: 686 ↛ 687line 686 didn't jump to line 687 because the condition on line 686 was never true

687 line = yield task.send(line) 

688 

689 activeParsers.remove(parser) 

690 

691 

692@export 

693class WriteBitstream(Command): 

694 _TCL_COMMAND: ClassVar[str] = "write_bitstream" 

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

696 

697 

698@export 

699class ReportDRC(Command): 

700 _TCL_COMMAND: ClassVar[str] = "report_drc" 

701 _TIME: ClassVar[str] = None 

702 

703 

704@export 

705class ReportMethodology(Command): 

706 _TCL_COMMAND: ClassVar[str] = "report_methodology" 

707 _TIME: ClassVar[str] = None 

708 

709 

710@export 

711class ReportPower(Command): 

712 _TCL_COMMAND: ClassVar[str] = "report_power" 

713 _TIME: ClassVar[str] = None