opencos-eda 0.2.52__py3-none-any.whl → 0.2.53__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/__init__.py +2 -0
- opencos/commands/build.py +1 -1
- opencos/commands/deps_help.py +259 -0
- opencos/commands/export.py +1 -1
- opencos/commands/flist.py +3 -0
- opencos/commands/lec.py +1 -1
- opencos/commands/open.py +2 -0
- opencos/commands/proj.py +1 -1
- opencos/commands/shell.py +1 -1
- opencos/commands/sim.py +32 -8
- opencos/commands/synth.py +1 -1
- opencos/commands/upload.py +3 -0
- opencos/commands/waves.py +1 -0
- opencos/deps/defaults.py +1 -0
- opencos/deps/deps_file.py +30 -4
- opencos/deps/deps_processor.py +72 -2
- opencos/deps_schema.py +3 -0
- opencos/eda.py +50 -26
- opencos/eda_base.py +159 -20
- opencos/eda_config.py +1 -1
- opencos/eda_config_defaults.yml +49 -3
- opencos/eda_extract_targets.py +1 -58
- opencos/tests/helpers.py +16 -0
- opencos/tests/test_eda.py +13 -2
- opencos/tests/test_tools.py +159 -132
- opencos/tools/cocotb.py +9 -0
- opencos/tools/modelsim_ase.py +67 -51
- opencos/tools/quartus.py +638 -0
- opencos/tools/questa.py +167 -88
- opencos/tools/questa_fse.py +10 -0
- opencos/tools/riviera.py +1 -0
- opencos/tools/vivado.py +3 -3
- opencos/util.py +20 -3
- opencos/utils/str_helpers.py +85 -0
- {opencos_eda-0.2.52.dist-info → opencos_eda-0.2.53.dist-info}/METADATA +1 -1
- {opencos_eda-0.2.52.dist-info → opencos_eda-0.2.53.dist-info}/RECORD +41 -39
- {opencos_eda-0.2.52.dist-info → opencos_eda-0.2.53.dist-info}/WHEEL +0 -0
- {opencos_eda-0.2.52.dist-info → opencos_eda-0.2.53.dist-info}/entry_points.txt +0 -0
- {opencos_eda-0.2.52.dist-info → opencos_eda-0.2.53.dist-info}/licenses/LICENSE +0 -0
- {opencos_eda-0.2.52.dist-info → opencos_eda-0.2.53.dist-info}/licenses/LICENSE.spdx +0 -0
- {opencos_eda-0.2.52.dist-info → opencos_eda-0.2.53.dist-info}/top_level.txt +0 -0
opencos/eda_config_defaults.yml
CHANGED
|
@@ -20,11 +20,33 @@ DEFAULT_HANDLERS:
|
|
|
20
20
|
sweep : opencos.commands.CommandSweep
|
|
21
21
|
flist : opencos.commands.CommandFList
|
|
22
22
|
# These commands (waves, export, targets) do not require a tool, or
|
|
23
|
-
# will self determine the tool
|
|
23
|
+
# will self determine the tool. See command_tool_is_optional in this config file.
|
|
24
24
|
waves : opencos.commands.CommandWaves
|
|
25
25
|
export : opencos.commands.CommandExport
|
|
26
26
|
shell : opencos.commands.CommandShell
|
|
27
27
|
targets : opencos.commands.CommandTargets
|
|
28
|
+
deps-help : opencos.commands.CommandDepsHelp
|
|
29
|
+
|
|
30
|
+
DEFAULT_HANDLERS_HELP:
|
|
31
|
+
sim: Simulates a DEPS target.
|
|
32
|
+
elab: Elaborates a DEPS target (lint, tool specific).
|
|
33
|
+
synth: Synthesizes a DEPS target.
|
|
34
|
+
flist: Create dependency from a DEPS target.
|
|
35
|
+
proj: Create a project from a DEPS target for GUI sim/waves/debug.
|
|
36
|
+
multi: Run multiple DEPS targets, serially or in parallel.
|
|
37
|
+
tools-multi: Same as 'multi' but run on all available tools, or specfied using --tools.
|
|
38
|
+
sweep: Sweep one or more arguments across a range, serially or in parallel.
|
|
39
|
+
build: Build for a board, creating a project and running build flow.
|
|
40
|
+
waves: Opens waveform from prior simulation.
|
|
41
|
+
upload: Uploads a finished design into hardware.
|
|
42
|
+
open: Opens a project.
|
|
43
|
+
export: Export files related to a target, tool independent.
|
|
44
|
+
shell: Runs only commands for DEPS target (like sim or elab, but stops prior to tool).
|
|
45
|
+
targets: List all possible targets given glob path.
|
|
46
|
+
lec: Run equivalence on two designs.
|
|
47
|
+
deps-help: Provide help about DEPS markup files, or schema using --verbose or --help.
|
|
48
|
+
help: This help (without args), or i.e. "eda help sim" for specific help.
|
|
49
|
+
|
|
28
50
|
|
|
29
51
|
|
|
30
52
|
defines: { } # Add these defines to every eda call
|
|
@@ -40,6 +62,7 @@ dep_command_enables:
|
|
|
40
62
|
dep_tags_enables:
|
|
41
63
|
with-tools: true
|
|
42
64
|
with-args: true
|
|
65
|
+
with-commands: true
|
|
43
66
|
args: true
|
|
44
67
|
replace-config-tools: true
|
|
45
68
|
additive-config-tools: true
|
|
@@ -83,6 +106,7 @@ command_tool_is_optional:
|
|
|
83
106
|
- flist
|
|
84
107
|
- export
|
|
85
108
|
- targets
|
|
109
|
+
- deps-help
|
|
86
110
|
|
|
87
111
|
|
|
88
112
|
tools:
|
|
@@ -234,6 +258,9 @@ tools:
|
|
|
234
258
|
- 2555 # 2555 - assignment to input port myname
|
|
235
259
|
- 2583 # 2583 - [SVCHK] - Extra checking for conflicts with always_comb and
|
|
236
260
|
# always_latch variables is done at vopt time.
|
|
261
|
+
- 13159 # 13159, 2685, 2718 are all related to module instance port default values.
|
|
262
|
+
- 2685
|
|
263
|
+
- 2718
|
|
237
264
|
simulate-waivers:
|
|
238
265
|
- 3009 # 3009: [TSCALE] - Module 'myname' does not have a timeunit/timeprecision
|
|
239
266
|
# specification in effect, but other modules do.
|
|
@@ -256,6 +283,9 @@ tools:
|
|
|
256
283
|
- 2555 # 2555 - assignment to input port myname
|
|
257
284
|
- 2583 # 2583 - [SVCHK] - Extra checking for conflicts with always_comb and
|
|
258
285
|
# always_latch variables is done at vopt time.
|
|
286
|
+
- 13159 # 13159, 2685, 2718 are all related to module instance port default values.
|
|
287
|
+
- 2685
|
|
288
|
+
- 2718
|
|
259
289
|
simulate-waivers:
|
|
260
290
|
- 3009 # 3009: [TSCALE] - Module 'myname' does not have a timeunit/timeprecision
|
|
261
291
|
# specification in effect, but other modules do.
|
|
@@ -295,6 +325,10 @@ tools:
|
|
|
295
325
|
- "Cocotb test completed successfully!"
|
|
296
326
|
|
|
297
327
|
|
|
328
|
+
quartus:
|
|
329
|
+
defines:
|
|
330
|
+
OC_TOOL_QUARTUS: null
|
|
331
|
+
|
|
298
332
|
vivado:
|
|
299
333
|
sim-libraries:
|
|
300
334
|
- xil_defaultlib
|
|
@@ -375,6 +409,16 @@ auto_tools_order:
|
|
|
375
409
|
exe: gtkwave
|
|
376
410
|
handlers: { }
|
|
377
411
|
|
|
412
|
+
quartus:
|
|
413
|
+
exe: quartus_sh
|
|
414
|
+
handlers:
|
|
415
|
+
synth: opencos.tools.quartus.CommandSynthQuartus
|
|
416
|
+
build: opencos.tools.quartus.CommandBuildQuartus
|
|
417
|
+
flist: opencos.tools.quartus.CommandFListQuartus
|
|
418
|
+
proj: opencos.tools.quartus.CommandProjQuartus
|
|
419
|
+
upload: opencos.tools.quartus.CommandUploadQuartus
|
|
420
|
+
open: opencos.tools.quartus.CommandOpenQuartus
|
|
421
|
+
|
|
378
422
|
vivado:
|
|
379
423
|
exe: vivado
|
|
380
424
|
handlers:
|
|
@@ -425,8 +469,9 @@ auto_tools_order:
|
|
|
425
469
|
exe: qrun
|
|
426
470
|
requires_vsim_helper: True
|
|
427
471
|
handlers:
|
|
428
|
-
elab: opencos.tools.
|
|
429
|
-
sim: opencos.tools.
|
|
472
|
+
elab: opencos.tools.questa.CommandElabQuesta
|
|
473
|
+
sim: opencos.tools.questa.CommandSimQuesta
|
|
474
|
+
flist: opencos.tools.questa.CommandFListQuesta
|
|
430
475
|
|
|
431
476
|
riviera:
|
|
432
477
|
exe: vsim
|
|
@@ -450,6 +495,7 @@ auto_tools_order:
|
|
|
450
495
|
handlers:
|
|
451
496
|
elab: opencos.tools.questa_fse.CommandElabQuestaFse
|
|
452
497
|
sim: opencos.tools.questa_fse.CommandSimQuestaFse
|
|
498
|
+
flist: opencos.tools.questa_fse.CommandFListQuestaFse
|
|
453
499
|
|
|
454
500
|
iverilog:
|
|
455
501
|
exe: iverilog
|
opencos/eda_extract_targets.py
CHANGED
|
@@ -10,68 +10,11 @@ import os
|
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
|
|
12
12
|
from opencos.deps.deps_file import get_all_targets
|
|
13
|
+
from opencos.utils.str_helpers import print_columns_manual
|
|
13
14
|
|
|
14
15
|
PATH_LPREFIX = str(Path('.')) + os.path.sep
|
|
15
16
|
|
|
16
17
|
|
|
17
|
-
def get_terminal_columns():
|
|
18
|
-
"""
|
|
19
|
-
Retrieves the number of columns (width) of the terminal window.
|
|
20
|
-
|
|
21
|
-
Returns:
|
|
22
|
-
int: The number of columns in the terminal, or a default value (e.g., 80)
|
|
23
|
-
if the terminal size cannot be determined.
|
|
24
|
-
"""
|
|
25
|
-
try:
|
|
26
|
-
size = os.get_terminal_size()
|
|
27
|
-
return size.columns
|
|
28
|
-
except OSError:
|
|
29
|
-
# Handle cases where the terminal size cannot be determined (e.g., not in a TTY)
|
|
30
|
-
return 80 # Default to 80 columns
|
|
31
|
-
|
|
32
|
-
return 80 # else default to 80.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def print_columns_manual(data: list, num_columns: int = 4, auto_columns: bool = True) -> None:
|
|
36
|
-
"""Prints a list of strings in columns, manually aligning them."""
|
|
37
|
-
|
|
38
|
-
if not data:
|
|
39
|
-
print()
|
|
40
|
-
return
|
|
41
|
-
|
|
42
|
-
_spacing = 2
|
|
43
|
-
|
|
44
|
-
# Calculate maximum width for each column
|
|
45
|
-
max_lengths = [0] * num_columns
|
|
46
|
-
max_item_len = 0
|
|
47
|
-
for i, item in enumerate(data):
|
|
48
|
-
col_index = i % num_columns
|
|
49
|
-
max_lengths[col_index] = max(max_lengths[col_index], len(item))
|
|
50
|
-
max_item_len = max(max_item_len, len(item))
|
|
51
|
-
|
|
52
|
-
if auto_columns and num_columns > 1:
|
|
53
|
-
window_cols = get_terminal_columns()
|
|
54
|
-
max_line_len = 0
|
|
55
|
-
for x in max_lengths:
|
|
56
|
-
max_line_len += x + _spacing
|
|
57
|
-
if max_line_len > window_cols:
|
|
58
|
-
# subtract a column (already >= 2):
|
|
59
|
-
print_columns_manual(data=data, num_columns=num_columns-1, auto_columns=True)
|
|
60
|
-
return
|
|
61
|
-
if max_line_len + max_item_len + _spacing < window_cols:
|
|
62
|
-
# add 1 more column if we're guaranteed to have room.
|
|
63
|
-
print_columns_manual(data=data, num_columns=num_columns+1, auto_columns=True)
|
|
64
|
-
return
|
|
65
|
-
# else continue
|
|
66
|
-
|
|
67
|
-
# Print data in columns
|
|
68
|
-
for i, item in enumerate(data):
|
|
69
|
-
col_index = i % num_columns
|
|
70
|
-
print(item.ljust(max_lengths[col_index] + _spacing), end="") # Add padding
|
|
71
|
-
if col_index == num_columns - 1 or i == len(data) - 1:
|
|
72
|
-
print() # New line at the end of a row or end of data
|
|
73
|
-
|
|
74
|
-
|
|
75
18
|
def get_path_and_pattern(partial_path: str = '', base_path=str(Path('.'))) -> (str, str):
|
|
76
19
|
'''Returns tuple of (partial_path, partial_target or filter)'''
|
|
77
20
|
|
opencos/tests/helpers.py
CHANGED
|
@@ -12,6 +12,22 @@ from contextlib import redirect_stdout, redirect_stderr
|
|
|
12
12
|
from opencos import eda
|
|
13
13
|
from opencos import deps_schema
|
|
14
14
|
from opencos.utils.markup_helpers import yaml_safe_load
|
|
15
|
+
from opencos.utils import status_constants
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def eda_wrap_is_sim_fail(rc: int, quiet: bool = False) -> bool:
|
|
19
|
+
'''Because eda_wrap calls eda_main(..) and will continue running
|
|
20
|
+
|
|
21
|
+
after the first error, we may get a higher return code.'''
|
|
22
|
+
if not quiet:
|
|
23
|
+
print(f'eda_wrap_is_sim_fail({rc=})')
|
|
24
|
+
return rc in (
|
|
25
|
+
status_constants.EDA_COMMAND_MISSING_TOP,
|
|
26
|
+
status_constants.EDA_SIM_LOG_HAS_BAD_STRING,
|
|
27
|
+
status_constants.EDA_SIM_LOG_MISSING_MUST_STRING,
|
|
28
|
+
status_constants.EDA_EXEC_NONZERO_RETURN_CODE2,
|
|
29
|
+
status_constants.EDA_DEFAULT_ERROR
|
|
30
|
+
)
|
|
15
31
|
|
|
16
32
|
def can_run_eda_command(*commands, config: dict) -> bool:
|
|
17
33
|
'''Returns True if we have any installed tool that can run: eda <command>'''
|
opencos/tests/test_eda.py
CHANGED
|
@@ -589,9 +589,15 @@ class TestDepsResolveErrorMessages(Helpers):
|
|
|
589
589
|
assert rc > 1
|
|
590
590
|
assert self.is_in_log(
|
|
591
591
|
"Trying to resolve command-line target=./nope_target0: was not",
|
|
592
|
-
"found in deps_file=./DEPS.yml
|
|
592
|
+
"found in deps_file=./DEPS.yml",
|
|
593
593
|
windows_path_support=True
|
|
594
594
|
)
|
|
595
|
+
assert self.is_in_log(
|
|
596
|
+
"Targets available in deps_file=./DEPS.yml:",
|
|
597
|
+
windows_path_support=True
|
|
598
|
+
)
|
|
599
|
+
assert self.is_in_log(" foo")
|
|
600
|
+
|
|
595
601
|
|
|
596
602
|
def test_cmd_line_bad1(self):
|
|
597
603
|
'''EDA calling a non-existent target in DEPS file, with file that exists.'''
|
|
@@ -600,9 +606,14 @@ class TestDepsResolveErrorMessages(Helpers):
|
|
|
600
606
|
assert rc > 1
|
|
601
607
|
assert self.is_in_log(
|
|
602
608
|
"Trying to resolve command-line target=./nope_target1: was not",
|
|
603
|
-
"found in deps_file=./DEPS.yml
|
|
609
|
+
"found in deps_file=./DEPS.yml",
|
|
610
|
+
windows_path_support=True
|
|
611
|
+
)
|
|
612
|
+
assert self.is_in_log(
|
|
613
|
+
"Targets available in deps_file=./DEPS.yml:",
|
|
604
614
|
windows_path_support=True
|
|
605
615
|
)
|
|
616
|
+
assert self.is_in_log(" foo")
|
|
606
617
|
|
|
607
618
|
def test_cmd_line_bad2(self):
|
|
608
619
|
'''EDA calling a non-existent file w/out DEPS'''
|
opencos/tests/test_tools.py
CHANGED
|
@@ -12,8 +12,9 @@ from opencos.tools.verilator import ToolVerilator
|
|
|
12
12
|
from opencos.tools.vivado import ToolVivado
|
|
13
13
|
from opencos.tools.cocotb import ToolCocotb
|
|
14
14
|
from opencos.tests import helpers
|
|
15
|
-
from opencos.tests.helpers import eda_wrap
|
|
15
|
+
from opencos.tests.helpers import eda_wrap, eda_wrap_is_sim_fail
|
|
16
16
|
from opencos.utils.markup_helpers import yaml_safe_load
|
|
17
|
+
from opencos.utils import status_constants
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
thispath = os.path.dirname(__file__)
|
|
@@ -97,6 +98,11 @@ list_of_deps_targets = [
|
|
|
97
98
|
('tb_dollar_err', False),
|
|
98
99
|
]
|
|
99
100
|
|
|
101
|
+
list_of_added_sim_args = [
|
|
102
|
+
'',
|
|
103
|
+
'--gui --test-mode',
|
|
104
|
+
]
|
|
105
|
+
|
|
100
106
|
cannot_use_cocotb = 'cocotb' not in tools_loaded or \
|
|
101
107
|
('iverilog' not in tools_loaded and \
|
|
102
108
|
'verilator' not in tools_loaded)
|
|
@@ -105,24 +111,32 @@ CANNOT_USE_COCOTB_REASON = 'requires cocotb in tools_loaded, and one of (iverilo
|
|
|
105
111
|
@pytest.mark.parametrize("command", list_of_commands)
|
|
106
112
|
@pytest.mark.parametrize("tool", list_of_tools)
|
|
107
113
|
@pytest.mark.parametrize("target,sim_expect_pass", list_of_deps_targets)
|
|
108
|
-
|
|
109
|
-
|
|
114
|
+
@pytest.mark.parametrize("added_sim_args_str", list_of_added_sim_args)
|
|
115
|
+
def test_sim_elab_tools_pass_or_fail(command, tool, target, sim_expect_pass, added_sim_args_str):
|
|
116
|
+
'''tests that: eda <sim|elab> --tool <parameter-tool> <parameter-args> <parameter-target>
|
|
110
117
|
|
|
111
118
|
will correctly pass or fail depending on if it is supported or not.
|
|
119
|
+
|
|
120
|
+
Also tests for: non-gui, or --gui --test-mode (runs non-gui, but most python args will
|
|
121
|
+
be for --gui mode, signal logging, etc).
|
|
112
122
|
'''
|
|
113
123
|
if tool not in tools_loaded:
|
|
114
124
|
pytest.skip(f"{tool=} skipped, {tools_loaded=}")
|
|
115
125
|
return # skip/pass
|
|
116
126
|
|
|
127
|
+
added_args = []
|
|
128
|
+
if command == 'sim':
|
|
129
|
+
added_args = added_sim_args_str.split()
|
|
130
|
+
|
|
117
131
|
relative_dir = "deps_files/test_err_fatal"
|
|
118
132
|
os.chdir(os.path.join(thispath, relative_dir))
|
|
119
|
-
rc = eda.main(command, '--tool', tool, target)
|
|
133
|
+
rc = eda.main(command, '--tool', tool, *(added_args), target)
|
|
120
134
|
print(f'{rc=}')
|
|
121
135
|
if command != 'sim' or sim_expect_pass:
|
|
122
136
|
# command='elab' should pass.
|
|
123
137
|
assert rc == 0
|
|
124
138
|
else:
|
|
125
|
-
assert rc
|
|
139
|
+
assert eda_wrap_is_sim_fail(rc)
|
|
126
140
|
|
|
127
141
|
|
|
128
142
|
@pytest.mark.skipif('vivado' not in tools_loaded, reason="requires vivado")
|
|
@@ -176,175 +190,188 @@ def test_vivado_tool_defines():
|
|
|
176
190
|
assert data['defines']['OC_LIBRARY_ULTRASCALE_PLUS'] is None # key present, no value
|
|
177
191
|
|
|
178
192
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
'''Test cocotb tool defines, configs, and integration.'''
|
|
193
|
+
class TestCocotb:
|
|
194
|
+
'''Namespace class for cocotb tests'''
|
|
182
195
|
|
|
183
|
-
|
|
196
|
+
@pytest.mark.skipif(cannot_use_cocotb, reason=CANNOT_USE_COCOTB_REASON)
|
|
197
|
+
def test_cocotb_tool_defines(self):
|
|
198
|
+
'''Test cocotb tool defines, configs, and integration.'''
|
|
184
199
|
|
|
185
|
-
|
|
186
|
-
rc = eda_wrap('multi', 'sim', '--tool=cocotb', '*test')
|
|
187
|
-
assert rc == 0
|
|
200
|
+
chdir_remove_work_dir('../../examples/cocotb')
|
|
188
201
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
202
|
+
# Test 0: Using eda multi:
|
|
203
|
+
rc = eda_wrap('multi', 'sim', '--tool=cocotb', '*test')
|
|
204
|
+
assert rc == 0
|
|
192
205
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
206
|
+
# Test 1: basic cocotb sim command with Python runner (default)
|
|
207
|
+
rc = eda_wrap('sim', '--tool', 'cocotb', 'cocotb_counter_test')
|
|
208
|
+
assert rc == 0
|
|
196
209
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
210
|
+
# Test 2: cocotb works with different simulators/configurations
|
|
211
|
+
rc = eda_wrap('sim', '--tool', 'cocotb', 'cocotb_counter_waves_test')
|
|
212
|
+
assert rc == 0
|
|
200
213
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
)
|
|
214
|
+
# Test 3: Makefile approach
|
|
215
|
+
rc = eda_wrap('sim', '--tool', 'cocotb', 'cocotb_counter_makefile_test')
|
|
216
|
+
assert rc == 0
|
|
205
217
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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'
|
|
218
|
+
# Test 4: cocotb-specific defines are set correctly
|
|
219
|
+
eda_config_yml_path = os.path.join(
|
|
220
|
+
os.getcwd(), 'eda.work', 'cocotb_counter_test.sim', 'eda_output_config.yml'
|
|
221
|
+
)
|
|
214
222
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
223
|
+
data = yaml_safe_load(eda_config_yml_path)
|
|
224
|
+
assert 'args' in data
|
|
225
|
+
assert data['args'].get('top', '') == 'counter'
|
|
226
|
+
assert 'config' in data
|
|
227
|
+
assert 'eda_original_args' in data['config']
|
|
228
|
+
assert 'cocotb_counter_test' in data['config']['eda_original_args'] or \
|
|
229
|
+
'./cocotb_counter_test' in data['config']['eda_original_args']
|
|
230
|
+
assert data.get('target', '') == 'cocotb_counter_test'
|
|
219
231
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
232
|
+
assert 'defines' in data
|
|
233
|
+
assert 'OC_TOOL_COCOTB' in data['defines']
|
|
234
|
+
assert 'SIMULATION' in data['defines']
|
|
235
|
+
assert 'COCOTB' in data['defines']
|
|
223
236
|
|
|
224
|
-
|
|
225
|
-
|
|
237
|
+
assert data['defines']['SIMULATION'] == 1
|
|
238
|
+
assert data['defines']['COCOTB'] == 1
|
|
239
|
+
assert data['defines']['OC_TOOL_COCOTB'] is None # key present, no value
|
|
226
240
|
|
|
241
|
+
assert 'VERILATOR' not in data['defines']
|
|
242
|
+
assert 'SYNTHESIS' not in data['defines']
|
|
227
243
|
|
|
228
244
|
|
|
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
245
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
246
|
+
@pytest.mark.parametrize("cocotb_simulator", ['verilator', 'iverilog'])
|
|
247
|
+
@pytest.mark.parametrize("waves_arg", ["", "--waves"])
|
|
248
|
+
@pytest.mark.skipif(cannot_use_cocotb, reason=CANNOT_USE_COCOTB_REASON)
|
|
249
|
+
def test_cocotb_different_simulators(self, cocotb_simulator, waves_arg):
|
|
250
|
+
'''Test cocotb with different simulator configurations.'''
|
|
238
251
|
|
|
239
|
-
|
|
252
|
+
if cocotb_simulator not in tools_loaded:
|
|
253
|
+
pytest.skip(f"{cocotb_simulator=} skipped, {tools_loaded=}")
|
|
254
|
+
return #skip/bypass
|
|
240
255
|
|
|
241
|
-
|
|
242
|
-
'sim', '--tool', 'cocotb',
|
|
243
|
-
f'--cocotb-simulator={cocotb_simulator}',
|
|
244
|
-
waves_arg,
|
|
245
|
-
'cocotb_counter_test',
|
|
246
|
-
)
|
|
247
|
-
assert rc == 0
|
|
256
|
+
chdir_remove_work_dir('../../examples/cocotb')
|
|
248
257
|
|
|
258
|
+
rc = eda_wrap(
|
|
259
|
+
'sim', '--tool', 'cocotb',
|
|
260
|
+
f'--cocotb-simulator={cocotb_simulator}',
|
|
261
|
+
waves_arg,
|
|
262
|
+
'cocotb_counter_test',
|
|
263
|
+
)
|
|
264
|
+
assert rc == 0
|
|
249
265
|
|
|
250
266
|
|
|
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
267
|
|
|
255
|
-
|
|
268
|
+
@pytest.mark.skipif(cannot_use_cocotb, reason=CANNOT_USE_COCOTB_REASON)
|
|
269
|
+
def test_cocotb_tool_instantiation(self):
|
|
270
|
+
'''Test that ToolCocotb can be instantiated and has correct properties.'''
|
|
256
271
|
|
|
257
|
-
|
|
258
|
-
version = tool.get_versions()
|
|
259
|
-
assert version, "Should return a non-empty version string"
|
|
260
|
-
assert isinstance(version, str)
|
|
272
|
+
tool = ToolCocotb(config={})
|
|
261
273
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
assert 'COCOTB' in defines
|
|
267
|
-
assert 'OC_TOOL_COCOTB' in defines
|
|
268
|
-
assert defines['SIMULATION'] == 1
|
|
269
|
-
assert defines['COCOTB'] == 1
|
|
274
|
+
# version detection works
|
|
275
|
+
version = tool.get_versions()
|
|
276
|
+
assert version, "Should return a non-empty version string"
|
|
277
|
+
assert isinstance(version, str)
|
|
270
278
|
|
|
279
|
+
# tool defines
|
|
280
|
+
tool.set_tool_defines()
|
|
281
|
+
defines = tool.defines
|
|
282
|
+
assert 'SIMULATION' in defines
|
|
283
|
+
assert 'COCOTB' in defines
|
|
284
|
+
assert 'OC_TOOL_COCOTB' in defines
|
|
285
|
+
assert defines['SIMULATION'] == 1
|
|
286
|
+
assert defines['COCOTB'] == 1
|
|
271
287
|
|
|
272
288
|
|
|
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
289
|
|
|
277
|
-
|
|
290
|
+
@pytest.mark.skipif(cannot_use_cocotb, reason=CANNOT_USE_COCOTB_REASON)
|
|
291
|
+
def test_cocotb_failure_cases(self):
|
|
292
|
+
'''Test cocotb failure scenarios to ensure proper error handling.'''
|
|
278
293
|
|
|
279
|
-
|
|
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"
|
|
294
|
+
chdir_remove_work_dir('../../examples/cocotb')
|
|
282
295
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
296
|
+
# Test 1: missing test files should fail gracefully
|
|
297
|
+
rc = eda_wrap('sim', '--tool', 'cocotb', 'counter') # Just HDL, no test files
|
|
298
|
+
assert rc == status_constants.EDA_DEPS_TARGET_NOT_FOUND, \
|
|
299
|
+
"Should fail when no cocotb test files are found"
|
|
286
300
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
301
|
+
# Test 2: non-existent target should fail
|
|
302
|
+
rc = eda_wrap('sim', '--tool', 'cocotb', 'nonexistent_target')
|
|
303
|
+
assert rc in (
|
|
304
|
+
status_constants.EDA_DEPS_TARGET_NOT_FOUND,
|
|
305
|
+
# b/c we run eda_wrap, eda.main will continue to run after first error.
|
|
306
|
+
status_constants.EDA_COMMAND_MISSING_TOP
|
|
307
|
+
), "Should fail for non-existent target"
|
|
294
308
|
|
|
309
|
+
# Test 3: invalid cocotb test module should fail
|
|
310
|
+
rc = eda_wrap(
|
|
311
|
+
'sim', '--tool', 'cocotb',
|
|
312
|
+
'--cocotb-test-module=nonexistent_test',
|
|
313
|
+
'cocotb_counter_test',
|
|
314
|
+
)
|
|
315
|
+
assert eda_wrap_is_sim_fail(rc), \
|
|
316
|
+
f"Should fail with invalid test module {rc=}"
|
|
295
317
|
|
|
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
318
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
assert version, "Should return version when cocotb is properly installed"
|
|
319
|
+
@pytest.mark.skipif(cannot_use_cocotb, reason=CANNOT_USE_COCOTB_REASON)
|
|
320
|
+
def test_cocotb_missing_dependencies(self):
|
|
321
|
+
'''Test cocotb behavior when dependencies are missing.'''
|
|
304
322
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
assert 'COCOTB' in defines
|
|
310
|
-
assert 'OC_TOOL_COCOTB' in defines
|
|
323
|
+
# Test missing cocotb installation (simulate by checking error handling)
|
|
324
|
+
tool = ToolCocotb(config={})
|
|
325
|
+
version = tool.get_versions()
|
|
326
|
+
assert version, "Should return version when cocotb is properly installed"
|
|
311
327
|
|
|
328
|
+
# Test tool defines are properly set even with minimal config
|
|
329
|
+
tool.set_tool_defines()
|
|
330
|
+
defines = tool.defines
|
|
331
|
+
assert 'SIMULATION' in defines
|
|
332
|
+
assert 'COCOTB' in defines
|
|
333
|
+
assert 'OC_TOOL_COCOTB' in defines
|
|
312
334
|
|
|
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
335
|
|
|
317
|
-
|
|
336
|
+
@pytest.mark.skipif(cannot_use_cocotb, reason=CANNOT_USE_COCOTB_REASON)
|
|
337
|
+
def test_cocotb_invalid_simulator(self):
|
|
338
|
+
'''Test cocotb with invalid simulator configuration.'''
|
|
318
339
|
|
|
319
|
-
|
|
320
|
-
'sim', '--tool', 'cocotb',
|
|
321
|
-
'--cocotb-simulator=invalid_sim',
|
|
322
|
-
'cocotb_counter_test',
|
|
323
|
-
)
|
|
324
|
-
assert rc > 1, "Should fail with invalid simulator"
|
|
340
|
+
chdir_remove_work_dir('../../examples/cocotb')
|
|
325
341
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
342
|
+
rc = eda_wrap(
|
|
343
|
+
'sim', '--tool', 'cocotb',
|
|
344
|
+
'--cocotb-simulator=invalid_sim',
|
|
345
|
+
'cocotb_counter_test',
|
|
346
|
+
)
|
|
347
|
+
assert eda_wrap_is_sim_fail(rc), \
|
|
348
|
+
f"Should fail with invalid simulator {rc=}"
|
|
329
349
|
|
|
330
|
-
|
|
350
|
+
@pytest.mark.skipif(cannot_use_cocotb, reason=CANNOT_USE_COCOTB_REASON)
|
|
351
|
+
def test_cocotb_malformed_hdl(self):
|
|
352
|
+
'''Test cocotb with malformed HDL files.'''
|
|
331
353
|
|
|
332
|
-
|
|
333
|
-
rc = eda_wrap(
|
|
334
|
-
'sim', '--tool', 'cocotb',
|
|
335
|
-
'--cocotb-test-module=test_counter',
|
|
336
|
-
'tb_dollar_fatal',
|
|
337
|
-
)
|
|
354
|
+
chdir_remove_work_dir('../../lib/tests')
|
|
338
355
|
|
|
339
|
-
|
|
356
|
+
# Test with a target that has syntax errors - should fail during compilation
|
|
357
|
+
rc = eda_wrap(
|
|
358
|
+
'sim', '--tool', 'cocotb',
|
|
359
|
+
'--cocotb-test-module=test_counter',
|
|
360
|
+
'tb_dollar_fatal',
|
|
361
|
+
)
|
|
340
362
|
|
|
363
|
+
# eda_wrap may continue to errors beyond normal sim fails:
|
|
364
|
+
assert eda_wrap_is_sim_fail(rc) or \
|
|
365
|
+
f"Should fail with malformed HDL or failing test assertions {rc=}"
|
|
341
366
|
|
|
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
367
|
|
|
346
|
-
|
|
368
|
+
@pytest.mark.skipif(cannot_use_cocotb, reason=CANNOT_USE_COCOTB_REASON)
|
|
369
|
+
def test_cocotb_test_failures(self):
|
|
370
|
+
'''Test that cocotb properly reports test failures.'''
|
|
347
371
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
372
|
+
chdir_remove_work_dir('../../examples/cocotb')
|
|
373
|
+
|
|
374
|
+
# Intentionally failing cocotb tests
|
|
375
|
+
rc = eda_wrap('sim', '--tool', 'cocotb', 'cocotb_failure_test')
|
|
376
|
+
assert eda_wrap_is_sim_fail(rc), \
|
|
377
|
+
f"Should fail when cocotb tests contain assertion failures {rc=}"
|
opencos/tools/cocotb.py
CHANGED
|
@@ -316,6 +316,11 @@ def run_cocotb_test():
|
|
|
316
316
|
# Get the runner for the specified simulator
|
|
317
317
|
runner = get_runner(simulator)
|
|
318
318
|
|
|
319
|
+
build_args = []
|
|
320
|
+
|
|
321
|
+
if simulator == "verilator":
|
|
322
|
+
build_args.extend({list(self.args.get('verilate-args', []))!r})
|
|
323
|
+
|
|
319
324
|
# Build the design
|
|
320
325
|
runner.build(
|
|
321
326
|
sources=sources,
|
|
@@ -324,6 +329,7 @@ def run_cocotb_test():
|
|
|
324
329
|
defines=defines,
|
|
325
330
|
parameters=parameters,
|
|
326
331
|
build_dir="sim_build",
|
|
332
|
+
build_args=build_args,
|
|
327
333
|
)
|
|
328
334
|
|
|
329
335
|
# Run the test
|
|
@@ -398,6 +404,9 @@ MODULE = {test_module}
|
|
|
398
404
|
define_args.append(f'-D{k}={sanitize_defines_for_sh(v)}')
|
|
399
405
|
makefile_content += 'COMPILE_ARGS += ' + ' '.join(define_args) + '\n'
|
|
400
406
|
|
|
407
|
+
if self.args['cocotb-simulator'] == 'verilator' and self.args.get('verilate-args'):
|
|
408
|
+
makefile_content += 'COMPILE_ARGS += ' + ' '.join(self.args['verilate-args']) + '\n'
|
|
409
|
+
|
|
401
410
|
makefile_content += '''
|
|
402
411
|
# Waves support
|
|
403
412
|
ifeq ($(WAVES),1)
|