diff options
Diffstat (limited to 'backends')
30 files changed, 2135 insertions, 1299 deletions
diff --git a/backends/aiger/aiger.cc b/backends/aiger/aiger.cc index e5a41b5c5..476b30488 100644 --- a/backends/aiger/aiger.cc +++ b/backends/aiger/aiger.cc @@ -111,7 +111,7 @@ struct AigerWriter // promote public wires for (auto wire : module->wires()) - if (wire->name[0] == '\\') + if (wire->name.isPublic()) sigmap.add(wire); // promote input wires @@ -681,7 +681,7 @@ struct AigerWriter struct AigerBackend : public Backend { AigerBackend() : Backend("aiger", "write design to AIGER file") { } - void help() YS_OVERRIDE + void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); @@ -719,7 +719,7 @@ struct AigerBackend : public Backend { log(" AIGER file happy.\n"); log("\n"); } - void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE + void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override { bool ascii_mode = false; bool zinit_mode = false; diff --git a/backends/aiger/xaiger.cc b/backends/aiger/xaiger.cc index 8bbadbc91..27499b64a 100644 --- a/backends/aiger/xaiger.cc +++ b/backends/aiger/xaiger.cc @@ -146,7 +146,7 @@ struct XAigerWriter // promote public wires for (auto wire : module->wires()) - if (wire->name[0] == '\\') + if (wire->name.isPublic()) sigmap.add(wire); // promote input wires @@ -731,7 +731,7 @@ struct XAigerWriter struct XAigerBackend : public Backend { XAigerBackend() : Backend("xaiger", "write design to XAIGER file") { } - void help() YS_OVERRIDE + void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); @@ -753,7 +753,7 @@ struct XAigerBackend : public Backend { log(" write $_DFF_[NP]_ cells\n"); log("\n"); } - void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE + void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override { bool ascii_mode = false, dff_mode = false; std::string map_filename; diff --git a/backends/blif/blif.cc b/backends/blif/blif.cc index b028df848..088819042 100644 --- a/backends/blif/blif.cc +++ b/backends/blif/blif.cc @@ -86,20 +86,18 @@ struct BlifDumper } } - vector<shared_str> cstr_buf; pool<SigBit> cstr_bits_seen; - const char *cstr(RTLIL::IdString id) + const std::string str(RTLIL::IdString id) { std::string str = RTLIL::unescape_id(id); for (size_t i = 0; i < str.size(); i++) if (str[i] == '#' || str[i] == '=' || str[i] == '<' || str[i] == '>') str[i] = '?'; - cstr_buf.push_back(str); - return cstr_buf.back().c_str(); + return str; } - const char *cstr(RTLIL::SigBit sig) + const std::string str(RTLIL::SigBit sig) { cstr_bits_seen.insert(sig); @@ -117,11 +115,10 @@ struct BlifDumper if (sig.wire->width != 1) str += stringf("[%d]", sig.wire->upto ? sig.wire->start_offset+sig.wire->width-sig.offset-1 : sig.wire->start_offset+sig.offset); - cstr_buf.push_back(str); - return cstr_buf.back().c_str(); + return str; } - const char *cstr_init(RTLIL::SigBit sig) + const std::string str_init(RTLIL::SigBit sig) { sigmap.apply(sig); @@ -130,8 +127,7 @@ struct BlifDumper string str = stringf(" %d", init_bits.at(sig)); - cstr_buf.push_back(str); - return cstr_buf.back().c_str(); + return str; } const char *subckt_or_gate(std::string cell_type) @@ -168,7 +164,7 @@ struct BlifDumper void dump() { f << stringf("\n"); - f << stringf(".model %s\n", cstr(module->name)); + f << stringf(".model %s\n", str(module->name).c_str()); std::map<int, RTLIL::Wire*> inputs, outputs; @@ -183,7 +179,7 @@ struct BlifDumper for (auto &it : inputs) { RTLIL::Wire *wire = it.second; for (int i = 0; i < wire->width; i++) - f << stringf(" %s", cstr(RTLIL::SigSpec(wire, i))); + f << stringf(" %s", str(RTLIL::SigSpec(wire, i)).c_str()); } f << stringf("\n"); @@ -191,7 +187,7 @@ struct BlifDumper for (auto &it : outputs) { RTLIL::Wire *wire = it.second; for (int i = 0; i < wire->width; i++) - f << stringf(" %s", cstr(RTLIL::SigSpec(wire, i))); + f << stringf(" %s", str(RTLIL::SigSpec(wire, i)).c_str()); } f << stringf("\n"); @@ -233,131 +229,131 @@ struct BlifDumper if (config->unbuf_types.count(cell->type)) { auto portnames = config->unbuf_types.at(cell->type); f << stringf(".names %s %s\n1 1\n", - cstr(cell->getPort(portnames.first)), cstr(cell->getPort(portnames.second))); + str(cell->getPort(portnames.first)).c_str(), str(cell->getPort(portnames.second)).c_str()); continue; } if (!config->icells_mode && cell->type == ID($_NOT_)) { f << stringf(".names %s %s\n0 1\n", - cstr(cell->getPort(ID::A)), cstr(cell->getPort(ID::Y))); + str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::Y)).c_str()); goto internal_cell; } if (!config->icells_mode && cell->type == ID($_AND_)) { f << stringf(".names %s %s %s\n11 1\n", - cstr(cell->getPort(ID::A)), cstr(cell->getPort(ID::B)), cstr(cell->getPort(ID::Y))); + str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(), str(cell->getPort(ID::Y)).c_str()); goto internal_cell; } if (!config->icells_mode && cell->type == ID($_OR_)) { f << stringf(".names %s %s %s\n1- 1\n-1 1\n", - cstr(cell->getPort(ID::A)), cstr(cell->getPort(ID::B)), cstr(cell->getPort(ID::Y))); + str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(), str(cell->getPort(ID::Y)).c_str()); goto internal_cell; } if (!config->icells_mode && cell->type == ID($_XOR_)) { f << stringf(".names %s %s %s\n10 1\n01 1\n", - cstr(cell->getPort(ID::A)), cstr(cell->getPort(ID::B)), cstr(cell->getPort(ID::Y))); + str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(), str(cell->getPort(ID::Y)).c_str()); goto internal_cell; } if (!config->icells_mode && cell->type == ID($_NAND_)) { f << stringf(".names %s %s %s\n0- 1\n-0 1\n", - cstr(cell->getPort(ID::A)), cstr(cell->getPort(ID::B)), cstr(cell->getPort(ID::Y))); + str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(), str(cell->getPort(ID::Y)).c_str()); goto internal_cell; } if (!config->icells_mode && cell->type == ID($_NOR_)) { f << stringf(".names %s %s %s\n00 1\n", - cstr(cell->getPort(ID::A)), cstr(cell->getPort(ID::B)), cstr(cell->getPort(ID::Y))); + str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(), str(cell->getPort(ID::Y)).c_str()); goto internal_cell; } if (!config->icells_mode && cell->type == ID($_XNOR_)) { f << stringf(".names %s %s %s\n11 1\n00 1\n", - cstr(cell->getPort(ID::A)), cstr(cell->getPort(ID::B)), cstr(cell->getPort(ID::Y))); + str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(), str(cell->getPort(ID::Y)).c_str()); goto internal_cell; } if (!config->icells_mode && cell->type == ID($_ANDNOT_)) { f << stringf(".names %s %s %s\n10 1\n", - cstr(cell->getPort(ID::A)), cstr(cell->getPort(ID::B)), cstr(cell->getPort(ID::Y))); + str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(), str(cell->getPort(ID::Y)).c_str()); goto internal_cell; } if (!config->icells_mode && cell->type == ID($_ORNOT_)) { f << stringf(".names %s %s %s\n1- 1\n-0 1\n", - cstr(cell->getPort(ID::A)), cstr(cell->getPort(ID::B)), cstr(cell->getPort(ID::Y))); + str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(), str(cell->getPort(ID::Y)).c_str()); goto internal_cell; } if (!config->icells_mode && cell->type == ID($_AOI3_)) { f << stringf(".names %s %s %s %s\n-00 1\n0-0 1\n", - cstr(cell->getPort(ID::A)), cstr(cell->getPort(ID::B)), cstr(cell->getPort(ID::C)), cstr(cell->getPort(ID::Y))); + str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(), str(cell->getPort(ID::C)).c_str(), str(cell->getPort(ID::Y)).c_str()); goto internal_cell; } if (!config->icells_mode && cell->type == ID($_OAI3_)) { f << stringf(".names %s %s %s %s\n00- 1\n--0 1\n", - cstr(cell->getPort(ID::A)), cstr(cell->getPort(ID::B)), cstr(cell->getPort(ID::C)), cstr(cell->getPort(ID::Y))); + str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(), str(cell->getPort(ID::C)).c_str(), str(cell->getPort(ID::Y)).c_str()); goto internal_cell; } if (!config->icells_mode && cell->type == ID($_AOI4_)) { f << stringf(".names %s %s %s %s %s\n-0-0 1\n-00- 1\n0--0 1\n0-0- 1\n", - cstr(cell->getPort(ID::A)), cstr(cell->getPort(ID::B)), - cstr(cell->getPort(ID::C)), cstr(cell->getPort(ID::D)), cstr(cell->getPort(ID::Y))); + str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(), + str(cell->getPort(ID::C)).c_str(), str(cell->getPort(ID::D)).c_str(), str(cell->getPort(ID::Y)).c_str()); goto internal_cell; } if (!config->icells_mode && cell->type == ID($_OAI4_)) { f << stringf(".names %s %s %s %s %s\n00-- 1\n--00 1\n", - cstr(cell->getPort(ID::A)), cstr(cell->getPort(ID::B)), - cstr(cell->getPort(ID::C)), cstr(cell->getPort(ID::D)), cstr(cell->getPort(ID::Y))); + str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(), + str(cell->getPort(ID::C)).c_str(), str(cell->getPort(ID::D)).c_str(), str(cell->getPort(ID::Y)).c_str()); goto internal_cell; } if (!config->icells_mode && cell->type == ID($_MUX_)) { f << stringf(".names %s %s %s %s\n1-0 1\n-11 1\n", - cstr(cell->getPort(ID::A)), cstr(cell->getPort(ID::B)), - cstr(cell->getPort(ID::S)), cstr(cell->getPort(ID::Y))); + str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(), + str(cell->getPort(ID::S)).c_str(), str(cell->getPort(ID::Y)).c_str()); goto internal_cell; } if (!config->icells_mode && cell->type == ID($_NMUX_)) { f << stringf(".names %s %s %s %s\n0-0 1\n-01 1\n", - cstr(cell->getPort(ID::A)), cstr(cell->getPort(ID::B)), - cstr(cell->getPort(ID::S)), cstr(cell->getPort(ID::Y))); + str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(), + str(cell->getPort(ID::S)).c_str(), str(cell->getPort(ID::Y)).c_str()); goto internal_cell; } if (!config->icells_mode && cell->type == ID($_FF_)) { - f << stringf(".latch %s %s%s\n", cstr(cell->getPort(ID::D)), cstr(cell->getPort(ID::Q)), - cstr_init(cell->getPort(ID::Q))); + f << stringf(".latch %s %s%s\n", str(cell->getPort(ID::D)).c_str(), str(cell->getPort(ID::Q)).c_str(), + str_init(cell->getPort(ID::Q)).c_str()); goto internal_cell; } if (!config->icells_mode && cell->type == ID($_DFF_N_)) { - f << stringf(".latch %s %s fe %s%s\n", cstr(cell->getPort(ID::D)), cstr(cell->getPort(ID::Q)), - cstr(cell->getPort(ID::C)), cstr_init(cell->getPort(ID::Q))); + f << stringf(".latch %s %s fe %s%s\n", str(cell->getPort(ID::D)).c_str(), str(cell->getPort(ID::Q)).c_str(), + str(cell->getPort(ID::C)).c_str(), str_init(cell->getPort(ID::Q)).c_str()); goto internal_cell; } if (!config->icells_mode && cell->type == ID($_DFF_P_)) { - f << stringf(".latch %s %s re %s%s\n", cstr(cell->getPort(ID::D)), cstr(cell->getPort(ID::Q)), - cstr(cell->getPort(ID::C)), cstr_init(cell->getPort(ID::Q))); + f << stringf(".latch %s %s re %s%s\n", str(cell->getPort(ID::D)).c_str(), str(cell->getPort(ID::Q)).c_str(), + str(cell->getPort(ID::C)).c_str(), str_init(cell->getPort(ID::Q)).c_str()); goto internal_cell; } if (!config->icells_mode && cell->type == ID($_DLATCH_N_)) { - f << stringf(".latch %s %s al %s%s\n", cstr(cell->getPort(ID::D)), cstr(cell->getPort(ID::Q)), - cstr(cell->getPort(ID::E)), cstr_init(cell->getPort(ID::Q))); + f << stringf(".latch %s %s al %s%s\n", str(cell->getPort(ID::D)).c_str(), str(cell->getPort(ID::Q)).c_str(), + str(cell->getPort(ID::E)).c_str(), str_init(cell->getPort(ID::Q)).c_str()); goto internal_cell; } if (!config->icells_mode && cell->type == ID($_DLATCH_P_)) { - f << stringf(".latch %s %s ah %s%s\n", cstr(cell->getPort(ID::D)), cstr(cell->getPort(ID::Q)), - cstr(cell->getPort(ID::E)), cstr_init(cell->getPort(ID::Q))); + f << stringf(".latch %s %s ah %s%s\n", str(cell->getPort(ID::D)).c_str(), str(cell->getPort(ID::Q)).c_str(), + str(cell->getPort(ID::E)).c_str(), str_init(cell->getPort(ID::Q)).c_str()); goto internal_cell; } @@ -367,10 +363,10 @@ struct BlifDumper auto width = cell->parameters.at(ID::WIDTH).as_int(); log_assert(inputs.size() == width); for (int i = width-1; i >= 0; i--) - f << stringf(" %s", cstr(inputs.extract(i, 1))); + f << stringf(" %s", str(inputs.extract(i, 1)).c_str()); auto &output = cell->getPort(ID::Y); log_assert(output.size() == 1); - f << stringf(" %s", cstr(output)); + f << stringf(" %s", str(output).c_str()); f << stringf("\n"); RTLIL::SigSpec mask = cell->parameters.at(ID::LUT); for (int i = 0; i < (1 << width); i++) @@ -393,10 +389,10 @@ struct BlifDumper table.push_back(State::S0); log_assert(inputs.size() == width); for (int i = 0; i < width; i++) - f << stringf(" %s", cstr(inputs.extract(i, 1))); + f << stringf(" %s", str(inputs.extract(i, 1)).c_str()); auto &output = cell->getPort(ID::Y); log_assert(output.size() == 1); - f << stringf(" %s", cstr(output)); + f << stringf(" %s", str(output).c_str()); f << stringf("\n"); for (int i = 0; i < depth; i++) { for (int j = 0; j < width; j++) { @@ -411,11 +407,11 @@ struct BlifDumper goto internal_cell; } - f << stringf(".%s %s", subckt_or_gate(cell->type.str()), cstr(cell->type)); + f << stringf(".%s %s", subckt_or_gate(cell->type.str()), str(cell->type).c_str()); for (auto &conn : cell->connections()) { if (conn.second.size() == 1) { - f << stringf(" %s=%s", cstr(conn.first), cstr(conn.second[0])); + f << stringf(" %s=%s", str(conn.first).c_str(), str(conn.second[0]).c_str()); continue; } @@ -424,20 +420,20 @@ struct BlifDumper if (w == nullptr) { for (int i = 0; i < GetSize(conn.second); i++) - f << stringf(" %s[%d]=%s", cstr(conn.first), i, cstr(conn.second[i])); + f << stringf(" %s[%d]=%s", str(conn.first).c_str(), i, str(conn.second[i]).c_str()); } else { for (int i = 0; i < std::min(GetSize(conn.second), GetSize(w)); i++) { SigBit sig(w, i); - f << stringf(" %s[%d]=%s", cstr(conn.first), sig.wire->upto ? + f << stringf(" %s[%d]=%s", str(conn.first).c_str(), sig.wire->upto ? sig.wire->start_offset+sig.wire->width-sig.offset-1 : - sig.wire->start_offset+sig.offset, cstr(conn.second[i])); + sig.wire->start_offset+sig.offset, str(conn.second[i]).c_str()); } } } f << stringf("\n"); if (config->cname_mode) - f << stringf(".cname %s\n", cstr(cell->name)); + f << stringf(".cname %s\n", str(cell->name).c_str()); if (config->attr_mode) dump_params(".attr", cell->attributes); if (config->param_mode) @@ -446,7 +442,7 @@ struct BlifDumper if (0) { internal_cell: if (config->iname_mode) - f << stringf(".cname %s\n", cstr(cell->name)); + f << stringf(".cname %s\n", str(cell->name).c_str()); if (config->iattr_mode) dump_params(".attr", cell->attributes); } @@ -462,12 +458,12 @@ struct BlifDumper continue; if (config->conn_mode) - f << stringf(".conn %s %s\n", cstr(rhs_bit), cstr(lhs_bit)); + f << stringf(".conn %s %s\n", str(rhs_bit).c_str(), str(lhs_bit).c_str()); else if (!config->buf_type.empty()) f << stringf(".%s %s %s=%s %s=%s\n", subckt_or_gate(config->buf_type), config->buf_type.c_str(), - config->buf_in.c_str(), cstr(rhs_bit), config->buf_out.c_str(), cstr(lhs_bit)); + config->buf_in.c_str(), str(rhs_bit).c_str(), config->buf_out.c_str(), str(lhs_bit).c_str()); else - f << stringf(".names %s %s\n1 1\n", cstr(rhs_bit), cstr(lhs_bit)); + f << stringf(".names %s %s\n1 1\n", str(rhs_bit).c_str(), str(lhs_bit).c_str()); } f << stringf(".end\n"); @@ -482,7 +478,7 @@ struct BlifDumper struct BlifBackend : public Backend { BlifBackend() : Backend("blif", "write design to BLIF file") { } - void help() YS_OVERRIDE + void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); @@ -552,7 +548,7 @@ struct BlifBackend : public Backend { log(" do not write definitions for the $true, $false and $undef wires.\n"); log("\n"); } - void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE + void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override { std::string top_module_name; std::string buf_type, buf_in, buf_out; diff --git a/backends/btor/.gitignore b/backends/btor/.gitignore new file mode 100644 index 000000000..d23d492d7 --- /dev/null +++ b/backends/btor/.gitignore @@ -0,0 +1 @@ +/test_cells.tmp/ diff --git a/backends/btor/btor.cc b/backends/btor/btor.cc index 9ac312480..639c6f129 100644 --- a/backends/btor/btor.cc +++ b/backends/btor/btor.cc @@ -27,6 +27,7 @@ #include "kernel/sigtools.h" #include "kernel/celltypes.h" #include "kernel/log.h" +#include "kernel/mem.h" #include <string> USING_YOSYS_NAMESPACE @@ -68,12 +69,15 @@ struct BtorWorker // ff inputs that need to be evaluated (<nid>, <ff_cell>) vector<pair<int, Cell*>> ff_todo; + vector<pair<int, Mem*>> mem_todo; pool<Cell*> cell_recursion_guard; vector<int> bad_properties; dict<SigBit, bool> initbits; pool<Wire*> statewires; pool<string> srcsymbols; + vector<Mem> memories; + dict<Cell*, Mem*> mem_cells; string indent, info_filename; vector<string> info_lines; @@ -205,9 +209,8 @@ struct BtorWorker if (cell->type.in(ID($xnor), ID($_XNOR_))) btor_op = "xnor"; log_assert(!btor_op.empty()); - int width = GetSize(cell->getPort(ID::Y)); - width = std::max(width, GetSize(cell->getPort(ID::A))); - width = std::max(width, GetSize(cell->getPort(ID::B))); + int width_ay = std::max(GetSize(cell->getPort(ID::A)), GetSize(cell->getPort(ID::Y))); + int width = std::max(width_ay, GetSize(cell->getPort(ID::B))); bool a_signed = cell->hasParam(ID::A_SIGNED) ? cell->getParam(ID::A_SIGNED).as_bool() : false; bool b_signed = cell->hasParam(ID::B_SIGNED) ? cell->getParam(ID::B_SIGNED).as_bool() : false; @@ -224,11 +227,23 @@ struct BtorWorker int sid = get_bv_sid(width); int nid; + int nid_a; + if (cell->type.in(ID($shl), ID($shr), ID($shift), ID($shiftx)) && a_signed && width_ay < width) { + // sign-extend A up to the width of Y + int nid_a_padded = get_sig_nid(cell->getPort(ID::A), width_ay, a_signed); + + // zero-extend the rest + int zeroes = get_sig_nid(Const(0, width-width_ay)); + nid_a = next_nid++; + btorf("%d concat %d %d %d\n", nid_a, sid, zeroes, nid_a_padded); + } else { + nid_a = get_sig_nid(cell->getPort(ID::A), width, a_signed); + } + + int nid_b = get_sig_nid(cell->getPort(ID::B), width, b_signed); + if (btor_op == "shift") { - int nid_a = get_sig_nid(cell->getPort(ID::A), width, false); - int nid_b = get_sig_nid(cell->getPort(ID::B), width, b_signed); - int nid_r = next_nid++; btorf("%d srl %d %d %d\n", nid_r, sid, nid_a, nid_b); @@ -248,9 +263,6 @@ struct BtorWorker } else { - int nid_a = get_sig_nid(cell->getPort(ID::A), width, a_signed); - int nid_b = get_sig_nid(cell->getPort(ID::B), width, b_signed); - nid = next_nid++; btorf("%d %s %d %d %d%s\n", nid, btor_op.c_str(), sid, nid_a, nid_b, getinfo(cell).c_str()); } @@ -696,49 +708,45 @@ struct BtorWorker goto okay; } - if (cell->type == ID($mem)) + if (cell->type.in(ID($mem), ID($memrd), ID($memwr), ID($meminit))) { - int abits = cell->getParam(ID::ABITS).as_int(); - int width = cell->getParam(ID::WIDTH).as_int(); - int nwords = cell->getParam(ID::SIZE).as_int(); - int rdports = cell->getParam(ID::RD_PORTS).as_int(); - int wrports = cell->getParam(ID::WR_PORTS).as_int(); + Mem *mem = mem_cells[cell]; - Const wr_clk_en = cell->getParam(ID::WR_CLK_ENABLE); - Const rd_clk_en = cell->getParam(ID::RD_CLK_ENABLE); + int abits = ceil_log2(mem->size); - bool asyncwr = wr_clk_en.is_fully_zero(); + bool asyncwr = false; + bool syncwr = false; - if (!asyncwr && !wr_clk_en.is_fully_ones()) - log_error("Memory %s.%s has mixed async/sync write ports.\n", - log_id(module), log_id(cell)); - - if (!rd_clk_en.is_fully_zero()) - log_error("Memory %s.%s has sync read ports.\n", - log_id(module), log_id(cell)); + for (auto &port : mem->wr_ports) { + if (port.clk_enable) + syncwr = true; + else + asyncwr = true; + } - SigSpec sig_rd_addr = sigmap(cell->getPort(ID::RD_ADDR)); - SigSpec sig_rd_data = sigmap(cell->getPort(ID::RD_DATA)); + if (asyncwr && syncwr) + log_error("Memory %s.%s has mixed async/sync write ports.\n", + log_id(module), log_id(mem->memid)); - SigSpec sig_wr_addr = sigmap(cell->getPort(ID::WR_ADDR)); - SigSpec sig_wr_data = sigmap(cell->getPort(ID::WR_DATA)); - SigSpec sig_wr_en = sigmap(cell->getPort(ID::WR_EN)); + for (auto &port : mem->rd_ports) + if (port.clk_enable) + log_error("Memory %s.%s has sync read ports.\n", + log_id(module), log_id(mem->memid)); - int data_sid = get_bv_sid(width); + int data_sid = get_bv_sid(mem->width); int bool_sid = get_bv_sid(1); - int sid = get_mem_sid(abits, width); + int sid = get_mem_sid(abits, mem->width); - Const initdata = cell->getParam(ID::INIT); - initdata.exts(nwords*width); int nid_init_val = -1; - if (!initdata.is_fully_undef()) + if (!mem->inits.empty()) { + Const initdata = mem->get_init_data(); bool constword = true; - Const firstword = initdata.extract(0, width); + Const firstword = initdata.extract(0, mem->width); - for (int i = 1; i < nwords; i++) { - Const thisword = initdata.extract(i*width, width); + for (int i = 1; i < mem->size; i++) { + Const thisword = initdata.extract(i*mem->width, mem->width); if (thisword != firstword) { constword = false; break; @@ -756,8 +764,8 @@ struct BtorWorker nid_init_val = next_nid++; btorf("%d state %d\n", nid_init_val, sid); - for (int i = 0; i < nwords; i++) { - Const thisword = initdata.extract(i*width, width); + for (int i = 0; i < mem->size; i++) { + Const thisword = initdata.extract(i*mem->width, mem->width); if (thisword.is_fully_undef()) continue; Const thisaddr(i, abits); @@ -776,10 +784,10 @@ struct BtorWorker int nid = next_nid++; int nid_head = nid; - if (cell->name[0] == '$') + if (mem->memid[0] == '$') btorf("%d state %d\n", nid, sid); else - btorf("%d state %d %s\n", nid, sid, log_id(cell)); + btorf("%d state %d %s\n", nid, sid, log_id(mem->memid)); if (nid_init_val >= 0) { @@ -789,15 +797,14 @@ struct BtorWorker if (asyncwr) { - for (int port = 0; port < wrports; port++) + for (auto &port : mem->wr_ports) { - SigSpec wa = sig_wr_addr.extract(port*abits, abits); - SigSpec wd = sig_wr_data.extract(port*width, width); - SigSpec we = sig_wr_en.extract(port*width, width); + SigSpec wa = port.addr; + wa.extend_u0(abits); int wa_nid = get_sig_nid(wa); - int wd_nid = get_sig_nid(wd); - int we_nid = get_sig_nid(we); + int wd_nid = get_sig_nid(port.data); + int we_nid = get_sig_nid(port.en); int nid2 = next_nid++; btorf("%d read %d %d %d\n", nid2, data_sid, nid_head, wa_nid); @@ -827,22 +834,22 @@ struct BtorWorker } } - for (int port = 0; port < rdports; port++) + for (auto &port : mem->rd_ports) { - SigSpec ra = sig_rd_addr.extract(port*abits, abits); - SigSpec rd = sig_rd_data.extract(port*width, width); + SigSpec ra = port.addr; + ra.extend_u0(abits); int ra_nid = get_sig_nid(ra); int rd_nid = next_nid++; btorf("%d read %d %d %d\n", rd_nid, data_sid, nid_head, ra_nid); - add_nid_sig(rd_nid, rd); + add_nid_sig(rd_nid, port.data); } if (!asyncwr) { - ff_todo.push_back(make_pair(nid, cell)); + mem_todo.push_back(make_pair(nid, mem)); } else { @@ -1057,6 +1064,15 @@ struct BtorWorker if (!info_filename.empty()) infof("name %s\n", log_id(module)); + memories = Mem::get_all_memories(module); + + dict<IdString, Mem*> mem_dict; + for (auto &mem : memories) + mem_dict[mem.memid] = &mem; + for (auto cell : module->cells()) + if (cell->type.in(ID($mem), ID($memwr), ID($memrd), ID($meminit))) + mem_cells[cell] = mem_dict[cell->parameters.at(ID::MEMID).decode_string()]; + btorf_push("inputs"); for (auto wire : module->wires()) @@ -1193,7 +1209,7 @@ struct BtorWorker continue; } - while (!ff_todo.empty()) + while (!ff_todo.empty() || !mem_todo.empty()) { vector<pair<int, Cell*>> todo; todo.swap(ff_todo); @@ -1205,70 +1221,71 @@ struct BtorWorker btorf_push(stringf("next %s", log_id(cell))); - if (cell->type == ID($mem)) - { - int abits = cell->getParam(ID::ABITS).as_int(); - int width = cell->getParam(ID::WIDTH).as_int(); - int wrports = cell->getParam(ID::WR_PORTS).as_int(); + SigSpec sig = sigmap(cell->getPort(ID::D)); + int nid_q = get_sig_nid(sig); + int sid = get_bv_sid(GetSize(sig)); + btorf("%d next %d %d %d%s\n", next_nid++, sid, nid, nid_q, getinfo(cell).c_str()); - SigSpec sig_wr_addr = sigmap(cell->getPort(ID::WR_ADDR)); - SigSpec sig_wr_data = sigmap(cell->getPort(ID::WR_DATA)); - SigSpec sig_wr_en = sigmap(cell->getPort(ID::WR_EN)); + btorf_pop(stringf("next %s", log_id(cell))); + } - int data_sid = get_bv_sid(width); - int bool_sid = get_bv_sid(1); - int sid = get_mem_sid(abits, width); - int nid_head = nid; + vector<pair<int, Mem*>> mtodo; + mtodo.swap(mem_todo); - for (int port = 0; port < wrports; port++) - { - SigSpec wa = sig_wr_addr.extract(port*abits, abits); - SigSpec wd = sig_wr_data.extract(port*width, width); - SigSpec we = sig_wr_en.extract(port*width, width); + for (auto &it : mtodo) + { + int nid = it.first; + Mem *mem = it.second; + + btorf_push(stringf("next %s", log_id(mem->memid))); + + int abits = ceil_log2(mem->size); - int wa_nid = get_sig_nid(wa); - int wd_nid = get_sig_nid(wd); - int we_nid = get_sig_nid(we); + int data_sid = get_bv_sid(mem->width); + int bool_sid = get_bv_sid(1); + int sid = get_mem_sid(abits, mem->width); + int nid_head = nid; - int nid2 = next_nid++; - btorf("%d read %d %d %d\n", nid2, data_sid, nid_head, wa_nid); + for (auto &port : mem->wr_ports) + { + SigSpec wa = port.addr; + wa.extend_u0(abits); - int nid3 = next_nid++; - btorf("%d not %d %d\n", nid3, data_sid, we_nid); + int wa_nid = get_sig_nid(wa); + int wd_nid = get_sig_nid(port.data); + int we_nid = get_sig_nid(port.en); - int nid4 = next_nid++; - btorf("%d and %d %d %d\n", nid4, data_sid, nid2, nid3); + int nid2 = next_nid++; + btorf("%d read %d %d %d\n", nid2, data_sid, nid_head, wa_nid); + + int nid3 = next_nid++; + btorf("%d not %d %d\n", nid3, data_sid, we_nid); - int nid5 = next_nid++; - btorf("%d and %d %d %d\n", nid5, data_sid, wd_nid, we_nid); + int nid4 = next_nid++; + btorf("%d and %d %d %d\n", nid4, data_sid, nid2, nid3); - int nid6 = next_nid++; - btorf("%d or %d %d %d\n", nid6, data_sid, nid5, nid4); + int nid5 = next_nid++; + btorf("%d and %d %d %d\n", nid5, data_sid, wd_nid, we_nid); - int nid7 = next_nid++; - btorf("%d write %d %d %d %d\n", nid7, sid, nid_head, wa_nid, nid6); + int nid6 = next_nid++; + btorf("%d or %d %d %d\n", nid6, data_sid, nid5, nid4); - int nid8 = next_nid++; - btorf("%d redor %d %d\n", nid8, bool_sid, we_nid); + int nid7 = next_nid++; + btorf("%d write %d %d %d %d\n", nid7, sid, nid_head, wa_nid, nid6); - int nid9 = next_nid++; - btorf("%d ite %d %d %d %d\n", nid9, sid, nid8, nid7, nid_head); + int nid8 = next_nid++; + btorf("%d redor %d %d\n", nid8, bool_sid, we_nid); - nid_head = nid9; - } + int nid9 = next_nid++; + btorf("%d ite %d %d %d %d\n", nid9, sid, nid8, nid7, nid_head); - int nid2 = next_nid++; - btorf("%d next %d %d %d%s\n", nid2, sid, nid, nid_head, getinfo(cell).c_str()); - } - else - { - SigSpec sig = sigmap(cell->getPort(ID::D)); - int nid_q = get_sig_nid(sig); - int sid = get_bv_sid(GetSize(sig)); - btorf("%d next %d %d %d%s\n", next_nid++, sid, nid, nid_q, getinfo(cell).c_str()); + nid_head = nid9; } - btorf_pop(stringf("next %s", log_id(cell))); + int nid2 = next_nid++; + btorf("%d next %d %d %d%s\n", nid2, sid, nid, nid_head, (mem->cell ? getinfo(mem->cell) : getinfo(mem->mem)).c_str()); + + btorf_pop(stringf("next %s", log_id(mem->memid))); } } @@ -1335,7 +1352,7 @@ struct BtorWorker struct BtorBackend : public Backend { BtorBackend() : Backend("btor", "write design to BTOR file") { } - void help() YS_OVERRIDE + void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); @@ -1359,7 +1376,7 @@ struct BtorBackend : public Backend { log(" Output symbols for internal netnames (starting with '$')\n"); log("\n"); } - void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) YS_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; string info_filename; diff --git a/backends/btor/test_cells.sh b/backends/btor/test_cells.sh index 3f077201a..0a011932d 100644..100755 --- a/backends/btor/test_cells.sh +++ b/backends/btor/test_cells.sh @@ -6,7 +6,7 @@ rm -rf test_cells.tmp mkdir -p test_cells.tmp cd test_cells.tmp -../../../yosys -p 'test_cell -n 5 -w test all /$alu /$fa /$lcu /$lut /$sop /$macc /$mul /$div /$mod /$divfloor /$modfloor' +../../../yosys -p 'test_cell -n 5 -w test all /$alu /$fa /$lcu /$lut /$sop /$macc /$mul /$div /$mod /$divfloor /$modfloor /$shiftx' for fn in test_*.il; do ../../../yosys -p " @@ -19,7 +19,7 @@ for fn in test_*.il; do hierarchy -top main write_btor ${fn%.il}.btor " - boolectormc -kmax 1 --trace-gen --stop-first -v ${fn%.il}.btor > ${fn%.il}.out + btormc -kmax 1 --trace-gen --stop-first -v ${fn%.il}.btor > ${fn%.il}.out if grep " SATISFIABLE" ${fn%.il}.out; then echo "Check failed for ${fn%.il}." exit 1 diff --git a/backends/cxxrtl/cxxrtl.h b/backends/cxxrtl/cxxrtl.h index c988c9e80..41089a153 100644 --- a/backends/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/cxxrtl.h @@ -17,6 +17,11 @@ */ // This file is included by the designs generated with `write_cxxrtl`. It is not used in Yosys itself. +// +// The CXXRTL support library implements compile time specialized arbitrary width arithmetics, as well as provides +// composite lvalues made out of bit slices and concatenations of lvalues. This allows the `write_cxxrtl` pass +// to perform a straightforward translation of RTLIL structures to readable C++, relying on the C++ compiler +// to unwrap the abstraction and generate efficient code. #ifndef CXXRTL_H #define CXXRTL_H @@ -35,10 +40,19 @@ #include <backends/cxxrtl/cxxrtl_capi.h> -// The CXXRTL support library implements compile time specialized arbitrary width arithmetics, as well as provides -// composite lvalues made out of bit slices and concatenations of lvalues. This allows the `write_cxxrtl` pass -// to perform a straightforward translation of RTLIL structures to readable C++, relying on the C++ compiler -// to unwrap the abstraction and generate efficient code. +// CXXRTL essentially uses the C++ compiler as a hygienic macro engine that feeds an instruction selector. +// It generates a lot of specialized template functions with relatively large bodies that, when inlined +// into the caller and (for those with loops) unrolled, often expose many new optimization opportunities. +// Because of this, most of the CXXRTL runtime must be always inlined for best performance. +#ifndef __has_attribute +# define __has_attribute(x) 0 +#endif +#if __has_attribute(always_inline) +#define CXXRTL_ALWAYS_INLINE inline __attribute__((__always_inline__)) +#else +#define CXXRTL_ALWAYS_INLINE inline +#endif + namespace cxxrtl { // All arbitrary-width values in CXXRTL are backed by arrays of unsigned integers called chunks. The chunk size @@ -52,6 +66,7 @@ namespace cxxrtl { // Therefore, using relatively wide chunks and clearing the high bits explicitly and only when we know they may be // clobbered results in simpler generated code. typedef uint32_t chunk_t; +typedef uint64_t wide_chunk_t; template<typename T> struct chunk_traits { @@ -85,6 +100,7 @@ struct value : public expr_base<value<Bits>> { value<Bits> &operator=(const value<Bits> &) = default; // A (no-op) helper that forces the cast to value<>. + CXXRTL_ALWAYS_INLINE const value<Bits> &val() const { return *this; } @@ -95,12 +111,42 @@ struct value : public expr_base<value<Bits>> { return ss.str(); } + // Conversion operations. + // + // These functions ensure that a conversion is never out of range, and should be always used, if at all + // possible, instead of direct manipulation of the `data` member. For very large types, .slice() and + // .concat() can be used to split them into more manageable parts. + template<class IntegerT> + CXXRTL_ALWAYS_INLINE + IntegerT get() const { + static_assert(std::numeric_limits<IntegerT>::is_integer && !std::numeric_limits<IntegerT>::is_signed, + "get<T>() requires T to be an unsigned integral type"); + static_assert(std::numeric_limits<IntegerT>::digits >= Bits, + "get<T>() requires T to be at least as wide as the value is"); + IntegerT result = 0; + for (size_t n = 0; n < chunks; n++) + result |= IntegerT(data[n]) << (n * chunk::bits); + return result; + } + + template<class IntegerT> + CXXRTL_ALWAYS_INLINE + void set(IntegerT other) { + static_assert(std::numeric_limits<IntegerT>::is_integer && !std::numeric_limits<IntegerT>::is_signed, + "set<T>() requires T to be an unsigned integral type"); + static_assert(std::numeric_limits<IntegerT>::digits >= Bits, + "set<T>() requires the value to be at least as wide as T is"); + for (size_t n = 0; n < chunks; n++) + data[n] = (other >> (n * chunk::bits)) & chunk::mask; + } + // Operations with compile-time parameters. // // These operations are used to implement slicing, concatenation, and blitting. // The trunc, zext and sext operations add or remove most significant bits (i.e. on the left); // the rtrunc and rzext operations add or remove least significant bits (i.e. on the right). template<size_t NewBits> + CXXRTL_ALWAYS_INLINE value<NewBits> trunc() const { static_assert(NewBits <= Bits, "trunc() may not increase width"); value<NewBits> result; @@ -111,6 +157,7 @@ struct value : public expr_base<value<Bits>> { } template<size_t NewBits> + CXXRTL_ALWAYS_INLINE value<NewBits> zext() const { static_assert(NewBits >= Bits, "zext() may not decrease width"); value<NewBits> result; @@ -120,6 +167,7 @@ struct value : public expr_base<value<Bits>> { } template<size_t NewBits> + CXXRTL_ALWAYS_INLINE value<NewBits> sext() const { static_assert(NewBits >= Bits, "sext() may not decrease width"); value<NewBits> result; @@ -135,6 +183,7 @@ struct value : public expr_base<value<Bits>> { } template<size_t NewBits> + CXXRTL_ALWAYS_INLINE value<NewBits> rtrunc() const { static_assert(NewBits <= Bits, "rtrunc() may not increase width"); value<NewBits> result; @@ -154,6 +203,7 @@ struct value : public expr_base<value<Bits>> { } template<size_t NewBits> + CXXRTL_ALWAYS_INLINE value<NewBits> rzext() const { static_assert(NewBits >= Bits, "rzext() may not decrease width"); value<NewBits> result; @@ -165,13 +215,14 @@ struct value : public expr_base<value<Bits>> { carry = (shift_bits == 0) ? 0 : data[n] >> (chunk::bits - shift_bits); } - if (carry != 0) - result.data[result.chunks - 1] = carry; + if (shift_chunks + chunks < result.chunks) + result.data[shift_chunks + chunks] = carry; return result; } // Bit blit operation, i.e. a partial read-modify-write. template<size_t Stop, size_t Start> + CXXRTL_ALWAYS_INLINE value<Bits> blit(const value<Stop - Start + 1> &source) const { static_assert(Stop >= Start, "blit() may not reverse bit order"); constexpr chunk::type start_mask = ~(chunk::mask << (Start % chunk::bits)); @@ -196,6 +247,7 @@ struct value : public expr_base<value<Bits>> { // than the operand. In C++17 these can be replaced with `if constexpr`. template<size_t NewBits, typename = void> struct zext_cast { + CXXRTL_ALWAYS_INLINE value<NewBits> operator()(const value<Bits> &val) { return val.template zext<NewBits>(); } @@ -203,6 +255,7 @@ struct value : public expr_base<value<Bits>> { template<size_t NewBits> struct zext_cast<NewBits, typename std::enable_if<(NewBits < Bits)>::type> { + CXXRTL_ALWAYS_INLINE value<NewBits> operator()(const value<Bits> &val) { return val.template trunc<NewBits>(); } @@ -210,6 +263,7 @@ struct value : public expr_base<value<Bits>> { template<size_t NewBits, typename = void> struct sext_cast { + CXXRTL_ALWAYS_INLINE value<NewBits> operator()(const value<Bits> &val) { return val.template sext<NewBits>(); } @@ -217,17 +271,20 @@ struct value : public expr_base<value<Bits>> { template<size_t NewBits> struct sext_cast<NewBits, typename std::enable_if<(NewBits < Bits)>::type> { + CXXRTL_ALWAYS_INLINE value<NewBits> operator()(const value<Bits> &val) { return val.template trunc<NewBits>(); } }; template<size_t NewBits> + CXXRTL_ALWAYS_INLINE value<NewBits> zcast() const { return zext_cast<NewBits>()(*this); } template<size_t NewBits> + CXXRTL_ALWAYS_INLINE value<NewBits> scast() const { return sext_cast<NewBits>()(*this); } @@ -246,6 +303,10 @@ struct value : public expr_base<value<Bits>> { data[offset_chunks] |= value ? 1 << offset_bits : 0; } + explicit operator bool() const { + return !is_zero(); + } + bool is_zero() const { for (size_t n = 0; n < chunks; n++) if (data[n] != 0) @@ -253,10 +314,6 @@ struct value : public expr_base<value<Bits>> { return true; } - explicit operator bool() const { - return !is_zero(); - } - bool is_neg() const { return data[chunks - 1] & (1 << ((Bits - 1) % chunk::bits)); } @@ -349,10 +406,12 @@ struct value : public expr_base<value<Bits>> { : data[chunks - 1 - n] << (chunk::bits - shift_bits); } if (Signed && is_neg()) { - for (size_t n = chunks - shift_chunks; n < chunks; n++) + size_t top_chunk_idx = (Bits - shift_bits) / chunk::bits; + size_t top_chunk_bits = (Bits - shift_bits) % chunk::bits; + for (size_t n = top_chunk_idx + 1; n < chunks; n++) result.data[n] = chunk::mask; if (shift_bits != 0) - result.data[chunks - shift_chunks] |= chunk::mask << (chunk::bits - shift_bits); + result.data[top_chunk_idx] |= chunk::mask << top_chunk_bits; } return result; } @@ -393,10 +452,11 @@ struct value : public expr_base<value<Bits>> { bool carry = CarryIn; for (size_t n = 0; n < result.chunks; n++) { result.data[n] = data[n] + (Invert ? ~other.data[n] : other.data[n]) + carry; + if (result.chunks - 1 == n) + result.data[result.chunks - 1] &= result.msb_mask; carry = (result.data[n] < data[n]) || (result.data[n] == data[n] && carry); } - result.data[result.chunks - 1] &= result.msb_mask; return {result, carry}; } @@ -425,6 +485,24 @@ struct value : public expr_base<value<Bits>> { bool overflow = (is_neg() == !other.is_neg()) && (is_neg() != result.is_neg()); return result.is_neg() ^ overflow; // a.scmp(b) ≡ a s< b } + + template<size_t ResultBits> + value<ResultBits> mul(const value<Bits> &other) const { + value<ResultBits> result; + wide_chunk_t wide_result[result.chunks + 1] = {}; + for (size_t n = 0; n < chunks; n++) { + for (size_t m = 0; m < chunks && n + m < result.chunks; m++) { + wide_result[n + m] += wide_chunk_t(data[n]) * wide_chunk_t(other.data[m]); + wide_result[n + m + 1] += wide_result[n + m] >> chunk::bits; + wide_result[n + m] &= chunk::mask; + } + } + for (size_t n = 0; n < result.chunks; n++) { + result.data[n] = wide_result[n]; + } + result.data[result.chunks - 1] &= result.msb_mask; + return result; + } }; // Expression template for a slice, usable as lvalue or rvalue, and composable with other expression templates here. @@ -439,12 +517,14 @@ struct slice_expr : public expr_base<slice_expr<T, Stop, Start>> { slice_expr(T &expr) : expr(expr) {} slice_expr(const slice_expr<T, Stop, Start> &) = delete; + CXXRTL_ALWAYS_INLINE operator value<bits>() const { return static_cast<const value<T::bits> &>(expr) .template rtrunc<T::bits - Start>() .template trunc<bits>(); } + CXXRTL_ALWAYS_INLINE slice_expr<T, Stop, Start> &operator=(const value<bits> &rhs) { // Generic partial assignment implemented using a read-modify-write operation on the sliced expression. expr = static_cast<const value<T::bits> &>(expr) @@ -453,6 +533,7 @@ struct slice_expr : public expr_base<slice_expr<T, Stop, Start>> { } // A helper that forces the cast to value<>, which allows deduction to work. + CXXRTL_ALWAYS_INLINE value<bits> val() const { return static_cast<const value<bits> &>(*this); } @@ -469,6 +550,7 @@ struct concat_expr : public expr_base<concat_expr<T, U>> { concat_expr(T &ms_expr, U &ls_expr) : ms_expr(ms_expr), ls_expr(ls_expr) {} concat_expr(const concat_expr<T, U> &) = delete; + CXXRTL_ALWAYS_INLINE operator value<bits>() const { value<bits> ms_shifted = static_cast<const value<T::bits> &>(ms_expr) .template rzext<bits>(); @@ -477,6 +559,7 @@ struct concat_expr : public expr_base<concat_expr<T, U>> { return ms_shifted.bit_or(ls_extended); } + CXXRTL_ALWAYS_INLINE concat_expr<T, U> &operator=(const value<bits> &rhs) { ms_expr = rhs.template rtrunc<T::bits>(); ls_expr = rhs.template trunc<U::bits>(); @@ -484,6 +567,7 @@ struct concat_expr : public expr_base<concat_expr<T, U>> { } // A helper that forces the cast to value<>, which allows deduction to work. + CXXRTL_ALWAYS_INLINE value<bits> val() const { return static_cast<const value<bits> &>(*this); } @@ -508,21 +592,25 @@ struct concat_expr : public expr_base<concat_expr<T, U>> { template<class T> struct expr_base { template<size_t Stop, size_t Start = Stop> + CXXRTL_ALWAYS_INLINE slice_expr<const T, Stop, Start> slice() const { return {*static_cast<const T *>(this)}; } template<size_t Stop, size_t Start = Stop> + CXXRTL_ALWAYS_INLINE slice_expr<T, Stop, Start> slice() { return {*static_cast<T *>(this)}; } template<class U> + CXXRTL_ALWAYS_INLINE concat_expr<const T, typename std::remove_reference<const U>::type> concat(const U &other) const { return {*static_cast<const T *>(this), other}; } template<class U> + CXXRTL_ALWAYS_INLINE concat_expr<T, typename std::remove_reference<U>::type> concat(U &&other) { return {*static_cast<T *>(this), other}; } @@ -563,6 +651,18 @@ struct wire { wire(wire<Bits> &&) = default; wire<Bits> &operator=(const wire<Bits> &) = delete; + template<class IntegerT> + CXXRTL_ALWAYS_INLINE + IntegerT get() const { + return curr.template get<IntegerT>(); + } + + template<class IntegerT> + CXXRTL_ALWAYS_INLINE + void set(IntegerT other) { + next.template set<IntegerT>(other); + } + bool commit() { if (curr != next) { curr = next; @@ -608,6 +708,7 @@ struct memory { // This utterly reprehensible construct is the most reasonable way to apply a function to every element // of a parameter pack, if the elements all have different types and so cannot be cast to an initializer list. auto _ = {std::move(std::begin(init.data), std::end(init.data), data.begin() + init.offset)...}; + (void)_; } // An operator for direct memory reads. May be used at any time during the simulation. @@ -676,10 +777,8 @@ struct metadata { // In debug mode, using the wrong .as_*() function will assert. // In release mode, using the wrong .as_*() function will safely return a default value. - union { - const unsigned uint_value = 0; - const signed sint_value; - }; + const unsigned uint_value = 0; + const signed sint_value = 0; const std::string string_value = ""; const double double_value = 0.0; @@ -716,68 +815,155 @@ struct metadata { typedef std::map<std::string, metadata> metadata_map; +// Helper class to disambiguate values/wires and their aliases. +struct debug_alias {}; + // This structure is intended for consumption via foreign function interfaces, like Python's ctypes. // Because of this it uses a C-style layout that is easy to parse rather than more idiomatic C++. // // To avoid violating strict aliasing rules, this structure has to be a subclass of the one used // in the C API, or it would not be possible to cast between the pointers to these. struct debug_item : ::cxxrtl_object { + // Object types. enum : uint32_t { VALUE = CXXRTL_VALUE, WIRE = CXXRTL_WIRE, MEMORY = CXXRTL_MEMORY, + ALIAS = CXXRTL_ALIAS, + }; + + // Object flags. + enum : uint32_t { + INPUT = CXXRTL_INPUT, + OUTPUT = CXXRTL_OUTPUT, + INOUT = CXXRTL_INOUT, + DRIVEN_SYNC = CXXRTL_DRIVEN_SYNC, + DRIVEN_COMB = CXXRTL_DRIVEN_COMB, + UNDRIVEN = CXXRTL_UNDRIVEN, }; debug_item(const ::cxxrtl_object &object) : cxxrtl_object(object) {} template<size_t Bits> - debug_item(value<Bits> &item) { + debug_item(value<Bits> &item, size_t lsb_offset = 0, uint32_t flags_ = 0) { static_assert(sizeof(item) == value<Bits>::chunks * sizeof(chunk_t), "value<Bits> is not compatible with C layout"); - type = VALUE; - width = Bits; - depth = 1; - curr = item.data; - next = item.data; + type = VALUE; + flags = flags_; + width = Bits; + lsb_at = lsb_offset; + depth = 1; + zero_at = 0; + curr = item.data; + next = item.data; } template<size_t Bits> - debug_item(const value<Bits> &item) { + debug_item(const value<Bits> &item, size_t lsb_offset = 0) { static_assert(sizeof(item) == value<Bits>::chunks * sizeof(chunk_t), "value<Bits> is not compatible with C layout"); - type = VALUE; - width = Bits; - depth = 1; - curr = const_cast<uint32_t*>(item.data); - next = nullptr; + type = VALUE; + flags = DRIVEN_COMB; + width = Bits; + lsb_at = lsb_offset; + depth = 1; + zero_at = 0; + curr = const_cast<chunk_t*>(item.data); + next = nullptr; } template<size_t Bits> - debug_item(wire<Bits> &item) { + debug_item(wire<Bits> &item, size_t lsb_offset = 0, uint32_t flags_ = 0) { static_assert(sizeof(item.curr) == value<Bits>::chunks * sizeof(chunk_t) && sizeof(item.next) == value<Bits>::chunks * sizeof(chunk_t), "wire<Bits> is not compatible with C layout"); - type = WIRE; - width = Bits; - depth = 1; - curr = item.curr.data; - next = item.next.data; + type = WIRE; + flags = flags_; + width = Bits; + lsb_at = lsb_offset; + depth = 1; + zero_at = 0; + curr = item.curr.data; + next = item.next.data; } template<size_t Width> - debug_item(memory<Width> &item) { + debug_item(memory<Width> &item, size_t zero_offset = 0) { static_assert(sizeof(item.data[0]) == value<Width>::chunks * sizeof(chunk_t), "memory<Width> is not compatible with C layout"); - type = MEMORY; - width = Width; - depth = item.data.size(); - curr = item.data.empty() ? nullptr : item.data[0].data; - next = nullptr; + type = MEMORY; + flags = 0; + width = Width; + lsb_at = 0; + depth = item.data.size(); + zero_at = zero_offset; + curr = item.data.empty() ? nullptr : item.data[0].data; + next = nullptr; + } + + template<size_t Bits> + debug_item(debug_alias, const value<Bits> &item, size_t lsb_offset = 0) { + static_assert(sizeof(item) == value<Bits>::chunks * sizeof(chunk_t), + "value<Bits> is not compatible with C layout"); + type = ALIAS; + flags = DRIVEN_COMB; + width = Bits; + lsb_at = lsb_offset; + depth = 1; + zero_at = 0; + curr = const_cast<chunk_t*>(item.data); + next = nullptr; + } + + template<size_t Bits> + debug_item(debug_alias, const wire<Bits> &item, size_t lsb_offset = 0) { + static_assert(sizeof(item.curr) == value<Bits>::chunks * sizeof(chunk_t) && + sizeof(item.next) == value<Bits>::chunks * sizeof(chunk_t), + "wire<Bits> is not compatible with C layout"); + type = ALIAS; + flags = DRIVEN_COMB; + width = Bits; + lsb_at = lsb_offset; + depth = 1; + zero_at = 0; + curr = const_cast<chunk_t*>(item.curr.data); + next = nullptr; } }; static_assert(std::is_standard_layout<debug_item>::value, "debug_item is not compatible with C layout"); -typedef std::map<std::string, debug_item> debug_items; +struct debug_items { + std::map<std::string, std::vector<debug_item>> table; + + void add(const std::string &name, debug_item &&item) { + std::vector<debug_item> &parts = table[name]; + parts.emplace_back(item); + std::sort(parts.begin(), parts.end(), + [](const debug_item &a, const debug_item &b) { + return a.lsb_at < b.lsb_at; + }); + } + + size_t count(const std::string &name) const { + if (table.count(name) == 0) + return 0; + return table.at(name).size(); + } + + const std::vector<debug_item> &parts_at(const std::string &name) const { + return table.at(name); + } + + const debug_item &at(const std::string &name) const { + const std::vector<debug_item> &parts = table.at(name); + assert(parts.size() == 1); + return parts.at(0); + } + + const debug_item &operator [](const std::string &name) const { + return at(name); + } +}; struct module { module() {} @@ -799,7 +985,9 @@ struct module { return deltas; } - virtual void debug_info(debug_items &items, std::string path = "") {} + virtual void debug_info(debug_items &items, std::string path = "") { + (void)items, (void)path; + } }; } // namespace cxxrtl @@ -823,271 +1011,322 @@ using namespace cxxrtl; // std::max isn't constexpr until C++14 for no particular reason (it's an oversight), so we define our own. template<class T> +CXXRTL_ALWAYS_INLINE constexpr T max(const T &a, const T &b) { return a > b ? a : b; } // Logic operations template<size_t BitsY, size_t BitsA> +CXXRTL_ALWAYS_INLINE value<BitsY> logic_not(const value<BitsA> &a) { return value<BitsY> { a ? 0u : 1u }; } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> logic_and(const value<BitsA> &a, const value<BitsB> &b) { - return value<BitsY> { (bool(a) & bool(b)) ? 1u : 0u }; + return value<BitsY> { (bool(a) && bool(b)) ? 1u : 0u }; } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> logic_or(const value<BitsA> &a, const value<BitsB> &b) { - return value<BitsY> { (bool(a) | bool(b)) ? 1u : 0u }; + return value<BitsY> { (bool(a) || bool(b)) ? 1u : 0u }; } // Reduction operations template<size_t BitsY, size_t BitsA> +CXXRTL_ALWAYS_INLINE value<BitsY> reduce_and(const value<BitsA> &a) { return value<BitsY> { a.bit_not().is_zero() ? 1u : 0u }; } template<size_t BitsY, size_t BitsA> +CXXRTL_ALWAYS_INLINE value<BitsY> reduce_or(const value<BitsA> &a) { return value<BitsY> { a ? 1u : 0u }; } template<size_t BitsY, size_t BitsA> +CXXRTL_ALWAYS_INLINE value<BitsY> reduce_xor(const value<BitsA> &a) { return value<BitsY> { (a.ctpop() % 2) ? 1u : 0u }; } template<size_t BitsY, size_t BitsA> +CXXRTL_ALWAYS_INLINE value<BitsY> reduce_xnor(const value<BitsA> &a) { return value<BitsY> { (a.ctpop() % 2) ? 0u : 1u }; } template<size_t BitsY, size_t BitsA> +CXXRTL_ALWAYS_INLINE value<BitsY> reduce_bool(const value<BitsA> &a) { return value<BitsY> { a ? 1u : 0u }; } // Bitwise operations template<size_t BitsY, size_t BitsA> +CXXRTL_ALWAYS_INLINE value<BitsY> not_u(const value<BitsA> &a) { return a.template zcast<BitsY>().bit_not(); } template<size_t BitsY, size_t BitsA> +CXXRTL_ALWAYS_INLINE value<BitsY> not_s(const value<BitsA> &a) { return a.template scast<BitsY>().bit_not(); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> and_uu(const value<BitsA> &a, const value<BitsB> &b) { return a.template zcast<BitsY>().bit_and(b.template zcast<BitsY>()); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> and_ss(const value<BitsA> &a, const value<BitsB> &b) { return a.template scast<BitsY>().bit_and(b.template scast<BitsY>()); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> or_uu(const value<BitsA> &a, const value<BitsB> &b) { return a.template zcast<BitsY>().bit_or(b.template zcast<BitsY>()); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> or_ss(const value<BitsA> &a, const value<BitsB> &b) { return a.template scast<BitsY>().bit_or(b.template scast<BitsY>()); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> xor_uu(const value<BitsA> &a, const value<BitsB> &b) { return a.template zcast<BitsY>().bit_xor(b.template zcast<BitsY>()); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> xor_ss(const value<BitsA> &a, const value<BitsB> &b) { return a.template scast<BitsY>().bit_xor(b.template scast<BitsY>()); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> xnor_uu(const value<BitsA> &a, const value<BitsB> &b) { return a.template zcast<BitsY>().bit_xor(b.template zcast<BitsY>()).bit_not(); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> xnor_ss(const value<BitsA> &a, const value<BitsB> &b) { return a.template scast<BitsY>().bit_xor(b.template scast<BitsY>()).bit_not(); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> shl_uu(const value<BitsA> &a, const value<BitsB> &b) { return a.template zcast<BitsY>().template shl(b); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> shl_su(const value<BitsA> &a, const value<BitsB> &b) { return a.template scast<BitsY>().template shl(b); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> sshl_uu(const value<BitsA> &a, const value<BitsB> &b) { return a.template zcast<BitsY>().template shl(b); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> sshl_su(const value<BitsA> &a, const value<BitsB> &b) { return a.template scast<BitsY>().template shl(b); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> shr_uu(const value<BitsA> &a, const value<BitsB> &b) { return a.template shr(b).template zcast<BitsY>(); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> shr_su(const value<BitsA> &a, const value<BitsB> &b) { return a.template shr(b).template scast<BitsY>(); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> sshr_uu(const value<BitsA> &a, const value<BitsB> &b) { return a.template shr(b).template zcast<BitsY>(); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> sshr_su(const value<BitsA> &a, const value<BitsB> &b) { return a.template sshr(b).template scast<BitsY>(); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> shift_uu(const value<BitsA> &a, const value<BitsB> &b) { return shr_uu<BitsY>(a, b); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> shift_su(const value<BitsA> &a, const value<BitsB> &b) { return shr_su<BitsY>(a, b); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> shift_us(const value<BitsA> &a, const value<BitsB> &b) { return b.is_neg() ? shl_uu<BitsY>(a, b.template sext<BitsB + 1>().neg()) : shr_uu<BitsY>(a, b); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> shift_ss(const value<BitsA> &a, const value<BitsB> &b) { return b.is_neg() ? shl_su<BitsY>(a, b.template sext<BitsB + 1>().neg()) : shr_su<BitsY>(a, b); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> shiftx_uu(const value<BitsA> &a, const value<BitsB> &b) { return shift_uu<BitsY>(a, b); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> shiftx_su(const value<BitsA> &a, const value<BitsB> &b) { return shift_su<BitsY>(a, b); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> shiftx_us(const value<BitsA> &a, const value<BitsB> &b) { return shift_us<BitsY>(a, b); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> shiftx_ss(const value<BitsA> &a, const value<BitsB> &b) { return shift_ss<BitsY>(a, b); } // Comparison operations template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> eq_uu(const value<BitsA> &a, const value<BitsB> &b) { constexpr size_t BitsExt = max(BitsA, BitsB); return value<BitsY>{ a.template zext<BitsExt>() == b.template zext<BitsExt>() ? 1u : 0u }; } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> eq_ss(const value<BitsA> &a, const value<BitsB> &b) { constexpr size_t BitsExt = max(BitsA, BitsB); return value<BitsY>{ a.template sext<BitsExt>() == b.template sext<BitsExt>() ? 1u : 0u }; } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> ne_uu(const value<BitsA> &a, const value<BitsB> &b) { constexpr size_t BitsExt = max(BitsA, BitsB); return value<BitsY>{ a.template zext<BitsExt>() != b.template zext<BitsExt>() ? 1u : 0u }; } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> ne_ss(const value<BitsA> &a, const value<BitsB> &b) { constexpr size_t BitsExt = max(BitsA, BitsB); return value<BitsY>{ a.template sext<BitsExt>() != b.template sext<BitsExt>() ? 1u : 0u }; } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> eqx_uu(const value<BitsA> &a, const value<BitsB> &b) { return eq_uu<BitsY>(a, b); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> eqx_ss(const value<BitsA> &a, const value<BitsB> &b) { return eq_ss<BitsY>(a, b); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> nex_uu(const value<BitsA> &a, const value<BitsB> &b) { return ne_uu<BitsY>(a, b); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> nex_ss(const value<BitsA> &a, const value<BitsB> &b) { return ne_ss<BitsY>(a, b); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> gt_uu(const value<BitsA> &a, const value<BitsB> &b) { constexpr size_t BitsExt = max(BitsA, BitsB); return value<BitsY> { b.template zext<BitsExt>().ucmp(a.template zext<BitsExt>()) ? 1u : 0u }; } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> gt_ss(const value<BitsA> &a, const value<BitsB> &b) { constexpr size_t BitsExt = max(BitsA, BitsB); return value<BitsY> { b.template sext<BitsExt>().scmp(a.template sext<BitsExt>()) ? 1u : 0u }; } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> ge_uu(const value<BitsA> &a, const value<BitsB> &b) { constexpr size_t BitsExt = max(BitsA, BitsB); return value<BitsY> { !a.template zext<BitsExt>().ucmp(b.template zext<BitsExt>()) ? 1u : 0u }; } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> ge_ss(const value<BitsA> &a, const value<BitsB> &b) { constexpr size_t BitsExt = max(BitsA, BitsB); return value<BitsY> { !a.template sext<BitsExt>().scmp(b.template sext<BitsExt>()) ? 1u : 0u }; } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> lt_uu(const value<BitsA> &a, const value<BitsB> &b) { constexpr size_t BitsExt = max(BitsA, BitsB); return value<BitsY> { a.template zext<BitsExt>().ucmp(b.template zext<BitsExt>()) ? 1u : 0u }; } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> lt_ss(const value<BitsA> &a, const value<BitsB> &b) { constexpr size_t BitsExt = max(BitsA, BitsB); return value<BitsY> { a.template sext<BitsExt>().scmp(b.template sext<BitsExt>()) ? 1u : 0u }; } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> le_uu(const value<BitsA> &a, const value<BitsB> &b) { constexpr size_t BitsExt = max(BitsA, BitsB); return value<BitsY> { !b.template zext<BitsExt>().ucmp(a.template zext<BitsExt>()) ? 1u : 0u }; } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> le_ss(const value<BitsA> &a, const value<BitsB> &b) { constexpr size_t BitsExt = max(BitsA, BitsB); return value<BitsY> { !b.template sext<BitsExt>().scmp(a.template sext<BitsExt>()) ? 1u : 0u }; @@ -1095,71 +1334,68 @@ value<BitsY> le_ss(const value<BitsA> &a, const value<BitsB> &b) { // Arithmetic operations template<size_t BitsY, size_t BitsA> +CXXRTL_ALWAYS_INLINE value<BitsY> pos_u(const value<BitsA> &a) { return a.template zcast<BitsY>(); } template<size_t BitsY, size_t BitsA> +CXXRTL_ALWAYS_INLINE value<BitsY> pos_s(const value<BitsA> &a) { return a.template scast<BitsY>(); } template<size_t BitsY, size_t BitsA> +CXXRTL_ALWAYS_INLINE value<BitsY> neg_u(const value<BitsA> &a) { return a.template zcast<BitsY>().neg(); } template<size_t BitsY, size_t BitsA> +CXXRTL_ALWAYS_INLINE value<BitsY> neg_s(const value<BitsA> &a) { return a.template scast<BitsY>().neg(); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> add_uu(const value<BitsA> &a, const value<BitsB> &b) { return a.template zcast<BitsY>().add(b.template zcast<BitsY>()); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> add_ss(const value<BitsA> &a, const value<BitsB> &b) { return a.template scast<BitsY>().add(b.template scast<BitsY>()); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> sub_uu(const value<BitsA> &a, const value<BitsB> &b) { return a.template zcast<BitsY>().sub(b.template zcast<BitsY>()); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> sub_ss(const value<BitsA> &a, const value<BitsB> &b) { return a.template scast<BitsY>().sub(b.template scast<BitsY>()); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> mul_uu(const value<BitsA> &a, const value<BitsB> &b) { - value<BitsY> product; - value<BitsY> multiplicand = a.template zcast<BitsY>(); - const value<BitsB> &multiplier = b; - uint32_t multiplicand_shift = 0; - for (size_t step = 0; step < BitsB; step++) { - if (multiplier.bit(step)) { - multiplicand = multiplicand.shl(value<32> { multiplicand_shift }); - product = product.add(multiplicand); - multiplicand_shift = 0; - } - multiplicand_shift++; - } - return product; + constexpr size_t BitsM = BitsA >= BitsB ? BitsA : BitsB; + return a.template zcast<BitsM>().template mul<BitsY>(b.template zcast<BitsM>()); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> mul_ss(const value<BitsA> &a, const value<BitsB> &b) { - value<BitsB + 1> ub = b.template sext<BitsB + 1>(); - if (ub.is_neg()) ub = ub.neg(); - value<BitsY> y = mul_uu<BitsY>(a.template scast<BitsY>(), ub); - return b.is_neg() ? y.neg() : y; + return a.template scast<BitsY>().template mul<BitsY>(b.template scast<BitsY>()); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE std::pair<value<BitsY>, value<BitsY>> divmod_uu(const value<BitsA> &a, const value<BitsB> &b) { constexpr size_t Bits = max(BitsY, max(BitsA, BitsB)); value<Bits> quotient; @@ -1181,6 +1417,7 @@ std::pair<value<BitsY>, value<BitsY>> divmod_uu(const value<BitsA> &a, const val } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE std::pair<value<BitsY>, value<BitsY>> divmod_ss(const value<BitsA> &a, const value<BitsB> &b) { value<BitsA + 1> ua = a.template sext<BitsA + 1>(); value<BitsB + 1> ub = b.template sext<BitsB + 1>(); @@ -1194,21 +1431,25 @@ std::pair<value<BitsY>, value<BitsY>> divmod_ss(const value<BitsA> &a, const val } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> div_uu(const value<BitsA> &a, const value<BitsB> &b) { return divmod_uu<BitsY>(a, b).first; } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> div_ss(const value<BitsA> &a, const value<BitsB> &b) { return divmod_ss<BitsY>(a, b).first; } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> mod_uu(const value<BitsA> &a, const value<BitsB> &b) { return divmod_uu<BitsY>(a, b).second; } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> mod_ss(const value<BitsA> &a, const value<BitsB> &b) { return divmod_ss<BitsY>(a, b).second; } diff --git a/backends/cxxrtl/cxxrtl_backend.cc b/backends/cxxrtl/cxxrtl_backend.cc index 4c04a2f14..a48ea5b23 100644 --- a/backends/cxxrtl/cxxrtl_backend.cc +++ b/backends/cxxrtl/cxxrtl_backend.cc @@ -22,6 +22,7 @@ #include "kernel/sigtools.h" #include "kernel/utils.h" #include "kernel/celltypes.h" +#include "kernel/mem.h" #include "kernel/log.h" USING_YOSYS_NAMESPACE @@ -171,11 +172,6 @@ struct Scheduler { } }; -bool is_input_wire(const RTLIL::Wire *wire) -{ - return wire->port_input && !wire->port_output; -} - bool is_unary_cell(RTLIL::IdString type) { return type.in( @@ -202,19 +198,15 @@ bool is_extending_cell(RTLIL::IdString type) bool is_elidable_cell(RTLIL::IdString type) { return is_unary_cell(type) || is_binary_cell(type) || type.in( - ID($mux), ID($concat), ID($slice)); -} - -bool is_sync_ff_cell(RTLIL::IdString type) -{ - return type.in( - ID($dff), ID($dffe)); + ID($mux), ID($concat), ID($slice), ID($pmux)); } bool is_ff_cell(RTLIL::IdString type) { - return is_sync_ff_cell(type) || type.in( - ID($adff), ID($dffsr), ID($dlatch), ID($dlatchsr), ID($sr)); + return type.in( + ID($dff), ID($dffe), ID($sdff), ID($sdffe), ID($sdffce), + ID($adff), ID($adffe), ID($dffsr), ID($dffsre), + ID($dlatch), ID($adlatch), ID($dlatchsr), ID($sr)); } bool is_internal_cell(RTLIL::IdString type) @@ -282,6 +274,7 @@ struct FlowGraph { std::vector<Node*> nodes; dict<const RTLIL::Wire*, pool<Node*, hash_ptr_ops>> wire_comb_defs, wire_sync_defs, wire_uses; dict<const RTLIL::Wire*, bool> wire_def_elidable, wire_use_elidable; + dict<RTLIL::SigBit, bool> bit_has_state; ~FlowGraph() { @@ -289,17 +282,24 @@ struct FlowGraph { delete node; } - void add_defs(Node *node, const RTLIL::SigSpec &sig, bool fully_sync, bool elidable) + void add_defs(Node *node, const RTLIL::SigSpec &sig, bool is_ff, bool elidable) { for (auto chunk : sig.chunks()) if (chunk.wire) { - if (fully_sync) + if (is_ff) { + // A sync def means that a wire holds design state because it is driven directly by + // a flip-flop output. Such a wire can never be unbuffered. wire_sync_defs[chunk.wire].insert(node); - else + } else { + // A comb def means that a wire doesn't hold design state. It might still be connected, + // indirectly, to a flip-flop output. wire_comb_defs[chunk.wire].insert(node); + } } + for (auto bit : sig.bits()) + bit_has_state[bit] |= is_ff; // Only comb defs of an entire wire in the right order can be elided. - if (!fully_sync && sig.is_wire()) + if (!is_ff && sig.is_wire()) wire_def_elidable[sig.as_wire()] = elidable; } @@ -327,7 +327,7 @@ struct FlowGraph { // Connections void add_connect_defs_uses(Node *node, const RTLIL::SigSig &conn) { - add_defs(node, conn.first, /*fully_sync=*/false, /*elidable=*/true); + add_defs(node, conn.first, /*is_ff=*/false, /*elidable=*/true); add_uses(node, conn.second); } @@ -374,7 +374,7 @@ struct FlowGraph { if (cell->output(conn.first)) if (is_cxxrtl_sync_port(cell, conn.first)) { // See note regarding elidability below. - add_defs(node, conn.second, /*fully_sync=*/false, /*elidable=*/false); + add_defs(node, conn.second, /*is_ff=*/false, /*elidable=*/false); } } @@ -383,18 +383,18 @@ struct FlowGraph { for (auto conn : cell->connections()) { if (cell->output(conn.first)) { if (is_elidable_cell(cell->type)) - add_defs(node, conn.second, /*fully_sync=*/false, /*elidable=*/true); - else if (is_sync_ff_cell(cell->type) || (cell->type == ID($memrd) && cell->getParam(ID::CLK_ENABLE).as_bool())) - add_defs(node, conn.second, /*fully_sync=*/true, /*elidable=*/false); + add_defs(node, conn.second, /*is_ff=*/false, /*elidable=*/true); + else if (is_ff_cell(cell->type) || (cell->type == ID($memrd) && cell->getParam(ID::CLK_ENABLE).as_bool())) + add_defs(node, conn.second, /*is_ff=*/true, /*elidable=*/false); else if (is_internal_cell(cell->type)) - add_defs(node, conn.second, /*fully_sync=*/false, /*elidable=*/false); + add_defs(node, conn.second, /*is_ff=*/false, /*elidable=*/false); else if (!is_cxxrtl_sync_port(cell, conn.first)) { // Although at first it looks like outputs of user-defined cells may always be elided, the reality is // more complex. Fully sync outputs produce no defs and so don't participate in elision. Fully comb // outputs are assigned in a different way depending on whether the cell's eval() immediately converged. // Unknown/mixed outputs could be elided, but should be rare in practical designs and don't justify // the infrastructure required to elide outputs of cells with many of them. - add_defs(node, conn.second, /*fully_sync=*/false, /*elidable=*/false); + add_defs(node, conn.second, /*is_ff=*/false, /*elidable=*/false); } } if (cell->input(conn.first)) @@ -432,7 +432,7 @@ struct FlowGraph { void add_case_defs_uses(Node *node, const RTLIL::CaseRule *case_) { for (auto &action : case_->actions) { - add_defs(node, action.first, /*is_sync=*/false, /*elidable=*/false); + add_defs(node, action.first, /*is_ff=*/false, /*elidable=*/false); add_uses(node, action.second); } for (auto sub_switch : case_->switches) { @@ -451,9 +451,9 @@ struct FlowGraph { for (auto sync : process->syncs) for (auto action : sync->actions) { if (sync->type == RTLIL::STp || sync->type == RTLIL::STn || sync->type == RTLIL::STe) - add_defs(node, action.first, /*is_sync=*/true, /*elidable=*/false); + add_defs(node, action.first, /*is_ff=*/true, /*elidable=*/false); else - add_defs(node, action.first, /*is_sync=*/false, /*elidable=*/false); + add_defs(node, action.first, /*is_ff=*/false, /*elidable=*/false); add_uses(node, action.second); } } @@ -527,12 +527,16 @@ struct CxxrtlWorker { std::ostream *impl_f = nullptr; std::ostream *intf_f = nullptr; - bool elide_internal = false; - bool elide_public = false; + bool run_hierarchy = false; + bool run_flatten = false; + bool run_proc = false; + + bool unbuffer_internal = false; + bool unbuffer_public = false; bool localize_internal = false; bool localize_public = false; - bool run_proc_flatten = false; - bool max_opt_level = false; + bool elide_internal = false; + bool elide_public = false; bool debug_info = false; @@ -547,9 +551,11 @@ struct CxxrtlWorker { dict<const RTLIL::Cell*, pool<const RTLIL::Cell*>> transparent_for; dict<const RTLIL::Wire*, FlowGraph::Node> elided_wires; dict<const RTLIL::Module*, std::vector<FlowGraph::Node>> schedule; + pool<const RTLIL::Wire*> unbuffered_wires; pool<const RTLIL::Wire*> localized_wires; dict<const RTLIL::Wire*, const RTLIL::Wire*> debug_alias_wires; dict<const RTLIL::Wire*, RTLIL::Const> debug_const_wires; + dict<RTLIL::SigBit, bool> bit_has_state; dict<const RTLIL::Module*, pool<std::string>> blackbox_specializations; dict<const RTLIL::Module*, bool> eval_converges; @@ -786,7 +792,8 @@ struct CxxrtlWorker { dump_const(chunk.data, chunk.width, chunk.offset); return false; } else { - if (!is_lhs && elided_wires.count(chunk.wire)) { + if (elided_wires.count(chunk.wire)) { + log_assert(!is_lhs); const FlowGraph::Node &node = elided_wires[chunk.wire]; switch (node.type) { case FlowGraph::Node::Type::CONNECT: @@ -799,7 +806,7 @@ struct CxxrtlWorker { default: log_assert(false); } - } else if (localized_wires[chunk.wire] || is_input_wire(chunk.wire)) { + } else if (unbuffered_wires[chunk.wire]) { f << mangle(chunk.wire); } else { f << mangle(chunk.wire) << (is_lhs ? ".next" : ".curr"); @@ -942,6 +949,21 @@ struct CxxrtlWorker { f << " : "; dump_sigspec_rhs(cell->getPort(ID::A)); f << ")"; + // Parallel (one-hot) muxes + } else if (cell->type == ID($pmux)) { + int width = cell->getParam(ID::WIDTH).as_int(); + int s_width = cell->getParam(ID::S_WIDTH).as_int(); + for (int part = 0; part < s_width; part++) { + f << "("; + dump_sigspec_rhs(cell->getPort(ID::S).extract(part)); + f << " ? "; + dump_sigspec_rhs(cell->getPort(ID::B).extract(part * width, width)); + f << " : "; + } + dump_sigspec_rhs(cell->getPort(ID::A)); + for (int part = 0; part < s_width; part++) { + f << ")"; + } // Concats } else if (cell->type == ID($concat)) { dump_sigspec_rhs(cell->getPort(ID::B)); @@ -1008,35 +1030,6 @@ struct CxxrtlWorker { f << " = "; dump_cell_elided(cell); f << ";\n"; - // Parallel (one-hot) muxes - } else if (cell->type == ID($pmux)) { - int width = cell->getParam(ID::WIDTH).as_int(); - int s_width = cell->getParam(ID::S_WIDTH).as_int(); - bool first = true; - for (int part = 0; part < s_width; part++) { - f << (first ? indent : " else "); - first = false; - f << "if ("; - dump_sigspec_rhs(cell->getPort(ID::S).extract(part)); - f << ") {\n"; - inc_indent(); - f << indent; - dump_sigspec_lhs(cell->getPort(ID::Y)); - f << " = "; - dump_sigspec_rhs(cell->getPort(ID::B).extract(part * width, width)); - f << ";\n"; - dec_indent(); - f << indent << "}"; - } - f << " else {\n"; - inc_indent(); - f << indent; - dump_sigspec_lhs(cell->getPort(ID::Y)); - f << " = "; - dump_sigspec_rhs(cell->getPort(ID::A)); - f << ";\n"; - dec_indent(); - f << indent << "}\n"; // Flip-flops } else if (is_ff_cell(cell->type)) { if (cell->hasPort(ID::CLK) && cell->getPort(ID::CLK).is_wire()) { @@ -1046,7 +1039,7 @@ struct CxxrtlWorker { f << indent << "if (" << (cell->getParam(ID::CLK_POLARITY).as_bool() ? "posedge_" : "negedge_") << mangle(clk_bit) << ") {\n"; inc_indent(); - if (cell->type == ID($dffe)) { + if (cell->hasPort(ID::EN)) { f << indent << "if ("; dump_sigspec_rhs(cell->getPort(ID::EN)); f << " == value<1> {" << cell->getParam(ID::EN_POLARITY).as_bool() << "u}) {\n"; @@ -1057,7 +1050,24 @@ struct CxxrtlWorker { f << " = "; dump_sigspec_rhs(cell->getPort(ID::D)); f << ";\n"; - if (cell->type == ID($dffe)) { + if (cell->hasPort(ID::EN) && cell->type != ID($sdffce)) { + dec_indent(); + f << indent << "}\n"; + } + if (cell->hasPort(ID::SRST)) { + f << indent << "if ("; + dump_sigspec_rhs(cell->getPort(ID::SRST)); + f << " == value<1> {" << cell->getParam(ID::SRST_POLARITY).as_bool() << "u}) {\n"; + inc_indent(); + f << indent; + dump_sigspec_lhs(cell->getPort(ID::Q)); + f << " = "; + dump_const(cell->getParam(ID::SRST_VALUE)); + f << ";\n"; + dec_indent(); + f << indent << "}\n"; + } + if (cell->hasPort(ID::EN) && cell->type == ID($sdffce)) { dec_indent(); f << indent << "}\n"; } @@ -1139,7 +1149,7 @@ struct CxxrtlWorker { } // The generated code has two bounds checks; one in an assertion, and another that guards the read. // This is done so that the code does not invoke undefined behavior under any conditions, but nevertheless - // loudly crashes if an illegal condition is encountered. The assert may be turned off with -NDEBUG not + // loudly crashes if an illegal condition is encountered. The assert may be turned off with -DNDEBUG not // just for release builds, but also to make sure the simulator (which is presumably embedded in some // larger program) will never crash the code that calls into it. // @@ -1148,31 +1158,33 @@ struct CxxrtlWorker { f << indent << "if(" << valid_index_temp << ".valid) {\n"; inc_indent(); if (writable_memories[memory]) { - std::string addr_temp = fresh_temporary(); - f << indent << "const value<" << cell->getPort(ID::ADDR).size() << "> &" << addr_temp << " = "; - dump_sigspec_rhs(cell->getPort(ID::ADDR)); - f << ";\n"; std::string lhs_temp = fresh_temporary(); f << indent << "value<" << memory->width << "> " << lhs_temp << " = " << mangle(memory) << "[" << valid_index_temp << ".index];\n"; std::vector<const RTLIL::Cell*> memwr_cells(transparent_for[cell].begin(), transparent_for[cell].end()); - std::sort(memwr_cells.begin(), memwr_cells.end(), - [](const RTLIL::Cell *a, const RTLIL::Cell *b) { - return a->getParam(ID::PRIORITY).as_int() < b->getParam(ID::PRIORITY).as_int(); - }); - for (auto memwr_cell : memwr_cells) { - f << indent << "if (" << addr_temp << " == "; - dump_sigspec_rhs(memwr_cell->getPort(ID::ADDR)); - f << ") {\n"; - inc_indent(); - f << indent << lhs_temp << " = " << lhs_temp; - f << ".update("; - dump_sigspec_rhs(memwr_cell->getPort(ID::DATA)); - f << ", "; - dump_sigspec_rhs(memwr_cell->getPort(ID::EN)); - f << ");\n"; - dec_indent(); - f << indent << "}\n"; + if (!memwr_cells.empty()) { + std::string addr_temp = fresh_temporary(); + f << indent << "const value<" << cell->getPort(ID::ADDR).size() << "> &" << addr_temp << " = "; + dump_sigspec_rhs(cell->getPort(ID::ADDR)); + f << ";\n"; + std::sort(memwr_cells.begin(), memwr_cells.end(), + [](const RTLIL::Cell *a, const RTLIL::Cell *b) { + return a->getParam(ID::PRIORITY).as_int() < b->getParam(ID::PRIORITY).as_int(); + }); + for (auto memwr_cell : memwr_cells) { + f << indent << "if (" << addr_temp << " == "; + dump_sigspec_rhs(memwr_cell->getPort(ID::ADDR)); + f << ") {\n"; + inc_indent(); + f << indent << lhs_temp << " = " << lhs_temp; + f << ".update("; + dump_sigspec_rhs(memwr_cell->getPort(ID::DATA)); + f << ", "; + dump_sigspec_rhs(memwr_cell->getPort(ID::EN)); + f << ");\n"; + dec_indent(); + f << indent << "}\n"; + } } f << indent; dump_sigspec_lhs(cell->getPort(ID::DATA)); @@ -1434,13 +1446,12 @@ struct CxxrtlWorker { { if (elided_wires.count(wire)) return; - if (localized_wires.count(wire) != is_local_context) - return; - if (is_local_context) { + if (localized_wires[wire] && is_local_context) { dump_attrs(wire); f << indent << "value<" << wire->width << "> " << mangle(wire) << ";\n"; - } else { + } + if (!localized_wires[wire] && !is_local_context) { std::string width; if (wire->module->has_attribute(ID(cxxrtl_blackbox)) && wire->has_attribute(ID(cxxrtl_width))) { width = wire->get_string_attribute(ID(cxxrtl_width)); @@ -1449,14 +1460,21 @@ struct CxxrtlWorker { } dump_attrs(wire); - f << indent << (is_input_wire(wire) ? "value" : "wire") << "<" << width << "> " << mangle(wire); + f << indent; + if (wire->port_input && wire->port_output) + f << "/*inout*/ "; + else if (wire->port_input) + f << "/*input*/ "; + else if (wire->port_output) + f << "/*output*/ "; + f << (unbuffered_wires[wire] ? "value" : "wire") << "<" << width << "> " << mangle(wire); if (wire->has_attribute(ID::init)) { f << " "; dump_const_init(wire->attributes.at(ID::init)); } f << ";\n"; if (edge_wires[wire]) { - if (is_input_wire(wire)) { + if (unbuffered_wires[wire]) { f << indent << "value<" << width << "> prev_" << mangle(wire); if (wire->has_attribute(ID::init)) { f << " "; @@ -1467,7 +1485,7 @@ struct CxxrtlWorker { for (auto edge_type : edge_types) { if (edge_type.first.wire == wire) { std::string prev, next; - if (is_input_wire(wire)) { + if (unbuffered_wires[wire]) { prev = "prev_" + mangle(edge_type.first.wire); next = mangle(edge_type.first.wire); } else { @@ -1590,9 +1608,9 @@ struct CxxrtlWorker { inc_indent(); f << indent << "bool changed = false;\n"; for (auto wire : module->wires()) { - if (elided_wires.count(wire) || localized_wires.count(wire)) + if (elided_wires.count(wire)) continue; - if (is_input_wire(wire)) { + if (unbuffered_wires[wire]) { if (edge_wires[wire]) f << indent << "prev_" << mangle(wire) << " = " << mangle(wire) << ";\n"; continue; @@ -1619,57 +1637,122 @@ struct CxxrtlWorker { void dump_debug_info_method(RTLIL::Module *module) { + size_t count_public_wires = 0; size_t count_const_wires = 0; size_t count_alias_wires = 0; size_t count_member_wires = 0; size_t count_skipped_wires = 0; + size_t count_driven_sync = 0; + size_t count_driven_comb = 0; + size_t count_undriven = 0; + size_t count_mixed_driver = 0; inc_indent(); f << indent << "assert(path.empty() || path[path.size() - 1] == ' ');\n"; for (auto wire : module->wires()) { if (wire->name[0] != '\\') continue; + if (module->get_bool_attribute(ID(cxxrtl_blackbox)) && (wire->port_id == 0)) + continue; + count_public_wires++; if (debug_const_wires.count(wire)) { // Wire tied to a constant f << indent << "static const value<" << wire->width << "> const_" << mangle(wire) << " = "; dump_const(debug_const_wires[wire]); f << ";\n"; - f << indent << "items.emplace(path + " << escape_cxx_string(get_hdl_name(wire)); - f << ", debug_item(const_" << mangle(wire) << "));\n"; + f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(wire)); + f << ", debug_item(const_" << mangle(wire) << ", "; + f << wire->start_offset << "));\n"; count_const_wires++; } else if (debug_alias_wires.count(wire)) { // Alias of a member wire - f << indent << "items.emplace(path + " << escape_cxx_string(get_hdl_name(wire)); - f << ", debug_item(" << mangle(debug_alias_wires[wire]) << "));\n"; + f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(wire)); + f << ", debug_item(debug_alias(), " << mangle(debug_alias_wires[wire]) << ", "; + f << wire->start_offset << "));\n"; count_alias_wires++; } else if (!localized_wires.count(wire)) { // Member wire - f << indent << "items.emplace(path + " << escape_cxx_string(get_hdl_name(wire)); - f << ", debug_item(" << mangle(wire) << "));\n"; + std::vector<std::string> flags; + + if (wire->port_input && wire->port_output) + flags.push_back("INOUT"); + else if (wire->port_input) + flags.push_back("INPUT"); + else if (wire->port_output) + flags.push_back("OUTPUT"); + + bool has_driven_sync = false; + bool has_driven_comb = false; + bool has_undriven = false; + SigSpec sig(wire); + for (auto bit : sig.bits()) + if (!bit_has_state.count(bit)) + has_undriven = true; + else if (bit_has_state[bit]) + has_driven_sync = true; + else + has_driven_comb = true; + if (has_driven_sync) + flags.push_back("DRIVEN_SYNC"); + if (has_driven_sync && !has_driven_comb && !has_undriven) + count_driven_sync++; + if (has_driven_comb) + flags.push_back("DRIVEN_COMB"); + if (!has_driven_sync && has_driven_comb && !has_undriven) + count_driven_comb++; + if (has_undriven) + flags.push_back("UNDRIVEN"); + if (!has_driven_sync && !has_driven_comb && has_undriven) + count_undriven++; + if (has_driven_sync + has_driven_comb + has_undriven > 1) + count_mixed_driver++; + + f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(wire)); + f << ", debug_item(" << mangle(wire) << ", "; + f << wire->start_offset; + bool first = true; + for (auto flag : flags) { + if (first) { + first = false; + f << ", "; + } else { + f << "|"; + } + f << "debug_item::" << flag; + } + f << "));\n"; count_member_wires++; } else { count_skipped_wires++; } } - for (auto &memory_it : module->memories) { - if (memory_it.first[0] != '\\') - continue; - f << indent << "items.emplace(path + " << escape_cxx_string(get_hdl_name(memory_it.second)); - f << ", debug_item(" << mangle(memory_it.second) << "));\n"; - } - for (auto cell : module->cells()) { - if (is_internal_cell(cell->type)) - continue; - const char *access = is_cxxrtl_blackbox_cell(cell) ? "->" : "."; - f << indent << mangle(cell) << access << "debug_info(items, "; - f << "path + " << escape_cxx_string(get_hdl_name(cell) + ' ') << ");\n"; + if (!module->get_bool_attribute(ID(cxxrtl_blackbox))) { + for (auto &memory_it : module->memories) { + if (memory_it.first[0] != '\\') + continue; + f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(memory_it.second)); + f << ", debug_item(" << mangle(memory_it.second) << ", "; + f << memory_it.second->start_offset << "));\n"; + } + for (auto cell : module->cells()) { + if (is_internal_cell(cell->type)) + continue; + const char *access = is_cxxrtl_blackbox_cell(cell) ? "->" : "."; + f << indent << mangle(cell) << access << "debug_info(items, "; + f << "path + " << escape_cxx_string(get_hdl_name(cell) + ' ') << ");\n"; + } } dec_indent(); - log_debug("Debug information statistics for module %s:\n", log_id(module)); - log_debug(" Const wires: %zu\n", count_const_wires); - log_debug(" Alias wires: %zu\n", count_alias_wires); - log_debug(" Member wires: %zu\n", count_member_wires); - log_debug(" Other wires: %zu (no debug information)\n", count_skipped_wires); + log_debug("Debug information statistics for module `%s':\n", log_id(module)); + log_debug(" Public wires: %zu, of which:\n", count_public_wires); + log_debug(" Const wires: %zu\n", count_const_wires); + log_debug(" Alias wires: %zu\n", count_alias_wires); + log_debug(" Member wires: %zu, of which:\n", count_member_wires); + log_debug(" Driven sync: %zu\n", count_driven_sync); + log_debug(" Driven comb: %zu\n", count_driven_comb); + log_debug(" Undriven: %zu\n", count_undriven); + log_debug(" Mixed driver: %zu\n", count_mixed_driver); + log_debug(" Other wires: %zu (no debug information)\n", count_skipped_wires); } void dump_metadata_map(const dict<RTLIL::IdString, RTLIL::Const> &metadata_map) @@ -1840,7 +1923,8 @@ struct CxxrtlWorker { topo_design.edge(cell_module, module); } } - log_assert(topo_design.sort()); + bool no_loops = topo_design.sort(); + log_assert(no_loops); modules.insert(modules.end(), topo_design.sorted.begin(), topo_design.sorted.end()); if (split_intf) { @@ -1912,10 +1996,12 @@ struct CxxrtlWorker { f << "} // namespace " << design_ns << "\n"; f << "\n"; if (top_module != nullptr && debug_info) { + f << "extern \"C\"\n"; f << "cxxrtl_toplevel " << design_ns << "_create() {\n"; inc_indent(); + std::string top_type = design_ns + "::" + mangle(top_module); f << indent << "return new _cxxrtl_toplevel { "; - f << "std::make_unique<" << design_ns << "::" << mangle(top_module) << ">()"; + f << "std::unique_ptr<" << top_type << ">(new " + top_type + ")"; f << " };\n"; dec_indent(); f << "}\n"; @@ -1949,7 +2035,7 @@ struct CxxrtlWorker { void analyze_design(RTLIL::Design *design) { bool has_feedback_arcs = false; - bool has_buffered_wires = false; + bool has_buffered_comb_wires = false; for (auto module : design->modules()) { if (!design->selected_module(module)) @@ -1961,6 +2047,8 @@ struct CxxrtlWorker { if (module->get_bool_attribute(ID(cxxrtl_blackbox))) { for (auto port : module->ports) { RTLIL::Wire *wire = module->wire(port); + if (wire->port_input && !wire->port_output) + unbuffered_wires.insert(wire); if (wire->has_attribute(ID(cxxrtl_edge))) { RTLIL::Const edge_attr = wire->attributes[ID(cxxrtl_edge)]; if (!(edge_attr.flags & RTLIL::CONST_FLAG_STRING) || (int)edge_attr.decode_string().size() != GetSize(wire)) @@ -2016,7 +2104,7 @@ struct CxxrtlWorker { FlowGraph::Node *node = flow.add_node(cell); // Various DFF cells are treated like posedge/negedge processes, see above for details. - if (cell->type.in(ID($dff), ID($dffe), ID($adff), ID($dffsr))) { + if (cell->type.in(ID($dff), ID($dffe), ID($adff), ID($adffe), ID($dffsr), ID($dffsre), ID($sdff), ID($sdffe), ID($sdffce))) { if (cell->getPort(ID::CLK).is_wire()) register_edge_signal(sigmap, cell->getPort(ID::CLK), cell->parameters[ID::CLK_POLARITY].as_bool() ? RTLIL::STp : RTLIL::STn); @@ -2096,6 +2184,8 @@ struct CxxrtlWorker { if (wire->name.begins_with("$") && !elide_internal) continue; if (wire->name.begins_with("\\") && !elide_public) continue; if (edge_wires[wire]) continue; + if (flow.wire_comb_defs[wire].size() > 1) + log_cmd_error("Wire %s.%s has multiple drivers.\n", log_id(module), log_id(wire)); log_assert(flow.wire_comb_defs[wire].size() == 1); elided_wires[wire] = **flow.wire_comb_defs[wire].begin(); } @@ -2145,17 +2235,20 @@ struct CxxrtlWorker { log("Module `%s' contains feedback arcs through wires:\n", log_id(module)); for (auto wire : feedback_wires) log(" %s\n", log_id(wire)); - log("\n"); } for (auto wire : module->wires()) { if (feedback_wires[wire]) continue; - if (wire->port_id != 0) continue; + if (wire->port_output && !module->get_bool_attribute(ID::top)) continue; + if (wire->name.begins_with("$") && !unbuffer_internal) continue; + if (wire->name.begins_with("\\") && !unbuffer_public) continue; + if (flow.wire_sync_defs.count(wire) > 0) continue; + unbuffered_wires.insert(wire); + if (edge_wires[wire]) continue; if (wire->get_bool_attribute(ID::keep)) continue; + if (wire->port_input || wire->port_output) continue; if (wire->name.begins_with("$") && !localize_internal) continue; if (wire->name.begins_with("\\") && !localize_public) continue; - if (edge_wires[wire]) continue; - if (flow.wire_sync_defs.count(wire) > 0) continue; localized_wires.insert(wire); } @@ -2165,22 +2258,22 @@ struct CxxrtlWorker { // it is possible that a design with no feedback arcs would end up with doubly buffered wires in such cases // as a wire with multiple drivers where one of them is combinatorial and the other is synchronous. Such designs // also require more than one delta cycle to converge. - pool<const RTLIL::Wire*> buffered_wires; + pool<const RTLIL::Wire*> buffered_comb_wires; for (auto wire : module->wires()) { - if (flow.wire_comb_defs[wire].size() > 0 && !elided_wires.count(wire) && !localized_wires[wire]) { - if (!feedback_wires[wire]) - buffered_wires.insert(wire); - } + if (flow.wire_comb_defs[wire].size() > 0 && !unbuffered_wires[wire] && !feedback_wires[wire]) + buffered_comb_wires.insert(wire); } - if (!buffered_wires.empty()) { - has_buffered_wires = true; + if (!buffered_comb_wires.empty()) { + has_buffered_comb_wires = true; log("Module `%s' contains buffered combinatorial wires:\n", log_id(module)); - for (auto wire : buffered_wires) + for (auto wire : buffered_comb_wires) log(" %s\n", log_id(wire)); - log("\n"); } - eval_converges[module] = feedback_wires.empty() && buffered_wires.empty(); + eval_converges[module] = feedback_wires.empty() && buffered_comb_wires.empty(); + + for (auto item : flow.bit_has_state) + bit_has_state.insert(item); if (debug_info) { // Find wires that alias other wires or are tied to a constant; debug information can be enriched with these @@ -2191,7 +2284,7 @@ struct CxxrtlWorker { for (auto wire : module->wires()) { if (wire->name[0] != '\\') continue; - if (!localized_wires[wire]) + if (!unbuffered_wires[wire]) continue; const RTLIL::Wire *wire_it = wire; while (1) { @@ -2204,7 +2297,7 @@ struct CxxrtlWorker { RTLIL::SigSpec rhs_sig = node->connect.second; if (rhs_sig.is_wire()) { RTLIL::Wire *rhs_wire = rhs_sig.as_wire(); - if (localized_wires[rhs_wire]) { + if (unbuffered_wires[rhs_wire]) { wire_it = rhs_wire; // maybe an alias } else { debug_alias_wires[wire] = rhs_wire; // is an alias @@ -2220,24 +2313,26 @@ struct CxxrtlWorker { } } } - if (has_feedback_arcs || has_buffered_wires) { + if (has_feedback_arcs || has_buffered_comb_wires) { // Although both non-feedback buffered combinatorial wires and apparent feedback wires may be eliminated // by optimizing the design, if after `proc; flatten` there are any feedback wires remaining, it is very // likely that these feedback wires are indicative of a true logic loop, so they get emphasized in the message. const char *why_pessimistic = nullptr; if (has_feedback_arcs) why_pessimistic = "feedback wires"; - else if (has_buffered_wires) + else if (has_buffered_comb_wires) why_pessimistic = "buffered combinatorial wires"; log_warning("Design contains %s, which require delta cycles during evaluation.\n", why_pessimistic); - if (!max_opt_level) - log("Increasing the optimization level may eliminate %s from the design.\n", why_pessimistic); + if (!run_flatten) + log("Flattening may eliminate %s from the design.\n", why_pessimistic); + if (!run_proc) + log("Converting processes to netlists may eliminate %s from the design.\n", why_pessimistic); } } - void check_design(RTLIL::Design *design, bool &has_sync_init, bool &has_packed_mem) + void check_design(RTLIL::Design *design, bool &has_top, bool &has_sync_init, bool &has_packed_mem) { - has_sync_init = has_packed_mem = false; + has_sync_init = has_packed_mem = has_top = false; for (auto module : design->modules()) { if (module->get_blackbox_attribute() && !module->has_attribute(ID(cxxrtl_blackbox))) @@ -2249,13 +2344,17 @@ struct CxxrtlWorker { if (!design->selected_module(module)) continue; + if (module->get_bool_attribute(ID::top)) + has_top = true; + for (auto proc : module->processes) for (auto sync : proc.second->syncs) if (sync->type == RTLIL::STi) has_sync_init = true; - for (auto cell : module->cells()) - if (cell->type == ID($mem)) + // The Mem constructor also checks for well-formedness of $meminit cells, if any. + for (auto &mem : Mem::get_all_memories(module)) + if (mem.packed) has_packed_mem = true; } } @@ -2263,13 +2362,20 @@ struct CxxrtlWorker { void prepare_design(RTLIL::Design *design) { bool did_anything = false; - bool has_sync_init, has_packed_mem; + bool has_top, has_sync_init, has_packed_mem; log_push(); - check_design(design, has_sync_init, has_packed_mem); - if (run_proc_flatten) { - Pass::call(design, "proc"); + check_design(design, has_top, has_sync_init, has_packed_mem); + if (run_hierarchy && !has_top) { + Pass::call(design, "hierarchy -auto-top"); + did_anything = true; + } + if (run_flatten) { Pass::call(design, "flatten"); did_anything = true; + } + if (run_proc) { + Pass::call(design, "proc"); + did_anything = true; } else if (has_sync_init) { // We're only interested in proc_init, but it depends on proc_prune and proc_clean, so call those // in case they weren't already. (This allows `yosys foo.v -o foo.cc` to work.) @@ -2283,9 +2389,9 @@ struct CxxrtlWorker { did_anything = true; } // Recheck the design if it was modified. - if (has_sync_init || has_packed_mem) - check_design(design, has_sync_init, has_packed_mem); - log_assert(!(has_sync_init || has_packed_mem)); + if (did_anything) + check_design(design, has_top, has_sync_init, has_packed_mem); + log_assert(has_top && !has_sync_init && !has_packed_mem); log_pop(); if (did_anything) log_spacer(); @@ -2294,11 +2400,12 @@ struct CxxrtlWorker { }; struct CxxrtlBackend : public Backend { - static const int DEFAULT_OPT_LEVEL = 5; + static const int DEFAULT_OPT_LEVEL = 6; + static const int OPT_LEVEL_DEBUG = 4; static const int DEFAULT_DEBUG_LEVEL = 1; CxxrtlBackend() : Backend("cxxrtl", "convert design to C++ RTL simulation") { } - void help() YS_OVERRIDE + void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); @@ -2317,9 +2424,9 @@ struct CxxrtlBackend : public Backend { log(" top.step();\n"); log(" while (1) {\n"); log(" /* user logic */\n"); - log(" top.p_clk = value<1> {0u};\n"); + log(" top.p_clk.set(false);\n"); log(" top.step();\n"); - log(" top.p_clk = value<1> {1u};\n"); + log(" top.p_clk.set(true);\n"); log(" top.step();\n"); log(" }\n"); log(" }\n"); @@ -2466,6 +2573,22 @@ struct CxxrtlBackend : public Backend { log(" place the generated code into namespace <ns-name>. if not specified,\n"); log(" \"cxxrtl_design\" is used.\n"); log("\n"); + log(" -nohierarchy\n"); + log(" use design hierarchy as-is. in most designs, a top module should be\n"); + log(" present as it is exposed through the C API and has unbuffered outputs\n"); + log(" for improved performance; it will be determined automatically if absent.\n"); + log("\n"); + log(" -noflatten\n"); + log(" don't flatten the design. fully flattened designs can evaluate within\n"); + log(" one delta cycle if they have no combinatorial feedback.\n"); + log(" note that the debug interface and waveform dumps use full hierarchical\n"); + log(" names for all wires even in flattened designs.\n"); + log("\n"); + log(" -noproc\n"); + log(" don't convert processes to netlists. in most designs, converting\n"); + log(" processes significantly improves evaluation performance at the cost of\n"); + log(" slight increase in compilation time.\n"); + log("\n"); log(" -O <level>\n"); log(" set the optimization level. the default is -O%d. higher optimization\n", DEFAULT_OPT_LEVEL); log(" levels dramatically decrease compile and run time, and highest level\n"); @@ -2475,19 +2598,26 @@ struct CxxrtlBackend : public Backend { log(" no optimization.\n"); log("\n"); log(" -O1\n"); - log(" elide internal wires if possible.\n"); + log(" localize internal wires if possible.\n"); log("\n"); log(" -O2\n"); - log(" like -O1, and localize internal wires if possible.\n"); + log(" like -O1, and unbuffer internal wires if possible.\n"); log("\n"); log(" -O3\n"); - log(" like -O2, and elide public wires not marked (*keep*) if possible.\n"); + log(" like -O2, and elide internal wires if possible.\n"); log("\n"); log(" -O4\n"); - log(" like -O3, and localize public wires not marked (*keep*) if possible.\n"); + log(" like -O3, and unbuffer public wires not marked (*keep*) if possible.\n"); log("\n"); log(" -O5\n"); - log(" like -O4, and run `proc; flatten` first.\n"); + log(" like -O4, and localize public wires not marked (*keep*) if possible.\n"); + log("\n"); + log(" -O6\n"); + log(" like -O5, and elide public wires not marked (*keep*) if possible.\n"); + log("\n"); + log(" -Og\n"); + log(" highest optimization level that provides debug information for all\n"); + log(" public wires. currently, alias for -O%d.\n", OPT_LEVEL_DEBUG); log("\n"); log(" -g <level>\n"); log(" set the debug level. the default is -g%d. higher debug levels provide\n", DEFAULT_DEBUG_LEVEL); @@ -2502,8 +2632,11 @@ struct CxxrtlBackend : public Backend { log("\n"); } - void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE + void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override { + bool nohierarchy = false; + bool noflatten = false; + bool noproc = false; int opt_level = DEFAULT_OPT_LEVEL; int debug_level = DEFAULT_DEBUG_LEVEL; CxxrtlWorker worker; @@ -2513,6 +2646,27 @@ struct CxxrtlBackend : public Backend { size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) { + if (args[argidx] == "-nohierarchy") { + nohierarchy = true; + continue; + } + if (args[argidx] == "-noflatten") { + noflatten = true; + continue; + } + if (args[argidx] == "-noproc") { + noproc = true; + continue; + } + if (args[argidx] == "-Og") { + opt_level = OPT_LEVEL_DEBUG; + continue; + } + if (args[argidx] == "-O" && argidx+1 < args.size() && args[argidx+1] == "g") { + argidx++; + opt_level = OPT_LEVEL_DEBUG; + continue; + } if (args[argidx] == "-O" && argidx+1 < args.size()) { opt_level = std::stoi(args[++argidx]); continue; @@ -2541,30 +2695,34 @@ struct CxxrtlBackend : public Backend { } extra_args(f, filename, args, argidx); + worker.run_hierarchy = !nohierarchy; + worker.run_flatten = !noflatten; + worker.run_proc = !noproc; switch (opt_level) { // the highest level here must match DEFAULT_OPT_LEVEL + case 6: + worker.elide_public = true; + YS_FALLTHROUGH case 5: - worker.max_opt_level = true; - worker.run_proc_flatten = true; + worker.localize_public = true; YS_FALLTHROUGH case 4: - worker.localize_public = true; + worker.unbuffer_public = true; YS_FALLTHROUGH case 3: - worker.elide_public = true; + worker.elide_internal = true; YS_FALLTHROUGH case 2: worker.localize_internal = true; YS_FALLTHROUGH case 1: - worker.elide_internal = true; + worker.unbuffer_internal = true; YS_FALLTHROUGH case 0: break; default: log_cmd_error("Invalid optimization level %d.\n", opt_level); } - switch (debug_level) { // the highest level here must match DEFAULT_DEBUG_LEVEL case 1: diff --git a/backends/cxxrtl/cxxrtl_capi.cc b/backends/cxxrtl/cxxrtl_capi.cc index 489d72da5..b77e4c491 100644 --- a/backends/cxxrtl/cxxrtl_capi.cc +++ b/backends/cxxrtl/cxxrtl_capi.cc @@ -43,18 +43,29 @@ void cxxrtl_destroy(cxxrtl_handle handle) { delete handle; } +int cxxrtl_eval(cxxrtl_handle handle) { + return handle->module->eval(); +} + +int cxxrtl_commit(cxxrtl_handle handle) { + return handle->module->commit(); +} + size_t cxxrtl_step(cxxrtl_handle handle) { return handle->module->step(); } -cxxrtl_object *cxxrtl_get(cxxrtl_handle handle, const char *name) { - if (handle->objects.count(name) > 0) - return static_cast<cxxrtl_object*>(&handle->objects.at(name)); - return nullptr; +struct cxxrtl_object *cxxrtl_get_parts(cxxrtl_handle handle, const char *name, size_t *parts) { + auto it = handle->objects.table.find(name); + if (it == handle->objects.table.end()) + return nullptr; + *parts = it->second.size(); + return static_cast<cxxrtl_object*>(&it->second[0]); } void cxxrtl_enum(cxxrtl_handle handle, void *data, - void (*callback)(void *data, const char *name, cxxrtl_object *object)) { - for (auto &it : handle->objects) - callback(data, it.first.c_str(), static_cast<cxxrtl_object*>(&it.second)); + void (*callback)(void *data, const char *name, + cxxrtl_object *object, size_t parts)) { + for (auto &it : handle->objects.table) + callback(data, it.first.c_str(), static_cast<cxxrtl_object*>(&it.second[0]), it.second.size()); } diff --git a/backends/cxxrtl/cxxrtl_capi.h b/backends/cxxrtl/cxxrtl_capi.h index 46aa662b2..385d6dcf3 100644 --- a/backends/cxxrtl/cxxrtl_capi.h +++ b/backends/cxxrtl/cxxrtl_capi.h @@ -26,6 +26,7 @@ #include <stddef.h> #include <stdint.h> +#include <assert.h> #ifdef __cplusplus extern "C" { @@ -54,12 +55,28 @@ cxxrtl_handle cxxrtl_create(cxxrtl_toplevel design); // Release all resources used by a design and its handle. void cxxrtl_destroy(cxxrtl_handle handle); +// Evaluate the design, propagating changes on inputs to the `next` value of internal state and +// output wires. +// +// Returns 1 if the design is known to immediately converge, 0 otherwise. +int cxxrtl_eval(cxxrtl_handle handle); + +// Commit the design, replacing the `curr` value of internal state and output wires with the `next` +// value. +// +// Return 1 if any of the `curr` values were updated, 0 otherwise. +int cxxrtl_commit(cxxrtl_handle handle); + // Simulate the design to a fixed point. // // Returns the number of delta cycles. size_t cxxrtl_step(cxxrtl_handle handle); // Type of a simulated object. +// +// The type of a simulated object indicates the way it is stored and the operations that are legal +// to perform on it (i.e. won't crash the simulation). It says very little about object semantics, +// which is specified through flags. enum cxxrtl_type { // Values correspond to singly buffered netlist nodes, i.e. nodes driven exclusively by // combinatorial cells, or toplevel input nodes. @@ -73,7 +90,8 @@ enum cxxrtl_type { CXXRTL_VALUE = 0, // Wires correspond to doubly buffered netlist nodes, i.e. nodes driven, at least in part, by - // storage cells, or by combinatorial cells that are a part of a feedback path. + // storage cells, or by combinatorial cells that are a part of a feedback path. They are also + // present in non-optimized builds. // // Wires can be inspected via the `curr` pointer and modified via the `next` pointer (which are // distinct for wires). Note that changes to the bits driven by combinatorial cells will be @@ -89,7 +107,74 @@ enum cxxrtl_type { // always NULL. CXXRTL_MEMORY = 2, - // More object types will be added in the future, but the existing ones will never change. + // Aliases correspond to netlist nodes driven by another node such that their value is always + // exactly equal. + // + // Aliases can be inspected via the `curr` pointer. They cannot be modified, and the `next` + // pointer is always NULL. + CXXRTL_ALIAS = 3, + + // More object types may be added in the future, but the existing ones will never change. +}; + +// Flags of a simulated object. +// +// The flags of a simulated object indicate its role in the netlist: +// * The flags `CXXRTL_INPUT` and `CXXRTL_OUTPUT` designate module ports. +// * The flags `CXXRTL_DRIVEN_SYNC`, `CXXRTL_DRIVEN_COMB`, and `CXXRTL_UNDRIVEN` specify +// the semantics of node state. An object with several of these flags set has different bits +// follow different semantics. +enum cxxrtl_flag { + // Node is a module input port. + // + // This flag can be set on objects of type `CXXRTL_VALUE` and `CXXRTL_WIRE`. It may be combined + // with `CXXRTL_OUTPUT`, as well as other flags. + CXXRTL_INPUT = 1 << 0, + + // Node is a module output port. + // + // This flag can be set on objects of type `CXXRTL_WIRE`. It may be combined with `CXXRTL_INPUT`, + // as well as other flags. + CXXRTL_OUTPUT = 1 << 1, + + // Node is a module inout port. + // + // This flag can be set on objects of type `CXXRTL_WIRE`. It may be combined with other flags. + CXXRTL_INOUT = (CXXRTL_INPUT|CXXRTL_OUTPUT), + + // Node has bits that are driven by a storage cell. + // + // This flag can be set on objects of type `CXXRTL_WIRE`. It may be combined with + // `CXXRTL_DRIVEN_COMB` and `CXXRTL_UNDRIVEN`, as well as other flags. + // + // This flag is set on wires that have bits connected directly to the output of a flip-flop or + // a latch, and hold its state. Many `CXXRTL_WIRE` objects may not have the `CXXRTL_DRIVEN_SYNC` + // flag set; for example, output ports and feedback wires generally won't. Writing to the `next` + // pointer of these wires updates stored state, and for designs without combinatorial loops, + // capturing the value from every of these wires through the `curr` pointer creates a complete + // snapshot of the design state. + CXXRTL_DRIVEN_SYNC = 1 << 2, + + // Node has bits that are driven by a combinatorial cell or another node. + // + // This flag can be set on objects of type `CXXRTL_VALUE` and `CXXRTL_WIRE`. It may be combined + // with `CXXRTL_DRIVEN_SYNC` and `CXXRTL_UNDRIVEN`, as well as other flags. + // + // This flag is set on objects that have bits connected to the output of a combinatorial cell, + // or directly to another node. For designs without combinatorial loops, writing to such bits + // through the `next` pointer (if it is not NULL) has no effect. + CXXRTL_DRIVEN_COMB = 1 << 3, + + // Node has bits that are not driven. + // + // This flag can be set on objects of type `CXXRTL_VALUE` and `CXXRTL_WIRE`. It may be combined + // with `CXXRTL_DRIVEN_SYNC` and `CXXRTL_DRIVEN_COMB`, as well as other flags. + // + // This flag is set on objects that have bits not driven by an output of any cell or by another + // node, such as inputs and dangling wires. + CXXRTL_UNDRIVEN = 1 << 4, + + // More object flags may be added in the future, but the existing ones will never change. }; // Description of a simulated object. @@ -103,12 +188,21 @@ struct cxxrtl_object { // determines all other properties of the object. uint32_t type; // actually `enum cxxrtl_type` + // Flags of the object. + uint32_t flags; // actually bit mask of `enum cxxrtl_flags` + // Width of the object in bits. size_t width; + // Index of the least significant bit. + size_t lsb_at; + // Depth of the object. Only meaningful for memories; for other objects, always 1. size_t depth; + // Index of the first word. Only meaningful for memories; for other objects, always 0; + size_t zero_at; + // Bits stored in the object, as 32-bit chunks, least significant bits first. // // The width is rounded up to a multiple of 32; the padding bits are always set to 0 by @@ -123,7 +217,7 @@ struct cxxrtl_object { uint32_t *curr; uint32_t *next; - // More description fields will be added in the future, but the existing ones will never change. + // More description fields may be added in the future, but the existing ones will never change. }; // Retrieve description of a simulated object. @@ -133,17 +227,36 @@ struct cxxrtl_object { // the top-level module instantiates a module `foo`, which in turn contains a wire `bar`, the full // hierarchical name is `\foo \bar`. // -// Returns the object if it was found, NULL otherwise. The returned value is valid until the design -// is destroyed. -struct cxxrtl_object *cxxrtl_get(cxxrtl_handle handle, const char *name); +// The storage of a single abstract object may be split (usually with the `splitnets` pass) into +// many physical parts, all of which correspond to the same hierarchical name. To handle such cases, +// this function returns an array and writes its length to `parts`. The array is sorted by `lsb_at`. +// +// Returns the object parts if it was found, NULL otherwise. The returned parts are valid until +// the design is destroyed. +struct cxxrtl_object *cxxrtl_get_parts(cxxrtl_handle handle, const char *name, size_t *parts); + +// Retrieve description of a single part simulated object. +// +// This function is a shortcut for the most common use of `cxxrtl_get_parts`. It asserts that, +// if the object exists, it consists of a single part. If assertions are disabled, it returns NULL +// for multi-part objects. +inline struct cxxrtl_object *cxxrtl_get(cxxrtl_handle handle, const char *name) { + size_t parts = 0; + struct cxxrtl_object *object = cxxrtl_get_parts(handle, name, &parts); + assert(object == NULL || parts == 1); + if (object == NULL || parts == 1) + return object; + return NULL; +} // Enumerate simulated objects. // // For every object in the simulation, `callback` is called with the provided `data`, the full -// hierarchical name of the object (see `cxxrtl_get` for details), and the object description. +// hierarchical name of the object (see `cxxrtl_get` for details), and the object parts. // The provided `name` and `object` values are valid until the design is destroyed. void cxxrtl_enum(cxxrtl_handle handle, void *data, - void (*callback)(void *data, const char *name, struct cxxrtl_object *object)); + void (*callback)(void *data, const char *name, + struct cxxrtl_object *object, size_t parts)); #ifdef __cplusplus } diff --git a/backends/cxxrtl/cxxrtl_vcd.h b/backends/cxxrtl/cxxrtl_vcd.h index f6b78bbf7..dbeabbaf2 100644 --- a/backends/cxxrtl/cxxrtl_vcd.h +++ b/backends/cxxrtl/cxxrtl_vcd.h @@ -66,11 +66,19 @@ class vcd_writer { } while (ident != 0); } - void emit_var(const variable &var, const std::string &type, const std::string &name) { + void emit_var(const variable &var, const std::string &type, const std::string &name, + size_t lsb_at, bool multipart) { assert(!streaming); buffer += "$var " + type + " " + std::to_string(var.width) + " "; emit_ident(var.ident); - buffer += " " + name + " $end\n"; + buffer += " " + name; + if (multipart || name.back() == ']' || lsb_at != 0) { + if (var.width == 1) + buffer += " [" + std::to_string(lsb_at) + "]"; + else + buffer += " [" + std::to_string(lsb_at + var.width - 1) + ":" + std::to_string(lsb_at) + "]"; + } + buffer += " $end\n"; } void emit_enddefinitions() { @@ -104,13 +112,13 @@ class vcd_writer { buffer += '\n'; } - const variable ®ister_variable(size_t width, chunk_t *curr, bool immutable = false) { + const variable ®ister_variable(size_t width, chunk_t *curr, bool constant = false) { if (aliases.count(curr)) { return variables[aliases[curr]]; } else { const size_t chunks = (width + (sizeof(chunk_t) * 8 - 1)) / (sizeof(chunk_t) * 8); aliases[curr] = variables.size(); - if (immutable) { + if (constant) { variables.emplace_back(variable { variables.size(), width, curr, (size_t)-1 }); } else { variables.emplace_back(variable { variables.size(), width, curr, cache.size() }); @@ -122,7 +130,7 @@ class vcd_writer { bool test_variable(const variable &var) { if (var.prev_off == (size_t)-1) - return false; // immutable + return false; // constant const size_t chunks = (var.width + (sizeof(chunk_t) * 8 - 1)) / (sizeof(chunk_t) * 8); if (std::equal(&var.curr[0], &var.curr[chunks], &cache[var.prev_off])) { return false; @@ -155,7 +163,7 @@ public: emit_timescale(number, unit); } - void add(const std::string &hier_name, const debug_item &item) { + void add(const std::string &hier_name, const debug_item &item, bool multipart = false) { std::vector<std::string> scope = split_hierarchy(hier_name); std::string name = scope.back(); scope.pop_back(); @@ -164,20 +172,31 @@ public: switch (item.type) { // Not the best naming but oh well... case debug_item::VALUE: - emit_var(register_variable(item.width, item.curr, /*immutable=*/item.next == nullptr), "wire", name); + emit_var(register_variable(item.width, item.curr, /*constant=*/item.next == nullptr), + "wire", name, item.lsb_at, multipart); break; case debug_item::WIRE: - emit_var(register_variable(item.width, item.curr), "reg", name); + emit_var(register_variable(item.width, item.curr), + "reg", name, item.lsb_at, multipart); break; case debug_item::MEMORY: { const size_t stride = (item.width + (sizeof(chunk_t) * 8 - 1)) / (sizeof(chunk_t) * 8); for (size_t index = 0; index < item.depth; index++) { chunk_t *nth_curr = &item.curr[stride * index]; std::string nth_name = name + '[' + std::to_string(index) + ']'; - emit_var(register_variable(item.width, nth_curr), "reg", nth_name); + emit_var(register_variable(item.width, nth_curr), + "reg", nth_name, item.lsb_at, multipart); } break; } + case debug_item::ALIAS: + // Like VALUE, but, even though `item.next == nullptr` always holds, the underlying value + // can actually change, and must be tracked. In most cases the VCD identifier will be + // unified with the aliased reg, but we should handle the case where only the alias is + // added to the VCD writer, too. + emit_var(register_variable(item.width, item.curr), + "wire", name, item.lsb_at, multipart); + break; } } @@ -185,9 +204,10 @@ public: void add(const debug_items &items, const Filter &filter) { // `debug_items` is a map, so the items are already sorted in an order optimal for emitting // VCD scope sections. - for (auto &it : items) - if (filter(it.first, it.second)) - add(it.first, it.second); + for (auto &it : items.table) + for (auto &part : it.second) + if (filter(it.first, part)) + add(it.first, part, it.second.size() > 1); } void add(const debug_items &items) { @@ -198,7 +218,7 @@ public: void add_without_memories(const debug_items &items) { this->template add(items, [](const std::string &, const debug_item &item) { - return item.type == debug_item::VALUE || item.type == debug_item::WIRE; + return item.type != debug_item::MEMORY; }); } diff --git a/backends/cxxrtl/cxxrtl_vcd_capi.h b/backends/cxxrtl/cxxrtl_vcd_capi.h index 6a7fb9f47..d55afe223 100644 --- a/backends/cxxrtl/cxxrtl_vcd_capi.h +++ b/backends/cxxrtl/cxxrtl_vcd_capi.h @@ -75,8 +75,8 @@ void cxxrtl_vcd_add_from(cxxrtl_vcd vcd, cxxrtl_handle handle); // // Objects can only be scheduled before the first call to `cxxrtl_vcd_sample`. void cxxrtl_vcd_add_from_if(cxxrtl_vcd vcd, cxxrtl_handle handle, void *data, - int (*filter)(void *data, const char *name, - const struct cxxrtl_object *object)); + int (*filter)(void *data, const char *name, + const struct cxxrtl_object *object)); // Schedule all CXXRTL objects in a simulation except for memories. // diff --git a/backends/edif/edif.cc b/backends/edif/edif.cc index 7e24468c0..e0013238c 100644 --- a/backends/edif/edif.cc +++ b/backends/edif/edif.cc @@ -90,7 +90,7 @@ struct EdifNames struct EdifBackend : public Backend { EdifBackend() : Backend("edif", "write design to EDIF netlist file") { } - void help() YS_OVERRIDE + void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); @@ -126,7 +126,7 @@ struct EdifBackend : public Backend { log("is targeted.\n"); log("\n"); } - void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE + void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override { log_header(design, "Executing EDIF backend.\n"); std::string top_module_name; @@ -330,7 +330,7 @@ struct EdifBackend : public Backend { } *f << stringf("\n (property %s (string \"%d'h%s\"))", EDIF_DEF(name), GetSize(val.bits), hex_string.c_str()); } - }; + }; for (auto module : sorted_modules) { if (module->get_blackbox_attribute()) @@ -373,8 +373,8 @@ struct EdifBackend : public Backend { } { - int c1 = w1->name[0] == '\\'; - int c2 = w2->name[0] == '\\'; + int c1 = w1->name.isPublic(); + int c2 = w2->name.isPublic(); if (c1 > c2) goto promote; if (c1 < c2) goto nopromote; @@ -524,7 +524,7 @@ struct EdifBackend : public Backend { *f << stringf(" (portRef %c (instanceRef GND))\n", gndvccy ? 'Y' : 'G'); if (sig == RTLIL::State::S1) *f << stringf(" (portRef %c (instanceRef VCC))\n", gndvccy ? 'Y' : 'P'); - } + } *f << stringf(" )"); if (attr_properties && sig.wire != NULL) for (auto &p : sig.wire->attributes) diff --git a/backends/firrtl/firrtl.cc b/backends/firrtl/firrtl.cc index b1d8500bb..44c3397da 100644 --- a/backends/firrtl/firrtl.cc +++ b/backends/firrtl/firrtl.cc @@ -102,6 +102,260 @@ const char *make_id(IdString id) return namecache.at(id).c_str(); } +std::string dump_const_string(const RTLIL::Const &data) +{ + std::string res_str; + + std::string str = data.decode_string(); + for (size_t i = 0; i < str.size(); i++) + { + if (str[i] == '\n') + res_str += "\\n"; + else if (str[i] == '\t') + res_str += "\\t"; + else if (str[i] < 32) + res_str += stringf("\\%03o", str[i]); + else if (str[i] == '"') + res_str += "\\\""; + else if (str[i] == '\\') + res_str += "\\\\"; + else + res_str += str[i]; + } + + return res_str; +} + +std::string dump_const(const RTLIL::Const &data) +{ + std::string res_str; + + // // For debugging purposes to find out how Yosys encodes flags. + // res_str += stringf("flags_%x --> ", data.flags); + + // Real-valued parameter. + if (data.flags & RTLIL::CONST_FLAG_REAL) + { + // Yosys stores real values as strings, so we call the string dumping code. + res_str += dump_const_string(data); + } + // String parameter. + else if (data.flags & RTLIL::CONST_FLAG_STRING) + { + res_str += "\""; + res_str += dump_const_string(data); + res_str += "\""; + } + // Numeric (non-real) parameter. + else + { + int width = data.bits.size(); + + // If a standard 32-bit int, then emit standard int value like "56" or + // "-56". Firrtl supports negative-valued int literals. + // + // SignedInt + // : ( '+' | '-' ) PosInt + // ; + if (width <= 32) + { + int32_t int_val = 0; + + for (int i = 0; i < width; i++) + { + switch (data.bits[i]) + { + case State::S0: break; + case State::S1: int_val |= (1 << i); break; + default: + log_error("Unexpected int value\n"); + break; + } + } + + res_str += stringf("%d", int_val); + } + else + { + // If value is larger than 32 bits, then emit a binary representation of + // the number as integers are not large enough to contain the result. + // There is a caveat to this approach though: + // + // Note that parameter may be defined as having a fixed width as follows: + // + // parameter signed [26:0] test_signed; + // parameter [26:0] test_unsigned; + // parameter signed [40:0] test_signed_large; + // + // However, if you assign a value on the RHS without specifying the + // precision, then yosys considers the value you used as an int and + // assigns it a width of 32 bits regardless of the type of the parameter. + // + // defparam <inst_name> .test_signed = 49; (width = 32, though should be 27 based on definition) + // defparam <inst_name> .test_unsigned = 40'd35; (width = 40, though should be 27 based on definition) + // defparam <inst_name> .test_signed_large = 40'd12; (width = 40) + // + // We therefore may lose the precision of the original verilog literal if + // it was written without its bitwidth specifier. + + // Emit binary prefix for string. + res_str += "\"b"; + + // Emit bits. + for (int i = width - 1; i >= 0; i--) + { + log_assert(i < width); + switch (data.bits[i]) + { + case State::S0: res_str += "0"; break; + case State::S1: res_str += "1"; break; + case State::Sx: res_str += "x"; break; + case State::Sz: res_str += "z"; break; + case State::Sa: res_str += "-"; break; + case State::Sm: res_str += "m"; break; + } + } + + res_str += "\""; + } + } + + return res_str; +} + +std::string extmodule_name(RTLIL::Cell *cell, RTLIL::Module *mod_instance) +{ + // Since we are creating a custom extmodule for every cell that instantiates + // this blackbox, we need to create a custom name for it. We just use the + // name of the blackbox itself followed by the name of the cell. + const std::string cell_name = std::string(make_id(cell->name)); + const std::string blackbox_name = std::string(make_id(mod_instance->name)); + const std::string extmodule_name = blackbox_name + "_" + cell_name; + return extmodule_name; +} + +/** + * Emits a parameterized extmodule. Instance parameters are obtained from + * ''cell'' as it represents the instantiation of the blackbox defined by + * ''mod_instance'' and therefore contains all its instance parameters. + */ +void emit_extmodule(RTLIL::Cell *cell, RTLIL::Module *mod_instance, std::ostream &f) +{ + const std::string indent = " "; + + const std::string blackbox_name = std::string(make_id(mod_instance->name)); + const std::string exported_name = extmodule_name(cell, mod_instance); + + // We use the cell's fileinfo for this extmodule as its parameters come from + // the cell and not from the module itself (the module contains default + // parameters, not the instance-specific ones we're using to emit the + // extmodule). + const std::string extmoduleFileinfo = getFileinfo(cell); + + // Emit extmodule header. + f << stringf(" extmodule %s: %s\n", exported_name.c_str(), extmoduleFileinfo.c_str()); + + // Emit extmodule ports. + for (auto wire : mod_instance->wires()) + { + const auto wireName = make_id(wire->name); + const std::string wireFileinfo = getFileinfo(wire); + + if (wire->port_input && wire->port_output) + { + log_error("Module port %s.%s is inout!\n", log_id(mod_instance), log_id(wire)); + } + + const std::string portDecl = stringf("%s%s %s: UInt<%d> %s\n", + indent.c_str(), + wire->port_input ? "input" : "output", + wireName, + wire->width, + wireFileinfo.c_str() + ); + + f << portDecl; + } + + // Emit extmodule "defname" field. This is the name of the verilog blackbox + // that is used when verilog is emitted, so we use the name of mod_instance + // here. + f << stringf("%sdefname = %s\n", indent.c_str(), blackbox_name.c_str()); + + // Emit extmodule generic parameters. + for (const auto &p : cell->parameters) + { + const RTLIL::IdString p_id = p.first; + const RTLIL::Const p_value = p.second; + + std::string param_name(p_id.c_str()); + const std::string param_value = dump_const(p_value); + + // Remove backslashes from parameters as these come from the internal RTLIL + // naming scheme, but should not exist in the emitted firrtl blackboxes. + // When firrtl is converted to verilog and given to downstream synthesis + // tools, these tools expect to find blackbox names and parameters as they + // were originally defined, i.e. without the extra RTLIL naming conventions. + param_name.erase( + std::remove(param_name.begin(), param_name.end(), '\\'), + param_name.end() + ); + + f << stringf("%sparameter %s = %s\n", indent.c_str(), param_name.c_str(), param_value.c_str()); + } + + f << "\n"; +} + +/** + * Emits extmodules for every instantiated blackbox in the design. + * + * RTLIL stores instance parameters at the cell's instantiation location. + * However, firrtl does not support module parameterization (everything is + * already elaborated). Firrtl instead supports external modules (extmodule), + * i.e. blackboxes that are defined by verilog and which have no body in + * firrtl itself other than the declaration of the blackboxes ports and + * parameters. + * + * Furthermore, firrtl does not support parameterization (even of extmodules) + * at a module's instantiation location and users must instead declare + * different extmodules with different instance parameters in the extmodule + * definition itself. + * + * This function goes through the design to identify all RTLIL blackboxes + * and emit parameterized extmodules with a unique name for each of them. The + * name that's given to the extmodule is + * + * <blackbox_name>_<instance_name> + * + * Beware that it is therefore necessary for users to replace "parameterized" + * instances in the RTLIL sense with these custom extmodules for the firrtl to + * be valid. + */ +void emit_elaborated_extmodules(RTLIL::Design *design, std::ostream &f) +{ + for (auto module : design->modules()) + { + for (auto cell : module->cells()) + { + // Is this cell a module instance? + bool cellIsModuleInstance = cell->type[0] != '$'; + + if (cellIsModuleInstance) + { + // Find the module corresponding to this instance. + auto modInstance = design->module(cell->type); + bool modIsBlackbox = modInstance->get_blackbox_attribute(); + + if (modIsBlackbox) + { + emit_extmodule(cell, modInstance, f); + } + } + } + } +} + struct FirrtlWorker { Module *module; @@ -328,8 +582,16 @@ struct FirrtlWorker log_warning("No instance for %s.%s\n", cell_type.c_str(), cell_name.c_str()); return; } + + // If the instance is that of a blackbox, use the modified extmodule name + // that contains per-instance parameterizations. These instances were + // emitted earlier in the firrtl backend. + const std::string instanceName = instModule->get_blackbox_attribute() ? + extmodule_name(cell, instModule) : + instanceOf; + std::string cellFileinfo = getFileinfo(cell); - wire_exprs.push_back(stringf("%s" "inst %s%s of %s %s", indent.c_str(), cell_name.c_str(), cell_name_comment.c_str(), instanceOf.c_str(), cellFileinfo.c_str())); + wire_exprs.push_back(stringf("%s" "inst %s%s of %s %s", indent.c_str(), cell_name.c_str(), cell_name_comment.c_str(), instanceName.c_str(), cellFileinfo.c_str())); for (auto it = cell->connections().begin(); it != cell->connections().end(); ++it) { if (it->second.size() > 0) { @@ -392,33 +654,6 @@ struct FirrtlWorker return result; } - void emit_extmodule() - { - std::string moduleFileinfo = getFileinfo(module); - f << stringf(" extmodule %s: %s\n", make_id(module->name), moduleFileinfo.c_str()); - vector<std::string> port_decls; - - for (auto wire : module->wires()) - { - const auto wireName = make_id(wire->name); - std::string wireFileinfo = getFileinfo(wire); - - if (wire->port_input && wire->port_output) - { - log_error("Module port %s.%s is inout!\n", log_id(module), log_id(wire)); - } - port_decls.push_back(stringf(" %s %s: UInt<%d> %s\n", wire->port_input ? "input" : "output", - wireName, wire->width, wireFileinfo.c_str())); - } - - for (auto &str : port_decls) - { - f << str; - } - - f << stringf("\n"); - } - void emit_module() { std::string moduleFileinfo = getFileinfo(module); @@ -440,12 +675,12 @@ struct FirrtlWorker { if (wire->port_input && wire->port_output) log_error("Module port %s.%s is inout!\n", log_id(module), log_id(wire)); - port_decls.push_back(stringf(" %s %s: UInt<%d> %s\n", wire->port_input ? "input" : "output", + port_decls.push_back(stringf("%s%s %s: UInt<%d> %s\n", indent.c_str(), wire->port_input ? "input" : "output", wireName, wire->width, wireFileinfo.c_str())); } else { - wire_decls.push_back(stringf(" wire %s: UInt<%d> %s\n", wireName, wire->width, wireFileinfo.c_str())); + wire_decls.push_back(stringf("%swire %s: UInt<%d> %s\n", indent.c_str(), wireName, wire->width, wireFileinfo.c_str())); } } @@ -476,7 +711,7 @@ struct FirrtlWorker if (cell->type.in(ID($not), ID($logic_not), ID($_NOT_), ID($neg), ID($reduce_and), ID($reduce_or), ID($reduce_xor), ID($reduce_bool), ID($reduce_xnor))) { string a_expr = make_expr(cell->getPort(ID::A)); - wire_decls.push_back(stringf(" wire %s: UInt<%d> %s\n", y_id.c_str(), y_width, cellFileinfo.c_str())); + wire_decls.push_back(stringf("%swire %s: UInt<%d> %s\n", indent.c_str(), y_id.c_str(), y_width, cellFileinfo.c_str())); if (a_signed) { a_expr = "asSInt(" + a_expr + ")"; @@ -516,7 +751,7 @@ struct FirrtlWorker if ((firrtl_is_signed && !always_uint)) expr = stringf("asUInt(%s)", expr.c_str()); - cell_exprs.push_back(stringf(" %s <= %s %s\n", y_id.c_str(), expr.c_str(), cellFileinfo.c_str())); + cell_exprs.push_back(stringf("%s%s <= %s %s\n", indent.c_str(), y_id.c_str(), expr.c_str(), cellFileinfo.c_str())); register_reverse_wire_map(y_id, cell->getPort(ID::Y)); continue; @@ -528,7 +763,7 @@ struct FirrtlWorker string a_expr = make_expr(cell->getPort(ID::A)); string b_expr = make_expr(cell->getPort(ID::B)); std::string cellFileinfo = getFileinfo(cell); - wire_decls.push_back(stringf(" wire %s: UInt<%d> %s\n", y_id.c_str(), y_width, cellFileinfo.c_str())); + wire_decls.push_back(stringf("%swire %s: UInt<%d> %s\n", indent.c_str(), y_id.c_str(), y_width, cellFileinfo.c_str())); if (a_signed) { a_expr = "asSInt(" + a_expr + ")"; @@ -746,7 +981,7 @@ struct FirrtlWorker if ((firrtl_is_signed && !always_uint)) expr = stringf("asUInt(%s)", expr.c_str()); - cell_exprs.push_back(stringf(" %s <= %s %s\n", y_id.c_str(), expr.c_str(), cellFileinfo.c_str())); + cell_exprs.push_back(stringf("%s%s <= %s %s\n", indent.c_str(), y_id.c_str(), expr.c_str(), cellFileinfo.c_str())); register_reverse_wire_map(y_id, cell->getPort(ID::Y)); continue; @@ -759,11 +994,11 @@ struct FirrtlWorker string a_expr = make_expr(cell->getPort(ID::A)); string b_expr = make_expr(cell->getPort(ID::B)); string s_expr = make_expr(cell->getPort(ID::S)); - wire_decls.push_back(stringf(" wire %s: UInt<%d> %s\n", y_id.c_str(), width, cellFileinfo.c_str())); + wire_decls.push_back(stringf("%swire %s: UInt<%d> %s\n", indent.c_str(), y_id.c_str(), width, cellFileinfo.c_str())); string expr = stringf("mux(%s, %s, %s)", s_expr.c_str(), b_expr.c_str(), a_expr.c_str()); - cell_exprs.push_back(stringf(" %s <= %s %s\n", y_id.c_str(), expr.c_str(), cellFileinfo.c_str())); + cell_exprs.push_back(stringf("%s%s <= %s %s\n", indent.c_str(), y_id.c_str(), expr.c_str(), cellFileinfo.c_str())); register_reverse_wire_map(y_id, cell->getPort(ID::Y)); continue; @@ -902,9 +1137,9 @@ struct FirrtlWorker string expr = make_expr(cell->getPort(ID::D)); string clk_expr = "asClock(" + make_expr(cell->getPort(ID::CLK)) + ")"; - wire_decls.push_back(stringf(" reg %s: UInt<%d>, %s %s\n", y_id.c_str(), width, clk_expr.c_str(), cellFileinfo.c_str())); + wire_decls.push_back(stringf("%sreg %s: UInt<%d>, %s %s\n", indent.c_str(), y_id.c_str(), width, clk_expr.c_str(), cellFileinfo.c_str())); - cell_exprs.push_back(stringf(" %s <= %s %s\n", y_id.c_str(), expr.c_str(), cellFileinfo.c_str())); + cell_exprs.push_back(stringf("%s%s <= %s %s\n", indent.c_str(), y_id.c_str(), expr.c_str(), cellFileinfo.c_str())); register_reverse_wire_map(y_id, cell->getPort(ID::Q)); continue; @@ -923,7 +1158,7 @@ struct FirrtlWorker string a_expr = make_expr(cell->getPort(ID::A)); // Get the initial bit selector string b_expr = make_expr(cell->getPort(ID::B)); - wire_decls.push_back(stringf(" wire %s: UInt<%d>\n", y_id.c_str(), y_width)); + wire_decls.push_back(stringf("%swire %s: UInt<%d>\n", indent.c_str(), y_id.c_str(), y_width)); if (cell->getParam(ID::B_SIGNED).as_bool()) { // Use validif to constrain the selection (test the sign bit) @@ -933,7 +1168,7 @@ struct FirrtlWorker } string expr = stringf("dshr(%s, %s)", a_expr.c_str(), b_expr.c_str()); - cell_exprs.push_back(stringf(" %s <= %s\n", y_id.c_str(), expr.c_str())); + cell_exprs.push_back(stringf("%s%s <= %s\n", indent.c_str(), y_id.c_str(), expr.c_str())); register_reverse_wire_map(y_id, cell->getPort(ID::Y)); continue; } @@ -945,7 +1180,7 @@ struct FirrtlWorker string b_expr = make_expr(cell->getPort(ID::B)); auto b_string = b_expr.c_str(); string expr; - wire_decls.push_back(stringf(" wire %s: UInt<%d>\n", y_id.c_str(), y_width)); + wire_decls.push_back(stringf("%swire %s: UInt<%d>\n", indent.c_str(), y_id.c_str(), y_width)); if (cell->getParam(ID::B_SIGNED).as_bool()) { // We generate a left or right shift based on the sign of b. @@ -959,7 +1194,7 @@ struct FirrtlWorker } else { expr = stringf("dshr(%s, %s)", a_expr.c_str(), b_string); } - cell_exprs.push_back(stringf(" %s <= %s\n", y_id.c_str(), expr.c_str())); + cell_exprs.push_back(stringf("%s%s <= %s\n", indent.c_str(), y_id.c_str(), expr.c_str())); register_reverse_wire_map(y_id, cell->getPort(ID::Y)); continue; } @@ -972,8 +1207,8 @@ struct FirrtlWorker if (a_width < y_width) { a_expr = stringf("pad(%s, %d)", a_expr.c_str(), y_width); } - wire_decls.push_back(stringf(" wire %s: UInt<%d>\n", y_id.c_str(), y_width)); - cell_exprs.push_back(stringf(" %s <= %s\n", y_id.c_str(), a_expr.c_str())); + wire_decls.push_back(stringf("%swire %s: UInt<%d>\n", indent.c_str(), y_id.c_str(), y_width)); + cell_exprs.push_back(stringf("%s%s <= %s\n", indent.c_str(), y_id.c_str(), a_expr.c_str())); register_reverse_wire_map(y_id, cell->getPort(ID::Y)); continue; } @@ -986,8 +1221,8 @@ struct FirrtlWorker int y_width = GetSize(conn.first); string expr = make_expr(conn.second); - wire_decls.push_back(stringf(" wire %s: UInt<%d>\n", y_id.c_str(), y_width)); - cell_exprs.push_back(stringf(" %s <= %s\n", y_id.c_str(), expr.c_str())); + wire_decls.push_back(stringf("%swire %s: UInt<%d>\n", indent.c_str(), y_id.c_str(), y_width)); + cell_exprs.push_back(stringf("%s%s <= %s\n", indent.c_str(), y_id.c_str(), expr.c_str())); register_reverse_wire_map(y_id, conn.first); } @@ -1053,13 +1288,13 @@ struct FirrtlWorker if (is_valid) { if (make_unconn_id) { - wire_decls.push_back(stringf(" wire %s: UInt<1> %s\n", unconn_id.c_str(), wireFileinfo.c_str())); + wire_decls.push_back(stringf("%swire %s: UInt<1> %s\n", indent.c_str(), unconn_id.c_str(), wireFileinfo.c_str())); // `invalid` is a firrtl construction for simulation so we will not // tag it with a @[fileinfo] tag as it doesn't directly correspond to // a specific line of verilog code. - wire_decls.push_back(stringf(" %s is invalid\n", unconn_id.c_str())); + wire_decls.push_back(stringf("%s%s is invalid\n", indent.c_str(), unconn_id.c_str())); } - wire_exprs.push_back(stringf(" %s <= %s %s\n", make_id(wire->name), expr.c_str(), wireFileinfo.c_str())); + wire_exprs.push_back(stringf("%s%s <= %s %s\n", indent.c_str(), make_id(wire->name), expr.c_str(), wireFileinfo.c_str())); } else { if (make_unconn_id) { unconn_id.clear(); @@ -1067,7 +1302,7 @@ struct FirrtlWorker // `invalid` is a firrtl construction for simulation so we will not // tag it with a @[fileinfo] tag as it doesn't directly correspond to // a specific line of verilog code. - wire_decls.push_back(stringf(" %s is invalid\n", make_id(wire->name))); + wire_decls.push_back(stringf("%s%s is invalid\n", indent.c_str(), make_id(wire->name))); } } @@ -1112,18 +1347,13 @@ struct FirrtlWorker void run() { - // Blackboxes should be emitted as `extmodule`s in firrtl. Only ports are - // emitted in such a case. - if (module->get_blackbox_attribute()) - emit_extmodule(); - else - emit_module(); + emit_module(); } }; struct FirrtlBackend : public Backend { FirrtlBackend() : Backend("firrtl", "write design to a FIRRTL file") { } - void help() YS_OVERRIDE + void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); @@ -1134,7 +1364,7 @@ struct FirrtlBackend : public Backend { log(" pmuxtree\n"); log("\n"); } - void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE + void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override { size_t argidx = args.size(); // We aren't expecting any arguments. @@ -1180,10 +1410,16 @@ struct FirrtlBackend : public Backend { std::string circuitFileinfo = getFileinfo(top); *f << stringf("circuit %s: %s\n", make_id(top->name), circuitFileinfo.c_str()); + emit_elaborated_extmodules(design, *f); + + // Emit non-blackbox modules. for (auto module : design->modules()) { - FirrtlWorker worker(module, *f, design); - worker.run(); + if (!module->get_blackbox_attribute()) + { + FirrtlWorker worker(module, *f, design); + worker.run(); + } } namecache.clear(); diff --git a/backends/ilang/Makefile.inc b/backends/ilang/Makefile.inc deleted file mode 100644 index 52fc2b891..000000000 --- a/backends/ilang/Makefile.inc +++ /dev/null @@ -1,3 +0,0 @@ - -OBJS += backends/ilang/ilang_backend.o - diff --git a/backends/intersynth/intersynth.cc b/backends/intersynth/intersynth.cc index 31dce1cca..a6b36de6c 100644 --- a/backends/intersynth/intersynth.cc +++ b/backends/intersynth/intersynth.cc @@ -46,7 +46,7 @@ static std::string netname(std::set<std::string> &conntypes_code, std::set<std:: struct IntersynthBackend : public Backend { IntersynthBackend() : Backend("intersynth", "write design to InterSynth netlist file") { } - void help() YS_OVERRIDE + void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); @@ -59,7 +59,7 @@ struct IntersynthBackend : public Backend { log(" do not generate celltypes and conntypes commands. i.e. just output\n"); log(" the netlists. this is used for postsilicon synthesis.\n"); log("\n"); - log(" -lib <verilog_or_ilang_file>\n"); + log(" -lib <verilog_or_rtlil_file>\n"); log(" Use the specified library file for determining whether cell ports are\n"); log(" inputs or outputs. This option can be used multiple times to specify\n"); log(" more than one library.\n"); @@ -71,7 +71,7 @@ struct IntersynthBackend : public Backend { log("http://www.clifford.at/intersynth/\n"); log("\n"); } - void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE + void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override { log_header(design, "Executing INTERSYNTH backend.\n"); log_push(); @@ -108,7 +108,7 @@ struct IntersynthBackend : public Backend { if (f.fail()) log_error("Can't open lib file `%s'.\n", filename.c_str()); RTLIL::Design *lib = new RTLIL::Design; - Frontend::frontend_call(lib, &f, filename, (filename.size() > 3 && filename.compare(filename.size()-3, std::string::npos, ".il") == 0 ? "ilang" : "verilog")); + Frontend::frontend_call(lib, &f, filename, (filename.size() > 3 && filename.compare(filename.size()-3, std::string::npos, ".il") == 0 ? "rtlil" : "verilog")); libs.push_back(lib); } diff --git a/backends/json/json.cc b/backends/json/json.cc index 5edc50f60..eeadc1b89 100644 --- a/backends/json/json.cc +++ b/backends/json/json.cc @@ -294,7 +294,7 @@ struct JsonWriter struct JsonBackend : public Backend { JsonBackend() : Backend("json", "write design to a JSON file") { } - void help() YS_OVERRIDE + void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); @@ -530,7 +530,7 @@ struct JsonBackend : public Backend { log("format. A program processing this format must ignore all unknown fields.\n"); log("\n"); } - void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE + void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override { bool aig_mode = false; bool compat_int_mode = false; @@ -559,7 +559,7 @@ struct JsonBackend : public Backend { struct JsonPass : public Pass { JsonPass() : Pass("json", "write design in JSON format") { } - void help() YS_OVERRIDE + void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); @@ -580,7 +580,7 @@ struct JsonPass : public Pass { log("See 'help write_json' for a description of the JSON format used.\n"); log("\n"); } - void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE + void execute(std::vector<std::string> args, RTLIL::Design *design) override { std::string filename; bool aig_mode = false; diff --git a/backends/protobuf/protobuf.cc b/backends/protobuf/protobuf.cc index 671686173..f6623a382 100644 --- a/backends/protobuf/protobuf.cc +++ b/backends/protobuf/protobuf.cc @@ -231,7 +231,7 @@ struct ProtobufDesignSerializer struct ProtobufBackend : public Backend { ProtobufBackend(): Backend("protobuf", "write design to a Protocol Buffer file") { } - void help() YS_OVERRIDE + void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); @@ -249,7 +249,7 @@ struct ProtobufBackend : public Backend { log("Yosys source code distribution.\n"); log("\n"); } - void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE + void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override { bool aig_mode = false; bool text_mode = false; @@ -286,7 +286,7 @@ struct ProtobufBackend : public Backend { struct ProtobufPass : public Pass { ProtobufPass() : Pass("protobuf", "write design in Protobuf format") { } - void help() YS_OVERRIDE + void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); @@ -307,7 +307,7 @@ struct ProtobufPass : public Pass { log("Yosys source code distribution.\n"); log("\n"); } - void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE + void execute(std::vector<std::string> args, RTLIL::Design *design) override { std::string filename; bool aig_mode = false; diff --git a/backends/rtlil/Makefile.inc b/backends/rtlil/Makefile.inc new file mode 100644 index 000000000..f691282ca --- /dev/null +++ b/backends/rtlil/Makefile.inc @@ -0,0 +1,3 @@ + +OBJS += backends/rtlil/rtlil_backend.o + diff --git a/backends/ilang/ilang_backend.cc b/backends/rtlil/rtlil_backend.cc index 3a418de3c..01b4bde53 100644 --- a/backends/ilang/ilang_backend.cc +++ b/backends/rtlil/rtlil_backend.cc @@ -18,19 +18,19 @@ * --- * * A very simple and straightforward backend for the RTLIL text - * representation (as understood by the 'ilang' frontend). + * representation. * */ -#include "ilang_backend.h" +#include "rtlil_backend.h" #include "kernel/yosys.h" #include <errno.h> USING_YOSYS_NAMESPACE -using namespace ILANG_BACKEND; +using namespace RTLIL_BACKEND; YOSYS_NAMESPACE_BEGIN -void ILANG_BACKEND::dump_const(std::ostream &f, const RTLIL::Const &data, int width, int offset, bool autoint) +void RTLIL_BACKEND::dump_const(std::ostream &f, const RTLIL::Const &data, int width, int offset, bool autoint) { if (width < 0) width = data.bits.size() - offset; @@ -83,7 +83,7 @@ void ILANG_BACKEND::dump_const(std::ostream &f, const RTLIL::Const &data, int wi } } -void ILANG_BACKEND::dump_sigchunk(std::ostream &f, const RTLIL::SigChunk &chunk, bool autoint) +void RTLIL_BACKEND::dump_sigchunk(std::ostream &f, const RTLIL::SigChunk &chunk, bool autoint) { if (chunk.wire == NULL) { dump_const(f, chunk.data, chunk.width, chunk.offset, autoint); @@ -97,7 +97,7 @@ void ILANG_BACKEND::dump_sigchunk(std::ostream &f, const RTLIL::SigChunk &chunk, } } -void ILANG_BACKEND::dump_sigspec(std::ostream &f, const RTLIL::SigSpec &sig, bool autoint) +void RTLIL_BACKEND::dump_sigspec(std::ostream &f, const RTLIL::SigSpec &sig, bool autoint) { if (sig.is_chunk()) { dump_sigchunk(f, sig.as_chunk(), autoint); @@ -111,7 +111,7 @@ void ILANG_BACKEND::dump_sigspec(std::ostream &f, const RTLIL::SigSpec &sig, boo } } -void ILANG_BACKEND::dump_wire(std::ostream &f, std::string indent, const RTLIL::Wire *wire) +void RTLIL_BACKEND::dump_wire(std::ostream &f, std::string indent, const RTLIL::Wire *wire) { for (auto &it : wire->attributes) { f << stringf("%s" "attribute %s ", indent.c_str(), it.first.c_str()); @@ -136,7 +136,7 @@ void ILANG_BACKEND::dump_wire(std::ostream &f, std::string indent, const RTLIL:: f << stringf("%s\n", wire->name.c_str()); } -void ILANG_BACKEND::dump_memory(std::ostream &f, std::string indent, const RTLIL::Memory *memory) +void RTLIL_BACKEND::dump_memory(std::ostream &f, std::string indent, const RTLIL::Memory *memory) { for (auto &it : memory->attributes) { f << stringf("%s" "attribute %s ", indent.c_str(), it.first.c_str()); @@ -153,7 +153,7 @@ void ILANG_BACKEND::dump_memory(std::ostream &f, std::string indent, const RTLIL f << stringf("%s\n", memory->name.c_str()); } -void ILANG_BACKEND::dump_cell(std::ostream &f, std::string indent, const RTLIL::Cell *cell) +void RTLIL_BACKEND::dump_cell(std::ostream &f, std::string indent, const RTLIL::Cell *cell) { for (auto &it : cell->attributes) { f << stringf("%s" "attribute %s ", indent.c_str(), it.first.c_str()); @@ -177,7 +177,7 @@ void ILANG_BACKEND::dump_cell(std::ostream &f, std::string indent, const RTLIL:: f << stringf("%s" "end\n", indent.c_str()); } -void ILANG_BACKEND::dump_proc_case_body(std::ostream &f, std::string indent, const RTLIL::CaseRule *cs) +void RTLIL_BACKEND::dump_proc_case_body(std::ostream &f, std::string indent, const RTLIL::CaseRule *cs) { for (auto it = cs->actions.begin(); it != cs->actions.end(); ++it) { @@ -192,7 +192,7 @@ void ILANG_BACKEND::dump_proc_case_body(std::ostream &f, std::string indent, con dump_proc_switch(f, indent, *it); } -void ILANG_BACKEND::dump_proc_switch(std::ostream &f, std::string indent, const RTLIL::SwitchRule *sw) +void RTLIL_BACKEND::dump_proc_switch(std::ostream &f, std::string indent, const RTLIL::SwitchRule *sw) { for (auto it = sw->attributes.begin(); it != sw->attributes.end(); ++it) { f << stringf("%s" "attribute %s ", indent.c_str(), it->first.c_str()); @@ -225,7 +225,7 @@ void ILANG_BACKEND::dump_proc_switch(std::ostream &f, std::string indent, const f << stringf("%s" "end\n", indent.c_str()); } -void ILANG_BACKEND::dump_proc_sync(std::ostream &f, std::string indent, const RTLIL::SyncRule *sy) +void RTLIL_BACKEND::dump_proc_sync(std::ostream &f, std::string indent, const RTLIL::SyncRule *sy) { f << stringf("%s" "sync ", indent.c_str()); switch (sy->type) { @@ -251,7 +251,7 @@ void ILANG_BACKEND::dump_proc_sync(std::ostream &f, std::string indent, const RT } } -void ILANG_BACKEND::dump_proc(std::ostream &f, std::string indent, const RTLIL::Process *proc) +void RTLIL_BACKEND::dump_proc(std::ostream &f, std::string indent, const RTLIL::Process *proc) { for (auto it = proc->attributes.begin(); it != proc->attributes.end(); ++it) { f << stringf("%s" "attribute %s ", indent.c_str(), it->first.c_str()); @@ -265,7 +265,7 @@ void ILANG_BACKEND::dump_proc(std::ostream &f, std::string indent, const RTLIL:: f << stringf("%s" "end\n", indent.c_str()); } -void ILANG_BACKEND::dump_conn(std::ostream &f, std::string indent, const RTLIL::SigSpec &left, const RTLIL::SigSpec &right) +void RTLIL_BACKEND::dump_conn(std::ostream &f, std::string indent, const RTLIL::SigSpec &left, const RTLIL::SigSpec &right) { f << stringf("%s" "connect ", indent.c_str()); dump_sigspec(f, left); @@ -274,7 +274,7 @@ void ILANG_BACKEND::dump_conn(std::ostream &f, std::string indent, const RTLIL:: f << stringf("\n"); } -void ILANG_BACKEND::dump_module(std::ostream &f, std::string indent, RTLIL::Module *module, RTLIL::Design *design, bool only_selected, bool flag_m, bool flag_n) +void RTLIL_BACKEND::dump_module(std::ostream &f, std::string indent, RTLIL::Module *module, RTLIL::Design *design, bool only_selected, bool flag_m, bool flag_n) { bool print_header = flag_m || design->selected_whole_module(module->name); bool print_body = !flag_n || !design->selected_whole_module(module->name); @@ -360,11 +360,9 @@ void ILANG_BACKEND::dump_module(std::ostream &f, std::string indent, RTLIL::Modu f << stringf("%s" "end\n", indent.c_str()); } -void ILANG_BACKEND::dump_design(std::ostream &f, RTLIL::Design *design, bool only_selected, bool flag_m, bool flag_n) +void RTLIL_BACKEND::dump_design(std::ostream &f, RTLIL::Design *design, bool only_selected, bool flag_m, bool flag_n) { -#ifndef NDEBUG int init_autoidx = autoidx; -#endif if (!flag_m) { int count_selected_mods = 0; @@ -398,26 +396,26 @@ void ILANG_BACKEND::dump_design(std::ostream &f, RTLIL::Design *design, bool onl YOSYS_NAMESPACE_END PRIVATE_NAMESPACE_BEGIN -struct IlangBackend : public Backend { - IlangBackend() : Backend("ilang", "write design to ilang file") { } - void help() YS_OVERRIDE +struct RTLILBackend : public Backend { + RTLILBackend() : Backend("rtlil", "write design to RTLIL file") { } + void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); - log(" write_ilang [filename]\n"); + log(" write_rtlil [filename]\n"); log("\n"); - log("Write the current design to an 'ilang' file. (ilang is a text representation\n"); + log("Write the current design to an RTLIL file. (RTLIL is a text representation\n"); log("of a design in yosys's internal format.)\n"); log("\n"); log(" -selected\n"); log(" only write selected parts of the design.\n"); log("\n"); } - void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE + void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override { bool selected = false; - log_header(design, "Executing ILANG backend.\n"); + log_header(design, "Executing RTLIL backend.\n"); size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) { @@ -434,20 +432,35 @@ struct IlangBackend : public Backend { log("Output filename: %s\n", filename.c_str()); *f << stringf("# Generated by %s\n", yosys_version_str); - ILANG_BACKEND::dump_design(*f, design, selected, true, false); + RTLIL_BACKEND::dump_design(*f, design, selected, true, false); + } +} RTLILBackend; + +struct IlangBackend : public Backend { + IlangBackend() : Backend("ilang", "(deprecated) alias of write_rtlil") { } + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log("See `help write_rtlil`.\n"); + log("\n"); + } + void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override + { + RTLILBackend.execute(f, filename, args, design); } } IlangBackend; struct DumpPass : public Pass { - DumpPass() : Pass("dump", "print parts of the design in ilang format") { } - void help() YS_OVERRIDE + DumpPass() : Pass("dump", "print parts of the design in RTLIL format") { } + void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); log(" dump [options] [selection]\n"); log("\n"); log("Write the selected parts of the design to the console or specified file in\n"); - log("ilang format.\n"); + log("RTLIL format.\n"); log("\n"); log(" -m\n"); log(" also dump the module headers, even if only parts of a single\n"); @@ -463,7 +476,7 @@ struct DumpPass : public Pass { log(" like -outfile but append instead of overwrite\n"); log("\n"); } - void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE + void execute(std::vector<std::string> args, RTLIL::Design *design) override { std::string filename; bool flag_m = false, flag_n = false, append = false; @@ -510,7 +523,7 @@ struct DumpPass : public Pass { f = &buf; } - ILANG_BACKEND::dump_design(*f, design, true, flag_m, flag_n); + RTLIL_BACKEND::dump_design(*f, design, true, flag_m, flag_n); if (!filename.empty()) { delete f; diff --git a/backends/ilang/ilang_backend.h b/backends/rtlil/rtlil_backend.h index 97dcbb628..77eea353c 100644 --- a/backends/ilang/ilang_backend.h +++ b/backends/rtlil/rtlil_backend.h @@ -18,19 +18,19 @@ * --- * * A very simple and straightforward backend for the RTLIL text - * representation (as understood by the 'ilang' frontend). + * representation. * */ -#ifndef ILANG_BACKEND_H -#define ILANG_BACKEND_H +#ifndef RTLIL_BACKEND_H +#define RTLIL_BACKEND_H #include "kernel/yosys.h" #include <stdio.h> YOSYS_NAMESPACE_BEGIN -namespace ILANG_BACKEND { +namespace RTLIL_BACKEND { void dump_const(std::ostream &f, const RTLIL::Const &data, int width = -1, int offset = 0, bool autoint = true); void dump_sigchunk(std::ostream &f, const RTLIL::SigChunk &chunk, bool autoint = true); void dump_sigspec(std::ostream &f, const RTLIL::SigSpec &sig, bool autoint = true); diff --git a/backends/simplec/simplec.cc b/backends/simplec/simplec.cc index 83ed5e6e0..3adeaa6c0 100644 --- a/backends/simplec/simplec.cc +++ b/backends/simplec/simplec.cc @@ -744,7 +744,7 @@ struct SimplecWorker struct SimplecBackend : public Backend { SimplecBackend() : Backend("simplec", "convert design to simple C code") { } - void help() YS_OVERRIDE + void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); @@ -763,7 +763,7 @@ struct SimplecBackend : public Backend { log("THIS COMMAND IS UNDER CONSTRUCTION\n"); log("\n"); } - void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE + void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override { reserved_cids.clear(); id2cid.clear(); diff --git a/backends/smt2/smt2.cc b/backends/smt2/smt2.cc index 26f17bcb3..a185fdd74 100644 --- a/backends/smt2/smt2.cc +++ b/backends/smt2/smt2.cc @@ -22,6 +22,7 @@ #include "kernel/sigtools.h" #include "kernel/celltypes.h" #include "kernel/log.h" +#include "kernel/mem.h" #include <string> USING_YOSYS_NAMESPACE @@ -40,12 +41,15 @@ struct Smt2Worker std::map<RTLIL::SigBit, RTLIL::Cell*> bit_driver; std::set<RTLIL::Cell*> exported_cells, hiercells, hiercells_queue; pool<Cell*> recursive_cells, registers; + std::vector<Mem> memories; + dict<Cell*, Mem*> mem_cells; + std::set<Mem*> memory_queue; pool<SigBit> clock_posedge, clock_negedge; vector<string> ex_state_eq, ex_input_eq; std::map<RTLIL::SigBit, std::pair<int, int>> fcache; - std::map<Cell*, int> memarrays; + std::map<Mem*, int> memarrays; std::map<int, int> bvsizes; dict<IdString, char*> ids; @@ -116,12 +120,73 @@ struct Smt2Worker makebits(stringf("%s_is", get_id(module))); + dict<IdString, Mem*> mem_dict; + memories = Mem::get_all_memories(module); + for (auto &mem : memories) + { + mem_dict[mem.memid] = &mem; + for (auto &port : mem.wr_ports) + { + if (port.clk_enable) { + SigSpec clk = sigmap(port.clk); + for (int i = 0; i < GetSize(clk); i++) + { + if (clk[i].wire == nullptr) + continue; + if (port.clk_polarity) + clock_posedge.insert(clk[i]); + else + clock_negedge.insert(clk[i]); + } + } + for (auto bit : sigmap(port.en)) + noclock.insert(bit); + for (auto bit : sigmap(port.addr)) + noclock.insert(bit); + for (auto bit : sigmap(port.data)) + noclock.insert(bit); + } + for (auto &port : mem.rd_ports) + { + if (port.clk_enable) { + SigSpec clk = sigmap(port.clk); + for (int i = 0; i < GetSize(clk); i++) + { + if (clk[i].wire == nullptr) + continue; + if (port.clk_polarity) + clock_posedge.insert(clk[i]); + else + clock_negedge.insert(clk[i]); + } + } + for (auto bit : sigmap(port.en)) + noclock.insert(bit); + for (auto bit : sigmap(port.addr)) + noclock.insert(bit); + for (auto bit : sigmap(port.data)) + noclock.insert(bit); + Cell *driver = port.cell ? port.cell : mem.cell; + for (auto bit : sigmap(port.data)) { + if (bit_driver.count(bit)) + log_error("Found multiple drivers for %s.\n", log_signal(bit)); + bit_driver[bit] = driver; + } + } + } + for (auto cell : module->cells()) for (auto &conn : cell->connections()) { if (GetSize(conn.second) == 0) continue; + // Handled above. + if (cell->type.in(ID($mem), ID($memrd), ID($memwr), ID($meminit))) { + mem_cells[cell] = mem_dict[cell->parameters.at(ID::MEMID).decode_string()]; + continue; + } + bool is_input = ct.cell_input(cell->type, conn.first); bool is_output = ct.cell_output(cell->type, conn.first); @@ -135,24 +200,6 @@ struct Smt2Worker log_error("Unsupported or unknown directionality on port %s of cell %s.%s (%s).\n", log_id(conn.first), log_id(module), log_id(cell), log_id(cell->type)); - if (cell->type.in(ID($mem)) && conn.first.in(ID::RD_CLK, ID::WR_CLK)) - { - SigSpec clk = sigmap(conn.second); - for (int i = 0; i < GetSize(clk); i++) - { - if (clk[i].wire == nullptr) - continue; - - if (cell->getParam(conn.first == ID::RD_CLK ? ID::RD_CLK_ENABLE : ID::WR_CLK_ENABLE)[i] != State::S1) - continue; - - if (cell->getParam(conn.first == ID::RD_CLK ? ID::RD_CLK_POLARITY : ID::WR_CLK_POLARITY)[i] == State::S1) - clock_posedge.insert(clk[i]); - else - clock_negedge.insert(clk[i]); - } - } - else if (cell->type.in(ID($dff), ID($_DFF_P_), ID($_DFF_N_)) && conn.first.in(ID::CLK, ID::C)) { bool posedge = (cell->type == ID($_DFF_N_)) || (cell->type == ID($dff) && cell->getParam(ID::CLK_POLARITY).as_bool()); @@ -647,27 +694,35 @@ struct Smt2Worker // FIXME: $slice $concat } - if (memmode && cell->type == ID($mem)) + if (memmode && cell->type.in(ID($mem), ID($memrd), ID($memwr), ID($meminit))) { + Mem *mem = mem_cells[cell]; + + if (memarrays.count(mem)) { + recursive_cells.erase(cell); + return; + } + int arrayid = idcounter++; - memarrays[cell] = arrayid; - - int abits = cell->getParam(ID::ABITS).as_int(); - int width = cell->getParam(ID::WIDTH).as_int(); - int rd_ports = cell->getParam(ID::RD_PORTS).as_int(); - int wr_ports = cell->getParam(ID::WR_PORTS).as_int(); - - bool async_read = false; - if (!cell->getParam(ID::WR_CLK_ENABLE).is_fully_ones()) { - if (!cell->getParam(ID::WR_CLK_ENABLE).is_fully_zero()) - log_error("Memory %s.%s has mixed clocked/nonclocked write ports. This is not supported by \"write_smt2\".\n", log_id(cell), log_id(module)); - async_read = true; + memarrays[mem] = arrayid; + + int abits = ceil_log2(mem->size); + + bool has_sync_wr = false; + bool has_async_wr = false; + for (auto &port : mem->wr_ports) { + if (port.clk_enable) + has_sync_wr = true; + else + has_async_wr = true; } + if (has_async_wr && has_sync_wr) + log_error("Memory %s.%s has mixed clocked/nonclocked write ports. This is not supported by \"write_smt2\".\n", log_id(cell), log_id(module)); - decls.push_back(stringf("; yosys-smt2-memory %s %d %d %d %d %s\n", get_id(cell), abits, width, rd_ports, wr_ports, async_read ? "async" : "sync")); + decls.push_back(stringf("; yosys-smt2-memory %s %d %d %d %d %s\n", get_id(mem->memid), abits, mem->width, GetSize(mem->rd_ports), GetSize(mem->wr_ports), has_async_wr ? "async" : "sync")); string memstate; - if (async_read) { + if (has_async_wr) { memstate = stringf("%s#%d#final", get_id(module), arrayid); } else { memstate = stringf("%s#%d#0", get_id(module), arrayid); @@ -675,80 +730,79 @@ struct Smt2Worker if (statebv) { - int mem_size = cell->getParam(ID::SIZE).as_int(); - int mem_offset = cell->getParam(ID::OFFSET).as_int(); - - makebits(memstate, width*mem_size, get_id(cell)); + makebits(memstate, mem->width*mem->size, get_id(mem->memid)); decls.push_back(stringf("(define-fun |%s_m %s| ((state |%s_s|)) (_ BitVec %d) (|%s| state))\n", - get_id(module), get_id(cell), get_id(module), width*mem_size, memstate.c_str())); + get_id(module), get_id(mem->memid), get_id(module), mem->width*mem->size, memstate.c_str())); - for (int i = 0; i < rd_ports; i++) + for (int i = 0; i < GetSize(mem->rd_ports); i++) { - SigSpec addr_sig = cell->getPort(ID::RD_ADDR).extract(abits*i, abits); - SigSpec data_sig = cell->getPort(ID::RD_DATA).extract(width*i, width); + auto &port = mem->rd_ports[i]; + SigSpec addr_sig = port.addr; + addr_sig.extend_u0(abits); std::string addr = get_bv(addr_sig); - if (cell->getParam(ID::RD_CLK_ENABLE).extract(i).as_bool()) + if (port.clk_enable) log_error("Read port %d (%s) of memory %s.%s is clocked. This is not supported by \"write_smt2\"! " - "Call \"memory\" with -nordff to avoid this error.\n", i, log_signal(data_sig), log_id(cell), log_id(module)); + "Call \"memory\" with -nordff to avoid this error.\n", i, log_signal(port.data), log_id(mem->memid), log_id(module)); decls.push_back(stringf("(define-fun |%s_m:R%dA %s| ((state |%s_s|)) (_ BitVec %d) %s) ; %s\n", - get_id(module), i, get_id(cell), get_id(module), abits, addr.c_str(), log_signal(addr_sig))); + get_id(module), i, get_id(mem->memid), get_id(module), abits, addr.c_str(), log_signal(addr_sig))); std::string read_expr = "#b"; - for (int k = 0; k < width; k++) + for (int k = 0; k < mem->width; k++) read_expr += "0"; - for (int k = 0; k < mem_size; k++) + for (int k = 0; k < mem->size; k++) read_expr = stringf("(ite (= (|%s_m:R%dA %s| state) #b%s) ((_ extract %d %d) (|%s| state))\n %s)", - get_id(module), i, get_id(cell), Const(k+mem_offset, abits).as_string().c_str(), - width*(k+1)-1, width*k, memstate.c_str(), read_expr.c_str()); + get_id(module), i, get_id(mem->memid), Const(k+mem->start_offset, abits).as_string().c_str(), + mem->width*(k+1)-1, mem->width*k, memstate.c_str(), read_expr.c_str()); decls.push_back(stringf("(define-fun |%s#%d| ((state |%s_s|)) (_ BitVec %d)\n %s) ; %s\n", - get_id(module), idcounter, get_id(module), width, read_expr.c_str(), log_signal(data_sig))); + get_id(module), idcounter, get_id(module), mem->width, read_expr.c_str(), log_signal(port.data))); decls.push_back(stringf("(define-fun |%s_m:R%dD %s| ((state |%s_s|)) (_ BitVec %d) (|%s#%d| state))\n", - get_id(module), i, get_id(cell), get_id(module), width, get_id(module), idcounter)); + get_id(module), i, get_id(mem->memid), get_id(module), mem->width, get_id(module), idcounter)); - register_bv(data_sig, idcounter++); + register_bv(port.data, idcounter++); } } else { if (statedt) dtmembers.push_back(stringf(" (|%s| (Array (_ BitVec %d) (_ BitVec %d))) ; %s\n", - memstate.c_str(), abits, width, get_id(cell))); + memstate.c_str(), abits, mem->width, get_id(mem->memid))); else decls.push_back(stringf("(declare-fun |%s| (|%s_s|) (Array (_ BitVec %d) (_ BitVec %d))) ; %s\n", - memstate.c_str(), get_id(module), abits, width, get_id(cell))); + memstate.c_str(), get_id(module), abits, mem->width, get_id(mem->memid))); decls.push_back(stringf("(define-fun |%s_m %s| ((state |%s_s|)) (Array (_ BitVec %d) (_ BitVec %d)) (|%s| state))\n", - get_id(module), get_id(cell), get_id(module), abits, width, memstate.c_str())); + get_id(module), get_id(mem->memid), get_id(module), abits, mem->width, memstate.c_str())); - for (int i = 0; i < rd_ports; i++) + for (int i = 0; i < GetSize(mem->rd_ports); i++) { - SigSpec addr_sig = cell->getPort(ID::RD_ADDR).extract(abits*i, abits); - SigSpec data_sig = cell->getPort(ID::RD_DATA).extract(width*i, width); + auto &port = mem->rd_ports[i]; + SigSpec addr_sig = port.addr; + addr_sig.extend_u0(abits); std::string addr = get_bv(addr_sig); - if (cell->getParam(ID::RD_CLK_ENABLE).extract(i).as_bool()) + if (port.clk_enable) log_error("Read port %d (%s) of memory %s.%s is clocked. This is not supported by \"write_smt2\"! " - "Call \"memory\" with -nordff to avoid this error.\n", i, log_signal(data_sig), log_id(cell), log_id(module)); + "Call \"memory\" with -nordff to avoid this error.\n", i, log_signal(port.data), log_id(mem->memid), log_id(module)); decls.push_back(stringf("(define-fun |%s_m:R%dA %s| ((state |%s_s|)) (_ BitVec %d) %s) ; %s\n", - get_id(module), i, get_id(cell), get_id(module), abits, addr.c_str(), log_signal(addr_sig))); + get_id(module), i, get_id(mem->memid), get_id(module), abits, addr.c_str(), log_signal(addr_sig))); decls.push_back(stringf("(define-fun |%s#%d| ((state |%s_s|)) (_ BitVec %d) (select (|%s| state) (|%s_m:R%dA %s| state))) ; %s\n", - get_id(module), idcounter, get_id(module), width, memstate.c_str(), get_id(module), i, get_id(cell), log_signal(data_sig))); + get_id(module), idcounter, get_id(module), mem->width, memstate.c_str(), get_id(module), i, get_id(mem->memid), log_signal(port.data))); decls.push_back(stringf("(define-fun |%s_m:R%dD %s| ((state |%s_s|)) (_ BitVec %d) (|%s#%d| state))\n", - get_id(module), i, get_id(cell), get_id(module), width, get_id(module), idcounter)); + get_id(module), i, get_id(mem->memid), get_id(module), mem->width, get_id(module), idcounter)); - register_bv(data_sig, idcounter++); + register_bv(port.data, idcounter++); } } - registers.insert(cell); + memory_queue.insert(mem); recursive_cells.erase(cell); return; } @@ -822,40 +876,51 @@ struct Smt2Worker for (auto bit : SigSpec(wire)) if (reg_bits.count(bit)) is_register = true; - if (wire->port_id || is_register || wire->get_bool_attribute(ID::keep) || (wiresmode && wire->name[0] == '\\')) { + if (wire->port_id || is_register || wire->get_bool_attribute(ID::keep) || (wiresmode && wire->name.isPublic())) { RTLIL::SigSpec sig = sigmap(wire); + std::vector<std::string> comments; if (wire->port_input) - decls.push_back(stringf("; yosys-smt2-input %s %d\n", get_id(wire), wire->width)); + comments.push_back(stringf("; yosys-smt2-input %s %d\n", get_id(wire), wire->width)); if (wire->port_output) - decls.push_back(stringf("; yosys-smt2-output %s %d\n", get_id(wire), wire->width)); + comments.push_back(stringf("; yosys-smt2-output %s %d\n", get_id(wire), wire->width)); if (is_register) - decls.push_back(stringf("; yosys-smt2-register %s %d\n", get_id(wire), wire->width)); - if (wire->get_bool_attribute(ID::keep) || (wiresmode && wire->name[0] == '\\')) - decls.push_back(stringf("; yosys-smt2-wire %s %d\n", get_id(wire), wire->width)); + comments.push_back(stringf("; yosys-smt2-register %s %d\n", get_id(wire), wire->width)); + if (wire->get_bool_attribute(ID::keep) || (wiresmode && wire->name.isPublic())) + comments.push_back(stringf("; yosys-smt2-wire %s %d\n", get_id(wire), wire->width)); if (GetSize(wire) == 1 && (clock_posedge.count(sig) || clock_negedge.count(sig))) - decls.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" : "")); if (bvmode && GetSize(sig) > 1) { + std::string sig_bv = get_bv(sig); + if (!comments.empty()) + decls.insert(decls.end(), comments.begin(), comments.end()); decls.push_back(stringf("(define-fun |%s_n %s| ((state |%s_s|)) (_ BitVec %d) %s)\n", - get_id(module), get_id(wire), get_id(module), GetSize(sig), get_bv(sig).c_str())); + get_id(module), get_id(wire), get_id(module), GetSize(sig), sig_bv.c_str())); if (wire->port_input) ex_input_eq.push_back(stringf(" (= (|%s_n %s| state) (|%s_n %s| other_state))", get_id(module), get_id(wire), get_id(module), get_id(wire))); } else { - for (int i = 0; i < GetSize(sig); i++) + std::vector<std::string> sig_bool; + for (int i = 0; i < GetSize(sig); i++) { + sig_bool.push_back(get_bool(sig[i])); + } + if (!comments.empty()) + decls.insert(decls.end(), comments.begin(), comments.end()); + for (int i = 0; i < GetSize(sig); i++) { if (GetSize(sig) > 1) { decls.push_back(stringf("(define-fun |%s_n %s %d| ((state |%s_s|)) Bool %s)\n", - get_id(module), get_id(wire), i, get_id(module), get_bool(sig[i]).c_str())); + get_id(module), get_id(wire), i, get_id(module), sig_bool[i].c_str())); if (wire->port_input) ex_input_eq.push_back(stringf(" (= (|%s_n %s %d| state) (|%s_n %s %d| other_state))", get_id(module), get_id(wire), i, get_id(module), get_id(wire), i)); } else { decls.push_back(stringf("(define-fun |%s_n %s| ((state |%s_s|)) Bool %s)\n", - get_id(module), get_id(wire), get_id(module), get_bool(sig[i]).c_str())); + get_id(module), get_id(wire), get_id(module), sig_bool[i].c_str())); if (wire->port_input) ex_input_eq.push_back(stringf(" (= (|%s_n %s| state) (|%s_n %s| other_state))", get_id(module), get_id(wire), get_id(module), get_id(wire))); } + } } } } @@ -966,7 +1031,7 @@ struct Smt2Worker } } - for (int iter = 1; !registers.empty(); iter++) + for (int iter = 1; !registers.empty() || !memory_queue.empty(); iter++) { pool<Cell*> this_regs; this_regs.swap(registers); @@ -999,152 +1064,156 @@ struct Smt2Worker if (cell->type == ID($anyconst)) ex_state_eq.push_back(stringf("(= %s %s)", get_bv(cell->getPort(ID::Y)).c_str(), get_bv(cell->getPort(ID::Y), "other_state").c_str())); } + } - if (cell->type == ID($mem)) - { - int arrayid = memarrays.at(cell); + std::set<Mem*> this_mems; + this_mems.swap(memory_queue); + + for (auto mem : this_mems) + { + int arrayid = memarrays.at(mem); + + int abits = ceil_log2(mem->size);; + + bool has_sync_wr = false; + bool has_async_wr = false; + for (auto &port : mem->wr_ports) { + if (port.clk_enable) + has_sync_wr = true; + else + has_async_wr = true; + } - int abits = cell->getParam(ID::ABITS).as_int(); - int width = cell->getParam(ID::WIDTH).as_int(); - int wr_ports = cell->getParam(ID::WR_PORTS).as_int(); + string initial_memstate, final_memstate; - bool async_read = false; - string initial_memstate, final_memstate; + if (has_async_wr) { + log_assert(!has_sync_wr); + initial_memstate = stringf("%s#%d#0", get_id(module), arrayid); + final_memstate = stringf("%s#%d#final", get_id(module), arrayid); + } - if (!cell->getParam(ID::WR_CLK_ENABLE).is_fully_ones()) { - log_assert(cell->getParam(ID::WR_CLK_ENABLE).is_fully_zero()); - async_read = true; - initial_memstate = stringf("%s#%d#0", get_id(module), arrayid); - final_memstate = stringf("%s#%d#final", get_id(module), arrayid); + if (statebv) + { + if (has_async_wr) { + makebits(final_memstate, mem->width*mem->size, get_id(mem->memid)); } - if (statebv) + for (int i = 0; i < GetSize(mem->wr_ports); i++) { - int mem_size = cell->getParam(ID::SIZE).as_int(); - int mem_offset = cell->getParam(ID::OFFSET).as_int(); - - if (async_read) { - makebits(final_memstate, width*mem_size, get_id(cell)); + auto &port = mem->wr_ports[i]; + SigSpec addr_sig = port.addr; + addr_sig.extend_u0(abits); + + std::string addr = get_bv(addr_sig); + std::string data = get_bv(port.data); + std::string mask = get_bv(port.en); + + decls.push_back(stringf("(define-fun |%s_m:W%dA %s| ((state |%s_s|)) (_ BitVec %d) %s) ; %s\n", + get_id(module), i, get_id(mem->memid), get_id(module), abits, addr.c_str(), log_signal(addr_sig))); + addr = stringf("(|%s_m:W%dA %s| state)", get_id(module), i, get_id(mem->memid)); + + decls.push_back(stringf("(define-fun |%s_m:W%dD %s| ((state |%s_s|)) (_ BitVec %d) %s) ; %s\n", + get_id(module), i, get_id(mem->memid), get_id(module), mem->width, data.c_str(), log_signal(port.data))); + data = stringf("(|%s_m:W%dD %s| state)", get_id(module), i, get_id(mem->memid)); + + decls.push_back(stringf("(define-fun |%s_m:W%dM %s| ((state |%s_s|)) (_ BitVec %d) %s) ; %s\n", + get_id(module), i, get_id(mem->memid), get_id(module), mem->width, mask.c_str(), log_signal(port.en))); + mask = stringf("(|%s_m:W%dM %s| state)", get_id(module), i, get_id(mem->memid)); + + std::string data_expr; + + for (int k = mem->size-1; k >= 0; k--) { + std::string new_data = stringf("(bvor (bvand %s %s) (bvand ((_ extract %d %d) (|%s#%d#%d| state)) (bvnot %s)))", + data.c_str(), mask.c_str(), mem->width*(k+1)-1, mem->width*k, get_id(module), arrayid, i, mask.c_str()); + data_expr += stringf("\n (ite (= %s #b%s) %s ((_ extract %d %d) (|%s#%d#%d| state)))", + addr.c_str(), Const(k+mem->start_offset, abits).as_string().c_str(), new_data.c_str(), + mem->width*(k+1)-1, mem->width*k, get_id(module), arrayid, i); } - for (int i = 0; i < wr_ports; i++) - { - SigSpec addr_sig = cell->getPort(ID::WR_ADDR).extract(abits*i, abits); - SigSpec data_sig = cell->getPort(ID::WR_DATA).extract(width*i, width); - SigSpec mask_sig = cell->getPort(ID::WR_EN).extract(width*i, width); + decls.push_back(stringf("(define-fun |%s#%d#%d| ((state |%s_s|)) (_ BitVec %d) (concat%s)) ; %s\n", + get_id(module), arrayid, i+1, get_id(module), mem->width*mem->size, data_expr.c_str(), get_id(mem->memid))); + } + } + else + { + if (has_async_wr) { + if (statedt) + dtmembers.push_back(stringf(" (|%s| (Array (_ BitVec %d) (_ BitVec %d))) ; %s\n", + initial_memstate.c_str(), abits, mem->width, get_id(mem->memid))); + else + decls.push_back(stringf("(declare-fun |%s| (|%s_s|) (Array (_ BitVec %d) (_ BitVec %d))) ; %s\n", + initial_memstate.c_str(), get_id(module), abits, mem->width, get_id(mem->memid))); + } - std::string addr = get_bv(addr_sig); - std::string data = get_bv(data_sig); - std::string mask = get_bv(mask_sig); + for (int i = 0; i < GetSize(mem->wr_ports); i++) + { + auto &port = mem->wr_ports[i]; + SigSpec addr_sig = port.addr; + addr_sig.extend_u0(abits); - decls.push_back(stringf("(define-fun |%s_m:W%dA %s| ((state |%s_s|)) (_ BitVec %d) %s) ; %s\n", - get_id(module), i, get_id(cell), get_id(module), abits, addr.c_str(), log_signal(addr_sig))); - addr = stringf("(|%s_m:W%dA %s| state)", get_id(module), i, get_id(cell)); + std::string addr = get_bv(addr_sig); + std::string data = get_bv(port.data); + std::string mask = get_bv(port.en); - decls.push_back(stringf("(define-fun |%s_m:W%dD %s| ((state |%s_s|)) (_ BitVec %d) %s) ; %s\n", - get_id(module), i, get_id(cell), get_id(module), width, data.c_str(), log_signal(data_sig))); - data = stringf("(|%s_m:W%dD %s| state)", get_id(module), i, get_id(cell)); + decls.push_back(stringf("(define-fun |%s_m:W%dA %s| ((state |%s_s|)) (_ BitVec %d) %s) ; %s\n", + get_id(module), i, get_id(mem->memid), get_id(module), abits, addr.c_str(), log_signal(addr_sig))); + addr = stringf("(|%s_m:W%dA %s| state)", get_id(module), i, get_id(mem->memid)); - decls.push_back(stringf("(define-fun |%s_m:W%dM %s| ((state |%s_s|)) (_ BitVec %d) %s) ; %s\n", - get_id(module), i, get_id(cell), get_id(module), width, mask.c_str(), log_signal(mask_sig))); - mask = stringf("(|%s_m:W%dM %s| state)", get_id(module), i, get_id(cell)); + decls.push_back(stringf("(define-fun |%s_m:W%dD %s| ((state |%s_s|)) (_ BitVec %d) %s) ; %s\n", + get_id(module), i, get_id(mem->memid), get_id(module), mem->width, data.c_str(), log_signal(port.data))); + data = stringf("(|%s_m:W%dD %s| state)", get_id(module), i, get_id(mem->memid)); - std::string data_expr; + decls.push_back(stringf("(define-fun |%s_m:W%dM %s| ((state |%s_s|)) (_ BitVec %d) %s) ; %s\n", + get_id(module), i, get_id(mem->memid), get_id(module), mem->width, mask.c_str(), log_signal(port.en))); + mask = stringf("(|%s_m:W%dM %s| state)", get_id(module), i, get_id(mem->memid)); - for (int k = mem_size-1; k >= 0; k--) { - std::string new_data = stringf("(bvor (bvand %s %s) (bvand ((_ extract %d %d) (|%s#%d#%d| state)) (bvnot %s)))", - data.c_str(), mask.c_str(), width*(k+1)-1, width*k, get_id(module), arrayid, i, mask.c_str()); - data_expr += stringf("\n (ite (= %s #b%s) %s ((_ extract %d %d) (|%s#%d#%d| state)))", - addr.c_str(), Const(k+mem_offset, abits).as_string().c_str(), new_data.c_str(), - width*(k+1)-1, width*k, get_id(module), arrayid, i); - } + data = stringf("(bvor (bvand %s %s) (bvand (select (|%s#%d#%d| state) %s) (bvnot %s)))", + data.c_str(), mask.c_str(), get_id(module), arrayid, i, addr.c_str(), mask.c_str()); - decls.push_back(stringf("(define-fun |%s#%d#%d| ((state |%s_s|)) (_ BitVec %d) (concat%s)) ; %s\n", - get_id(module), arrayid, i+1, get_id(module), width*mem_size, data_expr.c_str(), get_id(cell))); - } + decls.push_back(stringf("(define-fun |%s#%d#%d| ((state |%s_s|)) (Array (_ BitVec %d) (_ BitVec %d)) " + "(store (|%s#%d#%d| state) %s %s)) ; %s\n", + get_id(module), arrayid, i+1, get_id(module), abits, mem->width, + get_id(module), arrayid, i, addr.c_str(), data.c_str(), get_id(mem->memid))); } - else - { - if (async_read) { - if (statedt) - dtmembers.push_back(stringf(" (|%s| (Array (_ BitVec %d) (_ BitVec %d))) ; %s\n", - initial_memstate.c_str(), abits, width, get_id(cell))); - else - decls.push_back(stringf("(declare-fun |%s| (|%s_s|) (Array (_ BitVec %d) (_ BitVec %d))) ; %s\n", - initial_memstate.c_str(), get_id(module), abits, width, get_id(cell))); - } - - for (int i = 0; i < wr_ports; i++) - { - SigSpec addr_sig = cell->getPort(ID::WR_ADDR).extract(abits*i, abits); - SigSpec data_sig = cell->getPort(ID::WR_DATA).extract(width*i, width); - SigSpec mask_sig = cell->getPort(ID::WR_EN).extract(width*i, width); + } - std::string addr = get_bv(addr_sig); - std::string data = get_bv(data_sig); - std::string mask = get_bv(mask_sig); + std::string expr_d = stringf("(|%s#%d#%d| state)", get_id(module), arrayid, GetSize(mem->wr_ports)); + std::string expr_q = stringf("(|%s#%d#0| next_state)", get_id(module), arrayid); + trans.push_back(stringf(" (= %s %s) ; %s\n", expr_d.c_str(), expr_q.c_str(), get_id(mem->memid))); + ex_state_eq.push_back(stringf("(= (|%s#%d#0| state) (|%s#%d#0| other_state))", get_id(module), arrayid, get_id(module), arrayid)); - decls.push_back(stringf("(define-fun |%s_m:W%dA %s| ((state |%s_s|)) (_ BitVec %d) %s) ; %s\n", - get_id(module), i, get_id(cell), get_id(module), abits, addr.c_str(), log_signal(addr_sig))); - addr = stringf("(|%s_m:W%dA %s| state)", get_id(module), i, get_id(cell)); + if (has_async_wr) + hier.push_back(stringf(" (= %s (|%s| state)) ; %s\n", expr_d.c_str(), final_memstate.c_str(), get_id(mem->memid))); - decls.push_back(stringf("(define-fun |%s_m:W%dD %s| ((state |%s_s|)) (_ BitVec %d) %s) ; %s\n", - get_id(module), i, get_id(cell), get_id(module), width, data.c_str(), log_signal(data_sig))); - data = stringf("(|%s_m:W%dD %s| state)", get_id(module), i, get_id(cell)); + Const init_data = mem->get_init_data(); - decls.push_back(stringf("(define-fun |%s_m:W%dM %s| ((state |%s_s|)) (_ BitVec %d) %s) ; %s\n", - get_id(module), i, get_id(cell), get_id(module), width, mask.c_str(), log_signal(mask_sig))); - mask = stringf("(|%s_m:W%dM %s| state)", get_id(module), i, get_id(cell)); + for (int i = 0; i < mem->size; i++) + { + if (i*mem->width >= GetSize(init_data)) + break; - data = stringf("(bvor (bvand %s %s) (bvand (select (|%s#%d#%d| state) %s) (bvnot %s)))", - data.c_str(), mask.c_str(), get_id(module), arrayid, i, addr.c_str(), mask.c_str()); + Const initword = init_data.extract(i*mem->width, mem->width, State::Sx); + Const initmask = initword; + bool gen_init_constr = false; - decls.push_back(stringf("(define-fun |%s#%d#%d| ((state |%s_s|)) (Array (_ BitVec %d) (_ BitVec %d)) " - "(store (|%s#%d#%d| state) %s %s)) ; %s\n", - get_id(module), arrayid, i+1, get_id(module), abits, width, - get_id(module), arrayid, i, addr.c_str(), data.c_str(), get_id(cell))); + for (int k = 0; k < GetSize(initword); k++) { + if (initword[k] == State::S0 || initword[k] == State::S1) { + gen_init_constr = true; + initmask[k] = State::S1; + } else { + initmask[k] = State::S0; + initword[k] = State::S0; } } - std::string expr_d = stringf("(|%s#%d#%d| state)", get_id(module), arrayid, wr_ports); - std::string expr_q = stringf("(|%s#%d#0| next_state)", get_id(module), arrayid); - trans.push_back(stringf(" (= %s %s) ; %s\n", expr_d.c_str(), expr_q.c_str(), get_id(cell))); - ex_state_eq.push_back(stringf("(= (|%s#%d#0| state) (|%s#%d#0| other_state))", get_id(module), arrayid, get_id(module), arrayid)); - - if (async_read) - hier.push_back(stringf(" (= %s (|%s| state)) ; %s\n", expr_d.c_str(), final_memstate.c_str(), get_id(cell))); - - Const init_data = cell->getParam(ID::INIT); - int memsize = cell->getParam(ID::SIZE).as_int(); - - for (int i = 0; i < memsize; i++) + if (gen_init_constr) { - if (i*width >= GetSize(init_data)) - break; - - Const initword = init_data.extract(i*width, width, State::Sx); - Const initmask = initword; - bool gen_init_constr = false; - - for (int k = 0; k < GetSize(initword); k++) { - if (initword[k] == State::S0 || initword[k] == State::S1) { - gen_init_constr = true; - initmask[k] = State::S1; - } else { - initmask[k] = State::S0; - initword[k] = State::S0; - } - } - - if (gen_init_constr) - { - if (statebv) - /* FIXME */; - else - init_list.push_back(stringf("(= (bvand (select (|%s#%d#0| state) #b%s) #b%s) #b%s) ; %s[%d]", - get_id(module), arrayid, Const(i, abits).as_string().c_str(), - initmask.as_string().c_str(), initword.as_string().c_str(), get_id(cell), i)); - } + if (statebv) + /* FIXME */; + else + init_list.push_back(stringf("(= (bvand (select (|%s#%d#0| state) #b%s) #b%s) #b%s) ; %s[%d]", + get_id(module), arrayid, Const(i, abits).as_string().c_str(), + initmask.as_string().c_str(), initword.as_string().c_str(), get_id(mem->memid), i)); } } } @@ -1280,7 +1349,7 @@ struct Smt2Worker struct Smt2Backend : public Backend { Smt2Backend() : Backend("smt2", "write design to SMT-LIBv2 file") { } - void help() YS_OVERRIDE + void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); @@ -1387,8 +1456,12 @@ struct Smt2Backend : public Backend { log(" use the given template file. the line containing only the token '%%%%'\n"); log(" is replaced with the regular output of this command.\n"); log("\n"); + log(" -solver-option <option> <value>\n"); + log(" emit a `; yosys-smt2-solver-option` directive for yosys-smtbmc to write\n"); + log(" the given option as a `(set-option ...)` command in the SMT-LIBv2.\n"); + log("\n"); log("[1] For more information on SMT-LIBv2 visit http://smt-lib.org/ or read David\n"); - log("R. Cok's tutorial: http://www.grammatech.com/resources/smt/SMTLIBTutorial.pdf\n"); + log("R. Cok's tutorial: https://smtlib.github.io/jSMTLIB/SMTLIBTutorial.pdf\n"); log("\n"); log("---------------------------------------------------------------------------\n"); log("\n"); @@ -1436,11 +1509,12 @@ struct Smt2Backend : public Backend { log("from non-zero to zero in the test design.\n"); log("\n"); } - void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE + void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override { std::ifstream template_f; bool bvmode = true, memmode = true, wiresmode = false, verbose = false, statebv = false, statedt = false; bool forallmode = false; + dict<std::string, std::string> solver_options; log_header(design, "Executing SMT2 backend.\n"); @@ -1484,6 +1558,11 @@ struct Smt2Backend : public Backend { verbose = true; continue; } + if (args[argidx] == "-solver-option" && argidx+2 < args.size()) { + solver_options.emplace(args[argidx+1], args[argidx+2]); + argidx += 2; + continue; + } break; } extra_args(f, filename, args, argidx); @@ -1514,6 +1593,9 @@ struct Smt2Backend : public Backend { if (statedt) *f << stringf("; yosys-smt2-stdt\n"); + for (auto &it : solver_options) + *f << stringf("; yosys-smt2-solver-option %s %s\n", it.first.c_str(), it.second.c_str()); + std::vector<RTLIL::Module*> sorted_modules; // extract module dependencies @@ -1562,7 +1644,7 @@ struct Smt2Backend : public Backend { for (auto module : sorted_modules) { - if (module->get_blackbox_attribute() || module->has_memories_warn() || module->has_processes_warn()) + if (module->get_blackbox_attribute() || module->has_processes_warn()) continue; log("Creating SMT-LIBv2 representation of module %s.\n", log_id(module)); diff --git a/backends/smt2/smtbmc.py b/backends/smt2/smtbmc.py index 03f001bfd..da5a7f57e 100644 --- a/backends/smt2/smtbmc.py +++ b/backends/smt2/smtbmc.py @@ -817,6 +817,24 @@ def write_vcd_trace(steps_start, steps_stop, index): vcd.set_time(steps_stop) +def char_ok_in_verilog(c,i): + if ('A' <= c <= 'Z'): return True + if ('a' <= c <= 'z'): return True + if ('0' <= c <= '9' and i>0): return True + if (c == '_'): return True + if (c == '$'): return True + return False + +def escape_identifier(identifier): + if type(identifier) is list: + return map(escape_identifier, identifier) + if "." in identifier: + return ".".join(escape_identifier(identifier.split("."))) + if (all(char_ok_in_verilog(identifier[i],i) for i in range(0, len(identifier)))): + return identifier + return "\\"+identifier+" " + + def write_vlogtb_trace(steps_start, steps_stop, index): filename = vlogtbfile.replace("%", index) @@ -858,12 +876,12 @@ def write_vlogtb_trace(steps_start, steps_stop, index): for name, width in primary_inputs: if name in clock_inputs: - print(" wire [%d:0] PI_%s = clock;" % (width-1, name), file=f) + print(" wire [%d:0] %s = clock;" % (width-1, escape_identifier("PI_"+name)), file=f) else: - print(" reg [%d:0] PI_%s;" % (width-1, name), file=f) + print(" reg [%d:0] %s;" % (width-1, escape_identifier("PI_"+name)), file=f) - print(" %s UUT (" % vlogtb_topmod, file=f) - print(",\n".join(" .{name}(PI_{name})".format(name=name) for name, _ in primary_inputs), file=f) + print(" %s UUT (" % escape_identifier(vlogtb_topmod), file=f) + print(",\n".join(" .%s(%s)" % (escape_identifier(name), escape_identifier("PI_"+name)) for name, _ in primary_inputs), file=f) print(" );", file=f) print("`ifndef VERILATOR", file=f) @@ -893,14 +911,14 @@ def write_vlogtb_trace(steps_start, steps_stop, index): for n in reg: if n.startswith("$"): hidden_net = True - print(" %sUUT.%s = %d'b%s;" % ("// " if hidden_net else "", ".".join(reg), len(val), val), file=f) + print(" %sUUT.%s = %d'b%s;" % ("// " if hidden_net else "", ".".join(escape_identifier(reg)), len(val), val), file=f) anyconsts = sorted(smt.hieranyconsts(vlogtb_topmod)) for info in anyconsts: if info[3] is not None: modstate = smt.net_expr(vlogtb_topmod, vlogtb_state.replace("@@step_idx@@", str(steps_start)), info[0]) value = smt.bv2bin(smt.get("(|%s| %s)" % (info[1], modstate))) - print(" UUT.%s = %d'b%s;" % (".".join(info[0] + [info[3]]), len(value), value), file=f); + print(" UUT.%s = %d'b%s;" % (".".join(escape_identifier(info[0] + [info[3]])), len(value), value), file=f); mems = sorted(smt.hiermems(vlogtb_topmod)) for mempath in mems: @@ -924,7 +942,7 @@ def write_vlogtb_trace(steps_start, steps_stop, index): addr_data[addr] = data for addr, data in addr_data.items(): - print(" UUT.%s[%d'b%s] = %d'b%s;" % (".".join(mempath), len(addr), addr, len(data), data), file=f) + print(" UUT.%s[%d'b%s] = %d'b%s;" % (".".join(escape_identifier(mempath)), len(addr), addr, len(data), data), file=f) print("", file=f) anyseqs = sorted(smt.hieranyseqs(vlogtb_topmod)) @@ -940,18 +958,18 @@ def write_vlogtb_trace(steps_start, steps_stop, index): for name, val in zip(pi_names, pi_values): if i > 0: - print(" PI_%s <= %d'b%s;" % (".".join(name), len(val), val), file=f) + print(" %s <= %d'b%s;" % (escape_identifier("PI_"+".".join(name)), len(val), val), file=f) else: - print(" PI_%s = %d'b%s;" % (".".join(name), len(val), val), file=f) + print(" %s = %d'b%s;" % (escape_identifier("PI_"+".".join(name)), len(val), val), file=f) for info in anyseqs: if info[3] is not None: modstate = smt.net_expr(vlogtb_topmod, vlogtb_state.replace("@@step_idx@@", str(i)), info[0]) value = smt.bv2bin(smt.get("(|%s| %s)" % (info[1], modstate))) if i > 0: - print(" UUT.%s <= %d'b%s;" % (".".join(info[0] + [info[3]]), len(value), value), file=f); + print(" UUT.%s <= %d'b%s;" % (".".join(escape_identifier(info[0] + [info[3]])), len(value), value), file=f); else: - print(" UUT.%s = %d'b%s;" % (".".join(info[0] + [info[3]]), len(value), value), file=f); + print(" UUT.%s = %d'b%s;" % (".".join(escape_identifier(info[0] + [info[3]])), len(value), value), file=f); if i > 0: print(" end", file=f) @@ -1275,10 +1293,10 @@ def smt_pop(): asserts_consequent_cache.pop() smt.write("(pop 1)") -def smt_check_sat(): +def smt_check_sat(expected=["sat", "unsat"]): if asserts_cache_dirty: smt_forall_assert() - return smt.check_sat() + return smt.check_sat(expected=expected) if tempind: retstatus = "FAILED" diff --git a/backends/smt2/smtio.py b/backends/smt2/smtio.py index 72ab39d39..516091011 100644 --- a/backends/smt2/smtio.py +++ b/backends/smt2/smtio.py @@ -124,6 +124,7 @@ class SmtIo: self.timeout = 0 self.produce_models = True self.smt2cache = [list()] + self.smt2_options = dict() self.p = None self.p_index = solvers_index solvers_index += 1 @@ -258,14 +259,24 @@ class SmtIo: for stmt in self.info_stmts: self.write(stmt) - if self.forall and self.solver == "yices": - self.write("(set-option :yices-ef-max-iters 1000000000)") - if self.produce_models: self.write("(set-option :produce-models true)") + #See the SMT-LIB Standard, Section 4.1.7 + modestart_options = [":global-declarations", ":interactive-mode", ":produce-assertions", ":produce-assignments", ":produce-models", ":produce-proofs", ":produce-unsat-assumptions", ":produce-unsat-cores", ":random-seed"] + for key, val in self.smt2_options.items(): + if key in modestart_options: + self.write("(set-option {} {})".format(key, val)) + self.write("(set-logic %s)" % self.logic) + if self.forall and self.solver == "yices": + self.write("(set-option :yices-ef-max-iters 1000000000)") + + for key, val in self.smt2_options.items(): + if key not in modestart_options: + self.write("(set-option {} {})".format(key, val)) + def timestamp(self): secs = int(time() - self.start_time) return "## %3d:%02d:%02d " % (secs // (60*60), (secs // 60) % 60, secs % 60) @@ -468,6 +479,9 @@ class SmtIo: fields = stmt.split() + if fields[1] == "yosys-smt2-solver-option": + self.smt2_options[fields[2]] = fields[3] + if fields[1] == "yosys-smt2-nomem": if self.logic is None: self.logic_ax = False @@ -653,7 +667,7 @@ class SmtIo: return stmt - def check_sat(self): + def check_sat(self, expected=["sat", "unsat", "unknown", "timeout", "interrupted"]): if self.debug_print: print("> (check-sat)") if self.debug_file and not self.nocomments: @@ -740,7 +754,7 @@ class SmtIo: print("(check-sat)", file=self.debug_file) self.debug_file.flush() - if result not in ["sat", "unsat", "unknown", "timeout", "interrupted"]: + if result not in expected: if result == "": print("%s Unexpected EOF response from solver." % (self.timestamp()), flush=True) else: diff --git a/backends/smv/.gitignore b/backends/smv/.gitignore new file mode 100644 index 000000000..d23d492d7 --- /dev/null +++ b/backends/smv/.gitignore @@ -0,0 +1 @@ +/test_cells.tmp/ diff --git a/backends/smv/smv.cc b/backends/smv/smv.cc index 2fc7099f4..4e5c6050d 100644 --- a/backends/smv/smv.cc +++ b/backends/smv/smv.cc @@ -702,7 +702,7 @@ struct SmvWorker struct SmvBackend : public Backend { SmvBackend() : Backend("smv", "write design to SMV file") { } - void help() YS_OVERRIDE + void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); @@ -720,7 +720,7 @@ struct SmvBackend : public Backend { log("THIS COMMAND IS UNDER CONSTRUCTION\n"); log("\n"); } - void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE + void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override { std::ifstream template_f; bool verbose = false; diff --git a/backends/spice/spice.cc b/backends/spice/spice.cc index 84e93b61b..aa20f106a 100644 --- a/backends/spice/spice.cc +++ b/backends/spice/spice.cc @@ -130,7 +130,7 @@ static void print_spice_module(std::ostream &f, RTLIL::Module *module, RTLIL::De struct SpiceBackend : public Backend { SpiceBackend() : Backend("spice", "write design to SPICE netlist file") { } - void help() YS_OVERRIDE + void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); @@ -159,7 +159,7 @@ struct SpiceBackend : public Backend { log(" set the specified module as design top module\n"); log("\n"); } - void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE + void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override { std::string top_module_name; RTLIL::Module *top_module = NULL; diff --git a/backends/table/table.cc b/backends/table/table.cc index 796f18059..77642ccbd 100644 --- a/backends/table/table.cc +++ b/backends/table/table.cc @@ -29,7 +29,7 @@ PRIVATE_NAMESPACE_BEGIN struct TableBackend : public Backend { TableBackend() : Backend("table", "write design as connectivity table") { } - void help() YS_OVERRIDE + void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); @@ -48,7 +48,7 @@ struct TableBackend : public Backend { log("module inputs and outputs are output using cell type and port '-' and with\n"); log("'pi' (primary input) or 'po' (primary output) or 'pio' as direction.\n"); } - void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE + void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override { log_header(design, "Executing TABLE backend.\n"); diff --git a/backends/verilog/verilog_backend.cc b/backends/verilog/verilog_backend.cc index 4f44a053a..9523f4a52 100644 --- a/backends/verilog/verilog_backend.cc +++ b/backends/verilog/verilog_backend.cc @@ -25,6 +25,8 @@ #include "kernel/celltypes.h" #include "kernel/log.h" #include "kernel/sigtools.h" +#include "kernel/ff.h" +#include "kernel/mem.h" #include <string> #include <sstream> #include <set> @@ -33,10 +35,10 @@ USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN -bool verbose, norename, noattr, attr2comment, noexpr, nodec, nohex, nostr, extmem, defparam, decimal, siminit; +bool verbose, norename, noattr, attr2comment, noexpr, nodec, nohex, nostr, extmem, defparam, decimal, siminit, systemverilog; int auto_name_counter, auto_name_offset, auto_name_digits, extmem_counter; std::map<RTLIL::IdString, int> auto_name_map; -std::set<RTLIL::IdString> reg_wires, reg_ct; +std::set<RTLIL::IdString> reg_wires; std::string auto_prefix, extmem_prefix; RTLIL::Module *active_module; @@ -433,10 +435,250 @@ void dump_wire(std::ostream &f, std::string indent, RTLIL::Wire *wire) #endif } -void dump_memory(std::ostream &f, std::string indent, RTLIL::Memory *memory) +void dump_memory(std::ostream &f, std::string indent, Mem &mem) { - dump_attributes(f, indent, memory->attributes); - f << stringf("%s" "reg [%d:0] %s [%d:%d];\n", indent.c_str(), memory->width-1, id(memory->name).c_str(), memory->size+memory->start_offset-1, memory->start_offset); + std::string mem_id = id(mem.memid); + + dump_attributes(f, indent, mem.attributes); + f << stringf("%s" "reg [%d:0] %s [%d:%d];\n", indent.c_str(), mem.width-1, mem_id.c_str(), mem.size+mem.start_offset-1, mem.start_offset); + + // for memory block make something like: + // reg [7:0] memid [3:0]; + // initial begin + // memid[0] = ... + // end + if (!mem.inits.empty()) + { + if (extmem) + { + std::string extmem_filename = stringf("%s-%d.mem", extmem_prefix.c_str(), extmem_counter++); + + std::string extmem_filename_esc; + for (auto c : extmem_filename) + { + if (c == '\n') + extmem_filename_esc += "\\n"; + else if (c == '\t') + extmem_filename_esc += "\\t"; + else if (c < 32) + extmem_filename_esc += stringf("\\%03o", c); + else if (c == '"') + extmem_filename_esc += "\\\""; + else if (c == '\\') + extmem_filename_esc += "\\\\"; + else + extmem_filename_esc += c; + } + f << stringf("%s" "initial $readmemb(\"%s\", %s);\n", indent.c_str(), extmem_filename_esc.c_str(), mem_id.c_str()); + + std::ofstream extmem_f(extmem_filename, std::ofstream::trunc); + if (extmem_f.fail()) + log_error("Can't open file `%s' for writing: %s\n", extmem_filename.c_str(), strerror(errno)); + else + { + Const data = mem.get_init_data(); + for (int i=0; i<mem.size; i++) + { + RTLIL::Const element = data.extract(i*mem.width, mem.width); + for (int j=0; j<element.size(); j++) + { + switch (element[element.size()-j-1]) + { + case State::S0: extmem_f << '0'; break; + case State::S1: extmem_f << '1'; break; + case State::Sx: extmem_f << 'x'; break; + case State::Sz: extmem_f << 'z'; break; + case State::Sa: extmem_f << '_'; break; + case State::Sm: log_error("Found marker state in final netlist."); + } + } + extmem_f << '\n'; + } + } + } + else + { + f << stringf("%s" "initial begin\n", indent.c_str()); + for (auto &init : mem.inits) { + int words = GetSize(init.data) / mem.width; + int start = init.addr.as_int(); + for (int i=0; i<words; i++) + { + f << stringf("%s" " %s[%d] = ", indent.c_str(), mem_id.c_str(), i + start); + dump_const(f, init.data.extract(i*mem.width, mem.width)); + f << stringf(";\n"); + } + } + f << stringf("%s" "end\n", indent.c_str()); + } + } + + // create a map : "edge clk" -> expressions within that clock domain + dict<std::string, std::vector<std::string>> clk_to_lof_body; + clk_to_lof_body[""] = std::vector<std::string>(); + std::string clk_domain_str; + // create a list of reg declarations + std::vector<std::string> lof_reg_declarations; + + // read ports + for (auto &port : mem.rd_ports) + { + if (port.clk_enable) + { + { + std::ostringstream os; + dump_sigspec(os, port.clk); + clk_domain_str = stringf("%sedge %s", port.clk_polarity ? "pos" : "neg", os.str().c_str()); + if( clk_to_lof_body.count(clk_domain_str) == 0 ) + clk_to_lof_body[clk_domain_str] = std::vector<std::string>(); + } + if (!port.transparent) + { + // for clocked read ports make something like: + // reg [..] temp_id; + // always @(posedge clk) + // if (rd_en) temp_id <= array_reg[r_addr]; + // assign r_data = temp_id; + std::string temp_id = next_auto_id(); + lof_reg_declarations.push_back( stringf("reg [%d:0] %s;\n", port.data.size() - 1, temp_id.c_str()) ); + { + std::ostringstream os; + if (port.en != RTLIL::SigBit(true)) + { + os << stringf("if ("); + dump_sigspec(os, port.en); + os << stringf(") "); + } + os << stringf("%s <= %s[", temp_id.c_str(), mem_id.c_str()); + dump_sigspec(os, port.addr); + os << stringf("];\n"); + clk_to_lof_body[clk_domain_str].push_back(os.str()); + } + { + std::ostringstream os; + dump_sigspec(os, port.data); + std::string line = stringf("assign %s = %s;\n", os.str().c_str(), temp_id.c_str()); + clk_to_lof_body[""].push_back(line); + } + } + else + { + // for rd-transparent read-ports make something like: + // reg [..] temp_id; + // always @(posedge clk) + // temp_id <= r_addr; + // assign r_data = array_reg[temp_id]; + std::string temp_id = next_auto_id(); + lof_reg_declarations.push_back( stringf("reg [%d:0] %s;\n", port.addr.size() - 1, temp_id.c_str()) ); + { + std::ostringstream os; + dump_sigspec(os, port.addr); + std::string line = stringf("%s <= %s;\n", temp_id.c_str(), os.str().c_str()); + clk_to_lof_body[clk_domain_str].push_back(line); + } + { + std::ostringstream os; + dump_sigspec(os, port.data); + std::string line = stringf("assign %s = %s[%s];\n", os.str().c_str(), mem_id.c_str(), temp_id.c_str()); + clk_to_lof_body[""].push_back(line); + } + } + } else { + // for non-clocked read-ports make something like: + // assign r_data = array_reg[r_addr]; + std::ostringstream os, os2; + dump_sigspec(os, port.data); + dump_sigspec(os2, port.addr); + std::string line = stringf("assign %s = %s[%s];\n", os.str().c_str(), mem_id.c_str(), os2.str().c_str()); + clk_to_lof_body[""].push_back(line); + } + } + + // write ports + for (auto &port : mem.wr_ports) + { + { + std::ostringstream os; + dump_sigspec(os, port.clk); + clk_domain_str = stringf("%sedge %s", port.clk_polarity ? "pos" : "neg", os.str().c_str()); + if( clk_to_lof_body.count(clk_domain_str) == 0 ) + clk_to_lof_body[clk_domain_str] = std::vector<std::string>(); + } + // make something like: + // always @(posedge clk) + // if (wr_en_bit) memid[w_addr][??] <= w_data[??]; + // ... + for (int i = 0; i < GetSize(port.en); i++) + { + int start_i = i, width = 1; + SigBit wen_bit = port.en[i]; + + while (i+1 < GetSize(port.en) && active_sigmap(port.en[i+1]) == active_sigmap(wen_bit)) + i++, width++; + + if (wen_bit == State::S0) + continue; + + std::ostringstream os; + if (wen_bit != State::S1) + { + os << stringf("if ("); + dump_sigspec(os, wen_bit); + os << stringf(") "); + } + os << stringf("%s[", mem_id.c_str()); + dump_sigspec(os, port.addr); + if (width == GetSize(port.en)) + os << stringf("] <= "); + else + os << stringf("][%d:%d] <= ", i, start_i); + dump_sigspec(os, port.data.extract(start_i, width)); + os << stringf(";\n"); + clk_to_lof_body[clk_domain_str].push_back(os.str()); + } + } + // Output Verilog that looks something like this: + // reg [..] _3_; + // always @(posedge CLK2) begin + // _3_ <= memory[D1ADDR]; + // if (A1EN) + // memory[A1ADDR] <= A1DATA; + // if (A2EN) + // memory[A2ADDR] <= A2DATA; + // ... + // end + // always @(negedge CLK1) begin + // if (C1EN) + // memory[C1ADDR] <= C1DATA; + // end + // ... + // assign D1DATA = _3_; + // assign D2DATA <= memory[D2ADDR]; + + // the reg ... definitions + for(auto ® : lof_reg_declarations) + { + f << stringf("%s" "%s", indent.c_str(), reg.c_str()); + } + // the block of expressions by clock domain + for(auto &pair : clk_to_lof_body) + { + std::string clk_domain = pair.first; + std::vector<std::string> lof_lines = pair.second; + if( clk_domain != "") + { + f << stringf("%s" "always%s @(%s) begin\n", indent.c_str(), systemverilog ? "_ff" : "", clk_domain.c_str()); + for(auto &line : lof_lines) + f << stringf("%s%s" "%s", indent.c_str(), indent.c_str(), line.c_str()); + f << stringf("%s" "end\n", indent.c_str()); + } + else + { + // the non-clocked assignments + for(auto &line : lof_lines) + f << stringf("%s" "%s", indent.c_str(), line.c_str()); + } + } } void dump_cell_expr_port(std::ostream &f, RTLIL::Cell *cell, std::string port, bool gen_signed = true) @@ -451,7 +693,7 @@ void dump_cell_expr_port(std::ostream &f, RTLIL::Cell *cell, std::string port, b std::string cellname(RTLIL::Cell *cell) { - if (!norename && cell->name[0] == '$' && reg_ct.count(cell->type) && cell->hasPort(ID::Q)) + if (!norename && cell->name[0] == '$' && RTLIL::builtin_ff_cell_types().count(cell->type) && cell->hasPort(ID::Q) && !cell->type.in(ID($ff), ID($_FF_))) { RTLIL::SigSpec sig = cell->getPort(ID::Q); if (GetSize(sig) != 1 || sig.is_fully_const()) @@ -605,93 +847,6 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) return true; } - if (cell->type.begins_with("$_DFF_")) - { - std::string reg_name = cellname(cell); - bool out_is_reg_wire = is_reg_wire(cell->getPort(ID::Q), reg_name); - - if (!out_is_reg_wire) { - f << stringf("%s" "reg %s", indent.c_str(), reg_name.c_str()); - dump_reg_init(f, cell->getPort(ID::Q)); - f << ";\n"; - } - - dump_attributes(f, indent, cell->attributes); - f << stringf("%s" "always @(%sedge ", indent.c_str(), cell->type[6] == 'P' ? "pos" : "neg"); - dump_sigspec(f, cell->getPort(ID::C)); - if (cell->type[7] != '_') { - f << stringf(" or %sedge ", cell->type[7] == 'P' ? "pos" : "neg"); - dump_sigspec(f, cell->getPort(ID::R)); - } - f << stringf(")\n"); - - if (cell->type[7] != '_') { - f << stringf("%s" " if (%s", indent.c_str(), cell->type[7] == 'P' ? "" : "!"); - dump_sigspec(f, cell->getPort(ID::R)); - f << stringf(")\n"); - f << stringf("%s" " %s <= %c;\n", indent.c_str(), reg_name.c_str(), cell->type[8]); - f << stringf("%s" " else\n", indent.c_str()); - } - - f << stringf("%s" " %s <= ", indent.c_str(), reg_name.c_str()); - dump_cell_expr_port(f, cell, "D", false); - f << stringf(";\n"); - - if (!out_is_reg_wire) { - f << stringf("%s" "assign ", indent.c_str()); - dump_sigspec(f, cell->getPort(ID::Q)); - f << stringf(" = %s;\n", reg_name.c_str()); - } - - return true; - } - - if (cell->type.begins_with("$_DFFSR_")) - { - char pol_c = cell->type[8], pol_s = cell->type[9], pol_r = cell->type[10]; - - std::string reg_name = cellname(cell); - bool out_is_reg_wire = is_reg_wire(cell->getPort(ID::Q), reg_name); - - if (!out_is_reg_wire) { - f << stringf("%s" "reg %s", indent.c_str(), reg_name.c_str()); - dump_reg_init(f, cell->getPort(ID::Q)); - f << ";\n"; - } - - dump_attributes(f, indent, cell->attributes); - f << stringf("%s" "always @(%sedge ", indent.c_str(), pol_c == 'P' ? "pos" : "neg"); - dump_sigspec(f, cell->getPort(ID::C)); - f << stringf(" or %sedge ", pol_s == 'P' ? "pos" : "neg"); - dump_sigspec(f, cell->getPort(ID::S)); - f << stringf(" or %sedge ", pol_r == 'P' ? "pos" : "neg"); - dump_sigspec(f, cell->getPort(ID::R)); - f << stringf(")\n"); - - f << stringf("%s" " if (%s", indent.c_str(), pol_r == 'P' ? "" : "!"); - dump_sigspec(f, cell->getPort(ID::R)); - f << stringf(")\n"); - f << stringf("%s" " %s <= 0;\n", indent.c_str(), reg_name.c_str()); - - f << stringf("%s" " else if (%s", indent.c_str(), pol_s == 'P' ? "" : "!"); - dump_sigspec(f, cell->getPort(ID::S)); - f << stringf(")\n"); - f << stringf("%s" " %s <= 1;\n", indent.c_str(), reg_name.c_str()); - - f << stringf("%s" " else\n", indent.c_str()); - f << stringf("%s" " %s <= ", indent.c_str(), reg_name.c_str()); - dump_cell_expr_port(f, cell, "D", false); - f << stringf(";\n"); - - if (!out_is_reg_wire) { - f << stringf("%s" "assign ", indent.c_str()); - dump_sigspec(f, cell->getPort(ID::Q)); - f << stringf(" = %s;\n", reg_name.c_str()); - } - - return true; - } - #define HANDLE_UNIOP(_type, _operator) \ if (cell->type ==_type) { dump_cell_expr_uniop(f, indent, cell, _operator); return true; } #define HANDLE_BINOP(_type, _operator) \ @@ -836,21 +991,19 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) f << stringf(" = "); if (cell->getParam(ID::B_SIGNED).as_bool()) { - f << stringf("$signed("); - dump_sigspec(f, cell->getPort(ID::B)); - f << stringf(")"); + dump_cell_expr_port(f, cell, "B", true); f << stringf(" < 0 ? "); - dump_sigspec(f, cell->getPort(ID::A)); + dump_cell_expr_port(f, cell, "A", true); f << stringf(" << - "); dump_sigspec(f, cell->getPort(ID::B)); f << stringf(" : "); - dump_sigspec(f, cell->getPort(ID::A)); + dump_cell_expr_port(f, cell, "A", true); f << stringf(" >> "); dump_sigspec(f, cell->getPort(ID::B)); } else { - dump_sigspec(f, cell->getPort(ID::A)); + dump_cell_expr_port(f, cell, "A", true); f << stringf(" >> "); dump_sigspec(f, cell->getPort(ID::B)); } @@ -986,423 +1139,198 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) return true; } - if (cell->type == ID($dffsr)) + if (RTLIL::builtin_ff_cell_types().count(cell->type)) { - SigSpec sig_clk = cell->getPort(ID::CLK); - SigSpec sig_set = cell->getPort(ID::SET); - SigSpec sig_clr = cell->getPort(ID::CLR); - SigSpec sig_d = cell->getPort(ID::D); - SigSpec sig_q = cell->getPort(ID::Q); + FfData ff(nullptr, cell); - int width = cell->parameters[ID::WIDTH].as_int(); - bool pol_clk = cell->parameters[ID::CLK_POLARITY].as_bool(); - bool pol_set = cell->parameters[ID::SET_POLARITY].as_bool(); - bool pol_clr = cell->parameters[ID::CLR_POLARITY].as_bool(); + // $ff / $_FF_ cell: not supported. + if (ff.has_d && !ff.has_clk && !ff.has_en) + return false; std::string reg_name = cellname(cell); - bool out_is_reg_wire = is_reg_wire(sig_q, reg_name); + bool out_is_reg_wire = is_reg_wire(ff.sig_q, reg_name); if (!out_is_reg_wire) { - f << stringf("%s" "reg [%d:0] %s", indent.c_str(), width-1, reg_name.c_str()); - dump_reg_init(f, sig_q); - f << ";\n"; - } - - for (int i = 0; i < width; i++) { - f << stringf("%s" "always @(%sedge ", indent.c_str(), pol_clk ? "pos" : "neg"); - dump_sigspec(f, sig_clk); - f << stringf(", %sedge ", pol_set ? "pos" : "neg"); - dump_sigspec(f, sig_set); - f << stringf(", %sedge ", pol_clr ? "pos" : "neg"); - dump_sigspec(f, sig_clr); - f << stringf(")\n"); - - f << stringf("%s" " if (%s", indent.c_str(), pol_clr ? "" : "!"); - dump_sigspec(f, sig_clr); - f << stringf(") %s[%d] <= 1'b0;\n", reg_name.c_str(), i); - - f << stringf("%s" " else if (%s", indent.c_str(), pol_set ? "" : "!"); - dump_sigspec(f, sig_set); - f << stringf(") %s[%d] <= 1'b1;\n", reg_name.c_str(), i); - - f << stringf("%s" " else %s[%d] <= ", indent.c_str(), reg_name.c_str(), i); - dump_sigspec(f, sig_d[i]); - f << stringf(";\n"); - } - - if (!out_is_reg_wire) { - f << stringf("%s" "assign ", indent.c_str()); - dump_sigspec(f, sig_q); - f << stringf(" = %s;\n", reg_name.c_str()); - } - - return true; - } - - if (cell->type.in(ID($dff), ID($adff), ID($dffe))) - { - RTLIL::SigSpec sig_clk, sig_arst, sig_en, val_arst; - bool pol_clk, pol_arst = false, pol_en = false; - - sig_clk = cell->getPort(ID::CLK); - pol_clk = cell->parameters[ID::CLK_POLARITY].as_bool(); - - if (cell->type == ID($adff)) { - sig_arst = cell->getPort(ID::ARST); - pol_arst = cell->parameters[ID::ARST_POLARITY].as_bool(); - val_arst = RTLIL::SigSpec(cell->parameters[ID::ARST_VALUE]); - } - - if (cell->type == ID($dffe)) { - sig_en = cell->getPort(ID::EN); - pol_en = cell->parameters[ID::EN_POLARITY].as_bool(); - } - - std::string reg_name = cellname(cell); - bool out_is_reg_wire = is_reg_wire(cell->getPort(ID::Q), reg_name); - - if (!out_is_reg_wire) { - f << stringf("%s" "reg [%d:0] %s", indent.c_str(), cell->parameters[ID::WIDTH].as_int()-1, reg_name.c_str()); - dump_reg_init(f, cell->getPort(ID::Q)); - f << ";\n"; - } - - f << stringf("%s" "always @(%sedge ", indent.c_str(), pol_clk ? "pos" : "neg"); - dump_sigspec(f, sig_clk); - if (cell->type == ID($adff)) { - f << stringf(" or %sedge ", pol_arst ? "pos" : "neg"); - dump_sigspec(f, sig_arst); - } - f << stringf(")\n"); - - if (cell->type == ID($adff)) { - f << stringf("%s" " if (%s", indent.c_str(), pol_arst ? "" : "!"); - dump_sigspec(f, sig_arst); - f << stringf(")\n"); - f << stringf("%s" " %s <= ", indent.c_str(), reg_name.c_str()); - dump_sigspec(f, val_arst); - f << stringf(";\n"); - f << stringf("%s" " else\n", indent.c_str()); - } - - if (cell->type == ID($dffe)) { - f << stringf("%s" " if (%s", indent.c_str(), pol_en ? "" : "!"); - dump_sigspec(f, sig_en); - f << stringf(")\n"); - } - - f << stringf("%s" " %s <= ", indent.c_str(), reg_name.c_str()); - dump_cell_expr_port(f, cell, "D", false); - f << stringf(";\n"); - - if (!out_is_reg_wire) { - f << stringf("%s" "assign ", indent.c_str()); - dump_sigspec(f, cell->getPort(ID::Q)); - f << stringf(" = %s;\n", reg_name.c_str()); - } - - return true; - } - - if (cell->type == ID($dlatch)) - { - RTLIL::SigSpec sig_en; - bool pol_en = false; - - sig_en = cell->getPort(ID::EN); - pol_en = cell->parameters[ID::EN_POLARITY].as_bool(); - - std::string reg_name = cellname(cell); - bool out_is_reg_wire = is_reg_wire(cell->getPort(ID::Q), reg_name); - - if (!out_is_reg_wire) { - f << stringf("%s" "reg [%d:0] %s", indent.c_str(), cell->parameters[ID::WIDTH].as_int()-1, reg_name.c_str()); - dump_reg_init(f, cell->getPort(ID::Q)); + if (ff.width == 1) + f << stringf("%s" "reg %s", indent.c_str(), reg_name.c_str()); + else + f << stringf("%s" "reg [%d:0] %s", indent.c_str(), ff.width-1, reg_name.c_str()); + dump_reg_init(f, ff.sig_q); f << ";\n"; } - f << stringf("%s" "always @*\n", indent.c_str()); - - f << stringf("%s" " if (%s", indent.c_str(), pol_en ? "" : "!"); - dump_sigspec(f, sig_en); - f << stringf(")\n"); - - f << stringf("%s" " %s = ", indent.c_str(), reg_name.c_str()); - dump_cell_expr_port(f, cell, "D", false); - f << stringf(";\n"); - - if (!out_is_reg_wire) { - f << stringf("%s" "assign ", indent.c_str()); - dump_sigspec(f, cell->getPort(ID::Q)); - f << stringf(" = %s;\n", reg_name.c_str()); - } + // If the FF has CLR/SET inputs, emit every bit slice separately. + int chunks = ff.has_sr ? ff.width : 1; + bool chunky = ff.has_sr && ff.width != 1; - return true; - } - - if (cell->type == ID($mem)) - { - RTLIL::IdString memid = cell->parameters[ID::MEMID].decode_string(); - std::string mem_id = id(cell->parameters[ID::MEMID].decode_string()); - int abits = cell->parameters[ID::ABITS].as_int(); - int size = cell->parameters[ID::SIZE].as_int(); - int offset = cell->parameters[ID::OFFSET].as_int(); - int width = cell->parameters[ID::WIDTH].as_int(); - bool use_init = !(RTLIL::SigSpec(cell->parameters[ID::INIT]).is_fully_undef()); - - // for memory block make something like: - // reg [7:0] memid [3:0]; - // initial begin - // memid[0] = ... - // end - dump_attributes(f, indent.c_str(), cell->attributes); - f << stringf("%s" "reg [%d:%d] %s [%d:%d];\n", indent.c_str(), width-1, 0, mem_id.c_str(), size+offset-1, offset); - if (use_init) + for (int i = 0; i < chunks; i++) { - if (extmem) - { - std::string extmem_filename = stringf("%s-%d.mem", extmem_prefix.c_str(), extmem_counter++); - - std::string extmem_filename_esc; - for (auto c : extmem_filename) + SigSpec sig_d; + Const val_arst, val_srst; + std::string reg_bit_name, sig_set_name, sig_clr_name, sig_arst_name; + if (chunky) { + reg_bit_name = stringf("%s[%d]", reg_name.c_str(), i); + if (ff.has_d) + sig_d = ff.sig_d[i]; + } else { + reg_bit_name = reg_name; + if (ff.has_d) + sig_d = ff.sig_d; + } + if (ff.has_arst) + val_arst = chunky ? ff.val_arst[i] : ff.val_arst; + if (ff.has_srst) + val_srst = chunky ? ff.val_srst[i] : ff.val_srst; + + // If there are constants in the sensitivity list, replace them with an intermediate wire + if (ff.has_sr) { + if (ff.sig_set[i].wire == NULL) { - if (c == '\n') - extmem_filename_esc += "\\n"; - else if (c == '\t') - extmem_filename_esc += "\\t"; - else if (c < 32) - extmem_filename_esc += stringf("\\%03o", c); - else if (c == '"') - extmem_filename_esc += "\\\""; - else if (c == '\\') - extmem_filename_esc += "\\\\"; - else - extmem_filename_esc += c; + sig_set_name = next_auto_id(); + f << stringf("%s" "wire %s = ", indent.c_str(), sig_set_name.c_str()); + dump_const(f, ff.sig_set[i].data); + f << stringf(";\n"); } - f << stringf("%s" "initial $readmemb(\"%s\", %s);\n", indent.c_str(), extmem_filename_esc.c_str(), mem_id.c_str()); - - std::ofstream extmem_f(extmem_filename, std::ofstream::trunc); - if (extmem_f.fail()) - log_error("Can't open file `%s' for writing: %s\n", extmem_filename.c_str(), strerror(errno)); - else + if (ff.sig_clr[i].wire == NULL) { - for (int i=0; i<size; i++) - { - RTLIL::Const element = cell->parameters[ID::INIT].extract(i*width, width); - for (int j=0; j<element.size(); j++) - { - switch (element[element.size()-j-1]) - { - case State::S0: extmem_f << '0'; break; - case State::S1: extmem_f << '1'; break; - case State::Sx: extmem_f << 'x'; break; - case State::Sz: extmem_f << 'z'; break; - case State::Sa: extmem_f << '_'; break; - case State::Sm: log_error("Found marker state in final netlist."); - } - } - extmem_f << '\n'; - } + sig_clr_name = next_auto_id(); + f << stringf("%s" "wire %s = ", indent.c_str(), sig_clr_name.c_str()); + dump_const(f, ff.sig_clr[i].data); + f << stringf(";\n"); } - - } - else - { - f << stringf("%s" "initial begin\n", indent.c_str()); - for (int i=0; i<size; i++) + } else if (ff.has_arst) { + if (ff.sig_arst[i].wire == NULL) { - f << stringf("%s" " %s[%d] = ", indent.c_str(), mem_id.c_str(), i); - dump_const(f, cell->parameters[ID::INIT].extract(i*width, width)); + sig_arst_name = next_auto_id(); + f << stringf("%s" "wire %s = ", indent.c_str(), sig_arst_name.c_str()); + dump_const(f, ff.sig_arst[i].data); f << stringf(";\n"); } - f << stringf("%s" "end\n", indent.c_str()); } - } - // create a map : "edge clk" -> expressions within that clock domain - dict<std::string, std::vector<std::string>> clk_to_lof_body; - clk_to_lof_body[""] = std::vector<std::string>(); - std::string clk_domain_str; - // create a list of reg declarations - std::vector<std::string> lof_reg_declarations; - - int nread_ports = cell->parameters[ID::RD_PORTS].as_int(); - RTLIL::SigSpec sig_rd_clk, sig_rd_en, sig_rd_data, sig_rd_addr; - bool use_rd_clk, rd_clk_posedge, rd_transparent; - // read ports - for (int i=0; i < nread_ports; i++) - { - sig_rd_clk = cell->getPort(ID::RD_CLK).extract(i); - sig_rd_en = cell->getPort(ID::RD_EN).extract(i); - sig_rd_data = cell->getPort(ID::RD_DATA).extract(i*width, width); - sig_rd_addr = cell->getPort(ID::RD_ADDR).extract(i*abits, abits); - use_rd_clk = cell->parameters[ID::RD_CLK_ENABLE].extract(i).as_bool(); - rd_clk_posedge = cell->parameters[ID::RD_CLK_POLARITY].extract(i).as_bool(); - rd_transparent = cell->parameters[ID::RD_TRANSPARENT].extract(i).as_bool(); - if (use_rd_clk) + dump_attributes(f, indent, cell->attributes); + if (ff.has_clk) { - { - std::ostringstream os; - dump_sigspec(os, sig_rd_clk); - clk_domain_str = stringf("%sedge %s", rd_clk_posedge ? "pos" : "neg", os.str().c_str()); - if( clk_to_lof_body.count(clk_domain_str) == 0 ) - clk_to_lof_body[clk_domain_str] = std::vector<std::string>(); + // FFs. + f << stringf("%s" "always%s @(%sedge ", indent.c_str(), systemverilog ? "_ff" : "", ff.pol_clk ? "pos" : "neg"); + dump_sigspec(f, ff.sig_clk); + if (ff.has_sr) { + f << stringf(", %sedge ", ff.pol_set ? "pos" : "neg"); + if (ff.sig_set[i].wire == NULL) + f << stringf("%s", sig_set_name.c_str()); + else + dump_sigspec(f, ff.sig_set[i]); + + f << stringf(", %sedge ", ff.pol_clr ? "pos" : "neg"); + if (ff.sig_clr[i].wire == NULL) + f << stringf("%s", sig_clr_name.c_str()); + else + dump_sigspec(f, ff.sig_clr[i]); + + } else if (ff.has_arst) { + f << stringf(", %sedge ", ff.pol_arst ? "pos" : "neg"); + if (ff.sig_arst[i].wire == NULL) + f << stringf("%s", sig_arst_name.c_str()); + else + dump_sigspec(f, ff.sig_arst); } - if (!rd_transparent) - { - // for clocked read ports make something like: - // reg [..] temp_id; - // always @(posedge clk) - // if (rd_en) temp_id <= array_reg[r_addr]; - // assign r_data = temp_id; - std::string temp_id = next_auto_id(); - lof_reg_declarations.push_back( stringf("reg [%d:0] %s;\n", sig_rd_data.size() - 1, temp_id.c_str()) ); - { - std::ostringstream os; - if (sig_rd_en != RTLIL::SigBit(true)) - { - os << stringf("if ("); - dump_sigspec(os, sig_rd_en); - os << stringf(") "); - } - os << stringf("%s <= %s[", temp_id.c_str(), mem_id.c_str()); - dump_sigspec(os, sig_rd_addr); - os << stringf("];\n"); - clk_to_lof_body[clk_domain_str].push_back(os.str()); - } - { - std::ostringstream os; - dump_sigspec(os, sig_rd_data); - std::string line = stringf("assign %s = %s;\n", os.str().c_str(), temp_id.c_str()); - clk_to_lof_body[""].push_back(line); - } + f << stringf(")\n"); + + f << stringf("%s" " ", indent.c_str()); + if (ff.has_sr) { + f << stringf("if (%s", ff.pol_clr ? "" : "!"); + if (ff.sig_clr[i].wire == NULL) + f << stringf("%s", sig_clr_name.c_str()); + else + dump_sigspec(f, ff.sig_clr[i]); + f << stringf(") %s <= 1'b0;\n", reg_bit_name.c_str()); + f << stringf("%s" " else if (%s", indent.c_str(), ff.pol_set ? "" : "!"); + if (ff.sig_set[i].wire == NULL) + f << stringf("%s", sig_set_name.c_str()); + else + dump_sigspec(f, ff.sig_set[i]); + f << stringf(") %s <= 1'b1;\n", reg_bit_name.c_str()); + f << stringf("%s" " else ", indent.c_str()); + } else if (ff.has_arst) { + f << stringf("if (%s", ff.pol_arst ? "" : "!"); + if (ff.sig_arst[i].wire == NULL) + f << stringf("%s", sig_arst_name.c_str()); + else + dump_sigspec(f, ff.sig_arst); + f << stringf(") %s <= ", reg_bit_name.c_str()); + dump_sigspec(f, val_arst); + f << stringf(";\n"); + f << stringf("%s" " else ", indent.c_str()); } - else - { - // for rd-transparent read-ports make something like: - // reg [..] temp_id; - // always @(posedge clk) - // temp_id <= r_addr; - // assign r_data = array_reg[temp_id]; - std::string temp_id = next_auto_id(); - lof_reg_declarations.push_back( stringf("reg [%d:0] %s;\n", sig_rd_addr.size() - 1, temp_id.c_str()) ); - { - std::ostringstream os; - dump_sigspec(os, sig_rd_addr); - std::string line = stringf("%s <= %s;\n", temp_id.c_str(), os.str().c_str()); - clk_to_lof_body[clk_domain_str].push_back(line); + + if (ff.has_srst && ff.has_en && ff.ce_over_srst) { + f << stringf("if (%s", ff.pol_en ? "" : "!"); + dump_sigspec(f, ff.sig_en); + f << stringf(")\n"); + f << stringf("%s" " if (%s", indent.c_str(), ff.pol_srst ? "" : "!"); + dump_sigspec(f, ff.sig_srst); + f << stringf(") %s <= ", reg_bit_name.c_str()); + dump_sigspec(f, val_srst); + f << stringf(";\n"); + f << stringf("%s" " else ", indent.c_str()); + } else { + if (ff.has_srst) { + f << stringf("if (%s", ff.pol_srst ? "" : "!"); + dump_sigspec(f, ff.sig_srst); + f << stringf(") %s <= ", reg_bit_name.c_str()); + dump_sigspec(f, val_srst); + f << stringf(";\n"); + f << stringf("%s" " else ", indent.c_str()); } - { - std::ostringstream os; - dump_sigspec(os, sig_rd_data); - std::string line = stringf("assign %s = %s[%s];\n", os.str().c_str(), mem_id.c_str(), temp_id.c_str()); - clk_to_lof_body[""].push_back(line); + if (ff.has_en) { + f << stringf("if (%s", ff.pol_en ? "" : "!"); + dump_sigspec(f, ff.sig_en); + f << stringf(") "); } } - } else { - // for non-clocked read-ports make something like: - // assign r_data = array_reg[r_addr]; - std::ostringstream os, os2; - dump_sigspec(os, sig_rd_data); - dump_sigspec(os2, sig_rd_addr); - std::string line = stringf("assign %s = %s[%s];\n", os.str().c_str(), mem_id.c_str(), os2.str().c_str()); - clk_to_lof_body[""].push_back(line); - } - } - - int nwrite_ports = cell->parameters[ID::WR_PORTS].as_int(); - RTLIL::SigSpec sig_wr_clk, sig_wr_data, sig_wr_addr, sig_wr_en; - bool wr_clk_posedge; - // write ports - for (int i=0; i < nwrite_ports; i++) - { - sig_wr_clk = cell->getPort(ID::WR_CLK).extract(i); - sig_wr_data = cell->getPort(ID::WR_DATA).extract(i*width, width); - sig_wr_addr = cell->getPort(ID::WR_ADDR).extract(i*abits, abits); - sig_wr_en = cell->getPort(ID::WR_EN).extract(i*width, width); - wr_clk_posedge = cell->parameters[ID::WR_CLK_POLARITY].extract(i).as_bool(); - { - std::ostringstream os; - dump_sigspec(os, sig_wr_clk); - clk_domain_str = stringf("%sedge %s", wr_clk_posedge ? "pos" : "neg", os.str().c_str()); - if( clk_to_lof_body.count(clk_domain_str) == 0 ) - clk_to_lof_body[clk_domain_str] = std::vector<std::string>(); + f << stringf("%s <= ", reg_bit_name.c_str()); + dump_sigspec(f, sig_d); + f << stringf(";\n"); } - // make something like: - // always @(posedge clk) - // if (wr_en_bit) memid[w_addr][??] <= w_data[??]; - // ... - for (int i = 0; i < GetSize(sig_wr_en); i++) + else { - int start_i = i, width = 1; - SigBit wen_bit = sig_wr_en[i]; - - while (i+1 < GetSize(sig_wr_en) && active_sigmap(sig_wr_en[i+1]) == active_sigmap(wen_bit)) - i++, width++; - - if (wen_bit == State::S0) - continue; - - std::ostringstream os; - if (wen_bit != State::S1) - { - os << stringf("if ("); - dump_sigspec(os, wen_bit); - os << stringf(") "); + // Latches. + f << stringf("%s" "always%s\n", indent.c_str(), systemverilog ? "_latch" : " @*"); + + f << stringf("%s" " ", indent.c_str()); + if (ff.has_sr) { + f << stringf("if (%s", ff.pol_clr ? "" : "!"); + dump_sigspec(f, ff.sig_clr[i]); + f << stringf(") %s = 1'b0;\n", reg_bit_name.c_str()); + f << stringf("%s" " else if (%s", indent.c_str(), ff.pol_set ? "" : "!"); + dump_sigspec(f, ff.sig_set[i]); + f << stringf(") %s = 1'b1;\n", reg_bit_name.c_str()); + if (ff.has_d) + f << stringf("%s" " else ", indent.c_str()); + } else if (ff.has_arst) { + f << stringf("if (%s", ff.pol_arst ? "" : "!"); + dump_sigspec(f, ff.sig_arst); + f << stringf(") %s = ", reg_bit_name.c_str()); + dump_sigspec(f, val_arst); + f << stringf(";\n"); + if (ff.has_d) + f << stringf("%s" " else ", indent.c_str()); + } + if (ff.has_d) { + f << stringf("if (%s", ff.pol_en ? "" : "!"); + dump_sigspec(f, ff.sig_en); + f << stringf(") %s = ", reg_bit_name.c_str()); + dump_sigspec(f, sig_d); + f << stringf(";\n"); } - os << stringf("%s[", mem_id.c_str()); - dump_sigspec(os, sig_wr_addr); - if (width == GetSize(sig_wr_en)) - os << stringf("] <= "); - else - os << stringf("][%d:%d] <= ", i, start_i); - dump_sigspec(os, sig_wr_data.extract(start_i, width)); - os << stringf(";\n"); - clk_to_lof_body[clk_domain_str].push_back(os.str()); } } - // Output Verilog that looks something like this: - // reg [..] _3_; - // always @(posedge CLK2) begin - // _3_ <= memory[D1ADDR]; - // if (A1EN) - // memory[A1ADDR] <= A1DATA; - // if (A2EN) - // memory[A2ADDR] <= A2DATA; - // ... - // end - // always @(negedge CLK1) begin - // if (C1EN) - // memory[C1ADDR] <= C1DATA; - // end - // ... - // assign D1DATA = _3_; - // assign D2DATA <= memory[D2ADDR]; - - // the reg ... definitions - for(auto ® : lof_reg_declarations) - { - f << stringf("%s" "%s", indent.c_str(), reg.c_str()); - } - // the block of expressions by clock domain - for(auto &pair : clk_to_lof_body) - { - std::string clk_domain = pair.first; - std::vector<std::string> lof_lines = pair.second; - if( clk_domain != "") - { - f << stringf("%s" "always @(%s) begin\n", indent.c_str(), clk_domain.c_str()); - for(auto &line : lof_lines) - f << stringf("%s%s" "%s", indent.c_str(), indent.c_str(), line.c_str()); - f << stringf("%s" "end\n", indent.c_str()); - } - else - { - // the non-clocked assignments - for(auto &line : lof_lines) - f << stringf("%s" "%s", indent.c_str(), line.c_str()); - } + + if (!out_is_reg_wire) { + f << stringf("%s" "assign ", indent.c_str()); + dump_sigspec(f, ff.sig_q); + f << stringf(" = %s;\n", reg_name.c_str()); } return true; @@ -1410,7 +1338,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) if (cell->type.in(ID($assert), ID($assume), ID($cover))) { - f << stringf("%s" "always @* if (", indent.c_str()); + f << stringf("%s" "always%s if (", indent.c_str(), systemverilog ? "_comb" : " @*"); dump_sigspec(f, cell->getPort(ID::EN)); f << stringf(") %s(", cell->type.c_str()+1); dump_sigspec(f, cell->getPort(ID::A)); @@ -1528,14 +1456,17 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) return true; } - // FIXME: $_SR_[PN][PN]_, $_DLATCH_[PN]_, $_DLATCHSR_[PN][PN][PN]_ - // FIXME: $sr, $dlatch, $memrd, $memwr, $fsm + // FIXME: $fsm return false; } void dump_cell(std::ostream &f, std::string indent, RTLIL::Cell *cell) { + // Handled by dump_memory + if (cell->type.in(ID($mem), ID($memwr), ID($memrd), ID($meminit))) + return; + if (cell->type[0] == '$' && !noexpr) { if (dump_cell_expr(f, indent, cell)) return; @@ -1602,7 +1533,7 @@ void dump_cell(std::ostream &f, std::string indent, RTLIL::Cell *cell) } } - if (siminit && reg_ct.count(cell->type) && cell->hasPort(ID::Q)) { + if (siminit && RTLIL::builtin_ff_cell_types().count(cell->type) && cell->hasPort(ID::Q) && !cell->type.in(ID($ff), ID($_FF_))) { std::stringstream ss; dump_reg_init(ss, cell->getPort(ID::Q)); if (!ss.str().empty()) { @@ -1717,7 +1648,9 @@ void dump_process(std::ostream &f, std::string indent, RTLIL::Process *proc, boo return; } - f << stringf("%s" "always @* begin\n", indent.c_str()); + f << stringf("%s" "always%s begin\n", indent.c_str(), systemverilog ? "_comb" : " @*"); + if (!systemverilog) + f << indent + " " << "if (" << id("\\initial") << ") begin end\n"; dump_case_body(f, indent, &proc->root_case, true); std::string backup_indent = indent; @@ -1728,11 +1661,11 @@ void dump_process(std::ostream &f, std::string indent, RTLIL::Process *proc, boo indent = backup_indent; if (sync->type == RTLIL::STa) { - f << stringf("%s" "always @* begin\n", indent.c_str()); + f << stringf("%s" "always%s begin\n", indent.c_str(), systemverilog ? "_comb" : " @*"); } else if (sync->type == RTLIL::STi) { f << stringf("%s" "initial begin\n", indent.c_str()); } else { - f << stringf("%s" "always @(", indent.c_str()); + f << stringf("%s" "always%s @(", indent.c_str(), systemverilog ? "_ff" : ""); if (sync->type == RTLIL::STp || sync->type == RTLIL::ST1) f << stringf("posedge "); if (sync->type == RTLIL::STn || sync->type == RTLIL::ST0) @@ -1810,7 +1743,7 @@ void dump_module(std::ostream &f, std::string indent, RTLIL::Module *module) std::set<std::pair<RTLIL::Wire*,int>> reg_bits; for (auto cell : module->cells()) { - if (!reg_ct.count(cell->type) || !cell->hasPort(ID::Q)) + if (!RTLIL::builtin_ff_cell_types().count(cell->type) || !cell->hasPort(ID::Q) || cell->type.in(ID($ff), ID($_FF_))) continue; RTLIL::SigSpec sig = cell->getPort(ID::Q); @@ -1850,11 +1783,14 @@ void dump_module(std::ostream &f, std::string indent, RTLIL::Module *module) } f << stringf(");\n"); + if (!systemverilog && !module->processes.empty()) + f << indent + " " << "reg " << id("\\initial") << " = 0;\n"; + for (auto w : module->wires()) dump_wire(f, indent + " ", w); - for (auto it = module->memories.begin(); it != module->memories.end(); ++it) - dump_memory(f, indent + " ", it->second); + for (auto &mem : Mem::get_all_memories(module)) + dump_memory(f, indent + " ", mem); for (auto cell : module->cells()) dump_cell(f, indent + " ", cell); @@ -1873,7 +1809,7 @@ void dump_module(std::ostream &f, std::string indent, RTLIL::Module *module) struct VerilogBackend : public Backend { VerilogBackend() : Backend("verilog", "write design to Verilog file") { } - void help() YS_OVERRIDE + void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); @@ -1881,6 +1817,9 @@ struct VerilogBackend : public Backend { log("\n"); log("Write the current design to a Verilog file.\n"); log("\n"); + log(" -sv\n"); + log(" with this option, SystemVerilog constructs like always_comb are used\n"); + log("\n"); log(" -norename\n"); log(" without this option all internal object names (the ones with a dollar\n"); log(" instead of a backslash prefix) are changed to short names in the\n"); @@ -1953,7 +1892,7 @@ struct VerilogBackend : public Backend { log("this command is called on a design with RTLIL processes.\n"); log("\n"); } - void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE + void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override { log_header(design, "Executing Verilog backend.\n"); @@ -1976,37 +1915,14 @@ struct VerilogBackend : public Backend { auto_name_map.clear(); reg_wires.clear(); - reg_ct.clear(); - - reg_ct.insert(ID($dff)); - reg_ct.insert(ID($adff)); - reg_ct.insert(ID($dffe)); - reg_ct.insert(ID($dlatch)); - - reg_ct.insert(ID($_DFF_N_)); - reg_ct.insert(ID($_DFF_P_)); - - reg_ct.insert(ID($_DFF_NN0_)); - reg_ct.insert(ID($_DFF_NN1_)); - reg_ct.insert(ID($_DFF_NP0_)); - reg_ct.insert(ID($_DFF_NP1_)); - reg_ct.insert(ID($_DFF_PN0_)); - reg_ct.insert(ID($_DFF_PN1_)); - reg_ct.insert(ID($_DFF_PP0_)); - reg_ct.insert(ID($_DFF_PP1_)); - - reg_ct.insert(ID($_DFFSR_NNN_)); - reg_ct.insert(ID($_DFFSR_NNP_)); - reg_ct.insert(ID($_DFFSR_NPN_)); - reg_ct.insert(ID($_DFFSR_NPP_)); - reg_ct.insert(ID($_DFFSR_PNN_)); - reg_ct.insert(ID($_DFFSR_PNP_)); - reg_ct.insert(ID($_DFFSR_PPN_)); - reg_ct.insert(ID($_DFFSR_PPP_)); size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) { std::string arg = args[argidx]; + if (arg == "-sv") { + systemverilog = true; + continue; + } if (arg == "-norename") { norename = true; continue; @@ -2095,7 +2011,6 @@ struct VerilogBackend : public Backend { auto_name_map.clear(); reg_wires.clear(); - reg_ct.clear(); } } VerilogBackend; |