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

386 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"""A filtering anc classification processor for AMD/Xilinx Vivado Synthesis outputs.""" 

32from typing import Generator, ClassVar, List, Type, Dict, Tuple 

33 

34from pyTooling.Decorators import export 

35from pyTooling.MetaClasses import ExtendedType 

36 

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

38from pyEDAA.OutputFilter.Xilinx.Exception import ProcessorException 

39from pyEDAA.OutputFilter.Xilinx.Common2 import BaseParser, VivadoMessagesMixin 

40 

41 

42@export 

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

44 # _START: ClassVar[str] 

45 # _FINISH: ClassVar[str] 

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

47 

48 _command: "Command" 

49 _duration: float 

50 

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

52 super().__init__() 

53 VivadoMessagesMixin.__init__(self) 

54 

55 self._command = command 

56 

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

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

59 raise ProcessorException() 

60 

61 line._kind = LineKind.TaskStart 

62 nextLine = yield line 

63 return nextLine 

64 

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

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

67 raise ProcessorException() 

68 

69 line._kind = LineKind.TaskEnd 

70 line = yield line 

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

72 if line.StartsWith(self._TIME): 

73 line._kind = LineKind.TaskTime 

74 break 

75 

76 line = yield line 

77 

78 line = yield line 

79 return line 

80 

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

82 line = yield from self._TaskStart(line) 

83 

84 while True: 

85 if line._kind is LineKind.Empty: 

86 line = yield line 

87 continue 

88 elif line.StartsWith("Ending"): 

89 break 

90 elif isinstance(line, VivadoMessage): 

91 self._AddMessage(line) 

92 elif line.StartsWith(self._TIME): 

93 line._kind = LineKind.TaskTime 

94 nextLine = yield line 

95 return nextLine 

96 

97 line = yield line 

98 

99 nextLine = yield from self._TaskFinish(line) 

100 return nextLine 

101 

102 

103@export 

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

105 # _START: ClassVar[str] 

106 # _FINISH: ClassVar[str] 

107 

108 _task: Task 

109 _duration: float 

110 

111 def __init__(self, task: Task): 

112 super().__init__() 

113 VivadoMessagesMixin.__init__(self) 

114 

115 self._task = task 

116 

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

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

119 raise ProcessorException() 

120 

121 line._kind = LineKind.PhaseStart 

122 nextLine = yield line 

123 return nextLine 

124 

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

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

127 raise ProcessorException() 

128 

129 line._kind = LineKind.PhaseEnd 

130 line = yield line 

131 

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

133 if line.StartsWith(self._TIME): 

134 line._kind = LineKind.PhaseTime 

135 break 

136 

137 line = yield line 

138 

139 line = yield line 

140 while self._FINAL is not None: 

141 if line.StartsWith(self._FINAL): 141 ↛ 145line 141 didn't jump to line 145 because the condition on line 141 was always true

142 line._kind = LineKind.PhaseFinal 

143 break 

144 

145 line = yield line 

146 

147 nextLine = yield line 

148 return nextLine 

149 

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

151 line = yield from self._PhaseStart(line) 

152 

153 while True: 

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

155 line = yield line 

156 continue 

157 elif line.StartsWith(self._FINISH): 

158 break 

159 elif isinstance(line, VivadoMessage): 

160 self._AddMessage(line) 

161 

162 line = yield line 

163 

164 nextLine = yield from self._PhaseFinish(line) 

165 return nextLine 

166 

167 

168@export 

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

170 # _START: ClassVar[str] 

171 # _FINISH: ClassVar[str] 

172 

173 _phase: Phase 

174 _duration: float 

175 

176 def __init__(self, phase: Phase): 

177 super().__init__() 

178 VivadoMessagesMixin.__init__(self) 

179 

180 self._phase = phase 

181 

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

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

184 raise ProcessorException() 

185 

186 line._kind = LineKind.SubPhaseStart 

187 nextLine = yield line 

188 return nextLine 

189 

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

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

192 raise ProcessorException() 

193 

194 line._kind = LineKind.SubPhaseEnd 

195 line = yield line 

196 

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

198 if line.StartsWith(self._TIME): 

199 line._kind = LineKind.SubPhaseTime 

200 break 

201 

202 line = yield line 

203 

204 nextLine = yield line 

205 return nextLine 

206 

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

208 line = yield from self._SubPhaseStart(line) 

209 

210 while True: 

211 if line._kind is LineKind.Empty: 

212 line = yield line 

213 continue 

214 elif line.StartsWith(self._FINISH): 

215 break 

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

217 self._AddMessage(line) 

218 

219 line = yield line 

220 

221 nextLine = yield from self._SubPhaseFinish(line) 

222 return nextLine 

223 

224 

225@export 

226class Phase11_CoreGenerationAndDesignSetup(SubPhase): 

227 _START: ClassVar[str] = "Phase 1.1 Core Generation And Design Setup" 

228 _FINISH: ClassVar[str] = "Phase 1.1 Core Generation And Design Setup | Checksum:" 

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

230 

231 

232@export 

233class Phase12_SetupConstraintsAndSortNetlist(SubPhase): 

234 _START: ClassVar[str] = "Phase 1.2 Setup Constraints And Sort Netlist" 

235 _FINISH: ClassVar[str] = "Phase 1.2 Setup Constraints And Sort Netlist | Checksum:" 

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

237 

238 

239@export 

240class Phase1_Initialization(Phase): 

241 _START: ClassVar[str] = "Phase 1 Initialization" 

242 _FINISH: ClassVar[str] = "Phase 1 Initialization | Checksum:" 

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

244 _FINAL: ClassVar[str] = None 

245 

246 _PARSERS: ClassVar[Tuple[Type[Phase], ...]] = ( 

247 Phase11_CoreGenerationAndDesignSetup, 

248 Phase12_SetupConstraintsAndSortNetlist 

249 ) 

250 

251 _subphases: Dict[Type[SubPhase], SubPhase] 

252 

253 def __init__(self, phase: Phase): 

254 super().__init__(phase) 

255 

256 self._subphases = {p: p(self) for p in self._PARSERS} 

257 

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

259 line = yield from self._PhaseStart(line) 

260 

261 activeParsers: List[Phase] = list(self._subphases.values()) 

262 

263 while True: 

264 while True: 

265 if line._kind is LineKind.Empty: 

266 line = yield line 

267 continue 

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

269 self._AddMessage(line) 

270 elif line.StartsWith("Phase 1."): 

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

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

273 line = yield next(phase := parser.Generator(line)) 

274 break 

275 else: 

276 raise Exception(f"Unknown subphase: {line!r}") 

277 break 

278 elif line.StartsWith(self._FINISH): 278 ↛ 282line 278 didn't jump to line 282 because the condition on line 278 was always true

279 nextLine = yield from self._PhaseFinish(line) 

280 return nextLine 

281 

282 line = yield line 

283 

284 while phase is not None: 284 ↛ 263line 284 didn't jump to line 263 because the condition on line 284 was always true

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

286 # line = yield task.send(line) 

287 # break 

288 

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

290 self._AddMessage(line) 

291 

292 try: 

293 line = yield phase.send(line) 

294 except StopIteration as ex: 

295 activeParsers.remove(parser) 

296 line = ex.value 

297 break 

298 

299 

300@export 

301class Phase21_TimerUpdate(SubPhase): 

302 _START: ClassVar[str] = "Phase 2.1 Timer Update" 

303 _FINISH: ClassVar[str] = "Phase 2.1 Timer Update | Checksum:" 

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

305 

306 

307@export 

308class Phase22_TimingDataCollection(SubPhase): 

309 _START: ClassVar[str] = "Phase 2.2 Timing Data Collection" 

310 _FINISH: ClassVar[str] = "Phase 2.2 Timing Data Collection | Checksum:" 

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

312 

313 

314@export 

315class Phase2_TimerUpdateAndTimingDataCollection(Phase): 

316 _START: ClassVar[str] = "Phase 2 Timer Update And Timing Data Collection" 

317 _FINISH: ClassVar[str] = "Phase 2 Timer Update And Timing Data Collection | Checksum:" 

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

319 _FINAL: ClassVar[str] = None 

320 

321 _PARSERS: ClassVar[Tuple[Type[Phase], ...]] = ( 

322 Phase21_TimerUpdate, 

323 Phase22_TimingDataCollection 

324 ) 

325 

326 _subphases: Dict[Type[SubPhase], SubPhase] 

327 

328 def __init__(self, phase: Phase): 

329 super().__init__(phase) 

330 

331 self._subphases = {p: p(self) for p in self._PARSERS} 

332 

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

334 line = yield from self._PhaseStart(line) 

335 

336 activeParsers: List[Phase] = list(self._subphases.values()) 

337 

338 while True: 

339 while True: 

340 if line._kind is LineKind.Empty: 

341 line = yield line 

342 continue 

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

344 self._AddMessage(line) 

345 elif line.StartsWith("Phase 2."): 

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

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

348 line = yield next(phase := parser.Generator(line)) 

349 break 

350 else: 

351 raise Exception(f"Unknown subphase: {line!r}") 

352 break 

353 elif line.StartsWith(self._FINISH): 353 ↛ 357line 353 didn't jump to line 357 because the condition on line 353 was always true

354 nextLine = yield from self._PhaseFinish(line) 

355 return nextLine 

356 

357 line = yield line 

358 

359 while phase is not None: 359 ↛ 338line 359 didn't jump to line 338 because the condition on line 359 was always true

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

361 # line = yield task.send(line) 

362 # break 

363 

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

365 self._AddMessage(line) 

366 

367 try: 

368 line = yield phase.send(line) 

369 except StopIteration as ex: 

370 activeParsers.remove(parser) 

371 line = ex.value 

372 break 

373 

374 

375@export 

376class Phase3_Retarget(Phase): 

377 _START: ClassVar[str] = "Phase 3 Retarget" 

378 _FINISH: ClassVar[str] = "Phase 3 Retarget | Checksum:" 

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

380 _FINAL: ClassVar[str] = "Retarget | Checksum:" 

381 

382 

383@export 

384class Phase4_ConstantPropagation(Phase): 

385 _START: ClassVar[str] = "Phase 4 Constant propagation" 

386 _FINISH: ClassVar[str] = "Phase 4 Constant propagation | Checksum:" 

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

388 _FINAL: ClassVar[str] = "Constant propagation | Checksum:" 

389 

390 

391@export 

392class Phase5_Sweep(Phase): 

393 _START: ClassVar[str] = "Phase 5 Sweep" 

394 _FINISH: ClassVar[str] = "Phase 5 Sweep | Checksum:" 

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

396 _FINAL: ClassVar[str] = "Sweep | Checksum:" 

397 

398 

399@export 

400class Phase6_BUFGOptimization(Phase): 

401 _START: ClassVar[str] = "Phase 6 BUFG optimization" 

402 _FINISH: ClassVar[str] = "Phase 6 BUFG optimization | Checksum:" 

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

404 _FINAL: ClassVar[str] = "BUFG optimization | Checksum:" 

405 

406 

407@export 

408class Phase7_ShiftRegisterOptimization(Phase): 

409 _START: ClassVar[str] = "Phase 7 Shift Register Optimization" 

410 _FINISH: ClassVar[str] = "Phase 7 Shift Register Optimization | Checksum:" 

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

412 _FINAL: ClassVar[str] = "Shift Register Optimization | Checksum:" 

413 

414 

415@export 

416class Phase8_PostProcessingNetlist(Phase): 

417 _START: ClassVar[str] = "Phase 8 Post Processing Netlist" 

418 _FINISH: ClassVar[str] = "Phase 8 Post Processing Netlist | Checksum:" 

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

420 _FINAL: ClassVar[str] = "Post Processing Netlist | Checksum:" 

421 

422 

423@export 

424class Phase91_FinalizingDesignCoresAndUpdatingShapes(SubPhase): 

425 _START: ClassVar[str] = "Phase 9.1 Finalizing Design Cores and Updating Shapes" 

426 _FINISH: ClassVar[str] = "Phase 9.1 Finalizing Design Cores and Updating Shapes | Checksum:" 

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

428 

429 

430@export 

431class Phase92_VerifyingNetlistConnectivity(SubPhase): 

432 _START: ClassVar[str] = "Phase 9.2 Verifying Netlist Connectivity" 

433 _FINISH: ClassVar[str] = "Phase 9.2 Verifying Netlist Connectivity | Checksum:" 

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

435 

436 

437@export 

438class Phase9_Finalization(Phase): 

439 _START: ClassVar[str] = "Phase 9 Finalization" 

440 _FINISH: ClassVar[str] = "Phase 9 Finalization | Checksum:" 

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

442 _FINAL: ClassVar[str] = None 

443 

444 _PARSERS: ClassVar[Tuple[Type[Phase], ...]] = ( 

445 Phase91_FinalizingDesignCoresAndUpdatingShapes, 

446 Phase92_VerifyingNetlistConnectivity 

447 ) 

448 

449 _subphases: Dict[Type[SubPhase], SubPhase] 

450 

451 def __init__(self, phase: Phase): 

452 super().__init__(phase) 

453 

454 self._subphases = {p: p(self) for p in self._PARSERS} 

455 

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

457 line = yield from self._PhaseStart(line) 

458 

459 activeParsers: List[Phase] = list(self._subphases.values()) 

460 

461 while True: 

462 while True: 

463 if line._kind is LineKind.Empty: 

464 line = yield line 

465 continue 

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

467 self._AddMessage(line) 

468 elif line.StartsWith("Phase 9."): 

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

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

471 line = yield next(phase := parser.Generator(line)) 

472 break 

473 else: 

474 raise Exception(f"Unknown subphase: {line!r}") 

475 break 

476 elif line.StartsWith(self._FINISH): 476 ↛ 480line 476 didn't jump to line 480 because the condition on line 476 was always true

477 nextLine = yield from self._PhaseFinish(line) 

478 return nextLine 

479 

480 line = yield line 

481 

482 while phase is not None: 482 ↛ 461line 482 didn't jump to line 461 because the condition on line 482 was always true

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

484 # line = yield task.send(line) 

485 # break 

486 

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

488 self._AddMessage(line) 

489 

490 try: 

491 line = yield phase.send(line) 

492 except StopIteration as ex: 

493 activeParsers.remove(parser) 

494 line = ex.value 

495 break 

496 

497 

498@export 

499class DRCTask(Task): 

500 _START: ClassVar[str] = "Starting DRC Task" 

501 _FINISH: ClassVar[str] = "Time (s):" 

502 

503 

504@export 

505class CacheTimingInformationTask(Task): 

506 _START: ClassVar[str] = "Starting Cache Timing Information Task" 

507 _FINISH: ClassVar[str] = "Ending Cache Timing Information Task" 

508 

509 

510@export 

511class LogicOptimizationTask(Task): 

512 _START: ClassVar[str] = "Starting Logic Optimization Task" 

513 _FINISH: ClassVar[str] = "Ending Logic Optimization Task" 

514 

515 _PARSERS: ClassVar[Tuple[Type[Phase], ...]] = ( 

516 Phase1_Initialization, 

517 Phase2_TimerUpdateAndTimingDataCollection, 

518 Phase3_Retarget, 

519 Phase4_ConstantPropagation, 

520 Phase5_Sweep, 

521 Phase6_BUFGOptimization, 

522 Phase7_ShiftRegisterOptimization, 

523 Phase8_PostProcessingNetlist, 

524 Phase9_Finalization 

525 ) 

526 

527 _phases: Dict[Type[Phase], Phase] 

528 

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

530 super().__init__(command) 

531 

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

533 

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

535 line = yield from self._TaskStart(line) 

536 

537 activeParsers: List[Phase] = list(self._phases.values()) 

538 

539 while True: 

540 while True: 

541 if line._kind is LineKind.Empty: 

542 line = yield line 

543 continue 

544 elif isinstance(line, VivadoMessage): 

545 self._AddMessage(line) 

546 elif line.StartsWith("Phase "): 

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

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

549 line = yield next(phase := parser.Generator(line)) 

550 break 

551 else: 

552 raise Exception(f"Unknown phase: {line!r}") 

553 break 

554 elif line.StartsWith("Ending"): 

555 nextLine = yield from self._TaskFinish(line) 

556 return nextLine 

557 elif line.StartsWith(self._TIME): 557 ↛ 558line 557 didn't jump to line 558 because the condition on line 557 was never true

558 line._kind = LineKind.TaskTime 

559 nextLine = yield line 

560 return nextLine 

561 

562 line = yield line 

563 

564 while phase is not None: 564 ↛ 539line 564 didn't jump to line 539 because the condition on line 564 was always true

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

566 # line = yield task.send(line) 

567 # break 

568 

569 if isinstance(line, VivadoMessage): 

570 self._AddMessage(line) 

571 

572 try: 

573 line = yield phase.send(line) 

574 except StopIteration as ex: 

575 activeParsers.remove(parser) 

576 line = ex.value 

577 break 

578 

579# @export 

580# class ConnectivityCheckTask(Task): 

581# pass 

582 

583 

584@export 

585class PowerOptimizationTask(Task): 

586 _START: ClassVar[str] = "Starting Power Optimization Task" 

587 _FINISH: ClassVar[str] = "Ending Power Optimization Task" 

588 

589 

590@export 

591class FinalCleanupTask(Task): 

592 _START: ClassVar[str] = "Starting Final Cleanup Task" 

593 _FINISH: ClassVar[str] = "Ending Final Cleanup Task" 

594 

595 

596@export 

597class NetlistObfuscationTask(Task): 

598 _START: ClassVar[str] = "Starting Netlist Obfuscation Task" 

599 _FINISH: ClassVar[str] = "Ending Netlist Obfuscation Task"