diff options
Diffstat (limited to 'passes')
62 files changed, 5311 insertions, 712 deletions
diff --git a/passes/cmds/Makefile.inc b/passes/cmds/Makefile.inc index 16a38b511..29b3a1132 100644 --- a/passes/cmds/Makefile.inc +++ b/passes/cmds/Makefile.inc @@ -7,12 +7,14 @@ OBJS += passes/cmds/delete.o OBJS += passes/cmds/design.o OBJS += passes/cmds/select.o OBJS += passes/cmds/show.o +OBJS += passes/cmds/viz.o OBJS += passes/cmds/rename.o OBJS += passes/cmds/autoname.o OBJS += passes/cmds/connect.o OBJS += passes/cmds/scatter.o OBJS += passes/cmds/setundef.o OBJS += passes/cmds/splitnets.o +OBJS += passes/cmds/splitcells.o OBJS += passes/cmds/stat.o OBJS += passes/cmds/setattr.o OBJS += passes/cmds/copy.o @@ -43,3 +45,4 @@ OBJS += passes/cmds/logger.o OBJS += passes/cmds/printattrs.o OBJS += passes/cmds/sta.o OBJS += passes/cmds/clean_zerowidth.o +OBJS += passes/cmds/xprop.o diff --git a/passes/cmds/bugpoint.cc b/passes/cmds/bugpoint.cc index 7b621504d..c398afffa 100644 --- a/passes/cmds/bugpoint.cc +++ b/passes/cmds/bugpoint.cc @@ -34,10 +34,10 @@ struct BugpointPass : public Pass { log("\n"); log("This command minimizes the current design that is known to crash Yosys with the\n"); log("given script into a smaller testcase. It does this by removing an arbitrary part\n"); - log("of the design and recursively invokes a new Yosys process with this modified design\n"); - log("and the same script, repeating these steps while it can find a smaller design that\n"); - log("still causes a crash. Once this command finishes, it replaces the current design\n"); - log("with the smallest testcase it was able to produce.\n"); + log("of the design and recursively invokes a new Yosys process with this modified\n"); + log("design and the same script, repeating these steps while it can find a smaller\n"); + log("design that still causes a crash. Once this command finishes, it replaces the\n"); + log("current design with the smallest testcase it was able to produce.\n"); log("In order to save the reduced testcase you must write this out to a file with\n"); log("another command after `bugpoint` like `write_rtlil` or `write_verilog`.\n"); log("\n"); @@ -393,6 +393,7 @@ struct BugpointPass : public Pass { } } } + delete design_copy; return nullptr; } diff --git a/passes/cmds/chformal.cc b/passes/cmds/chformal.cc index d813a449c..66044b161 100644 --- a/passes/cmds/chformal.cc +++ b/passes/cmds/chformal.cc @@ -32,8 +32,8 @@ struct ChformalPass : public Pass { log(" chformal [types] [mode] [options] [selection]\n"); log("\n"); log("Make changes to the formal constraints of the design. The [types] options\n"); - log("the type of constraint to operate on. If none of the following options are given,\n"); - log("the command will operate on all constraint types:\n"); + log("the type of constraint to operate on. If none of the following options are\n"); + log("given, the command will operate on all constraint types:\n"); log("\n"); log(" -assert $assert cells, representing assert(...) constraints\n"); log(" -assume $assume cells, representing assume(...) constraints\n"); diff --git a/passes/cmds/design.cc b/passes/cmds/design.cc index 169f7cc4a..168d38563 100644 --- a/passes/cmds/design.cc +++ b/passes/cmds/design.cc @@ -118,6 +118,9 @@ struct DesignPass : public Pass { std::string save_name, load_name, as_name, delete_name; std::vector<RTLIL::Module*> copy_src_modules; + if (!design) + log_cmd_error("No default design.\n"); + size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) { @@ -280,7 +283,7 @@ struct DesignPass : public Pass { done[mod->name] = prefix; } - while (!queue.empty()) + while (!queue.empty() && copy_from_design) { pool<Module*> old_queue; old_queue.swap(queue); diff --git a/passes/cmds/exec.cc b/passes/cmds/exec.cc index f00629a02..e5fa4fb41 100644 --- a/passes/cmds/exec.cc +++ b/passes/cmds/exec.cc @@ -46,8 +46,8 @@ struct ExecPass : public Pass { log("\n"); log("Execute a command in the operating system shell. All supplied arguments are\n"); log("concatenated and passed as a command to popen(3). Whitespace is not guaranteed\n"); - log("to be preserved, even if quoted. stdin and stderr are not connected, while stdout is\n"); - log("logged unless the \"-q\" option is specified.\n"); + log("to be preserved, even if quoted. stdin and stderr are not connected, while\n"); + log("stdout is logged unless the \"-q\" option is specified.\n"); log("\n"); log("\n"); log(" -q\n"); @@ -86,7 +86,7 @@ struct ExecPass : public Pass { bool polarity; //true: this regex must match at least one line //false: this regex must not match any line std::string str; - YS_REGEX_TYPE re; + std::regex re; expect_stdout_elem() : matched(false), polarity(true), str(), re(){}; }; @@ -121,7 +121,7 @@ struct ExecPass : public Pass { x.str = args[argidx]; x.re = YS_REGEX_COMPILE(args[argidx]); expect_stdout.push_back(x); - } catch (const YS_REGEX_NS::regex_error& e) { + } catch (const std::regex_error& e) { log_cmd_error("Error in regex expression '%s' !\n", args[argidx].c_str()); } } else if (args[argidx] == "-not-expect-stdout") { @@ -136,7 +136,7 @@ struct ExecPass : public Pass { x.re = YS_REGEX_COMPILE(args[argidx]); x.polarity = false; expect_stdout.push_back(x); - } catch (const YS_REGEX_NS::regex_error& e) { + } catch (const std::regex_error& e) { log_cmd_error("Error in regex expression '%s' !\n", args[argidx].c_str()); } @@ -171,7 +171,7 @@ struct ExecPass : public Pass { if (flag_expect_stdout) for(auto &x : expect_stdout) - if (YS_REGEX_NS::regex_search(line, x.re)) + if (std::regex_search(line, x.re)) x.matched = true; pos = linebuf.find('\n'); diff --git a/passes/cmds/glift.cc b/passes/cmds/glift.cc index b398c3e04..439ded076 100644 --- a/passes/cmds/glift.cc +++ b/passes/cmds/glift.cc @@ -431,45 +431,46 @@ struct GliftPass : public Pass { log("\n"); log(" glift <command> [options] [selection]\n"); log("\n"); - log("Augments the current or specified module with gate-level information flow tracking\n"); - log("(GLIFT) logic using the \"constructive mapping\" approach. Also can set up QBF-SAT\n"); - log("optimization problems in order to optimize GLIFT models or trade off precision and\n"); - log("complexity.\n"); + log("Augments the current or specified module with gate-level information flow \n"); + log("tracking (GLIFT) logic using the \"constructive mapping\" approach. Also can set\n"); + log("up QBF-SAT optimization problems in order to optimize GLIFT models or trade off\n"); + log("precision and complexity.\n"); log("\n"); log("\n"); log("Commands:\n"); log("\n"); log(" -create-precise-model\n"); - log(" Replaces the current or specified module with one that has corresponding \"taint\"\n"); - log(" inputs, outputs, and internal nets along with precise taint tracking logic.\n"); - log(" For example, precise taint tracking logic for an AND gate is:\n"); + log(" Replaces the current or specified module with one that has corresponding\n"); + log(" \"taint\" inputs, outputs, and internal nets along with precise taint\n"); + log(" tracking logic. For example, precise taint tracking logic for an AND gate\n"); + log(" is:\n"); log("\n"); log(" y_t = a & b_t | b & a_t | a_t & b_t\n"); log("\n"); log("\n"); log(" -create-imprecise-model\n"); - log(" Replaces the current or specified module with one that has corresponding \"taint\"\n"); - log(" inputs, outputs, and internal nets along with imprecise \"All OR\" taint tracking\n"); - log(" logic:\n"); + log(" Replaces the current or specified module with one that has corresponding\n"); + log(" \"taint\" inputs, outputs, and internal nets along with imprecise \"All OR\"\n"); + log(" taint tracking logic:\n"); log("\n"); log(" y_t = a_t | b_t\n"); log("\n"); log("\n"); log(" -create-instrumented-model\n"); - log(" Replaces the current or specified module with one that has corresponding \"taint\"\n"); - log(" inputs, outputs, and internal nets along with 4 varying-precision versions of taint\n"); - log(" tracking logic. Which version of taint tracking logic is used for a given gate is\n"); - log(" determined by a MUX selected by an $anyconst cell. By default, unless the\n"); - log(" `-no-cost-model` option is provided, an additional wire named `__glift_weight` with\n"); - log(" the `keep` and `minimize` attributes is added to the module along with pmuxes and\n"); - log(" adders to calculate a rough estimate of the number of logic gates in the GLIFT model\n"); - log(" given an assignment for the $anyconst cells. The four versions of taint tracking logic\n"); - log(" for an AND gate are:"); - log("\n"); - log(" y_t = a & b_t | b & a_t | a_t & b_t (like `-create-precise-model`)\n"); + log(" Replaces the current or specified module with one that has corresponding\n"); + log(" \"taint\" inputs, outputs, and internal nets along with 4 varying-precision\n"); + log(" versions of taint tracking logic. Which version of taint tracking logic is\n"); + log(" used for a given gate is determined by a MUX selected by an $anyconst cell.\n"); + log(" By default, unless the `-no-cost-model` option is provided, an additional\n"); + log(" wire named `__glift_weight` with the `keep` and `minimize` attributes is\n"); + log(" added to the module along with pmuxes and adders to calculate a rough\n"); + log(" estimate of the number of logic gates in the GLIFT model given an assignment\n"); + log(" for the $anyconst cells. The four versions of taint tracking logic for an\n"); + log(" AND gate are:\n"); + log(" y_t = a & b_t | b & a_t | a_t & b_t (like `-create-precise-model`)\n"); log(" y_t = a_t | a & b_t\n"); log(" y_t = b_t | b & a_t\n"); - log(" y_t = a_t | b_t (like `-create-imprecise-model`)\n"); + log(" y_t = a_t | b_t (like `-create-imprecise-model`)\n"); log("\n"); log("\n"); log("Options:\n"); @@ -479,27 +480,30 @@ struct GliftPass : public Pass { log(" (default: label constants as un-tainted)\n"); log("\n"); log(" -keep-outputs\n"); - log(" Do not remove module outputs. Taint tracking outputs will appear in the module ports\n"); - log(" alongside the orignal outputs.\n"); + log(" Do not remove module outputs. Taint tracking outputs will appear in the\n"); + log(" module ports alongside the orignal outputs.\n"); log(" (default: original module outputs are removed)\n"); log("\n"); log(" -simple-cost-model\n"); - log(" Do not model logic area. Instead model the number of non-zero assignments to $anyconsts.\n"); - log(" Taint tracking logic versions vary in their size, but all reduced-precision versions are\n"); - log(" significantly smaller than the fully-precise version. A non-zero $anyconst assignment means\n"); - log(" that reduced-precision taint tracking logic was chosen for some gate.\n"); - log(" Only applicable in combination with `-create-instrumented-model`.\n"); - log(" (default: use a complex model and give that wire the \"keep\" and \"minimize\" attributes)\n"); + log(" Do not model logic area. Instead model the number of non-zero assignments to\n"); + log(" $anyconsts. Taint tracking logic versions vary in their size, but all\n"); + log(" reduced-precision versions are significantly smaller than the fully-precise\n"); + log(" version. A non-zero $anyconst assignment means that reduced-precision taint\n"); + log(" tracking logic was chosen for some gate. Only applicable in combination with\n"); + log(" `-create-instrumented-model`. (default: use a complex model and give that\n"); + log(" wire the \"keep\" and \"minimize\" attributes)\n"); log("\n"); log(" -no-cost-model\n"); - log(" Do not model taint tracking logic area and do not create a `__glift_weight` wire.\n"); - log(" Only applicable in combination with `-create-instrumented-model`.\n"); - log(" (default: model area and give that wire the \"keep\" and \"minimize\" attributes)\n"); + log(" Do not model taint tracking logic area and do not create a `__glift_weight`\n"); + log(" wire. Only applicable in combination with `-create-instrumented-model`.\n"); + log(" (default: model area and give that wire the \"keep\" and \"minimize\"\n"); + log(" attributes)\n"); log("\n"); log(" -instrument-more\n"); - log(" Allow choice from more versions of (even simpler) taint tracking logic. A total\n"); - log(" of 8 versions of taint tracking logic will be added per gate, including the 4\n"); - log(" versions from `-create-instrumented-model` and these additional versions:\n"); + log(" Allow choice from more versions of (even simpler) taint tracking logic. A\n"); + log(" total of 8 versions of taint tracking logic will be added per gate,\n"); + log(" including the 4 versions from `-create-instrumented-model` and these\n"); + log(" additional versions:\n"); log("\n"); log(" y_t = a_t\n"); log(" y_t = b_t\n"); diff --git a/passes/cmds/logcmd.cc b/passes/cmds/logcmd.cc index f1702400d..20cbd8943 100644 --- a/passes/cmds/logcmd.cc +++ b/passes/cmds/logcmd.cc @@ -38,8 +38,9 @@ struct LogPass : public Pass { log("logfiles.\n"); log("\n"); log(" -stdout\n"); - log(" Print the output to stdout too. This is useful when all Yosys is executed\n"); - log(" with a script and the -q (quiet operation) argument to notify the user.\n"); + log(" Print the output to stdout too. This is useful when all Yosys is\n"); + log(" executed with a script and the -q (quiet operation) argument to notify\n"); + log(" the user.\n"); log("\n"); log(" -stderr\n"); log(" Print the output to stderr too.\n"); diff --git a/passes/cmds/logger.cc b/passes/cmds/logger.cc index ec92f1d01..9e45e86af 100644 --- a/passes/cmds/logger.cc +++ b/passes/cmds/logger.cc @@ -104,7 +104,7 @@ struct LoggerPass : public Pass { log("Added regex '%s' for warnings to warn list.\n", pattern.c_str()); log_warn_regexes.push_back(YS_REGEX_COMPILE(pattern)); } - catch (const YS_REGEX_NS::regex_error& e) { + catch (const std::regex_error& e) { log_cmd_error("Error in regex expression '%s' !\n", pattern.c_str()); } continue; @@ -116,7 +116,7 @@ struct LoggerPass : public Pass { log("Added regex '%s' for warnings to nowarn list.\n", pattern.c_str()); log_nowarn_regexes.push_back(YS_REGEX_COMPILE(pattern)); } - catch (const YS_REGEX_NS::regex_error& e) { + catch (const std::regex_error& e) { log_cmd_error("Error in regex expression '%s' !\n", pattern.c_str()); } continue; @@ -128,7 +128,7 @@ struct LoggerPass : public Pass { log("Added regex '%s' for warnings to werror list.\n", pattern.c_str()); log_werror_regexes.push_back(YS_REGEX_COMPILE(pattern)); } - catch (const YS_REGEX_NS::regex_error& e) { + catch (const std::regex_error& e) { log_cmd_error("Error in regex expression '%s' !\n", pattern.c_str()); } continue; @@ -172,7 +172,7 @@ struct LoggerPass : public Pass { log_expect_log[pattern] = LogExpectedItem(YS_REGEX_COMPILE(pattern), count); else log_abort(); } - catch (const YS_REGEX_NS::regex_error& e) { + catch (const std::regex_error& e) { log_cmd_error("Error in regex expression '%s' !\n", pattern.c_str()); } continue; diff --git a/passes/cmds/rename.cc b/passes/cmds/rename.cc index 81da35ffe..45576c91c 100644 --- a/passes/cmds/rename.cc +++ b/passes/cmds/rename.cc @@ -20,6 +20,7 @@ #include "kernel/register.h" #include "kernel/rtlil.h" #include "kernel/log.h" +#include "kernel/hashlib.h" USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN @@ -105,6 +106,60 @@ static IdString derive_name_from_cell_output_wire(const RTLIL::Cell *cell, strin return name + suffix; } +static bool rename_witness(RTLIL::Design *design, dict<RTLIL::Module *, int> &cache, RTLIL::Module *module) +{ + auto cached = cache.find(module); + if (cached != cache.end()) { + if (cached->second == -1) + log_error("Cannot rename witness signals in a design containing recursive instantiations.\n"); + return cached->second; + } + cache.emplace(module, -1); + + bool has_witness_signals = false; + for (auto cell : module->cells()) + { + RTLIL::Module *impl = design->module(cell->type); + if (impl != nullptr) { + bool witness_in_cell = rename_witness(design, cache, impl); + has_witness_signals |= witness_in_cell; + if (witness_in_cell && !cell->name.isPublic()) { + std::string name = cell->name.c_str() + 1; + for (auto &c : name) + if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c < '0' || c > '9') && c != '_') + c = '_'; + auto new_id = module->uniquify("\\_witness_." + name); + cell->set_hdlname_attribute({ "_witness_", strstr(new_id.c_str(), ".") + 1 }); + module->rename(cell, new_id); + } + } + + if (cell->type.in(ID($anyconst), ID($anyseq), ID($anyinit), ID($allconst), ID($allseq))) { + has_witness_signals = true; + auto QY = cell->type == ID($anyinit) ? ID::Q : ID::Y; + auto sig_out = cell->getPort(QY); + + for (auto chunk : sig_out.chunks()) { + if (chunk.is_wire() && !chunk.wire->name.isPublic()) { + std::string name = stringf("%s_%s", cell->type.c_str() + 1, cell->name.c_str() + 1); + for (auto &c : name) + if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c < '0' || c > '9') && c != '_') + c = '_'; + auto new_id = module->uniquify("\\_witness_." + name); + auto new_wire = module->addWire(new_id, GetSize(sig_out)); + new_wire->set_hdlname_attribute({ "_witness_", strstr(new_id.c_str(), ".") + 1 }); + module->connect({sig_out, new_wire}); + cell->setPort(QY, new_wire); + break; + } + } + } + } + + cache[module] = has_witness_signals; + return has_witness_signals; +} + struct RenamePass : public Pass { RenamePass() : Pass("rename", "rename object in the design") { } void help() override @@ -146,6 +201,14 @@ struct RenamePass : public Pass { log("pattern is '_%%_'.\n"); log("\n"); log("\n"); + log(" rename -witness\n"); + log("\n"); + log("Assigns auto-generated names to all $any*/$all* output wires and containing\n"); + log("cells that do not have a public name. This ensures that, during formal\n"); + log("verification, a solver-found trace can be fully specified using a public\n"); + log("hierarchical names.\n"); + log("\n"); + log("\n"); log(" rename -hide [selection]\n"); log("\n"); log("Assign private names (the ones with $-prefix) to all selected wires and cells\n"); @@ -156,6 +219,13 @@ struct RenamePass : public Pass { log("\n"); log("Rename top module.\n"); log("\n"); + log("\n"); + log(" rename -scramble-name [-seed <seed>] [selection]\n"); + log("\n"); + log("Assign randomly-generated names to all selected wires and cells. The seed option\n"); + log("can be used to change the random number generator seed from the default, but it\n"); + log("must be non-zero.\n"); + log("\n"); } void execute(std::vector<std::string> args, RTLIL::Design *design) override { @@ -164,10 +234,13 @@ struct RenamePass : public Pass { bool flag_src = false; bool flag_wire = false; bool flag_enumerate = false; + bool flag_witness = false; bool flag_hide = false; bool flag_top = false; bool flag_output = false; + bool flag_scramble_name = false; bool got_mode = false; + unsigned int seed = 1; size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) @@ -193,6 +266,11 @@ struct RenamePass : public Pass { got_mode = true; continue; } + if (arg == "-witness" && !got_mode) { + flag_witness = true; + got_mode = true; + continue; + } if (arg == "-hide" && !got_mode) { flag_hide = true; got_mode = true; @@ -203,6 +281,11 @@ struct RenamePass : public Pass { got_mode = true; continue; } + if (arg == "-scramble-name" && !got_mode) { + flag_scramble_name = true; + got_mode = true; + continue; + } if (arg == "-pattern" && argidx+1 < args.size() && args[argidx+1].find('%') != std::string::npos) { int pos = args[++argidx].find('%'); pattern_prefix = args[argidx].substr(0, pos); @@ -211,6 +294,11 @@ struct RenamePass : public Pass { } if (arg == "-suffix" && argidx + 1 < args.size()) { cell_suffix = args[++argidx]; + continue; + } + if (arg == "-seed" && argidx+1 < args.size()) { + seed = std::stoi(args[++argidx]); + continue; } break; } @@ -289,6 +377,19 @@ struct RenamePass : public Pass { } } else + if (flag_witness) + { + extra_args(args, argidx, design, false); + + RTLIL::Module *module = design->top_module(); + + if (module == nullptr) + log_cmd_error("No top module found!\n"); + + dict<RTLIL::Module *, int> cache; + rename_witness(design, cache, module); + } + else if (flag_hide) { extra_args(args, argidx, design); @@ -329,6 +430,42 @@ struct RenamePass : public Pass { design->rename(module, new_name); } else + if (flag_scramble_name) + { + extra_args(args, argidx, design); + + if (seed == 0) + log_error("Seed for -scramble-name cannot be zero.\n"); + + for (auto module : design->selected_modules()) + { + if (module->memories.size() != 0 || module->processes.size() != 0) { + log_warning("Skipping module %s with unprocessed memories or processes\n", log_id(module)); + continue; + } + + dict<RTLIL::Wire *, IdString> new_wire_names; + dict<RTLIL::Cell *, IdString> new_cell_names; + + for (auto wire : module->selected_wires()) + if (wire->port_id == 0) { + seed = mkhash_xorshift(seed); + new_wire_names[wire] = stringf("$_%u_", seed); + } + + for (auto cell : module->selected_cells()) { + seed = mkhash_xorshift(seed); + new_cell_names[cell] = stringf("$_%u_", seed); + } + + for (auto &it : new_wire_names) + module->rename(it.first, it.second); + + for (auto &it : new_cell_names) + module->rename(it.first, it.second); + } + } + else { if (argidx+2 != args.size()) log_cmd_error("Invalid number of arguments!\n"); diff --git a/passes/cmds/scratchpad.cc b/passes/cmds/scratchpad.cc index 015eb97e7..aecc4c17d 100644 --- a/passes/cmds/scratchpad.cc +++ b/passes/cmds/scratchpad.cc @@ -49,7 +49,8 @@ struct ScratchpadPass : public Pass { log(" copy the value of the first identifier to the second identifier.\n"); log("\n"); log(" -assert <identifier> <value>\n"); - log(" assert that the entry for the given identifier is set to the given value.\n"); + log(" assert that the entry for the given identifier is set to the given\n"); + log(" value.\n"); log("\n"); log(" -assert-set <identifier>\n"); log(" assert that the entry for the given identifier exists.\n"); diff --git a/passes/cmds/select.cc b/passes/cmds/select.cc index b112b145c..03d00816e 100644 --- a/passes/cmds/select.cc +++ b/passes/cmds/select.cc @@ -1125,7 +1125,7 @@ struct SelectPass : public Pass { log(" <obj_pattern>\n"); log(" select the specified object(s) from the current module\n"); log("\n"); - log("By default, patterns will not match black/white-box modules or their"); + log("By default, patterns will not match black/white-box modules or their\n"); log("contents. To include such objects, prefix the pattern with '='.\n"); log("\n"); log("A <mod_pattern> can be a module name, wildcard expression (*, ?, [..])\n"); @@ -1607,12 +1607,14 @@ struct CdPass : public Pass { log("with the specified name in the current module, then this is equivalent\n"); log("to 'cd <celltype>'.\n"); log("\n"); + log("\n"); log(" cd ..\n"); log("\n"); log("Remove trailing substrings that start with '.' in current module name until\n"); log("the name of a module in the current design is generated, then switch to that\n"); log("module. Otherwise clear the current selection.\n"); log("\n"); + log("\n"); log(" cd\n"); log("\n"); log("This is just a shortcut for 'select -clear'.\n"); diff --git a/passes/cmds/setundef.cc b/passes/cmds/setundef.cc index a078b0b1c..7293002f3 100644 --- a/passes/cmds/setundef.cc +++ b/passes/cmds/setundef.cc @@ -20,6 +20,7 @@ #include "kernel/register.h" #include "kernel/celltypes.h" #include "kernel/sigtools.h" +#include "kernel/mem.h" #include "kernel/rtlil.h" #include "kernel/log.h" @@ -478,7 +479,38 @@ struct SetundefPass : public Pass { log_assert(ffbits.empty()); } - module->rewrite_sigspecs(worker); + if (worker.next_bit_mode == MODE_ANYSEQ || worker.next_bit_mode == MODE_ANYCONST) + { + // Do not add anyseq / anyconst to unused memory port clocks + std::vector<Mem> memories = Mem::get_selected_memories(module); + for (auto &mem : memories) { + bool changed = false; + for (auto &rd_port : mem.rd_ports) { + if (!rd_port.clk_enable && rd_port.clk.is_fully_undef()) { + changed = true; + rd_port.clk = State::S0; + } + } + for (auto &wr_port : mem.rd_ports) { + if (!wr_port.clk_enable && wr_port.clk.is_fully_undef()) { + changed = true; + wr_port.clk = State::S0; + } + } + if (changed) + mem.emit(); + } + } + + for (auto &it : module->cells_) + if (!it.second->get_bool_attribute(ID::xprop_decoder)) + it.second->rewrite_sigspecs(worker); + for (auto &it : module->processes) + it.second->rewrite_sigspecs(worker); + for (auto &it : module->connections_) { + worker(it.first); + worker(it.second); + } if (worker.next_bit_mode == MODE_ANYSEQ || worker.next_bit_mode == MODE_ANYCONST) { diff --git a/passes/cmds/show.cc b/passes/cmds/show.cc index 43deba47b..dd7de8273 100644 --- a/passes/cmds/show.cc +++ b/passes/cmds/show.cc @@ -233,77 +233,101 @@ struct ShowWorker return std::string(); } + // Return the pieces of a label joined by a '|' separator + std::string join_label_pieces(std::vector<std::string> pieces) + { + std::string ret = ""; + bool first_piece = true; + + for (auto &piece : pieces) { + if (!first_piece) + ret += "|"; + ret += piece; + first_piece = false; + } + + return ret; + } + std::string gen_portbox(std::string port, RTLIL::SigSpec sig, bool driver, std::string *node = nullptr) { std::string code; std::string net = gen_signode_simple(sig); if (net.empty()) { - std::string label_string; - int pos = sig.size()-1; - int idx = single_idx_count++; - for (int rep, i = int(sig.chunks().size())-1; i >= 0; i -= rep) { - const RTLIL::SigChunk &c = sig.chunks().at(i); + int dot_idx = single_idx_count++; + std::vector<std::string> label_pieces; + int bitpos = sig.size()-1; + + for (int rep, chunk_idx = ((int) sig.chunks().size()) - 1; chunk_idx >= 0; chunk_idx -= rep) { + const RTLIL::SigChunk &c = sig.chunks().at(chunk_idx); + + // Find the number of times this chunk is repeating + for (rep = 1; chunk_idx - rep >= 0 && c == sig.chunks().at(chunk_idx - rep); rep++); + int cl, cr; - if (c.wire) { + cl = c.offset + c.width - 1; + cr = c.offset; + + if (c.is_wire()) { if (c.wire->upto) { - cr = c.wire->start_offset + (c.wire->width - c.offset - 1); + cr = (c.wire->width - 1) - c.offset; cl = cr - (c.width - 1); - } else { - cr = c.wire->start_offset + c.offset; - cl = cr + c.width - 1; } - } else { - cl = c.offset + c.width - 1; - cr = c.offset; + + cl += c.wire->start_offset; + cr += c.wire->start_offset; } - if (!driver && c.wire == nullptr) { - RTLIL::State s1 = c.data.front(); - for (auto s2 : c.data) - if (s1 != s2) - goto not_const_stream; - net.clear(); - } else { - not_const_stream: + + // Is this chunk a constant filled with one kind of bit state? + bool no_signode = !driver && !c.is_wire() \ + && std::equal(c.data.begin() + 1, c.data.end(), c.data.begin()); + + if (!no_signode) { net = gen_signode_simple(c, false); log_assert(!net.empty()); } - for (rep = 1; i-rep >= 0 && c == sig.chunks().at(i-rep); rep++) {} + std::string repinfo = rep > 1 ? stringf("%dx ", rep) : ""; + std::string portside = stringf("%d:%d", bitpos, bitpos - rep*c.width + 1); + std::string remoteside = stringf("%s%d:%d", repinfo.c_str(), cl, cr); + if (driver) { log_assert(!net.empty()); - label_string += stringf("<s%d> %d:%d - %s%d:%d |", i, pos, pos-c.width+1, repinfo.c_str(), cl, cr); - net_conn_map[net].in.insert({stringf("x%d:s%d", idx, i), rep*c.width}); + label_pieces.push_back(stringf("<s%d> %s - %s ", chunk_idx, portside.c_str(), remoteside.c_str())); + net_conn_map[net].in.insert({stringf("x%d:s%d", dot_idx, chunk_idx), rep*c.width}); net_conn_map[net].color = nextColor(c, net_conn_map[net].color); - } else - if (net.empty()) { - log_assert(rep == 1); - label_string += stringf("%c -> %d:%d |", - c.data.front() == State::S0 ? '0' : - c.data.front() == State::S1 ? '1' : - c.data.front() == State::Sx ? 'X' : - c.data.front() == State::Sz ? 'Z' : '?', - pos, pos-rep*c.width+1); } else { - label_string += stringf("<s%d> %s%d:%d - %d:%d |", i, repinfo.c_str(), cl, cr, pos, pos-rep*c.width+1); - net_conn_map[net].out.insert({stringf("x%d:s%d", idx, i), rep*c.width}); - net_conn_map[net].color = nextColor(c, net_conn_map[net].color); + if (no_signode) { + log_assert(rep == 1); + label_pieces.push_back(stringf("%c -> %d:%d ", + c.data.front() == State::S0 ? '0' : + c.data.front() == State::S1 ? '1' : + c.data.front() == State::Sx ? 'X' : + c.data.front() == State::Sz ? 'Z' : '?', + bitpos, bitpos-rep*c.width+1)); + } else { + label_pieces.push_back(stringf("<s%d> %s - %s ", chunk_idx, remoteside.c_str(), portside.c_str())); + net_conn_map[net].out.insert({stringf("x%d:s%d", dot_idx, chunk_idx), rep*c.width}); + net_conn_map[net].color = nextColor(c, net_conn_map[net].color); + } } - pos -= rep * c.width; + + bitpos -= rep * c.width; } - if (label_string[label_string.size()-1] == '|') - label_string = label_string.substr(0, label_string.size()-1); - code += stringf("x%d [ shape=record, style=rounded, label=\"%s\" ];\n", idx, label_string.c_str()); + + code += stringf("x%d [ shape=record, style=rounded, label=\"", dot_idx) \ + + join_label_pieces(label_pieces) + "\" ];\n"; + if (!port.empty()) { currentColor = xorshift32(currentColor); - log_warning("WIDTHLABEL %s %d\n", log_signal(sig), GetSize(sig)); if (driver) - code += stringf("%s:e -> x%d:w [arrowhead=odiamond, arrowtail=odiamond, dir=both, %s, %s];\n", port.c_str(), idx, nextColor(sig).c_str(), widthLabel(sig.size()).c_str()); + code += stringf("%s:e -> x%d:w [arrowhead=odiamond, arrowtail=odiamond, dir=both, %s, %s];\n", port.c_str(), dot_idx, nextColor(sig).c_str(), widthLabel(sig.size()).c_str()); else - code += stringf("x%d:e -> %s:w [arrowhead=odiamond, arrowtail=odiamond, dir=both, %s, %s];\n", idx, port.c_str(), nextColor(sig).c_str(), widthLabel(sig.size()).c_str()); + code += stringf("x%d:e -> %s:w [arrowhead=odiamond, arrowtail=odiamond, dir=both, %s, %s];\n", dot_idx, port.c_str(), nextColor(sig).c_str(), widthLabel(sig.size()).c_str()); } if (node != nullptr) - *node = stringf("x%d", idx); + *node = stringf("x%d", dot_idx); } else { @@ -418,6 +442,7 @@ struct ShowWorker for (auto cell : module->selected_cells()) { std::vector<RTLIL::IdString> in_ports, out_ports; + std::vector<std::string> in_label_pieces, out_label_pieces; for (auto &conn : cell->connections()) { if (!ct.cell_output(cell->type, conn.first)) @@ -429,23 +454,23 @@ struct ShowWorker std::sort(in_ports.begin(), in_ports.end(), RTLIL::sort_by_id_str()); std::sort(out_ports.begin(), out_ports.end(), RTLIL::sort_by_id_str()); - std::string label_string = "{{"; + for (auto &p : in_ports) { + bool signed_suffix = genSignedLabels && cell->hasParam(p.str() + "_SIGNED") + && cell->getParam(p.str() + "_SIGNED").as_bool(); - for (auto &p : in_ports) - label_string += stringf("<p%d> %s%s|", id2num(p), escape(p.str()), - genSignedLabels && cell->hasParam(p.str() + "_SIGNED") && - cell->getParam(p.str() + "_SIGNED").as_bool() ? "*" : ""); - if (label_string[label_string.size()-1] == '|') - label_string = label_string.substr(0, label_string.size()-1); - - label_string += stringf("}|%s\\n%s|{", findLabel(cell->name.str()), escape(cell->type.str())); + in_label_pieces.push_back(stringf("<p%d> %s%s", id2num(p), escape(p.str()), + signed_suffix ? "*" : "")); + } for (auto &p : out_ports) - label_string += stringf("<p%d> %s|", id2num(p), escape(p.str())); - if (label_string[label_string.size()-1] == '|') - label_string = label_string.substr(0, label_string.size()-1); + out_label_pieces.push_back(stringf("<p%d> %s", id2num(p), escape(p.str()))); + + std::string in_label = join_label_pieces(in_label_pieces); + std::string out_label = join_label_pieces(out_label_pieces); - label_string += "}}"; + std::string label_string = stringf("{{%s}|%s\\n%s|{%s}}", in_label.c_str(), + findLabel(cell->name.str()), escape(cell->type.str()), + out_label.c_str()); std::string code; for (auto &conn : cell->connections()) { @@ -574,6 +599,7 @@ struct ShowWorker { ct.setup_internals(); ct.setup_internals_mem(); + ct.setup_internals_anyinit(); ct.setup_stdcells(); ct.setup_stdcells_mem(); ct.setup_design(design); @@ -824,6 +850,7 @@ struct ShowPass : public Pass { for (auto filename : libfiles) { std::ifstream f; + rewrite_filename(filename); f.open(filename.c_str()); yosys_input_files.insert(filename); if (f.fail()) diff --git a/passes/cmds/splitcells.cc b/passes/cmds/splitcells.cc new file mode 100644 index 000000000..82ed49074 --- /dev/null +++ b/passes/cmds/splitcells.cc @@ -0,0 +1,267 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com> + * + * 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 SplitcellsWorker +{ + Module *module; + SigMap sigmap; + dict<SigBit, tuple<IdString,IdString,int>> bit_drivers_db; + dict<SigBit, pool<tuple<IdString,IdString,int>>> bit_users_db; + + SplitcellsWorker(Module *module) : module(module), sigmap(module) + { + for (auto cell : module->cells()) { + for (auto conn : cell->connections()) { + if (!cell->output(conn.first)) continue; + for (int i = 0; i < GetSize(conn.second); i++) { + SigBit bit(sigmap(conn.second[i])); + bit_drivers_db[bit] = tuple<IdString,IdString,int>(cell->name, conn.first, i); + } + } + } + + for (auto cell : module->cells()) { + for (auto conn : cell->connections()) { + if (!cell->input(conn.first)) continue; + for (int i = 0; i < GetSize(conn.second); i++) { + SigBit bit(sigmap(conn.second[i])); + if (!bit_drivers_db.count(bit)) continue; + bit_users_db[bit].insert(tuple<IdString,IdString,int>(cell->name, + conn.first, i-std::get<2>(bit_drivers_db[bit]))); + } + } + } + + for (auto wire : module->wires()) { + if (!wire->name.isPublic()) continue; + SigSpec sig(sigmap(wire)); + for (int i = 0; i < GetSize(sig); i++) { + SigBit bit(sig[i]); + if (!bit_drivers_db.count(bit)) continue; + bit_users_db[bit].insert(tuple<IdString,IdString,int>(wire->name, + IdString(), i-std::get<2>(bit_drivers_db[bit]))); + } + } + } + + int split(Cell *cell, const std::string &format) + { + if (cell->type.in("$and", "$mux", "$not", "$or", "$pmux", "$xnor", "$xor")) + { + SigSpec outsig = sigmap(cell->getPort(ID::Y)); + if (GetSize(outsig) <= 1) return 0; + + std::vector<int> slices; + slices.push_back(0); + + int width = GetSize(outsig); + width = std::min(width, GetSize(cell->getPort(ID::A))); + if (cell->hasPort(ID::B)) + width = std::min(width, GetSize(cell->getPort(ID::B))); + + for (int i = 1; i < width; i++) { + auto &last_users = bit_users_db[outsig[slices.back()]]; + auto &this_users = bit_users_db[outsig[i]]; + if (last_users != this_users) slices.push_back(i); + } + if (GetSize(slices) <= 1) return 0; + slices.push_back(GetSize(outsig)); + + log("Splitting %s cell %s/%s into %d slices:\n", log_id(cell->type), log_id(module), log_id(cell), GetSize(slices)-1); + for (int i = 1; i < GetSize(slices); i++) + { + int slice_msb = slices[i]-1; + int slice_lsb = slices[i-1]; + + IdString slice_name = module->uniquify(cell->name.str() + (slice_msb == slice_lsb ? + stringf("%c%d%c", format[0], slice_lsb, format[1]) : + stringf("%c%d%c%d%c", format[0], slice_msb, format[2], slice_lsb, format[1]))); + + Cell *slice = module->addCell(slice_name, cell); + + auto slice_signal = [&](SigSpec old_sig) -> SigSpec { + SigSpec new_sig; + for (int i = 0; i < GetSize(old_sig); i += GetSize(outsig)) { + int offset = i+slice_lsb; + int length = std::min(GetSize(old_sig)-offset, slice_msb-slice_lsb+1); + new_sig.append(old_sig.extract(offset, length)); + } + return new_sig; + }; + + slice->setPort(ID::A, slice_signal(slice->getPort(ID::A))); + if (slice->hasParam(ID::A_WIDTH)) + slice->setParam(ID::A_WIDTH, GetSize(slice->getPort(ID::A))); + + if (slice->hasPort(ID::B)) { + slice->setPort(ID::B, slice_signal(slice->getPort(ID::B))); + if (slice->hasParam(ID::B_WIDTH)) + slice->setParam(ID::B_WIDTH, GetSize(slice->getPort(ID::B))); + } + + slice->setPort(ID::Y, slice_signal(slice->getPort(ID::Y))); + if (slice->hasParam(ID::Y_WIDTH)) + slice->setParam(ID::Y_WIDTH, GetSize(slice->getPort(ID::Y))); + if (slice->hasParam(ID::WIDTH)) + slice->setParam(ID::WIDTH, GetSize(slice->getPort(ID::Y))); + + log(" slice %d: %s => %s\n", i, log_id(slice_name), log_signal(slice->getPort(ID::Y))); + } + + module->remove(cell); + return GetSize(slices)-1; + } + + if (cell->type.in("$ff", "$dff", "$dffe", "$dffsr", "$dffsre", "$adff", "$adffe", "$aldffe", + "$sdff", "$sdffce", "$sdffe", "$dlatch", "$dlatchsr", "$adlatch")) + { + auto splitports = {ID::D, ID::Q, ID::AD, ID::SET, ID::CLR}; + auto splitparams = {ID::ARST_VALUE, ID::SRST_VALUE}; + + SigSpec outsig = sigmap(cell->getPort(ID::Q)); + if (GetSize(outsig) <= 1) return 0; + int width = GetSize(outsig); + + std::vector<int> slices; + slices.push_back(0); + + for (int i = 1; i < width; i++) { + auto &last_users = bit_users_db[outsig[slices.back()]]; + auto &this_users = bit_users_db[outsig[i]]; + if (last_users != this_users) slices.push_back(i); + } + + if (GetSize(slices) <= 1) return 0; + slices.push_back(GetSize(outsig)); + + log("Splitting %s cell %s/%s into %d slices:\n", log_id(cell->type), log_id(module), log_id(cell), GetSize(slices)-1); + for (int i = 1; i < GetSize(slices); i++) + { + int slice_msb = slices[i]-1; + int slice_lsb = slices[i-1]; + + IdString slice_name = module->uniquify(cell->name.str() + (slice_msb == slice_lsb ? + stringf("%c%d%c", format[0], slice_lsb, format[1]) : + stringf("%c%d%c%d%c", format[0], slice_msb, format[2], slice_lsb, format[1]))); + + Cell *slice = module->addCell(slice_name, cell); + + for (IdString portname : splitports) { + if (slice->hasPort(portname)) { + SigSpec sig = slice->getPort(portname); + sig = sig.extract(slice_lsb, slice_msb-slice_lsb+1); + slice->setPort(portname, sig); + } + } + + for (IdString paramname : splitparams) { + if (slice->hasParam(paramname)) { + Const val = slice->getParam(paramname); + val = val.extract(slice_lsb, slice_msb-slice_lsb+1); + slice->setParam(paramname, val); + } + } + + slice->setParam(ID::WIDTH, GetSize(slice->getPort(ID::Q))); + + log(" slice %d: %s => %s\n", i, log_id(slice_name), log_signal(slice->getPort(ID::Q))); + } + + module->remove(cell); + return GetSize(slices)-1; + } + + return 0; + } +}; + +struct SplitcellsPass : public Pass { + SplitcellsPass() : Pass("splitcells", "split up multi-bit cells") { } + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" splitcells [options] [selection]\n"); + log("\n"); + log("This command splits multi-bit cells into smaller chunks, based on usage of the\n"); + log("cell output bits.\n"); + log("\n"); + log("This command operates only in cells such as $or, $and, and $mux, that are easily\n"); + log("cut into bit-slices.\n"); + log("\n"); + log(" -format char1[char2[char3]]\n"); + log(" the first char is inserted between the cell name and the bit index, the\n"); + log(" second char is appended to the cell name. e.g. -format () creates cell\n"); + log(" names like 'mycell(42)'. the 3rd character is the range separation\n"); + log(" character when creating multi-bit cells. the default is '[]:'.\n"); + log("\n"); + } + void execute(std::vector<std::string> args, RTLIL::Design *design) override + { + std::string format; + + log_header(design, "Executing SPLITCELLS pass (splitting up multi-bit cells).\n"); + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) + { + if (args[argidx] == "-format" && argidx+1 < args.size()) { + format = args[++argidx]; + continue; + } + break; + } + extra_args(args, argidx, design); + + if (GetSize(format) < 1) format += "["; + if (GetSize(format) < 2) format += "]"; + if (GetSize(format) < 3) format += ":"; + + for (auto module : design->selected_modules()) + { + int count_split_pre = 0; + int count_split_post = 0; + + while (1) { + SplitcellsWorker worker(module); + bool did_something = false; + for (auto cell : module->selected_cells()) { + int n = worker.split(cell, format); + did_something |= (n != 0); + count_split_pre += (n != 0); + count_split_post += n; + } + if (!did_something) + break; + } + + if (count_split_pre) + log("Split %d cells in module %s into %d cell slices.\n", + count_split_pre, log_id(module), count_split_post); + } + } +} SplitnetsPass; + +PRIVATE_NAMESPACE_END diff --git a/passes/cmds/stat.cc b/passes/cmds/stat.cc index c858c8631..522957f39 100644 --- a/passes/cmds/stat.cc +++ b/passes/cmds/stat.cc @@ -17,10 +17,13 @@ * */ +#include <iterator> + #include "kernel/yosys.h" #include "kernel/celltypes.h" #include "passes/techmap/libparse.h" #include "kernel/cost.h" +#include "libs/json11/json11.hpp" USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN @@ -32,14 +35,14 @@ struct statdata_t #define STAT_NUMERIC_MEMBERS STAT_INT_MEMBERS X(area) - #define X(_name) int _name; + #define X(_name) unsigned int _name; STAT_INT_MEMBERS #undef X double area; string tech; std::map<RTLIL::IdString, int> techinfo; - std::map<RTLIL::IdString, int, RTLIL::sort_by_id_str> num_cells_by_type; + std::map<RTLIL::IdString, unsigned int, RTLIL::sort_by_id_str> num_cells_by_type; std::set<RTLIL::IdString> unknown_cell_area; statdata_t operator+(const statdata_t &other) const @@ -53,7 +56,7 @@ struct statdata_t return sum; } - statdata_t operator*(int other) const + statdata_t operator*(unsigned int other) const { statdata_t sum = *this; #define X(_name) sum._name *= other; @@ -146,19 +149,91 @@ struct statdata_t } } + unsigned int estimate_xilinx_lc() + { + unsigned int lut6_cnt = num_cells_by_type[ID(LUT6)]; + unsigned int lut5_cnt = num_cells_by_type[ID(LUT5)]; + unsigned int lut4_cnt = num_cells_by_type[ID(LUT4)]; + unsigned int lut3_cnt = num_cells_by_type[ID(LUT3)]; + unsigned int lut2_cnt = num_cells_by_type[ID(LUT2)]; + unsigned int lut1_cnt = num_cells_by_type[ID(LUT1)]; + unsigned int lc_cnt = 0; + + lc_cnt += lut6_cnt; + + lc_cnt += lut5_cnt; + if (lut1_cnt) { + int cnt = std::min(lut5_cnt, lut1_cnt); + lut5_cnt -= cnt; + lut1_cnt -= cnt; + } + + lc_cnt += lut4_cnt; + if (lut1_cnt) { + int cnt = std::min(lut4_cnt, lut1_cnt); + lut4_cnt -= cnt; + lut1_cnt -= cnt; + } + if (lut2_cnt) { + int cnt = std::min(lut4_cnt, lut2_cnt); + lut4_cnt -= cnt; + lut2_cnt -= cnt; + } + + lc_cnt += lut3_cnt; + if (lut1_cnt) { + int cnt = std::min(lut3_cnt, lut1_cnt); + lut3_cnt -= cnt; + lut1_cnt -= cnt; + } + if (lut2_cnt) { + int cnt = std::min(lut3_cnt, lut2_cnt); + lut3_cnt -= cnt; + lut2_cnt -= cnt; + } + if (lut3_cnt) { + int cnt = (lut3_cnt + 1) / 2; + lut3_cnt -= cnt; + } + + lc_cnt += (lut2_cnt + lut1_cnt + 1) / 2; + + return lc_cnt; + } + + unsigned int cmos_transistor_count(bool *tran_cnt_exact) + { + unsigned int tran_cnt = 0; + auto &gate_costs = CellCosts::cmos_gate_cost(); + + for (auto it : num_cells_by_type) { + auto ctype = it.first; + auto cnum = it.second; + + if (gate_costs.count(ctype)) + tran_cnt += cnum * gate_costs.at(ctype); + else if (ctype.in(ID($_DFF_P_), ID($_DFF_N_))) + tran_cnt += cnum * 16; + else + *tran_cnt_exact = false; + } + + return tran_cnt; + } + void log_data(RTLIL::IdString mod_name, bool top_mod) { - log(" Number of wires: %6d\n", num_wires); - log(" Number of wire bits: %6d\n", num_wire_bits); - log(" Number of public wires: %6d\n", num_pub_wires); - log(" Number of public wire bits: %6d\n", num_pub_wire_bits); - log(" Number of memories: %6d\n", num_memories); - log(" Number of memory bits: %6d\n", num_memory_bits); - log(" Number of processes: %6d\n", num_processes); - log(" Number of cells: %6d\n", num_cells); + log(" Number of wires: %6u\n", num_wires); + log(" Number of wire bits: %6u\n", num_wire_bits); + log(" Number of public wires: %6u\n", num_pub_wires); + log(" Number of public wire bits: %6u\n", num_pub_wire_bits); + log(" Number of memories: %6u\n", num_memories); + log(" Number of memory bits: %6u\n", num_memory_bits); + log(" Number of processes: %6u\n", num_processes); + log(" Number of cells: %6u\n", num_cells); for (auto &it : num_cells_by_type) if (it.second) - log(" %-26s %6d\n", log_id(it.first), it.second); + log(" %-26s %6u\n", log_id(it.first), it.second); if (!unknown_cell_area.empty()) { log("\n"); @@ -173,90 +248,74 @@ struct statdata_t if (tech == "xilinx") { - int lut6_cnt = num_cells_by_type[ID(LUT6)]; - int lut5_cnt = num_cells_by_type[ID(LUT5)]; - int lut4_cnt = num_cells_by_type[ID(LUT4)]; - int lut3_cnt = num_cells_by_type[ID(LUT3)]; - int lut2_cnt = num_cells_by_type[ID(LUT2)]; - int lut1_cnt = num_cells_by_type[ID(LUT1)]; - int lc_cnt = 0; - - lc_cnt += lut6_cnt; - - lc_cnt += lut5_cnt; - if (lut1_cnt) { - int cnt = std::min(lut5_cnt, lut1_cnt); - lut5_cnt -= cnt; - lut1_cnt -= cnt; - } - - lc_cnt += lut4_cnt; - if (lut1_cnt) { - int cnt = std::min(lut4_cnt, lut1_cnt); - lut4_cnt -= cnt; - lut1_cnt -= cnt; - } - if (lut2_cnt) { - int cnt = std::min(lut4_cnt, lut2_cnt); - lut4_cnt -= cnt; - lut2_cnt -= cnt; - } - - lc_cnt += lut3_cnt; - if (lut1_cnt) { - int cnt = std::min(lut3_cnt, lut1_cnt); - lut3_cnt -= cnt; - lut1_cnt -= cnt; - } - if (lut2_cnt) { - int cnt = std::min(lut3_cnt, lut2_cnt); - lut3_cnt -= cnt; - lut2_cnt -= cnt; - } - if (lut3_cnt) { - int cnt = (lut3_cnt + 1) / 2; - lut3_cnt -= cnt; - } - - lc_cnt += (lut2_cnt + lut1_cnt + 1) / 2; - log("\n"); - log(" Estimated number of LCs: %10d\n", lc_cnt); + log(" Estimated number of LCs: %10u\n", estimate_xilinx_lc()); } if (tech == "cmos") { - int tran_cnt = 0; bool tran_cnt_exact = true; - auto &gate_costs = CellCosts::cmos_gate_cost(); + unsigned int tran_cnt = cmos_transistor_count(&tran_cnt_exact); - for (auto it : num_cells_by_type) { - auto ctype = it.first; - auto cnum = it.second; + log("\n"); + log(" Estimated number of transistors: %10u%s\n", tran_cnt, tran_cnt_exact ? "" : "+"); + } + } - if (gate_costs.count(ctype)) - tran_cnt += cnum * gate_costs.at(ctype); - else if (ctype.in(ID($_DFF_P_), ID($_DFF_N_))) - tran_cnt += cnum * 16; - else - tran_cnt_exact = false; + void log_data_json(const char *mod_name, bool first_module) + { + if (!first_module) + log(",\n"); + log(" %s: {\n", json11::Json(mod_name).dump().c_str()); + log(" \"num_wires\": %u,\n", num_wires); + log(" \"num_wire_bits\": %u,\n", num_wire_bits); + log(" \"num_pub_wires\": %u,\n", num_pub_wires); + log(" \"num_pub_wire_bits\": %u,\n", num_pub_wire_bits); + log(" \"num_memories\": %u,\n", num_memories); + log(" \"num_memory_bits\": %u,\n", num_memory_bits); + log(" \"num_processes\": %u,\n", num_processes); + log(" \"num_cells\": %u,\n", num_cells); + if (area != 0) { + log(" \"area\": %f,\n", area); + } + log(" \"num_cells_by_type\": {\n"); + bool first_line = true; + for (auto &it : num_cells_by_type) + if (it.second) { + if (!first_line) + log(",\n"); + log(" %s: %u", json11::Json(log_id(it.first)).dump().c_str(), it.second); + first_line = false; } - - log("\n"); - log(" Estimated number of transistors: %10d%s\n", tran_cnt, tran_cnt_exact ? "" : "+"); + log("\n"); + log(" }"); + if (tech == "xilinx") + { + log(",\n"); + log(" \"estimated_num_lc\": %u", estimate_xilinx_lc()); } + if (tech == "cmos") + { + bool tran_cnt_exact = true; + unsigned int tran_cnt = cmos_transistor_count(&tran_cnt_exact); + log(",\n"); + log(" \"estimated_num_transistors\": \"%u%s\"", tran_cnt, tran_cnt_exact ? "" : "+"); + } + log("\n"); + log(" }"); } }; -statdata_t hierarchy_worker(std::map<RTLIL::IdString, statdata_t> &mod_stat, RTLIL::IdString mod, int level) +statdata_t hierarchy_worker(std::map<RTLIL::IdString, statdata_t> &mod_stat, RTLIL::IdString mod, int level, bool quiet = false) { statdata_t mod_data = mod_stat.at(mod); - std::map<RTLIL::IdString, int, RTLIL::sort_by_id_str> num_cells_by_type; + std::map<RTLIL::IdString, unsigned int, RTLIL::sort_by_id_str> num_cells_by_type; num_cells_by_type.swap(mod_data.num_cells_by_type); for (auto &it : num_cells_by_type) if (mod_stat.count(it.first) > 0) { - log(" %*s%-*s %6d\n", 2*level, "", 26-2*level, log_id(it.first), it.second); + if (!quiet) + log(" %*s%-*s %6u\n", 2*level, "", 26-2*level, log_id(it.first), it.second); mod_data = mod_data + hierarchy_worker(mod_stat, it.first, level+1) * it.second; mod_data.num_cells -= it.second; } else { @@ -314,12 +373,14 @@ struct StatPass : public Pass { log(" annotate internal cell types with their word width.\n"); log(" e.g. $add_8 for an 8 bit wide $add cell.\n"); log("\n"); + log(" -json\n"); + log(" output the statistics in a machine-readable JSON format.\n"); + log(" this is output to the console; use \"tee\" to output to a file.\n"); + log("\n"); } void execute(std::vector<std::string> args, RTLIL::Design *design) override { - log_header(design, "Printing statistics.\n"); - - bool width_mode = false; + bool width_mode = false, json_mode = false; RTLIL::Module *top_mod = nullptr; std::map<RTLIL::IdString, statdata_t> mod_stat; dict<IdString, double> cell_area; @@ -348,13 +409,30 @@ struct StatPass : public Pass { top_mod = design->module(RTLIL::escape_id(args[++argidx])); continue; } + if (args[argidx] == "-json") { + json_mode = true; + continue; + } break; } extra_args(args, argidx, design); - if (techname != "" && techname != "xilinx" && techname != "cmos") + if(!json_mode) + log_header(design, "Printing statistics.\n"); + + if (techname != "" && techname != "xilinx" && techname != "cmos" && !json_mode) log_cmd_error("Unsupported technology: '%s'\n", techname.c_str()); + if (json_mode) { + log("{\n"); + log(" \"creator\": %s,\n", json11::Json(yosys_version_str).dump().c_str()); + std::stringstream invocation; + std::copy(args.begin(), args.end(), std::ostream_iterator<std::string>(invocation, " ")); + log(" \"invocation\": %s,\n", json11::Json(invocation.str()).dump().c_str()); + log(" \"modules\": {\n"); + } + + bool first_module = true; for (auto mod : design->selected_modules()) { if (!top_mod && design->full_selection()) @@ -364,23 +442,40 @@ struct StatPass : public Pass { statdata_t data(design, mod, width_mode, cell_area, techname); mod_stat[mod->name] = data; + if (json_mode) { + data.log_data_json(mod->name.c_str(), first_module); + first_module = false; + } else { + log("\n"); + log("=== %s%s ===\n", log_id(mod->name), design->selected_whole_module(mod->name) ? "" : " (partially selected)"); + log("\n"); + data.log_data(mod->name, false); + } + } + + if (json_mode) { log("\n"); - log("=== %s%s ===\n", log_id(mod->name), design->selected_whole_module(mod->name) ? "" : " (partially selected)"); - log("\n"); - data.log_data(mod->name, false); + log(top_mod == nullptr ? " }\n" : " },\n"); } - if (top_mod != nullptr && GetSize(mod_stat) > 1) + if (top_mod != nullptr) { - log("\n"); - log("=== design hierarchy ===\n"); - log("\n"); + if (!json_mode && GetSize(mod_stat) > 1) { + log("\n"); + log("=== design hierarchy ===\n"); + log("\n"); + log(" %-28s %6d\n", log_id(top_mod->name), 1); + } - log(" %-28s %6d\n", log_id(top_mod->name), 1); - statdata_t data = hierarchy_worker(mod_stat, top_mod->name, 0); + statdata_t data = hierarchy_worker(mod_stat, top_mod->name, 0, /*quiet=*/json_mode); + + if (json_mode) + data.log_data_json("design", true); + else if (GetSize(mod_stat) > 1) { + log("\n"); + data.log_data(top_mod->name, true); + } - log("\n"); - data.log_data(top_mod->name, true); design->scratchpad_set_int("stat.num_wires", data.num_wires); design->scratchpad_set_int("stat.num_wire_bits", data.num_wire_bits); design->scratchpad_set_int("stat.num_pub_wires", data.num_pub_wires); @@ -392,6 +487,11 @@ struct StatPass : public Pass { design->scratchpad_set_int("stat.area", data.area); } + if (json_mode) { + log("\n"); + log("}\n"); + } + log("\n"); } } StatPass; diff --git a/passes/cmds/tee.cc b/passes/cmds/tee.cc index 7a1f4a36b..39ed4a7a8 100644 --- a/passes/cmds/tee.cc +++ b/passes/cmds/tee.cc @@ -45,6 +45,9 @@ struct TeePass : public Pass { log(" -a logfile\n"); log(" Write output to this file, append if exists.\n"); log("\n"); + log(" -s scratchpad\n"); + log(" Write output to this scratchpad value, truncate if it exists.\n"); + log("\n"); log(" +INT, -INT\n"); log(" Add/subtract INT from the -v setting for this command.\n"); log("\n"); @@ -53,9 +56,11 @@ struct TeePass : public Pass { { std::vector<FILE*> backup_log_files, files_to_close; std::vector<std::ostream*> backup_log_streams; + std::vector<std::string> backup_log_scratchpads; int backup_log_verbose_level = log_verbose_level; backup_log_streams = log_streams; backup_log_files = log_files; + backup_log_scratchpads = log_scratchpads; size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) @@ -78,6 +83,12 @@ struct TeePass : public Pass { files_to_close.push_back(f); continue; } + if (args[argidx] == "-s" && argidx+1 < args.size()) { + auto name = args[++argidx]; + design->scratchpad[name] = ""; + log_scratchpads.push_back(name); + continue; + } if (GetSize(args[argidx]) >= 2 && (args[argidx][0] == '-' || args[argidx][0] == '+') && args[argidx][1] >= '0' && args[argidx][1] <= '9') { log_verbose_level += atoi(args[argidx].c_str()); continue; @@ -93,6 +104,7 @@ struct TeePass : public Pass { fclose(cf); log_files = backup_log_files; log_streams = backup_log_streams; + log_scratchpads = backup_log_scratchpads; throw; } @@ -102,6 +114,7 @@ struct TeePass : public Pass { log_verbose_level = backup_log_verbose_level; log_files = backup_log_files; log_streams = backup_log_streams; + log_scratchpads = backup_log_scratchpads; } } TeePass; diff --git a/passes/cmds/viz.cc b/passes/cmds/viz.cc new file mode 100644 index 000000000..3655f3f49 --- /dev/null +++ b/passes/cmds/viz.cc @@ -0,0 +1,1081 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com> + * + * 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" + +#ifndef _WIN32 +# include <dirent.h> +#endif + +#ifdef __APPLE__ +# include <unistd.h> +#endif + +#ifdef YOSYS_ENABLE_READLINE +# include <readline/readline.h> +#endif + +#ifdef YOSYS_ENABLE_EDITLINE +# include <editline/readline.h> +#endif + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct VizConfig { + enum group_type_t { + TYPE_G, + TYPE_U, + TYPE_X, + TYPE_S + }; + + int effort = 9; + int similar_thresh = 30; + int small_group_thresh = 10; + int large_group_count = 10; + std::vector<std::pair<group_type_t, RTLIL::Selection>> groups; +}; + +struct GraphNode { + int index = -1; + bool nomerge = false; + bool terminal = false; + bool excluded = false; + bool special = false; + GraphNode *replaced = nullptr; + + GraphNode *get() { + if (replaced == nullptr) + return this; + return replaced = replaced->get(); + } + + pool<IdString> names_; + dict<int, uint8_t> tags_; + pool<GraphNode*, hash_ptr_ops> upstream_; + pool<GraphNode*, hash_ptr_ops> downstream_; + + pool<IdString> &names() { return get()->names_; } + dict<int, uint8_t> &tags() { return get()->tags_; } + pool<GraphNode*, hash_ptr_ops> &upstream() { return get()->upstream_; } + pool<GraphNode*, hash_ptr_ops> &downstream() { return get()->downstream_; } + + uint8_t tag(int index) { + return tags().at(index, 0); + } + + bool tag(int index, uint8_t mask) { + if (!mask) return false; + uint8_t &v = tags()[index]; + if (v == (v|mask)) return false; + v |= mask; + return true; + } +}; + +struct Graph { + bool dirty = true; + int phase_counter = 0; + + vector<GraphNode*> nodes; + vector<GraphNode*> term_nodes; + vector<GraphNode*> nonterm_nodes; + vector<GraphNode*> replaced_nodes; + + Module *module; + const VizConfig &config; + + // statistics and indices, updated by update() + std::vector<int> max_group_sizes; + double mean_group_size; + double rms_group_size; + int edge_count, tag_count; + + ~Graph() + { + for (auto n : nodes) delete n; + for (auto n : replaced_nodes) delete n; + } + + GraphNode *node(int index) + { + if (index) + return nodes[index-1]->get(); + return nullptr; + } + + void update_nodes() + { + // Filter-out replaced nodes + + term_nodes.clear(); + nonterm_nodes.clear(); + + for (auto n : nodes) { + if (n->replaced) + replaced_nodes.push_back(n); + else if (n->terminal) + term_nodes.push_back(n); + else + nonterm_nodes.push_back(n); + } + + // Re-index the remaining nodes + + nodes.clear(); + + max_group_sizes.clear(); + max_group_sizes.resize(config.large_group_count); + + mean_group_size = 0; + rms_group_size = 0; + edge_count = 0; + + auto update_node = [&](GraphNode *n) + { + nodes.push_back(n); + n->index = GetSize(nodes); + + pool<GraphNode*, hash_ptr_ops> new_upstream; + pool<GraphNode*, hash_ptr_ops> new_downstream; + + for (auto g : n->upstream()) { + if (n != (g = g->get())) + new_upstream.insert(g); + } + for (auto g : n->downstream()) { + if (n != (g = g->get())) + new_downstream.insert(g), edge_count++; + } + + new_upstream.sort(); + new_downstream.sort(); + + std::swap(n->upstream(), new_upstream); + std::swap(n->downstream(), new_downstream); + + if (!n->terminal) { + int t = GetSize(n->names()); + mean_group_size += t; + rms_group_size += t*t; + for (int i = 0; i < config.large_group_count; i++) + if (t >= max_group_sizes[i]) + std::swap(t, max_group_sizes[i]); + } + }; + + for (auto n : term_nodes) + update_node(n); + + for (auto n : nonterm_nodes) + update_node(n); + + mean_group_size /= GetSize(nonterm_nodes); + rms_group_size = sqrt(rms_group_size / GetSize(nonterm_nodes)); + } + + void update_tags() + { + std::function<void(GraphNode*,int,bool)> up_down_prop_tag = + [&](GraphNode *g, int index, bool down) + { + for (auto n : (down ? g->downstream_ : g->upstream_)) { + if (n->tag(index, down ? 2 : 1)) { + if (!n->terminal) + up_down_prop_tag(n, index, down); + tag_count++; + } + } + }; + + tag_count = 0; + for (auto g : nodes) + g->tags().clear(); + + for (auto g : term_nodes) { + up_down_prop_tag(g, g->index, false); + up_down_prop_tag(g, g->index, true); + } + + for (auto g : nodes) + g->tags().sort(); + } + + bool update() + { + if (!dirty) { + log(" Largest non-term group sizes: "); + for (int i = 0; i < config.large_group_count; i++) + log("%d%s", max_group_sizes[i], i+1 == config.large_group_count ? ".\n" : " "); + + // log(" Mean and Root-Mean-Square group sizes: %.1f and %.1f\n", mean_group_size, rms_group_size); + + return false; + } + + dirty = false; + update_nodes(); + update_tags(); + + log(" Status: %d nodes (%d term and %d non-term), %d edges, and %d tags\n", + GetSize(nodes), GetSize(term_nodes), GetSize(nonterm_nodes), edge_count, tag_count); + return true; + } + + void merge(GraphNode *g, GraphNode *n) + { + g = g->get(); + n = n->get(); + + log_assert(!g->nomerge); + log_assert(!n->nomerge); + log_assert(g->terminal == n->terminal); + + if (g == n) return; + + for (auto v : n->names_) + g->names_.insert(v); + + for (auto v : n->tags_) + g->tags_[v.first] |= v.second; + + for (auto v : n->upstream_) { + if (g != (v = v->get())) + g->upstream_.insert(v); + } + + for (auto v : n->downstream_) { + if (g != (v = v->get())) + g->downstream_.insert(v); + } + + n->names_.clear(); + n->tags_.clear(); + n->upstream_.clear(); + n->downstream_.clear(); + + dirty = true; + n->replaced = g; + } + + Graph(Module *module, const VizConfig &config) : module(module), config(config) + { + log("Running 'viz -%d' for module %s:\n", config.effort, log_id(module)); + log(" Phase %d: Construct initial graph\n", phase_counter++); + + SigMap sigmap(module); + dict<SigBit, GraphNode*> wire_nodes; + + for (auto wire : module->selected_wires()) + { + if (!wire->name.isPublic()) continue; + auto g = new GraphNode; + g->terminal = true; + g->names().insert(wire->name); + nodes.push_back(g); + + for (auto bit : sigmap(wire)) { + if (!bit.wire) continue; + auto it = wire_nodes.find(bit); + if (it == wire_nodes.end()) + wire_nodes[bit] = g; + else + merge(g, it->second); + } + } + + pool<GraphNode*, hash_ptr_ops> excluded; + + for (auto grp : config.groups) + { + GraphNode *g = nullptr; + + if (!grp.second.selected_module(module->name)) + continue; + + for (auto wire : module->wires()) { + if (!wire->name.isPublic()) continue; + if (!grp.second.selected_member(module->name, wire->name)) continue; + for (auto bit : sigmap(wire)) { + auto it = wire_nodes.find(bit); + if (it == wire_nodes.end()) + continue; + auto n = it->second->get(); + if (n->nomerge) + continue; + if (grp.first == VizConfig::TYPE_G || grp.first == VizConfig::TYPE_S) { + if (g) { + if (!n->nomerge) + merge(g, n); + } else + g = n; + } else if (grp.first == VizConfig::TYPE_U) { + n->nomerge = true; + } else if (grp.first == VizConfig::TYPE_X) { + n->nomerge = true; + excluded.insert(n); + } else + log_abort(); + } + } + + if (g) { + if (grp.first == VizConfig::TYPE_S) + g->special = true; + g->nomerge = true; + } + } + + for (auto g : excluded) + excluded.insert(g->get()); + + dict<Cell*, GraphNode*> cell_nodes; + dict<SigBit, pool<GraphNode*, hash_ptr_ops>> sig_users; + + for (auto cell : module->selected_cells()) { + auto g = new GraphNode; + cell_nodes[cell] = g; + g->names().insert(cell->name); + nodes.push_back(g); + + for (auto &conn : cell->connections()) { + if (cell->input(conn.first)) + for (auto bit : sigmap(conn.second)) { + if (!bit.wire) continue; + auto it = wire_nodes.find(bit); + if (it != wire_nodes.end()) { + auto n = it->second->get(); + if (!excluded.count(n)) { + g->upstream().insert(n); + n->downstream().insert(g); + } + } else { + sig_users[bit].insert(g); + } + } + if (cell->output(conn.first)) + for (auto bit : sigmap(conn.second)) { + if (!bit.wire) continue; + auto it = wire_nodes.find(bit); + if (it != wire_nodes.end()) { + auto n = it->second->get(); + if (!excluded.count(n)) { + g->downstream().insert(n); + n->upstream().insert(g); + } + } + } + } + } + + for (auto cell : module->selected_cells()) { + auto g = cell_nodes.at(cell); + + for (auto &conn : cell->connections()) { + if (!cell->output(conn.first)) continue; + for (auto bit : sigmap(conn.second)) { + if (!bit.wire) continue; + for (auto u : sig_users[bit]) { + g->downstream().insert(u); + u->upstream().insert(g); + } + } + } + } + + update(); + } + + int compare_tags(GraphNode *g, GraphNode *n, bool strict_mode) + { + if (GetSize(g->tags()) > GetSize(n->tags())) + return compare_tags(n, g, strict_mode); + + if (g->tags().empty()) + return 100; + + bool gn_specials = true; + bool g_nonspecials = false; + bool n_nonspecials = false; + + int score = 0; + for (auto it : g->tags()) { + auto g_tag = it.second; + auto n_tag = n->tag(it.first); + log_assert(g_tag != 0); + if (node(it.first)->special) { + gn_specials = true; + if (g_tag != n_tag) return 0; + } else + g_nonspecials = true; + if (n_tag == 0) continue; + if (g_tag == n_tag) + score += 2; + else if (!strict_mode && (g_tag + n_tag == 4)) + score += 1; + else + return 0; + } + for (auto it : n->tags()) { + auto n_tag = it.second; + log_assert(n_tag != 0); + if (node(it.first)->special) { + gn_specials = true; + auto g_tag = g->tag(it.first); + if (g_tag != n_tag) return 0; + } else + n_nonspecials = true; + } + + if (gn_specials && (g_nonspecials != n_nonspecials)) + return 0; + + return (100*score) / GetSize(g->tags()); + } + + int phase(bool term, int effort) + { + log(" Phase %d: Merge %sterminal nodes with effort level %d\n", phase_counter++, term ? "" : "non-", effort); + int start_replaced_nodes = GetSize(replaced_nodes); + + do { + dict<int,pool<pair<int,int>>> candidates; + auto queue = [&](GraphNode *g, GraphNode *n) -> bool { + if (g->terminal != n->terminal) + return false; + if (g->nomerge || n->nomerge) + return false; + int sz = GetSize(g->names()) + GetSize(n->names()); + if (g->index < n->index) + candidates[sz].insert(pair<int,int>(g->index, n->index)); + else if (g->index != n->index) + candidates[sz].insert(pair<int,int>(n->index, g->index)); + return true; + }; + + int last_candidates_size = 0; + const char *last_section_header = nullptr; + auto header = [&](const char *p = nullptr) { + if (GetSize(candidates) != last_candidates_size && last_section_header) + log(" Found %d cadidates of type '%s'.\n", + GetSize(candidates) - last_candidates_size, last_section_header); + last_candidates_size = GetSize(candidates); + last_section_header = p; + }; + + { + header("Any nodes with identical connections"); + typedef pair<pool<GraphNode*, hash_ptr_ops>, pool<GraphNode*, hash_ptr_ops>> node_conn_t; + dict<node_conn_t, pool<GraphNode*, hash_ptr_ops>> nodes_by_conn; + for (auto g : term ? term_nodes : nonterm_nodes) { + auto &entry = nodes_by_conn[node_conn_t(g->upstream(), g->downstream())]; + for (auto n : entry) + queue(g, n); + entry.insert(g); + } + } + + if (!candidates.empty() || effort < 1) goto execute; + + if (!term) { + header("Source-Sink with identical tags"); + for (auto g : nonterm_nodes) { + for (auto n : g->downstream()) { + if (n->terminal) continue; + if (g->tags() == n->tags()) queue(g, n); + } + } + + header("Sibblings with identical tags"); + for (auto g : nonterm_nodes) { + auto process_conns = [&](const pool<GraphNode*, hash_ptr_ops> &stream) { + dict<std::vector<int>, pool<GraphNode*, hash_ptr_ops>> nodes_by_tags; + for (auto n : stream) { + if (n->terminal) continue; + std::vector<int> key; + for (auto kv : n->tags()) + key.push_back(kv.first), key.push_back(kv.second); + auto &entry = nodes_by_tags[key]; + for (auto m : entry) queue(n, m); + entry.insert(n); + } + }; + process_conns(g->upstream()); + process_conns(g->downstream()); + } + } + + if (!candidates.empty() || effort < 2) goto execute; + + if (!term) { + header("Nodes with single fan-out and compatible tags"); + for (auto g : nonterm_nodes) { + if (GetSize(g->downstream()) != 1) continue; + auto n = *g->downstream().begin(); + if (!n->terminal && compare_tags(g, n, true)) queue(g, n); + } + + header("Nodes with single fan-in and compatible tags"); + for (auto g : nonterm_nodes) { + if (GetSize(g->upstream()) != 1) continue; + auto n = *g->upstream().begin(); + if (!n->terminal && compare_tags(g, n, true)) queue(g, n); + } + } + + if (!candidates.empty() || effort < 3) goto execute; + + if (!term) { + header("Connected nodes with similar tags (strict)"); + for (auto g : nonterm_nodes) { + for (auto n : g->downstream()) + if (!n->terminal && compare_tags(g, n, true) > config.similar_thresh) queue(g, n); + } + } + + if (!candidates.empty() || effort < 4) goto execute; + + if (!term) { + header("Sibblings with similar tags (strict)"); + for (auto g : nonterm_nodes) { + auto process_conns = [&](const pool<GraphNode*, hash_ptr_ops> &stream) { + std::vector<GraphNode*> nodes; + for (auto n : stream) + if (!n->terminal) nodes.push_back(n); + for (int i = 0; i < GetSize(nodes); i++) + for (int j = 0; j < i; j++) + if (compare_tags(nodes[i], nodes[j], true) > config.similar_thresh) + queue(nodes[i], nodes[j]); + }; + process_conns(g->upstream()); + process_conns(g->downstream()); + } + } + + if (!candidates.empty() || effort < 5) goto execute; + + if (!term) { + header("Connected nodes with similar tags (non-strict)"); + for (auto g : nonterm_nodes) { + for (auto n : g->downstream()) + if (!n->terminal && compare_tags(g, n, false) > config.similar_thresh) queue(g, n); + } + } + + if (!candidates.empty() || effort < 6) goto execute; + + if (!term) { + header("Sibblings with similar tags (non-strict)"); + for (auto g : nonterm_nodes) { + auto process_conns = [&](const pool<GraphNode*, hash_ptr_ops> &stream) { + std::vector<GraphNode*> nodes; + for (auto n : stream) + if (!n->terminal) nodes.push_back(n); + for (int i = 0; i < GetSize(nodes); i++) + for (int j = 0; j < i; j++) + if (compare_tags(nodes[i], nodes[j], false) > config.similar_thresh) + queue(nodes[i], nodes[j]); + }; + process_conns(g->upstream()); + process_conns(g->downstream()); + } + } + + if (!candidates.empty() || effort < 7) goto execute; + + { + header("Any nodes with identical fan-in or fan-out"); + dict<pool<GraphNode*, hash_ptr_ops>, pool<GraphNode*, hash_ptr_ops>> nodes_by_conn[2]; + for (auto g : term ? term_nodes : nonterm_nodes) { + auto &up_entry = nodes_by_conn[0][g->upstream()]; + auto &down_entry = nodes_by_conn[1][g->downstream()]; + for (auto n : up_entry) queue(g, n); + for (auto n : down_entry) queue(g, n); + up_entry.insert(g); + down_entry.insert(g); + } + } + + if (!candidates.empty() || effort < 8) goto execute; + + if (!term) { + header("Connected nodes with similar tags (lax)"); + for (auto g : nonterm_nodes) { + for (auto n : g->downstream()) + if (!n->terminal && compare_tags(g, n, false)) queue(g, n); + } + } + + if (!candidates.empty() || effort < 9) goto execute; + + if (!term) { + header("Sibblings with similar tags (lax)"); + for (auto g : nonterm_nodes) { + auto process_conns = [&](const pool<GraphNode*, hash_ptr_ops> &stream) { + std::vector<GraphNode*> nodes; + for (auto n : stream) + if (!n->terminal) nodes.push_back(n); + for (int i = 0; i < GetSize(nodes); i++) + for (int j = 0; j < i; j++) + if (compare_tags(nodes[i], nodes[j], false)) + queue(nodes[i], nodes[j]); + }; + process_conns(g->upstream()); + process_conns(g->downstream()); + } + } + + execute: + header(); + candidates.sort(); + bool small_mode = false; + bool medium_mode = false; + for (auto &candidate_group : candidates) { + for (auto &candidate : candidate_group.second) { + auto g = node(candidate.first); + auto n = node(candidate.second); + if (!term) { + int sz = GetSize(g->names()) + GetSize(n->names()); + if (sz <= config.small_group_thresh) + small_mode = true; + else if (small_mode && sz >= max_group_sizes.back()) + continue; + if (sz <= max_group_sizes.front()) + medium_mode = true; + else if (medium_mode && sz > max_group_sizes.front()) + continue; + } + merge(g, n); + } + } + if (small_mode) + log(" Using 'small-mode' to prevent big groups.\n"); + else if (medium_mode) + log(" Using 'medium-mode' to prevent big groups.\n"); + } while (update()); + + int merged_nodes = GetSize(replaced_nodes) - start_replaced_nodes; + log(" Merged a total of %d nodes.\n", merged_nodes); + return merged_nodes; + } +}; + +struct VizWorker +{ + VizConfig config; + Module *module; + Graph graph; + + VizWorker(Module *module, const VizConfig &cfg) : config(cfg), module(module), graph(module, config) + { + for (int effort = 0; effort <= config.effort; effort++) { + bool first = true; + while (1) { + if (!graph.phase(false, effort) && !first) break; + if (!graph.phase(true, effort)) break; + first = false; + } + log(" %s: %d nodes (%d term and %d non-term), %d edges, and %d tags\n", + effort == config.effort ? "Final" : "Status", GetSize(graph.nodes), + GetSize(graph.term_nodes), GetSize(graph.nonterm_nodes), + graph.edge_count, graph.tag_count); + } + } + + void update_attrs() + { + IdString vg_id("\\vg"); + for (auto c : module->cells()) + c->attributes.erase(vg_id); + for (auto g : graph.nodes) { + for (auto name : g->names()) { + auto w = module->wire(name); + auto c = module->cell(name); + if (w) w->attributes[vg_id] = g->index; + if (c) c->attributes[vg_id] = g->index; + } + } + } + + void write_dot(FILE *f) + { + fprintf(f, "digraph \"%s\" {\n", log_id(module)); + fprintf(f, " rankdir = LR;\n"); + + dict<GraphNode*, std::vector<std::vector<std::string>>, hash_ptr_ops> extra_lines; + dict<GraphNode*, GraphNode*, hash_ptr_ops> bypass_nodes; + pool<GraphNode*, hash_ptr_ops> bypass_candidates; + + auto bypass = [&](GraphNode *g, GraphNode *n) { + log_assert(g->terminal); + log_assert(!n->terminal); + bypass_nodes[g] = n; + + auto &buffer = extra_lines[n]; + buffer.emplace_back(); + + for (auto name : g->names()) + buffer.back().push_back(log_id(name)); + + std::sort(buffer.back().begin(), buffer.back().end()); + std::sort(buffer.begin(), buffer.end()); + }; + + for (auto g : graph.nonterm_nodes) { + for (auto n : g->downstream()) + if (!n->terminal) goto not_a_candidate; + bypass_candidates.insert(g); + not_a_candidate:; + } + + for (auto g : graph.term_nodes) + { + if (g->special || bypass_nodes.count(g)) continue; + if (GetSize(g->upstream()) != 1) continue; + if (!g->downstream().empty() && g->downstream() != g->upstream()) continue; + + auto n = *(g->upstream().begin()); + if (n->terminal || !bypass_candidates.count(n)) continue; + + bypass(g, n); + } + + for (auto g : graph.term_nodes) + { + if (g->special || bypass_nodes.count(g)) continue; + if (GetSize(g->upstream()) != 1) continue; + + auto n = *(g->upstream().begin()); + if (n->terminal || !bypass_candidates.count(n)) continue; + + if (GetSize(n->downstream()) != 1) continue; + if (extra_lines.count(n)) continue; + + bypass(g, n); + } + + for (auto g : graph.nodes) { + if (g->downstream().empty() && g->upstream().empty()) + continue; + if (bypass_nodes.count(g)) + continue; + if (g->terminal) { + g->names().sort(); + std::string label; // = stringf("vg=%d\\n", g->index); + for (auto n : g->names()) + label = label + (label.empty() ? "" : "\\n") + log_id(n); + fprintf(f, "\tn%d [shape=rectangle,label=\"%s\"];\n", g->index, label.c_str()); + } else { + std::string label = stringf("vg=%d | %d cells", g->index, GetSize(g->names())); + std::string shape = "oval"; + if (extra_lines.count(g)) { + for (auto &block : extra_lines.at(g)) { + label += label.empty() ? "" : "\\n"; + for (auto &line : block) + label = label + (label.empty() ? "" : "\\n") + line; + shape = "octagon"; + } + } + fprintf(f, "\tn%d [shape=%s,label=\"%s\"];\n", g->index, shape.c_str(), label.c_str()); + } + } + + pool<std::string> edges; + for (auto g : graph.nodes) { + auto p = bypass_nodes.at(g, g); + for (auto n : g->downstream()) { + auto q = bypass_nodes.at(n, n); + if (p == q) continue; + edges.insert(stringf("n%d -> n%d", p->index, q->index)); + } + } + edges.sort(); + for (auto e : edges) + fprintf(f, "\t%s;\n", e.c_str()); + + fprintf(f, "}\n"); + } +}; + +struct VizPass : public Pass { + VizPass() : Pass("viz", "visualize data flow graph") { } + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" viz [options] [selection]\n"); + log("\n"); + log("Create a graphviz DOT file for the selected part of the design, showing the\n"); + log("relationships between the selected wires, and compile it to a graphics\n"); + log("file (usually SVG or PostScript).\n"); + log("\n"); + log(" -viewer <viewer>\n"); + log(" Run the specified command with the graphics file as parameter.\n"); + log(" On Windows, this pauses yosys until the viewer exits.\n"); + log("\n"); + log(" -format <format>\n"); + log(" Generate a graphics file in the specified format. Use 'dot' to just\n"); + log(" generate a .dot file, or other <format> strings such as 'svg' or 'ps'\n"); + log(" to generate files in other formats (this calls the 'dot' command).\n"); + log("\n"); + log(" -prefix <prefix>\n"); + log(" generate <prefix>.* instead of ~/.yosys_viz.*\n"); + log("\n"); + log(" -pause\n"); + log(" wait for the user to press enter to before returning\n"); + log("\n"); + log(" -nobg\n"); + log(" don't run viewer in the background, IE wait for the viewer tool to\n"); + log(" exit before returning\n"); + log("\n"); + log(" -set-vg-attr\n"); + log(" set their group index as 'vg' attribute on cells and wires\n"); + log("\n"); + log(" -g <selection>\n"); + log(" manually define a group of terminal signals. this group is not being\n"); + log(" merged with other terminal groups.\n"); + log("\n"); + log(" -u <selection>\n"); + log(" manually define a unique group for each wire in the selection.\n"); + log("\n"); + log(" -x <selection>\n"); + log(" manually exclude wires from being considered. (usually this is\n"); + log(" used for global signals, such as reset.)\n"); + log("\n"); + log(" -s <selection>\n"); + log(" like -g, but mark group as 'special', changing the algorithm to\n"); + log(" preserve as much info about this groups connectivity as possible.\n"); + log("\n"); + log(" -G <selection_expr> .\n"); + log(" -U <selection_expr> .\n"); + log(" -X <selection_expr> .\n"); + log(" -S <selection_expr> .\n"); + log(" like -u, -g, -x, and -s, but parse all arguments up to a terminating .\n"); + log(" as a single select expression. (see 'help select' for details)\n"); + log("\n"); + log(" -0, -1, -2, -3, -4, -5, -6, -7, -8, -9\n"); + log(" select effort level. each level corresponds to an incresingly more\n"); + log(" aggressive sequence of strategies for merging nodes of the data flow\n"); + log(" graph. (default: %d)\n", VizConfig().effort); + log("\n"); + log("When no <format> is specified, 'dot' is used. When no <format> and <viewer> is\n"); + log("specified, 'xdot' is used to display the schematic (POSIX systems only).\n"); + log("\n"); + log("The generated output files are '~/.yosys_viz.dot' and '~/.yosys_viz.<format>',\n"); + log("unless another prefix is specified using -prefix <prefix>.\n"); + log("\n"); + log("Yosys on Windows and YosysJS use different defaults: The output is written\n"); + log("to 'show.dot' in the current directory and new viewer is launched each time\n"); + log("the 'show' command is executed.\n"); + log("\n"); + } + void execute(std::vector<std::string> args, RTLIL::Design *design) override + { + log_header(design, "Generating Graphviz representation of design.\n"); + log_push(); + +#if defined(_WIN32) || defined(YOSYS_DISABLE_SPAWN) + std::string format = "dot"; + std::string prefix = "show"; +#else + std::string format; + std::string prefix = stringf("%s/.yosys_viz", getenv("HOME") ? getenv("HOME") : "."); +#endif + std::string viewer_exe; + bool flag_pause = false; + bool flag_attr = false; + bool custom_prefix = false; + std::string background = "&"; + + VizConfig config; + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) + { + std::string arg = args[argidx]; + if (arg == "-viewer" && argidx+1 < args.size()) { + viewer_exe = args[++argidx]; + continue; + } + if (arg == "-prefix" && argidx+1 < args.size()) { + prefix = args[++argidx]; + custom_prefix = true; + continue; + } + if (arg == "-format" && argidx+1 < args.size()) { + format = args[++argidx]; + continue; + } + if (arg == "-pause") { + flag_pause= true; + continue; + } + if (arg == "-set-vg-attr") { + flag_attr= true; + continue; + } + if (arg == "-nobg") { + background= ""; + continue; + } + if ((arg == "-g" || arg == "-u" || arg == "-x" || arg == "-s" || + arg == "-G" || arg == "-U" || arg == "-X" || arg == "-S") && argidx+1 < args.size()) { + int numargs = 1; + int first_arg = ++argidx; + if (arg == "-G" || arg == "-U" || arg == "-X" || arg == "-S") { + while (argidx+1 < args.size()) { + if (args[++argidx] == ".") break; + numargs++; + } + } + handle_extra_select_args(this, args, first_arg, first_arg+numargs, design); + auto type = arg == "-g" || arg == "-G" ? VizConfig::TYPE_G : + arg == "-u" || arg == "-U" ? VizConfig::TYPE_U : + arg == "-x" || arg == "-X" ? VizConfig::TYPE_X : VizConfig::TYPE_S; + config.groups.push_back({type, design->selection_stack.back()}); + design->selection_stack.pop_back(); + continue; + } + if (arg == "-0" || arg == "-1" || arg == "-2" || arg == "-3" || arg == "-4" || + arg == "-5" || arg == "-6" || arg == "-7" || arg == "-8" || arg == "-9") { + config.effort = arg[1] - '0'; + continue; + } + break; + } + extra_args(args, argidx, design); + + std::vector<Module*> modlist; + for (auto module : design->selected_modules()) { + if (module->get_blackbox_attribute()) + continue; + if (module->cells().size() == 0 && module->connections().empty()) + continue; + modlist.push_back(module); + } + if (format != "ps" && format != "dot" && GetSize(modlist) > 1) + log_cmd_error("For formats different than 'ps' or 'dot' only one module must be selected.\n"); + if (modlist.empty()) + log_cmd_error("Nothing there to show.\n"); + + std::string dot_file = stringf("%s.dot", prefix.c_str()); + std::string out_file = stringf("%s.%s", prefix.c_str(), format.empty() ? "svg" : format.c_str()); + + if (custom_prefix) + yosys_output_files.insert(dot_file); + + log("Writing dot description to `%s'.\n", dot_file.c_str()); + FILE *f = nullptr; + auto open_dot_file = [&]() { + if (f != nullptr) return; + f = fopen(dot_file.c_str(), "w"); + if (f == nullptr) + log_cmd_error("Can't open dot file `%s' for writing.\n", dot_file.c_str()); + }; + for (auto module : modlist) { + VizWorker worker(module, config); + + if (flag_attr) + worker.update_attrs(); + + if (format != "dot" && GetSize(worker.graph.nodes) > 200) { + if (format.empty()) { + log_warning("Suppressing module in output as graph size exceeds 200 nodes.\n"); + continue; + } else { + log_warning("Changing format to 'dot' as graph size exceeds 200 nodes.\n"); + format = "dot"; + } + } + + // delay opening of output file until we have something to write, to avoid race with xdot + open_dot_file(); + worker.write_dot(f); + } + open_dot_file(); + fclose(f); + + if (format != "dot" && !format.empty()) { + #ifdef _WIN32 + // system()/cmd.exe does not understand single quotes on Windows. + #define DOT_CMD "dot -T%s \"%s\" > \"%s.new\" && move \"%s.new\" \"%s\"" + #else + #define DOT_CMD "dot -T%s '%s' > '%s.new' && mv '%s.new' '%s'" + #endif + std::string cmd = stringf(DOT_CMD, format.c_str(), dot_file.c_str(), out_file.c_str(), out_file.c_str(), out_file.c_str()); + #undef DOT_CMD + log("Exec: %s\n", cmd.c_str()); + #if !defined(YOSYS_DISABLE_SPAWN) + if (run_command(cmd) != 0) + log_cmd_error("Shell command failed!\n"); + #endif + } + + #if defined(YOSYS_DISABLE_SPAWN) + log_assert(viewer_exe.empty() && !format.empty()); + #else + if (!viewer_exe.empty()) { + #ifdef _WIN32 + // system()/cmd.exe does not understand single quotes nor + // background tasks on Windows. So we have to pause yosys + // until the viewer exits. + std::string cmd = stringf("%s \"%s\"", viewer_exe.c_str(), out_file.c_str()); + #else + std::string cmd = stringf("%s '%s' %s", viewer_exe.c_str(), out_file.c_str(), background.c_str()); + #endif + log("Exec: %s\n", cmd.c_str()); + if (run_command(cmd) != 0) + log_cmd_error("Shell command failed!\n"); + } else + if (format.empty()) { + #ifdef __APPLE__ + std::string cmd = stringf("ps -fu %d | grep -q '[ ]%s' || xdot '%s' %s", getuid(), dot_file.c_str(), dot_file.c_str(), background.c_str()); + #else + std::string cmd = stringf("{ test -f '%s.pid' && fuser -s '%s.pid' 2> /dev/null; } || ( echo $$ >&3; exec xdot '%s'; ) 3> '%s.pid' %s", dot_file.c_str(), dot_file.c_str(), dot_file.c_str(), dot_file.c_str(), background.c_str()); + #endif + log("Exec: %s\n", cmd.c_str()); + if (run_command(cmd) != 0) + log_cmd_error("Shell command failed!\n"); + } + #endif + + if (flag_pause) { + #ifdef YOSYS_ENABLE_READLINE + char *input = nullptr; + while ((input = readline("Press ENTER to continue (or type 'shell' to open a shell)> ")) != nullptr) { + if (input[strspn(input, " \t\r\n")] == 0) + break; + char *p = input + strspn(input, " \t\r\n"); + if (!strcmp(p, "shell")) { + Pass::call(design, "shell"); + break; + } + } + #else + log_cmd_error("This version of yosys is built without readline support => 'show -pause' is not available.\n"); + #endif + } + + log_pop(); + } +} VizPass; + +PRIVATE_NAMESPACE_END diff --git a/passes/cmds/xprop.cc b/passes/cmds/xprop.cc new file mode 100644 index 000000000..5e78ff9fc --- /dev/null +++ b/passes/cmds/xprop.cc @@ -0,0 +1,1234 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2022 Jannis Harder <jix@yosyshq.com> <me@jix.one> + * + * 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/celltypes.h" +#include "kernel/ffinit.h" +#include "kernel/ff.h" +#include "kernel/modtools.h" +#include "kernel/sigtools.h" +#include "kernel/utils.h" +#include "kernel/yosys.h" +#include <deque> + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct XpropOptions +{ + bool split_inputs = false; + bool split_outputs = false; + bool split_public = false; + bool assume_encoding = false; + bool assert_encoding = false; + bool assume_def_inputs = false; + bool required = false; + bool formal = false; + bool debug_asserts = false; +}; + +struct XpropWorker +{ + struct EncodedBit { + SigBit is_0, is_1, is_x; + bool driven; + }; + + struct EncodedSig { + SigSpec is_0, is_1, is_x; + Module *module; + + void invert() { std::swap(is_0, is_1); } + void auto_0() { connect_0(module->Not(NEW_ID, module->Or(NEW_ID, is_1, is_x))); } + void auto_1() { connect_1(module->Not(NEW_ID, module->Or(NEW_ID, is_0, is_x))); } + void auto_x() { connect_x(module->Not(NEW_ID, module->Or(NEW_ID, is_0, is_1))); } + + void connect_0(SigSpec sig) { module->connect(is_0, sig); } + void connect_1(SigSpec sig) { module->connect(is_1, sig); } + void connect_x(SigSpec sig) { module->connect(is_x, sig); } + + void connect_1_under_x(SigSpec sig) { connect_1(module->And(NEW_ID, sig, module->Not(NEW_ID, is_x))); } + void connect_0_under_x(SigSpec sig) { connect_0(module->And(NEW_ID, sig, module->Not(NEW_ID, is_x))); } + + void connect_x_under_0(SigSpec sig) { connect_x(module->And(NEW_ID, sig, module->Not(NEW_ID, is_0))); } + + void connect_as_bool() { + int width = GetSize(is_0); + if (width <= 1) + return; + module->connect(is_0.extract(1, width - 1), Const(State::S1, width - 1)); + module->connect(is_1.extract(1, width - 1), Const(State::S0, width - 1)); + module->connect(is_x.extract(1, width - 1), Const(State::S0, width - 1)); + is_0 = is_0[0]; + is_1 = is_1[0]; + is_x = is_x[0]; + } + + int size() const { return is_0.size(); } + }; + + Module *module; + XpropOptions options; + ModWalker modwalker; + SigMap &sigmap; + FfInitVals initvals; + + pool<SigBit> maybe_x_bits; + dict<SigBit, EncodedBit> encoded_bits; + + pool<Cell *> pending_cells; + std::deque<Cell *> pending_cell_queue; + + XpropWorker(Module *module, XpropOptions options) : + module(module), options(options), + modwalker(module->design), sigmap(modwalker.sigmap) + { + modwalker.setup(module); + initvals.set(&modwalker.sigmap, module); + + maybe_x_bits.insert(State::Sx); + + for (auto cell : module->cells()) { + pending_cells.insert(cell); + pending_cell_queue.push_back(cell); + } + + if (!options.assume_def_inputs) { + for (auto port : module->ports) { + auto wire = module->wire(port); + if (wire->port_input) + mark_maybe_x(SigSpec(wire)); + } + } + } + + bool maybe_x(SigBit bit) + { + return maybe_x_bits.count(sigmap(bit)); + } + + bool maybe_x(const SigSpec &sig) + { + for (auto bit : sig) + if (maybe_x(bit)) return true; + return false; + } + + bool ports_maybe_x(Cell *cell) + { + for (auto &conn : cell->connections()) + if (maybe_x(conn.second)) + return true; + return false; + } + + bool inputs_maybe_x(Cell *cell) + { + for (auto &conn : cell->connections()) + if (cell->input(conn.first) && maybe_x(conn.second)) + return true; + return false; + } + + void mark_maybe_x(SigBit bit) + { + sigmap.apply(bit); + if (!maybe_x_bits.insert(bit).second) + return; + auto it = modwalker.signal_consumers.find(bit); + if (it == modwalker.signal_consumers.end()) + return; + for (auto &consumer : it->second) + if (pending_cells.insert(consumer.cell).second) + pending_cell_queue.push_back(consumer.cell); + } + + void mark_maybe_x(const SigSpec &sig) + { + for (auto bit : sig) + mark_maybe_x(bit); + } + + void mark_outputs_maybe_x(Cell *cell) + { + for (auto &conn : cell->connections()) + if (cell->output(conn.first)) + mark_maybe_x(conn.second); + } + + EncodedSig encoded(SigSpec sig, bool driving = false) + { + EncodedSig result; + SigSpec invert; + + if (driving) + result.module = module; + + int new_bits = 0; + + sigmap.apply(sig); + + for (auto bit : sig) { + if (!bit.is_wire()) + continue; + else if (!maybe_x(bit) && !driving) + invert.append(bit); + else if (!encoded_bits.count(bit)) { + new_bits += 1; + encoded_bits.emplace(bit, { + State::Sm, State::Sm, State::Sm, false + }); + } + } + + if (!invert.empty() && !driving) + invert = module->Not(NEW_ID, invert); + + EncodedSig new_sigs; + if (new_bits > 0) { + new_sigs.is_0 = module->addWire(NEW_ID, new_bits); + new_sigs.is_1 = module->addWire(NEW_ID, new_bits); + new_sigs.is_x = module->addWire(NEW_ID, new_bits); + } + + int invert_pos = 0; + int new_pos = 0; + + SigSpec driven_orig; + EncodedSig driven_enc; + SigSig driven_never_x; + + for (auto bit : sig) + { + if (!bit.is_wire()) { + result.is_0.append(bit == State::S0 ? State::S1 : State::S0); + result.is_1.append(bit == State::S1 ? State::S1 : State::S0); + result.is_x.append(bit == State::Sx ? State::S1 : State::S0); + continue; + } else if (!maybe_x(bit) && !driving) { + result.is_0.append(invert[invert_pos++]); + result.is_1.append(bit); + result.is_x.append(State::S0); + continue; + } + auto &enc = encoded_bits.at(bit); + if (enc.is_0 == State::Sm) { + enc.is_0 = new_sigs.is_0[new_pos]; + enc.is_1 = new_sigs.is_1[new_pos]; + enc.is_x = new_sigs.is_x[new_pos]; + new_pos++; + } + if (driving) { + log_assert(!enc.driven); + enc.driven = true; + if (maybe_x(bit)) { + driven_orig.append(bit); + driven_enc.is_0.append(enc.is_0); + driven_enc.is_1.append(enc.is_1); + driven_enc.is_x.append(enc.is_x); + } else { + driven_never_x.first.append(bit); + driven_never_x.second.append(enc.is_1); + } + } + result.is_0.append(enc.is_0); + result.is_1.append(enc.is_1); + result.is_x.append(enc.is_x); + } + + if (!driven_orig.empty()) { + auto decoder = module->addBwmux(NEW_ID, driven_enc.is_1, Const(State::Sx, GetSize(driven_orig)), driven_enc.is_x, driven_orig); + decoder->set_bool_attribute(ID::xprop_decoder); + } + if (!driven_never_x.first.empty()) { + module->connect(driven_never_x); + } + + if (driving && (options.assert_encoding || options.assume_encoding)) { + auto not_0 = module->Not(NEW_ID, result.is_0); + auto not_1 = module->Not(NEW_ID, result.is_1); + auto not_x = module->Not(NEW_ID, result.is_x); + auto valid = module->ReduceAnd(NEW_ID, { + module->Eq(NEW_ID, result.is_0, module->And(NEW_ID, not_1, not_x)), + module->Eq(NEW_ID, result.is_1, module->And(NEW_ID, not_0, not_x)), + module->Eq(NEW_ID, result.is_x, module->And(NEW_ID, not_0, not_1)), + }); + if (options.assert_encoding) + module->addAssert(NEW_ID_SUFFIX("xprop_enc"), valid, State::S1); + else + module->addAssume(NEW_ID_SUFFIX("xprop_enc"), valid, State::S1); + if (options.debug_asserts) { + auto bad_bits = module->Bweqx(NEW_ID, {result.is_0, result.is_1, result.is_x}, Const(State::Sx, GetSize(result) * 3)); + module->addAssert(NEW_ID_SUFFIX("xprop_debug"), module->LogicNot(NEW_ID, bad_bits), State::S1); + } + } + + return result; + } + + void mark_all_maybe_x() + { + while (!pending_cell_queue.empty()) { + Cell *cell = pending_cell_queue.front(); + pending_cell_queue.pop_front(); + pending_cells.erase(cell); + + mark_maybe_x(cell); + } + } + + void mark_maybe_x(Cell *cell) { + if (cell->type.in(ID($bweqx), ID($eqx), ID($nex), ID($initstate), ID($assert), ID($assume), ID($cover), ID($anyseq), ID($anyconst))) + return; + + if (cell->type.in(ID($pmux))) { + mark_outputs_maybe_x(cell); + return; + } + + if (RTLIL::builtin_ff_cell_types().count(cell->type) || cell->type == ID($anyinit)) { + FfData ff(&initvals, cell); + + if (cell->type != ID($anyinit)) + for (int i = 0; i < ff.width; i++) + if (ff.val_init[i] == State::Sx) + mark_maybe_x(ff.sig_q[i]); + + for (int i = 0; i < ff.width; i++) + if (maybe_x(ff.sig_d[i])) + mark_maybe_x(ff.sig_q[i]); + + if ((ff.has_clk || ff.has_gclk) && !ff.has_ce && !ff.has_aload && !ff.has_srst && !ff.has_arst && !ff.has_sr) + return; + } + + if (cell->type == ID($not)) { + auto &sig_y = cell->getPort(ID::Y); + auto sig_a = cell->getPort(ID::A); sig_a.extend_u0(GetSize(sig_y), cell->getParam(ID::A_SIGNED).as_bool()); + for (int i = 0; i < GetSize(sig_y); i++) + if (maybe_x(sig_a[i])) + mark_maybe_x(sig_y[i]); + return; + } + + if (cell->type.in(ID($and), ID($or), ID($xor), ID($xnor))) { + auto &sig_y = cell->getPort(ID::Y); + auto sig_a = cell->getPort(ID::A); sig_a.extend_u0(GetSize(sig_y), cell->getParam(ID::A_SIGNED).as_bool()); + auto sig_b = cell->getPort(ID::B); sig_b.extend_u0(GetSize(sig_y), cell->getParam(ID::B_SIGNED).as_bool()); + for (int i = 0; i < GetSize(sig_y); i++) + if (maybe_x(sig_a[i]) || maybe_x(sig_b[i])) + mark_maybe_x(sig_y[i]); + return; + } + + if (cell->type.in(ID($bwmux))) { + auto &sig_y = cell->getPort(ID::Y); + auto &sig_a = cell->getPort(ID::A); + auto &sig_b = cell->getPort(ID::B); + auto &sig_s = cell->getPort(ID::S); + for (int i = 0; i < GetSize(sig_y); i++) + if (maybe_x(sig_a[i]) || maybe_x(sig_b[i]) || maybe_x(sig_s[i])) + mark_maybe_x(sig_y[i]); + return; + } + + if (cell->type.in(ID($_MUX_), ID($mux), ID($bmux))) { + auto &sig_y = cell->getPort(ID::Y); + auto &sig_a = cell->getPort(ID::A); + auto &sig_b = cell->getPort(ID::B); + auto &sig_s = cell->getPort(ID::S); + if (maybe_x(sig_s)) { + mark_maybe_x(sig_y); + return; + } + for (int i = 0; i < GetSize(sig_y); i++) { + if (maybe_x(sig_a[i])) { + mark_maybe_x(sig_y[i]); + continue; + } + for (int j = i; j < GetSize(sig_b); j += GetSize(sig_y)) { + if (maybe_x(sig_b[j])) { + mark_maybe_x(sig_y[i]); + break; + } + } + } + return; + } + + if (cell->type.in(ID($demux))) { + auto &sig_y = cell->getPort(ID::Y); + auto &sig_a = cell->getPort(ID::A); + auto &sig_s = cell->getPort(ID::S); + if (maybe_x(sig_s)) { + mark_maybe_x(sig_y); + return; + } + for (int i = 0; i < GetSize(sig_a); i++) + if (maybe_x(sig_a[i])) + for (int j = i; j < GetSize(sig_y); j += GetSize(sig_a)) + mark_maybe_x(sig_y[j]); + return; + } + + if (cell->type.in(ID($shl), ID($shr), ID($sshl), ID($sshr), ID($shift))) { + auto &sig_b = cell->getPort(ID::B); + auto &sig_y = cell->getPort(ID::Y); + + if (maybe_x(sig_b)) { + mark_maybe_x(sig_y); + return; + } + + auto &sig_a = cell->getPort(ID::A); + + if (maybe_x(sig_a)) { + // We could be more precise for shifts, but that's not required + // for correctness, so let's keep it simple + mark_maybe_x(sig_y); + return; + } + return; + } + + if (cell->type.in(ID($shiftx))) { + auto &sig_b = cell->getPort(ID::B); + auto &sig_y = cell->getPort(ID::Y); + + if (cell->getParam(ID::B_SIGNED).as_bool() || GetSize(sig_b) >= 30) { + mark_maybe_x(sig_y); + } else { + int max_shift = (1 << GetSize(sig_b)) - 1; + + auto &sig_a = cell->getPort(ID::A); + + for (int i = 0; i < GetSize(sig_y); i++) + if (i + max_shift >= GetSize(sig_a)) + mark_maybe_x(sig_y[i]); + } + + if (maybe_x(sig_b)) { + mark_maybe_x(sig_y); + return; + } + + auto &sig_a = cell->getPort(ID::A); + if (maybe_x(sig_a)) { + // We could be more precise for shifts, but that's not required + // for correctness, so let's keep it simple + mark_maybe_x(sig_y); + return; + } + return; + } + + if (cell->type.in(ID($add), ID($sub), ID($mul), ID($neg))) { + if (inputs_maybe_x(cell)) + mark_outputs_maybe_x(cell); + return; + } + + if (cell->type.in(ID($div), ID($mod), ID($divfloor), ID($modfloor))) { + mark_outputs_maybe_x(cell); + return; + } + + if (cell->type.in( + ID($le), ID($lt), ID($ge), ID($gt), + ID($reduce_and), ID($reduce_or), ID($reduce_xor), ID($reduce_xnor), + ID($reduce_bool), ID($logic_not), ID($logic_or), ID($logic_and), + ID($eq), ID($ne), + + ID($_NOT_), ID($_AND_), ID($_NAND_), ID($_ANDNOT_), ID($_OR_), ID($_NOR_), ID($_ORNOT_), ID($_XOR_), ID($_XNOR_) + )) { + auto &sig_y = cell->getPort(ID::Y); + if (inputs_maybe_x(cell)) + mark_maybe_x(sig_y[0]); + return; + } + + log_warning("Unhandled cell %s (%s) during maybe-x marking\n", log_id(cell), log_id(cell->type)); + mark_outputs_maybe_x(cell); + } + + void process_cells() + { + for (auto cell : module->selected_cells()) + process_cell(cell); + } + + void process_cell(Cell *cell) + { + if (!ports_maybe_x(cell)) { + + if (cell->type == ID($bweq)) { + auto sig_y = cell->getPort(ID::Y); + auto sig_a = cell->getPort(ID::A); + auto sig_b = cell->getPort(ID::B); + + auto name = cell->name; + module->remove(cell); + module->addXnor(name, sig_a, sig_b, sig_y); + return; + } + + if (cell->type.in(ID($nex), ID($eqx))) { + auto sig_y = cell->getPort(ID::Y); + auto sig_a = cell->getPort(ID::A); + auto sig_b = cell->getPort(ID::B); + + auto name = cell->name; + module->remove(cell); + if (cell->type == ID($eqx)) + module->addEq(name, sig_a, sig_b, sig_y); + else + module->addNe(name, sig_a, sig_b, sig_y); + return; + } + + return; + } + + if (cell->type.in(ID($not), ID($_NOT_))) { + auto &sig_y = cell->getPort(ID::Y); + auto sig_a = cell->getPort(ID::A); + if (cell->type == ID($not)) + sig_a.extend_u0(GetSize(sig_y), cell->getParam(ID::A_SIGNED).as_bool()); + + auto enc_a = encoded(sig_a); + auto enc_y = encoded(sig_y, true); + + enc_y.connect_x(enc_a.is_x); + enc_y.connect_0(enc_a.is_1); + enc_y.connect_1(enc_a.is_0); + + module->remove(cell); + return; + } + + if (cell->type.in(ID($and), ID($or), ID($_AND_), ID($_OR_), ID($_NAND_), ID($_NOR_), ID($_ANDNOT_), ID($_ORNOT_))) { + auto &sig_y = cell->getPort(ID::Y); + auto sig_a = cell->getPort(ID::A); + auto sig_b = cell->getPort(ID::B); + if (cell->type.in(ID($and), ID($or))) { + sig_a.extend_u0(GetSize(sig_y), cell->getParam(ID::A_SIGNED).as_bool()); + sig_b.extend_u0(GetSize(sig_y), cell->getParam(ID::B_SIGNED).as_bool()); + } + + auto enc_a = encoded(sig_a); + auto enc_b = encoded(sig_b); + auto enc_y = encoded(sig_y, true); + + if (cell->type.in(ID($or), ID($_OR_))) + enc_a.invert(), enc_b.invert(), enc_y.invert(); + if (cell->type.in(ID($_NAND_), ID($_NOR_))) + enc_y.invert(); + if (cell->type.in(ID($_ANDNOT_), ID($_ORNOT_))) + enc_b.invert(); + + enc_y.connect_0(module->Or(NEW_ID, enc_a.is_0, enc_b.is_0)); + enc_y.connect_1(module->And(NEW_ID, enc_a.is_1, enc_b.is_1)); + enc_y.auto_x(); + module->remove(cell); + return; + } + + if (cell->type.in(ID($reduce_and), ID($reduce_or), ID($reduce_bool), ID($logic_not))) { + auto &sig_y = cell->getPort(ID::Y); + auto &sig_a = cell->getPort(ID::A); + + auto enc_a = encoded(sig_a); + auto enc_y = encoded(sig_y, true); + + enc_y.connect_as_bool(); + + if (cell->type.in(ID($reduce_or), ID($reduce_bool))) + enc_a.invert(), enc_y.invert(); + if (cell->type == ID($logic_not)) + enc_a.invert(); + + enc_y.connect_0(module->ReduceOr(NEW_ID, enc_a.is_0)); + enc_y.connect_1(module->ReduceAnd(NEW_ID, enc_a.is_1)); + enc_y.auto_x(); + module->remove(cell); + + return; + } + + if (cell->type.in(ID($reduce_xor), ID($reduce_xnor))) { + auto &sig_y = cell->getPort(ID::Y); + auto &sig_a = cell->getPort(ID::A); + + auto enc_a = encoded(sig_a); + auto enc_y = encoded(sig_y, true); + + enc_y.connect_as_bool(); + if (cell->type == ID($reduce_xnor)) + enc_y.invert(); + + + enc_y.connect_x(module->ReduceOr(NEW_ID, enc_a.is_x)); + enc_y.connect_1_under_x(module->ReduceXor(NEW_ID, enc_a.is_1)); + enc_y.auto_0(); + module->remove(cell); + + return; + } + + if (cell->type.in(ID($logic_and), ID($logic_or))) { + auto &sig_y = cell->getPort(ID::Y); + auto &sig_a = cell->getPort(ID::A); + auto &sig_b = cell->getPort(ID::B); + + auto enc_a = encoded(sig_a); + auto enc_b = encoded(sig_b); + auto enc_y = encoded(sig_y, true); + + enc_y.connect_as_bool(); + + auto a_is_1 = module->ReduceOr(NEW_ID, enc_a.is_1); + auto a_is_0 = module->ReduceAnd(NEW_ID, enc_a.is_0); + auto b_is_1 = module->ReduceOr(NEW_ID, enc_b.is_1); + auto b_is_0 = module->ReduceAnd(NEW_ID, enc_b.is_0); + + if (cell->type == ID($logic_or)) + enc_y.invert(), std::swap(a_is_0, a_is_1), std::swap(b_is_0, b_is_1); + + enc_y.connect_0(module->Or(NEW_ID, a_is_0, b_is_0)); + enc_y.connect_1(module->And(NEW_ID, a_is_1, b_is_1)); + enc_y.auto_x(); + module->remove(cell); + return; + } + + if (cell->type.in(ID($xor), ID($xnor), ID($_XOR_), ID($_XNOR_))) { + auto &sig_y = cell->getPort(ID::Y); + auto sig_a = cell->getPort(ID::A); + auto sig_b = cell->getPort(ID::B); + if (cell->type.in(ID($xor), ID($xnor))) { + sig_a.extend_u0(GetSize(sig_y), cell->getParam(ID::A_SIGNED).as_bool()); + sig_b.extend_u0(GetSize(sig_y), cell->getParam(ID::B_SIGNED).as_bool()); + } + + auto enc_a = encoded(sig_a); + auto enc_b = encoded(sig_b); + auto enc_y = encoded(sig_y, true); + + if (cell->type.in(ID($xnor), ID($_XNOR_))) + enc_y.invert(); + + enc_y.connect_x(module->Or(NEW_ID, enc_a.is_x, enc_b.is_x)); + enc_y.connect_1_under_x(module->Xor(NEW_ID, enc_a.is_1, enc_b.is_1)); + enc_y.auto_0(); + module->remove(cell); + return; + } + + if (cell->type.in(ID($eq), ID($ne))) { + auto &sig_y = cell->getPort(ID::Y); + auto sig_a = cell->getPort(ID::A); + auto sig_b = cell->getPort(ID::B); + int width = std::max(GetSize(sig_a), GetSize(sig_b)); + sig_a.extend_u0(width, cell->getParam(ID::A_SIGNED).as_bool()); + sig_b.extend_u0(width, cell->getParam(ID::B_SIGNED).as_bool()); + + auto enc_a = encoded(sig_a); + auto enc_b = encoded(sig_b); + auto enc_y = encoded(sig_y, true); + enc_y.connect_as_bool(); + + if (cell->type == ID($ne)) + enc_y.invert(); + + auto delta = module->Xor(NEW_ID, enc_a.is_1, enc_b.is_1); + auto xpos = module->Or(NEW_ID, enc_a.is_x, enc_b.is_x); + + enc_y.connect_0(module->ReduceOr(NEW_ID, module->And(NEW_ID, delta, module->Not(NEW_ID, xpos)))); + enc_y.connect_x_under_0(module->ReduceOr(NEW_ID, xpos)); + enc_y.auto_1(); + module->remove(cell); + return; + } + + if (cell->type.in(ID($eqx), ID($nex))) { + auto &sig_y = cell->getPort(ID::Y); + auto sig_a = cell->getPort(ID::A); + auto sig_b = cell->getPort(ID::B); + int width = std::max(GetSize(sig_a), GetSize(sig_b)); + sig_a.extend_u0(width, cell->getParam(ID::A_SIGNED).as_bool()); + sig_b.extend_u0(width, cell->getParam(ID::B_SIGNED).as_bool()); + + auto enc_a = encoded(sig_a); + auto enc_b = encoded(sig_b); + + auto delta_0 = module->Xnor(NEW_ID, enc_a.is_0, enc_b.is_0); + auto delta_1 = module->Xnor(NEW_ID, enc_a.is_1, enc_b.is_1); + + auto eq = module->ReduceAnd(NEW_ID, {delta_0, delta_1}); + + auto res = cell->type == ID($nex) ? module->Not(NEW_ID, eq) : eq; + + module->connect(sig_y[0], res); + if (GetSize(sig_y) > 1) + module->connect(sig_y.extract(1, GetSize(sig_y) - 1), Const(State::S0, GetSize(sig_y) - 1)); + module->remove(cell); + return; + } + + if (cell->type.in(ID($bweqx))) { + auto &sig_y = cell->getPort(ID::Y); + auto &sig_a = cell->getPort(ID::A); + auto &sig_b = cell->getPort(ID::B); + + auto enc_a = encoded(sig_a); + auto enc_b = encoded(sig_b); + + auto delta_0 = module->Xnor(NEW_ID, enc_a.is_0, enc_b.is_0); + auto delta_1 = module->Xnor(NEW_ID, enc_a.is_1, enc_b.is_1); + module->addAnd(NEW_ID, delta_0, delta_1, sig_y); + module->remove(cell); + return; + } + + if (cell->type.in(ID($_MUX_), ID($mux), ID($bwmux))) { + auto &sig_y = cell->getPort(ID::Y); + auto &sig_a = cell->getPort(ID::A); + auto &sig_b = cell->getPort(ID::B); + auto sig_s = cell->getPort(ID::S); + + if (cell->type == ID($mux)) + sig_s = SigSpec(sig_s[0], GetSize(sig_y)); + + auto enc_a = encoded(sig_a); + auto enc_b = encoded(sig_b); + auto enc_s = encoded(sig_s); + auto enc_y = encoded(sig_y, true); + + enc_y.connect_1(module->And(NEW_ID, + module->Or(NEW_ID, enc_a.is_1, enc_s.is_1), + module->Or(NEW_ID, enc_b.is_1, enc_s.is_0))); + enc_y.connect_0(module->And(NEW_ID, + module->Or(NEW_ID, enc_a.is_0, enc_s.is_1), + module->Or(NEW_ID, enc_b.is_0, enc_s.is_0))); + enc_y.auto_x(); + module->remove(cell); + return; + } + + if (cell->type.in(ID($pmux))) { + auto &sig_y = cell->getPort(ID::Y); + auto &sig_a = cell->getPort(ID::A); + auto &sig_b = cell->getPort(ID::B); + auto &sig_s = cell->getPort(ID::S); + + auto enc_a = encoded(sig_a); + auto enc_b = encoded(sig_b); + auto enc_s = encoded(sig_s); + auto enc_y = encoded(sig_y, true); + + int width = GetSize(enc_y); + + auto all_x = module->ReduceOr(NEW_ID, { + enc_s.is_x, + module->And(NEW_ID, enc_s.is_1, module->Sub(NEW_ID, enc_s.is_1, Const(1, width))) + }); + + auto selected = enc_a; + + for (int i = 0; i < GetSize(enc_s); i++) { + auto sel_bit = enc_s.is_1[i]; + selected.is_0 = module->Mux(NEW_ID, selected.is_0, enc_b.is_0.extract(i * width, width), sel_bit); + selected.is_1 = module->Mux(NEW_ID, selected.is_1, enc_b.is_1.extract(i * width, width), sel_bit); + selected.is_x = module->Mux(NEW_ID, selected.is_x, enc_b.is_x.extract(i * width, width), sel_bit); + } + + enc_y.connect_0(module->Mux(NEW_ID, selected.is_0, Const(State::S0, width), all_x)); + enc_y.connect_1(module->Mux(NEW_ID, selected.is_1, Const(State::S0, width), all_x)); + enc_y.connect_x(module->Mux(NEW_ID, selected.is_x, Const(State::S1, width), all_x)); + + module->remove(cell); + return; + } + + if (cell->type.in(ID($shl), ID($shr), ID($sshl), ID($sshr), ID($shift), ID($shiftx))) { + auto &sig_y = cell->getPort(ID::Y); + auto &sig_a = cell->getPort(ID::A); + auto &sig_b = cell->getPort(ID::B); + + auto enc_a = encoded(sig_a); + auto enc_b = encoded(sig_b); + auto enc_y = encoded(sig_y, true); + + auto all_x = module->ReduceOr(NEW_ID, enc_b.is_x)[0]; + auto not_all_x = module->Not(NEW_ID, all_x)[0]; + + SigSpec y_not_0 = module->addWire(NEW_ID, GetSize(sig_y)); + SigSpec y_1 = module->addWire(NEW_ID, GetSize(sig_y)); + SigSpec y_x = module->addWire(NEW_ID, GetSize(sig_y)); + + auto encoded_type = cell->type == ID($shiftx) ? ID($shift) : cell->type; + + if (cell->type == ID($shiftx)) { + std::swap(enc_a.is_0, enc_a.is_x); + } + + auto shift_0 = module->addCell(NEW_ID, encoded_type); + shift_0->parameters = cell->parameters; + shift_0->setPort(ID::A, module->Not(NEW_ID, enc_a.is_0)); + shift_0->setPort(ID::B, enc_b.is_1); + shift_0->setPort(ID::Y, y_not_0); + + auto shift_1 = module->addCell(NEW_ID, encoded_type); + shift_1->parameters = cell->parameters; + shift_1->setPort(ID::A, enc_a.is_1); + shift_1->setPort(ID::B, enc_b.is_1); + shift_1->setPort(ID::Y, y_1); + + auto shift_x = module->addCell(NEW_ID, encoded_type); + shift_x->parameters = cell->parameters; + shift_x->setPort(ID::A, enc_a.is_x); + shift_x->setPort(ID::B, enc_b.is_1); + shift_x->setPort(ID::Y, y_x); + + SigSpec y_0 = module->Not(NEW_ID, y_not_0); + + if (cell->type == ID($shiftx)) + std::swap(y_0, y_x); + + enc_y.connect_0(module->And(NEW_ID, y_0, SigSpec(not_all_x, GetSize(sig_y)))); + enc_y.connect_1(module->And(NEW_ID, y_1, SigSpec(not_all_x, GetSize(sig_y)))); + enc_y.connect_x(module->Or(NEW_ID, y_x, SigSpec(all_x, GetSize(sig_y)))); + + module->remove(cell); + return; + } + + if (cell->type.in(ID($ff))) { + auto &sig_d = cell->getPort(ID::D); + auto &sig_q = cell->getPort(ID::Q); + + auto init_q = initvals(sig_q); + auto init_q_is_1 = init_q; + auto init_q_is_x = init_q; + + for (auto &bit : init_q_is_1) + bit = bit == State::S1 ? State::S1 : State::S0; + for (auto &bit : init_q_is_x) + bit = bit == State::Sx ? State::S1 : State::S0; + + initvals.remove_init(sig_q); + + auto enc_d = encoded(sig_d); + auto enc_q = encoded(sig_q, true); + + auto data_q = module->addWire(NEW_ID, GetSize(sig_q)); + + module->addFf(NEW_ID, enc_d.is_1, data_q); + module->addFf(NEW_ID, enc_d.is_x, enc_q.is_x); + + initvals.set_init(data_q, init_q_is_1); + initvals.set_init(enc_q.is_x, init_q_is_x); + + enc_q.connect_1_under_x(data_q); + enc_q.auto_0(); + + module->remove(cell); + return; + } + + if (RTLIL::builtin_ff_cell_types().count(cell->type) || cell->type == ID($anyinit)) { + FfData ff(&initvals, cell); + + if ((ff.has_clk || ff.has_gclk) && !ff.has_ce && !ff.has_aload && !ff.has_srst && !ff.has_arst && !ff.has_sr) { + if (ff.has_clk && maybe_x(ff.sig_clk)) { + log_warning("Only non-x CLK inputs are currently supported for %s (%s)\n", log_id(cell), log_id(cell->type)); + } else { + auto init_q = ff.val_init; + auto init_q_is_1 = init_q; + auto init_q_is_x = init_q; + + if (ff.is_anyinit) { + for (auto &bit : init_q_is_1) + bit = State::Sx; + for (auto &bit : init_q_is_x) + bit = State::S0; + } else { + for (auto &bit : init_q_is_1) + bit = bit == State::S1 ? State::S1 : State::S0; + for (auto &bit : init_q_is_x) + bit = bit == State::Sx ? State::S1 : State::S0; + } + + ff.remove(); + + auto enc_d = encoded(ff.sig_d); + auto enc_q = encoded(ff.sig_q, true); + + auto data_q = module->addWire(NEW_ID, GetSize(ff.sig_q)); + + ff.sig_d = enc_d.is_1; + ff.sig_q = data_q; + ff.val_init = init_q_is_1; + ff.emit(); + + ff.name = NEW_ID; + ff.cell = nullptr; + ff.sig_d = enc_d.is_x; + ff.sig_q = enc_q.is_x; + ff.is_anyinit = false; + ff.val_init = init_q_is_x; + ff.emit(); + + + enc_q.connect_1_under_x(data_q); + enc_q.auto_0(); + + return; + } + } else { + log_warning("Unhandled FF-cell %s (%s), consider running clk2fflogic, async2sync and/or dffunmap\n", log_id(cell), log_id(cell->type)); + } + } + + // Celltypes where any input x bit makes the whole output x + if (cell->type.in( + ID($neg), + ID($le), ID($lt), ID($ge), ID($gt), + ID($add), ID($sub), ID($mul), ID($div), ID($mod), ID($divfloor), ID($modfloor) + )) { + + SigSpec inbits_x; + for (auto &conn : cell->connections()) { + if (cell->input(conn.first)) { + auto enc_port = encoded(conn.second); + inbits_x.append(enc_port.is_x); + cell->setPort(conn.first, enc_port.is_1); + } + } + + if (cell->type.in(ID($div), ID($mod), ID($divfloor), ID($modfloor))) { + auto sig_b = cell->getPort(ID::B); + auto invalid = module->LogicNot(NEW_ID, sig_b); + inbits_x.append(invalid); + sig_b[0] = module->Or(NEW_ID, sig_b[0], invalid); + cell->setPort(ID::B, sig_b); + } + + SigBit outbits_x = (GetSize(inbits_x) == 1 ? inbits_x : module->ReduceOr(NEW_ID, inbits_x)); + + bool bool_out = cell->type.in(ID($le), ID($lt), ID($ge), ID($gt)); + + for (auto &conn : cell->connections()) { + if (cell->output(conn.first)) { + auto enc_port = encoded(conn.second, true); + if (bool_out) + enc_port.connect_as_bool(); + + SigSpec new_output = module->addWire(NEW_ID, GetSize(conn.second)); + + enc_port.connect_1_under_x(bool_out ? new_output.extract(0) : new_output); + enc_port.connect_x(SigSpec(outbits_x, GetSize(enc_port))); + enc_port.auto_0(); + + cell->setPort(conn.first, new_output); + } + } + + return; + } + + if (cell->type == ID($bmux)) // TODO might want to support bmux natively anyway + log("Running 'bmuxmap' preserves x-propagation and can be run before 'xprop'.\n"); + if (cell->type == ID($demux)) // TODO might want to support demux natively anyway + log("Running 'demuxmap' preserves x-propagation and can be run before 'xprop'.\n"); + + if (options.required) + log_error("Unhandled cell %s (%s)\n", log_id(cell), log_id(cell->type)); + else + log_warning("Unhandled cell %s (%s)\n", log_id(cell), log_id(cell->type)); + } + + void split_ports() + { + if (!options.split_inputs && !options.split_outputs) + return; + + int port_id = 1; + + for (auto port : module->ports) { + auto wire = module->wire(port); + if (module->design->selected(module, wire)) { + if (wire->port_input == wire->port_output) { + log_warning("Port %s not an input or an output port which is not supported by xprop\n", log_id(wire)); + } else if ((options.split_inputs && !options.assume_def_inputs && wire->port_input) || (options.split_outputs && wire->port_output)) { + auto port_d = module->uniquify(stringf("%s_d", port.c_str())); + auto port_x = module->uniquify(stringf("%s_x", port.c_str())); + + auto wire_d = module->addWire(port_d, GetSize(wire)); + auto wire_x = module->addWire(port_x, GetSize(wire)); + + wire_d->port_input = wire->port_input; + wire_d->port_output = wire->port_output; + wire_d->port_id = port_id++; + + wire_x->port_input = wire->port_input; + wire_x->port_output = wire->port_output; + wire_x->port_id = port_id++; + + if (wire->port_output) { + auto enc = encoded(wire); + module->connect(wire_d, enc.is_1); + module->connect(wire_x, enc.is_x); + + if (options.split_public) { + // Need to hide the original wire so split_public doesn't try to split it again + module->rename(wire, NEW_ID_SUFFIX(wire->name.c_str())); + } + } else { + auto enc = encoded(wire, true); + + enc.connect_x(wire_x); + enc.connect_1_under_x(wire_d); + enc.auto_0(); + } + + wire->port_input = wire->port_output = false; + wire->port_id = 0; + + continue; + } + } + wire->port_id = port_id++; + } + + module->fixup_ports(); + } + + void split_public() + { + if (!options.split_public) + return; + + for (auto wire : module->selected_wires()) { + if (wire->port_input || wire->port_output || !wire->name.isPublic()) + continue; + auto name_d = module->uniquify(stringf("%s_d", wire->name.c_str())); + auto name_x = module->uniquify(stringf("%s_x", wire->name.c_str())); + + auto wire_d = module->addWire(name_d, GetSize(wire)); + auto wire_x = module->addWire(name_x, GetSize(wire)); + + auto enc = encoded(wire); + module->connect(wire_d, enc.is_1); + module->connect(wire_x, enc.is_x); + + module->rename(wire, NEW_ID_SUFFIX(wire->name.c_str())); + } + } + + void encode_remaining() + { + pool<Wire *> enc_undriven_wires; + + for (auto &enc_bit : encoded_bits) { + if (!enc_bit.second.driven) { + log_assert(enc_bit.first.is_wire()); + enc_undriven_wires.insert(enc_bit.first.wire); + } + } + + if (options.formal && !enc_undriven_wires.empty()) { + for (auto &bit : enc_undriven_wires) + log_warning("Found encoded wire %s having a non-encoded driver\n", log_signal(bit)); + + log_error("Found encoded wires having a non-encoded driver, not allowed in -formal mode\n"); + } + + + for (auto wire : enc_undriven_wires) { + SigSpec sig(sigmap(wire)); + + SigSpec orig; + EncodedSig enc; + + for (auto bit : sig) { + auto it = encoded_bits.find(bit); + if (it == encoded_bits.end() || it->second.driven) + continue; + orig.append(bit); + enc.is_0.append(it->second.is_0); + enc.is_1.append(it->second.is_1); + enc.is_x.append(it->second.is_x); + it->second.driven = true; + } + + module->addBweqx(NEW_ID, orig, Const(State::S0, GetSize(orig)), enc.is_0); + module->addBweqx(NEW_ID, orig, Const(State::S1, GetSize(orig)), enc.is_1); + module->addBweqx(NEW_ID, orig, Const(State::Sx, GetSize(orig)), enc.is_x); + } + } +}; + +struct XpropPass : public Pass { + XpropPass() : Pass("xprop", "formal x propagation") {} + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" xprop [options] [selection]\n"); + log("\n"); + log("This pass transforms the circuit into an equivalent circuit that explicitly\n"); + log("encodes the propagation of x values using purely 2-valued logic. On the\n"); + log("interface between xprop-transformed and non-transformed parts of the design,\n"); + log("appropriate conversions are inserted automatically.\n"); + log("\n"); + log(" -split-inputs\n"); + log(" -split-outputs\n"); + log(" -split-ports\n"); + log(" Replace each input/output/port with two new ports, one carrying the\n"); + log(" defined values (named <portname>_d) and one carrying the mask of which\n"); + log(" bits are x (named <portname>_x). When a bit in the <portname>_x is set\n"); + log(" the corresponding bit in <portname>_d is ignored for inputs and\n"); + log(" guaranteed to be 0 for outputs.\n"); + log("\n"); + log(" -split-public\n"); + log(" Replace each public non-port wire with two new wires, one carrying the\n"); + log(" defined values (named <wirename>_d) and one carrying the mask of which\n"); + log(" bits are x (named <wirename>_x). When a bit in the <portname>_x is set\n"); + log(" the corresponding bit in <wirename>_d is guaranteed to be 0 for\n"); + log(" outputs.\n"); + log("\n"); + log(" -assume-encoding\n"); + log(" Add encoding invariants as assumptions. This can speed up formal\n"); + log(" verification tasks.\n"); + log("\n"); + log(" -assert-encoding\n"); + log(" Add encoding invariants as assertions. Used for testing the xprop\n"); + log(" pass itself.\n"); + log("\n"); + log(" -assume-def-inputs\n"); + log(" Assume all inputs are fully defined. This adds corresponding\n"); + log(" assumptions to the design and uses these assumptions to optimize the\n"); + log(" xprop encoding.\n"); + log("\n"); + log(" -required\n"); + log(" Produce a runtime error if any encountered cell could not be encoded.\n"); + log("\n"); + log(" -formal\n"); + log(" Produce a runtime error if any encoded cell uses a signal that is\n"); + log(" neither known to be non-x nor driven by another encoded cell.\n"); + log("\n"); + log(" -debug-asserts\n"); + log(" Add assertions checking that the encoding used by this pass never\n"); + log(" produces x values within the encoded signals.\n"); + log("\n"); + } + void execute(std::vector<std::string> args, RTLIL::Design *design) override + { + XpropOptions options; + + log_header(design, "Executing XPROP pass.\n"); + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) { + if (args[argidx] == "-split-ports") { + options.split_inputs = true; + options.split_outputs = true; + continue; + } + if (args[argidx] == "-split-inputs") { + options.split_inputs = true; + continue; + } + if (args[argidx] == "-split-outputs") { + options.split_outputs = true; + continue; + } + if (args[argidx] == "-split-public") { + options.split_public = true; + continue; + } + if (args[argidx] == "-assume-encoding") { + options.assume_encoding = true; + continue; + } + if (args[argidx] == "-assert-encoding") { + options.assert_encoding = true; + continue; + } + if (args[argidx] == "-assume-def-inputs") { + options.assume_def_inputs = true; + continue; + } + if (args[argidx] == "-required") { + options.required = true; // TODO check more + continue; + } + if (args[argidx] == "-formal") { + options.formal = true; + options.required = true; + continue; + } + if (args[argidx] == "-debug-asserts") { // TODO documented + options.debug_asserts = true; + options.assert_encoding = true; + continue; + } + break; + } + + if (options.assert_encoding && options.assume_encoding) + log_cmd_error("The options -assert-encoding and -assume-encoding are exclusive.\n"); + + extra_args(args, argidx, design); + + log_push(); + Pass::call(design, "bmuxmap"); + Pass::call(design, "demuxmap"); + log_pop(); + + for (auto module : design->selected_modules()) { + if (options.assume_def_inputs) { + for (auto port : module->ports) { + auto wire = module->wire(port); + if (!module->design->selected(module, wire)) + continue; + + if (wire->port_input) { + module->addAssume(NEW_ID, module->Not(NEW_ID, module->ReduceOr(NEW_ID, module->Bweqx(NEW_ID, wire, Const(State::Sx, GetSize(wire))))), State::S1); + } + } + } + + XpropWorker worker(module, options); + log_debug("Marking all x-bits.\n"); + worker.mark_all_maybe_x(); + log_debug("Repalcing cells.\n"); + worker.process_cells(); + log_debug("Splitting ports.\n"); + worker.split_ports(); + log_debug("Splitting public signals.\n"); + worker.split_public(); + log_debug("Encode remaining signals.\n"); + worker.encode_remaining(); + + } + } +} XpropPass; + +PRIVATE_NAMESPACE_END diff --git a/passes/equiv/equiv_make.cc b/passes/equiv/equiv_make.cc index 56bc50ced..e15e510be 100644 --- a/passes/equiv/equiv_make.cc +++ b/passes/equiv/equiv_make.cc @@ -41,16 +41,6 @@ struct EquivMakeWorker pool<SigBit> undriven_bits; SigMap assign_map; - dict<SigBit, pool<Cell*>> bit2driven; // map: bit <--> and its driven cells - - CellTypes comb_ct; - - EquivMakeWorker() - { - comb_ct.setup_internals(); - comb_ct.setup_stdcells(); - } - void read_blacklists() { for (auto fn : blacklists) @@ -154,6 +144,7 @@ struct EquivMakeWorker { SigMap assign_map(equiv_mod); SigMap rd_signal_map; + SigPool primary_inputs; // list of cells without added $equiv cells auto cells_list = equiv_mod->cells().to_vector(); @@ -278,6 +269,9 @@ struct EquivMakeWorker gate_wire->port_input = false; equiv_mod->connect(gold_wire, wire); equiv_mod->connect(gate_wire, wire); + primary_inputs.add(assign_map(gold_wire)); + primary_inputs.add(assign_map(gate_wire)); + primary_inputs.add(wire); } else { @@ -309,31 +303,19 @@ struct EquivMakeWorker } } - init_bit2driven(); - - pool<Cell*> visited_cells; for (auto c : cells_list) for (auto &conn : c->connections()) if (!ct.cell_output(c->type, conn.first)) { SigSpec old_sig = assign_map(conn.second); SigSpec new_sig = rd_signal_map(old_sig); - - if(old_sig != new_sig) { - SigSpec tmp_sig = old_sig; - for (int i = 0; i < GetSize(old_sig); i++) { - SigBit old_bit = old_sig[i], new_bit = new_sig[i]; - - visited_cells.clear(); - if (check_signal_in_fanout(visited_cells, old_bit, new_bit)) - continue; - - log("Changing input %s of cell %s (%s): %s -> %s\n", - log_id(conn.first), log_id(c), log_id(c->type), - log_signal(old_bit), log_signal(new_bit)); - - tmp_sig[i] = new_bit; - } - c->setPort(conn.first, tmp_sig); + for (int i = 0; i < GetSize(old_sig); i++) + if (primary_inputs.check(old_sig[i])) + new_sig[i] = old_sig[i]; + if (old_sig != new_sig) { + log("Changing input %s of cell %s (%s): %s -> %s\n", + log_id(conn.first), log_id(c), log_id(c->type), + log_signal(old_sig), log_signal(new_sig)); + c->setPort(conn.first, new_sig); } } @@ -432,57 +414,6 @@ struct EquivMakeWorker } } - void init_bit2driven() - { - for (auto cell : equiv_mod->cells()) { - if (!ct.cell_known(cell->type) && !cell->type.in(ID($dff), ID($_DFF_P_), ID($_DFF_N_), ID($ff), ID($_FF_))) - continue; - for (auto &conn : cell->connections()) - { - if (yosys_celltypes.cell_input(cell->type, conn.first)) - for (auto bit : assign_map(conn.second)) - { - bit2driven[bit].insert(cell); - } - } - } - } - - bool check_signal_in_fanout(pool<Cell*> & visited_cells, SigBit source_bit, SigBit target_bit) - { - if (source_bit == target_bit) - return true; - - if (bit2driven.count(source_bit) == 0) - return false; - - auto driven_cells = bit2driven.at(source_bit); - for (auto driven_cell: driven_cells) - { - bool is_comb = comb_ct.cell_known(driven_cell->type); - if (!is_comb) - continue; - - if (visited_cells.count(driven_cell) > 0) - continue; - visited_cells.insert(driven_cell); - - for (auto &conn: driven_cell->connections()) - { - if (yosys_celltypes.cell_input(driven_cell->type, conn.first)) - continue; - - for (auto bit: conn.second) { - bool is_in_fanout = check_signal_in_fanout(visited_cells, bit, target_bit); - if (is_in_fanout == true) - return true; - } - } - } - - return false; - } - void run() { copy_to_equiv(); diff --git a/passes/equiv/equiv_opt.cc b/passes/equiv/equiv_opt.cc index 4d0400448..f5eb75730 100644 --- a/passes/equiv/equiv_opt.cc +++ b/passes/equiv/equiv_opt.cc @@ -60,13 +60,16 @@ struct EquivOptPass:public ScriptPass log(" -undef\n"); log(" enable modelling of undef states during equiv_induct.\n"); log("\n"); + log(" -nocheck\n"); + log(" disable running check before and after the command under test.\n"); + log("\n"); log("The following commands are executed by this verification command:\n"); help_script(); log("\n"); } std::string command, techmap_opts, make_opts; - bool assert, undef, multiclock, async2sync; + bool assert, undef, multiclock, async2sync, nocheck; void clear_flags() override { @@ -77,6 +80,7 @@ struct EquivOptPass:public ScriptPass undef = false; multiclock = false; async2sync = false; + nocheck = false; } void execute(std::vector < std::string > args, RTLIL::Design * design) override @@ -110,6 +114,10 @@ struct EquivOptPass:public ScriptPass undef = true; continue; } + if (args[argidx] == "-nocheck") { + nocheck = true; + continue; + } if (args[argidx] == "-multiclock") { multiclock = true; continue; @@ -153,10 +161,14 @@ struct EquivOptPass:public ScriptPass if (check_label("run_pass")) { run("hierarchy -auto-top"); run("design -save preopt"); + if (!nocheck) + run("check -assert", "(unless -nocheck)"); if (help_mode) run("[command]"); else run(command); + if (!nocheck) + run("check -assert", "(unless -nocheck)"); run("design -stash postopt"); } diff --git a/passes/fsm/fsm.cc b/passes/fsm/fsm.cc index 0c5e624dc..8e7e09d4c 100644 --- a/passes/fsm/fsm.cc +++ b/passes/fsm/fsm.cc @@ -67,6 +67,15 @@ struct FsmPass : public Pass { log(" -encfile file\n"); log(" passed through to fsm_recode pass\n"); log("\n"); + log("This pass uses a subset of FF types to detect FSMs. Run 'opt -nosdff -nodffe'\n"); + log("before this pass to prepare the design.\n"); + log("\n"); +#ifdef YOSYS_ENABLE_VERIFIC + log("The Verific frontend may merge multiplexers in a way that interferes with FSM\n"); + log("detection. Run 'verific -cfg db_infer_wide_muxes_post_elaboration 0' before\n"); + log("reading the source, and 'bmuxmap' after 'proc' for best results.\n"); + log("\n"); +#endif } void execute(std::vector<std::string> args, RTLIL::Design *design) override { diff --git a/passes/fsm/fsm_detect.cc b/passes/fsm/fsm_detect.cc index a2d38a0bd..86d654cc4 100644 --- a/passes/fsm/fsm_detect.cc +++ b/passes/fsm/fsm_detect.cc @@ -118,7 +118,7 @@ static bool check_state_users(RTLIL::SigSpec sig) return true; } -static void detect_fsm(RTLIL::Wire *wire) +static void detect_fsm(RTLIL::Wire *wire, bool ignore_self_reset=false) { bool has_fsm_encoding_attr = wire->attributes.count(ID::fsm_encoding) > 0 && wire->attributes.at(ID::fsm_encoding).decode_string() != "none"; bool has_fsm_encoding_none = wire->attributes.count(ID::fsm_encoding) > 0 && wire->attributes.at(ID::fsm_encoding).decode_string() == "none"; @@ -199,7 +199,7 @@ static void detect_fsm(RTLIL::Wire *wire) } SigSpec sig_y = sig_d, sig_undef; - if (ce.eval(sig_y, sig_undef)) + if (!ignore_self_reset && ce.eval(sig_y, sig_undef)) is_self_resetting = true; } @@ -261,25 +261,50 @@ struct FsmDetectPass : public Pass { { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); - log(" fsm_detect [selection]\n"); + log(" fsm_detect [options] [selection]\n"); log("\n"); log("This pass detects finite state machines by identifying the state signal.\n"); log("The state signal is then marked by setting the attribute 'fsm_encoding'\n"); log("on the state signal to \"auto\".\n"); log("\n"); + log(" -ignore-self-reset\n"); + log(" Mark FSMs even if they are self-resetting\n"); + log("\n"); log("Existing 'fsm_encoding' attributes are not changed by this pass.\n"); log("\n"); log("Signals can be protected from being detected by this pass by setting the\n"); log("'fsm_encoding' attribute to \"none\".\n"); log("\n"); + log("This pass uses a subset of FF types to detect FSMs. Run 'opt -nosdff -nodffe'\n"); + log("before this pass to prepare the design for fsm_detect.\n"); + log("\n"); +#ifdef YOSYS_ENABLE_VERIFIC + log("The Verific frontend may optimize the design in a way that interferes with FSM\n"); + log("detection. Run 'verific -cfg db_infer_wide_muxes_post_elaboration 0' before\n"); + log("reading the source, and 'bmuxmap -pmux' after 'proc' for best results.\n"); + log("\n"); +#endif } void execute(std::vector<std::string> args, RTLIL::Design *design) override { log_header(design, "Executing FSM_DETECT pass (finding FSMs in design).\n"); - extra_args(args, 1, design); + + bool ignore_self_reset = false; + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) + { + if (args[argidx] == "-ignore-self-reset") { + ignore_self_reset = true; + continue; + } + break; + } + extra_args(args, argidx, design); CellTypes ct; ct.setup_internals(); + ct.setup_internals_anyinit(); ct.setup_internals_mem(); ct.setup_stdcells(); ct.setup_stdcells_mem(); @@ -311,7 +336,7 @@ struct FsmDetectPass : public Pass { sig_at_port.add(assign_map(wire)); for (auto wire : module->selected_wires()) - detect_fsm(wire); + detect_fsm(wire, ignore_self_reset); } assign_map.clear(); diff --git a/passes/hierarchy/hierarchy.cc b/passes/hierarchy/hierarchy.cc index d40d6e59f..bf0137503 100644 --- a/passes/hierarchy/hierarchy.cc +++ b/passes/hierarchy/hierarchy.cc @@ -439,7 +439,8 @@ void check_cell_connections(const RTLIL::Module &module, RTLIL::Cell &cell, RTLI } } -bool expand_module(RTLIL::Design *design, RTLIL::Module *module, bool flag_check, bool flag_simcheck, std::vector<std::string> &libdirs) +bool expand_module(RTLIL::Design *design, RTLIL::Module *module, bool flag_check, bool flag_simcheck, bool flag_smtcheck, + std::vector<std::string> &libdirs) { bool did_something = false; std::map<RTLIL::Cell*, std::pair<int, int>> array_cells; @@ -477,7 +478,7 @@ bool expand_module(RTLIL::Design *design, RTLIL::Module *module, bool flag_check RTLIL::Module *mod = design->module(cell->type); if (!mod) { - mod = get_module(*design, *cell, *module, flag_check || flag_simcheck, libdirs); + mod = get_module(*design, *cell, *module, flag_check || flag_simcheck || flag_smtcheck, libdirs); // If we still don't have a module, treat the cell as a black box and skip // it. Otherwise, we either loaded or derived something so should set the @@ -495,11 +496,11 @@ bool expand_module(RTLIL::Design *design, RTLIL::Module *module, bool flag_check // interfaces. if_expander.visit_connections(*cell, *mod); - if (flag_check || flag_simcheck) + if (flag_check || flag_simcheck || flag_smtcheck) check_cell_connections(*module, *cell, *mod); if (mod->get_blackbox_attribute()) { - if (flag_simcheck) + if (flag_simcheck || (flag_smtcheck && !mod->get_bool_attribute(ID::smtlib2_module))) log_error("Module `%s' referenced in module `%s' in cell `%s' is a blackbox/whitebox module.\n", cell->type.c_str(), module->name.c_str(), cell->name.c_str()); continue; @@ -737,6 +738,9 @@ struct HierarchyPass : public Pass { log(" like -check, but also throw an error if blackbox modules are\n"); log(" instantiated, and throw an error if the design has no top module.\n"); log("\n"); + log(" -smtcheck\n"); + log(" like -simcheck, but allow smtlib2_module modules.\n"); + log("\n"); log(" -purge_lib\n"); log(" by default the hierarchy command will not remove library (blackbox)\n"); log(" modules. use this option to also remove unused blackbox modules.\n"); @@ -803,6 +807,7 @@ struct HierarchyPass : public Pass { bool flag_check = false; bool flag_simcheck = false; + bool flag_smtcheck = false; bool purge_lib = false; RTLIL::Module *top_mod = NULL; std::string load_top_mod; @@ -821,7 +826,7 @@ struct HierarchyPass : public Pass { size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) { - if (args[argidx] == "-generate" && !flag_check && !flag_simcheck && !top_mod) { + if (args[argidx] == "-generate" && !flag_check && !flag_simcheck && !flag_smtcheck && !top_mod) { generate_mode = true; log("Entering generate mode.\n"); while (++argidx < args.size()) { @@ -868,6 +873,10 @@ struct HierarchyPass : public Pass { flag_simcheck = true; continue; } + if (args[argidx] == "-smtcheck") { + flag_smtcheck = true; + continue; + } if (args[argidx] == "-purge_lib") { purge_lib = true; continue; @@ -951,7 +960,7 @@ struct HierarchyPass : public Pass { if (top_mod == nullptr && !load_top_mod.empty()) { #ifdef YOSYS_ENABLE_VERIFIC if (verific_import_pending) { - verific_import(design, parameters, load_top_mod); + load_top_mod = verific_import(design, parameters, load_top_mod); top_mod = design->module(RTLIL::escape_id(load_top_mod)); } #endif @@ -1013,7 +1022,7 @@ struct HierarchyPass : public Pass { } } - if (flag_simcheck && top_mod == nullptr) + if ((flag_simcheck || flag_smtcheck) && top_mod == nullptr) log_error("Design has no top module.\n"); if (top_mod != NULL) { @@ -1039,7 +1048,7 @@ struct HierarchyPass : public Pass { } for (auto module : used_modules) { - if (expand_module(design, module, flag_check, flag_simcheck, libdirs)) + if (expand_module(design, module, flag_check, flag_simcheck, flag_smtcheck, libdirs)) did_something = true; } @@ -1049,6 +1058,7 @@ struct HierarchyPass : public Pass { if (tmp_top_mod != NULL) { if (tmp_top_mod != top_mod){ top_mod = tmp_top_mod; + top_mod->attributes[ID::initial_top] = RTLIL::Const(1); did_something = true; } } diff --git a/passes/hierarchy/submod.cc b/passes/hierarchy/submod.cc index 845dc850f..8476d392c 100644 --- a/passes/hierarchy/submod.cc +++ b/passes/hierarchy/submod.cc @@ -260,6 +260,7 @@ struct SubmodWorker } ct.setup_internals(); + ct.setup_internals_anyinit(); ct.setup_internals_mem(); ct.setup_stdcells(); ct.setup_stdcells_mem(); @@ -347,8 +348,8 @@ struct SubmodPass : public Pass { log("\n"); log(" -hidden\n"); log(" instead of creating submodule ports with public names, create ports with\n"); - log(" private names so that a subsequent 'flatten; clean' call will restore the\n"); - log(" original module with original public names.\n"); + log(" private names so that a subsequent 'flatten; clean' call will restore\n"); + log(" the original module with original public names.\n"); log("\n"); } void execute(std::vector<std::string> args, RTLIL::Design *design) override diff --git a/passes/hierarchy/uniquify.cc b/passes/hierarchy/uniquify.cc index e9322d359..49b59c8df 100644 --- a/passes/hierarchy/uniquify.cc +++ b/passes/hierarchy/uniquify.cc @@ -52,6 +52,7 @@ struct UniquifyPass : public Pass { // flag_check = true; // continue; // } + break; } extra_args(args, argidx, design); diff --git a/passes/memory/memory_bram.cc b/passes/memory/memory_bram.cc index b1f45d5fc..1cb50b3ea 100644 --- a/passes/memory/memory_bram.cc +++ b/passes/memory/memory_bram.cc @@ -1245,7 +1245,8 @@ struct MemoryBramPass : public Pass { log("greater than 1 share the same configuration bit.\n"); log("\n"); log("Using the same bram name in different bram blocks will create different variants\n"); - log("of the bram. Verilog configuration parameters for the bram are created as needed.\n"); + log("of the bram. Verilog configuration parameters for the bram are created as\n"); + log("needed.\n"); log("\n"); log("It is also possible to create variants by repeating statements in the bram block\n"); log("and appending '@<label>' to the individual statements.\n"); diff --git a/passes/memory/memory_dff.cc b/passes/memory/memory_dff.cc index 998e86491..75c6e6a3a 100644 --- a/passes/memory/memory_dff.cc +++ b/passes/memory/memory_dff.cc @@ -631,8 +631,8 @@ struct MemoryDffPass : public Pass { log("\n"); log(" memory_dff [-no-rw-check] [selection]\n"); log("\n"); - log("This pass detects DFFs at memory read ports and merges them into the memory port.\n"); - log("I.e. it consumes an asynchronous memory port and the flip-flops at its\n"); + log("This pass detects DFFs at memory read ports and merges them into the memory\n"); + log("port. I.e. it consumes an asynchronous memory port and the flip-flops at its\n"); log("interface and yields a synchronous memory port.\n"); log("\n"); log(" -no-rw-check\n"); diff --git a/passes/memory/memory_map.cc b/passes/memory/memory_map.cc index ccfb8c94f..e2f74c2e1 100644 --- a/passes/memory/memory_map.cc +++ b/passes/memory/memory_map.cc @@ -31,6 +31,8 @@ struct MemoryMapWorker { bool attr_icase = false; bool rom_only = false; + bool keepdc = false; + bool formal = false; dict<RTLIL::IdString, std::vector<RTLIL::Const>> attributes; RTLIL::Design *design; @@ -150,6 +152,8 @@ struct MemoryMapWorker // all write ports must share the same clock RTLIL::SigSpec refclock; bool refclock_pol = false; + bool async_wr = false; + bool static_only = true; for (int i = 0; i < GetSize(mem.wr_ports); i++) { auto &port = mem.wr_ports[i]; if (port.en.is_fully_const() && !port.en.as_bool()) { @@ -163,10 +167,20 @@ struct MemoryMapWorker static_ports.insert(i); continue; } - log("Not mapping memory %s in module %s (write port %d has no clock).\n", - mem.memid.c_str(), module->name.c_str(), i); - return; + static_only = false; + if (GetSize(refclock) != 0) + log("Not mapping memory %s in module %s (mixed clocked and async write ports).\n", + mem.memid.c_str(), module->name.c_str()); + if (!formal) + log("Not mapping memory %s in module %s (write port %d has no clock).\n", + mem.memid.c_str(), module->name.c_str(), i); + async_wr = true; + continue; } + static_only = false; + if (async_wr) + log("Not mapping memory %s in module %s (mixed clocked and async write ports).\n", + mem.memid.c_str(), module->name.c_str()); if (refclock.size() == 0) { refclock = port.clk; refclock_pol = port.clk_polarity; @@ -184,32 +198,47 @@ struct MemoryMapWorker std::vector<RTLIL::SigSpec> data_reg_in(1 << abits); std::vector<RTLIL::SigSpec> data_reg_out(1 << abits); + std::vector<RTLIL::SigSpec> &data_read = async_wr ? data_reg_in : data_reg_out; + int count_static = 0; for (int i = 0; i < mem.size; i++) { int addr = i + mem.start_offset; int idx = addr & ((1 << abits) - 1); + SigSpec w_init = init_data.extract(i*mem.width, mem.width); if (static_cells_map.count(addr) > 0) { - data_reg_out[idx] = static_cells_map[addr]; + data_read[idx] = static_cells_map[addr]; count_static++; } - else if (mem.wr_ports.empty()) + else if (static_only && (!keepdc || w_init.is_fully_def())) { - data_reg_out[idx] = init_data.extract(i*mem.width, mem.width); + data_read[idx] = w_init; } else { - RTLIL::Cell *c = module->addCell(genid(mem.memid, "", addr), ID($dff)); - c->parameters[ID::WIDTH] = mem.width; - if (GetSize(refclock) != 0) { + RTLIL::Cell *c; + auto ff_id = genid(mem.memid, "", addr); + + if (static_only) { + // non-static part is a ROM, we only reach this with keepdc + if (formal) { + c = module->addCell(ff_id, ID($ff)); + } else { + c = module->addCell(ff_id, ID($dff)); + c->parameters[ID::CLK_POLARITY] = RTLIL::Const(RTLIL::State::S1); + c->setPort(ID::CLK, RTLIL::SigSpec(RTLIL::State::S0)); + } + } else if (async_wr) { + log_assert(formal); // General async write not implemented yet, checked against above + c = module->addCell(ff_id, ID($ff)); + } else { + c = module->addCell(ff_id, ID($dff)); c->parameters[ID::CLK_POLARITY] = RTLIL::Const(refclock_pol); c->setPort(ID::CLK, refclock); - } else { - c->parameters[ID::CLK_POLARITY] = RTLIL::Const(RTLIL::State::S1); - c->setPort(ID::CLK, RTLIL::SigSpec(RTLIL::State::S0)); } + c->parameters[ID::WIDTH] = mem.width; RTLIL::Wire *w_in = module->addWire(genid(mem.memid, "", addr, "$d"), mem.width); data_reg_in[idx] = w_in; @@ -220,17 +249,28 @@ struct MemoryMapWorker w_out_name = genid(mem.memid, "", addr, "$q"); RTLIL::Wire *w_out = module->addWire(w_out_name, mem.width); - SigSpec w_init = init_data.extract(i*mem.width, mem.width); + + if (formal && mem.packed && mem.cell->name.c_str()[0] == '\\') { + auto hdlname = mem.cell->get_hdlname_attribute(); + if (hdlname.empty()) + hdlname.push_back(mem.cell->name.c_str() + 1); + hdlname.push_back(stringf("[%d]", addr)); + w_out->set_hdlname_attribute(hdlname); + } if (!w_init.is_fully_undef()) w_out->attributes[ID::init] = w_init.as_const(); data_reg_out[idx] = w_out; c->setPort(ID::Q, w_out); + + if (static_only) + module->connect(RTLIL::SigSig(w_in, w_out)); } } - log(" created %d $dff cells and %d static cells of width %d.\n", mem.size-count_static, count_static, mem.width); + log(" created %d %s cells and %d static cells of width %d.\n", + mem.size-count_static, formal && (static_only || async_wr) ? "$ff" : "$dff", count_static, mem.width); int count_dff = 0, count_mux = 0, count_wrmux = 0; @@ -268,13 +308,13 @@ struct MemoryMapWorker } for (int j = 0; j < (1 << abits); j++) - if (data_reg_out[j] != SigSpec()) - module->connect(RTLIL::SigSig(rd_signals[j >> port.wide_log2].extract((j & ((1 << port.wide_log2) - 1)) * mem.width, mem.width), data_reg_out[j])); + if (data_read[j] != SigSpec()) + module->connect(RTLIL::SigSig(rd_signals[j >> port.wide_log2].extract((j & ((1 << port.wide_log2) - 1)) * mem.width, mem.width), data_read[j])); } log(" read interface: %d $dff and %d $mux cells.\n", count_dff, count_mux); - if (!mem.wr_ports.empty()) + if (!static_only) { for (int i = 0; i < mem.size; i++) { @@ -380,11 +420,22 @@ struct MemoryMapPass : public Pass { log(" -rom-only\n"); log(" only perform conversion for ROMs (memories with no write ports).\n"); log("\n"); + log(" -keepdc\n"); + log(" when mapping ROMs, keep x-bits shared across read ports.\n"); + log("\n"); + log(" -formal\n"); + log(" map memories for a global clock based formal verification flow.\n"); + log(" This implies -keepdc, uses $ff cells for ROMs and sets hdlname\n"); + log(" attributes. It also has limited support for async write ports\n"); + log(" as generated by clk2fflogic.\n"); + log("\n"); } void execute(std::vector<std::string> args, RTLIL::Design *design) override { bool attr_icase = false; bool rom_only = false; + bool keepdc = false; + bool formal = false; dict<RTLIL::IdString, std::vector<RTLIL::Const>> attributes; log_header(design, "Executing MEMORY_MAP pass (converting memories to logic and flip-flops).\n"); @@ -426,6 +477,17 @@ struct MemoryMapPass : public Pass { rom_only = true; continue; } + if (args[argidx] == "-keepdc") + { + keepdc = true; + continue; + } + if (args[argidx] == "-formal") + { + formal = true; + keepdc = true; + continue; + } break; } extra_args(args, argidx, design); @@ -435,6 +497,8 @@ struct MemoryMapPass : public Pass { worker.attr_icase = attr_icase; worker.attributes = attributes; worker.rom_only = rom_only; + worker.keepdc = keepdc; + worker.formal = formal; worker.run(); } } diff --git a/passes/memory/memory_share.cc b/passes/memory/memory_share.cc index 5cb11d62b..8b2354ef8 100644 --- a/passes/memory/memory_share.cc +++ b/passes/memory/memory_share.cc @@ -523,8 +523,8 @@ struct MemorySharePass : public Pass { log(" - When multiple write ports access the same address then this is converted\n"); log(" to a single write port with a more complex data and/or enable logic path.\n"); log("\n"); - log(" - When multiple read or write ports access adjacent aligned addresses, they are\n"); - log(" merged to a single wide read or write port. This transformation can be\n"); + log(" - When multiple read or write ports access adjacent aligned addresses, they\n"); + log(" are merged to a single wide read or write port. This transformation can be\n"); log(" disabled with the \"-nowiden\" option.\n"); log("\n"); log(" - When multiple write ports are never accessed at the same time (a SAT\n"); diff --git a/passes/opt/opt_clean.cc b/passes/opt/opt_clean.cc index cb2c261c4..dde7c5299 100644 --- a/passes/opt/opt_clean.cc +++ b/passes/opt/opt_clean.cc @@ -633,6 +633,7 @@ struct OptCleanPass : public Pass { keep_cache.reset(design); ct_reg.setup_internals_mem(); + ct_reg.setup_internals_anyinit(); ct_reg.setup_stdcells_mem(); ct_all.setup(design); @@ -694,6 +695,7 @@ struct CleanPass : public Pass { keep_cache.reset(design); ct_reg.setup_internals_mem(); + ct_reg.setup_internals_anyinit(); ct_reg.setup_stdcells_mem(); ct_all.setup(design); diff --git a/passes/opt/opt_dff.cc b/passes/opt/opt_dff.cc index 0ad4acec2..f090d20b2 100644 --- a/passes/opt/opt_dff.cc +++ b/passes/opt/opt_dff.cc @@ -491,12 +491,17 @@ struct OptDffWorker ff.has_srst = false; ff.sig_d = ff.val_srst; changed = true; - } else { + } else if (!opt.keepdc || ff.val_init.is_fully_def()) { log("Handling never-active EN on %s (%s) from module %s (removing D path).\n", log_id(cell), log_id(cell->type), log_id(module)); // The D input path is effectively useless, so remove it (this will be a D latch, SR latch, or a const driver). ff.has_ce = ff.has_clk = ff.has_srst = false; changed = true; + } else { + // We need to keep the undefined initival around as such + ff.sig_d = ff.sig_q; + ff.has_ce = ff.has_srst = false; + changed = true; } } else if (ff.sig_ce == (ff.pol_ce ? State::S1 : State::S0)) { // Always-active enable. Just remove it. @@ -508,13 +513,20 @@ struct OptDffWorker } } - if (ff.has_clk) { - if (ff.sig_clk.is_fully_const()) { + if (ff.has_clk && ff.sig_clk.is_fully_const()) { + if (!opt.keepdc || ff.val_init.is_fully_def()) { // Const clock — the D input path is effectively useless, so remove it (this will be a D latch, SR latch, or a const driver). log("Handling const CLK on %s (%s) from module %s (removing D path).\n", log_id(cell), log_id(cell->type), log_id(module)); ff.has_ce = ff.has_clk = ff.has_srst = false; changed = true; + } else { + // Const clock, but we need to keep the undefined initval around as such + if (ff.has_ce || ff.has_srst || ff.sig_d != ff.sig_q) { + ff.sig_d = ff.sig_q; + ff.has_ce = ff.has_srst = false; + changed = true; + } } } @@ -550,7 +562,7 @@ struct OptDffWorker ff.has_srst = false; ff.sig_d = ff.val_srst; changed = true; - } else { + } else if (!opt.keepdc || ff.val_init.is_fully_def()) { // The D input path is effectively useless, so remove it (this will be a const-input D latch, SR latch, or a const driver). log("Handling D = Q on %s (%s) from module %s (removing D path).\n", log_id(cell), log_id(cell->type), log_id(module)); @@ -567,7 +579,7 @@ struct OptDffWorker } // The cell has been simplified as much as possible already. Now try to spice it up with enables / sync resets. - if (ff.has_clk) { + if (ff.has_clk && ff.sig_d != ff.sig_q) { if (!ff.has_arst && !ff.has_sr && (!ff.has_srst || !ff.has_ce || ff.ce_over_srst) && !opt.nosdff) { // Try to merge sync resets. std::map<ctrls_t, std::vector<int>> groups; @@ -841,14 +853,17 @@ struct OptDffPass : public Pass { log(" opt_dff [-nodffe] [-nosdff] [-keepdc] [-sat] [selection]\n"); log("\n"); log("This pass converts flip-flops to a more suitable type by merging clock enables\n"); - log("and synchronous reset multiplexers, removing unused control inputs, or potentially\n"); - log("removes the flip-flop altogether, converting it to a constant driver.\n"); + log("and synchronous reset multiplexers, removing unused control inputs, or\n"); + log("potentially removes the flip-flop altogether, converting it to a constant\n"); + log("driver.\n"); log("\n"); log(" -nodffe\n"); - log(" disables dff -> dffe conversion, and other transforms recognizing clock enable\n"); + log(" disables dff -> dffe conversion, and other transforms recognizing clock\n"); + log(" enable\n"); log("\n"); log(" -nosdff\n"); - log(" disables dff -> sdff conversion, and other transforms recognizing sync resets\n"); + log(" disables dff -> sdff conversion, and other transforms recognizing sync\n"); + log(" resets\n"); log("\n"); log(" -simple-dffe\n"); log(" only enables clock enable recognition transform for obvious cases\n"); diff --git a/passes/opt/opt_expr.cc b/passes/opt/opt_expr.cc index be0cd470b..9d5ca4ef9 100644 --- a/passes/opt/opt_expr.cc +++ b/passes/opt/opt_expr.cc @@ -643,6 +643,148 @@ void replace_const_cells(RTLIL::Design *design, RTLIL::Module *module, bool cons goto next_cell; } + if (cell->type.in(ID($and), ID($or), ID($xor), ID($xnor))) + { + RTLIL::SigSpec sig_a = assign_map(cell->getPort(ID::A)); + RTLIL::SigSpec sig_b = assign_map(cell->getPort(ID::B)); + + bool a_fully_const = (sig_a.is_fully_const() && (!keepdc || !sig_a.is_fully_undef())); + bool b_fully_const = (sig_b.is_fully_const() && (!keepdc || !sig_b.is_fully_undef())); + + if (a_fully_const != b_fully_const) + { + cover("opt.opt_expr.bitwise_logic_one_const"); + log_debug("Replacing %s cell `%s' in module `%s' having one fully constant input\n", + log_id(cell->type), log_id(cell->name), log_id(module)); + RTLIL::SigSpec sig_y = assign_map(cell->getPort(ID::Y)); + + int width = GetSize(cell->getPort(ID::Y)); + + sig_a.extend_u0(width, cell->getParam(ID::A_SIGNED).as_bool()); + sig_b.extend_u0(width, cell->getParam(ID::B_SIGNED).as_bool()); + + if (!a_fully_const) + std::swap(sig_a, sig_b); + + RTLIL::SigSpec b_group_0, b_group_1, b_group_x; + RTLIL::SigSpec y_group_0, y_group_1, y_group_x; + + for (int i = 0; i < width; i++) { + auto bit_a = sig_a[i].data; + if (bit_a == State::S0) b_group_0.append(sig_b[i]), y_group_0.append(sig_y[i]); + if (bit_a == State::S1) b_group_1.append(sig_b[i]), y_group_1.append(sig_y[i]); + if (bit_a == State::Sx) b_group_x.append(sig_b[i]), y_group_x.append(sig_y[i]); + } + + if (cell->type == ID($xnor)) { + std::swap(b_group_0, b_group_1); + std::swap(y_group_0, y_group_1); + } + + RTLIL::SigSpec y_new_0, y_new_1, y_new_x; + + if (cell->type == ID($and)) { + if (!y_group_0.empty()) y_new_0 = Const(State::S0, GetSize(y_group_0)); + if (!y_group_1.empty()) y_new_1 = b_group_1; + if (!y_group_x.empty()) { + if (keepdc) + y_new_x = module->And(NEW_ID, Const(State::Sx, GetSize(y_group_x)), b_group_x); + else + y_new_x = Const(State::S0, GetSize(y_group_x)); + } + } else if (cell->type == ID($or)) { + if (!y_group_0.empty()) y_new_0 = b_group_0; + if (!y_group_1.empty()) y_new_1 = Const(State::S1, GetSize(y_group_1)); + if (!y_group_x.empty()) { + if (keepdc) + y_new_x = module->Or(NEW_ID, Const(State::Sx, GetSize(y_group_x)), b_group_x); + else + y_new_x = Const(State::S1, GetSize(y_group_x)); + } + } else if (cell->type.in(ID($xor), ID($xnor))) { + if (!y_group_0.empty()) y_new_0 = b_group_0; + if (!y_group_1.empty()) y_new_1 = module->Not(NEW_ID, b_group_1); + if (!y_group_x.empty()) { + if (keepdc) + y_new_x = module->Xor(NEW_ID, Const(State::Sx, GetSize(y_group_x)), b_group_x); + else // This should be fine even with keepdc, but opt_expr_xor.ys wants to keep the xor + y_new_x = Const(State::Sx, GetSize(y_group_x)); + } + } else { + log_abort(); + } + + assign_map.add(y_group_0, y_new_0); module->connect(y_group_0, y_new_0); + assign_map.add(y_group_1, y_new_1); module->connect(y_group_1, y_new_1); + assign_map.add(y_group_x, y_new_x); module->connect(y_group_x, y_new_x); + + module->remove(cell); + did_something = true; + goto next_cell; + } + } + + if (cell->type == ID($bwmux)) + { + RTLIL::SigSpec sig_a = assign_map(cell->getPort(ID::A)); + RTLIL::SigSpec sig_b = assign_map(cell->getPort(ID::B)); + RTLIL::SigSpec sig_s = assign_map(cell->getPort(ID::S)); + RTLIL::SigSpec sig_y = assign_map(cell->getPort(ID::Y)); + int width = GetSize(cell->getPort(ID::Y)); + + if (sig_s.is_fully_def()) + { + RTLIL::SigSpec a_group_0, b_group_1; + RTLIL::SigSpec y_group_0, y_group_1; + for (int i = 0; i < width; i++) { + if (sig_s[i].data == State::S1) + y_group_1.append(sig_y[i]), b_group_1.append(sig_b[i]); + else + y_group_0.append(sig_y[i]), a_group_0.append(sig_a[i]); + } + + assign_map.add(y_group_0, a_group_0); module->connect(y_group_0, a_group_0); + assign_map.add(y_group_1, b_group_1); module->connect(y_group_1, b_group_1); + + module->remove(cell); + did_something = true; + goto next_cell; + } + else if (sig_a.is_fully_def() || sig_b.is_fully_def()) + { + bool flip = !sig_a.is_fully_def(); + if (flip) + std::swap(sig_a, sig_b); + + RTLIL::SigSpec b_group_0, b_group_1; + RTLIL::SigSpec s_group_0, s_group_1; + RTLIL::SigSpec y_group_0, y_group_1; + for (int i = 0; i < width; i++) { + if (sig_a[i].data == State::S1) + y_group_1.append(sig_y[i]), b_group_1.append(sig_b[i]), s_group_1.append(sig_s[i]); + else + y_group_0.append(sig_y[i]), b_group_0.append(sig_b[i]), s_group_0.append(sig_s[i]); + } + + RTLIL::SigSpec y_new_0, y_new_1; + + if (flip) { + if (!y_group_0.empty()) y_new_0 = module->And(NEW_ID, b_group_0, module->Not(NEW_ID, s_group_0)); + if (!y_group_1.empty()) y_new_1 = module->Or(NEW_ID, b_group_1, s_group_1); + } else { + if (!y_group_0.empty()) y_new_0 = module->And(NEW_ID, b_group_0, s_group_0); + if (!y_group_1.empty()) y_new_1 = module->Or(NEW_ID, b_group_1, module->Not(NEW_ID, s_group_1)); + } + + module->connect(y_group_0, y_new_0); + module->connect(y_group_1, y_new_1); + + module->remove(cell); + did_something = true; + goto next_cell; + } + } + if (do_fine) { if (cell->type.in(ID($not), ID($pos), ID($and), ID($or), ID($xor), ID($xnor))) @@ -905,7 +1047,7 @@ skip_fine_alu: } } - if (cell->type.in(ID($shiftx), ID($shift))) { + if (cell->type.in(ID($shiftx), ID($shift)) && (cell->type == ID($shiftx) || !cell->getParam(ID::A_SIGNED).as_bool())) { SigSpec sig_a = assign_map(cell->getPort(ID::A)); int width; bool trim_x = cell->type == ID($shiftx) || !keepdc; @@ -1152,7 +1294,7 @@ skip_fine_alu: goto next_cell; } - if (cell->type.in(ID($shl), ID($shr), ID($sshl), ID($sshr), ID($shift), ID($shiftx)) && assign_map(cell->getPort(ID::B)).is_fully_const()) + if (cell->type.in(ID($shl), ID($shr), ID($sshl), ID($sshr), ID($shift), ID($shiftx)) && (keepdc ? assign_map(cell->getPort(ID::B)).is_fully_def() : assign_map(cell->getPort(ID::B)).is_fully_const())) { bool sign_ext = cell->type == ID($sshr) && cell->getParam(ID::A_SIGNED).as_bool(); int shift_bits = assign_map(cell->getPort(ID::B)).as_int(cell->type.in(ID($shift), ID($shiftx)) && cell->getParam(ID::B_SIGNED).as_bool()); @@ -1163,7 +1305,7 @@ skip_fine_alu: RTLIL::SigSpec sig_a = assign_map(cell->getPort(ID::A)); RTLIL::SigSpec sig_y(cell->type == ID($shiftx) ? RTLIL::State::Sx : RTLIL::State::S0, cell->getParam(ID::Y_WIDTH).as_int()); - if (GetSize(sig_a) < GetSize(sig_y)) + if (cell->type != ID($shiftx) && GetSize(sig_a) < GetSize(sig_y)) sig_a.extend_u0(GetSize(sig_y), cell->getParam(ID::A_SIGNED).as_bool()); for (int i = 0; i < GetSize(sig_y); i++) { @@ -1446,6 +1588,31 @@ skip_identity: goto next_cell; \ } \ } +#define FOLD_2ARG_SIMPLE_CELL(_t, B_ID) \ + if (cell->type == ID($##_t)) { \ + RTLIL::SigSpec a = cell->getPort(ID::A); \ + RTLIL::SigSpec b = cell->getPort(B_ID); \ + assign_map.apply(a), assign_map.apply(b); \ + if (a.is_fully_const() && b.is_fully_const()) { \ + RTLIL::SigSpec y(RTLIL::const_ ## _t(a.as_const(), b.as_const())); \ + cover("opt.opt_expr.const.$" #_t); \ + replace_cell(assign_map, module, cell, stringf("%s, %s", log_signal(a), log_signal(b)), ID::Y, y); \ + goto next_cell; \ + } \ + } +#define FOLD_MUX_CELL(_t) \ + if (cell->type == ID($##_t)) { \ + RTLIL::SigSpec a = cell->getPort(ID::A); \ + RTLIL::SigSpec b = cell->getPort(ID::B); \ + RTLIL::SigSpec s = cell->getPort(ID::S); \ + assign_map.apply(a), assign_map.apply(b), assign_map.apply(s); \ + if (a.is_fully_const() && b.is_fully_const() && s.is_fully_const()) { \ + RTLIL::SigSpec y(RTLIL::const_ ## _t(a.as_const(), b.as_const(), s.as_const())); \ + cover("opt.opt_expr.const.$" #_t); \ + replace_cell(assign_map, module, cell, stringf("%s, %s, %s", log_signal(a), log_signal(b), log_signal(s)), ID::Y, y); \ + goto next_cell; \ + } \ + } FOLD_1ARG_CELL(not) FOLD_2ARG_CELL(and) @@ -1477,6 +1644,9 @@ skip_identity: FOLD_2ARG_CELL(gt) FOLD_2ARG_CELL(ge) + FOLD_2ARG_CELL(eqx) + FOLD_2ARG_CELL(nex) + FOLD_2ARG_CELL(add) FOLD_2ARG_CELL(sub) FOLD_2ARG_CELL(mul) @@ -1489,12 +1659,19 @@ skip_identity: FOLD_1ARG_CELL(pos) FOLD_1ARG_CELL(neg) + FOLD_MUX_CELL(mux); + FOLD_MUX_CELL(pmux); + FOLD_2ARG_SIMPLE_CELL(bmux, ID::S); + FOLD_2ARG_SIMPLE_CELL(demux, ID::S); + FOLD_2ARG_SIMPLE_CELL(bweqx, ID::B); + FOLD_MUX_CELL(bwmux); + // be very conservative with optimizing $mux cells as we do not want to break mux trees if (cell->type == ID($mux)) { RTLIL::SigSpec input = assign_map(cell->getPort(ID::S)); RTLIL::SigSpec inA = assign_map(cell->getPort(ID::A)); RTLIL::SigSpec inB = assign_map(cell->getPort(ID::B)); - if (input.is_fully_const()) + if (input.is_fully_const() && (!keepdc || input.is_fully_def())) ACTION_DO(ID::Y, input.as_bool() ? cell->getPort(ID::B) : cell->getPort(ID::A)); else if (inA == inB) ACTION_DO(ID::Y, cell->getPort(ID::A)); diff --git a/passes/opt/opt_ffinv.cc b/passes/opt/opt_ffinv.cc index 5d989dafd..3f7b4bc4a 100644 --- a/passes/opt/opt_ffinv.cc +++ b/passes/opt/opt_ffinv.cc @@ -64,6 +64,7 @@ struct OptFfInvWorker log_assert(d_inv == nullptr); d_inv = port.cell; } + if (!d_inv) return false; if (index.query_is_output(ff.sig_q)) return false; @@ -140,6 +141,7 @@ struct OptFfInvWorker log_assert(d_lut == nullptr); d_lut = port.cell; } + if (!d_lut) return false; if (index.query_is_output(ff.sig_q)) return false; @@ -167,6 +169,7 @@ struct OptFfInvWorker log_assert(q_inv == nullptr); q_inv = port.cell; } + if (!q_inv) return false; ff.flip_rst_bits({0}); ff.sig_q = q_inv->getPort(ID::Y); diff --git a/passes/opt/opt_reduce.cc b/passes/opt/opt_reduce.cc index 1a7c93fbd..c36a38dae 100644 --- a/passes/opt/opt_reduce.cc +++ b/passes/opt/opt_reduce.cc @@ -594,11 +594,9 @@ struct OptReduceWorker if (cell->type.in(ID($mux), ID($pmux))) opt_pmux(cell); - - if (cell->type == ID($bmux)) + else if (cell->type == ID($bmux)) opt_bmux(cell); - - if (cell->type == ID($demux)) + else if (cell->type == ID($demux)) opt_demux(cell); } } diff --git a/passes/opt/wreduce.cc b/passes/opt/wreduce.cc index 08ab6de6f..8fd4c788c 100644 --- a/passes/opt/wreduce.cc +++ b/passes/opt/wreduce.cc @@ -166,8 +166,8 @@ struct WreduceWorker for (int i = GetSize(sig_q)-1; i >= 0; i--) { - if (zero_ext && sig_d[i] == State::S0 && (initval[i] == State::S0 || initval[i] == State::Sx) && - (!has_reset || i >= GetSize(rst_value) || rst_value[i] == State::S0 || rst_value[i] == State::Sx)) { + if (zero_ext && sig_d[i] == State::S0 && (initval[i] == State::S0 || (!config->keepdc && initval[i] == State::Sx)) && + (!has_reset || i >= GetSize(rst_value) || rst_value[i] == State::S0 || (!config->keepdc && rst_value[i] == State::Sx))) { module->connect(sig_q[i], State::S0); initvals.remove_init(sig_q[i]); sig_d.remove(i); @@ -175,8 +175,8 @@ struct WreduceWorker continue; } - if (sign_ext && i > 0 && sig_d[i] == sig_d[i-1] && initval[i] == initval[i-1] && - (!has_reset || i >= GetSize(rst_value) || rst_value[i] == rst_value[i-1])) { + if (sign_ext && i > 0 && sig_d[i] == sig_d[i-1] && initval[i] == initval[i-1] && (!config->keepdc || initval[i] != State::Sx) && + (!has_reset || i >= GetSize(rst_value) || (rst_value[i] == rst_value[i-1] && (!config->keepdc || rst_value[i] != State::Sx)))) { module->connect(sig_q[i], sig_q[i-1]); initvals.remove_init(sig_q[i]); sig_d.remove(i); diff --git a/passes/pmgen/xilinx_srl.cc b/passes/pmgen/xilinx_srl.cc index a66a06586..eebd30017 100644 --- a/passes/pmgen/xilinx_srl.cc +++ b/passes/pmgen/xilinx_srl.cc @@ -196,9 +196,9 @@ struct XilinxSrlPass : public Pass { log("\n"); log("This pass converts chains of built-in flops (bit-level: $_DFF_[NP]_, $_DFFE_*\n"); log("and word-level: $dff, $dffe) as well as Xilinx flops (FDRE, FDRE_1) into a\n"); - log("$__XILINX_SHREG cell. Chains must be of the same cell type, clock, clock polarity,\n"); - log("enable, and enable polarity (where relevant).\n"); - log("Flops with resets cannot be mapped to Xilinx devices and will not be inferred."); + log("$__XILINX_SHREG cell. Chains must be of the same cell type, clock, clock\n"); + log("polarity, enable, and enable polarity (where relevant).\n"); + log("Flops with resets cannot be mapped to Xilinx devices and will not be inferred.\n"); log("\n"); log(" -minlen N\n"); log(" min length of shift register (default = 3)\n"); diff --git a/passes/proc/proc_dff.cc b/passes/proc/proc_dff.cc index 234671df5..fd56786f2 100644 --- a/passes/proc/proc_dff.cc +++ b/passes/proc/proc_dff.cc @@ -302,7 +302,7 @@ void proc_dff(RTLIL::Module *mod, RTLIL::Process *proc, ConstEval &ce) ce.assign_map.apply(rstval); ce.assign_map.apply(sig); - if (rstval == sig) { + if (rstval == sig && sync_level) { if (sync_level->type == RTLIL::SyncType::ST1) insig = mod->Mux(NEW_ID, insig, sig, sync_level->signal); else diff --git a/passes/sat/Makefile.inc b/passes/sat/Makefile.inc index da6d49433..ebe3dc536 100644 --- a/passes/sat/Makefile.inc +++ b/passes/sat/Makefile.inc @@ -10,6 +10,7 @@ OBJS += passes/sat/expose.o OBJS += passes/sat/assertpmux.o OBJS += passes/sat/clk2fflogic.o OBJS += passes/sat/async2sync.o +OBJS += passes/sat/formalff.o OBJS += passes/sat/supercover.o OBJS += passes/sat/fmcombine.o OBJS += passes/sat/mutate.o diff --git a/passes/sat/async2sync.cc b/passes/sat/async2sync.cc index 46c76eba9..6fdf470b1 100644 --- a/passes/sat/async2sync.cc +++ b/passes/sat/async2sync.cc @@ -75,6 +75,9 @@ struct Async2syncPass : public Pass { if (ff.has_gclk) continue; + if (ff.has_clk && ff.sig_clk.is_fully_const()) + ff.has_ce = ff.has_clk = ff.has_srst = false; + if (ff.has_clk) { if (ff.has_sr) { diff --git a/passes/sat/clk2fflogic.cc b/passes/sat/clk2fflogic.cc index b1b0567a0..bba2cbbec 100644 --- a/passes/sat/clk2fflogic.cc +++ b/passes/sat/clk2fflogic.cc @@ -26,6 +26,11 @@ USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN +struct SampledSig { + SigSpec sampled, current; + SigSpec &operator[](bool get_current) { return get_current ? current : sampled; } +}; + struct Clk2fflogicPass : public Pass { Clk2fflogicPass() : Pass("clk2fflogic", "convert clocked FFs to generic $ff cells") { } void help() override @@ -38,37 +43,65 @@ struct Clk2fflogicPass : public Pass { log("implicit global clock. This is useful for formal verification of designs with\n"); log("multiple clocks.\n"); log("\n"); + log("This pass assumes negative hold time for the async FF inputs. For example when\n"); + log("a reset deasserts with the clock edge, then the FF output will still drive the\n"); + log("reset value in the next cycle regardless of the data-in value at the time of\n"); + log("the clock edge.\n"); + log("\n"); } - SigSpec wrap_async_control(Module *module, SigSpec sig, bool polarity, bool is_fine, IdString past_sig_id) { - if (!is_fine) - return wrap_async_control(module, sig, polarity, past_sig_id); - return wrap_async_control_gate(module, sig, polarity, past_sig_id); + // Active-high sampled and current value of a level-triggered control signal. Initial sampled values is low/non-asserted. + SampledSig sample_control(Module *module, SigSpec sig, bool polarity, bool is_fine) { + if (!polarity) { + if (is_fine) + sig = module->NotGate(NEW_ID, sig); + else + sig = module->Not(NEW_ID, sig); + } + std::string sig_str = log_signal(sig); + sig_str.erase(std::remove(sig_str.begin(), sig_str.end(), ' '), sig_str.end()); + Wire *sampled_sig = module->addWire(NEW_ID_SUFFIX(stringf("%s#sampled", sig_str.c_str())), GetSize(sig)); + sampled_sig->attributes[ID::init] = RTLIL::Const(State::S0, GetSize(sig)); + if (is_fine) + module->addFfGate(NEW_ID, sig, sampled_sig); + else + module->addFf(NEW_ID, sig, sampled_sig); + return {sampled_sig, sig}; } - SigSpec wrap_async_control(Module *module, SigSpec sig, bool polarity, IdString past_sig_id) { - Wire *past_sig = module->addWire(past_sig_id, GetSize(sig)); - past_sig->attributes[ID::init] = RTLIL::Const(polarity ? State::S0 : State::S1, GetSize(sig)); - module->addFf(NEW_ID, sig, past_sig); - if (polarity) - sig = module->Or(NEW_ID, sig, past_sig); + // Active-high trigger signal for an edge-triggered control signal. Initial values is low/non-edge. + SigSpec sample_control_edge(Module *module, SigSpec sig, bool polarity, bool is_fine) { + std::string sig_str = log_signal(sig); + sig_str.erase(std::remove(sig_str.begin(), sig_str.end(), ' '), sig_str.end()); + Wire *sampled_sig = module->addWire(NEW_ID_SUFFIX(stringf("%s#sampled", sig_str.c_str())), GetSize(sig)); + sampled_sig->attributes[ID::init] = RTLIL::Const(polarity ? State::S1 : State::S0, GetSize(sig)); + if (is_fine) + module->addFfGate(NEW_ID, sig, sampled_sig); else - sig = module->And(NEW_ID, sig, past_sig); - if (polarity) - return sig; + module->addFf(NEW_ID, sig, sampled_sig); + return module->Eqx(NEW_ID, {sampled_sig, sig}, polarity ? SigSpec {State::S0, State::S1} : SigSpec {State::S1, State::S0}); + } + // Sampled and current value of a data signal. + SampledSig sample_data(Module *module, SigSpec sig, RTLIL::Const init, bool is_fine) { + std::string sig_str = log_signal(sig); + sig_str.erase(std::remove(sig_str.begin(), sig_str.end(), ' '), sig_str.end()); + Wire *sampled_sig = module->addWire(NEW_ID_SUFFIX(stringf("%s#sampled", sig_str.c_str())), GetSize(sig)); + sampled_sig->attributes[ID::init] = init; + if (is_fine) + module->addFfGate(NEW_ID, sig, sampled_sig); else - return module->Not(NEW_ID, sig); + module->addFf(NEW_ID, sig, sampled_sig); + return {sampled_sig, sig}; } - SigSpec wrap_async_control_gate(Module *module, SigSpec sig, bool polarity, IdString past_sig_id) { - Wire *past_sig = module->addWire(past_sig_id); - past_sig->attributes[ID::init] = polarity ? State::S0 : State::S1; - module->addFfGate(NEW_ID, sig, past_sig); - if (polarity) - sig = module->OrGate(NEW_ID, sig, past_sig); + SigSpec mux(Module *module, SigSpec a, SigSpec b, SigSpec s, bool is_fine) { + if (is_fine) + return module->MuxGate(NEW_ID, a, b, s); else - sig = module->AndGate(NEW_ID, sig, past_sig); - if (polarity) - return sig; + return module->Mux(NEW_ID, a, b, s); + } + SigSpec bitwise_sr(Module *module, SigSpec a, SigSpec s, SigSpec r, bool is_fine) { + if (is_fine) + return module->AndGate(NEW_ID, module->OrGate(NEW_ID, a, s), module->NotGate(NEW_ID, r)); else - return module->NotGate(NEW_ID, sig); + return module->And(NEW_ID, module->Or(NEW_ID, a, s), module->Not(NEW_ID, r)); } void execute(std::vector<std::string> args, RTLIL::Design *design) override { @@ -177,93 +210,47 @@ struct Clk2fflogicPass : public Pass { ff.remove(); - // Strip spaces from signal name, since Yosys IDs can't contain spaces - // Spaces only occur when we have a signal that's a slice of a larger bus, - // e.g. "\myreg [5:0]", so removing spaces shouldn't result in loss of uniqueness - std::string sig_q_str = log_signal(ff.sig_q); - sig_q_str.erase(std::remove(sig_q_str.begin(), sig_q_str.end(), ' '), sig_q_str.end()); - - Wire *past_q = module->addWire(NEW_ID_SUFFIX(stringf("%s#past_q_wire", sig_q_str.c_str())), ff.width); - - if (!ff.is_fine) { - module->addFf(NEW_ID, ff.sig_q, past_q); - } else { - module->addFfGate(NEW_ID, ff.sig_q, past_q); - } - if (!ff.val_init.is_fully_undef()) - initvals.set_init(past_q, ff.val_init); - - if (ff.has_clk) { + if (ff.has_clk) ff.unmap_ce_srst(); - Wire *past_clk = module->addWire(NEW_ID_SUFFIX(stringf("%s#past_clk#%s", sig_q_str.c_str(), log_signal(ff.sig_clk)))); - initvals.set_init(past_clk, ff.pol_clk ? State::S1 : State::S0); - - if (!ff.is_fine) - module->addFf(NEW_ID, ff.sig_clk, past_clk); - else - module->addFfGate(NEW_ID, ff.sig_clk, past_clk); + auto next_q = sample_data(module, ff.sig_q, ff.val_init, ff.is_fine).sampled; - SigSpec clock_edge_pattern; - - if (ff.pol_clk) { - clock_edge_pattern.append(State::S0); - clock_edge_pattern.append(State::S1); - } else { - clock_edge_pattern.append(State::S1); - clock_edge_pattern.append(State::S0); - } - - SigSpec clock_edge = module->Eqx(NEW_ID, {ff.sig_clk, SigSpec(past_clk)}, clock_edge_pattern); - - Wire *past_d = module->addWire(NEW_ID_SUFFIX(stringf("%s#past_d_wire", sig_q_str.c_str())), ff.width); - if (!ff.is_fine) - module->addFf(NEW_ID, ff.sig_d, past_d); - else - module->addFfGate(NEW_ID, ff.sig_d, past_d); - - if (!ff.val_init.is_fully_undef()) - initvals.set_init(past_d, ff.val_init); - - if (!ff.is_fine) - qval = module->Mux(NEW_ID, past_q, past_d, clock_edge); - else - qval = module->MuxGate(NEW_ID, past_q, past_d, clock_edge); - } else { - qval = past_q; + if (ff.has_clk) { + // The init value for the sampled d is never used, so we can set it to fixed zero, reducing uninit'd FFs + auto sampled_d = sample_data(module, ff.sig_d, RTLIL::Const(State::S0, ff.width), ff.is_fine); + auto clk_edge = sample_control_edge(module, ff.sig_clk, ff.pol_clk, ff.is_fine); + next_q = mux(module, next_q, sampled_d.sampled, clk_edge, ff.is_fine); } - if (ff.has_aload) { - SigSpec sig_aload = wrap_async_control(module, ff.sig_aload, ff.pol_aload, ff.is_fine, NEW_ID); - - if (!ff.is_fine) - qval = module->Mux(NEW_ID, qval, ff.sig_ad, sig_aload); - else - qval = module->MuxGate(NEW_ID, qval, ff.sig_ad, sig_aload); + SampledSig sampled_aload, sampled_ad, sampled_set, sampled_clr, sampled_arst; + // The check for a constant sig_aload is also done by opt_dff, but when using verific and running + // clk2fflogic before opt_dff (which does more and possibly unwanted optimizations) this check avoids + // generating a lot of extra logic. + bool has_nonconst_aload = ff.has_aload && ff.sig_aload != (ff.pol_aload ? State::S0 : State::S1); + if (has_nonconst_aload) { + sampled_aload = sample_control(module, ff.sig_aload, ff.pol_aload, ff.is_fine); + // The init value for the sampled ad is never used, so we can set it to fixed zero, reducing uninit'd FFs + sampled_ad = sample_data(module, ff.sig_ad, RTLIL::Const(State::S0, ff.width), ff.is_fine); } - if (ff.has_sr) { - SigSpec setval = wrap_async_control(module, ff.sig_set, ff.pol_set, ff.is_fine, NEW_ID); - SigSpec clrval = wrap_async_control(module, ff.sig_clr, ff.pol_clr, ff.is_fine, NEW_ID); - if (!ff.is_fine) { - clrval = module->Not(NEW_ID, clrval); - qval = module->Or(NEW_ID, qval, setval); - module->addAnd(NEW_ID, qval, clrval, ff.sig_q); - } else { - clrval = module->NotGate(NEW_ID, clrval); - qval = module->OrGate(NEW_ID, qval, setval); - module->addAndGate(NEW_ID, qval, clrval, ff.sig_q); - } - } else if (ff.has_arst) { - IdString id = NEW_ID_SUFFIX(stringf("%s#past_arst#%s", sig_q_str.c_str(), log_signal(ff.sig_arst))); - SigSpec arst = wrap_async_control(module, ff.sig_arst, ff.pol_arst, ff.is_fine, id); - if (!ff.is_fine) - module->addMux(NEW_ID, qval, ff.val_arst, arst, ff.sig_q); - else - module->addMuxGate(NEW_ID, qval, ff.val_arst[0], arst, ff.sig_q); - } else { - module->connect(ff.sig_q, qval); + sampled_set = sample_control(module, ff.sig_set, ff.pol_set, ff.is_fine); + sampled_clr = sample_control(module, ff.sig_clr, ff.pol_clr, ff.is_fine); + } + if (ff.has_arst) + sampled_arst = sample_control(module, ff.sig_arst, ff.pol_arst, ff.is_fine); + + // First perform updates using _only_ sampled values, then again using _only_ current values. Unlike the previous + // implementation, this approach correctly handles all the cases of multiple signals changing simultaneously. + for (int current = 0; current < 2; current++) { + if (has_nonconst_aload) + next_q = mux(module, next_q, sampled_ad[current], sampled_aload[current], ff.is_fine); + if (ff.has_sr) + next_q = bitwise_sr(module, next_q, sampled_set[current], sampled_clr[current], ff.is_fine); + if (ff.has_arst) + next_q = mux(module, next_q, ff.val_arst, sampled_arst[current], ff.is_fine); } + + module->connect(ff.sig_q, next_q); } } } diff --git a/passes/sat/formalff.cc b/passes/sat/formalff.cc new file mode 100644 index 000000000..264a9fb3b --- /dev/null +++ b/passes/sat/formalff.cc @@ -0,0 +1,766 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2022 Jannis Harder <jix@yosyshq.com> <me@jix.one> + * + * 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/ffinit.h" +#include "kernel/ff.h" +#include "kernel/modtools.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + + +// Finds signal values with known constant or known unused values in the initial state +struct InitValWorker +{ + Module *module; + + ModWalker modwalker; + SigMap &sigmap; + FfInitVals initvals; + + dict<RTLIL::SigBit, RTLIL::State> initconst_bits; + dict<RTLIL::SigBit, bool> used_bits; + + InitValWorker(Module *module) : module(module), modwalker(module->design), sigmap(modwalker.sigmap) + { + modwalker.setup(module); + initvals.set(&modwalker.sigmap, module); + + for (auto wire : module->wires()) + if (wire->name.isPublic() || wire->get_bool_attribute(ID::keep)) + for (auto bit : SigSpec(wire)) + used_bits[sigmap(bit)] = true; + } + + // Sign/Zero-extended indexing of individual port bits + static SigBit bit_in_port(RTLIL::Cell *cell, RTLIL::IdString port, RTLIL::IdString sign, int index) + { + auto sig_port = cell->getPort(port); + if (index < GetSize(sig_port)) + return sig_port[index]; + else if (cell->getParam(sign).as_bool()) + return GetSize(sig_port) > 0 ? sig_port[GetSize(sig_port) - 1] : State::Sx; + else + return State::S0; + } + + // Has the signal a known constant value in the initial state? + // + // For sync-only FFs outputs, this is their initval. For logic loops, + // multiple drivers or unknown cells this is Sx. For a small number of + // handled cells we recurse through their inputs. All results are cached. + RTLIL::State initconst(SigBit bit) + { + sigmap.apply(bit); + + if (!bit.is_wire()) + return bit.data; + + auto it = initconst_bits.find(bit); + if (it != initconst_bits.end()) + return it->second; + + // Setting this temporarily to x takes care of any logic loops + initconst_bits[bit] = State::Sx; + + pool<ModWalker::PortBit> portbits; + modwalker.get_drivers(portbits, {bit}); + + if (portbits.size() != 1) + return State::Sx; + + ModWalker::PortBit portbit = *portbits.begin(); + RTLIL::Cell *cell = portbit.cell; + + if (RTLIL::builtin_ff_cell_types().count(cell->type)) + { + FfData ff(&initvals, cell); + + if (ff.has_aload || ff.has_sr || ff.has_arst || (!ff.has_clk && !ff.has_gclk)) { + for (auto bit_q : ff.sig_q) { + initconst_bits[sigmap(bit_q)] = State::Sx; + } + return State::Sx; + } + + for (int i = 0; i < ff.width; i++) { + initconst_bits[sigmap(ff.sig_q[i])] = ff.val_init[i]; + } + + return ff.val_init[portbit.offset]; + } + + if (cell->type.in(ID($mux), ID($and), ID($or), ID($eq), ID($eqx), ID($initstate))) + { + if (cell->type == ID($mux)) + { + SigBit sig_s = sigmap(cell->getPort(ID::S)); + State init_s = initconst(sig_s); + State init_y; + + if (init_s == State::S0) { + init_y = initconst(cell->getPort(ID::A)[portbit.offset]); + } else if (init_s == State::S1) { + init_y = initconst(cell->getPort(ID::B)[portbit.offset]); + } else { + State init_a = initconst(cell->getPort(ID::A)[portbit.offset]); + State init_b = initconst(cell->getPort(ID::B)[portbit.offset]); + init_y = init_a == init_b ? init_a : State::Sx; + } + initconst_bits[bit] = init_y; + return init_y; + } + + if (cell->type.in(ID($and), ID($or))) + { + State init_a = initconst(bit_in_port(cell, ID::A, ID::A_SIGNED, portbit.offset)); + State init_b = initconst(bit_in_port(cell, ID::B, ID::B_SIGNED, portbit.offset)); + State init_y; + if (init_a == init_b) + init_y = init_a; + else if (cell->type == ID($and) && (init_a == State::S0 || init_b == State::S0)) + init_y = State::S0; + else if (cell->type == ID($or) && (init_a == State::S1 || init_b == State::S1)) + init_y = State::S1; + else + init_y = State::Sx; + + initconst_bits[bit] = init_y; + return init_y; + } + + if (cell->type.in(ID($eq), ID($eqx))) // Treats $eqx as $eq + { + if (portbit.offset > 0) { + initconst_bits[bit] = State::S0; + return State::S0; + } + + SigSpec sig_a = cell->getPort(ID::A); + SigSpec sig_b = cell->getPort(ID::B); + + State init_y = State::S1; + + for (int i = 0; init_y != State::S0 && i < GetSize(sig_a); i++) { + State init_ai = initconst(bit_in_port(cell, ID::A, ID::A_SIGNED, i)); + if (init_ai == State::Sx) { + init_y = State::Sx; + continue; + } + State init_bi = initconst(bit_in_port(cell, ID::B, ID::B_SIGNED, i)); + if (init_bi == State::Sx) + init_y = State::Sx; + else if (init_ai != init_bi) + init_y = State::S0; + } + + initconst_bits[bit] = init_y; + return init_y; + } + + if (cell->type == ID($initstate)) + { + initconst_bits[bit] = State::S1; + return State::S1; + } + + log_assert(false); + } + + return State::Sx; + } + + RTLIL::Const initconst(SigSpec sig) + { + std::vector<RTLIL::State> bits; + for (auto bit : sig) + bits.push_back(initconst(bit)); + return bits; + } + + // Is the initial value of this signal used? + // + // An initial value of a signal is considered as used if it a) aliases a + // wire with a public name, an output wire or with a keep attribute b) + // combinationally drives such a wire or c) drive an input to an unknown + // cell. + // + // This recurses into driven cells for a small number of known handled + // celltypes. Results are cached and initconst is used to detect unused + // inputs for the handled celltypes. + bool is_initval_used(SigBit bit) + { + if (!bit.is_wire()) + return false; + + auto it = used_bits.find(bit); + if (it != used_bits.end()) + return it->second; + + used_bits[bit] = true; // Temporarily set to guard against logic loops + + pool<ModWalker::PortBit> portbits; + modwalker.get_consumers(portbits, {bit}); + + for (auto portbit : portbits) { + RTLIL::Cell *cell = portbit.cell; + if (!cell->type.in(ID($mux), ID($and), ID($or), ID($mem_v2)) && !RTLIL::builtin_ff_cell_types().count(cell->type)) { + return true; + } + } + + for (auto portbit : portbits) + { + RTLIL::Cell *cell = portbit.cell; + if (RTLIL::builtin_ff_cell_types().count(cell->type)) + { + FfData ff(&initvals, cell); + if (ff.has_aload || ff.has_sr || ff.has_arst || ff.has_gclk || !ff.has_clk) + return true; + if (ff.has_ce && initconst(ff.sig_ce.as_bit()) == (ff.pol_ce ? State::S0 : State::S1)) + continue; + if (ff.has_srst && initconst(ff.sig_ce.as_bit()) == (ff.pol_srst ? State::S1 : State::S0)) + continue; + + return true; + } + else if (cell->type == ID($mux)) + { + State init_s = initconst(cell->getPort(ID::S).as_bit()); + if (init_s == State::S0 && portbit.port == ID::B) + continue; + if (init_s == State::S1 && portbit.port == ID::A) + continue; + auto sig_y = cell->getPort(ID::Y); + + if (is_initval_used(sig_y[portbit.offset])) + return true; + } + else if (cell->type.in(ID($and), ID($or))) + { + auto sig_a = cell->getPort(ID::A); + auto sig_b = cell->getPort(ID::B); + auto sig_y = cell->getPort(ID::Y); + if (GetSize(sig_y) != GetSize(sig_a) || GetSize(sig_y) != GetSize(sig_b)) + return true; // TODO handle more of this + State absorbing = cell->type == ID($and) ? State::S0 : State::S1; + if (portbit.port == ID::A && initconst(sig_b[portbit.offset]) == absorbing) + continue; + if (portbit.port == ID::B && initconst(sig_a[portbit.offset]) == absorbing) + continue; + + if (is_initval_used(sig_y[portbit.offset])) + return true; + } + else if (cell->type == ID($mem_v2)) + { + // TODO Use mem.h instead to uniformily cover all cases, most + // likely requires processing all memories when initializing + // the worker + if (!portbit.port.in(ID::WR_DATA, ID::WR_ADDR, ID::RD_ADDR)) + return true; + + if (portbit.port == ID::WR_DATA) + { + if (initconst(cell->getPort(ID::WR_EN)[portbit.offset]) == State::S0) + continue; + } + else if (portbit.port == ID::WR_ADDR) + { + int port = portbit.offset / cell->getParam(ID::ABITS).as_int(); + auto sig_en = cell->getPort(ID::WR_EN); + int width = cell->getParam(ID::WIDTH).as_int(); + + for (int i = port * width; i < (port + 1) * width; i++) + if (initconst(sig_en[i]) != State::S0) + return true; + + continue; + } + else if (portbit.port == ID::RD_ADDR) + { + int port = portbit.offset / cell->getParam(ID::ABITS).as_int(); + auto sig_en = cell->getPort(ID::RD_EN); + + if (initconst(sig_en[port]) != State::S0) + return true; + + continue; + } + else + return true; + } + else + log_assert(false); + } + + used_bits[bit] = false; + return false; + } +}; + +struct ReplacedPort { + IdString name; + int offset; + bool clk_pol; +}; + +struct HierarchyWorker +{ + Design *design; + pool<Module *> pending; + + dict<Module *, std::vector<ReplacedPort>> replaced_clk_inputs; + + HierarchyWorker(Design *design) : + design(design) + { + for (auto module : design->modules()) + pending.insert(module); + } + + void propagate(); + + const std::vector<ReplacedPort> &find_replaced_clk_inputs(IdString cell_type); +}; + +// Propagates replaced clock signals +struct PropagateWorker +{ + HierarchyWorker &hierarchy; + + Module *module; + SigMap sigmap; + + dict<SigBit, bool> replaced_clk_bits; + dict<SigBit, SigBit> not_drivers; + + std::vector<ReplacedPort> replaced_clk_inputs; + std::vector<std::pair<SigBit, bool>> pending; + + PropagateWorker(Module *module, HierarchyWorker &hierarchy) : + hierarchy(hierarchy), module(module), sigmap(module) + { + hierarchy.pending.erase(module); + + for (auto wire : module->wires()) + if (wire->has_attribute(ID::replaced_by_gclk)) + replace_clk_bit(SigBit(wire), wire->attributes[ID::replaced_by_gclk].bits.at(0) == State::S1, false); + + for (auto cell : module->cells()) { + if (cell->type.in(ID($not), ID($_NOT_))) { + auto sig_a = cell->getPort(ID::A); + auto &sig_y = cell->getPort(ID::Y); + sig_a.extend_u0(GetSize(sig_y), cell->hasParam(ID::A_SIGNED) && cell->parameters.at(ID::A_SIGNED).as_bool()); + + for (int i = 0; i < GetSize(sig_a); i++) + if (sig_a[i].is_wire()) + not_drivers.emplace(sigmap(sig_y[i]), sigmap(sig_a[i])); + } else { + for (auto &port_bit : hierarchy.find_replaced_clk_inputs(cell->type)) + replace_clk_bit(cell->getPort(port_bit.name)[port_bit.offset], port_bit.clk_pol, true); + } + } + + while (!pending.empty()) { + auto current = pending.back(); + pending.pop_back(); + auto it = not_drivers.find(current.first); + if (it == not_drivers.end()) + continue; + + replace_clk_bit(it->second, !current.second, true); + } + + for (auto cell : module->cells()) { + if (cell->type.in(ID($not), ID($_NOT_))) + continue; + for (auto &conn : cell->connections()) { + if (!cell->output(conn.first)) + continue; + for (SigBit bit : conn.second) { + sigmap.apply(bit); + if (replaced_clk_bits.count(bit)) + log_error("derived signal %s driven by %s (%s) from module %s is used as clock, derived clocks are only supported with clk2fflogic.\n", + log_signal(bit), log_id(cell->name), log_id(cell->type), log_id(module)); + } + } + } + + for (auto port : module->ports) { + auto wire = module->wire(port); + if (!wire->port_input) + continue; + for (int i = 0; i < GetSize(wire); i++) { + SigBit bit(wire, i); + sigmap.apply(bit); + auto it = replaced_clk_bits.find(bit); + if (it == replaced_clk_bits.end()) + continue; + replaced_clk_inputs.emplace_back(ReplacedPort {port, i, it->second}); + + if (it->second) { + bit = module->Not(NEW_ID, bit); + } + } + } + } + + void replace_clk_bit(SigBit bit, bool polarity, bool add_attribute) + { + sigmap.apply(bit); + if (!bit.is_wire()) + log_error("XXX todo\n"); + + auto it = replaced_clk_bits.find(bit); + if (it != replaced_clk_bits.end()) { + if (it->second != polarity) + log_error("signal %s from module %s is used as clock with different polarities, run clk2fflogic instead.\n", + log_signal(bit), log_id(module)); + return; + } + + replaced_clk_bits.emplace(bit, polarity); + + if (add_attribute) { + Wire *clk_wire = bit.wire; + if (bit.offset != 0 || GetSize(bit.wire) != 1) { + clk_wire = module->addWire(NEW_ID); + module->connect(RTLIL::SigBit(clk_wire), bit); + } + clk_wire->attributes[ID::replaced_by_gclk] = polarity ? State::S1 : State::S0; + clk_wire->set_bool_attribute(ID::keep); + } + + pending.emplace_back(bit, polarity); + } +}; + +const std::vector<ReplacedPort> &HierarchyWorker::find_replaced_clk_inputs(IdString cell_type) +{ + static const std::vector<ReplacedPort> empty; + if (!cell_type.isPublic()) + return empty; + + Module *module = design->module(cell_type); + if (module == nullptr) + return empty; + + auto it = replaced_clk_inputs.find(module); + if (it != replaced_clk_inputs.end()) + return it->second; + + if (pending.count(module)) { + PropagateWorker worker(module, *this); + return replaced_clk_inputs.emplace(module, std::move(worker.replaced_clk_inputs)).first->second; + } + + return empty; +} + + +void HierarchyWorker::propagate() +{ + while (!pending.empty()) + PropagateWorker worker(pending.pop(), *this); +} + +struct FormalFfPass : public Pass { + FormalFfPass() : Pass("formalff", "prepare FFs for formal") { } + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" formalff [options] [selection]\n"); + log("\n"); + log("This pass transforms clocked flip-flops to prepare a design for formal\n"); + log("verification. If a design contains latches and/or multiple different clocks run\n"); + log("the async2sync or clk2fflogic passes before using this pass.\n"); + log("\n"); + log(" -clk2ff\n"); + log(" Replace all clocked flip-flops with $ff cells that use the implicit\n"); + log(" global clock. This assumes, without checking, that the design uses a\n"); + log(" single global clock. If that is not the case, the clk2fflogic pass\n"); + log(" should be used instead.\n"); + log("\n"); + log(" -ff2anyinit\n"); + log(" Replace uninitialized bits of $ff cells with $anyinit cells. An\n"); + log(" $anyinit cell behaves exactly like an $ff cell with an undefined\n"); + log(" initialization value. The difference is that $anyinit inhibits\n"); + log(" don't-care optimizations and is used to track solver-provided values\n"); + log(" in witness traces.\n"); + log("\n"); + log(" If combined with -clk2ff this also affects newly created $ff cells.\n"); + log("\n"); + log(" -anyinit2ff\n"); + log(" Replaces $anyinit cells with uninitialized $ff cells. This performs the\n"); + log(" reverse of -ff2anyinit and can be used, before running a backend pass\n"); + log(" (or similar) that is not yet aware of $anyinit cells.\n"); + log("\n"); + log(" Note that after running -anyinit2ff, in general, performing don't-care\n"); + log(" optimizations is not sound in a formal verification setting.\n"); + log("\n"); + log(" -fine\n"); + log(" Emit fine-grained $_FF_ cells instead of coarse-grained $ff cells for\n"); + log(" -anyinit2ff. Cannot be combined with -clk2ff or -ff2anyinit.\n"); + log("\n"); + log(" -setundef\n"); + log(" Find FFs with undefined initialization values for which changing the\n"); + log(" initialization does not change the observable behavior and initialize\n"); + log(" them. For -ff2anyinit, this reduces the number of generated $anyinit\n"); + log(" cells that drive wires with private names.\n"); + log("\n"); + log(" -hierarchy\n"); + log(" Propagates the 'replaced_by_gclk' attribute set by clk2ff upwards\n"); + log(" through the design hierarchy towards the toplevel inputs. This option\n"); + log(" works on the whole design and ignores the selection.\n"); + log("\n"); + log(" -assume\n"); + log(" Add assumptions that constrain wires with the 'replaced_by_gclk'\n"); + log(" attribute to the value they would have before an active clock edge.\n"); + log("\n"); + + // TODO: An option to check whether all FFs use the same clock before changing it to the global clock + } + void execute(std::vector<std::string> args, RTLIL::Design *design) override + { + bool flag_clk2ff = false; + bool flag_ff2anyinit = false; + bool flag_anyinit2ff = false; + bool flag_fine = false; + bool flag_setundef = false; + bool flag_hierarchy = false; + bool flag_assume = false; + + log_header(design, "Executing FORMALFF pass.\n"); + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) + { + if (args[argidx] == "-clk2ff") { + flag_clk2ff = true; + continue; + } + if (args[argidx] == "-ff2anyinit") { + flag_ff2anyinit = true; + continue; + } + if (args[argidx] == "-anyinit2ff") { + flag_anyinit2ff = true; + continue; + } + if (args[argidx] == "-fine") { + flag_fine = true; + continue; + } + if (args[argidx] == "-setundef") { + flag_setundef = true; + continue; + } + if (args[argidx] == "-hierarchy") { + flag_hierarchy = true; + continue; + } + if (args[argidx] == "-assume") { + flag_assume = true; + continue; + } + break; + } + extra_args(args, argidx, design); + + if (!(flag_clk2ff || flag_ff2anyinit || flag_anyinit2ff || flag_hierarchy || flag_assume)) + log_cmd_error("One of the options -clk2ff, -ff2anyinit, -anyinit2ff, -hierarchy or -assume must be specified.\n"); + + if (flag_ff2anyinit && flag_anyinit2ff) + log_cmd_error("The options -ff2anyinit and -anyinit2ff are exclusive.\n"); + + if (flag_fine && !flag_anyinit2ff) + log_cmd_error("The option -fine requries the -anyinit2ff option.\n"); + + if (flag_fine && flag_clk2ff) + log_cmd_error("The options -fine and -clk2ff are exclusive.\n"); + + for (auto module : design->selected_modules()) + { + if (flag_setundef) + { + InitValWorker worker(module); + + for (auto cell : module->selected_cells()) + { + if (RTLIL::builtin_ff_cell_types().count(cell->type)) + { + FfData ff(&worker.initvals, cell); + if (ff.has_aload || ff.has_sr || ff.has_arst || ff.val_init.is_fully_def()) + continue; + + if (ff.has_ce && // CE can make the initval stick around + worker.initconst(ff.sig_ce.as_bit()) != (ff.pol_ce ? State::S1 : State::S0) && // unless it's known active + (!ff.has_srst || ff.ce_over_srst || + worker.initconst(ff.sig_srst.as_bit()) != (ff.pol_srst ? State::S1 : State::S0))) // or a srst with priority is known active + continue; + + auto before = ff.val_init; + for (int i = 0; i < ff.width; i++) + if (ff.val_init[i] == State::Sx && !worker.is_initval_used(ff.sig_q[i])) + ff.val_init[i] = State::S0; + + if (ff.val_init != before) { + log("Setting unused undefined initial value of %s.%s (%s) from %s to %s\n", + log_id(module), log_id(cell), log_id(cell->type), + log_const(before), log_const(ff.val_init)); + worker.initvals.set_init(ff.sig_q, ff.val_init); + } + } + } + } + SigMap sigmap(module); + FfInitVals initvals(&sigmap, module); + + + for (auto cell : module->selected_cells()) + { + if (flag_anyinit2ff && cell->type == ID($anyinit)) + { + FfData ff(&initvals, cell); + ff.remove(); + ff.is_anyinit = false; + ff.is_fine = flag_fine; + if (flag_fine) + for (int i = 0; i < ff.width; i++) + ff.slice({i}).emit(); + else + ff.emit(); + + continue; + } + + if (!RTLIL::builtin_ff_cell_types().count(cell->type)) + continue; + + FfData ff(&initvals, cell); + bool emit = false; + + if (flag_clk2ff && ff.has_clk) { + if (ff.sig_clk.is_fully_const()) + log_error("Const CLK on %s (%s) from module %s, run async2sync first.\n", + log_id(cell), log_id(cell->type), log_id(module)); + if (ff.has_aload || ff.has_arst || ff.has_sr) + log_error("Async inputs on %s (%s) from module %s, run async2sync first.\n", + log_id(cell), log_id(cell->type), log_id(module)); + + auto clk_wire = ff.sig_clk.is_wire() ? ff.sig_clk.as_wire() : nullptr; + + if (clk_wire == nullptr) { + clk_wire = module->addWire(NEW_ID); + module->connect(RTLIL::SigBit(clk_wire), ff.sig_clk); + } + + auto clk_polarity = ff.pol_clk ? State::S1 : State::S0; + + std::string attribute = clk_wire->get_string_attribute(ID::replaced_by_gclk); + + auto &attr = clk_wire->attributes[ID::replaced_by_gclk]; + + if (!attr.empty() && attr != clk_polarity) + log_error("CLK %s on %s (%s) from module %s also used with opposite polarity, run clk2fflogic instead.\n", + log_id(clk_wire), log_id(cell), log_id(cell->type), log_id(module)); + + attr = clk_polarity; + clk_wire->set_bool_attribute(ID::keep); + + // TODO propagate the replaced_by_gclk attribute upwards throughout the hierarchy + + ff.unmap_ce_srst(); + ff.has_clk = false; + ff.has_gclk = true; + emit = true; + } + + if (!ff.has_gclk) { + continue; + } + + if (flag_ff2anyinit && !ff.val_init.is_fully_def()) + { + ff.remove(); + emit = false; + + int cursor = 0; + while (cursor < ff.val_init.size()) + { + bool is_anyinit = ff.val_init[cursor] == State::Sx; + std::vector<int> bits; + bits.push_back(cursor++); + while (cursor < ff.val_init.size() && (ff.val_init[cursor] == State::Sx) == is_anyinit) + bits.push_back(cursor++); + + if ((int)bits.size() == ff.val_init.size()) { + // This check is only to make the private names more helpful for debugging + ff.is_anyinit = true; + ff.is_fine = false; + emit = true; + break; + } + + auto slice = ff.slice(bits); + slice.is_anyinit = is_anyinit; + slice.is_fine = false; + slice.emit(); + } + } + + if (emit) + ff.emit(); + } + } + + if (flag_hierarchy) { + HierarchyWorker worker(design); + worker.propagate(); + } + + if (flag_assume) { + for (auto module : design->selected_modules()) { + std::vector<pair<SigBit, bool>> found; + for (auto wire : module->wires()) { + if (!wire->has_attribute(ID::replaced_by_gclk)) + continue; + bool clk_pol = wire->attributes[ID::replaced_by_gclk].bits.at(0) == State::S1; + + found.emplace_back(SigSpec(wire), clk_pol); + } + for (auto pair : found) { + SigBit clk = pair.first; + + if (pair.second) + clk = module->Not(NEW_ID, clk); + + module->addAssume(NEW_ID, clk, State::S1); + + } + } + } + } +} FormalFfPass; + +PRIVATE_NAMESPACE_END diff --git a/passes/sat/miter.cc b/passes/sat/miter.cc index 37efadfbd..8f27c4c6f 100644 --- a/passes/sat/miter.cc +++ b/passes/sat/miter.cc @@ -30,7 +30,9 @@ void create_miter_equiv(struct Pass *that, std::vector<std::string> args, RTLIL: bool flag_make_outputs = false; bool flag_make_outcmp = false; bool flag_make_assert = false; + bool flag_make_cover = false; bool flag_flatten = false; + bool flag_cross = false; log_header(design, "Executing MITER pass (creating miter circuit).\n"); @@ -53,10 +55,18 @@ void create_miter_equiv(struct Pass *that, std::vector<std::string> args, RTLIL: flag_make_assert = true; continue; } + if (args[argidx] == "-make_cover") { + flag_make_cover = true; + continue; + } if (args[argidx] == "-flatten") { flag_flatten = true; continue; } + if (args[argidx] == "-cross") { + flag_cross = true; + continue; + } break; } if (argidx+3 != args.size() || args[argidx].compare(0, 1, "-") == 0) @@ -75,6 +85,7 @@ void create_miter_equiv(struct Pass *that, std::vector<std::string> args, RTLIL: RTLIL::Module *gold_module = design->module(gold_name); RTLIL::Module *gate_module = design->module(gate_name); + pool<Wire*> gold_cross_ports; for (auto gold_wire : gold_module->wires()) { if (gold_wire->port_id == 0) @@ -82,12 +93,17 @@ void create_miter_equiv(struct Pass *that, std::vector<std::string> args, RTLIL: RTLIL::Wire *gate_wire = gate_module->wire(gold_wire->name); if (gate_wire == nullptr) goto match_gold_port_error; + if (gold_wire->width != gate_wire->width) + goto match_gold_port_error; + if (flag_cross && !gold_wire->port_input && gold_wire->port_output && + gate_wire->port_input && !gate_wire->port_output) { + gold_cross_ports.insert(gold_wire); + continue; + } if (gold_wire->port_input != gate_wire->port_input) goto match_gold_port_error; if (gold_wire->port_output != gate_wire->port_output) goto match_gold_port_error; - if (gold_wire->width != gate_wire->width) - goto match_gold_port_error; continue; match_gold_port_error: log_cmd_error("No matching port in gate module was found for %s!\n", gold_wire->name.c_str()); @@ -99,12 +115,15 @@ void create_miter_equiv(struct Pass *that, std::vector<std::string> args, RTLIL: RTLIL::Wire *gold_wire = gold_module->wire(gate_wire->name); if (gold_wire == nullptr) goto match_gate_port_error; + if (gate_wire->width != gold_wire->width) + goto match_gate_port_error; + if (flag_cross && !gold_wire->port_input && gold_wire->port_output && + gate_wire->port_input && !gate_wire->port_output) + continue; if (gate_wire->port_input != gold_wire->port_input) goto match_gate_port_error; if (gate_wire->port_output != gold_wire->port_output) goto match_gate_port_error; - if (gate_wire->width != gold_wire->width) - goto match_gate_port_error; continue; match_gate_port_error: log_cmd_error("No matching port in gold module was found for %s!\n", gate_wire->name.c_str()); @@ -123,6 +142,22 @@ void create_miter_equiv(struct Pass *that, std::vector<std::string> args, RTLIL: for (auto gold_wire : gold_module->wires()) { + if (gold_cross_ports.count(gold_wire)) + { + SigSpec w = miter_module->addWire("\\cross_" + RTLIL::unescape_id(gold_wire->name), gold_wire->width); + gold_cell->setPort(gold_wire->name, w); + if (flag_ignore_gold_x) { + RTLIL::SigSpec w_x = miter_module->addWire(NEW_ID, GetSize(w)); + for (int i = 0; i < GetSize(w); i++) + miter_module->addEqx(NEW_ID, w[i], State::Sx, w_x[i]); + RTLIL::SigSpec w_any = miter_module->And(NEW_ID, miter_module->Anyseq(NEW_ID, GetSize(w)), w_x); + RTLIL::SigSpec w_masked = miter_module->And(NEW_ID, w, miter_module->Not(NEW_ID, w_x)); + w = miter_module->And(NEW_ID, w_any, w_masked); + } + gate_cell->setPort(gold_wire->name, w); + continue; + } + if (gold_wire->port_input) { RTLIL::Wire *w = miter_module->addWire("\\in_" + RTLIL::unescape_id(gold_wire->name), gold_wire->width); @@ -215,6 +250,12 @@ void create_miter_equiv(struct Pass *that, std::vector<std::string> args, RTLIL: miter_module->connect(RTLIL::SigSig(w_cmp, this_condition)); } + if (flag_make_cover) + { + auto cover_condition = miter_module->Not(NEW_ID, this_condition); + miter_module->addCover("\\cover_" + RTLIL::unescape_id(gold_wire->name), cover_condition, State::S1); + } + all_conditions.append(this_condition); } } @@ -380,10 +421,19 @@ struct MiterPass : public Pass { log(" -make_assert\n"); log(" also create an 'assert' cell that checks if trigger is always low.\n"); log("\n"); + log(" -make_cover\n"); + log(" also create a 'cover' cell for each gold/gate output pair.\n"); + log("\n"); log(" -flatten\n"); log(" call 'flatten -wb; opt_expr -keepdc -undriven;;' on the miter circuit.\n"); log("\n"); log("\n"); + log(" -cross\n"); + log(" allow output ports on the gold module to match input ports on the\n"); + log(" gate module. This is useful when the gold module contains additional\n"); + log(" logic to drive some of the gate module inputs.\n"); + log("\n"); + log("\n"); log(" miter -assert [options] module [miter_name]\n"); log("\n"); log("Creates a miter circuit for property checking. All input ports are kept,\n"); diff --git a/passes/sat/mutate.cc b/passes/sat/mutate.cc index 42eb0c6d0..02174be53 100644 --- a/passes/sat/mutate.cc +++ b/passes/sat/mutate.cc @@ -527,6 +527,8 @@ void mutate_list(Design *design, const mutate_opts_t &opts, const string &filena } log("Raw database size: %d\n", GetSize(database)); + if (N > GetSize(database)) + log_warning("%d mutations requested but only %d could be created!\n", N, GetSize(database)); if (N != 0) { database_reduce(database, opts, opts.none ? N-1 : N, rng); log("Reduced database size: %d\n", GetSize(database)); diff --git a/passes/sat/qbfsat.cc b/passes/sat/qbfsat.cc index 864d6f05d..db6836ea1 100644 --- a/passes/sat/qbfsat.cc +++ b/passes/sat/qbfsat.cc @@ -66,9 +66,9 @@ pool<std::string> validate_design_and_get_inputs(RTLIL::Module *module, bool ass } void specialize_from_file(RTLIL::Module *module, const std::string &file) { - YS_REGEX_TYPE hole_bit_assn_regex = YS_REGEX_COMPILE_WITH_SUBS("^(.+) ([0-9]+) ([^ ]+) \\[([0-9]+)] = ([01])$"); - YS_REGEX_TYPE hole_assn_regex = YS_REGEX_COMPILE_WITH_SUBS("^(.+) ([0-9]+) ([^ ]+) = ([01])$"); //if no index specified - YS_REGEX_MATCH_TYPE bit_m, m; + std::regex hole_bit_assn_regex = YS_REGEX_COMPILE_WITH_SUBS("^(.+) ([0-9]+) ([^ ]+) \\[([0-9]+)] = ([01])$"); + std::regex hole_assn_regex = YS_REGEX_COMPILE_WITH_SUBS("^(.+) ([0-9]+) ([^ ]+) = ([01])$"); //if no index specified + std::smatch bit_m, m; dict<pool<std::string>, RTLIL::Cell*> anyconst_loc_to_cell; dict<RTLIL::SigBit, RTLIL::State> hole_assignments; @@ -83,9 +83,9 @@ void specialize_from_file(RTLIL::Module *module, const std::string &file) { std::string buf; while (std::getline(fin, buf)) { bool bit_assn = true; - if (!YS_REGEX_NS::regex_search(buf, bit_m, hole_bit_assn_regex)) { + if (!std::regex_search(buf, bit_m, hole_bit_assn_regex)) { bit_assn = false; - if (!YS_REGEX_NS::regex_search(buf, m, hole_assn_regex)) + if (!std::regex_search(buf, m, hole_assn_regex)) log_cmd_error("solution file is not formatted correctly: \"%s\"\n", buf.c_str()); } @@ -216,7 +216,7 @@ 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 smtbmc_warning = "z3: WARNING:"; - const std::string smtbmc_cmd = stringf("%s -s %s %s -t 1 -g --binary %s %s/problem%d.smt2 2>&1", + const std::string smtbmc_cmd = stringf("\"%s\" -s %s %s -t 1 -g --binary %s %s/problem%d.smt2 2>&1", yosys_smtbmc_exe.c_str(), opt.get_solver_name().c_str(), (opt.timeout != 0? stringf("--timeout %d", opt.timeout) : "").c_str(), (opt.dump_final_smt2? "--dump-smt2 " + opt.dump_final_smt2_file : "").c_str(), @@ -416,6 +416,8 @@ QbfSolveOptions parse_args(const std::vector<std::string> &args) { opt.solver = opt.Solver::Yices; else if (args[opt.argidx+1] == "cvc4") opt.solver = opt.Solver::CVC4; + else if (args[opt.argidx+1] == "cvc5") + opt.solver = opt.Solver::CVC5; else log_cmd_error("Unknown solver \"%s\".\n", args[opt.argidx+1].c_str()); opt.argidx++; @@ -508,11 +510,11 @@ struct QbfSatPass : public Pass { log("\n"); log(" qbfsat [options] [selection]\n"); log("\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("This command solves an \"exists-forall\" 2QBF-SAT problem defined over the\n"); + log("currently selected module. Existentially-quantified variables are declared by\n"); + log("assigning a wire \"$anyconst\". Universally-quantified variables may be\n"); + log("explicitly declared by assigning a wire \"$allconst\", but module inputs will be\n"); + log("treated as universally-quantified variables by default.\n"); log("\n"); log(" -nocleanup\n"); log(" Do not delete temporary files and directories. Useful for debugging.\n"); @@ -521,27 +523,29 @@ struct QbfSatPass : public Pass { 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\n"); + log(" wires.\n"); log("\n"); log(" -assume-negative-polarity\n"); - log(" When adding $assume cells for one-bit module output wires, assume they are\n"); - log(" negative polarity signals and should always be low, for example like the\n"); - log(" miters created with the `miter` command.\n"); + log(" When adding $assume cells for one-bit module output wires, assume they\n"); + log(" are negative polarity signals and should always be low, for example like\n"); + log(" the miters created with the `miter` command.\n"); log("\n"); log(" -nooptimize\n"); - log(" Ignore \"\\minimize\" and \"\\maximize\" attributes, do not emit \"(maximize)\" or\n"); - log(" \"(minimize)\" in the SMT-LIBv2, and generally make no attempt to optimize anything.\n"); + log(" Ignore \"\\minimize\" and \"\\maximize\" attributes, do not emit\n"); + log(" \"(maximize)\" or \"(minimize)\" in the SMT-LIBv2, and generally make no\n"); + log(" 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 SMT-LIBv2 output and hope that the solver supports optimizing\n"); - log(" quantified bitvector problems.\n"); + log(" If a wire is marked with the \"\\minimize\" or \"\\maximize\" attribute,\n"); + log(" do not attempt to optimize that value with the default iterated solving\n"); + log(" and threshold bisection approach. Instead, have yosys-smtbmc emit a\n"); + log(" \"(minimize)\" or \"(maximize)\" command in the SMT-LIBv2 output and\n"); + log(" hope that the solver supports optimizing quantified bitvector problems.\n"); log("\n"); log(" -solver <solver>\n"); - log(" Use a particular solver. Choose one of: \"z3\", \"yices\", and \"cvc4\".\n"); - log(" (default: yices)\n"); + log(" Use a particular solver. Choose one of: \"z3\", \"yices\", \"cvc4\"\n"); + log(" and \"cvc5\". (default: yices)\n"); log("\n"); log(" -solver-option <name> <value>\n"); log(" Set the specified solver option in the SMT-LIBv2 problem file.\n"); @@ -567,13 +571,14 @@ struct QbfSatPass : public Pass { 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 each \"$anyconst\"\n"); - log(" cell in the current module with a constant value provided by the specified file.\n"); + log(" Do not run the solver, but instead only attempt to replace each\n"); + log(" \"$anyconst\" cell in the current module with a constant value provided\n"); + log(" by the specified file.\n"); log("\n"); log(" -write-solution <solution file>\n"); - 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(" If the problem is satisfiable, write the corresponding constant value\n"); + log(" for each \"$anyconst\" cell from the model produced by the solver to the\n"); + log(" specified file.\n"); log("\n"); } diff --git a/passes/sat/qbfsat.h b/passes/sat/qbfsat.h index c96c6f818..253cecce4 100644 --- a/passes/sat/qbfsat.h +++ b/passes/sat/qbfsat.h @@ -29,7 +29,7 @@ struct QbfSolveOptions { bool specialize = false, specialize_from_file = false, write_solution = false, nocleanup = false; bool dump_final_smt2 = false, assume_outputs = false, assume_neg = false, nooptimize = false; bool nobisection = false, sat = false, unsat = false, show_smtbmc = false; - enum Solver{Z3, Yices, CVC4} solver = Yices; + enum Solver{Z3, Yices, CVC4, CVC5} solver = Yices; enum OptimizationLevel{O0, O1, O2} oflag = O0; dict<std::string, std::string> solver_options; int timeout = 0; @@ -45,6 +45,8 @@ struct QbfSolveOptions { return "yices"; else if (solver == Solver::CVC4) return "cvc4"; + else if (solver == Solver::CVC5) + return "cvc5"; log_cmd_error("unknown solver specified.\n"); return ""; @@ -152,67 +154,67 @@ struct QbfSolutionType { } void recover_solution() { - 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_]* \\(([^:]*:[^\\)]*)\\): (.*)"); + std::regex sat_regex = YS_REGEX_COMPILE("Status: PASSED"); + std::regex unsat_regex = YS_REGEX_COMPILE("Solver Error.*model is not available"); + std::regex unsat_regex2 = YS_REGEX_COMPILE("Status: FAILED"); + std::regex timeout_regex = YS_REGEX_COMPILE("No solution found! \\(timeout\\)"); + std::regex timeout_regex2 = YS_REGEX_COMPILE("No solution found! \\(interrupted\\)"); + std::regex unknown_regex = YS_REGEX_COMPILE("No solution found! \\(unknown\\)"); + std::regex unknown_regex2 = YS_REGEX_COMPILE("Unexpected EOF response from solver"); + std::regex memout_regex = YS_REGEX_COMPILE("Solver Error:.*error \"out of memory\""); + std::regex hole_value_regex = YS_REGEX_COMPILE_WITH_SUBS("Value for anyconst in [a-zA-Z0-9_]* \\(([^:]*:[^\\)]*)\\): (.*)"); #ifndef NDEBUG - YS_REGEX_TYPE hole_loc_regex = YS_REGEX_COMPILE("[^:]*:[0-9]+.[0-9]+-[0-9]+.[0-9]+"); - YS_REGEX_TYPE hole_val_regex = YS_REGEX_COMPILE("[0-9]+"); + std::regex hole_loc_regex = YS_REGEX_COMPILE("[^:]*:[0-9]+.[0-9]+-[0-9]+.[0-9]+"); + std::regex hole_val_regex = YS_REGEX_COMPILE("[0-9]+"); #endif - YS_REGEX_MATCH_TYPE m; + std::smatch m; bool sat_regex_found = false; bool unsat_regex_found = false; dict<std::string, bool> hole_value_recovered; for (const std::string &x : stdout_lines) { - if(YS_REGEX_NS::regex_search(x, m, hole_value_regex)) { + if(std::regex_search(x, m, hole_value_regex)) { std::string loc = m[1].str(); std::string val = m[2].str(); #ifndef NDEBUG - log_assert(YS_REGEX_NS::regex_search(loc, hole_loc_regex)); - log_assert(YS_REGEX_NS::regex_search(val, hole_val_regex)); + log_assert(std::regex_search(loc, hole_loc_regex)); + log_assert(std::regex_search(val, hole_val_regex)); #endif auto locs = split_tokens(loc, "|"); pool<std::string> loc_pool(locs.begin(), locs.end()); hole_to_value[loc_pool] = val; } - else if (YS_REGEX_NS::regex_search(x, sat_regex)) { + else if (std::regex_search(x, sat_regex)) { sat_regex_found = true; sat = true; unknown = false; } - else if (YS_REGEX_NS::regex_search(x, unsat_regex)) { + else if (std::regex_search(x, unsat_regex)) { unsat_regex_found = true; sat = false; unknown = false; } - else if (YS_REGEX_NS::regex_search(x, memout_regex)) { + else if (std::regex_search(x, memout_regex)) { unknown = true; log_warning("solver ran out of memory\n"); } - else if (YS_REGEX_NS::regex_search(x, timeout_regex)) { + else if (std::regex_search(x, timeout_regex)) { unknown = true; log_warning("solver timed out\n"); } - else if (YS_REGEX_NS::regex_search(x, timeout_regex2)) { + else if (std::regex_search(x, timeout_regex2)) { unknown = true; log_warning("solver timed out\n"); } - else if (YS_REGEX_NS::regex_search(x, unknown_regex)) { + else if (std::regex_search(x, unknown_regex)) { unknown = true; log_warning("solver returned \"unknown\"\n"); } - else if (YS_REGEX_NS::regex_search(x, unsat_regex2)) { + else if (std::regex_search(x, unsat_regex2)) { unsat_regex_found = true; sat = false; unknown = false; } - else if (YS_REGEX_NS::regex_search(x, unknown_regex2)) { + else if (std::regex_search(x, unknown_regex2)) { unknown = true; } } diff --git a/passes/sat/sat.cc b/passes/sat/sat.cc index df2725b3c..69f81e3df 100644 --- a/passes/sat/sat.cc +++ b/passes/sat/sat.cc @@ -65,11 +65,12 @@ struct SatHelper int max_timestep, timeout; bool gotTimeout; - SatHelper(RTLIL::Design *design, RTLIL::Module *module, bool enable_undef) : + SatHelper(RTLIL::Design *design, RTLIL::Module *module, bool enable_undef, bool set_def_formal) : design(design), module(module), sigmap(module), ct(design), satgen(ez.get(), &sigmap) { this->enable_undef = enable_undef; satgen.model_undef = enable_undef; + satgen.def_formal = set_def_formal; set_init_def = false; set_init_undef = false; set_init_zero = false; @@ -254,7 +255,13 @@ struct SatHelper if (initstate) { - RTLIL::SigSpec big_lhs, big_rhs; + RTLIL::SigSpec big_lhs, big_rhs, forced_def; + + // Check for $anyinit cells that are forced to be defined + if (set_init_undef && satgen.def_formal) + for (auto cell : module->cells()) + if (cell->type == ID($anyinit)) + forced_def.append(sigmap(cell->getPort(ID::Q))); for (auto wire : module->wires()) { @@ -323,6 +330,7 @@ struct SatHelper if (set_init_undef) { RTLIL::SigSpec rem = satgen.initial_state.export_all(); rem.remove(big_lhs); + rem.remove(forced_def); big_lhs.append(rem); big_rhs.append(RTLIL::SigSpec(RTLIL::State::Sx, rem.size())); } @@ -758,6 +766,7 @@ struct SatHelper if (last_timestep == -2) log(" no model variables selected for display.\n"); + fprintf(f, "#%d\n", last_timestep+1); fclose(f); } @@ -932,6 +941,9 @@ struct SatPass : public Pass { log(" -set-def-inputs\n"); log(" add -set-def constraints for all module inputs\n"); log("\n"); + log(" -set-def-formal\n"); + log(" add -set-def constraints for formal $anyinit, $anyconst, $anyseq cells\n"); + log("\n"); log(" -show <signal>\n"); log(" show the model for the specified signal. if no -show option is\n"); log(" passed then a set of signals to be shown is automatically selected.\n"); @@ -1067,7 +1079,7 @@ struct SatPass : public Pass { std::map<int, std::vector<std::string>> unsets_at, sets_def_at, sets_any_undef_at, sets_all_undef_at; std::vector<std::string> shows, sets_def, sets_any_undef, sets_all_undef; int loopcount = 0, seq_len = 0, maxsteps = 0, initsteps = 0, timeout = 0, prove_skip = 0; - bool verify = false, fail_on_timeout = false, enable_undef = false, set_def_inputs = false; + bool verify = false, fail_on_timeout = false, enable_undef = false, set_def_inputs = false, set_def_formal = false; bool ignore_div_by_zero = false, set_init_undef = false, set_init_zero = false, max_undef = false; bool tempinduct = false, prove_asserts = false, show_inputs = false, show_outputs = false; bool show_regs = false, show_public = false, show_all = false; @@ -1140,6 +1152,11 @@ struct SatPass : public Pass { set_def_inputs = true; continue; } + if (args[argidx] == "-set-def-formal") { + enable_undef = true; + set_def_formal = true; + continue; + } if (args[argidx] == "-set" && argidx+2 < args.size()) { std::string lhs = args[++argidx]; std::string rhs = args[++argidx]; @@ -1379,8 +1396,8 @@ struct SatPass : public Pass { if (loopcount > 0 || max_undef) log_cmd_error("The options -max, -all, and -max_undef are not supported for temporal induction proofs!\n"); - SatHelper basecase(design, module, enable_undef); - SatHelper inductstep(design, module, enable_undef); + SatHelper basecase(design, module, enable_undef, set_def_formal); + SatHelper inductstep(design, module, enable_undef, set_def_formal); basecase.sets = sets; basecase.set_assumes = set_assumes; @@ -1569,7 +1586,7 @@ struct SatPass : public Pass { if (maxsteps > 0) log_cmd_error("The options -maxsteps is only supported for temporal induction proofs!\n"); - SatHelper sathelper(design, module, enable_undef); + SatHelper sathelper(design, module, enable_undef, set_def_formal); sathelper.sets = sets; sathelper.set_assumes = set_assumes; diff --git a/passes/sat/sim.cc b/passes/sat/sim.cc index d085fab2d..cfe31968d 100644 --- a/passes/sat/sim.cc +++ b/passes/sat/sim.cc @@ -23,6 +23,8 @@ #include "kernel/mem.h" #include "kernel/fstdata.h" #include "kernel/ff.h" +#include "kernel/yw.h" +#include "kernel/json.h" #include <ctime> @@ -74,6 +76,17 @@ struct OutputWriter SimWorker *worker; }; +struct SimInstance; +struct TriggeredAssertion { + int step; + SimInstance *instance; + Cell *cell; + + TriggeredAssertion(int step, SimInstance *instance, Cell *cell) : + step(step), instance(instance), cell(cell) + { } +}; + struct SimShared { bool debug = false; @@ -81,6 +94,7 @@ struct SimShared bool hide_internal = true; bool writeback = false; bool zinit = false; + bool hdlname = false; int rstlen = 1; FstData *fst = nullptr; double start_time = 0; @@ -92,6 +106,9 @@ struct SimShared bool ignore_x = false; bool date = false; bool multiclock = false; + int next_output_id = 0; + int step = 0; + std::vector<TriggeredAssertion> triggered_assertions; }; void zinit(State &v) @@ -151,12 +168,16 @@ struct SimInstance dict<Cell*, ff_state_t> ff_database; dict<IdString, mem_state_t> mem_database; pool<Cell*> formal_database; + pool<Cell*> initstate_database; dict<Cell*, IdString> mem_cells; std::vector<Mem> memories; dict<Wire*, pair<int, Const>> signal_database; + dict<IdString, std::map<int, pair<int, Const>>> trace_mem_database; + dict<std::pair<IdString, int>, Const> trace_mem_init_database; dict<Wire*, fstHandle> fst_handles; + dict<Wire*, fstHandle> fst_inputs; dict<IdString, dict<int,fstHandle>> fst_memories; SimInstance(SimShared *shared, std::string scope, Module *module, Cell *instance = nullptr, SimInstance *parent = nullptr) : @@ -230,7 +251,7 @@ struct SimInstance } } - if (RTLIL::builtin_ff_cell_types().count(cell->type)) { + if (RTLIL::builtin_ff_cell_types().count(cell->type) || cell->type == ID($anyinit)) { FfData ff_data(nullptr, cell); ff_state_t ff; ff.past_d = Const(State::Sx, ff_data.width); @@ -252,6 +273,8 @@ struct SimInstance if (cell->type.in(ID($assert), ID($cover), ID($assume))) { formal_database.insert(cell); } + if (cell->type == ID($initstate)) + initstate_database.insert(cell); } if (shared->zinit) @@ -298,6 +321,21 @@ struct SimInstance return log_id(module->name); } + vector<std::string> witness_full_path() const + { + if (instance != nullptr) + return parent->witness_full_path(instance); + return vector<std::string>(); + } + + vector<std::string> witness_full_path(Cell *cell) const + { + auto result = witness_full_path(); + auto cell_path = witness_path(cell); + result.insert(result.end(), cell_path.begin(), cell_path.end()); + return result; + } + Const get_state(SigSpec sig) { Const value; @@ -323,7 +361,7 @@ struct SimInstance log_assert(GetSize(sig) <= GetSize(value)); for (int i = 0; i < GetSize(sig); i++) - if (state_nets.at(sig[i]) != value[i]) { + if (value[i] != State::Sa && state_nets.at(sig[i]) != value[i]) { state_nets.at(sig[i]) = value[i]; dirty_bits.insert(sig[i]); did_something = true; @@ -336,12 +374,23 @@ struct SimInstance void set_memory_state(IdString memid, Const addr, Const data) { + set_memory_state(memid, addr.as_int(), data); + } + + void set_memory_state(IdString memid, int addr, Const data) + { auto &state = mem_database[memid]; - int offset = (addr.as_int() - state.mem->start_offset) * state.mem->width; + bool dirty = false; + + int offset = (addr - state.mem->start_offset) * state.mem->width; for (int i = 0; i < GetSize(data); i++) - if (0 <= i+offset && i+offset < state.mem->size * state.mem->width) - state.data.bits[i+offset] = data.bits[i]; + if (0 <= i+offset && i+offset < state.mem->size * state.mem->width && data.bits[i] != State::Sa) + if (state.data.bits[i+offset] != data.bits[i]) + dirty = true, state.data.bits[i+offset] = data.bits[i]; + + if (dirty) + dirty_memories.insert(memid); } void set_memory_state_bit(IdString memid, int offset, State data) @@ -349,7 +398,10 @@ struct SimInstance auto &state = mem_database[memid]; if (offset >= state.mem->size * state.mem->width) log_error("Addressing out of bounds bit %d/%d of memory %s\n", offset, state.mem->size * state.mem->width, log_id(memid)); - state.data.bits[offset] = data; + if (state.data.bits[offset] != data) { + state.data.bits[offset] = data; + dirty_memories.insert(memid); + } } void update_cell(Cell *cell) @@ -445,9 +497,14 @@ struct SimInstance log_error("Memory %s.%s has clocked read ports. Run 'memory' with -nordff.\n", log_id(module), log_id(mem.memid)); if (addr.is_fully_def()) { - int index = addr.as_int() - mem.start_offset; + int addr_int = addr.as_int(); + int index = addr_int - mem.start_offset; if (index >= 0 && index < mem.size) data = mdb.data.extract(index*mem.width, mem.width << port.wide_log2); + + for (int offset = 0; offset < 1 << port.wide_log2; offset++) { + register_memory_addr(id, addr_int + offset); + } } set_state(port.data, data); @@ -507,7 +564,7 @@ struct SimInstance } } - bool update_ph2() + bool update_ph2(bool gclk) { bool did_something = false; @@ -565,7 +622,8 @@ struct SimInstance } if (ff_data.has_gclk) { // $ff - current_q = ff.past_d; + if (gclk) + current_q = ff.past_d; } if (set_state(ff_data.sig_q, current_q)) did_something = true; @@ -601,7 +659,8 @@ struct SimInstance if (addr.is_fully_def()) { - int index = addr.as_int() - mem.start_offset; + int addr_int = addr.as_int(); + int index = addr_int - mem.start_offset; if (index >= 0 && index < mem.size) for (int i = 0; i < (mem.width << port.wide_log2); i++) if (enable[i] == State::S1 && mdb.data.bits.at(index*mem.width+i) != data[i]) { @@ -609,12 +668,15 @@ struct SimInstance dirty_memories.insert(mem.memid); did_something = true; } + + for (int i = 0; i < 1 << port.wide_log2; i++) + register_memory_addr(it.first, addr_int + i); } } } for (auto it : children) - if (it.second->update_ph2()) { + if (it.second->update_ph2(gclk)) { dirty_children.insert(it.second); did_something = true; } @@ -622,7 +684,7 @@ struct SimInstance return did_something; } - void update_ph3() + void update_ph3(bool check_assertions) { for (auto &it : ff_database) { @@ -657,35 +719,51 @@ struct SimInstance } } - for (auto cell : formal_database) + if (check_assertions) { - string label = log_id(cell); - if (cell->attributes.count(ID::src)) - label = cell->attributes.at(ID::src).decode_string(); + for (auto cell : formal_database) + { + string label = log_id(cell); + if (cell->attributes.count(ID::src)) + label = cell->attributes.at(ID::src).decode_string(); - State a = get_state(cell->getPort(ID::A))[0]; - State en = get_state(cell->getPort(ID::EN))[0]; + State a = get_state(cell->getPort(ID::A))[0]; + State en = get_state(cell->getPort(ID::EN))[0]; - if (cell->type == ID($cover) && en == State::S1 && a != State::S1) - log("Cover %s.%s (%s) reached.\n", hiername().c_str(), log_id(cell), label.c_str()); + if (en == State::S1 && (cell->type == ID($cover) ? a == State::S1 : a != State::S1)) { + shared->triggered_assertions.emplace_back(shared->step, this, cell); + } + + if (cell->type == ID($cover) && en == State::S1 && a == State::S1) + log("Cover %s.%s (%s) reached.\n", hiername().c_str(), log_id(cell), label.c_str()); - if (cell->type == ID($assume) && en == State::S1 && a != State::S1) - log("Assumption %s.%s (%s) failed.\n", hiername().c_str(), log_id(cell), label.c_str()); + if (cell->type == ID($assume) && en == State::S1 && a != State::S1) + log("Assumption %s.%s (%s) failed.\n", hiername().c_str(), log_id(cell), label.c_str()); - if (cell->type == ID($assert) && en == State::S1 && a != State::S1) - log_warning("Assert %s.%s (%s) failed.\n", hiername().c_str(), log_id(cell), label.c_str()); + if (cell->type == ID($assert) && en == State::S1 && a != State::S1) + log_warning("Assert %s.%s (%s) failed.\n", hiername().c_str(), log_id(cell), label.c_str()); + } } for (auto it : children) - it.second->update_ph3(); + it.second->update_ph3(check_assertions); } - void writeback(pool<Module*> &wbmods) + void set_initstate_outputs(State state) { - if (wbmods.count(module)) - log_error("Instance %s of module %s is not unique: Writeback not possible. (Fix by running 'uniquify'.)\n", hiername().c_str(), log_id(module)); + for (auto cell : initstate_database) + set_state(cell->getPort(ID::Y), state); + for (auto child : children) + child.second->set_initstate_outputs(state); + } - wbmods.insert(module); + void writeback(pool<Module*> &wbmods) + { + if (!ff_database.empty() || !mem_database.empty()) { + if (wbmods.count(module)) + log_error("Instance %s of module %s is not unique: Writeback not possible. (Fix by running 'uniquify'.)\n", hiername().c_str(), log_id(module)); + wbmods.insert(module); + } for (auto wire : module->wires()) wire->attributes.erase(ID::init); @@ -737,9 +815,17 @@ struct SimInstance child.second->register_signals(id); } - void write_output_header(std::function<void(IdString)> enter_scope, std::function<void()> exit_scope, std::function<void(Wire*, int, bool)> register_signal) + void write_output_header(std::function<void(IdString)> enter_scope, std::function<void()> exit_scope, std::function<void(const char*, int, Wire*, int, bool)> register_signal) { - enter_scope(name()); + int exit_scopes = 1; + if (shared->hdlname && instance != nullptr && instance->name.isPublic() && instance->has_attribute(ID::hdlname)) { + auto hdlname = instance->get_hdlname_attribute(); + log_assert(!hdlname.empty()); + for (auto name : hdlname) + enter_scope("\\" + name); + exit_scopes = hdlname.size(); + } else + enter_scope(name()); dict<Wire*,bool> registers; for (auto cell : module->cells()) @@ -755,13 +841,83 @@ struct SimInstance for (auto signal : signal_database) { - register_signal(signal.first, signal.second.first, registers.count(signal.first)!=0); + if (shared->hdlname && signal.first->name.isPublic() && signal.first->has_attribute(ID::hdlname)) { + auto hdlname = signal.first->get_hdlname_attribute(); + log_assert(!hdlname.empty()); + auto signal_name = std::move(hdlname.back()); + hdlname.pop_back(); + for (auto name : hdlname) + enter_scope("\\" + name); + register_signal(signal_name.c_str(), GetSize(signal.first), signal.first, signal.second.first, registers.count(signal.first)!=0); + for (auto name : hdlname) + exit_scope(); + } else + register_signal(log_id(signal.first->name), GetSize(signal.first), signal.first, signal.second.first, registers.count(signal.first)!=0); + } + + for (auto &trace_mem : trace_mem_database) + { + auto memid = trace_mem.first; + auto &mdb = mem_database.at(memid); + Cell *cell = mdb.mem->cell; + + std::vector<std::string> hdlname; + std::string signal_name; + bool has_hdlname = shared->hdlname && cell != nullptr && cell->name.isPublic() && cell->has_attribute(ID::hdlname); + + if (has_hdlname) { + hdlname = cell->get_hdlname_attribute(); + log_assert(!hdlname.empty()); + signal_name = std::move(hdlname.back()); + hdlname.pop_back(); + for (auto name : hdlname) + enter_scope("\\" + name); + } else { + signal_name = log_id(memid); + } + + for (auto &trace_index : trace_mem.second) { + int output_id = trace_index.second.first; + int index = trace_index.first; + register_signal( + stringf("%s[%d]", signal_name.c_str(), (index + mdb.mem->start_offset)).c_str(), + mdb.mem->width, nullptr, output_id, true); + } + + if (has_hdlname) + for (auto name : hdlname) + exit_scope(); } for (auto child : children) child.second->write_output_header(enter_scope, exit_scope, register_signal); - exit_scope(); + for (int i = 0; i < exit_scopes; i++) + exit_scope(); + } + + void register_memory_addr(IdString memid, int addr) + { + auto &mdb = mem_database.at(memid); + auto &mem = *mdb.mem; + int index = addr - mem.start_offset; + if (index < 0 || index >= mem.size) + return; + auto it = trace_mem_database.find(memid); + if (it != trace_mem_database.end() && it->second.count(index)) + return; + int output_id = shared->next_output_id++; + Const data; + if (!shared->output_data.empty()) { + auto init_it = trace_mem_init_database.find(std::make_pair(memid, addr)); + if (init_it != trace_mem_init_database.end()) + data = init_it->second; + else + data = mem.get_init_data().extract(index * mem.width, mem.width); + shared->output_data.front().second.emplace(output_id, data); + } + trace_mem_database[memid].emplace(index, make_pair(output_id, data)); + } void register_output_step_values(std::map<int,Const> *data) @@ -779,6 +935,26 @@ struct SimInstance data->emplace(id, value); } + for (auto &trace_mem : trace_mem_database) + { + auto memid = trace_mem.first; + auto &mdb = mem_database.at(memid); + auto &mem = *mdb.mem; + for (auto &trace_index : trace_mem.second) + { + int output_id = trace_index.second.first; + int index = trace_index.first; + + auto value = mdb.data.extract(index * mem.width, mem.width); + + if (trace_index.second.second == value) + continue; + + trace_index.second.second = value; + data->emplace(output_id, value); + } + } + for (auto child : children) child.second->register_output_step_values(data); } @@ -791,18 +967,6 @@ struct SimInstance std::string v = shared->fst->valueOf(item.second); did_something |= set_state(item.first, Const::from_string(v)); } - for (auto &it : ff_database) - { - ff_state_t &ff = it.second; - SigSpec dsig = it.second.data.sig_d; - Const value = get_state(dsig); - if (dsig.is_wire()) { - ff.past_d = value; - if (ff.data.has_aload) - ff.past_ad = value; - did_something |= true; - } - } for (auto cell : module->cells()) { if (cell->is_mem_cell()) { @@ -820,7 +984,7 @@ struct SimInstance return did_something; } - void addAdditionalInputs(std::map<Wire*,fstHandle> &inputs) + void addAdditionalInputs() { for (auto cell : module->cells()) { @@ -831,7 +995,7 @@ struct SimInstance for(auto &item : fst_handles) { if (item.second==0) continue; // Ignore signals not found if (sig_y == sigmap(item.first)) { - inputs[sig_y.as_wire()] = item.second; + fst_inputs[sig_y.as_wire()] = item.second; found = true; break; } @@ -842,7 +1006,21 @@ struct SimInstance } } for (auto child : children) - child.second->addAdditionalInputs(inputs); + child.second->addAdditionalInputs(); + } + + bool setInputs() + { + bool did_something = false; + for(auto &item : fst_inputs) { + std::string v = shared->fst->valueOf(item.second); + did_something |= set_state(item.first, Const::from_string(v)); + } + + for (auto child : children) + did_something |= child.second->setInputs(); + + return did_something; } void setState(dict<int, std::pair<SigBit,bool>> bits, std::string values) @@ -920,6 +1098,7 @@ struct SimWorker : SimShared std::string timescale; std::string sim_filename; std::string map_filename; + std::string summary_filename; std::string scope; ~SimWorker() @@ -930,8 +1109,8 @@ struct SimWorker : SimShared void register_signals() { - int id = 1; - top->register_signals(id); + next_output_id = 1; + top->register_signals(top->shared->next_output_id); } void register_output_step(int t) @@ -961,8 +1140,11 @@ struct SimWorker : SimShared writer->write(use_signal); } - void update() + void update(bool gclk) { + if (gclk) + step += 1; + while (1) { if (debug) @@ -973,14 +1155,24 @@ struct SimWorker : SimShared if (debug) log("\n-- ph2 --\n"); - if (!top->update_ph2()) + if (!top->update_ph2(gclk)) break; } if (debug) log("\n-- ph3 --\n"); - top->update_ph3(); + top->update_ph3(gclk); + } + + void initialize_stable_past() + { + if (debug) + log("\n-- ph1 (initialize) --\n"); + top->update_ph1(); + if (debug) + log("\n-- ph3 (initialize) --\n"); + top->update_ph3(true); } void set_inports(pool<IdString> ports, State value) @@ -1013,7 +1205,7 @@ struct SimWorker : SimShared set_inports(clock, State::Sx); set_inports(clockn, State::Sx); - update(); + update(false); register_output_step(0); @@ -1026,7 +1218,7 @@ struct SimWorker : SimShared set_inports(clock, State::S0); set_inports(clockn, State::S1); - update(); + update(true); register_output_step(10*cycle + 5); if (debug) @@ -1042,7 +1234,7 @@ struct SimWorker : SimShared set_inports(resetn, State::S1); } - update(); + update(true); register_output_step(10*cycle + 10); } @@ -1095,18 +1287,17 @@ struct SimWorker : SimShared } SigMap sigmap(topmod); - std::map<Wire*,fstHandle> inputs; for (auto wire : topmod->wires()) { if (wire->port_input) { fstHandle id = fst->getHandle(scope + "." + RTLIL::unescape_id(wire->name)); if (id==0) log_error("Unable to find required '%s' signal in file\n",(scope + "." + RTLIL::unescape_id(wire->name)).c_str()); - inputs[wire] = id; + top->fst_inputs[wire] = id; } } - top->addAdditionalInputs(inputs); + top->addAdditionalInputs(); uint64_t startCount = 0; uint64_t stopCount = 0; @@ -1152,18 +1343,15 @@ struct SimWorker : SimShared fst->reconstructAllAtTimes(fst_clock, startCount, stopCount, [&](uint64_t time) { if (verbose) log("Co-simulating %s %d [%lu%s].\n", (all_samples ? "sample" : "cycle"), cycle, (unsigned long)time, fst->getTimescaleString()); - bool did_something = false; - for(auto &item : inputs) { - std::string v = fst->valueOf(item.second); - did_something |= top->set_state(item.first, Const::from_string(v)); - } + bool did_something = top->setInputs(); if (initial) { did_something |= top->setInitState(); + initialize_stable_past(); initial = false; } if (did_something) - update(); + update(true); register_output_step(time); bool status = top->checkSignals(); @@ -1312,12 +1500,12 @@ struct SimWorker : SimShared set_inports(clock, State::S0); set_inports(clockn, State::S1); } - update(); + update(true); register_output_step(10*cycle); if (!multiclock && cycle) { set_inports(clock, State::S0); set_inports(clockn, State::S1); - update(); + update(true); register_output_step(10*cycle + 5); } cycle++; @@ -1389,12 +1577,12 @@ struct SimWorker : SimShared log("Simulating cycle %d.\n", cycle); set_inports(clock, State::S1); set_inports(clockn, State::S0); - update(); + update(true); register_output_step(10*cycle+0); if (!multiclock) { set_inports(clock, State::S0); set_inports(clockn, State::S1); - update(); + update(true); register_output_step(10*cycle+5); } cycle++; @@ -1458,6 +1646,242 @@ struct SimWorker : SimShared write_output_files(); } + struct FoundYWPath + { + SimInstance *instance; + Wire *wire; + IdString memid; + int addr; + }; + + struct YwHierarchy { + dict<IdPath, FoundYWPath> paths; + }; + + YwHierarchy prepare_yw_hierarchy(const ReadWitness &yw) + { + YwHierarchy hierarchy; + pool<IdPath> paths; + dict<IdPath, pool<IdString>> mem_paths; + + for (auto &signal : yw.signals) + paths.insert(signal.path); + + for (auto &clock : yw.clocks) + paths.insert(clock.path); + + for (auto &path : paths) + if (path.has_address()) + mem_paths[path.prefix()].insert(path.back()); + + witness_hierarchy(top->module, top, [&](IdPath const &path, WitnessHierarchyItem item, SimInstance *instance) { + if (item.cell != nullptr) + return instance->children.at(item.cell); + if (item.wire != nullptr) { + if (paths.count(path)) { + if (debug) + log("witness hierarchy: found wire %s\n", path.str().c_str()); + bool inserted = hierarchy.paths.emplace(path, {instance, item.wire, {}, INT_MIN}).second; + if (!inserted) + log_warning("Yosys witness path `%s` is ambiguous in this design\n", path.str().c_str()); + } + } else if (item.mem) { + auto it = mem_paths.find(path); + if (it != mem_paths.end()) { + if (debug) + log("witness hierarchy: found mem %s\n", path.str().c_str()); + IdPath word_path = path; + word_path.emplace_back(); + for (auto addr_part : it->second) { + word_path.back() = addr_part; + int addr; + word_path.get_address(addr); + if (addr < item.mem->start_offset || (addr - item.mem->start_offset) >= item.mem->size) + continue; + bool inserted = hierarchy.paths.emplace(word_path, {instance, nullptr, item.mem->memid, addr}).second; + if (!inserted) + log_warning("Yosys witness path `%s` is ambiguous in this design\n", path.str().c_str()); + } + } + } + return instance; + }); + + for (auto &path : paths) + if (!hierarchy.paths.count(path)) + log_warning("Yosys witness path `%s` was not found in this design, ignoring\n", path.str().c_str()); + + dict<IdPath, dict<int, bool>> clock_inputs; + + for (auto &clock : yw.clocks) { + if (clock.is_negedge == clock.is_posedge) + continue; + clock_inputs[clock.path].emplace(clock.offset, clock.is_posedge); + } + for (auto &signal : yw.signals) { + auto it = clock_inputs.find(signal.path); + if (it == clock_inputs.end()) + continue; + + for (auto &clock_input : it->second) { + int offset = clock_input.first; + if (offset >= signal.offset && (offset - signal.offset) < signal.width) { + int clock_bits_offset = signal.bits_offset + (offset - signal.offset); + + State expected = clock_input.second ? State::S0 : State::S1; + + for (int t = 0; t < GetSize(yw.steps); t++) { + if (yw.get_bits(t, clock_bits_offset, 1) != expected) + log_warning("Yosys witness trace has an unexpected value for the clock input `%s` in step %d.\n", signal.path.str().c_str(), t); + } + } + } + } + // TODO add checks and warnings for witness signals (toplevel inputs, $any*) not present in the witness file + + return hierarchy; + } + + void set_yw_state(const ReadWitness &yw, const YwHierarchy &hierarchy, int t) + { + log_assert(t >= 0 && t < GetSize(yw.steps)); + + for (auto &signal : yw.signals) { + if (signal.init_only && t >= 1) + continue; + auto found_path_it = hierarchy.paths.find(signal.path); + if (found_path_it == hierarchy.paths.end()) + continue; + auto &found_path = found_path_it->second; + + Const value = yw.get_bits(t, signal.bits_offset, signal.width); + + if (debug) + log("yw: set %s to %s\n", signal.path.str().c_str(), log_const(value)); + + if (found_path.wire != nullptr) { + found_path.instance->set_state( + SigChunk(found_path.wire, signal.offset, signal.width), + value); + } else if (!found_path.memid.empty()) { + if (t >= 1) + found_path.instance->register_memory_addr(found_path.memid, found_path.addr); + else + found_path.instance->trace_mem_init_database.emplace(make_pair(found_path.memid, found_path.addr), value); + found_path.instance->set_memory_state( + found_path.memid, found_path.addr, + value); + } + } + } + + void set_yw_clocks(const ReadWitness &yw, const YwHierarchy &hierarchy, bool active_edge) + { + for (auto &clock : yw.clocks) { + if (clock.is_negedge == clock.is_posedge) + continue; + auto found_path_it = hierarchy.paths.find(clock.path); + if (found_path_it == hierarchy.paths.end()) + continue; + auto &found_path = found_path_it->second; + + if (found_path.wire != nullptr) { + found_path.instance->set_state( + SigChunk(found_path.wire, clock.offset, 1), + active_edge == clock.is_posedge ? State::S1 : State::S0); + } + } + } + + void run_cosim_yw_witness(Module *topmod, int append) + { + if (!clock.empty()) + log_cmd_error("The -clock option is not required nor supported when reading a Yosys witness file.\n"); + if (!reset.empty()) + log_cmd_error("The -reset option is not required nor supported when reading a Yosys witness file.\n"); + if (multiclock) + log_warning("The -multiclock option is not required and ignored when reading a Yosys witness file.\n"); + + ReadWitness yw(sim_filename); + + top = new SimInstance(this, scope, topmod); + register_signals(); + + YwHierarchy hierarchy = prepare_yw_hierarchy(yw); + + if (yw.steps.empty()) { + log_warning("Yosys witness file `%s` contains no time steps\n", yw.filename.c_str()); + } else { + top->set_initstate_outputs(State::S1); + set_yw_state(yw, hierarchy, 0); + set_yw_clocks(yw, hierarchy, true); + initialize_stable_past(); + register_output_step(0); + + if (!yw.clocks.empty()) { + if (debug) + log("Simulating non-active clock edge.\n"); + set_yw_clocks(yw, hierarchy, false); + update(false); + register_output_step(5); + } + top->set_initstate_outputs(State::S0); + } + + for (int cycle = 1; cycle < GetSize(yw.steps) + append; cycle++) + { + if (verbose) + log("Simulating cycle %d.\n", cycle); + if (cycle < GetSize(yw.steps)) + set_yw_state(yw, hierarchy, cycle); + set_yw_clocks(yw, hierarchy, true); + update(true); + register_output_step(10 * cycle); + + if (!yw.clocks.empty()) { + if (debug) + log("Simulating non-active clock edge.\n"); + set_yw_clocks(yw, hierarchy, false); + update(false); + register_output_step(5 + 10 * cycle); + } + } + + register_output_step(10 * (GetSize(yw.steps) + append)); + write_output_files(); + } + + void write_summary() + { + if (summary_filename.empty()) + return; + + PrettyJson json; + if (!json.write_to_file(summary_filename)) + log_error("Can't open file `%s' for writing: %s\n", summary_filename.c_str(), strerror(errno)); + + json.begin_object(); + json.entry("version", "Yosys sim summary"); + json.entry("generator", yosys_version_str); + json.entry("steps", step); + json.entry("top", log_id(top->module->name)); + json.name("assertions"); + json.begin_array(); + for (auto &assertion : triggered_assertions) { + json.begin_object(); + json.entry("step", assertion.step); + json.entry("type", log_id(assertion.cell->type)); + json.entry("path", assertion.instance->witness_full_path(assertion.cell)); + auto src = assertion.cell->get_string_attribute(ID::src); + if (!src.empty()) { + json.entry("src", src); + } + json.end_object(); + } + json.end_array(); + json.end_object(); + } + std::string define_signal(Wire *wire) { std::stringstream f; @@ -1702,7 +2126,17 @@ struct VCDWriter : public OutputWriter worker->top->write_output_header( [this](IdString name) { vcdfile << stringf("$scope module %s $end\n", log_id(name)); }, [this]() { vcdfile << stringf("$upscope $end\n");}, - [this,use_signal](Wire *wire, int id, bool is_reg) { if (use_signal.at(id)) vcdfile << stringf("$var %s %d n%d %s%s $end\n", is_reg ? "reg" : "wire", GetSize(wire), id, wire->name[0] == '$' ? "\\" : "", log_id(wire)); } + [this,use_signal](const char *name, int size, Wire *, int id, bool is_reg) { + if (use_signal.at(id)) { + // Works around gtkwave trying to parse everything past the last [ in a signal + // name. While the emitted range doesn't necessarily match the wire's range, + // this is consistent with the range gtkwave makes up if it doesn't find a + // range + std::string range = strchr(name, '[') ? stringf("[%d:0]", size - 1) : std::string(); + vcdfile << stringf("$var %s %d n%d %s%s%s $end\n", is_reg ? "reg" : "wire", size, id, name[0] == '$' ? "\\" : "", name, range.c_str()); + + } + } ); vcdfile << stringf("$enddefinitions $end\n"); @@ -1760,11 +2194,10 @@ struct FSTWriter : public OutputWriter worker->top->write_output_header( [this](IdString name) { fstWriterSetScope(fstfile, FST_ST_VCD_MODULE, stringf("%s",log_id(name)).c_str(), nullptr); }, [this]() { fstWriterSetUpscope(fstfile); }, - [this,use_signal](Wire *wire, int id, bool is_reg) { + [this,use_signal](const char *name, int size, Wire *, int id, bool is_reg) { if (!use_signal.at(id)) return; - fstHandle fst_id = fstWriterCreateVar(fstfile, is_reg ? FST_VT_VCD_REG : FST_VT_VCD_WIRE, FST_VD_IMPLICIT, GetSize(wire), - stringf("%s%s", wire->name[0] == '$' ? "\\" : "", log_id(wire)).c_str(), 0); - + fstHandle fst_id = fstWriterCreateVar(fstfile, is_reg ? FST_VT_VCD_REG : FST_VT_VCD_WIRE, FST_VD_IMPLICIT, size, + name, 0); mapping.emplace(id, fst_id); } ); @@ -1846,7 +2279,7 @@ struct AIWWriter : public OutputWriter worker->top->write_output_header( [](IdString) {}, []() {}, - [this](Wire *wire, int id, bool) { mapping[wire] = id; } + [this](const char */*name*/, int /*size*/, Wire *wire, int id, bool) { if (wire != nullptr) mapping[wire] = id; } ); std::map<int, Yosys::RTLIL::Const> current; @@ -1935,6 +2368,10 @@ struct SimPass : public Pass { log(" write the simulation results to an AIGER witness file\n"); log(" (requires a *.aim file via -map)\n"); log("\n"); + log(" -hdlname\n"); + log(" use the hdlname attribute when writing simulation results\n"); + log(" (preserves hierarchy in a flattened design)\n"); + log("\n"); log(" -x\n"); log(" ignore constant x outputs in simulation file.\n"); log("\n"); @@ -1974,9 +2411,16 @@ struct SimPass : public Pass { log(" -w\n"); log(" writeback mode: use final simulation state as new init state\n"); log("\n"); - log(" -r\n"); - log(" read simulation results file (file formats supported: FST, VCD, AIW and WIT)\n"); - log(" VCD support requires vcd2fst external tool to be present\n"); + log(" -r <filename>\n"); + log(" read simulation or formal results file\n"); + log(" File formats supported: FST, VCD, AIW, WIT and .yw\n"); + log(" VCD support requires vcd2fst external tool to be present\n"); + log("\n"); + log(" -append <integer>\n"); + log(" number of extra clock cycles to simulate for a Yosys witness input\n"); + log("\n"); + log(" -summary <filename>\n"); + log(" write a JSON summary to the given file\n"); log("\n"); log(" -map <filename>\n"); log(" read file with port and latch symbols, needed for AIGER witness input\n"); @@ -2023,6 +2467,7 @@ struct SimPass : public Pass { { SimWorker worker; int numcycles = 20; + int append = 0; bool start_set = false, stop_set = false, at_set = false; log_header(design, "Executing SIM pass (simulate the circuit).\n"); @@ -2047,6 +2492,10 @@ struct SimPass : public Pass { worker.outputfiles.emplace_back(std::unique_ptr<AIWWriter>(new AIWWriter(&worker, aiw_filename.c_str()))); continue; } + if (args[argidx] == "-hdlname") { + worker.hdlname = true; + continue; + } if (args[argidx] == "-n" && argidx+1 < args.size()) { numcycles = atoi(args[++argidx].c_str()); worker.cycles_set = true; @@ -2102,12 +2551,22 @@ struct SimPass : public Pass { worker.sim_filename = sim_filename; continue; } + if (args[argidx] == "-append" && argidx+1 < args.size()) { + append = atoi(args[++argidx].c_str()); + continue; + } if (args[argidx] == "-map" && argidx+1 < args.size()) { std::string map_filename = args[++argidx]; rewrite_filename(map_filename); worker.map_filename = map_filename; continue; } + if (args[argidx] == "-summary" && argidx+1 < args.size()) { + std::string summary_filename = args[++argidx]; + rewrite_filename(summary_filename); + worker.summary_filename = summary_filename; + continue; + } if (args[argidx] == "-scope" && argidx+1 < args.size()) { worker.scope = args[++argidx]; continue; @@ -2191,10 +2650,14 @@ struct SimPass : public Pass { worker.run_cosim_aiger_witness(top_mod); } else if (filename_trim.size() > 4 && filename_trim.compare(filename_trim.size()-4, std::string::npos, ".wit") == 0) { worker.run_cosim_btor2_witness(top_mod); + } else if (filename_trim.size() > 3 && filename_trim.compare(filename_trim.size()-3, std::string::npos, ".yw") == 0) { + worker.run_cosim_yw_witness(top_mod, append); } else { log_cmd_error("Unhandled extension for simulation input file `%s`.\n", worker.sim_filename.c_str()); } } + + worker.write_summary(); } } SimPass; @@ -2206,8 +2669,8 @@ struct Fst2TbPass : public Pass { log("\n"); log(" fst2tb [options] [top-level]\n"); log("\n"); - log("This command generates testbench for the circuit using the given top-level module\n"); - log("and simulus signal from FST file\n"); + log("This command generates testbench for the circuit using the given top-level\n"); + log("module and simulus signal from FST file\n"); log("\n"); log(" -tb <name>\n"); log(" generated testbench name.\n"); diff --git a/passes/techmap/Makefile.inc b/passes/techmap/Makefile.inc index 98ccfc303..1b834fabc 100644 --- a/passes/techmap/Makefile.inc +++ b/passes/techmap/Makefile.inc @@ -31,6 +31,7 @@ OBJS += passes/techmap/dffinit.o OBJS += passes/techmap/pmuxtree.o OBJS += passes/techmap/bmuxmap.o OBJS += passes/techmap/demuxmap.o +OBJS += passes/techmap/bwmuxmap.o OBJS += passes/techmap/muxcover.o OBJS += passes/techmap/aigmap.o OBJS += passes/techmap/tribuf.o diff --git a/passes/techmap/abc.cc b/passes/techmap/abc.cc index 61ee99ee7..da601a856 100644 --- a/passes/techmap/abc.cc +++ b/passes/techmap/abc.cc @@ -29,11 +29,11 @@ // Kahn, Arthur B. (1962), "Topological sorting of large networks", Communications of the ACM 5 (11): 558-562, doi:10.1145/368996.369025 // http://en.wikipedia.org/wiki/Topological_sorting -#define ABC_COMMAND_LIB "strash; ifraig; scorr; dc2; dretime; strash; &get -n; &dch -f; &nf {D}; &put" -#define ABC_COMMAND_CTR "strash; ifraig; scorr; dc2; dretime; strash; &get -n; &dch -f; &nf {D}; &put; buffer; upsize {D}; dnsize {D}; stime -p" -#define ABC_COMMAND_LUT "strash; ifraig; scorr; dc2; dretime; strash; dch -f; if; mfs2" -#define ABC_COMMAND_SOP "strash; ifraig; scorr; dc2; dretime; strash; dch -f; cover {I} {P}" -#define ABC_COMMAND_DFL "strash; ifraig; scorr; dc2; dretime; strash; &get -n; &dch -f; &nf {D}; &put" +#define ABC_COMMAND_LIB "strash; &get -n; &fraig -x; &put; scorr; dc2; dretime; strash; &get -n; &dch -f; &nf {D}; &put" +#define ABC_COMMAND_CTR "strash; &get -n; &fraig -x; &put; scorr; dc2; dretime; strash; &get -n; &dch -f; &nf {D}; &put; buffer; upsize {D}; dnsize {D}; stime -p" +#define ABC_COMMAND_LUT "strash; &get -n; &fraig -x; &put; scorr; dc2; dretime; strash; dch -f; if; mfs2" +#define ABC_COMMAND_SOP "strash; &get -n; &fraig -x; &put; scorr; dc2; dretime; strash; dch -f; cover {I} {P}" +#define ABC_COMMAND_DFL "strash; &get -n; &fraig -x; &put; scorr; dc2; dretime; strash; &get -n; &dch -f; &nf {D}; &put" #define ABC_FAST_COMMAND_LIB "strash; dretime; map {D}" #define ABC_FAST_COMMAND_CTR "strash; dretime; map {D}; buffer; upsize {D}; dnsize {D}; stime -p" @@ -65,7 +65,9 @@ #include "frontends/blif/blifparse.h" #ifdef YOSYS_LINK_ABC -extern "C" int Abc_RealMain(int argc, char *argv[]); +namespace abc { + int Abc_RealMain(int argc, char *argv[]); +} #endif USING_YOSYS_NAMESPACE @@ -780,22 +782,25 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin if (dff_mode && clk_sig.empty()) log_cmd_error("Clock domain %s not found.\n", clk_str.c_str()); - std::string tempdir_name = get_base_tmpdir() + "/" + proc_program_prefix()+ "yosys-abc-XXXXXX"; - if (!cleanup) - tempdir_name[0] = tempdir_name[4] = '_'; + std::string tempdir_name; + if (cleanup) + tempdir_name = get_base_tmpdir() + "/"; + else + tempdir_name = "_tmp_"; + tempdir_name += proc_program_prefix() + "yosys-abc-XXXXXX"; tempdir_name = make_temp_dir(tempdir_name); log_header(design, "Extracting gate netlist of module `%s' to `%s/input.blif'..\n", module->name.c_str(), replace_tempdir(tempdir_name, tempdir_name, show_tempdir).c_str()); - std::string abc_script = stringf("read_blif %s/input.blif; ", tempdir_name.c_str()); + std::string abc_script = stringf("read_blif \"%s/input.blif\"; ", tempdir_name.c_str()); if (!liberty_files.empty() || !genlib_files.empty()) { for (std::string liberty_file : liberty_files) - abc_script += stringf("read_lib -w %s; ", liberty_file.c_str()); + abc_script += stringf("read_lib -w \"%s\"; ", liberty_file.c_str()); for (std::string liberty_file : genlib_files) - abc_script += stringf("read_library %s; ", liberty_file.c_str()); + abc_script += stringf("read_library \"%s\"; ", liberty_file.c_str()); if (!constr_file.empty()) - abc_script += stringf("read_constr -v %s; ", constr_file.c_str()); + abc_script += stringf("read_constr -v \"%s\"; ", constr_file.c_str()); } else if (!lut_costs.empty()) abc_script += stringf("read_lut %s/lutdefs.txt; ", tempdir_name.c_str()); @@ -844,7 +849,7 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin for (size_t pos = abc_script.find("{S}"); pos != std::string::npos; pos = abc_script.find("{S}", pos)) abc_script = abc_script.substr(0, pos) + lutin_shared + abc_script.substr(pos+3); if (abc_dress) - abc_script += "; dress"; + abc_script += stringf("; dress \"%s/input.blif\"", tempdir_name.c_str()); abc_script += stringf("; write_blif %s/output.blif", tempdir_name.c_str()); abc_script = add_echos_to_abc_cmd(abc_script); @@ -1083,7 +1088,7 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin fclose(f); } - buffer = stringf("%s -s -f %s/abc.script 2>&1", exe_file.c_str(), tempdir_name.c_str()); + buffer = stringf("\"%s\" -s -f %s/abc.script 2>&1", exe_file.c_str(), tempdir_name.c_str()); log("Running ABC command: %s\n", replace_tempdir(buffer, tempdir_name, show_tempdir).c_str()); #ifndef YOSYS_LINK_ABC @@ -1098,7 +1103,7 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin abc_argv[2] = strdup("-f"); abc_argv[3] = strdup(tmp_script_name.c_str()); abc_argv[4] = 0; - int ret = Abc_RealMain(4, abc_argv); + int ret = abc::Abc_RealMain(4, abc_argv); free(abc_argv[0]); free(abc_argv[1]); free(abc_argv[2]); @@ -1530,7 +1535,8 @@ struct AbcPass : public Pass { log(" NMUX, AOI3, OAI3, AOI4, OAI4.\n"); log(" (The NOT gate is always added to this list automatically.)\n"); log("\n"); - log(" The following aliases can be used to reference common sets of gate types:\n"); + log(" The following aliases can be used to reference common sets of gate\n"); + log(" types:\n"); log(" simple: AND OR XOR MUX\n"); log(" cmos2: NAND NOR\n"); log(" cmos3: NAND NOR AOI3 OAI3\n"); @@ -1574,8 +1580,8 @@ struct AbcPass : public Pass { log("\n"); log(" -dress\n"); log(" run the 'dress' command after all other ABC commands. This aims to\n"); - log(" preserve naming by an equivalence check between the original and post-ABC\n"); - log(" netlists (experimental).\n"); + log(" preserve naming by an equivalence check between the original and\n"); + log(" post-ABC netlists (experimental).\n"); log("\n"); log("When no target cell library is specified the Yosys standard cell library is\n"); log("loaded into ABC before the ABC script is executed.\n"); diff --git a/passes/techmap/abc9.cc b/passes/techmap/abc9.cc index 79c994b11..876917e56 100644 --- a/passes/techmap/abc9.cc +++ b/passes/techmap/abc9.cc @@ -93,8 +93,8 @@ struct Abc9Pass : public ScriptPass log("\n"); log(" abc9 [options] [selection]\n"); log("\n"); - log("This script pass performs a sequence of commands to facilitate the use of the ABC\n"); - log("tool [1] for technology mapping of the current design to a target FPGA\n"); + log("This script pass performs a sequence of commands to facilitate the use of the\n"); + log("ABC tool [1] for technology mapping of the current design to a target FPGA\n"); log("architecture. Only fully-selected modules are supported.\n"); log("\n"); log(" -run <from_label>:<to_label>\n"); @@ -404,9 +404,12 @@ struct Abc9Pass : public ScriptPass if (!active_design->selected_whole_module(mod)) log_error("Can't handle partially selected module %s!\n", log_id(mod)); - std::string tempdir_name = get_base_tmpdir() + "/" + proc_program_prefix() + "yosys-abc-XXXXXX"; - if (!cleanup) - tempdir_name[0] = tempdir_name[4] = '_'; + std::string tempdir_name; + if (cleanup) + tempdir_name = get_base_tmpdir() + "/"; + else + tempdir_name = "_tmp_"; + tempdir_name += proc_program_prefix() + "yosys-abc-XXXXXX"; tempdir_name = make_temp_dir(tempdir_name); if (!lut_mode) diff --git a/passes/techmap/abc9_exe.cc b/passes/techmap/abc9_exe.cc index a66e95e21..1d9bb4332 100644 --- a/passes/techmap/abc9_exe.cc +++ b/passes/techmap/abc9_exe.cc @@ -31,7 +31,9 @@ #endif #ifdef YOSYS_LINK_ABC -extern "C" int Abc_RealMain(int argc, char *argv[]); +namespace abc { + int Abc_RealMain(int argc, char *argv[]); +} #endif std::string fold_abc9_cmd(std::string str) @@ -173,12 +175,12 @@ void abc9_module(RTLIL::Design *design, std::string script_file, std::string exe if (!lut_costs.empty()) abc9_script += stringf("read_lut %s/lutdefs.txt; ", tempdir_name.c_str()); else if (!lut_file.empty()) - abc9_script += stringf("read_lut %s; ", lut_file.c_str()); + abc9_script += stringf("read_lut \"%s\"; ", lut_file.c_str()); else log_abort(); log_assert(!box_file.empty()); - abc9_script += stringf("read_box %s; ", box_file.c_str()); + abc9_script += stringf("read_box \"%s\"; ", box_file.c_str()); abc9_script += stringf("&read %s/input.xaig; &ps; ", tempdir_name.c_str()); if (!script_file.empty()) { @@ -262,7 +264,7 @@ void abc9_module(RTLIL::Design *design, std::string script_file, std::string exe fclose(f); } - buffer = stringf("%s -s -f %s/abc.script 2>&1", exe_file.c_str(), tempdir_name.c_str()); + buffer = stringf("\"%s\" -s -f %s/abc.script 2>&1", exe_file.c_str(), tempdir_name.c_str()); log("Running ABC command: %s\n", replace_tempdir(buffer, tempdir_name, show_tempdir).c_str()); #ifndef YOSYS_LINK_ABC @@ -277,7 +279,7 @@ void abc9_module(RTLIL::Design *design, std::string script_file, std::string exe abc9_argv[2] = strdup("-f"); abc9_argv[3] = strdup(tmp_script_name.c_str()); abc9_argv[4] = 0; - int ret = Abc_RealMain(4, abc9_argv); + int ret = abc::Abc_RealMain(4, abc9_argv); free(abc9_argv[0]); free(abc9_argv[1]); free(abc9_argv[2]); @@ -301,8 +303,8 @@ struct Abc9ExePass : public Pass { log("\n"); log(" \n"); log("This pass uses the ABC tool [1] for technology mapping of the top module\n"); - log("(according to the (* top *) attribute or if only one module is currently selected)\n"); - log("to a target FPGA architecture.\n"); + log("(according to the (* top *) attribute or if only one module is currently\n"); + log("selected) to a target FPGA architecture.\n"); log("\n"); log(" -exe <command>\n"); #ifdef ABCEXTERNAL diff --git a/passes/techmap/abc9_ops.cc b/passes/techmap/abc9_ops.cc index acafb0b65..9766e81cb 100644 --- a/passes/techmap/abc9_ops.cc +++ b/passes/techmap/abc9_ops.cc @@ -1572,14 +1572,14 @@ struct Abc9OpsPass : public Pass { log("the `abc9' script pass. Only fully-selected modules are supported.\n"); log("\n"); log(" -check\n"); - log(" check that the design is valid, e.g. (* abc9_box_id *) values are unique,\n"); - log(" (* abc9_carry *) is only given for one input/output port, etc.\n"); + log(" check that the design is valid, e.g. (* abc9_box_id *) values are\n"); + log(" unique, (* abc9_carry *) is only given for one input/output port, etc.\n"); log("\n"); log(" -prep_hier\n"); log(" derive all used (* abc9_box *) or (* abc9_flop *) (if -dff option)\n"); log(" whitebox modules. with (* abc9_flop *) modules, only those containing\n"); - log(" $dff/$_DFF_[NP]_ cells with zero initial state -- due to an ABC limitation\n"); - log(" -- will be derived.\n"); + log(" $dff/$_DFF_[NP]_ cells with zero initial state -- due to an ABC\n"); + log(" limitation -- will be derived.\n"); log("\n"); log(" -prep_bypass\n"); log(" create techmap rules in the '$abc9_map' and '$abc9_unmap' designs for\n"); @@ -1597,33 +1597,35 @@ struct Abc9OpsPass : public Pass { log(" -prep_dff_submod\n"); log(" within (* abc9_flop *) modules, rewrite all edge-sensitive path\n"); log(" declarations and $setup() timing checks ($specify3 and $specrule cells)\n"); - log(" that share a 'DST' port with the $_DFF_[NP]_.Q port from this 'Q' port to\n"); - log(" the DFF's 'D' port. this is to prepare such specify cells to be moved\n"); + log(" that share a 'DST' port with the $_DFF_[NP]_.Q port from this 'Q' port\n"); + log(" to the DFF's 'D' port. this is to prepare such specify cells to be moved\n"); log(" into the flop box.\n"); log("\n"); log(" -prep_dff_unmap\n"); - log(" populate the '$abc9_unmap' design with techmap rules for mapping *_$abc9_flop\n"); - log(" cells back into their derived cell types (where the rules created by\n"); - log(" -prep_hier will then map back to the original cell with parameters).\n"); + log(" populate the '$abc9_unmap' design with techmap rules for mapping\n"); + log(" *_$abc9_flop cells back into their derived cell types (where the rules\n"); + log(" created by -prep_hier will then map back to the original cell with\n"); + log(" parameters).\n"); log("\n"); log(" -prep_delays\n"); log(" insert `$__ABC9_DELAY' blackbox cells into the design to account for\n"); log(" certain required times.\n"); log("\n"); log(" -break_scc\n"); - log(" for an arbitrarily chosen cell in each unique SCC of each selected module\n"); - log(" (tagged with an (* abc9_scc_id = <int> *) attribute) interrupt all wires\n"); - log(" driven by this cell's outputs with a temporary $__ABC9_SCC_BREAKER cell\n"); - log(" to break the SCC.\n"); + log(" for an arbitrarily chosen cell in each unique SCC of each selected\n"); + log(" module (tagged with an (* abc9_scc_id = <int> *) attribute) interrupt\n"); + log(" all wires driven by this cell's outputs with a temporary\n"); + log(" $__ABC9_SCC_BREAKER cell to break the SCC.\n"); log("\n"); log(" -prep_xaiger\n"); log(" prepare the design for XAIGER output. this includes computing the\n"); - log(" topological ordering of ABC9 boxes, as well as preparing the '$abc9_holes'\n"); - log(" design that contains the logic behaviour of ABC9 whiteboxes.\n"); + log(" topological ordering of ABC9 boxes, as well as preparing the \n"); + log(" '$abc9_holes' design that contains the logic behaviour of ABC9\n"); + log(" whiteboxes.\n"); log("\n"); log(" -dff\n"); - log(" consider flop cells (those instantiating modules marked with (* abc9_flop *))\n"); - log(" during -prep_{delays,xaiger,box}.\n"); + log(" consider flop cells (those instantiating modules marked with\n"); + log(" (* abc9_flop *)) during -prep_{delays,xaiger,box}.\n"); log("\n"); log(" -prep_lut <maxlut>\n"); log(" pre-compute the lut library by analysing all modules marked with\n"); @@ -1641,8 +1643,8 @@ struct Abc9OpsPass : public Pass { log("\n"); log(" -reintegrate\n"); log(" for each selected module, re-intergrate the module '<module-name>$abc9'\n"); - log(" by first recovering ABC9 boxes, and then stitching in the remaining primary\n"); - log(" inputs and outputs.\n"); + log(" by first recovering ABC9 boxes, and then stitching in the remaining\n"); + log(" primary inputs and outputs.\n"); log("\n"); } void execute(std::vector<std::string> args, RTLIL::Design *design) override diff --git a/passes/techmap/bmuxmap.cc b/passes/techmap/bmuxmap.cc index 03673c278..7aa67d3c0 100644 --- a/passes/techmap/bmuxmap.cc +++ b/passes/techmap/bmuxmap.cc @@ -33,13 +33,22 @@ struct BmuxmapPass : public Pass { log("\n"); log("This pass transforms $bmux cells to trees of $mux cells.\n"); log("\n"); + log(" -pmux\n"); + log(" transform to $pmux instead of $mux cells.\n"); + log("\n"); } void execute(std::vector<std::string> args, RTLIL::Design *design) override { + bool pmux_mode = false; + log_header(design, "Executing BMUXMAP pass.\n"); size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) { + if (args[argidx] == "-pmux") { + pmux_mode = true; + continue; + } break; } extra_args(args, argidx, design); @@ -53,18 +62,36 @@ struct BmuxmapPass : public Pass { SigSpec sel = cell->getPort(ID::S); SigSpec data = cell->getPort(ID::A); int width = GetSize(cell->getPort(ID::Y)); + int s_width = GetSize(cell->getPort(ID::S)); - for (int idx = 0; idx < GetSize(sel); idx++) { - SigSpec new_data = module->addWire(NEW_ID, GetSize(data)/2); - for (int i = 0; i < GetSize(new_data); i += width) { - RTLIL::Cell *mux = module->addMux(NEW_ID, + if(pmux_mode) + { + int num_cases = 1 << s_width; + SigSpec new_a = SigSpec(State::Sx, width); + SigSpec new_s = module->addWire(NEW_ID, num_cases); + SigSpec new_data = module->addWire(NEW_ID, width); + for (int val = 0; val < num_cases; val++) + { + module->addEq(NEW_ID, sel, SigSpec(val, GetSize(sel)), new_s[val]); + } + RTLIL::Cell *pmux = module->addPmux(NEW_ID, new_a, data, new_s, new_data); + pmux->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); + data = new_data; + } + else + { + for (int idx = 0; idx < GetSize(sel); idx++) { + SigSpec new_data = module->addWire(NEW_ID, GetSize(data)/2); + for (int i = 0; i < GetSize(new_data); i += width) { + RTLIL::Cell *mux = module->addMux(NEW_ID, data.extract(i*2, width), data.extract(i*2+width, width), sel[idx], new_data.extract(i, width)); - mux->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); + mux->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); + } + data = new_data; } - data = new_data; } module->connect(cell->getPort(ID::Y), data); diff --git a/passes/techmap/bwmuxmap.cc b/passes/techmap/bwmuxmap.cc new file mode 100644 index 000000000..7fe1cded7 --- /dev/null +++ b/passes/techmap/bwmuxmap.cc @@ -0,0 +1,70 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2022 Jannis Harder <jix@yosyshq.com> <me@jix.one> + * + * 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 BwmuxmapPass : public Pass { + BwmuxmapPass() : Pass("bwmuxmap", "replace $bwmux cells with equivalent logic") {} + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" bwmxumap [options] [selection]\n"); + log("\n"); + log("This pass replaces $bwmux cells with equivalent logic\n"); + log("\n"); + } + void execute(std::vector<std::string> args, RTLIL::Design *design) override + { + log_header(design, "Executing BWMUXMAP pass.\n"); + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) { + // if (args[argidx] == "-arg") { + // continue; + // } + break; + } + + extra_args(args, argidx, design); + + for (auto module : design->selected_modules()) + for (auto cell : module->selected_cells()) + { + if (cell->type != ID($bwmux)) + continue; + auto &sig_y = cell->getPort(ID::Y); + auto &sig_a = cell->getPort(ID::A); + auto &sig_b = cell->getPort(ID::B); + auto &sig_s = cell->getPort(ID::S); + + auto not_s = module->Not(NEW_ID, sig_s); + auto masked_b = module->And(NEW_ID, sig_s, sig_b); + auto masked_a = module->And(NEW_ID, not_s, sig_a); + module->addOr(NEW_ID, masked_a, masked_b, sig_y); + + module->remove(cell); + } + } +} BwmuxmapPass; + +PRIVATE_NAMESPACE_END diff --git a/passes/techmap/dfflegalize.cc b/passes/techmap/dfflegalize.cc index 1d99caa3a..c6c3a58c5 100644 --- a/passes/techmap/dfflegalize.cc +++ b/passes/techmap/dfflegalize.cc @@ -118,34 +118,24 @@ struct DffLegalizePass : public Pass { log("- $_DLATCH_[NP][NP][01]_\n"); log("- $_DLATCHSR_[NP][NP][NP]_\n"); log("\n"); - log("The following transformations are performed by this pass:"); - log("\n"); - log("- upconversion from a less capable cell to a more capable cell, if the less"); - log(" capable cell is not supported (eg. dff -> dffe, or adff -> dffsr)"); - log("\n"); - log("- unmapping FFs with clock enable (due to unsupported cell type or -mince)"); - log("\n"); - log("- unmapping FFs with sync reset (due to unsupported cell type or -minsrst)"); - log("\n"); - log("- adding inverters on the control pins (due to unsupported polarity)"); + log("The following transformations are performed by this pass:\n"); log("\n"); + log("- upconversion from a less capable cell to a more capable cell, if the less\n"); + log(" capable cell is not supported (eg. dff -> dffe, or adff -> dffsr)\n"); + log("- unmapping FFs with clock enable (due to unsupported cell type or -mince)\n"); + log("- unmapping FFs with sync reset (due to unsupported cell type or -minsrst)\n"); + log("- adding inverters on the control pins (due to unsupported polarity)\n"); log("- adding inverters on the D and Q pins and inverting the init/reset values\n"); - log(" (due to unsupported init or reset value)"); - log("\n"); - log("- converting sr into adlatch (by tying D to 1 and using E as set input)"); - log("\n"); - log("- emulating unsupported dffsr cell by adff + adff + sr + mux"); - log("\n"); - log("- emulating unsupported dlatchsr cell by adlatch + adlatch + sr + mux"); - log("\n"); + log(" (due to unsupported init or reset value)\n"); + log("- converting sr into adlatch (by tying D to 1 and using E as set input)\n"); + log("- emulating unsupported dffsr cell by adff + adff + sr + mux\n"); + log("- emulating unsupported dlatchsr cell by adlatch + adlatch + sr + mux\n"); log("- emulating adff when the (reset, init) value combination is unsupported by\n"); - log(" dff + adff + dlatch + mux"); - log("\n"); + log(" dff + adff + dlatch + mux\n"); log("- emulating adlatch when the (reset, init) value combination is unsupported by\n"); - log("- dlatch + adlatch + dlatch + mux"); - log("\n"); - log("If the pass is unable to realize a given cell type (eg. adff when only plain dff"); - log("is available), an error is raised."); + log("- dlatch + adlatch + dlatch + mux\n"); + log("If the pass is unable to realize a given cell type (eg. adff when only plain dff\n"); + log("is available), an error is raised.\n"); } // Table of all supported cell types. diff --git a/passes/techmap/dfflibmap.cc b/passes/techmap/dfflibmap.cc index 252baae9a..12c3a95de 100644 --- a/passes/techmap/dfflibmap.cc +++ b/passes/techmap/dfflibmap.cc @@ -431,7 +431,7 @@ struct DfflibmapPass : public Pass { log("cells, leaving remaining internal cells untouched.\n"); log("\n"); log("When called with -info, this command will only print the target cell\n"); - log("list, along with their associated internal cell types, and the arguments"); + log("list, along with their associated internal cell types, and the arguments\n"); log("that would be passed to the dfflegalize pass. The design will not be\n"); log("changed.\n"); log("\n"); diff --git a/passes/techmap/dffunmap.cc b/passes/techmap/dffunmap.cc index 7312015f1..8703bf1a0 100644 --- a/passes/techmap/dffunmap.cc +++ b/passes/techmap/dffunmap.cc @@ -33,8 +33,8 @@ struct DffunmapPass : public Pass { log(" dffunmap [options] [selection]\n"); log("\n"); log("This pass transforms FF types with clock enable and/or synchronous reset into\n"); - log("their base type (with neither clock enable nor sync reset) by emulating the clock\n"); - log("enable and synchronous reset with multiplexers on the cell input.\n"); + log("their base type (with neither clock enable nor sync reset) by emulating the\n"); + log("clock enable and synchronous reset with multiplexers on the cell input.\n"); log("\n"); log(" -ce-only\n"); log(" unmap only clock enables, leave synchronous resets alone.\n"); diff --git a/passes/techmap/flowmap.cc b/passes/techmap/flowmap.cc index dfdbe6b88..579503a0b 100644 --- a/passes/techmap/flowmap.cc +++ b/passes/techmap/flowmap.cc @@ -1406,7 +1406,8 @@ struct FlowmapWorker RTLIL::SigSpec lut_a, lut_y = node; for (auto input_node : input_nodes) lut_a.append(input_node); - lut_a.append(RTLIL::Const(State::Sx, minlut - input_nodes.size())); + if ((int)input_nodes.size() < minlut) + lut_a.append(RTLIL::Const(State::Sx, minlut - input_nodes.size())); RTLIL::Cell *lut = module->addLut(NEW_ID, lut_a, lut_y, lut_table); mapped_nodes.insert(node); diff --git a/passes/techmap/insbuf.cc b/passes/techmap/insbuf.cc index 68c22c317..f288987a1 100644 --- a/passes/techmap/insbuf.cc +++ b/passes/techmap/insbuf.cc @@ -36,12 +36,16 @@ struct InsbufPass : public Pass { log(" Use the given cell type instead of $_BUF_. (Notice that the next\n"); log(" call to \"clean\" will remove all $_BUF_ in the design.)\n"); log("\n"); + log(" -chain\n"); + log(" Chain buffer cells\n"); + log("\n"); } void execute(std::vector<std::string> args, RTLIL::Design *design) override { log_header(design, "Executing INSBUF pass (insert buffer cells for connected wires).\n"); IdString celltype = ID($_BUF_), in_portname = ID::A, out_portname = ID::Y; + bool chain_mode = false; size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) @@ -53,6 +57,10 @@ struct InsbufPass : public Pass { out_portname = RTLIL::escape_id(args[++argidx]); continue; } + if (arg == "-chain") { + chain_mode = true; + continue; + } break; } extra_args(args, argidx, design); @@ -60,6 +68,8 @@ struct InsbufPass : public Pass { for (auto module : design->selected_modules()) { std::vector<RTLIL::SigSig> new_connections; + pool<Cell*> bufcells; + SigMap sigmap; for (auto &conn : module->connections()) { @@ -70,22 +80,48 @@ struct InsbufPass : public Pass { SigBit lhs = conn.first[i]; SigBit rhs = conn.second[i]; - if (lhs.wire && !design->selected(module, lhs.wire)) { + if (!lhs.wire || !design->selected(module, lhs.wire)) { new_conn.first.append(lhs); new_conn.second.append(rhs); + log("Skip %s: %s -> %s\n", log_id(module), log_signal(rhs), log_signal(lhs)); continue; } + if (chain_mode && rhs.wire) { + rhs = sigmap(rhs); + SigBit outbit = sigmap(lhs); + sigmap.add(lhs, rhs); + sigmap.add(outbit); + } + Cell *cell = module->addCell(NEW_ID, celltype); cell->setPort(in_portname, rhs); cell->setPort(out_portname, lhs); - log("Added %s.%s: %s -> %s\n", log_id(module), log_id(cell), log_signal(rhs), log_signal(lhs)); + + log("Add %s/%s: %s -> %s\n", log_id(module), log_id(cell), log_signal(rhs), log_signal(lhs)); + bufcells.insert(cell); } if (GetSize(new_conn.first)) new_connections.push_back(new_conn); } + if (chain_mode) { + for (auto &cell : module->selected_cells()) { + if (bufcells.count(cell)) + continue; + for (auto &port : cell->connections()) + if (cell->input(port.first)) { + auto s = sigmap(port.second); + if (s == port.second) + continue; + log("Rewrite %s/%s/%s: %s -> %s\n", log_id(module), log_id(cell), + log_id(port.first), log_signal(port.second), log_signal(s)); + cell->setPort(port.first, s); + } + } + } + module->new_connections(new_connections); } } diff --git a/passes/techmap/simplemap.cc b/passes/techmap/simplemap.cc index 7d8dba439..11692b715 100644 --- a/passes/techmap/simplemap.cc +++ b/passes/techmap/simplemap.cc @@ -58,28 +58,17 @@ void simplemap_bitop(RTLIL::Module *module, RTLIL::Cell *cell) RTLIL::SigSpec sig_b = cell->getPort(ID::B); RTLIL::SigSpec sig_y = cell->getPort(ID::Y); - sig_a.extend_u0(GetSize(sig_y), cell->parameters.at(ID::A_SIGNED).as_bool()); - sig_b.extend_u0(GetSize(sig_y), cell->parameters.at(ID::B_SIGNED).as_bool()); - - if (cell->type == ID($xnor)) - { - RTLIL::SigSpec sig_t = module->addWire(NEW_ID, GetSize(sig_y)); - - for (int i = 0; i < GetSize(sig_y); i++) { - RTLIL::Cell *gate = module->addCell(NEW_ID, ID($_NOT_)); - gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); - gate->setPort(ID::A, sig_t[i]); - gate->setPort(ID::Y, sig_y[i]); - } - - sig_y = sig_t; + if (cell->type != ID($bweqx)) { + sig_a.extend_u0(GetSize(sig_y), cell->parameters.at(ID::A_SIGNED).as_bool()); + sig_b.extend_u0(GetSize(sig_y), cell->parameters.at(ID::B_SIGNED).as_bool()); } IdString gate_type; - if (cell->type == ID($and)) gate_type = ID($_AND_); - if (cell->type == ID($or)) gate_type = ID($_OR_); - if (cell->type == ID($xor)) gate_type = ID($_XOR_); - if (cell->type == ID($xnor)) gate_type = ID($_XOR_); + if (cell->type == ID($and)) gate_type = ID($_AND_); + if (cell->type == ID($or)) gate_type = ID($_OR_); + if (cell->type == ID($xor)) gate_type = ID($_XOR_); + if (cell->type == ID($xnor)) gate_type = ID($_XNOR_); + if (cell->type == ID($bweqx)) gate_type = ID($_XNOR_); log_assert(!gate_type.empty()); for (int i = 0; i < GetSize(sig_y); i++) { @@ -284,6 +273,23 @@ void simplemap_mux(RTLIL::Module *module, RTLIL::Cell *cell) } } +void simplemap_bwmux(RTLIL::Module *module, RTLIL::Cell *cell) +{ + RTLIL::SigSpec sig_a = cell->getPort(ID::A); + RTLIL::SigSpec sig_b = cell->getPort(ID::B); + RTLIL::SigSpec sig_s = cell->getPort(ID::S); + RTLIL::SigSpec sig_y = cell->getPort(ID::Y); + + for (int i = 0; i < GetSize(sig_y); i++) { + RTLIL::Cell *gate = module->addCell(NEW_ID, ID($_MUX_)); + gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); + gate->setPort(ID::A, sig_a[i]); + gate->setPort(ID::B, sig_b[i]); + gate->setPort(ID::S, sig_s[i]); + gate->setPort(ID::Y, sig_y[i]); + } +} + void simplemap_tribuf(RTLIL::Module *module, RTLIL::Cell *cell) { RTLIL::SigSpec sig_a = cell->getPort(ID::A); @@ -409,6 +415,7 @@ void simplemap_get_mappers(dict<IdString, void(*)(RTLIL::Module*, RTLIL::Cell*)> mappers[ID($or)] = simplemap_bitop; mappers[ID($xor)] = simplemap_bitop; mappers[ID($xnor)] = simplemap_bitop; + mappers[ID($bweqx)] = simplemap_bitop; mappers[ID($reduce_and)] = simplemap_reduce; mappers[ID($reduce_or)] = simplemap_reduce; mappers[ID($reduce_xor)] = simplemap_reduce; @@ -422,6 +429,7 @@ void simplemap_get_mappers(dict<IdString, void(*)(RTLIL::Module*, RTLIL::Cell*)> mappers[ID($ne)] = simplemap_eqne; mappers[ID($nex)] = simplemap_eqne; mappers[ID($mux)] = simplemap_mux; + mappers[ID($bwmux)] = simplemap_bwmux; mappers[ID($tribuf)] = simplemap_tribuf; mappers[ID($bmux)] = simplemap_bmux; mappers[ID($lut)] = simplemap_lut; @@ -476,7 +484,8 @@ struct SimplemapPass : public Pass { log(" $not, $pos, $and, $or, $xor, $xnor\n"); log(" $reduce_and, $reduce_or, $reduce_xor, $reduce_xnor, $reduce_bool\n"); log(" $logic_not, $logic_and, $logic_or, $mux, $tribuf\n"); - log(" $sr, $ff, $dff, $dffe, $dffsr, $dffsre, $adff, $adffe, $aldff, $aldffe, $sdff, $sdffe, $sdffce, $dlatch, $adlatch, $dlatchsr\n"); + log(" $sr, $ff, $dff, $dffe, $dffsr, $dffsre, $adff, $adffe, $aldff, $aldffe, $sdff,\n"); + log(" $sdffe, $sdffce, $dlatch, $adlatch, $dlatchsr\n"); log("\n"); } void execute(std::vector<std::string> args, RTLIL::Design *design) override diff --git a/passes/techmap/simplemap.h b/passes/techmap/simplemap.h index c7654f68c..30cc1ccfe 100644 --- a/passes/techmap/simplemap.h +++ b/passes/techmap/simplemap.h @@ -31,6 +31,7 @@ extern void simplemap_reduce(RTLIL::Module *module, RTLIL::Cell *cell); extern void simplemap_lognot(RTLIL::Module *module, RTLIL::Cell *cell); extern void simplemap_logbin(RTLIL::Module *module, RTLIL::Cell *cell); extern void simplemap_mux(RTLIL::Module *module, RTLIL::Cell *cell); +extern void simplemap_bwmux(RTLIL::Module *module, RTLIL::Cell *cell); extern void simplemap_lut(RTLIL::Module *module, RTLIL::Cell *cell); extern void simplemap_slice(RTLIL::Module *module, RTLIL::Cell *cell); extern void simplemap_concat(RTLIL::Module *module, RTLIL::Cell *cell); diff --git a/passes/techmap/techmap.cc b/passes/techmap/techmap.cc index 5cd78fe28..144f596c8 100644 --- a/passes/techmap/techmap.cc +++ b/passes/techmap/techmap.cc @@ -1026,8 +1026,8 @@ struct TechmapPass : public Pass { log("When a module in the map file has the 'techmap_celltype' attribute set, it will\n"); log("match cells with a type that match the text value of this attribute. Otherwise\n"); log("the module name will be used to match the cell. Multiple space-separated cell\n"); - log("types can be listed, and wildcards using [] will be expanded (ie. \"$_DFF_[PN]_\"\n"); - log("is the same as \"$_DFF_P_ $_DFF_N_\").\n"); + log("types can be listed, and wildcards using [] will be expanded (ie.\n"); + log("\"$_DFF_[PN]_\" is the same as \"$_DFF_P_ $_DFF_N_\").\n"); log("\n"); log("When a module in the map file has the 'techmap_simplemap' attribute set, techmap\n"); log("will use 'simplemap' (see 'help simplemap') to map cells matching the module.\n"); @@ -1083,11 +1083,11 @@ struct TechmapPass : public Pass { log(" It is possible to combine both prefixes to 'RECURSION; CONSTMAP; '.\n"); log("\n"); log(" _TECHMAP_REMOVEINIT_<port-name>_\n"); - log(" When this wire is set to a constant value, the init attribute of the wire(s)\n"); - log(" connected to this port will be consumed. This wire must have the same\n"); - log(" width as the given port, and for every bit that is set to 1 in the value,\n"); - log(" the corresponding init attribute bit will be changed to 1'bx. If all\n"); - log(" bits of an init attribute are left as x, it will be removed.\n"); + log(" When this wire is set to a constant value, the init attribute of the\n"); + log(" wire(s) connected to this port will be consumed. This wire must have\n"); + log(" the same width as the given port, and for every bit that is set to 1 in\n"); + log(" the value, the corresponding init attribute bit will be changed to 1'bx.\n"); + log(" If all bits of an init attribute are left as x, it will be removed.\n"); log("\n"); log("In addition to this special wires, techmap also supports special parameters in\n"); log("modules in the map file:\n"); @@ -1108,8 +1108,8 @@ struct TechmapPass : public Pass { log("\n"); log(" _TECHMAP_WIREINIT_<port-name>_\n"); log(" When a parameter with this name exists, it will be set to the initial\n"); - log(" value of the wire(s) connected to the given port, as specified by the init\n"); - log(" attribute. If the attribute doesn't exist, x will be filled for the\n"); + log(" value of the wire(s) connected to the given port, as specified by the\n"); + log(" init attribute. If the attribute doesn't exist, x will be filled for the\n"); log(" missing bits. To remove the init attribute bits used, use the\n"); log(" _TECHMAP_REMOVEINIT_*_ wires.\n"); log("\n"); |