opencos-eda 0.2.44__tar.gz → 0.2.46__tar.gz

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 (85) hide show
  1. {opencos_eda-0.2.44/opencos_eda.egg-info → opencos_eda-0.2.46}/PKG-INFO +1 -1
  2. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/commands/__init__.py +4 -0
  3. opencos_eda-0.2.46/opencos/commands/lec.py +103 -0
  4. opencos_eda-0.2.46/opencos/commands/shell.py +202 -0
  5. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/commands/sim.py +1 -1
  6. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/deps_helpers.py +15 -12
  7. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/deps_schema.py +3 -0
  8. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/eda.py +2 -0
  9. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/eda_base.py +32 -15
  10. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/eda_config_defaults.yml +17 -5
  11. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/eda_extract_targets.py +13 -3
  12. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/tests/helpers.py +2 -1
  13. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/tests/test_deps_schema.py +3 -1
  14. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/tests/test_eda.py +19 -9
  15. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/tools/invio_yosys.py +7 -7
  16. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/tools/slang_yosys.py +29 -38
  17. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/tools/tabbycad_yosys.py +1 -1
  18. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/tools/vivado.py +8 -7
  19. opencos_eda-0.2.46/opencos/tools/yosys.py +679 -0
  20. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/util.py +14 -6
  21. {opencos_eda-0.2.44 → opencos_eda-0.2.46/opencos_eda.egg-info}/PKG-INFO +1 -1
  22. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos_eda.egg-info/SOURCES.txt +2 -0
  23. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/pyproject.toml +1 -1
  24. opencos_eda-0.2.44/opencos/tools/yosys.py +0 -258
  25. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/LICENSE +0 -0
  26. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/LICENSE.spdx +0 -0
  27. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/README.md +0 -0
  28. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/__init__.py +0 -0
  29. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/_version.py +0 -0
  30. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/_waves_pkg.sv +0 -0
  31. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/commands/build.py +0 -0
  32. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/commands/elab.py +0 -0
  33. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/commands/export.py +0 -0
  34. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/commands/flist.py +0 -0
  35. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/commands/multi.py +0 -0
  36. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/commands/open.py +0 -0
  37. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/commands/proj.py +0 -0
  38. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/commands/sweep.py +0 -0
  39. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/commands/synth.py +0 -0
  40. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/commands/targets.py +0 -0
  41. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/commands/upload.py +0 -0
  42. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/commands/waves.py +0 -0
  43. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/eda_config.py +0 -0
  44. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/eda_config_max_verilator_waivers.yml +0 -0
  45. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/eda_config_reduced.yml +0 -0
  46. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/eda_deps_bash_completion.bash +0 -0
  47. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/eda_tool_helper.py +0 -0
  48. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/export_helper.py +0 -0
  49. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/export_json_convert.py +0 -0
  50. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/files.py +0 -0
  51. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/names.py +0 -0
  52. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/oc_cli.py +0 -0
  53. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/pcie.py +0 -0
  54. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/peakrdl_cleanup.py +0 -0
  55. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/seed.py +0 -0
  56. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/tests/__init__.py +0 -0
  57. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/tests/custom_config.yml +0 -0
  58. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/tests/deps_files/command_order/DEPS.yml +0 -0
  59. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/tests/deps_files/error_msgs/DEPS.yml +0 -0
  60. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/tests/deps_files/iverilog_test/DEPS.yml +0 -0
  61. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/tests/deps_files/no_deps_here/DEPS.yml +0 -0
  62. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/tests/deps_files/non_sv_reqs/DEPS.yml +0 -0
  63. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/tests/deps_files/tags_with_tools/DEPS.yml +0 -0
  64. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/tests/deps_files/test_err_fatal/DEPS.yml +0 -0
  65. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/tests/test_build.py +0 -0
  66. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/tests/test_deps_helpers.py +0 -0
  67. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/tests/test_eda_elab.py +0 -0
  68. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/tests/test_eda_synth.py +0 -0
  69. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/tests/test_oc_cli.py +0 -0
  70. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/tests/test_tools.py +0 -0
  71. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/tools/__init__.py +0 -0
  72. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/tools/invio.py +0 -0
  73. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/tools/invio_helpers.py +0 -0
  74. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/tools/iverilog.py +0 -0
  75. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/tools/modelsim_ase.py +0 -0
  76. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/tools/questa.py +0 -0
  77. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/tools/riviera.py +0 -0
  78. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/tools/slang.py +0 -0
  79. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/tools/surelog.py +0 -0
  80. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos/tools/verilator.py +0 -0
  81. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos_eda.egg-info/dependency_links.txt +0 -0
  82. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos_eda.egg-info/entry_points.txt +0 -0
  83. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos_eda.egg-info/requires.txt +0 -0
  84. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/opencos_eda.egg-info/top_level.txt +0 -0
  85. {opencos_eda-0.2.44 → opencos_eda-0.2.46}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opencos-eda
3
- Version: 0.2.44
3
+ Version: 0.2.46
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
@@ -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
@@ -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
 
@@ -68,6 +68,7 @@ class Defaults:
68
68
  'shell',
69
69
  'work-dir-add-srcs', 'work-dir-add-sources',
70
70
  'peakrdl',
71
+ 'run-from-work-dir',
71
72
  'var-subst-args',
72
73
  'var-subst-os-env',
73
74
  'tee',
@@ -387,20 +388,20 @@ def path_substitutions_relative_to_work_dir(exec_list : list, info_str : str, ta
387
388
 
388
389
  # Look for path substitutions, b/c we later "work" in self.args['work-dir'], but
389
390
  # files should be relative to our target_path.
390
- for iter,word in enumerate(exec_list):
391
+ for i,word in enumerate(exec_list):
391
392
  m = re.search(r'(\.+\/+[^"\;\:\|\<\>\*]*)$', word)
392
393
  if m:
393
394
  # ./, ../, file=./../whatever It might be a filepath.
394
395
  # [^"\;\:\|\<\>\*] is looking for non-path like characters, so we dont' have a trailing
395
396
  # " : ; < > |
396
- # try and see if this file exists. Note that files in the self.args['work-dir'] don't
397
+ # try and see if this file or dir exists. Note that files in the self.args['work-dir'] don't
397
398
  # need this, and we can't assume dir levels in the work-dir.
398
399
  try:
399
400
  try_path = os.path.abspath(os.path.join(os.path.abspath(target_path), m.group(1)))
400
- if os.path.isfile(try_path):
401
+ if os.path.isfile(try_path) or os.path.isdir(try_path):
401
402
  # make the substitution
402
- exec_list[iter] = word.replace(m.group(1), try_path)
403
- debug(f'path substitution {info_str=} {target_path=}: replaced - {word=} is now ={exec_list[iter]}')
403
+ exec_list[i] = word.replace(m.group(1), try_path)
404
+ debug(f'path substitution {info_str=} {target_path=}: replaced - {word=} is now ={exec_list[i]}')
404
405
  except:
405
406
  pass
406
407
 
@@ -1064,6 +1065,7 @@ def parse_deps_shell_str(line : str, target_path : str, target_node : str, enabl
1064
1065
 
1065
1066
  d = {'target_path': os.path.abspath(target_path),
1066
1067
  'target_node': target_node,
1068
+ 'run_from_work_dir': True,
1067
1069
  'exec_list': exec_list,
1068
1070
  }
1069
1071
  return d
@@ -1137,12 +1139,12 @@ def parse_deps_peakrdl(line : str, target_path : str, target_node : str, enable
1137
1139
 
1138
1140
  sv_files = list()
1139
1141
  top = ''
1140
- for iter,str_value in enumerate(args_list):
1142
+ for i,str_value in enumerate(args_list):
1141
1143
  if '--top=' in str_value:
1142
1144
  _, top = str_value.split('=')
1143
1145
  elif '--top' in str_value:
1144
- if iter + 1 < len(args_list):
1145
- top = args_list[iter + 1]
1146
+ if i + 1 < len(args_list):
1147
+ top = args_list[i + 1]
1146
1148
 
1147
1149
  for str_item in args_list:
1148
1150
  if str_item[-4:] == '.rdl':
@@ -1235,14 +1237,14 @@ def deps_commands_handler(config: dict, eda_args: dict,
1235
1237
  var_subst_dict = eda_args
1236
1238
 
1237
1239
  tee_fpath = command.get('tee', None)
1240
+ run_from_work_dir = command.get('run-from-work-dir', True) # for shell, default True
1238
1241
 
1239
1242
  for key,item in command.items():
1240
1243
 
1241
1244
  # skip the var-subst-* keys, since these types are bools
1242
- if key.startswith('var-subst'):
1243
- continue
1244
-
1245
- if key.startswith('tee'):
1245
+ if key.startswith('var-subst') or \
1246
+ key.startswith('tee') or \
1247
+ key.startswith('run-from-work-dir'):
1246
1248
  continue
1247
1249
 
1248
1250
  # Optional variable substituion in commands
@@ -1262,6 +1264,7 @@ def deps_commands_handler(config: dict, eda_args: dict,
1262
1264
  # list item in ret_dict['exec_list'], and make it a util.ShellCommandList.
1263
1265
  if tee_fpath:
1264
1266
  ret_dict['exec_list'] = ShellCommandList(ret_dict['exec_list'], tee_fpath=tee_fpath)
1267
+ ret_dict['run_from_work_dir'] = run_from_work_dir
1265
1268
  assert ret_dict, f'shell command failed in {dep=} {target_node=} in {deps_file=}'
1266
1269
  shell_commands_list.append(ret_dict) # process this later, append to our to-be-returned tuple
1267
1270
 
@@ -69,6 +69,8 @@ my_target_name:
69
69
  - shell: <---- string for shell command to be run
70
70
  var-subst-args: <---- bool, perform var substitution using args
71
71
  var-subst-os-env: <---- bool, perform var substitution using os.environ
72
+ run-from-work-dir: <---- bool, default True, if False runs from target dir
73
+ instead of work-dir.
72
74
  tee: <---- string, filename to write logs to
73
75
  - work-dir-add-sources: <---- work-dir-add-sources, optional list (or string)
74
76
  - some_file_gen_from_sh.sv <---- string filename that we created with sh command
@@ -150,6 +152,7 @@ DEPS_COMMANDS_LIST = [
150
152
  Optional('shell'): str,
151
153
  Optional('var-subst-args'): bool,
152
154
  Optional('var-subst-os-env'): bool,
155
+ Optional('run-from-work-dir'): bool,
153
156
  Optional('tee'): Or(str, type(None)),
154
157
  },
155
158
  {
@@ -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,
@@ -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()
@@ -714,8 +716,9 @@ class CommandDesign(Command):
714
716
 
715
717
  log_fnames_count = {} # count per target_node.
716
718
 
717
- for iter, d in enumerate(self.dep_shell_commands):
719
+ for i, d in enumerate(self.dep_shell_commands):
718
720
  clist = util.ShellCommandList(d['exec_list'])
721
+ run_from_work_dir = d['run_from_work_dir'] # default True
719
722
  log = clist.tee_fpath
720
723
  target_node = d["target_node"]
721
724
  if clist.tee_fpath is None:
@@ -727,24 +730,38 @@ class CommandDesign(Command):
727
730
  all_cmds_lists += [
728
731
  [], # blank line
729
732
  # comment, where it came from, log to {node}__shell_{lognum}.log (or tee name from DEPS.yml)
730
- [f'# command {iter}: target: {d["target_path"]} : {target_node} --> {log}'],
731
- # actual command (list or util.ShellCommandList)
732
- clist,
733
+ [f'# command {i}: target: {d["target_path"]} : {target_node} --> {log}'],
733
734
  ]
735
+ if not run_from_work_dir:
736
+ all_cmds_lists.append([f'cd {d["target_path"]}'])
737
+
738
+ # actual command (list or util.ShellCommandList)
739
+ all_cmds_lists.append(clist)
740
+
741
+ if not run_from_work_dir:
742
+ all_cmds_lists.append([f'cd {os.path.abspath(self.args["work-dir"])}'])
743
+
734
744
  d['exec_list'] = clist # update to tee_fpath is set.
735
745
 
736
746
  util.write_shell_command_file(dirpath=self.args['work-dir'], filename='pre_compile_dep_shell_commands.sh',
737
747
  command_lists=all_cmds_lists)
738
748
 
739
- for iter,d in enumerate(self.dep_shell_commands):
740
- util.info(f'run_dep_shell_commands {iter=}: {d=}')
749
+ for i,d in enumerate(self.dep_shell_commands):
750
+ util.info(f'run_dep_shell_commands {i=}: {d=}')
741
751
  clist = util.ShellCommandList(d['exec_list'])
752
+ tee_fpath=clist.tee_fpath
753
+ if d['run_from_work_dir']:
754
+ run_from_dir = self.args['work-dir']
755
+ else:
756
+ # Run from the target's directory (not the `eda` caller $PWD)
757
+ run_from_dir = d["target_path"]
758
+ tee_fpath = os.path.abspath(os.path.join(self.args['work-dir'], tee_fpath))
742
759
  # 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
760
  if sys.platform.startswith('win'):
745
- self.exec(self.args['work-dir'], clist, shell=False)
761
+ # for Windows, we run shell=True otherwise most built-in cmd.exe calls won't work.
762
+ self.exec(run_from_dir, clist, tee_fpath=tee_fpath, shell=True)
746
763
  else:
747
- self.exec(self.args['work-dir'], clist, tee_fpath=clist.tee_fpath,
764
+ self.exec(run_from_dir, clist, tee_fpath=tee_fpath,
748
765
  shell=self.config.get('deps_subprocess_shell', False))
749
766
 
750
767
  def update_file_lists_for_work_dir(self):
@@ -764,10 +781,10 @@ class CommandDesign(Command):
764
781
  my_file_lists_list = [self.files_v, self.files_sv, self.files_vhd, self.files_cpp,
765
782
  self.files_sdc]
766
783
  for my_file_list in my_file_lists_list:
767
- for iter,value in enumerate(my_file_list):
784
+ for i,value in enumerate(my_file_list):
768
785
  if value and type(value) is str and value.startswith(self._work_dir_add_srcs_path_string):
769
786
  new_value = os.path.join(work_dir_abspath, value[_work_dir_add_srcs_path_string_len :])
770
- my_file_list[iter] = new_value
787
+ my_file_list[i] = new_value
771
788
  util.debug(f"file lists: replaced {value} with {new_value}")
772
789
 
773
790
  def update_non_source_files_in_work_dir(self):
@@ -1160,7 +1177,7 @@ class CommandDesign(Command):
1160
1177
  else:
1161
1178
  target_name = os.path.join(".", token) # prepend ./so that we always have a <path>/<file>
1162
1179
 
1163
- util.debug(f'Calling self.resolve_target on {target_name=}')
1180
+ util.debug(f'Calling self.resolve_target on {target_name=} ({token=})')
1164
1181
  if self.resolve_target(target_name, caller_info=caller_info):
1165
1182
  if not self.args['top']:
1166
1183
  # last cmd line arg was a target that we'll likely use for inferred top.
@@ -1573,10 +1590,10 @@ class CommandParallel(Command):
1573
1590
  else:
1574
1591
  util.info(f"Parallel: <No jobs found>")
1575
1592
  # Make sure all jobs have a set status:
1576
- for iter,rc in enumerate(self.jobs_status):
1593
+ for i,rc in enumerate(self.jobs_status):
1577
1594
  if rc is None or type(rc) != int:
1578
- self.error(f'job {iter=} {rc=} did not return a proper return code')
1579
- jobs_status[iter] = 1
1595
+ self.error(f'job {i=} {rc=} did not return a proper return code')
1596
+ jobs_status[i] = 1
1580
1597
 
1581
1598
  # if self.status > 0, then keep it non-zero, else set it if we still have running jobs.
1582
1599
  if self.status == 0:
@@ -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
 
@@ -60,6 +62,7 @@ file_extensions:
60
62
  - .vhdl
61
63
  cpp:
62
64
  - .cpp
65
+ - .cc
63
66
  synth_constraints:
64
67
  - .sdc
65
68
  - .xdc
@@ -337,6 +340,15 @@ auto_tools_order:
337
340
  flist: opencos.tools.vivado.CommandFListVivado
338
341
  build: opencos.tools.vivado.CommandBuildVivado
339
342
 
343
+ slang_yosys:
344
+ exe: yosys
345
+ requires_cmd:
346
+ - yosys -m slang
347
+ handlers:
348
+ elab: opencos.tools.slang_yosys.CommandElabSlangYosys
349
+ synth: opencos.tools.slang_yosys.CommandSynthSlangYosys
350
+ lec: opencos.tools.slang_yosys.CommandLecSlangYosys
351
+
340
352
  tabbycad_yosys:
341
353
  exe: yosys
342
354
  requires_env:
@@ -354,13 +366,13 @@ auto_tools_order:
354
366
  elab: opencos.tools.invio_yosys.CommandElabInvioYosys
355
367
  synth: opencos.tools.invio_yosys.CommandSynthInvioYosys
356
368
 
357
- slang_yosys:
369
+ yosys:
358
370
  exe: yosys
359
371
  requires_cmd:
360
- - yosys -m slang
372
+ - yosys
361
373
  handlers:
362
- elab: opencos.tools.slang_yosys.CommandElabSlangYosys
363
- synth: opencos.tools.slang_yosys.CommandSynthSlangYosys
374
+ synth: opencos.tools.slang_yosys.CommonSynthYosys
375
+ lec: opencos.tools.slang_yosys.CommandLecYosys
364
376
 
365
377
  questa:
366
378
  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
 
@@ -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):