mirror of https://github.com/YosysHQ/yosys.git
opt_expr for constant comparisons
This commit is contained in:
parent
a19dfc535c
commit
8485d57841
|
|
@ -2217,6 +2217,231 @@ skip_alu_split:
|
|||
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:;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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…
Reference in New Issue