opencos-eda 0.2.50__py3-none-any.whl → 0.2.51__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/commands/lec.py +7 -4
- opencos/commands/shell.py +11 -8
- opencos/commands/sim.py +38 -22
- opencos/deps/deps_file.py +67 -14
- opencos/eda.py +15 -3
- opencos/eda_base.py +39 -18
- opencos/eda_config.py +1 -0
- opencos/eda_config_defaults.yml +4 -14
- opencos/eda_deps_sanitize.py +73 -0
- opencos/tools/questa_fse.py +2 -0
- opencos/tools/yosys.py +21 -5
- opencos/util.py +28 -5
- opencos/utils/markup_helpers.py +31 -2
- opencos/utils/status_constants.py +27 -0
- opencos/utils/vsim_helper.py +55 -0
- {opencos_eda-0.2.50.dist-info → opencos_eda-0.2.51.dist-info}/METADATA +1 -1
- {opencos_eda-0.2.50.dist-info → opencos_eda-0.2.51.dist-info}/RECORD +22 -19
- {opencos_eda-0.2.50.dist-info → opencos_eda-0.2.51.dist-info}/entry_points.txt +1 -0
- {opencos_eda-0.2.50.dist-info → opencos_eda-0.2.51.dist-info}/WHEEL +0 -0
- {opencos_eda-0.2.50.dist-info → opencos_eda-0.2.51.dist-info}/licenses/LICENSE +0 -0
- {opencos_eda-0.2.50.dist-info → opencos_eda-0.2.51.dist-info}/licenses/LICENSE.spdx +0 -0
- {opencos_eda-0.2.50.dist-info → opencos_eda-0.2.51.dist-info}/top_level.txt +0 -0
opencos/commands/lec.py
CHANGED
|
@@ -69,16 +69,13 @@ class CommandLec(CommandDesign):
|
|
|
69
69
|
if self.status_any_error():
|
|
70
70
|
return unparsed
|
|
71
71
|
|
|
72
|
-
# add defines for this job type
|
|
73
|
-
if not self.args['top']:
|
|
74
|
-
self.args['top'] = 'eda.lec'
|
|
75
|
-
|
|
76
72
|
# we require there to be two --designs set.
|
|
77
73
|
if not self.args['designs'] and len(self.args['designs']) != 2:
|
|
78
74
|
self.error('Requires two designs via --designs=<target1> --designs=<target2>',
|
|
79
75
|
f'designs={self.args["designs"]}')
|
|
80
76
|
return []
|
|
81
77
|
|
|
78
|
+
|
|
82
79
|
# Before we do anything else, make sure the two designs actually exist.
|
|
83
80
|
for design in self.args['designs']:
|
|
84
81
|
# maybe it's a file?
|
|
@@ -89,6 +86,12 @@ class CommandLec(CommandDesign):
|
|
|
89
86
|
else:
|
|
90
87
|
self.error(f'--designs={design}, value is not a file or target')
|
|
91
88
|
|
|
89
|
+
if not self.args['top']:
|
|
90
|
+
# Correct the 'top' name so it's eda.<target1>.<target2>.lec (shortnames)
|
|
91
|
+
_, short_target1 = os.path.split(self.args['designs'][0])
|
|
92
|
+
_, short_target2 = os.path.split(self.args['designs'][1])
|
|
93
|
+
self.args['top'] = f'eda.{short_target1}.{short_target2}'
|
|
94
|
+
|
|
92
95
|
# create our work dir
|
|
93
96
|
self.create_work_dir()
|
|
94
97
|
self.run_dep_commands()
|
opencos/commands/shell.py
CHANGED
|
@@ -16,6 +16,8 @@ import os
|
|
|
16
16
|
from opencos import util, export_helper
|
|
17
17
|
from opencos.eda_base import CommandDesign
|
|
18
18
|
|
|
19
|
+
from opencos.utils import status_constants
|
|
20
|
+
|
|
19
21
|
|
|
20
22
|
class CommandShell(CommandDesign):
|
|
21
23
|
'''Base class command handler for: eda sim ...'''
|
|
@@ -181,7 +183,6 @@ class CommandShell(CommandDesign):
|
|
|
181
183
|
_must_strings.append(self.args['pass-pattern'])
|
|
182
184
|
|
|
183
185
|
if len(_bad_strings) > 0 or len(_must_strings) > 0:
|
|
184
|
-
hit_bad_string = False
|
|
185
186
|
hit_must_string_dict = dict.fromkeys(_must_strings)
|
|
186
187
|
fname = os.path.join(self.args['work-dir'], filename)
|
|
187
188
|
with open(fname, 'r', encoding='utf-8') as f:
|
|
@@ -191,12 +192,14 @@ class CommandShell(CommandDesign):
|
|
|
191
192
|
if k in line:
|
|
192
193
|
hit_must_string_dict[k] = True
|
|
193
194
|
if any(bad_str in line for bad_str in _bad_strings):
|
|
194
|
-
|
|
195
|
-
|
|
195
|
+
self.error(
|
|
196
|
+
f"log {fname}:{lineno} contains one of {_bad_strings=}",
|
|
197
|
+
error_code=status_constants.EDA_SHELL_LOG_HAS_BAD_STRING
|
|
198
|
+
)
|
|
196
199
|
|
|
197
|
-
if hit_bad_string:
|
|
198
|
-
self.status += 1
|
|
199
200
|
if any(x is None for x in hit_must_string_dict.values()):
|
|
200
|
-
self.error(
|
|
201
|
-
|
|
202
|
-
|
|
201
|
+
self.error(
|
|
202
|
+
f"Didn't get all passing patterns in log {fname}: {_must_strings=}",
|
|
203
|
+
f" {hit_must_string_dict=}",
|
|
204
|
+
error_code=status_constants.EDA_SHELL_LOG_MISSING_MUST_STRING
|
|
205
|
+
)
|
opencos/commands/sim.py
CHANGED
|
@@ -15,6 +15,7 @@ import os
|
|
|
15
15
|
|
|
16
16
|
from opencos import util, export_helper
|
|
17
17
|
from opencos.eda_base import CommandDesign, Tool
|
|
18
|
+
from opencos.utils import status_constants
|
|
18
19
|
|
|
19
20
|
class CommandSim(CommandDesign):
|
|
20
21
|
'''Base class command handler for: eda sim ...'''
|
|
@@ -170,11 +171,14 @@ class CommandSim(CommandDesign):
|
|
|
170
171
|
if log_filename:
|
|
171
172
|
log_fname = log_filename
|
|
172
173
|
|
|
173
|
-
|
|
174
|
+
_, stdout, _ = self.exec(
|
|
175
|
+
work_dir=self.args['work-dir'], command_list=clist, tee_fpath=tee_fpath
|
|
176
|
+
)
|
|
174
177
|
|
|
175
178
|
if check_logs and log_fname:
|
|
176
179
|
self.check_logs_for_errors(
|
|
177
|
-
filename=
|
|
180
|
+
filename='', file_contents_str=stdout,
|
|
181
|
+
bad_strings=bad_strings, must_strings=must_strings,
|
|
178
182
|
use_bad_strings=use_bad_strings, use_must_strings=use_must_strings
|
|
179
183
|
)
|
|
180
184
|
if log_fname:
|
|
@@ -273,8 +277,8 @@ class CommandSim(CommandDesign):
|
|
|
273
277
|
'''
|
|
274
278
|
return
|
|
275
279
|
|
|
276
|
-
def check_logs_for_errors( # pylint: disable=dangerous-default-value
|
|
277
|
-
self, filename: str,
|
|
280
|
+
def check_logs_for_errors( # pylint: disable=dangerous-default-value,too-many-locals,too-many-branches
|
|
281
|
+
self, filename: str = '', file_contents_str: str = '',
|
|
278
282
|
bad_strings: list = [], must_strings: list = [],
|
|
279
283
|
use_bad_strings: bool = True, use_must_strings: bool = True
|
|
280
284
|
) -> None:
|
|
@@ -294,26 +298,38 @@ class CommandSim(CommandDesign):
|
|
|
294
298
|
if self.args['pass-pattern'] != "":
|
|
295
299
|
_must_strings.append(self.args['pass-pattern'])
|
|
296
300
|
|
|
297
|
-
if len(_bad_strings)
|
|
298
|
-
|
|
299
|
-
|
|
301
|
+
if len(_bad_strings) == 0 and len(_must_strings) == 0:
|
|
302
|
+
return
|
|
303
|
+
|
|
304
|
+
hit_must_string_dict = dict.fromkeys(_must_strings)
|
|
305
|
+
|
|
306
|
+
lines = []
|
|
307
|
+
fname = ''
|
|
308
|
+
if file_contents_str:
|
|
309
|
+
lines = file_contents_str.split('\n')
|
|
310
|
+
elif filename:
|
|
300
311
|
fname = os.path.join(self.args['work-dir'], filename)
|
|
301
312
|
with open(fname, 'r', encoding='utf-8') as f:
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
313
|
+
lines = f.readl.splitlines()
|
|
314
|
+
|
|
315
|
+
if lines:
|
|
316
|
+
for lineno, line in enumerate(lines):
|
|
317
|
+
if any(must_str in line for must_str in _must_strings):
|
|
318
|
+
for k, _ in hit_must_string_dict.items():
|
|
319
|
+
if k in line:
|
|
320
|
+
hit_must_string_dict[k] = True
|
|
321
|
+
if any(bad_str in line for bad_str in _bad_strings):
|
|
322
|
+
self.error(
|
|
323
|
+
f"log {fname}:{lineno} contains one of {_bad_strings=}",
|
|
324
|
+
error_code=status_constants.EDA_SIM_LOG_HAS_BAD_STRING
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
if any(x is None for x in hit_must_string_dict.values()):
|
|
328
|
+
self.error(
|
|
329
|
+
f"Didn't get all passing patterns in log {fname}: {_must_strings=}",
|
|
330
|
+
f" {hit_must_string_dict=}",
|
|
331
|
+
error_code=status_constants.EDA_SIM_LOG_MISSING_MUST_STRING
|
|
332
|
+
)
|
|
317
333
|
|
|
318
334
|
# Methods that derived classes must override:
|
|
319
335
|
|
opencos/deps/deps_file.py
CHANGED
|
@@ -10,15 +10,20 @@ from pathlib import Path
|
|
|
10
10
|
|
|
11
11
|
import toml
|
|
12
12
|
|
|
13
|
+
from opencos import util
|
|
13
14
|
from opencos.deps.defaults import DEPS_FILE_EXTS, ROOT_TABLE_KEYS_NOT_TARGETS
|
|
14
15
|
from opencos.util import debug, error
|
|
15
|
-
from opencos.utils.markup_helpers import yaml_safe_load, toml_load_only_root_line_numbers
|
|
16
|
+
from opencos.utils.markup_helpers import yaml_safe_load, toml_load_only_root_line_numbers, \
|
|
17
|
+
markup_writer, markup_dumper
|
|
16
18
|
from opencos.utils.str_helpers import fnmatch_or_re, dep_str2list
|
|
17
19
|
from opencos.utils.subprocess_helpers import subprocess_run_background
|
|
20
|
+
from opencos.utils.status_constants import EDA_DEPS_FILE_NOT_FOUND, EDA_DEPS_TARGET_NOT_FOUND
|
|
18
21
|
|
|
19
22
|
|
|
20
23
|
def deps_data_get_all_targets(data: dict) -> list:
|
|
21
24
|
'''Given extracted DEPS data (dict) get all the root level keys that aren't defaults'''
|
|
25
|
+
if data is None:
|
|
26
|
+
return []
|
|
22
27
|
return [x for x in data.keys() if x not in ROOT_TABLE_KEYS_NOT_TARGETS]
|
|
23
28
|
|
|
24
29
|
|
|
@@ -166,32 +171,43 @@ def deps_target_get_deps_list(
|
|
|
166
171
|
ret = []
|
|
167
172
|
for dep in deps:
|
|
168
173
|
if isinstance(dep, str):
|
|
169
|
-
if dep.startswith('#')
|
|
174
|
+
if not dep or dep.startswith('#'):
|
|
170
175
|
continue
|
|
171
176
|
ret.append(dep)
|
|
172
177
|
return ret
|
|
173
178
|
|
|
174
179
|
|
|
175
180
|
def deps_list_target_sanitize(
|
|
176
|
-
entry, default_key: str = 'deps', target_node: str = '', deps_file: str = ''
|
|
181
|
+
entry, default_key: str = 'deps', target_node: str = '', deps_file: str = '',
|
|
182
|
+
entry_fix_deps_key: bool = True
|
|
177
183
|
) -> dict:
|
|
178
184
|
'''Returns a sanitized DEPS markup table entry (dict --> dict)
|
|
179
185
|
|
|
180
186
|
Since we support target entries that can be dict, list, or str(), sanitize
|
|
181
187
|
them so they are a dict, with a key named 'deps' that has a list of deps.
|
|
182
188
|
'''
|
|
189
|
+
ret = None
|
|
183
190
|
if isinstance(entry, dict):
|
|
184
|
-
|
|
191
|
+
ret = entry
|
|
185
192
|
|
|
186
193
|
if isinstance(entry, str):
|
|
187
194
|
mylist = dep_str2list(entry) # convert str to list
|
|
188
|
-
|
|
195
|
+
ret = {default_key: mylist}
|
|
189
196
|
|
|
190
197
|
if isinstance(entry, list):
|
|
191
198
|
# it's already a list
|
|
192
|
-
|
|
199
|
+
ret = {default_key: entry}
|
|
200
|
+
|
|
201
|
+
if ret is not None:
|
|
202
|
+
if entry_fix_deps_key and 'deps' in ret:
|
|
203
|
+
ret['deps'] = deps_target_get_deps_list(
|
|
204
|
+
entry=ret, default_key='deps', deps_file=deps_file,
|
|
205
|
+
entry_must_have_default_key=True
|
|
206
|
+
)
|
|
207
|
+
else:
|
|
208
|
+
assert False, f"Can't convert to list {entry=} {default_key=} {target_node=} {deps_file=}"
|
|
193
209
|
|
|
194
|
-
|
|
210
|
+
return ret
|
|
195
211
|
|
|
196
212
|
|
|
197
213
|
class DepsFile:
|
|
@@ -230,7 +246,10 @@ class DepsFile:
|
|
|
230
246
|
if deps_path and os.path.exists(deps_path):
|
|
231
247
|
self.rel_deps_file = os.path.join(os.path.relpath(deps_path), deps_leaf)
|
|
232
248
|
|
|
233
|
-
self.error = command_design_ref
|
|
249
|
+
self.error = getattr(command_design_ref, 'error', None)
|
|
250
|
+
if not self.error:
|
|
251
|
+
self.error = util.error
|
|
252
|
+
|
|
234
253
|
|
|
235
254
|
def found(self) -> bool:
|
|
236
255
|
'''Returns true if this DEPS file exists and extracted non-empty data'''
|
|
@@ -270,15 +289,18 @@ class DepsFile:
|
|
|
270
289
|
if '.' in target_node:
|
|
271
290
|
# Likely a filename (target_node does not include path)
|
|
272
291
|
self.error(f'Trying to resolve command-line target={t_full} (file?):',
|
|
273
|
-
f'File={t_node} not found in directory={t_path}'
|
|
292
|
+
f'File={t_node} not found in directory={t_path}',
|
|
293
|
+
error_code=EDA_DEPS_FILE_NOT_FOUND)
|
|
274
294
|
elif not self.rel_deps_file:
|
|
275
295
|
# target, but there's no DEPS file
|
|
276
296
|
self.error(f'Trying to resolve command-line target={t_full}:',
|
|
277
|
-
f'but path {t_path} has no DEPS markup file (DEPS.yml)'
|
|
297
|
+
f'but path {t_path} has no DEPS markup file (DEPS.yml)',
|
|
298
|
+
error_code=EDA_DEPS_FILE_NOT_FOUND)
|
|
278
299
|
else:
|
|
279
300
|
self.error(f'Trying to resolve command-line target={t_full}:',
|
|
280
301
|
f'was not found in deps_file={self.rel_deps_file},',
|
|
281
|
-
f'possible targets in deps file = {list(self.data.keys())}'
|
|
302
|
+
f'possible targets in deps file = {list(self.data.keys())}',
|
|
303
|
+
error_code=EDA_DEPS_TARGET_NOT_FOUND)
|
|
282
304
|
else:
|
|
283
305
|
# If we have caller_info, then this was a recursive call from another
|
|
284
306
|
# DEPS file. It should already have the useful error messaging:
|
|
@@ -287,16 +309,19 @@ class DepsFile:
|
|
|
287
309
|
# Likely a filename (target_node does not include path)
|
|
288
310
|
self.error(f'Trying to resolve target={t_full} (file?):',
|
|
289
311
|
f'called from {caller_info},',
|
|
290
|
-
f'File={t_node} not found in directory={t_path}'
|
|
312
|
+
f'File={t_node} not found in directory={t_path}',
|
|
313
|
+
error_code=EDA_DEPS_FILE_NOT_FOUND)
|
|
291
314
|
elif not self.rel_deps_file:
|
|
292
315
|
# target, but there's no DEPS file
|
|
293
316
|
self.error(f'Trying to resolve target={t_full}:',
|
|
294
317
|
f'called from {caller_info},',
|
|
295
|
-
f'but {t_path} has no DEPS markup file (DEPS.yml)'
|
|
318
|
+
f'but {t_path} has no DEPS markup file (DEPS.yml)',
|
|
319
|
+
error_code=EDA_DEPS_FILE_NOT_FOUND)
|
|
296
320
|
else:
|
|
297
321
|
self.error(f'Trying to resolve target={t_full}:',
|
|
298
322
|
f'called from {caller_info},',
|
|
299
|
-
f'Target not found in deps_file={self.rel_deps_file}'
|
|
323
|
+
f'Target not found in deps_file={self.rel_deps_file}',
|
|
324
|
+
error_code=EDA_DEPS_TARGET_NOT_FOUND)
|
|
300
325
|
else:
|
|
301
326
|
debug(f'Found {target_node=} in deps_file={self.rel_deps_file}')
|
|
302
327
|
found_target = True
|
|
@@ -324,3 +349,31 @@ class DepsFile:
|
|
|
324
349
|
entry.update(entry_sanitized)
|
|
325
350
|
|
|
326
351
|
return entry
|
|
352
|
+
|
|
353
|
+
def get_all_targets(self) -> list:
|
|
354
|
+
'''Returns list of all targets in this obj, skipping keys like DEFAULTS and METADATA'''
|
|
355
|
+
return deps_data_get_all_targets(self.data)
|
|
356
|
+
|
|
357
|
+
def get_sanitized_data(self) -> dict:
|
|
358
|
+
'''Returns a sanitized dict of self.data, resolves DEFAULTS, METADATA, and implicit
|
|
359
|
+
|
|
360
|
+
space-separated-str, lists for 'deps' in a target entry
|
|
361
|
+
'''
|
|
362
|
+
new_data = {}
|
|
363
|
+
targets = self.get_all_targets()
|
|
364
|
+
for target in targets:
|
|
365
|
+
new_data[target] = self.get_entry(target)
|
|
366
|
+
return new_data
|
|
367
|
+
|
|
368
|
+
def write_sanitized_markup(self, filepath: str) -> None:
|
|
369
|
+
'''Writes the sanitized dict of self.data as JSON/YAML (filepath extension)'''
|
|
370
|
+
markup_writer(data=self.get_sanitized_data(), filepath=filepath)
|
|
371
|
+
|
|
372
|
+
def str_sanitized_markup(self, as_yaml: bool = False) -> str:
|
|
373
|
+
'''Returns str sanitized YAML or JSON str of self.data'''
|
|
374
|
+
return markup_dumper(self.get_sanitized_data(), as_yaml=as_yaml)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def print_sanitized_markup(self, as_yaml: bool = False) -> None:
|
|
378
|
+
'''Prints sanitized JSON str of self.data to STDOUT'''
|
|
379
|
+
print(self.str_sanitized_markup(as_yaml=as_yaml))
|
opencos/eda.py
CHANGED
|
@@ -16,9 +16,12 @@ import argparse
|
|
|
16
16
|
import shlex
|
|
17
17
|
import importlib.util
|
|
18
18
|
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
19
21
|
import opencos
|
|
20
22
|
from opencos import util, eda_config, eda_base
|
|
21
23
|
from opencos.eda_base import Tool, which_tool
|
|
24
|
+
from opencos.utils import vsim_helper
|
|
22
25
|
|
|
23
26
|
# Configure util:
|
|
24
27
|
util.progname = "EDA"
|
|
@@ -151,6 +154,7 @@ def auto_tool_setup( # pylint: disable=too-many-locals,too-many-branches,too-man
|
|
|
151
154
|
If so, updates config['auto_tools_order'][tool]['exe']
|
|
152
155
|
'''
|
|
153
156
|
|
|
157
|
+
|
|
154
158
|
tool = eda_config.update_config_auto_tool_order_for_tool(
|
|
155
159
|
tool=tool, config=config
|
|
156
160
|
)
|
|
@@ -192,16 +196,23 @@ def auto_tool_setup( # pylint: disable=too-many-locals,too-many-branches,too-man
|
|
|
192
196
|
has_all_exe = True
|
|
193
197
|
has_all_in_exe_path = True
|
|
194
198
|
for exe in exe_list:
|
|
195
|
-
assert exe != '', f'{
|
|
199
|
+
assert exe != '', f'{name=} {value=} value missing "exe" {exe=}'
|
|
196
200
|
p = shutil.which(exe)
|
|
197
201
|
if not p:
|
|
198
202
|
has_all_exe = False
|
|
199
203
|
util.debug(f"... No, missing exe {exe}")
|
|
200
204
|
for req in value.get('requires_in_exe_path', []):
|
|
201
|
-
if p and req and str(req)
|
|
205
|
+
if p and req and str(Path(req)) not in str(Path(p)):
|
|
202
206
|
has_all_in_exe_path = False
|
|
203
207
|
util.debug(f"... No, missing path requirement {req}")
|
|
204
208
|
|
|
209
|
+
has_vsim_helper = True
|
|
210
|
+
if value.get('requires_vsim_helper', False):
|
|
211
|
+
# This tool name must be in opencos.utils.vsim_helper.TOOL_IS[name].
|
|
212
|
+
# Special case for vsim being used by a lot of tools.
|
|
213
|
+
vsim_helper.init() # only runs checks once internally
|
|
214
|
+
has_vsim_helper = vsim_helper.TOOL_IS.get(name, False)
|
|
215
|
+
|
|
205
216
|
if has_all_exe:
|
|
206
217
|
requires_cmd_list = value.get('requires_cmd', [])
|
|
207
218
|
for cmd in requires_cmd_list:
|
|
@@ -219,7 +230,8 @@ def auto_tool_setup( # pylint: disable=too-many-locals,too-many-branches,too-man
|
|
|
219
230
|
util.debug(f"... No, exception {e} running {cmd_list}")
|
|
220
231
|
|
|
221
232
|
|
|
222
|
-
if all([has_all_py, has_all_env, has_all_exe, has_all_in_exe_path
|
|
233
|
+
if all([has_all_py, has_all_env, has_all_exe, has_all_in_exe_path,
|
|
234
|
+
has_vsim_helper]):
|
|
223
235
|
exe = exe_list[0]
|
|
224
236
|
p = shutil.which(exe)
|
|
225
237
|
config['auto_tools_found'][name] = exe # populate key-value pairs w/ first exe in list
|
opencos/eda_base.py
CHANGED
|
@@ -28,6 +28,7 @@ from opencos.util import Colors
|
|
|
28
28
|
from opencos.utils.str_helpers import sprint_time, strip_outer_quotes, string_or_space, \
|
|
29
29
|
indent_wrap_long_text
|
|
30
30
|
from opencos.utils.subprocess_helpers import subprocess_run_background
|
|
31
|
+
from opencos.utils import status_constants
|
|
31
32
|
|
|
32
33
|
from opencos.deps.deps_file import DepsFile, deps_data_get_all_targets
|
|
33
34
|
from opencos.deps.deps_processor import DepsProcessor
|
|
@@ -274,7 +275,7 @@ class Command:
|
|
|
274
275
|
file=self.errors_log_f
|
|
275
276
|
)
|
|
276
277
|
|
|
277
|
-
self.status = util.error(*args, **kwargs)
|
|
278
|
+
self.status = util.error(*args, **kwargs) # error_code passed and returned via kwargs
|
|
278
279
|
|
|
279
280
|
def status_any_error(self, report=True) -> bool:
|
|
280
281
|
'''Used by derived classes process_tokens() to know an error was reached
|
|
@@ -416,10 +417,16 @@ class Command:
|
|
|
416
417
|
shell=shell
|
|
417
418
|
)
|
|
418
419
|
|
|
419
|
-
if return_code:
|
|
420
|
-
|
|
420
|
+
if return_code > 0:
|
|
421
|
+
if return_code == 1:
|
|
422
|
+
self.status = status_constants.EDA_EXEC_NONZERO_RETURN_CODE1
|
|
423
|
+
if return_code == 255:
|
|
424
|
+
self.status = status_constants.EDA_EXEC_NONZERO_RETURN_CODE255
|
|
425
|
+
else:
|
|
426
|
+
self.status = status_constants.EDA_EXEC_NONZERO_RETURN_CODE2
|
|
421
427
|
if stop_on_error:
|
|
422
|
-
self.error(f"exec: returned with error (return code: {return_code})"
|
|
428
|
+
self.error(f"exec: returned with error (return code: {return_code})",
|
|
429
|
+
error_code=self.status)
|
|
423
430
|
else:
|
|
424
431
|
util.debug(f"exec: returned with error (return code: {return_code})")
|
|
425
432
|
else:
|
|
@@ -437,7 +444,8 @@ class Command:
|
|
|
437
444
|
# Do some minimal type handling, preserving the type(self.args[key])
|
|
438
445
|
if key not in self.args:
|
|
439
446
|
self.error(f'set_arg, {key=} not in self.args {value=}',
|
|
440
|
-
f'({self.command_name=}, {self.__class__.__name__=})'
|
|
447
|
+
f'({self.command_name=}, {self.__class__.__name__=})',
|
|
448
|
+
error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
|
|
441
449
|
|
|
442
450
|
cur_value = self.args[key]
|
|
443
451
|
|
|
@@ -568,7 +576,8 @@ class Command:
|
|
|
568
576
|
parsed, unparsed = parser.parse_known_args(tokens + [''])
|
|
569
577
|
unparsed = list(filter(None, unparsed))
|
|
570
578
|
except argparse.ArgumentError:
|
|
571
|
-
self.error(f'problem {self.command_name=} attempting to parse_known_args for {tokens=}'
|
|
579
|
+
self.error(f'problem {self.command_name=} attempting to parse_known_args for {tokens=}',
|
|
580
|
+
error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
|
|
572
581
|
|
|
573
582
|
parsed_as_dict = vars(parsed)
|
|
574
583
|
|
|
@@ -630,7 +639,8 @@ class Command:
|
|
|
630
639
|
_, unparsed = self.run_argparser_on_list(tokens)
|
|
631
640
|
if process_all and len(unparsed) > 0:
|
|
632
641
|
self.error(f"Didn't understand argument: '{unparsed=}' in",
|
|
633
|
-
f" {self.command_name=} context, {pwd=}"
|
|
642
|
+
f" {self.command_name=} context, {pwd=}",
|
|
643
|
+
error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
|
|
634
644
|
|
|
635
645
|
return unparsed
|
|
636
646
|
|
|
@@ -650,7 +660,8 @@ class Command:
|
|
|
650
660
|
|
|
651
661
|
if not ret and error_if_no_command:
|
|
652
662
|
self.error(f"Looking for a valid eda {self.command_name} <command>",
|
|
653
|
-
f"but didn't find one in {tokens=}"
|
|
663
|
+
f"but didn't find one in {tokens=}",
|
|
664
|
+
error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
|
|
654
665
|
return ret
|
|
655
666
|
|
|
656
667
|
|
|
@@ -692,7 +703,8 @@ class Command:
|
|
|
692
703
|
process_tokens(..) is the starting entrypoint from eda.py'''
|
|
693
704
|
self.write_eda_config_and_args()
|
|
694
705
|
self.error(f"No tool bound to command '{self.command_name}', you",
|
|
695
|
-
" probably need to setup tool, or use '--tool <name>'"
|
|
706
|
+
" probably need to setup tool, or use '--tool <name>'",
|
|
707
|
+
error_code=status_constants.EDA_CONFIG_ERROR)
|
|
696
708
|
|
|
697
709
|
def command_safe_set_tool_defines(self) -> None:
|
|
698
710
|
'''Safe wrapper for calling self.set_tool_defines() in case a Tool parent class is
|
|
@@ -1006,7 +1018,8 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
|
1006
1018
|
self.defines[k] = v
|
|
1007
1019
|
util.debug(f"Defined {k}={v}")
|
|
1008
1020
|
return None
|
|
1009
|
-
self.error(f"Didn't understand +define+: '{plusarg}'"
|
|
1021
|
+
self.error(f"Didn't understand +define+: '{plusarg}'",
|
|
1022
|
+
error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
|
|
1010
1023
|
return None
|
|
1011
1024
|
|
|
1012
1025
|
if plusarg.startswith('+incdir+'):
|
|
@@ -1018,7 +1031,8 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
|
1018
1031
|
self.incdirs.append(os.path.abspath(incdir))
|
|
1019
1032
|
util.debug(f"Added include dir '{os.path.abspath(incdir)}'")
|
|
1020
1033
|
return None
|
|
1021
|
-
self.error(f"Didn't understand +incdir+: '{plusarg}'"
|
|
1034
|
+
self.error(f"Didn't understand +incdir+: '{plusarg}'",
|
|
1035
|
+
error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
|
|
1022
1036
|
return None
|
|
1023
1037
|
|
|
1024
1038
|
# remaining plusargs as stored in self.args['unprocessed-plusargs'] (list)
|
|
@@ -1032,7 +1046,8 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
|
1032
1046
|
# derived classes have the option to handle it
|
|
1033
1047
|
return plusarg
|
|
1034
1048
|
|
|
1035
|
-
self.error(f"Didn't understand +plusarg: '{plusarg}'"
|
|
1049
|
+
self.error(f"Didn't understand +plusarg: '{plusarg}'",
|
|
1050
|
+
error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
|
|
1036
1051
|
return None
|
|
1037
1052
|
|
|
1038
1053
|
|
|
@@ -1224,7 +1239,8 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
|
1224
1239
|
found_target = True
|
|
1225
1240
|
break # move on to the next target
|
|
1226
1241
|
if not found_target: # if STILL not found_this_target...
|
|
1227
|
-
self.error(f"Unable to resolve {target=}"
|
|
1242
|
+
self.error(f"Unable to resolve {target=}",
|
|
1243
|
+
error_code=status_constants.EDA_DEPS_TARGET_NOT_FOUND)
|
|
1228
1244
|
|
|
1229
1245
|
# if we've found any target since being called, it means we found the one we were called for
|
|
1230
1246
|
return found_target
|
|
@@ -1354,7 +1370,8 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
|
1354
1370
|
if len(unparsed) == 0:
|
|
1355
1371
|
# If unparsed is still empty, then error.
|
|
1356
1372
|
self.error(f"For command '{self.command_name}' no files or targets were",
|
|
1357
|
-
f"presented at the command line: {orig_tokens}"
|
|
1373
|
+
f"presented at the command line: {orig_tokens}",
|
|
1374
|
+
error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
|
|
1358
1375
|
|
|
1359
1376
|
# by this point hopefully this is a target ... is it a simple filename?
|
|
1360
1377
|
remove_list = []
|
|
@@ -1408,7 +1425,8 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
|
1408
1425
|
|
|
1409
1426
|
# we were unable to figure out what this command line token is for...
|
|
1410
1427
|
if process_all and len(unparsed) > 0:
|
|
1411
|
-
self.error(f"Didn't understand command remaining tokens {unparsed=} in CommandDesign"
|
|
1428
|
+
self.error(f"Didn't understand command remaining tokens {unparsed=} in CommandDesign",
|
|
1429
|
+
error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
|
|
1412
1430
|
|
|
1413
1431
|
# handle a missing self.args['top'] with last filepath or last target:
|
|
1414
1432
|
if not self.args.get('top', ''):
|
|
@@ -1441,7 +1459,8 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
|
1441
1459
|
|
|
1442
1460
|
if self.error_on_missing_top and not self.args.get('top', ''):
|
|
1443
1461
|
self.error("Did not get a --top or DEPS top, required to run command",
|
|
1444
|
-
f"'{self.command_name}' for tool={self.args.get('tool', None)}"
|
|
1462
|
+
f"'{self.command_name}' for tool={self.args.get('tool', None)}",
|
|
1463
|
+
error_code=status_constants.EDA_COMMAND_MISSING_TOP)
|
|
1445
1464
|
|
|
1446
1465
|
return unparsed
|
|
1447
1466
|
|
|
@@ -1943,7 +1962,8 @@ class CommandParallel(Command):
|
|
|
1943
1962
|
bad_remaining_args = [x for x in single_cmd_unparsed if x.startswith('-')]
|
|
1944
1963
|
if bad_remaining_args:
|
|
1945
1964
|
self.error(f'for {self.command_name} {command=} the following args are unknown',
|
|
1946
|
-
f'{bad_remaining_args}'
|
|
1965
|
+
f'{bad_remaining_args}',
|
|
1966
|
+
error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
|
|
1947
1967
|
|
|
1948
1968
|
# Remove unparsed args starting with '+', since those are commonly sent downstream to
|
|
1949
1969
|
# single job (example, CommandSim plusargs).
|
|
@@ -1985,7 +2005,8 @@ class CommandParallel(Command):
|
|
|
1985
2005
|
|
|
1986
2006
|
key = get_job_arg(job_dict, arg_name='job-name')
|
|
1987
2007
|
if not key:
|
|
1988
|
-
self.error(f'{job_dict=} needs to have a --job-name= arg attached'
|
|
2008
|
+
self.error(f'{job_dict=} needs to have a --job-name= arg attached',
|
|
2009
|
+
error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
|
|
1989
2010
|
if key not in job_names_count_dict:
|
|
1990
2011
|
job_names_count_dict[key] = 1
|
|
1991
2012
|
else:
|
opencos/eda_config.py
CHANGED
opencos/eda_config_defaults.yml
CHANGED
|
@@ -398,9 +398,7 @@ auto_tools_order:
|
|
|
398
398
|
|
|
399
399
|
questa:
|
|
400
400
|
exe: qrun
|
|
401
|
-
|
|
402
|
-
- qrun -version
|
|
403
|
-
- vsim -version
|
|
401
|
+
requires_vsim_helper: True
|
|
404
402
|
handlers:
|
|
405
403
|
elab: opencos.tools.queta.CommandElabQuesta
|
|
406
404
|
sim: opencos.tools.queta.CommandSimQuesta
|
|
@@ -408,30 +406,22 @@ auto_tools_order:
|
|
|
408
406
|
riviera:
|
|
409
407
|
exe: vsim
|
|
410
408
|
requires_cmd:
|
|
411
|
-
- vsim -version
|
|
412
409
|
- which riviera # Do not run it, make sure it's in PATH
|
|
413
|
-
|
|
414
|
-
- riviera
|
|
410
|
+
requires_vsim_helper: True
|
|
415
411
|
handlers:
|
|
416
412
|
elab: opencos.tools.riviera.CommandElabRiviera
|
|
417
413
|
sim: opencos.tools.riviera.CommandSimRiviera
|
|
418
414
|
|
|
419
415
|
modelsim_ase:
|
|
420
416
|
exe: vsim
|
|
421
|
-
|
|
422
|
-
- vsim -version
|
|
423
|
-
requires_in_exe_path:
|
|
424
|
-
- modelsim
|
|
417
|
+
requires_vsim_helper: True
|
|
425
418
|
handlers:
|
|
426
419
|
elab: opencos.tools.modelsim_ase.CommandElabModelsimAse
|
|
427
420
|
sim: opencos.tools.modelsim_ase.CommandSimModelsimAse
|
|
428
421
|
|
|
429
422
|
questa_fse: # free student edition, works similar to modelsim_ase
|
|
430
423
|
exe: vsim
|
|
431
|
-
|
|
432
|
-
- vsim -version
|
|
433
|
-
requires_in_exe_path:
|
|
434
|
-
- questa_fse
|
|
424
|
+
requires_vsim_helper: True
|
|
435
425
|
handlers:
|
|
436
426
|
elab: opencos.tools.questa_fse.CommandElabQuestaFse
|
|
437
427
|
sim: opencos.tools.questa_fse.CommandSimQuestaFse
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
'''opencos.eda_deps_sanitize is an executable script
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
eda_deps_sanitize --dir=<path>
|
|
7
|
+
|
|
8
|
+
Will print santized JSON data for the DEPS file found in --dir=<path>
|
|
9
|
+
'''
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
from opencos import util
|
|
18
|
+
from opencos.deps import deps_file
|
|
19
|
+
from opencos.utils import status_constants
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def run(*args) -> (int, str):
|
|
23
|
+
'''Runs the DEPS sanitizer, prints results to stdout'''
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
bool_kwargs = util.get_argparse_bool_action_kwargs()
|
|
27
|
+
|
|
28
|
+
parser = argparse.ArgumentParser(
|
|
29
|
+
prog='opencos eda_deps_sanitize', add_help=True, allow_abbrev=False
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
parser.add_argument('--yaml', **bool_kwargs,
|
|
33
|
+
help='Print output as YAML text, otherwise default is JSON text')
|
|
34
|
+
parser.add_argument('dir', type=str, default=str(Path('.')),
|
|
35
|
+
help='Directory to look for DEPS.[markup] file')
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
parsed, unparsed = parser.parse_known_args(list(args) + [''])
|
|
39
|
+
unparsed = list(filter(None, unparsed))
|
|
40
|
+
except argparse.ArgumentError:
|
|
41
|
+
return 1, f'problem attempting to parse_known_args for {args=}'
|
|
42
|
+
|
|
43
|
+
deps_path = parsed.dir
|
|
44
|
+
if os.path.isfile(deps_path):
|
|
45
|
+
deps_path, _ = os.path.split(deps_path)
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
my_depsfile_obj = deps_file.DepsFile(None, deps_path, {})
|
|
49
|
+
if not my_depsfile_obj.deps_file:
|
|
50
|
+
return status_constants.EDA_DEPS_FILE_NOT_FOUND, f'No DEPS markup file at {parsed.dir}'
|
|
51
|
+
|
|
52
|
+
ret_str = my_depsfile_obj.str_sanitized_markup(as_yaml=parsed.yaml)
|
|
53
|
+
rc = util.get_return_code()
|
|
54
|
+
return rc, ret_str
|
|
55
|
+
except Exception as e:
|
|
56
|
+
rc = 1, str(e)
|
|
57
|
+
|
|
58
|
+
return 0, ''
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def main() -> None:
|
|
62
|
+
'''calls sys.exit(), main entrypoint'''
|
|
63
|
+
args = []
|
|
64
|
+
if len(sys.argv) > 1 and not args:
|
|
65
|
+
args = sys.argv[1:]
|
|
66
|
+
|
|
67
|
+
rc, deps_str = run(*args)
|
|
68
|
+
print(deps_str)
|
|
69
|
+
sys.exit(rc)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
if __name__ == '__main__':
|
|
73
|
+
main()
|
opencos/tools/questa_fse.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
''' opencos.tools.questa_fse - Used by opencos.eda for sim/elab commands w/ --tool=questa_fse.
|
|
2
2
|
|
|
3
3
|
Contains classes for CommandSimQuestaFse, CommandElabQuestaFse.
|
|
4
|
+
For: Questa Intel Starter FPGA Edition-64 vsim 20XX.X Simulator
|
|
5
|
+
|
|
4
6
|
'''
|
|
5
7
|
|
|
6
8
|
# pylint: disable=R0801 # (duplicate code in derived classes, such as if-condition return.)
|
opencos/tools/yosys.py
CHANGED
|
@@ -117,6 +117,7 @@ class CommonSynthYosys(CommandSynth, ToolYosys):
|
|
|
117
117
|
'yosys-blackbox': [], # list of modules that yosys will blackbox.
|
|
118
118
|
'yosys-scriptfile': [],
|
|
119
119
|
'sta-scriptfile': [],
|
|
120
|
+
'rename-module': ''
|
|
120
121
|
})
|
|
121
122
|
self.args_help.update({
|
|
122
123
|
'sta': (
|
|
@@ -150,12 +151,14 @@ class CommonSynthYosys(CommandSynth, ToolYosys):
|
|
|
150
151
|
' via: sta -no_init -exit <this-arg>. You can set multiple args for multiple'
|
|
151
152
|
' scriptfile (appends)'
|
|
152
153
|
),
|
|
154
|
+
'rename-module': 'Renames the output .v and module name',
|
|
153
155
|
})
|
|
154
156
|
|
|
155
157
|
self.yosys_out_dir = ''
|
|
156
158
|
self.yosys_v_path = ''
|
|
157
159
|
self.full_work_dir = ''
|
|
158
160
|
self.blackbox_list = []
|
|
161
|
+
self.top_module = ''
|
|
159
162
|
|
|
160
163
|
def do_it(self) -> None:
|
|
161
164
|
self.set_tool_defines()
|
|
@@ -333,6 +336,13 @@ class CommonSynthYosys(CommandSynth, ToolYosys):
|
|
|
333
336
|
]
|
|
334
337
|
lines += [
|
|
335
338
|
'opt_clean',
|
|
339
|
+
]
|
|
340
|
+
if self.args['rename-module']:
|
|
341
|
+
lines += [f'rename {self.args["top"]} {self.args["rename-module"]}']
|
|
342
|
+
self.top_module = self.args['rename-module']
|
|
343
|
+
else:
|
|
344
|
+
self.top_module = self.args["top"]
|
|
345
|
+
lines += [
|
|
336
346
|
f'write_verilog {self.yosys_v_path}',
|
|
337
347
|
f'write_json {self.yosys_v_path}.json',
|
|
338
348
|
]
|
|
@@ -388,7 +398,7 @@ class CommonSynthYosys(CommandSynth, ToolYosys):
|
|
|
388
398
|
lines = [
|
|
389
399
|
'read_liberty ' + self.args['liberty-file'],
|
|
390
400
|
'read_verilog ' + self.yosys_v_path,
|
|
391
|
-
'link_design ' + self.
|
|
401
|
+
'link_design ' + self.top_module,
|
|
392
402
|
]
|
|
393
403
|
for _file in self.files_sdc:
|
|
394
404
|
lines.append('read_sdc ' + _file)
|
|
@@ -505,12 +515,13 @@ class CommandLecYosys(CommandLec, ToolYosys):
|
|
|
505
515
|
})
|
|
506
516
|
|
|
507
517
|
self.synth_work_dirs = [
|
|
508
|
-
os.path.join('eda.work', 'lec.
|
|
509
|
-
os.path.join('eda.work', 'lec.
|
|
518
|
+
os.path.join('eda.work', 'lec.Design1.synth'),
|
|
519
|
+
os.path.join('eda.work', 'lec.Design2.synth')
|
|
510
520
|
]
|
|
511
521
|
|
|
512
522
|
self.synth_designs_tops = [None, None]
|
|
513
523
|
self.synth_designs_fpaths = [None, None]
|
|
524
|
+
self.synth_design_top_module_names = [None, None]
|
|
514
525
|
|
|
515
526
|
def get_synth_result_fpath(self, target: str) -> str:
|
|
516
527
|
'''Overridden from CommandLec'''
|
|
@@ -534,9 +545,12 @@ class CommandLecYosys(CommandLec, ToolYosys):
|
|
|
534
545
|
|
|
535
546
|
synth_cmd_list += [
|
|
536
547
|
'--work-dir=' + self.synth_work_dirs[design_num],
|
|
537
|
-
self.args['designs'][design_num]
|
|
548
|
+
self.args['designs'][design_num],
|
|
549
|
+
f'--rename-module=Design{design_num + 1}'
|
|
538
550
|
]
|
|
539
551
|
|
|
552
|
+
self.synth_design_top_module_names[design_num] = f'Design{design_num + 1}'
|
|
553
|
+
|
|
540
554
|
return synth_cmd_list
|
|
541
555
|
|
|
542
556
|
|
|
@@ -663,10 +677,12 @@ class CommandLecYosys(CommandLec, ToolYosys):
|
|
|
663
677
|
else:
|
|
664
678
|
self.error(f' --pre-read-verilog file {x} does not exist')
|
|
665
679
|
lec_cmd_f_list += [
|
|
680
|
+
'# Design1 (module):',
|
|
666
681
|
f'read_verilog -sv -icells {self.synth_designs_fpaths[0]}',
|
|
682
|
+
'# Design2 (module):',
|
|
667
683
|
f'read_verilog -sv -icells {self.synth_designs_fpaths[1]}',
|
|
668
684
|
'clk2fflogic;',
|
|
669
|
-
f'miter -equiv -flatten {" ".join(self.
|
|
685
|
+
f'miter -equiv -flatten {" ".join(self.synth_design_top_module_names)} miter',
|
|
670
686
|
('sat -seq 50 -verify -prove trigger 0 -show-all -show-inputs -show-outputs'
|
|
671
687
|
' -set-init-zero miter'),
|
|
672
688
|
]
|
opencos/util.py
CHANGED
|
@@ -16,6 +16,8 @@ from enum import Enum
|
|
|
16
16
|
from pathlib import Path
|
|
17
17
|
from importlib import import_module
|
|
18
18
|
|
|
19
|
+
from opencos.utils import status_constants
|
|
20
|
+
|
|
19
21
|
global_exit_allowed = False # pylint: disable=invalid-name
|
|
20
22
|
progname = "UNKNOWN" # pylint: disable=invalid-name
|
|
21
23
|
progname_in_message = True # pylint: disable=invalid-name
|
|
@@ -32,6 +34,8 @@ args = { # pylint: disable=invalid-name
|
|
|
32
34
|
'artifacts-json': True,
|
|
33
35
|
}
|
|
34
36
|
|
|
37
|
+
max_error_code = 0 # pylint: disable=invalid-name
|
|
38
|
+
|
|
35
39
|
class Colors:
|
|
36
40
|
'''Namespace class for color printing help'''
|
|
37
41
|
red = "\x1B[31m"
|
|
@@ -191,17 +195,20 @@ class Artifacts:
|
|
|
191
195
|
return # do nothing if we have no artifacts
|
|
192
196
|
|
|
193
197
|
# Update all file sizes:
|
|
198
|
+
remove_keys = set()
|
|
194
199
|
for key, entry in self.data.items():
|
|
195
200
|
if os.path.isfile(entry['name']):
|
|
196
201
|
entry['size'] = os.path.getsize(entry['name'])
|
|
197
202
|
else:
|
|
198
203
|
# file doesn't exist, remove it from artifacts.
|
|
199
204
|
warning(f'Removing {key} ({entry["name"]}) from artifacts (file does not exist)')
|
|
200
|
-
|
|
205
|
+
remove_keys.add(key)
|
|
206
|
+
for key in remove_keys:
|
|
207
|
+
del self.data[key]
|
|
201
208
|
|
|
202
209
|
with open(self.artifacts_json_filepath, 'w', encoding='utf-8') as f:
|
|
203
210
|
json.dump(self.data, f, indent=4)
|
|
204
|
-
|
|
211
|
+
info(f'Wrote artifacts JSON: {self.artifacts_json_filepath}')
|
|
205
212
|
|
|
206
213
|
|
|
207
214
|
artifacts = Artifacts()
|
|
@@ -217,6 +224,7 @@ class UtilLogger:
|
|
|
217
224
|
# util's argparser: --no-default-log, --logfile=<name>, or --force-logfile=<name>
|
|
218
225
|
default_log_enabled = False
|
|
219
226
|
default_log_filepath = os.path.join('eda.work', 'eda.log')
|
|
227
|
+
enable = True
|
|
220
228
|
|
|
221
229
|
def clear(self) -> None:
|
|
222
230
|
'''Resets internals'''
|
|
@@ -234,6 +242,9 @@ class UtilLogger:
|
|
|
234
242
|
|
|
235
243
|
def start(self, filename: str, force: bool = False) -> None:
|
|
236
244
|
'''Starts (opens) log'''
|
|
245
|
+
if not self.enable:
|
|
246
|
+
self.file = None
|
|
247
|
+
return
|
|
237
248
|
if not filename:
|
|
238
249
|
error('Trying to start a logfile, but filename is missing')
|
|
239
250
|
return
|
|
@@ -420,14 +431,12 @@ def process_tokens(tokens:list) -> (argparse.Namespace, list):
|
|
|
420
431
|
# avoid this if someone has --help arg not yet parsed.
|
|
421
432
|
start_log(global_log.default_log_filepath, force=True)
|
|
422
433
|
|
|
423
|
-
|
|
424
434
|
parsed_as_dict = vars(parsed)
|
|
425
435
|
for key,value in parsed_as_dict.items():
|
|
426
436
|
key = key.replace('_', '-')
|
|
427
437
|
if value is not None:
|
|
428
438
|
args[key] = value # only update with non-None values to our global 'args' dict
|
|
429
439
|
|
|
430
|
-
|
|
431
440
|
return parsed, unparsed
|
|
432
441
|
|
|
433
442
|
# ********************
|
|
@@ -616,7 +625,9 @@ def warning(*text, start: object = None, end: str = '\n') -> None:
|
|
|
616
625
|
|
|
617
626
|
|
|
618
627
|
def error(
|
|
619
|
-
*text,
|
|
628
|
+
*text,
|
|
629
|
+
error_code: int = status_constants.EDA_DEFAULT_ERROR,
|
|
630
|
+
do_exit: bool = True, start: object = None, end: str = '\n'
|
|
620
631
|
) -> int:
|
|
621
632
|
'''Print error messaging (in red if possible).
|
|
622
633
|
|
|
@@ -629,9 +640,12 @@ def error(
|
|
|
629
640
|
|
|
630
641
|
Note these messages append to global logging. Increments global args['errors'] int.
|
|
631
642
|
'''
|
|
643
|
+
global max_error_code # pylint: disable=global-statement
|
|
644
|
+
|
|
632
645
|
if start is None:
|
|
633
646
|
start = "ERROR: " + (f"[{progname}] " if progname_in_message else "")
|
|
634
647
|
args['errors'] += 1
|
|
648
|
+
max_error_code = max(max_error_code, error_code)
|
|
635
649
|
print_red(f"{start}{' '.join(list(text))}", end=end)
|
|
636
650
|
if do_exit:
|
|
637
651
|
if args['debug']:
|
|
@@ -644,6 +658,15 @@ def error(
|
|
|
644
658
|
return abs(int(error_code))
|
|
645
659
|
|
|
646
660
|
|
|
661
|
+
def get_return_code() -> int:
|
|
662
|
+
'''Checks global max_error_code and args['errors']'''
|
|
663
|
+
if args['errors']:
|
|
664
|
+
if max_error_code == 0:
|
|
665
|
+
return 255
|
|
666
|
+
return max_error_code
|
|
667
|
+
return 0
|
|
668
|
+
|
|
669
|
+
|
|
647
670
|
def exit( # pylint: disable=redefined-builtin
|
|
648
671
|
error_code: int = 0, quiet: bool = False
|
|
649
672
|
) -> int:
|
opencos/utils/markup_helpers.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
''' opencos.utils.markup_helpers - function helpers for YAML, TOML reading and writing'''
|
|
2
2
|
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
3
5
|
import re
|
|
4
6
|
import shutil
|
|
5
7
|
import subprocess
|
|
@@ -89,10 +91,37 @@ def yaml_safe_load(filepath: str, only_root_line_numbers:bool = False) -> dict:
|
|
|
89
91
|
|
|
90
92
|
|
|
91
93
|
def yaml_safe_writer(data: dict, filepath: str) -> None:
|
|
92
|
-
'''
|
|
93
|
-
|
|
94
|
+
'''Wrapper for yaml.dump, enforces file extension otherwise warning'''
|
|
95
|
+
_, ext = os.path.splitext(filepath)
|
|
96
|
+
if ext.lower() in ('.yml', '.yaml'):
|
|
94
97
|
with open(filepath, 'w', encoding='utf-8') as f:
|
|
95
98
|
yaml.dump(data, f, allow_unicode=True,
|
|
96
99
|
default_flow_style=False, sort_keys=False, encoding='utf-8')
|
|
97
100
|
else:
|
|
98
101
|
warning(f'{filepath=} to be written for this extension not implemented.')
|
|
102
|
+
|
|
103
|
+
def json_writer(data: dict, filepath: str) -> None:
|
|
104
|
+
'''Wrapper for json.dump'''
|
|
105
|
+
with open(filepath, 'w', encoding='utf-8') as f:
|
|
106
|
+
json.dump(data, f, indent=4)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def markup_writer(data: dict, filepath: str) -> None:
|
|
110
|
+
'''Wrapper for yaml_safe_writer or json_writer'''
|
|
111
|
+
_, ext = os.path.splitext(filepath)
|
|
112
|
+
if ext.lower in ('.yml', '.yaml'):
|
|
113
|
+
yaml_safe_writer(data, filepath)
|
|
114
|
+
else:
|
|
115
|
+
json_writer(data, filepath)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def markup_dumper(data: dict, as_yaml: bool = False) -> str:
|
|
119
|
+
'''Returns JSON str; if as_yaml=True returns YAML str, from data'''
|
|
120
|
+
if as_yaml:
|
|
121
|
+
return yaml.dump(
|
|
122
|
+
data=data, allow_unicode=True,
|
|
123
|
+
default_flow_style=False, sort_keys=False
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# else return JSON:
|
|
127
|
+
return str(json.dumps(data, indent=4))
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'''opencos.utils.status_constants is a module containing known Error return codes
|
|
2
|
+
|
|
3
|
+
(0 pass, > 0 fail) from eda or other opencos executables.
|
|
4
|
+
'''
|
|
5
|
+
|
|
6
|
+
NO_ERROR = 0
|
|
7
|
+
|
|
8
|
+
PYTHON_EXCEPTION = 1
|
|
9
|
+
|
|
10
|
+
EDA_DEPS_FILE_NOT_FOUND = 10
|
|
11
|
+
EDA_DEPS_TARGET_NOT_FOUND = 11
|
|
12
|
+
|
|
13
|
+
EDA_COMMAND_OR_ARGS_ERROR = 20
|
|
14
|
+
EDA_CONFIG_ERROR = 21
|
|
15
|
+
EDA_COMMAND_MISSING_TOP = 22
|
|
16
|
+
|
|
17
|
+
EDA_SIM_LOG_HAS_BAD_STRING = 30
|
|
18
|
+
EDA_SIM_LOG_MISSING_MUST_STRING = 31
|
|
19
|
+
|
|
20
|
+
EDA_SHELL_LOG_HAS_BAD_STRING = 38
|
|
21
|
+
EDA_SHELL_LOG_MISSING_MUST_STRING = 39
|
|
22
|
+
|
|
23
|
+
EDA_EXEC_NONZERO_RETURN_CODE1 = 41
|
|
24
|
+
EDA_EXEC_NONZERO_RETURN_CODE2 = 42
|
|
25
|
+
EDA_EXEC_NONZERO_RETURN_CODE255 = 255
|
|
26
|
+
|
|
27
|
+
EDA_DEFAULT_ERROR = 255
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'''Because so many tools use the exe vsim, I don't want to run `vsim -version` N times for N
|
|
2
|
+
tools each to figure out if it's full-Questa, Modelsim, QuestaFSE, or Riviera.
|
|
3
|
+
|
|
4
|
+
Instead, eda.py can call this once, and then query if this tool exists when running
|
|
5
|
+
opencos.eda.auto_tool_setup(..)
|
|
6
|
+
'''
|
|
7
|
+
|
|
8
|
+
import shutil
|
|
9
|
+
import subprocess
|
|
10
|
+
|
|
11
|
+
from opencos.util import debug
|
|
12
|
+
|
|
13
|
+
vsim_path = shutil.which('vsim')
|
|
14
|
+
|
|
15
|
+
INIT_HAS_RUN = False
|
|
16
|
+
TOOL_IS = {
|
|
17
|
+
'riviera': False,
|
|
18
|
+
'modelsim_ase': False,
|
|
19
|
+
'questa' : False,
|
|
20
|
+
'questa_fse': False
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def init() -> None:
|
|
25
|
+
'''Sets INIT_HAS_RUN=True (only runs once) and one of TOOL_IS[tool] = True'''
|
|
26
|
+
global INIT_HAS_RUN # pylint: disable=global-statement
|
|
27
|
+
|
|
28
|
+
if INIT_HAS_RUN:
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
INIT_HAS_RUN = True
|
|
32
|
+
|
|
33
|
+
if not vsim_path:
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
proc = None
|
|
37
|
+
try:
|
|
38
|
+
proc = subprocess.run([vsim_path, '-version'], capture_output=True, check=False)
|
|
39
|
+
except Exception as e:
|
|
40
|
+
debug(f'vsim -version: exception {e}')
|
|
41
|
+
|
|
42
|
+
if proc is None or proc.returncode != 0:
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
stdout_str_lower = proc.stdout.decode('utf-8', errors='replace').lower()
|
|
47
|
+
|
|
48
|
+
if all(x in stdout_str_lower for x in ('starter', 'modelsim', 'fpga')):
|
|
49
|
+
TOOL_IS['modelsim_ase'] = True
|
|
50
|
+
elif all(x in stdout_str_lower for x in ('starter', 'questa', 'fpga')):
|
|
51
|
+
TOOL_IS['questa_fse'] = True
|
|
52
|
+
elif all(x in stdout_str_lower for x in ('riviera', 'aldec')):
|
|
53
|
+
TOOL_IS['riviera'] = True
|
|
54
|
+
elif 'questa' in stdout_str_lower:
|
|
55
|
+
TOOL_IS['questa'] = True
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: opencos-eda
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.51
|
|
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
|
|
@@ -2,13 +2,14 @@ opencos/__init__.py,sha256=RwJA9oc1uUlvNX7v5zoqwjnSRNq2NZwRlHqtS-ICJkI,122
|
|
|
2
2
|
opencos/_version.py,sha256=XiHFZjCofkkZvI9befTFKsRt5zLouUo1CIwhW4srWU0,582
|
|
3
3
|
opencos/_waves_pkg.sv,sha256=1lbhQOVGc3t_R8czYjP40hssP0I3FlZOpHTkI7yKFbI,1251
|
|
4
4
|
opencos/deps_schema.py,sha256=pdJpKmfXxCYZsfXTxsnACw-s6Mzk55QH06ZSA5uqPV0,15454
|
|
5
|
-
opencos/eda.py,sha256=
|
|
6
|
-
opencos/eda_base.py,sha256=
|
|
7
|
-
opencos/eda_config.py,sha256=
|
|
8
|
-
opencos/eda_config_defaults.yml,sha256=
|
|
5
|
+
opencos/eda.py,sha256=rYBt4Qu2y45zdwD4jDx_p3l2u5LMS1nsaGQCP2ZYdwY,20883
|
|
6
|
+
opencos/eda_base.py,sha256=q0uKEzAMzLmqb4jMBcGv7rTpdKnfrhnMZo7MyB8YN8Y,95706
|
|
7
|
+
opencos/eda_config.py,sha256=Xma8w7kBLCNPCSrCKpceieU7GUBv4i54XZlHjnZ95X4,11353
|
|
8
|
+
opencos/eda_config_defaults.yml,sha256=PdR3xHWQIdGeIyCTr2c3W9Vfq9RC1E9HbqiSydyDYZE,12417
|
|
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
|
+
opencos/eda_deps_sanitize.py,sha256=SQjvrte9Hv9JesRY0wljvbdC6pAmLCikI-Wdzzy-D04,1939
|
|
12
13
|
opencos/eda_extract_targets.py,sha256=dvBjc2qFBJkwlW6Fm-66Y_vlr0VZL1QwfIosMR_bgbY,4814
|
|
13
14
|
opencos/eda_tool_helper.py,sha256=_YgobDLEWW6Fzdr976LxaCDZ4DKRyuMs5CrYQHaTPrU,2558
|
|
14
15
|
opencos/export_helper.py,sha256=ArsM8qxBc08gj9S-UholGU417POfBYb_cHkBQZEfqfI,22046
|
|
@@ -17,18 +18,18 @@ opencos/files.py,sha256=aoq0O2KfISzZb-Vi_q_0TTGBER9xJc--FkVZf0ga7pA,1549
|
|
|
17
18
|
opencos/names.py,sha256=iC37PV7Pz0PicTDg09vbQ9NXAj-5m6RKrWHkkcHB8As,1145
|
|
18
19
|
opencos/peakrdl_cleanup.py,sha256=vHNGtalTrIVP335PhRjPt9RhoccgpK1HJAi-E4M8Kc8,736
|
|
19
20
|
opencos/seed.py,sha256=IL9Yg-r9SLSRseMVWaEHmuw2_DNi_eyut11EafoNTsU,942
|
|
20
|
-
opencos/util.py,sha256=
|
|
21
|
+
opencos/util.py,sha256=hnE66Mu7Uu-OGdAE5gkJ8nsKc_zUNMptXcuBFkm30wU,33975
|
|
21
22
|
opencos/commands/__init__.py,sha256=DtOA56oWJu68l-_1_7Gdv0N-gtXVB3-p9IhGzAYex8U,1014
|
|
22
23
|
opencos/commands/build.py,sha256=jI5ul53qfwn6X-yfSdSQIcLBhGtzZUk7r_wKBBmKJI0,1425
|
|
23
24
|
opencos/commands/elab.py,sha256=m6Gk03wSzX8UkcmReooK7turF7LpqO0IcdOZwJ8XiyI,1596
|
|
24
25
|
opencos/commands/export.py,sha256=juzxJL5-RpEnU5DmwF0fiG5pUrB2BbUbvCp2OasEd88,3494
|
|
25
26
|
opencos/commands/flist.py,sha256=OUu_ewTDLkZqdW4547iRrwOhT4ghm8oMYHsA63yChvo,8551
|
|
26
|
-
opencos/commands/lec.py,sha256=
|
|
27
|
+
opencos/commands/lec.py,sha256=mIrMKFEZrFYIzvBdffcIyVULx04Cu4-480FB4Y1EiTA,3863
|
|
27
28
|
opencos/commands/multi.py,sha256=dCo4rMIkGT3BtlBhUIAd7r31w8qxeJvybpl4H7DR77o,27225
|
|
28
29
|
opencos/commands/open.py,sha256=unrpGckzg0FE5W3oARq8x0jX7hhV_uM9Oh5FgISHFAg,724
|
|
29
30
|
opencos/commands/proj.py,sha256=MdHTOtQYG93_gT97dWuSyAgUxX2vi9FRhL0dtc-rM98,1096
|
|
30
|
-
opencos/commands/shell.py,sha256=
|
|
31
|
-
opencos/commands/sim.py,sha256=
|
|
31
|
+
opencos/commands/shell.py,sha256=0SNxiNRPD1BO6dK0yxU_iV-8S9UyF4GOWGLL1A_KeVs,7583
|
|
32
|
+
opencos/commands/sim.py,sha256=WPkn_lZqgMPSQTxxT4Qa0qHKMmdHAkt1GXCDw4iD6kI,14723
|
|
32
33
|
opencos/commands/sweep.py,sha256=ni4XFgnFF8HLXtwPhETyLWfvc2kgtm4bcxFcKzUhkf0,9343
|
|
33
34
|
opencos/commands/synth.py,sha256=quB-HWS4LKYTiFBHiYarQi4pMnRmt12wQTZpi14VvlE,4355
|
|
34
35
|
opencos/commands/targets.py,sha256=_jRNhm2Fqj0fmMvTw6Ba39DCsRHf_r_uZCy_R064kpA,1472
|
|
@@ -37,7 +38,7 @@ opencos/commands/waves.py,sha256=dsWwtjpDgH-YsiIjJgqTvteY3OZ48UnEAWc3blV2Fog,705
|
|
|
37
38
|
opencos/deps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
39
|
opencos/deps/defaults.py,sha256=4VgTRXf0hSJrj4tMk0t-mmvgEaaQQFDIYrxWrcKfYWk,1241
|
|
39
40
|
opencos/deps/deps_commands.py,sha256=OlqueYFK8U83loasok3xJGzUDpNcj2DPk37332DfmRo,17039
|
|
40
|
-
opencos/deps/deps_file.py,sha256=
|
|
41
|
+
opencos/deps/deps_file.py,sha256=29UkOT-UYw5XYYRK7DySmZkgdTj4wnxVQ5SgZVnrrds,15199
|
|
41
42
|
opencos/deps/deps_processor.py,sha256=NYGBrBAmk7ltrvxsEhv76Kpp76GBRJqeNNY_ckWf5mE,33028
|
|
42
43
|
opencos/hw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
43
44
|
opencos/hw/oc_cli.py,sha256=QBmf05VooB9kQFzgliak7PEvqVLTSEI3v6deV1OZQEs,132252
|
|
@@ -67,7 +68,7 @@ opencos/tools/invio_yosys.py,sha256=asSjbdPjBXB76KxNZIhoDRn2DoXKsZEQ1YDX_WBzKiA,
|
|
|
67
68
|
opencos/tools/iverilog.py,sha256=KQV5tiRdM0ZuJOO0q3ZeUmhRyEc-oJggOc6RKIjoH84,6482
|
|
68
69
|
opencos/tools/modelsim_ase.py,sha256=rW5YrzLIhZKdbIT2UM60DGmDEHDK3XMlndpBz906cmA,14229
|
|
69
70
|
opencos/tools/questa.py,sha256=bXH_ajMUBBHNUeWTBNWDp2gGyza2vf8xIC6vz5EWiz8,7619
|
|
70
|
-
opencos/tools/questa_fse.py,sha256=
|
|
71
|
+
opencos/tools/questa_fse.py,sha256=WLNvkD57ggI7lQgxYr6h_xmXenh50J_m8o8amhECmpk,1855
|
|
71
72
|
opencos/tools/riviera.py,sha256=0FKAUvgyUHvJ5z0LOjHV9p3dGF8LI5kj5WLOZXgCDas,10695
|
|
72
73
|
opencos/tools/slang.py,sha256=mFw58vhnCTRR9yaQ2zHPlNB5HKSf3Y078XcaVnpLaAc,7798
|
|
73
74
|
opencos/tools/slang_yosys.py,sha256=3fyLRRdTXhSppNtUhhUl00oG-cT9TyyPTH6JvasS9ZE,9804
|
|
@@ -75,15 +76,17 @@ opencos/tools/surelog.py,sha256=dtj3ApAKoQasnGdg42n9DPgeqoJ5nCuurIkIO3G5ZCY,5011
|
|
|
75
76
|
opencos/tools/tabbycad_yosys.py,sha256=2LePPgYXBVdsy7YcffPIWN-I0B7queLQ_f_pme2SCGw,7803
|
|
76
77
|
opencos/tools/verilator.py,sha256=h5VScZyJYxk2cXDRrTTD47C_yAVHuyXv9_p7UL1o5mk,19486
|
|
77
78
|
opencos/tools/vivado.py,sha256=roM0MxrHoNIT_DsUiql1vQFlez2ql4qhwAfB-rGH4Qg,40485
|
|
78
|
-
opencos/tools/yosys.py,sha256=
|
|
79
|
+
opencos/tools/yosys.py,sha256=UKWzvc2rSi3J9U2TROqRbmePdBmjskap664giumeRBk,26323
|
|
79
80
|
opencos/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
80
|
-
opencos/utils/markup_helpers.py,sha256=
|
|
81
|
+
opencos/utils/markup_helpers.py,sha256=A8Ev5UJ4EVKjdcF2g85SQbjdPZR4jGpNqCLaBy_4v7Q,4569
|
|
82
|
+
opencos/utils/status_constants.py,sha256=aQui30ohPCEaWNDh2iujJQ_KQa3plry_rk7uDzS3vWk,603
|
|
81
83
|
opencos/utils/str_helpers.py,sha256=DOkwfKJR6aENM3U2BkJ41ELDU5Uj_zyhEfxuaQEcpEY,3352
|
|
82
84
|
opencos/utils/subprocess_helpers.py,sha256=xemAGPey6M0sWY_FElvr-Z0phCfdjaC-znP8FKihPaE,3535
|
|
83
|
-
|
|
84
|
-
opencos_eda-0.2.
|
|
85
|
-
opencos_eda-0.2.
|
|
86
|
-
opencos_eda-0.2.
|
|
87
|
-
opencos_eda-0.2.
|
|
88
|
-
opencos_eda-0.2.
|
|
89
|
-
opencos_eda-0.2.
|
|
85
|
+
opencos/utils/vsim_helper.py,sha256=2voGRZI2iAQ2Pv2ZI5g2why6xpgig-To8im-LVXtuDU,1517
|
|
86
|
+
opencos_eda-0.2.51.dist-info/licenses/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
|
|
87
|
+
opencos_eda-0.2.51.dist-info/licenses/LICENSE.spdx,sha256=8gn1610RMP6eFgT3Hm6q9VKXt0RvdTItL_oxMo72jII,189
|
|
88
|
+
opencos_eda-0.2.51.dist-info/METADATA,sha256=XAX9j35DEWh0nLU7Gt8unosqGzK9dGeqHtGcj8IgK-8,604
|
|
89
|
+
opencos_eda-0.2.51.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
90
|
+
opencos_eda-0.2.51.dist-info/entry_points.txt,sha256=6n1T5NwVYDhN5l1h5zmyT197G4pE0SySDreB0QJzJR0,218
|
|
91
|
+
opencos_eda-0.2.51.dist-info/top_level.txt,sha256=J4JDP-LpRyJqPNeh9bSjx6yrLz2Mk0h6un6YLmtqql4,8
|
|
92
|
+
opencos_eda-0.2.51.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|