opencos-eda 0.3.11__py3-none-any.whl → 0.3.13__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/flist.py +222 -39
  2. opencos/commands/multi.py +5 -3
  3. opencos/commands/sim.py +121 -7
  4. opencos/commands/sweep.py +1 -1
  5. opencos/commands/upload.py +4 -4
  6. opencos/commands/waves.py +44 -0
  7. opencos/deps/deps_file.py +4 -1
  8. opencos/deps_schema.py +2 -2
  9. opencos/eda.py +284 -175
  10. opencos/eda_base.py +54 -22
  11. opencos/eda_config.py +51 -24
  12. opencos/eda_config_defaults.yml +326 -195
  13. opencos/eda_config_reduced.yml +19 -39
  14. opencos/eda_tool_helper.py +193 -24
  15. opencos/tools/cocotb.py +6 -29
  16. opencos/tools/invio.py +0 -6
  17. opencos/tools/iverilog.py +16 -16
  18. opencos/tools/modelsim_ase.py +1 -13
  19. opencos/tools/quartus.py +81 -24
  20. opencos/tools/questa.py +0 -14
  21. opencos/tools/questa_common.py +95 -30
  22. opencos/tools/questa_fe.py +0 -14
  23. opencos/tools/questa_fse.py +0 -14
  24. opencos/tools/riviera.py +56 -25
  25. opencos/tools/slang.py +15 -12
  26. opencos/tools/slang_yosys.py +0 -6
  27. opencos/tools/surelog.py +14 -11
  28. opencos/tools/tabbycad_yosys.py +1 -7
  29. opencos/tools/verilator.py +18 -14
  30. opencos/tools/vivado.py +57 -22
  31. opencos/tools/yosys.py +2 -4
  32. opencos/util.py +14 -5
  33. opencos/utils/str_helpers.py +42 -1
  34. {opencos_eda-0.3.11.dist-info → opencos_eda-0.3.13.dist-info}/METADATA +1 -2
  35. {opencos_eda-0.3.11.dist-info → opencos_eda-0.3.13.dist-info}/RECORD +40 -41
  36. opencos/eda_config_max_verilator_waivers.yml +0 -39
  37. {opencos_eda-0.3.11.dist-info → opencos_eda-0.3.13.dist-info}/WHEEL +0 -0
  38. {opencos_eda-0.3.11.dist-info → opencos_eda-0.3.13.dist-info}/entry_points.txt +0 -0
  39. {opencos_eda-0.3.11.dist-info → opencos_eda-0.3.13.dist-info}/licenses/LICENSE +0 -0
  40. {opencos_eda-0.3.11.dist-info → opencos_eda-0.3.13.dist-info}/licenses/LICENSE.spdx +0 -0
  41. {opencos_eda-0.3.11.dist-info → opencos_eda-0.3.13.dist-info}/top_level.txt +0 -0
opencos/commands/flist.py CHANGED
@@ -6,11 +6,13 @@ Intended to be overriden by Tool based classes (such as CommandFListVivado, etc)
6
6
  # pylint: disable=too-many-statements
7
7
 
8
8
  import os
9
+ import shlex
9
10
 
10
- from opencos import util
11
- from opencos.eda_base import CommandDesign
11
+ from opencos import util, eda_config
12
+ from opencos.eda_base import CommandDesign, Tool
12
13
  from opencos.utils.str_helpers import strip_all_quotes
13
14
  from opencos.commands.sim import parameters_dict_get_command_list
15
+ from opencos.utils.str_helpers import strip_outer_quotes
14
16
 
15
17
  class CommandFList(CommandDesign):
16
18
  '''Base class command handler for: eda flist ...'''
@@ -19,29 +21,41 @@ class CommandFList(CommandDesign):
19
21
 
20
22
  def __init__(self, config: dict):
21
23
  CommandDesign.__init__(self, config=config)
22
- self.args.update({
23
- 'eda-dir' : 'eda.flist', # user can specify eda-dir if files are generated.
24
+
25
+ # If there's no tool attached, then we'll assume this flist is being created
26
+ # to run in `eda`, not some vendor tool.
27
+ self.flist_has_tool = isinstance(self, Tool)
28
+
29
+ self.flist_args = {
24
30
  'out' : "flist.out",
25
31
  'emit-define' : True,
26
32
  'emit-parameter' : True,
27
33
  'emit-incdir' : True,
34
+ 'emit-plusargs' : True,
28
35
  'emit-v' : True,
29
36
  'emit-sv' : True,
30
37
  'emit-vhd' : True,
31
38
  'emit-cpp' : True,
32
39
  'emit-non-sources' : True, # as comments, from DEPS 'reqs'
40
+ 'emit-eda-args' : not self.flist_has_tool, # no Tool means flist for eda.
33
41
  'prefix-define' : "+define+",
34
42
  'prefix-parameter' : "-G",
35
43
  'prefix-incdir' : "+incdir+",
44
+ 'prefix-plusargs' : "+",
36
45
  'prefix-v' : "",
37
46
  'prefix-sv' : "",
38
47
  'prefix-vhd' : "",
39
48
  'prefix-cpp' : "",
40
49
  'prefix-non-sources' : "", # as comments anyway.
41
- # TODO(simon): make the define quoting work like the paths quoting
50
+ # NOTE - the defaults are for creating an flist that is suitable for 'eda', which for
51
+ # defines means: optionally single-quote the entire thing, double-quote string values
52
+ # only.
53
+ # Tool classes should override if they want to set safe-mode-defines=True.
54
+ # Tool classes may also avoid these args entirely in their derived CommandFList class
55
+ 'safe-mode-defines' : not self.flist_has_tool, # no Tool
42
56
  'bracket-quote-define': False,
43
57
  'single-quote-define': False,
44
- 'quote-define' : True,
58
+ 'quote-define' : self.flist_has_tool, # Tool, this is NOT for eda.
45
59
  'equal-define' : True,
46
60
  'escape-define-value': False,
47
61
  'quote-define-value' : False,
@@ -55,7 +69,14 @@ class CommandFList(CommandDesign):
55
69
 
56
70
  # ex: eda flist --print-to-stdout --emit-rel-path --quiet <target>
57
71
  'emit-rel-path' : False,
72
+ }
73
+
74
+
75
+ self.args.update({
76
+ 'eda-dir' : 'eda.flist', # user can specify eda-dir if files are generated.
58
77
  })
78
+ self.args.update(self.flist_args)
79
+
59
80
  self.args_help.update({
60
81
  'print-to-stdout': "do not save file, print to stdout",
61
82
  })
@@ -75,14 +96,15 @@ class CommandFList(CommandDesign):
75
96
  def get_flist_dict(self) -> dict:
76
97
  '''Returns dict of some internal class member vars, ignores args
77
98
 
78
- Useful for an extneral caller to get details about this CommandDesign child
99
+ Useful for an external caller to get details about this CommandDesign child
79
100
  object without generating a .f file, or having to know specifics about the
80
101
  class
81
102
  '''
82
103
  self.command_safe_set_tool_defines() # (Command.command_safe_set_tool_defines)
83
104
 
84
105
  ret = {}
85
- for key in ['files_sv', 'files_v', 'files_vhd', 'defines', 'incdirs']:
106
+ for key in ['files_sv', 'files_v', 'files_vhd', 'defines', 'incdirs',
107
+ 'parameters', 'unprocessed-plusargs']:
86
108
  # These keys must exist, all are lists, defines is a dict
87
109
  x = getattr(self, key, None)
88
110
  if isinstance(x, (dict, list)):
@@ -91,6 +113,134 @@ class CommandFList(CommandDesign):
91
113
  ret[key] = x
92
114
  return ret
93
115
 
116
+ def get_flist_defines_list(self) -> list:
117
+ '''Returns formatted list of str for known defines'''
118
+
119
+ ret = []
120
+ prefix = strip_all_quotes(self.args['prefix-define'])
121
+ for d, value in self.defines.items():
122
+
123
+ if value is None:
124
+ ret.append(prefix + d)
125
+ continue
126
+
127
+ # else, value exists:
128
+ safe_mode_guard_str_value = bool(
129
+ self.args['safe-mode-defines'] and isinstance(value, str) and ' ' in value
130
+ )
131
+
132
+ if self.args['bracket-quote-define']:
133
+ qd1 = "{"
134
+ qd2 = "}"
135
+ elif self.args['single-quote-define']:
136
+ qd1 = "'"
137
+ qd2 = "'"
138
+ elif self.args['quote-define']:
139
+ qd1 = '"'
140
+ qd2 = '"'
141
+ else:
142
+ qd1 = ''
143
+ qd2 = ''
144
+
145
+ if self.args['equal-define']:
146
+ ed1 = '='
147
+ else:
148
+ ed1 = ' '
149
+
150
+ if self.args['escape-define-value']:
151
+ value = value.replace('\\', '\\\\').replace('"', '\\"')
152
+ if self.args['quote-define-value']:
153
+ value = '"' + value + '"'
154
+ if safe_mode_guard_str_value:
155
+ value = strip_outer_quotes(value.strip('\n'))
156
+ value = '"' + value + '"'
157
+
158
+ if self.args['quote-define'] and value.startswith('"') and value.endswith('"'):
159
+ # If you wanted your define to look like:
160
+ # +define+"NAME=VALUE", but VALUE also has double quotes wrapping it,
161
+ # it's unlikely to work so we'll optimistically so escape the " wrapping value.
162
+ # If you have additional " in the middle of the value, good luck.
163
+ value = '\\"' + value[1:-1] + '\\"'
164
+
165
+ newline = prefix + qd1 + f"{d}{ed1}{value}" + qd2
166
+
167
+ if safe_mode_guard_str_value:
168
+ # wrap the entire thing with single-quotes, so it survives as a single
169
+ # token in an eda dot-f file:
170
+ newline = shlex.quote(newline)
171
+
172
+ ret.append(newline)
173
+
174
+ return ret
175
+
176
+ def get_flist_plusargs_list(self) -> list:
177
+ '''Returns formatted list of str for unprocessed plusargs
178
+
179
+ Tool based classes can override if they also want to query their own
180
+ processed plusargs, such as CommandSim.args[sim-plusargs']
181
+ '''
182
+ ret = []
183
+ for x in self.args.get('unprocessed-plusargs', []) + self.args.get('sim-plusargs', []):
184
+ if self.args['prefix-plusargs']:
185
+ if x.startswith('+'):
186
+ x = x[1:] # strip leading +
187
+ x = self.args['prefix-plusargs'] + x
188
+ ret.append(x)
189
+ return ret
190
+
191
+ def get_flist_parameter_list(self) -> list:
192
+ '''Returns formatted list of str for parameters'''
193
+ prefix = strip_all_quotes(self.args['prefix-parameter'])
194
+ return parameters_dict_get_command_list(
195
+ params=self.parameters, arg_prefix=prefix, for_flist=True
196
+ )
197
+
198
+ def get_flist_eda_args_list(self) -> list:
199
+ '''Returns list of eda args for an eda-capable flist
200
+
201
+ - This will NOT add any util based args (--color | --no-color, --debug, etc)
202
+ - This will NOT add any -f/--input-file args (those are already resolved)
203
+
204
+ - This WILL add --env-file args
205
+ - This WILL add --config-yml args that were not default value
206
+
207
+ Not intended to be overriden by Tool based command classes.
208
+ '''
209
+ ret = []
210
+
211
+ # --env-file(s), if used:
212
+ for env_file in util.env_files_loaded:
213
+ ret.append(f'--env-file={env_file}')
214
+
215
+ # --config-yml, if non-default:
216
+ ret.extend(eda_config.get_config_yml_args_for_flist())
217
+
218
+ # EDA args, but not the flist specific args, and only those that were modified.
219
+ for arg, _ in self.modified_args.items():
220
+
221
+ if arg in self.flist_args:
222
+ # do not emit flist command args
223
+ continue
224
+
225
+ value = self.args[arg]
226
+ if isinstance(value, bool):
227
+ if value:
228
+ ret.append(f'--{arg}')
229
+ else:
230
+ ret.append(f'--no-{arg}')
231
+ else:
232
+ ret.append(f'--{arg}={value}')
233
+ return ret
234
+
235
+ def get_additional_flist_args_list(self) -> list:
236
+ '''Derived classes may override, to output additional args in the flist'''
237
+ return []
238
+
239
+ def get_additional_flist_files_list(self) -> list:
240
+ '''Derived classes may override, to output additional files in the flist'''
241
+ return []
242
+
243
+
94
244
  def do_it(self) -> None:
95
245
  '''do_it() is the main entry point for creating the flist(),
96
246
 
@@ -105,6 +255,30 @@ class CommandFList(CommandDesign):
105
255
  self.write_eda_config_and_args()
106
256
  return
107
257
 
258
+ if self.config['tool']:
259
+ tool_string = f' (with --tool={self.config["tool"]})'
260
+ else:
261
+ tool_string = ''
262
+
263
+ # if config['tool'] is set, but self.flist_has_tool is False, we're likely using
264
+ # this default handler CommandFList and the Tool class hasn't defined what they
265
+ # do. In this case, simply warn that this will emit a non-tool specific default flist
266
+ # intended for use by `eda`:
267
+ if self.config['tool'] and not self.flist_has_tool:
268
+ util.warning(f'For command="flist"{tool_string}, there is no tool',
269
+ 'specific handler for producing an flist. The default eda flist will',
270
+ 'be emitted')
271
+ # If this happens, you'll likely want the Tool based defines (that were never set
272
+ # by Tool.set_tool_defines(self) b/c we have no Tool class.
273
+ # TODO(drew): This is only a best-effort, we could create a derived Tool object and
274
+ # instead call obj.set_tool_defines(), and update self.defines instead?
275
+ _tool_config = self.config.get('tools', {}).get(self.config['tool'], {})
276
+ self.defines.update(
277
+ _tool_config.get('defines', {})
278
+ )
279
+
280
+
281
+
108
282
  # check if we're overwriting the output flist file.
109
283
  if self.args['print-to-stdout']:
110
284
  pass
@@ -153,41 +327,33 @@ class CommandFList(CommandDesign):
153
327
  f = os.path.relpath(f)
154
328
  print('## ' + prefix + pq1 + f + pq2, file=fo)
155
329
 
330
+ if self.args['emit-eda-args']:
331
+ for newline in self.get_flist_eda_args_list():
332
+ print(newline, file=fo)
333
+
334
+ defines_lines = self.get_flist_defines_list()
335
+ if not self.args['emit-define'] and defines_lines:
336
+ util.warning(f'Command "flist"{tool_string}, has defines present but they were not',
337
+ f'included in the output flist: {defines_lines}')
338
+
339
+ parameter_lines = self.get_flist_parameter_list()
340
+ if not self.args['emit-parameter'] and parameter_lines:
341
+ util.warning(f'Command "flist"{tool_string}, has parameters present but they were not',
342
+ f'included in the output flist: {parameter_lines}')
343
+
344
+ plusarg_lines = self.get_flist_plusargs_list()
345
+ if not self.args['emit-plusargs'] and plusarg_lines:
346
+ util.warning(f'Command "flist"{tool_string}, has plusargs present but they were not',
347
+ f'included in the output flist: {plusarg_lines}')
348
+
156
349
  if self.args['emit-define']:
157
- prefix = strip_all_quotes(self.args['prefix-define'])
158
- for d, value in self.defines.items():
159
- if value is None:
160
- newline = prefix + d
161
- else:
162
- if self.args['bracket-quote-define']:
163
- qd1 = "{"
164
- qd2 = "}"
165
- elif self.args['single-quote-define']:
166
- qd1 = "'"
167
- qd2 = "'"
168
- elif self.args['quote-define']:
169
- qd1 = '"'
170
- qd2 = '"' # rename this one when things calm down
171
- else:
172
- qd1 = ''
173
- qd2 = ''
174
- if self.args['equal-define']:
175
- ed1 = '='
176
- else:
177
- ed1 = ' '
178
- if self.args['escape-define-value']:
179
- value = value.replace('\\', '\\\\').replace('"', '\\"')
180
- if self.args['quote-define-value']:
181
- value =f'"{value}"'
182
- newline = prefix + qd1 + f"{d}{ed1}{value}" + qd2
350
+ for newline in defines_lines:
183
351
  print(newline, file=fo)
184
352
 
185
353
  if self.args['emit-parameter']:
186
- prefix = strip_all_quotes(self.args['prefix-parameter'])
187
- for item in parameters_dict_get_command_list(
188
- params=self.parameters, arg_prefix=prefix
189
- ):
190
- print(item, file=fo)
354
+ for newline in parameter_lines:
355
+ print(newline, file=fo)
356
+
191
357
 
192
358
  if self.args['emit-incdir']:
193
359
  prefix = strip_all_quotes(self.args['prefix-incdir'])
@@ -195,12 +361,24 @@ class CommandFList(CommandDesign):
195
361
  if self.args['emit-rel-path']:
196
362
  i = os.path.relpath(i)
197
363
  print(prefix + pq1 + i + pq2, file=fo)
364
+
365
+ if self.args['emit-plusargs']:
366
+ for newline in plusarg_lines:
367
+ print(newline, file=fo)
368
+
369
+
370
+ # Hook for derived classes to optionally print additional custom args, prior to
371
+ # any files:
372
+ for newline in self.get_additional_flist_args_list():
373
+ print(newline, file=fo)
374
+
198
375
  if self.args['emit-v']:
199
376
  prefix = strip_all_quotes(self.args['prefix-v'])
200
377
  for f in self.files_v:
201
378
  if self.args['emit-rel-path']:
202
379
  f = os.path.relpath(f)
203
380
  print(prefix + pq1 + f + pq2, file=fo)
381
+
204
382
  if self.args['emit-sv']:
205
383
  prefix = strip_all_quotes(self.args['prefix-sv'])
206
384
  for f in self.files_sv:
@@ -220,6 +398,11 @@ class CommandFList(CommandDesign):
220
398
  f = os.path.relpath(f)
221
399
  print(prefix + pq1 + f + pq2, file=fo)
222
400
 
401
+ # Hook for derived classes to optionally print additional flist items after
402
+ # any files:
403
+ for newline in self.get_additional_flist_files_list():
404
+ print(newline, file=fo)
405
+
223
406
  if self.args['print-to-stdout']:
224
407
  print() # don't need to close fo (None)
225
408
  else:
opencos/commands/multi.py CHANGED
@@ -281,7 +281,7 @@ class CommandMulti(CommandParallel):
281
281
  if parsed.parallel < 1 or parsed.parallel > 256:
282
282
  self.error("Arg 'parallel' must be between 1 and 256")
283
283
 
284
- command = self.get_command_from_unparsed_args(tokens=unparsed)
284
+ command = self.get_sub_command_from_config()
285
285
 
286
286
  # Need to know the tool for this command, either it was set correctly via --tool and/or
287
287
  # the command (class) will tell us.
@@ -354,9 +354,11 @@ class CommandMulti(CommandParallel):
354
354
 
355
355
  if self.args['print-targets']:
356
356
  util.info('Multi print-targets (will not run jobs): -->')
357
- for t in self.targets:
357
+ print_targets = [t[0] for t in self.targets]
358
+ print_targets.sort()
359
+ for x in print_targets:
358
360
  # t = tuple of (target:str, tool:str), we just want the target.
359
- print(f' {t[0]}')
361
+ print(f' {x}')
360
362
  else:
361
363
  util.debug("Multi: converting list of targets into list of jobs")
362
364
  self.jobs = []
opencos/commands/sim.py CHANGED
@@ -12,15 +12,32 @@ Note that CommandSim is also a base class for opencos.commands.elab.CommandElab.
12
12
  # pylint: disable=too-many-arguments,too-many-positional-arguments
13
13
 
14
14
  import os
15
+ import shlex
15
16
 
16
17
  from opencos import util, export_helper
17
18
  from opencos.eda_base import CommandDesign, Tool
18
19
  from opencos.utils import status_constants
19
20
 
20
- from opencos.utils.str_helpers import sanitize_defines_for_sh, strip_outer_quotes
21
+ from opencos.utils.str_helpers import strip_outer_quotes
21
22
 
22
- def parameters_dict_get_command_list(params: dict, arg_prefix: str = '-G') -> list:
23
- '''Given dict of parameters, returns a command list'''
23
+ def parameters_dict_get_command_list(
24
+ params: dict, arg_prefix: str = '-G',
25
+ hier_delimiter: str = '.',
26
+ top_hier_str: str = '',
27
+ for_flist: bool = False
28
+ ) -> list:
29
+ '''Given dict of parameters, returns a command list.
30
+
31
+ Some simulators will set ALL parameters with Name=Value at all levels of hierarchy,
32
+ so to avoid that and only set top level parameters, but still allow package or
33
+ hierarchy parameters to be set, you can use:
34
+
35
+ hier_delimiter='/'
36
+ top_hier_str=f'/{TOP_MODULE_NAME}/'
37
+
38
+ Which will, if a parameter Name does not contain the heir delimiter, the Name
39
+ will be prepended with top_hier_str.
40
+ '''
24
41
 
25
42
  ret_list = []
26
43
  if ' ' in arg_prefix:
@@ -34,13 +51,29 @@ def parameters_dict_get_command_list(params: dict, arg_prefix: str = '-G') -> li
34
51
  if not isinstance(v, (int, str)):
35
52
  util.warning(f'parameter {k} has value: {v}, parameters must be int/string types')
36
53
 
54
+ if top_hier_str and hier_delimiter not in k:
55
+ k = f'{top_hier_str}{k}'
56
+
37
57
  ret_list.extend(arg_list_prefix)
38
58
  if isinstance(v, int):
39
59
  ret_list.append(f'{arg_str_prefix}{k}={v}')
40
60
  else: # string
61
+ # Note if for_flist=True, then we have to take extra care on what gets
62
+ # double-quotes " and \".
41
63
  v = strip_outer_quotes(v.strip('\n'))
42
64
  v = '"' + v + '"'
43
- ret_list.append(f'{arg_str_prefix}{k}={sanitize_defines_for_sh(v)}')
65
+
66
+ if for_flist:
67
+ # do not wrap with " unless there are spaces, if there are spaces
68
+ # wrap the entire thing with single quotes (bash)
69
+ if ' ' in v:
70
+ ret_list.append(shlex.quote(f'{arg_str_prefix}{k}={v}'))
71
+ else:
72
+ ret_list.append(f'{arg_str_prefix}{k}={v}')
73
+ else:
74
+ # This is for a tool, so simply guard single quotes:
75
+ v = v.replace("'", "\\" + "'")
76
+ ret_list.append(f'{arg_str_prefix}{k}={v}')
44
77
  return ret_list
45
78
 
46
79
 
@@ -79,6 +112,8 @@ class CommandSim(CommandDesign): # pylint: disable=too-many-public-methods
79
112
  # not used by other simulators, so these can go in DEPS files for custom things
80
113
  # like -CFLAGS -O0, etc.
81
114
  'verilate-args': [],
115
+ 'ext-defines-sv-fname': '',
116
+ 'license-queue': False,
82
117
  })
83
118
  self.args_help.update({
84
119
  'pre-sim-tcl': (
@@ -124,6 +159,15 @@ class CommandSim(CommandDesign): # pylint: disable=too-many-public-methods
124
159
  'uvm-version': (
125
160
  'Used if --uvm is set, for example --uvm-version=1.2'
126
161
  ),
162
+ 'ext-defines-sv-fname': (
163
+ 'If set to empty str, --ext-defines-sv-fname="", applies defines to tool via args.'
164
+ ' If set to non-emtpy str as a .sv file name, such as'
165
+ ' --ext-defines-sv-fname=_ext_defines.sv, saves defines to a file name in the'
166
+ ' work-dif, and adds this file to the SV/Verilog file list ahead of other files.'
167
+ ),
168
+ 'license-queue': (
169
+ 'Set to enable env vars LICENSE_QUEUE=1 which has effects on certain simulators'
170
+ ),
127
171
 
128
172
  })
129
173
 
@@ -133,9 +177,24 @@ class CommandSim(CommandDesign): # pylint: disable=too-many-public-methods
133
177
 
134
178
 
135
179
 
136
- def process_parameters_get_list(self, arg_prefix: str = '-G') -> list:
137
- '''Returns list (suitable command list for shell or for tool) from self.parameters'''
138
- return parameters_dict_get_command_list(params=self.parameters, arg_prefix=arg_prefix)
180
+ def process_parameters_get_list(
181
+ self, arg_prefix: str = '-G', **kwargs
182
+ ) -> list:
183
+ '''Returns list (suitable command list for shell or for tool) from self.parameters
184
+
185
+ Some simulators will set ALL parameters with Name=Value at all levels of hierarchy,
186
+ so to avoid that and only set top level parameters, but still allow package or
187
+ hierarchy parameters to be set, you can use args (in kwargs):
188
+
189
+ hier_delimiter='/'
190
+ top_hier_str=f'/{TOP_MODULE_NAME}/'
191
+
192
+ Which will, if a parameter Name does not contain the heir delimiter, the Name
193
+ will be prepended with top_hier_str.
194
+ '''
195
+ return parameters_dict_get_command_list(
196
+ params=self.parameters, arg_prefix=arg_prefix, **kwargs
197
+ )
139
198
 
140
199
  def process_plusarg(self, plusarg: str, pwd: str = os.getcwd()) -> None:
141
200
  '''Override for CommandDesign.process_plusarg(..)'''
@@ -159,6 +218,8 @@ class CommandSim(CommandDesign): # pylint: disable=too-many-public-methods
159
218
  if self.stop_process_tokens_before_do_it():
160
219
  return unparsed
161
220
 
221
+ self.handle_arg_license_queue()
222
+
162
223
  # add defines for this job type
163
224
  if self.args['lint'] or self.args['stop-after-elaborate']:
164
225
  self.args['lint'] = True
@@ -572,3 +633,56 @@ class CommandSim(CommandDesign): # pylint: disable=too-many-public-methods
572
633
  search_paths=self.args['work-dir'], file_extension='vcd',
573
634
  typ='waveform', description='Simulation Waveform VCD (Value Change Dump) file'
574
635
  )
636
+
637
+
638
+ def create_ext_defines_sv(self) -> None:
639
+ '''Creates _ext_defines.sv in the work-dir, adds to front of self.files_sv'''
640
+
641
+ if not self.defines:
642
+ return
643
+
644
+ if not self.args['ext-defines-sv-fname']:
645
+ return
646
+
647
+ fpath = os.path.join(self.args['work-dir'], self.args['ext-defines-sv-fname'])
648
+ fpath_abs = os.path.abspath(fpath)
649
+ if fpath_abs in self.files:
650
+ util.warning(f'{fpath} already exists, will overwrite. Consider changing the value',
651
+ f'of arg: --ext-defines-sv-fname={self.args["ext-defines-sv-fname"]}')
652
+ if not fpath.endswith('.sv') and not fpath.endswith('.v'):
653
+ util.error(f'--ext-defines-sv-fname={self.args["ext-defines-sv-fname"]} must end with',
654
+ '.v or .sv')
655
+ return
656
+
657
+ ifdef_guard = os.path.split(self.args['ext-defines-sv-fname'])[1].upper()
658
+ ifdef_guard = ifdef_guard.replace('.', '__').replace('/', '__')
659
+ with open(fpath, 'w', encoding='utf-8') as f:
660
+ f.write(f'`ifndef {ifdef_guard}\n')
661
+ f.write(f'`define {ifdef_guard}\n')
662
+ f.write('\n')
663
+ for key, value in self.defines.items():
664
+ if value is None:
665
+ f.write(f'`define {key}\n')
666
+ else:
667
+ # if the value v is a double-quoted string, such as v='"hi"' that needs
668
+ # to be preserved.
669
+ f.write(f'`define {key} {value}\n')
670
+
671
+ f.write('\n')
672
+ f.write(f'`endif // {ifdef_guard}\n')
673
+
674
+ self.files_sv.insert(0, fpath_abs)
675
+ self.files[fpath_abs] = True
676
+ self.files_caller_info[fpath_abs] = (
677
+ 'opencos.commands.sim::CommandSim::create_ext_defines_sv'
678
+ )
679
+ util.info(f'command "sim": {fpath} created for defines: {list(self.defines.keys())}')
680
+
681
+
682
+ def handle_arg_license_queue(self) -> None:
683
+ '''Handles self.args['license-queue'] (bool) to set env vars'''
684
+ if not self.args['license-queue']:
685
+ return
686
+
687
+ if 'LICENSE_QUEUE' not in os.environ:
688
+ os.environ['LICENSE_QUEUE'] = '1'
opencos/commands/sweep.py CHANGED
@@ -72,7 +72,7 @@ class CommandSweep(CommandDesign, CommandParallel):
72
72
 
73
73
  self.check_args()
74
74
 
75
- self.single_command = self.get_command_from_unparsed_args(tokens=unparsed)
75
+ self.single_command = self.get_sub_command_from_config()
76
76
 
77
77
  self._append_sweep_args(arg_tokens=arg_tokens)
78
78
 
@@ -204,9 +204,9 @@ class CommandUpload(Command):
204
204
  tool = tools_found.pop() # only item in set
205
205
  # Do we have a handler for this in our config?
206
206
  if tool in self.config.get('tools_loaded', []):
207
- auto_tools_order = self.config.get('auto_tools_order', [{}])[0]
208
- if tool in auto_tools_order:
209
- cls_str = auto_tools_order[tool].get('handlers', {}).get(self.command_name, None)
207
+ tool_cfg = self.config.get('tools', {}).get(tool, {})
208
+ if tool_cfg:
209
+ cls_str = tool_cfg.get('handlers', {}).get(self.command_name, None)
210
210
  if cls_str:
211
211
  cls = import_class_from_string(cls_str)
212
212
  if issubclass(cls, Command):
@@ -217,5 +217,5 @@ class CommandUpload(Command):
217
217
 
218
218
  warning(f'No handler found for tool={tool} to process bitfiles: {self.bitfiles}')
219
219
  debug(f'config -- tools_loaded: {self.config["tools_loaded"]}')
220
- debug(f'config -- auto_tools_order for tool: {self.config["auto_tools_order"][0][tool]}')
220
+ debug(f'config -- tools for tool: {self.config["tools"].get(tool, "")}')
221
221
  return self
opencos/commands/waves.py CHANGED
@@ -15,10 +15,12 @@ handler).
15
15
  # pylint: disable=R0801
16
16
 
17
17
  import os
18
+ import subprocess
18
19
 
19
20
  from opencos import util
20
21
  from opencos.eda_base import CommandDesign
21
22
  from opencos.files import safe_shutil_which
23
+ from opencos.utils import vscode_helper
22
24
 
23
25
 
24
26
  class CommandWaves(CommandDesign):
@@ -51,6 +53,48 @@ class CommandWaves(CommandDesign):
51
53
  + ' to stdout',
52
54
  })
53
55
 
56
+ def get_versions_of_tool(self, tool: str) -> str:
57
+ '''Similar to Tool.get_versions(), returns the version of 'tool' for tools like:
58
+
59
+ - vaporview
60
+ - gtkwave
61
+
62
+ This is called by eda_tool_helper.get_handler_tool_version(tool, cmd, config)
63
+ '''
64
+
65
+ entry = self.config.get('tools', {}).get(tool, {})
66
+
67
+ if entry and 'requires_vscode_extension' in entry:
68
+ # vaporview, surfer
69
+ vscode_ext_name = entry.get('requires_vscode_extension', [''])[0]
70
+ vscode_helper.init()
71
+ ver = vscode_helper.EXTENSIONS.get(vscode_ext_name)
72
+ return ver
73
+
74
+ if entry and tool == 'gtkwave':
75
+ # gtkwave --version is fast.
76
+ proc = None
77
+ try:
78
+ proc = subprocess.run(
79
+ [safe_shutil_which('gtkwave'), '--version'],
80
+ capture_output=True, check=False
81
+ )
82
+ except Exception as e:
83
+ util.debug(f'gtkwave --version: exception {e}')
84
+
85
+ if not proc or not proc.stdout:
86
+ return ''
87
+
88
+ for line in proc.stdout.decode('utf-8', errors='replace').split('\n'):
89
+ if line.lower().startswith('gtkwave analyzer v'):
90
+ parts = line.split(' ')
91
+ return parts[2][1:] # trim the leading 'v' in 'v1.2.3'
92
+ return ''
93
+
94
+ return ''
95
+
96
+
97
+
54
98
 
55
99
  def get_wave_files_in_dirs(self, wave_dirs: list, quiet: bool = False) -> list:
56
100
  '''Returns list of all wave files give wave_dirs (list)'''
opencos/deps/deps_file.py CHANGED
@@ -124,6 +124,7 @@ def get_all_targets( # pylint: disable=dangerous-default-value,too-many-locals,t
124
124
  targets = list(targets)
125
125
  if not targets and error_on_empty_return:
126
126
  error(f'get_all_targets: {base_path=} {filter_using_multi=} returned no targets')
127
+ targets.sort()
127
128
  return targets
128
129
 
129
130
  targets = set()
@@ -144,7 +145,9 @@ def get_all_targets( # pylint: disable=dangerous-default-value,too-many-locals,t
144
145
 
145
146
  if not targets and error_on_empty_return:
146
147
  error(f'get_all_targets: {base_path=} {dirs=} {filter_str=} returned no targets')
147
- return list(targets)
148
+ targets = list(targets)
149
+ targets.sort()
150
+ return targets
148
151
 
149
152
 
150
153
  def deps_target_get_deps_list(