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

244 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-05-16 22:22 +0000

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

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

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

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

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

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

7# |_| |___/ # 

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

9# Authors: # 

10# Patrick Lehmann # 

11# # 

12# License: # 

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

14# Copyright 2017-2025 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 _major: int 

54 _minor: int 

55 _micro: int 

56 _dev: bool 

57 _commitsSinceLastTag: int 

58 _gitHash: str 

59 _dirty: bool 

60 _edition: str 

61 _gnatCompiler: Tuple[int, int, int] 

62 _backend: str 

63 

64 VERSION_LINE_PATTERN = ( 

65 r"GHDL" 

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

67 r"\s\((?:" 

68 r"(?:" 

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

70 r")|(?:" 

71 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))?" 

72 r")|(?:" 

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

74 r")" 

75 r")\)" 

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

77 ) 

78 GNAT_LINE_PATTERN = ( 

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

80 r"(?:" 

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

82 r"|" 

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

84 r")" 

85 ) 

86 BACKEND_LINE_PATTERN = ( 

87 r"\s*" 

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

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

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

91 r"(?: JIT)?" 

92 r" code generator" 

93 ) 

94 

95 def __init__(self, versionLine: str, gnatLine: str, backendLine: str): 

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

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

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

99 

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

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

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

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

104 self._dev = suffix == "dev" 

105 else: 

106 self._dev = False 

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

108 self._commitsSinceLastTag = int(cslt) 

109 else: 

110 self._commitsSinceLastTag = 0 

111 self._gitHash = match["Hash"] 

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

113 self._edition = match["Edition"] 

114 

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

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

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

118 

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

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

121 else: 

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

123 

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

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

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

127 

128 self._backend = match["Backend"] 

129 

130 @property 

131 def Major(self) -> int: 

132 return self._major 

133 

134 @property 

135 def Minor(self) -> int: 

136 return self._minor 

137 

138 @property 

139 def Micro(self) -> int: 

140 return self._micro 

141 

142 @property 

143 def Dev(self) -> bool: 

144 return self._dev 

145 

146 @property 

147 def CommitsSinceLastTag(self) -> int: 

148 return self._commitsSinceLastTag 

149 

150 @property 

151 def GitHash(self) -> str: 

152 return self._gitHash 

153 

154 @property 

155 def Dirty(self) -> bool: 

156 return self._dirty 

157 

158 @property 

159 def Edition(self) -> str: 

160 return self._edition 

161 

162 def __str__(self) -> str: 

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

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

165 

166 def __repr__(self) -> str: 

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

168 

169 

170@export 

171class GHDL(Executable): 

172 _executableNames = { 

173 "Darwin": "ghdl", 

174 "FreeBSD": "ghdl", 

175 "Linux": "ghdl", 

176 "Windows": "ghdl.exe" 

177 } 

178 

179 # XXX: overwrite __init__ and get backend variant 

180 # XXX: check for compatible backends 

181 

182 @CLIArgument() 

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

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

185 

186 @CLIArgument() 

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

188 """Print version information.""" 

189 

190 @CLIArgument() 

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

192 """Check syntax.""" 

193 

194 @CLIArgument() 

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

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

197 

198 @CLIArgument() 

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

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

201 

202 @CLIArgument() 

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

204 """Elaborate design.""" 

205 

206 @CLIArgument() 

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

208 """Elaborate and simulate design.""" 

209 

210 @CLIArgument() 

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

212 """Simulate design.""" 

213 

214 @CLIArgument() 

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

216 """Bind design unit.""" 

217 

218 @CLIArgument() 

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

220 """Link design unit.""" 

221 

222 @CLIArgument() 

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

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

225 

226 @CLIArgument() 

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

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

229 

230 @CLIArgument() 

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

232 """Generate dependencies of design.""" 

233 

234 @CLIArgument() 

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

236 """Synthesis from design unit.""" 

237 

238 @CLIArgument() 

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

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

241 

242 # Analyze and elaborate options 

243 @CLIArgument() 

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

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

246 _value: VHDLVersion 

247 

248 def __init__(self, value: VHDLVersion): 

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

250 raise ValueError(f"") # XXX: add message 

251 

252 self._value = value 

253 

254 @property 

255 def Value(self) -> VHDLVersion: 

256 return self._value 

257 

258 @Value.setter 

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

260 if value is None: 

261 raise ValueError(f"") # XXX: add message 

262 

263 self._value = value 

264 

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

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

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

268 

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

270 

271 @CLIArgument() 

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

273 """Set the used VHDL flavor.""" 

274 

275 @CLIArgument() 

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

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

278 

279 @CLIArgument() 

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

281 """Relax some LRM rules.""" 

282 

283 @CLIArgument() 

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

285 

286 @CLIArgument() 

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

288 """Set working library.""" 

289 

290 @CLIArgument() 

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

292 """Set working directory.""" 

293 

294 @CLIArgument() 

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

296 """Allow multi-byte comments.""" 

297 

298 @CLIArgument() 

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

300 """Enable synthesis binding rule.""" 

301 

302 @CLIArgument() 

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

304 """Add search path.""" 

305 

306 @CLIArgument() 

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

308 """Set base time resolution. 

309 

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

311 """ 

312 

313 @CLIArgument() 

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

315 """Check VITAL restrictions.""" 

316 

317 @CLIArgument() 

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

319 """Warns for unbound components.""" 

320 

321 @CLIArgument() 

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

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

324 

325 @CLIArgument() 

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

327 """Warns for redefined design unit.""" 

328 

329 @CLIArgument() 

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

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

332 

333 @CLIArgument() 

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

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

336 

337 @CLIArgument() 

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

339 """Warns for unnecessary package body.""" 

340 

341 @CLIArgument() 

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

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

344 

345 @CLIArgument() 

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

347 """Warns for unused subprograms.""" 

348 

349 @CLIArgument() 

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

351 """Turns warnings into errors.""" 

352 

353 @CLIArgument() 

354 class OptionPaths(PathListArgument): 

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

356 

357 @CLIArgument() 

358 class OptionTopLevel(StringArgument): 

359 """Specify the toplevel design unit.""" 

360 

361 @CLIArgument() 

362 class OptionArchitecture(StringArgument): 

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

364 

365 @CLIArgument() 

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

367 """Set a generic value.""" 

368 

369 @CLIArgument() 

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

371 """Select how assertions are handled. 

372 

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

374 only at the start of simulation. 

375 """ 

376 

377 @CLIArgument() 

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

379 """Select how assertions are handled. 

380 

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

382 only at the start of simulation. 

383 """ 

384 

385 @CLIArgument() 

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

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

388 

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

390 """ 

391 

392 @CLIArgument() 

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

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

395 

396 @CLIArgument() 

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

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

399 

400 @CLIArgument() 

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

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

403 

404 @CLIArgument() 

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

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

407 

408 @CLIArgument() 

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

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

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

412 """ 

413 

414 @CLIArgument() 

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

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

417 

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

419 """ 

420 

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

422 for key in self.__cliParameters__: 

423 if self._NeedsParameterInitialization(key): 

424 value = self.__cliParameters__[key].Value 

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

426 else: 

427 tool.__cliParameters__[key] = key() 

428 

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

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

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

432 

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

434 tool[self.FlagVHDLStandard] = ieee 

435 

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

437 tool = GHDL(executablePath=self._executablePath) 

438 

439 tool[tool.CommandAnalyze] = True 

440 self._CopyParameters(tool) 

441 self._SetParameters(tool, std, ieee) 

442 

443 return tool 

444 

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

446 tool = GHDL(executablePath=self._executablePath) 

447 

448 tool[tool.CommandElaborate] = True 

449 self._CopyParameters(tool) 

450 self._SetParameters(tool, std, ieee) 

451 

452 return tool 

453 

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

455 tool = GHDL(executablePath=self._executablePath) 

456 

457 tool[tool.CommandRun] = True 

458 self._CopyParameters(tool) 

459 self._SetParameters(tool, std, ieee) 

460 

461 return tool 

462 

463 def Help(self) -> str: 

464 tool = GHDL(executablePath=self._executablePath) 

465 

466 tool[tool.CommandHelp] = True 

467 

468 tool.StartProcess() 

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

470 

471 def Version(self) -> GHDLVersion: 

472 tool = GHDL(executablePath=self._executablePath) 

473 

474 tool[tool.CommandVersion] = True 

475 

476 tool.StartProcess() 

477 iterator = iter(tool.GetLineReader()) 

478 firstLine = next(iterator) 

479 secondLine = next(iterator) 

480 thirdLine = next(iterator) 

481 

482 return GHDLVersion(firstLine, secondLine, thirdLine)