diff options
Diffstat (limited to 'generic')
-rw-r--r-- | generic/arch.cc | 161 | ||||
-rw-r--r-- | generic/arch.h | 53 | ||||
-rw-r--r-- | generic/arch_pybindings.cc | 197 | ||||
-rw-r--r-- | generic/archdefs.h | 10 | ||||
-rw-r--r-- | generic/cells.cc | 139 | ||||
-rw-r--r-- | generic/cells.h | 55 | ||||
-rw-r--r-- | generic/examples/.gitignore | 3 | ||||
-rw-r--r-- | generic/examples/README.md | 14 | ||||
-rw-r--r-- | generic/examples/__init__.py | 0 | ||||
-rw-r--r-- | generic/examples/bitstream.py | 17 | ||||
-rw-r--r-- | generic/examples/blinky.v | 9 | ||||
-rw-r--r-- | generic/examples/simple.py | 72 | ||||
-rwxr-xr-x | generic/examples/simple.sh | 4 | ||||
-rw-r--r-- | generic/examples/simple_config.py | 15 | ||||
-rw-r--r-- | generic/examples/simple_timing.py | 15 | ||||
-rw-r--r-- | generic/examples/write_fasm.py | 52 | ||||
-rw-r--r-- | generic/main.cc | 2 | ||||
-rw-r--r-- | generic/pack.cc | 293 | ||||
-rw-r--r-- | generic/synth/cells_map.v | 10 | ||||
-rw-r--r-- | generic/synth/prims.v | 59 | ||||
-rw-r--r-- | generic/synth/synth_generic.tcl | 24 |
21 files changed, 1187 insertions, 17 deletions
diff --git a/generic/arch.cc b/generic/arch.cc index aca81559..5617fa63 100644 --- a/generic/arch.cc +++ b/generic/arch.cc @@ -17,6 +17,7 @@ * */ +#include <iostream> #include <math.h> #include "nextpnr.h" #include "placer1.h" @@ -191,9 +192,53 @@ void Arch::setPipAttr(IdString pip, IdString key, const std::string &value) { pi void Arch::setBelAttr(IdString bel, IdString key, const std::string &value) { bels.at(bel).attrs[key] = value; } +void Arch::setLutK(int K) { args.K = K; } + +void Arch::setDelayScaling(double scale, double offset) +{ + args.delayScale = scale; + args.delayOffset = offset; +} + +void Arch::addCellTimingClock(IdString cell, IdString port) { cellTiming[cell].portClasses[port] = TMG_CLOCK_INPUT; } + +void Arch::addCellTimingDelay(IdString cell, IdString fromPort, IdString toPort, DelayInfo delay) +{ + if (get_or_default(cellTiming[cell].portClasses, fromPort, TMG_IGNORE) == TMG_IGNORE) + cellTiming[cell].portClasses[fromPort] = TMG_COMB_INPUT; + if (get_or_default(cellTiming[cell].portClasses, toPort, TMG_IGNORE) == TMG_IGNORE) + cellTiming[cell].portClasses[toPort] = TMG_COMB_OUTPUT; + cellTiming[cell].combDelays[CellDelayKey{fromPort, toPort}] = delay; +} + +void Arch::addCellTimingSetupHold(IdString cell, IdString port, IdString clock, DelayInfo setup, DelayInfo hold) +{ + TimingClockingInfo ci; + ci.clock_port = clock; + ci.edge = RISING_EDGE; + ci.setup = setup; + ci.hold = hold; + cellTiming[cell].clockingInfo[port].push_back(ci); + cellTiming[cell].portClasses[port] = TMG_REGISTER_INPUT; +} + +void Arch::addCellTimingClockToOut(IdString cell, IdString port, IdString clock, DelayInfo clktoq) +{ + TimingClockingInfo ci; + ci.clock_port = clock; + ci.edge = RISING_EDGE; + ci.clockToQ = clktoq; + cellTiming[cell].clockingInfo[port].push_back(ci); + cellTiming[cell].portClasses[port] = TMG_REGISTER_OUTPUT; +} + // --------------------------------------------------------------- -Arch::Arch(ArchArgs args) : chipName("generic"), args(args) {} +Arch::Arch(ArchArgs args) : chipName("generic"), args(args) +{ + // Dummy for empty decals + decal_graphics[IdString()]; +} void IdString::initialize_arch(const BaseCtx *ctx) {} @@ -260,7 +305,13 @@ IdString Arch::getBelType(BelId bel) const { return bels.at(bel).type; } const std::map<IdString, std::string> &Arch::getBelAttrs(BelId bel) const { return bels.at(bel).attrs; } -WireId Arch::getBelPinWire(BelId bel, IdString pin) const { return bels.at(bel).pins.at(pin).wire; } +WireId Arch::getBelPinWire(BelId bel, IdString pin) const +{ + const auto &bdata = bels.at(bel); + if (!bdata.pins.count(pin)) + log_error("bel '%s' has no pin '%s'\n", bel.c_str(this), pin.c_str(this)); + return bdata.pins.at(pin).wire; +} PortType Arch::getBelPinType(BelId bel, IdString pin) const { return bels.at(bel).pins.at(pin).type; } @@ -422,7 +473,7 @@ delay_t Arch::estimateDelay(WireId src, WireId dst) const const WireInfo &d = wires.at(dst); int dx = abs(s.x - d.x); int dy = abs(s.y - d.y); - return (dx + dy) * grid_distance_to_delay; + return (dx + dy) * args.delayScale + args.delayOffset; } delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const @@ -431,9 +482,9 @@ delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const auto driver_loc = getBelLocation(driver.cell->bel); auto sink_loc = getBelLocation(sink.cell->bel); - int dx = abs(driver_loc.x - driver_loc.x); - int dy = abs(sink_loc.y - sink_loc.y); - return (dx + dy) * grid_distance_to_delay; + int dx = abs(sink_loc.x - driver_loc.x); + int dy = abs(sink_loc.y - driver_loc.y); + return (dx + dy) * args.delayScale + args.delayOffset; } bool Arch::getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const { return false; } @@ -455,7 +506,14 @@ bool Arch::route() { return router1(getCtx(), Router1Cfg(getCtx())); } // --------------------------------------------------------------- -const std::vector<GraphicElement> &Arch::getDecalGraphics(DecalId decal) const { return decal_graphics.at(decal); } +const std::vector<GraphicElement> &Arch::getDecalGraphics(DecalId decal) const +{ + if (!decal_graphics.count(decal)) { + std::cerr << "No decal named " << decal.str(this) << std::endl; + log_error("No decal named %s!\n", decal.c_str(this)); + } + return decal_graphics.at(decal); +} DecalXY Arch::getBelDecal(BelId bel) const { return bels.at(bel).decalxy; } @@ -469,24 +527,103 @@ DecalXY Arch::getGroupDecal(GroupId group) const { return groups.at(group).decal bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const { - return false; + if (!cellTiming.count(cell->name)) + return false; + const auto &tmg = cellTiming.at(cell->name); + auto fnd = tmg.combDelays.find(CellDelayKey{fromPort, toPort}); + if (fnd != tmg.combDelays.end()) { + delay = fnd->second; + return true; + } else { + return false; + } } // Get the port class, also setting clockPort if applicable TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const { - return TMG_IGNORE; + if (!cellTiming.count(cell->name)) + return TMG_IGNORE; + const auto &tmg = cellTiming.at(cell->name); + if (tmg.clockingInfo.count(port)) + clockInfoCount = int(tmg.clockingInfo.at(port).size()); + else + clockInfoCount = 0; + return get_or_default(tmg.portClasses, port, TMG_IGNORE); } TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port, int index) const { - NPNR_ASSERT_FALSE("no clocking info for generic"); + NPNR_ASSERT(cellTiming.count(cell->name)); + const auto &tmg = cellTiming.at(cell->name); + NPNR_ASSERT(tmg.clockingInfo.count(port)); + return tmg.clockingInfo.at(port).at(index); +} + +bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const +{ + std::vector<const CellInfo *> cells; + cells.push_back(cell); + Loc loc = getBelLocation(bel); + for (auto tbel : getBelsByTile(loc.x, loc.y)) { + if (tbel == bel) + continue; + CellInfo *bound = getBoundBelCell(tbel); + if (bound != nullptr) + cells.push_back(bound); + } + return cellsCompatible(cells.data(), int(cells.size())); } -bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const { return true; } -bool Arch::isBelLocationValid(BelId bel) const { return true; } +bool Arch::isBelLocationValid(BelId bel) const +{ + std::vector<const CellInfo *> cells; + Loc loc = getBelLocation(bel); + for (auto tbel : getBelsByTile(loc.x, loc.y)) { + CellInfo *bound = getBoundBelCell(tbel); + if (bound != nullptr) + cells.push_back(bound); + } + return cellsCompatible(cells.data(), int(cells.size())); +} const std::string Arch::defaultPlacer = "sa"; const std::vector<std::string> Arch::availablePlacers = {"sa"}; +void Arch::assignArchInfo() +{ + for (auto &cell : getCtx()->cells) { + CellInfo *ci = cell.second.get(); + if (ci->type == id("GENERIC_SLICE")) { + ci->is_slice = true; + ci->slice_clk = get_net_or_empty(ci, id("CLK")); + } else { + ci->is_slice = false; + } + ci->user_group = int_or_default(ci->attrs, id("PACK_GROUP"), -1); + } +} + +bool Arch::cellsCompatible(const CellInfo **cells, int count) const +{ + const NetInfo *clk = nullptr; + int group = -1; + for (int i = 0; i < count; i++) { + const CellInfo *ci = cells[i]; + if (ci->is_slice && ci->slice_clk != nullptr) { + if (clk == nullptr) + clk = ci->slice_clk; + else if (clk != ci->slice_clk) + return false; + } + if (ci->user_group != -1) { + if (group == -1) + group = ci->user_group; + else if (group != ci->user_group) + return false; + } + } + return true; +} + NEXTPNR_NAMESPACE_END diff --git a/generic/arch.h b/generic/arch.h index 5b5d8c55..e9d3593c 100644 --- a/generic/arch.h +++ b/generic/arch.h @@ -25,6 +25,11 @@ NEXTPNR_NAMESPACE_BEGIN struct ArchArgs { + // Number of LUT inputs + int K = 4; + // y = mx + c relationship between distance and delay for interconnect + // delay estimates + double delayScale = 0.1, delayOffset = 0; }; struct WireInfo; @@ -81,6 +86,33 @@ struct GroupInfo DecalXY decalxy; }; +struct CellDelayKey +{ + IdString from, to; + inline bool operator==(const CellDelayKey &other) const { return from == other.from && to == other.to; } +}; + +NEXTPNR_NAMESPACE_END +namespace std { +template <> struct hash<NEXTPNR_NAMESPACE_PREFIX CellDelayKey> +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX CellDelayKey &dk) const noexcept + { + std::size_t seed = std::hash<NEXTPNR_NAMESPACE_PREFIX IdString>()(dk.from); + seed ^= std::hash<NEXTPNR_NAMESPACE_PREFIX IdString>()(dk.to) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + return seed; + } +}; +} // namespace std +NEXTPNR_NAMESPACE_BEGIN + +struct CellTiming +{ + std::unordered_map<IdString, TimingPortClass> portClasses; + std::unordered_map<CellDelayKey, DelayInfo> combDelays; + std::unordered_map<IdString, std::vector<TimingClockingInfo>> clockingInfo; +}; + struct Arch : BaseCtx { std::string chipName; @@ -101,7 +133,7 @@ struct Arch : BaseCtx std::vector<std::vector<int>> tileBelDimZ; std::vector<std::vector<int>> tilePipDimZ; - float grid_distance_to_delay; + std::unordered_map<IdString, CellTiming> cellTiming; void addWire(IdString name, IdString type, int x, int y); void addPip(IdString name, IdString type, IdString srcWire, IdString dstWire, DelayInfo delay, Loc loc); @@ -127,6 +159,14 @@ struct Arch : BaseCtx void setPipAttr(IdString pip, IdString key, const std::string &value); void setBelAttr(IdString bel, IdString key, const std::string &value); + void setLutK(int K); + void setDelayScaling(double scale, double offset); + + void addCellTimingClock(IdString cell, IdString port); + void addCellTimingDelay(IdString cell, IdString fromPort, IdString toPort, DelayInfo delay); + void addCellTimingSetupHold(IdString cell, IdString port, IdString clock, DelayInfo setup, DelayInfo hold); + void addCellTimingClockToOut(IdString cell, IdString port, IdString clock, DelayInfo clktoq); + // --------------------------------------------------------------- // Common Arch API. Every arch must provide the following methods. @@ -208,8 +248,8 @@ struct Arch : BaseCtx delay_t estimateDelay(WireId src, WireId dst) const; delay_t predictDelay(const NetInfo *net_info, const PortRef &sink) const; - delay_t getDelayEpsilon() const { return 0.01; } - delay_t getRipupDelayPenalty() const { return 1.0; } + delay_t getDelayEpsilon() const { return 0.001; } + delay_t getRipupDelayPenalty() const { return 0.015; } float getDelayNS(delay_t v) const { return v; } DelayInfo getDelayFromNS(float ns) const @@ -222,7 +262,7 @@ struct Arch : BaseCtx uint32_t getDelayChecksum(delay_t v) const { return 0; } bool getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const; - bool pack() { return true; } + bool pack(); bool place(); bool route(); @@ -243,6 +283,11 @@ struct Arch : BaseCtx static const std::string defaultPlacer; static const std::vector<std::string> availablePlacers; + + // --------------------------------------------------------------- + // Internal usage + void assignArchInfo(); + bool cellsCompatible(const CellInfo **cells, int count) const; }; NEXTPNR_NAMESPACE_END diff --git a/generic/arch_pybindings.cc b/generic/arch_pybindings.cc index 186b2c13..8526e409 100644 --- a/generic/arch_pybindings.cc +++ b/generic/arch_pybindings.cc @@ -23,18 +23,215 @@ #include "arch_pybindings.h" #include "nextpnr.h" #include "pybindings.h" +#include "pywrappers.h" NEXTPNR_NAMESPACE_BEGIN +namespace PythonConversion { +template <> struct string_converter<const IdString &> +{ + const IdString &from_str(Context *ctx, std::string name) { NPNR_ASSERT_FALSE("unsupported"); } + + std::string to_str(Context *ctx, const IdString &id) { return id.str(ctx); } +}; +} // namespace PythonConversion void arch_wrap_python() { using namespace PythonConversion; + auto arch_cls = class_<Arch, Arch *, bases<BaseCtx>, boost::noncopyable>("Arch", init<ArchArgs>()); + + auto dxy_cls = class_<ContextualWrapper<DecalXY>>("DecalXY_", no_init); + readwrite_wrapper<DecalXY, decltype(&DecalXY::decal), &DecalXY::decal, conv_to_str<DecalId>, + conv_from_str<DecalId>>::def_wrap(dxy_cls, "decal"); + readwrite_wrapper<DecalXY, decltype(&DecalXY::x), &DecalXY::x, pass_through<float>, pass_through<float>>::def_wrap( + dxy_cls, "x"); + readwrite_wrapper<DecalXY, decltype(&DecalXY::y), &DecalXY::y, pass_through<float>, pass_through<float>>::def_wrap( + dxy_cls, "y"); + auto ctx_cls = class_<Context, Context *, bases<Arch>, boost::noncopyable>("Context", no_init) .def("checksum", &Context::checksum) .def("pack", &Context::pack) .def("place", &Context::place) .def("route", &Context::route); + + class_<BelPin>("BelPin").def_readwrite("bel", &BelPin::bel).def_readwrite("pin", &BelPin::pin); + + class_<DelayInfo>("DelayInfo").def("maxDelay", &DelayInfo::maxDelay).def("minDelay", &DelayInfo::minDelay); + + fn_wrapper_1a<Context, decltype(&Context::getBelType), &Context::getBelType, conv_to_str<IdString>, + conv_from_str<BelId>>::def_wrap(ctx_cls, "getBelType"); + fn_wrapper_1a<Context, decltype(&Context::checkBelAvail), &Context::checkBelAvail, pass_through<bool>, + conv_from_str<BelId>>::def_wrap(ctx_cls, "checkBelAvail"); + fn_wrapper_1a<Context, decltype(&Context::getBelChecksum), &Context::getBelChecksum, pass_through<uint32_t>, + conv_from_str<BelId>>::def_wrap(ctx_cls, "getBelChecksum"); + fn_wrapper_3a_v<Context, decltype(&Context::bindBel), &Context::bindBel, conv_from_str<BelId>, + addr_and_unwrap<CellInfo>, pass_through<PlaceStrength>>::def_wrap(ctx_cls, "bindBel"); + fn_wrapper_1a_v<Context, decltype(&Context::unbindBel), &Context::unbindBel, conv_from_str<BelId>>::def_wrap( + ctx_cls, "unbindBel"); + fn_wrapper_1a<Context, decltype(&Context::getBoundBelCell), &Context::getBoundBelCell, deref_and_wrap<CellInfo>, + conv_from_str<BelId>>::def_wrap(ctx_cls, "getBoundBelCell"); + fn_wrapper_1a<Context, decltype(&Context::getConflictingBelCell), &Context::getConflictingBelCell, + deref_and_wrap<CellInfo>, conv_from_str<BelId>>::def_wrap(ctx_cls, "getConflictingBelCell"); + fn_wrapper_0a<Context, decltype(&Context::getBels), &Context::getBels, + wrap_context<const std::vector<BelId> &>>::def_wrap(ctx_cls, "getBels"); + + fn_wrapper_2a<Context, decltype(&Context::getBelPinWire), &Context::getBelPinWire, conv_to_str<WireId>, + conv_from_str<BelId>, conv_from_str<IdString>>::def_wrap(ctx_cls, "getBelPinWire"); + fn_wrapper_1a<Context, decltype(&Context::getWireBelPins), &Context::getWireBelPins, + wrap_context<const std::vector<BelPin> &>, conv_from_str<WireId>>::def_wrap(ctx_cls, + "getWireBelPins"); + + fn_wrapper_1a<Context, decltype(&Context::getWireChecksum), &Context::getWireChecksum, pass_through<uint32_t>, + conv_from_str<WireId>>::def_wrap(ctx_cls, "getWireChecksum"); + fn_wrapper_3a_v<Context, decltype(&Context::bindWire), &Context::bindWire, conv_from_str<WireId>, + addr_and_unwrap<NetInfo>, pass_through<PlaceStrength>>::def_wrap(ctx_cls, "bindWire"); + fn_wrapper_1a_v<Context, decltype(&Context::unbindWire), &Context::unbindWire, conv_from_str<WireId>>::def_wrap( + ctx_cls, "unbindWire"); + fn_wrapper_1a<Context, decltype(&Context::checkWireAvail), &Context::checkWireAvail, pass_through<bool>, + conv_from_str<WireId>>::def_wrap(ctx_cls, "checkWireAvail"); + fn_wrapper_1a<Context, decltype(&Context::getBoundWireNet), &Context::getBoundWireNet, deref_and_wrap<NetInfo>, + conv_from_str<WireId>>::def_wrap(ctx_cls, "getBoundWireNet"); + fn_wrapper_1a<Context, decltype(&Context::getConflictingWireNet), &Context::getConflictingWireNet, + deref_and_wrap<NetInfo>, conv_from_str<WireId>>::def_wrap(ctx_cls, "getConflictingWireNet"); + + fn_wrapper_0a<Context, decltype(&Context::getWires), &Context::getWires, + wrap_context<const std::vector<WireId> &>>::def_wrap(ctx_cls, "getWires"); + + fn_wrapper_0a<Context, decltype(&Context::getPips), &Context::getPips, + wrap_context<const std::vector<PipId> &>>::def_wrap(ctx_cls, "getPips"); + fn_wrapper_1a<Context, decltype(&Context::getPipChecksum), &Context::getPipChecksum, pass_through<uint32_t>, + conv_from_str<PipId>>::def_wrap(ctx_cls, "getPipChecksum"); + fn_wrapper_3a_v<Context, decltype(&Context::bindPip), &Context::bindPip, conv_from_str<PipId>, + addr_and_unwrap<NetInfo>, pass_through<PlaceStrength>>::def_wrap(ctx_cls, "bindPip"); + fn_wrapper_1a_v<Context, decltype(&Context::unbindPip), &Context::unbindPip, conv_from_str<PipId>>::def_wrap( + ctx_cls, "unbindPip"); + fn_wrapper_1a<Context, decltype(&Context::checkPipAvail), &Context::checkPipAvail, pass_through<bool>, + conv_from_str<PipId>>::def_wrap(ctx_cls, "checkPipAvail"); + fn_wrapper_1a<Context, decltype(&Context::getBoundPipNet), &Context::getBoundPipNet, deref_and_wrap<NetInfo>, + conv_from_str<PipId>>::def_wrap(ctx_cls, "getBoundPipNet"); + fn_wrapper_1a<Context, decltype(&Context::getConflictingPipNet), &Context::getConflictingPipNet, + deref_and_wrap<NetInfo>, conv_from_str<PipId>>::def_wrap(ctx_cls, "getConflictingPipNet"); + + fn_wrapper_1a<Context, decltype(&Context::getPipsDownhill), &Context::getPipsDownhill, + wrap_context<const std::vector<PipId> &>, conv_from_str<WireId>>::def_wrap(ctx_cls, + "getPipsDownhill"); + fn_wrapper_1a<Context, decltype(&Context::getPipsUphill), &Context::getPipsUphill, + wrap_context<const std::vector<PipId> &>, conv_from_str<WireId>>::def_wrap(ctx_cls, "getPipsUphill"); + fn_wrapper_1a<Context, decltype(&Context::getWireAliases), &Context::getWireAliases, + wrap_context<const std::vector<PipId> &>, conv_from_str<WireId>>::def_wrap(ctx_cls, "getWireAliases"); + + fn_wrapper_1a<Context, decltype(&Context::getPipSrcWire), &Context::getPipSrcWire, conv_to_str<WireId>, + conv_from_str<PipId>>::def_wrap(ctx_cls, "getPipSrcWire"); + fn_wrapper_1a<Context, decltype(&Context::getPipDstWire), &Context::getPipDstWire, conv_to_str<WireId>, + conv_from_str<PipId>>::def_wrap(ctx_cls, "getPipDstWire"); + fn_wrapper_1a<Context, decltype(&Context::getPipDelay), &Context::getPipDelay, pass_through<DelayInfo>, + conv_from_str<PipId>>::def_wrap(ctx_cls, "getPipDelay"); + + fn_wrapper_1a<Context, decltype(&Context::getDelayFromNS), &Context::getDelayFromNS, pass_through<DelayInfo>, + pass_through<double>>::def_wrap(ctx_cls, "getDelayFromNS"); + + fn_wrapper_0a<Context, decltype(&Context::getChipName), &Context::getChipName, pass_through<std::string>>::def_wrap( + ctx_cls, "getChipName"); + fn_wrapper_0a<Context, decltype(&Context::archId), &Context::archId, conv_to_str<IdString>>::def_wrap(ctx_cls, + "archId"); + + fn_wrapper_3a<Context, decltype(&Context::constructDecalXY), &Context::constructDecalXY, wrap_context<DecalXY>, + conv_from_str<DecalId>, pass_through<float>, pass_through<float>>::def_wrap(ctx_cls, "DecalXY"); + + typedef std::unordered_map<IdString, std::unique_ptr<CellInfo>> CellMap; + typedef std::unordered_map<IdString, std::unique_ptr<NetInfo>> NetMap; + + readonly_wrapper<Context, decltype(&Context::cells), &Context::cells, wrap_context<CellMap &>>::def_wrap(ctx_cls, + "cells"); + readonly_wrapper<Context, decltype(&Context::nets), &Context::nets, wrap_context<NetMap &>>::def_wrap(ctx_cls, + "nets"); + + fn_wrapper_2a_v<Context, decltype(&Context::addClock), &Context::addClock, conv_from_str<IdString>, + pass_through<float>>::def_wrap(ctx_cls, "addClock"); + + // Generic arch construction API + fn_wrapper_4a_v<Context, decltype(&Context::addWire), &Context::addWire, conv_from_str<IdString>, + conv_from_str<IdString>, pass_through<int>, pass_through<int>>::def_wrap(ctx_cls, "addWire", + (arg("name"), "type", "x", + "y")); + fn_wrapper_6a_v<Context, decltype(&Context::addPip), &Context::addPip, conv_from_str<IdString>, + conv_from_str<IdString>, conv_from_str<IdString>, conv_from_str<IdString>, pass_through<DelayInfo>, + pass_through<Loc>>::def_wrap(ctx_cls, "addPip", + (arg("name"), "type", "srcWire", "dstWire", "delay", "loc")); + fn_wrapper_5a_v<Context, decltype(&Context::addAlias), &Context::addAlias, conv_from_str<IdString>, + conv_from_str<IdString>, conv_from_str<IdString>, conv_from_str<IdString>, + pass_through<DelayInfo>>::def_wrap(ctx_cls, "addAlias", + (arg("name"), "type", "srcWire", "dstWire", "delay")); + + fn_wrapper_4a_v<Context, decltype(&Context::addBel), &Context::addBel, conv_from_str<IdString>, + conv_from_str<IdString>, pass_through<Loc>, pass_through<bool>>::def_wrap(ctx_cls, "addBel", + (arg("name"), "type", + "loc", "gb")); + fn_wrapper_3a_v<Context, decltype(&Context::addBelInput), &Context::addBelInput, conv_from_str<IdString>, + conv_from_str<IdString>, conv_from_str<IdString>>::def_wrap(ctx_cls, "addBelInput", + (arg("bel"), "name", "wire")); + fn_wrapper_3a_v<Context, decltype(&Context::addBelOutput), &Context::addBelOutput, conv_from_str<IdString>, + conv_from_str<IdString>, conv_from_str<IdString>>::def_wrap(ctx_cls, "addBelOutput", + (arg("bel"), "name", "wire")); + fn_wrapper_3a_v<Context, decltype(&Context::addBelInout), &Context::addBelInout, conv_from_str<IdString>, + conv_from_str<IdString>, conv_from_str<IdString>>::def_wrap(ctx_cls, "addBelInout", + (arg("bel"), "name", "wire")); + + fn_wrapper_2a_v<Context, decltype(&Context::addGroupBel), &Context::addGroupBel, conv_from_str<IdString>, + conv_from_str<IdString>>::def_wrap(ctx_cls, "addGroupBel", (arg("group"), "bel")); + fn_wrapper_2a_v<Context, decltype(&Context::addGroupWire), &Context::addGroupWire, conv_from_str<IdString>, + conv_from_str<IdString>>::def_wrap(ctx_cls, "addGroupWire", (arg("group"), "wire")); + fn_wrapper_2a_v<Context, decltype(&Context::addGroupPip), &Context::addGroupPip, conv_from_str<IdString>, + conv_from_str<IdString>>::def_wrap(ctx_cls, "addGroupPip", (arg("group"), "pip")); + fn_wrapper_2a_v<Context, decltype(&Context::addGroupGroup), &Context::addGroupPip, conv_from_str<IdString>, + conv_from_str<IdString>>::def_wrap(ctx_cls, "addGroupGroup", (arg("group"), "grp")); + + fn_wrapper_2a_v<Context, decltype(&Context::addDecalGraphic), &Context::addDecalGraphic, conv_from_str<DecalId>, + pass_through<GraphicElement>>::def_wrap(ctx_cls, "addDecalGraphic", (arg("decal"), "graphic")); + fn_wrapper_2a_v<Context, decltype(&Context::setWireDecal), &Context::setWireDecal, conv_from_str<DecalId>, + unwrap_context<DecalXY>>::def_wrap(ctx_cls, "setWireDecal", (arg("wire"), "decalxy")); + fn_wrapper_2a_v<Context, decltype(&Context::setPipDecal), &Context::setPipDecal, conv_from_str<DecalId>, + unwrap_context<DecalXY>>::def_wrap(ctx_cls, "setPipDecal", (arg("pip"), "decalxy")); + fn_wrapper_2a_v<Context, decltype(&Context::setBelDecal), &Context::setBelDecal, conv_from_str<DecalId>, + unwrap_context<DecalXY>>::def_wrap(ctx_cls, "setBelDecal", (arg("bel"), "decalxy")); + fn_wrapper_2a_v<Context, decltype(&Context::setGroupDecal), &Context::setGroupDecal, conv_from_str<DecalId>, + unwrap_context<DecalXY>>::def_wrap(ctx_cls, "setGroupDecal", (arg("group"), "decalxy")); + + fn_wrapper_3a_v<Context, decltype(&Context::setWireAttr), &Context::setWireAttr, conv_from_str<DecalId>, + conv_from_str<IdString>, pass_through<std::string>>::def_wrap(ctx_cls, "setWireAttr", + (arg("wire"), "key", "value")); + fn_wrapper_3a_v<Context, decltype(&Context::setBelAttr), &Context::setBelAttr, conv_from_str<DecalId>, + conv_from_str<IdString>, pass_through<std::string>>::def_wrap(ctx_cls, "setBelAttr", + (arg("bel"), "key", "value")); + fn_wrapper_3a_v<Context, decltype(&Context::setPipAttr), &Context::setPipAttr, conv_from_str<DecalId>, + conv_from_str<IdString>, pass_through<std::string>>::def_wrap(ctx_cls, "setPipAttr", + (arg("pip"), "key", "value")); + + fn_wrapper_1a_v<Context, decltype(&Context::setLutK), &Context::setLutK, pass_through<int>>::def_wrap( + ctx_cls, "setLutK", arg("K")); + fn_wrapper_2a_v<Context, decltype(&Context::setDelayScaling), &Context::setDelayScaling, pass_through<double>, + pass_through<double>>::def_wrap(ctx_cls, "setDelayScaling", (arg("scale"), "offset")); + + fn_wrapper_2a_v<Context, decltype(&Context::addCellTimingClock), &Context::addCellTimingClock, + conv_from_str<IdString>, conv_from_str<IdString>>::def_wrap(ctx_cls, "addCellTimingClock", + (arg("cell"), "port")); + fn_wrapper_4a_v<Context, decltype(&Context::addCellTimingDelay), &Context::addCellTimingDelay, + conv_from_str<IdString>, conv_from_str<IdString>, conv_from_str<IdString>, + pass_through<DelayInfo>>::def_wrap(ctx_cls, "addCellTimingDelay", + (arg("cell"), "fromPort", "toPort", "delay")); + fn_wrapper_5a_v<Context, decltype(&Context::addCellTimingSetupHold), &Context::addCellTimingSetupHold, + conv_from_str<IdString>, conv_from_str<IdString>, conv_from_str<IdString>, pass_through<DelayInfo>, + pass_through<DelayInfo>>::def_wrap(ctx_cls, "addCellTimingSetupHold", + (arg("cell"), "port", "clock", "setup", "hold")); + fn_wrapper_4a_v<Context, decltype(&Context::addCellTimingClockToOut), &Context::addCellTimingClockToOut, + conv_from_str<IdString>, conv_from_str<IdString>, conv_from_str<IdString>, + pass_through<DelayInfo>>::def_wrap(ctx_cls, "addCellTimingClockToOut", + (arg("cell"), "port", "clock", "clktoq")); + + WRAP_MAP_UPTR(CellMap, "IdCellMap"); + WRAP_MAP_UPTR(NetMap, "IdNetMap"); + WRAP_VECTOR(const std::vector<IdString>, conv_to_str<IdString>); } NEXTPNR_NAMESPACE_END diff --git a/generic/archdefs.h b/generic/archdefs.h index 2452aa17..978c9c9b 100644 --- a/generic/archdefs.h +++ b/generic/archdefs.h @@ -55,8 +55,18 @@ typedef IdString DecalId; struct ArchNetInfo { }; + +struct NetInfo; + struct ArchCellInfo { + // Custom grouping set via "PACK_GROUP" attribute. All cells with the same group + // value may share a tile (-1 = don't care, default if not set) + int user_group; + // Is a slice type primitive + bool is_slice; + // Only packing rule for slice type primitives is a single clock per tile + const NetInfo *slice_clk; }; NEXTPNR_NAMESPACE_END diff --git a/generic/cells.cc b/generic/cells.cc new file mode 100644 index 00000000..14b368b2 --- /dev/null +++ b/generic/cells.cc @@ -0,0 +1,139 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2019 David Shah <david@symbioticeda.com> + * + * 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 "cells.h" +#include "design_utils.h" +#include "log.h" +#include "util.h" + +NEXTPNR_NAMESPACE_BEGIN + +void add_port(const Context *ctx, CellInfo *cell, std::string name, PortType dir) +{ + IdString id = ctx->id(name); + NPNR_ASSERT(cell->ports.count(id) == 0); + cell->ports[id] = PortInfo{id, nullptr, dir}; +} + +std::unique_ptr<CellInfo> create_generic_cell(Context *ctx, IdString type, std::string name) +{ + static int auto_idx = 0; + std::unique_ptr<CellInfo> new_cell = std::unique_ptr<CellInfo>(new CellInfo()); + if (name.empty()) { + new_cell->name = ctx->id("$nextpnr_" + type.str(ctx) + "_" + std::to_string(auto_idx++)); + } else { + new_cell->name = ctx->id(name); + } + 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("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(), "Q", PORT_OUT); + } else if (type == ctx->id("GENERIC_IOB")) { + new_cell->params[ctx->id("INPUT_USED")] = "0"; + new_cell->params[ctx->id("OUTPUT_USED")] = "0"; + new_cell->params[ctx->id("ENABLE_USED")] = "0"; + + add_port(ctx, new_cell.get(), "PAD", PORT_INOUT); + add_port(ctx, new_cell.get(), "I", PORT_IN); + add_port(ctx, new_cell.get(), "EN", PORT_IN); + add_port(ctx, new_cell.get(), "O", PORT_OUT); + } else { + log_error("unable to create generic cell of type %s", type.c_str(ctx)); + } + return new_cell; +} + +void lut_to_lc(const Context *ctx, CellInfo *lut, CellInfo *lc, bool no_dff) +{ + lc->params[ctx->id("INIT")] = lut->params[ctx->id("INIT")]; + + int lut_k = int_or_default(lut->params, ctx->id("K"), 4); + NPNR_ASSERT(lut_k <= ctx->args.K); + + for (int i = 0; i < lut_k; i++) { + IdString port = ctx->id("I[" + std::to_string(i) + "]"); + replace_port(lut, port, lc, port); + } + + if (no_dff) { + replace_port(lut, ctx->id("Q"), lc, ctx->id("Q")); + lc->params[ctx->id("FF_USED")] = "0"; + } +} + +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"; + replace_port(dff, ctx->id("D"), lc, ctx->id("I[0]")); + } + + replace_port(dff, ctx->id("Q"), lc, ctx->id("Q")); +} + +void nxio_to_iob(Context *ctx, CellInfo *nxio, CellInfo *iob, std::unordered_set<IdString> &todelete_cells) +{ + if (nxio->type == ctx->id("$nextpnr_ibuf")) { + iob->params[ctx->id("INPUT_USED")] = "1"; + replace_port(nxio, ctx->id("O"), iob, ctx->id("O")); + } else if (nxio->type == ctx->id("$nextpnr_obuf")) { + iob->params[ctx->id("OUTPUT_USED")] = "1"; + replace_port(nxio, ctx->id("I"), iob, ctx->id("I")); + } else if (nxio->type == ctx->id("$nextpnr_iobuf")) { + // N.B. tristate will be dealt with below + iob->params[ctx->id("INPUT_USED")] = "1"; + iob->params[ctx->id("OUTPUT_USED")] = "1"; + replace_port(nxio, ctx->id("I"), iob, ctx->id("I")); + replace_port(nxio, ctx->id("O"), iob, ctx->id("O")); + } else { + NPNR_ASSERT(false); + } + NetInfo *donet = iob->ports.at(ctx->id("I")).net; + CellInfo *tbuf = net_driven_by( + ctx, donet, [](const Context *ctx, const CellInfo *cell) { return cell->type == ctx->id("$_TBUF_"); }, + ctx->id("Y")); + if (tbuf) { + iob->params[ctx->id("ENABLE_USED")] = "1"; + replace_port(tbuf, ctx->id("A"), iob, ctx->id("I")); + replace_port(tbuf, ctx->id("E"), iob, ctx->id("EN")); + + if (donet->users.size() > 1) { + for (auto user : donet->users) + log_info(" remaining tristate user: %s.%s\n", user.cell->name.c_str(ctx), user.port.c_str(ctx)); + log_error("unsupported tristate IO pattern for IO buffer '%s', " + "instantiate GENERIC_IOB manually to ensure correct behaviour\n", + nxio->name.c_str(ctx)); + } + ctx->nets.erase(donet->name); + todelete_cells.insert(tbuf->name); + } +} + +NEXTPNR_NAMESPACE_END diff --git a/generic/cells.h b/generic/cells.h new file mode 100644 index 00000000..646d738d --- /dev/null +++ b/generic/cells.h @@ -0,0 +1,55 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2019 David Shah <david@symbioticeda.com> + * + * 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" + +#ifndef GENERIC_CELLS_H +#define GENERIC_CELLS_H + +NEXTPNR_NAMESPACE_BEGIN + +// Create a generic arch cell and return it +// Name will be automatically assigned if not specified +std::unique_ptr<CellInfo> create_generic_cell(Context *ctx, IdString type, std::string name = ""); + +// Return true if a cell is a LUT +inline bool is_lut(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("LUT"); } + +// Return true if a cell is a flipflop +inline bool is_ff(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("DFF"); } + +inline bool is_lc(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("GENERIC_SLICE"); } + +// Convert a LUT primitive to (part of) an GENERIC_SLICE, swapping ports +// as needed. Set no_dff if a DFF is not being used, so that the output +// can be reconnected +void lut_to_lc(const Context *ctx, CellInfo *lut, CellInfo *lc, bool no_dff = true); + +// Convert a DFF primitive to (part of) an GENERIC_SLICE, setting parameters +// and reconnecting signals as necessary. If pass_thru_lut is True, the LUT will +// be configured as pass through and D connected to I0, otherwise D will be +// ignored +void dff_to_lc(const Context *ctx, CellInfo *dff, CellInfo *lc, bool pass_thru_lut = false); + +// Convert a nextpnr IO buffer to a GENERIC_IOB +void nxio_to_iob(Context *ctx, CellInfo *nxio, CellInfo *sbio, std::unordered_set<IdString> &todelete_cells); + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/generic/examples/.gitignore b/generic/examples/.gitignore new file mode 100644 index 00000000..38e95de5 --- /dev/null +++ b/generic/examples/.gitignore @@ -0,0 +1,3 @@ +blinky.fasm +__pycache__ +*.pyc diff --git a/generic/examples/README.md b/generic/examples/README.md new file mode 100644 index 00000000..9fd106d9 --- /dev/null +++ b/generic/examples/README.md @@ -0,0 +1,14 @@ +# Generic Architecture Example + +This contains a simple, artificial, example of the nextpnr generic API. + + - simple.py procedurally generates a simple FPGA architecture with IO at the edges, + logic slices in all other tiles, and interconnect only between adjacent tiles + + - simple_timing.py annotates cells with timing data (this is a separate script that must be run after packing) + + - write_fasm.py uses the nextpnr Python API to write a FASM file for a design + + - bitstream.py uses write_fasm.py to create a FASM ("FPGA assembly") file for the place-and-routed design + + - Run simple.sh to build an example design on the FPGA above
\ No newline at end of file diff --git a/generic/examples/__init__.py b/generic/examples/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/generic/examples/__init__.py diff --git a/generic/examples/bitstream.py b/generic/examples/bitstream.py new file mode 100644 index 00000000..1ab94f0c --- /dev/null +++ b/generic/examples/bitstream.py @@ -0,0 +1,17 @@ +from write_fasm import * +from simple_config import K + +# Need to tell FASM generator how to write parameters +# (celltype, parameter) -> ParameterConfig +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), + ("GENERIC_IOB", "ENABLE_USED"): ParameterConfig(write=True, numeric=True, width=1), +} + +with open("blinky.fasm", "w") as f: + write_fasm(ctx, param_map, f)
\ No newline at end of file diff --git a/generic/examples/blinky.v b/generic/examples/blinky.v new file mode 100644 index 00000000..b7cb1b86 --- /dev/null +++ b/generic/examples/blinky.v @@ -0,0 +1,9 @@ +module top(input clk, output reg [7:0] leds); + +reg [25:0] ctr; +always @(posedge clk) + ctr <= ctr + 1'b1; + +assign leds = ctr[25:18]; + +endmodule
\ No newline at end of file diff --git a/generic/examples/simple.py b/generic/examples/simple.py new file mode 100644 index 00000000..9339b68a --- /dev/null +++ b/generic/examples/simple.py @@ -0,0 +1,72 @@ +from simple_config import * + +def is_io(x, y): + return x == 0 or x == X-1 or y == 0 or y == Y-1 + +for x in range(X): + for y in range(Y): + # Bel port wires + 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) + 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 + for l in range(Wl): + ctx.addWire(name="X%dY%d_LOCAL%d" % (x, y, l), type="LOCAL", x=x, y=y) + # Create bels + if is_io(x, y): + if x == y: + continue + for z in range(2): + ctx.addBel(name="X%dY%d_IO%d" % (x, y, z), type="GENERIC_IOB", loc=Loc(x, y, z), gb=False) + ctx.addBelInput(bel="X%dY%d_IO%d" % (x, y, z), name="I", wire="X%dY%dZ%d_I0" % (x, y, z)) + ctx.addBelInput(bel="X%dY%d_IO%d" % (x, y, z), name="EN", wire="X%dY%dZ%d_I1" % (x, y, z)) + ctx.addBelOutput(bel="X%dY%d_IO%d" % (x, y, z), name="O", wire="X%dY%dZ%d_Q" % (x, y, z)) + else: + for z in range(N): + ctx.addBel(name="X%dY%d_SLICE%d" % (x, y, z), type="GENERIC_SLICE", loc=Loc(x, y, z), gb=False) + 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="Q", wire="X%dY%dZ%d_Q" % (x, y, z)) + +for x in range(X): + for y in range(Y): + # Pips driving bel input wires + # Bel input wires are driven by every Si'th local with an offset + def create_input_pips(dst, offset, skip): + for i in range(offset % skip, Wl, skip): + src = "X%dY%d_LOCAL%d" % (x, y, i) + ctx.addPip(name="X%dY%d.%s.%s" % (x, y, src, dst), type="BEL_INPUT", + srcWire=src, dstWire=dst, delay=ctx.getDelayFromNS(0.05), loc=Loc(x, y, 0)) + for z in range(N): + create_input_pips("X%dY%dZ%d_CLK" % (x, y, z), 0, Si) + for k in range(K): + create_input_pips("X%dY%dZ%d_I%d" % (x, y, z, k), k % Si, Si) + + # 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_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)) + # Pips from neighbour locals to locals + def create_neighbour_pips(dst, nx, ny, offset, skip): + if nx < 0 or nx >= X or ny < 0 or ny >= Y: + return + for i in range(offset % skip, Wl, skip): + src = "X%dY%d_LOCAL%d" % (nx, ny, i) + ctx.addPip(name="X%dY%d.%s.%s" % (x, y, src, dst), type="NEIGHBOUR", + srcWire=src, dstWire=dst, delay=ctx.getDelayFromNS(0.05), loc=Loc(x, y, 0)) + for l in range(Wl): + dst = "X%dY%d_LOCAL%d" % (x, y, l) + create_output_pips(dst, l % Sq, Sq) + create_neighbour_pips(dst, x-1, y-1, (l + 1) % Sl, Sl) + create_neighbour_pips(dst, x-1, y, (l + 2) % Sl, Sl) + create_neighbour_pips(dst, x-1, y+1, (l + 2) % Sl, Sl) + create_neighbour_pips(dst, x, y-1, (l + 3) % Sl, Sl) + 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 diff --git a/generic/examples/simple.sh b/generic/examples/simple.sh new file mode 100755 index 00000000..8ae903f9 --- /dev/null +++ b/generic/examples/simple.sh @@ -0,0 +1,4 @@ +#!/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 diff --git a/generic/examples/simple_config.py b/generic/examples/simple_config.py new file mode 100644 index 00000000..dfb38f1c --- /dev/null +++ b/generic/examples/simple_config.py @@ -0,0 +1,15 @@ +# Grid size including IOBs at edges +X = 12 +Y = 12 +# SLICEs per tile +N = 8 +# LUT input count +K = 4 +# Number of local wires +Wl = N*(K+1) + 8 +# 1/Fc for bel input wire pips +Si = 4 +# 1/Fc for Q to local wire pips +Sq = 4 +# ~1/Fc local to neighbour local wire pips +Sl = 8
\ No newline at end of file diff --git a/generic/examples/simple_timing.py b/generic/examples/simple_timing.py new file mode 100644 index 00000000..a955c8d7 --- /dev/null +++ b/generic/examples/simple_timing.py @@ -0,0 +1,15 @@ +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 cell.params["FF_USED"] == "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 diff --git a/generic/examples/write_fasm.py b/generic/examples/write_fasm.py new file mode 100644 index 00000000..1f279b63 --- /dev/null +++ b/generic/examples/write_fasm.py @@ -0,0 +1,52 @@ +from collections import namedtuple + +""" + write: set to True to enable writing this parameter to FASM + + numeric: set to True to write this parameter as a bit array (width>1) or + single bit (width==1) named after the parameter. Otherwise this + parameter will be written as `name.value` + + width: width of numeric parameter (ignored for non-numeric parameters) + + alias: an alternative name for this parameter (parameter name used if alias + is None) +""" +ParameterConfig = namedtuple('ParameterConfig', 'write numeric width alias') + +# FIXME use defaults= once Python 3.7 is standard +ParameterConfig.__new__.__defaults__ = (False, True, 1, None) + + +""" +Write a design as FASM + + ctx: nextpnr context + paramCfg: map from (celltype, parametername) -> ParameterConfig describing how to write parameters + f: output file +""" +def write_fasm(ctx, paramCfg, f): + for nname, net in sorted(ctx.nets, key=lambda x: str(x[1].name)): + print("# Net %s" % nname, file=f) + for wire, pip in sorted(net.wires, key=lambda x: str(x[1])): + if pip.pip != "": + print("%s" % pip.pip, file=f) + print("", file=f) + for cname, cell in sorted(ctx.cells, key=lambda x: str(x[1].name)): + print("# Cell %s at %s" % (cname, cell.bel), file=f) + for param, val in sorted(cell.params, key=lambda x: str(x)): + cfg = paramCfg[(cell.type, param)] + if not cfg.write: + continue + fasm_name = cfg.alias if cfg.alias is not None else param + if cfg.numeric: + if cfg.width == 1: + if int(val) != 0: + print("%s.%s" % (cell.bel, fasm_name), file=f) + else: + # Parameters with width >32 are direct binary, otherwise denary + binval = val if cfg.width > 32 else "{:0{}b}".format(int(val), cfg.width) + print("%s.%s[%d:0] = %d'b%s" % (cell.bel, fasm_name, cfg.width-1, cfg.width, binval), file=f) + else: + print("%s.%s.%s" % (cell.bel, fasm_name, val), file=f) + print("", file=f)
\ No newline at end of file diff --git a/generic/main.cc b/generic/main.cc index 08b0b348..c203f35c 100644 --- a/generic/main.cc +++ b/generic/main.cc @@ -49,7 +49,7 @@ po::options_description GenericCommandHandler::getArchOptions() return specific; } -void GenericCommandHandler::customBitstream(Context *ctx) { log_error("Here is when bitstream gets created"); } +void GenericCommandHandler::customBitstream(Context *ctx) {} std::unique_ptr<Context> GenericCommandHandler::createContext() { diff --git a/generic/pack.cc b/generic/pack.cc new file mode 100644 index 00000000..26ae9182 --- /dev/null +++ b/generic/pack.cc @@ -0,0 +1,293 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018-19 David Shah <david@symbioticeda.com> + * + * 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 <algorithm> +#include <iterator> +#include <unordered_set> +#include "cells.h" +#include "design_utils.h" +#include "log.h" +#include "util.h" + +NEXTPNR_NAMESPACE_BEGIN + +// Pack LUTs and LUT-FF pairs +static void pack_lut_lutffs(Context *ctx) +{ + log_info("Packing LUT-FFs..\n"); + + std::unordered_set<IdString> packed_cells; + std::vector<std::unique_ptr<CellInfo>> new_cells; + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (ctx->verbose) + log_info("cell '%s' is of type '%s'\n", ci->name.c_str(ctx), ci->type.c_str(ctx)); + if (is_lut(ctx, ci)) { + std::unique_ptr<CellInfo> packed = + create_generic_cell(ctx, ctx->id("GENERIC_SLICE"), ci->name.str(ctx) + "_LC"); + std::copy(ci->attrs.begin(), ci->attrs.end(), std::inserter(packed->attrs, packed->attrs.begin())); + packed_cells.insert(ci->name); + if (ctx->verbose) + log_info("packed cell %s into %s\n", ci->name.c_str(ctx), packed->name.c_str(ctx)); + // See if we can pack into a DFF + // TODO: LUT cascade + NetInfo *o = ci->ports.at(ctx->id("Q")).net; + CellInfo *dff = net_only_drives(ctx, o, is_ff, ctx->id("D"), true); + auto lut_bel = ci->attrs.find(ctx->id("BEL")); + bool packed_dff = false; + if (dff) { + if (ctx->verbose) + log_info("found attached dff %s\n", dff->name.c_str(ctx)); + auto dff_bel = dff->attrs.find(ctx->id("BEL")); + if (lut_bel != ci->attrs.end() && dff_bel != dff->attrs.end() && lut_bel->second != dff_bel->second) { + // Locations don't match, can't pack + } else { + lut_to_lc(ctx, ci, packed.get(), false); + dff_to_lc(ctx, dff, packed.get(), false); + ctx->nets.erase(o->name); + if (dff_bel != dff->attrs.end()) + packed->attrs[ctx->id("BEL")] = dff_bel->second; + packed_cells.insert(dff->name); + if (ctx->verbose) + log_info("packed cell %s into %s\n", dff->name.c_str(ctx), packed->name.c_str(ctx)); + packed_dff = true; + } + } + if (!packed_dff) { + lut_to_lc(ctx, ci, packed.get(), true); + } + new_cells.push_back(std::move(packed)); + } + } + for (auto pcell : packed_cells) { + ctx->cells.erase(pcell); + } + for (auto &ncell : new_cells) { + ctx->cells[ncell->name] = std::move(ncell); + } +} + +// Pack FFs not packed as LUTFFs +static void pack_nonlut_ffs(Context *ctx) +{ + log_info("Packing non-LUT FFs..\n"); + + std::unordered_set<IdString> packed_cells; + std::vector<std::unique_ptr<CellInfo>> new_cells; + + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (is_ff(ctx, ci)) { + std::unique_ptr<CellInfo> packed = + create_generic_cell(ctx, ctx->id("GENERIC_SLICE"), ci->name.str(ctx) + "_DFFLC"); + std::copy(ci->attrs.begin(), ci->attrs.end(), std::inserter(packed->attrs, packed->attrs.begin())); + if (ctx->verbose) + log_info("packed cell %s into %s\n", ci->name.c_str(ctx), packed->name.c_str(ctx)); + packed_cells.insert(ci->name); + dff_to_lc(ctx, ci, packed.get(), true); + new_cells.push_back(std::move(packed)); + } + } + for (auto pcell : packed_cells) { + ctx->cells.erase(pcell); + } + for (auto &ncell : new_cells) { + ctx->cells[ncell->name] = std::move(ncell); + } +} + +static bool net_is_constant(const Context *ctx, NetInfo *net, bool &value) +{ + if (net == nullptr) + return false; + if (net->name == ctx->id("$PACKER_GND_NET") || net->name == ctx->id("$PACKER_VCC_NET")) { + value = (net->name == ctx->id("$PACKER_VCC_NET")); + return true; + } else { + return false; + } +} + +// Merge a net into a constant net +static void set_net_constant(const Context *ctx, NetInfo *orig, NetInfo *constnet, bool constval) +{ + orig->driver.cell = nullptr; + for (auto user : orig->users) { + if (user.cell != nullptr) { + CellInfo *uc = user.cell; + if (ctx->verbose) + log_info("%s user %s\n", orig->name.c_str(ctx), uc->name.c_str(ctx)); + if ((is_lut(ctx, uc) || is_lc(ctx, uc)) && (user.port.str(ctx).at(0) == 'I') && !constval) { + uc->ports[user.port].net = nullptr; + } else { + uc->ports[user.port].net = constnet; + constnet->users.push_back(user); + } + } + } + orig->users.clear(); +} + +// Pack constants (simple implementation) +static void pack_constants(Context *ctx) +{ + log_info("Packing constants..\n"); + + std::unique_ptr<CellInfo> gnd_cell = create_generic_cell(ctx, ctx->id("GENERIC_SLICE"), "$PACKER_GND"); + gnd_cell->params[ctx->id("INIT")] = "0"; + std::unique_ptr<NetInfo> gnd_net = std::unique_ptr<NetInfo>(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(); + + std::unique_ptr<CellInfo> vcc_cell = create_generic_cell(ctx, ctx->id("GENERIC_SLICE"), "$PACKER_VCC"); + vcc_cell->params[ctx->id("INIT")] = "1"; + std::unique_ptr<NetInfo> vcc_net = std::unique_ptr<NetInfo>(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(); + + std::vector<IdString> dead_nets; + + bool gnd_used = false; + + for (auto net : sorted(ctx->nets)) { + NetInfo *ni = net.second; + if (ni->driver.cell != nullptr && ni->driver.cell->type == ctx->id("GND")) { + IdString drv_cell = ni->driver.cell->name; + set_net_constant(ctx, ni, gnd_net.get(), false); + gnd_used = true; + dead_nets.push_back(net.first); + ctx->cells.erase(drv_cell); + } else if (ni->driver.cell != nullptr && ni->driver.cell->type == ctx->id("VCC")) { + IdString drv_cell = ni->driver.cell->name; + set_net_constant(ctx, ni, vcc_net.get(), true); + dead_nets.push_back(net.first); + ctx->cells.erase(drv_cell); + } + } + + if (gnd_used) { + ctx->cells[gnd_cell->name] = std::move(gnd_cell); + ctx->nets[gnd_net->name] = std::move(gnd_net); + } + // Vcc cell always inserted for now, as it may be needed during carry legalisation (TODO: trim later if actually + // never used?) + ctx->cells[vcc_cell->name] = std::move(vcc_cell); + ctx->nets[vcc_net->name] = std::move(vcc_net); + + for (auto dn : dead_nets) { + ctx->nets.erase(dn); + } +} + +static bool is_nextpnr_iob(Context *ctx, CellInfo *cell) +{ + return cell->type == ctx->id("$nextpnr_ibuf") || cell->type == ctx->id("$nextpnr_obuf") || + cell->type == ctx->id("$nextpnr_iobuf"); +} + +static bool is_generic_iob(const Context *ctx, const CellInfo *cell) { return cell->type == ctx->id("GENERIC_IOB"); } + +// Pack IO buffers +static void pack_io(Context *ctx) +{ + std::unordered_set<IdString> packed_cells; + std::unordered_set<IdString> delete_nets; + + std::vector<std::unique_ptr<CellInfo>> new_cells; + log_info("Packing IOs..\n"); + + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (is_nextpnr_iob(ctx, ci)) { + CellInfo *iob = nullptr; + if (ci->type == ctx->id("$nextpnr_ibuf") || ci->type == ctx->id("$nextpnr_iobuf")) { + iob = net_only_drives(ctx, ci->ports.at(ctx->id("O")).net, is_generic_iob, ctx->id("PAD"), true, ci); + + } else if (ci->type == ctx->id("$nextpnr_obuf")) { + NetInfo *net = ci->ports.at(ctx->id("I")).net; + iob = net_only_drives(ctx, net, is_generic_iob, ctx->id("PAD"), true, ci); + } + if (iob != nullptr) { + // Trivial case, GENERIC_IOB used. Just destroy the net and the + // iobuf + log_info("%s feeds GENERIC_IOB %s, removing %s %s.\n", ci->name.c_str(ctx), iob->name.c_str(ctx), + ci->type.c_str(ctx), ci->name.c_str(ctx)); + NetInfo *net = iob->ports.at(ctx->id("PAD")).net; + if (((ci->type == ctx->id("$nextpnr_ibuf") || ci->type == ctx->id("$nextpnr_iobuf")) && + net->users.size() > 1) || + (ci->type == ctx->id("$nextpnr_obuf") && (net->users.size() > 2 || net->driver.cell != nullptr))) + log_error("PAD of %s '%s' connected to more than a single top level IO.\n", iob->type.c_str(ctx), + iob->name.c_str(ctx)); + + if (net != nullptr) { + delete_nets.insert(net->name); + iob->ports.at(ctx->id("PAD")).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 { + // Create a GENERIC_IOB buffer + std::unique_ptr<CellInfo> ice_cell = + create_generic_cell(ctx, ctx->id("GENERIC_IOB"), ci->name.str(ctx) + "$iob"); + nxio_to_iob(ctx, ci, ice_cell.get(), packed_cells); + new_cells.push_back(std::move(ice_cell)); + iob = new_cells.back().get(); + } + packed_cells.insert(ci->name); + std::copy(ci->attrs.begin(), ci->attrs.end(), std::inserter(iob->attrs, iob->attrs.begin())); + } + } + for (auto pcell : packed_cells) { + ctx->cells.erase(pcell); + } + for (auto dnet : delete_nets) { + ctx->nets.erase(dnet); + } + for (auto &ncell : new_cells) { + ctx->cells[ncell->name] = std::move(ncell); + } +} + +// Main pack function +bool Arch::pack() +{ + Context *ctx = getCtx(); + try { + log_break(); + pack_constants(ctx); + pack_io(ctx); + pack_lut_lutffs(ctx); + pack_nonlut_ffs(ctx); + ctx->assignArchInfo(); + log_info("Checksum: 0x%08x\n", ctx->checksum()); + return true; + } catch (log_execution_error_exception) { + return false; + } +} + +NEXTPNR_NAMESPACE_END diff --git a/generic/synth/cells_map.v b/generic/synth/cells_map.v new file mode 100644 index 00000000..a6027534 --- /dev/null +++ b/generic/synth/cells_map.v @@ -0,0 +1,10 @@ +module \$lut (A, Y); + parameter WIDTH = 0; + parameter LUT = 0; + input [WIDTH-1:0] A; + output Y; + + LUT #(.K(`LUT_K), .INIT(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 diff --git a/generic/synth/prims.v b/generic/synth/prims.v new file mode 100644 index 00000000..95fcfac7 --- /dev/null +++ b/generic/synth/prims.v @@ -0,0 +1,59 @@ +// LUT and DFF are combined to a GENERIC_SLICE + +module LUT #( + parameter K = 4, + parameter [2**K-1:0] INIT = 0, +) ( + input [K-1:0] I, + output Q +); + assign Q = INIT[I]; +endmodule + +module DFF ( + input CLK, D, + output reg Q +); + always @(posedge CLK) + Q <= D; +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 Q +); + + wire lut_q; + LUT #(.K(K), .INIT(INIT)) lut_i(.I(I), .Q(lut_q)); + + generate if (FF_USED) + DFF dff_i(.CLK(CLK), .D(lut_q), .Q(Q)); + else + assign Q = lut_q; + endgenerate +endmodule + +module GENERIC_IOB #( + parameter INPUT_USED = 1'b0, + parameter OUTPUT_USED = 1'b0, + parameter ENABLE_USED = 1'b0 +) ( + inout PAD, + input I, EN, + output O +); + generate if (OUTPUT_USED && ENABLE_USED) + assign PAD = EN ? I : 1'bz; + else if (OUTPUT_USED) + assign PAD = I; + endgenerate + + generate if (INPUT_USED) + assign O = PAD; + endgenerate +endmodule
\ No newline at end of file diff --git a/generic/synth/synth_generic.tcl b/generic/synth/synth_generic.tcl new file mode 100644 index 00000000..e5d88e0d --- /dev/null +++ b/generic/synth/synth_generic.tcl @@ -0,0 +1,24 @@ +# Usage +# tcl synth_generic.tcl {K} {out.json} + +set LUT_K 4 +if {$argc > 0} { set LUT_K [lindex $argv 0] } +yosys read_verilog -lib [file dirname [file normalize $argv0]]/prims.v +yosys hierarchy -check +yosys proc +yosys flatten +yosys tribuf -logic +yosys deminout +yosys synth -run coarse +yosys memory_map +yosys opt -full +yosys techmap -map +/techmap.v +yosys opt -fast +yosys abc -lut $LUT_K -dress +yosys clean +yosys techmap -D LUT_K=$LUT_K -map [file dirname [file normalize $argv0]]/cells_map.v +yosys clean +yosys hierarchy -check +yosys stat + +if {$argc > 1} { yosys write_json [lindex $argv 1] } |