opencos-eda 0.2.28__py3-none-any.whl → 0.2.32__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.
- opencos/commands/export.py +2 -1
- opencos/commands/flist.py +49 -12
- opencos/commands/multi.py +101 -130
- opencos/commands/synth.py +0 -1
- opencos/commands/upload.py +7 -7
- opencos/commands/waves.py +50 -19
- opencos/deps_helpers.py +93 -3
- opencos/deps_schema.py +28 -18
- opencos/eda.py +6 -1
- opencos/eda_base.py +29 -10
- opencos/eda_config.py +1 -0
- opencos/eda_config_defaults.yml +4 -1
- opencos/eda_extract_deps_keys.py +27 -10
- opencos/files.py +6 -8
- opencos/names.py +1 -0
- opencos/oc_cli.py +143 -1
- opencos/peakrdl_cleanup.py +0 -1
- opencos/tests/helpers.py +38 -10
- opencos/tests/test_deps_helpers.py +46 -2
- opencos/tests/test_eda.py +65 -41
- opencos/tests/test_tools.py +17 -9
- opencos/tools/iverilog.py +0 -2
- opencos/tools/modelsim_ase.py +11 -5
- opencos/tools/questa.py +14 -19
- opencos/tools/verilator.py +0 -2
- opencos/tools/vivado.py +253 -112
- opencos/tools/yosys.py +1 -6
- opencos/util.py +4 -0
- {opencos_eda-0.2.28.dist-info → opencos_eda-0.2.32.dist-info}/METADATA +1 -1
- {opencos_eda-0.2.28.dist-info → opencos_eda-0.2.32.dist-info}/RECORD +35 -35
- {opencos_eda-0.2.28.dist-info → opencos_eda-0.2.32.dist-info}/WHEEL +1 -1
- {opencos_eda-0.2.28.dist-info → opencos_eda-0.2.32.dist-info}/entry_points.txt +0 -0
- {opencos_eda-0.2.28.dist-info → opencos_eda-0.2.32.dist-info}/licenses/LICENSE +0 -0
- {opencos_eda-0.2.28.dist-info → opencos_eda-0.2.32.dist-info}/licenses/LICENSE.spdx +0 -0
- {opencos_eda-0.2.28.dist-info → opencos_eda-0.2.32.dist-info}/top_level.txt +0 -0
opencos/commands/export.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'''opencos.commands.export - Base class command handler for: eda export ...
|
|
2
2
|
|
|
3
|
-
Intended to be overriden by Tool based classes (such as CommandExportVivado, etc)
|
|
3
|
+
Intended to be overriden by Tool based classes (such as CommandExportVivado, etc), although
|
|
4
|
+
`eda export` can be run without --tool.'''
|
|
4
5
|
|
|
5
6
|
# Note - similar code waiver, tricky to eliminate it with inheritance when
|
|
6
7
|
# calling reusable methods.
|
opencos/commands/flist.py
CHANGED
|
@@ -34,9 +34,17 @@ class CommandFList(CommandDesign):
|
|
|
34
34
|
'prefix-vhd' : "",
|
|
35
35
|
'prefix-cpp' : "",
|
|
36
36
|
'prefix-non-sources' : "", # as comments anyway.
|
|
37
|
+
# TODO(simon): make the define quoting work like the paths quoting
|
|
38
|
+
'bracket-quote-define': False,
|
|
37
39
|
'single-quote-define': False,
|
|
38
40
|
'quote-define' : True,
|
|
39
|
-
'
|
|
41
|
+
'equal-define' : True,
|
|
42
|
+
'escape-define-value': False,
|
|
43
|
+
'quote-define-value' : False,
|
|
44
|
+
'bracket-quote-path' : False,
|
|
45
|
+
'single-quote-path' : False,
|
|
46
|
+
'double-quote-path' : False,
|
|
47
|
+
'no-quote-path' : False,
|
|
40
48
|
'build-script' : "", # we don't want this to error either
|
|
41
49
|
|
|
42
50
|
'print-to-stdout': False, # do not save to file, print to stdout.
|
|
@@ -102,6 +110,20 @@ class CommandFList(CommandDesign):
|
|
|
102
110
|
self.create_work_dir()
|
|
103
111
|
self.run_dep_commands()
|
|
104
112
|
|
|
113
|
+
pq1 = ""
|
|
114
|
+
pq2 = "" # pq = path quote
|
|
115
|
+
if self.args['no-quote-path']:
|
|
116
|
+
pass # if we decide to make one of the below default, this will override
|
|
117
|
+
elif self.args['bracket-quote-path']:
|
|
118
|
+
pq1 = "{"
|
|
119
|
+
pq2 = "}"
|
|
120
|
+
elif self.args['single-quote-path']:
|
|
121
|
+
pq1 = "'"
|
|
122
|
+
pq2 = "'"
|
|
123
|
+
elif self.args['double-quote-path']:
|
|
124
|
+
pq1 = '"'
|
|
125
|
+
pq2 = '"'
|
|
126
|
+
|
|
105
127
|
if self.args['print-to-stdout']:
|
|
106
128
|
fo = None
|
|
107
129
|
print()
|
|
@@ -119,7 +141,7 @@ class CommandFList(CommandDesign):
|
|
|
119
141
|
for f in self.files_non_source:
|
|
120
142
|
if self.args['emit-rel-path']:
|
|
121
143
|
f = os.path.relpath(f)
|
|
122
|
-
print('## ' + prefix + f, file=fo)
|
|
144
|
+
print('## ' + prefix + pq1 + f + pq2, file=fo)
|
|
123
145
|
|
|
124
146
|
if self.args['emit-define']:
|
|
125
147
|
prefix = util.strip_all_quotes(self.args['prefix-define'])
|
|
@@ -127,44 +149,59 @@ class CommandFList(CommandDesign):
|
|
|
127
149
|
if value is None:
|
|
128
150
|
newline = prefix + d
|
|
129
151
|
else:
|
|
130
|
-
if self.args['
|
|
131
|
-
|
|
152
|
+
if self.args['bracket-quote-define']:
|
|
153
|
+
qd1 = "{"
|
|
154
|
+
qd2 = "}"
|
|
155
|
+
elif self.args['single-quote-define']:
|
|
156
|
+
qd1 = "'"
|
|
157
|
+
qd2 = "'"
|
|
132
158
|
elif self.args['quote-define']:
|
|
133
|
-
|
|
159
|
+
qd1 = '"'
|
|
160
|
+
qd2 = '"' # rename this one when things calm down
|
|
161
|
+
else:
|
|
162
|
+
qd1 = ''
|
|
163
|
+
qd2 = ''
|
|
164
|
+
if self.args['equal-define']:
|
|
165
|
+
ed1 = '='
|
|
134
166
|
else:
|
|
135
|
-
|
|
136
|
-
|
|
167
|
+
ed1 = ' '
|
|
168
|
+
if self.args['escape-define-value']:
|
|
169
|
+
value = value.replace('\\', '\\\\').replace('"', '\\"')
|
|
170
|
+
if self.args['quote-define-value']:
|
|
171
|
+
value =f'"{value}"'
|
|
172
|
+
newline = prefix + qd1 + f"{d}{ed1}{value}" + qd2
|
|
137
173
|
print(newline, file=fo)
|
|
174
|
+
|
|
138
175
|
if self.args['emit-incdir']:
|
|
139
176
|
prefix = util.strip_all_quotes(self.args['prefix-incdir'])
|
|
140
177
|
for i in self.incdirs:
|
|
141
178
|
if self.args['emit-rel-path']:
|
|
142
179
|
i = os.path.relpath(i)
|
|
143
|
-
print(prefix + i, file=fo)
|
|
180
|
+
print(prefix + pq1 + i + pq2, file=fo)
|
|
144
181
|
if self.args['emit-v']:
|
|
145
182
|
prefix = util.strip_all_quotes(self.args['prefix-v'])
|
|
146
183
|
for f in self.files_v:
|
|
147
184
|
if self.args['emit-rel-path']:
|
|
148
185
|
f = os.path.relpath(f)
|
|
149
|
-
print(prefix + f, file=fo)
|
|
186
|
+
print(prefix + pq1 + f + pq2, file=fo)
|
|
150
187
|
if self.args['emit-sv']:
|
|
151
188
|
prefix = util.strip_all_quotes(self.args['prefix-sv'])
|
|
152
189
|
for f in self.files_sv:
|
|
153
190
|
if self.args['emit-rel-path']:
|
|
154
191
|
f = os.path.relpath(f)
|
|
155
|
-
print(prefix + f, file=fo)
|
|
192
|
+
print(prefix + pq1 + f + pq2, file=fo)
|
|
156
193
|
if self.args['emit-vhd']:
|
|
157
194
|
prefix = util.strip_all_quotes(self.args['prefix-vhd'])
|
|
158
195
|
for f in self.files_vhd:
|
|
159
196
|
if self.args['emit-rel-path']:
|
|
160
197
|
f = os.path.relpath(f)
|
|
161
|
-
print(prefix + f, file=fo)
|
|
198
|
+
print(prefix + pq1 + f + pq2, file=fo)
|
|
162
199
|
if self.args['emit-cpp']:
|
|
163
200
|
prefix = util.strip_all_quotes(self.args['prefix-cpp'])
|
|
164
201
|
for f in self.files_cpp:
|
|
165
202
|
if self.args['emit-rel-path']:
|
|
166
203
|
f = os.path.relpath(f)
|
|
167
|
-
print(prefix + f, file=fo)
|
|
204
|
+
print(prefix + pq1 + f + pq2, file=fo)
|
|
168
205
|
|
|
169
206
|
if self.args['print-to-stdout']:
|
|
170
207
|
print() # don't need to close fo (None)
|
opencos/commands/multi.py
CHANGED
|
@@ -6,8 +6,8 @@ These are not intended to be overriden by child classes. They do not inherit Too
|
|
|
6
6
|
import argparse
|
|
7
7
|
import glob
|
|
8
8
|
import os
|
|
9
|
-
import re
|
|
10
9
|
import shutil
|
|
10
|
+
from pathlib import Path
|
|
11
11
|
|
|
12
12
|
from opencos import util, eda_base, deps_helpers, eda_config, export_helper, \
|
|
13
13
|
eda_tool_helper
|
|
@@ -21,25 +21,43 @@ class CommandMulti(CommandParallel):
|
|
|
21
21
|
|
|
22
22
|
def __init__(self, config: dict):
|
|
23
23
|
CommandParallel.__init__(self, config=config)
|
|
24
|
-
|
|
24
|
+
|
|
25
|
+
self.multi_only_args = {
|
|
25
26
|
'fake': False,
|
|
26
27
|
'parallel': 1,
|
|
27
28
|
'single-timeout': None,
|
|
28
29
|
'fail-if-no-targets': False,
|
|
29
30
|
'export-jsonl': False,
|
|
30
|
-
|
|
31
|
+
'print-targets': False,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
self.args.update(self.multi_only_args)
|
|
31
35
|
self.args_help.update({
|
|
32
36
|
'single-timeout': ('shell timeout on a single operation in multi, not the entire'
|
|
33
37
|
'multi command'),
|
|
34
38
|
'fail-if-no-targets': 'fails the multi command if no targets were found',
|
|
35
39
|
'export-jsonl': ('If set, generates export.jsonl if possible, spawns single commands'
|
|
36
40
|
'with --export-json'),
|
|
41
|
+
'print-targets': 'Do not run jobs, prints targets to stdout',
|
|
37
42
|
})
|
|
38
43
|
self.single_command = ''
|
|
39
44
|
self.targets = [] # list of tuples (target:str, tool:str)
|
|
40
45
|
self.resolve_target_command = ''
|
|
41
46
|
|
|
42
47
|
|
|
48
|
+
def path_hidden_or_work_dir(self, path: str) -> bool:
|
|
49
|
+
'''Returns True if any portion of path is hidden file/dir or has a work-dir
|
|
50
|
+
|
|
51
|
+
such as "eda.work" (self.args['eda-dir'])'''
|
|
52
|
+
|
|
53
|
+
path_obj = Path(os.path.abspath(path))
|
|
54
|
+
if self.args['eda-dir'] in path_obj.parts:
|
|
55
|
+
return True
|
|
56
|
+
if any(x.startswith('.') and len(x) > 1 for x in path_obj.parts):
|
|
57
|
+
return True
|
|
58
|
+
return False
|
|
59
|
+
|
|
60
|
+
|
|
43
61
|
def resolve_target_get_command_level(self, level: int = -1) -> (str, int):
|
|
44
62
|
'''increments level, returns tuple of (command str, level int)'''
|
|
45
63
|
command = self.resolve_target_command
|
|
@@ -49,13 +67,55 @@ class CommandMulti(CommandParallel):
|
|
|
49
67
|
level += 1
|
|
50
68
|
return command, level
|
|
51
69
|
|
|
52
|
-
@staticmethod
|
|
53
|
-
def resolve_target_get_path_parts(target: str) -> (str, list):
|
|
54
|
-
'''Returns (target, path part list).
|
|
55
70
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
71
|
+
def resolve_path_and_target_patterns(
|
|
72
|
+
self, base_path: str, target: str, level: int = -1
|
|
73
|
+
) -> dict:
|
|
74
|
+
'''Returns a dict of: key = matching path, value = set of matched targets.
|
|
75
|
+
|
|
76
|
+
Looks at globbed paths from base_path/target, and looks for DEPS markup targets
|
|
77
|
+
matching target_pattern (using fnmatch or re.match)
|
|
78
|
+
'''
|
|
79
|
+
def debug(*text):
|
|
80
|
+
util.debug(f'resolve_target() {level=} {base_path=}', *text)
|
|
81
|
+
|
|
82
|
+
# join base_path / target
|
|
83
|
+
# - if target = ./some_path/**/*test
|
|
84
|
+
# split them again so we can do all globbing on the path_pattern portion:
|
|
85
|
+
path_pattern, target_pattern = os.path.split(os.path.join(base_path, target))
|
|
86
|
+
debug(f'{path_pattern=}, {target_pattern=}')
|
|
87
|
+
|
|
88
|
+
if target_pattern == '...':
|
|
89
|
+
# replace bazel style target ... with '*' for fnmatch.
|
|
90
|
+
target_pattern = '*'
|
|
91
|
+
|
|
92
|
+
matching_targets_dict = {}
|
|
93
|
+
|
|
94
|
+
# resolve the path_pattern portion using glob.
|
|
95
|
+
# we'll have to check for DEPS markup files in path_pattern, to match the target_wildcard
|
|
96
|
+
# using fnmatch or re.
|
|
97
|
+
for path in glob.glob(path_pattern, recursive=True):
|
|
98
|
+
|
|
99
|
+
if self.path_hidden_or_work_dir(path):
|
|
100
|
+
continue
|
|
101
|
+
|
|
102
|
+
deps_markup_file = deps_helpers.get_deps_markup_file(path)
|
|
103
|
+
if deps_markup_file:
|
|
104
|
+
data = deps_helpers.deps_markup_safe_load(deps_markup_file)
|
|
105
|
+
deps_targets = deps_helpers.deps_data_get_all_targets(data)
|
|
106
|
+
rel_path = os.path.relpath(path)
|
|
107
|
+
|
|
108
|
+
debug(f'in {rel_path=} looking for {target_pattern=} in {deps_targets=}')
|
|
109
|
+
|
|
110
|
+
for t in deps_targets:
|
|
111
|
+
if deps_helpers.fnmatch_or_re(pattern=target_pattern, string=t):
|
|
112
|
+
if rel_path not in matching_targets_dict:
|
|
113
|
+
matching_targets_dict[rel_path] = set()
|
|
114
|
+
matching_targets_dict[rel_path].add(t)
|
|
115
|
+
|
|
116
|
+
debug(f'Found potential targets for {target_pattern=}: {matching_targets_dict=}')
|
|
117
|
+
return matching_targets_dict
|
|
118
|
+
|
|
59
119
|
|
|
60
120
|
def resolve_target(self, base_path: str, target: str, level: int = -1) -> None:
|
|
61
121
|
'''Returns None, recursively attempts to determine the validity of a base_path/target,
|
|
@@ -66,37 +126,36 @@ class CommandMulti(CommandParallel):
|
|
|
66
126
|
'''
|
|
67
127
|
|
|
68
128
|
def debug(*text):
|
|
69
|
-
util.debug('resolve_target() {level=} {base_path=}', *text)
|
|
129
|
+
util.debug(f'resolve_target() {level=} {base_path=}', *text)
|
|
70
130
|
|
|
71
131
|
command, level = self.resolve_target_get_command_level(level)
|
|
72
132
|
|
|
73
133
|
debug(f"Enter/Start: target={target}, command={command}")
|
|
74
134
|
|
|
75
|
-
target,
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
debug(f"{target} is a single-part target, look for matches in here")
|
|
79
|
-
|
|
80
|
-
target_pattern = "^" + target_path_parts.pop(0) + "$"
|
|
81
|
-
target_pattern = target_pattern.replace("*", r"[^\/]*")
|
|
82
|
-
debug(f"{target_pattern=}")
|
|
83
|
-
self.resolve_target_single(base_path=base_path, target=target_pattern, level=level)
|
|
135
|
+
# Strip outer quote on target, in case it was passed this way from CLI:
|
|
136
|
+
for x in ['"', "'"]:
|
|
137
|
+
target = target.lstrip(x).rstrip(x)
|
|
84
138
|
|
|
85
|
-
|
|
139
|
+
matching_targets_dict = self.resolve_path_and_target_patterns(
|
|
140
|
+
base_path=base_path, target=target, level=level
|
|
141
|
+
)
|
|
86
142
|
|
|
87
|
-
|
|
143
|
+
for path, targets in matching_targets_dict.items():
|
|
144
|
+
self.resolve_target_single_path(base_path=path, targets=targets, level=level)
|
|
88
145
|
|
|
89
146
|
|
|
90
|
-
def
|
|
91
|
-
self, base_path: str,
|
|
147
|
+
def resolve_target_single_path( # pylint: disable=too-many-locals
|
|
148
|
+
self, base_path: str, targets: list, level: int = -1
|
|
92
149
|
) -> None:
|
|
93
|
-
'''Returns None, called by resolve_target(..) if we have a single
|
|
150
|
+
'''Returns None, called by resolve_target(..) if we have a single base_path,
|
|
94
151
|
|
|
95
|
-
and need to resolve it via
|
|
152
|
+
and multiple targets (list), and need to resolve it via base_path and DEPS.[markup]
|
|
153
|
+
file information. There should be no remaining wildcard information in targets
|
|
154
|
+
(that was handled earlier with fnmatch and re.)
|
|
96
155
|
'''
|
|
97
156
|
|
|
98
157
|
def debug(*text):
|
|
99
|
-
util.debug('resolve_target() {level=} {base_path=}', *text)
|
|
158
|
+
util.debug(f'resolve_target() {level=} {base_path=}', *text)
|
|
100
159
|
|
|
101
160
|
command, level = self.resolve_target_get_command_level(level)
|
|
102
161
|
|
|
@@ -110,20 +169,15 @@ class CommandMulti(CommandParallel):
|
|
|
110
169
|
if data is None:
|
|
111
170
|
data = {}
|
|
112
171
|
|
|
172
|
+
deps_targets = deps_helpers.deps_data_get_all_targets(data)
|
|
113
173
|
deps_file_defaults = data.get('DEFAULTS', {})
|
|
114
174
|
|
|
115
175
|
# Loop through all the targets in DEPS.yml, skipping DEFAULTS
|
|
116
|
-
for target_node
|
|
117
|
-
|
|
118
|
-
# Skip upper-case targets, including 'DEFAULTS':
|
|
119
|
-
if target_node == target_node.upper():
|
|
176
|
+
for target_node in targets:
|
|
177
|
+
if target_node not in deps_targets:
|
|
120
178
|
continue
|
|
121
179
|
|
|
122
|
-
|
|
123
|
-
if not m:
|
|
124
|
-
# If the target_node in our deps_file doesn't
|
|
125
|
-
# match the pattern, then skip.
|
|
126
|
-
continue
|
|
180
|
+
entry = data[target_node]
|
|
127
181
|
|
|
128
182
|
# Since we support a few schema flavors for a target (our
|
|
129
183
|
# 'target_node' key in a DEPS.yml file) santize the entry
|
|
@@ -182,96 +236,9 @@ class CommandMulti(CommandParallel):
|
|
|
182
236
|
|
|
183
237
|
for tool in all_multi_tools:
|
|
184
238
|
if tool not in multi_ignore_skip_this_target_node:
|
|
185
|
-
debug(f"Found dep {target_node=} {tool=} matching"
|
|
186
|
-
f"{target=} {entry=}")
|
|
239
|
+
debug(f"Found dep {target_node=} {tool=} matching, {entry=}")
|
|
187
240
|
self.targets.append( tuple([os.path.join(base_path, target_node), tool]) )
|
|
188
241
|
|
|
189
|
-
def resolve_target_path_parts( # pylint: disable=too-many-branches
|
|
190
|
-
self, base_path: str, target: str, level: int = -1
|
|
191
|
-
) -> None:
|
|
192
|
-
'''Returns None, recursively attempts to determine the validity of a base_path/target,
|
|
193
|
-
|
|
194
|
-
operates on a directory, will re-invoke resolve_target(...).
|
|
195
|
-
'''
|
|
196
|
-
|
|
197
|
-
_, level = self.resolve_target_get_command_level(level)
|
|
198
|
-
|
|
199
|
-
def debug(*text):
|
|
200
|
-
util.debug('resolve_target() {level=} {base_path=}', *text)
|
|
201
|
-
|
|
202
|
-
target, target_path_parts = self.resolve_target_get_path_parts(target)
|
|
203
|
-
|
|
204
|
-
# let's look at the first part of the multi-part target path, which should be a dir
|
|
205
|
-
part = target_path_parts.pop(0)
|
|
206
|
-
if part == ".":
|
|
207
|
-
# just reprocess this directory (matches "./some/path" and retries as "some/path")
|
|
208
|
-
debug(f"processing {part}, recursing here")
|
|
209
|
-
self.resolve_target(
|
|
210
|
-
base_path=base_path, target=os.path.sep.join(target_path_parts),
|
|
211
|
-
level=level
|
|
212
|
-
)
|
|
213
|
-
|
|
214
|
-
elif part == "..":
|
|
215
|
-
# reprocess from the directory above (../some/path --> change base_path, some/path)
|
|
216
|
-
debug(f"processing {part}, recursing above at ../")
|
|
217
|
-
new_base_path = os.path.abspath(os.path.join(base_path, part))
|
|
218
|
-
self.resolve_target(
|
|
219
|
-
base_path=new_base_path, target=os.path.sep.join(target_path_parts),
|
|
220
|
-
level=level
|
|
221
|
-
)
|
|
222
|
-
|
|
223
|
-
elif part == "...":
|
|
224
|
-
# support ... as bazel-style all subdirs.
|
|
225
|
-
debug(f"processing {part}, recursing to check here")
|
|
226
|
-
# first we check this dir: {"<base>",".../target"} should match "target" in <base>,
|
|
227
|
-
# so we call {"<base>","target"}
|
|
228
|
-
self.resolve_target(
|
|
229
|
-
base_path=base_path, target=os.path.sep.join(target_path_parts),
|
|
230
|
-
level=level
|
|
231
|
-
)
|
|
232
|
-
# now we find all dirs in <base> ...
|
|
233
|
-
debug(f"processing {part}, looking through dirs...")
|
|
234
|
-
for e in os.listdir(base_path):
|
|
235
|
-
debug(f"{e=}, isdir={os.path.isdir(os.path.join(base_path,e))}")
|
|
236
|
-
if e in ('eda.work', self.args['eda-dir']):
|
|
237
|
-
debug(f"processing {part}, skipping work dir {e}")
|
|
238
|
-
elif os.path.islink(os.path.join(base_path, e)):
|
|
239
|
-
debug(f"processing {part}, skipping link dir {e}")
|
|
240
|
-
elif os.path.isdir(os.path.join(base_path,e)):
|
|
241
|
-
debug(f"processing {part}, recursing into {e}")
|
|
242
|
-
self.resolve_target(
|
|
243
|
-
base_path=os.path.join(base_path, e), target=target,
|
|
244
|
-
level=level)
|
|
245
|
-
elif part.startswith("."):
|
|
246
|
-
debug(f"processing {part}, skipping hidden")
|
|
247
|
-
elif part == self.args['eda-dir']:
|
|
248
|
-
debug(f"processing {part}, skipping eda.dir")
|
|
249
|
-
elif os.path.isdir(os.path.join(base_path, part)):
|
|
250
|
-
# reprocess in a lower directory (matches "some/...",
|
|
251
|
-
# enters "some/", and retries "...")
|
|
252
|
-
debug(f"processing {part}, recursing down")
|
|
253
|
-
self.resolve_target(
|
|
254
|
-
base_path=os.path.join(base_path, part),
|
|
255
|
-
target=os.path.sep.join(target_path_parts), level=level
|
|
256
|
-
)
|
|
257
|
-
elif part == "*":
|
|
258
|
-
# descend into every directory, we only go in if there's a DEPS though
|
|
259
|
-
debug(f"processing {part}, looking through dirs...")
|
|
260
|
-
for e in os.listdir(base_path):
|
|
261
|
-
debug(f"{e=}")
|
|
262
|
-
if os.path.isdir(e):
|
|
263
|
-
debug(f"looking for ={os.path.join(base_path, e, 'DEPS-markup-file')}")
|
|
264
|
-
deps_markup_file = get_deps_markup_file(os.path.join(base_path, e))
|
|
265
|
-
if self.config['deps_markup_supported'] and deps_markup_file:
|
|
266
|
-
self.resolve_target(
|
|
267
|
-
base_path=os.path.join(base_path, e),
|
|
268
|
-
target=os.path.sep.join(target_path_parts),
|
|
269
|
-
level=level)
|
|
270
|
-
else:
|
|
271
|
-
# This is not a warning/error, just means we didn't find what we were looking
|
|
272
|
-
# for on expanding a path part with * and ... in the target.
|
|
273
|
-
debug(f"processing {part} ... but not sure what to do with it?")
|
|
274
|
-
|
|
275
242
|
|
|
276
243
|
def process_tokens( # pylint: disable=too-many-locals, too-many-branches, too-many-statements
|
|
277
244
|
self, tokens: list, process_all: bool = True, pwd: str = os.getcwd()
|
|
@@ -294,9 +261,7 @@ class CommandMulti(CommandParallel):
|
|
|
294
261
|
# only run it on a subset of our self.args:
|
|
295
262
|
parsed, unparsed = self.run_argparser_on_list(
|
|
296
263
|
tokens=tokens,
|
|
297
|
-
parser_arg_list=
|
|
298
|
-
'fake', 'parallel', 'single-timeout', 'fail-if-no-targets', 'export-jsonl'
|
|
299
|
-
],
|
|
264
|
+
parser_arg_list=list(self.multi_only_args.keys()),
|
|
300
265
|
apply_parsed_args=True
|
|
301
266
|
)
|
|
302
267
|
|
|
@@ -372,10 +337,16 @@ class CommandMulti(CommandParallel):
|
|
|
372
337
|
mylist = get_pretty_targets_tuple_as_list(self.targets)
|
|
373
338
|
util.info( ", ".join(mylist), start="")
|
|
374
339
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
340
|
+
if self.args['print-targets']:
|
|
341
|
+
util.info('Multi print-targets (will not run jobs): -->')
|
|
342
|
+
for t in self.targets:
|
|
343
|
+
# t = tuple of (target:str, tool:str), we just want the target.
|
|
344
|
+
print(f' {t[0]}')
|
|
345
|
+
else:
|
|
346
|
+
util.debug("Multi: converting list of targets into list of jobs")
|
|
347
|
+
self.jobs = []
|
|
348
|
+
self.append_jobs_from_targets(args=arg_tokens)
|
|
349
|
+
self.run_jobs(command)
|
|
379
350
|
|
|
380
351
|
# Because CommandMulti has a custom arg parsing, we do not have 'export' related
|
|
381
352
|
# args in self.args (they are left as 'unparsed' for the glob'ed commands)
|
opencos/commands/synth.py
CHANGED
opencos/commands/upload.py
CHANGED
|
@@ -5,9 +5,9 @@ Intended to be overriden by Tool based classes (such as CommandUploadVivado, etc
|
|
|
5
5
|
|
|
6
6
|
import os
|
|
7
7
|
|
|
8
|
-
from opencos.eda_base import
|
|
8
|
+
from opencos.eda_base import Command, Tool
|
|
9
9
|
|
|
10
|
-
class CommandUpload(
|
|
10
|
+
class CommandUpload(Command):
|
|
11
11
|
'''Base class command handler for: eda upload ...'''
|
|
12
12
|
|
|
13
13
|
CHECK_REQUIRES = [Tool]
|
|
@@ -15,16 +15,16 @@ class CommandUpload(CommandDesign):
|
|
|
15
15
|
command_name = 'upload'
|
|
16
16
|
|
|
17
17
|
def __init__(self, config: dict):
|
|
18
|
-
|
|
18
|
+
Command.__init__(self, config=config)
|
|
19
|
+
self.unparsed_args = []
|
|
19
20
|
|
|
20
21
|
def process_tokens(
|
|
21
22
|
self, tokens: list, process_all: bool = True, pwd: str = os.getcwd()
|
|
22
23
|
) -> list:
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
self, tokens=tokens, process_all=
|
|
25
|
+
self.unparsed_args = Command.process_tokens(
|
|
26
|
+
self, tokens=tokens, process_all=False, pwd=pwd
|
|
26
27
|
)
|
|
27
28
|
self.create_work_dir()
|
|
28
|
-
self.run_dep_commands()
|
|
29
29
|
self.do_it()
|
|
30
|
-
return
|
|
30
|
+
return []
|
opencos/commands/waves.py
CHANGED
|
@@ -30,9 +30,24 @@ class CommandWaves(CommandDesign):
|
|
|
30
30
|
'.wdb', '.vcd', '.wlf', '.fst'
|
|
31
31
|
]
|
|
32
32
|
|
|
33
|
+
VSIM_TOOLS = set([
|
|
34
|
+
'questa',
|
|
35
|
+
'modelsim_ase',
|
|
36
|
+
])
|
|
37
|
+
|
|
38
|
+
VSIM_VCD_TOOLS = set([
|
|
39
|
+
'questa',
|
|
40
|
+
])
|
|
33
41
|
|
|
34
42
|
def __init__(self, config: dict):
|
|
35
43
|
CommandDesign.__init__(self, config=config)
|
|
44
|
+
self.args.update({
|
|
45
|
+
'test-mode': False,
|
|
46
|
+
})
|
|
47
|
+
self.args_help.update({
|
|
48
|
+
'test-mode': 'Do not run the command to open the located wave file, instead print' \
|
|
49
|
+
+ ' to stdout',
|
|
50
|
+
})
|
|
36
51
|
|
|
37
52
|
|
|
38
53
|
def get_wave_files_in_dirs(self, wave_dirs: list, quiet: bool = False) -> list:
|
|
@@ -62,6 +77,9 @@ class CommandWaves(CommandDesign):
|
|
|
62
77
|
wave_dirs = []
|
|
63
78
|
tokens = CommandDesign.process_tokens(self, tokens=tokens, process_all=False, pwd=pwd)
|
|
64
79
|
|
|
80
|
+
if self.args['test-mode']:
|
|
81
|
+
self.exec = self._test_mode_exec
|
|
82
|
+
|
|
65
83
|
while tokens:
|
|
66
84
|
if os.path.isfile(tokens[0]):
|
|
67
85
|
if wave_file is not None:
|
|
@@ -87,8 +105,8 @@ class CommandWaves(CommandDesign):
|
|
|
87
105
|
wave_dirs.append('.')
|
|
88
106
|
all_files = self.get_wave_files_in_dirs(wave_dirs)
|
|
89
107
|
if len(all_files) > 1:
|
|
90
|
-
all_files.sort(os.path.getmtime)
|
|
91
|
-
util.info(f"Choosing: {
|
|
108
|
+
all_files.sort(key=os.path.getmtime)
|
|
109
|
+
util.info(f"Choosing: {all_files[-1]} (newest)")
|
|
92
110
|
if all_files:
|
|
93
111
|
wave_file = all_files[-1]
|
|
94
112
|
else:
|
|
@@ -106,17 +124,18 @@ class CommandWaves(CommandDesign):
|
|
|
106
124
|
tcl_name = wave_file + '.waves.tcl'
|
|
107
125
|
with open( tcl_name, 'w', encoding='utf-8') as fo :
|
|
108
126
|
print( 'current_fileset', file=fo)
|
|
109
|
-
print( f'open_wave_database {wave_file
|
|
127
|
+
print( f'open_wave_database {wave_file}', file=fo)
|
|
110
128
|
command_list = [ 'vivado', '-source', tcl_name]
|
|
111
129
|
self.exec(os.path.dirname(wave_file), command_list)
|
|
112
130
|
else:
|
|
113
131
|
self.error(f"Don't know how to open {wave_file} without Vivado in PATH")
|
|
114
132
|
elif wave_file.endswith('.wlf'):
|
|
115
|
-
if
|
|
133
|
+
if self._vsim_available():
|
|
116
134
|
command_list = ['vsim', wave_file]
|
|
117
135
|
self.exec(os.path.dirname(wave_file), command_list)
|
|
118
136
|
else:
|
|
119
|
-
self.error(f"Don't know how to open {wave_file} without
|
|
137
|
+
self.error(f"Don't know how to open {wave_file} without one of",
|
|
138
|
+
f"{self.VSIM_TOOLS} in PATH")
|
|
120
139
|
elif wave_file.endswith('.fst'):
|
|
121
140
|
if 'gtkwave' in self.config['tools_loaded'] and shutil.which('gtkwave'):
|
|
122
141
|
command_list = ['gtkwave', wave_file]
|
|
@@ -124,24 +143,36 @@ class CommandWaves(CommandDesign):
|
|
|
124
143
|
else:
|
|
125
144
|
self.error(f"Don't know how to open {wave_file} without GtkWave in PATH")
|
|
126
145
|
elif wave_file.endswith('.vcd'):
|
|
127
|
-
if 'questa' in self.config['tools_loaded'] and shutil.which('vsim'):
|
|
128
|
-
command_list = ['vsim', wave_file]
|
|
129
|
-
self.exec(os.path.dirname(wave_file), command_list)
|
|
130
|
-
elif 'vivado' in self.config['tools_loaded'] and shutil.which('vivado'):
|
|
131
|
-
# I don't think this works, this is a placeholder, I'm sure Vivado can open a VCD
|
|
132
|
-
# Also this would be a great place to start adding some open source (GTKWAVE)
|
|
133
|
-
# support...
|
|
134
|
-
tcl_name = wave_file + '.waves.tcl'
|
|
135
|
-
with open( tcl_name, 'w', encoding='utf-8') as fo :
|
|
136
|
-
print( 'current_fileset', file=fo)
|
|
137
|
-
print( f'open_wave_database {wave_file=}', file=fo)
|
|
138
|
-
command_list = [ 'vivado', '-source', tcl_name]
|
|
139
|
-
self.exec(os.path.dirname(wave_file), command_list)
|
|
140
146
|
if 'gtkwave' in self.config['tools_loaded'] and shutil.which('gktwave'):
|
|
141
147
|
command_list = ['gtkwave', wave_file]
|
|
142
148
|
self.exec(os.path.dirname(wave_file), command_list)
|
|
149
|
+
elif self._vsim_available(from_tools=self.VSIM_VCD_TOOLS):
|
|
150
|
+
# TODO(drew): untested, may not work, may need to use fst2vcd converter first
|
|
151
|
+
# (from gtkwave install)
|
|
152
|
+
command_list = ['vsim', wave_file]
|
|
153
|
+
self.exec(os.path.dirname(wave_file), command_list)
|
|
143
154
|
else:
|
|
144
155
|
self.error(f"Don't know how to open {wave_file} without Vivado,",
|
|
145
|
-
"
|
|
156
|
+
f"gtkwave, or {self.VSIM_VCD_TOOLS} in PATH")
|
|
146
157
|
|
|
147
158
|
return tokens
|
|
159
|
+
|
|
160
|
+
def _vsim_available( # pylint: disable=dangerous-default-value
|
|
161
|
+
self, from_tools: list = VSIM_TOOLS
|
|
162
|
+
) -> bool:
|
|
163
|
+
'''Returns True if 'vsim' is available (Questa or Modelsim)'''
|
|
164
|
+
return bool(shutil.which('vsim')) and \
|
|
165
|
+
any(x in self.config['tools_loaded'] for x in from_tools)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _test_mode_exec( # pylint: disable=unused-argument
|
|
169
|
+
self, work_dir: str,
|
|
170
|
+
command_list: list,
|
|
171
|
+
**kwargs
|
|
172
|
+
) -> None:
|
|
173
|
+
'''Override for Command.exec if arg --test-mode was set, does not run
|
|
174
|
+
|
|
175
|
+
the command_list, instead prints to stdout'''
|
|
176
|
+
|
|
177
|
+
util.info(f'waves.py: test_mode exec stdout: {" ".join(command_list)};',
|
|
178
|
+
f' ({work_dir=}')
|