Merge pull request #345 from YosysHQ/dave/sdf
Improve handling of top level IO and add SDF support
This commit is contained in:
commit
d08e2ade88
@ -149,6 +149,9 @@ po::options_description CommandHandler::getGeneralOptions()
|
|||||||
general.add_options()("freq", po::value<double>(), "set target frequency for design in MHz");
|
general.add_options()("freq", po::value<double>(), "set target frequency for design in MHz");
|
||||||
general.add_options()("timing-allow-fail", "allow timing to fail in design");
|
general.add_options()("timing-allow-fail", "allow timing to fail in design");
|
||||||
general.add_options()("no-tmdriv", "disable timing-driven placement");
|
general.add_options()("no-tmdriv", "disable timing-driven placement");
|
||||||
|
general.add_options()("sdf", po::value<std::string>(), "SDF delay back-annotation file to write");
|
||||||
|
general.add_options()("sdf-cvc", "enable tweaks for SDF file compatibility with the CVC simulator");
|
||||||
|
|
||||||
return general;
|
return general;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,6 +339,14 @@ int CommandHandler::executeMain(std::unique_ptr<Context> ctx)
|
|||||||
log_error("Saving design failed.\n");
|
log_error("Saving design failed.\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (vm.count("sdf")) {
|
||||||
|
std::string filename = vm["sdf"].as<std::string>();
|
||||||
|
std::ofstream f(filename);
|
||||||
|
if (!f)
|
||||||
|
log_error("Failed to open SDF file '%s' for writing.\n", filename.c_str());
|
||||||
|
ctx->writeSDF(f, vm.count("sdf-cvc"));
|
||||||
|
}
|
||||||
|
|
||||||
#ifndef NO_PYTHON
|
#ifndef NO_PYTHON
|
||||||
deinit_python();
|
deinit_python();
|
||||||
#endif
|
#endif
|
||||||
|
@ -88,7 +88,7 @@ void connect_port(const Context *ctx, NetInfo *net, CellInfo *cell, IdString por
|
|||||||
NPNR_ASSERT(net->driver.cell == nullptr);
|
NPNR_ASSERT(net->driver.cell == nullptr);
|
||||||
net->driver.cell = cell;
|
net->driver.cell = cell;
|
||||||
net->driver.port = port_name;
|
net->driver.port = port_name;
|
||||||
} else if (port.type == PORT_IN) {
|
} else if (port.type == PORT_IN || port.type == PORT_INOUT) {
|
||||||
PortRef user;
|
PortRef user;
|
||||||
user.cell = cell;
|
user.cell = cell;
|
||||||
user.port = port_name;
|
user.port = port_name;
|
||||||
@ -146,4 +146,14 @@ void rename_port(Context *ctx, CellInfo *cell, IdString old_name, IdString new_n
|
|||||||
cell->ports[new_name] = pi;
|
cell->ports[new_name] = pi;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void rename_net(Context *ctx, NetInfo *net, IdString new_name)
|
||||||
|
{
|
||||||
|
if (net == nullptr)
|
||||||
|
return;
|
||||||
|
NPNR_ASSERT(!ctx->nets.count(new_name));
|
||||||
|
std::swap(ctx->nets[net->name], ctx->nets[new_name]);
|
||||||
|
ctx->nets.erase(net->name);
|
||||||
|
net->name = new_name;
|
||||||
|
}
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_END
|
NEXTPNR_NAMESPACE_END
|
||||||
|
@ -94,6 +94,9 @@ void connect_ports(Context *ctx, CellInfo *cell1, IdString port1_name, CellInfo
|
|||||||
// Rename a port if it exists on a cell
|
// Rename a port if it exists on a cell
|
||||||
void rename_port(Context *ctx, CellInfo *cell, IdString old_name, IdString new_name);
|
void rename_port(Context *ctx, CellInfo *cell, IdString old_name, IdString new_name);
|
||||||
|
|
||||||
|
// Rename a net without invalidating pointers to it
|
||||||
|
void rename_net(Context *ctx, NetInfo *net, IdString new_name);
|
||||||
|
|
||||||
void print_utilisation(const Context *ctx);
|
void print_utilisation(const Context *ctx);
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_END
|
NEXTPNR_NAMESPACE_END
|
||||||
|
@ -808,6 +808,11 @@ struct Context : Arch, DeterministicRNG
|
|||||||
|
|
||||||
// --------------------------------------------------------------
|
// --------------------------------------------------------------
|
||||||
|
|
||||||
|
// provided by sdf.cc
|
||||||
|
void writeSDF(std::ostream &out, bool cvc_mode = false) const;
|
||||||
|
|
||||||
|
// --------------------------------------------------------------
|
||||||
|
|
||||||
uint32_t checksum() const;
|
uint32_t checksum() const;
|
||||||
|
|
||||||
void check() const;
|
void check() const;
|
||||||
|
334
common/sdf.cc
Normal file
334
common/sdf.cc
Normal file
@ -0,0 +1,334 @@
|
|||||||
|
/*
|
||||||
|
* nextpnr -- Next Generation Place and Route
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019 David Shah <dave@ds0.me>
|
||||||
|
*
|
||||||
|
* 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 "nextpnr.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
NEXTPNR_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
namespace SDF {
|
||||||
|
|
||||||
|
struct MinMaxTyp
|
||||||
|
{
|
||||||
|
double min, typ, max;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RiseFallDelay
|
||||||
|
{
|
||||||
|
MinMaxTyp rise, fall;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PortAndEdge
|
||||||
|
{
|
||||||
|
std::string port;
|
||||||
|
ClockEdge edge;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IOPath
|
||||||
|
{
|
||||||
|
std::string from, to;
|
||||||
|
RiseFallDelay delay;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TimingCheck
|
||||||
|
{
|
||||||
|
enum CheckType
|
||||||
|
{
|
||||||
|
SETUPHOLD,
|
||||||
|
PERIOD,
|
||||||
|
WIDTH
|
||||||
|
} type;
|
||||||
|
PortAndEdge from, to;
|
||||||
|
RiseFallDelay delay;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Cell
|
||||||
|
{
|
||||||
|
std::string celltype, instance;
|
||||||
|
std::vector<IOPath> iopaths;
|
||||||
|
std::vector<TimingCheck> checks;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CellPort
|
||||||
|
{
|
||||||
|
std::string cell, port;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Interconnect
|
||||||
|
{
|
||||||
|
CellPort from, to;
|
||||||
|
RiseFallDelay delay;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SDFWriter
|
||||||
|
{
|
||||||
|
bool cvc_mode = false;
|
||||||
|
std::vector<Cell> cells;
|
||||||
|
std::vector<Interconnect> conn;
|
||||||
|
std::string sdfversion, design, vendor, program;
|
||||||
|
|
||||||
|
std::string format_name(const std::string &name)
|
||||||
|
{
|
||||||
|
std::string fmt = "\"";
|
||||||
|
for (char c : name) {
|
||||||
|
if (c == '\\' || c == '\"')
|
||||||
|
fmt += "\"";
|
||||||
|
fmt += c;
|
||||||
|
}
|
||||||
|
fmt += "\"";
|
||||||
|
return fmt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string escape_name(const std::string &name)
|
||||||
|
{
|
||||||
|
std::string esc;
|
||||||
|
for (char c : name) {
|
||||||
|
if (c == '$' || c == '\\' || c == '[' || c == ']' || c == ':' || (cvc_mode && c == '.'))
|
||||||
|
esc += '\\';
|
||||||
|
esc += c;
|
||||||
|
}
|
||||||
|
return esc;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string timing_check_name(TimingCheck::CheckType type)
|
||||||
|
{
|
||||||
|
switch (type) {
|
||||||
|
case TimingCheck::SETUPHOLD:
|
||||||
|
return "SETUPHOLD";
|
||||||
|
case TimingCheck::PERIOD:
|
||||||
|
return "PERIOD";
|
||||||
|
case TimingCheck::WIDTH:
|
||||||
|
return "WIDTH";
|
||||||
|
default:
|
||||||
|
NPNR_ASSERT_FALSE("unknown timing check type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void write_delay(std::ostream &out, const RiseFallDelay &delay)
|
||||||
|
{
|
||||||
|
write_delay(out, delay.rise);
|
||||||
|
out << " ";
|
||||||
|
write_delay(out, delay.fall);
|
||||||
|
}
|
||||||
|
|
||||||
|
void write_delay(std::ostream &out, const MinMaxTyp &delay)
|
||||||
|
{
|
||||||
|
if (cvc_mode)
|
||||||
|
out << "(" << int(delay.min) << ":" << int(delay.typ) << ":" << int(delay.max) << ")";
|
||||||
|
else
|
||||||
|
out << "(" << delay.min << ":" << delay.typ << ":" << delay.max << ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
void write_port(std::ostream &out, const CellPort &port)
|
||||||
|
{
|
||||||
|
if (cvc_mode)
|
||||||
|
out << escape_name(port.cell) + "." + escape_name(port.port);
|
||||||
|
else
|
||||||
|
out << escape_name(port.cell + "/" + port.port);
|
||||||
|
}
|
||||||
|
|
||||||
|
void write_portedge(std::ostream &out, const PortAndEdge &pe)
|
||||||
|
{
|
||||||
|
out << "(" << (pe.edge == RISING_EDGE ? "posedge" : "negedge") << " " << escape_name(pe.port) << ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
void write(std::ostream &out)
|
||||||
|
{
|
||||||
|
out << "(DELAYFILE" << std::endl;
|
||||||
|
// Headers and metadata
|
||||||
|
out << " (SDFVERSION " << format_name(sdfversion) << ")" << std::endl;
|
||||||
|
out << " (DESIGN " << format_name(design) << ")" << std::endl;
|
||||||
|
out << " (VENDOR " << format_name(vendor) << ")" << std::endl;
|
||||||
|
out << " (PROGRAM " << format_name(program) << ")" << std::endl;
|
||||||
|
out << " (DIVIDER " << (cvc_mode ? "." : "/") << ")" << std::endl;
|
||||||
|
out << " (TIMESCALE 1ps)" << std::endl;
|
||||||
|
// Write interconnect delays, with the main design begin a "cell"
|
||||||
|
out << " (CELL" << std::endl;
|
||||||
|
out << " (CELLTYPE " << format_name(design) << ")" << std::endl;
|
||||||
|
out << " (INSTANCE )" << std::endl;
|
||||||
|
out << " (DELAY" << std::endl;
|
||||||
|
out << " (ABSOLUTE" << std::endl;
|
||||||
|
for (auto &ic : conn) {
|
||||||
|
out << " (INTERCONNECT ";
|
||||||
|
write_port(out, ic.from);
|
||||||
|
out << " ";
|
||||||
|
write_port(out, ic.to);
|
||||||
|
out << " ";
|
||||||
|
write_delay(out, ic.delay);
|
||||||
|
out << ")" << std::endl;
|
||||||
|
}
|
||||||
|
out << " )" << std::endl;
|
||||||
|
out << " )" << std::endl;
|
||||||
|
out << " )" << std::endl;
|
||||||
|
// Write cells
|
||||||
|
for (auto &cell : cells) {
|
||||||
|
out << " (CELL" << std::endl;
|
||||||
|
out << " (CELLTYPE " << format_name(cell.celltype) << ")" << std::endl;
|
||||||
|
out << " (INSTANCE " << escape_name(cell.instance) << ")" << std::endl;
|
||||||
|
// IOPATHs (combinational delay and clock-to-q)
|
||||||
|
if (!cell.iopaths.empty()) {
|
||||||
|
out << " (DELAY" << std::endl;
|
||||||
|
out << " (ABSOLUTE" << std::endl;
|
||||||
|
for (auto &path : cell.iopaths) {
|
||||||
|
out << " (IOPATH " << escape_name(path.from) << " " << escape_name(path.to) << " ";
|
||||||
|
write_delay(out, path.delay);
|
||||||
|
out << ")" << std::endl;
|
||||||
|
}
|
||||||
|
out << " )" << std::endl;
|
||||||
|
out << " )" << std::endl;
|
||||||
|
}
|
||||||
|
// Timing Checks (setup/hold, period, width)
|
||||||
|
if (!cell.checks.empty()) {
|
||||||
|
out << " (TIMINGCHECK" << std::endl;
|
||||||
|
for (auto &check : cell.checks) {
|
||||||
|
out << " (" << timing_check_name(check.type) << " ";
|
||||||
|
write_portedge(out, check.from);
|
||||||
|
out << " ";
|
||||||
|
if (check.type == TimingCheck::SETUPHOLD) {
|
||||||
|
write_portedge(out, check.to);
|
||||||
|
out << " ";
|
||||||
|
}
|
||||||
|
if (check.type == TimingCheck::SETUPHOLD)
|
||||||
|
write_delay(out, check.delay);
|
||||||
|
else
|
||||||
|
write_delay(out, check.delay.rise);
|
||||||
|
out << ")" << std::endl;
|
||||||
|
}
|
||||||
|
out << " )" << std::endl;
|
||||||
|
}
|
||||||
|
out << " )" << std::endl;
|
||||||
|
}
|
||||||
|
out << ")" << std::endl;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace SDF
|
||||||
|
|
||||||
|
void Context::writeSDF(std::ostream &out, bool cvc_mode) const
|
||||||
|
{
|
||||||
|
using namespace SDF;
|
||||||
|
SDFWriter wr;
|
||||||
|
wr.cvc_mode = cvc_mode;
|
||||||
|
wr.design = str_or_default(attrs, id("module"), "top");
|
||||||
|
wr.sdfversion = "3.0";
|
||||||
|
wr.vendor = "nextpnr";
|
||||||
|
wr.program = "nextpnr";
|
||||||
|
|
||||||
|
const double delay_scale = 1000;
|
||||||
|
// Convert from DelayInfo to SDF-friendly RiseFallDelay
|
||||||
|
auto convert_delay = [&](const DelayInfo &dly) {
|
||||||
|
RiseFallDelay rf;
|
||||||
|
rf.rise.min = getDelayNS(dly.minRaiseDelay()) * delay_scale;
|
||||||
|
rf.rise.typ = getDelayNS((dly.minRaiseDelay() + dly.maxRaiseDelay()) / 2) * delay_scale; // fixme: typ delays?
|
||||||
|
rf.rise.max = getDelayNS(dly.maxRaiseDelay()) * delay_scale;
|
||||||
|
rf.fall.min = getDelayNS(dly.minFallDelay()) * delay_scale;
|
||||||
|
rf.fall.typ = getDelayNS((dly.minFallDelay() + dly.maxFallDelay()) / 2) * delay_scale; // fixme: typ delays?
|
||||||
|
rf.fall.max = getDelayNS(dly.maxFallDelay()) * delay_scale;
|
||||||
|
return rf;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto convert_setuphold = [&](const DelayInfo &setup, const DelayInfo &hold) {
|
||||||
|
RiseFallDelay rf;
|
||||||
|
rf.rise.min = getDelayNS(setup.minDelay()) * delay_scale;
|
||||||
|
rf.rise.typ = getDelayNS((setup.minDelay() + setup.maxDelay()) / 2) * delay_scale; // fixme: typ delays?
|
||||||
|
rf.rise.max = getDelayNS(setup.maxDelay()) * delay_scale;
|
||||||
|
rf.fall.min = getDelayNS(hold.minDelay()) * delay_scale;
|
||||||
|
rf.fall.typ = getDelayNS((hold.minDelay() + hold.maxDelay()) / 2) * delay_scale; // fixme: typ delays?
|
||||||
|
rf.fall.max = getDelayNS(hold.maxDelay()) * delay_scale;
|
||||||
|
return rf;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto cell : sorted(cells)) {
|
||||||
|
Cell sc;
|
||||||
|
const CellInfo *ci = cell.second;
|
||||||
|
sc.instance = ci->name.str(this);
|
||||||
|
sc.celltype = ci->type.str(this);
|
||||||
|
for (auto port : ci->ports) {
|
||||||
|
int clockCount = 0;
|
||||||
|
TimingPortClass cls = getPortTimingClass(ci, port.first, clockCount);
|
||||||
|
if (cls == TMG_IGNORE)
|
||||||
|
continue;
|
||||||
|
if (port.second.net == nullptr)
|
||||||
|
continue; // Ignore disconnected ports
|
||||||
|
if (port.second.type != PORT_IN) {
|
||||||
|
// Add combinational paths to this output (or inout)
|
||||||
|
for (auto other : ci->ports) {
|
||||||
|
if (other.second.net == nullptr)
|
||||||
|
continue;
|
||||||
|
if (other.second.type == PORT_OUT)
|
||||||
|
continue;
|
||||||
|
DelayInfo dly;
|
||||||
|
if (!getCellDelay(ci, other.first, port.first, dly))
|
||||||
|
continue;
|
||||||
|
IOPath iop;
|
||||||
|
iop.from = other.first.str(this);
|
||||||
|
iop.to = port.first.str(this);
|
||||||
|
iop.delay = convert_delay(dly);
|
||||||
|
sc.iopaths.push_back(iop);
|
||||||
|
}
|
||||||
|
// Add clock-to-output delays, also as IOPaths
|
||||||
|
if (cls == TMG_REGISTER_OUTPUT)
|
||||||
|
for (int i = 0; i < clockCount; i++) {
|
||||||
|
auto clkInfo = getPortClockingInfo(ci, port.first, i);
|
||||||
|
IOPath cqp;
|
||||||
|
cqp.from = clkInfo.clock_port.str(this);
|
||||||
|
cqp.to = port.first.str(this);
|
||||||
|
cqp.delay = convert_delay(clkInfo.clockToQ);
|
||||||
|
sc.iopaths.push_back(cqp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (port.second.type != PORT_OUT && cls == TMG_REGISTER_INPUT) {
|
||||||
|
// Add setup/hold checks
|
||||||
|
for (int i = 0; i < clockCount; i++) {
|
||||||
|
auto clkInfo = getPortClockingInfo(ci, port.first, i);
|
||||||
|
TimingCheck chk;
|
||||||
|
chk.from.edge = RISING_EDGE; // Add setup/hold checks equally for rising and falling edges
|
||||||
|
chk.from.port = port.first.str(this);
|
||||||
|
chk.to.edge = clkInfo.edge;
|
||||||
|
chk.to.port = clkInfo.clock_port.str(this);
|
||||||
|
chk.type = TimingCheck::SETUPHOLD;
|
||||||
|
chk.delay = convert_setuphold(clkInfo.setup, clkInfo.hold);
|
||||||
|
sc.checks.push_back(chk);
|
||||||
|
chk.from.edge = FALLING_EDGE;
|
||||||
|
sc.checks.push_back(chk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wr.cells.push_back(sc);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto net : sorted(nets)) {
|
||||||
|
NetInfo *ni = net.second;
|
||||||
|
if (ni->driver.cell == nullptr)
|
||||||
|
continue;
|
||||||
|
for (auto &usr : ni->users) {
|
||||||
|
Interconnect ic;
|
||||||
|
ic.from.cell = ni->driver.cell->name.str(this);
|
||||||
|
ic.from.port = ni->driver.port.str(this);
|
||||||
|
ic.to.cell = usr.cell->name.str(this);
|
||||||
|
ic.to.port = usr.port.str(this);
|
||||||
|
// FIXME: min/max routing delay - or at least constructing DelayInfo here
|
||||||
|
ic.delay = convert_delay(getDelayFromNS(getDelayNS(getNetinfoRouteDelay(ni, usr))));
|
||||||
|
wr.conn.push_back(ic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wr.write(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
NEXTPNR_NAMESPACE_END
|
@ -416,7 +416,25 @@ void nxio_to_tr(Context *ctx, CellInfo *nxio, CellInfo *trio, std::vector<std::u
|
|||||||
} else {
|
} else {
|
||||||
NPNR_ASSERT(false);
|
NPNR_ASSERT(false);
|
||||||
}
|
}
|
||||||
NetInfo *donet = trio->ports.at(ctx->id("I")).net;
|
NetInfo *donet = trio->ports.at(ctx->id("I")).net, *dinet = trio->ports.at(ctx->id("O")).net;
|
||||||
|
|
||||||
|
// Rename I/O nets to avoid conflicts
|
||||||
|
if (donet != nullptr && donet->name == nxio->name)
|
||||||
|
rename_net(ctx, donet, ctx->id(donet->name.str(ctx) + "$TRELLIS_IO_OUT"));
|
||||||
|
if (dinet != nullptr && dinet->name == nxio->name)
|
||||||
|
rename_net(ctx, dinet, ctx->id(dinet->name.str(ctx) + "$TRELLIS_IO_IN"));
|
||||||
|
|
||||||
|
// Create a new top port net for accurate IO timing analysis and simulation netlists
|
||||||
|
if (ctx->ports.count(nxio->name)) {
|
||||||
|
IdString tn_netname = nxio->name;
|
||||||
|
NPNR_ASSERT(!ctx->nets.count(tn_netname));
|
||||||
|
std::unique_ptr<NetInfo> toplevel_net{new NetInfo};
|
||||||
|
toplevel_net->name = tn_netname;
|
||||||
|
connect_port(ctx, toplevel_net.get(), trio, ctx->id("B"));
|
||||||
|
ctx->ports[nxio->name].net = toplevel_net.get();
|
||||||
|
ctx->nets[tn_netname] = std::move(toplevel_net);
|
||||||
|
}
|
||||||
|
|
||||||
CellInfo *tbuf = net_driven_by(
|
CellInfo *tbuf = net_driven_by(
|
||||||
ctx, donet, [](const Context *ctx, const CellInfo *cell) { return cell->type == ctx->id("$_TBUF_"); },
|
ctx, donet, [](const Context *ctx, const CellInfo *cell) { return cell->type == ctx->id("$_TBUF_"); },
|
||||||
ctx->id("Y"));
|
ctx->id("Y"));
|
||||||
|
14
ecp5/pack.cc
14
ecp5/pack.cc
@ -362,8 +362,7 @@ class Ecp5Packer
|
|||||||
for (auto &port : ci->ports)
|
for (auto &port : ci->ports)
|
||||||
disconnect_port(ctx, ci, port.first);
|
disconnect_port(ctx, ci, port.first);
|
||||||
} else if (trio != nullptr) {
|
} else if (trio != nullptr) {
|
||||||
// Trivial case, TRELLIS_IO used. Just destroy the net and the
|
// Trivial case, TRELLIS_IO used. Just remove the IOBUF
|
||||||
// iobuf
|
|
||||||
log_info("%s feeds TRELLIS_IO %s, removing %s %s.\n", ci->name.c_str(ctx), trio->name.c_str(ctx),
|
log_info("%s feeds TRELLIS_IO %s, removing %s %s.\n", ci->name.c_str(ctx), trio->name.c_str(ctx),
|
||||||
ci->type.c_str(ctx), ci->name.c_str(ctx));
|
ci->type.c_str(ctx), ci->name.c_str(ctx));
|
||||||
|
|
||||||
@ -384,14 +383,6 @@ class Ecp5Packer
|
|||||||
std::swap(net->clkconstr, onet->clkconstr);
|
std::swap(net->clkconstr, onet->clkconstr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx->nets.erase(net->name);
|
|
||||||
trio->ports.at(ctx->id("B")).net = nullptr;
|
|
||||||
}
|
|
||||||
if (ci->type == ctx->id("$nextpnr_iobuf")) {
|
|
||||||
NetInfo *net2 = ci->ports.at(ctx->id("I")).net;
|
|
||||||
if (net2 != nullptr) {
|
|
||||||
ctx->nets.erase(net2->name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (drives_top_port(ionet, tp)) {
|
} else if (drives_top_port(ionet, tp)) {
|
||||||
log_info("%s feeds %s %s.%s, removing %s %s.\n", ci->name.c_str(ctx), tp.cell->type.c_str(ctx),
|
log_info("%s feeds %s %s.%s, removing %s %s.\n", ci->name.c_str(ctx), tp.cell->type.c_str(ctx),
|
||||||
@ -414,7 +405,8 @@ class Ecp5Packer
|
|||||||
new_cells.push_back(std::move(tr_cell));
|
new_cells.push_back(std::move(tr_cell));
|
||||||
trio = new_cells.back().get();
|
trio = new_cells.back().get();
|
||||||
}
|
}
|
||||||
|
for (auto port : ci->ports)
|
||||||
|
disconnect_port(ctx, ci, port.first);
|
||||||
packed_cells.insert(ci->name);
|
packed_cells.insert(ci->name);
|
||||||
if (trio != nullptr) {
|
if (trio != nullptr) {
|
||||||
for (const auto &attr : ci->attrs)
|
for (const auto &attr : ci->attrs)
|
||||||
|
@ -69,7 +69,7 @@ std::unique_ptr<CellInfo> create_ice_cell(Context *ctx, IdString type, std::stri
|
|||||||
new_cell->params[ctx->id("PIN_TYPE")] = Property(0, 6);
|
new_cell->params[ctx->id("PIN_TYPE")] = Property(0, 6);
|
||||||
new_cell->params[ctx->id("PULLUP")] = Property::State::S0;
|
new_cell->params[ctx->id("PULLUP")] = Property::State::S0;
|
||||||
new_cell->params[ctx->id("NEG_TRIGGER")] = Property::State::S0;
|
new_cell->params[ctx->id("NEG_TRIGGER")] = Property::State::S0;
|
||||||
new_cell->params[ctx->id("IOSTANDARD")] = Property("SB_LVCMOS");
|
new_cell->params[ctx->id("IO_STANDARD")] = Property("SB_LVCMOS");
|
||||||
|
|
||||||
add_port(ctx, new_cell.get(), "PACKAGE_PIN", PORT_INOUT);
|
add_port(ctx, new_cell.get(), "PACKAGE_PIN", PORT_INOUT);
|
||||||
|
|
||||||
@ -426,7 +426,25 @@ void nxio_to_sb(Context *ctx, CellInfo *nxio, CellInfo *sbio, std::unordered_set
|
|||||||
} else {
|
} else {
|
||||||
NPNR_ASSERT(false);
|
NPNR_ASSERT(false);
|
||||||
}
|
}
|
||||||
NetInfo *donet = sbio->ports.at(ctx->id("D_OUT_0")).net;
|
NetInfo *donet = sbio->ports.at(ctx->id("D_OUT_0")).net, *dinet = sbio->ports.at(ctx->id("D_IN_0")).net;
|
||||||
|
|
||||||
|
// Rename I/O nets to avoid conflicts
|
||||||
|
if (donet != nullptr && donet->name == nxio->name)
|
||||||
|
rename_net(ctx, donet, ctx->id(donet->name.str(ctx) + "$SB_IO_OUT"));
|
||||||
|
if (dinet != nullptr && dinet->name == nxio->name)
|
||||||
|
rename_net(ctx, dinet, ctx->id(dinet->name.str(ctx) + "$SB_IO_IN"));
|
||||||
|
|
||||||
|
// Create a new top port net for accurate IO timing analysis and simulation netlists
|
||||||
|
if (ctx->ports.count(nxio->name)) {
|
||||||
|
IdString tn_netname = nxio->name;
|
||||||
|
NPNR_ASSERT(!ctx->nets.count(tn_netname));
|
||||||
|
std::unique_ptr<NetInfo> toplevel_net{new NetInfo};
|
||||||
|
toplevel_net->name = tn_netname;
|
||||||
|
connect_port(ctx, toplevel_net.get(), sbio, ctx->id("PACKAGE_PIN"));
|
||||||
|
ctx->ports[nxio->name].net = toplevel_net.get();
|
||||||
|
ctx->nets[tn_netname] = std::move(toplevel_net);
|
||||||
|
}
|
||||||
|
|
||||||
CellInfo *tbuf = net_driven_by(
|
CellInfo *tbuf = net_driven_by(
|
||||||
ctx, donet, [](const Context *ctx, const CellInfo *cell) { return cell->type == ctx->id("$_TBUF_"); },
|
ctx, donet, [](const Context *ctx, const CellInfo *cell) { return cell->type == ctx->id("$_TBUF_"); },
|
||||||
ctx->id("Y"));
|
ctx->id("Y"));
|
||||||
|
@ -459,7 +459,6 @@ static void pack_io(Context *ctx)
|
|||||||
{
|
{
|
||||||
std::unordered_set<IdString> packed_cells;
|
std::unordered_set<IdString> packed_cells;
|
||||||
std::unordered_set<IdString> delete_nets;
|
std::unordered_set<IdString> delete_nets;
|
||||||
|
|
||||||
std::vector<std::unique_ptr<CellInfo>> new_cells;
|
std::vector<std::unique_ptr<CellInfo>> new_cells;
|
||||||
log_info("Packing IOs..\n");
|
log_info("Packing IOs..\n");
|
||||||
|
|
||||||
@ -478,8 +477,7 @@ static void pack_io(Context *ctx)
|
|||||||
rgb = net->driver.cell;
|
rgb = net->driver.cell;
|
||||||
}
|
}
|
||||||
if (sb != nullptr) {
|
if (sb != nullptr) {
|
||||||
// Trivial case, SB_IO used. Just destroy the net and the
|
// Trivial case, SB_IO used. Just destroy the iobuf
|
||||||
// iobuf
|
|
||||||
log_info("%s feeds SB_IO %s, removing %s %s.\n", ci->name.c_str(ctx), sb->name.c_str(ctx),
|
log_info("%s feeds SB_IO %s, removing %s %s.\n", ci->name.c_str(ctx), sb->name.c_str(ctx),
|
||||||
ci->type.c_str(ctx), ci->name.c_str(ctx));
|
ci->type.c_str(ctx), ci->name.c_str(ctx));
|
||||||
NetInfo *net = sb->ports.at(ctx->id("PACKAGE_PIN")).net;
|
NetInfo *net = sb->ports.at(ctx->id("PACKAGE_PIN")).net;
|
||||||
@ -490,7 +488,6 @@ static void pack_io(Context *ctx)
|
|||||||
sb->type.c_str(ctx), sb->name.c_str(ctx));
|
sb->type.c_str(ctx), sb->name.c_str(ctx));
|
||||||
|
|
||||||
if (net != nullptr) {
|
if (net != nullptr) {
|
||||||
|
|
||||||
if (net->clkconstr != nullptr) {
|
if (net->clkconstr != nullptr) {
|
||||||
if (sb->ports.count(id_D_IN_0)) {
|
if (sb->ports.count(id_D_IN_0)) {
|
||||||
NetInfo *din0_net = sb->ports.at(id_D_IN_0).net;
|
NetInfo *din0_net = sb->ports.at(id_D_IN_0).net;
|
||||||
@ -509,15 +506,6 @@ static void pack_io(Context *ctx)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delete_nets.insert(net->name);
|
|
||||||
sb->ports.at(ctx->id("PACKAGE_PIN")).net = nullptr;
|
|
||||||
}
|
|
||||||
if (ci->type == ctx->id("$nextpnr_iobuf")) {
|
|
||||||
NetInfo *net2 = ci->ports.at(ctx->id("I")).net;
|
|
||||||
if (net2 != nullptr) {
|
|
||||||
delete_nets.insert(net2->name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (rgb != nullptr) {
|
} else if (rgb != nullptr) {
|
||||||
log_info("%s use by SB_RGBA_DRV/SB_RGB_DRV %s, not creating SB_IO\n", ci->name.c_str(ctx),
|
log_info("%s use by SB_RGBA_DRV/SB_RGB_DRV %s, not creating SB_IO\n", ci->name.c_str(ctx),
|
||||||
@ -533,6 +521,8 @@ static void pack_io(Context *ctx)
|
|||||||
new_cells.push_back(std::move(ice_cell));
|
new_cells.push_back(std::move(ice_cell));
|
||||||
sb = new_cells.back().get();
|
sb = new_cells.back().get();
|
||||||
}
|
}
|
||||||
|
for (auto port : ci->ports)
|
||||||
|
disconnect_port(ctx, ci, port.first);
|
||||||
packed_cells.insert(ci->name);
|
packed_cells.insert(ci->name);
|
||||||
std::copy(ci->attrs.begin(), ci->attrs.end(), std::inserter(sb->attrs, sb->attrs.begin()));
|
std::copy(ci->attrs.begin(), ci->attrs.end(), std::inserter(sb->attrs, sb->attrs.begin()));
|
||||||
} else if (is_sb_io(ctx, ci) || is_sb_gb_io(ctx, ci)) {
|
} else if (is_sb_io(ctx, ci) || is_sb_gb_io(ctx, ci)) {
|
||||||
|
Loading…
Reference in New Issue
Block a user