aboutsummaryrefslogtreecommitdiffstats
path: root/passes
diff options
context:
space:
mode:
Diffstat (limited to 'passes')
-rw-r--r--passes/cmds/Makefile.inc3
-rw-r--r--passes/cmds/bugpoint.cc9
-rw-r--r--passes/cmds/chformal.cc4
-rw-r--r--passes/cmds/design.cc5
-rw-r--r--passes/cmds/exec.cc12
-rw-r--r--passes/cmds/glift.cc76
-rw-r--r--passes/cmds/logcmd.cc5
-rw-r--r--passes/cmds/logger.cc8
-rw-r--r--passes/cmds/rename.cc137
-rw-r--r--passes/cmds/scratchpad.cc3
-rw-r--r--passes/cmds/select.cc4
-rw-r--r--passes/cmds/setundef.cc34
-rw-r--r--passes/cmds/show.cc141
-rw-r--r--passes/cmds/splitcells.cc267
-rw-r--r--passes/cmds/stat.cc284
-rw-r--r--passes/cmds/tee.cc13
-rw-r--r--passes/cmds/viz.cc1081
-rw-r--r--passes/cmds/xprop.cc1234
-rw-r--r--passes/equiv/equiv_make.cc93
-rw-r--r--passes/equiv/equiv_opt.cc14
-rw-r--r--passes/fsm/fsm.cc9
-rw-r--r--passes/fsm/fsm_detect.cc35
-rw-r--r--passes/hierarchy/hierarchy.cc26
-rw-r--r--passes/hierarchy/submod.cc5
-rw-r--r--passes/hierarchy/uniquify.cc1
-rw-r--r--passes/memory/memory_bram.cc3
-rw-r--r--passes/memory/memory_dff.cc4
-rw-r--r--passes/memory/memory_map.cc98
-rw-r--r--passes/memory/memory_share.cc4
-rw-r--r--passes/opt/opt_clean.cc2
-rw-r--r--passes/opt/opt_dff.cc33
-rw-r--r--passes/opt/opt_expr.cc185
-rw-r--r--passes/opt/opt_ffinv.cc3
-rw-r--r--passes/opt/opt_reduce.cc6
-rw-r--r--passes/opt/wreduce.cc8
-rw-r--r--passes/pmgen/xilinx_srl.cc6
-rw-r--r--passes/proc/proc_dff.cc2
-rw-r--r--passes/sat/Makefile.inc1
-rw-r--r--passes/sat/async2sync.cc3
-rw-r--r--passes/sat/clk2fflogic.cc193
-rw-r--r--passes/sat/formalff.cc766
-rw-r--r--passes/sat/miter.cc58
-rw-r--r--passes/sat/mutate.cc2
-rw-r--r--passes/sat/qbfsat.cc63
-rw-r--r--passes/sat/qbfsat.h50
-rw-r--r--passes/sat/sat.cc29
-rw-r--r--passes/sat/sim.cc623
-rw-r--r--passes/techmap/Makefile.inc1
-rw-r--r--passes/techmap/abc.cc44
-rw-r--r--passes/techmap/abc9.cc13
-rw-r--r--passes/techmap/abc9_exe.cc16
-rw-r--r--passes/techmap/abc9_ops.cc40
-rw-r--r--passes/techmap/bmuxmap.cc39
-rw-r--r--passes/techmap/bwmuxmap.cc70
-rw-r--r--passes/techmap/dfflegalize.cc38
-rw-r--r--passes/techmap/dfflibmap.cc2
-rw-r--r--passes/techmap/dffunmap.cc4
-rw-r--r--passes/techmap/flowmap.cc3
-rw-r--r--passes/techmap/insbuf.cc40
-rw-r--r--passes/techmap/simplemap.cc49
-rw-r--r--passes/techmap/simplemap.h1
-rw-r--r--passes/techmap/techmap.cc18
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 -&gt; %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 -&gt; %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");