mirror of
https://github.com/YosysHQ/yosys
synced 2026-07-24 08:02:32 +00:00
Merge pull request #154 from Silimate/opt_expr_cmp_const
opt_expr for constant comparisons
This commit is contained in:
commit
8823608317
3 changed files with 279 additions and 0 deletions
|
|
@ -2217,6 +2217,231 @@ skip_alu_split:
|
||||||
goto next_cell;
|
goto next_cell;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SILIMATE: Generalized MSB-comparison rule. Recognize the pattern
|
||||||
|
// `(unsigned X < {sig, k zero bits})` (and its $ge / $gt / $le siblings)
|
||||||
|
// where X is provably bounded by 2^k - 1 (i.e., its bits at positions
|
||||||
|
// >= k are constant zero or absent due to a narrower port width).
|
||||||
|
// This catches comparators that survive after lowering has stripped
|
||||||
|
// the original `(signed X < 0)` signedness annotation.
|
||||||
|
//
|
||||||
|
// Identities (all unsigned, X bounded by 2^k - 1):
|
||||||
|
// $lt(X, {msb, k'b0}) -> Y = msb
|
||||||
|
// $ge(X, {msb, k'b0}) -> Y = !msb
|
||||||
|
// $gt({msb, k'b0}, X) -> Y = msb
|
||||||
|
// $le({msb, k'b0}, X) -> Y = !msb
|
||||||
|
{
|
||||||
|
IdString cmp = cell->type;
|
||||||
|
SigSpec sig_a = cell->getPort(ID::A);
|
||||||
|
SigSpec sig_b = cell->getPort(ID::B);
|
||||||
|
int a_width = cell->parameters[ID::A_WIDTH].as_int();
|
||||||
|
int b_width = cell->parameters[ID::B_WIDTH].as_int();
|
||||||
|
bool a_signed = cell->getParam(ID::A_SIGNED).as_bool();
|
||||||
|
bool b_signed = cell->getParam(ID::B_SIGNED).as_bool();
|
||||||
|
|
||||||
|
// Only handle unsigned comparisons; signed sign-extension changes the bound.
|
||||||
|
if (!a_signed && !b_signed)
|
||||||
|
{
|
||||||
|
// Detect SigSpec of the form {msb_bit, k_zero_bits} where msb_bit is a
|
||||||
|
// non-constant signal bit. Returns (matched, msb_bit, k).
|
||||||
|
auto detect_msb_zeros = [](const SigSpec &s, int width) -> std::tuple<bool, SigBit, int> {
|
||||||
|
if (width < 1)
|
||||||
|
return std::make_tuple(false, SigBit(), 0);
|
||||||
|
for (int i = 0; i < width - 1; i++) {
|
||||||
|
if (s[i] != RTLIL::State::S0)
|
||||||
|
return std::make_tuple(false, SigBit(), 0);
|
||||||
|
}
|
||||||
|
SigBit msb = s[width - 1];
|
||||||
|
if (msb.wire == nullptr)
|
||||||
|
return std::make_tuple(false, SigBit(), 0);
|
||||||
|
return std::make_tuple(true, msb, width - 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if SigSpec value (unsigned, within its declared width) is
|
||||||
|
// bounded by 2^k - 1, i.e., bits at positions [k, width) are all
|
||||||
|
// constant zero. Bits beyond `width` (if comparison is wider) are
|
||||||
|
// implicit unsigned zero-extension and therefore zero too.
|
||||||
|
auto is_bounded_by_2_pow_k = [](const SigSpec &s, int width, int k) -> bool {
|
||||||
|
for (int i = k; i < width; i++) {
|
||||||
|
if (s[i] != RTLIL::State::S0)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto emit_replace = [&](SigBit y_bit, bool inverted, const std::string &condition) {
|
||||||
|
std::string replacement = inverted ? "!Y" : "Y";
|
||||||
|
log_debug("Replacing %s cell `%s' (implementing %s) with %s.\n",
|
||||||
|
log_id(cell->type), log_id(cell), condition.c_str(), replacement.c_str());
|
||||||
|
if (inverted) {
|
||||||
|
// SILIMATE: Preserve original cell name on the replacement $logic_not.
|
||||||
|
RTLIL::IdString cell_name = cell->name;
|
||||||
|
module->rename(cell->name, NEW_ID);
|
||||||
|
module->addLogicNot(cell_name, y_bit, cell->getPort(ID::Y), false, cell->get_src_attribute());
|
||||||
|
} else {
|
||||||
|
SigSpec replace_sig(RTLIL::State::S0, GetSize(cell->getPort(ID::Y)));
|
||||||
|
replace_sig[0] = y_bit;
|
||||||
|
module->connect(cell->getPort(ID::Y), replace_sig);
|
||||||
|
}
|
||||||
|
module->remove(cell);
|
||||||
|
did_something = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Case 1: structured operand on the B side, free operand on A.
|
||||||
|
if (cmp == ID($lt) || cmp == ID($ge)) {
|
||||||
|
bool m;
|
||||||
|
SigBit msb;
|
||||||
|
int k;
|
||||||
|
std::tie(m, msb, k) = detect_msb_zeros(sig_b, b_width);
|
||||||
|
if (m && is_bounded_by_2_pow_k(sig_a, a_width, k)) {
|
||||||
|
std::string cond = stringf("unsigned X %s {Y, %d'b0}",
|
||||||
|
cmp == ID($lt) ? "<" : ">=", k);
|
||||||
|
emit_replace(msb, /*inverted=*/cmp == ID($ge), cond);
|
||||||
|
goto next_cell;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case 2: structured operand on the A side, free operand on B.
|
||||||
|
if (cmp == ID($gt) || cmp == ID($le)) {
|
||||||
|
bool m;
|
||||||
|
SigBit msb;
|
||||||
|
int k;
|
||||||
|
std::tie(m, msb, k) = detect_msb_zeros(sig_a, a_width);
|
||||||
|
if (m && is_bounded_by_2_pow_k(sig_b, b_width, k)) {
|
||||||
|
std::string cond = stringf("unsigned {Y, %d'b0} %s X", k,
|
||||||
|
cmp == ID($gt) ? ">" : "<=");
|
||||||
|
emit_replace(msb, /*inverted=*/cmp == ID($le), cond);
|
||||||
|
goto next_cell;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SILIMATE: Recognize the lowered form of `(signed X < -1)` after
|
||||||
|
// signedness has been stripped. Verific lowers it to:
|
||||||
|
// $lt(unsigned A, unsigned B) with A_WIDTH == B_WIDTH = N >= 2,
|
||||||
|
// A = {1'b1, X[N-2:0]} (MSB forced to 1)
|
||||||
|
// B = {X[N-1], (N-1)'b1 } (low N-1 bits forced to 1)
|
||||||
|
// where X is one coherent N-bit slice of a single wire.
|
||||||
|
//
|
||||||
|
// Truth table:
|
||||||
|
// X[N-1] == 0 -> A >= 2^(N-1) > B = 2^(N-1)-1, so A < B = 0
|
||||||
|
// X[N-1] == 1 -> A = 2^(N-1) + X[N-2:0], B = 2^N - 1,
|
||||||
|
// A < B iff X[N-2:0] != all 1s
|
||||||
|
//
|
||||||
|
// So Y = X[N-1] AND ~&X[N-2:0]
|
||||||
|
// = X[N-1] AND |~X[N-2:0] (the "reduce_or" form)
|
||||||
|
//
|
||||||
|
// Mirror cases:
|
||||||
|
// $gt(A, B) == $lt(B, A) -> swap operands
|
||||||
|
// $ge(A, B) == !$lt(A, B) -> invert output
|
||||||
|
// $le(A, B) == !$gt(A, B) == !$lt(B,A) -> swap + invert
|
||||||
|
{
|
||||||
|
IdString cmp = cell->type;
|
||||||
|
SigSpec sig_a = cell->getPort(ID::A);
|
||||||
|
SigSpec sig_b = cell->getPort(ID::B);
|
||||||
|
int a_width = cell->parameters[ID::A_WIDTH].as_int();
|
||||||
|
int b_width = cell->parameters[ID::B_WIDTH].as_int();
|
||||||
|
bool a_signed = cell->getParam(ID::A_SIGNED).as_bool();
|
||||||
|
bool b_signed = cell->getParam(ID::B_SIGNED).as_bool();
|
||||||
|
|
||||||
|
if (!a_signed && !b_signed && a_width == b_width && a_width >= 2)
|
||||||
|
{
|
||||||
|
int N = a_width;
|
||||||
|
|
||||||
|
// Try to match: lo = {1'b1, X[N-2:0]}, hi = {X[N-1], (N-1)'b1}.
|
||||||
|
// On success returns (msb_bit, x_low_sigspec) with x_low = X[N-2:0].
|
||||||
|
auto try_match_lt_neg1 =
|
||||||
|
[&](const SigSpec &lo, const SigSpec &hi) -> std::tuple<bool, SigBit, SigSpec> {
|
||||||
|
if (lo[N - 1] != RTLIL::State::S1)
|
||||||
|
return std::make_tuple(false, SigBit(), SigSpec());
|
||||||
|
for (int i = 0; i < N - 1; i++) {
|
||||||
|
if (hi[i] != RTLIL::State::S1)
|
||||||
|
return std::make_tuple(false, SigBit(), SigSpec());
|
||||||
|
}
|
||||||
|
SigBit msb = hi[N - 1];
|
||||||
|
if (msb.wire == nullptr)
|
||||||
|
return std::make_tuple(false, SigBit(), SigSpec());
|
||||||
|
int base = msb.offset - (N - 1);
|
||||||
|
if (base < 0)
|
||||||
|
return std::make_tuple(false, SigBit(), SigSpec());
|
||||||
|
for (int i = 0; i < N - 1; i++) {
|
||||||
|
SigBit b = lo[i];
|
||||||
|
if (b.wire != msb.wire)
|
||||||
|
return std::make_tuple(false, SigBit(), SigSpec());
|
||||||
|
if (b.offset != base + i)
|
||||||
|
return std::make_tuple(false, SigBit(), SigSpec());
|
||||||
|
}
|
||||||
|
SigSpec x_low(msb.wire, base, N - 1);
|
||||||
|
return std::make_tuple(true, msb, x_low);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto emit_lt_neg1 = [&](SigBit msb, SigSpec x_low, bool inverted,
|
||||||
|
const std::string &condition) {
|
||||||
|
std::string replacement = inverted
|
||||||
|
? "!(X[N-1] && !&X[N-2:0])"
|
||||||
|
: "X[N-1] && !&X[N-2:0]";
|
||||||
|
log_debug("Replacing %s cell `%s' (implementing %s) with %s.\n",
|
||||||
|
log_id(cell->type), log_id(cell), condition.c_str(),
|
||||||
|
replacement.c_str());
|
||||||
|
|
||||||
|
// SILIMATE: Preserve original cell name on the kept replacement
|
||||||
|
// cell, and derive intermediate wire/cell names from it via
|
||||||
|
// NEW_ID3_SUFFIX so the resulting netlist stays readable.
|
||||||
|
RTLIL::IdString cell_name = cell->name;
|
||||||
|
module->rename(cell->name, NEW_ID);
|
||||||
|
std::string src = cell->get_src_attribute();
|
||||||
|
|
||||||
|
// reduce_and(X[N-2:0]) -> w_red
|
||||||
|
Wire *w_red = module->addWire(NEW_ID3_SUFFIX("ry"));
|
||||||
|
module->addReduceAnd(NEW_ID3_SUFFIX("r"), x_low, w_red,
|
||||||
|
false, src);
|
||||||
|
// !reduce_and(...) -> w_some_zero
|
||||||
|
Wire *w_some_zero = module->addWire(NEW_ID3_SUFFIX("ny"));
|
||||||
|
module->addLogicNot(NEW_ID3_SUFFIX("n"), SigSpec(w_red),
|
||||||
|
SigSpec(w_some_zero), false, src);
|
||||||
|
// X[N-1] AND w_some_zero -> result
|
||||||
|
if (inverted) {
|
||||||
|
Wire *w_and = module->addWire(NEW_ID3_SUFFIX("ay"));
|
||||||
|
module->addAnd(NEW_ID3_SUFFIX("a"), SigSpec(msb),
|
||||||
|
SigSpec(w_some_zero), SigSpec(w_and), false, src);
|
||||||
|
module->addLogicNot(cell_name, SigSpec(w_and),
|
||||||
|
cell->getPort(ID::Y), false, src);
|
||||||
|
} else {
|
||||||
|
module->addAnd(cell_name, SigSpec(msb), SigSpec(w_some_zero),
|
||||||
|
cell->getPort(ID::Y), false, src);
|
||||||
|
}
|
||||||
|
module->remove(cell);
|
||||||
|
did_something = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (cmp == ID($lt) || cmp == ID($ge)) {
|
||||||
|
bool m;
|
||||||
|
SigBit msb;
|
||||||
|
SigSpec x_low;
|
||||||
|
std::tie(m, msb, x_low) = try_match_lt_neg1(sig_a, sig_b);
|
||||||
|
if (m) {
|
||||||
|
std::string cond = stringf("unsigned X %s -1 (signed-stripped)",
|
||||||
|
cmp == ID($lt) ? "<" : ">=");
|
||||||
|
emit_lt_neg1(msb, x_low, /*inverted=*/cmp == ID($ge), cond);
|
||||||
|
goto next_cell;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmp == ID($gt) || cmp == ID($le)) {
|
||||||
|
bool m;
|
||||||
|
SigBit msb;
|
||||||
|
SigSpec x_low;
|
||||||
|
std::tie(m, msb, x_low) = try_match_lt_neg1(sig_b, sig_a);
|
||||||
|
if (m) {
|
||||||
|
std::string cond = stringf("-1 %s unsigned X (signed-stripped)",
|
||||||
|
cmp == ID($gt) ? ">" : "<=");
|
||||||
|
emit_lt_neg1(msb, x_low, /*inverted=*/cmp == ID($le), cond);
|
||||||
|
goto next_cell;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
next_cell:;
|
next_cell:;
|
||||||
|
|
|
||||||
50
tests/opt/opt_expr_cmp_msb.v
Normal file
50
tests/opt/opt_expr_cmp_msb.v
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
module top(
|
||||||
|
a, b, x,
|
||||||
|
o1_1, o1_2, o1_3, o1_4,
|
||||||
|
o2_1, o2_2, o2_3, o2_4,
|
||||||
|
o3_1, o3_2, o3_3, o3_4,
|
||||||
|
o4_1, o4_2, o4_3, o4_4,
|
||||||
|
o5_1, o5_2, o5_3, o5_4
|
||||||
|
);
|
||||||
|
input [3:0] a;
|
||||||
|
input b;
|
||||||
|
input [7:0] x;
|
||||||
|
|
||||||
|
output o1_1, o1_2, o1_3, o1_4;
|
||||||
|
output o2_1, o2_2, o2_3, o2_4;
|
||||||
|
output o3_1, o3_2, o3_3, o3_4;
|
||||||
|
output o4_1, o4_2, o4_3, o4_4;
|
||||||
|
output o5_1, o5_2, o5_3, o5_4;
|
||||||
|
|
||||||
|
// RHS = {b, 4'b0000}: same width as `a`, lower bits zero.
|
||||||
|
assign o1_1 = a < {b, 4'b0000};
|
||||||
|
assign o1_2 = a >= {b, 4'b0000};
|
||||||
|
assign o1_3 = {b, 4'b0000} > a;
|
||||||
|
assign o1_4 = {b, 4'b0000} <= a;
|
||||||
|
|
||||||
|
// RHS = {b, 5'b00000}: wider than `a`; `a` zero-extends to fit.
|
||||||
|
assign o2_1 = a < {b, 5'b00000};
|
||||||
|
assign o2_2 = a >= {b, 5'b00000};
|
||||||
|
assign o2_3 = {b, 5'b00000} > a;
|
||||||
|
assign o2_4 = {b, 5'b00000} <= a;
|
||||||
|
|
||||||
|
// LHS = {1'b0, a}: explicit zero-extend on the bounded operand.
|
||||||
|
assign o3_1 = {1'b0, a} < {b, 4'b0000};
|
||||||
|
assign o3_2 = {1'b0, a} >= {b, 4'b0000};
|
||||||
|
assign o3_3 = {b, 4'b0000} > {1'b0, a};
|
||||||
|
assign o3_4 = {b, 4'b0000} <= {1'b0, a};
|
||||||
|
|
||||||
|
// Lowered form of `(signed x < -1)` after sign-stripping:
|
||||||
|
// A = {1'b1, x[N-2:0]}, B = {x[N-1], (N-1)'b1}, both unsigned.
|
||||||
|
// Expected: Y = x[N-1] && ~&x[N-2:0]
|
||||||
|
assign o4_1 = {1'b1, x[6:0]} < {x[7], 7'b1111111};
|
||||||
|
assign o4_2 = {1'b1, x[6:0]} >= {x[7], 7'b1111111};
|
||||||
|
assign o4_3 = {x[7], 7'b1111111} > {1'b1, x[6:0]};
|
||||||
|
assign o4_4 = {x[7], 7'b1111111} <= {1'b1, x[6:0]};
|
||||||
|
|
||||||
|
// Same pattern at width = 2 (smallest viable N).
|
||||||
|
assign o5_1 = {1'b1, x[0]} < {x[1], 1'b1};
|
||||||
|
assign o5_2 = {1'b1, x[0]} >= {x[1], 1'b1};
|
||||||
|
assign o5_3 = {x[1], 1'b1} > {1'b1, x[0]};
|
||||||
|
assign o5_4 = {x[1], 1'b1} <= {1'b1, x[0]};
|
||||||
|
endmodule
|
||||||
4
tests/opt/opt_expr_cmp_msb.ys
Normal file
4
tests/opt/opt_expr_cmp_msb.ys
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
read_verilog opt_expr_cmp_msb.v
|
||||||
|
equiv_opt -assert opt_expr -fine
|
||||||
|
design -load postopt
|
||||||
|
select -assert-count 0 t:$gt t:$ge t:$lt t:$le
|
||||||
Loading…
Add table
Add a link
Reference in a new issue