opencos-eda 0.2.43__tar.gz → 0.2.45__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.43/opencos_eda.egg-info → opencos_eda-0.2.45}/PKG-INFO +1 -1
  2. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/commands/__init__.py +4 -0
  3. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/commands/export.py +0 -1
  4. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/commands/flist.py +4 -1
  5. opencos_eda-0.2.45/opencos/commands/lec.py +103 -0
  6. opencos_eda-0.2.45/opencos/commands/shell.py +202 -0
  7. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/commands/sim.py +1 -1
  8. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/deps_helpers.py +2 -0
  9. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/eda.py +6 -3
  10. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/eda_base.py +12 -4
  11. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/eda_config.py +1 -1
  12. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/eda_config_defaults.yml +22 -7
  13. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/eda_extract_targets.py +13 -3
  14. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/export_helper.py +6 -4
  15. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/files.py +1 -0
  16. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/tests/helpers.py +3 -2
  17. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/tests/test_deps_schema.py +3 -1
  18. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/tests/test_eda.py +19 -9
  19. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/tests/test_eda_synth.py +63 -2
  20. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/tools/invio_yosys.py +7 -7
  21. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/tools/iverilog.py +1 -1
  22. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/tools/riviera.py +1 -1
  23. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/tools/slang.py +1 -1
  24. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/tools/slang_yosys.py +29 -38
  25. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/tools/surelog.py +1 -1
  26. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/tools/tabbycad_yosys.py +1 -1
  27. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/tools/verilator.py +1 -1
  28. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/tools/vivado.py +30 -11
  29. opencos_eda-0.2.45/opencos/tools/yosys.py +679 -0
  30. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/util.py +19 -12
  31. {opencos_eda-0.2.43 → opencos_eda-0.2.45/opencos_eda.egg-info}/PKG-INFO +1 -1
  32. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos_eda.egg-info/SOURCES.txt +2 -0
  33. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/pyproject.toml +1 -1
  34. opencos_eda-0.2.43/opencos/tools/yosys.py +0 -239
  35. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/LICENSE +0 -0
  36. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/LICENSE.spdx +0 -0
  37. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/README.md +0 -0
  38. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/__init__.py +0 -0
  39. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/_version.py +0 -0
  40. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/_waves_pkg.sv +0 -0
  41. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/commands/build.py +0 -0
  42. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/commands/elab.py +0 -0
  43. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/commands/multi.py +0 -0
  44. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/commands/open.py +0 -0
  45. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/commands/proj.py +0 -0
  46. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/commands/sweep.py +0 -0
  47. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/commands/synth.py +0 -0
  48. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/commands/targets.py +0 -0
  49. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/commands/upload.py +0 -0
  50. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/commands/waves.py +0 -0
  51. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/deps_schema.py +0 -0
  52. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/eda_config_max_verilator_waivers.yml +0 -0
  53. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/eda_config_reduced.yml +0 -0
  54. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/eda_deps_bash_completion.bash +0 -0
  55. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/eda_tool_helper.py +0 -0
  56. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/export_json_convert.py +0 -0
  57. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/names.py +0 -0
  58. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/oc_cli.py +0 -0
  59. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/pcie.py +0 -0
  60. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/peakrdl_cleanup.py +0 -0
  61. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/seed.py +0 -0
  62. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/tests/__init__.py +0 -0
  63. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/tests/custom_config.yml +0 -0
  64. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/tests/deps_files/command_order/DEPS.yml +0 -0
  65. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/tests/deps_files/error_msgs/DEPS.yml +0 -0
  66. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/tests/deps_files/iverilog_test/DEPS.yml +0 -0
  67. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/tests/deps_files/no_deps_here/DEPS.yml +0 -0
  68. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/tests/deps_files/non_sv_reqs/DEPS.yml +0 -0
  69. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/tests/deps_files/tags_with_tools/DEPS.yml +0 -0
  70. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/tests/deps_files/test_err_fatal/DEPS.yml +0 -0
  71. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/tests/test_build.py +0 -0
  72. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/tests/test_deps_helpers.py +0 -0
  73. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/tests/test_eda_elab.py +0 -0
  74. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/tests/test_oc_cli.py +0 -0
  75. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/tests/test_tools.py +0 -0
  76. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/tools/__init__.py +0 -0
  77. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/tools/invio.py +0 -0
  78. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/tools/invio_helpers.py +0 -0
  79. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/tools/modelsim_ase.py +0 -0
  80. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos/tools/questa.py +0 -0
  81. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos_eda.egg-info/dependency_links.txt +0 -0
  82. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos_eda.egg-info/entry_points.txt +0 -0
  83. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos_eda.egg-info/requires.txt +0 -0
  84. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/opencos_eda.egg-info/top_level.txt +0 -0
  85. {opencos_eda-0.2.43 → opencos_eda-0.2.45}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opencos-eda
3
- Version: 0.2.43
3
+ Version: 0.2.45
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
  ]
@@ -37,7 +37,6 @@ class CommandExport(CommandDesign):
37
37
  self, tokens: list, process_all: bool = True, pwd: str = os.getcwd()
38
38
  ) -> list:
39
39
 
40
- self.defines['OC_EXPORT'] = None
41
40
  unparsed = CommandDesign.process_tokens(
42
41
  self, tokens=tokens, process_all=process_all, pwd=pwd
43
42
  )
@@ -47,11 +47,14 @@ class CommandFList(CommandDesign):
47
47
  'no-quote-path' : False,
48
48
  'build-script' : "", # we don't want this to error either
49
49
 
50
- 'print-to-stdout': False, # do not save to file, print to stdout.
50
+ 'print-to-stdout': False,
51
51
 
52
52
  # ex: eda flist --print-to-stdout --emit-rel-path --quiet <target>
53
53
  'emit-rel-path' : False,
54
54
  })
55
+ self.args_help.update({
56
+ 'print-to-stdout': "do not save file, print to stdout",
57
+ })
55
58
 
56
59
  def process_tokens(
57
60
  self, tokens: list , process_all: bool = True, pwd: str = os.getcwd()
@@ -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
 
@@ -57,6 +57,8 @@ class Defaults:
57
57
  'with-tools',
58
58
  'with-args',
59
59
  'args',
60
+ 'deps',
61
+ 'reqs',
60
62
  'defines',
61
63
  'incdirs',
62
64
  'replace-config-tools',
@@ -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,
@@ -320,9 +322,10 @@ def process_tokens(tokens: list, original_args: list, config: dict, interactive=
320
322
  for value in unparsed:
321
323
  if value in config['DEFAULT_HANDLERS'].keys():
322
324
  command = value
323
- if value in config['command_uses_no_tools']:
325
+ if not parsed.tool and value in config['command_tool_is_optional']:
326
+ # only do this if --tool was not set.
324
327
  run_auto_tool_setup = False
325
- unparsed.remove(value)
328
+ unparsed.remove(value) # remove command (flist, export, targets, etc)
326
329
  break
327
330
 
328
331
  if not interactive:
@@ -361,7 +364,7 @@ def process_tokens(tokens: list, original_args: list, config: dict, interactive=
361
364
  util.debug(f'{type(sco)=}')
362
365
  if not parsed.tool and \
363
366
  command not in config.get('command_determines_tool', []) and \
364
- command not in config.get('command_uses_no_tools', []):
367
+ command not in config.get('command_tool_is_optional', []):
365
368
  use_tool = which_tool(command, config)
366
369
  util.info(f"--tool not specified, using default for {command=}: {use_tool}")
367
370
 
@@ -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()
@@ -686,6 +688,7 @@ class CommandDesign(Command):
686
688
  self.files_sv = []
687
689
  self.files_vhd = []
688
690
  self.files_cpp = []
691
+ self.files_sdc = []
689
692
  self.files_non_source = []
690
693
  self.files_caller_info = {}
691
694
  self.dep_shell_commands = [] # each list entry is a {}
@@ -739,9 +742,9 @@ class CommandDesign(Command):
739
742
  util.info(f'run_dep_shell_commands {iter=}: {d=}')
740
743
  clist = util.ShellCommandList(d['exec_list'])
741
744
  # NOTE(drew): shell=True subprocess call, can disable with self.config
742
- # However, in Windows, we seem to have to run these with shell=False
743
745
  if sys.platform.startswith('win'):
744
- 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)
745
748
  else:
746
749
  self.exec(self.args['work-dir'], clist, tee_fpath=clist.tee_fpath,
747
750
  shell=self.config.get('deps_subprocess_shell', False))
@@ -760,7 +763,8 @@ class CommandDesign(Command):
760
763
  self.files.pop(key)
761
764
  self.files[new_key] = True
762
765
 
763
- my_file_lists_list = [self.files_v, self.files_sv, self.files_vhd, self.files_cpp]
766
+ my_file_lists_list = [self.files_v, self.files_sv, self.files_vhd, self.files_cpp,
767
+ self.files_sdc]
764
768
  for my_file_list in my_file_lists_list:
765
769
  for iter,value in enumerate(my_file_list):
766
770
  if value and type(value) is str and value.startswith(self._work_dir_add_srcs_path_string):
@@ -1029,6 +1033,7 @@ class CommandDesign(Command):
1029
1033
  sv_file_ext_list = known_file_ext_dict.get('systemverilog', [])
1030
1034
  vhdl_file_ext_list = known_file_ext_dict.get('vhdl', [])
1031
1035
  cpp_file_ext_list = known_file_ext_dict.get('cpp', [])
1036
+ sdc_file_ext_list = known_file_ext_dict.get('synth_constraints', [])
1032
1037
 
1033
1038
  if forced_extension:
1034
1039
  # If forced_extension='systemverilog', then use the first known extension for
@@ -1057,6 +1062,9 @@ class CommandDesign(Command):
1057
1062
  elif file_ext in cpp_file_ext_list:
1058
1063
  self.files_cpp.append(file_abspath)
1059
1064
  util.debug("Added C++ file %s as %s" % (filename, file_abspath))
1065
+ elif file_ext in sdc_file_ext_list:
1066
+ self.files_sdc.append(file_abspath)
1067
+ util.debug("Added SDC file %s as %s" % (filename, file_abspath))
1060
1068
  else:
1061
1069
  # unknown file extension. In these cases we link the file to the working directory
1062
1070
  # so it is available (for example, a .mem file that is expected to exist with relative path)
@@ -1154,7 +1162,7 @@ class CommandDesign(Command):
1154
1162
  else:
1155
1163
  target_name = os.path.join(".", token) # prepend ./so that we always have a <path>/<file>
1156
1164
 
1157
- util.debug(f'Calling self.resolve_target on {target_name=}')
1165
+ util.debug(f'Calling self.resolve_target on {target_name=} ({token=})')
1158
1166
  if self.resolve_target(target_name, caller_info=caller_info):
1159
1167
  if not self.args['top']:
1160
1168
  # last cmd line arg was a target that we'll likely use for inferred top.
@@ -30,7 +30,7 @@ class Defaults:
30
30
  'vars',
31
31
  'file_extensions',
32
32
  'command_determines_tool',
33
- 'command_uses_no_tools',
33
+ 'command_tool_is_optional',
34
34
  'tools',
35
35
  'auto_tools_order',
36
36
  ])
@@ -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,9 @@ file_extensions:
60
62
  - .vhdl
61
63
  cpp:
62
64
  - .cpp
65
+ synth_constraints:
66
+ - .sdc
67
+ - .xdc
63
68
 
64
69
  inferred_top:
65
70
  # file extensions that we can infer "top" module from, if --top omitted.
@@ -72,8 +77,9 @@ command_determines_tool:
72
77
  # eda commands that will self-determine the tool to use
73
78
  - waves
74
79
 
75
- command_uses_no_tools:
76
- # eda commands that do not use a tool at all, will skip auto_tools_order
80
+ command_tool_is_optional:
81
+ # eda commands that may not need to use a tool at all, will skip auto_tools_order if --tool=None (default)
82
+ - flist
77
83
  - export
78
84
  - targets
79
85
 
@@ -333,6 +339,15 @@ auto_tools_order:
333
339
  flist: opencos.tools.vivado.CommandFListVivado
334
340
  build: opencos.tools.vivado.CommandBuildVivado
335
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
+
336
351
  tabbycad_yosys:
337
352
  exe: yosys
338
353
  requires_env:
@@ -350,13 +365,13 @@ auto_tools_order:
350
365
  elab: opencos.tools.invio_yosys.CommandElabInvioYosys
351
366
  synth: opencos.tools.invio_yosys.CommandSynthInvioYosys
352
367
 
353
- slang_yosys:
368
+ yosys:
354
369
  exe: yosys
355
370
  requires_cmd:
356
- - yosys -m slang
371
+ - yosys
357
372
  handlers:
358
- elab: opencos.tools.slang_yosys.CommandElabSlangYosys
359
- synth: opencos.tools.slang_yosys.CommandSynthSlangYosys
373
+ synth: opencos.tools.slang_yosys.CommonSynthYosys
374
+ lec: opencos.tools.slang_yosys.CommandLecYosys
360
375
 
361
376
  questa:
362
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
 
@@ -352,23 +352,25 @@ class ExportHelper:
352
352
  }
353
353
  }
354
354
 
355
- if len(deps_file_args) > 0:
355
+
356
+ if deps_file_args:
356
357
  data[self.target]['args'] = deps_file_args.copy()
357
358
 
358
359
  if self.args.get('top', None):
359
360
  data[self.target]['top'] = self.args['top']
360
361
 
361
- if len(self.cmd_design_obj.defines.keys()) > 0:
362
+ if self.cmd_design_obj.defines:
362
363
  data[self.target]['defines'] = self.cmd_design_obj.defines.copy()
363
364
  for define in _remove_DEPS_yml_defines:
364
365
  # Remove defines keys for OC_ROOT and OC_SEED. Change OC_SEED to _ORIG_OC_SEED
365
366
  if define in data[self.target]['defines']:
366
367
  data[self.target]['defines'].pop(define)
367
368
 
368
- if len(self.cmd_design_obj.files_non_source) > 0:
369
+ reqs_fullpath_list = self.included_files + self.cmd_design_obj.files_non_source
370
+ if reqs_fullpath_list:
369
371
  # Need to strip path information from non-source files:
370
372
  data[self.target]['reqs'] = list()
371
- for fullpath in self.cmd_design_obj.files_non_source:
373
+ for fullpath in reqs_fullpath_list:
372
374
  filename = os.path.split(fullpath)[1]
373
375
  data[self.target]['reqs'].append(filename)
374
376
 
@@ -23,6 +23,7 @@ FORCE_PREFIX_DICT = {
23
23
  'v@': 'verilog',
24
24
  'vhdl@': 'vhdl',
25
25
  'cpp@': 'cpp',
26
+ 'sdc@': 'synth_constraints',
26
27
  }
27
28
 
28
29
  ALL_FORCED_PREFIXES = set(list(FORCE_PREFIX_DICT.keys()))
@@ -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):
@@ -128,7 +129,7 @@ class Helpers:
128
129
  '''Changes directory to self.DEFAULT_DIR and removes eda.work, eda.export paths'''
129
130
  chdir_remove_work_dir('', self.DEFAULT_DIR)
130
131
 
131
- def log_it(self, command_str:str, logfile=None, use_eda_wrap=True):
132
+ def log_it(self, command_str:str, logfile=None, use_eda_wrap=True) -> int:
132
133
  '''Replacement for calling eda.main or eda_wrap, when you want an internal logfile
133
134
 
134
135
  Usage:
@@ -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