3
0
Fork 0
mirror of https://github.com/YosysHQ/yosys synced 2025-07-24 13:18:56 +00:00

smtbmc: Add native json based witness format + smt2 backend support

This adds a native json based witness trace format. By having a common
format that includes everything we support, and providing a conversion
utility (yosys-witness) we no longer need to implement every format for
every tool that deals with witness traces, avoiding a quadratic
opportunity to introduce subtle bugs.

Included:

  * smt2: New yosys-smt2-witness info lines containing full hierarchical
    paths without lossy escaping.
  * yosys-smtbmc --dump-yw trace.yw: Dump results in the new format.
  * yosys-smtbmc --yw trace.yw: Read new format as constraints.
  * yosys-witness: New tool to convert witness formats.
    Currently this can only display traces in a human-readable-only
    format and do a passthrough read/write of the new format.
  * ywio.py: Small python lib for reading and writing the new format.
    Used by yosys-smtbmc and yosys-witness to avoid duplication.
This commit is contained in:
Jannis Harder 2022-08-02 16:49:36 +02:00
parent 96a1173598
commit f041e36c6e
9 changed files with 983 additions and 113 deletions

View file

@ -17,9 +17,10 @@
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
import os, sys, getopt, re
import os, sys, getopt, re, bisect
##yosys-sys-path##
from smtio import SmtIo, SmtOpts, MkVcd
from ywio import ReadWitness, WriteWitness, WitnessValues
from collections import defaultdict
got_topt = False
@ -28,6 +29,8 @@ step_size = 1
num_steps = 20
append_steps = 0
vcdfile = None
inywfile = None
outywfile = None
cexfile = None
aimfile = None
aiwfile = None
@ -94,6 +97,9 @@ def usage():
the AIGER witness file does not include the status and
properties lines.
--yw <yosys_witness_filename>
read a Yosys witness.
--btorwit <btor_witness_filename>
read a BTOR witness.
@ -121,6 +127,9 @@ def usage():
(hint: use 'write_smt2 -wires' for maximum
coverage of signals in generated VCD file)
--dump-yw <yw_filename>
write trace as a Yosys witness trace
--dump-vlogtb <verilog_filename>
write trace as Verilog test bench
@ -167,8 +176,8 @@ def usage():
try:
opts, args = getopt.getopt(sys.argv[1:], so.shortopts + "t:igcm:", so.longopts +
["final-only", "assume-skipped=", "smtc=", "cex=", "aig=", "aig-noheader", "btorwit=", "presat",
"dump-vcd=", "dump-vlogtb=", "vlogtb-top=", "dump-smtc=", "dump-all", "noinfo", "append=",
["final-only", "assume-skipped=", "smtc=", "cex=", "aig=", "aig-noheader", "yw=", "btorwit=", "presat",
"dump-vcd=", "dump-yw=", "dump-vlogtb=", "vlogtb-top=", "dump-smtc=", "dump-all", "noinfo", "append=",
"smtc-init", "smtc-top=", "noinit", "binary", "keep-going"])
except:
usage()
@ -204,10 +213,14 @@ for o, a in opts:
aiwfile = a + ".aiw"
elif o == "--aig-noheader":
aigheader = False
elif o == "--yw":
inywfile = a
elif o == "--btorwit":
btorwitfile = a
elif o == "--dump-vcd":
vcdfile = a
elif o == "--dump-yw":
outywfile = a
elif o == "--dump-vlogtb":
vlogtbfile = a
elif o == "--vlogtb-top":
@ -602,6 +615,101 @@ if aimfile is not None:
num_steps = max(num_steps, step+2)
step += 1
if inywfile is not None:
with open(inywfile, "r") as f:
inyw = ReadWitness(f)
inits, seqs, clocks, mems = smt.hierwitness(topmod, allregs=True, blackbox=True)
smt_wires = defaultdict(list)
smt_mems = defaultdict(list)
for wire in inits + seqs:
smt_wires[wire["path"]].append(wire)
for mem in mems:
smt_mems[mem["path"]].append(mem)
addr_re = re.compile(r'\\\[[0-9]+\]$')
bits_re = re.compile(r'[01?]*$')
for t, step in inyw.steps():
present_signals, missing = step.present_signals(inyw.sigmap)
for sig in present_signals:
bits = step[sig]
if not bits_re.match(bits):
raise ValueError("unsupported bit value in Yosys witness file")
sig_end = sig.offset + len(bits)
if sig.path in smt_wires:
for wire in smt_wires[sig.path]:
width, offset = wire["width"], wire["offset"]
smt_bool = smt.net_width(topmod, wire["smtpath"]) == 1
offset = max(offset, 0)
end = width + offset
common_offset = max(sig.offset, offset)
common_end = min(sig_end, end)
if common_end <= common_offset:
continue
smt_expr = smt.net_expr(topmod, f"s{t}", wire["smtpath"])
if not smt_bool:
slice_high = common_end - offset - 1
slice_low = common_offset - offset
smt_expr = "((_ extract %d %d) %s)" % (slice_high, slice_low, smt_expr)
bit_slice = bits[len(bits) - (common_end - sig.offset):len(bits) - (common_offset - sig.offset)]
if bit_slice.count("?") == len(bit_slice):
continue
if smt_bool:
assert width == 1
smt_constr = "(= %s %s)" % (smt_expr, "true" if bit_slice == "1" else "false")
else:
if "?" in bit_slice:
mask = bit_slice.replace("0", "1").replace("?", "0")
bit_slice = bit_slice.replace("?", "0")
smt_expr = "(bvand %s #b%s)" % (smt_expr, mask)
smt_constr = "(= %s #b%s)" % (smt_expr, bit_slice)
constr_assumes[t].append((inywfile, smt_constr))
if sig.memory_path:
if sig.memory_path in smt_mems:
for mem in smt_mems[sig.memory_path]:
width, size, bv = mem["width"], mem["size"], mem["statebv"]
smt_expr = smt.net_expr(topmod, f"s{t}", mem["smtpath"])
if bv:
word_low = sig.memory_addr * width
word_high = word_low + width - 1
smt_expr = "((_ extract %d %d) %s)" % (word_high, word_low, smt_expr)
else:
addr_width = (size - 1).bit_length()
addr_bits = f"{sig.memory_addr:0{addr_width}b}"
smt_expr = "(select %s #b%s )" % (smt_expr, addr_bits)
if len(bits) < width:
slice_high = sig.offset + len(bits) - 1
smt_expr = "((_ extract %d %d) %s)" % (slice_high, sig.offset, smt_expr)
bit_slice = bits
if "?" in bit_slice:
mask = bit_slice.replace("0", "1").replace("?", "0")
bit_slice = bit_slice.replace("?", "0")
smt_expr = "(bvand %s #b%s)" % (smt_expr, mask)
smt_constr = "(= %s #b%s)" % (smt_expr, bit_slice)
constr_assumes[t].append((inywfile, smt_constr))
if btorwitfile is not None:
with open(btorwitfile, "r") as f:
step = None
@ -699,6 +807,115 @@ if btorwitfile is not None:
skip_steps = step
num_steps = step+1
def collect_mem_trace_data(steps_start, steps_stop, vcd=None):
mem_trace_data = dict()
for mempath in sorted(smt.hiermems(topmod)):
abits, width, rports, wports, asyncwr = smt.mem_info(topmod, mempath)
expr_id = list()
expr_list = list()
for i in range(steps_start, steps_stop):
for j in range(rports):
expr_id.append(('R', i-steps_start, j, 'A'))
expr_id.append(('R', i-steps_start, j, 'D'))
expr_list.append(smt.mem_expr(topmod, "s%d" % i, mempath, "R%dA" % j))
expr_list.append(smt.mem_expr(topmod, "s%d" % i, mempath, "R%dD" % j))
for j in range(wports):
expr_id.append(('W', i-steps_start, j, 'A'))
expr_id.append(('W', i-steps_start, j, 'D'))
expr_id.append(('W', i-steps_start, j, 'M'))
expr_list.append(smt.mem_expr(topmod, "s%d" % i, mempath, "W%dA" % j))
expr_list.append(smt.mem_expr(topmod, "s%d" % i, mempath, "W%dD" % j))
expr_list.append(smt.mem_expr(topmod, "s%d" % i, mempath, "W%dM" % j))
rdata = list()
wdata = list()
addrs = set()
for eid, edat in zip(expr_id, smt.get_list(expr_list)):
t, i, j, f = eid
if t == 'R':
c = rdata
elif t == 'W':
c = wdata
else:
assert False
while len(c) <= i:
c.append(list())
c = c[i]
while len(c) <= j:
c.append(dict())
c = c[j]
c[f] = smt.bv2bin(edat)
if f == 'A':
addrs.add(c[f])
for addr in addrs:
tdata = list()
data = ["x"] * width
gotread = False
if len(wdata) == 0 and len(rdata) != 0:
wdata = [[]] * len(rdata)
assert len(rdata) == len(wdata)
for i in range(len(wdata)):
if not gotread:
for j_data in rdata[i]:
if j_data["A"] == addr:
data = list(j_data["D"])
gotread = True
break
if gotread:
buf = data[:]
for ii in reversed(range(len(tdata))):
for k in range(width):
if tdata[ii][k] == "x":
tdata[ii][k] = buf[k]
else:
buf[k] = tdata[ii][k]
if not asyncwr:
tdata.append(data[:])
for j_data in wdata[i]:
if j_data["A"] != addr:
continue
D = j_data["D"]
M = j_data["M"]
for k in range(width):
if M[k] == "1":
data[k] = D[k]
if asyncwr:
tdata.append(data[:])
assert len(tdata) == len(rdata)
int_addr = int(addr, 2)
netpath = mempath[:]
if vcd:
netpath[-1] += "<%0*x>" % ((len(addr)+3) // 4, int_addr)
vcd.add_net([topmod] + netpath, width)
for i in range(steps_start, steps_stop):
if i not in mem_trace_data:
mem_trace_data[i] = list()
mem_trace_data[i].append((netpath, int_addr, "".join(tdata[i-steps_start])))
return mem_trace_data
def write_vcd_trace(steps_start, steps_stop, index):
filename = vcdfile.replace("%", index)
print_msg("Writing trace to VCD file: %s" % (filename))
@ -720,107 +937,7 @@ def write_vcd_trace(steps_start, steps_stop, index):
vcd.add_clock([topmod] + netpath, edge)
path_list.append(netpath)
mem_trace_data = dict()
for mempath in sorted(smt.hiermems(topmod)):
abits, width, rports, wports, asyncwr = smt.mem_info(topmod, mempath)
expr_id = list()
expr_list = list()
for i in range(steps_start, steps_stop):
for j in range(rports):
expr_id.append(('R', i-steps_start, j, 'A'))
expr_id.append(('R', i-steps_start, j, 'D'))
expr_list.append(smt.mem_expr(topmod, "s%d" % i, mempath, "R%dA" % j))
expr_list.append(smt.mem_expr(topmod, "s%d" % i, mempath, "R%dD" % j))
for j in range(wports):
expr_id.append(('W', i-steps_start, j, 'A'))
expr_id.append(('W', i-steps_start, j, 'D'))
expr_id.append(('W', i-steps_start, j, 'M'))
expr_list.append(smt.mem_expr(topmod, "s%d" % i, mempath, "W%dA" % j))
expr_list.append(smt.mem_expr(topmod, "s%d" % i, mempath, "W%dD" % j))
expr_list.append(smt.mem_expr(topmod, "s%d" % i, mempath, "W%dM" % j))
rdata = list()
wdata = list()
addrs = set()
for eid, edat in zip(expr_id, smt.get_list(expr_list)):
t, i, j, f = eid
if t == 'R':
c = rdata
elif t == 'W':
c = wdata
else:
assert False
while len(c) <= i:
c.append(list())
c = c[i]
while len(c) <= j:
c.append(dict())
c = c[j]
c[f] = smt.bv2bin(edat)
if f == 'A':
addrs.add(c[f])
for addr in addrs:
tdata = list()
data = ["x"] * width
gotread = False
if len(wdata) == 0 and len(rdata) != 0:
wdata = [[]] * len(rdata)
assert len(rdata) == len(wdata)
for i in range(len(wdata)):
if not gotread:
for j_data in rdata[i]:
if j_data["A"] == addr:
data = list(j_data["D"])
gotread = True
break
if gotread:
buf = data[:]
for ii in reversed(range(len(tdata))):
for k in range(width):
if tdata[ii][k] == "x":
tdata[ii][k] = buf[k]
else:
buf[k] = tdata[ii][k]
if not asyncwr:
tdata.append(data[:])
for j_data in wdata[i]:
if j_data["A"] != addr:
continue
D = j_data["D"]
M = j_data["M"]
for k in range(width):
if M[k] == "1":
data[k] = D[k]
if asyncwr:
tdata.append(data[:])
assert len(tdata) == len(rdata)
netpath = mempath[:]
netpath[-1] += "<%0*x>" % ((len(addr)+3) // 4, int(addr, 2))
vcd.add_net([topmod] + netpath, width)
for i in range(steps_start, steps_stop):
if i not in mem_trace_data:
mem_trace_data[i] = list()
mem_trace_data[i].append((netpath, "".join(tdata[i-steps_start])))
mem_trace_data = collect_mem_trace_data(steps_start, steps_stop, vcd)
for i in range(steps_start, steps_stop):
vcd.set_time(i)
@ -828,7 +945,7 @@ def write_vcd_trace(steps_start, steps_stop, index):
for path, value in zip(path_list, value_list):
vcd.set_net([topmod] + path, value)
if i in mem_trace_data:
for path, value in mem_trace_data[i]:
for path, addr, value in mem_trace_data[i]:
vcd.set_net([topmod] + path, value)
vcd.set_time(steps_stop)
@ -1072,8 +1189,72 @@ def write_constr_trace(steps_start, steps_stop, index):
for name, val in zip(pi_names, pi_values):
print("assume (= [%s%s] %s)" % (constr_prefix, ".".join(name), val), file=f)
def write_yw_trace(steps_start, steps_stop, index, allregs=False):
filename = outywfile.replace("%", index)
print_msg("Writing trace to Yosys witness file: %s" % (filename))
def write_trace(steps_start, steps_stop, index):
mem_trace_data = collect_mem_trace_data(steps_start, steps_stop)
with open(filename, "w") as f:
inits, seqs, clocks, mems = smt.hierwitness(topmod, allregs)
yw = WriteWitness(f, "smtbmc")
for clock in clocks:
yw.add_clock(clock["path"], clock["offset"], clock["type"])
for seq in seqs:
seq["sig"] = yw.add_sig(seq["path"], seq["offset"], seq["width"])
for init in inits:
init["sig"] = yw.add_sig(init["path"], init["offset"], init["width"], True)
inits = seqs + inits
mem_dict = {tuple(mem["smtpath"]): mem for mem in mems}
mem_init_values = []
for path, addr, value in mem_trace_data.get(0, ()):
json_mem = mem_dict.get(tuple(path))
if not json_mem:
continue
bit_addr = addr * json_mem["width"]
uninit_chunks = [(chunk["width"] + chunk["offset"], chunk["offset"]) for chunk in json_mem["uninitialized"]]
first_chunk_nr = bisect.bisect_left(uninit_chunks, (bit_addr + 1,))
for uninit_end, uninit_offset in uninit_chunks[first_chunk_nr:]:
assert uninit_end > bit_addr
if uninit_offset > bit_addr + json_mem["width"]:
break
word_path = (*json_mem["path"], f"\\[{addr}]")
overlap_start = max(uninit_offset - bit_addr, 0)
overlap_end = min(uninit_end - bit_addr, json_mem["width"])
overlap_bits = value[len(value)-overlap_end:len(value)-overlap_start]
sig = yw.add_sig(word_path, overlap_start, overlap_end - overlap_start, True)
mem_init_values.append((sig, overlap_bits.replace("x", "?")))
for k in range(steps_start, steps_stop):
step_values = WitnessValues()
if k == steps_start:
for sig, value in mem_init_values:
step_values[sig] = value
sigs = inits + seqs
else:
sigs = seqs
for sig in sigs:
step_values[sig["sig"]] = smt.bv2bin(smt.get(smt.net_expr(topmod, f"s{k}", sig["smtpath"])))
yw.step(step_values)
yw.end_trace()
def write_trace(steps_start, steps_stop, index, allregs=False):
if vcdfile is not None:
write_vcd_trace(steps_start, steps_stop, index)
@ -1083,6 +1264,9 @@ def write_trace(steps_start, steps_stop, index):
if outconstr is not None:
write_constr_trace(steps_start, steps_stop, index)
if outywfile is not None:
write_yw_trace(steps_start, steps_stop, index, allregs)
def print_failed_asserts_worker(mod, state, path, extrainfo, infomap, infokey=()):
assert mod in smt.modinfo
@ -1392,12 +1576,12 @@ if tempind:
print_msg("Temporal induction failed!")
print_anyconsts(num_steps)
print_failed_asserts(num_steps)
write_trace(step, num_steps+1, '%')
write_trace(step, num_steps+1, '%', allregs=True)
elif dumpall:
print_anyconsts(num_steps)
print_failed_asserts(num_steps)
write_trace(step, num_steps+1, "%d" % step)
write_trace(step, num_steps+1, "%d" % step, allregs=True)
else:
print_msg("Temporal induction successful.")