opencos-eda 0.2.50__py3-none-any.whl → 0.2.52__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/lec.py +7 -4
- opencos/commands/shell.py +11 -8
- opencos/commands/sim.py +38 -22
- opencos/commands/waves.py +12 -2
- opencos/deps/deps_file.py +67 -14
- opencos/eda.py +28 -3
- opencos/eda_base.py +39 -18
- opencos/eda_config.py +1 -0
- opencos/eda_config_defaults.yml +38 -14
- opencos/eda_deps_sanitize.py +73 -0
- opencos/tests/test_tools.py +195 -1
- opencos/tools/cocotb.py +483 -0
- opencos/tools/questa_fse.py +2 -0
- opencos/tools/verilator.py +31 -0
- opencos/tools/yosys.py +21 -5
- opencos/util.py +30 -5
- opencos/utils/markup_helpers.py +31 -2
- opencos/utils/status_constants.py +27 -0
- opencos/utils/vscode_helper.py +47 -0
- opencos/utils/vsim_helper.py +55 -0
- {opencos_eda-0.2.50.dist-info → opencos_eda-0.2.52.dist-info}/METADATA +2 -1
- {opencos_eda-0.2.50.dist-info → opencos_eda-0.2.52.dist-info}/RECORD +27 -22
- {opencos_eda-0.2.50.dist-info → opencos_eda-0.2.52.dist-info}/entry_points.txt +1 -0
- {opencos_eda-0.2.50.dist-info → opencos_eda-0.2.52.dist-info}/WHEEL +0 -0
- {opencos_eda-0.2.50.dist-info → opencos_eda-0.2.52.dist-info}/licenses/LICENSE +0 -0
- {opencos_eda-0.2.50.dist-info → opencos_eda-0.2.52.dist-info}/licenses/LICENSE.spdx +0 -0
- {opencos_eda-0.2.50.dist-info → opencos_eda-0.2.52.dist-info}/top_level.txt +0 -0
opencos/eda_config_defaults.yml
CHANGED
|
@@ -280,6 +280,20 @@ tools:
|
|
|
280
280
|
simulate-waves-args: |
|
|
281
281
|
+trace
|
|
282
282
|
|
|
283
|
+
cocotb:
|
|
284
|
+
defines:
|
|
285
|
+
OC_TOOL_COCOTB: null
|
|
286
|
+
log-bad-strings:
|
|
287
|
+
- "ERROR"
|
|
288
|
+
- "FAILED"
|
|
289
|
+
- "AssertionError"
|
|
290
|
+
- "Exception"
|
|
291
|
+
- "Traceback"
|
|
292
|
+
- "COCOTB_TEST_FAILED"
|
|
293
|
+
log-must-strings:
|
|
294
|
+
- "passed"
|
|
295
|
+
- "Cocotb test completed successfully!"
|
|
296
|
+
|
|
283
297
|
|
|
284
298
|
vivado:
|
|
285
299
|
sim-libraries:
|
|
@@ -345,6 +359,17 @@ auto_tools_order:
|
|
|
345
359
|
handlers:
|
|
346
360
|
elab: opencos.tools.invio.CommandElabInvio
|
|
347
361
|
|
|
362
|
+
vaporview:
|
|
363
|
+
exe: code
|
|
364
|
+
requires_vscode_extension:
|
|
365
|
+
- lramseyer.vaporview
|
|
366
|
+
handlers: { }
|
|
367
|
+
|
|
368
|
+
surfer:
|
|
369
|
+
exe: code
|
|
370
|
+
requires_vscode_extension:
|
|
371
|
+
- surfer-project.surfer
|
|
372
|
+
handlers: { }
|
|
348
373
|
|
|
349
374
|
gtkwave:
|
|
350
375
|
exe: gtkwave
|
|
@@ -398,9 +423,7 @@ auto_tools_order:
|
|
|
398
423
|
|
|
399
424
|
questa:
|
|
400
425
|
exe: qrun
|
|
401
|
-
|
|
402
|
-
- qrun -version
|
|
403
|
-
- vsim -version
|
|
426
|
+
requires_vsim_helper: True
|
|
404
427
|
handlers:
|
|
405
428
|
elab: opencos.tools.queta.CommandElabQuesta
|
|
406
429
|
sim: opencos.tools.queta.CommandSimQuesta
|
|
@@ -408,30 +431,22 @@ auto_tools_order:
|
|
|
408
431
|
riviera:
|
|
409
432
|
exe: vsim
|
|
410
433
|
requires_cmd:
|
|
411
|
-
- vsim -version
|
|
412
434
|
- which riviera # Do not run it, make sure it's in PATH
|
|
413
|
-
|
|
414
|
-
- riviera
|
|
435
|
+
requires_vsim_helper: True
|
|
415
436
|
handlers:
|
|
416
437
|
elab: opencos.tools.riviera.CommandElabRiviera
|
|
417
438
|
sim: opencos.tools.riviera.CommandSimRiviera
|
|
418
439
|
|
|
419
440
|
modelsim_ase:
|
|
420
441
|
exe: vsim
|
|
421
|
-
|
|
422
|
-
- vsim -version
|
|
423
|
-
requires_in_exe_path:
|
|
424
|
-
- modelsim
|
|
442
|
+
requires_vsim_helper: True
|
|
425
443
|
handlers:
|
|
426
444
|
elab: opencos.tools.modelsim_ase.CommandElabModelsimAse
|
|
427
445
|
sim: opencos.tools.modelsim_ase.CommandSimModelsimAse
|
|
428
446
|
|
|
429
447
|
questa_fse: # free student edition, works similar to modelsim_ase
|
|
430
448
|
exe: vsim
|
|
431
|
-
|
|
432
|
-
- vsim -version
|
|
433
|
-
requires_in_exe_path:
|
|
434
|
-
- questa_fse
|
|
449
|
+
requires_vsim_helper: True
|
|
435
450
|
handlers:
|
|
436
451
|
elab: opencos.tools.questa_fse.CommandElabQuestaFse
|
|
437
452
|
sim: opencos.tools.questa_fse.CommandSimQuestaFse
|
|
@@ -441,3 +456,12 @@ auto_tools_order:
|
|
|
441
456
|
handlers:
|
|
442
457
|
elab: opencos.tools.iverilog.CommandElabIverilog
|
|
443
458
|
sim: opencos.tools.iverilog.CommandSimIverilog
|
|
459
|
+
|
|
460
|
+
cocotb:
|
|
461
|
+
exe: python
|
|
462
|
+
requires_cmd:
|
|
463
|
+
- python -c "import cocotb; print(cocotb.__version__)"
|
|
464
|
+
requires_py:
|
|
465
|
+
- cocotb
|
|
466
|
+
handlers:
|
|
467
|
+
sim: opencos.tools.cocotb.CommandSimCocotb
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
'''opencos.eda_deps_sanitize is an executable script
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
eda_deps_sanitize --dir=<path>
|
|
7
|
+
|
|
8
|
+
Will print santized JSON data for the DEPS file found in --dir=<path>
|
|
9
|
+
'''
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
from opencos import util
|
|
18
|
+
from opencos.deps import deps_file
|
|
19
|
+
from opencos.utils import status_constants
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def run(*args) -> (int, str):
|
|
23
|
+
'''Runs the DEPS sanitizer, prints results to stdout'''
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
bool_kwargs = util.get_argparse_bool_action_kwargs()
|
|
27
|
+
|
|
28
|
+
parser = argparse.ArgumentParser(
|
|
29
|
+
prog='opencos eda_deps_sanitize', add_help=True, allow_abbrev=False
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
parser.add_argument('--yaml', **bool_kwargs,
|
|
33
|
+
help='Print output as YAML text, otherwise default is JSON text')
|
|
34
|
+
parser.add_argument('dir', type=str, default=str(Path('.')),
|
|
35
|
+
help='Directory to look for DEPS.[markup] file')
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
parsed, unparsed = parser.parse_known_args(list(args) + [''])
|
|
39
|
+
unparsed = list(filter(None, unparsed))
|
|
40
|
+
except argparse.ArgumentError:
|
|
41
|
+
return 1, f'problem attempting to parse_known_args for {args=}'
|
|
42
|
+
|
|
43
|
+
deps_path = parsed.dir
|
|
44
|
+
if os.path.isfile(deps_path):
|
|
45
|
+
deps_path, _ = os.path.split(deps_path)
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
my_depsfile_obj = deps_file.DepsFile(None, deps_path, {})
|
|
49
|
+
if not my_depsfile_obj.deps_file:
|
|
50
|
+
return status_constants.EDA_DEPS_FILE_NOT_FOUND, f'No DEPS markup file at {parsed.dir}'
|
|
51
|
+
|
|
52
|
+
ret_str = my_depsfile_obj.str_sanitized_markup(as_yaml=parsed.yaml)
|
|
53
|
+
rc = util.get_return_code()
|
|
54
|
+
return rc, ret_str
|
|
55
|
+
except Exception as e:
|
|
56
|
+
rc = 1, str(e)
|
|
57
|
+
|
|
58
|
+
return 0, ''
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def main() -> None:
|
|
62
|
+
'''calls sys.exit(), main entrypoint'''
|
|
63
|
+
args = []
|
|
64
|
+
if len(sys.argv) > 1 and not args:
|
|
65
|
+
args = sys.argv[1:]
|
|
66
|
+
|
|
67
|
+
rc, deps_str = run(*args)
|
|
68
|
+
print(deps_str)
|
|
69
|
+
sys.exit(rc)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
if __name__ == '__main__':
|
|
73
|
+
main()
|
opencos/tests/test_tools.py
CHANGED
|
@@ -10,6 +10,7 @@ from opencos import eda, eda_tool_helper, eda_base
|
|
|
10
10
|
|
|
11
11
|
from opencos.tools.verilator import ToolVerilator
|
|
12
12
|
from opencos.tools.vivado import ToolVivado
|
|
13
|
+
from opencos.tools.cocotb import ToolCocotb
|
|
13
14
|
from opencos.tests import helpers
|
|
14
15
|
from opencos.tests.helpers import eda_wrap
|
|
15
16
|
from opencos.utils.markup_helpers import yaml_safe_load
|
|
@@ -48,7 +49,18 @@ def test_tools_loaded():
|
|
|
48
49
|
full_ver = obj.get_full_tool_and_versions()
|
|
49
50
|
assert chk_str in full_ver, f'{chk_str=} not in {full_ver=}'
|
|
50
51
|
ver_num = full_ver.rsplit(':', maxsplit=1)[-1]
|
|
51
|
-
|
|
52
|
+
if 'b' in ver_num:
|
|
53
|
+
ver_num = ver_num.split('b')[0] # TODO(chaitanya): remove once cocotb 2.0 is released
|
|
54
|
+
if '.' in ver_num:
|
|
55
|
+
major_ver = ver_num.split('.')[0]
|
|
56
|
+
assert major_ver.isdigit(), (
|
|
57
|
+
f'Major version {major_ver=} is not a digit, from {full_ver=}'
|
|
58
|
+
)
|
|
59
|
+
assert float(major_ver) >= 0, (
|
|
60
|
+
f'{major_ver=} is not a valid version number, from {full_ver=}'
|
|
61
|
+
)
|
|
62
|
+
else:
|
|
63
|
+
assert float(ver_num), f'{ver_num=} is not a float, from {full_ver=}'
|
|
52
64
|
|
|
53
65
|
|
|
54
66
|
# Do some very crude checks on the eda.Tool methods, and make
|
|
@@ -61,6 +73,9 @@ def test_tools_loaded():
|
|
|
61
73
|
my_tool = ToolVivado(config={})
|
|
62
74
|
version_checker(obj=my_tool, chk_str='vivado:')
|
|
63
75
|
|
|
76
|
+
if 'cocotb' in tools_loaded:
|
|
77
|
+
my_tool = ToolCocotb(config={})
|
|
78
|
+
version_checker(obj=my_tool, chk_str='cocotb:')
|
|
64
79
|
|
|
65
80
|
# Run these on simulation tools.
|
|
66
81
|
list_of_commands = [
|
|
@@ -82,6 +97,11 @@ list_of_deps_targets = [
|
|
|
82
97
|
('tb_dollar_err', False),
|
|
83
98
|
]
|
|
84
99
|
|
|
100
|
+
cannot_use_cocotb = 'cocotb' not in tools_loaded or \
|
|
101
|
+
('iverilog' not in tools_loaded and \
|
|
102
|
+
'verilator' not in tools_loaded)
|
|
103
|
+
CANNOT_USE_COCOTB_REASON = 'requires cocotb in tools_loaded, and one of (iverilog, verilator) too'
|
|
104
|
+
|
|
85
105
|
@pytest.mark.parametrize("command", list_of_commands)
|
|
86
106
|
@pytest.mark.parametrize("tool", list_of_tools)
|
|
87
107
|
@pytest.mark.parametrize("target,sim_expect_pass", list_of_deps_targets)
|
|
@@ -154,3 +174,177 @@ def test_vivado_tool_defines():
|
|
|
154
174
|
|
|
155
175
|
assert data['defines']['OC_LIBRARY'] == '1'
|
|
156
176
|
assert data['defines']['OC_LIBRARY_ULTRASCALE_PLUS'] is None # key present, no value
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@pytest.mark.skipif(cannot_use_cocotb, reason=CANNOT_USE_COCOTB_REASON)
|
|
180
|
+
def test_cocotb_tool_defines():
|
|
181
|
+
'''Test cocotb tool defines, configs, and integration.'''
|
|
182
|
+
|
|
183
|
+
chdir_remove_work_dir('../../examples/cocotb')
|
|
184
|
+
|
|
185
|
+
# Test 0: Using eda multi:
|
|
186
|
+
rc = eda_wrap('multi', 'sim', '--tool=cocotb', '*test')
|
|
187
|
+
assert rc == 0
|
|
188
|
+
|
|
189
|
+
# Test 1: basic cocotb sim command with Python runner (default)
|
|
190
|
+
rc = eda_wrap('sim', '--tool', 'cocotb', 'cocotb_counter_test')
|
|
191
|
+
assert rc == 0
|
|
192
|
+
|
|
193
|
+
# Test 2: cocotb works with different simulators/configurations
|
|
194
|
+
rc = eda_wrap('sim', '--tool', 'cocotb', 'cocotb_counter_waves_test')
|
|
195
|
+
assert rc == 0
|
|
196
|
+
|
|
197
|
+
# Test 3: Makefile approach
|
|
198
|
+
rc = eda_wrap('sim', '--tool', 'cocotb', 'cocotb_counter_makefile_test')
|
|
199
|
+
assert rc == 0
|
|
200
|
+
|
|
201
|
+
# Test 4: cocotb-specific defines are set correctly
|
|
202
|
+
eda_config_yml_path = os.path.join(
|
|
203
|
+
os.getcwd(), 'eda.work', 'cocotb_counter_test.sim', 'eda_output_config.yml'
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
data = yaml_safe_load(eda_config_yml_path)
|
|
207
|
+
assert 'args' in data
|
|
208
|
+
assert data['args'].get('top', '') == 'counter'
|
|
209
|
+
assert 'config' in data
|
|
210
|
+
assert 'eda_original_args' in data['config']
|
|
211
|
+
assert 'cocotb_counter_test' in data['config']['eda_original_args'] or \
|
|
212
|
+
'./cocotb_counter_test' in data['config']['eda_original_args']
|
|
213
|
+
assert data.get('target', '') == 'cocotb_counter_test'
|
|
214
|
+
|
|
215
|
+
assert 'defines' in data
|
|
216
|
+
assert 'OC_TOOL_COCOTB' in data['defines']
|
|
217
|
+
assert 'SIMULATION' in data['defines']
|
|
218
|
+
assert 'COCOTB' in data['defines']
|
|
219
|
+
|
|
220
|
+
assert data['defines']['SIMULATION'] == 1
|
|
221
|
+
assert data['defines']['COCOTB'] == 1
|
|
222
|
+
assert data['defines']['OC_TOOL_COCOTB'] is None # key present, no value
|
|
223
|
+
|
|
224
|
+
assert 'VERILATOR' not in data['defines']
|
|
225
|
+
assert 'SYNTHESIS' not in data['defines']
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@pytest.mark.parametrize("cocotb_simulator", ['verilator', 'iverilog'])
|
|
230
|
+
@pytest.mark.parametrize("waves_arg", ["", "--waves"])
|
|
231
|
+
@pytest.mark.skipif(cannot_use_cocotb, reason=CANNOT_USE_COCOTB_REASON)
|
|
232
|
+
def test_cocotb_different_simulators(cocotb_simulator, waves_arg):
|
|
233
|
+
'''Test cocotb with different simulator configurations.'''
|
|
234
|
+
|
|
235
|
+
if cocotb_simulator not in tools_loaded:
|
|
236
|
+
pytest.skip(f"{cocotb_simulator=} skipped, {tools_loaded=}")
|
|
237
|
+
return #skip/bypass
|
|
238
|
+
|
|
239
|
+
chdir_remove_work_dir('../../examples/cocotb')
|
|
240
|
+
|
|
241
|
+
rc = eda_wrap(
|
|
242
|
+
'sim', '--tool', 'cocotb',
|
|
243
|
+
f'--cocotb-simulator={cocotb_simulator}',
|
|
244
|
+
waves_arg,
|
|
245
|
+
'cocotb_counter_test',
|
|
246
|
+
)
|
|
247
|
+
assert rc == 0
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
@pytest.mark.skipif(cannot_use_cocotb, reason=CANNOT_USE_COCOTB_REASON)
|
|
252
|
+
def test_cocotb_tool_instantiation():
|
|
253
|
+
'''Test that ToolCocotb can be instantiated and has correct properties.'''
|
|
254
|
+
|
|
255
|
+
tool = ToolCocotb(config={})
|
|
256
|
+
|
|
257
|
+
# version detection works
|
|
258
|
+
version = tool.get_versions()
|
|
259
|
+
assert version, "Should return a non-empty version string"
|
|
260
|
+
assert isinstance(version, str)
|
|
261
|
+
|
|
262
|
+
# tool defines
|
|
263
|
+
tool.set_tool_defines()
|
|
264
|
+
defines = tool.defines
|
|
265
|
+
assert 'SIMULATION' in defines
|
|
266
|
+
assert 'COCOTB' in defines
|
|
267
|
+
assert 'OC_TOOL_COCOTB' in defines
|
|
268
|
+
assert defines['SIMULATION'] == 1
|
|
269
|
+
assert defines['COCOTB'] == 1
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
@pytest.mark.skipif(cannot_use_cocotb, reason=CANNOT_USE_COCOTB_REASON)
|
|
274
|
+
def test_cocotb_failure_cases():
|
|
275
|
+
'''Test cocotb failure scenarios to ensure proper error handling.'''
|
|
276
|
+
|
|
277
|
+
chdir_remove_work_dir('../../examples/cocotb')
|
|
278
|
+
|
|
279
|
+
# Test 1: missing test files should fail gracefully
|
|
280
|
+
rc = eda_wrap('sim', '--tool', 'cocotb', 'counter') # Just HDL, no test files
|
|
281
|
+
assert rc > 1, "Should fail when no cocotb test files are found"
|
|
282
|
+
|
|
283
|
+
# Test 2: non-existent target should fail
|
|
284
|
+
rc = eda_wrap('sim', '--tool', 'cocotb', 'nonexistent_target')
|
|
285
|
+
assert rc > 1, "Should fail for non-existent target"
|
|
286
|
+
|
|
287
|
+
# Test 3: invalid cocotb test module should fail
|
|
288
|
+
rc = eda_wrap(
|
|
289
|
+
'sim', '--tool', 'cocotb',
|
|
290
|
+
'--cocotb-test-module=nonexistent_test',
|
|
291
|
+
'cocotb_counter_test',
|
|
292
|
+
)
|
|
293
|
+
assert rc > 1, "Should fail with invalid test module"
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
@pytest.mark.skipif(cannot_use_cocotb, reason=CANNOT_USE_COCOTB_REASON)
|
|
297
|
+
def test_cocotb_missing_dependencies():
|
|
298
|
+
'''Test cocotb behavior when dependencies are missing.'''
|
|
299
|
+
|
|
300
|
+
# Test missing cocotb installation (simulate by checking error handling)
|
|
301
|
+
tool = ToolCocotb(config={})
|
|
302
|
+
version = tool.get_versions()
|
|
303
|
+
assert version, "Should return version when cocotb is properly installed"
|
|
304
|
+
|
|
305
|
+
# Test tool defines are properly set even with minimal config
|
|
306
|
+
tool.set_tool_defines()
|
|
307
|
+
defines = tool.defines
|
|
308
|
+
assert 'SIMULATION' in defines
|
|
309
|
+
assert 'COCOTB' in defines
|
|
310
|
+
assert 'OC_TOOL_COCOTB' in defines
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
@pytest.mark.skipif(cannot_use_cocotb, reason=CANNOT_USE_COCOTB_REASON)
|
|
314
|
+
def test_cocotb_invalid_simulator():
|
|
315
|
+
'''Test cocotb with invalid simulator configuration.'''
|
|
316
|
+
|
|
317
|
+
chdir_remove_work_dir('../../examples/cocotb')
|
|
318
|
+
|
|
319
|
+
rc = eda_wrap(
|
|
320
|
+
'sim', '--tool', 'cocotb',
|
|
321
|
+
'--cocotb-simulator=invalid_sim',
|
|
322
|
+
'cocotb_counter_test',
|
|
323
|
+
)
|
|
324
|
+
assert rc > 1, "Should fail with invalid simulator"
|
|
325
|
+
|
|
326
|
+
@pytest.mark.skipif(cannot_use_cocotb, reason=CANNOT_USE_COCOTB_REASON)
|
|
327
|
+
def test_cocotb_malformed_hdl():
|
|
328
|
+
'''Test cocotb with malformed HDL files.'''
|
|
329
|
+
|
|
330
|
+
chdir_remove_work_dir('../../lib/tests')
|
|
331
|
+
|
|
332
|
+
# Test with a target that has syntax errors - should fail during compilation
|
|
333
|
+
rc = eda_wrap(
|
|
334
|
+
'sim', '--tool', 'cocotb',
|
|
335
|
+
'--cocotb-test-module=test_counter',
|
|
336
|
+
'tb_dollar_fatal',
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
assert rc > 1, "Should fail with malformed HDL or failing test assertions"
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
@pytest.mark.skipif(cannot_use_cocotb, reason=CANNOT_USE_COCOTB_REASON)
|
|
343
|
+
def test_cocotb_test_failures():
|
|
344
|
+
'''Test that cocotb properly reports test failures.'''
|
|
345
|
+
|
|
346
|
+
chdir_remove_work_dir('../../examples/cocotb')
|
|
347
|
+
|
|
348
|
+
# Intentionally failing cocotb tests
|
|
349
|
+
rc = eda_wrap('sim', '--tool', 'cocotb', 'cocotb_failure_test')
|
|
350
|
+
assert rc > 1, "Should fail when cocotb tests contain assertion failures"
|