Merge pull request #645 from litghost/add_counter_and_ram

FPGA interchange: Add counter and ram tests
This commit is contained in:
gatecat 2021-03-29 18:23:16 +01:00 committed by GitHub
commit 692d7dc26d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 1217 additions and 333 deletions

View File

@ -435,8 +435,9 @@ struct Router1
// TODO: this matches the situation before supporting multiple cell->bel pins, but do we want to keep
// this invariant?
if (phys_idx == 0)
log_error("No wires found for port %s on destination cell %s.\n",
ctx->nameOf(net_info->users[user_idx].port), ctx->nameOf(net_info->users[user_idx].cell));
log_warning("No wires found for port %s on destination cell %s.\n",
ctx->nameOf(net_info->users[user_idx].port),
ctx->nameOf(net_info->users[user_idx].cell));
}
src_to_net[src_wire] = net_info;

View File

@ -25,7 +25,7 @@ island based FPGA. It consists of three primary file formats:
design, a partially or fully placed design, and a partially or fully
routed design.
### Current status
### Current development status
This architecture implementation can be compiled in conjunction with a FPGA
interchange device database, and the outputs from
@ -36,157 +36,43 @@ library.
The current implementation is missing essential features for place and route.
As these features are added, this implementation will become more useful.
- [ ] The router lookahead is missing, meaning that router runtime
performance will be terrible.
- [ ] Pseudo pips (e.g. pips that consume BELs and or site resources) should
block their respective resources. This effects designs that have some
routing in place before placement.
- [ ] Pseudo site pips (e.g. site pips that route through BELs) should block
their respective resources. Without this, using some pseudo site pips
could result in invalid placements.
- [ ] Implemented site router lacks important features for tight packing.
Also the current site router is relatively untested, so legal
configurations may be rejected and illegal configurations may be
accepted.
- [ ] Logical netlist macro expansion is not implemented, meaning that any
macro primitives are unplaceable. Common macro primitives examples are
differential IO buffers (IBUFDS) and some LUT RAM (e.g. RAM64X1D).
- [ ] Timing information is missing from the FPGA interchange device
database, so it is also currently missing from the FPGA interchange
architecture. Once timing information is added to the device database
schema, it needs to be added to the architecture.
#### FPGA interchange fabrics
#### Weaknesses of current implementation
Currently only Xilinx 7-series, UltraScale and UltraScale+ fabrics have a
Initial development on the following features is started, but needs more
refinement.
- [ ] BEL validity checking is too expensive. The majority of the runtime
is currently in the LUT rotation. Profiling, optimization and
algorithm review is likely required to bring strict legalisation
runtimes into expected levels.
- [ ] The router lookahead is disabled by default. Without the lookahead,
router runtime is terrible. However the current lookahead
implementation is slow to compute and memory intensive, hence why it is
disabled by default.
- [ ] Pseudo pips (e.g. pips that consume BELs and or site resources) and
pseudo site pips (e.g. site pips that route through BELs) consume site
wires to indicate that they block some resources. This covers many
validity check cases, but misses some. In particular, when a pseudo
pip / pseudo site pip has an implication on the constraint system (e.g.
LUT on a LUT-RAM BEL), an edge may be allowed incorrectly, resulting
in an illegal design.
### FPGA interchange fabrics
Xilinx 7-series, UltraScale and UltraScale+ fabrics have a
device database generator, via [RapidWright](https://github.com/Xilinx/RapidWright).
##### Artix 35T example
A Lattice Nexus device database is being worked on, via
[prjoxide](https://github.com/gatecat/prjoxide).
Install capnproto if not already installed:
```
# Or equivalent for your local system.
sudo apt-get install capnproto libcapnp-dev
```
### FPGA interchange build system
Install capnproto-java if not already installed:
```
git clone https://github.com/capnproto/capnproto-java.git
cd capnproto-java
make
sudo make install
```
##### Makefile-driven BBA creation
In `${NEXTPNR_DIR}/fpga_interchange/examples/create_bba` is a Makefile that
should compile nextpnr and create a Xilinx A35 chipdb if java, capnproto and
capnproto-java are installed.
Instructions:
```
cd ${NEXTPNR_DIR}/fpga_interchange/examples/create_bba
make
```
This will create a virtual env in
`${NEXTPNR_DIR}/fpga_interchange/examples/create_bba/build/env` that has the
python-fpga-interchange library installed. Before running the design examples,
enter the virtual env, e.g.:
```
source ${NEXTPNR_DIR}/fpga_interchange/examples/create_bba/build/env/bin/activate
```
The chipdb will be written to `${NEXTPNR_DIR}/fpga_interchange/examples/create_bba/build/xc7a35.bin`
once completed.
##### Manual BBA creation
This covers the manual set of steps to create a Xilinx A35 chipdb.
Download RapidWright and generate the device database.
```
# FIXME: Use main branch once interchange branch is merged.
git clone -b interchange https://github.com/Xilinx/RapidWright.git
cd RapidWright
make update_jars
# FIXME: Current RapidWright jars generate database with duplicate PIPs
# https://github.com/Xilinx/RapidWright/issues/127
# Remove this wget once the latest RapidWright JAR is published.
wget https://github.com/Xilinx/RapidWright/releases/download/v2020.2.1-beta/rapidwright-api-lib-2020.2.1_update1.jar
mv rapidwright-api-lib-2020.2.1_update1.jar jars/rapidwright-api-lib-2020.2.0.jar
./scripts/invoke_rapidwright.sh com.xilinx.rapidwright.interchange.DeviceResourcesExample xc7a35tcpg236-1
export RAPIDWRIGHT_PATH=$(pwd)
```
Set `INTERCHANGE_DIR` to point to 3rdparty/fpga-interchange-schema:
```
export INTERCHANGE_DIR=$(NEXTPNR_DIR)/3rdparty/fpga-interchange-schema/interchange
```
Install python FPGA interchange library.
```
git clone https://github.com/SymbiFlow/python-fpga-interchange.git
cd python-fpga-interchange
pip install -r requirements.txt
```
Patch device database with cell constraints and LUT annotations:
```
python3 -mfpga_interchange.patch \
--schema_dir ${INTERCHANGE_DIR} \
--schema device \
--patch_path constraints \
--patch_format yaml \
${RAPIDWRIGHT_PATH}/xc7a35tcpg236-1.device \
test_data/series7_constraints.yaml \
xc7a35tcpg236-1_constraints.device
python3 -mfpga_interchange.patch \
--schema_dir ${INTERCHANGE_DIR} \
--schema device \
--patch_path lutDefinitions \
--patch_format yaml \
xc7a35tcpg236-1_constraints.device \
test_data/series7_luts.yaml \
xc7a35tcpg236-1_constraints_luts.device
```
Generate nextpnr BBA and constids.inc from device database:
```
python3 -mfpga_interchange.nextpnr_emit \
--schema_dir ${INTERCHANGE_DIR} \
--output_dir ${NEXTPNR_DIR}/fpga_interchange/ \
--bel_bucket_seeds test_data/series7_bel_buckets.yaml \
--device xc7a35tcpg236-1_constraints_luts.device \
```
Build nextpnr:
```
cd ${NEXTPNR_DIR}
cmake -DARCH=fpga_interchange .
make -j
```
Compile generated BBA:
```
bba/bbasm -l fpga_interchange/chipdb.bba fpga_interchange/chipdb.bin
```
Run nextpnr archcheck:
```
./nextpnr-fpga_interchange --chipdb fpga_interchange/chipdb.bin --test
```
Once nextpnr can complete the place and route task and output the physical
netlist, RapidWright can be used to generate a DCP suitable for bitstream
output and DRC checks.
```
${RAPIDWRIGHT_PATH}/scripts/invoke_rapidwright.sh \
com.xilinx.rapidwright.interchange.PhysicalNetlistToDcp \
<logical netlist file> <physical netlist file> <XDC file> <output DCP>
```
Construction of chipdb's is currently integrated into nextpnr's CMake build
system. See fpga\_interchange/examples/README.md for more details.

View File

@ -49,6 +49,10 @@
//#define USE_LOOKAHEAD
//#define DEBUG_CELL_PIN_MAPPING
// Define to enable some idempotent sanity checks for some important
// operations prior to placement and routing.
#define IDEMPOTENT_CHECK
NEXTPNR_NAMESPACE_BEGIN
struct SiteBelPair
{
@ -144,6 +148,7 @@ Arch::Arch(ArchArgs args) : args(args)
io_port_types.emplace(this->id("$nextpnr_ibuf"));
io_port_types.emplace(this->id("$nextpnr_obuf"));
io_port_types.emplace(this->id("$nextpnr_iobuf"));
io_port_types.emplace(this->id("$nextpnr_inv"));
if (!this->args.package.empty()) {
IdString package = this->id(this->args.package);
@ -709,18 +714,32 @@ bool Arch::pack()
return true;
}
bool Arch::place()
static void prepare_for_placement(Context *ctx)
{
std::string placer = str_or_default(settings, id("placer"), defaultPlacer);
ctx->remove_site_routing();
// Re-map BEL pins without constant pins
for (BelId bel : getBels()) {
CellInfo *cell = getBoundBelCell(bel);
for (BelId bel : ctx->getBels()) {
CellInfo *cell = ctx->getBoundBelCell(bel);
if (cell != nullptr && cell->cell_mapping != -1) {
map_cell_pins(cell, cell->cell_mapping, /*bind_constants=*/false);
ctx->map_cell_pins(cell, cell->cell_mapping, /*bind_constants=*/false);
}
}
}
bool Arch::place()
{
// Before placement, ripup placement specific bindings and unmask all cell
// pins.
getCtx()->check();
prepare_for_placement(getCtx());
getCtx()->check();
#ifdef IDEMPOTENT_CHECK
prepare_for_placement(getCtx());
getCtx()->check();
#endif
std::string placer = str_or_default(settings, id("placer"), defaultPlacer);
if (placer == "heap") {
PlacerHeapCfg cfg(getCtx());
cfg.criticalityExponent = 7;
@ -743,113 +762,43 @@ bool Arch::place()
getCtx()->attrs[getCtx()->id("step")] = std::string("place");
archInfoToAttributes();
getCtx()->check();
return true;
}
bool Arch::route()
static void prepare_sites_for_routing(Context *ctx)
{
std::string router = str_or_default(settings, id("router"), defaultRouter);
// Reset site routing and remove masked cell pins from previous router run
// (if any).
ctx->remove_site_routing();
// Re-map BEL pins with constant pins
for (BelId bel : getBels()) {
CellInfo *cell = getBoundBelCell(bel);
for (BelId bel : ctx->getBels()) {
CellInfo *cell = ctx->getBoundBelCell(bel);
if (cell != nullptr && cell->cell_mapping != -1) {
map_cell_pins(cell, cell->cell_mapping, /*bind_constants=*/true);
ctx->map_cell_pins(cell, cell->cell_mapping, /*bind_constants=*/true);
}
}
HashTables::HashSet<WireId> wires_to_unbind;
for (auto &net_pair : nets) {
for (auto &wire_pair : net_pair.second->wires) {
WireId wire = wire_pair.first;
if (wire_pair.second.strength != STRENGTH_PLACER) {
// Only looking for bound placer wires
continue;
}
const TileWireInfoPOD &wire_data = wire_info(wire);
NPNR_ASSERT(wire_data.site != -1);
wires_to_unbind.emplace(wire);
}
}
for (WireId wire : wires_to_unbind) {
unbindWire(wire);
}
for (auto &tile_pair : tileStatus) {
// Have site router bind site routing (via bindPip and bindWire).
// This is important so that the pseudo pips are correctly blocked prior
// to handing the design to the generalized router algorithms.
for (auto &tile_pair : ctx->tileStatus) {
for (auto &site_router : tile_pair.second.sites) {
if (site_router.cells_in_site.empty()) {
continue;
}
site_router.bindSiteRouting(getCtx());
}
}
bool result;
if (router == "router1") {
result = router1(getCtx(), Router1Cfg(getCtx()));
} else if (router == "router2") {
router2(getCtx(), Router2Cfg(getCtx()));
result = true;
} else {
log_error("FPGA interchange architecture does not support router '%s'\n", router.c_str());
}
if (result) {
result = route_vcc_to_unused_lut_pins();
}
getCtx()->attrs[getCtx()->id("step")] = std::string("route");
archInfoToAttributes();
return result;
}
bool Arch::route_vcc_to_unused_lut_pins()
{
std::string router = str_or_default(settings, id("router"), defaultRouter);
HashTables::HashMap<WireId, const NetInfo *> bound_wires;
for (auto &net_pair : nets) {
const NetInfo *net = net_pair.second.get();
for (auto &wire_pair : net->wires) {
auto result = bound_wires.emplace(wire_pair.first, net);
NPNR_ASSERT(result.first->second == net);
PipId pip = wire_pair.second.pip;
if (pip == PipId()) {
continue;
}
const PipInfoPOD &pip_data = pip_info(chip_info, pip);
#ifdef DEBUG_LUT_MAPPING
if (getCtx()->verbose) {
log_info("Pip %s in use, has %zu pseudo wires!\n", nameOfPip(pip), pip_data.pseudo_cell_wires.size());
}
#endif
WireId wire;
wire.tile = pip.tile;
for (int32_t wire_index : pip_data.pseudo_cell_wires) {
wire.index = wire_index;
#ifdef DEBUG_LUT_MAPPING
if (getCtx()->verbose) {
log_info("Marking wire %s as in use due to pseudo pip\n", nameOfWire(wire));
}
#endif
auto result = bound_wires.emplace(wire, net);
NPNR_ASSERT(result.first->second == net);
}
site_router.bindSiteRouting(ctx);
}
}
// Fixup LUT vcc pins.
IdString vcc_net_name(chip_info->constants->vcc_net_name);
for (BelId bel : getBels()) {
CellInfo *cell = getBoundBelCell(bel);
IdString vcc_net_name(ctx->chip_info->constants->vcc_net_name);
for (BelId bel : ctx->getBels()) {
CellInfo *cell = ctx->getBoundBelCell(bel);
if (cell == nullptr) {
continue;
}
@ -864,45 +813,60 @@ bool Arch::route_vcc_to_unused_lut_pins()
port_info.type = PORT_IN;
port_info.net = nullptr;
WireId lut_pin_wire = getBelPinWire(bel, bel_pin);
auto iter = bound_wires.find(lut_pin_wire);
if (iter != bound_wires.end()) {
#ifdef DEBUG_LUT_MAPPING
if (getCtx()->verbose) {
log_info("%s is now used as a LUT route-through, not tying to VCC\n", nameOfWire(lut_pin_wire));
}
#endif
continue;
}
#ifdef DEBUG_LUT_MAPPING
if (getCtx()->verbose) {
log_info("%s is an unused LUT pin, tying to VCC\n", nameOfWire(lut_pin_wire));
if (ctx->verbose) {
log_info("%s must be tied to VCC, tying now\n", ctx->nameOfWire(lut_pin_wire));
}
#endif
auto result = cell->ports.emplace(bel_pin, port_info);
if (result.second) {
cell->cell_bel_pins[bel_pin].push_back(bel_pin);
connectPort(vcc_net_name, cell->name, bel_pin);
ctx->connectPort(vcc_net_name, cell->name, bel_pin);
cell->const_ports.emplace(bel_pin);
} else {
NPNR_ASSERT(result.first->second.net == getNetByAlias(vcc_net_name));
NPNR_ASSERT(result.first->second.net == ctx->getNetByAlias(vcc_net_name));
auto result2 = cell->cell_bel_pins.emplace(bel_pin, std::vector<IdString>({bel_pin}));
NPNR_ASSERT(result2.first->second.at(0) == bel_pin);
NPNR_ASSERT(result2.first->second.size() == 1);
}
}
}
}
bool Arch::route()
{
getCtx()->check();
prepare_sites_for_routing(getCtx());
getCtx()->check();
#ifdef IDEMPOTENT_CHECK
prepare_sites_for_routing(getCtx());
getCtx()->check();
#endif
std::string router = str_or_default(settings, id("router"), defaultRouter);
bool result;
if (router == "router1") {
return router1(getCtx(), Router1Cfg(getCtx()));
result = router1(getCtx(), Router1Cfg(getCtx()));
} else if (router == "router2") {
router2(getCtx(), Router2Cfg(getCtx()));
return true;
result = true;
} else {
log_error("FPGA interchange architecture does not support router '%s'\n", router.c_str());
}
getCtx()->attrs[getCtx()->id("step")] = std::string("route");
archInfoToAttributes();
getCtx()->check();
// Now that routing is complete, unmask BEL pins.
unmask_bel_pins();
getCtx()->check();
return result;
}
// -----------------------------------------------------------------------
@ -1020,6 +984,7 @@ void Arch::map_cell_pins(CellInfo *cell, int32_t mapping, bool bind_constants)
cell->cell_mapping = mapping;
if (cell->lut_cell.pins.empty()) {
cell->cell_bel_pins.clear();
cell->masked_cell_bel_pins.clear();
} else {
std::vector<IdString> cell_pin_to_remove;
for (auto port_pair : cell->cell_bel_pins) {
@ -1032,7 +997,9 @@ void Arch::map_cell_pins(CellInfo *cell, int32_t mapping, bool bind_constants)
NPNR_ASSERT(cell->cell_bel_pins.erase(cell_pin));
}
}
for (IdString const_port : cell->const_ports) {
disconnectPort(cell->name, const_port);
NPNR_ASSERT(cell->ports.erase(const_port));
}
@ -1799,6 +1766,131 @@ bool Arch::can_invert(PipId pip) const
return bel_data.non_inverting_pin == pip_info.extra_data && bel_data.inverting_pin == pip_info.extra_data;
}
void Arch::mask_bel_pins_on_site_wire(NetInfo *net, WireId wire)
{
std::vector<size_t> bel_pins_to_mask;
for (const PortRef &port_ref : net->users) {
if (port_ref.cell->bel == BelId()) {
continue;
}
NPNR_ASSERT(port_ref.cell != nullptr);
auto iter = port_ref.cell->cell_bel_pins.find(port_ref.port);
if (iter == port_ref.cell->cell_bel_pins.end()) {
continue;
}
std::vector<IdString> &cell_bel_pins = iter->second;
bel_pins_to_mask.clear();
for (size_t bel_pin_idx = 0; bel_pin_idx < cell_bel_pins.size(); ++bel_pin_idx) {
IdString bel_pin = cell_bel_pins.at(bel_pin_idx);
WireId bel_pin_wire = getBelPinWire(port_ref.cell->bel, bel_pin);
if (bel_pin_wire == wire) {
bel_pins_to_mask.push_back(bel_pin_idx);
}
}
if (!bel_pins_to_mask.empty()) {
std::vector<IdString> &masked_cell_bel_pins = port_ref.cell->masked_cell_bel_pins[port_ref.port];
// Remove in reverse order to preserve indicies.
for (auto riter = bel_pins_to_mask.rbegin(); riter != bel_pins_to_mask.rend(); ++riter) {
size_t bel_pin_idx = *riter;
masked_cell_bel_pins.push_back(cell_bel_pins.at(bel_pin_idx));
cell_bel_pins.erase(cell_bel_pins.begin() + bel_pin_idx);
}
}
}
}
void Arch::unmask_bel_pins()
{
for (auto &cell_pair : cells) {
CellInfo *cell = cell_pair.second.get();
if (cell->masked_cell_bel_pins.empty()) {
continue;
}
for (auto &mask_pair : cell->masked_cell_bel_pins) {
IdString cell_port = mask_pair.first;
const std::vector<IdString> &bel_pins = mask_pair.second;
std::vector<IdString> &cell_bel_pins = cell->cell_bel_pins[cell_port];
cell_bel_pins.insert(cell_bel_pins.begin(), bel_pins.begin(), bel_pins.end());
}
cell->masked_cell_bel_pins.clear();
}
}
void Arch::remove_site_routing()
{
HashTables::HashSet<WireId> wires_to_unbind;
for (auto &net_pair : nets) {
for (auto &wire_pair : net_pair.second->wires) {
WireId wire = wire_pair.first;
if (wire_pair.second.strength != STRENGTH_PLACER) {
// Only looking for bound placer wires
continue;
}
wires_to_unbind.emplace(wire);
}
}
for (WireId wire : wires_to_unbind) {
unbindWire(wire);
}
unmask_bel_pins();
IdString id_NEXTPNR_INV = id("$nextpnr_inv");
IdString id_I = id("I");
std::vector<IdString> cells_to_remove;
for (auto &cell_pair : cells) {
CellInfo *cell = cell_pair.second.get();
if (cell->type != id_NEXTPNR_INV) {
continue;
}
disconnectPort(cell_pair.first, id_I);
cells_to_remove.push_back(cell_pair.first);
tileStatus.at(cell->bel.tile).boundcells[cell->bel.index] = nullptr;
}
for (IdString cell_name : cells_to_remove) {
NPNR_ASSERT(cells.erase(cell_name) == 1);
}
}
void Arch::explain_bel_status(BelId bel) const
{
if (isBelLocationValid(bel)) {
log_info("BEL %s is valid!\n", nameOfBel(bel));
return;
}
auto iter = tileStatus.find(bel.tile);
NPNR_ASSERT(iter != tileStatus.end());
const TileStatus &tile_status = iter->second;
const CellInfo *cell = tile_status.boundcells[bel.index];
if (!dedicated_interconnect.isBelLocationValid(bel, cell)) {
dedicated_interconnect.explain_bel_status(bel, cell);
return;
}
if (io_port_types.count(cell->type)) {
return;
}
if (!is_cell_valid_constraints(cell, tile_status, /*explain_constraints=*/true)) {
return;
}
auto &bel_data = bel_info(chip_info, bel);
const SiteRouter &site = get_site_status(tile_status, bel_data);
NPNR_ASSERT(!site.checkSiteRouting(getCtx(), tile_status));
site.explain(getCtx());
}
// Instance constraint templates.
template void Arch::ArchConstraints::bindBel(Arch::ArchConstraints::TagState *, const Arch::ConstraintRange);
template void Arch::ArchConstraints::unbindBel(Arch::ArchConstraints::TagState *, const Arch::ConstraintRange);

View File

@ -217,10 +217,15 @@ struct Arch : ArchAPI<ArchRanges>
PhysicalNetlist::PhysNetlist::NetType get_net_type(NetInfo *net) const
{
NPNR_ASSERT(net->driver.cell != nullptr);
if (net->driver.cell->bel == get_gnd_bel()) {
NPNR_ASSERT(net != nullptr);
IdString gnd_cell_name(chip_info->constants->gnd_cell_name);
IdString gnd_cell_port(chip_info->constants->gnd_cell_port);
IdString vcc_cell_name(chip_info->constants->vcc_cell_name);
IdString vcc_cell_port(chip_info->constants->vcc_cell_port);
if (net->driver.cell->type == gnd_cell_name && net->driver.port == gnd_cell_port) {
return PhysicalNetlist::PhysNetlist::NetType::GND;
} else if (net->driver.cell->bel == get_vcc_bel()) {
} else if (net->driver.cell->type == vcc_cell_name && net->driver.port == vcc_cell_port) {
return PhysicalNetlist::PhysNetlist::NetType::VCC;
} else {
return PhysicalNetlist::PhysNetlist::NetType::SIGNAL;
@ -1059,7 +1064,6 @@ struct Arch : ArchAPI<ArchRanges>
std::regex verilog_bin_constant;
std::regex verilog_hex_constant;
void read_lut_equation(DynamicBitarray<> *equation, const Property &equation_parameter) const;
bool route_vcc_to_unused_lut_pins();
IdString id_GND;
IdString id_VCC;
@ -1070,6 +1074,21 @@ struct Arch : ArchAPI<ArchRanges>
std::string chipdb_hash;
std::string get_chipdb_hash() const;
// Masking moves BEL pins from cell_bel_pins to masked_cell_bel_pins for
// the purposes routing. The idea is that masked BEL pins are already
// handled during site routing, and they shouldn't be visible to the
// router.
void mask_bel_pins_on_site_wire(NetInfo *net, WireId wire);
// This removes pips and wires bound by the site router, and unmasks all
// BEL pins masked during site routing.
void remove_site_routing();
// This unmasks any BEL pins that were masked when site routing was bound.
void unmask_bel_pins();
void explain_bel_status(BelId bel) const;
};
NEXTPNR_NAMESPACE_END

View File

@ -109,10 +109,9 @@ struct ArchNetInfo
struct ArchCellInfo
{
ArchCellInfo() : cell_mapping(-1) {}
int32_t cell_mapping;
int32_t cell_mapping = -1;
HashTables::HashMap<IdString, std::vector<IdString>> cell_bel_pins;
HashTables::HashMap<IdString, std::vector<IdString>> masked_cell_bel_pins;
HashTables::HashSet<IdString> const_ports;
LutCell lut_cell;
};

View File

@ -365,6 +365,35 @@ bool DedicatedInterconnect::isBelLocationValid(BelId bel, const CellInfo *cell)
return true;
}
void DedicatedInterconnect::explain_bel_status(BelId bel, const CellInfo *cell) const
{
NPNR_ASSERT(bel != BelId());
for (const auto &port_pair : cell->ports) {
IdString port_name = port_pair.first;
NetInfo *net = port_pair.second.net;
if (net == nullptr) {
continue;
}
// This net doesn't have a driver, probably not valid?
NPNR_ASSERT(net->driver.cell != nullptr);
// Only check sink BELs.
if (net->driver.cell == cell && net->driver.port == port_name) {
if (!is_driver_on_net_valid(bel, cell, port_name, net)) {
log_info("Driver %s/%s is not valid on net '%s'", cell->name.c_str(ctx), port_name.c_str(ctx),
net->name.c_str(ctx));
}
} else {
if (!is_sink_on_net_valid(bel, cell, port_name, net)) {
log_info("Sink %s/%s is not valid on net '%s'", cell->name.c_str(ctx), port_name.c_str(ctx),
net->name.c_str(ctx));
}
}
}
}
void DedicatedInterconnect::print_dedicated_interconnect() const
{
log_info("Found %zu sinks with dedicated interconnect\n", sinks.size());

View File

@ -133,6 +133,7 @@ struct DedicatedInterconnect
//
// Note: Only BEL pin sinks are checked.
bool isBelLocationValid(BelId bel, const CellInfo *cell) const;
void explain_bel_status(BelId bel, const CellInfo *cell) const;
void find_dedicated_interconnect();
void print_dedicated_interconnect() const;

View File

@ -29,6 +29,13 @@ Install python-fpga-interchange if not already installed:
```
git clone https://github.com/SymbiFlow/python-fpga-interchange.git
cd python-fpga-interchange.git
#
# Note: Recommend checking out a specific release, for example:
#
# git checkout v0.0.5
#
# Release of python-fpga-interchange library does have to match nextpnr
# implementation.
python -m pip install -e .
```

View File

@ -51,6 +51,17 @@ function(create_rapidwright_device_db)
add_custom_target(rapidwright-${device}-device DEPENDS ${rapidwright_device_db})
set_property(TARGET rapidwright-${device}-device PROPERTY LOCATION ${rapidwright_device_db})
add_custom_target(rapidwright-${device}-device-yaml
COMMAND
${PYTHON_EXECUTABLE} -mfpga_interchange.convert
--schema_dir ${INTERCHANGE_SCHEMA_PATH}
--schema device
--input_format capnp
--output_format yaml
${rapidwright_device_db}
${rapidwright_device_db}.yaml
DEPENDS ${rapidwright_device_db})
if (DEFINED output_target)
set(${output_target} rapidwright-${device}-device PARENT_SCOPE)
endif()
@ -129,6 +140,17 @@ function(create_patched_device_db)
add_custom_target(${patch_name}-${device}-device DEPENDS ${output_device_file})
set_property(TARGET ${patch_name}-${device}-device PROPERTY LOCATION ${output_device_file})
add_custom_target(${patch_name}-${device}-device-yaml
COMMAND
${PYTHON_EXECUTABLE} -mfpga_interchange.convert
--schema_dir ${INTERCHANGE_SCHEMA_PATH}
--schema device
--input_format capnp
--output_format yaml
${output_device_file}
${output_device_file}.yaml
DEPENDS ${output_device_file})
if (DEFINED output_target)
set(${output_target} ${patch_name}-${device}-device PARENT_SCOPE)
endif()

View File

@ -1,6 +1,6 @@
add_subdirectory(wire)
add_subdirectory(const_wire)
# FIXME: re-enable counter test as soon as post placement validity check completes successfully.
#add_subdirectory(counter)
add_subdirectory(counter)
add_subdirectory(ram)
add_subdirectory(ff)
add_subdirectory(lut)

View File

@ -0,0 +1,10 @@
add_interchange_test(
name ram_basys3
family ${family}
device xc7a35t
package cpg236
tcl run.tcl
xdc basys3.xdc
sources ram.v
)

View File

@ -0,0 +1,41 @@
# basys3 100 MHz CLK
set_io clk W5
set_io tx A18
set_io rx B18
#
# in[0:15] correspond with SW0-SW15 on the basys3
set_io sw[0] V17
set_io sw[1] V16
set_io sw[2] W16
set_io sw[3] W17
set_io sw[4] W15
set_io sw[5] V15
set_io sw[6] W14
set_io sw[7] W13
set_io sw[8] V2
set_io sw[9] T3
set_io sw[10] T2
set_io sw[11] R3
set_io sw[12] W2
set_io sw[13] U1
set_io sw[14] T1
set_io sw[15] R2
# out[0:15] correspond with LD0-LD15 on the basys3
set_io led[0] U16
set_io led[1] E19
set_io led[2] U19
set_io led[3] V19
set_io led[4] W18
set_io led[5] U15
set_io led[6] U14
set_io led[7] V14
set_io led[8] V13
set_io led[9] V3
set_io led[10] W3
set_io led[11] U3
set_io led[12] P3
set_io led[13] N3
set_io led[14] P1
set_io led[15] L1

View File

@ -0,0 +1,80 @@
# basys3 100 MHz CLK
set_property PACKAGE_PIN W5 [get_ports clk]
set_property PACKAGE_PIN A18 [get_ports tx]
set_property PACKAGE_PIN B18 [get_ports rx]
#
# in[0:15] correspond with SW0-SW15 on the basys3
set_property PACKAGE_PIN V17 [get_ports sw[0]]
set_property PACKAGE_PIN V16 [get_ports sw[1]]
set_property PACKAGE_PIN W16 [get_ports sw[2]]
set_property PACKAGE_PIN W17 [get_ports sw[3]]
set_property PACKAGE_PIN W15 [get_ports sw[4]]
set_property PACKAGE_PIN V15 [get_ports sw[5]]
set_property PACKAGE_PIN W14 [get_ports sw[6]]
set_property PACKAGE_PIN W13 [get_ports sw[7]]
set_property PACKAGE_PIN V2 [get_ports sw[8]]
set_property PACKAGE_PIN T3 [get_ports sw[9]]
set_property PACKAGE_PIN T2 [get_ports sw[10]]
set_property PACKAGE_PIN R3 [get_ports sw[11]]
set_property PACKAGE_PIN W2 [get_ports sw[12]]
set_property PACKAGE_PIN U1 [get_ports sw[13]]
set_property PACKAGE_PIN T1 [get_ports sw[14]]
set_property PACKAGE_PIN R2 [get_ports sw[15]]
# out[0:15] correspond with LD0-LD15 on the basys3
set_property PACKAGE_PIN U16 [get_ports led[0]]
set_property PACKAGE_PIN E19 [get_ports led[1]]
set_property PACKAGE_PIN U19 [get_ports led[2]]
set_property PACKAGE_PIN V19 [get_ports led[3]]
set_property PACKAGE_PIN W18 [get_ports led[4]]
set_property PACKAGE_PIN U15 [get_ports led[5]]
set_property PACKAGE_PIN U14 [get_ports led[6]]
set_property PACKAGE_PIN V14 [get_ports led[7]]
set_property PACKAGE_PIN V13 [get_ports led[8]]
set_property PACKAGE_PIN V3 [get_ports led[9]]
set_property PACKAGE_PIN W3 [get_ports led[10]]
set_property PACKAGE_PIN U3 [get_ports led[11]]
set_property PACKAGE_PIN P3 [get_ports led[12]]
set_property PACKAGE_PIN N3 [get_ports led[13]]
set_property PACKAGE_PIN P1 [get_ports led[14]]
set_property PACKAGE_PIN L1 [get_ports led[15]]
set_property IOSTANDARD LVCMOS33 [get_ports clk]
set_property IOSTANDARD LVCMOS33 [get_ports tx]
set_property IOSTANDARD LVCMOS33 [get_ports rx]
#
set_property IOSTANDARD LVCMOS33 [get_ports sw[0]]
set_property IOSTANDARD LVCMOS33 [get_ports sw[1]]
set_property IOSTANDARD LVCMOS33 [get_ports sw[2]]
set_property IOSTANDARD LVCMOS33 [get_ports sw[3]]
set_property IOSTANDARD LVCMOS33 [get_ports sw[4]]
set_property IOSTANDARD LVCMOS33 [get_ports sw[5]]
set_property IOSTANDARD LVCMOS33 [get_ports sw[6]]
set_property IOSTANDARD LVCMOS33 [get_ports sw[7]]
set_property IOSTANDARD LVCMOS33 [get_ports sw[8]]
set_property IOSTANDARD LVCMOS33 [get_ports sw[9]]
set_property IOSTANDARD LVCMOS33 [get_ports sw[10]]
set_property IOSTANDARD LVCMOS33 [get_ports sw[11]]
set_property IOSTANDARD LVCMOS33 [get_ports sw[12]]
set_property IOSTANDARD LVCMOS33 [get_ports sw[13]]
set_property IOSTANDARD LVCMOS33 [get_ports sw[14]]
set_property IOSTANDARD LVCMOS33 [get_ports sw[15]]
set_property IOSTANDARD LVCMOS33 [get_ports led[0]]
set_property IOSTANDARD LVCMOS33 [get_ports led[1]]
set_property IOSTANDARD LVCMOS33 [get_ports led[2]]
set_property IOSTANDARD LVCMOS33 [get_ports led[3]]
set_property IOSTANDARD LVCMOS33 [get_ports led[4]]
set_property IOSTANDARD LVCMOS33 [get_ports led[5]]
set_property IOSTANDARD LVCMOS33 [get_ports led[6]]
set_property IOSTANDARD LVCMOS33 [get_ports led[7]]
set_property IOSTANDARD LVCMOS33 [get_ports led[8]]
set_property IOSTANDARD LVCMOS33 [get_ports led[9]]
set_property IOSTANDARD LVCMOS33 [get_ports led[10]]
set_property IOSTANDARD LVCMOS33 [get_ports led[11]]
set_property IOSTANDARD LVCMOS33 [get_ports led[12]]
set_property IOSTANDARD LVCMOS33 [get_ports led[13]]
set_property IOSTANDARD LVCMOS33 [get_ports led[14]]
set_property IOSTANDARD LVCMOS33 [get_ports led[15]]

View File

@ -0,0 +1,134 @@
module ram0(
// Write port
input wrclk,
input [15:0] di,
input wren,
input [9:0] wraddr,
// Read port
input rdclk,
input rden,
input [9:0] rdaddr,
output reg [15:0] do);
(* ram_style = "block" *) reg [15:0] ram[0:1023];
initial begin
ram[0] = 16'b00000000_00000001;
ram[1] = 16'b10101010_10101010;
ram[2] = 16'b01010101_01010101;
ram[3] = 16'b11111111_11111111;
ram[4] = 16'b11110000_11110000;
ram[5] = 16'b00001111_00001111;
ram[6] = 16'b11001100_11001100;
ram[7] = 16'b00110011_00110011;
ram[8] = 16'b00000000_00000010;
ram[9] = 16'b00000000_00000100;
end
always @ (posedge wrclk) begin
if(wren == 1) begin
ram[wraddr] <= di;
end
end
always @ (posedge rdclk) begin
if(rden == 1) begin
do <= ram[rdaddr];
end
end
endmodule
module top (
input wire clk,
input wire rx,
output wire tx,
input wire [15:0] sw,
output wire [15:0] led
);
wire rden;
reg wren;
wire [9:0] rdaddr;
wire [9:0] wraddr;
wire [15:0] di;
wire [15:0] do;
ram0 ram(
.wrclk(clk),
.di(di),
.wren(wren),
.wraddr(wraddr),
.rdclk(clk),
.rden(rden),
.rdaddr(rdaddr),
.do(do)
);
reg [9:0] address_reg;
reg [15:0] data_reg;
reg [15:0] out_reg;
assign rdaddr = address_reg;
assign wraddr = address_reg;
// display_mode == 00 -> ram[address_reg]
// display_mode == 01 -> address_reg
// display_mode == 10 -> data_reg
wire [1:0] display_mode;
// input_mode == 00 -> in[9:0] -> address_reg
// input_mode == 01 -> in[7:0] -> data_reg[7:0]
// input_mode == 10 -> in[7:0] -> data_reg[15:8]
// input_mode == 11 -> data_reg -> ram[address_reg]
wire [1:0] input_mode;
// WE == 0 -> address_reg and data_reg unchanged.
// WE == 1 -> address_reg or data_reg is updated because on input_mode.
wire we;
assign display_mode[0] = sw[14];
assign display_mode[1] = sw[15];
assign input_mode[0] = sw[12];
assign input_mode[1] = sw[13];
assign we = sw[11];
assign led = out_reg;
assign di = data_reg;
assign rden = 1;
initial begin
address_reg = 10'b0;
data_reg = 16'b0;
out_reg = 16'b0;
end
always @ (posedge clk) begin
if(display_mode == 0) begin
out_reg <= do;
end else if(display_mode == 1) begin
out_reg <= address_reg;
end else if(display_mode == 2) begin
out_reg <= data_reg;
end
if(we == 1) begin
if(input_mode == 0) begin
address_reg <= sw[9:0];
wren <= 0;
end else if(input_mode == 1) begin
data_reg[7:0] <= sw[7:0];
wren <= 0;
end else if(input_mode == 2) begin
data_reg[15:8] <= sw[7:0];
wren <= 0;
end else if(input_mode == 3) begin
wren <= 1;
end
end
end
// Uart loopback
assign tx = rx;
endmodule

View File

@ -0,0 +1,17 @@
yosys -import
foreach src $::env(SOURCES) {
read_verilog $src
}
synth_xilinx -flatten -nolutram -nowidelut -nosrl -nocarry -nodsp
techmap -map $::env(TECHMAP)
# opt_expr -undriven makes sure all nets are driven, if only by the $undef
# net.
opt_expr -undriven
opt_clean
setundef -zero -params
write_json $::env(OUT_JSON)

View File

@ -220,11 +220,25 @@ static void init_bel_pin(
std::string site_name = site_and_type.substr(0, pos);
auto out_bel_pin = branch.getRouteSegment().initBelPin();
const BelInfoPOD & bel_data = bel_info(ctx->chip_info, bel);
if(bel_data.category == BEL_CATEGORY_LOGIC) {
// This is a boring old logic BEL.
auto out_bel_pin = branch.getRouteSegment().initBelPin();
out_bel_pin.setSite(strings->get_index(site_name));
out_bel_pin.setBel(strings->get_index(bel_name[1].str(ctx)));
out_bel_pin.setPin(strings->get_index(pin_name.str(ctx)));
out_bel_pin.setSite(strings->get_index(site_name));
out_bel_pin.setBel(strings->get_index(bel_name[1].str(ctx)));
out_bel_pin.setPin(strings->get_index(pin_name.str(ctx)));
} else {
// This is a local site inverter. This is represented with a
// $nextpnr_inv, and this BEL pin is the input to that inverter.
NPNR_ASSERT(bel_data.category == BEL_CATEGORY_ROUTING);
auto out_pip = branch.getRouteSegment().initSitePIP();
out_pip.setSite(strings->get_index(site_name));
out_pip.setBel(strings->get_index(bel_name[1].str(ctx)));
out_pip.setPin(strings->get_index(pin_name.str(ctx)));
out_pip.setIsInverting(true);
}
}
@ -383,10 +397,16 @@ void FpgaInterchange::write_physical_netlist(const Context * ctx, const std::str
StringEnumerator strings;
IdString nextpnr_inv = ctx->id("$nextpnr_inv");
size_t number_placements = 0;
for(auto & cell_name : placed_cells) {
const CellInfo & cell = *ctx->cells.at(cell_name);
if(cell.type == nextpnr_inv) {
continue;
}
if(cell.bel == BelId()) {
continue;
}
@ -412,6 +432,10 @@ void FpgaInterchange::write_physical_netlist(const Context * ctx, const std::str
for(auto & cell_name : placed_cells) {
const CellInfo & cell = *ctx->cells.at(cell_name);
if(cell.type == nextpnr_inv) {
continue;
}
if(cell.bel == BelId()) {
continue;
}
@ -513,8 +537,6 @@ void FpgaInterchange::write_physical_netlist(const Context * ctx, const std::str
net_out.setName(strings.get_index(net.name.str(ctx)));
}
// FIXME: Also vcc/gnd nets needs to get special handling through
// inverters.
std::unordered_map<WireId, BelPin> root_wires;
std::unordered_map<WireId, std::vector<PipId>> pip_downhill;
std::unordered_set<PipId> pips;

View File

@ -45,6 +45,12 @@ bool rotate_and_merge_lut_equation(std::vector<LogicLevel> *result, const LutBel
if ((bel_address & (1 << bel_pin_idx)) == 0) {
// This pin is unused, so the line will be tied high, this
// address is unreachable.
//
// FIXME: The assumption is that unused pins are tied VCC.
// This is not generally true.
//
// Use Arch::prefered_constant_net_type to determine what
// constant net should be used for unused pins.
if ((used_pins & (1 << bel_pin_idx)) == 0) {
address_reachable = false;
break;
@ -124,6 +130,86 @@ struct LutPin
//#define DEBUG_LUT_ROTATION
uint32_t LutMapper::check_wires(const std::vector<std::vector<int32_t>> &bel_to_cell_pin_remaps,
const std::vector<const LutBel *> &lut_bels, uint32_t used_pins) const
{
std::vector<const LutBel *> unused_luts;
for (auto &lut_bel_pair : element.lut_bels) {
if (std::find(lut_bels.begin(), lut_bels.end(), &lut_bel_pair.second) == lut_bels.end()) {
unused_luts.push_back(&lut_bel_pair.second);
}
}
// FIXME: The assumption is that unused pins are tied VCC.
// This is not generally true.
//
// Use Arch::prefered_constant_net_type to determine what
// constant net should be used for unused pins.
uint32_t vcc_mask = 0;
DynamicBitarray<> wire_equation;
wire_equation.resize(2);
wire_equation.set(0, false);
wire_equation.set(1, true);
std::vector<int32_t> wire_bel_to_cell_pin_map;
std::vector<LogicLevel> equation_result;
for (int32_t pin_idx = 0; pin_idx < (int32_t)element.pins.size(); ++pin_idx) {
if (used_pins & (1 << pin_idx)) {
// This pin is already used, so it cannot be used for a wire.
continue;
}
bool valid_pin_for_wire = false;
bool invalid_pin_for_wire = false;
for (const LutBel *lut_bel : unused_luts) {
if (pin_idx < lut_bel->min_pin) {
continue;
}
if (pin_idx > lut_bel->max_pin) {
continue;
}
wire_bel_to_cell_pin_map.clear();
wire_bel_to_cell_pin_map.resize(lut_bel->pins.size(), -1);
wire_bel_to_cell_pin_map[lut_bel->pin_to_index.at(element.pins[pin_idx])] = 0;
equation_result.clear();
equation_result.resize(element.width, LL_DontCare);
uint32_t used_pins_with_wire = used_pins | (1 << pin_idx);
for (size_t cell_idx = 0; cell_idx < cells.size(); ++cell_idx) {
const CellInfo *cell = cells[cell_idx];
auto &lut_bel_for_cell = *lut_bels[cell_idx];
if (!rotate_and_merge_lut_equation(&equation_result, lut_bel_for_cell, cell->lut_cell.equation,
bel_to_cell_pin_remaps[cell_idx], used_pins_with_wire)) {
invalid_pin_for_wire = true;
break;
}
}
if (invalid_pin_for_wire) {
break;
}
if (rotate_and_merge_lut_equation(&equation_result, *lut_bel, wire_equation, wire_bel_to_cell_pin_map,
used_pins_with_wire)) {
valid_pin_for_wire = true;
}
}
bool good_for_wire = valid_pin_for_wire && !invalid_pin_for_wire;
if (!good_for_wire) {
vcc_mask |= (1 << pin_idx);
}
}
return vcc_mask;
}
bool LutMapper::remap_luts(const Context *ctx)
{
std::unordered_map<NetInfo *, LutPin> lut_pin_map;
@ -259,12 +345,49 @@ bool LutMapper::remap_luts(const Context *ctx)
bel_pins.clear();
bel_pins.push_back(lut_bel.pins[cell_to_bel_pin_remaps[cell_idx][pin_idx]]);
}
}
cell->lut_cell.vcc_pins.clear();
for (size_t bel_pin_idx = 0; bel_pin_idx < lut_bel.pins.size(); ++bel_pin_idx) {
if ((used_pins & (1 << bel_pin_idx)) == 0) {
NPNR_ASSERT(bel_to_cell_pin_remaps[cell_idx][bel_pin_idx] == -1);
cell->lut_cell.vcc_pins.emplace(lut_bel.pins.at(bel_pin_idx));
if (cells.size() == element.lut_bels.size()) {
for (size_t cell_idx = 0; cell_idx < cells.size(); ++cell_idx) {
CellInfo *cell = cells[cell_idx];
auto &lut_bel = *lut_bels[cell_idx];
cell->lut_cell.vcc_pins.clear();
for (size_t bel_pin_idx = 0; bel_pin_idx < lut_bel.pins.size(); ++bel_pin_idx) {
if ((used_pins & (1 << bel_pin_idx)) == 0) {
NPNR_ASSERT(bel_to_cell_pin_remaps[cell_idx][bel_pin_idx] == -1);
cell->lut_cell.vcc_pins.emplace(lut_bel.pins.at(bel_pin_idx));
}
}
}
} else {
// Look to see if wires can be run from element inputs to unused
// outputs. If not, block the BEL pin by tying to VCC.
//
// FIXME: The assumption is that unused pins are tied VCC.
// This is not generally true.
//
// Use Arch::prefered_constant_net_type to determine what
// constant net should be used for unused pins.
uint32_t vcc_pins = check_wires(bel_to_cell_pin_remaps, lut_bels, used_pins);
#if defined(DEBUG_LUT_ROTATION)
log_info("vcc_pins = 0x%x", vcc_pins);
for (size_t cell_idx = 0; cell_idx < cells.size(); ++cell_idx) {
CellInfo *cell = cells[cell_idx];
log(", %s => %s", ctx->nameOfBel(cell->bel), cell->name.c_str(ctx));
}
log("\n");
#endif
for (size_t cell_idx = 0; cell_idx < cells.size(); ++cell_idx) {
CellInfo *cell = cells[cell_idx];
auto &lut_bel = *lut_bels[cell_idx];
cell->lut_cell.vcc_pins.clear();
for (size_t bel_pin_idx = 0; bel_pin_idx < lut_bel.pins.size(); ++bel_pin_idx) {
if ((vcc_pins & (1 << bel_pin_idx)) != 0) {
NPNR_ASSERT(bel_to_cell_pin_remaps[cell_idx][bel_pin_idx] == -1);
auto pin = lut_bel.pins.at(bel_pin_idx);
cell->lut_cell.vcc_pins.emplace(pin);
}
}
}
}

View File

@ -88,6 +88,8 @@ struct LutMapper
std::vector<CellInfo *> cells;
bool remap_luts(const Context *ctx);
uint32_t check_wires(const std::vector<std::vector<int32_t>> &bel_to_cell_pin_remaps,
const std::vector<const LutBel *> &lut_bels, uint32_t used_pins) const;
};
// Rotate and merge a LUT equation into an array of levels.

View File

@ -59,6 +59,10 @@ bool SiteArch::bindPip(const SitePip &pip, SiteNetInfo *net)
result.first->second.count += 1;
}
if (debug()) {
log_info("Bound pip %s to wire %s\n", nameOfPip(pip), nameOfWire(dst));
}
return true;
}
@ -67,6 +71,10 @@ void SiteArch::unbindPip(const SitePip &pip)
SiteWire src = getPipSrcWire(pip);
SiteWire dst = getPipDstWire(pip);
if (debug()) {
log_info("Unbinding pip %s from wire %s\n", nameOfPip(pip), nameOfWire(dst));
}
SiteNetInfo *src_net = unbindWire(src);
SiteNetInfo *dst_net = unbindWire(dst);
NPNR_ASSERT(src_net == dst_net);
@ -125,6 +133,7 @@ SiteArch::SiteArch(const SiteInformation *site_info) : ctx(site_info->ctx), site
// Create list of out of site sources and sinks.
bool have_vcc_pins = false;
for (CellInfo *cell : site_info->cells_in_site) {
for (const auto &pin_pair : cell->cell_bel_pins) {
const PortInfo &port = cell->ports.at(pin_pair.first);
@ -132,6 +141,10 @@ SiteArch::SiteArch(const SiteInformation *site_info) : ctx(site_info->ctx), site
nets.emplace(port.net, SiteNetInfo{port.net});
}
}
if (!cell->lut_cell.vcc_pins.empty()) {
have_vcc_pins = true;
}
}
for (auto &net_pair : nets) {
@ -222,6 +235,27 @@ SiteArch::SiteArch(const SiteInformation *site_info) : ctx(site_info->ctx), site
}
}
IdString vcc_net_name(ctx->chip_info->constants->vcc_net_name);
NetInfo *vcc_net = ctx->nets.at(vcc_net_name).get();
auto iter = nets.find(vcc_net);
if (iter == nets.end() && have_vcc_pins) {
// VCC net isn't present, add it.
SiteNetInfo net_info;
net_info.net = vcc_net;
net_info.driver.type = SiteWire::OUT_OF_SITE_SOURCE;
net_info.driver.net = vcc_net;
auto result = nets.emplace(vcc_net, net_info);
NPNR_ASSERT(result.second);
iter = result.first;
}
for (CellInfo *cell : site_info->cells_in_site) {
for (IdString vcc_pin : cell->lut_cell.vcc_pins) {
SiteWire wire = getBelPinWire(cell->bel, vcc_pin);
iter->second.users.emplace(wire);
}
}
for (auto &net_pair : nets) {
SiteNetInfo *net_info = &net_pair.second;
auto result = wire_to_nets.emplace(net_info->driver, SiteNetMap{net_info, 1});
@ -254,10 +288,16 @@ const char *SiteArch::nameOfWire(const SiteWire &wire) const
return ctx->nameOfWire(wire.wire);
case SiteWire::SITE_PORT_SOURCE:
return ctx->nameOfWire(wire.wire);
case SiteWire::OUT_OF_SITE_SOURCE:
return "out of site source, implement me!";
case SiteWire::OUT_OF_SITE_SINK:
return "out of site sink, implement me!";
case SiteWire::OUT_OF_SITE_SOURCE: {
std::string &str = ctx->log_strs.next();
str = stringf("Out of site source for net %s", wire.net->name.c_str(ctx));
return str.c_str();
}
case SiteWire::OUT_OF_SITE_SINK: {
std::string &str = ctx->log_strs.next();
str = stringf("Out of sink source for net %s", wire.net->name.c_str(ctx));
return str.c_str();
}
default:
// Unreachable!
NPNR_ASSERT(false);
@ -271,12 +311,24 @@ const char *SiteArch::nameOfPip(const SitePip &pip) const
return ctx->nameOfPip(pip.pip);
case SitePip::SITE_PORT:
return ctx->nameOfPip(pip.pip);
case SitePip::SOURCE_TO_SITE_PORT:
return "source to site port, implement me!";
case SitePip::SITE_PORT_TO_SINK:
return "site port to sink, implement me!";
case SitePip::SITE_PORT_TO_SITE_PORT:
return "site port to site port, implement me!";
case SitePip::SOURCE_TO_SITE_PORT: {
std::string &str = ctx->log_strs.next();
str = stringf("Out of site source for net %s => %s", pip.wire.net->name.c_str(ctx),
ctx->nameOfWire(ctx->getPipSrcWire(pip.pip)));
return str.c_str();
}
case SitePip::SITE_PORT_TO_SINK: {
std::string &str = ctx->log_strs.next();
str = stringf("%s => Out of site sink for net %s", ctx->nameOfWire(ctx->getPipDstWire(pip.pip)),
pip.wire.net->name.c_str(ctx));
return str.c_str();
}
case SitePip::SITE_PORT_TO_SITE_PORT: {
std::string &str = ctx->log_strs.next();
str = stringf("%s => %s", ctx->nameOfWire(ctx->getPipSrcWire(pip.pip)),
ctx->nameOfWire(ctx->getPipDstWire(pip.other_pip)));
return str.c_str();
}
default:
// Unreachable!
NPNR_ASSERT(false);

View File

@ -25,6 +25,7 @@
#include <unordered_set>
#include <vector>
#include "PhysicalNetlist.capnp.h"
#include "arch_iterators.h"
#include "chipdb.h"
#include "hash_table.h"
@ -295,6 +296,11 @@ struct SiteArch
// Can this site pip optional invert its signal?
inline bool canInvert(const SitePip &site_pip) const NPNR_ALWAYS_INLINE;
// For a site port, returns the preferred constant net type.
//
// If no preference, then NetType is SIGNAL.
inline PhysicalNetlist::PhysNetlist::NetType prefered_constant_net_type(const SitePip &site_pip) const;
inline SitePipDownhillRange getPipsDownhill(const SiteWire &site_wire) const NPNR_ALWAYS_INLINE;
inline SitePipUphillRange getPipsUphill(const SiteWire &site_wire) const NPNR_ALWAYS_INLINE;
SiteWireRange getWires() const;

View File

@ -295,6 +295,25 @@ inline bool SiteArch::canInvert(const SitePip &site_pip) const
return bel_data.non_inverting_pin == pip_data.extra_data && bel_data.inverting_pin == pip_data.extra_data;
}
inline PhysicalNetlist::PhysNetlist::NetType SiteArch::prefered_constant_net_type(const SitePip &site_pip) const
{
// FIXME: Implement site port overrides from chipdb once available.
IdString prefered_constant_net(ctx->chip_info->constants->best_constant_net);
IdString gnd_net_name(ctx->chip_info->constants->gnd_net_name);
IdString vcc_net_name(ctx->chip_info->constants->vcc_net_name);
if (prefered_constant_net == IdString()) {
return PhysicalNetlist::PhysNetlist::NetType::SIGNAL;
} else if (prefered_constant_net == gnd_net_name) {
return PhysicalNetlist::PhysNetlist::NetType::GND;
} else if (prefered_constant_net == vcc_net_name) {
return PhysicalNetlist::PhysNetlist::NetType::VCC;
} else {
log_error("prefered_constant_net %s is not the GND (%s) or VCC(%s) net?\n", prefered_constant_net.c_str(ctx),
gnd_net_name.c_str(ctx), vcc_net_name.c_str(ctx));
}
}
NEXTPNR_NAMESPACE_END
#endif /* SITE_ARCH_H */

View File

@ -19,12 +19,12 @@
#include "nextpnr.h"
#include "design_utils.h"
#include "dynamic_bitarray.h"
#include "hash_table.h"
#include "log.h"
#include "site_routing_cache.h"
#include "hash_table.h"
#include "site_arch.h"
#include "site_arch.impl.h"
@ -98,15 +98,15 @@ bool check_initial_wires(const Context *ctx, SiteInformation *site_info)
static bool is_invalid_site_port(const SiteArch *ctx, const SiteNetInfo *net, const SitePip &pip)
{
SyntheticType type = ctx->pip_synthetic_type(pip);
PhysicalNetlist::PhysNetlist::NetType net_type = ctx->ctx->get_net_type(net->net);
bool is_invalid = false;
if (type == SYNTH_GND) {
IdString gnd_net_name(ctx->ctx->chip_info->constants->gnd_net_name);
return net->net->name != gnd_net_name;
is_invalid = net_type != PhysicalNetlist::PhysNetlist::NetType::GND;
} else if (type == SYNTH_VCC) {
IdString vcc_net_name(ctx->ctx->chip_info->constants->vcc_net_name);
return net->net->name != vcc_net_name;
} else {
return false;
is_invalid = net_type != PhysicalNetlist::PhysNetlist::NetType::VCC;
}
return is_invalid;
}
struct SiteExpansionLoop
@ -328,7 +328,7 @@ void print_current_state(const SiteArch *site_arch)
log_info(" Cells in site:\n");
for (CellInfo *cell : cells_in_site) {
log_info(" - %s (%s)\n", cell->name.c_str(ctx), cell->type.c_str(ctx));
log_info(" - %s (%s) => %s\n", cell->name.c_str(ctx), cell->type.c_str(ctx), ctx->nameOfBel(cell->bel));
}
log_info(" Nets in site:\n");
@ -368,6 +368,7 @@ struct PossibleSolutions
std::vector<SitePip>::const_iterator pips_end;
bool inverted = false;
bool can_invert = false;
PhysicalNetlist::PhysNetlist::NetType prefered_constant_net_type = PhysicalNetlist::PhysNetlist::NetType::SIGNAL;
};
bool test_solution(SiteArch *ctx, SiteNetInfo *net, std::vector<SitePip>::const_iterator pips_begin,
@ -375,12 +376,16 @@ bool test_solution(SiteArch *ctx, SiteNetInfo *net, std::vector<SitePip>::const_
{
bool valid = true;
std::vector<SitePip>::const_iterator good_pip_end = pips_begin;
for (auto iter = pips_begin; iter != pips_end; ++iter) {
if (!ctx->bindPip(*iter, net)) {
std::vector<SitePip>::const_iterator iter = pips_begin;
SitePip pip;
while (iter != pips_end) {
pip = *iter;
if (!ctx->bindPip(pip, net)) {
valid = false;
break;
}
++iter;
good_pip_end = iter;
}
@ -390,7 +395,7 @@ bool test_solution(SiteArch *ctx, SiteNetInfo *net, std::vector<SitePip>::const_
ctx->unbindPip(*iter);
}
} else {
NPNR_ASSERT(net->driver == ctx->getPipSrcWire(*good_pip_end));
NPNR_ASSERT(net->driver == ctx->getPipSrcWire(pip));
}
return valid;
@ -404,8 +409,92 @@ void remove_solution(SiteArch *ctx, std::vector<SitePip>::const_iterator pips_be
}
}
struct SolutionPreference
{
const SiteArch *ctx;
const std::vector<PossibleSolutions> &solutions;
SolutionPreference(const SiteArch *ctx, const std::vector<PossibleSolutions> &solutions)
: ctx(ctx), solutions(solutions)
{
}
bool non_inverting_preference(const PossibleSolutions &lhs, const PossibleSolutions &rhs) const
{
// If the LHS is non-inverting and the RHS is inverting, then put the
// LHS first.
if (!lhs.inverted && rhs.inverted) {
return true;
}
// Better to have a path that can invert over a path that has no
// option to invert.
return (!lhs.can_invert) < (!rhs.can_invert);
}
bool inverting_preference(const PossibleSolutions &lhs, const PossibleSolutions &rhs) const
{
// If the LHS is inverting and the RHS is non-inverting, then put the
// LHS first (because this is the inverting preferred case).
if (lhs.inverted && !rhs.inverted) {
return true;
}
// Better to have a path that can invert over a path that has no
// option to invert.
return (!lhs.can_invert) < (!rhs.can_invert);
}
bool operator()(size_t lhs_solution_idx, size_t rhs_solution_idx) const
{
const PossibleSolutions &lhs = solutions.at(lhs_solution_idx);
const PossibleSolutions &rhs = solutions.at(rhs_solution_idx);
NPNR_ASSERT(lhs.net == rhs.net);
PhysicalNetlist::PhysNetlist::NetType net_type = ctx->ctx->get_net_type(lhs.net->net);
if (net_type == PhysicalNetlist::PhysNetlist::NetType::SIGNAL) {
return non_inverting_preference(lhs, rhs);
}
// All GND/VCC nets use out of site sources. Local constant sources
// are still connected via synthetic edges to the global GND/VCC
// network.
NPNR_ASSERT(lhs.net->driver.type == SiteWire::OUT_OF_SITE_SOURCE);
bool lhs_match_preference = net_type == lhs.prefered_constant_net_type;
bool rhs_match_preference = net_type == rhs.prefered_constant_net_type;
if (lhs_match_preference && !rhs_match_preference) {
// Prefer solutions where the net type already matches the
// prefered constant type.
return true;
}
if (!lhs_match_preference && rhs_match_preference) {
// Prefer solutions where the net type already matches the
// prefered constant type. In this case the RHS is better, which
// means that RHS < LHS, hence false here.
return false;
}
NPNR_ASSERT(lhs_match_preference == rhs_match_preference);
if (!lhs_match_preference) {
// If the net type does not match the preference, then prefer
// inverted solutions.
return inverting_preference(lhs, rhs);
} else {
// If the net type does match the preference, then prefer
// non-inverted solutions.
return non_inverting_preference(lhs, rhs);
}
}
};
static bool find_solution_via_backtrack(SiteArch *ctx, std::vector<PossibleSolutions> *solutions,
const std::vector<std::vector<size_t>> &sinks_to_solutions)
std::vector<std::vector<size_t>> sinks_to_solutions,
const std::vector<SiteWire> &sinks, bool explain)
{
std::vector<uint8_t> routed_sinks;
std::vector<size_t> solution_indicies;
@ -414,14 +503,43 @@ static bool find_solution_via_backtrack(SiteArch *ctx, std::vector<PossibleSolut
solution_indicies.resize(sinks_to_solutions.size(), 0);
// Scan solutions, and remove any solutions that are invalid immediately
for (auto &solution : *solutions) {
for (size_t solution_idx = 0; solution_idx < solutions->size(); ++solution_idx) {
PossibleSolutions &solution = (*solutions)[solution_idx];
if (verbose_site_router(ctx) || explain) {
log_info("Testing solution %zu\n", solution_idx);
}
if (test_solution(ctx, solution.net, solution.pips_begin, solution.pips_end)) {
if (verbose_site_router(ctx) || explain) {
log_info("Solution %zu is good\n", solution_idx);
}
remove_solution(ctx, solution.pips_begin, solution.pips_end);
} else {
if (verbose_site_router(ctx) || explain) {
log_info("Solution %zu is not useable\n", solution_idx);
}
solution.tested = true;
}
}
// Sort sinks_to_solutions so that preferred solutions are tested earlier
// than less preferred solutions.
for (size_t sink_idx = 0; sink_idx < sinks_to_solutions.size(); ++sink_idx) {
std::vector<size_t> &solutions_for_sink = sinks_to_solutions.at(sink_idx);
std::stable_sort(solutions_for_sink.begin(), solutions_for_sink.end(), SolutionPreference(ctx, *solutions));
if (verbose_site_router(ctx) || explain) {
log_info("Solutions for sink %s (%zu)\n", ctx->nameOfWire(sinks.at(sink_idx)), sink_idx);
for (size_t solution_idx : solutions_for_sink) {
const PossibleSolutions &solution = solutions->at(solution_idx);
log_info("%zu: inverted = %d, can_invert = %d, tested = %d\n", solution_idx, solution.inverted,
solution.can_invert, solution.tested);
for (auto iter = solution.pips_begin; iter != solution.pips_end; ++iter) {
log_info(" - %s\n", ctx->nameOfPip(*iter));
}
}
}
}
for (size_t sink_idx = 0; sink_idx < sinks_to_solutions.size(); ++sink_idx) {
size_t solution_count = 0;
for (size_t solution_idx : sinks_to_solutions[sink_idx]) {
@ -431,6 +549,9 @@ static bool find_solution_via_backtrack(SiteArch *ctx, std::vector<PossibleSolut
}
if (solution_count == 0) {
if (verbose_site_router(ctx) || explain) {
log_info("Sink %s has no solution in site\n", ctx->nameOfWire(sinks.at(sink_idx)));
}
return false;
}
@ -466,11 +587,14 @@ static bool find_solution_via_backtrack(SiteArch *ctx, std::vector<PossibleSolut
size_t sink_idx = solution_order[solution_stack.size()].first;
size_t next_solution_to_test = solution_indicies[sink_idx];
if (verbose_site_router(ctx) || explain) {
log_info("next %zu : %zu (of %zu)\n", sink_idx, next_solution_to_test, sinks_to_solutions[sink_idx].size());
}
if (next_solution_to_test >= sinks_to_solutions[sink_idx].size()) {
// We have exausted all solutions at this level of the stack!
if (solution_stack.empty()) {
// Search is done, failed!!!
if (verbose_site_router(ctx)) {
if (verbose_site_router(ctx) || explain) {
log_info("No solution found via backtrace with %zu solutions and %zu sinks\n", solutions->size(),
sinks_to_solutions.size());
}
@ -478,7 +602,11 @@ static bool find_solution_via_backtrack(SiteArch *ctx, std::vector<PossibleSolut
} else {
// This level of the stack is completely tapped out, pop back
// to the next level up.
size_t sink_idx = solution_order[solution_stack.size() - 1].first;
size_t solution_idx = solution_stack.back();
if (verbose_site_router(ctx) || explain) {
log_info("pop %zu : %zu\n", sink_idx, solution_idx);
}
solution_stack.pop_back();
// Remove the now tested bad solution at the previous level of
@ -488,7 +616,6 @@ static bool find_solution_via_backtrack(SiteArch *ctx, std::vector<PossibleSolut
// Because we had to pop up the stack, advance the index at
// the level below us and start again.
sink_idx = solution_order[solution_stack.size()].first;
solution_indicies[sink_idx] += 1;
continue;
}
@ -498,16 +625,26 @@ static bool find_solution_via_backtrack(SiteArch *ctx, std::vector<PossibleSolut
auto &solution = solutions->at(solution_idx);
if (solution.tested) {
// This solution was already determined to be no good, skip it.
if (verbose_site_router(ctx) || explain) {
log_info("skip %zu : %zu\n", sink_idx, solution_idx);
}
solution_indicies[sink_idx] += 1;
continue;
}
if (verbose_site_router(ctx) || explain) {
log_info("test %zu : %zu\n", sink_idx, solution_idx);
}
if (!test_solution(ctx, solution.net, solution.pips_begin, solution.pips_end)) {
// This solution was no good, try the next one at this level of
// the stack.
solution_indicies[sink_idx] += 1;
} else {
// This solution was good, push onto the stack.
if (verbose_site_router(ctx) || explain) {
log_info("push %zu : %zu\n", sink_idx, solution_idx);
}
solution_stack.push_back(solution_idx);
if (solution_stack.size() == sinks_to_solutions.size()) {
// Found a valid solution, done!
@ -529,7 +666,7 @@ static bool find_solution_via_backtrack(SiteArch *ctx, std::vector<PossibleSolut
NPNR_ASSERT(false);
}
bool route_site(SiteArch *ctx, SiteRoutingCache *site_routing_cache, RouteNodeStorage *node_storage)
bool route_site(SiteArch *ctx, SiteRoutingCache *site_routing_cache, RouteNodeStorage *node_storage, bool explain)
{
std::vector<SiteExpansionLoop *> expansions;
expansions.reserve(ctx->nets.size());
@ -544,7 +681,7 @@ bool route_site(SiteArch *ctx, SiteRoutingCache *site_routing_cache, RouteNodeSt
SiteExpansionLoop *router = expansions.back();
if (!router->expand_net(ctx, site_routing_cache, net)) {
if (verbose_site_router(ctx)) {
if (verbose_site_router(ctx) || explain) {
log_info("Net %s expansion failed to reach all users, site is unroutable!\n", ctx->nameOfNet(net));
}
@ -554,12 +691,14 @@ bool route_site(SiteArch *ctx, SiteRoutingCache *site_routing_cache, RouteNodeSt
// First convert remaining solutions into a flat solution set.
std::vector<PossibleSolutions> solutions;
std::vector<SiteWire> sinks;
HashTables::HashMap<SiteWire, size_t> sink_map;
std::vector<std::vector<size_t>> sinks_to_solutions;
for (const auto *expansion : expansions) {
for (const SiteWire &unrouted_sink : expansion->net_users) {
auto result = sink_map.emplace(unrouted_sink, sink_map.size());
NPNR_ASSERT(result.second);
sinks.push_back(unrouted_sink);
}
}
@ -572,12 +711,6 @@ bool route_site(SiteArch *ctx, SiteRoutingCache *site_routing_cache, RouteNodeSt
for (const auto *expansion : expansions) {
for (size_t idx = 0; idx < expansion->num_solutions(); ++idx) {
if (expansion->solution_inverted(idx)) {
// FIXME: May prefer an inverted solution if constant net
// type.
continue;
}
SiteWire wire = expansion->solution_sink(idx);
auto begin = expansion->solution_begin(idx);
auto end = expansion->solution_end(idx);
@ -595,15 +728,22 @@ bool route_site(SiteArch *ctx, SiteRoutingCache *site_routing_cache, RouteNodeSt
solution.can_invert = expansion->solution_can_invert(idx);
for (auto iter = begin; iter != end; ++iter) {
NPNR_ASSERT(ctx->getPipDstWire(*iter) == wire);
wire = ctx->getPipSrcWire(*iter);
const SitePip &site_pip = *iter;
NPNR_ASSERT(ctx->getPipDstWire(site_pip) == wire);
wire = ctx->getPipSrcWire(site_pip);
// If there is a input site port, mark on the solution what the
// prefered constant net type is for this site port.
if (site_pip.type == SitePip::SITE_PORT && wire.type == SiteWire::SITE_PORT_SOURCE) {
solution.prefered_constant_net_type = ctx->prefered_constant_net_type(site_pip);
}
}
NPNR_ASSERT(expansion->net_driver == wire);
}
}
return find_solution_via_backtrack(ctx, &solutions, sinks_to_solutions);
return find_solution_via_backtrack(ctx, &solutions, sinks_to_solutions, sinks, explain);
}
void check_routing(const SiteArch &site_arch)
@ -631,26 +771,192 @@ void check_routing(const SiteArch &site_arch)
}
}
void apply_routing(Context *ctx, const SiteArch &site_arch)
static void apply_simple_routing(Context *ctx, const SiteArch &site_arch, NetInfo *net, const SiteNetInfo *site_net,
const SiteWire &user)
{
for (auto &net_pair : site_arch.nets) {
NetInfo *net = net_pair.first;
SiteWire wire = user;
while (wire != site_net->driver) {
SitePip site_pip = site_net->wires.at(wire).pip;
NPNR_ASSERT(site_arch.getPipDstWire(site_pip) == wire);
// If the driver wire is a site wire, bind it.
if (net_pair.second.driver.type == SiteWire::SITE_WIRE) {
WireId driver_wire = net_pair.second.driver.wire;
if (ctx->getBoundWireNet(driver_wire) != net) {
ctx->bindWire(driver_wire, net, STRENGTH_PLACER);
if (site_pip.type == SitePip::SITE_PIP || site_pip.type == SitePip::SITE_PORT) {
NetInfo *bound_net = ctx->getBoundPipNet(site_pip.pip);
if (bound_net == nullptr) {
ctx->bindPip(site_pip.pip, net, STRENGTH_PLACER);
} else {
NPNR_ASSERT(bound_net == net);
}
}
for (auto &wire_pair : net_pair.second.wires) {
const SitePip &site_pip = wire_pair.second.pip;
if (site_pip.type != SitePip::SITE_PIP && site_pip.type != SitePip::SITE_PORT) {
continue;
wire = site_arch.getPipSrcWire(site_pip);
}
}
static void apply_constant_routing(Context *ctx, const SiteArch &site_arch, NetInfo *net, const SiteNetInfo *site_net)
{
IdString gnd_net_name(ctx->chip_info->constants->gnd_net_name);
NetInfo *gnd_net = ctx->nets.at(gnd_net_name).get();
IdString vcc_net_name(ctx->chip_info->constants->vcc_net_name);
NetInfo *vcc_net = ctx->nets.at(vcc_net_name).get();
// This function is designed to operate only on the gnd or vcc net, and
// assumes that the GND and VCC nets have been unified.
NPNR_ASSERT(net == vcc_net || net == gnd_net);
for (auto &user : site_net->users) {
// FIXME: Handle case where pip is "can_invert", and that
// inversion helps with accomidating "best constant".
bool is_path_inverting = false;
SiteWire wire = user;
PipId inverting_pip;
while (wire != site_net->driver) {
SitePip pip = site_net->wires.at(wire).pip;
NPNR_ASSERT(site_arch.getPipDstWire(pip) == wire);
if (site_arch.isInverting(pip)) {
// FIXME: Should be able to handle the general case of
// multiple inverters, but that is harder (and annoying). Also
// most sites won't allow for a double inversion, so just
// disallow for now.
NPNR_ASSERT(!is_path_inverting);
is_path_inverting = true;
NPNR_ASSERT(pip.type == SitePip::SITE_PIP);
inverting_pip = pip.pip;
}
ctx->bindPip(site_pip.pip, net, STRENGTH_PLACER);
wire = site_arch.getPipSrcWire(pip);
}
if (!is_path_inverting) {
// This routing is boring, use base logic.
apply_simple_routing(ctx, site_arch, net, site_net, user);
continue;
}
NPNR_ASSERT(inverting_pip != PipId());
// This net is going to become two nets.
// The portion of the net prior to the inverter is going to be bound
// to the opposite net. For example, if the original net was gnd_net,
// the portion prior to the inverter will not be the vcc_net.
//
// A new cell will be generated to sink the connection from the
// opposite net.
NetInfo *net_before_inverter;
if (net == gnd_net) {
net_before_inverter = vcc_net;
} else {
NPNR_ASSERT(net == vcc_net);
net_before_inverter = gnd_net;
}
// First find a name for the new cell
int count = 0;
CellInfo *new_cell = nullptr;
while (true) {
std::string new_cell_name = stringf("%s_%s.%d", net->name.c_str(ctx), site_arch.nameOfWire(user), count);
IdString new_cell_id = ctx->id(new_cell_name);
if (ctx->cells.count(new_cell_id)) {
count += 1;
} else {
new_cell = ctx->createCell(new_cell_id, ctx->id("$nextpnr_inv"));
break;
}
}
auto &tile_type = loc_info(ctx->chip_info, inverting_pip);
auto &pip_data = tile_type.pip_data[inverting_pip.index];
NPNR_ASSERT(pip_data.site != -1);
auto &bel_data = tile_type.bel_data[pip_data.bel];
BelId inverting_bel;
inverting_bel.tile = inverting_pip.tile;
inverting_bel.index = pip_data.bel;
IdString in_port(bel_data.ports[pip_data.extra_data]);
NPNR_ASSERT(bel_data.types[pip_data.extra_data] == PORT_IN);
IdString id_I = ctx->id("I");
new_cell->addInput(id_I);
new_cell->cell_bel_pins[id_I].push_back(in_port);
new_cell->bel = inverting_bel;
new_cell->belStrength = STRENGTH_PLACER;
ctx->tileStatus.at(inverting_bel.tile).boundcells[inverting_bel.index] = new_cell;
connect_port(ctx, net_before_inverter, new_cell, id_I);
// The original BEL pin is now routed, but only through the inverter.
// Because the cell/net model doesn't allow for multiple source pins
// and the fact that the portion of the net after the inverter is
// currently routed, all BEL pins on this site wire are going to be
// masked from the router.
NPNR_ASSERT(user.type == SiteWire::SITE_WIRE);
ctx->mask_bel_pins_on_site_wire(net, user.wire);
// Bind wires and pips to the two nets.
bool after_inverter = true;
wire = user;
while (wire != site_net->driver) {
SitePip site_pip = site_net->wires.at(wire).pip;
NPNR_ASSERT(site_arch.getPipDstWire(site_pip) == wire);
if (site_arch.isInverting(site_pip)) {
NPNR_ASSERT(after_inverter);
after_inverter = false;
// Because this wire is just after the inverter, bind it to
// the net without the pip, as this is a "source".
NPNR_ASSERT(wire.type == SiteWire::SITE_WIRE);
ctx->bindWire(wire.wire, net, STRENGTH_PLACER);
} else {
if (site_pip.type == SitePip::SITE_PIP || site_pip.type == SitePip::SITE_PORT) {
if (after_inverter) {
ctx->bindPip(site_pip.pip, net, STRENGTH_PLACER);
} else {
ctx->bindPip(site_pip.pip, net_before_inverter, STRENGTH_PLACER);
}
}
}
wire = site_arch.getPipSrcWire(site_pip);
}
}
}
static void apply_routing(Context *ctx, const SiteArch &site_arch)
{
IdString gnd_net_name(ctx->chip_info->constants->gnd_net_name);
NetInfo *gnd_net = ctx->nets.at(gnd_net_name).get();
IdString vcc_net_name(ctx->chip_info->constants->vcc_net_name);
NetInfo *vcc_net = ctx->nets.at(vcc_net_name).get();
for (auto &net_pair : site_arch.nets) {
NetInfo *net = net_pair.first;
const SiteNetInfo *site_net = &net_pair.second;
if (net == gnd_net || net == vcc_net) {
apply_constant_routing(ctx, site_arch, net, site_net);
} else {
// If the driver wire is a site wire, bind it.
if (site_net->driver.type == SiteWire::SITE_WIRE) {
WireId driver_wire = site_net->driver.wire;
if (ctx->getBoundWireNet(driver_wire) != net) {
ctx->bindWire(driver_wire, net, STRENGTH_PLACER);
}
}
for (auto &wire_pair : site_net->wires) {
const SitePip &site_pip = wire_pair.second.pip;
if (site_pip.type != SitePip::SITE_PIP && site_pip.type != SitePip::SITE_PORT) {
continue;
}
ctx->bindPip(site_pip.pip, net, STRENGTH_PLACER);
}
}
}
}
@ -699,12 +1005,6 @@ bool SiteRouter::checkSiteRouting(const Context *ctx, const TileStatus &tile_sta
}
}
// FIXME: Populate "consumed_wires" with all VCC/GND tied in the site.
// This will allow route_site to leverage site local constant sources.
//
// FIXME: Handle case where a constant is requested, but use of an
// inverter is possible. This is the place to handle "bestConstant"
// (e.g. route VCC's over GND's, etc).
auto tile_type_idx = ctx->chip_info->tiles[tile].type;
const std::vector<LutElement> &lut_elements = ctx->lut_elements.at(tile_type_idx);
std::vector<LutMapper> lut_mappers;
@ -747,7 +1047,7 @@ bool SiteRouter::checkSiteRouting(const Context *ctx, const TileStatus &tile_sta
SiteArch site_arch(&site_info);
// site_arch.archcheck();
site_ok = route_site(&site_arch, &ctx->site_routing_cache, &ctx->node_storage);
site_ok = route_site(&site_arch, &ctx->site_routing_cache, &ctx->node_storage, /*explain=*/false);
if (verbose_site_router(ctx)) {
if (site_ok) {
log_info("Site %s is routable\n", ctx->get_site_name(tile, site));
@ -799,7 +1099,7 @@ void SiteRouter::bindSiteRouting(Context *ctx)
SiteInformation site_info(ctx, tile, site, cells_in_site);
SiteArch site_arch(&site_info);
NPNR_ASSERT(route_site(&site_arch, &ctx->site_routing_cache, &ctx->node_storage));
NPNR_ASSERT(route_site(&site_arch, &ctx->site_routing_cache, &ctx->node_storage, /*explain=*/false));
check_routing(site_arch);
apply_routing(ctx, site_arch);
if (verbose_site_router(ctx)) {
@ -807,6 +1107,27 @@ void SiteRouter::bindSiteRouting(Context *ctx)
}
}
void SiteRouter::explain(const Context *ctx) const
{
NPNR_ASSERT(!dirty);
if (site_ok) {
return;
}
// Make sure all cells in this site belong!
auto iter = cells_in_site.begin();
NPNR_ASSERT((*iter)->bel != BelId());
auto tile = (*iter)->bel.tile;
SiteInformation site_info(ctx, tile, site, cells_in_site);
SiteArch site_arch(&site_info);
bool route_status = route_site(&site_arch, &ctx->site_routing_cache, &ctx->node_storage, /*explain=*/true);
if (!route_status) {
print_current_state(&site_arch);
}
}
ArchNetInfo::~ArchNetInfo() { delete loop; }
Arch::~Arch()

View File

@ -47,6 +47,7 @@ struct SiteRouter
void unbindBel(CellInfo *cell);
bool checkSiteRouting(const Context *ctx, const TileStatus &tile_status) const;
void bindSiteRouting(Context *ctx);
void explain(const Context *ctx) const;
};
NEXTPNR_NAMESPACE_END