opencos-eda 0.3.3__py3-none-any.whl → 0.3.5__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/deps/defaults.py +1 -0
- opencos/deps/deps_processor.py +47 -22
- opencos/deps_schema.py +17 -0
- opencos/eda.py +7 -14
- opencos/eda_base.py +1 -0
- opencos/eda_config.py +84 -40
- opencos/eda_config_defaults.yml +3 -0
- opencos/eda_extract_targets.py +0 -0
- opencos/eda_tool_helper.py +19 -0
- opencos/hw/oc_cli.py +0 -0
- opencos/tests/helpers.py +37 -0
- opencos/tests/test_eda.py +11 -1
- opencos/tools/iverilog.py +4 -0
- opencos/tools/riviera.py +23 -12
- opencos/tools/verilator.py +86 -0
- opencos/tools/vivado.py +1 -0
- opencos/util.py +14 -8
- opencos/utils/str_helpers.py +6 -1
- opencos/utils/subprocess_helpers.py +66 -3
- {opencos_eda-0.3.3.dist-info → opencos_eda-0.3.5.dist-info}/METADATA +10 -2
- {opencos_eda-0.3.3.dist-info → opencos_eda-0.3.5.dist-info}/RECORD +24 -24
- {opencos_eda-0.3.3.dist-info → opencos_eda-0.3.5.dist-info}/WHEEL +0 -0
- {opencos_eda-0.3.3.dist-info → opencos_eda-0.3.5.dist-info}/entry_points.txt +0 -0
- {opencos_eda-0.3.3.dist-info → opencos_eda-0.3.5.dist-info}/licenses/LICENSE +0 -0
- {opencos_eda-0.3.3.dist-info → opencos_eda-0.3.5.dist-info}/licenses/LICENSE.spdx +0 -0
- {opencos_eda-0.3.3.dist-info → opencos_eda-0.3.5.dist-info}/top_level.txt +0 -0
opencos/deps/defaults.py
CHANGED
opencos/deps/deps_processor.py
CHANGED
|
@@ -77,7 +77,7 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
|
|
|
77
77
|
).get('command_handler', {}).keys()
|
|
78
78
|
|
|
79
79
|
|
|
80
|
-
def apply_defines(self, defines_dict: dict):
|
|
80
|
+
def apply_defines(self, defines_dict: dict) -> None:
|
|
81
81
|
'''Given defines_dict, applies them to our self.command_design_ref obj'''
|
|
82
82
|
if not isinstance(defines_dict, dict):
|
|
83
83
|
self.error(f"{defines_dict=} is not type dict, can't apply defines,",
|
|
@@ -95,7 +95,19 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
|
|
|
95
95
|
self.command_design_ref.process_plusarg(f'+define+{k}={v}')
|
|
96
96
|
|
|
97
97
|
|
|
98
|
-
def
|
|
98
|
+
def apply_plusargs(self, plusargs_dict: dict) -> None:
|
|
99
|
+
'''Given plusarsg_dict, applies them to our self.command_design_ref obj'''
|
|
100
|
+
if not isinstance(plusargs_dict, dict):
|
|
101
|
+
self.error(f"{plusargs_dict=} is not type dict, can't apply plusargs,",
|
|
102
|
+
f"in {self.caller_info}")
|
|
103
|
+
for k,v in plusargs_dict.items():
|
|
104
|
+
if v is None or v == '':
|
|
105
|
+
self.command_design_ref.process_plusarg(f'+{k}')
|
|
106
|
+
else:
|
|
107
|
+
self.command_design_ref.process_plusarg(f'+{k}={v}')
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def apply_parameters(self, parameters_dict: dict) -> None:
|
|
99
111
|
'''Given parameters_dict, applies them to our self.command_design_ref obj'''
|
|
100
112
|
if not isinstance(parameters_dict, dict):
|
|
101
113
|
self.error(f"{parameters_dict=} is not type dict, can't apply defines,",
|
|
@@ -110,7 +122,7 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
|
|
|
110
122
|
)
|
|
111
123
|
|
|
112
124
|
|
|
113
|
-
def apply_incdirs(self, incdirs_list:list):
|
|
125
|
+
def apply_incdirs(self, incdirs_list:list) -> None:
|
|
114
126
|
'''Given incdirs_list, applies them to our self.command_design_ref obj'''
|
|
115
127
|
if not isinstance(incdirs_list, (str, list)):
|
|
116
128
|
self.error(f"{incdirs_list=} is not type str/list, can't apply incdirs",
|
|
@@ -290,7 +302,9 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
|
|
|
290
302
|
caller_info=self.caller_info
|
|
291
303
|
)
|
|
292
304
|
|
|
293
|
-
def process_deps_entry(
|
|
305
|
+
def process_deps_entry( # pylint: disable=too-many-branches
|
|
306
|
+
self
|
|
307
|
+
) -> list:
|
|
294
308
|
'''Main entry point (after creating DepsProcessor obj) to resolve a deps target
|
|
295
309
|
|
|
296
310
|
Example usage:
|
|
@@ -303,22 +317,6 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
|
|
|
303
317
|
This method will apply all target features to the CommandDesign ref object as
|
|
304
318
|
we traverse.
|
|
305
319
|
|
|
306
|
-
Supported target keys:
|
|
307
|
-
-- tags (or equivalent, to support multiple define/incdir/deps for a target)
|
|
308
|
-
-- supports tag-name, with-tools, with-args, args, defines, incdirs, deps
|
|
309
|
-
** to be applied if a tool matches.
|
|
310
|
-
-- TODO(drew): other features in docs/DEPS.md not yet implemented.
|
|
311
|
-
-- multi: ignore-this-target: - commands (handled in eda.py CommandMulti.resolve_target)
|
|
312
|
-
-- Named eda commands
|
|
313
|
-
-- (partially done) sim or other eda commands (eda.py command specific things)
|
|
314
|
-
basically, check the command, and apply/merge values to 'entry'?
|
|
315
|
-
-- args
|
|
316
|
-
-- defines
|
|
317
|
-
-- incdirs
|
|
318
|
-
-- top.
|
|
319
|
-
-- commands (not in deps)
|
|
320
|
-
-- deps
|
|
321
|
-
|
|
322
320
|
TODO(drew): This does not yet support conditional inclusions based on defines,
|
|
323
321
|
like the old DEPS files did with pattern:
|
|
324
322
|
SOME_DEFINE ? dep_if_define_present : dep_if_define_not_present
|
|
@@ -344,6 +342,8 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
|
|
|
344
342
|
remaining_deps_list += self.process_tags()
|
|
345
343
|
elif key == 'defines':
|
|
346
344
|
self.process_defines()
|
|
345
|
+
elif key == 'plusargs':
|
|
346
|
+
self.process_plusargs()
|
|
347
347
|
elif key == 'parameters':
|
|
348
348
|
self.process_parameters()
|
|
349
349
|
elif key == 'incdirs':
|
|
@@ -520,6 +520,10 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
|
|
|
520
520
|
# apply defines:
|
|
521
521
|
self.apply_defines(value.get('defines', {}))
|
|
522
522
|
|
|
523
|
+
if key == 'plusargs':
|
|
524
|
+
# apply plusargs:
|
|
525
|
+
self.apply_plusargs(value.get('plusargs', {}))
|
|
526
|
+
|
|
523
527
|
elif key == 'parameters':
|
|
524
528
|
self.apply_parameters(value.get('parameters', {}))
|
|
525
529
|
|
|
@@ -598,7 +602,7 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
|
|
|
598
602
|
return ret_deps_added_from_tags
|
|
599
603
|
|
|
600
604
|
|
|
601
|
-
def process_defines(self):
|
|
605
|
+
def process_defines(self) -> None:
|
|
602
606
|
'''Returns None, applies defines (dict, if any) from self.deps_entry to
|
|
603
607
|
self.command_design_ref.'''
|
|
604
608
|
|
|
@@ -614,7 +618,28 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
|
|
|
614
618
|
|
|
615
619
|
self.apply_defines(entry_defines)
|
|
616
620
|
|
|
617
|
-
|
|
621
|
+
|
|
622
|
+
def process_plusargs(self) -> None:
|
|
623
|
+
'''Returns None, applies plusargs (dict, if any) from self.deps_entry to
|
|
624
|
+
self.command_design_ref.
|
|
625
|
+
|
|
626
|
+
These work w/ the same rules as defines (no value, or value int/str)
|
|
627
|
+
'''
|
|
628
|
+
|
|
629
|
+
# Plusargs:
|
|
630
|
+
# apply command specific plusargs, with higher priority than the a
|
|
631
|
+
# deps_entry['sim']['plusargs'] entry,
|
|
632
|
+
# do this with dict1.update(dict2):
|
|
633
|
+
entry_plusargs = {}
|
|
634
|
+
entry_plusargs.update(self.deps_entry.get('plusargs', {}))
|
|
635
|
+
entry_plusargs.update(self.entry_eda_command.get('plusargs', {}))
|
|
636
|
+
assert isinstance(entry_plusargs, dict), \
|
|
637
|
+
f'{entry_plusargs=} for in {self.caller_info} must be a dict'
|
|
638
|
+
|
|
639
|
+
self.apply_plusargs(entry_plusargs)
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
def process_parameters(self) -> None:
|
|
618
643
|
'''Returns None, applies parameters (dict, if any) from self.deps_entry to
|
|
619
644
|
self.command_design_ref.'''
|
|
620
645
|
|
opencos/deps_schema.py
CHANGED
|
@@ -17,6 +17,9 @@ my_target_name:
|
|
|
17
17
|
incdirs: <---- incdirs, optional array (or string)
|
|
18
18
|
- ./
|
|
19
19
|
|
|
20
|
+
plusargs: <---- plusargs, optional table
|
|
21
|
+
some_plusarg: 32
|
|
22
|
+
|
|
20
23
|
top: tb <---- top, optional string
|
|
21
24
|
|
|
22
25
|
deps:
|
|
@@ -63,6 +66,7 @@ my_target_name:
|
|
|
63
66
|
defines: <---- defines, optional table
|
|
64
67
|
parameters: <---- parameters, optional table
|
|
65
68
|
incdirs: <---- incdirs, optional array (or string)
|
|
69
|
+
plusargs: <---- plusargs, optional table
|
|
66
70
|
top: tb <---- top, optional string
|
|
67
71
|
deps: <---- TARGET_DEPS_CONTENTS schema
|
|
68
72
|
- some_file.sv <---- string file name
|
|
@@ -216,6 +220,9 @@ TARGET_EDA_COMMAND_ENTRY_TABLE = {
|
|
|
216
220
|
Optional('defines'): {
|
|
217
221
|
Optional(str): Or(str, int, type(None)),
|
|
218
222
|
},
|
|
223
|
+
Optional('plusargs'): {
|
|
224
|
+
Optional(str): Or(str, int, type(None)),
|
|
225
|
+
},
|
|
219
226
|
Optional('parameters'): {
|
|
220
227
|
Optional(str): Or(str, int),
|
|
221
228
|
},
|
|
@@ -241,6 +248,9 @@ TARGET_TAGS_TABLE = {
|
|
|
241
248
|
Optional('defines'): {
|
|
242
249
|
Optional(str): Or(str, int, type(None)),
|
|
243
250
|
},
|
|
251
|
+
Optional('plusargs'): {
|
|
252
|
+
Optional(str): Or(str, int, type(None)),
|
|
253
|
+
},
|
|
244
254
|
Optional('parameters'): {
|
|
245
255
|
Optional(str): Or(str, int),
|
|
246
256
|
},
|
|
@@ -262,6 +272,10 @@ TARGET_CONTENTS = Or(
|
|
|
262
272
|
Optional('defines'): {
|
|
263
273
|
Optional(str): Or(str, int, type(None)),
|
|
264
274
|
},
|
|
275
|
+
# plusargs: table of key-value; value null or string
|
|
276
|
+
Optional('plusargs'): {
|
|
277
|
+
Optional(str): Or(str, int, type(None)),
|
|
278
|
+
},
|
|
265
279
|
# parameters: table of key-value
|
|
266
280
|
Optional('parameters'): {
|
|
267
281
|
Optional(str): Or(str, int),
|
|
@@ -324,6 +338,9 @@ FILE_SIMPLIFIED = Schema(
|
|
|
324
338
|
Optional('defines'): {
|
|
325
339
|
Optional(str): Or(type(None), str),
|
|
326
340
|
},
|
|
341
|
+
Optional('plusargs'): {
|
|
342
|
+
Optional(str): Or(type(None), str),
|
|
343
|
+
},
|
|
327
344
|
Optional('parameters'): {
|
|
328
345
|
Optional(str): str,
|
|
329
346
|
},
|
opencos/eda.py
CHANGED
|
@@ -23,7 +23,7 @@ from opencos import util, eda_config, eda_base
|
|
|
23
23
|
from opencos.eda_base import Tool, which_tool, get_eda_exec
|
|
24
24
|
from opencos.utils import vsim_helper, vscode_helper
|
|
25
25
|
from opencos.utils.subprocess_helpers import subprocess_run_background
|
|
26
|
-
from opencos.utils import status_constants, str_helpers
|
|
26
|
+
from opencos.utils import status_constants, str_helpers, subprocess_helpers
|
|
27
27
|
|
|
28
28
|
# Configure util:
|
|
29
29
|
util.progname = "EDA"
|
|
@@ -50,7 +50,7 @@ def init_config(
|
|
|
50
50
|
# For key DEFAULT_HANDLERS, we'll update config['command_handler'] with
|
|
51
51
|
# the actual class using importlib (via opencos.util)
|
|
52
52
|
|
|
53
|
-
eda_config.
|
|
53
|
+
eda_config.update_config_auto_tool_order_for_tool(tool=tool, config=config)
|
|
54
54
|
|
|
55
55
|
config['command_handler'] = {}
|
|
56
56
|
for _cmd, str_class in config['DEFAULT_HANDLERS'].items():
|
|
@@ -163,10 +163,7 @@ def auto_tool_setup( # pylint: disable=too-many-locals,too-many-branches,too-man
|
|
|
163
163
|
If so, updates config['auto_tools_order'][tool]['exe']
|
|
164
164
|
'''
|
|
165
165
|
|
|
166
|
-
|
|
167
|
-
tool = eda_config.update_config_auto_tool_order_for_tool(
|
|
168
|
-
tool=tool, config=config
|
|
169
|
-
)
|
|
166
|
+
tool = eda_config.tool_arg_remove_path_information(tool)
|
|
170
167
|
|
|
171
168
|
assert 'auto_tools_order' in config
|
|
172
169
|
assert isinstance(config['auto_tools_order'], list)
|
|
@@ -275,9 +272,7 @@ def tool_setup(tool: str, config: dict, quiet: bool = False, auto_setup: bool =
|
|
|
275
272
|
|
|
276
273
|
'''
|
|
277
274
|
|
|
278
|
-
tool = eda_config.
|
|
279
|
-
tool=tool, config=config
|
|
280
|
-
)
|
|
275
|
+
tool = eda_config.tool_arg_remove_path_information(tool)
|
|
281
276
|
|
|
282
277
|
if not quiet and not auto_setup:
|
|
283
278
|
util.info(f"Setup for tool: '{tool}'")
|
|
@@ -389,6 +384,8 @@ def process_tokens( # pylint: disable=too-many-branches,too-many-statements,too-
|
|
|
389
384
|
if not is_interactive:
|
|
390
385
|
# Run init_config() now, we deferred it in main(), but only run it
|
|
391
386
|
# for this tool (or tool=None to figure it out)
|
|
387
|
+
# This will handle any --tool=<name>=/path/to/bin also, so don't have to
|
|
388
|
+
# run tool_setup(..) on its own.
|
|
392
389
|
config = init_config(
|
|
393
390
|
config, tool=parsed.tool,
|
|
394
391
|
run_auto_tool_setup=run_auto_tool_setup
|
|
@@ -406,13 +403,8 @@ def process_tokens( # pylint: disable=too-many-branches,too-many-statements,too-
|
|
|
406
403
|
for arg in unparsed:
|
|
407
404
|
if not arg.startswith('-'):
|
|
408
405
|
command = arg
|
|
409
|
-
if parsed.tool:
|
|
410
|
-
tool_setup(parsed.tool, config=config)
|
|
411
406
|
return usage(tokens=unparsed, config=config, command=command, tool=parsed.tool)
|
|
412
407
|
|
|
413
|
-
if parsed.tool:
|
|
414
|
-
tool_setup(parsed.tool, config=config)
|
|
415
|
-
|
|
416
408
|
deferred_tokens = unparsed
|
|
417
409
|
if not command:
|
|
418
410
|
util.error("Didn't get a command!")
|
|
@@ -504,6 +496,7 @@ def signal_handler(sig, frame) -> None: # pylint: disable=unused-argument
|
|
|
504
496
|
'''Handles Ctrl-C, called by main_cli() if running from command line'''
|
|
505
497
|
util.fancy_stop()
|
|
506
498
|
util.info('Received Ctrl+C...', start='\nINFO: [EDA] ')
|
|
499
|
+
subprocess_helpers.cleanup_all()
|
|
507
500
|
util.exit(-1)
|
|
508
501
|
|
|
509
502
|
# **************************************************************
|
opencos/eda_base.py
CHANGED
|
@@ -594,6 +594,7 @@ class Command: # pylint: disable=too-many-public-methods
|
|
|
594
594
|
help_kwargs = {'help': f'default={value}'}
|
|
595
595
|
else:
|
|
596
596
|
help_kwargs = {'help': f'{type(value).__name__} default={value}'}
|
|
597
|
+
help_kwargs['help'] = help_kwargs['help'].replace('%', '%%')
|
|
597
598
|
|
|
598
599
|
|
|
599
600
|
# It's important to set the default=None on these, except for list types where default
|
opencos/eda_config.py
CHANGED
|
@@ -18,7 +18,10 @@ from opencos import util
|
|
|
18
18
|
from opencos.utils.markup_helpers import yaml_safe_load, yaml_safe_writer
|
|
19
19
|
|
|
20
20
|
class Defaults:
|
|
21
|
-
'''Defaults is a global
|
|
21
|
+
'''Defaults is a global namespace for constants and supported features.
|
|
22
|
+
|
|
23
|
+
Defaults.config_yml is set depending on search order for default eda_config[_defaults].yml
|
|
24
|
+
'''
|
|
22
25
|
|
|
23
26
|
environ_override_config_yml = os.environ.get('EDA_CONFIG_YML', '')
|
|
24
27
|
home_override_config_yml = os.path.join(
|
|
@@ -63,14 +66,15 @@ class Defaults:
|
|
|
63
66
|
'simulate-args',
|
|
64
67
|
'simulate-waves-args',
|
|
65
68
|
'simulate-waivers',
|
|
69
|
+
'simulate-coverage-tcl',
|
|
66
70
|
'coverage-args',
|
|
67
71
|
])
|
|
68
72
|
|
|
69
73
|
EDA_OUTPUT_CONFIG_FNAME = 'eda_output_config.yml'
|
|
70
74
|
|
|
71
|
-
if os.path.
|
|
75
|
+
if os.path.isfile(Defaults.environ_override_config_yml):
|
|
72
76
|
Defaults.config_yml = Defaults.environ_override_config_yml
|
|
73
|
-
elif os.path.
|
|
77
|
+
elif os.path.isfile(Defaults.home_override_config_yml):
|
|
74
78
|
Defaults.config_yml = Defaults.home_override_config_yml
|
|
75
79
|
else:
|
|
76
80
|
Defaults.config_yml = Defaults.opencos_config_yml
|
|
@@ -149,29 +153,9 @@ def update_config_auto_tool_order_for_tool(tool: str, config: dict) -> str:
|
|
|
149
153
|
Input arg tool can be in the form (for example):
|
|
150
154
|
tool='verlator', tool='verilator=/path/to/verilator.exe'
|
|
151
155
|
|
|
152
|
-
Performs no update if tool has no = in it. Returns tool (str) w/out = in it
|
|
156
|
+
Performs no update if tool has no = or : in it. Returns tool (str) w/out = in it
|
|
153
157
|
'''
|
|
154
|
-
|
|
155
|
-
return tool
|
|
156
|
-
|
|
157
|
-
tool, user_exe = tool.split('=')[0:2]
|
|
158
|
-
|
|
159
|
-
user_exe = shutil.which(user_exe)
|
|
160
|
-
|
|
161
|
-
# try adding to $PATH if in form --tool=/path/to/exe
|
|
162
|
-
tool_try_add_to_path(tool)
|
|
163
|
-
|
|
164
|
-
if tool not in config['auto_tools_order'][0]:
|
|
165
|
-
return tool
|
|
166
|
-
if not user_exe:
|
|
167
|
-
return tool
|
|
168
|
-
|
|
169
|
-
old_exe = config['auto_tools_order'][0][tool].get('exe', str())
|
|
170
|
-
if isinstance(old_exe, list):
|
|
171
|
-
config['auto_tools_order'][0][tool]['exe'][0] = user_exe
|
|
172
|
-
else:
|
|
173
|
-
config['auto_tools_order'][0][tool]['exe'] = user_exe
|
|
174
|
-
return tool
|
|
158
|
+
return tool_try_add_to_path(tool=tool, config=config, update_config=True)
|
|
175
159
|
|
|
176
160
|
|
|
177
161
|
def update_config_auto_tool_order_for_tools(tools: list, config: dict) -> list:
|
|
@@ -327,32 +311,92 @@ def write_eda_config_and_args(
|
|
|
327
311
|
yaml_safe_writer(data=data, filepath=fullpath)
|
|
328
312
|
|
|
329
313
|
|
|
330
|
-
def
|
|
331
|
-
'''
|
|
314
|
+
def tool_arg_get_parts(tool: str) -> list:
|
|
315
|
+
'''Given a tool (str or None) that may be in form <name>=/path/to/something
|
|
316
|
+
|
|
317
|
+
Return the parts [<name>, <path>, ..]
|
|
318
|
+
'''
|
|
319
|
+
if not tool or ('=' not in tool and ':' not in tool):
|
|
320
|
+
return [tool]
|
|
321
|
+
|
|
322
|
+
if '=' in tool:
|
|
323
|
+
parts = tool.split('=')
|
|
324
|
+
else:
|
|
325
|
+
parts = tool.split(':')
|
|
326
|
+
|
|
327
|
+
return parts
|
|
328
|
+
|
|
329
|
+
def tool_arg_remove_path_information(tool: str) -> str:
|
|
330
|
+
'''Given a tool (str or None) that may be in form <name>=/path/to/something
|
|
331
|
+
|
|
332
|
+
Return the <name> only
|
|
333
|
+
'''
|
|
334
|
+
if not tool:
|
|
335
|
+
return tool
|
|
336
|
+
return tool_arg_get_parts(tool)[0]
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def tool_try_add_to_path( # pylint: disable=too-many-branches
|
|
340
|
+
tool: str, config: dict, update_config: bool
|
|
341
|
+
) -> str:
|
|
342
|
+
'''Since we support --tool=<name>=/path/to/bin[/exe], attempt to prepend $PATH
|
|
343
|
+
|
|
344
|
+
(also works for --tool=<name>:/path/to/bin[/exe] )
|
|
332
345
|
|
|
333
346
|
with this information for this tool (which will nicely affect all subprocesses,
|
|
334
347
|
but not wreck our original shell).'''
|
|
335
348
|
|
|
336
|
-
|
|
337
|
-
|
|
349
|
+
name_path_parts = tool_arg_get_parts(tool)
|
|
350
|
+
if len(name_path_parts) == 1:
|
|
351
|
+
return name_path_parts[0]
|
|
352
|
+
|
|
353
|
+
name, path_arg = name_path_parts[0:2]
|
|
354
|
+
|
|
355
|
+
if name not in config['auto_tools_order'][0]:
|
|
356
|
+
return name
|
|
357
|
+
|
|
358
|
+
config_exe = config['auto_tools_order'][0][name].get('exe', str())
|
|
359
|
+
if isinstance(config_exe, list):
|
|
360
|
+
orig_exe = config_exe[0]
|
|
361
|
+
else:
|
|
362
|
+
orig_exe = config_exe
|
|
338
363
|
|
|
339
|
-
|
|
340
|
-
|
|
364
|
+
if path_arg and os.path.isfile(path_arg):
|
|
365
|
+
# Someone passes us --tool=<name>=/path/to/bin/exe, remove the exe from path:
|
|
366
|
+
path, exe = os.path.split(path_arg)
|
|
367
|
+
elif path_arg and os.path.isdir(path_arg):
|
|
341
368
|
# Someone passes us --tool=<name>=/path/to/bin/ (did not have exe)
|
|
342
|
-
path =
|
|
369
|
+
path, exe = path_arg, orig_exe
|
|
343
370
|
else:
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
if not path:
|
|
347
|
-
|
|
371
|
+
path, exe = '', ''
|
|
372
|
+
|
|
373
|
+
if not path or not exe:
|
|
374
|
+
util.error(f'Can not find path or exe for --tool={tool}: {name=} path={path_arg}')
|
|
375
|
+
return name
|
|
348
376
|
|
|
349
377
|
path = os.path.abspath(path)
|
|
350
378
|
if os.path.isdir(path):
|
|
351
|
-
paths = os.environ
|
|
379
|
+
paths = os.environ.get('PATH', '').split(':')
|
|
352
380
|
if path not in paths:
|
|
353
|
-
os.environ['PATH'] = path + ':' + os.environ['PATH']
|
|
354
381
|
util.info(f'--tool={tool} has path information, prepending PATH with: {path}')
|
|
382
|
+
os.environ['PATH'] = path + ':' + os.environ.get('PATH', '')
|
|
355
383
|
else:
|
|
356
384
|
util.info(f'--tool={tool} has path information, but {path} already in $PATH')
|
|
357
|
-
|
|
358
|
-
|
|
385
|
+
|
|
386
|
+
user_exe = os.path.join(path, exe)
|
|
387
|
+
if not os.access(user_exe, os.X_OK):
|
|
388
|
+
util.error(f'--tool setting for {tool}: {user_exe} is not an executable')
|
|
389
|
+
return name
|
|
390
|
+
|
|
391
|
+
user_exe = shutil.which(exe)
|
|
392
|
+
|
|
393
|
+
if update_config:
|
|
394
|
+
if isinstance(config_exe, list):
|
|
395
|
+
config['auto_tools_order'][0][name]['exe'][0] = user_exe
|
|
396
|
+
else:
|
|
397
|
+
config['auto_tools_order'][0][name]['exe'] = user_exe
|
|
398
|
+
util.debug(f'For {tool=}, auto_tools_order config updated')
|
|
399
|
+
|
|
400
|
+
util.debug(f'For {tool=}, final {user_exe=}')
|
|
401
|
+
|
|
402
|
+
return name
|
opencos/eda_config_defaults.yml
CHANGED
opencos/eda_extract_targets.py
CHANGED
|
File without changes
|
opencos/eda_tool_helper.py
CHANGED
|
@@ -68,3 +68,22 @@ def get_all_handler_commands(config=None, tools_loaded=None) -> dict:
|
|
|
68
68
|
all_handler_commands[command].append(tool)
|
|
69
69
|
|
|
70
70
|
return all_handler_commands
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def get_handler_tool_version(tool: str, eda_command: str, config: dict) -> str:
|
|
74
|
+
'''Attempts to get a Command Handler's version given tool + eda_command'''
|
|
75
|
+
|
|
76
|
+
entry = config['auto_tools_order'][0].get(tool, {})
|
|
77
|
+
if not entry:
|
|
78
|
+
return ''
|
|
79
|
+
|
|
80
|
+
handler_name = entry.get('handlers', {}).get(eda_command, '')
|
|
81
|
+
if not handler_name:
|
|
82
|
+
return ''
|
|
83
|
+
|
|
84
|
+
module = util.import_class_from_string(handler_name)
|
|
85
|
+
obj = module(config=config)
|
|
86
|
+
if not getattr(obj, 'get_versions', None):
|
|
87
|
+
return ''
|
|
88
|
+
|
|
89
|
+
return obj.get_versions()
|
opencos/hw/oc_cli.py
CHANGED
|
File without changes
|
opencos/tests/helpers.py
CHANGED
|
@@ -16,6 +16,7 @@ from opencos.utils.subprocess_helpers import subprocess_run_background
|
|
|
16
16
|
|
|
17
17
|
# Figure out what tools the system has available, without calling eda.main(..)
|
|
18
18
|
config, tools_loaded = eda_tool_helper.get_config_and_tools_loaded()
|
|
19
|
+
known_tool_versions = {}
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
def eda_wrap_is_sim_fail(rc: int, quiet: bool = False) -> bool:
|
|
@@ -32,6 +33,16 @@ def eda_wrap_is_sim_fail(rc: int, quiet: bool = False) -> bool:
|
|
|
32
33
|
status_constants.EDA_DEFAULT_ERROR
|
|
33
34
|
)
|
|
34
35
|
|
|
36
|
+
def handle_tool_version(tool: str, eda_command: str, cfg: dict = config) -> None:
|
|
37
|
+
'''Attempts to use a command handler for tool + eda_command and we'll
|
|
38
|
+
track the version globally'''
|
|
39
|
+
|
|
40
|
+
if not known_tool_versions.get(tool, ''):
|
|
41
|
+
handler_version = eda_tool_helper.get_handler_tool_version(
|
|
42
|
+
tool=tool, eda_command=eda_command, config=cfg
|
|
43
|
+
)
|
|
44
|
+
known_tool_versions[tool] = handler_version
|
|
45
|
+
|
|
35
46
|
def can_run_eda_command(*commands, cfg: dict = config) -> bool:
|
|
36
47
|
'''Returns True if we have any installed tool that can run: eda <command>'''
|
|
37
48
|
runnable = []
|
|
@@ -50,10 +61,36 @@ def can_run_eda_command(*commands, cfg: dict = config) -> bool:
|
|
|
50
61
|
if entry and entry.get('disable-auto', False):
|
|
51
62
|
# This tool cannot automatically run our command.
|
|
52
63
|
return False
|
|
64
|
+
# While we're here, set known tool versions.
|
|
65
|
+
handle_tool_version(tool=tool, eda_command=command, cfg=cfg)
|
|
53
66
|
|
|
54
67
|
runnable.append(True)
|
|
55
68
|
return runnable and all(runnable)
|
|
56
69
|
|
|
70
|
+
def can_uvm(tool: str) -> bool:
|
|
71
|
+
'''Returns True if we can run UVM, per tool'''
|
|
72
|
+
|
|
73
|
+
if tool not in tools_loaded:
|
|
74
|
+
return False
|
|
75
|
+
|
|
76
|
+
if tool == 'verilator':
|
|
77
|
+
# requires UVM_HOME to be set with uvm_pkg.sv existing.
|
|
78
|
+
uvm_home = os.environ.get('UVM_HOME', '')
|
|
79
|
+
uvm_pkg = os.path.join(uvm_home, 'uvm_pkg.sv')
|
|
80
|
+
if not all((os.path.isdir(uvm_home), os.path.isfile(uvm_pkg))):
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
handle_tool_version(tool=tool, eda_command='sim', cfg=config)
|
|
84
|
+
version_list = known_tool_versions.get('verilator', '').split('.')
|
|
85
|
+
if int(version_list[0]) < 5 or \
|
|
86
|
+
(int(version_list[0]) == 5 and int(version_list[1]) < 42):
|
|
87
|
+
return False
|
|
88
|
+
|
|
89
|
+
return True
|
|
90
|
+
|
|
91
|
+
# default return False:
|
|
92
|
+
return False
|
|
93
|
+
|
|
57
94
|
def can_run_eda_sim(cfg: dict = config) -> bool:
|
|
58
95
|
'''Returns True if we have any installed tool that can run: eda sim'''
|
|
59
96
|
return can_run_eda_command('sim', cfg=cfg)
|
opencos/tests/test_eda.py
CHANGED
|
@@ -100,9 +100,19 @@ class TestsRequiresVerilator( # pylint: disable=too-many-public-methods
|
|
|
100
100
|
def test_args_sim_tool_with_path(self):
|
|
101
101
|
'''Test for calling a tool as --tool=<tool>=</path/to/tool-exe>'''
|
|
102
102
|
verilator_fullpath = shutil.which('verilator')
|
|
103
|
+
verilator_path, _ = os.path.split(verilator_fullpath)
|
|
104
|
+
|
|
103
105
|
chdir_remove_work_dir('../../lib/tests')
|
|
104
106
|
rc = eda_sim_wrap('--tool', f'verilator={verilator_fullpath}', 'oclib_fifo_test')
|
|
105
|
-
|
|
107
|
+
assert rc == 0
|
|
108
|
+
|
|
109
|
+
rc = eda_sim_wrap('--tool', f'verilator:{verilator_fullpath}', 'oclib_fifo_test')
|
|
110
|
+
assert rc == 0
|
|
111
|
+
|
|
112
|
+
rc = eda_sim_wrap('--tool', f'verilator={verilator_path}', 'oclib_fifo_test')
|
|
113
|
+
assert rc == 0
|
|
114
|
+
|
|
115
|
+
rc = eda_sim_wrap('--tool', f'verilator:{verilator_fullpath}', 'oclib_fifo_test')
|
|
106
116
|
assert rc == 0
|
|
107
117
|
|
|
108
118
|
def test_args_sim_with_coverage(self):
|
opencos/tools/iverilog.py
CHANGED
|
@@ -160,6 +160,10 @@ class CommandSimIverilog(CommandSim, ToolIverilog):
|
|
|
160
160
|
cmd_list += self.tool_config.get('simulate-args', '').split()
|
|
161
161
|
if self.args['waves']:
|
|
162
162
|
cmd_list += self.tool_config.get('simulate-waves-args', '').split()
|
|
163
|
+
for x in self.args['sim-plusargs']:
|
|
164
|
+
if x[0] != '+':
|
|
165
|
+
x = f'+{x}'
|
|
166
|
+
cmd_list.append(x)
|
|
163
167
|
return [ util.ShellCommandList(cmd_list, tee_fpath='sim.log') ]
|
|
164
168
|
|
|
165
169
|
def get_post_simulate_command_lists(self, **kwargs) -> list:
|
opencos/tools/riviera.py
CHANGED
|
@@ -13,6 +13,7 @@ import subprocess
|
|
|
13
13
|
from opencos import util
|
|
14
14
|
from opencos.tools.modelsim_ase import ToolModelsimAse, CommandSimModelsimAse
|
|
15
15
|
from opencos.utils.str_helpers import sanitize_defines_for_sh
|
|
16
|
+
from opencos.utils import status_constants
|
|
16
17
|
|
|
17
18
|
class ToolRiviera(ToolModelsimAse):
|
|
18
19
|
'''ToolRiviera used by opencos.eda for --tool=riviera'''
|
|
@@ -61,6 +62,7 @@ class CommandSimRiviera(CommandSimModelsimAse, ToolRiviera):
|
|
|
61
62
|
'gui': False,
|
|
62
63
|
'waves-fst': True,
|
|
63
64
|
'waves-vcd': False,
|
|
65
|
+
'coverage-tcl': '',
|
|
64
66
|
})
|
|
65
67
|
self.args_help.update({
|
|
66
68
|
'waves-fst': (
|
|
@@ -70,6 +72,10 @@ class CommandSimRiviera(CommandSimModelsimAse, ToolRiviera):
|
|
|
70
72
|
),
|
|
71
73
|
'waves-vcd': 'If using --waves, apply simulation runtime arg +trace=vcd',
|
|
72
74
|
'waves': 'Save a .asdb offline wavefile, can be used with --waves-fst or --waves-vcd',
|
|
75
|
+
'coverage-tcl': (
|
|
76
|
+
'bring your own .tcl file to run in Riviera (vsim) for coverage. The default'
|
|
77
|
+
' tcl steps are (from tool config in --config-yml): '
|
|
78
|
+
) + '; '.join(self.tool_config.get('simulate-coverage-tcl', [])),
|
|
73
79
|
})
|
|
74
80
|
|
|
75
81
|
def set_tool_defines(self):
|
|
@@ -183,7 +189,9 @@ class CommandSimRiviera(CommandSimModelsimAse, ToolRiviera):
|
|
|
183
189
|
|
|
184
190
|
|
|
185
191
|
|
|
186
|
-
def write_vsim_dot_do(
|
|
192
|
+
def write_vsim_dot_do( # pylint: disable=too-many-branches
|
|
193
|
+
self, dot_do_to_write: list
|
|
194
|
+
) -> None:
|
|
187
195
|
'''Writes files(s) based on dot_do_to_write(list of str)
|
|
188
196
|
|
|
189
197
|
list arg values can be empty (all) or have items 'all', 'sim', 'lint', 'vlog'.'''
|
|
@@ -278,18 +286,21 @@ class CommandSimRiviera(CommandSimModelsimAse, ToolRiviera):
|
|
|
278
286
|
"}",
|
|
279
287
|
]
|
|
280
288
|
|
|
289
|
+
vsim_dot_do_lines += [
|
|
290
|
+
"run -all;",
|
|
291
|
+
]
|
|
281
292
|
if self.args['coverage']:
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
+
if self.args['coverage-tcl']:
|
|
294
|
+
tclfile = os.path.abspath(self.args['coverage-tcl'])
|
|
295
|
+
if not os.path.isfile(tclfile):
|
|
296
|
+
self.error(f'--coverage-tcl file not found: {tclfile}',
|
|
297
|
+
error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
|
|
298
|
+
vsim_dot_do_lines += [
|
|
299
|
+
f'source {tclfile}'
|
|
300
|
+
]
|
|
301
|
+
else:
|
|
302
|
+
# default TCL for coverage:
|
|
303
|
+
vsim_dot_do_lines += self.tool_config.get('simulate-coverage-tcl', [])
|
|
293
304
|
|
|
294
305
|
|
|
295
306
|
vsim_dot_do_lines += [
|
opencos/tools/verilator.py
CHANGED
|
@@ -83,6 +83,7 @@ class VerilatorSim(CommandSim, ToolVerilator):
|
|
|
83
83
|
'lint-only': False,
|
|
84
84
|
'cc-mode': False,
|
|
85
85
|
'verilator-coverage-args': [],
|
|
86
|
+
'uvm': False,
|
|
86
87
|
'x-assign': '',
|
|
87
88
|
'x-initial': '',
|
|
88
89
|
})
|
|
@@ -116,6 +117,15 @@ class VerilatorSim(CommandSim, ToolVerilator):
|
|
|
116
117
|
' where valid string values are: 0, unique, fast.'
|
|
117
118
|
' Also conditinally adds to verilated exe call:'
|
|
118
119
|
' +verilator+rand+reset+[0,2] for arg values 0, unique|fast'),
|
|
120
|
+
'uvm': (
|
|
121
|
+
'Warns on Verilator < 5.042, or missing $UVM_HOME environment var set (or in'
|
|
122
|
+
' .env, $UVM_HOME/uvm_pkg.sv should exist), and will run verilator with args:'
|
|
123
|
+
' -Wno-fatal +define+UVM_NO_DPI'
|
|
124
|
+
),
|
|
125
|
+
'verilator-coverage-args': (
|
|
126
|
+
'Requires --coverage, args to be applied to verilator_coverage, which runs'
|
|
127
|
+
' after running the compiled executable simulation'
|
|
128
|
+
),
|
|
119
129
|
})
|
|
120
130
|
|
|
121
131
|
self.verilate_command_lists = []
|
|
@@ -212,12 +222,19 @@ class VerilatorSim(CommandSim, ToolVerilator):
|
|
|
212
222
|
|
|
213
223
|
verilate_command_list = self._get_start_verilator_command_list(lint_only=lint_only)
|
|
214
224
|
|
|
225
|
+
# Handle UVM things (return args), but also handles uvm_pkg.sv in self.files_sv:
|
|
226
|
+
# since we run this 2x (lint-only and normal) only do warnings for one of them:
|
|
227
|
+
verilate_command_list += self._verilator_args_uvm(
|
|
228
|
+
warnings=(not lint_only), add_uvm_pkg_if_found=True
|
|
229
|
+
)
|
|
230
|
+
|
|
215
231
|
verilate_command_list += self._get_verilator_tool_config_waivers()
|
|
216
232
|
|
|
217
233
|
verilate_command_list += self._verilator_args_defaults_cflags_nproc()
|
|
218
234
|
|
|
219
235
|
verilate_command_list += self._get_verilator_waves_args(lint_only=lint_only)
|
|
220
236
|
|
|
237
|
+
|
|
221
238
|
if self.args.get('coverage', True):
|
|
222
239
|
verilate_command_list += self.tool_config.get(
|
|
223
240
|
'compile-coverage-args', '--coverage').split()
|
|
@@ -486,6 +503,75 @@ class VerilatorSim(CommandSim, ToolVerilator):
|
|
|
486
503
|
return verilate_args
|
|
487
504
|
|
|
488
505
|
|
|
506
|
+
def _verilator_support_uvm_pkg_fpath(self, add_if_found: bool = True) -> bool:
|
|
507
|
+
'''Returns False if we could not find a suitable uvm_pkg.sv to use, or if --no-uvm.
|
|
508
|
+
|
|
509
|
+
This will also auto-add uvm_pkg.sv from $UVM_HOME/uvm_pkg.sv if not present in
|
|
510
|
+
self.files already (adds to front of self.files_sv)
|
|
511
|
+
'''
|
|
512
|
+
|
|
513
|
+
if not self.args['uvm']:
|
|
514
|
+
return False
|
|
515
|
+
|
|
516
|
+
for fname, exists in self.files.items():
|
|
517
|
+
if exists and os.path.split(fname)[1] == 'uvm_pkg.sv':
|
|
518
|
+
# already present in our source files (assume someone doing it manually
|
|
519
|
+
# or via DEPS)
|
|
520
|
+
return True
|
|
521
|
+
|
|
522
|
+
uvm_home = os.environ.get('UVM_HOME', '')
|
|
523
|
+
if not uvm_home:
|
|
524
|
+
return False
|
|
525
|
+
|
|
526
|
+
uvm_pkg_fpath = os.path.join(uvm_home, 'uvm_pkg.sv')
|
|
527
|
+
if add_if_found and os.path.isfile(uvm_pkg_fpath):
|
|
528
|
+
uvm_pkg_fpath = os.path.abspath(uvm_pkg_fpath)
|
|
529
|
+
util.info(f'For --uvm, adding to source files: {uvm_pkg_fpath}')
|
|
530
|
+
self.files[uvm_pkg_fpath] = True
|
|
531
|
+
self.files_sv.insert(0, uvm_pkg_fpath)
|
|
532
|
+
self.files_caller_info[uvm_pkg_fpath] = 'verilator.py'
|
|
533
|
+
util.info(f'For --uvm, adding +incdir+: {uvm_home}')
|
|
534
|
+
self.incdirs.append(os.path.abspath(uvm_home))
|
|
535
|
+
return True
|
|
536
|
+
|
|
537
|
+
return False
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
def _verilator_args_uvm(
|
|
541
|
+
self, warnings: bool = True, add_uvm_pkg_if_found: bool = True
|
|
542
|
+
) -> list:
|
|
543
|
+
'''Returns list of args to be added to verilator (compile) step if --uvm present
|
|
544
|
+
|
|
545
|
+
Warnings on potential issues (Veriltor version, missing uvm_pkg.sv).
|
|
546
|
+
Optionally adds uvm_pkg.sv to source files.
|
|
547
|
+
'''
|
|
548
|
+
|
|
549
|
+
# Handle --uvm args:
|
|
550
|
+
if not self.args['uvm']:
|
|
551
|
+
return []
|
|
552
|
+
|
|
553
|
+
if warnings:
|
|
554
|
+
|
|
555
|
+
# prefers Verilator >= v5.042, $UVM_HOME to be set, or warning.
|
|
556
|
+
version_list = self._VERSION.split('.')
|
|
557
|
+
if int(version_list[0]) < 5 or \
|
|
558
|
+
(int(version_list[0]) == 5 and int(version_list[1]) < 42):
|
|
559
|
+
util.warning(f'Verilator version is {self._VERSION}, --uvm set prefers Verilator',
|
|
560
|
+
'version > v5.042')
|
|
561
|
+
|
|
562
|
+
if not os.environ.get('UVM_HOME', ''):
|
|
563
|
+
util.warning('--uvm set, however env (or .env or --env-file) $UVM_HOME is not set')
|
|
564
|
+
|
|
565
|
+
uvm_pkg_found = self._verilator_support_uvm_pkg_fpath(add_if_found=add_uvm_pkg_if_found)
|
|
566
|
+
if warnings and not uvm_pkg_found:
|
|
567
|
+
util.warning(
|
|
568
|
+
'--uvm set, however no suitable uvm_pkg.sv is source files,',
|
|
569
|
+
f'nor in $UVM_HOME/uvm_pkg.sv. $UVM_HOME={os.environ.get("UVM_HOME", "")}'
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
return ['-Wno-fatal', '+define+UVM_NO_DPI']
|
|
573
|
+
|
|
574
|
+
|
|
489
575
|
def artifacts_add(self, name: str, typ: str, description: str) -> None:
|
|
490
576
|
'''Override from Command.artifacts_add, so we can catch known file
|
|
491
577
|
|
opencos/tools/vivado.py
CHANGED
|
@@ -272,6 +272,7 @@ class CommandSimVivado(CommandSim, ToolVivado):
|
|
|
272
272
|
xsim_plusargs_list.append('--testplusarg')
|
|
273
273
|
if x[0] == '+':
|
|
274
274
|
x = x[1:]
|
|
275
|
+
x = x.replace('"', '\\\"') # we have to preserve " in the value.
|
|
275
276
|
xsim_plusargs_list.append(f'\"{x}\"')
|
|
276
277
|
|
|
277
278
|
# execute snapshot
|
opencos/util.py
CHANGED
|
@@ -21,6 +21,7 @@ from dotenv import load_dotenv
|
|
|
21
21
|
from supports_color import supportsColor
|
|
22
22
|
|
|
23
23
|
from opencos.utils import status_constants
|
|
24
|
+
from opencos.utils.str_helpers import strip_ansi_color
|
|
24
25
|
|
|
25
26
|
global_exit_allowed = False # pylint: disable=invalid-name
|
|
26
27
|
progname = "UNKNOWN" # pylint: disable=invalid-name
|
|
@@ -383,8 +384,10 @@ def get_argparser() -> argparse.ArgumentParser:
|
|
|
383
384
|
'Input .f file to be expanded as eda'
|
|
384
385
|
' args/defines/incdirs/files/targets'))
|
|
385
386
|
parser.add_argument('--env-file', default=[], action='append',
|
|
386
|
-
help=
|
|
387
|
-
|
|
387
|
+
help=(
|
|
388
|
+
"dotenv file(s) to pass ENV vars, (default: .env loaded first,"
|
|
389
|
+
" subsequent files' vars override .env"
|
|
390
|
+
))
|
|
388
391
|
return parser
|
|
389
392
|
|
|
390
393
|
|
|
@@ -407,10 +410,10 @@ def get_argparser_short_help(parser: object = None) -> str:
|
|
|
407
410
|
'''Returns short help for our ArgumentParser'''
|
|
408
411
|
if not parser:
|
|
409
412
|
parser = get_argparser()
|
|
410
|
-
full_lines = parser.format_help().split('\n')
|
|
413
|
+
full_lines = strip_ansi_color(parser.format_help()).split('\n')
|
|
411
414
|
lineno = 0
|
|
412
415
|
for lineno, line in enumerate(full_lines):
|
|
413
|
-
if line.startswith('options:'):
|
|
416
|
+
if any(line.startswith(x) for x in ('options:', 'optional arguments:')):
|
|
414
417
|
break
|
|
415
418
|
# skip the line that says 'options:', repalce with the progname:
|
|
416
419
|
return f'{parser.prog}:\n' + '\n'.join(full_lines[lineno + 1:])
|
|
@@ -432,11 +435,12 @@ def process_token(arg: list) -> bool:
|
|
|
432
435
|
def load_env_file(env_file: str) -> None:
|
|
433
436
|
'''Handles .env file (from util CLI args --env-file)'''
|
|
434
437
|
if os.path.isfile(env_file):
|
|
435
|
-
load_dotenv(env_file)
|
|
438
|
+
load_dotenv(env_file, override=True)
|
|
436
439
|
env_files_loaded.add(os.path.abspath(env_file))
|
|
437
440
|
else:
|
|
438
441
|
warning(f'--env-file {env_file} does not exist and is not loaded.')
|
|
439
442
|
|
|
443
|
+
|
|
440
444
|
def patch_args_for_dir(tokens: list, patch_dir: str, caller_info: str) -> list:
|
|
441
445
|
'''Given list of args, attempt to correct for relative dir'''
|
|
442
446
|
|
|
@@ -536,8 +540,10 @@ def process_tokens( # pylint: disable=too-many-branches
|
|
|
536
540
|
parser.add_argument('--debug-level', type=int, default=0,
|
|
537
541
|
help='Set debug level messaging (default: 0)')
|
|
538
542
|
parser.add_argument('--env-file', default=[], action='append',
|
|
539
|
-
help=
|
|
540
|
-
|
|
543
|
+
help=(
|
|
544
|
+
"dotenv file(s) to pass ENV vars, (default: .env loaded first,"
|
|
545
|
+
" subsequent files' vars override .env"
|
|
546
|
+
))
|
|
541
547
|
parser.add_argument('-f', '--input-file', default=[], action='append',
|
|
542
548
|
help=(
|
|
543
549
|
'Input .f file to be expanded as eda args, defines, incdirs,'
|
|
@@ -553,7 +559,7 @@ def process_tokens( # pylint: disable=too-many-branches
|
|
|
553
559
|
debug(f'util.process_tokens: {parsed=} {unparsed=} from: {tokens}')
|
|
554
560
|
|
|
555
561
|
if os.path.isfile(str(Path('.env'))):
|
|
556
|
-
parsed.env_file.
|
|
562
|
+
parsed.env_file.insert(0, '.env')
|
|
557
563
|
if parsed.env_file:
|
|
558
564
|
for env_file in parsed.env_file:
|
|
559
565
|
load_env_file(env_file)
|
opencos/utils/str_helpers.py
CHANGED
|
@@ -172,7 +172,7 @@ def pretty_list_columns_manual(data: list, num_columns: int = 4, auto_columns: b
|
|
|
172
172
|
pretty_list_columns_manual(data=data, num_columns=num_columns-1, auto_columns=True)
|
|
173
173
|
)
|
|
174
174
|
return ret_lines
|
|
175
|
-
if max_line_len + max_item_len + _spacing
|
|
175
|
+
if max_line_len + max_item_len + _spacing <= window_cols:
|
|
176
176
|
# add 1 more column if we're guaranteed to have room.
|
|
177
177
|
ret_lines.extend(
|
|
178
178
|
pretty_list_columns_manual(data=data, num_columns=num_columns+1, auto_columns=True)
|
|
@@ -198,3 +198,8 @@ def print_columns_manual(data: list, num_columns: int = 4, auto_columns: bool =
|
|
|
198
198
|
data=data, num_columns=num_columns, auto_columns=auto_columns
|
|
199
199
|
)
|
|
200
200
|
print('\n'.join(lines))
|
|
201
|
+
|
|
202
|
+
def strip_ansi_color(text: str) -> str:
|
|
203
|
+
'''Strip ANSI color characters from str'''
|
|
204
|
+
ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
|
|
205
|
+
return ansi_escape.sub('', text)
|
|
@@ -5,10 +5,18 @@ import shutil
|
|
|
5
5
|
import subprocess
|
|
6
6
|
import sys
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
import psutil
|
|
9
|
+
from opencos.util import debug, error, info, warning, progname, global_log
|
|
9
10
|
|
|
10
11
|
IS_WINDOWS = sys.platform.startswith('win')
|
|
11
12
|
|
|
13
|
+
# For non-Windows, we track the background parent PIDs, because some tools (vivado XSim,
|
|
14
|
+
# most Modelsim/Questa variants) tend to spawn children PIDs that don't always respond
|
|
15
|
+
# nicely to a friendly *nix SIGTERM. So we'll remember what our parent PIDs are, and
|
|
16
|
+
# eda.py's (or other CLI opencos script) can use signal to cleanup any remaining
|
|
17
|
+
# parents + children using subprocess_helpers.cleanup_all()
|
|
18
|
+
ALL_PARENT_PIDS = set()
|
|
19
|
+
|
|
12
20
|
def subprocess_run(
|
|
13
21
|
work_dir: str, command_list: list, fake: bool = False, shell: bool = False
|
|
14
22
|
) -> int:
|
|
@@ -35,10 +43,11 @@ def subprocess_run(
|
|
|
35
43
|
|
|
36
44
|
debug(f"subprocess_run: About to call subprocess.run({c}, **{proc_kwargs}")
|
|
37
45
|
proc = subprocess.run(c, check=True, **proc_kwargs)
|
|
46
|
+
# Note - we do not get PID management for subprocess_run(...)
|
|
38
47
|
return proc.returncode
|
|
39
48
|
|
|
40
49
|
|
|
41
|
-
def subprocess_run_background(
|
|
50
|
+
def subprocess_run_background( # pylint: disable=too-many-branches
|
|
42
51
|
work_dir: str, command_list: list, background: bool = True, fake : bool = False,
|
|
43
52
|
shell: bool = False, tee_fpath: str = ''
|
|
44
53
|
) -> (str, str, int):
|
|
@@ -76,6 +85,9 @@ def subprocess_run_background(
|
|
|
76
85
|
|
|
77
86
|
debug(f"subprocess_run_background: about to call subprocess.Popen({c}, **{proc_kwargs})")
|
|
78
87
|
proc = subprocess.Popen(c, **proc_kwargs) # pylint: disable=consider-using-with
|
|
88
|
+
if not background:
|
|
89
|
+
info(f'PID {proc.pid} for {command_list[0]}')
|
|
90
|
+
add_running_parent_pid(proc.pid)
|
|
79
91
|
|
|
80
92
|
stdout = ''
|
|
81
93
|
tee_fpath_f = None
|
|
@@ -99,10 +111,61 @@ def subprocess_run_background(
|
|
|
99
111
|
stdout += line + '\n'
|
|
100
112
|
|
|
101
113
|
proc.communicate()
|
|
114
|
+
remove_completed_parent_pid(proc.pid)
|
|
115
|
+
|
|
102
116
|
rc = proc.returncode
|
|
103
117
|
if tee_fpath_f:
|
|
104
118
|
tee_fpath_f.write(f'INFO: [{progname}] subprocess_run_background: returncode={rc}\n')
|
|
105
119
|
tee_fpath_f.close()
|
|
106
|
-
|
|
120
|
+
if not background:
|
|
121
|
+
info('subprocess_run_background: wrote: ' + os.path.abspath(tee_fpath))
|
|
107
122
|
|
|
108
123
|
return stdout, '', rc
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def add_running_parent_pid(pid: int) -> None:
|
|
127
|
+
'''Adds pid (if still alive) to ALL_PARENT_PIDS'''
|
|
128
|
+
try:
|
|
129
|
+
p = psutil.Process(pid)
|
|
130
|
+
ALL_PARENT_PIDS.add(p.pid)
|
|
131
|
+
except psutil.NoSuchProcess:
|
|
132
|
+
pass
|
|
133
|
+
except Exception as e:
|
|
134
|
+
error(f'{pid=} exception {e}')
|
|
135
|
+
|
|
136
|
+
def remove_completed_parent_pid(pid: int) -> None:
|
|
137
|
+
'''Removes pid (if no longer alive) from ALL_PARENT_PIDS.'''
|
|
138
|
+
try:
|
|
139
|
+
p = psutil.Process(pid)
|
|
140
|
+
warning(f'PID {p.pid} still running')
|
|
141
|
+
except psutil.NoSuchProcess:
|
|
142
|
+
ALL_PARENT_PIDS.remove(pid)
|
|
143
|
+
except Exception as e:
|
|
144
|
+
error(f'{pid=} exception {e}')
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def cleanup_all() -> None:
|
|
148
|
+
'''Kills everything from ALL_PARENT_PIDS.'''
|
|
149
|
+
for parent in ALL_PARENT_PIDS:
|
|
150
|
+
kill_proc_tree(parent)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def kill_proc_tree(pid: int, including_parent: bool = True) -> None:
|
|
154
|
+
'''Kills a process and its entire descendant tree'''
|
|
155
|
+
try:
|
|
156
|
+
parent = psutil.Process(pid)
|
|
157
|
+
children = parent.children(recursive=True)
|
|
158
|
+
info(f'{pid=} {parent=} {children=}')
|
|
159
|
+
for child in children:
|
|
160
|
+
if psutil.Process(child.pid):
|
|
161
|
+
info(f'parent {pid=} killing {child=}')
|
|
162
|
+
child.kill()
|
|
163
|
+
_, still_alive = psutil.wait_procs(children, timeout=5)
|
|
164
|
+
if still_alive:
|
|
165
|
+
warning(f'parent {pid=} {still_alive=}')
|
|
166
|
+
if including_parent:
|
|
167
|
+
info(f'parent {pid=} killing {parent=}')
|
|
168
|
+
parent.kill()
|
|
169
|
+
parent.wait(5)
|
|
170
|
+
except psutil.NoSuchProcess:
|
|
171
|
+
pass # Process already terminated
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: opencos-eda
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.5
|
|
4
4
|
Summary: A simple Python package for wrapping RTL simuliatons and synthesis
|
|
5
5
|
Author-email: Simon Sabato <simon@cognichip.ai>, Drew Ranck <drew@cognichip.ai>
|
|
6
6
|
Project-URL: Homepage, https://github.com/cognichip/opencos
|
|
@@ -9,8 +9,8 @@ License-File: LICENSE
|
|
|
9
9
|
License-File: LICENSE.spdx
|
|
10
10
|
Requires-Dist: mergedeep>=1.3.4
|
|
11
11
|
Requires-Dist: peakrdl>=1.1.0
|
|
12
|
+
Requires-Dist: psutil>=7.0.0
|
|
12
13
|
Requires-Dist: pyyaml>=6.0.2
|
|
13
|
-
Requires-Dist: pytest>=8.3.5
|
|
14
14
|
Requires-Dist: python-dotenv>=1.0.1
|
|
15
15
|
Requires-Dist: schema>=0.7.7
|
|
16
16
|
Requires-Dist: toml>=0.10.2
|
|
@@ -18,4 +18,12 @@ Requires-Dist: yamllint>=1.35.1
|
|
|
18
18
|
Requires-Dist: PySerial>=3.5
|
|
19
19
|
Requires-Dist: cocotb>=2.0
|
|
20
20
|
Requires-Dist: supports_color>=0.2.0
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pylint>=3.0.0; extra == "dev"
|
|
23
|
+
Requires-Dist: pytest>=8.3.5; extra == "dev"
|
|
24
|
+
Provides-Extra: docs
|
|
25
|
+
Requires-Dist: mkdocs; extra == "docs"
|
|
26
|
+
Requires-Dist: mkdocs-material; extra == "docs"
|
|
27
|
+
Requires-Dist: mkdocs-wavedrom-plugin; extra == "docs"
|
|
28
|
+
Requires-Dist: mkdocs-plantuml; extra == "docs"
|
|
21
29
|
Dynamic: license-file
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
opencos/__init__.py,sha256=RwJA9oc1uUlvNX7v5zoqwjnSRNq2NZwRlHqtS-ICJkI,122
|
|
2
2
|
opencos/_version.py,sha256=KaWIjS0c08g-C0fgYY1kXwSPqhOFxaq5pYEeoZhOR_I,617
|
|
3
3
|
opencos/_waves_pkg.sv,sha256=TL5YT9lT-fn2FD54MbVVZROmZ7vtW3ScA_rM2eRzKmU,2068
|
|
4
|
-
opencos/deps_schema.py,sha256=
|
|
5
|
-
opencos/eda.py,sha256=
|
|
6
|
-
opencos/eda_base.py,sha256=
|
|
7
|
-
opencos/eda_config.py,sha256=
|
|
8
|
-
opencos/eda_config_defaults.yml,sha256=
|
|
4
|
+
opencos/deps_schema.py,sha256=fx1_IJhsDYkUciwwCPTXHP6ftFjTsPVjO4xg12twIjw,17384
|
|
5
|
+
opencos/eda.py,sha256=m_Jq1Iht0I7dSN4ceI4c4QZCkKjW85ewvoLfCaOo7-s,23315
|
|
6
|
+
opencos/eda_base.py,sha256=PK89dJMmTHAqvfrxkVXkYZlvyk4er2AVDJ5FMVHry6A,109724
|
|
7
|
+
opencos/eda_config.py,sha256=afimZEfvVitWS-k3IsR7fwzIz5dHWKfiJp5qHzzBF6c,14110
|
|
8
|
+
opencos/eda_config_defaults.yml,sha256=2IX9i7VCwgXHKzsjcEGdLJ6SqxiQqXbVIupYNhjHIes,16263
|
|
9
9
|
opencos/eda_config_max_verilator_waivers.yml,sha256=lTAU4IOEbUWVlPzuer1YYhIyxpPINeA4EJqcRIT-Ymk,840
|
|
10
10
|
opencos/eda_config_reduced.yml,sha256=cQ9jY4J7EvAbeHTiP6bvpDSVJAYiitjLZPSxxLKIEbk,1440
|
|
11
11
|
opencos/eda_deps_bash_completion.bash,sha256=jMkQKY82HBgOnQeMdA1hMrXguRFtB52SMBxUemKovL4,1958
|
|
12
12
|
opencos/eda_deps_sanitize.py,sha256=SQjvrte9Hv9JesRY0wljvbdC6pAmLCikI-Wdzzy-D04,1939
|
|
13
13
|
opencos/eda_extract_targets.py,sha256=POlxZfqf2dNH2nc1CEw5B_53vSHAicSTkpU9_-2_6Zw,2851
|
|
14
|
-
opencos/eda_tool_helper.py,sha256=
|
|
14
|
+
opencos/eda_tool_helper.py,sha256=Xm6nr9XweCjueWFLkrH5U3nK96JGeeh86f2GCPhwY-o,3108
|
|
15
15
|
opencos/export_helper.py,sha256=5BnrkhiieJBgYKAryhXD7HSGtrgvXQpZ8B5ltdrhbRY,22649
|
|
16
16
|
opencos/export_json_convert.py,sha256=tSIMbLFtc_Fo66EhFovMii1v_qJYyFZJrPNnoPdW7L0,4182
|
|
17
17
|
opencos/files.py,sha256=AQOnsrvoc0r76LiFrkoMbwOGdUO1FpBiFY_jyyI_ve8,1566
|
|
18
18
|
opencos/names.py,sha256=Y2aJ5wgpbNIJ-_P5xUXnHMv_h-zMOX2Rt6iLuduqC1Q,1213
|
|
19
19
|
opencos/peakrdl_cleanup.py,sha256=vHNGtalTrIVP335PhRjPt9RhoccgpK1HJAi-E4M8Kc8,736
|
|
20
20
|
opencos/seed.py,sha256=IL9Yg-r9SLSRseMVWaEHmuw2_DNi_eyut11EafoNTsU,942
|
|
21
|
-
opencos/util.py,sha256=
|
|
21
|
+
opencos/util.py,sha256=PGkc0JYKsx2sKiuoCmL3rLdEFyXeM6xY3r42Q-8KUJw,42454
|
|
22
22
|
opencos/commands/__init__.py,sha256=oOOQmn5_jHAMSOfA3swJJ7mdoyHsJA0lJwKPTudlTns,1125
|
|
23
23
|
opencos/commands/build.py,sha256=mvJYxk5J15k0Cr8R7oIdIIdsEtWV3gE-LnPweVwtSDo,1487
|
|
24
24
|
opencos/commands/deps_help.py,sha256=WDrU7H9sypzDAxe_CHqhW5B_scbQMzBEdf-v-Jcfd5Q,10682
|
|
@@ -38,20 +38,20 @@ opencos/commands/targets.py,sha256=_jRNhm2Fqj0fmMvTw6Ba39DCsRHf_r_uZCy_R064kpA,1
|
|
|
38
38
|
opencos/commands/upload.py,sha256=oyImgcEFGxDkdeY9EYyX2R6fTOmN-lTs-HYxAZqXUUo,871
|
|
39
39
|
opencos/commands/waves.py,sha256=nrp3ALwfJujZns44tgCgia_dEedQyKe0T3fuws8h39U,7697
|
|
40
40
|
opencos/deps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
41
|
-
opencos/deps/defaults.py,sha256=
|
|
41
|
+
opencos/deps/defaults.py,sha256=Z6mIVJEV0zQ9rC-HkQFMBFAkixjqKS1TATPSc27wOeA,1502
|
|
42
42
|
opencos/deps/deps_commands.py,sha256=q4JfSfzRO2nM2zdNT4enCy33FokEytZYQJn1HJ6osJk,16606
|
|
43
43
|
opencos/deps/deps_file.py,sha256=YQ5ftYvppRTqUto22r-XDH6-bcMO7cA-WiJ7QzPjljY,17103
|
|
44
|
-
opencos/deps/deps_processor.py,sha256=
|
|
44
|
+
opencos/deps/deps_processor.py,sha256=5eGE__Isssm6JDnVxI2hM1_ljS968K-24eUWGDEr_vs,42303
|
|
45
45
|
opencos/hw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
46
46
|
opencos/hw/oc_cli.py,sha256=U1JGlshLZhtd0LgndZFBZVltAj_HemdhbjO_Zo8ZuVM,132252
|
|
47
47
|
opencos/hw/pcie.py,sha256=VUJljaZJYgScAAx5yn7F6GoA8K9eTcw24otYZbkMpYs,3035
|
|
48
48
|
opencos/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
49
49
|
opencos/tests/custom_config.yml,sha256=TRoVM9ZFKPOA_8JmlpzaMhnGO1txmaD14N_8P1oqzew,257
|
|
50
|
-
opencos/tests/helpers.py,sha256=
|
|
50
|
+
opencos/tests/helpers.py,sha256=77ljULYa-DhnC9VgT_R1wic_GdgeqsAJdkOXDt1KjDo,13839
|
|
51
51
|
opencos/tests/test_build.py,sha256=FQAxOpLVQShAHD_L5rqJctPeSAoqoOCNFI0RXflLuY0,387
|
|
52
52
|
opencos/tests/test_deps_helpers.py,sha256=uQZxleh6aKO-mZQhagHh5xLIBbpQ8dav7-5D0eemq_g,8164
|
|
53
53
|
opencos/tests/test_deps_schema.py,sha256=T3P9KjaMyKsk8b7snNVvNSsom2hIJcg6Z9apYiXoH9Y,941
|
|
54
|
-
opencos/tests/test_eda.py,sha256=
|
|
54
|
+
opencos/tests/test_eda.py,sha256=8X6kej5-uPv2l0xeWOhitCE640XZtiGLwzCktra_Ehg,37784
|
|
55
55
|
opencos/tests/test_eda_elab.py,sha256=AjU4WMYtFoHpNe1Z4yWWpxDKy4V_hAjL5rl3jqphZrk,3179
|
|
56
56
|
opencos/tests/test_eda_synth.py,sha256=BtBrNVJ9C-LJt3K0wNNS5ukEVrET16AbRXl2IzxudJ8,5744
|
|
57
57
|
opencos/tests/test_oc_cli.py,sha256=w-F-LjSSWVql3D2WG8tcV4_C52i-hL_2WT3oDpKQn9s,734
|
|
@@ -68,30 +68,30 @@ opencos/tools/cocotb.py,sha256=hlBR6M4k_amOOWa7cZDuw9fTGJtJha1jSpGgyJN8Iis,17734
|
|
|
68
68
|
opencos/tools/invio.py,sha256=S2ChWr8xMZHSOOhX2hGKQhMmtQY2potVQjc-lsMg73o,3299
|
|
69
69
|
opencos/tools/invio_helpers.py,sha256=86WOGmSf4m_lEqBtK3DLjWqI0jnqAWzBEBRYfBUGiSY,8804
|
|
70
70
|
opencos/tools/invio_yosys.py,sha256=CszGeTdE1ilnMmWPLW77BrtobbsGb1CKXqot0hGimFU,5996
|
|
71
|
-
opencos/tools/iverilog.py,sha256=
|
|
71
|
+
opencos/tools/iverilog.py,sha256=3IQIZVDioChKEJIVVJki-q7NlvBg0k3n61oM7ltG9c8,6551
|
|
72
72
|
opencos/tools/modelsim_ase.py,sha256=Jt-6N3BZZyu25fT1ehFQLRUTVvrcCo4e2Gl7UtsQcuk,17834
|
|
73
73
|
opencos/tools/quartus.py,sha256=_TfmPSYpbhmDLw7Dur-rRP0iGwv9hhQ6E5G-XLiYPEM,30486
|
|
74
74
|
opencos/tools/questa.py,sha256=nHImM0Wydcf4YHGibHmQAwmqKHmMxKZUqY-E-vz1o8M,9827
|
|
75
75
|
opencos/tools/questa_fse.py,sha256=hytkeuGg4qImj7rStV1i2kxkz9B0KFheGtcadxmpYAo,2550
|
|
76
|
-
opencos/tools/riviera.py,sha256=
|
|
76
|
+
opencos/tools/riviera.py,sha256=_-vsN7TD6WdW4PVsSJaEhJls3RgXGRowhY_QV1hdFqE,13678
|
|
77
77
|
opencos/tools/slang.py,sha256=S_vODMT5Zl5vi9FMGHfahp5B0oMNyDIRJXtRAldVCwY,8625
|
|
78
78
|
opencos/tools/slang_yosys.py,sha256=MKh13eAmLJDkynZiezyT8E2gI4CKnXipzgFCZppaMXo,10230
|
|
79
79
|
opencos/tools/surelog.py,sha256=S2RAZJyjdISm_tRvAhXbla7_z_tJfotZih5f9Y3m7DQ,5648
|
|
80
80
|
opencos/tools/tabbycad_yosys.py,sha256=2LePPgYXBVdsy7YcffPIWN-I0B7queLQ_f_pme2SCGw,7803
|
|
81
|
-
opencos/tools/verilator.py,sha256=
|
|
82
|
-
opencos/tools/vivado.py,sha256=
|
|
81
|
+
opencos/tools/verilator.py,sha256=42SLAuqP2TmAE8pPgcLZBcXaALlvgc7z7v48FFvnCyo,24654
|
|
82
|
+
opencos/tools/vivado.py,sha256=I9yVFK5wnFl1268l8WCk5Xh8wZBlSCODVTg4qStVF8c,41475
|
|
83
83
|
opencos/tools/yosys.py,sha256=t3Au8gdwTepIKCPCXHpRXEdtmORQK8xqNvF6baIa7DM,28260
|
|
84
84
|
opencos/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
85
85
|
opencos/utils/markup_helpers.py,sha256=A8Ev5UJ4EVKjdcF2g85SQbjdPZR4jGpNqCLaBy_4v7Q,4569
|
|
86
86
|
opencos/utils/status_constants.py,sha256=na6YsqlsCwIYzTXWE14dPadUYRNTrOS6YTXHCer2NbA,635
|
|
87
|
-
opencos/utils/str_helpers.py,sha256
|
|
88
|
-
opencos/utils/subprocess_helpers.py,sha256=
|
|
87
|
+
opencos/utils/str_helpers.py,sha256=-hR7MAQLOoY2lIfqtxNtnzb3apeJPkh8shEGFzkwQfs,6637
|
|
88
|
+
opencos/utils/subprocess_helpers.py,sha256=idWc-sy_XJaxIl06tt_QjThYWoLL_Wmy7aLCpEo9y3c,5829
|
|
89
89
|
opencos/utils/vscode_helper.py,sha256=9nHyMUIL-gzfW-qLH06sgaCnVK-YTOtu6pusitNNhL8,1363
|
|
90
90
|
opencos/utils/vsim_helper.py,sha256=1johPOGbjbMgnCDSTpgsQcSuAquiqq1Y2MBxS6WY6b4,1552
|
|
91
|
-
opencos_eda-0.3.
|
|
92
|
-
opencos_eda-0.3.
|
|
93
|
-
opencos_eda-0.3.
|
|
94
|
-
opencos_eda-0.3.
|
|
95
|
-
opencos_eda-0.3.
|
|
96
|
-
opencos_eda-0.3.
|
|
97
|
-
opencos_eda-0.3.
|
|
91
|
+
opencos_eda-0.3.5.dist-info/licenses/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
|
|
92
|
+
opencos_eda-0.3.5.dist-info/licenses/LICENSE.spdx,sha256=8gn1610RMP6eFgT3Hm6q9VKXt0RvdTItL_oxMo72jII,189
|
|
93
|
+
opencos_eda-0.3.5.dist-info/METADATA,sha256=1H76gr0Vw6Cz16Kf3sbAQR2pMjBzxGLAHbdm2oSNEvI,1024
|
|
94
|
+
opencos_eda-0.3.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
95
|
+
opencos_eda-0.3.5.dist-info/entry_points.txt,sha256=6n1T5NwVYDhN5l1h5zmyT197G4pE0SySDreB0QJzJR0,218
|
|
96
|
+
opencos_eda-0.3.5.dist-info/top_level.txt,sha256=J4JDP-LpRyJqPNeh9bSjx6yrLz2Mk0h6un6YLmtqql4,8
|
|
97
|
+
opencos_eda-0.3.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|