Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

peepopt: Add shiftadd pattern #3883

Merged
merged 5 commits into from
Nov 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions passes/pmgen/Makefile.inc
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ $(eval $(call add_extra_objs,passes/pmgen/peepopt_pm.h))

PEEPOPT_PATTERN = passes/pmgen/peepopt_shiftmul_right.pmg
PEEPOPT_PATTERN += passes/pmgen/peepopt_shiftmul_left.pmg
PEEPOPT_PATTERN += passes/pmgen/peepopt_shiftadd.pmg
PEEPOPT_PATTERN += passes/pmgen/peepopt_muldiv.pmg

passes/pmgen/peepopt_pm.h: passes/pmgen/pmgen.py $(PEEPOPT_PATTERN)
Expand Down
4 changes: 4 additions & 0 deletions passes/pmgen/peepopt.cc
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ struct PeepoptPass : public Pass {
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("\n");
}
void execute(std::vector<std::string> args, RTLIL::Design *design) override
{
Expand All @@ -72,6 +75,7 @@ struct PeepoptPass : public Pass {

pm.setup(module->selected_cells());

pm.run_shiftadd();
pm.run_shiftmul_right();
pm.run_shiftmul_left();
pm.run_muldiv();
Expand Down
121 changes: 121 additions & 0 deletions passes/pmgen/peepopt_shiftadd.pmg
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
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
phsauter marked this conversation as resolved.
Show resolved Hide resolved
// 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)

// 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> offset_negative ((port(add, constport).bits().back() == State::S1) ^ (is_sub && varport_A))

// checking some value boundaries as well:
// data[...-c +:W1] is fine for +/-var (pad at LSB, all data still accessible)
// data[...+c +:W1] is only fine for +var(add) and var unsigned
phsauter marked this conversation as resolved.
Show resolved Hide resolved
// (+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)

// 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));

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 old_a = port(shift, \A), new_a;
if(offset<0) {
// data >> (...-c) transformed to {data, c'X} >> (...)
SigSpec padding( (shift->type.in($shiftx) ? State::Sx : State::S0), -offset );
phsauter marked this conversation as resolved.
Show resolved Hide resolved
new_a.append(padding);
new_a.append(old_a);
} else {
// data >> (...+c) transformed to data[MAX:c] >> (...)
new_a.append(old_a.extract_end(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
39 changes: 39 additions & 0 deletions tests/simple/partsel.v
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,42 @@ module partsel_test007 (
dout[n+1] = din[n];
end
endmodule


module partsel_test008 (
input [127:0] din,
input [3:0] idx,
input [4:0] uoffset,
input signed [4:0] soffset,
output [ 7:0] dout0,
output [ 7:0] dout1,
output [ 7:0] dout2,
output [ 7:0] dout3,
output [ 3:0] dout4,
output [ 3:0] dout5,
output [ 3:0] dout6,
output [ 3:0] dout7,
output [ 3:0] dout8,
output [11:0] dout9,
output [11:0] dout10,
output [11:0] dout11
);

// common: block-select with offsets
assign dout0 = din[idx*8 +uoffset +:8];
assign dout1 = din[idx*8 -uoffset +:8];
assign dout2 = din[idx*8 +soffset +:8];
assign dout3 = din[idx*8 -soffset +:8];

// only partial block used
assign dout4 = din[idx*8 +uoffset +:4];
assign dout5 = din[idx*8 -uoffset +:4];
assign dout6 = din[idx*8 +soffset +:4];
assign dout7 = din[idx*8 -soffset +:4];

// uncommon: more than one block used
assign dout8 = din[idx*8 +uoffset +:12];
assign dout9 = din[idx*8 -uoffset +:12];
assign dout10 = din[idx*8 +soffset +:12];
assign dout11 = din[idx*8 -soffset +:12];
endmodule