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
« 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
36from pyTooling.Decorators import export
37from pyTooling.MetaClasses import ExtendedType
38from pyVHDLModel import VHDLVersion
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
48from pyEDAA.CLITool import CLIToolException
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
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 )
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}'.")
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"]
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}'.")
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)
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}'.")
128 self._backend = match["Backend"]
130 @property
131 def Major(self) -> int:
132 return self._major
134 @property
135 def Minor(self) -> int:
136 return self._minor
138 @property
139 def Micro(self) -> int:
140 return self._micro
142 @property
143 def Dev(self) -> bool:
144 return self._dev
146 @property
147 def CommitsSinceLastTag(self) -> int:
148 return self._commitsSinceLastTag
150 @property
151 def GitHash(self) -> str:
152 return self._gitHash
154 @property
155 def Dirty(self) -> bool:
156 return self._dirty
158 @property
159 def Edition(self) -> str:
160 return self._edition
162 def __str__(self) -> str:
163 dev = f"-dev" if self._dev else ""
164 return f"{self._major}.{self._minor}.{self._micro}{dev}"
166 def __repr__(self) -> str:
167 return f"{self.__str__()} (Backend: {self._backend}; Git: {self._gitHash})"
170@export
171class GHDL(Executable):
172 _executableNames = {
173 "Darwin": "ghdl",
174 "FreeBSD": "ghdl",
175 "Linux": "ghdl",
176 "Windows": "ghdl.exe"
177 }
179 # XXX: overwrite __init__ and get backend variant
180 # XXX: check for compatible backends
182 @CLIArgument()
183 class CommandHelp(CommandArgument, name="help"):
184 """Print help page(s)."""
186 @CLIArgument()
187 class CommandVersion(CommandArgument, name="version"):
188 """Print version information."""
190 @CLIArgument()
191 class CommandSyntax(CommandArgument, name="syntax"):
192 """Check syntax."""
194 @CLIArgument()
195 class CommandElaborationOrder(CommandArgument, name="elab-order"):
196 """Display (elaboration) ordered source files."""
198 @CLIArgument()
199 class CommandAnalyze(CommandArgument, name="analyze"):
200 """Analyze VHDL source file(s)."""
202 @CLIArgument()
203 class CommandElaborate(CommandArgument, name="elaborate"):
204 """Elaborate design."""
206 @CLIArgument()
207 class CommandElaborationAndRun(CommandArgument, name="elab-run"):
208 """Elaborate and simulate design."""
210 @CLIArgument()
211 class CommandRun(CommandArgument, name="run"):
212 """Simulate design."""
214 @CLIArgument()
215 class CommandBind(CommandArgument, name="bind"):
216 """Bind design unit."""
218 @CLIArgument()
219 class CommandLink(CommandArgument, name="link"):
220 """Link design unit."""
222 @CLIArgument()
223 class CommandListLink(CommandArgument, name="list-link"):
224 """List objects file to link a design unit."""
226 @CLIArgument()
227 class CommandCompile(CommandArgument, name="compile"):
228 """Generate whole sequence to elaborate design from files."""
230 @CLIArgument()
231 class CommandGenerateDependencies(CommandArgument, name="gen-depends"):
232 """Generate dependencies of design."""
234 @CLIArgument()
235 class CommandSynthesize(CommandArgument, name="synth"):
236 """Synthesis from design unit."""
238 @CLIArgument()
239 class FlagVerbose(ShortFlag, name="v"):
240 """Run in verbose mode (print more messages)."""
242 # Analyze and elaborate options
243 @CLIArgument()
244 class FlagVHDLStandard(LongValuedFlag, name="std"):
245 """Set the used VHDL standard version."""
246 _value: VHDLVersion
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
252 self._value = value
254 @property
255 def Value(self) -> VHDLVersion:
256 return self._value
258 @Value.setter
259 def Value(self, value: VHDLVersion) -> None:
260 if value is None:
261 raise ValueError(f"") # XXX: add message
263 self._value = value
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
269 return self._pattern.format(self._name, str(self._value)[-2:])
271 @CLIArgument()
272 class FlagIEEEFlavor(LongValuedFlag, name="ieee"):
273 """Set the used VHDL flavor."""
275 @CLIArgument()
276 class FlagSynopsys(ShortFlag, name="fsynopsys"):
277 """Set used VHDL flavor to *Synopsys* and make Synopsys packages visible in library ``ìeee``."""
279 @CLIArgument()
280 class FlagRelaxed(ShortFlag, name="frelaxed"):
281 """Relax some LRM rules."""
283 @CLIArgument()
284 class FlagExplicit(ShortFlag, name="fexplicit"): ...
286 @CLIArgument()
287 class FlagLibrary(LongValuedFlag, name="work"):
288 """Set working library."""
290 @CLIArgument()
291 class FlagWorkingDirectory(LongValuedFlag, name="workdir"):
292 """Set working directory."""
294 @CLIArgument()
295 class FlagMultiByteComments(LongFlag, name="mb-comments"):
296 """Allow multi-byte comments."""
298 @CLIArgument()
299 class FlagSyntesisBindingRule(LongFlag, name="syn-binding"):
300 """Enable synthesis binding rule."""
302 @CLIArgument()
303 class FlagSearchPath(ShortValuedFlag, name="P", pattern="-{0}{1}"):
304 """Add search path."""
306 @CLIArgument()
307 class FlagTimeResolution(LongValuedFlag, name="time-resolution"):
308 """Set base time resolution.
310 Allowed values are ``auto`` (default), ``fs``, ``ps``, ``ns``, ``us``, ``ms`` or ``sec``.
311 """
313 @CLIArgument()
314 class FlagVitalChecks(LongBooleanFlag, name="vital-checks", pattern="-{0}", falsePattern="--no-{0}"):
315 """Check VITAL restrictions."""
317 @CLIArgument()
318 class FlagWarnUnboundComponents(ShortFlag, name="binding", pattern="-W{0}"):
319 """Warns for unbound components."""
321 @CLIArgument()
322 class FlagWarnReservedWords(ShortFlag, name="reserved", pattern="-W{0}"):
323 """Warns if VHDL'93 reserved words are used in VHDL'87."""
325 @CLIArgument()
326 class FlagWarnRedefinedDesignUnits(ShortFlag, name="library", pattern="-W{0}"):
327 """Warns for redefined design unit."""
329 @CLIArgument()
330 class FlagWarnNonVitalGenericNames(ShortFlag, name="vital-generic", pattern="-W{0}"):
331 """Warns of non-vital generic names."""
333 @CLIArgument()
334 class FlagWarnElaborationChecks(ShortFlag, name="delayed-checks", pattern="-W{0}"):
335 """Warns for checks performed at elaboration."""
337 @CLIArgument()
338 class FlagWarnUnnecessaryPackageBody(ShortFlag, name="body", pattern="-W{0}"):
339 """Warns for unnecessary package body."""
341 @CLIArgument()
342 class FlagWarnOthersSpecifications(ShortFlag, name="specs", pattern="-W{0}"):
343 """Warns if an all/others specification does not apply."""
345 @CLIArgument()
346 class FlagUnused(ShortFlag, name="unused", pattern="-W{0}"):
347 """Warns for unused subprograms."""
349 @CLIArgument()
350 class FlagError(ShortFlag, name="error", pattern="-W{0}"):
351 """Turns warnings into errors."""
353 @CLIArgument()
354 class OptionPaths(PathListArgument):
355 """Add list of VHDL files to analyze."""
357 @CLIArgument()
358 class OptionTopLevel(StringArgument):
359 """Specify the toplevel design unit."""
361 @CLIArgument()
362 class OptionArchitecture(StringArgument):
363 """Specify the architecture name, if the toplevel design unit is an entity."""
365 @CLIArgument()
366 class FlagGenerics(ShortKeyValueFlag, pattern="-{0}{1}={2}"):
367 """Set a generic value."""
369 @CLIArgument()
370 class FlagAsserts(ShortValuedFlag, name="asserts"):
371 """Select how assertions are handled.
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 """
377 @CLIArgument()
378 class FlagIEEEAsserts(ShortValuedFlag, name="ieee-asserts"):
379 """Select how assertions are handled.
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 """
385 @CLIArgument()
386 class FlagStopTime(ShortValuedFlag, name="stop-time"):
387 """Stop the simulation after a given simulation time.
389 The time is expressed as a time value, without any spaces. The time is the simulation time, not the real execution time.
390 """
392 @CLIArgument()
393 class FlagMaxDeltaCycles(ShortValuedFlag, name="stop-delta"):
394 """Stop the simulation after N delta cycles in the same current time."""
396 @CLIArgument()
397 class FlagDisplayDeltaCycles(ShortValuedFlag, name="disp-time"):
398 """Display the time and delta cycle number as simulation advances."""
400 @CLIArgument()
401 class FlagUnbufferedIO(ShortValuedFlag, name="unbuffered"):
402 """Disable buffering on STDOUT, STDERR and files opened in write or append mode (TEXTIO)."""
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."""
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 """
414 @CLIArgument()
415 class FlagGHWWaveformFile(ShortValuedFlag, name="wave"):
416 """Write the waveforms into a GHDL Waveform (``*.ghw``) file.
418 Contrary to VCD files, any VHDL type can be dumped into a GHW file.
419 """
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()
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)
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
436 def GetGHDLAsAnalyzer(self, std: Nullable[VHDLVersion] = None, ieee: Nullable[str] = None) -> "GHDL":
437 tool = GHDL(executablePath=self._executablePath)
439 tool[tool.CommandAnalyze] = True
440 self._CopyParameters(tool)
441 self._SetParameters(tool, std, ieee)
443 return tool
445 def GetGHDLAsElaborator(self, std: Nullable[VHDLVersion] = None, ieee: Nullable[str] = None) -> "GHDL":
446 tool = GHDL(executablePath=self._executablePath)
448 tool[tool.CommandElaborate] = True
449 self._CopyParameters(tool)
450 self._SetParameters(tool, std, ieee)
452 return tool
454 def GetGHDLAsSimulator(self, std: Nullable[VHDLVersion] = None, ieee: Nullable[str] = None) -> "GHDL":
455 tool = GHDL(executablePath=self._executablePath)
457 tool[tool.CommandRun] = True
458 self._CopyParameters(tool)
459 self._SetParameters(tool, std, ieee)
461 return tool
463 def Help(self) -> str:
464 tool = GHDL(executablePath=self._executablePath)
466 tool[tool.CommandHelp] = True
468 tool.StartProcess()
469 return "\n".join(tool.GetLineReader())
471 def Version(self) -> GHDLVersion:
472 tool = GHDL(executablePath=self._executablePath)
474 tool[tool.CommandVersion] = True
476 tool.StartProcess()
477 iterator = iter(tool.GetLineReader())
478 firstLine = next(iterator)
479 secondLine = next(iterator)
480 thirdLine = next(iterator)
482 return GHDLVersion(firstLine, secondLine, thirdLine)