opencos-eda 0.3.3__tar.gz → 0.3.5__tar.gz

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 (102) hide show
  1. {opencos_eda-0.3.3/opencos_eda.egg-info → opencos_eda-0.3.5}/PKG-INFO +10 -2
  2. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/deps/defaults.py +1 -0
  3. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/deps/deps_processor.py +47 -22
  4. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/deps_schema.py +17 -0
  5. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/eda.py +7 -14
  6. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/eda_base.py +1 -0
  7. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/eda_config.py +84 -40
  8. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/eda_config_defaults.yml +3 -0
  9. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/eda_tool_helper.py +19 -0
  10. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/tests/helpers.py +37 -0
  11. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/tests/test_eda.py +11 -1
  12. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/tools/iverilog.py +4 -0
  13. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/tools/riviera.py +23 -12
  14. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/tools/verilator.py +86 -0
  15. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/tools/vivado.py +1 -0
  16. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/util.py +14 -8
  17. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/utils/str_helpers.py +6 -1
  18. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/utils/subprocess_helpers.py +66 -3
  19. {opencos_eda-0.3.3 → opencos_eda-0.3.5/opencos_eda.egg-info}/PKG-INFO +10 -2
  20. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos_eda.egg-info/requires.txt +11 -1
  21. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/pyproject.toml +14 -2
  22. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/LICENSE +0 -0
  23. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/LICENSE.spdx +0 -0
  24. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/README.md +0 -0
  25. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/__init__.py +0 -0
  26. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/_version.py +0 -0
  27. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/_waves_pkg.sv +0 -0
  28. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/commands/__init__.py +0 -0
  29. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/commands/build.py +0 -0
  30. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/commands/deps_help.py +0 -0
  31. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/commands/elab.py +0 -0
  32. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/commands/export.py +0 -0
  33. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/commands/flist.py +0 -0
  34. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/commands/lec.py +0 -0
  35. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/commands/lint.py +0 -0
  36. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/commands/multi.py +0 -0
  37. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/commands/open.py +0 -0
  38. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/commands/proj.py +0 -0
  39. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/commands/shell.py +0 -0
  40. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/commands/sim.py +0 -0
  41. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/commands/sweep.py +0 -0
  42. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/commands/synth.py +0 -0
  43. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/commands/targets.py +0 -0
  44. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/commands/upload.py +0 -0
  45. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/commands/waves.py +0 -0
  46. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/deps/__init__.py +0 -0
  47. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/deps/deps_commands.py +0 -0
  48. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/deps/deps_file.py +0 -0
  49. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/eda_config_max_verilator_waivers.yml +0 -0
  50. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/eda_config_reduced.yml +0 -0
  51. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/eda_deps_bash_completion.bash +0 -0
  52. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/eda_deps_sanitize.py +0 -0
  53. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/eda_extract_targets.py +0 -0
  54. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/export_helper.py +0 -0
  55. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/export_json_convert.py +0 -0
  56. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/files.py +0 -0
  57. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/hw/__init__.py +0 -0
  58. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/hw/oc_cli.py +0 -0
  59. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/hw/pcie.py +0 -0
  60. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/names.py +0 -0
  61. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/peakrdl_cleanup.py +0 -0
  62. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/seed.py +0 -0
  63. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/tests/__init__.py +0 -0
  64. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/tests/custom_config.yml +0 -0
  65. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/tests/deps_files/command_order/DEPS.yml +0 -0
  66. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/tests/deps_files/error_msgs/DEPS.yml +0 -0
  67. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/tests/deps_files/iverilog_test/DEPS.yml +0 -0
  68. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/tests/deps_files/no_deps_here/DEPS.yml +0 -0
  69. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/tests/deps_files/non_sv_reqs/DEPS.yml +0 -0
  70. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/tests/deps_files/tags_with_tools/DEPS.yml +0 -0
  71. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/tests/deps_files/test_err_fatal/DEPS.yml +0 -0
  72. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/tests/test_build.py +0 -0
  73. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/tests/test_deps_helpers.py +0 -0
  74. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/tests/test_deps_schema.py +0 -0
  75. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/tests/test_eda_elab.py +0 -0
  76. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/tests/test_eda_synth.py +0 -0
  77. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/tests/test_oc_cli.py +0 -0
  78. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/tests/test_tools.py +0 -0
  79. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/tools/__init__.py +0 -0
  80. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/tools/cocotb.py +0 -0
  81. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/tools/invio.py +0 -0
  82. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/tools/invio_helpers.py +0 -0
  83. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/tools/invio_yosys.py +0 -0
  84. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/tools/modelsim_ase.py +0 -0
  85. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/tools/quartus.py +0 -0
  86. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/tools/questa.py +0 -0
  87. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/tools/questa_fse.py +0 -0
  88. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/tools/slang.py +0 -0
  89. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/tools/slang_yosys.py +0 -0
  90. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/tools/surelog.py +0 -0
  91. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/tools/tabbycad_yosys.py +0 -0
  92. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/tools/yosys.py +0 -0
  93. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/utils/__init__.py +0 -0
  94. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/utils/markup_helpers.py +0 -0
  95. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/utils/status_constants.py +0 -0
  96. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/utils/vscode_helper.py +0 -0
  97. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos/utils/vsim_helper.py +0 -0
  98. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos_eda.egg-info/SOURCES.txt +0 -0
  99. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos_eda.egg-info/dependency_links.txt +0 -0
  100. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos_eda.egg-info/entry_points.txt +0 -0
  101. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/opencos_eda.egg-info/top_level.txt +0 -0
  102. {opencos_eda-0.3.3 → opencos_eda-0.3.5}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opencos-eda
3
- Version: 0.3.3
3
+ Version: 0.3.5
4
4
  Summary: A simple Python package for wrapping RTL simuliatons and synthesis
5
5
  Author-email: Simon Sabato <simon@cognichip.ai>, Drew Ranck <drew@cognichip.ai>
6
6
  Project-URL: Homepage, https://github.com/cognichip/opencos
@@ -9,8 +9,8 @@ License-File: LICENSE
9
9
  License-File: LICENSE.spdx
10
10
  Requires-Dist: mergedeep>=1.3.4
11
11
  Requires-Dist: peakrdl>=1.1.0
12
+ Requires-Dist: psutil>=7.0.0
12
13
  Requires-Dist: pyyaml>=6.0.2
13
- Requires-Dist: pytest>=8.3.5
14
14
  Requires-Dist: python-dotenv>=1.0.1
15
15
  Requires-Dist: schema>=0.7.7
16
16
  Requires-Dist: toml>=0.10.2
@@ -18,4 +18,12 @@ Requires-Dist: yamllint>=1.35.1
18
18
  Requires-Dist: PySerial>=3.5
19
19
  Requires-Dist: cocotb>=2.0
20
20
  Requires-Dist: supports_color>=0.2.0
21
+ Provides-Extra: dev
22
+ Requires-Dist: pylint>=3.0.0; extra == "dev"
23
+ Requires-Dist: pytest>=8.3.5; extra == "dev"
24
+ Provides-Extra: docs
25
+ Requires-Dist: mkdocs; extra == "docs"
26
+ Requires-Dist: mkdocs-material; extra == "docs"
27
+ Requires-Dist: mkdocs-wavedrom-plugin; extra == "docs"
28
+ Requires-Dist: mkdocs-plantuml; extra == "docs"
21
29
  Dynamic: license-file
@@ -33,6 +33,7 @@ SUPPORTED_TARGET_TABLE_KEYS = set([
33
33
  'defines',
34
34
  'parameters',
35
35
  'incdirs',
36
+ 'plusargs',
36
37
  'top',
37
38
  'deps',
38
39
  'reqs',
@@ -77,7 +77,7 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
77
77
  ).get('command_handler', {}).keys()
78
78
 
79
79
 
80
- def apply_defines(self, defines_dict: dict):
80
+ def apply_defines(self, defines_dict: dict) -> None:
81
81
  '''Given defines_dict, applies them to our self.command_design_ref obj'''
82
82
  if not isinstance(defines_dict, dict):
83
83
  self.error(f"{defines_dict=} is not type dict, can't apply defines,",
@@ -95,7 +95,19 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
95
95
  self.command_design_ref.process_plusarg(f'+define+{k}={v}')
96
96
 
97
97
 
98
- def apply_parameters(self, parameters_dict: dict):
98
+ def apply_plusargs(self, plusargs_dict: dict) -> None:
99
+ '''Given plusarsg_dict, applies them to our self.command_design_ref obj'''
100
+ if not isinstance(plusargs_dict, dict):
101
+ self.error(f"{plusargs_dict=} is not type dict, can't apply plusargs,",
102
+ f"in {self.caller_info}")
103
+ for k,v in plusargs_dict.items():
104
+ if v is None or v == '':
105
+ self.command_design_ref.process_plusarg(f'+{k}')
106
+ else:
107
+ self.command_design_ref.process_plusarg(f'+{k}={v}')
108
+
109
+
110
+ def apply_parameters(self, parameters_dict: dict) -> None:
99
111
  '''Given parameters_dict, applies them to our self.command_design_ref obj'''
100
112
  if not isinstance(parameters_dict, dict):
101
113
  self.error(f"{parameters_dict=} is not type dict, can't apply defines,",
@@ -110,7 +122,7 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
110
122
  )
111
123
 
112
124
 
113
- def apply_incdirs(self, incdirs_list:list):
125
+ def apply_incdirs(self, incdirs_list:list) -> None:
114
126
  '''Given incdirs_list, applies them to our self.command_design_ref obj'''
115
127
  if not isinstance(incdirs_list, (str, list)):
116
128
  self.error(f"{incdirs_list=} is not type str/list, can't apply incdirs",
@@ -290,7 +302,9 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
290
302
  caller_info=self.caller_info
291
303
  )
292
304
 
293
- def process_deps_entry(self):
305
+ def process_deps_entry( # pylint: disable=too-many-branches
306
+ self
307
+ ) -> list:
294
308
  '''Main entry point (after creating DepsProcessor obj) to resolve a deps target
295
309
 
296
310
  Example usage:
@@ -303,22 +317,6 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
303
317
  This method will apply all target features to the CommandDesign ref object as
304
318
  we traverse.
305
319
 
306
- Supported target keys:
307
- -- tags (or equivalent, to support multiple define/incdir/deps for a target)
308
- -- supports tag-name, with-tools, with-args, args, defines, incdirs, deps
309
- ** to be applied if a tool matches.
310
- -- TODO(drew): other features in docs/DEPS.md not yet implemented.
311
- -- multi: ignore-this-target: - commands (handled in eda.py CommandMulti.resolve_target)
312
- -- Named eda commands
313
- -- (partially done) sim or other eda commands (eda.py command specific things)
314
- basically, check the command, and apply/merge values to 'entry'?
315
- -- args
316
- -- defines
317
- -- incdirs
318
- -- top.
319
- -- commands (not in deps)
320
- -- deps
321
-
322
320
  TODO(drew): This does not yet support conditional inclusions based on defines,
323
321
  like the old DEPS files did with pattern:
324
322
  SOME_DEFINE ? dep_if_define_present : dep_if_define_not_present
@@ -344,6 +342,8 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
344
342
  remaining_deps_list += self.process_tags()
345
343
  elif key == 'defines':
346
344
  self.process_defines()
345
+ elif key == 'plusargs':
346
+ self.process_plusargs()
347
347
  elif key == 'parameters':
348
348
  self.process_parameters()
349
349
  elif key == 'incdirs':
@@ -520,6 +520,10 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
520
520
  # apply defines:
521
521
  self.apply_defines(value.get('defines', {}))
522
522
 
523
+ if key == 'plusargs':
524
+ # apply plusargs:
525
+ self.apply_plusargs(value.get('plusargs', {}))
526
+
523
527
  elif key == 'parameters':
524
528
  self.apply_parameters(value.get('parameters', {}))
525
529
 
@@ -598,7 +602,7 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
598
602
  return ret_deps_added_from_tags
599
603
 
600
604
 
601
- def process_defines(self):
605
+ def process_defines(self) -> None:
602
606
  '''Returns None, applies defines (dict, if any) from self.deps_entry to
603
607
  self.command_design_ref.'''
604
608
 
@@ -614,7 +618,28 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
614
618
 
615
619
  self.apply_defines(entry_defines)
616
620
 
617
- def process_parameters(self):
621
+
622
+ def process_plusargs(self) -> None:
623
+ '''Returns None, applies plusargs (dict, if any) from self.deps_entry to
624
+ self.command_design_ref.
625
+
626
+ These work w/ the same rules as defines (no value, or value int/str)
627
+ '''
628
+
629
+ # Plusargs:
630
+ # apply command specific plusargs, with higher priority than the a
631
+ # deps_entry['sim']['plusargs'] entry,
632
+ # do this with dict1.update(dict2):
633
+ entry_plusargs = {}
634
+ entry_plusargs.update(self.deps_entry.get('plusargs', {}))
635
+ entry_plusargs.update(self.entry_eda_command.get('plusargs', {}))
636
+ assert isinstance(entry_plusargs, dict), \
637
+ f'{entry_plusargs=} for in {self.caller_info} must be a dict'
638
+
639
+ self.apply_plusargs(entry_plusargs)
640
+
641
+
642
+ def process_parameters(self) -> None:
618
643
  '''Returns None, applies parameters (dict, if any) from self.deps_entry to
619
644
  self.command_design_ref.'''
620
645
 
@@ -17,6 +17,9 @@ my_target_name:
17
17
  incdirs: <---- incdirs, optional array (or string)
18
18
  - ./
19
19
 
20
+ plusargs: <---- plusargs, optional table
21
+ some_plusarg: 32
22
+
20
23
  top: tb <---- top, optional string
21
24
 
22
25
  deps:
@@ -63,6 +66,7 @@ my_target_name:
63
66
  defines: <---- defines, optional table
64
67
  parameters: <---- parameters, optional table
65
68
  incdirs: <---- incdirs, optional array (or string)
69
+ plusargs: <---- plusargs, optional table
66
70
  top: tb <---- top, optional string
67
71
  deps: <---- TARGET_DEPS_CONTENTS schema
68
72
  - some_file.sv <---- string file name
@@ -216,6 +220,9 @@ TARGET_EDA_COMMAND_ENTRY_TABLE = {
216
220
  Optional('defines'): {
217
221
  Optional(str): Or(str, int, type(None)),
218
222
  },
223
+ Optional('plusargs'): {
224
+ Optional(str): Or(str, int, type(None)),
225
+ },
219
226
  Optional('parameters'): {
220
227
  Optional(str): Or(str, int),
221
228
  },
@@ -241,6 +248,9 @@ TARGET_TAGS_TABLE = {
241
248
  Optional('defines'): {
242
249
  Optional(str): Or(str, int, type(None)),
243
250
  },
251
+ Optional('plusargs'): {
252
+ Optional(str): Or(str, int, type(None)),
253
+ },
244
254
  Optional('parameters'): {
245
255
  Optional(str): Or(str, int),
246
256
  },
@@ -262,6 +272,10 @@ TARGET_CONTENTS = Or(
262
272
  Optional('defines'): {
263
273
  Optional(str): Or(str, int, type(None)),
264
274
  },
275
+ # plusargs: table of key-value; value null or string
276
+ Optional('plusargs'): {
277
+ Optional(str): Or(str, int, type(None)),
278
+ },
265
279
  # parameters: table of key-value
266
280
  Optional('parameters'): {
267
281
  Optional(str): Or(str, int),
@@ -324,6 +338,9 @@ FILE_SIMPLIFIED = Schema(
324
338
  Optional('defines'): {
325
339
  Optional(str): Or(type(None), str),
326
340
  },
341
+ Optional('plusargs'): {
342
+ Optional(str): Or(type(None), str),
343
+ },
327
344
  Optional('parameters'): {
328
345
  Optional(str): str,
329
346
  },
@@ -23,7 +23,7 @@ from opencos import util, eda_config, eda_base
23
23
  from opencos.eda_base import Tool, which_tool, get_eda_exec
24
24
  from opencos.utils import vsim_helper, vscode_helper
25
25
  from opencos.utils.subprocess_helpers import subprocess_run_background
26
- from opencos.utils import status_constants, str_helpers
26
+ from opencos.utils import status_constants, str_helpers, subprocess_helpers
27
27
 
28
28
  # Configure util:
29
29
  util.progname = "EDA"
@@ -50,7 +50,7 @@ def init_config(
50
50
  # For key DEFAULT_HANDLERS, we'll update config['command_handler'] with
51
51
  # the actual class using importlib (via opencos.util)
52
52
 
53
- eda_config.tool_try_add_to_path(tool)
53
+ eda_config.update_config_auto_tool_order_for_tool(tool=tool, config=config)
54
54
 
55
55
  config['command_handler'] = {}
56
56
  for _cmd, str_class in config['DEFAULT_HANDLERS'].items():
@@ -163,10 +163,7 @@ def auto_tool_setup( # pylint: disable=too-many-locals,too-many-branches,too-man
163
163
  If so, updates config['auto_tools_order'][tool]['exe']
164
164
  '''
165
165
 
166
-
167
- tool = eda_config.update_config_auto_tool_order_for_tool(
168
- tool=tool, config=config
169
- )
166
+ tool = eda_config.tool_arg_remove_path_information(tool)
170
167
 
171
168
  assert 'auto_tools_order' in config
172
169
  assert isinstance(config['auto_tools_order'], list)
@@ -275,9 +272,7 @@ def tool_setup(tool: str, config: dict, quiet: bool = False, auto_setup: bool =
275
272
 
276
273
  '''
277
274
 
278
- tool = eda_config.update_config_auto_tool_order_for_tool(
279
- tool=tool, config=config
280
- )
275
+ tool = eda_config.tool_arg_remove_path_information(tool)
281
276
 
282
277
  if not quiet and not auto_setup:
283
278
  util.info(f"Setup for tool: '{tool}'")
@@ -389,6 +384,8 @@ def process_tokens( # pylint: disable=too-many-branches,too-many-statements,too-
389
384
  if not is_interactive:
390
385
  # Run init_config() now, we deferred it in main(), but only run it
391
386
  # for this tool (or tool=None to figure it out)
387
+ # This will handle any --tool=<name>=/path/to/bin also, so don't have to
388
+ # run tool_setup(..) on its own.
392
389
  config = init_config(
393
390
  config, tool=parsed.tool,
394
391
  run_auto_tool_setup=run_auto_tool_setup
@@ -406,13 +403,8 @@ def process_tokens( # pylint: disable=too-many-branches,too-many-statements,too-
406
403
  for arg in unparsed:
407
404
  if not arg.startswith('-'):
408
405
  command = arg
409
- if parsed.tool:
410
- tool_setup(parsed.tool, config=config)
411
406
  return usage(tokens=unparsed, config=config, command=command, tool=parsed.tool)
412
407
 
413
- if parsed.tool:
414
- tool_setup(parsed.tool, config=config)
415
-
416
408
  deferred_tokens = unparsed
417
409
  if not command:
418
410
  util.error("Didn't get a command!")
@@ -504,6 +496,7 @@ def signal_handler(sig, frame) -> None: # pylint: disable=unused-argument
504
496
  '''Handles Ctrl-C, called by main_cli() if running from command line'''
505
497
  util.fancy_stop()
506
498
  util.info('Received Ctrl+C...', start='\nINFO: [EDA] ')
499
+ subprocess_helpers.cleanup_all()
507
500
  util.exit(-1)
508
501
 
509
502
  # **************************************************************
@@ -594,6 +594,7 @@ class Command: # pylint: disable=too-many-public-methods
594
594
  help_kwargs = {'help': f'default={value}'}
595
595
  else:
596
596
  help_kwargs = {'help': f'{type(value).__name__} default={value}'}
597
+ help_kwargs['help'] = help_kwargs['help'].replace('%', '%%')
597
598
 
598
599
 
599
600
  # It's important to set the default=None on these, except for list types where default
@@ -18,7 +18,10 @@ from opencos import util
18
18
  from opencos.utils.markup_helpers import yaml_safe_load, yaml_safe_writer
19
19
 
20
20
  class Defaults:
21
- '''Defaults is a global placeholder for constants and supported features.'''
21
+ '''Defaults is a global namespace for constants and supported features.
22
+
23
+ Defaults.config_yml is set depending on search order for default eda_config[_defaults].yml
24
+ '''
22
25
 
23
26
  environ_override_config_yml = os.environ.get('EDA_CONFIG_YML', '')
24
27
  home_override_config_yml = os.path.join(
@@ -63,14 +66,15 @@ class Defaults:
63
66
  'simulate-args',
64
67
  'simulate-waves-args',
65
68
  'simulate-waivers',
69
+ 'simulate-coverage-tcl',
66
70
  'coverage-args',
67
71
  ])
68
72
 
69
73
  EDA_OUTPUT_CONFIG_FNAME = 'eda_output_config.yml'
70
74
 
71
- if os.path.exists(Defaults.environ_override_config_yml):
75
+ if os.path.isfile(Defaults.environ_override_config_yml):
72
76
  Defaults.config_yml = Defaults.environ_override_config_yml
73
- elif os.path.exists(Defaults.home_override_config_yml):
77
+ elif os.path.isfile(Defaults.home_override_config_yml):
74
78
  Defaults.config_yml = Defaults.home_override_config_yml
75
79
  else:
76
80
  Defaults.config_yml = Defaults.opencos_config_yml
@@ -149,29 +153,9 @@ def update_config_auto_tool_order_for_tool(tool: str, config: dict) -> str:
149
153
  Input arg tool can be in the form (for example):
150
154
  tool='verlator', tool='verilator=/path/to/verilator.exe'
151
155
 
152
- Performs no update if tool has no = in it. Returns tool (str) w/out = in it
156
+ Performs no update if tool has no = or : in it. Returns tool (str) w/out = in it
153
157
  '''
154
- if not tool or '=' not in tool:
155
- return tool
156
-
157
- tool, user_exe = tool.split('=')[0:2]
158
-
159
- user_exe = shutil.which(user_exe)
160
-
161
- # try adding to $PATH if in form --tool=/path/to/exe
162
- tool_try_add_to_path(tool)
163
-
164
- if tool not in config['auto_tools_order'][0]:
165
- return tool
166
- if not user_exe:
167
- return tool
168
-
169
- old_exe = config['auto_tools_order'][0][tool].get('exe', str())
170
- if isinstance(old_exe, list):
171
- config['auto_tools_order'][0][tool]['exe'][0] = user_exe
172
- else:
173
- config['auto_tools_order'][0][tool]['exe'] = user_exe
174
- return tool
158
+ return tool_try_add_to_path(tool=tool, config=config, update_config=True)
175
159
 
176
160
 
177
161
  def update_config_auto_tool_order_for_tools(tools: list, config: dict) -> list:
@@ -327,32 +311,92 @@ def write_eda_config_and_args(
327
311
  yaml_safe_writer(data=data, filepath=fullpath)
328
312
 
329
313
 
330
- def tool_try_add_to_path(tool: str) -> None:
331
- '''Since we support --tool=<name>=/path/to/bin/exe, attempt to prepend $PATH
314
+ def tool_arg_get_parts(tool: str) -> list:
315
+ '''Given a tool (str or None) that may be in form <name>=/path/to/something
316
+
317
+ Return the parts [<name>, <path>, ..]
318
+ '''
319
+ if not tool or ('=' not in tool and ':' not in tool):
320
+ return [tool]
321
+
322
+ if '=' in tool:
323
+ parts = tool.split('=')
324
+ else:
325
+ parts = tool.split(':')
326
+
327
+ return parts
328
+
329
+ def tool_arg_remove_path_information(tool: str) -> str:
330
+ '''Given a tool (str or None) that may be in form <name>=/path/to/something
331
+
332
+ Return the <name> only
333
+ '''
334
+ if not tool:
335
+ return tool
336
+ return tool_arg_get_parts(tool)[0]
337
+
338
+
339
+ def tool_try_add_to_path( # pylint: disable=too-many-branches
340
+ tool: str, config: dict, update_config: bool
341
+ ) -> str:
342
+ '''Since we support --tool=<name>=/path/to/bin[/exe], attempt to prepend $PATH
343
+
344
+ (also works for --tool=<name>:/path/to/bin[/exe] )
332
345
 
333
346
  with this information for this tool (which will nicely affect all subprocesses,
334
347
  but not wreck our original shell).'''
335
348
 
336
- if not tool or '=' not in tool:
337
- return
349
+ name_path_parts = tool_arg_get_parts(tool)
350
+ if len(name_path_parts) == 1:
351
+ return name_path_parts[0]
352
+
353
+ name, path_arg = name_path_parts[0:2]
354
+
355
+ if name not in config['auto_tools_order'][0]:
356
+ return name
357
+
358
+ config_exe = config['auto_tools_order'][0][name].get('exe', str())
359
+ if isinstance(config_exe, list):
360
+ orig_exe = config_exe[0]
361
+ else:
362
+ orig_exe = config_exe
338
363
 
339
- name, exe = tool.split('=')
340
- if os.path.isdir(name):
364
+ if path_arg and os.path.isfile(path_arg):
365
+ # Someone passes us --tool=<name>=/path/to/bin/exe, remove the exe from path:
366
+ path, exe = os.path.split(path_arg)
367
+ elif path_arg and os.path.isdir(path_arg):
341
368
  # Someone passes us --tool=<name>=/path/to/bin/ (did not have exe)
342
- path = name
369
+ path, exe = path_arg, orig_exe
343
370
  else:
344
- # Someone passes us --tool=<name>=/path/to/bin/exe, remove the exe.
345
- path, _ = os.path.split(exe)
346
- if not path:
347
- return
371
+ path, exe = '', ''
372
+
373
+ if not path or not exe:
374
+ util.error(f'Can not find path or exe for --tool={tool}: {name=} path={path_arg}')
375
+ return name
348
376
 
349
377
  path = os.path.abspath(path)
350
378
  if os.path.isdir(path):
351
- paths = os.environ['PATH'].split(':')
379
+ paths = os.environ.get('PATH', '').split(':')
352
380
  if path not in paths:
353
- os.environ['PATH'] = path + ':' + os.environ['PATH']
354
381
  util.info(f'--tool={tool} has path information, prepending PATH with: {path}')
382
+ os.environ['PATH'] = path + ':' + os.environ.get('PATH', '')
355
383
  else:
356
384
  util.info(f'--tool={tool} has path information, but {path} already in $PATH')
357
- if exe and os.path.isfile(exe):
358
- util.info(f'--tool={tool} has path information, using exe {shutil.which(exe)}')
385
+
386
+ user_exe = os.path.join(path, exe)
387
+ if not os.access(user_exe, os.X_OK):
388
+ util.error(f'--tool setting for {tool}: {user_exe} is not an executable')
389
+ return name
390
+
391
+ user_exe = shutil.which(exe)
392
+
393
+ if update_config:
394
+ if isinstance(config_exe, list):
395
+ config['auto_tools_order'][0][name]['exe'][0] = user_exe
396
+ else:
397
+ config['auto_tools_order'][0][name]['exe'] = user_exe
398
+ util.debug(f'For {tool=}, auto_tools_order config updated')
399
+
400
+ util.debug(f'For {tool=}, final {user_exe=}')
401
+
402
+ return name
@@ -247,6 +247,9 @@ tools:
247
247
  +accb +accr +access +r+w
248
248
  coverage-args: |
249
249
  -acdb -acdb_cov sbfectapm
250
+ simulate-coverage-tcl:
251
+ - acdb save
252
+ - acdb report -db work.acdb -txt -o cov.txt
250
253
 
251
254
 
252
255
  modelsim_ase:
@@ -68,3 +68,22 @@ def get_all_handler_commands(config=None, tools_loaded=None) -> dict:
68
68
  all_handler_commands[command].append(tool)
69
69
 
70
70
  return all_handler_commands
71
+
72
+
73
+ def get_handler_tool_version(tool: str, eda_command: str, config: dict) -> str:
74
+ '''Attempts to get a Command Handler's version given tool + eda_command'''
75
+
76
+ entry = config['auto_tools_order'][0].get(tool, {})
77
+ if not entry:
78
+ return ''
79
+
80
+ handler_name = entry.get('handlers', {}).get(eda_command, '')
81
+ if not handler_name:
82
+ return ''
83
+
84
+ module = util.import_class_from_string(handler_name)
85
+ obj = module(config=config)
86
+ if not getattr(obj, 'get_versions', None):
87
+ return ''
88
+
89
+ return obj.get_versions()
@@ -16,6 +16,7 @@ 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(..)
18
18
  config, tools_loaded = eda_tool_helper.get_config_and_tools_loaded()
19
+ known_tool_versions = {}
19
20
 
20
21
 
21
22
  def eda_wrap_is_sim_fail(rc: int, quiet: bool = False) -> bool:
@@ -32,6 +33,16 @@ def eda_wrap_is_sim_fail(rc: int, quiet: bool = False) -> bool:
32
33
  status_constants.EDA_DEFAULT_ERROR
33
34
  )
34
35
 
36
+ def handle_tool_version(tool: str, eda_command: str, cfg: dict = config) -> None:
37
+ '''Attempts to use a command handler for tool + eda_command and we'll
38
+ track the version globally'''
39
+
40
+ if not known_tool_versions.get(tool, ''):
41
+ handler_version = eda_tool_helper.get_handler_tool_version(
42
+ tool=tool, eda_command=eda_command, config=cfg
43
+ )
44
+ known_tool_versions[tool] = handler_version
45
+
35
46
  def can_run_eda_command(*commands, cfg: dict = config) -> bool:
36
47
  '''Returns True if we have any installed tool that can run: eda <command>'''
37
48
  runnable = []
@@ -50,10 +61,36 @@ def can_run_eda_command(*commands, cfg: dict = config) -> bool:
50
61
  if entry and entry.get('disable-auto', False):
51
62
  # This tool cannot automatically run our command.
52
63
  return False
64
+ # While we're here, set known tool versions.
65
+ handle_tool_version(tool=tool, eda_command=command, cfg=cfg)
53
66
 
54
67
  runnable.append(True)
55
68
  return runnable and all(runnable)
56
69
 
70
+ def can_uvm(tool: str) -> bool:
71
+ '''Returns True if we can run UVM, per tool'''
72
+
73
+ if tool not in tools_loaded:
74
+ return False
75
+
76
+ if tool == 'verilator':
77
+ # requires UVM_HOME to be set with uvm_pkg.sv existing.
78
+ uvm_home = os.environ.get('UVM_HOME', '')
79
+ uvm_pkg = os.path.join(uvm_home, 'uvm_pkg.sv')
80
+ if not all((os.path.isdir(uvm_home), os.path.isfile(uvm_pkg))):
81
+ return False
82
+
83
+ handle_tool_version(tool=tool, eda_command='sim', cfg=config)
84
+ version_list = known_tool_versions.get('verilator', '').split('.')
85
+ if int(version_list[0]) < 5 or \
86
+ (int(version_list[0]) == 5 and int(version_list[1]) < 42):
87
+ return False
88
+
89
+ return True
90
+
91
+ # default return False:
92
+ return False
93
+
57
94
  def can_run_eda_sim(cfg: dict = config) -> bool:
58
95
  '''Returns True if we have any installed tool that can run: eda sim'''
59
96
  return can_run_eda_command('sim', cfg=cfg)
@@ -100,9 +100,19 @@ class TestsRequiresVerilator( # pylint: disable=too-many-public-methods
100
100
  def test_args_sim_tool_with_path(self):
101
101
  '''Test for calling a tool as --tool=<tool>=</path/to/tool-exe>'''
102
102
  verilator_fullpath = shutil.which('verilator')
103
+ verilator_path, _ = os.path.split(verilator_fullpath)
104
+
103
105
  chdir_remove_work_dir('../../lib/tests')
104
106
  rc = eda_sim_wrap('--tool', f'verilator={verilator_fullpath}', 'oclib_fifo_test')
105
- print(f'{rc=}')
107
+ assert rc == 0
108
+
109
+ rc = eda_sim_wrap('--tool', f'verilator:{verilator_fullpath}', 'oclib_fifo_test')
110
+ assert rc == 0
111
+
112
+ rc = eda_sim_wrap('--tool', f'verilator={verilator_path}', 'oclib_fifo_test')
113
+ assert rc == 0
114
+
115
+ rc = eda_sim_wrap('--tool', f'verilator:{verilator_fullpath}', 'oclib_fifo_test')
106
116
  assert rc == 0
107
117
 
108
118
  def test_args_sim_with_coverage(self):
@@ -160,6 +160,10 @@ class CommandSimIverilog(CommandSim, ToolIverilog):
160
160
  cmd_list += self.tool_config.get('simulate-args', '').split()
161
161
  if self.args['waves']:
162
162
  cmd_list += self.tool_config.get('simulate-waves-args', '').split()
163
+ for x in self.args['sim-plusargs']:
164
+ if x[0] != '+':
165
+ x = f'+{x}'
166
+ cmd_list.append(x)
163
167
  return [ util.ShellCommandList(cmd_list, tee_fpath='sim.log') ]
164
168
 
165
169
  def get_post_simulate_command_lists(self, **kwargs) -> list: