opencos-eda 0.2.57__py3-none-any.whl → 0.3.1__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/_version.py +6 -3
- opencos/_waves_pkg.sv +34 -2
- opencos/commands/build.py +1 -0
- opencos/commands/export.py +1 -0
- opencos/commands/flist.py +1 -0
- opencos/commands/lec.py +1 -0
- opencos/commands/proj.py +1 -0
- opencos/commands/shell.py +4 -0
- opencos/commands/sim.py +47 -1
- opencos/commands/synth.py +4 -0
- opencos/deps/defaults.py +15 -7
- opencos/deps/deps_commands.py +84 -74
- opencos/deps/deps_file.py +11 -5
- opencos/deps/deps_processor.py +79 -3
- opencos/deps_schema.py +3 -0
- opencos/eda.py +2 -1
- opencos/eda_base.py +105 -34
- opencos/eda_config_defaults.yml +5 -1
- opencos/files.py +1 -0
- opencos/tests/deps_files/command_order/DEPS.yml +11 -0
- opencos/tests/helpers.py +50 -20
- opencos/tests/test_deps_helpers.py +37 -25
- opencos/tests/test_eda.py +26 -60
- opencos/tools/modelsim_ase.py +17 -9
- opencos/tools/riviera.py +31 -6
- opencos/tools/verilator.py +28 -38
- opencos/util.py +111 -16
- opencos/utils/vscode_helper.py +1 -1
- opencos/utils/vsim_helper.py +1 -1
- {opencos_eda-0.2.57.dist-info → opencos_eda-0.3.1.dist-info}/METADATA +2 -1
- {opencos_eda-0.2.57.dist-info → opencos_eda-0.3.1.dist-info}/RECORD +36 -36
- {opencos_eda-0.2.57.dist-info → opencos_eda-0.3.1.dist-info}/WHEEL +0 -0
- {opencos_eda-0.2.57.dist-info → opencos_eda-0.3.1.dist-info}/entry_points.txt +0 -0
- {opencos_eda-0.2.57.dist-info → opencos_eda-0.3.1.dist-info}/licenses/LICENSE +0 -0
- {opencos_eda-0.2.57.dist-info → opencos_eda-0.3.1.dist-info}/licenses/LICENSE.spdx +0 -0
- {opencos_eda-0.2.57.dist-info → opencos_eda-0.3.1.dist-info}/top_level.txt +0 -0
opencos/deps/deps_processor.py
CHANGED
|
@@ -4,11 +4,13 @@ a DEPS markup files targets (applying deps, reqs, commands, tags, incdirs, defin
|
|
|
4
4
|
CommandDesign ref object
|
|
5
5
|
'''
|
|
6
6
|
|
|
7
|
+
import argparse
|
|
7
8
|
import os
|
|
8
9
|
|
|
9
10
|
from opencos import files
|
|
10
11
|
from opencos import eda_config
|
|
11
|
-
from opencos.util import debug, info, warning, error
|
|
12
|
+
from opencos.util import debug, info, warning, error, read_tokens_from_dot_f, \
|
|
13
|
+
patch_args_for_dir
|
|
12
14
|
from opencos.utils.str_helpers import dep_str2list
|
|
13
15
|
from opencos.deps.deps_file import deps_target_get_deps_list
|
|
14
16
|
from opencos.deps.deps_commands import deps_commands_handler
|
|
@@ -48,6 +50,7 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
|
|
|
48
50
|
self.target_path = target_path
|
|
49
51
|
self.target_node = target_node # for debug
|
|
50
52
|
self.deps_file = deps_file # for debug
|
|
53
|
+
self.deps_dir, _ = os.path.split(deps_file)
|
|
51
54
|
self.caller_info = caller_info
|
|
52
55
|
|
|
53
56
|
assert isinstance(deps_entry, dict), \
|
|
@@ -150,7 +153,9 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
|
|
|
150
153
|
return tokens
|
|
151
154
|
|
|
152
155
|
|
|
153
|
-
def apply_args(
|
|
156
|
+
def apply_args( # pylint: disable=too-many-locals,too-many-branches
|
|
157
|
+
self, args_list:list
|
|
158
|
+
) -> list:
|
|
154
159
|
'''Given args_list, applies them to our self.command_design_ref obj
|
|
155
160
|
|
|
156
161
|
This will return unparsed args that weren't in the self.command_design_ref.args keys
|
|
@@ -162,10 +167,54 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
|
|
|
162
167
|
f"in {self.caller_info}")
|
|
163
168
|
tokens = dep_str2list(args_list)
|
|
164
169
|
|
|
170
|
+
# patch args relative to the DEPS (if self.deps_dir exists) so things like
|
|
171
|
+
# --build-tcl=<file> for relative <file> works when calling targets from any directory.
|
|
172
|
+
tokens = patch_args_for_dir(
|
|
173
|
+
tokens=tokens, patch_dir=self.deps_dir, caller_info=self.caller_info
|
|
174
|
+
)
|
|
175
|
+
|
|
165
176
|
# We're going to run an ArgumentParser here, which is not the most efficient
|
|
166
177
|
# thing to do b/c it runs on all of self.command_design_ref.args (dict) even
|
|
167
178
|
# if we're applying a single token.
|
|
168
179
|
|
|
180
|
+
# Since some args (util.py, eda_config.py, eda.py) can only be handled from command
|
|
181
|
+
# line, it would be nice if -f or --input-file is handled from DEPS, so we'll special
|
|
182
|
+
# case that now. Recursively resolve -f / --input-file.
|
|
183
|
+
parser = argparse.ArgumentParser(
|
|
184
|
+
prog='deps_processor -f/--input-file', add_help=False, allow_abbrev=False
|
|
185
|
+
)
|
|
186
|
+
parser.add_argument('-f', '--input-file', default=[], action='append',
|
|
187
|
+
help=(
|
|
188
|
+
'Input .f file to be expanded as eda args, defines, incdirs,'
|
|
189
|
+
' files, or targets.'
|
|
190
|
+
))
|
|
191
|
+
try:
|
|
192
|
+
parsed, unparsed = parser.parse_known_args(tokens + [''])
|
|
193
|
+
tokens2 = list(filter(None, unparsed))
|
|
194
|
+
except argparse.ArgumentError:
|
|
195
|
+
error('deps_processor -f/--input-file, problem attempting to parse_known_args for:',
|
|
196
|
+
f'{tokens}')
|
|
197
|
+
tokens2 = tokens
|
|
198
|
+
|
|
199
|
+
if parsed.input_file:
|
|
200
|
+
dotf_tokens = []
|
|
201
|
+
for filepath in parsed.input_file:
|
|
202
|
+
# Since this isn't command line, we have to assume the path is relative
|
|
203
|
+
# to this DEPS file.
|
|
204
|
+
if not os.path.isabs(filepath):
|
|
205
|
+
filepath = os.path.join(self.deps_dir, filepath)
|
|
206
|
+
dotf_tokens.extend(read_tokens_from_dot_f(
|
|
207
|
+
filepath=filepath, caller_info=self.caller_info, verbose=True
|
|
208
|
+
))
|
|
209
|
+
|
|
210
|
+
# put the .f files before the unparsed args.
|
|
211
|
+
tokens2 = dotf_tokens + tokens2
|
|
212
|
+
|
|
213
|
+
# recurse until we've resolved nested .f files.
|
|
214
|
+
return self.apply_args(args_list=tokens2)
|
|
215
|
+
|
|
216
|
+
tokens = tokens2 # if no --input-file values, keep parsing the remaining tokens2
|
|
217
|
+
|
|
169
218
|
# We have to special-case anything with --tool[=value] in tokens, otherwise
|
|
170
219
|
# the user may think they were allowed to set --tool, but in our flow the Command handler
|
|
171
220
|
# (self.command_design_ref) has already been chosen, so setting the tool can have
|
|
@@ -180,11 +229,38 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
|
|
|
180
229
|
_, unparsed = self.command_design_ref.run_argparser_on_list(
|
|
181
230
|
tokens=tokens
|
|
182
231
|
)
|
|
232
|
+
|
|
183
233
|
# Annoying, but check for plusargs in unparsed, and have referenced CommandDesign
|
|
184
234
|
# or CommandSim class handle it with process_plusarg.
|
|
185
|
-
for arg in unparsed:
|
|
235
|
+
for arg in list(unparsed):
|
|
186
236
|
if arg.startswith('+'):
|
|
187
237
|
self.command_design_ref.process_plusarg(plusarg=arg, pwd=self.target_path)
|
|
238
|
+
unparsed.remove(arg)
|
|
239
|
+
|
|
240
|
+
# For any leftover files, or targets, attempt to process those too:
|
|
241
|
+
for arg in list(unparsed):
|
|
242
|
+
# Since this isn't command line, we have to assume for files, the path is relative
|
|
243
|
+
# to this DEPS file.
|
|
244
|
+
if os.path.isabs(arg):
|
|
245
|
+
target = arg
|
|
246
|
+
else:
|
|
247
|
+
target = os.path.join(self.deps_dir, arg)
|
|
248
|
+
|
|
249
|
+
file_exists, fpath, forced_extension = files.get_source_file(target)
|
|
250
|
+
if file_exists:
|
|
251
|
+
_, file_ext = os.path.splitext(fpath)
|
|
252
|
+
if forced_extension or file_ext:
|
|
253
|
+
self.command_design_ref.add_file(fpath, caller_info=self.caller_info,
|
|
254
|
+
forced_extension=forced_extension)
|
|
255
|
+
unparsed.remove(arg)
|
|
256
|
+
|
|
257
|
+
else:
|
|
258
|
+
if not os.path.isdir(target) and \
|
|
259
|
+
self.command_design_ref.resolve_target_core(
|
|
260
|
+
target=target, no_recursion=False, caller_info=self.caller_info,
|
|
261
|
+
error_on_not_found=False
|
|
262
|
+
):
|
|
263
|
+
unparsed.remove(arg)
|
|
188
264
|
|
|
189
265
|
if unparsed:
|
|
190
266
|
# This is only a warning - because things like CommandFlist may not have every
|
opencos/deps_schema.py
CHANGED
|
@@ -85,6 +85,8 @@ my_target_name:
|
|
|
85
85
|
directory path substitution relative to target dir
|
|
86
86
|
(if substituted directory exists).
|
|
87
87
|
tee: <---- string, filename to write logs to
|
|
88
|
+
run-after-tool: <---- bool, default false. Set to true to run after any
|
|
89
|
+
EDA tools, or any command handlers have completed.
|
|
88
90
|
- work-dir-add-sources: <---- work-dir-add-sources, optional list (or string)
|
|
89
91
|
- some_file_gen_from_sh.sv <---- string filename that we created with sh command
|
|
90
92
|
- peakrdl: <---- string peakrdl command for CSR generation
|
|
@@ -171,6 +173,7 @@ DEPS_COMMANDS_LIST = [
|
|
|
171
173
|
Optional('var-subst-args'): bool,
|
|
172
174
|
Optional('var-subst-os-env'): bool,
|
|
173
175
|
Optional('run-from-work-dir'): bool,
|
|
176
|
+
Optional('run-after-tool'): bool,
|
|
174
177
|
Optional('filepath-subst-target-dir'): bool,
|
|
175
178
|
Optional('dirpath-subst-target-dir'): bool,
|
|
176
179
|
Optional('tee'): Or(str, type(None)),
|
opencos/eda.py
CHANGED
|
@@ -520,7 +520,8 @@ def main(*args):
|
|
|
520
520
|
|
|
521
521
|
original_args = args.copy() # save before any parsing.
|
|
522
522
|
|
|
523
|
-
# Set global --debug, --quiet, --color
|
|
523
|
+
# Set global --debug, --quiet, --color, -f/--iput-file, --envfile
|
|
524
|
+
# early before parsing other args:
|
|
524
525
|
util_parsed, unparsed = util.process_tokens(args)
|
|
525
526
|
|
|
526
527
|
util.debug(f"main: file: {os.path.realpath(__file__)}")
|
opencos/eda_base.py
CHANGED
|
@@ -230,6 +230,7 @@ class Command: # pylint: disable=too-many-public-methods
|
|
|
230
230
|
'enable-tags': [],
|
|
231
231
|
'disable-tags': [],
|
|
232
232
|
'test-mode': False,
|
|
233
|
+
'error-unknown-args': True,
|
|
233
234
|
})
|
|
234
235
|
self.args_help.update({
|
|
235
236
|
'stop-before-compile': ('stop this run before any compile (if possible for tool) and'
|
|
@@ -249,6 +250,7 @@ class Command: # pylint: disable=too-many-public-methods
|
|
|
249
250
|
' --disable-tags has higher precedence than --enable-tags.'),
|
|
250
251
|
'test-mode': ('command and tool dependent, usually stops the command early without'
|
|
251
252
|
' executing.'),
|
|
253
|
+
'error-unknown-args': 'Enable errors on unknown/unparsable args',
|
|
252
254
|
})
|
|
253
255
|
self.modified_args = {}
|
|
254
256
|
self.config = copy.deepcopy(config) # avoid external modifications.
|
|
@@ -495,9 +497,10 @@ class Command: # pylint: disable=too-many-public-methods
|
|
|
495
497
|
|
|
496
498
|
# Do some minimal type handling, preserving the type(self.args[key])
|
|
497
499
|
if key not in self.args:
|
|
498
|
-
self.
|
|
499
|
-
|
|
500
|
-
|
|
500
|
+
self.error_unknown_arg(
|
|
501
|
+
f'set_arg, {key=} not in self.args {value=}',
|
|
502
|
+
f'({self.command_name=}, {self.__class__.__name__=})'
|
|
503
|
+
)
|
|
501
504
|
|
|
502
505
|
cur_value = self.args[key]
|
|
503
506
|
|
|
@@ -506,12 +509,11 @@ class Command: # pylint: disable=too-many-public-methods
|
|
|
506
509
|
self.args[key].update(value)
|
|
507
510
|
|
|
508
511
|
elif isinstance(cur_value, list):
|
|
509
|
-
# if list, append (
|
|
512
|
+
# if list, append (allow duplicates)
|
|
510
513
|
if isinstance(value, list):
|
|
511
514
|
# new value also a list
|
|
512
515
|
for x in value:
|
|
513
|
-
|
|
514
|
-
self.args[key].append(x)
|
|
516
|
+
self.args[key].append(x)
|
|
515
517
|
elif value not in cur_value:
|
|
516
518
|
self.args[key].append(value)
|
|
517
519
|
|
|
@@ -693,9 +695,10 @@ class Command: # pylint: disable=too-many-public-methods
|
|
|
693
695
|
_, unparsed = self.run_argparser_on_list(tokens)
|
|
694
696
|
if process_all and unparsed:
|
|
695
697
|
self.warning_show_known_args()
|
|
696
|
-
self.
|
|
697
|
-
|
|
698
|
-
|
|
698
|
+
self.error_unknown_arg(
|
|
699
|
+
f"Didn't understand argument: '{unparsed=}' in",
|
|
700
|
+
f"{self.command_name=} context, {pwd=}"
|
|
701
|
+
)
|
|
699
702
|
|
|
700
703
|
return unparsed
|
|
701
704
|
|
|
@@ -911,6 +914,16 @@ class Command: # pylint: disable=too-many-public-methods
|
|
|
911
914
|
lines.append(self.pretty_str_known_args(command=commands[-1])) # use last command if > 1
|
|
912
915
|
util.warning("\n".join(lines))
|
|
913
916
|
|
|
917
|
+
def error_unknown_arg(self, *msg) -> None:
|
|
918
|
+
'''For errors involving an unknown --arg, they can be optionally disabled
|
|
919
|
+
|
|
920
|
+
using --no-error-unknown-args
|
|
921
|
+
'''
|
|
922
|
+
if self.args['error-unknown-args']:
|
|
923
|
+
self.error(*msg, error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
|
|
924
|
+
else:
|
|
925
|
+
util.warning(*msg)
|
|
926
|
+
|
|
914
927
|
|
|
915
928
|
class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
916
929
|
'''CommandDesign is the eda base class for command handlers that need to track files.
|
|
@@ -970,13 +983,16 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
|
970
983
|
self.targets_dict = {} # key = targets that we've already processed in DEPS files
|
|
971
984
|
self.last_added_source_file_inferred_top = ''
|
|
972
985
|
|
|
973
|
-
self.
|
|
986
|
+
self.has_pre_compile_dep_shell_commands = False
|
|
987
|
+
self.has_post_tool_dep_shell_commands = False
|
|
974
988
|
|
|
975
989
|
|
|
976
990
|
def run_dep_commands(self) -> None:
|
|
977
|
-
'''Run shell/peakrdl style commands from DEPS files
|
|
991
|
+
'''Run shell/peakrdl style commands from DEPS files, this is peformed before
|
|
978
992
|
|
|
979
|
-
These are deferred to maintain the deps ordering, and
|
|
993
|
+
any tool compile step. These are deferred to maintain the deps ordering, and
|
|
994
|
+
run in that order. Note this will NOT run any DEPS command marked with
|
|
995
|
+
run-after-tool=True.
|
|
980
996
|
'''
|
|
981
997
|
self.run_dep_shell_commands()
|
|
982
998
|
# Update any work_dir_add_srcs@ in our self.files, self.files_v, etc, b/c
|
|
@@ -986,17 +1002,41 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
|
986
1002
|
self.update_non_source_files_in_work_dir()
|
|
987
1003
|
|
|
988
1004
|
|
|
989
|
-
def
|
|
990
|
-
'''
|
|
1005
|
+
def run_post_tool_dep_commands(self) -> None:
|
|
1006
|
+
'''Run shell style commands from DEPS files that have been marked with
|
|
1007
|
+
|
|
1008
|
+
run-after-tool=True. Note these are skipped if any args like
|
|
1009
|
+
stop-before- or stop-after- are set.
|
|
1010
|
+
'''
|
|
1011
|
+
|
|
1012
|
+
self.run_dep_shell_commands(filter_run_after_tool=True)
|
|
1013
|
+
|
|
1014
|
+
|
|
1015
|
+
def run_dep_shell_commands( # pylint: disable=too-many-branches,too-many-locals
|
|
1016
|
+
self, filter_run_after_tool: bool = False
|
|
1017
|
+
) -> None:
|
|
1018
|
+
'''Runs collected shell command from DEPS files.
|
|
1019
|
+
|
|
1020
|
+
There are two flavors of shell commands: with or without 'run-after-tool'
|
|
1021
|
+
set. The default is to run shell command before the compile step of any tool,
|
|
1022
|
+
by calling this method with default pre_compile=True before any tool runs
|
|
1023
|
+
(for generating code, etc). However, it may be useful to run shell commands
|
|
1024
|
+
after a tool is complete (check timing, coverage, etc).
|
|
1025
|
+
'''
|
|
991
1026
|
|
|
992
1027
|
# Runs from self.args['work-dir']
|
|
993
1028
|
all_cmds_lists = []
|
|
994
1029
|
|
|
995
1030
|
log_fnames_count = {} # count per target_node.
|
|
996
1031
|
|
|
997
|
-
|
|
1032
|
+
filtered_dep_shell_commands = []
|
|
1033
|
+
for value in self.dep_shell_commands:
|
|
1034
|
+
if value['attributes']['run-after-tool'] == filter_run_after_tool:
|
|
1035
|
+
filtered_dep_shell_commands.append(value)
|
|
1036
|
+
|
|
1037
|
+
|
|
1038
|
+
for i, d in enumerate(filtered_dep_shell_commands):
|
|
998
1039
|
clist = util.ShellCommandList(d['exec_list'])
|
|
999
|
-
run_from_work_dir = d['run_from_work_dir'] # default True
|
|
1000
1040
|
log = clist.tee_fpath
|
|
1001
1041
|
target_node = d["target_node"]
|
|
1002
1042
|
if clist.tee_fpath is None:
|
|
@@ -1011,29 +1051,48 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
|
1011
1051
|
# (or tee name from DEPS.yml)
|
|
1012
1052
|
[f'# command {i}: target: {d["target_path"]} : {target_node} --> {log}'],
|
|
1013
1053
|
]
|
|
1014
|
-
if not
|
|
1054
|
+
if not d['attributes']['run-from-work-dir']:
|
|
1015
1055
|
all_cmds_lists.append([f'cd {d["target_path"]}'])
|
|
1016
1056
|
|
|
1017
1057
|
# actual command (list or util.ShellCommandList)
|
|
1018
1058
|
all_cmds_lists.append(clist)
|
|
1019
1059
|
|
|
1020
|
-
if not
|
|
1060
|
+
if not d['attributes']['run-from-work-dir']:
|
|
1021
1061
|
all_cmds_lists.append([f'cd {os.path.abspath(self.args["work-dir"])}'])
|
|
1022
1062
|
|
|
1023
1063
|
d['exec_list'] = clist # update to tee_fpath is set.
|
|
1024
1064
|
|
|
1025
1065
|
if all_cmds_lists:
|
|
1066
|
+
if filter_run_after_tool:
|
|
1067
|
+
filename='post_tool_dep_shell_commands.sh'
|
|
1068
|
+
self.has_post_tool_dep_shell_commands = True
|
|
1069
|
+
else:
|
|
1070
|
+
filename='pre_compile_dep_shell_commands.sh'
|
|
1071
|
+
self.has_pre_compile_dep_shell_commands = True
|
|
1072
|
+
|
|
1026
1073
|
util.write_shell_command_file(
|
|
1027
|
-
dirpath=self.args['work-dir'], filename=
|
|
1074
|
+
dirpath=self.args['work-dir'], filename=filename,
|
|
1028
1075
|
command_lists=all_cmds_lists
|
|
1029
1076
|
)
|
|
1030
|
-
self.has_dep_shell_commands = True
|
|
1031
1077
|
|
|
1032
|
-
|
|
1078
|
+
|
|
1079
|
+
if all_cmds_lists and filter_run_after_tool and \
|
|
1080
|
+
any(self.args.get(x, False) for x in (
|
|
1081
|
+
"stop-before-compile",
|
|
1082
|
+
"stop-after-compile",
|
|
1083
|
+
"stop-after-elaborate"
|
|
1084
|
+
)):
|
|
1085
|
+
args_set = [key for key,value in self.args.items() if \
|
|
1086
|
+
key.startswith('stop-') and value]
|
|
1087
|
+
util.info(f'Skipping DEPS run-after-tool commands due to args {args_set}')
|
|
1088
|
+
util.debug(f'Skipped commands: {filtered_dep_shell_commands=}')
|
|
1089
|
+
return
|
|
1090
|
+
|
|
1091
|
+
for i, d in enumerate(filtered_dep_shell_commands):
|
|
1033
1092
|
util.info(f'run_dep_shell_commands {i=}: {d=}')
|
|
1034
1093
|
clist = util.ShellCommandList(d['exec_list'])
|
|
1035
1094
|
tee_fpath=clist.tee_fpath
|
|
1036
|
-
if d['
|
|
1095
|
+
if d['attributes']['run-from-work-dir']:
|
|
1037
1096
|
run_from_dir = self.args['work-dir']
|
|
1038
1097
|
else:
|
|
1039
1098
|
# Run from the target's directory (not the `eda` caller $PWD)
|
|
@@ -1347,7 +1406,8 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
|
1347
1406
|
return self.resolve_target_core(target, no_recursion, caller_info)
|
|
1348
1407
|
|
|
1349
1408
|
def resolve_target_core( # pylint: disable=too-many-locals,too-many-branches
|
|
1350
|
-
self, target: str, no_recursion: bool, caller_info: str = ''
|
|
1409
|
+
self, target: str, no_recursion: bool, caller_info: str = '',
|
|
1410
|
+
error_on_not_found: bool = True
|
|
1351
1411
|
) -> bool:
|
|
1352
1412
|
'''Returns True if target is found. recursive point for resolving path or DEPS markup
|
|
1353
1413
|
target names.'''
|
|
@@ -1436,14 +1496,14 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
|
1436
1496
|
self.add_file(try_file, caller_info=f'n/a::{target}::n/a')
|
|
1437
1497
|
found_target = True
|
|
1438
1498
|
break # move on to the next target
|
|
1439
|
-
if not found_target: # if STILL not found_this_target...
|
|
1499
|
+
if not found_target and error_on_not_found: # if STILL not found_this_target...
|
|
1440
1500
|
self.error(f"Unable to resolve {target=}",
|
|
1441
1501
|
error_code=status_constants.EDA_DEPS_TARGET_NOT_FOUND)
|
|
1442
1502
|
|
|
1443
1503
|
# if we've found any target since being called, it means we found the one we were called for
|
|
1444
1504
|
return found_target
|
|
1445
1505
|
|
|
1446
|
-
def add_file(
|
|
1506
|
+
def add_file( # pylint: disable=too-many-locals,too-many-branches
|
|
1447
1507
|
self, filename: str, use_abspath: bool = True, add_to_non_sources: bool = False,
|
|
1448
1508
|
caller_info: str = '', forced_extension: str = ''
|
|
1449
1509
|
) -> str:
|
|
@@ -1467,6 +1527,7 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
|
1467
1527
|
vhdl_file_ext_list = known_file_ext_dict.get('vhdl', [])
|
|
1468
1528
|
cpp_file_ext_list = known_file_ext_dict.get('cpp', [])
|
|
1469
1529
|
sdc_file_ext_list = known_file_ext_dict.get('synth_constraints', [])
|
|
1530
|
+
dotf_file_ext_list = known_file_ext_dict.get('dotf', [])
|
|
1470
1531
|
|
|
1471
1532
|
if forced_extension:
|
|
1472
1533
|
# If forced_extension='systemverilog', then use the first known extension for
|
|
@@ -1499,6 +1560,13 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
|
1499
1560
|
elif file_ext in sdc_file_ext_list:
|
|
1500
1561
|
self.files_sdc.append(file_abspath)
|
|
1501
1562
|
util.debug(f"Added SDC file {filename} as {file_abspath}")
|
|
1563
|
+
elif file_ext in dotf_file_ext_list:
|
|
1564
|
+
# a stray .f file as a source file, sure why not support it:
|
|
1565
|
+
dp = DepsProcessor(command_design_ref=self, deps_entry={}, target='',
|
|
1566
|
+
target_path='', target_node='', deps_file='',
|
|
1567
|
+
caller_info=caller_info)
|
|
1568
|
+
dp.apply_args(args_list=[f'-f={file_abspath}'])
|
|
1569
|
+
del dp
|
|
1502
1570
|
else:
|
|
1503
1571
|
# unknown file extension. In these cases we link the file to the working directory
|
|
1504
1572
|
# so it is available (for example, a .mem file that is expected to exist with relative
|
|
@@ -1592,9 +1660,10 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
|
1592
1660
|
if process_all and possible_unparsed_args:
|
|
1593
1661
|
_tool = self.safe_which_tool()
|
|
1594
1662
|
self.warning_show_known_args()
|
|
1595
|
-
self.
|
|
1596
|
-
|
|
1597
|
-
|
|
1663
|
+
self.error_unknown_arg(
|
|
1664
|
+
f"Didn't understand unparsed args: {possible_unparsed_args}, for command",
|
|
1665
|
+
f"'{self.command_name}', tool '{_tool}'"
|
|
1666
|
+
)
|
|
1598
1667
|
|
|
1599
1668
|
remove_list = []
|
|
1600
1669
|
last_potential_top_file = ('', '') # (top, fpath)
|
|
@@ -1652,9 +1721,10 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
|
1652
1721
|
# we were unable to figure out what this command line token is for...
|
|
1653
1722
|
if process_all and unparsed:
|
|
1654
1723
|
self.warning_show_known_args()
|
|
1655
|
-
self.
|
|
1656
|
-
|
|
1657
|
-
|
|
1724
|
+
self.error_unknown_arg(
|
|
1725
|
+
f"Didn't understand remaining args or targets {unparsed=} for command",
|
|
1726
|
+
f"'{self.command_name}'"
|
|
1727
|
+
)
|
|
1658
1728
|
|
|
1659
1729
|
# handle a missing self.args['top'] with last filepath or last target:
|
|
1660
1730
|
if not self.args.get('top', ''):
|
|
@@ -2199,9 +2269,10 @@ class CommandParallel(Command):
|
|
|
2199
2269
|
bad_remaining_args = [x for x in single_cmd_unparsed if x.startswith('-')]
|
|
2200
2270
|
if bad_remaining_args:
|
|
2201
2271
|
self.warning_show_known_args(command=f'{self.command_name} {command}')
|
|
2202
|
-
self.
|
|
2203
|
-
|
|
2204
|
-
|
|
2272
|
+
self.error_unknown_arg(
|
|
2273
|
+
f'for {self.command_name} {command=} the following args are unknown',
|
|
2274
|
+
f'{bad_remaining_args}'
|
|
2275
|
+
)
|
|
2205
2276
|
|
|
2206
2277
|
# Remove unparsed args starting with '+', since those are commonly sent downstream to
|
|
2207
2278
|
# single job (example, CommandSim plusargs).
|
opencos/eda_config_defaults.yml
CHANGED
|
@@ -91,6 +91,9 @@ file_extensions:
|
|
|
91
91
|
synth_constraints:
|
|
92
92
|
- .sdc
|
|
93
93
|
- .xdc
|
|
94
|
+
dotf:
|
|
95
|
+
- .f
|
|
96
|
+
- .vc
|
|
94
97
|
|
|
95
98
|
inferred_top:
|
|
96
99
|
# file extensions that we can infer "top" module from, if --top omitted.
|
|
@@ -229,6 +232,7 @@ tools:
|
|
|
229
232
|
riviera:
|
|
230
233
|
defines:
|
|
231
234
|
OC_TOOL_RIVIERA: 1
|
|
235
|
+
RIVIERA: 1
|
|
232
236
|
log-bad-strings:
|
|
233
237
|
- "Error:"
|
|
234
238
|
log-must-strings:
|
|
@@ -292,7 +296,7 @@ tools:
|
|
|
292
296
|
- 3009 # 3009: [TSCALE] - Module 'myname' does not have a timeunit/timeprecision
|
|
293
297
|
# specification in effect, but other modules do.
|
|
294
298
|
simulate-waves-args: |
|
|
295
|
-
-voptargs=+acc=
|
|
299
|
+
-voptargs=+acc=bcnprst
|
|
296
300
|
|
|
297
301
|
|
|
298
302
|
iverilog:
|
opencos/files.py
CHANGED
|
@@ -18,3 +18,14 @@ target_echo_hi_bye:
|
|
|
18
18
|
target_test:
|
|
19
19
|
deps: target_echo_hi_bye
|
|
20
20
|
top: foo
|
|
21
|
+
|
|
22
|
+
target_test_with_post_tool_commands:
|
|
23
|
+
deps:
|
|
24
|
+
# In this test, we want to put a new command in the front of the ordered "deps" list,
|
|
25
|
+
# but with run-after-tool=true, so it should run after any of the normal pre-compile
|
|
26
|
+
# shell commands.
|
|
27
|
+
- commands:
|
|
28
|
+
- shell: echo "final goodbye"
|
|
29
|
+
run-after-tool: true
|
|
30
|
+
- target_echo_hi_bye
|
|
31
|
+
top: foo
|
opencos/tests/helpers.py
CHANGED
|
@@ -13,6 +13,7 @@ from opencos import eda
|
|
|
13
13
|
from opencos import deps_schema
|
|
14
14
|
from opencos.utils.markup_helpers import yaml_safe_load
|
|
15
15
|
from opencos.utils import status_constants
|
|
16
|
+
from opencos.utils.subprocess_helpers import subprocess_run_background
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
def eda_wrap_is_sim_fail(rc: int, quiet: bool = False) -> bool:
|
|
@@ -157,10 +158,20 @@ class Helpers:
|
|
|
157
158
|
DEFAULT_DIR = ''
|
|
158
159
|
DEFAULT_LOG_DIR = os.getcwd()
|
|
159
160
|
DEFAULT_LOG = os.path.join(DEFAULT_LOG_DIR, '.pytest.eda.log')
|
|
161
|
+
|
|
162
|
+
# How should the job run? subprocess? eda_wrap? eda.main?
|
|
163
|
+
# Note - if using eda.main, args like --debug will persist in opencos.util.args,
|
|
164
|
+
# so if you need those to be re-loaded, set RUN_IN_SUBPROCESS=True.
|
|
165
|
+
# Note - if you mess with os.enviorn, it may persist through subprocess.
|
|
166
|
+
RUN_IN_SUBPROCESS = True
|
|
167
|
+
USE_EDA_WRAP = True
|
|
168
|
+
PRESERVE_ENV = False
|
|
169
|
+
|
|
160
170
|
def chdir(self):
|
|
161
171
|
'''Changes directory to self.DEFAULT_DIR and removes eda.work, eda.export paths'''
|
|
162
172
|
chdir_remove_work_dir('', self.DEFAULT_DIR)
|
|
163
173
|
|
|
174
|
+
|
|
164
175
|
def _resolve_logfile(self, logfile=None) -> str:
|
|
165
176
|
'''Returns the logfile's filepath'''
|
|
166
177
|
ret = logfile
|
|
@@ -173,7 +184,11 @@ class Helpers:
|
|
|
173
184
|
ret = os.path.join(self.DEFAULT_LOG_DIR, right)
|
|
174
185
|
return ret
|
|
175
186
|
|
|
176
|
-
def log_it(
|
|
187
|
+
def log_it(
|
|
188
|
+
self, command_str: str, logfile=None, use_eda_wrap: bool = True,
|
|
189
|
+
run_in_subprocess: bool = False,
|
|
190
|
+
preserve_env: bool = False
|
|
191
|
+
) -> int:
|
|
177
192
|
'''Replacement for calling eda.main or eda_wrap, when you want an internal logfile
|
|
178
193
|
|
|
179
194
|
Usage:
|
|
@@ -183,6 +198,10 @@ class Helpers:
|
|
|
183
198
|
Note this will run with --no-default-log to avoid a Windows problem with stomping
|
|
184
199
|
on a log file.
|
|
185
200
|
'''
|
|
201
|
+
|
|
202
|
+
if self.PRESERVE_ENV or preserve_env:
|
|
203
|
+
saved_env = os.environ.copy()
|
|
204
|
+
|
|
186
205
|
logfile = self._resolve_logfile(logfile)
|
|
187
206
|
rc = 50
|
|
188
207
|
|
|
@@ -191,17 +210,30 @@ class Helpers:
|
|
|
191
210
|
# look at eda.work/{target}.sim/sim.log or xsim.log.
|
|
192
211
|
print(f'{os.getcwd()=}')
|
|
193
212
|
print(f'{command_str=}')
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
213
|
+
if run_in_subprocess or self.RUN_IN_SUBPROCESS:
|
|
214
|
+
command_list = ['eda', '--no-default-log'] + command_str.split()
|
|
215
|
+
_, _, rc = subprocess_run_background(
|
|
216
|
+
work_dir=self.DEFAULT_DIR,
|
|
217
|
+
command_list=command_list,
|
|
218
|
+
background=True,
|
|
219
|
+
tee_fpath=logfile
|
|
220
|
+
)
|
|
221
|
+
else:
|
|
222
|
+
with open(logfile, 'w', encoding='utf-8') as f:
|
|
223
|
+
with redirect_stdout(f), redirect_stderr(f):
|
|
224
|
+
if use_eda_wrap or self.USE_EDA_WRAP:
|
|
225
|
+
rc = eda_wrap('--no-default-log', *(command_str.split()))
|
|
226
|
+
else:
|
|
227
|
+
rc = eda.main('--no-default-log', *(command_str.split()))
|
|
228
|
+
print(f'Wrote: {os.path.abspath(logfile)=}')
|
|
229
|
+
|
|
230
|
+
if self.PRESERVE_ENV or preserve_env:
|
|
231
|
+
os.environ = saved_env
|
|
232
|
+
|
|
201
233
|
return rc
|
|
202
234
|
|
|
203
235
|
def is_in_log(self, *want_str, logfile=None, windows_path_support=False):
|
|
204
|
-
'''Check if
|
|
236
|
+
'''Check if want_str (joined) is in the logfile, or self.DEFAULT_LOG'''
|
|
205
237
|
logfile = self._resolve_logfile(logfile)
|
|
206
238
|
want_str0 = ' '.join(list(want_str))
|
|
207
239
|
want_str1 = want_str0.replace('/', '\\')
|
|
@@ -216,33 +248,31 @@ class Helpers:
|
|
|
216
248
|
'''gets all log lines with any of want_str args are in the logfile, or self.DEFAULT_LOG'''
|
|
217
249
|
logfile = self._resolve_logfile(logfile)
|
|
218
250
|
ret_list = []
|
|
219
|
-
want_str0 = ' '.join(list(want_str))
|
|
220
|
-
want_str1 = want_str0.replace('/', '\\')
|
|
221
251
|
with open(logfile, encoding='utf-8') as f:
|
|
222
252
|
for line in f.readlines():
|
|
223
|
-
if
|
|
253
|
+
if any(x in line for x in list(want_str)):
|
|
224
254
|
ret_list.append(line)
|
|
225
|
-
elif windows_path_support and
|
|
255
|
+
elif windows_path_support and \
|
|
256
|
+
any(x.replace('/', '\\') in line for x in list(want_str)):
|
|
226
257
|
ret_list.append(line)
|
|
227
258
|
return ret_list
|
|
228
259
|
|
|
229
260
|
def get_log_words_with(self, *want_str, logfile=None, windows_path_support=False):
|
|
230
|
-
'''gets all log
|
|
261
|
+
'''gets all log words with any of *want_str within a single word
|
|
231
262
|
in the logfile or self.DEFAULT_LOG
|
|
232
263
|
'''
|
|
233
264
|
logfile = self._resolve_logfile(logfile)
|
|
234
265
|
ret_list = []
|
|
235
|
-
want_str0 = ' '.join(list(want_str))
|
|
236
|
-
want_str1 = want_str0.replace('/', '\\')
|
|
237
266
|
with open(logfile, encoding='utf-8') as f:
|
|
238
267
|
for line in f.readlines():
|
|
239
|
-
if
|
|
268
|
+
if any(x in line for x in list(want_str)):
|
|
240
269
|
for word in line.split():
|
|
241
|
-
if
|
|
270
|
+
if any(x in word for x in list(want_str)):
|
|
242
271
|
ret_list.append(word)
|
|
243
|
-
elif windows_path_support and
|
|
272
|
+
elif windows_path_support and \
|
|
273
|
+
any(x.replace('/', '\\') in line for x in list(want_str)):
|
|
244
274
|
for word in line.split():
|
|
245
|
-
if
|
|
275
|
+
if any(x.replace('/', '\\') in word for x in list(want_str)):
|
|
246
276
|
ret_list.append(word)
|
|
247
277
|
|
|
248
278
|
return ret_list
|