gatemate: use CPE bridge (#1538)

* Add bridge support

* Use bridge only if CPE is unused

* do not use CPE_MULT for MUX routing

* Fixed and documented

* delay for CPE_BRIDGE

* Convert bridge pips into bels

Co-authored-by: Miodrag Milanovic <mmicko@gmail.com>

* recursively reassign bridges

* reconnect cell ports to new nets

* handle inversion bits

* sort data in output for easier compare

* one to be removed after testing

* debug message

* Remove need for notifyPipChange

* use same logic for detecting bridge pips

* make sure that the pip used is the one assigned

* one wire may feed multiple ports

* remove #if

* clean up wire binding

* add debugging

* fix

* clangformat

* put back to error

* use tile instead of getting name out of bel/pip

* bump chipdb

* adressing review comments

* Addressed last one

---------

Co-authored-by: Lofty <dan.ravensloft@gmail.com>
This commit is contained in:
Miodrag Milanović 2025-09-02 18:00:01 +02:00 committed by GitHub
parent 4e4f4ab113
commit 3eb682bcbb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 195 additions and 40 deletions

View File

@ -65,6 +65,11 @@ struct BitstreamBackend
WireId cursor = dst_wire;
bool invert = false;
if (net_info->driver.cell && net_info->driver.cell->type == id_CPE_BRIDGE &&
net_info->driver.port == id_MUXOUT) {
int val = int_or_default(net_info->driver.cell->params, id_C_SN, 0) + 1;
invert ^= need_inversion(net_info->driver.cell, ctx->idf("IN%d", val));
}
while (cursor != WireId() && cursor != src_wire) {
auto it = net_info->wires.find(cursor);
@ -147,8 +152,7 @@ struct BitstreamBackend
void export_connection(ChipConfig &cc, PipId pip)
{
const auto extra_data =
*reinterpret_cast<const GateMatePipExtraDataPOD *>(chip_pip_info(ctx->chip_info, pip).extra_data.get());
const auto &extra_data = *uarch->pip_extra_data(pip);
if (extra_data.type == PipExtra::PIP_EXTRA_MUX && (extra_data.flags & MUX_VISIBLE)) {
IdString name = IdString(extra_data.name);
CfgLoc loc = get_config_loc(pip.tile);
@ -293,7 +297,8 @@ struct BitstreamBackend
case id_IOSEL.index:
for (auto &p : params) {
bank[loc.die][ctx->get_bel_package_pin(cell.second.get()->bel)->pad_bank] = 1;
cc.tiles[loc].add_word(stringf("GPIO.%s", p.first.c_str(ctx)), p.second.as_bits());
cc.tiles[loc].add_word(stringf("GPIO.%s", p.first.c_str(ctx)), p.second.as_bits(),
cell.second->name.c_str(ctx));
}
break;
case id_CPE_CPLINES.index:
@ -308,6 +313,7 @@ struct BitstreamBackend
case id_CPE_LATCH.index:
case id_CPE_RAMI.index:
case id_CPE_RAMO.index:
case id_CPE_BRIDGE.index:
case id_CPE_RAMIO.index: {
// Update configuration bits based on signal inversion
dict<IdString, Property> params = cell.second->params;
@ -409,23 +415,27 @@ struct BitstreamBackend
}
break;
}
cc.tiles[loc].add_word(stringf("CPE%d.%s", id, name.c_str(ctx)), p.second.as_bits());
cc.tiles[loc].add_word(stringf("CPE%d.%s", id, name.c_str(ctx)), p.second.as_bits(),
cell.second->name.c_str(ctx));
}
} break;
case id_CLKIN.index: {
for (auto &p : params) {
cc.configs[loc.die].add_word(stringf("CLKIN.%s", p.first.c_str(ctx)), p.second.as_bits());
cc.configs[loc.die].add_word(stringf("CLKIN.%s", p.first.c_str(ctx)), p.second.as_bits(),
cell.second->name.c_str(ctx));
}
} break;
case id_GLBOUT.index: {
for (auto &p : params) {
cc.configs[loc.die].add_word(stringf("GLBOUT.%s", p.first.c_str(ctx)), p.second.as_bits());
cc.configs[loc.die].add_word(stringf("GLBOUT.%s", p.first.c_str(ctx)), p.second.as_bits(),
cell.second->name.c_str(ctx));
}
} break;
case id_PLL.index: {
Loc l = ctx->getBelLocation(cell.second->bel);
for (auto &p : params) {
cc.configs[loc.die].add_word(stringf("PLL%d.%s", l.z - 2, p.first.c_str(ctx)), p.second.as_bits());
cc.configs[loc.die].add_word(stringf("PLL%d.%s", l.z - 2, p.first.c_str(ctx)), p.second.as_bits(),
cell.second->name.c_str(ctx));
}
} break;
case id_RAM.index: {

View File

@ -43,12 +43,14 @@ std::ostream &operator<<(std::ostream &out, const ConfigWord &cw)
std::ostream &operator<<(std::ostream &out, const TileConfig &tc)
{
for (const auto &cword : tc.cwords)
auto sorted = tc.cwords;
std::sort(sorted.begin(), sorted.end(), [](const ConfigWord &a, const ConfigWord &b) { return a.name < b.name; });
for (const auto &cword : sorted)
out << cword;
return out;
}
void TileConfig::add_word(const std::string &name, const std::vector<bool> &value)
void TileConfig::add_word(const std::string &name, const std::vector<bool> &value, const char *c)
{
if (!added.count(name)) {
cwords.push_back({name, value});
@ -56,7 +58,7 @@ void TileConfig::add_word(const std::string &name, const std::vector<bool> &valu
} else {
auto val = added.at(name);
if (val != value)
log_error("Trying to add value to already assigned word %s\n", name.c_str());
log_error("Trying to add value to already assigned word %s for %s\n", name.c_str(), c ? c : "");
}
}

View File

@ -39,7 +39,7 @@ struct TileConfig
std::vector<ConfigWord> cwords;
std::map<std::string, std::vector<bool>> added;
void add_word(const std::string &name, const std::vector<bool> &value);
void add_word(const std::string &name, const std::vector<bool> &value, const char *c = nullptr);
std::string to_string() const;

View File

@ -112,6 +112,8 @@ bool GateMateImpl::getCellDelay(const CellInfo *cell, IdString fromPort, IdStrin
return false;
} else if (cell->type.in(id_CPE_CPLINES)) {
return true;
} else if (cell->type.in(id_CPE_BRIDGE)) {
return true;
} else if (cell->type.in(id_CPE_COMP)) {
return get_delay_from_tmg_db(fromPort == id_COMB1 ? id_timing_comb1_compout : id_timing_comb2_compout, delay);
} else if (cell->type.in(id_CPE_RAMI, id_CPE_RAMO, id_CPE_RAMIO)) {
@ -192,6 +194,10 @@ TimingPortClass GateMateImpl::getPortTimingClass(const CellInfo *cell, IdString
if (port.in(id_OUT1, id_OUT2, id_COMPOUT, id_CINX, id_PINX, id_CINY1, id_PINY1, id_CINY2, id_PINY2))
return TMG_COMB_INPUT;
return TMG_COMB_OUTPUT;
} else if (cell->type.in(id_CPE_BRIDGE)) {
if (port.in(id_MUXOUT))
return TMG_COMB_OUTPUT;
return TMG_COMB_INPUT;
} else if (cell->type.in(id_CPE_FF, id_CPE_FF_L, id_CPE_FF_U, id_CPE_LATCH)) {
if (port == id_CLK)
return TMG_CLOCK_INPUT;

View File

@ -74,6 +74,7 @@ enum MuxFlags
MUX_INVERT = 1,
MUX_VISIBLE = 2,
MUX_CONFIG = 4,
MUX_ROUTING = 8,
};
enum PipExtra
@ -103,7 +104,7 @@ enum CPE_Z
CPE_COMP_Z = 6,
CPE_CPLINES_Z = 7,
CPE_LT_FULL_Z = 8,
CPE_BRIDGE_Z = 9,
RAM_FULL_Z = 10,
RAM_HALF_U_Z = 11,
RAM_HALF_L_Z = 12,

View File

@ -17,6 +17,8 @@
*
*/
#include <utility>
#include "gatemate.h"
#include "log.h"
#include "placer_heap.h"
@ -278,6 +280,26 @@ void GateMateImpl::postPlace()
{
repack();
ctx->assignArchInfo();
used_cpes.resize(ctx->getGridDimX() * ctx->getGridDimY());
for (auto &cell : ctx->cells) {
// We need to skip CPE_MULT since using CP outputs is mandatory
// even if output is actually not connected
bool marked_used = cell.second.get()->type == id_CPE_MULT;
// Can not use FF for OUT2 if CPE is used in bridge mode
if (cell.second.get()->type == id_CPE_FF && ctx->getBelLocation(cell.second.get()->bel).z == CPE_FF_U_Z)
marked_used = true;
if (marked_used)
used_cpes[cell.second.get()->bel.tile] = true;
}
}
bool GateMateImpl::checkPipAvail(PipId pip) const
{
const auto &extra_data = *pip_extra_data(pip);
if (extra_data.type != PipExtra::PIP_EXTRA_MUX || (extra_data.flags & MUX_ROUTING) == 0)
return true;
if (used_cpes[pip.tile])
return false;
return true;
}
void GateMateImpl::preRoute()
@ -287,8 +309,120 @@ void GateMateImpl::preRoute()
ctx->assignArchInfo();
}
void GateMateImpl::reassign_bridges(NetInfo *ni, const dict<WireId, PipMap> &net_wires, WireId wire,
dict<WireId, IdString> &wire_to_net, int &num)
{
wire_to_net.insert({wire, ni->name});
for (auto pip : ctx->getPipsDownhill(wire)) {
auto dst = ctx->getPipDstWire(pip);
// Ignore wires not part of the net
auto it = net_wires.find(dst);
if (it == net_wires.end())
continue;
// Ignore pips if the wire is driven by another pip.
if (pip != it->second.pip)
continue;
// Ignore wires already visited.
if (wire_to_net.count(dst))
continue;
const auto &extra_data = *pip_extra_data(pip);
// If not a bridge, just recurse.
if (extra_data.type != PipExtra::PIP_EXTRA_MUX || !(extra_data.flags & MUX_ROUTING)) {
reassign_bridges(ni, net_wires, dst, wire_to_net, num);
continue;
}
// We have a bridge that needs to be translated to a bel.
IdString name = ctx->idf("%s$bridge%d", ni->name.c_str(ctx), num);
IdStringList id = ctx->getPipName(pip);
Loc loc = ctx->getPipLocation(pip);
BelId bel = ctx->getBelByLocation({loc.x, loc.y, CPE_BRIDGE_Z});
CellInfo *cell = ctx->createCell(name, id_CPE_BRIDGE);
ctx->bindBel(bel, cell, PlaceStrength::STRENGTH_FIXED);
cell->params[id_C_BR] = Property(Property::State::S1, 1);
cell->params[id_C_SN] = Property(extra_data.value, 3);
NetInfo *new_net = ctx->createNet(ctx->idf("%s$muxout", name.c_str(ctx)));
IdString in_port = ctx->idf("IN%d", extra_data.value + 1);
cell->addInput(in_port);
cell->connectPort(in_port, ni);
cell->addOutput(id_MUXOUT);
cell->connectPort(id_MUXOUT, new_net);
num++;
reassign_bridges(new_net, net_wires, dst, wire_to_net, num);
}
}
void GateMateImpl::postRoute()
{
int num = 0;
pool<IdString> nets_with_bridges;
for (auto &net : ctx->nets) {
NetInfo *ni = net.second.get();
for (auto &w : ni->wires) {
if (w.second.pip != PipId()) {
const auto &extra_data = *pip_extra_data(w.second.pip);
if (extra_data.type == PipExtra::PIP_EXTRA_MUX && (extra_data.flags & MUX_ROUTING)) {
this->cpe_bridges.insert({w.second.pip, ni->name});
nets_with_bridges.insert(ni->name);
}
}
}
}
for (auto net_name : nets_with_bridges) {
auto *ni = ctx->nets.at(net_name).get();
auto net_wires = ni->wires; // copy wires to preserve across unbind/rebind.
auto wire_to_net = dict<WireId, IdString>{};
auto wire_to_port = dict<WireId, std::vector<PortRef>>{};
for (auto &usr : ni->users)
for (auto sink_wire : ctx->getNetinfoSinkWires(ni, usr)) {
auto result = wire_to_port.find(sink_wire);
if (result == wire_to_port.end())
wire_to_port.insert({sink_wire, std::vector<PortRef>{usr}});
else
result->second.push_back(usr);
}
// traverse the routing tree to assign bridge nets to wires.
reassign_bridges(ni, net_wires, ctx->getNetinfoSourceWire(ni), wire_to_net, num);
for (auto &pair : net_wires)
ctx->unbindWire(pair.first);
for (auto &pair : net_wires) {
auto wire = pair.first;
auto pip = pair.second.pip;
auto strength = pair.second.strength;
auto *net = ctx->nets.at(wire_to_net.at(wire)).get();
if (pip == PipId())
ctx->bindWire(wire, net, strength);
else
ctx->bindPip(pip, net, strength);
if (wire_to_port.count(wire)) {
for (auto sink : wire_to_port.at(wire)) {
NPNR_ASSERT(sink.cell != nullptr && sink.port != IdString());
sink.cell->disconnectPort(sink.port);
sink.cell->connectPort(sink.port, net);
}
}
}
}
ctx->assignArchInfo();
const ArchArgs &args = ctx->args;
if (args.options.count("out")) {
write_bitstream(args.device, args.options.at("out"));
@ -430,8 +564,7 @@ bool GateMateImpl::isValidBelForCellType(IdString cell_type, BelId bel) const
bool GateMateImpl::isPipInverting(PipId pip) const
{
const auto &extra_data =
*reinterpret_cast<const GateMatePipExtraDataPOD *>(chip_pip_info(ctx->chip_info, pip).extra_data.get());
const auto &extra_data = *pip_extra_data(pip);
return extra_data.type == PipExtra::PIP_EXTRA_MUX && (extra_data.flags & MUX_INVERT);
}
@ -440,6 +573,11 @@ const GateMateBelExtraDataPOD *GateMateImpl::bel_extra_data(BelId bel) const
return reinterpret_cast<const GateMateBelExtraDataPOD *>(chip_bel_info(ctx->chip_info, bel).extra_data.get());
}
const GateMatePipExtraDataPOD *GateMateImpl::pip_extra_data(PipId pip) const
{
return reinterpret_cast<const GateMatePipExtraDataPOD *>(chip_pip_info(ctx->chip_info, pip).extra_data.get());
}
struct GateMateArch : HimbaechelArch
{
GateMateArch() : HimbaechelArch("gatemate") {};

View File

@ -46,6 +46,8 @@ struct GateMateImpl : HimbaechelAPI
BoundingBox getRouteBoundingBox(WireId src, WireId dst) const override;
void expandBoundingBox(BoundingBox &bb) const override;
bool checkPipAvail(PipId pip) const override;
bool checkPipAvailForNet(PipId pip, const NetInfo *net) const override { return checkPipAvail(pip); };
bool isBelLocationValid(BelId bel, bool explain_invalid = false) const override;
delay_t estimateDelay(WireId src, WireId dst) const override;
@ -69,6 +71,7 @@ struct GateMateImpl : HimbaechelAPI
bool isPipInverting(PipId pip) const override;
const GateMateTileExtraDataPOD *tile_extra_data(int tile) const;
const GateMatePipExtraDataPOD *pip_extra_data(PipId pip) const;
int get_dff_config(CellInfo *dff) const;
int get_ram_config(CellInfo *ram) const;
@ -83,6 +86,8 @@ struct GateMateImpl : HimbaechelAPI
pool<IdString> multiplier_a_passthru_uppers;
pool<IdString> multiplier_zero_drivers;
std::vector<CellInfo *> multipliers;
std::vector<bool> used_cpes;
dict<PipId, IdString> cpe_bridges;
int fpga_mode;
int timing_mode;
@ -97,6 +102,8 @@ struct GateMateImpl : HimbaechelAPI
void assign_cell_info();
void route_clock();
void route_mult();
void reassign_bridges(NetInfo *net, const dict<WireId, PipMap> &net_wires, WireId wire,
dict<WireId, IdString> &wire_to_net, int &num);
void repack();
const GateMateBelExtraDataPOD *bel_extra_data(BelId bel) const;

View File

@ -30,6 +30,7 @@ PIP_EXTRA_MUX = 1
MUX_INVERT = 1
MUX_VISIBLE = 2
MUX_CONFIG = 4
MUX_ROUTING = 8
parser = argparse.ArgumentParser()
parser.add_argument("--lib", help="Project Peppercorn python database script path", type=str, required=True)
@ -193,7 +194,7 @@ def set_timings(ch):
# assert k in timing, f"pip class {k} not found in timing data"
# tmg.set_pip_class(grade=speed, name=k, delay=convert_timing(timing[k]))
EXPECTED_VERSION = 1.5
EXPECTED_VERSION = 1.6
def main():
# Range needs to be +1, but we are adding +2 more to coordinates, since
@ -233,7 +234,7 @@ def main():
tt.create_wire(wire.name, wire.type)
for prim in sorted(die.get_primitives_for_type(type_name)):
bel = tt.create_bel(prim.name, prim.type, prim.z)
if (prim.name in ["CPE_LT_FULL", "RAM"]):
if (prim.name in ["CPE_LT_FULL", "CPE_BRIDGE", "RAM"]):
bel.flags |= BEL_FLAG_HIDDEN
extra = BelExtraData()
for constr in sorted(die.get_pins_constraint(type_name, prim.name, prim.type)):
@ -256,6 +257,8 @@ def main():
plane = int(mux.name[8:10])
if mux.name.startswith("SB_DRIVE"):
plane = int(mux.name[10:12])
if mux.name == "CPE.C_SN":
mux_flags |= MUX_ROUTING
pp.extra_data = PipExtraData(PIP_EXTRA_MUX, ch.strs.id(mux.name), mux.bits, mux.value, mux_flags, plane)
# Setup tile grid

View File

@ -272,8 +272,7 @@ void GateMatePacker::insert_bufg(CellInfo *cell, IdString port)
CellInfo *bufg =
create_cell_ptr(id_CC_BUFG, ctx->idf("%s$BUFG_%s", cell->name.c_str(ctx), port.c_str(ctx)));
cell->movePortTo(port, bufg, id_O);
cell->ports[port].name = port;
cell->ports[port].type = PORT_OUT;
cell->addOutput(port);
NetInfo *net = ctx->createNet(ctx->idf("%s", bufg->name.c_str(ctx)));
cell->connectPort(port, net);
bufg->connectPort(id_I, net);

View File

@ -207,8 +207,7 @@ void GateMatePacker::pack_cpe()
ci.renamePort(id_D1, id_IN1);
NetInfo *sel = ci.getPort(id_S0);
ci.renamePort(id_S0, id_IN2);
ci.ports[id_IN3].name = id_IN3;
ci.ports[id_IN3].type = PORT_IN;
ci.addInput(id_IN3);
ci.connectPort(id_IN3, sel);
ci.renamePort(id_D0, id_IN4);
ci.disconnectPort(id_D1);
@ -262,8 +261,7 @@ void GateMatePacker::pack_cpe()
// Reconnect net
NetInfo *ci_out_conn = ctx->createNet(ctx->idf("%s$out", ci.name.c_str(ctx)));
ci.connectPort(id_OUT, ci_out_conn);
lower->ports[id_COMBIN].name = id_COMBIN;
lower->ports[id_COMBIN].type = PORT_IN;
lower->addInput(id_COMBIN);
lower->connectPort(id_COMBIN, ci_out_conn);
dff->disconnectPort(id_DIN);
dff->connectPort(id_DIN, ci_out_conn);
@ -291,8 +289,7 @@ void GateMatePacker::pack_cpe()
NetInfo *ci_out_conn = ctx->createNet(ctx->idf("%s$combin", ci->name.c_str(ctx)));
upper->connectPort(id_OUT, ci_out_conn);
ci->ports[id_COMBIN].name = id_COMBIN;
ci->ports[id_COMBIN].type = PORT_IN;
ci->addInput(id_COMBIN);
ci->connectPort(id_COMBIN, ci_out_conn);
}
l2t5_list.clear();
@ -388,8 +385,7 @@ void GateMatePacker::pack_cpe()
ci.type = (ci.type == id_CC_DLT) ? id_CPE_LATCH : id_CPE_FF;
NetInfo *conn = ctx->createNet(ctx->idf("%s$di", ci.name.c_str(ctx)));
lt->connectPort(id_OUT, conn);
ci.ports[id_DIN].name = id_DIN;
ci.ports[id_DIN].type = PORT_IN;
ci.addInput(id_DIN);
ci.connectPort(id_DIN, conn);
}
dff_list.clear();
@ -577,8 +573,7 @@ void GateMatePacker::pack_addf()
NetInfo *ci_conn = ctx->createNet(ctx->idf("%s$ci_net", root->name.c_str(ctx)));
ci_cplines->connectPort(id_COUTY1, ci_conn);
root->ports[id_CINY1].name = id_CINY1;
root->ports[id_CINY1].type = PORT_IN;
root->addInput(id_CINY1);
root->connectPort(id_CINY1, ci_conn);
for (size_t i = 0; i < grp.size(); i++) {
@ -646,8 +641,7 @@ void GateMatePacker::pack_addf()
NetInfo *co_conn = ctx->createNet(ctx->idf("%s$co_net", cy->name.c_str(ctx)));
co_lower->connectPort(id_CINY1, co_conn);
cy->ports[id_COUTY1].name = id_COUTY1;
cy->ports[id_COUTY1].type = PORT_OUT;
cy->addOutput(id_COUTY1);
cy->connectPort(id_COUTY1, co_conn);
cy->movePortTo(id_CO, co_lower, id_OUT);
@ -660,8 +654,7 @@ void GateMatePacker::pack_addf()
if (usr.cell->type == id_CC_ADDF || usr.port == id_CI) {
usr.cell->disconnectPort(id_CI);
NetInfo *co_conn = ctx->createNet(ctx->idf("%s$co_net", cy->name.c_str(ctx)));
cy->ports[id_COUTY1].name = id_COUTY1;
cy->ports[id_COUTY1].type = PORT_OUT;
cy->addOutput(id_COUTY1);
cy->connectPort(id_COUTY1, co_conn);
usr.cell->connectPort(id_CI, co_conn);
break;

View File

@ -518,8 +518,7 @@ void GateMatePacker::pack_io_sel()
if (cpe_half) {
if (cpe_half->getPort(id_IN1) != oddr->getPort(id_DDR))
log_error("DDR port use signal different than already occupied DDR source.\n");
ci.ports[id_DDR].name = id_DDR;
ci.ports[id_DDR].type = PORT_IN;
ci.addInput(id_DDR);
ci.connectPort(id_DDR, cpe_ramio->getPort(id_RAM_O));
} else {
auto l = reinterpret_cast<const GateMatePadExtraDataPOD *>(pad->extra_data.get());

View File

@ -430,8 +430,7 @@ void GateMatePacker::pack_mult()
NetInfo *comb2_conn = ctx->createNet(ctx->idf("%s$carrycomb2", name.c_str(ctx)));
carry_upper->connectPort(id_OUT, comb2_conn);
carry_lower->ports[id_COMBIN].name = id_COMBIN;
carry_lower->ports[id_COMBIN].type = PORT_IN;
carry_lower->addInput(id_COMBIN);
carry_lower->connectPort(id_COMBIN, comb2_conn);
NetInfo *comp_in = ctx->createNet(ctx->idf("%s$carry$comp_in", name.c_str(ctx)));
@ -461,8 +460,7 @@ void GateMatePacker::pack_mult()
NetInfo *comb2_conn = ctx->createNet(ctx->idf("%s$multf%c$comb2", name.c_str(ctx), is_even_x ? 'a' : 'b'));
multfab_upper->connectPort(id_OUT, comb2_conn);
multfab_lower->ports[id_COMBIN].name = id_COMBIN;
multfab_lower->ports[id_COMBIN].type = PORT_IN;
multfab_lower->addInput(id_COMBIN);
multfab_lower->connectPort(id_COMBIN, comb2_conn);
multfab_comp->connectPort(id_COMB2, comb2_conn);

View File

@ -57,8 +57,7 @@ void GateMateImpl::route_clock()
};
auto pip_plane = [&](PipId pip) {
const auto &extra_data =
*reinterpret_cast<const GateMatePipExtraDataPOD *>(chip_pip_info(ctx->chip_info, pip).extra_data.get());
const auto &extra_data = *pip_extra_data(pip);
if (extra_data.type != PipExtra::PIP_EXTRA_MUX)
return uint8_t{0};
return extra_data.plane;