aboutsummaryrefslogtreecommitdiffstats
path: root/common
diff options
context:
space:
mode:
Diffstat (limited to 'common')
-rw-r--r--common/arch_pybindings_shared.h4
-rw-r--r--common/command.cc31
-rw-r--r--common/design_utils.cc12
-rw-r--r--common/design_utils.h3
-rw-r--r--common/nextpnr.cc84
-rw-r--r--common/nextpnr.h45
-rw-r--r--common/placer_heap.cc100
-rw-r--r--common/pybindings.cc28
-rw-r--r--common/sdf.cc334
-rw-r--r--common/timing.cc3
10 files changed, 610 insertions, 34 deletions
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<Context, decltype(&Context::cells), &Context::cells, wrap_conte
readonly_wrapper<Context, decltype(&Context::nets), &Context::nets, wrap_context<NetMap &>>::def_wrap(ctx_cls, "nets");
readonly_wrapper<Context, decltype(&Context::net_aliases), &Context::net_aliases, wrap_context<AliasMap &>>::def_wrap(
ctx_cls, "net_aliases");
+readonly_wrapper<Context, decltype(&Context::hierarchy), &Context::hierarchy, wrap_context<HierarchyMap &>>::def_wrap(
+ ctx_cls, "hierarchy");
+readwrite_wrapper<Context, decltype(&Context::top_module), &Context::top_module, conv_to_str<IdString>,
+ conv_from_str<IdString>>::def_wrap(ctx_cls, "top_module");
fn_wrapper_1a<Context, decltype(&Context::getNetByAlias), &Context::getNetByAlias, deref_and_wrap<NetInfo>,
conv_from_str<IdString>>::def_wrap(ctx_cls, "getNetByAlias");
diff --git a/common/command.cc b/common/command.cc
index ad5b6c54..c2f02b27 100644
--- a/common/command.cc
+++ b/common/command.cc
@@ -35,7 +35,7 @@
#include <iostream>
#include "command.h"
#include "design_utils.h"
-#include "jsonparse.h"
+#include "json_frontend.h"
#include "jsonwrite.h"
#include "log.h"
#include "timing.h"
@@ -149,6 +149,9 @@ po::options_description CommandHandler::getGeneralOptions()
general.add_options()("freq", po::value<double>(), "set target frequency for design in MHz");
general.add_options()("timing-allow-fail", "allow timing to fail in design");
general.add_options()("no-tmdriv", "disable timing-driven placement");
+ general.add_options()("sdf", po::value<std::string>(), "SDF delay back-annotation file to write");
+ general.add_options()("sdf-cvc", "enable tweaks for SDF file compatibility with the CVC simulator");
+
return general;
}
@@ -262,9 +265,8 @@ int CommandHandler::executeMain(std::unique_ptr<Context> ctx)
if (vm.count("json")) {
std::string filename = vm["json"].as<std::string>();
std::ifstream f(filename);
- if (!parse_json_file(f, filename, w.getContext()))
+ if (!parse_json(f, filename, w.getContext()))
log_error("Loading design failed.\n");
-
customAfterLoad(w.getContext());
w.notifyChangeContext();
w.updateActions();
@@ -281,7 +283,7 @@ int CommandHandler::executeMain(std::unique_ptr<Context> ctx)
if (vm.count("json")) {
std::string filename = vm["json"].as<std::string>();
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());
@@ -336,6 +338,14 @@ int CommandHandler::executeMain(std::unique_ptr<Context> ctx)
log_error("Saving design failed.\n");
}
+ if (vm.count("sdf")) {
+ std::string filename = vm["sdf"].as<std::string>();
+ std::ofstream f(filename);
+ if (!f)
+ log_error("Failed to open SDF file '%s' for writing.\n", filename.c_str());
+ ctx->writeSDF(f, vm.count("sdf-cvc"));
+ }
+
#ifndef NO_PYTHON
deinit_python();
#endif
@@ -371,12 +381,6 @@ int CommandHandler::exec()
return 0;
std::unordered_map<std::string, Property> values;
- if (vm.count("json")) {
- std::string filename = vm["json"].as<std::string>();
- std::ifstream f(filename);
- if (!load_json_settings(f, filename, values))
- log_error("Loading design failed.\n");
- }
std::unique_ptr<Context> ctx = createContext(values);
setupContext(ctx.get());
setupArchContext(ctx.get());
@@ -393,17 +397,12 @@ std::unique_ptr<Context> CommandHandler::load_json(std::string filename)
{
vm.clear();
std::unordered_map<std::string, Property> values;
- {
- std::ifstream f(filename);
- if (!load_json_settings(f, filename, values))
- log_error("Loading design failed.\n");
- }
std::unique_ptr<Context> 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/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/common/nextpnr.cc b/common/nextpnr.cc
index 933f124c..1156490c 100644
--- a/common/nextpnr.cc
+++ b/common/nextpnr.cc
@@ -21,6 +21,7 @@
#include <boost/algorithm/string.hpp>
#include "design_utils.h"
#include "log.h"
+#include "util.h"
NEXTPNR_NAMESPACE_BEGIN
@@ -522,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)
{
@@ -723,4 +733,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<IdString> 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 bae828f6..61e04415 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;
@@ -397,6 +397,8 @@ struct NetInfo : ArchNetInfo
// wire -> uphill_pip
std::unordered_map<WireId, PipMap> wires;
+ std::vector<IdString> aliases; // entries in net_aliases that point to this net
+
std::unique_ptr<ClockConstraint> clkconstr;
TimingConstrObjectId tmg_id;
@@ -421,7 +423,7 @@ struct PortInfo
struct CellInfo : ArchCellInfo
{
- IdString name, type;
+ IdString name, type, hierpath;
int32_t udata;
std::unordered_map<IdString, PortInfo> ports;
@@ -525,6 +527,31 @@ struct TimingConstraint
std::unordered_set<TimingConstrObjectId> to;
};
+// Represents the contents of a non-leaf cell in a design
+// with hierarchy
+
+struct HierarchicalPort
+{
+ IdString name;
+ PortType dir;
+ std::vector<IdString> nets;
+ int offset;
+ bool upto;
+};
+
+struct HierarchicalCell
+{
+ IdString name, type, parent, fullpath;
+ // Name inside cell instance -> global name
+ std::unordered_map<IdString, IdString> leaf_cells, nets;
+ // Global name -> name inside cell instance
+ std::unordered_map<IdString, IdString> leaf_cells_by_gname, nets_by_gname;
+ // Cell port to net
+ std::unordered_map<IdString, HierarchicalPort> ports;
+ // Name inside cell instance -> global name
+ std::unordered_map<IdString, IdString> hier_cells;
+};
+
inline bool operator==(const std::pair<const TimingConstrObjectId, TimingConstraint *> &a,
const std::pair<TimingConstrObjectId, TimingConstraint *> &b)
{
@@ -618,6 +645,11 @@ struct BaseCtx
std::unordered_map<IdString, std::unique_ptr<NetInfo>> nets;
std::unordered_map<IdString, std::unique_ptr<CellInfo>> cells;
+ // Hierarchical (non-leaf) cells by full path
+ std::unordered_map<IdString, HierarchicalCell> 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<IdString, IdString> net_aliases;
@@ -807,6 +839,15 @@ struct Context : Arch, DeterministicRNG
std::unordered_map<WireId, PipId> *route = nullptr, bool useEstimate = true);
// --------------------------------------------------------------
+ // call after changing hierpath or adding/removing nets and cells
+ void fixupHierarchy();
+
+ // --------------------------------------------------------------
+
+ // provided by sdf.cc
+ void writeSDF(std::ostream &out, bool cvc_mode = false) const;
+
+ // --------------------------------------------------------------
uint32_t checksum() const;
diff --git a/common/placer_heap.cc b/common/placer_heap.cc
index e9fc2fb2..01e50123 100644
--- a/common/placer_heap.cc
+++ b/common/placer_heap.cc
@@ -308,6 +308,14 @@ class HeAPPlacer
std::vector<std::vector<int>> nearest_row_with_bel;
std::vector<std::vector<int>> nearest_col_with_bel;
+ struct BoundingBox
+ {
+ // Actual bounding box
+ int x0 = 0, x1 = 0, y0 = 0, y1 = 0;
+ };
+
+ std::unordered_map<IdString, BoundingBox> 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 &region : sorted(ctx->region)) {
+ Region *r = region.second;
+ BoundingBox bb;
+ if (r->constr_bels) {
+ bb.x0 = std::numeric_limits<int>::max();
+ bb.x1 = std::numeric_limits<int>::min();
+ bb.y0 = std::numeric_limits<int>::max();
+ bb.y1 = std::numeric_limits<int>::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);
}
}
@@ -735,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();
@@ -754,15 +794,38 @@ class HeAPPlacer
int best_inp_len = std::numeric_limits<int>::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(5000, 8 * int(ctx->cells.size()))) {
+ log_error("Unable to find legal placement for all cells, design is probably at utilisation limit.\n");
+ }
+
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);
+ // 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) {
+ 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 +883,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 +946,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))
+ goto fail;
CellInfo *bound;
if (target == BelId() || ctx->getBelType(target) != vc->type)
goto fail;
@@ -948,6 +1015,15 @@ class HeAPPlacer
// Implementation of the cut-based spreading as described in the HeAP/SimPL papers
static constexpr float beta = 0.9;
+ template <typename T> 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<T>(std::min<T>(val, limit_high), limit_low);
+ }
+
struct ChainExtent
{
int x0, y0, x1, y1;
@@ -1460,10 +1536,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);
}
diff --git a/common/pybindings.cc b/common/pybindings.cc
index 03979233..3b2a3744 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
@@ -131,7 +131,7 @@ BOOST_PYTHON_MODULE(MODULE_NAME)
typedef std::unordered_map<IdString, Property> AttrMap;
typedef std::unordered_map<IdString, PortInfo> PortMap;
- typedef std::unordered_map<IdString, IdString> PinMap;
+ typedef std::unordered_map<IdString, IdString> IdIdMap;
typedef std::unordered_map<IdString, std::unique_ptr<Region>> RegionMap;
class_<BaseCtx, BaseCtx *, boost::noncopyable>("BaseCtx", no_init);
@@ -157,8 +157,8 @@ BOOST_PYTHON_MODULE(MODULE_NAME)
conv_from_str<BelId>>::def_wrap(ci_cls, "bel");
readwrite_wrapper<CellInfo &, decltype(&CellInfo::belStrength), &CellInfo::belStrength, pass_through<PlaceStrength>,
pass_through<PlaceStrength>>::def_wrap(ci_cls, "belStrength");
- readonly_wrapper<CellInfo &, decltype(&CellInfo::pins), &CellInfo::pins, wrap_context<PinMap &>>::def_wrap(ci_cls,
- "pins");
+ readonly_wrapper<CellInfo &, decltype(&CellInfo::pins), &CellInfo::pins, wrap_context<IdIdMap &>>::def_wrap(ci_cls,
+ "pins");
fn_wrapper_1a_v<CellInfo &, decltype(&CellInfo::addInput), &CellInfo::addInput, conv_from_str<IdString>>::def_wrap(
ci_cls, "addInput");
@@ -230,9 +230,25 @@ BOOST_PYTHON_MODULE(MODULE_NAME)
readonly_wrapper<Region &, decltype(&Region::wires), &Region::wires, wrap_context<WireSet &>>::def_wrap(region_cls,
"wires");
+ auto hierarchy_cls = class_<ContextualWrapper<HierarchicalCell &>>("HierarchicalCell", no_init);
+ readwrite_wrapper<HierarchicalCell &, decltype(&HierarchicalCell::name), &HierarchicalCell::name,
+ conv_to_str<IdString>, conv_from_str<IdString>>::def_wrap(hierarchy_cls, "name");
+ readwrite_wrapper<HierarchicalCell &, decltype(&HierarchicalCell::type), &HierarchicalCell::type,
+ conv_to_str<IdString>, conv_from_str<IdString>>::def_wrap(hierarchy_cls, "type");
+ readwrite_wrapper<HierarchicalCell &, decltype(&HierarchicalCell::parent), &HierarchicalCell::parent,
+ conv_to_str<IdString>, conv_from_str<IdString>>::def_wrap(hierarchy_cls, "parent");
+ readwrite_wrapper<HierarchicalCell &, decltype(&HierarchicalCell::fullpath), &HierarchicalCell::fullpath,
+ conv_to_str<IdString>, conv_from_str<IdString>>::def_wrap(hierarchy_cls, "fullpath");
+
+ readonly_wrapper<HierarchicalCell &, decltype(&HierarchicalCell::leaf_cells), &HierarchicalCell::leaf_cells,
+ wrap_context<IdIdMap &>>::def_wrap(hierarchy_cls, "leaf_cells");
+ readonly_wrapper<HierarchicalCell &, decltype(&HierarchicalCell::nets), &HierarchicalCell::nets,
+ wrap_context<IdIdMap &>>::def_wrap(hierarchy_cls, "nets");
+ readonly_wrapper<HierarchicalCell &, decltype(&HierarchicalCell::hier_cells), &HierarchicalCell::hier_cells,
+ wrap_context<IdIdMap &>>::def_wrap(hierarchy_cls, "hier_cells");
WRAP_MAP(AttrMap, conv_to_str<Property>, "AttrMap");
WRAP_MAP(PortMap, wrap_context<PortInfo &>, "PortMap");
- WRAP_MAP(PinMap, conv_to_str<IdString>, "PinMap");
+ WRAP_MAP(IdIdMap, conv_to_str<IdString>, "IdIdMap");
WRAP_MAP(WireMap, wrap_context<PipMap &>, "WireMap");
WRAP_MAP_UPTR(RegionMap, "RegionMap");
diff --git a/common/sdf.cc b/common/sdf.cc
new file mode 100644
index 00000000..b9606907
--- /dev/null
+++ b/common/sdf.cc
@@ -0,0 +1,334 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2019 David Shah <dave@ds0.me>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ */
+
+#include "nextpnr.h"
+#include "util.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+namespace SDF {
+
+struct MinMaxTyp
+{
+ double min, typ, max;
+};
+
+struct RiseFallDelay
+{
+ MinMaxTyp rise, fall;
+};
+
+struct PortAndEdge
+{
+ std::string port;
+ ClockEdge edge;
+};
+
+struct IOPath
+{
+ std::string from, to;
+ RiseFallDelay delay;
+};
+
+struct TimingCheck
+{
+ enum CheckType
+ {
+ SETUPHOLD,
+ PERIOD,
+ WIDTH
+ } type;
+ PortAndEdge from, to;
+ RiseFallDelay delay;
+};
+
+struct Cell
+{
+ std::string celltype, instance;
+ std::vector<IOPath> iopaths;
+ std::vector<TimingCheck> checks;
+};
+
+struct CellPort
+{
+ std::string cell, port;
+};
+
+struct Interconnect
+{
+ CellPort from, to;
+ RiseFallDelay delay;
+};
+
+struct SDFWriter
+{
+ bool cvc_mode = false;
+ std::vector<Cell> cells;
+ std::vector<Interconnect> conn;
+ std::string sdfversion, design, vendor, program;
+
+ std::string format_name(const std::string &name)
+ {
+ std::string fmt = "\"";
+ for (char c : name) {
+ if (c == '\\' || c == '\"')
+ fmt += "\"";
+ fmt += c;
+ }
+ fmt += "\"";
+ return fmt;
+ }
+
+ std::string escape_name(const std::string &name)
+ {
+ std::string esc;
+ for (char c : name) {
+ if (c == '$' || c == '\\' || c == '[' || c == ']' || c == ':' || (cvc_mode && c == '.'))
+ esc += '\\';
+ esc += c;
+ }
+ return esc;
+ }
+
+ std::string timing_check_name(TimingCheck::CheckType type)
+ {
+ switch (type) {
+ case TimingCheck::SETUPHOLD:
+ return "SETUPHOLD";
+ case TimingCheck::PERIOD:
+ return "PERIOD";
+ case TimingCheck::WIDTH:
+ return "WIDTH";
+ default:
+ NPNR_ASSERT_FALSE("unknown timing check type");
+ }
+ }
+
+ void write_delay(std::ostream &out, const RiseFallDelay &delay)
+ {
+ write_delay(out, delay.rise);
+ out << " ";
+ write_delay(out, delay.fall);
+ }
+
+ void write_delay(std::ostream &out, const MinMaxTyp &delay)
+ {
+ if (cvc_mode)
+ out << "(" << int(delay.min) << ":" << int(delay.typ) << ":" << int(delay.max) << ")";
+ else
+ out << "(" << delay.min << ":" << delay.typ << ":" << delay.max << ")";
+ }
+
+ void write_port(std::ostream &out, const CellPort &port)
+ {
+ if (cvc_mode)
+ out << escape_name(port.cell) + "." + escape_name(port.port);
+ else
+ out << escape_name(port.cell + "/" + port.port);
+ }
+
+ void write_portedge(std::ostream &out, const PortAndEdge &pe)
+ {
+ out << "(" << (pe.edge == RISING_EDGE ? "posedge" : "negedge") << " " << escape_name(pe.port) << ")";
+ }
+
+ void write(std::ostream &out)
+ {
+ out << "(DELAYFILE" << std::endl;
+ // Headers and metadata
+ out << " (SDFVERSION " << format_name(sdfversion) << ")" << std::endl;
+ out << " (DESIGN " << format_name(design) << ")" << std::endl;
+ out << " (VENDOR " << format_name(vendor) << ")" << std::endl;
+ out << " (PROGRAM " << format_name(program) << ")" << std::endl;
+ out << " (DIVIDER " << (cvc_mode ? "." : "/") << ")" << std::endl;
+ out << " (TIMESCALE 1ps)" << std::endl;
+ // Write interconnect delays, with the main design begin a "cell"
+ out << " (CELL" << std::endl;
+ out << " (CELLTYPE " << format_name(design) << ")" << std::endl;
+ out << " (INSTANCE )" << std::endl;
+ out << " (DELAY" << std::endl;
+ out << " (ABSOLUTE" << std::endl;
+ for (auto &ic : conn) {
+ out << " (INTERCONNECT ";
+ write_port(out, ic.from);
+ out << " ";
+ write_port(out, ic.to);
+ out << " ";
+ write_delay(out, ic.delay);
+ out << ")" << std::endl;
+ }
+ out << " )" << std::endl;
+ out << " )" << std::endl;
+ out << " )" << std::endl;
+ // Write cells
+ for (auto &cell : cells) {
+ out << " (CELL" << std::endl;
+ out << " (CELLTYPE " << format_name(cell.celltype) << ")" << std::endl;
+ out << " (INSTANCE " << escape_name(cell.instance) << ")" << std::endl;
+ // IOPATHs (combinational delay and clock-to-q)
+ if (!cell.iopaths.empty()) {
+ out << " (DELAY" << std::endl;
+ out << " (ABSOLUTE" << std::endl;
+ for (auto &path : cell.iopaths) {
+ out << " (IOPATH " << escape_name(path.from) << " " << escape_name(path.to) << " ";
+ write_delay(out, path.delay);
+ out << ")" << std::endl;
+ }
+ out << " )" << std::endl;
+ out << " )" << std::endl;
+ }
+ // Timing Checks (setup/hold, period, width)
+ if (!cell.checks.empty()) {
+ out << " (TIMINGCHECK" << std::endl;
+ for (auto &check : cell.checks) {
+ out << " (" << timing_check_name(check.type) << " ";
+ write_portedge(out, check.from);
+ out << " ";
+ if (check.type == TimingCheck::SETUPHOLD) {
+ write_portedge(out, check.to);
+ out << " ";
+ }
+ if (check.type == TimingCheck::SETUPHOLD)
+ write_delay(out, check.delay);
+ else
+ write_delay(out, check.delay.rise);
+ out << ")" << std::endl;
+ }
+ out << " )" << std::endl;
+ }
+ out << " )" << std::endl;
+ }
+ out << ")" << std::endl;
+ }
+};
+
+} // namespace SDF
+
+void Context::writeSDF(std::ostream &out, bool cvc_mode) const
+{
+ using namespace SDF;
+ SDFWriter wr;
+ wr.cvc_mode = cvc_mode;
+ wr.design = str_or_default(attrs, id("module"), "top");
+ wr.sdfversion = "3.0";
+ wr.vendor = "nextpnr";
+ wr.program = "nextpnr";
+
+ const double delay_scale = 1000;
+ // Convert from DelayInfo to SDF-friendly RiseFallDelay
+ auto convert_delay = [&](const DelayInfo &dly) {
+ RiseFallDelay rf;
+ rf.rise.min = getDelayNS(dly.minRaiseDelay()) * delay_scale;
+ rf.rise.typ = getDelayNS((dly.minRaiseDelay() + dly.maxRaiseDelay()) / 2) * delay_scale; // fixme: typ delays?
+ rf.rise.max = getDelayNS(dly.maxRaiseDelay()) * delay_scale;
+ rf.fall.min = getDelayNS(dly.minFallDelay()) * delay_scale;
+ rf.fall.typ = getDelayNS((dly.minFallDelay() + dly.maxFallDelay()) / 2) * delay_scale; // fixme: typ delays?
+ rf.fall.max = getDelayNS(dly.maxFallDelay()) * delay_scale;
+ return rf;
+ };
+
+ auto convert_setuphold = [&](const DelayInfo &setup, const DelayInfo &hold) {
+ RiseFallDelay rf;
+ rf.rise.min = getDelayNS(setup.minDelay()) * delay_scale;
+ rf.rise.typ = getDelayNS((setup.minDelay() + setup.maxDelay()) / 2) * delay_scale; // fixme: typ delays?
+ rf.rise.max = getDelayNS(setup.maxDelay()) * delay_scale;
+ rf.fall.min = getDelayNS(hold.minDelay()) * delay_scale;
+ rf.fall.typ = getDelayNS((hold.minDelay() + hold.maxDelay()) / 2) * delay_scale; // fixme: typ delays?
+ rf.fall.max = getDelayNS(hold.maxDelay()) * delay_scale;
+ return rf;
+ };
+
+ for (auto cell : sorted(cells)) {
+ Cell sc;
+ const CellInfo *ci = cell.second;
+ sc.instance = ci->name.str(this);
+ sc.celltype = ci->type.str(this);
+ for (auto port : ci->ports) {
+ int clockCount = 0;
+ TimingPortClass cls = getPortTimingClass(ci, port.first, clockCount);
+ if (cls == TMG_IGNORE)
+ continue;
+ if (port.second.net == nullptr)
+ continue; // Ignore disconnected ports
+ if (port.second.type != PORT_IN) {
+ // Add combinational paths to this output (or inout)
+ for (auto other : ci->ports) {
+ if (other.second.net == nullptr)
+ continue;
+ if (other.second.type == PORT_OUT)
+ continue;
+ DelayInfo dly;
+ if (!getCellDelay(ci, other.first, port.first, dly))
+ continue;
+ IOPath iop;
+ iop.from = other.first.str(this);
+ iop.to = port.first.str(this);
+ iop.delay = convert_delay(dly);
+ sc.iopaths.push_back(iop);
+ }
+ // Add clock-to-output delays, also as IOPaths
+ if (cls == TMG_REGISTER_OUTPUT)
+ for (int i = 0; i < clockCount; i++) {
+ auto clkInfo = getPortClockingInfo(ci, port.first, i);
+ IOPath cqp;
+ cqp.from = clkInfo.clock_port.str(this);
+ cqp.to = port.first.str(this);
+ cqp.delay = convert_delay(clkInfo.clockToQ);
+ sc.iopaths.push_back(cqp);
+ }
+ }
+ if (port.second.type != PORT_OUT && cls == TMG_REGISTER_INPUT) {
+ // Add setup/hold checks
+ for (int i = 0; i < clockCount; i++) {
+ auto clkInfo = getPortClockingInfo(ci, port.first, i);
+ TimingCheck chk;
+ chk.from.edge = RISING_EDGE; // Add setup/hold checks equally for rising and falling edges
+ chk.from.port = port.first.str(this);
+ chk.to.edge = clkInfo.edge;
+ chk.to.port = clkInfo.clock_port.str(this);
+ chk.type = TimingCheck::SETUPHOLD;
+ chk.delay = convert_setuphold(clkInfo.setup, clkInfo.hold);
+ sc.checks.push_back(chk);
+ chk.from.edge = FALLING_EDGE;
+ sc.checks.push_back(chk);
+ }
+ }
+ }
+ wr.cells.push_back(sc);
+ }
+
+ for (auto net : sorted(nets)) {
+ NetInfo *ni = net.second;
+ if (ni->driver.cell == nullptr)
+ continue;
+ for (auto &usr : ni->users) {
+ Interconnect ic;
+ ic.from.cell = ni->driver.cell->name.str(this);
+ ic.from.port = ni->driver.port.str(this);
+ ic.to.cell = usr.cell->name.str(this);
+ ic.to.port = usr.port.str(this);
+ // FIXME: min/max routing delay - or at least constructing DelayInfo here
+ ic.delay = convert_delay(getDelayFromNS(getDelayNS(getNetinfoRouteDelay(ni, usr))));
+ wr.conn.push_back(ic);
+ }
+ }
+ wr.write(out);
+}
+
+NEXTPNR_NAMESPACE_END \ No newline at end of file
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) &&