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/deps_helpers.py
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
|
|
2
|
+
import fnmatch
|
|
1
3
|
import os
|
|
4
|
+
from pathlib import Path
|
|
2
5
|
import sys
|
|
3
6
|
import re
|
|
4
7
|
import shutil
|
|
@@ -7,7 +10,8 @@ import toml, json
|
|
|
7
10
|
from opencos import files
|
|
8
11
|
from opencos import eda_config
|
|
9
12
|
from opencos.util import debug, info, warning, error, ShellCommandList, \
|
|
10
|
-
yaml_safe_load, toml_load_only_root_line_numbers
|
|
13
|
+
yaml_safe_load, toml_load_only_root_line_numbers, \
|
|
14
|
+
subprocess_run_background
|
|
11
15
|
|
|
12
16
|
|
|
13
17
|
class Defaults:
|
|
@@ -233,6 +237,92 @@ peakrdl_cleanup_py = os.path.join(thispath, 'peakrdl_cleanup.py')
|
|
|
233
237
|
def deps_data_get_all_targets(data:dict) -> list:
|
|
234
238
|
return [x for x in data.keys() if x not in Defaults.root_table_keys_not_targets]
|
|
235
239
|
|
|
240
|
+
|
|
241
|
+
def fnmatch_or_re(pattern: str, string: str) -> bool:
|
|
242
|
+
'''Returns True if pattern/string matches in re.match or fnmatch'''
|
|
243
|
+
matches = []
|
|
244
|
+
# fnmatch check, aka: ./*test
|
|
245
|
+
matches.append(
|
|
246
|
+
bool(fnmatch.fnmatch(name=string, pat=pattern))
|
|
247
|
+
)
|
|
248
|
+
# regex check, aka: ./.*test
|
|
249
|
+
try:
|
|
250
|
+
matches.append(
|
|
251
|
+
bool(re.match(pattern=pattern, string=string))
|
|
252
|
+
)
|
|
253
|
+
except: # pylint: disable=bare-except
|
|
254
|
+
# could have been an illegal/unsupported regex, so don't match.
|
|
255
|
+
pass
|
|
256
|
+
return any(matches)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def get_all_targets(
|
|
260
|
+
dirs: list = [os.getcwd()],
|
|
261
|
+
base_path: str = os.getcwd(),
|
|
262
|
+
filter_str: str = '',
|
|
263
|
+
filter_using_multi: str = '',
|
|
264
|
+
error_on_empty_return: bool = True
|
|
265
|
+
) -> list:
|
|
266
|
+
'''Returns a list of [dir/target, ... ] using relpath from base_path
|
|
267
|
+
|
|
268
|
+
If using filter_using_multi (str), dirs (list) is not required. Example:
|
|
269
|
+
filter_using_multi='sim --tool vivado path/to/*test'
|
|
270
|
+
and filter_str is applied to all resulting targets.
|
|
271
|
+
|
|
272
|
+
If not using filter_using_multi, dirs is required, and filter_str is applied
|
|
273
|
+
To all targets from dirs.
|
|
274
|
+
'''
|
|
275
|
+
|
|
276
|
+
if filter_using_multi:
|
|
277
|
+
targets = []
|
|
278
|
+
orig_dir = os.path.abspath(os.getcwd())
|
|
279
|
+
os.chdir(base_path)
|
|
280
|
+
cmd_str = 'eda multi --quiet --print-targets ' + filter_using_multi
|
|
281
|
+
stdout, stderr, rc = subprocess_run_background(
|
|
282
|
+
work_dir='.', command_list=cmd_str.split()
|
|
283
|
+
)
|
|
284
|
+
os.chdir(orig_dir)
|
|
285
|
+
if rc != 0:
|
|
286
|
+
error(f'get_all_targets: {base_path=} {filter_using_multi=} {cmd_str=} returned:',
|
|
287
|
+
f'{rc=}, {stdout=}')
|
|
288
|
+
|
|
289
|
+
multi_filtered_targets = stdout.split()
|
|
290
|
+
if not filter_str:
|
|
291
|
+
targets = multi_filtered_targets
|
|
292
|
+
else:
|
|
293
|
+
targets = set()
|
|
294
|
+
for target in multi_filtered_targets:
|
|
295
|
+
this_dir, leaf_target = os.path.split(target)
|
|
296
|
+
if fnmatch_or_re(pattern=filter_str,
|
|
297
|
+
string=leaf_target):
|
|
298
|
+
targets.add(
|
|
299
|
+
os.path.join(os.path.relpath(this_dir, start=base_path), leaf_target)
|
|
300
|
+
)
|
|
301
|
+
targets = list(targets)
|
|
302
|
+
if not targets and error_on_empty_return:
|
|
303
|
+
error(f'get_all_targets: {base_path=} {filter_using_multi=} returned no targets')
|
|
304
|
+
return targets
|
|
305
|
+
|
|
306
|
+
targets = set()
|
|
307
|
+
for this_dir in dirs:
|
|
308
|
+
this_dir = os.path.join(base_path, this_dir)
|
|
309
|
+
deps_file = get_deps_markup_file(this_dir)
|
|
310
|
+
if not deps_file:
|
|
311
|
+
continue
|
|
312
|
+
data = deps_markup_safe_load(filepath=deps_file)
|
|
313
|
+
|
|
314
|
+
for leaf_target in deps_data_get_all_targets(data):
|
|
315
|
+
if not filter_str or fnmatch_or_re(pattern=filter_str,
|
|
316
|
+
string=leaf_target):
|
|
317
|
+
targets.add(
|
|
318
|
+
os.path.join(os.path.relpath(this_dir, start=base_path), leaf_target)
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
if not targets and error_on_empty_return:
|
|
322
|
+
error(f'get_all_targets: {base_path=} {dirs=} {filter_str=} returned no targets')
|
|
323
|
+
return list(targets)
|
|
324
|
+
|
|
325
|
+
|
|
236
326
|
def dep_str2list(value) -> list():
|
|
237
327
|
if value is None:
|
|
238
328
|
return []
|
|
@@ -1060,9 +1150,9 @@ def parse_deps_peakrdl(line : str, target_path : str, target_node : str, enable
|
|
|
1060
1150
|
|
|
1061
1151
|
|
|
1062
1152
|
shell_commands = [
|
|
1063
|
-
[ 'peakrdl', 'regblock', '-o', 'peakrdl/'] + args_list,
|
|
1153
|
+
[ 'peakrdl', 'regblock', '-o', str(Path('peakrdl/'))] + args_list,
|
|
1064
1154
|
# Edit file to apply some verilator waivers, etc, from peakrdl_cleanup.py:
|
|
1065
|
-
[ 'python3', peakrdl_cleanup_py, f'peakrdl/{top}.sv', f'peakrdl/{top}.sv' ],
|
|
1155
|
+
[ 'python3', peakrdl_cleanup_py, str(Path(f'peakrdl/{top}.sv')), str(Path(f'peakrdl/{top}.sv')) ],
|
|
1066
1156
|
]
|
|
1067
1157
|
|
|
1068
1158
|
ret_dict = {
|
opencos/deps_schema.py
CHANGED
|
@@ -120,8 +120,12 @@ my_target_name:
|
|
|
120
120
|
|
|
121
121
|
'''
|
|
122
122
|
|
|
123
|
-
|
|
124
|
-
import sys
|
|
123
|
+
import os
|
|
124
|
+
import sys
|
|
125
|
+
|
|
126
|
+
from schema import Schema, Or, Optional, SchemaError
|
|
127
|
+
|
|
128
|
+
from opencos.deps_helpers import deps_markup_safe_load, get_deps_markup_file
|
|
125
129
|
|
|
126
130
|
# Because we deal with YAML, where a Table Key with dangling/empty value is allowed
|
|
127
131
|
# and we have things like SystemVerilog defines where there's a Table key with no Value,
|
|
@@ -302,19 +306,18 @@ def check(data: dict, schema_obj=FILE):
|
|
|
302
306
|
return True, None
|
|
303
307
|
except SchemaError as e:
|
|
304
308
|
return False, str(e)
|
|
305
|
-
except Exception as e:
|
|
309
|
+
except Exception as e: # pylint: disable=broad-exception-caught
|
|
306
310
|
return False, str(e)
|
|
307
311
|
|
|
308
312
|
|
|
309
313
|
def check_files(files, schema_obj=FILE) -> bool:
|
|
310
314
|
'''Returns True if files lint cleanly in the FILE schema.'''
|
|
311
|
-
from opencos.deps_helpers import deps_markup_safe_load, get_deps_markup_file
|
|
312
315
|
|
|
313
|
-
if
|
|
316
|
+
if isinstance(files, str):
|
|
314
317
|
files = [files]
|
|
315
318
|
|
|
316
|
-
passes_list =
|
|
317
|
-
error_files =
|
|
319
|
+
passes_list = []
|
|
320
|
+
error_files = []
|
|
318
321
|
for filepath in files:
|
|
319
322
|
deps_filepath = filepath
|
|
320
323
|
if os.path.isdir(filepath):
|
|
@@ -328,7 +331,7 @@ def check_files(files, schema_obj=FILE) -> bool:
|
|
|
328
331
|
print(f'{deps_filepath}: [PASS]')
|
|
329
332
|
if not passes:
|
|
330
333
|
print(f'ERROR: {deps_filepath}:')
|
|
331
|
-
print(
|
|
334
|
+
print('-- retdata --')
|
|
332
335
|
print(retdata)
|
|
333
336
|
print(f' previous error on: {deps_filepath}\n')
|
|
334
337
|
error_files.append(deps_filepath)
|
|
@@ -339,18 +342,25 @@ def check_files(files, schema_obj=FILE) -> bool:
|
|
|
339
342
|
print(f'ERROR: files with problems (see above): {error_files}')
|
|
340
343
|
return ret
|
|
341
344
|
|
|
342
|
-
def main(
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
345
|
+
def main( # pylint: disable=dangerous-default-value
|
|
346
|
+
filepaths: list = []
|
|
347
|
+
) -> None:
|
|
348
|
+
'''Returns None, will exit on completion, checks all DEPS schema in list filepaths
|
|
349
|
+
|
|
350
|
+
If filepaths is empty, uses sys.argv[1:]'''
|
|
351
|
+
|
|
352
|
+
fpaths = filepaths
|
|
353
|
+
if not fpaths:
|
|
354
|
+
assert len(sys.argv) >= 2, 'Need 1 or more args - DEPS file to check'
|
|
355
|
+
fpaths = sys.argv[1:]
|
|
356
|
+
|
|
357
|
+
assert fpaths and isinstance(fpaths, list), \
|
|
358
|
+
f'Need 1 or more files to check: {fpaths=}'
|
|
346
359
|
|
|
347
|
-
for filepath in
|
|
360
|
+
for filepath in fpaths:
|
|
348
361
|
assert os.path.exists(filepath), f'{filepath=} does not exist'
|
|
349
|
-
ret = check_files(
|
|
362
|
+
ret = check_files(fpaths)
|
|
350
363
|
sys.exit(int(not ret))
|
|
351
364
|
|
|
352
365
|
if __name__ == '__main__':
|
|
353
|
-
|
|
354
|
-
assert len(sys.argv) >= 2, f'Need 1 or more args - DEPS file to check'
|
|
355
|
-
filepaths = sys.argv[1:]
|
|
356
|
-
main(filepaths)
|
|
366
|
+
main()
|
opencos/eda.py
CHANGED
|
@@ -168,6 +168,7 @@ def auto_tool_setup(warnings:bool=True, config=None, quiet=False, tool=None) ->
|
|
|
168
168
|
if tool and tool != name:
|
|
169
169
|
continue # if called with tool=(some_name), then only load that tool.
|
|
170
170
|
|
|
171
|
+
util.debug(f"Checking for ability to run tool: {name}")
|
|
171
172
|
exe = value.get('exe', str())
|
|
172
173
|
if type(exe) is list:
|
|
173
174
|
exe_list = exe
|
|
@@ -183,12 +184,14 @@ def auto_tool_setup(warnings:bool=True, config=None, quiet=False, tool=None) ->
|
|
|
183
184
|
spec = importlib.util.find_spec(pkg)
|
|
184
185
|
if not spec:
|
|
185
186
|
has_all_py = False
|
|
187
|
+
util.debug(f"... No, missing pkg {spec}")
|
|
186
188
|
|
|
187
189
|
has_all_env = True
|
|
188
190
|
requires_env_list = value.get('requires_env', list())
|
|
189
191
|
for env in requires_env_list:
|
|
190
192
|
if not os.environ.get(env, ''):
|
|
191
193
|
has_all_env = False
|
|
194
|
+
util.debug(f"... No, missing env {env}")
|
|
192
195
|
|
|
193
196
|
has_all_exe = True
|
|
194
197
|
for exe in exe_list:
|
|
@@ -196,6 +199,7 @@ def auto_tool_setup(warnings:bool=True, config=None, quiet=False, tool=None) ->
|
|
|
196
199
|
p = shutil.which(exe)
|
|
197
200
|
if not p:
|
|
198
201
|
has_all_exe = False
|
|
202
|
+
util.debug(f"... No, missing exe {exe}")
|
|
199
203
|
|
|
200
204
|
if has_all_exe:
|
|
201
205
|
requires_cmd_list = value.get('requires_cmd', list())
|
|
@@ -209,6 +213,7 @@ def auto_tool_setup(warnings:bool=True, config=None, quiet=False, tool=None) ->
|
|
|
209
213
|
has_all_exe = False
|
|
210
214
|
except:
|
|
211
215
|
has_all_exe = False
|
|
216
|
+
util.debug(f"... No, exception running {cmd_list}")
|
|
212
217
|
|
|
213
218
|
|
|
214
219
|
if all([has_all_py, has_all_env, has_all_exe]):
|
|
@@ -358,7 +363,7 @@ def process_tokens(tokens: list, original_args: list, config: dict, interactive=
|
|
|
358
363
|
util.debug(f'{command=}')
|
|
359
364
|
util.debug(f'{sco.config=}')
|
|
360
365
|
util.debug(f'{type(sco)=}')
|
|
361
|
-
if not parsed.tool:
|
|
366
|
+
if not parsed.tool and command not in config.get('command_determines_tool', []):
|
|
362
367
|
use_tool = which_tool(command, config)
|
|
363
368
|
util.info(f"--tool not specified, using default for {command=}: {use_tool}")
|
|
364
369
|
|
opencos/eda_base.py
CHANGED
|
@@ -119,11 +119,9 @@ class Tool:
|
|
|
119
119
|
self.defines = {}
|
|
120
120
|
self.args.update({
|
|
121
121
|
'tool': self._TOOL, # Set for all derived classes.
|
|
122
|
-
'xilinx': False,
|
|
123
122
|
})
|
|
124
123
|
self.args_help.update({
|
|
125
124
|
'tool': 'Tool to use for this command, such as: verilator',
|
|
126
|
-
'xilinx': 'Set to use XPMs, and other global Xilinx specific cells/libraries',
|
|
127
125
|
})
|
|
128
126
|
# update self._EXE if config says to:
|
|
129
127
|
self.set_exe(config)
|
|
@@ -224,22 +222,30 @@ class Command:
|
|
|
224
222
|
return which_tool(command, config=self.config)
|
|
225
223
|
|
|
226
224
|
def create_work_dir(self):
|
|
225
|
+
util.debug(f"create_work_dir: {self.args['eda-dir']=} {self.args['work-dir']=}")
|
|
227
226
|
if (not os.path.exists(self.args['eda-dir'])): # use os.path.isfile / isdir also
|
|
228
227
|
os.mkdir(self.args['eda-dir'])
|
|
228
|
+
util.info(f"create_work_dir: created {self.args['eda-dir']}")
|
|
229
229
|
if self.args['design'] == "":
|
|
230
230
|
if ('top' in self.args) and (self.args['top'] != ""):
|
|
231
231
|
self.args['design'] = self.args['top']
|
|
232
|
+
util.debug(f"create_work_dir: set {self.args['design']=} from {self.args['top']=}, since it was empty")
|
|
232
233
|
else:
|
|
233
234
|
self.args['design'] = "design" # generic, i.e. to create work dir "design_upload"
|
|
235
|
+
util.debug(f"create_work_dir: set {self.args['design']=} to 'design', since it was empty and we have no top")
|
|
234
236
|
if self.target == "":
|
|
235
237
|
self.target = self.args['design']
|
|
238
|
+
util.debug(f"create_work_dir: set {self.target=} from design name, since it was empty")
|
|
236
239
|
if self.args['work-dir'] == '':
|
|
237
240
|
if self.args['sub-work-dir'] == '':
|
|
238
241
|
if self.args['job-name'] != '':
|
|
239
242
|
self.args['sub-work-dir'] = self.args['job-name']
|
|
243
|
+
util.debug(f"create_work_dir: set {self.args['sub-work-dir']=} from {self.args['job-name']=}, since it was empty")
|
|
240
244
|
else:
|
|
241
245
|
self.args['sub-work-dir'] = f'{self.target}.{self.command_name}'
|
|
246
|
+
util.debug(f"create_work_dir: set {self.args['sub-work-dir']=} from {self.target=} and {self.command_name=}, since it was empty and we have no job-name")
|
|
242
247
|
self.args['work-dir'] = os.path.join(self.args['eda-dir'], self.args['sub-work-dir'])
|
|
248
|
+
util.debug(f"create_work_dir: set {self.args['work-dir']=}")
|
|
243
249
|
keep_file = os.path.join(self.args['work-dir'], "eda.keep")
|
|
244
250
|
if (os.path.exists(self.args['work-dir'])):
|
|
245
251
|
if os.path.exists(keep_file) and not self.args['force']:
|
|
@@ -247,9 +253,11 @@ class Command:
|
|
|
247
253
|
util.info(f"Removing previous '{self.args['work-dir']}'")
|
|
248
254
|
shutil.rmtree(self.args['work-dir'])
|
|
249
255
|
os.mkdir(self.args['work-dir'])
|
|
256
|
+
util.debug(f'create_work_dir: created {self.args["work-dir"]}')
|
|
250
257
|
if (self.args['keep']):
|
|
251
258
|
open(keep_file, 'w').close()
|
|
252
|
-
|
|
259
|
+
util.debug(f'create_work_dir: created {keep_file}')
|
|
260
|
+
util.info(f'create_work_dir: created {self.args["work-dir"]}')
|
|
253
261
|
return self.args['work-dir']
|
|
254
262
|
|
|
255
263
|
def exec(self, work_dir, command_list, background=False, stop_on_error=True,
|
|
@@ -390,6 +398,12 @@ class Command:
|
|
|
390
398
|
else:
|
|
391
399
|
assert False, f'{key=} {value=} how do we do argparse for this type of value?'
|
|
392
400
|
|
|
401
|
+
# TODO(drew): it might be nice to support positional args here as a list
|
|
402
|
+
# self.target_args (files/targets/patterns), something like:
|
|
403
|
+
# parser.add_argument(
|
|
404
|
+
# 'targets', nargs='+', help='positional arg for targets/files/pattern'
|
|
405
|
+
# )
|
|
406
|
+
|
|
393
407
|
return parser
|
|
394
408
|
|
|
395
409
|
|
|
@@ -682,8 +696,12 @@ class CommandDesign(Command):
|
|
|
682
696
|
util.info(f'run_dep_shell_commands {iter=}: {d=}')
|
|
683
697
|
clist = util.ShellCommandList(d['exec_list'])
|
|
684
698
|
# NOTE(drew): shell=True subprocess call, can disable with self.config
|
|
685
|
-
|
|
686
|
-
|
|
699
|
+
# However, in Windows, we seem to have to run these with shell=False
|
|
700
|
+
if sys.platform.startswith('win'):
|
|
701
|
+
self.exec(self.args['work-dir'], clist, shell=False)
|
|
702
|
+
else:
|
|
703
|
+
self.exec(self.args['work-dir'], clist, tee_fpath=clist.tee_fpath,
|
|
704
|
+
shell=self.config.get('deps_subprocess_shell', False))
|
|
687
705
|
|
|
688
706
|
def update_file_lists_for_work_dir(self):
|
|
689
707
|
if len(self.dep_work_dir_add_srcs) == 0:
|
|
@@ -718,7 +736,10 @@ class CommandDesign(Command):
|
|
|
718
736
|
self.error(f'Non-source file (reqs?) {relfname=} does not exist from {caller_info}')
|
|
719
737
|
elif not os.path.exists(destfile):
|
|
720
738
|
util.debug(f'updating non-source file to work-dir: Linked {fname=} to {destfile=}, from {caller_info}')
|
|
721
|
-
|
|
739
|
+
if sys.platform == "win32":
|
|
740
|
+
shutil.copyfile(fname, destfile) # On Windows, fall back to copying
|
|
741
|
+
else:
|
|
742
|
+
os.symlink(src=fname, dst=destfile)
|
|
722
743
|
|
|
723
744
|
def get_top_name(self, name):
|
|
724
745
|
return os.path.splitext(os.path.basename(name))[0]
|
|
@@ -731,7 +752,7 @@ class CommandDesign(Command):
|
|
|
731
752
|
to unprocessed-plusargs.
|
|
732
753
|
'''
|
|
733
754
|
|
|
734
|
-
# Since this may be coming from a raw CLI/bash argparser, we may have
|
|
755
|
+
# Since this may be coming from a raw CLI/bash/powershell argparser, we may have
|
|
735
756
|
# args that come from shlex.quote(token), such as:
|
|
736
757
|
# token = '\'+define+OC_ROOT="/foo/bar/opencos"\''
|
|
737
758
|
# So we strip all outer ' or " on the plusarg:
|
|
@@ -836,10 +857,8 @@ class CommandDesign(Command):
|
|
|
836
857
|
util.debug("Entered resolve_target(%s)" % (target))
|
|
837
858
|
# self.target is a name we grab for the job (i.e. for naming work dir etc). we don't want the path prefix.
|
|
838
859
|
# TODO: too messy -- there's also a self.target, args['job-name'], args['work-dir'], args['design'], args['top'], args['sub-work-dir'] ...
|
|
839
|
-
self.target = target
|
|
840
|
-
m = re.match(r'.*\/([\w\-]+)$', self.target)
|
|
841
|
-
if m: self.target = m.group(1)
|
|
842
860
|
|
|
861
|
+
self.target = os.path.basename(target)
|
|
843
862
|
|
|
844
863
|
if target in self.targets_dict:
|
|
845
864
|
# If we're encountered this target before, stop. We're not traversing again.
|
opencos/eda_config.py
CHANGED
opencos/eda_config_defaults.yml
CHANGED
|
@@ -46,6 +46,9 @@ file_extensions:
|
|
|
46
46
|
- .vhd
|
|
47
47
|
- .vhdl
|
|
48
48
|
|
|
49
|
+
command_determines_tool:
|
|
50
|
+
# eda commands that will self-determine the tool to use.
|
|
51
|
+
- waves
|
|
49
52
|
|
|
50
53
|
|
|
51
54
|
tools:
|
|
@@ -92,8 +95,8 @@ tools:
|
|
|
92
95
|
--autoflush
|
|
93
96
|
-j 2
|
|
94
97
|
-sv
|
|
95
|
-
# Note that we do NOT use --coverage or --coverage-line, until verilator 5920 is resolved.
|
|
96
98
|
compile-coverage-args: |
|
|
99
|
+
--coverage-line
|
|
97
100
|
--coverage-toggle
|
|
98
101
|
--coverage-underscore
|
|
99
102
|
--coverage-user
|
opencos/eda_extract_deps_keys.py
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
|
|
3
|
+
'''
|
|
4
|
+
Helper pymodule used by eda_deps_bash_completion.bash, extracts valid
|
|
5
|
+
targets from DEPS files
|
|
6
|
+
'''
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
import os
|
|
10
|
+
import json
|
|
11
|
+
|
|
12
|
+
import yaml
|
|
13
|
+
import toml
|
|
14
|
+
|
|
4
15
|
from opencos.deps_helpers import get_deps_markup_file
|
|
5
16
|
|
|
6
|
-
def get_markup_table_keys(partial_path='./'):
|
|
17
|
+
def get_markup_table_keys(partial_path='./') -> list:
|
|
7
18
|
'''Returns a list of root level keys for DEPS.[yml|yaml|toml|json]
|
|
8
19
|
|
|
9
20
|
Does not include DEFAULTS.
|
|
@@ -26,15 +37,15 @@ def get_markup_table_keys(partial_path='./'):
|
|
|
26
37
|
_, file_ext = os.path.splitext(filepath)
|
|
27
38
|
try:
|
|
28
39
|
if file_ext in ['', '.yml', 'yaml']:
|
|
29
|
-
with open(filepath, 'r') as f:
|
|
40
|
+
with open(filepath, 'r', encoding='utf-8') as f:
|
|
30
41
|
data = yaml.safe_load(f)
|
|
31
42
|
elif file_ext == '.toml':
|
|
32
43
|
data = toml.load(filepath)
|
|
33
44
|
elif file_ext == '.json':
|
|
34
|
-
with open(filepath) as f:
|
|
45
|
+
with open(filepath, 'r', encoding='utf-8') as f:
|
|
35
46
|
data = json.load(f)
|
|
36
|
-
except:
|
|
37
|
-
|
|
47
|
+
except Exception: # pylint: disable=broad-exception-caught
|
|
48
|
+
return []
|
|
38
49
|
|
|
39
50
|
if not isinstance(data, dict):
|
|
40
51
|
# We found a DEPS file, but it wasn't a table/dict so we can't return root keys
|
|
@@ -48,11 +59,17 @@ def get_markup_table_keys(partial_path='./'):
|
|
|
48
59
|
if not partial_path.endswith('/'):
|
|
49
60
|
prepend += '/'
|
|
50
61
|
|
|
51
|
-
# Return the list of keys w/ prepended path information, and don't include
|
|
52
|
-
|
|
62
|
+
# Return the list of keys w/ prepended path information, and don't include
|
|
63
|
+
# uppercase strings like 'DEFAULTS' or 'METADATA'
|
|
64
|
+
return [
|
|
65
|
+
prepend + x for x in list(data.keys()) if x.startswith(partial_target) and not x.isupper()
|
|
66
|
+
]
|
|
67
|
+
|
|
53
68
|
|
|
69
|
+
def main() -> None:
|
|
70
|
+
'''Returns None, prints DEPS keys space separated, uses sys.argv for a single
|
|
71
|
+
partial path arg (DEPS file to examine)'''
|
|
54
72
|
|
|
55
|
-
def main():
|
|
56
73
|
if len(sys.argv) > 1:
|
|
57
74
|
partial_path = sys.argv[1]
|
|
58
75
|
else:
|
opencos/files.py
CHANGED
|
@@ -33,14 +33,12 @@ def get_source_file(target:str) -> (bool, str, str):
|
|
|
33
33
|
# target exists as a file, return True w/ original target:
|
|
34
34
|
return True, target, ''
|
|
35
35
|
|
|
36
|
-
if
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
# along with the fpath and forced ext to use:
|
|
43
|
-
return True, fpath, FORCE_PREFIX_DICT.get(ext + '@', '')
|
|
36
|
+
if '@' in target:
|
|
37
|
+
for p in ALL_FORCED_PREFIXES:
|
|
38
|
+
if p in target:
|
|
39
|
+
fpath = ''.join(target.split(p)) # essentially just removing the "sv@" or whatever it is
|
|
40
|
+
if os.path.exists(fpath):
|
|
41
|
+
return True, fpath, FORCE_PREFIX_DICT.get(p)
|
|
44
42
|
|
|
45
43
|
# target or fpath didn't exist, return False with the original target:
|
|
46
44
|
return False, target, ''
|
opencos/names.py
CHANGED
opencos/oc_cli.py
CHANGED
|
@@ -657,7 +657,7 @@ class BlockLED(Block):
|
|
|
657
657
|
count = data & 0xff
|
|
658
658
|
self.dump_reg(0, 0x0004, "Prescale", [ [9,0,"CYCLES"] ])
|
|
659
659
|
for i in range(count):
|
|
660
|
-
self.dump_reg(0, 0x0008+i, "LedControl%d" % i, [ [18,16,"BLINKS"], [13,8,"BRIGHT"], [1,0,"MODE"] ])
|
|
660
|
+
self.dump_reg(0, 0x0008+(i*4), "LedControl%d" % i, [ [18,16,"BLINKS"], [13,8,"BRIGHT"], [1,0,"MODE"] ])
|
|
661
661
|
|
|
662
662
|
def command_set(self, parts=[], help_line=False):
|
|
663
663
|
if help_line: return "Set the state of the led: [on|off|blink|heartbeat] [brightness=0-63]";
|
|
@@ -700,6 +700,143 @@ class BlockLED(Block):
|
|
|
700
700
|
for i in range(self.numled): self.csr_write32(0, 8 + ( i*4), ((blinks<<16) | (bright<<8) | command) )
|
|
701
701
|
else: self.csr_write32(0, 8 + (led*4), ((blinks<<16) | (bright<<8) | command) )
|
|
702
702
|
|
|
703
|
+
class BlockRGB(Block):
|
|
704
|
+
def __init__(self, channel, blockid):
|
|
705
|
+
Block.__init__(self, channel, blockid, "RGB")
|
|
706
|
+
|
|
707
|
+
def connect(self):
|
|
708
|
+
Block.connect(self)
|
|
709
|
+
data = self.csr_read32(0, 0)
|
|
710
|
+
self.csrid = ((data >> 16) & 0xffff)
|
|
711
|
+
self.numrgb = ((data) & 0xff)
|
|
712
|
+
util.debug(f"Connect: {self} CsrId={self.csrid} NumRgb={self.numrgb}")
|
|
713
|
+
|
|
714
|
+
def rgb_status(self, rgb):
|
|
715
|
+
data = self.csr_read32(0, 8 + (rgb*4))
|
|
716
|
+
red = ((data >> 24) & 0x3f)
|
|
717
|
+
green = ((data >>16) & 0x3f)
|
|
718
|
+
blue = ((data >> 8) & 0x3f)
|
|
719
|
+
if (data & 0x1) == 1: return (f"on ({red:3d} {green:3d} {blue:3d})")
|
|
720
|
+
elif (data & 0x2) == 2: return (f"blink ({red:3d} {green:3d} {blue:3d})")
|
|
721
|
+
elif (data & 0x3) == 3: return (f"heartbeat ({red:3d} {green:3d} {blue:3d})")
|
|
722
|
+
return "off"
|
|
723
|
+
|
|
724
|
+
def command_show(self, parts=[], help_line=False):
|
|
725
|
+
if help_line: return "Shows high level status of the block in human readable form";
|
|
726
|
+
for i in range(self.numrgb):
|
|
727
|
+
util.info(f"RGB {i:3}: {self.rgb_status(i):20}")
|
|
728
|
+
|
|
729
|
+
def command_dump(self, parts=[], help_line=False):
|
|
730
|
+
if help_line: return "Dumps detailed config/status info of the block in human readable form";
|
|
731
|
+
self.info(f"Dumping {self}:")
|
|
732
|
+
data = self.dump_reg(0, 0x0000, "OcID", [ [31,16,"ID"], [7,0,"RGB_COUNT"] ])
|
|
733
|
+
count = data & 0xff
|
|
734
|
+
self.dump_reg(0, 0x0004, "Prescale", [ [9,0,"CYCLES"] ])
|
|
735
|
+
for i in range(count):
|
|
736
|
+
self.dump_reg(0, 0x0008+(i*4), "RgbControl%d" % i, [ [24,24,"RED"], [16,16,"GREEN"], [8,8,"BLUE"], [0,0,"MODE"] ])
|
|
737
|
+
|
|
738
|
+
def command_set(self, parts=[], help_line=False):
|
|
739
|
+
if help_line: return "Set the state of the rgb: [on|off|blink|heartbeat] [red=0-63] [green=0-63] [blue=0-63]";
|
|
740
|
+
rgb = -1
|
|
741
|
+
red = 0x3f
|
|
742
|
+
green = 0x3f
|
|
743
|
+
blue = 0x3f
|
|
744
|
+
command = -1 # report status
|
|
745
|
+
prescale = -1
|
|
746
|
+
while (len(parts)):
|
|
747
|
+
cmd = parts.pop(0)
|
|
748
|
+
if (eval_regex(r'^(\d+)$', cmd)):
|
|
749
|
+
rgb = eval_number(m.group(1))
|
|
750
|
+
elif (eval_regex(r'^status$', cmd)):
|
|
751
|
+
command = -1
|
|
752
|
+
elif (eval_regex(r'^off$', cmd)):
|
|
753
|
+
command = 0
|
|
754
|
+
elif (eval_regex(r'^on$', cmd)):
|
|
755
|
+
command = 1
|
|
756
|
+
elif (eval_regex(r'^blink$', cmd)):
|
|
757
|
+
command = 2
|
|
758
|
+
elif (eval_regex(r'^(?:heart)?beat$', cmd)):
|
|
759
|
+
command = 3
|
|
760
|
+
elif (eval_regex(r'^prescale=(.+)', cmd)):
|
|
761
|
+
prescale = eval_number(m.group(1), check_uint_bits=10, check_int_min=5)
|
|
762
|
+
elif (eval_regex(r'^red=(.+)', cmd)):
|
|
763
|
+
red = eval_number(m.group(1), check_uint_bits=6)
|
|
764
|
+
elif (eval_regex(r'^green=(.+)', cmd)):
|
|
765
|
+
green = eval_number(m.group(1), check_uint_bits=6)
|
|
766
|
+
elif (eval_regex(r'^blue=(.+)', cmd)):
|
|
767
|
+
blue = eval_number(m.group(1), check_uint_bits=6)
|
|
768
|
+
else:
|
|
769
|
+
util.error(f"RGB didn't understand arg(s): {parts}", do_exit=False)
|
|
770
|
+
raise HandledError
|
|
771
|
+
if prescale != -1:
|
|
772
|
+
self.csr_write32(0, 4, prescale)
|
|
773
|
+
if command == -1: # we reporting status
|
|
774
|
+
if rgb == -1: # on all RGBs
|
|
775
|
+
for i in range(self.numrgb): print("RGB %3d : %s" % ( i, self.rgb_status( i)))
|
|
776
|
+
else: print("RGB %3d : %s" % (rgb, self.rgb_status(rgb)))
|
|
777
|
+
else:
|
|
778
|
+
if rgb == -1: # on all RGBs
|
|
779
|
+
for i in range(self.numrgb): self.csr_write32(0, 8 + ( i*4), ((blue<<24) | (green<<16) | (red<<8) | command) )
|
|
780
|
+
else: self.csr_write32(0, 8 + (rgb*4), ((blue<<24) | (green<<16) | (red<<8) | command) )
|
|
781
|
+
|
|
782
|
+
class BlockToggle(Block):
|
|
783
|
+
def __init__(self, channel, blockid):
|
|
784
|
+
Block.__init__(self, channel, blockid, "Toggle")
|
|
785
|
+
|
|
786
|
+
def connect(self):
|
|
787
|
+
Block.connect(self)
|
|
788
|
+
data = self.csr_read32(0, 0)
|
|
789
|
+
self.csrid = ((data >> 16) & 0xffff)
|
|
790
|
+
self.numtoggle = ((data) & 0xff)
|
|
791
|
+
util.debug(f"Connect: {self} CsrId={self.csrid} NumToggle={self.numtoggle}")
|
|
792
|
+
|
|
793
|
+
def toggle_status(self, toggle):
|
|
794
|
+
data = self.csr_read32(0, 4 + (toggle*4))
|
|
795
|
+
if (data & 0x80000000): return "on"
|
|
796
|
+
return "off"
|
|
797
|
+
|
|
798
|
+
def command_show(self, parts=[], help_line=False):
|
|
799
|
+
if help_line: return "Shows high level status of the block in human readable form";
|
|
800
|
+
for i in range(self.numtoggle):
|
|
801
|
+
util.info(f"Toggle {i:3}: {self.toggle_status(i):20}")
|
|
802
|
+
|
|
803
|
+
def command_dump(self, parts=[], help_line=False):
|
|
804
|
+
if help_line: return "Dumps detailed config/status info of the block in human readable form";
|
|
805
|
+
self.info(f"Dumping {self}:")
|
|
806
|
+
data = self.dump_reg(0, 0x0000, "OcID", [ [31,16,"ID"], [7,0,"TOGGLE_COUNT"] ])
|
|
807
|
+
count = data & 0xff
|
|
808
|
+
for i in range(count):
|
|
809
|
+
self.dump_reg(0, 0x0004+(i*4), "ToggleStatus%d" % i, [ [31,31,"STATE"], [30,30,"EVENT"], [15,0,"COUNT"] ])
|
|
810
|
+
|
|
811
|
+
class BlockButton(Block):
|
|
812
|
+
def __init__(self, channel, blockid):
|
|
813
|
+
Block.__init__(self, channel, blockid, "Button")
|
|
814
|
+
|
|
815
|
+
def connect(self):
|
|
816
|
+
Block.connect(self)
|
|
817
|
+
data = self.csr_read32(0, 0)
|
|
818
|
+
self.csrid = ((data >> 16) & 0xffff)
|
|
819
|
+
self.numbutton = ((data) & 0xff)
|
|
820
|
+
util.debug(f"Connect: {self} CsrId={self.csrid} NumButton={self.numbutton}")
|
|
821
|
+
|
|
822
|
+
def button_status(self, button):
|
|
823
|
+
data = self.csr_read32(0, 4 + (button*4))
|
|
824
|
+
if (data & 0x80000000): return "on"
|
|
825
|
+
return "off"
|
|
826
|
+
|
|
827
|
+
def command_show(self, parts=[], help_line=False):
|
|
828
|
+
if help_line: return "Shows high level status of the block in human readable form";
|
|
829
|
+
for i in range(self.numbutton):
|
|
830
|
+
util.info(f"Button {i:3}: {self.button_status(i):20}")
|
|
831
|
+
|
|
832
|
+
def command_dump(self, parts=[], help_line=False):
|
|
833
|
+
if help_line: return "Dumps detailed config/status info of the block in human readable form";
|
|
834
|
+
self.info(f"Dumping {self}:")
|
|
835
|
+
data = self.dump_reg(0, 0x0000, "OcID", [ [31,16,"ID"], [7,0,"BUTTON_COUNT"] ])
|
|
836
|
+
count = data & 0xff
|
|
837
|
+
for i in range(count):
|
|
838
|
+
self.dump_reg(0, 0x0004+(i*4), "ButtonStatus%d" % i, [ [31,31,"STATE"], [30,30,"EVENT"], [15,0,"COUNT"] ])
|
|
839
|
+
|
|
703
840
|
class BlockIIC(Block):
|
|
704
841
|
def __init__(self, channel, blockid):
|
|
705
842
|
Block.__init__(self, channel, blockid, "IIC")
|
|
@@ -1498,6 +1635,11 @@ block_table[ 8] = { 'name' : 'Fan' , 'csrid' : 8, 'handler' : Block
|
|
|
1498
1635
|
block_table[ 9] = { 'name' : 'HBM' , 'csrid' : 9, 'handler' : Block }
|
|
1499
1636
|
block_table[10] = { 'name' : 'CMAC' , 'csrid' : 10, 'handler' : Block }
|
|
1500
1637
|
block_table[11] = { 'name' : 'PCIe' , 'csrid' : 11, 'handler' : Block }
|
|
1638
|
+
block_table[12] = { 'name' : 'Eth1G' , 'csrid' : 12, 'handler' : Block }
|
|
1639
|
+
block_table[13] = { 'name' : 'Eth10G' , 'csrid' : 13, 'handler' : Block }
|
|
1640
|
+
block_table[14] = { 'name' : 'RGB' , 'csrid' : 14, 'handler' : BlockRGB }
|
|
1641
|
+
block_table[15] = { 'name' : 'Toggle' , 'csrid' : 15, 'handler' : BlockToggle }
|
|
1642
|
+
block_table[16] = { 'name' : 'Button' , 'csrid' : 16, 'handler' : BlockButton }
|
|
1501
1643
|
|
|
1502
1644
|
# **************************************************************
|
|
1503
1645
|
# *** Channels
|