mirror of https://github.com/YosysHQ/yosys.git
Merge pull request #3624 from jix/sim_yw
Changes to support SBY trace generation with the sim command
This commit is contained in:
commit
8180cc4325
2
Makefile
2
Makefile
|
@ -653,7 +653,7 @@ ifneq ($(ABCEXTERNAL),)
|
||||||
kernel/yosys.o: CXXFLAGS += -DABCEXTERNAL='"$(ABCEXTERNAL)"'
|
kernel/yosys.o: CXXFLAGS += -DABCEXTERNAL='"$(ABCEXTERNAL)"'
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
OBJS += kernel/cellaigs.o kernel/celledges.o kernel/satgen.o kernel/qcsat.o kernel/mem.o kernel/ffmerge.o kernel/ff.o
|
OBJS += kernel/cellaigs.o kernel/celledges.o kernel/satgen.o kernel/qcsat.o kernel/mem.o kernel/ffmerge.o kernel/ff.o kernel/yw.o kernel/json.o
|
||||||
ifeq ($(ENABLE_ZLIB),1)
|
ifeq ($(ENABLE_ZLIB),1)
|
||||||
OBJS += kernel/fstdata.o
|
OBJS += kernel/fstdata.o
|
||||||
endif
|
endif
|
||||||
|
|
|
@ -19,6 +19,8 @@
|
||||||
|
|
||||||
#include "kernel/yosys.h"
|
#include "kernel/yosys.h"
|
||||||
#include "kernel/sigtools.h"
|
#include "kernel/sigtools.h"
|
||||||
|
#include "kernel/json.h"
|
||||||
|
#include "kernel/yw.h"
|
||||||
#include "libs/json11/json11.hpp"
|
#include "libs/json11/json11.hpp"
|
||||||
|
|
||||||
USING_YOSYS_NAMESPACE
|
USING_YOSYS_NAMESPACE
|
||||||
|
@ -709,30 +711,19 @@ struct AigerWriter
|
||||||
f << it.second;
|
f << it.second;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class T> static std::vector<std::string> witness_path(T *obj) {
|
void write_ywmap(PrettyJson &json)
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
void write_ywmap(std::ostream &f)
|
|
||||||
{
|
{
|
||||||
f << "{\n";
|
json.begin_object();
|
||||||
f << " \"version\": \"Yosys Witness Aiger Map\",\n";
|
json.entry("version", "Yosys Witness Aiger map");
|
||||||
f << stringf(" \"generator\": %s,\n", json11::Json(yosys_version_str).dump().c_str());
|
json.entry("gennerator", yosys_version_str);
|
||||||
f << stringf(" \"latch_count\": %d,\n", aig_l);
|
|
||||||
f << stringf(" \"input_count\": %d,\n", aig_i);
|
|
||||||
|
|
||||||
dict<int, string> clock_lines;
|
json.entry("latch_count", aig_l);
|
||||||
dict<int, string> input_lines;
|
json.entry("input_count", aig_i);
|
||||||
dict<int, string> init_lines;
|
|
||||||
dict<int, string> seq_lines;
|
dict<int, Json> clock_lines;
|
||||||
|
dict<int, Json> input_lines;
|
||||||
|
dict<int, Json> init_lines;
|
||||||
|
dict<int, Json> seq_lines;
|
||||||
|
|
||||||
for (auto cell : module->cells())
|
for (auto cell : module->cells())
|
||||||
{
|
{
|
||||||
|
@ -751,21 +742,21 @@ struct AigerWriter
|
||||||
if (init_inputs.count(sig[i])) {
|
if (init_inputs.count(sig[i])) {
|
||||||
int a = init_inputs.at(sig[i]);
|
int a = init_inputs.at(sig[i]);
|
||||||
log_assert((a & 1) == 0);
|
log_assert((a & 1) == 0);
|
||||||
init_lines[a] += json11::Json(json11::Json::object {
|
init_lines[a] = json11::Json(json11::Json::object {
|
||||||
{ "path", witness_path(wire) },
|
{ "path", witness_path(wire) },
|
||||||
{ "input", (a >> 1) - 1 },
|
{ "input", (a >> 1) - 1 },
|
||||||
{ "offset", sig_qy[i].offset },
|
{ "offset", sig_qy[i].offset },
|
||||||
}).dump();
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input_bits.count(sig[i])) {
|
if (input_bits.count(sig[i])) {
|
||||||
int a = aig_map.at(sig[i]);
|
int a = aig_map.at(sig[i]);
|
||||||
log_assert((a & 1) == 0);
|
log_assert((a & 1) == 0);
|
||||||
seq_lines[a] += json11::Json(json11::Json::object {
|
seq_lines[a] = json11::Json(json11::Json::object {
|
||||||
{ "path", witness_path(wire) },
|
{ "path", witness_path(wire) },
|
||||||
{ "input", (a >> 1) - 1 },
|
{ "input", (a >> 1) - 1 },
|
||||||
{ "offset", sig_qy[i].offset },
|
{ "offset", sig_qy[i].offset },
|
||||||
}).dump();
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -783,60 +774,55 @@ struct AigerWriter
|
||||||
|
|
||||||
int a = aig_map.at(sig[i]);
|
int a = aig_map.at(sig[i]);
|
||||||
log_assert((a & 1) == 0);
|
log_assert((a & 1) == 0);
|
||||||
input_lines[a] += json11::Json(json11::Json::object {
|
input_lines[a] = json11::Json(json11::Json::object {
|
||||||
{ "path", path },
|
{ "path", path },
|
||||||
{ "input", (a >> 1) - 1 },
|
{ "input", (a >> 1) - 1 },
|
||||||
{ "offset", i },
|
{ "offset", i },
|
||||||
}).dump();
|
});
|
||||||
|
|
||||||
if (ywmap_clocks.count(sig[i])) {
|
if (ywmap_clocks.count(sig[i])) {
|
||||||
int clock_mode = ywmap_clocks[sig[i]];
|
int clock_mode = ywmap_clocks[sig[i]];
|
||||||
if (clock_mode != 3) {
|
if (clock_mode != 3) {
|
||||||
clock_lines[a] += json11::Json(json11::Json::object {
|
clock_lines[a] = json11::Json(json11::Json::object {
|
||||||
{ "path", path },
|
{ "path", path },
|
||||||
{ "input", (a >> 1) - 1 },
|
{ "input", (a >> 1) - 1 },
|
||||||
{ "offset", i },
|
{ "offset", i },
|
||||||
{ "edge", clock_mode == 1 ? "posedge" : "negedge" },
|
{ "edge", clock_mode == 1 ? "posedge" : "negedge" },
|
||||||
}).dump();
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
f << " \"clocks\": [";
|
json.name("clocks");
|
||||||
|
json.begin_array();
|
||||||
clock_lines.sort();
|
clock_lines.sort();
|
||||||
const char *sep = "\n ";
|
for (auto &it : clock_lines)
|
||||||
for (auto &it : clock_lines) {
|
json.value(it.second);
|
||||||
f << sep << it.second;
|
json.end_array();
|
||||||
sep = ",\n ";
|
|
||||||
}
|
|
||||||
f << "\n ],\n";
|
|
||||||
|
|
||||||
f << " \"inputs\": [";
|
json.name("inputs");
|
||||||
|
json.begin_array();
|
||||||
input_lines.sort();
|
input_lines.sort();
|
||||||
sep = "\n ";
|
for (auto &it : input_lines)
|
||||||
for (auto &it : input_lines) {
|
json.value(it.second);
|
||||||
f << sep << it.second;
|
json.end_array();
|
||||||
sep = ",\n ";
|
|
||||||
}
|
|
||||||
f << "\n ],\n";
|
|
||||||
|
|
||||||
f << " \"seqs\": [";
|
json.name("seqs");
|
||||||
sep = "\n ";
|
json.begin_array();
|
||||||
for (auto &it : seq_lines) {
|
input_lines.sort();
|
||||||
f << sep << it.second;
|
for (auto &it : seq_lines)
|
||||||
sep = ",\n ";
|
json.value(it.second);
|
||||||
}
|
json.end_array();
|
||||||
f << "\n ],\n";
|
|
||||||
|
|
||||||
f << " \"inits\": [";
|
json.name("inits");
|
||||||
sep = "\n ";
|
json.begin_array();
|
||||||
for (auto &it : init_lines) {
|
input_lines.sort();
|
||||||
f << sep << it.second;
|
for (auto &it : init_lines)
|
||||||
sep = ",\n ";
|
json.value(it.second);
|
||||||
}
|
json.end_array();
|
||||||
f << "\n ]\n}\n";
|
json.end_object();
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -991,9 +977,12 @@ struct AigerBackend : public Backend {
|
||||||
if (!yw_map_filename.empty()) {
|
if (!yw_map_filename.empty()) {
|
||||||
std::ofstream mapf;
|
std::ofstream mapf;
|
||||||
mapf.open(yw_map_filename.c_str(), std::ofstream::trunc);
|
mapf.open(yw_map_filename.c_str(), std::ofstream::trunc);
|
||||||
if (mapf.fail())
|
|
||||||
log_error("Can't open file `%s' for writing: %s\n", map_filename.c_str(), strerror(errno));
|
PrettyJson json;
|
||||||
writer.write_ywmap(mapf);
|
|
||||||
|
if (!json.write_to_file(yw_map_filename))
|
||||||
|
log_error("Can't open file `%s' for writing: %s\n", yw_map_filename.c_str(), strerror(errno));
|
||||||
|
writer.write_ywmap(json);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} AigerBackend;
|
} AigerBackend;
|
||||||
|
|
|
@ -28,6 +28,8 @@
|
||||||
#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 "kernel/json.h"
|
||||||
|
#include "kernel/yw.h"
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
USING_YOSYS_NAMESPACE
|
USING_YOSYS_NAMESPACE
|
||||||
|
@ -83,6 +85,22 @@ struct BtorWorker
|
||||||
vector<string> info_lines;
|
vector<string> info_lines;
|
||||||
dict<int, int> info_clocks;
|
dict<int, int> info_clocks;
|
||||||
|
|
||||||
|
struct ywmap_btor_sig {
|
||||||
|
SigSpec sig;
|
||||||
|
Cell *cell = nullptr;
|
||||||
|
|
||||||
|
ywmap_btor_sig(const SigSpec &sig) : sig(sig) {}
|
||||||
|
ywmap_btor_sig(Cell *cell) : cell(cell) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
vector<ywmap_btor_sig> ywmap_inputs;
|
||||||
|
vector<ywmap_btor_sig> ywmap_states;
|
||||||
|
dict<SigBit, int> ywmap_clock_bits;
|
||||||
|
dict<SigBit, int> ywmap_clock_inputs;
|
||||||
|
|
||||||
|
|
||||||
|
PrettyJson ywmap_json;
|
||||||
|
|
||||||
void btorf(const char *fmt, ...) YS_ATTRIBUTE(format(printf, 2, 3))
|
void btorf(const char *fmt, ...) YS_ATTRIBUTE(format(printf, 2, 3))
|
||||||
{
|
{
|
||||||
va_list ap;
|
va_list ap;
|
||||||
|
@ -126,6 +144,50 @@ struct BtorWorker
|
||||||
return " " + infostr;
|
return " " + infostr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ywmap_state(const SigSpec &sig) {
|
||||||
|
if (ywmap_json.active())
|
||||||
|
ywmap_states.emplace_back(sig);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ywmap_state(Cell *cell) {
|
||||||
|
if (ywmap_json.active())
|
||||||
|
ywmap_states.emplace_back(cell);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ywmap_input(const SigSpec &sig) {
|
||||||
|
if (ywmap_json.active())
|
||||||
|
ywmap_inputs.emplace_back(sig);
|
||||||
|
}
|
||||||
|
|
||||||
|
void emit_ywmap_btor_sig(const ywmap_btor_sig &btor_sig) {
|
||||||
|
if (btor_sig.cell == nullptr) {
|
||||||
|
if (btor_sig.sig.empty()) {
|
||||||
|
ywmap_json.value(nullptr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ywmap_json.begin_array();
|
||||||
|
ywmap_json.compact();
|
||||||
|
for (auto &chunk : btor_sig.sig.chunks()) {
|
||||||
|
log_assert(chunk.is_wire());
|
||||||
|
|
||||||
|
ywmap_json.begin_object();
|
||||||
|
ywmap_json.entry("path", witness_path(chunk.wire));
|
||||||
|
ywmap_json.entry("width", chunk.width);
|
||||||
|
ywmap_json.entry("offset", chunk.offset);
|
||||||
|
ywmap_json.end_object();
|
||||||
|
}
|
||||||
|
ywmap_json.end_array();
|
||||||
|
} else {
|
||||||
|
ywmap_json.begin_object();
|
||||||
|
ywmap_json.compact();
|
||||||
|
ywmap_json.entry("path", witness_path(btor_sig.cell));
|
||||||
|
Mem *mem = mem_cells[btor_sig.cell];
|
||||||
|
ywmap_json.entry("width", mem->width);
|
||||||
|
ywmap_json.entry("size", mem->size);
|
||||||
|
ywmap_json.end_object();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void btorf_push(const string &id)
|
void btorf_push(const string &id)
|
||||||
{
|
{
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
|
@ -617,7 +679,7 @@ struct BtorWorker
|
||||||
SigSpec sig_d = sigmap(cell->getPort(ID::D));
|
SigSpec sig_d = sigmap(cell->getPort(ID::D));
|
||||||
SigSpec sig_q = sigmap(cell->getPort(ID::Q));
|
SigSpec sig_q = sigmap(cell->getPort(ID::Q));
|
||||||
|
|
||||||
if (!info_filename.empty() && cell->type.in(ID($dff), ID($_DFF_P_), ID($_DFF_N_)))
|
if ((!info_filename.empty() || ywmap_json.active()) && cell->type.in(ID($dff), ID($_DFF_P_), ID($_DFF_N_)))
|
||||||
{
|
{
|
||||||
SigSpec sig_c = sigmap(cell->getPort(cell->type == ID($dff) ? ID::CLK : ID::C));
|
SigSpec sig_c = sigmap(cell->getPort(cell->type == ID($dff) ? ID::CLK : ID::C));
|
||||||
int nid = get_sig_nid(sig_c);
|
int nid = get_sig_nid(sig_c);
|
||||||
|
@ -629,7 +691,11 @@ struct BtorWorker
|
||||||
if (cell->type == ID($dff) && !cell->getParam(ID::CLK_POLARITY).as_bool())
|
if (cell->type == ID($dff) && !cell->getParam(ID::CLK_POLARITY).as_bool())
|
||||||
negedge = true;
|
negedge = true;
|
||||||
|
|
||||||
|
if (!info_filename.empty())
|
||||||
info_clocks[nid] |= negedge ? 2 : 1;
|
info_clocks[nid] |= negedge ? 2 : 1;
|
||||||
|
|
||||||
|
if (ywmap_json.active())
|
||||||
|
ywmap_clock_bits[sig_c] |= negedge ? 2 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
IdString symbol;
|
IdString symbol;
|
||||||
|
@ -662,6 +728,8 @@ struct BtorWorker
|
||||||
else
|
else
|
||||||
btorf("%d state %d %s\n", nid, sid, log_id(symbol));
|
btorf("%d state %d %s\n", nid, sid, log_id(symbol));
|
||||||
|
|
||||||
|
ywmap_state(sig_q);
|
||||||
|
|
||||||
if (nid_init_val >= 0) {
|
if (nid_init_val >= 0) {
|
||||||
int nid_init = next_nid++;
|
int nid_init = next_nid++;
|
||||||
if (verbose)
|
if (verbose)
|
||||||
|
@ -683,6 +751,8 @@ struct BtorWorker
|
||||||
|
|
||||||
btorf("%d state %d%s\n", nid, sid, getinfo(cell).c_str());
|
btorf("%d state %d%s\n", nid, sid, getinfo(cell).c_str());
|
||||||
|
|
||||||
|
ywmap_state(sig_y);
|
||||||
|
|
||||||
if (cell->type == ID($anyconst)) {
|
if (cell->type == ID($anyconst)) {
|
||||||
int nid2 = next_nid++;
|
int nid2 = next_nid++;
|
||||||
btorf("%d next %d %d %d\n", nid2, sid, nid, nid);
|
btorf("%d next %d %d %d\n", nid2, sid, nid, nid);
|
||||||
|
@ -705,6 +775,8 @@ struct BtorWorker
|
||||||
btorf("%d state %d%s\n", initstate_nid, sid, getinfo(cell).c_str());
|
btorf("%d state %d%s\n", initstate_nid, sid, getinfo(cell).c_str());
|
||||||
btorf("%d init %d %d %d\n", next_nid++, sid, initstate_nid, one_nid);
|
btorf("%d init %d %d %d\n", next_nid++, sid, initstate_nid, one_nid);
|
||||||
btorf("%d next %d %d %d\n", next_nid++, sid, initstate_nid, zero_nid);
|
btorf("%d next %d %d %d\n", next_nid++, sid, initstate_nid, zero_nid);
|
||||||
|
|
||||||
|
ywmap_state(sig_y);
|
||||||
}
|
}
|
||||||
|
|
||||||
add_nid_sig(initstate_nid, sig_y);
|
add_nid_sig(initstate_nid, sig_y);
|
||||||
|
@ -768,6 +840,8 @@ struct BtorWorker
|
||||||
nid_init_val = next_nid++;
|
nid_init_val = next_nid++;
|
||||||
btorf("%d state %d\n", nid_init_val, sid);
|
btorf("%d state %d\n", nid_init_val, sid);
|
||||||
|
|
||||||
|
ywmap_state(nullptr);
|
||||||
|
|
||||||
for (int i = 0; i < mem->size; i++) {
|
for (int i = 0; i < mem->size; i++) {
|
||||||
Const thisword = initdata.extract(i*mem->width, mem->width);
|
Const thisword = initdata.extract(i*mem->width, mem->width);
|
||||||
if (thisword.is_fully_undef())
|
if (thisword.is_fully_undef())
|
||||||
|
@ -793,6 +867,8 @@ struct BtorWorker
|
||||||
else
|
else
|
||||||
btorf("%d state %d %s\n", nid, sid, log_id(mem->memid));
|
btorf("%d state %d %s\n", nid, sid, log_id(mem->memid));
|
||||||
|
|
||||||
|
ywmap_state(cell);
|
||||||
|
|
||||||
if (nid_init_val >= 0)
|
if (nid_init_val >= 0)
|
||||||
{
|
{
|
||||||
int nid_init = next_nid++;
|
int nid_init = next_nid++;
|
||||||
|
@ -915,10 +991,13 @@ struct BtorWorker
|
||||||
int sid = get_bv_sid(GetSize(sig));
|
int sid = get_bv_sid(GetSize(sig));
|
||||||
|
|
||||||
int nid_input = next_nid++;
|
int nid_input = next_nid++;
|
||||||
if (is_init)
|
if (is_init) {
|
||||||
btorf("%d state %d\n", nid_input, sid);
|
btorf("%d state %d\n", nid_input, sid);
|
||||||
else
|
ywmap_state(sig);
|
||||||
|
} else {
|
||||||
btorf("%d input %d\n", nid_input, sid);
|
btorf("%d input %d\n", nid_input, sid);
|
||||||
|
ywmap_input(sig);
|
||||||
|
}
|
||||||
|
|
||||||
int nid_masked_input;
|
int nid_masked_input;
|
||||||
if (sig_mask_undef.is_fully_ones()) {
|
if (sig_mask_undef.is_fully_ones()) {
|
||||||
|
@ -993,6 +1072,7 @@ struct BtorWorker
|
||||||
int sid = get_bv_sid(GetSize(s));
|
int sid = get_bv_sid(GetSize(s));
|
||||||
int nid = next_nid++;
|
int nid = next_nid++;
|
||||||
btorf("%d input %d\n", nid, sid);
|
btorf("%d input %d\n", nid, sid);
|
||||||
|
ywmap_input(s);
|
||||||
nid_width[nid] = GetSize(s);
|
nid_width[nid] = GetSize(s);
|
||||||
|
|
||||||
for (int j = 0; j < GetSize(s); j++)
|
for (int j = 0; j < GetSize(s); j++)
|
||||||
|
@ -1075,12 +1155,15 @@ struct BtorWorker
|
||||||
return nid;
|
return nid;
|
||||||
}
|
}
|
||||||
|
|
||||||
BtorWorker(std::ostream &f, RTLIL::Module *module, bool verbose, bool single_bad, bool cover_mode, bool print_internal_names, string info_filename) :
|
BtorWorker(std::ostream &f, RTLIL::Module *module, bool verbose, bool single_bad, bool cover_mode, bool print_internal_names, string info_filename, string ywmap_filename) :
|
||||||
f(f), sigmap(module), module(module), verbose(verbose), single_bad(single_bad), cover_mode(cover_mode), print_internal_names(print_internal_names), info_filename(info_filename)
|
f(f), sigmap(module), module(module), verbose(verbose), single_bad(single_bad), cover_mode(cover_mode), print_internal_names(print_internal_names), info_filename(info_filename)
|
||||||
{
|
{
|
||||||
if (!info_filename.empty())
|
if (!info_filename.empty())
|
||||||
infof("name %s\n", log_id(module));
|
infof("name %s\n", log_id(module));
|
||||||
|
|
||||||
|
if (!ywmap_filename.empty())
|
||||||
|
ywmap_json.write_to_file(ywmap_filename);
|
||||||
|
|
||||||
memories = Mem::get_all_memories(module);
|
memories = Mem::get_all_memories(module);
|
||||||
|
|
||||||
dict<IdString, Mem*> mem_dict;
|
dict<IdString, Mem*> mem_dict;
|
||||||
|
@ -1094,6 +1177,20 @@ struct BtorWorker
|
||||||
|
|
||||||
btorf_push("inputs");
|
btorf_push("inputs");
|
||||||
|
|
||||||
|
if (ywmap_json.active()) {
|
||||||
|
for (auto wire : module->wires())
|
||||||
|
{
|
||||||
|
auto gclk_attr = wire->attributes.find(ID::replaced_by_gclk);
|
||||||
|
if (gclk_attr == wire->attributes.end())
|
||||||
|
continue;
|
||||||
|
SigSpec sig = sigmap(wire);
|
||||||
|
if (gclk_attr->second == State::S1)
|
||||||
|
ywmap_clock_bits[sig] |= 1;
|
||||||
|
else if (gclk_attr->second == State::S0)
|
||||||
|
ywmap_clock_bits[sig] |= 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (auto wire : module->wires())
|
for (auto wire : module->wires())
|
||||||
{
|
{
|
||||||
if (wire->attributes.count(ID::init)) {
|
if (wire->attributes.count(ID::init)) {
|
||||||
|
@ -1111,6 +1208,7 @@ struct BtorWorker
|
||||||
int nid = next_nid++;
|
int nid = next_nid++;
|
||||||
|
|
||||||
btorf("%d input %d%s\n", nid, sid, getinfo(wire).c_str());
|
btorf("%d input %d%s\n", nid, sid, getinfo(wire).c_str());
|
||||||
|
ywmap_input(wire);
|
||||||
add_nid_sig(nid, sig);
|
add_nid_sig(nid, sig);
|
||||||
|
|
||||||
if (!info_filename.empty()) {
|
if (!info_filename.empty()) {
|
||||||
|
@ -1122,6 +1220,16 @@ struct BtorWorker
|
||||||
info_clocks[nid] |= 2;
|
info_clocks[nid] |= 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ywmap_json.active()) {
|
||||||
|
for (int i = 0; i < GetSize(sig); i++) {
|
||||||
|
auto input_bit = SigBit(wire, i);
|
||||||
|
auto bit = sigmap(input_bit);
|
||||||
|
if (!ywmap_clock_bits.count(bit))
|
||||||
|
continue;
|
||||||
|
ywmap_clock_inputs[input_bit] = ywmap_clock_bits[bit];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
btorf_pop("inputs");
|
btorf_pop("inputs");
|
||||||
|
@ -1378,6 +1486,42 @@ struct BtorWorker
|
||||||
f << it;
|
f << it;
|
||||||
f.close();
|
f.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ywmap_json.active())
|
||||||
|
{
|
||||||
|
ywmap_json.begin_object();
|
||||||
|
ywmap_json.entry("version", "Yosys Witness BTOR map");
|
||||||
|
ywmap_json.entry("generator", yosys_version_str);
|
||||||
|
|
||||||
|
ywmap_json.name("clocks");
|
||||||
|
ywmap_json.begin_array();
|
||||||
|
for (auto &entry : ywmap_clock_inputs) {
|
||||||
|
if (entry.second != 1 && entry.second != 2)
|
||||||
|
continue;
|
||||||
|
log_assert(entry.first.is_wire());
|
||||||
|
ywmap_json.begin_object();
|
||||||
|
ywmap_json.compact();
|
||||||
|
ywmap_json.entry("path", witness_path(entry.first.wire));
|
||||||
|
ywmap_json.entry("offset", entry.first.offset);
|
||||||
|
ywmap_json.entry("edge", entry.second == 1 ? "posedge" : "negedge");
|
||||||
|
ywmap_json.end_object();
|
||||||
|
}
|
||||||
|
ywmap_json.end_array();
|
||||||
|
|
||||||
|
ywmap_json.name("inputs");
|
||||||
|
ywmap_json.begin_array();
|
||||||
|
for (auto &entry : ywmap_inputs)
|
||||||
|
emit_ywmap_btor_sig(entry);
|
||||||
|
ywmap_json.end_array();
|
||||||
|
|
||||||
|
ywmap_json.name("states");
|
||||||
|
ywmap_json.begin_array();
|
||||||
|
for (auto &entry : ywmap_states)
|
||||||
|
emit_ywmap_btor_sig(entry);
|
||||||
|
ywmap_json.end_array();
|
||||||
|
|
||||||
|
ywmap_json.end_object();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1406,11 +1550,15 @@ struct BtorBackend : public Backend {
|
||||||
log(" -x\n");
|
log(" -x\n");
|
||||||
log(" Output symbols for internal netnames (starting with '$')\n");
|
log(" Output symbols for internal netnames (starting with '$')\n");
|
||||||
log("\n");
|
log("\n");
|
||||||
|
log(" -ywmap <filename>\n");
|
||||||
|
log(" Create a map file for conversion to and from Yosys witness traces\n");
|
||||||
|
log("\n");
|
||||||
}
|
}
|
||||||
void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override
|
void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override
|
||||||
{
|
{
|
||||||
bool verbose = false, single_bad = false, cover_mode = false, print_internal_names = false;
|
bool verbose = false, single_bad = false, cover_mode = false, print_internal_names = false;
|
||||||
string info_filename;
|
string info_filename;
|
||||||
|
string ywmap_filename;
|
||||||
|
|
||||||
log_header(design, "Executing BTOR backend.\n");
|
log_header(design, "Executing BTOR backend.\n");
|
||||||
|
|
||||||
|
@ -1443,6 +1591,10 @@ struct BtorBackend : public Backend {
|
||||||
print_internal_names = true;
|
print_internal_names = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (args[argidx] == "-ywmap" && argidx+1 < args.size()) {
|
||||||
|
ywmap_filename = args[++argidx];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
extra_args(f, filename, args, argidx);
|
extra_args(f, filename, args, argidx);
|
||||||
|
@ -1455,7 +1607,7 @@ struct BtorBackend : public Backend {
|
||||||
*f << stringf("; BTOR description generated by %s for module %s.\n",
|
*f << stringf("; BTOR description generated by %s for module %s.\n",
|
||||||
yosys_version_str, log_id(topmod));
|
yosys_version_str, log_id(topmod));
|
||||||
|
|
||||||
BtorWorker(*f, topmod, verbose, single_bad, cover_mode, print_internal_names, info_filename);
|
BtorWorker(*f, topmod, verbose, single_bad, cover_mode, print_internal_names, info_filename, ywmap_filename);
|
||||||
|
|
||||||
*f << stringf("; end of yosys output\n");
|
*f << stringf("; end of yosys output\n");
|
||||||
}
|
}
|
||||||
|
|
|
@ -458,7 +458,7 @@ struct Smt2Worker
|
||||||
{
|
{
|
||||||
RTLIL::SigSpec sig_a, sig_b;
|
RTLIL::SigSpec sig_a, sig_b;
|
||||||
RTLIL::SigSpec sig_y = sigmap(cell->getPort(ID::Y));
|
RTLIL::SigSpec sig_y = sigmap(cell->getPort(ID::Y));
|
||||||
bool is_signed = cell->getParam(ID::A_SIGNED).as_bool();
|
bool is_signed = type == 'U' ? false : cell->getParam(ID::A_SIGNED).as_bool();
|
||||||
int width = GetSize(sig_y);
|
int width = GetSize(sig_y);
|
||||||
|
|
||||||
if (type == 's' || type == 'S' || type == 'd' || type == 'b') {
|
if (type == 's' || type == 'S' || type == 'd' || type == 'b') {
|
||||||
|
@ -483,6 +483,7 @@ struct Smt2Worker
|
||||||
if (ch == 'A') processed_expr += get_bv(sig_a);
|
if (ch == 'A') processed_expr += get_bv(sig_a);
|
||||||
else if (ch == 'B') processed_expr += get_bv(sig_b);
|
else if (ch == 'B') processed_expr += get_bv(sig_b);
|
||||||
else if (ch == 'P') processed_expr += get_bv(cell->getPort(ID::B));
|
else if (ch == 'P') processed_expr += get_bv(cell->getPort(ID::B));
|
||||||
|
else if (ch == 'S') processed_expr += get_bv(cell->getPort(ID::S));
|
||||||
else if (ch == 'L') processed_expr += is_signed ? "a" : "l";
|
else if (ch == 'L') processed_expr += is_signed ? "a" : "l";
|
||||||
else if (ch == 'U') processed_expr += is_signed ? "s" : "u";
|
else if (ch == 'U') processed_expr += is_signed ? "s" : "u";
|
||||||
else processed_expr += ch;
|
else processed_expr += ch;
|
||||||
|
@ -639,6 +640,9 @@ struct Smt2Worker
|
||||||
if (cell->type == ID($xor)) return export_bvop(cell, "(bvxor A B)");
|
if (cell->type == ID($xor)) return export_bvop(cell, "(bvxor A B)");
|
||||||
if (cell->type == ID($xnor)) return export_bvop(cell, "(bvxnor A B)");
|
if (cell->type == ID($xnor)) return export_bvop(cell, "(bvxnor A B)");
|
||||||
|
|
||||||
|
if (cell->type == ID($bweqx)) return export_bvop(cell, "(bvxnor A B)", 'U');
|
||||||
|
if (cell->type == ID($bwmux)) return export_bvop(cell, "(bvor (bvand A (bvnot S)) (bvand B S))", 'U');
|
||||||
|
|
||||||
if (cell->type == ID($shl)) return export_bvop(cell, "(bvshl A B)", 's');
|
if (cell->type == ID($shl)) return export_bvop(cell, "(bvshl A B)", 's');
|
||||||
if (cell->type == ID($shr)) return export_bvop(cell, "(bvlshr A B)", 's');
|
if (cell->type == ID($shr)) return export_bvop(cell, "(bvlshr A B)", 's');
|
||||||
if (cell->type == ID($sshl)) return export_bvop(cell, "(bvshl A B)", 's');
|
if (cell->type == ID($sshl)) return export_bvop(cell, "(bvshl A B)", 's');
|
||||||
|
@ -994,7 +998,7 @@ struct Smt2Worker
|
||||||
if (contains_clock && 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) {
|
if (wire->port_input && contains_clock) {
|
||||||
for (int i = 0; i < GetSize(sig); i++) {
|
for (int i = 0; i < GetSize(sig); i++) {
|
||||||
bool is_posedge = clock_posedge.count(sig[i]);
|
bool is_posedge = clock_posedge.count(sig[i]);
|
||||||
bool is_negedge = clock_negedge.count(sig[i]);
|
bool is_negedge = clock_negedge.count(sig[i]);
|
||||||
|
@ -1744,7 +1748,6 @@ struct Smt2Backend : public Backend {
|
||||||
log_push();
|
log_push();
|
||||||
Pass::call(design, "bmuxmap");
|
Pass::call(design, "bmuxmap");
|
||||||
Pass::call(design, "demuxmap");
|
Pass::call(design, "demuxmap");
|
||||||
Pass::call(design, "bwmuxmap");
|
|
||||||
log_pop();
|
log_pop();
|
||||||
|
|
||||||
size_t argidx;
|
size_t argidx;
|
||||||
|
|
|
@ -110,6 +110,10 @@ class AigerMap:
|
||||||
def __init__(self, mapfile):
|
def __init__(self, mapfile):
|
||||||
data = json.load(mapfile)
|
data = json.load(mapfile)
|
||||||
|
|
||||||
|
version = data.get("version") if isinstance(data, dict) else {}
|
||||||
|
if version != "Yosys Witness Aiger map":
|
||||||
|
raise click.ClickException(f"{mapfile.name}: unexpected file format version {version!r}")
|
||||||
|
|
||||||
self.latch_count = data["latch_count"]
|
self.latch_count = data["latch_count"]
|
||||||
self.input_count = data["input_count"]
|
self.input_count = data["input_count"]
|
||||||
|
|
||||||
|
@ -250,5 +254,157 @@ def yw2aiw(input, mapfile, output):
|
||||||
|
|
||||||
click.echo(f"Converted {len(inyw)} time steps.")
|
click.echo(f"Converted {len(inyw)} time steps.")
|
||||||
|
|
||||||
|
class BtorMap:
|
||||||
|
def __init__(self, mapfile):
|
||||||
|
self.data = data = json.load(mapfile)
|
||||||
|
|
||||||
|
version = data.get("version") if isinstance(data, dict) else {}
|
||||||
|
if version != "Yosys Witness BTOR map":
|
||||||
|
raise click.ClickException(f"{mapfile.name}: unexpected file format version {version!r}")
|
||||||
|
|
||||||
|
self.sigmap = WitnessSigMap()
|
||||||
|
|
||||||
|
for mode in ("states", "inputs"):
|
||||||
|
for btor_signal_def in data[mode]:
|
||||||
|
if btor_signal_def is None:
|
||||||
|
continue
|
||||||
|
if isinstance(btor_signal_def, dict):
|
||||||
|
btor_signal_def["path"] = tuple(btor_signal_def["path"])
|
||||||
|
else:
|
||||||
|
for chunk in btor_signal_def:
|
||||||
|
chunk["path"] = tuple(chunk["path"])
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command(help="""
|
||||||
|
Convert a BTOR witness trace into a Yosys witness trace.
|
||||||
|
|
||||||
|
This requires a Yosys witness BTOR map file as generated by 'write_btor -ywmap'.
|
||||||
|
""")
|
||||||
|
@click.argument("input", type=click.File("r"))
|
||||||
|
@click.argument("mapfile", type=click.File("r"))
|
||||||
|
@click.argument("output", type=click.File("w"))
|
||||||
|
def wit2yw(input, mapfile, output):
|
||||||
|
input_name = input.name
|
||||||
|
click.echo(f"Converting BTOR witness trace {input_name!r} to Yosys witness trace {output.name!r}...")
|
||||||
|
click.echo(f"Using Yosys witness BTOR map file {mapfile.name!r}")
|
||||||
|
btor_map = BtorMap(mapfile)
|
||||||
|
|
||||||
|
input = filter(None, (line.split(';', 1)[0].strip() for line in input))
|
||||||
|
|
||||||
|
sat = next(input, None)
|
||||||
|
if sat != "sat":
|
||||||
|
raise click.ClickException(f"{input_name}: not a BTOR witness file")
|
||||||
|
|
||||||
|
props = next(input, None)
|
||||||
|
|
||||||
|
t = -1
|
||||||
|
|
||||||
|
line = next(input, None)
|
||||||
|
|
||||||
|
frames = []
|
||||||
|
bits = {}
|
||||||
|
|
||||||
|
while line and not line.startswith('.'):
|
||||||
|
current_t = int(line[1:].strip())
|
||||||
|
if line[0] == '#':
|
||||||
|
mode = "states"
|
||||||
|
elif line[0] == '@':
|
||||||
|
mode = "inputs"
|
||||||
|
else:
|
||||||
|
raise click.ClickException(f"{input_name}: unexpected data in BTOR witness file")
|
||||||
|
|
||||||
|
if current_t > t:
|
||||||
|
t = current_t
|
||||||
|
values = WitnessValues()
|
||||||
|
array_inits = set()
|
||||||
|
frames.append(values)
|
||||||
|
|
||||||
|
line = next(input, None)
|
||||||
|
while line and line[0] not in "#@.":
|
||||||
|
if current_t > 0 and mode == "states":
|
||||||
|
line = next(input, None)
|
||||||
|
continue
|
||||||
|
tokens = line.split()
|
||||||
|
line = next(input, None)
|
||||||
|
|
||||||
|
btor_sig = btor_map.data[mode][int(tokens[0])]
|
||||||
|
btor_sigs = [btor_sig]
|
||||||
|
|
||||||
|
if btor_sig is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if isinstance(btor_sig, dict):
|
||||||
|
addr = tokens[1]
|
||||||
|
if not addr.startswith('['):
|
||||||
|
addr = '[*]'
|
||||||
|
value = tokens[1]
|
||||||
|
else:
|
||||||
|
value = tokens[2]
|
||||||
|
if not addr.endswith(']'):
|
||||||
|
raise click.ClickException(f"{input_name}: expected address in BTOR witness file")
|
||||||
|
path = btor_sig["path"]
|
||||||
|
width = btor_sig["width"]
|
||||||
|
size = btor_sig["size"]
|
||||||
|
if addr == '[*]':
|
||||||
|
btor_sigs = [
|
||||||
|
[{
|
||||||
|
"path": (*path, f"\\[{addr}]"),
|
||||||
|
"width": width,
|
||||||
|
"offset": 0,
|
||||||
|
}]
|
||||||
|
for addr in range(size)
|
||||||
|
if (path, addr) not in array_inits
|
||||||
|
]
|
||||||
|
array_inits.update((path, addr) for addr in range(size))
|
||||||
|
else:
|
||||||
|
addr = int(addr[1:-1], 2)
|
||||||
|
|
||||||
|
if addr < 0 or addr >= size:
|
||||||
|
raise click.ClickException(f"{input_name}: out of bounds address in BTOR witness file")
|
||||||
|
|
||||||
|
array_inits.add((path, addr))
|
||||||
|
btor_sig = [{
|
||||||
|
"path": (*path, f"\\[{addr}]"),
|
||||||
|
"width": width,
|
||||||
|
"offset": 0,
|
||||||
|
}]
|
||||||
|
btor_sigs = [btor_sig]
|
||||||
|
else:
|
||||||
|
value = tokens[1]
|
||||||
|
|
||||||
|
for btor_sig in btor_sigs:
|
||||||
|
value_bits = iter(reversed(value))
|
||||||
|
|
||||||
|
for chunk in btor_sig:
|
||||||
|
offset = chunk["offset"]
|
||||||
|
path = chunk["path"]
|
||||||
|
for i in range(offset, offset + chunk["width"]):
|
||||||
|
key = (path, i)
|
||||||
|
bits[key] = mode == "inputs"
|
||||||
|
values[key] = next(value_bits)
|
||||||
|
|
||||||
|
if next(value_bits, None) is not None:
|
||||||
|
raise click.ClickException(f"{input_name}: excess bits in BTOR witness file")
|
||||||
|
|
||||||
|
|
||||||
|
if line is None:
|
||||||
|
raise click.ClickException(f"{input_name}: unexpected end of BTOR witness file")
|
||||||
|
if line.strip() != '.':
|
||||||
|
raise click.ClickException(f"{input_name}: unexpected data in BTOR witness file")
|
||||||
|
if next(input, None) is not None:
|
||||||
|
raise click.ClickException(f"{input_name}: unexpected trailing data in BTOR witness file")
|
||||||
|
|
||||||
|
outyw = WriteWitness(output, 'yosys-witness wit2yw')
|
||||||
|
|
||||||
|
outyw.signals = coalesce_signals((), bits)
|
||||||
|
for clock in btor_map.data["clocks"]:
|
||||||
|
outyw.add_clock(clock["path"], clock["offset"], clock["edge"])
|
||||||
|
|
||||||
|
for values in frames:
|
||||||
|
outyw.step(values)
|
||||||
|
|
||||||
|
outyw.end_trace()
|
||||||
|
click.echo(f"Converted {outyw.t} time steps.")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
cli()
|
cli()
|
||||||
|
|
|
@ -175,7 +175,8 @@ class WitnessSig:
|
||||||
return self.sort_key < other.sort_key
|
return self.sort_key < other.sort_key
|
||||||
|
|
||||||
|
|
||||||
def coalesce_signals(signals):
|
def coalesce_signals(signals, bits=None):
|
||||||
|
if bits is None:
|
||||||
bits = {}
|
bits = {}
|
||||||
for sig in signals:
|
for sig in signals:
|
||||||
for bit in sig.bits():
|
for bit in sig.bits():
|
||||||
|
|
|
@ -259,5 +259,6 @@ X(WR_PORTS)
|
||||||
X(WR_PRIORITY_MASK)
|
X(WR_PRIORITY_MASK)
|
||||||
X(WR_WIDE_CONTINUATION)
|
X(WR_WIDE_CONTINUATION)
|
||||||
X(X)
|
X(X)
|
||||||
|
X(xprop_decoder)
|
||||||
X(Y)
|
X(Y)
|
||||||
X(Y_WIDTH)
|
X(Y_WIDTH)
|
||||||
|
|
|
@ -0,0 +1,172 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "kernel/json.h"
|
||||||
|
|
||||||
|
USING_YOSYS_NAMESPACE
|
||||||
|
|
||||||
|
void PrettyJson::emit_to_log()
|
||||||
|
{
|
||||||
|
struct LogTarget : public Target {
|
||||||
|
void emit(const char *data) override { log("%s", data); }
|
||||||
|
};
|
||||||
|
|
||||||
|
targets.push_back(std::unique_ptr<Target>(new LogTarget));
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrettyJson::append_to_string(std::string &target)
|
||||||
|
{
|
||||||
|
struct AppendStringTarget : public Target {
|
||||||
|
std::string ⌖
|
||||||
|
AppendStringTarget(std::string &target) : target(target) {}
|
||||||
|
void emit(const char *data) override { target += data; }
|
||||||
|
};
|
||||||
|
|
||||||
|
targets.push_back(std::unique_ptr<Target>(new AppendStringTarget(target)));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PrettyJson::write_to_file(const std::string &path)
|
||||||
|
{
|
||||||
|
struct WriteFileTarget : public Target {
|
||||||
|
std::ofstream target;
|
||||||
|
void emit(const char *data) override { target << data; }
|
||||||
|
void flush() override { target.flush(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
auto target = std::unique_ptr<WriteFileTarget>(new WriteFileTarget);
|
||||||
|
target->target.open(path);
|
||||||
|
if (target->target.fail())
|
||||||
|
return false;
|
||||||
|
targets.push_back(std::unique_ptr<Target>(target.release()));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrettyJson::line(bool space_if_inline)
|
||||||
|
{
|
||||||
|
if (compact_depth != INT_MAX) {
|
||||||
|
if (space_if_inline)
|
||||||
|
raw(" ");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int indent = state.size() - (state.empty() ? 0 : state.back() == VALUE);
|
||||||
|
newline_indent.resize(1 + 2 * indent, ' ');
|
||||||
|
raw(newline_indent.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrettyJson::raw(const char *raw_json)
|
||||||
|
{
|
||||||
|
for (auto &target : targets)
|
||||||
|
target->emit(raw_json);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrettyJson::flush()
|
||||||
|
{
|
||||||
|
for (auto &target : targets)
|
||||||
|
target->flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrettyJson::begin_object()
|
||||||
|
{
|
||||||
|
begin_value();
|
||||||
|
raw("{");
|
||||||
|
state.push_back(OBJECT_FIRST);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrettyJson::begin_array()
|
||||||
|
{
|
||||||
|
begin_value();
|
||||||
|
raw("[");
|
||||||
|
state.push_back(ARRAY_FIRST);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrettyJson::end_object()
|
||||||
|
{
|
||||||
|
Scope top_scope = state.back();
|
||||||
|
state.pop_back();
|
||||||
|
if (top_scope == OBJECT)
|
||||||
|
line(false);
|
||||||
|
else
|
||||||
|
log_assert(top_scope == OBJECT_FIRST);
|
||||||
|
raw("}");
|
||||||
|
end_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrettyJson::end_array()
|
||||||
|
{
|
||||||
|
Scope top_scope = state.back();
|
||||||
|
state.pop_back();
|
||||||
|
if (top_scope == ARRAY)
|
||||||
|
line(false);
|
||||||
|
else
|
||||||
|
log_assert(top_scope == ARRAY_FIRST);
|
||||||
|
raw("]");
|
||||||
|
end_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrettyJson::name(const char *name)
|
||||||
|
{
|
||||||
|
if (state.back() == OBJECT_FIRST) {
|
||||||
|
state.back() = OBJECT;
|
||||||
|
line(false);
|
||||||
|
} else {
|
||||||
|
raw(",");
|
||||||
|
line();
|
||||||
|
}
|
||||||
|
raw(Json(name).dump().c_str());
|
||||||
|
raw(": ");
|
||||||
|
state.push_back(VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrettyJson::begin_value()
|
||||||
|
{
|
||||||
|
if (state.back() == ARRAY_FIRST) {
|
||||||
|
line(false);
|
||||||
|
state.back() = ARRAY;
|
||||||
|
} else if (state.back() == ARRAY) {
|
||||||
|
raw(",");
|
||||||
|
line();
|
||||||
|
} else {
|
||||||
|
log_assert(state.back() == VALUE);
|
||||||
|
state.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrettyJson::end_value()
|
||||||
|
{
|
||||||
|
if (state.empty()) {
|
||||||
|
raw("\n");
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
if (GetSize(state) < compact_depth)
|
||||||
|
compact_depth = INT_MAX;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrettyJson::value_json(const Json &value)
|
||||||
|
{
|
||||||
|
begin_value();
|
||||||
|
raw(value.dump().c_str());
|
||||||
|
end_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrettyJson::entry_json(const char *name, const Json &value)
|
||||||
|
{
|
||||||
|
this->name(name);
|
||||||
|
this->value(value);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef JSON_H
|
||||||
|
#define JSON_H
|
||||||
|
|
||||||
|
#include "kernel/yosys.h"
|
||||||
|
#include "libs/json11/json11.hpp"
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
YOSYS_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
using json11::Json;
|
||||||
|
|
||||||
|
class PrettyJson
|
||||||
|
{
|
||||||
|
enum Scope {
|
||||||
|
VALUE,
|
||||||
|
OBJECT_FIRST,
|
||||||
|
OBJECT,
|
||||||
|
ARRAY_FIRST,
|
||||||
|
ARRAY,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Target {
|
||||||
|
virtual void emit(const char *data) = 0;
|
||||||
|
virtual void flush() {};
|
||||||
|
virtual ~Target() {};
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string newline_indent = "\n";
|
||||||
|
std::vector<std::unique_ptr<Target>> targets;
|
||||||
|
std::vector<Scope> state = {VALUE};
|
||||||
|
int compact_depth = INT_MAX;
|
||||||
|
public:
|
||||||
|
|
||||||
|
void emit_to_log();
|
||||||
|
void append_to_string(std::string &target);
|
||||||
|
bool write_to_file(const std::string &path);
|
||||||
|
|
||||||
|
bool active() { return !targets.empty(); }
|
||||||
|
|
||||||
|
void compact() { compact_depth = GetSize(state); }
|
||||||
|
|
||||||
|
void line(bool space_if_inline = true);
|
||||||
|
void raw(const char *raw_json);
|
||||||
|
void flush();
|
||||||
|
void begin_object();
|
||||||
|
void begin_array();
|
||||||
|
void end_object();
|
||||||
|
void end_array();
|
||||||
|
void name(const char *name);
|
||||||
|
void begin_value();
|
||||||
|
void end_value();
|
||||||
|
void value_json(const Json &value);
|
||||||
|
void value(unsigned int value) { value_json(Json((int)value)); }
|
||||||
|
template<typename T>
|
||||||
|
void value(T &&value) { value_json(Json(std::forward<T>(value))); };
|
||||||
|
|
||||||
|
void entry_json(const char *name, const Json &value);
|
||||||
|
void entry(const char *name, unsigned int value) { entry_json(name, Json((int)value)); }
|
||||||
|
template<typename T>
|
||||||
|
void entry(const char *name, T &&value) { entry_json(name, Json(std::forward<T>(value))); };
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void object(const T &&values)
|
||||||
|
{
|
||||||
|
begin_object();
|
||||||
|
for (auto &item : values)
|
||||||
|
entry(item.first, item.second);
|
||||||
|
end_object();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void array(const T &&values)
|
||||||
|
{
|
||||||
|
begin_object();
|
||||||
|
for (auto &item : values)
|
||||||
|
value(item);
|
||||||
|
end_object();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
YOSYS_NAMESPACE_END
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,209 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "kernel/yw.h"
|
||||||
|
#include "libs/json11/json11.hpp"
|
||||||
|
|
||||||
|
USING_YOSYS_NAMESPACE
|
||||||
|
|
||||||
|
// Use the same formatting as witness.py uses
|
||||||
|
static const char *pretty_name(IdString id)
|
||||||
|
{
|
||||||
|
const char *c_str = id.c_str();
|
||||||
|
const char *p = c_str;
|
||||||
|
|
||||||
|
if (*p != '\\')
|
||||||
|
return c_str;
|
||||||
|
p++;
|
||||||
|
|
||||||
|
if (*p == '[') {
|
||||||
|
p++;
|
||||||
|
while (*p >= '0' && *p <= '9')
|
||||||
|
p++;
|
||||||
|
if (p[0] != ']' || p[1] != 0)
|
||||||
|
return c_str;
|
||||||
|
return c_str + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(*p >= 'a' && *p <= 'z') && !(*p >= 'A' && *p <= 'Z') && *p != '_')
|
||||||
|
return c_str;
|
||||||
|
p++;
|
||||||
|
while ((*p >= '0' && *p <= '9') || (*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || *p == '_')
|
||||||
|
p++;
|
||||||
|
|
||||||
|
if (*p != 0)
|
||||||
|
return c_str;
|
||||||
|
return c_str + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string IdPath::str() const
|
||||||
|
{
|
||||||
|
std::string result;
|
||||||
|
|
||||||
|
for (auto &item : *this) {
|
||||||
|
const char *pretty = pretty_name(item);
|
||||||
|
if (pretty[0] == '[') {
|
||||||
|
result += pretty;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!result.empty())
|
||||||
|
result += '.';
|
||||||
|
result += pretty;
|
||||||
|
if (pretty[0] == '\\' || pretty[0] == '$')
|
||||||
|
result += ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IdPath::get_address(int &addr) const
|
||||||
|
{
|
||||||
|
if (empty())
|
||||||
|
return false;
|
||||||
|
auto &last = back();
|
||||||
|
if (!last.begins_with("\\["))
|
||||||
|
return false;
|
||||||
|
if (last == "\\[0]") {
|
||||||
|
addr = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
char first = last.c_str()[2];
|
||||||
|
if (first < '1' || first > '9')
|
||||||
|
return false;
|
||||||
|
char *endptr;
|
||||||
|
addr = std::strtol(last.c_str() + 2, &endptr, 10);
|
||||||
|
return endptr[0] == ']' && endptr[1] == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<IdString> get_path(const json11::Json &json)
|
||||||
|
{
|
||||||
|
std::vector<IdString> result;
|
||||||
|
for (auto &path_item : json.array_items()) {
|
||||||
|
auto const &path_item_str = path_item.string_value();
|
||||||
|
if (path_item_str.empty())
|
||||||
|
return {};;
|
||||||
|
result.push_back(path_item_str);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReadWitness::ReadWitness(const std::string &filename) :
|
||||||
|
filename(filename)
|
||||||
|
{
|
||||||
|
std::ifstream f(filename.c_str());
|
||||||
|
if (f.fail() || GetSize(filename) == 0)
|
||||||
|
log_error("Cannot open file `%s`\n", filename.c_str());
|
||||||
|
std::stringstream buf;
|
||||||
|
buf << f.rdbuf();
|
||||||
|
std::string err;
|
||||||
|
json11::Json json = json11::Json::parse(buf.str(), err);
|
||||||
|
if (!err.empty())
|
||||||
|
log_error("Failed to parse `%s`: %s\n", filename.c_str(), err.c_str());
|
||||||
|
|
||||||
|
std::string format = json["format"].string_value();
|
||||||
|
|
||||||
|
if (format.empty())
|
||||||
|
log_error("Failed to parse `%s`: Unknown format\n", filename.c_str());
|
||||||
|
if (format != "Yosys Witness Trace")
|
||||||
|
log_error("Failed to parse `%s`: Unsupported format `%s`\n", filename.c_str(), format.c_str());
|
||||||
|
|
||||||
|
for (auto &clock_json : json["clocks"].array_items()) {
|
||||||
|
Clock clock;
|
||||||
|
clock.path = get_path(clock_json["path"]);
|
||||||
|
if (clock.path.empty())
|
||||||
|
log_error("Failed to parse `%s`: Missing path for clock `%s`\n", filename.c_str(), clock_json.dump().c_str());
|
||||||
|
auto edge_str = clock_json["edge"];
|
||||||
|
if (edge_str.string_value() == "posedge")
|
||||||
|
clock.is_posedge = true;
|
||||||
|
else if (edge_str.string_value() == "negedge")
|
||||||
|
clock.is_negedge = true;
|
||||||
|
else
|
||||||
|
log_error("Failed to parse `%s`: Unknown edge type for clock `%s`\n", filename.c_str(), clock_json.dump().c_str());
|
||||||
|
if (!clock_json["offset"].is_number())
|
||||||
|
log_error("Failed to parse `%s`: Unknown offset for clock `%s`\n", filename.c_str(), clock_json.dump().c_str());
|
||||||
|
clock.offset = clock_json["offset"].int_value();
|
||||||
|
if (clock.offset < 0)
|
||||||
|
log_error("Failed to parse `%s`: Invalid offset for clock `%s`\n", filename.c_str(), clock_json.dump().c_str());
|
||||||
|
clocks.push_back(clock);
|
||||||
|
}
|
||||||
|
|
||||||
|
int bits_offset = 0;
|
||||||
|
for (auto &signal_json : json["signals"].array_items()) {
|
||||||
|
Signal signal;
|
||||||
|
signal.bits_offset = bits_offset;
|
||||||
|
signal.path = get_path(signal_json["path"]);
|
||||||
|
if (signal.path.empty())
|
||||||
|
log_error("Failed to parse `%s`: Missing path for signal `%s`\n", filename.c_str(), signal_json.dump().c_str());
|
||||||
|
if (!signal_json["width"].is_number())
|
||||||
|
log_error("Failed to parse `%s`: Unknown width for signal `%s`\n", filename.c_str(), signal_json.dump().c_str());
|
||||||
|
signal.width = signal_json["width"].int_value();
|
||||||
|
if (signal.width < 0)
|
||||||
|
log_error("Failed to parse `%s`: Invalid width for signal `%s`\n", filename.c_str(), signal_json.dump().c_str());
|
||||||
|
bits_offset += signal.width;
|
||||||
|
if (!signal_json["offset"].is_number())
|
||||||
|
log_error("Failed to parse `%s`: Unknown offset for signal `%s`\n", filename.c_str(), signal_json.dump().c_str());
|
||||||
|
signal.offset = signal_json["offset"].int_value();
|
||||||
|
if (signal.offset < 0)
|
||||||
|
log_error("Failed to parse `%s`: Invalid offset for signal `%s`\n", filename.c_str(), signal_json.dump().c_str());
|
||||||
|
signal.init_only = signal_json["init_only"].bool_value();
|
||||||
|
signals.push_back(signal);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &step_json : json["steps"].array_items()) {
|
||||||
|
Step step;
|
||||||
|
if (!step_json["bits"].is_string())
|
||||||
|
log_error("Failed to parse `%s`: Expected string as bits value for step %d\n", filename.c_str(), GetSize(steps));
|
||||||
|
step.bits = step_json["bits"].string_value();
|
||||||
|
for (char c : step.bits) {
|
||||||
|
if (c != '0' && c != '1' && c != 'x' && c != '?')
|
||||||
|
log_error("Failed to parse `%s`: Invalid bit '%c' value for step %d\n", filename.c_str(), c, GetSize(steps));
|
||||||
|
}
|
||||||
|
steps.push_back(step);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RTLIL::Const ReadWitness::get_bits(int t, int bits_offset, int width) const
|
||||||
|
{
|
||||||
|
log_assert(t >= 0 && t < GetSize(steps));
|
||||||
|
|
||||||
|
const std::string &bits = steps[t].bits;
|
||||||
|
|
||||||
|
RTLIL::Const result(State::Sa, width);
|
||||||
|
result.bits.reserve(width);
|
||||||
|
|
||||||
|
int read_begin = GetSize(bits) - 1 - bits_offset;
|
||||||
|
int read_end = max(-1, read_begin - width);
|
||||||
|
|
||||||
|
min(width, GetSize(bits) - bits_offset);
|
||||||
|
|
||||||
|
for (int i = read_begin, j = 0; i > read_end; i--, j++) {
|
||||||
|
RTLIL::State bit = State::Sa;
|
||||||
|
switch (bits[i]) {
|
||||||
|
case '0': bit = State::S0; break;
|
||||||
|
case '1': bit = State::S1; break;
|
||||||
|
case 'x': bit = State::Sx; break;
|
||||||
|
case '?': bit = State::Sa; break;
|
||||||
|
default:
|
||||||
|
log_abort();
|
||||||
|
}
|
||||||
|
result.bits[j] = bit;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
|
@ -0,0 +1,182 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef YW_H
|
||||||
|
#define YW_H
|
||||||
|
|
||||||
|
#include "kernel/yosys.h"
|
||||||
|
#include "kernel/mem.h"
|
||||||
|
|
||||||
|
YOSYS_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
struct IdPath : public std::vector<RTLIL::IdString>
|
||||||
|
{
|
||||||
|
template<typename... T>
|
||||||
|
IdPath(T&&... args) : std::vector<RTLIL::IdString>(std::forward<T>(args)...) { }
|
||||||
|
IdPath prefix() const { return {begin(), end() - !empty()}; }
|
||||||
|
std::string str() const;
|
||||||
|
|
||||||
|
bool has_address() const { int tmp; return get_address(tmp); };
|
||||||
|
bool get_address(int &addr) const;
|
||||||
|
|
||||||
|
int hash() const { return hashlib::hash_ops<std::vector<RTLIL::IdString>>::hash(*this); }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct WitnessHierarchyItem {
|
||||||
|
RTLIL::Module *module;
|
||||||
|
RTLIL::Wire *wire = nullptr;
|
||||||
|
RTLIL::Cell *cell = nullptr;
|
||||||
|
Mem *mem = nullptr;
|
||||||
|
|
||||||
|
WitnessHierarchyItem(RTLIL::Module *module, RTLIL::Wire *wire) : module(module), wire(wire) {}
|
||||||
|
WitnessHierarchyItem(RTLIL::Module *module, RTLIL::Cell *cell) : module(module), cell(cell) {}
|
||||||
|
WitnessHierarchyItem(RTLIL::Module *module, Mem *mem) : module(module), mem(mem) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename D, typename T>
|
||||||
|
void witness_hierarchy(RTLIL::Module *module, D data, T callback);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ReadWitness
|
||||||
|
{
|
||||||
|
struct Clock {
|
||||||
|
IdPath path;
|
||||||
|
int offset;
|
||||||
|
bool is_posedge = false;
|
||||||
|
bool is_negedge = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Signal {
|
||||||
|
IdPath path;
|
||||||
|
int offset;
|
||||||
|
int width;
|
||||||
|
bool init_only;
|
||||||
|
|
||||||
|
int bits_offset;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Step {
|
||||||
|
std::string bits;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string filename;
|
||||||
|
std::vector<Clock> clocks;
|
||||||
|
std::vector<Signal> signals;
|
||||||
|
std::vector<Step> steps;
|
||||||
|
|
||||||
|
ReadWitness(const std::string &filename);
|
||||||
|
|
||||||
|
RTLIL::Const get_bits(int t, int bits_offset, int width) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename D, typename T>
|
||||||
|
void witness_hierarchy_recursion(IdPath &path, int hdlname_mode, RTLIL::Module *module, D data, T &callback)
|
||||||
|
{
|
||||||
|
auto const &const_path = path;
|
||||||
|
size_t path_size = path.size();
|
||||||
|
for (auto wire : module->wires())
|
||||||
|
{
|
||||||
|
auto hdlname = hdlname_mode < 0 ? std::vector<std::string>() : wire->get_hdlname_attribute();
|
||||||
|
for (auto item : hdlname)
|
||||||
|
path.push_back("\\" + item);
|
||||||
|
if (hdlname.size() == 1 && path.back() == wire->name)
|
||||||
|
hdlname.clear();
|
||||||
|
if (!hdlname.empty())
|
||||||
|
callback(const_path, WitnessHierarchyItem(module, wire), data);
|
||||||
|
path.resize(path_size);
|
||||||
|
if (hdlname.empty() || hdlname_mode <= 0) {
|
||||||
|
path.push_back(wire->name);
|
||||||
|
callback(const_path, WitnessHierarchyItem(module, wire), data);
|
||||||
|
path.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto cell : module->cells())
|
||||||
|
{
|
||||||
|
Module *child = module->design->module(cell->type);
|
||||||
|
if (child == nullptr)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto hdlname = hdlname_mode < 0 ? std::vector<std::string>() : cell->get_hdlname_attribute();
|
||||||
|
for (auto item : hdlname)
|
||||||
|
path.push_back("\\" + item);
|
||||||
|
if (hdlname.size() == 1 && path.back() == cell->name)
|
||||||
|
hdlname.clear();
|
||||||
|
if (!hdlname.empty()) {
|
||||||
|
D child_data = callback(const_path, WitnessHierarchyItem(module, cell), data);
|
||||||
|
witness_hierarchy_recursion<D, T>(path, 1, child, child_data, callback);
|
||||||
|
}
|
||||||
|
path.resize(path_size);
|
||||||
|
if (hdlname.empty() || hdlname_mode <= 0) {
|
||||||
|
path.push_back(cell->name);
|
||||||
|
D child_data = callback(const_path, WitnessHierarchyItem(module, cell), data);
|
||||||
|
witness_hierarchy_recursion<D, T>(path, hdlname.empty() ? hdlname_mode : -1, child, child_data, callback);
|
||||||
|
path.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto mem : Mem::get_all_memories(module)) {
|
||||||
|
std::vector<std::string> hdlname;
|
||||||
|
|
||||||
|
if (hdlname_mode >= 0 && mem.cell != nullptr)
|
||||||
|
hdlname = mem.cell->get_hdlname_attribute();
|
||||||
|
for (auto item : hdlname)
|
||||||
|
path.push_back("\\" + item);
|
||||||
|
if (hdlname.size() == 1 && path.back() == mem.cell->name)
|
||||||
|
hdlname.clear();
|
||||||
|
if (!hdlname.empty()) {
|
||||||
|
callback(const_path, WitnessHierarchyItem(module, &mem), data);
|
||||||
|
}
|
||||||
|
path.resize(path_size);
|
||||||
|
|
||||||
|
if (hdlname.empty() || hdlname_mode <= 0) {
|
||||||
|
path.push_back(mem.memid);
|
||||||
|
callback(const_path, WitnessHierarchyItem(module, &mem), data);
|
||||||
|
path.pop_back();
|
||||||
|
|
||||||
|
if (mem.cell != nullptr && mem.cell->name != mem.memid) {
|
||||||
|
path.push_back(mem.cell->name);
|
||||||
|
callback(const_path, WitnessHierarchyItem(module, &mem), data);
|
||||||
|
path.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename D, typename T>
|
||||||
|
void witness_hierarchy(RTLIL::Module *module, D data, T callback)
|
||||||
|
{
|
||||||
|
IdPath path;
|
||||||
|
witness_hierarchy_recursion<D, T>(path, 0, module, data, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
YOSYS_NAMESPACE_END
|
||||||
|
|
||||||
|
#endif
|
|
@ -502,7 +502,15 @@ struct SetundefPass : public Pass {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module->rewrite_sigspecs(worker);
|
for (auto &it : module->cells_)
|
||||||
|
if (!it.second->get_bool_attribute(ID::xprop_decoder))
|
||||||
|
it.second->rewrite_sigspecs(worker);
|
||||||
|
for (auto &it : module->processes)
|
||||||
|
it.second->rewrite_sigspecs(worker);
|
||||||
|
for (auto &it : module->connections_) {
|
||||||
|
worker(it.first);
|
||||||
|
worker(it.second);
|
||||||
|
}
|
||||||
|
|
||||||
if (worker.next_bit_mode == MODE_ANYSEQ || worker.next_bit_mode == MODE_ANYCONST)
|
if (worker.next_bit_mode == MODE_ANYSEQ || worker.next_bit_mode == MODE_ANYCONST)
|
||||||
{
|
{
|
||||||
|
|
|
@ -252,7 +252,8 @@ struct XpropWorker
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!driven_orig.empty()) {
|
if (!driven_orig.empty()) {
|
||||||
module->addBwmux(NEW_ID, driven_enc.is_1, Const(State::Sx, GetSize(driven_orig)), driven_enc.is_x, driven_orig);
|
auto decoder = module->addBwmux(NEW_ID, driven_enc.is_1, Const(State::Sx, GetSize(driven_orig)), driven_enc.is_x, driven_orig);
|
||||||
|
decoder->set_bool_attribute(ID::xprop_decoder);
|
||||||
}
|
}
|
||||||
if (!driven_never_x.first.empty()) {
|
if (!driven_never_x.first.empty()) {
|
||||||
module->connect(driven_never_x);
|
module->connect(driven_never_x);
|
||||||
|
|
|
@ -317,6 +317,172 @@ struct InitValWorker
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ReplacedPort {
|
||||||
|
IdString name;
|
||||||
|
int offset;
|
||||||
|
bool clk_pol;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HierarchyWorker
|
||||||
|
{
|
||||||
|
Design *design;
|
||||||
|
pool<Module *> pending;
|
||||||
|
|
||||||
|
dict<Module *, std::vector<ReplacedPort>> replaced_clk_inputs;
|
||||||
|
|
||||||
|
HierarchyWorker(Design *design) :
|
||||||
|
design(design)
|
||||||
|
{
|
||||||
|
for (auto module : design->modules())
|
||||||
|
pending.insert(module);
|
||||||
|
}
|
||||||
|
|
||||||
|
void propagate();
|
||||||
|
|
||||||
|
const std::vector<ReplacedPort> &find_replaced_clk_inputs(IdString cell_type);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Propagates replaced clock signals
|
||||||
|
struct PropagateWorker
|
||||||
|
{
|
||||||
|
HierarchyWorker &hierarchy;
|
||||||
|
|
||||||
|
Module *module;
|
||||||
|
SigMap sigmap;
|
||||||
|
|
||||||
|
dict<SigBit, bool> replaced_clk_bits;
|
||||||
|
dict<SigBit, SigBit> not_drivers;
|
||||||
|
|
||||||
|
std::vector<ReplacedPort> replaced_clk_inputs;
|
||||||
|
std::vector<std::pair<SigBit, bool>> pending;
|
||||||
|
|
||||||
|
PropagateWorker(Module *module, HierarchyWorker &hierarchy) :
|
||||||
|
hierarchy(hierarchy), module(module), sigmap(module)
|
||||||
|
{
|
||||||
|
hierarchy.pending.erase(module);
|
||||||
|
|
||||||
|
for (auto wire : module->wires())
|
||||||
|
if (wire->has_attribute(ID::replaced_by_gclk))
|
||||||
|
replace_clk_bit(SigBit(wire), wire->attributes[ID::replaced_by_gclk].bits.at(0) == State::S1, false);
|
||||||
|
|
||||||
|
for (auto cell : module->cells()) {
|
||||||
|
if (cell->type.in(ID($not), ID($_NOT_))) {
|
||||||
|
auto sig_a = cell->getPort(ID::A);
|
||||||
|
auto &sig_y = cell->getPort(ID::Y);
|
||||||
|
sig_a.extend_u0(GetSize(sig_y), cell->parameters.at(ID::A_SIGNED).as_bool());
|
||||||
|
|
||||||
|
for (int i = 0; i < GetSize(sig_a); i++)
|
||||||
|
if (sig_a[i].is_wire())
|
||||||
|
not_drivers.emplace(sigmap(sig_y[i]), sigmap(sig_a[i]));
|
||||||
|
} else {
|
||||||
|
for (auto &port_bit : hierarchy.find_replaced_clk_inputs(cell->type))
|
||||||
|
replace_clk_bit(cell->getPort(port_bit.name)[port_bit.offset], port_bit.clk_pol, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!pending.empty()) {
|
||||||
|
auto current = pending.back();
|
||||||
|
pending.pop_back();
|
||||||
|
auto it = not_drivers.find(current.first);
|
||||||
|
if (it == not_drivers.end())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
replace_clk_bit(it->second, !current.second, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto cell : module->cells()) {
|
||||||
|
if (cell->type.in(ID($not), ID($_NOT_)))
|
||||||
|
continue;
|
||||||
|
for (auto &conn : cell->connections()) {
|
||||||
|
if (!cell->output(conn.first))
|
||||||
|
continue;
|
||||||
|
for (SigBit bit : conn.second) {
|
||||||
|
sigmap.apply(bit);
|
||||||
|
if (replaced_clk_bits.count(bit))
|
||||||
|
log_error("derived signal %s driven by %s (%s) from module %s is used as clock, derived clocks are only supported with clk2fflogic.\n",
|
||||||
|
log_signal(bit), log_id(cell->name), log_id(cell->type), log_id(module));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto port : module->ports) {
|
||||||
|
auto wire = module->wire(port);
|
||||||
|
if (!wire->port_input)
|
||||||
|
continue;
|
||||||
|
for (int i = 0; i < GetSize(wire); i++) {
|
||||||
|
SigBit bit(wire, i);
|
||||||
|
sigmap.apply(bit);
|
||||||
|
auto it = replaced_clk_bits.find(bit);
|
||||||
|
if (it == replaced_clk_bits.end())
|
||||||
|
continue;
|
||||||
|
replaced_clk_inputs.emplace_back(ReplacedPort {port, i, it->second});
|
||||||
|
|
||||||
|
if (it->second) {
|
||||||
|
bit = module->Not(NEW_ID, bit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void replace_clk_bit(SigBit bit, bool polarity, bool add_attribute)
|
||||||
|
{
|
||||||
|
sigmap.apply(bit);
|
||||||
|
if (!bit.is_wire())
|
||||||
|
log_error("XXX todo\n");
|
||||||
|
|
||||||
|
auto it = replaced_clk_bits.find(bit);
|
||||||
|
if (it != replaced_clk_bits.end()) {
|
||||||
|
if (it->second != polarity)
|
||||||
|
log_error("signal %s from module %s is used as clock with different polarities, run clk2fflogic instead.\n",
|
||||||
|
log_signal(bit), log_id(module));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
replaced_clk_bits.emplace(bit, polarity);
|
||||||
|
|
||||||
|
if (add_attribute) {
|
||||||
|
Wire *clk_wire = bit.wire;
|
||||||
|
if (bit.offset != 0 || GetSize(bit.wire) != 1) {
|
||||||
|
clk_wire = module->addWire(NEW_ID);
|
||||||
|
module->connect(RTLIL::SigBit(clk_wire), bit);
|
||||||
|
}
|
||||||
|
clk_wire->attributes[ID::replaced_by_gclk] = polarity ? State::S1 : State::S0;
|
||||||
|
clk_wire->set_bool_attribute(ID::keep);
|
||||||
|
}
|
||||||
|
|
||||||
|
pending.emplace_back(bit, polarity);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::vector<ReplacedPort> &HierarchyWorker::find_replaced_clk_inputs(IdString cell_type)
|
||||||
|
{
|
||||||
|
static const std::vector<ReplacedPort> empty;
|
||||||
|
if (!cell_type.isPublic())
|
||||||
|
return empty;
|
||||||
|
|
||||||
|
Module *module = design->module(cell_type);
|
||||||
|
if (module == nullptr)
|
||||||
|
return empty;
|
||||||
|
|
||||||
|
auto it = replaced_clk_inputs.find(module);
|
||||||
|
if (it != replaced_clk_inputs.end())
|
||||||
|
return it->second;
|
||||||
|
|
||||||
|
if (pending.count(module)) {
|
||||||
|
PropagateWorker worker(module, *this);
|
||||||
|
return replaced_clk_inputs.emplace(module, std::move(worker.replaced_clk_inputs)).first->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
return empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void HierarchyWorker::propagate()
|
||||||
|
{
|
||||||
|
while (!pending.empty())
|
||||||
|
PropagateWorker worker(pending.pop(), *this);
|
||||||
|
}
|
||||||
|
|
||||||
struct FormalFfPass : public Pass {
|
struct FormalFfPass : public Pass {
|
||||||
FormalFfPass() : Pass("formalff", "prepare FFs for formal") { }
|
FormalFfPass() : Pass("formalff", "prepare FFs for formal") { }
|
||||||
void help() override
|
void help() override
|
||||||
|
@ -362,6 +528,15 @@ struct FormalFfPass : public Pass {
|
||||||
log(" them. For -ff2anyinit, this reduces the number of generated $anyinit\n");
|
log(" them. For -ff2anyinit, this reduces the number of generated $anyinit\n");
|
||||||
log(" cells that drive wires with private names.\n");
|
log(" cells that drive wires with private names.\n");
|
||||||
log("\n");
|
log("\n");
|
||||||
|
log(" -hierarchy\n");
|
||||||
|
log(" Propagates the 'replaced_by_gclk' attribute set by clk2ff upwards\n");
|
||||||
|
log(" through the design hierarchy towards the toplevel inputs. This option\n");
|
||||||
|
log(" works on the whole design and ignores the selection.\n");
|
||||||
|
log("\n");
|
||||||
|
log(" -assume\n");
|
||||||
|
log(" Add assumptions that constrain wires with the 'replaced_by_gclk'\n");
|
||||||
|
log(" attribute to the value they would have before an active clock edge.\n");
|
||||||
|
log("\n");
|
||||||
|
|
||||||
// TODO: An option to check whether all FFs use the same clock before changing it to the global clock
|
// TODO: An option to check whether all FFs use the same clock before changing it to the global clock
|
||||||
}
|
}
|
||||||
|
@ -372,6 +547,8 @@ struct FormalFfPass : public Pass {
|
||||||
bool flag_anyinit2ff = false;
|
bool flag_anyinit2ff = false;
|
||||||
bool flag_fine = false;
|
bool flag_fine = false;
|
||||||
bool flag_setundef = false;
|
bool flag_setundef = false;
|
||||||
|
bool flag_hierarchy = false;
|
||||||
|
bool flag_assume = false;
|
||||||
|
|
||||||
log_header(design, "Executing FORMALFF pass.\n");
|
log_header(design, "Executing FORMALFF pass.\n");
|
||||||
|
|
||||||
|
@ -398,12 +575,20 @@ struct FormalFfPass : public Pass {
|
||||||
flag_setundef = true;
|
flag_setundef = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (args[argidx] == "-hierarchy") {
|
||||||
|
flag_hierarchy = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (args[argidx] == "-assume") {
|
||||||
|
flag_assume = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
extra_args(args, argidx, design);
|
extra_args(args, argidx, design);
|
||||||
|
|
||||||
if (!(flag_clk2ff || flag_ff2anyinit || flag_anyinit2ff))
|
if (!(flag_clk2ff || flag_ff2anyinit || flag_anyinit2ff || flag_hierarchy || flag_assume))
|
||||||
log_cmd_error("One of the options -clk2ff, -ff2anyinit, or -anyinit2ff must be specified.\n");
|
log_cmd_error("One of the options -clk2ff, -ff2anyinit, -anyinit2ff, -hierarchy or -assume must be specified.\n");
|
||||||
|
|
||||||
if (flag_ff2anyinit && flag_anyinit2ff)
|
if (flag_ff2anyinit && flag_anyinit2ff)
|
||||||
log_cmd_error("The options -ff2anyinit and -anyinit2ff are exclusive.\n");
|
log_cmd_error("The options -ff2anyinit and -anyinit2ff are exclusive.\n");
|
||||||
|
@ -548,6 +733,33 @@ struct FormalFfPass : public Pass {
|
||||||
ff.emit();
|
ff.emit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (flag_hierarchy) {
|
||||||
|
HierarchyWorker worker(design);
|
||||||
|
worker.propagate();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flag_assume) {
|
||||||
|
for (auto module : design->selected_modules()) {
|
||||||
|
std::vector<pair<SigBit, bool>> found;
|
||||||
|
for (auto wire : module->wires()) {
|
||||||
|
if (!wire->has_attribute(ID::replaced_by_gclk))
|
||||||
|
continue;
|
||||||
|
bool clk_pol = wire->attributes[ID::replaced_by_gclk].bits.at(0) == State::S1;
|
||||||
|
|
||||||
|
found.emplace_back(SigSpec(wire), clk_pol);
|
||||||
|
}
|
||||||
|
for (auto pair : found) {
|
||||||
|
SigBit clk = pair.first;
|
||||||
|
|
||||||
|
if (pair.second)
|
||||||
|
clk = module->Not(NEW_ID, clk);
|
||||||
|
|
||||||
|
module->addAssume(NEW_ID, clk, State::S1);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} FormalFfPass;
|
} FormalFfPass;
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,8 @@
|
||||||
#include "kernel/mem.h"
|
#include "kernel/mem.h"
|
||||||
#include "kernel/fstdata.h"
|
#include "kernel/fstdata.h"
|
||||||
#include "kernel/ff.h"
|
#include "kernel/ff.h"
|
||||||
|
#include "kernel/yw.h"
|
||||||
|
#include "kernel/json.h"
|
||||||
|
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
|
|
||||||
|
@ -74,6 +76,17 @@ struct OutputWriter
|
||||||
SimWorker *worker;
|
SimWorker *worker;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct SimInstance;
|
||||||
|
struct TriggeredAssertion {
|
||||||
|
int step;
|
||||||
|
SimInstance *instance;
|
||||||
|
Cell *cell;
|
||||||
|
|
||||||
|
TriggeredAssertion(int step, SimInstance *instance, Cell *cell) :
|
||||||
|
step(step), instance(instance), cell(cell)
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
|
||||||
struct SimShared
|
struct SimShared
|
||||||
{
|
{
|
||||||
bool debug = false;
|
bool debug = false;
|
||||||
|
@ -93,6 +106,9 @@ struct SimShared
|
||||||
bool ignore_x = false;
|
bool ignore_x = false;
|
||||||
bool date = false;
|
bool date = false;
|
||||||
bool multiclock = false;
|
bool multiclock = false;
|
||||||
|
int next_output_id = 0;
|
||||||
|
int step = 0;
|
||||||
|
std::vector<TriggeredAssertion> triggered_assertions;
|
||||||
};
|
};
|
||||||
|
|
||||||
void zinit(State &v)
|
void zinit(State &v)
|
||||||
|
@ -152,11 +168,14 @@ struct SimInstance
|
||||||
dict<Cell*, ff_state_t> ff_database;
|
dict<Cell*, ff_state_t> ff_database;
|
||||||
dict<IdString, mem_state_t> mem_database;
|
dict<IdString, mem_state_t> mem_database;
|
||||||
pool<Cell*> formal_database;
|
pool<Cell*> formal_database;
|
||||||
|
pool<Cell*> initstate_database;
|
||||||
dict<Cell*, IdString> mem_cells;
|
dict<Cell*, IdString> mem_cells;
|
||||||
|
|
||||||
std::vector<Mem> memories;
|
std::vector<Mem> memories;
|
||||||
|
|
||||||
dict<Wire*, pair<int, Const>> signal_database;
|
dict<Wire*, pair<int, Const>> signal_database;
|
||||||
|
dict<IdString, std::map<int, pair<int, Const>>> trace_mem_database;
|
||||||
|
dict<std::pair<IdString, int>, Const> trace_mem_init_database;
|
||||||
dict<Wire*, fstHandle> fst_handles;
|
dict<Wire*, fstHandle> fst_handles;
|
||||||
dict<Wire*, fstHandle> fst_inputs;
|
dict<Wire*, fstHandle> fst_inputs;
|
||||||
dict<IdString, dict<int,fstHandle>> fst_memories;
|
dict<IdString, dict<int,fstHandle>> fst_memories;
|
||||||
|
@ -254,6 +273,8 @@ struct SimInstance
|
||||||
if (cell->type.in(ID($assert), ID($cover), ID($assume))) {
|
if (cell->type.in(ID($assert), ID($cover), ID($assume))) {
|
||||||
formal_database.insert(cell);
|
formal_database.insert(cell);
|
||||||
}
|
}
|
||||||
|
if (cell->type == ID($initstate))
|
||||||
|
initstate_database.insert(cell);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shared->zinit)
|
if (shared->zinit)
|
||||||
|
@ -300,6 +321,21 @@ struct SimInstance
|
||||||
return log_id(module->name);
|
return log_id(module->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vector<std::string> witness_full_path() const
|
||||||
|
{
|
||||||
|
if (instance != nullptr)
|
||||||
|
return parent->witness_full_path(instance);
|
||||||
|
return vector<std::string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<std::string> witness_full_path(Cell *cell) const
|
||||||
|
{
|
||||||
|
auto result = witness_full_path();
|
||||||
|
auto cell_path = witness_path(cell);
|
||||||
|
result.insert(result.end(), cell_path.begin(), cell_path.end());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
Const get_state(SigSpec sig)
|
Const get_state(SigSpec sig)
|
||||||
{
|
{
|
||||||
Const value;
|
Const value;
|
||||||
|
@ -325,7 +361,7 @@ struct SimInstance
|
||||||
log_assert(GetSize(sig) <= GetSize(value));
|
log_assert(GetSize(sig) <= GetSize(value));
|
||||||
|
|
||||||
for (int i = 0; i < GetSize(sig); i++)
|
for (int i = 0; i < GetSize(sig); i++)
|
||||||
if (state_nets.at(sig[i]) != value[i]) {
|
if (value[i] != State::Sa && state_nets.at(sig[i]) != value[i]) {
|
||||||
state_nets.at(sig[i]) = value[i];
|
state_nets.at(sig[i]) = value[i];
|
||||||
dirty_bits.insert(sig[i]);
|
dirty_bits.insert(sig[i]);
|
||||||
did_something = true;
|
did_something = true;
|
||||||
|
@ -337,13 +373,24 @@ struct SimInstance
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_memory_state(IdString memid, Const addr, Const data)
|
void set_memory_state(IdString memid, Const addr, Const data)
|
||||||
|
{
|
||||||
|
set_memory_state(memid, addr.as_int(), data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_memory_state(IdString memid, int addr, Const data)
|
||||||
{
|
{
|
||||||
auto &state = mem_database[memid];
|
auto &state = mem_database[memid];
|
||||||
|
|
||||||
int offset = (addr.as_int() - state.mem->start_offset) * state.mem->width;
|
bool dirty = false;
|
||||||
|
|
||||||
|
int offset = (addr - state.mem->start_offset) * state.mem->width;
|
||||||
for (int i = 0; i < GetSize(data); i++)
|
for (int i = 0; i < GetSize(data); i++)
|
||||||
if (0 <= i+offset && i+offset < state.mem->size * state.mem->width)
|
if (0 <= i+offset && i+offset < state.mem->size * state.mem->width && data.bits[i] != State::Sa)
|
||||||
state.data.bits[i+offset] = data.bits[i];
|
if (state.data.bits[i+offset] != data.bits[i])
|
||||||
|
dirty = true, state.data.bits[i+offset] = data.bits[i];
|
||||||
|
|
||||||
|
if (dirty)
|
||||||
|
dirty_memories.insert(memid);
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_memory_state_bit(IdString memid, int offset, State data)
|
void set_memory_state_bit(IdString memid, int offset, State data)
|
||||||
|
@ -351,7 +398,10 @@ struct SimInstance
|
||||||
auto &state = mem_database[memid];
|
auto &state = mem_database[memid];
|
||||||
if (offset >= state.mem->size * state.mem->width)
|
if (offset >= state.mem->size * state.mem->width)
|
||||||
log_error("Addressing out of bounds bit %d/%d of memory %s\n", offset, state.mem->size * state.mem->width, log_id(memid));
|
log_error("Addressing out of bounds bit %d/%d of memory %s\n", offset, state.mem->size * state.mem->width, log_id(memid));
|
||||||
|
if (state.data.bits[offset] != data) {
|
||||||
state.data.bits[offset] = data;
|
state.data.bits[offset] = data;
|
||||||
|
dirty_memories.insert(memid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void update_cell(Cell *cell)
|
void update_cell(Cell *cell)
|
||||||
|
@ -447,9 +497,14 @@ struct SimInstance
|
||||||
log_error("Memory %s.%s has clocked read ports. Run 'memory' with -nordff.\n", log_id(module), log_id(mem.memid));
|
log_error("Memory %s.%s has clocked read ports. Run 'memory' with -nordff.\n", log_id(module), log_id(mem.memid));
|
||||||
|
|
||||||
if (addr.is_fully_def()) {
|
if (addr.is_fully_def()) {
|
||||||
int index = addr.as_int() - mem.start_offset;
|
int addr_int = addr.as_int();
|
||||||
|
int index = addr_int - mem.start_offset;
|
||||||
if (index >= 0 && index < mem.size)
|
if (index >= 0 && index < mem.size)
|
||||||
data = mdb.data.extract(index*mem.width, mem.width << port.wide_log2);
|
data = mdb.data.extract(index*mem.width, mem.width << port.wide_log2);
|
||||||
|
|
||||||
|
for (int offset = 0; offset < 1 << port.wide_log2; offset++) {
|
||||||
|
register_memory_addr(id, addr_int + offset);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
set_state(port.data, data);
|
set_state(port.data, data);
|
||||||
|
@ -604,7 +659,8 @@ struct SimInstance
|
||||||
|
|
||||||
if (addr.is_fully_def())
|
if (addr.is_fully_def())
|
||||||
{
|
{
|
||||||
int index = addr.as_int() - mem.start_offset;
|
int addr_int = addr.as_int();
|
||||||
|
int index = addr_int - mem.start_offset;
|
||||||
if (index >= 0 && index < mem.size)
|
if (index >= 0 && index < mem.size)
|
||||||
for (int i = 0; i < (mem.width << port.wide_log2); i++)
|
for (int i = 0; i < (mem.width << port.wide_log2); i++)
|
||||||
if (enable[i] == State::S1 && mdb.data.bits.at(index*mem.width+i) != data[i]) {
|
if (enable[i] == State::S1 && mdb.data.bits.at(index*mem.width+i) != data[i]) {
|
||||||
|
@ -612,6 +668,9 @@ struct SimInstance
|
||||||
dirty_memories.insert(mem.memid);
|
dirty_memories.insert(mem.memid);
|
||||||
did_something = true;
|
did_something = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 1 << port.wide_log2; i++)
|
||||||
|
register_memory_addr(it.first, addr_int + i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -625,7 +684,7 @@ struct SimInstance
|
||||||
return did_something;
|
return did_something;
|
||||||
}
|
}
|
||||||
|
|
||||||
void update_ph3()
|
void update_ph3(bool check_assertions)
|
||||||
{
|
{
|
||||||
for (auto &it : ff_database)
|
for (auto &it : ff_database)
|
||||||
{
|
{
|
||||||
|
@ -660,6 +719,8 @@ struct SimInstance
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (check_assertions)
|
||||||
|
{
|
||||||
for (auto cell : formal_database)
|
for (auto cell : formal_database)
|
||||||
{
|
{
|
||||||
string label = log_id(cell);
|
string label = log_id(cell);
|
||||||
|
@ -669,7 +730,11 @@ struct SimInstance
|
||||||
State a = get_state(cell->getPort(ID::A))[0];
|
State a = get_state(cell->getPort(ID::A))[0];
|
||||||
State en = get_state(cell->getPort(ID::EN))[0];
|
State en = get_state(cell->getPort(ID::EN))[0];
|
||||||
|
|
||||||
if (cell->type == ID($cover) && en == State::S1 && a != State::S1)
|
if (en == State::S1 && (cell->type == ID($cover) ? a == State::S1 : a != State::S1)) {
|
||||||
|
shared->triggered_assertions.emplace_back(shared->step, this, cell);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cell->type == ID($cover) && en == State::S1 && a == State::S1)
|
||||||
log("Cover %s.%s (%s) reached.\n", hiername().c_str(), log_id(cell), label.c_str());
|
log("Cover %s.%s (%s) reached.\n", hiername().c_str(), log_id(cell), label.c_str());
|
||||||
|
|
||||||
if (cell->type == ID($assume) && en == State::S1 && a != State::S1)
|
if (cell->type == ID($assume) && en == State::S1 && a != State::S1)
|
||||||
|
@ -678,9 +743,18 @@ struct SimInstance
|
||||||
if (cell->type == ID($assert) && en == State::S1 && a != State::S1)
|
if (cell->type == ID($assert) && en == State::S1 && a != State::S1)
|
||||||
log_warning("Assert %s.%s (%s) failed.\n", hiername().c_str(), log_id(cell), label.c_str());
|
log_warning("Assert %s.%s (%s) failed.\n", hiername().c_str(), log_id(cell), label.c_str());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (auto it : children)
|
for (auto it : children)
|
||||||
it.second->update_ph3();
|
it.second->update_ph3(check_assertions);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_initstate_outputs(State state)
|
||||||
|
{
|
||||||
|
for (auto cell : initstate_database)
|
||||||
|
set_state(cell->getPort(ID::Y), state);
|
||||||
|
for (auto child : children)
|
||||||
|
child.second->set_initstate_outputs(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
void writeback(pool<Module*> &wbmods)
|
void writeback(pool<Module*> &wbmods)
|
||||||
|
@ -741,7 +815,7 @@ struct SimInstance
|
||||||
child.second->register_signals(id);
|
child.second->register_signals(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
void write_output_header(std::function<void(IdString)> enter_scope, std::function<void()> exit_scope, std::function<void(const char*, Wire*, int, bool)> register_signal)
|
void write_output_header(std::function<void(IdString)> enter_scope, std::function<void()> exit_scope, std::function<void(const char*, int, Wire*, int, bool)> register_signal)
|
||||||
{
|
{
|
||||||
int exit_scopes = 1;
|
int exit_scopes = 1;
|
||||||
if (shared->hdlname && instance != nullptr && instance->name.isPublic() && instance->has_attribute(ID::hdlname)) {
|
if (shared->hdlname && instance != nullptr && instance->name.isPublic() && instance->has_attribute(ID::hdlname)) {
|
||||||
|
@ -774,11 +848,45 @@ struct SimInstance
|
||||||
hdlname.pop_back();
|
hdlname.pop_back();
|
||||||
for (auto name : hdlname)
|
for (auto name : hdlname)
|
||||||
enter_scope("\\" + name);
|
enter_scope("\\" + name);
|
||||||
register_signal(signal_name.c_str(), signal.first, signal.second.first, registers.count(signal.first)!=0);
|
register_signal(signal_name.c_str(), GetSize(signal.first), signal.first, signal.second.first, registers.count(signal.first)!=0);
|
||||||
for (auto name : hdlname)
|
for (auto name : hdlname)
|
||||||
exit_scope();
|
exit_scope();
|
||||||
} else
|
} else
|
||||||
register_signal(log_id(signal.first->name), signal.first, signal.second.first, registers.count(signal.first)!=0);
|
register_signal(log_id(signal.first->name), GetSize(signal.first), signal.first, signal.second.first, registers.count(signal.first)!=0);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &trace_mem : trace_mem_database)
|
||||||
|
{
|
||||||
|
auto memid = trace_mem.first;
|
||||||
|
auto &mdb = mem_database.at(memid);
|
||||||
|
Cell *cell = mdb.mem->cell;
|
||||||
|
|
||||||
|
std::vector<std::string> hdlname;
|
||||||
|
std::string signal_name;
|
||||||
|
bool has_hdlname = shared->hdlname && cell != nullptr && cell->name.isPublic() && cell->has_attribute(ID::hdlname);
|
||||||
|
|
||||||
|
if (has_hdlname) {
|
||||||
|
hdlname = cell->get_hdlname_attribute();
|
||||||
|
log_assert(!hdlname.empty());
|
||||||
|
signal_name = std::move(hdlname.back());
|
||||||
|
hdlname.pop_back();
|
||||||
|
for (auto name : hdlname)
|
||||||
|
enter_scope("\\" + name);
|
||||||
|
} else {
|
||||||
|
signal_name = log_id(memid);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &trace_index : trace_mem.second) {
|
||||||
|
int output_id = trace_index.second.first;
|
||||||
|
int index = trace_index.first;
|
||||||
|
register_signal(
|
||||||
|
stringf("%s[%d]", signal_name.c_str(), (index + mdb.mem->start_offset)).c_str(),
|
||||||
|
mdb.mem->width, nullptr, output_id, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (has_hdlname)
|
||||||
|
for (auto name : hdlname)
|
||||||
|
exit_scope();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto child : children)
|
for (auto child : children)
|
||||||
|
@ -788,6 +896,30 @@ struct SimInstance
|
||||||
exit_scope();
|
exit_scope();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void register_memory_addr(IdString memid, int addr)
|
||||||
|
{
|
||||||
|
auto &mdb = mem_database.at(memid);
|
||||||
|
auto &mem = *mdb.mem;
|
||||||
|
int index = addr - mem.start_offset;
|
||||||
|
if (index < 0 || index >= mem.size)
|
||||||
|
return;
|
||||||
|
auto it = trace_mem_database.find(memid);
|
||||||
|
if (it != trace_mem_database.end() && it->second.count(index))
|
||||||
|
return;
|
||||||
|
int output_id = shared->next_output_id++;
|
||||||
|
Const data;
|
||||||
|
if (!shared->output_data.empty()) {
|
||||||
|
auto init_it = trace_mem_init_database.find(std::make_pair(memid, addr));
|
||||||
|
if (init_it != trace_mem_init_database.end())
|
||||||
|
data = init_it->second;
|
||||||
|
else
|
||||||
|
data = mem.get_init_data().extract(index * mem.width, mem.width);
|
||||||
|
shared->output_data.front().second.emplace(output_id, data);
|
||||||
|
}
|
||||||
|
trace_mem_database[memid].emplace(index, make_pair(output_id, data));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void register_output_step_values(std::map<int,Const> *data)
|
void register_output_step_values(std::map<int,Const> *data)
|
||||||
{
|
{
|
||||||
for (auto &it : signal_database)
|
for (auto &it : signal_database)
|
||||||
|
@ -803,6 +935,26 @@ struct SimInstance
|
||||||
data->emplace(id, value);
|
data->emplace(id, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (auto &trace_mem : trace_mem_database)
|
||||||
|
{
|
||||||
|
auto memid = trace_mem.first;
|
||||||
|
auto &mdb = mem_database.at(memid);
|
||||||
|
auto &mem = *mdb.mem;
|
||||||
|
for (auto &trace_index : trace_mem.second)
|
||||||
|
{
|
||||||
|
int output_id = trace_index.second.first;
|
||||||
|
int index = trace_index.first;
|
||||||
|
|
||||||
|
auto value = mdb.data.extract(index * mem.width, mem.width);
|
||||||
|
|
||||||
|
if (trace_index.second.second == value)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
trace_index.second.second = value;
|
||||||
|
data->emplace(output_id, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (auto child : children)
|
for (auto child : children)
|
||||||
child.second->register_output_step_values(data);
|
child.second->register_output_step_values(data);
|
||||||
}
|
}
|
||||||
|
@ -946,6 +1098,7 @@ struct SimWorker : SimShared
|
||||||
std::string timescale;
|
std::string timescale;
|
||||||
std::string sim_filename;
|
std::string sim_filename;
|
||||||
std::string map_filename;
|
std::string map_filename;
|
||||||
|
std::string summary_filename;
|
||||||
std::string scope;
|
std::string scope;
|
||||||
|
|
||||||
~SimWorker()
|
~SimWorker()
|
||||||
|
@ -956,8 +1109,8 @@ struct SimWorker : SimShared
|
||||||
|
|
||||||
void register_signals()
|
void register_signals()
|
||||||
{
|
{
|
||||||
int id = 1;
|
next_output_id = 1;
|
||||||
top->register_signals(id);
|
top->register_signals(top->shared->next_output_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
void register_output_step(int t)
|
void register_output_step(int t)
|
||||||
|
@ -989,6 +1142,9 @@ struct SimWorker : SimShared
|
||||||
|
|
||||||
void update(bool gclk)
|
void update(bool gclk)
|
||||||
{
|
{
|
||||||
|
if (gclk)
|
||||||
|
step += 1;
|
||||||
|
|
||||||
while (1)
|
while (1)
|
||||||
{
|
{
|
||||||
if (debug)
|
if (debug)
|
||||||
|
@ -1006,7 +1162,7 @@ struct SimWorker : SimShared
|
||||||
if (debug)
|
if (debug)
|
||||||
log("\n-- ph3 --\n");
|
log("\n-- ph3 --\n");
|
||||||
|
|
||||||
top->update_ph3();
|
top->update_ph3(gclk);
|
||||||
}
|
}
|
||||||
|
|
||||||
void initialize_stable_past()
|
void initialize_stable_past()
|
||||||
|
@ -1016,7 +1172,7 @@ struct SimWorker : SimShared
|
||||||
top->update_ph1();
|
top->update_ph1();
|
||||||
if (debug)
|
if (debug)
|
||||||
log("\n-- ph3 (initialize) --\n");
|
log("\n-- ph3 (initialize) --\n");
|
||||||
top->update_ph3();
|
top->update_ph3(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_inports(pool<IdString> ports, State value)
|
void set_inports(pool<IdString> ports, State value)
|
||||||
|
@ -1490,6 +1646,242 @@ struct SimWorker : SimShared
|
||||||
write_output_files();
|
write_output_files();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct FoundYWPath
|
||||||
|
{
|
||||||
|
SimInstance *instance;
|
||||||
|
Wire *wire;
|
||||||
|
IdString memid;
|
||||||
|
int addr;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct YwHierarchy {
|
||||||
|
dict<IdPath, FoundYWPath> paths;
|
||||||
|
};
|
||||||
|
|
||||||
|
YwHierarchy prepare_yw_hierarchy(const ReadWitness &yw)
|
||||||
|
{
|
||||||
|
YwHierarchy hierarchy;
|
||||||
|
pool<IdPath> paths;
|
||||||
|
dict<IdPath, pool<IdString>> mem_paths;
|
||||||
|
|
||||||
|
for (auto &signal : yw.signals)
|
||||||
|
paths.insert(signal.path);
|
||||||
|
|
||||||
|
for (auto &clock : yw.clocks)
|
||||||
|
paths.insert(clock.path);
|
||||||
|
|
||||||
|
for (auto &path : paths)
|
||||||
|
if (path.has_address())
|
||||||
|
mem_paths[path.prefix()].insert(path.back());
|
||||||
|
|
||||||
|
witness_hierarchy(top->module, top, [&](IdPath const &path, WitnessHierarchyItem item, SimInstance *instance) {
|
||||||
|
if (item.cell != nullptr)
|
||||||
|
return instance->children.at(item.cell);
|
||||||
|
if (item.wire != nullptr) {
|
||||||
|
if (paths.count(path)) {
|
||||||
|
if (debug)
|
||||||
|
log("witness hierarchy: found wire %s\n", path.str().c_str());
|
||||||
|
bool inserted = hierarchy.paths.emplace(path, {instance, item.wire, {}, INT_MIN}).second;
|
||||||
|
if (!inserted)
|
||||||
|
log_warning("Yosys witness path `%s` is ambiguous in this design\n", path.str().c_str());
|
||||||
|
}
|
||||||
|
} else if (item.mem) {
|
||||||
|
auto it = mem_paths.find(path);
|
||||||
|
if (it != mem_paths.end()) {
|
||||||
|
if (debug)
|
||||||
|
log("witness hierarchy: found mem %s\n", path.str().c_str());
|
||||||
|
IdPath word_path = path;
|
||||||
|
word_path.emplace_back();
|
||||||
|
for (auto addr_part : it->second) {
|
||||||
|
word_path.back() = addr_part;
|
||||||
|
int addr;
|
||||||
|
word_path.get_address(addr);
|
||||||
|
if (addr < item.mem->start_offset || (addr - item.mem->start_offset) >= item.mem->size)
|
||||||
|
continue;
|
||||||
|
bool inserted = hierarchy.paths.emplace(word_path, {instance, nullptr, item.mem->memid, addr}).second;
|
||||||
|
if (!inserted)
|
||||||
|
log_warning("Yosys witness path `%s` is ambiguous in this design\n", path.str().c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (auto &path : paths)
|
||||||
|
if (!hierarchy.paths.count(path))
|
||||||
|
log_warning("Yosys witness path `%s` was not found in this design, ignoring\n", path.str().c_str());
|
||||||
|
|
||||||
|
dict<IdPath, dict<int, bool>> clock_inputs;
|
||||||
|
|
||||||
|
for (auto &clock : yw.clocks) {
|
||||||
|
if (clock.is_negedge == clock.is_posedge)
|
||||||
|
continue;
|
||||||
|
clock_inputs[clock.path].emplace(clock.offset, clock.is_posedge);
|
||||||
|
}
|
||||||
|
for (auto &signal : yw.signals) {
|
||||||
|
auto it = clock_inputs.find(signal.path);
|
||||||
|
if (it == clock_inputs.end())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (auto &clock_input : it->second) {
|
||||||
|
int offset = clock_input.first;
|
||||||
|
if (offset >= signal.offset && (offset - signal.offset) < signal.width) {
|
||||||
|
int clock_bits_offset = signal.bits_offset + (offset - signal.offset);
|
||||||
|
|
||||||
|
State expected = clock_input.second ? State::S0 : State::S1;
|
||||||
|
|
||||||
|
for (int t = 0; t < GetSize(yw.steps); t++) {
|
||||||
|
if (yw.get_bits(t, clock_bits_offset, 1) != expected)
|
||||||
|
log_warning("Yosys witness trace has an unexpected value for the clock input `%s` in step %d.\n", signal.path.str().c_str(), t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO add checks and warnings for witness signals (toplevel inputs, $any*) not present in the witness file
|
||||||
|
|
||||||
|
return hierarchy;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_yw_state(const ReadWitness &yw, const YwHierarchy &hierarchy, int t)
|
||||||
|
{
|
||||||
|
log_assert(t >= 0 && t < GetSize(yw.steps));
|
||||||
|
|
||||||
|
for (auto &signal : yw.signals) {
|
||||||
|
if (signal.init_only && t >= 1)
|
||||||
|
continue;
|
||||||
|
auto found_path_it = hierarchy.paths.find(signal.path);
|
||||||
|
if (found_path_it == hierarchy.paths.end())
|
||||||
|
continue;
|
||||||
|
auto &found_path = found_path_it->second;
|
||||||
|
|
||||||
|
Const value = yw.get_bits(t, signal.bits_offset, signal.width);
|
||||||
|
|
||||||
|
if (debug)
|
||||||
|
log("yw: set %s to %s\n", signal.path.str().c_str(), log_const(value));
|
||||||
|
|
||||||
|
if (found_path.wire != nullptr) {
|
||||||
|
found_path.instance->set_state(
|
||||||
|
SigChunk(found_path.wire, signal.offset, signal.width),
|
||||||
|
value);
|
||||||
|
} else if (!found_path.memid.empty()) {
|
||||||
|
if (t >= 1)
|
||||||
|
found_path.instance->register_memory_addr(found_path.memid, found_path.addr);
|
||||||
|
else
|
||||||
|
found_path.instance->trace_mem_init_database.emplace(make_pair(found_path.memid, found_path.addr), value);
|
||||||
|
found_path.instance->set_memory_state(
|
||||||
|
found_path.memid, found_path.addr,
|
||||||
|
value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_yw_clocks(const ReadWitness &yw, const YwHierarchy &hierarchy, bool active_edge)
|
||||||
|
{
|
||||||
|
for (auto &clock : yw.clocks) {
|
||||||
|
if (clock.is_negedge == clock.is_posedge)
|
||||||
|
continue;
|
||||||
|
auto found_path_it = hierarchy.paths.find(clock.path);
|
||||||
|
if (found_path_it == hierarchy.paths.end())
|
||||||
|
continue;
|
||||||
|
auto &found_path = found_path_it->second;
|
||||||
|
|
||||||
|
if (found_path.wire != nullptr) {
|
||||||
|
found_path.instance->set_state(
|
||||||
|
SigChunk(found_path.wire, clock.offset, 1),
|
||||||
|
active_edge == clock.is_posedge ? State::S1 : State::S0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void run_cosim_yw_witness(Module *topmod, int append)
|
||||||
|
{
|
||||||
|
if (!clock.empty())
|
||||||
|
log_cmd_error("The -clock option is not required nor supported when reading a Yosys witness file.\n");
|
||||||
|
if (!reset.empty())
|
||||||
|
log_cmd_error("The -reset option is not required nor supported when reading a Yosys witness file.\n");
|
||||||
|
if (multiclock)
|
||||||
|
log_warning("The -multiclock option is not required and ignored when reading a Yosys witness file.\n");
|
||||||
|
|
||||||
|
ReadWitness yw(sim_filename);
|
||||||
|
|
||||||
|
top = new SimInstance(this, scope, topmod);
|
||||||
|
register_signals();
|
||||||
|
|
||||||
|
YwHierarchy hierarchy = prepare_yw_hierarchy(yw);
|
||||||
|
|
||||||
|
if (yw.steps.empty()) {
|
||||||
|
log_warning("Yosys witness file `%s` contains no time steps\n", yw.filename.c_str());
|
||||||
|
} else {
|
||||||
|
top->set_initstate_outputs(State::S1);
|
||||||
|
set_yw_state(yw, hierarchy, 0);
|
||||||
|
set_yw_clocks(yw, hierarchy, true);
|
||||||
|
initialize_stable_past();
|
||||||
|
register_output_step(0);
|
||||||
|
|
||||||
|
if (!yw.clocks.empty()) {
|
||||||
|
if (debug)
|
||||||
|
log("Simulating non-active clock edge.\n");
|
||||||
|
set_yw_clocks(yw, hierarchy, false);
|
||||||
|
update(false);
|
||||||
|
register_output_step(5);
|
||||||
|
}
|
||||||
|
top->set_initstate_outputs(State::S0);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int cycle = 1; cycle < GetSize(yw.steps) + append; cycle++)
|
||||||
|
{
|
||||||
|
if (verbose)
|
||||||
|
log("Simulating cycle %d.\n", cycle);
|
||||||
|
if (cycle < GetSize(yw.steps))
|
||||||
|
set_yw_state(yw, hierarchy, cycle);
|
||||||
|
set_yw_clocks(yw, hierarchy, true);
|
||||||
|
update(true);
|
||||||
|
register_output_step(10 * cycle);
|
||||||
|
|
||||||
|
if (!yw.clocks.empty()) {
|
||||||
|
if (debug)
|
||||||
|
log("Simulating non-active clock edge.\n");
|
||||||
|
set_yw_clocks(yw, hierarchy, false);
|
||||||
|
update(false);
|
||||||
|
register_output_step(5 + 10 * cycle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
register_output_step(10 * (GetSize(yw.steps) + append));
|
||||||
|
write_output_files();
|
||||||
|
}
|
||||||
|
|
||||||
|
void write_summary()
|
||||||
|
{
|
||||||
|
if (summary_filename.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
PrettyJson json;
|
||||||
|
if (!json.write_to_file(summary_filename))
|
||||||
|
log_error("Can't open file `%s' for writing: %s\n", summary_filename.c_str(), strerror(errno));
|
||||||
|
|
||||||
|
json.begin_object();
|
||||||
|
json.entry("version", "Yosys sim summary");
|
||||||
|
json.entry("generator", yosys_version_str);
|
||||||
|
json.entry("steps", step);
|
||||||
|
json.entry("top", log_id(top->module->name));
|
||||||
|
json.name("assertions");
|
||||||
|
json.begin_array();
|
||||||
|
for (auto &assertion : triggered_assertions) {
|
||||||
|
json.begin_object();
|
||||||
|
json.entry("step", assertion.step);
|
||||||
|
json.entry("type", log_id(assertion.cell->type));
|
||||||
|
json.entry("path", assertion.instance->witness_full_path(assertion.cell));
|
||||||
|
auto src = assertion.cell->get_string_attribute(ID::src);
|
||||||
|
if (!src.empty()) {
|
||||||
|
json.entry("src", src);
|
||||||
|
}
|
||||||
|
json.end_object();
|
||||||
|
}
|
||||||
|
json.end_array();
|
||||||
|
json.end_object();
|
||||||
|
}
|
||||||
|
|
||||||
std::string define_signal(Wire *wire)
|
std::string define_signal(Wire *wire)
|
||||||
{
|
{
|
||||||
std::stringstream f;
|
std::stringstream f;
|
||||||
|
@ -1734,9 +2126,15 @@ struct VCDWriter : public OutputWriter
|
||||||
worker->top->write_output_header(
|
worker->top->write_output_header(
|
||||||
[this](IdString name) { vcdfile << stringf("$scope module %s $end\n", log_id(name)); },
|
[this](IdString name) { vcdfile << stringf("$scope module %s $end\n", log_id(name)); },
|
||||||
[this]() { vcdfile << stringf("$upscope $end\n");},
|
[this]() { vcdfile << stringf("$upscope $end\n");},
|
||||||
[this,use_signal](const char *name, Wire *wire, int id, bool is_reg) {
|
[this,use_signal](const char *name, int size, Wire *, int id, bool is_reg) {
|
||||||
if (use_signal.at(id)) {
|
if (use_signal.at(id)) {
|
||||||
vcdfile << stringf("$var %s %d n%d %s%s $end\n", is_reg ? "reg" : "wire", GetSize(wire), id, name[0] == '$' ? "\\" : "", name);
|
// Works around gtkwave trying to parse everything past the last [ in a signal
|
||||||
|
// name. While the emitted range doesn't necessarily match the wire's range,
|
||||||
|
// this is consistent with the range gtkwave makes up if it doesn't find a
|
||||||
|
// range
|
||||||
|
std::string range = strchr(name, '[') ? stringf("[%d:0]", size - 1) : std::string();
|
||||||
|
vcdfile << stringf("$var %s %d n%d %s%s%s $end\n", is_reg ? "reg" : "wire", size, id, name[0] == '$' ? "\\" : "", name, range.c_str());
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -1796,9 +2194,9 @@ struct FSTWriter : public OutputWriter
|
||||||
worker->top->write_output_header(
|
worker->top->write_output_header(
|
||||||
[this](IdString name) { fstWriterSetScope(fstfile, FST_ST_VCD_MODULE, stringf("%s",log_id(name)).c_str(), nullptr); },
|
[this](IdString name) { fstWriterSetScope(fstfile, FST_ST_VCD_MODULE, stringf("%s",log_id(name)).c_str(), nullptr); },
|
||||||
[this]() { fstWriterSetUpscope(fstfile); },
|
[this]() { fstWriterSetUpscope(fstfile); },
|
||||||
[this,use_signal](const char *name, Wire *wire, int id, bool is_reg) {
|
[this,use_signal](const char *name, int size, Wire *, int id, bool is_reg) {
|
||||||
if (!use_signal.at(id)) return;
|
if (!use_signal.at(id)) return;
|
||||||
fstHandle fst_id = fstWriterCreateVar(fstfile, is_reg ? FST_VT_VCD_REG : FST_VT_VCD_WIRE, FST_VD_IMPLICIT, GetSize(wire),
|
fstHandle fst_id = fstWriterCreateVar(fstfile, is_reg ? FST_VT_VCD_REG : FST_VT_VCD_WIRE, FST_VD_IMPLICIT, size,
|
||||||
name, 0);
|
name, 0);
|
||||||
mapping.emplace(id, fst_id);
|
mapping.emplace(id, fst_id);
|
||||||
}
|
}
|
||||||
|
@ -1881,7 +2279,7 @@ struct AIWWriter : public OutputWriter
|
||||||
worker->top->write_output_header(
|
worker->top->write_output_header(
|
||||||
[](IdString) {},
|
[](IdString) {},
|
||||||
[]() {},
|
[]() {},
|
||||||
[this](const char */*name*/, Wire *wire, int id, bool) { mapping[wire] = id; }
|
[this](const char */*name*/, int /*size*/, Wire *wire, int id, bool) { if (wire != nullptr) mapping[wire] = id; }
|
||||||
);
|
);
|
||||||
|
|
||||||
std::map<int, Yosys::RTLIL::Const> current;
|
std::map<int, Yosys::RTLIL::Const> current;
|
||||||
|
@ -2013,11 +2411,17 @@ struct SimPass : public Pass {
|
||||||
log(" -w\n");
|
log(" -w\n");
|
||||||
log(" writeback mode: use final simulation state as new init state\n");
|
log(" writeback mode: use final simulation state as new init state\n");
|
||||||
log("\n");
|
log("\n");
|
||||||
log(" -r\n");
|
log(" -r <filename>\n");
|
||||||
log(" read simulation results file\n");
|
log(" read simulation or formal results file\n");
|
||||||
log(" File formats supported: FST, VCD, AIW and WIT\n");
|
log(" File formats supported: FST, VCD, AIW, WIT and .yw\n");
|
||||||
log(" VCD support requires vcd2fst external tool to be present\n");
|
log(" VCD support requires vcd2fst external tool to be present\n");
|
||||||
log("\n");
|
log("\n");
|
||||||
|
log(" -append <integer>\n");
|
||||||
|
log(" number of extra clock cycles to simulate for a Yosys witness input\n");
|
||||||
|
log("\n");
|
||||||
|
log(" -summary <filename>\n");
|
||||||
|
log(" write a JSON summary to the given file\n");
|
||||||
|
log("\n");
|
||||||
log(" -map <filename>\n");
|
log(" -map <filename>\n");
|
||||||
log(" read file with port and latch symbols, needed for AIGER witness input\n");
|
log(" read file with port and latch symbols, needed for AIGER witness input\n");
|
||||||
log("\n");
|
log("\n");
|
||||||
|
@ -2063,6 +2467,7 @@ struct SimPass : public Pass {
|
||||||
{
|
{
|
||||||
SimWorker worker;
|
SimWorker worker;
|
||||||
int numcycles = 20;
|
int numcycles = 20;
|
||||||
|
int append = 0;
|
||||||
bool start_set = false, stop_set = false, at_set = false;
|
bool start_set = false, stop_set = false, at_set = false;
|
||||||
|
|
||||||
log_header(design, "Executing SIM pass (simulate the circuit).\n");
|
log_header(design, "Executing SIM pass (simulate the circuit).\n");
|
||||||
|
@ -2146,12 +2551,22 @@ struct SimPass : public Pass {
|
||||||
worker.sim_filename = sim_filename;
|
worker.sim_filename = sim_filename;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (args[argidx] == "-append" && argidx+1 < args.size()) {
|
||||||
|
append = atoi(args[++argidx].c_str());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (args[argidx] == "-map" && argidx+1 < args.size()) {
|
if (args[argidx] == "-map" && argidx+1 < args.size()) {
|
||||||
std::string map_filename = args[++argidx];
|
std::string map_filename = args[++argidx];
|
||||||
rewrite_filename(map_filename);
|
rewrite_filename(map_filename);
|
||||||
worker.map_filename = map_filename;
|
worker.map_filename = map_filename;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (args[argidx] == "-summary" && argidx+1 < args.size()) {
|
||||||
|
std::string summary_filename = args[++argidx];
|
||||||
|
rewrite_filename(summary_filename);
|
||||||
|
worker.summary_filename = summary_filename;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (args[argidx] == "-scope" && argidx+1 < args.size()) {
|
if (args[argidx] == "-scope" && argidx+1 < args.size()) {
|
||||||
worker.scope = args[++argidx];
|
worker.scope = args[++argidx];
|
||||||
continue;
|
continue;
|
||||||
|
@ -2235,10 +2650,14 @@ struct SimPass : public Pass {
|
||||||
worker.run_cosim_aiger_witness(top_mod);
|
worker.run_cosim_aiger_witness(top_mod);
|
||||||
} else if (filename_trim.size() > 4 && filename_trim.compare(filename_trim.size()-4, std::string::npos, ".wit") == 0) {
|
} else if (filename_trim.size() > 4 && filename_trim.compare(filename_trim.size()-4, std::string::npos, ".wit") == 0) {
|
||||||
worker.run_cosim_btor2_witness(top_mod);
|
worker.run_cosim_btor2_witness(top_mod);
|
||||||
|
} else if (filename_trim.size() > 3 && filename_trim.compare(filename_trim.size()-3, std::string::npos, ".yw") == 0) {
|
||||||
|
worker.run_cosim_yw_witness(top_mod, append);
|
||||||
} else {
|
} else {
|
||||||
log_cmd_error("Unhandled extension for simulation input file `%s`.\n", worker.sim_filename.c_str());
|
log_cmd_error("Unhandled extension for simulation input file `%s`.\n", worker.sim_filename.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
worker.write_summary();
|
||||||
}
|
}
|
||||||
} SimPass;
|
} SimPass;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue