1126 lines
40 KiB
C++
1126 lines
40 KiB
C++
/*
|
|
* nextpnr -- Next Generation Place and Route
|
|
*
|
|
* Copyright (C) 2021 Symbiflow Authors
|
|
*
|
|
*
|
|
* Permission to use, copy, modify, and/or distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*
|
|
*/
|
|
|
|
#include "fpga_interchange.h"
|
|
#include <capnp/message.h>
|
|
#include <sstream>
|
|
#include <capnp/serialize.h>
|
|
#include <kj/std/iostream.h>
|
|
#include "PhysicalNetlist.capnp.h"
|
|
#include "LogicalNetlist.capnp.h"
|
|
#include "zlib.h"
|
|
#include "frontend_base.h"
|
|
|
|
NEXTPNR_NAMESPACE_BEGIN
|
|
|
|
static void write_message(::capnp::MallocMessageBuilder & message, const std::string &filename) {
|
|
kj::Array<capnp::word> words = messageToFlatArray(message);
|
|
kj::ArrayPtr<kj::byte> bytes = words.asBytes();
|
|
|
|
gzFile file = gzopen(filename.c_str(), "w");
|
|
NPNR_ASSERT(file != Z_NULL);
|
|
|
|
NPNR_ASSERT(gzwrite(file, &bytes[0], bytes.size()) == (int)bytes.size());
|
|
NPNR_ASSERT(gzclose(file) == Z_OK);
|
|
}
|
|
|
|
struct StringEnumerator {
|
|
std::vector<std::string> strings;
|
|
std::unordered_map<std::string, size_t> string_to_index;
|
|
|
|
size_t get_index(const std::string &s) {
|
|
auto result = string_to_index.emplace(s, strings.size());
|
|
if(result.second) {
|
|
// This string was inserted, append.
|
|
strings.push_back(s);
|
|
}
|
|
|
|
return result.first->second;
|
|
}
|
|
};
|
|
|
|
static PhysicalNetlist::PhysNetlist::RouteBranch::Builder emit_branch(
|
|
const Context * ctx,
|
|
StringEnumerator * strings,
|
|
const std::unordered_map<PipId, PlaceStrength> &pip_place_strength,
|
|
PipId pip,
|
|
PhysicalNetlist::PhysNetlist::RouteBranch::Builder branch) {
|
|
if(ctx->is_pip_synthetic(pip)) {
|
|
log_error("FPGA interchange should not emit synthetic pip %s\n", ctx->nameOfPip(pip));
|
|
}
|
|
|
|
const PipInfoPOD & pip_data = pip_info(ctx->chip_info, pip);
|
|
const TileTypeInfoPOD & tile_type = loc_info(ctx->chip_info, pip);
|
|
const TileInstInfoPOD & tile = ctx->chip_info->tiles[pip.tile];
|
|
|
|
if(pip_data.site == -1) {
|
|
// This is a PIP
|
|
auto pip_obj = branch.getRouteSegment().initPip();
|
|
pip_obj.setTile(strings->get_index(tile.name.get()));
|
|
|
|
// FIXME: This might be broken for reverse bi-pips. Re-visit this one.
|
|
//
|
|
// pip_data might need to mark that it is a reversed bi-pip, so the
|
|
// pip emission for the physical netlist would be:
|
|
//
|
|
// wire0: dst_wire
|
|
// wire1: src_wire
|
|
// forward: false
|
|
//
|
|
IdString src_wire_name = IdString(tile_type.wire_data[pip_data.src_index].name);
|
|
IdString dst_wire_name = IdString(tile_type.wire_data[pip_data.dst_index].name);
|
|
pip_obj.setWire0(strings->get_index(src_wire_name.str(ctx)));
|
|
pip_obj.setWire1(strings->get_index(dst_wire_name.str(ctx)));
|
|
pip_obj.setForward(true);
|
|
pip_obj.setIsFixed(pip_place_strength.at(pip) >= STRENGTH_FIXED);
|
|
|
|
return branch;
|
|
} else {
|
|
BelId bel;
|
|
bel.tile = pip.tile;
|
|
bel.index = pip_data.bel;
|
|
|
|
const BelInfoPOD & bel_data = bel_info(ctx->chip_info, bel);
|
|
|
|
IdStringList bel_name = ctx->getBelName(bel);
|
|
NPNR_ASSERT(bel_name.size() == 2);
|
|
std::string site_and_type = bel_name[0].str(ctx);
|
|
auto pos = site_and_type.find_first_of('.');
|
|
NPNR_ASSERT(pos != std::string::npos);
|
|
|
|
std::string site_name = site_and_type.substr(0, pos);
|
|
int site_idx = strings->get_index(site_name);
|
|
|
|
if(bel_data.category == BEL_CATEGORY_LOGIC) {
|
|
// This is a psuedo site-pip.
|
|
auto in_bel_pin = branch.getRouteSegment().initBelPin();
|
|
WireId src_wire = ctx->getPipSrcWire(pip);
|
|
WireId dst_wire = ctx->getPipDstWire(pip);
|
|
|
|
NPNR_ASSERT(src_wire.index == bel_data.wires[pip_data.extra_data]);
|
|
|
|
IdString src_pin(bel_data.ports[pip_data.extra_data]);
|
|
|
|
IdString dst_pin;
|
|
for(IdString pin : ctx->getBelPins(bel)) {
|
|
if(ctx->getBelPinWire(bel, pin) == dst_wire) {
|
|
NPNR_ASSERT(dst_pin == IdString());
|
|
dst_pin = pin;
|
|
}
|
|
}
|
|
|
|
NPNR_ASSERT(src_pin != IdString());
|
|
NPNR_ASSERT(dst_pin != IdString());
|
|
|
|
int bel_idx = strings->get_index(bel_name[1].str(ctx));
|
|
in_bel_pin.setSite(site_idx);
|
|
in_bel_pin.setBel(bel_idx);
|
|
in_bel_pin.setPin(strings->get_index(src_pin.str(ctx)));
|
|
|
|
auto subbranch = branch.initBranches(1);
|
|
auto bel_pin_branch = subbranch[0];
|
|
auto out_bel_pin = bel_pin_branch.getRouteSegment().initBelPin();
|
|
out_bel_pin.setSite(site_idx);
|
|
out_bel_pin.setBel(bel_idx);
|
|
out_bel_pin.setPin(strings->get_index(dst_pin.str(ctx)));
|
|
|
|
return bel_pin_branch;
|
|
} else if(bel_data.category == BEL_CATEGORY_ROUTING) {
|
|
// This is a site-pip.
|
|
IdStringList pip_name = ctx->getPipName(pip);
|
|
|
|
auto site_pip = branch.getRouteSegment().initSitePIP();
|
|
site_pip.setSite(site_idx);
|
|
site_pip.setBel(strings->get_index(pip_name[1].str(ctx)));
|
|
site_pip.setPin(strings->get_index(pip_name[2].str(ctx)));
|
|
site_pip.setIsFixed(pip_place_strength.at(pip) >= STRENGTH_FIXED);
|
|
|
|
// FIXME: Mark inverter state.
|
|
// This is required for US/US+ inverters, because those inverters
|
|
// only have 1 input.
|
|
|
|
return branch;
|
|
} else {
|
|
NPNR_ASSERT(bel_data.category == BEL_CATEGORY_SITE_PORT);
|
|
|
|
// This is a site port.
|
|
const TileWireInfoPOD &tile_wire = tile_type.wire_data[pip_data.src_index];
|
|
|
|
int site_pin_idx = strings->get_index(bel_name[1].str(ctx));
|
|
|
|
if(tile_wire.site == -1) {
|
|
// This site port is routing -> site.
|
|
auto site_pin = branch.getRouteSegment().initSitePin();
|
|
site_pin.setSite(site_idx);
|
|
site_pin.setPin(site_pin_idx);
|
|
|
|
auto subbranch = branch.initBranches(1);
|
|
auto bel_pin_branch = subbranch[0];
|
|
auto bel_pin = bel_pin_branch.getRouteSegment().initBelPin();
|
|
bel_pin.setSite(site_idx);
|
|
bel_pin.setBel(site_pin_idx);
|
|
bel_pin.setPin(site_pin_idx);
|
|
|
|
return bel_pin_branch;
|
|
} else {
|
|
// This site port is site -> routing.
|
|
auto bel_pin = branch.getRouteSegment().initBelPin();
|
|
bel_pin.setSite(site_idx);
|
|
bel_pin.setBel(site_pin_idx);
|
|
bel_pin.setPin(site_pin_idx);
|
|
|
|
auto subbranch = branch.initBranches(1);
|
|
auto site_pin_branch = subbranch[0];
|
|
auto site_pin = site_pin_branch.getRouteSegment().initSitePin();
|
|
site_pin.setSite(site_idx);
|
|
site_pin.setPin(site_pin_idx);
|
|
|
|
return site_pin_branch;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void init_bel_pin(
|
|
const Context * ctx,
|
|
StringEnumerator * strings,
|
|
const BelPin &bel_pin,
|
|
PhysicalNetlist::PhysNetlist::RouteBranch::Builder branch) {
|
|
if(ctx->is_bel_synthetic(bel_pin.bel)) {
|
|
log_error("FPGA interchange should not emit synthetic BEL pin %s/%s\n",
|
|
ctx->nameOfBel(bel_pin.bel), bel_pin.pin.c_str(ctx));
|
|
}
|
|
|
|
BelId bel = bel_pin.bel;
|
|
IdString pin_name = bel_pin.pin;
|
|
|
|
IdStringList bel_name = ctx->getBelName(bel);
|
|
NPNR_ASSERT(bel_name.size() == 2);
|
|
std::string site_and_type = bel_name[0].str(ctx);
|
|
auto pos = site_and_type.find_first_of('.');
|
|
NPNR_ASSERT(pos != std::string::npos);
|
|
|
|
std::string site_name = site_and_type.substr(0, pos);
|
|
|
|
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)));
|
|
} 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);
|
|
}
|
|
}
|
|
|
|
|
|
static void emit_net(
|
|
const Context * ctx,
|
|
StringEnumerator * strings,
|
|
const std::unordered_map<WireId, std::vector<PipId>> &pip_downhill,
|
|
const std::unordered_map<WireId, std::vector<BelPin>> &sinks,
|
|
std::unordered_set<PipId> *pips,
|
|
const std::unordered_map<PipId, PlaceStrength> &pip_place_strength,
|
|
WireId wire, PhysicalNetlist::PhysNetlist::RouteBranch::Builder branch) {
|
|
size_t number_branches = 0;
|
|
|
|
auto downhill_iter = pip_downhill.find(wire);
|
|
if(downhill_iter != pip_downhill.end()) {
|
|
number_branches += downhill_iter->second.size();
|
|
}
|
|
|
|
auto sink_iter = sinks.find(wire);
|
|
if(sink_iter != sinks.end()) {
|
|
number_branches += sink_iter->second.size();
|
|
}
|
|
|
|
size_t branch_index = 0;
|
|
auto branches = branch.initBranches(number_branches);
|
|
|
|
if(downhill_iter != pip_downhill.end()) {
|
|
const std::vector<PipId> & wire_pips = downhill_iter->second;
|
|
for(size_t i = 0; i < wire_pips.size(); ++i) {
|
|
PipId pip = wire_pips.at(i);
|
|
NPNR_ASSERT(pips->erase(pip) == 1);
|
|
PhysicalNetlist::PhysNetlist::RouteBranch::Builder leaf_branch = emit_branch(
|
|
ctx, strings, pip_place_strength, pip, branches[branch_index++]);
|
|
|
|
emit_net(ctx, strings, pip_downhill, sinks, pips,
|
|
pip_place_strength,
|
|
ctx->getPipDstWire(pip), leaf_branch);
|
|
}
|
|
}
|
|
|
|
if(sink_iter != sinks.end()) {
|
|
for(const auto bel_pin : sink_iter->second) {
|
|
auto leaf_branch = branches[branch_index++];
|
|
init_bel_pin(ctx, strings, bel_pin, leaf_branch);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Given a site wire, find the source BEL pin.
|
|
//
|
|
// All site wires should have exactly 1 source BEL pin.
|
|
//
|
|
// FIXME: Consider making sure that wire_data.bel_pins[0] is always the
|
|
// source BEL pin in the BBA generator.
|
|
static BelPin find_source(const Context *ctx, WireId source_wire) {
|
|
const TileTypeInfoPOD & tile_type = loc_info(ctx->chip_info, source_wire);
|
|
const TileWireInfoPOD & wire_data = tile_type.wire_data[source_wire.index];
|
|
|
|
// Make sure this is a site wire, otherwise something odd is happening
|
|
// here.
|
|
if(wire_data.site == -1) {
|
|
return BelPin();
|
|
}
|
|
|
|
BelPin source_bel_pin;
|
|
for(const BelPin & bel_pin : ctx->getWireBelPins(source_wire)) {
|
|
if(ctx->getBelPinType(bel_pin.bel, bel_pin.pin) == PORT_OUT) {
|
|
// Synthetic BEL's (like connection to the VCC/GND network) are
|
|
// ignored here, because synthetic BEL's don't exists outside of
|
|
// the BBA.
|
|
if(ctx->is_bel_synthetic(bel_pin.bel)) {
|
|
continue;
|
|
}
|
|
|
|
NPNR_ASSERT(source_bel_pin.bel == BelId());
|
|
source_bel_pin = bel_pin;
|
|
}
|
|
}
|
|
|
|
NPNR_ASSERT(source_bel_pin.bel != BelId());
|
|
NPNR_ASSERT(source_bel_pin.pin != IdString());
|
|
|
|
return source_bel_pin;
|
|
}
|
|
|
|
// Initial a local signal source (usually VCC/GND).
|
|
static PhysicalNetlist::PhysNetlist::RouteBranch::Builder init_local_source(
|
|
const Context *ctx,
|
|
StringEnumerator * strings,
|
|
PhysicalNetlist::PhysNetlist::RouteBranch::Builder source_branch,
|
|
PipId root,
|
|
const std::unordered_map<PipId, PlaceStrength> &pip_place_strength,
|
|
WireId *root_wire) {
|
|
WireId source_wire = ctx->getPipSrcWire(root);
|
|
BelPin source_bel_pin = find_source(ctx, source_wire);
|
|
if(source_bel_pin.bel != BelId()) {
|
|
// This branch should first emit the BEL pin that is the source, followed
|
|
// by the pip that brings the source to the net.
|
|
init_bel_pin(ctx, strings, source_bel_pin, source_branch);
|
|
|
|
source_branch = source_branch.initBranches(1)[0];
|
|
}
|
|
*root_wire = ctx->getPipDstWire(root);
|
|
return emit_branch(ctx, strings, pip_place_strength, root, source_branch);
|
|
}
|
|
|
|
static void find_non_synthetic_edges(const Context * ctx, WireId root_wire,
|
|
const std::unordered_map<WireId, std::vector<PipId>> &pip_downhill,
|
|
std::vector<PipId> *root_pips) {
|
|
std::vector<WireId> wires_to_expand;
|
|
|
|
wires_to_expand.push_back(root_wire);
|
|
while(!wires_to_expand.empty()) {
|
|
WireId wire = wires_to_expand.back();
|
|
wires_to_expand.pop_back();
|
|
|
|
auto downhill_iter = pip_downhill.find(wire);
|
|
if(downhill_iter == pip_downhill.end()) {
|
|
if(root_wire != wire) {
|
|
log_warning("Wire %s never entered the real fabric?\n",
|
|
ctx->nameOfWire(wire));
|
|
}
|
|
continue;
|
|
}
|
|
|
|
for(PipId pip : pip_downhill.at(wire)) {
|
|
if(!ctx->is_pip_synthetic(pip)) {
|
|
// Stop following edges that are non-synthetic, they will be
|
|
// followed during emit_net
|
|
root_pips->push_back(pip);
|
|
} else {
|
|
// Continue to follow synthetic edges.
|
|
wires_to_expand.push_back(ctx->getPipDstWire(pip));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FpgaInterchange::write_physical_netlist(const Context * ctx, const std::string &filename) {
|
|
::capnp::MallocMessageBuilder message;
|
|
|
|
PhysicalNetlist::PhysNetlist::Builder phys_netlist = message.initRoot<PhysicalNetlist::PhysNetlist>();
|
|
|
|
phys_netlist.setPart(ctx->get_part());
|
|
|
|
std::unordered_set<IdString> placed_cells;
|
|
for(const auto & cell_pair : ctx->cells) {
|
|
const CellInfo & cell = *cell_pair.second;
|
|
if(cell.bel == BelId()) {
|
|
// This cell was not placed!
|
|
continue;
|
|
}
|
|
NPNR_ASSERT(cell_pair.first == cell.name);
|
|
NPNR_ASSERT(placed_cells.emplace(cell.name).second);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if(!ctx->isBelLocationValid(cell.bel)) {
|
|
log_error("Cell '%s' is placed at BEL '%s', but this location is currently invalid. Not writing physical netlist.\n",
|
|
cell.name.c_str(ctx), ctx->nameOfBel(cell.bel));
|
|
}
|
|
|
|
if(ctx->is_bel_synthetic(cell.bel)) {
|
|
continue;
|
|
}
|
|
|
|
number_placements += 1;
|
|
}
|
|
|
|
std::vector<IdString> ports;
|
|
|
|
std::unordered_map<std::string, std::string> sites;
|
|
auto placements = phys_netlist.initPlacements(number_placements);
|
|
auto placement_iter = placements.begin();
|
|
|
|
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;
|
|
}
|
|
|
|
NPNR_ASSERT(ctx->isBelLocationValid(cell.bel));
|
|
|
|
if(ctx->is_bel_synthetic(cell.bel)) {
|
|
continue;
|
|
}
|
|
|
|
IdStringList bel_name = ctx->getBelName(cell.bel);
|
|
NPNR_ASSERT(bel_name.size() == 2);
|
|
std::string site_and_type = bel_name[0].str(ctx);
|
|
auto pos = site_and_type.find_first_of('.');
|
|
NPNR_ASSERT(pos != std::string::npos);
|
|
|
|
std::string site_name = site_and_type.substr(0, pos);
|
|
std::string site_type = site_and_type.substr(pos + 1);
|
|
|
|
auto result = sites.emplace(site_name, site_type);
|
|
if(!result.second) {
|
|
NPNR_ASSERT(result.first->second == site_type);
|
|
}
|
|
|
|
auto placement = *placement_iter++;
|
|
|
|
placement.setCellName(strings.get_index(cell.name.str(ctx)));
|
|
if(ctx->io_port_types.count(cell.type)) {
|
|
// Always mark IO ports as type <PORT>.
|
|
placement.setType(strings.get_index("<PORT>"));
|
|
ports.push_back(cell.name);
|
|
} else {
|
|
placement.setType(strings.get_index(cell.type.str(ctx)));
|
|
}
|
|
placement.setSite(strings.get_index(site_name));
|
|
|
|
size_t bel_index = strings.get_index(bel_name[1].str(ctx));
|
|
placement.setBel(bel_index);
|
|
placement.setIsBelFixed(cell.belStrength >= STRENGTH_FIXED);
|
|
placement.setIsSiteFixed(cell.belStrength >= STRENGTH_FIXED);
|
|
|
|
if(!ctx->io_port_types.count(cell.type)) {
|
|
// Don't emit pin map for ports.
|
|
size_t pin_count = 0;
|
|
for(const auto & pin : cell.cell_bel_pins) {
|
|
if(cell.const_ports.count(pin.first)) {
|
|
continue;
|
|
}
|
|
pin_count += pin.second.size();
|
|
}
|
|
|
|
auto pins = placement.initPinMap(pin_count);
|
|
auto pin_iter = pins.begin();
|
|
|
|
for(const auto & cell_to_bel_pins : cell.cell_bel_pins) {
|
|
if(cell.const_ports.count(cell_to_bel_pins.first)) {
|
|
continue;
|
|
}
|
|
|
|
std::string cell_pin = cell_to_bel_pins.first.str(ctx);
|
|
size_t cell_pin_index = strings.get_index(cell_pin);
|
|
|
|
for(const auto & bel_pin : cell_to_bel_pins.second) {
|
|
auto pin_output = *pin_iter++;
|
|
pin_output.setCellPin(cell_pin_index);
|
|
pin_output.setBel(bel_index);
|
|
pin_output.setBelPin(strings.get_index(bel_pin.str(ctx)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
auto phys_cells = phys_netlist.initPhysCells(ports.size());
|
|
auto phys_cells_iter = phys_cells.begin();
|
|
for(IdString port : ports) {
|
|
auto phys_cell = *phys_cells_iter++;
|
|
phys_cell.setCellName(strings.get_index(port.str(ctx)));
|
|
phys_cell.setPhysType(PhysicalNetlist::PhysNetlist::PhysCellType::PORT);
|
|
}
|
|
|
|
auto nets = phys_netlist.initPhysNets(ctx->nets.size());
|
|
auto net_iter = nets.begin();
|
|
for(auto & net_pair : ctx->nets) {
|
|
auto &net = *net_pair.second;
|
|
auto net_out = *net_iter++;
|
|
|
|
const CellInfo *driver_cell = net.driver.cell;
|
|
|
|
// Handle GND and VCC nets.
|
|
if(driver_cell->bel == ctx->get_gnd_bel()) {
|
|
IdString gnd_net_name(ctx->chip_info->constants->gnd_net_name);
|
|
net_out.setName(strings.get_index(gnd_net_name.str(ctx)));
|
|
net_out.setType(PhysicalNetlist::PhysNetlist::NetType::GND);
|
|
} else if(driver_cell->bel == ctx->get_vcc_bel()) {
|
|
IdString vcc_net_name(ctx->chip_info->constants->vcc_net_name);
|
|
net_out.setName(strings.get_index(vcc_net_name.str(ctx)));
|
|
net_out.setType(PhysicalNetlist::PhysNetlist::NetType::VCC);
|
|
} else {
|
|
net_out.setName(strings.get_index(net.name.str(ctx)));
|
|
}
|
|
|
|
std::unordered_map<WireId, BelPin> root_wires;
|
|
std::unordered_map<WireId, std::vector<PipId>> pip_downhill;
|
|
std::unordered_set<PipId> pips;
|
|
|
|
if (driver_cell != nullptr && driver_cell->bel != BelId() && ctx->isBelLocationValid(driver_cell->bel)) {
|
|
for(IdString bel_pin_name : driver_cell->cell_bel_pins.at(net.driver.port)) {
|
|
BelPin driver_bel_pin;
|
|
driver_bel_pin.bel = driver_cell->bel;
|
|
driver_bel_pin.pin = bel_pin_name;
|
|
|
|
WireId driver_wire = ctx->getBelPinWire(driver_bel_pin.bel, bel_pin_name);
|
|
if(driver_wire != WireId()) {
|
|
root_wires[driver_wire] = driver_bel_pin;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::unordered_map<WireId, std::vector<BelPin>> sinks;
|
|
for(const auto &port_ref : net.users) {
|
|
if(port_ref.cell != nullptr && port_ref.cell->bel != BelId() && ctx->isBelLocationValid(port_ref.cell->bel)) {
|
|
auto pin_iter = port_ref.cell->cell_bel_pins.find(port_ref.port);
|
|
if(pin_iter == port_ref.cell->cell_bel_pins.end()) {
|
|
log_warning("Cell %s port %s on net %s is legal, but has no BEL pins?\n",
|
|
port_ref.cell->name.c_str(ctx),
|
|
port_ref.port.c_str(ctx),
|
|
net.name.c_str(ctx));
|
|
continue;
|
|
}
|
|
|
|
for(IdString bel_pin_name : pin_iter->second) {
|
|
BelPin sink_bel_pin;
|
|
sink_bel_pin.bel = port_ref.cell->bel;
|
|
sink_bel_pin.pin = bel_pin_name;
|
|
|
|
WireId sink_wire = ctx->getBelPinWire(sink_bel_pin.bel, bel_pin_name);
|
|
if(sink_wire != WireId()) {
|
|
sinks[sink_wire].push_back(sink_bel_pin);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::unordered_map<PipId, PlaceStrength> pip_place_strength;
|
|
|
|
for(auto &wire_pair : net.wires) {
|
|
WireId downhill_wire = wire_pair.first;
|
|
PipId pip = wire_pair.second.pip;
|
|
PlaceStrength strength = wire_pair.second.strength;
|
|
pip_place_strength[pip] = strength;
|
|
if(pip != PipId()) {
|
|
pips.emplace(pip);
|
|
|
|
WireId uphill_wire = ctx->getPipSrcWire(pip);
|
|
NPNR_ASSERT(downhill_wire != uphill_wire);
|
|
pip_downhill[uphill_wire].push_back(pip);
|
|
} else {
|
|
// This is a root wire.
|
|
NPNR_ASSERT(root_wires.count(downhill_wire));
|
|
}
|
|
}
|
|
|
|
std::vector<PipId> root_pips;
|
|
std::vector<WireId> roots_to_remove;
|
|
|
|
for(const auto & root_pair : root_wires) {
|
|
WireId root_wire = root_pair.first;
|
|
BelPin src_bel_pin = root_pair.second;
|
|
|
|
if(!ctx->is_bel_synthetic(src_bel_pin.bel)) {
|
|
continue;
|
|
}
|
|
|
|
roots_to_remove.push_back(root_wire);
|
|
find_non_synthetic_edges(ctx, root_wire, pip_downhill, &root_pips);
|
|
}
|
|
|
|
// Remove wires that have a synthetic root.
|
|
for(WireId wire : roots_to_remove) {
|
|
NPNR_ASSERT(root_wires.erase(wire) == 1);
|
|
}
|
|
|
|
auto sources = net_out.initSources(root_wires.size() + root_pips.size());
|
|
auto source_iter = sources.begin();
|
|
|
|
for(const auto & root_pair : root_wires) {
|
|
WireId root_wire = root_pair.first;
|
|
BelPin src_bel_pin = root_pair.second;
|
|
|
|
PhysicalNetlist::PhysNetlist::RouteBranch::Builder source_branch = *source_iter++;
|
|
init_bel_pin(ctx, &strings, src_bel_pin, source_branch);
|
|
|
|
emit_net(ctx, &strings, pip_downhill, sinks, &pips, pip_place_strength, root_wire, source_branch);
|
|
}
|
|
|
|
for(const PipId root : root_pips) {
|
|
PhysicalNetlist::PhysNetlist::RouteBranch::Builder source_branch = *source_iter++;
|
|
|
|
NPNR_ASSERT(pips.erase(root) == 1);
|
|
WireId root_wire;
|
|
source_branch = init_local_source(ctx, &strings, source_branch, root, pip_place_strength, &root_wire);
|
|
emit_net(ctx, &strings, pip_downhill, sinks, &pips, pip_place_strength, root_wire, source_branch);
|
|
}
|
|
|
|
// Any pips that were not part of a tree starting from the source are
|
|
// stubs.
|
|
size_t real_pips = 0;
|
|
for(PipId pip : pips) {
|
|
if(ctx->is_pip_synthetic(pip)) {
|
|
continue;
|
|
}
|
|
real_pips += 1;
|
|
}
|
|
auto stubs = net_out.initStubs(real_pips);
|
|
auto stub_iter = stubs.begin();
|
|
for(PipId pip : pips) {
|
|
if(ctx->is_pip_synthetic(pip)) {
|
|
continue;
|
|
}
|
|
emit_branch(ctx, &strings, pip_place_strength, pip, *stub_iter++);
|
|
}
|
|
}
|
|
|
|
auto site_instances = phys_netlist.initSiteInsts(sites.size());
|
|
auto site_inst_iter = site_instances.begin();
|
|
auto site_iter = sites.begin();
|
|
while(site_iter != sites.end()) {
|
|
NPNR_ASSERT(site_inst_iter != site_instances.end());
|
|
|
|
auto site_instance = *site_inst_iter;
|
|
site_instance.setSite(strings.get_index(site_iter->first));
|
|
site_instance.setType(strings.get_index(site_iter->second));
|
|
|
|
++site_inst_iter;
|
|
++site_iter;
|
|
}
|
|
|
|
auto str_list = phys_netlist.initStrList(strings.strings.size());
|
|
for(size_t i = 0; i < strings.strings.size(); ++i) {
|
|
str_list.set(i, strings.strings[i]);
|
|
}
|
|
|
|
write_message(message, filename);
|
|
}
|
|
|
|
struct LogicalNetlistImpl;
|
|
|
|
size_t get_port_width(LogicalNetlist::Netlist::Port::Reader port) {
|
|
if(port.isBit()) {
|
|
return 1;
|
|
} else {
|
|
auto bus = port.getBus();
|
|
if(bus.getBusStart() < bus.getBusEnd()) {
|
|
return bus.getBusEnd() - bus.getBusStart() + 1;
|
|
} else {
|
|
return bus.getBusStart() - bus.getBusEnd() + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
struct PortKey {
|
|
PortKey(int32_t inst_idx, uint32_t port_idx) : inst_idx(inst_idx), port_idx(port_idx) {}
|
|
int32_t inst_idx;
|
|
uint32_t port_idx;
|
|
|
|
bool operator == (const PortKey &other) const {
|
|
return inst_idx == other.inst_idx && port_idx == other.port_idx;
|
|
}
|
|
};
|
|
|
|
NEXTPNR_NAMESPACE_END
|
|
|
|
template <> struct std::hash<NEXTPNR_NAMESPACE_PREFIX PortKey>
|
|
{
|
|
std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX PortKey &key) const noexcept
|
|
{
|
|
std::size_t seed = 0;
|
|
boost::hash_combine(seed, std::hash<int32_t>()(key.inst_idx));
|
|
boost::hash_combine(seed, std::hash<uint32_t>()(key.port_idx));
|
|
return seed;
|
|
}
|
|
};
|
|
|
|
NEXTPNR_NAMESPACE_BEGIN
|
|
|
|
struct ModuleReader {
|
|
const LogicalNetlistImpl *root;
|
|
|
|
bool is_top;
|
|
LogicalNetlist::Netlist::CellInstance::Reader cell_inst;
|
|
LogicalNetlist::Netlist::Cell::Reader cell;
|
|
LogicalNetlist::Netlist::CellDeclaration::Reader cell_decl;
|
|
|
|
std::unordered_map<int32_t, LogicalNetlist::Netlist::Net::Reader> net_indicies;
|
|
std::unordered_map<int32_t, std::string> disconnected_nets;
|
|
std::unordered_map<PortKey, std::vector<int32_t>> connections;
|
|
|
|
ModuleReader(const LogicalNetlistImpl *root,
|
|
LogicalNetlist::Netlist::CellInstance::Reader cell_inst, bool is_top);
|
|
|
|
size_t translate_port_index(LogicalNetlist::Netlist::PortInstance::Reader port_inst) const;
|
|
};
|
|
|
|
struct PortReader {
|
|
const ModuleReader * module;
|
|
size_t port_idx;
|
|
};
|
|
|
|
struct CellReader {
|
|
const ModuleReader * module;
|
|
size_t inst_idx;
|
|
};
|
|
|
|
struct NetReader {
|
|
NetReader(const ModuleReader * module, size_t net_idx) : module(module), net_idx(net_idx) {
|
|
scratch.resize(1);
|
|
scratch[0] = net_idx;
|
|
}
|
|
|
|
const ModuleReader * module;
|
|
size_t net_idx;
|
|
LogicalNetlist::Netlist::PropertyMap::Reader property_map;
|
|
std::vector<int32_t> scratch;
|
|
};
|
|
|
|
struct LogicalNetlistImpl
|
|
{
|
|
LogicalNetlist::Netlist::Reader root;
|
|
std::vector<std::string> strings;
|
|
|
|
typedef const ModuleReader ModuleDataType;
|
|
typedef const PortReader& ModulePortDataType;
|
|
typedef const CellReader& CellDataType;
|
|
typedef const NetReader& NetnameDataType;
|
|
typedef const std::vector<int32_t>& BitVectorDataType;
|
|
|
|
LogicalNetlistImpl(LogicalNetlist::Netlist::Reader root) : root(root) {
|
|
strings.reserve(root.getStrList().size());
|
|
for(auto s : root.getStrList()) {
|
|
strings.push_back(s);
|
|
}
|
|
}
|
|
|
|
template <typename TFunc> void foreach_module(TFunc Func) const
|
|
{
|
|
for (const auto &cell_inst : root.getInstList()) {
|
|
ModuleReader module(this, cell_inst, /*is_top=*/false);
|
|
Func(strings.at(cell_inst.getName()), module);
|
|
}
|
|
|
|
auto top = root.getTopInst();
|
|
ModuleReader top_module(this, top, /*is_top=*/true);
|
|
Func(strings.at(top.getName()), top_module);
|
|
}
|
|
|
|
template <typename TFunc> void foreach_port(const ModuleReader &mod, TFunc Func) const
|
|
{
|
|
for(auto port_idx : mod.cell_decl.getPorts()) {
|
|
PortReader port_reader;
|
|
port_reader.module = &mod;
|
|
port_reader.port_idx = port_idx;
|
|
auto port = root.getPortList()[port_idx];
|
|
Func(strings.at(port.getName()), port_reader);
|
|
}
|
|
}
|
|
|
|
template <typename TFunc> void foreach_cell(const ModuleReader &mod, TFunc Func) const
|
|
{
|
|
for (auto cell_inst_idx : mod.cell.getInsts()) {
|
|
auto cell_inst = root.getInstList()[cell_inst_idx];
|
|
CellReader cell_reader;
|
|
cell_reader.module = &mod;
|
|
cell_reader.inst_idx = cell_inst_idx;
|
|
Func(strings.at(cell_inst.getName()), cell_reader);
|
|
}
|
|
}
|
|
|
|
template <typename TFunc> void foreach_netname(const ModuleReader &mod, TFunc Func) const
|
|
{
|
|
// std::unordered_map<int32_t, LogicalNetlist::Netlist::Net::Reader> net_indicies;
|
|
for(auto net_pair : mod.net_indicies) {
|
|
NetReader net_reader(&mod, net_pair.first);
|
|
auto net = net_pair.second;
|
|
net_reader.property_map = net.getPropMap();
|
|
Func(strings.at(net.getName()), net_reader);
|
|
}
|
|
|
|
// std::unordered_map<int32_t, IdString> disconnected_nets;
|
|
for(auto net_pair : mod.disconnected_nets) {
|
|
NetReader net_reader(&mod, net_pair.first);
|
|
Func(net_pair.second, net_reader);
|
|
}
|
|
}
|
|
|
|
PortType get_port_type_for_direction(LogicalNetlist::Netlist::Direction dir) const {
|
|
if(dir == LogicalNetlist::Netlist::Direction::INPUT) {
|
|
return PORT_IN;
|
|
} else if (dir == LogicalNetlist::Netlist::Direction::INOUT) {
|
|
return PORT_INOUT;
|
|
} else if (dir == LogicalNetlist::Netlist::Direction::OUTPUT) {
|
|
return PORT_OUT;
|
|
} else {
|
|
NPNR_ASSERT_FALSE("invalid logical netlist port direction");
|
|
}
|
|
}
|
|
|
|
PortType get_port_dir(const PortReader &port_reader) const {
|
|
auto port = root.getPortList()[port_reader.port_idx];
|
|
auto dir = port.getDir();
|
|
return get_port_type_for_direction(dir);
|
|
}
|
|
|
|
const std::string &get_cell_type(const CellReader &cell) const {
|
|
auto cell_inst = root.getInstList()[cell.inst_idx];
|
|
auto cell_def = root.getCellList()[cell_inst.getCell()];
|
|
auto cell_decl = root.getCellDecls()[cell_def.getIndex()];
|
|
return strings.at(cell_decl.getName());
|
|
}
|
|
|
|
|
|
template <typename TFunc> void foreach_port_dir(const CellReader &cell, TFunc Func) const {
|
|
auto cell_inst = root.getInstList()[cell.inst_idx];
|
|
auto cell_def = root.getCellList()[cell_inst.getCell()];
|
|
auto cell_decl = root.getCellDecls()[cell_def.getIndex()];
|
|
|
|
for(auto port_idx : cell_decl.getPorts()) {
|
|
auto port = root.getPortList()[port_idx];
|
|
Func(strings.at(port.getName()), get_port_type_for_direction(port.getDir()));
|
|
}
|
|
}
|
|
|
|
template <typename TFunc> void foreach_prop_map(LogicalNetlist::Netlist::PropertyMap::Reader prop_map, TFunc Func) const {
|
|
for(auto prop : prop_map.getEntries()) {
|
|
if(prop.isTextValue()) {
|
|
Func(strings.at(prop.getKey()), Property(strings.at(prop.getTextValue())));
|
|
} else if(prop.isIntValue()) {
|
|
Func(strings.at(prop.getKey()), Property(prop.getIntValue()));
|
|
} else {
|
|
NPNR_ASSERT(prop.isBoolValue());
|
|
Func(strings.at(prop.getKey()), Property(prop.getBoolValue()));
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename TFunc> void foreach_attr(const ModuleReader &mod, TFunc Func) const {
|
|
if(mod.is_top) {
|
|
// Emit attribute "top" for top instance.
|
|
Func("top", Property(1));
|
|
}
|
|
|
|
auto cell_def = root.getCellList()[mod.cell_inst.getCell()];
|
|
auto cell_decl = root.getCellDecls()[cell_def.getIndex()];
|
|
foreach_prop_map(cell_decl.getPropMap(), Func);
|
|
}
|
|
|
|
template <typename TFunc> void foreach_attr(const CellReader &cell, TFunc Func) const {
|
|
auto cell_inst = root.getInstList()[cell.inst_idx];
|
|
foreach_prop_map(cell_inst.getPropMap(), Func);
|
|
}
|
|
|
|
template <typename TFunc> void foreach_attr(const PortReader &port_reader, TFunc Func) const {
|
|
auto port = root.getPortList()[port_reader.port_idx];
|
|
foreach_prop_map(port.getPropMap(), Func);
|
|
}
|
|
|
|
template <typename TFunc> void foreach_attr(const NetReader &net_reader, TFunc Func) const {
|
|
foreach_prop_map(net_reader.property_map, Func);
|
|
}
|
|
|
|
template <typename TFunc> void foreach_param(const CellReader &cell_reader, TFunc Func) const
|
|
{
|
|
auto cell_inst = root.getInstList()[cell_reader.inst_idx];
|
|
foreach_prop_map(cell_inst.getPropMap(), Func);
|
|
}
|
|
|
|
template <typename TFunc> void foreach_setting(const ModuleReader &obj, TFunc Func) const
|
|
{
|
|
foreach_prop_map(root.getPropMap(), Func);
|
|
}
|
|
|
|
template <typename TFunc> void foreach_port_conn(const CellReader &cell, TFunc Func) const
|
|
{
|
|
auto cell_inst = root.getInstList()[cell.inst_idx];
|
|
auto cell_def = root.getCellList()[cell_inst.getCell()];
|
|
auto cell_decl = root.getCellDecls()[cell_def.getIndex()];
|
|
|
|
for(auto port_idx : cell_decl.getPorts()) {
|
|
auto port = root.getPortList()[port_idx];
|
|
PortKey port_key(cell.inst_idx, port_idx);
|
|
const std::vector<int32_t> &connections = cell.module->connections.at(port_key);
|
|
Func(strings.at(port.getName()), connections);
|
|
}
|
|
}
|
|
|
|
int get_array_offset(const NetReader &port_reader) const
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
bool is_array_upto(const NetReader &port_reader) const {
|
|
return false;
|
|
}
|
|
|
|
int get_array_offset(const PortReader &port_reader) const
|
|
{
|
|
auto port = root.getPortList()[port_reader.port_idx];
|
|
if(port.isBus()) {
|
|
auto bus = port.getBus();
|
|
return std::min(bus.getBusStart(), bus.getBusEnd());
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
bool is_array_upto(const PortReader &port_reader) const {
|
|
auto port = root.getPortList()[port_reader.port_idx];
|
|
if(port.isBus()) {
|
|
auto bus = port.getBus();
|
|
return bus.getBusStart() < bus.getBusEnd();
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const std::vector<int32_t> &get_port_bits(const PortReader &port_reader) const {
|
|
PortKey port_key(-1, port_reader.port_idx);
|
|
return port_reader.module->connections.at(port_key);
|
|
}
|
|
|
|
const std::vector<int32_t> &get_net_bits(const NetReader &net) const {
|
|
return net.scratch;
|
|
}
|
|
|
|
int get_vector_length(const std::vector<int32_t> &bits) const { return int(bits.size()); }
|
|
|
|
bool is_vector_bit_constant(const std::vector<int32_t> &bits, int i) const
|
|
{
|
|
// Note: This appears weird, but is correct. This is because VCC/GND
|
|
// nets are not handled in frontend_base for FPGA interchange.
|
|
return false;
|
|
}
|
|
|
|
char get_vector_bit_constval(const std::vector<int32_t>&bits, int i) const
|
|
{
|
|
// Unreachable!
|
|
NPNR_ASSERT(false);
|
|
}
|
|
|
|
int get_vector_bit_signal(const std::vector<int32_t>&bits, int i) const
|
|
{
|
|
return bits.at(i);
|
|
}
|
|
};
|
|
|
|
ModuleReader::ModuleReader(const LogicalNetlistImpl *root,
|
|
LogicalNetlist::Netlist::CellInstance::Reader cell_inst, bool is_top) :
|
|
root(root), is_top(is_top), cell_inst(cell_inst) {
|
|
cell = root->root.getCellList()[cell_inst.getCell()];
|
|
cell_decl = root->root.getCellDecls()[cell.getIndex()];
|
|
|
|
// Auto-assign all ports to a net index, and then re-assign based on the
|
|
// nets.
|
|
int net_idx = 2;
|
|
|
|
auto ports = root->root.getPortList();
|
|
for(auto port_idx : cell_decl.getPorts()) {
|
|
auto port = ports[port_idx];
|
|
size_t port_width = get_port_width(port);
|
|
|
|
PortKey port_key(-1, port_idx);
|
|
auto result = connections.emplace(port_key, std::vector<int32_t>());
|
|
NPNR_ASSERT(result.second);
|
|
|
|
std::vector<int32_t> & port_connections = result.first->second;
|
|
port_connections.resize(port_width);
|
|
for(size_t i = 0; i < port_width; ++i) {
|
|
port_connections[i] = net_idx++;
|
|
}
|
|
}
|
|
|
|
for(auto inst_idx : cell.getInsts()) {
|
|
auto inst = root->root.getInstList()[inst_idx];
|
|
auto inst_cell = root->root.getCellList()[inst.getCell()];
|
|
auto inst_cell_decl = root->root.getCellDecls()[inst_cell.getIndex()];
|
|
|
|
auto inst_ports = inst_cell_decl.getPorts();
|
|
for(auto inst_port_idx : inst_ports) {
|
|
PortKey port_key(inst_idx, inst_port_idx);
|
|
auto result = connections.emplace(port_key, std::vector<int32_t>());
|
|
NPNR_ASSERT(result.second);
|
|
|
|
auto inst_port = ports[inst_port_idx];
|
|
size_t port_width = get_port_width(inst_port);
|
|
|
|
std::vector<int32_t> & port_connections = result.first->second;
|
|
port_connections.resize(port_width);
|
|
for(size_t i = 0; i < port_width; ++i) {
|
|
port_connections[i] = net_idx++;
|
|
}
|
|
}
|
|
}
|
|
|
|
auto nets = cell.getNets();
|
|
for(size_t i = 0; i < nets.size(); ++i, ++net_idx) {
|
|
auto net = nets[i];
|
|
net_indicies[net_idx] = net;
|
|
|
|
for(auto port_inst : net.getPortInsts()) {
|
|
int32_t inst_idx = -1;
|
|
if(port_inst.isInst()) {
|
|
inst_idx = port_inst.getInst();
|
|
}
|
|
|
|
PortKey port_key(inst_idx, port_inst.getPort());
|
|
std::vector<int32_t> & port_connections = connections.at(port_key);
|
|
|
|
size_t port_idx = translate_port_index(port_inst);
|
|
port_connections.at(port_idx) = net_idx;
|
|
}
|
|
}
|
|
|
|
for(const auto & port_connections : connections) {
|
|
for(size_t i = 0; i < port_connections.second.size(); ++i) {
|
|
int32_t net_idx = port_connections.second[i];
|
|
|
|
auto iter = net_indicies.find(net_idx);
|
|
if(iter == net_indicies.end()) {
|
|
PortKey port_key = port_connections.first;
|
|
auto port = ports[port_key.port_idx];
|
|
disconnected_nets[net_idx] = stringf("%s.%d", root->strings.at(port.getName()).c_str(), i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FpgaInterchange::read_logical_netlist(Context * ctx, const std::string &filename) {
|
|
gzFile file = gzopen(filename.c_str(), "r");
|
|
NPNR_ASSERT(file != Z_NULL);
|
|
|
|
std::vector<uint8_t> output_data;
|
|
output_data.resize(4096);
|
|
std::stringstream sstream(std::ios_base::in | std::ios_base::out | std::ios_base::binary);
|
|
while(true) {
|
|
int ret = gzread(file, output_data.data(), output_data.size());
|
|
NPNR_ASSERT(ret >= 0);
|
|
if(ret > 0) {
|
|
sstream.write((const char*)output_data.data(), ret);
|
|
NPNR_ASSERT(sstream);
|
|
} else {
|
|
NPNR_ASSERT(ret == 0);
|
|
int error;
|
|
gzerror(file, &error);
|
|
NPNR_ASSERT(error == Z_OK);
|
|
break;
|
|
}
|
|
}
|
|
|
|
NPNR_ASSERT(gzclose(file) == Z_OK);
|
|
|
|
sstream.seekg(0);
|
|
kj::std::StdInputStream istream(sstream);
|
|
capnp::ReaderOptions reader_options;
|
|
reader_options.traversalLimitInWords = 32llu*1024llu*1024llu*1024llu;
|
|
capnp::InputStreamMessageReader message_reader(istream, reader_options);
|
|
|
|
LogicalNetlist::Netlist::Reader netlist = message_reader.getRoot<LogicalNetlist::Netlist>();
|
|
LogicalNetlistImpl netlist_reader(netlist);
|
|
|
|
GenericFrontend<LogicalNetlistImpl>(ctx, netlist_reader, /*split_io=*/false)();
|
|
}
|
|
|
|
size_t ModuleReader::translate_port_index(LogicalNetlist::Netlist::PortInstance::Reader port_inst) const {
|
|
LogicalNetlist::Netlist::Port::Reader port = root->root.getPortList()[port_inst.getPort()];
|
|
if(port_inst.getBusIdx().isSingleBit()) {
|
|
NPNR_ASSERT(port.isBit());
|
|
return 0;
|
|
} else {
|
|
NPNR_ASSERT(port.isBus());
|
|
uint32_t idx = port_inst.getBusIdx().getIdx();
|
|
size_t width = get_port_width(port);
|
|
NPNR_ASSERT(idx < width);
|
|
return width - 1 - idx;
|
|
}
|
|
}
|
|
|
|
NEXTPNR_NAMESPACE_END
|
|
|