+ 1# ==================================================================================================================== #
+ 2# _____ ____ _ _ ____ _ ___ _____ _ #
+ 3# _ __ _ _| ____| _ \ / \ / \ / ___| | |_ _|_ _|__ ___ | | #
+ 4# | '_ \| | | | _| | | | |/ _ \ / _ \ | | | | | | | |/ _ \ / _ \| | #
+ 5# | |_) | |_| | |___| |_| / ___ \ / ___ \ | |___| |___ | | | | (_) | (_) | | #
+ 6# | .__/ \__, |_____|____/_/ \_\/_/ \_(_)____|_____|___| |_|\___/ \___/|_| #
+ 7# |_| |___/ #
+ 8# ==================================================================================================================== #
+ 9# Authors: #
+ 10# Patrick Lehmann #
+ 11# #
+ 12# License: #
+ 13# ==================================================================================================================== #
+ 14# Copyright 2017-2023 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, ClassVar, Dict
+ 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 def __init__(self, versionLine: str, gnatLine: str, backendLine: str):
+ 65 match = re_search(
+ 66 r"GHDL"
+ 67 r"\s(?P<major>\d+)"
+ 68 r"\.(?P<minor>\d+)"
+ 69 r"\.(?P<micro>\d+)"
+ 70 r"(?:-(?P<suffix>dev))?"
+ 71 r"\s\("
+ 72 r"(?P<major2>\d+)"
+ 73 r"\.(?P<minor2>\d+)"
+ 74 r"\.(?P<micro2>\d+)"
+ 75 r"\.(?:r(?P<cslt>\d+))"
+ 76 r"\.(?:g(?P<hash>[0-9a-f]+))"
+ 77 r"(?:\.(?P<dirty>dirty))?"
+ 78 r"\)\s"
+ 79 r"\[(?P<edition>[\w\s]+)\]",
+ 80 versionLine)
+ 81
+ 82 if match is None:
+ 83 raise CLIToolException(f"Unknown first GHDL version string '{versionLine}'.")
+ 84
+ 85 self._major = int(match["major"])
+ 86 self._minor = int(match["minor"])
+ 87 self._micro = int(match["micro"])
+ 88 self._dev = "dev" in match.groups()
+ 89 self._commitsSinceLastTag = int(match["cslt"])
+ 90 self._gitHash = match["hash"]
+ 91 self._dirty = "dirty" in match.groups()
+ 92 self._edition = match["edition"]
+ 93
+ 94 match = re_search(
+ 95 r"\s*[\w\s]+:\s(?P<major>\d+)\.(?P<minor>\d+)\.(?P<micro>\d+)", gnatLine)
+ 96
+ 97 if match is None:
+ 98 raise CLIToolException(f"Unknown second GHDL version string '{gnatLine}'.")
+ 99
+ 100 self._gnatCompiler = (match["major"], match["minor"], match["micro"])
+ 101
+ 102 match = re_search(
+ 103 r"\s*(?P<backend>\w+)\scode\sgenerator", backendLine)
+ 104
+ 105 if match is None:
+ 106 raise CLIToolException(f"Unknown third GHDL version string '{backendLine}'.")
+ 107
+ 108 self._backend = match["backend"]
+ 109
+ 110 @property
+ 111 def Major(self) -> int:
+ 112 return self._major
+ 113
+ 114 @property
+ 115 def Minor(self) -> int:
+ 116 return self._minor
+ 117
+ 118 @property
+ 119 def Micro(self) -> int:
+ 120 return self._micro
+ 121
+ 122 @property
+ 123 def Dev(self) -> bool:
+ 124 return self._dev
+ 125
+ 126 @property
+ 127 def CommitsSinceLastTag(self) -> int:
+ 128 return self._commitsSinceLastTag
+ 129
+ 130 @property
+ 131 def GitHash(self) -> str:
+ 132 return self._gitHash
+ 133
+ 134 @property
+ 135 def Dirty(self) -> bool:
+ 136 return self._dirty
+ 137
+ 138 @property
+ 139 def Edition(self) -> str:
+ 140 return self._edition
+ 141
+ 142 def __str__(self) -> str:
+ 143 dev = f"-dev" if self._dev else ""
+ 144 return f"{self._major}.{self._minor}.{self._micro}{dev}"
+ 145
+ 146 def __repr__(self) -> str:
+ 147 return f"{self.__str__()} (Backend: {self._backend}; Git: {self._gitHash})"
+ 148
+ 149
+ 150@export
+ 151class GHDL(Executable):
+ 152 _executableNames: ClassVar[Dict[str, str]] = {
+ 153 "Darwin": "ghdl",
+ 154 "Linux": "ghdl",
+ 155 "Windows": "ghdl.exe"
+ 156 }
+ 157
+ 158 # XXX: overwrite __init__ and get backend variant
+ 159 # XXX: check for compatible backends
+ 160
+ 161 @CLIArgument()
+ 162 class CommandHelp(CommandArgument, name="help"):
+ 163 """Print help page(s)."""
+ 164
+ 165 @CLIArgument()
+ 166 class CommandVersion(CommandArgument, name="version"):
+ 167 """Print version information."""
+ 168
+ 169 @CLIArgument()
+ 170 class CommandSyntax(CommandArgument, name="syntax"):
+ 171 """Check syntax."""
+ 172
+ 173 @CLIArgument()
+ 174 class CommandElaborationOrder(CommandArgument, name="elab-order"):
+ 175 """Display (elaboration) ordered source files."""
+ 176
+ 177 @CLIArgument()
+ 178 class CommandAnalyze(CommandArgument, name="analyze"):
+ 179 """Analyze VHDL source file(s)."""
+ 180
+ 181 @CLIArgument()
+ 182 class CommandElaborate(CommandArgument, name="elaborate"):
+ 183 """Elaborate design."""
+ 184
+ 185 @CLIArgument()
+ 186 class CommandElaborationAndRun(CommandArgument, name="elab-run"):
+ 187 """Elaborate and simulate design."""
+ 188
+ 189 @CLIArgument()
+ 190 class CommandRun(CommandArgument, name="run"):
+ 191 """Simulate design."""
+ 192
+ 193 @CLIArgument()
+ 194 class CommandBind(CommandArgument, name="bind"):
+ 195 """Bind design unit."""
+ 196
+ 197 @CLIArgument()
+ 198 class CommandLink(CommandArgument, name="link"):
+ 199 """Link design unit."""
+ 200
+ 201 @CLIArgument()
+ 202 class CommandListLink(CommandArgument, name="list-link"):
+ 203 """List objects file to link a design unit."""
+ 204
+ 205 @CLIArgument()
+ 206 class CommandCompile(CommandArgument, name="compile"):
+ 207 """Generate whole sequence to elaborate design from files."""
+ 208
+ 209 @CLIArgument()
+ 210 class CommandGenerateDependencies(CommandArgument, name="gen-depends"):
+ 211 """Generate dependencies of design."""
+ 212
+ 213 @CLIArgument()
+ 214 class CommandSynthesize(CommandArgument, name="synth"):
+ 215 """Synthesis from design unit."""
+ 216
+ 217 @CLIArgument()
+ 218 class FlagVerbose(ShortFlag, name="v"):
+ 219 """Run in verbose mode (print more messages)."""
+ 220
+ 221 # Analyze and elaborate options
+ 222 @CLIArgument()
+ 223 class FlagVHDLStandard(LongValuedFlag, name="std"):
+ 224 """Set the used VHDL standard version."""
+ 225 _value: VHDLVersion
+ 226
+ 227 def __init__(self, value: VHDLVersion):
+ 228 if value is None: 228 ↛ 229line 228 didn't jump to line 229, because the condition on line 228 was never true
+ 229 raise ValueError(f"") # XXX: add message
+ 230
+ 231 self._value = value
+ 232
+ 233 @property
+ 234 def Value(self) -> VHDLVersion:
+ 235 return self._value
+ 236
+ 237 @Value.setter
+ 238 def Value(self, value: VHDLVersion) -> None:
+ 239 if value is None:
+ 240 raise ValueError(f"") # XXX: add message
+ 241
+ 242 self._value = value
+ 243
+ 244 def AsArgument(self) -> Union[str, Iterable[str]]:
+ 245 if self._name is None: 245 ↛ 246line 245 didn't jump to line 246, because the condition on line 245 was never true
+ 246 raise ValueError(f"") # XXX: add message
+ 247
+ 248 return self._pattern.format(self._name, str(self._value)[-2:])
+ 249
+ 250 @CLIArgument()
+ 251 class FlagIEEEFlavor(LongValuedFlag, name="ieee"):
+ 252 """Set the used VHDL flavor."""
+ 253
+ 254 @CLIArgument()
+ 255 class FlagSynopsys(ShortFlag, name="fsynopsys"):
+ 256 """Set used VHDL flavor to *Synopsys* and make Synopsys packages visible in library ``ìeee``."""
+ 257
+ 258 @CLIArgument()
+ 259 class FlagRelaxed(ShortFlag, name="frelaxed"):
+ 260 """Relax some LRM rules."""
+ 261
+ 262 @CLIArgument()
+ 263 class FlagExplicit(ShortFlag, name="fexplicit"): ...
+ 264
+ 265 @CLIArgument()
+ 266 class FlagLibrary(LongValuedFlag, name="work"):
+ 267 """Set working library."""
+ 268
+ 269 @CLIArgument()
+ 270 class FlagWorkingDirectory(LongValuedFlag, name="workdir"):
+ 271 """Set working directory."""
+ 272
+ 273 @CLIArgument()
+ 274 class FlagMultiByteComments(LongFlag, name="mb-comments"):
+ 275 """Allow multi-byte comments."""
+ 276
+ 277 @CLIArgument()
+ 278 class FlagSyntesisBindingRule(LongFlag, name="syn-binding"):
+ 279 """Enable synthesis binding rule."""
+ 280
+ 281 @CLIArgument()
+ 282 class FlagSearchPath(ShortValuedFlag, name="P", pattern="-{0}{1}"):
+ 283 """Add search path."""
+ 284
+ 285 @CLIArgument()
+ 286 class FlagTimeResolution(LongValuedFlag, name="time-resolution"):
+ 287 """Set base time resolution.
+ 288
+ 289 Allowed values are ``auto`` (default), ``fs``, ``ps``, ``ns``, ``us``, ``ms`` or ``sec``.
+ 290 """
+ 291
+ 292 @CLIArgument()
+ 293 class FlagVitalChecks(LongBooleanFlag, name="vital-checks", pattern="-{0}", falsePattern="--no-{0}"):
+ 294 """Check VITAL restrictions."""
+ 295
+ 296 @CLIArgument()
+ 297 class FlagWarnUnboundComponents(ShortFlag, name="binding", pattern="-W{0}"):
+ 298 """Warns for unbound components."""
+ 299
+ 300 @CLIArgument()
+ 301 class FlagWarnReservedWords(ShortFlag, name="reserved", pattern="-W{0}"):
+ 302 """Warns if VHDL'93 reserved words are used in VHDL'87."""
+ 303
+ 304 @CLIArgument()
+ 305 class FlagWarnRedefinedDesignUnits(ShortFlag, name="library", pattern="-W{0}"):
+ 306 """Warns for redefined design unit."""
+ 307
+ 308 @CLIArgument()
+ 309 class FlagWarnNonVitalGenericNames(ShortFlag, name="vital-generic", pattern="-W{0}"):
+ 310 """Warns of non-vital generic names."""
+ 311
+ 312 @CLIArgument()
+ 313 class FlagWarnElaborationChecks(ShortFlag, name="delayed-checks", pattern="-W{0}"):
+ 314 """Warns for checks performed at elaboration."""
+ 315
+ 316 @CLIArgument()
+ 317 class FlagWarnUnnecessaryPackageBody(ShortFlag, name="body", pattern="-W{0}"):
+ 318 """Warns for unnecessary package body."""
+ 319
+ 320 @CLIArgument()
+ 321 class FlagWarnOthersSpecifications(ShortFlag, name="specs", pattern="-W{0}"):
+ 322 """Warns if an all/others specification does not apply."""
+ 323
+ 324 @CLIArgument()
+ 325 class FlagSyntesisBindingRule(ShortFlag, name="unused", pattern="-W{0}"):
+ 326 """Warns for unused subprograms."""
+ 327
+ 328 @CLIArgument()
+ 329 class FlagSyntesisBindingRule(ShortFlag, name="error", pattern="-W{0}"):
+ 330 """Turns warnings into errors."""
+ 331
+ 332 @CLIArgument()
+ 333 class OptionPaths(PathListArgument):
+ 334 """Add list of VHDL files to analyze."""
+ 335
+ 336 @CLIArgument()
+ 337 class OptionTopLevel(StringArgument):
+ 338 """Specify the toplevel design unit."""
+ 339
+ 340 @CLIArgument()
+ 341 class OptionArchitecture(StringArgument):
+ 342 """Specify the architecture name, if the toplevel design unit is an entity."""
+ 343
+ 344 @CLIArgument()
+ 345 class FlagGenerics(ShortKeyValueFlag, pattern="-{0}{1}={2}"):
+ 346 """Set a generic value."""
+ 347
+ 348 @CLIArgument()
+ 349 class FlagAsserts(ShortValuedFlag, name="asserts"):
+ 350 """Select how assertions are handled.
+ 351
+ 352 It can be ``enable`` (the default), ``disable`` which disables all assertions and ``disable-at-0`` which disables
+ 353 only at the start of simulation.
+ 354 """
+ 355
+ 356 @CLIArgument()
+ 357 class FlagIEEEAsserts(ShortValuedFlag, name="ieee-asserts"):
+ 358 """Select how assertions are handled.
+ 359
+ 360 It can be ``enable`` (the default), ``disable`` which disables all assertions and ``disable-at-0`` which disables
+ 361 only at the start of simulation.
+ 362 """
+ 363
+ 364 @CLIArgument()
+ 365 class FlagStopTime(ShortValuedFlag, name="stop-time"):
+ 366 """Stop the simulation after a given simulation time.
+ 367
+ 368 The time is expressed as a time value, without any spaces. The time is the simulation time, not the real execution time.
+ 369 """
+ 370
+ 371 @CLIArgument()
+ 372 class FlagMaxDeltaCycles(ShortValuedFlag, name="stop-delta"):
+ 373 """Stop the simulation after N delta cycles in the same current time."""
+ 374
+ 375 @CLIArgument()
+ 376 class FlagDisplayDeltaCycles(ShortValuedFlag, name="disp-time"):
+ 377 """Display the time and delta cycle number as simulation advances."""
+ 378
+ 379 @CLIArgument()
+ 380 class FlagUnbufferedIO(ShortValuedFlag, name="unbuffered"):
+ 381 """Disable buffering on STDOUT, STDERR and files opened in write or append mode (TEXTIO)."""
+ 382
+ 383 @CLIArgument()
+ 384 class FlagReadWaveformOptionsFile(ShortValuedFlag, name="read-wave-opt"):
+ 385 """Filter signals to be dumped to the waveform file according to the wavefile option file provided."""
+ 386
+ 387 @CLIArgument()
+ 388 class FlagWriteWaveformOptionsFile(ShortValuedFlag, name="write-wave-opt"):
+ 389 """If the wavefile option file doesn’t exist, creates it with all the signals of the design.
+ 390 Otherwise, it throws an error, because it won’t erase an existing file.
+ 391 """
+ 392
+ 393 @CLIArgument()
+ 394 class FlagGHWWaveformFile(ShortValuedFlag, name="wave"):
+ 395 """Write the waveforms into a GHDL Waveform (``*.ghw``) file.
+ 396
+ 397 Contrary to VCD files, any VHDL type can be dumped into a GHW file.
+ 398 """
+ 399
+ 400 def _CopyParameters(self, tool: "GHDL") -> None:
+ 401 for key in self.__cliParameters__:
+ 402 if self._NeedsParameterInitialization(key):
+ 403 value = self.__cliParameters__[key].Value
+ 404 tool.__cliParameters__[key] = key(value)
+ 405 else:
+ 406 tool.__cliParameters__[key] = key()
+ 407
+ 408 def _SetParameters(self, tool: "GHDL", std: VHDLVersion = None, ieee: str = None):
+ 409 if std is not None:
+ 410 tool[self.FlagVHDLStandard] = str(std)
+ 411
+ 412 if ieee is not None:
+ 413 tool[self.FlagVHDLStandard] = ieee
+ 414
+ 415 def GetGHDLAsAnalyzer(self, std: VHDLVersion = None, ieee: str = None):
+ 416 tool = GHDL(executablePath=self._executablePath)
+ 417
+ 418 tool[tool.CommandAnalyze] = True
+ 419 self._CopyParameters(tool)
+ 420 self._SetParameters(tool, std, ieee)
+ 421
+ 422 return tool
+ 423
+ 424 def GetGHDLAsElaborator(self, std: VHDLVersion = None, ieee: str = None):
+ 425 tool = GHDL(executablePath=self._executablePath)
+ 426
+ 427 tool[tool.CommandElaborate] = True
+ 428 self._CopyParameters(tool)
+ 429 self._SetParameters(tool, std, ieee)
+ 430
+ 431 return tool
+ 432
+ 433 def GetGHDLAsSimulator(self, std: VHDLVersion = None, ieee: str = None):
+ 434 tool = GHDL(executablePath=self._executablePath)
+ 435
+ 436 tool[tool.CommandRun] = True
+ 437 self._CopyParameters(tool)
+ 438 self._SetParameters(tool, std, ieee)
+ 439
+ 440 return tool
+ 441
+ 442 def Help(self):
+ 443 tool = GHDL(executablePath=self._executablePath)
+ 444
+ 445 tool[tool.CommandHelp] = True
+ 446
+ 447 tool.StartProcess()
+ 448 return "\n".join(tool.GetLineReader())
+ 449
+ 450 def Version(self):
+ 451 tool = GHDL(executablePath=self._executablePath)
+ 452
+ 453 tool[tool.CommandVersion] = True
+ 454
+ 455 tool.StartProcess()
+ 456 iterator = iter(tool.GetLineReader())
+ 457 firstLine = next(iterator)
+ 458 secondLine = next(iterator)
+ 459 thirdLine = next(iterator)
+ 460
+ 461 return GHDLVersion(firstLine, secondLine, thirdLine)
+
+