opencos-eda 0.2.44__py3-none-any.whl → 0.2.45__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,7 +17,9 @@ from .sweep import CommandSweep
17
17
  from .synth import CommandSynth
18
18
  from .upload import CommandUpload
19
19
  from .waves import CommandWaves
20
+ from .shell import CommandShell
20
21
  from .targets import CommandTargets
22
+ from .lec import CommandLec
21
23
 
22
24
  __all__ = [
23
25
  'CommandBuild',
@@ -33,5 +35,7 @@ __all__ = [
33
35
  'CommandToolsMulti',
34
36
  'CommandUpload',
35
37
  'CommandWaves',
38
+ 'CommandShell',
36
39
  'CommandTargets',
40
+ 'CommandLec',
37
41
  ]
@@ -0,0 +1,103 @@
1
+ '''opencos.commands.lec - Base class command handler for: eda lec ...
2
+
3
+ Intended to be overriden by Tool based classes (such as CommandLecYosys, etc)
4
+ '''
5
+
6
+ # Note - similar code waiver, tricky to eliminate it with inheritance when
7
+ # calling reusable methods.
8
+ # pylint: disable=R0801
9
+
10
+ import os
11
+
12
+ from opencos import eda_extract_targets
13
+ from opencos.eda_base import CommandDesign, Tool
14
+
15
+
16
+ class CommandLec(CommandDesign):
17
+ '''Base class command handler for: eda lec ...'''
18
+
19
+ CHECK_REQUIRES = [Tool]
20
+ error_on_no_files_or_targets = False
21
+ error_on_missing_top = False # we'll override it.
22
+
23
+ command_name = 'lec'
24
+
25
+ def __init__(self, config: dict):
26
+ CommandDesign.__init__(self, config=config)
27
+ self.args.update({
28
+ 'designs': [],
29
+ 'synth': True,
30
+ })
31
+ self.args_help.update({
32
+ 'designs': (
33
+ 'Set the two LEC comparison designs: --designs=<target1> --designs=<target2>,'
34
+ ' use this arg twice'
35
+ ),
36
+ 'synth': 'run synthesis on the two designs prior to running LEC',
37
+ })
38
+
39
+ self.synth_design_verilog_fpaths = ['', '']
40
+
41
+
42
+ def do_it(self) -> None:
43
+ '''Common do_it() method that child classes can use prior to customization'''
44
+
45
+ # set_tool_defines() is from class Tool. Since that is not inherited yet, but
46
+ # should be by any handlers, check on the existence of set_tool_defines, and
47
+ # error if not present.
48
+ if not all(isinstance(self, x) for x in self.CHECK_REQUIRES):
49
+ self.error('CommandLec.do_it() requires a Tool to be in parent classes, but none is.',
50
+ f'{self.CHECK_REQUIRES=}')
51
+ return
52
+
53
+ # add defines for this job from Tool class if present
54
+ self.command_safe_set_tool_defines() # (Command.command_safe_set_tool_defines)
55
+
56
+ # dump our config to work-dir for debug
57
+ self.write_eda_config_and_args()
58
+
59
+ # Note - we do not support --export in LEC.
60
+ # Derived classes can do the rest, can call CommandLec.do_it(self) as a first step.
61
+
62
+
63
+ def process_tokens(self, tokens: list, process_all: bool = True,
64
+ pwd: str = os.getcwd()) -> list:
65
+ unparsed = CommandDesign.process_tokens(
66
+ self, tokens=tokens, process_all=process_all, pwd=pwd
67
+ )
68
+
69
+ if self.status_any_error():
70
+ return unparsed
71
+
72
+ # add defines for this job type
73
+ if not self.args['top']:
74
+ self.args['top'] = 'eda.lec'
75
+
76
+ # we require there to be two --designs set.
77
+ if not self.args['designs'] and len(self.args['designs']) != 2:
78
+ self.error('Requires two designs via --designs=<target1> --designs=<target2>',
79
+ f'designs={self.args["designs"]}')
80
+ return []
81
+
82
+ # Before we do anything else, make sure the two designs actually exist.
83
+ for design in self.args['designs']:
84
+ # maybe it's a file?
85
+ if os.path.isfile(design):
86
+ pass
87
+ elif design in eda_extract_targets.get_targets(partial_paths=[design], base_path=pwd):
88
+ pass
89
+ else:
90
+ self.error(f'--designs={design}, value is not a file or target')
91
+
92
+ # create our work dir
93
+ self.create_work_dir()
94
+ self.run_dep_commands()
95
+ self.do_it()
96
+ return unparsed
97
+
98
+
99
+ def get_synth_result_fpath(self, target: str) -> str:
100
+ '''Derived classes must define. Given a synth target for one of the two
101
+
102
+ designs to compare, return the location of the synthesis file'''
103
+ raise NotImplementedError
@@ -0,0 +1,202 @@
1
+ '''opencos.commands.shell - Base class command handler for: eda shell ...
2
+
3
+ Not intended to be overriden by Tool based classes.'''
4
+
5
+ # Note - tricky to eliminate it with inheritance when calling reusable methods.
6
+ # pylint: disable=R0801
7
+
8
+ # TODO(drew): clean up CommandShell.check_logs_for_errors and CommandShell.run_commands_check_logs
9
+ # These also share a lot with CommandSim.* methods, so consider refactoring to share code,
10
+ # for example, CommandShell.do_export could move to CommandDesign, and derived classes
11
+ # set the allow-listed args to pass to export.
12
+ # pylint: disable=too-many-arguments,too-many-positional-arguments
13
+
14
+ import os
15
+
16
+ from opencos import util, export_helper
17
+ from opencos.eda_base import CommandDesign
18
+
19
+
20
+ class CommandShell(CommandDesign):
21
+ '''Base class command handler for: eda sim ...'''
22
+
23
+ command_name = 'shell'
24
+
25
+ def __init__(self, config: dict):
26
+ CommandDesign.__init__(self, config=config)
27
+ self.args.update({
28
+ 'pass-pattern': "",
29
+ 'log-bad-strings': [],
30
+ 'log-must-strings': [],
31
+ })
32
+ self.args_help.update({
33
+ 'log-bad-strings': (
34
+ 'strings that if present in the log will cause an `eda shell` error'
35
+ ),
36
+ 'log-must-strings': (
37
+ 'strings that are required by the log to not-fail the `eda shell` call'
38
+ ),
39
+ 'pass-pattern': (
40
+ 'Additional string required to pass the `eda shell` call, appends to'
41
+ ' log-must-strings'
42
+ ),
43
+ })
44
+
45
+ def process_tokens(self, tokens: list, process_all: bool = True,
46
+ pwd: str = os.getcwd()) -> list:
47
+ unparsed = CommandDesign.process_tokens(
48
+ self, tokens=tokens, process_all=process_all, pwd=pwd
49
+ )
50
+
51
+ if self.status_any_error():
52
+ return unparsed
53
+
54
+ if self.args['top']:
55
+ # create our work dir
56
+ self.create_work_dir()
57
+ self.run_dep_commands()
58
+ self.do_it()
59
+ return unparsed
60
+
61
+
62
+ def run_commands_check_logs( # pylint: disable=dangerous-default-value
63
+ self, commands: list , check_logs: bool = True, log_filename=None,
64
+ bad_strings: list = [],
65
+ must_strings: list = [],
66
+ use_bad_strings: bool = True, use_must_strings: bool = True
67
+ ) -> None:
68
+ '''Returns None, runs all commands (each element is a list) and checks logs
69
+
70
+ for bad-strings and must-strings (args or class member vars)
71
+ '''
72
+
73
+ for obj in commands:
74
+
75
+ assert isinstance(obj, list), \
76
+ (f'{self.target=} command {obj=} is not a list or util.ShellCommandList,'
77
+ ' not going to run it.')
78
+
79
+ clist = list(obj).copy()
80
+ tee_fpath = getattr(obj, 'tee_fpath', None)
81
+
82
+ util.debug(f'run_commands_check_logs: {clist=}, {tee_fpath=}')
83
+
84
+ log_fname = None
85
+ if tee_fpath:
86
+ log_fname = tee_fpath
87
+ if log_filename:
88
+ log_fname = log_filename
89
+
90
+ self.exec(work_dir=self.args['work-dir'], command_list=clist, tee_fpath=tee_fpath)
91
+
92
+ if check_logs and log_fname:
93
+ self.check_logs_for_errors(
94
+ filename=log_fname, bad_strings=bad_strings, must_strings=must_strings,
95
+ use_bad_strings=use_bad_strings, use_must_strings=use_must_strings
96
+ )
97
+
98
+
99
+ def do_export(self) -> None:
100
+ '''CommandShell helper for handling args --export*
101
+
102
+ We allow commands such as: eda shell --export <target>
103
+ '''
104
+
105
+ out_dir = os.path.join(self.args['work-dir'], 'export')
106
+
107
+ target = self.target
108
+ if not target:
109
+ target = 'test'
110
+
111
+ export_obj = export_helper.ExportHelper(
112
+ cmd_design_obj=self,
113
+ eda_command=self.command_name,
114
+ out_dir=out_dir,
115
+ # Note this may not be the correct target for debug infomation,
116
+ # so we'll only have the first one.
117
+ target=target
118
+ )
119
+
120
+ # Set things in the exported: DEPS.yml
121
+ tool = self.args.get('tool', None)
122
+ # Certain args are allow-listed here
123
+ deps_file_args = []
124
+ for a in self.get_command_line_args():
125
+ if any(a.startswith(x) for x in [
126
+ '--log-must',
127
+ '--log-bad',
128
+ '--pass-pattern']):
129
+ deps_file_args.append(a)
130
+
131
+ export_obj.run(
132
+ deps_file_args=deps_file_args,
133
+ export_json_eda_config={
134
+ 'tool': tool,
135
+ }
136
+ )
137
+
138
+ if self.args['export-run']:
139
+
140
+ # remove the '--export' named args, we don't want those.
141
+ args_no_export = self.get_command_line_args(remove_args_startswith=['export'])
142
+
143
+ command_list = ['eda', self.command_name] + args_no_export + [target]
144
+
145
+ util.info(f'export-run: from {export_obj.out_dir=}: {command_list=}')
146
+ self.exec(
147
+ work_dir=export_obj.out_dir,
148
+ command_list=command_list,
149
+ )
150
+
151
+
152
+ def do_it(self) -> None:
153
+ self.write_eda_config_and_args()
154
+
155
+ if self.is_export_enabled():
156
+ # If we're exporting the target, we do NOT run the test here
157
+ # (do_export() may run the test in a separate process and
158
+ # from the out_dir if --export-run was set)
159
+ self.do_export()
160
+
161
+
162
+ def check_logs_for_errors( # pylint: disable=dangerous-default-value
163
+ self, filename: str,
164
+ bad_strings: list = [], must_strings: list = [],
165
+ use_bad_strings: bool = True, use_must_strings: bool = True
166
+ ) -> None:
167
+ '''Returns None, checks logs using args bad_strings, must_strings,
168
+
169
+ and internals self.args["log-[bad|must]-strings"] (lists).
170
+ '''
171
+
172
+ _bad_strings = bad_strings
173
+ _must_strings = must_strings
174
+ # append, if not they would 'replace' the args values:
175
+ if use_bad_strings:
176
+ _bad_strings = bad_strings + self.args.get('log-bad-strings', [])
177
+ if use_must_strings:
178
+ _must_strings = must_strings + self.args.get('log-must-strings', [])
179
+
180
+ if self.args['pass-pattern'] != "":
181
+ _must_strings.append(self.args['pass-pattern'])
182
+
183
+ if len(_bad_strings) > 0 or len(_must_strings) > 0:
184
+ hit_bad_string = False
185
+ hit_must_string_dict = dict.fromkeys(_must_strings)
186
+ fname = os.path.join(self.args['work-dir'], filename)
187
+ with open(fname, 'r', encoding='utf-8') as f:
188
+ for lineno, line in enumerate(f):
189
+ if any(must_str in line for must_str in _must_strings):
190
+ for k, _ in hit_must_string_dict.items():
191
+ if k in line:
192
+ hit_must_string_dict[k] = True
193
+ if any(bad_str in line for bad_str in _bad_strings):
194
+ hit_bad_string = True
195
+ self.error(f"log {fname}:{lineno} contains one of {_bad_strings=}")
196
+
197
+ if hit_bad_string:
198
+ self.status += 1
199
+ if any(x is None for x in hit_must_string_dict.values()):
200
+ self.error(f"Didn't get all passing patterns in log {fname}: {_must_strings=}",
201
+ f" {hit_must_string_dict=}")
202
+ self.status += 1
opencos/commands/sim.py CHANGED
@@ -306,7 +306,7 @@ class CommandSim(CommandDesign):
306
306
  if hit_bad_string:
307
307
  self.status += 1
308
308
  if any(x is None for x in hit_must_string_dict.values()):
309
- self.error(f"Didn't get all passing patternsin log {fname}: {_must_strings=}",
309
+ self.error(f"Didn't get all passing patterns in log {fname}: {_must_strings=}",
310
310
  f" {hit_must_string_dict=}")
311
311
  self.status += 1
312
312
 
opencos/eda.py CHANGED
@@ -87,6 +87,8 @@ Where <command> is one of:
87
87
  upload - Uploads a finished design into hardware
88
88
  open - Opens a project
89
89
  export - Export files related to a target, tool independent
90
+ shell - Runs only commands for DEPS target (like sim or elab, but stops prior to tool)
91
+ targets - list all possible targets given glob path.
90
92
  help - This help (without args), or i.e. "eda help sim" for specific help
91
93
 
92
94
  And <files|targets, ...> is one or more source file or DEPS markup file target,
opencos/eda_base.py CHANGED
@@ -302,6 +302,8 @@ class Command:
302
302
 
303
303
  def exec(self, work_dir, command_list, background=False, stop_on_error=True,
304
304
  quiet=False, tee_fpath=None, shell=False):
305
+ if not tee_fpath and getattr(command_list, 'tee_fpath', None):
306
+ tee_fpath = getattr(command_list, 'tee_fpath', '')
305
307
  if not quiet:
306
308
  util.info(f"exec: {' '.join(command_list)} (in {work_dir}, {tee_fpath=})")
307
309
  original_cwd = util.getcwd()
@@ -740,9 +742,9 @@ class CommandDesign(Command):
740
742
  util.info(f'run_dep_shell_commands {iter=}: {d=}')
741
743
  clist = util.ShellCommandList(d['exec_list'])
742
744
  # NOTE(drew): shell=True subprocess call, can disable with self.config
743
- # However, in Windows, we seem to have to run these with shell=False
744
745
  if sys.platform.startswith('win'):
745
- self.exec(self.args['work-dir'], clist, shell=False)
746
+ # for Windows, we run shell=True otherwise most built-in cmd.exe calls won't work.
747
+ self.exec(self.args['work-dir'], clist, tee_fpath=clist.tee_fpath, shell=True)
746
748
  else:
747
749
  self.exec(self.args['work-dir'], clist, tee_fpath=clist.tee_fpath,
748
750
  shell=self.config.get('deps_subprocess_shell', False))
@@ -1160,7 +1162,7 @@ class CommandDesign(Command):
1160
1162
  else:
1161
1163
  target_name = os.path.join(".", token) # prepend ./so that we always have a <path>/<file>
1162
1164
 
1163
- util.debug(f'Calling self.resolve_target on {target_name=}')
1165
+ util.debug(f'Calling self.resolve_target on {target_name=} ({token=})')
1164
1166
  if self.resolve_target(target_name, caller_info=caller_info):
1165
1167
  if not self.args['top']:
1166
1168
  # last cmd line arg was a target that we'll likely use for inferred top.
@@ -9,19 +9,21 @@ DEFAULT_HANDLERS:
9
9
  sim : opencos.commands.CommandSim
10
10
  elab : opencos.commands.CommandElab
11
11
  synth : opencos.commands.CommandSynth
12
- flist : opencos.commands.CommandFList
13
12
  proj : opencos.commands.CommandProj
14
13
  build : opencos.commands.CommandBuild
15
14
  upload : opencos.commands.CommandUpload
16
15
  open : opencos.commands.CommandOpen
16
+ lec : opencos.commands.CommandLec
17
17
  # These commands don't necessarily require a tool
18
18
  multi : opencos.commands.CommandMulti
19
19
  tools-multi : opencos.commands.CommandToolsMulti
20
20
  sweep : opencos.commands.CommandSweep
21
+ flist : opencos.commands.CommandFList
21
22
  # These commands (waves, export, targets) do not require a tool, or
22
23
  # will self determine the tool:
23
24
  waves : opencos.commands.CommandWaves
24
25
  export : opencos.commands.CommandExport
26
+ shell : opencos.commands.CommandShell
25
27
  targets : opencos.commands.CommandTargets
26
28
 
27
29
 
@@ -337,6 +339,15 @@ auto_tools_order:
337
339
  flist: opencos.tools.vivado.CommandFListVivado
338
340
  build: opencos.tools.vivado.CommandBuildVivado
339
341
 
342
+ slang_yosys:
343
+ exe: yosys
344
+ requires_cmd:
345
+ - yosys -m slang
346
+ handlers:
347
+ elab: opencos.tools.slang_yosys.CommandElabSlangYosys
348
+ synth: opencos.tools.slang_yosys.CommandSynthSlangYosys
349
+ lec: opencos.tools.slang_yosys.CommandLecSlangYosys
350
+
340
351
  tabbycad_yosys:
341
352
  exe: yosys
342
353
  requires_env:
@@ -354,13 +365,13 @@ auto_tools_order:
354
365
  elab: opencos.tools.invio_yosys.CommandElabInvioYosys
355
366
  synth: opencos.tools.invio_yosys.CommandSynthInvioYosys
356
367
 
357
- slang_yosys:
368
+ yosys:
358
369
  exe: yosys
359
370
  requires_cmd:
360
- - yosys -m slang
371
+ - yosys
361
372
  handlers:
362
- elab: opencos.tools.slang_yosys.CommandElabSlangYosys
363
- synth: opencos.tools.slang_yosys.CommandSynthSlangYosys
373
+ synth: opencos.tools.slang_yosys.CommonSynthYosys
374
+ lec: opencos.tools.slang_yosys.CommandLecYosys
364
375
 
365
376
  questa:
366
377
  exe: qrun
@@ -86,8 +86,9 @@ def get_path_and_pattern(partial_path: str = '', base_path=str(Path('.'))) -> (s
86
86
 
87
87
 
88
88
 
89
- def run(partial_paths: list, base_path=str(Path('.'))) -> None:
90
- '''Returns None, prints DEPS keys into pretty columns, using arg
89
+
90
+ def get_targets(partial_paths: list, base_path=str(Path('.'))) -> list:
91
+ '''Returns a list of DEPS keys into pretty columns, using arg
91
92
 
92
93
  partial_path as a string filter for target completions.
93
94
  '''
@@ -113,7 +114,16 @@ def run(partial_paths: list, base_path=str(Path('.'))) -> None:
113
114
  for key in keys:
114
115
  targets_set.add(key)
115
116
 
116
- data = list(targets_set)
117
+ return list(targets_set)
118
+
119
+
120
+ def run(partial_paths: list, base_path=str(Path('.'))) -> None:
121
+ '''Returns None, prints DEPS keys into pretty columns, using arg
122
+
123
+ partial_path as a string filter for target completions.
124
+ '''
125
+
126
+ data = get_targets(partial_paths=partial_paths, base_path=base_path)
117
127
  data.sort()
118
128
  print_columns_manual(data=data, num_columns=4, auto_columns=True)
119
129
 
opencos/tests/helpers.py CHANGED
@@ -5,6 +5,7 @@
5
5
  import json
6
6
  import os
7
7
  import shutil
8
+ from pathlib import Path
8
9
 
9
10
  from contextlib import redirect_stdout, redirect_stderr
10
11
 
@@ -26,7 +27,7 @@ def can_run_eda_command(*commands, config: dict) -> bool:
26
27
 
27
28
  def chdir_remove_work_dir(startpath, relpath):
28
29
  '''Changes dir to startpath/relpath, removes the work directories (eda.work, eda.export*)'''
29
- os.chdir(os.path.join(startpath, relpath))
30
+ os.chdir(os.path.join(str(Path(startpath)), str(Path(relpath))))
30
31
  for outdir in ['eda.export', 'eda.work']:
31
32
  fullp = os.path.join(os.getcwd(), outdir)
32
33
  if fullp and ('eda.' in fullp) and os.path.isdir(fullp):
@@ -16,7 +16,9 @@ def test_all_deps():
16
16
  for root, _, files in os.walk(os.getcwd()):
17
17
  for fname in files:
18
18
  if fname.startswith('DEPS') and \
19
- any(fname.endswith(x) for x in ['.yml', '.json', '.toml', 'DEPS']):
19
+ any(fname.endswith(x) for x in [
20
+ '.yml', '.yaml', '.json', '.toml', 'DEPS'
21
+ ]):
20
22
 
21
23
  all_deps_files.append(os.path.join(root, fname))
22
24
 
opencos/tests/test_eda.py CHANGED
@@ -692,14 +692,22 @@ class TestDepsReqs:
692
692
  assert rc != 0
693
693
 
694
694
 
695
- @pytest.mark.skipif(not can_run_eda_sim(), reason='no tool found to handle command: sim')
696
- def test_deps_command_order():
695
+ @pytest.mark.parametrize("command", ['sim', 'shell'])
696
+ def test_deps_command_order(command):
697
697
  '''Test for various "commands" within a DEPS target. This test checks that command
698
698
  order is preserved in the top-to-bottom deps order, meaning that eda.py has to collect
699
699
  all commands deps order, and then execute them in that order.'''
700
700
 
701
701
  chdir_remove_work_dir('deps_files/command_order')
702
- cmd_list = 'sim --stop-before-compile target_test'.split()
702
+ if command == 'sim' and not can_run_eda_sim():
703
+ pytest.skip(f'sim skipped, {can_run_eda_sim()=}')
704
+ return # skip/pass
705
+
706
+ if command == 'shell':
707
+ cmd_list = 'shell target_test'.split()
708
+ else:
709
+ cmd_list = 'sim --stop-before-compile target_test'.split()
710
+
703
711
  with open('eda.log', 'w', encoding='utf-8') as f:
704
712
  with redirect_stdout(f):
705
713
  with redirect_stderr(f):
@@ -730,14 +738,16 @@ def test_deps_command_order():
730
738
  # Added check, we redirected to create eda.log earlier to confirm the targets worked,
731
739
  # but as a general eda.py check, all shell commands should create their own
732
740
  # {target}__shell_0.log file:
733
- work_dir = os.path.join(THISPATH, 'deps_files/command_order', 'eda.work', 'target_test.sim')
741
+ work_dir = os.path.join(
742
+ THISPATH, 'deps_files', 'command_order', 'eda.work', f'target_test.{command}'
743
+ )
734
744
  with open(os.path.join(work_dir, 'target_echo_hi__shell_0.log'), encoding='utf-8') as f:
735
745
  text = ''.join(f.readlines()).strip()
736
- assert text == 'hi'
746
+ assert text in ['hi', '"hi"', '\\"hi\\"']
737
747
  # Added check, one of the targets uses a custom 'tee' file name, instead of the default log.
738
748
  with open(os.path.join(work_dir, 'custom_tee_echo_bye.log'), encoding='utf-8') as f:
739
749
  text = ''.join(f.readlines()).strip()
740
- assert text == 'bye'
750
+ assert text in ['bye', '"bye"', '\\"bye\\"']
741
751
 
742
752
 
743
753
  @pytest.mark.skipif('verilator' not in tools_loaded, reason="requires verilator")
@@ -896,9 +906,9 @@ class TestDepsTags(Helpers):
896
906
  # b/c that should only apply in 'verilator' for this target.)
897
907
  exec_lines = self.get_log_lines_with('exec: ', logfile=logfile)
898
908
  assert len(exec_lines) == 3
899
- assert 'xvlog ' in exec_lines[0]
900
- assert 'xelab ' in exec_lines[1]
901
- assert 'xsim ' in exec_lines[2]
909
+ assert 'xvlog' in exec_lines[0]
910
+ assert 'xelab' in exec_lines[1]
911
+ assert 'xsim' in exec_lines[2]
902
912
  assert not self.is_in_log('--lint-only', logfile=logfile)
903
913
 
904
914
 
@@ -51,7 +51,7 @@ class CommandSynthInvioYosys(CommonSynthYosys, ToolInvioYosys):
51
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
+ def write_and_run_yosys_f_files(self) -> None:
55
55
 
56
56
  # Use helper module for Invio/Verific to save out Verilog-2001 from our
57
57
  # Verilog + SystemVerilog + VHDL file lists.
@@ -104,7 +104,7 @@ class CommandSynthInvioYosys(CommonSynthYosys, ToolInvioYosys):
104
104
  )
105
105
 
106
106
  # Optinally create and run a sta.f:
107
- sta_command_list = self.create_sta_f() # [] or util.ShellCommandList
107
+ sta_command_lists = self.create_sta_f() # [] or [util.ShellCommandList]
108
108
 
109
109
  # We create a run_yosys.sh wrapping these scripts, but we do not run this one.
110
110
  util.write_shell_command_file(
@@ -122,8 +122,7 @@ class CommandSynthInvioYosys(CommonSynthYosys, ToolInvioYosys):
122
122
  # Gives us bash commands with tee and pipstatus:
123
123
  invio_command_list,
124
124
  synth_command_list,
125
- sta_command_list,
126
- ],
125
+ ] + sta_command_lists,
127
126
  )
128
127
 
129
128
  # Do not run this if args['stop-before-compile'] is True
@@ -135,9 +134,10 @@ class CommandSynthInvioYosys(CommonSynthYosys, ToolInvioYosys):
135
134
  self.exec( work_dir=work_dir, command_list=synth_command_list,
136
135
  tee_fpath=synth_command_list.tee_fpath )
137
136
 
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)
137
+ for x in sta_command_lists:
138
+ if self.args['sta'] and x:
139
+ self.exec(work_dir=self.full_work_dir, command_list=x,
140
+ tee_fpath=x.tee_fpath)
141
141
 
142
142
  if self.status == 0:
143
143
  util.info(f'yosys: wrote verilog to {self.yosys_v_path}')