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/exec.cc8
-rw-r--r--passes/cmds/logger.cc8
-rw-r--r--passes/cmds/splitcells.cc267
-rw-r--r--passes/cmds/stat.cc3
-rw-r--r--passes/cmds/viz.cc1081
-rw-r--r--passes/cmds/xprop.cc1233
-rw-r--r--passes/hierarchy/uniquify.cc1
-rw-r--r--passes/opt/opt_expr.cc183
-rw-r--r--passes/sat/formalff.cc2
-rw-r--r--passes/sat/miter.cc24
-rw-r--r--passes/sat/qbfsat.cc10
-rw-r--r--passes/sat/qbfsat.h46
-rw-r--r--passes/sat/sim.cc36
-rw-r--r--passes/techmap/Makefile.inc1
-rw-r--r--passes/techmap/bwmuxmap.cc70
-rw-r--r--passes/techmap/insbuf.cc40
-rw-r--r--passes/techmap/simplemap.cc46
-rw-r--r--passes/techmap/simplemap.h1
19 files changed, 2985 insertions, 78 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/exec.cc b/passes/cmds/exec.cc
index c15ef23bf..e5fa4fb41 100644
--- a/passes/cmds/exec.cc
+++ b/passes/cmds/exec.cc
@@ -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/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/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 10e98952e..522957f39 100644
--- a/passes/cmds/stat.cc
+++ b/passes/cmds/stat.cc
@@ -275,6 +275,9 @@ struct statdata_t
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)
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..5dee72e1b
--- /dev/null
+++ b/passes/cmds/xprop.cc
@@ -0,0 +1,1233 @@
+/*
+ * 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()) {
+ module->addBwmux(NEW_ID, driven_enc.is_1, Const(State::Sx, GetSize(driven_orig)), driven_enc.is_x, driven_orig);
+ }
+ 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/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/opt/opt_expr.cc b/passes/opt/opt_expr.cc
index 9cc0170dc..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,6 +1659,13 @@ 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));
diff --git a/passes/sat/formalff.cc b/passes/sat/formalff.cc
index 962e350a1..e36379096 100644
--- a/passes/sat/formalff.cc
+++ b/passes/sat/formalff.cc
@@ -532,12 +532,14 @@ struct FormalFfPass : public Pass {
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();
}
}
diff --git a/passes/sat/miter.cc b/passes/sat/miter.cc
index bf33c9ce3..8f27c4c6f 100644
--- a/passes/sat/miter.cc
+++ b/passes/sat/miter.cc
@@ -30,6 +30,7 @@ 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;
@@ -54,6 +55,10 @@ 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;
@@ -139,8 +144,16 @@ void create_miter_equiv(struct Pass *that, std::vector<std::string> args, RTLIL:
{
if (gold_cross_ports.count(gold_wire))
{
- RTLIL::Wire *w = miter_module->addWire("\\cross_" + RTLIL::unescape_id(gold_wire->name), gold_wire->width);
+ 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;
}
@@ -237,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);
}
}
@@ -402,6 +421,9 @@ 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");
diff --git a/passes/sat/qbfsat.cc b/passes/sat/qbfsat.cc
index 42d3e188a..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());
}
diff --git a/passes/sat/qbfsat.h b/passes/sat/qbfsat.h
index 8fb6093bc..253cecce4 100644
--- a/passes/sat/qbfsat.h
+++ b/passes/sat/qbfsat.h
@@ -154,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/sim.cc b/passes/sat/sim.cc
index 1cd1ebc71..0084a1f28 100644
--- a/passes/sat/sim.cc
+++ b/passes/sat/sim.cc
@@ -509,7 +509,7 @@ struct SimInstance
}
}
- bool update_ph2()
+ bool update_ph2(bool gclk)
{
bool did_something = false;
@@ -567,7 +567,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;
@@ -616,7 +617,7 @@ struct SimInstance
}
for (auto it : children)
- if (it.second->update_ph2()) {
+ if (it.second->update_ph2(gclk)) {
dirty_children.insert(it.second);
did_something = true;
}
@@ -684,10 +685,11 @@ struct SimInstance
void writeback(pool<Module*> &wbmods)
{
- 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);
+ 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);
@@ -985,7 +987,7 @@ struct SimWorker : SimShared
writer->write(use_signal);
}
- void update()
+ void update(bool gclk)
{
while (1)
{
@@ -997,7 +999,7 @@ struct SimWorker : SimShared
if (debug)
log("\n-- ph2 --\n");
- if (!top->update_ph2())
+ if (!top->update_ph2(gclk))
break;
}
@@ -1047,7 +1049,7 @@ struct SimWorker : SimShared
set_inports(clock, State::Sx);
set_inports(clockn, State::Sx);
- update();
+ update(false);
register_output_step(0);
@@ -1060,7 +1062,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)
@@ -1076,7 +1078,7 @@ struct SimWorker : SimShared
set_inports(resetn, State::S1);
}
- update();
+ update(true);
register_output_step(10*cycle + 10);
}
@@ -1193,7 +1195,7 @@ struct SimWorker : SimShared
initial = false;
}
if (did_something)
- update();
+ update(true);
register_output_step(time);
bool status = top->checkSignals();
@@ -1342,12 +1344,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++;
@@ -1419,12 +1421,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++;
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/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/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 f75b82919..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;
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);