mirror of https://github.com/YosysHQ/yosys.git
opt_addcin pass
This commit is contained in:
parent
63df096fed
commit
e39395132d
|
|
@ -25,6 +25,7 @@ OBJS += passes/opt/muxpack.o
|
|||
OBJS += passes/opt/opt_balance_tree.o
|
||||
OBJS += passes/opt/opt_parallel_prefix.o
|
||||
OBJS += passes/opt/opt_prienc.o
|
||||
OBJS += passes/opt/opt_addcin.o
|
||||
|
||||
OBJS += passes/opt/peepopt.o
|
||||
GENFILES += passes/opt/peepopt_pm.h
|
||||
|
|
|
|||
|
|
@ -0,0 +1,277 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) 2026 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"
|
||||
|
||||
USING_YOSYS_NAMESPACE
|
||||
PRIVATE_NAMESPACE_BEGIN
|
||||
|
||||
struct OptAddcinWorker
|
||||
{
|
||||
struct Leaf {
|
||||
SigSpec sig;
|
||||
bool is_signed;
|
||||
};
|
||||
|
||||
struct Rewrite {
|
||||
Cell *outer;
|
||||
Cell *inner;
|
||||
Leaf a;
|
||||
Leaf b;
|
||||
SigSpec cin;
|
||||
SigSpec y;
|
||||
int width;
|
||||
};
|
||||
|
||||
Module *module;
|
||||
SigMap sigmap;
|
||||
dict<SigSpec, Cell*> add_driver;
|
||||
pool<SigBit> tracked_output_bits;
|
||||
dict<SigBit, int> input_users;
|
||||
pool<Cell*> claimed;
|
||||
vector<Rewrite> rewrites;
|
||||
|
||||
OptAddcinWorker(Module *module) : module(module), sigmap(module)
|
||||
{
|
||||
}
|
||||
|
||||
void build_indexes()
|
||||
{
|
||||
for (auto cell : module->selected_cells()) {
|
||||
if (cell->type != ID($add))
|
||||
continue;
|
||||
SigSpec y = sigmap(cell->getPort(ID::Y));
|
||||
add_driver[y] = cell;
|
||||
for (auto bit : y)
|
||||
tracked_output_bits.insert(bit);
|
||||
}
|
||||
|
||||
for (auto wire : module->wires()) {
|
||||
if (!wire->port_output)
|
||||
continue;
|
||||
for (auto bit : sigmap(wire))
|
||||
if (tracked_output_bits.count(bit))
|
||||
input_users[bit]++;
|
||||
}
|
||||
|
||||
for (auto cell : module->cells()) {
|
||||
for (auto &conn : cell->connections()) {
|
||||
if (!cell->input(conn.first))
|
||||
continue;
|
||||
for (auto bit : sigmap(conn.second))
|
||||
if (tracked_output_bits.count(bit))
|
||||
input_users[bit]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Cell *driver_of(const SigSpec &sig)
|
||||
{
|
||||
auto it = add_driver.find(sig);
|
||||
return it == add_driver.end() ? nullptr : it->second;
|
||||
}
|
||||
|
||||
bool has_single_use(const SigSpec &sig)
|
||||
{
|
||||
for (auto bit : sig) {
|
||||
auto it = input_users.find(bit);
|
||||
if (it == input_users.end() || it->second != 1)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool collect(Cell *outer)
|
||||
{
|
||||
if (claimed.count(outer) || outer->type != ID($add))
|
||||
return false;
|
||||
|
||||
SigSpec outer_a = sigmap(outer->getPort(ID::A));
|
||||
SigSpec outer_b = sigmap(outer->getPort(ID::B));
|
||||
SigSpec outer_y = sigmap(outer->getPort(ID::Y));
|
||||
Cell *inner_a = driver_of(outer_a);
|
||||
Cell *inner_b = driver_of(outer_b);
|
||||
|
||||
if ((inner_a != nullptr) == (inner_b != nullptr))
|
||||
return false;
|
||||
|
||||
Cell *inner = inner_a ? inner_a : inner_b;
|
||||
if (inner == outer || claimed.count(inner) || inner->type != ID($add))
|
||||
return false;
|
||||
|
||||
SigSpec inner_y = sigmap(inner->getPort(ID::Y));
|
||||
SigSpec inner_operand = inner_a ? outer_a : outer_b;
|
||||
if (inner_y != inner_operand)
|
||||
return false;
|
||||
|
||||
int width = GetSize(outer_y);
|
||||
if (width == 0 || GetSize(inner_y) != width)
|
||||
return false;
|
||||
if (inner->getParam(ID::Y_WIDTH).as_int() != width)
|
||||
return false;
|
||||
if (outer->getParam(ID::Y_WIDTH).as_int() != width)
|
||||
return false;
|
||||
if (!has_single_use(inner_y))
|
||||
return false;
|
||||
|
||||
vector<Leaf> leaves;
|
||||
leaves.push_back({sigmap(inner->getPort(ID::A)), inner->getParam(ID::A_SIGNED).as_bool()});
|
||||
leaves.push_back({sigmap(inner->getPort(ID::B)), inner->getParam(ID::B_SIGNED).as_bool()});
|
||||
if (inner_a)
|
||||
leaves.push_back({outer_b, outer->getParam(ID::B_SIGNED).as_bool()});
|
||||
else
|
||||
leaves.push_back({outer_a, outer->getParam(ID::A_SIGNED).as_bool()});
|
||||
|
||||
int cin_index = -1;
|
||||
for (int i = 0; i < GetSize(leaves); i++) {
|
||||
if (GetSize(leaves[i].sig) > width)
|
||||
return false;
|
||||
if (GetSize(leaves[i].sig) == 1 && !leaves[i].is_signed) {
|
||||
if (cin_index != -1)
|
||||
return false;
|
||||
cin_index = i;
|
||||
}
|
||||
}
|
||||
if (cin_index == -1)
|
||||
return false;
|
||||
|
||||
vector<Leaf> operands;
|
||||
for (int i = 0; i < GetSize(leaves); i++) {
|
||||
if (i == cin_index)
|
||||
continue;
|
||||
operands.push_back(leaves[i]);
|
||||
}
|
||||
log_assert(GetSize(operands) == 2);
|
||||
|
||||
claimed.insert(outer);
|
||||
claimed.insert(inner);
|
||||
rewrites.push_back({outer, inner, operands[0], operands[1], leaves[cin_index].sig, outer_y, width});
|
||||
return true;
|
||||
}
|
||||
|
||||
SigSpec extend_leaf(const Leaf &leaf, int width)
|
||||
{
|
||||
SigSpec sig = leaf.sig;
|
||||
sig.extend_u0(width, leaf.is_signed);
|
||||
return sig;
|
||||
}
|
||||
|
||||
void apply(const Rewrite &rewrite)
|
||||
{
|
||||
Cell *cell = rewrite.outer;
|
||||
|
||||
SigSpec a = extend_leaf(rewrite.a, rewrite.width);
|
||||
SigSpec b = extend_leaf(rewrite.b, rewrite.width);
|
||||
|
||||
SigSpec wide_a(State::S1);
|
||||
wide_a.append(a);
|
||||
|
||||
SigSpec wide_b = rewrite.cin;
|
||||
wide_b.append(b);
|
||||
|
||||
Wire *wide_y_wire = module->addWire(NEW_ID2_SUFFIX("addcin_y"), rewrite.width + 1);
|
||||
SigSpec wide_y(wide_y_wire);
|
||||
Cell *wide_add = module->addCell(NEW_ID2_SUFFIX("addcin"), ID($add));
|
||||
wide_add->attributes = rewrite.outer->attributes;
|
||||
wide_add->setPort(ID::A, wide_a);
|
||||
wide_add->setPort(ID::B, wide_b);
|
||||
wide_add->setPort(ID::Y, wide_y);
|
||||
wide_add->setParam(ID::A_SIGNED, false);
|
||||
wide_add->setParam(ID::B_SIGNED, false);
|
||||
wide_add->fixup_parameters();
|
||||
|
||||
module->connect(rewrite.y, wide_y.extract(1, rewrite.width));
|
||||
module->remove(rewrite.outer);
|
||||
module->remove(rewrite.inner);
|
||||
}
|
||||
|
||||
int run()
|
||||
{
|
||||
build_indexes();
|
||||
|
||||
vector<Cell*> cells;
|
||||
for (auto cell : module->selected_cells())
|
||||
cells.push_back(cell);
|
||||
for (auto cell : cells)
|
||||
collect(cell);
|
||||
|
||||
for (auto &rewrite : rewrites)
|
||||
apply(rewrite);
|
||||
|
||||
return GetSize(rewrites);
|
||||
}
|
||||
};
|
||||
|
||||
struct OptAddcinPass : public Pass {
|
||||
OptAddcinPass() : Pass("opt_addcin", "rewrite add-with-carry-in patterns as widened adders") { }
|
||||
|
||||
void help() override
|
||||
{
|
||||
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
|
||||
log("\n");
|
||||
log(" opt_addcin [selection]\n");
|
||||
log("\n");
|
||||
log("This pass rewrites a conservative two-$add, three-leaf carry-in pattern\n");
|
||||
log("into a single widened $add:\n");
|
||||
log("\n");
|
||||
log(" (A + B) + CI -> ({A_ext, 1'b1} + {B_ext, CI})[WIDTH:1]\n");
|
||||
log("\n");
|
||||
log("Only one leaf may be a one-bit unsigned carry input. The other two leaves\n");
|
||||
log("are explicitly sign- or zero-extended to the shared add width before the\n");
|
||||
log("widened add is emitted. The pass rejects $sub/$alu patterns, mismatched\n");
|
||||
log("intermediate/final widths, multi-bit carry operands, signed carry operands,\n");
|
||||
log("and intermediate sums with fanout outside the final add.\n");
|
||||
log("\n");
|
||||
log("This pass is not invoked by the default 'opt' or 'peepopt' scripts. It is\n");
|
||||
log("intended for flows that want to map carry-in adds onto adders without an\n");
|
||||
log("explicit carry input. Run add-chain timing transforms such as\n");
|
||||
log("'opt_balance_tree -arith' or 'opt_parallel_prefix -arith' before this pass,\n");
|
||||
log("then run 'alumacc' or technology mapping afterwards.\n");
|
||||
log("\n");
|
||||
log("Runtime is linear in the number of module connections plus the total width\n");
|
||||
log("of rewritten candidates. The implementation builds driver and input-use\n");
|
||||
log("indexes once per module and does not scan all cells per candidate.\n");
|
||||
log("\n");
|
||||
}
|
||||
|
||||
void execute(std::vector<std::string> args, RTLIL::Design *design) override
|
||||
{
|
||||
log_header(design, "Executing OPT_ADDCIN pass (carry-in add widening).\n");
|
||||
|
||||
size_t argidx = 1;
|
||||
extra_args(args, argidx, design);
|
||||
|
||||
int total = 0;
|
||||
for (auto module : design->selected_modules()) {
|
||||
OptAddcinWorker worker(module);
|
||||
int count = worker.run();
|
||||
total += count;
|
||||
if (count)
|
||||
log("Rewrote %d add-carry-in pattern%s in module %s.\n",
|
||||
count, count == 1 ? "" : "s", log_id(module));
|
||||
}
|
||||
|
||||
if (total)
|
||||
design->scratchpad_set_bool("opt.did_something", true);
|
||||
log("Rewrote %d add-carry-in pattern%s total.\n", total, total == 1 ? "" : "s");
|
||||
}
|
||||
} OptAddcinPass;
|
||||
|
||||
PRIVATE_NAMESPACE_END
|
||||
|
|
@ -0,0 +1,381 @@
|
|||
# Tests for opt_addcin.
|
||||
#
|
||||
# opt_addcin recognizes a conservative two-$add, three-leaf pattern that
|
||||
# represents A + B + cin with a one-bit unsigned carry input. It rewrites the
|
||||
# two adders into one WIDTH+1 adder whose artificial LSB produces the carry into
|
||||
# the real bit 0. Each test below states the exact RTLIL shape being built and
|
||||
# why the pass should either rewrite it or leave it alone.
|
||||
|
||||
# ============================================================================
|
||||
# Group A: Positive rewrites
|
||||
# ============================================================================
|
||||
|
||||
# Test A1: unsigned carry on the B port of the final add.
|
||||
#
|
||||
# Shape:
|
||||
# add_ab : s = a + b
|
||||
# add_cin : y = s + cin
|
||||
#
|
||||
# The intermediate and final add widths are both 4, and cin is exactly one
|
||||
# unsigned bit, so the pass should replace both adders with one 5-bit $add.
|
||||
log -header "A1: unsigned carry on final-add B port"
|
||||
log -push
|
||||
design -reset
|
||||
read_verilog -icells <<EOF
|
||||
module top(
|
||||
input wire [3:0] a,
|
||||
input wire [3:0] b,
|
||||
input wire cin,
|
||||
output wire [3:0] y
|
||||
);
|
||||
wire [3:0] s;
|
||||
\$add #(.A_WIDTH(4), .B_WIDTH(4), .Y_WIDTH(4), .A_SIGNED(0), .B_SIGNED(0))
|
||||
add_ab (.A(a), .B(b), .Y(s));
|
||||
\$add #(.A_WIDTH(4), .B_WIDTH(1), .Y_WIDTH(4), .A_SIGNED(0), .B_SIGNED(0))
|
||||
add_cin (.A(s), .B(cin), .Y(y));
|
||||
endmodule
|
||||
EOF
|
||||
check -assert
|
||||
design -save preopt
|
||||
equiv_opt -assert opt_addcin
|
||||
design -load preopt
|
||||
opt_addcin
|
||||
select -assert-count 1 t:$add
|
||||
select -assert-count 1 t:$add r:Y_WIDTH=5 %i
|
||||
design -reset
|
||||
log -pop
|
||||
|
||||
# Test A2: unsigned carry on the A port of the final add.
|
||||
#
|
||||
# Shape:
|
||||
# add_ab : s = a + b
|
||||
# add_cin : y = cin + s
|
||||
#
|
||||
# This is the same arithmetic as A1 but with the outer add operands swapped.
|
||||
# The structural assertion proves the matcher is not sensitive to which final
|
||||
# add port receives the carry bit.
|
||||
log -header "A2: unsigned carry on final-add A port"
|
||||
log -push
|
||||
design -reset
|
||||
read_verilog -icells <<EOF
|
||||
module top(
|
||||
input wire [3:0] a,
|
||||
input wire [3:0] b,
|
||||
input wire cin,
|
||||
output wire [3:0] y
|
||||
);
|
||||
wire [3:0] s;
|
||||
\$add #(.A_WIDTH(4), .B_WIDTH(4), .Y_WIDTH(4), .A_SIGNED(0), .B_SIGNED(0))
|
||||
add_ab (.A(a), .B(b), .Y(s));
|
||||
\$add #(.A_WIDTH(1), .B_WIDTH(4), .Y_WIDTH(4), .A_SIGNED(0), .B_SIGNED(0))
|
||||
add_cin (.A(cin), .B(s), .Y(y));
|
||||
endmodule
|
||||
EOF
|
||||
check -assert
|
||||
design -save preopt
|
||||
equiv_opt -assert opt_addcin
|
||||
design -load preopt
|
||||
opt_addcin
|
||||
select -assert-count 1 t:$add
|
||||
select -assert-count 1 t:$add r:Y_WIDTH=5 %i
|
||||
design -reset
|
||||
log -pop
|
||||
|
||||
# Test A3: signed data operands with an unsigned carry input.
|
||||
#
|
||||
# Shape:
|
||||
# add_ab : s = signed(a) + signed(b), sign-extended to 6 bits
|
||||
# add_cin : y = s + unsigned cin
|
||||
#
|
||||
# The pass must preserve the original operand extension behavior before it
|
||||
# appends the artificial LSBs. The final add uses unsigned parameters because
|
||||
# this repository's internal $add checker requires matching signedness on A/B;
|
||||
# since s is already 6 bits, this does not change the modulo-6-bit sum. The
|
||||
# resulting adder is 7 bits wide because the shared wrap boundary is 6 bits.
|
||||
log -header "A3: signed data operands and unsigned carry"
|
||||
log -push
|
||||
design -reset
|
||||
read_verilog -icells <<EOF
|
||||
module top(
|
||||
input wire signed [2:0] a,
|
||||
input wire signed [3:0] b,
|
||||
input wire cin,
|
||||
output wire signed [5:0] y
|
||||
);
|
||||
wire signed [5:0] s;
|
||||
\$add #(.A_WIDTH(3), .B_WIDTH(4), .Y_WIDTH(6), .A_SIGNED(1), .B_SIGNED(1))
|
||||
add_ab (.A(a), .B(b), .Y(s));
|
||||
\$add #(.A_WIDTH(6), .B_WIDTH(1), .Y_WIDTH(6), .A_SIGNED(0), .B_SIGNED(0))
|
||||
add_cin (.A(s), .B(cin), .Y(y));
|
||||
endmodule
|
||||
EOF
|
||||
check -assert
|
||||
design -save preopt
|
||||
equiv_opt -assert opt_addcin
|
||||
design -load preopt
|
||||
opt_addcin
|
||||
select -assert-count 1 t:$add
|
||||
select -assert-count 1 t:$add r:Y_WIDTH=7 %i
|
||||
design -reset
|
||||
log -pop
|
||||
|
||||
# Test A4: unsigned carry is one leaf of the inner add.
|
||||
#
|
||||
# Shape:
|
||||
# add_acin : s = a + cin
|
||||
# add_b : y = s + b
|
||||
#
|
||||
# This is the same three-leaf tree as A1/A2 but with the one-bit carry leaf in
|
||||
# the inner add instead of as the final add's other operand. This covers the
|
||||
# source-order freedom needed after small-tree balancing or frontend variation.
|
||||
log -header "A4: unsigned carry inside inner add"
|
||||
log -push
|
||||
design -reset
|
||||
read_verilog -icells <<EOF
|
||||
module top(
|
||||
input wire [3:0] a,
|
||||
input wire [3:0] b,
|
||||
input wire cin,
|
||||
output wire [3:0] y
|
||||
);
|
||||
wire [3:0] s;
|
||||
\$add #(.A_WIDTH(4), .B_WIDTH(1), .Y_WIDTH(4), .A_SIGNED(0), .B_SIGNED(0))
|
||||
add_acin (.A(a), .B(cin), .Y(s));
|
||||
\$add #(.A_WIDTH(4), .B_WIDTH(4), .Y_WIDTH(4), .A_SIGNED(0), .B_SIGNED(0))
|
||||
add_b (.A(s), .B(b), .Y(y));
|
||||
endmodule
|
||||
EOF
|
||||
check -assert
|
||||
design -save preopt
|
||||
equiv_opt -assert opt_addcin
|
||||
design -load preopt
|
||||
opt_addcin
|
||||
select -assert-count 1 t:$add
|
||||
select -assert-count 1 t:$add r:Y_WIDTH=5 %i
|
||||
design -reset
|
||||
log -pop
|
||||
|
||||
# Test A5: unequal real operand widths.
|
||||
#
|
||||
# Shape:
|
||||
# add_ab : s = a[2:0] + b[5:0], zero-extended to 6 bits
|
||||
# add_cin : y = s + cin
|
||||
#
|
||||
# The two non-carry leaves do not have the same width. The pass must extend
|
||||
# both to the shared 6-bit wrap boundary before appending the artificial LSBs,
|
||||
# producing one 7-bit widened adder.
|
||||
log -header "A5: unequal real operand widths"
|
||||
log -push
|
||||
design -reset
|
||||
read_verilog -icells <<EOF
|
||||
module top(
|
||||
input wire [2:0] a,
|
||||
input wire [5:0] b,
|
||||
input wire cin,
|
||||
output wire [5:0] y
|
||||
);
|
||||
wire [5:0] s;
|
||||
\$add #(.A_WIDTH(3), .B_WIDTH(6), .Y_WIDTH(6), .A_SIGNED(0), .B_SIGNED(0))
|
||||
add_ab (.A(a), .B(b), .Y(s));
|
||||
\$add #(.A_WIDTH(6), .B_WIDTH(1), .Y_WIDTH(6), .A_SIGNED(0), .B_SIGNED(0))
|
||||
add_cin (.A(s), .B(cin), .Y(y));
|
||||
endmodule
|
||||
EOF
|
||||
check -assert
|
||||
design -save preopt
|
||||
equiv_opt -assert opt_addcin
|
||||
design -load preopt
|
||||
opt_addcin
|
||||
select -assert-count 1 t:$add
|
||||
select -assert-count 1 t:$add r:Y_WIDTH=7 %i
|
||||
design -reset
|
||||
log -pop
|
||||
|
||||
# ============================================================================
|
||||
# Group B: Rejected shapes
|
||||
# ============================================================================
|
||||
|
||||
# Test B1: multi-bit carry-like operand.
|
||||
#
|
||||
# Shape:
|
||||
# add_ab : s = a + b
|
||||
# add_c : y = s + c[1:0]
|
||||
#
|
||||
# The identity only creates a carry from a single low bit. A two-bit operand is
|
||||
# a real addend, so both original adders must remain.
|
||||
log -header "B1: reject multi-bit carry operand"
|
||||
log -push
|
||||
design -reset
|
||||
read_verilog -icells <<EOF
|
||||
module top(
|
||||
input wire [3:0] a,
|
||||
input wire [3:0] b,
|
||||
input wire [1:0] c,
|
||||
output wire [3:0] y
|
||||
);
|
||||
wire [3:0] s;
|
||||
\$add #(.A_WIDTH(4), .B_WIDTH(4), .Y_WIDTH(4), .A_SIGNED(0), .B_SIGNED(0))
|
||||
add_ab (.A(a), .B(b), .Y(s));
|
||||
\$add #(.A_WIDTH(4), .B_WIDTH(2), .Y_WIDTH(4), .A_SIGNED(0), .B_SIGNED(0))
|
||||
add_c (.A(s), .B(c), .Y(y));
|
||||
endmodule
|
||||
EOF
|
||||
check -assert
|
||||
equiv_opt -assert opt_addcin
|
||||
design -load postopt
|
||||
select -assert-count 2 t:$add
|
||||
select -assert-count 2 t:$add r:Y_WIDTH=4 %i
|
||||
design -reset
|
||||
log -pop
|
||||
|
||||
# Test B2: signed one-bit carry-like operand.
|
||||
#
|
||||
# Shape:
|
||||
# add_ab : s = a + b
|
||||
# add_cin : y = s + signed(cin)
|
||||
#
|
||||
# A signed one-bit value represents 0 or -1, not a carry-in value of 0 or 1.
|
||||
# The pass must reject it and leave the two original adders intact.
|
||||
log -header "B2: reject signed one-bit carry operand"
|
||||
log -push
|
||||
design -reset
|
||||
read_verilog -icells <<EOF
|
||||
module top(
|
||||
input wire [3:0] a,
|
||||
input wire [3:0] b,
|
||||
input wire signed cin,
|
||||
output wire [3:0] y
|
||||
);
|
||||
wire [3:0] s;
|
||||
\$add #(.A_WIDTH(4), .B_WIDTH(4), .Y_WIDTH(4), .A_SIGNED(0), .B_SIGNED(0))
|
||||
add_ab (.A(a), .B(b), .Y(s));
|
||||
\$add #(.A_WIDTH(4), .B_WIDTH(1), .Y_WIDTH(4), .A_SIGNED(1), .B_SIGNED(1))
|
||||
add_cin (.A(s), .B(cin), .Y(y));
|
||||
endmodule
|
||||
EOF
|
||||
check -assert
|
||||
equiv_opt -assert opt_addcin
|
||||
design -load postopt
|
||||
select -assert-count 2 t:$add
|
||||
select -assert-count 2 t:$add r:Y_WIDTH=4 %i
|
||||
design -reset
|
||||
log -pop
|
||||
|
||||
# Test B3: mismatched intermediate and final wrap boundaries.
|
||||
#
|
||||
# Shape:
|
||||
# add_ab : s[3:0] = a + b
|
||||
# add_cin : y[4:0] = s + cin
|
||||
#
|
||||
# The first add wraps at 4 bits and the second add wraps at 5 bits. Moving to a
|
||||
# single widened add would shift that boundary, so the pass must reject it.
|
||||
log -header "B3: reject mismatched intermediate and final widths"
|
||||
log -push
|
||||
design -reset
|
||||
read_verilog -icells <<EOF
|
||||
module top(
|
||||
input wire [3:0] a,
|
||||
input wire [3:0] b,
|
||||
input wire cin,
|
||||
output wire [4:0] y
|
||||
);
|
||||
wire [3:0] s;
|
||||
\$add #(.A_WIDTH(4), .B_WIDTH(4), .Y_WIDTH(4), .A_SIGNED(0), .B_SIGNED(0))
|
||||
add_ab (.A(a), .B(b), .Y(s));
|
||||
\$add #(.A_WIDTH(4), .B_WIDTH(1), .Y_WIDTH(5), .A_SIGNED(0), .B_SIGNED(0))
|
||||
add_cin (.A(s), .B(cin), .Y(y));
|
||||
endmodule
|
||||
EOF
|
||||
check -assert
|
||||
equiv_opt -assert opt_addcin
|
||||
design -load postopt
|
||||
select -assert-count 2 t:$add
|
||||
select -assert-count 1 t:$add r:Y_WIDTH=4 %i
|
||||
select -assert-count 1 t:$add r:Y_WIDTH=5 %i
|
||||
design -reset
|
||||
log -pop
|
||||
|
||||
# Test B4: intermediate sum has external fanout.
|
||||
#
|
||||
# Shape:
|
||||
# add_ab : s = a + b
|
||||
# add_cin : y = s + cin
|
||||
# s is also a module output
|
||||
#
|
||||
# Replacing add_ab would remove the externally visible intermediate result, so
|
||||
# the pass must reject this even though the arithmetic into y matches.
|
||||
log -header "B4: reject intermediate fanout"
|
||||
log -push
|
||||
design -reset
|
||||
read_verilog -icells <<EOF
|
||||
module top(
|
||||
input wire [3:0] a,
|
||||
input wire [3:0] b,
|
||||
input wire cin,
|
||||
output wire [3:0] y,
|
||||
output wire [3:0] s
|
||||
);
|
||||
\$add #(.A_WIDTH(4), .B_WIDTH(4), .Y_WIDTH(4), .A_SIGNED(0), .B_SIGNED(0))
|
||||
add_ab (.A(a), .B(b), .Y(s));
|
||||
\$add #(.A_WIDTH(4), .B_WIDTH(1), .Y_WIDTH(4), .A_SIGNED(0), .B_SIGNED(0))
|
||||
add_cin (.A(s), .B(cin), .Y(y));
|
||||
endmodule
|
||||
EOF
|
||||
check -assert
|
||||
equiv_opt -assert opt_addcin
|
||||
design -load postopt
|
||||
select -assert-count 2 t:$add
|
||||
select -assert-count 2 t:$add r:Y_WIDTH=4 %i
|
||||
design -reset
|
||||
log -pop
|
||||
|
||||
# ============================================================================
|
||||
# Group C: Interaction with add-chain balancing
|
||||
# ============================================================================
|
||||
|
||||
# Test C1: run opt_balance_tree before opt_addcin.
|
||||
#
|
||||
# Shape:
|
||||
# y1 = a + b
|
||||
# y2 = y1 + c
|
||||
# y3 = y2 + cin
|
||||
#
|
||||
# This test intentionally runs the balancing pass before opt_addcin, matching
|
||||
# the intended flow order. The chain has four leaves, so opt_addcin's
|
||||
# conservative two-add/three-leaf matcher must not consume any of the balanced
|
||||
# tree. The structural assertion checks that add-chain balancing had the chance
|
||||
# to run first and the later carry-in pass did not collapse the larger tree.
|
||||
log -header "C1: opt_balance_tree runs before opt_addcin"
|
||||
log -push
|
||||
design -reset
|
||||
read_verilog <<EOF
|
||||
module top(
|
||||
input wire [3:0] a,
|
||||
input wire [3:0] b,
|
||||
input wire [3:0] c,
|
||||
input wire cin,
|
||||
output wire [3:0] y
|
||||
);
|
||||
wire [3:0] y1;
|
||||
wire [3:0] y2;
|
||||
assign y1 = a + b;
|
||||
assign y2 = y1 + c;
|
||||
assign y = y2 + cin;
|
||||
endmodule
|
||||
EOF
|
||||
check -assert
|
||||
design -save orig
|
||||
opt_balance_tree -arith
|
||||
opt_addcin
|
||||
design -save postopt
|
||||
design -reset
|
||||
design -copy-from orig -as gold top
|
||||
design -copy-from postopt -as gate top
|
||||
rename -hide
|
||||
equiv_make gold gate equiv
|
||||
equiv_simple equiv
|
||||
equiv_status -assert equiv
|
||||
design -load postopt
|
||||
select -assert-count 3 t:$add
|
||||
design -reset
|
||||
log -pop
|
||||
Loading…
Reference in New Issue