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

3
.gitignore vendored
View File

@ -28,6 +28,9 @@ __pycache__
/yosys-smtbmc /yosys-smtbmc
/yosys-smtbmc.exe /yosys-smtbmc.exe
/yosys-smtbmc-script.py /yosys-smtbmc-script.py
/yosys-witness
/yosys-witness.exe
/yosys-witness-script.py
/yosys-filterlib /yosys-filterlib
/yosys-filterlib.exe /yosys-filterlib.exe
/kernel/*.pyh /kernel/*.pyh

View File

@ -11,6 +11,10 @@ Yosys 0.20 .. Yosys 0.20-dev
* Formal Verification * Formal Verification
- Added $anyinit cell to directly represent FFs with an unconstrained - Added $anyinit cell to directly represent FFs with an unconstrained
initialization value. These can be generated by the new formalff pass. initialization value. These can be generated by the new formalff pass.
- New JSON based yosys witness format for formal verification traces.
- yosys-smtbmc: Reading and writing of yosys witness traces.
- write_smt2: Emit inline metadata to support yosys witness trace.
- yosys-witness is a new tool to inspect and convert yosys witness traces.
Yosys 0.19 .. Yosys 0.20 Yosys 0.19 .. Yosys 0.20
-------------------------- --------------------------

View File

@ -7,6 +7,7 @@ ifneq ($(CONFIG),emcc)
# MSYS targets support yosys-smtbmc, but require a launcher script # MSYS targets support yosys-smtbmc, but require a launcher script
ifeq ($(CONFIG),$(filter $(CONFIG),msys2 msys2-64)) ifeq ($(CONFIG),$(filter $(CONFIG),msys2 msys2-64))
TARGETS += $(PROGRAM_PREFIX)yosys-smtbmc.exe $(PROGRAM_PREFIX)yosys-smtbmc-script.py TARGETS += $(PROGRAM_PREFIX)yosys-smtbmc.exe $(PROGRAM_PREFIX)yosys-smtbmc-script.py
TARGETS += $(PROGRAM_PREFIX)yosys-witness.exe $(PROGRAM_PREFIX)yosys-witness-script.py
# Needed to find the Python interpreter for yosys-smtbmc scripts. # Needed to find the Python interpreter for yosys-smtbmc scripts.
# Override if necessary, it is only used for msys2 targets. # Override if necessary, it is only used for msys2 targets.
PYTHON := $(shell cygpath -w -m $(PREFIX)/bin/python3) PYTHON := $(shell cygpath -w -m $(PREFIX)/bin/python3)
@ -15,18 +16,31 @@ $(PROGRAM_PREFIX)yosys-smtbmc-script.py: backends/smt2/smtbmc.py
$(P) sed -e 's|##yosys-sys-path##|sys.path += [os.path.dirname(os.path.realpath(__file__)) + p for p in ["/share/python3", "/../share/$(PROGRAM_PREFIX)yosys/python3"]]|;' \ $(P) sed -e 's|##yosys-sys-path##|sys.path += [os.path.dirname(os.path.realpath(__file__)) + p for p in ["/share/python3", "/../share/$(PROGRAM_PREFIX)yosys/python3"]]|;' \
-e "s|#!/usr/bin/env python3|#!$(PYTHON)|" < $< > $@ -e "s|#!/usr/bin/env python3|#!$(PYTHON)|" < $< > $@
$(PROGRAM_PREFIX)yosys-witness-script.py: backends/smt2/witness.py
$(P) sed -e 's|##yosys-sys-path##|sys.path += [os.path.dirname(os.path.realpath(__file__)) + p for p in ["/share/python3", "/../share/$(PROGRAM_PREFIX)yosys/python3"]]|;' \
-e "s|#!/usr/bin/env python3|#!$(PYTHON)|" < $< > $@
$(PROGRAM_PREFIX)yosys-smtbmc.exe: misc/launcher.c $(PROGRAM_PREFIX)yosys-smtbmc-script.py $(PROGRAM_PREFIX)yosys-smtbmc.exe: misc/launcher.c $(PROGRAM_PREFIX)yosys-smtbmc-script.py
$(P) $(CXX) -DGUI=0 -O -s -o $@ $< $(P) $(CXX) -DGUI=0 -O -s -o $@ $<
$(PROGRAM_PREFIX)yosys-witness.exe: misc/launcher.c $(PROGRAM_PREFIX)yosys-witness-script.py
$(P) $(CXX) -DGUI=0 -O -s -o $@ $<
# Other targets # Other targets
else else
TARGETS += $(PROGRAM_PREFIX)yosys-smtbmc TARGETS += $(PROGRAM_PREFIX)yosys-smtbmc $(PROGRAM_PREFIX)yosys-witness
$(PROGRAM_PREFIX)yosys-smtbmc: backends/smt2/smtbmc.py $(PROGRAM_PREFIX)yosys-smtbmc: backends/smt2/smtbmc.py
$(P) sed 's|##yosys-sys-path##|sys.path += [os.path.dirname(os.path.realpath(__file__)) + p for p in ["/share/python3", "/../share/$(PROGRAM_PREFIX)yosys/python3"]]|;' < $< > $@.new $(P) sed 's|##yosys-sys-path##|sys.path += [os.path.dirname(os.path.realpath(__file__)) + p for p in ["/share/python3", "/../share/$(PROGRAM_PREFIX)yosys/python3"]]|;' < $< > $@.new
$(Q) chmod +x $@.new $(Q) chmod +x $@.new
$(Q) mv $@.new $@ $(Q) mv $@.new $@
$(PROGRAM_PREFIX)yosys-witness: backends/smt2/witness.py
$(P) sed 's|##yosys-sys-path##|sys.path += [os.path.dirname(os.path.realpath(__file__)) + p for p in ["/share/python3", "/../share/$(PROGRAM_PREFIX)yosys/python3"]]|;' < $< > $@.new
$(Q) chmod +x $@.new
$(Q) mv $@.new $@
endif endif
$(eval $(call add_share_file,share/python3,backends/smt2/smtio.py)) $(eval $(call add_share_file,share/python3,backends/smt2/smtio.py))
$(eval $(call add_share_file,share/python3,backends/smt2/ywio.py))
endif endif
endif endif

View File

@ -23,6 +23,7 @@
#include "kernel/celltypes.h" #include "kernel/celltypes.h"
#include "kernel/log.h" #include "kernel/log.h"
#include "kernel/mem.h" #include "kernel/mem.h"
#include "libs/json11/json11.hpp"
#include <string> #include <string>
USING_YOSYS_NAMESPACE USING_YOSYS_NAMESPACE
@ -588,6 +589,9 @@ struct Smt2Worker
if (cell->type.in(ID($ff), ID($dff))) if (cell->type.in(ID($ff), ID($dff)))
{ {
registers.insert(cell); registers.insert(cell);
for (auto chunk : cell->getPort(ID::Q).chunks())
if (chunk.is_wire())
decls.push_back(witness_signal("reg", chunk.width, chunk.offset, "", idcounter, chunk.wire));
makebits(stringf("%s#%d", get_id(module), idcounter), GetSize(cell->getPort(ID::Q)), log_signal(cell->getPort(ID::Q))); makebits(stringf("%s#%d", get_id(module), idcounter), GetSize(cell->getPort(ID::Q)), log_signal(cell->getPort(ID::Q)));
register_bv(cell->getPort(ID::Q), idcounter++); register_bv(cell->getPort(ID::Q), idcounter++);
recursive_cells.erase(cell); recursive_cells.erase(cell);
@ -610,6 +614,12 @@ struct Smt2Worker
decls.push_back(stringf("; yosys-smt2-minimize %s#%d\n", get_id(module), idcounter)); decls.push_back(stringf("; yosys-smt2-minimize %s#%d\n", get_id(module), idcounter));
log("Wire %s is minimized\n", cell->getPort(QY).as_wire()->name.str().c_str()); log("Wire %s is minimized\n", cell->getPort(QY).as_wire()->name.str().c_str());
} }
bool init_only = cell->type.in(ID($anyconst), ID($anyinit), ID($allconst));
for (auto chunk : cell->getPort(QY).chunks())
if (chunk.is_wire())
decls.push_back(witness_signal(init_only ? "init" : "seq", chunk.width, chunk.offset, "", idcounter, chunk.wire));
makebits(stringf("%s#%d", get_id(module), idcounter), GetSize(cell->getPort(QY)), log_signal(cell->getPort(QY))); makebits(stringf("%s#%d", get_id(module), idcounter), GetSize(cell->getPort(QY)), log_signal(cell->getPort(QY)));
if (cell->type == ID($anyseq)) if (cell->type == ID($anyseq))
ex_input_eq.push_back(stringf(" (= (|%s#%d| state) (|%s#%d| other_state))", get_id(module), idcounter, get_id(module), idcounter)); ex_input_eq.push_back(stringf(" (= (|%s#%d| state) (|%s#%d| other_state))", get_id(module), idcounter, get_id(module), idcounter));
@ -760,6 +770,7 @@ struct Smt2Worker
log_error("Memory %s.%s has mixed clocked/nonclocked write ports. This is not supported by \"write_smt2\".\n", log_id(cell), log_id(module)); log_error("Memory %s.%s has mixed clocked/nonclocked write ports. This is not supported by \"write_smt2\".\n", log_id(cell), log_id(module));
decls.push_back(stringf("; yosys-smt2-memory %s %d %d %d %d %s\n", get_id(mem->memid), abits, mem->width, GetSize(mem->rd_ports), GetSize(mem->wr_ports), has_async_wr ? "async" : "sync")); decls.push_back(stringf("; yosys-smt2-memory %s %d %d %d %d %s\n", get_id(mem->memid), abits, mem->width, GetSize(mem->rd_ports), GetSize(mem->wr_ports), has_async_wr ? "async" : "sync"));
decls.push_back(witness_memory(get_id(mem->memid), cell, mem));
string memstate; string memstate;
if (has_async_wr) { if (has_async_wr) {
@ -852,6 +863,7 @@ struct Smt2Worker
if (m != nullptr) if (m != nullptr)
{ {
decls.push_back(stringf("; yosys-smt2-cell %s %s\n", get_id(cell->type), get_id(cell->name))); decls.push_back(stringf("; yosys-smt2-cell %s %s\n", get_id(cell->type), get_id(cell->name)));
decls.push_back(witness_cell(get_id(cell->name), cell));
string cell_state = stringf("(|%s_h %s| state)", get_id(module), get_id(cell->name)); string cell_state = stringf("(|%s_h %s| state)", get_id(module), get_id(cell->name));
for (auto &conn : cell->connections()) for (auto &conn : cell->connections())
@ -950,14 +962,19 @@ struct Smt2Worker
for (auto wire : module->wires()) { for (auto wire : module->wires()) {
bool is_register = false; bool is_register = false;
for (auto bit : SigSpec(wire)) bool contains_clock = false;
for (auto bit : SigSpec(wire)) {
if (reg_bits.count(bit)) if (reg_bits.count(bit))
is_register = true; is_register = true;
auto sig_bit = sigmap(bit);
if (clock_posedge.count(sig_bit) || clock_negedge.count(sig_bit))
contains_clock = true;
}
bool is_smtlib2_comb_expr = wire->has_attribute(ID::smtlib2_comb_expr); bool is_smtlib2_comb_expr = wire->has_attribute(ID::smtlib2_comb_expr);
if (is_smtlib2_comb_expr && !is_smtlib2_module) if (is_smtlib2_comb_expr && !is_smtlib2_module)
log_error("smtlib2_comb_expr is only valid in a module with the smtlib2_module attribute: wire %s.%s", log_id(module), log_error("smtlib2_comb_expr is only valid in a module with the smtlib2_module attribute: wire %s.%s", log_id(module),
log_id(wire)); log_id(wire));
if (wire->port_id || is_register || wire->get_bool_attribute(ID::keep) || (wiresmode && wire->name.isPublic())) { if (wire->port_id || is_register || contains_clock || wire->get_bool_attribute(ID::keep) || (wiresmode && wire->name.isPublic())) {
RTLIL::SigSpec sig = sigmap(wire); RTLIL::SigSpec sig = sigmap(wire);
std::vector<std::string> comments; std::vector<std::string> comments;
if (wire->port_input) if (wire->port_input)
@ -968,9 +985,20 @@ struct Smt2Worker
comments.push_back(stringf("; yosys-smt2-register %s %d\n", get_id(wire), wire->width)); comments.push_back(stringf("; yosys-smt2-register %s %d\n", get_id(wire), wire->width));
if (wire->get_bool_attribute(ID::keep) || (wiresmode && wire->name.isPublic())) if (wire->get_bool_attribute(ID::keep) || (wiresmode && wire->name.isPublic()))
comments.push_back(stringf("; yosys-smt2-wire %s %d\n", get_id(wire), wire->width)); comments.push_back(stringf("; yosys-smt2-wire %s %d\n", get_id(wire), wire->width));
if (GetSize(wire) == 1 && (clock_posedge.count(sig) || clock_negedge.count(sig))) if (contains_clock && GetSize(wire) == 1 && (clock_posedge.count(sig) || clock_negedge.count(sig)))
comments.push_back(stringf("; yosys-smt2-clock %s%s%s\n", get_id(wire), comments.push_back(stringf("; yosys-smt2-clock %s%s%s\n", get_id(wire),
clock_posedge.count(sig) ? " posedge" : "", clock_negedge.count(sig) ? " negedge" : "")); clock_posedge.count(sig) ? " posedge" : "", clock_negedge.count(sig) ? " negedge" : ""));
if (contains_clock) {
for (int i = 0; i < GetSize(sig); i++) {
bool is_posedge = clock_posedge.count(sig[i]);
bool is_negedge = clock_negedge.count(sig[i]);
if (is_posedge != is_negedge)
comments.push_back(witness_signal(
is_posedge ? "posedge" : "negedge", 1, i, get_id(wire), -1, wire));
}
}
if (wire->port_input)
comments.push_back(witness_signal("input", wire->width, 0, get_id(wire), -1, wire));
std::string smtlib2_comb_expr; std::string smtlib2_comb_expr;
if (is_smtlib2_comb_expr) { if (is_smtlib2_comb_expr) {
smtlib2_comb_expr = smtlib2_comb_expr =
@ -980,6 +1008,8 @@ struct Smt2Worker
if (!bvmode && GetSize(sig) > 1) if (!bvmode && GetSize(sig) > 1)
log_error("smtlib2_comb_expr is unsupported on multi-bit wires when -nobv is specified: wire %s.%s", log_error("smtlib2_comb_expr is unsupported on multi-bit wires when -nobv is specified: wire %s.%s",
log_id(module), log_id(wire)); log_id(module), log_id(wire));
comments.push_back(witness_signal("blackbox", wire->width, 0, get_id(wire), -1, wire));
} }
auto &out_decls = is_smtlib2_comb_expr ? smtlib2_decls : decls; auto &out_decls = is_smtlib2_comb_expr ? smtlib2_decls : decls;
if (bvmode && GetSize(sig) > 1) { if (bvmode && GetSize(sig) > 1) {
@ -1447,6 +1477,90 @@ struct Smt2Worker
f << "true)"; f << "true)";
f << stringf(" ; end of module %s\n", get_id(module)); f << stringf(" ; end of module %s\n", get_id(module));
} }
template<class T> static std::vector<std::string> witness_path(T *obj) {
std::vector<std::string> path;
if (obj->name.isPublic()) {
auto hdlname = obj->get_string_attribute(ID::hdlname);
for (auto token : split_tokens(hdlname))
path.push_back("\\" + token);
}
if (path.empty())
path.push_back(obj->name.str());
return path;
}
std::string witness_signal(const char *type, int width, int offset, const std::string &smtname, int smtid, RTLIL::Wire *wire)
{
std::vector<std::string> hiername;
const char *wire_name = wire->name.c_str();
if (wire_name[0] == '\\') {
auto hdlname = wire->get_string_attribute(ID::hdlname);
for (auto token : split_tokens(hdlname))
hiername.push_back("\\" + token);
}
if (hiername.empty())
hiername.push_back(wire->name.str());
std::string line = "; yosys-smt2-witness ";
(json11::Json { json11::Json::object {
{ "type", type },
{ "offset", offset },
{ "width", width },
{ "smtname", smtname.empty() ? json11::Json(smtid) : json11::Json(smtname) },
{ "path", witness_path(wire) },
}}).dump(line);
line += "\n";
return line;
}
std::string witness_cell(const char *smtname, RTLIL::Cell *cell)
{
std::string line = "; yosys-smt2-witness ";
(json11::Json {json11::Json::object {
{ "type", "cell" },
{ "smtname", smtname },
{ "path", witness_path(cell) },
}}).dump(line);
line += "\n";
return line;
}
std::string witness_memory(const char *smtname, RTLIL::Cell *cell, Mem *mem)
{
json11::Json::array uninitialized;
auto init_data = mem->get_init_data();
int cursor = 0;
while (cursor < init_data.size()) {
while (cursor < init_data.size() && init_data[cursor] != State::Sx)
cursor++;
int offset = cursor;
while (cursor < init_data.size() && init_data[cursor] == State::Sx)
cursor++;
int width = cursor - offset;
if (width)
uninitialized.push_back(json11::Json::object {
{"width", width},
{"offset", offset},
});
}
std::string line = "; yosys-smt2-witness ";
(json11::Json { json11::Json::object {
{ "type", "mem" },
{ "width", mem->width },
{ "size", mem->size },
{ "rom", mem->wr_ports.empty() },
{ "statebv", statebv },
{ "smtname", smtname },
{ "uninitialized", uninitialized },
{ "path", witness_path(cell) },
}}).dump(line);
line += "\n";
return line;
}
}; };
struct Smt2Backend : public Backend { struct Smt2Backend : public Backend {

View File

@ -17,9 +17,10 @@
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # 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## ##yosys-sys-path##
from smtio import SmtIo, SmtOpts, MkVcd from smtio import SmtIo, SmtOpts, MkVcd
from ywio import ReadWitness, WriteWitness, WitnessValues
from collections import defaultdict from collections import defaultdict
got_topt = False got_topt = False
@ -28,6 +29,8 @@ step_size = 1
num_steps = 20 num_steps = 20
append_steps = 0 append_steps = 0
vcdfile = None vcdfile = None
inywfile = None
outywfile = None
cexfile = None cexfile = None
aimfile = None aimfile = None
aiwfile = None aiwfile = None
@ -94,6 +97,9 @@ def usage():
the AIGER witness file does not include the status and the AIGER witness file does not include the status and
properties lines. properties lines.
--yw <yosys_witness_filename>
read a Yosys witness.
--btorwit <btor_witness_filename> --btorwit <btor_witness_filename>
read a BTOR witness. read a BTOR witness.
@ -121,6 +127,9 @@ def usage():
(hint: use 'write_smt2 -wires' for maximum (hint: use 'write_smt2 -wires' for maximum
coverage of signals in generated VCD file) coverage of signals in generated VCD file)
--dump-yw <yw_filename>
write trace as a Yosys witness trace
--dump-vlogtb <verilog_filename> --dump-vlogtb <verilog_filename>
write trace as Verilog test bench write trace as Verilog test bench
@ -167,8 +176,8 @@ def usage():
try: try:
opts, args = getopt.getopt(sys.argv[1:], so.shortopts + "t:igcm:", so.longopts + opts, args = getopt.getopt(sys.argv[1:], so.shortopts + "t:igcm:", so.longopts +
["final-only", "assume-skipped=", "smtc=", "cex=", "aig=", "aig-noheader", "btorwit=", "presat", ["final-only", "assume-skipped=", "smtc=", "cex=", "aig=", "aig-noheader", "yw=", "btorwit=", "presat",
"dump-vcd=", "dump-vlogtb=", "vlogtb-top=", "dump-smtc=", "dump-all", "noinfo", "append=", "dump-vcd=", "dump-yw=", "dump-vlogtb=", "vlogtb-top=", "dump-smtc=", "dump-all", "noinfo", "append=",
"smtc-init", "smtc-top=", "noinit", "binary", "keep-going"]) "smtc-init", "smtc-top=", "noinit", "binary", "keep-going"])
except: except:
usage() usage()
@ -204,10 +213,14 @@ for o, a in opts:
aiwfile = a + ".aiw" aiwfile = a + ".aiw"
elif o == "--aig-noheader": elif o == "--aig-noheader":
aigheader = False aigheader = False
elif o == "--yw":
inywfile = a
elif o == "--btorwit": elif o == "--btorwit":
btorwitfile = a btorwitfile = a
elif o == "--dump-vcd": elif o == "--dump-vcd":
vcdfile = a vcdfile = a
elif o == "--dump-yw":
outywfile = a
elif o == "--dump-vlogtb": elif o == "--dump-vlogtb":
vlogtbfile = a vlogtbfile = a
elif o == "--vlogtb-top": elif o == "--vlogtb-top":
@ -602,6 +615,101 @@ if aimfile is not None:
num_steps = max(num_steps, step+2) num_steps = max(num_steps, step+2)
step += 1 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: if btorwitfile is not None:
with open(btorwitfile, "r") as f: with open(btorwitfile, "r") as f:
step = None step = None
@ -699,6 +807,115 @@ if btorwitfile is not None:
skip_steps = step skip_steps = step
num_steps = step+1 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): def write_vcd_trace(steps_start, steps_stop, index):
filename = vcdfile.replace("%", index) filename = vcdfile.replace("%", index)
print_msg("Writing trace to VCD file: %s" % (filename)) 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) vcd.add_clock([topmod] + netpath, edge)
path_list.append(netpath) path_list.append(netpath)
mem_trace_data = dict() mem_trace_data = collect_mem_trace_data(steps_start, steps_stop, vcd)
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])))
for i in range(steps_start, steps_stop): for i in range(steps_start, steps_stop):
vcd.set_time(i) 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): for path, value in zip(path_list, value_list):
vcd.set_net([topmod] + path, value) vcd.set_net([topmod] + path, value)
if i in mem_trace_data: 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_net([topmod] + path, value)
vcd.set_time(steps_stop) 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): for name, val in zip(pi_names, pi_values):
print("assume (= [%s%s] %s)" % (constr_prefix, ".".join(name), val), file=f) 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: if vcdfile is not None:
write_vcd_trace(steps_start, steps_stop, index) 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: if outconstr is not None:
write_constr_trace(steps_start, steps_stop, index) 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=()): def print_failed_asserts_worker(mod, state, path, extrainfo, infomap, infokey=()):
assert mod in smt.modinfo assert mod in smt.modinfo
@ -1392,12 +1576,12 @@ if tempind:
print_msg("Temporal induction failed!") print_msg("Temporal induction failed!")
print_anyconsts(num_steps) print_anyconsts(num_steps)
print_failed_asserts(num_steps) print_failed_asserts(num_steps)
write_trace(step, num_steps+1, '%') write_trace(step, num_steps+1, '%', allregs=True)
elif dumpall: elif dumpall:
print_anyconsts(num_steps) print_anyconsts(num_steps)
print_failed_asserts(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: else:
print_msg("Temporal induction successful.") print_msg("Temporal induction successful.")

View File

@ -16,7 +16,7 @@
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
# #
import sys, re, os, signal import sys, re, os, signal, json
import subprocess import subprocess
if os.name == "posix": if os.name == "posix":
import resource import resource
@ -108,6 +108,7 @@ class SmtModInfo:
self.allconsts = dict() self.allconsts = dict()
self.allseqs = dict() self.allseqs = dict()
self.asize = dict() self.asize = dict()
self.witness = []
class SmtIo: class SmtIo:
@ -587,6 +588,11 @@ class SmtIo:
self.modinfo[self.curmod].allseqs[fields[2]] = (fields[4], None if len(fields) <= 5 else fields[5]) self.modinfo[self.curmod].allseqs[fields[2]] = (fields[4], None if len(fields) <= 5 else fields[5])
self.modinfo[self.curmod].asize[fields[2]] = int(fields[3]) self.modinfo[self.curmod].asize[fields[2]] = int(fields[3])
if fields[1] == "yosys-smt2-witness":
data = json.loads(stmt.split(None, 2)[2])
if data.get("type") in ["cell", "mem", "posedge", "negedge", "input", "reg", "init", "seq", "blackbox"]:
self.modinfo[self.curmod].witness.append(data)
def hiernets(self, top, regs_only=False): def hiernets(self, top, regs_only=False):
def hiernets_worker(nets, mod, cursor): def hiernets_worker(nets, mod, cursor):
for netname in sorted(self.modinfo[mod].wsize.keys()): for netname in sorted(self.modinfo[mod].wsize.keys()):
@ -658,6 +664,57 @@ class SmtIo:
hiermems_worker(mems, top, []) hiermems_worker(mems, top, [])
return mems return mems
def hierwitness(self, top, allregs=False, blackbox=True):
init_witnesses = []
seq_witnesses = []
clk_witnesses = []
mem_witnesses = []
def absolute(path, cursor, witness):
return {
**witness,
"path": path + tuple(witness["path"]),
"smtpath": cursor + [witness["smtname"]],
}
for witness in self.modinfo[top].witness:
if witness["type"] == "input":
seq_witnesses.append(absolute((), [], witness))
if witness["type"] in ("posedge", "negedge"):
clk_witnesses.append(absolute((), [], witness))
init_types = ["init"]
if allregs:
init_types.append("reg")
seq_types = ["seq"]
if blackbox:
seq_types.append("blackbox")
def worker(mod, path, cursor):
cell_paths = {}
for witness in self.modinfo[mod].witness:
if witness["type"] in init_types:
init_witnesses.append(absolute(path, cursor, witness))
if witness["type"] in seq_types:
seq_witnesses.append(absolute(path, cursor, witness))
if witness["type"] == "mem":
if allregs and not witness["rom"]:
width, size = witness["width"], witness["size"]
witness = {**witness, "uninitialized": {"width": width * size, "offset": 0}}
if not witness["uninitialized"]:
continue
mem_witnesses.append(absolute(path, cursor, witness))
if witness["type"] == "cell":
cell_paths[witness["smtname"]] = tuple(witness["path"])
for cellname, celltype in sorted(self.modinfo[mod].cells.items()):
worker(celltype, path + cell_paths.get(cellname, ("?" + cellname,)), cursor + [cellname])
worker(top, (), [])
return init_witnesses, seq_witnesses, clk_witnesses, mem_witnesses
def read(self): def read(self):
stmt = [] stmt = []
count_brackets = 0 count_brackets = 0
@ -887,6 +944,8 @@ class SmtIo:
assert mod in self.modinfo assert mod in self.modinfo
if path[0] == "": if path[0] == "":
return base return base
if isinstance(path[0], int):
return "(|%s#%d| %s)" % (mod, path[0], base)
if path[0] in self.modinfo[mod].cells: if path[0] in self.modinfo[mod].cells:
return "(|%s_h %s| %s)" % (mod, path[0], base) return "(|%s_h %s| %s)" % (mod, path[0], base)
if path[0] in self.modinfo[mod].wsize: if path[0] in self.modinfo[mod].wsize:
@ -909,6 +968,8 @@ class SmtIo:
mod = self.modinfo[mod].cells[net_path[i]] mod = self.modinfo[mod].cells[net_path[i]]
assert mod in self.modinfo assert mod in self.modinfo
if isinstance(net_path[-1], int):
return None
assert net_path[-1] in self.modinfo[mod].wsize assert net_path[-1] in self.modinfo[mod].wsize
return self.modinfo[mod].wsize[net_path[-1]] return self.modinfo[mod].wsize[net_path[-1]]

90
backends/smt2/witness.py Normal file
View File

@ -0,0 +1,90 @@
#!/usr/bin/env python3
#
# yosys -- Yosys Open SYnthesis Suite
#
# Copyright (C) 2022 Jannis Harder <jix@yosyshq.com> <me@jix.one>
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
import os, sys
##yosys-sys-path##
import click
from ywio import ReadWitness, WriteWitness, WitnessSig
@click.group()
def cli():
pass
@cli.command(help="""
Display a Yosys witness trace in a human readable format.
""")
@click.argument("input", type=click.File("r"))
def display(input):
click.echo(f"Reading Yosys witness trace {input.name!r}...")
inyw = ReadWitness(input)
def output():
yield click.style("*** RTLIL bit-order below may differ from source level declarations ***", fg="red")
if inyw.clocks:
yield click.style("=== Clock Signals ===", fg="blue")
for clock in inyw.clocks:
yield f" {clock['edge']} {WitnessSig(clock['path'], clock['offset']).pretty()}"
for t, values in inyw.steps():
if t:
yield click.style(f"=== Step {t} ===", fg="blue")
else:
yield click.style("=== Initial State ===", fg="blue")
step_prefix = click.style(f"#{t}", fg="bright_black")
signals, missing = values.present_signals(inyw.sigmap)
assert not missing
for sig in signals:
display_bits = values[sig].replace("?", click.style("?", fg="bright_black"))
yield f" {step_prefix} {sig.pretty()} = {display_bits}"
click.echo_via_pager([line + "\n" for line in output()])
@cli.command(help="""
Transform a Yosys witness trace.
Currently no transformations are implemented, so it is only useful for testing.
""")
@click.argument("input", type=click.File("r"))
@click.argument("output", type=click.File("w"))
def yw2yw(input, output):
click.echo(f"Copying yosys witness trace from {input.name!r} to {output.name!r}...")
inyw = ReadWitness(input)
outyw = WriteWitness(output, "yosys-witness yw2yw")
for clock in inyw.clocks:
outyw.add_clock(clock["path"], clock["offset"], clock["edge"])
for sig in inyw.signals:
outyw.add_sig(sig.path, sig.offset, sig.width, sig.init_only)
for t, values in inyw.steps():
outyw.step(values)
outyw.end_trace()
click.echo(f"Copied {outyw.t + 1} time steps.")
if __name__ == "__main__":
cli()

392
backends/smt2/ywio.py Normal file
View File

@ -0,0 +1,392 @@
#
# yosys -- Yosys Open SYnthesis Suite
#
# Copyright (C) 2022 Jannis Harder <jix@yosyshq.com> <me@jix.one>
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
import json, re
from functools import total_ordering
class PrettyJson:
def __init__(self, f):
self.f = f
self.indent = 0
self.state = ["value"]
def line(self):
indent = len(self.state) - bool(self.state and self.state[-1] == "value")
print("\n", end=" " * (2 * indent), file=self.f)
def raw(self, str):
print(end=str, file=self.f)
def begin_object(self):
self.begin_value()
self.raw("{")
self.state.append("object_first")
def begin_array(self):
self.begin_value()
self.raw("[")
self.state.append("array_first")
def end_object(self):
state = self.state.pop()
if state == "object":
self.line()
else:
assert state == "object_first"
self.raw("}")
self.end_value()
def end_array(self):
state = self.state.pop()
if state == "array":
self.line()
else:
assert state == "array_first"
self.raw("]")
self.end_value()
def name(self, name):
if self.state[-1] == "object_first":
self.state[-1] = "object"
else:
self.raw(",")
self.line()
json.dump(str(name), self.f)
self.raw(": ")
self.state.append("value")
def begin_value(self):
if self.state[-1] == "array_first":
self.line()
self.state[-1] = "array"
elif self.state[-1] == "array":
self.raw(",")
self.line()
else:
assert self.state.pop() == "value"
def end_value(self):
if not self.state:
print(file=self.f, flush=True)
def value(self, value):
self.begin_value()
json.dump(value, self.f)
self.end_value()
def entry(self, name, value):
self.name(name)
self.value(value)
def object(self, entries=None):
if isinstance(entries, dict):
entries = dict.items()
self.begin_object()
for name, value in entries:
self.entry(name, value)
self.end_object()
def array(self, values=None):
self.begin_array()
for value in values:
self.value(value)
self.end_array()
addr_re = re.compile(r'\\\[[0-9]+\]$')
public_name_re = re.compile(r"\\([a-zA-Z_][a-zA-Z0-9_]*(\[[0-9]+\])?|\[[0-9]+\])$")
def pretty_name(id):
if public_name_re.match(id):
return id.lstrip("\\")
else:
return id
def pretty_path(path):
out = ""
for name in path:
name = pretty_name(name)
if name.startswith("["):
out += name
continue
if out:
out += "."
if name.startswith("\\") or name.startswith("$"):
out += name + " "
else:
out += name
return out
@total_ordering
class WitnessSig:
def __init__(self, path, offset, width=1, init_only=False):
path = tuple(path)
self.path, self.width, self.offset, self.init_only = path, width, offset, init_only
self.memory_path = None
self.memory_addr = None
sort_path = path
sort_id = -1
if path and addr_re.match(path[-1]):
self.memory_path = sort_path = path[:-1]
self.memory_addr = sort_id = int(path[-1][2:-1])
self.sort_key = (init_only, sort_path, sort_id, offset, width)
def bits(self):
return ((self.path, i) for i in range(self.offset, self.offset + self.width))
def rev_bits(self):
return ((self.path, i) for i in reversed(range(self.offset, self.offset + self.width)))
def pretty(self):
if self.width > 1:
last_offset = self.offset + self.width - 1
return f"{pretty_path(self.path)}[{last_offset}:{self.offset}]"
else:
return f"{pretty_path(self.path)}[{self.offset}]"
def __eq__(self):
return self.sort_key
def __hash__(self):
return hash(self.sort_key)
def __lt__(self, other):
return self.sort_key < other.sort_key
def coalesce_signals(signals):
bits = {}
for sig in signals:
for bit in sig.bits():
if sig.init_only:
bits.setdefault(bit, False)
else:
bits[bit] = True
active = None
out = []
for bit, not_init in sorted(bits.items()):
if active:
if active[0] == bit[0] and active[2] == bit[1] and active[3] == not_init:
active[2] += 1
else:
out.append(WitnessSig(active[0], active[1], active[2] - active[1], not active[3]))
active = None
if active is None:
active = [bit[0], bit[1], bit[1] + 1, not_init]
if active:
out.append(WitnessSig(active[0], active[1], active[2] - active[1], not active[3]))
return sorted(out)
class WitnessSigMap:
def __init__(self, signals=[]):
self.signals = []
self.id_to_bit = []
self.bit_to_id = {}
self.bit_to_sig = {}
for sig in signals:
self.add_signal(sig)
def add_signal(self, sig):
self.signals.append(sig)
for bit in sig.bits():
self.add_bit(bit)
self.bit_to_sig[bit] = sig
def add_bit(self, bit, id=None):
if id is None:
id = len(self.id_to_bit)
self.id_to_bit.append(bit)
else:
if len(self.id_to_bit) <= id:
self.id_to_bit += [None] * (id - len(self.id_to_bit) + 1)
self.id_to_bit[id] = bit
self.bit_to_id[bit] = id
class WitnessValues:
def __init__(self):
self.values = {}
def __setitem__(self, key, value):
if isinstance(key, tuple) and len(key) == 2:
if value != "?":
assert isinstance(value, str)
assert len(value) == 1
self.values[key] = value
else:
assert isinstance(key, WitnessSig)
assert key.width == len(value)
if isinstance(value, str):
value = reversed(value)
for bit, bit_value in zip(key.bits(), value):
if bit_value != "?":
self.values[bit] = bit_value
def __getitem__(self, key):
if isinstance(key, tuple) and len(key) == 2:
return self.values.get(key, "?")
else:
assert isinstance(key, WitnessSig)
return "".join([self.values.get(bit, "?") for bit in key.rev_bits()])
def pack_present(self, sigmap):
missing = []
max_id = max((sigmap.bit_to_id.get(bit, -1) for bit in self.values), default=-1)
vector = ["?"] * (max_id + 1)
for bit, bit_value in self.values.items():
id = sigmap.bit_to_id.get(bit, - 1)
if id < 0:
missing.append(bit)
else:
vector[max_id - sigmap.bit_to_id[bit]] = bit_value
return "".join(vector), missing
def pack(self, sigmap):
packed, missing = self.pack_present(sigmap)
if missing:
raise RuntimeError(f"Cannot pack bits {missing!r}")
return packed
def unpack(self, sigmap, bits):
for i, bit_value in enumerate(reversed(bits)):
if bit_value != "?":
self.values[sigmap.id_to_bit[i]] = bit_value
def present_signals(self, sigmap):
signals = set(sigmap.bit_to_sig.get(bit) for bit in self.values)
missing_signals = None in signals
if missing_signals:
signals.discard(None)
return sorted(signals), missing_signals
class WriteWitness:
def __init__(self, f, generator):
self.out = PrettyJson(f)
self.t = 0
self.header_written = False
self.clocks = []
self.signals = []
self.out.begin_object()
self.out.entry("format", "Yosys Witness Trace")
self.out.entry("generator", generator)
def add_clock(self, path, offset, edge):
assert not self.header_written
self.clocks.append({
"path": path,
"edge": edge,
"offset": offset,
})
def add_sig(self, path, offset, width=1, init_only=False):
assert not self.header_written
sig = WitnessSig(path, offset, width, init_only)
self.signals.append(sig)
return sig
def write_header(self):
assert not self.header_written
self.header_written = True
self.out.name("clocks")
self.out.array(self.clocks)
self.signals = coalesce_signals(self.signals)
self.sigmap = WitnessSigMap(self.signals)
self.out.name("signals")
self.out.array({
"path": sig.path,
"width": sig.width,
"offset": sig.offset,
"init_only": sig.init_only,
} for sig in self.signals)
self.out.name("steps")
self.out.begin_array()
def step(self, values):
if not self.header_written:
self.write_header()
self.out.value({"bits": values.pack(self.sigmap)})
self.t += 1
def end_trace(self):
if not self.header_written:
self.write_header()
self.out.end_array()
self.out.end_object()
class ReadWitness:
def __init__(self, f):
data = json.load(f)
if not isinstance(data, dict):
data = {}
data_format = data.get("format", "Unknown Format")
if data_format != "Yosys Witness Trace":
raise ValueError(f"unsupported format {data_format!r}")
self.clocks = data["clocks"]
for clock in self.clocks:
clock["path"] = tuple(clock["path"])
self.signals = [
WitnessSig(sig["path"], sig["offset"], sig["width"], sig["init_only"])
for sig in data["signals"]
]
self.sigmap = WitnessSigMap(self.signals)
self.bits = [step["bits"] for step in data["steps"]]
def step(self, t):
values = WitnessValues()
values.unpack(self.sigmap, self.bits[t])
return values
def steps(self):
for i in range(len(self.bits)):
yield i, self.step(i)
def __len__(self):
return len(self.bits)

View File

@ -4,11 +4,14 @@
(declare-fun |smtlib2_is| (|smtlib2_s|) Bool) (declare-fun |smtlib2_is| (|smtlib2_s|) Bool)
(declare-fun |smtlib2#0| (|smtlib2_s|) (_ BitVec 8)) ; \a (declare-fun |smtlib2#0| (|smtlib2_s|) (_ BitVec 8)) ; \a
; yosys-smt2-input a 8 ; yosys-smt2-input a 8
; yosys-smt2-witness {"offset": 0, "path": ["\\a"], "smtname": "a", "type": "input", "width": 8}
(define-fun |smtlib2_n a| ((state |smtlib2_s|)) (_ BitVec 8) (|smtlib2#0| state)) (define-fun |smtlib2_n a| ((state |smtlib2_s|)) (_ BitVec 8) (|smtlib2#0| state))
(declare-fun |smtlib2#1| (|smtlib2_s|) (_ BitVec 8)) ; \b (declare-fun |smtlib2#1| (|smtlib2_s|) (_ BitVec 8)) ; \b
; yosys-smt2-input b 8 ; yosys-smt2-input b 8
; yosys-smt2-witness {"offset": 0, "path": ["\\b"], "smtname": "b", "type": "input", "width": 8}
(define-fun |smtlib2_n b| ((state |smtlib2_s|)) (_ BitVec 8) (|smtlib2#1| state)) (define-fun |smtlib2_n b| ((state |smtlib2_s|)) (_ BitVec 8) (|smtlib2#1| state))
; yosys-smt2-output add 8 ; yosys-smt2-output add 8
; yosys-smt2-witness {"offset": 0, "path": ["\\add"], "smtname": "add", "type": "blackbox", "width": 8}
(define-fun |smtlib2_n add| ((state |smtlib2_s|)) (_ BitVec 8) (let ( (define-fun |smtlib2_n add| ((state |smtlib2_s|)) (_ BitVec 8) (let (
(|a| (|smtlib2_n a| state)) (|a| (|smtlib2_n a| state))
(|b| (|smtlib2_n b| state)) (|b| (|smtlib2_n b| state))
@ -16,6 +19,7 @@
(bvadd a b) (bvadd a b)
)) ))
; yosys-smt2-output eq 1 ; yosys-smt2-output eq 1
; yosys-smt2-witness {"offset": 0, "path": ["\\eq"], "smtname": "eq", "type": "blackbox", "width": 1}
(define-fun |smtlib2_n eq| ((state |smtlib2_s|)) Bool (let ( (define-fun |smtlib2_n eq| ((state |smtlib2_s|)) Bool (let (
(|a| (|smtlib2_n a| state)) (|a| (|smtlib2_n a| state))
(|b| (|smtlib2_n b| state)) (|b| (|smtlib2_n b| state))
@ -23,6 +27,7 @@
(= a b) (= a b)
)) ))
; yosys-smt2-output sub 8 ; yosys-smt2-output sub 8
; yosys-smt2-witness {"offset": 0, "path": ["\\sub"], "smtname": "sub", "type": "blackbox", "width": 8}
(define-fun |smtlib2_n sub| ((state |smtlib2_s|)) (_ BitVec 8) (let ( (define-fun |smtlib2_n sub| ((state |smtlib2_s|)) (_ BitVec 8) (let (
(|a| (|smtlib2_n a| state)) (|a| (|smtlib2_n a| state))
(|b| (|smtlib2_n b| state)) (|b| (|smtlib2_n b| state))
@ -38,13 +43,16 @@
(declare-sort |uut_s| 0) (declare-sort |uut_s| 0)
(declare-fun |uut_is| (|uut_s|) Bool) (declare-fun |uut_is| (|uut_s|) Bool)
; yosys-smt2-cell smtlib2 s ; yosys-smt2-cell smtlib2 s
; yosys-smt2-witness {"path": ["\\s"], "smtname": "s", "type": "cell"}
(declare-fun |uut#0| (|uut_s|) (_ BitVec 8)) ; \add (declare-fun |uut#0| (|uut_s|) (_ BitVec 8)) ; \add
(declare-fun |uut#1| (|uut_s|) Bool) ; \eq (declare-fun |uut#1| (|uut_s|) Bool) ; \eq
(declare-fun |uut#2| (|uut_s|) (_ BitVec 8)) ; \sub (declare-fun |uut#2| (|uut_s|) (_ BitVec 8)) ; \sub
(declare-fun |uut_h s| (|uut_s|) |smtlib2_s|) (declare-fun |uut_h s| (|uut_s|) |smtlib2_s|)
; yosys-smt2-anyconst uut#3 8 smtlib2_module.v:14.17-14.26 ; yosys-smt2-anyconst uut#3 8 smtlib2_module.v:14.17-14.26
; yosys-smt2-witness {"offset": 0, "path": ["\\a"], "smtname": 3, "type": "init", "width": 8}
(declare-fun |uut#3| (|uut_s|) (_ BitVec 8)) ; \a (declare-fun |uut#3| (|uut_s|) (_ BitVec 8)) ; \a
; yosys-smt2-anyconst uut#4 8 smtlib2_module.v:14.32-14.41 ; yosys-smt2-anyconst uut#4 8 smtlib2_module.v:14.32-14.41
; yosys-smt2-witness {"offset": 0, "path": ["\\b"], "smtname": 4, "type": "init", "width": 8}
(declare-fun |uut#4| (|uut_s|) (_ BitVec 8)) ; \b (declare-fun |uut#4| (|uut_s|) (_ BitVec 8)) ; \b
(define-fun |uut#5| ((state |uut_s|)) (_ BitVec 8) (bvadd (|uut#3| state) (|uut#4| state))) ; \add2 (define-fun |uut#5| ((state |uut_s|)) (_ BitVec 8) (bvadd (|uut#3| state) (|uut#4| state))) ; \add2
(define-fun |uut#6| ((state |uut_s|)) Bool (= (|uut#0| state) (|uut#5| state))) ; $0$formal$smtlib2_module.v:28$1_CHECK[0:0]$9 (define-fun |uut#6| ((state |uut_s|)) Bool (= (|uut#0| state) (|uut#5| state))) ; $0$formal$smtlib2_module.v:28$1_CHECK[0:0]$9