primitive 0.1.36__py3-none-any.whl → 0.1.38__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
primitive/__about__.py CHANGED
@@ -1,4 +1,4 @@
1
1
  # SPDX-FileCopyrightText: 2024-present Dylan Stein <dylan@primitive.tech>
2
2
  #
3
3
  # SPDX-License-Identifier: MIT
4
- __version__ = "0.1.36"
4
+ __version__ = "0.1.38"
@@ -1,5 +1,5 @@
1
- import platform
2
1
  import sys
2
+ import shutil
3
3
  from pathlib import Path
4
4
  from time import sleep
5
5
  from primitive.utils.actions import BaseAction
@@ -7,6 +7,7 @@ from loguru import logger
7
7
  from primitive.__about__ import __version__
8
8
  import yaml
9
9
  from ..utils.yaml import generate_script_from_yaml
10
+ from ..utils.cache import get_sources_cache
10
11
 
11
12
  try:
12
13
  from yaml import CLoader as Loader
@@ -15,22 +16,6 @@ except ImportError:
15
16
 
16
17
 
17
18
  class Agent(BaseAction):
18
- def set_cache_dir(self):
19
- os_family = platform.system()
20
-
21
- if os_family == "Darwin":
22
- self.cache_dir = Path(
23
- Path.home() / "Library" / "Caches" / "tech.primitive.agent"
24
- )
25
- elif os_family == "Linux":
26
- self.cache_dir = Path(Path.home() / ".cache" / "primitive")
27
- elif os_family == "Windows":
28
- raise NotImplementedError("Windows is not currently supported.")
29
- self.cache_dir = None
30
-
31
- if not self.cache_dir.exists():
32
- self.cache_dir.mkdir(parents=True, exist_ok=True)
33
-
34
19
  def execute(
35
20
  self,
36
21
  ):
@@ -39,7 +24,7 @@ class Agent(BaseAction):
39
24
  logger.info(f" [*] Version: {__version__}")
40
25
 
41
26
  # Create cache dir if it doesnt exist
42
- self.set_cache_dir()
27
+ cache_dir = get_sources_cache()
43
28
 
44
29
  # self.primitive.hardware.update_hardware_system_info()
45
30
  try:
@@ -126,7 +111,7 @@ class Agent(BaseAction):
126
111
  git_repo_full_name=git_repo_full_name,
127
112
  git_ref=git_ref,
128
113
  github_access_token=github_access_token,
129
- destination=self.cache_dir,
114
+ destination=cache_dir,
130
115
  )
131
116
  )
132
117
 
@@ -210,6 +195,9 @@ class Agent(BaseAction):
210
195
 
211
196
  logger.debug("Sim Job Completed")
212
197
 
198
+ # Clean up
199
+ shutil.rmtree(path=downloaded_git_repository_dir)
200
+
213
201
  sleep(5)
214
202
  except KeyboardInterrupt:
215
203
  logger.info(" [*] Stopping primitive...")
primitive/lint/actions.py CHANGED
@@ -5,6 +5,7 @@ from typing import Tuple
5
5
  from loguru import logger
6
6
  from ..utils.files import find_files_for_extension
7
7
  from ..utils.verible import install_verible
8
+ from ..utils.cache import get_deps_cache
8
9
 
9
10
 
10
11
  class Lint(BaseAction):
@@ -17,20 +18,31 @@ class Lint(BaseAction):
17
18
  return False, message
18
19
 
19
20
  logger.debug("Checking if verible is installed")
21
+ verible_path = Path("verible-verilog-lint")
20
22
  try:
21
- subprocess.run(["verible-verilog-lint", "--version"], capture_output=True)
23
+ subprocess.run([str(verible_path), "--version"], capture_output=True)
22
24
  except FileNotFoundError:
23
- logger.debug("Did not find verible. Installing...")
24
- try:
25
- system_info = self.primitive.hardware.get_system_info()
26
- install_verible(system_info=system_info)
27
- except Exception as exception:
28
- message = f"Failed to install verible. {exception}"
29
- logger.error(message)
30
- return False, message
25
+ logger.debug("Verible not found in $PATH. Looking in deps cache...")
26
+ cache_dir = get_deps_cache()
27
+
28
+ possible_dirs = cache_dir.glob("verible*")
29
+ verible_dir = next(possible_dirs, None)
30
+
31
+ if verible_dir is not None:
32
+ verible_path = verible_dir / "bin" / verible_path
33
+ else:
34
+ logger.debug("Did not find verible. Installing...")
35
+ try:
36
+ system_info = self.primitive.hardware.get_system_info()
37
+ verible_bin = install_verible(system_info=system_info)
38
+ verible_path = verible_bin / verible_path
39
+ except Exception as exception:
40
+ message = f"Failed to install verible. {exception}"
41
+ logger.error(message)
42
+ return False, message
31
43
 
32
44
  try:
33
- subprocess.run(["verible-verilog-lint", "--version"], capture_output=True)
45
+ subprocess.run([str(verible_path), "--version"], capture_output=True)
34
46
  except FileNotFoundError:
35
47
  message = "Verible is not installed. Please install it to run lint."
36
48
  logger.error(message)
@@ -40,7 +52,7 @@ class Lint(BaseAction):
40
52
  # run is great for now! we will need to switch to Popen if we want to stream the output
41
53
  logger.debug("Running linter...")
42
54
  result = subprocess.run(
43
- ["verible-verilog-lint", *files],
55
+ [str(verible_path), *files],
44
56
  capture_output=True,
45
57
  text=True,
46
58
  )
@@ -0,0 +1,41 @@
1
+ import platform
2
+ from pathlib import Path
3
+
4
+
5
+ def get_cache_dir() -> Path:
6
+ os_family = platform.system()
7
+
8
+ cache_dir = None
9
+ if os_family == "Darwin":
10
+ cache_dir = Path(Path.home() / "Library" / "Caches" / "tech.primitive.agent")
11
+ elif os_family == "Linux":
12
+ cache_dir = Path(Path.home() / ".cache" / "primitive")
13
+ elif os_family == "Windows":
14
+ raise NotImplementedError("Windows is not currently supported.")
15
+
16
+ if not cache_dir.exists():
17
+ cache_dir.mkdir(parents=True, exist_ok=True)
18
+
19
+ return cache_dir
20
+
21
+
22
+ def get_sources_cache() -> Path:
23
+ cache_dir = get_cache_dir()
24
+
25
+ sources_dir = cache_dir / "sources"
26
+
27
+ if not sources_dir.exists():
28
+ sources_dir.mkdir(parents=True, exist_ok=True)
29
+
30
+ return sources_dir
31
+
32
+
33
+ def get_deps_cache() -> Path:
34
+ cache_dir = get_cache_dir()
35
+
36
+ deps_dir = cache_dir / "deps"
37
+
38
+ if not deps_dir.exists():
39
+ deps_dir.mkdir(parents=True, exist_ok=True)
40
+
41
+ return deps_dir
@@ -1,7 +1,7 @@
1
1
  import tarfile
2
2
  import requests
3
3
  from .shell import add_path_to_shell
4
- from pathlib import Path
4
+ from .cache import get_deps_cache
5
5
 
6
6
  from loguru import logger
7
7
 
@@ -12,7 +12,7 @@ VERIBLE_LINUX_X86_64_OS_LINK = "https://github.com/chipsalliance/verible/release
12
12
  VERIBLE_LINUX_ARM64_LINK = "https://github.com/chipsalliance/verible/releases/download/v0.0-3752-g8b64887e/verible-v0.0-3752-g8b64887e-linux-static-arm64.tar.gz"
13
13
 
14
14
 
15
- def install_verible(system_info: dict) -> bool:
15
+ def install_verible(system_info: dict) -> str:
16
16
  url = None
17
17
  if system_info.get("os_family") == "Darwin":
18
18
  url = VERIBLE_MAC_OS_LINK
@@ -23,8 +23,10 @@ def install_verible(system_info: dict) -> bool:
23
23
  elif system_info.get("processor") == "arm":
24
24
  url = VERIBLE_LINUX_X86_64_OS_LINK
25
25
 
26
+ deps_cache = get_deps_cache()
27
+
26
28
  verible_dir_name = url.split("/")[-1].split(".tar.gz")[0]
27
- file_download_path = Path.home() / f"{verible_dir_name}.tar.gz"
29
+ file_download_path = deps_cache / f"{verible_dir_name}.tar.gz"
28
30
 
29
31
  logger.debug("Downloading verible")
30
32
  response = requests.get(url, stream=True)
@@ -38,7 +40,7 @@ def install_verible(system_info: dict) -> bool:
38
40
 
39
41
  logger.debug("Untaring verible")
40
42
  with tarfile.open(file_download_path) as tar:
41
- tar.extractall(Path.home())
43
+ tar.extractall(deps_cache)
42
44
 
43
45
  logger.debug("Deleting tar.gz artifact")
44
46
  file_download_path.unlink()
@@ -47,9 +49,9 @@ def install_verible(system_info: dict) -> bool:
47
49
  if "linux" in unpacked_verible_dir_name:
48
50
  unpacked_verible_dir_name = unpacked_verible_dir_name.split("-linux")[0]
49
51
 
50
- verible_bin = Path.home().joinpath(unpacked_verible_dir_name).joinpath("bin")
52
+ verible_bin = deps_cache.joinpath(unpacked_verible_dir_name).joinpath("bin")
51
53
 
52
54
  logger.debug("Adding verible to PATH")
53
55
  add_path_to_shell(verible_bin)
54
56
 
55
- return True
57
+ return verible_bin
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: primitive
3
- Version: 0.1.36
3
+ Version: 0.1.38
4
4
  Project-URL: Documentation, https://github.com//primitivecorp/primitive-cli#readme
5
5
  Project-URL: Issues, https://github.com//primitivecorp/primitive-cli/issues
6
6
  Project-URL: Source, https://github.com//primitivecorp/primitive-cli
@@ -1,8 +1,8 @@
1
- primitive/__about__.py,sha256=3pcK2kwWyYKd_eACBuwQFxiovvgGIJY1DjsQV2qGJvg,130
1
+ primitive/__about__.py,sha256=MQcjqY2SeE60v4UHTDYOAkgz_ZYToia__NNE8XaHE6o,130
2
2
  primitive/__init__.py,sha256=bwKdgggKNVssJFVPfKSxqFMz4IxSr54WWbmiZqTMPNI,106
3
3
  primitive/cli.py,sha256=VQPSewC6ouGdEG9W1gllawGJTydpOY0Lzg7LURXcqQg,2374
4
4
  primitive/client.py,sha256=SFPG4H2wJao8euGdnYp-l7dk_fDpWeVn2aT2WNJUAqo,2370
5
- primitive/agent/actions.py,sha256=v4g5b8UkJZan5rTfkl9acfTHEE_DRrT2s5yMp_8gnow,8904
5
+ primitive/agent/actions.py,sha256=cxavCfRG76KJ6M-FVwg3htMbJLz16gUjy30h-vsJ7A0,8468
6
6
  primitive/agent/commands.py,sha256=-dVDilELfkGfbZB7qfEPs77Dm1oT62qJj4tsIk4KoxI,254
7
7
  primitive/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  primitive/auth/actions.py,sha256=N2bGcwXNsB89pzs66gF9A5_WzUScY5fhfOyWixqo2y8,1054
@@ -22,7 +22,7 @@ primitive/hardware/actions.py,sha256=JJXEeW35QzVGcLN4ym5gYZwY71hxLzM1GYPXWaObEts
22
22
  primitive/hardware/commands.py,sha256=QE7LLeFdfOqlvz3JwdwJJRZAY3fHI1zB9kYmmDajpq0,1477
23
23
  primitive/jobs/actions.py,sha256=LpUJN-GOiDSehQANfV_F86eRRKPZ2ew9skKdnlalXU4,7734
24
24
  primitive/jobs/commands.py,sha256=MxPCkBEYW_eLNqgCRYeyj7ZcLOFAWfpVZlqDR2Y_S0o,830
25
- primitive/lint/actions.py,sha256=fGnNlcD_B-E0SvRUTvTdSlTm2kCQUTrlBLB0mt1sXKM,2268
25
+ primitive/lint/actions.py,sha256=_1Yp7rOoatJSg_yi8H5XRu70SLvMIhzhXOoc2SMr5jk,2809
26
26
  primitive/lint/commands.py,sha256=3CZvkOEMpJspJWmaQzA5bpPKx0_VCijQIXA9l-eTnZE,487
27
27
  primitive/organizations/actions.py,sha256=e0V4E1UK1IcBJsWWH6alHYUmArhzPrBqZ8WkHPIcLq0,2268
28
28
  primitive/organizations/commands.py,sha256=_dwgVEJCqMa5VgB_7P1wLPFc0AuT1p9dtyR9JRr4kpw,487
@@ -32,18 +32,18 @@ primitive/projects/commands.py,sha256=Fqqgpi4cm6zOgkHK--0F0hiiIj32BmgZ-h1MydmWwd
32
32
  primitive/sim/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
33
  primitive/sim/actions.py,sha256=MkhiAH5QvF2jbnpgWL29HeSrTYiSW0UJpql2wZ4q18w,4900
34
34
  primitive/sim/commands.py,sha256=8PaOfL1MO6qxTn7mNVRnBU1X2wa3gk_mlbAhBW6MnI0,591
35
- primitive/sim/vcd.py,sha256=mAbGnKWM0qzIUMkuSmO0p3sU25kOqbl31mvCsDSrXeM,22221
36
35
  primitive/utils/actions.py,sha256=HOFrmM3-0A_A3NS84MqrZ6JmQEiiPSoDqEeuu6b_qfQ,196
36
+ primitive/utils/cache.py,sha256=rzMhqDnP8QIzRidF7iyYA5rBGRLEu0tjX0POX0E1xFw,975
37
37
  primitive/utils/config.py,sha256=DlFM5Nglo22WPtbpZSVtH7NX-PTMaKYlcrUE7GPRG4c,1058
38
38
  primitive/utils/files.py,sha256=Yv__bQes3YIlzhOT9kVxtYhoA5CmUjPSvphl9PZ41k4,867
39
39
  primitive/utils/git.py,sha256=1qNOu8X-33CavmrD580BmrFhD_WVO9PGWHUUboXJR_g,663
40
40
  primitive/utils/memory_size.py,sha256=4xfha21kW82nFvOTtDFx9Jk2ZQoEhkfXii-PGNTpIUk,3058
41
41
  primitive/utils/printer.py,sha256=f1XUpqi5dkTL3GWvYRUGlSwtj2IxU1q745T4Fxo7Tn4,370
42
42
  primitive/utils/shell.py,sha256=-7UjQaBqSGHzEEyX8pNjeYFFP0P3lVnDV0OkgPz1qHU,1050
43
- primitive/utils/verible.py,sha256=QYczN1IvxODfj4jeq0nqjFuF0Oi0Zdx-Q32ySOJgcw8,2205
43
+ primitive/utils/verible.py,sha256=r7c_hfqvL0UicMmIzK3Cy_BfZI1ZpcfBeLqKEWFWqJo,2252
44
44
  primitive/utils/yaml.py,sha256=4UP_9MXHoNb9_SCeUDm9xqYg9sHltqpVhNgsY6GNfb8,527
45
- primitive-0.1.36.dist-info/METADATA,sha256=rURs2pkg832eyJGHv8XDhfIC-cthvyvZr2Vnl5b6KFQ,3782
46
- primitive-0.1.36.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
47
- primitive-0.1.36.dist-info/entry_points.txt,sha256=p1K8DMCWka5FqLlqP1sPek5Uovy9jq8u51gUsP-z334,48
48
- primitive-0.1.36.dist-info/licenses/LICENSE.txt,sha256=B8kmQMJ2sxYygjCLBk770uacaMci4mPSoJJ8WoDBY_c,1098
49
- primitive-0.1.36.dist-info/RECORD,,
45
+ primitive-0.1.38.dist-info/METADATA,sha256=myj66bhzFYZdrqHGnnkxO19sfbywigbUUwGFvIeU66E,3782
46
+ primitive-0.1.38.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
47
+ primitive-0.1.38.dist-info/entry_points.txt,sha256=p1K8DMCWka5FqLlqP1sPek5Uovy9jq8u51gUsP-z334,48
48
+ primitive-0.1.38.dist-info/licenses/LICENSE.txt,sha256=B8kmQMJ2sxYygjCLBk770uacaMci4mPSoJJ8WoDBY_c,1098
49
+ primitive-0.1.38.dist-info/RECORD,,
primitive/sim/vcd.py DELETED
@@ -1,761 +0,0 @@
1
- """Read Value Change Dump (VCD) files.
2
-
3
- Adapted from Western Digital's PyVCD: https://github.com/westerndigitalcorporation/pyvcd
4
-
5
- The primary interface is the :func:`tokenize()` generator function,
6
- parses a binary VCD stream, yielding tokens as they are encountered.
7
-
8
- .. code::
9
-
10
- >>> import io
11
- >>> from vcd.reader import TokenKind, tokenize
12
- >>> vcd = b"$date today $end $timescale 1 ns $end"
13
- >>> tokens = tokenize(io.BytesIO(vcd))
14
- >>> token = next(tokens)
15
- >>> assert token.kind is TokenKind.DATE
16
- >>> assert token.date == 'today'
17
- >>> token = next(tokens)
18
- >>> assert token.kind is TokenKind.TIMESCALE
19
- >>> assert token.timescale.magnitude.value == 1
20
- >>> assert token.timescale.unit.value == 'ns'
21
-
22
- """
23
-
24
- import io
25
- from dataclasses import dataclass
26
- from enum import Enum
27
- from typing import Iterator, NamedTuple, Optional, Tuple, Union
28
-
29
-
30
- class ScopeType(Enum):
31
- """Valid VCD scope types."""
32
-
33
- begin = "begin"
34
- fork = "fork"
35
- function = "function"
36
- module = "module"
37
- task = "task"
38
-
39
-
40
- class VarType(Enum):
41
- """Valid VCD variable types."""
42
-
43
- event = "event"
44
- integer = "integer"
45
- parameter = "parameter"
46
- real = "real"
47
- realtime = "realtime"
48
- reg = "reg"
49
- supply0 = "supply0"
50
- supply1 = "supply1"
51
- time = "time"
52
- tri = "tri"
53
- triand = "triand"
54
- trior = "trior"
55
- trireg = "trireg"
56
- tri0 = "tri0"
57
- tri1 = "tri1"
58
- wand = "wand"
59
- wire = "wire"
60
- wor = "wor"
61
- string = "string"
62
-
63
- def __str__(self) -> str:
64
- return self.value
65
-
66
-
67
- class TimescaleMagnitude(Enum):
68
- """Valid timescale magnitudes."""
69
-
70
- one = 1
71
- ten = 10
72
- hundred = 100
73
-
74
-
75
- class TimescaleUnit(Enum):
76
- """Valid timescale units."""
77
-
78
- second = "s"
79
- millisecond = "ms"
80
- microsecond = "us"
81
- nanosecond = "ns"
82
- picosecond = "ps"
83
- femtosecond = "fs"
84
-
85
-
86
- class Timescale(NamedTuple):
87
- """Timescale magnitude and unit."""
88
-
89
- magnitude: TimescaleMagnitude
90
- unit: TimescaleUnit
91
-
92
- @classmethod
93
- def from_str(cls, s: str) -> "Timescale":
94
- for unit in TimescaleUnit:
95
- if s == unit.value:
96
- mag = TimescaleMagnitude(1)
97
- break
98
- else:
99
- for mag in reversed(TimescaleMagnitude):
100
- mag_str = str(mag.value)
101
- if s.startswith(mag_str):
102
- unit_str = s[len(mag_str) :].lstrip(" ")
103
- unit = TimescaleUnit(unit_str)
104
- break
105
- else:
106
- raise ValueError(f"Invalid timescale magnitude {s!r}")
107
- return Timescale(mag, unit)
108
-
109
- def __str__(self) -> str:
110
- return f"{self.magnitude.value} {self.unit.value}"
111
-
112
-
113
- class TokenKind(Enum):
114
- """Kinds of VCD tokens."""
115
-
116
- COMMENT = 1
117
- DATE = 2
118
- ENDDEFINITIONS = 3
119
- SCOPE = 4
120
- TIMESCALE = 5
121
- UPSCOPE = 6
122
- VAR = 7
123
- VERSION = 8
124
- DUMPALL = 9
125
- DUMPOFF = 10
126
- DUMPON = 11
127
- DUMPVARS = 12
128
- END = 13
129
- CHANGE_TIME = 14
130
- CHANGE_SCALAR = 15
131
- CHANGE_VECTOR = 16
132
- CHANGE_REAL = 17
133
- CHANGE_STRING = 18
134
-
135
-
136
- class VarDecl(NamedTuple):
137
- """VCD variable declaration.
138
-
139
- Examples::
140
-
141
- $var wire 4 !@# foobar [ 3 : 1 ] $end
142
- $var real 1 aaa foobar $end
143
- $var integer 32 > foobar[8] $end
144
-
145
- """
146
-
147
- type_: VarType #: Type of variable
148
- size: int #: Size, in bits, of variable
149
- id_code: str
150
- """Identifer code of variable.
151
-
152
- This code is used in subsequent value change descriptors
153
- to map-back to this variable declaration."""
154
-
155
- reference: str
156
- """Reference name of variable.
157
-
158
- This human-readable name typically corresponds to the name of a
159
- variable in the model that output the VCD."""
160
-
161
- bit_index: Union[None, int, Tuple[int, int]]
162
- """Optional range of bits to select from the variable.
163
-
164
- May select a single bit index, e.g. ``ref [ 3 ]``. Or a range of
165
- bits, e.g. from ``ref [ 7 : 3 ]`` (MSB index then LSB index)."""
166
-
167
- @property
168
- def ref_str(self) -> str:
169
- if self.bit_index is None:
170
- return self.reference
171
- elif isinstance(self.bit_index, int):
172
- return f"{self.reference}[{self.bit_index}]"
173
- else:
174
- return f"{self.reference}[{self.bit_index[0]}:{self.bit_index[1]}]"
175
-
176
-
177
- class ScopeDecl(NamedTuple):
178
- """VCD scope declaration.
179
-
180
- Examples::
181
-
182
- $scope module Foo $end
183
- $scope
184
- fork alpha_beta
185
- $end
186
-
187
- """
188
-
189
- type_: ScopeType #: Type of scope
190
- ident: str #: Scope name
191
-
192
-
193
- class VectorChange(NamedTuple):
194
- """Vector value change descriptor.
195
-
196
- A vector value consists of multiple 4-state values, where the four
197
- states are 0, 1, X, and Z. When a vector value consists entirely
198
- of 0 and 1 states, :attr:`value` will be an int. Otherwise
199
- :attr:`value` will be a str.
200
-
201
- """
202
-
203
- id_code: str #: Identifier code of associated variable.
204
- value: Union[int, str] #: New value of associated vector variable.
205
-
206
-
207
- class RealChange(NamedTuple):
208
- """Real value (floating point) change descriptor."""
209
-
210
- id_code: str #: Identifier code of associated variable.
211
- value: float #: New value of associated real variable.
212
-
213
-
214
- class ScalarChange(NamedTuple):
215
- """Scalar value change descriptor.
216
-
217
- A scalar is a single 4-state value. The value is one of '0', '1',
218
- 'X', or 'Z'.
219
-
220
- """
221
-
222
- id_code: str #: Identifier code of associated variable.
223
- value: str #: New value of associated scalar variable.
224
-
225
-
226
- class StringChange(NamedTuple):
227
- """String value change descriptor.
228
-
229
- Strings are VCD extension supported by GTKWave.
230
-
231
- """
232
-
233
- id_code: str #: Identifier code of associated variable.
234
- value: str #: New value of associated string variable.
235
-
236
-
237
- class Location(NamedTuple):
238
- """Describe location within VCD stream/file."""
239
-
240
- line: int #: Line number
241
- column: int #: Column number
242
-
243
-
244
- class Span(NamedTuple):
245
- """Describe location span within VCD stream/file."""
246
-
247
- start: Location #: Start of span
248
- end: Location #: End of span
249
-
250
-
251
- class Token(NamedTuple):
252
- """VCD token yielded from :func:`tokenize()`.
253
-
254
- These are relatively high-level tokens insofar as each token fully
255
- captures an entire VCD declaration, command, or change descriptor.
256
-
257
- The :attr:`kind` attribute determines the :attr:`data` type. Various
258
- kind-specific properties provide runtime type-checked access to the
259
- kind-specific data.
260
-
261
- .. Note::
262
-
263
- The :attr:`data` attribute may be accessed directly to avoid
264
- runtime type checks and thus achieve better runtime performance
265
- versus accessing kind-specific properties such as
266
- :attr:`scalar_change`.
267
-
268
- """
269
-
270
- kind: TokenKind
271
- "The kind of token."
272
-
273
- span: Span
274
- "The start and end location of the token within the file/stream."
275
-
276
- data: Union[
277
- None, # $enddefinitions $upscope $dump* $end
278
- int, # time change
279
- str, # $comment, $date, $version
280
- ScopeDecl, # $scope
281
- Timescale, # $timescale
282
- VarDecl, # $var
283
- ScalarChange,
284
- VectorChange,
285
- RealChange,
286
- StringChange,
287
- ]
288
- "Data associated with the token. The data type depends on :attr:`kind`."
289
-
290
- @property
291
- def comment(self) -> str:
292
- """Unstructured text from a ``$comment`` declaration."""
293
- assert self.kind is TokenKind.COMMENT
294
- assert isinstance(self.data, str)
295
- return self.data
296
-
297
- @property
298
- def date(self) -> str:
299
- """Unstructured text from a ``$date`` declaration."""
300
- assert self.kind is TokenKind.DATE
301
- assert isinstance(self.data, str)
302
- return self.data
303
-
304
- @property
305
- def scope(self) -> ScopeDecl:
306
- """Scope type and identifier from ``$scope`` declaration."""
307
- assert self.kind is TokenKind.SCOPE
308
- assert isinstance(self.data, ScopeDecl)
309
- return self.data
310
-
311
- @property
312
- def timescale(self) -> Timescale:
313
- """Magnitude and unit from ``$timescale`` declaration."""
314
- assert self.kind is TokenKind.TIMESCALE
315
- assert isinstance(self.data, Timescale)
316
- return self.data
317
-
318
- @property
319
- def var(self) -> VarDecl:
320
- """Details from a ``$var`` declaration."""
321
- assert self.kind is TokenKind.VAR
322
- assert isinstance(self.data, VarDecl)
323
- return self.data
324
-
325
- @property
326
- def version(self) -> str:
327
- """Unstructured text from a ``$version`` declaration."""
328
- assert self.kind is TokenKind.VERSION
329
- assert isinstance(self.data, str)
330
- return self.data
331
-
332
- @property
333
- def time_change(self) -> int:
334
- """Simulation time change."""
335
- assert self.kind is TokenKind.CHANGE_TIME
336
- assert isinstance(self.data, int)
337
- return self.data
338
-
339
- @property
340
- def scalar_change(self) -> ScalarChange:
341
- """Scalar value change descriptor."""
342
- assert self.kind is TokenKind.CHANGE_SCALAR
343
- assert isinstance(self.data, ScalarChange)
344
- return self.data
345
-
346
- @property
347
- def vector_change(self) -> VectorChange:
348
- """Vector value change descriptor."""
349
- assert self.kind is TokenKind.CHANGE_VECTOR
350
- assert isinstance(self.data, VectorChange)
351
- return self.data
352
-
353
- @property
354
- def real_change(self) -> RealChange:
355
- """Real (float) value change descriptor."""
356
- assert self.kind is TokenKind.CHANGE_REAL
357
- assert isinstance(self.data, RealChange)
358
- return self.data
359
-
360
- @property
361
- def string_change(self) -> StringChange:
362
- "String value change descriptor."
363
- assert self.kind is TokenKind.CHANGE_STRING
364
- assert isinstance(self.data, StringChange)
365
- return self.data
366
-
367
-
368
- class VCDParseError(Exception):
369
- """Catch-all error for any VCD parsing errors."""
370
-
371
- def __init__(self, loc: Location, msg: str) -> None:
372
- super().__init__(f"{loc.line}:{loc.column}: {msg}")
373
- self.loc = loc
374
- "Location within VCD file where error was detected."
375
-
376
-
377
- HasReadinto = Union[io.BufferedIOBase, io.RawIOBase]
378
-
379
-
380
- def tokenize(stream: HasReadinto, buf_size: Optional[int] = None) -> Iterator[Token]:
381
- """Parse VCD stream into tokens.
382
-
383
- The input stream must be opened in binary mode. E.g. with ``open(path, 'rb')``.
384
-
385
- """
386
- if buf_size is None:
387
- buf_size = io.DEFAULT_BUFFER_SIZE
388
-
389
- s = _TokenizerState(stream, bytearray(buf_size))
390
-
391
- try:
392
- while True:
393
- s.advance()
394
- yield _parse_token(s)
395
- except StopIteration:
396
- return
397
-
398
-
399
- @dataclass
400
- class _TokenizerState:
401
- stream: HasReadinto
402
- buf: bytearray
403
- pos: int = 0
404
- end: int = 0
405
- lineno: int = 1
406
- column: int = 0
407
-
408
- @property
409
- def loc(self) -> Location:
410
- return Location(self.lineno, self.column)
411
-
412
- def span(self, start: Location) -> Span:
413
- return Span(start, self.loc)
414
-
415
- def advance(self, raise_on_eof: bool = True) -> int:
416
- if self.pos < self.end:
417
- self.pos += 1
418
- else:
419
- n = self.stream.readinto(self.buf)
420
- if n:
421
- self.end = n - 1
422
- self.pos = 0
423
- elif raise_on_eof:
424
- raise StopIteration()
425
- else:
426
- return 0
427
- c = self.buf[self.pos]
428
- if c == 10:
429
- self.lineno += 1
430
- self.column = 1
431
- else:
432
- self.column += 1
433
- return self.buf[self.pos]
434
-
435
- def skip_ws(self) -> int:
436
- c = self.buf[self.pos]
437
- while c == 32 or 9 <= c <= 13:
438
- c = self.advance()
439
- return c
440
-
441
- def take_ws_after_kw(self, kw: str) -> None:
442
- if _is_ws(self.buf[self.pos]):
443
- self.advance()
444
- else:
445
- raise VCDParseError(self.loc, f"Expected whitespace after identifier ${kw}")
446
-
447
- def take_decimal(self) -> int:
448
- digits = []
449
- c = self.buf[self.pos]
450
- while 48 <= c <= 57: # '0' <= c <= '9'
451
- digits.append(c)
452
- c = self.advance(raise_on_eof=False)
453
- if digits:
454
- return int(bytes(digits))
455
- else:
456
- raise VCDParseError(self.loc, "Expected decimal value")
457
-
458
- def take_id_code(self) -> str:
459
- printables = []
460
- c = self.buf[self.pos]
461
- while 33 <= c <= 126: # printable character
462
- printables.append(c)
463
- c = self.advance(raise_on_eof=False)
464
- if printables:
465
- return bytes(printables).decode("ascii")
466
- else:
467
- raise VCDParseError(self.loc, "Expected id code")
468
-
469
- def take_identifier(self) -> str:
470
- identifier = []
471
- c = self.buf[self.pos]
472
-
473
- # Identifiers must start with letter or underscore
474
- if (
475
- 65 <= c <= 90 # 'A' <= c <= 'Z'
476
- or 97 <= c <= 122 # 'a' - 'z'
477
- or c == 95 # '_'
478
- ):
479
- identifier.append(c)
480
- c = self.advance()
481
- elif c == 92:
482
- # If we encounter an escaped identifier,
483
- # just remove the escape character
484
- # NOTE: may need to revisit this
485
- c = self.advance()
486
- else:
487
- raise VCDParseError(self.loc, "Identifier must start with a-zA-Z_")
488
-
489
- while (
490
- 48 <= c <= 57 # '0' - '9'
491
- or 65 <= c <= 90 # 'A' - 'Z'
492
- or 97 <= c <= 122 # 'a' - 'z'
493
- or c == 95 # '_'
494
- or c == 36 # '$'
495
- or c == 46 # '.' not in spec, but seen in the wild
496
- or c == 40 # '(' - produced by cva6 core
497
- or c == 41 # ')' - produced by cva6 core
498
- or c == 91 # '[' - produced by dumping packed arrays
499
- or c == 93 # ']' - produced by dumping packed arrays
500
- ):
501
- identifier.append(c)
502
- c = self.advance(raise_on_eof=False)
503
-
504
- return bytes(identifier).decode("ascii")
505
-
506
- def take_bit_index(self) -> Union[int, Tuple[int, int]]:
507
- self.skip_ws()
508
- index0 = self.take_decimal()
509
- index1: Optional[int]
510
-
511
- c = self.skip_ws()
512
- if c == 58: # ':'
513
- self.advance()
514
- self.skip_ws()
515
- index1 = self.take_decimal()
516
- else:
517
- index1 = None
518
-
519
- c = self.skip_ws()
520
- if c == 93: # ']'
521
- self.advance(raise_on_eof=False)
522
- if index1 is None:
523
- return index0
524
- else:
525
- return (index0, index1)
526
- else:
527
- raise VCDParseError(self.loc, 'Expected bit index to terminate with "]"')
528
-
529
- def take_to_end(self) -> str:
530
- chars = [
531
- self.buf[self.pos], # $
532
- self.advance(), # --> e
533
- self.advance(), # --> n
534
- self.advance(), # --> d
535
- ]
536
- while not ( # Check for 'd' 'n' 'e' '$'
537
- chars[-1] == 100
538
- and chars[-2] == 110
539
- and chars[-3] == 101
540
- and chars[-4] == 36
541
- ):
542
- chars.append(self.advance())
543
-
544
- if len(chars) > 4 and not _is_ws(chars[-5]):
545
- loc = Location(self.lineno, self.column - min(len(chars), 5))
546
- raise VCDParseError(loc, "Expected whitespace before $end")
547
-
548
- return bytes(chars[:-5]).decode("ascii")
549
-
550
- def take_end(self) -> None:
551
- if (
552
- self.skip_ws() != 36 # '$'
553
- or self.advance() != 101 # 'e'
554
- or self.advance() != 110 # 'n'
555
- or self.advance() != 100 # 'd'
556
- ):
557
- raise VCDParseError(self.loc, "Expected $end")
558
-
559
-
560
- def _is_ws(c: int) -> bool:
561
- return c == 32 or 9 <= c <= 13
562
-
563
-
564
- def _parse_token(s: _TokenizerState) -> Token:
565
- c = s.skip_ws()
566
- start = s.loc
567
- if c == 35: # '#'
568
- # Parse time change
569
- s.advance()
570
- time = s.take_decimal()
571
- return Token(TokenKind.CHANGE_TIME, s.span(start), time)
572
- elif c == 48 or c == 49 or c == 122 or c == 90 or c == 120 or c == 88:
573
- # c in '01zZxX'
574
- # Parse scalar change
575
- scalar_value = chr(c)
576
- s.advance()
577
- id_code = s.take_id_code()
578
- return Token(
579
- TokenKind.CHANGE_SCALAR, s.span(start), ScalarChange(id_code, scalar_value)
580
- )
581
- elif c == 66 or c == 98: # 'B' or 'b'
582
- # Parse vector change
583
- vector = []
584
- c = s.advance()
585
- while c == 48 or c == 49: # '0' or '1'
586
- vector.append(c)
587
- c = s.advance()
588
- vector_value: Union[int, str]
589
-
590
- if c == 122 or c == 90 or c == 120 or c == 88: # c in 'zZxX'
591
- vector.append(c)
592
- c = s.advance()
593
- while (
594
- c == 48 or c == 49 or c == 122 or c == 90 or c == 120 or c == 88
595
- ): # c in '01zZxX'
596
- vector.append(c)
597
- c = s.advance()
598
- vector_value = bytes(vector).decode("ascii")
599
- else:
600
- vector_value = int(bytes(vector), 2)
601
-
602
- if not _is_ws(c):
603
- raise VCDParseError(s.loc, "Expected whitespace after vector value")
604
-
605
- s.skip_ws()
606
-
607
- id_code = s.take_id_code()
608
-
609
- return Token(
610
- TokenKind.CHANGE_VECTOR, s.span(start), VectorChange(id_code, vector_value)
611
- )
612
- elif c == 82 or c == 114: # 'R' or 'r'
613
- # Parse real change
614
- real_digits = []
615
- c = s.advance()
616
-
617
- while not _is_ws(c):
618
- real_digits.append(c)
619
- c = s.advance()
620
-
621
- try:
622
- real = float(bytes(real_digits))
623
- except ValueError:
624
- real_str = bytes(real_digits).decode("ascii")
625
- raise VCDParseError(start, f"Expected real value, got: {real_str}")
626
-
627
- s.skip_ws()
628
-
629
- id_code = s.take_id_code()
630
-
631
- return Token(TokenKind.CHANGE_REAL, s.span(start), RealChange(id_code, real))
632
- elif c == 83 or c == 115: # 'S' or 's'
633
- chars = []
634
- c = s.advance()
635
- while not _is_ws(c):
636
- chars.append(c)
637
- c = s.advance()
638
- s.skip_ws()
639
- id_code = s.take_id_code()
640
- string_value = bytes(chars).decode("ascii")
641
- return Token(
642
- TokenKind.CHANGE_STRING, s.span(start), StringChange(id_code, string_value)
643
- )
644
- elif c == 36: # '$'
645
- s.advance()
646
- kw = s.take_identifier()
647
-
648
- if kw == "comment":
649
- s.take_ws_after_kw(kw)
650
- comment = s.take_to_end()
651
- return Token(TokenKind.COMMENT, s.span(start), comment)
652
- elif kw == "date":
653
- s.take_ws_after_kw(kw)
654
- date_str = s.take_to_end()
655
- return Token(TokenKind.DATE, s.span(start), date_str)
656
- elif kw == "enddefinitions":
657
- s.take_ws_after_kw(kw)
658
- s.take_end()
659
- return Token(TokenKind.ENDDEFINITIONS, s.span(start), None)
660
- elif kw == "scope":
661
- s.take_ws_after_kw(kw)
662
- s.skip_ws()
663
- identifier = s.take_identifier()
664
- try:
665
- scope_type = ScopeType(identifier)
666
- except ValueError:
667
- raise VCDParseError(s.loc, f"Invalid $scope type: {identifier}")
668
-
669
- s.skip_ws()
670
-
671
- scope_ident = s.take_identifier()
672
-
673
- s.take_end()
674
-
675
- scope_decl = ScopeDecl(scope_type, scope_ident)
676
-
677
- return Token(TokenKind.SCOPE, s.span(start), scope_decl)
678
- elif kw == "timescale":
679
- s.take_ws_after_kw(kw)
680
- s.skip_ws()
681
- mag_int = s.take_decimal()
682
-
683
- try:
684
- magnitude = TimescaleMagnitude(mag_int)
685
- except ValueError:
686
- valid_magnitudes = ", ".join(str(m.value) for m in TimescaleMagnitude)
687
- raise VCDParseError(
688
- s.loc,
689
- f"Invalid $timescale magnitude: {mag_int}. "
690
- f"Must be one of: {valid_magnitudes}.",
691
- )
692
-
693
- s.skip_ws()
694
- unit_str = s.take_identifier()
695
- try:
696
- unit = TimescaleUnit(unit_str)
697
- except ValueError:
698
- valid_units = ", ".join(u.value for u in TimescaleUnit)
699
- raise VCDParseError(
700
- s.loc,
701
- f"Invalid $timescale unit: {unit_str}. "
702
- f"Must be one of: {valid_units}.",
703
- )
704
-
705
- s.take_end()
706
-
707
- timescale = Timescale(magnitude, unit)
708
- return Token(TokenKind.TIMESCALE, s.span(start), timescale)
709
- elif kw == "upscope":
710
- s.take_ws_after_kw(kw)
711
- s.take_end()
712
- return Token(TokenKind.UPSCOPE, s.span(start), None)
713
- elif kw == "var":
714
- s.take_ws_after_kw(kw)
715
- s.skip_ws()
716
- type_str = s.take_identifier()
717
- try:
718
- type_ = VarType(type_str)
719
- except ValueError:
720
- valid_types = ", ".join(t.value for t in VarType)
721
- raise VCDParseError(
722
- s.loc,
723
- f"Invalid $var type: {type_str}. Must be one of: {valid_types}",
724
- )
725
-
726
- s.skip_ws()
727
- size = s.take_decimal()
728
- s.skip_ws()
729
- id_code = s.take_id_code()
730
- s.skip_ws()
731
- ident = s.take_identifier()
732
-
733
- bit_index: Union[None, int, Tuple[int, int]]
734
- c = s.skip_ws()
735
- if c == 91: # '['
736
- s.advance()
737
- bit_index = s.take_bit_index()
738
- else:
739
- bit_index = None
740
-
741
- s.take_end()
742
- var_decl = VarDecl(type_, size, id_code, ident, bit_index)
743
- return Token(TokenKind.VAR, s.span(start), var_decl)
744
- elif kw == "version":
745
- s.take_ws_after_kw(kw)
746
- version = s.take_to_end()
747
- return Token(TokenKind.VERSION, s.span(start), version)
748
- elif kw == "dumpall":
749
- return Token(TokenKind.DUMPALL, s.span(start), None)
750
- elif kw == "dumpoff":
751
- return Token(TokenKind.DUMPOFF, s.span(start), None)
752
- elif kw == "dumpon":
753
- return Token(TokenKind.DUMPON, s.span(start), None)
754
- elif kw == "dumpvars":
755
- return Token(TokenKind.DUMPVARS, s.span(start), None)
756
- elif kw == "end":
757
- return Token(TokenKind.END, s.span(start), None)
758
- else:
759
- raise VCDParseError(s.loc, f"invalid keyword ${kw}")
760
- else:
761
- raise VCDParseError(s.loc, f"confused: {chr(c)}")