Merge pull request #104 from Silimate/mux_push_implementation

mux_push implementation
This commit is contained in:
Akash Levy 2026-02-05 17:55:51 -08:00 committed by GitHub
commit dc1847f89a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 460 additions and 0 deletions

View File

@ -6,6 +6,7 @@ OBJS += passes/silimate/breaksop.o
OBJS += passes/silimate/bus_rebuild.o
OBJS += passes/silimate/fanoutbuf.o
OBJS += passes/silimate/l2j_frontend.o
OBJS += passes/silimate/mux_push.o
OBJS += passes/silimate/obs_clean.o
OBJS += passes/silimate/segv.o
OBJS += passes/silimate/reg_rename.o

343
passes/silimate/mux_push.cc Normal file
View File

@ -0,0 +1,343 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
* Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
* Abhinav Tondapu <abhinav@silimate.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/register.h"
#include "kernel/sigtools.h"
#include "kernel/log.h"
#include "kernel/io.h"
#include <stdlib.h>
#include <stdio.h>
#include <set>
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
struct OptMuxPushWorker
{
RTLIL::Design *design;
RTLIL::Module *module;
SigMap sigmap;
dict<SigBit, RTLIL::Cell*> driver_map;
dict<SigBit, int> fanout_map;
pool<IdString> target_types;
int fanout_limit;
int total_count;
OptMuxPushWorker(RTLIL::Design *design, RTLIL::Module *module,
const pool<IdString> &target_types, int fanout_limit) :
design(design), module(module), sigmap(module),
target_types(target_types), fanout_limit(fanout_limit), total_count(0)
{
}
void build_connectivity()
{
driver_map.clear();
fanout_map.clear();
// Build per-bit driver and fanout maps for the current module
for (auto cell : module->cells())
{
for (auto &it : cell->connections()) {
RTLIL::SigSpec sig = sigmap(it.second);
if (cell->output(it.first)) {
for (auto &bit : sig) {
if (bit.wire == nullptr)
continue;
auto it_drv = driver_map.find(bit);
if (it_drv == driver_map.end()) {
driver_map[bit] = cell;
} else if (it_drv->second != cell) {
driver_map[bit] = nullptr;
}
}
}
if (cell->input(it.first)) {
for (auto &bit : sig) {
if (bit.wire == nullptr)
continue;
fanout_map[bit]++;
}
}
}
}
// Treat module output ports as consumers
for (auto wire : module->wires()) {
if (!wire->port_output)
continue;
RTLIL::SigSpec sig = sigmap(RTLIL::SigSpec(wire));
for (auto &bit : sig) {
if (bit.wire == nullptr)
continue;
fanout_map[bit]++;
}
}
}
bool sig_has_keep(const RTLIL::SigSpec &sig)
{
for (auto &bit : sig) {
if (bit.wire != nullptr && bit.wire->get_bool_attribute(ID::keep))
return true;
}
return false;
}
bool mux_drives_sig(const RTLIL::SigSpec &sig, RTLIL::Cell *&mux_cell)
{
mux_cell = nullptr;
for (auto &bit : sig) {
if (bit.wire == nullptr)
return false;
// Require a single consistent driver for all bits in the SigSpec
auto it_drv = driver_map.find(bit);
if (it_drv == driver_map.end() || it_drv->second == nullptr)
return false;
if (mux_cell == nullptr)
mux_cell = it_drv->second;
else if (mux_cell != it_drv->second)
return false;
}
return mux_cell != nullptr && mux_cell->type == ID($mux);
}
bool fanout_within_limit(const RTLIL::SigSpec &sig)
{
for (auto &bit : sig) {
if (bit.wire == nullptr)
return false;
// Enforce fanout cap per bit to keep the mux exclusive to this operator
if (fanout_map[bit] > fanout_limit)
return false;
}
return true;
}
bool fanout_is_one(const RTLIL::SigSpec &sig)
{
for (auto &bit : sig) {
if (bit.wire == nullptr)
return false;
if (fanout_map[bit] != 1)
return false;
}
return true;
}
void run()
{
while (true)
{
build_connectivity();
struct candidate_t {
RTLIL::Cell *cell = nullptr;
RTLIL::Cell *mux_cell = nullptr;
IdString port;
};
std::vector<candidate_t> candidates;
for (auto cell : module->selected_cells())
{
if (!target_types.count(cell->type))
continue;
if (cell->get_bool_attribute(ID::keep))
continue;
RTLIL::SigSpec cell_out = sigmap(cell->getPort(ID::Y));
if (sig_has_keep(cell_out))
continue;
// Look for one mux driven input to push through per operator
for (auto &it : cell->connections())
{
if (!cell->input(it.first))
continue;
RTLIL::SigSpec in_sig = sigmap(it.second);
RTLIL::Cell *mux_cell = nullptr;
if (!mux_drives_sig(in_sig, mux_cell))
continue;
if (!design->selected(module, mux_cell))
continue;
if (mux_cell->get_bool_attribute(ID::keep))
continue;
RTLIL::SigSpec mux_out = sigmap(mux_cell->getPort(ID::Y));
// Require the mux to drive the entire operator input
if (mux_out != in_sig)
continue;
if (sig_has_keep(mux_out))
continue;
if (!fanout_within_limit(mux_out))
continue;
// Only push one mux per operator per iteration
candidates.push_back({cell, mux_cell, it.first});
break;
}
}
if (candidates.empty())
break;
pool<RTLIL::Cell*> cells_to_remove;
pool<RTLIL::SigBit> touched_bits;
for (auto &cand : candidates)
{
RTLIL::Cell *cell = cand.cell;
RTLIL::Cell *mux_cell = cand.mux_cell;
RTLIL::SigSpec cand_in = sigmap(cell->getPort(cand.port));
bool overlaps = false;
for (auto &bit : cand_in) {
if (touched_bits.count(bit)) {
overlaps = true;
break;
}
}
if (overlaps)
continue;
// Avoid rewriting overlapping signals within a single iteration
for (auto &bit : cand_in)
touched_bits.insert(bit);
log_debug(" Pushing mux %s through %s cell %s port %s.\n",
log_id(mux_cell->name), log_id(cell->type), log_id(cell->name), log_id(cand.port));
// Reuse the original operator as branch A to preserve the instance name and metadata
RTLIL::Cell *branch_a = cell;
// Create branch B with a deterministic name derived from the original
RTLIL::IdString branch_b_name = NEW_ID2;
RTLIL::Cell *branch_b = module->addCell(branch_b_name, cell->type);
branch_b->parameters = cell->parameters;
branch_b->attributes = cell->attributes;
branch_b->set_src_attribute(cell->get_src_attribute());
RTLIL::SigSpec orig_y = cell->getPort(ID::Y);
std::vector<std::pair<IdString, RTLIL::SigSpec>> conns;
for (auto &p : cell->connections())
conns.push_back(p);
for (auto &p : conns) {
RTLIL::SigSpec conn_sig = p.second;
if (p.first == cand.port) {
branch_a->setPort(p.first, mux_cell->getPort(ID::A));
branch_b->setPort(p.first, mux_cell->getPort(ID::B));
} else {
branch_a->setPort(p.first, conn_sig);
branch_b->setPort(p.first, conn_sig);
}
}
RTLIL::IdString out_a_name = NEW_ID2_SUFFIX("mpa_y");
RTLIL::IdString out_b_name = NEW_ID2_SUFFIX("mpb_y");
RTLIL::SigSpec out_a = module->addWire(out_a_name, GetSize(orig_y));
RTLIL::SigSpec out_b = module->addWire(out_b_name, GetSize(orig_y));
branch_a->setPort(ID::Y, out_a);
branch_b->setPort(ID::Y, out_b);
branch_a->fixup_parameters();
branch_b->fixup_parameters();
// Always create a new mux so other consumers of the original mux are unaffected
RTLIL::IdString new_mux_name = NEW_ID2_SUFFIX("muxpush");
RTLIL::Cell *new_mux = module->addMux(new_mux_name, out_a, out_b, mux_cell->getPort(ID::S), orig_y);
new_mux->set_src_attribute(cell->get_src_attribute());
// Remove the original mux when it becomes dead after the rewrite
RTLIL::SigSpec mux_out = sigmap(mux_cell->getPort(ID::Y));
if (fanout_is_one(mux_out))
cells_to_remove.insert(mux_cell);
total_count++;
}
for (auto cell : cells_to_remove)
module->remove(cell);
}
}
};
struct OptMuxPushPass : public Pass {
OptMuxPushPass() : Pass("muxpush", "push muxes through lightweight operators") { }
void help() override
{
log("\n");
log(" muxpush [options] [selection]\n");
log("\n");
log("Push $mux cells forward through lightweight operators by cloning\n");
log("the operator and re-inserting the mux at the output.\n");
log("\n");
log(" -limit <int>\n");
log(" maximum fanout allowed for the mux output (default: 1)\n");
log("\n");
log(" -types <string>\n");
log(" comma-separated list of operator cell types to push through\n");
log(" (default: $add,$sub,$xor)\n");
log("\n");
}
void execute(std::vector<std::string> args, RTLIL::Design *design) override
{
int fanout_limit = 1;
std::string types = "$add,$sub,$xor";
log_header(design, "Executing MUXPUSH pass (push muxes through light ops).\n");
size_t argidx;
for (argidx = 1; argidx < args.size(); argidx++) {
if (args[argidx] == "-limit" && argidx+1 < args.size()) {
fanout_limit = atoi(args[++argidx].c_str());
continue;
}
if (args[argidx] == "-types" && argidx+1 < args.size()) {
types = args[++argidx];
continue;
}
break;
}
extra_args(args, argidx, design);
pool<IdString> target_types;
for (auto &tok : split_tokens(types, ", \t\r\n")) {
if (tok.empty())
continue;
target_types.insert(RTLIL::escape_id(tok));
}
int total_count = 0;
for (auto module : design->selected_modules()) {
if (module->get_bool_attribute(ID::blackbox))
continue;
OptMuxPushWorker worker(design, module, target_types, fanout_limit);
worker.run();
total_count += worker.total_count;
}
log(" Pushed muxes through %d operator inputs.\n", total_count);
}
} OptMuxPushPass;
PRIVATE_NAMESPACE_END

116
tests/silimate/mux_push.ys Normal file
View File

@ -0,0 +1,116 @@
log -header "Interleaved mux/add chain pushes muxes to the end"
log -push
design -reset
read_verilog <<EOF
module top (
input wire s0,
input wire s1,
input wire s2,
input wire [7:0] a,
input wire [7:0] b,
input wire [7:0] c,
input wire [7:0] d,
input wire [7:0] e,
output wire [7:0] y
);
wire [7:0] m0;
wire [7:0] a0;
wire [7:0] m1;
wire [7:0] a1;
assign m0 = s0 ? b : a;
assign a0 = m0 + c;
assign m1 = s1 ? a0 : d;
assign a1 = m1 + e;
assign y = s2 ? a1 : a;
endmodule
EOF
proc
check -assert
# Check equivalence after mux push
equiv_opt -assert muxpush -limit 1 -types $add
# After the pass, no $add should have a $mux driving its inputs
design -load postopt
select -set add_fanin t:$add %ci*
select -set mux_cells t:$mux t:$ternary
select -assert-count 0 @add_fanin @mux_cells %i
design -reset
log -pop
log -header "Negative case: fanout limit blocks push"
log -push
design -reset
read_verilog <<EOF
module top (
input wire s0,
input wire [7:0] a,
input wire [7:0] b,
input wire [7:0] c,
input wire [7:0] d,
output wire [7:0] y0,
output wire [7:0] y1
);
wire [7:0] m0;
assign m0 = s0 ? b : a;
assign y0 = m0 + c;
assign y1 = m0 + d;
endmodule
EOF
proc
check -assert
# Check equivalence after mux push (should not apply due to fanout>1)
equiv_opt -assert muxpush -limit 1 -types $add
# Mux still drives add inputs due to fanout limit
design -load postopt
select -set add_fanin t:$add %ci*
select -set mux_cells t:$mux t:$ternary
select -assert-count 1 @add_fanin @mux_cells %i
design -reset
log -pop
log -header "Push with fanout limit > 1"
log -push
design -reset
read_verilog <<EOF
module top (
input wire s0,
input wire [7:0] a,
input wire [7:0] b,
input wire [7:0] c,
input wire [7:0] d,
output wire [7:0] y0,
output wire [7:0] y1
);
wire [7:0] m0;
wire [7:0] a0;
assign m0 = s0 ? b : a;
assign a0 = m0 + c;
assign y0 = a0;
assign y1 = m0 + d;
endmodule
EOF
proc
check -assert
# Check equivalence after mux push (allowed due to fanout limit)
equiv_opt -assert muxpush -limit 2 -types $add
# Both adders should be pushed with fanout limit > 1
design -load postopt
select -set add_fanin t:$add %ci*
select -set mux_cells t:$mux t:$ternary
select -assert-count 0 @add_fanin @mux_cells %i
design -reset
log -pop