opencos-eda 0.2.48__py3-none-any.whl → 0.2.50__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. opencos/__init__.py +4 -2
  2. opencos/_version.py +10 -7
  3. opencos/commands/flist.py +8 -7
  4. opencos/commands/multi.py +14 -15
  5. opencos/commands/sim.py +5 -0
  6. opencos/commands/sweep.py +3 -2
  7. opencos/deps/__init__.py +0 -0
  8. opencos/deps/defaults.py +69 -0
  9. opencos/deps/deps_commands.py +419 -0
  10. opencos/deps/deps_file.py +326 -0
  11. opencos/deps/deps_processor.py +670 -0
  12. opencos/deps_schema.py +7 -8
  13. opencos/eda.py +84 -64
  14. opencos/eda_base.py +585 -316
  15. opencos/eda_config.py +85 -14
  16. opencos/eda_config_defaults.yml +36 -4
  17. opencos/eda_extract_targets.py +22 -14
  18. opencos/eda_tool_helper.py +33 -7
  19. opencos/export_helper.py +166 -86
  20. opencos/export_json_convert.py +31 -23
  21. opencos/files.py +2 -1
  22. opencos/hw/__init__.py +0 -0
  23. opencos/{oc_cli.py → hw/oc_cli.py} +9 -4
  24. opencos/names.py +0 -4
  25. opencos/peakrdl_cleanup.py +13 -7
  26. opencos/seed.py +19 -11
  27. opencos/tests/helpers.py +3 -2
  28. opencos/tests/test_deps_helpers.py +35 -32
  29. opencos/tests/test_eda.py +36 -29
  30. opencos/tests/test_eda_elab.py +7 -4
  31. opencos/tests/test_eda_synth.py +1 -1
  32. opencos/tests/test_oc_cli.py +1 -1
  33. opencos/tests/test_tools.py +4 -2
  34. opencos/tools/iverilog.py +2 -2
  35. opencos/tools/modelsim_ase.py +24 -2
  36. opencos/tools/questa.py +5 -3
  37. opencos/tools/questa_fse.py +57 -0
  38. opencos/tools/riviera.py +1 -1
  39. opencos/tools/slang.py +9 -3
  40. opencos/tools/surelog.py +1 -1
  41. opencos/tools/verilator.py +26 -1
  42. opencos/tools/vivado.py +34 -27
  43. opencos/tools/yosys.py +4 -3
  44. opencos/util.py +532 -474
  45. opencos/utils/__init__.py +0 -0
  46. opencos/utils/markup_helpers.py +98 -0
  47. opencos/utils/str_helpers.py +111 -0
  48. opencos/utils/subprocess_helpers.py +108 -0
  49. {opencos_eda-0.2.48.dist-info → opencos_eda-0.2.50.dist-info}/METADATA +1 -1
  50. opencos_eda-0.2.50.dist-info/RECORD +89 -0
  51. {opencos_eda-0.2.48.dist-info → opencos_eda-0.2.50.dist-info}/entry_points.txt +1 -1
  52. opencos/deps_helpers.py +0 -1346
  53. opencos_eda-0.2.48.dist-info/RECORD +0 -79
  54. /opencos/{pcie.py → hw/pcie.py} +0 -0
  55. {opencos_eda-0.2.48.dist-info → opencos_eda-0.2.50.dist-info}/WHEEL +0 -0
  56. {opencos_eda-0.2.48.dist-info → opencos_eda-0.2.50.dist-info}/licenses/LICENSE +0 -0
  57. {opencos_eda-0.2.48.dist-info → opencos_eda-0.2.50.dist-info}/licenses/LICENSE.spdx +0 -0
  58. {opencos_eda-0.2.48.dist-info → opencos_eda-0.2.50.dist-info}/top_level.txt +0 -0
opencos/eda_config.py CHANGED
@@ -1,16 +1,25 @@
1
+ ''' opencos.eda_config - handles --config-yml arg, and providing a
2
+ 'config' dict for use by opencos.eda, opencos.commands, and opencos.tools
3
+
4
+ Order of precedence for default value of eda arg: --config-yaml:
5
+ 1) os.environ.get('EDA_CONFIG_YML', '') -- filepath to an eda_config.yml file
6
+ 2) ~/.opencos-eda/EDA_CONFIG.yml
7
+ 3) (package pip installed dir for opencos)/eda_config_defaults.yml
8
+ '''
9
+
10
+ import copy
1
11
  import os
2
- import sys
3
12
  import argparse
4
- import mergedeep
5
13
  import shutil
6
14
 
15
+ import mergedeep
16
+
7
17
  from opencos import util
18
+ from opencos.utils.markup_helpers import yaml_safe_load, yaml_safe_writer
8
19
 
9
20
  class Defaults:
10
- # Order of precedence for default value of eda arg: --config-yaml:
11
- # 1) os.environ.get('EDA_CONFIG_YML', '') -- filepath to an eda_config.yml file
12
- # 2) ~/.opencos-eda/EDA_CONFIG.yml
13
- # 3) (package pip installed dir for opencos)/eda_config_defaults.yml
21
+ '''Defaults is a global placeholder for constants and supported features.'''
22
+
14
23
  environ_override_config_yml = os.environ.get('EDA_CONFIG_YML', '')
15
24
  home_override_config_yml = os.path.join(
16
25
  os.environ.get('HOME', ''), '.opencos-eda', 'EDA_CONFIG.yml'
@@ -56,6 +65,7 @@ class Defaults:
56
65
  'coverage-args',
57
66
  ])
58
67
 
68
+ EDA_OUTPUT_CONFIG_FNAME = 'eda_output_config.yml'
59
69
 
60
70
  if os.path.exists(Defaults.environ_override_config_yml):
61
71
  Defaults.config_yml = Defaults.environ_override_config_yml
@@ -65,9 +75,11 @@ else:
65
75
  Defaults.config_yml = Defaults.opencos_config_yml
66
76
 
67
77
 
68
- def find_eda_config_yml_fpath(filename:str, package_search_only=False, package_search_enabled=True) -> str:
78
+ def find_eda_config_yml_fpath(
79
+ filename:str, package_search_only=False, package_search_enabled=True
80
+ ) -> str:
69
81
  '''Locates the filename (.yml) either from fullpath provided or from the sys.path
70
- opencos package paths.'''
82
+ (pip-installed) opencos-eda package paths.'''
71
83
 
72
84
  # Check fullpath, unless we're only checking the installed pacakge dir.
73
85
  if package_search_only:
@@ -103,6 +115,11 @@ def find_eda_config_yml_fpath(filename:str, package_search_only=False, package_s
103
115
 
104
116
 
105
117
  def check_config(config:dict, filename='') -> None:
118
+ '''Returns None, will util.error(..) if there are issues in 'config'
119
+
120
+ checks for known dict keys and data types, is NOT exhaustive checking.
121
+ '''
122
+
106
123
  # sanity checks:
107
124
  for key in config:
108
125
  if key not in Defaults.supported_config_keys:
@@ -146,7 +163,7 @@ def update_config_auto_tool_order_for_tool(tool: str, config: dict) -> str:
146
163
  return tool
147
164
 
148
165
  old_exe = config['auto_tools_order'][0][tool].get('exe', str())
149
- if type(old_exe) is list:
166
+ if isinstance(old_exe, list):
150
167
  config['auto_tools_order'][0][tool]['exe'][0] = user_exe
151
168
  else:
152
169
  config['auto_tools_order'][0][tool]['exe'] = user_exe
@@ -154,23 +171,33 @@ def update_config_auto_tool_order_for_tool(tool: str, config: dict) -> str:
154
171
 
155
172
 
156
173
  def update_config_auto_tool_order_for_tools(tools: list, config: dict) -> list:
157
- ret = list()
174
+ '''Given a list of tools and eda_config style 'config' dict, update
175
+
176
+ the auto_tool_order (consumed by opencos.eda when --tool is not specified).
177
+ '''
178
+ ret = []
158
179
  for tool in tools:
159
180
  ret.append(update_config_auto_tool_order_for_tool(tool, config))
160
181
  return ret
161
182
 
162
183
 
163
184
  def update_config_for_eda_safe(config) -> None:
185
+ '''Set method to update config dict values to run in a "safe" mode'''
164
186
  config['dep_command_enables']['shell'] = False
165
187
 
166
188
 
167
189
  def deps_shell_commands_enabled(config) -> bool:
190
+ '''Get method on config to determine if DEPS.yml shell-style commands are allowed'''
168
191
  return config['dep_command_enables']['shell']
169
192
 
170
193
 
171
194
  def get_config(filename) -> dict:
195
+ '''Given an eda_config_default.yml (or --config-yml=<filename>) return a config
196
+
197
+ dict from the filename.'''
198
+
172
199
  fpath = find_eda_config_yml_fpath(filename)
173
- user_config = util.yaml_safe_load(fpath)
200
+ user_config = yaml_safe_load(fpath)
174
201
  check_config(user_config, filename=filename)
175
202
 
176
203
  # The final thing we do is update key 'config-yml' with the full path used.
@@ -181,6 +208,10 @@ def get_config(filename) -> dict:
181
208
 
182
209
 
183
210
  def get_config_handle_defaults(filename) -> dict:
211
+ '''Given a user provided --config-yml=<filename>, return a merged config with
212
+
213
+ the existing default config.'''
214
+
184
215
  user_config = get_config(filename)
185
216
  user_config = get_config_merged_with_defaults(user_config)
186
217
  return user_config
@@ -197,19 +228,31 @@ def merge_config(dst_config:dict, overrides_config:dict, additive_strategy=False
197
228
 
198
229
 
199
230
  def get_config_merged_with_defaults(config:dict) -> dict:
231
+ '''Returns a new config that has been merged with the default config.
232
+
233
+ The default config location is based on Defaults.config_yml (env, local, or pip
234
+ installed location)'''
235
+
200
236
  default_fpath = find_eda_config_yml_fpath(Defaults.config_yml, package_search_only=True)
201
- default_config = util.yaml_safe_load(default_fpath)
237
+ default_config = yaml_safe_load(default_fpath)
202
238
  merge_config(default_config, overrides_config=config)
203
239
  # This technically mutated updated into default_config, so return that one:
204
240
  return default_config
205
241
 
242
+
206
243
  def get_argparser() -> argparse.ArgumentParser:
207
- parser = argparse.ArgumentParser(prog='opencos eda config options', add_help=False, allow_abbrev=False)
244
+ '''Returns an ArgumentParser, handles --config-yml=<filename> arg'''
245
+ parser = argparse.ArgumentParser(
246
+ prog='opencos eda config options', add_help=False, allow_abbrev=False
247
+ )
208
248
  parser.add_argument('--config-yml', type=str, default=Defaults.config_yml,
209
- help=f'YAML filename to use for configuration (default {Defaults.config_yml})')
249
+ help=('YAML filename to use for configuration (default'
250
+ f' {Defaults.config_yml})'))
210
251
  return parser
211
252
 
253
+
212
254
  def get_argparser_short_help() -> str:
255
+ '''Returns a shortened help string given for arg --config-yml.'''
213
256
  return util.get_argparser_short_help(parser=get_argparser())
214
257
 
215
258
 
@@ -249,3 +292,31 @@ def get_eda_config(args:list, quiet=False) -> (dict, list):
249
292
  config = get_config_merged_with_defaults(config)
250
293
 
251
294
  return config, unparsed
295
+
296
+
297
+ def write_eda_config_and_args(
298
+ dirpath : str, filename: str = EDA_OUTPUT_CONFIG_FNAME,
299
+ command_obj_ref: object = None
300
+ ) -> None:
301
+ '''Writes and eda_config style dict to dirpath/filename'''
302
+ if command_obj_ref is None:
303
+ return
304
+ fullpath = os.path.join(dirpath, filename)
305
+ data = {}
306
+ for x in ['command_name', 'config', 'target', 'args', 'modified_args', 'defines',
307
+ 'incdirs', 'files_v', 'files_sv', 'files_vhd']:
308
+ # Use deep copy b/c otherwise these are references to opencos.eda.
309
+ data[x] = copy.deepcopy(getattr(command_obj_ref, x, ''))
310
+
311
+ # copy util.args
312
+ data['util'] = {
313
+ 'args': util.args
314
+ }
315
+
316
+ # fix some burried class references in command_obj_ref.config,
317
+ # otherwise we won't be able to safe load this yaml, so cast as str repr.
318
+ for k, v in getattr(command_obj_ref, 'config', {}).items():
319
+ if k == 'command_handler':
320
+ data['config'][k] = str(v)
321
+
322
+ yaml_safe_writer(data=data, filepath=fullpath)
@@ -230,17 +230,39 @@ tools:
230
230
  compile-args: |
231
231
  -sv -svinputport=net -lint
232
232
  compile-waivers:
233
- - 2275 # 2275 - Existing package 'foo_pkg' will be overwritten.
234
- - 2555 # 2555 - assignment to input port foo
233
+ - 2275 # 2275 - Existing package 'myname_pkg' will be overwritten.
234
+ - 2555 # 2555 - assignment to input port myname
235
235
  - 2583 # 2583 - [SVCHK] - Extra checking for conflicts with always_comb and
236
236
  # always_latch variables is done at vopt time.
237
237
  simulate-waivers:
238
- - 3009 # 3009: [TSCALE] - Module 'foo' does not have a timeunit/timeprecision
238
+ - 3009 # 3009: [TSCALE] - Module 'myname' does not have a timeunit/timeprecision
239
239
  # specification in effect, but other modules do.
240
240
  simulate-waves-args: |
241
241
  +acc
242
242
 
243
243
 
244
+ questa_fse:
245
+ defines:
246
+ OC_TOOL_QUESTA_FSE: 1
247
+ log-bad-strings:
248
+ - "Error:"
249
+ log-must-strings:
250
+ - " vsim "
251
+ - "Errors: 0"
252
+ compile-args: |
253
+ -sv -svinputport=net -lint
254
+ compile-waivers:
255
+ - 2275 # 2275 - Existing package 'myname_pkg' will be overwritten.
256
+ - 2555 # 2555 - assignment to input port myname
257
+ - 2583 # 2583 - [SVCHK] - Extra checking for conflicts with always_comb and
258
+ # always_latch variables is done at vopt time.
259
+ simulate-waivers:
260
+ - 3009 # 3009: [TSCALE] - Module 'myname' does not have a timeunit/timeprecision
261
+ # specification in effect, but other modules do.
262
+ simulate-waves-args: |
263
+ -voptargs=+acc=bcgnprst
264
+
265
+
244
266
  iverilog:
245
267
  log-bad-strings:
246
268
  - "Error:"
@@ -303,7 +325,7 @@ auto_tools_order:
303
325
  # TODO(drew): surelog is disabled from `eda tools-multi`. It is still
304
326
  # enabled for `eda [multi] elab --tool surelog`. It does not support
305
327
  # type comparisons:
306
- # if type(foo) == type(bar)
328
+ # if type(myname) == type(othername)
307
329
  # [ERR:UH0700] ... Unsupported expression
308
330
  # modelsim_ase also doesn't, but it won't fail elab, whereas surelog does.
309
331
  surelog:
@@ -404,6 +426,16 @@ auto_tools_order:
404
426
  elab: opencos.tools.modelsim_ase.CommandElabModelsimAse
405
427
  sim: opencos.tools.modelsim_ase.CommandSimModelsimAse
406
428
 
429
+ questa_fse: # free student edition, works similar to modelsim_ase
430
+ exe: vsim
431
+ requires_cmd:
432
+ - vsim -version
433
+ requires_in_exe_path:
434
+ - questa_fse
435
+ handlers:
436
+ elab: opencos.tools.questa_fse.CommandElabQuestaFse
437
+ sim: opencos.tools.questa_fse.CommandSimQuestaFse
438
+
407
439
  iverilog:
408
440
  exe: iverilog
409
441
  handlers:
@@ -8,12 +8,8 @@ targets from DEPS files
8
8
  import sys
9
9
  import os
10
10
  from pathlib import Path
11
- import json
12
11
 
13
- import yaml
14
- import toml
15
-
16
- from opencos.deps_helpers import get_all_targets
12
+ from opencos.deps.deps_file import get_all_targets
17
13
 
18
14
  PATH_LPREFIX = str(Path('.')) + os.path.sep
19
15
 
@@ -32,12 +28,13 @@ def get_terminal_columns():
32
28
  except OSError:
33
29
  # Handle cases where the terminal size cannot be determined (e.g., not in a TTY)
34
30
  return 80 # Default to 80 columns
35
- else:
36
- return 80
31
+
32
+ return 80 # else default to 80.
37
33
 
38
34
 
39
35
  def print_columns_manual(data: list, num_columns: int = 4, auto_columns: bool = True) -> None:
40
36
  """Prints a list of strings in columns, manually aligning them."""
37
+
41
38
  if not data:
42
39
  print()
43
40
  return
@@ -59,10 +56,12 @@ def print_columns_manual(data: list, num_columns: int = 4, auto_columns: bool =
59
56
  max_line_len += x + _spacing
60
57
  if max_line_len > window_cols:
61
58
  # subtract a column (already >= 2):
62
- return print_columns_manual(data=data, num_columns=num_columns-1, auto_columns=True)
59
+ print_columns_manual(data=data, num_columns=num_columns-1, auto_columns=True)
60
+ return
63
61
  if max_line_len + max_item_len + _spacing < window_cols:
64
62
  # add 1 more column if we're guaranteed to have room.
65
- return print_columns_manual(data=data, num_columns=num_columns+1, auto_columns=True)
63
+ print_columns_manual(data=data, num_columns=num_columns+1, auto_columns=True)
64
+ return
66
65
  # else continue
67
66
 
68
67
  # Print data in columns
@@ -72,21 +71,30 @@ def print_columns_manual(data: list, num_columns: int = 4, auto_columns: bool =
72
71
  if col_index == num_columns - 1 or i == len(data) - 1:
73
72
  print() # New line at the end of a row or end of data
74
73
 
74
+
75
75
  def get_path_and_pattern(partial_path: str = '', base_path=str(Path('.'))) -> (str, str):
76
76
  '''Returns tuple of (partial_path, partial_target or filter)'''
77
+
77
78
  partial_target = ''
78
79
  if not partial_path or partial_path == str(Path('.')):
79
80
  partial_path = PATH_LPREFIX
80
- if not os.path.exists(partial_path):
81
- partial_path, partial_target = os.path.split(partial_path)
81
+
82
+ # if the partial path is not an existing file or dir, then treat it as
83
+ # a partial and split it so partial_path/partial_target can be used later.
84
+ if not base_path or base_path == str(Path('.')):
85
+ if not os.path.exists(partial_path):
86
+ partial_path, partial_target = os.path.split(partial_path)
87
+ else:
88
+ if not os.path.exists(os.path.join(base_path, partial_path)):
89
+ partial_path, partial_target = os.path.split(partial_path)
90
+
91
+ # if we have no partial path, use compatible ./
82
92
  if not partial_path:
83
93
  partial_path = PATH_LPREFIX
84
94
 
85
95
  return partial_path, partial_target
86
96
 
87
97
 
88
-
89
-
90
98
  def get_targets(partial_paths: list, base_path=str(Path('.'))) -> list:
91
99
  '''Returns a list of DEPS keys into pretty columns, using arg
92
100
 
@@ -109,7 +117,7 @@ def get_targets(partial_paths: list, base_path=str(Path('.'))) -> list:
109
117
  error_on_empty_return=False,
110
118
  lstrip_path=True
111
119
  )
112
- except:
120
+ except Exception:
113
121
  keys = []
114
122
  for key in keys:
115
123
  targets_set.add(key)
@@ -1,12 +1,28 @@
1
+ ''' opencos.eda_tool_helper -- used by pytests and other checks to see if tools are loaded
2
+
3
+ which helps determine if a pytest is runnable for a given tool, or should be skipped.
4
+ Does this without calling `eda` or eda.main(..)
5
+
6
+ Example uses:
7
+ from opencos import eda_tool_helper
8
+ cfg, tools_loaded = eda_tool_helper.get_config_and_tools_loaded()
9
+ assert 'verilator' in tools_loaded
10
+
11
+ '''
1
12
 
2
- import os
3
- import sys
4
13
 
5
14
  from opencos import eda, eda_config, util
6
15
 
7
16
  # Used by pytest, so we can skip tests if tools aren't present.
8
17
 
9
- def get_config_and_tools_loaded(quiet=False, args=[]):
18
+ def get_config_and_tools_loaded( # pylint: disable=dangerous-default-value
19
+ quiet: bool = False, args: list = []
20
+ ) -> (dict, set):
21
+ '''Returns config dict and set tools_loaded, given the found config.
22
+
23
+ Can BYO args such as --config-yml=MY_OWN_EDA_CONFIG.yml
24
+ '''
25
+
10
26
  # We have to figure out what tools are avaiable w/out calling eda.main,
11
27
  # so we can get some of these using eda_config.get_eda_config()
12
28
  config, _ = eda_config.get_eda_config(args=args, quiet=quiet)
@@ -14,14 +30,24 @@ def get_config_and_tools_loaded(quiet=False, args=[]):
14
30
  tools_loaded = config.get('tools_loaded', set()).copy()
15
31
  return config, tools_loaded
16
32
 
33
+
17
34
  def get_all_handler_commands(config=None, tools_loaded=None) -> dict:
18
- all_handler_commands = dict()
35
+ '''Given a config and tools_loaded (or if not supplied uses defaults) returns a dict
36
+
37
+ of { <command>: [list of tools that run that command, in auto-tool-order] }.
38
+
39
+ For example:
40
+ { "sim": ["verilator", "vivado"],
41
+ "elab": ["slang", "verilator", ...], ...
42
+ }
43
+ '''
44
+ all_handler_commands = {}
19
45
 
20
46
  if config is None or tools_loaded is None:
21
47
  config, tools_loaded = get_config_and_tools_loaded()
22
48
 
23
- assert type(config) is dict
24
- assert type(tools_loaded) is set
49
+ assert isinstance(config, dict)
50
+ assert isinstance(tools_loaded, set)
25
51
 
26
52
  # Let's re-walk auto_tools_order to get this ordered per eda command:
27
53
  for tool, table in config.get('auto_tools_order', [{}])[0].items():
@@ -34,7 +60,7 @@ def get_all_handler_commands(config=None, tools_loaded=None) -> dict:
34
60
  'disable-tools-multi in config')
35
61
  continue
36
62
 
37
- for command, handler in table.get('handlers', {}).items():
63
+ for command in table.get('handlers', {}).keys():
38
64
  if command not in all_handler_commands:
39
65
  # create ordered list from config.
40
66
  all_handler_commands[command] = list([tool])