mirror of https://github.com/YosysHQ/yosys.git
Merge pull request #104 from Silimate/mux_push_implementation
mux_push implementation
This commit is contained in:
commit
dc1847f89a
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue