| /* |
| * 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/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<SigBit, SigBit> data_bit_to_eq_net; |
| dict<Cell*, SigSpec> cell_to_eq_nets; |
| dict<SigSpec, SigSpec> reduce_db; |
| dict<SigSpec, SigSpec> 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<SigBit> 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<std::string> 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("Gate 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("Gate 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 |