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 +0 -0
- opencos/deps_helpers.py +680 -0
- opencos/eda.py +3751 -0
- opencos/names.py +61 -0
- opencos/oc_cli.py +2547 -0
- opencos/pcie.py +84 -0
- opencos/seed.py +28 -0
- opencos/util.py +449 -0
- opencos_eda-0.1.2.dist-info/LICENSE +373 -0
- opencos_eda-0.1.2.dist-info/LICENSE.spdx +6 -0
- opencos_eda-0.1.2.dist-info/METADATA +12 -0
- opencos_eda-0.1.2.dist-info/RECORD +15 -0
- opencos_eda-0.1.2.dist-info/WHEEL +5 -0
- opencos_eda-0.1.2.dist-info/entry_points.txt +3 -0
- opencos_eda-0.1.2.dist-info/top_level.txt +1 -0
opencos/__init__.py
ADDED
|
File without changes
|
opencos/deps_helpers.py
ADDED
|
@@ -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)
|