2019-03-15 14:18:38 -05:00
|
|
|
/*
|
|
|
|
* 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
|
|
|
|
|
2019-03-15 15:45:37 -05:00
|
|
|
struct opts_t
|
|
|
|
{
|
2019-05-11 02:28:55 -05:00
|
|
|
bool initeq = false;
|
|
|
|
bool anyeq = false;
|
2019-03-15 15:45:37 -05:00
|
|
|
bool fwd = false;
|
|
|
|
bool bwd = false;
|
|
|
|
bool nop = false;
|
|
|
|
};
|
|
|
|
|
2019-03-15 14:18:38 -05:00
|
|
|
struct FmcombineWorker
|
|
|
|
{
|
2019-03-15 15:45:37 -05:00
|
|
|
const opts_t &opts;
|
2019-03-15 14:18:38 -05:00
|
|
|
Design *design;
|
|
|
|
Module *original = nullptr;
|
|
|
|
Module *module = nullptr;
|
|
|
|
IdString orig_type, combined_type;
|
|
|
|
|
2019-03-15 15:45:37 -05:00
|
|
|
FmcombineWorker(Design *design, IdString orig_type, const opts_t &opts) :
|
|
|
|
opts(opts), design(design), original(design->module(orig_type)),
|
2019-03-15 14:18:38 -05:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2019-05-11 02:28:55 -05:00
|
|
|
Cell *import_prim_cell(Cell *cell, const string &suffix)
|
2019-03-15 14:18:38 -05:00
|
|
|
{
|
|
|
|
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));
|
2019-05-11 02:28:55 -05:00
|
|
|
|
|
|
|
return c;
|
2019-03-15 14:18:38 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
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));
|
|
|
|
|
2019-03-15 15:45:37 -05:00
|
|
|
FmcombineWorker sub_worker(design, cell->type, opts);
|
2019-03-15 14:18:38 -05:00
|
|
|
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) {
|
2019-05-11 02:28:55 -05:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-03-15 14:18:38 -05:00
|
|
|
} 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"));
|
|
|
|
}
|
|
|
|
|
2019-03-15 15:45:37 -05:00
|
|
|
if (opts.nop)
|
|
|
|
return;
|
|
|
|
|
2019-03-15 14:18:38 -05:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2019-03-15 15:45:37 -05:00
|
|
|
if (opts.fwd)
|
|
|
|
module->addAssume(NEW_ID, consequent, antecedent);
|
2019-03-15 14:18:38 -05:00
|
|
|
|
2019-03-15 15:45:37 -05:00
|
|
|
if (opts.bwd)
|
|
|
|
{
|
|
|
|
if (invert_db.count(antecedent) == 0)
|
|
|
|
invert_db[antecedent] = module->Not(NEW_ID, antecedent);
|
2019-03-15 14:18:38 -05:00
|
|
|
|
2019-03-15 15:45:37 -05:00
|
|
|
if (invert_db.count(consequent) == 0)
|
|
|
|
invert_db[consequent] = module->Not(NEW_ID, consequent);
|
2019-03-15 14:18:38 -05:00
|
|
|
|
2019-03-15 15:45:37 -05:00
|
|
|
module->addAssume(NEW_ID, invert_db.at(antecedent), invert_db.at(consequent));
|
|
|
|
}
|
2019-03-15 14:18:38 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
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");
|
2019-05-11 02:28:55 -05:00
|
|
|
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");
|
2019-03-15 15:45:37 -05:00
|
|
|
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");
|
2019-03-15 14:18:38 -05:00
|
|
|
}
|
|
|
|
void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE
|
|
|
|
{
|
2019-03-15 15:45:37 -05:00
|
|
|
opts_t opts;
|
2019-03-15 14:18:38 -05:00
|
|
|
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;
|
|
|
|
// }
|
2019-05-11 02:28:55 -05:00
|
|
|
if (args[argidx] == "-initeq") {
|
|
|
|
opts.initeq = true;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (args[argidx] == "-anyeq") {
|
|
|
|
opts.anyeq = true;
|
|
|
|
continue;
|
|
|
|
}
|
2019-03-15 15:45:37 -05:00
|
|
|
if (args[argidx] == "-fwd") {
|
|
|
|
opts.fwd = true;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (args[argidx] == "-bwd") {
|
|
|
|
opts.bwd = true;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (args[argidx] == "-nop") {
|
|
|
|
opts.nop = true;
|
|
|
|
continue;
|
|
|
|
}
|
2019-03-15 14:18:38 -05:00
|
|
|
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);
|
|
|
|
|
2019-03-15 15:45:37 -05:00
|
|
|
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;
|
|
|
|
|
2019-03-15 14:18:38 -05:00
|
|
|
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");
|
|
|
|
|
2019-03-15 15:45:37 -05:00
|
|
|
FmcombineWorker worker(design, gold_cell->type, opts);
|
2019-03-15 14:18:38 -05:00
|
|
|
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
|