opencos-eda 0.2.47__py3-none-any.whl → 0.2.49__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 (55) hide show
  1. opencos/__init__.py +4 -2
  2. opencos/_version.py +10 -7
  3. opencos/commands/flist.py +8 -7
  4. opencos/commands/multi.py +35 -18
  5. opencos/commands/sweep.py +9 -4
  6. opencos/commands/waves.py +1 -1
  7. opencos/deps/__init__.py +0 -0
  8. opencos/deps/defaults.py +69 -0
  9. opencos/deps/deps_commands.py +419 -0
  10. opencos/deps/deps_file.py +326 -0
  11. opencos/deps/deps_processor.py +670 -0
  12. opencos/deps_schema.py +7 -8
  13. opencos/eda.py +92 -67
  14. opencos/eda_base.py +625 -332
  15. opencos/eda_config.py +80 -14
  16. opencos/eda_extract_targets.py +22 -14
  17. opencos/eda_tool_helper.py +33 -7
  18. opencos/export_helper.py +166 -86
  19. opencos/export_json_convert.py +31 -23
  20. opencos/files.py +2 -1
  21. opencos/hw/__init__.py +0 -0
  22. opencos/{oc_cli.py → hw/oc_cli.py} +9 -4
  23. opencos/names.py +0 -4
  24. opencos/peakrdl_cleanup.py +13 -7
  25. opencos/seed.py +19 -11
  26. opencos/tests/helpers.py +27 -14
  27. opencos/tests/test_deps_helpers.py +35 -32
  28. opencos/tests/test_eda.py +47 -41
  29. opencos/tests/test_eda_elab.py +5 -3
  30. opencos/tests/test_eda_synth.py +1 -1
  31. opencos/tests/test_oc_cli.py +1 -1
  32. opencos/tests/test_tools.py +3 -2
  33. opencos/tools/iverilog.py +2 -2
  34. opencos/tools/modelsim_ase.py +2 -2
  35. opencos/tools/riviera.py +1 -1
  36. opencos/tools/slang.py +1 -1
  37. opencos/tools/surelog.py +1 -1
  38. opencos/tools/verilator.py +1 -1
  39. opencos/tools/vivado.py +1 -1
  40. opencos/tools/yosys.py +4 -3
  41. opencos/util.py +440 -483
  42. opencos/utils/__init__.py +0 -0
  43. opencos/utils/markup_helpers.py +98 -0
  44. opencos/utils/str_helpers.py +111 -0
  45. opencos/utils/subprocess_helpers.py +108 -0
  46. {opencos_eda-0.2.47.dist-info → opencos_eda-0.2.49.dist-info}/METADATA +1 -1
  47. opencos_eda-0.2.49.dist-info/RECORD +88 -0
  48. {opencos_eda-0.2.47.dist-info → opencos_eda-0.2.49.dist-info}/entry_points.txt +1 -1
  49. opencos/deps_helpers.py +0 -1346
  50. opencos_eda-0.2.47.dist-info/RECORD +0 -79
  51. /opencos/{pcie.py → hw/pcie.py} +0 -0
  52. {opencos_eda-0.2.47.dist-info → opencos_eda-0.2.49.dist-info}/WHEEL +0 -0
  53. {opencos_eda-0.2.47.dist-info → opencos_eda-0.2.49.dist-info}/licenses/LICENSE +0 -0
  54. {opencos_eda-0.2.47.dist-info → opencos_eda-0.2.49.dist-info}/licenses/LICENSE.spdx +0 -0
  55. {opencos_eda-0.2.47.dist-info → opencos_eda-0.2.49.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,419 @@
1
+ '''opencos.deps.deps_commands - pymodule for processing DEPS markup deps-commands
2
+
3
+ This supports things like:
4
+ my_target:
5
+ - deps:
6
+ - commands:
7
+ - shell: <string>
8
+ - peakrdl: <string>
9
+ - work-dir-add-sources: <list>
10
+ '''
11
+
12
+
13
+ import os
14
+ from pathlib import Path
15
+ import re
16
+ import shutil
17
+
18
+ from opencos.util import debug, error, warning, ShellCommandList
19
+ from opencos.deps.defaults import SUPPORTED_COMMAND_KEYS
20
+
21
+ THISPATH = os.path.dirname(__file__)
22
+ PEAKRDL_CLEANUP_PY = os.path.join(THISPATH, '..', 'peakrdl_cleanup.py')
23
+
24
+ def path_substitutions_relative_to_work_dir(
25
+ exec_list: list, info_str: str, target_path: str,
26
+ enable_filepath_subst: bool, enable_dirpath_subst: bool
27
+ ) -> list:
28
+ '''For shell commands, since eda.py Command objects operate out of a "work-dir", it can
29
+ make shell commands confusing with additional ../.. to get to the calling directory, and
30
+ the original target directory is completely lost otherwise.
31
+
32
+ By default, if a FILE exists in the calling target's path, we will substitute that file
33
+ instead of looking in the work-dir. (can be disabled via filepath-subst-target-dir: False)
34
+
35
+ Optionally if a DIR exists in the calling target's path, will will substitute that DIR
36
+ instead of relative to work-dir. This can be annoying with relative paths like ../ (which
37
+ would exist in both) so this is disabled by default (can be enabled with
38
+ dirpath-subst-target-dir: True)
39
+ '''
40
+
41
+ if not enable_filepath_subst and not enable_dirpath_subst:
42
+ return exec_list
43
+
44
+ # Look for path substitutions, b/c we later "work" in self.args['work-dir'], but
45
+ # files should be relative to our target_path.
46
+ for i,word in enumerate(exec_list):
47
+ m = re.search(r'(\.+\/+[^"\;\:\|\<\>\*]*)$', word)
48
+ if m:
49
+ # ./, ../, file=./../whatever It might be a filepath.
50
+ # [^"\;\:\|\<\>\*] is looking for non-path like characters, so we dont' have a trailing
51
+ # " : ; < > |
52
+ # try and see if this file or dir exists. Note that files in the
53
+ # self.args['work-dir'] don't need this, and we can't assume dir levels in the work-dir.
54
+ try:
55
+ try_path = os.path.abspath(os.path.join(os.path.abspath(target_path), m.group(1)))
56
+ if enable_filepath_subst and os.path.isfile(try_path):
57
+ # make the substitution
58
+ exec_list[i] = word.replace(m.group(1), try_path)
59
+ debug(f'file path substitution {info_str=} {target_path=}: replaced - {word=}'
60
+ f'is now ={exec_list[i]}. This can be disabled in DEPS with:',
61
+ '"filepath-subst-targetdir: false"')
62
+ elif enable_dirpath_subst and os.path.isdir(try_path):
63
+ # make the substitution
64
+ exec_list[i] = word.replace(m.group(1), try_path)
65
+ debug(f'dir path substitution {info_str=} {target_path=}: replaced - {word=}'
66
+ f'is now ={exec_list[i]}. This can be disabled in DEPS with:',
67
+ '"dirpath-subst-targetdir: false"')
68
+ except Exception:
69
+ pass
70
+
71
+ return exec_list
72
+
73
+
74
+ def line_with_var_subst( # pylint: disable=dangerous-default-value
75
+ line : str, replace_vars_dict: dict = {},
76
+ replace_vars_os_env: bool = False,
77
+ target_node: str = '', target_path: str = ''
78
+ ) -> str:
79
+ '''Given a line (often from a shell style command) perform var substitution'''
80
+
81
+ # We can try for replacing any formatted strings, using self.args, and os.environ?
82
+ # We have to do this per-word, so that missing replacements or tcl-like things, such
83
+ # as '{}' wouldn't bail if trying to do line.format(**dict)
84
+ if '{' not in line:
85
+ return line
86
+
87
+ if replace_vars_os_env:
88
+ replace_dict = {}
89
+ replace_dict.update(os.environ)
90
+ replace_dict.update(replace_vars_dict)
91
+ else:
92
+ replace_dict = replace_vars_dict
93
+
94
+ words = line.split()
95
+ for i,word in enumerate(words):
96
+ try:
97
+ words[i] = word.format(**replace_dict)
98
+ except Exception:
99
+ # this was an attempt, no need to catch exceptions if we couldn't replace the word.
100
+ pass
101
+
102
+ new_line = ' '.join(words)
103
+ if new_line != line:
104
+ debug(f'{target_node=} {target_path=} performed string format replacement,',
105
+ f'{line=} {new_line=}')
106
+ return new_line
107
+
108
+ debug(f'{target_node=} {target_path=} string format replacement attempted,',
109
+ f'no replacement. {line=}')
110
+ return line
111
+
112
+
113
+ def parse_deps_shell_str(
114
+ line: str, target_path: str, target_node: str,
115
+ enable_filepath_subst_target_dir: bool = True,
116
+ enable_dirpath_subst_target_dir: bool = False,
117
+ enable: bool = True
118
+ ) -> dict:
119
+ '''Returns None or a dict of a possible shell command from line (str)
120
+
121
+ Examples of 'line' str:
122
+ shell@echo "hello world" > hello.txt
123
+ shell@ generate_something.sh
124
+ shell@ generate_this.py --input=some_data.json
125
+ shell@ cp ./some_file.txt some_file_COPY.txt
126
+ shell@ vivado -mode tcl -script ./some.tcl -tclargs foo_ip 1.2 foo_part foo_our_name \
127
+ {property value}
128
+
129
+ Returns None if no parsing was performed, or if enable is False
130
+
131
+ target_path (str) -- from dependency parsing (relative path of the DEPS file)
132
+ target_node (str) -- from dependency parsing, the target containing this 'line' str.
133
+ '''
134
+ if not enable:
135
+ return {}
136
+
137
+ m = re.match(r'^\s*shell\@(.*)\s*$', line)
138
+ if not m:
139
+ return {}
140
+
141
+ exec_str = m.group(1)
142
+ exec_list = exec_str.split()
143
+
144
+ # Look for path substitutions, b/c we later "work" in self.args['work-dir'], but
145
+ # files should be relative to our target_path.
146
+ # Note this can be disable in DEPS via path-subst-target-dir=False
147
+ exec_list = path_substitutions_relative_to_work_dir(
148
+ exec_list=exec_list, info_str='shell@', target_path=target_path,
149
+ enable_filepath_subst=enable_filepath_subst_target_dir,
150
+ enable_dirpath_subst=enable_dirpath_subst_target_dir
151
+ )
152
+
153
+ return {
154
+ 'target_path': os.path.abspath(target_path),
155
+ 'target_node': target_node,
156
+ 'run_from_work_dir': True, # may be overriden later.
157
+ 'exec_list': exec_list,
158
+ }
159
+
160
+
161
+ def parse_deps_work_dir_add_srcs(
162
+ line : str, target_path : str, target_node : str, enable : bool = True
163
+ ) -> dict:
164
+ '''Returns None or a dict describing source files to add from the work-dir path
165
+
166
+ Examples of 'line' str:
167
+ work_dir_add_srcs@ my_csrs.sv
168
+ work_dir_add_srcs@ some_generated_file.sv some_dir/some_other.v ./gen-vhd-dir/even_more.vhd
169
+
170
+ Returns None if no parsing was performed, or if enable is False
171
+
172
+ target_path (str) -- from dependency parsing (relative path of the DEPS file)
173
+ target_node (str) -- from dependency parsing, the target containing this 'line' str.
174
+ '''
175
+ if not enable:
176
+ return {}
177
+
178
+ m = re.match(r'^\s*work_dir_add_srcs\@(.*)\s*$', line)
179
+ if not m:
180
+ return {}
181
+
182
+ files_str = m.group(1)
183
+ file_list = files_str.split()
184
+
185
+ d = {'target_path': os.path.abspath(target_path),
186
+ 'target_node': target_node,
187
+ 'file_list': file_list,
188
+ }
189
+ return d
190
+
191
+
192
+ def parse_deps_peakrdl( # pylint: disable=too-many-locals
193
+ line: str, target_path: str, target_node: str, enable: bool = True,
194
+ enable_filepath_subst_target_dir: bool = True,
195
+ enable_dirpath_subst_target_dir: bool = False
196
+ ) -> dict:
197
+ '''Returns None or a dict describing a PeakRDL CSR register generator dependency
198
+
199
+ Examples of 'line' str:
200
+ peakrdl@ --cpuif axi4-lite-flat --top oc_eth_10g_1port_csrs ./oc_eth_10g_csrs.rdl
201
+
202
+ Returns None if no parsing was performed, or if enable=False
203
+
204
+ target_path (str) -- from dependency parsing (relative path of the DEPS file)
205
+ target_node (str) -- from dependency parsing, the target containing this 'line' str.
206
+ '''
207
+
208
+ m = re.match(r'^\s*peakrdl\@(.*)\s*$', line)
209
+ if not m:
210
+ return None
211
+
212
+ if not enable:
213
+ warning(f'peakrdl: encountered peakrdl command in {target_path=} {target_node=},' \
214
+ + ' however it is not enabled in edy.py - eda.config[dep_command_enables]')
215
+ return None
216
+
217
+ if not shutil.which('peakrdl'):
218
+ error('peakrdl: is not present in shell path, or the python package is not avaiable,' \
219
+ + f' yet we encountered a peakrdl command in {target_path=} {target_node=}')
220
+ return None
221
+
222
+
223
+ args_str = m.group(1)
224
+ args_list = args_str.split()
225
+
226
+ # Fish out the .rdl name
227
+ # If there is --top=value or --top value, then fish out that value (that will be the
228
+ # value.sv and value_pkg.sv generated names.
229
+
230
+ sv_files = []
231
+ top = ''
232
+ for i,str_value in enumerate(args_list):
233
+ if '--top=' in str_value:
234
+ _, top = str_value.split('=')
235
+ elif '--top' in str_value:
236
+ if i + 1 < len(args_list):
237
+ top = args_list[i + 1]
238
+
239
+ for str_item in args_list:
240
+ if str_item[-4:] == '.rdl':
241
+ _, rdl_fileonly = os.path.split(str_item) # strip all path info
242
+ rdl_filebase, _ = os.path.splitext(rdl_fileonly) # strip .rdl ext
243
+ if not top:
244
+ top = rdl_filebase
245
+
246
+ assert top != '', \
247
+ f'peakrdl@ DEP, could not determine value {top=}: {line=}, {target_path=}, {target_node=}'
248
+
249
+ sv_files += [ f'peakrdl/{top}_pkg.sv', f'peakrdl/{top}.sv' ]
250
+
251
+
252
+ shell_commands = [
253
+ [ 'peakrdl', 'regblock', '-o', str(Path('peakrdl/'))] + args_list,
254
+ # Edit file to apply some verilator waivers, etc, from peakrdl_cleanup.py:
255
+ [ 'python3', PEAKRDL_CLEANUP_PY, str(Path(f'peakrdl/{top}.sv')),
256
+ str(Path(f'peakrdl/{top}.sv')) ],
257
+ ]
258
+
259
+ ret_dict = {
260
+ 'shell_commands_list': [], # Entry needs target_path, target_node, exec_list
261
+ 'work_dir_add_srcs': {}, # Single dict needs target_path, target_node, file_list
262
+ }
263
+
264
+ # Make these look like a dep_shell_command:
265
+ for one_cmd_as_list in shell_commands:
266
+ ret_dict['shell_commands_list'].append(
267
+ parse_deps_shell_str(
268
+ line=(' shell@ ' + ' '.join(one_cmd_as_list)),
269
+ target_path=target_path,
270
+ target_node=target_node,
271
+ enable_filepath_subst_target_dir=enable_filepath_subst_target_dir,
272
+ enable_dirpath_subst_target_dir=enable_dirpath_subst_target_dir
273
+ )
274
+ )
275
+
276
+ # Make the work_dir_add_srcs dict:
277
+ ret_dict['work_dir_add_srcs'] = parse_deps_work_dir_add_srcs(
278
+ line = ' work_dir_add_srcs@ ' + ' '.join(sv_files),
279
+ target_path = target_path,
280
+ target_node = target_node
281
+ )
282
+
283
+ return ret_dict
284
+
285
+
286
+
287
+ def deps_commands_handler( #pylint: disable=too-many-locals,too-many-branches
288
+ config: dict, eda_args: dict,
289
+ dep : str, deps_file : str, target_node : str, target_path : str,
290
+ commands : list
291
+ ) -> (list, list):
292
+ ''' Returns a tuple of (shell_commands_list, work_dir_add_srcs_list), from processing
293
+ a DEPS.yml entry for something like:
294
+
295
+ target_foo:
296
+ deps:
297
+ - some_file
298
+ - commands: # (list of dicts) These are directly in a 'deps' list.
299
+ - shell: ...
300
+ - peakrdl: ...
301
+ - work-dir-add-sources: ...
302
+ - shell: ...
303
+
304
+ target_foo:
305
+ commands: # (list of dicts) These are in a target, but not ordered with other deps
306
+ - shell: ...
307
+ - peakrdl: ...
308
+ - work-dir-add-sources: ...
309
+ - shell: ...
310
+
311
+ We'd like to handle the list in a 'commands' entry, supporting it in a few places in a
312
+ DEPS.yml, so this this a generic way to do that. Currently these are broken down into Shell
313
+ commands and Files that will be later added to our sources (b/c we haven't run the Shell
314
+ commands yet, and the Files aren't present yet but we'd like them in our eda.py filelist in
315
+ order.
316
+
317
+ '''
318
+
319
+ shell_commands_list = []
320
+ work_dir_add_srcs_list = []
321
+
322
+ for command in commands:
323
+ assert isinstance(command, dict), \
324
+ (f'{type(command)=} must be dict, for {deps_file=} {target_node=} {target_path=} with'
325
+ f'{commands=}')
326
+
327
+ for key in command.keys():
328
+ if key not in SUPPORTED_COMMAND_KEYS:
329
+ error(f'deps_commands.process_commands - command {key=} not in',
330
+ f'{SUPPORTED_COMMAND_KEYS=}')
331
+
332
+ var_subst_dict = {} # this is per-command.
333
+ if config['dep_command_enables'].get('var_subst_os_env', False) and \
334
+ command.get('var-subst-os-env', False):
335
+ var_subst_dict.update(dict(os.environ))
336
+ if config['dep_command_enables'].get('var_subst_args', False) and \
337
+ command.get('var-subst-args', False):
338
+ var_subst_dict = eda_args
339
+
340
+ tee_fpath = command.get('tee', None)
341
+
342
+ # These are both optional bools, default True, would have to explicitly be set to False
343
+ # to take effect:
344
+ run_from_work_dir = command.get('run-from-work-dir', True)
345
+ filepath_subst_target_dir = command.get('filepath-subst-target-dir', True)
346
+ dirpath_subst_target_dir = command.get('dirpath-subst-target-dir', False)
347
+
348
+ for key,item in command.items():
349
+
350
+ # skip the tee and var-subst-* keys, since these types are bools and not commands.
351
+ if key in ['tee',
352
+ 'var-subst-os-env',
353
+ 'var-subst-args',
354
+ 'run-from-work-dir',
355
+ 'filepath-subst-target-dir',
356
+ 'dirpath-subst-target-dir']:
357
+ continue
358
+
359
+ # Optional variable substituion in commands
360
+ if isinstance(item, str):
361
+ item = line_with_var_subst(item, replace_vars_dict=var_subst_dict,
362
+ target_node=target_node, target_path=deps_file)
363
+
364
+ if key == 'shell':
365
+ # For now, piggyback on parse_deps_shell_str:
366
+ ret_dict = parse_deps_shell_str(
367
+ line=('shell@ ' + item),
368
+ target_path=target_path,
369
+ target_node=target_node,
370
+ enable_filepath_subst_target_dir=filepath_subst_target_dir,
371
+ enable_dirpath_subst_target_dir=dirpath_subst_target_dir,
372
+ enable=config['dep_command_enables']['shell'],
373
+ )
374
+ # To support 'tee: <some-file>' need to append it to last
375
+ # list item in ret_dict['exec_list'], and make it a util.ShellCommandList.
376
+ if tee_fpath:
377
+ ret_dict['exec_list'] = ShellCommandList(
378
+ ret_dict['exec_list'], tee_fpath=tee_fpath
379
+ )
380
+ ret_dict['run_from_work_dir'] = run_from_work_dir
381
+ assert ret_dict, f'shell command failed in {dep=} {target_node=} in {deps_file=}'
382
+ shell_commands_list.append(ret_dict) # process this later, append to ret tuple
383
+
384
+ elif key in ['work-dir-add-srcs', 'work-dir-add-sources']:
385
+ # For now, piggyback on parse_deps_work_dir_add_srcs:
386
+ ret_dict = parse_deps_work_dir_add_srcs(
387
+ line = 'work_dir_add_srcs@ ' + item,
388
+ target_path = target_path,
389
+ target_node = target_node,
390
+ enable=config['dep_command_enables']['work_dir_add_srcs'],
391
+ )
392
+ assert ret_dict, \
393
+ f'work-dir-add-srcs command failed in {dep=} {target_node=} in {deps_file=}'
394
+
395
+ work_dir_add_srcs_list.append(ret_dict) # process this later, append to ret tuple
396
+
397
+ elif key == 'peakrdl':
398
+ # for now, piggyback on parse_deps_peakrdl:
399
+ ret_dict = parse_deps_peakrdl(
400
+ line = 'peakrdl@ ' + item,
401
+ target_path = target_path,
402
+ target_node = target_node,
403
+ enable_filepath_subst_target_dir=filepath_subst_target_dir,
404
+ enable_dirpath_subst_target_dir=dirpath_subst_target_dir,
405
+ enable=config['dep_command_enables']['peakrdl']
406
+ )
407
+ assert ret_dict, f'peakrdl command failed in {dep=} {target_node=} in {deps_file=}'
408
+
409
+ # add all the shell commands:
410
+ shell_commands_list += ret_dict['shell_commands_list'] # several entries.
411
+ # all the work_dir_add_srcs:
412
+ work_dir_add_srcs_list += [ ret_dict['work_dir_add_srcs'] ] # single entry append
413
+
414
+
415
+ else:
416
+ assert False, \
417
+ f'unknown {key=} in {command=}, {item=} {dep=} {target_node=} in {deps_file=}'
418
+
419
+ return (shell_commands_list, work_dir_add_srcs_list)