opencos-eda 0.3.9__py3-none-any.whl → 0.3.11__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 (74) hide show
  1. opencos/commands/deps_help.py +89 -120
  2. opencos/commands/export.py +7 -2
  3. opencos/commands/multi.py +3 -3
  4. opencos/commands/sim.py +14 -16
  5. opencos/commands/synth.py +1 -2
  6. opencos/commands/upload.py +192 -4
  7. opencos/commands/waves.py +8 -8
  8. opencos/deps/deps_commands.py +6 -6
  9. opencos/deps/deps_file.py +82 -79
  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 +175 -41
  24. opencos/eda_base.py +180 -50
  25. opencos/eda_config.py +62 -16
  26. opencos/eda_config_defaults.yml +21 -4
  27. opencos/eda_deps_bash_completion.bash +37 -15
  28. opencos/files.py +26 -1
  29. opencos/tools/cocotb.py +5 -5
  30. opencos/tools/invio.py +2 -2
  31. opencos/tools/invio_yosys.py +2 -1
  32. opencos/tools/iverilog.py +3 -3
  33. opencos/tools/quartus.py +113 -115
  34. opencos/tools/questa_common.py +3 -4
  35. opencos/tools/riviera.py +3 -3
  36. opencos/tools/slang.py +11 -7
  37. opencos/tools/slang_yosys.py +1 -0
  38. opencos/tools/surelog.py +4 -3
  39. opencos/tools/verilator.py +5 -4
  40. opencos/tools/vivado.py +307 -176
  41. opencos/tools/yosys.py +4 -4
  42. opencos/util.py +6 -3
  43. opencos/utils/dict_helpers.py +31 -0
  44. opencos/utils/markup_helpers.py +2 -2
  45. opencos/utils/str_helpers.py +7 -0
  46. opencos/utils/subprocess_helpers.py +3 -3
  47. opencos/utils/vscode_helper.py +2 -2
  48. opencos/utils/vsim_helper.py +58 -22
  49. {opencos_eda-0.3.9.dist-info → opencos_eda-0.3.11.dist-info}/METADATA +1 -1
  50. opencos_eda-0.3.11.dist-info/RECORD +94 -0
  51. {opencos_eda-0.3.9.dist-info → opencos_eda-0.3.11.dist-info}/entry_points.txt +1 -0
  52. opencos/tests/__init__.py +0 -0
  53. opencos/tests/custom_config.yml +0 -13
  54. opencos/tests/deps_files/command_order/DEPS.yml +0 -44
  55. opencos/tests/deps_files/error_msgs/DEPS.yml +0 -55
  56. opencos/tests/deps_files/iverilog_test/DEPS.yml +0 -4
  57. opencos/tests/deps_files/no_deps_here/DEPS.yml +0 -0
  58. opencos/tests/deps_files/non_sv_reqs/DEPS.yml +0 -50
  59. opencos/tests/deps_files/tags_with_tools/DEPS.yml +0 -54
  60. opencos/tests/deps_files/test_err_fatal/DEPS.yml +0 -4
  61. opencos/tests/helpers.py +0 -354
  62. opencos/tests/test_build.py +0 -12
  63. opencos/tests/test_deps_helpers.py +0 -207
  64. opencos/tests/test_deps_schema.py +0 -30
  65. opencos/tests/test_eda.py +0 -921
  66. opencos/tests/test_eda_elab.py +0 -110
  67. opencos/tests/test_eda_synth.py +0 -150
  68. opencos/tests/test_oc_cli.py +0 -25
  69. opencos/tests/test_tools.py +0 -404
  70. opencos_eda-0.3.9.dist-info/RECORD +0 -99
  71. {opencos_eda-0.3.9.dist-info → opencos_eda-0.3.11.dist-info}/WHEEL +0 -0
  72. {opencos_eda-0.3.9.dist-info → opencos_eda-0.3.11.dist-info}/licenses/LICENSE +0 -0
  73. {opencos_eda-0.3.9.dist-info → opencos_eda-0.3.11.dist-info}/licenses/LICENSE.spdx +0 -0
  74. {opencos_eda-0.3.9.dist-info → opencos_eda-0.3.11.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}')")
@@ -120,7 +115,10 @@ class ToolVivado(Tool):
120
115
 
121
116
 
122
117
  class CommandSimVivado(CommandSim, ToolVivado):
123
- '''CommandSimVivado is a command handler for: eda sim --tool=vivado, uses xvlog, xelab, xsim'''
118
+ '''CommandSimVivado is a command handler for: eda sim --tool=vivado, uses xvlog, xelab, xsim
119
+
120
+ Note that we attempt to run a generated .tcl script within vivado, that will perform the
121
+ 3-step compile/elaborate/simulate steps'''
124
122
 
125
123
  def __init__(self, config: dict):
126
124
  CommandSim.__init__(self, config)
@@ -141,9 +139,19 @@ class CommandSimVivado(CommandSim, ToolVivado):
141
139
  })
142
140
 
143
141
  self.sim_libraries = self.tool_config.get('sim-libraries', [])
144
- self.xvlog_commands = []
145
- self.xelab_commands = []
146
- self.xsim_commands = []
142
+
143
+ self.vivado_tcl = {
144
+ 'xvlog': [],
145
+ 'xelab': [],
146
+ 'xsim': [],
147
+ 'exe_list': [],
148
+ 'check_logs': [],
149
+ }
150
+
151
+ # Note this is the syntax to have your Vivado tcl print to
152
+ # stdout, which we do for xvlog, xelab always, and even for xsim if in
153
+ # --gui or not GUI
154
+ self.tcl_exec_pipe = ['>@stdout', '2>@stderr']
147
155
 
148
156
 
149
157
  def set_tool_defines(self):
@@ -153,72 +161,144 @@ class CommandSimVivado(CommandSim, ToolVivado):
153
161
 
154
162
  def prepare_compile(self):
155
163
  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
164
+
165
+ # Don't use the return values, these will set values in self.vivado_tcl:
166
+ self.get_compile_command_lists()
167
+ self.get_elaborate_command_lists()
168
+ self.get_simulate_command_lists()
169
+
170
+ # We will always run a generated .tcl scirpt from vivado as:
171
+ # one command line call for:
172
+ # vivado -mode batch -source all_vivado.tcl
173
+ # So we have to create all_vivado.tcl based on our commands that we saved
174
+ # in self.vivado_tcl:
175
+ tclfname = os.path.abspath(
176
+ os.path.join(self.args['work-dir'], 'all_vivado.tcl')
163
177
  )
178
+ with open(tclfname, 'w', encoding='utf-8' ) as ftcl:
179
+ for line in self.vivado_tcl['xvlog']:
180
+ if self.args['stop-before-compile']:
181
+ ftcl.write('# ')
182
+ ftcl.write(line)
183
+ ftcl.write('\n\n')
184
+ for line in self.vivado_tcl['xelab']:
185
+ if any(self.args[x] for x in ('stop-before-compile', 'stop-after-compile')):
186
+ ftcl.write('# ')
187
+ ftcl.write(line)
188
+ ftcl.write('\n\n')
189
+ for line in self.vivado_tcl['xsim']:
190
+ if any(self.args[x] for x in ('stop-before-compile', 'stop-after-compile',
191
+ 'stop-after-elaborate')):
192
+ ftcl.write('# ')
193
+ ftcl.write(line)
194
+ ftcl.write('\n\n')
195
+
196
+ # We will alwyas run this in -mode batch, the xsim call in
197
+ # all_vivado.tcl will have -gui in it:
198
+ self.vivado_tcl['exe_list'] = [
199
+ self.vivado_exe, '-mode', 'batch', '-source', 'all_vivado.tcl'
200
+ ]
164
201
 
165
- def compile(self):
202
+ # Since we run this in a single vivado call, we don't get the automatic
203
+ # log checking from sim.run_commands_check_logs(..), so we need to remember
204
+ # which logs to check:
166
205
  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'
206
+ self.vivado_tcl['check_logs'] = []
207
+ elif self.args['stop-after-compile']:
208
+ self.vivado_tcl['check_logs'] = ['vivado.log', 'xvlog.log']
209
+ elif self.args['stop-after-elaborate']:
210
+ self.vivado_tcl['check_logs'] = ['vivado.log', 'xvlog.log', 'xelab.log']
211
+ else:
212
+ self.vivado_tcl['check_logs'] = ['vivado.log', 'xvlog.log', 'xelab.log', 'xsim.log']
213
+
214
+ self.write_sh_scripts_to_work_dir(
215
+ compile_lists=[],
216
+ elaborate_lists=[],
217
+ simulate_lists=[self.vivado_tcl['exe_list']],
218
+ compile_line_breaks=False
170
219
  )
171
220
 
221
+
222
+ def compile(self):
223
+ # handled in simulate()
224
+ return
225
+
172
226
  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
- )
227
+ # handled in simulate()
228
+ return
180
229
 
181
230
  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
231
+ # even though we run one command line call (vivado -mode batch -sourc TCLFILE)
232
+ # it still saves xvlog.log, xelab.log, xsim.log, and a (useless) vivado.log.
233
+ # we'll have to manually check the logs for errors if this was run in --gui:
185
234
  self.run_commands_check_logs(
186
- self.xsim_commands, check_logs=True, log_filename='xsim.log'
235
+ [self.vivado_tcl['exe_list']], check_logs=False, log_filename='vivado.log'
187
236
  )
188
237
 
238
+ for log_fname in self.vivado_tcl['check_logs']:
239
+ filename = os.path.join(self.args['work-dir'], log_fname)
240
+ self.check_logs_for_errors(filename=filename)
241
+
242
+
189
243
  def get_compile_command_lists(self, **kwargs) -> list:
244
+ '''Override from sim.CommandSim - which expects a return type list.
245
+
246
+ Since we run all in Vivado tcl, we always return an empty list,
247
+ and instead save in self.vivado_tcl['xvlog'].
248
+ '''
190
249
  self.set_tool_defines()
191
- ret = [] # list of (list of ['xvlog', arg0, arg1, ..])
192
250
 
193
251
  if self.args['add-glbl-v']:
194
252
  self._add_glbl_v()
195
253
 
196
254
  # compile verilog
197
255
  if self.files_v:
198
- ret.append(
199
- self.get_xvlog_commands(files_list=self.files_v, typ='v')
200
- )
256
+ self.add_xvlog_commands(files_list=self.files_v, typ='v')
201
257
 
202
258
  # compile systemverilog
203
259
  if self.files_sv:
204
- ret.append(
205
- self.get_xvlog_commands(files_list=self.files_sv, typ='sv')
206
- )
260
+ self.add_xvlog_commands(files_list=self.files_sv, typ='sv')
261
+
262
+ return []
263
+
264
+ def process_parameters_get_list(self, arg_prefix: str = '-G') -> list:
265
+ '''Override from sim.CommandSim
266
+
267
+ custom handler for parameters, instead of the one in sim.py
268
+ all will be passed as -generic_top "k=v", avoids all unknown shell or bash
269
+ behavior in windows.
270
+ '''
271
+
272
+ # ignore arg_prefix (use -generic_top):
273
+ ret = []
274
+ for k,v in self.parameters.items():
275
+ if not isinstance(v, (int, str)):
276
+ util.warning(
277
+ f'parameter {k} has value: {v}, parameters must be int/string types'
278
+ )
279
+ if isinstance(v, int):
280
+ ret.extend(['-generic_top', f'"{k}={v}"'])
281
+ else:
282
+ v = f'{v}'.strip('\n') # stringify
283
+ # Because we're writing to a .tcl file, \" will become ", and \\\" will become \"
284
+ # we want \" in the final file.
285
+ v = v.replace('"', '\\\"')
286
+ ret.extend(['-generic_top', f'"{k}={v}"'])
287
+ return ret
207
288
 
208
- return ret # list of lists
209
289
 
210
290
  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()
291
+ '''Override from sim.CommandSim - which expects a return type list.
292
+
293
+ Since we run all in Vivado tcl, we always return an empty list,
294
+ and instead save in self.vivado_tcl['xelab'].
295
+ '''
296
+
297
+ command_list = ['exec', 'xelab', self.args['top']]
298
+
299
+ command_list += self.tool_config.get(
300
+ 'elab-args', '-s snapshot -timescale 1ns/1ps --stats').split()
220
301
 
221
- # parameters
222
302
  command_list.extend(
223
303
  self.process_parameters_get_list(arg_prefix='-generic_top ')
224
304
  )
@@ -241,11 +321,33 @@ class CommandSimVivado(CommandSim, ToolVivado):
241
321
  command_list += ['-L', x]
242
322
  command_list += ['glbl']
243
323
  command_list += self.args['elab-args']
244
- return [command_list]
324
+
325
+ # For Windows compatibility, we have some issues with command/Powershell passing args
326
+ # so as a workaround we'll create a .tcl script xelab_vivado.tcl:
327
+ # exec {command_list} >@stdout 2>@stderr
328
+ # with the caveat that it needs POSIX style paths in the vivado tclsh,
329
+ # and we'll return the list to run it:
330
+ # and we'll save this to self.vivado_tcl['xelab']
331
+ command_list += self.tcl_exec_pipe
332
+ tclfname = os.path.abspath(
333
+ os.path.join(self.args['work-dir'], 'xelab_vivado.tcl')
334
+ )
335
+ line = ' '.join(command_list).replace('\n', ' ')
336
+ self.vivado_tcl['xelab'].append(line)
337
+ with open(tclfname, 'w', encoding='utf-8' ) as ftcl:
338
+ ftcl.write(line)
339
+ # return list that will go unused
340
+ return [ ]
341
+
245
342
 
246
343
  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']))
344
+ '''Override from sim.CommandSim - which expects a return type list.
345
+
346
+ Since we run all in Vivado tcl, we always return an empty list,
347
+ and instead save in self.vivado_tcl['xsim'].
348
+ '''
349
+ # create TCL for in-simulation
350
+ sim_tcl_name = os.path.abspath(os.path.join(self.args['work-dir'], self.args['tcl-file']))
249
351
 
250
352
  if self.args['waves']:
251
353
  util.artifacts.add_extension(
@@ -253,8 +355,7 @@ class CommandSimVivado(CommandSim, ToolVivado):
253
355
  typ='waveform', description='Vivado XSim Waveform WDB (Wave DataBase) file'
254
356
  )
255
357
 
256
-
257
- with open( tcl_name, 'w', encoding='utf-8' ) as fo:
358
+ with open( sim_tcl_name, 'w', encoding='utf-8' ) as fo:
258
359
  if self.args['waves']:
259
360
  if self.args['waves-start']:
260
361
  print(f"run {self.args['waves-start']} ns", file=fo)
@@ -268,7 +369,10 @@ class CommandSimVivado(CommandSim, ToolVivado):
268
369
  assert isinstance(self.args["sim-plusargs"], list), \
269
370
  f'{self.target=} {type(self.args["sim-plusargs"])=} but must be list'
270
371
 
271
- # xsim uses: --testplusarg foo=bar
372
+ # xsim uses: --testplusarg name=value
373
+ # Note - this was problematic in Windows Powershell for --testplusarg "name=value"
374
+ # --testplusarg "name" would work, but the only way for xsim to parse it was to
375
+ # run it in vivado's tclsh.
272
376
  xsim_plusargs_list = []
273
377
  for x in self.args['sim-plusargs']:
274
378
  xsim_plusargs_list.append('--testplusarg')
@@ -278,61 +382,104 @@ class CommandSimVivado(CommandSim, ToolVivado):
278
382
  xsim_plusargs_list.append(f'\"{x}\"')
279
383
 
280
384
  # execute snapshot
281
- command_list = [ os.path.join(self.vivado_base_path, 'xsim') ]
282
- if sys.platform == "win32":
283
- command_list[0] += ".bat"
385
+ command_list = ['exec', 'xsim']
284
386
  command_list += self.tool_config.get('simulate-args', 'snapshot --stats').split()
285
387
  if self.args['gui'] and not self.args['test-mode']:
286
388
  command_list += ['-gui']
287
389
  command_list += [
288
- '--tclbatch', tcl_name.replace('\\','\\\\'), # needed for windows paths
390
+ '--tclbatch', Path(sim_tcl_name).as_posix(), # running in tclsh needs POSIX
289
391
  "--sv_seed", sv_seed
290
392
  ]
291
393
  command_list += xsim_plusargs_list
292
394
  command_list += self.args['sim-args']
293
- return [command_list] # single command
395
+
396
+ # For Windows compatibility we have some issues with command/Powershell passing args
397
+ # so as a workaround we'll create a .tcl script xsim_vivado.tcl:
398
+ # exec {command_list} >@stdout 2>@stderr
399
+ # with the caveat that it needs POSIX style paths in the vivado tclsh,
400
+ # and we'll save this to self.vivado_tcl['xsim']
401
+ command_list += self.tcl_exec_pipe
402
+
403
+ tclfname = os.path.abspath(
404
+ os.path.join(self.args['work-dir'], 'xsim_vivado.tcl')
405
+ )
406
+ line = ' '.join(command_list).replace('\n', ' ')
407
+ self.vivado_tcl['xsim'].append(line)
408
+ with open(tclfname, 'w', encoding='utf-8' ) as ftcl:
409
+ ftcl.write(line)
410
+
411
+ # Need to return list, will go unused.
412
+ return []
294
413
 
295
414
  def get_post_simulate_command_lists(self, **kwargs) -> list:
296
415
  return []
297
416
 
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
417
+
418
+ def add_xvlog_commands( # pylint: disable=too-many-branches
419
+ self, files_list: list, typ: str = 'sv'
420
+ ) -> None:
421
+ '''Returns None, because we'll save the results in self.vivado_tcl['xvlog'].
422
+
423
+ Vivado still treats .v files like Verilog-2001, so we split xvlog into .v and .sv sections,
424
+ as two entries in self.vivado_tcl['xvlog'] if self.args['all-sv'] is False.
425
+ '''
426
+
427
+
428
+ if not files_list:
429
+ return
430
+
431
+ # For Windows compatibility we have some issues with command/Powershell passing args
432
+ # so as a workaround we'll create a .tcl script xvlog_sv_vivado.tcl:
433
+ # exec {command_list} >@stdout 2>@stderr
434
+ # with the caveat that it needs POSIX style paths in the vivado tclsh,
435
+ # and we'll save this to self.vivado_tcl['xvlog']
436
+ command_list = ['exec', 'xvlog']
437
+ if typ == 'sv':
438
+ command_list.append('-sv')
439
+ if self.args['uvm']:
440
+ command_list.extend(['-L', 'uvm'])
441
+ command_list += self.tool_config.get('compile-args', '').split()
442
+ if util.args['verbose']:
443
+ command_list += ['-v', '2']
444
+ for value in self.incdirs:
445
+ command_list.append('-i')
446
+ command_list.append(Path(value).as_posix())
447
+ for key, value in self.defines.items():
448
+ command_list.append('-d')
449
+ if value is not None:
450
+ # Because we're writing to a .tcl file, \" will become ", and \\\" will become \"
451
+ # we want \" in the final file. Parameters need to act the same way as defines:
452
+ value = f'{value}'.replace('"', '\\\"')
453
+
454
+ if value is None:
455
+ command_list.append(key)
456
+ else:
457
+ command_list.append(f"\"{key}={value}\"")
458
+
459
+ command_list += self.args['compile-args']
460
+
461
+ # Convert these to POSIX (even though we might be in Windows) b/c that's what Vivado
462
+ # tclsh needs
463
+ command_list += [Path(fpath).as_posix() for fpath in files_list]
464
+ command_list += self.tcl_exec_pipe
465
+
466
+ tclfname = os.path.abspath(
467
+ os.path.join(self.args['work-dir'], f'xvlog_{typ}_vivado.tcl')
468
+ )
469
+
470
+ # Make a one-liner from command line, save in self.vivado_tcl['xvlog']:
471
+ line = ' '.join(command_list).replace('\n', ' ')
472
+ self.vivado_tcl['xvlog'].append(line)
473
+ with open(tclfname, 'w', encoding='utf-8' ) as ftcl:
474
+ ftcl.write(line)
475
+ ftcl.write('\n')
331
476
 
332
477
 
333
478
  def _add_glbl_v(self):
334
479
  '''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')
480
+ glbl_v = self.vivado_base_path.replace(
481
+ 'bin', os.path.join('data', 'verilog', 'src', 'glbl.v')
482
+ )
336
483
  if any(x.endswith('glbl.v') for x in self.files_v):
337
484
  util.warning(f'--add-glbl-v: Not adding {glbl_v=} b/c glbl.v already in',
338
485
  f'{self.files_v=}')
@@ -349,6 +496,8 @@ class CommandSimVivado(CommandSim, ToolVivado):
349
496
  sim.log or compile.log
350
497
  '''
351
498
  _, leafname = os.path.split(name)
499
+ if leafname == 'vivado.log':
500
+ description = 'Vivado XSim log from stdout/stderr'
352
501
  if leafname == 'xsim.log':
353
502
  description = 'Vivado XSim simulation step (3/3) log from stdout/stderr'
354
503
  elif leafname == 'xelab.log':
@@ -429,7 +578,7 @@ class CommandSynthVivado(CommandSynth, ToolVivado):
429
578
  parameters = ' '.join(
430
579
  sim.parameters_dict_get_command_list(params=self.parameters, arg_prefix='-generic ')
431
580
  )
432
- incdirs = ' '.join([f'-include_dirs {x}' for x in self.incdirs])
581
+ incdirs = ' '.join([f'-include_dirs {Path(x).as_posix()}' for x in self.incdirs])
433
582
  flatten = ""
434
583
  if self.args['flatten-all']:
435
584
  flatten = "-flatten_hierarchy full"
@@ -437,12 +586,14 @@ class CommandSynthVivado(CommandSynth, ToolVivado):
437
586
  flatten = "-flatten_hierarchy none"
438
587
 
439
588
  tcl_lines = []
589
+ # Note - if we're in Windows, the vivado tcl that processes these lines expects POSIX
590
+ # paths, not command/Powershell paths. so we will use Path(str).as_posix()
440
591
  for f in self.files_v:
441
- tcl_lines.append(f"read_verilog {f}")
592
+ tcl_lines.append(f"read_verilog {Path(f).as_posix()}")
442
593
  for f in self.files_sv:
443
- tcl_lines.append(f"read_verilog -sv {f}")
594
+ tcl_lines.append(f"read_verilog -sv {Path(f).as_posix()}")
444
595
  for f in self.files_vhd:
445
- tcl_lines.append(f"add_file {f}")
596
+ tcl_lines.append(f"add_file {Path(f).as_posix()}")
446
597
 
447
598
  part = self.args['part']
448
599
  top = self.args['top']
@@ -465,11 +616,11 @@ class CommandSynthVivado(CommandSynth, ToolVivado):
465
616
  for _file in self.files_sdc:
466
617
  # NOTE - sdc files cannot (yet) be attached to other modules.
467
618
  tcl_lines += [
468
- f"add_files -fileset constraints_1 {_file} {v}",
619
+ f"add_files -fileset constraints_1 {Path(_file).as_posix()} {v}",
469
620
  ]
470
621
  if xdc_file:
471
622
  tcl_lines += [
472
- f"add_files -fileset constraints_1 {xdc_file} {v}",
623
+ f"add_files -fileset constraints_1 {Path(xdc_file).as_posix()} {v}",
473
624
  ]
474
625
  tcl_lines += [
475
626
  "# FIRST PASS -- auto_detect_xpm",
@@ -513,14 +664,14 @@ class CommandSynthVivado(CommandSynth, ToolVivado):
513
664
 
514
665
  if default_xdc:
515
666
  tcl_lines += [
516
- f"puts \"(Used default XDC: {xdc_file})\"",
667
+ f"puts \"(Used default XDC: {Path(xdc_file).as_posix()})\"",
517
668
  f"puts \"DEF CLOCK NS : [format %.3f {self.args['clock-ns']}]\"",
518
669
  f"puts \"DEF IDELAY NS : [format %.3f {self.args['idelay-ns']}]\"",
519
670
  f"puts \"DEF ODELAY NS : [format %.3f {self.args['odelay-ns']}]\"",
520
671
  ]
521
672
  else:
522
673
  tcl_lines += [
523
- f"puts \"(Used provided XDC: {xdc_file})\"",
674
+ f"puts \"(Used provided XDC: {Path(xdc_file).as_posix()})\"",
524
675
  ]
525
676
  tcl_lines += [
526
677
  "puts \"\"",
@@ -592,13 +743,13 @@ class CommandProjVivado(CommandProj, ToolVivado):
592
743
  tcl_file = os.path.abspath(os.path.join(self.args['work-dir'], self.args['tcl-file']))
593
744
  v = self.get_vivado_tcl_verbose_arg()
594
745
 
595
- incdirs = " ".join(self.incdirs)
746
+ incdirs = " ".join([Path(x).as_posix() for x in self.incdirs])
596
747
  defines = ""
597
748
  for key, value in self.defines.items():
598
749
  defines += (f"{key} " if value is None else f"{key}={value} ")
599
750
 
600
751
  tcl_lines = [
601
- f"create_project {self.args['top']}_proj {self.args['work-dir']} {v}"
752
+ f"create_project {self.args['top']}_proj {Path(self.args['work-dir']).as_posix()} {v}"
602
753
  ]
603
754
 
604
755
  if self.args['oc-vivado-tcl'] and oc_root:
@@ -630,7 +781,7 @@ class CommandProjVivado(CommandProj, ToolVivado):
630
781
  else:
631
782
  fileset = "sources_1"
632
783
  tcl_lines += [
633
- f"add_files -norecurse {f} -fileset [get_filesets {fileset}]"
784
+ f"add_files -norecurse {Path(f).as_posix()} -fileset [get_filesets {fileset}]"
634
785
  ]
635
786
 
636
787
  tcl_lines += [
@@ -776,16 +927,16 @@ class CommandFListVivado(CommandFList, ToolVivado):
776
927
  class CommandUploadVivado(CommandUpload, ToolVivado):
777
928
  '''CommandUploadVivado is a command handler for: eda upload --tool=vivado'''
778
929
 
930
+ SUPPORTED_BIT_EXT = ['.bit']
931
+
779
932
  def __init__(self, config: dict):
780
933
  CommandUpload.__init__(self, config)
781
934
  ToolVivado.__init__(self, config=self.config)
782
935
  # add args specific to this tool
783
936
  self.args.update({
784
937
  'gui': False,
785
- 'bitfile': "",
786
938
  'list-usbs': False,
787
939
  'list-devices': False,
788
- 'list-bitfiles': False,
789
940
  'usb': -1,
790
941
  'device': -1,
791
942
  'host': "localhost",
@@ -795,82 +946,51 @@ class CommandUploadVivado(CommandUpload, ToolVivado):
795
946
  'log-file': "eda_upload.log",
796
947
  'test-mode': False,
797
948
  })
798
- # TODO(drew): Add self.args_help.update({...})
799
949
 
800
- # TODO(drew): This method needs to be refactored (with tests somehow) to clean
801
- # up pylint waivers
950
+ # TODO(drew): Complete self.args_help.update({...})
951
+ self.args_help.update({
952
+ 'bitfile': 'BIT file to upload (auto-detected if not specified)',
953
+ 'list-bitfiles': 'List available BIT files',
954
+ })
955
+
956
+
802
957
  def do_it(self): # pylint: disable=too-many-locals,too-many-branches,too-many-statements
958
+ '''
959
+ Note this is called directly by opencos.commands.CommandUpload, based
960
+ on which bitfile(s) were found, or if --tool=vivado was set
961
+
962
+ We do not need to handle --list-bitfiles, was handled by CommandUpload.
963
+ '''
964
+
803
965
  # add defines for this job
804
966
  self.set_tool_defines()
805
967
  self.write_eda_config_and_args()
806
968
 
807
969
  bitfile = None
808
- targets = []
809
970
  if self.args['bitfile']:
810
971
  if os.path.isfile(self.args['bitfile']):
811
972
  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
973
 
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...")
974
+ # self.bitfiles was already set by CommandUpload.process_tokens()
975
+ if len(self.bitfiles) == 1:
976
+ bitfile = self.bitfiles[0]
863
977
 
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
978
 
872
- if matched and bitfile is None:
873
- bitfile = matched[0]
979
+ # Auto-discover bitfile logic (for when we have no bitfile, and we
980
+ # weren't called just to listdevice/listusb)
981
+ if not bitfile and not self.args['list-devices'] and not self.args['list-usbs']:
982
+
983
+ # CommandUpload already displayed them, and exited on --list-bitfiles.
984
+ if len(self.bitfiles) > 1:
985
+ util.warning("Too many matches to continue without adding search terms,",
986
+ "upload not performed.")
987
+ if not self.args['test-mode']:
988
+ self.error('Upload not performed, multiple bit files found, please add',
989
+ 'search terms...')
990
+ return
991
+
992
+ self.error("Failed to find a matching bitfile")
993
+ return
874
994
 
875
995
  # ── Generate TCL script ───────────────────────────────────────────────────
876
996
  script_file = Path(self.args['tcl-file'])
@@ -945,10 +1065,11 @@ class CommandUploadVivado(CommandUpload, ToolVivado):
945
1065
  w('refresh_hw_device -update_hw_probes false -quiet $hw_device\n')
946
1066
 
947
1067
  if bitfile is not None:
948
- w('set_property PROGRAM.FILE {' + bitfile + '} $hw_device\n')
1068
+ w('set_property PROGRAM.FILE {' + Path(bitfile).as_posix() + '} $hw_device\n')
949
1069
  w('program_hw_devices [current_hw_device]\n')
950
1070
 
951
1071
  w('close_hw_target\n')
1072
+ w('quit\n')
952
1073
 
953
1074
  except OSError as exc:
954
1075
  util.error(f"Cannot create {script_file}: {exc}")
@@ -971,11 +1092,21 @@ class CommandUploadVivado(CommandUpload, ToolVivado):
971
1092
  ]
972
1093
  if not util.args['verbose']:
973
1094
  command_list.append('-notrace')
974
- self.exec(Path(util.getcwd()), command_list)
1095
+ _, stdout, _ = self.exec(Path(util.getcwd()), command_list)
1096
+
1097
+ # Do some log scraping
1098
+ for line in stdout.split('\n'):
1099
+ if line.startswith('WARNING:'):
1100
+ self.tool_warning_count += 1
1101
+ elif line.startswith('ERROR:') or line.startswith('[ERROR]'):
1102
+ self.tool_error_count += 1
975
1103
 
976
1104
  if not self.args['keep']:
977
1105
  os.unlink(self.args['tcl-file'])
978
1106
 
1107
+ self.report_tool_warn_error_counts()
1108
+ self.report_pass_fail()
1109
+
979
1110
  util.info("Upload done")
980
1111
 
981
1112