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.
- opencos/commands/__init__.py +4 -0
- opencos/commands/export.py +0 -1
- opencos/commands/flist.py +4 -1
- opencos/commands/lec.py +103 -0
- opencos/commands/shell.py +202 -0
- opencos/commands/sim.py +1 -1
- opencos/deps_helpers.py +2 -0
- opencos/eda.py +6 -3
- opencos/eda_base.py +12 -4
- opencos/eda_config.py +1 -1
- opencos/eda_config_defaults.yml +22 -7
- opencos/eda_extract_targets.py +13 -3
- opencos/export_helper.py +6 -4
- opencos/files.py +1 -0
- opencos/tests/helpers.py +3 -2
- opencos/tests/test_deps_schema.py +3 -1
- opencos/tests/test_eda.py +19 -9
- opencos/tests/test_eda_synth.py +63 -2
- opencos/tools/invio_yosys.py +7 -7
- opencos/tools/iverilog.py +1 -1
- opencos/tools/riviera.py +1 -1
- opencos/tools/slang.py +1 -1
- opencos/tools/slang_yosys.py +29 -38
- opencos/tools/surelog.py +1 -1
- opencos/tools/tabbycad_yosys.py +1 -1
- opencos/tools/verilator.py +1 -1
- opencos/tools/vivado.py +30 -11
- opencos/tools/yosys.py +465 -25
- opencos/util.py +19 -12
- {opencos_eda-0.2.43.dist-info → opencos_eda-0.2.45.dist-info}/METADATA +1 -1
- {opencos_eda-0.2.43.dist-info → opencos_eda-0.2.45.dist-info}/RECORD +36 -34
- {opencos_eda-0.2.43.dist-info → opencos_eda-0.2.45.dist-info}/WHEEL +0 -0
- {opencos_eda-0.2.43.dist-info → opencos_eda-0.2.45.dist-info}/entry_points.txt +0 -0
- {opencos_eda-0.2.43.dist-info → opencos_eda-0.2.45.dist-info}/licenses/LICENSE +0 -0
- {opencos_eda-0.2.43.dist-info → opencos_eda-0.2.45.dist-info}/licenses/LICENSE.spdx +0 -0
- {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':
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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':
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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.
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
401
|
+
# return list with our one generated command-list
|
|
402
|
+
return [util.ShellCommandList(
|
|
213
403
|
sta_command_list,
|
|
214
|
-
tee_fpath =
|
|
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=
|
|
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.
|
|
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
|