Merge pull request #645 from litghost/add_counter_and_ram
FPGA interchange: Add counter and ram tests
This commit is contained in:
commit
692d7dc26d
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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());
|
||||
|
@ -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;
|
||||
|
@ -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 .
|
||||
```
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
10
fpga_interchange/examples/tests/ram/CMakeLists.txt
Normal file
10
fpga_interchange/examples/tests/ram/CMakeLists.txt
Normal 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
|
||||
)
|
||||
|
41
fpga_interchange/examples/tests/ram/basys3.pcf
Normal file
41
fpga_interchange/examples/tests/ram/basys3.pcf
Normal 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
|
80
fpga_interchange/examples/tests/ram/basys3.xdc
Normal file
80
fpga_interchange/examples/tests/ram/basys3.xdc
Normal 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]]
|
134
fpga_interchange/examples/tests/ram/ram.v
Normal file
134
fpga_interchange/examples/tests/ram/ram.v
Normal 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
|
17
fpga_interchange/examples/tests/ram/run.tcl
Normal file
17
fpga_interchange/examples/tests/ram/run.tcl
Normal 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)
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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 */
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user