mirror of
https://github.com/YosysHQ/yosys
synced 2025-08-02 01:13:19 +00:00
Merge upstream
This commit is contained in:
parent
12137c7ac4
commit
881080a827
43 changed files with 510 additions and 94 deletions
1
passes/opt/.gitignore
vendored
Normal file
1
passes/opt/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/peepopt*_pm.h
|
|
@ -12,7 +12,6 @@ OBJS += passes/opt/opt_share.o
|
|||
OBJS += passes/opt/opt_clean.o
|
||||
OBJS += passes/opt/opt_expr.o
|
||||
|
||||
ifneq ($(SMALL),1)
|
||||
OBJS += passes/opt/share.o
|
||||
OBJS += passes/opt/wreduce.o
|
||||
OBJS += passes/opt/opt_demorgan.o
|
||||
|
@ -22,4 +21,19 @@ OBJS += passes/opt/opt_lut_ins.o
|
|||
OBJS += passes/opt/opt_ffinv.o
|
||||
OBJS += passes/opt/pmux2shiftx.o
|
||||
OBJS += passes/opt/muxpack.o
|
||||
endif
|
||||
|
||||
OBJS += passes/opt/peepopt.o
|
||||
GENFILES += passes/opt/peepopt_pm.h
|
||||
passes/opt/peepopt.o: passes/opt/peepopt_pm.h
|
||||
$(eval $(call add_extra_objs,passes/opt/peepopt_pm.h))
|
||||
|
||||
PEEPOPT_PATTERN = passes/opt/peepopt_shiftmul_right.pmg
|
||||
PEEPOPT_PATTERN += passes/opt/peepopt_shiftmul_left.pmg
|
||||
PEEPOPT_PATTERN += passes/opt/peepopt_shiftadd.pmg
|
||||
PEEPOPT_PATTERN += passes/opt/peepopt_muldiv.pmg
|
||||
PEEPOPT_PATTERN += passes/opt/peepopt_muldiv_c.pmg
|
||||
PEEPOPT_PATTERN += passes/opt/peepopt_muxadd.pmg
|
||||
PEEPOPT_PATTERN += passes/opt/peepopt_formal_clockgateff.pmg
|
||||
|
||||
passes/opt/peepopt_pm.h: passes/pmgen/pmgen.py $(PEEPOPT_PATTERN)
|
||||
$(P) mkdir -p $(dir $@) && $(PYTHON_EXECUTABLE) $< -o $@ -p peepopt $(filter-out $<,$^)
|
||||
|
|
139
passes/opt/peepopt.cc
Normal file
139
passes/opt/peepopt.cc
Normal file
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "kernel/yosys.h"
|
||||
#include "kernel/sigtools.h"
|
||||
|
||||
USING_YOSYS_NAMESPACE
|
||||
PRIVATE_NAMESPACE_BEGIN
|
||||
|
||||
bool did_something;
|
||||
|
||||
// scratchpad configurations for pmgen
|
||||
int shiftadd_max_ratio;
|
||||
|
||||
// Helper function, removes LSB 0s
|
||||
SigSpec remove_bottom_padding(SigSpec sig);
|
||||
|
||||
#include "passes/opt/peepopt_pm.h"
|
||||
|
||||
struct PeepoptPass : public Pass {
|
||||
PeepoptPass() : Pass("peepopt", "collection of peephole optimizers") { }
|
||||
void help() override
|
||||
{
|
||||
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
|
||||
log("\n");
|
||||
log(" peepopt [options] [selection]\n");
|
||||
log("\n");
|
||||
log("This pass applies a collection of peephole optimizers to the current design.\n");
|
||||
log("\n");
|
||||
log("This pass employs the following rules by default:\n");
|
||||
log("\n");
|
||||
log(" * muldiv - Replace (A*B)/B with A\n");
|
||||
log("\n");
|
||||
log(" * muldiv_c - Replace (A*B)/C with A*(B/C) when C is a const divisible by B.\n");
|
||||
log("\n");
|
||||
log(" * shiftmul - Replace A>>(B*C) with A'>>(B<<K) where C and K are constants\n");
|
||||
log(" and A' is derived from A by appropriately inserting padding\n");
|
||||
log(" into the signal. (right variant)\n");
|
||||
log("\n");
|
||||
log(" Analogously, replace A<<(B*C) with appropriate selection of\n");
|
||||
log(" output bits from A<<(B<<K). (left variant)\n");
|
||||
log("\n");
|
||||
log(" * shiftadd - Replace A>>(B+D) with (A'>>D)>>(B) where D is constant and\n");
|
||||
log(" A' is derived from A by padding or cutting inaccessible bits.\n");
|
||||
log(" Scratchpad: 'peepopt.shiftadd.max_data_multiple' (default: 2)\n");
|
||||
log(" limits the amount of padding to a multiple of the data, \n");
|
||||
log(" to avoid high resource usage from large temporary MUX trees.\n");
|
||||
log("\n");
|
||||
log("If -formalclk is specified it instead employs the following rules:\n");
|
||||
log("\n");
|
||||
log(" * clockgateff - Replace latch based clock gating patterns with a flip-flop\n");
|
||||
log(" based pattern to prevent combinational paths from the\n");
|
||||
log(" output to the enable input after running clk2fflogic.\n");
|
||||
log("\n");
|
||||
log("If -withmuxadd is specified it adds the following rule:\n");
|
||||
log("\n");
|
||||
log(" * muxadd - Replace S?(A+B):A with A+(S?B:0)\n");
|
||||
log("\n");
|
||||
}
|
||||
void execute(std::vector<std::string> args, RTLIL::Design *design) override
|
||||
{
|
||||
log_header(design, "Executing PEEPOPT pass (run peephole optimizers).\n");
|
||||
|
||||
bool formalclk = false;
|
||||
bool withmuxadd = false;
|
||||
size_t argidx;
|
||||
for (argidx = 1; argidx < args.size(); argidx++)
|
||||
{
|
||||
if (args[argidx] == "-formalclk") {
|
||||
formalclk = true;
|
||||
continue;
|
||||
}
|
||||
if (args[argidx] == "-withmuxadd") {
|
||||
withmuxadd = true;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
extra_args(args, argidx, design);
|
||||
|
||||
// limit the padding from shiftadd to a multiple of the input data
|
||||
// during techmap it creates (#data + #padding) * log(shift) $_MUX_ cells
|
||||
// 2x implies there is a constant shift larger than the input-data which should be extremely rare
|
||||
shiftadd_max_ratio = design->scratchpad_get_int("peepopt.shiftadd.max_data_multiple", 2);
|
||||
|
||||
for (auto module : design->selected_modules())
|
||||
{
|
||||
did_something = true;
|
||||
|
||||
while (did_something)
|
||||
{
|
||||
did_something = false;
|
||||
|
||||
peepopt_pm pm(module);
|
||||
|
||||
pm.setup(module->selected_cells());
|
||||
|
||||
if (formalclk) {
|
||||
pm.run_formal_clockgateff();
|
||||
} else {
|
||||
pm.run_shiftadd();
|
||||
pm.run_shiftmul_right();
|
||||
pm.run_shiftmul_left();
|
||||
pm.run_muldiv();
|
||||
pm.run_muldiv_c();
|
||||
if (withmuxadd)
|
||||
pm.run_muxadd();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} PeepoptPass;
|
||||
|
||||
|
||||
SigSpec remove_bottom_padding(SigSpec sig)
|
||||
{
|
||||
int i = 0;
|
||||
for (; i < sig.size() - 1 && sig[i] == State::S0; i++) {
|
||||
}
|
||||
return sig.extract(i, sig.size() - i);
|
||||
}
|
||||
|
||||
PRIVATE_NAMESPACE_END
|
59
passes/opt/peepopt_formal_clockgateff.pmg
Normal file
59
passes/opt/peepopt_formal_clockgateff.pmg
Normal file
|
@ -0,0 +1,59 @@
|
|||
pattern formal_clockgateff
|
||||
|
||||
// Detects the most common clock gating pattern using a latch and replaces it
|
||||
// with a functionally equivalent pattern based on a flip-flop. The latch
|
||||
// based pattern has a combinational path from the enable input to output after
|
||||
// clk2fflogic, but this is a stable loop and the flip-flop based pattern does
|
||||
// not exhibit this.
|
||||
//
|
||||
// This optimization is suitable for formal to prevent false comb loops, but
|
||||
// should not be used for synthesis where the latch is an intentional choice
|
||||
//
|
||||
// Latch style:
|
||||
// always @* if (!clk_i) latched_en = en;
|
||||
// assign gated_clk_o = latched_en & clk_i;
|
||||
//
|
||||
// Flip-flop style:
|
||||
// always @(posedge clk) flopped_en <= en;
|
||||
// assign gated_clk_o = flopped_en & clk_i;
|
||||
|
||||
state <SigSpec> clk en latched_en gated_clk
|
||||
state <IdString> latched_en_port_name
|
||||
|
||||
match latch
|
||||
select latch->type == $dlatch
|
||||
select param(latch, \WIDTH) == 1
|
||||
select param(latch, \EN_POLARITY).as_bool() == false
|
||||
set clk port(latch, \EN)
|
||||
set en port(latch, \D)
|
||||
set latched_en port(latch, \Q)
|
||||
endmatch
|
||||
|
||||
match and_gate
|
||||
select and_gate->type.in($and, $logic_and)
|
||||
select param(and_gate, \A_WIDTH) == 1
|
||||
select param(and_gate, \B_WIDTH) == 1
|
||||
select param(and_gate, \Y_WIDTH) == 1
|
||||
choice <IdString> clk_port {\A, \B}
|
||||
define <IdString> latch_port {clk_port == \A ? \B : \A}
|
||||
index <SigSpec> port(and_gate, clk_port) === clk
|
||||
index <SigSpec> port(and_gate, latch_port) === latched_en
|
||||
set gated_clk port(and_gate, \Y)
|
||||
set latched_en_port_name latch_port
|
||||
endmatch
|
||||
|
||||
code
|
||||
log("replacing clock gate pattern in %s with ff: latch=%s, and=%s\n",
|
||||
log_id(module), log_id(latch), log_id(and_gate));
|
||||
|
||||
// Add a flip-flop and rewire the AND gate to use the output of this flop
|
||||
// instead of the latch. We don't delete the latch in case its output is
|
||||
// used to drive other nodes. If it isn't, it will be trivially removed by
|
||||
// clean
|
||||
SigSpec flopped_en = module->addWire(NEW_ID);
|
||||
module->addDff(NEW_ID, clk, en, flopped_en, true, latch->get_src_attribute());
|
||||
and_gate->setPort(latched_en_port_name, flopped_en);
|
||||
did_something = true;
|
||||
|
||||
accept;
|
||||
endcode
|
39
passes/opt/peepopt_muldiv.pmg
Normal file
39
passes/opt/peepopt_muldiv.pmg
Normal file
|
@ -0,0 +1,39 @@
|
|||
pattern muldiv
|
||||
|
||||
state <SigSpec> t x y
|
||||
state <bool> is_signed
|
||||
|
||||
match mul
|
||||
select mul->type == $mul
|
||||
select GetSize(port(mul, \A)) + GetSize(port(mul, \B)) <= GetSize(port(mul, \Y))
|
||||
endmatch
|
||||
|
||||
code t x y is_signed
|
||||
t = port(mul, \Y);
|
||||
x = port(mul, \A);
|
||||
y = port(mul, \B);
|
||||
is_signed = param(mul, \A_SIGNED).as_bool();
|
||||
branch;
|
||||
std::swap(x, y);
|
||||
endcode
|
||||
|
||||
match div
|
||||
select div->type.in($div)
|
||||
index <SigSpec> port(div, \A) === t
|
||||
index <SigSpec> port(div, \B) === x
|
||||
filter param(div, \A_SIGNED).as_bool() == is_signed
|
||||
endmatch
|
||||
|
||||
code
|
||||
SigSpec div_y = port(div, \Y);
|
||||
SigSpec val_y = y;
|
||||
|
||||
if (GetSize(div_y) != GetSize(val_y))
|
||||
val_y.extend_u0(GetSize(div_y), param(div, \A_SIGNED).as_bool());
|
||||
|
||||
did_something = true;
|
||||
log("muldiv pattern in %s: mul=%s, div=%s\n", log_id(module), log_id(mul), log_id(div));
|
||||
module->connect(div_y, val_y);
|
||||
autoremove(div);
|
||||
accept;
|
||||
endcode
|
125
passes/opt/peepopt_muldiv_c.pmg
Normal file
125
passes/opt/peepopt_muldiv_c.pmg
Normal file
|
@ -0,0 +1,125 @@
|
|||
pattern muldiv_c
|
||||
//
|
||||
// Authored by Akash Levy and Alain Dargelas of Silimate, Inc. under ISC license.
|
||||
// Transforms mul->div into const->mul when b and c are divisible constants:
|
||||
// y = (a * b_const) / c_const ===> a * eval(b_const / c_const)
|
||||
//
|
||||
|
||||
state <SigSpec> a b_const mul_y
|
||||
|
||||
match mul
|
||||
// Select multiplier
|
||||
select mul->type == $mul
|
||||
endmatch
|
||||
|
||||
code a b_const mul_y
|
||||
// Get multiplier signals
|
||||
a = port(mul, \A);
|
||||
b_const = port(mul, \B);
|
||||
mul_y = port(mul, \Y);
|
||||
|
||||
// Fanout of each multiplier Y bit should be 1 (no bit-split)
|
||||
if (nusers(mul_y) != 2)
|
||||
reject;
|
||||
|
||||
// A and B can be interchanged
|
||||
branch;
|
||||
std::swap(a, b_const);
|
||||
endcode
|
||||
|
||||
match div
|
||||
// Select div of form (a * b_const) / c_const
|
||||
select div->type == $div
|
||||
|
||||
// Check that b_const and c_const is constant
|
||||
filter b_const.is_fully_const()
|
||||
filter port(div, \B).is_fully_const()
|
||||
index <SigSpec> remove_bottom_padding(port(div, \A)) === mul_y
|
||||
endmatch
|
||||
|
||||
code
|
||||
// Get div signals
|
||||
SigSpec div_a = port(div, \A);
|
||||
SigSpec c_const = port(div, \B);
|
||||
SigSpec div_y = port(div, \Y);
|
||||
|
||||
// Get offset of multiplier result chunk in divider
|
||||
int offset = GetSize(div_a) - GetSize(mul_y);
|
||||
|
||||
// Get properties and values of b_const and c_const
|
||||
// b_const may be coming from the A port
|
||||
// But it is an RTLIL invariant that A_SIGNED equals B_SIGNED
|
||||
bool b_const_signed = mul->getParam(ID::B_SIGNED).as_bool();
|
||||
bool c_const_signed = div->getParam(ID::B_SIGNED).as_bool();
|
||||
int b_const_int = b_const.as_int(b_const_signed);
|
||||
int c_const_int = c_const.as_int(c_const_signed);
|
||||
int b_const_int_shifted = b_const_int << offset;
|
||||
|
||||
// Helper lambdas for two's complement math
|
||||
auto sign2sComplement = [](auto value, int numBits) {
|
||||
if (value & (1 << (numBits - 1))) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
auto twosComplement = [](auto value, int numBits) {
|
||||
if (value & (1 << (numBits - 1))) {
|
||||
return (~value) + 1; // invert bits before adding 1
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
// Two's complement conversion
|
||||
if (b_const_signed)
|
||||
b_const_int = sign2sComplement(b_const_int, GetSize(b_const)) * twosComplement(b_const_int, GetSize(b_const));
|
||||
if (c_const_signed)
|
||||
c_const_int = sign2sComplement(c_const_int, GetSize(c_const)) * twosComplement(c_const_int, GetSize(c_const));
|
||||
// Calculate the constant and compress the width to fit the value
|
||||
Const const_ratio;
|
||||
Const b_const_actual;
|
||||
// Avoid division by zero
|
||||
if (c_const_int == 0)
|
||||
reject;
|
||||
b_const_actual = b_const_int_shifted;
|
||||
b_const_actual.compress(b_const_signed);
|
||||
|
||||
const_ratio = b_const_int_shifted / c_const_int;
|
||||
const_ratio.compress(b_const_signed | c_const_signed);
|
||||
|
||||
// Integer values should be lesser than 32 bits
|
||||
// This is because we are using C++ types, and int is 32 bits
|
||||
// FIXME: use long long or BigInteger to make pass work with >32 bits
|
||||
if (GetSize(mul->getParam(ID::B_WIDTH)) > 32)
|
||||
reject;
|
||||
if (GetSize(b_const) > 32)
|
||||
reject;
|
||||
if (GetSize(c_const) + offset > 32)
|
||||
reject;
|
||||
|
||||
// Check for potential multiplier overflow
|
||||
if (GetSize(b_const_actual) + GetSize(a) > GetSize(mul_y))
|
||||
reject;
|
||||
|
||||
// Check that there are only zeros before offset
|
||||
if (offset < 0 || !div_a.extract(0, offset).is_fully_zero())
|
||||
reject;
|
||||
|
||||
// Check that b is divisible by c
|
||||
if (b_const_int_shifted % c_const_int != 0)
|
||||
reject;
|
||||
|
||||
// Rewire to only keep multiplier
|
||||
mul->setPort(\A, a);
|
||||
mul->setPort(\B, const_ratio);
|
||||
mul->setPort(\Y, div_y);
|
||||
|
||||
// Remove divider
|
||||
autoremove(div);
|
||||
|
||||
// Log, fixup, accept
|
||||
log("muldiv_const pattern in %s: mul=%s, div=%s\n", log_id(module), log_id(mul), log_id(div));
|
||||
mul->fixup_parameters();
|
||||
accept;
|
||||
endcode
|
128
passes/opt/peepopt_muxadd.pmg
Normal file
128
passes/opt/peepopt_muxadd.pmg
Normal file
|
@ -0,0 +1,128 @@
|
|||
pattern muxadd
|
||||
//
|
||||
// Authored by Akash Levy and Alain Dargelas of Silimate, Inc. under ISC license.
|
||||
// Transforms add->mux into mux->add:
|
||||
// y = s ? (a + b) : a ===> y = a + (s ? b : 0)
|
||||
// or
|
||||
// y = s ? a : (a + b) ===> y = a + (s ? 0 : b)
|
||||
|
||||
state <SigSpec> add_a add_b add_y add_a_ext mux_a mux_b mux_y
|
||||
state <Const> add_a_signed
|
||||
state <IdString> add_a_id add_b_id mux_a_id mux_b_id
|
||||
|
||||
match add
|
||||
// Select adder
|
||||
select add->type == $add
|
||||
|
||||
// Set ports, allowing A and B to be swapped
|
||||
choice <IdString> A {\A, \B}
|
||||
define <IdString> B (A == \A ? \B : \A)
|
||||
set add_a port(add, A)
|
||||
set add_b port(add, B)
|
||||
set add_y port(add, \Y)
|
||||
|
||||
// Get signedness
|
||||
set add_a_signed param(add, (A == \A) ? \A_SIGNED : \B_SIGNED)
|
||||
|
||||
// Choice ids
|
||||
set add_a_id A
|
||||
set add_b_id B
|
||||
endmatch
|
||||
|
||||
code add_y add_a add_b add_a_ext
|
||||
// Get adder signals
|
||||
add_a_ext = SigSpec(port(add, add_a_id));
|
||||
add_a_ext.extend_u0(GetSize(add_y), add_a_signed.as_bool());
|
||||
|
||||
// Fanout of each adder Y bit should be 1 (no bit-split)
|
||||
if (nusers(add_y) != 2)
|
||||
reject;
|
||||
endcode
|
||||
|
||||
match mux
|
||||
// Select mux of form: s ? (a + b) : a
|
||||
// Allow leading 0s when A_WIDTH != Y_WIDTH or s ? a : (a + b)
|
||||
select mux->type == $mux
|
||||
choice <IdString> AB {\A, \B}
|
||||
define <IdString> BA (AB == \A ? \B : \A)
|
||||
set mux_y port(mux, \Y)
|
||||
set mux_a port(mux, AB)
|
||||
set mux_b port(mux, BA)
|
||||
set mux_a_id AB
|
||||
set mux_b_id BA
|
||||
index <SigSpec> port(mux, AB) === add_a_ext
|
||||
index <SigSpec> port(mux, BA) === add_y
|
||||
endmatch
|
||||
|
||||
code add_y add_a add_b add_a_ext add_a_id add_b_id mux_y mux_a mux_b mux_a_id mux_b_id
|
||||
// Get mux signal
|
||||
SigSpec mid;
|
||||
std::string adder_y_name;
|
||||
if (add_y.is_wire())
|
||||
adder_y_name = add_y.as_wire()->name.c_str();
|
||||
else
|
||||
adder_y_name = add_y.as_string();
|
||||
|
||||
// Start by renaming the LHS of an eventual assign statement
|
||||
// where the RHS is the adder output (that is getting rewired).
|
||||
// Renaming the signal allows equiv_opt to function as it would
|
||||
// otherwise try to match the functionality which would fail
|
||||
// as the LHS signal has indeed changed function.
|
||||
|
||||
// Adder output could be assigned
|
||||
for (auto it = module->connections().begin(); it != module->connections().end(); ++it) {
|
||||
RTLIL::SigSpec rhs = it->second;
|
||||
if (rhs.is_wire()) {
|
||||
const std::string& rhs_name = rhs.as_wire()->name.c_str();
|
||||
if (rhs_name == adder_y_name) {
|
||||
RTLIL::SigSpec lhs = it->first;
|
||||
if (lhs.is_wire()) {
|
||||
const std::string& lhs_name = lhs.as_wire()->name.c_str();
|
||||
module->rename(lhs_name, module->uniquify("$" + lhs_name));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Alternatively, the port name could be a wire name
|
||||
if (add_y.is_wire()) {
|
||||
if (GetSize(adder_y_name)) {
|
||||
if (adder_y_name[0] != '$') {
|
||||
module->rename(adder_y_name, module->uniquify("$" + adder_y_name));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (auto chunk : add_y.chunks()) {
|
||||
if (chunk.is_wire()) {
|
||||
const std::string& name = chunk.wire->name.c_str();
|
||||
if (name[0] != '$') {
|
||||
module->rename(name, module->uniquify("$" + name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create new mid wire
|
||||
Cell *cell = mux;
|
||||
mid = module->addWire(NEW_ID2_SUFFIX("mid"), GetSize(add_b));
|
||||
|
||||
// Connect ports
|
||||
add->setPort(add_b_id, mid);
|
||||
add->setPort(add_a_id, add_a);
|
||||
add->setPort(\Y, add_y);
|
||||
cell = add;
|
||||
module->rename(add, NEW_ID2_SUFFIX("rot"));
|
||||
mux->setPort(mux_a_id, Const(State::S0, GetSize(add_b)));
|
||||
mux->setPort(mux_b_id, add_b);
|
||||
mux->setPort(\Y, mid);
|
||||
cell = mux;
|
||||
module->rename(mux, NEW_ID2_SUFFIX("rot"));
|
||||
module->connect(mux_y, add_y);
|
||||
|
||||
// Log, fixup, accept
|
||||
log("muxadd pattern in %s: mux=%s, add=%s\n", log_id(module), log_id(mux), log_id(add));
|
||||
add->fixup_parameters();
|
||||
mux->fixup_parameters();
|
||||
did_something = true;
|
||||
accept;
|
||||
endcode
|
151
passes/opt/peepopt_shiftadd.pmg
Normal file
151
passes/opt/peepopt_shiftadd.pmg
Normal file
|
@ -0,0 +1,151 @@
|
|||
pattern shiftadd
|
||||
//
|
||||
// Transforms add/sub+shift pairs that result from expressions such as data[s*W +C +:W2]
|
||||
// specifically something like: out[W2-1:0] = data >> (s*W +C)
|
||||
// will be transformed into: out[W2-1:0] = (data >> C) >> (s*W)
|
||||
// this can then be optimized using peepopt_shiftmul_right.pmg
|
||||
//
|
||||
|
||||
match shift
|
||||
select shift->type.in($shift, $shiftx, $shr)
|
||||
filter !port(shift, \B).empty()
|
||||
endmatch
|
||||
|
||||
// the right shift amount
|
||||
state <SigSpec> shift_amount
|
||||
// log2 scale factor in interpreting of shift_amount
|
||||
// due to zero padding on the shift cell's B port
|
||||
state <int> log2scale
|
||||
// zeros at the MSB position make it unsigned
|
||||
state <bool> msb_zeros
|
||||
|
||||
code shift_amount log2scale msb_zeros
|
||||
shift_amount = port(shift, \B);
|
||||
|
||||
log2scale = 0;
|
||||
while (shift_amount[0] == State::S0) {
|
||||
shift_amount.remove(0);
|
||||
if (shift_amount.empty()) reject;
|
||||
log2scale++;
|
||||
}
|
||||
|
||||
msb_zeros = 0;
|
||||
while (shift_amount.bits().back() == State::S0) {
|
||||
msb_zeros = true;
|
||||
shift_amount.remove(GetSize(shift_amount) - 1);
|
||||
if (shift_amount.empty()) reject;
|
||||
}
|
||||
endcode
|
||||
|
||||
state <bool> var_signed
|
||||
state <SigSpec> var_signal
|
||||
// offset: signed constant value c in data[var+c +:W1] (constant shift-right amount)
|
||||
state <int> offset
|
||||
|
||||
match add
|
||||
// either data[var+c +:W1] or data[var-c +:W1]
|
||||
select add->type.in($add, $sub)
|
||||
index <SigSpec> port(add, \Y) === shift_amount
|
||||
|
||||
// one must be constant, the other is variable
|
||||
choice <IdString> constport {\A, \B}
|
||||
select !port(add, constport).empty()
|
||||
select port(add, constport).is_fully_const()
|
||||
define <IdString> varport (constport == \A ? \B : \A)
|
||||
|
||||
// only optimize for constants up to a fixed width. this prevents cases
|
||||
// with a blowup in internal term size and prevents larger constants being
|
||||
// casted to int incorrectly
|
||||
select (GetSize(port(add, constport)) <= 24)
|
||||
|
||||
// if a value of var is able to wrap the output, the transformation might give wrong results
|
||||
// an addition/substraction can at most flip one more bit than the largest operand (the carry bit)
|
||||
// as long as the output can show this bit, no wrap should occur (assuming all signed-ness make sense)
|
||||
select ( GetSize(port(add, \Y)) > max(GetSize(port(add, \A)), GetSize(port(add, \B))) )
|
||||
|
||||
define <bool> varport_A (varport == \A)
|
||||
define <bool> is_sub add->type.in($sub)
|
||||
|
||||
define <bool> constport_signed param(add, !varport_A ? \A_SIGNED : \B_SIGNED).as_bool()
|
||||
define <bool> varport_signed param(add, varport_A ? \A_SIGNED : \B_SIGNED).as_bool();
|
||||
define <bool> const_negative (constport_signed && (port(add, constport).bits().back() == State::S1))
|
||||
define <bool> offset_negative ((is_sub && varport_A) ^ const_negative)
|
||||
|
||||
// checking some value boundaries as well:
|
||||
// data[...-c +:W1] is fine for any signed var (pad at LSB, all data still accessible)
|
||||
// unsigned shift may underflow (eg var-3 with var<3) -> cannot be converted
|
||||
// data[...+c +:W1] is only fine for +var(add) and var unsigned
|
||||
// (+c cuts lower C bits, making them inaccessible, a signed var could try to access them)
|
||||
// either its an add or the variable port is A (it must be positive)
|
||||
select (add->type.in($add) || varport == \A)
|
||||
|
||||
// -> data[var+c +:W1] (with var signed) is illegal
|
||||
filter !(!offset_negative && varport_signed)
|
||||
// -> data >> (var-c) (with var unsigned) is illegal
|
||||
filter !(offset_negative && !varport_signed)
|
||||
|
||||
// state-variables are assigned at the end only:
|
||||
// shift the log2scale offset in-front of add to get true value: (var+c)<<N -> (var<<N)+(c<<N)
|
||||
set offset ( (port(add, constport).as_int(constport_signed) << log2scale) * ( (is_sub && varport_A) ? -1 : 1 ) )
|
||||
|
||||
set var_signed varport_signed
|
||||
set var_signal add->getPort(varport)
|
||||
endmatch
|
||||
|
||||
code
|
||||
{
|
||||
// positive constant offset with a signed variable (index) cannot be handled
|
||||
// the above filter should get rid of this case but 'offset' is calculated differently
|
||||
// due to limitations of state-variables in pmgen
|
||||
// it should only differ if previous passes create invalid data
|
||||
log_assert(!(offset>0 && var_signed));
|
||||
|
||||
SigSpec old_a = port(shift, \A); // data
|
||||
std::string location = shift->get_src_attribute();
|
||||
|
||||
if(shiftadd_max_ratio>0 && offset<0 && -offset*shiftadd_max_ratio > old_a.size()) {
|
||||
log_warning("at %s: candiate for shiftadd optimization (shifting '%s' by '%s - %d' bits) "
|
||||
"was ignored to avoid high resource usage, see help peepopt\n",
|
||||
location.c_str(), log_signal(old_a), log_signal(var_signal), -offset);
|
||||
reject;
|
||||
}
|
||||
|
||||
did_something = true;
|
||||
log("shiftadd pattern in %s: shift=%s, add/sub=%s, offset: %d\n", \
|
||||
log_id(module), log_id(shift), log_id(add), offset);
|
||||
|
||||
SigSpec new_a;
|
||||
if(offset<0) {
|
||||
// data >> (...-c) transformed to {data, c'X} >> (...)
|
||||
SigSpec padding( (shift->type.in($shiftx) ? State::Sx : State::S0), -offset );
|
||||
new_a.append(padding);
|
||||
new_a.append(old_a);
|
||||
} else {
|
||||
// data >> (...+c) transformed to data[MAX:c] >> (...)
|
||||
if(offset < GetSize(old_a)) { // some signal bits left?
|
||||
new_a.append(old_a.extract_end(offset));
|
||||
} else {
|
||||
// warn user in case data is empty (no bits left)
|
||||
if (location.empty())
|
||||
location = shift->name.str();
|
||||
if(shift->type.in($shiftx))
|
||||
log_warning("at %s: result of indexed part-selection is always constant (selecting from '%s' with index '%s + %d')\n", \
|
||||
location.c_str(), log_signal(old_a), log_signal(var_signal), offset);
|
||||
else
|
||||
log_warning("at %s: result of shift operation is always constant (shifting '%s' by '%s + %d' bits)\n", \
|
||||
location.c_str(), log_signal(old_a), log_signal(var_signal), offset);
|
||||
}
|
||||
}
|
||||
|
||||
SigSpec new_b = {var_signal, SigSpec(State::S0, log2scale)};
|
||||
if (msb_zeros || !var_signed)
|
||||
new_b.append(State::S0);
|
||||
|
||||
shift->setPort(\A, new_a);
|
||||
shift->setParam(\A_WIDTH, GetSize(new_a));
|
||||
shift->setPort(\B, new_b);
|
||||
shift->setParam(\B_WIDTH, GetSize(new_b));
|
||||
blacklist(add);
|
||||
accept;
|
||||
}
|
||||
endcode
|
163
passes/opt/peepopt_shiftmul_left.pmg
Normal file
163
passes/opt/peepopt_shiftmul_left.pmg
Normal file
|
@ -0,0 +1,163 @@
|
|||
pattern shiftmul_left
|
||||
//
|
||||
// Optimize mul+shift pairs that result from expressions such as foo[s*W+:W]
|
||||
//
|
||||
|
||||
match shift
|
||||
select shift->type.in($shift, $shiftx, $shl)
|
||||
select shift->type.in($shl) || param(shift, \B_SIGNED).as_bool()
|
||||
filter !port(shift, \B).empty()
|
||||
endmatch
|
||||
|
||||
match neg
|
||||
if shift->type.in($shift, $shiftx)
|
||||
select neg->type == $neg
|
||||
index <SigSpec> port(neg, \Y) === port(shift, \B)
|
||||
filter !port(shift, \A).empty()
|
||||
endmatch
|
||||
|
||||
// the left shift amount
|
||||
state <SigSpec> shift_amount
|
||||
// log2 scale factor in interpreting of shift_amount
|
||||
// due to zero padding on the shift cell's B port
|
||||
state <int> log2scale
|
||||
|
||||
code shift_amount log2scale
|
||||
if (neg) {
|
||||
// case of `$shift`, `$shiftx`
|
||||
shift_amount = port(neg, \A);
|
||||
if (!param(neg, \A_SIGNED).as_bool())
|
||||
shift_amount.append(State::S0);
|
||||
} else {
|
||||
// case of `$shl`
|
||||
shift_amount = port(shift, \B);
|
||||
if (!param(shift, \B_SIGNED).as_bool())
|
||||
shift_amount.append(State::S0);
|
||||
}
|
||||
|
||||
// at this point shift_amount is signed, make
|
||||
// sure we can never go negative
|
||||
if (shift_amount.bits().back() != State::S0)
|
||||
reject;
|
||||
|
||||
while (shift_amount.bits().back() == State::S0) {
|
||||
shift_amount.remove(GetSize(shift_amount) - 1);
|
||||
if (shift_amount.empty()) reject;
|
||||
}
|
||||
|
||||
log2scale = 0;
|
||||
while (shift_amount[0] == State::S0) {
|
||||
shift_amount.remove(0);
|
||||
if (shift_amount.empty()) reject;
|
||||
log2scale++;
|
||||
}
|
||||
|
||||
if (GetSize(shift_amount) > 20)
|
||||
reject;
|
||||
endcode
|
||||
|
||||
state <SigSpec> mul_din
|
||||
state <Const> mul_const
|
||||
|
||||
match mul
|
||||
select mul->type.in($mul)
|
||||
index <SigSpec> port(mul, \Y) === shift_amount
|
||||
filter !param(mul, \A_SIGNED).as_bool()
|
||||
|
||||
choice <IdString> constport {\A, \B}
|
||||
filter port(mul, constport).is_fully_const()
|
||||
|
||||
define <IdString> varport (constport == \A ? \B : \A)
|
||||
set mul_const SigSpec({port(mul, constport), SigSpec(State::S0, log2scale)}).as_const()
|
||||
// get mul_din unmapped (so no `port()` shorthand)
|
||||
// because we will be using it to set the \A port
|
||||
// on the shift cell, and we want to stay close
|
||||
// to the original design
|
||||
set mul_din mul->getPort(varport)
|
||||
endmatch
|
||||
|
||||
code
|
||||
{
|
||||
// SILIMATE: Alias cell to shift for wires
|
||||
Cell *cell = shift;
|
||||
|
||||
if (mul_const.empty() || GetSize(mul_const) > 20)
|
||||
reject;
|
||||
|
||||
// make sure there's no overlap in the signal
|
||||
// selections by the shiftmul pattern
|
||||
if (GetSize(port(shift, \A)) > mul_const.as_int())
|
||||
reject;
|
||||
|
||||
int factor_bits = ceil_log2(mul_const.as_int());
|
||||
// make sure the multiplication never wraps around
|
||||
if (GetSize(shift_amount) < factor_bits + GetSize(mul_din))
|
||||
reject;
|
||||
|
||||
if (neg) {
|
||||
// make sure the negation never wraps around
|
||||
if (GetSize(port(shift, \B)) < factor_bits + GetSize(mul_din)
|
||||
+ log2scale + 1)
|
||||
reject;
|
||||
}
|
||||
|
||||
did_something = true;
|
||||
log("left shiftmul pattern in %s: shift=%s, mul=%s\n", log_id(module), log_id(shift), log_id(mul));
|
||||
|
||||
int const_factor = mul_const.as_int();
|
||||
int new_const_factor = 1 << factor_bits;
|
||||
SigSpec padding(State::Sm, new_const_factor-const_factor);
|
||||
SigSpec old_y = port(shift, \Y), new_y;
|
||||
int trunc = 0;
|
||||
|
||||
if (GetSize(old_y) % const_factor != 0) {
|
||||
trunc = const_factor - GetSize(old_y) % const_factor;
|
||||
old_y.append(SigSpec(State::Sm, trunc));
|
||||
}
|
||||
|
||||
for (int i = 0; i*const_factor < GetSize(old_y); i++) {
|
||||
SigSpec slice = old_y.extract(i*const_factor, const_factor);
|
||||
new_y.append(slice);
|
||||
new_y.append(padding);
|
||||
}
|
||||
|
||||
if (trunc > 0)
|
||||
new_y.remove(GetSize(new_y)-trunc, trunc);
|
||||
|
||||
{
|
||||
// Now replace occurences of Sm in new_y with bits
|
||||
// of a dummy wire
|
||||
int padbits = 0;
|
||||
for (auto bit : new_y)
|
||||
if (bit == SigBit(State::Sm))
|
||||
padbits++;
|
||||
|
||||
SigSpec padwire = module->addWire(NEW_ID2_SUFFIX("pad"), padbits); // SILIMATE: Improve the naming
|
||||
|
||||
for (int i = new_y.size() - 1; i >= 0; i--)
|
||||
if (new_y[i] == SigBit(State::Sm)) {
|
||||
new_y[i] = padwire.bits().back();
|
||||
padwire.remove(padwire.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
SigSpec new_b = {mul_din, SigSpec(State::S0, factor_bits)};
|
||||
|
||||
shift->setPort(\Y, new_y);
|
||||
shift->setParam(\Y_WIDTH, GetSize(new_y));
|
||||
if (shift->type == $shl) {
|
||||
if (param(shift, \B_SIGNED).as_bool())
|
||||
new_b.append(State::S0);
|
||||
shift->setPort(\B, new_b);
|
||||
shift->setParam(\B_WIDTH, GetSize(new_b));
|
||||
} else {
|
||||
SigSpec b_neg = module->addWire(NEW_ID2_SUFFIX("b_neg"), GetSize(new_b) + 1); // SILIMATE: Improve the naming
|
||||
module->addNeg(NEW_ID2_SUFFIX("neg"), new_b, b_neg, false, cell->get_src_attribute()); // SILIMATE: Improve the naming
|
||||
shift->setPort(\B, b_neg);
|
||||
shift->setParam(\B_WIDTH, GetSize(b_neg));
|
||||
}
|
||||
|
||||
blacklist(shift);
|
||||
accept;
|
||||
}
|
||||
endcode
|
108
passes/opt/peepopt_shiftmul_right.pmg
Normal file
108
passes/opt/peepopt_shiftmul_right.pmg
Normal file
|
@ -0,0 +1,108 @@
|
|||
pattern shiftmul_right
|
||||
//
|
||||
// Optimize mul+shift pairs that result from expressions such as foo[s*W+:W]
|
||||
//
|
||||
|
||||
match shift
|
||||
select shift->type.in($shift, $shiftx, $shr)
|
||||
filter !port(shift, \B).empty()
|
||||
endmatch
|
||||
|
||||
// the right shift amount
|
||||
state <SigSpec> shift_amount
|
||||
// log2 scale factor in interpreting of shift_amount
|
||||
// due to zero padding on the shift cell's B port
|
||||
state <int> log2scale
|
||||
|
||||
code shift_amount log2scale
|
||||
shift_amount = port(shift, \B);
|
||||
if (shift->type.in($shr) || !param(shift, \B_SIGNED).as_bool())
|
||||
shift_amount.append(State::S0);
|
||||
|
||||
// at this point shift_amount is signed, make
|
||||
// sure we can never go negative
|
||||
if (shift_amount.bits().back() != State::S0)
|
||||
reject;
|
||||
|
||||
while (shift_amount.bits().back() == State::S0) {
|
||||
shift_amount.remove(GetSize(shift_amount) - 1);
|
||||
if (shift_amount.empty()) reject;
|
||||
}
|
||||
|
||||
log2scale = 0;
|
||||
while (shift_amount[0] == State::S0) {
|
||||
shift_amount.remove(0);
|
||||
if (shift_amount.empty()) reject;
|
||||
log2scale++;
|
||||
}
|
||||
|
||||
if (GetSize(shift_amount) > 20)
|
||||
reject;
|
||||
endcode
|
||||
|
||||
state <SigSpec> mul_din
|
||||
state <Const> mul_const
|
||||
|
||||
match mul
|
||||
select mul->type.in($mul)
|
||||
index <SigSpec> port(mul, \Y) === shift_amount
|
||||
filter !param(mul, \A_SIGNED).as_bool()
|
||||
|
||||
choice <IdString> constport {\A, \B}
|
||||
filter port(mul, constport).is_fully_const()
|
||||
|
||||
define <IdString> varport (constport == \A ? \B : \A)
|
||||
set mul_const SigSpec({port(mul, constport), SigSpec(State::S0, log2scale)}).as_const()
|
||||
// get mul_din unmapped (so no `port()` shorthand)
|
||||
// because we will be using it to set the \A port
|
||||
// on the shift cell, and we want to stay close
|
||||
// to the original design
|
||||
set mul_din mul->getPort(varport)
|
||||
endmatch
|
||||
|
||||
code
|
||||
{
|
||||
if (mul_const.empty() || GetSize(mul_const) > 20)
|
||||
reject;
|
||||
|
||||
// make sure there's no overlap in the signal
|
||||
// selections by the shiftmul pattern
|
||||
if (GetSize(port(shift, \Y)) > mul_const.as_int())
|
||||
reject;
|
||||
|
||||
int factor_bits = ceil_log2(mul_const.as_int());
|
||||
// make sure the multiplication never wraps around
|
||||
if (GetSize(shift_amount) + log2scale < factor_bits + GetSize(mul_din))
|
||||
reject;
|
||||
|
||||
did_something = true;
|
||||
log("right shiftmul pattern in %s: shift=%s, mul=%s\n", log_id(module), log_id(shift), log_id(mul));
|
||||
|
||||
int const_factor = mul_const.as_int();
|
||||
int new_const_factor = 1 << factor_bits;
|
||||
SigSpec padding(State::Sx, new_const_factor-const_factor);
|
||||
SigSpec old_a = port(shift, \A), new_a;
|
||||
|
||||
for (int i = 0; i*const_factor < GetSize(old_a); i++) {
|
||||
if ((i+1)*const_factor < GetSize(old_a)) {
|
||||
SigSpec slice = old_a.extract(i*const_factor, const_factor);
|
||||
new_a.append(slice);
|
||||
new_a.append(padding);
|
||||
} else {
|
||||
new_a.append(old_a.extract_end(i*const_factor));
|
||||
}
|
||||
}
|
||||
|
||||
SigSpec new_b = {mul_din, SigSpec(State::S0, factor_bits)};
|
||||
if (param(shift, \B_SIGNED).as_bool())
|
||||
new_b.append(State::S0);
|
||||
|
||||
shift->setPort(\A, new_a);
|
||||
shift->setParam(\A_WIDTH, GetSize(new_a));
|
||||
shift->setPort(\B, new_b);
|
||||
shift->setParam(\B_WIDTH, GetSize(new_b));
|
||||
|
||||
blacklist(shift);
|
||||
accept;
|
||||
}
|
||||
endcode
|
Loading…
Add table
Add a link
Reference in a new issue