opencos-eda 0.2.36__py3-none-any.whl → 0.2.38__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.
@@ -17,6 +17,7 @@ from .sweep import CommandSweep
17
17
  from .synth import CommandSynth
18
18
  from .upload import CommandUpload
19
19
  from .waves import CommandWaves
20
+ from .targets import CommandTargets
20
21
 
21
22
  __all__ = [
22
23
  'CommandBuild',
@@ -32,4 +33,5 @@ __all__ = [
32
33
  'CommandToolsMulti',
33
34
  'CommandUpload',
34
35
  'CommandWaves',
36
+ 'CommandTargets',
35
37
  ]
@@ -0,0 +1,49 @@
1
+ '''opencos.commands.targets - command handler for: eda targets [args]
2
+
3
+ Note this command is handled differently than others (such as CommandSim),
4
+ it is generally run as simply
5
+
6
+ > eda targets
7
+ > eda targets <directory>
8
+ > eda targets [directory/]<pattern> [directory2/]<pattern2> ...
9
+
10
+ uses no tools and will print a pretty list of targets to stdout.
11
+ '''
12
+
13
+ # Note - similar code waiver, tricky to eliminate it with inheritance when
14
+ # calling reusable methods.
15
+ # pylint: disable=R0801
16
+
17
+ import os
18
+
19
+ from opencos import eda_extract_targets
20
+ from opencos.eda_base import Command
21
+
22
+
23
+ class CommandTargets:
24
+ '''command handler for: eda targets'''
25
+
26
+ command_name = 'targets'
27
+
28
+ def __init__(self, config: dict):
29
+ # We don't inherit opencos.eda_base.Command, so we have to set a few
30
+ # member vars for Command.help to work.
31
+ self.args = {}
32
+ self.args_help = {}
33
+ self.config = config
34
+ self.status = 0
35
+
36
+ def process_tokens( # pylint: disable=unused-argument
37
+ self, tokens: list, process_all: bool = True,
38
+ pwd: str = os.getcwd()
39
+ ) -> list:
40
+ '''This is effectively our 'run' method, entrypoint from opencos.eda.main'''
41
+
42
+ eda_extract_targets.run(partial_paths=tokens, base_path=pwd)
43
+ return []
44
+
45
+ def help(self, tokens: list) -> None:
46
+ '''Since we don't inherit from opencos.eda_base.Command, need our own help
47
+ method
48
+ '''
49
+ Command.help(self, tokens=tokens)
opencos/eda.py CHANGED
@@ -10,6 +10,7 @@ import re
10
10
  import signal
11
11
  import argparse
12
12
  import shlex
13
+ import importlib.util
13
14
 
14
15
  import opencos
15
16
  from opencos import util, files
@@ -19,7 +20,6 @@ from opencos.eda_base import Tool, which_tool
19
20
 
20
21
  # Globals
21
22
 
22
- debug_respawn = False
23
23
  util.progname = "EDA"
24
24
 
25
25
 
@@ -30,37 +30,25 @@ util.progname = "EDA"
30
30
  # eda command (such as, command: eda sim) is handled by which class (such as class: CommandSim)
31
31
  # These are also overriden depending on the tool, for example --tool verilator sets
32
32
  # "sim": CommandSimVerilator.
33
- def init_config(config: dict, quiet=False, tool=None) -> dict:
33
+ def init_config(
34
+ config: dict, quiet: bool = False, tool=None, run_auto_tool_setup: bool = True
35
+ ) -> dict:
34
36
  '''Sets or clears entries in config (dict) so tools can be re-loaded.'''
35
37
 
36
- from opencos.commands import CommandSim, CommandElab, CommandSynth, \
37
- CommandFList, CommandProj, CommandMulti, CommandToolsMulti, \
38
- CommandSweep, CommandBuild, CommandWaves, CommandUpload, CommandOpen, \
39
- CommandExport
40
-
41
- # If config didn't set the auto_tools_order, then use a fully populated default
42
- # dict:
43
- config = eda_config.get_config_merged_with_defaults(config)
44
-
45
- config['command_handler'] = {
46
- "sim" : CommandSim,
47
- "elab" : CommandElab,
48
- "synth" : CommandSynth,
49
- "flist" : CommandFList,
50
- "proj" : CommandProj,
51
- "multi" : CommandMulti,
52
- "tools-multi" : CommandToolsMulti,
53
- "sweep" : CommandSweep,
54
- "build" : CommandBuild,
55
- "waves" : CommandWaves,
56
- "upload" : CommandUpload,
57
- "open" : CommandOpen,
58
- "export" : CommandExport,
59
- }
38
+ # For key DEFAULT_HANDLERS, we'll update config['command_handler'] with
39
+ # the actual class using importlib (via opencos.util)
40
+ config['command_handler'] = {}
41
+ for command, str_class in config['DEFAULT_HANDLERS'].items():
42
+ cls = util.import_class_from_string(str_class)
43
+ if not cls:
44
+ util.error(f"config DEFAULT_HANDLERS {command=} {str_class=} could not import")
45
+ else:
46
+ config['command_handler'][command] = cls
60
47
 
61
48
  config['auto_tools_found'] = dict()
62
49
  config['tools_loaded'] = set()
63
- config = auto_tool_setup(config=config, quiet=quiet, tool=tool)
50
+ if run_auto_tool_setup:
51
+ config = auto_tool_setup(config=config, quiet=quiet, tool=tool)
64
52
  return config
65
53
 
66
54
 
@@ -154,7 +142,6 @@ def auto_tool_setup(warnings:bool=True, config=None, quiet=False, tool=None) ->
154
142
  tool='verlator', tool='verilator=/path/to/verilator.exe'
155
143
  If so, updates config['auto_tools_order'][tool]['exe']
156
144
  '''
157
- import importlib.util
158
145
 
159
146
  tool = eda_config.update_config_auto_tool_order_for_tool(
160
147
  tool=tool, config=config
@@ -237,7 +224,6 @@ def tool_setup(tool: str, config: dict, quiet: bool = False, auto_setup: bool =
237
224
  tool='verlator', tool='verilator=/path/to/verilator.exe'
238
225
 
239
226
  '''
240
- import importlib
241
227
 
242
228
  tool = eda_config.update_config_auto_tool_order_for_tool(
243
229
  tool=tool, config=config
@@ -281,10 +267,7 @@ def tool_setup(tool: str, config: dict, quiet: bool = False, auto_setup: bool =
281
267
  # skip, already has a tool associated with it, and we're in auto_setup=True
282
268
  continue
283
269
 
284
- if str_class_name not in globals():
285
- cls = util.import_class_from_string(str_class_name)
286
- else:
287
- cls = globals().get(str_class_name, None)
270
+ cls = util.import_class_from_string(str_class_name)
288
271
 
289
272
  assert issubclass(cls, Tool), f'{str_class_name=} is does not have Tool class associated with it'
290
273
  util.debug(f'Setting {cls=} for {command=} in config.command_handler')
@@ -302,6 +285,7 @@ def process_tokens(tokens: list, original_args: list, config: dict, interactive=
302
285
 
303
286
  deferred_tokens = []
304
287
  command = ""
288
+ run_auto_tool_setup = True
305
289
 
306
290
  parser = eda_base.get_argparser()
307
291
  try:
@@ -324,22 +308,29 @@ def process_tokens(tokens: list, original_args: list, config: dict, interactive=
324
308
  if parsed.eda_safe:
325
309
  eda_config.update_config_for_eda_safe(config)
326
310
 
311
+ util.debug(f'eda process_tokens: {parsed=} {unparsed=}')
312
+
313
+ # Attempt to get the 'command' in the unparsed args before we've even
314
+ # set the command handlers (some commands don't use tools).
315
+ for value in unparsed:
316
+ if value in config['DEFAULT_HANDLERS'].keys():
317
+ command = value
318
+ if value in config['command_uses_no_tools']:
319
+ run_auto_tool_setup = False
320
+ unparsed.remove(value)
321
+ break
322
+
327
323
  if not interactive:
328
324
  # Run init_config() now, we deferred it in main(), but only run it
329
325
  # for this tool (or tool=None to figure it out)
330
- config = init_config(config, tool=parsed.tool)
326
+ config = init_config(
327
+ config, tool=parsed.tool,
328
+ run_auto_tool_setup=run_auto_tool_setup
329
+ )
331
330
  if not config:
332
331
  util.error(f'eda.py main: problem loading config, {args=}')
333
332
  return 3
334
333
 
335
-
336
- util.debug(f'eda process_tokens: {parsed=} {unparsed=}')
337
- for value in unparsed:
338
- if value in config['command_handler'].keys():
339
- command = value
340
- unparsed.remove(value)
341
- break
342
-
343
334
  # Deal with help, now that we have the command (if it was set).
344
335
  if parsed.help:
345
336
  if not command:
@@ -363,7 +354,9 @@ def process_tokens(tokens: list, original_args: list, config: dict, interactive=
363
354
  util.debug(f'{command=}')
364
355
  util.debug(f'{sco.config=}')
365
356
  util.debug(f'{type(sco)=}')
366
- if not parsed.tool and command not in config.get('command_determines_tool', []):
357
+ if not parsed.tool and \
358
+ command not in config.get('command_determines_tool', []) and \
359
+ command not in config.get('command_uses_no_tools', []):
367
360
  use_tool = which_tool(command, config)
368
361
  util.info(f"--tool not specified, using default for {command=}: {use_tool}")
369
362
 
@@ -377,7 +370,7 @@ def process_tokens(tokens: list, original_args: list, config: dict, interactive=
377
370
  sco.config['eda_original_args'] = original_args
378
371
 
379
372
  setattr(sco, 'command_name', command) # as a safeguard, b/c 'command' is not always passed to 'sco'
380
- unparsed = sco.process_tokens(deferred_tokens)
373
+ unparsed = sco.process_tokens(tokens=deferred_tokens, pwd=os.getcwd())
381
374
 
382
375
  # query the status from the Command object (0 is pass, > 0 is fail)
383
376
  rc = getattr(sco, 'status', 1)
@@ -439,7 +432,6 @@ def main(*args):
439
432
  # Handle --config-yml= arg
440
433
  config, unparsed = eda_config.get_eda_config(unparsed)
441
434
 
442
-
443
435
  # Note - we used to call: config = init_config(config=config)
444
436
  # However, we now defer calling init_config(..) until eda.process_tokens(..)
445
437
 
@@ -459,22 +451,8 @@ def main(*args):
459
451
  config=config)
460
452
 
461
453
 
462
- def main_cli(support_respawn=False):
454
+ def main_cli() -> None:
463
455
  ''' Returns None, will exit with return code. Entry point for package script or __main__.'''
464
-
465
- if support_respawn and '--no-respawn' not in sys.argv:
466
- # If someone called eda.py directly (aka, __name__ == '__main__'),
467
- # then we still support a legacy mode of operation - where we check
468
- # for OC_ROOT (in env, or git repo) to make sure this is the right
469
- # location of eda.py by calling main_cli(support_respawn=True).
470
- # Otherwise, we do not respawn $OC_ROOT/bin/eda.py
471
- # Can also be avoided with --no-respawn.
472
-
473
- # Note - respawn will never work if calling as a package executable script,
474
- # which is why our package entrypoint will be main_cli() w/out support_respawn.
475
- main_maybe_respawn()
476
-
477
-
478
456
  signal.signal(signal.SIGINT, signal_handler)
479
457
  util.global_exit_allowed = True
480
458
  # Strip eda or eda.py from sys.argv, we know who we are if called from __main__:
@@ -482,46 +460,8 @@ def main_cli(support_respawn=False):
482
460
  util.exit(rc)
483
461
 
484
462
 
485
- def main_maybe_respawn():
486
- ''' Returns None, will respawn - run - exit, or will return and the command
487
-
488
- is expected to run in main_cli()'''
489
-
490
- # First we check if we are respawning
491
- this_path = os.path.realpath(__file__)
492
- if debug_respawn: util.info(f"RESPAWN: this_path : '{this_path}'")
493
- oc_root = util.get_oc_root()
494
- if debug_respawn: util.info(f"RESPAWN: oc_root : '{oc_root}'")
495
- cwd = util.getcwd()
496
- if debug_respawn: util.info(f"RESPAWN: cwd : '{cwd}'")
497
- if oc_root:
498
- new_paths = [
499
- os.path.join(oc_root, 'opencos', 'eda.py'),
500
- os.path.join(oc_root, 'bin', 'eda'),
501
- ]
502
- if debug_respawn: util.info(f"RESPAWN: {new_paths=} {this_path=}")
503
- if this_path not in new_paths and os.path.exists(new_paths[0]):
504
- # we are not the correct version of EDA for this Git repo, we should respawn
505
- util.info(f"{this_path} respawning {new_paths[0]} in {cwd} with --no-respawn")
506
- sys.argv[0] = new_paths[0]
507
- sys.argv.insert(1, '--no-respawn')
508
- proc = subprocess.Popen(sys.argv, shell=0, cwd=cwd, universal_newlines=True)
509
- while True:
510
- try:
511
- proc.communicate()
512
- break
513
- except KeyboardInterrupt:
514
- continue
515
- # get exit status from proc and return it
516
- util.exit(proc.returncode, quiet=True)
517
- else:
518
- if debug_respawn: util.info(f"RESPAWN: {oc_root=} respawn not necessary")
519
- else:
520
- if debug_respawn: util.info("RESPAWN: respawn not necessary")
521
-
522
-
523
463
  if __name__ == '__main__':
524
- main_cli(support_respawn=True)
464
+ main_cli()
525
465
 
526
466
  # IDEAS:
527
467
  # * options with no default (i.e. if user doesn't override, THEN we set it, like "seed" or "work-dir") can be given a
opencos/eda_base.py CHANGED
@@ -550,7 +550,7 @@ class Command:
550
550
  self.set_tool_defines()
551
551
 
552
552
 
553
- def help(self, tokens: list = []):
553
+ def help(self, tokens: list = []) -> None:
554
554
  '''Since we don't quite follow standard argparger help()/usage(), we'll format our own
555
555
 
556
556
  if self.args_help has additional help information.
@@ -569,8 +569,13 @@ class Command:
569
569
 
570
570
  print_base_help()
571
571
  lines = []
572
+ if not self.args:
573
+ print(f'Unparsed args: {tokens}')
574
+ return
575
+
572
576
  if self.command_name:
573
- lines.append(f"Generic help for command='{self.command_name}' (using '{self.__class__.__name__}')")
577
+ lines.append(f"Generic help for command='{self.command_name}'"
578
+ f" (using '{self.__class__.__name__}')")
574
579
  else:
575
580
  lines.append(f"Generic help (from class Command):")
576
581
 
@@ -752,7 +757,7 @@ class CommandDesign(Command):
752
757
  # token = '\'+define+OC_ROOT="/foo/bar/opencos"\''
753
758
  # So we strip all outer ' or " on the plusarg:
754
759
  plusarg = util.strip_outer_quotes(plusarg)
755
- if pwd is None:
760
+ if not pwd:
756
761
  pwd = ''
757
762
 
758
763
  if plusarg.startswith('+define+'):
opencos/eda_config.py CHANGED
@@ -15,9 +15,11 @@ class Defaults:
15
15
  home_override_config_yml = os.path.join(
16
16
  os.environ.get('HOME', ''), '.opencos-eda', 'EDA_CONFIG.yml'
17
17
  )
18
- config_yml = 'eda_config_defaults.yml'
18
+ opencos_config_yml = 'eda_config_defaults.yml'
19
+ config_yml = ''
19
20
 
20
21
  supported_config_keys = set([
22
+ 'DEFAULT_HANDLERS',
21
23
  'defines',
22
24
  'dep_command_enables',
23
25
  'dep_tags_enables',
@@ -26,10 +28,11 @@ class Defaults:
26
28
  'bare_plusarg_supported',
27
29
  'dep_sub',
28
30
  'vars',
29
- 'tools',
30
- 'auto_tools_order',
31
31
  'file_extensions',
32
32
  'command_determines_tool',
33
+ 'command_uses_no_tools',
34
+ 'tools',
35
+ 'auto_tools_order',
33
36
  ])
34
37
  supported_config_auto_tools_order_keys = set([
35
38
  'exe', 'handlers', 'requires_env', 'requires_py', 'requires_cmd',
@@ -53,6 +56,14 @@ class Defaults:
53
56
  ])
54
57
 
55
58
 
59
+ if os.path.exists(Defaults.environ_override_config_yml):
60
+ Defaults.config_yml = Defaults.environ_override_config_yml
61
+ elif os.path.exists(Defaults.home_override_config_yml):
62
+ Defaults.config_yml = Defaults.home_override_config_yml
63
+ else:
64
+ Defaults.config_yml = Defaults.opencos_config_yml
65
+
66
+
56
67
  def find_eda_config_yml_fpath(filename:str, package_search_only=False, package_search_enabled=True) -> str:
57
68
  '''Locates the filename (.yml) either from fullpath provided or from the sys.path
58
69
  opencos package paths.'''
@@ -192,15 +203,9 @@ def get_config_merged_with_defaults(config:dict) -> dict:
192
203
  return default_config
193
204
 
194
205
  def get_argparser() -> argparse.ArgumentParser:
195
- if os.path.exists(Defaults.environ_override_config_yml):
196
- default_config_yml = Defaults.environ_override_config_yml
197
- elif os.path.exists(Defaults.home_override_config_yml):
198
- default_config_yml = Defaults.home_override_config_yml
199
- else:
200
- default_config_yml = Defaults.config_yml
201
206
  parser = argparse.ArgumentParser(prog='opencos eda config options', add_help=False, allow_abbrev=False)
202
- parser.add_argument('--config-yml', type=str, default=default_config_yml,
203
- help=f'YAML filename to use for configuration (default {default_config_yml})')
207
+ parser.add_argument('--config-yml', type=str, default=Defaults.config_yml,
208
+ help=f'YAML filename to use for configuration (default {Defaults.config_yml})')
204
209
  return parser
205
210
 
206
211
  def get_argparser_short_help() -> str:
@@ -213,6 +218,8 @@ def get_eda_config(args:list, quiet=False) -> (dict, list):
213
218
 
214
219
  Handles args for:
215
220
  --config-yml=<YAMLFILE>
221
+
222
+ This will merge the result with the default config (if overriden)
216
223
  '''
217
224
 
218
225
  parser = get_argparser()
@@ -237,5 +244,7 @@ def get_eda_config(args:list, quiet=False) -> (dict, list):
237
244
  else:
238
245
  config = None
239
246
 
247
+ if parsed.config_yml != Defaults.config_yml:
248
+ config = get_config_merged_with_defaults(config)
240
249
 
241
250
  return config, unparsed
@@ -3,6 +3,28 @@
3
3
  # python mergedeep with Strategy.TYPESAFE_REPLACE. I considered doing TYPESAFE_ADDITIVE but then
4
4
  # the user would lose full control over existing list values.
5
5
 
6
+ DEFAULT_HANDLERS:
7
+ # These commands (sim, elab, etc) require a tool, but have a default handler
8
+ # base class:
9
+ sim : opencos.commands.CommandSim
10
+ elab : opencos.commands.CommandElab
11
+ synth : opencos.commands.CommandSynth
12
+ flist : opencos.commands.CommandFList
13
+ proj : opencos.commands.CommandProj
14
+ build : opencos.commands.CommandBuild
15
+ upload : opencos.commands.CommandUpload
16
+ open : opencos.commands.CommandOpen
17
+ # These commands don't necessarily require a tool
18
+ multi : opencos.commands.CommandMulti
19
+ tools-multi : opencos.commands.CommandToolsMulti
20
+ sweep : opencos.commands.CommandSweep
21
+ # These commands (waves, export, targets) do not require a tool, or
22
+ # will self determine the tool:
23
+ waves : opencos.commands.CommandWaves
24
+ export : opencos.commands.CommandExport
25
+ targets : opencos.commands.CommandTargets
26
+
27
+
6
28
  defines: { } # Add these defines to every eda call
7
29
 
8
30
  dep_command_enables:
@@ -47,9 +69,14 @@ file_extensions:
47
69
  - .vhdl
48
70
 
49
71
  command_determines_tool:
50
- # eda commands that will self-determine the tool to use.
72
+ # eda commands that will self-determine the tool to use
51
73
  - waves
52
74
 
75
+ command_uses_no_tools:
76
+ # eda commands that do not use a tool at all, will skip auto_tools_order
77
+ - export
78
+ - targets
79
+
53
80
 
54
81
  tools:
55
82
 
@@ -9,7 +9,6 @@ dep_command_enables:
9
9
  var_subst_os_env: false
10
10
 
11
11
  # Values overriden to false:
12
- deps_legacy_supported: false
13
12
  deps_subprocess_shell: false
14
13
 
15
14
 
@@ -35,7 +35,7 @@ _eda_script_completion() {
35
35
  if [ -z "${completions}" ]; then
36
36
  # If we didn't find anything in a DEPS.[yml|yaml|toml|json], then use:
37
37
  # 1. a bunch of known eda words or args.
38
- eda_words="multi sim elab flist build synth waves proj waves \
38
+ eda_words="multi sim elab flist build synth waves proj waves targets \
39
39
  +define+ +incdirs+ \
40
40
  --help --quiet --verbose --debug \
41
41
  --tool --seed --top --keep --force --fake --lint --work-dir \
@@ -72,12 +72,8 @@ def print_columns_manual(data: list, num_columns: int = 4, auto_columns: bool =
72
72
  if col_index == num_columns - 1 or i == len(data) - 1:
73
73
  print() # New line at the end of a row or end of data
74
74
 
75
-
76
- def run(partial_path: str = '', base_path=str(Path('.'))) -> None:
77
- '''Returns None, prints DEPS keys into pretty columns, using arg
78
-
79
- partial_path as a string filter for target completions.
80
- '''
75
+ def get_path_and_pattern(partial_path: str = '', base_path=str(Path('.'))) -> (str, str):
76
+ '''Returns tuple of (partial_path, partial_target or filter)'''
81
77
  partial_target = ''
82
78
  if not partial_path or partial_path == str(Path('.')):
83
79
  partial_path = PATH_LPREFIX
@@ -86,18 +82,40 @@ def run(partial_path: str = '', base_path=str(Path('.'))) -> None:
86
82
  if not partial_path:
87
83
  partial_path = PATH_LPREFIX
88
84
 
89
- try:
90
- keys = get_all_targets(
91
- dirs=[partial_path],
92
- base_path=base_path,
93
- filter_str=partial_target,
94
- error_on_empty_return=False,
95
- lstrip_path=True
96
- )
97
- except:
98
- keys = []
85
+ return partial_path, partial_target
99
86
 
100
- print_columns_manual(data=keys, num_columns=4, auto_columns=True)
87
+
88
+
89
+ def run(partial_paths: list, base_path=str(Path('.'))) -> None:
90
+ '''Returns None, prints DEPS keys into pretty columns, using arg
91
+
92
+ partial_path as a string filter for target completions.
93
+ '''
94
+
95
+ targets_set = set()
96
+ if not partial_paths:
97
+ partial_paths = [PATH_LPREFIX] # run on current directory.
98
+
99
+ for partial_path in partial_paths:
100
+ partial_path, partial_target = get_path_and_pattern(
101
+ partial_path=partial_path, base_path=base_path
102
+ )
103
+ try:
104
+ keys = get_all_targets(
105
+ dirs=[partial_path],
106
+ base_path=base_path,
107
+ filter_str=partial_target,
108
+ error_on_empty_return=False,
109
+ lstrip_path=True
110
+ )
111
+ except:
112
+ keys = []
113
+ for key in keys:
114
+ targets_set.add(key)
115
+
116
+ data = list(targets_set)
117
+ data.sort()
118
+ print_columns_manual(data=data, num_columns=4, auto_columns=True)
101
119
 
102
120
 
103
121
  def main() -> None:
@@ -109,8 +127,8 @@ def main() -> None:
109
127
  if len(sys.argv) > 1:
110
128
  partial_path = sys.argv[1]
111
129
  else:
112
- partial_path = PATH_LPREFIX
113
- run(partial_path)
130
+ partial_path = ''
131
+ run(partial_paths=[partial_path])
114
132
 
115
133
 
116
134
  if __name__ == "__main__":
opencos/files.py CHANGED
@@ -29,7 +29,7 @@ ALL_FORCED_PREFIXES = set(list(FORCE_PREFIX_DICT.keys()))
29
29
 
30
30
  def get_source_file(target:str) -> (bool, str, str):
31
31
  '''Returns tuple: bool if file exists, filepath str, and optional forced file type str'''
32
- if os.path.exists(target):
32
+ if os.path.isfile(target):
33
33
  # target exists as a file, return True w/ original target:
34
34
  return True, target, ''
35
35
 
@@ -37,7 +37,7 @@ def get_source_file(target:str) -> (bool, str, str):
37
37
  for p in ALL_FORCED_PREFIXES:
38
38
  if p in target:
39
39
  fpath = ''.join(target.split(p)) # essentially just removing the "sv@" or whatever it is
40
- if os.path.exists(fpath):
40
+ if os.path.isfile(fpath):
41
41
  return True, fpath, FORCE_PREFIX_DICT.get(p)
42
42
 
43
43
  # target or fpath didn't exist, return False with the original target:
opencos/tests/helpers.py CHANGED
@@ -142,13 +142,14 @@ class Helpers:
142
142
  # TODO(drew): There are some issues with log_it redirecting stdout from vivado
143
143
  # and modelsim_ase. So this may not work for all tools, you may have to directly
144
144
  # look at eda.work/{target}.sim/sim.log or xsim.log.
145
+ print(f'{os.getcwd()=}')
146
+ print(f'{command_str=}')
145
147
  with open(logfile, 'w', encoding='utf-8') as f:
146
- with redirect_stdout(f):
147
- with redirect_stderr(f):
148
- if use_eda_wrap:
149
- rc = eda_wrap(*(command_str.split()))
150
- else:
151
- rc = eda.main(*(command_str.split()))
148
+ with redirect_stdout(f), redirect_stderr(f):
149
+ if use_eda_wrap:
150
+ rc = eda_wrap(*(command_str.split()))
151
+ else:
152
+ rc = eda.main(*(command_str.split()))
152
153
  print(f'Wrote: {os.path.abspath(logfile)=}')
153
154
  return rc
154
155
 
opencos/tests/test_eda.py CHANGED
@@ -53,6 +53,28 @@ def test_args_sim_default_tool():
53
53
  assert rc == 0
54
54
 
55
55
 
56
+ class TestTargets(Helpers):
57
+ '''Tests for: eda targets'''
58
+
59
+ DEFAULT_DIR = os.path.join(THISPATH, '..', '..', 'lib', 'tests')
60
+
61
+ def test_lib_tests__no_pattern(self):
62
+ '''Test that this works: eda targets'''
63
+ self.chdir()
64
+ rc = self.log_it('targets --debug', use_eda_wrap=False)
65
+ assert rc == 0
66
+ assert self.is_in_log('oclib_fifo_test')
67
+ assert self.is_in_log('oclib_rrarb_test')
68
+
69
+ def test_lib_tests__with_pattern(self):
70
+ '''Test that this works: eda targets oclib_fifo*test'''
71
+ self.chdir()
72
+ rc = self.log_it('targets oclib_fifo*test', use_eda_wrap=False)
73
+ assert rc == 0
74
+ assert self.is_in_log('oclib_fifo_test')
75
+ assert not self.is_in_log('oclib_rrarb_test')
76
+
77
+
56
78
  @pytest.mark.skipif(
57
79
  'verilator' not in tools_loaded, reason="requires verilator"
58
80
  )
opencos/tools/invio.py CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  # pylint: disable=R0801 # (duplicate code in derived classes, such as if-condition return.)
4
4
 
5
- import shutil
6
5
  import importlib.util
7
6
 
8
7
  from opencos import util
@@ -22,11 +21,11 @@ class ToolInvio(Tool):
22
21
  if self._VERSION:
23
22
  return self._VERSION
24
23
 
25
- # We also have to make sure invio-py exists, or that we can import invio within python.
26
- invio_py_path = shutil.which('invio-py')
24
+ # We also have to make sure we can import invio within python.
27
25
  spec = importlib.util.find_spec('invio')
28
- if not spec or not invio_py_path:
29
- self.error('"invio-py" not in path, or invio package not in python env')
26
+ if not spec:
27
+ self.error('"invio" package not in python env')
28
+
30
29
 
31
30
  return super().get_versions()
32
31
 
@@ -5,7 +5,6 @@
5
5
  # pylint: disable=too-many-locals # TODO(drew): fix this later.
6
6
 
7
7
  import os
8
- import shutil
9
8
  import importlib.util
10
9
 
11
10
  from opencos import util
@@ -23,11 +22,10 @@ class ToolInvioYosys(ToolYosys):
23
22
  if self._VERSION:
24
23
  return self._VERSION
25
24
 
26
- # We also have to make sure invio-py exists, or that we can import invio within python.
27
- invio_py_path = shutil.which('invio-py')
25
+ # We also have to make sure we can import invio within python.
28
26
  spec = importlib.util.find_spec('invio')
29
- if not spec or not invio_py_path:
30
- self.error('"invio-py" not in path, or invio package not in python env')
27
+ if not spec:
28
+ self.error('"invio" package not in python env')
31
29
 
32
30
  # run ToolYosys.get_versions() to set up self.yosys_exe, and return the version
33
31
  # str:
@@ -49,6 +47,9 @@ class CommandSynthInvioYosys(CommonSynthYosys, ToolInvioYosys):
49
47
  self.args.update({
50
48
  'invio-blackbox': [], # list of modules that invio/verific will blackbox.
51
49
  })
50
+ self.args_help.update({
51
+ 'invio-blackbox': 'List of modules that invio will blackbox prior to yosys',
52
+ })
52
53
 
53
54
  def write_and_run_yosys_f_files(self, **kwargs) -> None:
54
55
 
@@ -74,13 +75,13 @@ class CommandSynthInvioYosys(CommonSynthYosys, ToolInvioYosys):
74
75
  os.mkdir(fullp)
75
76
 
76
77
  # create yosys.f so we can run a few commands within yosys.
77
- yosys_f_path = os.path.join(work_dir, 'yosys.f')
78
- yosys_v_path = os.path.join(work_dir, 'yosys', invio_dict['v_filename'])
79
-
80
- synth_command = self.args.get('yosys-synth', 'synth')
78
+ yosys_f_path = os.path.join(self.full_work_dir, 'yosys.f')
79
+ self.yosys_v_path = os.path.join(self.yosys_out_dir, invio_dict['v_filename'])
81
80
 
82
81
  with open(yosys_f_path, 'w', encoding='utf-8') as f:
83
82
  lines = []
83
+ if self.args['liberty-file']:
84
+ lines.append('read_liberty -lib ' + self.args['liberty-file'])
84
85
  for path in invio_dict.get('blackbox_files_list', []):
85
86
  # We have to read the verilog files from the invio blackbox_files_list:
86
87
  lines.append(f'read_verilog {path}')
@@ -88,12 +89,9 @@ class CommandSynthInvioYosys(CommonSynthYosys, ToolInvioYosys):
88
89
  # But we may blackbox different cells for yosys synthesis.
89
90
  lines.append(f'blackbox {module}')
90
91
 
92
+
91
93
  lines.append(f'read_verilog {invio_dict["full_v_filename"]}')
92
- lines += self.args.get('yosys-pre-synth', [])
93
- lines += [
94
- synth_command,
95
- f'write_verilog {yosys_v_path}'
96
- ]
94
+ lines += self.get_synth_command_lines()
97
95
  f.write('\n'.join(lines))
98
96
 
99
97
  synth_command_list = util.ShellCommandList(
@@ -105,6 +103,9 @@ class CommandSynthInvioYosys(CommonSynthYosys, ToolInvioYosys):
105
103
  ['python3', invio_dict['full_py_filename']], tee_fpath=invio_dict['full_py_filename']
106
104
  )
107
105
 
106
+ # Optinally create and run a sta.f:
107
+ sta_command_list = self.create_sta_f() # [] or util.ShellCommandList
108
+
108
109
  # We create a run_yosys.sh wrapping these scripts, but we do not run this one.
109
110
  util.write_shell_command_file(
110
111
  dirpath=self.args['work-dir'],
@@ -121,18 +122,25 @@ class CommandSynthInvioYosys(CommonSynthYosys, ToolInvioYosys):
121
122
  # Gives us bash commands with tee and pipstatus:
122
123
  invio_command_list,
123
124
  synth_command_list,
125
+ sta_command_list,
124
126
  ],
125
127
  )
126
128
 
127
129
  # Do not run this if args['stop-before-compile'] is True
128
130
  if self.args.get('stop-before-compile', False) or \
129
131
  self.args.get('stop-after-compile', False):
130
- pass # skip it.
131
- else:
132
- self.exec( work_dir=work_dir, command_list=synth_command_list,
133
- tee_fpath=synth_command_list.tee_fpath )
134
- util.info(f'yosys: wrote verilog to {yosys_v_path}')
135
- return self.status
132
+ return
133
+
134
+ # Run the synth commands standalone:
135
+ self.exec( work_dir=work_dir, command_list=synth_command_list,
136
+ tee_fpath=synth_command_list.tee_fpath )
137
+
138
+ if self.args['sta']:
139
+ self.exec(work_dir=self.full_work_dir, command_list=sta_command_list,
140
+ tee_fpath=sta_command_list.tee_fpath)
141
+
142
+ if self.status == 0:
143
+ util.info(f'yosys: wrote verilog to {self.yosys_v_path}')
136
144
 
137
145
 
138
146
  class CommandElabInvioYosys(CommandSynthInvioYosys):
@@ -8,8 +8,7 @@ Contains classes for ToolSlangYosys, CommandSynthSlangYosys
8
8
  import os
9
9
 
10
10
  from opencos import util
11
- from opencos.commands import CommandSynth
12
- from opencos.tools.yosys import ToolYosys
11
+ from opencos.tools.yosys import ToolYosys, CommonSynthYosys
13
12
 
14
13
  class ToolSlangYosys(ToolYosys):
15
14
  '''Uses slang.so in yosys plugins directory, called via yosys > plugin -i slang'''
@@ -28,49 +27,17 @@ class ToolSlangYosys(ToolYosys):
28
27
  })
29
28
 
30
29
 
31
- class CommandSynthSlangYosys(CommandSynth, ToolSlangYosys):
30
+ class CommandSynthSlangYosys(CommonSynthYosys, ToolSlangYosys):
32
31
  '''CommandSynthSlangYosys is a command handler for: eda synth --tool=slang_yosys'''
33
32
 
34
33
  def __init__(self, config: dict):
35
- CommandSynth.__init__(self, config)
34
+ CommonSynthYosys.__init__(self, config)
36
35
  ToolSlangYosys.__init__(self, config=self.config)
37
- self.args.update({
38
- 'sta': False,
39
- 'liberty-file': '',
40
- 'sdc-file': '',
41
- 'yosys-synth': 'synth', # synth_xilinx, synth_altera, etc (see: yosys help)
42
- 'yosys-pre-synth': ['prep', 'proc'], # command run in yosys prior to yosys-synth.
43
- 'yosys-blackbox': [], # list of modules that yosys will blackbox.
44
- })
45
- self.args_help.update({
46
- 'sta': 'After running Yosys, run "sta" with --liberty-file.' \
47
- + ' sta can be installed via: https://github.com/The-OpenROAD-Project/OpenSTA',
48
- 'sdc-file': '.sdc file to use with --sta, if not present will use auto constraints',
49
- 'liberty-file': 'Single liberty file for synthesis and sta,' \
50
- + ' for example: github/OpenSTA/examples/nangate45_slow.lib.gz',
51
- 'yosys-synth': 'The synth command provided to Yosys, see: yosys help.',
52
- 'yosys-pre-synth': 'Yosys commands performed prior to running "synth"' \
53
- + ' (or eda arg value for --yosys-synth)',
54
- 'yosys-blackbox': 'List of modules that yosys will blackbox, likely will need these' \
55
- + ' in Verilog-2001 for yosys to read outside of slang and synth',
56
- })
57
36
 
58
37
  self.slang_out_dir = ''
59
- self.yosys_out_dir = ''
60
38
  self.slang_v_path = ''
61
- self.yosys_v_path = ''
62
- self.full_work_dir = ''
63
- self.blackbox_list = []
64
39
 
65
- def do_it(self) -> None:
66
- CommandSynth.do_it(self)
67
-
68
- if self.is_export_enabled():
69
- return
70
-
71
- self._write_and_run_yosys_f_files()
72
-
73
- def _write_and_run_yosys_f_files(self):
40
+ def write_and_run_yosys_f_files(self, **kwargs) -> None:
74
41
  '''
75
42
  1. Creates and runs: yosys.slang.f
76
43
  -- should create post_slang_ls.txt
@@ -88,28 +55,20 @@ class CommandSynthSlangYosys(CommandSynth, ToolSlangYosys):
88
55
  util.debug(f'slang_yosys: {self.blackbox_list=}')
89
56
 
90
57
  # create {work_dir} / yosys
91
- self.full_work_dir = self.args.get('work-dir', '')
92
- if not self.full_work_dir:
93
- self.error(f'work_dir={self.full_work_dir} is not set')
94
- self.full_work_dir = os.path.abspath(self.full_work_dir)
95
58
  self.slang_out_dir = os.path.join(self.full_work_dir, 'slang')
96
- self.yosys_out_dir = os.path.join(self.full_work_dir, 'yosys')
97
- for p in [self.slang_out_dir, self.yosys_out_dir]:
98
- util.safe_mkdir(p)
59
+ util.safe_mkdir(self.slang_out_dir)
99
60
 
100
61
  self.slang_v_path = os.path.join(self.slang_out_dir, f'{self.args["top"]}.v')
101
- self.yosys_v_path = os.path.join(self.yosys_out_dir, f'{self.args["top"]}.v')
102
-
103
62
 
104
63
  # Run our created yosys.slang.f script
105
64
  # Note - this will always run, even if --stop-before-compile is set.
106
65
  slang_command_list = self._create_yosys_slang_f() # util.ShellCommandList
107
66
 
108
67
  # Create and run yosys.synth.f
109
- synth_command_list = self._create_yosys_synth_f() # util.ShellCommandList
68
+ synth_command_list = self.create_yosys_synth_f() # util.ShellCommandList
110
69
 
111
70
  # Optinally create and run a sta.f:
112
- sta_command_list = self._create_sta_f() # [] or util.ShellCommandList
71
+ sta_command_list = self.create_sta_f() # [] or util.ShellCommandList
113
72
 
114
73
  # We create a run_yosys.sh wrapping these scripts, but we do not run this one.
115
74
  util.write_shell_command_file(
@@ -246,7 +205,7 @@ class CommandSynthSlangYosys(CommandSynth, ToolSlangYosys):
246
205
  yosys_blackbox_list.append(line)
247
206
  return yosys_blackbox_list
248
207
 
249
- def _create_yosys_synth_f(self) -> util.ShellCommandList:
208
+ def create_yosys_synth_f(self) -> util.ShellCommandList:
250
209
  # Create yosys.synth.f
251
210
  yosys_synth_f_path = os.path.join(self.full_work_dir, 'yosys.synth.f')
252
211
 
@@ -254,10 +213,6 @@ class CommandSynthSlangYosys(CommandSynth, ToolSlangYosys):
254
213
  # yosys.synth.f script.
255
214
  yosys_blackbox_list = self._get_yosys_blackbox_list()
256
215
 
257
- synth_command = self.args.get('yosys-synth', 'synth')
258
- if self.args['flatten-all']:
259
- synth_command += ' -flatten'
260
-
261
216
  if self.args['liberty-file'] and not os.path.exists(self.args['liberty-file']):
262
217
  self.error(f'--liberty-file={self.args["liberty-file"]} file does not exist')
263
218
 
@@ -273,20 +228,7 @@ class CommandSynthSlangYosys(CommandSynth, ToolSlangYosys):
273
228
  for inst in yosys_blackbox_list:
274
229
  lines.append('blackbox ' + inst)
275
230
 
276
- lines += self.args.get('yosys-pre-synth', [])
277
- lines.append(synth_command)
278
-
279
- # TODO(drew): I need a blackbox flow here? Or a memory_libmap?
280
- # --> https://yosyshq.readthedocs.io/projects/yosys/en/latest/cmd/memory_libmap.html
281
- # TODO(drew): can I run multiple liberty files?
282
- if self.args['liberty-file']:
283
- lines += [
284
- 'dfflibmap -liberty ' + self.args['liberty-file'],
285
- #'memory_libmap -lib ' + self.args['liberty-file'], # Has to be unzipped?
286
- 'abc -liberty ' + self.args['liberty-file'],
287
- ]
288
-
289
- lines.append(f'write_verilog {self.yosys_v_path}')
231
+ lines += self.get_synth_command_lines()
290
232
  f.write('\n'.join(lines))
291
233
 
292
234
  synth_command_list = util.ShellCommandList(
@@ -296,74 +238,7 @@ class CommandSynthSlangYosys(CommandSynth, ToolSlangYosys):
296
238
  return synth_command_list
297
239
 
298
240
 
299
- def _create_sta_f(self) -> list:
300
-
301
- if not self.args['sta']:
302
- return []
303
-
304
- if not self.args['liberty-file']:
305
- self.error('--sta is set, but need to also set --liberty-file=<file>')
306
-
307
- if self.args['sdc-file']:
308
- if not os.path.exists(self.args['sdc-file']):
309
- self.error(f'--sdc-file={self.args["sdc-file"]} file does not exist')
310
-
311
- if not self.sta_exe:
312
- self.error(f'--sta is set, but "sta" was not found in PATH, see: {self._URL}')
313
-
314
- sta_command_list = util.ShellCommandList(
315
- [ self.sta_exe, '-no_init', '-exit', 'sta.f' ],
316
- tee_fpath = 'sta.log'
317
- )
318
-
319
- # Need to create sta.f:
320
- if self.args['sdc-file']:
321
- sdc_path = self.args['sdc-file']
322
- else:
323
- # Need to create sdc.f:
324
- sdc_path = 'sdc.f'
325
- self._create_sdc_f()
326
-
327
- with open(os.path.join(self.args['work-dir'], 'sta.f'), 'w',
328
- encoding='utf-8') as f:
329
- lines = [
330
- 'read_liberty ' + self.args['liberty-file'],
331
- 'read_verilog ' + self.yosys_v_path,
332
- 'link_design ' + self.args['top'],
333
- 'read_sdc ' + sdc_path,
334
- 'report_checks',
335
- ]
336
- f.write('\n'.join(lines))
337
-
338
- return util.ShellCommandList(
339
- sta_command_list,
340
- tee_fpath = 'sta.log'
341
- )
342
-
343
-
344
- def _create_sdc_f(self) -> None:
345
- if self.args['sdc-file']:
346
- # already exists from args, return b/c nothing to create.
347
- return
348
-
349
- with open(os.path.join(self.args['work-dir'], 'sdc.f'), 'w',
350
- encoding='utf-8') as f:
351
- clock_name = self.args['clock-name']
352
- period = self.args['clock-ns']
353
- name_not_equal_clocks_str = f'NAME !~ "{clock_name}"'
354
- lines = [
355
- f'create_clock -add -name {clock_name} -period {period} [get_ports ' \
356
- + '{' + clock_name + '}];',
357
- f'set_input_delay -max {self.args["idelay-ns"]} -clock {clock_name}' \
358
- + ' [get_ports * -filter {DIRECTION == IN && ' \
359
- + name_not_equal_clocks_str + '}];',
360
- f'set_output_delay -max {self.args["odelay-ns"]} -clock {clock_name}' \
361
- + ' [get_ports * -filter {DIRECTION == OUT}];',
362
- ]
363
- f.write('\n'.join(lines))
364
-
365
-
366
- class CommandElabSlangYosys(CommandSynthSlangYosys):
241
+ class CommandElabSlangYosys(CommandSynthSlangYosys): # pylint: disable=too-many-ancestors
367
242
  '''CommandSynthSlangYosys is a command handler for: eda synth --tool=slang_yosys
368
243
 
369
244
  Runs slang-yosys as elab only (does not run the synthesis portion), but is
opencos/tools/yosys.py CHANGED
@@ -5,6 +5,7 @@ Contains classes for ToolYosys
5
5
 
6
6
  # pylint: disable=R0801 # (calling functions with same arguments)
7
7
 
8
+ import os
8
9
  import shutil
9
10
  import subprocess
10
11
 
@@ -84,15 +85,44 @@ class CommonSynthYosys(CommandSynth, ToolYosys):
84
85
  ToolYosys.__init__(self, config=self.config)
85
86
 
86
87
  self.args.update({
88
+ 'sta': False,
89
+ 'liberty-file': '',
90
+ 'sdc-file': '',
87
91
  'yosys-synth': 'synth', # synth_xilinx, synth_altera, etc (see: yosys help)
88
92
  'yosys-pre-synth': ['prep', 'proc'], # command run in yosys prior to yosys-synth.
89
93
  'yosys-blackbox': [], # list of modules that yosys will blackbox.
90
94
  })
95
+ self.args_help.update({
96
+ 'sta': 'After running Yosys, run "sta" with --liberty-file.' \
97
+ + ' sta can be installed via: https://github.com/The-OpenROAD-Project/OpenSTA',
98
+ 'sdc-file': '.sdc file to use with --sta, if not present will use auto constraints',
99
+ 'liberty-file': 'Single liberty file for synthesis and sta,' \
100
+ + ' for example: github/OpenSTA/examples/nangate45_slow.lib.gz',
101
+ 'yosys-synth': 'The synth command provided to Yosys, see: yosys help.',
102
+ 'yosys-pre-synth': 'Yosys commands performed prior to running "synth"' \
103
+ + ' (or eda arg value for --yosys-synth)',
104
+ 'yosys-blackbox': 'List of modules that yosys will blackbox, likely will need these' \
105
+ + ' in Verilog-2001 for yosys to read outside of slang and synth',
106
+ })
107
+
108
+ self.yosys_out_dir = ''
109
+ self.yosys_v_path = ''
110
+ self.full_work_dir = ''
111
+ self.blackbox_list = []
91
112
 
92
113
  def do_it(self) -> None:
93
114
  self.set_tool_defines()
94
115
  self.write_eda_config_and_args()
95
116
 
117
+ # Set up some dirs and filenames.
118
+ self.full_work_dir = self.args.get('work-dir', '')
119
+ if not self.full_work_dir:
120
+ self.error(f'work_dir={self.full_work_dir} is not set')
121
+ self.full_work_dir = os.path.abspath(self.full_work_dir)
122
+ self.yosys_out_dir = os.path.join(self.full_work_dir, 'yosys')
123
+ util.safe_mkdir(self.yosys_out_dir)
124
+ self.yosys_v_path = os.path.join(self.yosys_out_dir, f'{self.args["top"]}.v')
125
+
96
126
  if self.is_export_enabled():
97
127
  self.do_export()
98
128
  return
@@ -102,3 +132,108 @@ class CommonSynthYosys(CommandSynth, ToolYosys):
102
132
  def write_and_run_yosys_f_files(self, **kwargs) -> None:
103
133
  '''Derived classes must define, to run remainder of do_it() steps'''
104
134
  raise NotImplementedError
135
+
136
+
137
+ def create_yosys_synth_f(self) -> util.ShellCommandList:
138
+ '''Derived classes may define, if they wish to get a list of yosys commands'''
139
+ return util.ShellCommandList([])
140
+
141
+
142
+ def get_synth_command_lines(self) -> list:
143
+ '''Common yosys tcl after all blackbox and read_verilog commands'''
144
+
145
+ lines = []
146
+ lines += self.args.get('yosys-pre-synth', [])
147
+
148
+ synth_command = self.args.get('yosys-synth', 'synth')
149
+ if self.args['flatten-all']:
150
+ synth_command += ' -flatten'
151
+
152
+ lines.append(synth_command)
153
+
154
+
155
+ # TODO(drew): I need a blackbox flow here? Or a memory_libmap?
156
+ # --> https://yosyshq.readthedocs.io/projects/yosys/en/latest/cmd/memory_libmap.html
157
+ # TODO(drew): can I run multiple liberty files?
158
+ if self.args['liberty-file']:
159
+ lines += [
160
+ 'dfflibmap -liberty ' + self.args['liberty-file'],
161
+ #'memory_libmap -lib ' + self.args['liberty-file'], # Has to be unzipped?
162
+ 'abc -liberty ' + self.args['liberty-file'],
163
+ ]
164
+ lines += [
165
+ 'opt_clean',
166
+ f'write_verilog {self.yosys_v_path}',
167
+ f'write_json {self.yosys_v_path}.json',
168
+ ]
169
+ return lines
170
+
171
+
172
+ def create_sta_f(self) -> util.ShellCommandList:
173
+ '''Returns command list, for running 'sta' on sta.f'''
174
+
175
+ if not self.args['sta']:
176
+ return []
177
+
178
+ if not self.args['liberty-file']:
179
+ self.error('--sta is set, but need to also set --liberty-file=<file>')
180
+
181
+ if self.args['sdc-file']:
182
+ if not os.path.exists(self.args['sdc-file']):
183
+ self.error(f'--sdc-file={self.args["sdc-file"]} file does not exist')
184
+
185
+ if not self.sta_exe:
186
+ self.error(f'--sta is set, but "sta" was not found in PATH, see: {self._URL}')
187
+
188
+ sta_command_list = util.ShellCommandList(
189
+ [ self.sta_exe, '-no_init', '-exit', 'sta.f' ],
190
+ tee_fpath = 'sta.log'
191
+ )
192
+
193
+ # Need to create sta.f:
194
+ if self.args['sdc-file']:
195
+ sdc_path = self.args['sdc-file']
196
+ else:
197
+ # Need to create sdc.f:
198
+ sdc_path = 'sdc.f'
199
+ self.create_sdc_f()
200
+
201
+ with open(os.path.join(self.args['work-dir'], 'sta.f'), 'w',
202
+ encoding='utf-8') as f:
203
+ lines = [
204
+ 'read_liberty ' + self.args['liberty-file'],
205
+ 'read_verilog ' + self.yosys_v_path,
206
+ 'link_design ' + self.args['top'],
207
+ 'read_sdc ' + sdc_path,
208
+ 'report_checks',
209
+ ]
210
+ f.write('\n'.join(lines))
211
+
212
+ return util.ShellCommandList(
213
+ sta_command_list,
214
+ tee_fpath = 'sta.log'
215
+ )
216
+
217
+
218
+ def create_sdc_f(self) -> None:
219
+ '''Returns None, creates sdc.f'''
220
+
221
+ if self.args['sdc-file']:
222
+ # already exists from args, return b/c nothing to create.
223
+ return
224
+
225
+ with open(os.path.join(self.args['work-dir'], 'sdc.f'), 'w',
226
+ encoding='utf-8') as f:
227
+ clock_name = self.args['clock-name']
228
+ period = self.args['clock-ns']
229
+ name_not_equal_clocks_str = f'NAME !~ "{clock_name}"'
230
+ lines = [
231
+ f'create_clock -add -name {clock_name} -period {period} [get_ports ' \
232
+ + '{' + clock_name + '}];',
233
+ f'set_input_delay -max {self.args["idelay-ns"]} -clock {clock_name}' \
234
+ + ' [get_ports * -filter {DIRECTION == IN && ' \
235
+ + name_not_equal_clocks_str + '}];',
236
+ f'set_output_delay -max {self.args["odelay-ns"]} -clock {clock_name}' \
237
+ + ' [get_ports * -filter {DIRECTION == OUT}];',
238
+ ]
239
+ f.write('\n'.join(lines))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opencos-eda
3
- Version: 0.2.36
3
+ Version: 0.2.38
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
@@ -3,25 +3,25 @@ opencos/_version.py,sha256=qN7iBoOv-v4tEZz-Pu9sVUJwefshJOsgdaddn8HcHio,510
3
3
  opencos/_waves_pkg.sv,sha256=1lbhQOVGc3t_R8czYjP40hssP0I3FlZOpHTkI7yKFbI,1251
4
4
  opencos/deps_helpers.py,sha256=Pgo3dO_QBHzGB0lE2B0uf2vWWDy3dSDN0pvW-eA6ocs,56939
5
5
  opencos/deps_schema.py,sha256=MhytzXwp071F14RwxqHt78ak8Qruoe4FeK5XSzkO2f0,14658
6
- opencos/eda.py,sha256=NCsSAnWANumzP2EzlsT-TvGUEuY8ONpHo-cvTcWC2wM,21261
7
- opencos/eda_base.py,sha256=Fi21IIKvYIzyj_vQtaqPwrUXDaszfIixoB80-TQMM2k,77455
8
- opencos/eda_config.py,sha256=CXfFVW4QOtP1gpMcX8JhCe93_aibcdbGoS7RAiYQnOs,8561
9
- opencos/eda_config_defaults.yml,sha256=4ryY9THfaeL2kclo5KLkC7F1_g7FbjVzDMQFdAsvTl0,9195
6
+ opencos/eda.py,sha256=k0_7ppBBPFsQ8Jd0mJGOnc6llUNo2hryHwx8DZ_B_jU,18832
7
+ opencos/eda_base.py,sha256=8kDrKM5YpwSmSqnSIItiV6R9FDr5QjraiEF3PeXe7Lw,77580
8
+ opencos/eda_config.py,sha256=FVp-dg9IVq78LZoh43kFYo9WXKfFxsMQMo6JGulEbEM,8818
9
+ opencos/eda_config_defaults.yml,sha256=0tqs4paxCYZJshb2WW2GfaZyeh_l9ZUKWMh8hh17AWk,10235
10
10
  opencos/eda_config_max_verilator_waivers.yml,sha256=lTAU4IOEbUWVlPzuer1YYhIyxpPINeA4EJqcRIT-Ymk,840
11
- opencos/eda_config_reduced.yml,sha256=2mV9CiUEj_tyJnQsC9_k0RlxuBrky0zW1HX2xlDVEjU,1469
12
- opencos/eda_deps_bash_completion.bash,sha256=BmCZNnRBioiPf93qwjtOmIPdDeHes0ojS_s_k4jhMoo,1950
13
- opencos/eda_extract_targets.py,sha256=Qc4IEx82ZNY2bLNYY0htpB4XLT-Mr2JQfPriyRigWkI,3467
11
+ opencos/eda_config_reduced.yml,sha256=cQ9jY4J7EvAbeHTiP6bvpDSVJAYiitjLZPSxxLKIEbk,1440
12
+ opencos/eda_deps_bash_completion.bash,sha256=jMkQKY82HBgOnQeMdA1hMrXguRFtB52SMBxUemKovL4,1958
13
+ opencos/eda_extract_targets.py,sha256=LLf8pGia0HxqXQFRck0E990EchbEdiFTC1fQyA8FZqs,4101
14
14
  opencos/eda_tool_helper.py,sha256=MxNd_ImyA13DZbzMol1R5H8tJZddJfqyDgFVR2ad374,1666
15
15
  opencos/export_helper.py,sha256=2SfzHM3m6b5Wh-OMgRWv5I_A95MHIYBn8-H_P3YC7ZQ,19680
16
16
  opencos/export_json_convert.py,sha256=KsP1ESmSWs8ouecDC5ikuwOFZNJtLjmiYOvYLeN5UZU,3768
17
- opencos/files.py,sha256=IB-hTRrVQx884GNqdJX1V1ikSRUS1Vwu-B3yX0AN6FI,1495
17
+ opencos/files.py,sha256=z7HYk5j7cMyjK8e25xzCyyUzOnj9iZt9IpGJibbbh-U,1495
18
18
  opencos/names.py,sha256=LMhAE5UH5LEy4zqHp3rOe6tP26i_Dka6JOJzKMo_bkg,1201
19
19
  opencos/oc_cli.py,sha256=kj2OXvgxli2WPj4MQ4zTBb36eFtsP2gsqDdeNeWGL4E,132108
20
20
  opencos/pcie.py,sha256=VUJljaZJYgScAAx5yn7F6GoA8K9eTcw24otYZbkMpYs,3035
21
21
  opencos/peakrdl_cleanup.py,sha256=vLhSOVs6cEzsi_PwAP4pSXu5_ZMZjDvfK_WmHDLbDac,486
22
22
  opencos/seed.py,sha256=8TA2uXhBuT_lOaQdAKqdReYvfBWi_KuyQCFJzA2rOVM,549
23
23
  opencos/util.py,sha256=3rADW0tUzq69wdHsa2GB060N1jCAK2Dk0NLx6N6BMcc,27861
24
- opencos/commands/__init__.py,sha256=cHermtb27ZOUurW1UIGarQIJihH1oJvpFWwUbk6q2i0,858
24
+ opencos/commands/__init__.py,sha256=d-8heMkBjjm6_yeduZgUv34lPfHVpj9MWP1ifpdjJws,916
25
25
  opencos/commands/build.py,sha256=jI5ul53qfwn6X-yfSdSQIcLBhGtzZUk7r_wKBBmKJI0,1425
26
26
  opencos/commands/elab.py,sha256=m6Gk03wSzX8UkcmReooK7turF7LpqO0IcdOZwJ8XiyI,1596
27
27
  opencos/commands/export.py,sha256=uGDI5_lUhyNB54gFjm9cWv9g_3waEOJN4wOC01oFlCY,3535
@@ -32,15 +32,16 @@ opencos/commands/proj.py,sha256=MdHTOtQYG93_gT97dWuSyAgUxX2vi9FRhL0dtc-rM98,1096
32
32
  opencos/commands/sim.py,sha256=phATz_wR0BAH6B_IJ4zyzg_Hptp5hSEOe_-9NLaL_bY,14058
33
33
  opencos/commands/sweep.py,sha256=L4AYF3vQHR-fi0871f1CwrD0y4tydrsNpWSDjVf1xIA,8719
34
34
  opencos/commands/synth.py,sha256=quB-HWS4LKYTiFBHiYarQi4pMnRmt12wQTZpi14VvlE,4355
35
+ opencos/commands/targets.py,sha256=_jRNhm2Fqj0fmMvTw6Ba39DCsRHf_r_uZCy_R064kpA,1472
35
36
  opencos/commands/upload.py,sha256=nlb4nlxrDCQPcabEmH3nP19g4PFILDqFDab4LwJ95Z4,796
36
37
  opencos/commands/waves.py,sha256=SRfjfsqhuszXHylQrgqYiUT3a5CQs9doxJQzuV4Ae0w,7055
37
38
  opencos/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
39
  opencos/tests/custom_config.yml,sha256=TRoVM9ZFKPOA_8JmlpzaMhnGO1txmaD14N_8P1oqzew,257
39
- opencos/tests/helpers.py,sha256=M_XwM1vQmcrVEgfvpL7C23VPjiFhPpsCqhAH5ZZqAT4,8211
40
+ opencos/tests/helpers.py,sha256=zzQQP8ZeNUwrvJRQVlpTnvz9yxFG0Jfuss50ZPjxa-c,8240
40
41
  opencos/tests/test_build.py,sha256=FQAxOpLVQShAHD_L5rqJctPeSAoqoOCNFI0RXflLuY0,387
41
42
  opencos/tests/test_deps_helpers.py,sha256=_nJSgLN6WVlMKqu6sCr29gjQyN3Jj-dVk8Ac64ygpJs,5928
42
43
  opencos/tests/test_deps_schema.py,sha256=mWTWI4wriGXC8UAnaeq_MIvWJOvf08-fPUqUgELptQ4,896
43
- opencos/tests/test_eda.py,sha256=HCUGCg9yVILIIyyey2XArllgtBV5XHeVVnL5AGglO7I,37786
44
+ opencos/tests/test_eda.py,sha256=eraWvyJJHrlq4CVoeWakYckRG8RRUYtecJRzlcXX45Q,38545
44
45
  opencos/tests/test_eda_elab.py,sha256=75bJpOaoO8rn1FXFxiE4KSu5FdjZP1IbW6SyTCjM_ao,2553
45
46
  opencos/tests/test_eda_synth.py,sha256=C_1LzblTuK_lHFv_Hh8v3DKUN4hGfxoChYR77GricX4,2871
46
47
  opencos/tests/test_oc_cli.py,sha256=-ZmwVX_CPBXCGT9hXIBEr_XUSIGG2eky89YpSJIbRAg,731
@@ -53,23 +54,23 @@ opencos/tests/deps_files/non_sv_reqs/DEPS.yml,sha256=VZkahO1AKhD9GUV5lK8VwUONEi5
53
54
  opencos/tests/deps_files/tags_with_tools/DEPS.yml,sha256=-5U1qfJElgpVhmkLEu3lYuvDYva8kDlt6JOdB9jidmc,1377
54
55
  opencos/tests/deps_files/test_err_fatal/DEPS.yml,sha256=GnXIUJvshQWR9PlYxX67T53ejf5KhDbtD8sUJB4Rwd0,95
55
56
  opencos/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
- opencos/tools/invio.py,sha256=xAkYkMFKIUZDU1PkHeydDuPFjpEF_TN-1uYv7ErH4hY,3296
57
+ opencos/tools/invio.py,sha256=q9E9n6xsozDfar-1rLvJEZbCpPb_bQEy6WKEI3KS3dk,3163
57
58
  opencos/tools/invio_helpers.py,sha256=1au4CYmV5aC7DHjaZBNemydH6Eq0i-Yt5L3HyKfQOfY,7638
58
- opencos/tools/invio_yosys.py,sha256=a7GGuPgxyilves_EjE1DSZqetPr7n0fcQC-xxrzjqag,5714
59
+ opencos/tools/invio_yosys.py,sha256=4zWBeFKXHSyb5WSnf6ZpVG2UwoKF0SC-00I1_VqgXZM,6016
59
60
  opencos/tools/iverilog.py,sha256=1SmkJAtG096ozIY6N19UtpD-YT1UKJAcMLJ9blfraP0,6413
60
61
  opencos/tools/modelsim_ase.py,sha256=x5xFWBNaln2lVLixN0Od0uNaLKN_Bt1q6a91QmhKoNA,13162
61
62
  opencos/tools/questa.py,sha256=AX_3USyf6eMcssH4b-8WLbCzz-cXYnQzlHvCyL9C7Og,7505
62
63
  opencos/tools/slang.py,sha256=74EDAAnN7mrrYxgxaPDaoRJZK7Se9B_HsW8Ioi2Nw44,7425
63
- opencos/tools/slang_yosys.py,sha256=8zLwScd3PjouuLQpLvpX837kGqYQiJ9bnButCwrmSqI,15047
64
+ opencos/tools/slang_yosys.py,sha256=mw4AfutGjKyCj7NLrHDy2j3p0XC2H7uuBf9RkVQJYoQ,9856
64
65
  opencos/tools/surelog.py,sha256=JOMs8SqnzJ_ZL5mEdyyn3Z1r1Kc8hfbV3Pnen1YzxWA,4980
65
66
  opencos/tools/tabbycad_yosys.py,sha256=h9kkAi479cZzYfb4R9WBNY_JmR6BgVFj4s3VShnGpoA,7813
66
67
  opencos/tools/verilator.py,sha256=0ZHAkkUqnsM1A4B8-u6tff3lhl-cgfDUx-Dq7DaRwkI,18080
67
68
  opencos/tools/vivado.py,sha256=cL5IZdudqquddvZqHQGDVmaHSxgBZsMUmAq18q8AgoM,39134
68
- opencos/tools/yosys.py,sha256=Nov3zKyliZ6rHwMbH4f8XEkoo9H66T2-MN-KNQTjWFk,3530
69
- opencos_eda-0.2.36.dist-info/licenses/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
70
- opencos_eda-0.2.36.dist-info/licenses/LICENSE.spdx,sha256=8gn1610RMP6eFgT3Hm6q9VKXt0RvdTItL_oxMo72jII,189
71
- opencos_eda-0.2.36.dist-info/METADATA,sha256=bcp36pjsJ-jZgB8xXIiaYRKT86yOHYl_cBgKHaMVDDI,604
72
- opencos_eda-0.2.36.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
73
- opencos_eda-0.2.36.dist-info/entry_points.txt,sha256=V8OE1lySAFcFQpDNJuVxVZteeSmDH-joLMhGvrxrvmg,164
74
- opencos_eda-0.2.36.dist-info/top_level.txt,sha256=J4JDP-LpRyJqPNeh9bSjx6yrLz2Mk0h6un6YLmtqql4,8
75
- opencos_eda-0.2.36.dist-info/RECORD,,
69
+ opencos/tools/yosys.py,sha256=aZnRFbsODYRe4BHbfxl6vfWeEP7WJYJk50gCZcn8Fu0,8902
70
+ opencos_eda-0.2.38.dist-info/licenses/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
71
+ opencos_eda-0.2.38.dist-info/licenses/LICENSE.spdx,sha256=8gn1610RMP6eFgT3Hm6q9VKXt0RvdTItL_oxMo72jII,189
72
+ opencos_eda-0.2.38.dist-info/METADATA,sha256=kKhYeLRFFBsjoHaLMNW-xyGGbQejy0hEnW00rsC6puQ,604
73
+ opencos_eda-0.2.38.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
74
+ opencos_eda-0.2.38.dist-info/entry_points.txt,sha256=V8OE1lySAFcFQpDNJuVxVZteeSmDH-joLMhGvrxrvmg,164
75
+ opencos_eda-0.2.38.dist-info/top_level.txt,sha256=J4JDP-LpRyJqPNeh9bSjx6yrLz2Mk0h6un6YLmtqql4,8
76
+ opencos_eda-0.2.38.dist-info/RECORD,,