/* * 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" #include "kernel/celltypes.h" USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN struct opts_t { bool initeq = false; bool anyeq = false; bool fwd = false; bool bwd = false; bool nop = false; }; struct FmcombineWorker { const opts_t &opts; Design *design; Module *original = nullptr; Module *module = nullptr; IdString orig_type, combined_type; FmcombineWorker(Design *design, IdString orig_type, const opts_t &opts) : opts(opts), design(design), original(design->module(orig_type)), orig_type(orig_type), combined_type("$fmcombine" + orig_type.str()) { } SigSpec import_sig(SigSpec sig, const string &suffix) { SigSpec newsig; for (auto chunk : sig.chunks()) { if (chunk.wire != nullptr) chunk.wire = module->wire(chunk.wire->name.str() + suffix); newsig.append(chunk); } return newsig; } Cell *import_prim_cell(Cell *cell, const string &suffix) { Cell *c = module->addCell(cell->name.str() + suffix, cell->type); c->parameters = cell->parameters; c->attributes = cell->attributes; for (auto &conn : cell->connections()) c->setPort(conn.first, import_sig(conn.second, suffix)); return c; } void import_hier_cell(Cell *cell) { if (!cell->parameters.empty()) log_cmd_error("Cell %s.%s has unresolved instance parameters.\n", log_id(original), log_id(cell)); FmcombineWorker sub_worker(design, cell->type, opts); sub_worker.generate(); Cell *c = module->addCell(cell->name.str() + "_combined", sub_worker.combined_type); // c->parameters = cell->parameters; c->attributes = cell->attributes; for (auto &conn : cell->connections()) { c->setPort(conn.first.str() + "_gold", import_sig(conn.second, "_gold")); c->setPort(conn.first.str() + "_gate", import_sig(conn.second, "_gate")); } } void generate() { if (design->module(combined_type)) { // log("Combined module %s already exists.\n", log_id(combined_type)); return; } log("Generating combined module %s from module %s.\n", log_id(combined_type), log_id(orig_type)); module = design->addModule(combined_type); for (auto wire : original->wires()) { module->addWire(wire->name.str() + "_gold", wire); module->addWire(wire->name.str() + "_gate", wire); } module->fixup_ports(); for (auto cell : original->cells()) { if (design->module(cell->type) == nullptr) { if (opts.anyeq && cell->type.in("$anyseq", "$anyconst")) { Cell *gold = import_prim_cell(cell, "_gold"); for (auto &conn : cell->connections()) module->connect(import_sig(conn.second, "_gate"), gold->getPort(conn.first)); } else { Cell *gold = import_prim_cell(cell, "_gold"); Cell *gate = import_prim_cell(cell, "_gate"); if (opts.initeq) { if (cell->type.in("$ff", "$dff", "$dffe", "$dffsr", "$adff", "$dlatch", "$dlatchsr")) { SigSpec gold_q = gold->getPort("\\Q"); SigSpec gate_q = gate->getPort("\\Q"); SigSpec en = module->Initstate(NEW_ID); SigSpec eq = module->Eq(NEW_ID, gold_q, gate_q); module->addAssume(NEW_ID, eq, en); } } } } else { import_hier_cell(cell); } } for (auto &conn : original->connections()) { module->connect(import_sig(conn.first, "_gold"), import_sig(conn.second, "_gold")); module->connect(import_sig(conn.first, "_gate"), import_sig(conn.second, "_gate")); } if (opts.nop) return; CellTypes ct; ct.setup_internals_eval(); ct.setup_stdcells_eval(); SigMap sigmap(module); dict data_bit_to_eq_net; dict cell_to_eq_nets; dict reduce_db; dict invert_db; for (auto cell : original->cells()) { if (!ct.cell_known(cell->type)) continue; for (auto &conn : cell->connections()) { if (!cell->output(conn.first)) continue; SigSpec A = import_sig(conn.second, "_gold"); SigSpec B = import_sig(conn.second, "_gate"); SigBit EQ = module->Eq(NEW_ID, A, B); for (auto bit : sigmap({A, B})) data_bit_to_eq_net[bit] = EQ; cell_to_eq_nets[cell].append(EQ); } } for (auto cell : original->cells()) { if (!ct.cell_known(cell->type)) continue; bool skip_cell = !cell_to_eq_nets.count(cell); pool src_eq_bits; for (auto &conn : cell->connections()) { if (skip_cell) break; if (cell->output(conn.first)) continue; SigSpec A = import_sig(conn.second, "_gold"); SigSpec B = import_sig(conn.second, "_gate"); for (auto bit : sigmap({A, B})) { if (data_bit_to_eq_net.count(bit)) src_eq_bits.insert(data_bit_to_eq_net.at(bit)); else skip_cell = true; } } if (!skip_cell) { SigSpec antecedent = SigSpec(src_eq_bits); antecedent.sort_and_unify(); if (GetSize(antecedent) > 1) { if (reduce_db.count(antecedent) == 0) reduce_db[antecedent] = module->ReduceAnd(NEW_ID, antecedent); antecedent = reduce_db.at(antecedent); } SigSpec consequent = cell_to_eq_nets.at(cell); consequent.sort_and_unify(); if (GetSize(consequent) > 1) { if (reduce_db.count(consequent) == 0) reduce_db[consequent] = module->ReduceAnd(NEW_ID, consequent); consequent = reduce_db.at(consequent); } if (opts.fwd) module->addAssume(NEW_ID, consequent, antecedent); if (opts.bwd) { if (invert_db.count(antecedent) == 0) invert_db[antecedent] = module->Not(NEW_ID, antecedent); if (invert_db.count(consequent) == 0) invert_db[consequent] = module->Not(NEW_ID, consequent); module->addAssume(NEW_ID, invert_db.at(antecedent), invert_db.at(consequent)); } } } } }; struct FmcombinePass : public Pass { FmcombinePass() : Pass("fmcombine", "combine two instances of a cell into one") { } void help() YS_OVERRIDE { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); log(" fmcombine [options] module_name gold_cell gate_cell\n"); // log(" fmcombine [options] @gold_cell @gate_cell\n"); log("\n"); log("This pass takes two cells, which are instances of the same module, and replaces\n"); log("them with one instance of a special 'combined' module, that effectively\n"); log("contains two copies of the original module, plus some formal properties.\n"); log("\n"); log("This is useful for formal test benches that check what differences in behavior\n"); log("a slight difference in input causes in a module.\n"); log("\n"); log(" -initeq\n"); log(" Insert assumptions that initially all FFs in both circuits have the\n"); log(" same initial values.\n"); log("\n"); log(" -anyeq\n"); log(" Do not duplicate $anyseq/$anyconst cells.\n"); log("\n"); log(" -fwd\n"); log(" Insert forward hint assumptions into the combined module.\n"); log("\n"); log(" -bwd\n"); log(" Insert backward hint assumptions into the combined module.\n"); log(" (Backward hints are logically equivalend to fordward hits, but\n"); log(" some solvers are faster with bwd hints, or even both -bwd and -fwd.)\n"); log("\n"); log(" -nop\n"); log(" Don't insert hint assumptions into the combined module.\n"); log(" (This should not provide any speedup over the original design, but\n"); log(" strangely sometimes it does.)\n"); log("\n"); log("If none of -fwd, -bwd, and -nop is given, then -fwd is used as default.\n"); log("\n"); } void execute(std::vector args, RTLIL::Design *design) YS_OVERRIDE { opts_t opts; Module *module = nullptr; Cell *gold_cell = nullptr; Cell *gate_cell = nullptr; log_header(design, "Executing FMCOMBINE pass.\n"); size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) { // if (args[argidx] == "-o" && argidx+1 < args.size()) { // filename = args[++argidx]; // continue; // } if (args[argidx] == "-initeq") { opts.initeq = true; continue; } if (args[argidx] == "-anyeq") { opts.anyeq = true; continue; } if (args[argidx] == "-fwd") { opts.fwd = true; continue; } if (args[argidx] == "-bwd") { opts.bwd = true; continue; } if (args[argidx] == "-nop") { opts.nop = true; continue; } break; } if (argidx+2 == args.size()) { string gold_name = args[argidx++]; string gate_name = args[argidx++]; log_cmd_error("fmcombine @gold_cell @gate_cell call style is not implemented yet."); } else if (argidx+3 == args.size()) { IdString module_name = RTLIL::escape_id(args[argidx++]); IdString gold_name = RTLIL::escape_id(args[argidx++]); IdString gate_name = RTLIL::escape_id(args[argidx++]); module = design->module(module_name); if (module == nullptr) log_cmd_error("Module %s not found.\n", log_id(module_name)); gold_cell = module->cell(gold_name); if (gold_cell == nullptr) log_cmd_error("Gold cell %s not found in module %s.\n", log_id(gold_name), log_id(module)); gate_cell = module->cell(gate_name); if (gate_cell == nullptr) log_cmd_error("Gold cell %s not found in module %s.\n", log_id(gate_name), log_id(module)); } else { log_cmd_error("Invalid number of arguments.\n"); } // extra_args(args, argidx, design); if (opts.nop && (opts.fwd || opts.bwd)) log_cmd_error("Option -nop can not be combined with -fwd and/or -bwd.\n"); if (!opts.nop && !opts.fwd && !opts.bwd) opts.fwd = true; if (gold_cell->type != gate_cell->type) log_cmd_error("Types of gold and gate cells do not match.\n"); if (!gold_cell->parameters.empty()) log_cmd_error("Gold cell has unresolved instance parameters.\n"); if (!gate_cell->parameters.empty()) log_cmd_error("Gold cell has unresolved instance parameters.\n"); FmcombineWorker worker(design, gold_cell->type, opts); worker.generate(); IdString combined_cell_name = module->uniquify(stringf("\\%s_%s", log_id(gold_cell), log_id(gate_cell))); Cell *cell = module->addCell(combined_cell_name, worker.combined_type); cell->attributes = gold_cell->attributes; cell->add_strpool_attribute("\\src", gate_cell->get_strpool_attribute("\\src")); log("Combining cells %s and %s in module %s into new cell %s.\n", log_id(gold_cell), log_id(gate_cell), log_id(module), log_id(cell)); for (auto &conn : gold_cell->connections()) cell->setPort(conn.first.str() + "_gold", conn.second); module->remove(gold_cell); for (auto &conn : gate_cell->connections()) cell->setPort(conn.first.str() + "_gate", conn.second); module->remove(gate_cell); } } FmcombinePass; PRIVATE_NAMESPACE_END