netlist: Add PseudoCell API

When implementing concepts such as partition pins or deliberately split
nets, there's a need for something that looks like a cell (starts/ends
routing with pins on nets, has timing data) but isn't mapped to a fixed
bel in the architecture, but instead can have pin mappings defined at
runtime.

The PseudoCell allows this by providing an alternate, virtual-function
based API for such cells. When a cell has `pseudo_cell` used, instead of
calling functions such as getBelPinWire, getBelLocation or getCellDelay
in the Arch API; such data is provided by the cell itself, fully
flexible at runtime regardless of arch, via methods on the PseudoCell
implementation.
This commit is contained in:
gatecat 2022-06-23 18:48:31 +01:00
parent 86396c41d6
commit 09e388f453
16 changed files with 223 additions and 74 deletions

View File

@ -150,4 +150,10 @@ fn_wrapper_1a<Context, decltype(&Context::getDelayFromNS), &Context::getDelayFro
pass_through<double>>::def_wrap(ctx_cls, "getDelayFromNS");
fn_wrapper_1a<Context, decltype(&Context::getDelayNS), &Context::getDelayNS, pass_through<double>,
pass_through<delay_t>>::def_wrap(ctx_cls, "getDelayNS");
pass_through<delay_t>>::def_wrap(ctx_cls, "getDelayNS");
fn_wrapper_3a_v<Context, decltype(&Context::createRegionPlug), &Context::createRegionPlug, conv_from_str<IdString>,
conv_from_str<IdString>, pass_through<Loc>>::def_wrap(ctx_cls, "createRegionPlug");
fn_wrapper_4a_v<Context, decltype(&Context::addPlugPin), &Context::addPlugPin, conv_from_str<IdString>,
conv_from_str<IdString>, pass_through<PortType>, conv_from_str<WireId>>::def_wrap(ctx_cls,
"addPlugPin");

View File

@ -131,6 +131,30 @@ void BaseCtx::constrainCellToRegion(IdString cell, IdString region_name)
if (!matched)
log_warning("No cell matched '%s' when constraining to region '%s'\n", nameOf(cell), nameOf(region_name));
}
void BaseCtx::createRegionPlug(IdString name, IdString type, Loc approx_loc)
{
CellInfo *cell = nullptr;
if (cells.count(name))
cell = cells.at(name).get();
else
cell = createCell(name, type);
cell->pseudo_cell = std::make_unique<RegionPlug>(approx_loc);
}
void BaseCtx::addPlugPin(IdString plug, IdString pin, PortType dir, WireId wire)
{
if (!cells.count(plug))
log_error("no cell named '%s' found\n", plug.c_str(this));
CellInfo *ci = cells.at(plug).get();
RegionPlug *rplug = dynamic_cast<RegionPlug *>(ci->pseudo_cell.get());
if (!rplug)
log_error("cell '%s' is not a RegionPlug\n", plug.c_str(this));
rplug->port_wires[pin] = wire;
ci->ports[pin].name = pin;
ci->ports[pin].type = dir;
}
DecalXY BaseCtx::constructDecalXY(DecalId decal, float x, float y)
{
DecalXY dxy;

View File

@ -220,6 +220,10 @@ struct BaseCtx
void addBelToRegion(IdString name, BelId bel);
void constrainCellToRegion(IdString cell, IdString region_name);
// Helper functions for the partial reconfiguration plug API using PseudoCells
void createRegionPlug(IdString name, IdString type, Loc approx_loc);
void addPlugPin(IdString plug, IdString pin, PortType dir, WireId wire);
// Helper functions for Python bindings
NetInfo *createNet(IdString name);
void connectPort(IdString net, IdString cell, IdString port);

View File

@ -30,6 +30,9 @@ WireId Context::getNetinfoSourceWire(const NetInfo *net_info) const
if (net_info->driver.cell == nullptr)
return WireId();
if (net_info->driver.cell->isPseudo())
return net_info->driver.cell->pseudo_cell->getPortWire(net_info->driver.port);
auto src_bel = net_info->driver.cell->bel;
if (src_bel == BelId())
@ -47,6 +50,8 @@ WireId Context::getNetinfoSourceWire(const NetInfo *net_info) const
SSOArray<WireId, 2> Context::getNetinfoSinkWires(const NetInfo *net_info, const PortRef &user_info) const
{
if (user_info.cell->isPseudo())
return SSOArray<WireId, 2>(1, user_info.cell->pseudo_cell->getPortWire(user_info.port));
auto dst_bel = user_info.cell->bel;
if (dst_bel == BelId())
return SSOArray<WireId, 2>(0, WireId());

View File

@ -64,6 +64,24 @@ struct Context : Arch, DeterministicRNG
bool getActualRouteDelay(WireId src_wire, WireId dst_wire, delay_t *delay = nullptr,
dict<WireId, PipId> *route = nullptr, bool useEstimate = true);
// --------------------------------------------------------------
// Dispatch to the Arch API or pseudo-cell API accordingly
bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayQuad &delay) const override
{
return cell->pseudo_cell ? cell->pseudo_cell->getDelay(fromPort, toPort, delay)
: Arch::getCellDelay(cell, fromPort, toPort, delay);
}
TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const override
{
return cell->pseudo_cell ? cell->pseudo_cell->getPortTimingClass(port, clockInfoCount)
: Arch::getPortTimingClass(cell, port, clockInfoCount);
}
TimingClockingInfo getPortClockingInfo(const CellInfo *cell, IdString port, int index) const override
{
return cell->pseudo_cell ? cell->pseudo_cell->getPortClockingInfo(port, index)
: Arch::getPortClockingInfo(cell, port, index);
}
// --------------------------------------------------------------
// call after changing hierpath or adding/removing nets and cells
void fixupHierarchy();

View File

@ -177,4 +177,14 @@ void CellInfo::copyPortBusTo(IdString old_name, int old_offset, bool old_bracket
}
}
Loc CellInfo::getLocation() const
{
if (pseudo_cell) {
return pseudo_cell->getLocation();
} else {
NPNR_ASSERT(bel != BelId());
return ctx->getBelLocation(bel);
}
}
NEXTPNR_NAMESPACE_END

View File

@ -160,58 +160,6 @@ struct PortInfo
struct Context;
struct CellInfo : ArchCellInfo
{
CellInfo(Context *ctx, IdString name, IdString type) : ctx(ctx), name(name), type(type){};
Context *ctx = nullptr;
IdString name, type, hierpath;
int32_t udata;
dict<IdString, PortInfo> ports;
dict<IdString, Property> attrs, params;
BelId bel;
PlaceStrength belStrength = STRENGTH_NONE;
// cell is part of a cluster if != ClusterId
ClusterId cluster;
Region *region = nullptr;
void addInput(IdString name);
void addOutput(IdString name);
void addInout(IdString name);
void setParam(IdString name, Property value);
void unsetParam(IdString name);
void setAttr(IdString name, Property value);
void unsetAttr(IdString name);
// check whether a bel complies with the cell's region constraint
bool testRegion(BelId bel) const;
NetInfo *getPort(IdString name)
{
auto found = ports.find(name);
return (found == ports.end()) ? nullptr : found->second.net;
}
const NetInfo *getPort(IdString name) const
{
auto found = ports.find(name);
return (found == ports.end()) ? nullptr : found->second.net;
}
void connectPort(IdString port, NetInfo *net);
void disconnectPort(IdString port);
void connectPorts(IdString port, CellInfo *other, IdString other_port);
void movePortTo(IdString port, CellInfo *other, IdString other_port);
void renamePort(IdString old_name, IdString new_name);
void movePortBusTo(IdString old_name, int old_offset, bool old_brackets, CellInfo *new_cell, IdString new_name,
int new_offset, bool new_brackets, int width);
void copyPortTo(IdString port, CellInfo *other, IdString other_port);
void copyPortBusTo(IdString old_name, int old_offset, bool old_brackets, CellInfo *new_cell, IdString new_name,
int new_offset, bool new_brackets, int width);
};
enum TimingPortClass
{
TMG_CLOCK_INPUT, // Clock input to a sequential cell
@ -239,6 +187,90 @@ struct TimingClockingInfo
DelayQuad clockToQ; // Output clock-to-Q time
};
struct PseudoCell
{
virtual Loc getLocation() const = 0;
virtual WireId getPortWire(IdString port) const = 0;
virtual bool getDelay(IdString fromPort, IdString toPort, DelayQuad &delay) const = 0;
virtual TimingPortClass getPortTimingClass(IdString port, int &clockInfoCount) const = 0;
virtual TimingClockingInfo getPortClockingInfo(IdString port, int index) const = 0;
virtual ~PseudoCell(){};
};
struct RegionPlug : PseudoCell
{
RegionPlug(Loc loc) : loc(loc){}; // 'loc' is a notional location for the placer only
Loc getLocation() const override { return loc; }
WireId getPortWire(IdString port) const override { return port_wires.at(port); }
// TODO: partial reconfiguration region timing
bool getDelay(IdString fromPort, IdString toPort, DelayQuad &delay) const { return false; }
TimingPortClass getPortTimingClass(IdString port, int &clockInfoCount) const { return TMG_IGNORE; }
virtual TimingClockingInfo getPortClockingInfo(IdString port, int index) const { return TimingClockingInfo{}; }
dict<IdString, WireId> port_wires;
Loc loc;
};
struct CellInfo : ArchCellInfo
{
CellInfo(Context *ctx, IdString name, IdString type) : ctx(ctx), name(name), type(type){};
Context *ctx = nullptr;
IdString name, type, hierpath;
int32_t udata;
dict<IdString, PortInfo> ports;
dict<IdString, Property> attrs, params;
BelId bel;
PlaceStrength belStrength = STRENGTH_NONE;
// cell is part of a cluster if != ClusterId
ClusterId cluster;
Region *region = nullptr;
std::unique_ptr<PseudoCell> pseudo_cell{};
void addInput(IdString name);
void addOutput(IdString name);
void addInout(IdString name);
void setParam(IdString name, Property value);
void unsetParam(IdString name);
void setAttr(IdString name, Property value);
void unsetAttr(IdString name);
// check whether a bel complies with the cell's region constraint
bool testRegion(BelId bel) const;
bool isPseudo() const { return bool(pseudo_cell); }
Loc getLocation() const;
NetInfo *getPort(IdString name)
{
auto found = ports.find(name);
return (found == ports.end()) ? nullptr : found->second.net;
}
const NetInfo *getPort(IdString name) const
{
auto found = ports.find(name);
return (found == ports.end()) ? nullptr : found->second.net;
}
void connectPort(IdString port, NetInfo *net);
void disconnectPort(IdString port);
void connectPorts(IdString port, CellInfo *other, IdString other_port);
void movePortTo(IdString port, CellInfo *other, IdString other_port);
void renamePort(IdString old_name, IdString new_name);
void movePortBusTo(IdString old_name, int old_offset, bool old_brackets, CellInfo *new_cell, IdString new_name,
int new_offset, bool new_brackets, int width);
void copyPortTo(IdString port, CellInfo *other, IdString other_port);
void copyPortBusTo(IdString old_name, int old_offset, bool old_brackets, CellInfo *new_cell, IdString new_name,
int new_offset, bool new_brackets, int width);
};
struct ClockConstraint
{
DelayPair high;

View File

@ -37,6 +37,8 @@ PlacePartition::PlacePartition(Context *ctx)
x1 = 0;
y1 = 0;
for (auto &cell : ctx->cells) {
if (cell.second->isPseudo())
continue;
Loc l = ctx->getBelLocation(cell.second->bel);
x0 = std::min(x0, l.x);
x1 = std::max(x1, l.x);
@ -110,6 +112,8 @@ NetBB NetBB::compute(const Context *ctx, const NetInfo *net, const dict<IdString
if (!net->driver.cell)
return result;
auto bel_loc = [&](const CellInfo *cell) {
if (cell->isPseudo())
return cell->getLocation();
BelId bel = cell2bel ? cell2bel->at(cell->name) : cell->bel;
return ctx->getBelLocation(bel);
};
@ -176,10 +180,12 @@ void DetailPlacerThreadState::set_partition(const PlacePartition &part)
// Set up the original cell-bel map for all nets inside the thread
local_cell2bel.clear();
for (NetInfo *net : thread_nets) {
if (net->driver.cell)
if (net->driver.cell && !net->driver.cell->isPseudo())
local_cell2bel[net->driver.cell->name] = net->driver.cell->bel;
for (auto &usr : net->users)
local_cell2bel[usr.cell->name] = usr.cell->bel;
for (auto &usr : net->users) {
if (!usr.cell->isPseudo())
local_cell2bel[usr.cell->name] = usr.cell->bel;
}
}
}

View File

@ -390,6 +390,8 @@ struct ParallelRefine
// Setup fast bels map
pool<IdString> cell_types_in_use;
for (auto &cell : ctx->cells) {
if (cell.second->isPseudo())
continue;
IdString cell_type = cell.second->type;
cell_types_in_use.insert(cell_type);
if (cell.second->cluster != ClusterId())

View File

@ -293,6 +293,8 @@ class ConstraintLegaliseWorker
{
if (cell->cluster != ClusterId() && ctx->getClusterRootCell(cell->cluster) != cell)
return true; // Only process chain roots
if (cell->isPseudo())
return true;
if (constraints_satisfied(cell)) {
if (cell->cluster != ClusterId())
lockdown_chain(cell);
@ -415,7 +417,7 @@ class ConstraintLegaliseWorker
{
log_info("Legalising relative constraints...\n");
for (auto &cell : ctx->cells) {
oldLocations[cell.first] = ctx->getBelLocation(cell.second->bel);
oldLocations[cell.first] = cell.second->getLocation();
}
for (auto &cell : ctx->cells) {
bool res = legalise_cell(cell.second.get());
@ -448,6 +450,8 @@ bool legalise_relative_constraints(Context *ctx) { return ConstraintLegaliseWork
int get_constraints_distance(const Context *ctx, const CellInfo *cell)
{
int dist = 0;
if (cell->isPseudo())
return 0;
if (cell->bel == BelId())
return 100000;
Loc loc = ctx->getBelLocation(cell->bel);

View File

@ -76,6 +76,8 @@ class SAPlacer
pool<IdString> cell_types_in_use;
for (auto &cell : ctx->cells) {
if (cell.second->isPseudo())
continue;
IdString cell_type = cell.second->type;
cell_types_in_use.insert(cell_type);
}
@ -120,7 +122,7 @@ class SAPlacer
}
for (auto &cell : ctx->cells) {
CellInfo *ci = cell.second.get();
if (ci->cluster == ClusterId())
if (ci->isPseudo() || ci->cluster == ClusterId())
continue;
cluster2cell[ci->cluster].push_back(ci);
}
@ -145,6 +147,8 @@ class SAPlacer
// Initial constraints placer
for (auto &cell_entry : ctx->cells) {
CellInfo *cell = cell_entry.second.get();
if (cell->isPseudo())
continue;
auto loc = cell->attrs.find(ctx->id("BEL"));
if (loc != cell->attrs.end()) {
std::string loc_name = loc->second.as_string();
@ -187,7 +191,7 @@ class SAPlacer
for (auto &cell : ctx->cells) {
CellInfo *ci = cell.second.get();
if (ci->bel == BelId()) {
if (!ci->isPseudo() && (ci->bel == BelId())) {
autoplaced.push_back(cell.second.get());
}
}
@ -217,7 +221,7 @@ class SAPlacer
} else {
for (auto &cell : ctx->cells) {
CellInfo *ci = cell.second.get();
if (ci->belStrength > STRENGTH_STRONG) {
if (ci->isPseudo() || ci->belStrength > STRENGTH_STRONG) {
continue;
} else if (ci->cluster != ClusterId()) {
if (ctx->getClusterRootCell(ci->cluster) == ci)
@ -353,6 +357,8 @@ class SAPlacer
autoplaced.clear();
chain_basis.clear();
for (auto &cell : ctx->cells) {
if (cell.second->isPseudo())
continue;
if (cell.second->belStrength <= STRENGTH_STRONG && cell.second->cluster != ClusterId() &&
ctx->getClusterRootCell(cell.second->cluster) == cell.second.get())
chain_basis.push_back(cell.second.get());
@ -814,7 +820,7 @@ class SAPlacer
{
BoundingBox bb;
NPNR_ASSERT(net->driver.cell != nullptr);
Loc dloc = ctx->getBelLocation(net->driver.cell->bel);
Loc dloc = net->driver.cell->getLocation();
bb.x0 = dloc.x;
bb.x1 = dloc.x;
bb.y0 = dloc.y;
@ -824,9 +830,9 @@ class SAPlacer
bb.ny0 = 1;
bb.ny1 = 1;
for (auto user : net->users) {
if (user.cell->bel == BelId())
if (!user.cell->isPseudo() && user.cell->bel == BelId())
continue;
Loc uloc = ctx->getBelLocation(user.cell->bel);
Loc uloc = user.cell->getLocation();
if (bb.x0 == uloc.x)
++bb.nx0;
else if (uloc.x < bb.x0) {
@ -1173,7 +1179,7 @@ class SAPlacer
nets_by_tile.resize(max_x + 1, std::vector<dict<IdString, int>>(max_y + 1));
for (auto &cell : ctx->cells) {
CellInfo *ci = cell.second.get();
if (int(ci->ports.size()) > large_cell_thresh)
if (ci->isPseudo() || (int(ci->ports.size()) > large_cell_thresh))
continue;
Loc loc = ctx->getBelLocation(ci->bel);
auto &nbt = nets_by_tile.at(loc.x).at(loc.y);

View File

@ -147,7 +147,7 @@ class HeAPPlacer
tmg.setup();
for (auto &cell : ctx->cells)
if (cell.second->cluster != ClusterId())
if (!cell.second->isPseudo() && cell.second->cluster != ClusterId())
cluster2cells[cell.second->cluster].push_back(cell.second.get());
}
@ -284,6 +284,8 @@ class HeAPPlacer
// Save solution
solution.clear();
for (auto &cell : ctx->cells) {
if (cell.second->isPseudo())
continue;
solution.emplace_back(cell.second.get(), cell.second->bel, cell.second->belStrength);
}
} else {
@ -312,6 +314,8 @@ class HeAPPlacer
}
for (auto &cell : ctx->cells) {
if (cell.second->isPseudo())
continue;
if (cell.second->bel == BelId())
log_error("Found unbound cell %s\n", cell.first.c_str(ctx));
if (ctx->getBoundBelCell(cell.second->bel) != cell.second.get())
@ -411,7 +415,8 @@ class HeAPPlacer
// Initial constraints placer
for (auto &cell_entry : ctx->cells) {
CellInfo *cell = cell_entry.second.get();
if (cell->isPseudo())
continue;
auto loc = cell->attrs.find(ctx->id("BEL"));
if (loc != cell->attrs.end()) {
std::string loc_name = loc->second.as_string();
@ -461,6 +466,8 @@ class HeAPPlacer
pool<IdString> cell_types_in_use;
pool<BelBucketId> buckets_in_use;
for (auto &cell : ctx->cells) {
if (cell.second->isPseudo())
continue;
IdString cell_type = cell.second->type;
cell_types_in_use.insert(cell_type);
BelBucketId bucket = ctx->getBelBucketForCellType(cell_type);
@ -527,6 +534,8 @@ class HeAPPlacer
{
pool<IdString> cell_types;
for (const auto &cell : ctx->cells) {
if (cell.second->isPseudo())
continue;
cell_types.insert(cell.second->type);
}
@ -551,6 +560,14 @@ class HeAPPlacer
for (auto &cell : ctx->cells) {
CellInfo *ci = cell.second.get();
if (ci->isPseudo()) {
Loc loc = ci->pseudo_cell->getLocation();
cell_locs[cell.first].x = loc.x;
cell_locs[cell.first].y = loc.y;
cell_locs[cell.first].locked = true;
cell_locs[cell.first].global = false;
continue;
}
if (ci->bel != BelId()) {
Loc loc = ctx->getBelLocation(ci->bel);
cell_locs[cell.first].x = loc.x;
@ -627,8 +644,9 @@ class HeAPPlacer
int row = 0;
solve_cells.clear();
// First clear the udata of all cells
for (auto &cell : ctx->cells)
for (auto &cell : ctx->cells) {
cell.second->udata = dont_solve;
}
// Then update cells to be placed, which excludes cell children
for (auto cell : place_cells) {
if (buckets && !buckets->count(ctx->getBelBucketForCellType(cell->type)))

View File

@ -128,7 +128,7 @@ struct Router2
nets.at(i).cy = 0;
if (ni->driver.cell != nullptr) {
Loc drv_loc = ctx->getBelLocation(ni->driver.cell->bel);
Loc drv_loc = ni->driver.cell->getLocation();
nets.at(i).cx += drv_loc.x;
nets.at(i).cy += drv_loc.y;
}
@ -159,7 +159,7 @@ struct Router2
nets.at(i).bb.y1 = std::max(nets.at(i).bb.y1, ad.bb.y1);
}
// Add location to centroid sum
Loc usr_loc = ctx->getBelLocation(usr.value.cell->bel);
Loc usr_loc = usr.value.cell->getLocation();
nets.at(i).cx += usr_loc.x;
nets.at(i).cy += usr_loc.y;
}

View File

@ -25,10 +25,24 @@ Other structures used by these basic structures include:
- `params` and `attrs` store parameters and attributes - from the input JSON or assigned in flows to add metadata - by mapping from parameter name `IdString` to `Property`.
- `cluster` is used to specify that the cell is inside a placement cluster, with the details of the placement within the cluster provided by the architecture.
- `region` is a reference to a `Region` if the cell is constrained to a placement region (e.g. for partial reconfiguration or out-of-context flows) or `nullptr` otherwise.
- `pseudo_cell` is an optional pointer to an implementation of the pseudo-cell API, used for cells implementing virtual functions such as partition pins without a mapped bel. `bel` will always be `BelId()` for pseudo-cells.
## PseudoCellAPI
Pseudo-cells can be used to implement cells with runtime-defined cell pin to wire mappings. This means they don't have to be a fixed part of the architecture, example use cases could be for implementing partition pins for partial reconfiguration regions; or forcing splits between SLRs. Pseudo-cells implement a series of virtual functions to provide data that for an ordinary cell would be obtained by calling 'bel' ArchAPI functions
The pseudo-cell API is as follows:
- `Loc getLocation() const` : get an approximate location of the pseudocell
- `WireId getPortWire(IdString port) const`: gets the wire corresponding to a port (or WireId if it has no wire)
It also implements functions for getting timing data, mirroring that of the Arch API:
- `bool getDelay(IdString fromPort, IdString toPort, DelayQuad &delay) const`
- `TimingPortClass getPortTimingClass(IdString port, int &clockInfoCount) const`
- `TimingClockingInfo getPortClockingInfo(IdString port, int index) const`
## NetInfo
`NetInfo` instances have the following fields:
`NetInfo` instances have the following fields:\
- `name` is the IdString name of the net - for nets with multiple names, one name is chosen according to a set of rules by the JSON frontend
- `hierpath` is name of the hierarchical cell containing the instance, for designs with hierarchy

View File

@ -748,11 +748,11 @@ struct Arch : ArchAPI<ArchRanges>
// Get the delay through a cell from one port to another, returning false
// if no path exists. This only considers combinational delays, as required by the Arch API
bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayQuad &delay) const final;
bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayQuad &delay) const;
// Get the port class, also setting clockInfoCount to the number of TimingClockingInfos associated with a port
TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const final;
TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const;
// Get the TimingClockingInfo of a port
TimingClockingInfo getPortClockingInfo(const CellInfo *cell, IdString port, int index) const final;
TimingClockingInfo getPortClockingInfo(const CellInfo *cell, IdString port, int index) const;
// -------------------------------------------------

View File

@ -605,7 +605,7 @@ bool Arch::place()
bool have_iobuf_or_constr = false;
for (auto &cell : cells) {
CellInfo *ci = cell.second.get();
if (ci->type == id("GENERIC_IOB") || ci->bel != BelId() || ci->attrs.count(id("BEL"))) {
if (ci->isPseudo() || ci->type == id("GENERIC_IOB") || ci->bel != BelId() || ci->attrs.count(id("BEL"))) {
have_iobuf_or_constr = true;
break;
}