opencos-eda 0.3.10__py3-none-any.whl → 0.3.12__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 (59) hide show
  1. opencos/commands/deps_help.py +63 -113
  2. opencos/commands/export.py +7 -2
  3. opencos/commands/multi.py +4 -4
  4. opencos/commands/sim.py +14 -15
  5. opencos/commands/sweep.py +1 -1
  6. opencos/commands/synth.py +1 -2
  7. opencos/commands/upload.py +192 -4
  8. opencos/commands/waves.py +52 -8
  9. opencos/deps/deps_commands.py +6 -6
  10. opencos/deps/deps_processor.py +129 -50
  11. opencos/docs/Architecture.md +45 -0
  12. opencos/docs/ConnectingApps.md +29 -0
  13. opencos/docs/DEPS.md +199 -0
  14. opencos/docs/Debug.md +77 -0
  15. opencos/docs/DirectoryStructure.md +22 -0
  16. opencos/docs/Installation.md +117 -0
  17. opencos/docs/OcVivadoTcl.md +63 -0
  18. opencos/docs/OpenQuestions.md +7 -0
  19. opencos/docs/README.md +13 -0
  20. opencos/docs/RtlCodingStyle.md +54 -0
  21. opencos/docs/eda.md +147 -0
  22. opencos/docs/oc_cli.md +135 -0
  23. opencos/eda.py +358 -155
  24. opencos/eda_base.py +187 -60
  25. opencos/eda_config.py +70 -35
  26. opencos/eda_config_defaults.yml +310 -186
  27. opencos/eda_config_reduced.yml +19 -39
  28. opencos/eda_tool_helper.py +190 -21
  29. opencos/files.py +26 -1
  30. opencos/tools/cocotb.py +11 -23
  31. opencos/tools/invio.py +2 -2
  32. opencos/tools/invio_yosys.py +2 -1
  33. opencos/tools/iverilog.py +3 -3
  34. opencos/tools/modelsim_ase.py +1 -1
  35. opencos/tools/quartus.py +172 -137
  36. opencos/tools/questa_common.py +50 -9
  37. opencos/tools/riviera.py +5 -4
  38. opencos/tools/slang.py +14 -10
  39. opencos/tools/slang_yosys.py +1 -0
  40. opencos/tools/surelog.py +7 -6
  41. opencos/tools/verilator.py +9 -7
  42. opencos/tools/vivado.py +315 -180
  43. opencos/tools/yosys.py +5 -5
  44. opencos/util.py +6 -3
  45. opencos/utils/dict_helpers.py +31 -0
  46. opencos/utils/markup_helpers.py +2 -2
  47. opencos/utils/str_helpers.py +38 -0
  48. opencos/utils/subprocess_helpers.py +3 -3
  49. opencos/utils/vscode_helper.py +2 -2
  50. opencos/utils/vsim_helper.py +16 -5
  51. {opencos_eda-0.3.10.dist-info → opencos_eda-0.3.12.dist-info}/METADATA +1 -1
  52. opencos_eda-0.3.12.dist-info/RECORD +93 -0
  53. opencos/eda_config_max_verilator_waivers.yml +0 -39
  54. opencos_eda-0.3.10.dist-info/RECORD +0 -81
  55. {opencos_eda-0.3.10.dist-info → opencos_eda-0.3.12.dist-info}/WHEEL +0 -0
  56. {opencos_eda-0.3.10.dist-info → opencos_eda-0.3.12.dist-info}/entry_points.txt +0 -0
  57. {opencos_eda-0.3.10.dist-info → opencos_eda-0.3.12.dist-info}/licenses/LICENSE +0 -0
  58. {opencos_eda-0.3.10.dist-info → opencos_eda-0.3.12.dist-info}/licenses/LICENSE.spdx +0 -0
  59. {opencos_eda-0.3.10.dist-info → opencos_eda-0.3.12.dist-info}/top_level.txt +0 -0
opencos/tools/vivado.py CHANGED
@@ -10,19 +10,14 @@ upload, flist, open, proj.
10
10
  import os
11
11
  import re
12
12
  import shlex
13
- import shutil
14
- import sys
15
13
 
16
- from datetime import datetime
17
14
  from pathlib import Path
18
15
 
19
16
  from opencos import util, eda_base
20
- from opencos.eda_base import Tool
21
-
22
- from opencos.commands import CommandSim, CommandSynth, CommandProj, CommandBuild, \
17
+ from opencos.commands import sim, CommandSim, CommandSynth, CommandProj, CommandBuild, \
23
18
  CommandFList, CommandUpload, CommandOpen
24
-
25
- from opencos.commands import sim
19
+ from opencos.eda_base import Tool
20
+ from opencos.files import safe_shutil_which
26
21
 
27
22
 
28
23
  class ToolVivado(Tool):
@@ -52,7 +47,7 @@ class ToolVivado(Tool):
52
47
  if self._VERSION:
53
48
  return self._VERSION
54
49
 
55
- path = shutil.which(self._EXE)
50
+ path = safe_shutil_which(self._EXE)
56
51
  if not path:
57
52
  self.error("Vivado not in path, need to install or add to $PATH",
58
53
  f"(looked for '{self._EXE}')")
@@ -61,10 +56,14 @@ class ToolVivado(Tool):
61
56
  self.vivado_base_path, _ = os.path.split(path)
62
57
 
63
58
  xilinx_vivado = os.environ.get('XILINX_VIVADO')
64
- if not xilinx_vivado or \
59
+
60
+ # Since get_versions() doesn't have a quiet mode, and it's called as part
61
+ # of opencos.eda.init_config(..), let's only show an information message
62
+ # if XILINX_VIVADO env is set and it doesn't match the Path/bin/vivado[.bat|.exe]
63
+ if xilinx_vivado and \
65
64
  os.path.abspath(os.path.join(xilinx_vivado, 'bin')) != \
66
65
  os.path.abspath(os.path.dirname(self.vivado_exe)):
67
- util.info("environment for XILINX_VIVADO is not set or doesn't match the vivado path:",
66
+ util.info("environment for XILINX_VIVADO is set, and doesn't match the vivado path:",
68
67
  f"XILINX_VIVADO={xilinx_vivado} EXE PATH={self.vivado_exe}")
69
68
 
70
69
  # Get version based on install path name. Calling vivado -verison is too slow.
@@ -74,7 +73,7 @@ class ToolVivado(Tool):
74
73
  version = m.group(1) + '.' + m.group(2)
75
74
  self._VERSION = version
76
75
  else:
77
- self.error("Vivado path doesn't specificy version, expecting (dddd.d)")
76
+ util.warning("Vivado path doesn't specificy version, expecting (dddd.d)")
78
77
 
79
78
  if version:
80
79
  numbers_list = version.split('.')
@@ -82,7 +81,7 @@ class ToolVivado(Tool):
82
81
  self.vivado_release = int(numbers_list[1])
83
82
  self.vivado_version = float(numbers_list[0] + '.' + numbers_list[1])
84
83
  else:
85
- self.error(f"Vivado version not found, vivado path = {self.vivado_exe}")
84
+ util.warning(f"Vivado version not found, vivado path = {self.vivado_exe}")
86
85
  return self._VERSION
87
86
 
88
87
 
@@ -120,7 +119,10 @@ class ToolVivado(Tool):
120
119
 
121
120
 
122
121
  class CommandSimVivado(CommandSim, ToolVivado):
123
- '''CommandSimVivado is a command handler for: eda sim --tool=vivado, uses xvlog, xelab, xsim'''
122
+ '''CommandSimVivado is a command handler for: eda sim --tool=vivado, uses xvlog, xelab, xsim
123
+
124
+ Note that we attempt to run a generated .tcl script within vivado, that will perform the
125
+ 3-step compile/elaborate/simulate steps'''
124
126
 
125
127
  def __init__(self, config: dict):
126
128
  CommandSim.__init__(self, config)
@@ -141,9 +143,19 @@ class CommandSimVivado(CommandSim, ToolVivado):
141
143
  })
142
144
 
143
145
  self.sim_libraries = self.tool_config.get('sim-libraries', [])
144
- self.xvlog_commands = []
145
- self.xelab_commands = []
146
- self.xsim_commands = []
146
+
147
+ self.vivado_tcl = {
148
+ 'xvlog': [],
149
+ 'xelab': [],
150
+ 'xsim': [],
151
+ 'exe_list': [],
152
+ 'check_logs': [],
153
+ }
154
+
155
+ # Note this is the syntax to have your Vivado tcl print to
156
+ # stdout, which we do for xvlog, xelab always, and even for xsim if in
157
+ # --gui or not GUI
158
+ self.tcl_exec_pipe = ['>@stdout', '2>@stderr']
147
159
 
148
160
 
149
161
  def set_tool_defines(self):
@@ -153,72 +165,144 @@ class CommandSimVivado(CommandSim, ToolVivado):
153
165
 
154
166
  def prepare_compile(self):
155
167
  self.set_tool_defines()
156
- self.xvlog_commands = self.get_compile_command_lists()
157
- self.xelab_commands = self.get_elaborate_command_lists()
158
- self.xsim_commands = self.get_simulate_command_lists()
159
- self.write_sh_scripts_to_work_dir(
160
- compile_lists=self.xvlog_commands,
161
- elaborate_lists=self.xelab_commands,
162
- simulate_lists=self.xsim_commands
168
+
169
+ # Don't use the return values, these will set values in self.vivado_tcl:
170
+ self.get_compile_command_lists()
171
+ self.get_elaborate_command_lists()
172
+ self.get_simulate_command_lists()
173
+
174
+ # We will always run a generated .tcl scirpt from vivado as:
175
+ # one command line call for:
176
+ # vivado -mode batch -source all_vivado.tcl
177
+ # So we have to create all_vivado.tcl based on our commands that we saved
178
+ # in self.vivado_tcl:
179
+ tclfname = os.path.abspath(
180
+ os.path.join(self.args['work-dir'], 'all_vivado.tcl')
163
181
  )
182
+ with open(tclfname, 'w', encoding='utf-8' ) as ftcl:
183
+ for line in self.vivado_tcl['xvlog']:
184
+ if self.args['stop-before-compile']:
185
+ ftcl.write('# ')
186
+ ftcl.write(line)
187
+ ftcl.write('\n\n')
188
+ for line in self.vivado_tcl['xelab']:
189
+ if any(self.args[x] for x in ('stop-before-compile', 'stop-after-compile')):
190
+ ftcl.write('# ')
191
+ ftcl.write(line)
192
+ ftcl.write('\n\n')
193
+ for line in self.vivado_tcl['xsim']:
194
+ if any(self.args[x] for x in ('stop-before-compile', 'stop-after-compile',
195
+ 'stop-after-elaborate')):
196
+ ftcl.write('# ')
197
+ ftcl.write(line)
198
+ ftcl.write('\n\n')
199
+
200
+ # We will alwyas run this in -mode batch, the xsim call in
201
+ # all_vivado.tcl will have -gui in it:
202
+ self.vivado_tcl['exe_list'] = [
203
+ self.vivado_exe, '-mode', 'batch', '-source', 'all_vivado.tcl'
204
+ ]
164
205
 
165
- def compile(self):
206
+ # Since we run this in a single vivado call, we don't get the automatic
207
+ # log checking from sim.run_commands_check_logs(..), so we need to remember
208
+ # which logs to check:
166
209
  if self.args['stop-before-compile']:
167
- return
168
- self.run_commands_check_logs(
169
- self.xvlog_commands, check_logs=True, log_filename='xvlog.log'
210
+ self.vivado_tcl['check_logs'] = []
211
+ elif self.args['stop-after-compile']:
212
+ self.vivado_tcl['check_logs'] = ['vivado.log', 'xvlog.log']
213
+ elif self.args['stop-after-elaborate']:
214
+ self.vivado_tcl['check_logs'] = ['vivado.log', 'xvlog.log', 'xelab.log']
215
+ else:
216
+ self.vivado_tcl['check_logs'] = ['vivado.log', 'xvlog.log', 'xelab.log', 'xsim.log']
217
+
218
+ self.write_sh_scripts_to_work_dir(
219
+ compile_lists=[],
220
+ elaborate_lists=[],
221
+ simulate_lists=[self.vivado_tcl['exe_list']],
222
+ compile_line_breaks=False
170
223
  )
171
224
 
225
+
226
+ def compile(self):
227
+ # handled in simulate()
228
+ return
229
+
172
230
  def elaborate(self):
173
- if self.args['stop-before-compile'] or self.args['stop-after-compile']:
174
- return
175
- # In this flow, we need to run compile + elaborate separately (unlike ModelsimASE)
176
- self.run_commands_check_logs(
177
- self.xelab_commands, check_logs=True, log_filename='xelab.log',
178
- must_strings=['Built simulation snapshot snapshot']
179
- )
231
+ # handled in simulate()
232
+ return
180
233
 
181
234
  def simulate(self):
182
- if self.args['stop-before-compile'] or self.args['stop-after-compile'] or \
183
- self.args['stop-after-elaborate']:
184
- return
235
+ # even though we run one command line call (vivado -mode batch -sourc TCLFILE)
236
+ # it still saves xvlog.log, xelab.log, xsim.log, and a (useless) vivado.log.
237
+ # we'll have to manually check the logs for errors if this was run in --gui:
185
238
  self.run_commands_check_logs(
186
- self.xsim_commands, check_logs=True, log_filename='xsim.log'
239
+ [self.vivado_tcl['exe_list']], check_logs=False, log_filename='vivado.log'
187
240
  )
188
241
 
242
+ for log_fname in self.vivado_tcl['check_logs']:
243
+ filename = os.path.join(self.args['work-dir'], log_fname)
244
+ self.check_logs_for_errors(filename=filename)
245
+
246
+
189
247
  def get_compile_command_lists(self, **kwargs) -> list:
248
+ '''Override from sim.CommandSim - which expects a return type list.
249
+
250
+ Since we run all in Vivado tcl, we always return an empty list,
251
+ and instead save in self.vivado_tcl['xvlog'].
252
+ '''
190
253
  self.set_tool_defines()
191
- ret = [] # list of (list of ['xvlog', arg0, arg1, ..])
192
254
 
193
255
  if self.args['add-glbl-v']:
194
256
  self._add_glbl_v()
195
257
 
196
258
  # compile verilog
197
259
  if self.files_v:
198
- ret.append(
199
- self.get_xvlog_commands(files_list=self.files_v, typ='v')
200
- )
260
+ self.add_xvlog_commands(files_list=self.files_v, typ='v')
201
261
 
202
262
  # compile systemverilog
203
263
  if self.files_sv:
204
- ret.append(
205
- self.get_xvlog_commands(files_list=self.files_sv, typ='sv')
206
- )
264
+ self.add_xvlog_commands(files_list=self.files_sv, typ='sv')
265
+
266
+ return []
267
+
268
+ def process_parameters_get_list(self, arg_prefix: str = '-G') -> list:
269
+ '''Override from sim.CommandSim
270
+
271
+ custom handler for parameters, instead of the one in sim.py
272
+ all will be passed as -generic_top "k=v", avoids all unknown shell or bash
273
+ behavior in windows.
274
+ '''
275
+
276
+ # ignore arg_prefix (use -generic_top):
277
+ ret = []
278
+ for k,v in self.parameters.items():
279
+ if not isinstance(v, (int, str)):
280
+ util.warning(
281
+ f'parameter {k} has value: {v}, parameters must be int/string types'
282
+ )
283
+ if isinstance(v, int):
284
+ ret.extend(['-generic_top', f'"{k}={v}"'])
285
+ else:
286
+ v = f'{v}'.strip('\n') # stringify
287
+ # Because we're writing to a .tcl file, \" will become ", and \\\" will become \"
288
+ # we want \" in the final file.
289
+ v = v.replace('"', '\\\"')
290
+ ret.extend(['-generic_top', f'"{k}={v}"'])
291
+ return ret
207
292
 
208
- return ret # list of lists
209
293
 
210
294
  def get_elaborate_command_lists(self, **kwargs) -> list:
211
- # elab into snapshot
212
- command_list = [
213
- os.path.join(self.vivado_base_path, 'xelab'),
214
- self.args['top']
215
- ]
216
- if sys.platform == "win32":
217
- command_list[0] += ".bat"
218
- command_list += self.tool_config.get('elab-args',
219
- '-s snapshot -timescale 1ns/1ps --stats').split()
295
+ '''Override from sim.CommandSim - which expects a return type list.
296
+
297
+ Since we run all in Vivado tcl, we always return an empty list,
298
+ and instead save in self.vivado_tcl['xelab'].
299
+ '''
300
+
301
+ command_list = ['exec', 'xelab', self.args['top']]
302
+
303
+ command_list += self.tool_config.get(
304
+ 'elab-args', '-s snapshot -timescale 1ns/1ps --stats').split()
220
305
 
221
- # parameters
222
306
  command_list.extend(
223
307
  self.process_parameters_get_list(arg_prefix='-generic_top ')
224
308
  )
@@ -241,11 +325,33 @@ class CommandSimVivado(CommandSim, ToolVivado):
241
325
  command_list += ['-L', x]
242
326
  command_list += ['glbl']
243
327
  command_list += self.args['elab-args']
244
- return [command_list]
328
+
329
+ # For Windows compatibility, we have some issues with command/Powershell passing args
330
+ # so as a workaround we'll create a .tcl script xelab_vivado.tcl:
331
+ # exec {command_list} >@stdout 2>@stderr
332
+ # with the caveat that it needs POSIX style paths in the vivado tclsh,
333
+ # and we'll return the list to run it:
334
+ # and we'll save this to self.vivado_tcl['xelab']
335
+ command_list += self.tcl_exec_pipe
336
+ tclfname = os.path.abspath(
337
+ os.path.join(self.args['work-dir'], 'xelab_vivado.tcl')
338
+ )
339
+ line = ' '.join(command_list).replace('\n', ' ')
340
+ self.vivado_tcl['xelab'].append(line)
341
+ with open(tclfname, 'w', encoding='utf-8' ) as ftcl:
342
+ ftcl.write(line)
343
+ # return list that will go unused
344
+ return [ ]
345
+
245
346
 
246
347
  def get_simulate_command_lists(self, **kwargs) -> list:
247
- # create TCL
248
- tcl_name = os.path.abspath(os.path.join(self.args['work-dir'], self.args['tcl-file']))
348
+ '''Override from sim.CommandSim - which expects a return type list.
349
+
350
+ Since we run all in Vivado tcl, we always return an empty list,
351
+ and instead save in self.vivado_tcl['xsim'].
352
+ '''
353
+ # create TCL for in-simulation
354
+ sim_tcl_name = os.path.abspath(os.path.join(self.args['work-dir'], self.args['tcl-file']))
249
355
 
250
356
  if self.args['waves']:
251
357
  util.artifacts.add_extension(
@@ -253,8 +359,7 @@ class CommandSimVivado(CommandSim, ToolVivado):
253
359
  typ='waveform', description='Vivado XSim Waveform WDB (Wave DataBase) file'
254
360
  )
255
361
 
256
-
257
- with open( tcl_name, 'w', encoding='utf-8' ) as fo:
362
+ with open( sim_tcl_name, 'w', encoding='utf-8' ) as fo:
258
363
  if self.args['waves']:
259
364
  if self.args['waves-start']:
260
365
  print(f"run {self.args['waves-start']} ns", file=fo)
@@ -268,7 +373,10 @@ class CommandSimVivado(CommandSim, ToolVivado):
268
373
  assert isinstance(self.args["sim-plusargs"], list), \
269
374
  f'{self.target=} {type(self.args["sim-plusargs"])=} but must be list'
270
375
 
271
- # xsim uses: --testplusarg foo=bar
376
+ # xsim uses: --testplusarg name=value
377
+ # Note - this was problematic in Windows Powershell for --testplusarg "name=value"
378
+ # --testplusarg "name" would work, but the only way for xsim to parse it was to
379
+ # run it in vivado's tclsh.
272
380
  xsim_plusargs_list = []
273
381
  for x in self.args['sim-plusargs']:
274
382
  xsim_plusargs_list.append('--testplusarg')
@@ -278,61 +386,104 @@ class CommandSimVivado(CommandSim, ToolVivado):
278
386
  xsim_plusargs_list.append(f'\"{x}\"')
279
387
 
280
388
  # execute snapshot
281
- command_list = [ os.path.join(self.vivado_base_path, 'xsim') ]
282
- if sys.platform == "win32":
283
- command_list[0] += ".bat"
389
+ command_list = ['exec', 'xsim']
284
390
  command_list += self.tool_config.get('simulate-args', 'snapshot --stats').split()
285
391
  if self.args['gui'] and not self.args['test-mode']:
286
392
  command_list += ['-gui']
287
393
  command_list += [
288
- '--tclbatch', tcl_name.replace('\\','\\\\'), # needed for windows paths
394
+ '--tclbatch', Path(sim_tcl_name).as_posix(), # running in tclsh needs POSIX
289
395
  "--sv_seed", sv_seed
290
396
  ]
291
397
  command_list += xsim_plusargs_list
292
398
  command_list += self.args['sim-args']
293
- return [command_list] # single command
399
+
400
+ # For Windows compatibility we have some issues with command/Powershell passing args
401
+ # so as a workaround we'll create a .tcl script xsim_vivado.tcl:
402
+ # exec {command_list} >@stdout 2>@stderr
403
+ # with the caveat that it needs POSIX style paths in the vivado tclsh,
404
+ # and we'll save this to self.vivado_tcl['xsim']
405
+ command_list += self.tcl_exec_pipe
406
+
407
+ tclfname = os.path.abspath(
408
+ os.path.join(self.args['work-dir'], 'xsim_vivado.tcl')
409
+ )
410
+ line = ' '.join(command_list).replace('\n', ' ')
411
+ self.vivado_tcl['xsim'].append(line)
412
+ with open(tclfname, 'w', encoding='utf-8' ) as ftcl:
413
+ ftcl.write(line)
414
+
415
+ # Need to return list, will go unused.
416
+ return []
294
417
 
295
418
  def get_post_simulate_command_lists(self, **kwargs) -> list:
296
419
  return []
297
420
 
298
- def get_xvlog_commands(self, files_list: list, typ: str = 'sv') -> list:
299
- '''Returns list. Vivado still treats .v files like Verilog-2001, so we split
300
-
301
- xvlog into .v and .sv sections'''
302
- command_list = []
303
-
304
- if files_list:
305
- command_list = [ os.path.join(self.vivado_base_path, 'xvlog') ]
306
- if sys.platform == "win32":
307
- command_list[0] += ".bat"
308
- if typ == 'sv':
309
- command_list.append('-sv')
310
- if self.args['uvm']:
311
- command_list.extend(['-L', 'uvm'])
312
- command_list += self.tool_config.get('compile-args', '').split()
313
- if util.args['verbose']:
314
- command_list += ['-v', '2']
315
- for value in self.incdirs:
316
- command_list.append('-i')
317
- command_list.append(value)
318
- for key, value in self.defines.items():
319
- command_list.append('-d')
320
- if value is None:
321
- command_list.append(key)
322
- elif sys.platform == "win32":
323
- command_list.append(f"\"{key}={value}\"") # only thing that seems to work
324
- elif "\'" in value:
325
- command_list.append(f"\"{key}={value}\"")
326
- else:
327
- command_list.append(f"{key}={value}")
328
- command_list += self.args['compile-args']
329
- command_list += files_list
330
- return command_list
421
+
422
+ def add_xvlog_commands( # pylint: disable=too-many-branches
423
+ self, files_list: list, typ: str = 'sv'
424
+ ) -> None:
425
+ '''Returns None, because we'll save the results in self.vivado_tcl['xvlog'].
426
+
427
+ Vivado still treats .v files like Verilog-2001, so we split xvlog into .v and .sv sections,
428
+ as two entries in self.vivado_tcl['xvlog'] if self.args['all-sv'] is False.
429
+ '''
430
+
431
+
432
+ if not files_list:
433
+ return
434
+
435
+ # For Windows compatibility we have some issues with command/Powershell passing args
436
+ # so as a workaround we'll create a .tcl script xvlog_sv_vivado.tcl:
437
+ # exec {command_list} >@stdout 2>@stderr
438
+ # with the caveat that it needs POSIX style paths in the vivado tclsh,
439
+ # and we'll save this to self.vivado_tcl['xvlog']
440
+ command_list = ['exec', 'xvlog']
441
+ if typ == 'sv':
442
+ command_list.append('-sv')
443
+ if self.args['uvm']:
444
+ command_list.extend(['-L', 'uvm'])
445
+ command_list += self.tool_config.get('compile-args', '').split()
446
+ if util.args['verbose']:
447
+ command_list += ['-v', '2']
448
+ for value in self.incdirs:
449
+ command_list.append('-i')
450
+ command_list.append(Path(value).as_posix())
451
+ for key, value in self.defines.items():
452
+ command_list.append('-d')
453
+ if value is not None:
454
+ # Because we're writing to a .tcl file, \" will become ", and \\\" will become \"
455
+ # we want \" in the final file. Parameters need to act the same way as defines:
456
+ value = f'{value}'.replace('"', '\\\"')
457
+
458
+ if value is None:
459
+ command_list.append(key)
460
+ else:
461
+ command_list.append(f"\"{key}={value}\"")
462
+
463
+ command_list += self.args['compile-args']
464
+
465
+ # Convert these to POSIX (even though we might be in Windows) b/c that's what Vivado
466
+ # tclsh needs
467
+ command_list += [Path(fpath).as_posix() for fpath in files_list]
468
+ command_list += self.tcl_exec_pipe
469
+
470
+ tclfname = os.path.abspath(
471
+ os.path.join(self.args['work-dir'], f'xvlog_{typ}_vivado.tcl')
472
+ )
473
+
474
+ # Make a one-liner from command line, save in self.vivado_tcl['xvlog']:
475
+ line = ' '.join(command_list).replace('\n', ' ')
476
+ self.vivado_tcl['xvlog'].append(line)
477
+ with open(tclfname, 'w', encoding='utf-8' ) as ftcl:
478
+ ftcl.write(line)
479
+ ftcl.write('\n')
331
480
 
332
481
 
333
482
  def _add_glbl_v(self):
334
483
  '''Adds glbl.v from Vivado's install path to self.files_v'''
335
- glbl_v = self.vivado_base_path.replace('bin', 'data/verilog/src/glbl.v')
484
+ glbl_v = self.vivado_base_path.replace(
485
+ 'bin', os.path.join('data', 'verilog', 'src', 'glbl.v')
486
+ )
336
487
  if any(x.endswith('glbl.v') for x in self.files_v):
337
488
  util.warning(f'--add-glbl-v: Not adding {glbl_v=} b/c glbl.v already in',
338
489
  f'{self.files_v=}')
@@ -349,6 +500,8 @@ class CommandSimVivado(CommandSim, ToolVivado):
349
500
  sim.log or compile.log
350
501
  '''
351
502
  _, leafname = os.path.split(name)
503
+ if leafname == 'vivado.log':
504
+ description = 'Vivado XSim log from stdout/stderr'
352
505
  if leafname == 'xsim.log':
353
506
  description = 'Vivado XSim simulation step (3/3) log from stdout/stderr'
354
507
  elif leafname == 'xelab.log':
@@ -429,7 +582,7 @@ class CommandSynthVivado(CommandSynth, ToolVivado):
429
582
  parameters = ' '.join(
430
583
  sim.parameters_dict_get_command_list(params=self.parameters, arg_prefix='-generic ')
431
584
  )
432
- incdirs = ' '.join([f'-include_dirs {x}' for x in self.incdirs])
585
+ incdirs = ' '.join([f'-include_dirs {Path(x).as_posix()}' for x in self.incdirs])
433
586
  flatten = ""
434
587
  if self.args['flatten-all']:
435
588
  flatten = "-flatten_hierarchy full"
@@ -437,12 +590,14 @@ class CommandSynthVivado(CommandSynth, ToolVivado):
437
590
  flatten = "-flatten_hierarchy none"
438
591
 
439
592
  tcl_lines = []
593
+ # Note - if we're in Windows, the vivado tcl that processes these lines expects POSIX
594
+ # paths, not command/Powershell paths. so we will use Path(str).as_posix()
440
595
  for f in self.files_v:
441
- tcl_lines.append(f"read_verilog {f}")
596
+ tcl_lines.append(f"read_verilog {Path(f).as_posix()}")
442
597
  for f in self.files_sv:
443
- tcl_lines.append(f"read_verilog -sv {f}")
598
+ tcl_lines.append(f"read_verilog -sv {Path(f).as_posix()}")
444
599
  for f in self.files_vhd:
445
- tcl_lines.append(f"add_file {f}")
600
+ tcl_lines.append(f"add_file {Path(f).as_posix()}")
446
601
 
447
602
  part = self.args['part']
448
603
  top = self.args['top']
@@ -465,11 +620,11 @@ class CommandSynthVivado(CommandSynth, ToolVivado):
465
620
  for _file in self.files_sdc:
466
621
  # NOTE - sdc files cannot (yet) be attached to other modules.
467
622
  tcl_lines += [
468
- f"add_files -fileset constraints_1 {_file} {v}",
623
+ f"add_files -fileset constraints_1 {Path(_file).as_posix()} {v}",
469
624
  ]
470
625
  if xdc_file:
471
626
  tcl_lines += [
472
- f"add_files -fileset constraints_1 {xdc_file} {v}",
627
+ f"add_files -fileset constraints_1 {Path(xdc_file).as_posix()} {v}",
473
628
  ]
474
629
  tcl_lines += [
475
630
  "# FIRST PASS -- auto_detect_xpm",
@@ -513,14 +668,14 @@ class CommandSynthVivado(CommandSynth, ToolVivado):
513
668
 
514
669
  if default_xdc:
515
670
  tcl_lines += [
516
- f"puts \"(Used default XDC: {xdc_file})\"",
671
+ f"puts \"(Used default XDC: {Path(xdc_file).as_posix()})\"",
517
672
  f"puts \"DEF CLOCK NS : [format %.3f {self.args['clock-ns']}]\"",
518
673
  f"puts \"DEF IDELAY NS : [format %.3f {self.args['idelay-ns']}]\"",
519
674
  f"puts \"DEF ODELAY NS : [format %.3f {self.args['odelay-ns']}]\"",
520
675
  ]
521
676
  else:
522
677
  tcl_lines += [
523
- f"puts \"(Used provided XDC: {xdc_file})\"",
678
+ f"puts \"(Used provided XDC: {Path(xdc_file).as_posix()})\"",
524
679
  ]
525
680
  tcl_lines += [
526
681
  "puts \"\"",
@@ -592,13 +747,13 @@ class CommandProjVivado(CommandProj, ToolVivado):
592
747
  tcl_file = os.path.abspath(os.path.join(self.args['work-dir'], self.args['tcl-file']))
593
748
  v = self.get_vivado_tcl_verbose_arg()
594
749
 
595
- incdirs = " ".join(self.incdirs)
750
+ incdirs = " ".join([Path(x).as_posix() for x in self.incdirs])
596
751
  defines = ""
597
752
  for key, value in self.defines.items():
598
753
  defines += (f"{key} " if value is None else f"{key}={value} ")
599
754
 
600
755
  tcl_lines = [
601
- f"create_project {self.args['top']}_proj {self.args['work-dir']} {v}"
756
+ f"create_project {self.args['top']}_proj {Path(self.args['work-dir']).as_posix()} {v}"
602
757
  ]
603
758
 
604
759
  if self.args['oc-vivado-tcl'] and oc_root:
@@ -630,7 +785,7 @@ class CommandProjVivado(CommandProj, ToolVivado):
630
785
  else:
631
786
  fileset = "sources_1"
632
787
  tcl_lines += [
633
- f"add_files -norecurse {f} -fileset [get_filesets {fileset}]"
788
+ f"add_files -norecurse {Path(f).as_posix()} -fileset [get_filesets {fileset}]"
634
789
  ]
635
790
 
636
791
  tcl_lines += [
@@ -776,16 +931,16 @@ class CommandFListVivado(CommandFList, ToolVivado):
776
931
  class CommandUploadVivado(CommandUpload, ToolVivado):
777
932
  '''CommandUploadVivado is a command handler for: eda upload --tool=vivado'''
778
933
 
934
+ SUPPORTED_BIT_EXT = ['.bit']
935
+
779
936
  def __init__(self, config: dict):
780
937
  CommandUpload.__init__(self, config)
781
938
  ToolVivado.__init__(self, config=self.config)
782
939
  # add args specific to this tool
783
940
  self.args.update({
784
941
  'gui': False,
785
- 'bitfile': "",
786
942
  'list-usbs': False,
787
943
  'list-devices': False,
788
- 'list-bitfiles': False,
789
944
  'usb': -1,
790
945
  'device': -1,
791
946
  'host': "localhost",
@@ -795,82 +950,51 @@ class CommandUploadVivado(CommandUpload, ToolVivado):
795
950
  'log-file': "eda_upload.log",
796
951
  'test-mode': False,
797
952
  })
798
- # TODO(drew): Add self.args_help.update({...})
799
953
 
800
- # TODO(drew): This method needs to be refactored (with tests somehow) to clean
801
- # up pylint waivers
954
+ # TODO(drew): Complete self.args_help.update({...})
955
+ self.args_help.update({
956
+ 'bitfile': 'BIT file to upload (auto-detected if not specified)',
957
+ 'list-bitfiles': 'List available BIT files',
958
+ })
959
+
960
+
802
961
  def do_it(self): # pylint: disable=too-many-locals,too-many-branches,too-many-statements
962
+ '''
963
+ Note this is called directly by opencos.commands.CommandUpload, based
964
+ on which bitfile(s) were found, or if --tool=vivado was set
965
+
966
+ We do not need to handle --list-bitfiles, was handled by CommandUpload.
967
+ '''
968
+
803
969
  # add defines for this job
804
970
  self.set_tool_defines()
805
971
  self.write_eda_config_and_args()
806
972
 
807
973
  bitfile = None
808
- targets = []
809
974
  if self.args['bitfile']:
810
975
  if os.path.isfile(self.args['bitfile']):
811
976
  bitfile = self.args['bitfile']
812
- else:
813
- # Not a file, treat as search pattern
814
- targets = [self.args['bitfile']]
815
-
816
- # TODO(drew): It might be nice to use positional args (supported by
817
- # eda_base.Command) here, and in multi.py, so we don't accidentally
818
- # grab errant --arg style strings as potential filenames or target patterns
819
- for f in self.unparsed_args:
820
- # self.unparsed_args are leftovers from Command.process_tokens(..)
821
- if os.path.isfile(f):
822
- if bitfile is None:
823
- bitfile = f
824
- else:
825
- util.error("Too many bitfiles provided")
826
- if not self.args['test-mode']:
827
- sys.exit(0)
828
- else:
829
- # Not a file, treat as search pattern
830
- targets.append(f)
831
-
832
- # Auto-discover bitfile logic (for when we have no bitfile, and we
833
- # weren't called just to listdevice/listusb)
834
- if self.args['list-bitfiles'] or \
835
- (not bitfile and not self.args['list-devices'] and not self.args['list-usbs']):
836
- bitfiles: list[Path] = []
837
977
 
838
- util.debug(f"Looking for bitfiles in {os.path.abspath('.')=}")
839
- for root, _, files in os.walk("."):
840
- for f in files:
841
- if f.endswith(".bit"):
842
- fullpath = os.path.abspath(Path(root) / f)
843
- if fullpath not in bitfiles:
844
- bitfiles.append(fullpath)
845
-
846
- matched: list[Path] = []
847
- for cand in bitfiles:
848
- util.debug(f"Looking for {cand=} in {targets=}")
849
- passing = all(re.search(t, str(cand)) for t in targets)
850
- if passing:
851
- matched.append(cand)
852
- mod_time_string = datetime.fromtimestamp(
853
- os.path.getmtime(cand)).strftime('%Y-%m-%d %H:%M:%S')
854
- util.info(f"Found matching bitfile: {mod_time_string} : {cand}")
855
-
856
- if len(matched) > 1:
857
- if self.args['list-bitfiles']:
858
- util.info("Too many matches to continue without adding search terms. Done.")
859
- if not self.args['test-mode']:
860
- sys.exit(0)
861
- else:
862
- util.error("Too many matches, please add search terms...")
978
+ # self.bitfiles was already set by CommandUpload.process_tokens()
979
+ if len(self.bitfiles) == 1:
980
+ bitfile = self.bitfiles[0]
863
981
 
864
- if not matched and bitfile is None:
865
- if self.args['list-bitfiles']:
866
- util.info("No matching bitfiles found, done.")
867
- if not self.args['test-mode']:
868
- sys.exit(0)
869
- else:
870
- util.error("Failed to find a matching bitfile")
871
982
 
872
- if matched and bitfile is None:
873
- bitfile = matched[0]
983
+ # Auto-discover bitfile logic (for when we have no bitfile, and we
984
+ # weren't called just to listdevice/listusb)
985
+ if not bitfile and not self.args['list-devices'] and not self.args['list-usbs']:
986
+
987
+ # CommandUpload already displayed them, and exited on --list-bitfiles.
988
+ if len(self.bitfiles) > 1:
989
+ util.warning("Too many matches to continue without adding search terms,",
990
+ "upload not performed.")
991
+ if not self.args['test-mode']:
992
+ self.error('Upload not performed, multiple bit files found, please add',
993
+ 'search terms...')
994
+ return
995
+
996
+ self.error("Failed to find a matching bitfile")
997
+ return
874
998
 
875
999
  # ── Generate TCL script ───────────────────────────────────────────────────
876
1000
  script_file = Path(self.args['tcl-file'])
@@ -945,10 +1069,11 @@ class CommandUploadVivado(CommandUpload, ToolVivado):
945
1069
  w('refresh_hw_device -update_hw_probes false -quiet $hw_device\n')
946
1070
 
947
1071
  if bitfile is not None:
948
- w('set_property PROGRAM.FILE {' + bitfile + '} $hw_device\n')
1072
+ w('set_property PROGRAM.FILE {' + Path(bitfile).as_posix() + '} $hw_device\n')
949
1073
  w('program_hw_devices [current_hw_device]\n')
950
1074
 
951
1075
  w('close_hw_target\n')
1076
+ w('quit\n')
952
1077
 
953
1078
  except OSError as exc:
954
1079
  util.error(f"Cannot create {script_file}: {exc}")
@@ -971,11 +1096,21 @@ class CommandUploadVivado(CommandUpload, ToolVivado):
971
1096
  ]
972
1097
  if not util.args['verbose']:
973
1098
  command_list.append('-notrace')
974
- self.exec(Path(util.getcwd()), command_list)
1099
+ _, stdout, _ = self.exec(Path(util.getcwd()), command_list)
1100
+
1101
+ # Do some log scraping
1102
+ for line in stdout.split('\n'):
1103
+ if line.startswith('WARNING:'):
1104
+ self.tool_warning_count += 1
1105
+ elif line.startswith('ERROR:') or line.startswith('[ERROR]'):
1106
+ self.tool_error_count += 1
975
1107
 
976
1108
  if not self.args['keep']:
977
1109
  os.unlink(self.args['tcl-file'])
978
1110
 
1111
+ self.report_tool_warn_error_counts()
1112
+ self.report_pass_fail()
1113
+
979
1114
  util.info("Upload done")
980
1115
 
981
1116