yowasp-yosys 0.38.0.92.post687.dev0__py3-none-any.whl → 0.39.0.165.post702.dev0__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.
@@ -0,0 +1,344 @@
1
+ from __future__ import annotations
2
+
3
+ import sqlite3
4
+ import os
5
+ import time
6
+ import json
7
+ from collections import defaultdict
8
+ from functools import wraps
9
+ from pathlib import Path
10
+ from typing import Any, Callable, TypeVar, Optional, Iterable
11
+ from sby_design import SbyProperty, pretty_path
12
+
13
+
14
+ Fn = TypeVar("Fn", bound=Callable[..., Any])
15
+
16
+
17
+ def transaction(method: Fn) -> Fn:
18
+ @wraps(method)
19
+ def wrapper(self: SbyStatusDb, *args: Any, **kwargs: Any) -> Any:
20
+ if self._transaction_active:
21
+ return method(self, *args, **kwargs)
22
+
23
+ try:
24
+ self.log_debug(f"begin {method.__name__!r} transaction")
25
+ self.db.execute("begin")
26
+ self._transaction_active = True
27
+ result = method(self, *args, **kwargs)
28
+ self.db.execute("commit")
29
+ self._transaction_active = False
30
+ self.log_debug(f"comitted {method.__name__!r} transaction")
31
+ return result
32
+ except sqlite3.OperationalError as err:
33
+ self.log_debug(f"failed {method.__name__!r} transaction {err}")
34
+ self.db.rollback()
35
+ self._transaction_active = False
36
+ except Exception as err:
37
+ self.log_debug(f"failed {method.__name__!r} transaction {err}")
38
+ self.db.rollback()
39
+ self._transaction_active = False
40
+ raise
41
+ try:
42
+ self.log_debug(
43
+ f"retrying {method.__name__!r} transaction once in immediate mode"
44
+ )
45
+ self.db.execute("begin immediate")
46
+ self._transaction_active = True
47
+ result = method(self, *args, **kwargs)
48
+ self.db.execute("commit")
49
+ self._transaction_active = False
50
+ self.log_debug(f"comitted {method.__name__!r} transaction")
51
+ return result
52
+ except Exception as err:
53
+ self.log_debug(f"failed {method.__name__!r} transaction {err}")
54
+ self.db.rollback()
55
+ self._transaction_active = False
56
+ raise
57
+
58
+ return wrapper # type: ignore
59
+
60
+
61
+ class SbyStatusDb:
62
+ def __init__(self, path: Path, task, timeout: float = 5.0):
63
+ self.debug = False
64
+ self.task = task
65
+ self._transaction_active = False
66
+
67
+ setup = not os.path.exists(path)
68
+
69
+ self.db = sqlite3.connect(path, isolation_level=None, timeout=timeout)
70
+ self.db.row_factory = sqlite3.Row
71
+ self.db.execute("PRAGMA journal_mode=WAL")
72
+ self.db.execute("PRAGMA synchronous=0")
73
+
74
+ if setup:
75
+ self._setup()
76
+
77
+ if task is not None:
78
+ self.task_id = self.create_task(workdir=task.workdir, mode=task.opt_mode)
79
+
80
+ def log_debug(self, *args):
81
+ if self.debug:
82
+ if self.task:
83
+ self.task.log(" ".join(str(arg) for arg in args))
84
+ else:
85
+ print(*args)
86
+
87
+ @transaction
88
+ def _setup(self):
89
+ script = """
90
+ CREATE TABLE task (
91
+ id INTEGER PRIMARY KEY,
92
+ workdir TEXT,
93
+ mode TEXT,
94
+ created REAL
95
+ );
96
+ CREATE TABLE task_status (
97
+ id INTEGER PRIMARY KEY,
98
+ task INTEGER,
99
+ status TEXT,
100
+ data TEXT,
101
+ created REAL,
102
+ FOREIGN KEY(task) REFERENCES task(id)
103
+ );
104
+ CREATE TABLE task_property (
105
+ id INTEGER PRIMARY KEY,
106
+ task INTEGER,
107
+ src TEXT,
108
+ name TEXT,
109
+ created REAL,
110
+ FOREIGN KEY(task) REFERENCES task(id)
111
+ );
112
+ CREATE TABLE task_property_status (
113
+ id INTEGER PRIMARY KEY,
114
+ task_property INTEGER,
115
+ status TEXT,
116
+ data TEXT,
117
+ created REAL,
118
+ FOREIGN KEY(task_property) REFERENCES task_property(id)
119
+ );
120
+ CREATE TABLE task_property_data (
121
+ id INTEGER PRIMARY KEY,
122
+ task_property INTEGER,
123
+ kind TEXT,
124
+ data TEXT,
125
+ created REAL,
126
+ FOREIGN KEY(task_property) REFERENCES task_property(id)
127
+ );
128
+ """
129
+ for statement in script.split(";\n"):
130
+ statement = statement.strip()
131
+ if statement:
132
+ self.db.execute(statement)
133
+
134
+ @transaction
135
+ def create_task(self, workdir: str, mode: str) -> int:
136
+ return self.db.execute(
137
+ """
138
+ INSERT INTO task (workdir, mode, created)
139
+ VALUES (:workdir, :mode, :now)
140
+ """,
141
+ dict(workdir=workdir, mode=mode, now=time.time()),
142
+ ).lastrowid
143
+
144
+ @transaction
145
+ def create_task_properties(
146
+ self, properties: Iterable[SbyProperty], *, task_id: Optional[int] = None
147
+ ):
148
+ if task_id is None:
149
+ task_id = self.task_id
150
+ now = time.time()
151
+ self.db.executemany(
152
+ """
153
+ INSERT INTO task_property (name, src, task, created)
154
+ VALUES (:name, :src, :task, :now)
155
+ """,
156
+ [
157
+ dict(
158
+ name=json.dumps(prop.path),
159
+ src=prop.location or "",
160
+ task=task_id,
161
+ now=now,
162
+ )
163
+ for prop in properties
164
+ ],
165
+ )
166
+
167
+ @transaction
168
+ def set_task_status(
169
+ self,
170
+ status: Optional[str] = None,
171
+ data: Any = None,
172
+ ):
173
+ if status is None:
174
+ status = property.status
175
+
176
+ now = time.time()
177
+ self.db.execute(
178
+ """
179
+ INSERT INTO task_status (
180
+ task, status, data, created
181
+ )
182
+ VALUES (
183
+ :task, :status, :data, :now
184
+ )
185
+ """,
186
+ dict(
187
+ task=self.task_id,
188
+ status=status,
189
+ data=json.dumps(data),
190
+ now=now,
191
+ ),
192
+ )
193
+
194
+ @transaction
195
+ def set_task_property_status(
196
+ self,
197
+ property: SbyProperty,
198
+ status: Optional[str] = None,
199
+ data: Any = None,
200
+ ):
201
+ if status is None:
202
+ status = property.status
203
+
204
+ now = time.time()
205
+ self.db.execute(
206
+ """
207
+ INSERT INTO task_property_status (
208
+ task_property, status, data, created
209
+ )
210
+ VALUES (
211
+ (SELECT id FROM task_property WHERE task = :task AND name = :name),
212
+ :status, :data, :now
213
+ )
214
+ """,
215
+ dict(
216
+ task=self.task_id,
217
+ name=json.dumps(property.path),
218
+ status=status,
219
+ data=json.dumps(data),
220
+ now=now,
221
+ ),
222
+ )
223
+
224
+ @transaction
225
+ def add_task_property_data(self, property: SbyProperty, kind: str, data: Any):
226
+ now = time.time()
227
+ self.db.execute(
228
+ """
229
+ INSERT INTO task_property_data (
230
+ task_property, kind, data, created
231
+ )
232
+ VALUES (
233
+ (SELECT id FROM task_property WHERE task = :task AND name = :name),
234
+ :kind, :data, :now
235
+ )
236
+ """,
237
+ dict(
238
+ task=self.task_id,
239
+ name=json.dumps(property.path),
240
+ kind=kind,
241
+ data=json.dumps(data),
242
+ now=now,
243
+ ),
244
+ )
245
+
246
+ @transaction
247
+ def all_tasks(self):
248
+ rows = self.db.execute(
249
+ """
250
+ SELECT id, workdir, created FROM task
251
+ """
252
+ ).fetchall()
253
+
254
+ return {row["id"]: dict(row) for row in rows}
255
+
256
+ @transaction
257
+ def all_task_properties(self):
258
+ rows = self.db.execute(
259
+ """
260
+ SELECT id, task, src, name, created FROM task_property
261
+ """
262
+ ).fetchall()
263
+
264
+ def get_result(row):
265
+ row = dict(row)
266
+ row["name"] = tuple(json.loads(row.get("name", "[]")))
267
+ row["data"] = json.loads(row.get("data", "null"))
268
+ return row
269
+
270
+ return {row["id"]: get_result(row) for row in rows}
271
+
272
+ @transaction
273
+ def all_task_property_statuses(self):
274
+ rows = self.db.execute(
275
+ """
276
+ SELECT id, task_property, status, data, created
277
+ FROM task_property_status
278
+ """
279
+ ).fetchall()
280
+
281
+ def get_result(row):
282
+ row = dict(row)
283
+ row["data"] = json.loads(row.get("data", "null"))
284
+ return row
285
+
286
+ return {row["id"]: get_result(row) for row in rows}
287
+
288
+ @transaction
289
+ def all_status_data(self):
290
+ return (
291
+ self.all_tasks(),
292
+ self.all_task_properties(),
293
+ self.all_task_property_statuses(),
294
+ )
295
+
296
+ @transaction
297
+ def reset(self):
298
+ self.db.execute("""DELETE FROM task_property_status""")
299
+ self.db.execute("""DELETE FROM task_property_data""")
300
+ self.db.execute("""DELETE FROM task_property""")
301
+ self.db.execute("""DELETE FROM task_status""")
302
+ self.db.execute("""DELETE FROM task""")
303
+
304
+ def print_status_summary(self):
305
+ tasks, task_properties, task_property_statuses = self.all_status_data()
306
+ properties = defaultdict(set)
307
+
308
+ uniquify_paths = defaultdict(dict)
309
+
310
+ def add_status(task_property, status):
311
+
312
+ display_name = task_property["name"]
313
+ if display_name[-1].startswith("$"):
314
+ counters = uniquify_paths[task_property["src"]]
315
+ counter = counters.setdefault(display_name[-1], len(counters) + 1)
316
+ if task_property["src"]:
317
+ if counter < 2:
318
+ path_based = f"<unnamed at {task_property['src']}>"
319
+ else:
320
+ path_based = f"<unnamed #{counter} at {task_property['src']}>"
321
+ else:
322
+ path_based = f"<unnamed #{counter}>"
323
+ display_name = (*display_name[:-1], path_based)
324
+
325
+ properties[display_name].add(status)
326
+
327
+ for task_property in task_properties.values():
328
+ add_status(task_property, "UNKNOWN")
329
+
330
+ for status in task_property_statuses.values():
331
+ task_property = task_properties[status["task_property"]]
332
+ add_status(task_property, status["status"])
333
+
334
+ for display_name, statuses in sorted(properties.items()):
335
+ print(pretty_path(display_name), combine_statuses(statuses))
336
+
337
+
338
+ def combine_statuses(statuses):
339
+ statuses = set(statuses)
340
+
341
+ if len(statuses) > 1:
342
+ statuses.discard("UNKNOWN")
343
+
344
+ return ",".join(sorted(statuses))
@@ -79,6 +79,20 @@ def except_hook(exctype, value, traceback):
79
79
  sys.excepthook = except_hook
80
80
 
81
81
 
82
+ def recursion_helper(iteration, *request):
83
+ stack = [iteration(*request)]
84
+
85
+ while stack:
86
+ top = stack.pop()
87
+ try:
88
+ request = next(top)
89
+ except StopIteration:
90
+ continue
91
+
92
+ stack.append(top)
93
+ stack.append(iteration(*request))
94
+
95
+
82
96
  hex_dict = {
83
97
  "0": "0000", "1": "0001", "2": "0010", "3": "0011",
84
98
  "4": "0100", "5": "0101", "6": "0110", "7": "0111",
@@ -100,6 +114,7 @@ class SmtModInfo:
100
114
  self.clocks = dict()
101
115
  self.cells = dict()
102
116
  self.asserts = dict()
117
+ self.assumes = dict()
103
118
  self.covers = dict()
104
119
  self.maximize = set()
105
120
  self.minimize = set()
@@ -127,6 +142,7 @@ class SmtIo:
127
142
  self.recheck = False
128
143
  self.smt2cache = [list()]
129
144
  self.smt2_options = dict()
145
+ self.smt2_assumptions = dict()
130
146
  self.p = None
131
147
  self.p_index = solvers_index
132
148
  solvers_index += 1
@@ -298,10 +314,22 @@ class SmtIo:
298
314
  return stmt
299
315
 
300
316
  def unroll_stmt(self, stmt):
317
+ result = []
318
+ recursion_helper(self._unroll_stmt_into, stmt, result)
319
+ return result.pop()
320
+
321
+ def _unroll_stmt_into(self, stmt, output, depth=128):
301
322
  if not isinstance(stmt, list):
302
- return stmt
323
+ output.append(stmt)
324
+ return
303
325
 
304
- stmt = [self.unroll_stmt(s) for s in stmt]
326
+ new_stmt = []
327
+ for s in stmt:
328
+ if depth:
329
+ yield from self._unroll_stmt_into(s, new_stmt, depth - 1)
330
+ else:
331
+ yield s, new_stmt
332
+ stmt = new_stmt
305
333
 
306
334
  if len(stmt) >= 2 and not isinstance(stmt[0], list) and stmt[0] in self.unroll_decls:
307
335
  assert stmt[1] in self.unroll_objs
@@ -330,12 +358,19 @@ class SmtIo:
330
358
  decl[2] = list()
331
359
 
332
360
  if len(decl) > 0:
333
- decl = self.unroll_stmt(decl)
361
+ tmp = []
362
+ if depth:
363
+ yield from self._unroll_stmt_into(decl, tmp, depth - 1)
364
+ else:
365
+ yield decl, tmp
366
+
367
+ decl = tmp.pop()
334
368
  self.write(self.unparse(decl), unroll=False)
335
369
 
336
- return self.unroll_cache[key]
370
+ output.append(self.unroll_cache[key])
371
+ return
337
372
 
338
- return stmt
373
+ output.append(stmt)
339
374
 
340
375
  def p_thread_main(self):
341
376
  while True:
@@ -569,6 +604,12 @@ class SmtIo:
569
604
  else:
570
605
  self.modinfo[self.curmod].covers["%s_c %s" % (self.curmod, fields[2])] = fields[3]
571
606
 
607
+ if fields[1] == "yosys-smt2-assume":
608
+ if len(fields) > 4:
609
+ self.modinfo[self.curmod].assumes["%s_u %s" % (self.curmod, fields[2])] = f'{fields[4]} ({fields[3]})'
610
+ else:
611
+ self.modinfo[self.curmod].assumes["%s_u %s" % (self.curmod, fields[2])] = fields[3]
612
+
572
613
  if fields[1] == "yosys-smt2-maximize":
573
614
  self.modinfo[self.curmod].maximize.add(fields[2])
574
615
 
@@ -752,8 +793,13 @@ class SmtIo:
752
793
  return stmt
753
794
 
754
795
  def check_sat(self, expected=["sat", "unsat", "unknown", "timeout", "interrupted"]):
796
+ if self.smt2_assumptions:
797
+ assume_exprs = " ".join(self.smt2_assumptions.values())
798
+ check_stmt = f"(check-sat-assuming ({assume_exprs}))"
799
+ else:
800
+ check_stmt = "(check-sat)"
755
801
  if self.debug_print:
756
- print("> (check-sat)")
802
+ print(f"> {check_stmt}")
757
803
  if self.debug_file and not self.nocomments:
758
804
  print("; running check-sat..", file=self.debug_file)
759
805
  self.debug_file.flush()
@@ -767,7 +813,7 @@ class SmtIo:
767
813
  for cache_stmt in cache_ctx:
768
814
  self.p_write(cache_stmt + "\n", False)
769
815
 
770
- self.p_write("(check-sat)\n", True)
816
+ self.p_write(f"{check_stmt}\n", True)
771
817
 
772
818
  if self.timeinfo:
773
819
  i = 0
@@ -835,7 +881,7 @@ class SmtIo:
835
881
 
836
882
  if self.debug_file:
837
883
  print("(set-info :status %s)" % result, file=self.debug_file)
838
- print("(check-sat)", file=self.debug_file)
884
+ print(check_stmt, file=self.debug_file)
839
885
  self.debug_file.flush()
840
886
 
841
887
  if result not in expected:
@@ -912,6 +958,48 @@ class SmtIo:
912
958
  def bv2int(self, v):
913
959
  return int(self.bv2bin(v), 2)
914
960
 
961
+ def get_raw_unsat_assumptions(self):
962
+ self.write("(get-unsat-assumptions)")
963
+ exprs = set(self.unparse(part) for part in self.parse(self.read()))
964
+ unsat_assumptions = []
965
+ for key, value in self.smt2_assumptions.items():
966
+ # normalize expression
967
+ value = self.unparse(self.parse(value))
968
+ if value in exprs:
969
+ exprs.remove(value)
970
+ unsat_assumptions.append(key)
971
+ return unsat_assumptions
972
+
973
+ def get_unsat_assumptions(self, minimize=False):
974
+ if not minimize:
975
+ return self.get_raw_unsat_assumptions()
976
+ required_assumptions = {}
977
+
978
+ while True:
979
+ candidate_assumptions = {}
980
+ for key in self.get_raw_unsat_assumptions():
981
+ if key not in required_assumptions:
982
+ candidate_assumptions[key] = self.smt2_assumptions[key]
983
+
984
+ while candidate_assumptions:
985
+
986
+ candidate_key, candidate_assume = candidate_assumptions.popitem()
987
+
988
+ self.smt2_assumptions = {}
989
+ for key, assume in candidate_assumptions.items():
990
+ self.smt2_assumptions[key] = assume
991
+ for key, assume in required_assumptions.items():
992
+ self.smt2_assumptions[key] = assume
993
+ result = self.check_sat()
994
+
995
+ if result == 'unsat':
996
+ candidate_assumptions = None
997
+ else:
998
+ required_assumptions[candidate_key] = candidate_assume
999
+
1000
+ if candidate_assumptions is not None:
1001
+ return list(required_assumptions)
1002
+
915
1003
  def get(self, expr):
916
1004
  self.write("(get-value (%s))" % (expr))
917
1005
  return self.parse(self.read())[0][1]
@@ -1,5 +1,5 @@
1
1
  // **AUTOGENERATED FILE** **DO NOT EDIT**
2
- // Generated by ../yosys-src/techlibs/quicklogic/qlf_k6n10f/generate_bram_types_sim.py at 2024-02-27 08:47:46.596631+00:00
2
+ // Generated by ../yosys-src/techlibs/quicklogic/qlf_k6n10f/generate_bram_types_sim.py at 2024-04-04 02:33:31.959107+00:00
3
3
  `timescale 1ns /10ps
4
4
 
5
5
  module TDP36K_BRAM_A_X1_B_X1_nonsplit (