/* * yosys -- Yosys Open SYnthesis Suite * * Copyright (C) 2012 Clifford Wolf * * 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/sigtools.h" USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN struct ShregmapTech { virtual ~ShregmapTech() { } virtual void init(const Module * /*module*/, const SigMap &/*sigmap*/) {} virtual void non_chain_user(const SigBit &/*bit*/, const Cell* /*cell*/, IdString /*port*/) {} virtual bool analyze(vector &taps, const vector &qbits) = 0; virtual bool fixup(Cell *cell, dict &taps) = 0; }; struct ShregmapOptions { int minlen, maxlen; int keep_before, keep_after; bool zinit, init, params, ffe; dict> ffcells; ShregmapTech *tech; ShregmapOptions() { minlen = 2; maxlen = 0; keep_before = 0; keep_after = 0; zinit = false; init = false; params = false; ffe = false; tech = nullptr; } }; struct ShregmapTechGreenpak4 : ShregmapTech { bool analyze(vector &taps, const vector &/*qbits*/) { if (GetSize(taps) > 2 && taps[0] == 0 && taps[2] < 17) { taps.clear(); return true; } if (GetSize(taps) > 2) return false; if (taps.back() > 16) return false; return true; } bool fixup(Cell *cell, dict &taps) { auto D = cell->getPort(ID(D)); auto C = cell->getPort(ID(C)); auto newcell = cell->module->addCell(NEW_ID, ID(GP_SHREG)); newcell->setPort(ID(nRST), State::S1); newcell->setPort(ID(CLK), C); newcell->setPort(ID(IN), D); int i = 0; for (auto tap : taps) { newcell->setPort(i ? ID(OUTB) : ID(OUTA), tap.second); newcell->setParam(i ? ID(OUTB_TAP) : ID(OUTA_TAP), tap.first + 1); i++; } cell->setParam(ID(OUTA_INVERT), 0); return false; } }; struct ShregmapTechXilinx7 : ShregmapTech { dict> sigbit_to_shiftx_offset; const ShregmapOptions &opts; ShregmapTechXilinx7(const ShregmapOptions &opts) : opts(opts) {} virtual void init(const Module* module, const SigMap &sigmap) override { for (const auto &i : module->cells_) { auto cell = i.second; if (cell->type == ID($shiftx)) { if (cell->getParam(ID(Y_WIDTH)) != 1) continue; int j = 0; for (auto bit : sigmap(cell->getPort(ID::A))) sigbit_to_shiftx_offset[bit] = std::make_tuple(cell, j++, 0); log_assert(j == cell->getParam(ID(A_WIDTH)).as_int()); } else if (cell->type == ID($mux)) { int j = 0; for (auto bit : sigmap(cell->getPort(ID::A))) sigbit_to_shiftx_offset[bit] = std::make_tuple(cell, 0, j++); j = 0; for (auto bit : sigmap(cell->getPort(ID::B))) sigbit_to_shiftx_offset[bit] = std::make_tuple(cell, 1, j++); } } } virtual void non_chain_user(const SigBit &bit, const Cell *cell, IdString port) override { auto it = sigbit_to_shiftx_offset.find(bit); if (it == sigbit_to_shiftx_offset.end()) return; if (cell) { if (cell->type == ID($shiftx) && port == ID::A) return; if (cell->type == ID($mux) && port.in(ID::A, ID::B)) return; } sigbit_to_shiftx_offset.erase(it); } virtual bool analyze(vector &taps, const vector &qbits) override { if (GetSize(taps) == 1) return taps[0] >= opts.minlen-1 && sigbit_to_shiftx_offset.count(qbits[0]); if (taps.back() < opts.minlen-1) return false; Cell *shiftx = nullptr; int group = 0; for (int i = 0; i < GetSize(taps); ++i) { auto it = sigbit_to_shiftx_offset.find(qbits[i]); if (it == sigbit_to_shiftx_offset.end()) return false; // Check taps are sequential if (i != taps[i]) return false; // Check taps are not connected to a shift register, // or sequential to the same shift register if (i == 0) { int offset; std::tie(shiftx,offset,group) = it->second; if (offset != i) return false; } else { Cell *shiftx_ = std::get<0>(it->second); if (shiftx_ != shiftx) return false; int offset = std::get<1>(it->second); if (offset != i) return false; int group_ = std::get<2>(it->second); if (group_ != group) return false; } } log_assert(shiftx); // Only map if $shiftx exclusively covers the shift register if (shiftx->type == ID($shiftx)) { if (GetSize(taps) > shiftx->getParam(ID(A_WIDTH)).as_int()) return false; // Due to padding the most significant bits of A may be 1'bx, // and if so, discount them if (GetSize(taps) < shiftx->getParam(ID(A_WIDTH)).as_int()) { const SigSpec A = shiftx->getPort(ID::A); const int A_width = shiftx->getParam(ID(A_WIDTH)).as_int(); for (int i = GetSize(taps); i < A_width; ++i) if (A[i] != RTLIL::Sx) return false; } else if (GetSize(taps) != shiftx->getParam(ID(A_WIDTH)).as_int()) return false; } else if (shiftx->type == ID($mux)) { if (GetSize(taps) != 2) return false; } else log_abort(); return true; } virtual bool fixup(Cell *cell, dict &taps) override { const auto &tap = *taps.begin(); auto bit = tap.second; auto it = sigbit_to_shiftx_offset.find(bit); log_assert(it != sigbit_to_shiftx_offset.end()); auto newcell = cell->module->addCell(NEW_ID, ID($__XILINX_SHREG_)); newcell->set_src_attribute(cell->get_src_attribute()); newcell->setParam(ID(DEPTH), cell->getParam(ID(DEPTH))); newcell->setParam(ID(INIT), cell->getParam(ID(INIT))); newcell->setParam(ID(CLKPOL), cell->getParam(ID(CLKPOL))); newcell->setParam(ID(ENPOL), cell->getParam(ID(ENPOL))); newcell->setPort(ID(C), cell->getPort(ID(C))); newcell->setPort(ID(D), cell->getPort(ID(D))); if (cell->hasPort(ID(E))) newcell->setPort(ID(E), cell->getPort(ID(E))); Cell* shiftx = std::get<0>(it->second); RTLIL::SigSpec l_wire, q_wire; if (shiftx->type == ID($shiftx)) { l_wire = shiftx->getPort(ID::B); q_wire = shiftx->getPort(ID::Y); shiftx->setPort(ID::Y, cell->module->addWire(NEW_ID)); } else if (shiftx->type == ID($mux)) { l_wire = shiftx->getPort(ID(S)); q_wire = shiftx->getPort(ID::Y); shiftx->setPort(ID::Y, cell->module->addWire(NEW_ID)); } else log_abort(); newcell->setPort(ID(Q), q_wire); newcell->setPort(ID(L), l_wire); return false; } }; struct ShregmapWorker { Module *module; SigMap sigmap; const ShregmapOptions &opts; int dff_count, shreg_count; pool remove_cells; pool remove_init; dict sigbit_init; dict sigbit_chain_next; dict sigbit_chain_prev; pool sigbit_with_non_chain_users; pool chain_start_cells; void make_sigbit_chain_next_prev() { for (auto wire : module->wires()) { if (wire->port_output || wire->get_bool_attribute(ID::keep)) { for (auto bit : sigmap(wire)) { sigbit_with_non_chain_users.insert(bit); if (opts.tech) opts.tech->non_chain_user(bit, nullptr, {}); } } if (wire->attributes.count(ID(init))) { SigSpec initsig = sigmap(wire); Const initval = wire->attributes.at(ID(init)); for (int i = 0; i < GetSize(initsig) && i < GetSize(initval); i++) if (initval[i] == State::S0 && !opts.zinit) sigbit_init[initsig[i]] = false; else if (initval[i] == State::S1) sigbit_init[initsig[i]] = true; } } for (auto cell : module->cells()) { if (opts.ffcells.count(cell->type) && !cell->get_bool_attribute(ID::keep)) { IdString d_port = opts.ffcells.at(cell->type).first; IdString q_port = opts.ffcells.at(cell->type).second; SigBit d_bit = sigmap(cell->getPort(d_port).as_bit()); SigBit q_bit = sigmap(cell->getPort(q_port).as_bit()); if (opts.init || sigbit_init.count(q_bit) == 0) { auto r = sigbit_chain_next.insert(std::make_pair(d_bit, cell)); if (!r.second) { // Insertion not successful means that d_bit is already // connected to another register, thus mark it as a // non chain user ... sigbit_with_non_chain_users.insert(d_bit); // ... and clone d_bit into another wire, and use that // wire as a different key in the d_bit-to-cell dictionary // so that it can be identified as another chain // (omitting this common flop) // Link: https://github.com/YosysHQ/yosys/pull/1085 Wire *wire = module->addWire(NEW_ID); module->connect(wire, d_bit); sigmap.add(wire, d_bit); sigbit_chain_next.insert(std::make_pair(wire, cell)); } sigbit_chain_prev[q_bit] = cell; continue; } } for (auto conn : cell->connections()) if (cell->input(conn.first)) for (auto bit : sigmap(conn.second)) { sigbit_with_non_chain_users.insert(bit); if (opts.tech) opts.tech->non_chain_user(bit, cell, conn.first); } } } void find_chain_start_cells() { for (auto it : sigbit_chain_next) { if (opts.tech == nullptr && sigbit_with_non_chain_users.count(it.first)) goto start_cell; if (sigbit_chain_prev.count(it.first) != 0) { Cell *c1 = sigbit_chain_prev.at(it.first); Cell *c2 = it.second; if (c1->type != c2->type) goto start_cell; if (c1->parameters != c2->parameters) goto start_cell; IdString d_port = opts.ffcells.at(c1->type).first; IdString q_port = opts.ffcells.at(c1->type).second; auto c1_conn = c1->connections(); auto c2_conn = c1->connections(); c1_conn.erase(d_port); c1_conn.erase(q_port); c2_conn.erase(d_port); c2_conn.erase(q_port); if (c1_conn != c2_conn) goto start_cell; continue; } start_cell: chain_start_cells.insert(it.second); } } vector create_chain(Cell *start_cell) { vector chain; Cell *c = start_cell; while (c != nullptr) { chain.push_back(c); IdString q_port = opts.ffcells.at(c->type).second; SigBit q_bit = sigmap(c->getPort(q_port).as_bit()); if (sigbit_chain_next.count(q_bit) == 0) break; c = sigbit_chain_next.at(q_bit); if (chain_start_cells.count(c) != 0) break; } return chain; } void process_chain(vector &chain) { if (GetSize(chain) < opts.keep_before + opts.minlen + opts.keep_after) return; int cursor = opts.keep_before; while (cursor < GetSize(chain) - opts.keep_after) { int depth = GetSize(chain) - opts.keep_after - cursor; if (opts.maxlen > 0) depth = std::min(opts.maxlen, depth); Cell *first_cell = chain[cursor]; IdString q_port = opts.ffcells.at(first_cell->type).second; dict taps_dict; if (opts.tech) { vector qbits; vector taps; for (int i = 0; i < depth; i++) { Cell *cell = chain[cursor+i]; auto qbit = sigmap(cell->getPort(q_port)); qbits.push_back(qbit); if (sigbit_with_non_chain_users.count(qbit)) taps.push_back(i); } while (depth > 0) { if (taps.empty() || taps.back() < depth-1) taps.push_back(depth-1); if (opts.tech->analyze(taps, qbits)) break; taps.pop_back(); depth--; } depth = 0; for (auto tap : taps) { taps_dict[tap] = qbits.at(tap); log_assert(depth < tap+1); depth = tap+1; } } if (depth < 2) { cursor++; continue; } Cell *last_cell = chain[cursor+depth-1]; log("Converting %s.%s ... %s.%s to a shift register with depth %d.\n", log_id(module), log_id(first_cell), log_id(module), log_id(last_cell), depth); dff_count += depth; shreg_count += 1; string shreg_cell_type_str = "$__SHREG"; if (opts.params) { shreg_cell_type_str += "_"; } else { if (first_cell->type[1] != '_') shreg_cell_type_str += "_"; shreg_cell_type_str += first_cell->type.substr(1); } if (opts.init) { vector initval; for (int i = depth-1; i >= 0; i--) { SigBit bit = sigmap(chain[cursor+i]->getPort(q_port).as_bit()); if (sigbit_init.count(bit) == 0) initval.push_back(State::Sx); else if (sigbit_init.at(bit)) initval.push_back(State::S1); else initval.push_back(State::S0); remove_init.insert(bit); } first_cell->setParam(ID(INIT), initval); } if (opts.zinit) for (int i = depth-1; i >= 0; i--) { SigBit bit = sigmap(chain[cursor+i]->getPort(q_port).as_bit()); remove_init.insert(bit); } if (opts.params) { int param_clkpol = -1; int param_enpol = 2; if (first_cell->type == ID($_DFF_N_)) param_clkpol = 0; if (first_cell->type == ID($_DFF_P_)) param_clkpol = 1; if (first_cell->type == ID($_DFFE_NN_)) param_clkpol = 0, param_enpol = 0; if (first_cell->type == ID($_DFFE_NP_)) param_clkpol = 0, param_enpol = 1; if (first_cell->type == ID($_DFFE_PN_)) param_clkpol = 1, param_enpol = 0; if (first_cell->type == ID($_DFFE_PP_)) param_clkpol = 1, param_enpol = 1; log_assert(param_clkpol >= 0); first_cell->setParam(ID(CLKPOL), param_clkpol); if (opts.ffe) first_cell->setParam(ID(ENPOL), param_enpol); } first_cell->type = shreg_cell_type_str; first_cell->setPort(q_port, last_cell->getPort(q_port)); first_cell->setParam(ID(DEPTH), depth); if (opts.tech != nullptr && !opts.tech->fixup(first_cell, taps_dict)) remove_cells.insert(first_cell); for (int i = 1; i < depth; i++) remove_cells.insert(chain[cursor+i]); cursor += depth; } } void cleanup() { for (auto cell : remove_cells) module->remove(cell); for (auto wire : module->wires()) { if (wire->attributes.count(ID(init)) == 0) continue; SigSpec initsig = sigmap(wire); Const &initval = wire->attributes.at(ID(init)); for (int i = 0; i < GetSize(initsig) && i < GetSize(initval); i++) if (remove_init.count(initsig[i])) initval[i] = State::Sx; if (SigSpec(initval).is_fully_undef()) wire->attributes.erase(ID(init)); } remove_cells.clear(); sigbit_chain_next.clear(); sigbit_chain_prev.clear(); chain_start_cells.clear(); } ShregmapWorker(Module *module, const ShregmapOptions &opts) : module(module), sigmap(module), opts(opts), dff_count(0), shreg_count(0) { if (opts.tech) opts.tech->init(module, sigmap); make_sigbit_chain_next_prev(); find_chain_start_cells(); for (auto c : chain_start_cells) { vector chain = create_chain(c); process_chain(chain); } cleanup(); } }; struct ShregmapPass : public Pass { ShregmapPass() : Pass("shregmap", "map shift registers") { } void help() YS_OVERRIDE { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); log(" shregmap [options] [selection]\n"); log("\n"); log("This pass converts chains of $_DFF_[NP]_ gates to target specific shift register\n"); log("primitives. The generated shift register will be of type $__SHREG_DFF_[NP]_ and\n"); log("will use the same interface as the original $_DFF_*_ cells. The cell parameter\n"); log("'DEPTH' will contain the depth of the shift register. Use a target-specific\n"); log("'techmap' map file to convert those cells to the actual target cells.\n"); log("\n"); log(" -minlen N\n"); log(" minimum length of shift register (default = 2)\n"); log(" (this is the length after -keep_before and -keep_after)\n"); log("\n"); log(" -maxlen N\n"); log(" maximum length of shift register (default = no limit)\n"); log(" larger chains will be mapped to multiple shift register instances\n"); log("\n"); log(" -keep_before N\n"); log(" number of DFFs to keep before the shift register (default = 0)\n"); log("\n"); log(" -keep_after N\n"); log(" number of DFFs to keep after the shift register (default = 0)\n"); log("\n"); log(" -clkpol pos|neg|any\n"); log(" limit match to only positive or negative edge clocks. (default = any)\n"); log("\n"); log(" -enpol pos|neg|none|any_or_none|any\n"); log(" limit match to FFs with the specified enable polarity. (default = none)\n"); log("\n"); log(" -match [::]\n"); log(" match the specified cells instead of $_DFF_N_ and $_DFF_P_. If\n"); log(" '::' is omitted then 'D' and 'Q' is used\n"); log(" by default. E.g. the option '-clkpol pos' is just an alias for\n"); log(" '-match $_DFF_P_', which is an alias for '-match $_DFF_P_:D:Q'.\n"); log("\n"); log(" -params\n"); log(" instead of encoding the clock and enable polarity in the cell name by\n"); log(" deriving from the original cell name, simply name all generated cells\n"); log(" $__SHREG_ and use CLKPOL and ENPOL parameters. An ENPOL value of 2 is\n"); log(" used to denote cells without enable input. The ENPOL parameter is\n"); log(" omitted when '-enpol none' (or no -enpol option) is passed.\n"); log("\n"); log(" -zinit\n"); log(" assume the shift register is automatically zero-initialized, so it\n"); log(" becomes legal to merge zero initialized FFs into the shift register.\n"); log("\n"); log(" -init\n"); log(" map initialized registers to the shift reg, add an INIT parameter to\n"); log(" generated cells with the initialization value. (first bit to shift out\n"); log(" in LSB position)\n"); log("\n"); log(" -tech greenpak4\n"); log(" map to greenpak4 shift registers.\n"); log(" this option also implies -clkpol pos -zinit\n"); log("\n"); log(" -tech xilinx\n"); log(" map to xilinx dynamic-length shift registers.\n"); log(" this option also implies -params -init\n"); log("\n"); } void execute(std::vector args, RTLIL::Design *design) YS_OVERRIDE { ShregmapOptions opts; string clkpol, enpol; log_header(design, "Executing SHREGMAP pass (map shift registers).\n"); size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) { if (args[argidx] == "-clkpol" && argidx+1 < args.size()) { clkpol = args[++argidx]; continue; } if (args[argidx] == "-enpol" && argidx+1 < args.size()) { enpol = args[++argidx]; continue; } if (args[argidx] == "-match" && argidx+1 < args.size()) { vector match_args = split_tokens(args[++argidx], ":"); if (GetSize(match_args) < 2) match_args.push_back("D"); if (GetSize(match_args) < 3) match_args.push_back("Q"); IdString id_cell_type(RTLIL::escape_id(match_args[0])); IdString id_d_port_name(RTLIL::escape_id(match_args[1])); IdString id_q_port_name(RTLIL::escape_id(match_args[2])); opts.ffcells[id_cell_type] = make_pair(id_d_port_name, id_q_port_name); continue; } if (args[argidx] == "-minlen" && argidx+1 < args.size()) { opts.minlen = atoi(args[++argidx].c_str()); continue; } if (args[argidx] == "-maxlen" && argidx+1 < args.size()) { opts.maxlen = atoi(args[++argidx].c_str()); continue; } if (args[argidx] == "-keep_before" && argidx+1 < args.size()) { opts.keep_before = atoi(args[++argidx].c_str()); continue; } if (args[argidx] == "-keep_after" && argidx+1 < args.size()) { opts.keep_after = atoi(args[++argidx].c_str()); continue; } if (args[argidx] == "-tech" && argidx+1 < args.size() && opts.tech == nullptr) { string tech = args[++argidx]; if (tech == "greenpak4") { clkpol = "pos"; opts.zinit = true; opts.tech = new ShregmapTechGreenpak4; } else if (tech == "xilinx") { opts.init = true; opts.params = true; enpol = "any_or_none"; opts.tech = new ShregmapTechXilinx7(opts); } else { argidx--; break; } continue; } if (args[argidx] == "-zinit") { opts.zinit = true; continue; } if (args[argidx] == "-init") { opts.init = true; continue; } if (args[argidx] == "-params") { opts.params = true; continue; } break; } extra_args(args, argidx, design); if (opts.zinit && opts.init) log_cmd_error("Options -zinit and -init are exclusive!\n"); if (opts.ffcells.empty()) { bool clk_pos = clkpol == "" || clkpol == "pos" || clkpol == "any"; bool clk_neg = clkpol == "" || clkpol == "neg" || clkpol == "any"; bool en_none = enpol == "" || enpol == "none" || enpol == "any_or_none"; bool en_pos = enpol == "pos" || enpol == "any" || enpol == "any_or_none"; bool en_neg = enpol == "neg" || enpol == "any" || enpol == "any_or_none"; if (clk_pos && en_none) opts.ffcells[ID($_DFF_P_)] = make_pair(IdString(ID(D)), IdString(ID(Q))); if (clk_neg && en_none) opts.ffcells[ID($_DFF_N_)] = make_pair(IdString(ID(D)), IdString(ID(Q))); if (clk_pos && en_pos) opts.ffcells[ID($_DFFE_PP_)] = make_pair(IdString(ID(D)), IdString(ID(Q))); if (clk_pos && en_neg) opts.ffcells[ID($_DFFE_PN_)] = make_pair(IdString(ID(D)), IdString(ID(Q))); if (clk_neg && en_pos) opts.ffcells[ID($_DFFE_NP_)] = make_pair(IdString(ID(D)), IdString(ID(Q))); if (clk_neg && en_neg) opts.ffcells[ID($_DFFE_NN_)] = make_pair(IdString(ID(D)), IdString(ID(Q))); if (en_pos || en_neg) opts.ffe = true; } else { if (!clkpol.empty()) log_cmd_error("Options -clkpol and -match are exclusive!\n"); if (!enpol.empty()) log_cmd_error("Options -enpol and -match are exclusive!\n"); if (opts.params) log_cmd_error("Options -params and -match are exclusive!\n"); } int dff_count = 0; int shreg_count = 0; for (auto module : design->selected_modules()) { ShregmapWorker worker(module, opts); dff_count += worker.dff_count; shreg_count += worker.shreg_count; } log("Converted %d dff cells into %d shift registers.\n", dff_count, shreg_count); if (opts.tech != nullptr) { delete opts.tech; opts.tech = nullptr; } } } ShregmapPass; PRIVATE_NAMESPACE_END