diff --git a/docs/source/code_examples/macro_commands/opt.ys b/docs/source/code_examples/macro_commands/opt.ys index cb883bc58..ebd938836 100644 --- a/docs/source/code_examples/macro_commands/opt.ys +++ b/docs/source/code_examples/macro_commands/opt.ys @@ -9,6 +9,7 @@ do opt_merge opt_share (-full only) opt_dff (except when called with -noff) + opt_hier (-hier only) opt_clean opt_expr while diff --git a/docs/source/using_yosys/synthesis/opt.rst b/docs/source/using_yosys/synthesis/opt.rst index 743b24997..43b558739 100644 --- a/docs/source/using_yosys/synthesis/opt.rst +++ b/docs/source/using_yosys/synthesis/opt.rst @@ -192,6 +192,13 @@ control inputs. Called with ``-nodffe`` and ``-nosdff``, this pass is used to prepare a design for :doc:`/using_yosys/synthesis/fsm`. +Hierarchical optimization - `opt_hier` pass +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This pass considers the design hierarchy and propagates unused signals, constant +signals, and tied-together signals across module boundaries to facilitate +optimization by other passes. + Removing unused cells and wires - `opt_clean` pass ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/passes/opt/Makefile.inc b/passes/opt/Makefile.inc index 08d8191c7..426d9a79a 100644 --- a/passes/opt/Makefile.inc +++ b/passes/opt/Makefile.inc @@ -11,6 +11,7 @@ OBJS += passes/opt/opt_dff.o OBJS += passes/opt/opt_share.o OBJS += passes/opt/opt_clean.o OBJS += passes/opt/opt_expr.o +OBJS += passes/opt/opt_hier.o ifneq ($(SMALL),1) OBJS += passes/opt/share.o diff --git a/passes/opt/opt.cc b/passes/opt/opt.cc index dc88563c2..146c21cce 100644 --- a/passes/opt/opt.cc +++ b/passes/opt/opt.cc @@ -46,6 +46,7 @@ struct OptPass : public Pass { log(" opt_merge [-share_all]\n"); log(" opt_share (-full only)\n"); log(" opt_dff [-nodffe] [-nosdff] [-keepdc] [-sat] (except when called with -noff)\n"); + log(" opt_hier (-hier only)\n"); log(" opt_clean [-purge]\n"); log(" opt_expr [-mux_undef] [-mux_bool] [-undriven] [-noclkinv] [-fine] [-full] [-keepdc]\n"); log(" while \n"); @@ -56,6 +57,7 @@ struct OptPass : public Pass { log(" opt_expr [-mux_undef] [-mux_bool] [-undriven] [-noclkinv] [-fine] [-full] [-keepdc]\n"); log(" opt_merge [-share_all]\n"); log(" opt_dff [-nodffe] [-nosdff] [-keepdc] [-sat] (except when called with -noff)\n"); + log(" opt_hier (-hier only)\n"); log(" opt_clean [-purge]\n"); log(" while \n"); log("\n"); @@ -74,6 +76,7 @@ struct OptPass : public Pass { bool opt_share = false; bool fast_mode = false; bool noff_mode = false; + bool hier_mode = false; log_header(design, "Executing OPT pass (performing simple optimizations).\n"); log_push(); @@ -141,6 +144,10 @@ struct OptPass : public Pass { noff_mode = true; continue; } + if (args[argidx] == "-hier") { + hier_mode = true; + continue; + } break; } extra_args(args, argidx, design); @@ -155,6 +162,8 @@ struct OptPass : public Pass { Pass::call(design, "opt_dff" + opt_dff_args); if (design->scratchpad_get_bool("opt.did_something") == false) break; + if (hier_mode) + Pass::call(design, "opt_hier"); Pass::call(design, "opt_clean" + opt_clean_args); log_header(design, "Rerunning OPT passes. (Removed registers in this run.)\n"); } @@ -173,6 +182,8 @@ struct OptPass : public Pass { Pass::call(design, "opt_share"); if (!noff_mode) Pass::call(design, "opt_dff" + opt_dff_args); + if (hier_mode) + Pass::call(design, "opt_hier"); Pass::call(design, "opt_clean" + opt_clean_args); Pass::call(design, "opt_expr" + opt_expr_args); if (design->scratchpad_get_bool("opt.did_something") == false) diff --git a/passes/opt/opt_hier.cc b/passes/opt/opt_hier.cc new file mode 100644 index 000000000..a8df78dc1 --- /dev/null +++ b/passes/opt/opt_hier.cc @@ -0,0 +1,470 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) Martin PoviĊĦer + * + * 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/register.h" +#include "kernel/rtlil.h" +#include "kernel/sigtools.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +// Used to propagate information out of a module +struct ModuleIndex { + Module *module; + SigMap sigmap; + SigPool used; + dict constant_outputs; + std::vector tie_together_outputs; + + ModuleIndex(Module *module) + : module(module), sigmap(module) + { + if (module->get_blackbox_attribute()) { + for (auto wire : module->wires()) { + for (auto bit : SigSpec(wire)) + used.add(sigmap(bit)); + } + return; + } + + auto count_usage = [&](const SigSpec &signal) { + for (auto bit : signal) + used.add(sigmap(bit)); + }; + for (auto wire : module->wires()) { + if (wire->port_output) { + SigSpec wire1 = wire; + count_usage(wire1); + } + } + for (auto [_, process] : module->processes) + process->rewrite_sigspecs(count_usage); + for (auto cell : module->cells()) { + bool known = cell->known(); + for (auto &conn : cell->connections()) { + if (!known || cell->input(conn.first)) + count_usage(conn.second); + } + } + + + dict classes; + for (auto &pair : module->connections_) { + for (int i = 0; i < pair.first.size(); i++) { + if (pair.first[i].wire + && pair.first[i].wire->port_output + && !pair.first[i].wire->port_input) { + if (!pair.second[i].wire) { + constant_outputs[pair.first[i]] = pair.second[i]; + } else { + classes[pair.second[i]].append(pair.first[i]); + } + } + } + } + + for (auto [key, new_class] : classes) { + if (new_class.size() > 1) { + new_class.sort_and_unify(); + tie_together_outputs.push_back(new_class); + } + } + } + + bool apply_changes(ModuleIndex &parent, Cell *instantiation) { + log_assert(instantiation->module == parent.module); + + if (module->get_blackbox_attribute()) { + // no propagating out of blackboxes + return false; + } + + bool changed = false; + for (auto &[port_name, value] : instantiation->connections_) { + Wire *port = module->wire(port_name); + if (!port || (!port->port_input && !port->port_output) || port->width != value.size()) { + log_error("Port %s connected on instance %s not found in module %s" + " or width is not matching\n", + log_id(port_name), log_id(instantiation), log_id(module)); + } + + if (port->port_input && port->port_output) { + // ignore bidirectional: hard to come up with sound handling + continue; + } + + int nunused = 0, nconstants = 0; + // disconnect unused inputs + if (port->port_input) { + for (int i = 0; i < port->width; i++) { + if (value[i].is_wire() && !used.check(sigmap(SigBit(port, i)))) { + value[i] = RTLIL::Sx; + nunused++; + } + } + } + + // propagate constants + if (port->port_output) { + SigSpec port_new_const; + + for (int i = 0; i < port->width; i++) { + SigBit port_bit(port, i); + if (value[i].is_wire() && constant_outputs.count(port_bit) && parent.used.check(value[i])) { + port_new_const.append(port_bit); + nconstants++; + } + } + + for (auto chunk : port_new_const.chunks()) { + RTLIL::SigSpec rhs = chunk; + rhs.replace(constant_outputs); + log_assert(rhs.is_fully_const()); + parent.module->connect(value.extract(chunk.offset, chunk.width), rhs); + SigSpec dummy = parent.module->addWire(NEW_ID_SUFFIX("const_output"), chunk.width); + for (int i = 0; i < chunk.width; i++) + value[chunk.offset + i] = dummy[i]; + } + } + + if (nunused > 0) { + log("Disconnected %d input bits of instance '%s' of '%s' in '%s'\n", + nunused, log_id(instantiation), log_id(instantiation->type), log_id(parent.module)); + changed = true; + } + if (nconstants > 0) { + log("Substituting constant for %d output bits of instance '%s' of '%s' in '%s'\n", + nconstants, log_id(instantiation), log_id(instantiation->type), log_id(parent.module)); + changed = true; + } + } + + // propagate tie-togethers + int ntie_togethers = 0; + SigSpec severed_port_bits; + for (auto class_ : tie_together_outputs) { + // filtered class represented by bits on the two sides of boundary + SigSpec new_tie; + + for (auto port_bit : class_) { + if (instantiation->connections_.count(port_bit.wire->name)) { + SigBit bit = instantiation->connections_.at(port_bit.wire->name)[port_bit.offset]; + if (parent.used.check(bit)) { + if (!new_tie.empty()) { + severed_port_bits.append(port_bit); + ntie_togethers++; + } + new_tie.append(bit); + } + } + } + + if (new_tie.size() > 1) + parent.module->connect(new_tie.extract_end(1), SigSpec(new_tie[0]).repeat(new_tie.size() - 1)); + } + + severed_port_bits.sort_and_unify(); + for (auto chunk : severed_port_bits.chunks()) { + SigSpec &value = instantiation->connections_.at(chunk.wire->name); + SigSpec dummy = parent.module->addWire(NEW_ID_SUFFIX("tie_together"), chunk.width); + for (int i = 0; i < chunk.width; i++) + value[chunk.offset + i] = dummy[i]; + } + + if (ntie_togethers > 0) { + log("Replacing %d output bits with tie-togethers on instance '%s' of '%s' in '%s'\n", + ntie_togethers, log_id(instantiation), log_id(instantiation->type), log_id(parent.module)); + changed = true; + } + + return changed; + } +}; + +// Used to propagate information into a module +struct UsageData { + Module *module; + SigPool used_outputs; + // Values are constant nets. We're not using `dict` + // since we want to use this with `SigSpec::replace()` + dict constant_inputs; + std::vector tie_together_inputs; + + SigSpec all_inputs; + SigSpec all_outputs; + + UsageData(Module *module) + : module(module) + { + SigSpec all_inputs; + + for (auto port_name : module->ports) { + Wire *port = module->wire(port_name); + log_assert(port); + + if (port->port_input && port->port_output) { + // ignore bidirectional: hard to come up with sound handling + continue; + } + + if (port->port_input) { + for (int i = 0; i < port->width; i++) { + constant_inputs[SigBit(port, i)] = RTLIL::Sx; + } + all_inputs.append(port); + } else { + all_outputs.append(port); + } + } + + tie_together_inputs.push_back(all_inputs); + } + + void refine_used_outputs(Wire *port, SigSpec connection, ModuleIndex &index) { + for (int i = 0; i < port->width; i++) { + if (connection[i].is_wire() && index.used.check(index.sigmap(connection[i]))) { + used_outputs.add(SigBit(port, i)); + } + } + } + + void refine_input_constants(Wire *port, SigSpec connection) { + for (int i = 0; i < port->width; i++) { + SigBit port_bit(port, i); + // is connnected constant incompatible with candidate constant? + if (connection[i] != RTLIL::Sx + && constant_inputs.count(port_bit) + && constant_inputs.at(port_bit) != connection[i]) { + // we can go Sx -> S1/S0, otherwise erase the candidate constant + if (constant_inputs.at(port_bit) == RTLIL::Sx && !connection[i].is_wire()) { + constant_inputs[port_bit] = connection[i]; + } else { + constant_inputs.erase(port_bit); + } + } + } + } + + void refine_tie_togethers(const dict &inputs) { + std::vector new_tie_togethers; + + for (auto &class_ : tie_together_inputs) { + dict new_classes; + + for (auto bit : class_) { + SigBit connected_bit = inputs.count(bit) ? inputs.at(bit) : RTLIL::Sx; + new_classes[connected_bit].append(bit); + } + + for (auto [key, new_class] : new_classes) { + if (new_class.size() > 1) + new_tie_togethers.push_back(new_class); + } + } + + new_tie_togethers.swap(tie_together_inputs); + } + + // inspect the given instantiation and refine usage data accordingly + void refine(Cell *instance, ModuleIndex &index) { + dict inputs; + + for (auto &[port_name, value] : instance->connections_) { + Wire *port = module->wire(port_name); + if (!port || (!port->port_input && !port->port_output) || port->width != value.size()) { + log_error("Port %s connected on instance %s not found in module %s" + " or width is not matching\n", + log_id(port_name), log_id(instance), log_id(module)); + } + + if (port->port_input && port->port_output) { + // ignore bidirectional: hard to come up with sound handling + continue; + } + + if (port->port_output) { + refine_used_outputs(port, value, index); + } else { + refine_input_constants(port, value); + for (int i = 0; i < port->width; i++) + inputs[SigBit(port, i)] = value[i]; + } + } + + refine_tie_togethers(inputs); + } + + bool apply_changes() { + bool did_something = false; + + if (module->get_blackbox_attribute()) { + // no propagating into blackboxes + return false; + } + + // Disconnect unused outputs + for (auto &pair : module->connections_) { + for (int i = 0; i < pair.first.size(); i++) { + // If an output is constant there's no benefit to disconnecting + // so consider it "used" + if (pair.first[i].wire + && pair.first[i].wire->port_output + && !pair.second[i].wire) + used_outputs.add(pair.first[i]); + } + } + + SigSpec disconnect_outputs; + for (auto bit : all_outputs) { + if (!used_outputs.check(bit)) + disconnect_outputs.append(bit); + } + + dict replacement_map; + for (auto chunk : disconnect_outputs.chunks()) { + Wire *repl_wire = module->addWire(module->uniquify(std::string("$") + chunk.wire->name.str()), chunk.size()); + for (int i = 0; i < repl_wire->width; i++) + replacement_map[SigSpec(chunk)[i]] = SigBit(repl_wire, i); + } + auto disconnect_rewrite = [&](SigSpec &signal) { + signal.replace(replacement_map); + }; + module->rewrite_sigspecs(disconnect_rewrite); + for (auto chunk : disconnect_outputs.chunks()) { + log("Disconnected unused output terminal '%s' in module '%s'\n", log_signal(chunk), log_id(module)); + did_something = true; + module->connect(chunk, SigSpec(RTLIL::Sx, chunk.size())); + } + + // Connect constant inputs + SigPool applied_constants; + auto constant_rewrite = [&](SigSpec &signal) { + for (auto bit : signal) { + if (constant_inputs.count(bit)) + applied_constants.add(bit); + } + signal.replace(constant_inputs); + }; + module->rewrite_sigspecs(constant_rewrite); + SigSpec applied_constants2 = applied_constants.export_all(); + applied_constants2.sort_and_unify(); + for (auto chunk : applied_constants2.chunks()) { + SigSpec const_ = chunk; + const_.replace(constant_inputs); + log("Substituting constant %s for input terminal '%s' in module '%s'\n", + log_signal(const_), log_signal(chunk), log_id(module)); + } + + // Propagate tied-together inputs + dict ties; + for (auto group : tie_together_inputs) { + for (int i = 1; i < group.size(); i++) + ties[group[i]] = group[0]; + } + SigPool applied_ties; + auto ties_rewrite = [&](SigSpec &signal) { + for (auto bit : signal) { + if (ties.count(bit)) { + applied_ties.add(bit); + } + } + signal.replace(ties); + }; + module->rewrite_sigspecs(ties_rewrite); + if (applied_ties.size()) { + log("Replacing %zu input terminal bits with tie-togethers in module '%s'\n", + applied_ties.size(), log_id(module)); + } + return did_something; + } +}; + +struct OptHierPass : Pass { + OptHierPass() : Pass("opt_hier", "perform cross-boundary optimization") {} + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" opt_hier [selection]\n"); + log("\n"); + log("This pass considers the design hierarchy and propagates unused signals, constant\n"); + log("signals, and tied-together signals across module boundaries to facilitate\n"); + log("optimization. Only the selected modules are affected.\n"); + log("\n"); + log("Note this pass changes port semantics on modules which are not the top.\n"); + log("\n"); + } + void execute(std::vector args, RTLIL::Design *d) override + { + log_header(d, "Executing OPT_HIER pass.\n"); + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) { + break; + } + extra_args(args, argidx, d); + + if (!d->top_module()) + log_cmd_error("Top module needs to be selected for opt_hier\n"); + + dict indices; + for (auto module : d->modules()) { + log_debug("Building index for %s\n", log_id(module)); + indices.emplace(module->name, ModuleIndex(module)); + } + + dict usage_datas; + for (auto module : d->selected_modules(RTLIL::SELECT_WHOLE_ONLY, RTLIL::SB_UNBOXED_CMDERR)) { + if (module->get_bool_attribute(ID::top)) + continue; + + log_debug("Starting usage data for %s\n", log_id(module)); + usage_datas.emplace(module->name, UsageData(module)); + } + + for (auto module : d->modules()) { + for (auto cell : module->cells()) { + if (usage_datas.count(cell->type)) { + log_debug("Account for instance %s of %s in %s\n", log_id(cell), log_id(cell->type), log_id(module)); + usage_datas.at(cell->type).refine(cell, indices.at(module->name)); + } + } + } + + bool did_something = false; + for (auto module : d->selected_modules(RTLIL::SELECT_WHOLE_ONLY, RTLIL::SB_UNBOXED_CMDERR)) { + if (usage_datas.count(module->name)) { + log_debug("Applying usage data changes to %s\n", log_id(module)); + did_something |= usage_datas.at(module->name).apply_changes(); + } + + ModuleIndex &parent_index = indices.at(module->name); + for (auto cell : module->cells()) { + if (indices.count(cell->type)) { + log_debug("Applying changes to instance %s of %s in %s\n", log_id(cell), log_id(cell->type), log_id(module)); + did_something |= indices.at(cell->type).apply_changes(parent_index, cell); + } + } + } + if (did_something) + d->scratchpad_set_bool("opt.did_something", true); + } +} OptHierPass; + +PRIVATE_NAMESPACE_END diff --git a/techlibs/common/synth.cc b/techlibs/common/synth.cc index 74a484d59..8b4561c34 100644 --- a/techlibs/common/synth.cc +++ b/techlibs/common/synth.cc @@ -47,6 +47,11 @@ struct SynthPass : public ScriptPass { log(" flatten the design before synthesis. this will pass '-auto-top' to\n"); log(" 'hierarchy' if no top module is specified.\n"); log("\n"); + log(" -hieropt\n"); + log(" enable hierarchical optimization. this option is useful when `-flatten'\n"); + log(" is not used, or when selected modules are marked with 'keep_hierarchy'\n."); + log(" to prevent their dissolution.\n"); + log("\n"); log(" -encfile \n"); log(" passed to 'fsm_recode' via 'fsm'\n"); log("\n"); @@ -99,7 +104,7 @@ struct SynthPass : public ScriptPass { } string top_module, fsm_opts, memory_opts, abc; - bool autotop, flatten, noalumacc, nofsm, noabc, noshare, flowmap, booth; + bool autotop, flatten, noalumacc, nofsm, noabc, noshare, flowmap, booth, hieropt; int lut; std::vector techmap_maps; @@ -118,6 +123,7 @@ struct SynthPass : public ScriptPass { noshare = false; flowmap = false; booth = false; + hieropt = false; abc = "abc"; techmap_maps.clear(); } @@ -201,6 +207,10 @@ struct SynthPass : public ScriptPass { techmap_maps.push_back(args[++argidx]); continue; } + if (args[argidx] == "-hieropt") { + hieropt = true; + continue; + } break; } extra_args(args, argidx, design); @@ -223,6 +233,12 @@ struct SynthPass : public ScriptPass { void script() override { + std::string hieropt_flag; + if (help_mode) + hieropt_flag = " [-hier]"; + else + hieropt_flag = hieropt ? " -hier" : ""; + if (check_label("begin")) { if (help_mode) { run("hierarchy -check [-top | -auto-top]"); @@ -247,7 +263,7 @@ struct SynthPass : public ScriptPass { run("opt -nodffe -nosdff"); if (!nofsm || help_mode) run("fsm" + fsm_opts, " (unless -nofsm)"); - run("opt"); + run("opt" + hieropt_flag); run("wreduce"); run("peepopt"); run("opt_clean"); @@ -261,13 +277,13 @@ struct SynthPass : public ScriptPass { run("alumacc", " (unless -noalumacc)"); if (!noshare) run("share", " (unless -noshare)"); - run("opt"); + run("opt" + hieropt_flag); run("memory -nomap" + memory_opts); run("opt_clean"); } if (check_label("fine")) { - run("opt -fast -full"); + run("opt -fast -full" + hieropt_flag); run("memory_map"); run("opt -full"); if (help_mode) { @@ -291,7 +307,7 @@ struct SynthPass : public ScriptPass { } else if (flowmap) { run(stringf("flowmap -maxlut %d", lut)); } - run("opt -fast"); + run("opt -fast" + hieropt_flag); if ((!noabc && !flowmap) || help_mode) { #ifdef YOSYS_ENABLE_ABC diff --git a/tests/opt/opt_hier.tcl b/tests/opt/opt_hier.tcl new file mode 100644 index 000000000..65d8f9809 --- /dev/null +++ b/tests/opt/opt_hier.tcl @@ -0,0 +1,34 @@ +yosys -import + +# per each opt_hier_*.v source file, confirm flattening and hieropt+flattening +# are combinationally equivalent +foreach fn [glob opt_hier_*.v] { + log -header "Test $fn" + log -push + design -reset + + read_verilog $fn + hierarchy -auto-top + prep -top top + design -save start + flatten + design -save gold + design -load start + opt -hier + # check any instances marked `should_get_optimized_out` were + # indeed optimized out + select -assert-none a:should_get_optimized_out + dump + flatten + design -save gate + + design -reset + design -copy-from gold -as gold A:top + design -copy-from gate -as gate A:top + yosys rename -hide + equiv_make gold gate equiv + equiv_induct equiv + equiv_status -assert equiv + + log -pop +} diff --git a/tests/opt/opt_hier_simple1.v b/tests/opt/opt_hier_simple1.v new file mode 100644 index 000000000..17935a960 --- /dev/null +++ b/tests/opt/opt_hier_simple1.v @@ -0,0 +1,8 @@ +module m(input a, output y1, output y2); + assign y1 = a; + assign y2 = a; +endmodule + +module top(input a, output y2, output y1); + m inst(.a(a), .y1(y1), .y2(y2)); +endmodule diff --git a/tests/opt/opt_hier_simple2.v b/tests/opt/opt_hier_simple2.v new file mode 100644 index 000000000..9e724216b --- /dev/null +++ b/tests/opt/opt_hier_simple2.v @@ -0,0 +1,7 @@ +module m(input [3:0] i, output [3:0] y); + assign y = i + 1; +endmodule + +module top(output [3:0] y); + m inst(.i(4), .y(y)); +endmodule diff --git a/tests/opt/opt_hier_test1.v b/tests/opt/opt_hier_test1.v new file mode 100644 index 000000000..69de6b92f --- /dev/null +++ b/tests/opt/opt_hier_test1.v @@ -0,0 +1,22 @@ +(* blackbox *) +module bb(output y); +endmodule + +// all instances of `m` tie together a[1], a[2] +// this can be used to conclude y[0]=0 +module m(input [3:0] a, output [1:0] y, output x); + assign y[0] = a[1] != a[2]; + assign x = a[0] ^ a[3]; + (* should_get_optimized_out *) + bb bb1(.y(y[1])); +endmodule + +module top(input j, output z, output [2:0] x); + wire [1:0] y1; + wire [1:0] y2; + wire [1:0] y3; + m inst1(.a(0), .y(y1), .x(x[0])); + m inst2(.a(15), .y(y2), .x(x[1])); + m inst3(.a({1'b1, j, j, 1'b0}), .y(y3), .x(x[2])); + assign z = (&y1) ^ (&y2) ^ (&y3); +endmodule diff --git a/tests/opt/run-test.sh b/tests/opt/run-test.sh index 006c731e3..1d1d9b7a6 100755 --- a/tests/opt/run-test.sh +++ b/tests/opt/run-test.sh @@ -1,4 +1,4 @@ #!/usr/bin/env bash set -eu source ../gen-tests-makefile.sh -generate_mk --yosys-scripts +generate_mk --yosys-scripts --tcl-scripts