primitive 0.1.14__py3-none-any.whl → 0.1.16__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 +1 -1
- primitive/agent/actions.py +53 -22
- primitive/cli.py +2 -0
- primitive/client.py +2 -2
- primitive/daemons/launch_agents.py +11 -1
- primitive/daemons/launch_service.py +10 -1
- primitive/jobs/actions.py +4 -0
- primitive/lint/actions.py +1 -1
- primitive/projects/actions.py +0 -1
- primitive/sim/actions.py +190 -0
- primitive/sim/commands.py +19 -0
- primitive/sim/vcd.py +761 -0
- {primitive-0.1.14.dist-info → primitive-0.1.16.dist-info}/METADATA +1 -1
- {primitive-0.1.14.dist-info → primitive-0.1.16.dist-info}/RECORD +18 -16
- primitive/simulations/actions.py +0 -48
- /primitive/{simulations → sim}/__init__.py +0 -0
- {primitive-0.1.14.dist-info → primitive-0.1.16.dist-info}/WHEEL +0 -0
- {primitive-0.1.14.dist-info → primitive-0.1.16.dist-info}/entry_points.txt +0 -0
- {primitive-0.1.14.dist-info → primitive-0.1.16.dist-info}/licenses/LICENSE.txt +0 -0
primitive/__about__.py
CHANGED
primitive/agent/actions.py
CHANGED
@@ -65,28 +65,59 @@ class Agent(BaseAction):
|
|
65
65
|
)
|
66
66
|
)
|
67
67
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
68
|
+
match job_run["job"]["slug"]:
|
69
|
+
case "lint":
|
70
|
+
logger.debug("Executing Lint Job")
|
71
|
+
|
72
|
+
self.primitive.jobs.job_run_update(
|
73
|
+
job_run["id"], status="request_in_progress"
|
74
|
+
)
|
75
|
+
|
76
|
+
result, message = self.primitive.lint.execute(
|
77
|
+
source=downloaded_git_repository_dir
|
78
|
+
)
|
79
|
+
if result:
|
80
|
+
conclusion = "success"
|
81
|
+
else:
|
82
|
+
conclusion = "failure"
|
83
|
+
self.primitive.jobs.job_run_update(
|
84
|
+
job_run["id"],
|
85
|
+
status="request_completed",
|
86
|
+
conclusion=conclusion,
|
87
|
+
stdout=message,
|
88
|
+
)
|
89
|
+
|
90
|
+
logger.debug("Lint Job Completed")
|
91
|
+
case "sim":
|
92
|
+
logger.debug("Executing Sim Job")
|
93
|
+
|
94
|
+
self.primitive.job.job_run_update(
|
95
|
+
job_run["id"], status="request_in_progress"
|
96
|
+
)
|
97
|
+
|
98
|
+
result, message = self.primitive.sim.execute(
|
99
|
+
source=downloaded_git_repository_dir,
|
100
|
+
cmd=(
|
101
|
+
"make",
|
102
|
+
"all",
|
103
|
+
), # TODO: Change this to use container args container cmd
|
104
|
+
)
|
105
|
+
|
106
|
+
# Attempt artifact collection
|
107
|
+
self.primitive.sim.collect_artifacts(
|
108
|
+
source=downloaded_git_repository_dir
|
109
|
+
)
|
110
|
+
|
111
|
+
if result:
|
112
|
+
conclusion = "success"
|
113
|
+
else:
|
114
|
+
conclusion = "failure"
|
115
|
+
self.primitive.jobs.job_run_update(
|
116
|
+
job_run["id"],
|
117
|
+
status="request_completed",
|
118
|
+
conclusion=conclusion,
|
119
|
+
stdout=message,
|
120
|
+
)
|
90
121
|
|
91
122
|
sleep(5)
|
92
123
|
except KeyboardInterrupt:
|
primitive/cli.py
CHANGED
@@ -13,6 +13,7 @@ from .daemons.commands import cli as daemons_commands
|
|
13
13
|
from .jobs.commands import cli as jobs_commands
|
14
14
|
from .organizations.commands import cli as organizations_commands
|
15
15
|
from .projects.commands import cli as projects_commands
|
16
|
+
from .sim.commands import cli as sim_commands
|
16
17
|
|
17
18
|
|
18
19
|
@click.group()
|
@@ -67,6 +68,7 @@ cli.add_command(daemons_commands, "daemons")
|
|
67
68
|
cli.add_command(jobs_commands, "jobs")
|
68
69
|
cli.add_command(organizations_commands, "organizations")
|
69
70
|
cli.add_command(projects_commands, "projects")
|
71
|
+
cli.add_command(sim_commands, "sim")
|
70
72
|
|
71
73
|
if __name__ == "__main__":
|
72
74
|
cli(obj={})
|
primitive/client.py
CHANGED
@@ -4,7 +4,7 @@ from .projects.actions import Projects
|
|
4
4
|
from .graphql.sdk import create_session
|
5
5
|
from .utils.config import read_config_file
|
6
6
|
from .files.actions import Files
|
7
|
-
from .
|
7
|
+
from .sim.actions import Sim
|
8
8
|
from .hardware.actions import Hardware
|
9
9
|
from .lint.actions import Lint
|
10
10
|
from .agent.actions import Agent
|
@@ -61,7 +61,7 @@ class Primitive:
|
|
61
61
|
self.projects: Projects = Projects(self)
|
62
62
|
self.jobs: Jobs = Jobs(self)
|
63
63
|
self.files: Files = Files(self)
|
64
|
-
self.
|
64
|
+
self.sim: Sim = Sim(self)
|
65
65
|
self.hardware: Hardware = Hardware(self)
|
66
66
|
self.lint: Lint = Lint(self)
|
67
67
|
self.agent: Agent = Agent(self)
|
@@ -6,6 +6,7 @@ HOME_DIRECTORY = Path.home()
|
|
6
6
|
CURRENT_USER = str(HOME_DIRECTORY.expanduser()).lstrip("/Users/")
|
7
7
|
|
8
8
|
PRIMITIVE_BINARY_PATH = Path(HOME_DIRECTORY / ".pyenv" / "shims" / "primitive")
|
9
|
+
|
9
10
|
PRIMITIVE_LAUNCH_AGENT_FILEPATH = Path(
|
10
11
|
HOME_DIRECTORY / "Library" / "LaunchAgents" / "tech.primitive.agent.plist"
|
11
12
|
)
|
@@ -78,6 +79,15 @@ def populate_fresh_launch_agent():
|
|
78
79
|
PRIMITIVE_LAUNCH_AGENT_FILEPATH.parent.mkdir(parents=True, exist_ok=True)
|
79
80
|
PRIMITIVE_LAUNCH_AGENT_FILEPATH.touch()
|
80
81
|
|
82
|
+
found_primitive_binary_path = PRIMITIVE_BINARY_PATH
|
83
|
+
if not PRIMITIVE_BINARY_PATH.exists():
|
84
|
+
result = subprocess.run(["which", "primitive"], capture_output=True)
|
85
|
+
if result.returncode == 0:
|
86
|
+
found_primitive_binary_path = result.stdout.decode().rstrip("\n")
|
87
|
+
else:
|
88
|
+
print("primitive binary not found")
|
89
|
+
return False
|
90
|
+
|
81
91
|
PRIMITIVE_LAUNCH_AGENT_FILEPATH.write_text(
|
82
92
|
f"""<?xml version="1.0" encoding="UTF-8"?>
|
83
93
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
@@ -96,7 +106,7 @@ def populate_fresh_launch_agent():
|
|
96
106
|
</array>
|
97
107
|
<key>ProgramArguments</key>
|
98
108
|
<array>
|
99
|
-
<string>{
|
109
|
+
<string>{found_primitive_binary_path}</string>
|
100
110
|
<string>agent</string>
|
101
111
|
</array>
|
102
112
|
<key>RunAtLoad</key>
|
@@ -82,8 +82,17 @@ def populate_service_file():
|
|
82
82
|
"After": "network.target",
|
83
83
|
}
|
84
84
|
|
85
|
+
found_primitive_binary_path = PRIMITIVE_BINARY_PATH
|
86
|
+
if not PRIMITIVE_BINARY_PATH.exists():
|
87
|
+
result = subprocess.run(["which", "primitive"], capture_output=True)
|
88
|
+
if result.returncode == 0:
|
89
|
+
found_primitive_binary_path = result.stdout.decode().rstrip("\n")
|
90
|
+
else:
|
91
|
+
print("primitive binary not found")
|
92
|
+
return False
|
93
|
+
|
85
94
|
config["Service"] = {
|
86
|
-
"ExecStart": f"{
|
95
|
+
"ExecStart": f"{found_primitive_binary_path} agent",
|
87
96
|
"Restart": "always",
|
88
97
|
"StandardError": f"append:{PRIMITIVE_AGENT_LOGS_FILEPATH}",
|
89
98
|
"StandardOutput": f"append:{PRIMITIVE_AGENT_LOGS_FILEPATH}",
|
primitive/jobs/actions.py
CHANGED
primitive/lint/actions.py
CHANGED
@@ -9,7 +9,7 @@ from ..utils.verible import install_verible
|
|
9
9
|
|
10
10
|
class Lint(BaseAction):
|
11
11
|
def execute(self, source: Path = Path.cwd()) -> Tuple[bool, str]:
|
12
|
-
logger.debug("Starting linter for source: {source}")
|
12
|
+
logger.debug(f"Starting linter for source: {source}")
|
13
13
|
files = find_files_for_extension(source, ".sv")
|
14
14
|
if not files:
|
15
15
|
message = "No files found to lint."
|
primitive/projects/actions.py
CHANGED
primitive/sim/actions.py
ADDED
@@ -0,0 +1,190 @@
|
|
1
|
+
from gql import gql
|
2
|
+
from pathlib import Path
|
3
|
+
from primitive.utils.actions import BaseAction
|
4
|
+
from loguru import logger
|
5
|
+
import subprocess
|
6
|
+
from typing import Tuple, List
|
7
|
+
from ..utils.files import find_files_for_extension
|
8
|
+
import os
|
9
|
+
from .vcd import TokenKind, tokenize
|
10
|
+
import io
|
11
|
+
from collections import defaultdict
|
12
|
+
import urllib
|
13
|
+
import json
|
14
|
+
|
15
|
+
|
16
|
+
class Sim(BaseAction):
|
17
|
+
def execute(
|
18
|
+
self, source: Path = Path.cwd(), cmd: Tuple[str] = ["make"]
|
19
|
+
) -> Tuple[bool, str]:
|
20
|
+
logger.debug(f"Starting simulation run for source: {source}")
|
21
|
+
|
22
|
+
os.chdir(source)
|
23
|
+
logger.debug(f"Changed to {source}, starting sim run")
|
24
|
+
try:
|
25
|
+
result = subprocess.run(cmd, capture_output=True, text=True, env=os.environ)
|
26
|
+
except FileNotFoundError:
|
27
|
+
message = f"Did not find {cmd}"
|
28
|
+
logger.error(message)
|
29
|
+
return False, message
|
30
|
+
|
31
|
+
logger.debug("Sim run complete.")
|
32
|
+
|
33
|
+
message = ""
|
34
|
+
if result.stderr:
|
35
|
+
logger.error("\n" + result.stderr)
|
36
|
+
if result.stdout:
|
37
|
+
logger.info("\n" + result.stdout)
|
38
|
+
message = "See above logs for sim output."
|
39
|
+
|
40
|
+
if result.returncode != 0:
|
41
|
+
if not self.primitive.DEBUG:
|
42
|
+
message = result.stderr
|
43
|
+
return False, message
|
44
|
+
else:
|
45
|
+
message = "Sim run successful."
|
46
|
+
|
47
|
+
return True, message
|
48
|
+
|
49
|
+
def upload_file(self, path: Path) -> str:
|
50
|
+
file_upload_response = self.primitive.files.file_upload(
|
51
|
+
path, key_prefix=f"{self.job_run_id}/{str(path.parent)}"
|
52
|
+
)
|
53
|
+
return file_upload_response.json()["data"]["fileUpload"]["id"]
|
54
|
+
|
55
|
+
def collect_artifacts(self, source) -> None:
|
56
|
+
file_ids = []
|
57
|
+
|
58
|
+
# Look for VCD artifacts
|
59
|
+
files = find_files_for_extension(source, ".vcd")
|
60
|
+
for file in files:
|
61
|
+
trace_file_ids = self.generate_timeseries(path=file)
|
62
|
+
file_ids.extend(trace_file_ids)
|
63
|
+
|
64
|
+
logger.debug("Uploading additional artifacts...")
|
65
|
+
files = find_files_for_extension(source, (".xml", ".vcd", ".log", ".history"))
|
66
|
+
for file_path in files:
|
67
|
+
try:
|
68
|
+
file_ids.append(self.upload_file(file_path))
|
69
|
+
except FileNotFoundError:
|
70
|
+
logger.warning(f"{file_path} not found...")
|
71
|
+
|
72
|
+
logger.debug("Updating job run...")
|
73
|
+
if len(file_ids) > 0:
|
74
|
+
job_run_update_response = self.primitive.projects.job_run_update(
|
75
|
+
id=self.job_run_id, file_ids=file_ids
|
76
|
+
)
|
77
|
+
logger.success(job_run_update_response)
|
78
|
+
|
79
|
+
def generate_timeseries(self, path: Path) -> List[str]:
|
80
|
+
logger.debug("Parsing VCD file...")
|
81
|
+
with open(path, "rb") as f:
|
82
|
+
tokens = tokenize(io.BytesIO(f.read()))
|
83
|
+
|
84
|
+
metadata = defaultdict(dict)
|
85
|
+
traces = defaultdict(list)
|
86
|
+
timescale_unit = "s"
|
87
|
+
timescale_magnitude = 1
|
88
|
+
active_module: str = ""
|
89
|
+
time: int = 0
|
90
|
+
|
91
|
+
for token in tokens:
|
92
|
+
match token.kind:
|
93
|
+
case TokenKind.TIMESCALE:
|
94
|
+
timescale_unit = token.data.unit.value
|
95
|
+
timescale_magnitude = token.data.magnitude.value
|
96
|
+
case TokenKind.SCOPE:
|
97
|
+
active_module = token.data.ident
|
98
|
+
case TokenKind.CHANGE_TIME:
|
99
|
+
time = int(token.data)
|
100
|
+
case TokenKind.VAR:
|
101
|
+
var = {
|
102
|
+
"id_code": token.data.id_code,
|
103
|
+
"module": active_module,
|
104
|
+
"var_type": str(token.data.type_),
|
105
|
+
"var_size": token.data.size,
|
106
|
+
"reference": token.data.reference,
|
107
|
+
"bit_index": str(token.data.bit_index),
|
108
|
+
}
|
109
|
+
metadata[token.data.id_code] = var
|
110
|
+
case TokenKind.CHANGE_SCALAR:
|
111
|
+
traces[token.data.id_code].append(
|
112
|
+
(str(time), str(token.data.value))
|
113
|
+
)
|
114
|
+
case TokenKind.CHANGE_VECTOR:
|
115
|
+
traces[token.data.id_code].append(
|
116
|
+
(str(time), str(token.data.value))
|
117
|
+
)
|
118
|
+
|
119
|
+
# Add traces and write files
|
120
|
+
logger.debug("Uploading traces...")
|
121
|
+
trace_file_ids = []
|
122
|
+
for id_code, timeseries in traces.items():
|
123
|
+
|
124
|
+
def hashed(id_code):
|
125
|
+
return urllib.parse.quote_plus(id_code, safe="")
|
126
|
+
|
127
|
+
file_path = path.parent / f"{hashed(id_code)}.vcd.json"
|
128
|
+
with open(file_path, "w") as f:
|
129
|
+
f.write(json.dumps(timeseries))
|
130
|
+
|
131
|
+
trace_file_id = self.upload_file(file_path)
|
132
|
+
trace_file_ids.append(trace_file_id)
|
133
|
+
|
134
|
+
self.trace_create(
|
135
|
+
id_code=id_code,
|
136
|
+
module=metadata[id_code]["module"],
|
137
|
+
var_type=metadata[id_code]["var_type"],
|
138
|
+
var_size=metadata[id_code]["var_size"],
|
139
|
+
reference=metadata[id_code]["reference"],
|
140
|
+
bit_index=metadata[id_code]["bit_index"],
|
141
|
+
timescale_unit=timescale_unit,
|
142
|
+
timescale_magnitude=timescale_magnitude,
|
143
|
+
organization=self.organization_id,
|
144
|
+
file=trace_file_id,
|
145
|
+
job_run=self.job_run_id,
|
146
|
+
)
|
147
|
+
|
148
|
+
return trace_file_ids
|
149
|
+
|
150
|
+
def trace_create(
|
151
|
+
self,
|
152
|
+
id_code: str,
|
153
|
+
module: str,
|
154
|
+
var_type: str,
|
155
|
+
var_size: int,
|
156
|
+
reference: str,
|
157
|
+
bit_index: str,
|
158
|
+
timescale_unit: str,
|
159
|
+
timescale_magnitude: int,
|
160
|
+
organization: str,
|
161
|
+
file: str,
|
162
|
+
job_run: str,
|
163
|
+
):
|
164
|
+
mutation = gql(
|
165
|
+
"""
|
166
|
+
mutation createTrace($input: TraceCreateInput!) {
|
167
|
+
traceCreate(input: $input) {
|
168
|
+
... on Trace {
|
169
|
+
id
|
170
|
+
}
|
171
|
+
}
|
172
|
+
}
|
173
|
+
"""
|
174
|
+
)
|
175
|
+
input = {
|
176
|
+
"idCode": id_code,
|
177
|
+
"module": module,
|
178
|
+
"varType": var_type,
|
179
|
+
"varSize": var_size,
|
180
|
+
"reference": reference,
|
181
|
+
"bitIndex": bit_index,
|
182
|
+
"timescaleUnit": timescale_unit,
|
183
|
+
"timescaleMagnitude": timescale_magnitude,
|
184
|
+
"organization": organization,
|
185
|
+
"file": file,
|
186
|
+
"jobRun": job_run,
|
187
|
+
}
|
188
|
+
variables = {"input": input}
|
189
|
+
result = self.primitive.session.execute(mutation, variable_values=variables)
|
190
|
+
return result
|
@@ -0,0 +1,19 @@
|
|
1
|
+
import click
|
2
|
+
from pathlib import Path
|
3
|
+
import typing
|
4
|
+
from typing import Tuple
|
5
|
+
from ..utils.printer import print_result
|
6
|
+
|
7
|
+
if typing.TYPE_CHECKING:
|
8
|
+
from ..client import Primitive
|
9
|
+
|
10
|
+
|
11
|
+
@click.command("sim")
|
12
|
+
@click.pass_context
|
13
|
+
@click.argument("cmd", nargs=-1, required=True)
|
14
|
+
@click.option("--source", type=click.Path(exists=True), default=".")
|
15
|
+
def cli(context, source: str, cmd: Tuple[str]) -> None:
|
16
|
+
"""Sim"""
|
17
|
+
primitive: Primitive = context.obj.get("PRIMITIVE")
|
18
|
+
result, message = primitive.sim.execute(source=Path(source), cmd=cmd)
|
19
|
+
print_result(message=message, context=context)
|
primitive/sim/vcd.py
ADDED
@@ -0,0 +1,761 @@
|
|
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)}")
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: primitive
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.16
|
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,16 +1,16 @@
|
|
1
|
-
primitive/__about__.py,sha256=
|
1
|
+
primitive/__about__.py,sha256=HhQ4GDjamHIwuTA3ry_5pOfBegiEFbQZ7WGCiJd3A00,129
|
2
2
|
primitive/__init__.py,sha256=bwKdgggKNVssJFVPfKSxqFMz4IxSr54WWbmiZqTMPNI,106
|
3
|
-
primitive/cli.py,sha256=
|
4
|
-
primitive/client.py,sha256=
|
5
|
-
primitive/agent/actions.py,sha256=
|
3
|
+
primitive/cli.py,sha256=VQPSewC6ouGdEG9W1gllawGJTydpOY0Lzg7LURXcqQg,2374
|
4
|
+
primitive/client.py,sha256=SFPG4H2wJao8euGdnYp-l7dk_fDpWeVn2aT2WNJUAqo,2370
|
5
|
+
primitive/agent/actions.py,sha256=e0xqQm0EeAqwhS45JrsxjNdT_JPMAgolav62jsHjdVM,5131
|
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
|
9
9
|
primitive/auth/commands.py,sha256=JahUq0E2e7Xa-FX1WEUv7TgM6ieDvNH4VwRRtxAW7HE,2340
|
10
10
|
primitive/daemons/actions.py,sha256=Nt3yNtbBhen0jK4sRsH_N7AP3UBuyL48VaUhtC7wYq8,2015
|
11
11
|
primitive/daemons/commands.py,sha256=-Muh-6ib4uAVtPn_67AcMrDwuCwYlCnRQozCi2Xurmk,1726
|
12
|
-
primitive/daemons/launch_agents.py,sha256=
|
13
|
-
primitive/daemons/launch_service.py,sha256=
|
12
|
+
primitive/daemons/launch_agents.py,sha256=qovt32gwpjGDd82z_SY5EGCUjaUyNA49pZFajZsw3eE,4796
|
13
|
+
primitive/daemons/launch_service.py,sha256=GZYUQfScpFAxma_pCZH4nRSVtrynTQEfeAvn9eJK2PE,5586
|
14
14
|
primitive/files/actions.py,sha256=f4JN3QFB2WXw-0JpnE-4-movnqtvXIpCrGd_9pdkeW4,2624
|
15
15
|
primitive/files/commands.py,sha256=DDizo3xJnU3KLUBTMeeM72viVpnJinLwxs84tmqKhqo,810
|
16
16
|
primitive/git/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -20,17 +20,19 @@ primitive/graphql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU
|
|
20
20
|
primitive/graphql/sdk.py,sha256=BhCGmDtc4sNnH8CxbQSJyFwOZ-ZSqMtjsxMB3JRBhPw,1456
|
21
21
|
primitive/hardware/actions.py,sha256=Ea3_2E3F_3WapV60g_mOIcpXhadoknwihR7slXyUWtk,18840
|
22
22
|
primitive/hardware/commands.py,sha256=QE7LLeFdfOqlvz3JwdwJJRZAY3fHI1zB9kYmmDajpq0,1477
|
23
|
-
primitive/jobs/actions.py,sha256=
|
23
|
+
primitive/jobs/actions.py,sha256=JsoVh7XVPX7AVb73fKwxY_UsMcFt4YWgMhveyJf56oc,7622
|
24
24
|
primitive/jobs/commands.py,sha256=MxPCkBEYW_eLNqgCRYeyj7ZcLOFAWfpVZlqDR2Y_S0o,830
|
25
|
-
primitive/lint/actions.py,sha256=
|
25
|
+
primitive/lint/actions.py,sha256=fGnNlcD_B-E0SvRUTvTdSlTm2kCQUTrlBLB0mt1sXKM,2268
|
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
|
29
29
|
primitive/projects/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
30
|
-
primitive/projects/actions.py,sha256=
|
30
|
+
primitive/projects/actions.py,sha256=xhebDUMN9DXWvngWJyJkiijghbZwffy-JIPSsOg8agE,2061
|
31
31
|
primitive/projects/commands.py,sha256=Fqqgpi4cm6zOgkHK--0F0hiiIj32BmgZ-h1MydmWwdE,464
|
32
|
-
primitive/
|
33
|
-
primitive/
|
32
|
+
primitive/sim/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
33
|
+
primitive/sim/actions.py,sha256=6gAHotyD3QjglwQKdXsBK0cPuJmOAaDI0DlJyh9KpFY,6513
|
34
|
+
primitive/sim/commands.py,sha256=8PaOfL1MO6qxTn7mNVRnBU1X2wa3gk_mlbAhBW6MnI0,591
|
35
|
+
primitive/sim/vcd.py,sha256=mAbGnKWM0qzIUMkuSmO0p3sU25kOqbl31mvCsDSrXeM,22221
|
34
36
|
primitive/utils/actions.py,sha256=HOFrmM3-0A_A3NS84MqrZ6JmQEiiPSoDqEeuu6b_qfQ,196
|
35
37
|
primitive/utils/config.py,sha256=DlFM5Nglo22WPtbpZSVtH7NX-PTMaKYlcrUE7GPRG4c,1058
|
36
38
|
primitive/utils/files.py,sha256=Yv__bQes3YIlzhOT9kVxtYhoA5CmUjPSvphl9PZ41k4,867
|
@@ -39,8 +41,8 @@ primitive/utils/memory_size.py,sha256=4xfha21kW82nFvOTtDFx9Jk2ZQoEhkfXii-PGNTpIU
|
|
39
41
|
primitive/utils/printer.py,sha256=f1XUpqi5dkTL3GWvYRUGlSwtj2IxU1q745T4Fxo7Tn4,370
|
40
42
|
primitive/utils/shell.py,sha256=-7UjQaBqSGHzEEyX8pNjeYFFP0P3lVnDV0OkgPz1qHU,1050
|
41
43
|
primitive/utils/verible.py,sha256=QYczN1IvxODfj4jeq0nqjFuF0Oi0Zdx-Q32ySOJgcw8,2205
|
42
|
-
primitive-0.1.
|
43
|
-
primitive-0.1.
|
44
|
-
primitive-0.1.
|
45
|
-
primitive-0.1.
|
46
|
-
primitive-0.1.
|
44
|
+
primitive-0.1.16.dist-info/METADATA,sha256=waiNzX14CjPaARYniKRCDd9gwCAKAEu9yMuwFhQ8yOI,1818
|
45
|
+
primitive-0.1.16.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
46
|
+
primitive-0.1.16.dist-info/entry_points.txt,sha256=p1K8DMCWka5FqLlqP1sPek5Uovy9jq8u51gUsP-z334,48
|
47
|
+
primitive-0.1.16.dist-info/licenses/LICENSE.txt,sha256=B8kmQMJ2sxYygjCLBk770uacaMci4mPSoJJ8WoDBY_c,1098
|
48
|
+
primitive-0.1.16.dist-info/RECORD,,
|
primitive/simulations/actions.py
DELETED
@@ -1,48 +0,0 @@
|
|
1
|
-
from gql import gql
|
2
|
-
|
3
|
-
|
4
|
-
from primitive.utils.actions import BaseAction
|
5
|
-
|
6
|
-
|
7
|
-
class Simulations(BaseAction):
|
8
|
-
def trace_create(
|
9
|
-
self,
|
10
|
-
id_code: str,
|
11
|
-
module: str,
|
12
|
-
var_type: str,
|
13
|
-
var_size: int,
|
14
|
-
reference: str,
|
15
|
-
bit_index: str,
|
16
|
-
timescale_unit: str,
|
17
|
-
timescale_magnitude: int,
|
18
|
-
organization: str,
|
19
|
-
file: str,
|
20
|
-
job_run: str,
|
21
|
-
):
|
22
|
-
mutation = gql(
|
23
|
-
"""
|
24
|
-
mutation createTrace($input: TraceCreateInput!) {
|
25
|
-
traceCreate(input: $input) {
|
26
|
-
... on Trace {
|
27
|
-
id
|
28
|
-
}
|
29
|
-
}
|
30
|
-
}
|
31
|
-
"""
|
32
|
-
)
|
33
|
-
input = {
|
34
|
-
"idCode": id_code,
|
35
|
-
"module": module,
|
36
|
-
"varType": var_type,
|
37
|
-
"varSize": var_size,
|
38
|
-
"reference": reference,
|
39
|
-
"bitIndex": bit_index,
|
40
|
-
"timescaleUnit": timescale_unit,
|
41
|
-
"timescaleMagnitude": timescale_magnitude,
|
42
|
-
"organization": organization,
|
43
|
-
"file": file,
|
44
|
-
"jobRun": job_run,
|
45
|
-
}
|
46
|
-
variables = {"input": input}
|
47
|
-
result = self.primitive.session.execute(mutation, variable_values=variables)
|
48
|
-
return result
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|