diff options
90 files changed, 3526 insertions, 978 deletions
@@ -66,6 +66,7 @@ Yosys 0.9 .. Yosys 0.9-dev - Added "design -delete" - Added "select -unset" - Use YosysHQ/abc instead of upstream berkeley-abc/abc + - Added $divfloor and $modfloor cells Yosys 0.8 .. Yosys 0.9 ---------------------- diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 000000000..a73779920 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,37 @@ +## CODE NOTIFICATIONS +# Register yourself here to be notified about modifications +# for any files you have an interest in/know your way around. + +# Each line is a file pattern followed by one or more users. +# Both github usernames and email addresses are supported. +# Order is important; the last matching pattern takes the most +# precedence. Previous matches will not be applied. + + +# PATH (can use glob) USERNAME(S) + +passes/cmds/scratchpad.cc @nakengelhardt +frontends/rpc/ @whitequark +backends/cxxrtl/ @whitequark +passes/cmds/bugpoint.cc @whitequark +passes/techmap/flowmap.cc @whitequark +passes/opt/opt_lut.cc @whitequark + + +## External Contributors +# Only users with write permission to the repository get review +# requests automatically, but we add information for other +# contributors here too, so we know who to ask to take a look. +# These still override previous lines, so be careful not to +# accidentally disable any of the above rules. + +techlibs/intel_alm/ @ZirconiumX + +# pyosys +misc/*.py @btut + +backends/firrtl @ucbjrl @azidar + +passes/sat/qbfsat.cc @boqwxp +passes/cmds/exec.cc @boqwxp +passes/cmds/printattrs.cc @boqwxp @@ -717,7 +717,7 @@ ifneq ($(ABCREV),default) echo 'REEBE: NOP pbagnvaf ybpny zbqvsvpngvbaf! Frg NOPERI=qrsnhyg va Lbflf Znxrsvyr!' | tr 'A-Za-z' 'N-ZA-Mn-za-m'; false; \ fi # set a variable so the test fails if git fails to run - when comparing outputs directly, empty string would match empty string - $(Q) if ! (cd abc && rev="`git rev-parse $(ABCREV)`" && test "`git rev-parse HEAD`" == "$$rev"); then \ + $(Q) if ! (cd abc 2> /dev/null && rev="`git rev-parse $(ABCREV)`" && test "`git rev-parse HEAD`" == "$$rev"); then \ test $(ABCPULL) -ne 0 || { echo 'REEBE: NOP abg hc gb qngr naq NOPCHYY frg gb 0 va Znxrsvyr!' | tr 'A-Za-z' 'N-ZA-Mn-za-m'; exit 1; }; \ echo "Pulling ABC from $(ABCURL):"; set -x; \ test -d abc || git clone $(ABCURL) abc; \ @@ -556,6 +556,8 @@ from SystemVerilog: - enums are supported (including inside packages) - but are currently not strongly typed +- packed structs and unions are supported. + - SystemVerilog interfaces (SVIs) are supported. Modports for specifying whether ports are inputs or outputs are supported. diff --git a/backends/aiger/xaiger.cc b/backends/aiger/xaiger.cc index 6b910eecd..8bbadbc91 100644 --- a/backends/aiger/xaiger.cc +++ b/backends/aiger/xaiger.cc @@ -85,7 +85,7 @@ struct XAigerWriter dict<SigBit, SigBit> not_map, alias_map; dict<SigBit, pair<SigBit, SigBit>> and_map; vector<SigBit> ci_bits, co_bits; - dict<SigBit, Cell*> ff_bits; + vector<Cell*> ff_list; dict<SigBit, float> arrival_times; vector<pair<int, int>> aig_gates; @@ -156,7 +156,7 @@ struct XAigerWriter // promote keep wires for (auto wire : module->wires()) - if (wire->get_bool_attribute(ID::keep)) + if (wire->get_bool_attribute(ID::keep) || wire->get_bool_attribute(ID::abc9_keep)) sigmap.add(wire); for (auto wire : module->wires()) { @@ -232,8 +232,7 @@ struct XAigerWriter unused_bits.erase(D); undriven_bits.erase(Q); alias_map[Q] = D; - auto r YS_ATTRIBUTE(unused) = ff_bits.insert(std::make_pair(D, cell)); - log_assert(r.second); + ff_list.emplace_back(cell); continue; } @@ -420,8 +419,7 @@ struct XAigerWriter aig_map[bit] = 2*aig_m; } - for (const auto &i : ff_bits) { - const Cell *cell = i.second; + for (auto cell : ff_list) { const SigBit &q = sigmap(cell->getPort(ID::Q)); aig_m++, aig_i++; log_assert(!aig_map.count(q)); @@ -468,8 +466,8 @@ struct XAigerWriter aig_outputs.push_back(aig); } - for (auto &i : ff_bits) { - const SigBit &d = i.first; + for (auto cell : ff_list) { + const SigBit &d = sigmap(cell->getPort(ID::D)); aig_o++; aig_outputs.push_back(aig_map.at(d)); } @@ -541,16 +539,16 @@ struct XAigerWriter std::stringstream h_buffer; auto write_h_buffer = std::bind(write_buffer, std::ref(h_buffer), std::placeholders::_1); write_h_buffer(1); - log_debug("ciNum = %d\n", GetSize(input_bits) + GetSize(ff_bits) + GetSize(ci_bits)); - write_h_buffer(input_bits.size() + ff_bits.size() + ci_bits.size()); - log_debug("coNum = %d\n", GetSize(output_bits) + GetSize(ff_bits) + GetSize(co_bits)); - write_h_buffer(output_bits.size() + GetSize(ff_bits) + GetSize(co_bits)); - log_debug("piNum = %d\n", GetSize(input_bits) + GetSize(ff_bits)); - write_h_buffer(input_bits.size() + ff_bits.size()); - log_debug("poNum = %d\n", GetSize(output_bits) + GetSize(ff_bits)); - write_h_buffer(output_bits.size() + ff_bits.size()); + log_debug("ciNum = %d\n", GetSize(input_bits) + GetSize(ff_list) + GetSize(ci_bits)); + write_h_buffer(GetSize(input_bits) + GetSize(ff_list) + GetSize(ci_bits)); + log_debug("coNum = %d\n", GetSize(output_bits) + GetSize(ff_list) + GetSize(co_bits)); + write_h_buffer(GetSize(output_bits) + GetSize(ff_list) + GetSize(co_bits)); + log_debug("piNum = %d\n", GetSize(input_bits) + GetSize(ff_list)); + write_h_buffer(GetSize(input_bits) + GetSize(ff_list)); + log_debug("poNum = %d\n", GetSize(output_bits) + GetSize(ff_list)); + write_h_buffer(GetSize(output_bits) + GetSize(ff_list)); log_debug("boxNum = %d\n", GetSize(box_list)); - write_h_buffer(box_list.size()); + write_h_buffer(GetSize(box_list)); auto write_buffer_float = [](std::stringstream &buffer, float f32) { buffer.write(reinterpret_cast<const char*>(&f32), sizeof(f32)); @@ -564,7 +562,7 @@ struct XAigerWriter //for (auto bit : output_bits) // write_o_buffer(0); - if (!box_list.empty() || !ff_bits.empty()) { + if (!box_list.empty() || !ff_list.empty()) { dict<IdString, std::tuple<int,int,int>> cell_cache; int box_count = 0; @@ -601,17 +599,17 @@ struct XAigerWriter std::stringstream r_buffer; auto write_r_buffer = std::bind(write_buffer, std::ref(r_buffer), std::placeholders::_1); - log_debug("flopNum = %d\n", GetSize(ff_bits)); - write_r_buffer(ff_bits.size()); + log_debug("flopNum = %d\n", GetSize(ff_list)); + write_r_buffer(ff_list.size()); std::stringstream s_buffer; auto write_s_buffer = std::bind(write_buffer, std::ref(s_buffer), std::placeholders::_1); - write_s_buffer(ff_bits.size()); + write_s_buffer(ff_list.size()); dict<SigSpec, int> clk_to_mergeability; - for (const auto &i : ff_bits) { - const SigBit &d = i.first; - const Cell *cell = i.second; + for (const auto cell : ff_list) { + const SigBit &d = sigmap(cell->getPort(ID::D)); + const SigBit &q = sigmap(cell->getPort(ID::Q)); SigSpec clk_and_pol{sigmap(cell->getPort(ID::C)), cell->type[6] == 'P' ? State::S1 : State::S0}; auto r = clk_to_mergeability.insert(std::make_pair(clk_and_pol, clk_to_mergeability.size()+1)); @@ -619,8 +617,7 @@ struct XAigerWriter log_assert(mergeability > 0); write_r_buffer(mergeability); - SigBit Q = sigmap(cell->getPort(ID::Q)); - State init = init_map.at(Q, State::Sx); + State init = init_map.at(q, State::Sx); log_debug("Cell '%s' (type %s) has (* init *) value '%s'.\n", log_id(cell), log_id(cell->type), log_signal(init)); if (init == State::S1) write_s_buffer(1); @@ -700,8 +697,6 @@ struct XAigerWriter for (auto wire : module->wires()) { - SigSpec sig = sigmap(wire); - for (int i = 0; i < GetSize(wire); i++) { RTLIL::SigBit b(wire, i); @@ -714,7 +709,6 @@ struct XAigerWriter if (output_bits.count(b)) { int o = ordered_outputs.at(b); output_lines[o] += stringf("output %d %d %s\n", o - GetSize(co_bits), wire->start_offset+i, log_id(wire)); - continue; } } } diff --git a/backends/btor/btor.cc b/backends/btor/btor.cc index 14c8484e8..9ac312480 100644 --- a/backends/btor/btor.cc +++ b/backends/btor/btor.cc @@ -40,6 +40,7 @@ struct BtorWorker bool verbose; bool single_bad; bool cover_mode; + bool print_internal_names; int next_nid = 1; int initstate_nid = -1; @@ -78,7 +79,7 @@ struct BtorWorker vector<string> info_lines; dict<int, int> info_clocks; - void btorf(const char *fmt, ...) + void btorf(const char *fmt, ...) YS_ATTRIBUTE(format(printf, 2, 3)) { va_list ap; va_start(ap, fmt); @@ -86,7 +87,7 @@ struct BtorWorker va_end(ap); } - void infof(const char *fmt, ...) + void infof(const char *fmt, ...) YS_ATTRIBUTE(format(printf, 2, 3)) { va_list ap; va_start(ap, fmt); @@ -98,6 +99,7 @@ struct BtorWorker string getinfo(T *obj, bool srcsym = false) { string infostr = log_id(obj); + if (!srcsym && !print_internal_names && infostr[0] == '$') return ""; if (obj->attributes.count(ID::src)) { string src = obj->attributes.at(ID::src).decode_string().c_str(); if (srcsym && infostr[0] == '$') { @@ -117,7 +119,7 @@ struct BtorWorker infostr += " ; " + src; } } - return infostr; + return " " + infostr; } void btorf_push(const string &id) @@ -242,7 +244,7 @@ struct BtorWorker btorf("%d slt %d %d %d\n", nid_b_ltz, sid_bit, nid_b, nid_zero); nid = next_nid++; - btorf("%d ite %d %d %d %d %s\n", nid, sid, nid_b_ltz, nid_l, nid_r, getinfo(cell).c_str()); + btorf("%d ite %d %d %d %d%s\n", nid, sid, nid_b_ltz, nid_l, nid_r, getinfo(cell).c_str()); } else { @@ -250,7 +252,7 @@ struct BtorWorker 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()); + btorf("%d %s %d %d %d%s\n", nid, btor_op.c_str(), sid, nid_a, nid_b, getinfo(cell).c_str()); } SigSpec sig = sigmap(cell->getPort(ID::Y)); @@ -266,26 +268,32 @@ struct BtorWorker goto okay; } - if (cell->type.in(ID($div), ID($mod))) + if (cell->type.in(ID($div), ID($mod), ID($modfloor))) { + 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; + string btor_op; if (cell->type == ID($div)) btor_op = "div"; + // "rem" = truncating modulo if (cell->type == ID($mod)) btor_op = "rem"; + // "mod" = flooring modulo + if (cell->type == ID($modfloor)) { + // "umod" doesn't exist because it's the same as "urem" + btor_op = a_signed || b_signed ? "mod" : "rem"; + } 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))); - 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; - 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); int sid = get_bv_sid(width); int nid = next_nid++; - btorf("%d %c%s %d %d %d %s\n", nid, a_signed || b_signed ? 's' : 'u', btor_op.c_str(), sid, nid_a, nid_b, getinfo(cell).c_str()); + btorf("%d %c%s %d %d %d%s\n", nid, a_signed || b_signed ? 's' : 'u', btor_op.c_str(), sid, nid_a, nid_b, getinfo(cell).c_str()); SigSpec sig = sigmap(cell->getPort(ID::Y)); @@ -311,12 +319,12 @@ struct BtorWorker if (cell->type == ID($_ANDNOT_)) { btorf("%d not %d %d\n", nid1, sid, nid_b); - btorf("%d and %d %d %d %s\n", nid2, sid, nid_a, nid1, getinfo(cell).c_str()); + btorf("%d and %d %d %d%s\n", nid2, sid, nid_a, nid1, getinfo(cell).c_str()); } if (cell->type == ID($_ORNOT_)) { btorf("%d not %d %d\n", nid1, sid, nid_b); - btorf("%d or %d %d %d %s\n", nid2, sid, nid_a, nid1, getinfo(cell).c_str()); + btorf("%d or %d %d %d%s\n", nid2, sid, nid_a, nid1, getinfo(cell).c_str()); } SigSpec sig = sigmap(cell->getPort(ID::Y)); @@ -338,13 +346,13 @@ struct BtorWorker if (cell->type == ID($_OAI3_)) { btorf("%d or %d %d %d\n", nid1, sid, nid_a, nid_b); btorf("%d and %d %d %d\n", nid2, sid, nid1, nid_c); - btorf("%d not %d %d %s\n", nid3, sid, nid2, getinfo(cell).c_str()); + btorf("%d not %d %d%s\n", nid3, sid, nid2, getinfo(cell).c_str()); } if (cell->type == ID($_AOI3_)) { btorf("%d and %d %d %d\n", nid1, sid, nid_a, nid_b); btorf("%d or %d %d %d\n", nid2, sid, nid1, nid_c); - btorf("%d not %d %d %s\n", nid3, sid, nid2, getinfo(cell).c_str()); + btorf("%d not %d %d%s\n", nid3, sid, nid2, getinfo(cell).c_str()); } SigSpec sig = sigmap(cell->getPort(ID::Y)); @@ -369,14 +377,14 @@ struct BtorWorker btorf("%d or %d %d %d\n", nid1, sid, nid_a, nid_b); btorf("%d or %d %d %d\n", nid2, sid, nid_c, nid_d); btorf("%d and %d %d %d\n", nid3, sid, nid1, nid2); - btorf("%d not %d %d %s\n", nid4, sid, nid3, getinfo(cell).c_str()); + btorf("%d not %d %d%s\n", nid4, sid, nid3, getinfo(cell).c_str()); } if (cell->type == ID($_AOI4_)) { btorf("%d and %d %d %d\n", nid1, sid, nid_a, nid_b); btorf("%d and %d %d %d\n", nid2, sid, nid_c, nid_d); btorf("%d or %d %d %d\n", nid3, sid, nid1, nid2); - btorf("%d not %d %d %s\n", nid4, sid, nid3, getinfo(cell).c_str()); + btorf("%d not %d %d%s\n", nid4, sid, nid3, getinfo(cell).c_str()); } SigSpec sig = sigmap(cell->getPort(ID::Y)); @@ -408,9 +416,9 @@ struct BtorWorker int nid = next_nid++; if (cell->type.in(ID($lt), ID($le), ID($ge), ID($gt))) { - btorf("%d %c%s %d %d %d %s\n", nid, a_signed || b_signed ? 's' : 'u', btor_op.c_str(), sid, nid_a, nid_b, getinfo(cell).c_str()); + btorf("%d %c%s %d %d %d%s\n", nid, a_signed || b_signed ? 's' : 'u', btor_op.c_str(), sid, nid_a, nid_b, getinfo(cell).c_str()); } else { - btorf("%d %s %d %d %d %s\n", nid, btor_op.c_str(), sid, nid_a, nid_b, getinfo(cell).c_str()); + btorf("%d %s %d %d %d%s\n", nid, btor_op.c_str(), sid, nid_a, nid_b, getinfo(cell).c_str()); } SigSpec sig = sigmap(cell->getPort(ID::Y)); @@ -441,7 +449,7 @@ struct BtorWorker int nid_a = get_sig_nid(cell->getPort(ID::A), width, a_signed); int nid = next_nid++; - btorf("%d %s %d %d\n", nid, btor_op.c_str(), sid, nid_a, getinfo(cell).c_str()); + btorf("%d %s %d %d%s\n", nid, btor_op.c_str(), sid, nid_a, getinfo(cell).c_str()); SigSpec sig = sigmap(cell->getPort(ID::Y)); @@ -482,9 +490,9 @@ struct BtorWorker int nid = next_nid++; if (btor_op != "not") - btorf("%d %s %d %d %d\n", nid, btor_op.c_str(), sid, nid_a, nid_b, getinfo(cell).c_str()); + btorf("%d %s %d %d %d%s\n", nid, btor_op.c_str(), sid, nid_a, nid_b, getinfo(cell).c_str()); else - btorf("%d %s %d %d\n", nid, btor_op.c_str(), sid, nid_a, getinfo(cell).c_str()); + btorf("%d %s %d %d%s\n", nid, btor_op.c_str(), sid, nid_a, getinfo(cell).c_str()); SigSpec sig = sigmap(cell->getPort(ID::Y)); @@ -515,11 +523,11 @@ struct BtorWorker if (cell->type == ID($reduce_xnor)) { int nid2 = next_nid++; - btorf("%d %s %d %d %s\n", nid, btor_op.c_str(), sid, nid_a, getinfo(cell).c_str()); - btorf("%d not %d %d %d\n", nid2, sid, nid); + btorf("%d %s %d %d%s\n", nid, btor_op.c_str(), sid, nid_a, getinfo(cell).c_str()); + btorf("%d not %d %d\n", nid2, sid, nid); nid = nid2; } else { - btorf("%d %s %d %d %s\n", nid, btor_op.c_str(), sid, nid_a, getinfo(cell).c_str()); + btorf("%d %s %d %d%s\n", nid, btor_op.c_str(), sid, nid_a, getinfo(cell).c_str()); } SigSpec sig = sigmap(cell->getPort(ID::Y)); @@ -554,9 +562,9 @@ struct BtorWorker int tmp = nid; nid = next_nid++; btorf("%d ite %d %d %d %d\n", tmp, sid, nid_s, nid_b, nid_a); - btorf("%d not %d %d %s\n", nid, sid, tmp, getinfo(cell).c_str()); + btorf("%d not %d %d%s\n", nid, sid, tmp, getinfo(cell).c_str()); } else { - btorf("%d ite %d %d %d %d %s\n", nid, sid, nid_s, nid_b, nid_a, getinfo(cell).c_str()); + btorf("%d ite %d %d %d %d%s\n", nid, sid, nid_s, nid_b, nid_a, getinfo(cell).c_str()); } add_nid_sig(nid, sig_y); @@ -579,7 +587,7 @@ struct BtorWorker int nid_s = get_sig_nid(sig_s.extract(i)); int nid2 = next_nid++; if (i == GetSize(sig_s)-1) - btorf("%d ite %d %d %d %d %s\n", nid2, sid, nid_s, nid_b, nid, getinfo(cell).c_str()); + btorf("%d ite %d %d %d %d%s\n", nid2, sid, nid_s, nid_b, nid, getinfo(cell).c_str()); else btorf("%d ite %d %d %d %d\n", nid2, sid, nid_s, nid_b, nid); nid = nid2; @@ -634,7 +642,7 @@ struct BtorWorker int sid = get_bv_sid(GetSize(sig_q)); int nid = next_nid++; - if (symbol.empty()) + if (symbol.empty() || (!print_internal_names && symbol[0] == '$')) btorf("%d state %d\n", nid, sid); else btorf("%d state %d %s\n", nid, sid, log_id(symbol)); @@ -1043,8 +1051,8 @@ struct BtorWorker return nid; } - BtorWorker(std::ostream &f, RTLIL::Module *module, bool verbose, bool single_bad, bool cover_mode, string info_filename) : - f(f), sigmap(module), module(module), verbose(verbose), single_bad(single_bad), cover_mode(cover_mode), info_filename(info_filename) + BtorWorker(std::ostream &f, RTLIL::Module *module, bool verbose, bool single_bad, bool cover_mode, bool print_internal_names, string info_filename) : + f(f), sigmap(module), module(module), verbose(verbose), single_bad(single_bad), cover_mode(cover_mode), print_internal_names(print_internal_names), info_filename(info_filename) { if (!info_filename.empty()) infof("name %s\n", log_id(module)); @@ -1067,7 +1075,7 @@ struct BtorWorker int sid = get_bv_sid(GetSize(sig)); int nid = next_nid++; - btorf("%d input %d %s\n", nid, sid, getinfo(wire).c_str()); + btorf("%d input %d%s\n", nid, sid, getinfo(wire).c_str()); add_nid_sig(nid, sig); } @@ -1091,7 +1099,7 @@ struct BtorWorker btorf_push(stringf("output %s", log_id(wire))); int nid = get_sig_nid(wire); - btorf("%d output %d %s\n", next_nid++, nid, getinfo(wire).c_str()); + btorf("%d output %d%s\n", next_nid++, nid, getinfo(wire).c_str()); btorf_pop(stringf("output %s", log_id(wire))); } @@ -1133,10 +1141,10 @@ struct BtorWorker bad_properties.push_back(nid_en_and_not_a); } else { if (cover_mode) { - infof("bad %d %s\n", nid_en_and_not_a, getinfo(cell, true).c_str()); + infof("bad %d%s\n", nid_en_and_not_a, getinfo(cell, true).c_str()); } else { int nid = next_nid++; - btorf("%d bad %d %s\n", nid, nid_en_and_not_a, getinfo(cell, true).c_str()); + btorf("%d bad %d%s\n", nid, nid_en_and_not_a, getinfo(cell, true).c_str()); } } @@ -1158,7 +1166,7 @@ struct BtorWorker bad_properties.push_back(nid_en_and_a); } else { int nid = next_nid++; - btorf("%d bad %d %s\n", nid, nid_en_and_a, getinfo(cell, true).c_str()); + btorf("%d bad %d%s\n", nid, nid_en_and_a, getinfo(cell, true).c_str()); } btorf_pop(log_id(cell)); @@ -1179,7 +1187,7 @@ struct BtorWorker continue; int this_nid = next_nid++; - btorf("%d uext %d %d %d %s\n", this_nid, sid, nid, 0, getinfo(wire).c_str()); + btorf("%d uext %d %d %d%s\n", this_nid, sid, nid, 0, getinfo(wire).c_str()); btorf_pop(stringf("wire %s", log_id(wire))); continue; @@ -1250,14 +1258,14 @@ struct BtorWorker } int nid2 = next_nid++; - btorf("%d next %d %d %d %s\n", nid2, sid, nid, nid_head, getinfo(cell).c_str()); + 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()); + btorf("%d next %d %d %d%s\n", next_nid++, sid, nid, nid_q, getinfo(cell).c_str()); } btorf_pop(stringf("next %s", log_id(cell))); @@ -1347,10 +1355,13 @@ struct BtorBackend : public Backend { log(" -i <filename>\n"); log(" Create additional info file with auxiliary information\n"); log("\n"); + log(" -x\n"); + 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 { - bool verbose = false, single_bad = false, cover_mode = false; + bool verbose = false, single_bad = false, cover_mode = false, print_internal_names = false; string info_filename; log_header(design, "Executing BTOR backend.\n"); @@ -1374,6 +1385,10 @@ struct BtorBackend : public Backend { info_filename = args[++argidx]; continue; } + if (args[argidx] == "-x") { + print_internal_names = true; + continue; + } break; } extra_args(f, filename, args, argidx); @@ -1386,7 +1401,7 @@ struct BtorBackend : public Backend { *f << stringf("; BTOR description generated by %s for module %s.\n", yosys_version_str, log_id(topmod)); - BtorWorker(*f, topmod, verbose, single_bad, cover_mode, info_filename); + BtorWorker(*f, topmod, verbose, single_bad, cover_mode, print_internal_names, info_filename); *f << stringf("; end of yosys output\n"); } diff --git a/backends/btor/test_cells.sh b/backends/btor/test_cells.sh index e0f1a0514..3f077201a 100644 --- 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' +../../../yosys -p 'test_cell -n 5 -w test all /$alu /$fa /$lcu /$lut /$sop /$macc /$mul /$div /$mod /$divfloor /$modfloor' for fn in test_*.il; do ../../../yosys -p " diff --git a/backends/cxxrtl/Makefile.inc b/backends/cxxrtl/Makefile.inc index f93e65f85..aaa304502 100644 --- a/backends/cxxrtl/Makefile.inc +++ b/backends/cxxrtl/Makefile.inc @@ -1,2 +1,2 @@ -OBJS += backends/cxxrtl/cxxrtl.o +OBJS += backends/cxxrtl/cxxrtl_backend.o diff --git a/backends/cxxrtl/cxxrtl.h b/backends/cxxrtl/cxxrtl.h index 701510b7f..5f74899fd 100644 --- a/backends/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/cxxrtl.h @@ -33,13 +33,15 @@ #include <memory> #include <sstream> -// The cxxrtl support library implements compile time specialized arbitrary width arithmetics, as well as provides +#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. namespace cxxrtl { -// All arbitrary-width values in cxxrtl are backed by arrays of unsigned integers called chunks. The chunk size +// All arbitrary-width values in CXXRTL are backed by arrays of unsigned integers called chunks. The chunk size // is the same regardless of the value width to simplify manipulating values via FFI interfaces, e.g. driving // and introspecting the simulation in Python. // @@ -49,6 +51,8 @@ namespace cxxrtl { // invisible to the compiler, (b) we often operate on non-power-of-2 values and have to clear the high bits anyway. // 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; + template<typename T> struct chunk_traits { static_assert(std::is_integral<T>::value && std::is_unsigned<T>::value, @@ -65,7 +69,7 @@ template<size_t Bits> struct value : public expr_base<value<Bits>> { static constexpr size_t bits = Bits; - using chunk = chunk_traits<uint32_t>; + using chunk = chunk_traits<chunk_t>; static constexpr chunk::type msb_mask = (Bits % chunk::bits == 0) ? chunk::mask : chunk::mask >> (chunk::bits - (Bits % chunk::bits)); @@ -712,6 +716,58 @@ struct metadata { typedef std::map<std::string, metadata> metadata_map; +// 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 { + enum : uint32_t { + VALUE = CXXRTL_VALUE, + WIRE = CXXRTL_WIRE, + MEMORY = CXXRTL_MEMORY, + }; + + debug_item(const ::cxxrtl_object &object) : cxxrtl_object(object) {} + + template<size_t Bits> + debug_item(value<Bits> &item) { + 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; + } + + template<size_t Bits> + debug_item(wire<Bits> &item) { + 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; + } + + template<size_t Width> + debug_item(memory<Width> &item) { + 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; + } +}; +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 module { module() {} virtual ~module() {} @@ -731,11 +787,18 @@ struct module { } while (commit() && !converged); return deltas; } + + virtual void debug_info(debug_items &items, std::string path = "") {} }; } // namespace cxxrtl -// Definitions of internal Yosys cells. Other than the functions in this namespace, cxxrtl is fully generic +// Internal structure used to communicate with the implementation of the C interface. +typedef struct _cxxrtl_toplevel { + std::unique_ptr<cxxrtl::module> module; +} *cxxrtl_toplevel; + +// Definitions of internal Yosys cells. Other than the functions in this namespace, CXXRTL is fully generic // and indepenent of Yosys implementation details. // // The `write_cxxrtl` pass translates internal cells (cells with names that start with `$`) to calls of these @@ -921,7 +984,7 @@ value<BitsY> sshr_uu(const value<BitsA> &a, const value<BitsB> &b) { template<size_t BitsY, size_t BitsA, size_t BitsB> value<BitsY> sshr_su(const value<BitsA> &a, const value<BitsB> &b) { - return a.template shr(b).template scast<BitsY>(); + return a.template sshr(b).template scast<BitsY>(); } template<size_t BitsY, size_t BitsA, size_t BitsB> diff --git a/backends/cxxrtl/cxxrtl.cc b/backends/cxxrtl/cxxrtl_backend.cc index 549404184..64af5dab8 100644 --- a/backends/cxxrtl/cxxrtl.cc +++ b/backends/cxxrtl/cxxrtl_backend.cc @@ -502,6 +502,15 @@ std::string escape_cxx_string(const std::string &input) return output; } +template<class T> +std::string get_hdl_name(T *object) +{ + if (object->has_attribute(ID::hdlname)) + return object->get_string_attribute(ID::hdlname); + else + return object->name.str(); +} + struct CxxrtlWorker { bool split_intf = false; std::string intf_filename; @@ -513,10 +522,11 @@ struct CxxrtlWorker { bool elide_public = false; bool localize_internal = false; bool localize_public = false; - bool run_opt_clean_purge = false; bool run_proc_flatten = false; bool max_opt_level = false; + bool debug_info = false; + std::ostringstream f; std::string indent; int temporary = 0; @@ -1594,6 +1604,34 @@ struct CxxrtlWorker { dec_indent(); } + void dump_debug_info_method(RTLIL::Module *module) + { + inc_indent(); + f << indent << "assert(path.empty() || path[path.size() - 1] == ' ');\n"; + for (auto wire : module->wires()) { + if (wire->name[0] != '\\') + continue; + if (localized_wires.count(wire)) + continue; + f << indent << "items.emplace(path + " << escape_cxx_string(get_hdl_name(wire)); + f << ", debug_item(" << mangle(wire) << "));\n"; + } + 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"; + } + dec_indent(); + } + void dump_metadata_map(const dict<RTLIL::IdString, RTLIL::Const> &metadata_map) { if (metadata_map.empty()) { @@ -1642,6 +1680,12 @@ struct CxxrtlWorker { dump_commit_method(module); f << indent << "}\n"; f << "\n"; + if (debug_info) { + f << indent << "void debug_info(debug_items &items, std::string path = \"\") override {\n"; + dump_debug_info_method(module); + f << indent << "}\n"; + f << "\n"; + } f << indent << "static std::unique_ptr<" << mangle(module); f << template_params(module, /*is_decl=*/false) << "> "; f << "create(std::string name, metadata_map parameters, metadata_map attributes);\n"; @@ -1690,7 +1734,7 @@ struct CxxrtlWorker { if (cell_module->get_bool_attribute(ID(cxxrtl_blackbox))) { f << indent << "std::unique_ptr<" << mangle(cell_module) << template_args(cell) << "> "; f << mangle(cell) << " = " << mangle(cell_module) << template_args(cell); - f << "::create(" << escape_cxx_string(cell->name.str()) << ", "; + f << "::create(" << escape_cxx_string(get_hdl_name(cell)) << ", "; dump_metadata_map(cell->parameters); f << ", "; dump_metadata_map(cell->attributes); @@ -1704,6 +1748,8 @@ struct CxxrtlWorker { f << "\n"; f << indent << "bool eval() override;\n"; f << indent << "bool commit() override;\n"; + if (debug_info) + f << indent << "void debug_info(debug_items &items, std::string path = \"\") override;\n"; dec_indent(); f << indent << "}; // struct " << mangle(module) << "\n"; f << "\n"; @@ -1722,10 +1768,17 @@ struct CxxrtlWorker { dump_commit_method(module); f << indent << "}\n"; f << "\n"; + if (debug_info) { + f << indent << "void " << mangle(module) << "::debug_info(debug_items &items, std::string path) {\n"; + dump_debug_info_method(module); + f << indent << "}\n"; + f << "\n"; + } } void dump_design(RTLIL::Design *design) { + RTLIL::Module *top_module = nullptr; std::vector<RTLIL::Module*> modules; TopoSort<RTLIL::Module*> topo_design; for (auto module : design->modules()) { @@ -1735,6 +1788,8 @@ struct CxxrtlWorker { modules.push_back(module); // cxxrtl blackboxes first if (module->get_blackbox_attribute() || module->get_bool_attribute(ID(cxxrtl_blackbox))) continue; + if (module->get_bool_attribute(ID::top)) + top_module = module; topo_design.node(module); for (auto cell : module->cells()) { @@ -1756,6 +1811,25 @@ struct CxxrtlWorker { f << "#ifndef " << include_guard << "\n"; f << "#define " << include_guard << "\n"; f << "\n"; + if (top_module != nullptr && debug_info) { + f << "#include <backends/cxxrtl/cxxrtl_capi.h>\n"; + f << "\n"; + f << "#ifdef __cplusplus\n"; + f << "extern \"C\" {\n"; + f << "#endif\n"; + f << "\n"; + f << "cxxrtl_toplevel " << design_ns << "_create();\n"; + f << "\n"; + f << "#ifdef __cplusplus\n"; + f << "}\n"; + f << "#endif\n"; + f << "\n"; + } else { + f << "// The CXXRTL C API is not available because the design is built without debug information.\n"; + f << "\n"; + } + f << "#ifdef __cplusplus\n"; + f << "\n"; f << "#include <backends/cxxrtl/cxxrtl.h>\n"; f << "\n"; f << "using namespace cxxrtl;\n"; @@ -1766,6 +1840,8 @@ struct CxxrtlWorker { dump_module_intf(module); f << "} // namespace " << design_ns << "\n"; f << "\n"; + f << "#endif // __cplusplus\n"; + f << "\n"; f << "#endif\n"; *intf_f << f.str(); f.str(""); } @@ -1775,6 +1851,15 @@ struct CxxrtlWorker { else f << "#include <backends/cxxrtl/cxxrtl.h>\n"; f << "\n"; + f << "#if defined(CXXRTL_INCLUDE_CAPI_IMPL) || \\\n"; + f << " defined(CXXRTL_INCLUDE_VCD_CAPI_IMPL)\n"; + f << "#include <backends/cxxrtl/cxxrtl_capi.cc>\n"; + f << "#endif\n"; + f << "\n"; + f << "#if defined(CXXRTL_INCLUDE_VCD_CAPI_IMPL)\n"; + f << "#include <backends/cxxrtl/cxxrtl_vcd_capi.cc>\n"; + f << "#endif\n"; + f << "\n"; f << "using namespace cxxrtl_yosys;\n"; f << "\n"; f << "namespace " << design_ns << " {\n"; @@ -1785,6 +1870,17 @@ struct CxxrtlWorker { dump_module_impl(module); } f << "} // namespace " << design_ns << "\n"; + f << "\n"; + if (top_module != nullptr && debug_info) { + f << "cxxrtl_toplevel " << design_ns << "_create() {\n"; + inc_indent(); + f << indent << "return new _cxxrtl_toplevel { "; + f << "std::make_unique<" << design_ns << "::" << mangle(top_module) << ">()"; + f << " };\n"; + dec_indent(); + f << "}\n"; + } + *impl_f << f.str(); f.str(""); } @@ -2009,6 +2105,7 @@ 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()) { @@ -2040,20 +2137,20 @@ struct CxxrtlWorker { log("Module `%s' contains buffered combinatorial wires:\n", log_id(module)); for (auto wire : buffered_wires) log(" %s\n", log_id(wire)); + log("\n"); } eval_converges[module] = feedback_wires.empty() && buffered_wires.empty(); } if (has_feedback_arcs || has_buffered_wires) { // Although both non-feedback buffered combinatorial wires and apparent feedback wires may be eliminated - // by optimizing the design, if after `opt_clean -purge` there are any feedback wires remaining, it is very + // 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) why_pessimistic = "buffered combinatorial wires"; - log("\n"); 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); @@ -2087,34 +2184,40 @@ struct CxxrtlWorker { void prepare_design(RTLIL::Design *design) { + bool did_anything = false; bool 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"); Pass::call(design, "flatten"); + 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.) Pass::call(design, "proc_prune"); Pass::call(design, "proc_clean"); Pass::call(design, "proc_init"); + did_anything = true; } - if (has_packed_mem) + if (has_packed_mem) { Pass::call(design, "memory_unpack"); + 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 (run_opt_clean_purge) - Pass::call(design, "opt_clean -purge"); log_pop(); + if (did_anything) + log_spacer(); analyze_design(design); } }; struct CxxrtlBackend : public Backend { - static const int DEFAULT_OPT_LEVEL = 6; + static const int DEFAULT_OPT_LEVEL = 5; + static const int DEFAULT_DEBUG_LEVEL = 1; CxxrtlBackend() : Backend("cxxrtl", "convert design to C++ RTL simulation") { } void help() YS_OVERRIDE @@ -2308,10 +2411,22 @@ struct CxxrtlBackend : public Backend { log(" -O5\n"); log(" like -O4, and run `proc; flatten` first.\n"); log("\n"); + log(" -g <level>\n"); + log(" set the debug level. the default is -g%d. higher debug levels provide\n", DEFAULT_DEBUG_LEVEL); + log(" more visibility and generate more code, but do not pessimize evaluation.\n"); + log("\n"); + log(" -g0\n"); + log(" no debug information.\n"); + log("\n"); + log(" -g1\n"); + log(" debug information for non-localized public wires.\n"); + log("\n"); } + void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE { int opt_level = DEFAULT_OPT_LEVEL; + int debug_level = DEFAULT_DEBUG_LEVEL; CxxrtlWorker worker; log_header(design, "Executing CXXRTL backend.\n"); @@ -2327,6 +2442,14 @@ struct CxxrtlBackend : public Backend { opt_level = std::stoi(args[argidx].substr(2)); continue; } + if (args[argidx] == "-g" && argidx+1 < args.size()) { + debug_level = std::stoi(args[++argidx]); + continue; + } + if (args[argidx].substr(0, 2) == "-g" && args[argidx].size() == 3 && isdigit(args[argidx][2])) { + debug_level = std::stoi(args[argidx].substr(2)); + continue; + } if (args[argidx] == "-header") { worker.split_intf = true; continue; @@ -2340,6 +2463,7 @@ struct CxxrtlBackend : public Backend { extra_args(f, filename, args, argidx); switch (opt_level) { + // the highest level here must match DEFAULT_OPT_LEVEL case 5: worker.max_opt_level = true; worker.run_proc_flatten = true; @@ -2362,6 +2486,17 @@ struct CxxrtlBackend : public Backend { log_cmd_error("Invalid optimization level %d.\n", opt_level); } + switch (debug_level) { + // the highest level here must match DEFAULT_DEBUG_LEVEL + case 1: + worker.debug_info = true; + YS_FALLTHROUGH + case 0: + break; + default: + log_cmd_error("Invalid optimization level %d.\n", opt_level); + } + std::ofstream intf_f; if (worker.split_intf) { if (filename == "<stdout>") diff --git a/backends/cxxrtl/cxxrtl_capi.cc b/backends/cxxrtl/cxxrtl_capi.cc new file mode 100644 index 000000000..489d72da5 --- /dev/null +++ b/backends/cxxrtl/cxxrtl_capi.cc @@ -0,0 +1,60 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2020 whitequark <whitequark@whitequark.org> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +// This file is a part of the CXXRTL C API. It should be used together with `cxxrtl_capi.h`. + +#include <backends/cxxrtl/cxxrtl.h> +#include <backends/cxxrtl/cxxrtl_capi.h> + +struct _cxxrtl_handle { + std::unique_ptr<cxxrtl::module> module; + cxxrtl::debug_items objects; +}; + +// Private function for use by other units of the C API. +const cxxrtl::debug_items &cxxrtl_debug_items_from_handle(cxxrtl_handle handle) { + return handle->objects; +} + +cxxrtl_handle cxxrtl_create(cxxrtl_toplevel design) { + cxxrtl_handle handle = new _cxxrtl_handle; + handle->module = std::move(design->module); + handle->module->debug_info(handle->objects); + delete design; + return handle; +} + +void cxxrtl_destroy(cxxrtl_handle handle) { + delete handle; +} + +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; +} + +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)); +} diff --git a/backends/cxxrtl/cxxrtl_capi.h b/backends/cxxrtl/cxxrtl_capi.h new file mode 100644 index 000000000..bee5a94c7 --- /dev/null +++ b/backends/cxxrtl/cxxrtl_capi.h @@ -0,0 +1,151 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2020 whitequark <whitequark@whitequark.org> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef CXXRTL_CAPI_H +#define CXXRTL_CAPI_H + +// This file is a part of the CXXRTL C API. It should be used together with `cxxrtl_capi.cc`. +// +// The CXXRTL C API makes it possible to drive CXXRTL designs using C or any other language that +// supports the C ABI, for example, Python. It does not provide a way to implement black boxes. + +#include <stddef.h> +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +// Opaque reference to a design toplevel. +// +// A design toplevel can only be used to create a design handle. +typedef struct _cxxrtl_toplevel *cxxrtl_toplevel; + +// The constructor for a design toplevel is provided as a part of generated code for that design. +// Its prototype matches: +// +// cxxrtl_toplevel <design-name>_create(); + +// Opaque reference to a design handle. +// +// A design handle is required by all operations in the C API. +typedef struct _cxxrtl_handle *cxxrtl_handle; + +// Create a design handle from a design toplevel. +// +// The `design` is consumed by this operation and cannot be used afterwards. +cxxrtl_handle cxxrtl_create(cxxrtl_toplevel design); + +// Release all resources used by a design and its handle. +void cxxrtl_destroy(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. +enum cxxrtl_type { + // Values correspond to singly buffered netlist nodes, i.e. nodes driven exclusively by + // combinatorial cells, or toplevel input nodes. + // + // Values can be inspected via the `curr` pointer and modified via the `next` pointer (which are + // equal for values); however, note that changes to the bits driven by combinatorial cells will + // be ignored. + // + // Values always have depth 1. + 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. + // + // Wires can be inspected via the `curr` pointer and modified via the `next` pointer (which are + // distinct for wires); however, note that changes to the bits driven by combinatorial cells will + // be ignored. + // + // Wires always have depth 1. + CXXRTL_WIRE = 1, + + // Memories correspond to memory cells. + // + // Memories can be inspected and modified via the `curr` pointer. Due to a limitation of this + // API, memories cannot yet be modified in a guaranteed race-free way, and the `next` pointer is + // always NULL. + CXXRTL_MEMORY = 2, + + // More object types will be added in the future, but the existing ones will never change. +}; + +// Description of a simulated object. +// +// The `data` array can be accessed directly to inspect and, if applicable, modify the bits +// stored in the object. +struct cxxrtl_object { + // Type of the object. + // + // All objects have the same memory layout determined by `width` and `depth`, but the type + // determines all other properties of the object. + uint32_t type; // actually `enum cxxrtl_type` + + // Width of the object in bits. + size_t width; + + // Depth of the object. Only meaningful for memories; for other objects, always 1. + size_t depth; + + // 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 + // the simulation code, and must be always written as 0 when modified by user code. + // In memories, every element is stored contiguously. Therefore, the total number of chunks + // in any object is `((width + 31) / 32) * depth`. + // + // To allow the simulation to be partitioned into multiple independent units communicating + // through wires, the bits are double buffered. To avoid race conditions, user code should + // always read from `curr` and write to `next`. The `curr` pointer is always valid; for objects + // that cannot be modified, or cannot be modified in a race-free way, `next` is NULL. + uint32_t *curr; + uint32_t *next; + + // More description fields will be added in the future, but the existing ones will never change. +}; + +// Retrieve description of a simulated object. +// +// The `name` is the full hierarchical name of the object in the Yosys notation, where public names +// have a `\` prefix and hierarchy levels are separated by single spaces. For example, if +// 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); + +// 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. +// 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)); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/backends/cxxrtl/cxxrtl_vcd.h b/backends/cxxrtl/cxxrtl_vcd.h new file mode 100644 index 000000000..5706917ca --- /dev/null +++ b/backends/cxxrtl/cxxrtl_vcd.h @@ -0,0 +1,214 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2020 whitequark <whitequark@whitequark.org> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef CXXRTL_VCD_H +#define CXXRTL_VCD_H + +#include <backends/cxxrtl/cxxrtl.h> + +namespace cxxrtl { + +class vcd_writer { + struct variable { + size_t ident; + size_t width; + chunk_t *curr; + size_t prev_off; + }; + + std::vector<std::string> current_scope; + std::vector<variable> variables; + std::vector<chunk_t> cache; + bool streaming = false; + + void emit_timescale(unsigned number, const std::string &unit) { + assert(!streaming); + assert(number == 1 || number == 10 || number == 100); + assert(unit == "s" || unit == "ms" || unit == "us" || + unit == "ns" || unit == "ps" || unit == "fs"); + buffer += "$timescale " + std::to_string(number) + " " + unit + " $end\n"; + } + + void emit_scope(const std::vector<std::string> &scope) { + assert(!streaming); + while (current_scope.size() > scope.size() || + (current_scope.size() > 0 && + current_scope[current_scope.size() - 1] != scope[current_scope.size() - 1])) { + buffer += "$upscope $end\n"; + current_scope.pop_back(); + } + while (current_scope.size() < scope.size()) { + buffer += "$scope module " + scope[current_scope.size()] + " $end\n"; + current_scope.push_back(scope[current_scope.size()]); + } + } + + void emit_ident(size_t ident) { + do { + buffer += '!' + ident % 94; // "base94" + ident /= 94; + } while (ident != 0); + } + + void emit_var(const variable &var, const std::string &type, const std::string &name) { + assert(!streaming); + buffer += "$var " + type + " " + std::to_string(var.width) + " "; + emit_ident(var.ident); + buffer += " " + name + " $end\n"; + } + + void emit_enddefinitions() { + assert(!streaming); + buffer += "$enddefinitions $end\n"; + streaming = true; + } + + void emit_time(uint64_t timestamp) { + assert(streaming); + buffer += "#" + std::to_string(timestamp) + "\n"; + } + + void emit_scalar(const variable &var) { + assert(streaming); + assert(var.width == 1); + buffer += (*var.curr ? '1' : '0'); + emit_ident(var.ident); + buffer += '\n'; + } + + void emit_vector(const variable &var) { + assert(streaming); + buffer += 'b'; + for (size_t bit = var.width - 1; bit != (size_t)-1; bit--) { + bool bit_curr = var.curr[bit / (8 * sizeof(chunk_t))] & (1 << (bit % (8 * sizeof(chunk_t)))); + buffer += (bit_curr ? '1' : '0'); + } + buffer += ' '; + emit_ident(var.ident); + buffer += '\n'; + } + + void append_variable(size_t width, chunk_t *curr) { + const size_t chunks = (width + (sizeof(chunk_t) * 8 - 1)) / (sizeof(chunk_t) * 8); + variables.emplace_back(variable { variables.size(), width, curr, cache.size() }); + cache.insert(cache.end(), &curr[0], &curr[chunks]); + } + + bool test_variable(const variable &var) { + 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; + } else { + std::copy(&var.curr[0], &var.curr[chunks], &cache[var.prev_off]); + return true; + } + } + + static std::vector<std::string> split_hierarchy(const std::string &hier_name) { + std::vector<std::string> hierarchy; + size_t prev = 0; + while (true) { + size_t curr = hier_name.find_first_of(' ', prev + 1); + if (curr > hier_name.size()) + curr = hier_name.size(); + if (curr > prev + 1) + hierarchy.push_back(hier_name.substr(prev, curr - prev)); + if (curr == hier_name.size()) + break; + prev = curr + 1; + } + return hierarchy; + } + +public: + std::string buffer; + + void timescale(unsigned number, const std::string &unit) { + emit_timescale(number, unit); + } + + void add(const std::string &hier_name, const debug_item &item) { + std::vector<std::string> scope = split_hierarchy(hier_name); + std::string name = scope.back(); + scope.pop_back(); + + emit_scope(scope); + switch (item.type) { + // Not the best naming but oh well... + case debug_item::VALUE: + append_variable(item.width, item.curr); + emit_var(variables.back(), "wire", name); + break; + case debug_item::WIRE: + append_variable(item.width, item.curr); + emit_var(variables.back(), "reg", name); + 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) + ']'; + append_variable(item.width, nth_curr); + emit_var(variables.back(), "reg", nth_name); + } + break; + } + } + } + + template<class Filter> + 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); + } + + void add(const debug_items &items) { + this->template add(items, [](const std::string &, const debug_item &) { + return true; + }); + } + + 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; + }); + } + + void sample(uint64_t timestamp) { + bool first_sample = !streaming; + if (first_sample) { + emit_scope({}); + emit_enddefinitions(); + } + emit_time(timestamp); + for (auto var : variables) + if (test_variable(var) || first_sample) { + if (var.width == 1) + emit_scalar(var); + else + emit_vector(var); + } + } +}; + +} + +#endif diff --git a/backends/cxxrtl/cxxrtl_vcd_capi.cc b/backends/cxxrtl/cxxrtl_vcd_capi.cc new file mode 100644 index 000000000..46e4f1c45 --- /dev/null +++ b/backends/cxxrtl/cxxrtl_vcd_capi.cc @@ -0,0 +1,83 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2020 whitequark <whitequark@whitequark.org> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +// This file is a part of the CXXRTL C API. It should be used together with `cxxrtl_vcd_capi.h`. + +#include <backends/cxxrtl/cxxrtl_vcd.h> +#include <backends/cxxrtl/cxxrtl_vcd_capi.h> + +extern const cxxrtl::debug_items &cxxrtl_debug_items_from_handle(cxxrtl_handle handle); + +struct _cxxrtl_vcd { + cxxrtl::vcd_writer writer; + bool flush = false; +}; + +cxxrtl_vcd cxxrtl_vcd_create() { + return new _cxxrtl_vcd; +} + +void cxxrtl_vcd_destroy(cxxrtl_vcd vcd) { + delete vcd; +} + +void cxxrtl_vcd_timescale(cxxrtl_vcd vcd, int number, const char *unit) { + vcd->writer.timescale(number, unit); +} + +void cxxrtl_vcd_add(cxxrtl_vcd vcd, const char *name, cxxrtl_object *object) { + // Note the copy. We don't know whether `object` came from a design (in which case it is + // an instance of `debug_item`), or from user code (in which case it is an instance of + // `cxxrtl_object`), so casting the pointer wouldn't be safe. + vcd->writer.add(name, debug_item(*object)); +} + +void cxxrtl_vcd_add_from(cxxrtl_vcd vcd, cxxrtl_handle handle) { + vcd->writer.add(cxxrtl_debug_items_from_handle(handle)); +} + +void cxxrtl_vcd_add_from_if(cxxrtl_vcd vcd, cxxrtl_handle handle, void *data, + int (*filter)(void *data, const char *name, + const cxxrtl_object *object)) { + vcd->writer.add(cxxrtl_debug_items_from_handle(handle), + [=](const std::string &name, const debug_item &item) { + return filter(data, name.c_str(), static_cast<const cxxrtl_object*>(&item)); + }); +} + +void cxxrtl_vcd_add_from_without_memories(cxxrtl_vcd vcd, cxxrtl_handle handle) { + vcd->writer.add_without_memories(cxxrtl_debug_items_from_handle(handle)); +} + +void cxxrtl_vcd_sample(cxxrtl_vcd vcd, uint64_t time) { + if (vcd->flush) { + vcd->writer.buffer.clear(); + vcd->flush = false; + } + vcd->writer.sample(time); +} + +void cxxrtl_vcd_read(cxxrtl_vcd vcd, const char **data, size_t *size) { + if (vcd->flush) { + vcd->writer.buffer.clear(); + vcd->flush = false; + } + *data = vcd->writer.buffer.c_str(); + *size = vcd->writer.buffer.size(); + vcd->flush = true; +} diff --git a/backends/cxxrtl/cxxrtl_vcd_capi.h b/backends/cxxrtl/cxxrtl_vcd_capi.h new file mode 100644 index 000000000..6a7fb9f47 --- /dev/null +++ b/backends/cxxrtl/cxxrtl_vcd_capi.h @@ -0,0 +1,107 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2020 whitequark <whitequark@whitequark.org> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef CXXRTL_VCD_CAPI_H +#define CXXRTL_VCD_CAPI_H + +// This file is a part of the CXXRTL C API. It should be used together with `cxxrtl_vcd_capi.cc`. +// +// The CXXRTL C API for VCD writing makes it possible to insert virtual probes into designs and +// dump waveforms to Value Change Dump files. + +#include <stddef.h> +#include <stdint.h> + +#include <backends/cxxrtl/cxxrtl_capi.h> + +#ifdef __cplusplus +extern "C" { +#endif + +// Opaque reference to a VCD writer. +typedef struct _cxxrtl_vcd *cxxrtl_vcd; + +// Create a VCD writer. +cxxrtl_vcd cxxrtl_vcd_create(); + +// Release all resources used by a VCD writer. +void cxxrtl_vcd_destroy(cxxrtl_vcd vcd); + +// Set VCD timescale. +// +// The `number` must be 1, 10, or 100, and the `unit` must be one of `"s"`, `"ms"`, `"us"`, `"ns"`, +// `"ps"`, or `"fs"`. +// +// Timescale can only be set before the first call to `cxxrtl_vcd_sample`. +void cxxrtl_vcd_timescale(cxxrtl_vcd vcd, int number, const char *unit); + +// Schedule a specific CXXRTL object to be sampled. +// +// The `name` is a full hierarchical name as described for `cxxrtl_get`; it does not need to match +// the original name of `object`, if any. The `object` must outlive the VCD writer, but there are +// no other requirements; if desired, it can be provided by user code, rather than come from +// a design. +// +// Objects can only be scheduled before the first call to `cxxrtl_vcd_sample`. +void cxxrtl_vcd_add(cxxrtl_vcd vcd, const char *name, struct cxxrtl_object *object); + +// Schedule all CXXRTL objects in a simulation. +// +// The design `handle` must outlive the VCD writer. +// +// Objects can only be scheduled before the first call to `cxxrtl_vcd_sample`. +void cxxrtl_vcd_add_from(cxxrtl_vcd vcd, cxxrtl_handle handle); + +// Schedule CXXRTL objects in a simulation that match a given predicate. +// +// For every object in the simulation, `filter` is called with the provided `data`, the full +// hierarchical name of the object (see `cxxrtl_get` for details), and the object description. +// The object will be sampled if the predicate returns a non-zero value. +// +// 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)); + +// Schedule all CXXRTL objects in a simulation except for memories. +// +// The design `handle` must outlive the VCD writer. +// +// Objects can only be scheduled before the first call to `cxxrtl_vcd_sample`. +void cxxrtl_vcd_add_from_without_memories(cxxrtl_vcd vcd, cxxrtl_handle handle); + +// Sample all scheduled objects. +// +// First, `time` is written to the internal buffer. Second, the values of every signal changed since +// the previous call to `cxxrtl_vcd_sample` (all values if this is the first call) are written to +// the internal buffer. The contents of the buffer can be retrieved with `cxxrtl_vcd_read`. +void cxxrtl_vcd_sample(cxxrtl_vcd vcd, uint64_t time); + +// Retrieve buffered VCD data. +// +// The pointer to the start of the next chunk of VCD data is assigned to `*data`, and the length +// of that chunk is assigned to `*size`. The pointer to the data is valid until the next call to +// `cxxrtl_vcd_sample` or `cxxrtl_vcd_read`. Once all of the buffered data has been retrieved, +// this function will always return zero sized chunks. +void cxxrtl_vcd_read(cxxrtl_vcd vcd, const char **data, size_t *size); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/backends/firrtl/firrtl.cc b/backends/firrtl/firrtl.cc index f6dae1d8c..b1d8500bb 100644 --- a/backends/firrtl/firrtl.cc +++ b/backends/firrtl/firrtl.cc @@ -392,7 +392,34 @@ struct FirrtlWorker return result; } - void run() + 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); f << stringf(" module %s: %s\n", make_id(module->name), moduleFileinfo.c_str()); @@ -446,7 +473,7 @@ struct FirrtlWorker string y_id = make_id(cell->name); std::string cellFileinfo = getFileinfo(cell); - if (cell->type.in(ID($not), ID($logic_not), ID($neg), ID($reduce_and), ID($reduce_or), ID($reduce_xor), ID($reduce_bool), ID($reduce_xnor))) + 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())); @@ -462,7 +489,7 @@ struct FirrtlWorker // Assume the FIRRTL width is a single bit. firrtl_width = 1; - if (cell->type == ID($not)) primop = "not"; + if (cell->type.in(ID($not), ID($_NOT_))) primop = "not"; else if (cell->type == ID($neg)) { primop = "neg"; firrtl_is_signed = true; // Result of "neg" is signed (an SInt). @@ -494,7 +521,7 @@ struct FirrtlWorker continue; } - if (cell->type.in(ID($add), ID($sub), ID($mul), ID($div), ID($mod), ID($xor), ID($xnor), ID($and), ID($or), ID($eq), ID($eqx), + if (cell->type.in(ID($add), ID($sub), ID($mul), ID($div), ID($mod), ID($xor), ID($_XOR_), ID($xnor), ID($and), ID($_AND_), ID($or), ID($_OR_), ID($eq), ID($eqx), ID($gt), ID($ge), ID($lt), ID($le), ID($ne), ID($nex), ID($shr), ID($sshr), ID($sshl), ID($shl), ID($logic_and), ID($logic_or), ID($pow))) { @@ -524,7 +551,7 @@ struct FirrtlWorker // For the arithmetic ops, expand operand widths to result widths befor performing the operation. // This corresponds (according to iverilog) to what verilog compilers implement. - if (cell->type.in(ID($add), ID($sub), ID($mul), ID($div), ID($mod), ID($xor), ID($xnor), ID($and), ID($or))) + if (cell->type.in(ID($add), ID($sub), ID($mul), ID($div), ID($mod), ID($xor), ID($_XOR_), ID($xnor), ID($and), ID($_AND_), ID($or), ID($_OR_))) { if (a_width < y_width) { a_expr = stringf("pad(%s, %d)", a_expr.c_str(), y_width); @@ -558,19 +585,20 @@ struct FirrtlWorker firrtl_is_signed = a_signed | b_signed; firrtl_width = a_width; } else if (cell->type == ID($mod)) { + // "rem" = truncating modulo primop = "rem"; firrtl_width = min(a_width, b_width); - } else if (cell->type == ID($and)) { + } else if (cell->type.in(ID($and), ID($_AND_))) { primop = "and"; always_uint = true; firrtl_width = max(a_width, b_width); } - else if (cell->type == ID($or) ) { + else if (cell->type.in(ID($or), ID($_OR_))) { primop = "or"; always_uint = true; firrtl_width = max(a_width, b_width); } - else if (cell->type == ID($xor)) { + else if (cell->type.in(ID($xor), ID($_XOR_))) { primop = "xor"; always_uint = true; firrtl_width = max(a_width, b_width); @@ -694,7 +722,8 @@ struct FirrtlWorker } } - if (!cell->parameters.at(ID::B_SIGNED).as_bool()) { + auto it = cell->parameters.find(ID::B_SIGNED); + if (it == cell->parameters.end() || !it->second.as_bool()) { b_expr = "asUInt(" + b_expr + ")"; } @@ -723,9 +752,10 @@ struct FirrtlWorker continue; } - if (cell->type.in(ID($mux))) + if (cell->type.in(ID($mux), ID($_MUX_))) { - int width = cell->parameters.at(ID::WIDTH).as_int(); + auto it = cell->parameters.find(ID::WIDTH); + int width = it == cell->parameters.end()? 1 : it->second.as_int(); 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)); @@ -1076,6 +1106,18 @@ struct FirrtlWorker for (auto str : wire_exprs) f << str; + + f << stringf("\n"); + } + + 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(); } }; diff --git a/backends/ilang/ilang_backend.cc b/backends/ilang/ilang_backend.cc index 6e3882d2d..3a418de3c 100644 --- a/backends/ilang/ilang_backend.cc +++ b/backends/ilang/ilang_backend.cc @@ -131,6 +131,8 @@ void ILANG_BACKEND::dump_wire(std::ostream &f, std::string indent, const RTLIL:: f << stringf("output %d ", wire->port_id); if (wire->port_input && wire->port_output) f << stringf("inout %d ", wire->port_id); + if (wire->is_signed) + f << stringf("signed "); f << stringf("%s\n", wire->name.c_str()); } diff --git a/backends/json/json.cc b/backends/json/json.cc index 1a8b757ef..5edc50f60 100644 --- a/backends/json/json.cc +++ b/backends/json/json.cc @@ -160,6 +160,8 @@ struct JsonWriter f << stringf(" \"offset\": %d,\n", w->start_offset); if (w->upto) f << stringf(" \"upto\": 1,\n"); + if (w->is_signed) + f << stringf(" \"signed\": %d,\n", w->is_signed); f << stringf(" \"bits\": %s\n", get_bits(w).c_str()); f << stringf(" }"); first = false; @@ -227,6 +229,8 @@ struct JsonWriter f << stringf(" \"offset\": %d,\n", w->start_offset); if (w->upto) f << stringf(" \"upto\": 1,\n"); + if (w->is_signed) + f << stringf(" \"signed\": %d,\n", w->is_signed); f << stringf(" \"attributes\": {"); write_parameters(w->attributes); f << stringf("\n }\n"); diff --git a/backends/smt2/smt2.cc b/backends/smt2/smt2.cc index 3e67e55f2..26f17bcb3 100644 --- a/backends/smt2/smt2.cc +++ b/backends/smt2/smt2.cc @@ -590,7 +590,17 @@ struct Smt2Worker if (cell->type == ID($sub)) return export_bvop(cell, "(bvsub A B)"); if (cell->type == ID($mul)) return export_bvop(cell, "(bvmul A B)"); if (cell->type == ID($div)) return export_bvop(cell, "(bvUdiv A B)", 'd'); + // "rem" = truncating modulo if (cell->type == ID($mod)) return export_bvop(cell, "(bvUrem A B)", 'd'); + // "mod" = flooring modulo + if (cell->type == ID($modfloor)) { + // bvumod doesn't exist because it's the same as bvurem + if (cell->getParam(ID::A_SIGNED).as_bool()) { + return export_bvop(cell, "(bvsmod A B)", 'd'); + } else { + return export_bvop(cell, "(bvurem A B)", 'd'); + } + } if (cell->type.in(ID($reduce_and), ID($reduce_or), ID($reduce_bool)) && 2*GetSize(cell->getPort(ID::A).chunks()) < GetSize(cell->getPort(ID::A))) { diff --git a/backends/smt2/smtbmc.py b/backends/smt2/smtbmc.py index cc3ebb129..03f001bfd 100644 --- a/backends/smt2/smtbmc.py +++ b/backends/smt2/smtbmc.py @@ -1557,8 +1557,9 @@ else: # not tempind, covermode smt_assert(get_constr_expr(constr_asserts, i)) print_msg("Solving for step %d.." % (last_check_step)) - if smt_check_sat() != "sat": - print("%s No solution found!" % smt.timestamp()) + status = smt_check_sat() + if status != "sat": + print("%s No solution found! (%s)" % (smt.timestamp(), status)) retstatus = "FAILED" break diff --git a/backends/smt2/smtio.py b/backends/smt2/smtio.py index 69f59df79..72ab39d39 100644 --- a/backends/smt2/smtio.py +++ b/backends/smt2/smtio.py @@ -39,7 +39,7 @@ if os.name == "posix": smtio_stacksize = 128 * 1024 * 1024 if os.uname().sysname == "Darwin": # MacOS has rather conservative stack limits - smtio_stacksize = 16 * 1024 * 1024 + smtio_stacksize = 8 * 1024 * 1024 if current_rlimit_stack[1] != resource.RLIM_INFINITY: smtio_stacksize = min(smtio_stacksize, current_rlimit_stack[1]) if current_rlimit_stack[0] < smtio_stacksize: @@ -121,6 +121,7 @@ class SmtIo: self.logic_bv = True self.logic_dt = False self.forall = False + self.timeout = 0 self.produce_models = True self.smt2cache = [list()] self.p = None @@ -135,6 +136,7 @@ class SmtIo: self.debug_file = opts.debug_file self.dummy_file = opts.dummy_file self.timeinfo = opts.timeinfo + self.timeout = opts.timeout self.unroll = opts.unroll self.noincr = opts.noincr self.info_stmts = opts.info_stmts @@ -147,6 +149,7 @@ class SmtIo: self.debug_file = None self.dummy_file = None self.timeinfo = os.name != "nt" + self.timeout = 0 self.unroll = False self.noincr = False self.info_stmts = list() @@ -172,22 +175,32 @@ class SmtIo: self.unroll = False if self.solver == "yices": - if self.noincr: + if self.noincr or self.forall: self.popen_vargs = ['yices-smt2'] + self.solver_opts else: self.popen_vargs = ['yices-smt2', '--incremental'] + self.solver_opts + if self.timeout != 0: + self.popen_vargs.append('-t') + self.popen_vargs.append('%d' % self.timeout); if self.solver == "z3": self.popen_vargs = ['z3', '-smt2', '-in'] + self.solver_opts + if self.timeout != 0: + self.popen_vargs.append('-T:%d' % self.timeout); if self.solver == "cvc4": if self.noincr: self.popen_vargs = ['cvc4', '--lang', 'smt2.6' if self.logic_dt else 'smt2'] + self.solver_opts else: self.popen_vargs = ['cvc4', '--incremental', '--lang', 'smt2.6' if self.logic_dt else 'smt2'] + self.solver_opts + if self.timeout != 0: + self.popen_vargs.append('--tlimit=%d000' % self.timeout); if self.solver == "mathsat": self.popen_vargs = ['mathsat'] + self.solver_opts + if self.timeout != 0: + print('timeout option is not supported for mathsat.') + sys.exit(1) if self.solver == "boolector": if self.noincr: @@ -195,6 +208,9 @@ class SmtIo: else: self.popen_vargs = ['boolector', '--smt2', '-i'] + self.solver_opts self.unroll = True + if self.timeout != 0: + print('timeout option is not supported for boolector.') + sys.exit(1) if self.solver == "abc": if len(self.solver_opts) > 0: @@ -204,6 +220,9 @@ class SmtIo: self.logic_ax = False self.unroll = True self.noincr = True + if self.timeout != 0: + print('timeout option is not supported for abc.') + sys.exit(1) if self.solver == "dummy": assert self.dummy_file is not None @@ -232,12 +251,16 @@ class SmtIo: if self.logic_uf: self.logic += "UF" if self.logic_bv: self.logic += "BV" if self.logic_dt: self.logic = "ALL" + if self.solver == "yices" and self.forall: self.logic = "BV" self.setup_done = True 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)") @@ -706,7 +729,7 @@ class SmtIo: if self.forall: result = self.read() - while result not in ["sat", "unsat", "unknown"]: + while result not in ["sat", "unsat", "unknown", "timeout", "interrupted", ""]: print("%s %s: %s" % (self.timestamp(), self.solver, result)) result = self.read() else: @@ -717,7 +740,7 @@ class SmtIo: print("(check-sat)", file=self.debug_file) self.debug_file.flush() - if result not in ["sat", "unsat"]: + if result not in ["sat", "unsat", "unknown", "timeout", "interrupted"]: if result == "": print("%s Unexpected EOF response from solver." % (self.timestamp()), flush=True) else: @@ -927,7 +950,7 @@ class SmtIo: class SmtOpts: def __init__(self): self.shortopts = "s:S:v" - self.longopts = ["unroll", "noincr", "noprogress", "dump-smt2=", "logic=", "dummy=", "info=", "nocomments"] + self.longopts = ["unroll", "noincr", "noprogress", "timeout=", "dump-smt2=", "logic=", "dummy=", "info=", "nocomments"] self.solver = "yices" self.solver_opts = list() self.debug_print = False @@ -936,6 +959,7 @@ class SmtOpts: self.unroll = False self.noincr = False self.timeinfo = os.name != "nt" + self.timeout = 0 self.logic = None self.info_stmts = list() self.nocomments = False @@ -945,6 +969,8 @@ class SmtOpts: self.solver = a elif o == "-S": self.solver_opts.append(a) + elif o == "--timeout": + self.timeout = int(a) elif o == "-v": self.debug_print = True elif o == "--unroll": @@ -976,6 +1002,9 @@ class SmtOpts: -S <opt> pass <opt> as command line argument to the solver + --timeout <value> + set the solver timeout to the specified value (in seconds). + --logic <smt2_logic> use the specified SMT2 logic (e.g. QF_AUFBV) diff --git a/backends/smv/smv.cc b/backends/smv/smv.cc index 7113ebc97..2fc7099f4 100644 --- a/backends/smv/smv.cc +++ b/backends/smv/smv.cc @@ -358,7 +358,8 @@ struct SmvWorker continue; } - if (cell->type.in(ID($div), ID($mod))) + // SMV has a "mod" operator, but its semantics don't seem to be well-defined - to be safe, don't generate it at all + if (cell->type.in(ID($div)/*, ID($mod), ID($modfloor)*/)) { int width_y = GetSize(cell->getPort(ID::Y)); int width = max(width_y, GetSize(cell->getPort(ID::A))); @@ -366,7 +367,7 @@ struct SmvWorker string expr_a, expr_b, op; if (cell->type == ID($div)) op = "/"; - if (cell->type == ID($mod)) op = "mod"; + //if (cell->type == ID($mod)) op = "mod"; if (cell->getParam(ID::A_SIGNED).as_bool()) { diff --git a/backends/smv/test_cells.sh b/backends/smv/test_cells.sh index 63de465c0..145b9c33b 100644 --- a/backends/smv/test_cells.sh +++ b/backends/smv/test_cells.sh @@ -7,8 +7,8 @@ mkdir -p test_cells.tmp cd test_cells.tmp # don't test $mul to reduce runtime -# don't test $div and $mod to reduce runtime and avoid "div by zero" message -../../../yosys -p 'test_cell -n 5 -w test all /$alu /$fa /$lcu /$lut /$macc /$mul /$div /$mod' +# don't test $div/$mod/$divfloor/$modfloor to reduce runtime and avoid "div by zero" message +../../../yosys -p 'test_cell -n 5 -w test all /$alu /$fa /$lcu /$lut /$macc /$mul /$div /$mod /$divfloor /$modfloor' cat > template.txt << "EOT" %module main diff --git a/backends/verilog/verilog_backend.cc b/backends/verilog/verilog_backend.cc index 11b2ae10f..4f44a053a 100644 --- a/backends/verilog/verilog_backend.cc +++ b/backends/verilog/verilog_backend.cc @@ -740,6 +740,95 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) #undef HANDLE_UNIOP #undef HANDLE_BINOP + if (cell->type == ID($divfloor)) + { + // wire [MAXLEN+1:0] _0_, _1_, _2_; + // assign _0_ = $signed(A); + // assign _1_ = $signed(B); + // assign _2_ = (A[-1] == B[-1]) || A == 0 ? _0_ : $signed(_0_ - (B[-1] ? _1_ + 1 : _1_ - 1)); + // assign Y = $signed(_2_) / $signed(_1_); + + if (cell->getParam(ID::A_SIGNED).as_bool() && cell->getParam(ID::B_SIGNED).as_bool()) { + SigSpec sig_a = cell->getPort(ID::A); + SigSpec sig_b = cell->getPort(ID::B); + + std::string buf_a = next_auto_id(); + std::string buf_b = next_auto_id(); + std::string buf_num = next_auto_id(); + int size_a = GetSize(sig_a); + int size_b = GetSize(sig_b); + int size_y = GetSize(cell->getPort(ID::Y)); + int size_max = std::max(size_a, std::max(size_b, size_y)); + + // intentionally one wider than maximum width + f << stringf("%s" "wire [%d:0] %s, %s, %s;\n", indent.c_str(), size_max, buf_a.c_str(), buf_b.c_str(), buf_num.c_str()); + f << stringf("%s" "assign %s = ", indent.c_str(), buf_a.c_str()); + dump_cell_expr_port(f, cell, "A", true); + f << stringf(";\n"); + f << stringf("%s" "assign %s = ", indent.c_str(), buf_b.c_str()); + dump_cell_expr_port(f, cell, "B", true); + f << stringf(";\n"); + + f << stringf("%s" "assign %s = ", indent.c_str(), buf_num.c_str()); + f << stringf("("); + dump_sigspec(f, sig_a.extract(sig_a.size()-1)); + f << stringf(" == "); + dump_sigspec(f, sig_b.extract(sig_b.size()-1)); + f << stringf(") || "); + dump_sigspec(f, sig_a); + f << stringf(" == 0 ? %s : ", buf_a.c_str()); + f << stringf("$signed(%s - (", buf_a.c_str()); + dump_sigspec(f, sig_b.extract(sig_b.size()-1)); + f << stringf(" ? %s + 1 : %s - 1));\n", buf_b.c_str(), buf_b.c_str()); + + + f << stringf("%s" "assign ", indent.c_str()); + dump_sigspec(f, cell->getPort(ID::Y)); + f << stringf(" = $signed(%s) / ", buf_num.c_str()); + dump_attributes(f, "", cell->attributes, ' '); + f << stringf("$signed(%s);\n", buf_b.c_str()); + return true; + } else { + // same as truncating division + dump_cell_expr_binop(f, indent, cell, "/"); + return true; + } + } + + if (cell->type == ID($modfloor)) + { + // wire truncated = $signed(A) % $signed(B); + // assign Y = (A[-1] == B[-1]) || truncated == 0 ? truncated : $signed(B) + $signed(truncated); + + if (cell->getParam(ID::A_SIGNED).as_bool() && cell->getParam(ID::B_SIGNED).as_bool()) { + SigSpec sig_a = cell->getPort(ID::A); + SigSpec sig_b = cell->getPort(ID::B); + + std::string temp_id = next_auto_id(); + f << stringf("%s" "wire [%d:0] %s = ", indent.c_str(), GetSize(cell->getPort(ID::A))-1, temp_id.c_str()); + dump_cell_expr_port(f, cell, "A", true); + f << stringf(" %% "); + dump_attributes(f, "", cell->attributes, ' '); + dump_cell_expr_port(f, cell, "B", true); + f << stringf(";\n"); + + f << stringf("%s" "assign ", indent.c_str()); + dump_sigspec(f, cell->getPort(ID::Y)); + f << stringf(" = ("); + dump_sigspec(f, sig_a.extract(sig_a.size()-1)); + f << stringf(" == "); + dump_sigspec(f, sig_b.extract(sig_b.size()-1)); + f << stringf(") || %s == 0 ? %s : ", temp_id.c_str(), temp_id.c_str()); + dump_cell_expr_port(f, cell, "B", true); + f << stringf(" + $signed(%s);\n", temp_id.c_str()); + return true; + } else { + // same as truncating modulo + dump_cell_expr_binop(f, indent, cell, "%"); + return true; + } + } + if (cell->type == ID($shift)) { f << stringf("%s" "assign ", indent.c_str()); diff --git a/frontends/aiger/aigerparse.cc b/frontends/aiger/aigerparse.cc index d25587e48..fef788267 100644 --- a/frontends/aiger/aigerparse.cc +++ b/frontends/aiger/aigerparse.cc @@ -775,7 +775,6 @@ void AigerReader::post_process() } } - dict<int, Wire*> mergeability_to_clock; for (uint32_t i = 0; i < flopNum; i++) { RTLIL::Wire *d = outputs[outputs.size() - flopNum + i]; log_assert(d); @@ -895,7 +894,9 @@ void AigerReader::post_process() } else if (type == "box") { RTLIL::Cell* cell = module->cell(stringf("$box%d", variable)); - if (cell) // ABC could have optimised this box away + if (!cell) + log_debug("Box %d (%s) no longer exists.\n", variable, log_id(escaped_s)); + else module->rename(cell, escaped_s); } else @@ -907,6 +908,8 @@ void AigerReader::post_process() auto name = wp.first; int min = wp.second.first; int max = wp.second.second; + if (min == 0 && max == 0) + continue; RTLIL::Wire *wire = module->wire(name); if (wire) diff --git a/frontends/ast/ast.cc b/frontends/ast/ast.cc index 689fa9fb4..03fd272da 100644 --- a/frontends/ast/ast.cc +++ b/frontends/ast/ast.cc @@ -171,6 +171,9 @@ std::string AST::type2str(AstNodeType type) X(AST_PACKAGE) X(AST_WIRETYPE) X(AST_TYPEDEF) + X(AST_STRUCT) + X(AST_UNION) + X(AST_STRUCT_ITEM) #undef X default: log_abort(); diff --git a/frontends/ast/ast.h b/frontends/ast/ast.h index 8932108e3..6d556fae2 100644 --- a/frontends/ast/ast.h +++ b/frontends/ast/ast.h @@ -143,7 +143,7 @@ namespace AST AST_GENCASE, AST_GENBLOCK, AST_TECALL, - + AST_POSEDGE, AST_NEGEDGE, AST_EDGE, @@ -156,7 +156,10 @@ namespace AST AST_PACKAGE, AST_WIRETYPE, - AST_TYPEDEF + AST_TYPEDEF, + AST_STRUCT, + AST_UNION, + AST_STRUCT_ITEM }; struct AstSrcLocType { @@ -306,6 +309,7 @@ namespace AST // helpers for enum void allocateDefaultEnumValues(); + void annotateTypedEnums(AstNode *template_node); }; // process an AST tree (ast must point to an AST_DESIGN node) and generate RTLIL code diff --git a/frontends/ast/genrtlil.cc b/frontends/ast/genrtlil.cc index cdc3adc9c..9546558aa 100644 --- a/frontends/ast/genrtlil.cc +++ b/frontends/ast/genrtlil.cc @@ -991,6 +991,8 @@ RTLIL::SigSpec AstNode::genRTLIL(int width_hint, bool sign_hint) case AST_MODPORT: case AST_MODPORTMEMBER: case AST_TYPEDEF: + case AST_STRUCT: + case AST_UNION: break; case AST_INTERFACEPORT: { // If a port in a module with unknown type is found, mark it with the attribute 'is_interface' @@ -1065,6 +1067,7 @@ RTLIL::SigSpec AstNode::genRTLIL(int width_hint, bool sign_hint) wire->port_input = is_input; wire->port_output = is_output; wire->upto = range_swapped; + wire->is_signed = is_signed; for (auto &attr : attributes) { if (attr.second->type != AST_CONSTANT) diff --git a/frontends/ast/simplify.cc b/frontends/ast/simplify.cc index 3314819fb..6970135d0 100644 --- a/frontends/ast/simplify.cc +++ b/frontends/ast/simplify.cc @@ -168,6 +168,321 @@ std::string AstNode::process_format_str(const std::string &sformat, int next_arg } +void AstNode::annotateTypedEnums(AstNode *template_node) +{ + //check if enum + if (template_node->attributes.count(ID::enum_type)) { + //get reference to enum node: + std::string enum_type = template_node->attributes[ID::enum_type]->str.c_str(); + // log("enum_type=%s (count=%lu)\n", enum_type.c_str(), current_scope.count(enum_type)); + // log("current scope:\n"); + // for (auto &it : current_scope) + // log(" %s\n", it.first.c_str()); + log_assert(current_scope.count(enum_type) == 1); + AstNode *enum_node = current_scope.at(enum_type); + log_assert(enum_node->type == AST_ENUM); + //get width from 1st enum item: + log_assert(enum_node->children.size() >= 1); + AstNode *enum_item0 = enum_node->children[0]; + log_assert(enum_item0->type == AST_ENUM_ITEM); + int width; + if (!enum_item0->range_valid) + width = 1; + else if (enum_item0->range_swapped) + width = enum_item0->range_right - enum_item0->range_left + 1; + else + width = enum_item0->range_left - enum_item0->range_right + 1; + log_assert(width > 0); + //add declared enum items: + for (auto enum_item : enum_node->children){ + log_assert(enum_item->type == AST_ENUM_ITEM); + //get is_signed + bool is_signed; + if (enum_item->children.size() == 1){ + is_signed = false; + } else if (enum_item->children.size() == 2){ + log_assert(enum_item->children[1]->type == AST_RANGE); + is_signed = enum_item->children[1]->is_signed; + } else { + log_error("enum_item children size==%lu, expected 1 or 2 for %s (%s)\n", + enum_item->children.size(), + enum_item->str.c_str(), enum_node->str.c_str() + ); + } + //start building attribute string + std::string enum_item_str = "\\enum_value_"; + //get enum item value + if(enum_item->children[0]->type != AST_CONSTANT){ + log_error("expected const, got %s for %s (%s)\n", + type2str(enum_item->children[0]->type).c_str(), + enum_item->str.c_str(), enum_node->str.c_str() + ); + } + RTLIL::Const val = enum_item->children[0]->bitsAsConst(width, is_signed); + enum_item_str.append(val.as_string()); + //set attribute for available val to enum item name mappings + attributes[enum_item_str.c_str()] = mkconst_str(enum_item->str); + } + } +} + +static bool name_has_dot(const std::string &name, std::string &struct_name) +{ + // check if plausible struct member name \sss.mmm + std::string::size_type pos; + if (name.substr(0, 1) == "\\" && (pos = name.find('.', 0)) != std::string::npos) { + struct_name = name.substr(0, pos); + return true; + } + return false; +} + +static AstNode *make_range(int left, int right, bool is_signed = false) +{ + // generate a pre-validated range node for a fixed signal range. + auto range = new AstNode(AST_RANGE); + range->range_left = left; + range->range_right = right; + range->range_valid = true; + range->children.push_back(AstNode::mkconst_int(left, true)); + range->children.push_back(AstNode::mkconst_int(right, true)); + range->is_signed = is_signed; + return range; +} + +static int range_width(AstNode *node, AstNode *rnode) +{ + log_assert(rnode->type==AST_RANGE); + if (!rnode->range_valid) { + log_file_error(node->filename, node->location.first_line, "Size must be constant in packed struct/union member %s\n", node->str.c_str()); + + } + // note: range swapping has already been checked for + return rnode->range_left - rnode->range_right + 1; +} + +[[noreturn]] static void struct_array_packing_error(AstNode *node) +{ + log_file_error(node->filename, node->location.first_line, "Unpacked array in packed struct/union member %s\n", node->str.c_str()); +} + +static void save_struct_array_width(AstNode *node, int width) +{ + // stash the stride for the array + node->multirange_dimensions.push_back(width); + +} + +static int get_struct_array_width(AstNode *node) +{ + // the stride for the array, 1 if not an array + return (node->multirange_dimensions.empty() ? 1 : node->multirange_dimensions.back()); + +} + +static int size_packed_struct(AstNode *snode, int base_offset) +{ + // Struct members will be laid out in the structure contiguously from left to right. + // Union members all have zero offset from the start of the union. + // Determine total packed size and assign offsets. Store these in the member node. + bool is_union = (snode->type == AST_UNION); + int offset = 0; + int packed_width = -1; + // examine members from last to first + for (auto it = snode->children.rbegin(); it != snode->children.rend(); ++it) { + auto node = *it; + int width; + if (node->type == AST_STRUCT || node->type == AST_UNION) { + // embedded struct or union + width = size_packed_struct(node, base_offset + offset); + } + else { + log_assert(node->type == AST_STRUCT_ITEM); + if (node->children.size() > 0 && node->children[0]->type == AST_RANGE) { + // member width e.g. bit [7:0] a + width = range_width(node, node->children[0]); + if (node->children.size() == 2) { + if (node->children[1]->type == AST_RANGE) { + // unpacked array e.g. bit [63:0] a [0:3] + auto rnode = node->children[1]; + int array_count = range_width(node, rnode); + if (array_count == 1) { + // C-type array size e.g. bit [63:0] a [4] + array_count = rnode->range_left; + } + save_struct_array_width(node, width); + width *= array_count; + } + else { + // array element must be single bit for a packed array + struct_array_packing_error(node); + } + } + // range nodes are now redundant + node->children.clear(); + } + else if (node->children.size() == 1 && node->children[0]->type == AST_MULTIRANGE) { + // packed 2D array, e.g. bit [3:0][63:0] a + auto rnode = node->children[0]; + if (rnode->children.size() != 2) { + // packed arrays can only be 2D + struct_array_packing_error(node); + } + int array_count = range_width(node, rnode->children[0]); + width = range_width(node, rnode->children[1]); + save_struct_array_width(node, width); + width *= array_count; + // range nodes are now redundant + node->children.clear(); + } + else if (node->range_left < 0) { + // 1 bit signal: bit, logic or reg + width = 1; + } + else { + // already resolved and compacted + width = node->range_left - node->range_right + 1; + } + if (is_union) { + node->range_right = base_offset; + node->range_left = base_offset + width - 1; + } + else { + node->range_right = base_offset + offset; + node->range_left = base_offset + offset + width - 1; + } + node->range_valid = true; + } + if (is_union) { + // check that all members have the same size + if (packed_width == -1) { + // first member + packed_width = width; + } + else { + if (packed_width != width) { + + log_file_error(node->filename, node->location.first_line, "member %s of a packed union has %d bits, expecting %d\n", node->str.c_str(), width, packed_width); + } + } + } + else { + offset += width; + } + } + return (is_union ? packed_width : offset); +} + +[[noreturn]] static void struct_op_error(AstNode *node) +{ + log_file_error(node->filename, node->location.first_line, "Unsupported operation for struct/union member %s\n", node->str.c_str()+1); +} + +static AstNode *node_int(int ival) +{ + // maybe mkconst_int should have default values for the common integer case + return AstNode::mkconst_int(ival, true, 32); +} + +static AstNode *offset_indexed_range(int offset_right, int stride, AstNode *left_expr, AstNode *right_expr) +{ + // adjust the range expressions to add an offset into the struct + // and maybe index using an array stride + auto left = left_expr->clone(); + auto right = right_expr->clone(); + if (stride == 1) { + // just add the offset + left = new AstNode(AST_ADD, node_int(offset_right), left); + right = new AstNode(AST_ADD, node_int(offset_right), right); + } + else { + // newleft = offset_right - 1 + (left + 1) * stride + left = new AstNode(AST_ADD, new AstNode(AST_SUB, node_int(offset_right), node_int(1)), + new AstNode(AST_MUL, node_int(stride), new AstNode(AST_ADD, left, node_int(1)))); + // newright = offset_right + right * stride + right = new AstNode(AST_ADD, node_int(offset_right), new AstNode(AST_MUL, right, node_int(stride))); + } + return new AstNode(AST_RANGE, left, right); +} + +static AstNode *make_struct_member_range(AstNode *node, AstNode *member_node) +{ + // Work out the range in the packed array that corresponds to a struct member + // taking into account any range operations applicable to the current node + // such as array indexing or slicing + int range_left = member_node->range_left; + int range_right = member_node->range_right; + if (node->children.empty()) { + // no range operations apply, return the whole width + } + else if (node->children.size() == 1 && node->children[0]->type == AST_RANGE) { + auto rnode = node->children[0]; + int stride = get_struct_array_width(member_node); + if (rnode->children.size() == 1) { + // index e.g. s.a[i] + return offset_indexed_range(range_right, stride, rnode->children[0], rnode->children[0]); + } + else if (rnode->children.size() == 2) { + // slice e.g. s.a[i:j] + return offset_indexed_range(range_right, stride, rnode->children[0], rnode->children[1]); + } + else { + struct_op_error(node); + } + } + else { + // TODO multirange, i.e. bit slice after array index s.a[i][p:q] + struct_op_error(node); + } + return make_range(range_left, range_right); +} + +static void add_members_to_scope(AstNode *snode, std::string name) +{ + // add all the members in a struct or union to local scope + // in case later referenced in assignments + log_assert(snode->type==AST_STRUCT || snode->type==AST_UNION); + for (auto *node : snode->children) { + if (node->type != AST_STRUCT_ITEM) { + // embedded struct or union + add_members_to_scope(node, name + "." + node->str); + } + else { + auto member_name = name + "." + node->str; + current_scope[member_name] = node; + } + } +} + +static int get_max_offset(AstNode *node) +{ + // get the width from the MS member in the struct + // as members are laid out from left to right in the packed wire + log_assert(node->type==AST_STRUCT || node->type==AST_UNION); + while (node->type != AST_STRUCT_ITEM) { + node = node->children[0]; + } + return node->range_left; +} + +static AstNode *make_packed_struct(AstNode *template_node, std::string &name) +{ + // create a wire for the packed struct + auto wnode = new AstNode(AST_WIRE); + wnode->str = name; + wnode->is_logic = true; + wnode->range_valid = true; + wnode->is_signed = template_node->is_signed; + int offset = get_max_offset(template_node); + auto range = make_range(offset, 0); + wnode->children.push_back(range); + // make sure this node is the one in scope for this name + current_scope[name] = wnode; + // add all the struct members to scope under the wire's name + add_members_to_scope(template_node, name); + return wnode; +} + // convert the AST into a simpler AST that has all parameters substituted by their // values, unrolled for-loops, expanded generate blocks, etc. when this function // is done with an AST it can be converted into RTLIL using genRTLIL(). @@ -567,6 +882,32 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage, } break; + case AST_STRUCT: + case AST_UNION: + if (!basic_prep) { + for (auto *node : children) { + // resolve any ranges + while (!node->basic_prep && node->simplify(true, false, false, stage, -1, false, false)) { + did_something = true; + } + } + // determine member offsets and widths + size_packed_struct(this, 0); + + // instance rather than just a type in a typedef or outer struct? + if (!str.empty() && str[0] == '\\') { + // instance so add a wire for the packed structure + auto wnode = make_packed_struct(this, str); + log_assert(current_ast_mod); + current_ast_mod->children.push_back(wnode); + } + basic_prep = true; + } + break; + + case AST_STRUCT_ITEM: + break; + case AST_ENUM: //log("\nENUM %s: %d child %d\n", str.c_str(), basic_prep, children[0]->basic_prep); if (!basic_prep) { @@ -884,10 +1225,12 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage, // resolve typedefs if (type == AST_TYPEDEF) { log_assert(children.size() == 1); - log_assert(children[0]->type == AST_WIRE || children[0]->type == AST_MEMORY); - while(children[0]->simplify(const_fold, at_zero, in_lvalue, stage, width_hint, sign_hint, in_param)) + auto type_node = children[0]; + log_assert(type_node->type == AST_WIRE || type_node->type == AST_MEMORY || type_node->type == AST_STRUCT || type_node->type == AST_UNION); + while (type_node->simplify(const_fold, at_zero, in_lvalue, stage, width_hint, sign_hint, in_param)) { did_something = true; - log_assert(!children[0]->is_custom_type); + } + log_assert(!type_node->is_custom_type); } // resolve types of wires @@ -895,100 +1238,57 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage, if (is_custom_type) { log_assert(children.size() >= 1); log_assert(children[0]->type == AST_WIRETYPE); - if (!current_scope.count(children[0]->str)) - log_file_error(filename, location.first_line, "Unknown identifier `%s' used as type name\n", children[0]->str.c_str()); - AstNode *resolved_type = current_scope.at(children[0]->str); - if (resolved_type->type != AST_TYPEDEF) - log_file_error(filename, location.first_line, "`%s' does not name a type\n", children[0]->str.c_str()); - log_assert(resolved_type->children.size() == 1); - AstNode *templ = resolved_type->children[0]; + auto type_name = children[0]->str; + if (!current_scope.count(type_name)) { + log_file_error(filename, location.first_line, "Unknown identifier `%s' used as type name\n", type_name.c_str()); + } + AstNode *resolved_type_node = current_scope.at(type_name); + if (resolved_type_node->type != AST_TYPEDEF) + log_file_error(filename, location.first_line, "`%s' does not name a type\n", type_name.c_str()); + log_assert(resolved_type_node->children.size() == 1); + AstNode *template_node = resolved_type_node->children[0]; + + // Ensure typedef itself is fully simplified + while (template_node->simplify(const_fold, at_zero, in_lvalue, stage, width_hint, sign_hint, in_param)) {}; + + if (template_node->type == AST_STRUCT || template_node->type == AST_UNION) { + // replace with wire representing the packed structure + newNode = make_packed_struct(template_node, str); + current_scope[str] = this; + goto apply_newNode; + } + // Remove type reference delete children[0]; children.erase(children.begin()); - // Ensure typedef itself is fully simplified - while(templ->simplify(const_fold, at_zero, in_lvalue, stage, width_hint, sign_hint, in_param)) {}; - if (type == AST_WIRE) - type = templ->type; - is_reg = templ->is_reg; - is_logic = templ->is_logic; - is_signed = templ->is_signed; - is_string = templ->is_string; - is_custom_type = templ->is_custom_type; - - range_valid = templ->range_valid; - range_swapped = templ->range_swapped; - range_left = templ->range_left; - range_right = templ->range_right; - attributes[ID::wiretype] = mkconst_str(resolved_type->str); - //check if enum - if (templ->attributes.count(ID::enum_type)){ - //get reference to enum node: - const std::string &enum_type = templ->attributes[ID::enum_type]->str; - // log("enum_type=%s (count=%lu)\n", enum_type.c_str(), current_scope.count(enum_type)); - // log("current scope:\n"); - // for (auto &it : current_scope) - // log(" %s\n", it.first.c_str()); - log_assert(current_scope.count(enum_type) == 1); - AstNode *enum_node = current_scope.at(enum_type); - log_assert(enum_node->type == AST_ENUM); - //get width from 1st enum item: - log_assert(enum_node->children.size() >= 1); - AstNode *enum_item0 = enum_node->children[0]; - log_assert(enum_item0->type == AST_ENUM_ITEM); - int width; - if (!enum_item0->range_valid) - width = 1; - else if (enum_item0->range_swapped) - width = enum_item0->range_right - enum_item0->range_left + 1; - else - width = enum_item0->range_left - enum_item0->range_right + 1; - log_assert(width > 0); - //add declared enum items: - for (auto enum_item : enum_node->children){ - log_assert(enum_item->type == AST_ENUM_ITEM); - //get is_signed - bool is_signed; - if (enum_item->children.size() == 1){ - is_signed = false; - } else if (enum_item->children.size() == 2){ - log_assert(enum_item->children[1]->type == AST_RANGE); - is_signed = enum_item->children[1]->is_signed; - } else { - log_error("enum_item children size==%lu, expected 1 or 2 for %s (%s)\n", - enum_item->children.size(), - enum_item->str.c_str(), enum_node->str.c_str() - ); - } - //start building attribute string - std::string enum_item_str = "\\enum_value_"; - //get enum item value - if(enum_item->children[0]->type != AST_CONSTANT){ - log_error("expected const, got %s for %s (%s)\n", - type2str(enum_item->children[0]->type).c_str(), - enum_item->str.c_str(), enum_node->str.c_str() - ); - } - RTLIL::Const val = enum_item->children[0]->bitsAsConst(width, is_signed); - enum_item_str.append(val.as_string()); - //set attribute for available val to enum item name mappings - attributes[enum_item_str] = mkconst_str(enum_item->str); - } - } + type = template_node->type; + is_reg = template_node->is_reg; + is_logic = template_node->is_logic; + is_signed = template_node->is_signed; + is_string = template_node->is_string; + is_custom_type = template_node->is_custom_type; + + range_valid = template_node->range_valid; + range_swapped = template_node->range_swapped; + range_left = template_node->range_left; + range_right = template_node->range_right; + + attributes[ID::wiretype] = mkconst_str(resolved_type_node->str); + + // if an enum then add attributes to support simulator tracing + annotateTypedEnums(template_node); // Insert clones children from template at beginning - for (int i = 0; i < GetSize(templ->children); i++) - children.insert(children.begin() + i, templ->children[i]->clone()); + for (int i = 0; i < GetSize(template_node->children); i++) + children.insert(children.begin() + i, template_node->children[i]->clone()); if (type == AST_MEMORY && GetSize(children) == 1) { // Single-bit memories must have [0:0] range - AstNode *rng = new AstNode(AST_RANGE); - rng->children.push_back(AstNode::mkconst_int(0, true)); - rng->children.push_back(AstNode::mkconst_int(0, true)); + AstNode *rng = make_range(0, 0); children.insert(children.begin(), rng); } - did_something = true; } log_assert(!is_custom_type); @@ -1001,29 +1301,29 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage, log_assert(children[1]->type == AST_WIRETYPE); if (!current_scope.count(children[1]->str)) log_file_error(filename, location.first_line, "Unknown identifier `%s' used as type name\n", children[1]->str.c_str()); - AstNode *resolved_type = current_scope.at(children[1]->str); - if (resolved_type->type != AST_TYPEDEF) + AstNode *resolved_type_node = current_scope.at(children[1]->str); + if (resolved_type_node->type != AST_TYPEDEF) log_file_error(filename, location.first_line, "`%s' does not name a type\n", children[1]->str.c_str()); - log_assert(resolved_type->children.size() == 1); - AstNode *templ = resolved_type->children[0]; + log_assert(resolved_type_node->children.size() == 1); + AstNode *template_node = resolved_type_node->children[0]; delete children[1]; children.pop_back(); // Ensure typedef itself is fully simplified - while(templ->simplify(const_fold, at_zero, in_lvalue, stage, width_hint, sign_hint, in_param)) {}; + while(template_node->simplify(const_fold, at_zero, in_lvalue, stage, width_hint, sign_hint, in_param)) {}; - if (templ->type == AST_MEMORY) + if (template_node->type == AST_MEMORY) log_file_error(filename, location.first_line, "unpacked array type `%s' cannot be used for a parameter\n", children[1]->str.c_str()); - is_signed = templ->is_signed; - is_string = templ->is_string; - is_custom_type = templ->is_custom_type; - - range_valid = templ->range_valid; - range_swapped = templ->range_swapped; - range_left = templ->range_left; - range_right = templ->range_right; - attributes[ID::wiretype] = mkconst_str(resolved_type->str); - for (auto template_child : templ->children) + is_signed = template_node->is_signed; + is_string = template_node->is_string; + is_custom_type = template_node->is_custom_type; + + range_valid = template_node->range_valid; + range_swapped = template_node->range_swapped; + range_left = template_node->range_left; + range_right = template_node->range_right; + attributes[ID::wiretype] = mkconst_str(resolved_type_node->str); + for (auto template_child : template_node->children) children.push_back(template_child->clone()); did_something = true; } @@ -1216,6 +1516,23 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage, } } + if (type == AST_IDENTIFIER && !basic_prep) { + // check if a plausible struct member sss.mmmm + std::string sname; + if (name_has_dot(str, sname)) { + if (current_scope.count(str) > 0) { + auto item_node = current_scope[str]; + if (item_node->type == AST_STRUCT_ITEM) { + // structure member, rewrite this node to reference the packed struct wire + auto range = make_struct_member_range(this, item_node); + newNode = new AstNode(AST_IDENTIFIER, range); + newNode->str = sname; + newNode->basic_prep = true; + goto apply_newNode; + } + } + } + } // annotate identifiers using scope resolution and create auto-wires as needed if (type == AST_IDENTIFIER) { if (current_scope.count(str) == 0) { @@ -3540,8 +3857,8 @@ void AstNode::mem2reg_as_needed_pass1(dict<AstNode*, pool<std::string>> &mem2reg } } - // also activate if requested, either by using mem2reg attribute or by declaring array as 'wire' instead of 'reg' - if (type == AST_MEMORY && (get_bool_attribute(ID::mem2reg) || (flags & AstNode::MEM2REG_FL_ALL) || !is_reg)) + // also activate if requested, either by using mem2reg attribute or by declaring array as 'wire' instead of 'reg' or 'logic' + if (type == AST_MEMORY && (get_bool_attribute(ID::mem2reg) || (flags & AstNode::MEM2REG_FL_ALL) || !(is_reg || is_logic))) mem2reg_candidates[this] |= AstNode::MEM2REG_FL_FORCED; if (type == AST_MODULE && get_bool_attribute(ID::mem2reg)) diff --git a/frontends/ilang/ilang_lexer.l b/frontends/ilang/ilang_lexer.l index 62f53d18e..3362ed641 100644 --- a/frontends/ilang/ilang_lexer.l +++ b/frontends/ilang/ilang_lexer.l @@ -91,8 +91,10 @@ USING_YOSYS_NAMESPACE [0-9]+'[01xzm-]* { rtlil_frontend_ilang_yylval.string = strdup(yytext); return TOK_VALUE; } -?[0-9]+ { char *end = nullptr; + errno = 0; long value = strtol(yytext, &end, 10); - if (end != yytext + strlen(yytext)) + log_assert(end == yytext + strlen(yytext)); + if (errno == ERANGE) return TOK_INVALID; // literal out of range of long if (value < INT_MIN || value > INT_MAX) return TOK_INVALID; // literal out of range of int (relevant mostly for LP64 platforms) diff --git a/frontends/ilang/ilang_parser.y b/frontends/ilang/ilang_parser.y index 118f13de9..879ef4af9 100644 --- a/frontends/ilang/ilang_parser.y +++ b/frontends/ilang/ilang_parser.y @@ -192,6 +192,9 @@ wire_options: wire_options TOK_UPTO { current_wire->upto = true; } | + wire_options TOK_SIGNED { + current_wire->is_signed = true; + } | wire_options TOK_OFFSET TOK_INT { current_wire->start_offset = $3; } | diff --git a/frontends/json/jsonparse.cc b/frontends/json/jsonparse.cc index 7aceffbfc..8ae7c6578 100644 --- a/frontends/json/jsonparse.cc +++ b/frontends/json/jsonparse.cc @@ -309,6 +309,12 @@ void json_import(Design *design, string &modname, JsonNode *node) port_wire->upto = val->data_number != 0; } + if (port_node->data_dict.count("signed") != 0) { + JsonNode *val = port_node->data_dict.at("signed"); + if (val->type == 'N') + port_wire->is_signed = val->data_number != 0; + } + if (port_node->data_dict.count("offset") != 0) { JsonNode *val = port_node->data_dict.at("offset"); if (val->type == 'N') @@ -573,4 +579,3 @@ struct JsonFrontend : public Frontend { } JsonFrontend; YOSYS_NAMESPACE_END - diff --git a/frontends/verific/verific.cc b/frontends/verific/verific.cc index fe4bda68e..cb0368fd5 100644 --- a/frontends/verific/verific.cc +++ b/frontends/verific/verific.cc @@ -974,6 +974,7 @@ void VerificImporter::import_netlist(RTLIL::Design *design, Netlist *nl, std::se module->memories[memory->name] = memory; int number_of_bits = net->Size(); + number_of_bits = 1 << ceil_log2(number_of_bits); int bits_in_word = number_of_bits; FOREACH_PORTREF_OF_NET(net, si, pr) { if (pr->GetInst()->Type() == OPER_READ_PORT) { @@ -1265,9 +1266,6 @@ void VerificImporter::import_netlist(RTLIL::Design *design, Netlist *nl, std::se int numchunks = int(inst->OutputSize()) / memory->width; int chunksbits = ceil_log2(numchunks); - if ((numchunks * memory->width) != int(inst->OutputSize()) || (numchunks & (numchunks - 1)) != 0) - log_error("Import of asymmetric memories of this type is not supported yet: %s %s\n", inst->Name(), inst->GetInput()->Name()); - for (int i = 0; i < numchunks; i++) { RTLIL::SigSpec addr = {operatorInput1(inst), RTLIL::Const(i, chunksbits)}; @@ -1295,9 +1293,6 @@ void VerificImporter::import_netlist(RTLIL::Design *design, Netlist *nl, std::se int numchunks = int(inst->Input2Size()) / memory->width; int chunksbits = ceil_log2(numchunks); - if ((numchunks * memory->width) != int(inst->Input2Size()) || (numchunks & (numchunks - 1)) != 0) - log_error("Import of asymmetric memories of this type is not supported yet: %s %s\n", inst->Name(), inst->GetOutput()->Name()); - for (int i = 0; i < numchunks; i++) { RTLIL::SigSpec addr = {operatorInput1(inst), RTLIL::Const(i, chunksbits)}; diff --git a/frontends/verilog/verilog_lexer.l b/frontends/verilog/verilog_lexer.l index f6a3ac4db..e6fa6361e 100644 --- a/frontends/verilog/verilog_lexer.l +++ b/frontends/verilog/verilog_lexer.l @@ -48,16 +48,18 @@ USING_YOSYS_NAMESPACE using namespace AST; using namespace VERILOG_FRONTEND; +#define YYSTYPE FRONTEND_VERILOG_YYSTYPE +#define YYLTYPE FRONTEND_VERILOG_YYLTYPE + YOSYS_NAMESPACE_BEGIN namespace VERILOG_FRONTEND { std::vector<std::string> fn_stack; std::vector<int> ln_stack; + YYLTYPE real_location; + YYLTYPE old_location; } YOSYS_NAMESPACE_END -#define YYSTYPE FRONTEND_VERILOG_YYSTYPE -#define YYLTYPE FRONTEND_VERILOG_YYLTYPE - #define SV_KEYWORD(_tok) \ if (sv_mode) return _tok; \ log("Lexer warning: The SystemVerilog keyword `%s' (at %s:%d) is not "\ @@ -73,9 +75,6 @@ YOSYS_NAMESPACE_END #define YY_INPUT(buf,result,max_size) \ result = readsome(*VERILOG_FRONTEND::lexin, buf, max_size) -YYLTYPE real_location; -YYLTYPE old_location; - #define YY_USER_ACTION \ old_location = real_location; \ real_location.first_line = real_location.last_line; \ @@ -128,7 +127,9 @@ static bool isUserType(std::string &s) %x BASED_CONST %% - int comment_caller; + // Initialise comment_caller to something to avoid a "maybe undefined" + // warning from GCC. + int comment_caller = INITIAL; <INITIAL,SYNOPSYS_TRANSLATE_OFF>"`file_push "[^\n]* { fn_stack.push_back(current_filename); @@ -262,7 +263,10 @@ static bool isUserType(std::string &s) "final" { SV_KEYWORD(TOK_FINAL); } "logic" { SV_KEYWORD(TOK_LOGIC); } "var" { SV_KEYWORD(TOK_VAR); } -"bit" { SV_KEYWORD(TOK_REG); } +"bit" { SV_KEYWORD(TOK_LOGIC); } +"int" { SV_KEYWORD(TOK_INT); } +"byte" { SV_KEYWORD(TOK_BYTE); } +"shortint" { SV_KEYWORD(TOK_SHORTINT); } "eventually" { if (formal_mode) return TOK_EVENTUALLY; SV_KEYWORD(TOK_EVENTUALLY); } "s_eventually" { if (formal_mode) return TOK_EVENTUALLY; SV_KEYWORD(TOK_EVENTUALLY); } @@ -276,11 +280,15 @@ static bool isUserType(std::string &s) "reg" { return TOK_REG; } "integer" { return TOK_INTEGER; } "signed" { return TOK_SIGNED; } +"unsigned" { SV_KEYWORD(TOK_UNSIGNED); } "genvar" { return TOK_GENVAR; } "real" { return TOK_REAL; } "enum" { SV_KEYWORD(TOK_ENUM); } "typedef" { SV_KEYWORD(TOK_TYPEDEF); } +"struct" { SV_KEYWORD(TOK_STRUCT); } +"union" { SV_KEYWORD(TOK_UNION); } +"packed" { SV_KEYWORD(TOK_PACKED); } [0-9][0-9_]* { yylval->string = new std::string(yytext); diff --git a/frontends/verilog/verilog_parser.y b/frontends/verilog/verilog_parser.y index c8223f41d..b34a62248 100644 --- a/frontends/verilog/verilog_parser.y +++ b/frontends/verilog/verilog_parser.y @@ -161,6 +161,23 @@ static bool isInLocalScope(const std::string *name) return (user_types->count(*name) > 0); } +static AstNode *getTypeDefinitionNode(std::string type_name) +{ + // return the definition nodes from the typedef statement + auto user_types = user_type_stack.back(); + log_assert(user_types->count(type_name) > 0); + auto typedef_node = (*user_types)[type_name]; + log_assert(typedef_node->type == AST_TYPEDEF); + return typedef_node->children[0]; +} + +static AstNode *copyTypeDefinition(std::string type_name) +{ + // return a copy of the template from a typedef definition + auto typedef_node = getTypeDefinitionNode(type_name); + return typedef_node->clone(); +} + static AstNode *makeRange(int msb = 31, int lsb = 0, bool isSigned = true) { auto range = new AstNode(AST_RANGE); @@ -175,6 +192,35 @@ static void addRange(AstNode *parent, int msb = 31, int lsb = 0, bool isSigned = auto range = makeRange(msb, lsb, isSigned); parent->children.push_back(range); } + +static AstNode *checkRange(AstNode *type_node, AstNode *range_node) +{ + if (type_node->range_left >= 0 && type_node->range_right >= 0) { + // type already restricts the range + if (range_node) { + frontend_verilog_yyerror("integer/genvar types cannot have packed dimensions."); + } + else { + range_node = makeRange(type_node->range_left, type_node->range_right, false); + } + } + if (range_node && range_node->children.size() != 2) { + frontend_verilog_yyerror("wire/reg/logic packed dimension must be of the form: [<expr>:<expr>], [<expr>+:<expr>], or [<expr>-:<expr>]"); + } + return range_node; +} + +static void rewriteAsMemoryNode(AstNode *node, AstNode *rangeNode) +{ + node->type = AST_MEMORY; + if (rangeNode->type == AST_RANGE && rangeNode->children.size() == 1) { + // SV array size [n], rewrite as [n-1:0] + rangeNode->children[0] = new AstNode(AST_SUB, rangeNode->children[0], AstNode::mkconst_int(1, true)); + rangeNode->children.push_back(AstNode::mkconst_int(0, false)); + } + node->children.push_back(rangeNode); +} + %} %define api.prefix {frontend_verilog_yy} @@ -223,14 +269,16 @@ static void addRange(AstNode *parent, int msb = 31, int lsb = 0, bool isSigned = %token TOK_POS_INDEXED TOK_NEG_INDEXED TOK_PROPERTY TOK_ENUM TOK_TYPEDEF %token TOK_RAND TOK_CONST TOK_CHECKER TOK_ENDCHECKER TOK_EVENTUALLY %token TOK_INCREMENT TOK_DECREMENT TOK_UNIQUE TOK_PRIORITY +%token TOK_STRUCT TOK_PACKED TOK_UNSIGNED TOK_INT TOK_BYTE TOK_SHORTINT TOK_UNION %type <ast> range range_or_multirange non_opt_range non_opt_multirange range_or_signed_int %type <ast> wire_type expr basic_expr concat_list rvalue lvalue lvalue_concat_list %type <string> opt_label opt_sva_label tok_prim_wrapper hierarchical_id hierarchical_type_id integral_number %type <string> type_name -%type <ast> opt_enum_init +%type <ast> opt_enum_init enum_type struct_type non_wire_data_type %type <boolean> opt_signed opt_property unique_case_attr always_comb_or_latch always_or_always_ff %type <al> attr case_attr +%type <ast> struct_union %type <specify_target_ptr> specify_target %type <specify_triple_ptr> specify_triple specify_opt_triple @@ -520,9 +568,10 @@ package_body: ; package_body_stmt: - typedef_decl | - localparam_decl | - param_decl; + typedef_decl + | localparam_decl + | param_decl + ; interface: TOK_INTERFACE { @@ -582,6 +631,7 @@ wire_type_token_list: astbuf3->is_custom_type = true; astbuf3->children.push_back(new AstNode(AST_WIRETYPE)); astbuf3->children.back()->str = *$1; + delete $1; }; wire_type_token_io: @@ -682,15 +732,9 @@ range_or_multirange: non_opt_multirange { $$ = $1; }; range_or_signed_int: - range { - $$ = $1; - } | - TOK_INTEGER { - $$ = new AstNode(AST_RANGE); - $$->children.push_back(AstNode::mkconst_int(31, true)); - $$->children.push_back(AstNode::mkconst_int(0, true)); - $$->is_signed = true; - }; + range { $$ = $1; } + | TOK_INTEGER { $$ = makeRange(); } + ; module_body: module_body module_body_stmt | @@ -700,7 +744,7 @@ module_body: module_body_stmt: task_func_decl | specify_block | param_decl | localparam_decl | typedef_decl | defparam_decl | specparam_declaration | wire_decl | assign_stmt | cell_stmt | - enum_decl | + enum_decl | struct_decl | always_stmt | TOK_GENERATE module_gen_body TOK_ENDGENERATE | defattr | assert_property | checker_decl | ignored_specify_block; checker_decl: @@ -841,18 +885,7 @@ task_func_port: } albuf = $1; astbuf1 = $2; - astbuf2 = $3; - if (astbuf1->range_left >= 0 && astbuf1->range_right >= 0) { - if (astbuf2) { - frontend_verilog_yyerror("integer/genvar types cannot have packed dimensions (task/function arguments)"); - } else { - astbuf2 = new AstNode(AST_RANGE); - astbuf2->children.push_back(AstNode::mkconst_int(astbuf1->range_left, true)); - astbuf2->children.push_back(AstNode::mkconst_int(astbuf1->range_right, true)); - } - } - if (astbuf2 && astbuf2->children.size() != 2) - frontend_verilog_yyerror("task/function argument range must be of the form: [<expr>:<expr>], [<expr>+:<expr>], or [<expr>-:<expr>]"); + astbuf2 = checkRange(astbuf1, $3); } wire_name | { if (!astbuf1) { @@ -1387,6 +1420,10 @@ single_defparam_decl: ast_stack.back()->children.push_back(node); }; +///////// +// enum +///////// + enum_type: TOK_ENUM { static int enum_count; // create parent node for the enum @@ -1397,31 +1434,40 @@ enum_type: TOK_ENUM { // create the template for the names astbuf1 = new AstNode(AST_ENUM_ITEM); astbuf1->children.push_back(AstNode::mkconst_int(0, true)); - } param_signed enum_base_type '{' enum_name_list '}' { // create template for the enum vars - auto tnode = astbuf1->clone(); - delete astbuf1; - astbuf1 = tnode; - tnode->type = AST_WIRE; - tnode->attributes[ID::enum_type] = AstNode::mkconst_str(astbuf2->str); - // drop constant but keep any range - delete tnode->children[0]; - tnode->children.erase(tnode->children.begin()); } + } enum_base_type '{' enum_name_list '}' { // create template for the enum vars + auto tnode = astbuf1->clone(); + delete astbuf1; + astbuf1 = tnode; + tnode->type = AST_WIRE; + tnode->attributes[ID::enum_type] = AstNode::mkconst_str(astbuf2->str); + // drop constant but keep any range + delete tnode->children[0]; + tnode->children.erase(tnode->children.begin()); + $$ = astbuf1; } ; -enum_base_type: int_vec param_range - | int_atom - | /* nothing */ {astbuf1->is_reg = true; addRange(astbuf1); } +enum_base_type: type_atom type_signing + | type_vec type_signing range { if ($3) astbuf1->children.push_back($3); } + | /* nothing */ { astbuf1->is_reg = true; addRange(astbuf1); } ; -int_atom: TOK_INTEGER {astbuf1->is_reg=true; addRange(astbuf1); } // probably should do byte, range [7:0] here +type_atom: TOK_INTEGER { astbuf1->is_reg = true; addRange(astbuf1); } // 4-state signed + | TOK_INT { astbuf1->is_reg = true; addRange(astbuf1); } // 2-state signed + | TOK_SHORTINT { astbuf1->is_reg = true; addRange(astbuf1, 15, 0); } // 2-state signed + | TOK_BYTE { astbuf1->is_reg = true; addRange(astbuf1, 7, 0); } // 2-state signed ; -int_vec: TOK_REG {astbuf1->is_reg = true;} - | TOK_LOGIC {astbuf1->is_logic = true;} +type_vec: TOK_REG { astbuf1->is_reg = true; } // unsigned + | TOK_LOGIC { astbuf1->is_logic = true; } // unsigned ; -enum_name_list: - enum_name_decl +type_signing: + TOK_SIGNED { astbuf1->is_signed = true; } + | TOK_UNSIGNED { astbuf1->is_signed = false; } + | // optional + ; + +enum_name_list: enum_name_decl | enum_name_list ',' enum_name_decl ; @@ -1433,6 +1479,7 @@ enum_name_decl: auto node = astbuf1->clone(); node->str = *$1; delete $1; + SET_AST_NODE_LOC(node, @1, @1); delete node->children[0]; node->children[0] = $2 ?: new AstNode(AST_NONE); astbuf2->children.push_back(node); @@ -1456,32 +1503,122 @@ enum_var: TOK_ID { ast_stack.back()->children.push_back(node); node->str = *$1; delete $1; + SET_AST_NODE_LOC(node, @1, @1); node->is_enum = true; } ; -enum_decl: enum_type enum_var_list ';' { - //enum_type creates astbuf1 for use by typedef only - delete astbuf1; - } +enum_decl: enum_type enum_var_list ';' { delete $1; } + ; + +////////////////// +// struct or union +////////////////// + +struct_decl: struct_type struct_var_list ';' { delete astbuf2; } + ; + +struct_type: struct_union { astbuf2 = $1; } struct_body { $$ = astbuf2; } + ; + +struct_union: + TOK_STRUCT { $$ = new AstNode(AST_STRUCT); } + | TOK_UNION { $$ = new AstNode(AST_UNION); } + ; + +struct_body: opt_packed '{' struct_member_list '}' + ; + +opt_packed: TOK_PACKED opt_signed_struct + | { frontend_verilog_yyerror("Only PACKED supported at this time"); } + ; + +opt_signed_struct: + TOK_SIGNED { astbuf2->is_signed = true; } + | TOK_UNSIGNED { astbuf2->is_signed = false; } + | // default is unsigned + ; + +struct_member_list: struct_member + | struct_member_list struct_member + ; + +struct_member: struct_member_type member_name_list ';' { delete astbuf1; } + ; + +member_name_list: + member_name + | member_name_list ',' member_name + ; + +member_name: TOK_ID { + astbuf1->str = $1->substr(1); + delete $1; + astbuf3 = astbuf1->clone(); + SET_AST_NODE_LOC(astbuf3, @1, @1); + astbuf2->children.push_back(astbuf3); + } range { if ($3) astbuf3->children.push_back($3); } + ; + +struct_member_type: { astbuf1 = new AstNode(AST_STRUCT_ITEM); } member_type_token + ; + +member_type_token: + member_type + | hierarchical_type_id { + // use a clone of the typedef definition nodes + auto template_node = copyTypeDefinition(*$1); + delete $1; + switch (template_node->type) { + case AST_WIRE: + template_node->type = AST_STRUCT_ITEM; + break; + case AST_STRUCT: + case AST_UNION: + break; + default: + frontend_verilog_yyerror("Invalid type for struct member: %s", type2str(template_node->type).c_str()); + } + delete astbuf1; + astbuf1 = template_node; + } + | struct_union { + // stash state on ast_stack + ast_stack.push_back(astbuf2); + astbuf2 = $1; + } struct_body { + astbuf1 = astbuf2; + // recover state + astbuf2 = ast_stack.back(); + ast_stack.pop_back(); + } + ; + +member_type: type_atom type_signing + | type_vec type_signing range_or_multirange { if ($3) astbuf1->children.push_back($3); } + ; + +struct_var_list: struct_var + | struct_var_list ',' struct_var ; +struct_var: TOK_ID { auto *var_node = astbuf2->clone(); + var_node->str = *$1; + delete $1; + SET_AST_NODE_LOC(var_node, @1, @1); + ast_stack.back()->children.push_back(var_node); + } + ; + +///////// +// wire +///////// + wire_decl: attr wire_type range { albuf = $1; astbuf1 = $2; - astbuf2 = $3; - if (astbuf1->range_left >= 0 && astbuf1->range_right >= 0) { - if (astbuf2) { - frontend_verilog_yyerror("integer/genvar types cannot have packed dimensions."); - } else { - astbuf2 = new AstNode(AST_RANGE); - astbuf2->children.push_back(AstNode::mkconst_int(astbuf1->range_left, true)); - astbuf2->children.push_back(AstNode::mkconst_int(astbuf1->range_right, true)); - } - } - if (astbuf2 && astbuf2->children.size() != 2) - frontend_verilog_yyerror("wire/reg/logic packed dimension must be of the form: [<expr>:<expr>], [<expr>+:<expr>], or [<expr>-:<expr>]"); + astbuf2 = checkRange(astbuf1, $3); } delay wire_name_list { delete astbuf1; if (astbuf2 != NULL) @@ -1603,19 +1740,9 @@ wire_name: if (node->is_input || node->is_output) frontend_verilog_yyerror("input/output/inout ports cannot have unpacked dimensions."); if (!astbuf2 && !node->is_custom_type) { - AstNode *rng = new AstNode(AST_RANGE); - rng->children.push_back(AstNode::mkconst_int(0, true)); - rng->children.push_back(AstNode::mkconst_int(0, true)); - node->children.push_back(rng); - } - node->type = AST_MEMORY; - auto *rangeNode = $2; - if (rangeNode->type == AST_RANGE && rangeNode->children.size() == 1) { - // SV array size [n], rewrite as [n-1:0] - rangeNode->children[0] = new AstNode(AST_SUB, rangeNode->children[0], AstNode::mkconst_int(1, true)); - rangeNode->children.push_back(AstNode::mkconst_int(0, false)); + addRange(node, 0, 0, false); } - node->children.push_back(rangeNode); + rewriteAsMemoryNode(node, $2); } if (current_function_or_task == NULL) { if (do_not_require_port_stubs && (node->is_input || node->is_output) && port_stubs.count(*$1) == 0) { @@ -1663,42 +1790,23 @@ type_name: TOK_ID // first time seen typedef_decl: TOK_TYPEDEF wire_type range type_name range_or_multirange ';' { astbuf1 = $2; - astbuf2 = $3; - if (astbuf1->range_left >= 0 && astbuf1->range_right >= 0) { - if (astbuf2) { - frontend_verilog_yyerror("integer/genvar types cannot have packed dimensions."); - } else { - astbuf2 = new AstNode(AST_RANGE); - astbuf2->children.push_back(AstNode::mkconst_int(astbuf1->range_left, true)); - astbuf2->children.push_back(AstNode::mkconst_int(astbuf1->range_right, true)); - } - } - if (astbuf2 && astbuf2->children.size() != 2) - frontend_verilog_yyerror("wire/reg/logic packed dimension must be of the form: [<expr>:<expr>], [<expr>+:<expr>], or [<expr>-:<expr>]"); + astbuf2 = checkRange(astbuf1, $3); if (astbuf2) astbuf1->children.push_back(astbuf2); if ($5 != NULL) { if (!astbuf2) { - AstNode *rng = new AstNode(AST_RANGE); - rng->children.push_back(AstNode::mkconst_int(0, true)); - rng->children.push_back(AstNode::mkconst_int(0, true)); - astbuf1->children.push_back(rng); + addRange(astbuf1, 0, 0, false); } - astbuf1->type = AST_MEMORY; - auto *rangeNode = $5; - if (rangeNode->type == AST_RANGE && rangeNode->children.size() == 1) { - // SV array size [n], rewrite as [n-1:0] - rangeNode->children[0] = new AstNode(AST_SUB, rangeNode->children[0], AstNode::mkconst_int(1, true)); - rangeNode->children.push_back(AstNode::mkconst_int(0, false)); - } - astbuf1->children.push_back(rangeNode); + rewriteAsMemoryNode(astbuf1, $5); } - addTypedefNode($4, astbuf1); - } | - TOK_TYPEDEF enum_type type_name ';' { - addTypedefNode($3, astbuf1); - } + addTypedefNode($4, astbuf1); } + | TOK_TYPEDEF non_wire_data_type type_name ';' { addTypedefNode($3, $2); } + ; + +non_wire_data_type: + enum_type + | struct_type ; cell_stmt: diff --git a/kernel/calc.cc b/kernel/calc.cc index 4a4840771..ae18809d3 100644 --- a/kernel/calc.cc +++ b/kernel/calc.cc @@ -489,6 +489,7 @@ RTLIL::Const RTLIL::const_mul(const RTLIL::Const &arg1, const RTLIL::Const &arg2 return big2const(y, result_len >= 0 ? result_len : max(arg1.bits.size(), arg2.bits.size()), min(undef_bit_pos, 0)); } +// truncating division RTLIL::Const RTLIL::const_div(const RTLIL::Const &arg1, const RTLIL::Const &arg2, bool signed1, bool signed2, int result_len) { int undef_bit_pos = -1; @@ -502,6 +503,7 @@ RTLIL::Const RTLIL::const_div(const RTLIL::Const &arg1, const RTLIL::Const &arg2 return big2const(result_neg ? -(a / b) : (a / b), result_len >= 0 ? result_len : max(arg1.bits.size(), arg2.bits.size()), min(undef_bit_pos, 0)); } +// truncating modulo RTLIL::Const RTLIL::const_mod(const RTLIL::Const &arg1, const RTLIL::Const &arg2, bool signed1, bool signed2, int result_len) { int undef_bit_pos = -1; @@ -515,6 +517,51 @@ RTLIL::Const RTLIL::const_mod(const RTLIL::Const &arg1, const RTLIL::Const &arg2 return big2const(result_neg ? -(a % b) : (a % b), result_len >= 0 ? result_len : max(arg1.bits.size(), arg2.bits.size()), min(undef_bit_pos, 0)); } +RTLIL::Const RTLIL::const_divfloor(const RTLIL::Const &arg1, const RTLIL::Const &arg2, bool signed1, bool signed2, int result_len) +{ + int undef_bit_pos = -1; + BigInteger a = const2big(arg1, signed1, undef_bit_pos); + BigInteger b = const2big(arg2, signed2, undef_bit_pos); + if (b.isZero()) + return RTLIL::Const(RTLIL::State::Sx, result_len); + + bool result_pos = (a.getSign() == BigInteger::negative) == (b.getSign() == BigInteger::negative); + a = a.getSign() == BigInteger::negative ? -a : a; + b = b.getSign() == BigInteger::negative ? -b : b; + BigInteger result; + + if (result_pos || a == 0) { + result = a / b; + } else { + // bigint division with negative numbers is wonky, make sure we only negate at the very end + result = -((a + b - 1) / b); + } + return big2const(result, result_len >= 0 ? result_len : max(arg1.bits.size(), arg2.bits.size()), min(undef_bit_pos, 0)); +} + +RTLIL::Const RTLIL::const_modfloor(const RTLIL::Const &arg1, const RTLIL::Const &arg2, bool signed1, bool signed2, int result_len) +{ + int undef_bit_pos = -1; + BigInteger a = const2big(arg1, signed1, undef_bit_pos); + BigInteger b = const2big(arg2, signed2, undef_bit_pos); + if (b.isZero()) + return RTLIL::Const(RTLIL::State::Sx, result_len); + + BigInteger::Sign a_sign = a.getSign(); + BigInteger::Sign b_sign = b.getSign(); + a = a_sign == BigInteger::negative ? -a : a; + b = b_sign == BigInteger::negative ? -b : b; + BigInteger truncated = a_sign == BigInteger::negative ? -(a % b) : (a % b); + BigInteger modulo; + + if (truncated == 0 || (a_sign == b_sign)) { + modulo = truncated; + } else { + modulo = b_sign == BigInteger::negative ? truncated - b : truncated + b; + } + return big2const(modulo, result_len >= 0 ? result_len : max(arg1.bits.size(), arg2.bits.size()), min(undef_bit_pos, 0)); +} + RTLIL::Const RTLIL::const_pow(const RTLIL::Const &arg1, const RTLIL::Const &arg2, bool signed1, bool signed2, int result_len) { int undef_bit_pos = -1; diff --git a/kernel/celledges.cc b/kernel/celledges.cc index 54e0168e2..314e7c77e 100644 --- a/kernel/celledges.cc +++ b/kernel/celledges.cc @@ -187,7 +187,7 @@ bool YOSYS_NAMESPACE_PREFIX AbstractCellEdgesDatabase::add_edges_from_cell(RTLIL return true; } - // FIXME: $mul $div $mod $slice $concat + // FIXME: $mul $div $mod $divfloor $modfloor $slice $concat // FIXME: $lut $sop $alu $lcu $macc $fa return false; diff --git a/kernel/celltypes.h b/kernel/celltypes.h index 450865ce9..db54436cb 100644 --- a/kernel/celltypes.h +++ b/kernel/celltypes.h @@ -114,7 +114,7 @@ struct CellTypes ID($and), ID($or), ID($xor), ID($xnor), ID($shl), ID($shr), ID($sshl), ID($sshr), ID($shift), ID($shiftx), ID($lt), ID($le), ID($eq), ID($ne), ID($eqx), ID($nex), ID($ge), ID($gt), - ID($add), ID($sub), ID($mul), ID($div), ID($mod), ID($pow), + ID($add), ID($sub), ID($mul), ID($div), ID($mod), ID($divfloor), ID($modfloor), ID($pow), ID($logic_and), ID($logic_or), ID($concat), ID($macc) }; @@ -304,6 +304,8 @@ struct CellTypes HANDLE_CELL_TYPE(mul) HANDLE_CELL_TYPE(div) HANDLE_CELL_TYPE(mod) + HANDLE_CELL_TYPE(divfloor) + HANDLE_CELL_TYPE(modfloor) HANDLE_CELL_TYPE(pow) HANDLE_CELL_TYPE(pos) HANDLE_CELL_TYPE(neg) diff --git a/kernel/constids.inc b/kernel/constids.inc index 93c3a7609..383d7c615 100644 --- a/kernel/constids.inc +++ b/kernel/constids.inc @@ -171,6 +171,7 @@ X(techmap_autopurge) X(_TECHMAP_BITS_CONNMAP_) X(_TECHMAP_CELLTYPE_) X(techmap_celltype) +X(_TECHMAP_FAIL_) X(techmap_maccmap) X(_TECHMAP_REPLACE_) X(techmap_simplemap) diff --git a/kernel/hashlib.h b/kernel/hashlib.h index 592d6e577..5c87b55f5 100644 --- a/kernel/hashlib.h +++ b/kernel/hashlib.h @@ -207,6 +207,7 @@ class dict entry_t() { } entry_t(const std::pair<K, T> &udata, int next) : udata(udata), next(next) { } entry_t(std::pair<K, T> &&udata, int next) : udata(std::move(udata)), next(next) { } + bool operator<(const entry_t &other) const { return udata.first < other.udata.first; } }; std::vector<int> hashtable; @@ -615,6 +616,15 @@ public: return !operator==(other); } + unsigned int hash() const { + unsigned int h = mkhash_init; + for (auto &entry : entries) { + h ^= hash_ops<K>::hash(entry.udata.first); + h ^= hash_ops<T>::hash(entry.udata.second); + } + return h; + } + void reserve(size_t n) { entries.reserve(n); } size_t size() const { return entries.size(); } bool empty() const { return entries.empty(); } diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index 196e301b6..109113370 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -948,7 +948,7 @@ namespace { return; } - if (cell->type.in(ID($add), ID($sub), ID($mul), ID($div), ID($mod), ID($pow))) { + if (cell->type.in(ID($add), ID($sub), ID($mul), ID($div), ID($mod), ID($divfloor), ID($modfloor), ID($pow))) { param_bool(ID::A_SIGNED); param_bool(ID::B_SIGNED); port(ID::A, param(ID::A_WIDTH)); @@ -1862,6 +1862,7 @@ RTLIL::Wire *RTLIL::Module::addWire(RTLIL::IdString name, const RTLIL::Wire *oth wire->port_input = other->port_input; wire->port_output = other->port_output; wire->upto = other->upto; + wire->is_signed = other->is_signed; wire->attributes = other->attributes; return wire; } @@ -1884,6 +1885,18 @@ RTLIL::Cell *RTLIL::Module::addCell(RTLIL::IdString name, const RTLIL::Cell *oth return cell; } +RTLIL::Memory *RTLIL::Module::addMemory(RTLIL::IdString name, const RTLIL::Memory *other) +{ + RTLIL::Memory *mem = new RTLIL::Memory; + mem->name = name; + mem->width = other->width; + mem->start_offset = other->start_offset; + mem->size = other->size; + mem->attributes = other->attributes; + memories[mem->name] = mem; + return mem; +} + #define DEF_METHOD(_func, _y_size, _type) \ RTLIL::Cell* RTLIL::Module::add ## _func(RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_y, bool is_signed, const std::string &src) { \ RTLIL::Cell *cell = addCell(name, _type); \ @@ -1949,6 +1962,8 @@ DEF_METHOD(Sub, max(sig_a.size(), sig_b.size()), ID($sub)) DEF_METHOD(Mul, max(sig_a.size(), sig_b.size()), ID($mul)) DEF_METHOD(Div, max(sig_a.size(), sig_b.size()), ID($div)) DEF_METHOD(Mod, max(sig_a.size(), sig_b.size()), ID($mod)) +DEF_METHOD(DivFloor, max(sig_a.size(), sig_b.size()), ID($divfloor)) +DEF_METHOD(ModFloor, max(sig_a.size(), sig_b.size()), ID($modfloor)) DEF_METHOD(LogicAnd, 1, ID($logic_and)) DEF_METHOD(LogicOr, 1, ID($logic_or)) #undef DEF_METHOD @@ -2445,6 +2460,7 @@ RTLIL::Wire::Wire() port_input = false; port_output = false; upto = false; + is_signed = false; #ifdef WITH_PYTHON RTLIL::Wire::get_all_wires()->insert(std::pair<unsigned int, RTLIL::Wire*>(hashidx_, this)); diff --git a/kernel/rtlil.h b/kernel/rtlil.h index 11c45bbec..86b4e25b6 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -150,9 +150,6 @@ namespace RTLIL if (!p[0]) return 0; - log_assert(p[0] == '$' || p[0] == '\\'); - log_assert(p[1] != 0); - auto it = global_id_index_.find((char*)p); if (it != global_id_index_.end()) { #ifndef YOSYS_NO_IDS_REFCNT @@ -165,6 +162,11 @@ namespace RTLIL return it->second; } + log_assert(p[0] == '$' || p[0] == '\\'); + log_assert(p[1] != 0); + for (const char *c = p; *c; c++) + log_assert((unsigned)*c > (unsigned)' '); + #ifndef YOSYS_NO_IDS_REFCNT if (global_free_idx_list_.empty()) { if (global_id_storage_.empty()) { @@ -296,8 +298,8 @@ namespace RTLIL // The methods below are just convenience functions for better compatibility with std::string. - bool operator==(const std::string &rhs) const { return str() == rhs; } - bool operator!=(const std::string &rhs) const { return str() != rhs; } + bool operator==(const std::string &rhs) const { return c_str() == rhs; } + bool operator!=(const std::string &rhs) const { return c_str() != rhs; } bool operator==(const char *rhs) const { return strcmp(c_str(), rhs) == 0; } bool operator!=(const char *rhs) const { return strcmp(c_str(), rhs) != 0; } @@ -466,6 +468,8 @@ namespace RTLIL RTLIL::Const const_sub (const RTLIL::Const &arg1, const RTLIL::Const &arg2, bool signed1, bool signed2, int result_len); RTLIL::Const const_mul (const RTLIL::Const &arg1, const RTLIL::Const &arg2, bool signed1, bool signed2, int result_len); RTLIL::Const const_div (const RTLIL::Const &arg1, const RTLIL::Const &arg2, bool signed1, bool signed2, int result_len); + RTLIL::Const const_divfloor (const RTLIL::Const &arg1, const RTLIL::Const &arg2, bool signed1, bool signed2, int result_len); + RTLIL::Const const_modfloor (const RTLIL::Const &arg1, const RTLIL::Const &arg2, bool signed1, bool signed2, int result_len); RTLIL::Const const_mod (const RTLIL::Const &arg1, const RTLIL::Const &arg2, bool signed1, bool signed2, int result_len); RTLIL::Const const_pow (const RTLIL::Const &arg1, const RTLIL::Const &arg2, bool signed1, bool signed2, int result_len); @@ -721,7 +725,7 @@ struct RTLIL::SigBit SigBit(const RTLIL::SigChunk &chunk); SigBit(const RTLIL::SigChunk &chunk, int index); SigBit(const RTLIL::SigSpec &sig); - SigBit(const RTLIL::SigBit &sigbit); + SigBit(const RTLIL::SigBit &sigbit) = default; RTLIL::SigBit &operator =(const RTLIL::SigBit &other) = default; bool operator <(const RTLIL::SigBit &other) const; @@ -1134,8 +1138,14 @@ public: return design->selected_member(name, member->name); } - RTLIL::Wire* wire(RTLIL::IdString id) { return wires_.count(id) ? wires_.at(id) : nullptr; } - RTLIL::Cell* cell(RTLIL::IdString id) { return cells_.count(id) ? cells_.at(id) : nullptr; } + RTLIL::Wire* wire(RTLIL::IdString id) { + auto it = wires_.find(id); + return it == wires_.end() ? nullptr : it->second; + } + RTLIL::Cell* cell(RTLIL::IdString id) { + auto it = cells_.find(id); + return it == cells_.end() ? nullptr : it->second; + } RTLIL::ObjRange<RTLIL::Wire*> wires() { return RTLIL::ObjRange<RTLIL::Wire*>(&wires_, &refcount_wires_); } RTLIL::ObjRange<RTLIL::Cell*> cells() { return RTLIL::ObjRange<RTLIL::Cell*>(&cells_, &refcount_cells_); } @@ -1160,6 +1170,8 @@ public: RTLIL::Cell *addCell(RTLIL::IdString name, RTLIL::IdString type); RTLIL::Cell *addCell(RTLIL::IdString name, const RTLIL::Cell *other); + RTLIL::Memory *addMemory(RTLIL::IdString name, const RTLIL::Memory *other); + // The add* methods create a cell and return the created cell. All signals must exist in advance. RTLIL::Cell* addNot (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_y, bool is_signed = false, const std::string &src = ""); @@ -1196,8 +1208,12 @@ public: RTLIL::Cell* addAdd (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_b, const RTLIL::SigSpec &sig_y, bool is_signed = false, const std::string &src = ""); RTLIL::Cell* addSub (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_b, const RTLIL::SigSpec &sig_y, bool is_signed = false, const std::string &src = ""); RTLIL::Cell* addMul (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_b, const RTLIL::SigSpec &sig_y, bool is_signed = false, const std::string &src = ""); + // truncating division RTLIL::Cell* addDiv (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_b, const RTLIL::SigSpec &sig_y, bool is_signed = false, const std::string &src = ""); + // truncating modulo RTLIL::Cell* addMod (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_b, const RTLIL::SigSpec &sig_y, bool is_signed = false, const std::string &src = ""); + RTLIL::Cell* addDivFloor (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_b, const RTLIL::SigSpec &sig_y, bool is_signed = false, const std::string &src = ""); + RTLIL::Cell* addModFloor (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_b, const RTLIL::SigSpec &sig_y, bool is_signed = false, const std::string &src = ""); RTLIL::Cell* addPow (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_b, const RTLIL::SigSpec &sig_y, bool a_signed = false, bool b_signed = false, const std::string &src = ""); RTLIL::Cell* addLogicNot (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_y, bool is_signed = false, const std::string &src = ""); @@ -1222,13 +1238,10 @@ public: RTLIL::Cell* addFf (RTLIL::IdString name, const RTLIL::SigSpec &sig_d, const RTLIL::SigSpec &sig_q, const std::string &src = ""); RTLIL::Cell* addDff (RTLIL::IdString name, const RTLIL::SigSpec &sig_clk, const RTLIL::SigSpec &sig_d, const RTLIL::SigSpec &sig_q, bool clk_polarity = true, const std::string &src = ""); RTLIL::Cell* addDffe (RTLIL::IdString name, const RTLIL::SigSpec &sig_clk, const RTLIL::SigSpec &sig_en, const RTLIL::SigSpec &sig_d, const RTLIL::SigSpec &sig_q, bool clk_polarity = true, bool en_polarity = true, const std::string &src = ""); - RTLIL::Cell* addDffsr (RTLIL::IdString name, const RTLIL::SigSpec &sig_clk, const RTLIL::SigSpec &sig_set, const RTLIL::SigSpec &sig_clr, - RTLIL::SigSpec sig_d, const RTLIL::SigSpec &sig_q, bool clk_polarity = true, bool set_polarity = true, bool clr_polarity = true, const std::string &src = ""); - RTLIL::Cell* addAdff (RTLIL::IdString name, const RTLIL::SigSpec &sig_clk, const RTLIL::SigSpec &sig_arst, const RTLIL::SigSpec &sig_d, const RTLIL::SigSpec &sig_q, - RTLIL::Const arst_value, bool clk_polarity = true, bool arst_polarity = true, const std::string &src = ""); + RTLIL::Cell* addDffsr (RTLIL::IdString name, const RTLIL::SigSpec &sig_clk, const RTLIL::SigSpec &sig_set, const RTLIL::SigSpec &sig_clr, RTLIL::SigSpec sig_d, const RTLIL::SigSpec &sig_q, bool clk_polarity = true, bool set_polarity = true, bool clr_polarity = true, const std::string &src = ""); + RTLIL::Cell* addAdff (RTLIL::IdString name, const RTLIL::SigSpec &sig_clk, const RTLIL::SigSpec &sig_arst, const RTLIL::SigSpec &sig_d, const RTLIL::SigSpec &sig_q, RTLIL::Const arst_value, bool clk_polarity = true, bool arst_polarity = true, const std::string &src = ""); RTLIL::Cell* addDlatch (RTLIL::IdString name, const RTLIL::SigSpec &sig_en, const RTLIL::SigSpec &sig_d, const RTLIL::SigSpec &sig_q, bool en_polarity = true, const std::string &src = ""); - RTLIL::Cell* addDlatchsr (RTLIL::IdString name, const RTLIL::SigSpec &sig_en, const RTLIL::SigSpec &sig_set, const RTLIL::SigSpec &sig_clr, - RTLIL::SigSpec sig_d, const RTLIL::SigSpec &sig_q, bool en_polarity = true, bool set_polarity = true, bool clr_polarity = true, const std::string &src = ""); + RTLIL::Cell* addDlatchsr (RTLIL::IdString name, const RTLIL::SigSpec &sig_en, const RTLIL::SigSpec &sig_set, const RTLIL::SigSpec &sig_clr, RTLIL::SigSpec sig_d, const RTLIL::SigSpec &sig_q, bool en_polarity = true, bool set_polarity = true, bool clr_polarity = true, const std::string &src = ""); RTLIL::Cell* addBufGate (RTLIL::IdString name, const RTLIL::SigBit &sig_a, const RTLIL::SigBit &sig_y, const std::string &src = ""); RTLIL::Cell* addNotGate (RTLIL::IdString name, const RTLIL::SigBit &sig_a, const RTLIL::SigBit &sig_y, const std::string &src = ""); @@ -1295,8 +1308,12 @@ public: RTLIL::SigSpec Add (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_b, bool is_signed = false, const std::string &src = ""); RTLIL::SigSpec Sub (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_b, bool is_signed = false, const std::string &src = ""); RTLIL::SigSpec Mul (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_b, bool is_signed = false, const std::string &src = ""); + // truncating division RTLIL::SigSpec Div (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_b, bool is_signed = false, const std::string &src = ""); + // truncating modulo RTLIL::SigSpec Mod (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_b, bool is_signed = false, const std::string &src = ""); + RTLIL::SigSpec DivFloor (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_b, bool is_signed = false, const std::string &src = ""); + RTLIL::SigSpec ModFloor (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_b, bool is_signed = false, const std::string &src = ""); RTLIL::SigSpec Pow (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_b, bool a_signed = false, bool b_signed = false, const std::string &src = ""); RTLIL::SigSpec LogicNot (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, bool is_signed = false, const std::string &src = ""); @@ -1353,7 +1370,7 @@ public: RTLIL::Module *module; RTLIL::IdString name; int width, start_offset, port_id; - bool port_input, port_output, upto; + bool port_input, port_output, upto, is_signed; #ifdef WITH_PYTHON static std::map<unsigned int, RTLIL::Wire*> *get_all_wires(void); @@ -1494,7 +1511,6 @@ inline RTLIL::SigBit::SigBit(RTLIL::Wire *wire) : wire(wire), offset(0) { log_as inline RTLIL::SigBit::SigBit(RTLIL::Wire *wire, int offset) : wire(wire), offset(offset) { log_assert(wire != nullptr); } inline RTLIL::SigBit::SigBit(const RTLIL::SigChunk &chunk) : wire(chunk.wire) { log_assert(chunk.width == 1); if (wire) offset = chunk.offset; else data = chunk.data[0]; } inline RTLIL::SigBit::SigBit(const RTLIL::SigChunk &chunk, int index) : wire(chunk.wire) { if (wire) offset = chunk.offset + index; else data = chunk.data[index]; } -inline RTLIL::SigBit::SigBit(const RTLIL::SigBit &sigbit) : wire(sigbit.wire), data(sigbit.data){ if (wire) offset = sigbit.offset; } inline bool RTLIL::SigBit::operator<(const RTLIL::SigBit &other) const { if (wire == other.wire) diff --git a/kernel/satgen.h b/kernel/satgen.h index 88b84b7e6..3929a8708 100644 --- a/kernel/satgen.h +++ b/kernel/satgen.h @@ -279,7 +279,7 @@ struct SatGen bool arith_undef_handled = false; bool is_arith_compare = cell->type.in(ID($lt), ID($le), ID($ge), ID($gt)); - if (model_undef && (cell->type.in(ID($add), ID($sub), ID($mul), ID($div), ID($mod)) || is_arith_compare)) + if (model_undef && (cell->type.in(ID($add), ID($sub), ID($mul), ID($div), ID($mod), ID($divfloor), ID($modfloor)) || is_arith_compare)) { std::vector<int> undef_a = importUndefSigSpec(cell->getPort(ID::A), timestep); std::vector<int> undef_b = importUndefSigSpec(cell->getPort(ID::B), timestep); @@ -293,7 +293,7 @@ struct SatGen int undef_any_b = ez->expression(ezSAT::OpOr, undef_b); int undef_y_bit = ez->OR(undef_any_a, undef_any_b); - if (cell->type.in(ID($div), ID($mod))) { + if (cell->type.in(ID($div), ID($mod), ID($divfloor), ID($modfloor))) { std::vector<int> b = importSigSpec(cell->getPort(ID::B), timestep); undef_y_bit = ez->OR(undef_y_bit, ez->NOT(ez->expression(ezSAT::OpOr, b))); } @@ -935,7 +935,7 @@ struct SatGen return true; } - if (cell->type.in(ID($div), ID($mod))) + if (cell->type.in(ID($div), ID($mod), ID($divfloor), ID($modfloor))) { std::vector<int> a = importDefSigSpec(cell->getPort(ID::A), timestep); std::vector<int> b = importDefSigSpec(cell->getPort(ID::B), timestep); @@ -970,23 +970,48 @@ struct SatGen } std::vector<int> y_tmp = ignore_div_by_zero ? yy : ez->vec_var(y.size()); + + // modulo calculation + std::vector<int> modulo_trunc; + int floored_eq_trunc; + if (cell->parameters[ID::A_SIGNED].as_bool() && cell->parameters[ID::B_SIGNED].as_bool()) { + modulo_trunc = ez->vec_ite(a.back(), ez->vec_neg(chain_buf), chain_buf); + // floor == trunc when sgn(a) == sgn(b) or trunc == 0 + floored_eq_trunc = ez->OR(ez->IFF(a.back(), b.back()), ez->NOT(ez->expression(ezSAT::OpOr, modulo_trunc))); + } else { + modulo_trunc = chain_buf; + floored_eq_trunc = ez->CONST_TRUE; + } + if (cell->type == ID($div)) { if (cell->parameters[ID::A_SIGNED].as_bool() && cell->parameters[ID::B_SIGNED].as_bool()) ez->assume(ez->vec_eq(y_tmp, ez->vec_ite(ez->XOR(a.back(), b.back()), ez->vec_neg(y_u), y_u))); else ez->assume(ez->vec_eq(y_tmp, y_u)); - } else { + } else if (cell->type == ID($mod)) { + ez->assume(ez->vec_eq(y_tmp, modulo_trunc)); + } else if (cell->type == ID($divfloor)) { if (cell->parameters[ID::A_SIGNED].as_bool() && cell->parameters[ID::B_SIGNED].as_bool()) - ez->assume(ez->vec_eq(y_tmp, ez->vec_ite(a.back(), ez->vec_neg(chain_buf), chain_buf))); + ez->assume(ez->vec_eq(y_tmp, ez->vec_ite( + ez->XOR(a.back(), b.back()), + ez->vec_neg(ez->vec_ite( + ez->vec_reduce_or(modulo_trunc), + ez->vec_add(y_u, ez->vec_const_unsigned(1, y_u.size())), + y_u + )), + y_u + ))); else - ez->assume(ez->vec_eq(y_tmp, chain_buf)); + ez->assume(ez->vec_eq(y_tmp, y_u)); + } else if (cell->type == ID($modfloor)) { + ez->assume(ez->vec_eq(y_tmp, ez->vec_ite(floored_eq_trunc, modulo_trunc, ez->vec_add(modulo_trunc, b)))); } if (ignore_div_by_zero) { ez->assume(ez->expression(ezSAT::OpOr, b)); } else { std::vector<int> div_zero_result; - if (cell->type == ID($div)) { + if (cell->type.in(ID($div), ID($divfloor))) { if (cell->parameters[ID::A_SIGNED].as_bool() && cell->parameters[ID::B_SIGNED].as_bool()) { std::vector<int> all_ones(y.size(), ez->CONST_TRUE); std::vector<int> only_first_one(y.size(), ez->CONST_FALSE); @@ -996,7 +1021,8 @@ struct SatGen div_zero_result.insert(div_zero_result.end(), cell->getPort(ID::A).size(), ez->CONST_TRUE); div_zero_result.insert(div_zero_result.end(), y.size() - div_zero_result.size(), ez->CONST_FALSE); } - } else { + } else if (cell->type.in(ID($mod), ID($modfloor))) { + // a mod 0 = a int copy_a_bits = min(cell->getPort(ID::A).size(), cell->getPort(ID::B).size()); div_zero_result.insert(div_zero_result.end(), a.begin(), a.begin() + copy_a_bits); if (cell->parameters[ID::A_SIGNED].as_bool() && cell->parameters[ID::B_SIGNED].as_bool()) diff --git a/manual/CHAPTER_CellLib.tex b/manual/CHAPTER_CellLib.tex index 55abd9b96..32c530582 100644 --- a/manual/CHAPTER_CellLib.tex +++ b/manual/CHAPTER_CellLib.tex @@ -139,6 +139,8 @@ Verilog & Cell Type \\ \lstinline[language=Verilog]; Y = A * B; & {\tt \$mul} \\ \lstinline[language=Verilog]; Y = A / B; & {\tt \$div} \\ \lstinline[language=Verilog]; Y = A % B; & {\tt \$mod} \\ +\multicolumn{1}{c}{\tt [N/A]} & {\tt \$divfloor} \\ +\multicolumn{1}{c}{\tt [N/A]} & {\tt \$modfoor} \\ \lstinline[language=Verilog]; Y = A ** B; & {\tt \$pow} \\ \end{tabular} \caption{Cell types for binary operators with their corresponding Verilog expressions.} @@ -161,6 +163,27 @@ For the binary cells that output a logical value ({\tt \$logic\_and}, {\tt \$log {\tt \$gt}), when the \B{Y\_WIDTH} parameter is greater than 1, the output is zero-extended, and only the least significant bit varies. +Division and modulo cells are available in two rounding modes. The original {\tt \$div} and {\tt \$mod} +cells are based on truncating division, and correspond to the semantics of the verilog {\tt /} and +{\tt \%} operators. The {\tt \$divfloor} and {\tt \$modfloor} cells represent flooring division and +flooring modulo, the latter of which is also known as ``remainder'' in several languages. See +table~\ref{tab:CellLib_divmod} for a side-by-side comparison between the different semantics. + +\begin{table}[h] +\hfil +\begin{tabular}{lr|rr|rr} +\multirow{2}{*}{Division} & \multirow{2}{*}{Result} & \multicolumn{2}{c|}{Truncating} & \multicolumn{2}{c}{Flooring} \\ + & & {\tt \$div} & {\tt \$mod} & {\tt \$divfloor} & {\tt \$modfloor} \\ +\hline +{\tt -10 / 3} & {\tt -3.3} & {\tt -3} & {\tt -1} & {\tt -4} & {\tt 2} \\ +{\tt 10 / -3} & {\tt -3.3} & {\tt -3} & {\tt 1} & {\tt -4} & {\tt -2} \\ +{\tt -10 / -3} & {\tt 3.3} & {\tt 3} & {\tt -1} & {\tt 3} & {\tt -1} \\ +{\tt 10 / 3} & {\tt 3.3} & {\tt 3} & {\tt 1} & {\tt 3} & {\tt 1} \\ +\end{tabular} +\caption{Comparison between different rounding modes for division and modulo cells.} +\label{tab:CellLib_divmod} +\end{table} + \subsection{Multiplexers} Multiplexers are generated by the Verilog HDL frontend for {\tt diff --git a/manual/CHAPTER_Overview.tex b/manual/CHAPTER_Overview.tex index be37d8d39..ac0f48e47 100644 --- a/manual/CHAPTER_Overview.tex +++ b/manual/CHAPTER_Overview.tex @@ -184,9 +184,12 @@ may hold important information for Yosys developers can be used without disturbing external tools. For example the Verilog backend assigns names in the form {\tt \_{\it integer}\_}. \end{itemize} -In order to avoid programming errors, the RTLIL data structures check if all -identifiers start with either a backslash or a dollar sign and generate a -runtime error if this rule is violated. +Whitespace and control characters (any character with an ASCII code 32 or less) are not allowed +in RTLIL identifiers; most frontends and backends cannot support these characters in identifiers. + +In order to avoid programming errors, the RTLIL data structures check if all identifiers start +with either a backslash or a dollar sign, and contain no whitespace or control characters. +Violating these rules results in a runtime error. All RTLIL identifiers are case sensitive. diff --git a/manual/PRESENTATION_Prog.tex b/manual/PRESENTATION_Prog.tex index b85eda892..a9416f82a 100644 --- a/manual/PRESENTATION_Prog.tex +++ b/manual/PRESENTATION_Prog.tex @@ -307,7 +307,7 @@ cell name from the internal cell library: \begin{lstlisting}[xleftmargin=1cm, basicstyle=\ttfamily\fontsize{6pt}{7pt}\selectfont] $not $pos $neg $and $or $xor $xnor $reduce_and $reduce_or $reduce_xor $reduce_xnor $reduce_bool $shl $shr $sshl $sshr $lt $le $eq $ne $eqx $nex $ge $gt $add $sub $mul $div $mod -$pow $logic_not $logic_and $logic_or $mux $pmux $slice $concat $lut $assert $sr $dff +$divfloor $modfloor $pow $logic_not $logic_and $logic_or $mux $pmux $slice $concat $lut $assert $sr $dff $dffsr $adff $dlatch $dlatchsr $memrd $memwr $mem $fsm $_NOT_ $_AND_ $_OR_ $_XOR_ $_MUX_ $_SR_NN_ $_SR_NP_ $_SR_PN_ $_SR_PP_ $_DFF_N_ $_DFF_P_ $_DFF_NN0_ $_DFF_NN1_ $_DFF_NP0_ $_DFF_NP1_ $_DFF_PN0_ $_DFF_PN1_ $_DFF_PP0_ $_DFF_PP1_ $_DFFSR_NNN_ $_DFFSR_NNP_ $_DFFSR_NPN_ $_DFFSR_NPP_ $_DFFSR_PNN_ diff --git a/manual/PRESENTATION_Prog/Makefile b/manual/PRESENTATION_Prog/Makefile index 794f5c12c..7e3cf814b 100644 --- a/manual/PRESENTATION_Prog/Makefile +++ b/manual/PRESENTATION_Prog/Makefile @@ -1,8 +1,11 @@ all: test0.log test1.log test2.log +CXXFLAGS=$(shell ../../yosys-config --cxxflags) +DATDIR=$(shell ../../yosys-config --datdir) + my_cmd.so: my_cmd.cc - ../../yosys-config --exec --cxx --cxxflags --ldflags -o my_cmd.so -shared my_cmd.cc --ldlibs + ../../yosys-config --exec --cxx $(subst $(DATDIR),../../share,$(CXXFLAGS)) --ldflags -o my_cmd.so -shared my_cmd.cc --ldlibs test0.log: my_cmd.so ../../yosys -Ql test0.log_new -m ./my_cmd.so -p 'my_cmd foo bar' absval_ref.v diff --git a/misc/py_wrap_generator.py b/misc/py_wrap_generator.py index fac5b48a4..fa23e3b2c 100644 --- a/misc/py_wrap_generator.py +++ b/misc/py_wrap_generator.py @@ -312,16 +312,16 @@ class PythonListTranslator(Translator): text += prefix + "\t" + known_containers[types[0].name].typename + " " + tmp_name + " = boost::python::extract<" + known_containers[types[0].name].typename + ">(" + varname + "[" + cntr_name + "]);" text += known_containers[types[0].name].translate(tmp_name, types[0].cont.args, prefix+"\t") tmp_name = tmp_name + "___tmp" - text += prefix + "\t" + varname + "___tmp." + c.insert_name + "(" + tmp_name + ");" + text += prefix + "\t" + varname + "___tmp" + c.insert_name + "(" + tmp_name + ");" elif types[0].name in classnames: text += prefix + "\t" + types[0].name + "* " + tmp_name + " = boost::python::extract<" + types[0].name + "*>(" + varname + "[" + cntr_name + "]);" if types[0].attr_type == attr_types.star: - text += prefix + "\t" + varname + "___tmp." + c.insert_name + "(" + tmp_name + "->get_cpp_obj());" + text += prefix + "\t" + varname + "___tmp" + c.insert_name + "(" + tmp_name + "->get_cpp_obj());" else: - text += prefix + "\t" + varname + "___tmp." + c.insert_name + "(*" + tmp_name + "->get_cpp_obj());" + text += prefix + "\t" + varname + "___tmp" + c.insert_name + "(*" + tmp_name + "->get_cpp_obj());" else: text += prefix + "\t" + types[0].name + " " + tmp_name + " = boost::python::extract<" + types[0].name + ">(" + varname + "[" + cntr_name + "]);" - text += prefix + "\t" + varname + "___tmp." + c.insert_name + "(" + tmp_name + ");" + text += prefix + "\t" + varname + "___tmp" + c.insert_name + "(" + tmp_name + ");" text += prefix + "}" return text @@ -349,19 +349,24 @@ class PythonListTranslator(Translator): text += prefix + "}" return text +class IDictTranslator(PythonListTranslator): + typename = "boost::python::list" + orig_name = "idict" + insert_name = "" + #Sub-type for std::set class SetTranslator(PythonListTranslator): - insert_name = "insert" + insert_name = ".insert" orig_name = "std::set" #Sub-type for std::vector class VectorTranslator(PythonListTranslator): - insert_name = "push_back" + insert_name = ".push_back" orig_name = "std::vector" #Sub-type for pool class PoolTranslator(PythonListTranslator): - insert_name = "insert" + insert_name = ".insert" orig_name = "pool" #Translates dict-types (dict, std::map), that only differ in their name and @@ -528,6 +533,7 @@ known_containers = { "std::set" : SetTranslator, "std::vector" : VectorTranslator, "pool" : PoolTranslator, + "idict" : IDictTranslator, "dict" : DictTranslator, "std::pair" : TupleTranslator, "std::map" : MapTranslator diff --git a/passes/cmds/Makefile.inc b/passes/cmds/Makefile.inc index a88980eaf..53bfd40c6 100644 --- a/passes/cmds/Makefile.inc +++ b/passes/cmds/Makefile.inc @@ -39,3 +39,4 @@ OBJS += passes/cmds/bugpoint.o endif OBJS += passes/cmds/scratchpad.o OBJS += passes/cmds/logger.o +OBJS += passes/cmds/printattrs.o diff --git a/passes/cmds/blackbox.cc b/passes/cmds/blackbox.cc index 5c0405f15..b8297cd77 100644 --- a/passes/cmds/blackbox.cc +++ b/passes/cmds/blackbox.cc @@ -48,31 +48,7 @@ struct BlackboxPass : public Pass { for (auto module : design->selected_whole_modules_warn()) { - pool<Cell*> remove_cells; - pool<Wire*> remove_wires; - - for (auto cell : module->cells()) - remove_cells.insert(cell); - - for (auto wire : module->wires()) - if (wire->port_id == 0) - remove_wires.insert(wire); - - for (auto it = module->memories.begin(); it != module->memories.end(); ++it) - delete it->second; - module->memories.clear(); - - for (auto it = module->processes.begin(); it != module->processes.end(); ++it) - delete it->second; - module->processes.clear(); - - module->new_connections(std::vector<RTLIL::SigSig>()); - - for (auto cell : remove_cells) - module->remove(cell); - - module->remove(remove_wires); - + module->makeblackbox(); module->set_bool_attribute(ID::blackbox); } } diff --git a/passes/cmds/printattrs.cc b/passes/cmds/printattrs.cc new file mode 100644 index 000000000..80dbfa259 --- /dev/null +++ b/passes/cmds/printattrs.cc @@ -0,0 +1,90 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2020 Alberto Gonzalez <boqwxp@airmail.cc> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "kernel/yosys.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct PrintAttrsPass : public Pass { + PrintAttrsPass() : Pass("printattrs", "print attributes of selected objects") { } + void help() YS_OVERRIDE + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" printattrs [selection]\n"); + log("\n"); + log("Print all attributes of the selected objects.\n"); + log("\n"); + log("\n"); + } + + static std::string get_indent_str(const unsigned int indent) { + return stringf("%*s", indent, ""); + } + + static void log_const(const RTLIL::IdString &s, const RTLIL::Const &x, const unsigned int indent) { + if (x.flags == RTLIL::CONST_FLAG_STRING) + log("%s(* %s=\"%s\" *)\n", get_indent_str(indent).c_str(), log_id(s), x.decode_string().c_str()); + else if (x.flags == RTLIL::CONST_FLAG_NONE) + log("%s(* %s=%s *)\n", get_indent_str(indent).c_str(), log_id(s), x.as_string().c_str()); + else + log_assert(x.flags == RTLIL::CONST_FLAG_STRING || x.flags == RTLIL::CONST_FLAG_NONE); //intended to fail + } + + void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE + { + size_t argidx = 1; + extra_args(args, argidx, design); + + unsigned int indent = 0; + for (auto mod : design->selected_modules()) + { + if (design->selected_whole_module(mod)) { + log("%s%s\n", get_indent_str(indent).c_str(), log_id(mod->name)); + indent += 2; + for (auto &it : mod->attributes) + log_const(it.first, it.second, indent); + } + + for (auto cell : mod->selected_cells()) { + log("%s%s\n", get_indent_str(indent).c_str(), log_id(cell->name)); + indent += 2; + for (auto &it : cell->attributes) + log_const(it.first, it.second, indent); + indent -= 2; + } + + for (auto wire : mod->selected_wires()) { + log("%s%s\n", get_indent_str(indent).c_str(), log_id(wire->name)); + indent += 2; + for (auto &it : wire->attributes) + log_const(it.first, it.second, indent); + indent -= 2; + } + + if (design->selected_whole_module(mod)) + indent -= 2; + } + + log("\n"); + } +} PrintAttrsPass; + +PRIVATE_NAMESPACE_END diff --git a/passes/cmds/stat.cc b/passes/cmds/stat.cc index 6c4bc0e5b..30436d829 100644 --- a/passes/cmds/stat.cc +++ b/passes/cmds/stat.cc @@ -109,7 +109,7 @@ struct statdata_t ID($lut), ID($and), ID($or), ID($xor), ID($xnor), ID($shl), ID($shr), ID($sshl), ID($sshr), ID($shift), ID($shiftx), ID($lt), ID($le), ID($eq), ID($ne), ID($eqx), ID($nex), ID($ge), ID($gt), - ID($add), ID($sub), ID($mul), ID($div), ID($mod), ID($pow), ID($alu))) { + ID($add), ID($sub), ID($mul), ID($div), ID($mod), ID($divfloor), ID($modfloor), ID($pow), ID($alu))) { int width_a = cell->hasPort(ID::A) ? GetSize(cell->getPort(ID::A)) : 0; int width_b = cell->hasPort(ID::B) ? GetSize(cell->getPort(ID::B)) : 0; int width_y = cell->hasPort(ID::Y) ? GetSize(cell->getPort(ID::Y)) : 0; diff --git a/passes/fsm/fsm_extract.cc b/passes/fsm/fsm_extract.cc index 3840aabc8..6f99886f0 100644 --- a/passes/fsm/fsm_extract.cc +++ b/passes/fsm/fsm_extract.cc @@ -394,7 +394,7 @@ static void extract_fsm(RTLIL::Wire *wire) RTLIL::Cell *cell = module->cells_.at(cellport.first); RTLIL::SigSpec port_sig = assign_map(cell->getPort(cellport.second)); RTLIL::SigSpec unconn_sig = port_sig.extract(ctrl_out); - RTLIL::Wire *unconn_wire = module->addWire(stringf("$fsm_unconnect$%s$%d", log_signal(unconn_sig), autoidx++), unconn_sig.size()); + RTLIL::Wire *unconn_wire = module->addWire(stringf("$fsm_unconnect$%d", autoidx++), unconn_sig.size()); port_sig.replace(unconn_sig, RTLIL::SigSpec(unconn_wire), &cell->connections_[cellport.second]); } } diff --git a/passes/hierarchy/hierarchy.cc b/passes/hierarchy/hierarchy.cc index 95d74d1eb..b4eb0f1dd 100644 --- a/passes/hierarchy/hierarchy.cc +++ b/passes/hierarchy/hierarchy.cc @@ -254,16 +254,6 @@ bool expand_module(RTLIL::Design *design, RTLIL::Module *module, bool flag_check // some lists, so that the ports for sub-modules can be replaced further down: for (auto &conn : cell->connections()) { if(mod->wire(conn.first) != nullptr && mod->wire(conn.first)->get_bool_attribute(ID::is_interface)) { // Check if the connection is present as an interface in the sub-module's port list - //const pool<string> &interface_type_pool = mod->wire(conn.first)->get_strpool_attribute(ID::interface_type); - //for (auto &d : interface_type_pool) { // TODO: Compare interface type to type in parent module (not crucially important, but good for robustness) - //} - - // Find if the sub-module has set a modport for the current interface connection: - const pool<string> &interface_modport_pool = mod->wire(conn.first)->get_strpool_attribute(ID::interface_modport); - std::string interface_modport = ""; - for (auto &d : interface_modport_pool) { - interface_modport = "\\" + d; - } if(conn.second.bits().size() == 1 && conn.second.bits()[0].wire->get_bool_attribute(ID::is_interface)) { // Check if the connected wire is a potential interface in the parent module std::string interface_name_str = conn.second.bits()[0].wire->name.str(); interface_name_str.replace(0,23,""); // Strip the prefix '$dummywireforinterface' from the dummy wire to get the name @@ -297,9 +287,12 @@ bool expand_module(RTLIL::Design *design, RTLIL::Module *module, bool flag_check connections_to_remove.push_back(conn.first); interfaces_to_add_to_submodule[conn.first] = interfaces_in_module.at(interface_name2); - // Add modports to a dict which will be passed to AstModule::derive - if (interface_modport != "") { - modports_used_in_submodule[conn.first] = interface_modport; + // Find if the sub-module has set a modport for the current + // interface connection. Add any modports to a dict which will + // be passed to AstModule::derive + string modport_name = mod->wire(conn.first)->get_string_attribute(ID::interface_modport); + if (!modport_name.empty()) { + modports_used_in_submodule[conn.first] = "\\" + modport_name; } } else not_found_interface = true; @@ -574,9 +567,9 @@ struct HierarchyPass : public Pass { log("\n"); log("In parametric designs, a module might exists in several variations with\n"); log("different parameter values. This pass looks at all modules in the current\n"); - log("design an re-runs the language frontends for the parametric modules as\n"); + log("design and re-runs the language frontends for the parametric modules as\n"); log("needed. It also resolves assignments to wired logic data types (wand/wor),\n"); - log("resolves positional module parameters, unroll array instances, and more.\n"); + log("resolves positional module parameters, unrolls array instances, and more.\n"); log("\n"); log(" -check\n"); log(" also check the design hierarchy. this generates an error when\n"); diff --git a/passes/memory/memory_share.cc b/passes/memory/memory_share.cc index 477246687..e11958bd6 100644 --- a/passes/memory/memory_share.cc +++ b/passes/memory/memory_share.cc @@ -715,6 +715,8 @@ struct MemoryShareWorker cone_ct.cell_types.erase(ID($mul)); cone_ct.cell_types.erase(ID($mod)); cone_ct.cell_types.erase(ID($div)); + cone_ct.cell_types.erase(ID($modfloor)); + cone_ct.cell_types.erase(ID($divfloor)); cone_ct.cell_types.erase(ID($pow)); cone_ct.cell_types.erase(ID($shl)); cone_ct.cell_types.erase(ID($shr)); diff --git a/passes/opt/opt_expr.cc b/passes/opt/opt_expr.cc index 777a24777..e5b8bda95 100644 --- a/passes/opt/opt_expr.cc +++ b/passes/opt/opt_expr.cc @@ -864,7 +864,7 @@ void replace_const_cells(RTLIL::Design *design, RTLIL::Module *module, bool cons skip_fine_alu: if (cell->type.in(ID($reduce_xor), ID($reduce_xnor), ID($shift), ID($shiftx), ID($shl), ID($shr), ID($sshl), ID($sshr), - ID($lt), ID($le), ID($ge), ID($gt), ID($neg), ID($add), ID($sub), ID($mul), ID($div), ID($mod), ID($pow))) + ID($lt), ID($le), ID($ge), ID($gt), ID($neg), ID($add), ID($sub), ID($mul), ID($div), ID($mod), ID($divfloor), ID($modfloor), ID($pow))) { RTLIL::SigSpec sig_a = assign_map(cell->getPort(ID::A)); RTLIL::SigSpec sig_b = cell->hasPort(ID::B) ? assign_map(cell->getPort(ID::B)) : RTLIL::SigSpec(); @@ -883,7 +883,7 @@ skip_fine_alu: if (0) { found_the_x_bit: cover_list("opt.opt_expr.xbit", "$reduce_xor", "$reduce_xnor", "$shl", "$shr", "$sshl", "$sshr", "$shift", "$shiftx", - "$lt", "$le", "$ge", "$gt", "$neg", "$add", "$sub", "$mul", "$div", "$mod", "$pow", cell->type.str()); + "$lt", "$le", "$ge", "$gt", "$neg", "$add", "$sub", "$mul", "$div", "$mod", "$divfloor", "$modfloor", "$pow", cell->type.str()); if (cell->type.in(ID($reduce_xor), ID($reduce_xnor), ID($lt), ID($le), ID($ge), ID($gt))) replace_cell(assign_map, module, cell, "x-bit in input", ID::Y, RTLIL::State::Sx); else @@ -1469,6 +1469,8 @@ skip_identity: FOLD_2ARG_CELL(mul) FOLD_2ARG_CELL(div) FOLD_2ARG_CELL(mod) + FOLD_2ARG_CELL(divfloor) + FOLD_2ARG_CELL(modfloor) FOLD_2ARG_CELL(pow) FOLD_1ARG_CELL(pos) @@ -1583,9 +1585,11 @@ skip_identity: } } - if (!keepdc && cell->type.in(ID($div), ID($mod))) + if (!keepdc && cell->type.in(ID($div), ID($mod), ID($divfloor), ID($modfloor))) { + bool a_signed = cell->parameters[ID::A_SIGNED].as_bool(); bool b_signed = cell->parameters[ID::B_SIGNED].as_bool(); + SigSpec sig_a = assign_map(cell->getPort(ID::A)); SigSpec sig_b = assign_map(cell->getPort(ID::B)); SigSpec sig_y = assign_map(cell->getPort(ID::Y)); @@ -1610,11 +1614,13 @@ skip_identity: for (int i = 1; i < (b_signed ? sig_b.size()-1 : sig_b.size()); i++) if (b_val == (1 << i)) { - if (cell->type == ID($div)) + if (cell->type.in(ID($div), ID($divfloor))) { cover("opt.opt_expr.div_shift"); - log_debug("Replacing divide-by-%d cell `%s' in module `%s' with shift-by-%d.\n", + bool is_truncating = cell->type == ID($div); + log_debug("Replacing %s-divide-by-%d cell `%s' in module `%s' with shift-by-%d.\n", + is_truncating ? "truncating" : "flooring", b_val, cell->name.c_str(), module->name.c_str(), i); std::vector<RTLIL::SigBit> new_b = RTLIL::SigSpec(i, 6); @@ -1622,17 +1628,35 @@ skip_identity: while (GetSize(new_b) > 1 && new_b.back() == RTLIL::State::S0) new_b.pop_back(); - cell->type = ID($shr); + cell->type = ID($sshr); cell->parameters[ID::B_WIDTH] = GetSize(new_b); cell->parameters[ID::B_SIGNED] = false; cell->setPort(ID::B, new_b); + + // Truncating division is the same as flooring division, except when + // the result is negative and there is a remainder - then trunc = floor + 1 + if (is_truncating && a_signed) { + Wire *flooring = module->addWire(NEW_ID, sig_y.size()); + cell->setPort(ID::Y, flooring); + + Wire *result_neg = module->addWire(NEW_ID); + module->addXor(NEW_ID, sig_a[sig_a.size()-1], sig_b[sig_b.size()-1], result_neg); + Wire *rem_nonzero = module->addWire(NEW_ID); + module->addReduceOr(NEW_ID, sig_a.extract(0, i), rem_nonzero); + Wire *should_add = module->addWire(NEW_ID); + module->addAnd(NEW_ID, result_neg, rem_nonzero, should_add); + module->addAdd(NEW_ID, flooring, should_add, sig_y); + } + cell->check(); } - else + else if (cell->type.in(ID($mod), ID($modfloor))) { cover("opt.opt_expr.mod_mask"); - log_debug("Replacing modulo-by-%d cell `%s' in module `%s' with bitmask.\n", + bool is_truncating = cell->type == ID($mod); + log_debug("Replacing %s-modulo-by-%d cell `%s' in module `%s' with bitmask.\n", + is_truncating ? "truncating" : "flooring", b_val, cell->name.c_str(), module->name.c_str()); std::vector<RTLIL::SigBit> new_b = RTLIL::SigSpec(State::S1, i); @@ -1643,6 +1667,24 @@ skip_identity: cell->type = ID($and); cell->parameters[ID::B_WIDTH] = GetSize(new_b); cell->setPort(ID::B, new_b); + + // truncating modulo has the same masked bits as flooring modulo, but + // the sign bits are those of A (except when R=0) + if (is_truncating && a_signed) { + Wire *flooring = module->addWire(NEW_ID, sig_y.size()); + cell->setPort(ID::Y, flooring); + SigSpec truncating = SigSpec(flooring).extract(0, i); + + Wire *rem_nonzero = module->addWire(NEW_ID); + module->addReduceOr(NEW_ID, truncating, rem_nonzero); + SigSpec a_sign = sig_a[sig_a.size()-1]; + Wire *extend_bit = module->addWire(NEW_ID); + module->addAnd(NEW_ID, a_sign, rem_nonzero, extend_bit); + + truncating.append(extend_bit); + module->addPos(NEW_ID, truncating, sig_y, true); + } + cell->check(); } diff --git a/passes/opt/opt_share.cc b/passes/opt/opt_share.cc index 1f69c98f4..cbace7bac 100644 --- a/passes/opt/opt_share.cc +++ b/passes/opt/opt_share.cc @@ -103,7 +103,7 @@ bool cell_supported(RTLIL::Cell *cell) if (sig_bi.is_fully_const() && sig_ci.is_fully_const() && sig_bi == sig_ci) return true; - } else if (cell->type.in(LOGICAL_OPS, SHIFT_OPS, BITWISE_OPS, RELATIONAL_OPS, ID($add), ID($sub), ID($mul), ID($div), ID($mod), ID($concat))) { + } else if (cell->type.in(LOGICAL_OPS, SHIFT_OPS, BITWISE_OPS, RELATIONAL_OPS, ID($add), ID($sub), ID($mul), ID($div), ID($mod), ID($divfloor), ID($modfloor), ID($concat))) { return true; } @@ -130,7 +130,7 @@ bool mergeable(RTLIL::Cell *a, RTLIL::Cell *b) RTLIL::IdString decode_port_semantics(RTLIL::Cell *cell, RTLIL::IdString port_name) { - if (cell->type.in(ID($lt), ID($le), ID($ge), ID($gt), ID($div), ID($mod), ID($concat), SHIFT_OPS) && port_name == ID::B) + if (cell->type.in(ID($lt), ID($le), ID($ge), ID($gt), ID($div), ID($mod), ID($divfloor), ID($modfloor), ID($concat), SHIFT_OPS) && port_name == ID::B) return port_name; return ""; diff --git a/passes/opt/share.cc b/passes/opt/share.cc index 2839507b0..988253edf 100644 --- a/passes/opt/share.cc +++ b/passes/opt/share.cc @@ -376,7 +376,7 @@ struct ShareWorker continue; } - if (cell->type.in(ID($mul), ID($div), ID($mod))) { + if (cell->type.in(ID($mul), ID($div), ID($mod), ID($divfloor), ID($modfloor))) { if (config.opt_aggressive || cell->parameters.at(ID::Y_WIDTH).as_int() >= 4) shareable_cells.insert(cell); continue; @@ -1133,6 +1133,8 @@ struct ShareWorker cone_ct.cell_types.erase(ID($mul)); cone_ct.cell_types.erase(ID($mod)); cone_ct.cell_types.erase(ID($div)); + cone_ct.cell_types.erase(ID($modfloor)); + cone_ct.cell_types.erase(ID($divfloor)); cone_ct.cell_types.erase(ID($pow)); cone_ct.cell_types.erase(ID($shl)); cone_ct.cell_types.erase(ID($shr)); @@ -1512,6 +1514,8 @@ struct SharePass : public Pass { config.generic_bin_ops.insert(ID($sub)); config.generic_bin_ops.insert(ID($div)); config.generic_bin_ops.insert(ID($mod)); + config.generic_bin_ops.insert(ID($divfloor)); + config.generic_bin_ops.insert(ID($modfloor)); // config.generic_bin_ops.insert(ID($pow)); config.generic_uni_ops.insert(ID($logic_not)); diff --git a/passes/opt/wreduce.cc b/passes/opt/wreduce.cc index 195400bf0..f60f2f8a8 100644 --- a/passes/opt/wreduce.cc +++ b/passes/opt/wreduce.cc @@ -37,7 +37,7 @@ struct WreduceConfig ID($and), ID($or), ID($xor), ID($xnor), ID($shl), ID($shr), ID($sshl), ID($sshr), ID($shift), ID($shiftx), ID($lt), ID($le), ID($eq), ID($ne), ID($eqx), ID($nex), ID($ge), ID($gt), - ID($add), ID($sub), ID($mul), // ID($div), ID($mod), ID($pow), + ID($add), ID($sub), ID($mul), // ID($div), ID($mod), ID($divfloor), ID($modfloor), ID($pow), ID($mux), ID($pmux), ID($dff), ID($adff) }); @@ -545,7 +545,7 @@ struct WreducePass : public Pass { } } - if (c->type.in(ID($div), ID($mod), ID($pow))) + if (c->type.in(ID($div), ID($mod), ID($divfloor), ID($modfloor), ID($pow))) { SigSpec A = c->getPort(ID::A); int original_a_width = GetSize(A); diff --git a/passes/sat/qbfsat.cc b/passes/sat/qbfsat.cc index c42760488..d6dbf8ef4 100644 --- a/passes/sat/qbfsat.cc +++ b/passes/sat/qbfsat.cc @@ -41,6 +41,8 @@ struct QbfSolveOptions { bool specialize, specialize_from_file, write_solution, nocleanup, dump_final_smt2, assume_outputs, assume_neg; bool nooptimize, nobisection; bool sat, unsat, show_smtbmc; + enum Solver{Z3, Yices, CVC4} solver; + int timeout; std::string specialize_soln_file; std::string write_soln_soln_file; std::string dump_final_smt2_file; @@ -48,12 +50,29 @@ struct QbfSolveOptions { QbfSolveOptions() : specialize(false), specialize_from_file(false), write_solution(false), nocleanup(false), dump_final_smt2(false), assume_outputs(false), assume_neg(false), nooptimize(false), nobisection(false), sat(false), unsat(false), show_smtbmc(false), - argidx(0) {}; + solver(Yices), timeout(0), argidx(0) {}; }; +std::string get_solver_name(const QbfSolveOptions &opt) { + if (opt.solver == opt.Solver::Z3) + return "z3"; + else if (opt.solver == opt.Solver::Yices) + return "yices"; + else if (opt.solver == opt.Solver::CVC4) + return "cvc4"; + else + log_cmd_error("unknown solver specified.\n"); + return ""; +} + void recover_solution(QbfSolutionType &sol) { YS_REGEX_TYPE sat_regex = YS_REGEX_COMPILE("Status: PASSED"); YS_REGEX_TYPE unsat_regex = YS_REGEX_COMPILE("Solver Error.*model is not available"); + YS_REGEX_TYPE unsat_regex2 = YS_REGEX_COMPILE("Status: FAILED"); + YS_REGEX_TYPE timeout_regex = YS_REGEX_COMPILE("No solution found! \\(timeout\\)"); + YS_REGEX_TYPE timeout_regex2 = YS_REGEX_COMPILE("No solution found! \\(interrupted\\)"); + YS_REGEX_TYPE unknown_regex = YS_REGEX_COMPILE("No solution found! \\(unknown\\)"); + YS_REGEX_TYPE unknown_regex2 = YS_REGEX_COMPILE("Unexpected EOF response from solver"); YS_REGEX_TYPE memout_regex = YS_REGEX_COMPILE("Solver Error:.*error \"out of memory\""); YS_REGEX_TYPE hole_value_regex = YS_REGEX_COMPILE_WITH_SUBS("Value for anyconst in [a-zA-Z0-9_]* \\(([^:]*:[^\\)]*)\\): (.*)"); #ifndef NDEBUG @@ -74,14 +93,40 @@ void recover_solution(QbfSolutionType &sol) { #endif sol.hole_to_value[loc] = val; } - else if (YS_REGEX_NS::regex_search(x, sat_regex)) + else if (YS_REGEX_NS::regex_search(x, sat_regex)) { sat_regex_found = true; - else if (YS_REGEX_NS::regex_search(x, unsat_regex)) + sol.sat = true; + sol.unknown = false; + } + else if (YS_REGEX_NS::regex_search(x, unsat_regex)) { unsat_regex_found = true; + sol.sat = false; + sol.unknown = false; + } else if (YS_REGEX_NS::regex_search(x, memout_regex)) { sol.unknown = true; log_warning("solver ran out of memory\n"); } + else if (YS_REGEX_NS::regex_search(x, timeout_regex)) { + sol.unknown = true; + log_warning("solver timed out\n"); + } + else if (YS_REGEX_NS::regex_search(x, timeout_regex2)) { + sol.unknown = true; + log_warning("solver timed out\n"); + } + else if (YS_REGEX_NS::regex_search(x, unknown_regex)) { + sol.unknown = true; + log_warning("solver returned \"unknown\"\n"); + } + else if (YS_REGEX_NS::regex_search(x, unsat_regex2)) { + unsat_regex_found = true; + sol.sat = false; + sol.unknown = false; + } + else if (YS_REGEX_NS::regex_search(x, unknown_regex2)) { + sol.unknown = true; + } } #ifndef NDEBUG log_assert(!sol.unknown && sol.sat? sat_regex_found : true); @@ -315,31 +360,23 @@ QbfSolutionType call_qbf_solver(RTLIL::Module *mod, const QbfSolveOptions &opt, QbfSolutionType ret; const std::string yosys_smtbmc_exe = proc_self_dirname() + "yosys-smtbmc"; const std::string smt2_command = "write_smt2 -stbv -wires " + tempdir_name + "/problem" + (iter_num != 0? stringf("%d", iter_num) : "") + ".smt2"; - const std::string smtbmc_cmd = yosys_smtbmc_exe + " -s z3 -t 1 -g --binary " + (opt.dump_final_smt2? "--dump-smt2 " + opt.dump_final_smt2_file + " " : "") + tempdir_name + "/problem" + (iter_num != 0? stringf("%d", iter_num) : "") + ".smt2 2>&1"; const std::string smtbmc_warning = "z3: WARNING:"; - const bool show_smtbmc = opt.show_smtbmc; + const std::string smtbmc_cmd = yosys_smtbmc_exe + " -s " + (get_solver_name(opt)) + (opt.timeout != 0? stringf(" --timeout %d", opt.timeout) : "") + " -t 1 -g --binary " + (opt.dump_final_smt2? "--dump-smt2 " + opt.dump_final_smt2_file + " " : "") + tempdir_name + "/problem" + (iter_num != 0? stringf("%d", iter_num) : "") + ".smt2 2>&1"; Pass::call(mod->design, smt2_command); - auto process_line = [&ret, &smtbmc_warning, &show_smtbmc, &quiet](const std::string &line) { + auto process_line = [&ret, &smtbmc_warning, &opt, &quiet](const std::string &line) { ret.stdout_lines.push_back(line.substr(0, line.size()-1)); //don't include trailing newline auto warning_pos = line.find(smtbmc_warning); if (warning_pos != std::string::npos) log_warning("%s", line.substr(warning_pos + smtbmc_warning.size() + 1).c_str()); else - if (show_smtbmc && !quiet) + if (opt.show_smtbmc && !quiet) log("smtbmc output: %s", line.c_str()); }; log_header(mod->design, "Solving QBF-SAT problem.\n"); if (!quiet) log("Launching \"%s\".\n", smtbmc_cmd.c_str()); - int retval = run_command(smtbmc_cmd, process_line); - if (retval == 0) { - ret.sat = true; - ret.unknown = false; - } else if (retval == 1) { - ret.sat = false; - ret.unknown = false; - } + run_command(smtbmc_cmd, process_line); recover_solution(ret); return ret; @@ -486,6 +523,35 @@ QbfSolveOptions parse_args(const std::vector<std::string> &args) { opt.nobisection = true; continue; } + else if (args[opt.argidx] == "-solver") { + if (args.size() <= opt.argidx + 1) + log_cmd_error("solver not specified.\n"); + else { + if (args[opt.argidx+1] == "z3") + opt.solver = opt.Solver::Z3; + else if (args[opt.argidx+1] == "yices") + opt.solver = opt.Solver::Yices; + else if (args[opt.argidx+1] == "cvc4") + opt.solver = opt.Solver::CVC4; + else + log_cmd_error("Unknown solver \"%s\".\n", args[opt.argidx+1].c_str()); + opt.argidx++; + } + continue; + } + else if (args[opt.argidx] == "-timeout") { + if (args.size() <= opt.argidx + 1) + log_cmd_error("timeout not specified.\n"); + else { + int timeout = atoi(args[opt.argidx+1].c_str()); + if (timeout > 0) + opt.timeout = timeout; + else + log_cmd_error("timeout must be greater than 0.\n"); + opt.argidx++; + } + continue; + } else if (args[opt.argidx] == "-sat") { opt.sat = true; continue; @@ -563,21 +629,20 @@ struct QbfSatPass : public Pass { log("\n"); log(" qbfsat [options] [selection]\n"); log("\n"); - log("This command solves a 2QBF-SAT problem defined over the currently selected module.\n"); - log("Existentially-quantified variables are declared by assigning a wire \"$anyconst\".\n"); - log("Universally-quantified variables may be explicitly declared by assigning a wire\n"); - log("\"$allconst\", but module inputs will be treated as universally-quantified variables\n"); - log("by default.\n"); + log("This command solves an \"exists-forall\" 2QBF-SAT problem defined over the currently\n"); + log("selected module. Existentially-quantified variables are declared by assigning a wire\n"); + log("\"$anyconst\". Universally-quantified variables may be explicitly declared by assigning\n"); + log("a wire \"$allconst\", but module inputs will be treated as universally-quantified\n"); + log("variables by default.\n"); log("\n"); log(" -nocleanup\n"); - log(" Do not delete temporary files and directories. Useful for\n"); - log(" debugging.\n"); + log(" Do not delete temporary files and directories. Useful for debugging.\n"); log("\n"); log(" -dump-final-smt2 <file>\n"); log(" Pass the --dump-smt2 option to yosys-smtbmc.\n"); log("\n"); log(" -assume-outputs\n"); - log(" Add an $assume cell for the conjunction of all one-bit module output wires.\n"); + log(" Add an \"$assume\" cell for the conjunction of all one-bit module output wires.\n"); log("\n"); log(" -assume-negative-polarity\n"); log(" When adding $assume cells for one-bit module output wires, assume they are\n"); @@ -586,15 +651,21 @@ struct QbfSatPass : public Pass { log("\n"); log(" -nooptimize\n"); log(" Ignore \"\\minimize\" and \"\\maximize\" attributes, do not emit \"(maximize)\" or\n"); - log(" \"(minimize)\" in the SMTLIBv2, and generally make no attempt to optimize anything.\n"); + log(" \"(minimize)\" in the SMT-LIBv2, and generally make no attempt to optimize anything.\n"); log("\n"); log(" -nobisection\n"); log(" If a wire is marked with the \"\\minimize\" or \"\\maximize\" attribute, do not\n"); log(" attempt to optimize that value with the default iterated solving and threshold\n"); log(" bisection approach. Instead, have yosys-smtbmc emit a \"(minimize)\" or \"(maximize)\"\n"); - log(" command in the SMTLIBv2 output and hope that the solver supports optimizing\n"); + log(" command in the SMT-LIBv2 output and hope that the solver supports optimizing\n"); log(" quantified bitvector problems.\n"); log("\n"); + log(" -solver <solver>\n"); + log(" Use a particular solver. Choose one of: \"z3\", \"yices\", and \"cvc4\".\n"); + log("\n"); + log(" -timeout <value>\n"); + log(" Set the per-iteration timeout in seconds.\n"); + log("\n"); log(" -sat\n"); log(" Generate an error if the solver does not return \"sat\".\n"); log("\n"); @@ -605,15 +676,16 @@ struct QbfSatPass : public Pass { log(" Print the output from yosys-smtbmc.\n"); log("\n"); log(" -specialize\n"); - log(" Replace all \"$anyconst\" cells with constant values determined by the solver.\n"); + log(" If the problem is satisfiable, replace each \"$anyconst\" cell with its\n"); + log(" corresponding constant value from the model produced by the solver.\n"); log("\n"); log(" -specialize-from-file <solution file>\n"); - log(" Do not run the solver, but instead only attempt to replace all \"$anyconst\"\n"); - log(" cells in the current module with values provided by the specified file.\n"); + log(" Do not run the solver, but instead only attempt to replace each \"$anyconst\"\n"); + log(" cell in the current module with a constant value provided by the specified file.\n"); log("\n"); log(" -write-solution <solution file>\n"); - log(" Write the assignments discovered by the solver for all \"$anyconst\" cells\n"); - log(" to the specified file."); + log(" If the problem is satisfiable, write the corresponding constant value for each\n"); + log(" \"$anyconst\" cell from the model produced by the solver to the specified file."); log("\n"); log("\n"); } @@ -641,7 +713,6 @@ struct QbfSatPass : public Pass { QbfSolutionType ret = qbf_solve(module, opt); module = design->module(module_name); if (ret.unknown) { - log_warning("solver did not give an answer\n"); if (opt.sat || opt.unsat) log_cmd_error("expected problem to be %s\n", opt.sat? "SAT" : "UNSAT"); } diff --git a/passes/techmap/.gitignore b/passes/techmap/.gitignore deleted file mode 100644 index e6dcc6bc0..000000000 --- a/passes/techmap/.gitignore +++ /dev/null @@ -1 +0,0 @@ -techmap.inc diff --git a/passes/techmap/Makefile.inc b/passes/techmap/Makefile.inc index 1802ba0de..a54b4913d 100644 --- a/passes/techmap/Makefile.inc +++ b/passes/techmap/Makefile.inc @@ -1,4 +1,5 @@ +OBJS += passes/techmap/flatten.o OBJS += passes/techmap/techmap.o OBJS += passes/techmap/simplemap.o OBJS += passes/techmap/dfflibmap.o @@ -45,18 +46,6 @@ OBJS += passes/techmap/flowmap.o OBJS += passes/techmap/extractinv.o endif -GENFILES += passes/techmap/techmap.inc - -passes/techmap/techmap.inc: techlibs/common/techmap.v - $(Q) mkdir -p $(dir $@) - $(P) echo "// autogenerated from $<" > $@.new - $(Q) echo "static char stdcells_code[] = {" >> $@.new - $(Q) od -v -td1 -An $< | $(SED) -e 's/[0-9][0-9]*/&,/g' >> $@.new - $(Q) echo "0};" >> $@.new - $(Q) mv $@.new $@ - -passes/techmap/techmap.o: passes/techmap/techmap.inc - ifeq ($(DISABLE_SPAWN),0) TARGETS += $(PROGRAM_PREFIX)yosys-filterlib$(EXE) EXTRA_OBJS += passes/techmap/filterlib.o diff --git a/passes/techmap/abc9_ops.cc b/passes/techmap/abc9_ops.cc index 8d55b18a0..873c37b9a 100644 --- a/passes/techmap/abc9_ops.cc +++ b/passes/techmap/abc9_ops.cc @@ -102,8 +102,6 @@ void check(RTLIL::Design *design, bool dff_mode) auto inst_module = design->module(cell->type); if (!inst_module) continue; - if (!inst_module->get_blackbox_attribute()) - continue; IdString derived_type; Module *derived_module; if (cell->parameters.empty()) { @@ -111,6 +109,10 @@ void check(RTLIL::Design *design, bool dff_mode) derived_module = inst_module; } else { + // Check potential (since its value may depend on a parameter, + // but not its existence) + if (!inst_module->has_attribute(ID::abc9_flop)) + continue; derived_type = inst_module->derive(design, cell->parameters); derived_module = design->module(derived_type); log_assert(derived_module); @@ -127,20 +129,20 @@ void check(RTLIL::Design *design, bool dff_mode) for (auto derived_cell : derived_module->cells()) { if (derived_cell->type.in(ID($dff), ID($_DFF_N_), ID($_DFF_P_))) { if (found) - log_error("Module '%s' with (* abc9_flop *) contains more than one $_DFF_[NP]_ cell.\n", log_id(derived_module)); + log_error("Whitebox '%s' with (* abc9_flop *) contains more than one $_DFF_[NP]_ cell.\n", log_id(derived_module)); found = true; SigBit Q = derived_cell->getPort(ID::Q); log_assert(GetSize(Q.wire) == 1); if (!Q.wire->port_output) - log_error("Module '%s' contains a %s cell where its 'Q' port does not drive a module output!\n", log_id(derived_module), log_id(derived_cell->type)); + log_error("Whitebox '%s' with (* abc9_flop *) contains a %s cell where its 'Q' port does not drive a module output.\n", log_id(derived_module), log_id(derived_cell->type)); Const init = Q.wire->attributes.at(ID::init, State::Sx); log_assert(GetSize(init) == 1); } else if (unsupported.count(derived_cell->type)) - log_error("Module '%s' with (* abc9_flop *) contains a %s cell, which is not supported for sequential synthesis.\n", log_id(derived_module), log_id(derived_cell->type)); + log_error("Whitebox '%s' with (* abc9_flop *) contains a %s cell, which is not supported for sequential synthesis.\n", log_id(derived_module), log_id(derived_cell->type)); } } } @@ -173,8 +175,6 @@ void prep_hier(RTLIL::Design *design, bool dff_mode) auto inst_module = design->module(cell->type); if (!inst_module) continue; - if (!inst_module->get_blackbox_attribute()) - continue; IdString derived_type; Module *derived_module; if (cell->parameters.empty()) { @@ -182,6 +182,10 @@ void prep_hier(RTLIL::Design *design, bool dff_mode) derived_module = inst_module; } else { + // Check potential for any one of those three + // (since its value may depend on a parameter, but not its existence) + if (!inst_module->has_attribute(ID::abc9_flop) && !inst_module->has_attribute(ID::abc9_box) && !inst_module->get_bool_attribute(ID::abc9_bypass)) + continue; derived_type = inst_module->derive(design, cell->parameters); derived_module = design->module(derived_type); } @@ -211,7 +215,7 @@ void prep_hier(RTLIL::Design *design, bool dff_mode) // Block sequential synthesis on cells with (* init *) != 1'b0 // because ABC9 doesn't support them if (init != State::S0) { - log_warning("Module '%s' contains a %s cell with non-zero initial state -- this is not unsupported for ABC9 sequential synthesis. Treating as a blackbox.\n", log_id(derived_module), log_id(derived_cell->type)); + log_warning("Whitebox '%s' with (* abc9_flop *) contains a %s cell with non-zero initial state -- this is not supported for ABC9 sequential synthesis. Treating as a blackbox.\n", log_id(derived_module), log_id(derived_cell->type)); derived_module->set_bool_attribute(ID::abc9_flop, false); } break; @@ -232,10 +236,8 @@ void prep_hier(RTLIL::Design *design, bool dff_mode) auto w = unmap_module->addWire(port, derived_module->wire(port)); // Do not propagate (* init *) values into the box, // in fact, remove it from outside too - if (w->port_output && w->attributes.erase(ID::init)) { - auto r = unmap_module->addWire(stringf("\\_TECHMAP_REMOVEINIT_%s_", log_id(port))); - unmap_module->connect(r, State::S1); - } + if (w->port_output) + w->attributes.erase(ID::init); } unmap_module->ports = derived_module->ports; unmap_module->check(); @@ -719,8 +721,10 @@ void prep_xaiger(RTLIL::Module *module, bool dff) bit_users[bit].insert(cell->name); if (cell->output(conn.first) && !abc9_flop) - for (auto bit : sigmap(conn.second)) - bit_drivers[bit].insert(cell->name); + for (const auto &chunk : conn.second.chunks()) + if (!chunk.wire->get_bool_attribute(ID::abc9_keep)) + for (auto b : sigmap(SigSpec(chunk))) + bit_drivers[b].insert(cell->name); } toposort.node(cell->name); } @@ -1110,7 +1114,7 @@ void reintegrate(RTLIL::Module *module, bool dff_mode) for (auto w : mapped_mod->wires()) { auto nw = module->addWire(remap_name(w->name), GetSize(w)); nw->start_offset = w->start_offset; - // Remove all (* init *) since they only existon $_DFF_[NP]_ + // Remove all (* init *) since they only exist on $_DFF_[NP]_ w->attributes.erase(ID::init); } @@ -1147,16 +1151,36 @@ void reintegrate(RTLIL::Module *module, bool dff_mode) } } + SigMap initmap; + if (dff_mode) { + // Build a sigmap prioritising bits with (* init *) + initmap.set(module); + for (auto w : module->wires()) { + auto it = w->attributes.find(ID::init); + if (it == w->attributes.end()) + continue; + for (auto i = 0; i < GetSize(w); i++) + if (it->second[i] == State::S0 || it->second[i] == State::S1) + initmap.add(w); + } + } + std::vector<Cell*> boxes; for (auto cell : module->cells().to_vector()) { if (cell->has_keep_attr()) continue; - // Short out $_DFF_[NP]_ cells since the flop box already has - // all the information we need to reconstruct cell + // Short out (so that existing name can be preserved) and remove + // $_DFF_[NP]_ cells since flop box already has all the information + // we need to reconstruct them if (dff_mode && cell->type.in(ID($_DFF_N_), ID($_DFF_P_)) && !cell->get_bool_attribute(ID::abc9_keep)) { - module->connect(cell->getPort(ID::Q), cell->getPort(ID::D)); + SigBit Q = cell->getPort(ID::Q); + module->connect(Q, cell->getPort(ID::D)); module->remove(cell); + auto Qi = initmap(Q); + auto it = Qi.wire->attributes.find(ID::init); + if (it != Qi.wire->attributes.end()) + it->second[Qi.offset] = State::Sx; } else if (cell->type.in(ID($_AND_), ID($_NOT_))) module->remove(cell); @@ -1299,7 +1323,25 @@ void reintegrate(RTLIL::Module *module, bool dff_mode) mapped_cell->connections_.erase(jt); auto abc9_flop = box_module->get_bool_attribute(ID::abc9_flop); - if (!abc9_flop) { + if (abc9_flop) { + // Link this sole flop box output to the output of the existing + // flop box, so that any (public) signal it drives will be + // preserved + SigBit old_q; + for (const auto &port_name : box_ports.at(existing_cell->type)) { + RTLIL::Wire *w = box_module->wire(port_name); + log_assert(w); + if (!w->port_output) + continue; + log_assert(old_q == SigBit()); + log_assert(GetSize(w) == 1); + old_q = existing_cell->getPort(port_name); + } + auto new_q = outputs[0]; + new_q.wire = module->wires_.at(remap_name(new_q.wire->name)); + module->connect(old_q, new_q); + } + else { for (const auto &i : inputs) bit_users[i].insert(mapped_cell->name); for (const auto &i : outputs) @@ -1332,11 +1374,12 @@ void reintegrate(RTLIL::Module *module, bool dff_mode) c.wire = module->wires_.at(remap_name(c.wire->name)); newsig.append(c); } - cell->setPort(port_name, newsig); if (w->port_input && !abc9_flop) for (const auto &i : newsig) bit2sinks[i].push_back(cell); + + cell->setPort(port_name, std::move(newsig)); } } @@ -1398,7 +1441,7 @@ void reintegrate(RTLIL::Module *module, bool dff_mode) // treated as being "free"), in particular driving primary // outputs (real primary outputs, or cells treated as blackboxes) // or driving box inputs. - // Instead of just mapping those $_NOT_ gates into 2-input $lut-s + // Instead of just mapping those $_NOT_ gates into 1-input $lut-s // at an area and delay cost, see if it is possible to push // this $_NOT_ into the driving LUT, or into all sink LUTs. // When this is not possible, (i.e. this signal drives two primary diff --git a/passes/techmap/flatten.cc b/passes/techmap/flatten.cc new file mode 100644 index 000000000..027bb137d --- /dev/null +++ b/passes/techmap/flatten.cc @@ -0,0 +1,357 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2012 Clifford Wolf <clifford@clifford.at> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "kernel/yosys.h" +#include "kernel/utils.h" +#include "kernel/sigtools.h" + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +void apply_prefix(IdString prefix, IdString &id) +{ + if (id[0] == '\\') + id = stringf("%s.%s", prefix.c_str(), id.c_str()+1); + else + id = stringf("$flatten%s.%s", prefix.c_str(), id.c_str()); +} + +void apply_prefix(IdString prefix, RTLIL::SigSpec &sig, RTLIL::Module *module) +{ + vector<SigChunk> chunks = sig; + for (auto &chunk : chunks) + if (chunk.wire != nullptr) { + IdString wire_name = chunk.wire->name; + apply_prefix(prefix, wire_name); + log_assert(module->wire(wire_name) != nullptr); + chunk.wire = module->wire(wire_name); + } + sig = chunks; +} + +struct FlattenWorker +{ + bool ignore_wb = false; + + void flatten_cell(RTLIL::Design *design, RTLIL::Module *module, RTLIL::Cell *cell, RTLIL::Module *tpl, std::vector<RTLIL::Cell*> &new_cells) + { + if (tpl->processes.size() != 0) { + log("Flattening yielded processes:"); + for (auto &it : tpl->processes) + log(" %s",log_id(it.first)); + log("\n"); + log_error("Flattening yielded processes -> this is not supported.\n"); + } + + pool<string> extra_src_attrs = cell->get_strpool_attribute(ID::src); + + dict<IdString, IdString> memory_renames; + + for (auto &it : tpl->memories) { + IdString m_name = it.first; + apply_prefix(cell->name, m_name); + RTLIL::Memory *m = module->addMemory(m_name, it.second); + if (m->attributes.count(ID::src)) + m->add_strpool_attribute(ID::src, extra_src_attrs); + memory_renames[it.first] = m->name; + design->select(module, m); + } + + dict<IdString, IdString> positional_ports; + dict<Wire*, IdString> temp_renamed_wires; + + for (auto tpl_w : tpl->wires()) + { + if (tpl_w->port_id > 0) + { + IdString posportname = stringf("$%d", tpl_w->port_id); + positional_ports.emplace(posportname, tpl_w->name); + } + IdString w_name = tpl_w->name; + apply_prefix(cell->name, w_name); + RTLIL::Wire *w = module->wire(w_name); + if (w != nullptr) { + if (!w->get_bool_attribute(ID::hierconn)) { + temp_renamed_wires[w] = w->name; + module->rename(w, NEW_ID); + w = nullptr; + } else { + w->attributes.erase(ID::hierconn); + if (GetSize(w) < GetSize(tpl_w)) { + log_warning("Widening signal %s.%s to match size of %s.%s (via %s.%s).\n", log_id(module), log_id(w), + log_id(tpl), log_id(tpl_w), log_id(module), log_id(cell)); + w->width = GetSize(tpl_w); + } + } + } + if (w == nullptr) { + w = module->addWire(w_name, tpl_w); + w->port_input = false; + w->port_output = false; + w->port_id = 0; + if (w->attributes.count(ID::src)) + w->add_strpool_attribute(ID::src, extra_src_attrs); + } + design->select(module, w); + } + + SigMap sigmap(module); + + SigMap tpl_sigmap(tpl); + pool<SigBit> tpl_written_bits; + + for (auto tpl_cell : tpl->cells()) + for (auto &conn : tpl_cell->connections()) + if (tpl_cell->output(conn.first)) + for (auto bit : tpl_sigmap(conn.second)) + tpl_written_bits.insert(bit); + for (auto &conn : tpl->connections()) + for (auto bit : tpl_sigmap(conn.first)) + tpl_written_bits.insert(bit); + + SigMap port_signal_map; + + for (auto &it : cell->connections()) + { + IdString portname = it.first; + if (positional_ports.count(portname) > 0) + portname = positional_ports.at(portname); + if (tpl->wire(portname) == nullptr || tpl->wire(portname)->port_id == 0) { + if (portname.begins_with("$")) + log_error("Can't map port `%s' of cell `%s' to template `%s'!\n", portname.c_str(), cell->name.c_str(), tpl->name.c_str()); + continue; + } + + if (GetSize(it.second) == 0) + continue; + + RTLIL::Wire *w = tpl->wire(portname); + RTLIL::SigSig c; + + if (w->port_output && !w->port_input) { + c.first = it.second; + c.second = RTLIL::SigSpec(w); + apply_prefix(cell->name, c.second, module); + } else if (!w->port_output && w->port_input) { + c.first = RTLIL::SigSpec(w); + c.second = it.second; + apply_prefix(cell->name, c.first, module); + } else { + SigSpec sig_tpl = w, sig_tpl_pf = w, sig_mod = it.second; + apply_prefix(cell->name, sig_tpl_pf, module); + for (int i = 0; i < GetSize(sig_tpl) && i < GetSize(sig_mod); i++) { + if (tpl_written_bits.count(tpl_sigmap(sig_tpl[i]))) { + c.first.append(sig_mod[i]); + c.second.append(sig_tpl_pf[i]); + } else { + c.first.append(sig_tpl_pf[i]); + c.second.append(sig_mod[i]); + } + } + } + + if (c.second.size() > c.first.size()) + c.second.remove(c.first.size(), c.second.size() - c.first.size()); + + if (c.second.size() < c.first.size()) + c.second.append(RTLIL::SigSpec(RTLIL::State::S0, c.first.size() - c.second.size())); + + log_assert(c.first.size() == c.second.size()); + + // connect internal and external wires + + if (sigmap(c.first).has_const()) + log_error("Mismatch in directionality for cell port %s.%s.%s: %s <= %s\n", + log_id(module), log_id(cell), log_id(it.first), log_signal(c.first), log_signal(c.second)); + + module->connect(c); + } + + for (auto tpl_cell : tpl->cells()) + { + IdString c_name = tpl_cell->name; + apply_prefix(cell->name, c_name); + + RTLIL::Cell *c = module->addCell(c_name, tpl_cell); + new_cells.push_back(c); + design->select(module, c); + + for (auto &conn : c->connections()) + { + RTLIL::SigSpec new_conn = conn.second; + apply_prefix(cell->name, new_conn, module); + port_signal_map.apply(new_conn); + c->setPort(conn.first, std::move(new_conn)); + } + + if (c->type.in(ID($memrd), ID($memwr), ID($meminit))) { + IdString memid = c->getParam(ID::MEMID).decode_string(); + log_assert(memory_renames.count(memid) != 0); + c->setParam(ID::MEMID, Const(memory_renames[memid].str())); + } + + if (c->type == ID($mem)) { + IdString memid = c->getParam(ID::MEMID).decode_string(); + apply_prefix(cell->name, memid); + c->setParam(ID::MEMID, Const(memid.c_str())); + } + + if (c->attributes.count(ID::src)) + c->add_strpool_attribute(ID::src, extra_src_attrs); + } + + for (auto &it : tpl->connections()) { + RTLIL::SigSig c = it; + apply_prefix(cell->name.str(), c.first, module); + apply_prefix(cell->name.str(), c.second, module); + port_signal_map.apply(c.first); + port_signal_map.apply(c.second); + module->connect(c); + } + + module->remove(cell); + + for (auto &it : temp_renamed_wires) + { + Wire *w = it.first; + IdString name = it.second; + IdString altname = module->uniquify(name); + Wire *other_w = module->wire(name); + module->rename(other_w, altname); + module->rename(w, name); + } + } + + void flatten_module(RTLIL::Design *design, RTLIL::Module *module, pool<RTLIL::Module*> &used_modules) + { + if (!design->selected(module) || module->get_blackbox_attribute(ignore_wb)) + return; + + std::vector<RTLIL::Cell*> worklist = module->selected_cells(); + while (!worklist.empty()) + { + RTLIL::Cell *cell = worklist.back(); + worklist.pop_back(); + + if (!design->has(cell->type)) + continue; + + RTLIL::Module *tpl = design->module(cell->type); + if (tpl->get_blackbox_attribute(ignore_wb)) + continue; + + if (cell->get_bool_attribute(ID::keep_hierarchy) || tpl->get_bool_attribute(ID::keep_hierarchy)) { + log("Keeping %s.%s (found keep_hierarchy attribute).\n", log_id(module), log_id(cell)); + used_modules.insert(tpl); + continue; + } + + log_debug("Flattening %s.%s (%s).\n", log_id(module), log_id(cell), log_id(cell->type)); + // If a design is fully selected and has a top module defined, topological sorting ensures that all cells + // added during flattening are black boxes, and flattening is finished in one pass. However, when flattening + // individual modules, this isn't the case, and the newly added cells might have to be flattened further. + flatten_cell(design, module, cell, tpl, worklist); + } + } +}; + +struct FlattenPass : public Pass { + FlattenPass() : Pass("flatten", "flatten design") { } + void help() YS_OVERRIDE + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" flatten [options] [selection]\n"); + log("\n"); + log("This pass flattens the design by replacing cells by their implementation. This\n"); + log("pass is very similar to the 'techmap' pass. The only difference is that this\n"); + log("pass is using the current design as mapping library.\n"); + log("\n"); + log("Cells and/or modules with the 'keep_hierarchy' attribute set will not be\n"); + log("flattened by this command.\n"); + log("\n"); + log(" -wb\n"); + log(" Ignore the 'whitebox' attribute on cell implementations.\n"); + log("\n"); + } + void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE + { + log_header(design, "Executing FLATTEN pass (flatten design).\n"); + log_push(); + + FlattenWorker worker; + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) { + if (args[argidx] == "-wb") { + worker.ignore_wb = true; + continue; + } + break; + } + extra_args(args, argidx, design); + + RTLIL::Module *top = nullptr; + if (design->full_selection()) + for (auto module : design->modules()) + if (module->get_bool_attribute(ID::top)) + top = module; + + pool<RTLIL::Module*> used_modules; + if (top == nullptr) + used_modules = design->modules(); + else + used_modules.insert(top); + + TopoSort<RTLIL::Module*, IdString::compare_ptr_by_name<RTLIL::Module>> topo_modules; + pool<RTLIL::Module*> worklist = used_modules; + while (!worklist.empty()) { + RTLIL::Module *module = worklist.pop(); + for (auto cell : module->selected_cells()) { + RTLIL::Module *tpl = design->module(cell->type); + if (tpl != nullptr) { + if (topo_modules.database.count(tpl) == 0) + worklist.insert(tpl); + topo_modules.edge(tpl, module); + } + } + } + + if (!topo_modules.sort()) + log_error("Cannot flatten a design containing recursive instantiations.\n"); + + for (auto module : topo_modules.sorted) + worker.flatten_module(design, module, used_modules); + + if (top != nullptr) + for (auto module : design->modules().to_vector()) + if (!used_modules[module] && !module->get_blackbox_attribute(worker.ignore_wb)) { + log("Deleting now unused module %s.\n", log_id(module)); + design->remove(module); + } + + log_pop(); + } +} FlattenPass; + +PRIVATE_NAMESPACE_END diff --git a/passes/techmap/simplemap.cc b/passes/techmap/simplemap.cc index b65b3e972..214157a64 100644 --- a/passes/techmap/simplemap.cc +++ b/passes/techmap/simplemap.cc @@ -522,7 +522,7 @@ void simplemap_dlatch(RTLIL::Module *module, RTLIL::Cell *cell) } } -void simplemap_get_mappers(std::map<RTLIL::IdString, void(*)(RTLIL::Module*, RTLIL::Cell*)> &mappers) +void simplemap_get_mappers(dict<IdString, void(*)(RTLIL::Module*, RTLIL::Cell*)> &mappers) { mappers[ID($not)] = simplemap_not; mappers[ID($pos)] = simplemap_pos; @@ -559,7 +559,7 @@ void simplemap_get_mappers(std::map<RTLIL::IdString, void(*)(RTLIL::Module*, RTL void simplemap(RTLIL::Module *module, RTLIL::Cell *cell) { - static std::map<RTLIL::IdString, void(*)(RTLIL::Module*, RTLIL::Cell*)> mappers; + static dict<IdString, void(*)(RTLIL::Module*, RTLIL::Cell*)> mappers; static bool initialized_mappers = false; if (!initialized_mappers) { @@ -595,7 +595,7 @@ struct SimplemapPass : public Pass { log_header(design, "Executing SIMPLEMAP pass (map simple cells to gate primitives).\n"); extra_args(args, 1, design); - std::map<RTLIL::IdString, void(*)(RTLIL::Module*, RTLIL::Cell*)> mappers; + dict<IdString, void(*)(RTLIL::Module*, RTLIL::Cell*)> mappers; simplemap_get_mappers(mappers); for (auto mod : design->modules()) { diff --git a/passes/techmap/simplemap.h b/passes/techmap/simplemap.h index c2d73ea79..5091050a1 100644 --- a/passes/techmap/simplemap.h +++ b/passes/techmap/simplemap.h @@ -42,7 +42,7 @@ extern void simplemap_adff(RTLIL::Module *module, RTLIL::Cell *cell); extern void simplemap_dlatch(RTLIL::Module *module, RTLIL::Cell *cell); extern void simplemap(RTLIL::Module *module, RTLIL::Cell *cell); -extern void simplemap_get_mappers(std::map<RTLIL::IdString, void(*)(RTLIL::Module*, RTLIL::Cell*)> &mappers); +extern void simplemap_get_mappers(dict<RTLIL::IdString, void(*)(RTLIL::Module*, RTLIL::Cell*)> &mappers); YOSYS_NAMESPACE_END diff --git a/passes/techmap/techmap.cc b/passes/techmap/techmap.cc index a554be257..535db9465 100644 --- a/passes/techmap/techmap.cc +++ b/passes/techmap/techmap.cc @@ -27,7 +27,6 @@ #include <string.h> #include "simplemap.h" -#include "passes/techmap/techmap.inc" YOSYS_NAMESPACE_BEGIN @@ -51,27 +50,23 @@ void apply_prefix(IdString prefix, RTLIL::SigSpec &sig, RTLIL::Module *module) { vector<SigChunk> chunks = sig; for (auto &chunk : chunks) - if (chunk.wire != NULL) { + if (chunk.wire != nullptr) { IdString wire_name = chunk.wire->name; apply_prefix(prefix, wire_name); - log_assert(module->wires_.count(wire_name) > 0); - chunk.wire = module->wires_[wire_name]; + log_assert(module->wire(wire_name) != nullptr); + chunk.wire = module->wire(wire_name); } sig = chunks; } struct TechmapWorker { - std::map<RTLIL::IdString, void(*)(RTLIL::Module*, RTLIL::Cell*)> simplemap_mappers; - std::map<std::pair<RTLIL::IdString, std::map<RTLIL::IdString, RTLIL::Const>>, RTLIL::Module*> techmap_cache; - std::map<RTLIL::Module*, bool> techmap_do_cache; - std::set<RTLIL::Module*, RTLIL::IdString::compare_ptr_by_name<RTLIL::Module>> module_queue; + dict<IdString, void(*)(RTLIL::Module*, RTLIL::Cell*)> simplemap_mappers; + dict<std::pair<IdString, dict<IdString, RTLIL::Const>>, RTLIL::Module*> techmap_cache; + dict<RTLIL::Module*, bool> techmap_do_cache; + pool<RTLIL::Module*> module_queue; dict<Module*, SigMap> sigmaps; - pool<IdString> flatten_do_list; - pool<IdString> flatten_done_list; - pool<Cell*> flatten_keep_list; - pool<string> log_msg_cache; struct TechmapWireData { @@ -79,31 +74,20 @@ struct TechmapWorker RTLIL::SigSpec value; }; - typedef std::map<std::string, std::vector<TechmapWireData>> TechmapWires; + typedef dict<IdString, std::vector<TechmapWireData>> TechmapWires; - bool extern_mode; - bool assert_mode; - bool flatten_mode; - bool recursive_mode; - bool autoproc_mode; - bool ignore_wb; - - TechmapWorker() - { - extern_mode = false; - assert_mode = false; - flatten_mode = false; - recursive_mode = false; - autoproc_mode = false; - ignore_wb = false; - } + bool extern_mode = false; + bool assert_mode = false; + bool recursive_mode = false; + bool autoproc_mode = false; + bool ignore_wb = false; std::string constmap_tpl_name(SigMap &sigmap, RTLIL::Module *tpl, RTLIL::Cell *cell, bool verbose) { std::string constmap_info; - std::map<RTLIL::SigBit, std::pair<RTLIL::IdString, int>> connbits_map; + dict<RTLIL::SigBit, std::pair<IdString, int>> connbits_map; - for (auto conn : cell->connections()) + for (auto &conn : cell->connections()) for (int i = 0; i < GetSize(conn.second); i++) { RTLIL::SigBit bit = sigmap(conn.second[i]); if (bit.wire == nullptr) { @@ -117,7 +101,7 @@ struct TechmapWorker constmap_info += stringf("|%s %d %s %d", log_id(conn.first), i, log_id(connbits_map.at(bit).first), connbits_map.at(bit).second); } else { - connbits_map[bit] = std::pair<RTLIL::IdString, int>(conn.first, i); + connbits_map.emplace(bit, std::make_pair(conn.first, i)); constmap_info += stringf("|%s %d", log_id(conn.first), i); } } @@ -129,24 +113,25 @@ struct TechmapWorker { TechmapWires result; - if (module == NULL) + if (module == nullptr) return result; - for (auto &it : module->wires_) { - const char *p = it.first.c_str(); + for (auto w : module->wires()) { + const char *p = w->name.c_str(); if (*p == '$') continue; const char *q = strrchr(p+1, '.'); - p = q ? q+1 : p+1; + if (q) + p = q; - if (!strncmp(p, "_TECHMAP_", 9)) { + if (!strncmp(p, "\\_TECHMAP_", 10)) { TechmapWireData record; - record.wire = it.second; - record.value = it.second; + record.wire = w; + record.value = w; result[p].push_back(record); - it.second->attributes[ID::keep] = RTLIL::Const(1); - it.second->attributes[ID::_techmap_special_] = RTLIL::Const(1); + w->set_bool_attribute(ID::keep); + w->set_bool_attribute(ID::_techmap_special_); } } @@ -165,7 +150,7 @@ struct TechmapWorker if (tpl->processes.size() != 0) { log("Technology map yielded processes:"); for (auto &it : tpl->processes) - log(" %s",RTLIL::id2cstr(it.first)); + log(" %s",log_id(it.first)); log("\n"); if (autoproc_mode) { Pass::call_on_module(tpl->design, tpl, "proc"); @@ -178,89 +163,71 @@ struct TechmapWorker pool<string> extra_src_attrs = cell->get_strpool_attribute(ID::src); orig_cell_name = cell->name.str(); - if (!flatten_mode) { - for (auto &it : tpl->cells_) - if (it.first == ID::_TECHMAP_REPLACE_) { - module->rename(cell, stringf("$techmap%d", autoidx++) + cell->name.str()); - break; - } - } + for (auto tpl_cell : tpl->cells()) + if (tpl_cell->name == ID::_TECHMAP_REPLACE_) { + module->rename(cell, stringf("$techmap%d", autoidx++) + cell->name.str()); + break; + } dict<IdString, IdString> memory_renames; for (auto &it : tpl->memories) { IdString m_name = it.first; apply_prefix(cell->name, m_name); - RTLIL::Memory *m = new RTLIL::Memory; - m->name = m_name; - m->width = it.second->width; - m->start_offset = it.second->start_offset; - m->size = it.second->size; - m->attributes = it.second->attributes; + RTLIL::Memory *m = module->addMemory(m_name, it.second); if (m->attributes.count(ID::src)) m->add_strpool_attribute(ID::src, extra_src_attrs); - module->memories[m->name] = m; memory_renames[it.first] = m->name; design->select(module, m); } - std::map<RTLIL::IdString, RTLIL::IdString> positional_ports; + dict<IdString, IdString> positional_ports; dict<Wire*, IdString> temp_renamed_wires; pool<SigBit> autopurge_tpl_bits; - for (auto &it : tpl->wires_) + for (auto tpl_w : tpl->wires()) { - if (it.second->port_id > 0) + if (tpl_w->port_id > 0) { - IdString posportname = stringf("$%d", it.second->port_id); - positional_ports[posportname] = it.first; + IdString posportname = stringf("$%d", tpl_w->port_id); + positional_ports.emplace(posportname, tpl_w->name); - if (!flatten_mode && it.second->get_bool_attribute(ID::techmap_autopurge) && - (!cell->hasPort(it.second->name) || !GetSize(cell->getPort(it.second->name))) && + if (tpl_w->get_bool_attribute(ID::techmap_autopurge) && + (!cell->hasPort(tpl_w->name) || !GetSize(cell->getPort(tpl_w->name))) && (!cell->hasPort(posportname) || !GetSize(cell->getPort(posportname)))) { if (sigmaps.count(tpl) == 0) sigmaps[tpl].set(tpl); - for (auto bit : sigmaps.at(tpl)(it.second)) + for (auto bit : sigmaps.at(tpl)(tpl_w)) if (bit.wire != nullptr) autopurge_tpl_bits.insert(bit); } } - IdString w_name = it.second->name; + IdString w_name = tpl_w->name; apply_prefix(cell->name, w_name); RTLIL::Wire *w = module->wire(w_name); if (w != nullptr) { - if (!flatten_mode || !w->get_bool_attribute(ID::hierconn)) { - temp_renamed_wires[w] = w->name; - module->rename(w, NEW_ID); - w = nullptr; - } else { - w->attributes.erase(ID::hierconn); - if (GetSize(w) < GetSize(it.second)) { - log_warning("Widening signal %s.%s to match size of %s.%s (via %s.%s).\n", log_id(module), log_id(w), - log_id(tpl), log_id(it.second), log_id(module), log_id(cell)); - w->width = GetSize(it.second); - } - } + temp_renamed_wires[w] = w->name; + module->rename(w, NEW_ID); + w = nullptr; } if (w == nullptr) { - w = module->addWire(w_name, it.second); + w = module->addWire(w_name, tpl_w); w->port_input = false; w->port_output = false; w->port_id = 0; - if (!flatten_mode) - w->attributes.erase(ID::techmap_autopurge); - if (it.second->get_bool_attribute(ID::_techmap_special_)) + w->attributes.erase(ID::techmap_autopurge); + if (tpl_w->get_bool_attribute(ID::_techmap_special_)) w->attributes.clear(); if (w->attributes.count(ID::src)) w->add_strpool_attribute(ID::src, extra_src_attrs); } design->select(module, w); - if (it.second->name.begins_with("\\_TECHMAP_REPLACE_.")) { - IdString replace_name = stringf("%s%s", orig_cell_name.c_str(), it.second->name.c_str() + strlen("\\_TECHMAP_REPLACE_")); - Wire *replace_w = module->addWire(replace_name, it.second); + if (tpl_w->name.begins_with("\\_TECHMAP_REPLACE_.")) { + IdString replace_name = stringf("%s%s", orig_cell_name.c_str(), tpl_w->name.c_str() + strlen("\\_TECHMAP_REPLACE_")); + Wire *replace_w = module->addWire(replace_name, tpl_w); module->connect(replace_w, w); } } @@ -268,24 +235,23 @@ struct TechmapWorker SigMap tpl_sigmap(tpl); pool<SigBit> tpl_written_bits; - for (auto &it1 : tpl->cells_) - for (auto &it2 : it1.second->connections_) - if (it1.second->output(it2.first)) - for (auto bit : tpl_sigmap(it2.second)) + for (auto tpl_cell : tpl->cells()) + for (auto &conn : tpl_cell->connections()) + if (tpl_cell->output(conn.first)) + for (auto bit : tpl_sigmap(conn.second)) tpl_written_bits.insert(bit); - for (auto &it1 : tpl->connections_) - for (auto bit : tpl_sigmap(it1.first)) + for (auto &conn : tpl->connections()) + for (auto bit : tpl_sigmap(conn.first)) tpl_written_bits.insert(bit); SigMap port_signal_map; - SigSig port_signal_assign; for (auto &it : cell->connections()) { - RTLIL::IdString portname = it.first; + IdString portname = it.first; if (positional_ports.count(portname) > 0) portname = positional_ports.at(portname); - if (tpl->wires_.count(portname) == 0 || tpl->wires_.at(portname)->port_id == 0) { + if (tpl->wire(portname) == nullptr || tpl->wire(portname)->port_id == 0) { if (portname.begins_with("$")) log_error("Can't map port `%s' of cell `%s' to template `%s'!\n", portname.c_str(), cell->name.c_str(), tpl->name.c_str()); continue; @@ -294,7 +260,7 @@ struct TechmapWorker if (GetSize(it.second) == 0) continue; - RTLIL::Wire *w = tpl->wires_.at(portname); + RTLIL::Wire *w = tpl->wire(portname); RTLIL::SigSig c, extra_connect; if (w->port_output && !w->port_input) { @@ -333,76 +299,57 @@ struct TechmapWorker log_assert(c.first.size() == c.second.size()); - if (flatten_mode) - { - // more conservative approach: - // connect internal and external wires - - if (sigmaps.count(module) == 0) - sigmaps[module].set(module); - - if (sigmaps.at(module)(c.first).has_const()) - log_error("Mismatch in directionality for cell port %s.%s.%s: %s <= %s\n", - log_id(module), log_id(cell), log_id(it.first), log_signal(c.first), log_signal(c.second)); + // replace internal wires that are connected to external wires + if (w->port_output && !w->port_input) { + port_signal_map.add(c.second, c.first); + } else + if (!w->port_output && w->port_input) { + port_signal_map.add(c.first, c.second); + } else { module->connect(c); + extra_connect = SigSig(); } - else - { - // approach that yields nicer outputs: - // replace internal wires that are connected to external wires - - if (w->port_output && !w->port_input) { - port_signal_map.add(c.second, c.first); - } else - if (!w->port_output && w->port_input) { - port_signal_map.add(c.first, c.second); - } else { - module->connect(c); - extra_connect = SigSig(); - } - for (auto &attr : w->attributes) { - if (attr.first == ID::src) - continue; - auto lhs = GetSize(extra_connect.first); - auto rhs = GetSize(extra_connect.second); - if (lhs > rhs) - extra_connect.first.remove(rhs, lhs-rhs); - else if (rhs > lhs) - extra_connect.second.remove(lhs, rhs-lhs); - module->connect(extra_connect); - break; - } + for (auto &attr : w->attributes) { + if (attr.first == ID::src) + continue; + auto lhs = GetSize(extra_connect.first); + auto rhs = GetSize(extra_connect.second); + if (lhs > rhs) + extra_connect.first.remove(rhs, lhs-rhs); + else if (rhs > lhs) + extra_connect.second.remove(lhs, rhs-lhs); + module->connect(extra_connect); + break; } } - for (auto &it : tpl->cells_) + for (auto tpl_cell : tpl->cells()) { - IdString c_name = it.second->name.str(); - bool techmap_replace_cell = (!flatten_mode) && (c_name == ID::_TECHMAP_REPLACE_); + IdString c_name = tpl_cell->name; - if (techmap_replace_cell) + if (c_name == ID::_TECHMAP_REPLACE_) c_name = orig_cell_name; - else if (it.second->name.begins_with("\\_TECHMAP_REPLACE_.")) + else if (tpl_cell->name.begins_with("\\_TECHMAP_REPLACE_.")) c_name = stringf("%s%s", orig_cell_name.c_str(), c_name.c_str() + strlen("\\_TECHMAP_REPLACE_")); else apply_prefix(cell->name, c_name); - RTLIL::Cell *c = module->addCell(c_name, it.second); + RTLIL::Cell *c = module->addCell(c_name, tpl_cell); design->select(module, c); - if (!flatten_mode && c->type.begins_with("\\$")) + if (c->type.begins_with("\\$")) c->type = c->type.substr(1); vector<IdString> autopurge_ports; - for (auto &it2 : c->connections_) + for (auto &conn : c->connections()) { bool autopurge = false; if (!autopurge_tpl_bits.empty()) { - autopurge = GetSize(it2.second) != 0; - for (auto &bit : sigmaps.at(tpl)(it2.second)) + autopurge = GetSize(conn.second) != 0; + for (auto &bit : sigmaps.at(tpl)(conn.second)) if (!autopurge_tpl_bits.count(bit)) { autopurge = false; break; @@ -410,10 +357,12 @@ struct TechmapWorker } if (autopurge) { - autopurge_ports.push_back(it2.first); + autopurge_ports.push_back(conn.first); } else { - apply_prefix(cell->name, it2.second, module); - port_signal_map.apply(it2.second); + RTLIL::SigSpec new_conn = conn.second; + apply_prefix(cell->name, new_conn, module); + port_signal_map.apply(new_conn); + c->setPort(conn.first, std::move(new_conn)); } } @@ -435,7 +384,7 @@ struct TechmapWorker if (c->attributes.count(ID::src)) c->add_strpool_attribute(ID::src, extra_src_attrs); - if (techmap_replace_cell) + if (c_name == ID::_TECHMAP_REPLACE_) for (auto attr : cell->attributes) if (!c->attributes.count(attr.first)) c->attributes[attr.first] = attr.second; @@ -463,8 +412,8 @@ struct TechmapWorker } } - bool techmap_module(RTLIL::Design *design, RTLIL::Module *module, RTLIL::Design *map, std::set<RTLIL::Cell*> &handled_cells, - const std::map<RTLIL::IdString, std::set<RTLIL::IdString, RTLIL::sort_by_id_str>> &celltypeMap, bool in_recursion) + bool techmap_module(RTLIL::Design *design, RTLIL::Module *module, RTLIL::Design *map, pool<RTLIL::Cell*> &handled_cells, + const dict<IdString, pool<IdString>> &celltypeMap, bool in_recursion) { std::string mapmsg_prefix = in_recursion ? "Recursively mapping" : "Mapping"; @@ -489,13 +438,13 @@ struct TechmapWorker } } - TopoSort<RTLIL::Cell*, RTLIL::IdString::compare_ptr_by_name<RTLIL::Cell>> cells; - std::map<RTLIL::Cell*, std::set<RTLIL::SigBit>> cell_to_inbit; - std::map<RTLIL::SigBit, std::set<RTLIL::Cell*>> outbit_to_cell; + TopoSort<RTLIL::Cell*, IdString::compare_ptr_by_name<RTLIL::Cell>> cells; + dict<RTLIL::Cell*, pool<RTLIL::SigBit>> cell_to_inbit; + dict<RTLIL::SigBit, pool<RTLIL::Cell*>> outbit_to_cell; - for (auto cell : module->cells()) + for (auto cell : module->selected_cells()) { - if (!design->selected(module, cell) || handled_cells.count(cell) > 0) + if (handled_cells.count(cell) > 0) continue; std::string cell_type = cell->type.str(); @@ -508,22 +457,6 @@ struct TechmapWorker continue; } - if (flatten_mode) { - bool keepit = cell->get_bool_attribute(ID::keep_hierarchy); - for (auto &tpl_name : celltypeMap.at(cell_type)) - if (map->modules_[tpl_name]->get_bool_attribute(ID::keep_hierarchy)) - keepit = true; - if (keepit) { - if (!flatten_keep_list[cell]) { - log("Keeping %s.%s (found keep_hierarchy property).\n", log_id(module), log_id(cell)); - flatten_keep_list.insert(cell); - } - if (!flatten_done_list[cell->type]) - flatten_do_list.insert(cell->type); - continue; - } - } - for (auto &conn : cell->connections()) { RTLIL::SigSpec sig = sigmap(conn.second); @@ -533,7 +466,7 @@ struct TechmapWorker continue; for (auto &tpl_name : celltypeMap.at(cell_type)) { - RTLIL::Module *tpl = map->modules_[tpl_name]; + RTLIL::Module *tpl = map->module(tpl_name); RTLIL::Wire *port = tpl->wire(conn.first); if (port && port->port_input) cell_to_inbit[cell].insert(sig.begin(), sig.end()); @@ -566,189 +499,188 @@ struct TechmapWorker for (auto &tpl_name : celltypeMap.at(cell_type)) { - RTLIL::IdString derived_name = tpl_name; - RTLIL::Module *tpl = map->modules_[tpl_name]; - std::map<RTLIL::IdString, RTLIL::Const> parameters(cell->parameters.begin(), cell->parameters.end()); + IdString derived_name = tpl_name; + RTLIL::Module *tpl = map->module(tpl_name); + dict<IdString, RTLIL::Const> parameters(cell->parameters); if (tpl->get_blackbox_attribute(ignore_wb)) continue; - if (!flatten_mode) - { - std::string extmapper_name; - - if (tpl->get_bool_attribute(ID::techmap_simplemap)) - extmapper_name = "simplemap"; - - if (tpl->get_bool_attribute(ID::techmap_maccmap)) - extmapper_name = "maccmap"; + std::string extmapper_name; - if (tpl->attributes.count(ID::techmap_wrap)) - extmapper_name = "wrap"; + if (tpl->get_bool_attribute(ID::techmap_simplemap)) + extmapper_name = "simplemap"; - if (!extmapper_name.empty()) - { - cell->type = cell_type; - - if ((extern_mode && !in_recursion) || extmapper_name == "wrap") - { - std::string m_name = stringf("$extern:%s:%s", extmapper_name.c_str(), log_id(cell->type)); + if (tpl->get_bool_attribute(ID::techmap_maccmap)) + extmapper_name = "maccmap"; - for (auto &c : cell->parameters) - m_name += stringf(":%s=%s", log_id(c.first), log_signal(c.second)); + if (tpl->attributes.count(ID::techmap_wrap)) + extmapper_name = "wrap"; - if (extmapper_name == "wrap") - m_name += ":" + sha1(tpl->attributes.at(ID::techmap_wrap).decode_string()); - - RTLIL::Design *extmapper_design = extern_mode && !in_recursion ? design : tpl->design; - RTLIL::Module *extmapper_module = extmapper_design->module(m_name); + if (!extmapper_name.empty()) + { + cell->type = cell_type; - if (extmapper_module == nullptr) - { - extmapper_module = extmapper_design->addModule(m_name); - RTLIL::Cell *extmapper_cell = extmapper_module->addCell(cell->type, cell); - - extmapper_cell->set_src_attribute(cell->get_src_attribute()); - - int port_counter = 1; - for (auto &c : extmapper_cell->connections_) { - RTLIL::Wire *w = extmapper_module->addWire(c.first, GetSize(c.second)); - if (w->name.in(ID::Y, ID::Q)) - w->port_output = true; - else - w->port_input = true; - w->port_id = port_counter++; - c.second = w; - } + if ((extern_mode && !in_recursion) || extmapper_name == "wrap") + { + std::string m_name = stringf("$extern:%s:%s", extmapper_name.c_str(), log_id(cell->type)); - extmapper_module->fixup_ports(); - extmapper_module->check(); + for (auto &c : cell->parameters) + m_name += stringf(":%s=%s", log_id(c.first), log_signal(c.second)); - if (extmapper_name == "simplemap") { - log("Creating %s with simplemap.\n", log_id(extmapper_module)); - if (simplemap_mappers.count(extmapper_cell->type) == 0) - log_error("No simplemap mapper for cell type %s found!\n", log_id(extmapper_cell->type)); - simplemap_mappers.at(extmapper_cell->type)(extmapper_module, extmapper_cell); - extmapper_module->remove(extmapper_cell); - } + if (extmapper_name == "wrap") + m_name += ":" + sha1(tpl->attributes.at(ID::techmap_wrap).decode_string()); - if (extmapper_name == "maccmap") { - log("Creating %s with maccmap.\n", log_id(extmapper_module)); - if (extmapper_cell->type != ID($macc)) - log_error("The maccmap mapper can only map $macc (not %s) cells!\n", log_id(extmapper_cell->type)); - maccmap(extmapper_module, extmapper_cell); - extmapper_module->remove(extmapper_cell); - } + RTLIL::Design *extmapper_design = extern_mode && !in_recursion ? design : tpl->design; + RTLIL::Module *extmapper_module = extmapper_design->module(m_name); - if (extmapper_name == "wrap") { - std::string cmd_string = tpl->attributes.at(ID::techmap_wrap).decode_string(); - log("Running \"%s\" on wrapper %s.\n", cmd_string.c_str(), log_id(extmapper_module)); - mkdebug.on(); - Pass::call_on_module(extmapper_design, extmapper_module, cmd_string); - log_continue = true; - } + if (extmapper_module == nullptr) + { + extmapper_module = extmapper_design->addModule(m_name); + RTLIL::Cell *extmapper_cell = extmapper_module->addCell(cell->type, cell); + + extmapper_cell->set_src_attribute(cell->get_src_attribute()); + + int port_counter = 1; + for (auto &c : extmapper_cell->connections_) { + RTLIL::Wire *w = extmapper_module->addWire(c.first, GetSize(c.second)); + if (w->name.in(ID::Y, ID::Q)) + w->port_output = true; + else + w->port_input = true; + w->port_id = port_counter++; + c.second = w; } - cell->type = extmapper_module->name; - cell->parameters.clear(); + extmapper_module->fixup_ports(); + extmapper_module->check(); - if (!extern_mode || in_recursion) { - tpl = extmapper_module; - goto use_wrapper_tpl; + if (extmapper_name == "simplemap") { + log("Creating %s with simplemap.\n", log_id(extmapper_module)); + if (simplemap_mappers.count(extmapper_cell->type) == 0) + log_error("No simplemap mapper for cell type %s found!\n", log_id(extmapper_cell->type)); + simplemap_mappers.at(extmapper_cell->type)(extmapper_module, extmapper_cell); + extmapper_module->remove(extmapper_cell); } - auto msg = stringf("Using extmapper %s for cells of type %s.", log_id(extmapper_module), log_id(cell->type)); - if (!log_msg_cache.count(msg)) { - log_msg_cache.insert(msg); - log("%s\n", msg.c_str()); - } - log_debug("%s %s.%s (%s) to %s.\n", mapmsg_prefix.c_str(), log_id(module), log_id(cell), log_id(cell->type), log_id(extmapper_module)); - } - else - { - auto msg = stringf("Using extmapper %s for cells of type %s.", extmapper_name.c_str(), log_id(cell->type)); - if (!log_msg_cache.count(msg)) { - log_msg_cache.insert(msg); - log("%s\n", msg.c_str()); + if (extmapper_name == "maccmap") { + log("Creating %s with maccmap.\n", log_id(extmapper_module)); + if (extmapper_cell->type != ID($macc)) + log_error("The maccmap mapper can only map $macc (not %s) cells!\n", log_id(extmapper_cell->type)); + maccmap(extmapper_module, extmapper_cell); + extmapper_module->remove(extmapper_cell); } - log_debug("%s %s.%s (%s) with %s.\n", mapmsg_prefix.c_str(), log_id(module), log_id(cell), log_id(cell->type), extmapper_name.c_str()); - if (extmapper_name == "simplemap") { - if (simplemap_mappers.count(cell->type) == 0) - log_error("No simplemap mapper for cell type %s found!\n", RTLIL::id2cstr(cell->type)); - simplemap_mappers.at(cell->type)(module, cell); + if (extmapper_name == "wrap") { + std::string cmd_string = tpl->attributes.at(ID::techmap_wrap).decode_string(); + log("Running \"%s\" on wrapper %s.\n", cmd_string.c_str(), log_id(extmapper_module)); + mkdebug.on(); + Pass::call_on_module(extmapper_design, extmapper_module, cmd_string); + log_continue = true; } + } - if (extmapper_name == "maccmap") { - if (cell->type != ID($macc)) - log_error("The maccmap mapper can only map $macc (not %s) cells!\n", log_id(cell->type)); - maccmap(module, cell); - } + cell->type = extmapper_module->name; + cell->parameters.clear(); - module->remove(cell); - cell = NULL; + if (!extern_mode || in_recursion) { + tpl = extmapper_module; + goto use_wrapper_tpl; } - did_something = true; - mapped_cell = true; - break; + auto msg = stringf("Using extmapper %s for cells of type %s.", log_id(extmapper_module), log_id(cell->type)); + if (!log_msg_cache.count(msg)) { + log_msg_cache.insert(msg); + log("%s\n", msg.c_str()); + } + log_debug("%s %s.%s (%s) to %s.\n", mapmsg_prefix.c_str(), log_id(module), log_id(cell), log_id(cell->type), log_id(extmapper_module)); } + else + { + auto msg = stringf("Using extmapper %s for cells of type %s.", extmapper_name.c_str(), log_id(cell->type)); + if (!log_msg_cache.count(msg)) { + log_msg_cache.insert(msg); + log("%s\n", msg.c_str()); + } + log_debug("%s %s.%s (%s) with %s.\n", mapmsg_prefix.c_str(), log_id(module), log_id(cell), log_id(cell->type), extmapper_name.c_str()); + + if (extmapper_name == "simplemap") { + if (simplemap_mappers.count(cell->type) == 0) + log_error("No simplemap mapper for cell type %s found!\n", log_id(cell->type)); + simplemap_mappers.at(cell->type)(module, cell); + } + + if (extmapper_name == "maccmap") { + if (cell->type != ID($macc)) + log_error("The maccmap mapper can only map $macc (not %s) cells!\n", log_id(cell->type)); + maccmap(module, cell); + } - for (auto conn : cell->connections()) { - if (conn.first.begins_with("$")) - continue; - if (tpl->wires_.count(conn.first) > 0 && tpl->wires_.at(conn.first)->port_id > 0) - continue; - if (!conn.second.is_fully_const() || parameters.count(conn.first) > 0 || tpl->avail_parameters.count(conn.first) == 0) - goto next_tpl; - parameters[conn.first] = conn.second.as_const(); + module->remove(cell); + cell = nullptr; } - if (0) { - next_tpl: + did_something = true; + mapped_cell = true; + break; + } + + for (auto &conn : cell->connections()) { + if (conn.first.begins_with("$")) continue; - } + if (tpl->wire(conn.first) != nullptr && tpl->wire(conn.first)->port_id > 0) + continue; + if (!conn.second.is_fully_const() || parameters.count(conn.first) > 0 || tpl->avail_parameters.count(conn.first) == 0) + goto next_tpl; + parameters[conn.first] = conn.second.as_const(); + } - if (tpl->avail_parameters.count(ID::_TECHMAP_CELLTYPE_) != 0) - parameters[ID::_TECHMAP_CELLTYPE_] = RTLIL::unescape_id(cell->type); + if (0) { + next_tpl: + continue; + } - for (auto conn : cell->connections()) { - if (tpl->avail_parameters.count(stringf("\\_TECHMAP_CONSTMSK_%s_", RTLIL::id2cstr(conn.first))) != 0) { - std::vector<RTLIL::SigBit> v = sigmap(conn.second).to_sigbit_vector(); - for (auto &bit : v) - bit = RTLIL::SigBit(bit.wire == NULL ? RTLIL::State::S1 : RTLIL::State::S0); - parameters[stringf("\\_TECHMAP_CONSTMSK_%s_", RTLIL::id2cstr(conn.first))] = RTLIL::SigSpec(v).as_const(); - } - if (tpl->avail_parameters.count(stringf("\\_TECHMAP_CONSTVAL_%s_", RTLIL::id2cstr(conn.first))) != 0) { - std::vector<RTLIL::SigBit> v = sigmap(conn.second).to_sigbit_vector(); - for (auto &bit : v) - if (bit.wire != NULL) - bit = RTLIL::SigBit(RTLIL::State::Sx); - parameters[stringf("\\_TECHMAP_CONSTVAL_%s_", RTLIL::id2cstr(conn.first))] = RTLIL::SigSpec(v).as_const(); - } - if (tpl->avail_parameters.count(stringf("\\_TECHMAP_WIREINIT_%s_", RTLIL::id2cstr(conn.first))) != 0) { - auto sig = sigmap(conn.second); - RTLIL::Const value(State::Sx, sig.size()); - for (int i = 0; i < sig.size(); i++) { - auto it = init_bits.find(sig[i]); - if (it != init_bits.end()) { - value[i] = it->second; - } + if (tpl->avail_parameters.count(ID::_TECHMAP_CELLTYPE_) != 0) + parameters.emplace(ID::_TECHMAP_CELLTYPE_, RTLIL::unescape_id(cell->type)); + + for (auto &conn : cell->connections()) { + if (tpl->avail_parameters.count(stringf("\\_TECHMAP_CONSTMSK_%s_", log_id(conn.first))) != 0) { + std::vector<RTLIL::SigBit> v = sigmap(conn.second).to_sigbit_vector(); + for (auto &bit : v) + bit = RTLIL::SigBit(bit.wire == nullptr ? RTLIL::State::S1 : RTLIL::State::S0); + parameters.emplace(stringf("\\_TECHMAP_CONSTMSK_%s_", log_id(conn.first)), RTLIL::SigSpec(v).as_const()); + } + if (tpl->avail_parameters.count(stringf("\\_TECHMAP_CONSTVAL_%s_", log_id(conn.first))) != 0) { + std::vector<RTLIL::SigBit> v = sigmap(conn.second).to_sigbit_vector(); + for (auto &bit : v) + if (bit.wire != nullptr) + bit = RTLIL::SigBit(RTLIL::State::Sx); + parameters.emplace(stringf("\\_TECHMAP_CONSTVAL_%s_", log_id(conn.first)), RTLIL::SigSpec(v).as_const()); + } + if (tpl->avail_parameters.count(stringf("\\_TECHMAP_WIREINIT_%s_", log_id(conn.first))) != 0) { + auto sig = sigmap(conn.second); + RTLIL::Const value(State::Sx, sig.size()); + for (int i = 0; i < sig.size(); i++) { + auto it = init_bits.find(sig[i]); + if (it != init_bits.end()) { + value[i] = it->second; } - parameters[stringf("\\_TECHMAP_WIREINIT_%s_", RTLIL::id2cstr(conn.first))] = value; } + parameters.emplace(stringf("\\_TECHMAP_WIREINIT_%s_", log_id(conn.first)), value); } + } + { int unique_bit_id_counter = 0; - std::map<RTLIL::SigBit, int> unique_bit_id; + dict<RTLIL::SigBit, int> unique_bit_id; unique_bit_id[RTLIL::State::S0] = unique_bit_id_counter++; unique_bit_id[RTLIL::State::S1] = unique_bit_id_counter++; unique_bit_id[RTLIL::State::Sx] = unique_bit_id_counter++; unique_bit_id[RTLIL::State::Sz] = unique_bit_id_counter++; - for (auto conn : cell->connections()) - if (tpl->avail_parameters.count(stringf("\\_TECHMAP_CONNMAP_%s_", RTLIL::id2cstr(conn.first))) != 0) { - for (auto &bit : sigmap(conn.second).to_sigbit_vector()) + for (auto &conn : cell->connections()) + if (tpl->avail_parameters.count(stringf("\\_TECHMAP_CONNMAP_%s_", log_id(conn.first))) != 0) { + for (auto &bit : sigmap(conn.second)) if (unique_bit_id.count(bit) == 0) unique_bit_id[bit] = unique_bit_id_counter++; } @@ -763,14 +695,17 @@ struct TechmapWorker if (tpl->avail_parameters.count(ID::_TECHMAP_BITS_CONNMAP_)) parameters[ID::_TECHMAP_BITS_CONNMAP_] = bits; - for (auto conn : cell->connections()) - if (tpl->avail_parameters.count(stringf("\\_TECHMAP_CONNMAP_%s_", RTLIL::id2cstr(conn.first))) != 0) { + for (auto &conn : cell->connections()) + if (tpl->avail_parameters.count(stringf("\\_TECHMAP_CONNMAP_%s_", log_id(conn.first))) != 0) { RTLIL::Const value; - for (auto &bit : sigmap(conn.second).to_sigbit_vector()) { - RTLIL::Const chunk(unique_bit_id.at(bit), bits); - value.bits.insert(value.bits.end(), chunk.bits.begin(), chunk.bits.end()); + for (auto &bit : sigmap(conn.second)) { + int val = unique_bit_id.at(bit); + for (int i = 0; i < bits; i++) { + value.bits.push_back((val & 1) != 0 ? State::S1 : State::S0); + val = val >> 1; + } } - parameters[stringf("\\_TECHMAP_CONNMAP_%s_", RTLIL::id2cstr(conn.first))] = value; + parameters.emplace(stringf("\\_TECHMAP_CONNMAP_%s_", log_id(conn.first)), value); } } @@ -778,34 +713,31 @@ struct TechmapWorker use_wrapper_tpl:; // do not register techmap_wrap modules with techmap_cache } else { - std::pair<RTLIL::IdString, std::map<RTLIL::IdString, RTLIL::Const>> key(tpl_name, parameters); - if (techmap_cache.count(key) > 0) { - tpl = techmap_cache[key]; + std::pair<IdString, dict<IdString, RTLIL::Const>> key(tpl_name, parameters); + auto it = techmap_cache.find(key); + if (it != techmap_cache.end()) { + tpl = it->second; } else { if (parameters.size() != 0) { mkdebug.on(); - derived_name = tpl->derive(map, dict<RTLIL::IdString, RTLIL::Const>(parameters.begin(), parameters.end())); + derived_name = tpl->derive(map, parameters); tpl = map->module(derived_name); log_continue = true; } - techmap_cache[key] = tpl; + techmap_cache.emplace(std::move(key), tpl); } } - if (flatten_mode) { - techmap_do_cache[tpl] = true; - } else { - RTLIL::Module *constmapped_tpl = map->module(constmap_tpl_name(sigmap, tpl, cell, false)); - if (constmapped_tpl != nullptr) - tpl = constmapped_tpl; - } + RTLIL::Module *constmapped_tpl = map->module(constmap_tpl_name(sigmap, tpl, cell, false)); + if (constmapped_tpl != nullptr) + tpl = constmapped_tpl; if (techmap_do_cache.count(tpl) == 0) { bool keep_running = true; techmap_do_cache[tpl] = true; - std::set<std::string> techmap_wire_names; + pool<IdString> techmap_wire_names; while (keep_running) { @@ -815,11 +747,11 @@ struct TechmapWorker for (auto &it : twd) techmap_wire_names.insert(it.first); - for (auto &it : twd["_TECHMAP_FAIL_"]) { + for (auto &it : twd[ID::_TECHMAP_FAIL_]) { RTLIL::SigSpec value = it.value; if (value.is_fully_const() && value.as_bool()) { log("Not using module `%s' from techmap as it contains a %s marker wire with non-zero value %s.\n", - derived_name.c_str(), RTLIL::id2cstr(it.wire->name), log_signal(value)); + derived_name.c_str(), log_id(it.wire->name), log_signal(value)); techmap_do_cache[tpl] = false; } } @@ -829,13 +761,13 @@ struct TechmapWorker for (auto &it : twd) { - if (it.first.compare(0, 12, "_TECHMAP_DO_") != 0 || it.second.empty()) + if (!it.first.begins_with("\\_TECHMAP_DO_") || it.second.empty()) continue; auto &data = it.second.front(); if (!data.value.is_fully_const()) - log_error("Techmap yielded config wire %s with non-const value %s.\n", RTLIL::id2cstr(data.wire->name), log_signal(data.value)); + log_error("Techmap yielded config wire %s with non-const value %s.\n", log_id(data.wire->name), log_signal(data.value)); techmap_wire_names.erase(it.first); @@ -851,7 +783,7 @@ struct TechmapWorker cmd_string = cmd_string.substr(strlen("CONSTMAP; ")); log("Analyzing pattern of constant bits for this cell:\n"); - RTLIL::IdString new_tpl_name = constmap_tpl_name(sigmap, tpl, cell, true); + IdString new_tpl_name = constmap_tpl_name(sigmap, tpl, cell, true); log("Creating constmapped module `%s'.\n", log_id(new_tpl_name)); log_assert(map->module(new_tpl_name) == nullptr); @@ -862,16 +794,16 @@ struct TechmapWorker techmap_do_cache[new_tpl] = true; tpl = new_tpl; - std::map<RTLIL::SigBit, RTLIL::SigBit> port_new2old_map; - std::map<RTLIL::SigBit, RTLIL::SigBit> port_connmap; - std::map<RTLIL::SigBit, RTLIL::SigBit> cellbits_to_tplbits; + dict<RTLIL::SigBit, RTLIL::SigBit> port_new2old_map; + dict<RTLIL::SigBit, RTLIL::SigBit> port_connmap; + dict<RTLIL::SigBit, RTLIL::SigBit> cellbits_to_tplbits; for (auto wire : tpl->wires().to_vector()) { if (!wire->port_input || wire->port_output) continue; - RTLIL::IdString port_name = wire->name; + IdString port_name = wire->name; tpl->rename(wire, NEW_ID); RTLIL::Wire *new_wire = tpl->addWire(port_name, wire); @@ -879,12 +811,12 @@ struct TechmapWorker wire->port_id = 0; for (int i = 0; i < wire->width; i++) { - port_new2old_map[RTLIL::SigBit(new_wire, i)] = RTLIL::SigBit(wire, i); - port_connmap[RTLIL::SigBit(wire, i)] = RTLIL::SigBit(new_wire, i); + port_new2old_map.emplace(RTLIL::SigBit(new_wire, i), RTLIL::SigBit(wire, i)); + port_connmap.emplace(RTLIL::SigBit(wire, i), RTLIL::SigBit(new_wire, i)); } } - for (auto conn : cell->connections()) + for (auto &conn : cell->connections()) for (int i = 0; i < GetSize(conn.second); i++) { RTLIL::SigBit bit = sigmap(conn.second[i]); @@ -926,7 +858,7 @@ struct TechmapWorker log_assert(!strncmp(q, "_TECHMAP_DO_", 12)); std::string new_name = data.wire->name.substr(0, q-p) + "_TECHMAP_DONE_" + data.wire->name.substr(q-p+12); - while (tpl->wires_.count(new_name)) + while (tpl->wire(new_name) != nullptr) new_name += "_"; tpl->rename(data.wire->name, new_name); @@ -937,17 +869,17 @@ struct TechmapWorker TechmapWires twd = techmap_find_special_wires(tpl); for (auto &it : twd) { - if (it.first != "_TECHMAP_FAIL_" && (it.first.substr(0, 20) != "_TECHMAP_REMOVEINIT_" || it.first[it.first.size()-1] != '_') && it.first.substr(0, 12) != "_TECHMAP_DO_" && it.first.substr(0, 14) != "_TECHMAP_DONE_") - log_error("Techmap yielded unknown config wire %s.\n", it.first.c_str()); + if (it.first != ID::_TECHMAP_FAIL_ && (!it.first.begins_with("\\_TECHMAP_REMOVEINIT_") || !it.first.ends_with("_")) && !it.first.begins_with("\\_TECHMAP_DO_") && !it.first.begins_with("\\_TECHMAP_DONE_")) + log_error("Techmap yielded unknown config wire %s.\n", log_id(it.first)); if (techmap_do_cache[tpl]) for (auto &it2 : it.second) if (!it2.value.is_fully_const()) - log_error("Techmap yielded config wire %s with non-const value %s.\n", RTLIL::id2cstr(it2.wire->name), log_signal(it2.value)); + log_error("Techmap yielded config wire %s with non-const value %s.\n", log_id(it2.wire->name), log_signal(it2.value)); techmap_wire_names.erase(it.first); } for (auto &it : techmap_wire_names) - log_error("Techmap special wire %s disappeared. This is considered a fatal error.\n", RTLIL::id2cstr(it)); + log_error("Techmap special wire %s disappeared. This is considered a fatal error.\n", log_id(it)); if (recursive_mode) { if (log_continue) { @@ -970,10 +902,10 @@ struct TechmapWorker TechmapWires twd = techmap_find_special_wires(tpl); for (auto &it : twd) { - if (it.first.substr(0, 20) == "_TECHMAP_REMOVEINIT_") { + if (it.first.begins_with("\\_TECHMAP_REMOVEINIT_")) { for (auto &it2 : it.second) { auto val = it2.value.as_const(); - auto wirename = RTLIL::escape_id(it.first.substr(20, it.first.size() - 20 - 1)); + auto wirename = RTLIL::escape_id(it.first.substr(21, it.first.size() - 21 - 1)); auto it = cell->connections().find(wirename); if (it != cell->connections().end()) { auto sig = sigmap(it->second); @@ -1015,7 +947,7 @@ struct TechmapWorker } log_debug("%s %s.%s (%s) using %s.\n", mapmsg_prefix.c_str(), log_id(module), log_id(cell), log_id(cell->type), log_id(tpl)); techmap_module_worker(design, module, cell, tpl); - cell = NULL; + cell = nullptr; } did_something = true; mapped_cell = true; @@ -1275,8 +1207,7 @@ struct TechmapPass : public Pass { RTLIL::Design *map = new RTLIL::Design; if (map_files.empty()) { - std::istringstream f(stdcells_code); - Frontend::frontend_call(map, &f, "<techmap.v>", verilog_frontend); + Frontend::frontend_call(map, nullptr, "+/techmap.v", verilog_frontend); } else { for (auto &fn : map_files) if (fn.compare(0, 1, "%") == 0) { @@ -1285,35 +1216,30 @@ struct TechmapPass : public Pass { log_cmd_error("Can't open saved design `%s'.\n", fn.c_str()+1); } for (auto mod : saved_designs.at(fn.substr(1))->modules()) - if (!map->has(mod->name)) + if (!map->module(mod->name)) map->add(mod->clone()); } else { - std::ifstream f; - rewrite_filename(fn); - f.open(fn.c_str()); - yosys_input_files.insert(fn); - if (f.fail()) - log_cmd_error("Can't open map file `%s'\n", fn.c_str()); - Frontend::frontend_call(map, &f, fn, (fn.size() > 3 && fn.compare(fn.size()-3, std::string::npos, ".il") == 0 ? "ilang" : verilog_frontend)); + Frontend::frontend_call(map, nullptr, fn, (fn.size() > 3 && fn.compare(fn.size()-3, std::string::npos, ".il") == 0 ? "ilang" : verilog_frontend)); } } log_header(design, "Continuing TECHMAP pass.\n"); - std::map<RTLIL::IdString, std::set<RTLIL::IdString, RTLIL::sort_by_id_str>> celltypeMap; - for (auto &it : map->modules_) { - if (it.second->attributes.count(ID::techmap_celltype) && !it.second->attributes.at(ID::techmap_celltype).bits.empty()) { - char *p = strdup(it.second->attributes.at(ID::techmap_celltype).decode_string().c_str()); - for (char *q = strtok(p, " \t\r\n"); q; q = strtok(NULL, " \t\r\n")) - celltypeMap[RTLIL::escape_id(q)].insert(it.first); + dict<IdString, pool<IdString>> celltypeMap; + for (auto module : map->modules()) { + if (module->attributes.count(ID::techmap_celltype) && !module->attributes.at(ID::techmap_celltype).bits.empty()) { + char *p = strdup(module->attributes.at(ID::techmap_celltype).decode_string().c_str()); + for (char *q = strtok(p, " \t\r\n"); q; q = strtok(nullptr, " \t\r\n")) + celltypeMap[RTLIL::escape_id(q)].insert(module->name); free(p); } else { - string module_name = it.first.str(); - if (it.first.begins_with("\\$")) - module_name = module_name.substr(1); - celltypeMap[module_name].insert(it.first); + IdString module_name = module->name.begins_with("\\$") ? + module->name.substr(1) : module->name.str(); + celltypeMap[module_name].insert(module->name); } } + for (auto &i : celltypeMap) + i.second.sort(RTLIL::sort_by_id_str()); for (auto module : design->modules()) worker.module_queue.insert(module); @@ -1325,7 +1251,7 @@ struct TechmapPass : public Pass { int module_max_iter = max_iter; bool did_something = true; - std::set<RTLIL::Cell*> handled_cells; + pool<RTLIL::Cell*> handled_cells; while (did_something) { did_something = false; if (worker.techmap_module(design, module, map, handled_cells, celltypeMap, false)) @@ -1344,100 +1270,4 @@ struct TechmapPass : public Pass { } } TechmapPass; -struct FlattenPass : public Pass { - FlattenPass() : Pass("flatten", "flatten design") { } - void help() YS_OVERRIDE - { - // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| - log("\n"); - log(" flatten [options] [selection]\n"); - log("\n"); - log("This pass flattens the design by replacing cells by their implementation. This\n"); - log("pass is very similar to the 'techmap' pass. The only difference is that this\n"); - log("pass is using the current design as mapping library.\n"); - log("\n"); - log("Cells and/or modules with the 'keep_hierarchy' attribute set will not be\n"); - log("flattened by this command.\n"); - log("\n"); - log(" -wb\n"); - log(" Ignore the 'whitebox' attribute on cell implementations.\n"); - log("\n"); - } - void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE - { - log_header(design, "Executing FLATTEN pass (flatten design).\n"); - log_push(); - - TechmapWorker worker; - worker.flatten_mode = true; - - size_t argidx; - for (argidx = 1; argidx < args.size(); argidx++) { - if (args[argidx] == "-wb") { - worker.ignore_wb = true; - continue; - } - break; - } - extra_args(args, argidx, design); - - - std::map<RTLIL::IdString, std::set<RTLIL::IdString, RTLIL::sort_by_id_str>> celltypeMap; - for (auto module : design->modules()) - celltypeMap[module->name].insert(module->name); - - RTLIL::Module *top_mod = NULL; - if (design->full_selection()) - for (auto mod : design->modules()) - if (mod->get_bool_attribute(ID::top)) - top_mod = mod; - - std::set<RTLIL::Cell*> handled_cells; - if (top_mod != NULL) { - worker.flatten_do_list.insert(top_mod->name); - while (!worker.flatten_do_list.empty()) { - auto mod = design->module(*worker.flatten_do_list.begin()); - while (worker.techmap_module(design, mod, design, handled_cells, celltypeMap, false)) { } - worker.flatten_done_list.insert(mod->name); - worker.flatten_do_list.erase(mod->name); - } - } else { - for (auto mod : vector<Module*>(design->modules())) { - while (worker.techmap_module(design, mod, design, handled_cells, celltypeMap, false)) { } - } - } - - log_suppressed(); - log("No more expansions possible.\n"); - - if (top_mod != NULL) - { - pool<RTLIL::IdString> used_modules, new_used_modules; - new_used_modules.insert(top_mod->name); - while (!new_used_modules.empty()) { - pool<RTLIL::IdString> queue; - queue.swap(new_used_modules); - for (auto modname : queue) - used_modules.insert(modname); - for (auto modname : queue) - for (auto cell : design->module(modname)->cells()) - if (design->module(cell->type) && !used_modules[cell->type]) - new_used_modules.insert(cell->type); - } - - dict<RTLIL::IdString, RTLIL::Module*> new_modules; - for (auto mod : vector<Module*>(design->modules())) - if (used_modules[mod->name] || mod->get_blackbox_attribute(worker.ignore_wb)) { - new_modules[mod->name] = mod; - } else { - log("Deleting now unused module %s.\n", log_id(mod)); - delete mod; - } - design->modules_.swap(new_modules); - } - - log_pop(); - } -} FlattenPass; - PRIVATE_NAMESPACE_END diff --git a/passes/tests/test_cell.cc b/passes/tests/test_cell.cc index cdbe922b2..c6801007d 100644 --- a/passes/tests/test_cell.cc +++ b/passes/tests/test_cell.cc @@ -264,7 +264,7 @@ static void create_gold_module(RTLIL::Design *design, RTLIL::IdString cell_type, cell->setPort(ID::Y, wire); } - if (muxdiv && cell_type.in(ID($div), ID($mod))) { + if (muxdiv && cell_type.in(ID($div), ID($mod), ID($divfloor), ID($modfloor))) { auto b_not_zero = module->ReduceBool(NEW_ID, cell->getPort(ID::B)); auto div_out = module->addWire(NEW_ID, GetSize(cell->getPort(ID::Y))); module->addMux(NEW_ID, RTLIL::SigSpec(0, GetSize(div_out)), div_out, b_not_zero, cell->getPort(ID::Y)); @@ -839,6 +839,8 @@ struct TestCellPass : public Pass { cell_types[ID($mul)] = "ABSY"; cell_types[ID($div)] = "ABSY"; cell_types[ID($mod)] = "ABSY"; + cell_types[ID($divfloor)] = "ABSY"; + cell_types[ID($modfloor)] = "ABSY"; // cell_types[ID($pow)] = "ABsY"; cell_types[ID($logic_not)] = "ASY"; diff --git a/techlibs/common/abc9_unmap.v b/techlibs/common/abc9_unmap.v index bcbe91477..c39648c62 100644 --- a/techlibs/common/abc9_unmap.v +++ b/techlibs/common/abc9_unmap.v @@ -1,5 +1,5 @@ (* techmap_celltype = "$__DFF_N__$abc9_flop $__DFF_P__$abc9_flop" *) -module $__DFF_x__$abc9_flop (input C, D, Q, output n1); +module $__DFF_x__$abc9_flop (input C, D, (* init = 1'b0 *) input Q, output n1); parameter _TECHMAP_CELLTYPE_ = ""; generate if (_TECHMAP_CELLTYPE_ == "$__DFF_N__$abc9_flop") $_DFF_N_ _TECHMAP_REPLACE_ (.C(C), .D(D), .Q(Q)); diff --git a/techlibs/common/simlib.v b/techlibs/common/simlib.v index 2cdddeabb..125b8e013 100644 --- a/techlibs/common/simlib.v +++ b/techlibs/common/simlib.v @@ -997,6 +997,12 @@ endmodule // -------------------------------------------------------- +// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| +//- +//- $div (A, B, Y) +//- +//- Division with truncated result (rounded towards 0). +//- module \$div (A, B, Y); parameter A_SIGNED = 0; @@ -1021,6 +1027,14 @@ endmodule // -------------------------------------------------------- +// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| +//- +//- $mod (A, B, Y) +//- +//- Modulo/remainder of division with truncated result (rounded towards 0). +//- +//- Invariant: $div(A, B) * B + $mod(A, B) == A +//- module \$mod (A, B, Y); parameter A_SIGNED = 0; @@ -1044,6 +1058,83 @@ endgenerate endmodule // -------------------------------------------------------- + +// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| +//- +//- $divfloor (A, B, Y) +//- +//- Division with floored result (rounded towards negative infinity). +//- +module \$divfloor (A, B, Y); + +parameter A_SIGNED = 0; +parameter B_SIGNED = 0; +parameter A_WIDTH = 0; +parameter B_WIDTH = 0; +parameter Y_WIDTH = 0; + +input [A_WIDTH-1:0] A; +input [B_WIDTH-1:0] B; +output [Y_WIDTH-1:0] Y; + +generate + if (A_SIGNED && B_SIGNED) begin:BLOCK1 + localparam WIDTH = + A_WIDTH >= B_WIDTH && A_WIDTH >= Y_WIDTH ? A_WIDTH : + B_WIDTH >= A_WIDTH && B_WIDTH >= Y_WIDTH ? B_WIDTH : Y_WIDTH; + wire [WIDTH:0] A_buf, B_buf, N_buf; + assign A_buf = $signed(A); + assign B_buf = $signed(B); + assign N_buf = (A[A_WIDTH-1] == B[B_WIDTH-1]) || A == 0 ? A_buf : $signed(A_buf - (B[B_WIDTH-1] ? B_buf+1 : B_buf-1)); + assign Y = $signed(N_buf) / $signed(B_buf); + end else begin:BLOCK2 + assign Y = A / B; + end +endgenerate + +endmodule + +// -------------------------------------------------------- + +// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| +//- +//- $modfloor (A, B, Y) +//- +//- Modulo/remainder of division with floored result (rounded towards negative infinity). +//- +//- Invariant: $divfloor(A, B) * B + $modfloor(A, B) == A +//- +module \$modfloor (A, B, Y); + +parameter A_SIGNED = 0; +parameter B_SIGNED = 0; +parameter A_WIDTH = 0; +parameter B_WIDTH = 0; +parameter Y_WIDTH = 0; + +input [A_WIDTH-1:0] A; +input [B_WIDTH-1:0] B; +output [Y_WIDTH-1:0] Y; + +generate + if (A_SIGNED && B_SIGNED) begin:BLOCK1 + localparam WIDTH = B_WIDTH >= Y_WIDTH ? B_WIDTH : Y_WIDTH; + wire [WIDTH-1:0] B_buf, Y_trunc; + assign B_buf = $signed(B); + assign Y_trunc = $signed(A) % $signed(B); + // flooring mod is the same as truncating mod for positive division results (A and B have + // the same sign), as well as when there's no remainder. + // For all other cases, they behave as `floor - trunc = B` + assign Y = (A[A_WIDTH-1] == B[B_WIDTH-1]) || Y_trunc == 0 ? Y_trunc : $signed(B_buf) + $signed(Y_trunc); + end else begin:BLOCK2 + // no difference between truncating and flooring for unsigned + assign Y = A % B; + end +endgenerate + +endmodule + +// -------------------------------------------------------- `ifndef SIMLIB_NOPOW module \$pow (A, B, Y); diff --git a/techlibs/common/techmap.v b/techlibs/common/techmap.v index c1efc378b..eafe8d4da 100644 --- a/techlibs/common/techmap.v +++ b/techlibs/common/techmap.v @@ -364,7 +364,8 @@ module \$__div_mod_u (A, B, Y, R); end endgenerate endmodule -module \$__div_mod (A, B, Y, R); +// truncating signed division/modulo +module \$__div_mod_trunc (A, B, Y, R); parameter A_SIGNED = 0; parameter B_SIGNED = 0; parameter A_WIDTH = 1; @@ -420,7 +421,7 @@ module _90_div (A, B, Y); (* force_downto *) output [Y_WIDTH-1:0] Y; - \$__div_mod #( + \$__div_mod_trunc #( .A_SIGNED(A_SIGNED), .B_SIGNED(B_SIGNED), .A_WIDTH(A_WIDTH), @@ -448,7 +449,107 @@ module _90_mod (A, B, Y); (* force_downto *) output [Y_WIDTH-1:0] Y; - \$__div_mod #( + \$__div_mod_trunc #( + .A_SIGNED(A_SIGNED), + .B_SIGNED(B_SIGNED), + .A_WIDTH(A_WIDTH), + .B_WIDTH(B_WIDTH), + .Y_WIDTH(Y_WIDTH) + ) div_mod ( + .A(A), + .B(B), + .R(Y) + ); +endmodule + +// flooring signed division/modulo +module \$__div_mod_floor (A, B, Y, R); + parameter A_SIGNED = 0; + parameter B_SIGNED = 0; + parameter A_WIDTH = 1; + parameter B_WIDTH = 1; + parameter Y_WIDTH = 1; + + localparam WIDTH = + A_WIDTH >= B_WIDTH && A_WIDTH >= Y_WIDTH ? A_WIDTH : + B_WIDTH >= A_WIDTH && B_WIDTH >= Y_WIDTH ? B_WIDTH : Y_WIDTH; + + input [A_WIDTH-1:0] A; + input [B_WIDTH-1:0] B; + output [Y_WIDTH-1:0] Y, R; + + wire [WIDTH-1:0] A_buf, B_buf; + \$pos #(.A_SIGNED(A_SIGNED), .A_WIDTH(A_WIDTH), .Y_WIDTH(WIDTH)) A_conv (.A(A), .Y(A_buf)); + \$pos #(.A_SIGNED(B_SIGNED), .A_WIDTH(B_WIDTH), .Y_WIDTH(WIDTH)) B_conv (.A(B), .Y(B_buf)); + + wire [WIDTH-1:0] A_buf_u, B_buf_u, Y_u, R_u, R_s; + assign A_buf_u = A_SIGNED && A_buf[WIDTH-1] ? -A_buf : A_buf; + assign B_buf_u = B_SIGNED && B_buf[WIDTH-1] ? -B_buf : B_buf; + + \$__div_mod_u #( + .WIDTH(WIDTH) + ) div_mod_u ( + .A(A_buf_u), + .B(B_buf_u), + .Y(Y_u), + .R(R_u) + ); + + // For negative results, if there was a remainder, subtract one to turn + // the round towards 0 into a round towards -inf + assign Y = A_SIGNED && B_SIGNED && (A_buf[WIDTH-1] != B_buf[WIDTH-1]) ? (R_u == 0 ? -Y_u : -Y_u-1) : Y_u; + + // truncating modulo + assign R_s = A_SIGNED && B_SIGNED && A_buf[WIDTH-1] ? -R_u : R_u; + // Flooring modulo differs from truncating modulo only if it is nonzero and + // A and B have different signs - then `floor - trunc = B` + assign R = (R_s != 0) && A_SIGNED && B_SIGNED && (A_buf[WIDTH-1] != B_buf[WIDTH-1]) ? $signed(B_buf) + $signed(R_s) : R_s; +endmodule + +(* techmap_celltype = "$divfloor" *) +module _90_divfloor (A, B, Y); + parameter A_SIGNED = 0; + parameter B_SIGNED = 0; + parameter A_WIDTH = 1; + parameter B_WIDTH = 1; + parameter Y_WIDTH = 1; + + (* force_downto *) + input [A_WIDTH-1:0] A; + (* force_downto *) + input [B_WIDTH-1:0] B; + (* force_downto *) + output [Y_WIDTH-1:0] Y; + + \$__div_mod_floor #( + .A_SIGNED(A_SIGNED), + .B_SIGNED(B_SIGNED), + .A_WIDTH(A_WIDTH), + .B_WIDTH(B_WIDTH), + .Y_WIDTH(Y_WIDTH) + ) div_mod ( + .A(A), + .B(B), + .Y(Y) + ); +endmodule + +(* techmap_celltype = "$modfloor" *) +module _90_modfloor (A, B, Y); + parameter A_SIGNED = 0; + parameter B_SIGNED = 0; + parameter A_WIDTH = 1; + parameter B_WIDTH = 1; + parameter Y_WIDTH = 1; + + (* force_downto *) + input [A_WIDTH-1:0] A; + (* force_downto *) + input [B_WIDTH-1:0] B; + (* force_downto *) + output [Y_WIDTH-1:0] Y; + + \$__div_mod_floor #( .A_SIGNED(A_SIGNED), .B_SIGNED(B_SIGNED), .A_WIDTH(A_WIDTH), diff --git a/tests/arch/ecp5/latches_abc9.ys b/tests/arch/ecp5/latches_abc9.ys new file mode 100644 index 000000000..4daf04050 --- /dev/null +++ b/tests/arch/ecp5/latches_abc9.ys @@ -0,0 +1,13 @@ +read_verilog <<EOT +module top(input e, d, output q); +reg l; +always @* + if (e) + l = ~d; +assign q = ~l; +endmodule +EOT +# Can't run any sort of equivalence check because latches are blown to LUTs +synth_ecp5 -abc9 +select -assert-count 2 t:LUT4 +select -assert-none t:LUT4 %% t:* %D diff --git a/tests/arch/ice40/mux.ys b/tests/arch/ice40/mux.ys index 99822391d..2b661fd6b 100644 --- a/tests/arch/ice40/mux.ys +++ b/tests/arch/ice40/mux.ys @@ -35,6 +35,7 @@ proc equiv_opt -assert -map +/ice40/cells_sim.v synth_ice40 # equivalency check design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) cd mux16 # Constrain all select calls below inside the top module -select -assert-count 11 t:SB_LUT4 +select -assert-min 11 t:SB_LUT4 +select -assert-max 12 t:SB_LUT4 select -assert-none t:SB_LUT4 %% t:* %D diff --git a/tests/arch/intel_alm/.gitignore b/tests/arch/intel_alm/.gitignore new file mode 100644 index 000000000..ba42e1ee6 --- /dev/null +++ b/tests/arch/intel_alm/.gitignore @@ -0,0 +1,2 @@ +/*.log +/run-test.mk diff --git a/tests/arch/xilinx/abc9_dff.ys b/tests/arch/xilinx/abc9_dff.ys index fd343969b..210e87477 100644 --- a/tests/arch/xilinx/abc9_dff.ys +++ b/tests/arch/xilinx/abc9_dff.ys @@ -50,10 +50,10 @@ FDCE_1 /*#(.INIT(1))*/ fd7(.C(C), .CE(1'b0), .D(D), .CLR(1'b0), .Q(Q[6])); FDPE_1 #(.INIT(1)) fd8(.C(C), .CE(1'b0), .D(D), .PRE(1'b0), .Q(Q[7])); endmodule EOT -logger -expect warning "Module '\$paramod\\FDRE\\INIT=1' contains a \$dff cell .*" 1 -logger -expect warning "Module '\$paramod\\FDRE_1\\INIT=1' contains a \$dff cell .*" 1 -logger -expect warning "Module 'FDSE' contains a \$dff cell .*" 1 -logger -expect warning "Module '\$paramod\\FDSE_1\\INIT=1' contains a \$dff cell .*" 1 +logger -expect warning "Whitebox '\$paramod\\FDRE\\INIT=1' with \(\* abc9_flop \*\) contains a \$dff cell with non-zero initial state -- this is not supported for ABC9 sequential synthesis. Treating as a blackbox\." 1 +logger -expect warning "Whitebox '\$paramod\\FDRE_1\\INIT=1' with \(\* abc9_flop \*\) contains a \$dff cell with non-zero initial state -- this is not supported for ABC9 sequential synthesis. Treating as a blackbox\." 1 +logger -expect warning "Whitebox 'FDSE' with \(\* abc9_flop \*\) contains a \$dff cell with non-zero initial state -- this is not supported for ABC9 sequential synthesis. Treating as a blackbox\." 1 +logger -expect warning "Whitebox '\$paramod\\FDSE_1\\INIT=1' with \(\* abc9_flop \*\) contains a \$dff cell with non-zero initial state -- this is not supported for ABC9 sequential synthesis. Treating as a blackbox\." 1 equiv_opt -assert -multiclock -map +/xilinx/cells_sim.v synth_xilinx -abc9 -dff -noiopad -noclkbuf design -load postopt select -assert-count 8 t:FD* @@ -82,4 +82,53 @@ select -assert-count 1 t:FDPE select -assert-count 2 t:INV select -assert-count 0 t:FD* t:INV %% t:* %D + +design -reset +read_verilog <<EOT +module top(input clk, input d, output q); +reg r; +always @(posedge clk) begin +r <= d; +end +assign q = ~r; +endmodule +EOT +proc +equiv_opt -assert -multiclock -map +/xilinx/cells_sim.v synth_xilinx -abc9 -dff -noiopad -noclkbuf +design -load postopt +select -assert-count 1 t:FDRE %co w:r %i + + +design -reset +read_verilog <<EOT +module top(input clk, input a, b, output reg q1, output q2); +reg r; +always @(posedge clk) begin + q1 <= a | b; + r <= ~(~a & ~b); +end +assign q2 = r; +endmodule +EOT +proc +equiv_opt -assert -multiclock -map +/xilinx/cells_sim.v synth_xilinx -abc9 -dff -noiopad -noclkbuf +design -load postopt +select -assert-count 1 t:FDRE %co %a w:r %i + + +design -reset +read_verilog <<EOT +module top(input clk, input a, b, output o); +reg r1, r2; +always @(posedge clk) begin + r1 <= a | b; + r2 <= ~(~a & ~b); +end +assign o = r1 | r2; +endmodule +EOT +proc +equiv_opt -assert -multiclock -map +/xilinx/cells_sim.v synth_xilinx -abc9 -dff -noiopad -noclkbuf + + logger -expect-no-warnings diff --git a/tests/arch/xilinx/pmgen_xilinx_srl.ys b/tests/arch/xilinx/pmgen_xilinx_srl.ys index ea2f20487..e76fb20ab 100644 --- a/tests/arch/xilinx/pmgen_xilinx_srl.ys +++ b/tests/arch/xilinx/pmgen_xilinx_srl.ys @@ -1,6 +1,6 @@ read_verilog -icells <<EOT module \$__XILINX_SHREG_ (input C, input D, input [31:0] L, input E, output Q, output SO); - parameter DEPTH = 1; + parameter DEPTH = 2; parameter [DEPTH-1:0] INIT = 0; parameter CLKPOL = 1; parameter ENPOL = 2; diff --git a/tests/arch/xilinx/xilinx_srl.v b/tests/arch/xilinx/xilinx_srl.v index bc2a15ab2..29920da41 100644 --- a/tests/arch/xilinx/xilinx_srl.v +++ b/tests/arch/xilinx/xilinx_srl.v @@ -29,7 +29,7 @@ endmodule module $__XILINX_SHREG_(input C, D, E, input [1:0] L, output Q); parameter CLKPOL = 1; parameter ENPOL = 1; -parameter DEPTH = 1; +parameter DEPTH = 2; parameter [DEPTH-1:0] INIT = {DEPTH{1'b0}}; reg [DEPTH-1:0] r = INIT; wire clk = C ^ CLKPOL; diff --git a/tests/simple/constmuldivmod.v b/tests/simple/constmuldivmod.v index d1d8be862..5dd8f9295 100644 --- a/tests/simple/constmuldivmod.v +++ b/tests/simple/constmuldivmod.v @@ -1,4 +1,4 @@ -module constmuldivmod(input [7:0] A, input [2:0] mode, output reg [7:0] Y); +module constmuldivmod(input [7:0] A, input [5:0] mode, output reg [7:0] Y); always @* begin case (mode) 0: Y = A / 8'd0; @@ -21,6 +21,46 @@ module constmuldivmod(input [7:0] A, input [2:0] mode, output reg [7:0] Y); 13: Y = A % 8'd8; 14: Y = A * 8'd8; + 15: Y = $signed(A) / $signed(8'd0); + 16: Y = $signed(A) % $signed(8'd0); + 17: Y = $signed(A) * $signed(8'd0); + + 18: Y = $signed(A) / $signed(8'd1); + 19: Y = $signed(A) % $signed(8'd1); + 20: Y = $signed(A) * $signed(8'd1); + + 21: Y = $signed(A) / $signed(8'd2); + 22: Y = $signed(A) % $signed(8'd2); + 23: Y = $signed(A) * $signed(8'd2); + + 24: Y = $signed(A) / $signed(8'd4); + 25: Y = $signed(A) % $signed(8'd4); + 26: Y = $signed(A) * $signed(8'd4); + + 27: Y = $signed(A) / $signed(8'd8); + 28: Y = $signed(A) % $signed(8'd8); + 29: Y = $signed(A) * $signed(8'd8); + + 30: Y = $signed(A) / $signed(-8'd0); + 31: Y = $signed(A) % $signed(-8'd0); + 32: Y = $signed(A) * $signed(-8'd0); + + 33: Y = $signed(A) / $signed(-8'd1); + 34: Y = $signed(A) % $signed(-8'd1); + 35: Y = $signed(A) * $signed(-8'd1); + + 36: Y = $signed(A) / $signed(-8'd2); + 37: Y = $signed(A) % $signed(-8'd2); + 38: Y = $signed(A) * $signed(-8'd2); + + 39: Y = $signed(A) / $signed(-8'd4); + 40: Y = $signed(A) % $signed(-8'd4); + 41: Y = $signed(A) * $signed(-8'd4); + + 42: Y = $signed(A) / $signed(-8'd8); + 43: Y = $signed(A) % $signed(-8'd8); + 44: Y = $signed(A) * $signed(-8'd8); + default: Y = 8'd16 * A; endcase end diff --git a/tests/svtypes/logic_rom.sv b/tests/svtypes/logic_rom.sv new file mode 100644 index 000000000..45fe0a4ca --- /dev/null +++ b/tests/svtypes/logic_rom.sv @@ -0,0 +1,6 @@ +module top(input [3:0] addr, output [7:0] data); + logic [7:0] mem[0:15]; + assign data = mem[addr]; + integer i; + initial for(i = 0; i < 16; i = i + 1) mem[i] = i; +endmodule diff --git a/tests/svtypes/logic_rom.ys b/tests/svtypes/logic_rom.ys new file mode 100644 index 000000000..7b079c136 --- /dev/null +++ b/tests/svtypes/logic_rom.ys @@ -0,0 +1,3 @@ +read_verilog -sv logic_rom.sv +prep -top top +select -assert-count 1 t:$mem r:SIZE=16 %i r:WIDTH=8 %i diff --git a/tests/svtypes/struct_array.sv b/tests/svtypes/struct_array.sv new file mode 100644 index 000000000..022ad56c6 --- /dev/null +++ b/tests/svtypes/struct_array.sv @@ -0,0 +1,22 @@ +// test for array indexing in structures + +module top; + + struct packed { + bit [5:0] [7:0] a; // 6 element packed array of bytes + bit [15:0] b; // filler for non-zero offset + } s; + + initial begin + s = '0; + + s.a[2:1] = 16'h1234; + s.a[5] = 8'h42; + + s.b = '1; + s.b[1:0] = '0; + end + + always_comb assert(s==64'h4200_0012_3400_FFFC); + +endmodule diff --git a/tests/svtypes/struct_simple.sv b/tests/svtypes/struct_simple.sv new file mode 100644 index 000000000..c74289cc3 --- /dev/null +++ b/tests/svtypes/struct_simple.sv @@ -0,0 +1,48 @@ +module top; + localparam BITS=8; + + struct packed { + logic a; + logic[BITS-1:0] b; + byte c; + logic x, y; + } s; + + struct packed signed { + integer a; + logic[15:0] b; + logic[7:0] c; + bit [7:0] d; + } pack1; + + struct packed { + byte a; + struct packed { + byte x, y; + } b; + } s2; + + assign s.a = '1; + assign s.b = '1; + assign s.c = 8'hAA; + assign s.x = '1; + logic[7:0] t; + assign t = s.b; + assign pack1.a = 42; + assign pack1.b = 16'hAAAA; + assign pack1.c = '1; + assign pack1.d = 8'h55; + assign s2.b.x = 'h42; + + always_comb assert(s.a == 1'b1); + always_comb assert(s.c == 8'hAA); + always_comb assert(s.x == 1'b1); + always_comb assert(t == 8'hFF); + always_comb assert(pack1.a == 42); + always_comb assert(pack1.b == 16'hAAAA); + always_comb assert(pack1.c == 8'hFF); + always_comb assert(pack1[15:8] == 8'hFF); + always_comb assert(pack1.d == 8'h55); + always_comb assert(s2.b.x == 'h42); + +endmodule diff --git a/tests/svtypes/typedef_struct.sv b/tests/svtypes/typedef_struct.sv new file mode 100644 index 000000000..7ae007952 --- /dev/null +++ b/tests/svtypes/typedef_struct.sv @@ -0,0 +1,42 @@ +package p; + +typedef struct packed { + byte a; + byte b; +} p_t; + +endpackage + + +module top; + + typedef logic[7:0] t_t; + + typedef struct packed { + bit a; + logic[7:0] b; + t_t t; + } s_t; + + s_t s; + s_t s1; + + p::p_t ps; + + assign s.a = '1; + assign s.b = '1; + assign s.t = 8'h55; + assign s1 = s; + assign ps.a = 8'hAA; + assign ps.b = 8'h55; + + always_comb begin + assert(s.a == 1'b1); + assert(s.b == 8'hFF); + assert(s.t == 8'h55); + assert(s1.t == 8'h55); + assert(ps.a == 8'hAA); + assert(ps.b == 8'h55); + end + +endmodule diff --git a/tests/svtypes/union_simple.sv b/tests/svtypes/union_simple.sv new file mode 100644 index 000000000..12e4b376f --- /dev/null +++ b/tests/svtypes/union_simple.sv @@ -0,0 +1,72 @@ +module top; + + typedef struct packed { + byte a,b,c,d; + } byte4_t; + + typedef union packed { + int x; + byte4_t y; + } w_t; + + w_t w; + + assign w.x = 'h42; + always_comb begin + assert(w.y.d == 8'h42); + end + + typedef logic[4:0] reg_addr_t; + typedef logic[6:0] opcode_t; + + typedef struct packed { + bit [6:0] func7; + reg_addr_t rs2; + reg_addr_t rs1; + bit [2:0] func3; + reg_addr_t rd; + opcode_t opcode; + } R_t; + + typedef struct packed { + bit[11:0] imm; + reg_addr_t rs1; + bit[2:0] func3; + reg_addr_t rd; + opcode_t opcode; + } I_t; + + typedef struct packed { + bit[19:0] imm; + reg_addr_t rd; + opcode_t opcode; + } U_t; + + typedef union packed { + R_t r; + I_t i; + U_t u; + } instruction_t; + + instruction_t ir1; + assign ir1 = 32'h0AA01EB7; // lui t4,0xAA01 + always_comb begin + assert(ir1.u.opcode == 'h37); + assert(ir1.r.opcode == 'h37); + assert(ir1.u.rd == 'd29); + assert(ir1.r.rd == 'd29); + assert(ir1.u.imm == 'hAA01); + end + + union packed { + int word; + struct packed { + byte a, b, c, d; + } byte4; + } u; + assign u.word = 'h42; + always_comb begin + assert(u.byte4.d == 'h42); + end + +endmodule diff --git a/tests/various/abc9.ys b/tests/various/abc9.ys index ac714665f..a9880c722 100644 --- a/tests/various/abc9.ys +++ b/tests/various/abc9.ys @@ -97,4 +97,5 @@ select -assert-count 3 t:$_DFF_N_ select -assert-none c:ff1 c:ff2 c:ff4 %% c:* %D clean select -assert-count 2 a:init -select -assert-none w:w w:z %% a:init %D +select -assert-count 1 w:w a:init %i +select -assert-count 1 c:ff4 %co c:ff4 %d %a a:init %i diff --git a/tests/various/attrib07_func_call.v b/tests/various/attrib07_func_call.v index f55ef2316..8c9fb2926 100644 --- a/tests/various/attrib07_func_call.v +++ b/tests/various/attrib07_func_call.v @@ -11,7 +11,7 @@ module foo(clk, rst, inp_a, inp_b, out); input wire rst; input wire [7:0] inp_a; input wire [7:0] inp_b; - output wire [7:0] out; + output reg [7:0] out; always @(posedge clk) if (rst) out <= 0; diff --git a/tests/various/constmsk_testmap.v b/tests/various/constmsk_testmap.v index fab1b1bbc..b6809c7c0 100644 --- a/tests/various/constmsk_testmap.v +++ b/tests/various/constmsk_testmap.v @@ -1,7 +1,7 @@ (* techmap_celltype = "$reduce_or" *) module my_opt_reduce_or(...); parameter A_SIGNED = 0; - parameter A_WIDTH = 1; + parameter A_WIDTH = 2; parameter Y_WIDTH = 1; input [A_WIDTH-1:0] A; diff --git a/tests/various/printattr.ys b/tests/various/printattr.ys new file mode 100644 index 000000000..afc6d8eb6 --- /dev/null +++ b/tests/various/printattr.ys @@ -0,0 +1,14 @@ +logger -expect log ".*cells_not_processed=[01]* .*" 1 +logger -expect log ".*src=.<<EOT:1\.1-9\.10. .*" 1 +read_verilog <<EOT +module mux2(a, b, s, y); + input a, b, s; + output y; + + wire s_n = ~s; + wire t0 = s & a; + wire t1 = s_n & b; + assign y = t0 | t1; +endmodule +EOT +printattrs diff --git a/tests/various/shregmap.v b/tests/various/shregmap.v index 604c2c976..dc828eda7 100644 --- a/tests/various/shregmap.v +++ b/tests/various/shregmap.v @@ -13,7 +13,7 @@ assign q = {shift2[3], shift1[3]}; endmodule module $__SHREG_DFF_P_(input C, D, output Q); -parameter DEPTH = 1; +parameter DEPTH = 2; parameter [DEPTH-1:0] INIT = {DEPTH{1'b0}}; reg [DEPTH-1:0] r = INIT; always @(posedge C) @@ -38,7 +38,7 @@ endmodule module $__XILINX_SHREG_(input C, D, input [1:0] L, output Q); parameter CLKPOL = 1; parameter ENPOL = 1; -parameter DEPTH = 1; +parameter DEPTH = 2; parameter [DEPTH-1:0] INIT = {DEPTH{1'b0}}; reg [DEPTH-1:0] r = INIT; wire clk = C ^ CLKPOL; diff --git a/tests/verilog/bug2042-sv.ys b/tests/verilog/bug2042-sv.ys index e815d7fc5..91989f412 100644 --- a/tests/verilog/bug2042-sv.ys +++ b/tests/verilog/bug2042-sv.ys @@ -2,7 +2,7 @@ read_verilog -sv <<EOT module Task_Test_Top ( input a, -output b +output reg b ); task SomeTaskName(a); |