opencos-eda 0.2.52__py3-none-any.whl → 0.2.54__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. opencos/commands/__init__.py +2 -0
  2. opencos/commands/build.py +1 -1
  3. opencos/commands/deps_help.py +259 -0
  4. opencos/commands/export.py +1 -1
  5. opencos/commands/flist.py +4 -1
  6. opencos/commands/lec.py +1 -1
  7. opencos/commands/open.py +2 -0
  8. opencos/commands/proj.py +1 -1
  9. opencos/commands/shell.py +1 -1
  10. opencos/commands/sim.py +76 -8
  11. opencos/commands/synth.py +1 -1
  12. opencos/commands/upload.py +3 -0
  13. opencos/commands/waves.py +1 -0
  14. opencos/deps/defaults.py +1 -0
  15. opencos/deps/deps_file.py +30 -4
  16. opencos/deps/deps_processor.py +72 -2
  17. opencos/deps_schema.py +3 -0
  18. opencos/eda.py +50 -26
  19. opencos/eda_base.py +177 -33
  20. opencos/eda_config.py +1 -1
  21. opencos/eda_config_defaults.yml +49 -3
  22. opencos/eda_extract_targets.py +1 -58
  23. opencos/tests/helpers.py +16 -0
  24. opencos/tests/test_eda.py +14 -3
  25. opencos/tests/test_tools.py +159 -132
  26. opencos/tools/cocotb.py +15 -14
  27. opencos/tools/iverilog.py +4 -24
  28. opencos/tools/modelsim_ase.py +70 -57
  29. opencos/tools/quartus.py +680 -0
  30. opencos/tools/questa.py +158 -90
  31. opencos/tools/questa_fse.py +10 -0
  32. opencos/tools/riviera.py +1 -0
  33. opencos/tools/verilator.py +9 -15
  34. opencos/tools/vivado.py +30 -23
  35. opencos/util.py +89 -15
  36. opencos/utils/status_constants.py +1 -0
  37. opencos/utils/str_helpers.py +85 -0
  38. {opencos_eda-0.2.52.dist-info → opencos_eda-0.2.54.dist-info}/METADATA +1 -1
  39. {opencos_eda-0.2.52.dist-info → opencos_eda-0.2.54.dist-info}/RECORD +44 -42
  40. {opencos_eda-0.2.52.dist-info → opencos_eda-0.2.54.dist-info}/WHEEL +0 -0
  41. {opencos_eda-0.2.52.dist-info → opencos_eda-0.2.54.dist-info}/entry_points.txt +0 -0
  42. {opencos_eda-0.2.52.dist-info → opencos_eda-0.2.54.dist-info}/licenses/LICENSE +0 -0
  43. {opencos_eda-0.2.52.dist-info → opencos_eda-0.2.54.dist-info}/licenses/LICENSE.spdx +0 -0
  44. {opencos_eda-0.2.52.dist-info → opencos_eda-0.2.54.dist-info}/top_level.txt +0 -0
opencos/tools/questa.py CHANGED
@@ -14,7 +14,7 @@ import shutil
14
14
 
15
15
  from opencos import util
16
16
  from opencos.eda_base import Tool
17
- from opencos.commands import CommandSim
17
+ from opencos.commands import CommandSim, CommandFList
18
18
 
19
19
  class ToolQuesta(Tool):
20
20
  '''Base class for CommandSimQuesta, collects version information about qrun'''
@@ -23,6 +23,7 @@ class ToolQuesta(Tool):
23
23
  _EXE = 'qrun'
24
24
 
25
25
  starter_edition = False # Aka, modelsim_ase
26
+ use_vopt = shutil.which('vopt') # vopt exists in qrun/vsim framework, and we'll use it.
26
27
  sim_exe = '' # vsim or qrun
27
28
  sim_exe_base_path = ''
28
29
  questa_major = None
@@ -74,126 +75,193 @@ class CommandSimQuesta(CommandSim, ToolQuesta):
74
75
  CommandSim.__init__(self, config)
75
76
  ToolQuesta.__init__(self, config=self.config)
76
77
  # add args specific to this simulator
77
- self.args['gui'] = False
78
- self.args['tcl-file'] = "sim.tcl"
78
+ self.args.update({
79
+ 'gui': False,
80
+ 'tcl-file': 'sim.tcl',
81
+ 'work-lib': 'work',
82
+ })
83
+ self.args_help.update({
84
+ 'gui': 'Run Questa in GUI mode',
85
+ 'tcl-file': 'name of TCL file to be created for Questa simulation',
86
+ 'work-lib': 'Questa work library name',
87
+ })
88
+
79
89
  self.shell_command = self.sim_exe # set by ToolQuesta.get_versions(self)
90
+ self.vlog_commands = []
91
+ self.vopt_commands = []
92
+ self.vsim_commands = []
80
93
 
81
94
  def set_tool_defines(self):
82
95
  ToolQuesta.set_tool_defines(self)
83
96
 
84
- def do_it(self):
85
- # add defines for this job
97
+ # We do not override CommandSim.do_it(), CommandSim.check_logs_for_errors(...)
98
+
99
+ def prepare_compile(self):
100
+ self.set_tool_defines()
101
+ self.vlog_commands = self.get_compile_command_lists()
102
+ self.vopt_commands = self.get_elaborate_command_lists()
103
+ self.vsim_commands = self.get_simulate_command_lists()
104
+ self.write_sh_scripts_to_work_dir(
105
+ compile_lists=self.vlog_commands,
106
+ elaborate_lists=self.vopt_commands,
107
+ simulate_lists=self.vsim_commands
108
+ )
109
+
110
+ def compile(self):
111
+ if self.args['stop-before-compile']:
112
+ return
113
+ self.run_commands_check_logs(
114
+ self.vlog_commands, check_logs=True, log_filename='vlog.log'
115
+ )
116
+
117
+ def elaborate(self):
118
+ if self.args['stop-before-compile'] or self.args['stop-after-compile']:
119
+ return
120
+ self.run_commands_check_logs(
121
+ self.vopt_commands, check_logs=True, log_filename='vopt.log'
122
+ )
123
+
124
+ def simulate(self):
125
+ if self.args['stop-before-compile'] or self.args['stop-after-compile'] or \
126
+ self.args['stop-after-elaborate']:
127
+ return
128
+ self.run_commands_check_logs(
129
+ self.vsim_commands, check_logs=True, log_filename='vsim.log'
130
+ )
131
+
132
+ def get_compile_command_lists(self, **kwargs) -> list:
86
133
  self.set_tool_defines()
87
- self.write_eda_config_and_args()
88
-
89
- # it all gets done with one command
90
- command_list = [ self.shell_command, "-64", "-sv" ]
91
-
92
- # incdirs
93
- for value in self.incdirs:
94
- command_list += [ f"+incdir+{value}" ]
95
-
96
- # defines
97
- for key, value in self.defines.items():
98
- if value is None:
99
- command_list += [ f"+define+{key}" ]
100
- elif isinstance(value, str) and "\'" in value:
101
- command_list += [ f"\"+define+{key}={value}\"" ]
102
- else:
103
- command_list += [ f"\'+define+{key}={value}\'" ]
104
-
105
- # compile verilog
106
- for f in self.files_v:
107
- command_list += [ f ]
108
-
109
- # compile systemverilog
110
- for f in self.files_sv:
111
- command_list += [ f ]
112
-
113
- # TODO(drew): We don't natively support Xilinx Ultrascale+ glbl.v in questa yet.
114
- # To do so, would need to do something along the lines of:
115
- # if using_queta_and_xilinx_libs:
116
- # vivado_base_path = getattr(self, 'vivado_base_path', '')
117
- # glbl_v = vivado_base_path.replace('bin', 'data/verilog/src/glbl.v')
118
- # if not os.path.exists(glbl_v):
119
- # self.error(f"Vivado is not setup, could not find file {glbl_v=}")
120
- # command_list.append(glbl_v)
121
-
122
- # misc options
123
- command_list += [ '-top', self.args['top'], '-timescale', '1ns/1ps', '-work', 'work.lib']
124
- # TODO(drew): most of these should move to eda_config_defaults.yml
125
- command_list += [
126
- # avoid warnings about defaulting to "var" which isn't LRM behavior
127
- '-svinputport=net',
128
- # Existing package 'xxxx_pkg' at line 9 will be overwritten.
129
- '-suppress', 'vlog-2275',
130
- # Extra checking for conflict in always_comb and always_latch variables at vopt time
131
- '-suppress', 'vlog-2583',
132
- # Missing connection for port 'xxxx' (The default port value will be used)
134
+ ret = []
135
+
136
+ # Create work library
137
+ vlib_cmd = [os.path.join(self.sim_exe_base_path, 'vlib'), self.args['work-lib']]
138
+ ret.append(vlib_cmd)
139
+
140
+ # Map work library
141
+ vmap_cmd = [os.path.join(self.sim_exe_base_path, 'vmap'), 'work', self.args['work-lib']]
142
+ ret.append(vmap_cmd)
143
+
144
+ # Compile files
145
+ if self.files_v or self.files_sv:
146
+ vlog_cmd = [os.path.join(self.sim_exe_base_path, 'vlog'), '-64', '-sv']
147
+
148
+ # Add include directories
149
+ for incdir in self.incdirs:
150
+ vlog_cmd += [f'+incdir+{incdir}']
151
+
152
+ # Add defines
153
+ for key, value in self.defines.items():
154
+ if value is None:
155
+ vlog_cmd += [f'+define+{key}']
156
+ else:
157
+ vlog_cmd += [f'+define+{key}={value}']
158
+
159
+ # Add suppression flags
160
+ vlog_cmd += [
161
+ '-svinputport=net',
162
+ '-suppress', 'vlog-2275',
163
+ '-suppress', 'vlog-2583',
164
+ ]
165
+
166
+ # Add source files
167
+ vlog_cmd += self.files_v + self.files_sv
168
+
169
+ ret.append(vlog_cmd)
170
+
171
+ # Compile VHDL files if any
172
+ if self.files_vhd:
173
+ vcom_cmd = [os.path.join(self.sim_exe_base_path, 'vcom'), '-64']
174
+ vcom_cmd += self.files_vhd
175
+ ret.append(vcom_cmd)
176
+
177
+ return ret
178
+
179
+ def get_elaborate_command_lists(self, **kwargs) -> list:
180
+ if self.args['stop-after-compile']:
181
+ return []
182
+
183
+ vopt_cmd = [os.path.join(self.sim_exe_base_path, 'vopt'), '-64']
184
+
185
+ # Add optimization flags
186
+ vopt_cmd += [
133
187
  '-suppress', 'vopt-13159',
134
- # Too few port connections for 'uAW_FIFO'. Expected 10, found 8
135
188
  '-suppress', 'vopt-2685',
136
- # Missing connection for port 'almostEmpty' ... same message for inputs and outputs.
137
189
  '-note', 'vopt-2718',
138
190
  ]
139
- if self.args['gui']:
140
- command_list += ['-gui=interactive', '+acc', '-i']
141
- elif self.args['waves']:
142
- command_list += ['+acc', '-c']
143
- else:
144
- command_list += ['-c']
145
191
 
146
- if util.args['verbose']:
147
- command_list += ['-verbose']
192
+ if self.args['gui'] or self.args['waves']:
193
+ vopt_cmd += ['+acc']
148
194
 
149
- # TODO(drew): We don't natively support Xilinx Ultrascale+ libraries in --tool=questa
150
- # with an easy hook (like --xilinx) but we could?
151
- # To get this to work, need to add this, among other items:
152
- # --> command_list += '''-L xil_defaultlib -L unisims_ver -L unimacro_ver
153
- # -L xpm -L secureip -L xilinx_vip'''.split(" ")
195
+ # Add top module and output
196
+ vopt_cmd += [self.args['top'], '-o', 'opt_design']
154
197
 
155
- # check if we're bailing out early
156
- if self.args['stop-after-elaborate']:
157
- command_list += ['-elab', 'elab.output', '-do', '"quit"' ]
198
+ return [vopt_cmd]
158
199
 
159
- # create TCL
200
+ def get_simulate_command_lists(self, **kwargs) -> list:
201
+ # Create TCL file
160
202
  tcl_name = os.path.abspath(os.path.join(self.args['work-dir'], self.args['tcl-file']))
161
- with open( tcl_name, 'w', encoding='utf-8' ) as fo:
203
+
204
+ if self.args['waves']:
205
+ util.artifacts.add_extension(
206
+ search_paths=self.args['work-dir'], file_extension='wlf',
207
+ typ='waveform', description='Questa Waveform WLF file'
208
+ )
209
+
210
+ with open(tcl_name, 'w', encoding='utf-8') as fo:
162
211
  if self.args['waves']:
163
212
  if self.args['waves-start']:
164
213
  print(f"run {self.args['waves-start']} ns", file=fo)
165
214
  print("add wave -r /*", file=fo)
166
215
  print("run -all", file=fo)
167
- if not self.args['gui']:
216
+ if self.run_in_batch_mode():
168
217
  print("quit", file=fo)
169
- command_list += ['-do', tcl_name ]
170
218
 
171
- # execute snapshot
172
- self.exec(self.args['work-dir'], command_list)
219
+ # Create vsim command
220
+ vsim_cmd = [os.path.join(self.sim_exe_base_path, 'vsim'), '-64']
173
221
 
174
- # Note that CommandSimQuesta doesn't yet follow the framework from CommandSim.do_it()
175
- # so we have to define all helper methods for pylint:
222
+ if not self.run_in_batch_mode():
223
+ vsim_cmd += ['-gui']
224
+ else:
225
+ vsim_cmd += ['-c']
176
226
 
177
- def compile(self) -> None:
178
- pass
227
+ if util.args['verbose']:
228
+ vsim_cmd += ['-verbose']
179
229
 
180
- def elaborate(self) -> None:
181
- pass
230
+ # Add simulation arguments
231
+ vsim_cmd += ['-do', tcl_name, 'opt_design']
182
232
 
183
- def simulate(self) -> None:
184
- pass
233
+ return [vsim_cmd]
185
234
 
186
- def get_compile_command_lists(self, **kwargs) -> list:
235
+ def get_post_simulate_command_lists(self, **kwargs) -> list:
187
236
  return []
188
237
 
189
- def get_elaborate_command_lists(self, **kwargs) -> list:
190
- return []
238
+ def run_in_batch_mode(self) -> bool:
239
+ '''Returns bool if we should run in batch mode (-c) from command line'''
240
+ if self.args['test-mode']:
241
+ return True
242
+ if self.args['gui']:
243
+ return False
244
+ return True
191
245
 
192
- def get_simulate_command_lists(self, **kwargs) -> list:
193
- return []
246
+ def artifacts_add(self, name: str, typ: str, description: str) -> None:
247
+ '''Override from Command.artifacts_add for better descriptions'''
248
+ _, leafname = os.path.split(name)
249
+ if leafname == 'vsim.log':
250
+ description = 'Questa simulation step (3/3) log from stdout/stderr'
251
+ elif leafname == 'vopt.log':
252
+ description = 'Questa elaboration step (2/3) log from stdout/stderr'
253
+ elif leafname == 'vlog.log':
254
+ description = 'Questa compile step (1/3) log from stdout/stderr'
194
255
 
195
- def get_post_simulate_command_lists(self, **kwargs) -> list:
196
- return []
256
+ super().artifacts_add(name=name, typ=typ, description=description)
257
+
258
+
259
+ class CommandFListQuesta(CommandFList, ToolQuesta):
260
+ '''CommandFListQuesta is a command handler for: eda flist --tool=questa'''
261
+
262
+ def __init__(self, config: dict):
263
+ CommandFList.__init__(self, config=config)
264
+ ToolQuesta.__init__(self, config=self.config)
197
265
 
198
266
 
199
267
  class CommandElabQuesta(CommandSimQuesta):
@@ -11,6 +11,7 @@ For: Questa Intel Starter FPGA Edition-64 vsim 20XX.X Simulator
11
11
  import os
12
12
 
13
13
  from opencos.tools.modelsim_ase import CommandSimModelsimAse
14
+ from opencos.tools.questa import CommandFListQuesta
14
15
 
15
16
 
16
17
  class CommandSimQuestaFse(CommandSimModelsimAse):
@@ -20,6 +21,7 @@ class CommandSimQuestaFse(CommandSimModelsimAse):
20
21
  '''
21
22
  _TOOL = 'questa_fse'
22
23
  _EXE = 'vsim'
24
+ use_vopt = True
23
25
 
24
26
  def __init__(self, config: dict):
25
27
  # this will setup with self._TOOL = modelsim_ase, which is not ideal so
@@ -57,3 +59,11 @@ class CommandElabQuestaFse(CommandSimQuestaFse):
57
59
  def __init__(self, config:dict):
58
60
  super().__init__(config)
59
61
  self.args['stop-after-elaborate'] = True
62
+
63
+
64
+ class CommandFListQuestaFse(CommandFListQuesta):
65
+ '''CommandFListQuestaFse is a command handler for: eda flist --tool=questa_fse'''
66
+
67
+ def __init__(self, config: dict):
68
+ CommandFListQuesta.__init__(self, config=config)
69
+ self._TOOL = 'questa_fse'
opencos/tools/riviera.py CHANGED
@@ -19,6 +19,7 @@ class ToolRiviera(ToolModelsimAse):
19
19
 
20
20
  _TOOL = 'riviera'
21
21
  _EXE = 'vsim'
22
+ use_vopt = False
22
23
 
23
24
  def get_versions(self) -> str:
24
25
  if self._VERSION:
@@ -135,24 +135,18 @@ class VerilatorSim(CommandSim, ToolVerilator):
135
135
  paths = ['obj_dir', 'logs']
136
136
  util.safe_mkdirs(base=self.args['work-dir'], new_dirs=paths)
137
137
 
138
- util.write_shell_command_file(dirpath=self.args['work-dir'], filename='compile_only.sh',
139
- command_lists=self.verilate_command_lists, line_breaks=True)
140
-
141
138
  util.write_shell_command_file(dirpath=self.args['work-dir'], filename='lint_only.sh',
142
139
  command_lists=self.lint_only_command_lists, line_breaks=True)
143
140
 
144
- util.write_shell_command_file(dirpath=self.args['work-dir'], filename='simulate_only.sh',
145
- command_lists = self.verilated_exec_command_lists +
146
- (self.verilated_post_exec_coverage_command_lists
147
- if self.args.get('coverage', True) else [])
148
- )
149
-
150
- util.write_shell_command_file(dirpath=self.args['work-dir'], filename='all.sh',
151
- command_lists = [
152
- ['./pre_compile_dep_shell_commands.sh'],
153
- ['./compile_only.sh'],
154
- ['./simulate_only.sh'],
155
- ])
141
+ sim_cmd_lists = self.verilated_exec_command_lists + \
142
+ (self.verilated_post_exec_coverage_command_lists
143
+ if self.args.get('coverage', True) else [])
144
+
145
+ self.write_sh_scripts_to_work_dir(
146
+ compile_lists=self.verilate_command_lists,
147
+ elaborate_lists=[],
148
+ simulate_lists=sim_cmd_lists
149
+ )
156
150
 
157
151
 
158
152
  def compile(self):
opencos/tools/vivado.py CHANGED
@@ -5,6 +5,7 @@ upload, flist, open, proj.
5
5
  '''
6
6
 
7
7
  # pylint: disable=R0801 # (setting similar, but not identical, self.defines key/value pairs)
8
+ # pylint: disable=too-many-lines
8
9
 
9
10
  import os
10
11
  import re
@@ -152,22 +153,11 @@ class CommandSimVivado(CommandSim, ToolVivado):
152
153
  self.xvlog_commands = self.get_compile_command_lists()
153
154
  self.xelab_commands = self.get_elaborate_command_lists()
154
155
  self.xsim_commands = self.get_simulate_command_lists()
155
-
156
- util.write_shell_command_file(dirpath=self.args['work-dir'], filename='compile.sh',
157
- command_lists=self.xvlog_commands)
158
- util.write_shell_command_file(dirpath=self.args['work-dir'], filename='elaborate.sh',
159
- command_lists=self.xelab_commands)
160
- util.write_shell_command_file(dirpath=self.args['work-dir'], filename='simulate.sh',
161
- command_lists=self.xsim_commands)
162
- util.write_shell_command_file(dirpath=self.args['work-dir'], filename='all.sh',
163
- command_lists = [
164
- ['./pre_compile_dep_shell_commands.sh'],
165
- ['./compile.sh'],
166
- ['./elaborate.sh'],
167
- ['./simulate.sh'],
168
- ])
169
-
170
- self.write_eda_config_and_args()
156
+ self.write_sh_scripts_to_work_dir(
157
+ compile_lists=self.xvlog_commands,
158
+ elaborate_lists=self.xelab_commands,
159
+ simulate_lists=self.xsim_commands
160
+ )
171
161
 
172
162
  def compile(self):
173
163
  if self.args['stop-before-compile']:
@@ -259,7 +249,7 @@ class CommandSimVivado(CommandSim, ToolVivado):
259
249
  print(f"run {self.args['waves-start']} ns", file=fo)
260
250
  print("log_wave -recursive *", file=fo)
261
251
  print("run -all", file=fo)
262
- if not self.args['gui']:
252
+ if not self.args['gui'] or self.args['test-mode']:
263
253
  print("exit", file=fo)
264
254
 
265
255
  sv_seed = str(self.args['seed'])
@@ -280,7 +270,7 @@ class CommandSimVivado(CommandSim, ToolVivado):
280
270
  if sys.platform == "win32":
281
271
  command_list[0] += ".bat"
282
272
  command_list += self.tool_config.get('simulate-args', 'snapshot --stats').split()
283
- if self.args['gui']:
273
+ if self.args['gui'] and not self.args['test-mode']:
284
274
  command_list += ['-gui']
285
275
  command_list += [
286
276
  '--tclbatch', tcl_name.replace('\\','\\\\'), # needed for windows paths
@@ -652,14 +642,15 @@ class CommandBuildVivado(CommandBuild, ToolVivado):
652
642
  self.write_eda_config_and_args()
653
643
 
654
644
  # create FLIST
655
- flist_file = os.path.join(self.args['work-dir'],'build.flist')
656
- util.debug(f"CommandBuildVivado: {self.args['top-path']=}")
645
+ flist_file = os.path.abspath(os.path.join(self.args['work-dir'],'build.flist'))
646
+ util.debug(f"CommandBuildVivado: top={self.args['top']} target={self.target}",
647
+ f"design={self.args['design']}")
657
648
 
658
649
  eda_path = eda_base.get_eda_exec('flist')
659
650
  command_list = [
660
651
  eda_path, 'flist',
652
+ '--no-default-log',
661
653
  '--tool=' + self.args['tool'],
662
- self.args['top-path'],
663
654
  '--force',
664
655
  '--out=' + flist_file,
665
656
  #'--no-emit-incdir',
@@ -677,6 +668,14 @@ class CommandBuildVivado(CommandBuild, ToolVivado):
677
668
  '--prefix-v=' + shlex.quote("add_files -norecurse "),
678
669
  '--prefix-vhd=' + shlex.quote("add_files -norecurse "),
679
670
  ]
671
+
672
+ # create an eda.flist_input.f that we'll pass to flist:
673
+ with open(os.path.join(self.args['work-dir'], 'eda.flist_input.f'),
674
+ 'w', encoding='utf-8') as f:
675
+ f.write('\n'.join(self.files_v + self.files_sv + self.files_vhd + ['']))
676
+
677
+ command_list.append('--input-file=eda.flist_input.f')
678
+
680
679
  for key,value in self.defines.items():
681
680
  if value is None:
682
681
  command_list += [ f"+define+{key}" ]
@@ -691,7 +690,10 @@ class CommandBuildVivado(CommandBuild, ToolVivado):
691
690
  util.write_shell_command_file(dirpath=self.args['work-dir'], filename='run_eda_flist.sh',
692
691
  command_lists=[command_list], line_breaks=True)
693
692
 
694
- self.exec(cwd, command_list, tee_fpath=command_list.tee_fpath)
693
+ ###self.exec(cwd, command_list, tee_fpath=command_list.tee_fpath)
694
+ # Run this from work-dir
695
+ self.exec(work_dir=self.args['work-dir'], command_list=command_list,
696
+ tee_fpath=command_list.tee_fpath)
695
697
 
696
698
  if self.args['job-name'] == "":
697
699
  self.args['job-name'] = self.args['design']
@@ -701,7 +703,7 @@ class CommandBuildVivado(CommandBuild, ToolVivado):
701
703
  command_list = [self.vivado_exe]
702
704
  command_list += [
703
705
  '-mode',
704
- 'gui' if self.args['gui'] else 'batch',
706
+ 'gui' if self.args['gui'] and not self.args['test-mode'] else 'batch',
705
707
  '-log', os.path.join(self.args['work-dir'], self.args['top'] + '.build.log')
706
708
  ]
707
709
  if not util.args['verbose']:
@@ -724,6 +726,11 @@ class CommandBuildVivado(CommandBuild, ToolVivado):
724
726
  util.write_shell_command_file(dirpath=self.args['work-dir'], filename='run_vivado.sh',
725
727
  command_lists=[command_list], line_breaks=True)
726
728
 
729
+ if self.args['stop-before-compile']:
730
+ util.info(f"--stop-before-compile set: scripts in : {self.args['work-dir']}")
731
+ return
732
+
733
+ # Run this from current working dir (not work-dir)
727
734
  self.exec(cwd, command_list, tee_fpath=command_list.tee_fpath)
728
735
  util.info(f"Build done, results are in: {self.args['work-dir']}")
729
736
 
opencos/util.py CHANGED
@@ -88,6 +88,7 @@ class ArtifactTypes(Enum):
88
88
  DOTF = 4
89
89
  TCL = 5
90
90
  SHELL = 6
91
+ BITSTREAM = 7
91
92
 
92
93
  class Artifacts:
93
94
  '''Class to hold file artifacts, for logs generated by EDA, or other artifcats created
@@ -224,16 +225,17 @@ class UtilLogger:
224
225
  # util's argparser: --no-default-log, --logfile=<name>, or --force-logfile=<name>
225
226
  default_log_enabled = False
226
227
  default_log_filepath = os.path.join('eda.work', 'eda.log')
228
+ default_log_disable_with_args = ['-h', '--help'] # common argparse help
227
229
  enable = True
228
230
 
229
231
  def clear(self) -> None:
230
- '''Resets internals'''
232
+ '''Resets some internals for logging, but preserve others'''
231
233
  self.file = None
232
234
  self.filepath = ''
233
235
  self.time_last = 0
234
236
 
235
237
  def stop(self) -> None:
236
- '''Closes open log, resets internals'''
238
+ '''Closes open log, resets some internals'''
237
239
  if self.file:
238
240
  self.write_timestamp(f'stop - {self.filepath}')
239
241
  info(f"Closing logfile: {self.filepath}")
@@ -364,9 +366,28 @@ def get_argparser() -> argparse.ArgumentParser:
364
366
  ' using $OC_ROOT/bin'))
365
367
  parser.add_argument('--artifacts-json', **bool_action_kwargs,
366
368
  help='Store a work-dir/artifacts.json file of known tool output files')
369
+ parser.add_argument('-f', '--input-file', default=[], action='append',
370
+ help=(
371
+ 'Input .f file to be expanded as eda'
372
+ ' args/defines/incdirs/files/targets'))
367
373
  return parser
368
374
 
369
375
 
376
+ def get_argparser_args_list(parser: object = None) -> list:
377
+ '''Returns list of all args, all items will include the -- prefix (--help, etc)'''
378
+ if not isinstance(parser, argparse.ArgumentParser):
379
+ return []
380
+ return list(vars(parser).get('_option_string_actions', {}).keys())
381
+
382
+
383
+ def get_argparsers_args_list(parsers: list) -> list:
384
+ '''Returns list of all args from list of parsers, all items have -- prefix (--help, etc)'''
385
+ _args_list = []
386
+ for parser in parsers:
387
+ _args_list.extend(get_argparser_args_list(parser))
388
+ return list(dict.fromkeys(_args_list)) # uniqify, preserve order
389
+
390
+
370
391
  def get_argparser_short_help(parser: object = None) -> str:
371
392
  '''Returns short help for our ArgumentParser'''
372
393
  if not parser:
@@ -393,28 +414,81 @@ def process_token(arg: list) -> bool:
393
414
  return False
394
415
 
395
416
 
396
- def process_tokens(tokens:list) -> (argparse.Namespace, list):
417
+ def read_tokens_from_dot_f(filepath: str) -> list:
418
+ '''Returns list of tokens from a .f file'''
419
+ debug(f"Opening -f / --input-file '{filepath}' for contents")
420
+ if not os.path.isfile(filepath):
421
+ error(f'-f (or --input-file): {filepath} does not exist',
422
+ error_code=status_constants.EDA_GENERAL_FILE_NOT_FOUND)
423
+ return []
424
+ tokens = []
425
+ with open(filepath, encoding='utf-8') as f:
426
+ for line in f:
427
+ tokens.extend(line.split())
428
+ return tokens
429
+
430
+ def process_debug_args(parsed: argparse.Namespace) -> None:
431
+ '''Sets global debug_level based on parsed args'''
432
+ global debug_level # pylint: disable=global-statement
433
+
434
+ if parsed.debug_level:
435
+ set_debug_level(parsed.debug_level)
436
+ elif parsed.debug:
437
+ set_debug_level(1)
438
+ elif parsed.debug is False:
439
+ # not None, explicitly set to False via --no-debug
440
+ debug_level = 0
441
+
442
+
443
+ def process_tokens(tokens: list) -> (argparse.Namespace, list):
397
444
  '''Processes tokens (unparsed args list) on util's ArgumentParser
398
445
 
399
446
  Returns tuple of (parsed Namespace, unparsed args list)
400
447
  '''
401
448
  global debug_level # pylint: disable=global-statement
449
+ debug_level = 0
402
450
 
403
- parser = get_argparser()
451
+ # Deal with --debug, --debug-level, and expand all parsed.input_file (list) tokens first,
452
+ # for now put dot-f file contents in front of of tokens, do this in a separate custom
453
+ # argparser.
454
+ bool_action_kwargs = get_argparse_bool_action_kwargs()
455
+ parser = argparse.ArgumentParser(
456
+ prog='opencos -f/--input-file', add_help=False, allow_abbrev=False
457
+ )
458
+ parser.add_argument('--debug', **bool_action_kwargs,
459
+ help='Display additional debug messaging level 1 or higher')
460
+ parser.add_argument('--debug-level', type=int, default=0,
461
+ help='Set debug level messaging (default: 0)')
462
+ parser.add_argument('-f', '--input-file', default=[], action='append',
463
+ help=(
464
+ 'Input .f file to be expanded as eda'
465
+ ' args/defines/incdirs/files/targets'))
404
466
  try:
405
467
  parsed, unparsed = parser.parse_known_args(tokens + [''])
468
+ tokens2 = list(filter(None, unparsed))
469
+ except argparse.ArgumentError:
470
+ error(f'util -f/--input-file, problem attempting to parse_known_args for: {tokens}')
471
+
472
+ process_debug_args(parsed=parsed)
473
+ debug(f'util.process_tokens: {parsed=} {unparsed=} from: {tokens}')
474
+
475
+ for filepath in parsed.input_file:
476
+ tokens2[0:0] = read_tokens_from_dot_f(filepath)
477
+
478
+ # Continue with all normal parsing beyond --debug and -f/--input-file,
479
+ # Note that we re-parse everything in case there was --debug or --debug-level in
480
+ # the .f file(s), or anything else that this argparser would pick up.
481
+ parser = get_argparser()
482
+ try:
483
+ parsed, unparsed = parser.parse_known_args(tokens2 + [''])
406
484
  unparsed = list(filter(None, unparsed))
407
485
  except argparse.ArgumentError:
408
- error(f'problem attempting to parse_known_args for {tokens=}')
486
+ error(f'util: problem attempting to parse_known_args for tokens={tokens2}')
409
487
 
410
- if parsed.debug_level:
411
- set_debug_level(parsed.debug_level)
412
- elif parsed.debug:
413
- set_debug_level(1)
414
- else:
415
- debug_level = 0
488
+ process_debug_args(parsed=parsed)
416
489
 
417
- debug(f'util.process_tokens: {parsed=} {unparsed=} from {tokens=}')
490
+ if parsed.input_file:
491
+ warning(f'Skipping nested -f/--input-file(s) {parsed.input_file}')
418
492
 
419
493
  # clear existing artifacts dicts (mostly for pytests repeatedly calling eda.main),
420
494
  # set artifacts.enabled based on args['artifacts-json']
@@ -426,7 +500,7 @@ def process_tokens(tokens:list) -> (argparse.Namespace, list):
426
500
  start_log(parsed.logfile, force=False)
427
501
  elif parsed.default_log and \
428
502
  not parsed.version and \
429
- not any(x in unparsed for x in ('help', '-h', '--help')) and \
503
+ not any(x in unparsed for x in global_log.default_log_disable_with_args) and \
430
504
  (parsed.force_logfile is None and parsed.logfile is None):
431
505
  # Use a forced logfile in the eda.work/eda.log:
432
506
  # avoid this if someone has --help arg not yet parsed.
@@ -434,9 +508,9 @@ def process_tokens(tokens:list) -> (argparse.Namespace, list):
434
508
  start_log(global_log.default_log_filepath, force=True)
435
509
 
436
510
  parsed_as_dict = vars(parsed)
437
- for key,value in parsed_as_dict.items():
511
+ for key, value in parsed_as_dict.items():
438
512
  key = key.replace('_', '-')
439
- if value is not None:
513
+ if key in args and value is not None:
440
514
  args[key] = value # only update with non-None values to our global 'args' dict
441
515
 
442
516
  return parsed, unparsed