mirror of
https://github.com/YosysHQ/yosys
synced 2026-07-05 06:56:11 +00:00
Merge branch 'main' into related_load_data
This commit is contained in:
commit
f8b5da5058
226 changed files with 16144 additions and 2736 deletions
|
|
@ -23,6 +23,7 @@ OBJS += passes/opt/opt_lut_ins.o
|
|||
OBJS += passes/opt/opt_ffinv.o
|
||||
OBJS += passes/opt/pmux2shiftx.o
|
||||
OBJS += passes/opt/muxpack.o
|
||||
OBJS += passes/opt/opt_balance_tree.o
|
||||
|
||||
OBJS += passes/opt/peepopt.o
|
||||
GENFILES += passes/opt/peepopt_pm.h
|
||||
|
|
|
|||
|
|
@ -193,7 +193,6 @@ struct OptPass : public Pass {
|
|||
}
|
||||
|
||||
design->optimize();
|
||||
design->sort();
|
||||
design->check();
|
||||
|
||||
log_header(design, "Finished fast OPT passes.%s\n", fast_mode ? "" : " (There is nothing left to do.)");
|
||||
|
|
|
|||
379
passes/opt/opt_balance_tree.cc
Normal file
379
passes/opt/opt_balance_tree.cc
Normal file
|
|
@ -0,0 +1,379 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
|
||||
* 2019 Eddie Hung <eddie@fpgeh.com>
|
||||
* 2024 Akash Levy <akash@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/yosys.h"
|
||||
#include "kernel/sigtools.h"
|
||||
#include <deque>
|
||||
|
||||
USING_YOSYS_NAMESPACE
|
||||
PRIVATE_NAMESPACE_BEGIN
|
||||
|
||||
|
||||
struct OptBalanceTreeWorker {
|
||||
// Module and signal map
|
||||
Module *module;
|
||||
SigMap sigmap;
|
||||
|
||||
// Counts of each cell type that are getting balanced
|
||||
dict<IdString, int> cell_count;
|
||||
|
||||
// Check if cell is of the right type and has matching input/output widths
|
||||
// Only allow cells with "natural" output widths (no truncation) to prevent
|
||||
// equivalence issues when rebalancing (see YosysHQ/yosys#5605)
|
||||
bool is_right_type(Cell* cell, IdString cell_type) {
|
||||
if (cell->type != cell_type)
|
||||
return false;
|
||||
|
||||
int y_width = cell->getParam(ID::Y_WIDTH).as_int();
|
||||
int a_width = cell->getParam(ID::A_WIDTH).as_int();
|
||||
int b_width = cell->getParam(ID::B_WIDTH).as_int();
|
||||
|
||||
// Calculate the "natural" output width for this operation
|
||||
int natural_width;
|
||||
if (cell_type == ID($add)) {
|
||||
// Addition produces max(A_WIDTH, B_WIDTH) + 1 (for carry bit)
|
||||
natural_width = std::max(a_width, b_width) + 1;
|
||||
} else if (cell_type == ID($mul)) {
|
||||
// Multiplication produces A_WIDTH + B_WIDTH
|
||||
natural_width = a_width + b_width;
|
||||
} else {
|
||||
// Logic operations ($and/$or/$xor) produce max(A_WIDTH, B_WIDTH)
|
||||
natural_width = std::max(a_width, b_width);
|
||||
}
|
||||
|
||||
// Only allow cells where Y_WIDTH >= natural width (no truncation)
|
||||
// This prevents rebalancing chains where truncation semantics matter
|
||||
return y_width >= natural_width;
|
||||
}
|
||||
|
||||
// Create a balanced binary tree from a vector of source signals
|
||||
SigSpec create_balanced_tree(vector<SigSpec> &sources, IdString cell_type, Cell* cell) {
|
||||
// Base case: if we have no sources, return an empty signal
|
||||
if (sources.size() == 0)
|
||||
return SigSpec();
|
||||
|
||||
// Base case: if we have only one source, return it
|
||||
if (sources.size() == 1)
|
||||
return sources[0];
|
||||
|
||||
// Base case: if we have two sources, create a single cell
|
||||
if (sources.size() == 2) {
|
||||
// Create a new cell of the same type
|
||||
Cell* new_cell = module->addCell(NEW_ID, cell_type);
|
||||
|
||||
// Copy attributes from reference cell
|
||||
new_cell->attributes = cell->attributes;
|
||||
|
||||
// Create output wire
|
||||
int out_width = cell->getParam(ID::Y_WIDTH).as_int();
|
||||
if (cell_type == ID($add))
|
||||
out_width = max(sources[0].size(), sources[1].size()) + 1;
|
||||
else if (cell_type == ID($mul))
|
||||
out_width = sources[0].size() + sources[1].size();
|
||||
Wire* out_wire = module->addWire(NEW_ID, out_width);
|
||||
|
||||
// Connect ports and fix up parameters
|
||||
new_cell->setPort(ID::A, sources[0]);
|
||||
new_cell->setPort(ID::B, sources[1]);
|
||||
new_cell->setPort(ID::Y, out_wire);
|
||||
new_cell->fixup_parameters();
|
||||
new_cell->setParam(ID::A_SIGNED, cell->getParam(ID::A_SIGNED));
|
||||
new_cell->setParam(ID::B_SIGNED, cell->getParam(ID::B_SIGNED));
|
||||
|
||||
// Update count and return output wire
|
||||
cell_count[cell_type]++;
|
||||
return out_wire;
|
||||
}
|
||||
|
||||
// Recursive case: split sources into two groups and create subtrees
|
||||
int mid = (sources.size() + 1) / 2;
|
||||
vector<SigSpec> left_sources(sources.begin(), sources.begin() + mid);
|
||||
vector<SigSpec> right_sources(sources.begin() + mid, sources.end());
|
||||
|
||||
SigSpec left_tree = create_balanced_tree(left_sources, cell_type, cell);
|
||||
SigSpec right_tree = create_balanced_tree(right_sources, cell_type, cell);
|
||||
|
||||
// Create a cell to combine the two subtrees
|
||||
Cell* new_cell = module->addCell(NEW_ID, cell_type);
|
||||
|
||||
// Copy attributes from reference cell
|
||||
new_cell->attributes = cell->attributes;
|
||||
|
||||
// Create output wire
|
||||
int out_width = cell->getParam(ID::Y_WIDTH).as_int();
|
||||
if (cell_type == ID($add))
|
||||
out_width = max(left_tree.size(), right_tree.size()) + 1;
|
||||
else if (cell_type == ID($mul))
|
||||
out_width = left_tree.size() + right_tree.size();
|
||||
Wire* out_wire = module->addWire(NEW_ID, out_width);
|
||||
|
||||
// Connect ports and fix up parameters
|
||||
new_cell->setPort(ID::A, left_tree);
|
||||
new_cell->setPort(ID::B, right_tree);
|
||||
new_cell->setPort(ID::Y, out_wire);
|
||||
new_cell->fixup_parameters();
|
||||
new_cell->setParam(ID::A_SIGNED, cell->getParam(ID::A_SIGNED));
|
||||
new_cell->setParam(ID::B_SIGNED, cell->getParam(ID::B_SIGNED));
|
||||
|
||||
// Update count and return output wire
|
||||
cell_count[cell_type]++;
|
||||
return out_wire;
|
||||
}
|
||||
|
||||
OptBalanceTreeWorker(Module *module, const vector<IdString> cell_types) : module(module), sigmap(module) {
|
||||
// Do for each cell type
|
||||
for (auto cell_type : cell_types) {
|
||||
// Index all of the nets in the module
|
||||
dict<SigSpec, Cell*> sig_to_driver;
|
||||
dict<SigSpec, pool<Cell*>> sig_to_sink;
|
||||
for (auto cell : module->selected_cells())
|
||||
{
|
||||
for (auto &conn : cell->connections())
|
||||
{
|
||||
if (cell->output(conn.first))
|
||||
sig_to_driver[sigmap(conn.second)] = cell;
|
||||
|
||||
if (cell->input(conn.first))
|
||||
{
|
||||
SigSpec sig = sigmap(conn.second);
|
||||
if (sig_to_sink.count(sig) == 0)
|
||||
sig_to_sink[sig] = pool<Cell*>();
|
||||
sig_to_sink[sig].insert(cell);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Need to check if any wires connect to module ports
|
||||
pool<SigSpec> input_port_sigs;
|
||||
pool<SigSpec> output_port_sigs;
|
||||
for (auto wire : module->selected_wires())
|
||||
if (wire->port_input || wire->port_output) {
|
||||
SigSpec sig = sigmap(wire);
|
||||
for (auto bit : sig) {
|
||||
if (wire->port_input)
|
||||
input_port_sigs.insert(bit);
|
||||
if (wire->port_output)
|
||||
output_port_sigs.insert(bit);
|
||||
}
|
||||
}
|
||||
|
||||
// Actual logic starts here
|
||||
pool<Cell*> consumed_cells;
|
||||
for (auto cell : module->selected_cells())
|
||||
{
|
||||
// If consumed or not the correct type, skip
|
||||
if (consumed_cells.count(cell) || !is_right_type(cell, cell_type))
|
||||
continue;
|
||||
|
||||
// BFS, following all chains until they hit a cell of a different type
|
||||
// Pick the longest one
|
||||
auto y = sigmap(cell->getPort(ID::Y));
|
||||
pool<Cell*> sinks;
|
||||
pool<Cell*> current_loads = sig_to_sink[y];
|
||||
pool<Cell*> next_loads;
|
||||
while (!current_loads.empty())
|
||||
{
|
||||
// Find each sink and see what they are
|
||||
for (auto x : current_loads)
|
||||
{
|
||||
// If not the correct type, don't follow any further
|
||||
// (but add the originating cell to the list of sinks)
|
||||
if (!is_right_type(x, cell_type))
|
||||
{
|
||||
sinks.insert(cell);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto xy = sigmap(x->getPort(ID::Y));
|
||||
|
||||
// If this signal drives a port, add it to the sinks
|
||||
// (even though it may not be the end of a chain)
|
||||
for (auto bit : xy) {
|
||||
if (output_port_sigs.count(bit) && !consumed_cells.count(x)) {
|
||||
sinks.insert(x);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Search signal's fanout
|
||||
auto& next = sig_to_sink[xy];
|
||||
for (auto z : next)
|
||||
next_loads.insert(z);
|
||||
}
|
||||
|
||||
// If we couldn't find any downstream loads, stop.
|
||||
// Create a reduction for each of the max-length chains we found
|
||||
if (next_loads.empty())
|
||||
{
|
||||
for (auto s : current_loads)
|
||||
{
|
||||
// Not one of our gates? Don't follow any further
|
||||
if (!is_right_type(s, cell_type))
|
||||
continue;
|
||||
|
||||
sinks.insert(s);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Otherwise, continue down the chain
|
||||
current_loads = next_loads;
|
||||
next_loads.clear();
|
||||
}
|
||||
|
||||
// We have our list of sinks, now go tree balance the chains
|
||||
for (auto head_cell : sinks)
|
||||
{
|
||||
// Avoid duplication if we already were covered
|
||||
if (consumed_cells.count(head_cell))
|
||||
continue;
|
||||
|
||||
// Get sources of the chain
|
||||
dict<SigSpec, int> sources;
|
||||
dict<SigSpec, bool> signeds;
|
||||
int inner_cells = 0;
|
||||
std::deque<Cell*> bfs_queue = {head_cell};
|
||||
while (bfs_queue.size())
|
||||
{
|
||||
Cell* x = bfs_queue.front();
|
||||
bfs_queue.pop_front();
|
||||
|
||||
for (IdString port: {ID::A, ID::B}) {
|
||||
auto sig = sigmap(x->getPort(port));
|
||||
Cell* drv = sig_to_driver[sig];
|
||||
bool drv_ok = drv && is_right_type(drv, cell_type);
|
||||
for (auto bit : sig) {
|
||||
if (input_port_sigs.count(bit) && !consumed_cells.count(drv)) {
|
||||
drv_ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (drv_ok) {
|
||||
inner_cells++;
|
||||
bfs_queue.push_back(drv);
|
||||
} else {
|
||||
sources[sig]++;
|
||||
signeds[sig] = x->getParam(port == ID::A ? ID::A_SIGNED : ID::B_SIGNED).as_bool();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (inner_cells)
|
||||
{
|
||||
// Create a tree
|
||||
log_debug(" Creating tree for %s with %d sources and %d inner cells...\n", log_id(head_cell), GetSize(sources), inner_cells);
|
||||
|
||||
// Build a vector of all source signals
|
||||
vector<SigSpec> source_signals;
|
||||
vector<bool> signed_flags;
|
||||
for (auto &source : sources) {
|
||||
for (int i = 0; i < source.second; i++) {
|
||||
source_signals.push_back(source.first);
|
||||
signed_flags.push_back(signeds[source.first]);
|
||||
}
|
||||
}
|
||||
|
||||
// If not all signed flags are the same, do not balance
|
||||
if (!std::all_of(signed_flags.begin(), signed_flags.end(), [&](bool flag) { return flag == signed_flags[0]; })) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create the balanced tree
|
||||
SigSpec tree_output = create_balanced_tree(source_signals, cell_type, head_cell);
|
||||
|
||||
// Connect the tree output to the head cell's output
|
||||
SigSpec head_output = sigmap(head_cell->getPort(ID::Y));
|
||||
int connect_width = std::min(head_output.size(), tree_output.size());
|
||||
module->connect(head_output.extract(0, connect_width), tree_output.extract(0, connect_width));
|
||||
if (head_output.size() > tree_output.size()) {
|
||||
SigBit sext_bit = head_cell->getParam(ID::A_SIGNED).as_bool() ? head_output[connect_width - 1] : State::S0;
|
||||
module->connect(head_output.extract(connect_width, head_output.size() - connect_width), SigSpec(sext_bit, head_output.size() - connect_width));
|
||||
}
|
||||
|
||||
// Mark consumed cell for removal
|
||||
consumed_cells.insert(head_cell);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all consumed cells, which now have been replaced by trees
|
||||
for (auto cell : consumed_cells)
|
||||
module->remove(cell);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct OptBalanceTreePass : public Pass {
|
||||
OptBalanceTreePass() : Pass("opt_balance_tree", "$and/$or/$xor/$add/$mul cascades to trees") { }
|
||||
void help() override {
|
||||
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
|
||||
log("\n");
|
||||
log(" opt_balance_tree [options] [selection]\n");
|
||||
log("\n");
|
||||
log("This pass converts cascaded chains of $and/$or/$xor/$add/$mul cells into\n");
|
||||
log("trees of cells to improve timing.\n");
|
||||
log("\n");
|
||||
log(" -arith\n");
|
||||
log(" only convert arithmetic cells.\n");
|
||||
log("\n");
|
||||
log(" -logic\n");
|
||||
log(" only convert logic cells.\n");
|
||||
log("\n");
|
||||
}
|
||||
void execute(std::vector<std::string> args, RTLIL::Design *design) override {
|
||||
log_header(design, "Executing OPT_BALANCE_TREE pass (cell cascades to trees).\n");
|
||||
log_experimental("open_balance_tree");
|
||||
|
||||
// Handle arguments
|
||||
size_t argidx;
|
||||
vector<IdString> cell_types = {ID($and), ID($or), ID($xor), ID($add), ID($mul)};
|
||||
for (argidx = 1; argidx < args.size(); argidx++) {
|
||||
if (args[argidx] == "-arith") {
|
||||
cell_types = {ID($add), ID($mul)};
|
||||
continue;
|
||||
}
|
||||
if (args[argidx] == "-logic") {
|
||||
cell_types = {ID($and), ID($or), ID($xor)};
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
extra_args(args, argidx, design);
|
||||
|
||||
// Count of all cells that were packed
|
||||
dict<IdString, int> cell_count;
|
||||
for (auto module : design->selected_modules()) {
|
||||
OptBalanceTreeWorker worker(module, cell_types);
|
||||
for (auto cell : worker.cell_count) {
|
||||
cell_count[cell.first] += cell.second;
|
||||
}
|
||||
}
|
||||
|
||||
// Log stats
|
||||
for (auto cell_type : cell_types)
|
||||
log("Converted %d %s cells into trees.\n", cell_count[cell_type], log_id(cell_type));
|
||||
|
||||
// Clean up
|
||||
Yosys::run_pass("clean -purge");
|
||||
}
|
||||
} OptBalanceTreePass;
|
||||
|
||||
PRIVATE_NAMESPACE_END
|
||||
|
|
@ -21,6 +21,7 @@
|
|||
#include "kernel/sigtools.h"
|
||||
#include "kernel/log.h"
|
||||
#include "kernel/celltypes.h"
|
||||
#include "kernel/newcelltypes.h"
|
||||
#include "kernel/ffinit.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
|
@ -101,7 +102,10 @@ struct keep_cache_t
|
|||
};
|
||||
|
||||
keep_cache_t keep_cache;
|
||||
CellTypes ct_reg, ct_all;
|
||||
static constexpr auto ct_reg = StaticCellTypes::Categories::join(
|
||||
StaticCellTypes::Compat::mem_ff,
|
||||
StaticCellTypes::categories.is_anyinit);
|
||||
NewCellTypes ct_all;
|
||||
int count_rm_cells, count_rm_wires;
|
||||
|
||||
void rmunused_module_cells(Module *module, bool verbose)
|
||||
|
|
@ -271,6 +275,9 @@ bool compare_signals(RTLIL::SigBit &s1, RTLIL::SigBit &s2, SigPool ®s, SigPoo
|
|||
return conns.check_any(s2);
|
||||
}
|
||||
|
||||
if (w1 == w2)
|
||||
return s2.offset < s1.offset;
|
||||
|
||||
if (w1->port_output != w2->port_output)
|
||||
return w2->port_output;
|
||||
|
||||
|
|
@ -307,10 +314,10 @@ bool rmunused_module_signals(RTLIL::Module *module, bool purge_mode, bool verbos
|
|||
if (!purge_mode)
|
||||
for (auto &it : module->cells_) {
|
||||
RTLIL::Cell *cell = it.second;
|
||||
if (ct_reg.cell_known(cell->type)) {
|
||||
if (ct_reg(cell->type)) {
|
||||
bool clk2fflogic = cell->get_bool_attribute(ID(clk2fflogic));
|
||||
for (auto &it2 : cell->connections())
|
||||
if (clk2fflogic ? it2.first == ID::D : ct_reg.cell_output(cell->type, it2.first))
|
||||
if (clk2fflogic ? it2.first == ID::D : ct_all.cell_output(cell->type, it2.first))
|
||||
register_signals.add(it2.second);
|
||||
}
|
||||
for (auto &it2 : cell->connections())
|
||||
|
|
@ -343,7 +350,7 @@ bool rmunused_module_signals(RTLIL::Module *module, bool purge_mode, bool verbos
|
|||
RTLIL::Wire *wire = it.second;
|
||||
for (int i = 0; i < wire->width; i++) {
|
||||
RTLIL::SigBit s1 = RTLIL::SigBit(wire, i), s2 = assign_map(s1);
|
||||
if (!compare_signals(s1, s2, register_signals, connected_signals, direct_wires))
|
||||
if (compare_signals(s2, s1, register_signals, connected_signals, direct_wires))
|
||||
assign_map.add(s1);
|
||||
}
|
||||
}
|
||||
|
|
@ -467,8 +474,6 @@ bool rmunused_module_signals(RTLIL::Module *module, bool purge_mode, bool verbos
|
|||
wire->attributes.erase(ID::init);
|
||||
else
|
||||
wire->attributes.at(ID::init) = initval;
|
||||
used_signals.add(new_conn.first);
|
||||
used_signals.add(new_conn.second);
|
||||
module->connect(new_conn);
|
||||
}
|
||||
|
||||
|
|
@ -516,14 +521,12 @@ bool rmunused_module_signals(RTLIL::Module *module, bool purge_mode, bool verbos
|
|||
bool rmunused_module_init(RTLIL::Module *module, bool verbose)
|
||||
{
|
||||
bool did_something = false;
|
||||
CellTypes fftypes;
|
||||
fftypes.setup_internals_mem();
|
||||
|
||||
SigMap sigmap(module);
|
||||
dict<SigBit, State> qbits;
|
||||
|
||||
for (auto cell : module->cells())
|
||||
if (fftypes.cell_known(cell->type) && cell->hasPort(ID::Q))
|
||||
if (StaticCellTypes::Compat::internals_mem_ff(cell->type) && cell->hasPort(ID::Q))
|
||||
{
|
||||
SigSpec sig = cell->getPort(ID::Q);
|
||||
|
||||
|
|
@ -696,10 +699,6 @@ struct OptCleanPass : public Pass {
|
|||
|
||||
keep_cache.reset(design, purge_mode);
|
||||
|
||||
ct_reg.setup_internals_mem();
|
||||
ct_reg.setup_internals_anyinit();
|
||||
ct_reg.setup_stdcells_mem();
|
||||
|
||||
ct_all.setup(design);
|
||||
|
||||
count_rm_cells = 0;
|
||||
|
|
@ -715,11 +714,9 @@ struct OptCleanPass : public Pass {
|
|||
log("Removed %d unused cells and %d unused wires.\n", count_rm_cells, count_rm_wires);
|
||||
|
||||
design->optimize();
|
||||
design->sort();
|
||||
design->check();
|
||||
|
||||
keep_cache.reset();
|
||||
ct_reg.clear();
|
||||
ct_all.clear();
|
||||
log_pop();
|
||||
|
||||
|
|
@ -760,10 +757,6 @@ struct CleanPass : public Pass {
|
|||
|
||||
keep_cache.reset(design);
|
||||
|
||||
ct_reg.setup_internals_mem();
|
||||
ct_reg.setup_internals_anyinit();
|
||||
ct_reg.setup_stdcells_mem();
|
||||
|
||||
ct_all.setup(design);
|
||||
|
||||
count_rm_cells = 0;
|
||||
|
|
@ -780,11 +773,9 @@ struct CleanPass : public Pass {
|
|||
log("Removed %d unused cells and %d unused wires.\n", count_rm_cells, count_rm_wires);
|
||||
|
||||
design->optimize();
|
||||
design->sort();
|
||||
design->check();
|
||||
|
||||
keep_cache.reset();
|
||||
ct_reg.clear();
|
||||
ct_all.clear();
|
||||
|
||||
request_garbage_collection();
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -20,6 +20,7 @@
|
|||
#include "kernel/register.h"
|
||||
#include "kernel/sigtools.h"
|
||||
#include "kernel/celltypes.h"
|
||||
#include "kernel/newcelltypes.h"
|
||||
#include "kernel/utils.h"
|
||||
#include "kernel/log.h"
|
||||
#include <stdlib.h>
|
||||
|
|
@ -31,7 +32,7 @@ PRIVATE_NAMESPACE_BEGIN
|
|||
|
||||
bool did_something;
|
||||
|
||||
void replace_undriven(RTLIL::Module *module, const CellTypes &ct)
|
||||
void replace_undriven(RTLIL::Module *module, const NewCellTypes &ct)
|
||||
{
|
||||
SigMap sigmap(module);
|
||||
SigPool driven_signals;
|
||||
|
|
@ -407,9 +408,6 @@ void replace_const_cells(RTLIL::Design *design, RTLIL::Module *module, bool cons
|
|||
}
|
||||
}
|
||||
|
||||
CellTypes ct_memcells;
|
||||
ct_memcells.setup_stdcells_mem();
|
||||
|
||||
if (!noclkinv)
|
||||
for (auto cell : module->cells())
|
||||
if (design->selected(module, cell)) {
|
||||
|
|
@ -433,7 +431,7 @@ void replace_const_cells(RTLIL::Design *design, RTLIL::Module *module, bool cons
|
|||
if (cell->type.in(ID($dffe), ID($adffe), ID($aldffe), ID($sdffe), ID($sdffce), ID($dffsre), ID($dlatch), ID($adlatch), ID($dlatchsr)))
|
||||
handle_polarity_inv(cell, ID::EN, ID::EN_POLARITY, assign_map, invert_map);
|
||||
|
||||
if (!ct_memcells.cell_known(cell->type))
|
||||
if (!StaticCellTypes::Compat::stdcells_mem(cell->type))
|
||||
continue;
|
||||
|
||||
handle_clkpol_celltype_swap(cell, "$_SR_N?_", "$_SR_P?_", ID::S, assign_map, invert_map);
|
||||
|
|
@ -1667,7 +1665,11 @@ skip_identity:
|
|||
int bit_idx;
|
||||
const auto onehot = sig_a.is_onehot(&bit_idx);
|
||||
|
||||
if (onehot) {
|
||||
// Power of two
|
||||
// A is unsigned or positive
|
||||
if (onehot && (!cell->parameters[ID::A_SIGNED].as_bool() || bit_idx < sig_a.size() - 1)) {
|
||||
cell->parameters[ID::A_SIGNED] = 0;
|
||||
// 2^B = 1<<B
|
||||
if (bit_idx == 1) {
|
||||
log_debug("Replacing pow cell `%s' in module `%s' with left-shift\n",
|
||||
cell->name.c_str(), module->name.c_str());
|
||||
|
|
@ -1679,7 +1681,6 @@ skip_identity:
|
|||
log_debug("Replacing pow cell `%s' in module `%s' with multiply and left-shift\n",
|
||||
cell->name.c_str(), module->name.c_str());
|
||||
cell->type = ID($mul);
|
||||
cell->parameters[ID::A_SIGNED] = 0;
|
||||
cell->setPort(ID::A, Const(bit_idx, cell->parameters[ID::A_WIDTH].as_int()));
|
||||
|
||||
SigSpec y_wire = module->addWire(NEW_ID, y_size);
|
||||
|
|
@ -2291,7 +2292,7 @@ struct OptExprPass : public Pass {
|
|||
}
|
||||
extra_args(args, argidx, design);
|
||||
|
||||
CellTypes ct(design);
|
||||
NewCellTypes ct(design);
|
||||
for (auto module : design->selected_modules())
|
||||
{
|
||||
log("Optimizing module %s.\n", log_id(module));
|
||||
|
|
|
|||
|
|
@ -39,7 +39,8 @@ struct OptLutInsPass : public Pass {
|
|||
log("\n");
|
||||
log(" -tech <technology>\n");
|
||||
log(" Instead of generic $lut cells, operate on LUT cells specific\n");
|
||||
log(" to the given technology. Valid values are: xilinx, lattice, gowin.\n");
|
||||
log(" to the given technology. Valid values are: xilinx, lattice,\n");
|
||||
log(" gowin, analogdevices.\n");
|
||||
log("\n");
|
||||
}
|
||||
void execute(std::vector<std::string> args, RTLIL::Design *design) override
|
||||
|
|
@ -58,7 +59,7 @@ struct OptLutInsPass : public Pass {
|
|||
}
|
||||
extra_args(args, argidx, design);
|
||||
|
||||
if (techname != "" && techname != "xilinx" && techname != "lattice" && techname != "ecp5" && techname != "gowin")
|
||||
if (techname != "" && techname != "xilinx" && techname != "lattice" && techname != "analogdevices" && techname != "gowin")
|
||||
log_cmd_error("Unsupported technology: '%s'\n", techname);
|
||||
|
||||
for (auto module : design->selected_modules())
|
||||
|
|
@ -81,7 +82,7 @@ struct OptLutInsPass : public Pass {
|
|||
inputs = cell->getPort(ID::A);
|
||||
output = cell->getPort(ID::Y);
|
||||
lut = cell->getParam(ID::LUT);
|
||||
} else if (techname == "xilinx" || techname == "gowin") {
|
||||
} else if (techname == "xilinx" || techname == "gowin" || techname == "analogdevices") {
|
||||
if (cell->type == ID(LUT1)) {
|
||||
inputs = {
|
||||
cell->getPort(ID(I0)),
|
||||
|
|
@ -126,11 +127,11 @@ struct OptLutInsPass : public Pass {
|
|||
continue;
|
||||
}
|
||||
lut = cell->getParam(ID::INIT);
|
||||
if (techname == "xilinx")
|
||||
if (techname == "xilinx" || techname == "analogdevices")
|
||||
output = cell->getPort(ID::O);
|
||||
else
|
||||
output = cell->getPort(ID::F);
|
||||
} else if (techname == "lattice" || techname == "ecp5") {
|
||||
} else if (techname == "lattice") {
|
||||
if (cell->type == ID(LUT4)) {
|
||||
inputs = {
|
||||
cell->getPort(ID::A),
|
||||
|
|
@ -236,7 +237,7 @@ struct OptLutInsPass : public Pass {
|
|||
} else {
|
||||
// xilinx, gowin
|
||||
cell->setParam(ID::INIT, new_lut);
|
||||
if (techname == "xilinx")
|
||||
if (techname == "xilinx" || techname == "analogdevices")
|
||||
log_assert(GetSize(new_inputs) <= 6);
|
||||
else
|
||||
log_assert(GetSize(new_inputs) <= 4);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
#include "kernel/sigtools.h"
|
||||
#include "kernel/log.h"
|
||||
#include "kernel/celltypes.h"
|
||||
#include "kernel/threading.h"
|
||||
#include "libs/sha1/sha1.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
|
@ -37,16 +38,73 @@ PRIVATE_NAMESPACE_BEGIN
|
|||
template <typename T, typename U>
|
||||
inline Hasher hash_pair(const T &t, const U &u) { return hash_ops<std::pair<T, U>>::hash(t, u); }
|
||||
|
||||
struct OptMergeWorker
|
||||
// Some cell and its hash value.
|
||||
struct CellHash
|
||||
{
|
||||
RTLIL::Design *design;
|
||||
RTLIL::Module *module;
|
||||
SigMap assign_map;
|
||||
FfInitVals initvals;
|
||||
bool mode_share_all;
|
||||
// Index of a cell in the module
|
||||
int cell_index;
|
||||
Hasher::hash_t hash_value;
|
||||
};
|
||||
|
||||
CellTypes ct;
|
||||
int total_count;
|
||||
// The algorithm:
|
||||
// 1) Compute and store the hashes of all relevant cells, in parallel.
|
||||
// 2) Given N = the number of threads, partition the cells into N buckets by hash value:
|
||||
// bucket k contains the cells whose hash value mod N = k.
|
||||
// 3) For each bucket in parallel, build a hashtable of that bucket’s cells (using the
|
||||
// precomputed hashes) and record the duplicates found.
|
||||
// 4) On the main thread, process the list of duplicates to remove cells.
|
||||
// For efficiency we fuse the second step into the first step by having the parallel
|
||||
// threads write the cells into buckets directly.
|
||||
// To avoid synchronization overhead, we divide each bucket into N shards. Each
|
||||
// thread j adds a cell to bucket k by writing to shard j of bucket k —
|
||||
// no synchronization required. In the next phase, thread k builds the hashtable for
|
||||
// bucket k by iterating over all shards of the bucket.
|
||||
|
||||
// The input to each thread in the "compute cell hashes" phase.
|
||||
struct CellRange
|
||||
{
|
||||
int begin;
|
||||
int end;
|
||||
};
|
||||
|
||||
// The output from each thread in the "compute cell hashes" phase.
|
||||
struct CellHashes
|
||||
{
|
||||
// Entry i contains the hashes where hash_value % bucketed_cell_hashes.size() == i
|
||||
std::vector<std::vector<CellHash>> bucketed_cell_hashes;
|
||||
};
|
||||
|
||||
// A duplicate cell that has been found.
|
||||
struct DuplicateCell
|
||||
{
|
||||
// Remove this cell from the design
|
||||
int remove_cell;
|
||||
// ... and use this cell instead.
|
||||
int keep_cell;
|
||||
};
|
||||
|
||||
// The input to each thread in the "find duplicate cells" phase.
|
||||
// Shards of buckets of cell hashes
|
||||
struct Shards
|
||||
{
|
||||
std::vector<std::vector<std::vector<CellHash>>> &bucketed_cell_hashes;
|
||||
};
|
||||
|
||||
// The output from each thread in the "find duplicate cells" phase.
|
||||
struct FoundDuplicates
|
||||
{
|
||||
std::vector<DuplicateCell> duplicates;
|
||||
};
|
||||
|
||||
struct OptMergeThreadWorker
|
||||
{
|
||||
const RTLIL::Module *module;
|
||||
const SigMap &assign_map;
|
||||
const FfInitVals &initvals;
|
||||
const CellTypes &ct;
|
||||
int workers;
|
||||
bool mode_share_all;
|
||||
bool mode_keepdc;
|
||||
|
||||
static Hasher hash_pmux_in(const SigSpec& sig_s, const SigSpec& sig_b, Hasher h)
|
||||
{
|
||||
|
|
@ -62,8 +120,8 @@ struct OptMergeWorker
|
|||
|
||||
static void sort_pmux_conn(dict<RTLIL::IdString, RTLIL::SigSpec> &conn)
|
||||
{
|
||||
SigSpec sig_s = conn.at(ID::S);
|
||||
SigSpec sig_b = conn.at(ID::B);
|
||||
const SigSpec &sig_s = conn.at(ID::S);
|
||||
const SigSpec &sig_b = conn.at(ID::B);
|
||||
|
||||
int s_width = GetSize(sig_s);
|
||||
int width = GetSize(sig_b) / s_width;
|
||||
|
|
@ -144,7 +202,6 @@ struct OptMergeWorker
|
|||
|
||||
if (cell1->parameters != cell2->parameters)
|
||||
return false;
|
||||
|
||||
if (cell1->connections_.size() != cell2->connections_.size())
|
||||
return false;
|
||||
for (const auto &it : cell1->connections_)
|
||||
|
|
@ -199,7 +256,7 @@ struct OptMergeWorker
|
|||
return conn1 == conn2;
|
||||
}
|
||||
|
||||
bool has_dont_care_initval(const RTLIL::Cell *cell)
|
||||
bool has_dont_care_initval(const RTLIL::Cell *cell) const
|
||||
{
|
||||
if (!cell->is_builtin_ff())
|
||||
return false;
|
||||
|
|
@ -207,36 +264,134 @@ struct OptMergeWorker
|
|||
return !initvals(cell->getPort(ID::Q)).is_fully_def();
|
||||
}
|
||||
|
||||
OptMergeWorker(RTLIL::Design *design, RTLIL::Module *module, bool mode_nomux, bool mode_share_all, bool mode_keepdc) :
|
||||
design(design), module(module), mode_share_all(mode_share_all)
|
||||
OptMergeThreadWorker(const RTLIL::Module *module, const FfInitVals &initvals,
|
||||
const SigMap &assign_map, const CellTypes &ct, int workers,
|
||||
bool mode_share_all, bool mode_keepdc) :
|
||||
module(module), assign_map(assign_map), initvals(initvals), ct(ct),
|
||||
workers(workers), mode_share_all(mode_share_all), mode_keepdc(mode_keepdc)
|
||||
{
|
||||
total_count = 0;
|
||||
ct.setup_internals();
|
||||
ct.setup_internals_mem();
|
||||
ct.setup_stdcells();
|
||||
ct.setup_stdcells_mem();
|
||||
}
|
||||
|
||||
if (mode_nomux) {
|
||||
ct.cell_types.erase(ID($mux));
|
||||
ct.cell_types.erase(ID($pmux));
|
||||
CellHashes compute_cell_hashes(const CellRange &cell_range) const
|
||||
{
|
||||
std::vector<std::vector<CellHash>> bucketed_cell_hashes(workers);
|
||||
for (int cell_index = cell_range.begin; cell_index < cell_range.end; ++cell_index) {
|
||||
const RTLIL::Cell *cell = module->cell_at(cell_index);
|
||||
if (!module->selected(cell))
|
||||
continue;
|
||||
if (cell->type.in(ID($meminit), ID($meminit_v2), ID($mem), ID($mem_v2))) {
|
||||
// Ignore those for performance: meminit can have an excessively large port,
|
||||
// mem can have an excessively large parameter holding the init data
|
||||
continue;
|
||||
}
|
||||
if (cell->type == ID($scopeinfo))
|
||||
continue;
|
||||
if (mode_keepdc && has_dont_care_initval(cell))
|
||||
continue;
|
||||
if (!cell->known())
|
||||
continue;
|
||||
if (!mode_share_all && !ct.cell_known(cell->type))
|
||||
continue;
|
||||
|
||||
Hasher::hash_t h = hash_cell_function(cell, Hasher()).yield();
|
||||
int bucket_index = h % workers;
|
||||
bucketed_cell_hashes[bucket_index].push_back({cell_index, h});
|
||||
}
|
||||
return {std::move(bucketed_cell_hashes)};
|
||||
}
|
||||
|
||||
ct.cell_types.erase(ID($tribuf));
|
||||
ct.cell_types.erase(ID($_TBUF_));
|
||||
ct.cell_types.erase(ID($anyseq));
|
||||
ct.cell_types.erase(ID($anyconst));
|
||||
ct.cell_types.erase(ID($allseq));
|
||||
ct.cell_types.erase(ID($allconst));
|
||||
ct.cell_types.erase(ID($check));
|
||||
ct.cell_types.erase(ID($assert));
|
||||
ct.cell_types.erase(ID($assume));
|
||||
ct.cell_types.erase(ID($live));
|
||||
ct.cell_types.erase(ID($cover));
|
||||
FoundDuplicates find_duplicate_cells(int index, const Shards &in) const
|
||||
{
|
||||
// We keep a set of known cells. They're hashed with our hash_cell_function
|
||||
// and compared with our compare_cell_parameters_and_connections.
|
||||
struct CellHashOp {
|
||||
std::size_t operator()(const CellHash &c) const {
|
||||
return (std::size_t)c.hash_value;
|
||||
}
|
||||
};
|
||||
struct CellEqualOp {
|
||||
const OptMergeThreadWorker& worker;
|
||||
CellEqualOp(const OptMergeThreadWorker& w) : worker(w) {}
|
||||
bool operator()(const CellHash &lhs, const CellHash &rhs) const {
|
||||
return worker.compare_cell_parameters_and_connections(
|
||||
worker.module->cell_at(lhs.cell_index),
|
||||
worker.module->cell_at(rhs.cell_index));
|
||||
}
|
||||
};
|
||||
std::unordered_set<
|
||||
CellHash,
|
||||
CellHashOp,
|
||||
CellEqualOp> known_cells(0, CellHashOp(), CellEqualOp(*this));
|
||||
|
||||
std::vector<DuplicateCell> duplicates;
|
||||
for (const std::vector<std::vector<CellHash>> &buckets : in.bucketed_cell_hashes) {
|
||||
// Clear out our buckets as we go. This keeps the work of deallocation
|
||||
// off the main thread.
|
||||
std::vector<CellHash> bucket = std::move(buckets[index]);
|
||||
for (CellHash c : bucket) {
|
||||
auto [cell_in_map, inserted] = known_cells.insert(c);
|
||||
if (inserted)
|
||||
continue;
|
||||
CellHash map_c = *cell_in_map;
|
||||
if (module->cell_at(c.cell_index)->has_keep_attr()) {
|
||||
if (module->cell_at(map_c.cell_index)->has_keep_attr())
|
||||
continue;
|
||||
known_cells.erase(map_c);
|
||||
known_cells.insert(c);
|
||||
std::swap(c, map_c);
|
||||
}
|
||||
duplicates.push_back({c.cell_index, map_c.cell_index});
|
||||
}
|
||||
}
|
||||
return {duplicates};
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
void initialize_queues(std::vector<ConcurrentQueue<T>> &queues, int size) {
|
||||
queues.reserve(size);
|
||||
for (int i = 0; i < size; ++i)
|
||||
queues.emplace_back(1);
|
||||
}
|
||||
|
||||
struct OptMergeWorker
|
||||
{
|
||||
int total_count;
|
||||
|
||||
OptMergeWorker(RTLIL::Module *module, const CellTypes &ct, bool mode_share_all, bool mode_keepdc) :
|
||||
total_count(0)
|
||||
{
|
||||
SigMap assign_map(module);
|
||||
FfInitVals initvals;
|
||||
initvals.set(&assign_map, module);
|
||||
|
||||
log("Finding identical cells in module `%s'.\n", module->name);
|
||||
assign_map.set(module);
|
||||
|
||||
initvals.set(&assign_map, module);
|
||||
// Use no more than one worker per thousand cells, rounded down, so
|
||||
// we only start multithreading with at least 2000 cells.
|
||||
int num_worker_threads = ThreadPool::pool_size(0, module->cells_size()/1000);
|
||||
int workers = std::max(1, num_worker_threads);
|
||||
|
||||
// The main thread doesn't do any work, so if there is only one worker thread,
|
||||
// just run everything on the main thread instead.
|
||||
// This avoids creating and waiting on a thread, which is pretty high overhead
|
||||
// for very small modules.
|
||||
if (num_worker_threads == 1)
|
||||
num_worker_threads = 0;
|
||||
OptMergeThreadWorker thread_worker(module, initvals, assign_map, ct, workers, mode_share_all, mode_keepdc);
|
||||
|
||||
std::vector<ConcurrentQueue<CellRange>> cell_ranges_queues(num_worker_threads);
|
||||
std::vector<ConcurrentQueue<CellHashes>> cell_hashes_queues(num_worker_threads);
|
||||
std::vector<ConcurrentQueue<Shards>> shards_queues(num_worker_threads);
|
||||
std::vector<ConcurrentQueue<FoundDuplicates>> duplicates_queues(num_worker_threads);
|
||||
|
||||
ThreadPool thread_pool(num_worker_threads, [&](int i) {
|
||||
while (std::optional<CellRange> c = cell_ranges_queues[i].pop_front()) {
|
||||
cell_hashes_queues[i].push_back(thread_worker.compute_cell_hashes(*c));
|
||||
std::optional<Shards> shards = shards_queues[i].pop_front();
|
||||
duplicates_queues[i].push_back(thread_worker.find_duplicate_cells(i, *shards));
|
||||
}
|
||||
});
|
||||
|
||||
bool did_something = true;
|
||||
// A cell may have to go through a lot of collisions if the hash
|
||||
|
|
@ -244,87 +399,99 @@ struct OptMergeWorker
|
|||
// beyond the user's control.
|
||||
while (did_something)
|
||||
{
|
||||
std::vector<RTLIL::Cell*> cells;
|
||||
cells.reserve(module->cells().size());
|
||||
for (auto cell : module->cells()) {
|
||||
if (!design->selected(module, cell))
|
||||
continue;
|
||||
if (cell->type.in(ID($meminit), ID($meminit_v2), ID($mem), ID($mem_v2))) {
|
||||
// Ignore those for performance: meminit can have an excessively large port,
|
||||
// mem can have an excessively large parameter holding the init data
|
||||
continue;
|
||||
}
|
||||
if (cell->type == ID($scopeinfo))
|
||||
continue;
|
||||
if (mode_keepdc && has_dont_care_initval(cell))
|
||||
continue;
|
||||
if (!cell->known())
|
||||
continue;
|
||||
if (!mode_share_all && !ct.cell_known(cell->type))
|
||||
continue;
|
||||
cells.push_back(cell);
|
||||
}
|
||||
int cells_size = module->cells_size();
|
||||
log("Computing hashes of %d cells of `%s'.\n", cells_size, module->name);
|
||||
std::vector<std::vector<std::vector<CellHash>>> sharded_bucketed_cell_hashes(workers);
|
||||
|
||||
did_something = false;
|
||||
|
||||
// We keep a set of known cells. They're hashed with our hash_cell_function
|
||||
// and compared with our compare_cell_parameters_and_connections.
|
||||
// Both need to capture OptMergeWorker to access initvals
|
||||
struct CellPtrHash {
|
||||
const OptMergeWorker& worker;
|
||||
CellPtrHash(const OptMergeWorker& w) : worker(w) {}
|
||||
std::size_t operator()(const Cell* c) const {
|
||||
return (std::size_t)worker.hash_cell_function(c, Hasher()).yield();
|
||||
}
|
||||
};
|
||||
struct CellPtrEqual {
|
||||
const OptMergeWorker& worker;
|
||||
CellPtrEqual(const OptMergeWorker& w) : worker(w) {}
|
||||
bool operator()(const Cell* lhs, const Cell* rhs) const {
|
||||
return worker.compare_cell_parameters_and_connections(lhs, rhs);
|
||||
}
|
||||
};
|
||||
std::unordered_set<
|
||||
RTLIL::Cell*,
|
||||
CellPtrHash,
|
||||
CellPtrEqual> known_cells (0, CellPtrHash(*this), CellPtrEqual(*this));
|
||||
|
||||
for (auto cell : cells)
|
||||
int cell_index = 0;
|
||||
int cells_size_mod_workers = cells_size % workers;
|
||||
{
|
||||
auto [cell_in_map, inserted] = known_cells.insert(cell);
|
||||
if (!inserted) {
|
||||
// We've failed to insert since we already have an equivalent cell
|
||||
Cell* other_cell = *cell_in_map;
|
||||
if (cell->has_keep_attr()) {
|
||||
if (other_cell->has_keep_attr())
|
||||
continue;
|
||||
known_cells.erase(other_cell);
|
||||
known_cells.insert(cell);
|
||||
std::swap(other_cell, cell);
|
||||
}
|
||||
|
||||
did_something = true;
|
||||
log_debug(" Cell `%s' is identical to cell `%s'.\n", cell->name, other_cell->name);
|
||||
for (auto &it : cell->connections()) {
|
||||
if (cell->output(it.first)) {
|
||||
RTLIL::SigSpec other_sig = other_cell->getPort(it.first);
|
||||
log_debug(" Redirecting output %s: %s = %s\n", it.first,
|
||||
log_signal(it.second), log_signal(other_sig));
|
||||
Const init = initvals(other_sig);
|
||||
initvals.remove_init(it.second);
|
||||
initvals.remove_init(other_sig);
|
||||
module->connect(RTLIL::SigSig(it.second, other_sig));
|
||||
assign_map.add(it.second, other_sig);
|
||||
initvals.set_init(other_sig, init);
|
||||
}
|
||||
}
|
||||
log_debug(" Removing %s cell `%s' from module `%s'.\n", cell->type, cell->name, module->name);
|
||||
module->remove(cell);
|
||||
total_count++;
|
||||
Multithreading multithreading;
|
||||
for (int i = 0; i < workers; ++i) {
|
||||
int num_cells = cells_size/workers + ((i < cells_size_mod_workers) ? 1 : 0);
|
||||
CellRange c = { cell_index, cell_index + num_cells };
|
||||
cell_index += num_cells;
|
||||
if (num_worker_threads > 0)
|
||||
cell_ranges_queues[i].push_back(c);
|
||||
else
|
||||
sharded_bucketed_cell_hashes[i] = std::move(thread_worker.compute_cell_hashes(c).bucketed_cell_hashes);
|
||||
}
|
||||
log_assert(cell_index == cells_size);
|
||||
if (num_worker_threads > 0)
|
||||
for (int i = 0; i < workers; ++i)
|
||||
sharded_bucketed_cell_hashes[i] = std::move(cell_hashes_queues[i].pop_front()->bucketed_cell_hashes);
|
||||
}
|
||||
|
||||
log("Finding duplicate cells in `%s'.\n", module->name);
|
||||
std::vector<DuplicateCell> merged_duplicates;
|
||||
{
|
||||
Multithreading multithreading;
|
||||
for (int i = 0; i < workers; ++i) {
|
||||
Shards thread_shards = { sharded_bucketed_cell_hashes };
|
||||
if (num_worker_threads > 0)
|
||||
shards_queues[i].push_back(thread_shards);
|
||||
else {
|
||||
std::vector<DuplicateCell> d = std::move(thread_worker.find_duplicate_cells(i, thread_shards).duplicates);
|
||||
merged_duplicates.insert(merged_duplicates.end(), d.begin(), d.end());
|
||||
}
|
||||
}
|
||||
if (num_worker_threads > 0)
|
||||
for (int i = 0; i < workers; ++i) {
|
||||
std::vector<DuplicateCell> d = std::move(duplicates_queues[i].pop_front()->duplicates);
|
||||
merged_duplicates.insert(merged_duplicates.end(), d.begin(), d.end());
|
||||
}
|
||||
}
|
||||
std::sort(merged_duplicates.begin(), merged_duplicates.end(), [](const DuplicateCell &lhs, const DuplicateCell &rhs) {
|
||||
// Sort them by the order in which duplicates would have been detected in a single-threaded
|
||||
// run. The cell at which the duplicate would have been detected is the latter of the two
|
||||
// cells involved.
|
||||
return std::max(lhs.remove_cell, lhs.keep_cell) < std::max(rhs.remove_cell, rhs.keep_cell);
|
||||
});
|
||||
|
||||
// Convert to cell pointers because removing cells will invalidate the indices.
|
||||
std::vector<std::pair<RTLIL::Cell*, RTLIL::Cell*>> cell_ptrs;
|
||||
for (DuplicateCell dup : merged_duplicates)
|
||||
cell_ptrs.push_back({module->cell_at(dup.remove_cell), module->cell_at(dup.keep_cell)});
|
||||
|
||||
for (auto [remove_cell, keep_cell] : cell_ptrs)
|
||||
{
|
||||
log_debug(" Cell `%s' is identical to cell `%s'.\n", remove_cell->name, keep_cell->name);
|
||||
for (auto &it : remove_cell->connections()) {
|
||||
if (remove_cell->output(it.first)) {
|
||||
RTLIL::SigSpec keep_sig = keep_cell->getPort(it.first);
|
||||
log_debug(" Redirecting output %s: %s = %s\n", it.first,
|
||||
log_signal(it.second), log_signal(keep_sig));
|
||||
Const init = initvals(keep_sig);
|
||||
initvals.remove_init(it.second);
|
||||
initvals.remove_init(keep_sig);
|
||||
module->connect(RTLIL::SigSig(it.second, keep_sig));
|
||||
auto keep_sig_it = keep_sig.begin();
|
||||
for (SigBit remove_sig_bit : it.second) {
|
||||
assign_map.add(remove_sig_bit, *keep_sig_it);
|
||||
++keep_sig_it;
|
||||
}
|
||||
initvals.set_init(keep_sig, init);
|
||||
}
|
||||
}
|
||||
log_debug(" Removing %s cell `%s' from module `%s'.\n", remove_cell->type, remove_cell->name, module->name);
|
||||
module->remove(remove_cell);
|
||||
total_count++;
|
||||
}
|
||||
did_something = !merged_duplicates.empty();
|
||||
}
|
||||
|
||||
for (ConcurrentQueue<CellRange> &q : cell_ranges_queues)
|
||||
q.close();
|
||||
|
||||
for (ConcurrentQueue<Shards> &q : shards_queues)
|
||||
q.close();
|
||||
|
||||
for (ConcurrentQueue<CellRange> &q : cell_ranges_queues)
|
||||
q.close();
|
||||
|
||||
for (ConcurrentQueue<Shards> &q : shards_queues)
|
||||
q.close();
|
||||
|
||||
log_suppressed();
|
||||
}
|
||||
};
|
||||
|
|
@ -377,9 +544,25 @@ struct OptMergePass : public Pass {
|
|||
}
|
||||
extra_args(args, argidx, design);
|
||||
|
||||
CellTypes ct;
|
||||
ct.setup_internals();
|
||||
ct.setup_internals_mem();
|
||||
ct.setup_stdcells();
|
||||
ct.setup_stdcells_mem();
|
||||
if (mode_nomux) {
|
||||
ct.cell_types.erase(ID($mux));
|
||||
ct.cell_types.erase(ID($pmux));
|
||||
}
|
||||
ct.cell_types.erase(ID($tribuf));
|
||||
ct.cell_types.erase(ID($_TBUF_));
|
||||
ct.cell_types.erase(ID($anyseq));
|
||||
ct.cell_types.erase(ID($anyconst));
|
||||
ct.cell_types.erase(ID($allseq));
|
||||
ct.cell_types.erase(ID($allconst));
|
||||
|
||||
int total_count = 0;
|
||||
for (auto module : design->selected_modules()) {
|
||||
OptMergeWorker worker(design, module, mode_nomux, mode_share_all, mode_keepdc);
|
||||
OptMergeWorker worker(module, ct, mode_share_all, mode_keepdc);
|
||||
total_count += worker.total_count;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ struct OptMuxtreeWorker
|
|||
RTLIL::Module *module;
|
||||
SigMap assign_map;
|
||||
int removed_count;
|
||||
int glob_evals_left = 10000000;
|
||||
int glob_evals_left = 10'000'000;
|
||||
|
||||
struct bitinfo_t {
|
||||
// Is bit directly used by non-mux cells or ports?
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
#include "kernel/modtools.h"
|
||||
#include "kernel/utils.h"
|
||||
#include "kernel/macc.h"
|
||||
#include "kernel/newcelltypes.h"
|
||||
#include <iterator>
|
||||
|
||||
USING_YOSYS_NAMESPACE
|
||||
|
|
@ -38,19 +39,18 @@ struct ShareWorkerConfig
|
|||
bool opt_force;
|
||||
bool opt_aggressive;
|
||||
bool opt_fast;
|
||||
pool<RTLIL::IdString> generic_uni_ops, generic_bin_ops, generic_cbin_ops, generic_other_ops;
|
||||
StaticCellTypes::Categories::Category generic_uni_ops, generic_bin_ops, generic_cbin_ops, generic_other_ops;
|
||||
};
|
||||
|
||||
struct ShareWorker
|
||||
{
|
||||
const ShareWorkerConfig config;
|
||||
int limit;
|
||||
pool<RTLIL::IdString> generic_ops;
|
||||
StaticCellTypes::Categories::Category generic_ops;
|
||||
|
||||
RTLIL::Design *design;
|
||||
RTLIL::Module *module;
|
||||
|
||||
CellTypes fwd_ct, cone_ct;
|
||||
ModWalker modwalker;
|
||||
|
||||
pool<RTLIL::Cell*> cells_to_remove;
|
||||
|
|
@ -75,7 +75,7 @@ struct ShareWorker
|
|||
queue_bits.insert(modwalker.signal_outputs.begin(), modwalker.signal_outputs.end());
|
||||
|
||||
for (auto &it : module->cells_)
|
||||
if (!fwd_ct.cell_known(it.second->type)) {
|
||||
if (!StaticCellTypes::Compat::internals_nomem_noff(it.second->type)) {
|
||||
pool<RTLIL::SigBit> &bits = modwalker.cell_inputs[it.second];
|
||||
queue_bits.insert(bits.begin(), bits.end());
|
||||
}
|
||||
|
|
@ -95,7 +95,7 @@ struct ShareWorker
|
|||
queue_bits.insert(bits.begin(), bits.end());
|
||||
visited_cells.insert(pbit.cell);
|
||||
}
|
||||
if (fwd_ct.cell_known(pbit.cell->type) && visited_cells.count(pbit.cell) == 0) {
|
||||
if (StaticCellTypes::Compat::internals_nomem_noff(pbit.cell->type) && visited_cells.count(pbit.cell) == 0) {
|
||||
pool<RTLIL::SigBit> &bits = modwalker.cell_inputs[pbit.cell];
|
||||
terminal_bits.insert(bits.begin(), bits.end());
|
||||
queue_bits.insert(bits.begin(), bits.end());
|
||||
|
|
@ -388,7 +388,7 @@ struct ShareWorker
|
|||
continue;
|
||||
}
|
||||
|
||||
if (generic_ops.count(cell->type)) {
|
||||
if (generic_ops(cell->type)) {
|
||||
if (config.opt_aggressive)
|
||||
shareable_cells.insert(cell);
|
||||
continue;
|
||||
|
|
@ -412,7 +412,7 @@ struct ShareWorker
|
|||
return true;
|
||||
}
|
||||
|
||||
if (config.generic_uni_ops.count(c1->type))
|
||||
if (config.generic_uni_ops(c1->type))
|
||||
{
|
||||
if (!config.opt_aggressive)
|
||||
{
|
||||
|
|
@ -429,7 +429,7 @@ struct ShareWorker
|
|||
return true;
|
||||
}
|
||||
|
||||
if (config.generic_bin_ops.count(c1->type) || c1->type == ID($alu))
|
||||
if (config.generic_bin_ops(c1->type) || c1->type == ID($alu))
|
||||
{
|
||||
if (!config.opt_aggressive)
|
||||
{
|
||||
|
|
@ -449,7 +449,7 @@ struct ShareWorker
|
|||
return true;
|
||||
}
|
||||
|
||||
if (config.generic_cbin_ops.count(c1->type))
|
||||
if (config.generic_cbin_ops(c1->type))
|
||||
{
|
||||
if (!config.opt_aggressive)
|
||||
{
|
||||
|
|
@ -511,7 +511,7 @@ struct ShareWorker
|
|||
{
|
||||
log_assert(c1->type == c2->type);
|
||||
|
||||
if (config.generic_uni_ops.count(c1->type))
|
||||
if (config.generic_uni_ops(c1->type))
|
||||
{
|
||||
if (c1->parameters.at(ID::A_SIGNED).as_bool() != c2->parameters.at(ID::A_SIGNED).as_bool())
|
||||
{
|
||||
|
|
@ -560,11 +560,11 @@ struct ShareWorker
|
|||
return supercell;
|
||||
}
|
||||
|
||||
if (config.generic_bin_ops.count(c1->type) || config.generic_cbin_ops.count(c1->type) || c1->type == ID($alu))
|
||||
if (config.generic_bin_ops(c1->type) || config.generic_cbin_ops(c1->type) || c1->type == ID($alu))
|
||||
{
|
||||
bool modified_src_cells = false;
|
||||
|
||||
if (config.generic_cbin_ops.count(c1->type))
|
||||
if (config.generic_cbin_ops(c1->type))
|
||||
{
|
||||
int score_unflipped = max(c1->parameters.at(ID::A_WIDTH).as_int(), c2->parameters.at(ID::A_WIDTH).as_int()) +
|
||||
max(c1->parameters.at(ID::B_WIDTH).as_int(), c2->parameters.at(ID::B_WIDTH).as_int());
|
||||
|
|
@ -758,7 +758,7 @@ struct ShareWorker
|
|||
recursion_state.insert(cell);
|
||||
|
||||
for (auto c : consumer_cells)
|
||||
if (fwd_ct.cell_known(c->type)) {
|
||||
if (StaticCellTypes::Compat::internals_nomem_noff(c->type)) {
|
||||
const pool<RTLIL::SigBit> &bits = find_forbidden_controls(c);
|
||||
forbidden_controls_cache[cell].insert(bits.begin(), bits.end());
|
||||
}
|
||||
|
|
@ -897,7 +897,7 @@ struct ShareWorker
|
|||
return activation_patterns_cache.at(cell);
|
||||
}
|
||||
for (auto &pbit : modwalker.signal_consumers[bit]) {
|
||||
log_assert(fwd_ct.cell_known(pbit.cell->type));
|
||||
log_assert(StaticCellTypes::Compat::internals_nomem_noff(pbit.cell->type));
|
||||
if ((pbit.cell->type == ID($mux) || pbit.cell->type == ID($pmux)) && (pbit.port == ID::A || pbit.port == ID::B))
|
||||
driven_data_muxes.insert(pbit.cell);
|
||||
else
|
||||
|
|
@ -1214,24 +1214,10 @@ struct ShareWorker
|
|||
ShareWorker(ShareWorkerConfig config, RTLIL::Design* design) :
|
||||
config(config), design(design), modwalker(design)
|
||||
{
|
||||
generic_ops.insert(config.generic_uni_ops.begin(), config.generic_uni_ops.end());
|
||||
generic_ops.insert(config.generic_bin_ops.begin(), config.generic_bin_ops.end());
|
||||
generic_ops.insert(config.generic_cbin_ops.begin(), config.generic_cbin_ops.end());
|
||||
generic_ops.insert(config.generic_other_ops.begin(), config.generic_other_ops.end());
|
||||
|
||||
fwd_ct.setup_internals();
|
||||
|
||||
cone_ct.setup_internals();
|
||||
cone_ct.cell_types.erase(ID($mul));
|
||||
cone_ct.cell_types.erase(ID($mod));
|
||||
cone_ct.cell_types.erase(ID($div));
|
||||
cone_ct.cell_types.erase(ID($modfloor));
|
||||
cone_ct.cell_types.erase(ID($divfloor));
|
||||
cone_ct.cell_types.erase(ID($pow));
|
||||
cone_ct.cell_types.erase(ID($shl));
|
||||
cone_ct.cell_types.erase(ID($shr));
|
||||
cone_ct.cell_types.erase(ID($sshl));
|
||||
cone_ct.cell_types.erase(ID($sshr));
|
||||
generic_ops = StaticCellTypes::Categories::join(generic_ops, config.generic_uni_ops);
|
||||
generic_ops = StaticCellTypes::Categories::join(generic_ops, config.generic_bin_ops);
|
||||
generic_ops = StaticCellTypes::Categories::join(generic_ops, config.generic_cbin_ops);
|
||||
generic_ops = StaticCellTypes::Categories::join(generic_ops, config.generic_other_ops);
|
||||
}
|
||||
|
||||
void operator()(RTLIL::Module *module) {
|
||||
|
|
@ -1561,45 +1547,45 @@ struct SharePass : public Pass {
|
|||
config.opt_aggressive = false;
|
||||
config.opt_fast = false;
|
||||
|
||||
config.generic_uni_ops.insert(ID($not));
|
||||
// config.generic_uni_ops.insert(ID($pos));
|
||||
config.generic_uni_ops.insert(ID($neg));
|
||||
config.generic_uni_ops.set_id(ID($not));
|
||||
// config.generic_uni_ops.set_id(ID($pos));
|
||||
config.generic_uni_ops.set_id(ID($neg));
|
||||
|
||||
config.generic_cbin_ops.insert(ID($and));
|
||||
config.generic_cbin_ops.insert(ID($or));
|
||||
config.generic_cbin_ops.insert(ID($xor));
|
||||
config.generic_cbin_ops.insert(ID($xnor));
|
||||
config.generic_cbin_ops.set_id(ID($and));
|
||||
config.generic_cbin_ops.set_id(ID($or));
|
||||
config.generic_cbin_ops.set_id(ID($xor));
|
||||
config.generic_cbin_ops.set_id(ID($xnor));
|
||||
|
||||
config.generic_bin_ops.insert(ID($shl));
|
||||
config.generic_bin_ops.insert(ID($shr));
|
||||
config.generic_bin_ops.insert(ID($sshl));
|
||||
config.generic_bin_ops.insert(ID($sshr));
|
||||
config.generic_bin_ops.set_id(ID($shl));
|
||||
config.generic_bin_ops.set_id(ID($shr));
|
||||
config.generic_bin_ops.set_id(ID($sshl));
|
||||
config.generic_bin_ops.set_id(ID($sshr));
|
||||
|
||||
config.generic_bin_ops.insert(ID($lt));
|
||||
config.generic_bin_ops.insert(ID($le));
|
||||
config.generic_bin_ops.insert(ID($eq));
|
||||
config.generic_bin_ops.insert(ID($ne));
|
||||
config.generic_bin_ops.insert(ID($eqx));
|
||||
config.generic_bin_ops.insert(ID($nex));
|
||||
config.generic_bin_ops.insert(ID($ge));
|
||||
config.generic_bin_ops.insert(ID($gt));
|
||||
config.generic_bin_ops.set_id(ID($lt));
|
||||
config.generic_bin_ops.set_id(ID($le));
|
||||
config.generic_bin_ops.set_id(ID($eq));
|
||||
config.generic_bin_ops.set_id(ID($ne));
|
||||
config.generic_bin_ops.set_id(ID($eqx));
|
||||
config.generic_bin_ops.set_id(ID($nex));
|
||||
config.generic_bin_ops.set_id(ID($ge));
|
||||
config.generic_bin_ops.set_id(ID($gt));
|
||||
|
||||
config.generic_cbin_ops.insert(ID($add));
|
||||
config.generic_cbin_ops.insert(ID($mul));
|
||||
config.generic_cbin_ops.set_id(ID($add));
|
||||
config.generic_cbin_ops.set_id(ID($mul));
|
||||
|
||||
config.generic_bin_ops.insert(ID($sub));
|
||||
config.generic_bin_ops.insert(ID($div));
|
||||
config.generic_bin_ops.insert(ID($mod));
|
||||
config.generic_bin_ops.insert(ID($divfloor));
|
||||
config.generic_bin_ops.insert(ID($modfloor));
|
||||
// config.generic_bin_ops.insert(ID($pow));
|
||||
config.generic_bin_ops.set_id(ID($sub));
|
||||
config.generic_bin_ops.set_id(ID($div));
|
||||
config.generic_bin_ops.set_id(ID($mod));
|
||||
config.generic_bin_ops.set_id(ID($divfloor));
|
||||
config.generic_bin_ops.set_id(ID($modfloor));
|
||||
// config.generic_bin_ops.set_id(ID($pow));
|
||||
|
||||
config.generic_uni_ops.insert(ID($logic_not));
|
||||
config.generic_cbin_ops.insert(ID($logic_and));
|
||||
config.generic_cbin_ops.insert(ID($logic_or));
|
||||
config.generic_uni_ops.set_id(ID($logic_not));
|
||||
config.generic_cbin_ops.set_id(ID($logic_and));
|
||||
config.generic_cbin_ops.set_id(ID($logic_or));
|
||||
|
||||
config.generic_other_ops.insert(ID($alu));
|
||||
config.generic_other_ops.insert(ID($macc));
|
||||
config.generic_other_ops.set_id(ID($alu));
|
||||
config.generic_other_ops.set_id(ID($macc));
|
||||
|
||||
log_header(design, "Executing SHARE pass (SAT-based resource sharing).\n");
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue