opencos-eda 0.2.28__py3-none-any.whl → 0.2.32__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 (35) hide show
  1. opencos/commands/export.py +2 -1
  2. opencos/commands/flist.py +49 -12
  3. opencos/commands/multi.py +101 -130
  4. opencos/commands/synth.py +0 -1
  5. opencos/commands/upload.py +7 -7
  6. opencos/commands/waves.py +50 -19
  7. opencos/deps_helpers.py +93 -3
  8. opencos/deps_schema.py +28 -18
  9. opencos/eda.py +6 -1
  10. opencos/eda_base.py +29 -10
  11. opencos/eda_config.py +1 -0
  12. opencos/eda_config_defaults.yml +4 -1
  13. opencos/eda_extract_deps_keys.py +27 -10
  14. opencos/files.py +6 -8
  15. opencos/names.py +1 -0
  16. opencos/oc_cli.py +143 -1
  17. opencos/peakrdl_cleanup.py +0 -1
  18. opencos/tests/helpers.py +38 -10
  19. opencos/tests/test_deps_helpers.py +46 -2
  20. opencos/tests/test_eda.py +65 -41
  21. opencos/tests/test_tools.py +17 -9
  22. opencos/tools/iverilog.py +0 -2
  23. opencos/tools/modelsim_ase.py +11 -5
  24. opencos/tools/questa.py +14 -19
  25. opencos/tools/verilator.py +0 -2
  26. opencos/tools/vivado.py +253 -112
  27. opencos/tools/yosys.py +1 -6
  28. opencos/util.py +4 -0
  29. {opencos_eda-0.2.28.dist-info → opencos_eda-0.2.32.dist-info}/METADATA +1 -1
  30. {opencos_eda-0.2.28.dist-info → opencos_eda-0.2.32.dist-info}/RECORD +35 -35
  31. {opencos_eda-0.2.28.dist-info → opencos_eda-0.2.32.dist-info}/WHEEL +1 -1
  32. {opencos_eda-0.2.28.dist-info → opencos_eda-0.2.32.dist-info}/entry_points.txt +0 -0
  33. {opencos_eda-0.2.28.dist-info → opencos_eda-0.2.32.dist-info}/licenses/LICENSE +0 -0
  34. {opencos_eda-0.2.28.dist-info → opencos_eda-0.2.32.dist-info}/licenses/LICENSE.spdx +0 -0
  35. {opencos_eda-0.2.28.dist-info → opencos_eda-0.2.32.dist-info}/top_level.txt +0 -0
opencos/deps_helpers.py CHANGED
@@ -1,4 +1,7 @@
1
+
2
+ import fnmatch
1
3
  import os
4
+ from pathlib import Path
2
5
  import sys
3
6
  import re
4
7
  import shutil
@@ -7,7 +10,8 @@ import toml, json
7
10
  from opencos import files
8
11
  from opencos import eda_config
9
12
  from opencos.util import debug, info, warning, error, ShellCommandList, \
10
- yaml_safe_load, toml_load_only_root_line_numbers
13
+ yaml_safe_load, toml_load_only_root_line_numbers, \
14
+ subprocess_run_background
11
15
 
12
16
 
13
17
  class Defaults:
@@ -233,6 +237,92 @@ peakrdl_cleanup_py = os.path.join(thispath, 'peakrdl_cleanup.py')
233
237
  def deps_data_get_all_targets(data:dict) -> list:
234
238
  return [x for x in data.keys() if x not in Defaults.root_table_keys_not_targets]
235
239
 
240
+
241
+ def fnmatch_or_re(pattern: str, string: str) -> bool:
242
+ '''Returns True if pattern/string matches in re.match or fnmatch'''
243
+ matches = []
244
+ # fnmatch check, aka: ./*test
245
+ matches.append(
246
+ bool(fnmatch.fnmatch(name=string, pat=pattern))
247
+ )
248
+ # regex check, aka: ./.*test
249
+ try:
250
+ matches.append(
251
+ bool(re.match(pattern=pattern, string=string))
252
+ )
253
+ except: # pylint: disable=bare-except
254
+ # could have been an illegal/unsupported regex, so don't match.
255
+ pass
256
+ return any(matches)
257
+
258
+
259
+ def get_all_targets(
260
+ dirs: list = [os.getcwd()],
261
+ base_path: str = os.getcwd(),
262
+ filter_str: str = '',
263
+ filter_using_multi: str = '',
264
+ error_on_empty_return: bool = True
265
+ ) -> list:
266
+ '''Returns a list of [dir/target, ... ] using relpath from base_path
267
+
268
+ If using filter_using_multi (str), dirs (list) is not required. Example:
269
+ filter_using_multi='sim --tool vivado path/to/*test'
270
+ and filter_str is applied to all resulting targets.
271
+
272
+ If not using filter_using_multi, dirs is required, and filter_str is applied
273
+ To all targets from dirs.
274
+ '''
275
+
276
+ if filter_using_multi:
277
+ targets = []
278
+ orig_dir = os.path.abspath(os.getcwd())
279
+ os.chdir(base_path)
280
+ cmd_str = 'eda multi --quiet --print-targets ' + filter_using_multi
281
+ stdout, stderr, rc = subprocess_run_background(
282
+ work_dir='.', command_list=cmd_str.split()
283
+ )
284
+ os.chdir(orig_dir)
285
+ if rc != 0:
286
+ error(f'get_all_targets: {base_path=} {filter_using_multi=} {cmd_str=} returned:',
287
+ f'{rc=}, {stdout=}')
288
+
289
+ multi_filtered_targets = stdout.split()
290
+ if not filter_str:
291
+ targets = multi_filtered_targets
292
+ else:
293
+ targets = set()
294
+ for target in multi_filtered_targets:
295
+ this_dir, leaf_target = os.path.split(target)
296
+ if fnmatch_or_re(pattern=filter_str,
297
+ string=leaf_target):
298
+ targets.add(
299
+ os.path.join(os.path.relpath(this_dir, start=base_path), leaf_target)
300
+ )
301
+ targets = list(targets)
302
+ if not targets and error_on_empty_return:
303
+ error(f'get_all_targets: {base_path=} {filter_using_multi=} returned no targets')
304
+ return targets
305
+
306
+ targets = set()
307
+ for this_dir in dirs:
308
+ this_dir = os.path.join(base_path, this_dir)
309
+ deps_file = get_deps_markup_file(this_dir)
310
+ if not deps_file:
311
+ continue
312
+ data = deps_markup_safe_load(filepath=deps_file)
313
+
314
+ for leaf_target in deps_data_get_all_targets(data):
315
+ if not filter_str or fnmatch_or_re(pattern=filter_str,
316
+ string=leaf_target):
317
+ targets.add(
318
+ os.path.join(os.path.relpath(this_dir, start=base_path), leaf_target)
319
+ )
320
+
321
+ if not targets and error_on_empty_return:
322
+ error(f'get_all_targets: {base_path=} {dirs=} {filter_str=} returned no targets')
323
+ return list(targets)
324
+
325
+
236
326
  def dep_str2list(value) -> list():
237
327
  if value is None:
238
328
  return []
@@ -1060,9 +1150,9 @@ def parse_deps_peakrdl(line : str, target_path : str, target_node : str, enable
1060
1150
 
1061
1151
 
1062
1152
  shell_commands = [
1063
- [ 'peakrdl', 'regblock', '-o', 'peakrdl/'] + args_list,
1153
+ [ 'peakrdl', 'regblock', '-o', str(Path('peakrdl/'))] + args_list,
1064
1154
  # Edit file to apply some verilator waivers, etc, from peakrdl_cleanup.py:
1065
- [ 'python3', peakrdl_cleanup_py, f'peakrdl/{top}.sv', f'peakrdl/{top}.sv' ],
1155
+ [ 'python3', peakrdl_cleanup_py, str(Path(f'peakrdl/{top}.sv')), str(Path(f'peakrdl/{top}.sv')) ],
1066
1156
  ]
1067
1157
 
1068
1158
  ret_dict = {
opencos/deps_schema.py CHANGED
@@ -120,8 +120,12 @@ my_target_name:
120
120
 
121
121
  '''
122
122
 
123
- from schema import Schema, And, Or, Use, Optional, SchemaError
124
- import sys, os
123
+ import os
124
+ import sys
125
+
126
+ from schema import Schema, Or, Optional, SchemaError
127
+
128
+ from opencos.deps_helpers import deps_markup_safe_load, get_deps_markup_file
125
129
 
126
130
  # Because we deal with YAML, where a Table Key with dangling/empty value is allowed
127
131
  # and we have things like SystemVerilog defines where there's a Table key with no Value,
@@ -302,19 +306,18 @@ def check(data: dict, schema_obj=FILE):
302
306
  return True, None
303
307
  except SchemaError as e:
304
308
  return False, str(e)
305
- except Exception as e:
309
+ except Exception as e: # pylint: disable=broad-exception-caught
306
310
  return False, str(e)
307
311
 
308
312
 
309
313
  def check_files(files, schema_obj=FILE) -> bool:
310
314
  '''Returns True if files lint cleanly in the FILE schema.'''
311
- from opencos.deps_helpers import deps_markup_safe_load, get_deps_markup_file
312
315
 
313
- if type(files) is str:
316
+ if isinstance(files, str):
314
317
  files = [files]
315
318
 
316
- passes_list = list()
317
- error_files = list()
319
+ passes_list = []
320
+ error_files = []
318
321
  for filepath in files:
319
322
  deps_filepath = filepath
320
323
  if os.path.isdir(filepath):
@@ -328,7 +331,7 @@ def check_files(files, schema_obj=FILE) -> bool:
328
331
  print(f'{deps_filepath}: [PASS]')
329
332
  if not passes:
330
333
  print(f'ERROR: {deps_filepath}:')
331
- print(f'-- retdata --')
334
+ print('-- retdata --')
332
335
  print(retdata)
333
336
  print(f' previous error on: {deps_filepath}\n')
334
337
  error_files.append(deps_filepath)
@@ -339,18 +342,25 @@ def check_files(files, schema_obj=FILE) -> bool:
339
342
  print(f'ERROR: files with problems (see above): {error_files}')
340
343
  return ret
341
344
 
342
- def main(filepaths=[]):
343
- if not filepaths:
344
- assert len(sys.argv) >= 2, f'Need 1 or more args - DEPS file to check'
345
- filepaths = sys.argv[1:]
345
+ def main( # pylint: disable=dangerous-default-value
346
+ filepaths: list = []
347
+ ) -> None:
348
+ '''Returns None, will exit on completion, checks all DEPS schema in list filepaths
349
+
350
+ If filepaths is empty, uses sys.argv[1:]'''
351
+
352
+ fpaths = filepaths
353
+ if not fpaths:
354
+ assert len(sys.argv) >= 2, 'Need 1 or more args - DEPS file to check'
355
+ fpaths = sys.argv[1:]
356
+
357
+ assert fpaths and isinstance(fpaths, list), \
358
+ f'Need 1 or more files to check: {fpaths=}'
346
359
 
347
- for filepath in filepaths:
360
+ for filepath in fpaths:
348
361
  assert os.path.exists(filepath), f'{filepath=} does not exist'
349
- ret = check_files(filepaths)
362
+ ret = check_files(fpaths)
350
363
  sys.exit(int(not ret))
351
364
 
352
365
  if __name__ == '__main__':
353
- '''Runs sys.argv[1:] filenames against check_files, for linting DEPS.'''
354
- assert len(sys.argv) >= 2, f'Need 1 or more args - DEPS file to check'
355
- filepaths = sys.argv[1:]
356
- main(filepaths)
366
+ main()
opencos/eda.py CHANGED
@@ -168,6 +168,7 @@ def auto_tool_setup(warnings:bool=True, config=None, quiet=False, tool=None) ->
168
168
  if tool and tool != name:
169
169
  continue # if called with tool=(some_name), then only load that tool.
170
170
 
171
+ util.debug(f"Checking for ability to run tool: {name}")
171
172
  exe = value.get('exe', str())
172
173
  if type(exe) is list:
173
174
  exe_list = exe
@@ -183,12 +184,14 @@ def auto_tool_setup(warnings:bool=True, config=None, quiet=False, tool=None) ->
183
184
  spec = importlib.util.find_spec(pkg)
184
185
  if not spec:
185
186
  has_all_py = False
187
+ util.debug(f"... No, missing pkg {spec}")
186
188
 
187
189
  has_all_env = True
188
190
  requires_env_list = value.get('requires_env', list())
189
191
  for env in requires_env_list:
190
192
  if not os.environ.get(env, ''):
191
193
  has_all_env = False
194
+ util.debug(f"... No, missing env {env}")
192
195
 
193
196
  has_all_exe = True
194
197
  for exe in exe_list:
@@ -196,6 +199,7 @@ def auto_tool_setup(warnings:bool=True, config=None, quiet=False, tool=None) ->
196
199
  p = shutil.which(exe)
197
200
  if not p:
198
201
  has_all_exe = False
202
+ util.debug(f"... No, missing exe {exe}")
199
203
 
200
204
  if has_all_exe:
201
205
  requires_cmd_list = value.get('requires_cmd', list())
@@ -209,6 +213,7 @@ def auto_tool_setup(warnings:bool=True, config=None, quiet=False, tool=None) ->
209
213
  has_all_exe = False
210
214
  except:
211
215
  has_all_exe = False
216
+ util.debug(f"... No, exception running {cmd_list}")
212
217
 
213
218
 
214
219
  if all([has_all_py, has_all_env, has_all_exe]):
@@ -358,7 +363,7 @@ def process_tokens(tokens: list, original_args: list, config: dict, interactive=
358
363
  util.debug(f'{command=}')
359
364
  util.debug(f'{sco.config=}')
360
365
  util.debug(f'{type(sco)=}')
361
- if not parsed.tool:
366
+ if not parsed.tool and command not in config.get('command_determines_tool', []):
362
367
  use_tool = which_tool(command, config)
363
368
  util.info(f"--tool not specified, using default for {command=}: {use_tool}")
364
369
 
opencos/eda_base.py CHANGED
@@ -119,11 +119,9 @@ class Tool:
119
119
  self.defines = {}
120
120
  self.args.update({
121
121
  'tool': self._TOOL, # Set for all derived classes.
122
- 'xilinx': False,
123
122
  })
124
123
  self.args_help.update({
125
124
  'tool': 'Tool to use for this command, such as: verilator',
126
- 'xilinx': 'Set to use XPMs, and other global Xilinx specific cells/libraries',
127
125
  })
128
126
  # update self._EXE if config says to:
129
127
  self.set_exe(config)
@@ -224,22 +222,30 @@ class Command:
224
222
  return which_tool(command, config=self.config)
225
223
 
226
224
  def create_work_dir(self):
225
+ util.debug(f"create_work_dir: {self.args['eda-dir']=} {self.args['work-dir']=}")
227
226
  if (not os.path.exists(self.args['eda-dir'])): # use os.path.isfile / isdir also
228
227
  os.mkdir(self.args['eda-dir'])
228
+ util.info(f"create_work_dir: created {self.args['eda-dir']}")
229
229
  if self.args['design'] == "":
230
230
  if ('top' in self.args) and (self.args['top'] != ""):
231
231
  self.args['design'] = self.args['top']
232
+ util.debug(f"create_work_dir: set {self.args['design']=} from {self.args['top']=}, since it was empty")
232
233
  else:
233
234
  self.args['design'] = "design" # generic, i.e. to create work dir "design_upload"
235
+ util.debug(f"create_work_dir: set {self.args['design']=} to 'design', since it was empty and we have no top")
234
236
  if self.target == "":
235
237
  self.target = self.args['design']
238
+ util.debug(f"create_work_dir: set {self.target=} from design name, since it was empty")
236
239
  if self.args['work-dir'] == '':
237
240
  if self.args['sub-work-dir'] == '':
238
241
  if self.args['job-name'] != '':
239
242
  self.args['sub-work-dir'] = self.args['job-name']
243
+ util.debug(f"create_work_dir: set {self.args['sub-work-dir']=} from {self.args['job-name']=}, since it was empty")
240
244
  else:
241
245
  self.args['sub-work-dir'] = f'{self.target}.{self.command_name}'
246
+ util.debug(f"create_work_dir: set {self.args['sub-work-dir']=} from {self.target=} and {self.command_name=}, since it was empty and we have no job-name")
242
247
  self.args['work-dir'] = os.path.join(self.args['eda-dir'], self.args['sub-work-dir'])
248
+ util.debug(f"create_work_dir: set {self.args['work-dir']=}")
243
249
  keep_file = os.path.join(self.args['work-dir'], "eda.keep")
244
250
  if (os.path.exists(self.args['work-dir'])):
245
251
  if os.path.exists(keep_file) and not self.args['force']:
@@ -247,9 +253,11 @@ class Command:
247
253
  util.info(f"Removing previous '{self.args['work-dir']}'")
248
254
  shutil.rmtree(self.args['work-dir'])
249
255
  os.mkdir(self.args['work-dir'])
256
+ util.debug(f'create_work_dir: created {self.args["work-dir"]}')
250
257
  if (self.args['keep']):
251
258
  open(keep_file, 'w').close()
252
- util.info(f'Creating work-dir: {self.args["work-dir"]=}')
259
+ util.debug(f'create_work_dir: created {keep_file}')
260
+ util.info(f'create_work_dir: created {self.args["work-dir"]}')
253
261
  return self.args['work-dir']
254
262
 
255
263
  def exec(self, work_dir, command_list, background=False, stop_on_error=True,
@@ -390,6 +398,12 @@ class Command:
390
398
  else:
391
399
  assert False, f'{key=} {value=} how do we do argparse for this type of value?'
392
400
 
401
+ # TODO(drew): it might be nice to support positional args here as a list
402
+ # self.target_args (files/targets/patterns), something like:
403
+ # parser.add_argument(
404
+ # 'targets', nargs='+', help='positional arg for targets/files/pattern'
405
+ # )
406
+
393
407
  return parser
394
408
 
395
409
 
@@ -682,8 +696,12 @@ class CommandDesign(Command):
682
696
  util.info(f'run_dep_shell_commands {iter=}: {d=}')
683
697
  clist = util.ShellCommandList(d['exec_list'])
684
698
  # NOTE(drew): shell=True subprocess call, can disable with self.config
685
- self.exec(self.args['work-dir'], clist, tee_fpath=clist.tee_fpath,
686
- shell=self.config.get('deps_subprocess_shell', False))
699
+ # However, in Windows, we seem to have to run these with shell=False
700
+ if sys.platform.startswith('win'):
701
+ self.exec(self.args['work-dir'], clist, shell=False)
702
+ else:
703
+ self.exec(self.args['work-dir'], clist, tee_fpath=clist.tee_fpath,
704
+ shell=self.config.get('deps_subprocess_shell', False))
687
705
 
688
706
  def update_file_lists_for_work_dir(self):
689
707
  if len(self.dep_work_dir_add_srcs) == 0:
@@ -718,7 +736,10 @@ class CommandDesign(Command):
718
736
  self.error(f'Non-source file (reqs?) {relfname=} does not exist from {caller_info}')
719
737
  elif not os.path.exists(destfile):
720
738
  util.debug(f'updating non-source file to work-dir: Linked {fname=} to {destfile=}, from {caller_info}')
721
- os.symlink(src=fname, dst=destfile)
739
+ if sys.platform == "win32":
740
+ shutil.copyfile(fname, destfile) # On Windows, fall back to copying
741
+ else:
742
+ os.symlink(src=fname, dst=destfile)
722
743
 
723
744
  def get_top_name(self, name):
724
745
  return os.path.splitext(os.path.basename(name))[0]
@@ -731,7 +752,7 @@ class CommandDesign(Command):
731
752
  to unprocessed-plusargs.
732
753
  '''
733
754
 
734
- # Since this may be coming from a raw CLI/bash argparser, we may have
755
+ # Since this may be coming from a raw CLI/bash/powershell argparser, we may have
735
756
  # args that come from shlex.quote(token), such as:
736
757
  # token = '\'+define+OC_ROOT="/foo/bar/opencos"\''
737
758
  # So we strip all outer ' or " on the plusarg:
@@ -836,10 +857,8 @@ class CommandDesign(Command):
836
857
  util.debug("Entered resolve_target(%s)" % (target))
837
858
  # self.target is a name we grab for the job (i.e. for naming work dir etc). we don't want the path prefix.
838
859
  # TODO: too messy -- there's also a self.target, args['job-name'], args['work-dir'], args['design'], args['top'], args['sub-work-dir'] ...
839
- self.target = target
840
- m = re.match(r'.*\/([\w\-]+)$', self.target)
841
- if m: self.target = m.group(1)
842
860
 
861
+ self.target = os.path.basename(target)
843
862
 
844
863
  if target in self.targets_dict:
845
864
  # If we're encountered this target before, stop. We're not traversing again.
opencos/eda_config.py CHANGED
@@ -29,6 +29,7 @@ class Defaults:
29
29
  'tools',
30
30
  'auto_tools_order',
31
31
  'file_extensions',
32
+ 'command_determines_tool',
32
33
  ])
33
34
  supported_config_auto_tools_order_keys = set([
34
35
  'exe', 'handlers', 'requires_env', 'requires_py', 'requires_cmd',
@@ -46,6 +46,9 @@ file_extensions:
46
46
  - .vhd
47
47
  - .vhdl
48
48
 
49
+ command_determines_tool:
50
+ # eda commands that will self-determine the tool to use.
51
+ - waves
49
52
 
50
53
 
51
54
  tools:
@@ -92,8 +95,8 @@ tools:
92
95
  --autoflush
93
96
  -j 2
94
97
  -sv
95
- # Note that we do NOT use --coverage or --coverage-line, until verilator 5920 is resolved.
96
98
  compile-coverage-args: |
99
+ --coverage-line
97
100
  --coverage-toggle
98
101
  --coverage-underscore
99
102
  --coverage-user
@@ -1,9 +1,20 @@
1
1
  #!/usr/bin/env python3
2
- import yaml, toml, json
3
- import sys, os
2
+
3
+ '''
4
+ Helper pymodule used by eda_deps_bash_completion.bash, extracts valid
5
+ targets from DEPS files
6
+ '''
7
+
8
+ import sys
9
+ import os
10
+ import json
11
+
12
+ import yaml
13
+ import toml
14
+
4
15
  from opencos.deps_helpers import get_deps_markup_file
5
16
 
6
- def get_markup_table_keys(partial_path='./'):
17
+ def get_markup_table_keys(partial_path='./') -> list:
7
18
  '''Returns a list of root level keys for DEPS.[yml|yaml|toml|json]
8
19
 
9
20
  Does not include DEFAULTS.
@@ -26,15 +37,15 @@ def get_markup_table_keys(partial_path='./'):
26
37
  _, file_ext = os.path.splitext(filepath)
27
38
  try:
28
39
  if file_ext in ['', '.yml', 'yaml']:
29
- with open(filepath, 'r') as f:
40
+ with open(filepath, 'r', encoding='utf-8') as f:
30
41
  data = yaml.safe_load(f)
31
42
  elif file_ext == '.toml':
32
43
  data = toml.load(filepath)
33
44
  elif file_ext == '.json':
34
- with open(filepath) as f:
45
+ with open(filepath, 'r', encoding='utf-8') as f:
35
46
  data = json.load(f)
36
- except:
37
- pass
47
+ except Exception: # pylint: disable=broad-exception-caught
48
+ return []
38
49
 
39
50
  if not isinstance(data, dict):
40
51
  # We found a DEPS file, but it wasn't a table/dict so we can't return root keys
@@ -48,11 +59,17 @@ def get_markup_table_keys(partial_path='./'):
48
59
  if not partial_path.endswith('/'):
49
60
  prepend += '/'
50
61
 
51
- # Return the list of keys w/ prepended path information, and don't include 'DEFAULTS'
52
- return [prepend + x for x in list(data.keys()) if x.startswith(partial_target) and x != 'DEFAULTS']
62
+ # Return the list of keys w/ prepended path information, and don't include
63
+ # uppercase strings like 'DEFAULTS' or 'METADATA'
64
+ return [
65
+ prepend + x for x in list(data.keys()) if x.startswith(partial_target) and not x.isupper()
66
+ ]
67
+
53
68
 
69
+ def main() -> None:
70
+ '''Returns None, prints DEPS keys space separated, uses sys.argv for a single
71
+ partial path arg (DEPS file to examine)'''
54
72
 
55
- def main():
56
73
  if len(sys.argv) > 1:
57
74
  partial_path = sys.argv[1]
58
75
  else:
opencos/files.py CHANGED
@@ -33,14 +33,12 @@ def get_source_file(target:str) -> (bool, str, str):
33
33
  # target exists as a file, return True w/ original target:
34
34
  return True, target, ''
35
35
 
36
- if any(target.startswith(x) for x in ALL_FORCED_PREFIXES):
37
- parts = target.split('@')
38
- ext = parts[0]
39
- fpath = '@'.join(parts[1:]) # if target str had mulitple @'s
40
- if os.path.exists(fpath):
41
- # target exists as ext@fpath, return True b/c fpath exists,
42
- # along with the fpath and forced ext to use:
43
- return True, fpath, FORCE_PREFIX_DICT.get(ext + '@', '')
36
+ if '@' in target:
37
+ for p in ALL_FORCED_PREFIXES:
38
+ if p in target:
39
+ fpath = ''.join(target.split(p)) # essentially just removing the "sv@" or whatever it is
40
+ if os.path.exists(fpath):
41
+ return True, fpath, FORCE_PREFIX_DICT.get(p)
44
42
 
45
43
  # target or fpath didn't exist, return False with the original target:
46
44
  return False, target, ''
opencos/names.py CHANGED
@@ -31,6 +31,7 @@ table = {
31
31
  3: "U50",
32
32
  4: "U55N",
33
33
  5: "U50C",
34
+ 6: "PYNQ-Z2",
34
35
  },
35
36
 
36
37
  'OC_LIBRARY': {
opencos/oc_cli.py CHANGED
@@ -657,7 +657,7 @@ class BlockLED(Block):
657
657
  count = data & 0xff
658
658
  self.dump_reg(0, 0x0004, "Prescale", [ [9,0,"CYCLES"] ])
659
659
  for i in range(count):
660
- self.dump_reg(0, 0x0008+i, "LedControl%d" % i, [ [18,16,"BLINKS"], [13,8,"BRIGHT"], [1,0,"MODE"] ])
660
+ self.dump_reg(0, 0x0008+(i*4), "LedControl%d" % i, [ [18,16,"BLINKS"], [13,8,"BRIGHT"], [1,0,"MODE"] ])
661
661
 
662
662
  def command_set(self, parts=[], help_line=False):
663
663
  if help_line: return "Set the state of the led: [on|off|blink|heartbeat] [brightness=0-63]";
@@ -700,6 +700,143 @@ class BlockLED(Block):
700
700
  for i in range(self.numled): self.csr_write32(0, 8 + ( i*4), ((blinks<<16) | (bright<<8) | command) )
701
701
  else: self.csr_write32(0, 8 + (led*4), ((blinks<<16) | (bright<<8) | command) )
702
702
 
703
+ class BlockRGB(Block):
704
+ def __init__(self, channel, blockid):
705
+ Block.__init__(self, channel, blockid, "RGB")
706
+
707
+ def connect(self):
708
+ Block.connect(self)
709
+ data = self.csr_read32(0, 0)
710
+ self.csrid = ((data >> 16) & 0xffff)
711
+ self.numrgb = ((data) & 0xff)
712
+ util.debug(f"Connect: {self} CsrId={self.csrid} NumRgb={self.numrgb}")
713
+
714
+ def rgb_status(self, rgb):
715
+ data = self.csr_read32(0, 8 + (rgb*4))
716
+ red = ((data >> 24) & 0x3f)
717
+ green = ((data >>16) & 0x3f)
718
+ blue = ((data >> 8) & 0x3f)
719
+ if (data & 0x1) == 1: return (f"on ({red:3d} {green:3d} {blue:3d})")
720
+ elif (data & 0x2) == 2: return (f"blink ({red:3d} {green:3d} {blue:3d})")
721
+ elif (data & 0x3) == 3: return (f"heartbeat ({red:3d} {green:3d} {blue:3d})")
722
+ return "off"
723
+
724
+ def command_show(self, parts=[], help_line=False):
725
+ if help_line: return "Shows high level status of the block in human readable form";
726
+ for i in range(self.numrgb):
727
+ util.info(f"RGB {i:3}: {self.rgb_status(i):20}")
728
+
729
+ def command_dump(self, parts=[], help_line=False):
730
+ if help_line: return "Dumps detailed config/status info of the block in human readable form";
731
+ self.info(f"Dumping {self}:")
732
+ data = self.dump_reg(0, 0x0000, "OcID", [ [31,16,"ID"], [7,0,"RGB_COUNT"] ])
733
+ count = data & 0xff
734
+ self.dump_reg(0, 0x0004, "Prescale", [ [9,0,"CYCLES"] ])
735
+ for i in range(count):
736
+ self.dump_reg(0, 0x0008+(i*4), "RgbControl%d" % i, [ [24,24,"RED"], [16,16,"GREEN"], [8,8,"BLUE"], [0,0,"MODE"] ])
737
+
738
+ def command_set(self, parts=[], help_line=False):
739
+ if help_line: return "Set the state of the rgb: [on|off|blink|heartbeat] [red=0-63] [green=0-63] [blue=0-63]";
740
+ rgb = -1
741
+ red = 0x3f
742
+ green = 0x3f
743
+ blue = 0x3f
744
+ command = -1 # report status
745
+ prescale = -1
746
+ while (len(parts)):
747
+ cmd = parts.pop(0)
748
+ if (eval_regex(r'^(\d+)$', cmd)):
749
+ rgb = eval_number(m.group(1))
750
+ elif (eval_regex(r'^status$', cmd)):
751
+ command = -1
752
+ elif (eval_regex(r'^off$', cmd)):
753
+ command = 0
754
+ elif (eval_regex(r'^on$', cmd)):
755
+ command = 1
756
+ elif (eval_regex(r'^blink$', cmd)):
757
+ command = 2
758
+ elif (eval_regex(r'^(?:heart)?beat$', cmd)):
759
+ command = 3
760
+ elif (eval_regex(r'^prescale=(.+)', cmd)):
761
+ prescale = eval_number(m.group(1), check_uint_bits=10, check_int_min=5)
762
+ elif (eval_regex(r'^red=(.+)', cmd)):
763
+ red = eval_number(m.group(1), check_uint_bits=6)
764
+ elif (eval_regex(r'^green=(.+)', cmd)):
765
+ green = eval_number(m.group(1), check_uint_bits=6)
766
+ elif (eval_regex(r'^blue=(.+)', cmd)):
767
+ blue = eval_number(m.group(1), check_uint_bits=6)
768
+ else:
769
+ util.error(f"RGB didn't understand arg(s): {parts}", do_exit=False)
770
+ raise HandledError
771
+ if prescale != -1:
772
+ self.csr_write32(0, 4, prescale)
773
+ if command == -1: # we reporting status
774
+ if rgb == -1: # on all RGBs
775
+ for i in range(self.numrgb): print("RGB %3d : %s" % ( i, self.rgb_status( i)))
776
+ else: print("RGB %3d : %s" % (rgb, self.rgb_status(rgb)))
777
+ else:
778
+ if rgb == -1: # on all RGBs
779
+ for i in range(self.numrgb): self.csr_write32(0, 8 + ( i*4), ((blue<<24) | (green<<16) | (red<<8) | command) )
780
+ else: self.csr_write32(0, 8 + (rgb*4), ((blue<<24) | (green<<16) | (red<<8) | command) )
781
+
782
+ class BlockToggle(Block):
783
+ def __init__(self, channel, blockid):
784
+ Block.__init__(self, channel, blockid, "Toggle")
785
+
786
+ def connect(self):
787
+ Block.connect(self)
788
+ data = self.csr_read32(0, 0)
789
+ self.csrid = ((data >> 16) & 0xffff)
790
+ self.numtoggle = ((data) & 0xff)
791
+ util.debug(f"Connect: {self} CsrId={self.csrid} NumToggle={self.numtoggle}")
792
+
793
+ def toggle_status(self, toggle):
794
+ data = self.csr_read32(0, 4 + (toggle*4))
795
+ if (data & 0x80000000): return "on"
796
+ return "off"
797
+
798
+ def command_show(self, parts=[], help_line=False):
799
+ if help_line: return "Shows high level status of the block in human readable form";
800
+ for i in range(self.numtoggle):
801
+ util.info(f"Toggle {i:3}: {self.toggle_status(i):20}")
802
+
803
+ def command_dump(self, parts=[], help_line=False):
804
+ if help_line: return "Dumps detailed config/status info of the block in human readable form";
805
+ self.info(f"Dumping {self}:")
806
+ data = self.dump_reg(0, 0x0000, "OcID", [ [31,16,"ID"], [7,0,"TOGGLE_COUNT"] ])
807
+ count = data & 0xff
808
+ for i in range(count):
809
+ self.dump_reg(0, 0x0004+(i*4), "ToggleStatus%d" % i, [ [31,31,"STATE"], [30,30,"EVENT"], [15,0,"COUNT"] ])
810
+
811
+ class BlockButton(Block):
812
+ def __init__(self, channel, blockid):
813
+ Block.__init__(self, channel, blockid, "Button")
814
+
815
+ def connect(self):
816
+ Block.connect(self)
817
+ data = self.csr_read32(0, 0)
818
+ self.csrid = ((data >> 16) & 0xffff)
819
+ self.numbutton = ((data) & 0xff)
820
+ util.debug(f"Connect: {self} CsrId={self.csrid} NumButton={self.numbutton}")
821
+
822
+ def button_status(self, button):
823
+ data = self.csr_read32(0, 4 + (button*4))
824
+ if (data & 0x80000000): return "on"
825
+ return "off"
826
+
827
+ def command_show(self, parts=[], help_line=False):
828
+ if help_line: return "Shows high level status of the block in human readable form";
829
+ for i in range(self.numbutton):
830
+ util.info(f"Button {i:3}: {self.button_status(i):20}")
831
+
832
+ def command_dump(self, parts=[], help_line=False):
833
+ if help_line: return "Dumps detailed config/status info of the block in human readable form";
834
+ self.info(f"Dumping {self}:")
835
+ data = self.dump_reg(0, 0x0000, "OcID", [ [31,16,"ID"], [7,0,"BUTTON_COUNT"] ])
836
+ count = data & 0xff
837
+ for i in range(count):
838
+ self.dump_reg(0, 0x0004+(i*4), "ButtonStatus%d" % i, [ [31,31,"STATE"], [30,30,"EVENT"], [15,0,"COUNT"] ])
839
+
703
840
  class BlockIIC(Block):
704
841
  def __init__(self, channel, blockid):
705
842
  Block.__init__(self, channel, blockid, "IIC")
@@ -1498,6 +1635,11 @@ block_table[ 8] = { 'name' : 'Fan' , 'csrid' : 8, 'handler' : Block
1498
1635
  block_table[ 9] = { 'name' : 'HBM' , 'csrid' : 9, 'handler' : Block }
1499
1636
  block_table[10] = { 'name' : 'CMAC' , 'csrid' : 10, 'handler' : Block }
1500
1637
  block_table[11] = { 'name' : 'PCIe' , 'csrid' : 11, 'handler' : Block }
1638
+ block_table[12] = { 'name' : 'Eth1G' , 'csrid' : 12, 'handler' : Block }
1639
+ block_table[13] = { 'name' : 'Eth10G' , 'csrid' : 13, 'handler' : Block }
1640
+ block_table[14] = { 'name' : 'RGB' , 'csrid' : 14, 'handler' : BlockRGB }
1641
+ block_table[15] = { 'name' : 'Toggle' , 'csrid' : 15, 'handler' : BlockToggle }
1642
+ block_table[16] = { 'name' : 'Button' , 'csrid' : 16, 'handler' : BlockButton }
1501
1643
 
1502
1644
  # **************************************************************
1503
1645
  # *** Channels
@@ -2,7 +2,6 @@
2
2
 
3
3
  import sys
4
4
  import os
5
- import subprocess
6
5
 
7
6
  def run(file_in, file_out):
8
7
  with open(file_in) as f: