3
0
Fork 0
mirror of https://github.com/YosysHQ/yosys synced 2025-04-16 05:48:44 +00:00
yosys/passes/techmap/booth.cc
andyfox-rushc e63a0685ab
Parallel wallace tree
Signed-off-by: andyfox-rushc <andy@rushc.com>
2024-10-20 16:14:53 -07:00

1428 lines
47 KiB
C++

/*
* yosys -- Yosys Open SYnthesis Suite
*
* Copyright (C) 2023 Andy Fox <andy@rushc.com> https://www.linkedin.com/in/awfox/
*
* 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.
*
*/
/*
Booth Pass
----------
Replace $mul with booth encoded multipliers. Two different
architectures used for signed/unsigned.
References:
Signed architecture: A Low Power Radix-4 Booth Multipliers with Pre-Encoded Mechanism, IEEE Access
https://ieeexplore.ieee.org/document/9121226
Unsigned architecture: Gary Bewick, Fast Multiplication algorithms and implementation. Stanford PhD:
http://i.stanford.edu/pub/cstr/reports/csl/tr/94/617/CSL-TR-94-617.pdf
How to use:
Add booth pass to your yosys script eg:
read_verilog smultiply5_rtl.v
opt
wreduce
opt
booth
alumacc
maccmap
opt
techmap -map ./techmap.v
dfflibmap -liberty NangateOpenCellLibrary_typical.lib
abc -liberty NangateOpenCellLibrary_typical.lib
stat -liberty NangateOpenCellLibrary_typical.lib
write_verilog -norename booth_final.v
or in generic synthesis call with -booth argument:
synth -top my_design -booth
*/
//#define DEBUG_CPA
//#define DEBUG_CSA 1
#include "kernel/sigtools.h"
#include "kernel/yosys.h"
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
struct BoothPassWorker {
RTLIL::Module *module;
SigMap sigmap;
int booth_counter;
bool lowpower = false;
bool mapped_cpa = false;
BoothPassWorker(RTLIL::Module *module) : module(module), sigmap(module) { booth_counter = 0; }
// Booth unsigned decoder lsb
SigBit Bur4d_lsb(std::string name, SigBit lsb_i, SigBit one_i, SigBit s_i)
{
SigBit and_op = module->AndGate(NEW_ID_SUFFIX(name), lsb_i, one_i);
return module->XorGate(NEW_ID_SUFFIX(name), and_op, s_i);
}
// Booth unsigned radix4 decoder
SigBit Bur4d_n(std::string name, SigBit yn_i, SigBit ynm1_i, SigBit one_i, SigBit two_i, SigBit s_i)
{
// ppij = ((yn & one) | (ynm1 & two)) ^ s;
SigBit an1 = module->AndGate(NEW_ID_SUFFIX(name), yn_i, one_i);
SigBit an2 = module->AndGate(NEW_ID_SUFFIX(name), ynm1_i, two_i);
SigBit or1 = module->OrGate(NEW_ID_SUFFIX(name), an1, an2);
return module->XorGate(NEW_ID_SUFFIX(name), s_i, or1);
}
// Booth unsigned radix4 decoder
SigBit Bur4d_msb(std::string name, SigBit msb_i, SigBit two_i, SigBit s_i)
{
// ppij = (msb & two) ^ s;
SigBit an1 = module->AndGate(NEW_ID_SUFFIX(name), msb_i, two_i);
return module->XorGate(NEW_ID_SUFFIX(name), s_i, an1);
}
// half adder, used in CPA
void BuildHa(std::string name, SigBit a_i, SigBit b_i, SigBit &s_o, SigBit &c_o)
{
s_o = module->XorGate(NEW_ID_SUFFIX(name), a_i, b_i);
c_o = module->AndGate(NEW_ID_SUFFIX(name), a_i, b_i);
}
// Booth unsigned radix 4 encoder
void BuildBur4e(std::string name, SigBit y0_i, SigBit y1_i, SigBit y2_i, SigBit &one_o, SigBit &two_o, SigBit &s_o, SigBit &sb_o)
{
one_o = module->XorGate(NEW_ID_SUFFIX(name), y0_i, y1_i);
s_o = y2_i;
sb_o = module->NotGate(NEW_ID_SUFFIX(name), y2_i);
SigBit y1_xnor_y2 = module->XnorGate(NEW_ID_SUFFIX(name), y1_i, y2_i);
two_o = module->NorGate(NEW_ID_SUFFIX(name), y1_xnor_y2, one_o);
}
void BuildBr4e(std::string name, SigBit y2_m1_i,
SigBit y2_i, // y2i
SigBit y2_p1_i, SigBit &negi_o, SigBit &twoi_n_o, SigBit &onei_n_o, SigBit &cori_o)
{
auto y2_p1_n = module->NotGate(NEW_ID_SUFFIX(name), y2_p1_i);
auto y2_n = module->NotGate(NEW_ID_SUFFIX(name), y2_i);
auto y2_m1_n = module->NotGate(NEW_ID_SUFFIX(name), y2_m1_i);
negi_o = y2_p1_i;
// twoi_n = ~(
// (y2_p1_n & y2_i & y2_m1_i) |
// (y2_p1 & y2_n & y2_m1_n)
// )
twoi_n_o = module->NorGate(NEW_ID_SUFFIX(name),
module->AndGate(NEW_ID_SUFFIX(name), y2_p1_n, module->AndGate(NEW_ID_SUFFIX(name), y2_i, y2_m1_i)),
module->AndGate(NEW_ID_SUFFIX(name), y2_p1_i, module->AndGate(NEW_ID_SUFFIX(name), y2_n, y2_m1_n)));
// onei_n = ~(y2_m1_i ^ y2_i);
onei_n_o = module->XnorGate(NEW_ID_SUFFIX(name), y2_m1_i, y2_i);
// cori = (y2_m1_n | y2_n) & y2_p1_i;
cori_o = module->AndGate(NEW_ID_SUFFIX(name), module->OrGate(NEW_ID_SUFFIX(name), y2_m1_n, y2_n), y2_p1_i);
}
//
// signed booth radix 4 decoder
//
void BuildBr4d(std::string name, SigBit nxj_m1_i, SigBit twoi_n_i, SigBit xj_i, SigBit negi_i, SigBit onei_n_i, SigBit &ppij_o, SigBit &nxj_o)
{
// nxj_in = xnor(xj,negi)
// nxj_o = xnj_in,
// ppij = ~( (nxj_m1_i | twoi_n_i) & (nxj_int | onei_n_i));
nxj_o = module->XnorGate(NEW_ID_SUFFIX(name), xj_i, negi_i);
ppij_o = module->NandGate(NEW_ID_SUFFIX(name), module->OrGate(NEW_ID_SUFFIX(name), nxj_m1_i, twoi_n_i),
module->OrGate(NEW_ID_SUFFIX(name), nxj_o, onei_n_i));
}
/*
In signed case 1st two bits best realised
using non-booth encoded logic. We can save a booth
encoder for the first couple of bits.
*/
void BuildBoothQ1(std::string name, SigBit negi_i, SigBit cori_i, SigBit x0_i, SigBit x1_i, SigBit y0_i, SigBit y1_i, SigBit &nxj_o,
SigBit &cor_o, SigBit &pp0_o, SigBit &pp1_o)
{
/*
assign NXJO = ~(X1 ^ NEGI);
assign PP0 = (X0 & Y0);
//and terms for multiply
wire pp1_1_int = X1 & Y0;
wire pp1_2_int = X0 & Y1;
//sum generation for pp[1]
assign PP1 = pp1_1_int ^ pp1_2_int;
//correction propagation
assign CORO = (~PP1 & ~PP0)? CORI : 1'b0;
*/
nxj_o = module->XnorGate(NEW_ID_SUFFIX(name), x1_i, negi_i);
pp0_o = module->AndGate(NEW_ID_SUFFIX(name), x0_i, y0_i);
SigBit pp1_1_int = module->AndGate(NEW_ID_SUFFIX(name), x1_i, y0_i);
SigBit pp1_2_int = module->AndGate(NEW_ID_SUFFIX(name), x0_i, y1_i);
pp1_o = module->XorGate(NEW_ID_SUFFIX(name), pp1_1_int, pp1_2_int);
SigBit pp1_nor_pp0 = module->NorGate(NEW_ID_SUFFIX(name), pp1_o, pp0_o);
cor_o = module->AndGate(NEW_ID_SUFFIX(name), pp1_nor_pp0, cori_i);
}
void BuildBitwiseFa(Module *mod, std::string name, const SigSpec &sig_a, const SigSpec &sig_b, const SigSpec &sig_c, const SigSpec &sig_x,
const SigSpec &sig_y, const std::string &src = "")
{
// We can't emit a single wide full-adder cell here since
// there would typically be feedback loops involving the cells'
// input and output ports, and Yosys doesn't cope well with
// those
log_assert(sig_a.size() == sig_b.size());
log_assert(sig_a.size() == sig_c.size());
log_assert(sig_a.size() == sig_x.size());
log_assert(sig_a.size() == sig_y.size());
for (int i = 0; i < sig_a.size(); i++)
mod->addFa(stringf("%s[%d]", name.c_str(), i), sig_a[i], sig_b[i], sig_c[i], sig_x[i], sig_y[i], src);
}
void run()
{
for (auto cell : module->selected_cells()) {
if (cell->type != ID($mul))
continue;
SigSpec A = cell->getPort(ID::A);
SigSpec B = cell->getPort(ID::B);
SigSpec Y = cell->getPort(ID::Y);
int x_sz = GetSize(A), y_sz = GetSize(B), z_sz = GetSize(Y);
if (x_sz < 4 || y_sz < 4 || z_sz < 8) {
log_debug("Not mapping cell %s sized at %dx%x, %x: size below threshold\n", log_id(cell), x_sz, y_sz, z_sz);
continue;
}
log_assert(cell->getParam(ID::A_SIGNED).as_bool() == cell->getParam(ID::B_SIGNED).as_bool());
bool is_signed = cell->getParam(ID::A_SIGNED).as_bool();
log("Mapping cell %s to %s Booth multiplier\n", log_id(cell), is_signed ? "signed" : "unsigned");
// To simplify the generator size the arguments
// to be the same. Then allow logic synthesis to
// clean things up. Size to biggest
int x_sz_revised = x_sz;
int y_sz_revised = y_sz;
if (x_sz != y_sz) {
if (x_sz < y_sz) {
if (y_sz % 2 != 0) {
x_sz_revised = y_sz + 1;
y_sz_revised = y_sz + 1;
} else {
x_sz_revised = y_sz;
}
} else {
if (x_sz % 2 != 0) {
y_sz_revised = x_sz + 1;
x_sz_revised = x_sz + 1;
} else {
y_sz_revised = x_sz;
}
}
} else {
if (x_sz % 2 != 0) {
y_sz_revised = y_sz + 1;
x_sz_revised = x_sz + 1;
}
}
log_assert((x_sz_revised == y_sz_revised) && (x_sz_revised % 2 == 0) && (y_sz_revised % 2 == 0));
A.extend_u0(x_sz_revised, is_signed);
B.extend_u0(y_sz_revised, is_signed);
// Make sure output domain is big enough to take
// all combinations.
// Later logic synthesis will kill unused
// portions of the output domain.
int required_op_size = x_sz_revised + y_sz_revised;
if (required_op_size != z_sz) {
SigSpec expanded_Y = module->addWire(NEW_ID, required_op_size);
SigSpec Y_driver = expanded_Y;
Y_driver.extend_u0(Y.size(), is_signed);
module->connect(Y, Y_driver);
Y = expanded_Y;
}
log_assert(GetSize(Y) == required_op_size);
if (!lowpower)
CreateBoothMult(module,
A, // multiplicand
B, // multiplier(scanned)
Y, // result
is_signed);
else
CreateBoothLowpowerMult(module,
A, // multiplicand
B, // multiplier(scanned)
Y, // result
is_signed);
module->remove(cell);
booth_counter++;
}
}
SigSig WallaceSum(int width, std::vector<SigSpec> summands)
{
for (auto &s : summands)
s.extend_u0(width);
while (summands.size() > 2) {
std::vector<SigSpec> new_summands;
int i;
for (i = 0; i < (int)summands.size() - 2; i += 3) {
SigSpec x = module->addWire(NEW_ID, width);
SigSpec y = module->addWire(NEW_ID, width);
BuildBitwiseFa(module, NEW_ID.str(), summands[i], summands[i + 1], summands[i + 2], x, y);
new_summands.push_back(y);
new_summands.push_back({x.extract(0, width - 1), State::S0});
}
new_summands.insert(new_summands.begin(), summands.begin() + i, summands.end());
std::swap(summands, new_summands);
}
if (!summands.size())
return SigSig(SigSpec(width, State::S0), SigSpec(width, State::S0));
else if (summands.size() == 1)
return SigSig(summands[0], SigSpec(width, State::S0));
else
return SigSig(summands[0], summands[1]);
}
/*
Build Multiplier.
-------------------------
Uses a generic booth multiplier
*/
void CreateBoothMult(RTLIL::Module *module,
SigSpec X, // multiplicand
SigSpec Y, // multiplier
SigSpec Z, bool is_signed)
{ // result
int z_sz = Z.size();
SigSpec one_int, two_int, s_int, sb_int;
int encoder_count = 0;
BuildBoothMultEncoders(Y, one_int, two_int, s_int, sb_int, module, encoder_count, is_signed);
// Build the decoder rows
// format of each Partial product to be passed to CSA
// tree builder:
//
// Bits to be added
// Shift
// Sign bit to be added
//
std::vector<std::tuple<SigSpec, int, SigBit>> ppij_int;
// Row 0: special case 1. Format S/.S.S.C.Data
SigSpec ppij_row_0;
BuildBoothMultDecoderRow0(module, X, s_int, sb_int, one_int, two_int, ppij_row_0, is_signed);
// data, shift, sign
ppij_int.push_back(std::make_tuple(ppij_row_0, 0, s_int[0]));
for (int i = 1; i < encoder_count; i++) {
// format 1,S.Data.shift = encoder_ix*2,sign = sb_int[i]
SigSpec ppij_row_n;
BuildBoothMultDecoderRowN(module,
X, // multiplicand
one_int[i], two_int[i], s_int[i], sb_int[i], ppij_row_n, i, is_signed);
// data, shift, sign
ppij_int.push_back(std::make_tuple(ppij_row_n, i * 2, s_int[i]));
}
// Debug dump out partial products
// DebugDumpPP(ppij_int);
// Summation of Partial Products (Wallace Tree)
std::vector<SigSpec> aligned_pp;
aligned_pp.resize(encoder_count + 1); // make an entirely redundant row
// just for sign bit in lsb. (We then filter this out).
// resize all to be same size as z
for (int i = 0; i < encoder_count + 1; i++)
aligned_pp[i].extend_u0(z_sz);
AlignPP(z_sz, ppij_int, aligned_pp);
// Debug: dump out aligned partial products.
// Later on yosys will clean up unused constants
#ifdef DEBUG_CSA
DebugDumpAlignPP(aligned_pp);
#endif
SigSpec s_vec;
SigSpec c_vec;
std::vector<std::vector<RTLIL::Cell *>> debug_csa_trees;
BuildCSATree(module, aligned_pp, s_vec, c_vec, debug_csa_trees);
#ifdef DEBUG_CSA
printf("Size of Sum Vec %d Size of Carry Vec %d\n", s_vec.size(), c_vec.size());
printf("Sum Vec %s \n", s_vec.as_string().c_str());
printf("Carry Vec %s \n", c_vec.as_string().c_str());
printf("Size of Sum %d Size of Result %d\n", s_vec.size(), Z.size());
#endif
// Debug code: Dump out the csa trees
// DumpCSATrees(debug_csa_trees);
// BuildCPA(module, s_vec, c_vec, Z);
// Build the CPA to do the final accumulation.
// log_assert(wtree_sum.second[0] == State::S0);
// wtree_sum.first, {State::S0, wtree_sum.second.extract_end(1)}, Z);
// if (mapped_cpa)
// BuildCPA(module, wtree_sum.first, {State::S0, wtree_sum.second.extract_end(1)}, Z);
// else
// printf("Svec size %d c_vec size %d\n", s_vec.size(), c_vec.size());
// module->addAdd(NEW_ID, s_vec,
// {c_vec.extract(0,c_vec.size()-1),State::S0}, Z);
// Brent Kung adder
SigSpec g = module->addWire(NEW_ID, s_vec.size());
SigSpec p = module->addWire(NEW_ID, s_vec.size());
SigSpec co = module->addWire(NEW_ID, s_vec.size());
module->addAnd(NEW_ID, s_vec, {c_vec.extract(0, c_vec.size() - 1), State::S0}, g); // generate
module->addXor(NEW_ID, s_vec, {c_vec.extract(0, c_vec.size() - 1), State::S0}, p); // propagate
auto lcu = module->addCell(NEW_ID, ID($lcu));
auto lcu_int = module->addWire(NEW_ID, s_vec.size());
lcu->setParam(ID::WIDTH, s_vec.size());
lcu->setPort(ID::G, g);
lcu->setPort(ID::P, p);
lcu->setPort(ID::CI, State::S0);
lcu->setPort(ID::CO, lcu_int);
module->addXor(NEW_ID, p, {lcu_int, State::S0}, Z);
}
/*
Build Row 0 of decoders
*/
void BuildBoothMultDecoderRow0(RTLIL::Module *module,
SigSpec X, // multiplicand
SigSpec s_int, SigSpec sb_int, SigSpec one_int, SigSpec two_int, SigSpec &ppij_vec, bool is_signed)
{
(void)sb_int;
(void)module;
int x_sz = GetSize(X);
SigBit ppij;
// lsb
ppij_vec.append(Bur4d_lsb("row0_lsb_dec", X[0], one_int[0], s_int[0]));
// 1..xsize -1
for (int i = 1; i < x_sz; i++)
ppij_vec.append(Bur4d_n(stringf("row0_dec_%d", i), X[i], X[i - 1], one_int[0], two_int[0], s_int[0]));
// The redundant bit. Duplicate decoding of last bit.
if (!is_signed) {
ppij_vec.append(Bur4d_msb("row0_dec_msb", X.msb(), two_int[0], s_int[0]));
} else {
ppij_vec.append(Bur4d_n("row0_dec_msb", X.msb(), X.msb(), one_int[0], two_int[0], s_int[0]));
}
// append the sign bits
if (is_signed) {
SigBit e =
module->XorGate(NEW_ID, s_int[0], module->AndGate(NEW_ID, X.msb(), module->OrGate(NEW_ID, two_int[0], one_int[0])));
ppij_vec.append({module->NotGate(NEW_ID, e), e, e});
} else {
// append the sign bits
ppij_vec.append({module->NotGate(NEW_ID, s_int[0]), s_int[0], s_int[0]});
}
}
// Build a generic row of decoders.
void BuildBoothMultDecoderRowN(RTLIL::Module *module,
SigSpec X, // multiplicand
SigSpec one_int, SigSpec two_int, SigSpec s_int, SigSpec sb_int, SigSpec &ppij_vec, int row_ix, bool is_signed)
{
(void)module;
int x_sz = GetSize(X);
// lsb
ppij_vec.append(Bur4d_lsb(stringf("row_%d_lsb_dec", row_ix), X[0], one_int, s_int));
// core bits
for (int i = 1; i < x_sz; i++)
ppij_vec.append(Bur4d_n(stringf("row_%d_dec_%d", row_ix, i), X[i], X[i - 1], one_int, two_int, s_int));
if (!is_signed) { // redundant bit
ppij_vec.append(Bur4d_msb("row_dec_red", X[x_sz - 1], two_int, s_int));
} else {
ppij_vec.append(Bur4d_n(stringf("row_%d_dec_msb", row_ix), X[x_sz - 1], X[x_sz - 1], one_int, two_int, s_int));
}
ppij_vec.append(!is_signed
? sb_int[0]
: module->XorGate(NEW_ID, sb_int, module->AndGate(NEW_ID, X.msb(), module->OrGate(NEW_ID, two_int, one_int))));
ppij_vec.append(State::S1);
}
void DumpModule()
{
printf("Cells\n");
for (auto cell : module->cells_) {
printf("+Cell %s type %s\n", cell.first.c_str(), cell.second->type.c_str());
printf("Connections on cell\n");
for (auto c : cell.second->connections()) {
printf("Connection %s\n", c.first.c_str());
DumpSigSpec(1, c.second);
}
printf("-\n");
}
printf("Wires\n");
for (auto w : module->wires_) {
printf("Wire %s (%p) width %d port_id %d dir %s\n", w.first.c_str(), w.second, w.second->width, w.second->port_id,
w.second->port_input ? "input" : "output");
}
printf("Connections\n");
for (auto con : module->connections_) {
SigSpec from = con.first;
SigSpec to = con.second;
printf("\t From connection %s width %d bit lengtht %d", from.is_wire() ? "wire" : "?", from.size(), from.bits().size());
printf("\tTo connection %s ", to.is_wire() ? "wire" : to.is_bit() ? "bit" : to.is_chunk() ? "chunk" : "?");
if (to.is_bit()) {
RTLIL::SigBit sb = to.as_bit();
if (to.as_bit().wire) {
printf("Sig bit wire offset %d dir %s\n", to.as_bit().offset, to.as_bit().wire->port_input ? "ip" : "op");
} else
printf("Sig bit value is state\n");
}
printf("\n");
}
}
std::string Indent(int indent)
{
std::string ret = "";
for (int i = 0; i < indent; i++)
ret = ret + '\t';
return ret;
}
void DumpSigSpec(int ident, RTLIL::SigSpec sp, std::string hdr = "")
{
printf("%s Sig spec %s\n", Indent(ident).c_str(), hdr.c_str());
printf("%s Width %d\n", Indent(ident + 1).c_str(), sp.size());
printf("%s Type: %s\n", Indent(ident + 1).c_str(),
sp.is_wire()
? "wire"
: sp.is_chunk() ? "chunk"
: sp.is_bit() ? "bit"
: sp.is_fully_const()
? "fully const"
: sp.is_fully_def()
? "fully def"
: sp.is_fully_undef() ? "fully undef" : sp.has_marked_bits() ? "marked bits" : "unk");
printf("%s Number of bits %d Number of chunks %d\n", Indent(ident + 1).c_str(), sp.bits().size(), sp.chunks().size());
printf("%s Bits:\n", Indent(ident + 1).c_str());
int count = 0;
// b is SigBit
for (auto b : sp.bits()) {
if (b.wire) {
printf("%s [%d] Bit wire %s (%p)\n", Indent(ident + 1).c_str(), count, b.wire->name.c_str(), b.wire);
} else {
if (b.data == State::S0)
printf("%s Bit constant 0\n", Indent(ident + 1).c_str());
else if (b.data == State::S1)
printf("%s Bit constant 1\n", Indent(ident + 1).c_str());
else
printf("%s Unknown constant\n", Indent(ident + 1).c_str());
}
count++;
}
/*
count=0;
printf("%s Chunks:\n",Indent(ident).c_str());
if (sp.chunks().size() >0){
for (auto sc: sp.chunks()){
printf("%s [%d] Chunk wire %s (%p)width %d offset %d\n",
Indent(ident).c_str(),
count,
sc.wire ? sc.wire -> name.c_str(): "non-wire chunk: empty",
sc.wire,
sc.width,
sc.offset);
count++;
}
}
*/
}
void DebugDumpAlignPP(std::vector<SigSpec> &aligned_pp)
{
printf("Aligned & Padded Partial products\n");
int pp_ix = 0;
for (auto pp_row : aligned_pp) {
printf("PP_%d \t", pp_ix);
for (int i = 0; i < pp_row.size(); i++) {
RTLIL::SigSpec sb_el = pp_row.extract(i);
std::string col_ix = "PP_" + std::to_string(pp_ix) + " col" + std::to_string(i);
DumpSigSpec(1, sb_el, col_ix);
}
printf("\n");
pp_ix++;
}
}
// Debug routines to inspect intermediate results
void DebugDumpPP(std::vector<std::tuple<std::vector<RTLIL::Wire *>, int, RTLIL::Wire *>> &ppij_int)
{
printf("Debug dump of partial products\n");
int pp_ix = 0;
for (auto pp : ppij_int) {
int shift = get<1>(pp);
RTLIL::Wire *sign_bit = get<2>(pp);
printf("PP %d\n", pp_ix);
printf("\tShift %d\n", shift);
printf("\tData (0 lsb)\n\t");
int ix = 0;
for (auto pp_wire : get<0>(pp)) {
RTLIL::IdString wire_name = pp_wire->name;
printf(" [%d]:%s ", ix, wire_name.c_str());
ix++;
}
printf("\n");
printf("\tSign bit to add in: %s\n", sign_bit->name.c_str());
pp_ix++;
}
}
void DumpCSATrees(std::vector<std::vector<RTLIL::Cell *>> &debug_csa_trees)
{
int i = 0;
for (auto csa_tree : debug_csa_trees) {
printf("CSA Tree column %d\n", i);
int ix = 0;
for (auto csa_elem : csa_tree) {
printf("\tCell %d %s type %s\n", ix, csa_elem->name.c_str(), csa_elem->type.c_str());
if (csa_elem->getPort(ID::A) == State::S0)
printf("\tA set to constant 0\n");
else if (csa_elem->getPort(ID::A) == State::S1)
printf("\tA set to constant 1\n");
else
printf("\tA driven by %s\n", csa_elem->getPort(ID::A).as_wire()->name.c_str());
if (csa_elem->getPort(ID::B) == State::S0)
printf("\tB set to constant 0\n");
else if (csa_elem->getPort(ID::B) == State::S1)
printf("\tB set to constant 1\n");
else
printf("\tB driven by %s\n", csa_elem->getPort(ID::B).as_wire()->name.c_str());
if (csa_elem->getPort(ID::C) == State::S0)
printf("\tC set to constant 0\n");
else if (csa_elem->getPort(ID::C) == State::S1)
printf("\tC set to constant 1\n");
else
printf("\tC driven by %s\n", csa_elem->getPort(ID::C).as_wire()->name.c_str());
printf("Carry out: %s\n", csa_elem->getPort(ID::X).as_wire()->name.c_str());
printf("Sum out: %s\n", csa_elem->getPort(ID::Y).as_wire()->name.c_str());
ix++;
}
i++;
}
}
/*
Decompose the bits for pp[x] 2**n to reduce into groups of 3. Each group
will ultimately feed a csa. Where we have odd bits
we put them in A, B.
*/
void makeCSAGroups(SigSpec bits_to_reduce, std::vector<std::tuple<SigBit, SigBit, SigBit>> &groups, SigBit &A, SigBit &B)
{
int bit_ix = 0;
A = State::S0;
B = State::S0;
while (bit_ix < bits_to_reduce.size()) {
if (bit_ix == (bits_to_reduce.size() - 1)) {
A = bits_to_reduce.extract(bit_ix);
bit_ix++;
} else if (bit_ix == bits_to_reduce.size() - 2) {
A = bits_to_reduce.extract(bit_ix);
B = bits_to_reduce.extract(bit_ix + 1);
bit_ix = bit_ix + 2;
} else {
groups.push_back(std::make_tuple(bits_to_reduce.extract(bit_ix), bits_to_reduce.extract(bit_ix + 1),
bits_to_reduce.extract(bit_ix + 2)));
bit_ix = bit_ix + 3;
}
}
}
void makeCSARow(int row_ix, std::vector<std::vector<RTLIL::Cell *>> &debug_csa_trees, RTLIL::Module *module,
std::vector<std::tuple<SigBit, SigBit, SigBit>> &groups, SigBit &A, SigBit &B, SigSpec &carry_bits_to_sum,
SigSpec &next_bits_to_reduce)
{
int col_ix = 0;
std::vector<RTLIL::Cell *> csa_row;
for (auto g : groups) {
std::string csa_name = "csa_" + std::to_string(row_ix) + "_" + std::to_string(col_ix);
auto csa = module->addCell(new_id(csa_name, __LINE__, ""), ID($fa));
csa_row.push_back(csa);
csa->setParam(ID::WIDTH, 1);
col_ix++;
csa->setPort(ID::A, get<0>(g));
csa->setPort(ID::B, get<1>(g));
csa->setPort(ID::C, get<2>(g));
std::string s_name = "s_" + std::to_string(row_ix) + "_" + std::to_string(col_ix);
std::string c_name = "c_" + std::to_string(row_ix) + "_" + std::to_string(col_ix);
SigBit sum = module->addWire(new_id(s_name, __LINE__, ""), 1);
SigBit carry = module->addWire(new_id(c_name, __LINE__, ""), 1);
csa->setPort(ID::Y, sum);
csa->setPort(ID::X, carry);
carry_bits_to_sum.append(carry);
next_bits_to_reduce.append(sum);
#ifdef DEBUG_CSA
printf("CSA Appending carry bit %s\n", carry.is_wire() ? carry.wire->name.c_str() : "unk");
printf("New number of carry bits %d \n", carry_bits_to_sum.size());
DumpSigSpec(1, carry_bits_to_sum);
#endif
}
if (A != State::S0 && B != State::S0) {
SigBit sum;
SigBit carry;
std::string ha_name = "ha_" + std::to_string(row_ix) + "_" + std::to_string(col_ix);
BuildHa(ha_name, A, B, sum, carry);
carry_bits_to_sum.append(carry);
next_bits_to_reduce.append(sum);
#ifdef DEBUG_CSA
printf("HA Appending carry bit %s\n", carry.is_wire() ? carry.wire->name.c_str() : "unk");
printf("New number of carry bits %d \n", carry_bits_to_sum.size());
DumpSigSpec(1, carry_bits_to_sum);
#endif
} else if (A != State::S0) {
next_bits_to_reduce.append(A);
col_ix++;
}
debug_csa_trees.push_back(csa_row);
}
void ReduceBitsParallel(RTLIL::Module *module, int column_ix, SigSpec column_bits, SigBit &s_result, SigBit &c_result,
SigSpec &carry_bits_to_sum, std::vector<std::vector<RTLIL::Cell *>> &debug_csa_trees)
{
(void)column_ix;
#ifdef DEBUG_CSA
if (column_bits.size() > 0)
printf("Parallel Column %d reduce bits parallel given %d bits (%s) to reduce\n", column_ix, column_bits.size(),
column_bits.as_string().c_str());
else
printf("Column %d given %d bits\n", column_ix, 0);
#endif
SigSpec next_bits_to_reduce = column_bits;
std::vector<std::tuple<SigBit, SigBit, SigBit>> groups;
SigBit A;
SigBit B;
int row_ix = 0;
while (next_bits_to_reduce.size() > 1) {
#ifdef DEBUG_CSA
printf("Row %d Column %d Reducing %d bits\n", row_ix, column_ix, next_bits_to_reduce.size());
#endif
groups.clear();
makeCSAGroups(next_bits_to_reduce, groups, A, B);
#ifdef DEBUG_CSA
printf("Row %d Got %ld groups to reduce + A %d + B %d\n", row_ix, groups.size(), A == State::S0 ? 0 : 1,
B == State::S0 ? 0 : 1);
#endif
SigSpec csa_row_bits;
int csa_count_before = debug_csa_trees.size();
makeCSARow(row_ix, debug_csa_trees, module, groups, A, B, carry_bits_to_sum, csa_row_bits);
int csa_count_after = debug_csa_trees.size();
#ifdef DEBUG_CSA
printf("Reduced %d bits to %d bits using %d csa components\n", next_bits_to_reduce.size(), csa_row_bits.size(),
csa_count_after - csa_count_before);
#endif
next_bits_to_reduce = csa_row_bits;
row_ix++;
}
s_result = State::S0;
c_result = State::S0;
if (next_bits_to_reduce.size() == 1)
s_result = next_bits_to_reduce.extract(0);
if (carry_bits_to_sum.size() > 0) {
c_result = carry_bits_to_sum.extract(carry_bits_to_sum.size() - 1);
carry_bits_to_sum.remove(carry_bits_to_sum.size() - 1);
}
}
/*
Build a parallel CSA tree
*/
void BuildCSATree(RTLIL::Module *module, std::vector<SigSpec> &bits_to_reduce, SigSpec &s_vec, SigSpec &c_vec,
std::vector<std::vector<RTLIL::Cell *>> &debug_csa_trees)
{
if (!(bits_to_reduce.size() > 0))
return;
int column_size = bits_to_reduce[0].size();
int row_size = bits_to_reduce.size();
SigSpec carry_bits_to_add_to_next_column;
#ifdef DEBUG_CSA
printf("Reducing %d partial products. Each with %d columns\n", row_size, column_size);
#endif
for (int column_ix = 0; column_ix < column_size; column_ix++) {
// get the bits in this column.
SigSpec column_bits;
for (int row_ix = 0; row_ix < row_size; row_ix++) {
if (bits_to_reduce[row_ix][column_ix] != State::S0)
column_bits.append(bits_to_reduce[row_ix][column_ix]);
}
for (auto c : carry_bits_to_add_to_next_column) {
#ifdef DEBUG_CSA
printf("\t Propagating column bit %s to column %d from column %d\n", c.wire->name.c_str(), column_ix, column_ix - 1);
#endif
column_bits.append(c);
}
carry_bits_to_add_to_next_column = {};
#ifdef DEBUG_CSA
printf("Column %d Reducing %d bits\n", column_ix, column_bits.size());
for (auto b : column_bits) {
printf("\t %s\n", b.wire ? b.wire->name.c_str()
: b.data == State::S0 ? "Const 0" : (b.data == State::S1 ? "Const 1" : " Unk constant"));
}
printf("\n");
#endif
// Build parallel reduction tree
SigBit s, c;
ReduceBitsParallel(module, column_ix, column_bits, s, c, carry_bits_to_add_to_next_column, debug_csa_trees);
s_vec.append(s);
c_vec.append(c);
#ifdef DEBUG_CSA
printf("#Carry bits to next column: %d (%s)\n", carry_bits_to_add_to_next_column.size(),
carry_bits_to_add_to_next_column.as_string().c_str());
for (auto b : carry_bits_to_add_to_next_column) {
printf("\t %s\n", b.wire ? b.wire->name.c_str()
: b.data == State::S0 ? "Const 0" : (b.data == State::S1 ? "Const 1" : " Unk constant"));
}
printf("\n");
#endif
}
}
/*
Alignment:
---------
Concept traverse from last row.
Pad row by shift
Add sign bit from prior row to 2 bits right of end of data.
Example
SCDDDDDDD- +S
DDDDDDDD_
==>
SCDDDDDDD-
DDDDDDDD_S <-- prior rows sign bit added 2 columns to right on next row.
Pad out rows with zeros and left the opt pass clean them up.
*/
void AlignPP(int z_sz, std::vector<std::tuple<SigSpec, int, SigBit>> &ppij_int, std::vector<SigSpec> &aligned_pp)
{
unsigned aligned_pp_ix = aligned_pp.size() - 1;
// default is zero for everything (so don't have to think to hard
// about padding).
for (unsigned i = 0; i < aligned_pp.size(); i++) {
for (int j = 0; j < z_sz; j++) {
aligned_pp[i][j] = State::S0;
}
}
// for very last row we just have the sign bit
// Note that the aligned_pp is one row bigger
// than the ppij_int. We put the sign bit
// in first column of the last partial product
// which is at index corresponding to size of multiplicand
{
int prior_row_idx = get<1>(ppij_int[aligned_pp_ix - 1]);
SigBit prior_row_sign = get<2>(ppij_int[aligned_pp_ix - 1]);
if (prior_row_idx < z_sz)
aligned_pp[aligned_pp_ix][prior_row_idx] = prior_row_sign;
}
for (int row_ix = aligned_pp_ix - 1; row_ix >= 0; row_ix--) {
int shift_amount = get<1>(ppij_int[row_ix]);
// copy in data
int copy_ix = shift_amount;
for (auto w : get<0>(ppij_int[row_ix])) {
if (copy_ix < aligned_pp[row_ix].size()) {
aligned_pp[row_ix][copy_ix] = w;
}
copy_ix++;
}
// copy in the sign bit from the prior row
if (row_ix > 0) {
// if sign bit on prior row, copy in
// the destination of the sign bit is the (row_ix -1)*2
// eg destination for sign bit for row 0 is 0.
// eg destination for sign bit for row 1 is 1
SigBit prior_row_sign = get<2>(ppij_int[row_ix - 1]);
copy_ix = (row_ix - 1) * 2;
aligned_pp[row_ix][copy_ix] = prior_row_sign;
}
}
}
/*
Build a Carry Propagate Adder
-----------------------------
First build the sum and carry vectors to be added.
*/
void BuildCPA(RTLIL::Module *module, SigSpec s_vec, SigSpec c_vec, SigSpec result)
{
static int cpa_id;
cpa_id++;
log_assert(c_vec.size() == s_vec.size());
log_assert(result.size() == s_vec.size());
SigBit carry;
for (int n = 0; n < s_vec.size(); n++) {
std::string carry_name;
// Base Case: Bit 0 is sum 0
if (n == 0) {
module->addBufGate(NEW_ID_SUFFIX(stringf("base_buf_%d_%d", cpa_id, n)), s_vec[0], result[0]);
#ifdef DEBUG_CPA
printf("CPA bit [%d] Cell %s IP 0 %s \n", n, buf->name.c_str(), s_vec[0]->name.c_str());
#endif
}
//
// Base Case
// c,s = ha(s_vec[1],c_vec[0])
//
else if (n == 1) {
std::string ha_name = "cpa_" + std::to_string(cpa_id) + "_ha_" + std::to_string(n);
SigBit ha_op;
BuildHa(ha_name, s_vec[n], c_vec[n - 1], ha_op, carry);
module->connect(result[n], ha_op);
#ifdef DEBUG_CPA
printf("CPA bit [%d] Cell %s IPs [%s] [%s] \n", n, ha_cell->name.c_str(), s_vec[n]->name.c_str(),
c_vec[n - 1]->name.c_str());
#endif
}
// End Case
else if (n == s_vec.size() - 1) {
// Make the carry results.. Two extra bits after fa.
SigBit carry_out = module->addWire(NEW_ID, 1);
module->addFa(NEW_ID_SUFFIX(stringf("cpa_%d_fa_%d", cpa_id, n)),
/* A */ s_vec[n],
/* B */ c_vec[n - 1],
/* C */ carry,
/* X */ carry_out,
/* Y */ result[n]);
carry = carry_out;
#ifdef DEBUG_CPA
printf("CPA bit [%d] Cell %s IPs [%s] [%s] [%s]\n", n, fa_cell->name.c_str(), s_vec[n]->name.c_str(),
c_vec[n - 1]->name.c_str(), carry->name.c_str());
#endif
if (n + 1 < GetSize(result)) {
// Now make a half adder: c_vec[n] = carry
std::string ha_name = "cpa_" + std::to_string(cpa_id) + "_ha_" + std::to_string(n);
SigBit ha_sum;
SigBit ha_carry;
BuildHa(ha_name, c_vec[n], carry, ha_sum, ha_carry);
if (n + 1 < GetSize(result))
module->connect(result[n + 1], ha_sum);
if (n + 2 < GetSize(result))
module->connect(result[n + 2], ha_carry);
}
}
// Step case
else {
SigBit carry_out = module->addWire(NEW_ID_SUFFIX(stringf("cpa_%d_carry_%d", cpa_id, n)), 1);
module->addFa(NEW_ID_SUFFIX(stringf("cpa_%d_fa_%d", cpa_id, n)),
/* A */ s_vec[n],
/* B */ c_vec[n - 1],
/* C */ carry,
/* X */ carry_out,
/* Y */ result[n]);
carry = carry_out;
#ifdef DEBUG_CPA
printf("CPA bit [%d] Cell %s IPs [%s] [%s] [%s]\n", n, fa_cell->name.c_str(), s_vec[n]->name.c_str(),
c_vec[n - 1]->name.c_str(), carry->name.c_str());
#endif
}
}
}
// Sum the bits in the current column
// Pass the carry bits from each csa to the next
// column for summation. Build serial tree
void ReduceBits(RTLIL::Module *module, int column_ix, SigSpec column_bits, SigBit &s_result, SigBit &c_result, SigSpec &carry_bits_to_sum,
std::vector<std::vector<RTLIL::Cell *>> &debug_csa_trees)
{
#ifdef DEBUG_CSA
if (column_bits.size() > 0)
printf("Serial reduce Column %d reduce bits serial given %d bits (%s) to reduce\n", column_ix, column_bits.size(),
column_bits.as_string().c_str());
else
printf("Serial reduce given 0 bits to reduce\n");
#endif
int csa_ix = 0;
int column_size = column_bits.size();
if (column_size > 0) {
int var_ix = 0;
SigSpec first_csa_ips;
// get the first 3 inputs, if possible
for (var_ix = 0; var_ix < column_bits.size() && first_csa_ips.size() != 3; var_ix++) {
if (column_bits[var_ix] != State::S0)
first_csa_ips.append(column_bits[var_ix]);
}
if (first_csa_ips.size() > 0) {
// build the first csa
auto s_wire = module->addWire(NEW_ID_SUFFIX(stringf("csa_%d_%d_s", column_ix, csa_ix + 1)), 1);
auto c_wire = module->addWire(NEW_ID_SUFFIX(stringf("csa_%d_%d_c", column_ix, csa_ix + 1)), 1);
auto csa = module->addFa(NEW_ID_SUFFIX(stringf("csa_%d_%d", column_ix, csa_ix)),
/* A */ first_csa_ips[0],
/* B */ first_csa_ips.size() > 1 ? first_csa_ips[1] : State::S0,
/* C */ first_csa_ips.size() > 2 ? first_csa_ips[2] : State::S0,
/* X */ c_wire,
/* Y */ s_wire);
s_result = s_wire;
c_result = c_wire;
// debug_csa_trees[column_ix].push_back(csa);
csa_ix++;
if (var_ix <= column_bits.size() - 1)
carry_bits_to_sum.append(c_wire);
// Now build the rest of the tree if we can
while (var_ix <= column_bits.size() - 1) {
SigSpec csa_ips;
// get the next two variables to sum
for (; var_ix <= column_bits.size() - 1 && csa_ips.size() < 2;) {
// skip any empty bits
if (column_bits[var_ix] != State::S0)
csa_ips.append(column_bits[var_ix]);
var_ix++;
}
if (csa_ips.size() > 0) {
auto c_wire = module->addWire(NEW_ID_SUFFIX(stringf("csa_%d_%d_c", column_ix, csa_ix + 1)), 1);
auto s_wire = module->addWire(NEW_ID_SUFFIX(stringf("csa_%d_%d_s", column_ix, csa_ix + 1)), 1);
auto csa = module->addFa(NEW_ID_SUFFIX(stringf("csa_%d_%d", column_ix, csa_ix)),
/* A */ s_result,
/* B */ csa_ips[0],
/* C */ csa_ips.size() > 1 ? csa_ips[1] : State::S0,
/* X */ c_wire,
/* Y */ s_wire);
// debug_csa_trees[column_ix].push_back(csa);
csa_ix++;
if (var_ix <= column_bits.size() - 1)
carry_bits_to_sum.append(c_wire);
s_result = s_wire;
c_result = c_wire;
}
}
}
}
}
void BuildBoothMultEncoders(SigSpec Y, SigSpec &one_int, SigSpec &two_int, SigSpec &s_int, SigSpec &sb_int, RTLIL::Module *module,
int &encoder_ix, bool is_signed)
{
int y_sz = GetSize(Y);
for (int y_ix = 0; y_ix < (!is_signed ? y_sz : y_sz - 1);) {
std::string enc_name = stringf("bur_enc_%d", encoder_ix);
two_int.append(module->addWire(NEW_ID_SUFFIX(stringf("two_int_%d", encoder_ix)), 1));
one_int.append(module->addWire(NEW_ID_SUFFIX(stringf("one_int_%d", encoder_ix)), 1));
s_int.append(module->addWire(NEW_ID_SUFFIX(stringf("s_int_%d", encoder_ix)), 1));
sb_int.append(module->addWire(NEW_ID_SUFFIX(stringf("sb_int_%d", encoder_ix)), 1));
if (y_ix == 0) {
BuildBur4e(enc_name, State::S0, Y[y_ix], Y[y_ix + 1], one_int[encoder_ix], two_int[encoder_ix], s_int[encoder_ix],
sb_int[encoder_ix]);
y_ix = y_ix + 1;
encoder_ix++;
} else {
//
// step case. If multiplier ends on a boundary
// then add an extra booth encoder bounded by
// zeroes to ensure unsigned works.
//
SigBit y0, y1, y2;
bool need_padded_cell = false;
if (y_ix > y_sz - 1) {
y0 = is_signed ? Y.msb() : State::S0;
need_padded_cell = false;
} else {
y0 = Y[y_ix];
y_ix++;
}
if (y_ix > y_sz - 1) {
need_padded_cell = false;
y1 = is_signed ? Y.msb() : State::S0;
} else {
y1 = Y[y_ix];
y_ix++;
}
if (y_ix > y_sz - 1) {
need_padded_cell = false;
y2 = is_signed ? Y.msb() : State::S0;
} else {
if (y_ix == y_sz - 1)
need_padded_cell = !is_signed;
else
need_padded_cell = false;
y2 = Y[y_ix];
BuildBur4e(enc_name, y0, y1, y2, one_int[encoder_ix], two_int[encoder_ix], s_int[encoder_ix],
sb_int[encoder_ix]);
}
encoder_ix++;
if (need_padded_cell == true) {
// make extra encoder cell
// y_ix at y0, rest 0
std::string enc_name = stringf("br_enc_pad_%d", encoder_ix);
two_int.append(module->addWire(NEW_ID_SUFFIX(stringf("two_int_%d", encoder_ix)), 1));
one_int.append(module->addWire(NEW_ID_SUFFIX(stringf("one_int_%d", encoder_ix)), 1));
s_int.append(module->addWire(NEW_ID_SUFFIX(stringf("s_int_%d", encoder_ix)), 1));
sb_int.append(module->addWire(NEW_ID_SUFFIX(stringf("sb_int_%d", encoder_ix)), 1));
SigBit one_o_int, two_o_int, s_o_int, sb_o_int;
BuildBur4e(enc_name, Y[y_ix], State::S0, State::S0, one_o_int, two_o_int, s_o_int, sb_o_int);
module->connect(one_int[encoder_ix], one_o_int);
module->connect(two_int[encoder_ix], two_o_int);
module->connect(s_int[encoder_ix], s_o_int);
module->connect(sb_int[encoder_ix], sb_o_int);
y_ix++;
encoder_ix++;
}
}
}
}
/*
Low-power Multiplier
*/
void CreateBoothLowpowerMult(RTLIL::Module *module, SigSpec X, SigSpec Y, SigSpec Z, bool is_signed)
{ // product
int x_sz = X.size(), y_sz = Y.size(), z_sz = Z.size();
if (!is_signed)
log_error("Low-power Booth architecture is only supported on signed multipliers.\n");
unsigned enc_count = (y_sz / 2) + (((y_sz % 2) != 0) ? 1 : 0);
int dec_count = x_sz + 1;
int fa_count = x_sz + 4;
int fa_row_count = enc_count - 1;
log_debug("Mapping %d x %d -> %d multiplier: %d encoders %d decoders\n", x_sz, y_sz, z_sz, enc_count, dec_count);
SigSpec negi_n_int, twoi_n_int, onei_n_int, cori_n_int;
negi_n_int.extend_u0(enc_count);
twoi_n_int.extend_u0(enc_count);
onei_n_int.extend_u0(enc_count);
cori_n_int.extend_u0(enc_count);
for (unsigned encoder_ix = 1; encoder_ix <= enc_count; encoder_ix++) {
std::string enc_name = stringf("enc_%d", encoder_ix);
negi_n_int[encoder_ix - 1] = module->addWire(NEW_ID_SUFFIX(stringf("negi_n_int_%d", encoder_ix)), 1);
twoi_n_int[encoder_ix - 1] = module->addWire(NEW_ID_SUFFIX(stringf("twoi_n_int_%d", encoder_ix)), 1);
onei_n_int[encoder_ix - 1] = module->addWire(NEW_ID_SUFFIX(stringf("onei_n_int_%d", encoder_ix)), 1);
cori_n_int[encoder_ix - 1] = module->addWire(NEW_ID_SUFFIX(stringf("cori_n_int_%d", encoder_ix)), 1);
if (encoder_ix == 1) {
BuildBr4e(enc_name, State::S0, Y[0], Y[1], negi_n_int[encoder_ix - 1], twoi_n_int[encoder_ix - 1],
onei_n_int[encoder_ix - 1], cori_n_int[encoder_ix - 1]);
} else {
SigBit y1, y2, y3;
y1 = Y[(encoder_ix - 1) * 2 - 1];
if ((encoder_ix - 1) * 2 >= (unsigned)y_sz)
y2 = State::S0; // constant 0
else
y2 = Y[(encoder_ix - 1) * 2]; // 0
if (((encoder_ix - 1) * 2 + 1) >= (unsigned)y_sz)
y3 = State::S0; // constant 0
else
y3 = Y[(encoder_ix - 1) * 2 + 1]; //+1
BuildBr4e(enc_name, y1, y2, y3, negi_n_int[encoder_ix - 1], twoi_n_int[encoder_ix - 1], onei_n_int[encoder_ix - 1],
cori_n_int[encoder_ix - 1]);
}
}
// Decoders and PP generation
SigSpec PPij(State::S0, enc_count * dec_count);
SigSpec nxj(State::S0, enc_count * dec_count);
for (int encoder_ix = 1; encoder_ix <= (int)enc_count; encoder_ix++) {
for (int decoder_ix = 1; decoder_ix <= dec_count; decoder_ix++) {
PPij[((encoder_ix - 1) * dec_count) + decoder_ix - 1] =
module->addWire(NEW_ID_SUFFIX(stringf("ppij_%d_%d", encoder_ix, decoder_ix)), 1);
nxj[((encoder_ix - 1) * dec_count) + decoder_ix - 1] = module->addWire(
NEW_ID_SUFFIX(stringf("nxj_%s%d_%d", decoder_ix == 1 ? "pre_dec_" : "", encoder_ix, decoder_ix)), 1);
}
}
//
// build decoder array
//
for (int encoder_ix = 1; encoder_ix <= (int)enc_count; encoder_ix++) {
// pre-decoder
std::string pre_dec_name = "pre_dec_" + std::to_string(encoder_ix) + "_";
if (encoder_ix == 1) {
// quadrant 1 optimization
} else {
module->addNotGate(NEW_ID_SUFFIX(stringf("pre_dec_%d", encoder_ix)), negi_n_int[encoder_ix - 1],
nxj[(encoder_ix - 1) * dec_count]);
}
for (int decoder_ix = 1; decoder_ix < dec_count; decoder_ix++) {
// range 1..8
// quadrant 1 optimization.
if ((decoder_ix == 1 || decoder_ix == 2) && encoder_ix == 1)
continue;
std::string dec_name = stringf("dec_%d_%d", encoder_ix, decoder_ix);
BuildBr4d(dec_name, nxj[((encoder_ix - 1) * dec_count) + decoder_ix - 1], twoi_n_int[encoder_ix - 1],
X[decoder_ix - 1], negi_n_int[encoder_ix - 1], onei_n_int[encoder_ix - 1],
PPij[((encoder_ix - 1) * dec_count) + decoder_ix - 1], nxj[((encoder_ix - 1) * dec_count) + decoder_ix]);
}
// duplicate end for sign fix
// applies to 9th decoder (xsz+1 decoder).
std::string dec_name = stringf("dec_%d_%d", encoder_ix, x_sz + 1);
SigBit unused_op;
BuildBr4d(dec_name, nxj[((encoder_ix - 1) * dec_count) + dec_count - 1], twoi_n_int[encoder_ix - 1], X[dec_count - 2],
negi_n_int[encoder_ix - 1], onei_n_int[encoder_ix - 1], PPij[((encoder_ix - 1) * dec_count) + dec_count - 1],
unused_op);
}
//
// instantiate the quadrant 1 cell. This is the upper right
// quadrant which can be realized using non-booth encoded logic.
//
SigBit pp0_o_int, pp1_o_int, nxj_o_int, q1_carry_out;
BuildBoothQ1("icb_booth_q1_",
negi_n_int[0], // negi
cori_n_int[0], // cori
X[0], X[1], Y[0], Y[1], nxj_o_int, q1_carry_out, pp0_o_int, pp1_o_int);
module->connect(Z[0], pp0_o_int);
module->connect(Z[1], pp1_o_int);
module->connect(nxj[(0 * dec_count) + 2], nxj_o_int);
//
// sum up the partial products
//
int fa_row_ix = 0;
std::vector<SigSpec> fa_sum;
std::vector<SigSpec> fa_carry;
for (fa_row_ix = 0; fa_row_ix < fa_row_count; fa_row_ix++) {
fa_sum.push_back(module->addWire(NEW_ID_SUFFIX(stringf("fa_sum_%d", fa_row_ix)), fa_count));
fa_carry.push_back(module->addWire(NEW_ID_SUFFIX(stringf("fa_carry_%d", fa_row_ix)), fa_count));
}
// full adder creation
// base case: 1st row: Inputs from decoders
// 1st row exception: two localized inverters due to sign extension structure
SigBit d08_inv = module->NotGate(NEW_ID_SUFFIX("bfa_0_exc_inv1"), PPij[(0 * dec_count) + dec_count - 1]);
SigBit d18_inv = module->NotGate(NEW_ID_SUFFIX("bfa_0_exc_inv2"), PPij[(1 * dec_count) + dec_count - 1]);
BuildBitwiseFa(module, NEW_ID_SUFFIX("fa_row_0").str(),
/* A */ {State::S0, d08_inv, PPij[(0 * dec_count) + x_sz], PPij.extract((0 * dec_count) + 2, x_sz - 1)},
/* B */ {State::S1, d18_inv, PPij.extract((1 * dec_count), x_sz)},
/* C */ fa_carry[0].extract(1, x_sz + 2),
/* X */ fa_carry[0].extract(2, x_sz + 2),
/* Y */ fa_sum[0].extract(2, x_sz + 2));
module->connect(fa_carry[0][1], q1_carry_out);
// step case: 2nd and rest of rows. (fa_row_ix == 1...n)
// special because these are driven by a decoder and prior fa.
for (fa_row_ix = 1; fa_row_ix < fa_row_count; fa_row_ix++) {
// end two bits: sign extension
SigBit d_inv =
module->NotGate(NEW_ID_SUFFIX(stringf("bfa_se_inv_%d_L", fa_row_ix)), PPij[((fa_row_ix + 1) * dec_count) + dec_count - 1]);
BuildBitwiseFa(module, NEW_ID_SUFFIX(stringf("fa_row_%d", fa_row_ix)).str(),
/* A */ {State::S0, fa_carry[fa_row_ix - 1][fa_count - 1], fa_sum[fa_row_ix - 1].extract(2, x_sz + 2)},
/* B */ {State::S1, d_inv, PPij.extract((fa_row_ix + 1) * dec_count, x_sz), State::S0, State::S0},
/* C */ {fa_carry[fa_row_ix].extract(0, x_sz + 3), cori_n_int[fa_row_ix]},
/* X */ fa_carry[fa_row_ix],
/* Y */ fa_sum[fa_row_ix]);
}
// instantiate the cpa
SigSpec cpa_carry;
if (z_sz > fa_row_count * 2)
cpa_carry = module->addWire(NEW_ID_SUFFIX("cpa_carry"), z_sz - fa_row_count * 2);
// The end case where we pass the last two summands
// from prior row directly to product output
// without using a cpa cell. This is always
// 0,1 index of prior fa row
for (int cpa_ix = 0; cpa_ix < fa_row_count * 2; cpa_ix += 2) {
int fa_row_ix = cpa_ix / 2;
module->connect(Z.extract(cpa_ix, 2), fa_sum[fa_row_ix].extract(0, 2));
}
for (int cpa_ix = fa_row_count * 2; cpa_ix < z_sz; cpa_ix++) {
int offset = fa_row_count * 2;
std::string cpa_name = stringf("cpa_%d", cpa_ix - offset);
SigBit ci = (cpa_ix == offset) ? cori_n_int[enc_count - 1] : cpa_carry[cpa_ix - offset - 1];
SigBit op;
BuildHa(cpa_name, fa_sum[fa_row_count - 1][cpa_ix - offset + 2], ci, op, cpa_carry[cpa_ix - offset]);
module->connect(Z[cpa_ix], op);
}
}
};
struct BoothPass : public Pass {
BoothPass() : Pass("booth", "map $mul cells to Booth multipliers") {}
void help() override
{
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
log("\n");
log(" booth [selection]\n");
log("\n");
log("This pass replaces multiplier cells with a radix-4 Booth-encoded implementation.\n");
log("It operates on $mul cells whose width of operands is at least 4x4 and whose\n");
log("width of result is at least 8.\n");
log("\n");
log(" -lowpower\n");
log(" use an alternative low-power architecture for the generated multiplier\n");
log(" (signed multipliers only)\n");
log("\n");
}
void execute(vector<string> args, RTLIL::Design *design) override
{
log_header(design, "**Executing BOOTH pass (map to Booth multipliers).\n");
size_t argidx;
bool mapped_cpa = false;
bool lowpower = false;
for (argidx = 1; argidx < args.size(); argidx++) {
if (args[argidx] == "-mapped_cpa")
// Have an undocumented option which helps with multiplier
// verification using specialized tools (AMulet2 in particular)
mapped_cpa = true;
else if (args[argidx] == "-lowpower")
lowpower = true;
else
break;
}
extra_args(args, argidx, design);
int total = 0;
for (auto mod : design->selected_modules()) {
if (!mod->has_processes_warn()) {
BoothPassWorker worker(mod);
worker.mapped_cpa = mapped_cpa;
worker.lowpower = lowpower;
worker.run();
total += worker.booth_counter;
}
}
log_header(design, "Mapped %d multipliers.\n", total);
}
} MultPass;
PRIVATE_NAMESPACE_END