mirror of
				https://github.com/YosysHQ/yosys
				synced 2025-10-31 03:32:29 +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:
		
							parent
							
								
									96a1173598
								
							
						
					
					
						commit
						f041e36c6e
					
				
					 9 changed files with 983 additions and 113 deletions
				
			
		
							
								
								
									
										3
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
| -------------------------- | -------------------------- | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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 { | ||||||
|  |  | ||||||
|  | @ -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.") | ||||||
|  |  | ||||||
|  | @ -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
									
								
							
							
						
						
									
										90
									
								
								backends/smt2/witness.py
									
										
									
									
									
										Normal 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
									
								
							
							
						
						
									
										392
									
								
								backends/smt2/ywio.py
									
										
									
									
									
										Normal 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) | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue