opencos-eda 0.3.3__py3-none-any.whl → 0.3.6__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/sim.py +16 -6
- opencos/deps/defaults.py +1 -0
- opencos/deps/deps_processor.py +60 -24
- opencos/deps_schema.py +17 -0
- opencos/eda.py +13 -19
- opencos/eda_base.py +78 -29
- opencos/eda_config.py +86 -41
- opencos/eda_config_defaults.yml +8 -1
- opencos/eda_extract_targets.py +0 -0
- opencos/eda_tool_helper.py +19 -0
- opencos/export_helper.py +89 -31
- opencos/files.py +3 -1
- opencos/hw/oc_cli.py +0 -0
- opencos/tests/helpers.py +60 -17
- opencos/tests/test_eda.py +11 -1
- opencos/tools/cocotb.py +94 -21
- opencos/tools/iverilog.py +4 -0
- opencos/tools/riviera.py +23 -12
- opencos/tools/verilator.py +91 -2
- opencos/tools/vivado.py +1 -0
- opencos/util.py +86 -57
- opencos/utils/str_helpers.py +6 -1
- opencos/utils/subprocess_helpers.py +66 -3
- {opencos_eda-0.3.3.dist-info → opencos_eda-0.3.6.dist-info}/METADATA +12 -3
- {opencos_eda-0.3.3.dist-info → opencos_eda-0.3.6.dist-info}/RECORD +28 -28
- {opencos_eda-0.3.3.dist-info → opencos_eda-0.3.6.dist-info}/WHEEL +0 -0
- {opencos_eda-0.3.3.dist-info → opencos_eda-0.3.6.dist-info}/entry_points.txt +0 -0
- {opencos_eda-0.3.3.dist-info → opencos_eda-0.3.6.dist-info}/licenses/LICENSE +0 -0
- {opencos_eda-0.3.3.dist-info → opencos_eda-0.3.6.dist-info}/licenses/LICENSE.spdx +0 -0
- {opencos_eda-0.3.3.dist-info → opencos_eda-0.3.6.dist-info}/top_level.txt +0 -0
opencos/eda_config.py
CHANGED
|
@@ -15,10 +15,14 @@ import shutil
|
|
|
15
15
|
import mergedeep
|
|
16
16
|
|
|
17
17
|
from opencos import util
|
|
18
|
+
from opencos.util import safe_emoji
|
|
18
19
|
from opencos.utils.markup_helpers import yaml_safe_load, yaml_safe_writer
|
|
19
20
|
|
|
20
21
|
class Defaults:
|
|
21
|
-
'''Defaults is a global
|
|
22
|
+
'''Defaults is a global namespace for constants and supported features.
|
|
23
|
+
|
|
24
|
+
Defaults.config_yml is set depending on search order for default eda_config[_defaults].yml
|
|
25
|
+
'''
|
|
22
26
|
|
|
23
27
|
environ_override_config_yml = os.environ.get('EDA_CONFIG_YML', '')
|
|
24
28
|
home_override_config_yml = os.path.join(
|
|
@@ -63,14 +67,15 @@ class Defaults:
|
|
|
63
67
|
'simulate-args',
|
|
64
68
|
'simulate-waves-args',
|
|
65
69
|
'simulate-waivers',
|
|
70
|
+
'simulate-coverage-tcl',
|
|
66
71
|
'coverage-args',
|
|
67
72
|
])
|
|
68
73
|
|
|
69
74
|
EDA_OUTPUT_CONFIG_FNAME = 'eda_output_config.yml'
|
|
70
75
|
|
|
71
|
-
if os.path.
|
|
76
|
+
if os.path.isfile(Defaults.environ_override_config_yml):
|
|
72
77
|
Defaults.config_yml = Defaults.environ_override_config_yml
|
|
73
|
-
elif os.path.
|
|
78
|
+
elif os.path.isfile(Defaults.home_override_config_yml):
|
|
74
79
|
Defaults.config_yml = Defaults.home_override_config_yml
|
|
75
80
|
else:
|
|
76
81
|
Defaults.config_yml = Defaults.opencos_config_yml
|
|
@@ -149,29 +154,9 @@ def update_config_auto_tool_order_for_tool(tool: str, config: dict) -> str:
|
|
|
149
154
|
Input arg tool can be in the form (for example):
|
|
150
155
|
tool='verlator', tool='verilator=/path/to/verilator.exe'
|
|
151
156
|
|
|
152
|
-
Performs no update if tool has no = in it. Returns tool (str) w/out = in it
|
|
157
|
+
Performs no update if tool has no = or : in it. Returns tool (str) w/out = in it
|
|
153
158
|
'''
|
|
154
|
-
|
|
155
|
-
return tool
|
|
156
|
-
|
|
157
|
-
tool, user_exe = tool.split('=')[0:2]
|
|
158
|
-
|
|
159
|
-
user_exe = shutil.which(user_exe)
|
|
160
|
-
|
|
161
|
-
# try adding to $PATH if in form --tool=/path/to/exe
|
|
162
|
-
tool_try_add_to_path(tool)
|
|
163
|
-
|
|
164
|
-
if tool not in config['auto_tools_order'][0]:
|
|
165
|
-
return tool
|
|
166
|
-
if not user_exe:
|
|
167
|
-
return tool
|
|
168
|
-
|
|
169
|
-
old_exe = config['auto_tools_order'][0][tool].get('exe', str())
|
|
170
|
-
if isinstance(old_exe, list):
|
|
171
|
-
config['auto_tools_order'][0][tool]['exe'][0] = user_exe
|
|
172
|
-
else:
|
|
173
|
-
config['auto_tools_order'][0][tool]['exe'] = user_exe
|
|
174
|
-
return tool
|
|
159
|
+
return tool_try_add_to_path(tool=tool, config=config, update_config=True)
|
|
175
160
|
|
|
176
161
|
|
|
177
162
|
def update_config_auto_tool_order_for_tools(tools: list, config: dict) -> list:
|
|
@@ -247,7 +232,7 @@ def get_config_merged_with_defaults(config:dict) -> dict:
|
|
|
247
232
|
def get_argparser() -> argparse.ArgumentParser:
|
|
248
233
|
'''Returns an ArgumentParser, handles --config-yml=<filename> arg'''
|
|
249
234
|
parser = argparse.ArgumentParser(
|
|
250
|
-
prog='opencos eda config options', add_help=False, allow_abbrev=False
|
|
235
|
+
prog=f'{safe_emoji("🔎 ")}opencos eda config options', add_help=False, allow_abbrev=False
|
|
251
236
|
)
|
|
252
237
|
parser.add_argument('--config-yml', type=str, default=Defaults.config_yml,
|
|
253
238
|
help=('YAML filename to use for configuration (default'
|
|
@@ -327,32 +312,92 @@ def write_eda_config_and_args(
|
|
|
327
312
|
yaml_safe_writer(data=data, filepath=fullpath)
|
|
328
313
|
|
|
329
314
|
|
|
330
|
-
def
|
|
331
|
-
'''
|
|
315
|
+
def tool_arg_get_parts(tool: str) -> list:
|
|
316
|
+
'''Given a tool (str or None) that may be in form <name>=/path/to/something
|
|
317
|
+
|
|
318
|
+
Return the parts [<name>, <path>, ..]
|
|
319
|
+
'''
|
|
320
|
+
if not tool or ('=' not in tool and ':' not in tool):
|
|
321
|
+
return [tool]
|
|
322
|
+
|
|
323
|
+
if '=' in tool:
|
|
324
|
+
parts = tool.split('=')
|
|
325
|
+
else:
|
|
326
|
+
parts = tool.split(':')
|
|
327
|
+
|
|
328
|
+
return parts
|
|
329
|
+
|
|
330
|
+
def tool_arg_remove_path_information(tool: str) -> str:
|
|
331
|
+
'''Given a tool (str or None) that may be in form <name>=/path/to/something
|
|
332
|
+
|
|
333
|
+
Return the <name> only
|
|
334
|
+
'''
|
|
335
|
+
if not tool:
|
|
336
|
+
return tool
|
|
337
|
+
return tool_arg_get_parts(tool)[0]
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def tool_try_add_to_path( # pylint: disable=too-many-branches
|
|
341
|
+
tool: str, config: dict, update_config: bool
|
|
342
|
+
) -> str:
|
|
343
|
+
'''Since we support --tool=<name>=/path/to/bin[/exe], attempt to prepend $PATH
|
|
344
|
+
|
|
345
|
+
(also works for --tool=<name>:/path/to/bin[/exe] )
|
|
332
346
|
|
|
333
347
|
with this information for this tool (which will nicely affect all subprocesses,
|
|
334
348
|
but not wreck our original shell).'''
|
|
335
349
|
|
|
336
|
-
|
|
337
|
-
|
|
350
|
+
name_path_parts = tool_arg_get_parts(tool)
|
|
351
|
+
if len(name_path_parts) == 1:
|
|
352
|
+
return name_path_parts[0]
|
|
353
|
+
|
|
354
|
+
name, path_arg = name_path_parts[0:2]
|
|
355
|
+
|
|
356
|
+
if name not in config['auto_tools_order'][0]:
|
|
357
|
+
return name
|
|
358
|
+
|
|
359
|
+
config_exe = config['auto_tools_order'][0][name].get('exe', str())
|
|
360
|
+
if isinstance(config_exe, list):
|
|
361
|
+
orig_exe = config_exe[0]
|
|
362
|
+
else:
|
|
363
|
+
orig_exe = config_exe
|
|
338
364
|
|
|
339
|
-
|
|
340
|
-
|
|
365
|
+
if path_arg and os.path.isfile(path_arg):
|
|
366
|
+
# Someone passes us --tool=<name>=/path/to/bin/exe, remove the exe from path:
|
|
367
|
+
path, exe = os.path.split(path_arg)
|
|
368
|
+
elif path_arg and os.path.isdir(path_arg):
|
|
341
369
|
# Someone passes us --tool=<name>=/path/to/bin/ (did not have exe)
|
|
342
|
-
path =
|
|
370
|
+
path, exe = path_arg, orig_exe
|
|
343
371
|
else:
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
if not path:
|
|
347
|
-
|
|
372
|
+
path, exe = '', ''
|
|
373
|
+
|
|
374
|
+
if not path or not exe:
|
|
375
|
+
util.error(f'Can not find path or exe for --tool={tool}: {name=} path={path_arg}')
|
|
376
|
+
return name
|
|
348
377
|
|
|
349
378
|
path = os.path.abspath(path)
|
|
350
379
|
if os.path.isdir(path):
|
|
351
|
-
paths = os.environ
|
|
380
|
+
paths = os.environ.get('PATH', '').split(':')
|
|
352
381
|
if path not in paths:
|
|
353
|
-
os.environ['PATH'] = path + ':' + os.environ['PATH']
|
|
354
382
|
util.info(f'--tool={tool} has path information, prepending PATH with: {path}')
|
|
383
|
+
os.environ['PATH'] = path + ':' + os.environ.get('PATH', '')
|
|
355
384
|
else:
|
|
356
385
|
util.info(f'--tool={tool} has path information, but {path} already in $PATH')
|
|
357
|
-
|
|
358
|
-
|
|
386
|
+
|
|
387
|
+
user_exe = os.path.join(path, exe)
|
|
388
|
+
if not os.access(user_exe, os.X_OK):
|
|
389
|
+
util.error(f'--tool setting for {tool}: {user_exe} is not an executable')
|
|
390
|
+
return name
|
|
391
|
+
|
|
392
|
+
user_exe = shutil.which(exe)
|
|
393
|
+
|
|
394
|
+
if update_config:
|
|
395
|
+
if isinstance(config_exe, list):
|
|
396
|
+
config['auto_tools_order'][0][name]['exe'][0] = user_exe
|
|
397
|
+
else:
|
|
398
|
+
config['auto_tools_order'][0][name]['exe'] = user_exe
|
|
399
|
+
util.debug(f'For {tool=}, auto_tools_order config updated')
|
|
400
|
+
|
|
401
|
+
util.debug(f'For {tool=}, final {user_exe=}')
|
|
402
|
+
|
|
403
|
+
return name
|
opencos/eda_config_defaults.yml
CHANGED
|
@@ -94,6 +94,11 @@ file_extensions:
|
|
|
94
94
|
dotf:
|
|
95
95
|
- .f
|
|
96
96
|
- .vc
|
|
97
|
+
python:
|
|
98
|
+
- .py
|
|
99
|
+
makefile:
|
|
100
|
+
- .mk
|
|
101
|
+
|
|
97
102
|
|
|
98
103
|
inferred_top:
|
|
99
104
|
# file extensions that we can infer "top" module from, if --top omitted.
|
|
@@ -247,6 +252,9 @@ tools:
|
|
|
247
252
|
+accb +accr +access +r+w
|
|
248
253
|
coverage-args: |
|
|
249
254
|
-acdb -acdb_cov sbfectapm
|
|
255
|
+
simulate-coverage-tcl:
|
|
256
|
+
- acdb save
|
|
257
|
+
- acdb report -db work.acdb -txt -o cov.txt
|
|
250
258
|
|
|
251
259
|
|
|
252
260
|
modelsim_ase:
|
|
@@ -329,7 +337,6 @@ tools:
|
|
|
329
337
|
- "COCOTB_TEST_FAILED"
|
|
330
338
|
log-must-strings:
|
|
331
339
|
- "passed"
|
|
332
|
-
- "Cocotb test completed successfully!"
|
|
333
340
|
|
|
334
341
|
|
|
335
342
|
quartus:
|
opencos/eda_extract_targets.py
CHANGED
|
File without changes
|
opencos/eda_tool_helper.py
CHANGED
|
@@ -68,3 +68,22 @@ def get_all_handler_commands(config=None, tools_loaded=None) -> dict:
|
|
|
68
68
|
all_handler_commands[command].append(tool)
|
|
69
69
|
|
|
70
70
|
return all_handler_commands
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def get_handler_tool_version(tool: str, eda_command: str, config: dict) -> str:
|
|
74
|
+
'''Attempts to get a Command Handler's version given tool + eda_command'''
|
|
75
|
+
|
|
76
|
+
entry = config['auto_tools_order'][0].get(tool, {})
|
|
77
|
+
if not entry:
|
|
78
|
+
return ''
|
|
79
|
+
|
|
80
|
+
handler_name = entry.get('handlers', {}).get(eda_command, '')
|
|
81
|
+
if not handler_name:
|
|
82
|
+
return ''
|
|
83
|
+
|
|
84
|
+
module = util.import_class_from_string(handler_name)
|
|
85
|
+
obj = module(config=config)
|
|
86
|
+
if not getattr(obj, 'get_versions', None):
|
|
87
|
+
return ''
|
|
88
|
+
|
|
89
|
+
return obj.get_versions()
|
opencos/export_helper.py
CHANGED
|
@@ -42,12 +42,18 @@ def json_paths_to_jsonl(
|
|
|
42
42
|
with open(output_json_path, 'w', encoding='utf-8') as outf:
|
|
43
43
|
|
|
44
44
|
# jsonl is every line of the file is a json.
|
|
45
|
+
# We would expect the JSON to have "tests": [ ... ] with >= 1 test.
|
|
45
46
|
for json_file_path in json_file_paths:
|
|
46
47
|
with open(json_file_path, encoding='utf-8') as f:
|
|
47
48
|
data = json.load(f)
|
|
48
49
|
if len(assert_json_types) > 0 and type(data) not in assert_json_types:
|
|
49
50
|
error(f'{json_file_path=} JSON data is not a Table (py dict) {type(data)=}')
|
|
50
|
-
|
|
51
|
+
if 'tests' in data and isinstance(data['tests'], list):
|
|
52
|
+
for test in data['tests']:
|
|
53
|
+
json.dump(test, outf)
|
|
54
|
+
else:
|
|
55
|
+
json.dump(data, outf)
|
|
56
|
+
|
|
51
57
|
outf.write('\n')
|
|
52
58
|
info(f'Wrote {len(json_file_paths)} tests to {output_json_path=}')
|
|
53
59
|
|
|
@@ -78,7 +84,12 @@ def json_paths_to_single_json(
|
|
|
78
84
|
data = json.load(f)
|
|
79
85
|
if len(assert_json_types) > 0 and type(data) not in assert_json_types:
|
|
80
86
|
error(f'{json_file_path=} JSON data is not a Table (py dict) {type(data)=}')
|
|
81
|
-
|
|
87
|
+
if 'tests' in data and isinstance(data['tests'], list):
|
|
88
|
+
for test in data['tests']:
|
|
89
|
+
out_json_data['tests'].append(test)
|
|
90
|
+
else:
|
|
91
|
+
out_json_data['tests'].append(data)
|
|
92
|
+
|
|
82
93
|
json.dump(out_json_data, outf)
|
|
83
94
|
outf.write('\n')
|
|
84
95
|
info(f'Wrote {len(json_file_paths)} tests {output_json_path=}')
|
|
@@ -471,7 +482,52 @@ class ExportHelper:
|
|
|
471
482
|
yaml_safe_writer(data=data, filepath=dst)
|
|
472
483
|
|
|
473
484
|
|
|
474
|
-
def
|
|
485
|
+
def create_combined_env_file_in_out_dir(self) -> list:
|
|
486
|
+
''' Returns list of all .env lines to be put in output directory
|
|
487
|
+
|
|
488
|
+
Creates single exported .env file if any --env-file(s)
|
|
489
|
+
were loaded by CLI or DEPS targets. --input-file/-f was handled as args
|
|
490
|
+
already, but --env-files need to be special cased. We do not add this
|
|
491
|
+
combined ".env" file to the all_files list, just build it on the fly if needed.
|
|
492
|
+
'''
|
|
493
|
+
env_lines = []
|
|
494
|
+
for filepath in util.env_files_loaded:
|
|
495
|
+
with open(filepath, encoding='utf-8') as f:
|
|
496
|
+
env_lines.extend(f.readlines() + ['\n'])
|
|
497
|
+
|
|
498
|
+
if env_lines:
|
|
499
|
+
dst = os.path.join(self.out_dir, '.env')
|
|
500
|
+
|
|
501
|
+
# Write this to the export directory too:
|
|
502
|
+
with open(dst, 'w', encoding='utf-8') as f:
|
|
503
|
+
for lineno, line in enumerate(env_lines):
|
|
504
|
+
|
|
505
|
+
# modify VERILOG_SOURCES line if present, although it is known via DEPS.yml,
|
|
506
|
+
# we can set it according to files_v and files_sv:
|
|
507
|
+
if line.strip().startswith('VERILOG_SOURCES'):
|
|
508
|
+
base_verilog_filenames = [
|
|
509
|
+
os.path.split(x)[1] for x in \
|
|
510
|
+
self.cmd_design_obj.files_v + self.cmd_design_obj.files_sv
|
|
511
|
+
]
|
|
512
|
+
env_lines[lineno] = (
|
|
513
|
+
f'VERILOG_SOURCES = {" ".join(base_verilog_filenames)}'
|
|
514
|
+
)
|
|
515
|
+
continue
|
|
516
|
+
|
|
517
|
+
# remove PYTHONPATH from .env
|
|
518
|
+
if line.strip().startswith('PYTHONPATH'):
|
|
519
|
+
env_lines[lineno] = ''
|
|
520
|
+
continue
|
|
521
|
+
|
|
522
|
+
f.write(line)
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
info(f'export_helper: Wrote {dst}')
|
|
526
|
+
|
|
527
|
+
return env_lines
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def create_export_json_in_out_dir( # pylint: disable=unused-argument,too-many-locals,too-many-branches
|
|
475
531
|
self, eda_config:dict={}, **kwargs
|
|
476
532
|
) -> None:
|
|
477
533
|
'''Optionally creates an exported JSON file in the output directory'''
|
|
@@ -482,40 +538,43 @@ class ExportHelper:
|
|
|
482
538
|
# assumes we've run self.create_deps_yml_in_out_dir():
|
|
483
539
|
assert self.target
|
|
484
540
|
assert self.out_deps_file
|
|
541
|
+
full_eda_cmd_list = ['eda', self.eda_command]
|
|
542
|
+
if self.args.get('waves', False):
|
|
543
|
+
full_eda_cmd_list.append('--waves')
|
|
544
|
+
if self.args.get('tool', ''):
|
|
545
|
+
full_eda_cmd_list.append(f'--tool={self.args["tool"]}')
|
|
546
|
+
full_eda_cmd_list.append(self.target)
|
|
485
547
|
|
|
486
548
|
data = {
|
|
487
|
-
'
|
|
488
|
-
'
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
'targets': [self.target],
|
|
493
|
-
'args': [],
|
|
494
|
-
'waves': self.args.get('waves', False),
|
|
495
|
-
# tool - eda.CommandSimVerilator has this set in self.args:
|
|
496
|
-
'tool': self.args.get('tool', None),
|
|
497
|
-
},
|
|
498
|
-
'files': [],
|
|
549
|
+
'correlationId': self.target,
|
|
550
|
+
'jobType': 'edaCmd',
|
|
551
|
+
'cmd': ' '.join(full_eda_cmd_list),
|
|
552
|
+
'timeout': 600,
|
|
553
|
+
'filesList': [], # filename (str), content (str)
|
|
499
554
|
}
|
|
500
555
|
|
|
501
|
-
# allow caller to override eda - tool, or eda - args, etc.
|
|
502
|
-
for k,v in eda_config.items():
|
|
503
|
-
if k in data['eda'] and v is not None:
|
|
504
|
-
data['eda'][k] = v
|
|
505
|
-
|
|
506
556
|
# Note that args may already be set via:
|
|
507
557
|
# create_deps_yml_in_out_dir(deps_file_args=some_list)
|
|
508
558
|
# For example, eda.CommandSim.do_export() will set certain allow-listed
|
|
509
559
|
# args if present with non-default values.
|
|
510
560
|
|
|
511
|
-
|
|
512
561
|
all_files = [self.out_deps_file] + self.included_files \
|
|
513
562
|
+ self.cmd_design_obj.files_sv + self.cmd_design_obj.files_v \
|
|
514
563
|
+ self.cmd_design_obj.files_vhd + self.cmd_design_obj.files_cpp \
|
|
564
|
+
+ self.cmd_design_obj.files_sdc + self.cmd_design_obj.files_py \
|
|
565
|
+
+ self.cmd_design_obj.files_makefile + self.cmd_design_obj.files_non_source
|
|
566
|
+
|
|
567
|
+
all_files = list(dict.fromkeys(all_files)) # uniqify list.
|
|
568
|
+
|
|
569
|
+
# The last file we handle is a single exported .env file if any --env-file(s)
|
|
570
|
+
env_lines = self.create_combined_env_file_in_out_dir()
|
|
571
|
+
if env_lines:
|
|
572
|
+
# write the updated env_lines to the export.json file.
|
|
573
|
+
data['filesList'].append({
|
|
574
|
+
'filename': '.env',
|
|
575
|
+
'content': ''.join(env_lines), # already has \n per line
|
|
576
|
+
})
|
|
515
577
|
|
|
516
|
-
for x in self.cmd_design_obj.files_non_source:
|
|
517
|
-
if x not in all_files:
|
|
518
|
-
all_files.append(x)
|
|
519
578
|
|
|
520
579
|
for somefile in all_files:
|
|
521
580
|
|
|
@@ -532,18 +591,17 @@ class ExportHelper:
|
|
|
532
591
|
|
|
533
592
|
assert os.path.exists(somefile)
|
|
534
593
|
with open(somefile, encoding='utf-8') as f:
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
'
|
|
538
|
-
'content': filestr,
|
|
594
|
+
data['filesList'].append({
|
|
595
|
+
'filename': os.path.split(somefile)[1],
|
|
596
|
+
'content': ''.join(f.readlines()),
|
|
539
597
|
})
|
|
540
598
|
|
|
541
|
-
|
|
599
|
+
test_runner_data = {'tests': [data]} # single test for test runner.
|
|
542
600
|
dst = os.path.join(self.out_dir, 'export.json')
|
|
543
601
|
with open(dst, 'w', encoding='utf-8') as f:
|
|
544
|
-
json.dump(
|
|
602
|
+
json.dump(test_runner_data, f)
|
|
545
603
|
f.write('\n')
|
|
546
|
-
info(f'export_helper: Wrote {dst
|
|
604
|
+
info(f'export_helper: Wrote {dst}')
|
|
547
605
|
|
|
548
606
|
# If this was from an `export` command, and the self.out_dir != self.args['work-dir'], then
|
|
549
607
|
# copy the export.json to the work-dir:
|
opencos/files.py
CHANGED
opencos/hw/oc_cli.py
CHANGED
|
File without changes
|
opencos/tests/helpers.py
CHANGED
|
@@ -16,6 +16,7 @@ from opencos.utils.subprocess_helpers import subprocess_run_background
|
|
|
16
16
|
|
|
17
17
|
# Figure out what tools the system has available, without calling eda.main(..)
|
|
18
18
|
config, tools_loaded = eda_tool_helper.get_config_and_tools_loaded()
|
|
19
|
+
known_tool_versions = {}
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
def eda_wrap_is_sim_fail(rc: int, quiet: bool = False) -> bool:
|
|
@@ -32,6 +33,16 @@ def eda_wrap_is_sim_fail(rc: int, quiet: bool = False) -> bool:
|
|
|
32
33
|
status_constants.EDA_DEFAULT_ERROR
|
|
33
34
|
)
|
|
34
35
|
|
|
36
|
+
def handle_tool_version(tool: str, eda_command: str, cfg: dict = config) -> None:
|
|
37
|
+
'''Attempts to use a command handler for tool + eda_command and we'll
|
|
38
|
+
track the version globally'''
|
|
39
|
+
|
|
40
|
+
if not known_tool_versions.get(tool, ''):
|
|
41
|
+
handler_version = eda_tool_helper.get_handler_tool_version(
|
|
42
|
+
tool=tool, eda_command=eda_command, config=cfg
|
|
43
|
+
)
|
|
44
|
+
known_tool_versions[tool] = handler_version
|
|
45
|
+
|
|
35
46
|
def can_run_eda_command(*commands, cfg: dict = config) -> bool:
|
|
36
47
|
'''Returns True if we have any installed tool that can run: eda <command>'''
|
|
37
48
|
runnable = []
|
|
@@ -50,10 +61,36 @@ def can_run_eda_command(*commands, cfg: dict = config) -> bool:
|
|
|
50
61
|
if entry and entry.get('disable-auto', False):
|
|
51
62
|
# This tool cannot automatically run our command.
|
|
52
63
|
return False
|
|
64
|
+
# While we're here, set known tool versions.
|
|
65
|
+
handle_tool_version(tool=tool, eda_command=command, cfg=cfg)
|
|
53
66
|
|
|
54
67
|
runnable.append(True)
|
|
55
68
|
return runnable and all(runnable)
|
|
56
69
|
|
|
70
|
+
def can_uvm(tool: str) -> bool:
|
|
71
|
+
'''Returns True if we can run UVM, per tool'''
|
|
72
|
+
|
|
73
|
+
if tool not in tools_loaded:
|
|
74
|
+
return False
|
|
75
|
+
|
|
76
|
+
if tool == 'verilator':
|
|
77
|
+
# requires UVM_HOME to be set with uvm_pkg.sv existing.
|
|
78
|
+
uvm_home = os.environ.get('UVM_HOME', '')
|
|
79
|
+
uvm_pkg = os.path.join(uvm_home, 'uvm_pkg.sv')
|
|
80
|
+
if not all((os.path.isdir(uvm_home), os.path.isfile(uvm_pkg))):
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
handle_tool_version(tool=tool, eda_command='sim', cfg=config)
|
|
84
|
+
version_list = known_tool_versions.get('verilator', '').split('.')
|
|
85
|
+
if int(version_list[0]) < 5 or \
|
|
86
|
+
(int(version_list[0]) == 5 and int(version_list[1]) < 42):
|
|
87
|
+
return False
|
|
88
|
+
|
|
89
|
+
return True
|
|
90
|
+
|
|
91
|
+
# default return False:
|
|
92
|
+
return False
|
|
93
|
+
|
|
57
94
|
def can_run_eda_sim(cfg: dict = config) -> bool:
|
|
58
95
|
'''Returns True if we have any installed tool that can run: eda sim'''
|
|
59
96
|
return can_run_eda_command('sim', cfg=cfg)
|
|
@@ -127,38 +164,43 @@ def assert_gen_deps_yml_good(filepath:str, want_target:str='') -> dict:
|
|
|
127
164
|
|
|
128
165
|
def assert_export_json_good(filepath:str) -> dict:
|
|
129
166
|
'''Checks that an exported JSON (from eda export, or eda <command> --export) has known keys'''
|
|
130
|
-
assert os.path.
|
|
167
|
+
assert os.path.isfile(filepath), f'{filepath=} does not exist'
|
|
131
168
|
with open(filepath, encoding='utf-8') as f:
|
|
132
169
|
data = json.load(f)
|
|
133
|
-
assert '
|
|
134
|
-
assert '
|
|
135
|
-
|
|
170
|
+
assert 'tests' in data
|
|
171
|
+
assert len(data.get('tests', [])) >= 1
|
|
172
|
+
for test in data.get('tests', []):
|
|
173
|
+
check_test_runner_schema(test)
|
|
136
174
|
return data
|
|
137
175
|
|
|
176
|
+
def check_test_runner_schema(test: dict) -> None:
|
|
177
|
+
'''Confirm that a single test's JSON/JSONL schema is OK.'''
|
|
178
|
+
assert 'correlationId' in test
|
|
179
|
+
assert 'jobType' in test
|
|
180
|
+
assert 'cmd' in test
|
|
181
|
+
assert 'filesList' in test # 0 files is OK.
|
|
182
|
+
|
|
138
183
|
|
|
139
184
|
def assert_export_jsonl_good(filepath:str, jsonl:bool=True) -> list:
|
|
140
185
|
'''Checks that an exported JSONL (from eda multi --export) has known keys'''
|
|
141
|
-
assert os.path.
|
|
186
|
+
assert os.path.isfile(filepath), f'{filepath=} does not exist'
|
|
142
187
|
ret = []
|
|
143
188
|
with open(filepath, encoding='utf-8') as f:
|
|
144
189
|
if jsonl:
|
|
190
|
+
print(f'Using JSONL for {filepath=}')
|
|
145
191
|
for line in f.readlines():
|
|
146
192
|
line = line.strip()
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
assert any(x in data for x in ['files', 'tb'])
|
|
151
|
-
ret.append(data)
|
|
193
|
+
test = json.loads(line)
|
|
194
|
+
check_test_runner_schema(test)
|
|
195
|
+
ret.append(test)
|
|
152
196
|
else:
|
|
197
|
+
print(f'Using JSON for {filepath=}')
|
|
153
198
|
data = json.load(f)
|
|
154
199
|
assert 'tests' in data
|
|
155
|
-
assert
|
|
156
|
-
for
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
assert any(x in entry for x in ['files', 'tb'])
|
|
160
|
-
ret.append(entry)
|
|
161
|
-
|
|
200
|
+
assert len(data.get('tests', [])) >= 1
|
|
201
|
+
for test in data.get('tests', []):
|
|
202
|
+
check_test_runner_schema(test)
|
|
203
|
+
ret.append(test)
|
|
162
204
|
|
|
163
205
|
return ret
|
|
164
206
|
|
|
@@ -228,6 +270,7 @@ class Helpers:
|
|
|
228
270
|
background=True,
|
|
229
271
|
tee_fpath=logfile
|
|
230
272
|
)
|
|
273
|
+
print(f'Wrote: {os.path.abspath(logfile)=}')
|
|
231
274
|
else:
|
|
232
275
|
with open(logfile, 'w', encoding='utf-8') as f:
|
|
233
276
|
with redirect_stdout(f), redirect_stderr(f):
|
opencos/tests/test_eda.py
CHANGED
|
@@ -100,9 +100,19 @@ class TestsRequiresVerilator( # pylint: disable=too-many-public-methods
|
|
|
100
100
|
def test_args_sim_tool_with_path(self):
|
|
101
101
|
'''Test for calling a tool as --tool=<tool>=</path/to/tool-exe>'''
|
|
102
102
|
verilator_fullpath = shutil.which('verilator')
|
|
103
|
+
verilator_path, _ = os.path.split(verilator_fullpath)
|
|
104
|
+
|
|
103
105
|
chdir_remove_work_dir('../../lib/tests')
|
|
104
106
|
rc = eda_sim_wrap('--tool', f'verilator={verilator_fullpath}', 'oclib_fifo_test')
|
|
105
|
-
|
|
107
|
+
assert rc == 0
|
|
108
|
+
|
|
109
|
+
rc = eda_sim_wrap('--tool', f'verilator:{verilator_fullpath}', 'oclib_fifo_test')
|
|
110
|
+
assert rc == 0
|
|
111
|
+
|
|
112
|
+
rc = eda_sim_wrap('--tool', f'verilator={verilator_path}', 'oclib_fifo_test')
|
|
113
|
+
assert rc == 0
|
|
114
|
+
|
|
115
|
+
rc = eda_sim_wrap('--tool', f'verilator:{verilator_fullpath}', 'oclib_fifo_test')
|
|
106
116
|
assert rc == 0
|
|
107
117
|
|
|
108
118
|
def test_args_sim_with_coverage(self):
|