diff options
Diffstat (limited to 'common')
-rw-r--r-- | common/arch_pybindings_shared.h | 4 | ||||
-rw-r--r-- | common/command.cc | 31 | ||||
-rw-r--r-- | common/design_utils.cc | 12 | ||||
-rw-r--r-- | common/design_utils.h | 3 | ||||
-rw-r--r-- | common/nextpnr.cc | 84 | ||||
-rw-r--r-- | common/nextpnr.h | 45 | ||||
-rw-r--r-- | common/placer_heap.cc | 100 | ||||
-rw-r--r-- | common/pybindings.cc | 28 | ||||
-rw-r--r-- | common/sdf.cc | 334 | ||||
-rw-r--r-- | common/timing.cc | 3 |
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 ®ion : 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) && |