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.
Files changed (41) hide show
  1. opencos/commands/__init__.py +2 -0
  2. opencos/commands/build.py +1 -1
  3. opencos/commands/deps_help.py +259 -0
  4. opencos/commands/export.py +1 -1
  5. opencos/commands/flist.py +3 -0
  6. opencos/commands/lec.py +1 -1
  7. opencos/commands/open.py +2 -0
  8. opencos/commands/proj.py +1 -1
  9. opencos/commands/shell.py +1 -1
  10. opencos/commands/sim.py +32 -8
  11. opencos/commands/synth.py +1 -1
  12. opencos/commands/upload.py +3 -0
  13. opencos/commands/waves.py +1 -0
  14. opencos/deps/defaults.py +1 -0
  15. opencos/deps/deps_file.py +30 -4
  16. opencos/deps/deps_processor.py +72 -2
  17. opencos/deps_schema.py +3 -0
  18. opencos/eda.py +50 -26
  19. opencos/eda_base.py +159 -20
  20. opencos/eda_config.py +1 -1
  21. opencos/eda_config_defaults.yml +49 -3
  22. opencos/eda_extract_targets.py +1 -58
  23. opencos/tests/helpers.py +16 -0
  24. opencos/tests/test_eda.py +13 -2
  25. opencos/tests/test_tools.py +159 -132
  26. opencos/tools/cocotb.py +9 -0
  27. opencos/tools/modelsim_ase.py +67 -51
  28. opencos/tools/quartus.py +638 -0
  29. opencos/tools/questa.py +167 -88
  30. opencos/tools/questa_fse.py +10 -0
  31. opencos/tools/riviera.py +1 -0
  32. opencos/tools/vivado.py +3 -3
  33. opencos/util.py +20 -3
  34. opencos/utils/str_helpers.py +85 -0
  35. {opencos_eda-0.2.52.dist-info → opencos_eda-0.2.53.dist-info}/METADATA +1 -1
  36. {opencos_eda-0.2.52.dist-info → opencos_eda-0.2.53.dist-info}/RECORD +41 -39
  37. {opencos_eda-0.2.52.dist-info → opencos_eda-0.2.53.dist-info}/WHEEL +0 -0
  38. {opencos_eda-0.2.52.dist-info → opencos_eda-0.2.53.dist-info}/entry_points.txt +0 -0
  39. {opencos_eda-0.2.52.dist-info → opencos_eda-0.2.53.dist-info}/licenses/LICENSE +0 -0
  40. {opencos_eda-0.2.52.dist-info → opencos_eda-0.2.53.dist-info}/licenses/LICENSE.spdx +0 -0
  41. {opencos_eda-0.2.52.dist-info → opencos_eda-0.2.53.dist-info}/top_level.txt +0 -0
@@ -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.queta.CommandElabQuesta
429
- sim: opencos.tools.queta.CommandSimQuesta
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
@@ -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, possible targets in deps file = ['foo'",
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, possible targets in deps file = ['foo'",
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'''
@@ -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
- def test_err_fatal(command, tool, target, sim_expect_pass):
109
- '''tests that: eda <sim|elab> --tool <parameter-tool> <parameter-target>
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 > 0
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
- @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.'''
193
+ class TestCocotb:
194
+ '''Namespace class for cocotb tests'''
182
195
 
183
- chdir_remove_work_dir('../../examples/cocotb')
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
- # Test 0: Using eda multi:
186
- rc = eda_wrap('multi', 'sim', '--tool=cocotb', '*test')
187
- assert rc == 0
200
+ chdir_remove_work_dir('../../examples/cocotb')
188
201
 
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
202
+ # Test 0: Using eda multi:
203
+ rc = eda_wrap('multi', 'sim', '--tool=cocotb', '*test')
204
+ assert rc == 0
192
205
 
193
- # Test 2: cocotb works with different simulators/configurations
194
- rc = eda_wrap('sim', '--tool', 'cocotb', 'cocotb_counter_waves_test')
195
- assert rc == 0
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
- # Test 3: Makefile approach
198
- rc = eda_wrap('sim', '--tool', 'cocotb', 'cocotb_counter_makefile_test')
199
- assert rc == 0
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
- # 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
- )
214
+ # Test 3: Makefile approach
215
+ rc = eda_wrap('sim', '--tool', 'cocotb', 'cocotb_counter_makefile_test')
216
+ assert rc == 0
205
217
 
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'
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
- assert 'defines' in data
216
- assert 'OC_TOOL_COCOTB' in data['defines']
217
- assert 'SIMULATION' in data['defines']
218
- assert 'COCOTB' in data['defines']
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
- assert data['defines']['SIMULATION'] == 1
221
- assert data['defines']['COCOTB'] == 1
222
- assert data['defines']['OC_TOOL_COCOTB'] is None # key present, no value
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
- assert 'VERILATOR' not in data['defines']
225
- assert 'SYNTHESIS' not in data['defines']
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
- if cocotb_simulator not in tools_loaded:
236
- pytest.skip(f"{cocotb_simulator=} skipped, {tools_loaded=}")
237
- return #skip/bypass
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
- chdir_remove_work_dir('../../examples/cocotb')
252
+ if cocotb_simulator not in tools_loaded:
253
+ pytest.skip(f"{cocotb_simulator=} skipped, {tools_loaded=}")
254
+ return #skip/bypass
240
255
 
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
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
- tool = ToolCocotb(config={})
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
- # version detection works
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
- # 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
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
- chdir_remove_work_dir('../../examples/cocotb')
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
- # 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"
294
+ chdir_remove_work_dir('../../examples/cocotb')
282
295
 
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"
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
- # 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"
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
- # 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"
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
- # 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
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
- chdir_remove_work_dir('../../examples/cocotb')
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
- 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"
340
+ chdir_remove_work_dir('../../examples/cocotb')
325
341
 
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.'''
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
- chdir_remove_work_dir('../../lib/tests')
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
- # 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
- )
354
+ chdir_remove_work_dir('../../lib/tests')
338
355
 
339
- assert rc > 1, "Should fail with malformed HDL or failing test assertions"
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
- chdir_remove_work_dir('../../examples/cocotb')
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
- # 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"
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)