opencos-eda 0.1.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
opencos/__init__.py ADDED
File without changes
@@ -0,0 +1,680 @@
1
+ import os
2
+ import sys
3
+ import re
4
+ import shutil
5
+
6
+ from opencos.util import debug, info, warning, error
7
+
8
+ # Conditional imports, where someone may not have 'peakrdl' package installed.
9
+ # attempt to gracefull handle this instead of dying on missing module/package:
10
+ try:
11
+ import peakrdl
12
+ except:
13
+ pass
14
+
15
+ def dep_str2list(dep) -> list():
16
+ if type(dep) is str:
17
+ return re.split('\n+| +', dep) # convert \n separated to list, also split on spaces
18
+ else:
19
+ return dep
20
+
21
+ def deps_target_get_deps_list(entry, default_key:str='deps', target_node:str='',
22
+ deps_file:str='', entry_must_have_default_key:bool=False) -> list():
23
+ # For convenience, if key 'deps' in not in an entry, and entry is a list or string, then
24
+ # assume it's a list of deps
25
+ deps = list()
26
+ if type(entry) is str:
27
+ deps = dep_str2list(entry)
28
+ elif type(entry) is list:
29
+ deps = entry # already a list
30
+ elif type(entry) is dict:
31
+
32
+ if entry_must_have_default_key:
33
+ assert default_key in entry, f'{target_node=} in {deps_file=} does not have a key for {default_key=} in {entry=}'
34
+ deps = entry.get(default_key, list())
35
+ if type(deps) is str:
36
+ deps = dep_str2list(deps)
37
+
38
+ # Strip commented out list entries, strip blank strings, preserve non-strings
39
+ ret = list()
40
+ for dep in deps:
41
+ if type(dep) is str:
42
+ if dep.startswith('#') or dep == '':
43
+ continue
44
+ ret.append(dep)
45
+ return ret
46
+
47
+
48
+ def deps_list_target_sanitize(entry, default_key:str='deps', target_node:str='', deps_file:str='') -> dict():
49
+ # Since we support target entries that can be dict(), list(), or str(), sanitize
50
+ # them so they are a dict(), with a key named 'deps' that has a list of deps.
51
+ if type(entry) is dict:
52
+ return entry
53
+
54
+ if type(entry) is str:
55
+ mylist = dep_str2list(entry) # convert str to list()
56
+ return {default_key: mylist}
57
+
58
+ if type(entry) is list:
59
+ # it's already a list
60
+ return {default_key: entry}
61
+
62
+ assert False, f"Can't convert to list {entry=} {default_key=} {target_node=} {deps_file=}"
63
+
64
+
65
+ def path_substitutions_relative_to_work_dir(exec_list : list, info_str : str, target_path : str):
66
+
67
+ # Look for path substitutions, b/c we later "work" in self.args['work-dir'], but
68
+ # files should be relative to our target_path.
69
+ for iter,word in enumerate(exec_list):
70
+ m = re.search(r'(\.+\/+[^"\;\:\|\<\>\*]*)$', word)
71
+ if m:
72
+ # ./, ../, file=./../whatever It might be a filepath.
73
+ # [^"\;\:\|\<\>\*] is looking for non-path like characters, so we dont' have a trailing
74
+ # " : ; < > |
75
+ # try and see if this file exists. Note that files in the self.args['work-dir'] don't
76
+ # need this, and we can't assume dir levels in the work-dir.
77
+ try:
78
+ try_path = os.path.abspath(os.path.join(os.path.abspath(target_path), m.group(1)))
79
+ if os.path.isfile(try_path):
80
+ # make the substitution
81
+ exec_list[iter] = word.replace(m.group(1), try_path)
82
+ debug(f'path substitution {info_str=} {target_path=}: replaced - {word=} is now ={exec_list[iter]}')
83
+ except:
84
+ pass
85
+
86
+ return exec_list
87
+
88
+
89
+ def line_with_var_subst(line : str, replace_vars_dict=dict(), replace_vars_os_env=False,
90
+ target_node='', target_path='') -> str:
91
+ # We can try for replacing any formatted strings, using self.args, and os.environ?
92
+ # We have to do this per-word, so that missing replacements or tcl-like things, such
93
+ # as '{}' wouldn't bail if trying to do line.format(**dict)
94
+ if '{' not in line:
95
+ return line
96
+
97
+ if replace_vars_os_env:
98
+ replace_dict = dict()
99
+ replace_dict.update(os.environ)
100
+ replace_dict.update(replace_vars_dict)
101
+ else:
102
+ replace_dict = replace_vars_dict
103
+
104
+ words = line.split()
105
+ for iter,word in enumerate(words):
106
+ try:
107
+ words[iter] = word.format(**replace_dict)
108
+ except:
109
+ pass
110
+
111
+ new_line = ' '.join(words)
112
+ if new_line != line:
113
+ debug(f'{target_node=} {target_path=} performed string format replacement, {line=} {new_line=}')
114
+ return new_line
115
+ else:
116
+ debug(f'{target_node=} {target_path=} string format replacement attempted, no replacement. {line=}')
117
+ return line
118
+
119
+
120
+ class DepsProcessor:
121
+ def __init__(self, command_design_ref, deps_entry:dict, target:str,
122
+ target_path:str, target_node:str, deps_file:str):
123
+ '''
124
+ command_design_ref (eda.CommandDesign),
125
+ deps_entry (dict, target in DEPS.yml file)
126
+ target_node (str) -- key in DEPS.yml that got us the deps_entry, used for debug
127
+ deps_file (str) -- file, used for debug
128
+ '''
129
+
130
+ self.command_design_ref = command_design_ref
131
+ self.deps_entry = deps_entry
132
+ self.target = target
133
+ self.target_path = target_path
134
+ self.target_node = target_node # for debug
135
+ self.deps_file = deps_file # for debug
136
+
137
+ assert type(deps_entry) is dict, \
138
+ f'{deps_entry=} for {target_node=} in {deps_file=} must be a dict()'
139
+ assert command_design_ref is not None, \
140
+ f'called DepsProcessor.__init__, but no ref to CommandDesign object (is None)'
141
+
142
+ # named eda commands in the target:
143
+ # If this deps_entry has a 'sim', 'build', etc command entry for this target, grab that because it
144
+ # can set defines or other things specific to an eda command ('sim', for example)
145
+ self.entry_eda_command = self.deps_entry.get(command_design_ref.command_name, dict())
146
+
147
+ # alias some of the self.command_design_ref values
148
+ self.command_name = self.command_design_ref.command_name # str, for debug
149
+ self.args = self.command_design_ref.args # dict
150
+ self.set_arg = self.command_design_ref.set_arg # method
151
+
152
+
153
+ def process_deps_entry(self):
154
+
155
+ # Supported target keys:
156
+ # -- tags (or equivalent, to support multiple define/incdir/deps for a target)
157
+ # TODO(drew): this is not done.
158
+ # -- multi-commands (partially done)
159
+ # -- Named eda commands
160
+ # -- (partially done) sim or other eda commands (eda.py command specific things)
161
+ # basically, check the command, and apply/merge values to 'entry'?
162
+ # -- args
163
+ # -- defines
164
+ # -- incdirs
165
+ # -- top.
166
+ # -- commands (not in deps)
167
+ # -- deps
168
+
169
+ # TODO(drew): This does not yet support conditional inclusions based on defines,
170
+ # like the old DEPS files did with pattern:
171
+ # SOME_DEFINE ? dep_if_define_present : dep_if_define_not_present
172
+ # I would like to deprecate that in favor of 'tags'. However, likely will need
173
+ # to walk the entire DEPS.yml once to populate all args/defines, and then re-
174
+ # walk them to add/prune the correct tag based dependencies.
175
+
176
+ self.process_defines()
177
+ self.process_incdirs()
178
+ self.process_top()
179
+ self.process_args()
180
+ self.process_commands()
181
+
182
+ # We return the list of deps that still need to be resolved (['full_path/some_target', ...])
183
+ return self.process_deps_return_discovered_deps()
184
+
185
+
186
+ def process_defines(self):
187
+ '''Returns None, applies defines (dict, if any) from self.deps_entry to self.command_design_ref.'''
188
+
189
+ # Defines:
190
+ # apply command specific defines, with higher priority than the a deps_entry['sim']['defines'] entry,
191
+ # do this with dict1.update(dict2):
192
+ entry_defines = dict()
193
+ entry_defines.update(self.deps_entry.get('defines', dict()))
194
+ entry_defines.update(self.entry_eda_command.get('defines', dict()))
195
+ assert type(entry_defines) is dict, \
196
+ f'{entry_defines=} for {self.target_node=} in {self.deps_file=} must be a dict()'
197
+
198
+ for k,v in entry_defines.items():
199
+ if v is None or v == '':
200
+ # TODO(drew): this can be simplified if 'DEPS' files are deprecated.
201
+ self.command_design_ref.process_plusarg(f'+define+{k}')
202
+ else:
203
+ self.command_design_ref.process_plusarg(f'+define+{k}={v}')
204
+
205
+ def process_incdirs(self):
206
+ '''Returns None, applies incdirs (dict, if any) from self.deps_entry to self.command_design_ref.'''
207
+
208
+ entry_incdirs = list()
209
+ # apply command specific incdirs, higher in the incdir list:
210
+ entry_incdirs = dep_str2list(self.entry_eda_command.get('incdirs', list()))
211
+ entry_incdirs += dep_str2list(self.deps_entry.get('incdirs', list()))
212
+ assert type(entry_incdirs) is list, \
213
+ f'{entry_incdirs=} for {self.target_node=} in {self.deps_file=} must be a list()'
214
+ for x in entry_incdirs:
215
+ abspath = os.path.abspath(os.path.join(self.target_path, x))
216
+ if abspath not in self.command_design_ref.incdirs:
217
+ self.command_design_ref.incdirs.append(abspath)
218
+ debug(f'Added include dir {abspath} from {self.target_node=} {self.deps_file=}')
219
+
220
+ def process_top(self):
221
+ '''Returns None, applies top (str, if any) from self.deps_entry to self.command_design_ref.'''
222
+
223
+ if self.args['top'] != '':
224
+ return # already set
225
+
226
+ # For 'top', we overwrite it if not yet set.
227
+ # the command specific 'top' has higher priority.
228
+ entry_top = self.entry_eda_command.get('top', str()) # if someone set target['sim']['top']
229
+ if entry_top == '':
230
+ entry_top = self.deps_entry.get('top', str()) # if this target has target['top'] set
231
+
232
+ if entry_top != '':
233
+ if self.args['top'] == '':
234
+ # overwrite only if unset - we don't want other deps overriding the topmost
235
+ # target's setting for 'top'.
236
+ self.set_arg('top', str(entry_top))
237
+
238
+ def process_args(self):
239
+ '''Returns None, applies args (list or str, if any) from self.deps_entry to self.command_design_ref.'''
240
+
241
+ # for 'args', process each. command specific args take higher priority that target args.
242
+ # run_argparser_on_list: uses argparse, which takes precedence on the last arg that is set,
243
+ # so put the command specific args last.
244
+ # Note that if an arg is already set, we do NOT update it
245
+ args_list = dep_str2list(self.deps_entry.get('args', list()))
246
+ args_list += dep_str2list(self.entry_eda_command.get('args', list()))
247
+
248
+ # for args_list, re-parse these args to apply them to self.args.
249
+ unparsed = list()
250
+ if len(args_list) == 0:
251
+ return
252
+
253
+ unparsed = self.command_design_ref.run_argparser_on_list(tokens=args_list)
254
+ if len(unparsed) > 0:
255
+ # This is only a warning - because things like CommandFlist may not have every
256
+ # one of their self.args.keys() set for a given target, such as a 'sim' target that
257
+ # has --optimize, which is not an arg for CommandFlist. But we'd still like to get an flist
258
+ # from that target.
259
+ warning(f'For {self.command_design_ref.command_name}:' \
260
+ + f' {self.target_node=} in {self.deps_file=} has unknown args {unparsed=}')
261
+
262
+ def get_commands(self, commands=list(), dep=None):
263
+ '''Returns tuple of (shell_commands_list, work_dir_add_srcs_list).
264
+
265
+ Does not have side effects on self.command_design_ref.
266
+ '''
267
+
268
+ default_ret = list(), list()
269
+
270
+ if len(commands) == 0:
271
+ # if we weren't passed commands, then get them from our target (self.deps_entry)
272
+ commands = self.deps_entry.get('commands', list())
273
+
274
+ assert type(commands) is list, f'{self.deps_entry=} has {commands=} type is not list'
275
+
276
+ if len(commands) == 0: # No commands in this target
277
+ return default_ret
278
+
279
+ debug(f"Got {self.deps_entry=} for {self.target_node=} in {self.deps_file=}, has {commands=}")
280
+ shell_commands_list = list() # list of dict()s
281
+ work_dir_add_srcs_list = list() # list of dict()s
282
+
283
+ if dep is None:
284
+ # if we weren't passed a dep, then use our target_node (str key for our self.deps_entry)
285
+ dep = self.target_node
286
+
287
+ # Run handler for this to convert to shell commands in self.command_design_ref
288
+ shell_commands_list, work_dir_add_srcs_list = deps_commands_handler(
289
+ config=self.command_design_ref.config,
290
+ eda_args=self.command_design_ref.args,
291
+ dep=dep,
292
+ deps_file=self.deps_file,
293
+ target_node=self.target_node,
294
+ target_path=self.target_path,
295
+ commands=commands
296
+ )
297
+
298
+ return shell_commands_list, work_dir_add_srcs_list
299
+
300
+ def process_commands(self, commands=list(), dep=None):
301
+ '''Returns None, handles commands (shell, etc) in the target that aren' in the 'deps' list.
302
+
303
+ Applies these to self.command_design_ref.
304
+
305
+ You can optionally call this with a commands list and a single dep, which we support for
306
+ commands lists that exist within the 'deps' entry of a target.
307
+ '''
308
+
309
+ shell_commands_list, work_dir_add_srcs_list = self.get_commands(commands=commands, dep=dep)
310
+
311
+ # add these commands lists to self.command_design_ref:
312
+ # Process all shell_commands_list:
313
+ # This will track each shell command with its target_node and target_path
314
+ self.command_design_ref.append_shell_commands( cmds=shell_commands_list )
315
+ # Process all work_dir_add_srcs_list:
316
+ # This will track each added filename with its target_node and target_path
317
+ self.command_design_ref.append_work_dir_add_srcs( add_srcs=work_dir_add_srcs_list )
318
+
319
+
320
+ def process_deps_return_discovered_deps(self):
321
+ '''Returns list of deps targets to continue processing,
322
+
323
+ -- iterates through 'deps' for this target (self.deps_entry['deps'])
324
+ -- applies to self.command_design_ref
325
+ '''
326
+
327
+ # Get the list of deps from this entry (entry is a target in our DEPS.yml):
328
+ deps = deps_target_get_deps_list(
329
+ self.deps_entry,
330
+ target_node=self.target_node,
331
+ deps_file=self.deps_file
332
+ )
333
+
334
+ deps_targets_to_resolve = list()
335
+
336
+ # Process deps (list)
337
+ for dep in deps:
338
+
339
+ # In-line commands in the deps list, in case the results need to be in strict file
340
+ # order for other deps
341
+ if type(dep) is dict and 'commands' in dep:
342
+
343
+ commands = dep['commands']
344
+ debug(f"Got commands {dep=} for {self.target_node=} in {self.deps_file=}, {commands=}")
345
+
346
+ assert type(commands) is list, \
347
+ f'dep commands must be a list: {dep=} {self.deps_file=} {self.target_node=}'
348
+
349
+ # For this, we need to get the returned commands (to keep strict order w/ other deps)
350
+ command_tuple = self.get_commands( commands=commands, dep=dep )
351
+ # TODO(drew): it might be cleaner to return a dict instead of list, b/c those are also ordered
352
+ # and we can pass type information, something like:
353
+ deps_targets_to_resolve.append(command_tuple)
354
+
355
+
356
+ elif type(dep) is str and any(dep.startswith(x) for x in ['+define+', '+incdir']):
357
+ # Note: we still support +define+ and +incdir in the deps list.
358
+ # check for compile-time Verilog style plusarg, which are supported under targets
359
+ # These are not run-time Verilog style plusargs comsumable from within the .sv:
360
+ debug(f"Got plusarg (define, incdir) {dep=} for {self.target_node=} {self.deps_file=}")
361
+ self.command_design_ref.process_plusarg(plusarg=dep, pwd=self.target_path)
362
+
363
+ else:
364
+ # If we made it this far, dep better be a str type.
365
+ assert type(dep) is str, f'{dep=} {type(dep)=} must be str'
366
+ dep_path = os.path.join(self.target_path, dep)
367
+ debug(f"Got dep {dep_path=} for {self.target_node=} in {self.deps_file=}")
368
+
369
+ if dep_path in self.command_design_ref.targets_dict or \
370
+ dep_path in deps_targets_to_resolve:
371
+ debug(" - already processed, skipping")
372
+ elif os.path.exists(dep_path):
373
+ debug(" - raw file, adding to return list...")
374
+ deps_targets_to_resolve.append(dep_path) # append to list, keeping file order.
375
+ ###self.command_design_ref.add_file(dep_path)
376
+ else:
377
+ debug(f" - a target (not a file) needing to be resolved, adding to return list...")
378
+ deps_targets_to_resolve.append(dep_path) # append to list, keeping file order.
379
+
380
+ # We return the list of deps or files that still need to be resolved (['full_path/some_target', ...])
381
+ # items in this list are either:
382
+ # -- string (dep or file)
383
+ # -- tuple (unprocessed commands, in form: (shell_commands_list, work_dir_add_srcs_list))
384
+ # TODO(drew): it might be cleaner to return a dict instead of list, b/c those are also ordered
385
+ # and we can pass type information, something like:
386
+ # { dep1: 'file',
387
+ # dep2: 'target',
388
+ # dep3: 'command_tuple',
389
+ # }
390
+ return deps_targets_to_resolve
391
+
392
+
393
+
394
+
395
+ def parse_deps_shell_str(line : str, target_path : str, target_node : str, enable : bool = True):
396
+ '''Returns None or a dict of a possible shell command from line (str)
397
+
398
+ Examples of 'line' str:
399
+ shell@echo "hello world" > hello.txt
400
+ shell@ generate_something.sh
401
+ shell@ generate_this.py --input=some_data.json
402
+ shell@ cp ./some_file.txt some_file_COPY.txt
403
+ shell@ vivado -mode tcl -script ./some.tcl -tclargs foo_ip 1.2 foo_part foo_our_name {property value}
404
+
405
+ Returns None if no parsing was performed, or if enable is False
406
+
407
+ target_path (str) -- from dependency parsing (relative path of the DEPS file)
408
+ target_node (str) -- from dependency parsing, the target containing this 'line' str.
409
+ '''
410
+ if not enable:
411
+ return None
412
+
413
+ m = re.match(r'^\s*shell\@(.*)\s*$', line)
414
+ if not m:
415
+ return None
416
+
417
+ exec_str = m.group(1)
418
+ exec_list = exec_str.split()
419
+
420
+ # Look for path substitutions, b/c we later "work" in self.args['work-dir'], but
421
+ # files should be relative to our target_path.
422
+ exec_list = path_substitutions_relative_to_work_dir(exec_list=exec_list, info_str='shell@', target_path=target_path)
423
+
424
+ d = {'target_path': os.path.abspath(target_path),
425
+ 'target_node': target_node,
426
+ 'exec_list': exec_list,
427
+ }
428
+ return d
429
+
430
+
431
+ def parse_deps_work_dir_add_srcs(line : str, target_path : str, target_node : str, enable : bool = True):
432
+ '''Returns None or a dict describing source files to add from the work-dir path
433
+
434
+ Examples of 'line' str:
435
+ work_dir_add_srcs@ my_csrs.sv
436
+ work_dir_add_srcs@ some_generated_file.sv some_dir/some_other.v ./gen-vhd-dir/even_more.vhd
437
+
438
+ Returns None if no parsing was performed, or if enable is False
439
+
440
+ target_path (str) -- from dependency parsing (relative path of the DEPS file)
441
+ target_node (str) -- from dependency parsing, the target containing this 'line' str.
442
+ '''
443
+ if not enable:
444
+ return None
445
+
446
+ m = re.match(r'^\s*work_dir_add_srcs\@(.*)\s*$', line)
447
+ if not m:
448
+ return None
449
+
450
+ files_str = m.group(1)
451
+ file_list = files_str.split()
452
+
453
+ d = {'target_path': os.path.abspath(target_path),
454
+ 'target_node': target_node,
455
+ 'file_list': file_list,
456
+ }
457
+ return d
458
+
459
+
460
+ def parse_deps_peakrdl(line : str, target_path : str, target_node : str, enable : bool = True,
461
+ tool : str = ''):
462
+ '''Returns None or a dict describing a PeakRDL CSR register generator dependency
463
+
464
+ Examples of 'line' str:
465
+ peakrdl@ --cpuif axi4-lite-flat --top oc_eth_10g_1port_csrs ./oc_eth_10g_csrs.rdl
466
+
467
+ Returns None if no parsing was performed, or if enable=False
468
+
469
+ target_path (str) -- from dependency parsing (relative path of the DEPS file)
470
+ target_node (str) -- from dependency parsing, the target containing this 'line' str.
471
+ '''
472
+
473
+ m = re.match(r'^\s*peakrdl\@(.*)\s*$', line)
474
+ if not m:
475
+ return None
476
+
477
+ if not enable:
478
+ warning(f'peakrdl: encountered peakrdl command in {target_path=} {target_node=},' \
479
+ + ' however it is not enabled in edy.py - eda.config[dep_command_enables]')
480
+ return None
481
+
482
+ if not shutil.which('peakrdl') or \
483
+ 'peakrdl' not in globals().keys():
484
+
485
+ error('peakrdl: is not present in shell path, or the python package is not avaiable,' \
486
+ + f' yet we encountered a peakrdl command in {target_path=} {target_node=}')
487
+ return None
488
+
489
+
490
+ args_str = m.group(1)
491
+ args_list = args_str.split()
492
+
493
+ # Fish out the .rdl name
494
+ # If there is --top=value or --top value, then fish out that value (that will be the
495
+ # value.sv and value_pkg.sv generated names.
496
+
497
+ ## This is an example of how DEPS would look without this helper peakrdl@ DEPS string:
498
+ # shell@ peakrdl regblock -o peakrdl/ --cpuif axi4-lite-flat --top oc_eth_10g_1port_csrs ./oc_eth_10g_csrs.rdl
499
+ # shell@ echo "// verilator lint_off MULTIDRIVEN" | cat - peakrdl/oc_eth_10g_1port_csrs.sv > temp.sv && mv temp.sv peakrdl/oc_eth_10g_1port_csrs.sv
500
+ # shell@ echo "// verilator lint_on MULTIDRIVEN" >> peakrdl/oc_eth_10g_1port_csrs.sv
501
+ # work_dir_add_srcs@ ./peakrdl/oc_eth_10g_1port_csrs_pkg.sv ./peakrdl/oc_eth_10g_1port_csrs.sv
502
+
503
+ # TODO(drew): We also could fold Verilator config files into 'eda' to avoid this sort of thing, since
504
+ # peakrdl@ is now a known entity. Or have eda support verilator config files in DEPs (.vlt files)
505
+ # Such as verilator_peakrdl_wavivers.vlt:
506
+ # `verilator_config
507
+ # lint_off -rule MULTIDRIVEN -file "peakrdl/*.sv" -match "*"
508
+
509
+ sv_files = list()
510
+ top = ''
511
+ for iter,str_value in enumerate(args_list):
512
+ if '--top=' in str_value:
513
+ _, top = str_value.split('=')
514
+ elif '--top' in str_value:
515
+ if iter + 1 < len(args_list):
516
+ top = args_list[iter + 1]
517
+
518
+ for str_item in args_list:
519
+ if str_item[-4:] == '.rdl':
520
+ _, rdl_fileonly = os.path.split(str_item) # strip all path info
521
+ rdl_filebase, rdl_ext = os.path.splitext(rdl_fileonly) # strip .rdl
522
+ if top == '':
523
+ top = rdl_filebase
524
+
525
+ assert top != '', f'peakrdl@ DEP, could not determine value for {top=}: {line=}, {target_path=}, {target_node=}'
526
+
527
+ sv_files += [ f'peakrdl/{top}_pkg.sv', f'peakrdl/{top}.sv' ]
528
+
529
+ shell_commands = [
530
+ [ 'peakrdl', 'regblock', '-o', 'peakrdl/'] + args_list,
531
+ # Put // verilator lint_off MULTIDRIVEN at top of file:
532
+ [ 'echo', '"// verilator lint_off MULTIDRIVEN"', '|', f'cat - peakrdl/{top}.sv > temp.sv && mv temp.sv peakrdl/{top}.sv' ],
533
+ # Put // verilator lint_on MULTIDRIVEN at bottom of file
534
+ [ 'echo', '"// verilator lint_on MULTIDRIVEN"', ' >>', f'peakrdl/{top}.sv' ],
535
+ ]
536
+
537
+ if tool == 'verilator':
538
+ shell_commands += [
539
+
540
+ # Workaround for Verilator v5.031 issue w/ unpacked structs:
541
+ # -- https://github.com/verilator/verilator/issues/5590
542
+ # -- https://github.com/netchipguy/opencos/issues/35
543
+ # Change all struct { to struct packed {
544
+ [ 'sed', "'s/struct {/struct packed {/'", f'peakrdl/{top}.sv', ' > ', f'peakrdl/tmp_{top}.sv' ],
545
+ [ 'mv', f'peakrdl/tmp_{top}.sv', f'peakrdl/{top}.sv' ],
546
+ ]
547
+
548
+
549
+ ret_dict = {
550
+ 'shell_commands_list': list(), # Entry needs target_path, target_node, exec_list
551
+ 'work_dir_add_srcs': dict(), # Single dict needs target_path, target_node, file_list
552
+ }
553
+
554
+ # Make these look like a dep_shell_command:
555
+ for one_cmd_as_list in shell_commands:
556
+ ret_dict['shell_commands_list'].append(
557
+ parse_deps_shell_str(line = ' shell@ ' + ' '.join(one_cmd_as_list),
558
+ target_path = target_path,
559
+ target_node = target_node
560
+ )
561
+ )
562
+
563
+ # Make the work_dir_add_srcs dict:
564
+ ret_dict['work_dir_add_srcs'] = parse_deps_work_dir_add_srcs(line = ' work_dir_add_srcs@ ' + ' '.join(sv_files),
565
+ target_path = target_path,
566
+ target_node = target_node
567
+ )
568
+
569
+ return ret_dict
570
+
571
+
572
+
573
+ def deps_commands_handler(config: dict, eda_args: dict,
574
+ dep : str, deps_file : str, target_node : str, target_path : str,
575
+ commands : list):
576
+ ''' Returns a tuple of (shell_commands_list, work_dir_add_srcs_list), from processing
577
+ a DEPS.yml entry for something like:
578
+
579
+ target_foo:
580
+ deps:
581
+ - some_file
582
+ - commands: # (list of dicts) These are directly in a 'deps' list.
583
+ - shell: ...
584
+ - peakrdl: ...
585
+ - work-dir-add-sources: ...
586
+ - shell: ...
587
+
588
+ target_foo:
589
+ commands: # (list of dicts) These are in a target, but not ordered with other deps
590
+ - shell: ...
591
+ - peakrdl: ...
592
+ - work-dir-add-sources: ...
593
+ - shell: ...
594
+
595
+ We'd like to handle the list in a 'commands' entry, supporting it in a few places in a DEPS.yml, so this
596
+ this a generic way to do that. Currently these are broken down into Shell commands and Files
597
+ that will be later added to our sources (b/c we haven't run the Shell commands yet, and the Files
598
+ aren't present yet but we'd like them in our eda.py filelist in order.
599
+
600
+ '''
601
+
602
+ supported_command_keys = [
603
+ 'shell',
604
+ 'work-dir-add-srcs', 'work-dir-add-sources',
605
+ 'peakrdl',
606
+ 'var-subst-args',
607
+ 'var-subst-os-env',
608
+ ]
609
+
610
+ shell_commands_list = list()
611
+ work_dir_add_srcs_list = list()
612
+
613
+ for command in commands:
614
+ assert type(command) is dict, \
615
+ f'{type(command)=} must be dict, for {deps_file=} {target_node=} {target_path=} with {commands=}'
616
+
617
+ assert all(x in supported_command_keys for x in command.keys()), \
618
+ f'{command.keys()=} but we only support {supported_command_keys=}'
619
+
620
+ var_subst_dict = dict() # this is per-command.
621
+ if command.get('var-subst-os-env', False):
622
+ var_subst_dict.update(os.env)
623
+ if command.get('var-subst-args', False):
624
+ var_subst_dict = eda_args
625
+
626
+ for key,item in command.items():
627
+
628
+ # skip the var-subst-* keys, since these types are bools
629
+ if key.startswith('var-subst'):
630
+ continue
631
+
632
+ # Optional variable substituion in commands
633
+ if type(item) is str:
634
+ item = line_with_var_subst(item, replace_vars_dict=var_subst_dict,
635
+ target_node=target_node, target_path=deps_file)
636
+
637
+ if key == 'shell':
638
+ # For now, piggyback on parse_deps_shell_str:
639
+ ret_dict = parse_deps_shell_str(
640
+ line = 'shell@ ' + item,
641
+ target_path = target_path,
642
+ target_node = target_node,
643
+ enable=config['dep_command_enables']['shell'],
644
+ )
645
+ assert ret_dict, f'shell command failed in {dep=} {target_node=} in {deps_file=}'
646
+ shell_commands_list.append(ret_dict) # process this later, append to our to-be-returned tuple
647
+
648
+ elif key in ['work-dir-add-srcs', 'work-dir-add-sources']:
649
+ # For now, piggyback on parse_deps_work_dir_add_srcs:
650
+ ret_dict = parse_deps_work_dir_add_srcs(
651
+ line = 'work_dir_add_srcs@ ' + item,
652
+ target_path = target_path,
653
+ target_node = target_node,
654
+ enable=config['dep_command_enables']['work_dir_add_srcs'],
655
+ )
656
+ assert ret_dict, f'work-dir-add-srcs command failed in {dep=} {target_node=} in {deps_file=}'
657
+
658
+ work_dir_add_srcs_list.append(ret_dict) # process this later, append to our to-be-returned tuple
659
+
660
+ elif key == 'peakrdl':
661
+ # for now, piggyback on parse_deps_peakrdl:
662
+ ret_dict = parse_deps_peakrdl(
663
+ line = 'peakrdl@ ' + item,
664
+ target_path = target_path,
665
+ target_node = target_node,
666
+ enable=config['dep_command_enables']['peakrdl'],
667
+ tool=eda_args.get('tool', '')
668
+ )
669
+ assert ret_dict, f'peakrdl command failed in {dep=} {target_node=} in {deps_file=}'
670
+
671
+ # add all the shell commands:
672
+ shell_commands_list += ret_dict['shell_commands_list'] # several entries.
673
+ # all the work_dir_add_srcs:
674
+ work_dir_add_srcs_list += [ ret_dict['work_dir_add_srcs'] ] # single entry append
675
+
676
+
677
+ else:
678
+ assert False, f'unknown {key=} in {command=}, {item=} {dep=} {target_node=} in {deps_file=}'
679
+
680
+ return (shell_commands_list, work_dir_add_srcs_list)