Coverage for pyEDAA / CLITool / GHDL.py: 87%

247 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-23 22:25 +0000

1# ==================================================================================================================== # 

2# _____ ____ _ _ ____ _ ___ _____ _ # 

3# _ __ _ _| ____| _ \ / \ / \ / ___| | |_ _|_ _|__ ___ | | # 

4# | '_ \| | | | _| | | | |/ _ \ / _ \ | | | | | | | |/ _ \ / _ \| | # 

5# | |_) | |_| | |___| |_| / ___ \ / ___ \ | |___| |___ | | | | (_) | (_) | | # 

6# | .__/ \__, |_____|____/_/ \_\/_/ \_(_)____|_____|___| |_|\___/ \___/|_| # 

7# |_| |___/ # 

8# ==================================================================================================================== # 

9# Authors: # 

10# Patrick Lehmann # 

11# # 

12# License: # 

13# ==================================================================================================================== # 

14# Copyright 2017-2026 Patrick Lehmann - Boetzingen, Germany # 

15# Copyright 2014-2016 Technische Universität Dresden - Germany, Chair of VLSI-Design, Diagnostics and Architecture # 

16# # 

17# Licensed under the Apache License, Version 2.0 (the "License"); # 

18# you may not use this file except in compliance with the License. # 

19# You may obtain a copy of the License at # 

20# # 

21# http://www.apache.org/licenses/LICENSE-2.0 # 

22# # 

23# Unless required by applicable law or agreed to in writing, software # 

24# distributed under the License is distributed on an "AS IS" BASIS, # 

25# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 

26# See the License for the specific language governing permissions and # 

27# limitations under the License. # 

28# # 

29# SPDX-License-Identifier: Apache-2.0 # 

30# ==================================================================================================================== # 

31# 

32"""This module contains the CLI abstraction layer for `GHDL <https://github.com/ghdl/ghdl>`__.""" 

33from re import search as re_search 

34from typing import Union, Iterable, Tuple, Optional as Nullable 

35 

36from pyTooling.Decorators import export 

37from pyTooling.MetaClasses import ExtendedType 

38from pyVHDLModel import VHDLVersion 

39 

40from pyTooling.CLIAbstraction import CLIArgument, Executable 

41from pyTooling.CLIAbstraction.Argument import PathListArgument, StringArgument 

42from pyTooling.CLIAbstraction.Command import CommandArgument 

43from pyTooling.CLIAbstraction.Flag import ShortFlag, LongFlag 

44from pyTooling.CLIAbstraction.BooleanFlag import LongBooleanFlag 

45from pyTooling.CLIAbstraction.ValuedFlag import ShortValuedFlag, LongValuedFlag 

46from pyTooling.CLIAbstraction.KeyValueFlag import ShortKeyValueFlag 

47 

48from pyEDAA.CLITool import CLIToolException 

49 

50 

51@export 

52class GHDLVersion(metaclass=ExtendedType, slots=True): 

53 """ 

54 

55 .. code-block:: 

56 

57 GHDL 6.0.0-dev (4.1.0.r1065.gd3ea86f11.dirty) [Dunoon edition] 

58 Compiled with GNAT Version: 15.2.0 

59 static elaboration, mcode JIT code generator 

60 Written by Tristan Gingold. 

61 

62 Copyright (C) 2003 - 2025 Tristan Gingold. 

63 GHDL is free software, covered by the GNU General Public License. There is NO 

64 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 

65 """ 

66 _major: int 

67 _minor: int 

68 _micro: int 

69 _dev: bool 

70 _commitsSinceLastTag: int 

71 _gitHash: str 

72 _dirty: bool 

73 _edition: str 

74 _gnatCompiler: Tuple[int, int, int] 

75 _backend: str 

76 

77 VERSION_LINE_PATTERN = ( 

78 r"GHDL" 

79 r"\s(?P<Major>\d+)\.(?P<Minor>\d+)\.(?P<Micro>\d+)(?:-(?P<Suffix>dev|rc\d+))?" 

80 r"\s\((?:" 

81 r"(?:" 

82 r"(?P<Packaging>tarball)" 

83 r")|(?:" 

84 r"[a-z]?(?P<major2>\d+)\.(?P<minor2>\d+)\.(?P<micro2>\d+)\.(?:r(?P<cslt>\d+))\.(?:g(?P<Hash>[0-9a-f]+))(?:\.(?P<Dirty>dirty))?" 

85 r")|(?:" 

86 r"Ubuntu\s(?P<UbuntuMajor>\d+)\.(?P<UbuntuMinor>\d+)\.(?P<UbuntuMicro>\d+)\+dfsg-(?P<dfsg>\d+)ubuntu(?P<UbuntuPackage>\d+)" 

87 r")" 

88 r")\)" 

89 r"\s\[(?P<Edition>[\w\s]+)\]" 

90 ) 

91 GNAT_LINE_PATTERN = ( 

92 r"\s*[\w\s]+:\s" 

93 r"(?:" 

94 r"(?:(?P<Major>\d+)\.(?P<Minor>\d+)\.(?P<Micro>\d+))" 

95 r"|" 

96 r"(?:Community\s(?P<Year>\d{4})\s\((?P<DateCode>\d{8}-\d{2})\))" 

97 r")" 

98 ) 

99 BACKEND_LINE_PATTERN = ( 

100 r"\s*" 

101 r"(?:static elaboration, )?" 

102 r"(?P<Backend>llvm|mcode|gcc)" 

103 r"(?: (?P<LLVMMajor>\d+)\.(?P<LLVMMinor>\d+)\.(?P<LLVMMicro>\d+))?" 

104 r"(?: JIT)?" 

105 r" code generator" 

106 ) 

107 

108 def __init__(self, versionLine: str, gnatLine: str, backendLine: str) -> None: 

109 match = re_search("^" + self.VERSION_LINE_PATTERN + "$", versionLine) 

110 if match is None: 110 ↛ 111line 110 didn't jump to line 111 because the condition on line 110 was never true

111 raise CLIToolException(f"Unknown first GHDL version string '{versionLine}'.") 

112 

113 self._major = int(match["Major"]) 

114 self._minor = int(match["Minor"]) 

115 self._micro = int(match["Micro"]) 

116 if (suffix := match["Suffix"]) is not None: 116 ↛ 119line 116 didn't jump to line 119 because the condition on line 116 was always true

117 self._dev = suffix == "dev" 

118 else: 

119 self._dev = False 

120 if (cslt := match["cslt"]) is not None: 120 ↛ 123line 120 didn't jump to line 123 because the condition on line 120 was always true

121 self._commitsSinceLastTag = int(cslt) 

122 else: 

123 self._commitsSinceLastTag = 0 

124 self._gitHash = match["Hash"] 

125 self._dirty = "Dirty" in match.groups() 

126 self._edition = match["Edition"] 

127 

128 match = re_search("^" + self.GNAT_LINE_PATTERN + "$", gnatLine) 

129 if match is None: 129 ↛ 130line 129 didn't jump to line 130 because the condition on line 129 was never true

130 raise CLIToolException(f"Unknown second GHDL version string '{gnatLine}'.") 

131 

132 if match["Year"] is None: 132 ↛ 135line 132 didn't jump to line 135 because the condition on line 132 was always true

133 self._gnatCompiler = (int(match["Major"]), int(match["Minor"]), int(match["Micro"])) 

134 else: 

135 self._gnatCompiler = (int(match["Year"]), 0, 0) 

136 

137 match = re_search("^" + self.BACKEND_LINE_PATTERN + "$", backendLine) 

138 if match is None: 138 ↛ 139line 138 didn't jump to line 139 because the condition on line 138 was never true

139 raise CLIToolException(f"Unknown third GHDL version string '{backendLine}'.") 

140 

141 self._backend = match["Backend"] 

142 

143 @property 

144 def Major(self) -> int: 

145 return self._major 

146 

147 @property 

148 def Minor(self) -> int: 

149 return self._minor 

150 

151 @property 

152 def Micro(self) -> int: 

153 return self._micro 

154 

155 @property 

156 def Dev(self) -> bool: 

157 return self._dev 

158 

159 @property 

160 def CommitsSinceLastTag(self) -> int: 

161 return self._commitsSinceLastTag 

162 

163 @property 

164 def GitHash(self) -> str: 

165 return self._gitHash 

166 

167 @property 

168 def Dirty(self) -> bool: 

169 return self._dirty 

170 

171 @property 

172 def Edition(self) -> str: 

173 return self._edition 

174 

175 @property 

176 def Backend(self) -> str: 

177 return self._backend 

178 

179 def __str__(self) -> str: 

180 dev = f"-dev" if self._dev else "" 

181 return f"{self._major}.{self._minor}.{self._micro}{dev}" 

182 

183 def __repr__(self) -> str: 

184 return f"{self.__str__()} (Backend: {self._backend}; Git: {self._gitHash})" 

185 

186 

187@export 

188class GHDL(Executable): 

189 _executableNames = { 

190 "Darwin": "ghdl", 

191 "FreeBSD": "ghdl", 

192 "Linux": "ghdl", 

193 "Windows": "ghdl.exe" 

194 } 

195 

196 # XXX: overwrite __init__ and get backend variant 

197 # XXX: check for compatible backends 

198 

199 @CLIArgument() 

200 class CommandHelp(CommandArgument, name="help"): 

201 """Print help page(s).""" 

202 

203 @CLIArgument() 

204 class CommandVersion(CommandArgument, name="version"): 

205 """Print version information.""" 

206 

207 @CLIArgument() 

208 class CommandSyntax(CommandArgument, name="syntax"): 

209 """Check syntax.""" 

210 

211 @CLIArgument() 

212 class CommandElaborationOrder(CommandArgument, name="elab-order"): 

213 """Display (elaboration) ordered source files.""" 

214 

215 @CLIArgument() 

216 class CommandAnalyze(CommandArgument, name="analyze"): 

217 """Analyze VHDL source file(s).""" 

218 

219 @CLIArgument() 

220 class CommandElaborate(CommandArgument, name="elaborate"): 

221 """Elaborate design.""" 

222 

223 @CLIArgument() 

224 class CommandElaborationAndRun(CommandArgument, name="elab-run"): 

225 """Elaborate and simulate design.""" 

226 

227 @CLIArgument() 

228 class CommandRun(CommandArgument, name="run"): 

229 """Simulate design.""" 

230 

231 @CLIArgument() 

232 class CommandBind(CommandArgument, name="bind"): 

233 """Bind design unit.""" 

234 

235 @CLIArgument() 

236 class CommandLink(CommandArgument, name="link"): 

237 """Link design unit.""" 

238 

239 @CLIArgument() 

240 class CommandListLink(CommandArgument, name="list-link"): 

241 """List objects file to link a design unit.""" 

242 

243 @CLIArgument() 

244 class CommandCompile(CommandArgument, name="compile"): 

245 """Generate whole sequence to elaborate design from files.""" 

246 

247 @CLIArgument() 

248 class CommandGenerateDependencies(CommandArgument, name="gen-depends"): 

249 """Generate dependencies of design.""" 

250 

251 @CLIArgument() 

252 class CommandSynthesize(CommandArgument, name="synth"): 

253 """Synthesis from design unit.""" 

254 

255 @CLIArgument() 

256 class FlagVerbose(ShortFlag, name="v"): 

257 """Run in verbose mode (print more messages).""" 

258 

259 # Analyze and elaborate options 

260 @CLIArgument() 

261 class FlagVHDLStandard(LongValuedFlag, name="std"): 

262 """Set the used VHDL standard version.""" 

263 _value: VHDLVersion 

264 

265 def __init__(self, value: VHDLVersion): 

266 if value is None: 266 ↛ 267line 266 didn't jump to line 267 because the condition on line 266 was never true

267 raise ValueError(f"") # FIXME: add message 

268 

269 self._value = value 

270 

271 @property 

272 def Value(self) -> VHDLVersion: 

273 return self._value 

274 

275 @Value.setter 

276 def Value(self, value: VHDLVersion) -> None: 

277 if value is None: 

278 raise ValueError(f"") # FIXME: add message 

279 

280 self._value = value 

281 

282 def AsArgument(self) -> Union[str, Iterable[str]]: 

283 if self._name is None: 283 ↛ 284line 283 didn't jump to line 284 because the condition on line 283 was never true

284 raise ValueError(f"") # FIXME: add message 

285 

286 return self._pattern.format(self._name, str(self._value)[-2:]) 

287 

288 @CLIArgument() 

289 class FlagIEEEFlavor(LongValuedFlag, name="ieee"): 

290 """Set the used VHDL flavor.""" 

291 

292 @CLIArgument() 

293 class FlagSynopsys(ShortFlag, name="fsynopsys"): 

294 """Set used VHDL flavor to *Synopsys* and make Synopsys packages visible in library ``ìeee``.""" 

295 

296 @CLIArgument() 

297 class FlagRelaxed(ShortFlag, name="frelaxed"): 

298 """Relax some LRM rules.""" 

299 

300 @CLIArgument() 

301 class FlagExplicit(ShortFlag, name="fexplicit"): ... 

302 

303 @CLIArgument() 

304 class FlagLibrary(LongValuedFlag, name="work"): 

305 """Set working library.""" 

306 

307 @CLIArgument() 

308 class FlagWorkingDirectory(LongValuedFlag, name="workdir"): 

309 """Set working directory.""" 

310 

311 @CLIArgument() 

312 class FlagMultiByteComments(LongFlag, name="mb-comments"): 

313 """Allow multi-byte comments.""" 

314 

315 @CLIArgument() 

316 class FlagSyntesisBindingRule(LongFlag, name="syn-binding"): 

317 """Enable synthesis binding rule.""" 

318 

319 @CLIArgument() 

320 class FlagSearchPath(ShortValuedFlag, name="P", pattern="-{0}{1}"): 

321 """Add search path.""" 

322 

323 @CLIArgument() 

324 class FlagTimeResolution(LongValuedFlag, name="time-resolution"): 

325 """Set base time resolution. 

326 

327 Allowed values are ``auto`` (default), ``fs``, ``ps``, ``ns``, ``us``, ``ms`` or ``sec``. 

328 """ 

329 

330 @CLIArgument() 

331 class FlagVitalChecks(LongBooleanFlag, name="vital-checks", pattern="-{0}", falsePattern="--no-{0}"): 

332 """Check VITAL restrictions.""" 

333 

334 @CLIArgument() 

335 class FlagWarnUnboundComponents(ShortFlag, name="binding", pattern="-W{0}"): 

336 """Warns for unbound components.""" 

337 

338 @CLIArgument() 

339 class FlagWarnReservedWords(ShortFlag, name="reserved", pattern="-W{0}"): 

340 """Warns if VHDL'93 reserved words are used in VHDL'87.""" 

341 

342 @CLIArgument() 

343 class FlagWarnRedefinedDesignUnits(ShortFlag, name="library", pattern="-W{0}"): 

344 """Warns for redefined design unit.""" 

345 

346 @CLIArgument() 

347 class FlagWarnNonVitalGenericNames(ShortFlag, name="vital-generic", pattern="-W{0}"): 

348 """Warns of non-vital generic names.""" 

349 

350 @CLIArgument() 

351 class FlagWarnElaborationChecks(ShortFlag, name="delayed-checks", pattern="-W{0}"): 

352 """Warns for checks performed at elaboration.""" 

353 

354 @CLIArgument() 

355 class FlagWarnUnnecessaryPackageBody(ShortFlag, name="body", pattern="-W{0}"): 

356 """Warns for unnecessary package body.""" 

357 

358 @CLIArgument() 

359 class FlagWarnOthersSpecifications(ShortFlag, name="specs", pattern="-W{0}"): 

360 """Warns if an all/others specification does not apply.""" 

361 

362 @CLIArgument() 

363 class FlagUnused(ShortFlag, name="unused", pattern="-W{0}"): 

364 """Warns for unused subprograms.""" 

365 

366 @CLIArgument() 

367 class FlagError(ShortFlag, name="error", pattern="-W{0}"): 

368 """Turns warnings into errors.""" 

369 

370 @CLIArgument() 

371 class OptionPaths(PathListArgument): 

372 """Add list of VHDL files to analyze.""" 

373 

374 @CLIArgument() 

375 class OptionTopLevel(StringArgument): 

376 """Specify the toplevel design unit.""" 

377 

378 @CLIArgument() 

379 class OptionArchitecture(StringArgument): 

380 """Specify the architecture name, if the toplevel design unit is an entity.""" 

381 

382 @CLIArgument() 

383 class FlagGenerics(ShortKeyValueFlag, pattern="-{0}{1}={2}"): 

384 """Set a generic value.""" 

385 

386 @CLIArgument() 

387 class FlagAsserts(ShortValuedFlag, name="asserts"): 

388 """Select how assertions are handled. 

389 

390 It can be ``enable`` (the default), ``disable`` which disables all assertions and ``disable-at-0`` which disables 

391 only at the start of simulation. 

392 """ 

393 

394 @CLIArgument() 

395 class FlagIEEEAsserts(ShortValuedFlag, name="ieee-asserts"): 

396 """Select how assertions are handled. 

397 

398 It can be ``enable`` (the default), ``disable`` which disables all assertions and ``disable-at-0`` which disables 

399 only at the start of simulation. 

400 """ 

401 

402 @CLIArgument() 

403 class FlagStopTime(ShortValuedFlag, name="stop-time"): 

404 """Stop the simulation after a given simulation time. 

405 

406 The time is expressed as a time value, without any spaces. The time is the simulation time, not the real execution time. 

407 """ 

408 

409 @CLIArgument() 

410 class FlagMaxDeltaCycles(ShortValuedFlag, name="stop-delta"): 

411 """Stop the simulation after N delta cycles in the same current time.""" 

412 

413 @CLIArgument() 

414 class FlagDisplayDeltaCycles(ShortValuedFlag, name="disp-time"): 

415 """Display the time and delta cycle number as simulation advances.""" 

416 

417 @CLIArgument() 

418 class FlagUnbufferedIO(ShortValuedFlag, name="unbuffered"): 

419 """Disable buffering on STDOUT, STDERR and files opened in write or append mode (TEXTIO).""" 

420 

421 @CLIArgument() 

422 class FlagReadWaveformOptionsFile(ShortValuedFlag, name="read-wave-opt"): 

423 """Filter signals to be dumped to the waveform file according to the wavefile option file provided.""" 

424 

425 @CLIArgument() 

426 class FlagWriteWaveformOptionsFile(ShortValuedFlag, name="write-wave-opt"): 

427 """If the wavefile option file doesn’t exist, creates it with all the signals of the design. 

428 Otherwise, it throws an error, because it won’t erase an existing file. 

429 """ 

430 

431 @CLIArgument() 

432 class FlagGHWWaveformFile(ShortValuedFlag, name="wave"): 

433 """Write the waveforms into a GHDL Waveform (``*.ghw``) file. 

434 

435 Contrary to VCD files, any VHDL type can be dumped into a GHW file. 

436 """ 

437 

438 def _CopyParameters(self, tool: "GHDL") -> None: 

439 for key in self.__cliParameters__: 

440 if self._NeedsParameterInitialization(key): 

441 value = self.__cliParameters__[key].Value 

442 tool.__cliParameters__[key] = key(value) 

443 else: 

444 tool.__cliParameters__[key] = key() 

445 

446 def _SetParameters(self, tool: "GHDL", std: Nullable[VHDLVersion] = None, ieee: Nullable[str] = None): 

447 if std is not None: 447 ↛ 448line 447 didn't jump to line 448 because the condition on line 447 was never true

448 tool[self.FlagVHDLStandard] = str(std) 

449 

450 if ieee is not None: 450 ↛ 451line 450 didn't jump to line 451 because the condition on line 450 was never true

451 tool[self.FlagVHDLStandard] = ieee 

452 

453 def GetGHDLAsAnalyzer(self, std: Nullable[VHDLVersion] = None, ieee: Nullable[str] = None) -> "GHDL": 

454 tool = GHDL(executablePath=self._executablePath) 

455 

456 tool[tool.CommandAnalyze] = True 

457 self._CopyParameters(tool) 

458 self._SetParameters(tool, std, ieee) 

459 

460 return tool 

461 

462 def GetGHDLAsElaborator(self, std: Nullable[VHDLVersion] = None, ieee: Nullable[str] = None) -> "GHDL": 

463 tool = GHDL(executablePath=self._executablePath) 

464 

465 tool[tool.CommandElaborate] = True 

466 self._CopyParameters(tool) 

467 self._SetParameters(tool, std, ieee) 

468 

469 return tool 

470 

471 def GetGHDLAsSimulator(self, std: Nullable[VHDLVersion] = None, ieee: Nullable[str] = None) -> "GHDL": 

472 tool = GHDL(executablePath=self._executablePath) 

473 

474 tool[tool.CommandRun] = True 

475 self._CopyParameters(tool) 

476 self._SetParameters(tool, std, ieee) 

477 

478 return tool 

479 

480 def Help(self) -> str: 

481 tool = GHDL(executablePath=self._executablePath) 

482 

483 tool[tool.CommandHelp] = True 

484 

485 tool.StartProcess() 

486 return "\n".join(tool.GetLineReader()) 

487 

488 def Version(self) -> GHDLVersion: 

489 tool = GHDL(executablePath=self._executablePath) 

490 

491 tool[tool.CommandVersion] = True 

492 

493 tool.StartProcess() 

494 iterator = iter(tool.GetLineReader()) 

495 firstLine = next(iterator) 

496 secondLine = next(iterator) 

497 thirdLine = next(iterator) 

498 

499 return GHDLVersion(firstLine, secondLine, thirdLine)