From cf5cbd1153c3ebaf6bcff98f2d936e1663407dff Mon Sep 17 00:00:00 2001 From: David Shah Date: Fri, 18 Oct 2019 15:58:57 +0100 Subject: [PATCH 01/81] ecp5: Preserve top level IO properly Signed-off-by: David Shah --- common/design_utils.cc | 12 +++++++++++- common/design_utils.h | 3 +++ ecp5/cells.cc | 20 +++++++++++++++++++- ecp5/pack.cc | 14 +++----------- 4 files changed, 36 insertions(+), 13 deletions(-) diff --git a/common/design_utils.cc b/common/design_utils.cc index bdf5ca5c..10212a03 100644 --- a/common/design_utils.cc +++ b/common/design_utils.cc @@ -88,7 +88,7 @@ void connect_port(const Context *ctx, NetInfo *net, CellInfo *cell, IdString por NPNR_ASSERT(net->driver.cell == nullptr); net->driver.cell = cell; net->driver.port = port_name; - } else if (port.type == PORT_IN) { + } else if (port.type == PORT_IN || port.type == PORT_INOUT) { PortRef user; user.cell = cell; 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; } +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 diff --git a/common/design_utils.h b/common/design_utils.h index 3eb9024f..1ae1d648 100644 --- a/common/design_utils.h +++ b/common/design_utils.h @@ -94,6 +94,9 @@ void connect_ports(Context *ctx, CellInfo *cell1, IdString port1_name, CellInfo // Rename a port if it exists on a cell 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); NEXTPNR_NAMESPACE_END diff --git a/ecp5/cells.cc b/ecp5/cells.cc index 37b6ac8b..b70b412d 100644 --- a/ecp5/cells.cc +++ b/ecp5/cells.cc @@ -416,7 +416,25 @@ void nxio_to_tr(Context *ctx, CellInfo *nxio, CellInfo *trio, std::vectorports.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) + rename_net(ctx, donet, ctx->id(donet->name.str(ctx) + "$TRELLIS_IO_OUT")); + if (dinet != nullptr) + 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 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( ctx, donet, [](const Context *ctx, const CellInfo *cell) { return cell->type == ctx->id("$_TBUF_"); }, ctx->id("Y")); diff --git a/ecp5/pack.cc b/ecp5/pack.cc index 244a79d5..00ed891a 100644 --- a/ecp5/pack.cc +++ b/ecp5/pack.cc @@ -362,8 +362,7 @@ class Ecp5Packer for (auto &port : ci->ports) disconnect_port(ctx, ci, port.first); } else if (trio != nullptr) { - // Trivial case, TRELLIS_IO used. Just destroy the net and the - // iobuf + // Trivial case, TRELLIS_IO used. Just remove the IOBUF 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)); @@ -384,14 +383,6 @@ class Ecp5Packer 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)) { 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)); trio = new_cells.back().get(); } - + for (auto port : ci->ports) + disconnect_port(ctx, ci, port.first); packed_cells.insert(ci->name); if (trio != nullptr) { for (const auto &attr : ci->attrs) From a22f86f86192bbd782cc2fd430b2900ddb6740cc Mon Sep 17 00:00:00 2001 From: David Shah Date: Fri, 18 Oct 2019 16:32:55 +0100 Subject: [PATCH 02/81] ice40: Preserve top level IO properly Signed-off-by: David Shah --- ecp5/cells.cc | 4 ++-- ice40/cells.cc | 20 +++++++++++++++++++- ice40/pack.cc | 16 +++------------- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/ecp5/cells.cc b/ecp5/cells.cc index b70b412d..9accdd55 100644 --- a/ecp5/cells.cc +++ b/ecp5/cells.cc @@ -419,9 +419,9 @@ void nxio_to_tr(Context *ctx, CellInfo *nxio, CellInfo *trio, std::vectorports.at(ctx->id("I")).net, *dinet = trio->ports.at(ctx->id("O")).net; // Rename I/O nets to avoid conflicts - if (donet != nullptr) + if (donet != nullptr && donet->name == nxio->name) rename_net(ctx, donet, ctx->id(donet->name.str(ctx) + "$TRELLIS_IO_OUT")); - if (dinet != nullptr) + 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 diff --git a/ice40/cells.cc b/ice40/cells.cc index c4e93d5b..3f1a0fbf 100644 --- a/ice40/cells.cc +++ b/ice40/cells.cc @@ -426,7 +426,25 @@ void nxio_to_sb(Context *ctx, CellInfo *nxio, CellInfo *sbio, std::unordered_set } else { 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 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( ctx, donet, [](const Context *ctx, const CellInfo *cell) { return cell->type == ctx->id("$_TBUF_"); }, ctx->id("Y")); diff --git a/ice40/pack.cc b/ice40/pack.cc index a1eba411..90c6de31 100644 --- a/ice40/pack.cc +++ b/ice40/pack.cc @@ -459,7 +459,6 @@ static void pack_io(Context *ctx) { std::unordered_set packed_cells; std::unordered_set delete_nets; - std::vector> new_cells; log_info("Packing IOs..\n"); @@ -478,8 +477,7 @@ static void pack_io(Context *ctx) rgb = net->driver.cell; } if (sb != nullptr) { - // Trivial case, SB_IO used. Just destroy the net and the - // iobuf + // Trivial case, SB_IO used. Just destroy the iobuf 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)); 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)); if (net != nullptr) { - if (net->clkconstr != nullptr) { if (sb->ports.count(id_D_IN_0)) { 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) { 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)); sb = new_cells.back().get(); } + for (auto port : ci->ports) + disconnect_port(ctx, ci, port.first); packed_cells.insert(ci->name); 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)) { From c0484a317d1a25a23541b60dd3bfc800230a0536 Mon Sep 17 00:00:00 2001 From: David Shah Date: Sat, 19 Oct 2019 16:08:11 +0100 Subject: [PATCH 03/81] sdf: Framework for writing out SDF files Signed-off-by: David Shah --- common/sdf.cc | 200 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 common/sdf.cc diff --git a/common/sdf.cc b/common/sdf.cc new file mode 100644 index 00000000..570b8aee --- /dev/null +++ b/common/sdf.cc @@ -0,0 +1,200 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2019 David Shah + * + * 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" + +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 iopaths; + std::vector checks; +}; + +struct CellPort +{ + std::string cell, port; +}; + +struct Interconnect +{ + CellPort from, to; + RiseFallDelay delay; +}; + +struct SDFWriter +{ + std::vector cells; + std::vector 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 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) + { + out << "(" << delay.min << ":" << delay.typ << ":" << delay.max << ")"; + } + + void write_port(std::ostream &out, const CellPort &port) { out << format_name(port.cell + "/" + port.port); } + + void write_portedge(std::ostream &out, const PortAndEdge &pe) + { + out << "(" << (pe.edge == RISING_EDGE ? "posedge" : "negedge") << " " << 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 /)" << std::endl; + out << " (TIMESCALE 1ps)" << std::endl; + // Write cells + for (auto &cell : cells) { + out << " (CELL" << std::endl; + out << " (CELLTYPE " << format_name(cell.celltype) << ")" << std::endl; + out << " (INSTANCE " << format_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 " << path.from << " " << 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; + } + // Write interconnect delays, with the main design begin a "cell" + out << " (CELL" << std::endl; + out << " (CELLTYPE " << format_name(design) << ")" << 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; + out << ")" << std::endl; + } +}; + +} // namespace SDF + +NEXTPNR_NAMESPACE_END \ No newline at end of file From 4775930e4943fef042b8a4c7e339fefeb1f251be Mon Sep 17 00:00:00 2001 From: David Shah Date: Sat, 19 Oct 2019 16:52:47 +0100 Subject: [PATCH 04/81] sdf: Add basic support for writing SDF files Signed-off-by: David Shah --- common/command.cc | 10 +++ common/nextpnr.h | 5 ++ common/sdf.cc | 161 ++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 155 insertions(+), 21 deletions(-) diff --git a/common/command.cc b/common/command.cc index ad5b6c54..f7f804a0 100644 --- a/common/command.cc +++ b/common/command.cc @@ -149,6 +149,8 @@ po::options_description CommandHandler::getGeneralOptions() general.add_options()("freq", po::value(), "set target frequency for design in MHz"); general.add_options()("timing-allow-fail", "allow timing to fail in design"); general.add_options()("no-tmdriv", "disable timing-driven placement"); + general.add_options()("sdf", po::value(), "SDF delay back-annotation file to write"); + return general; } @@ -336,6 +338,14 @@ int CommandHandler::executeMain(std::unique_ptr ctx) log_error("Saving design failed.\n"); } + if (vm.count("sdf")) { + std::string filename = vm["sdf"].as(); + std::ofstream f(filename); + if (!f) + log_error("Failed to open SDF file '%s' for writing.\n", filename.c_str()); + ctx->writeSDF(f); + } + #ifndef NO_PYTHON deinit_python(); #endif diff --git a/common/nextpnr.h b/common/nextpnr.h index bae828f6..89e9c4f0 100644 --- a/common/nextpnr.h +++ b/common/nextpnr.h @@ -808,6 +808,11 @@ struct Context : Arch, DeterministicRNG // -------------------------------------------------------------- + // provided by sdf.cc + void writeSDF(std::ostream &out) const; + + // -------------------------------------------------------------- + uint32_t checksum() const; void check() const; diff --git a/common/sdf.cc b/common/sdf.cc index 570b8aee..07773a6a 100644 --- a/common/sdf.cc +++ b/common/sdf.cc @@ -18,6 +18,7 @@ */ #include "nextpnr.h" +#include "util.h" NEXTPNR_NAMESPACE_BEGIN @@ -93,6 +94,17 @@ struct SDFWriter return fmt; } + std::string escape_name(const std::string &name) + { + std::string esc; + for (char c : name) { + if (c == '$' || c == '\\' || c == '[' || c == ']' || c == ':') + esc += '\\'; + esc += c; + } + return esc; + } + std::string timing_check_name(TimingCheck::CheckType type) { switch (type) { @@ -119,11 +131,11 @@ struct SDFWriter out << "(" << delay.min << ":" << delay.typ << ":" << delay.max << ")"; } - void write_port(std::ostream &out, const CellPort &port) { out << format_name(port.cell + "/" + port.port); } + void write_port(std::ostream &out, const CellPort &port) { out << escape_name(port.cell + "/" + port.port); } void write_portedge(std::ostream &out, const PortAndEdge &pe) { - out << "(" << (pe.edge == RISING_EDGE ? "posedge" : "negedge") << " " << pe.port << ")"; + out << "(" << (pe.edge == RISING_EDGE ? "posedge" : "negedge") << " " << escape_name(pe.port) << ")"; } void write(std::ostream &out) @@ -136,17 +148,35 @@ struct SDFWriter out << " (PROGRAM " << format_name(program) << ")" << std::endl; out << " (DIVIDER /)" << 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 " << format_name(cell.instance) << ")" << 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 " << path.from << " " << path.to << " "; + out << " (IOPATH " << escape_name(path.from) << " " << escape_name(path.to) << " "; write_delay(out, path.delay); out << ")" << std::endl; } @@ -174,27 +204,116 @@ struct SDFWriter } out << " )" << std::endl; } - // Write interconnect delays, with the main design begin a "cell" - out << " (CELL" << std::endl; - out << " (CELLTYPE " << format_name(design) << ")" << 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; out << ")" << std::endl; } }; } // namespace SDF +void Context::writeSDF(std::ostream &out) const +{ + using namespace SDF; + SDFWriter wr; + 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.type != PORT_IN) { + // Add combinational paths to this output (or inout) + for (auto other : ci->ports) { + 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 \ No newline at end of file From b582ba810cc54282d4c574bee80a7339c8da17f9 Mon Sep 17 00:00:00 2001 From: David Shah Date: Sun, 20 Oct 2019 10:29:07 +0100 Subject: [PATCH 05/81] ecp5: Make database build depend on constids.inc Signed-off-by: David Shah --- ecp5/family.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ecp5/family.cmake b/ecp5/family.cmake index 5512e7dd..2d8dcfcc 100644 --- a/ecp5/family.cmake +++ b/ecp5/family.cmake @@ -48,7 +48,7 @@ if (NOT EXTERNAL_CHIPDB) else() add_custom_command(OUTPUT ${DEV_CC_BBA_DB} COMMAND ${ENV_CMD} ${PYTHON_EXECUTABLE} ${DB_PY} -p ${DEV_CONSTIDS_INC} ${dev} > ${DEV_CC_BBA_DB} - DEPENDS ${DB_PY} ${PREV_DEV_CC_BBA_DB} + DEPENDS ${DB_PY} ${DEV_CONSTIDS_INC} ${PREV_DEV_CC_BBA_DB} ) add_custom_command(OUTPUT ${DEV_CC_DB} COMMAND bbasm ${BBASM_ENDIAN_FLAG} ${DEV_CC_BBA_DB} ${DEV_CC_DB} @@ -80,7 +80,7 @@ if (NOT EXTERNAL_CHIPDB) add_custom_command(OUTPUT ${DEV_CC_BBA_DB} COMMAND ${ENV_CMD} ${PYTHON_EXECUTABLE} ${DB_PY} -p ${DEV_CONSTIDS_INC} ${dev} > ${DEV_CC_BBA_DB}.new COMMAND mv ${DEV_CC_BBA_DB}.new ${DEV_CC_BBA_DB} - DEPENDS ${DB_PY} ${PREV_DEV_CC_BBA_DB} + DEPENDS ${DB_PY} ${DEV_CONSTIDS_INC} ${PREV_DEV_CC_BBA_DB} ) add_custom_command(OUTPUT ${DEV_CC_DB} COMMAND bbasm --c ${BBASM_ENDIAN_FLAG} ${DEV_CC_BBA_DB} ${DEV_CC_DB}.new From 5b993820025b69a91a3547cbf8dfb4c2f1c69cfc Mon Sep 17 00:00:00 2001 From: Sean Cross Date: Thu, 24 Oct 2019 13:35:38 +0800 Subject: [PATCH 06/81] ice40: cmake: fix build with pregenerated bba path When building using non-pregenerated bba files, the rule to create bbasm files gets called twice: once unconditionally, and once as part of the conditional that determines we're not using a pregenerated bba path. If we _are_ using a pregenerated bba path, then this rule gets called anyway, resulting in a build error. Remove the duplicate, unconditional creation of the bba file generation, to fix the build when using pregenerated files, and to speed up the build when not using pregenerated files. Signed-off-by: Sean Cross --- ice40/family.cmake | 8 -------- 1 file changed, 8 deletions(-) diff --git a/ice40/family.cmake b/ice40/family.cmake index f22883fb..e31fce2d 100644 --- a/ice40/family.cmake +++ b/ice40/family.cmake @@ -40,14 +40,6 @@ if (NOT EXTERNAL_CHIPDB) set(DEV_CC_DB ${CMAKE_CURRENT_BINARY_DIR}/ice40/chipdbs/chipdb-${dev}.bin) set(DEV_CONSTIDS_INC ${CMAKE_CURRENT_SOURCE_DIR}/ice40/constids.inc) set(DEV_GFXH ${CMAKE_CURRENT_SOURCE_DIR}/ice40/gfx.h) - add_custom_command(OUTPUT ${DEV_CC_BBA_DB} - COMMAND ${PYTHON_EXECUTABLE} ${DB_PY} -p ${DEV_CONSTIDS_INC} -g ${DEV_GFXH} ${OPT_FAST} ${OPT_SLOW} ${DEV_TXT_DB} > ${DEV_CC_BBA_DB} - DEPENDS ${DEV_CONSTIDS_INC} ${DEV_GFXH} ${DEV_TXT_DB} ${DB_PY} ${PREV_DEV_CC_BBA_DB} - ) - add_custom_command(OUTPUT ${DEV_CC_DB} - COMMAND bbasm ${BBASM_ENDIAN_FLAG} ${DEV_CC_BBA_DB} ${DEV_CC_DB} - DEPENDS bbasm ${DEV_CC_BBA_DB} - ) if(PREGENERATED_BBA_PATH) add_custom_command(OUTPUT ${DEV_CC_DB} From 8343488bdf6ef21844913bd1043e27541b0573c1 Mon Sep 17 00:00:00 2001 From: David Shah Date: Thu, 24 Oct 2019 10:43:18 +0100 Subject: [PATCH 07/81] sdf: Improve SDF output Signed-off-by: David Shah --- common/sdf.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/common/sdf.cc b/common/sdf.cc index 07773a6a..769728e4 100644 --- a/common/sdf.cc +++ b/common/sdf.cc @@ -253,9 +253,13 @@ void Context::writeSDF(std::ostream &out) const 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; From f2b9cc6d2311c437d740873111e806e9e5078736 Mon Sep 17 00:00:00 2001 From: David Shah Date: Thu, 24 Oct 2019 12:37:07 +0100 Subject: [PATCH 08/81] sdf: Working on support for CVC Signed-off-by: David Shah --- common/command.cc | 3 ++- common/nextpnr.h | 2 +- common/sdf.cc | 21 ++++++++++++++++----- ice40/cells.cc | 2 +- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/common/command.cc b/common/command.cc index f7f804a0..fd310789 100644 --- a/common/command.cc +++ b/common/command.cc @@ -150,6 +150,7 @@ po::options_description CommandHandler::getGeneralOptions() general.add_options()("timing-allow-fail", "allow timing to fail in design"); general.add_options()("no-tmdriv", "disable timing-driven placement"); general.add_options()("sdf", po::value(), "SDF delay back-annotation file to write"); + general.add_options()("sdf-cvc", "enable tweaks for SDF file compatibility with the CVC simulator"); return general; } @@ -343,7 +344,7 @@ int CommandHandler::executeMain(std::unique_ptr ctx) std::ofstream f(filename); if (!f) log_error("Failed to open SDF file '%s' for writing.\n", filename.c_str()); - ctx->writeSDF(f); + ctx->writeSDF(f, vm.count("sdf-cvc")); } #ifndef NO_PYTHON diff --git a/common/nextpnr.h b/common/nextpnr.h index 89e9c4f0..24f6948b 100644 --- a/common/nextpnr.h +++ b/common/nextpnr.h @@ -809,7 +809,7 @@ struct Context : Arch, DeterministicRNG // -------------------------------------------------------------- // provided by sdf.cc - void writeSDF(std::ostream &out) const; + void writeSDF(std::ostream &out, bool cvc_mode = false) const; // -------------------------------------------------------------- diff --git a/common/sdf.cc b/common/sdf.cc index 769728e4..b9606907 100644 --- a/common/sdf.cc +++ b/common/sdf.cc @@ -78,6 +78,7 @@ struct Interconnect struct SDFWriter { + bool cvc_mode = false; std::vector cells; std::vector conn; std::string sdfversion, design, vendor, program; @@ -98,7 +99,7 @@ struct SDFWriter { std::string esc; for (char c : name) { - if (c == '$' || c == '\\' || c == '[' || c == ']' || c == ':') + if (c == '$' || c == '\\' || c == '[' || c == ']' || c == ':' || (cvc_mode && c == '.')) esc += '\\'; esc += c; } @@ -128,10 +129,19 @@ struct SDFWriter void write_delay(std::ostream &out, const MinMaxTyp &delay) { - out << "(" << delay.min << ":" << delay.typ << ":" << delay.max << ")"; + 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) { out << escape_name(port.cell + "/" + port.port); } + 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) { @@ -146,7 +156,7 @@ struct SDFWriter out << " (DESIGN " << format_name(design) << ")" << std::endl; out << " (VENDOR " << format_name(vendor) << ")" << std::endl; out << " (PROGRAM " << format_name(program) << ")" << std::endl; - out << " (DIVIDER /)" << 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; @@ -210,10 +220,11 @@ struct SDFWriter } // namespace SDF -void Context::writeSDF(std::ostream &out) const +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"; diff --git a/ice40/cells.cc b/ice40/cells.cc index 3f1a0fbf..3def82bf 100644 --- a/ice40/cells.cc +++ b/ice40/cells.cc @@ -69,7 +69,7 @@ std::unique_ptr create_ice_cell(Context *ctx, IdString type, std::stri new_cell->params[ctx->id("PIN_TYPE")] = Property(0, 6); new_cell->params[ctx->id("PULLUP")] = 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); From 36c07a0f45b13e4cf34e6db3b73ccf864af522f0 Mon Sep 17 00:00:00 2001 From: David Shah Date: Fri, 25 Oct 2019 09:37:13 +0100 Subject: [PATCH 09/81] ecp5: Fix routing to shared DSP control inputs Signed-off-by: David Shah --- ecp5/arch.cc | 9 ++++++++- ecp5/arch.h | 5 +++++ ecp5/arch_place.cc | 24 ++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/ecp5/arch.cc b/ecp5/arch.cc index 8ce0653c..5f62d5ae 100644 --- a/ecp5/arch.cc +++ b/ecp5/arch.cc @@ -477,7 +477,13 @@ delay_t Arch::estimateDelay(WireId src, WireId dst) const } }; - auto src_loc = est_location(src), dst_loc = est_location(dst); + auto src_loc = est_location(src); + std::pair dst_loc; + if (wire_loc_overrides.count(dst)) { + dst_loc = wire_loc_overrides.at(dst); + } else { + dst_loc = est_location(dst); + } int dx = abs(src_loc.first - dst_loc.first), dy = abs(src_loc.second - dst_loc.second); @@ -562,6 +568,7 @@ bool Arch::place() bool Arch::route() { + setupWireLocations(); route_ecp5_globals(getCtx()); assignArchInfo(); assign_budget(getCtx(), true); diff --git a/ecp5/arch.h b/ecp5/arch.h index a479abb6..444dfa07 100644 --- a/ecp5/arch.h +++ b/ecp5/arch.h @@ -1048,6 +1048,11 @@ struct Arch : BaseCtx // Special case for delay estimates due to its physical location // being far from the logical location of its primitive WireId gsrclk_wire; + // Improves directivity of routing to DSP inputs, avoids issues + // with different routes to the same physical reset wire causing + // conflicts and slow routing + std::unordered_map> wire_loc_overrides; + void setupWireLocations(); mutable std::unordered_map> celldelay_cache; diff --git a/ecp5/arch_place.cc b/ecp5/arch_place.cc index d5c345af..6057605b 100644 --- a/ecp5/arch_place.cc +++ b/ecp5/arch_place.cc @@ -196,4 +196,28 @@ void Arch::permute_luts() } } +void Arch::setupWireLocations() +{ + wire_loc_overrides.clear(); + for (auto cell : sorted(cells)) { + CellInfo *ci = cell.second; + if (ci->bel == BelId()) + continue; + if (ci->type == id_MULT18X18D || ci->type == id_DCUA) { + for (auto &port : ci->ports) { + if (port.second.type != PORT_IN || port.second.net == nullptr) + continue; + WireId pw = getBelPinWire(ci->bel, port.first); + if (pw == WireId()) + continue; + for (auto uh : getPipsUphill(pw)) { + WireId pip_src = getPipSrcWire(uh); + wire_loc_overrides[pw] = std::make_pair(pip_src.location.x, pip_src.location.y); + break; + } + } + } + } +} + NEXTPNR_NAMESPACE_END From 475fcd442520a1464ce6c3b1aecbea03aa158007 Mon Sep 17 00:00:00 2001 From: David Shah Date: Sat, 26 Oct 2019 20:38:28 +0100 Subject: [PATCH 10/81] ecp5: Add an error for out-of-sync constids and bba Signed-off-by: David Shah --- ecp5/arch.cc | 3 +++ ecp5/arch.h | 1 + ecp5/archdefs.h | 2 ++ ecp5/trellis_import.py | 6 ++++-- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/ecp5/arch.cc b/ecp5/arch.cc index 5f62d5ae..d931c5b2 100644 --- a/ecp5/arch.cc +++ b/ecp5/arch.cc @@ -117,6 +117,9 @@ Arch::Arch(ArchArgs args) : args(args) log_error("Unsupported ECP5 chip type.\n"); } #endif + if (chip_info->const_id_count != DB_CONST_ID_COUNT) + log_error("Chip database 'bba' and nextpnr code are out of sync; please rebuild (or contact distribution " + "maintainer)!\n"); package_info = nullptr; for (int i = 0; i < chip_info->num_packages; i++) { if (args.package == chip_info->package_info[i].name.get()) { diff --git a/ecp5/arch.h b/ecp5/arch.h index 444dfa07..3df2d84f 100644 --- a/ecp5/arch.h +++ b/ecp5/arch.h @@ -194,6 +194,7 @@ NPNR_PACKED_STRUCT(struct ChipInfoPOD { int32_t num_tiles; int32_t num_location_types; int32_t num_packages, num_pios; + int32_t const_id_count; RelPtr locations; RelPtr location_type; RelPtr location_glbinfo; diff --git a/ecp5/archdefs.h b/ecp5/archdefs.h index da12eeaa..3cd92119 100644 --- a/ecp5/archdefs.h +++ b/ecp5/archdefs.h @@ -58,6 +58,8 @@ enum ConstIds #define X(t) , ID_##t #include "constids.inc" #undef X + , + DB_CONST_ID_COUNT }; #define X(t) static constexpr auto id_##t = IdString(ID_##t); diff --git a/ecp5/trellis_import.py b/ecp5/trellis_import.py index 610bd331..549b3080 100755 --- a/ecp5/trellis_import.py +++ b/ecp5/trellis_import.py @@ -441,6 +441,7 @@ def write_database(dev_name, chip, ddrg, endianness): bba.u32(len(location_types), "num_location_types") bba.u32(len(packages), "num_packages") bba.u32(len(pindata), "num_pios") + bba.u32(const_id_count, "const_id_count") bba.r("locations", "locations") bba.r("location_types", "location_type") @@ -457,11 +458,12 @@ def write_database(dev_name, chip, ddrg, endianness): dev_names = {"25k": "LFE5UM5G-25F", "45k": "LFE5UM5G-45F", "85k": "LFE5UM5G-85F"} def main(): - global max_row, max_col + global max_row, max_col, const_id_count pytrellis.load_database(database.get_db_root()) args = parser.parse_args() # Read port pin file + const_id_count = 1 # count ID_NONE with open(args.constids) as f: for line in f: line = line.replace("(", " ") @@ -473,7 +475,7 @@ def main(): assert line[0] == "X" idx = len(constids) + 1 constids[line[1]] = idx - + const_id_count += 1 constids["SLICE"] = constids["TRELLIS_SLICE"] constids["PIO"] = constids["TRELLIS_IO"] From bac833522239e75f9fd27ba4bc13bf44586fe162 Mon Sep 17 00:00:00 2001 From: David Shah Date: Sat, 26 Oct 2019 20:50:50 +0100 Subject: [PATCH 11/81] ecp5: Add constids for new timing cell types Signed-off-by: David Shah --- ecp5/constids.inc | 8 ++++++++ ecp5/trellis_import.py | 2 ++ 2 files changed, 10 insertions(+) diff --git a/ecp5/constids.inc b/ecp5/constids.inc index 5e8fc7da..4b5e3a3a 100644 --- a/ecp5/constids.inc +++ b/ecp5/constids.inc @@ -1294,3 +1294,11 @@ X(P) X(ECLKBRIDGECS) X(SEL) X(ECSOUT) + +X(IOLOGIC_MODE_IDDRX1F) +X(IOLOGIC_MODE_IDDRX2F) +X(IOLOGIC_MODE_IREG) +X(IOLOGIC_MODE_ODDRX1F) +X(IOLOGIC_MODE_ODDRX2F) +X(IOLOGIC_MODE_OREG) +X(IOLOGIC_MODE_TSREG) diff --git a/ecp5/trellis_import.py b/ecp5/trellis_import.py index 549b3080..ee960fd3 100755 --- a/ecp5/trellis_import.py +++ b/ecp5/trellis_import.py @@ -186,6 +186,8 @@ def process_timing_data(): max_delay = min(entry["rising"][2], entry["falling"][2]) delays.append((constids[from_pin], constids[to_pin], min_delay, max_delay)) elif entry["type"] == "SetupHold": + if type(entry["pin"]) is list: + continue pin = constids[entry["pin"]] clock = constids[entry["clock"][1]] min_setup = entry["setup"][0] From 5cf0ed5edead8aa4060978f7b22e598ab03ef1b9 Mon Sep 17 00:00:00 2001 From: David Shah Date: Sat, 26 Oct 2019 22:21:18 +0100 Subject: [PATCH 12/81] ecp5: Allow setting drive strength for 3V3 IOs Signed-off-by: David Shah --- ecp5/bitstream.cc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ecp5/bitstream.cc b/ecp5/bitstream.cc index acab95dd..9732cd52 100644 --- a/ecp5/bitstream.cc +++ b/ecp5/bitstream.cc @@ -865,6 +865,16 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex if (ci->attrs.count(ctx->id("DIFFRESISTOR"))) cc.tiles[pio_tile].add_enum(pio + ".DIFFRESISTOR", str_or_default(ci->attrs, ctx->id("DIFFRESISTOR"), "OFF")); + if (ci->attrs.count(ctx->id("DRIVE"))) { + static bool drive_3v3_warning_done = false; + if (iotype == "LVCMOS33") { + cc.tiles[pio_tile].add_enum(pio + ".DRIVE", str_or_default(ci->attrs, ctx->id("DRIVE"), "8")); + } else { + if (!drive_3v3_warning_done) + log_warning("Trellis limitation: DRIVE can only be set on 3V3 IO pins.\n"); + drive_3v3_warning_done = true; + } + } if (ci->attrs.count(ctx->id("TERMINATION"))) { auto vccio = get_vccio(ioType_from_str(iotype)); switch (vccio) { From 58b7cb920ff80f85a0e28df59d84977804f8d67d Mon Sep 17 00:00:00 2001 From: David Shah Date: Fri, 1 Nov 2019 16:02:23 +0000 Subject: [PATCH 13/81] ecp5: Fix placement of ECLKBRIDGECS Signed-off-by: David Shah --- ecp5/pack.cc | 52 +++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/ecp5/pack.cc b/ecp5/pack.cc index 244a79d5..8ba3d651 100644 --- a/ecp5/pack.cc +++ b/ecp5/pack.cc @@ -2353,6 +2353,7 @@ class Ecp5Packer for (auto cell : sorted(ctx->cells)) { CellInfo *ci = cell.second; if (ci->type == id_ECLKBRIDGECS) { + Loc loc; NetInfo *i0 = get_net_or_empty(ci, id_CLK0), *i1 = get_net_or_empty(ci, id_CLK1), *o = get_net_or_empty(ci, id_ECSOUT); for (NetInfo *input : {i0, i1}) { @@ -2366,24 +2367,53 @@ class Ecp5Packer for (auto bel : ctx->getBels()) { if (ctx->getBelType(bel) != id_ECLKBRIDGECS) continue; - Loc loc = ctx->getBelLocation(bel); + loc = ctx->getBelLocation(bel); if (loc.x == user_loc.x) { ci->attrs[ctx->id("BEL")] = ctx->getBelName(bel).str(ctx); - if (o != nullptr) - for (auto user2 : o->users) { - // Set side hint to ensure edge clock choice is routeable - if (user2.cell->type == id_ECLKSYNCB && user2.port == id_ECLKI) { - NetInfo *synco = get_net_or_empty(user2.cell, id_ECLKO); - if (synco != nullptr) - bridge_side_hint[synco] = (loc.x > 1) ? 0 : 1; - } - } goto eclkbridge_done; } } } + if (input->driver.cell != nullptr) { + CellInfo *drv = input->driver.cell; + if (!drv->attrs.count(ctx->id("BEL"))) + continue; + Loc drv_loc = ctx->getBelLocation( + ctx->getBelByName(ctx->id(drv->attrs.at(ctx->id("BEL")).as_string()))); + BelId closest; + int closest_x = -1; // aim for same side of chip + for (auto bel : ctx->getBels()) { + if (ctx->getBelType(bel) != id_ECLKBRIDGECS) + continue; + loc = ctx->getBelLocation(bel); + if (closest_x == -1 || std::abs(loc.x - drv_loc.x) < std::abs(closest_x - drv_loc.x)) { + closest_x = loc.x; + closest = bel; + } + } + NPNR_ASSERT(closest != BelId()); + loc = ctx->getBelLocation(closest); + ci->attrs[ctx->id("BEL")] = ctx->getBelName(closest).str(ctx); + goto eclkbridge_done; + } + } + // If all else fails, place randomly + for (auto bel : ctx->getBels()) { + if (ctx->getBelType(bel) != id_ECLKBRIDGECS) + continue; + loc = ctx->getBelLocation(bel); + ci->attrs[ctx->id("BEL")] = ctx->getBelName(bel).str(ctx); } eclkbridge_done: + if (o != nullptr) + for (auto user2 : o->users) { + // Set side hint to ensure edge clock choice is routeable + if (user2.cell->type == id_ECLKSYNCB && user2.port == id_ECLKI) { + NetInfo *synco = get_net_or_empty(user2.cell, id_ECLKO); + if (synco != nullptr) + bridge_side_hint[synco] = (loc.x > 1) ? 0 : 1; + } + } continue; } } @@ -2659,12 +2689,12 @@ class Ecp5Packer prepack_checks(); pack_io(); pack_dqsbuf(); + preplace_plls(); pack_iologic(); pack_ebr(); pack_dsps(); pack_dcus(); pack_misc(); - preplace_plls(); pack_constants(); pack_dram(); pack_carries(); From 21c09c8b8f5e0456cc4d7e8017c27d25dd676377 Mon Sep 17 00:00:00 2001 From: David Shah Date: Fri, 1 Nov 2019 16:27:51 +0000 Subject: [PATCH 14/81] ecp5: Copy timing constraints across ECLKBRIDGECS Signed-off-by: David Shah --- ecp5/pack.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ecp5/pack.cc b/ecp5/pack.cc index 8ba3d651..166ed180 100644 --- a/ecp5/pack.cc +++ b/ecp5/pack.cc @@ -2613,7 +2613,7 @@ class Ecp5Packer std::unordered_set changed_cells; for (auto net : changed_nets) for (auto &user : ctx->nets.at(net)->users) - if (user.port == id_CLKI || user.port == id_ECLKI) + if (user.port == id_CLKI || user.port == id_ECLKI || user.port == id_CLK0 || user.port == id_CLK1) changed_cells.insert(user.cell->name); changed_nets.clear(); for (auto cell : sorted(changed_cells)) { @@ -2630,6 +2630,9 @@ class Ecp5Packer copy_constraint(ci, id_CLKI, id_CDIVX, ratio); } else if (ci->type == id_ECLKSYNCB || ci->type == id_TRELLIS_ECLKBUF) { copy_constraint(ci, id_ECLKI, id_ECLKO, 1); + } else if (ci->type == id_ECLKBRIDGECS) { + copy_constraint(ci, id_CLK0, id_ECSOUT, 1); + copy_constraint(ci, id_CLK1, id_ECSOUT, 1); } else if (ci->type == id_DCCA) { copy_constraint(ci, id_CLKI, id_CLKO, 1); } else if (ci->type == id_EHXPLLL) { From caf7abdb8dd7ec97801fd13efc8ae132e637ecd3 Mon Sep 17 00:00:00 2001 From: David Shah Date: Wed, 6 Nov 2019 13:51:05 +0000 Subject: [PATCH 15/81] cmake: Add boost system library Signed-off-by: David Shah --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7bdff611..9af4bb6c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -97,7 +97,7 @@ endif() find_package(Sanitizers) # List of Boost libraries to include -set(boost_libs filesystem thread program_options iostreams) +set(boost_libs filesystem thread program_options iostreams system) if (BUILD_GUI AND NOT BUILD_PYTHON) message(FATAL_ERROR "GUI requires Python to build") From 7c362f292c0807b1e781922c79af94fd8b82477c Mon Sep 17 00:00:00 2001 From: Pepijn de Vos Date: Fri, 8 Nov 2019 15:54:27 +0100 Subject: [PATCH 16/81] dedicated output for LUT in GENERIC_SLICE --- generic/cells.cc | 8 +++----- generic/examples/bitstream.py | 3 +-- generic/examples/simple.py | 7 ++++++- generic/examples/simple.sh | 3 ++- generic/examples/simple_timing.py | 26 ++++++++++++-------------- generic/synth/prims.v | 16 +++++++--------- 6 files changed, 31 insertions(+), 32 deletions(-) diff --git a/generic/cells.cc b/generic/cells.cc index 53886e33..3b754406 100644 --- a/generic/cells.cc +++ b/generic/cells.cc @@ -44,13 +44,13 @@ std::unique_ptr create_generic_cell(Context *ctx, IdString type, std:: if (type == ctx->id("GENERIC_SLICE")) { new_cell->params[ctx->id("K")] = std::to_string(ctx->args.K); new_cell->params[ctx->id("INIT")] = 0; - new_cell->params[ctx->id("FF_USED")] = 0; for (int i = 0; i < ctx->args.K; i++) add_port(ctx, new_cell.get(), "I[" + std::to_string(i) + "]", PORT_IN); add_port(ctx, new_cell.get(), "CLK", PORT_IN); + add_port(ctx, new_cell.get(), "F", PORT_OUT); add_port(ctx, new_cell.get(), "Q", PORT_OUT); } else if (type == ctx->id("GENERIC_IOB")) { new_cell->params[ctx->id("INPUT_USED")] = 0; @@ -80,18 +80,16 @@ void lut_to_lc(const Context *ctx, CellInfo *lut, CellInfo *lc, bool no_dff) } if (no_dff) { - replace_port(lut, ctx->id("Q"), lc, ctx->id("Q")); - lc->params[ctx->id("FF_USED")] = 0; + replace_port(lut, ctx->id("Q"), lc, ctx->id("F")); } } void dff_to_lc(const Context *ctx, CellInfo *dff, CellInfo *lc, bool pass_thru_lut) { - lc->params[ctx->id("FF_USED")] = 1; replace_port(dff, ctx->id("CLK"), lc, ctx->id("CLK")); if (pass_thru_lut) { - lc->params[ctx->id("INIT")] = 2; + lc->params[ctx->id("INIT")] = 0xAAAA; replace_port(dff, ctx->id("D"), lc, ctx->id("I[0]")); } diff --git a/generic/examples/bitstream.py b/generic/examples/bitstream.py index 1ab94f0c..6cd63a58 100644 --- a/generic/examples/bitstream.py +++ b/generic/examples/bitstream.py @@ -6,7 +6,6 @@ from simple_config import K param_map = { ("GENERIC_SLICE", "K"): ParameterConfig(write=False), ("GENERIC_SLICE", "INIT"): ParameterConfig(write=True, numeric=True, width=2**K), - ("GENERIC_SLICE", "FF_USED"): ParameterConfig(write=True, numeric=True, width=1), ("GENERIC_IOB", "INPUT_USED"): ParameterConfig(write=True, numeric=True, width=1), ("GENERIC_IOB", "OUTPUT_USED"): ParameterConfig(write=True, numeric=True, width=1), @@ -14,4 +13,4 @@ param_map = { } with open("blinky.fasm", "w") as f: - write_fasm(ctx, param_map, f) \ No newline at end of file + write_fasm(ctx, param_map, f) diff --git a/generic/examples/simple.py b/generic/examples/simple.py index 9339b68a..9379b505 100644 --- a/generic/examples/simple.py +++ b/generic/examples/simple.py @@ -9,6 +9,7 @@ for x in range(X): for z in range(N): ctx.addWire(name="X%dY%dZ%d_CLK" % (x, y, z), type="BEL_CLK", x=x, y=y) ctx.addWire(name="X%dY%dZ%d_Q" % (x, y, z), type="BEL_Q", x=x, y=y) + ctx.addWire(name="X%dY%dZ%d_F" % (x, y, z), type="BEL_F", x=x, y=y) for i in range(K): ctx.addWire(name="X%dY%dZ%d_I%d" % (x, y, z, i), type="BEL_I", x=x, y=y) # Local wires @@ -29,6 +30,7 @@ for x in range(X): ctx.addBelInput(bel="X%dY%d_SLICE%d" % (x, y, z), name="CLK", wire="X%dY%dZ%d_CLK" % (x, y, z)) for k in range(K): ctx.addBelInput(bel="X%dY%d_SLICE%d" % (x, y, z), name="I[%d]" % k, wire="X%dY%dZ%d_I%d" % (x, y, z, k)) + ctx.addBelOutput(bel="X%dY%d_SLICE%d" % (x, y, z), name="F", wire="X%dY%dZ%d_F" % (x, y, z)) ctx.addBelOutput(bel="X%dY%d_SLICE%d" % (x, y, z), name="Q", wire="X%dY%dZ%d_Q" % (x, y, z)) for x in range(X): @@ -48,6 +50,9 @@ for x in range(X): # Pips from bel outputs to locals def create_output_pips(dst, offset, skip): for i in range(offset % skip, N, skip): + src = "X%dY%dZ%d_F" % (x, y, i) + ctx.addPip(name="X%dY%d.%s.%s" % (x, y, src, dst), type="BEL_OUTPUT", + srcWire=src, dstWire=dst, delay=ctx.getDelayFromNS(0.05), loc=Loc(x, y, 0)) src = "X%dY%dZ%d_Q" % (x, y, i) ctx.addPip(name="X%dY%d.%s.%s" % (x, y, src, dst), type="BEL_OUTPUT", srcWire=src, dstWire=dst, delay=ctx.getDelayFromNS(0.05), loc=Loc(x, y, 0)) @@ -69,4 +74,4 @@ for x in range(X): create_neighbour_pips(dst, x, y+1, (l + 4) % Sl, Sl) create_neighbour_pips(dst, x+1, y-1, (l + 5) % Sl, Sl) create_neighbour_pips(dst, x+1, y, (l + 6) % Sl, Sl) - create_neighbour_pips(dst, x+1, y+1, (l + 7) % Sl, Sl) \ No newline at end of file + create_neighbour_pips(dst, x+1, y+1, (l + 7) % Sl, Sl) diff --git a/generic/examples/simple.sh b/generic/examples/simple.sh index 8ae903f9..76bc6168 100755 --- a/generic/examples/simple.sh +++ b/generic/examples/simple.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash set -ex yosys -p "tcl ../synth/synth_generic.tcl 4 blinky.json" blinky.v -${NEXTPNR:-../../nextpnr-generic} --pre-pack simple.py --pre-place simple_timing.py --json blinky.json --post-route bitstream.py +${NEXTPNR:-../../nextpnr-generic} --pre-pack simple.py --pre-place simple_timing.py --json blinky.json --post-route bitstream.py --write pnrblinky.json +yosys -p "read_verilog -lib ../synth/prims.v; read_json pnrblinky.json; dump -o blinky.il; show -format png -prefix blinky" diff --git a/generic/examples/simple_timing.py b/generic/examples/simple_timing.py index 2ccb197e..1067b556 100644 --- a/generic/examples/simple_timing.py +++ b/generic/examples/simple_timing.py @@ -1,15 +1,13 @@ for cname, cell in ctx.cells: - if cell.type != "GENERIC_SLICE": - continue - if cname in ("$PACKER_GND", "$PACKER_VCC"): - continue - K = int(cell.params["K"]) - if int(cell.params["FF_USED"], 2) == 1: - ctx.addCellTimingClock(cell=cname, port="CLK") - for i in range(K): - ctx.addCellTimingSetupHold(cell=cname, port="I[%d]" % i, clock="CLK", - setup=ctx.getDelayFromNS(0.2), hold=ctx.getDelayFromNS(0)) - ctx.addCellTimingClockToOut(cell=cname, port="Q", clock="CLK", clktoq=ctx.getDelayFromNS(0.2)) - else: - for i in range(K): - ctx.addCellTimingDelay(cell=cname, fromPort="I[%d]" % i, toPort="Q", delay=ctx.getDelayFromNS(0.2)) \ No newline at end of file + if cell.type != "GENERIC_SLICE": + continue + if cname in ("$PACKER_GND", "$PACKER_VCC"): + continue + K = int(cell.params["K"]) + ctx.addCellTimingClock(cell=cname, port="CLK") + for i in range(K): + ctx.addCellTimingSetupHold(cell=cname, port="I[%d]" % i, clock="CLK", + setup=ctx.getDelayFromNS(0.2), hold=ctx.getDelayFromNS(0)) + ctx.addCellTimingClockToOut(cell=cname, port="Q", clock="CLK", clktoq=ctx.getDelayFromNS(0.2)) + for i in range(K): + ctx.addCellTimingDelay(cell=cname, fromPort="I[%d]" % i, toPort="F", delay=ctx.getDelayFromNS(0.2)) diff --git a/generic/synth/prims.v b/generic/synth/prims.v index 95fcfac7..47a5df0f 100644 --- a/generic/synth/prims.v +++ b/generic/synth/prims.v @@ -21,21 +21,19 @@ endmodule module GENERIC_SLICE #( parameter K = 4, parameter [2**K-1:0] INIT = 0, - parameter FF_USED = 1'b0 ) ( input CLK, input [K-1:0] I, + output F, output Q ); + wire f_wire; - wire lut_q; - LUT #(.K(K), .INIT(INIT)) lut_i(.I(I), .Q(lut_q)); + LUT #(.K(K), .INIT(INIT)) lut_i(.I(I), .Q(f_wire)); - generate if (FF_USED) - DFF dff_i(.CLK(CLK), .D(lut_q), .Q(Q)); - else - assign Q = lut_q; - endgenerate + DFF dff_i(.CLK(CLK), .D(f_wire), .Q(Q)); + + assign F = f_wire; endmodule module GENERIC_IOB #( @@ -56,4 +54,4 @@ module GENERIC_IOB #( generate if (INPUT_USED) assign O = PAD; endgenerate -endmodule \ No newline at end of file +endmodule From 5dd1e5e51e65d1efdd2f84c10a2b751e3228b982 Mon Sep 17 00:00:00 2001 From: Pepijn de Vos Date: Fri, 8 Nov 2019 17:15:12 +0100 Subject: [PATCH 17/81] return FF_USED, formatting, correct INIT --- .gitignore | 3 +++ generic/cells.cc | 14 ++++++++++++-- generic/examples/bitstream.py | 1 + generic/synth/prims.v | 7 ++++--- 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 9b3ffd02..9796d242 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,9 @@ CMakeCache.txt .*.swp a.out *.json +*.dot +*.il +/generic/examples/blinky.png build/ *.asc *.bin diff --git a/generic/cells.cc b/generic/cells.cc index 3b754406..2b555f62 100644 --- a/generic/cells.cc +++ b/generic/cells.cc @@ -42,8 +42,9 @@ std::unique_ptr create_generic_cell(Context *ctx, IdString type, std:: } new_cell->type = type; if (type == ctx->id("GENERIC_SLICE")) { - new_cell->params[ctx->id("K")] = std::to_string(ctx->args.K); + new_cell->params[ctx->id("K")] = ctx->args.K; new_cell->params[ctx->id("INIT")] = 0; + new_cell->params[ctx->id("FF_USED")] = 0; for (int i = 0; i < ctx->args.K; i++) add_port(ctx, new_cell.get(), "I[" + std::to_string(i) + "]", PORT_IN); @@ -80,16 +81,25 @@ void lut_to_lc(const Context *ctx, CellInfo *lut, CellInfo *lc, bool no_dff) } if (no_dff) { + lc->params[ctx->id("FF_USED")] = 0; replace_port(lut, ctx->id("Q"), lc, ctx->id("F")); } } void dff_to_lc(const Context *ctx, CellInfo *dff, CellInfo *lc, bool pass_thru_lut) { + lc->params[ctx->id("FF_USED")] = 1; replace_port(dff, ctx->id("CLK"), lc, ctx->id("CLK")); if (pass_thru_lut) { - lc->params[ctx->id("INIT")] = 0xAAAA; + // Fill LUT with alternating 10 + const int init_size = 1 << lc->params[ctx->id("K")].as_int64(); + std::string init; + init.reserve(init_size); + for(int i = 0; i < init_size; i+=2) + init.append("10"); + lc->params[ctx->id("INIT")] = Property::from_string(init); + replace_port(dff, ctx->id("D"), lc, ctx->id("I[0]")); } diff --git a/generic/examples/bitstream.py b/generic/examples/bitstream.py index 6cd63a58..7f0b5c07 100644 --- a/generic/examples/bitstream.py +++ b/generic/examples/bitstream.py @@ -6,6 +6,7 @@ from simple_config import K param_map = { ("GENERIC_SLICE", "K"): ParameterConfig(write=False), ("GENERIC_SLICE", "INIT"): ParameterConfig(write=True, numeric=True, width=2**K), + ("GENERIC_SLICE", "FF_USED"): ParameterConfig(write=True, numeric=True, width=1), ("GENERIC_IOB", "INPUT_USED"): ParameterConfig(write=True, numeric=True, width=1), ("GENERIC_IOB", "OUTPUT_USED"): ParameterConfig(write=True, numeric=True, width=1), diff --git a/generic/synth/prims.v b/generic/synth/prims.v index 47a5df0f..acce585c 100644 --- a/generic/synth/prims.v +++ b/generic/synth/prims.v @@ -21,19 +21,20 @@ endmodule module GENERIC_SLICE #( parameter K = 4, parameter [2**K-1:0] INIT = 0, + parameter FF_USED = 1'b0 ) ( input CLK, input [K-1:0] I, output F, output Q ); - wire f_wire; + wire f_wire; LUT #(.K(K), .INIT(INIT)) lut_i(.I(I), .Q(f_wire)); - DFF dff_i(.CLK(CLK), .D(f_wire), .Q(Q)); + DFF dff_i(.CLK(CLK), .D(f_wire), .Q(Q)); - assign F = f_wire; + assign F = f_wire; endmodule module GENERIC_IOB #( From a4848f6902f81b6d3f2a36ab853922047e177288 Mon Sep 17 00:00:00 2001 From: Pepijn de Vos Date: Fri, 8 Nov 2019 17:18:50 +0100 Subject: [PATCH 18/81] more formatting --- generic/synth/prims.v | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/generic/synth/prims.v b/generic/synth/prims.v index acce585c..1148041c 100644 --- a/generic/synth/prims.v +++ b/generic/synth/prims.v @@ -21,11 +21,11 @@ endmodule module GENERIC_SLICE #( parameter K = 4, parameter [2**K-1:0] INIT = 0, - parameter FF_USED = 1'b0 + parameter FF_USED = 1'b0 ) ( input CLK, input [K-1:0] I, - output F, + output F, output Q ); wire f_wire; From 0cbc25f74aab1c08463e3bdbc5eb62fc4767a802 Mon Sep 17 00:00:00 2001 From: David Shah Date: Mon, 11 Nov 2019 09:42:22 +0000 Subject: [PATCH 19/81] python: Add interactive.py for a REPL during PnR Signed-off-by: David Shah --- python/interactive.py | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 python/interactive.py diff --git a/python/interactive.py b/python/interactive.py new file mode 100644 index 00000000..c8c359f0 --- /dev/null +++ b/python/interactive.py @@ -0,0 +1,6 @@ +# Pass this file to one of the Python script arguments (e.g. --pre-place interactive.py) +# to drop to an command-line interactive Python session in the middle of place and route + +import code +print("Press Ctrl+D to finish interactive session") +code.interact(local=locals()) From 2898d8182da79da8ebe238571760006436dfc19d Mon Sep 17 00:00:00 2001 From: David Shah Date: Mon, 11 Nov 2019 09:44:11 +0000 Subject: [PATCH 20/81] Fix typo Signed-off-by: David Shah --- python/interactive.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/interactive.py b/python/interactive.py index c8c359f0..a2f09edb 100644 --- a/python/interactive.py +++ b/python/interactive.py @@ -1,5 +1,5 @@ # Pass this file to one of the Python script arguments (e.g. --pre-place interactive.py) -# to drop to an command-line interactive Python session in the middle of place and route +# to drop to a command-line interactive Python session in the middle of place and route import code print("Press Ctrl+D to finish interactive session") From ea05c48032a2d50ba83c949b71492a6917ffe3c9 Mon Sep 17 00:00:00 2001 From: Pepijn de Vos Date: Mon, 18 Nov 2019 10:48:19 +0100 Subject: [PATCH 21/81] leftover Q from before slice api change --- generic/pack.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/generic/pack.cc b/generic/pack.cc index 3dc12bc1..69f248d2 100644 --- a/generic/pack.cc +++ b/generic/pack.cc @@ -154,16 +154,16 @@ static void pack_constants(Context *ctx) std::unique_ptr gnd_net = std::unique_ptr(new NetInfo); gnd_net->name = ctx->id("$PACKER_GND_NET"); gnd_net->driver.cell = gnd_cell.get(); - gnd_net->driver.port = ctx->id("Q"); - gnd_cell->ports.at(ctx->id("Q")).net = gnd_net.get(); + gnd_net->driver.port = ctx->id("F"); + gnd_cell->ports.at(ctx->id("F")).net = gnd_net.get(); std::unique_ptr vcc_cell = create_generic_cell(ctx, ctx->id("GENERIC_SLICE"), "$PACKER_VCC"); vcc_cell->params[ctx->id("INIT")] = 1; std::unique_ptr vcc_net = std::unique_ptr(new NetInfo); vcc_net->name = ctx->id("$PACKER_VCC_NET"); vcc_net->driver.cell = vcc_cell.get(); - vcc_net->driver.port = ctx->id("Q"); - vcc_cell->ports.at(ctx->id("Q")).net = vcc_net.get(); + vcc_net->driver.port = ctx->id("F"); + vcc_cell->ports.at(ctx->id("F")).net = vcc_net.get(); std::vector dead_nets; From 0a8e9b0fbdf1c8bf13937b382d8783334bce97dc Mon Sep 17 00:00:00 2001 From: David Shah Date: Mon, 18 Nov 2019 15:07:55 +0000 Subject: [PATCH 22/81] clangformat Signed-off-by: David Shah --- generic/cells.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generic/cells.cc b/generic/cells.cc index 2b555f62..c4421f90 100644 --- a/generic/cells.cc +++ b/generic/cells.cc @@ -96,7 +96,7 @@ void dff_to_lc(const Context *ctx, CellInfo *dff, CellInfo *lc, bool pass_thru_l const int init_size = 1 << lc->params[ctx->id("K")].as_int64(); std::string init; init.reserve(init_size); - for(int i = 0; i < init_size; i+=2) + for (int i = 0; i < init_size; i += 2) init.append("10"); lc->params[ctx->id("INIT")] = Property::from_string(init); From 9a848d9d76aed1f0997db924a0814be7617c2e7c Mon Sep 17 00:00:00 2001 From: David Shah Date: Mon, 18 Nov 2019 16:54:42 +0000 Subject: [PATCH 23/81] ecp5: Add logic utilisation before packing statistics Signed-off-by: David Shah --- ecp5/pack.cc | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/ecp5/pack.cc b/ecp5/pack.cc index c498d20a..ab80e156 100644 --- a/ecp5/pack.cc +++ b/ecp5/pack.cc @@ -56,6 +56,50 @@ class Ecp5Packer new_cells.clear(); } + // Print logic usgage + void print_logic_usage() + { + int total_luts = 0, total_ffs = 0; + int total_ramluts = 0, total_ramwluts = 0; + for (auto bel : ctx->getBels()) { + if (ctx->getBelType(bel) == id_TRELLIS_SLICE) { + total_luts += 2; + total_ffs += 2; + Loc l = ctx->getBelLocation(bel); + if (l.z == 0 || l.z == 1) + total_ramluts += 2; + if (l.z == 2) + total_ramwluts += 2; + } + } + int used_lgluts = 0, used_cyluts = 0, used_ramluts = 0, used_ramwluts = 0, used_ffs = 0; + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (is_lut(ctx, ci)) + ++used_lgluts; + if (is_carry(ctx, ci)) + used_cyluts += 2; + if (is_dpram(ctx, ci)) { + used_ramluts += 4; + used_ramwluts += 2; + } + if (is_ff(ctx, ci)) + used_ffs += 2; + } + log_info("Logic utilisation before packing:\n"); + auto pc = [](int used, int total) { return 100 * used / total; }; + int used_luts = used_lgluts + used_cyluts + used_ramluts + used_ramwluts; + log_info(" Total LUT4s: %5d/%5d %5d%%\n", used_luts, total_luts, pc(used_luts, total_luts)); + log_info(" logic LUTs: %5d/%5d %5d%%\n", used_lgluts, total_luts, pc(used_lgluts, total_luts)); + log_info(" carry LUTs: %5d/%5d %5d%%\n", used_cyluts, total_luts, pc(used_cyluts, total_luts)); + log_info(" RAM LUTs: %5d/%5d %5d%%\n", used_ramluts, total_ramluts, pc(used_ramluts, total_ramluts)); + log_info(" RAMW LUTs: %5d/%5d %5d%%\n", used_ramwluts, total_ramwluts, + pc(used_ramwluts, total_ramwluts)); + log_break(); + log_info(" Total DFFs: %5d/%5d %5d%%\n", used_ffs, total_ffs, pc(used_ffs, total_ffs)); + log_break(); + } + // Find FFs associated with LUTs, or LUT expansion muxes void find_lutff_pairs() { @@ -2682,6 +2726,7 @@ class Ecp5Packer void pack() { prepack_checks(); + print_logic_usage(); pack_io(); pack_dqsbuf(); preplace_plls(); From 36c0ff2dbceea409ad66e8ecf3c2d52430f26fa9 Mon Sep 17 00:00:00 2001 From: David Shah Date: Mon, 18 Nov 2019 20:58:08 +0000 Subject: [PATCH 24/81] ecp5: Fix dynamic DELAYF control Signed-off-by: David Shah --- ecp5/bitstream.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ecp5/bitstream.cc b/ecp5/bitstream.cc index 9732cd52..b491bf75 100644 --- a/ecp5/bitstream.cc +++ b/ecp5/bitstream.cc @@ -1293,6 +1293,9 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex else cc.tiles[pic_tile].add_enum(prim + "." + param.first.str(ctx), param.second.as_string()); } + if (get_net_or_empty(ci, id_LOADN) != nullptr) { + cc.tiles[pic_tile].add_enum(prim + ".LOADNMUX", "LOADN"); + } } else if (ci->type == id_DCUA) { TileGroup tg; tg.tiles = get_dcu_tiles(ctx, ci->bel); From f28186bb1b2ed554cb4053ebd09ec11f8193f508 Mon Sep 17 00:00:00 2001 From: David Shah Date: Mon, 18 Nov 2019 21:34:32 +0000 Subject: [PATCH 25/81] timing: Fix critical path walking for ECP5 Signed-off-by: David Shah --- common/timing.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/common/timing.cc b/common/timing.cc index 37600c8c..4e84fffe 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -434,8 +434,7 @@ struct Timing int port_clocks; TimingPortClass portClass = ctx->getPortTimingClass(crit_net->driver.cell, port.first, port_clocks); - if (portClass == TMG_CLOCK_INPUT || portClass == TMG_ENDPOINT || portClass == TMG_IGNORE || - portClass == TMG_REGISTER_INPUT) + if (portClass == TMG_CLOCK_INPUT || portClass == TMG_ENDPOINT || portClass == TMG_IGNORE) continue; // And find the fanin net with the latest arrival time if (net_data.count(port.second.net) && From c3d4117a219570f0a132ae4f810dec961811311a Mon Sep 17 00:00:00 2001 From: David Shah Date: Mon, 18 Nov 2019 22:03:11 +0000 Subject: [PATCH 26/81] ecp5: Fix handling of custom DEL_VALUE Signed-off-by: David Shah --- ecp5/pack.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ecp5/pack.cc b/ecp5/pack.cc index ab80e156..7ec08d20 100644 --- a/ecp5/pack.cc +++ b/ecp5/pack.cc @@ -2022,7 +2022,8 @@ class Ecp5Packer iol->params[ctx->id("DELAY.DEL_VALUE")] = lookup_delay(str_or_default(ci->params, ctx->id("DEL_MODE"), "USER_DEFINED")); if (ci->params.count(ctx->id("DEL_VALUE")) && - std::string(ci->params.at(ctx->id("DEL_VALUE")).as_string()).substr(0, 5) != "DELAY") + (!ci->params.at(ctx->id("DEL_VALUE")).is_string || + std::string(ci->params.at(ctx->id("DEL_VALUE")).as_string()).substr(0, 5) != "DELAY")) iol->params[ctx->id("DELAY.DEL_VALUE")] = ci->params.at(ctx->id("DEL_VALUE")); if (ci->ports.count(id_LOADN)) replace_port(ci, id_LOADN, iol, id_LOADN); From 67e216f8fb6dfd88c4620c301d12864e765439be Mon Sep 17 00:00:00 2001 From: David Shah Date: Tue, 19 Nov 2019 14:08:35 +0000 Subject: [PATCH 27/81] ecp5: Add support for promotion of LSRs to global network Signed-off-by: David Shah --- ecp5/globals.cc | 76 +++++++++++++++++++++++++++++++++++++++++++++---- ecp5/main.cc | 3 ++ 2 files changed, 74 insertions(+), 5 deletions(-) diff --git a/ecp5/globals.cc b/ecp5/globals.cc index da2ba8f0..4bfc2b16 100644 --- a/ecp5/globals.cc +++ b/ecp5/globals.cc @@ -62,7 +62,12 @@ class Ecp5GlobalRouter return true; return false; } - + bool is_lsr_port(const PortRef &user) + { + if (user.cell->type == id_TRELLIS_SLICE && (user.port == id_LSR)) + return true; + return false; + } std::vector get_clocks() { std::unordered_map clockCount; @@ -102,6 +107,32 @@ class Ecp5GlobalRouter return clocks; } + std::vector get_globalworthy_lsrs() + { + // Fanout threshold for LSRs is high (300), and we only promote up to two of them, in the nature of being + // conservative given the cost of a global + std::unordered_map lsrCount; + for (auto &net : ctx->nets) { + NetInfo *ni = net.second.get(); + lsrCount[ni->name] = 0; + for (const auto &user : ni->users) + if (is_lsr_port(user)) + lsrCount[ni->name]++; + } + // DCCAs must always drive globals + std::vector lsrs; + while (lsrs.size() < 2) { + auto max = std::max_element(lsrCount.begin(), lsrCount.end(), + [](const decltype(lsrCount)::value_type &a, + const decltype(lsrCount)::value_type &b) { return a.second < b.second; }); + if (max == lsrCount.end() || max->second < 300) + break; + lsrs.push_back(ctx->nets.at(max->first).get()); + lsrCount.erase(max->first); + } + return lsrs; + } + PipId find_tap_pip(WireId tile_glb) { std::string wireName = ctx->getWireBasename(tile_glb).str(ctx); @@ -140,6 +171,9 @@ class Ecp5GlobalRouter upstream.push(userWire); bool already_routed = false; WireId next; + + IdString id_lsr1 = ctx->id("LSR1"); + // Search back from the pin until we reach the global network while (true) { next = upstream.front(); @@ -155,6 +189,11 @@ class Ecp5GlobalRouter globalWire = next; break; } + + // Forbid to use LSR1 for non-WRE LSRs; as this is reserved for WRE + if (ctx->getWireBasename(next) == id_lsr1 && user.port != id_WRE) + continue; + if (ctx->checkWireAvail(next)) { for (auto pip : ctx->getPipsUphill(next)) { WireId src = ctx->getPipSrcWire(pip); @@ -464,16 +503,33 @@ class Ecp5GlobalRouter else insert_dcc(clock); } + if (clocks.size() < 12 && !bool_or_default(ctx->settings, ctx->id("arch.nolsrglobal"), false)) { + // Promote up to two set/resets to global + auto lsrs = get_globalworthy_lsrs(); + for (auto lsr : lsrs) { + bool is_noglobal = bool_or_default(lsr->attrs, ctx->id("noglobal"), false); + if (is_noglobal) + continue; + log_info(" promoting LSR net %s to global network\n", lsr->name.c_str(ctx)); + if (is_ooc) // Don't actually do anything in OOC mode, global routing will be done in the full design + lsr->is_global = true; + else + insert_dcc(lsr); + } + } } void route_globals() { log_info("Routing globals...\n"); - std::set all_globals, fab_globals; + std::set all_globals, fab_globals, lsr_globals; for (int i = 0; i < 16; i++) { all_globals.insert(i); - if (i < 8) + if (i < 8) { + if (i >= 4) + lsr_globals.insert(i); fab_globals.insert(i); + } } std::vector> toroute; std::unordered_map clocks; @@ -484,9 +540,18 @@ class Ecp5GlobalRouter NPNR_ASSERT(clock != nullptr); bool drives_fabric = std::any_of(clock->users.begin(), clock->users.end(), [this](const PortRef &port) { return !is_clock_port(port); }); - + bool drives_lsr = std::any_of(clock->users.begin(), clock->users.end(), + [this](const PortRef &port) { return is_lsr_port(port); }); int glbid; - if (drives_fabric) { + if (drives_lsr) { + if (lsr_globals.empty()) { + // Route LSR through fabric (suboptimal) + NPNR_ASSERT(!fab_globals.empty()); + glbid = *(fab_globals.begin()); + } else { + glbid = *(lsr_globals.begin()); + } + } else if (drives_fabric) { if (fab_globals.empty()) continue; glbid = *(fab_globals.begin()); @@ -495,6 +560,7 @@ class Ecp5GlobalRouter } all_globals.erase(glbid); fab_globals.erase(glbid); + lsr_globals.erase(glbid); log_info(" routing clock net %s using global %d\n", clock->name.c_str(ctx), glbid); bool routed = route_onto_global(clock, glbid); diff --git a/ecp5/main.cc b/ecp5/main.cc index 24a98df4..d1297a95 100644 --- a/ecp5/main.cc +++ b/ecp5/main.cc @@ -74,6 +74,7 @@ po::options_description ECP5CommandHandler::getArchOptions() specific.add_options()( "out-of-context", "disable IO buffer insertion and global promotion/routing, for building pre-routed blocks (experimental)"); + specific.add_options()("no-lsr-globals", "don't promote local set/reset (LSR) signals to global networks"); return specific; } @@ -239,6 +240,8 @@ std::unique_ptr ECP5CommandHandler::createContext(std::unordered_mapsettings[ctx->id(val.first)] = val.second; ctx->settings[ctx->id("arch.package")] = ctx->archArgs().package; ctx->settings[ctx->id("arch.speed")] = speedString(ctx->archArgs().speed); + if (vm.count("no-lsr-globals")) + ctx->settings[ctx->id("arch.nolsrglobal")] = 1; if (vm.count("out-of-context")) ctx->settings[ctx->id("arch.ooc")] = 1; return ctx; From 08cf545d9b8b32eda7998261bdc8a5401432112c Mon Sep 17 00:00:00 2001 From: David Shah Date: Wed, 20 Nov 2019 17:10:11 +0000 Subject: [PATCH 28/81] Revert "Merge pull request #355 from YosysHQ/ecp5-promote-lsr" This reverts commit 6a7d1fe53d2b55e03ebe3400c0f4c9e5741ed2e1, reversing changes made to c3d4117a219570f0a132ae4f810dec961811311a. --- ecp5/globals.cc | 76 ++++--------------------------------------------- ecp5/main.cc | 3 -- 2 files changed, 5 insertions(+), 74 deletions(-) diff --git a/ecp5/globals.cc b/ecp5/globals.cc index 4bfc2b16..da2ba8f0 100644 --- a/ecp5/globals.cc +++ b/ecp5/globals.cc @@ -62,12 +62,7 @@ class Ecp5GlobalRouter return true; return false; } - bool is_lsr_port(const PortRef &user) - { - if (user.cell->type == id_TRELLIS_SLICE && (user.port == id_LSR)) - return true; - return false; - } + std::vector get_clocks() { std::unordered_map clockCount; @@ -107,32 +102,6 @@ class Ecp5GlobalRouter return clocks; } - std::vector get_globalworthy_lsrs() - { - // Fanout threshold for LSRs is high (300), and we only promote up to two of them, in the nature of being - // conservative given the cost of a global - std::unordered_map lsrCount; - for (auto &net : ctx->nets) { - NetInfo *ni = net.second.get(); - lsrCount[ni->name] = 0; - for (const auto &user : ni->users) - if (is_lsr_port(user)) - lsrCount[ni->name]++; - } - // DCCAs must always drive globals - std::vector lsrs; - while (lsrs.size() < 2) { - auto max = std::max_element(lsrCount.begin(), lsrCount.end(), - [](const decltype(lsrCount)::value_type &a, - const decltype(lsrCount)::value_type &b) { return a.second < b.second; }); - if (max == lsrCount.end() || max->second < 300) - break; - lsrs.push_back(ctx->nets.at(max->first).get()); - lsrCount.erase(max->first); - } - return lsrs; - } - PipId find_tap_pip(WireId tile_glb) { std::string wireName = ctx->getWireBasename(tile_glb).str(ctx); @@ -171,9 +140,6 @@ class Ecp5GlobalRouter upstream.push(userWire); bool already_routed = false; WireId next; - - IdString id_lsr1 = ctx->id("LSR1"); - // Search back from the pin until we reach the global network while (true) { next = upstream.front(); @@ -189,11 +155,6 @@ class Ecp5GlobalRouter globalWire = next; break; } - - // Forbid to use LSR1 for non-WRE LSRs; as this is reserved for WRE - if (ctx->getWireBasename(next) == id_lsr1 && user.port != id_WRE) - continue; - if (ctx->checkWireAvail(next)) { for (auto pip : ctx->getPipsUphill(next)) { WireId src = ctx->getPipSrcWire(pip); @@ -503,33 +464,16 @@ class Ecp5GlobalRouter else insert_dcc(clock); } - if (clocks.size() < 12 && !bool_or_default(ctx->settings, ctx->id("arch.nolsrglobal"), false)) { - // Promote up to two set/resets to global - auto lsrs = get_globalworthy_lsrs(); - for (auto lsr : lsrs) { - bool is_noglobal = bool_or_default(lsr->attrs, ctx->id("noglobal"), false); - if (is_noglobal) - continue; - log_info(" promoting LSR net %s to global network\n", lsr->name.c_str(ctx)); - if (is_ooc) // Don't actually do anything in OOC mode, global routing will be done in the full design - lsr->is_global = true; - else - insert_dcc(lsr); - } - } } void route_globals() { log_info("Routing globals...\n"); - std::set all_globals, fab_globals, lsr_globals; + std::set all_globals, fab_globals; for (int i = 0; i < 16; i++) { all_globals.insert(i); - if (i < 8) { - if (i >= 4) - lsr_globals.insert(i); + if (i < 8) fab_globals.insert(i); - } } std::vector> toroute; std::unordered_map clocks; @@ -540,18 +484,9 @@ class Ecp5GlobalRouter NPNR_ASSERT(clock != nullptr); bool drives_fabric = std::any_of(clock->users.begin(), clock->users.end(), [this](const PortRef &port) { return !is_clock_port(port); }); - bool drives_lsr = std::any_of(clock->users.begin(), clock->users.end(), - [this](const PortRef &port) { return is_lsr_port(port); }); + int glbid; - if (drives_lsr) { - if (lsr_globals.empty()) { - // Route LSR through fabric (suboptimal) - NPNR_ASSERT(!fab_globals.empty()); - glbid = *(fab_globals.begin()); - } else { - glbid = *(lsr_globals.begin()); - } - } else if (drives_fabric) { + if (drives_fabric) { if (fab_globals.empty()) continue; glbid = *(fab_globals.begin()); @@ -560,7 +495,6 @@ class Ecp5GlobalRouter } all_globals.erase(glbid); fab_globals.erase(glbid); - lsr_globals.erase(glbid); log_info(" routing clock net %s using global %d\n", clock->name.c_str(ctx), glbid); bool routed = route_onto_global(clock, glbid); diff --git a/ecp5/main.cc b/ecp5/main.cc index d1297a95..24a98df4 100644 --- a/ecp5/main.cc +++ b/ecp5/main.cc @@ -74,7 +74,6 @@ po::options_description ECP5CommandHandler::getArchOptions() specific.add_options()( "out-of-context", "disable IO buffer insertion and global promotion/routing, for building pre-routed blocks (experimental)"); - specific.add_options()("no-lsr-globals", "don't promote local set/reset (LSR) signals to global networks"); return specific; } @@ -240,8 +239,6 @@ std::unique_ptr ECP5CommandHandler::createContext(std::unordered_mapsettings[ctx->id(val.first)] = val.second; ctx->settings[ctx->id("arch.package")] = ctx->archArgs().package; ctx->settings[ctx->id("arch.speed")] = speedString(ctx->archArgs().speed); - if (vm.count("no-lsr-globals")) - ctx->settings[ctx->id("arch.nolsrglobal")] = 1; if (vm.count("out-of-context")) ctx->settings[ctx->id("arch.ooc")] = 1; return ctx; From aee2e0198366408e74d4b96026befc96a0460e13 Mon Sep 17 00:00:00 2001 From: David Shah Date: Tue, 19 Nov 2019 18:14:40 +0000 Subject: [PATCH 29/81] ecp5: Improve flipflop packing density Signed-off-by: David Shah --- ecp5/pack.cc | 153 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) diff --git a/ecp5/pack.cc b/ecp5/pack.cc index 7ec08d20..d2a21c87 100644 --- a/ecp5/pack.cc +++ b/ecp5/pack.cc @@ -57,12 +57,14 @@ class Ecp5Packer } // Print logic usgage + int available_slices = 0; void print_logic_usage() { int total_luts = 0, total_ffs = 0; int total_ramluts = 0, total_ramwluts = 0; for (auto bel : ctx->getBels()) { if (ctx->getBelType(bel) == id_TRELLIS_SLICE) { + available_slices += 1; total_luts += 2; total_ffs += 2; Loc l = ctx->getBelLocation(bel); @@ -119,6 +121,58 @@ class Ecp5Packer } } + // Check if a flipflop is available in a slice + bool is_ff_available(CellInfo *slice, int ff) + { + if (get_net_or_empty(slice, (ff == 1) ? id_Q1 : id_Q0) != nullptr) + return false; + if (get_net_or_empty(slice, (ff == 1) ? id_M1 : id_M0) != nullptr) + return false; + return true; + } + + // Check if a flipflop can be added to a slice + bool can_add_ff_to_slice(CellInfo *slice, CellInfo *ff) + { + std::string clkmux = str_or_default(ff->params, ctx->id("CLKMUX"), "CLK"); + std::string lsrmux = str_or_default(ff->params, ctx->id("LSRMUX"), "LSR"); + + bool has_dpram = str_or_default(slice->params, ctx->id("MODE"), "LOGIC") == "DPRAM"; + if (has_dpram) { + std::string wckmux = str_or_default(slice->params, ctx->id("WCKMUX"), "WCK"); + std::string wremux = str_or_default(slice->params, ctx->id("WREMUX"), "WRE"); + if (wckmux != clkmux && !(wckmux == "WCK" && clkmux == "CLK")) + return false; + if (wremux != lsrmux && !(wremux == "WRE" && lsrmux == "LSR")) + return false; + } + bool has_ff0 = get_net_or_empty(slice, id_Q0) != nullptr; + bool has_ff1 = get_net_or_empty(slice, id_Q1) != nullptr; + if (!has_ff0 && !has_ff1) + return true; + if (str_or_default(ff->params, ctx->id("GSR"), "DISABLED") != + str_or_default(slice->params, ctx->id("GSR"), "DISABLED")) + return false; + if (str_or_default(ff->params, ctx->id("SRMODE"), "LSR_OVER_CE") != + str_or_default(slice->params, ctx->id("SRMODE"), "LSR_OVER_CE")) + return false; + if (str_or_default(ff->params, ctx->id("CEMUX"), "1") != str_or_default(slice->params, ctx->id("CEMUX"), "1")) + return false; + if (str_or_default(ff->params, ctx->id("LSRMUX"), "LSR") != + str_or_default(slice->params, ctx->id("LSRMUX"), "LSR")) + return false; + if (str_or_default(ff->params, ctx->id("CLKMUX"), "CLK") != + str_or_default(slice->params, ctx->id("CLKMUX"), "CLK")) + return false; + if (net_or_nullptr(ff, ctx->id("CLK")) != net_or_nullptr(slice, ctx->id("CLK"))) + return false; + if (net_or_nullptr(ff, ctx->id("CE")) != net_or_nullptr(slice, ctx->id("CE"))) + return false; + if (net_or_nullptr(ff, ctx->id("LSR")) != net_or_nullptr(slice, ctx->id("LSR"))) + return false; + return true; + } + const NetInfo *net_or_nullptr(CellInfo *cell, IdString port) { auto fnd = cell->ports.find(port); @@ -1069,17 +1123,116 @@ class Ecp5Packer flush_cells(); } + // Find a cell that meets some criterea near an origin cell + // Used for packing an FF into a nearby SLICE + template CellInfo *find_nearby_cell(CellInfo *origin, TFunc Func) + { + std::unordered_set visited_cells; + std::queue to_visit; + visited_cells.insert(origin); + to_visit.push(origin); + int iter = 0; + while (!to_visit.empty() && iter < 10000) { + CellInfo *cursor = to_visit.front(); + to_visit.pop(); + if (Func(cursor)) + return cursor; + for (const auto &port : cursor->ports) { + NetInfo *pn = port.second.net; + if (pn == nullptr) + continue; + // Skip high-fanout nets that are unlikely to be relevant + if (pn->users.size() > 25) + continue; + // Add other ports on this net if not already visited + auto visit_port = [&](const PortRef &port) { + if (port.cell == nullptr) + return; + if (visited_cells.count(port.cell)) + return; + // If not already visited; add the cell of this port to the queue + to_visit.push(port.cell); + visited_cells.insert(port.cell); + }; + visit_port(pn->driver); + for (const auto &usr : pn->users) + visit_port(usr); + } + ++iter; + } + return nullptr; + } + // Pack flipflops that weren't paired with a LUT + float dense_pack_mode_thresh = 0.95f; void pack_remaining_ffs() { + // Enter dense flipflop packing mode once utilisation exceeds a threshold (default: 95%) + int used_slices = 0; + for (auto &cell : ctx->cells) + if (cell.second->type == id_TRELLIS_SLICE) + ++used_slices; + log_info("Packing unpaired FFs into a SLICE...\n"); for (auto cell : sorted(ctx->cells)) { CellInfo *ci = cell.second; if (is_ff(ctx, ci)) { + bool pack_dense = used_slices > (dense_pack_mode_thresh * available_slices); + if (pack_dense) { + // If dense packing threshold exceeded; always try and pack the FF into an existing slice + // Find a SLICE with space "near" the flipflop in the netlist + std::vector ltile; + CellInfo *target = find_nearby_cell(ci, [&](CellInfo *cursor) { + if (cursor->type != id_TRELLIS_SLICE) + return false; + if (!cursor->constr_children.empty() || cursor->constr_parent != nullptr) { + auto &constr_children = (cursor->constr_parent != nullptr) + ? cursor->constr_parent->constr_children + : cursor->constr_children; + // Skip big chains for performance + if (constr_children.size() > 8) + return false; + // Have to check the whole of the tile for legality when dealing with chains, not just slice + ltile.clear(); + if (cursor->constr_parent != nullptr) + ltile.push_back(cursor->constr_parent); + else + ltile.push_back(cursor); + for (auto c : constr_children) + ltile.push_back(c); + if (!can_add_ff_to_tile(ltile, cursor)) + return false; + } + if (!can_add_ff_to_slice(cursor, ci)) + return false; + for (int i = 0; i < 2; i++) + if (is_ff_available(cursor, i)) + return true; + return false; + }); + + // If found, add the FF to this slice instead of creating a new one + if (target != nullptr) { + for (int i = 0; i < 2; i++) { + if (is_ff_available(target, i)) { + ff_to_slice(ctx, ci, target, i, false); + goto ff_packed; + } + } + } + + if (false) { + ff_packed: + packed_cells.insert(ci->name); + continue; + } + } + std::unique_ptr slice = create_ecp5_cell(ctx, ctx->id("TRELLIS_SLICE"), ci->name.str(ctx) + "_SLICE"); ff_to_slice(ctx, ci, slice.get(), 0, false); new_cells.push_back(std::move(slice)); + ++used_slices; packed_cells.insert(ci->name); } } From 75f403db603df9b6abf65d8d6fa9d8c871f58116 Mon Sep 17 00:00:00 2001 From: David Shah Date: Tue, 26 Nov 2019 10:01:33 +0000 Subject: [PATCH 30/81] HeAP: support for bel region constraints Signed-off-by: David Shah --- common/placer_heap.cc | 89 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 83 insertions(+), 6 deletions(-) diff --git a/common/placer_heap.cc b/common/placer_heap.cc index e9fc2fb2..bbca2014 100644 --- a/common/placer_heap.cc +++ b/common/placer_heap.cc @@ -308,6 +308,14 @@ class HeAPPlacer std::vector> nearest_row_with_bel; std::vector> nearest_col_with_bel; + struct BoundingBox + { + // Actual bounding box + int x0 = 0, x1 = 0, y0 = 0, y1 = 0; + }; + + std::unordered_map constraint_region_bounds; + // In some cases, we can't use bindBel because we allow overlap in the earlier stages. So we use this custom // structure instead struct CellLocation @@ -443,6 +451,31 @@ class HeAPPlacer nr.at(y) = loc.y; } } + + // Determine bounding boxes of region constraints + for (auto ®ion : sorted(ctx->region)) { + Region *r = region.second; + BoundingBox bb; + if (r->constr_bels) { + bb.x0 = std::numeric_limits::max(); + bb.x1 = std::numeric_limits::min(); + bb.y0 = std::numeric_limits::max(); + bb.y1 = std::numeric_limits::min(); + for (auto bel : r->bels) { + Loc loc = ctx->getBelLocation(bel); + bb.x0 = std::min(bb.x0, loc.x); + bb.x1 = std::max(bb.x1, loc.x); + bb.y0 = std::min(bb.y0, loc.y); + bb.y1 = std::max(bb.y1, loc.y); + } + } else { + bb.x0 = 0; + bb.y0 = 0; + bb.x1 = max_x; + bb.y1 = max_y; + } + constraint_region_bounds[r->name] = bb; + } } // Build and solve in one direction @@ -684,9 +717,15 @@ class HeAPPlacer if (yaxis) { cell_locs.at(solve_cells.at(i)->name).rawy = vals.at(i); cell_locs.at(solve_cells.at(i)->name).y = std::min(max_y, std::max(0, int(vals.at(i)))); + if (solve_cells.at(i)->region != nullptr) + cell_locs.at(solve_cells.at(i)->name).y = + limit_to_reg(solve_cells.at(i)->region, cell_locs.at(solve_cells.at(i)->name).y, true); } else { cell_locs.at(solve_cells.at(i)->name).rawx = vals.at(i); cell_locs.at(solve_cells.at(i)->name).x = std::min(max_x, std::max(0, int(vals.at(i)))); + if (solve_cells.at(i)->region != nullptr) + cell_locs.at(solve_cells.at(i)->name).x = + limit_to_reg(solve_cells.at(i)->region, cell_locs.at(solve_cells.at(i)->name).x, false); } } @@ -761,8 +800,21 @@ class HeAPPlacer while (!placed) { - int nx = ctx->rng(2 * radius + 1) + std::max(cell_locs.at(ci->name).x - radius, 0); - int ny = ctx->rng(2 * radius + 1) + std::max(cell_locs.at(ci->name).y - radius, 0); + int rx = radius, ry = radius; + + if (ci->region != nullptr) { + rx = std::min(radius, (constraint_region_bounds[ci->region->name].x1 - + constraint_region_bounds[ci->region->name].x0) / + 2 + + 1); + ry = std::min(radius, (constraint_region_bounds[ci->region->name].y1 - + constraint_region_bounds[ci->region->name].y0) / + 2 + + 1); + } + + int nx = ctx->rng(2 * rx + 1) + std::max(cell_locs.at(ci->name).x - rx, 0); + int ny = ctx->rng(2 * ry + 1) + std::max(cell_locs.at(ci->name).y - ry, 0); iter++; iter_at_radius++; @@ -820,6 +872,8 @@ class HeAPPlacer if (ci->constr_children.empty() && !ci->constr_abs_z) { for (auto sz : fb.at(nx).at(ny)) { + if (ci->region != nullptr && ci->region->constr_bels && !ci->region->bels.count(sz)) + continue; if (ctx->checkBelAvail(sz) || (radius > ripup_radius || ctx->rng(20000) < 10)) { CellInfo *bound = ctx->getBoundBelCell(sz); if (bound != nullptr) { @@ -881,6 +935,8 @@ class HeAPPlacer Loc ploc = visit.front().second; visit.pop(); BelId target = ctx->getBelByLocation(ploc); + if (vc->region != nullptr && vc->region->constr_bels && !vc->region->bels.count(target)) + continue; CellInfo *bound; if (target == BelId() || ctx->getBelType(target) != vc->type) goto fail; @@ -948,6 +1004,15 @@ class HeAPPlacer // Implementation of the cut-based spreading as described in the HeAP/SimPL papers static constexpr float beta = 0.9; + template T limit_to_reg(Region *reg, T val, bool dir) + { + if (reg == nullptr) + return val; + int limit_low = dir ? constraint_region_bounds[reg->name].y0 : constraint_region_bounds[reg->name].x0; + int limit_high = dir ? constraint_region_bounds[reg->name].y1 : constraint_region_bounds[reg->name].x1; + return std::max(std::min(val, limit_high), limit_low); + } + struct ChainExtent { int x0, y0, x1, y1; @@ -1460,10 +1525,22 @@ class HeAPPlacer : p->cell_locs.at(cut_cells.at(br.first - 1)->name).rawx; double m = (br.second - bl.second) / std::max(0.00001, orig_right - orig_left); for (int j = bl.first; j < br.first; j++) { - auto &pos = dir ? p->cell_locs.at(cut_cells.at(j)->name).rawy - : p->cell_locs.at(cut_cells.at(j)->name).rawx; - NPNR_ASSERT(pos >= orig_left && pos <= orig_right); - pos = bl.second + m * (pos - orig_left); + Region *cr = cut_cells.at(j)->region; + if (cr != nullptr) { + // Limit spreading bounds to constraint region; if applicable + double brsc = p->limit_to_reg(cr, br.second, dir); + double blsc = p->limit_to_reg(cr, bl.second, dir); + double mr = (brsc - blsc) / std::max(0.00001, orig_right - orig_left); + auto &pos = dir ? p->cell_locs.at(cut_cells.at(j)->name).rawy + : p->cell_locs.at(cut_cells.at(j)->name).rawx; + NPNR_ASSERT(pos >= orig_left && pos <= orig_right); + pos = blsc + mr * (pos - orig_left); + } else { + auto &pos = dir ? p->cell_locs.at(cut_cells.at(j)->name).rawy + : p->cell_locs.at(cut_cells.at(j)->name).rawx; + NPNR_ASSERT(pos >= orig_left && pos <= orig_right); + pos = bl.second + m * (pos - orig_left); + } // log("[%f, %f] -> [%f, %f]: %f -> %f\n", orig_left, orig_right, bl.second, br.second, // orig_pos, pos); } From ff9d6b4f89c6b2d92326ce821d045cb1875a7999 Mon Sep 17 00:00:00 2001 From: David Shah Date: Tue, 26 Nov 2019 10:03:28 +0000 Subject: [PATCH 31/81] ice40: Make HeAP the default placer Signed-off-by: David Shah --- ice40/arch.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ice40/arch.cc b/ice40/arch.cc index cc50cb68..0f8e5969 100644 --- a/ice40/arch.cc +++ b/ice40/arch.cc @@ -1245,7 +1245,11 @@ void Arch::assignCellInfo(CellInfo *cell) } } +#ifdef WITH_HEAP +const std::string Arch::defaultPlacer = "heap"; +#else const std::string Arch::defaultPlacer = "sa"; +#endif const std::vector Arch::availablePlacers = {"sa", #ifdef WITH_HEAP From defafcf5fee8a6dac4a4d147751823aae7c2288b Mon Sep 17 00:00:00 2001 From: David Shah Date: Tue, 26 Nov 2019 10:10:26 +0000 Subject: [PATCH 32/81] generic: Use HeAP as placer where possible Signed-off-by: David Shah --- generic/arch.cc | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/generic/arch.cc b/generic/arch.cc index 9e59540e..14d15115 100644 --- a/generic/arch.cc +++ b/generic/arch.cc @@ -21,6 +21,7 @@ #include #include "nextpnr.h" #include "placer1.h" +#include "placer_heap.h" #include "router1.h" #include "util.h" @@ -494,8 +495,29 @@ bool Arch::getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay bool Arch::place() { std::string placer = str_or_default(settings, id("placer"), defaultPlacer); - // FIXME: No HeAP because it needs a list of IO buffers - if (placer == "sa") { + if (placer == "heap") { + bool have_iobuf_or_constr = false; + for (auto cell : sorted(cells)) { + CellInfo *ci = cell.second; + if (ci->type == id("GENERIC_IOB") || ci->bel != BelId() || ci->attrs.count(id("BEL"))) { + have_iobuf_or_constr = true; + break; + } + } + bool retVal; + if (!have_iobuf_or_constr) { + log_warning("Unable to use HeAP due to a lack of IO buffers or constrained cells as anchors; reverting to " + "SA.\n"); + retVal = placer1(getCtx(), Placer1Cfg(getCtx())); + } else { + PlacerHeapCfg cfg(getCtx()); + cfg.ioBufTypes.insert(id("GENERIC_IOB")); + retVal = placer_heap(getCtx(), cfg); + } + getCtx()->settings[getCtx()->id("place")] = 1; + archInfoToAttributes(); + return retVal; + } else if (placer == "sa") { bool retVal = placer1(getCtx(), Placer1Cfg(getCtx())); getCtx()->settings[getCtx()->id("place")] = 1; archInfoToAttributes(); @@ -596,9 +618,17 @@ bool Arch::isBelLocationValid(BelId bel) const return cellsCompatible(cells.data(), int(cells.size())); } +#ifdef WITH_HEAP +const std::string Arch::defaultPlacer = "heap"; +#else const std::string Arch::defaultPlacer = "sa"; -const std::vector Arch::availablePlacers = {"sa"}; +#endif +const std::vector Arch::availablePlacers = {"sa", +#ifdef WITH_HEAP + "heap" +#endif +}; void Arch::assignArchInfo() { for (auto &cell : getCtx()->cells) { From 523ed4cfb2e37d23d40a56d77c082e3ea42f9082 Mon Sep 17 00:00:00 2001 From: David Shah Date: Tue, 26 Nov 2019 10:16:27 +0000 Subject: [PATCH 33/81] HeAP: improve error handling when stuck Signed-off-by: David Shah --- common/placer_heap.cc | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/common/placer_heap.cc b/common/placer_heap.cc index bbca2014..4a380a04 100644 --- a/common/placer_heap.cc +++ b/common/placer_heap.cc @@ -774,6 +774,7 @@ class HeAPPlacer } int ripup_radius = 2; int total_iters = 0; + int total_iters_noreset = 0; while (!remaining.empty()) { auto top = remaining.top(); remaining.pop(); @@ -793,13 +794,23 @@ class HeAPPlacer int best_inp_len = std::numeric_limits::max(); total_iters++; + total_iters_noreset++; if (total_iters > int(solve_cells.size())) { total_iters = 0; ripup_radius = std::max(std::max(max_x, max_y), ripup_radius * 2); } + if (total_iters_noreset > std::max(50000, 1000 * int(ctx->cells.size()))) { + log_error("Unable to find legal placement for all cells, design is probably at utilisation limit.\n"); + } + while (!placed) { + // Set a conservative timeout + if (iter > std::max(1000, 3 * int(ctx->cells.size()))) + log_error("Unable to find legal placement for cell '%s', check constraints and utilisation.\n", + ctx->nameOf(ci)); + int rx = radius, ry = radius; if (ci->region != nullptr) { From 98fe4438f18bc48558f403ebd2d4e80e0989b5c0 Mon Sep 17 00:00:00 2001 From: David Shah Date: Tue, 26 Nov 2019 16:10:07 +0000 Subject: [PATCH 34/81] ECP5 support is no longer experimental Signed-off-by: David Shah --- README.md | 9 +- docs/faq.md | 3 +- ecp5/synth/.gitignore | 3 - ecp5/synth/blinky.v | 77 ------ ecp5/synth/blinky.ys | 2 - ecp5/synth/ulx3s_empty.config | 439 ---------------------------------- 6 files changed, 3 insertions(+), 530 deletions(-) delete mode 100644 ecp5/synth/.gitignore delete mode 100644 ecp5/synth/blinky.v delete mode 100644 ecp5/synth/blinky.ys delete mode 100644 ecp5/synth/ulx3s_empty.config diff --git a/README.md b/README.md index efbc6bb0..6c639cf0 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ tool. Currently nextpnr supports: * Lattice iCE40 devices supported by [Project IceStorm](http://www.clifford.at/icestorm/) - * *(experimental)* Lattice ECP5 devices supported by [Project Trellis](https://github.com/SymbiFlow/prjtrellis) + * Lattice ECP5 devices supported by [Project Trellis](https://github.com/SymbiFlow/prjtrellis) * *(experimental)* a "generic" back-end for user-defined architectures We hope to see Xilinx 7 Series thanks to @@ -106,12 +106,7 @@ make -j$(nproc) sudo make install ``` - - For an ECP5 blinky on the 45k ULX3S board, first synthesise using `yosys blinky.ys` in `ecp5/synth`. - - Then run ECP5 place-and route using `./nextpnr-ecp5 --json ecp5/synth/blinky.json --basecfg ecp5/synth/ulx3s_empty.config --textcfg ecp5/synth/ulx3s_out.config` - - Create a bitstream using `ecppack ulx3s_out.config ulx3s.bit` - - Note that `ulx3s_empty.config` contains fixed/unknown bits to be copied to the output bitstream - - - More examples of the ECP5 flow for a range of boards can be found in the [Project Trellis Examples](https://github.com/SymbiFlow/prjtrellis/tree/master/examples). + - Examples of the ECP5 flow for a range of boards can be found in the [Project Trellis Examples](https://github.com/SymbiFlow/prjtrellis/tree/master/examples). ### nextpnr-generic diff --git a/docs/faq.md b/docs/faq.md index 7b358187..8a102af8 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -144,8 +144,7 @@ Nextpnr and other tools migrating to nextpnr. * If you are developing Verilog FPGA code targeted at the Lattice ECP5 and - need an open source toolchain, you may consider the **extremely - experimental** ECP5 support in Yosys and nextpnr. + need an open source toolchain, there is also stable ECP5 support in Yosys and nextpnr. * If you are developing FPGA code in **VHDL** you will need to use either a version of [Yosys with Verific support](https://github.com/YosysHQ/yosys/tree/master/frontends/verific) or the vendor provided tools due diff --git a/ecp5/synth/.gitignore b/ecp5/synth/.gitignore deleted file mode 100644 index f4dfa215..00000000 --- a/ecp5/synth/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.bit -*_out.config - diff --git a/ecp5/synth/blinky.v b/ecp5/synth/blinky.v deleted file mode 100644 index 9c6b187b..00000000 --- a/ecp5/synth/blinky.v +++ /dev/null @@ -1,77 +0,0 @@ -module top(input clk_pin, input btn_pin, output [7:0] led_pin, output gpio0_pin); - - wire clk; - wire [7:0] led; - wire btn; - wire gpio0; - - (* LOC="G2" *) (* IO_TYPE="LVCMOS33" *) - TRELLIS_IO #(.DIR("INPUT")) clk_buf (.B(clk_pin), .O(clk)); - - (* LOC="R1" *) (* IO_TYPE="LVCMOS33" *) - TRELLIS_IO #(.DIR("INPUT")) btn_buf (.B(btn_pin), .O(btn)); - - (* LOC="B2" *) (* IO_TYPE="LVCMOS33" *) - TRELLIS_IO #(.DIR("OUTPUT")) led_buf_0 (.B(led_pin[0]), .I(led[0])); - (* LOC="C2" *) (* IO_TYPE="LVCMOS33" *) - TRELLIS_IO #(.DIR("OUTPUT")) led_buf_1 (.B(led_pin[1]), .I(led[1])); - (* LOC="C1" *) (* IO_TYPE="LVCMOS33" *) - TRELLIS_IO #(.DIR("OUTPUT")) led_buf_2 (.B(led_pin[2]), .I(led[2])); - (* LOC="D2" *) (* IO_TYPE="LVCMOS33" *) - TRELLIS_IO #(.DIR("OUTPUT")) led_buf_3 (.B(led_pin[3]), .I(led[3])); - - (* LOC="D1" *) (* IO_TYPE="LVCMOS33" *) - TRELLIS_IO #(.DIR("OUTPUT")) led_buf_4 (.B(led_pin[4]), .I(led[4])); - (* LOC="E2" *) (* IO_TYPE="LVCMOS33" *) - TRELLIS_IO #(.DIR("OUTPUT")) led_buf_5 (.B(led_pin[5]), .I(led[5])); - (* LOC="E1" *) (* IO_TYPE="LVCMOS33" *) - TRELLIS_IO #(.DIR("OUTPUT")) led_buf_6 (.B(led_pin[6]), .I(led[6])); - (* LOC="H3" *) (* IO_TYPE="LVCMOS33" *) - TRELLIS_IO #(.DIR("OUTPUT")) led_buf_7 (.B(led_pin[7]), .I(led[7])); - - - (* LOC="L2" *) (* IO_TYPE="LVCMOS33" *) - TRELLIS_IO #(.DIR("OUTPUT")) gpio0_buf (.B(gpio0_pin), .I(gpio0)); - - localparam ctr_width = 24; - localparam ctr_max = 2**ctr_width - 1; - reg [ctr_width-1:0] ctr = 0; - reg [9:0] pwm_ctr = 0; - reg dir = 0; - - always@(posedge clk) begin - ctr <= btn ? ctr : (dir ? ctr - 1'b1 : ctr + 1'b1); - if (ctr[ctr_width-1 : ctr_width-3] == 0 && dir == 1) - dir <= 1'b0; - else if (ctr[ctr_width-1 : ctr_width-3] == 7 && dir == 0) - dir <= 1'b1; - pwm_ctr <= pwm_ctr + 1'b1; - end - - reg [9:0] brightness [0:7]; - localparam bright_max = 2**10 - 1; - reg [7:0] led_reg; - - genvar i; - generate - for (i = 0; i < 8; i=i+1) begin - always @ (posedge clk) begin - if (ctr[ctr_width-1 : ctr_width-3] == i) - brightness[i] <= bright_max; - else if (ctr[ctr_width-1 : ctr_width-3] == (i - 1)) - brightness[i] <= ctr[ctr_width-4:ctr_width-13]; - else if (ctr[ctr_width-1 : ctr_width-3] == (i + 1)) - brightness[i] <= bright_max - ctr[ctr_width-4:ctr_width-13]; - else - brightness[i] <= 0; - led_reg[i] <= pwm_ctr < brightness[i]; - end - end - endgenerate - - assign led = led_reg; - - // Tie GPIO0, keep board from rebooting - assign gpio0 = 1'b1; - -endmodule diff --git a/ecp5/synth/blinky.ys b/ecp5/synth/blinky.ys deleted file mode 100644 index fb359380..00000000 --- a/ecp5/synth/blinky.ys +++ /dev/null @@ -1,2 +0,0 @@ -read_verilog blinky.v -synth_ecp5 -noccu2 -nomux -nodram -json blinky.json diff --git a/ecp5/synth/ulx3s_empty.config b/ecp5/synth/ulx3s_empty.config deleted file mode 100644 index 815e7f0d..00000000 --- a/ecp5/synth/ulx3s_empty.config +++ /dev/null @@ -1,439 +0,0 @@ -.device LFE5U-45F - -.tile CIB_R10C3:PVT_COUNT2 -unknown: F2B0 -unknown: F3B0 -unknown: F5B0 -unknown: F11B0 -unknown: F13B0 - -.tile CIB_R5C1:CIB_PLL1 -enum: CIB.JA3MUX 0 -enum: CIB.JB3MUX 0 - - -.tile CIB_R5C89:CIB_PLL1 -enum: CIB.JA3MUX 0 -enum: CIB.JB3MUX 0 - - -.tile CIB_R70C3:CIB_PLL3 -enum: CIB.JA3MUX 0 -enum: CIB.JB3MUX 0 - - -.tile CIB_R70C42:VCIB_DCU0 -enum: CIB.JA1MUX 0 -enum: CIB.JA3MUX 0 -enum: CIB.JA5MUX 0 -enum: CIB.JA7MUX 0 -enum: CIB.JB1MUX 0 -enum: CIB.JB3MUX 0 -enum: CIB.JB5MUX 0 -enum: CIB.JB7MUX 0 -enum: CIB.JC0MUX 0 -enum: CIB.JC2MUX 0 -enum: CIB.JC4MUX 0 -enum: CIB.JC6MUX 0 -enum: CIB.JD0MUX 0 -enum: CIB.JD2MUX 0 -enum: CIB.JD4MUX 0 -enum: CIB.JD6MUX 0 - - -.tile CIB_R70C43:VCIB_DCUA -enum: CIB.JA1MUX 0 -enum: CIB.JA3MUX 0 -enum: CIB.JA5MUX 0 -enum: CIB.JA7MUX 0 -enum: CIB.JB1MUX 0 -enum: CIB.JB3MUX 0 -enum: CIB.JB5MUX 0 -enum: CIB.JB7MUX 0 -enum: CIB.JC0MUX 0 -enum: CIB.JC2MUX 0 -enum: CIB.JC4MUX 0 -enum: CIB.JC6MUX 0 -enum: CIB.JD0MUX 0 -enum: CIB.JD2MUX 0 -enum: CIB.JD4MUX 0 -enum: CIB.JD6MUX 0 - - -.tile CIB_R70C44:VCIB_DCUB -enum: CIB.JA1MUX 0 -enum: CIB.JA3MUX 0 -enum: CIB.JA5MUX 0 -enum: CIB.JA7MUX 0 -enum: CIB.JB1MUX 0 -enum: CIB.JB3MUX 0 -enum: CIB.JB5MUX 0 -enum: CIB.JB7MUX 0 -enum: CIB.JC0MUX 0 -enum: CIB.JC2MUX 0 -enum: CIB.JC4MUX 0 -enum: CIB.JC6MUX 0 -enum: CIB.JD0MUX 0 -enum: CIB.JD2MUX 0 -enum: CIB.JD4MUX 0 -enum: CIB.JD6MUX 0 - - -.tile CIB_R70C45:VCIB_DCUC -enum: CIB.JA1MUX 0 -enum: CIB.JA3MUX 0 -enum: CIB.JA5MUX 0 -enum: CIB.JA7MUX 0 -enum: CIB.JB1MUX 0 -enum: CIB.JB3MUX 0 -enum: CIB.JB5MUX 0 -enum: CIB.JB7MUX 0 -enum: CIB.JC0MUX 0 -enum: CIB.JC2MUX 0 -enum: CIB.JC4MUX 0 -enum: CIB.JC6MUX 0 -enum: CIB.JD0MUX 0 -enum: CIB.JD2MUX 0 -enum: CIB.JD4MUX 0 -enum: CIB.JD6MUX 0 - - -.tile CIB_R70C46:VCIB_DCUD -enum: CIB.JA1MUX 0 -enum: CIB.JA5MUX 0 -enum: CIB.JA7MUX 0 -enum: CIB.JB1MUX 0 -enum: CIB.JB3MUX 0 -enum: CIB.JB5MUX 0 -enum: CIB.JB7MUX 0 -enum: CIB.JC0MUX 0 -enum: CIB.JC2MUX 0 -enum: CIB.JC4MUX 0 -enum: CIB.JC6MUX 0 -enum: CIB.JD0MUX 0 -enum: CIB.JD2MUX 0 -enum: CIB.JD4MUX 0 -enum: CIB.JD6MUX 0 - - -.tile CIB_R70C47:VCIB_DCUF -enum: CIB.JA1MUX 0 -enum: CIB.JA3MUX 0 -enum: CIB.JA5MUX 0 -enum: CIB.JA7MUX 0 -enum: CIB.JB1MUX 0 -enum: CIB.JB3MUX 0 -enum: CIB.JB5MUX 0 -enum: CIB.JB7MUX 0 -enum: CIB.JC0MUX 0 -enum: CIB.JC2MUX 0 -enum: CIB.JC4MUX 0 -enum: CIB.JC6MUX 0 -enum: CIB.JD0MUX 0 -enum: CIB.JD2MUX 0 -enum: CIB.JD4MUX 0 -enum: CIB.JD6MUX 0 - - -.tile CIB_R70C48:VCIB_DCU3 -enum: CIB.JA5MUX 0 -enum: CIB.JA7MUX 0 -enum: CIB.JB1MUX 0 -enum: CIB.JB3MUX 0 -enum: CIB.JB5MUX 0 -enum: CIB.JB7MUX 0 -enum: CIB.JC0MUX 0 -enum: CIB.JC4MUX 0 -enum: CIB.JC6MUX 0 -enum: CIB.JD0MUX 0 -enum: CIB.JD2MUX 0 -enum: CIB.JD4MUX 0 -enum: CIB.JD6MUX 0 - - -.tile CIB_R70C49:VCIB_DCU2 -enum: CIB.JB1MUX 0 -enum: CIB.JB3MUX 0 -enum: CIB.JB5MUX 0 -enum: CIB.JB7MUX 0 -enum: CIB.JD0MUX 0 -enum: CIB.JD2MUX 0 -enum: CIB.JD4MUX 0 -enum: CIB.JD6MUX 0 - - -.tile CIB_R70C50:VCIB_DCUG -enum: CIB.JB1MUX 0 -enum: CIB.JB3MUX 0 -enum: CIB.JB5MUX 0 -enum: CIB.JB7MUX 0 -enum: CIB.JD0MUX 0 -enum: CIB.JD2MUX 0 -enum: CIB.JD4MUX 0 -enum: CIB.JD6MUX 0 - - -.tile CIB_R70C51:VCIB_DCUH -enum: CIB.JB1MUX 0 -enum: CIB.JB3MUX 0 -enum: CIB.JB5MUX 0 -enum: CIB.JB7MUX 0 -enum: CIB.JD0MUX 0 -enum: CIB.JD2MUX 0 -enum: CIB.JD4MUX 0 -enum: CIB.JD6MUX 0 - - -.tile CIB_R70C52:VCIB_DCUI -enum: CIB.JB1MUX 0 -enum: CIB.JB3MUX 0 -enum: CIB.JB5MUX 0 -enum: CIB.JB7MUX 0 -enum: CIB.JD0MUX 0 -enum: CIB.JD2MUX 0 -enum: CIB.JD4MUX 0 -enum: CIB.JD6MUX 0 - - -.tile CIB_R70C53:VCIB_DCU1 -enum: CIB.JB1MUX 0 -enum: CIB.JB3MUX 0 -enum: CIB.JB5MUX 0 -enum: CIB.JD0MUX 0 -enum: CIB.JD2MUX 0 - - -.tile CIB_R70C69:VCIB_DCU0 -enum: CIB.JA1MUX 0 -enum: CIB.JA3MUX 0 -enum: CIB.JA5MUX 0 -enum: CIB.JA7MUX 0 -enum: CIB.JB1MUX 0 -enum: CIB.JB3MUX 0 -enum: CIB.JB5MUX 0 -enum: CIB.JB7MUX 0 -enum: CIB.JC0MUX 0 -enum: CIB.JC2MUX 0 -enum: CIB.JC4MUX 0 -enum: CIB.JC6MUX 0 -enum: CIB.JD0MUX 0 -enum: CIB.JD2MUX 0 -enum: CIB.JD4MUX 0 -enum: CIB.JD6MUX 0 - - -.tile CIB_R70C6:CIB_EFB0 -enum: CIB.JB3MUX 0 -enum: CIB.JC6MUX 0 -enum: CIB.JD6MUX 0 - - -.tile CIB_R70C70:VCIB_DCUA -enum: CIB.JA1MUX 0 -enum: CIB.JA3MUX 0 -enum: CIB.JA5MUX 0 -enum: CIB.JA7MUX 0 -enum: CIB.JB1MUX 0 -enum: CIB.JB3MUX 0 -enum: CIB.JB5MUX 0 -enum: CIB.JB7MUX 0 -enum: CIB.JC0MUX 0 -enum: CIB.JC2MUX 0 -enum: CIB.JC4MUX 0 -enum: CIB.JC6MUX 0 -enum: CIB.JD0MUX 0 -enum: CIB.JD2MUX 0 -enum: CIB.JD4MUX 0 -enum: CIB.JD6MUX 0 - - -.tile CIB_R70C71:VCIB_DCUB -enum: CIB.JA1MUX 0 -enum: CIB.JA3MUX 0 -enum: CIB.JA5MUX 0 -enum: CIB.JA7MUX 0 -enum: CIB.JB1MUX 0 -enum: CIB.JB3MUX 0 -enum: CIB.JB5MUX 0 -enum: CIB.JB7MUX 0 -enum: CIB.JC0MUX 0 -enum: CIB.JC2MUX 0 -enum: CIB.JC4MUX 0 -enum: CIB.JC6MUX 0 -enum: CIB.JD0MUX 0 -enum: CIB.JD2MUX 0 -enum: CIB.JD4MUX 0 -enum: CIB.JD6MUX 0 - - -.tile CIB_R70C72:VCIB_DCUC -enum: CIB.JA1MUX 0 -enum: CIB.JA3MUX 0 -enum: CIB.JA5MUX 0 -enum: CIB.JA7MUX 0 -enum: CIB.JB1MUX 0 -enum: CIB.JB3MUX 0 -enum: CIB.JB5MUX 0 -enum: CIB.JB7MUX 0 -enum: CIB.JC0MUX 0 -enum: CIB.JC2MUX 0 -enum: CIB.JC4MUX 0 -enum: CIB.JC6MUX 0 -enum: CIB.JD0MUX 0 -enum: CIB.JD2MUX 0 -enum: CIB.JD4MUX 0 -enum: CIB.JD6MUX 0 - - -.tile CIB_R70C73:VCIB_DCUD -enum: CIB.JA1MUX 0 -enum: CIB.JA5MUX 0 -enum: CIB.JA7MUX 0 -enum: CIB.JB1MUX 0 -enum: CIB.JB3MUX 0 -enum: CIB.JB5MUX 0 -enum: CIB.JB7MUX 0 -enum: CIB.JC0MUX 0 -enum: CIB.JC2MUX 0 -enum: CIB.JC4MUX 0 -enum: CIB.JC6MUX 0 -enum: CIB.JD0MUX 0 -enum: CIB.JD2MUX 0 -enum: CIB.JD4MUX 0 -enum: CIB.JD6MUX 0 - - -.tile CIB_R70C74:VCIB_DCUF -enum: CIB.JA1MUX 0 -enum: CIB.JA3MUX 0 -enum: CIB.JA5MUX 0 -enum: CIB.JA7MUX 0 -enum: CIB.JB1MUX 0 -enum: CIB.JB3MUX 0 -enum: CIB.JB5MUX 0 -enum: CIB.JB7MUX 0 -enum: CIB.JC0MUX 0 -enum: CIB.JC2MUX 0 -enum: CIB.JC4MUX 0 -enum: CIB.JC6MUX 0 -enum: CIB.JD0MUX 0 -enum: CIB.JD2MUX 0 -enum: CIB.JD4MUX 0 -enum: CIB.JD6MUX 0 - - -.tile CIB_R70C75:VCIB_DCU3 -enum: CIB.JA5MUX 0 -enum: CIB.JA7MUX 0 -enum: CIB.JB1MUX 0 -enum: CIB.JB3MUX 0 -enum: CIB.JB5MUX 0 -enum: CIB.JB7MUX 0 -enum: CIB.JC0MUX 0 -enum: CIB.JC4MUX 0 -enum: CIB.JC6MUX 0 -enum: CIB.JD0MUX 0 -enum: CIB.JD2MUX 0 -enum: CIB.JD4MUX 0 -enum: CIB.JD6MUX 0 - - -.tile CIB_R70C76:VCIB_DCU2 -enum: CIB.JB1MUX 0 -enum: CIB.JB3MUX 0 -enum: CIB.JB5MUX 0 -enum: CIB.JB7MUX 0 -enum: CIB.JD0MUX 0 -enum: CIB.JD2MUX 0 -enum: CIB.JD4MUX 0 -enum: CIB.JD6MUX 0 - - -.tile CIB_R70C77:VCIB_DCUG -enum: CIB.JB1MUX 0 -enum: CIB.JB3MUX 0 -enum: CIB.JB5MUX 0 -enum: CIB.JB7MUX 0 -enum: CIB.JD0MUX 0 -enum: CIB.JD2MUX 0 -enum: CIB.JD4MUX 0 -enum: CIB.JD6MUX 0 - - -.tile CIB_R70C78:VCIB_DCUH -enum: CIB.JB1MUX 0 -enum: CIB.JB3MUX 0 -enum: CIB.JB5MUX 0 -enum: CIB.JB7MUX 0 -enum: CIB.JD0MUX 0 -enum: CIB.JD2MUX 0 -enum: CIB.JD4MUX 0 -enum: CIB.JD6MUX 0 - - -.tile CIB_R70C79:VCIB_DCUI -enum: CIB.JB1MUX 0 -enum: CIB.JB3MUX 0 -enum: CIB.JB5MUX 0 -enum: CIB.JB7MUX 0 -enum: CIB.JD0MUX 0 -enum: CIB.JD2MUX 0 -enum: CIB.JD4MUX 0 -enum: CIB.JD6MUX 0 - - -.tile CIB_R70C7:CIB_EFB1 -enum: CIB.JA3MUX 0 -enum: CIB.JA4MUX 0 -enum: CIB.JA5MUX 0 -enum: CIB.JA6MUX 0 -enum: CIB.JB3MUX 0 -enum: CIB.JB4MUX 0 -enum: CIB.JB5MUX 0 -enum: CIB.JB6MUX 0 -enum: CIB.JC3MUX 0 -enum: CIB.JC4MUX 0 -enum: CIB.JC5MUX 0 -enum: CIB.JD3MUX 0 -enum: CIB.JD4MUX 0 -enum: CIB.JD5MUX 0 - - -.tile CIB_R70C80:VCIB_DCU1 -enum: CIB.JB1MUX 0 -enum: CIB.JB3MUX 0 -enum: CIB.JB5MUX 0 -enum: CIB.JD0MUX 0 -enum: CIB.JD2MUX 0 - - -.tile CIB_R70C87:CIB_PLL3 -enum: CIB.JA3MUX 0 -enum: CIB.JB3MUX 0 - - -.tile MIB_R10C40:CMUX_UL_0 -arc: G_DCS0CLK0 G_VPFN0000 - - -.tile MIB_R10C41:CMUX_UR_0 -arc: G_DCS0CLK1 G_VPFN0000 - - -.tile MIB_R58C40:CMUX_LL_0 -arc: G_DCS1CLK0 G_VPFN0000 - - -.tile MIB_R58C41:CMUX_LR_0 -arc: G_DCS1CLK1 G_VPFN0000 - - -.tile MIB_R71C4:EFB0_PICB0 -unknown: F54B1 -unknown: F56B1 -unknown: F82B1 -unknown: F94B1 - -.tile MIB_R71C3:BANKREF8 -unknown: F18B0 - From 4916eb9bb16b09a547e97e7aa18444660d44ddd8 Mon Sep 17 00:00:00 2001 From: David Shah Date: Tue, 26 Nov 2019 21:36:29 +0000 Subject: [PATCH 35/81] HeAP: more realistic timeout threshold Signed-off-by: David Shah --- common/placer_heap.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/placer_heap.cc b/common/placer_heap.cc index 4a380a04..80ce67b2 100644 --- a/common/placer_heap.cc +++ b/common/placer_heap.cc @@ -800,7 +800,7 @@ class HeAPPlacer ripup_radius = std::max(std::max(max_x, max_y), ripup_radius * 2); } - if (total_iters_noreset > std::max(50000, 1000 * int(ctx->cells.size()))) { + if (total_iters_noreset > std::max(5000, 8 * int(ctx->cells.size()))) { log_error("Unable to find legal placement for all cells, design is probably at utilisation limit.\n"); } From 1e4feed3832d8b5c98c75423d6ce941f4e0247b2 Mon Sep 17 00:00:00 2001 From: David Shah Date: Tue, 26 Nov 2019 21:47:20 +0000 Subject: [PATCH 36/81] General documentation updates Signed-off-by: David Shah --- README.md | 7 ++----- docs/faq.md | 30 +++++++++++------------------- 2 files changed, 13 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 6c639cf0..abe86caa 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,7 @@ Currently nextpnr supports: * Lattice ECP5 devices supported by [Project Trellis](https://github.com/SymbiFlow/prjtrellis) * *(experimental)* a "generic" back-end for user-defined architectures -We hope to see Xilinx 7 Series thanks to -[Project X-Ray](https://github.com/SymbiFlow/prjxray) and even more FPGA families -supported in the future. We would love your help in developing this -awesome new project! +There is some work in progress towards [support for Xilinx devices](https://github.com/daveshah1/nextpnr-xilinx/) but it is not upstream and not intended for end users at the present time. We hope to see more FPGA families supported in the future. We would love your help in developing this awesome new project! A brief (academic) paper describing the Yosys+nextpnr flow can be found on [arXiv](https://arxiv.org/abs/1903.10407). @@ -119,7 +116,7 @@ make -j$(nproc) sudo make install ``` -TBD: Getting started example for generic target. +An example of how to use the generic flow is in [generic/examples](generic/examples). See also the [Generic Architecture docs](docs/generic.md). Additional notes for building nextpnr ------------------------------------- diff --git a/docs/faq.md b/docs/faq.md index 8a102af8..fe0c7231 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -132,9 +132,8 @@ Nextpnr and other tools ### Which toolchain should I use and why? - * If you wish to do new **research** into FPGA architectures, place and route - algorithms or other similar topics, we suggest you look at using - [Verilog to Routing](https://verilogtorouting.org). + * If you wish to do new **research** into FPGA architectures, or other similar topics, we suggest you look at using + [Verilog to Routing](https://verilogtorouting.org). If you want to use nextpnr, you might also be able to use the [Generic Arch](generic.md). * If you are developing FPGA code in **Verilog** for a **Lattice iCE40** and need an open source toolchain, we suggest you use [Yosys](http://www.clifford.at/yosys/) and nextpnr. @@ -146,11 +145,7 @@ Nextpnr and other tools * If you are developing Verilog FPGA code targeted at the Lattice ECP5 and need an open source toolchain, there is also stable ECP5 support in Yosys and nextpnr. - * If you are developing FPGA code in **VHDL** you will need to use either a - version of [Yosys with Verific support](https://github.com/YosysHQ/yosys/tree/master/frontends/verific) or the vendor provided tools due - to the lack of useful open source VHDL support in Yosys. You could also look at developing - one of the experimental open source VHDL frontends, such as [yavhdl](https://github.com/rqou/yavhdl) - or [ghdlsynth-beta](https://github.com/tgingold/ghdlsynth-beta), further. + * If you are developing FPGA code in **VHDL** you may wish to look at the [ghdlsynth-beta](https://github.com/tgingold/ghdlsynth-beta) experimental VHDL frontend for Yosys. ### Why didn't you just improve [arachne-pnr](https://github.com/cseed/arachne-pnr)? @@ -161,11 +156,9 @@ that actually produced valid bitstreams. For its original purpose, it has served the community extremely well. However, it was never designed to support multiple different FPGA families, nor more -complicated timing driven placement and routing used by most commercial place and route -tools. +complicated timing driven placement and routing used by most commercial place and route tools. -It felt like extending arachne-pnr was not going to be the best path forward, so -it was decided to build nextpnr as replacement. +It felt like extending arachne-pnr was not going to be the best path forward, so it was decided to build nextpnr as replacement. ### arachne-pnr does X better! @@ -173,7 +166,8 @@ If you have a use case which prevents you from switching to nextpnr from arachne, we want to hear about it! Please create an issue and we will do our best to solve the problem! We want nextpnr to be a suitable replacement for anyone who is currently a user -of arachne-pnr. +of arachne-pnr, and it is important to bear in mind that arachne-pnr is no +longer in active development. ### Why are you not just contributing to [Verilog to Routing](https://verilogtorouting.org)? @@ -190,8 +184,7 @@ for current FPGAs. We also believe that support for real architectures will enable interesting new research. nextpnr (like all place and route tools) depends heavily on -research groups like the VtR developers to investigate and push forward FPGA placement and routing -algorithms in new and exciting ways. +research groups like the VtR developers to investigate and push forward FPGA placement and routing algorithms in new and exciting ways. #### What is VPR? @@ -219,15 +212,14 @@ enable support for creation of bitstreams for these parts. the bitstream format for the Xilinx Series 7 series of FPGAs. It also includes tooling around bitstream generation for these parts. -While nextpnr currently does **not** support these Xilinx parts, we expect it -will soon be using Project X-Ray in a similar manner to Project Trellis. +While upstream nextpnr currently does **not** support these Xilinx parts, we expect it might soon be using Project X-Ray in a similar manner to Project Trellis. ### What is [Project IceStorm](http://www.clifford.at/icestorm/)? [Project IceStorm](http://www.clifford.at/icestorm/) is both a project to document the bitstream for the Lattice iCE40 series of parts **and** a full -flow including Yosys and arachne-pnr for converting Verilog into a bitstream for -these parts. +flow including Yosys and arachne-pnr for converting Verilog into a bitstream +for these parts. As the open source community now has support for multiple different FPGA parts, in the nextpnr documentation we generally use Project IceStorm to mean the database and From b7079d159b0ca9db8c4cab350f2ee08ab65810b7 Mon Sep 17 00:00:00 2001 From: David Shah Date: Tue, 26 Nov 2019 21:52:02 +0000 Subject: [PATCH 37/81] Update generic arch docs Signed-off-by: David Shah --- docs/generic.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/generic.md b/docs/generic.md index d6ddbfb6..a635f98c 100644 --- a/docs/generic.md +++ b/docs/generic.md @@ -72,7 +72,7 @@ Sets the number of input pins a LUT in the architecture has. Only affects the ge ### void setDelayScaling(double scale, double offset); -Set the linear scaling vs distance and fixed offset (both values in nanoseconds) for routing delay estimates. +Set the linear scaling vs distance and fixed offset (both values in nanoseconds) for routing delay estimates. Delay estimates that correlate to pip delays, even if they have no bearing to reality, are important for reasonable routing runtime. ### void addCellTimingClock(IdString cell, IdString port); @@ -96,17 +96,19 @@ Specify clock-to-out time for a port of a cell, and set the timing class of that ## Generic Packer -The generic packer combines K-input LUTs (`LUT` cells) and simple D-type flip flops (`DFF` cells) (posedge clock only, no set/reset or enable) into a `GENERIC_SLICE` cell. It also inserts `GENERIC_IOB`s onto any top level IO pins without an IO buffer. +The generic packer combines K-input LUTs (`LUT` cells) and simple D-type flip flops (`DFF` cells) (posedge clock only, no set/reset or enable) into a `GENERIC_SLICE` cell. It also inserts `GENERIC_IOB`s onto any top level IO pins without an IO buffer. Constrained IOBs can be implemented by instantiating `GENERIC_IOB` and setting the `BEL` attribute to an IO location. Thus, the architecture should provide bels with the following ports in order to use the generic packer: - - `GENERIC_SLICE` bels with `CLK` input, `I[0]` .. `I[K-1]` LUT inputs and `Q` LUT/FF output (N.B. both LUT and FF outputs are not available at the same time) + - `GENERIC_SLICE` bels with `CLK` input, `I[0]` .. `I[K-1]` LUT inputs, `F` LUT output and `Q` FF output (N.B. both LUT and FF outputs are not available at the same time, to represent the constraints of some FPGAs). - `GENERIC_IOB` bels with `I` output buffer input, `EN` output enable input, and `O` input buffer output. See [prims.v](../generic/synth/prims.v) for Verilog simulation models for all these cells. [synth_generic.tcl](../generic/synth/synth_generic.tcl) can be used with Yosys to perform synthesis to the generic `LUT` and `DFF` cells which the generic packer supports. Invoke it using `tcl synth_generic.tcl K out.json` where _K_ is the number of LUT inputs and _out.json_ the name of the JSON file to write. +The generic packer in its current state is intended for experimentation and proof-of-concept tests. It is _not_ intended to make use of all FPGA features or support complex designs. In these cases a proper [Arch API](archapi.md) implementation is strongly recommended. + ## Validity Checks The following constraints are enforced by the generic architecture during placement. From 2957eb7cc926286b0943ef0deb97f7f3e8a7e615 Mon Sep 17 00:00:00 2001 From: David Shah Date: Wed, 27 Nov 2019 14:33:35 +0000 Subject: [PATCH 38/81] generic: Improve error handling when Wire/Pip/Bel is not found Signed-off-by: David Shah --- generic/arch.cc | 66 +++++++++++++++++++++++++++++++++---------------- generic/arch.h | 5 ++++ 2 files changed, 50 insertions(+), 21 deletions(-) diff --git a/generic/arch.cc b/generic/arch.cc index 14d15115..c1c01d26 100644 --- a/generic/arch.cc +++ b/generic/arch.cc @@ -27,6 +27,30 @@ NEXTPNR_NAMESPACE_BEGIN +WireInfo &Arch::wire_info(IdString wire) +{ + auto w = wires.find(wire); + if (w == wires.end()) + NPNR_ASSERT_FALSE_STR("no wire named " + wire.str(this)); + return w->second; +} + +PipInfo &Arch::pip_info(IdString pip) +{ + auto p = pips.find(pip); + if (p == pips.end()) + NPNR_ASSERT_FALSE_STR("no pip named " + pip.str(this)); + return p->second; +} + +BelInfo &Arch::bel_info(IdString bel) +{ + auto b = bels.find(bel); + if (b == bels.end()) + NPNR_ASSERT_FALSE_STR("no bel named " + bel.str(this)); + return b->second; +} + void Arch::addWire(IdString name, IdString type, int x, int y) { NPNR_ASSERT(wires.count(name) == 0); @@ -50,8 +74,8 @@ void Arch::addPip(IdString name, IdString type, IdString srcWire, IdString dstWi pi.delay = delay; pi.loc = loc; - wires.at(srcWire).downhill.push_back(name); - wires.at(dstWire).uphill.push_back(name); + wire_info(srcWire).downhill.push_back(name); + wire_info(dstWire).uphill.push_back(name); pip_ids.push_back(name); if (int(tilePipDimZ.size()) <= loc.x) @@ -75,7 +99,7 @@ void Arch::addAlias(IdString name, IdString type, IdString srcWire, IdString dst pi.dstWire = dstWire; pi.delay = delay; - wires.at(srcWire).aliases.push_back(name); + wire_info(srcWire).aliases.push_back(name); pip_ids.push_back(name); } @@ -115,38 +139,38 @@ void Arch::addBel(IdString name, IdString type, Loc loc, bool gb) void Arch::addBelInput(IdString bel, IdString name, IdString wire) { - NPNR_ASSERT(bels.at(bel).pins.count(name) == 0); - PinInfo &pi = bels.at(bel).pins[name]; + NPNR_ASSERT(bel_info(bel).pins.count(name) == 0); + PinInfo &pi = bel_info(bel).pins[name]; pi.name = name; pi.wire = wire; pi.type = PORT_IN; - wires.at(wire).downhill_bel_pins.push_back(BelPin{bel, name}); - wires.at(wire).bel_pins.push_back(BelPin{bel, name}); + wire_info(wire).downhill_bel_pins.push_back(BelPin{bel, name}); + wire_info(wire).bel_pins.push_back(BelPin{bel, name}); } void Arch::addBelOutput(IdString bel, IdString name, IdString wire) { - NPNR_ASSERT(bels.at(bel).pins.count(name) == 0); - PinInfo &pi = bels.at(bel).pins[name]; + NPNR_ASSERT(bel_info(bel).pins.count(name) == 0); + PinInfo &pi = bel_info(bel).pins[name]; pi.name = name; pi.wire = wire; pi.type = PORT_OUT; - wires.at(wire).uphill_bel_pin = BelPin{bel, name}; - wires.at(wire).bel_pins.push_back(BelPin{bel, name}); + wire_info(wire).uphill_bel_pin = BelPin{bel, name}; + wire_info(wire).bel_pins.push_back(BelPin{bel, name}); } void Arch::addBelInout(IdString bel, IdString name, IdString wire) { - NPNR_ASSERT(bels.at(bel).pins.count(name) == 0); - PinInfo &pi = bels.at(bel).pins[name]; + NPNR_ASSERT(bel_info(bel).pins.count(name) == 0); + PinInfo &pi = bel_info(bel).pins[name]; pi.name = name; pi.wire = wire; pi.type = PORT_INOUT; - wires.at(wire).downhill_bel_pins.push_back(BelPin{bel, name}); - wires.at(wire).bel_pins.push_back(BelPin{bel, name}); + wire_info(wire).downhill_bel_pins.push_back(BelPin{bel, name}); + wire_info(wire).bel_pins.push_back(BelPin{bel, name}); } void Arch::addGroupBel(IdString group, IdString bel) { groups[group].bels.push_back(bel); } @@ -165,19 +189,19 @@ void Arch::addDecalGraphic(DecalId decal, const GraphicElement &graphic) void Arch::setWireDecal(WireId wire, DecalXY decalxy) { - wires.at(wire).decalxy = decalxy; + wire_info(wire).decalxy = decalxy; refreshUiWire(wire); } void Arch::setPipDecal(PipId pip, DecalXY decalxy) { - pips.at(pip).decalxy = decalxy; + pip_info(pip).decalxy = decalxy; refreshUiPip(pip); } void Arch::setBelDecal(BelId bel, DecalXY decalxy) { - bels.at(bel).decalxy = decalxy; + bel_info(bel).decalxy = decalxy; refreshUiBel(bel); } @@ -187,11 +211,11 @@ void Arch::setGroupDecal(GroupId group, DecalXY decalxy) refreshUiGroup(group); } -void Arch::setWireAttr(IdString wire, IdString key, const std::string &value) { wires.at(wire).attrs[key] = value; } +void Arch::setWireAttr(IdString wire, IdString key, const std::string &value) { wire_info(wire).attrs[key] = value; } -void Arch::setPipAttr(IdString pip, IdString key, const std::string &value) { pips.at(pip).attrs[key] = value; } +void Arch::setPipAttr(IdString pip, IdString key, const std::string &value) { pip_info(pip).attrs[key] = value; } -void Arch::setBelAttr(IdString bel, IdString key, const std::string &value) { bels.at(bel).attrs[key] = value; } +void Arch::setBelAttr(IdString bel, IdString key, const std::string &value) { bel_info(bel).attrs[key] = value; } void Arch::setLutK(int K) { args.K = K; } diff --git a/generic/arch.h b/generic/arch.h index e9d3593c..444d2636 100644 --- a/generic/arch.h +++ b/generic/arch.h @@ -122,6 +122,11 @@ struct Arch : BaseCtx std::unordered_map bels; std::unordered_map groups; + // These functions include useful errors if not found + WireInfo &wire_info(IdString wire); + PipInfo &pip_info(IdString wire); + BelInfo &bel_info(IdString wire); + std::vector bel_ids, wire_ids, pip_ids; std::unordered_map bel_by_loc; From 2b3dda7535c0a5cab83fc58d1cac40338a1f9400 Mon Sep 17 00:00:00 2001 From: David Shah Date: Wed, 27 Nov 2019 14:41:35 +0000 Subject: [PATCH 39/81] generic: Add support for disabling IOB insertion Signed-off-by: David Shah --- generic/main.cc | 6 +++++- generic/pack.cc | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/generic/main.cc b/generic/main.cc index 7dfc6aa7..bb780996 100644 --- a/generic/main.cc +++ b/generic/main.cc @@ -46,6 +46,7 @@ po::options_description GenericCommandHandler::getArchOptions() { po::options_description specific("Architecture specific options"); specific.add_options()("generic", "set device type to generic"); + specific.add_options()("no-iobs", "disable automatic IO buffer insertion"); return specific; } @@ -59,7 +60,10 @@ std::unique_ptr GenericCommandHandler::createContext(std::unordered_map if (arch_name != "generic") log_error("Unsuported architecture '%s'.\n", arch_name.c_str()); } - return std::unique_ptr(new Context(chipArgs)); + auto ctx = std::unique_ptr(new Context(chipArgs)); + if (vm.count("no-iobs")) + ctx->settings[ctx->id("disable_iobs")] = Property::State::S1; + return ctx; } int main(int argc, char *argv[]) diff --git a/generic/pack.cc b/generic/pack.cc index 69f248d2..19266aba 100644 --- a/generic/pack.cc +++ b/generic/pack.cc @@ -249,6 +249,10 @@ static void pack_io(Context *ctx) delete_nets.insert(net2->name); } } + } else if (bool_or_default(ctx->settings, ctx->id("disable_iobs"))) { + // No IO buffer insertion; just remove nextpnr_[io]buf + for (auto &p : ci->ports) + disconnect_port(ctx, ci, p.first); } else { // Create a GENERIC_IOB buffer std::unique_ptr ice_cell = From 6562edc98e212516f466a70e949c71b2d48cc75d Mon Sep 17 00:00:00 2001 From: David Shah Date: Wed, 27 Nov 2019 15:16:28 +0000 Subject: [PATCH 40/81] jsonwrite: Fix bus cell ports Signed-off-by: David Shah --- json/jsonwrite.cc | 56 +++++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/json/jsonwrite.cc b/json/jsonwrite.cc index 3d3b70e4..9723d3b2 100644 --- a/json/jsonwrite.cc +++ b/json/jsonwrite.cc @@ -73,14 +73,17 @@ struct PortGroup PortType dir; }; -std::vector group_ports(Context *ctx) +std::vector group_ports(Context *ctx, const std::unordered_map &ports, + bool is_cell = false) { std::vector groups; std::unordered_map base_to_group; - for (auto &pair : ctx->ports) { + for (auto &pair : ports) { std::string name = pair.second.name.str(ctx); if ((name.back() != ']') || (name.find('[') == std::string::npos)) { - groups.push_back({name, {pair.first.index}, pair.second.type}); + groups.push_back({name, + {is_cell ? (pair.second.net ? pair.second.net->name.index : -1) : pair.first.index}, + pair.second.type}); } else { int off1 = int(name.find_last_of('[')); std::string basename = name.substr(0, off1); @@ -95,26 +98,27 @@ std::vector group_ports(Context *ctx) if (int(grp.bits.size()) <= index) grp.bits.resize(index + 1, -1); NPNR_ASSERT(grp.bits.at(index) == -1); - grp.bits.at(index) = pair.second.net ? pair.second.net->name.index : pair.first.index; + grp.bits.at(index) = pair.second.net ? pair.second.net->name.index : (is_cell ? -1 : pair.first.index); } } return groups; -}; +} -std::string format_port_bits(const PortGroup &port) +std::string format_port_bits(const PortGroup &port, int &dummy_idx) { std::stringstream s; s << "[ "; bool first = true; - for (auto bit : port.bits) { - if (!first) - s << ", "; - if (bit == -1) - s << "\"x\""; - else - s << bit; - first = false; - } + if (port.bits.size() != 1 || port.bits.at(0) != -1) // skip single disconnected ports + for (auto bit : port.bits) { + if (!first) + s << ", "; + if (bit == -1) + s << (++dummy_idx); + else + s << bit; + first = false; + } s << " ]"; return s.str(); } @@ -122,6 +126,7 @@ std::string format_port_bits(const PortGroup &port) void write_module(std::ostream &f, Context *ctx) { auto val = ctx->attrs.find(ctx->id("module")); + int dummy_idx = int(ctx->idstring_idx_to_str->size()) + 1000; if (val != ctx->attrs.end()) f << stringf(" %s: {\n", get_string(val->second.as_string()).c_str()); else @@ -134,14 +139,14 @@ void write_module(std::ostream &f, Context *ctx) f << stringf("\n },\n"); f << stringf(" \"ports\": {"); - auto ports = group_ports(ctx); + auto ports = group_ports(ctx, ctx->ports); bool first = true; for (auto &port : ports) { f << stringf("%s\n", first ? "" : ","); f << stringf(" %s: {\n", get_string(port.name).c_str()); f << stringf(" \"direction\": \"%s\",\n", port.dir == PORT_IN ? "input" : port.dir == PORT_INOUT ? "inout" : "output"); - f << stringf(" \"bits\": %s\n", format_port_bits(port).c_str()); + f << stringf(" \"bits\": %s\n", format_port_bits(port, dummy_idx).c_str()); f << stringf(" }"); first = false; } @@ -151,6 +156,7 @@ void write_module(std::ostream &f, Context *ctx) first = true; for (auto &pair : ctx->cells) { auto &c = pair.second; + auto cell_ports = group_ports(ctx, c->ports, true); f << stringf("%s\n", first ? "" : ","); f << stringf(" %s: {\n", get_name(c->name, ctx).c_str()); f << stringf(" \"hide_name\": %s,\n", c->name.c_str(ctx)[0] == '$' ? "1" : "0"); @@ -163,24 +169,18 @@ void write_module(std::ostream &f, Context *ctx) f << stringf("\n },\n"); f << stringf(" \"port_directions\": {"); bool first2 = true; - for (auto &conn : c->ports) { - auto &p = conn.second; - std::string direction = (p.type == PORT_IN) ? "input" : (p.type == PORT_OUT) ? "output" : "inout"; + for (auto &pg : cell_ports) { + std::string direction = (pg.dir == PORT_IN) ? "input" : (pg.dir == PORT_OUT) ? "output" : "inout"; f << stringf("%s\n", first2 ? "" : ","); - f << stringf(" %s: \"%s\"", get_name(conn.first, ctx).c_str(), direction.c_str()); + f << stringf(" %s: \"%s\"", get_string(pg.name).c_str(), direction.c_str()); first2 = false; } f << stringf("\n },\n"); f << stringf(" \"connections\": {"); first2 = true; - for (auto &conn : c->ports) { - auto &p = conn.second; + for (auto &pg : cell_ports) { f << stringf("%s\n", first2 ? "" : ","); - if (p.net) - f << stringf(" %s: [ %d ]", get_name(conn.first, ctx).c_str(), p.net->name.index); - else - f << stringf(" %s: [ ]", get_name(conn.first, ctx).c_str()); - + f << stringf(" %s: %s", get_string(pg.name).c_str(), format_port_bits(pg, dummy_idx).c_str()); first2 = false; } f << stringf("\n }\n"); From 2f56b989598def4682b29ccfe3bbe6f540e4e12a Mon Sep 17 00:00:00 2001 From: David Shah Date: Wed, 27 Nov 2019 15:17:53 +0000 Subject: [PATCH 41/81] generic: Add support for post-PnR simulation Signed-off-by: David Shah --- generic/examples/.gitignore | 3 +++ generic/examples/blinky.v | 13 +++++++----- generic/examples/blinky_tb.v | 38 ++++++++++++++++++++++++++++++++++++ generic/examples/simtest.sh | 7 +++++++ generic/synth/prims.v | 13 ++++++++++-- 5 files changed, 67 insertions(+), 7 deletions(-) create mode 100644 generic/examples/blinky_tb.v create mode 100755 generic/examples/simtest.sh diff --git a/generic/examples/.gitignore b/generic/examples/.gitignore index 38e95de5..ad2fba28 100644 --- a/generic/examples/.gitignore +++ b/generic/examples/.gitignore @@ -1,3 +1,6 @@ blinky.fasm __pycache__ *.pyc +pnrblinky.v +/blinky_simtest +*.vcd diff --git a/generic/examples/blinky.v b/generic/examples/blinky.v index b7cb1b86..42becb72 100644 --- a/generic/examples/blinky.v +++ b/generic/examples/blinky.v @@ -1,9 +1,12 @@ -module top(input clk, output reg [7:0] leds); +module top(input clk, rst, output reg [7:0] leds); -reg [25:0] ctr; +reg [7:0] ctr; always @(posedge clk) - ctr <= ctr + 1'b1; + if (rst) + ctr <= 8'h00; + else + ctr <= ctr + 1'b1; -assign leds = ctr[25:18]; +assign leds = ctr; -endmodule \ No newline at end of file +endmodule diff --git a/generic/examples/blinky_tb.v b/generic/examples/blinky_tb.v new file mode 100644 index 00000000..f9925e6f --- /dev/null +++ b/generic/examples/blinky_tb.v @@ -0,0 +1,38 @@ +`timescale 1ns / 1ps +module blinky_tb; + +reg clk = 1'b0, rst = 1'b0; +reg [7:0] ctr_gold = 8'h00; +wire [7:0] ctr_gate; +top dut_i(.clk(clk), .rst(rst), .leds(ctr_gate)); + +task oneclk; + begin + clk = 1'b1; + #10; + clk = 1'b0; + #10; + end +endtask + +initial begin + $dumpfile("blinky_simtest.vcd"); + $dumpvars(0, blinky_tb); + #100; + rst = 1'b1; + repeat (5) oneclk; + #5 + rst = 1'b0; + #5 + repeat (500) begin + if (ctr_gold !== ctr_gate) begin + $display("mismatch gold=%b gate=%b", ctr_gold, ctr_gate); + $stop; + end + oneclk; + ctr_gold = ctr_gold + 1'b1; + end + $finish; +end + +endmodule diff --git a/generic/examples/simtest.sh b/generic/examples/simtest.sh new file mode 100755 index 00000000..ef328914 --- /dev/null +++ b/generic/examples/simtest.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -ex +yosys -p "tcl ../synth/synth_generic.tcl 4 blinky.json" blinky.v +${NEXTPNR:-../../nextpnr-generic} --no-iobs --pre-pack simple.py --pre-place simple_timing.py --json blinky.json --post-route bitstream.py --write pnrblinky.json +yosys -p "read_json pnrblinky.json; write_verilog -noattr -norename pnrblinky.v" +iverilog -o blinky_simtest ../synth/prims.v blinky_tb.v pnrblinky.v +vvp -N ./blinky_simtest diff --git a/generic/synth/prims.v b/generic/synth/prims.v index 1148041c..ca445e6e 100644 --- a/generic/synth/prims.v +++ b/generic/synth/prims.v @@ -2,18 +2,27 @@ module LUT #( parameter K = 4, - parameter [2**K-1:0] INIT = 0, + parameter [2**K-1:0] INIT = 0 ) ( input [K-1:0] I, output Q ); - assign Q = INIT[I]; + wire [K-1:0] I_pd; + + genvar ii; + generate + for (ii = 0; ii < K; ii = ii + 1'b1) + assign I_pd[ii] = (I[ii] === 1'bz) ? 1'b0 : I[ii]; + endgenerate + + assign Q = INIT[I_pd]; endmodule module DFF ( input CLK, D, output reg Q ); + initial Q = 1'b0; always @(posedge CLK) Q <= D; endmodule From 29d4192f6f26fb53802501c74f689cde566453ad Mon Sep 17 00:00:00 2001 From: David Shah Date: Wed, 27 Nov 2019 15:18:53 +0000 Subject: [PATCH 42/81] ci: Run generic post-PnR sim smoketest Signed-off-by: David Shah --- .cirrus.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.cirrus.yml b/.cirrus.yml index 5cac994e..bdb3c48c 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -11,6 +11,6 @@ task: test_ice40_script: cd build && ./nextpnr-ice40-test smoketest_ice40_script: export NEXTPNR=$(pwd)/build/nextpnr-ice40 && cd ice40/smoketest/attosoc && ./smoketest.sh test_ecp5_script: cd build && ./nextpnr-ecp5-test - smoketest_generic_script: export NEXTPNR=$(pwd)/build/nextpnr-generic && cd generic/examples && ./simple.sh + smoketest_generic_script: export NEXTPNR=$(pwd)/build/nextpnr-generic && cd generic/examples && ./simple.sh && ./simtest.sh regressiontest_ice40_script: make -j $(nproc) -C tests/ice40/regressions NPNR=$(pwd)/build/nextpnr-ice40 regressiontest_ecp5_script: make -j $(nproc) -C tests/ecp5/regressions NPNR=$(pwd)/build/nextpnr-ecp5 From ff30bc87fed024189a49bc372b1db0bde6db29f2 Mon Sep 17 00:00:00 2001 From: David Shah Date: Fri, 29 Nov 2019 10:50:13 +0000 Subject: [PATCH 43/81] ecp5: Fix placement of DDRDLLA Signed-off-by: David Shah --- ecp5/pack.cc | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/ecp5/pack.cc b/ecp5/pack.cc index d2a21c87..09947d88 100644 --- a/ecp5/pack.cc +++ b/ecp5/pack.cc @@ -2688,12 +2688,37 @@ class Ecp5Packer const NetInfo *clk = net_or_nullptr(ci, id_CLK); if (clk == nullptr) log_error("DDRDLLA '%s' has disconnected port CLK\n", ci->name.c_str(ctx)); + + bool left_bank_users = false, right_bank_users = false; + // Check which side the delay codes (DDRDEL) are used on + const NetInfo *ddrdel = net_or_nullptr(ci, id_DDRDEL); + if (ddrdel != nullptr) { + for (auto &usr : ddrdel->users) { + const CellInfo *uc = usr.cell; + if (uc->type != id_DQSBUFM || !uc->attrs.count(ctx->id("BEL"))) + continue; + BelId dqsb_bel = ctx->getBelByName(ctx->id(uc->attrs.at(ctx->id("BEL")).as_string())); + Loc dqsb_loc = ctx->getBelLocation(dqsb_bel); + if (dqsb_loc.x > 15) + right_bank_users = true; + if (dqsb_loc.x < 15) + left_bank_users = true; + } + } + + if (left_bank_users && right_bank_users) + log_error("DDRDLLA '%s' has DDRDEL uses on both sides of the chip.\n", ctx->nameOf(ci)); + for (auto &eclk : eclks) { if (eclk.second.unbuf == clk) { for (auto bel : ctx->getBels()) { if (ctx->getBelType(bel) != id_DDRDLL) continue; Loc loc = ctx->getBelLocation(bel); + if (loc.x > 15 && left_bank_users) + continue; + if (loc.x < 15 && right_bank_users) + continue; int ddrdll_bank = -1; if (loc.x < 15 && loc.y < 15) ddrdll_bank = 7; @@ -2705,6 +2730,7 @@ class Ecp5Packer ddrdll_bank = 3; if (eclk.first.first != ddrdll_bank) continue; + log_info("Constraining DDRDLLA '%s' to bel '%s'\n", ctx->nameOf(ci), ctx->nameOfBel(bel)); ci->attrs[ctx->id("BEL")] = ctx->getBelName(bel).str(ctx); make_eclk(ci->ports.at(id_CLK), ci, bel, eclk.first.first); goto ddrdll_done; From 1c1c096861d553f1fc71d7db3652af25b980b219 Mon Sep 17 00:00:00 2001 From: David Shah Date: Fri, 29 Nov 2019 10:56:04 +0000 Subject: [PATCH 44/81] ecp5: Fix 25k DDRDLLA bitstream gen Signed-off-by: David Shah --- ecp5/arch.h | 2 +- ecp5/bitstream.cc | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ecp5/arch.h b/ecp5/arch.h index 3df2d84f..5e5fc3e4 100644 --- a/ecp5/arch.h +++ b/ecp5/arch.h @@ -1029,7 +1029,7 @@ struct Arch : BaseCtx if (chip_info->tiletype_names[tileloc.tile_names[j].type_idx].get() == type) return tileloc.tile_names[j].name.get(); } - NPNR_ASSERT_FALSE_STR("no with type " + type); + NPNR_ASSERT_FALSE_STR("no tile with type " + type); } GlobalInfoPOD globalInfoAtLoc(Location loc); diff --git a/ecp5/bitstream.cc b/ecp5/bitstream.cc index b491bf75..9b1a65ab 100644 --- a/ecp5/bitstream.cc +++ b/ecp5/bitstream.cc @@ -1397,8 +1397,9 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex Loc loc = ctx->getBelLocation(ci->bel); bool u = loc.y<15, r = loc.x> 15; std::string tiletype = fmt_str("DDRDLL_" << (u ? 'U' : 'L') << (r ? 'R' : 'L')); - if (ctx->args.type == ArchArgs::LFE5U_25F || ctx->args.type == ArchArgs::LFE5UM_25F || - ctx->args.type == ArchArgs::LFE5UM5G_25F) + if ((ctx->args.type == ArchArgs::LFE5U_25F || ctx->args.type == ArchArgs::LFE5UM_25F || + ctx->args.type == ArchArgs::LFE5UM5G_25F) && + u) tiletype += "A"; std::string tile = ctx->getTileByType(tiletype); cc.tiles[tile].add_enum("DDRDLL.MODE", "DDRDLLA"); From 0392cd3a5b2a00a13dc3f48ea74f4f28613ee6ab Mon Sep 17 00:00:00 2001 From: Clifford Wolf Date: Sun, 1 Dec 2019 00:42:53 +0100 Subject: [PATCH 45/81] Add bba #embed support Signed-off-by: Clifford Wolf --- bba/main.cc | 28 ++++++++++++++++++++++++++-- ice40/family.cmake | 19 ++++++++++++++----- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/bba/main.cc b/bba/main.cc index d4d16e12..5940797e 100644 --- a/bba/main.cc +++ b/bba/main.cc @@ -72,6 +72,7 @@ int main(int argc, char **argv) bool verbose = false; bool bigEndian; bool writeC = false; + bool writeE = false; char buffer[512]; namespace po = boost::program_options; @@ -82,7 +83,8 @@ int main(int argc, char **argv) options.add_options()("debug,d", "debug output"); options.add_options()("be,b", "big endian"); options.add_options()("le,l", "little endian"); - options.add_options()("c,c", "write c strings"); + options.add_options()("c,c", "write C strings"); + options.add_options()("e,e", "write #embed C"); options.add_options()("files", po::value>(), "file parameters"); pos.add("files", -1); @@ -115,13 +117,19 @@ int main(int argc, char **argv) } if (vm.count("c")) writeC = true; + if (vm.count("e")) + writeE = true; + if (writeC && writeE) { + printf("Incompatible modes\n"); + exit(-1); + } if (vm.count("files") == 0) { printf("File parameters are mandatory\n"); exit(-1); } std::vector files = vm["files"].as>(); - if (files.size() != 2) { + if (files.size() != (writeE ? 3 : 2)) { printf("Input and output parameters must be set\n"); exit(-1); } @@ -433,6 +441,22 @@ int main(int argc, char **argv) for (auto &s : postText) fprintf(fileOut, "%s\n", s.c_str()); + } else if (writeE) { + for (auto &s : preText) + fprintf(fileOut, "%s\n", s.c_str()); + + fprintf(fileOut, "const char %s[%d] =\n", streams[0].name.c_str(), int(data.size())); + char *bin_basename_buf = strdup(files.at(2).c_str()); + fprintf(fileOut, "#embed \"%s\"\n", basename(bin_basename_buf)); + fprintf(fileOut, ";\n"); + + for (auto &s : postText) + fprintf(fileOut, "%s\n", s.c_str()); + + FILE *fileBin = fopen(files.at(2).c_str(), "wb"); + assert(fileBin != nullptr); + fwrite(data.data(), int(data.size()), 1, fileBin); + fclose(fileBin); } else { fwrite(data.data(), int(data.size()), 1, fileOut); } diff --git a/ice40/family.cmake b/ice40/family.cmake index e31fce2d..2859f456 100644 --- a/ice40/family.cmake +++ b/ice40/family.cmake @@ -84,6 +84,7 @@ if (NOT EXTERNAL_CHIPDB) set(DEV_TXT_DB ${ICEBOX_ROOT}/chipdb-${dev}.txt) set(DEV_CC_BBA_DB ${CMAKE_CURRENT_SOURCE_DIR}/ice40/chipdbs/chipdb-${dev}.bba) set(DEV_CC_DB ${CMAKE_CURRENT_BINARY_DIR}/ice40/chipdbs/chipdb-${dev}.cc) + set(DEV_BIN_DB ${CMAKE_CURRENT_BINARY_DIR}/ice40/chipdbs/chipdb-${dev}.bin) set(DEV_CONSTIDS_INC ${CMAKE_CURRENT_SOURCE_DIR}/ice40/constids.inc) set(DEV_GFXH ${CMAKE_CURRENT_SOURCE_DIR}/ice40/gfx.h) if(PREGENERATED_BBA_PATH) @@ -97,11 +98,19 @@ if (NOT EXTERNAL_CHIPDB) COMMAND mv ${DEV_CC_BBA_DB}.new ${DEV_CC_BBA_DB} DEPENDS ${DEV_CONSTIDS_INC} ${DEV_GFXH} ${DEV_TXT_DB} ${DB_PY} ${PREV_DEV_CC_BBA_DB} ) - add_custom_command(OUTPUT ${DEV_CC_DB} - COMMAND bbasm --c ${BBASM_ENDIAN_FLAG} ${DEV_CC_BBA_DB} ${DEV_CC_DB}.new - COMMAND mv ${DEV_CC_DB}.new ${DEV_CC_DB} - DEPENDS bbasm ${DEV_CC_BBA_DB} - ) + if(USE_C_EMBED) + add_custom_command(OUTPUT ${DEV_CC_DB} + COMMAND bbasm --e ${BBASM_ENDIAN_FLAG} ${DEV_CC_BBA_DB} ${DEV_CC_DB}.new ${DEV_BIN_DB} + COMMAND mv ${DEV_CC_DB}.new ${DEV_CC_DB} + DEPENDS bbasm ${DEV_CC_BBA_DB} + ) + else() + add_custom_command(OUTPUT ${DEV_CC_DB} + COMMAND bbasm --c ${BBASM_ENDIAN_FLAG} ${DEV_CC_BBA_DB} ${DEV_CC_DB}.new + COMMAND mv ${DEV_CC_DB}.new ${DEV_CC_DB} + DEPENDS bbasm ${DEV_CC_BBA_DB} + ) + endif() endif() if (SERIALIZE_CHIPDB) set(PREV_DEV_CC_BBA_DB ${DEV_CC_BBA_DB}) From 89e15d488ad5c24536ef9ee5762995dfe2d02c6e Mon Sep 17 00:00:00 2001 From: David Shah Date: Sun, 1 Dec 2019 14:18:10 +0000 Subject: [PATCH 46/81] HeAP: fix region constraint handling Signed-off-by: David Shah --- common/placer_heap.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/placer_heap.cc b/common/placer_heap.cc index 80ce67b2..01e50123 100644 --- a/common/placer_heap.cc +++ b/common/placer_heap.cc @@ -947,7 +947,7 @@ class HeAPPlacer visit.pop(); BelId target = ctx->getBelByLocation(ploc); if (vc->region != nullptr && vc->region->constr_bels && !vc->region->bels.count(target)) - continue; + goto fail; CellInfo *bound; if (target == BelId() || ctx->getBelType(target) != vc->type) goto fail; From f57c2bcebb9a5e0722fe90ac9b5d11d4db518c22 Mon Sep 17 00:00:00 2001 From: David Shah Date: Sun, 1 Dec 2019 17:07:36 +0000 Subject: [PATCH 47/81] generic: Don't assume unused LUT inputs are zero Signed-off-by: David Shah --- generic/pack.cc | 3 ++- generic/synth/cells_map.v | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/generic/pack.cc b/generic/pack.cc index 19266aba..b593c076 100644 --- a/generic/pack.cc +++ b/generic/pack.cc @@ -158,7 +158,8 @@ static void pack_constants(Context *ctx) gnd_cell->ports.at(ctx->id("F")).net = gnd_net.get(); std::unique_ptr vcc_cell = create_generic_cell(ctx, ctx->id("GENERIC_SLICE"), "$PACKER_VCC"); - vcc_cell->params[ctx->id("INIT")] = 1; + // Fill with 1s + vcc_cell->params[ctx->id("INIT")] = Property(Property::S1).extract(0, (1 << ctx->args.K), Property::S1); std::unique_ptr vcc_net = std::unique_ptr(new NetInfo); vcc_net->name = ctx->id("$PACKER_VCC_NET"); vcc_net->driver.cell = vcc_cell.get(); diff --git a/generic/synth/cells_map.v b/generic/synth/cells_map.v index a6027534..1d0939e0 100644 --- a/generic/synth/cells_map.v +++ b/generic/synth/cells_map.v @@ -4,7 +4,9 @@ module \$lut (A, Y); input [WIDTH-1:0] A; output Y; - LUT #(.K(`LUT_K), .INIT(LUT)) _TECHMAP_REPLACE_ (.I(A), .Q(Y)); + localparam rep = 1<<(`LUT_K-WIDTH); + + LUT #(.K(`LUT_K), .INIT({rep{LUT}})) _TECHMAP_REPLACE_ (.I(A), .Q(Y)); endmodule module \$_DFF_P_ (input D, C, output Q); DFF _TECHMAP_REPLACE_ (.D(D), .Q(Q), .CLK(C)); endmodule From be37a39ecbdedf1525b29deafecc104b4a603179 Mon Sep 17 00:00:00 2001 From: David Shah Date: Sun, 1 Dec 2019 17:19:52 +0000 Subject: [PATCH 48/81] generic: Fix width of 0-driver INIT Signed-off-by: David Shah --- generic/pack.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generic/pack.cc b/generic/pack.cc index b593c076..e92e04c5 100644 --- a/generic/pack.cc +++ b/generic/pack.cc @@ -150,7 +150,7 @@ static void pack_constants(Context *ctx) log_info("Packing constants..\n"); std::unique_ptr gnd_cell = create_generic_cell(ctx, ctx->id("GENERIC_SLICE"), "$PACKER_GND"); - gnd_cell->params[ctx->id("INIT")] = 0; + gnd_cell->params[ctx->id("INIT")] = Property(0, 1 << ctx->args.K); std::unique_ptr gnd_net = std::unique_ptr(new NetInfo); gnd_net->name = ctx->id("$PACKER_GND_NET"); gnd_net->driver.cell = gnd_cell.get(); From 5897a6a9634baa479ff044e008c6f3c5193be86d Mon Sep 17 00:00:00 2001 From: Clifford Wolf Date: Sun, 1 Dec 2019 17:22:47 -0800 Subject: [PATCH 49/81] Switch to #embed_str for shorter compile time Signed-off-by: Clifford Wolf --- bba/main.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bba/main.cc b/bba/main.cc index 5940797e..9d7f9f8f 100644 --- a/bba/main.cc +++ b/bba/main.cc @@ -445,9 +445,9 @@ int main(int argc, char **argv) for (auto &s : preText) fprintf(fileOut, "%s\n", s.c_str()); - fprintf(fileOut, "const char %s[%d] =\n", streams[0].name.c_str(), int(data.size())); + fprintf(fileOut, "const char %s[%d] =\n", streams[0].name.c_str(), int(data.size()) + 1); char *bin_basename_buf = strdup(files.at(2).c_str()); - fprintf(fileOut, "#embed \"%s\"\n", basename(bin_basename_buf)); + fprintf(fileOut, "#embed_str \"%s\"\n", basename(bin_basename_buf)); fprintf(fileOut, ";\n"); for (auto &s : postText) From a28786097da1096e29b3951db745d18fead7093c Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Fri, 6 Dec 2019 20:26:49 +0100 Subject: [PATCH 50/81] Fix for bba to make it portable --- bba/bba.cmake | 2 +- bba/main.cc | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bba/bba.cmake b/bba/bba.cmake index a6995ca3..f1f5a754 100644 --- a/bba/bba.cmake +++ b/bba/bba.cmake @@ -5,7 +5,7 @@ ENDIF(CMAKE_CROSSCOMPILING) IF(NOT CMAKE_CROSSCOMPILING) ADD_EXECUTABLE(bbasm bba/main.cc) - target_link_libraries(bbasm LINK_PUBLIC ${Boost_PROGRAM_OPTIONS_LIBRARY}) + target_link_libraries(bbasm LINK_PUBLIC ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY}) ENDIF(NOT CMAKE_CROSSCOMPILING) IF(NOT CMAKE_CROSSCOMPILING) diff --git a/bba/main.cc b/bba/main.cc index 9d7f9f8f..fc53a4d8 100644 --- a/bba/main.cc +++ b/bba/main.cc @@ -19,6 +19,7 @@ */ #include +#include #include #include #include @@ -446,8 +447,7 @@ int main(int argc, char **argv) fprintf(fileOut, "%s\n", s.c_str()); fprintf(fileOut, "const char %s[%d] =\n", streams[0].name.c_str(), int(data.size()) + 1); - char *bin_basename_buf = strdup(files.at(2).c_str()); - fprintf(fileOut, "#embed_str \"%s\"\n", basename(bin_basename_buf)); + fprintf(fileOut, "#embed_str \"%s\"\n",boost::filesystem::basename(files.at(2)).c_str()); fprintf(fileOut, ";\n"); for (auto &s : postText) From 7220f456ef08dce93c63cc7185b1caaf5934e67b Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Fri, 6 Dec 2019 20:39:35 +0100 Subject: [PATCH 51/81] proper formating --- bba/main.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bba/main.cc b/bba/main.cc index fc53a4d8..bff42028 100644 --- a/bba/main.cc +++ b/bba/main.cc @@ -447,7 +447,7 @@ int main(int argc, char **argv) fprintf(fileOut, "%s\n", s.c_str()); fprintf(fileOut, "const char %s[%d] =\n", streams[0].name.c_str(), int(data.size()) + 1); - fprintf(fileOut, "#embed_str \"%s\"\n",boost::filesystem::basename(files.at(2)).c_str()); + fprintf(fileOut, "#embed_str \"%s\"\n", boost::filesystem::basename(files.at(2)).c_str()); fprintf(fileOut, ";\n"); for (auto &s : postText) From 349be76d2693f0c9527c6373d31fae7e711f9270 Mon Sep 17 00:00:00 2001 From: David Shah Date: Sat, 7 Dec 2019 12:14:34 +0000 Subject: [PATCH 52/81] ecp5: Add support for flipflops with preload Signed-off-by: David Shah --- ecp5/bitstream.cc | 4 ++++ ecp5/cells.cc | 18 +++++++++++++++--- ecp5/pack.cc | 8 ++++++-- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/ecp5/bitstream.cc b/ecp5/bitstream.cc index 9b1a65ab..66bd639a 100644 --- a/ecp5/bitstream.cc +++ b/ecp5/bitstream.cc @@ -759,6 +759,10 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex str_or_default(ci->params, ctx->id("REG0_REGSET"), "RESET")); cc.tiles[tname].add_enum(slice + ".REG1.REGSET", str_or_default(ci->params, ctx->id("REG1_REGSET"), "RESET")); + cc.tiles[tname].add_enum(slice + ".REG0.LSRMODE", + str_or_default(ci->params, ctx->id("REG0_LSRMODE"), "LSR")); + cc.tiles[tname].add_enum(slice + ".REG1.LSRMODE", + str_or_default(ci->params, ctx->id("REG1_LSRMODE"), "LSR")); cc.tiles[tname].add_enum(slice + ".CEMUX", str_or_default(ci->params, ctx->id("CEMUX"), "1")); if (ci->sliceInfo.using_dff) { diff --git a/ecp5/cells.cc b/ecp5/cells.cc index 9accdd55..b06350c7 100644 --- a/ecp5/cells.cc +++ b/ecp5/cells.cc @@ -243,6 +243,7 @@ void ff_to_slice(Context *ctx, CellInfo *ff, CellInfo *lc, int index, bool drive lc->params[ctx->id(reg + "_SD")] = std::string(driven_by_lut ? "1" : "0"); lc->params[ctx->id(reg + "_REGSET")] = str_or_default(ff->params, ctx->id("REGSET"), "RESET"); + lc->params[ctx->id(reg + "_LSRMODE")] = str_or_default(ff->params, ctx->id("LSRMODE"), "LSR"); replace_port_safe(has_ff, ff, ctx->id("CLK"), lc, ctx->id("CLK")); if (ff->ports.find(ctx->id("LSR")) != ff->ports.end()) replace_port_safe(has_ff, ff, ctx->id("LSR"), lc, ctx->id("LSR")); @@ -250,10 +251,21 @@ void ff_to_slice(Context *ctx, CellInfo *ff, CellInfo *lc, int index, bool drive replace_port_safe(has_ff, ff, ctx->id("CE"), lc, ctx->id("CE")); replace_port(ff, ctx->id("Q"), lc, ctx->id("Q" + std::to_string(index))); - if (driven_by_lut) { - replace_port(ff, ctx->id("DI"), lc, ctx->id("DI" + std::to_string(index))); + if (get_net_or_empty(ff, ctx->id("M")) != nullptr) { + // PRLD FFs that use both M and DI + NPNR_ASSERT(!driven_by_lut); + // As M is used; must route DI through a new LUT + lc->params[ctx->id(reg + "_SD")] = std::string("1"); + lc->params[ctx->id("LUT" + std::to_string(index) + "_INITVAL")] = Property(0xFF00, 16); + replace_port(ff, ctx->id("DI"), lc, ctx->id("D" + std::to_string(index))); + replace_port(ff, ctx->id("M"), lc, ctx->id("M" + std::to_string(index))); + connect_ports(ctx, lc, ctx->id("F" + std::to_string(index)), lc, ctx->id("DI" + std::to_string(index))); } else { - replace_port(ff, ctx->id("DI"), lc, ctx->id("M" + std::to_string(index))); + if (driven_by_lut) { + replace_port(ff, ctx->id("DI"), lc, ctx->id("DI" + std::to_string(index))); + } else { + replace_port(ff, ctx->id("DI"), lc, ctx->id("M" + std::to_string(index))); + } } } diff --git a/ecp5/pack.cc b/ecp5/pack.cc index 09947d88..3867ab3d 100644 --- a/ecp5/pack.cc +++ b/ecp5/pack.cc @@ -112,7 +112,8 @@ class Ecp5Packer NetInfo *znet = ci->ports.at(ctx->id("Z")).net; if (znet != nullptr) { CellInfo *ff = net_only_drives(ctx, znet, is_ff, ctx->id("DI"), false); - if (ff != nullptr) { + // Can't combine preload FF with LUT due to conflict on M + if (ff != nullptr && get_net_or_empty(ff, ctx->id("M")) == nullptr) { lutffPairs[ci->name] = ff->name; fflutPairs[ff->name] = ci->name; } @@ -232,6 +233,8 @@ class Ecp5Packer // Return true if a FF can be added to a DPRAM slice bool can_pack_ff_dram(CellInfo *dpram, CellInfo *ff) { + if (get_net_or_empty(ff, ctx->id("M")) != nullptr) + return false; // skip PRLD FFs due to M/DI conflict std::string wckmux = str_or_default(dpram->params, ctx->id("WCKMUX"), "WCK"); std::string clkmux = str_or_default(ff->params, ctx->id("CLKMUX"), "CLK"); if (wckmux != clkmux && !(wckmux == "WCK" && clkmux == "CLK")) @@ -1178,7 +1181,8 @@ class Ecp5Packer CellInfo *ci = cell.second; if (is_ff(ctx, ci)) { bool pack_dense = used_slices > (dense_pack_mode_thresh * available_slices); - if (pack_dense) { + bool requires_m = get_net_or_empty(ci, ctx->id("M")) != nullptr; + if (pack_dense && !requires_m) { // If dense packing threshold exceeded; always try and pack the FF into an existing slice // Find a SLICE with space "near" the flipflop in the netlist std::vector ltile; From 992c9acd569687aabda1083b5cf9c2a1e66b7350 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miodrag=20Milanovi=C4=87?= Date: Mon, 9 Dec 2019 17:27:09 +0100 Subject: [PATCH 53/81] Clarify step for macOS --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index abe86caa..43407620 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,8 @@ of the selected architecture: - For building on macOS, brew utility is needed. - Install all needed packages `brew install cmake python boost boost-python3 qt5 eigen` - Do not forget to add qt5 in path as well `echo 'export PATH="/usr/local/opt/qt/bin:$PATH"' >> ~/.bash_profile` + + NOTE: this change is effective in next terminal session, so please re-open terminal window before next step Getting started --------------- From dd7f7a53bd5ae51ca3ff172b6e4de65128ab8a6d Mon Sep 17 00:00:00 2001 From: David Shah Date: Tue, 10 Dec 2019 15:28:16 +0000 Subject: [PATCH 54/81] ice40: Improve error handling of Lattice-style parameters Signed-off-by: David Shah --- ice40/bitstream.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ice40/bitstream.cc b/ice40/bitstream.cc index 7149f127..9586b8ff 100644 --- a/ice40/bitstream.cc +++ b/ice40/bitstream.cc @@ -159,6 +159,9 @@ void configure_extra_cell(chipconfig_t &config, const Context *ctx, CellInfo *ce if (string_style) { // Lattice's weird string style params, not sure if // prefixes other than 0b should be supported, only 0b features in docs + if (cell->params.count(ctx->id(p.first)) && !cell->params.at(ctx->id(p.first)).is_string) + log_error("expected configuration string starting with '0b' for parameter '%s' on cell '%s'\n", + p.first.c_str(), cell->name.c_str(ctx)); std::string raw = get_param_str_or_def(cell, ctx->id(p.first), "0b0"); if (raw.substr(0, 2) != "0b") log_error("expected configuration string starting with '0b' for parameter '%s' on cell '%s'\n", From 16e28f002ebe98341c18261dc6c02e01c4e1ac78 Mon Sep 17 00:00:00 2001 From: Franck HOUSSEN Date: Sun, 15 Dec 2019 20:14:25 +0100 Subject: [PATCH 55/81] [DOC] Fixing typo: cmake test suite triggered by 'make test'. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 43407620..401d1178 100644 --- a/README.md +++ b/README.md @@ -164,7 +164,7 @@ Notes for developers Testing ------- -- To build test binaries as well, use `-DBUILD_TESTS=ON` and after `make` run `make tests` to run them, or you can run separate binaries. +- To build test binaries as well, use `-DBUILD_TESTS=ON` and after `make` run `make test` to run them, or you can run separate binaries. - To use code sanitizers use the `cmake` options: - `-DSANITIZE_ADDRESS=ON` - `-DSANITIZE_MEMORY=ON -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++` From 346998caed79a45b63e7694af5b6b309c597463b Mon Sep 17 00:00:00 2001 From: Franck HOUSSEN Date: Wed, 25 Dec 2019 11:56:18 +0100 Subject: [PATCH 56/81] [README] Add prerequisite: libboost-iostreams-dev. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 401d1178..cf743732 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ of the selected architecture: - Qt5 or later (`qt5-default` for Ubuntu 16.04) - Python 3.5 or later, including development libraries (`python3-dev` for Ubuntu) - on Windows make sure to install same version as supported by [vcpkg](https://github.com/Microsoft/vcpkg/blob/master/ports/python3/CONTROL) -- Boost libraries (`libboost-dev libboost-filesystem-dev libboost-thread-dev libboost-program-options-dev libboost-python-dev libboost-dev` or `libboost-all-dev` for Ubuntu) +- Boost libraries (`libboost-dev libboost-filesystem-dev libboost-thread-dev libboost-program-options-dev libboost-python-dev libboost-iostreams-dev libboost-dev` or `libboost-all-dev` for Ubuntu) - Eigen3 (`libeigen3-dev` for Ubuntu) is required to build the analytic placer - Latest git Yosys is required to synthesise the demo design - For building on Windows with MSVC, usage of vcpkg is advised for dependency installation. From b6e2159cecdf79d0d94d0e6a9dda8cef6389cac1 Mon Sep 17 00:00:00 2001 From: David Shah Date: Fri, 27 Dec 2019 10:32:23 +0000 Subject: [PATCH 57/81] Work around Qt MOC issue with IdString enums Signed-off-by: David Shah --- ecp5/archdefs.h | 4 ++++ ice40/archdefs.h | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/ecp5/archdefs.h b/ecp5/archdefs.h index 3cd92119..8faa41ed 100644 --- a/ecp5/archdefs.h +++ b/ecp5/archdefs.h @@ -52,6 +52,9 @@ struct DelayInfo // ----------------------------------------------------------------------- +// https://bugreports.qt.io/browse/QTBUG-80789 + +#ifndef Q_MOC_RUN enum ConstIds { ID_NONE @@ -65,6 +68,7 @@ enum ConstIds #define X(t) static constexpr auto id_##t = IdString(ID_##t); #include "constids.inc" #undef X +#endif NPNR_PACKED_STRUCT(struct LocationPOD { int16_t x, y; }); diff --git a/ice40/archdefs.h b/ice40/archdefs.h index 89591af5..e95953f1 100644 --- a/ice40/archdefs.h +++ b/ice40/archdefs.h @@ -48,6 +48,9 @@ struct DelayInfo // ----------------------------------------------------------------------- +// https://bugreports.qt.io/browse/QTBUG-80789 + +#ifndef Q_MOC_RUN enum ConstIds { ID_NONE @@ -59,6 +62,7 @@ enum ConstIds #define X(t) static constexpr auto id_##t = IdString(ID_##t); #include "constids.inc" #undef X +#endif struct BelId { From fa7317f5940841e7ff56fed748c4f1f4117baae4 Mon Sep 17 00:00:00 2001 From: David Shah Date: Mon, 11 Nov 2019 16:55:52 +0000 Subject: [PATCH 58/81] frontend: Draft for a generic template-based frontend API Signed-off-by: David Shah --- frontend/generic.h | 102 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 frontend/generic.h diff --git a/frontend/generic.h b/frontend/generic.h new file mode 100644 index 00000000..35428052 --- /dev/null +++ b/frontend/generic.h @@ -0,0 +1,102 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2019 David Shah + * + * 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. + * + */ + +/* + * Generic Frontend Framework + * + * This is designed to make it possible to build frontends for parsing any format isomorphic to Yosys JSON [1] + * with maximal inlining and minimal need for overhead such as runtime polymorphism or extra wrapper types. + * + * [1] http://www.clifford.at/yosys/cmd_write_json.html + * + * The frontend should implement a class referred to as FrontendType that defines the following type(def)s and + * functions: + * + * Types: + * ModuleDataType: corresponds to a single entry in "modules" + * ModulePortDataType: corresponds to a single entry in "ports" of a module + * CellDataType: corresponds to a single entry in "cells" + * NetnameDataType: corresponds to a single entry in "netnames" + * BitVectorDataType: corresponds to a signal/constant bit vector (e.g. a "connections" field) + * + * Functions: + * + * void foreach_module(Func); + * calls Func(const std::string &name, const ModuleDataType &mod); + * for each module in the netlist + * + * void foreach_port(const ModuleDataType &mod, Func); + * calls Func(const std::string &name, const ModulePortDataType &port); + * for each port of mod + * + * void foreach_cell(const ModuleDataType &mod, Func); + * calls Func(const std::string &name, const CellDataType &cell); + * for each cell of mod + * + * void foreach_netname(const ModuleDataType &mod, Func); + * calls Func(const std::string &name, const NetnameDataType &cell); + * for each netname entry of mod + * + * PortType get_port_dir(const ModulePortDataType &port); + * gets the PortType direction of a module port + * + * int get_port_offset(const ModulePortDataType &port); + * gets the start bit number of a port + * + * bool is_port_upto(const ModulePortDataType &port); + * returns true if a port is an "upto" type port + * + * const BitVectorDataType &get_port_bits(const ModulePortDataType &port); + * gets the bit vector of a module port + * + * const std::string& get_cell_type(const CellDataType &cell); + * gets the type of a cell + * + * void foreach_attr(const {ModuleDataType|CellDataType|ModulePortDataType|NetnameDataType} &obj, Func); + * calls Func(const std::string &name, const Property &value); + * for each attribute on a module, cell, module port or net + * + * void foreach_param(const CellDataType &obj, Func); + * calls Func(const std::string &name, const Property &value); + * for each parameter of a cell + * + * void foreach_port_dir(const CellDataType &cell, Func); + * calls Func(const std::string &name, PortType dir); + * for each port direction of a cell + * + * void foreach_port_conn(const CellDataType &cell, Func); + * calls Func(const std::string &name, const BitVectorDataType &conn); + * for each port connection of a cell + * + * const BitVectorDataType &get_net_bits(const NetnameDataType &net); + * gets the BitVector corresponding to the bits entry of a netname field + * + * int get_vector_length(const BitVectorDataType &bits); + * gets the length of a BitVector + * + * bool is_vector_bit_constant(const BitVectorDataType &bits, int i); + * returns true if bit of bits is constant + * + * char get_vector_bit_constval(const BitVectorDataType &bits, int i); + * returns a char [01xz] corresponding to the constant value of bit + * + * int get_vector_bit_signal(const BitVectorDataType &bits, int i); + * returns the signal number of vector bit + * + */ \ No newline at end of file From fffc3b844730d1241d0056f989b3a9492d62005c Mon Sep 17 00:00:00 2001 From: David Shah Date: Tue, 12 Nov 2019 11:33:49 +0000 Subject: [PATCH 59/81] frontend/base: Top module handling Signed-off-by: David Shah --- CMakeLists.txt | 6 +- common/command.cc | 1 + frontend/{generic.h => frontend_base.h} | 92 ++++++++++++++++++++++++- 3 files changed, 96 insertions(+), 3 deletions(-) rename frontend/{generic.h => frontend_base.h} (54%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9af4bb6c..e8fa4ec0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -191,7 +191,7 @@ if (BUILD_PYTHON) endif () endif() -include_directories(common/ json/ ${Boost_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS}) +include_directories(common/ json/ frontend/ ${Boost_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS}) if(BUILD_HEAP) find_package (Eigen3 REQUIRED NO_MODULE) @@ -202,7 +202,9 @@ endif() aux_source_directory(common/ COMMON_SRC_FILES) aux_source_directory(json/ JSON_PARSER_FILES) -set(COMMON_FILES ${COMMON_SRC_FILES} ${JSON_PARSER_FILES}) +aux_source_directory(frontend/ FRONTEND_FILES) + +set(COMMON_FILES ${COMMON_SRC_FILES} ${JSON_PARSER_FILES} ${FRONTEND_FILES}) set(CMAKE_BUILD_TYPE Release) if(MINGW) diff --git a/common/command.cc b/common/command.cc index fd310789..13cd3498 100644 --- a/common/command.cc +++ b/common/command.cc @@ -35,6 +35,7 @@ #include #include "command.h" #include "design_utils.h" +#include "frontend_base.h" #include "jsonparse.h" #include "jsonwrite.h" #include "log.h" diff --git a/frontend/generic.h b/frontend/frontend_base.h similarity index 54% rename from frontend/generic.h rename to frontend/frontend_base.h index 35428052..8b76cc85 100644 --- a/frontend/generic.h +++ b/frontend/frontend_base.h @@ -99,4 +99,94 @@ * int get_vector_bit_signal(const BitVectorDataType &bits, int i); * returns the signal number of vector bit * - */ \ No newline at end of file + */ + +#include "log.h" +#include "nextpnr.h" +NEXTPNR_NAMESPACE_BEGIN + +namespace { + +template struct GenericFrontend +{ + GenericFrontend(Context *ctx, const FrontendType &impl) : ctx(ctx), impl(impl) {} + Context *ctx; + const FrontendType &impl; + using mod_dat_t = typename FrontendType::ModuleDataType; + using mod_port_dat_t = typename FrontendType::ModulePortDataType; + using cell_dat_t = typename FrontendType::CellDataType; + using netname_dat_t = typename FrontendType::NetnameDataType; + using bitvector_t = typename FrontendType::BitVectorDataType; + + // Used for hierarchy resolution + struct ModuleInfo + { + mod_dat_t *mod_data; + bool is_top = false, is_blackbox = false, is_whitebox = false; + inline bool is_box() const { return is_blackbox || is_whitebox; } + std::unordered_set instantiated_celltypes; + }; + std::unordered_map mods; + IdString top; + + // Process the list of modules and determine + // the top module + void find_top_module() + { + impl.foreach_module([&](const std::string &name, const mod_dat_t &mod) { + IdString mod_id = ctx->id(name); + auto &mi = mods[mod_id]; + mi.mod_data = &mod; + impl.foreach_attr(mod, [&](const std::string &name, const Property &value) { + if (name == "top") + mi.is_top = (value.intval != 0); + else if (name == "blackbox") + mi.is_blackbox = (value.intval != 0); + else if (name == "whitebox") + mi.is_whitebox = (value.intval != 0); + }); + impl.foreach_cell(mod, [&](const std::string &name, const cell_dat_t &cell) { + mi.instantiated_cells.insert(ctx->id(impl.get_cell_type(cell))); + }); + }); + // First of all, see if a top module has been manually specified + if (ctx->settings.count(ctx->id("frontend/top"))) { + IdString user_top = ctx->id(ctx->settings.at(ctx->id("frontend/top")).as_string()); + if (!mods.count(user_top)) + log_error("Top module '%s' not found!\n", ctx->nameOf(user_top)); + top = user_top; + return; + } + // If not, look for a module with the top attribute set + IdString top_by_attr; + for (auto &mod : mods) { + if (mod.second.is_top && !mod.second.is_box()) { + if (top_by_attr != IdString()) + log_error("Found multiple modules with (* top *) set (including %s and %s).\n", + ctx->nameOf(top_by_attr), ctx->nameOf(mod.first)); + top_by_attr = mod.first; + } + } + if (top_by_attr != IdString()) { + top = top_by_attr; + return; + } + // Finally, attempt to autodetect the top module using hierarchy + // (a module that is not a box and is not used as a cell by any other module) + std::unordered_set candidate_top; + for (auto &mod : mods) + if (!mod.second.is_box()) + candidate_top.insert(mod.first); + for (auto &mod : mods) + for (auto &c : mod.second.instantiated_celltypes) + candidate_top.erase(c); + if (candidate_top.size() != 1) + log_error("Failed to autodetect top module, please specify using --top.\n"); + top = *(candidate_top.begin()); + } +}; +} // namespace + +template void run_frontend(Context *ctx, const FrontendType &impl) {} + +NEXTPNR_NAMESPACE_END \ No newline at end of file From 6aaa9f5a3d02224f2760d993d114163ce7678e1f Mon Sep 17 00:00:00 2001 From: David Shah Date: Wed, 13 Nov 2019 12:11:17 +0000 Subject: [PATCH 60/81] frontend/base: Functions for port import Signed-off-by: David Shah --- common/nextpnr.h | 2 + frontend/frontend_base.h | 145 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 146 insertions(+), 1 deletion(-) diff --git a/common/nextpnr.h b/common/nextpnr.h index 24f6948b..3fce97a2 100644 --- a/common/nextpnr.h +++ b/common/nextpnr.h @@ -397,6 +397,8 @@ struct NetInfo : ArchNetInfo // wire -> uphill_pip std::unordered_map wires; + std::vector aliases; // entries in net_aliases that point to this net + std::unique_ptr clkconstr; TimingConstrObjectId tmg_id; diff --git a/frontend/frontend_base.h b/frontend/frontend_base.h index 8b76cc85..42be2bfd 100644 --- a/frontend/frontend_base.h +++ b/frontend/frontend_base.h @@ -101,6 +101,7 @@ * */ +#include "design_utils.h" #include "log.h" #include "nextpnr.h" NEXTPNR_NAMESPACE_BEGIN @@ -121,7 +122,7 @@ template struct GenericFrontend // Used for hierarchy resolution struct ModuleInfo { - mod_dat_t *mod_data; + const mod_dat_t *mod_data; bool is_top = false, is_blackbox = false, is_whitebox = false; inline bool is_box() const { return is_blackbox || is_whitebox; } std::unordered_set instantiated_celltypes; @@ -184,6 +185,148 @@ template struct GenericFrontend log_error("Failed to autodetect top module, please specify using --top.\n"); top = *(candidate_top.begin()); } + + // Create a unique name (guaranteed collision free) for a net or a cell; based on + // a base name and suffix. __unique__i will be be appended with increasing i + // if a collision is found until no collision + IdString unique_name(const std::string &base, const std::string &suffix, bool is_net) + { + IdString name; + int incr = 0; + do { + std::string comb = base + suffix; + if (incr > 0) { + comb += "__unique__"; + comb += std::to_string(incr); + } + name = ctx->id(comb); + incr++; + } while (is_net ? ctx->nets.count(name) : ctx->cells.count(name)); + return name; + } + + // A flat index of map; designed to cope with renaming + // A net's udata points into this index + std::vector net_flatindex; + + // This structure contains some structures specific to the import of a module at + // a certain point in the hierarchy + struct HierModuleState + { + bool is_toplevel; + std::string prefix; + // Map from index in module to "flat" index of nets + std::vector index_to_net_flatindex; + // Get a reference to index_to_net; resizing if + // appropriate + NetInfo *&net_by_idx(int idx) + { + NPNR_ASSERT(idx >= 0); + if (idx >= int(index_to_net_flatindex.size())) + index_to_net_flatindex.resize(idx + 1, nullptr); + return index_to_net_flatindex.at(idx); + } + std::unordered_map> port_to_bus; + }; + + void import_module(HierModuleState &m, mod_dat_t *data) + { + std::vector index_to_net; + // Import port connections; for submodules only + if (!m.is_toplevel) { + import_port_connections(m, data); + } + } + + // Add a constant-driving VCC or GND cell to make a net constant + // (constval can be [01xz], x and z or no-ops) + int const_autoidx = 0; + void add_constant_driver(HierModuleState &m, NetInfo *net, char constval) + { + + if (constval == 'x' || constval == 'z') + return; // 'x' or 'z' is the same as undriven + NPNR_ASSERT(constval == '0' || constval == '1'); + IdString cell_name = unique_name( + m.prefix, net->name.str(ctx) + (constval == '1' ? "$VCC$" : "$GND$") + std::to_string(const_autoidx++), + false); + CellInfo *cc = ctx->createCell(cell_name, ctx->id(constval == '1' ? "VCC" : "GND")); + cc->ports[ctx->id("Y")].name = ctx->id("Y"); + cc->ports[ctx->id("Y")].type = PORT_OUT; + if (net->driver.cell != nullptr) + log_error("Net '%s' is multiply driven by port %s.%s and constant '%c'\n", ctx->nameOf(net), + ctx->nameOf(net->driver.cell), ctx->nameOf(net->driver.port), constval); + connect_port(ctx, net, cc, ctx->id("Y")); + } + + // Merge two nets - e.g. if one net in a submodule bifurcates to two output bits and therefore two different + // parent nets + void merge_nets(NetInfo *base, NetInfo *mergee) + { + // Resolve drivers + if (mergee->driver.cell != nullptr) { + if (base->driver.cell != nullptr) + log_error("Attempting to merge nets '%s' and '%s' due to port connectivity; but this would result in a " + "multiply driven net\n", + ctx->nameOf(base), ctx->nameOf(mergee)); + else { + mergee->driver.cell->ports[mergee->driver.port].net = base; + base->driver = mergee->driver; + } + } + // Combine users + for (auto &usr : mergee->users) { + usr.cell->ports[usr.port].net = base; + base->users.push_back(usr); + } + // Point aliases to the new net + for (IdString alias : mergee->aliases) { + ctx->net_aliases[alias] = base->name; + base->aliases.push_back(alias); + } + // Create a new alias from mergee's name to new base name + ctx->net_aliases[mergee->name] = base->name; + // Update flat index of nets + net_flatindex.at(mergee->udata) = base; + // Remove merged net from context + ctx->nets.erase(mergee->name); + } + + // Import connections between a submodule and its parent + void import_port_connections(HierModuleState &m, const mod_dat_t &data) + { + impl.foreach_port(data, [&](const std::string &name, const mod_port_dat_t &port) { + // CHECK: should disconnected module inputs really just be skipped; or is it better + // to insert a ground driver? + if (!m.port_to_bus.count(ctx->id(name))) + return; + auto &p2b = m.port_to_bus.at(ctx->id(name)); + // Get direction and vector of port bits + PortType dir = impl.get_port_dir(port); + const auto &bv = impl.get_port_bits(port); + int bv_size = impl.get_vector_length(bv); + // Iterate over bits of port; making connections + for (int i = 0; i < std::min(bv_size, p2b.size()); i++) { + NetInfo *conn_net = p2b.at(i); + if (conn_net == nullptr) + continue; + if (impl.is_vector_bit_constant(bv, i)) { + // It is a constant, we might need to insert a constant driver here to drive the corresponding + // net in the parent + char constval = impl.get_vector_bit_constval(bv, i); + // Inputs cannot be driving a constant back to the parent + if (dir == PORT_IN) + log_error("Input port %s%s[%d] cannot be driving a constant '%c'.\n", m.prefix.c_str(), + port.c_str(), i, constval); + // Insert the constant driver + add_constant_driver(m, conn_net, constval); + } else { + // If not driving a constant; simply make the port bit net index in the submodule correspond + // to connected net in the parent module + } + } + }); + } }; } // namespace From 25867e3f231cc8ffe3ce87e6f1af1ca1d4c46146 Mon Sep 17 00:00:00 2001 From: David Shah Date: Wed, 13 Nov 2019 13:51:28 +0000 Subject: [PATCH 61/81] frontend/base: Improve net indexing Signed-off-by: David Shah --- frontend/frontend_base.h | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/frontend/frontend_base.h b/frontend/frontend_base.h index 42be2bfd..35d4409c 100644 --- a/frontend/frontend_base.h +++ b/frontend/frontend_base.h @@ -205,9 +205,10 @@ template struct GenericFrontend return name; } - // A flat index of map; designed to cope with renaming + // A flat index of map; designed to cope with merging nets where pointers to nets would go stale // A net's udata points into this index std::vector net_flatindex; + std::vector> net_old_indices; // the other indices of a net in net_flatindex for merging // This structure contains some structures specific to the import of a module at // a certain point in the hierarchy @@ -216,17 +217,17 @@ template struct GenericFrontend bool is_toplevel; std::string prefix; // Map from index in module to "flat" index of nets - std::vector index_to_net_flatindex; + std::vector index_to_net_flatindex; // Get a reference to index_to_net; resizing if // appropriate - NetInfo *&net_by_idx(int idx) + int &net_by_idx(int idx) { NPNR_ASSERT(idx >= 0); if (idx >= int(index_to_net_flatindex.size())) - index_to_net_flatindex.resize(idx + 1, nullptr); + index_to_net_flatindex.resize(idx + 1, -1); return index_to_net_flatindex.at(idx); } - std::unordered_map> port_to_bus; + std::unordered_map> port_to_bus; }; void import_module(HierModuleState &m, mod_dat_t *data) @@ -287,7 +288,13 @@ template struct GenericFrontend // Create a new alias from mergee's name to new base name ctx->net_aliases[mergee->name] = base->name; // Update flat index of nets + for (auto old_idx : net_old_indices.at(mergee->udata)) { + net_old_indices.at(base).push_back(old_idx); + net_flatindex.at(old_idx) = base; + } + net_old_indices.at(base).push_back(mergee->udata); net_flatindex.at(mergee->udata) = base; + net_old_indices.at(mergee->udata).clear(); // Remove merged net from context ctx->nets.erase(mergee->name); } @@ -307,9 +314,11 @@ template struct GenericFrontend int bv_size = impl.get_vector_length(bv); // Iterate over bits of port; making connections for (int i = 0; i < std::min(bv_size, p2b.size()); i++) { - NetInfo *conn_net = p2b.at(i); - if (conn_net == nullptr) + int conn_net = p2b.at(i); + if (conn_net == -1) continue; + NetInfo *conn_ni = net_flatindex.at(conn_net); + NPNR_ASSERT(conn_ni != nullptr); if (impl.is_vector_bit_constant(bv, i)) { // It is a constant, we might need to insert a constant driver here to drive the corresponding // net in the parent @@ -319,10 +328,20 @@ template struct GenericFrontend log_error("Input port %s%s[%d] cannot be driving a constant '%c'.\n", m.prefix.c_str(), port.c_str(), i, constval); // Insert the constant driver - add_constant_driver(m, conn_net, constval); + add_constant_driver(m, conn_ni, constval); } else { // If not driving a constant; simply make the port bit net index in the submodule correspond // to connected net in the parent module + int &submod_net = m.net_by_idx(impl.get_vector_bit_signal(bv, i)); + if (submod_net == -1) { + // A net at this index doesn't yet exist + // We can simply set this index to point to the net in the parent + submod_net = conn_net; + } else { + // A net at this index already exists (this would usually be a submodule net + // connected to more than one I/O port) + merge_nets(net_flatindex.at(submod_net), net_flatindex.at(conn_net)); + } } } }); From 240561c370c03ad33b2ac397369659a0b9a54c18 Mon Sep 17 00:00:00 2001 From: David Shah Date: Wed, 13 Nov 2019 16:17:43 +0000 Subject: [PATCH 62/81] 3rdparty: Add json11 Signed-off-by: David Shah --- 3rdparty/json11/LICENSE.txt | 19 + 3rdparty/json11/json11.cpp | 790 ++++++++++++++++++++++++++++++++++++ 3rdparty/json11/json11.hpp | 232 +++++++++++ CMakeLists.txt | 5 +- 4 files changed, 1044 insertions(+), 2 deletions(-) create mode 100644 3rdparty/json11/LICENSE.txt create mode 100644 3rdparty/json11/json11.cpp create mode 100644 3rdparty/json11/json11.hpp diff --git a/3rdparty/json11/LICENSE.txt b/3rdparty/json11/LICENSE.txt new file mode 100644 index 00000000..691742e9 --- /dev/null +++ b/3rdparty/json11/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (c) 2013 Dropbox, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/3rdparty/json11/json11.cpp b/3rdparty/json11/json11.cpp new file mode 100644 index 00000000..88024e92 --- /dev/null +++ b/3rdparty/json11/json11.cpp @@ -0,0 +1,790 @@ +/* Copyright (c) 2013 Dropbox, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "json11.hpp" +#include +#include +#include +#include +#include + +namespace json11 { + +static const int max_depth = 200; + +using std::string; +using std::vector; +using std::map; +using std::make_shared; +using std::initializer_list; +using std::move; + +/* Helper for representing null - just a do-nothing struct, plus comparison + * operators so the helpers in JsonValue work. We can't use nullptr_t because + * it may not be orderable. + */ +struct NullStruct { + bool operator==(NullStruct) const { return true; } + bool operator<(NullStruct) const { return false; } +}; + +/* * * * * * * * * * * * * * * * * * * * + * Serialization + */ + +static void dump(NullStruct, string &out) { + out += "null"; +} + +static void dump(double value, string &out) { + if (std::isfinite(value)) { + char buf[32]; + snprintf(buf, sizeof buf, "%.17g", value); + out += buf; + } else { + out += "null"; + } +} + +static void dump(int value, string &out) { + char buf[32]; + snprintf(buf, sizeof buf, "%d", value); + out += buf; +} + +static void dump(bool value, string &out) { + out += value ? "true" : "false"; +} + +static void dump(const string &value, string &out) { + out += '"'; + for (size_t i = 0; i < value.length(); i++) { + const char ch = value[i]; + if (ch == '\\') { + out += "\\\\"; + } else if (ch == '"') { + out += "\\\""; + } else if (ch == '\b') { + out += "\\b"; + } else if (ch == '\f') { + out += "\\f"; + } else if (ch == '\n') { + out += "\\n"; + } else if (ch == '\r') { + out += "\\r"; + } else if (ch == '\t') { + out += "\\t"; + } else if (static_cast(ch) <= 0x1f) { + char buf[8]; + snprintf(buf, sizeof buf, "\\u%04x", ch); + out += buf; + } else if (static_cast(ch) == 0xe2 && static_cast(value[i+1]) == 0x80 + && static_cast(value[i+2]) == 0xa8) { + out += "\\u2028"; + i += 2; + } else if (static_cast(ch) == 0xe2 && static_cast(value[i+1]) == 0x80 + && static_cast(value[i+2]) == 0xa9) { + out += "\\u2029"; + i += 2; + } else { + out += ch; + } + } + out += '"'; +} + +static void dump(const Json::array &values, string &out) { + bool first = true; + out += "["; + for (const auto &value : values) { + if (!first) + out += ", "; + value.dump(out); + first = false; + } + out += "]"; +} + +static void dump(const Json::object &values, string &out) { + bool first = true; + out += "{"; + for (const auto &kv : values) { + if (!first) + out += ", "; + dump(kv.first, out); + out += ": "; + kv.second.dump(out); + first = false; + } + out += "}"; +} + +void Json::dump(string &out) const { + m_ptr->dump(out); +} + +/* * * * * * * * * * * * * * * * * * * * + * Value wrappers + */ + +template +class Value : public JsonValue { +protected: + + // Constructors + explicit Value(const T &value) : m_value(value) {} + explicit Value(T &&value) : m_value(move(value)) {} + + // Get type tag + Json::Type type() const override { + return tag; + } + + // Comparisons + bool equals(const JsonValue * other) const override { + return m_value == static_cast *>(other)->m_value; + } + bool less(const JsonValue * other) const override { + return m_value < static_cast *>(other)->m_value; + } + + const T m_value; + void dump(string &out) const override { json11::dump(m_value, out); } +}; + +class JsonDouble final : public Value { + double number_value() const override { return m_value; } + int int_value() const override { return static_cast(m_value); } + bool equals(const JsonValue * other) const override { return m_value == other->number_value(); } + bool less(const JsonValue * other) const override { return m_value < other->number_value(); } +public: + explicit JsonDouble(double value) : Value(value) {} +}; + +class JsonInt final : public Value { + double number_value() const override { return m_value; } + int int_value() const override { return m_value; } + bool equals(const JsonValue * other) const override { return m_value == other->number_value(); } + bool less(const JsonValue * other) const override { return m_value < other->number_value(); } +public: + explicit JsonInt(int value) : Value(value) {} +}; + +class JsonBoolean final : public Value { + bool bool_value() const override { return m_value; } +public: + explicit JsonBoolean(bool value) : Value(value) {} +}; + +class JsonString final : public Value { + const string &string_value() const override { return m_value; } +public: + explicit JsonString(const string &value) : Value(value) {} + explicit JsonString(string &&value) : Value(move(value)) {} +}; + +class JsonArray final : public Value { + const Json::array &array_items() const override { return m_value; } + const Json & operator[](size_t i) const override; +public: + explicit JsonArray(const Json::array &value) : Value(value) {} + explicit JsonArray(Json::array &&value) : Value(move(value)) {} +}; + +class JsonObject final : public Value { + const Json::object &object_items() const override { return m_value; } + const Json & operator[](const string &key) const override; +public: + explicit JsonObject(const Json::object &value) : Value(value) {} + explicit JsonObject(Json::object &&value) : Value(move(value)) {} +}; + +class JsonNull final : public Value { +public: + JsonNull() : Value({}) {} +}; + +/* * * * * * * * * * * * * * * * * * * * + * Static globals - static-init-safe + */ +struct Statics { + const std::shared_ptr null = make_shared(); + const std::shared_ptr t = make_shared(true); + const std::shared_ptr f = make_shared(false); + const string empty_string; + const vector empty_vector; + const map empty_map; + Statics() {} +}; + +static const Statics & statics() { + static const Statics s {}; + return s; +} + +static const Json & static_null() { + // This has to be separate, not in Statics, because Json() accesses statics().null. + static const Json json_null; + return json_null; +} + +/* * * * * * * * * * * * * * * * * * * * + * Constructors + */ + +Json::Json() noexcept : m_ptr(statics().null) {} +Json::Json(std::nullptr_t) noexcept : m_ptr(statics().null) {} +Json::Json(double value) : m_ptr(make_shared(value)) {} +Json::Json(int value) : m_ptr(make_shared(value)) {} +Json::Json(bool value) : m_ptr(value ? statics().t : statics().f) {} +Json::Json(const string &value) : m_ptr(make_shared(value)) {} +Json::Json(string &&value) : m_ptr(make_shared(move(value))) {} +Json::Json(const char * value) : m_ptr(make_shared(value)) {} +Json::Json(const Json::array &values) : m_ptr(make_shared(values)) {} +Json::Json(Json::array &&values) : m_ptr(make_shared(move(values))) {} +Json::Json(const Json::object &values) : m_ptr(make_shared(values)) {} +Json::Json(Json::object &&values) : m_ptr(make_shared(move(values))) {} + +/* * * * * * * * * * * * * * * * * * * * + * Accessors + */ + +Json::Type Json::type() const { return m_ptr->type(); } +double Json::number_value() const { return m_ptr->number_value(); } +int Json::int_value() const { return m_ptr->int_value(); } +bool Json::bool_value() const { return m_ptr->bool_value(); } +const string & Json::string_value() const { return m_ptr->string_value(); } +const vector & Json::array_items() const { return m_ptr->array_items(); } +const map & Json::object_items() const { return m_ptr->object_items(); } +const Json & Json::operator[] (size_t i) const { return (*m_ptr)[i]; } +const Json & Json::operator[] (const string &key) const { return (*m_ptr)[key]; } + +double JsonValue::number_value() const { return 0; } +int JsonValue::int_value() const { return 0; } +bool JsonValue::bool_value() const { return false; } +const string & JsonValue::string_value() const { return statics().empty_string; } +const vector & JsonValue::array_items() const { return statics().empty_vector; } +const map & JsonValue::object_items() const { return statics().empty_map; } +const Json & JsonValue::operator[] (size_t) const { return static_null(); } +const Json & JsonValue::operator[] (const string &) const { return static_null(); } + +const Json & JsonObject::operator[] (const string &key) const { + auto iter = m_value.find(key); + return (iter == m_value.end()) ? static_null() : iter->second; +} +const Json & JsonArray::operator[] (size_t i) const { + if (i >= m_value.size()) return static_null(); + else return m_value[i]; +} + +/* * * * * * * * * * * * * * * * * * * * + * Comparison + */ + +bool Json::operator== (const Json &other) const { + if (m_ptr == other.m_ptr) + return true; + if (m_ptr->type() != other.m_ptr->type()) + return false; + + return m_ptr->equals(other.m_ptr.get()); +} + +bool Json::operator< (const Json &other) const { + if (m_ptr == other.m_ptr) + return false; + if (m_ptr->type() != other.m_ptr->type()) + return m_ptr->type() < other.m_ptr->type(); + + return m_ptr->less(other.m_ptr.get()); +} + +/* * * * * * * * * * * * * * * * * * * * + * Parsing + */ + +/* esc(c) + * + * Format char c suitable for printing in an error message. + */ +static inline string esc(char c) { + char buf[12]; + if (static_cast(c) >= 0x20 && static_cast(c) <= 0x7f) { + snprintf(buf, sizeof buf, "'%c' (%d)", c, c); + } else { + snprintf(buf, sizeof buf, "(%d)", c); + } + return string(buf); +} + +static inline bool in_range(long x, long lower, long upper) { + return (x >= lower && x <= upper); +} + +namespace { +/* JsonParser + * + * Object that tracks all state of an in-progress parse. + */ +struct JsonParser final { + + /* State + */ + const string &str; + size_t i; + string &err; + bool failed; + const JsonParse strategy; + + /* fail(msg, err_ret = Json()) + * + * Mark this parse as failed. + */ + Json fail(string &&msg) { + return fail(move(msg), Json()); + } + + template + T fail(string &&msg, const T err_ret) { + if (!failed) + err = std::move(msg); + failed = true; + return err_ret; + } + + /* consume_whitespace() + * + * Advance until the current character is non-whitespace. + */ + void consume_whitespace() { + while (str[i] == ' ' || str[i] == '\r' || str[i] == '\n' || str[i] == '\t') + i++; + } + + /* consume_comment() + * + * Advance comments (c-style inline and multiline). + */ + bool consume_comment() { + bool comment_found = false; + if (str[i] == '/') { + i++; + if (i == str.size()) + return fail("unexpected end of input after start of comment", false); + if (str[i] == '/') { // inline comment + i++; + // advance until next line, or end of input + while (i < str.size() && str[i] != '\n') { + i++; + } + comment_found = true; + } + else if (str[i] == '*') { // multiline comment + i++; + if (i > str.size()-2) + return fail("unexpected end of input inside multi-line comment", false); + // advance until closing tokens + while (!(str[i] == '*' && str[i+1] == '/')) { + i++; + if (i > str.size()-2) + return fail( + "unexpected end of input inside multi-line comment", false); + } + i += 2; + comment_found = true; + } + else + return fail("malformed comment", false); + } + return comment_found; + } + + /* consume_garbage() + * + * Advance until the current character is non-whitespace and non-comment. + */ + void consume_garbage() { + consume_whitespace(); + if(strategy == JsonParse::COMMENTS) { + bool comment_found = false; + do { + comment_found = consume_comment(); + if (failed) return; + consume_whitespace(); + } + while(comment_found); + } + } + + /* get_next_token() + * + * Return the next non-whitespace character. If the end of the input is reached, + * flag an error and return 0. + */ + char get_next_token() { + consume_garbage(); + if (failed) return static_cast(0); + if (i == str.size()) + return fail("unexpected end of input", static_cast(0)); + + return str[i++]; + } + + /* encode_utf8(pt, out) + * + * Encode pt as UTF-8 and add it to out. + */ + void encode_utf8(long pt, string & out) { + if (pt < 0) + return; + + if (pt < 0x80) { + out += static_cast(pt); + } else if (pt < 0x800) { + out += static_cast((pt >> 6) | 0xC0); + out += static_cast((pt & 0x3F) | 0x80); + } else if (pt < 0x10000) { + out += static_cast((pt >> 12) | 0xE0); + out += static_cast(((pt >> 6) & 0x3F) | 0x80); + out += static_cast((pt & 0x3F) | 0x80); + } else { + out += static_cast((pt >> 18) | 0xF0); + out += static_cast(((pt >> 12) & 0x3F) | 0x80); + out += static_cast(((pt >> 6) & 0x3F) | 0x80); + out += static_cast((pt & 0x3F) | 0x80); + } + } + + /* parse_string() + * + * Parse a string, starting at the current position. + */ + string parse_string() { + string out; + long last_escaped_codepoint = -1; + while (true) { + if (i == str.size()) + return fail("unexpected end of input in string", ""); + + char ch = str[i++]; + + if (ch == '"') { + encode_utf8(last_escaped_codepoint, out); + return out; + } + + if (in_range(ch, 0, 0x1f)) + return fail("unescaped " + esc(ch) + " in string", ""); + + // The usual case: non-escaped characters + if (ch != '\\') { + encode_utf8(last_escaped_codepoint, out); + last_escaped_codepoint = -1; + out += ch; + continue; + } + + // Handle escapes + if (i == str.size()) + return fail("unexpected end of input in string", ""); + + ch = str[i++]; + + if (ch == 'u') { + // Extract 4-byte escape sequence + string esc = str.substr(i, 4); + // Explicitly check length of the substring. The following loop + // relies on std::string returning the terminating NUL when + // accessing str[length]. Checking here reduces brittleness. + if (esc.length() < 4) { + return fail("bad \\u escape: " + esc, ""); + } + for (size_t j = 0; j < 4; j++) { + if (!in_range(esc[j], 'a', 'f') && !in_range(esc[j], 'A', 'F') + && !in_range(esc[j], '0', '9')) + return fail("bad \\u escape: " + esc, ""); + } + + long codepoint = strtol(esc.data(), nullptr, 16); + + // JSON specifies that characters outside the BMP shall be encoded as a pair + // of 4-hex-digit \u escapes encoding their surrogate pair components. Check + // whether we're in the middle of such a beast: the previous codepoint was an + // escaped lead (high) surrogate, and this is a trail (low) surrogate. + if (in_range(last_escaped_codepoint, 0xD800, 0xDBFF) + && in_range(codepoint, 0xDC00, 0xDFFF)) { + // Reassemble the two surrogate pairs into one astral-plane character, per + // the UTF-16 algorithm. + encode_utf8((((last_escaped_codepoint - 0xD800) << 10) + | (codepoint - 0xDC00)) + 0x10000, out); + last_escaped_codepoint = -1; + } else { + encode_utf8(last_escaped_codepoint, out); + last_escaped_codepoint = codepoint; + } + + i += 4; + continue; + } + + encode_utf8(last_escaped_codepoint, out); + last_escaped_codepoint = -1; + + if (ch == 'b') { + out += '\b'; + } else if (ch == 'f') { + out += '\f'; + } else if (ch == 'n') { + out += '\n'; + } else if (ch == 'r') { + out += '\r'; + } else if (ch == 't') { + out += '\t'; + } else if (ch == '"' || ch == '\\' || ch == '/') { + out += ch; + } else { + return fail("invalid escape character " + esc(ch), ""); + } + } + } + + /* parse_number() + * + * Parse a double. + */ + Json parse_number() { + size_t start_pos = i; + + if (str[i] == '-') + i++; + + // Integer part + if (str[i] == '0') { + i++; + if (in_range(str[i], '0', '9')) + return fail("leading 0s not permitted in numbers"); + } else if (in_range(str[i], '1', '9')) { + i++; + while (in_range(str[i], '0', '9')) + i++; + } else { + return fail("invalid " + esc(str[i]) + " in number"); + } + + if (str[i] != '.' && str[i] != 'e' && str[i] != 'E' + && (i - start_pos) <= static_cast(std::numeric_limits::digits10)) { + return std::atoi(str.c_str() + start_pos); + } + + // Decimal part + if (str[i] == '.') { + i++; + if (!in_range(str[i], '0', '9')) + return fail("at least one digit required in fractional part"); + + while (in_range(str[i], '0', '9')) + i++; + } + + // Exponent part + if (str[i] == 'e' || str[i] == 'E') { + i++; + + if (str[i] == '+' || str[i] == '-') + i++; + + if (!in_range(str[i], '0', '9')) + return fail("at least one digit required in exponent"); + + while (in_range(str[i], '0', '9')) + i++; + } + + return std::strtod(str.c_str() + start_pos, nullptr); + } + + /* expect(str, res) + * + * Expect that 'str' starts at the character that was just read. If it does, advance + * the input and return res. If not, flag an error. + */ + Json expect(const string &expected, Json res) { + assert(i != 0); + i--; + if (str.compare(i, expected.length(), expected) == 0) { + i += expected.length(); + return res; + } else { + return fail("parse error: expected " + expected + ", got " + str.substr(i, expected.length())); + } + } + + /* parse_json() + * + * Parse a JSON object. + */ + Json parse_json(int depth) { + if (depth > max_depth) { + return fail("exceeded maximum nesting depth"); + } + + char ch = get_next_token(); + if (failed) + return Json(); + + if (ch == '-' || (ch >= '0' && ch <= '9')) { + i--; + return parse_number(); + } + + if (ch == 't') + return expect("true", true); + + if (ch == 'f') + return expect("false", false); + + if (ch == 'n') + return expect("null", Json()); + + if (ch == '"') + return parse_string(); + + if (ch == '{') { + map data; + ch = get_next_token(); + if (ch == '}') + return data; + + while (1) { + if (ch != '"') + return fail("expected '\"' in object, got " + esc(ch)); + + string key = parse_string(); + if (failed) + return Json(); + + ch = get_next_token(); + if (ch != ':') + return fail("expected ':' in object, got " + esc(ch)); + + data[std::move(key)] = parse_json(depth + 1); + if (failed) + return Json(); + + ch = get_next_token(); + if (ch == '}') + break; + if (ch != ',') + return fail("expected ',' in object, got " + esc(ch)); + + ch = get_next_token(); + } + return data; + } + + if (ch == '[') { + vector data; + ch = get_next_token(); + if (ch == ']') + return data; + + while (1) { + i--; + data.push_back(parse_json(depth + 1)); + if (failed) + return Json(); + + ch = get_next_token(); + if (ch == ']') + break; + if (ch != ',') + return fail("expected ',' in list, got " + esc(ch)); + + ch = get_next_token(); + (void)ch; + } + return data; + } + + return fail("expected value, got " + esc(ch)); + } +}; +}//namespace { + +Json Json::parse(const string &in, string &err, JsonParse strategy) { + JsonParser parser { in, 0, err, false, strategy }; + Json result = parser.parse_json(0); + + // Check for any trailing garbage + parser.consume_garbage(); + if (parser.failed) + return Json(); + if (parser.i != in.size()) + return parser.fail("unexpected trailing " + esc(in[parser.i])); + + return result; +} + +// Documented in json11.hpp +vector Json::parse_multi(const string &in, + std::string::size_type &parser_stop_pos, + string &err, + JsonParse strategy) { + JsonParser parser { in, 0, err, false, strategy }; + parser_stop_pos = 0; + vector json_vec; + while (parser.i != in.size() && !parser.failed) { + json_vec.push_back(parser.parse_json(0)); + if (parser.failed) + break; + + // Check for another object + parser.consume_garbage(); + if (parser.failed) + break; + parser_stop_pos = parser.i; + } + return json_vec; +} + +/* * * * * * * * * * * * * * * * * * * * + * Shape-checking + */ + +bool Json::has_shape(const shape & types, string & err) const { + if (!is_object()) { + err = "expected JSON object, got " + dump(); + return false; + } + + const auto& obj_items = object_items(); + for (auto & item : types) { + const auto it = obj_items.find(item.first); + if (it == obj_items.cend() || it->second.type() != item.second) { + err = "bad type for " + item.first + " in " + dump(); + return false; + } + } + + return true; +} + +} // namespace json11 diff --git a/3rdparty/json11/json11.hpp b/3rdparty/json11/json11.hpp new file mode 100644 index 00000000..0c47d050 --- /dev/null +++ b/3rdparty/json11/json11.hpp @@ -0,0 +1,232 @@ +/* json11 + * + * json11 is a tiny JSON library for C++11, providing JSON parsing and serialization. + * + * The core object provided by the library is json11::Json. A Json object represents any JSON + * value: null, bool, number (int or double), string (std::string), array (std::vector), or + * object (std::map). + * + * Json objects act like values: they can be assigned, copied, moved, compared for equality or + * order, etc. There are also helper methods Json::dump, to serialize a Json to a string, and + * Json::parse (static) to parse a std::string as a Json object. + * + * Internally, the various types of Json object are represented by the JsonValue class + * hierarchy. + * + * A note on numbers - JSON specifies the syntax of number formatting but not its semantics, + * so some JSON implementations distinguish between integers and floating-point numbers, while + * some don't. In json11, we choose the latter. Because some JSON implementations (namely + * Javascript itself) treat all numbers as the same type, distinguishing the two leads + * to JSON that will be *silently* changed by a round-trip through those implementations. + * Dangerous! To avoid that risk, json11 stores all numbers as double internally, but also + * provides integer helpers. + * + * Fortunately, double-precision IEEE754 ('double') can precisely store any integer in the + * range +/-2^53, which includes every 'int' on most systems. (Timestamps often use int64 + * or long long to avoid the Y2038K problem; a double storing microseconds since some epoch + * will be exact for +/- 275 years.) + */ + +/* Copyright (c) 2013 Dropbox, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#ifdef _MSC_VER + #if _MSC_VER <= 1800 // VS 2013 + #ifndef noexcept + #define noexcept throw() + #endif + + #ifndef snprintf + #define snprintf _snprintf_s + #endif + #endif +#endif + +namespace json11 { + +enum JsonParse { + STANDARD, COMMENTS +}; + +class JsonValue; + +class Json final { +public: + // Types + enum Type { + NUL, NUMBER, BOOL, STRING, ARRAY, OBJECT + }; + + // Array and object typedefs + typedef std::vector array; + typedef std::map object; + + // Constructors for the various types of JSON value. + Json() noexcept; // NUL + Json(std::nullptr_t) noexcept; // NUL + Json(double value); // NUMBER + Json(int value); // NUMBER + Json(bool value); // BOOL + Json(const std::string &value); // STRING + Json(std::string &&value); // STRING + Json(const char * value); // STRING + Json(const array &values); // ARRAY + Json(array &&values); // ARRAY + Json(const object &values); // OBJECT + Json(object &&values); // OBJECT + + // Implicit constructor: anything with a to_json() function. + template + Json(const T & t) : Json(t.to_json()) {} + + // Implicit constructor: map-like objects (std::map, std::unordered_map, etc) + template ().begin()->first)>::value + && std::is_constructible().begin()->second)>::value, + int>::type = 0> + Json(const M & m) : Json(object(m.begin(), m.end())) {} + + // Implicit constructor: vector-like objects (std::list, std::vector, std::set, etc) + template ().begin())>::value, + int>::type = 0> + Json(const V & v) : Json(array(v.begin(), v.end())) {} + + // This prevents Json(some_pointer) from accidentally producing a bool. Use + // Json(bool(some_pointer)) if that behavior is desired. + Json(void *) = delete; + + // Accessors + Type type() const; + + bool is_null() const { return type() == NUL; } + bool is_number() const { return type() == NUMBER; } + bool is_bool() const { return type() == BOOL; } + bool is_string() const { return type() == STRING; } + bool is_array() const { return type() == ARRAY; } + bool is_object() const { return type() == OBJECT; } + + // Return the enclosed value if this is a number, 0 otherwise. Note that json11 does not + // distinguish between integer and non-integer numbers - number_value() and int_value() + // can both be applied to a NUMBER-typed object. + double number_value() const; + int int_value() const; + + // Return the enclosed value if this is a boolean, false otherwise. + bool bool_value() const; + // Return the enclosed string if this is a string, "" otherwise. + const std::string &string_value() const; + // Return the enclosed std::vector if this is an array, or an empty vector otherwise. + const array &array_items() const; + // Return the enclosed std::map if this is an object, or an empty map otherwise. + const object &object_items() const; + + // Return a reference to arr[i] if this is an array, Json() otherwise. + const Json & operator[](size_t i) const; + // Return a reference to obj[key] if this is an object, Json() otherwise. + const Json & operator[](const std::string &key) const; + + // Serialize. + void dump(std::string &out) const; + std::string dump() const { + std::string out; + dump(out); + return out; + } + + // Parse. If parse fails, return Json() and assign an error message to err. + static Json parse(const std::string & in, + std::string & err, + JsonParse strategy = JsonParse::STANDARD); + static Json parse(const char * in, + std::string & err, + JsonParse strategy = JsonParse::STANDARD) { + if (in) { + return parse(std::string(in), err, strategy); + } else { + err = "null input"; + return nullptr; + } + } + // Parse multiple objects, concatenated or separated by whitespace + static std::vector parse_multi( + const std::string & in, + std::string::size_type & parser_stop_pos, + std::string & err, + JsonParse strategy = JsonParse::STANDARD); + + static inline std::vector parse_multi( + const std::string & in, + std::string & err, + JsonParse strategy = JsonParse::STANDARD) { + std::string::size_type parser_stop_pos; + return parse_multi(in, parser_stop_pos, err, strategy); + } + + bool operator== (const Json &rhs) const; + bool operator< (const Json &rhs) const; + bool operator!= (const Json &rhs) const { return !(*this == rhs); } + bool operator<= (const Json &rhs) const { return !(rhs < *this); } + bool operator> (const Json &rhs) const { return (rhs < *this); } + bool operator>= (const Json &rhs) const { return !(*this < rhs); } + + /* has_shape(types, err) + * + * Return true if this is a JSON object and, for each item in types, has a field of + * the given type. If not, return false and set err to a descriptive message. + */ + typedef std::initializer_list> shape; + bool has_shape(const shape & types, std::string & err) const; + +private: + std::shared_ptr m_ptr; +}; + +// Internal class hierarchy - JsonValue objects are not exposed to users of this API. +class JsonValue { +protected: + friend class Json; + friend class JsonInt; + friend class JsonDouble; + virtual Json::Type type() const = 0; + virtual bool equals(const JsonValue * other) const = 0; + virtual bool less(const JsonValue * other) const = 0; + virtual void dump(std::string &out) const = 0; + virtual double number_value() const; + virtual int int_value() const; + virtual bool bool_value() const; + virtual const std::string &string_value() const; + virtual const Json::array &array_items() const; + virtual const Json &operator[](size_t i) const; + virtual const Json::object &object_items() const; + virtual const Json &operator[](const std::string &key) const; + virtual ~JsonValue() {} +}; + +} // namespace json11 diff --git a/CMakeLists.txt b/CMakeLists.txt index e8fa4ec0..54da5bdd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -191,7 +191,7 @@ if (BUILD_PYTHON) endif () endif() -include_directories(common/ json/ frontend/ ${Boost_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS}) +include_directories(common/ json/ frontend/ 3rdparty/json11/ ${Boost_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS}) if(BUILD_HEAP) find_package (Eigen3 REQUIRED NO_MODULE) @@ -202,9 +202,10 @@ endif() aux_source_directory(common/ COMMON_SRC_FILES) aux_source_directory(json/ JSON_PARSER_FILES) +aux_source_directory(3rdparty/json11 EXT_JSON11_FILES) aux_source_directory(frontend/ FRONTEND_FILES) -set(COMMON_FILES ${COMMON_SRC_FILES} ${JSON_PARSER_FILES} ${FRONTEND_FILES}) +set(COMMON_FILES ${COMMON_SRC_FILES} ${EXT_JSON11_FILES} ${JSON_PARSER_FILES} ${FRONTEND_FILES}) set(CMAKE_BUILD_TYPE Release) if(MINGW) From 77cbd70a72997e659bcba70175761b9fb930c5c6 Mon Sep 17 00:00:00 2001 From: David Shah Date: Wed, 13 Nov 2019 18:52:13 +0000 Subject: [PATCH 63/81] frontend: JSON implementation of the generic framework Signed-off-by: David Shah --- frontend/frontend_base.h | 38 ++++---- frontend/json_frontend.cc | 189 ++++++++++++++++++++++++++++++++++++++ frontend/json_frontend.h | 26 ++++++ 3 files changed, 235 insertions(+), 18 deletions(-) create mode 100644 frontend/json_frontend.cc create mode 100644 frontend/json_frontend.h diff --git a/frontend/frontend_base.h b/frontend/frontend_base.h index 35d4409c..426d9431 100644 --- a/frontend/frontend_base.h +++ b/frontend/frontend_base.h @@ -56,11 +56,11 @@ * PortType get_port_dir(const ModulePortDataType &port); * gets the PortType direction of a module port * - * int get_port_offset(const ModulePortDataType &port); - * gets the start bit number of a port + * int get_array_offset(const ModulePortDataType &port); + * gets the start bit number of a port or netname entry * - * bool is_port_upto(const ModulePortDataType &port); - * returns true if a port is an "upto" type port + * bool is_array_upto(const ModulePortDataType &port); + * returns true if a port/net is an "upto" type port or netname entry * * const BitVectorDataType &get_port_bits(const ModulePortDataType &port); * gets the bit vector of a module port @@ -108,6 +108,14 @@ NEXTPNR_NAMESPACE_BEGIN namespace { +// Used for hierarchy resolution +struct ModuleInfo +{ + bool is_top = false, is_blackbox = false, is_whitebox = false; + inline bool is_box() const { return is_blackbox || is_whitebox; } + std::unordered_set instantiated_celltypes; +}; + template struct GenericFrontend { GenericFrontend(Context *ctx, const FrontendType &impl) : ctx(ctx), impl(impl) {} @@ -119,14 +127,6 @@ template struct GenericFrontend using netname_dat_t = typename FrontendType::NetnameDataType; using bitvector_t = typename FrontendType::BitVectorDataType; - // Used for hierarchy resolution - struct ModuleInfo - { - const mod_dat_t *mod_data; - bool is_top = false, is_blackbox = false, is_whitebox = false; - inline bool is_box() const { return is_blackbox || is_whitebox; } - std::unordered_set instantiated_celltypes; - }; std::unordered_map mods; IdString top; @@ -137,7 +137,6 @@ template struct GenericFrontend impl.foreach_module([&](const std::string &name, const mod_dat_t &mod) { IdString mod_id = ctx->id(name); auto &mi = mods[mod_id]; - mi.mod_data = &mod; impl.foreach_attr(mod, [&](const std::string &name, const Property &value) { if (name == "top") mi.is_top = (value.intval != 0); @@ -147,7 +146,7 @@ template struct GenericFrontend mi.is_whitebox = (value.intval != 0); }); impl.foreach_cell(mod, [&](const std::string &name, const cell_dat_t &cell) { - mi.instantiated_cells.insert(ctx->id(impl.get_cell_type(cell))); + mi.instantiated_celltypes.insert(ctx->id(impl.get_cell_type(cell))); }); }); // First of all, see if a top module has been manually specified @@ -230,7 +229,7 @@ template struct GenericFrontend std::unordered_map> port_to_bus; }; - void import_module(HierModuleState &m, mod_dat_t *data) + void import_module(HierModuleState &m, const mod_dat_t &data) { std::vector index_to_net; // Import port connections; for submodules only @@ -289,10 +288,10 @@ template struct GenericFrontend ctx->net_aliases[mergee->name] = base->name; // Update flat index of nets for (auto old_idx : net_old_indices.at(mergee->udata)) { - net_old_indices.at(base).push_back(old_idx); + net_old_indices.at(base->udata).push_back(old_idx); net_flatindex.at(old_idx) = base; } - net_old_indices.at(base).push_back(mergee->udata); + net_old_indices.at(base->udata).push_back(mergee->udata); net_flatindex.at(mergee->udata) = base; net_old_indices.at(mergee->udata).clear(); // Remove merged net from context @@ -349,6 +348,9 @@ template struct GenericFrontend }; } // namespace -template void run_frontend(Context *ctx, const FrontendType &impl) {} +template void run_frontend(Context *ctx, const FrontendType &impl) +{ + GenericFrontend(ctx, impl); +} NEXTPNR_NAMESPACE_END \ No newline at end of file diff --git a/frontend/json_frontend.cc b/frontend/json_frontend.cc new file mode 100644 index 00000000..91c9f06a --- /dev/null +++ b/frontend/json_frontend.cc @@ -0,0 +1,189 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2019 David Shah + * + * 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 "json_frontend.h" +#include "frontend_base.h" +#include "json11.hpp" +#include "log.h" +#include "nextpnr.h" + +#include + +NEXTPNR_NAMESPACE_BEGIN + +using namespace json11; + +struct JsonFrontendImpl +{ + // See specification in frontend_base.h + JsonFrontendImpl(Json &root) : root(root){}; + Json &root; + typedef const Json &ModuleDataType; + typedef const Json &ModulePortDataType; + typedef const Json &CellDataType; + typedef const Json &NetnameDataType; + typedef const Json::array &BitVectorDataType; + + template void foreach_module(TFunc Func) + { + for (const auto &mod : root.object_items()) + Func(mod.first, mod.second); + } + + template void foreach_port(const ModuleDataType &mod, TFunc Func) + { + const auto &ports = mod["ports"]; + if (ports.is_null()) + return; + for (const auto &port : ports.object_items()) + Func(port.first, port.second); + } + + template void foreach_cell(const ModuleDataType &mod, TFunc Func) + { + const auto &cells = mod["cells"]; + if (cells.is_null()) + return; + for (const auto &cell : cells.object_items()) + Func(cell.first, cell.second); + } + + template void foreach_netname(const ModuleDataType &mod, TFunc Func) + { + const auto &netnames = mod["netnames"]; + if (netnames.is_null()) + return; + for (const auto &netname : netnames.object_items()) + Func(netname.first, netname.second); + } + + PortType lookup_portdir(const std::string &dir) + { + if (dir == "input") + return PORT_IN; + else if (dir == "inout") + return PORT_INOUT; + else if (dir == "output") + return PORT_OUT; + else + NPNR_ASSERT_FALSE("invalid json port direction"); + } + + PortType get_port_dir(const ModulePortDataType &port) { return lookup_portdir(port["direction"].string_value()); } + + int get_array_offset(const Json &obj) + { + auto offset = obj["offset"]; + return offset.is_null() ? 0 : offset.int_value(); + } + + bool is_array_upto(const Json &obj) + { + auto upto = obj["upto"]; + return upto.is_null() ? false : bool(upto.int_value()); + } + + const BitVectorDataType &get_port_bits(const ModulePortDataType &port) { return port["bits"].array_items(); } + + const std::string &get_cell_type(const CellDataType &cell) { return cell["type"].string_value(); } + + Property parse_property(const Json &val) + { + if (val.is_number()) + return Property(val.int_value(), 32); + else + return Property::from_string(val.string_value()); + } + + template void foreach_attr(const Json &obj, TFunc Func) + { + const auto &attrs = obj["attributes"]; + if (attrs.is_null()) + return; + for (const auto &attr : attrs.object_items()) { + Func(attr.first, parse_property(attr.second)); + } + } + + template void foreach_param(const Json &obj, TFunc Func) + { + const auto ¶ms = obj["parameters"]; + if (params.is_null()) + return; + for (const auto ¶m : params.object_items()) { + Func(param.first, parse_property(param.second)); + } + } + + template void foreach_port_dir(const CellDataType &cell, TFunc Func) + { + for (const auto &pdir : cell["port_directions"].object_items()) + Func(pdir.first, lookup_portdir(pdir.second.string_value())); + } + + template void foreach_port_conn(const CellDataType &cell, TFunc Func) + { + for (const auto &pconn : cell["connections"].object_items()) + Func(pconn.first, pconn.second); + } + + template const BitVectorDataType &get_net_bits(const NetnameDataType &net) + { + return net["bits"].array_items(); + } + + int get_vector_length(const BitVectorDataType &bits) { return int(bits.size()); } + + bool is_vector_bit_constant(const BitVectorDataType &bits, int i) + { + NPNR_ASSERT(i < int(bits.size())); + return bits[i].is_string(); + } + + char get_vector_bit_constval(const BitVectorDataType &bits, int i) + { + auto s = bits.at(i).string_value(); + NPNR_ASSERT(s.size() == 1); + return s.at(0); + } + + int get_vector_bit_signal(const BitVectorDataType &bits, int i) + { + NPNR_ASSERT(bits.at(i).is_number()); + return bits.at(i).int_value(); + } +}; + +bool parse_json(std::istream &in, const std::string &filename, Context *ctx) +{ + Json root; + { + if (!in) + log_error("Failed to open JSON file '%s'.\n", filename.c_str()); + std::string json_str((std::istreambuf_iterator(in)), std::istreambuf_iterator()); + std::string error; + root = Json::parse(json_str, error, JsonParse::COMMENTS); + if (root.is_null()) + log_error("Failed to parse JSON file '%s': %s.\n", filename.c_str(), error.c_str()); + } + run_frontend(ctx, JsonFrontendImpl(root)); + return true; +} + +NEXTPNR_NAMESPACE_END \ No newline at end of file diff --git a/frontend/json_frontend.h b/frontend/json_frontend.h new file mode 100644 index 00000000..1e3deb8d --- /dev/null +++ b/frontend/json_frontend.h @@ -0,0 +1,26 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2019 David Shah + * + * 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" + +NEXTPNR_NAMESPACE_BEGIN + +bool parse_json(std::istream &in, const std::string &filename, Context *ctx); + +NEXTPNR_NAMESPACE_END \ No newline at end of file From 21b4965966048402424e76027ee9807d071e2b1c Mon Sep 17 00:00:00 2001 From: David Shah Date: Thu, 14 Nov 2019 19:07:38 +0000 Subject: [PATCH 64/81] frontend/base: Functions for net[name] import Signed-off-by: David Shah --- frontend/frontend_base.h | 116 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/frontend/frontend_base.h b/frontend/frontend_base.h index 426d9431..6d0e04c7 100644 --- a/frontend/frontend_base.h +++ b/frontend/frontend_base.h @@ -227,6 +227,8 @@ template struct GenericFrontend return index_to_net_flatindex.at(idx); } std::unordered_map> port_to_bus; + // All of the names given to a net + std::vector> net_names; }; void import_module(HierModuleState &m, const mod_dat_t &data) @@ -238,6 +240,120 @@ template struct GenericFrontend } } + // Multiple labels might refer to the same net. Resolve conflicts for the primary name thus: + // - (toplevel) ports are always preferred + // - names with fewer $ are always prefered + // - between equal $ counts, fewer .s are prefered + // - ties are resolved alphabetically + bool prefer_netlabel(HierModuleState &m, const std::string &a, const std::string &b) + { + if (m.port_to_bus.count(ctx->id(a))) + return true; + if (m.port_to_bus.count(ctx->id(b))) + return false; + + if (b.empty()) + return true; + long a_dollars = std::count(a.begin(), a.end(), '$'), b_dollars = std::count(b.begin(), b.end(), '$'); + if (a_dollars < b_dollars) + return true; + else if (a_dollars > b_dollars) + return false; + long a_dots = std::count(a.begin(), a.end(), '.'), b_dots = std::count(b.begin(), b.end(), '.'); + if (a_dots < b_dots) + return true; + else if (a_dots > b_dots) + return false; + return a < b; + }; + + // Get a net by index in modulestate (not flatindex); creating it if it doesn't already exist + NetInfo *create_or_get_net(HierModuleState &m, int idx) + { + std::string name; + if (idx < int(m.net_names.size()) && !m.net_names.at(idx).empty()) { + // Use the rule above to find the preferred name for a net + name = m.net_names.at(idx).at(0); + for (size_t j = 1; j < m.net_names.at(idx).size(); j++) + if (prefer_netlabel(m.net_names.at(idx).at(j), name)) + name = m.net_names.at(idx).at(j); + } else { + name = "$frontend$" + std::to_string(idx); + } + NetInfo *net = ctx->createNet(unique_name(m.prefix, name, true)); + // Add to the flat index of nets + net->udata = int(net_flatindex.size()); + net_flatindex.push_back(net); + // Add to the module-level index of nets + auto &midx = m.net_by_idx(idx); + // Check we don't try and create more than one net with the same index + NPNR_ASSERT(midx == -1); + midx = net->udata; + // Create aliases for all possible names + if (idx < int(m.net_names.size()) && !m.net_names.at(idx).empty()) { + for (const auto &name : m.net_names.at(idx)) { + IdString name_id = ctx->id(name); + net->aliases.push_back(name_id); + ctx->net_aliases[name_id] = net->name; + } + } else { + net->aliases.push_back(net->name); + ctx->net_aliases[net->name] = net->name; + } + } + + // Get the name of a vector bit given basename; settings and index + std::string get_bit_name(const std::string &base, int index, int length, int offset = 0, bool upto = false) + { + std::string port = base; + if (length == 1 && offset == 0) + return port; + int real_index; + if (upto) + real_index = offset + length - index - 1; // reversed ports like [0:7] + else + real_index = offset + index; // normal 'downto' ports like [7:0] + port += '['; + port += std::to_string(real_index); + port += ']'; + return port; + } + + // Import the netnames section of a module + void import_module_netnames(HierModuleState &m, const mod_dat_t &data) + { + impl.foreach_netname(data, [&](const std::string &basename, const netname_dat_t &nn) { + bool upto = impl.is_array_upto(nn); + int offset = impl.get_array_offset(nn); + const auto &bits = impl.get_net_bits(nn); + int width = impl.get_vector_length(bits); + for (int i = 0; i < width; i++) { + if (impl.is_vector_bit_constant(bits, i)) + continue; + + std::string bit_name = get_bit_name(basename, i, width, offset, upto); + + int net_bit = impl.get_vecotr_bit_signal(bits, i); + int mapped_bit = m.net_by_idx(net_bit); + if (mapped_bit == -1) { + // Net doesn't exist yet. Add the name here to the list of candidate names so we have that for when + // we create it later + if (net_bit >= int(m.net_names.size())) + m.net_names.resize(net_bit + 1); + m.net_names.at(net_bit).push_back(bit_name); + } else { + // Net already exists; add this name as an alias + NetInfo *ni = net_flatindex.at(mapped_bit); + IdString alias_name = ctx->id(m.prefix + bit_name); + if (ctx->net_aliases.count(alias_name)) + continue; // don't add duplicate aliases + ctx->net_aliases[alias_name] = ni->name; + ni->aliases.push_back(alias_name); + } + } + }); + } + // Add a constant-driving VCC or GND cell to make a net constant // (constval can be [01xz], x and z or no-ops) int const_autoidx = 0; From bed053251eece5a45e32eebc958fd5f3c683a9c1 Mon Sep 17 00:00:00 2001 From: David Shah Date: Thu, 14 Nov 2019 20:01:45 +0000 Subject: [PATCH 65/81] frontend/base: Functions for leaf cell import Signed-off-by: David Shah --- frontend/frontend_base.h | 72 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/frontend/frontend_base.h b/frontend/frontend_base.h index 6d0e04c7..beb0e31a 100644 --- a/frontend/frontend_base.h +++ b/frontend/frontend_base.h @@ -354,6 +354,78 @@ template struct GenericFrontend }); } + void create_constant_net(HierModuleState &m, const std::string name_hint, char constval) + { + IdString name = unique_name(m.base, name_hint); + NetInfo *ni = ctx->createNet(name); + add_constant_driver(m, ni, constval); + } + + // Import a leaf cell - (white|black)box + void import_leaf_cell(HierModuleState &m, const std::string &name, const cell_dat_t &cd) + { + IdString inst_name = unique_name(m.base, name, false); + CellInfo *ci = ctx->createCell(inst_name, ctx->id(get_cell_type(cd))); + // Import port directions + std::unordered_map port_dirs; + impl.foreach_port_dir(cd, [&](const std::string &port, PortType dir) { port_dirs[ctx->id(port)] = dir; }); + // Import port connectivity + impl.foreach_port_conn(cd, [&](const std::string &name, const bitvector_t &bits) { + if (!port_dirs.count(ctx->id(name))) + log_error("Failed to get direction for port '%s' of cell '%s'\n", name.c_str(), inst_name.c_str(ctx)); + PortType dir = port_dirs.at(ctx->id(name)); + int width = impl.get_vector_length(bits); + for (int i = 0; i < width; i++) { + std::string port_bit_name = get_bit_name(name, i, width); + IdString port_bit_ids = ctx->id(port_bit_name); + // Create cell port + ci->ports[port_bit_ids].name = port_bit_ids; + ci->ports[port_bit_ids].type = dir; + // Resolve connectivity + NetInfo *net; + if (impl.is_vector_bit_constant(bits, i)) { + // Create a constant driver if one is needed + net = create_constant_net(m, name + "." + port_bit_name + "$const", + impl.get_vector_bit_constval(bits, i)); + } else { + // Otherwise, lookup (creating if needed) the net with this index + net = create_or_get_net(m, impl.get_vector_bit_signal(bits, i)); + } + NPNR_ASSERT(net != nullptr); + + // Check for multiple drivers + if (dir == PORT_OUT && net->driver.cell != nullptr) + log_error("Net '%s' is multiply driven by cell ports %s.%s and %s.%s\n", ctx->nameOf(net), + ctx->nameOf(net->driver.cell), ctx->nameOf(net->driver.port), ctx->nameOf(inst_name), + port_bit_name.c_str()); + connect_port(ctx, net, ci, port_bit_ids); + } + }); + // Import attributes and parameters + impl.foreach_attr(cd, + [&](const std::string &name, const Property &value) { ci->attrs[ctx->id(name)] = value; }); + impl.foreach_param(cd, + [&](const std::string &name, const Property &value) { ci->params[ctx->id(name)] = value; }); + } + + // Import a submodule cell + void import_submodule_cell(HierModuleState &m, const std::string &name, const cell_dat_t &cd) {} + + // Import the cells section of a module + void import_module_cells(HierModuleState &m, const mod_dat_t &data) + { + m.foreach_cell(data, [&](const std::string &cellname, const cell_dat_t &cd) { + IdString type = ctx->id(get_cell_type(cd)); + if (mods.count(type) && !mods.at(type).is_box()) { + // Module type is known; and not boxed. Import as a submodule by flattening hierarchy + import_submodule_cell(m, cellname, cd); + } else { + // Module type is unknown or boxes. Import as a leaf cell (nextpnr CellInfo) + import_leaf_cell(m, cellname, cd); + } + }); + } + // Add a constant-driving VCC or GND cell to make a net constant // (constval can be [01xz], x and z or no-ops) int const_autoidx = 0; From 98f93f39be7385179036e565fdf9e51eafbce173 Mon Sep 17 00:00:00 2001 From: David Shah Date: Thu, 14 Nov 2019 20:13:57 +0000 Subject: [PATCH 66/81] frontend/base: Functions for hierarchical submodule cell import Signed-off-by: David Shah --- frontend/frontend_base.h | 127 ++++++++++++++++++++++++--------------- 1 file changed, 79 insertions(+), 48 deletions(-) diff --git a/frontend/frontend_base.h b/frontend/frontend_base.h index beb0e31a..dbf4b4bc 100644 --- a/frontend/frontend_base.h +++ b/frontend/frontend_base.h @@ -128,6 +128,7 @@ template struct GenericFrontend using bitvector_t = typename FrontendType::BitVectorDataType; std::unordered_map mods; + std::unordered_map mod_refs; IdString top; // Process the list of modules and determine @@ -137,6 +138,7 @@ template struct GenericFrontend impl.foreach_module([&](const std::string &name, const mod_dat_t &mod) { IdString mod_id = ctx->id(name); auto &mi = mods[mod_id]; + mod_refs[mod_id] = mod; impl.foreach_attr(mod, [&](const std::string &name, const Property &value) { if (name == "top") mi.is_top = (value.intval != 0); @@ -354,75 +356,104 @@ template struct GenericFrontend }); } - void create_constant_net(HierModuleState &m, const std::string name_hint, char constval) - { + void create_constant_net(HierModuleState &m, const std::string name_hint, char constval) { IdString name = unique_name(m.base, name_hint); NetInfo *ni = ctx->createNet(name); add_constant_driver(m, ni, constval); } // Import a leaf cell - (white|black)box - void import_leaf_cell(HierModuleState &m, const std::string &name, const cell_dat_t &cd) - { + void import_leaf_cell(HierModuleState &m, const std::string &name, const cell_dat_t &cd) { IdString inst_name = unique_name(m.base, name, false); CellInfo *ci = ctx->createCell(inst_name, ctx->id(get_cell_type(cd))); // Import port directions std::unordered_map port_dirs; - impl.foreach_port_dir(cd, [&](const std::string &port, PortType dir) { port_dirs[ctx->id(port)] = dir; }); + impl.foreach_port_dir(cd, [&](const std::string &port, PortType dir) { + port_dirs[ctx->id(port)] = dir; + }); // Import port connectivity impl.foreach_port_conn(cd, [&](const std::string &name, const bitvector_t &bits) { - if (!port_dirs.count(ctx->id(name))) - log_error("Failed to get direction for port '%s' of cell '%s'\n", name.c_str(), inst_name.c_str(ctx)); - PortType dir = port_dirs.at(ctx->id(name)); - int width = impl.get_vector_length(bits); - for (int i = 0; i < width; i++) { - std::string port_bit_name = get_bit_name(name, i, width); - IdString port_bit_ids = ctx->id(port_bit_name); - // Create cell port - ci->ports[port_bit_ids].name = port_bit_ids; - ci->ports[port_bit_ids].type = dir; - // Resolve connectivity - NetInfo *net; - if (impl.is_vector_bit_constant(bits, i)) { - // Create a constant driver if one is needed - net = create_constant_net(m, name + "." + port_bit_name + "$const", - impl.get_vector_bit_constval(bits, i)); - } else { - // Otherwise, lookup (creating if needed) the net with this index - net = create_or_get_net(m, impl.get_vector_bit_signal(bits, i)); - } - NPNR_ASSERT(net != nullptr); + if (!port_dirs.count(ctx->id(name))) + log_error("Failed to get direction for port '%s' of cell '%s'\n", name.c_str(), inst_name.c_str(ctx)); + PortType dir = port_dirs.at(ctx->id(name)); + int width = impl.get_vector_length(bits); + for (int i = 0; i < width; i++) { + std::string port_bit_name = get_bit_name(name, i, width); + IdString port_bit_ids = ctx->id(port_bit_name); + // Create cell port + ci->ports[port_bit_ids].name = port_bit_ids; + ci->ports[port_bit_ids].type = dir; + // Resolve connectivity + NetInfo *net; + if (impl.is_vector_bit_constant(bits, i)) { + // Create a constant driver if one is needed + net = create_constant_net(m, name + "." + port_bit_name + "$const", impl.get_vector_bit_constval(bits, i)); + } else { + // Otherwise, lookup (creating if needed) the net with this index + net = create_or_get_net(m, impl.get_vector_bit_signal(bits, i)); + } + NPNR_ASSERT(net != nullptr); - // Check for multiple drivers - if (dir == PORT_OUT && net->driver.cell != nullptr) - log_error("Net '%s' is multiply driven by cell ports %s.%s and %s.%s\n", ctx->nameOf(net), - ctx->nameOf(net->driver.cell), ctx->nameOf(net->driver.port), ctx->nameOf(inst_name), - port_bit_name.c_str()); - connect_port(ctx, net, ci, port_bit_ids); - } + // Check for multiple drivers + if (dir == PORT_OUT && net->driver.cell != nullptr) + log_error("Net '%s' is multiply driven by cell ports %s.%s and %s.%s\n", ctx->nameOf(net), + ctx->nameOf(net->driver.cell), ctx->nameOf(net->driver.port), ctx->nameOf(inst_name), port_bit_name.c_str()); + connect_port(ctx, net, ci, port_bit_ids); + } }); // Import attributes and parameters - impl.foreach_attr(cd, - [&](const std::string &name, const Property &value) { ci->attrs[ctx->id(name)] = value; }); - impl.foreach_param(cd, - [&](const std::string &name, const Property &value) { ci->params[ctx->id(name)] = value; }); + impl.foreach_attr(cd, [&](const std::string &name, const Property &value) { + ci->attrs[ctx->id(name)] = value; + }); + impl.foreach_param(cd, [&](const std::string &name, const Property &value) { + ci->params[ctx->id(name)] = value; + }); } // Import a submodule cell - void import_submodule_cell(HierModuleState &m, const std::string &name, const cell_dat_t &cd) {} + void import_submodule_cell(HierModuleState &m, const std::string &name, const cell_dat_t &cd) { + HierModuleState submod; + submod.is_toplevel = false; + // Create mapping from submodule port to nets (referenced by index in flatindex) + impl.foreach_port_conn(cd, [&](const std::string &name, const bitvector_t &bits) { + int width = impl.get_vector_length(bits); + for (int i = 0; i < width; i++) { + // Index of port net in flatindex + int net_ref = -1; + if (impl.is_vector_bit_constant(bits, i)) { + // Create a constant driver if one is needed + std::string port_bit_name = get_bit_name(name, i, width); + NetInfo *cnet = create_constant_net(m, name + "." + port_bit_name + "$const", impl.get_vector_bit_constval(bits, i)); + cnet->udata = int(net_flatindex.size()); + net_flatindex.push_back(cnet); + net_ref = cnet->udata; + } else { + // Otherwise, lookup (creating if needed) the net with given in-module index + net_ref = create_or_get_net(m, impl.get_vector_bit_signal(bits, i))->udata; + } + NPNR_ASSERT(net_ref != -1); + submod.port_to_bus[ctx->id(name)].push_back(net_ref); + } + }); + // Create prefix for submodule + submod.prefix = m.prefix; + submod.prefix += name; + submod.prefix += '.'; + // Do the submodule import + import_module(submod, mod_refs.at(ctx->id(impl.get_cell_type(cd)))); + } // Import the cells section of a module - void import_module_cells(HierModuleState &m, const mod_dat_t &data) - { + void import_module_cells(HierModuleState &m, const mod_dat_t &data) { m.foreach_cell(data, [&](const std::string &cellname, const cell_dat_t &cd) { - IdString type = ctx->id(get_cell_type(cd)); - if (mods.count(type) && !mods.at(type).is_box()) { - // Module type is known; and not boxed. Import as a submodule by flattening hierarchy - import_submodule_cell(m, cellname, cd); - } else { - // Module type is unknown or boxes. Import as a leaf cell (nextpnr CellInfo) - import_leaf_cell(m, cellname, cd); - } + IdString type = ctx->id(get_cell_type(cd)); + if (mods.count(type) && !mods.at(type).is_box()) { + // Module type is known; and not boxed. Import as a submodule by flattening hierarchy + import_submodule_cell(m, cellname, cd); + } else { + // Module type is unknown or boxes. Import as a leaf cell (nextpnr CellInfo) + import_leaf_cell(m, cellname, cd); + } }); } From a26b1a276dbb367beec1f07da19224d11109ef51 Mon Sep 17 00:00:00 2001 From: David Shah Date: Fri, 15 Nov 2019 17:43:32 +0000 Subject: [PATCH 67/81] frontend: Top level port import Signed-off-by: David Shah --- frontend/frontend_base.h | 208 +++++++++++++++++++++++++++++---------- 1 file changed, 157 insertions(+), 51 deletions(-) diff --git a/frontend/frontend_base.h b/frontend/frontend_base.h index dbf4b4bc..196a28a3 100644 --- a/frontend/frontend_base.h +++ b/frontend/frontend_base.h @@ -119,6 +119,14 @@ struct ModuleInfo template struct GenericFrontend { GenericFrontend(Context *ctx, const FrontendType &impl) : ctx(ctx), impl(impl) {} + void operator()() + { + find_top_module(); + HierModuleState m; + m.is_toplevel = true; + m.prefix = ""; + import_module(m, mod_refs.at(top)); + } Context *ctx; const FrontendType &impl; using mod_dat_t = typename FrontendType::ModuleDataType; @@ -128,7 +136,7 @@ template struct GenericFrontend using bitvector_t = typename FrontendType::BitVectorDataType; std::unordered_map mods; - std::unordered_map mod_refs; + std::unordered_map mod_refs; IdString top; // Process the list of modules and determine @@ -236,10 +244,18 @@ template struct GenericFrontend void import_module(HierModuleState &m, const mod_dat_t &data) { std::vector index_to_net; - // Import port connections; for submodules only if (!m.is_toplevel) { + // Import port connections; for submodules only import_port_connections(m, data); + } else { + // Just create a list of ports for netname resolution + impl.foreach_port(m, + [&](const std::string &name, const mod_port_dat_t &) { m.port_to_bus[ctx->id(name)]; }); } + import_module_netnames(m, data); + import_module_cells(m, data); + if (m.is_toplevel) + import_toplevel_ports(m, data); } // Multiple labels might refer to the same net. Resolve conflicts for the primary name thus: @@ -356,62 +372,63 @@ template struct GenericFrontend }); } - void create_constant_net(HierModuleState &m, const std::string name_hint, char constval) { + void create_constant_net(HierModuleState &m, const std::string name_hint, char constval) + { IdString name = unique_name(m.base, name_hint); NetInfo *ni = ctx->createNet(name); add_constant_driver(m, ni, constval); } // Import a leaf cell - (white|black)box - void import_leaf_cell(HierModuleState &m, const std::string &name, const cell_dat_t &cd) { + void import_leaf_cell(HierModuleState &m, const std::string &name, const cell_dat_t &cd) + { IdString inst_name = unique_name(m.base, name, false); CellInfo *ci = ctx->createCell(inst_name, ctx->id(get_cell_type(cd))); // Import port directions std::unordered_map port_dirs; - impl.foreach_port_dir(cd, [&](const std::string &port, PortType dir) { - port_dirs[ctx->id(port)] = dir; - }); + impl.foreach_port_dir(cd, [&](const std::string &port, PortType dir) { port_dirs[ctx->id(port)] = dir; }); // Import port connectivity impl.foreach_port_conn(cd, [&](const std::string &name, const bitvector_t &bits) { - if (!port_dirs.count(ctx->id(name))) - log_error("Failed to get direction for port '%s' of cell '%s'\n", name.c_str(), inst_name.c_str(ctx)); - PortType dir = port_dirs.at(ctx->id(name)); - int width = impl.get_vector_length(bits); - for (int i = 0; i < width; i++) { - std::string port_bit_name = get_bit_name(name, i, width); - IdString port_bit_ids = ctx->id(port_bit_name); - // Create cell port - ci->ports[port_bit_ids].name = port_bit_ids; - ci->ports[port_bit_ids].type = dir; - // Resolve connectivity - NetInfo *net; - if (impl.is_vector_bit_constant(bits, i)) { - // Create a constant driver if one is needed - net = create_constant_net(m, name + "." + port_bit_name + "$const", impl.get_vector_bit_constval(bits, i)); - } else { - // Otherwise, lookup (creating if needed) the net with this index - net = create_or_get_net(m, impl.get_vector_bit_signal(bits, i)); - } - NPNR_ASSERT(net != nullptr); + if (!port_dirs.count(ctx->id(name))) + log_error("Failed to get direction for port '%s' of cell '%s'\n", name.c_str(), inst_name.c_str(ctx)); + PortType dir = port_dirs.at(ctx->id(name)); + int width = impl.get_vector_length(bits); + for (int i = 0; i < width; i++) { + std::string port_bit_name = get_bit_name(name, i, width); + IdString port_bit_ids = ctx->id(port_bit_name); + // Create cell port + ci->ports[port_bit_ids].name = port_bit_ids; + ci->ports[port_bit_ids].type = dir; + // Resolve connectivity + NetInfo *net; + if (impl.is_vector_bit_constant(bits, i)) { + // Create a constant driver if one is needed + net = create_constant_net(m, name + "." + port_bit_name + "$const", + impl.get_vector_bit_constval(bits, i)); + } else { + // Otherwise, lookup (creating if needed) the net with this index + net = create_or_get_net(m, impl.get_vector_bit_signal(bits, i)); + } + NPNR_ASSERT(net != nullptr); - // Check for multiple drivers - if (dir == PORT_OUT && net->driver.cell != nullptr) - log_error("Net '%s' is multiply driven by cell ports %s.%s and %s.%s\n", ctx->nameOf(net), - ctx->nameOf(net->driver.cell), ctx->nameOf(net->driver.port), ctx->nameOf(inst_name), port_bit_name.c_str()); - connect_port(ctx, net, ci, port_bit_ids); - } + // Check for multiple drivers + if (dir == PORT_OUT && net->driver.cell != nullptr) + log_error("Net '%s' is multiply driven by cell ports %s.%s and %s.%s\n", ctx->nameOf(net), + ctx->nameOf(net->driver.cell), ctx->nameOf(net->driver.port), ctx->nameOf(inst_name), + port_bit_name.c_str()); + connect_port(ctx, net, ci, port_bit_ids); + } }); // Import attributes and parameters - impl.foreach_attr(cd, [&](const std::string &name, const Property &value) { - ci->attrs[ctx->id(name)] = value; - }); - impl.foreach_param(cd, [&](const std::string &name, const Property &value) { - ci->params[ctx->id(name)] = value; - }); + impl.foreach_attr(cd, + [&](const std::string &name, const Property &value) { ci->attrs[ctx->id(name)] = value; }); + impl.foreach_param(cd, + [&](const std::string &name, const Property &value) { ci->params[ctx->id(name)] = value; }); } // Import a submodule cell - void import_submodule_cell(HierModuleState &m, const std::string &name, const cell_dat_t &cd) { + void import_submodule_cell(HierModuleState &m, const std::string &name, const cell_dat_t &cd) + { HierModuleState submod; submod.is_toplevel = false; // Create mapping from submodule port to nets (referenced by index in flatindex) @@ -423,7 +440,8 @@ template struct GenericFrontend if (impl.is_vector_bit_constant(bits, i)) { // Create a constant driver if one is needed std::string port_bit_name = get_bit_name(name, i, width); - NetInfo *cnet = create_constant_net(m, name + "." + port_bit_name + "$const", impl.get_vector_bit_constval(bits, i)); + NetInfo *cnet = create_constant_net(m, name + "." + port_bit_name + "$const", + impl.get_vector_bit_constval(bits, i)); cnet->udata = int(net_flatindex.size()); net_flatindex.push_back(cnet); net_ref = cnet->udata; @@ -444,16 +462,104 @@ template struct GenericFrontend } // Import the cells section of a module - void import_module_cells(HierModuleState &m, const mod_dat_t &data) { + void import_module_cells(HierModuleState &m, const mod_dat_t &data) + { m.foreach_cell(data, [&](const std::string &cellname, const cell_dat_t &cd) { - IdString type = ctx->id(get_cell_type(cd)); - if (mods.count(type) && !mods.at(type).is_box()) { - // Module type is known; and not boxed. Import as a submodule by flattening hierarchy - import_submodule_cell(m, cellname, cd); - } else { - // Module type is unknown or boxes. Import as a leaf cell (nextpnr CellInfo) - import_leaf_cell(m, cellname, cd); - } + IdString type = ctx->id(get_cell_type(cd)); + if (mods.count(type) && !mods.at(type).is_box()) { + // Module type is known; and not boxed. Import as a submodule by flattening hierarchy + import_submodule_cell(m, cellname, cd); + } else { + // Module type is unknown or boxes. Import as a leaf cell (nextpnr CellInfo) + import_leaf_cell(m, cellname, cd); + } + }); + } + + // Create a top level input/output buffer + CellInfo *create_iobuf(NetInfo *net, PortType dir, const std::string &name) + { + // Skip IOBUF insertion if this is a design checkpoint (where they will already exist) + if (ctx->settings.count(ctx->id("synth"))) + return nullptr; + IdString name_id = ctx->id(name); + if (ctx->cells.count(name_id)) + log_error("Cell '%s' of type '%s' with the same name as a top-level IO is not allowed.\n", name.c_str(), + ctx->cells.at(name_id)->type.c_str(ctx)); + CellInfo *iobuf = ctx->createCell(name_id, ctx->id("unknown_iob")); + // Copy attributes from net to IOB + for (auto &attr : net->attrs) + iobuf->attrs[attr.first] = attr.second; + // What we do now depends on port type + if (dir == PORT_IN) { + iobuf->type = ctx->id("$nextpnr_ibuf"); + iobuf->addOutput(ctx->id("O")); + if (net->driver.cell != nullptr) { + CellInfo *drv = net->driver.cell; + if (drv->type != ctx->id("$nextpnr_iobuf")) + log_error("Net '%s' is multiply driven by cell port %s.%s and top level input '%s'.\n", + ctx->nameOf(net), ctx->nameOf(drv), ctx->nameOf(net->driver.port), name.c_str()); + // Special case: input, etc, directly drives inout + // Use the input net of the inout instead + net = drv->ports.at(ctx->id("I")).net; + } + NPNR_ASSERT(net->driver.cell == nullptr); + // Connect IBUF output and net + connect_port(ctx, net, iobuf, ctx->id("O")); + } else if (dir == PORT_OUT) { + iobuf->type = ctx->id("$nextpnr_obuf"); + iobuf->addInput(ctx->id("I")); + // Connect IBUF input and net + connect_port(ctx, net, iobuf, ctx->id("I")); + } else if (dir == PORT_INOUT) { + iobuf->type = ctx->id("$nextpnr_iobuf"); + iobuf->addInput(ctx->id("I")); + iobuf->addOutput(ctx->id("O")); + // Need to bifurcate the net to avoid multiple drivers and split + // the input/output parts of an inout + // Create a new net connecting only the current net's driver and the IOBUF input + // Then use the IOBUF output to drive all of the current net's users + NetInfo *split_iobuf_i = ctx->createNet(unique_name("", "$" + name + "$iobuf_i", true)); + auto drv = net->driver; + if (drv.cell != nullptr) { + disconnect_port(ctx, drv.cell, drv.port); + connect_port(ctx, split_iobuf_i, drv.cell, drv.port); + } + connect_port(ctx, split_iobuf_i, iobuf, ctx->id("I")); + NPNR_ASSERT(net->driver.cell == nullptr); + connect_port(ctx, net, iobuf, ctx->id("O")); + } + + PortInfo pinfo; + pinfo.name = name_id; + pinfo.net = net; + pinfo.type = dir; + ctx->ports[pinfo.name] = pinfo; + + return iobuf; + } + + // Import ports of the top level module + void import_toplevel_ports(HierModuleState &m, const mod_dat_t &data) + { + m.foreach_port(data, [&](const std::string &portname, const mod_port_dat_t &pd) { + const auto &port_bv = impl.get_port_bits(pd); + int offset = impl.get_array_offset(pd); + bool is_upto = impl.is_array_upto(pd); + int width = impl.get_vector_length(port_bv); + PortType dir = impl.get_port_dir(pd); + for (int i = 0; i < width; i++) { + std::string pbit_name = get_bit_name(portname, i, width, offset, is_upto); + NetInfo *port_net = nullptr; + if (impl.is_vector_bit_constant(port_bv, i)) { + // Port bit is constant. Need to create a new constant net. + port_net = create_constant_net(m, pbit_name + "$const", impl.get_vector_bit_constval(port_bv, i)); + } else { + // Port bit is a signal. Need to create/get the associated net + port_net = create_or_get_net(m, impl.get_vector_bit_signal(port_bv, i)); + } + create_iobuf(port_net, dir, pbit_name); + } }); } @@ -569,7 +675,7 @@ template struct GenericFrontend template void run_frontend(Context *ctx, const FrontendType &impl) { - GenericFrontend(ctx, impl); + GenericFrontend(ctx, impl)(); } NEXTPNR_NAMESPACE_END \ No newline at end of file From eb14cf09f4d2c81a3f01f33394b78ec29df44035 Mon Sep 17 00:00:00 2001 From: David Shah Date: Fri, 15 Nov 2019 17:55:02 +0000 Subject: [PATCH 68/81] frontend: Fix template compile issues Signed-off-by: David Shah --- frontend/frontend_base.h | 73 ++++++++++++++++++++------------------- frontend/json_frontend.cc | 50 +++++++++++++-------------- 2 files changed, 62 insertions(+), 61 deletions(-) diff --git a/frontend/frontend_base.h b/frontend/frontend_base.h index 196a28a3..3484b5b4 100644 --- a/frontend/frontend_base.h +++ b/frontend/frontend_base.h @@ -37,66 +37,66 @@ * * Functions: * - * void foreach_module(Func); + * void foreach_module(Func) const; * calls Func(const std::string &name, const ModuleDataType &mod); * for each module in the netlist * - * void foreach_port(const ModuleDataType &mod, Func); + * void foreach_port(const ModuleDataType &mod, Func) const; * calls Func(const std::string &name, const ModulePortDataType &port); * for each port of mod * - * void foreach_cell(const ModuleDataType &mod, Func); - * calls Func(const std::string &name, const CellDataType &cell); + * void foreach_cell(const ModuleDataType &mod, Func) const; + * calls Func(const std::string &name, const CellDataType &cell) * for each cell of mod * - * void foreach_netname(const ModuleDataType &mod, Func); + * void foreach_netname(const ModuleDataType &mod, Func) const; * calls Func(const std::string &name, const NetnameDataType &cell); * for each netname entry of mod * - * PortType get_port_dir(const ModulePortDataType &port); + * PortType get_port_dir(const ModulePortDataType &port) const; * gets the PortType direction of a module port * - * int get_array_offset(const ModulePortDataType &port); + * int get_array_offset(const ModulePortDataType &port) const; * gets the start bit number of a port or netname entry * - * bool is_array_upto(const ModulePortDataType &port); + * bool is_array_upto(const ModulePortDataType &port) const; * returns true if a port/net is an "upto" type port or netname entry * - * const BitVectorDataType &get_port_bits(const ModulePortDataType &port); + * const BitVectorDataType &get_port_bits(const ModulePortDataType &port) const; * gets the bit vector of a module port * - * const std::string& get_cell_type(const CellDataType &cell); + * const std::string& get_cell_type(const CellDataType &cell) const; * gets the type of a cell * - * void foreach_attr(const {ModuleDataType|CellDataType|ModulePortDataType|NetnameDataType} &obj, Func); + * void foreach_attr(const {ModuleDataType|CellDataType|ModulePortDataType|NetnameDataType} &obj, Func) const; * calls Func(const std::string &name, const Property &value); * for each attribute on a module, cell, module port or net * - * void foreach_param(const CellDataType &obj, Func); + * void foreach_param(const CellDataType &obj, Func) const; * calls Func(const std::string &name, const Property &value); * for each parameter of a cell * - * void foreach_port_dir(const CellDataType &cell, Func); + * void foreach_port_dir(const CellDataType &cell, Func) const; * calls Func(const std::string &name, PortType dir); * for each port direction of a cell * - * void foreach_port_conn(const CellDataType &cell, Func); + * void foreach_port_conn(const CellDataType &cell, Func) const; * calls Func(const std::string &name, const BitVectorDataType &conn); * for each port connection of a cell * - * const BitVectorDataType &get_net_bits(const NetnameDataType &net); + * const BitVectorDataType &get_net_bits(const NetnameDataType &net) const; * gets the BitVector corresponding to the bits entry of a netname field * - * int get_vector_length(const BitVectorDataType &bits); + * int get_vector_length(const BitVectorDataType &bits) const; * gets the length of a BitVector * - * bool is_vector_bit_constant(const BitVectorDataType &bits, int i); + * bool is_vector_bit_constant(const BitVectorDataType &bits, int i) const; * returns true if bit of bits is constant * - * char get_vector_bit_constval(const BitVectorDataType &bits, int i); + * char get_vector_bit_constval(const BitVectorDataType &bits, int i) const; * returns a char [01xz] corresponding to the constant value of bit * - * int get_vector_bit_signal(const BitVectorDataType &bits, int i); + * int get_vector_bit_signal(const BitVectorDataType &bits, int i) const; * returns the signal number of vector bit * */ @@ -121,12 +121,15 @@ template struct GenericFrontend GenericFrontend(Context *ctx, const FrontendType &impl) : ctx(ctx), impl(impl) {} void operator()() { + // Find which module is top find_top_module(); HierModuleState m; m.is_toplevel = true; m.prefix = ""; + // Do the actual import, starting from the top level module import_module(m, mod_refs.at(top)); } + Context *ctx; const FrontendType &impl; using mod_dat_t = typename FrontendType::ModuleDataType; @@ -146,7 +149,7 @@ template struct GenericFrontend impl.foreach_module([&](const std::string &name, const mod_dat_t &mod) { IdString mod_id = ctx->id(name); auto &mi = mods[mod_id]; - mod_refs[mod_id] = mod; + mod_refs.emplace(mod_id, mod); impl.foreach_attr(mod, [&](const std::string &name, const Property &value) { if (name == "top") mi.is_top = (value.intval != 0); @@ -249,7 +252,7 @@ template struct GenericFrontend import_port_connections(m, data); } else { // Just create a list of ports for netname resolution - impl.foreach_port(m, + impl.foreach_port(data, [&](const std::string &name, const mod_port_dat_t &) { m.port_to_bus[ctx->id(name)]; }); } import_module_netnames(m, data); @@ -293,7 +296,7 @@ template struct GenericFrontend // Use the rule above to find the preferred name for a net name = m.net_names.at(idx).at(0); for (size_t j = 1; j < m.net_names.at(idx).size(); j++) - if (prefer_netlabel(m.net_names.at(idx).at(j), name)) + if (prefer_netlabel(m, m.net_names.at(idx).at(j), name)) name = m.net_names.at(idx).at(j); } else { name = "$frontend$" + std::to_string(idx); @@ -318,6 +321,7 @@ template struct GenericFrontend net->aliases.push_back(net->name); ctx->net_aliases[net->name] = net->name; } + return net; } // Get the name of a vector bit given basename; settings and index @@ -351,7 +355,7 @@ template struct GenericFrontend std::string bit_name = get_bit_name(basename, i, width, offset, upto); - int net_bit = impl.get_vecotr_bit_signal(bits, i); + int net_bit = impl.get_vector_bit_signal(bits, i); int mapped_bit = m.net_by_idx(net_bit); if (mapped_bit == -1) { // Net doesn't exist yet. Add the name here to the list of candidate names so we have that for when @@ -372,18 +376,20 @@ template struct GenericFrontend }); } - void create_constant_net(HierModuleState &m, const std::string name_hint, char constval) + // Create a new constant net; given a hint for what the name should be and its value + NetInfo *create_constant_net(HierModuleState &m, const std::string &name_hint, char constval) { - IdString name = unique_name(m.base, name_hint); + IdString name = unique_name(m.prefix, name_hint, true); NetInfo *ni = ctx->createNet(name); add_constant_driver(m, ni, constval); + return ni; } // Import a leaf cell - (white|black)box void import_leaf_cell(HierModuleState &m, const std::string &name, const cell_dat_t &cd) { - IdString inst_name = unique_name(m.base, name, false); - CellInfo *ci = ctx->createCell(inst_name, ctx->id(get_cell_type(cd))); + IdString inst_name = unique_name(m.prefix, name, false); + CellInfo *ci = ctx->createCell(inst_name, ctx->id(impl.get_cell_type(cd))); // Import port directions std::unordered_map port_dirs; impl.foreach_port_dir(cd, [&](const std::string &port, PortType dir) { port_dirs[ctx->id(port)] = dir; }); @@ -464,8 +470,8 @@ template struct GenericFrontend // Import the cells section of a module void import_module_cells(HierModuleState &m, const mod_dat_t &data) { - m.foreach_cell(data, [&](const std::string &cellname, const cell_dat_t &cd) { - IdString type = ctx->id(get_cell_type(cd)); + impl.foreach_cell(data, [&](const std::string &cellname, const cell_dat_t &cd) { + IdString type = ctx->id(impl.get_cell_type(cd)); if (mods.count(type) && !mods.at(type).is_box()) { // Module type is known; and not boxed. Import as a submodule by flattening hierarchy import_submodule_cell(m, cellname, cd); @@ -542,7 +548,7 @@ template struct GenericFrontend // Import ports of the top level module void import_toplevel_ports(HierModuleState &m, const mod_dat_t &data) { - m.foreach_port(data, [&](const std::string &portname, const mod_port_dat_t &pd) { + impl.foreach_port(data, [&](const std::string &portname, const mod_port_dat_t &pd) { const auto &port_bv = impl.get_port_bits(pd); int offset = impl.get_array_offset(pd); bool is_upto = impl.is_array_upto(pd); @@ -650,7 +656,7 @@ template struct GenericFrontend // Inputs cannot be driving a constant back to the parent if (dir == PORT_IN) log_error("Input port %s%s[%d] cannot be driving a constant '%c'.\n", m.prefix.c_str(), - port.c_str(), i, constval); + name.c_str(), i, constval); // Insert the constant driver add_constant_driver(m, conn_ni, constval); } else { @@ -673,9 +679,4 @@ template struct GenericFrontend }; } // namespace -template void run_frontend(Context *ctx, const FrontendType &impl) -{ - GenericFrontend(ctx, impl)(); -} - NEXTPNR_NAMESPACE_END \ No newline at end of file diff --git a/frontend/json_frontend.cc b/frontend/json_frontend.cc index 91c9f06a..37c1dabd 100644 --- a/frontend/json_frontend.cc +++ b/frontend/json_frontend.cc @@ -40,13 +40,13 @@ struct JsonFrontendImpl typedef const Json &NetnameDataType; typedef const Json::array &BitVectorDataType; - template void foreach_module(TFunc Func) + template void foreach_module(TFunc Func) const { for (const auto &mod : root.object_items()) Func(mod.first, mod.second); } - template void foreach_port(const ModuleDataType &mod, TFunc Func) + template void foreach_port(const ModuleDataType &mod, TFunc Func) const { const auto &ports = mod["ports"]; if (ports.is_null()) @@ -55,7 +55,7 @@ struct JsonFrontendImpl Func(port.first, port.second); } - template void foreach_cell(const ModuleDataType &mod, TFunc Func) + template void foreach_cell(const ModuleDataType &mod, TFunc Func) const { const auto &cells = mod["cells"]; if (cells.is_null()) @@ -64,7 +64,7 @@ struct JsonFrontendImpl Func(cell.first, cell.second); } - template void foreach_netname(const ModuleDataType &mod, TFunc Func) + template void foreach_netname(const ModuleDataType &mod, TFunc Func) const { const auto &netnames = mod["netnames"]; if (netnames.is_null()) @@ -73,7 +73,7 @@ struct JsonFrontendImpl Func(netname.first, netname.second); } - PortType lookup_portdir(const std::string &dir) + PortType lookup_portdir(const std::string &dir) const { if (dir == "input") return PORT_IN; @@ -85,25 +85,28 @@ struct JsonFrontendImpl NPNR_ASSERT_FALSE("invalid json port direction"); } - PortType get_port_dir(const ModulePortDataType &port) { return lookup_portdir(port["direction"].string_value()); } + PortType get_port_dir(const ModulePortDataType &port) const + { + return lookup_portdir(port["direction"].string_value()); + } - int get_array_offset(const Json &obj) + int get_array_offset(const Json &obj) const { auto offset = obj["offset"]; return offset.is_null() ? 0 : offset.int_value(); } - bool is_array_upto(const Json &obj) + bool is_array_upto(const Json &obj) const { auto upto = obj["upto"]; return upto.is_null() ? false : bool(upto.int_value()); } - const BitVectorDataType &get_port_bits(const ModulePortDataType &port) { return port["bits"].array_items(); } + const BitVectorDataType &get_port_bits(const ModulePortDataType &port) const { return port["bits"].array_items(); } - const std::string &get_cell_type(const CellDataType &cell) { return cell["type"].string_value(); } + const std::string &get_cell_type(const CellDataType &cell) const { return cell["type"].string_value(); } - Property parse_property(const Json &val) + Property parse_property(const Json &val) const { if (val.is_number()) return Property(val.int_value(), 32); @@ -111,7 +114,7 @@ struct JsonFrontendImpl return Property::from_string(val.string_value()); } - template void foreach_attr(const Json &obj, TFunc Func) + template void foreach_attr(const Json &obj, TFunc Func) const { const auto &attrs = obj["attributes"]; if (attrs.is_null()) @@ -121,7 +124,7 @@ struct JsonFrontendImpl } } - template void foreach_param(const Json &obj, TFunc Func) + template void foreach_param(const Json &obj, TFunc Func) const { const auto ¶ms = obj["parameters"]; if (params.is_null()) @@ -131,39 +134,36 @@ struct JsonFrontendImpl } } - template void foreach_port_dir(const CellDataType &cell, TFunc Func) + template void foreach_port_dir(const CellDataType &cell, TFunc Func) const { for (const auto &pdir : cell["port_directions"].object_items()) Func(pdir.first, lookup_portdir(pdir.second.string_value())); } - template void foreach_port_conn(const CellDataType &cell, TFunc Func) + template void foreach_port_conn(const CellDataType &cell, TFunc Func) const { for (const auto &pconn : cell["connections"].object_items()) - Func(pconn.first, pconn.second); + Func(pconn.first, pconn.second.array_items()); } - template const BitVectorDataType &get_net_bits(const NetnameDataType &net) - { - return net["bits"].array_items(); - } + const BitVectorDataType &get_net_bits(const NetnameDataType &net) const { return net["bits"].array_items(); } - int get_vector_length(const BitVectorDataType &bits) { return int(bits.size()); } + int get_vector_length(const BitVectorDataType &bits) const { return int(bits.size()); } - bool is_vector_bit_constant(const BitVectorDataType &bits, int i) + bool is_vector_bit_constant(const BitVectorDataType &bits, int i) const { NPNR_ASSERT(i < int(bits.size())); return bits[i].is_string(); } - char get_vector_bit_constval(const BitVectorDataType &bits, int i) + char get_vector_bit_constval(const BitVectorDataType &bits, int i) const { auto s = bits.at(i).string_value(); NPNR_ASSERT(s.size() == 1); return s.at(0); } - int get_vector_bit_signal(const BitVectorDataType &bits, int i) + int get_vector_bit_signal(const BitVectorDataType &bits, int i) const { NPNR_ASSERT(bits.at(i).is_number()); return bits.at(i).int_value(); @@ -182,7 +182,7 @@ bool parse_json(std::istream &in, const std::string &filename, Context *ctx) if (root.is_null()) log_error("Failed to parse JSON file '%s': %s.\n", filename.c_str(), error.c_str()); } - run_frontend(ctx, JsonFrontendImpl(root)); + GenericFrontend(ctx, JsonFrontendImpl(root))(); return true; } From 9e6770af9086326b3d7c0cb29e8b144fb3578369 Mon Sep 17 00:00:00 2001 From: David Shah Date: Fri, 15 Nov 2019 17:59:42 +0000 Subject: [PATCH 69/81] command: Use new frontend experimentally Signed-off-by: David Shah --- common/command.cc | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/common/command.cc b/common/command.cc index 13cd3498..c75f91ef 100644 --- a/common/command.cc +++ b/common/command.cc @@ -35,7 +35,7 @@ #include #include "command.h" #include "design_utils.h" -#include "frontend_base.h" +#include "json_frontend.h" #include "jsonparse.h" #include "jsonwrite.h" #include "log.h" @@ -266,9 +266,13 @@ int CommandHandler::executeMain(std::unique_ptr ctx) if (vm.count("json")) { std::string filename = vm["json"].as(); std::ifstream f(filename); +#ifdef LEGACY_FRONTEND if (!parse_json_file(f, filename, w.getContext())) log_error("Loading design failed.\n"); - +#else + if (!parse_json(f, filename, w.getContext())) + log_error("Loading design failed.\n"); +#endif customAfterLoad(w.getContext()); w.notifyChangeContext(); w.updateActions(); @@ -285,8 +289,13 @@ int CommandHandler::executeMain(std::unique_ptr ctx) if (vm.count("json")) { std::string filename = vm["json"].as(); std::ifstream f(filename); +#ifdef LEGACY_FRONTEND if (!parse_json_file(f, filename, ctx.get())) log_error("Loading design failed.\n"); +#else + if (!parse_json(f, filename, ctx.get())) + log_error("Loading design failed.\n"); +#endif customAfterLoad(ctx.get()); } From 3e21f894f4cbf843fbf3c9d1603886e63f2a8d5b Mon Sep 17 00:00:00 2001 From: David Shah Date: Fri, 15 Nov 2019 18:04:02 +0000 Subject: [PATCH 70/81] frontend: Improved error handling and fixes Signed-off-by: David Shah --- frontend/frontend_base.h | 9 ++++++++- frontend/json_frontend.cc | 4 ++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/frontend/frontend_base.h b/frontend/frontend_base.h index 3484b5b4..40d03863 100644 --- a/frontend/frontend_base.h +++ b/frontend/frontend_base.h @@ -104,6 +104,7 @@ #include "design_utils.h" #include "log.h" #include "nextpnr.h" +#include "util.h" NEXTPNR_NAMESPACE_BEGIN namespace { @@ -193,8 +194,14 @@ template struct GenericFrontend for (auto &mod : mods) for (auto &c : mod.second.instantiated_celltypes) candidate_top.erase(c); - if (candidate_top.size() != 1) + if (candidate_top.size() != 1) { + if (candidate_top.size() == 0) + log_info("No candidate top level modules.\n"); + else + for (auto ctp : sorted(candidate_top)) + log_info("Candidate top module: '%s'\n", ctx->nameOf(ctp)); log_error("Failed to autodetect top module, please specify using --top.\n"); + } top = *(candidate_top.begin()); } diff --git a/frontend/json_frontend.cc b/frontend/json_frontend.cc index 37c1dabd..2eb0a39b 100644 --- a/frontend/json_frontend.cc +++ b/frontend/json_frontend.cc @@ -181,6 +181,10 @@ bool parse_json(std::istream &in, const std::string &filename, Context *ctx) root = Json::parse(json_str, error, JsonParse::COMMENTS); if (root.is_null()) log_error("Failed to parse JSON file '%s': %s.\n", filename.c_str(), error.c_str()); + root = root["modules"]; + if (root.is_null()) + log_error("JSON file '%s' doesn't look like a netlist (doesn't contain \"modules\" key)\n", + filename.c_str()); } GenericFrontend(ctx, JsonFrontendImpl(root))(); return true; From c9a0033c5c7d5bfcf75df3afb0f877e61a1d0df5 Mon Sep 17 00:00:00 2001 From: David Shah Date: Fri, 15 Nov 2019 18:08:27 +0000 Subject: [PATCH 71/81] frontend/base: Fix lookup of nets by module index Signed-off-by: David Shah --- frontend/frontend_base.h | 58 +++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/frontend/frontend_base.h b/frontend/frontend_base.h index 40d03863..35558d2f 100644 --- a/frontend/frontend_base.h +++ b/frontend/frontend_base.h @@ -298,37 +298,39 @@ template struct GenericFrontend // Get a net by index in modulestate (not flatindex); creating it if it doesn't already exist NetInfo *create_or_get_net(HierModuleState &m, int idx) { - std::string name; - if (idx < int(m.net_names.size()) && !m.net_names.at(idx).empty()) { - // Use the rule above to find the preferred name for a net - name = m.net_names.at(idx).at(0); - for (size_t j = 1; j < m.net_names.at(idx).size(); j++) - if (prefer_netlabel(m, m.net_names.at(idx).at(j), name)) - name = m.net_names.at(idx).at(j); - } else { - name = "$frontend$" + std::to_string(idx); - } - NetInfo *net = ctx->createNet(unique_name(m.prefix, name, true)); - // Add to the flat index of nets - net->udata = int(net_flatindex.size()); - net_flatindex.push_back(net); - // Add to the module-level index of nets auto &midx = m.net_by_idx(idx); - // Check we don't try and create more than one net with the same index - NPNR_ASSERT(midx == -1); - midx = net->udata; - // Create aliases for all possible names - if (idx < int(m.net_names.size()) && !m.net_names.at(idx).empty()) { - for (const auto &name : m.net_names.at(idx)) { - IdString name_id = ctx->id(name); - net->aliases.push_back(name_id); - ctx->net_aliases[name_id] = net->name; - } + if (midx != -1) { + return net_flatindex.at(midx); } else { - net->aliases.push_back(net->name); - ctx->net_aliases[net->name] = net->name; + std::string name; + if (idx < int(m.net_names.size()) && !m.net_names.at(idx).empty()) { + // Use the rule above to find the preferred name for a net + name = m.net_names.at(idx).at(0); + for (size_t j = 1; j < m.net_names.at(idx).size(); j++) + if (prefer_netlabel(m, m.net_names.at(idx).at(j), name)) + name = m.net_names.at(idx).at(j); + } else { + name = "$frontend$" + std::to_string(idx); + } + NetInfo *net = ctx->createNet(unique_name(m.prefix, name, true)); + // Add to the flat index of nets + net->udata = int(net_flatindex.size()); + net_flatindex.push_back(net); + // Add to the module-level index of netsd + midx = net->udata; + // Create aliases for all possible names + if (idx < int(m.net_names.size()) && !m.net_names.at(idx).empty()) { + for (const auto &name : m.net_names.at(idx)) { + IdString name_id = ctx->id(name); + net->aliases.push_back(name_id); + ctx->net_aliases[name_id] = net->name; + } + } else { + net->aliases.push_back(net->name); + ctx->net_aliases[net->name] = net->name; + } + return net; } - return net; } // Get the name of a vector bit given basename; settings and index From 28279b18fe219c182a97efa992f022c8d243ae28 Mon Sep 17 00:00:00 2001 From: David Shah Date: Mon, 18 Nov 2019 15:07:19 +0000 Subject: [PATCH 72/81] frontend/generic: Fix regressions Signed-off-by: David Shah --- frontend/frontend_base.h | 46 +++++++++++++++++++++++---------------- frontend/json_frontend.cc | 2 +- frontend/json_frontend.h | 2 +- ice40/pack.cc | 4 +++- 4 files changed, 32 insertions(+), 22 deletions(-) diff --git a/frontend/frontend_base.h b/frontend/frontend_base.h index 35558d2f..d55e0329 100644 --- a/frontend/frontend_base.h +++ b/frontend/frontend_base.h @@ -538,6 +538,7 @@ template struct GenericFrontend auto drv = net->driver; if (drv.cell != nullptr) { disconnect_port(ctx, drv.cell, drv.port); + drv.cell->ports[drv.port].net = nullptr; connect_port(ctx, split_iobuf_i, drv.cell, drv.port); } connect_port(ctx, split_iobuf_i, iobuf, ctx->id("I")); @@ -557,25 +558,32 @@ template struct GenericFrontend // Import ports of the top level module void import_toplevel_ports(HierModuleState &m, const mod_dat_t &data) { - impl.foreach_port(data, [&](const std::string &portname, const mod_port_dat_t &pd) { - const auto &port_bv = impl.get_port_bits(pd); - int offset = impl.get_array_offset(pd); - bool is_upto = impl.is_array_upto(pd); - int width = impl.get_vector_length(port_bv); - PortType dir = impl.get_port_dir(pd); - for (int i = 0; i < width; i++) { - std::string pbit_name = get_bit_name(portname, i, width, offset, is_upto); - NetInfo *port_net = nullptr; - if (impl.is_vector_bit_constant(port_bv, i)) { - // Port bit is constant. Need to create a new constant net. - port_net = create_constant_net(m, pbit_name + "$const", impl.get_vector_bit_constval(port_bv, i)); - } else { - // Port bit is a signal. Need to create/get the associated net - port_net = create_or_get_net(m, impl.get_vector_bit_signal(port_bv, i)); + // For correct handling of inout ports driving other ports + // first import non-inouts then import inouts so that they bifurcate correctly + for (bool inout : {false, true}) { + impl.foreach_port(data, [&](const std::string &portname, const mod_port_dat_t &pd) { + const auto &port_bv = impl.get_port_bits(pd); + int offset = impl.get_array_offset(pd); + bool is_upto = impl.is_array_upto(pd); + int width = impl.get_vector_length(port_bv); + PortType dir = impl.get_port_dir(pd); + if ((dir == PORT_INOUT) != inout) + return; + for (int i = 0; i < width; i++) { + std::string pbit_name = get_bit_name(portname, i, width, offset, is_upto); + NetInfo *port_net = nullptr; + if (impl.is_vector_bit_constant(port_bv, i)) { + // Port bit is constant. Need to create a new constant net. + port_net = + create_constant_net(m, pbit_name + "$const", impl.get_vector_bit_constval(port_bv, i)); + } else { + // Port bit is a signal. Need to create/get the associated net + port_net = create_or_get_net(m, impl.get_vector_bit_signal(port_bv, i)); + } + create_iobuf(port_net, dir, pbit_name); } - create_iobuf(port_net, dir, pbit_name); - } - }); + }); + } } // Add a constant-driving VCC or GND cell to make a net constant @@ -688,4 +696,4 @@ template struct GenericFrontend }; } // namespace -NEXTPNR_NAMESPACE_END \ No newline at end of file +NEXTPNR_NAMESPACE_END diff --git a/frontend/json_frontend.cc b/frontend/json_frontend.cc index 2eb0a39b..0debd9f2 100644 --- a/frontend/json_frontend.cc +++ b/frontend/json_frontend.cc @@ -190,4 +190,4 @@ bool parse_json(std::istream &in, const std::string &filename, Context *ctx) return true; } -NEXTPNR_NAMESPACE_END \ No newline at end of file +NEXTPNR_NAMESPACE_END diff --git a/frontend/json_frontend.h b/frontend/json_frontend.h index 1e3deb8d..4d6c28f7 100644 --- a/frontend/json_frontend.h +++ b/frontend/json_frontend.h @@ -23,4 +23,4 @@ NEXTPNR_NAMESPACE_BEGIN bool parse_json(std::istream &in, const std::string &filename, Context *ctx); -NEXTPNR_NAMESPACE_END \ No newline at end of file +NEXTPNR_NAMESPACE_END diff --git a/ice40/pack.cc b/ice40/pack.cc index 90c6de31..cb430eb9 100644 --- a/ice40/pack.cc +++ b/ice40/pack.cc @@ -527,7 +527,9 @@ static void pack_io(Context *ctx) 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)) { NetInfo *net = ci->ports.at(ctx->id("PACKAGE_PIN")).net; - if ((net != nullptr) && (net->users.size() > 1)) + if ((net != nullptr) && ((net->users.size() > 2) || + (net->driver.cell != nullptr && + net->driver.cell->type == ctx->id("$nextpnr_obuf") && net->users.size() > 1))) log_error("PACKAGE_PIN of %s '%s' connected to more than a single top level IO.\n", ci->type.c_str(ctx), ci->name.c_str(ctx)); } From 14b18cb6fa8909cbe34bfb01c6ea43a2ccff9617 Mon Sep 17 00:00:00 2001 From: David Shah Date: Fri, 22 Nov 2019 17:29:45 +0000 Subject: [PATCH 73/81] frontend: Support for loading settings and nextpnr state Signed-off-by: David Shah --- frontend/frontend_base.h | 18 +++++++++++++++++- frontend/json_frontend.cc | 10 ++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/frontend/frontend_base.h b/frontend/frontend_base.h index d55e0329..c23e2196 100644 --- a/frontend/frontend_base.h +++ b/frontend/frontend_base.h @@ -76,6 +76,10 @@ * calls Func(const std::string &name, const Property &value); * for each parameter of a cell * + * void foreach_setting(const ModuleDataType &obj, Func) const; + * calls Func(const std::string &name, const Property &value); + * for each module-level setting + * * void foreach_port_dir(const CellDataType &cell, Func) const; * calls Func(const std::string &name, PortType dir); * for each port direction of a cell @@ -261,11 +265,23 @@ template struct GenericFrontend // Just create a list of ports for netname resolution impl.foreach_port(data, [&](const std::string &name, const mod_port_dat_t &) { m.port_to_bus[ctx->id(name)]; }); + // Import module-level attributes + impl.foreach_attr( + data, [&](const std::string &name, const Property &value) { ctx->attrs[ctx->id(name)] = value; }); + // Import settings + impl.foreach_setting(data, [&](const std::string &name, const Property &value) { + ctx->settings[ctx->id(name)] = value; + }); } import_module_netnames(m, data); import_module_cells(m, data); - if (m.is_toplevel) + if (m.is_toplevel) { import_toplevel_ports(m, data); + // Mark design as loaded through nextpnr + ctx->settings[ctx->id("synth")] = 1; + // Process nextpnr-specific attributes + ctx->attributesToArchInfo(); + } } // Multiple labels might refer to the same net. Resolve conflicts for the primary name thus: diff --git a/frontend/json_frontend.cc b/frontend/json_frontend.cc index 0debd9f2..d2e6248e 100644 --- a/frontend/json_frontend.cc +++ b/frontend/json_frontend.cc @@ -134,6 +134,16 @@ struct JsonFrontendImpl } } + template void foreach_setting(const Json &obj, TFunc Func) const + { + const auto &settings = obj["settings"]; + if (settings.is_null()) + return; + for (const auto &setting : settings.object_items()) { + Func(setting.first, parse_property(setting.second)); + } + } + template void foreach_port_dir(const CellDataType &cell, TFunc Func) const { for (const auto &pdir : cell["port_directions"].object_items()) From 035bfb0fe501fe5b840e83acaf781f2dffef8513 Mon Sep 17 00:00:00 2001 From: David Shah Date: Wed, 27 Nov 2019 12:11:18 +0000 Subject: [PATCH 74/81] json: Remove legacy frontend Signed-off-by: David Shah --- common/command.cc | 24 +- common/pybindings.cc | 4 +- gui/basewindow.cc | 1 - gui/ice40/mainwindow.cc | 1 - ice40/main.cc | 1 - json/jsonparse.cc | 1028 --------------------------------------- json/jsonparse.h | 34 -- 7 files changed, 3 insertions(+), 1090 deletions(-) delete mode 100644 json/jsonparse.cc delete mode 100644 json/jsonparse.h diff --git a/common/command.cc b/common/command.cc index c75f91ef..c2f02b27 100644 --- a/common/command.cc +++ b/common/command.cc @@ -36,7 +36,6 @@ #include "command.h" #include "design_utils.h" #include "json_frontend.h" -#include "jsonparse.h" #include "jsonwrite.h" #include "log.h" #include "timing.h" @@ -266,13 +265,8 @@ int CommandHandler::executeMain(std::unique_ptr ctx) if (vm.count("json")) { std::string filename = vm["json"].as(); std::ifstream f(filename); -#ifdef LEGACY_FRONTEND - if (!parse_json_file(f, filename, w.getContext())) - log_error("Loading design failed.\n"); -#else if (!parse_json(f, filename, w.getContext())) log_error("Loading design failed.\n"); -#endif customAfterLoad(w.getContext()); w.notifyChangeContext(); w.updateActions(); @@ -289,13 +283,8 @@ int CommandHandler::executeMain(std::unique_ptr ctx) if (vm.count("json")) { std::string filename = vm["json"].as(); std::ifstream f(filename); -#ifdef LEGACY_FRONTEND - if (!parse_json_file(f, filename, ctx.get())) - log_error("Loading design failed.\n"); -#else if (!parse_json(f, filename, ctx.get())) log_error("Loading design failed.\n"); -#endif customAfterLoad(ctx.get()); } @@ -392,12 +381,6 @@ int CommandHandler::exec() return 0; std::unordered_map values; - if (vm.count("json")) { - std::string filename = vm["json"].as(); - std::ifstream f(filename); - if (!load_json_settings(f, filename, values)) - log_error("Loading design failed.\n"); - } std::unique_ptr ctx = createContext(values); setupContext(ctx.get()); setupArchContext(ctx.get()); @@ -414,17 +397,12 @@ std::unique_ptr CommandHandler::load_json(std::string filename) { vm.clear(); std::unordered_map values; - { - std::ifstream f(filename); - if (!load_json_settings(f, filename, values)) - log_error("Loading design failed.\n"); - } std::unique_ptr ctx = createContext(values); setupContext(ctx.get()); setupArchContext(ctx.get()); { std::ifstream f(filename); - if (!parse_json_file(f, filename, ctx.get())) + if (!parse_json(f, filename, ctx.get())) log_error("Loading design failed.\n"); } customAfterLoad(ctx.get()); diff --git a/common/pybindings.cc b/common/pybindings.cc index 03979233..53830284 100644 --- a/common/pybindings.cc +++ b/common/pybindings.cc @@ -22,7 +22,7 @@ #include "pybindings.h" #include "arch_pybindings.h" -#include "jsonparse.h" +#include "json_frontend.h" #include "log.h" #include "nextpnr.h" @@ -53,7 +53,7 @@ void parse_json_shim(std::string filename, Context &d) std::ifstream inf(filename); if (!inf) throw std::runtime_error("failed to open file " + filename); - parse_json_file(inf, filename, &d); + parse_json(inf, filename, &d); } // Create a new Chip and load design from json file diff --git a/gui/basewindow.cc b/gui/basewindow.cc index 550a4b93..a470335d 100644 --- a/gui/basewindow.cc +++ b/gui/basewindow.cc @@ -28,7 +28,6 @@ #include #include "designwidget.h" #include "fpgaviewwidget.h" -#include "jsonparse.h" #include "jsonwrite.h" #include "log.h" #include "mainwindow.h" diff --git a/gui/ice40/mainwindow.cc b/gui/ice40/mainwindow.cc index ccff2117..dc8a3a23 100644 --- a/gui/ice40/mainwindow.cc +++ b/gui/ice40/mainwindow.cc @@ -27,7 +27,6 @@ #include #include "bitstream.h" #include "design_utils.h" -#include "jsonparse.h" #include "log.h" #include "pcf.h" diff --git a/ice40/main.cc b/ice40/main.cc index 5a5fa0c7..3b512a5a 100644 --- a/ice40/main.cc +++ b/ice40/main.cc @@ -24,7 +24,6 @@ #include "bitstream.h" #include "command.h" #include "design_utils.h" -#include "jsonparse.h" #include "log.h" #include "pcf.h" #include "timing.h" diff --git a/json/jsonparse.cc b/json/jsonparse.cc deleted file mode 100644 index fb712b28..00000000 --- a/json/jsonparse.cc +++ /dev/null @@ -1,1028 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 SymbioticEDA - * - * jsonparse.cc -- liberally copied from the yosys file of the same name by - * - * Copyright (C) 2018 Clifford Wolf - * - * 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 "jsonparse.h" -#include -#include -#include -#include -#include -#include -#include -#include "nextpnr.h" - -NEXTPNR_NAMESPACE_BEGIN - -extern bool check_all_nets_driven(Context *ctx); - -namespace JsonParser { - -const bool json_debug = false; - -typedef std::string string; - -template int GetSize(const T &obj) { return obj.size(); } - -struct JsonNode -{ - char type; // S=String, N=Number, A=Array, D=Dict - string data_string; - int data_number; - std::vector data_array; - std::map data_dict; - std::vector data_dict_keys; - - JsonNode(std::istream &f, int &lineno) - { - type = 0; - data_number = 0; - - while (1) { - int ch = f.get(); - - if (ch == EOF) - log_error("Unexpected EOF in JSON file.\n"); - - if (ch == '\n') - lineno++; - if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') - continue; - - if (ch == '\"') { - type = 'S'; - - while (1) { - ch = f.get(); - - if (ch == EOF) - log_error("Unexpected EOF in JSON string.\n"); - - if (ch == '\"') - break; - - if (ch == '\\') { - int ch = f.get(); - - if (ch == EOF) - log_error("Unexpected EOF in JSON string.\n"); - } - - data_string += ch; - } - - break; - } - - if (('0' <= ch && ch <= '9') || ('-' == ch)) { - type = 'N'; - if (ch == '-') - data_number = 0; - else - data_number = ch - '0'; - data_string += ch; - - while (1) { - ch = f.get(); - - if (ch == EOF) - break; - - if (ch == '.') - goto parse_real; - - if (ch < '0' || '9' < ch) { - f.unget(); - break; - } - - data_number = data_number * 10 + (ch - '0'); - data_string += ch; - } - - if (data_string[0] == '-') - data_number = -data_number; - data_string = ""; - break; - - parse_real: - type = 'S'; - data_number = 0; - data_string += ch; - - while (1) { - ch = f.get(); - - if (ch == EOF) - break; - - if (ch < '0' || '9' < ch) { - f.unget(); - break; - } - - data_string += ch; - } - - break; - } - - if (ch == '[') { - type = 'A'; - - while (1) { - ch = f.get(); - - if (ch == EOF) - log_error("Unexpected EOF in JSON file.\n"); - - if (ch == '\n') - lineno++; - if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == ',') - continue; - - if (ch == ']') - break; - - f.unget(); - data_array.push_back(new JsonNode(f, lineno)); - } - - break; - } - - if (ch == '{') { - type = 'D'; - - while (1) { - ch = f.get(); - - if (ch == EOF) - log_error("Unexpected EOF in JSON file.\n"); - - if (ch == '\n') - lineno++; - if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == ',') - continue; - - if (ch == '}') - break; - - f.unget(); - JsonNode key(f, lineno); - - while (1) { - ch = f.get(); - - if (ch == EOF) - log_error("Unexpected EOF in JSON file.\n"); - - if (ch == '\n') - lineno++; - if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == ':') - continue; - - f.unget(); - break; - } - - JsonNode *value = new JsonNode(f, lineno); - - if (key.type != 'S') - log_error("Unexpected non-string key in JSON dict, line %d.\n", lineno); - - data_dict[key.data_string] = value; - data_dict_keys.push_back(key.data_string); - } - - break; - } - - log_error("Unexpected character in JSON file, line %d: '%c'\n", lineno, ch); - } - } - - ~JsonNode() - { - for (auto it : data_array) - delete it; - for (auto &it : data_dict) - delete it.second; - } -}; - -inline Property json_parse_attr_param_value(JsonNode *node) -{ - Property value; - - if (node->type == 'S') { - value = Property::from_string(node->data_string); - } else if (node->type == 'N') { - value = Property(node->data_number, 32); - } else if (node->type == 'A') { - log_error("JSON attribute or parameter value is an array.\n"); - } else if (node->type == 'D') { - log_error("JSON attribute or parameter value is a dict.\n"); - } else { - log_abort(); - } - - return value; -} - -void ground_net(Context *ctx, NetInfo *net) -{ - std::unique_ptr cell = std::unique_ptr(new CellInfo); - PortInfo port_info; - PortRef port_ref; - - cell->name = ctx->id(net->name.str(ctx) + ".GND"); - cell->type = ctx->id("GND"); - - port_info.name = ctx->id(cell->name.str(ctx) + "[]"); - port_info.net = net; - port_info.type = PORT_OUT; - - port_ref.cell = cell.get(); - port_ref.port = port_info.name; - - net->driver = port_ref; - - cell->ports[port_info.name] = port_info; - - ctx->cells[cell->name] = std::move(cell); -} - -void vcc_net(Context *ctx, NetInfo *net) -{ - std::unique_ptr cell = std::unique_ptr(new CellInfo); - PortInfo port_info; - PortRef port_ref; - - cell->name = ctx->id(net->name.str(ctx) + ".VCC"); - cell->type = ctx->id("VCC"); - - port_info.name = ctx->id(cell->name.str(ctx) + "[]"); - port_info.net = net; - port_info.type = PORT_OUT; - - port_ref.cell = cell.get(); - port_ref.port = port_info.name; - - net->driver = port_ref; - - cell->ports[port_info.name] = port_info; - - ctx->cells[cell->name] = std::move(cell); -} - -// -// is_blackbox -// -// Checks the JsonNode for an attributes dictionary, with a "blackbox" entry. -// An item is deemed to be a blackbox if this entry exists and if its -// value is not zero. If the item is a black box, this routine will return -// true, false otherwise -bool is_blackbox(JsonNode *node) -{ - JsonNode *attr_node, *bbox_node = nullptr, *wbox_node = nullptr; - - if (node->data_dict.count("attributes") == 0) - return false; - attr_node = node->data_dict.at("attributes"); - if (attr_node == NULL) - return false; - if (attr_node->type != 'D') - return false; - if (GetSize(attr_node->data_dict) == 0) - return false; - if (attr_node->data_dict.count("blackbox")) - bbox_node = attr_node->data_dict.at("blackbox"); - if (attr_node->data_dict.count("whitebox")) - wbox_node = attr_node->data_dict.at("whitebox"); - if (bbox_node == NULL && wbox_node == NULL) - return false; - if (bbox_node && bbox_node->type != 'N') - log_error("JSON module blackbox attribute value is not a number\n"); - if (bbox_node && bbox_node->data_number == 0) - return false; - if (wbox_node && wbox_node->type != 'N') - log_error("JSON module whitebox attribute value is not a number\n"); - if (wbox_node && wbox_node->data_number == 0) - return false; - return true; -} - -void json_import_cell_params(Context *ctx, string &modname, CellInfo *cell, JsonNode *param_node, - std::unordered_map *dest, int param_id) -{ - // - JsonNode *param; - IdString pId; - // - param = param_node->data_dict.at(param_node->data_dict_keys[param_id]); - - pId = ctx->id(param_node->data_dict_keys[param_id]); - (*dest)[pId] = json_parse_attr_param_value(param); - - if (json_debug) - log_info(" Added parameter \'%s\'=%s to cell \'%s\' " - "of module \'%s\'\n", - pId.c_str(ctx), cell->params[pId].as_string().c_str(), cell->name.c_str(ctx), modname.c_str()); -} - -void json_import_net_attrib(Context *ctx, string &modname, NetInfo *net, JsonNode *param_node, - std::unordered_map *dest, int param_id) -{ - // - JsonNode *param; - IdString pId; - // - param = param_node->data_dict.at(param_node->data_dict_keys[param_id]); - - pId = ctx->id(param_node->data_dict_keys[param_id]); - (*dest)[pId] = json_parse_attr_param_value(param); - - if (json_debug) - log_info(" Added parameter \'%s\'=%s to net \'%s\' " - "of module \'%s\'\n", - pId.c_str(ctx), net->attrs[pId].as_string().c_str(), net->name.c_str(ctx), modname.c_str()); -} - -void json_import_top_attrib(Context *ctx, string &modname, JsonNode *param_node, - std::unordered_map *dest, int param_id) -{ - // - JsonNode *param; - IdString pId; - // - param = param_node->data_dict.at(param_node->data_dict_keys[param_id]); - - pId = ctx->id(param_node->data_dict_keys[param_id]); - (*dest)[pId] = json_parse_attr_param_value(param); - - if (json_debug) - log_info(" Added parameter \'%s\'=%s module \'%s\'\n", pId.c_str(ctx), (*dest)[pId].as_string().c_str(), - modname.c_str()); -} - -static int const_net_idx = 0; - -template -void json_import_ports(Context *ctx, const string &modname, const std::vector &netnames, - const string &obj_name, const string &port_name, JsonNode *dir_node, JsonNode *wire_group_node, - bool upto, int start_offset, F visitor) -{ - // Examine a port of a cell or the design. For every bit of the port, - // the connected net will be processed and `visitor` will be called - // with (PortType dir, std::string name, NetInfo *net) - assert(dir_node); - - if (json_debug) - log_info(" Examining port %s, node %s\n", port_name.c_str(), obj_name.c_str()); - - if (!wire_group_node) - log_error("JSON no connection match " - "for port_direction \'%s\' of node \'%s\' " - "in module \'%s\'\n", - port_name.c_str(), obj_name.c_str(), modname.c_str()); - - assert(wire_group_node); - - assert(dir_node->type == 'S'); - assert(wire_group_node->type == 'A'); - - PortInfo port_info; - - port_info.name = ctx->id(port_name); - if (dir_node->data_string.compare("input") == 0) - port_info.type = PORT_IN; - else if (dir_node->data_string.compare("output") == 0) - port_info.type = PORT_OUT; - else if (dir_node->data_string.compare("inout") == 0) - port_info.type = PORT_INOUT; - else - log_error("JSON unknown port direction \'%s\' in node \'%s\' " - "of module \'%s\'\n", - dir_node->data_string.c_str(), obj_name.c_str(), modname.c_str()); - // - // Find an update, or create a net to connect - // to this port. - // - NetInfo *this_net = nullptr; - bool is_bus; - - // - // If this port references a bus, then there will be multiple nets - // connected to it, all specified as part of an array. - // - is_bus = (wire_group_node->data_array.size() > 1); - - // Now loop through all of the connections to this port. - if (wire_group_node->data_array.size() == 0) { - // - // There is/are no connections to this port. - // - // Create the port, but leave the net NULL - - visitor(port_info.type, port_info.name.str(ctx), nullptr); - - if (json_debug) - log_info(" Port \'%s\' has no connection in \'%s\'\n", port_info.name.c_str(ctx), obj_name.c_str()); - - } else - for (int index = 0; index < int(wire_group_node->data_array.size()); index++) { - // - JsonNode *wire_node; - PortInfo this_port; - IdString net_id; - // - wire_node = wire_group_node->data_array[index]; - // - // Pick a name for this port - int ndx = index + start_offset; - if (upto) - ndx = start_offset + wire_group_node->data_array.size() - index - 1; - if (is_bus) - this_port.name = ctx->id(port_info.name.str(ctx) + "[" + std::to_string(ndx) + "]"); - else - this_port.name = port_info.name; - this_port.type = port_info.type; - - if (wire_node->type == 'N') { - int net_num; - - // A simple net, specified by a number - net_num = wire_node->data_number; - if (net_num < int(netnames.size())) - net_id = netnames.at(net_num); - else - net_id = ctx->id(std::to_string(net_num)); - if (ctx->nets.count(net_id) == 0) { - // The net doesn't exist in the design (yet) - // Create in now - - if (json_debug) - log_info(" Generating a new net, \'%d\'\n", net_num); - - std::unique_ptr net = std::unique_ptr(new NetInfo()); - net->name = net_id; - net->driver.cell = NULL; - net->driver.port = IdString(); - ctx->nets[net_id] = std::move(net); - - this_net = ctx->nets[net_id].get(); - } else { - // - // The net already exists within the design. - // We'll connect to it - // - this_net = ctx->nets[net_id].get(); - if (json_debug) - log_info(" Reusing net \'%s\', id \'%s\', " - "with driver \'%s\'\n", - this_net->name.c_str(ctx), net_id.c_str(ctx), - (this_net->driver.cell != NULL) ? this_net->driver.port.c_str(ctx) : "NULL"); - } - - } else if (wire_node->type == 'S') { - // Strings are only used to drive wires for the fixed - // values "0", "1", and "x". Handle those constant - // values here. - // - // Constants always get their own new net - std::unique_ptr net = std::unique_ptr(new NetInfo()); - net->name = ctx->id("$const_" + std::to_string(const_net_idx++)); - - if (wire_node->data_string.compare(string("0")) == 0) { - - if (json_debug) - log_info(" Generating a constant " - "zero net\n"); - ground_net(ctx, net.get()); - - } else if (wire_node->data_string.compare(string("1")) == 0) { - - if (json_debug) - log_info(" Generating a constant " - "one net\n"); - vcc_net(ctx, net.get()); - - } else if (wire_node->data_string.compare(string("x")) == 0) { - ground_net(ctx, net.get()); - } else - log_error(" Unknown fixed type wire node " - "value, \'%s\'\n", - wire_node->data_string.c_str()); - IdString n = net->name; - ctx->nets[net->name] = std::move(net); - this_net = ctx->nets[n].get(); - } - - if (json_debug) - log_info(" Inserting port \'%s\' into cell \'%s\'\n", this_port.name.c_str(ctx), obj_name.c_str()); - visitor(this_port.type, this_port.name.str(ctx), this_net); - } -} - -void json_import_cell(Context *ctx, string modname, const std::vector &netnames, JsonNode *cell_node, - string cell_name) -{ - JsonNode *cell_type, *param_node, *attr_node; - - cell_type = cell_node->data_dict.at("type"); - if (cell_type == NULL) - return; - - std::unique_ptr cell = std::unique_ptr(new CellInfo); - - cell->name = ctx->id(cell_name); - assert(cell_type->type == 'S'); - cell->type = ctx->id(cell_type->data_string); - // No BEL assignment here/yet - - if (json_debug) - log_info(" Processing %s $ %s\n", modname.c_str(), cell->name.c_str(ctx)); - - param_node = cell_node->data_dict.at("parameters"); - if (param_node->type != 'D') - log_error("JSON parameter list of \'%s\' is not a data dictionary\n", cell->name.c_str(ctx)); - - // - // Loop through all parameters, adding them into the - // design to annotate the cell - // - for (int paramid = 0; paramid < GetSize(param_node->data_dict_keys); paramid++) { - - json_import_cell_params(ctx, modname, cell.get(), param_node, &cell->params, paramid); - } - - attr_node = cell_node->data_dict.at("attributes"); - if (attr_node->type != 'D') - log_error("JSON attribute list of \'%s\' is not a data dictionary\n", cell->name.c_str(ctx)); - - // - // Loop through all attributes, adding them into the - // design to annotate the cell - // - for (int attrid = 0; attrid < GetSize(attr_node->data_dict_keys); attrid++) { - - json_import_cell_params(ctx, modname, cell.get(), attr_node, &cell->attrs, attrid); - } - - // - // Now connect the ports of this module. The ports are defined by - // both the port directions node as well as the connections node. - // Both should contain dictionaries having the same keys. - // - - JsonNode *pdir_node = NULL; - if (cell_node->data_dict.count("port_directions") > 0) { - - pdir_node = cell_node->data_dict.at("port_directions"); - if (pdir_node->type != 'D') - log_error("JSON port_directions node of \'%s\' " - "in module \'%s\' is not a " - "dictionary\n", - cell->name.c_str(ctx), modname.c_str()); - - } else if (cell_node->data_dict.count("ports") > 0) { - pdir_node = cell_node->data_dict.at("ports"); - if (pdir_node->type != 'D') - log_error("JSON ports node of \'%s\' " - "in module \'%s\' is not a " - "dictionary\n", - cell->name.c_str(ctx), modname.c_str()); - } - - JsonNode *connections = cell_node->data_dict.at("connections"); - if (connections->type != 'D') - log_error("JSON connections node of \'%s\' " - "in module \'%s\' is not a " - "dictionary\n", - cell->name.c_str(ctx), modname.c_str()); - - if (GetSize(pdir_node->data_dict_keys) != GetSize(connections->data_dict_keys)) - log_error("JSON number of connections doesnt " - "match number of ports in node \'%s\' " - "of module \'%s\'\n", - cell->name.c_str(ctx), modname.c_str()); - - // - // Loop through all of the ports of this logic element - // - for (int portid = 0; portid < GetSize(pdir_node->data_dict_keys); portid++) { - // - string port_name; - JsonNode *dir_node, *wire_group_node; - // - - port_name = pdir_node->data_dict_keys[portid]; - dir_node = pdir_node->data_dict.at(port_name); - wire_group_node = connections->data_dict.at(port_name); - - json_import_ports(ctx, modname, netnames, cell->name.str(ctx), port_name, dir_node, wire_group_node, false, 0, - [&cell, ctx](PortType type, const std::string &name, NetInfo *net) { - cell->ports[ctx->id(name)] = PortInfo{ctx->id(name), net, type}; - PortRef pr; - pr.cell = cell.get(); - pr.port = ctx->id(name); - if (net != nullptr) { - if (type == PORT_IN || type == PORT_INOUT) { - net->users.push_back(pr); - } else if (type == PORT_OUT) { - if (net->driver.cell != nullptr) - log_error("multiple drivers on net '%s' (%s.%s and %s.%s)\n", - net->name.c_str(ctx), net->driver.cell->name.c_str(ctx), - net->driver.port.c_str(ctx), pr.cell->name.c_str(ctx), - pr.port.c_str(ctx)); - net->driver = pr; - } - } - }); - } - - ctx->cells[cell->name] = std::move(cell); - // check_all_nets_driven(ctx); -} - -static void insert_iobuf(Context *ctx, NetInfo *net, PortType type, const string &name) -{ - // Instantiate a architecture-independent IO buffer connected to a given - // net, of a given type, and named after the IO port. - // - // During packing, this generic IO buffer will be converted to an - // architecure primitive. - // - if (ctx->settings.find(ctx->id("synth")) == ctx->settings.end()) { - std::unique_ptr iobuf = std::unique_ptr(new CellInfo()); - iobuf->name = ctx->id(name); - std::copy(net->attrs.begin(), net->attrs.end(), std::inserter(iobuf->attrs, iobuf->attrs.begin())); - if (type == PORT_IN) { - if (ctx->verbose) - log_info("processing input port %s\n", name.c_str()); - iobuf->type = ctx->id("$nextpnr_ibuf"); - iobuf->ports[ctx->id("O")] = PortInfo{ctx->id("O"), net, PORT_OUT}; - // Special case: input, etc, directly drives inout - if (net->driver.cell != nullptr) { - if (net->driver.cell->type != ctx->id("$nextpnr_iobuf")) - log_error("Top-level input '%s' also driven by %s.%s.\n", name.c_str(), - net->driver.cell->name.c_str(ctx), net->driver.port.c_str(ctx)); - net = net->driver.cell->ports.at(ctx->id("I")).net; - } - assert(net->driver.cell == nullptr); - net->driver.port = ctx->id("O"); - net->driver.cell = iobuf.get(); - } else if (type == PORT_OUT) { - if (ctx->verbose) - log_info("processing output port %s\n", name.c_str()); - iobuf->type = ctx->id("$nextpnr_obuf"); - iobuf->ports[ctx->id("I")] = PortInfo{ctx->id("I"), net, PORT_IN}; - PortRef ref; - ref.cell = iobuf.get(); - ref.port = ctx->id("I"); - net->users.push_back(ref); - } else if (type == PORT_INOUT) { - if (ctx->verbose) - log_info("processing inout port %s\n", name.c_str()); - iobuf->type = ctx->id("$nextpnr_iobuf"); - iobuf->ports[ctx->id("I")] = PortInfo{ctx->id("I"), nullptr, PORT_IN}; - - // Split the input and output nets for bidir ports - std::unique_ptr net2 = std::unique_ptr(new NetInfo()); - net2->name = ctx->id("$" + net->name.str(ctx) + "$iobuf_i"); - net2->driver = net->driver; - if (net->driver.cell != nullptr) { - net2->driver.cell->ports[net2->driver.port].net = net2.get(); - net->driver.cell = nullptr; - } - iobuf->ports[ctx->id("I")].net = net2.get(); - PortRef ref; - ref.cell = iobuf.get(); - ref.port = ctx->id("I"); - net2->users.push_back(ref); - ctx->nets[net2->name] = std::move(net2); - - iobuf->ports[ctx->id("O")] = PortInfo{ctx->id("O"), net, PORT_OUT}; - assert(net->driver.cell == nullptr); - net->driver.port = ctx->id("O"); - net->driver.cell = iobuf.get(); - } else { - assert(false); - } - ctx->cells[iobuf->name] = std::move(iobuf); - } - - PortInfo pinfo; - pinfo.name = ctx->id(name); - pinfo.net = net; - pinfo.type = type; - ctx->ports[pinfo.name] = pinfo; -} - -void json_import_toplevel_port(Context *ctx, const string &modname, const std::vector &netnames, - const string &portname, JsonNode *node) -{ - JsonNode *dir_node = node->data_dict.at("direction"); - JsonNode *nets_node = node->data_dict.at("bits"); - bool upto = false; - int start_offset = 0; - if (node->data_dict.count("upto") != 0) { - JsonNode *val = node->data_dict.at("upto"); - if (val->type == 'N') - upto = val->data_number != 0; - } - if (node->data_dict.count("offset") != 0) { - JsonNode *val = node->data_dict.at("offset"); - if (val->type == 'N') - start_offset = val->data_number; - } - json_import_ports( - ctx, modname, netnames, "Top Level IO", portname, dir_node, nets_node, upto, start_offset, - [ctx](PortType type, const std::string &name, NetInfo *net) { insert_iobuf(ctx, net, type, name); }); -} - -void json_import(Context *ctx, string modname, JsonNode *node) -{ - if (is_blackbox(node)) - return; - - log_info("Importing module %s\n", modname.c_str()); - ctx->attrs[ctx->id("module")] = modname; - JsonNode *attr_node = node->data_dict.at("attributes"); - for (int attrid = 0; attrid < GetSize(attr_node->data_dict_keys); attrid++) { - json_import_top_attrib(ctx, modname, attr_node, &ctx->attrs, attrid); - } - - JsonNode *ports_parent = nullptr; - if (node->data_dict.count("ports") > 0) - ports_parent = node->data_dict.at("ports"); - - // Multiple labels might refer to the same net. For now we resolve conflicts thus: - // - (toplevel) ports are always preferred - // - names with fewer $ are always prefered - // - between equal $ counts, fewer .s are prefered - // - ties are resolved alphabetically - auto prefer_netlabel = [ports_parent](const std::string &a, const std::string &b) { - if (ports_parent != nullptr) { - if (ports_parent->data_dict.count(a)) - return true; - if (ports_parent->data_dict.count(b)) - return false; - } - if (b.empty()) - return true; - long a_dollars = std::count(a.begin(), a.end(), '$'), b_dollars = std::count(b.begin(), b.end(), '$'); - if (a_dollars < b_dollars) - return true; - else if (a_dollars > b_dollars) - return false; - long a_dots = std::count(a.begin(), a.end(), '.'), b_dots = std::count(b.begin(), b.end(), '.'); - if (a_dots < b_dots) - return true; - else if (a_dots > b_dots) - return false; - return a < b; - }; - - // Import netnames - std::vector> netlabels; - if (node->data_dict.count("netnames")) { - JsonNode *cell_parent = node->data_dict.at("netnames"); - for (int nnid = 0; nnid < GetSize(cell_parent->data_dict_keys); nnid++) { - JsonNode *here; - - here = cell_parent->data_dict.at(cell_parent->data_dict_keys[nnid]); - std::string basename = cell_parent->data_dict_keys[nnid]; - bool upto = false; - int start_offset = 0; - if (here->data_dict.count("upto") != 0) { - JsonNode *val = here->data_dict.at("upto"); - if (val->type == 'N') - upto = val->data_number != 0; - } - if (here->data_dict.count("offset") != 0) { - JsonNode *val = here->data_dict.at("offset"); - if (val->type == 'N') - start_offset = val->data_number; - } - if (here->data_dict.count("bits")) { - JsonNode *bits = here->data_dict.at("bits"); - assert(bits->type == 'A'); - size_t num_bits = bits->data_array.size(); - for (size_t i = 0; i < num_bits; i++) { - int netid = bits->data_array.at(i)->data_number; - if (netid >= int(netlabels.size())) - netlabels.resize(netid + 1); - int ndx = i + start_offset; - if (upto) - ndx = start_offset + num_bits - i - 1; - std::string name = - basename + (num_bits == 1 ? "" : std::string("[") + std::to_string(ndx) + std::string("]")); - netlabels.at(netid).push_back(name); - } - } - } - } - std::vector netids; - for (size_t i = 0; i < netlabels.size(); i++) { - auto &labels = netlabels.at(i); - if (labels.empty()) { - // Backup for unnamed nets (not sure if these should actually happen) - netids.push_back(ctx->id("$nextpnr$unknown_netname$" + std::to_string(i))); - } else { - // Pick a primary name for the net according to a simple heuristic - std::string pref = labels.at(0); - for (size_t j = 1; j < labels.size(); j++) - if (prefer_netlabel(labels.at(j), pref)) - pref = labels.at(j); - netids.push_back(ctx->id(pref)); - } - } - if (node->data_dict.count("cells")) { - JsonNode *cell_parent = node->data_dict.at("cells"); - // - // - // Loop through all of the logic elements in a flattened design - // - // - for (int cellid = 0; cellid < GetSize(cell_parent->data_dict_keys); cellid++) { - JsonNode *here = cell_parent->data_dict.at(cell_parent->data_dict_keys[cellid]); - json_import_cell(ctx, modname, netids, here, cell_parent->data_dict_keys[cellid]); - } - } - - if (ports_parent != nullptr) { - // N.B. ports must be imported after cells for tristate behaviour - // to be correct - // Loop through all ports, first non-tristate then tristate to handle - // interconnected ports correctly - for (int portid = 0; portid < GetSize(ports_parent->data_dict_keys); portid++) { - JsonNode *here; - - here = ports_parent->data_dict.at(ports_parent->data_dict_keys[portid]); - JsonNode *dir_node = here->data_dict.at("direction"); - NPNR_ASSERT(dir_node->type == 'S'); - if (dir_node->data_string == "inout") - continue; - json_import_toplevel_port(ctx, modname, netids, ports_parent->data_dict_keys[portid], here); - } - for (int portid = 0; portid < GetSize(ports_parent->data_dict_keys); portid++) { - JsonNode *here; - - here = ports_parent->data_dict.at(ports_parent->data_dict_keys[portid]); - JsonNode *dir_node = here->data_dict.at("direction"); - NPNR_ASSERT(dir_node->type == 'S'); - if (dir_node->data_string != "inout") - continue; - json_import_toplevel_port(ctx, modname, netids, ports_parent->data_dict_keys[portid], here); - } - } - if (node->data_dict.count("netnames")) { - JsonNode *net_parent = node->data_dict.at("netnames"); - for (int nnid = 0; nnid < GetSize(net_parent->data_dict_keys); nnid++) { - JsonNode *here; - - here = net_parent->data_dict.at(net_parent->data_dict_keys[nnid]); - std::string basename = net_parent->data_dict_keys[nnid]; - if (here->data_dict.count("bits")) { - JsonNode *bits = here->data_dict.at("bits"); - assert(bits->type == 'A'); - size_t num_bits = bits->data_array.size(); - for (size_t i = 0; i < num_bits; i++) { - std::string name = - basename + (num_bits == 1 ? "" : std::string("[") + std::to_string(i) + std::string("]")); - IdString net_id = ctx->id(name); - if (here->data_dict.count("attributes") && ctx->nets.find(net_id) != ctx->nets.end()) { - NetInfo *this_net = ctx->nets[net_id].get(); - - JsonNode *attr_node = here->data_dict.at("attributes"); - if (attr_node->type != 'D') - log_error("JSON attribute list of \'%s\' is not a data dictionary\n", - this_net->name.c_str(ctx)); - - // - // Loop through all attributes, adding them into the - // design to annotate the cell - // - for (int attrid = 0; attrid < GetSize(attr_node->data_dict_keys); attrid++) { - json_import_net_attrib(ctx, modname, this_net, attr_node, &this_net->attrs, attrid); - } - } - } - } - } - } - // Import net aliases - for (size_t i = 0; i < netids.size(); i++) { - IdString netname = netids.at(i); - if (!ctx->nets.count(netname)) - continue; - for (auto &label : netlabels.at(i)) { - IdString labelid = ctx->id(label); - NPNR_ASSERT(!ctx->net_aliases.count(labelid)); - ctx->net_aliases[labelid] = netname; - } - } - check_all_nets_driven(ctx); - ctx->settings[ctx->id("synth")] = 1; -} -}; // End Namespace JsonParser - -bool parse_json_file(std::istream &f, std::string &filename, Context *ctx) -{ - try { - using namespace JsonParser; - - if (!f) - log_error("failed to open JSON file.\n"); - - int lineno = 1; - - JsonNode root(f, lineno); - - if (root.type != 'D') - log_error("JSON root node is not a dictionary.\n"); - - if (root.data_dict.count("modules") != 0) { - JsonNode *modules = root.data_dict.at("modules"); - - if (modules->type != 'D') - log_error("JSON modules node is not a dictionary.\n"); - - for (auto &it : modules->data_dict) - json_import(ctx, it.first, it.second); - } - - log_info("Checksum: 0x%08x\n", ctx->checksum()); - log_break(); - ctx->attributesToArchInfo(); - return true; - } catch (log_execution_error_exception) { - return false; - } -} - -bool load_json_settings(std::istream &f, std::string &filename, std::unordered_map &values) -{ - try { - using namespace JsonParser; - - if (!f) - log_error("failed to open JSON file.\n"); - - int lineno = 1; - - JsonNode root(f, lineno); - - if (root.type != 'D') - log_error("JSON root node is not a dictionary.\n"); - - if (root.data_dict.count("modules") != 0) { - JsonNode *modules = root.data_dict.at("modules"); - - if (modules->type != 'D') - log_error("JSON modules node is not a dictionary.\n"); - - for (auto &it : modules->data_dict) { - JsonNode *node = it.second; - if (is_blackbox(node)) - continue; - - if (node->data_dict.count("settings")) { - JsonNode *attr_node = node->data_dict.at("settings"); - for (int attrid = 0; attrid < GetSize(attr_node->data_dict_keys); attrid++) { - JsonNode *param = attr_node->data_dict.at(attr_node->data_dict_keys[attrid]); - std::string pId = attr_node->data_dict_keys[attrid]; - values[pId] = json_parse_attr_param_value(param); - } - } - } - } - - return true; - } catch (log_execution_error_exception) { - return false; - } -} - -NEXTPNR_NAMESPACE_END diff --git a/json/jsonparse.h b/json/jsonparse.h deleted file mode 100644 index 65e3f02e..00000000 --- a/json/jsonparse.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 SymbioticEDA - * - * 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. - * - */ - -#ifndef JSON_PARSER -#define JSON_PARSER - -#include -#include -#include "nextpnr.h" - -NEXTPNR_NAMESPACE_BEGIN - -extern bool parse_json_file(std::istream &, std::string &, Context *); -extern bool load_json_settings(std::istream &f, std::string &filename, - std::unordered_map &values); -NEXTPNR_NAMESPACE_END - -#endif From 6bf3c261fa3e13319430380096aca65042476ae3 Mon Sep 17 00:00:00 2001 From: David Shah Date: Thu, 28 Nov 2019 20:45:49 +0000 Subject: [PATCH 75/81] First pass at data structures for hierarchy Signed-off-by: David Shah --- common/nextpnr.h | 31 +++++++++++++++++++++++++++++-- docs/netlist.md | 2 ++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/common/nextpnr.h b/common/nextpnr.h index 3fce97a2..ceea2088 100644 --- a/common/nextpnr.h +++ b/common/nextpnr.h @@ -387,7 +387,7 @@ struct ClockConstraint; struct NetInfo : ArchNetInfo { - IdString name; + IdString name, hierpath; int32_t udata = 0; PortRef driver; @@ -423,7 +423,7 @@ struct PortInfo struct CellInfo : ArchCellInfo { - IdString name, type; + IdString name, type, hierpath; int32_t udata; std::unordered_map ports; @@ -527,6 +527,28 @@ struct TimingConstraint std::unordered_set to; }; +// Represents the contents of a non-leaf cell in a design +// with hierarchy + +struct HierachicalPort +{ + IdString name; + PortType dir; + std::vector nets; + int offset; + bool upto; +}; + +struct HierachicalCell +{ + IdString name, type, parent, fullpath; + // Name inside cell instance -> global name + std::unordered_map leaf_cells, nets; + std::unordered_map ports; + // Name inside cell instance -> global name + std::unordered_map hier_cells; +}; + inline bool operator==(const std::pair &a, const std::pair &b) { @@ -620,6 +642,11 @@ struct BaseCtx std::unordered_map> nets; std::unordered_map> cells; + // Hierarchical (non-leaf) cells by full path + std::unordered_map hierarchy; + // This is the root of the above structure + IdString top_module; + // Aliases for nets, which may have more than one name due to assignments and hierarchy std::unordered_map net_aliases; diff --git a/docs/netlist.md b/docs/netlist.md index 0f9a8969..8886e4f8 100644 --- a/docs/netlist.md +++ b/docs/netlist.md @@ -19,6 +19,7 @@ Other structures used by these basic structures include: `CellInfo` instances have the following fields: - `name` and `type` are `IdString`s containing the instance name, and type + - `hierpath` is name of the hierarchical cell containing the instance, for designs with hierarchy - `ports` is a map from port name `IdString` to `PortInfo` structures for each cell port - `bel` and `belStrength` contain the ID of the Bel the cell is placed onto; and placement strength of the cell; if placed. Placement/ripup should always be done by `Arch::bindBel` and `Arch::unbindBel` rather than by manipulating these fields. - `params` and `attrs` store parameters and attributes - from the input JSON or assigned in flows to add metadata - by mapping from parameter name `IdString` to `Property`. @@ -34,6 +35,7 @@ Other structures used by these basic structures include: `NetInfo` instances have the following fields: - `name` is the IdString name of the net - for nets with multiple names, one name is chosen according to a set of rules by the JSON frontend + - `hierpath` is name of the hierarchical cell containing the instance, for designs with hierarchy - `driver` refers to the source of the net using `PortRef`; `driver.cell == nullptr` means that the net is undriven. Nets must have zero or one driver only. The corresponding cell port must be an output and its `PortInfo::net` must refer back to this net. - `users` contains a list of `PortRef` references to sink ports on the net. Nets can have zero or more sinks. Each corresponding cell port must be an input or inout; and its `PortInfo::net` must refer back to this net. - `wires` is a map that stores the routing tree of a net, if the net is routed. From 9f6031cda13a290903785a1c469af02838309b39 Mon Sep 17 00:00:00 2001 From: David Shah Date: Fri, 29 Nov 2019 15:58:02 +0000 Subject: [PATCH 76/81] frontend_base: Import cell hierarchy Signed-off-by: David Shah --- frontend/frontend_base.h | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/frontend/frontend_base.h b/frontend/frontend_base.h index c23e2196..be764266 100644 --- a/frontend/frontend_base.h +++ b/frontend/frontend_base.h @@ -131,8 +131,10 @@ template struct GenericFrontend HierModuleState m; m.is_toplevel = true; m.prefix = ""; + m.path = top; + ctx->top_module = top; // Do the actual import, starting from the top level module - import_module(m, mod_refs.at(top)); + import_module(m, top.str(ctx), top.str(ctx), mod_refs.at(top)); } Context *ctx; @@ -239,6 +241,7 @@ template struct GenericFrontend { bool is_toplevel; std::string prefix; + IdString parent_path, path; // Map from index in module to "flat" index of nets std::vector index_to_net_flatindex; // Get a reference to index_to_net; resizing if @@ -255,8 +258,14 @@ template struct GenericFrontend std::vector> net_names; }; - void import_module(HierModuleState &m, const mod_dat_t &data) + void import_module(HierModuleState &m, const std::string &name, const std::string &type, const mod_dat_t &data) { + NPNR_ASSERT(!ctx->hierarchy.count(m.path)); + ctx->hierarchy[m.path].name = ctx->id(name); + ctx->hierarchy[m.path].type = ctx->id(type); + ctx->hierarchy[m.path].parent = m.parent_path; + ctx->hierarchy[m.path].fullpath = m.path; + std::vector index_to_net; if (!m.is_toplevel) { // Import port connections; for submodules only @@ -414,6 +423,7 @@ template struct GenericFrontend void import_leaf_cell(HierModuleState &m, const std::string &name, const cell_dat_t &cd) { IdString inst_name = unique_name(m.prefix, name, false); + ctx->hierarchy[m.path].leaf_cells[ctx->id(name)] = inst_name; CellInfo *ci = ctx->createCell(inst_name, ctx->id(impl.get_cell_type(cd))); // Import port directions std::unordered_map port_dirs; @@ -488,8 +498,11 @@ template struct GenericFrontend submod.prefix = m.prefix; submod.prefix += name; submod.prefix += '.'; + submod.parent_path = m.path; + submod.path = ctx->id(m.path.str(ctx) + "/" + name); // Do the submodule import - import_module(submod, mod_refs.at(ctx->id(impl.get_cell_type(cd)))); + auto type = impl.get_cell_type(cd); + import_module(submod, name, type, mod_refs.at(ctx->id(type))); } // Import the cells section of a module From b1000870244dbb1a73198e23a859825865938b4c Mon Sep 17 00:00:00 2001 From: David Shah Date: Fri, 29 Nov 2019 16:25:11 +0000 Subject: [PATCH 77/81] python: Add bindings for hierarchy structures Signed-off-by: David Shah --- common/arch_pybindings_shared.h | 4 ++++ common/nextpnr.h | 8 ++++---- common/pybindings.cc | 24 ++++++++++++++++++++---- ecp5/arch_pybindings.cc | 2 ++ frontend/frontend_base.h | 1 + generic/arch_pybindings.cc | 2 ++ ice40/arch_pybindings.cc | 2 ++ python/report_hierarchy.py | 10 ++++++++++ 8 files changed, 45 insertions(+), 8 deletions(-) create mode 100644 python/report_hierarchy.py diff --git a/common/arch_pybindings_shared.h b/common/arch_pybindings_shared.h index f681af92..89a61dad 100644 --- a/common/arch_pybindings_shared.h +++ b/common/arch_pybindings_shared.h @@ -5,6 +5,10 @@ readonly_wrapper>::def_wrap(ctx_cls, "nets"); readonly_wrapper>::def_wrap( ctx_cls, "net_aliases"); +readonly_wrapper>::def_wrap( + ctx_cls, "hierarchy"); +readwrite_wrapper, + conv_from_str>::def_wrap(ctx_cls, "top_module"); fn_wrapper_1a, conv_from_str>::def_wrap(ctx_cls, "getNetByAlias"); diff --git a/common/nextpnr.h b/common/nextpnr.h index ceea2088..7dfebd62 100644 --- a/common/nextpnr.h +++ b/common/nextpnr.h @@ -530,7 +530,7 @@ struct TimingConstraint // Represents the contents of a non-leaf cell in a design // with hierarchy -struct HierachicalPort +struct HierarchicalPort { IdString name; PortType dir; @@ -539,12 +539,12 @@ struct HierachicalPort bool upto; }; -struct HierachicalCell +struct HierarchicalCell { IdString name, type, parent, fullpath; // Name inside cell instance -> global name std::unordered_map leaf_cells, nets; - std::unordered_map ports; + std::unordered_map ports; // Name inside cell instance -> global name std::unordered_map hier_cells; }; @@ -643,7 +643,7 @@ struct BaseCtx std::unordered_map> cells; // Hierarchical (non-leaf) cells by full path - std::unordered_map hierarchy; + std::unordered_map hierarchy; // This is the root of the above structure IdString top_module; diff --git a/common/pybindings.cc b/common/pybindings.cc index 53830284..3b2a3744 100644 --- a/common/pybindings.cc +++ b/common/pybindings.cc @@ -131,7 +131,7 @@ BOOST_PYTHON_MODULE(MODULE_NAME) typedef std::unordered_map AttrMap; typedef std::unordered_map PortMap; - typedef std::unordered_map PinMap; + typedef std::unordered_map IdIdMap; typedef std::unordered_map> RegionMap; class_("BaseCtx", no_init); @@ -157,8 +157,8 @@ BOOST_PYTHON_MODULE(MODULE_NAME) conv_from_str>::def_wrap(ci_cls, "bel"); readwrite_wrapper, pass_through>::def_wrap(ci_cls, "belStrength"); - readonly_wrapper>::def_wrap(ci_cls, - "pins"); + readonly_wrapper>::def_wrap(ci_cls, + "pins"); fn_wrapper_1a_v>::def_wrap( ci_cls, "addInput"); @@ -230,9 +230,25 @@ BOOST_PYTHON_MODULE(MODULE_NAME) readonly_wrapper>::def_wrap(region_cls, "wires"); + auto hierarchy_cls = class_>("HierarchicalCell", no_init); + readwrite_wrapper, conv_from_str>::def_wrap(hierarchy_cls, "name"); + readwrite_wrapper, conv_from_str>::def_wrap(hierarchy_cls, "type"); + readwrite_wrapper, conv_from_str>::def_wrap(hierarchy_cls, "parent"); + readwrite_wrapper, conv_from_str>::def_wrap(hierarchy_cls, "fullpath"); + + readonly_wrapper>::def_wrap(hierarchy_cls, "leaf_cells"); + readonly_wrapper>::def_wrap(hierarchy_cls, "nets"); + readonly_wrapper>::def_wrap(hierarchy_cls, "hier_cells"); WRAP_MAP(AttrMap, conv_to_str, "AttrMap"); WRAP_MAP(PortMap, wrap_context, "PortMap"); - WRAP_MAP(PinMap, conv_to_str, "PinMap"); + WRAP_MAP(IdIdMap, conv_to_str, "IdIdMap"); WRAP_MAP(WireMap, wrap_context, "WireMap"); WRAP_MAP_UPTR(RegionMap, "RegionMap"); diff --git a/ecp5/arch_pybindings.cc b/ecp5/arch_pybindings.cc index da6d3e50..cd5e31c3 100644 --- a/ecp5/arch_pybindings.cc +++ b/ecp5/arch_pybindings.cc @@ -49,6 +49,7 @@ void arch_wrap_python() typedef std::unordered_map> CellMap; typedef std::unordered_map> NetMap; typedef std::unordered_map AliasMap; + typedef std::unordered_map HierarchyMap; auto belpin_cls = class_>("BelPin", no_init); readonly_wrapper>::def_wrap(belpin_cls, "bel"); @@ -64,6 +65,7 @@ void arch_wrap_python() WRAP_MAP_UPTR(CellMap, "IdCellMap"); WRAP_MAP_UPTR(NetMap, "IdNetMap"); + WRAP_MAP(HierarchyMap, wrap_context, "HierarchyMap"); } NEXTPNR_NAMESPACE_END diff --git a/frontend/frontend_base.h b/frontend/frontend_base.h index be764266..9e16cb24 100644 --- a/frontend/frontend_base.h +++ b/frontend/frontend_base.h @@ -500,6 +500,7 @@ template struct GenericFrontend submod.prefix += '.'; submod.parent_path = m.path; submod.path = ctx->id(m.path.str(ctx) + "/" + name); + ctx->hierarchy[m.path].hier_cells[ctx->id(name)] = submod.path; // Do the submodule import auto type = impl.get_cell_type(cd); import_module(submod, name, type, mod_refs.at(ctx->id(type))); diff --git a/generic/arch_pybindings.cc b/generic/arch_pybindings.cc index 8526e409..2600cac0 100644 --- a/generic/arch_pybindings.cc +++ b/generic/arch_pybindings.cc @@ -141,6 +141,7 @@ void arch_wrap_python() typedef std::unordered_map> CellMap; typedef std::unordered_map> NetMap; + typedef std::unordered_map HierarchyMap; readonly_wrapper>::def_wrap(ctx_cls, "cells"); @@ -231,6 +232,7 @@ void arch_wrap_python() WRAP_MAP_UPTR(CellMap, "IdCellMap"); WRAP_MAP_UPTR(NetMap, "IdNetMap"); + WRAP_MAP(HierarchyMap, wrap_context, "HierarchyMap"); WRAP_VECTOR(const std::vector, conv_to_str); } diff --git a/ice40/arch_pybindings.cc b/ice40/arch_pybindings.cc index cef7c58f..e2022091 100644 --- a/ice40/arch_pybindings.cc +++ b/ice40/arch_pybindings.cc @@ -59,6 +59,7 @@ void arch_wrap_python() typedef std::unordered_map> CellMap; typedef std::unordered_map> NetMap; + typedef std::unordered_map HierarchyMap; typedef std::unordered_map AliasMap; auto belpin_cls = class_>("BelPin", no_init); @@ -75,6 +76,7 @@ void arch_wrap_python() WRAP_MAP_UPTR(CellMap, "IdCellMap"); WRAP_MAP_UPTR(NetMap, "IdNetMap"); + WRAP_MAP(HierarchyMap, wrap_context, "HierarchyMap"); } NEXTPNR_NAMESPACE_END diff --git a/python/report_hierarchy.py b/python/report_hierarchy.py new file mode 100644 index 00000000..6d409a9b --- /dev/null +++ b/python/report_hierarchy.py @@ -0,0 +1,10 @@ +def visit(indent, data): + istr = " " * indent + print("{}{}: {}".format(istr, data.name, data.type)) + for lname, gname in data.leaf_cells: + print("{} {} -> {}".format(istr, lname, gname)) + for lname, gname in data.hier_cells: + visit(indent + 4, ctx.hierarchy[gname]) + +visit(0, ctx.hierarchy[ctx.top_module]) + From fe40094216d0007797c51ff8894127b37a4ff045 Mon Sep 17 00:00:00 2001 From: David Shah Date: Sun, 1 Dec 2019 14:03:23 +0000 Subject: [PATCH 78/81] Preserve hierarchy through packing Signed-off-by: David Shah --- common/nextpnr.cc | 73 ++++++++++++++++++++++++++++++++++++++++ common/nextpnr.h | 7 ++++ ecp5/cells.cc | 10 ++++++ frontend/frontend_base.h | 2 ++ ice40/cells.cc | 4 +++ ice40/pack.cc | 1 + 6 files changed, 97 insertions(+) diff --git a/common/nextpnr.cc b/common/nextpnr.cc index 933f124c..a6345b42 100644 --- a/common/nextpnr.cc +++ b/common/nextpnr.cc @@ -21,6 +21,7 @@ #include #include "design_utils.h" #include "log.h" +#include "util.h" NEXTPNR_NAMESPACE_BEGIN @@ -723,4 +724,76 @@ void BaseCtx::copyBelPorts(IdString cell, BelId bel) } } +namespace { +struct FixupHierarchyWorker +{ + FixupHierarchyWorker(Context *ctx) : ctx(ctx){}; + Context *ctx; + void run() + { + trim_hierarchy(ctx->top_module); + rebuild_hierarchy(); + }; + // Remove cells and nets that no longer exist in the netlist + std::vector todelete_cells, todelete_nets; + void trim_hierarchy(IdString path) + { + auto &h = ctx->hierarchy.at(path); + todelete_cells.clear(); + todelete_nets.clear(); + for (auto &lc : h.leaf_cells) { + if (!ctx->cells.count(lc.second)) + todelete_cells.push_back(lc.first); + } + for (auto &n : h.nets) + if (!ctx->nets.count(n.second)) + todelete_nets.push_back(n.first); + for (auto tdc : todelete_cells) { + h.leaf_cells_by_gname.erase(h.leaf_cells.at(tdc)); + h.leaf_cells.erase(tdc); + } + for (auto tdn : todelete_nets) { + h.nets_by_gname.erase(h.nets.at(tdn)); + h.nets.erase(tdn); + } + for (auto &sc : h.hier_cells) + trim_hierarchy(sc.second); + } + + IdString construct_local_name(HierarchicalCell &hc, IdString global_name, bool is_cell) + { + std::string gn = global_name.str(ctx); + auto dp = gn.find_last_of('.'); + if (dp != std::string::npos) + gn = gn.substr(dp + 1); + IdString name = ctx->id(gn); + // Make sure name is unique + int adder = 0; + while (is_cell ? hc.leaf_cells.count(name) : hc.nets.count(name)) { + ++adder; + name = ctx->id(gn + "$" + std::to_string(adder)); + } + return name; + } + + // Update hierarchy structure for nets and cells that have hiercell set + void rebuild_hierarchy() + { + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (ci->hierpath == IdString()) + ci->hierpath = ctx->top_module; + auto &hc = ctx->hierarchy.at(ci->hierpath); + if (hc.leaf_cells_by_gname.count(ci->name)) + continue; // already known + IdString local_name = construct_local_name(hc, ci->name, true); + hc.leaf_cells_by_gname[ci->name] = local_name; + hc.leaf_cells[local_name] = ci->name; + } + } +}; +} // namespace + +void Context::fixupHierarchy() { FixupHierarchyWorker(this).run(); } + NEXTPNR_NAMESPACE_END diff --git a/common/nextpnr.h b/common/nextpnr.h index 7dfebd62..61e04415 100644 --- a/common/nextpnr.h +++ b/common/nextpnr.h @@ -544,6 +544,9 @@ struct HierarchicalCell IdString name, type, parent, fullpath; // Name inside cell instance -> global name std::unordered_map leaf_cells, nets; + // Global name -> name inside cell instance + std::unordered_map leaf_cells_by_gname, nets_by_gname; + // Cell port to net std::unordered_map ports; // Name inside cell instance -> global name std::unordered_map hier_cells; @@ -835,6 +838,10 @@ struct Context : Arch, DeterministicRNG bool getActualRouteDelay(WireId src_wire, WireId dst_wire, delay_t *delay = nullptr, std::unordered_map *route = nullptr, bool useEstimate = true); + // -------------------------------------------------------------- + // call after changing hierpath or adding/removing nets and cells + void fixupHierarchy(); + // -------------------------------------------------------------- // provided by sdf.cc diff --git a/ecp5/cells.cc b/ecp5/cells.cc index b06350c7..c630c2c3 100644 --- a/ecp5/cells.cc +++ b/ecp5/cells.cc @@ -233,6 +233,8 @@ static void replace_port_safe(bool has_ff, CellInfo *ff, IdString ff_port, CellI void ff_to_slice(Context *ctx, CellInfo *ff, CellInfo *lc, int index, bool driven_by_lut) { + if (lc->hierpath == IdString()) + lc->hierpath = ff->hierpath; bool has_ff = lc->ports.at(ctx->id("Q0")).net != nullptr || lc->ports.at(ctx->id("Q1")).net != nullptr; std::string reg = "REG" + std::to_string(index); set_param_safe(has_ff, lc, ctx->id("SRMODE"), str_or_default(ff->params, ctx->id("SRMODE"), "LSR_OVER_CE")); @@ -271,6 +273,8 @@ void ff_to_slice(Context *ctx, CellInfo *ff, CellInfo *lc, int index, bool drive void lut_to_slice(Context *ctx, CellInfo *lut, CellInfo *lc, int index) { + if (lc->hierpath == IdString()) + lc->hierpath = lut->hierpath; lc->params[ctx->id("LUT" + std::to_string(index) + "_INITVAL")] = get_or_default(lut->params, ctx->id("INIT"), Property(0, 16)); replace_port(lut, ctx->id("A"), lc, ctx->id("A" + std::to_string(index))); @@ -282,6 +286,8 @@ void lut_to_slice(Context *ctx, CellInfo *lut, CellInfo *lc, int index) void ccu2c_to_slice(Context *ctx, CellInfo *ccu, CellInfo *lc) { + if (lc->hierpath == IdString()) + lc->hierpath = ccu->hierpath; lc->params[ctx->id("MODE")] = std::string("CCU2"); lc->params[ctx->id("LUT0_INITVAL")] = get_or_default(ccu->params, ctx->id("INIT0"), Property(0, 16)); lc->params[ctx->id("LUT1_INITVAL")] = get_or_default(ccu->params, ctx->id("INIT1"), Property(0, 16)); @@ -309,6 +315,8 @@ void ccu2c_to_slice(Context *ctx, CellInfo *ccu, CellInfo *lc) void dram_to_ramw(Context *ctx, CellInfo *ram, CellInfo *lc) { + if (lc->hierpath == IdString()) + lc->hierpath = ram->hierpath; lc->params[ctx->id("MODE")] = std::string("RAMW"); replace_port(ram, ctx->id("WAD[0]"), lc, ctx->id("D0")); replace_port(ram, ctx->id("WAD[1]"), lc, ctx->id("B0")); @@ -340,6 +348,8 @@ static unsigned get_dram_init(const Context *ctx, const CellInfo *ram, int bit) void dram_to_ram_slice(Context *ctx, CellInfo *ram, CellInfo *lc, CellInfo *ramw, int index) { + if (lc->hierpath == IdString()) + lc->hierpath = ram->hierpath; lc->params[ctx->id("MODE")] = std::string("DPRAM"); lc->params[ctx->id("WREMUX")] = str_or_default(ram->params, ctx->id("WREMUX"), "WRE"); lc->params[ctx->id("WCKMUX")] = str_or_default(ram->params, ctx->id("WCKMUX"), "WCK"); diff --git a/frontend/frontend_base.h b/frontend/frontend_base.h index 9e16cb24..45847e21 100644 --- a/frontend/frontend_base.h +++ b/frontend/frontend_base.h @@ -423,8 +423,10 @@ template struct GenericFrontend void import_leaf_cell(HierModuleState &m, const std::string &name, const cell_dat_t &cd) { IdString inst_name = unique_name(m.prefix, name, false); + ctx->hierarchy[m.path].leaf_cells_by_gname[inst_name] = ctx->id(name); ctx->hierarchy[m.path].leaf_cells[ctx->id(name)] = inst_name; CellInfo *ci = ctx->createCell(inst_name, ctx->id(impl.get_cell_type(cd))); + ci->hierpath = m.path; // Import port directions std::unordered_map port_dirs; impl.foreach_port_dir(cd, [&](const std::string &port, PortType dir) { port_dirs[ctx->id(port)] = dir; }); diff --git a/ice40/cells.cc b/ice40/cells.cc index 3def82bf..f1901c43 100644 --- a/ice40/cells.cc +++ b/ice40/cells.cc @@ -346,6 +346,8 @@ std::unique_ptr create_ice_cell(Context *ctx, IdString type, std::stri void lut_to_lc(const Context *ctx, CellInfo *lut, CellInfo *lc, bool no_dff) { + if (lc->hierpath == IdString()) + lc->hierpath = lut->hierpath; lc->params[ctx->id("LUT_INIT")] = lut->params[ctx->id("LUT_INIT")].extract(0, 16, Property::State::S0); replace_port(lut, ctx->id("I0"), lc, ctx->id("I0")); replace_port(lut, ctx->id("I1"), lc, ctx->id("I1")); @@ -359,6 +361,8 @@ void lut_to_lc(const Context *ctx, CellInfo *lut, CellInfo *lc, bool no_dff) void dff_to_lc(const Context *ctx, CellInfo *dff, CellInfo *lc, bool pass_thru_lut) { + if (lc->hierpath == IdString()) + lc->hierpath = dff->hierpath; lc->params[ctx->id("DFF_ENABLE")] = Property::State::S1; std::string config = dff->type.str(ctx).substr(6); auto citer = config.begin(); diff --git a/ice40/pack.cc b/ice40/pack.cc index cb430eb9..5b13e9ee 100644 --- a/ice40/pack.cc +++ b/ice40/pack.cc @@ -1487,6 +1487,7 @@ bool Arch::pack() promote_globals(ctx); ctx->assignArchInfo(); constrain_chains(ctx); + ctx->fixupHierarchy(); ctx->assignArchInfo(); ctx->settings[ctx->id("pack")] = 1; archInfoToAttributes(); From 0ea7f153a1f14ac6af079b5660afe1981e5f8b51 Mon Sep 17 00:00:00 2001 From: David Shah Date: Sun, 1 Dec 2019 14:18:33 +0000 Subject: [PATCH 79/81] Allow constraining non-leaf cells to regions Signed-off-by: David Shah --- common/nextpnr.cc | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/common/nextpnr.cc b/common/nextpnr.cc index a6345b42..1156490c 100644 --- a/common/nextpnr.cc +++ b/common/nextpnr.cc @@ -523,7 +523,16 @@ void BaseCtx::createRectangularRegion(IdString name, int x0, int y0, int x1, int void BaseCtx::addBelToRegion(IdString name, BelId bel) { region[name]->bels.insert(bel); } void BaseCtx::constrainCellToRegion(IdString cell, IdString region_name) { - cells[cell]->region = region[region_name].get(); + // Support hierarchical cells as well as leaf ones + if (hierarchy.count(cell)) { + auto &hc = hierarchy.at(cell); + for (auto &lc : hc.leaf_cells) + constrainCellToRegion(lc.second, region_name); + for (auto &hsc : hc.hier_cells) + constrainCellToRegion(hsc.second, region_name); + } + if (cells.count(cell)) + cells.at(cell)->region = region[region_name].get(); } DecalXY BaseCtx::constructDecalXY(DecalId decal, float x, float y) { From 5774b13984bb151909b90ee2c668bdfb08387a2b Mon Sep 17 00:00:00 2001 From: David Shah Date: Fri, 27 Dec 2019 10:44:20 +0000 Subject: [PATCH 80/81] Document hierarchy structures Signed-off-by: David Shah --- docs/netlist.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/docs/netlist.md b/docs/netlist.md index 8886e4f8..3953241e 100644 --- a/docs/netlist.md +++ b/docs/netlist.md @@ -72,4 +72,18 @@ The second is `ArchCellInfo` and `ArchNetInfo`. These are provided by architectu - `getNetinfoSourceWire` gets the physical wire `WireId` associated with the source of a net - `getNetinfoSinkWire` gets the physical wire `WireId` associated with a given sink (specified by `PortRef`) - `getNetinfoRouteDelay` gets the routing delay - actual if the net is fully routed, estimated otherwise - between the source and a given sink of a net - - `getNetByAlias` returns the pointer to a net given any of its aliases - this should be used in preference to a direct lookup in `nets` whenever a net name is provided by the user \ No newline at end of file + - `getNetByAlias` returns the pointer to a net given any of its aliases - this should be used in preference to a direct lookup in `nets` whenever a net name is provided by the user + +## Hierarchy + +As most place and route algorithms require a flattened netlist to work with (consider - each leaf cell instance must have its own bel), the primary netlist structures are flattened. However, some tasks such as floorplanning require an understanding of hierarchy. + +`HierarchicalCell` is the main data structure for storing hierarchy. This represents an instance of a hierarchical, rather than leaf cell (leaf cells are represented by a `CellInfo`). + + - `name` and `type` are the instance name and cell type + - `parent` is the hierarchical path of the parent cell, and `fullpath` is the hierarchical path of this cell + - `leaf_cells`, `nets` map from a name inside the hierarchical cell to a 'global' name in the flattened netlist (i.e. one that indexes into `ctx->{cells,nets}`) + - `leaf_cells_by_gname`, `nets_by_gname` are the inverse of the above maps; going from `{CellInfo,NetInfo}::name` to an instance name inside the cell + - `hier_cells` maps instance names of sub-hierarchical (non-leaf) cells to global names (indexing into `ctx->hierarchy`) + +To preserve hierarchy during passes such as packing, ensure that `hierpath` is set on new cells derived from existing ones, and call `fixupHierarchy()` at the end to rebuild `HierarchicalCell` structures. From 0d43aff2682d91817ea4a1fb5dff6e169ae9a659 Mon Sep 17 00:00:00 2001 From: David Shah Date: Fri, 27 Dec 2019 15:53:00 +0000 Subject: [PATCH 81/81] ecp5: Always promote IOLOGIC SCLK to global Fixes #374 Signed-off-by: David Shah --- ecp5/globals.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ecp5/globals.cc b/ecp5/globals.cc index da2ba8f0..c0f4b504 100644 --- a/ecp5/globals.cc +++ b/ecp5/globals.cc @@ -58,7 +58,7 @@ class Ecp5GlobalRouter if (user.cell->type == id_DCUA && (user.port == id_CH0_FF_RXI_CLK || user.port == id_CH1_FF_RXI_CLK || user.port == id_CH0_FF_TXI_CLK || user.port == id_CH1_FF_TXI_CLK)) return true; - if ((user.cell->type == id_IOLOGIC || user.cell->type == id_SIOLOGIC) && user.port == id_CLK) + if ((user.cell->type == id_IOLOGIC || user.cell->type == id_SIOLOGIC) && (user.port == id_CLK)) return true; return false; } @@ -74,6 +74,8 @@ class Ecp5GlobalRouter clockCount[ni->name]++; if (user.cell->type == id_DCUA) clockCount[ni->name] += 100; + if (user.cell->type == id_IOLOGIC || user.cell->type == id_SIOLOGIC) + clockCount[ni->name] += 10; } } // log_info("clkcount %s: %d\n", ni->name.c_str(ctx),clockCount[ni->name]);