opencos-eda 0.2.43__py3-none-any.whl → 0.2.45__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 (36) hide show
  1. opencos/commands/__init__.py +4 -0
  2. opencos/commands/export.py +0 -1
  3. opencos/commands/flist.py +4 -1
  4. opencos/commands/lec.py +103 -0
  5. opencos/commands/shell.py +202 -0
  6. opencos/commands/sim.py +1 -1
  7. opencos/deps_helpers.py +2 -0
  8. opencos/eda.py +6 -3
  9. opencos/eda_base.py +12 -4
  10. opencos/eda_config.py +1 -1
  11. opencos/eda_config_defaults.yml +22 -7
  12. opencos/eda_extract_targets.py +13 -3
  13. opencos/export_helper.py +6 -4
  14. opencos/files.py +1 -0
  15. opencos/tests/helpers.py +3 -2
  16. opencos/tests/test_deps_schema.py +3 -1
  17. opencos/tests/test_eda.py +19 -9
  18. opencos/tests/test_eda_synth.py +63 -2
  19. opencos/tools/invio_yosys.py +7 -7
  20. opencos/tools/iverilog.py +1 -1
  21. opencos/tools/riviera.py +1 -1
  22. opencos/tools/slang.py +1 -1
  23. opencos/tools/slang_yosys.py +29 -38
  24. opencos/tools/surelog.py +1 -1
  25. opencos/tools/tabbycad_yosys.py +1 -1
  26. opencos/tools/verilator.py +1 -1
  27. opencos/tools/vivado.py +30 -11
  28. opencos/tools/yosys.py +465 -25
  29. opencos/util.py +19 -12
  30. {opencos_eda-0.2.43.dist-info → opencos_eda-0.2.45.dist-info}/METADATA +1 -1
  31. {opencos_eda-0.2.43.dist-info → opencos_eda-0.2.45.dist-info}/RECORD +36 -34
  32. {opencos_eda-0.2.43.dist-info → opencos_eda-0.2.45.dist-info}/WHEEL +0 -0
  33. {opencos_eda-0.2.43.dist-info → opencos_eda-0.2.45.dist-info}/entry_points.txt +0 -0
  34. {opencos_eda-0.2.43.dist-info → opencos_eda-0.2.45.dist-info}/licenses/LICENSE +0 -0
  35. {opencos_eda-0.2.43.dist-info → opencos_eda-0.2.45.dist-info}/licenses/LICENSE.spdx +0 -0
  36. {opencos_eda-0.2.43.dist-info → opencos_eda-0.2.45.dist-info}/top_level.txt +0 -0
opencos/tools/yosys.py CHANGED
@@ -10,8 +10,31 @@ import shutil
10
10
  import subprocess
11
11
 
12
12
  from opencos import util
13
- from opencos.eda_base import Tool
14
- from opencos.commands import CommandSynth
13
+ from opencos.eda_base import Tool, get_eda_exec
14
+ from opencos.commands import CommandSynth, CommandLec
15
+
16
+
17
+ def get_commands_to_run_scriptfiles(
18
+ script_fnames_list: list, yosys_exe: str
19
+ ) -> [util.ShellCommandList]:
20
+ '''Checks file existence and returns list of commands to run a
21
+
22
+ list of yoysys script(s)'''
23
+
24
+ if script_fnames_list:
25
+ return []
26
+
27
+ yosys_cmdlists = []
28
+ for i,fpath in enumerate(script_fnames_list):
29
+ if not os.path.isfile(fpath):
30
+ util.error(f'yosys-scriptfile={fpath} file does not exist')
31
+ cmdlist = util.ShellCommandList(
32
+ [yosys_exe, '--scriptfile', os.path.abspath(fpath)],
33
+ tee_fpath = f'yosys_scriptfile.{i}.log'
34
+ )
35
+ yosys_cmdlists.append(cmdlist)
36
+ return yosys_cmdlists
37
+
15
38
 
16
39
  class ToolYosys(Tool):
17
40
  '''Parent class for ToolTabbyCadYosys, ToolInvioYosys, ToolSlangYosys'''
@@ -46,7 +69,7 @@ class ToolYosys(Tool):
46
69
  [self.sta_exe, '-version'], capture_output=True, check=False
47
70
  )
48
71
  util.debug(f'{self.yosys_exe} {sta_version_ret=}')
49
- sta_ver = sta_version_ret.stdout.decode('utf-8').split()[0]
72
+ sta_ver = sta_version_ret.stdout.decode('utf-8', errors='replace').split()[0]
50
73
  if sta_ver:
51
74
  self.sta_version = sta_ver
52
75
 
@@ -56,7 +79,7 @@ class ToolYosys(Tool):
56
79
  util.debug(f'{self.yosys_exe} {version_ret=}')
57
80
 
58
81
  # Yosys 0.48 (git sha1 aaa534749, clang++ 14.0.0-1ubuntu1.1 -fPIC -O3)
59
- words = version_ret.stdout.decode('utf-8').split()
82
+ words = version_ret.stdout.decode('utf-8', errors='replace').split()
60
83
 
61
84
  if len(words) < 2:
62
85
  self.error(f'{self.yosys_exe} --version: returned unexpected str {version_ret=}')
@@ -80,7 +103,7 @@ class CommonSynthYosys(CommandSynth, ToolYosys):
80
103
  for child classes: CommandSynthInvioYosys and tabbycad_yosys.CommandSynthTabbyCadYosys
81
104
  '''
82
105
 
83
- def __init__(self, config:dict):
106
+ def __init__(self, config: dict):
84
107
  CommandSynth.__init__(self, config=config)
85
108
  ToolYosys.__init__(self, config=self.config)
86
109
 
@@ -91,18 +114,41 @@ class CommonSynthYosys(CommandSynth, ToolYosys):
91
114
  'yosys-synth': 'synth', # synth_xilinx, synth_altera, etc (see: yosys help)
92
115
  'yosys-pre-synth': ['prep', 'proc'], # command run in yosys prior to yosys-synth.
93
116
  'yosys-blackbox': [], # list of modules that yosys will blackbox.
117
+ 'yosys-scriptfile': [],
118
+ 'sta-scriptfile': [],
94
119
  })
95
120
  self.args_help.update({
96
- 'sta': 'After running Yosys, run "sta" with --liberty-file.' \
97
- + ' sta can be installed via: https://github.com/The-OpenROAD-Project/OpenSTA',
98
- 'sdc-file': '.sdc file to use with --sta, if not present will use auto constraints',
99
- 'liberty-file': 'Single liberty file for synthesis and sta,' \
100
- + ' for example: github/OpenSTA/examples/nangate45_slow.lib.gz',
121
+ 'sta': (
122
+ 'After running Yosys, run "sta" with --liberty-file.'
123
+ ' sta can be installed via: https://github.com/The-OpenROAD-Project/OpenSTA'
124
+ ),
125
+ 'sdc-file': (
126
+ '.sdc file to use with --sta, if not present will use auto constraints.'
127
+ ' Note you can have .sdc files in "deps" of DEPS.yml targets.'
128
+ ),
129
+ 'liberty-file': (
130
+ 'Single liberty file for synthesis and sta,'
131
+ ' for example: github/OpenSTA/examples/nangate45_slow.lib.gz'
132
+ ),
101
133
  'yosys-synth': 'The synth command provided to Yosys, see: yosys help.',
102
- 'yosys-pre-synth': 'Yosys commands performed prior to running "synth"' \
103
- + ' (or eda arg value for --yosys-synth)',
104
- 'yosys-blackbox': 'List of modules that yosys will blackbox, likely will need these' \
105
- + ' in Verilog-2001 for yosys to read outside of slang and synth',
134
+ 'yosys-pre-synth': (
135
+ 'Yosys commands performed prior to running "synth"'
136
+ ' (or eda arg value for --yosys-synth)'
137
+ ),
138
+ 'yosys-blackbox': (
139
+ 'List of modules that yosys will blackbox, likely will need these'
140
+ ' in Verilog-2001 for yosys to read outside of slang and synth'
141
+ ),
142
+ 'yosys-scriptfile': (
143
+ 'Instead of using a built-in flow from eda, use your own scripts that are called'
144
+ ' via: yosys --scriptfile <this-arg>. You can set multiple args for multiple'
145
+ ' scriptfile (appends)'
146
+ ),
147
+ 'sta-scriptfile': (
148
+ 'Instead of using a built-in flow from eda, use your own script that is called'
149
+ ' via: sta -no_init -exit <this-arg>. You can set multiple args for multiple'
150
+ ' scriptfile (appends)'
151
+ ),
106
152
  })
107
153
 
108
154
  self.yosys_out_dir = ''
@@ -127,16 +173,139 @@ class CommonSynthYosys(CommandSynth, ToolYosys):
127
173
  self.do_export()
128
174
  return
129
175
 
130
- self.write_and_run_yosys_f_files()
176
+ if self.args['yosys-scriptfile']:
177
+ yosys_cmdlists = self.get_commands_user_yosys_scriptfile()
178
+ sta_cmdlists = self.create_sta_f() # works for --sta w/out BYO scripts.
179
+
180
+ # We create a run_yosys.sh wrapping these scripts, but we do not run this one.
181
+ util.write_shell_command_file(
182
+ dirpath=self.args['work-dir'],
183
+ filename='run_yosys.sh',
184
+ command_lists=(yosys_cmdlists + sta_cmdlists)
185
+ )
186
+
187
+ # actually run it.
188
+ for x in yosys_cmdlists + sta_cmdlists:
189
+ if x:
190
+ self.exec(work_dir=self.full_work_dir, command_list=x,
191
+ tee_fpath=x.tee_fpath)
192
+
193
+ else:
194
+ self.write_and_run_yosys_f_files()
195
+
196
+
197
+ def get_commands_user_yosys_scriptfile(self) -> [util.ShellCommandList]:
198
+ '''Checks file existence and returns list of commands to run a
199
+
200
+ list of yoysys script(s)'''
201
+ cmd_lists = get_commands_to_run_scriptfiles(
202
+ script_fnames_list=self.args['yosys-scriptfile'],
203
+ yosys_exe=self.yosys_exe
204
+ )
205
+
206
+ if not cmd_lists:
207
+ util.error('Could not generate yosys commands for scripts',
208
+ f'{self.args["yosys-scriptfile"]}')
131
209
 
132
- def write_and_run_yosys_f_files(self, **kwargs) -> None:
133
- '''Derived classes must define, to run remainder of do_it() steps'''
134
- raise NotImplementedError
210
+ return cmd_lists
211
+
212
+
213
+ def get_commands_user_sta_scriptfile(self) -> [util.ShellCommandList]:
214
+ '''Checks file existence and returns list of commands'''
215
+ if not self.args['sta-scriptfile']:
216
+ return []
217
+
218
+ ret_list = []
219
+ for i,fpath in enumerate(self.args['sta-scriptfile']):
220
+ if not os.path.isfile(fpath):
221
+ self.error(f'sta-scriptfile={fpath} file does not exist')
222
+ cmdlist = util.ShellCommandList(
223
+ [self.sta_exe, '-no_init', '-exit', os.path.abspath(fpath)],
224
+ tee_fpath = f'sta_scriptfile.{i}.log'
225
+ )
226
+ ret_list.append(cmdlist)
227
+ return ret_list
228
+
229
+
230
+ def write_and_run_yosys_f_files(self) -> None:
231
+ '''Derived classes may override, to run remainder of do_it() steps
232
+
233
+ These built-ins do not use slang or another SV preprocessing step.
234
+ 1. Creates and runs: yosys.synth.f
235
+ -- does blackboxing and synth steps
236
+ 4. Creates a wrapper for human debug and reuse: yosys.f
237
+ '''
238
+
239
+ # Note - big assumption here that "module myname" is contained in myname.[v|sv]:
240
+ # we use both synth-blackbox and yosys-blackbox lists to blackbox modules in the
241
+ # yosys step (not in the slang step)
242
+ self.blackbox_list = self.args.get('yosys-blackbox', [])
243
+ self.blackbox_list += self.args.get('synth-blackbox', [])
244
+
245
+ # work-dir / yosys has already been created.
246
+
247
+ # Create and run yosys.synth.f
248
+ synth_command_list = self.create_yosys_synth_f() # util.ShellCommandList
249
+
250
+ # Optinally create and run a sta.f:
251
+ sta_command_lists = self.create_sta_f() # [] or [util.ShellCommandList]
252
+
253
+ # We create a run_yosys.sh wrapping these scripts, but we do not run this one.
254
+ util.write_shell_command_file(
255
+ dirpath=self.args['work-dir'],
256
+ filename='run_yosys.sh',
257
+ command_lists=[synth_command_list] + sta_command_lists,
258
+ )
259
+
260
+ # Do not run this if args['stop-before-compile'] is True
261
+ if self.args.get('stop-before-compile', False):
262
+ return
263
+
264
+ # Run the synth commands standalone:
265
+ self.exec(work_dir=self.full_work_dir, command_list=synth_command_list,
266
+ tee_fpath=synth_command_list.tee_fpath)
267
+
268
+ for x in sta_command_lists:
269
+ if self.args['sta'] and x:
270
+ self.exec(work_dir=self.full_work_dir, command_list=x,
271
+ tee_fpath=x.tee_fpath)
272
+
273
+ if self.status == 0:
274
+ util.info(f'yosys: wrote verilog to {self.yosys_v_path}')
135
275
 
136
276
 
137
277
  def create_yosys_synth_f(self) -> util.ShellCommandList:
138
278
  '''Derived classes may define, if they wish to get a list of yosys commands'''
139
- return util.ShellCommandList([])
279
+
280
+ # Create yosys.synth.f
281
+ yosys_synth_f_path = os.path.join(self.full_work_dir, 'yosys.synth.f')
282
+
283
+ # Since this assumes we didnt' run a SystemVerilog pre-processing step,
284
+ # read in all the verilog
285
+ yosys_blackbox_list = self.get_yosys_blackbox_list()
286
+
287
+ if self.args['liberty-file'] and not os.path.exists(self.args['liberty-file']):
288
+ self.error(f'--liberty-file={self.args["liberty-file"]} file does not exist')
289
+
290
+ with open(yosys_synth_f_path, 'w', encoding='utf-8') as f:
291
+ lines = [
292
+ self._get_read_verilog_one_liner()
293
+ ]
294
+
295
+ if self.args['liberty-file']:
296
+ lines.append('read_liberty -lib ' + self.args['liberty-file'])
297
+
298
+ for inst in yosys_blackbox_list:
299
+ lines.append('blackbox ' + inst)
300
+
301
+ lines += self.get_synth_command_lines()
302
+ f.write('\n'.join(lines))
303
+
304
+ synth_command_list = util.ShellCommandList(
305
+ [self.yosys_exe, '--scriptfile', 'yosys.synth.f'],
306
+ tee_fpath = 'yosys.synth.log'
307
+ )
308
+ return synth_command_list
140
309
 
141
310
 
142
311
  def get_synth_command_lines(self) -> list:
@@ -168,13 +337,25 @@ class CommonSynthYosys(CommandSynth, ToolYosys):
168
337
  ]
169
338
  return lines
170
339
 
340
+ def get_yosys_blackbox_list(self) -> list:
341
+ '''Returns blackbox list, since we don't have a preprocessing step like
342
+
343
+ slang, simply return self.blackbox_list. Intended to be overwritten by
344
+ derived classes so they can blackbox post-preprocessing.
345
+ '''
346
+ return self.blackbox_list
171
347
 
172
- def create_sta_f(self) -> util.ShellCommandList:
348
+
349
+ def create_sta_f(self) -> [util.ShellCommandList]:
173
350
  '''Returns command list, for running 'sta' on sta.f'''
174
351
 
175
352
  if not self.args['sta']:
176
353
  return []
177
354
 
355
+ if self.args['sta-scriptfile']:
356
+ # User brought one or more scriptfiles for STA, use those.
357
+ return self.get_commands_user_sta_scriptfile()
358
+
178
359
  if not self.args['liberty-file']:
179
360
  self.error('--sta is set, but need to also set --liberty-file=<file>')
180
361
 
@@ -193,6 +374,9 @@ class CommonSynthYosys(CommandSynth, ToolYosys):
193
374
  # Need to create sta.f:
194
375
  if self.args['sdc-file']:
195
376
  sdc_path = self.args['sdc-file']
377
+ elif self.files_sdc:
378
+ # Use files from DEPS target or command line.
379
+ sdc_path = ''
196
380
  else:
197
381
  # Need to create sdc.f:
198
382
  sdc_path = 'sdc.f'
@@ -204,15 +388,21 @@ class CommonSynthYosys(CommandSynth, ToolYosys):
204
388
  'read_liberty ' + self.args['liberty-file'],
205
389
  'read_verilog ' + self.yosys_v_path,
206
390
  'link_design ' + self.args['top'],
207
- 'read_sdc ' + sdc_path,
208
- 'report_checks',
209
391
  ]
392
+ for _file in self.files_sdc:
393
+ lines.append('read_sdc ' + _file)
394
+ if sdc_path:
395
+ lines.append('read_sdc ' + sdc_path)
396
+
397
+ lines.append('report_checks')
398
+
210
399
  f.write('\n'.join(lines))
211
400
 
212
- return util.ShellCommandList(
401
+ # return list with our one generated command-list
402
+ return [util.ShellCommandList(
213
403
  sta_command_list,
214
- tee_fpath = 'sta.log'
215
- )
404
+ tee_fpath = sta_command_list.tee_fpath
405
+ )]
216
406
 
217
407
 
218
408
  def create_sdc_f(self) -> None:
@@ -237,3 +427,253 @@ class CommonSynthYosys(CommandSynth, ToolYosys):
237
427
  + ' [get_ports * -filter {DIRECTION == OUT}];',
238
428
  ]
239
429
  f.write('\n'.join(lines))
430
+
431
+
432
+ def _get_read_verilog_one_liner(self) -> str:
433
+ '''Returns a string, intended to be used w/out Slang, for Verilog or simple
434
+
435
+ SV designs'''
436
+
437
+ read_verilog_cmd = [
438
+ 'read_verilog',
439
+ '-sv',
440
+ '-icells',
441
+ ]
442
+ read_verilog_cmd += self.get_yosys_read_verilog_defines_incdirs_files()
443
+ read_verilog_cmd.append(f'--top {self.args["top"]}')
444
+ return ' '.join(read_verilog_cmd)
445
+
446
+
447
+ def get_yosys_read_verilog_defines_incdirs_files(self) -> list:
448
+ '''Returns a partial list of all the args for a read_verilog or read_slang command in yosys
449
+
450
+ Handles defines, incdirs, files_sv, files_v
451
+ '''
452
+ ret_list = []
453
+
454
+ for name,value in self.defines.items():
455
+ if not name:
456
+ continue
457
+ if name in ['SIMULATION']:
458
+ continue
459
+
460
+ if value is None:
461
+ ret_list.append(f'--define-macro {name}')
462
+ else:
463
+ ret_list.append(f'--define-macro {name}={value}')
464
+
465
+ # We must define SYNTHESIS for oclib_defines.vh to work correctly.
466
+ if 'SYNTHESIS' not in self.defines:
467
+ ret_list.append('--define-macro SYNTHESIS')
468
+
469
+ for path in self.incdirs:
470
+ ret_list.append(f'-I {path}')
471
+
472
+ for path in self.files_v:
473
+ ret_list.append(path)
474
+
475
+ for path in self.files_sv:
476
+ ret_list.append(path)
477
+
478
+ ret_list.append(f'--top {self.args["top"]}')
479
+ return ret_list
480
+
481
+
482
+ class CommandLecYosys(CommandLec, ToolYosys):
483
+ '''Command handler for: eda lec --designs=<target1> --designs=<target2> --tool=yosys
484
+
485
+ Also supports: eda lec --tool=yosys <target>
486
+ If the target sets two args for --designs
487
+ '''
488
+
489
+ def __init__(self, config: dict):
490
+ CommandLec.__init__(self, config=config)
491
+ ToolYosys.__init__(self, config=self.config)
492
+
493
+ self.args.update({
494
+ 'yosys-scriptfile': [],
495
+ 'pre-read-verilog': [],
496
+ })
497
+ self.args_help.update({
498
+ 'yosys-scriptfile': (
499
+ 'Instead of using a built-in flow from eda, use your own scripts that are called'
500
+ ' via: yosys --scriptfile <this-arg>. You can set multiple args for multiple'
501
+ ' scriptfile (appends)'
502
+ ),
503
+ 'pre-read-verilog': 'Additional verilog files to read prior to running LEC',
504
+ })
505
+
506
+ self.synth_work_dirs = [
507
+ os.path.join('eda.work', 'lec.design1.synth'),
508
+ os.path.join('eda.work', 'lec.design2.synth')
509
+ ]
510
+
511
+ self.synth_designs_tops = [None, None]
512
+ self.synth_designs_fpaths = [None, None]
513
+
514
+ def get_synth_result_fpath(self, target: str) -> str:
515
+ '''Overridden from CommandLec'''
516
+
517
+ # Read the eda_output_config.yml, find the "top", and find the output .v filename.
518
+ return ""
519
+
520
+ def get_synth_command_list(self, design_num: int) -> list:
521
+ '''Returns one of the synthesis command lists, for design_num=0 or 1'''
522
+
523
+ if not design_num in [0, 1]:
524
+ self.error(f'{design_num=} we only support LEC on designs 0 and 1')
525
+
526
+ synth_cmd_list = [
527
+ get_eda_exec('synth'),
528
+ 'synth',
529
+ ]
530
+
531
+ if self.args['tool']:
532
+ synth_cmd_list.append('--tool=' + self.args['tool'])
533
+
534
+ synth_cmd_list += [
535
+ '--work-dir=' + self.synth_work_dirs[design_num],
536
+ self.args['designs'][design_num]
537
+ ]
538
+
539
+ return synth_cmd_list
540
+
541
+
542
+ def get_synth_top_from_output_config(self, design_num: int) -> str:
543
+ '''Returns the top name given the design number that we synthesized'''
544
+
545
+ work_dir = self.synth_work_dirs[design_num]
546
+ output_cfg_fpath = os.path.join(work_dir, util.EDA_OUTPUT_CONFIG_FNAME)
547
+ data = util.yaml_safe_load(output_cfg_fpath)
548
+ top = data.get('args', {}).get('top', '')
549
+ if not top:
550
+ self.error(f'"top" not found in synth run from {work_dir=} in',
551
+ f'config {output_cfg_fpath}')
552
+ return top
553
+
554
+
555
+ def get_synth_results_fpath(self, design_num: int, top: str) -> str:
556
+ '''Returns the synthesized .v file fpath'''
557
+ if not top:
558
+ top = self.get_synth_top_from_output_config(design_num=design_num)
559
+
560
+ work_dir = self.synth_work_dirs[design_num]
561
+ fpath = os.path.join(work_dir, 'yosys', f'{top}.v')
562
+ if not os.path.isfile(fpath):
563
+ self.error(f'{fpath=} does not exists, looking for synth results for LEC {design_num=}')
564
+ return fpath
565
+
566
+
567
+ def do_it(self) -> None:
568
+ self.set_tool_defines()
569
+ self.write_eda_config_and_args()
570
+
571
+ pwd = os.getcwd()
572
+
573
+ if not self.args['top']:
574
+ self.args['top'] = 'yosys_lec'
575
+
576
+ if self.args['yosys-scriptfile']:
577
+ yosys_cmdlists = get_commands_to_run_scriptfiles(
578
+ script_fnames_list=self.args['yosys-scriptfile'],
579
+ yosys_exe=self.yosys_exe
580
+ )
581
+
582
+ # We create a run_yosys.sh wrapping these scripts, but we do not run this one.
583
+ util.write_shell_command_file(
584
+ dirpath=self.args['work-dir'],
585
+ filename='run_yosys.sh',
586
+ command_lists=yosys_cmdlists
587
+ )
588
+
589
+ # actually run it.
590
+ for x in yosys_cmdlists:
591
+ if x:
592
+ self.exec(work_dir=self.args['work-dir'], command_list=x,
593
+ tee_fpath=x.tee_fpath)
594
+
595
+
596
+ if self.args['synth']:
597
+ synth1_cmd_list = self.get_synth_command_list(design_num=0)
598
+ synth2_cmd_list = self.get_synth_command_list(design_num=1)
599
+
600
+ util.info(f'LEC {synth1_cmd_list=}')
601
+ util.info(f'LEC {synth2_cmd_list=}')
602
+
603
+ self.exec(pwd, synth1_cmd_list, background=True)
604
+ util.info(f'Finished with 1st LEC synthesis {self.args["designs"][0]}')
605
+
606
+ self.exec(pwd, synth2_cmd_list, background=True)
607
+ util.info(f'Finished with 2nd LEC synthesis {self.args["designs"][1]}')
608
+
609
+ self.synth_designs_tops = [
610
+ self.get_synth_top_from_output_config(design_num=0),
611
+ self.get_synth_top_from_output_config(design_num=1)
612
+ ]
613
+ util.info(f'Design tops: {self.synth_designs_tops}')
614
+
615
+ # read the output config
616
+ self.synth_designs_fpaths = [
617
+ os.path.abspath(
618
+ self.get_synth_results_fpath(design_num=0, top=self.synth_designs_tops[0])),
619
+ os.path.abspath(
620
+ self.get_synth_results_fpath(design_num=1, top=self.synth_designs_tops[1]))
621
+ ]
622
+ util.info(f'Design tops: {self.synth_designs_fpaths}')
623
+
624
+ else:
625
+ # don't run synthesis, need the two top level .v files in
626
+ # self.synth_designs_fpaths, and need the two top module names in
627
+ # self.synth_designs_tops
628
+ self.synth_designs_fpaths = [
629
+ os.path.abspath(self.args['designs'][0]),
630
+ os.path.abspath(self.args['designs'][1])
631
+ ]
632
+
633
+ path, fname = os.path.split(self.synth_designs_fpaths[0])
634
+ module_guess, _ = os.path.splitext(fname)
635
+ top1 = util.get_inferred_top_module_name(
636
+ module_guess=module_guess, module_fpath=self.synth_designs_fpaths[0]
637
+ )
638
+ util.info(f'design1 top module name = {top1} (from {path} / {fname})')
639
+
640
+ path, fname = os.path.split(self.synth_designs_fpaths[1])
641
+ module_guess, _ = os.path.splitext(fname)
642
+ top2 = util.get_inferred_top_module_name(
643
+ module_guess=module_guess, module_fpath=self.synth_designs_fpaths[1]
644
+ )
645
+ util.info(f'design2 top module name = {top2} (from {path} / {fname})')
646
+
647
+ self.synth_designs_tops = [top1, top2]
648
+
649
+ # Need to create final LEC yosys script, that reads our two designs and runs
650
+ # LEC. Note the designs must have different module names
651
+ if self.synth_designs_tops[0] == self.synth_designs_tops[1]:
652
+ self.error('Cannot run Yosys LEC on two designs with the same top module name:',
653
+ f'{self.synth_designs_tops}')
654
+
655
+ lec_cmd_f_list = []
656
+ if self.args['pre-read-verilog']:
657
+ for x in self.args['pre-read-verilog']:
658
+ if os.path.isfile(x):
659
+ lec_cmd_f_list += [
660
+ 'read_verilog -sv -icells ' + os.path.abspath(x)
661
+ ]
662
+ else:
663
+ self.error(f' --pre-read-verilog file {x} does not exist')
664
+ lec_cmd_f_list += [
665
+ f'read_verilog -sv -icells {self.synth_designs_fpaths[0]}',
666
+ f'read_verilog -sv -icells {self.synth_designs_fpaths[1]}',
667
+ 'clk2fflogic;',
668
+ f'miter -equiv -flatten {" ".join(self.synth_designs_tops)} miter',
669
+ ('sat -seq 50 -verify -prove trigger 0 -show-all -show-inputs -show-outputs'
670
+ ' -set-init-zero miter'),
671
+ ]
672
+
673
+ lec_cmd_f_fpath = os.path.join(self.args['work-dir'], 'yosys_lec.f')
674
+ with open(lec_cmd_f_fpath, 'w', encoding='utf-8') as f:
675
+ f.write('\n'.join(lec_cmd_f_list) + '\n')
676
+
677
+ lec_cmd_list = 'yosys --scriptfile yosys_lec.f'.split()
678
+ util.info(f'LEC running {lec_cmd_list}')
679
+ self.exec(self.args['work-dir'], lec_cmd_list)
opencos/util.py CHANGED
@@ -23,6 +23,8 @@ logfile = None
23
23
  loglast = 0
24
24
  debug_level = 0
25
25
 
26
+ EDA_OUTPUT_CONFIG_FNAME = 'eda_output_config.yml'
27
+
26
28
  args = {
27
29
  'color' : False,
28
30
  'quiet' : False,
@@ -33,11 +35,10 @@ args = {
33
35
  'errors' : 0,
34
36
  }
35
37
 
36
-
37
- def strip_all_quotes(s:str):
38
+ def strip_all_quotes(s: str) -> str:
38
39
  return s.replace("'", '').replace('"', '')
39
40
 
40
- def strip_outer_quotes(s:str):
41
+ def strip_outer_quotes(s: str) -> str:
41
42
  ret = str(s)
42
43
  while (ret.startswith("'") and ret.endswith("'")) or \
43
44
  (ret.startswith('"') and ret.endswith('"')):
@@ -615,7 +616,7 @@ def write_shell_command_file(dirpath : str, filename : str, command_lists : list
615
616
  os.chmod(fullpath, 0o755)
616
617
 
617
618
 
618
- def write_eda_config_and_args(dirpath : str, filename='eda_output_config.yml', command_obj_ref=None):
619
+ def write_eda_config_and_args(dirpath : str, filename=EDA_OUTPUT_CONFIG_FNAME, command_obj_ref=None):
619
620
  import copy
620
621
  if command_obj_ref is None:
621
622
  return
@@ -654,7 +655,7 @@ def get_inferred_top_module_name(module_guess: str, module_fpath: str) -> str:
654
655
  if line.startswith('module '):
655
656
  parts = line.split()
656
657
  module_name = parts[1]
657
- rstrip_nonword_pattern = r'\W+$'
658
+ rstrip_nonword_pattern = r'\W+.*$'
658
659
  module_name = re.sub(rstrip_nonword_pattern, '', module_name)
659
660
  if bool(re.fullmatch(r'^\w+$', module_name)):
660
661
  if module_name == module_guess:
@@ -673,12 +674,14 @@ def subprocess_run(work_dir, command_list, fake:bool=False, shell=False) -> int:
673
674
  if work_dir is not None:
674
675
  os.chdir(work_dir)
675
676
 
677
+ is_windows = sys.platform.startswith('win')
678
+
676
679
  proc_kwargs = {'shell': shell}
677
680
  bash_exec = shutil.which('bash')
678
- if shell and bash_exec:
681
+ if shell and bash_exec and not is_windows:
679
682
  proc_kwargs.update({'executable': bash_exec})
680
683
 
681
- if shell:
684
+ if not is_windows and shell:
682
685
  c = ' '.join(command_list)
683
686
  else:
684
687
  c = command_list
@@ -699,6 +702,9 @@ def subprocess_run_background(work_dir, command_list, background=True, fake:bool
699
702
  tee_fpath is relative to work_dir.
700
703
  '''
701
704
 
705
+
706
+ is_windows = sys.platform.startswith('win')
707
+
702
708
  debug(f'util.subprocess_run_background: {background=} {tee_fpath=} {shell=}')
703
709
 
704
710
  if fake or (not background and not tee_fpath):
@@ -716,10 +722,11 @@ def subprocess_run_background(work_dir, command_list, background=True, fake:bool
716
722
  }
717
723
 
718
724
  bash_exec = shutil.which('bash')
719
- if shell and bash_exec:
725
+ if shell and bash_exec and not is_windows:
726
+ # Note - windows powershell will end up calling: /bin/bash /c, which won't work
720
727
  proc_kwargs.update({'executable': bash_exec})
721
728
 
722
- if shell:
729
+ if not is_windows and shell:
723
730
  c = ' '.join(command_list)
724
731
  else:
725
732
  c = command_list # leave as list.
@@ -732,7 +739,7 @@ def subprocess_run_background(work_dir, command_list, background=True, fake:bool
732
739
  stderr = ''
733
740
  with open(tee_fpath, 'w') as f:
734
741
  for line in iter(proc.stdout.readline, b''):
735
- line = line.rstrip().decode("utf-8")
742
+ line = line.rstrip().decode("utf-8", errors="replace")
736
743
  if not background:
737
744
  print(line)
738
745
  f.write(line + '\n')
@@ -749,8 +756,8 @@ def subprocess_run_background(work_dir, command_list, background=True, fake:bool
749
756
  stdout, stderr = proc.communicate()
750
757
  rc = proc.returncode
751
758
 
752
- stdout = stdout.decode('utf-8') if stdout else ""
753
- stderr = stderr.decode('utf-8') if stderr else ""
759
+ stdout = stdout.decode('utf-8', errors="replace") if stdout else ""
760
+ stderr = stderr.decode('utf-8', errors="replace") if stderr else ""
754
761
  debug(f"shell_run_background: {rc=}")
755
762
  if stdout:
756
763
  for lineno, line in enumerate(stdout.strip().split('\n')):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opencos-eda
3
- Version: 0.2.43
3
+ Version: 0.2.45
4
4
  Summary: A simple Python package for wrapping RTL simuliatons and synthesis
5
5
  Author-email: Simon Sabato <simon@cognichip.ai>, Drew Ranck <drew@cognichip.ai>
6
6
  Project-URL: Homepage, https://github.com/cognichip/opencos