opencos-eda 0.2.51__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 (43) 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 +13 -2
  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 +66 -29
  19. opencos/eda_base.py +159 -20
  20. opencos/eda_config.py +2 -2
  21. opencos/eda_config_defaults.yml +83 -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 +227 -6
  26. opencos/tools/cocotb.py +492 -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/verilator.py +31 -0
  33. opencos/tools/vivado.py +3 -3
  34. opencos/util.py +22 -3
  35. opencos/utils/str_helpers.py +85 -0
  36. opencos/utils/vscode_helper.py +47 -0
  37. {opencos_eda-0.2.51.dist-info → opencos_eda-0.2.53.dist-info}/METADATA +2 -1
  38. {opencos_eda-0.2.51.dist-info → opencos_eda-0.2.53.dist-info}/RECORD +43 -39
  39. {opencos_eda-0.2.51.dist-info → opencos_eda-0.2.53.dist-info}/WHEEL +0 -0
  40. {opencos_eda-0.2.51.dist-info → opencos_eda-0.2.53.dist-info}/entry_points.txt +0 -0
  41. {opencos_eda-0.2.51.dist-info → opencos_eda-0.2.53.dist-info}/licenses/LICENSE +0 -0
  42. {opencos_eda-0.2.51.dist-info → opencos_eda-0.2.53.dist-info}/licenses/LICENSE.spdx +0 -0
  43. {opencos_eda-0.2.51.dist-info → opencos_eda-0.2.53.dist-info}/top_level.txt +0 -0
@@ -10,9 +10,11 @@ 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
- from opencos.tests.helpers import eda_wrap
15
+ from opencos.tests.helpers import eda_wrap, eda_wrap_is_sim_fail
15
16
  from opencos.utils.markup_helpers import yaml_safe_load
17
+ from opencos.utils import status_constants
16
18
 
17
19
 
18
20
  thispath = os.path.dirname(__file__)
@@ -48,7 +50,18 @@ def test_tools_loaded():
48
50
  full_ver = obj.get_full_tool_and_versions()
49
51
  assert chk_str in full_ver, f'{chk_str=} not in {full_ver=}'
50
52
  ver_num = full_ver.rsplit(':', maxsplit=1)[-1]
51
- assert float(ver_num), f'{ver_num=} is not a float, from {full_ver=}'
53
+ if 'b' in ver_num:
54
+ ver_num = ver_num.split('b')[0] # TODO(chaitanya): remove once cocotb 2.0 is released
55
+ if '.' in ver_num:
56
+ major_ver = ver_num.split('.')[0]
57
+ assert major_ver.isdigit(), (
58
+ f'Major version {major_ver=} is not a digit, from {full_ver=}'
59
+ )
60
+ assert float(major_ver) >= 0, (
61
+ f'{major_ver=} is not a valid version number, from {full_ver=}'
62
+ )
63
+ else:
64
+ assert float(ver_num), f'{ver_num=} is not a float, from {full_ver=}'
52
65
 
53
66
 
54
67
  # Do some very crude checks on the eda.Tool methods, and make
@@ -61,6 +74,9 @@ def test_tools_loaded():
61
74
  my_tool = ToolVivado(config={})
62
75
  version_checker(obj=my_tool, chk_str='vivado:')
63
76
 
77
+ if 'cocotb' in tools_loaded:
78
+ my_tool = ToolCocotb(config={})
79
+ version_checker(obj=my_tool, chk_str='cocotb:')
64
80
 
65
81
  # Run these on simulation tools.
66
82
  list_of_commands = [
@@ -82,27 +98,45 @@ list_of_deps_targets = [
82
98
  ('tb_dollar_err', False),
83
99
  ]
84
100
 
101
+ list_of_added_sim_args = [
102
+ '',
103
+ '--gui --test-mode',
104
+ ]
105
+
106
+ cannot_use_cocotb = 'cocotb' not in tools_loaded or \
107
+ ('iverilog' not in tools_loaded and \
108
+ 'verilator' not in tools_loaded)
109
+ CANNOT_USE_COCOTB_REASON = 'requires cocotb in tools_loaded, and one of (iverilog, verilator) too'
110
+
85
111
  @pytest.mark.parametrize("command", list_of_commands)
86
112
  @pytest.mark.parametrize("tool", list_of_tools)
87
113
  @pytest.mark.parametrize("target,sim_expect_pass", list_of_deps_targets)
88
- def test_err_fatal(command, tool, target, sim_expect_pass):
89
- '''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>
90
117
 
91
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).
92
122
  '''
93
123
  if tool not in tools_loaded:
94
124
  pytest.skip(f"{tool=} skipped, {tools_loaded=}")
95
125
  return # skip/pass
96
126
 
127
+ added_args = []
128
+ if command == 'sim':
129
+ added_args = added_sim_args_str.split()
130
+
97
131
  relative_dir = "deps_files/test_err_fatal"
98
132
  os.chdir(os.path.join(thispath, relative_dir))
99
- rc = eda.main(command, '--tool', tool, target)
133
+ rc = eda.main(command, '--tool', tool, *(added_args), target)
100
134
  print(f'{rc=}')
101
135
  if command != 'sim' or sim_expect_pass:
102
136
  # command='elab' should pass.
103
137
  assert rc == 0
104
138
  else:
105
- assert rc > 0
139
+ assert eda_wrap_is_sim_fail(rc)
106
140
 
107
141
 
108
142
  @pytest.mark.skipif('vivado' not in tools_loaded, reason="requires vivado")
@@ -154,3 +188,190 @@ def test_vivado_tool_defines():
154
188
 
155
189
  assert data['defines']['OC_LIBRARY'] == '1'
156
190
  assert data['defines']['OC_LIBRARY_ULTRASCALE_PLUS'] is None # key present, no value
191
+
192
+
193
+ class TestCocotb:
194
+ '''Namespace class for cocotb tests'''
195
+
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.'''
199
+
200
+ chdir_remove_work_dir('../../examples/cocotb')
201
+
202
+ # Test 0: Using eda multi:
203
+ rc = eda_wrap('multi', 'sim', '--tool=cocotb', '*test')
204
+ assert rc == 0
205
+
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
209
+
210
+ # Test 2: cocotb works with different simulators/configurations
211
+ rc = eda_wrap('sim', '--tool', 'cocotb', 'cocotb_counter_waves_test')
212
+ assert rc == 0
213
+
214
+ # Test 3: Makefile approach
215
+ rc = eda_wrap('sim', '--tool', 'cocotb', 'cocotb_counter_makefile_test')
216
+ assert rc == 0
217
+
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
+ )
222
+
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'
231
+
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']
236
+
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
240
+
241
+ assert 'VERILATOR' not in data['defines']
242
+ assert 'SYNTHESIS' not in data['defines']
243
+
244
+
245
+
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.'''
251
+
252
+ if cocotb_simulator not in tools_loaded:
253
+ pytest.skip(f"{cocotb_simulator=} skipped, {tools_loaded=}")
254
+ return #skip/bypass
255
+
256
+ chdir_remove_work_dir('../../examples/cocotb')
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
265
+
266
+
267
+
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.'''
271
+
272
+ tool = ToolCocotb(config={})
273
+
274
+ # version detection works
275
+ version = tool.get_versions()
276
+ assert version, "Should return a non-empty version string"
277
+ assert isinstance(version, str)
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
287
+
288
+
289
+
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.'''
293
+
294
+ chdir_remove_work_dir('../../examples/cocotb')
295
+
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"
300
+
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"
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=}"
317
+
318
+
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.'''
322
+
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"
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
334
+
335
+
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.'''
339
+
340
+ chdir_remove_work_dir('../../examples/cocotb')
341
+
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=}"
349
+
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.'''
353
+
354
+ chdir_remove_work_dir('../../lib/tests')
355
+
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
+ )
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=}"
366
+
367
+
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.'''
371
+
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=}"