opencos-eda 0.3.5__py3-none-any.whl → 0.3.7__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/flist.py +2 -2
- opencos/commands/multi.py +4 -3
- opencos/commands/sim.py +28 -9
- opencos/deps/deps_processor.py +13 -2
- opencos/eda.py +23 -20
- opencos/eda_base.py +224 -90
- opencos/eda_config.py +2 -1
- opencos/eda_config_defaults.yml +5 -1
- opencos/export_helper.py +89 -31
- opencos/files.py +3 -1
- opencos/tests/deps_files/command_order/DEPS.yml +13 -0
- opencos/tests/helpers.py +32 -22
- opencos/tests/test_eda.py +1 -1
- opencos/tests/test_tools.py +9 -3
- opencos/tools/cocotb.py +94 -21
- opencos/tools/verilator.py +6 -4
- opencos/tools/yosys.py +3 -3
- opencos/util.py +100 -55
- opencos/utils/subprocess_helpers.py +23 -6
- {opencos_eda-0.3.5.dist-info → opencos_eda-0.3.7.dist-info}/METADATA +5 -2
- {opencos_eda-0.3.5.dist-info → opencos_eda-0.3.7.dist-info}/RECORD +26 -26
- {opencos_eda-0.3.5.dist-info → opencos_eda-0.3.7.dist-info}/WHEEL +0 -0
- {opencos_eda-0.3.5.dist-info → opencos_eda-0.3.7.dist-info}/entry_points.txt +0 -0
- {opencos_eda-0.3.5.dist-info → opencos_eda-0.3.7.dist-info}/licenses/LICENSE +0 -0
- {opencos_eda-0.3.5.dist-info → opencos_eda-0.3.7.dist-info}/licenses/LICENSE.spdx +0 -0
- {opencos_eda-0.3.5.dist-info → opencos_eda-0.3.7.dist-info}/top_level.txt +0 -0
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
|
@@ -29,3 +29,16 @@ target_test_with_post_tool_commands:
|
|
|
29
29
|
run-after-tool: true
|
|
30
30
|
- target_echo_hi_bye
|
|
31
31
|
top: foo
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
test_run_from_work_dir:
|
|
35
|
+
deps:
|
|
36
|
+
- commands:
|
|
37
|
+
- shell: echo "pwd=$PWD"
|
|
38
|
+
run-from-work-dir: true
|
|
39
|
+
|
|
40
|
+
test_run_from_work_dir_false:
|
|
41
|
+
deps:
|
|
42
|
+
- commands:
|
|
43
|
+
- shell: echo "pwd=$PWD"
|
|
44
|
+
run-from-work-dir: false
|
opencos/tests/helpers.py
CHANGED
|
@@ -10,8 +10,8 @@ from pathlib import Path
|
|
|
10
10
|
from contextlib import redirect_stdout, redirect_stderr
|
|
11
11
|
|
|
12
12
|
from opencos import eda, eda_tool_helper, deps_schema
|
|
13
|
-
from opencos.utils.markup_helpers import yaml_safe_load
|
|
14
13
|
from opencos.utils import status_constants
|
|
14
|
+
from opencos.utils.markup_helpers import yaml_safe_load
|
|
15
15
|
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(..)
|
|
@@ -164,38 +164,43 @@ def assert_gen_deps_yml_good(filepath:str, want_target:str='') -> dict:
|
|
|
164
164
|
|
|
165
165
|
def assert_export_json_good(filepath:str) -> dict:
|
|
166
166
|
'''Checks that an exported JSON (from eda export, or eda <command> --export) has known keys'''
|
|
167
|
-
assert os.path.
|
|
167
|
+
assert os.path.isfile(filepath), f'{filepath=} does not exist'
|
|
168
168
|
with open(filepath, encoding='utf-8') as f:
|
|
169
169
|
data = json.load(f)
|
|
170
|
-
assert '
|
|
171
|
-
assert '
|
|
172
|
-
|
|
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)
|
|
173
174
|
return data
|
|
174
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
|
+
|
|
175
183
|
|
|
176
184
|
def assert_export_jsonl_good(filepath:str, jsonl:bool=True) -> list:
|
|
177
185
|
'''Checks that an exported JSONL (from eda multi --export) has known keys'''
|
|
178
|
-
assert os.path.
|
|
186
|
+
assert os.path.isfile(filepath), f'{filepath=} does not exist'
|
|
179
187
|
ret = []
|
|
180
188
|
with open(filepath, encoding='utf-8') as f:
|
|
181
189
|
if jsonl:
|
|
190
|
+
print(f'Using JSONL for {filepath=}')
|
|
182
191
|
for line in f.readlines():
|
|
183
192
|
line = line.strip()
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
assert any(x in data for x in ['files', 'tb'])
|
|
188
|
-
ret.append(data)
|
|
193
|
+
test = json.loads(line)
|
|
194
|
+
check_test_runner_schema(test)
|
|
195
|
+
ret.append(test)
|
|
189
196
|
else:
|
|
197
|
+
print(f'Using JSON for {filepath=}')
|
|
190
198
|
data = json.load(f)
|
|
191
199
|
assert 'tests' in data
|
|
192
|
-
assert
|
|
193
|
-
for
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
assert any(x in entry for x in ['files', 'tb'])
|
|
197
|
-
ret.append(entry)
|
|
198
|
-
|
|
200
|
+
assert len(data.get('tests', [])) >= 1
|
|
201
|
+
for test in data.get('tests', []):
|
|
202
|
+
check_test_runner_schema(test)
|
|
203
|
+
ret.append(test)
|
|
199
204
|
|
|
200
205
|
return ret
|
|
201
206
|
|
|
@@ -233,7 +238,7 @@ class Helpers:
|
|
|
233
238
|
|
|
234
239
|
def log_it(
|
|
235
240
|
self, command_str: str, logfile=None, use_eda_wrap: bool = True,
|
|
236
|
-
run_in_subprocess: bool = False,
|
|
241
|
+
run_in_subprocess: bool = False, include_default_log: bool = False,
|
|
237
242
|
preserve_env: bool = False
|
|
238
243
|
) -> int:
|
|
239
244
|
'''Replacement for calling eda.main or eda_wrap, when you want an internal logfile
|
|
@@ -252,26 +257,31 @@ class Helpers:
|
|
|
252
257
|
logfile = self._resolve_logfile(logfile)
|
|
253
258
|
rc = 50
|
|
254
259
|
|
|
260
|
+
eda_log_arg = '--no-default-log'
|
|
261
|
+
if include_default_log:
|
|
262
|
+
eda_log_arg = ''
|
|
263
|
+
|
|
255
264
|
# TODO(drew): There are some issues with log_it redirecting stdout from vivado
|
|
256
265
|
# and modelsim_ase. So this may not work for all tools, you may have to directly
|
|
257
266
|
# look at eda.work/{target}.sim/sim.log or xsim.log.
|
|
258
267
|
print(f'{os.getcwd()=}')
|
|
259
268
|
print(f'{command_str=}')
|
|
260
269
|
if run_in_subprocess or self.RUN_IN_SUBPROCESS:
|
|
261
|
-
command_list = ['eda',
|
|
270
|
+
command_list = ['eda', eda_log_arg] + command_str.split()
|
|
262
271
|
_, _, rc = subprocess_run_background(
|
|
263
272
|
work_dir=self.DEFAULT_DIR,
|
|
264
273
|
command_list=command_list,
|
|
265
274
|
background=True,
|
|
266
275
|
tee_fpath=logfile
|
|
267
276
|
)
|
|
277
|
+
print(f'Wrote: {os.path.abspath(logfile)=}')
|
|
268
278
|
else:
|
|
269
279
|
with open(logfile, 'w', encoding='utf-8') as f:
|
|
270
280
|
with redirect_stdout(f), redirect_stderr(f):
|
|
271
281
|
if use_eda_wrap or self.USE_EDA_WRAP:
|
|
272
|
-
rc = eda_wrap(
|
|
282
|
+
rc = eda_wrap(eda_log_arg, *(command_str.split()))
|
|
273
283
|
else:
|
|
274
|
-
rc = eda.main(
|
|
284
|
+
rc = eda.main(eda_log_arg, *(command_str.split()))
|
|
275
285
|
print(f'Wrote: {os.path.abspath(logfile)=}')
|
|
276
286
|
|
|
277
287
|
if self.PRESERVE_ENV or preserve_env:
|
opencos/tests/test_eda.py
CHANGED
|
@@ -648,7 +648,7 @@ class TestsRequiresIVerilog(Helpers):
|
|
|
648
648
|
print(f'{rc=}')
|
|
649
649
|
assert rc == 0
|
|
650
650
|
assert self.is_in_log('Detected iverilog')
|
|
651
|
-
assert self.is_in_log("Generic help for command=
|
|
651
|
+
assert self.is_in_log("Generic help for command=sim tool=iverilog")
|
|
652
652
|
|
|
653
653
|
def test_iverilog_sim(self):
|
|
654
654
|
'''Test for command sim'''
|
opencos/tests/test_tools.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
# pylint: disable=R0801 # (similar lines in 2+ files)
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
|
+
import shutil
|
|
6
7
|
import sys
|
|
7
8
|
import pytest
|
|
8
9
|
|
|
@@ -32,14 +33,19 @@ def test_tools_loaded():
|
|
|
32
33
|
assert config
|
|
33
34
|
assert len(config.keys()) > 0
|
|
34
35
|
|
|
36
|
+
|
|
35
37
|
# It's possible we're running in some container or install that has no tools, for example,
|
|
36
38
|
# Windows.
|
|
37
39
|
if sys.platform.startswith('win') and \
|
|
38
40
|
not helpers.can_run_eda_command('elab', 'sim', cfg=config):
|
|
39
|
-
# Windows,
|
|
41
|
+
# Windows, no handlers for elab or sim:
|
|
40
42
|
pass
|
|
41
43
|
else:
|
|
42
|
-
|
|
44
|
+
basic_tools_available_via_path = any(shutil.which(x) for x in (
|
|
45
|
+
'verilator', 'iverilog', 'xsim', 'vsim', 'slang', 'yosys'
|
|
46
|
+
))
|
|
47
|
+
if basic_tools_available_via_path:
|
|
48
|
+
assert len(tools_loaded) > 0
|
|
43
49
|
|
|
44
50
|
def version_checker(
|
|
45
51
|
obj: eda_base.Tool, chk_str: str
|
|
@@ -49,7 +55,7 @@ def test_tools_loaded():
|
|
|
49
55
|
assert chk_str in full_ver, f'{chk_str=} not in {full_ver=}'
|
|
50
56
|
ver_num = full_ver.rsplit(':', maxsplit=1)[-1]
|
|
51
57
|
if 'b' in ver_num:
|
|
52
|
-
ver_num = ver_num.split('b')[0]
|
|
58
|
+
ver_num = ver_num.split('b')[0]
|
|
53
59
|
if '.' in ver_num:
|
|
54
60
|
major_ver = ver_num.split('.')[0]
|
|
55
61
|
assert major_ver.isdigit(), (
|
opencos/tools/cocotb.py
CHANGED
|
@@ -10,7 +10,9 @@ import subprocess
|
|
|
10
10
|
from opencos import util
|
|
11
11
|
from opencos.eda_base import Tool
|
|
12
12
|
from opencos.commands import CommandSim
|
|
13
|
+
from opencos.utils import status_constants
|
|
13
14
|
from opencos.utils.str_helpers import sanitize_defines_for_sh
|
|
15
|
+
from opencos.tools import verilator # For default waivers.
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
class ToolCocotb(Tool):
|
|
@@ -76,6 +78,8 @@ class CommandSimCocotb(CommandSim, ToolCocotb):
|
|
|
76
78
|
'tcl-file': None,
|
|
77
79
|
'cocotb-test-module': None,
|
|
78
80
|
'cocotb-test-runner': 'python',
|
|
81
|
+
'cocotb-test-runner-file': '',
|
|
82
|
+
'cocotb-test-run-dir': None, # If None, use self.args['work-dir']
|
|
79
83
|
'cocotb-simulator': 'verilator',
|
|
80
84
|
'cocotb-makefile': False,
|
|
81
85
|
'cocotb-python-runner': True,
|
|
@@ -86,17 +90,25 @@ class CommandSimCocotb(CommandSim, ToolCocotb):
|
|
|
86
90
|
'waves': 'Include waveforms by setting COCOTB_ENABLE_WAVES=1',
|
|
87
91
|
'cocotb-test-module': 'Python test module name (e.g., test_my_design)',
|
|
88
92
|
'cocotb-test-runner': 'Test runner to use: python (default) or pytest',
|
|
89
|
-
'cocotb-
|
|
90
|
-
|
|
93
|
+
'cocotb-test-runner-file': 'Bring your own test_runner.py file',
|
|
94
|
+
'cocotb-test-run-dir': (
|
|
95
|
+
'Directory where cocotb-test-runner will run, if unset uses default eda.work/(name)'
|
|
96
|
+
),
|
|
97
|
+
'cocotb-simulator': (
|
|
98
|
+
'Simulator backend: verilator (default), icarus, etc.'
|
|
99
|
+
' Note that iverilog will convert to icarus here'
|
|
100
|
+
),
|
|
91
101
|
'cocotb-makefile': 'Use traditional Makefile system instead of Python runner',
|
|
92
102
|
'cocotb-python-runner': 'Use Python-based runner system (default, cocotb 1.8+)',
|
|
93
|
-
'cocotb-standalone-makefile': (
|
|
94
|
-
|
|
103
|
+
'cocotb-standalone-makefile': (
|
|
104
|
+
'Use provided Makefile as-is, run make in source directory'
|
|
105
|
+
),
|
|
95
106
|
})
|
|
96
107
|
|
|
97
108
|
self.cocotb_command_lists = []
|
|
98
109
|
self.cocotb_test_files = []
|
|
99
110
|
|
|
111
|
+
|
|
100
112
|
def set_tool_defines(self):
|
|
101
113
|
ToolCocotb.set_tool_defines(self)
|
|
102
114
|
|
|
@@ -155,12 +167,13 @@ class CommandSimCocotb(CommandSim, ToolCocotb):
|
|
|
155
167
|
simulate_sh_fname='cocotb_test.sh'
|
|
156
168
|
)
|
|
157
169
|
|
|
170
|
+
|
|
158
171
|
def _find_cocotb_test_files(self):
|
|
159
172
|
'''Find Python test files that contain cocotb tests'''
|
|
160
173
|
self.cocotb_test_files = []
|
|
161
174
|
|
|
162
175
|
# Look for test files in the current directory and deps
|
|
163
|
-
for file_path in self.files_non_source:
|
|
176
|
+
for file_path in self.files_non_source + self.files_py:
|
|
164
177
|
if (file_path.endswith('.py') and
|
|
165
178
|
('test' in file_path.lower() or 'tb' in file_path.lower())):
|
|
166
179
|
# Check if it's a cocotb test file
|
|
@@ -193,8 +206,19 @@ class CommandSimCocotb(CommandSim, ToolCocotb):
|
|
|
193
206
|
def _prepare_python_runner_system(self):
|
|
194
207
|
'''Prepare cocotb using the Python-based runner system (cocotb 1.8+)'''
|
|
195
208
|
|
|
196
|
-
# Create a Python runner script
|
|
197
|
-
|
|
209
|
+
# Create a Python runner script, or use from --cocotb-test-runner-file
|
|
210
|
+
if self.args['cocotb-test-runner-file']:
|
|
211
|
+
|
|
212
|
+
runner_script = self.args['cocotb-test-runner-file']
|
|
213
|
+
if not os.path.isfile(self.args['cocotb-test-runner-file']):
|
|
214
|
+
self.error(
|
|
215
|
+
"File does not exist, for: --cocotb-test-runner-file=",
|
|
216
|
+
f"{self.args['cocotb-test-runner-file']}",
|
|
217
|
+
error_code=status_constants.EDA_GENERAL_FILE_NOT_FOUND
|
|
218
|
+
)
|
|
219
|
+
return
|
|
220
|
+
else:
|
|
221
|
+
runner_script = self._create_python_runner_script()
|
|
198
222
|
|
|
199
223
|
if self.args['cocotb-test-runner'] == 'pytest':
|
|
200
224
|
# Use pytest to run the tests
|
|
@@ -212,28 +236,41 @@ class CommandSimCocotb(CommandSim, ToolCocotb):
|
|
|
212
236
|
# Set environment variables
|
|
213
237
|
env_vars = self._get_cocotb_env_vars()
|
|
214
238
|
|
|
215
|
-
# Create command with environment variables
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
239
|
+
# Create command with environment variables, needs to be list-of-(cmd_list)
|
|
240
|
+
self.cocotb_command_lists = [
|
|
241
|
+
util.ShellCommandList(
|
|
242
|
+
self._create_env_command(env_vars) + cmd_list,
|
|
243
|
+
tee_fpath='cocotb_test.log',
|
|
244
|
+
# If someone wanted to run this from a specific dir, then
|
|
245
|
+
# run it from there.
|
|
246
|
+
work_dir=self.args['cocotb-test-run-dir']
|
|
247
|
+
)
|
|
248
|
+
]
|
|
219
249
|
|
|
220
250
|
def _prepare_makefile_system(self):
|
|
221
251
|
'''Prepare cocotb using the traditional Makefile system'''
|
|
222
252
|
|
|
223
253
|
makefile_path = os.path.join(self.args['work-dir'], 'Makefile')
|
|
254
|
+
self.files_makefile.append(makefile_path)
|
|
224
255
|
with open(makefile_path, 'w', encoding='utf-8') as f:
|
|
225
256
|
f.write(self._create_makefile_content())
|
|
226
257
|
|
|
227
258
|
cmd_list = self._create_shell_command_with_success('make -f Makefile')
|
|
228
|
-
self.cocotb_command_lists = [
|
|
229
|
-
|
|
259
|
+
self.cocotb_command_lists = [
|
|
260
|
+
util.ShellCommandList(
|
|
261
|
+
cmd_list, tee_fpath='cocotb_makefile.log',
|
|
262
|
+
# If someone wanted to run this from a specific dir, then
|
|
263
|
+
# run it from there.
|
|
264
|
+
work_dir=self.args['cocotb-test-run-dir']
|
|
265
|
+
)
|
|
266
|
+
]
|
|
230
267
|
|
|
231
268
|
def _prepare_standalone_makefile_system(self):
|
|
232
269
|
'''Use provided Makefile as-is, run make in source directory'''
|
|
233
270
|
|
|
234
271
|
# Find the Makefile in our dependencies
|
|
235
272
|
makefile_path = None
|
|
236
|
-
for file_path in self.files_non_source:
|
|
273
|
+
for file_path in self.files_non_source + self.files_makefile:
|
|
237
274
|
if os.path.basename(file_path).lower() == 'makefile':
|
|
238
275
|
makefile_path = file_path
|
|
239
276
|
break
|
|
@@ -241,10 +278,19 @@ class CommandSimCocotb(CommandSim, ToolCocotb):
|
|
|
241
278
|
if not makefile_path:
|
|
242
279
|
self.error('No Makefile found in deps for --cocotb-standalone-makefile')
|
|
243
280
|
|
|
281
|
+
if any(value for key,value in self.args.items() if key.startswith('export')):
|
|
282
|
+
util.warning(f'Using an --export arg with Cocotb standalong Makefile {makefile_path}',
|
|
283
|
+
'is not fully supported')
|
|
284
|
+
|
|
244
285
|
makefile_dir = os.path.dirname(os.path.abspath(makefile_path))
|
|
245
|
-
cmd_list = self._create_shell_command_with_success(
|
|
246
|
-
self.cocotb_command_lists = [
|
|
247
|
-
|
|
286
|
+
cmd_list = self._create_shell_command_with_success('make')
|
|
287
|
+
self.cocotb_command_lists = [
|
|
288
|
+
util.ShellCommandList(
|
|
289
|
+
cmd_list,
|
|
290
|
+
tee_fpath='cocotb_standalone.log',
|
|
291
|
+
work_dir=makefile_dir
|
|
292
|
+
)
|
|
293
|
+
]
|
|
248
294
|
|
|
249
295
|
def _get_test_module_name(self) -> str:
|
|
250
296
|
'''Get the test module name from args or detected files'''
|
|
@@ -280,8 +326,22 @@ class CommandSimCocotb(CommandSim, ToolCocotb):
|
|
|
280
326
|
|
|
281
327
|
return script_path
|
|
282
328
|
|
|
329
|
+
|
|
283
330
|
def _generate_runner_script_content(self, test_module: str, hdl_sources: list) -> str:
|
|
284
331
|
'''Generate the content for the Python runner script'''
|
|
332
|
+
|
|
333
|
+
if shutil.which('verilator'):
|
|
334
|
+
# TODO(drew): this shortcuts if verilator is truly usable,
|
|
335
|
+
# consider using eda_tool_helper to get "tools_loaded", which
|
|
336
|
+
# is not set in self.config['tools_loaded'] when --tool=cocotb.
|
|
337
|
+
# Would need minor refactor for eda.py methods auto_tools_order,
|
|
338
|
+
# tool_setup to go into eda_tool_helper.py, and those methods would
|
|
339
|
+
# need hooks to be non-destructive to config.
|
|
340
|
+
tmp_verilator_obj = verilator.VerilatorSim(config=self.config)
|
|
341
|
+
verilator_waivers = tmp_verilator_obj.get_verilator_tool_config_waivers()
|
|
342
|
+
else:
|
|
343
|
+
verilator_waivers = []
|
|
344
|
+
|
|
285
345
|
return f'''#!/usr/bin/env python3
|
|
286
346
|
"""
|
|
287
347
|
Cocotb test runner script generated by opencos
|
|
@@ -337,6 +397,8 @@ def run_cocotb_test():
|
|
|
337
397
|
|
|
338
398
|
if simulator == "verilator":
|
|
339
399
|
build_args.extend({list(self.args.get('verilate-args', []))!r})
|
|
400
|
+
build_args.extend({list(self.args.get('compile-args', []))!r})
|
|
401
|
+
build_args.extend({list(verilator_waivers)!r})
|
|
340
402
|
|
|
341
403
|
# Build the design
|
|
342
404
|
runner.build(
|
|
@@ -449,9 +511,20 @@ include $(shell cocotb-config --makefiles)/Makefile.sim
|
|
|
449
511
|
'''Get environment variables for cocotb execution'''
|
|
450
512
|
env_vars = {}
|
|
451
513
|
|
|
514
|
+
top = self.args.get('top', 'top')
|
|
515
|
+
|
|
452
516
|
# Basic cocotb configuration
|
|
453
|
-
env_vars
|
|
454
|
-
|
|
517
|
+
env_vars.update({
|
|
518
|
+
'SIM': self.args['cocotb-simulator'],
|
|
519
|
+
'TOPLEVEL': top,
|
|
520
|
+
'COCOTB_TOPLEVEL': top,
|
|
521
|
+
})
|
|
522
|
+
|
|
523
|
+
if test_module := self.args['cocotb-test-module']:
|
|
524
|
+
env_vars.update({
|
|
525
|
+
'MODULE': test_module,
|
|
526
|
+
'COCOTB_TEST_MODULES': test_module,
|
|
527
|
+
})
|
|
455
528
|
|
|
456
529
|
# Enable waves if requested
|
|
457
530
|
if self.args.get('waves', False):
|
|
@@ -464,8 +537,8 @@ include $(shell cocotb-config --makefiles)/Makefile.sim
|
|
|
464
537
|
env_vars['COCOTB_LOG_LEVEL'] = 'INFO'
|
|
465
538
|
|
|
466
539
|
# Random seed
|
|
467
|
-
if self.args.get('seed'):
|
|
468
|
-
env_vars['COCOTB_RANDOM_SEED'] = str(
|
|
540
|
+
if seed := self.args.get('seed'):
|
|
541
|
+
env_vars['COCOTB_RANDOM_SEED'] = str(seed)
|
|
469
542
|
|
|
470
543
|
return env_vars
|
|
471
544
|
|
opencos/tools/verilator.py
CHANGED
|
@@ -76,7 +76,6 @@ class VerilatorSim(CommandSim, ToolVerilator):
|
|
|
76
76
|
ToolVerilator.__init__(self, config=self.config)
|
|
77
77
|
self.args.update({
|
|
78
78
|
'gui': False,
|
|
79
|
-
'tcl-file': None,
|
|
80
79
|
'dump-vcd': False,
|
|
81
80
|
'waves-fst': True,
|
|
82
81
|
'waves-vcd': False,
|
|
@@ -95,7 +94,7 @@ class VerilatorSim(CommandSim, ToolVerilator):
|
|
|
95
94
|
' plusarg and apply $dumpfile("dump.fst").'
|
|
96
95
|
),
|
|
97
96
|
'waves-fst': (
|
|
98
|
-
'
|
|
97
|
+
'If using --waves, apply simulation runtime arg +trace.'
|
|
99
98
|
' Note that if you do not have SV code using $dumpfile, eda will add'
|
|
100
99
|
' _waves_pkg.sv to handle this for you with +trace runtime plusarg.'
|
|
101
100
|
),
|
|
@@ -228,7 +227,7 @@ class VerilatorSim(CommandSim, ToolVerilator):
|
|
|
228
227
|
warnings=(not lint_only), add_uvm_pkg_if_found=True
|
|
229
228
|
)
|
|
230
229
|
|
|
231
|
-
verilate_command_list += self.
|
|
230
|
+
verilate_command_list += self.get_verilator_tool_config_waivers()
|
|
232
231
|
|
|
233
232
|
verilate_command_list += self._verilator_args_defaults_cflags_nproc()
|
|
234
233
|
|
|
@@ -408,7 +407,10 @@ class VerilatorSim(CommandSim, ToolVerilator):
|
|
|
408
407
|
|
|
409
408
|
return verilate_command_list
|
|
410
409
|
|
|
411
|
-
def
|
|
410
|
+
def get_verilator_tool_config_waivers(self) -> list:
|
|
411
|
+
'''Returns list of args to verilator for waviers, from --compile-waivers and
|
|
412
|
+
|
|
413
|
+
--config-yml for tool: (config)'''
|
|
412
414
|
|
|
413
415
|
# Add compile waivers from self.config (tools.verilator.compile-waivers list):
|
|
414
416
|
# list(set(mylist)) to get unique.
|
opencos/tools/yosys.py
CHANGED
|
@@ -70,9 +70,9 @@ class ToolYosys(Tool):
|
|
|
70
70
|
[self.sta_exe, '-version'], capture_output=True, check=False
|
|
71
71
|
)
|
|
72
72
|
util.debug(f'{self.yosys_exe} {sta_version_ret=}')
|
|
73
|
-
sta_ver = sta_version_ret.stdout.decode('utf-8', errors='replace').split()
|
|
74
|
-
if sta_ver:
|
|
75
|
-
self.sta_version = sta_ver
|
|
73
|
+
sta_ver = sta_version_ret.stdout.decode('utf-8', errors='replace').split()
|
|
74
|
+
if sta_ver and isinstance(sta_ver, list):
|
|
75
|
+
self.sta_version = sta_ver[0]
|
|
76
76
|
|
|
77
77
|
version_ret = subprocess.run(
|
|
78
78
|
[self.yosys_exe, '--version'], capture_output=True, check=False
|