diff options
author | gatecat <gatecat@ds0.me> | 2021-05-15 22:37:19 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-05-15 22:37:19 +0100 |
commit | 47b4e42b1c0b849dc73a06357ad7e5cb24e0cbd7 (patch) | |
tree | 2a1a642e6bd577851161e0806453325ad777a302 /mistral | |
parent | 1b5767928de16b1df2e8d90066023e3cd076d40d (diff) | |
parent | 3eeb2b20ebd3e527fb82a46774f4584575a3a9e1 (diff) | |
download | nextpnr-47b4e42b1c0b849dc73a06357ad7e5cb24e0cbd7.tar.gz nextpnr-47b4e42b1c0b849dc73a06357ad7e5cb24e0cbd7.tar.bz2 nextpnr-47b4e42b1c0b849dc73a06357ad7e5cb24e0cbd7.zip |
Merge pull request #707 from gatecat/cyclonev
mistral: Initial Cyclone V support
Diffstat (limited to 'mistral')
-rw-r--r-- | mistral/arch.cc | 487 | ||||
-rw-r--r-- | mistral/arch.h | 550 | ||||
-rw-r--r-- | mistral/arch_pybindings.cc | 87 | ||||
-rw-r--r-- | mistral/arch_pybindings.h | 125 | ||||
-rw-r--r-- | mistral/archdefs.cc | 33 | ||||
-rw-r--r-- | mistral/archdefs.h | 236 | ||||
-rw-r--r-- | mistral/base_bitstream.cc | 100 | ||||
-rw-r--r-- | mistral/bitstream.cc | 361 | ||||
-rw-r--r-- | mistral/constids.inc | 78 | ||||
-rw-r--r-- | mistral/family.cmake | 14 | ||||
-rw-r--r-- | mistral/globals.cc | 46 | ||||
-rw-r--r-- | mistral/io.cc | 61 | ||||
-rw-r--r-- | mistral/lab.cc | 969 | ||||
-rw-r--r-- | mistral/main.cc | 105 | ||||
-rw-r--r-- | mistral/pack.cc | 365 | ||||
-rw-r--r-- | mistral/pins.cc | 67 | ||||
-rw-r--r-- | mistral/qsf.cc | 281 |
17 files changed, 3965 insertions, 0 deletions
diff --git a/mistral/arch.cc b/mistral/arch.cc new file mode 100644 index 00000000..cfa3e8b3 --- /dev/null +++ b/mistral/arch.cc @@ -0,0 +1,487 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021 Lofty <dan.ravensloft@gmail.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 "log.h" +#include "nextpnr.h" + +#include "placer1.h" +#include "placer_heap.h" +#include "router1.h" +#include "router2.h" +#include "timing.h" +#include "util.h" + +#include "cyclonev.h" + +NEXTPNR_NAMESPACE_BEGIN + +using namespace mistral; + +void IdString::initialize_arch(const BaseCtx *ctx) +{ +#define X(t) initialize_add(ctx, #t, ID_##t); + +#include "constids.inc" + +#undef X +} + +Arch::Arch(ArchArgs args) +{ + this->args = args; + this->cyclonev = mistral::CycloneV::get_model(args.device, args.mistral_root); + NPNR_ASSERT(this->cyclonev != nullptr); + + // Setup fast identifier maps + for (int i = 0; i < 1024; i++) { + IdString int_id = id(stringf("%d", i)); + int2id.push_back(int_id); + id2int[int_id] = i; + } + + for (int t = int(CycloneV::NONE); t <= int(CycloneV::DCMUX); t++) { + IdString rnode_id = id(CycloneV::rnode_type_names[t]); + rn_t2id.push_back(rnode_id); + id2rn_t[rnode_id] = CycloneV::rnode_type_t(t); + } + + log_info("Initialising bels...\n"); + bels_by_tile.resize(cyclonev->get_tile_sx() * cyclonev->get_tile_sy()); + for (int x = 0; x < cyclonev->get_tile_sx(); x++) { + for (int y = 0; y < cyclonev->get_tile_sy(); y++) { + CycloneV::pos_t pos = cyclonev->xy2pos(x, y); + + for (CycloneV::block_type_t bel : cyclonev->pos_get_bels(pos)) { + switch (bel) { + case CycloneV::block_type_t::LAB: + create_lab(x, y); + break; + default: + continue; + } + } + } + } + + for (auto gpio_pos : cyclonev->gpio_get_pos()) + create_gpio(CycloneV::pos2x(gpio_pos), CycloneV::pos2y(gpio_pos)); + + for (auto cmuxh_pos : cyclonev->cmuxh_get_pos()) + create_clkbuf(CycloneV::pos2x(cmuxh_pos), CycloneV::pos2y(cmuxh_pos)); + + // This import takes about 5s, perhaps long term we can speed it up, e.g. defer to Mistral more... + log_info("Initialising routing graph...\n"); + int pip_count = 0; + for (const auto &mux : cyclonev->dest_node_to_rmux) { + const auto &rmux = cyclonev->rmux_info[mux.second]; + WireId dst_wire(mux.first); + for (const auto &src : rmux.sources) { + if (CycloneV::rn2t(src) == CycloneV::NONE) + continue; + WireId src_wire(src); + wires[dst_wire].wires_uphill.push_back(src_wire); + wires[src_wire].wires_downhill.push_back(dst_wire); + ++pip_count; + } + } + + log_info(" imported %d wires and %d pips\n", int(wires.size()), pip_count); + + BaseArch::init_cell_types(); + BaseArch::init_bel_buckets(); +} + +int Arch::getTileBelDimZ(int x, int y) const +{ + // This seems like a reasonable upper bound + return 256; +} + +BelId Arch::getBelByName(IdStringList name) const +{ + BelId bel; + NPNR_ASSERT(name.size() == 4); + int x = id2int.at(name[1]); + int y = id2int.at(name[2]); + int z = id2int.at(name[3]); + + bel.pos = CycloneV::xy2pos(x, y); + bel.z = z; + + NPNR_ASSERT(name[0] == getBelType(bel)); + + return bel; +} + +IdStringList Arch::getBelName(BelId bel) const +{ + int x = CycloneV::pos2x(bel.pos); + int y = CycloneV::pos2y(bel.pos); + int z = bel.z & 0xFF; + + std::array<IdString, 4> ids{ + getBelType(bel), + int2id.at(x), + int2id.at(y), + int2id.at(z), + }; + + return IdStringList(ids); +} + +bool Arch::isBelLocationValid(BelId bel) const +{ + auto &data = bel_data(bel); + if (data.type == id_MISTRAL_COMB) { + return is_alm_legal(data.lab_data.lab, data.lab_data.alm) && check_lab_input_count(data.lab_data.lab); + } else if (data.type == id_MISTRAL_FF) { + return is_alm_legal(data.lab_data.lab, data.lab_data.alm) && check_lab_input_count(data.lab_data.lab) && + is_lab_ctrlset_legal(data.lab_data.lab); + } + return true; +} + +void Arch::update_bel(BelId bel) +{ + auto &data = bel_data(bel); + if (data.type == id_MISTRAL_COMB || data.type == id_MISTRAL_FF) { + update_alm_input_count(data.lab_data.lab, data.lab_data.alm); + } +} + +WireId Arch::getWireByName(IdStringList name) const +{ + // non-mistral wires + auto found_npnr = npnr_wirebyname.find(name); + if (found_npnr != npnr_wirebyname.end()) + return found_npnr->second; + // mistral wires + NPNR_ASSERT(name.size() == 4); + CycloneV::rnode_type_t ty = id2rn_t.at(name[0]); + int x = id2int.at(name[1]); + int y = id2int.at(name[2]); + int z = id2int.at(name[3]); + return WireId(CycloneV::rnode(ty, x, y, z)); +} + +IdStringList Arch::getWireName(WireId wire) const +{ + if (wire.is_nextpnr_created()) { + // non-mistral wires + std::array<IdString, 4> ids{ + id_WIRE, + int2id.at(CycloneV::rn2x(wire.node)), + int2id.at(CycloneV::rn2y(wire.node)), + wires.at(wire).name_override, + }; + return IdStringList(ids); + } else { + std::array<IdString, 4> ids{ + rn_t2id.at(CycloneV::rn2t(wire.node)), + int2id.at(CycloneV::rn2x(wire.node)), + int2id.at(CycloneV::rn2y(wire.node)), + int2id.at(CycloneV::rn2z(wire.node)), + }; + return IdStringList(ids); + } +} + +PipId Arch::getPipByName(IdStringList name) const +{ + WireId src = getWireByName(name.slice(0, 4)); + WireId dst = getWireByName(name.slice(4, 8)); + NPNR_ASSERT(src != WireId()); + NPNR_ASSERT(dst != WireId()); + return PipId(src.node, dst.node); +} + +IdStringList Arch::getPipName(PipId pip) const +{ + return IdStringList::concat(getWireName(getPipSrcWire(pip)), getWireName(getPipDstWire(pip))); +} + +std::vector<BelId> Arch::getBelsByTile(int x, int y) const +{ + // This should probably be redesigned, but it's a hack. + std::vector<BelId> bels; + if (x >= 0 && x < cyclonev->get_tile_sx() && y >= 0 && y < cyclonev->get_tile_sy()) { + for (size_t i = 0; i < bels_by_tile.at(pos2idx(x, y)).size(); i++) + bels.push_back(BelId(CycloneV::xy2pos(x, y), i)); + } + + return bels; +} + +IdString Arch::getBelType(BelId bel) const { return bel_data(bel).type; } + +std::vector<IdString> Arch::getBelPins(BelId bel) const +{ + std::vector<IdString> pins; + for (auto &p : bel_data(bel).pins) + pins.push_back(p.first); + return pins; +} + +bool Arch::isValidBelForCellType(IdString cell_type, BelId bel) const +{ + // Any combinational cell type can - theoretically - be placed at a combinational ALM bel + // The precise legality mechanics will be dealt with in isBelLocationValid. + IdString bel_type = getBelType(bel); + if (bel_type == id_MISTRAL_COMB) + return is_comb_cell(cell_type); + else if (bel_type == id_MISTRAL_IO) + return is_io_cell(cell_type); + else if (bel_type == id_MISTRAL_CLKENA) + return is_clkbuf_cell(cell_type); + else + return bel_type == cell_type; +} + +BelBucketId Arch::getBelBucketForCellType(IdString cell_type) const +{ + if (is_comb_cell(cell_type)) + return id_MISTRAL_COMB; + else if (is_io_cell(cell_type)) + return id_MISTRAL_IO; + else if (is_clkbuf_cell(cell_type)) + return id_MISTRAL_CLKENA; + else + return cell_type; +} + +BelId Arch::bel_by_block_idx(int x, int y, IdString type, int block_index) const +{ + auto &bels = bels_by_tile.at(pos2idx(x, y)); + for (size_t i = 0; i < bels.size(); i++) { + auto &bel_data = bels.at(i); + if (bel_data.type == type && bel_data.block_index == block_index) + return BelId(CycloneV::xy2pos(x, y), i); + } + return BelId(); +} + +BelId Arch::add_bel(int x, int y, IdString name, IdString type) +{ + auto &bels = bels_by_tile.at(pos2idx(x, y)); + BelId id = BelId(CycloneV::xy2pos(x, y), bels.size()); + all_bels.push_back(id); + bels.emplace_back(); + auto &bel = bels.back(); + bel.name = name; + bel.type = type; + // TODO: buckets (for example LABs and MLABs in the same bucket) + bel.bucket = type; + return id; +} + +WireId Arch::add_wire(int x, int y, IdString name, uint64_t flags) +{ + std::array<IdString, 4> ids{ + id_WIRE, + int2id.at(x), + int2id.at(y), + name, + }; + IdStringList full_name(ids); + auto existing = npnr_wirebyname.find(full_name); + if (existing != npnr_wirebyname.end()) { + // Already exists, don't create anything + return existing->second; + } else { + // Determine a unique ID for the wire + int z = 0; + WireId id; + while (wires.count(id = WireId(CycloneV::rnode(CycloneV::rnode_type_t((z >> 10) + 128), x, y, (z & 0x3FF))))) + z++; + wires[id].name_override = name; + wires[id].flags = flags; + npnr_wirebyname[full_name] = id; + return id; + } +} + +void Arch::reserve_route(WireId src, WireId dst) +{ + auto &dst_data = wires.at(dst); + int idx = -1; + + for (int i = 0; i < int(dst_data.wires_uphill.size()); i++) { + if (dst_data.wires_uphill.at(i) == src) { + idx = i; + break; + } + } + + NPNR_ASSERT(idx != -1); + + dst_data.flags = WireInfo::RESERVED_ROUTE | unsigned(idx); +} + +bool Arch::wires_connected(WireId src, WireId dst) const +{ + PipId pip(src.node, dst.node); + return getBoundPipNet(pip) != nullptr; +} + +PipId Arch::add_pip(WireId src, WireId dst) +{ + wires[src].wires_downhill.push_back(dst); + wires[dst].wires_uphill.push_back(src); + return PipId(src.node, dst.node); +} + +void Arch::add_bel_pin(BelId bel, IdString pin, PortType dir, WireId wire) +{ + auto &b = bel_data(bel); + NPNR_ASSERT(!b.pins.count(pin)); + b.pins[pin].dir = dir; + b.pins[pin].wire = wire; + + BelPin bel_pin; + bel_pin.bel = bel; + bel_pin.pin = pin; + wires[wire].bel_pins.push_back(bel_pin); +} + +void Arch::assign_default_pinmap(CellInfo *cell) +{ + for (auto &port : cell->ports) { + auto &pinmap = cell->pin_data[port.first].bel_pins; + if (!pinmap.empty()) + continue; // already mapped + if (is_comb_cell(cell->type) && comb_pinmap.count(port.first)) + pinmap.push_back(comb_pinmap.at(port.first)); // default comb mapping for placer purposes + else + pinmap.push_back(port.first); // default: assume bel pin named the same as cell pin + } +} + +void Arch::assignArchInfo() +{ + for (auto cell : sorted(cells)) { + CellInfo *ci = cell.second; + if (is_comb_cell(ci->type)) + assign_comb_info(ci); + else if (ci->type == id_MISTRAL_FF) + assign_ff_info(ci); + assign_default_pinmap(ci); + } +} + +delay_t Arch::estimateDelay(WireId src, WireId dst) const +{ + int x0 = CycloneV::rn2x(src.node); + int y0 = CycloneV::rn2y(src.node); + int x1 = CycloneV::rn2x(dst.node); + int y1 = CycloneV::rn2y(dst.node); + return 100 * std::abs(y1 - y0) + 100 * std::abs(x1 - x0) + 100; +} + +ArcBounds Arch::getRouteBoundingBox(WireId src, WireId dst) const +{ + ArcBounds bounds; + int src_x = CycloneV::rn2x(src.node); + int src_y = CycloneV::rn2y(src.node); + int dst_x = CycloneV::rn2x(dst.node); + int dst_y = CycloneV::rn2y(dst.node); + bounds.x0 = std::min(src_x, dst_x); + bounds.y0 = std::min(src_y, dst_y); + bounds.x1 = std::max(src_x, dst_x); + bounds.y1 = std::max(src_y, dst_y); + return bounds; +} + +delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const +{ + if (net_info->driver.cell == nullptr || net_info->driver.cell->bel == BelId()) + return 100; + if (sink.cell->bel == BelId()) + return 100; + Loc src_loc = getBelLocation(net_info->driver.cell->bel); + Loc dst_loc = getBelLocation(sink.cell->bel); + return std::abs(dst_loc.y - src_loc.y) * 100 + std::abs(dst_loc.x - src_loc.x) * 100 + 100; +} + +bool Arch::place() +{ + std::string placer = str_or_default(settings, id("placer"), defaultPlacer); + + if (placer == "heap") { + PlacerHeapCfg cfg(getCtx()); + cfg.ioBufTypes.insert(id_MISTRAL_IO); + cfg.ioBufTypes.insert(id_MISTRAL_IB); + cfg.ioBufTypes.insert(id_MISTRAL_OB); + cfg.cellGroups.emplace_back(); + cfg.cellGroups.back().insert({id_MISTRAL_COMB}); + cfg.cellGroups.back().insert({id_MISTRAL_FF}); + + cfg.beta = 0.5; // TODO: find a good value of beta for sensible ALM spreading + cfg.criticalityExponent = 7; + if (!placer_heap(getCtx(), cfg)) + return false; + } else if (placer == "sa") { + if (!placer1(getCtx(), Placer1Cfg(getCtx()))) + return false; + } else { + log_error("Mistral architecture does not support placer '%s'\n", placer.c_str()); + } + + getCtx()->attrs[getCtx()->id("step")] = std::string("place"); + archInfoToAttributes(); + return true; +} + +bool Arch::route() +{ + assign_budget(getCtx(), true); + + lab_pre_route(); + + std::string router = str_or_default(settings, id("router"), defaultRouter); + bool result; + if (router == "router1") { + result = router1(getCtx(), Router1Cfg(getCtx())); + } else if (router == "router2") { + router2(getCtx(), Router2Cfg(getCtx())); + result = true; + } else { + log_error("Mistral architecture does not support router '%s'\n", router.c_str()); + } + getCtx()->attrs[getCtx()->id("step")] = std::string("route"); + archInfoToAttributes(); + return result; +} + +#ifdef WITH_HEAP +const std::string Arch::defaultPlacer = "heap"; +#else +const std::string Arch::defaultPlacer = "sa"; +#endif + +const std::vector<std::string> Arch::availablePlacers = {"sa", +#ifdef WITH_HEAP + "heap" +#endif +}; + +const std::string Arch::defaultRouter = "router2"; +const std::vector<std::string> Arch::availableRouters = {"router1", "router2"}; + +NEXTPNR_NAMESPACE_END diff --git a/mistral/arch.h b/mistral/arch.h new file mode 100644 index 00000000..5913a615 --- /dev/null +++ b/mistral/arch.h @@ -0,0 +1,550 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021 Lofty <dan.ravensloft@gmail.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. + * + */ + +#ifndef MISTRAL_ARCH_H +#define MISTRAL_ARCH_H + +#include <set> +#include <sstream> + +#include "base_arch.h" +#include "nextpnr_types.h" +#include "relptr.h" + +#include "cyclonev.h" + +NEXTPNR_NAMESPACE_BEGIN + +struct ArchArgs +{ + std::string device; + std::string mistral_root; +}; + +// These structures are used for fast ALM validity checking +struct ALMInfo +{ + // Wires, so bitstream gen can determine connectivity + std::array<WireId, 2> comb_out; + std::array<WireId, 2> sel_clk, sel_ena, sel_aclr, sel_ef; + std::array<WireId, 4> ff_in, ff_out; + // Pointers to bels + std::array<BelId, 2> lut_bels; + std::array<BelId, 4> ff_bels; + + bool l6_mode = false; + + // Which CLK/ENA and ACLR is chosen for each half + std::array<int, 2> clk_ena_idx, aclr_idx; + + // For keeping track of how many inputs are currently being used, for the LAB routeability check + int unique_input_count = 0; +}; + +struct LABInfo +{ + std::array<ALMInfo, 10> alms; + // Control set wires + std::array<WireId, 3> clk_wires, ena_wires; + std::array<WireId, 2> aclr_wires; + WireId sclr_wire, sload_wire; + // TODO: LAB configuration (control set etc) + std::array<bool, 2> aclr_used; +}; + +struct PinInfo +{ + WireId wire; + PortType dir; +}; + +struct BelInfo +{ + IdString name; + IdString type; + IdString bucket; + + CellInfo *bound = nullptr; + + // For cases where we need to determine an original block index, due to multiple bels at the same tile this + // might not be the same as the nextpnr z-coordinate + int block_index; + std::unordered_map<IdString, PinInfo> pins; + // Info for different kinds of bels + union + { + // This enables fast lookup of the associated ALM, etc + struct + { + uint32_t lab; // index into the list of LABs + uint8_t alm; // ALM index inside LAB + uint8_t idx; // LUT or FF index inside ALM + } lab_data; + }; +}; + +// We maintain our own wire data based on mistral's. This gets us the bidirectional linking that nextpnr needs, +// and also makes it easy to add wires and pips for our own purposes like LAB internal routing, global clock +// sources, etc. +struct WireInfo +{ + // name_override is only used for nextpnr-created wires + // otherwise; this is empty and a name is created according to mistral rules + IdString name_override; + + // these are transformed on-the-fly to PipId by the iterator, to save space (WireId is half the size of PipId) + std::vector<WireId> wires_downhill; + std::vector<WireId> wires_uphill; + + std::vector<BelPin> bel_pins; + + // flags for special wires (currently unused) + uint64_t flags; + + // if the RESERVED_ROUTE mask is set in flags, then only wires_uphill[flags&0xFF] may drive this wire - used for + // control set preallocations + static const uint64_t RESERVED_ROUTE = 0x100; +}; + +// This transforms a WireIds, and adds the mising half of the pair to create a PipId +using WireVecIterator = std::vector<WireId>::const_iterator; +struct UpDownhillPipIterator +{ + WireVecIterator base; + WireId other_wire; + bool is_uphill; + + UpDownhillPipIterator(WireVecIterator base, WireId other_wire, bool is_uphill) + : base(base), other_wire(other_wire), is_uphill(is_uphill){}; + + bool operator!=(const UpDownhillPipIterator &other) { return base != other.base; } + UpDownhillPipIterator operator++() + { + ++base; + return *this; + } + UpDownhillPipIterator operator++(int) + { + UpDownhillPipIterator prior(*this); + ++(*this); + return prior; + } + PipId operator*() { return is_uphill ? PipId(base->node, other_wire.node) : PipId(other_wire.node, base->node); } +}; + +struct UpDownhillPipRange +{ + UpDownhillPipIterator b, e; + + UpDownhillPipRange(const std::vector<WireId> &v, WireId other_wire, bool is_uphill) + : b(v.cbegin(), other_wire, is_uphill), e(v.cend(), other_wire, is_uphill){}; + + UpDownhillPipIterator begin() const { return b; } + UpDownhillPipIterator end() const { return e; } +}; + +// This iterates over the list of wires, and for each wire yields its uphill pips, as an efficient way of going over +// all the pips in the device +using WireMapIterator = std::unordered_map<WireId, WireInfo>::const_iterator; +struct AllPipIterator +{ + WireMapIterator base, end; + int uphill_idx; + + AllPipIterator(WireMapIterator base, WireMapIterator end, int uphill_idx) + : base(base), end(end), uphill_idx(uphill_idx){}; + + bool operator!=(const AllPipIterator &other) { return base != other.base || uphill_idx != other.uphill_idx; } + AllPipIterator operator++() + { + // Increment uphill list index by one + ++uphill_idx; + // We've reached the end of the current wire. Keep incrementing the wire of interest until we find one with + // uphill pips, or we reach the end of the list of wires + while (base != end && uphill_idx >= int(base->second.wires_uphill.size())) { + uphill_idx = 0; + ++base; + } + return *this; + } + AllPipIterator operator++(int) + { + AllPipIterator prior(*this); + ++(*this); + return prior; + } + PipId operator*() { return PipId(base->second.wires_uphill.at(uphill_idx).node, base->first.node); } +}; + +struct AllPipRange +{ + AllPipIterator b, e; + + AllPipRange(const std::unordered_map<WireId, WireInfo> &wires) + : b(wires.cbegin(), wires.cend(), -1), e(wires.cend(), wires.cend(), 0) + { + // Starting the begin iterator at index -1 and incrementing it ensures we skip over the first wire if it has no + // uphill pips + ++b; + }; + + AllPipIterator begin() const { return b; } + AllPipIterator end() const { return e; } +}; + +// This transforms a map to a range of keys, used as the wire iterator +template <typename T> struct key_range +{ + key_range(const T &t) : b(t.cbegin()), e(t.cend()){}; + typename T::const_iterator b, e; + + struct xformed_iterator : public T::const_iterator + { + explicit xformed_iterator(typename T::const_iterator base) : T::const_iterator(base){}; + typename T::key_type operator*() { return this->T::const_iterator::operator*().first; } + }; + + xformed_iterator begin() const { return xformed_iterator(b); } + xformed_iterator end() const { return xformed_iterator(e); } +}; + +using AllWireRange = key_range<std::unordered_map<WireId, WireInfo>>; + +struct ArchRanges : BaseArchRanges +{ + using ArchArgsT = ArchArgs; + // Bels + using AllBelsRangeT = const std::vector<BelId> &; + using TileBelsRangeT = std::vector<BelId>; + using BelPinsRangeT = std::vector<IdString>; + using CellBelPinRangeT = const std::vector<IdString> &; + // Wires + using AllWiresRangeT = AllWireRange; + using DownhillPipRangeT = UpDownhillPipRange; + using UphillPipRangeT = UpDownhillPipRange; + using WireBelPinRangeT = const std::vector<BelPin> &; + // Pips + using AllPipsRangeT = AllPipRange; +}; + +// This enum captures different 'styles' of cell pins +// This is a combination of the modes available for a pin (tied high, low or inverted) +// and the default value to set it to not connected +enum CellPinStyle +{ + PINOPT_NONE = 0x0, // no options, just signal as-is + PINOPT_LO = 0x1, // can be tied low + PINOPT_HI = 0x2, // can be tied high + PINOPT_INV = 0x4, // can be inverted + + PINOPT_LOHI = 0x3, // can be tied low or high + PINOPT_LOHIINV = 0x7, // can be tied low or high; or inverted + + PINOPT_MASK = 0x7, + + PINDEF_NONE = 0x00, // leave disconnected + PINDEF_0 = 0x10, // connect to 0 if not used + PINDEF_1 = 0x20, // connect to 1 if not used + + PINDEF_MASK = 0x30, + + PINGLB_CLK = 0x100, // pin is a 'clock' for global purposes + + PINGLB_MASK = 0x100, + + PINSTYLE_NONE = 0x000, // default + + PINSTYLE_COMB = 0x017, // combinational signal, defaults low, can be inverted and tied + PINSTYLE_CLK = 0x107, // CLK type signal, invertible and defaults to disconnected + + PINSTYLE_CE = 0x027, // CE type signal, invertible and defaults to enabled + PINSTYLE_RST = 0x017, // RST type signal, invertible and defaults to not reset + PINSTYLE_DEDI = 0x000, // dedicated signals, leave alone + PINSTYLE_INP = 0x001, // general inputs, no inversion/tieing but defaults low + PINSTYLE_PU = 0x022, // signals that float high and default high + + PINSTYLE_CARRY = 0x001, // carry chains can be floating or 0? + +}; + +struct Arch : BaseArch<ArchRanges> +{ + ArchArgs args; + mistral::CycloneV *cyclonev; + + Arch(ArchArgs args); + ArchArgs archArgs() const { return args; } + + std::string getChipName() const override { return std::string{"TODO: getChipName"}; } + // ------------------------------------------------- + + int getGridDimX() const override { return cyclonev->get_tile_sx(); } + int getGridDimY() const override { return cyclonev->get_tile_sy(); } + int getTileBelDimZ(int x, int y) const override; // arch.cc + char getNameDelimiter() const override { return '.'; } + + // ------------------------------------------------- + + BelId getBelByName(IdStringList name) const override; // arch.cc + IdStringList getBelName(BelId bel) const override; // arch.cc + const std::vector<BelId> &getBels() const override { return all_bels; } + std::vector<BelId> getBelsByTile(int x, int y) const override; + Loc getBelLocation(BelId bel) const override + { + return Loc(CycloneV::pos2x(bel.pos), CycloneV::pos2y(bel.pos), bel.z); + } + BelId getBelByLocation(Loc loc) const override + { + if (loc.x < 0 || loc.x >= cyclonev->get_tile_sx()) + return BelId(); + if (loc.y < 0 || loc.y >= cyclonev->get_tile_sy()) + return BelId(); + auto &bels = bels_by_tile.at(pos2idx(loc.x, loc.y)); + if (loc.z < 0 || loc.z >= int(bels.size())) + return BelId(); + return BelId(CycloneV::xy2pos(loc.x, loc.y), loc.z); + } + IdString getBelType(BelId bel) const override; // arch.cc + WireId getBelPinWire(BelId bel, IdString pin) const override + { + auto &pins = bel_data(bel).pins; + auto found = pins.find(pin); + if (found == pins.end()) + return WireId(); + else + return found->second.wire; + } + PortType getBelPinType(BelId bel, IdString pin) const override { return bel_data(bel).pins.at(pin).dir; } + std::vector<IdString> getBelPins(BelId bel) const override; + + bool isBelLocationValid(BelId bel) const override; + + void bindBel(BelId bel, CellInfo *cell, PlaceStrength strength) override + { + auto &data = bel_data(bel); + NPNR_ASSERT(data.bound == nullptr); + data.bound = cell; + cell->bel = bel; + cell->belStrength = strength; + update_bel(bel); + } + void unbindBel(BelId bel) override + { + auto &data = bel_data(bel); + NPNR_ASSERT(data.bound != nullptr); + data.bound->bel = BelId(); + data.bound->belStrength = STRENGTH_NONE; + data.bound = nullptr; + update_bel(bel); + } + bool checkBelAvail(BelId bel) const override { return bel_data(bel).bound == nullptr; } + CellInfo *getBoundBelCell(BelId bel) const override { return bel_data(bel).bound; } + CellInfo *getConflictingBelCell(BelId bel) const override { return bel_data(bel).bound; } + + void update_bel(BelId bel); + BelId bel_by_block_idx(int x, int y, IdString type, int block_index) const; + + // ------------------------------------------------- + + WireId getWireByName(IdStringList name) const override; + IdStringList getWireName(WireId wire) const override; + DelayQuad getWireDelay(WireId wire) const override { return DelayQuad(0); } + const std::vector<BelPin> &getWireBelPins(WireId wire) const override { return wires.at(wire).bel_pins; } + AllWireRange getWires() const override { return AllWireRange(wires); } + + bool wires_connected(WireId src, WireId dst) const; + // Only allow src, and not any other wire, to drive dst + void reserve_route(WireId src, WireId dst); + + // ------------------------------------------------- + + PipId getPipByName(IdStringList name) const override; + AllPipRange getPips() const override { return AllPipRange(wires); } + Loc getPipLocation(PipId pip) const override { return Loc(CycloneV::rn2x(pip.dst), CycloneV::rn2y(pip.dst), 0); } + IdStringList getPipName(PipId pip) const override; + WireId getPipSrcWire(PipId pip) const override { return WireId(pip.src); }; + WireId getPipDstWire(PipId pip) const override { return WireId(pip.dst); }; + DelayQuad getPipDelay(PipId pip) const override { return DelayQuad(100); } + UpDownhillPipRange getPipsDownhill(WireId wire) const override + { + return UpDownhillPipRange(wires.at(wire).wires_downhill, wire, false); + } + UpDownhillPipRange getPipsUphill(WireId wire) const override + { + return UpDownhillPipRange(wires.at(wire).wires_uphill, wire, true); + } + + bool checkPipAvail(PipId pip) const override + { + // Check reserved routes + WireId dst(pip.dst); + const auto &dst_data = wires.at(dst); + if ((dst_data.flags & WireInfo::RESERVED_ROUTE) != 0) { + if (WireId(pip.src) != dst_data.wires_uphill.at(dst_data.flags & 0xFF)) + return false; + } + return BaseArch::checkPipAvail(pip); + } + + bool checkPipAvailForNet(PipId pip, NetInfo *net) const override + { + if (!checkPipAvail(pip)) + return false; + return BaseArch::checkPipAvailForNet(pip, net); + } + + // ------------------------------------------------- + + delay_t estimateDelay(WireId src, WireId dst) const override; + delay_t predictDelay(const NetInfo *net_info, const PortRef &sink) const override; + delay_t getDelayEpsilon() const override { return 10; }; + delay_t getRipupDelayPenalty() const override { return 100; }; + float getDelayNS(delay_t v) const override { return float(v) / 1000.0f; }; + delay_t getDelayFromNS(float ns) const override { return delay_t(ns * 1000.0f); }; + uint32_t getDelayChecksum(delay_t v) const override { return v; }; + + ArcBounds getRouteBoundingBox(WireId src, WireId dst) const override; + + // ------------------------------------------------- + + const std::vector<IdString> &getBelPinsForCellPin(const CellInfo *cell_info, IdString pin) const override + { + return cell_info->pin_data.at(pin).bel_pins; + } + + bool isValidBelForCellType(IdString cell_type, BelId bel) const override; + BelBucketId getBelBucketForCellType(IdString cell_type) const override; + + // ------------------------------------------------- + + void assignArchInfo() override; + bool pack() override; + bool place() override; + bool route() override; + + // ------------------------------------------------- + // Functions for device setup + + BelId add_bel(int x, int y, IdString name, IdString type); + WireId add_wire(int x, int y, IdString name, uint64_t flags = 0); + PipId add_pip(WireId src, WireId dst); + + void add_bel_pin(BelId bel, IdString pin, PortType dir, WireId wire); + + WireId get_port(CycloneV::block_type_t bt, int x, int y, int bi, CycloneV::port_type_t port, int pi = -1) const + { + return WireId(cyclonev->pnode_to_rnode(CycloneV::pnode(bt, x, y, port, bi, pi))); + } + + void create_lab(int x, int y); // lab.cc + void create_gpio(int x, int y); // io.cc + void create_clkbuf(int x, int y); // globals.cc + + // ------------------------------------------------- + + bool is_comb_cell(IdString cell_type) const; // lab.cc + bool is_alm_legal(uint32_t lab, uint8_t alm) const; // lab.cc + bool is_lab_ctrlset_legal(uint32_t lab) const; // lab.cc + bool check_lab_input_count(uint32_t lab) const; // lab.cc + + void assign_comb_info(CellInfo *cell) const; // lab.cc + void assign_ff_info(CellInfo *cell) const; // lab.cc + + void lab_pre_route(); // lab.cc + void assign_control_sets(uint32_t lab); // lab.cc + void reassign_alm_inputs(uint32_t lab, uint8_t alm); // lab.cc + void update_alm_input_count(uint32_t lab, uint8_t alm); // lab.cc + + uint64_t compute_lut_mask(uint32_t lab, uint8_t alm); // lab.cc + + // ------------------------------------------------- + + bool is_io_cell(IdString cell_type) const; // io.cc + BelId get_io_pin_bel(const CycloneV::pin_info_t *pin) const; // io.cc + + // ------------------------------------------------- + + bool is_clkbuf_cell(IdString cell_type) const; // globals.cc + + // ------------------------------------------------- + + static const std::string defaultPlacer; + static const std::vector<std::string> availablePlacers; + static const std::string defaultRouter; + static const std::vector<std::string> availableRouters; + + std::unordered_map<WireId, WireInfo> wires; + + // List of LABs + std::vector<LABInfo> labs; + + // WIP to link without failure + std::vector<BelPin> empty_belpin_list; + + // Conversion between numbers and rnode types and IdString, for fast wire name implementation + std::vector<IdString> int2id; + std::unordered_map<IdString, int> id2int; + + std::vector<IdString> rn_t2id; + std::unordered_map<IdString, CycloneV::rnode_type_t> id2rn_t; + + // This structure is only used for nextpnr-created wires + std::unordered_map<IdStringList, WireId> npnr_wirebyname; + + std::vector<std::vector<BelInfo>> bels_by_tile; + std::vector<BelId> all_bels; + + size_t pos2idx(int x, int y) const + { + NPNR_ASSERT(x >= 0 && x < int(cyclonev->get_tile_sx())); + NPNR_ASSERT(y >= 0 && y < int(cyclonev->get_tile_sy())); + return y * cyclonev->get_tile_sx() + x; + } + + size_t pos2idx(CycloneV::pos_t pos) const { return pos2idx(CycloneV::pos2x(pos), CycloneV::pos2y(pos)); } + + BelInfo &bel_data(BelId bel) { return bels_by_tile.at(pos2idx(bel.pos)).at(bel.z); } + const BelInfo &bel_data(BelId bel) const { return bels_by_tile.at(pos2idx(bel.pos)).at(bel.z); } + + // ------------------------------------------------- + + void assign_default_pinmap(CellInfo *cell); + static const std::unordered_map<IdString, IdString> comb_pinmap; + + // ------------------------------------------------- + + typedef std::unordered_map<IdString, CellPinStyle> CellPinsData; // pins.cc + static const std::unordered_map<IdString, CellPinsData> cell_pins_db; // pins.cc + CellPinStyle get_cell_pin_style(const CellInfo *cell, IdString port) const; // pins.cc + + // ------------------------------------------------- + + // List of IO constraints, used by QSF parser + std::unordered_map<IdString, std::unordered_map<IdString, Property>> io_attr; + void read_qsf(std::istream &in); // qsf.cc + + // ------------------------------------------------- + + void init_base_bitstream(); // base_bitstream.cc + void build_bitstream(); // bitstream.cc +}; + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/mistral/arch_pybindings.cc b/mistral/arch_pybindings.cc new file mode 100644 index 00000000..23716c93 --- /dev/null +++ b/mistral/arch_pybindings.cc @@ -0,0 +1,87 @@ + +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xen <claire@symbioticeda.com> + * Copyright (C) 2018 gatecat <gatecat@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. + * + */ + +#ifndef NO_PYTHON + +#include "arch_pybindings.h" +#include "nextpnr.h" +#include "pybindings.h" + +NEXTPNR_NAMESPACE_BEGIN + +void arch_wrap_python(py::module &m) +{ + using namespace PythonConversion; + py::class_<ArchArgs>(m, "ArchArgs").def_readwrite("device", &ArchArgs::device); + + py::class_<BelId>(m, "BelId").def_readwrite("pos", &BelId::pos).def_readwrite("z", &BelId::z); + + py::class_<WireId>(m, "WireId").def_readwrite("node", &WireId::node); + + py::class_<PipId>(m, "PipId").def_readwrite("src", &PipId::src).def_readwrite("dst", &PipId::dst); + + auto arch_cls = py::class_<Arch, BaseCtx>(m, "Arch").def(py::init<ArchArgs>()); + auto ctx_cls = py::class_<Context, Arch>(m, "Context") + .def("checksum", &Context::checksum) + .def("pack", &Context::pack) + .def("place", &Context::place) + .def("route", &Context::route); + + fn_wrapper_2a<Context, decltype(&Context::wires_connected), &Context::wires_connected, pass_through<bool>, + conv_from_str<WireId>, conv_from_str<WireId>>::def_wrap(ctx_cls, "wires_connected"); + fn_wrapper_2a<Context, decltype(&Context::compute_lut_mask), &Context::compute_lut_mask, pass_through<uint64_t>, + pass_through<uint32_t>, pass_through<uint8_t>>::def_wrap(ctx_cls, "compute_lut_mask"); + + typedef std::unordered_map<IdString, std::unique_ptr<CellInfo>> CellMap; + typedef std::unordered_map<IdString, std::unique_ptr<NetInfo>> NetMap; + typedef std::unordered_map<IdString, IdString> AliasMap; + typedef std::unordered_map<IdString, HierarchicalCell> HierarchyMap; + + auto belpin_cls = py::class_<ContextualWrapper<BelPin>>(m, "BelPin"); + readonly_wrapper<BelPin, decltype(&BelPin::bel), &BelPin::bel, conv_to_str<BelId>>::def_wrap(belpin_cls, "bel"); + readonly_wrapper<BelPin, decltype(&BelPin::pin), &BelPin::pin, conv_to_str<IdString>>::def_wrap(belpin_cls, "pin"); + + typedef const std::vector<BelId> &BelRange; + + typedef UpDownhillPipRange UphillPipRange; + typedef UpDownhillPipRange DownhillPipRange; + + typedef AllWireRange WireRange; + typedef const std::vector<BelPin> &BelPinRange; + + typedef const std::vector<BelBucketId> &BelBucketRange; + typedef const std::vector<BelId> &BelRangeForBelBucket; +#include "arch_pybindings_shared.h" + + WRAP_RANGE(m, Bel, conv_to_str<BelId>); + WRAP_RANGE(m, AllWire, conv_to_str<WireId>); + WRAP_RANGE(m, AllPip, conv_to_str<PipId>); + WRAP_RANGE(m, UpDownhillPip, conv_to_str<PipId>); + WRAP_RANGE(m, BelPin, wrap_context<BelPin>); + + WRAP_MAP_UPTR(m, CellMap, "IdCellMap"); + WRAP_MAP_UPTR(m, NetMap, "IdNetMap"); + WRAP_MAP(m, HierarchyMap, wrap_context<HierarchicalCell &>, "HierarchyMap"); +} + +NEXTPNR_NAMESPACE_END + +#endif // NO_PYTHON diff --git a/mistral/arch_pybindings.h b/mistral/arch_pybindings.h new file mode 100644 index 00000000..aa5b322e --- /dev/null +++ b/mistral/arch_pybindings.h @@ -0,0 +1,125 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xen <claire@symbioticeda.com> + * Copyright (C) 2018 gatecat <gatecat@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. + * + */ +#ifndef ARCH_PYBINDINGS_H +#define ARCH_PYBINDINGS_H +#ifndef NO_PYTHON + +#include "nextpnr.h" +#include "pybindings.h" + +NEXTPNR_NAMESPACE_BEGIN + +namespace PythonConversion { + +template <> struct string_converter<BelId> +{ + BelId from_str(Context *ctx, std::string name) { return ctx->getBelByNameStr(name); } + + std::string to_str(Context *ctx, BelId id) + { + if (id == BelId()) + throw bad_wrap(); + return ctx->getBelName(id).str(ctx); + } +}; + +template <> struct string_converter<const BelId &> +{ + const BelId &from_str(Context *ctx, std::string name) { NPNR_ASSERT_FALSE("unreachable"); } + + std::string to_str(Context *ctx, const BelId &id) + { + if (id == BelId()) + throw bad_wrap(); + return ctx->getBelName(id).str(ctx); + } +}; + +template <> struct string_converter<WireId> +{ + WireId from_str(Context *ctx, std::string name) { return ctx->getWireByNameStr(name); } + + std::string to_str(Context *ctx, WireId id) + { + if (id == WireId()) + throw bad_wrap(); + return ctx->getWireName(id).str(ctx); + } +}; + +template <> struct string_converter<const WireId> +{ + WireId from_str(Context *ctx, std::string name) { return ctx->getWireByNameStr(name); } + + std::string to_str(Context *ctx, WireId id) + { + if (id == WireId()) + throw bad_wrap(); + return ctx->getWireName(id).str(ctx); + } +}; + +template <> struct string_converter<PipId> +{ + PipId from_str(Context *ctx, std::string name) { return ctx->getPipByNameStr(name); } + + std::string to_str(Context *ctx, PipId id) + { + if (id == PipId()) + throw bad_wrap(); + return ctx->getPipName(id).str(ctx); + } +}; + +template <> struct string_converter<BelPin> +{ + BelPin from_str(Context *ctx, std::string name) + { + NPNR_ASSERT_FALSE("string_converter<BelPin>::from_str not implemented"); + } + + std::string to_str(Context *ctx, BelPin pin) + { + if (pin.bel == BelId()) + throw bad_wrap(); + return ctx->getBelName(pin.bel).str(ctx) + "/" + pin.pin.str(ctx); + } +}; + +template <> struct string_converter<const BelPin &> +{ + BelPin from_str(Context *ctx, std::string name) + { + NPNR_ASSERT_FALSE("string_converter<BelPin>::from_str not implemented"); + } + + std::string to_str(Context *ctx, const BelPin &pin) + { + if (pin.bel == BelId()) + throw bad_wrap(); + return ctx->getBelName(pin.bel).str(ctx) + "/" + pin.pin.str(ctx); + } +}; + +} // namespace PythonConversion + +NEXTPNR_NAMESPACE_END +#endif +#endif diff --git a/mistral/archdefs.cc b/mistral/archdefs.cc new file mode 100644 index 00000000..8a2bbe7a --- /dev/null +++ b/mistral/archdefs.cc @@ -0,0 +1,33 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021 gatecat <gatecat@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" + +NEXTPNR_NAMESPACE_BEGIN + +CellPinState ArchCellInfo::get_pin_state(IdString pin) const +{ + auto fnd = pin_data.find(pin); + if (fnd != pin_data.end()) + return fnd->second.state; + else + return PIN_SIG; +} + +NEXTPNR_NAMESPACE_END diff --git a/mistral/archdefs.h b/mistral/archdefs.h new file mode 100644 index 00000000..5fd02ad5 --- /dev/null +++ b/mistral/archdefs.h @@ -0,0 +1,236 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021 Lofty <dan.ravensloft@gmail.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. + * + */ + +#ifndef MISTRAL_ARCHDEFS_H +#define MISTRAL_ARCHDEFS_H + +#include <boost/functional/hash.hpp> + +#include "base_clusterinfo.h" +#include "cyclonev.h" + +#include "idstring.h" +#include "nextpnr_assertions.h" +#include "nextpnr_namespaces.h" + +NEXTPNR_NAMESPACE_BEGIN + +using mistral::CycloneV; + +typedef int delay_t; + +// https://bugreports.qt.io/browse/QTBUG-80789 + +#ifndef Q_MOC_RUN +enum ConstIds +{ + ID_NONE +#define X(t) , ID_##t +#include "constids.inc" +#undef X +}; + +#define X(t) static constexpr auto id_##t = IdString(ID_##t); +#include "constids.inc" +#undef X +#endif + +struct DelayInfo +{ + delay_t delay = 0; + + delay_t minRaiseDelay() const { return delay; } + delay_t maxRaiseDelay() const { return delay; } + + delay_t minFallDelay() const { return delay; } + delay_t maxFallDelay() const { return delay; } + + delay_t minDelay() const { return delay; } + delay_t maxDelay() const { return delay; } + + DelayInfo operator+(const DelayInfo &other) const + { + DelayInfo ret; + ret.delay = this->delay + other.delay; + return ret; + } +}; + +struct BelId +{ + BelId() = default; + BelId(CycloneV::pos_t _pos, uint16_t _z) : pos{_pos}, z{_z} {} + + // pos_t is used for X/Y, nextpnr-cyclonev uses its own Z coordinate system. + CycloneV::pos_t pos = 0; + uint16_t z = 0; + + bool operator==(const BelId &other) const { return pos == other.pos && z == other.z; } + bool operator!=(const BelId &other) const { return pos != other.pos || z != other.z; } + bool operator<(const BelId &other) const { return pos < other.pos || (pos == other.pos && z < other.z); } +}; + +static constexpr auto invalid_rnode = std::numeric_limits<CycloneV::rnode_t>::max(); + +struct WireId +{ + WireId() = default; + explicit WireId(CycloneV::rnode_t node) : node(node){}; + CycloneV::rnode_t node = invalid_rnode; + + // Wires created by nextpnr have rnode type >= 128 + bool is_nextpnr_created() const + { + NPNR_ASSERT(node != invalid_rnode); + return CycloneV::rn2t(node) >= 128; + } + + bool operator==(const WireId &other) const { return node == other.node; } + bool operator!=(const WireId &other) const { return node != other.node; } + bool operator<(const WireId &other) const { return node < other.node; } +}; + +struct PipId +{ + PipId() = default; + PipId(CycloneV::rnode_t src, CycloneV::rnode_t dst) : src(src), dst(dst){}; + CycloneV::rnode_t src = invalid_rnode, dst = invalid_rnode; + + bool operator==(const PipId &other) const { return src == other.src && dst == other.dst; } + bool operator!=(const PipId &other) const { return src != other.src || dst != other.dst; } + bool operator<(const PipId &other) const { return dst < other.dst || (dst == other.dst && src < other.src); } +}; + +typedef IdString DecalId; +typedef IdString GroupId; +typedef IdString BelBucketId; +typedef IdString ClusterId; + +struct ArchNetInfo +{ + bool is_global = false; +}; + +enum CellPinState +{ + PIN_SIG = 0, + PIN_0 = 1, + PIN_1 = 2, + PIN_INV = 3, +}; + +struct ArchPinInfo +{ + // Used to represent signals that are either tied to implicit constants (rather than explicitly routed constants); + // or are inverted + CellPinState state = PIN_SIG; + // The physical bel pins that this logical pin maps to + std::vector<IdString> bel_pins; +}; + +struct NetInfo; + +// Structures for representing how FF control sets are stored and validity-checked +struct ControlSig +{ + const NetInfo *net; + bool inverted; + + bool connected() const { return net != nullptr; } + bool operator==(const ControlSig &other) const { return net == other.net && inverted == other.inverted; } + bool operator!=(const ControlSig &other) const { return net == other.net && inverted == other.inverted; } +}; + +struct FFControlSet +{ + ControlSig clk, ena, aclr, sclr, sload; + + bool operator==(const FFControlSet &other) const + { + return clk == other.clk && ena == other.ena && aclr == other.aclr && sclr == other.sclr && sload == other.sload; + } + bool operator!=(const FFControlSet &other) const { return !(*this == other); } +}; + +struct ArchCellInfo : BaseClusterInfo +{ + union + { + struct + { + // Store the nets here for fast validity checking (avoids too many map lookups in a hot path) + std::array<const NetInfo *, 7> lut_in; + const NetInfo *comb_out; + + int lut_input_count; + int used_lut_input_count; // excluding those null/constant + int lut_bits_count; + + // for the LAB routeability check (see the detailed description in lab.cc); usually the same signal feeding + // multiple ALMs in a LAB is counted multiple times, due to not knowing which routing resources it will need + // in each case. But carry chains where we know how things will pack are allowed to share across ALMs as a + // special case, primarily to support adders/subtractors with a 'B invert' control signal shared across all + // ALMs. + int chain_shared_input_count; + + bool is_carry, is_shared, is_extended; + bool carry_start, carry_end; + } combInfo; + struct + { + FFControlSet ctrlset; + const NetInfo *sdata, *datain; + } ffInfo; + }; + + std::unordered_map<IdString, ArchPinInfo> pin_data; + + CellPinState get_pin_state(IdString pin) const; +}; + +NEXTPNR_NAMESPACE_END + +namespace std { +template <> struct hash<NEXTPNR_NAMESPACE_PREFIX BelId> +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX BelId &bel) const noexcept + { + return hash<uint32_t>()((static_cast<uint32_t>(bel.pos) << 16) | bel.z); + } +}; + +template <> struct hash<NEXTPNR_NAMESPACE_PREFIX WireId> +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX WireId &wire) const noexcept + { + return hash<uint32_t>()(wire.node); + } +}; + +template <> struct hash<NEXTPNR_NAMESPACE_PREFIX PipId> +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX PipId &pip) const noexcept + { + return hash<uint64_t>()((uint64_t(pip.dst) << 32) | pip.src); + } +}; + +} // namespace std + +#endif diff --git a/mistral/base_bitstream.cc b/mistral/base_bitstream.cc new file mode 100644 index 00000000..9fa74fb9 --- /dev/null +++ b/mistral/base_bitstream.cc @@ -0,0 +1,100 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021 gatecat <gatecat@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 "log.h" +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +namespace { +// Device-specific default config for the sx120f die +void default_sx120f(CycloneV *cv) +{ + // Default PMA config? + cv->bmux_m_set(CycloneV::PMA3, CycloneV::xy2pos(0, 11), CycloneV::FFPLL_IQCLK_DIRECTION, 0, CycloneV::TRISTATE); + cv->bmux_m_set(CycloneV::PMA3, CycloneV::xy2pos(0, 11), CycloneV::FFPLL_IQCLK_DIRECTION, 1, CycloneV::TRISTATE); + cv->bmux_m_set(CycloneV::PMA3, CycloneV::xy2pos(0, 23), CycloneV::FFPLL_IQCLK_DIRECTION, 0, CycloneV::DOWN); + cv->bmux_m_set(CycloneV::PMA3, CycloneV::xy2pos(0, 23), CycloneV::FFPLL_IQCLK_DIRECTION, 1, CycloneV::UP); + cv->bmux_m_set(CycloneV::PMA3, CycloneV::xy2pos(0, 35), CycloneV::FFPLL_IQCLK_DIRECTION, 0, CycloneV::UP); + cv->bmux_m_set(CycloneV::PMA3, CycloneV::xy2pos(0, 35), CycloneV::FFPLL_IQCLK_DIRECTION, 1, CycloneV::UP); + cv->bmux_b_set(CycloneV::PMA3, CycloneV::xy2pos(0, 35), CycloneV::FPLL_DRV_EN, 0, 0); + cv->bmux_m_set(CycloneV::PMA3, CycloneV::xy2pos(0, 35), CycloneV::HCLK_TOP_OUT_DRIVER, 0, CycloneV::TRISTATE); + // Default PLL config + cv->bmux_b_set(CycloneV::FPLL, CycloneV::xy2pos(0, 73), CycloneV::PL_AUX_ATB_EN0, 0, 1); + cv->bmux_b_set(CycloneV::FPLL, CycloneV::xy2pos(0, 73), CycloneV::PL_AUX_ATB_EN0_PRECOMP, 0, 1); + cv->bmux_b_set(CycloneV::FPLL, CycloneV::xy2pos(0, 73), CycloneV::PL_AUX_ATB_EN1, 0, 1); + cv->bmux_b_set(CycloneV::FPLL, CycloneV::xy2pos(0, 73), CycloneV::PL_AUX_ATB_EN1_PRECOMP, 0, 1); + cv->bmux_b_set(CycloneV::FPLL, CycloneV::xy2pos(0, 73), CycloneV::PL_AUX_BG_KICKSTART, 0, 1); + cv->bmux_b_set(CycloneV::FPLL, CycloneV::xy2pos(0, 73), CycloneV::PL_AUX_VBGMON_POWERDOWN, 0, 1); + // Default TERM config + cv->bmux_b_set(CycloneV::TERM, CycloneV::xy2pos(89, 34), CycloneV::INTOSC_2_EN, 0, 0); + + // TODO: what if these pins are used? where do these come from + for (int z = 0; z < 4; z++) { + cv->bmux_m_set(CycloneV::GPIO, CycloneV::xy2pos(89, 43), CycloneV::IOCSR_STD, z, CycloneV::NVR_LOW); + cv->bmux_m_set(CycloneV::GPIO, CycloneV::xy2pos(89, 66), CycloneV::IOCSR_STD, z, CycloneV::NVR_LOW); + } + for (int y : {38, 44, 51, 58, 65, 73, 79}) { + // TODO: Why only these upper DQS? is there a pattern? + cv->bmux_b_set(CycloneV::DQS16, CycloneV::xy2pos(89, y), CycloneV::RB_2X_CLK_DQS_INV, 0, 1); + cv->bmux_b_set(CycloneV::DQS16, CycloneV::xy2pos(89, y), CycloneV::RB_ACLR_LFIFO_EN, 0, 1); + cv->bmux_b_set(CycloneV::DQS16, CycloneV::xy2pos(89, y), CycloneV::RB_LFIFO_BYPASS, 0, 0); + } + + // Discover these mux values using + // grep 'i [_A-Z0-9.]* 1' empty.bt + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 12), 69), true); + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 13), 4), true); + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 34), 69), true); + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 35), 4), true); + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 37), 31), true); + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 40), 43), true); + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 46), 69), true); + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 47), 43), true); + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 53), 69), true); + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 54), 4), true); + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 73), 68), true); + + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(9, 18), 66), true); + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(9, 20), 8), true); + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(9, 27), 69), true); + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(9, 28), 43), true); + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(9, 59), 66), true); + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(9, 61), 8), true); + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(9, 68), 69), true); + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(9, 69), 43), true); + + for (int z = 10; z <= 45; z++) + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(51, 80), z), true); +} +} // namespace + +void Arch::init_base_bitstream() +{ + switch (cyclonev->current_model()->variant.die.type) { + case CycloneV::SX120F: + default_sx120f(cyclonev); + break; + default: + log_error("FIXME: die type %s currently unsupported for bitgen.\n", + cyclonev->current_model()->variant.die.name); + } +} + +NEXTPNR_NAMESPACE_END diff --git a/mistral/bitstream.cc b/mistral/bitstream.cc new file mode 100644 index 00000000..92d86410 --- /dev/null +++ b/mistral/bitstream.cc @@ -0,0 +1,361 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021 gatecat <gatecat@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 "log.h" +#include "nextpnr.h" +#include "util.h" + +NEXTPNR_NAMESPACE_BEGIN +namespace { +struct MistralBitgen +{ + MistralBitgen(Context *ctx) : ctx(ctx), cv(ctx->cyclonev){}; + Context *ctx; + CycloneV *cv; + + void init() + { + ctx->init_base_bitstream(); + // Default options + cv->opt_b_set(CycloneV::ALLOW_DEVICE_WIDE_OUTPUT_ENABLE_DIS, true); + cv->opt_n_set(CycloneV::CRC_DIVIDE_ORDER, 8); + cv->opt_b_set(CycloneV::CVP_CONF_DONE_EN, true); + cv->opt_b_set(CycloneV::DEVICE_WIDE_RESET_EN, true); + cv->opt_n_set(CycloneV::DRIVE_STRENGTH, 8); + cv->opt_b_set(CycloneV::IOCSR_READY_FROM_CSR_DONE_EN, true); + cv->opt_b_set(CycloneV::NCEO_DIS, true); + cv->opt_b_set(CycloneV::OCT_DONE_DIS, true); + cv->opt_r_set(CycloneV::OPT_A, 0x1dff); + cv->opt_r_set(CycloneV::OPT_B, 0xffffff402dffffffULL); + cv->opt_b_set(CycloneV::RELEASE_CLEARS_BEFORE_TRISTATES_DIS, true); + cv->opt_b_set(CycloneV::RETRY_CONFIG_ON_ERROR_EN, true); + cv->opt_r_set(CycloneV::START_UP_CLOCK, 0x3F); + // Default inversion + write_default_inv(); + } + + void write_default_inv() + { + // Some PNODEs are inverted by default. Set them up here. + for (const auto &pn2r : cv->get_all_p2r()) { + const auto &pn = pn2r.first; + auto pt = CycloneV::pn2pt(pn); + auto pi = CycloneV::pn2pi(pn); + + switch (CycloneV::pn2bt(pn)) { + case CycloneV::HMC: { + // HMC OE are inverted to set OE=0, i.e. unused pins floating + // TODO: handle the case when we are using the HMC or HMC bypass + std::string name(CycloneV::port_type_names[pt]); + if (name.compare(0, 5, "IOINT") != 0 || name.compare(name.size() - 2, 2, "OE") != 0) + continue; + cv->inv_set(pn2r.second, true); + break; + }; + // HPS IO - TODO: what about when we actually support the HPS primitives? + case CycloneV::HPS_BOOT: { + switch (pt) { + case CycloneV::CSEL_EN: + case CycloneV::BSEL_EN: + case CycloneV::BOOT_FROM_FPGA_READY: + case CycloneV::BOOT_FROM_FPGA_ON_FAILURE: + cv->inv_set(pn2r.second, true); + break; + case CycloneV::CSEL: + if (pi < 2) + cv->inv_set(pn2r.second, true); + break; + case CycloneV::BSEL: + if (pi < 3) + cv->inv_set(pn2r.second, true); + break; + default: + break; + }; + break; + }; + case CycloneV::HPS_CROSS_TRIGGER: { + if (pt == CycloneV::CLK_EN) + cv->inv_set(pn2r.second, true); + break; + }; + case CycloneV::HPS_TEST: { + if (pt == CycloneV::CFG_DFX_BYPASS_ENABLE) + cv->inv_set(pn2r.second, true); + break; + }; + case CycloneV::GPIO: { + // Ignore GPIO used by the design + BelId bel = ctx->bel_by_block_idx(CycloneV::pn2x(pn), CycloneV::pn2y(pn), id_MISTRAL_IO, + CycloneV::pn2bi(pn)); + if (bel != BelId() && ctx->getBoundBelCell(bel) != nullptr) + continue; + // Bonded IO invert OEIN.1 which disables the output buffer and floats the IO + // Unbonded IO invert OEIN.0 which enables the output buffer, and {DATAIN.[0123]} to drive a constant + // GND, presumably for power/EMI reasons + bool is_bonded = cv->pin_find_pnode(pn) != nullptr; + if (is_bonded && (pt != CycloneV::OEIN || pi != 1)) + continue; + if (!is_bonded && (pt != CycloneV::DATAIN) && (pt != CycloneV::OEIN || pi != 0)) + continue; + cv->inv_set(pn2r.second, true); + break; + }; + case CycloneV::FPLL: { + if (pt == CycloneV::EXTSWITCH || (pt == CycloneV::CLKEN && pi < 2)) + cv->inv_set(pn2r.second, true); + break; + }; + default: + break; + } + } + } + + void write_dqs() + { + for (auto pos : cv->dqs16_get_pos()) { + int x = CycloneV::pos2x(pos), y = CycloneV::pos2y(pos); + // DQS bypass for used output pins + for (int z = 0; z < 16; z++) { + int ioy = y + (z / 4) - 2; + if (ioy < 0 || ioy >= int(cv->get_tile_sy())) + continue; + BelId bel = ctx->bel_by_block_idx(x, ioy, id_MISTRAL_IO, z % 4); + if (bel == BelId()) + continue; + CellInfo *ci = ctx->getBoundBelCell(bel); + if (ci == nullptr || (ci->type != id_MISTRAL_IO && ci->type != id_MISTRAL_OB)) + continue; // not an output + cv->bmux_m_set(CycloneV::DQS16, pos, CycloneV::INPUT_REG4_SEL, z, CycloneV::SEL_LOCKED_DPA); + cv->bmux_r_set(CycloneV::DQS16, pos, CycloneV::RB_T9_SEL_EREG_CFF_DELAY, z, 0x1f); + } + } + } + + void write_routing() + { + for (auto net : sorted(ctx->nets)) { + NetInfo *ni = net.second; + for (auto wire : sorted_ref(ni->wires)) { + PipId pip = wire.second.pip; + if (pip == PipId()) + continue; + WireId src = ctx->getPipSrcWire(pip), dst = ctx->getPipDstWire(pip); + // Only write out routes that are entirely in the Mistral domain. Everything else is dealt with + // specially + if (src.is_nextpnr_created() || dst.is_nextpnr_created()) + continue; + cv->rnode_link(src.node, dst.node); + } + } + } + + void write_io_cell(CellInfo *ci, int x, int y, int bi) + { + bool is_output = + (ci->type == id_MISTRAL_OB || (ci->type == id_MISTRAL_IO && get_net_or_empty(ci, id_OE) != nullptr)); + auto pos = CycloneV::xy2pos(x, y); + // TODO: configurable pull, IO standard, etc + cv->bmux_b_set(CycloneV::GPIO, pos, CycloneV::USE_WEAK_PULLUP, bi, false); + if (is_output) { + cv->bmux_m_set(CycloneV::GPIO, pos, CycloneV::DRIVE_STRENGTH, bi, CycloneV::V3P3_LVTTL_16MA_LVCMOS_2MA); + cv->bmux_m_set(CycloneV::GPIO, pos, CycloneV::IOCSR_STD, bi, CycloneV::DIS); + } + // There seem to be two mirrored OEIN inversion bits for constant OE for inputs/outputs. This might be to + // prevent a single bitflip from turning inputs to outputs and messing up other devices on the boards, notably + // ECP5 does similar. OEIN.0 inverted for outputs; OEIN.1 for inputs + cv->inv_set(cv->pnode_to_rnode(CycloneV::pnode(CycloneV::GPIO, pos, CycloneV::OEIN, bi, is_output ? 0 : 1)), + true); + } + + void write_clkbuf_cell(CellInfo *ci, int x, int y, int bi) + { + (void)ci; // currently unused + auto pos = CycloneV::xy2pos(x, y); + cv->bmux_r_set(CycloneV::CMUXHG, pos, CycloneV::INPUT_SELECT, bi, 0x1b); // hardcode to general routing + cv->bmux_m_set(CycloneV::CMUXHG, pos, CycloneV::TESTSYN_ENOUT_SELECT, bi, CycloneV::PRE_SYNENB); + } + + void write_cells() + { + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + Loc loc = ctx->getBelLocation(ci->bel); + int bi = ctx->bel_data(ci->bel).block_index; + if (ctx->is_io_cell(ci->type)) + write_io_cell(ci, loc.x, loc.y, bi); + else if (ctx->is_clkbuf_cell(ci->type)) + write_clkbuf_cell(ci, loc.x, loc.y, bi); + } + } + + bool write_alm(uint32_t lab, uint8_t alm) + { + auto &alm_data = ctx->labs.at(lab).alms.at(alm); + + std::array<CellInfo *, 2> luts{ctx->getBoundBelCell(alm_data.lut_bels[0]), + ctx->getBoundBelCell(alm_data.lut_bels[1])}; + std::array<CellInfo *, 4> ffs{ + ctx->getBoundBelCell(alm_data.ff_bels[0]), ctx->getBoundBelCell(alm_data.ff_bels[1]), + ctx->getBoundBelCell(alm_data.ff_bels[2]), ctx->getBoundBelCell(alm_data.ff_bels[3])}; + // Skip empty ALMs + if (std::all_of(luts.begin(), luts.end(), [](CellInfo *c) { return !c; }) && + std::all_of(ffs.begin(), ffs.end(), [](CellInfo *c) { return !c; })) + return false; + + auto pos = alm_data.lut_bels[0].pos; + // Combinational mode - TODO: flop feedback + cv->bmux_m_set(CycloneV::LAB, pos, CycloneV::MODE, alm, alm_data.l6_mode ? CycloneV::L6 : CycloneV::L5); + // LUT function + cv->bmux_r_set(CycloneV::LAB, pos, CycloneV::LUT_MASK, alm, ctx->compute_lut_mask(lab, alm)); + // DFF/LUT output selection + const std::array<CycloneV::bmux_type_t, 6> mux_settings{CycloneV::TDFF0, CycloneV::TDFF1, CycloneV::TDFF1L, + CycloneV::BDFF0, CycloneV::BDFF1, CycloneV::BDFF1L}; + const std::array<CycloneV::port_type_t, 6> mux_port{CycloneV::FFT0, CycloneV::FFT1, CycloneV::FFT1L, + CycloneV::FFB0, CycloneV::FFB1, CycloneV::FFB1L}; + for (int i = 0; i < 6; i++) { + if (ctx->wires_connected(alm_data.comb_out[i / 3], ctx->get_port(CycloneV::LAB, CycloneV::pos2x(pos), + CycloneV::pos2y(pos), alm, mux_port[i]))) + cv->bmux_m_set(CycloneV::LAB, pos, mux_settings[i], alm, CycloneV::NLUT); + } + + bool is_carry = (luts[0] && luts[0]->combInfo.is_carry) || (luts[1] && luts[1]->combInfo.is_carry); + if (is_carry) + cv->bmux_m_set(CycloneV::LAB, pos, CycloneV::ARITH_SEL, alm, CycloneV::ADDER); + // The carry in/out enable bits + if (is_carry && alm == 0 && !luts[0]->combInfo.carry_start) + cv->bmux_b_set(CycloneV::LAB, pos, CycloneV::TTO_DIS, 0, true); + if (is_carry && alm == 5) + cv->bmux_b_set(CycloneV::LAB, pos, CycloneV::BTO_DIS, 0, true); + // Flipflop configuration + const std::array<CycloneV::bmux_type_t, 2> ef_sel{CycloneV::TEF_SEL, CycloneV::BEF_SEL}; + // This isn't a typo; the *PKREG* bits really are mirrored. + const std::array<CycloneV::bmux_type_t, 4> pkreg{CycloneV::TPKREG1, CycloneV::TPKREG0, CycloneV::BPKREG1, + CycloneV::BPKREG0}; + + const std::array<CycloneV::bmux_type_t, 2> clk_sel{CycloneV::TCLK_SEL, CycloneV::BCLK_SEL}, + clr_sel{CycloneV::TCLR_SEL, CycloneV::BCLR_SEL}, sclr_dis{CycloneV::TSCLR_DIS, CycloneV::BSCLR_DIS}, + sload_en{CycloneV::TSLOAD_EN, CycloneV::BSLOAD_EN}; + + const std::array<CycloneV::bmux_type_t, 3> clk_choice{CycloneV::CLK0, CycloneV::CLK1, CycloneV::CLK2}; + + const std::array<CycloneV::bmux_type_t, 3> clk_inv{CycloneV::CLK0_INV, CycloneV::CLK1_INV, CycloneV::CLK2_INV}, + en_en{CycloneV::EN0_EN, CycloneV::EN1_EN, CycloneV::EN2_EN}, + en_ninv{CycloneV::EN0_NINV, CycloneV::EN1_NINV, CycloneV::EN2_NINV}; + const std::array<CycloneV::bmux_type_t, 2> aclr_inv{CycloneV::ACLR0_INV, CycloneV::ACLR1_INV}; + + for (int i = 0; i < 2; i++) { + // EF selection mux + if (ctx->wires_connected(ctx->getBelPinWire(alm_data.lut_bels[i], i ? id_F0 : id_F1), alm_data.sel_ef[i])) + cv->bmux_m_set(CycloneV::LAB, pos, ef_sel[i], alm, CycloneV::bmux_type_t::F); + } + + for (int i = 0; i < 4; i++) { + CellInfo *ff = ffs[i]; + if (!ff) + continue; + // PKREG (input selection) + if (ctx->wires_connected(alm_data.sel_ef[i / 2], alm_data.ff_in[i])) + cv->bmux_b_set(CycloneV::LAB, pos, pkreg[i], alm, true); + // Control set + // CLK+ENA + int ce_idx = alm_data.clk_ena_idx[i / 2]; + cv->bmux_m_set(CycloneV::LAB, pos, clk_sel[i / 2], alm, clk_choice[ce_idx]); + if (ff->ffInfo.ctrlset.clk.inverted) + cv->bmux_b_set(CycloneV::LAB, pos, clk_inv[ce_idx], 0, true); + if (get_net_or_empty(ff, id_ENA) != nullptr) { // not using ffInfo.ctrlset, this has a fake net always to + // ensure different constants don't collide + cv->bmux_b_set(CycloneV::LAB, pos, en_en[ce_idx], 0, true); + cv->bmux_b_set(CycloneV::LAB, pos, en_ninv[ce_idx], 0, ff->ffInfo.ctrlset.ena.inverted); + } else { + cv->bmux_b_set(CycloneV::LAB, pos, en_en[ce_idx], 0, false); + } + // ACLR + int aclr_idx = alm_data.aclr_idx[i / 2]; + cv->bmux_b_set(CycloneV::LAB, pos, clr_sel[i / 2], alm, aclr_idx == 1); + if (ff->ffInfo.ctrlset.aclr.inverted) + cv->bmux_b_set(CycloneV::LAB, pos, aclr_inv[aclr_idx], 0, true); + // SCLR + if (ff->ffInfo.ctrlset.sclr.net != nullptr) { + cv->bmux_b_set(CycloneV::LAB, pos, CycloneV::SCLR_INV, 0, ff->ffInfo.ctrlset.sclr.inverted); + } else { + cv->bmux_b_set(CycloneV::LAB, pos, sclr_dis[i / 2], alm, true); + } + // SLOAD + if (ff->ffInfo.ctrlset.sload.net != nullptr) { + cv->bmux_b_set(CycloneV::LAB, pos, sload_en[i / 2], alm, true); + cv->bmux_b_set(CycloneV::LAB, pos, CycloneV::SLOAD_INV, 0, ff->ffInfo.ctrlset.sload.inverted); + } + } + return true; + } + + void write_ff_routing(uint32_t lab) + { + auto &lab_data = ctx->labs.at(lab); + auto pos = lab_data.alms.at(0).lut_bels[0].pos; + + const std::array<CycloneV::bmux_type_t, 2> aclr_inp{CycloneV::ACLR0_SEL, CycloneV::ACLR1_SEL}; + for (int i = 0; i < 2; i++) { + // Quartus seems to set unused ACLRs to CLKI2... + if (!lab_data.aclr_used[i]) + cv->bmux_m_set(CycloneV::LAB, pos, aclr_inp[i], 0, CycloneV::CLKI2); + else + cv->bmux_m_set(CycloneV::LAB, pos, aclr_inp[i], 0, (i == 1) ? CycloneV::GIN0 : CycloneV::GIN1); + } + for (int i = 0; i < 3; i++) { + // Check for fabric->clock routing + if (ctx->wires_connected(ctx->get_port(CycloneV::LAB, CycloneV::pos2x(pos), CycloneV::pos2y(pos), -1, + CycloneV::DATAIN, 0), + lab_data.clk_wires[i])) + cv->bmux_m_set(CycloneV::LAB, pos, CycloneV::CLKA_SEL, 0, CycloneV::GIN2); + } + } + + void write_labs() + { + for (size_t lab = 0; lab < ctx->labs.size(); lab++) { + bool used = false; + for (uint8_t alm = 0; alm < 10; alm++) + used |= write_alm(lab, alm); + if (used) + write_ff_routing(lab); + } + } + + void run() + { + cv->clear(); + init(); + write_routing(); + write_dqs(); + write_cells(); + write_labs(); + } +}; +} // namespace + +void Arch::build_bitstream() +{ + MistralBitgen gen(getCtx()); + gen.run(); +} + +NEXTPNR_NAMESPACE_END diff --git a/mistral/constids.inc b/mistral/constids.inc new file mode 100644 index 00000000..6bb45c3c --- /dev/null +++ b/mistral/constids.inc @@ -0,0 +1,78 @@ +X(MISTRAL_COMB) +X(MISTRAL_FF) +X(LAB) +X(MLAB) + +X(MISTRAL_IO) +X(I) +X(O) +X(OE) +X(PAD) +X(MISTRAL_IB) +X(MISTRAL_OB) + +X(A) +X(B) +X(C) +X(D) +X(E) +X(F) +X(E0) +X(F0) +X(E1) +X(F1) +X(DATAIN) +X(FFT0) +X(FFT1) +X(FFB0) +X(FFB1) +X(FFT1L) +X(FFB1L) +X(CLKIN) +X(ACLR) +X(CLK) +X(ENA) +X(SCLR) +X(SLOAD) +X(SDATA) +X(Q) + +X(COMBOUT) +X(SUM_OUT) +X(CI) +X(SHAREIN) +X(CO) +X(SHAREOUT) + +X(CARRY_START) +X(SHARE_START) +X(CARRY_END) +X(SHARE_END) + +X(MISTRAL_ALUT6) +X(MISTRAL_ALUT5) +X(MISTRAL_ALUT4) +X(MISTRAL_ALUT3) +X(MISTRAL_ALUT2) +X(MISTRAL_NOT) +X(MISTRAL_BUF) +X(MISTRAL_CONST) +X(MISTRAL_ALUT_ARITH) +X(LUT) +X(LUT0) +X(LUT1) + +X(D0) +X(D1) +X(SO) + +X(WIRE) + +X(GND) +X(VCC) +X(Y) + +X(LOC) + +X(MISTRAL_CLKENA) +X(MISTRAL_CLKBUF) diff --git a/mistral/family.cmake b/mistral/family.cmake new file mode 100644 index 00000000..79681ba8 --- /dev/null +++ b/mistral/family.cmake @@ -0,0 +1,14 @@ +set(MISTRAL_ROOT "" CACHE STRING "Mistral install path") + +aux_source_directory(${MISTRAL_ROOT}/lib MISTRAL_LIB_FILES) +add_library(mistral STATIC ${MISTRAL_LIB_FILES}) +target_compile_options(mistral PRIVATE -Wno-maybe-uninitialized) + +find_package(LibLZMA REQUIRED) + +foreach(family_target ${family_targets}) + target_include_directories(${family_target} PRIVATE ${MISTRAL_ROOT}/lib ${LIBLZMA_INCLUDE_DIRS}) + target_link_libraries(${family_target} PRIVATE mistral ${LIBLZMA_LIBRARIES}) + # Currently required to avoid issues with mistral (LTO means the warnings can end up in nextpnr) + target_link_options(${family_target} PRIVATE -Wno-maybe-uninitialized) +endforeach() diff --git a/mistral/globals.cc b/mistral/globals.cc new file mode 100644 index 00000000..3203893b --- /dev/null +++ b/mistral/globals.cc @@ -0,0 +1,46 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021 gatecat <gatecat@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 "log.h" +#include "nextpnr.h" +#include "util.h" + +NEXTPNR_NAMESPACE_BEGIN + +void Arch::create_clkbuf(int x, int y) +{ + for (int z = 0; z < 4; z++) { + if (z != 2) + continue; // TODO: why do other Zs not work? + // For now we only consider the input path from general routing, other inputs like dedicated clock pins are + // still a TODO + BelId bel = add_bel(x, y, id(stringf("CLKBUF[%d]", z)), id_MISTRAL_CLKENA); + add_bel_pin(bel, id_A, PORT_IN, get_port(CycloneV::CMUXHG, x, y, -1, CycloneV::CLKIN, z)); + add_bel_pin(bel, id_Q, PORT_OUT, get_port(CycloneV::CMUXHG, x, y, z, CycloneV::CLKOUT)); + // TODO: enable pin + bel_data(bel).block_index = z; + } +} + +bool Arch::is_clkbuf_cell(IdString cell_type) const +{ + return cell_type == id_MISTRAL_CLKENA || cell_type == id_MISTRAL_CLKBUF; +} + +NEXTPNR_NAMESPACE_END diff --git a/mistral/io.cc b/mistral/io.cc new file mode 100644 index 00000000..3a72b001 --- /dev/null +++ b/mistral/io.cc @@ -0,0 +1,61 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021 gatecat <gatecat@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 "log.h" +#include "nextpnr.h" +#include "util.h" + +NEXTPNR_NAMESPACE_BEGIN + +void Arch::create_gpio(int x, int y) +{ + for (int z = 0; z < 4; z++) { + // Notional pad wire + WireId pad = add_wire(x, y, id(stringf("PAD[%d]", z))); + BelId bel = add_bel(x, y, id(stringf("IO[%d]", z)), id_MISTRAL_IO); + add_bel_pin(bel, id_PAD, PORT_INOUT, pad); + // FIXME: is the port index of zero always correct? + add_bel_pin(bel, id_I, PORT_IN, get_port(CycloneV::GPIO, x, y, z, CycloneV::DATAIN, 0)); + add_bel_pin(bel, id_OE, PORT_IN, get_port(CycloneV::GPIO, x, y, z, CycloneV::OEIN, 0)); + add_bel_pin(bel, id_O, PORT_OUT, get_port(CycloneV::GPIO, x, y, z, CycloneV::DATAOUT, 0)); + bel_data(bel).block_index = z; + } +} + +bool Arch::is_io_cell(IdString cell_type) const +{ + // Return true if a cell is an IO buffer cell type + switch (cell_type.index) { + case ID_MISTRAL_IB: + case ID_MISTRAL_OB: + case ID_MISTRAL_IO: + return true; + default: + return false; + } +} + +BelId Arch::get_io_pin_bel(const CycloneV::pin_info_t *pin) const +{ + auto pad = pin->pad; + CycloneV::pos_t pos = (pad & 0x3FFF); + return bel_by_block_idx(CycloneV::pos2x(pos), CycloneV::pos2y(pos), id_MISTRAL_IO, (pad >> 14)); +} + +NEXTPNR_NAMESPACE_END diff --git a/mistral/lab.cc b/mistral/lab.cc new file mode 100644 index 00000000..abd0fec3 --- /dev/null +++ b/mistral/lab.cc @@ -0,0 +1,969 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021 gatecat <gatecat@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 "design_utils.h" +#include "log.h" +#include "nextpnr.h" +#include "util.h" + +NEXTPNR_NAMESPACE_BEGIN + +// This file contains functions related to our custom LAB structure, including creating the LAB bels; checking the +// legality of LABs; and manipulating LUT inputs and equations + +// LAB/ALM structure creation functions +namespace { +static void create_alm(Arch *arch, int x, int y, int z, uint32_t lab_idx) +{ + auto &lab = arch->labs.at(lab_idx); + auto &alm = lab.alms.at(z); + // Create the combinational part of ALMs. + // There are two of these, for the two LUT outputs, and these also contain the carry chain and associated logic + // Each one has all 8 ALM inputs as input pins. In many cases only a subset of these are used; depending on mode; + // and the bel-cell pin mappings are used to handle this post-placement without losing flexibility + for (int i = 0; i < 2; i++) { + // Carry/share wires are a bit tricky due to all the different permutations + WireId carry_in, share_in; + WireId carry_out, share_out; + if (z == 0 && i == 0) { + carry_in = arch->add_wire(x, y, id_CI); + share_in = arch->add_wire(x, y, id_SHAREIN); + if (y < (arch->getGridDimY() - 1)) { + // Carry is split at tile boundary (TTO_DIS bit), add a PIP to represent this. + // TODO: what about BTO_DIS, in the middle of the LAB? + arch->add_pip(arch->add_wire(x, y + 1, id_CO), carry_in); + arch->add_pip(arch->add_wire(x, y + 1, id_SHAREOUT), share_in); + } + } else { + // Output from last combinational unit + carry_in = arch->add_wire(x, y, arch->id(stringf("CARRY[%d]", (z * 2 + i) - 1))); + share_in = arch->add_wire(x, y, arch->id(stringf("SHARE[%d]", (z * 2 + i) - 1))); + } + + if (z == 9 && i == 1) { + carry_out = arch->add_wire(x, y, id_CO); + share_out = arch->add_wire(x, y, id_SHAREOUT); + } else { + carry_out = arch->add_wire(x, y, arch->id(stringf("CARRY[%d]", z * 2 + i))); + share_out = arch->add_wire(x, y, arch->id(stringf("SHARE[%d]", z * 2 + i))); + } + + BelId bel = arch->add_bel(x, y, arch->id(stringf("ALM%d_COMB%d", z, i)), id_MISTRAL_COMB); + // LUT/MUX inputs + arch->add_bel_pin(bel, id_A, PORT_IN, arch->get_port(CycloneV::LAB, x, y, z, CycloneV::A)); + arch->add_bel_pin(bel, id_B, PORT_IN, arch->get_port(CycloneV::LAB, x, y, z, CycloneV::B)); + arch->add_bel_pin(bel, id_C, PORT_IN, arch->get_port(CycloneV::LAB, x, y, z, CycloneV::C)); + arch->add_bel_pin(bel, id_D, PORT_IN, arch->get_port(CycloneV::LAB, x, y, z, CycloneV::D)); + arch->add_bel_pin(bel, id_E0, PORT_IN, arch->get_port(CycloneV::LAB, x, y, z, CycloneV::E0)); + arch->add_bel_pin(bel, id_E1, PORT_IN, arch->get_port(CycloneV::LAB, x, y, z, CycloneV::E1)); + arch->add_bel_pin(bel, id_F0, PORT_IN, arch->get_port(CycloneV::LAB, x, y, z, CycloneV::F0)); + arch->add_bel_pin(bel, id_F1, PORT_IN, arch->get_port(CycloneV::LAB, x, y, z, CycloneV::F1)); + // Carry/share chain + arch->add_bel_pin(bel, id_CI, PORT_IN, carry_in); + arch->add_bel_pin(bel, id_SHAREIN, PORT_IN, share_in); + arch->add_bel_pin(bel, id_CO, PORT_OUT, carry_out); + arch->add_bel_pin(bel, id_SHAREOUT, PORT_OUT, share_out); + // Combinational output + alm.comb_out[i] = arch->add_wire(x, y, arch->id(stringf("COMBOUT[%d]", z * 2 + i))); + arch->add_bel_pin(bel, id_COMBOUT, PORT_OUT, alm.comb_out[i]); + // Assign indexing + alm.lut_bels.at(i) = bel; + auto &b = arch->bel_data(bel); + b.lab_data.lab = lab_idx; + b.lab_data.alm = z; + b.lab_data.idx = i; + } + // Create the control set and E/F selection - which is per pair of FF + for (int i = 0; i < 2; i++) { + // Wires + alm.sel_clk[i] = arch->add_wire(x, y, arch->id(stringf("CLK%c[%d]", i ? 'B' : 'T', z))); + alm.sel_ena[i] = arch->add_wire(x, y, arch->id(stringf("ENA%c[%d]", i ? 'B' : 'T', z))); + alm.sel_aclr[i] = arch->add_wire(x, y, arch->id(stringf("ACLR%c[%d]", i ? 'B' : 'T', z))); + alm.sel_ef[i] = arch->add_wire(x, y, arch->id(stringf("%cEF[%d]", i ? 'B' : 'T', z))); + // Muxes - three CLK/ENA per LAB, two ACLR + for (int j = 0; j < 3; j++) { + arch->add_pip(lab.clk_wires[j], alm.sel_clk[i]); + arch->add_pip(lab.ena_wires[j], alm.sel_ena[i]); + if (j < 2) + arch->add_pip(lab.aclr_wires[j], alm.sel_aclr[i]); + } + // E/F pips + // Note that the F choice is mirrored, F from the other half is picked + arch->add_pip(arch->get_port(CycloneV::LAB, x, y, z, i ? CycloneV::E1 : CycloneV::E0), alm.sel_ef[i]); + arch->add_pip(arch->get_port(CycloneV::LAB, x, y, z, i ? CycloneV::F0 : CycloneV::F1), alm.sel_ef[i]); + } + + // Create the flipflops and associated routing + const CycloneV::port_type_t outputs[4] = {CycloneV::FFT0, CycloneV::FFT1, CycloneV::FFB0, CycloneV::FFB1}; + const CycloneV::port_type_t l_outputs[4] = {CycloneV::FFT1L, CycloneV::FFB1L}; + + for (int i = 0; i < 4; i++) { + // FF input, selected by *PKREG* + alm.ff_in[i] = arch->add_wire(x, y, arch->id(stringf("FFIN[%d]", (z * 4) + i))); + arch->add_pip(alm.comb_out[i / 2], alm.ff_in[i]); + arch->add_pip(alm.sel_ef[i / 2], alm.ff_in[i]); + // FF bel + BelId bel = arch->add_bel(x, y, arch->id(stringf("ALM%d_FF%d", z, i)), id_MISTRAL_FF); + arch->add_bel_pin(bel, id_CLK, PORT_IN, alm.sel_clk[i / 2]); + arch->add_bel_pin(bel, id_ENA, PORT_IN, alm.sel_ena[i / 2]); + arch->add_bel_pin(bel, id_ACLR, PORT_IN, alm.sel_aclr[i / 2]); + arch->add_bel_pin(bel, id_SCLR, PORT_IN, lab.sclr_wire); + arch->add_bel_pin(bel, id_SLOAD, PORT_IN, lab.sload_wire); + arch->add_bel_pin(bel, id_DATAIN, PORT_IN, alm.ff_in[i]); + arch->add_bel_pin(bel, id_SDATA, PORT_IN, alm.sel_ef[i / 2]); + + // FF output + alm.ff_out[i] = arch->add_wire(x, y, arch->id(stringf("FFOUT[%d]", (z * 4) + i))); + arch->add_bel_pin(bel, id_Q, PORT_OUT, alm.ff_out[i]); + // Output mux (*DFF*) + WireId out = arch->get_port(CycloneV::LAB, x, y, z, outputs[i]); + arch->add_pip(alm.ff_out[i], out); + arch->add_pip(alm.comb_out[i / 2], out); + // 'L' output mux where applicable + if (i == 1 || i == 3) { + WireId l_out = arch->get_port(CycloneV::LAB, x, y, z, l_outputs[i / 2]); + arch->add_pip(alm.ff_out[i], l_out); + arch->add_pip(alm.comb_out[i / 2], l_out); + } + + lab.alms.at(z).ff_bels.at(i) = bel; + auto &b = arch->bel_data(bel); + b.lab_data.lab = lab_idx; + b.lab_data.alm = z; + b.lab_data.idx = i; + } +} +} // namespace + +void Arch::create_lab(int x, int y) +{ + uint32_t lab_idx = labs.size(); + labs.emplace_back(); + + auto &lab = labs.back(); + + // Create common control set configuration. This is actually a subset of what's possible, but errs on the side of + // caution due to incomplete documentation + + // Clocks - hardcode to CLKA choices, as both CLKA and CLKB coming from general routing causes unexpected + // permutations + for (int i = 0; i < 3; i++) { + lab.clk_wires[i] = add_wire(x, y, id(stringf("CLK%d", i))); + add_pip(get_port(CycloneV::LAB, x, y, -1, CycloneV::CLKIN, 0), lab.clk_wires[i]); // dedicated routing + add_pip(get_port(CycloneV::LAB, x, y, -1, CycloneV::DATAIN, 0), lab.clk_wires[i]); // general routing + } + + // Enables - while it looks from the config like there are choices for these, it seems like EN0_SEL actually selects + // SCLR not ENA0 and EN1_SEL actually selects SLOAD? + lab.ena_wires[0] = get_port(CycloneV::LAB, x, y, -1, CycloneV::DATAIN, 2); + lab.ena_wires[1] = get_port(CycloneV::LAB, x, y, -1, CycloneV::DATAIN, 3); + lab.ena_wires[2] = get_port(CycloneV::LAB, x, y, -1, CycloneV::DATAIN, 0); + + // ACLRs - only consider general routing for now + lab.aclr_wires[0] = get_port(CycloneV::LAB, x, y, -1, CycloneV::DATAIN, 3); + lab.aclr_wires[1] = get_port(CycloneV::LAB, x, y, -1, CycloneV::DATAIN, 2); + + // SCLR and SLOAD - as above it seems like these might be selectable using the "EN*_SEL" bits but play it safe for + // now + lab.sclr_wire = get_port(CycloneV::LAB, x, y, -1, CycloneV::DATAIN, 3); + lab.sload_wire = get_port(CycloneV::LAB, x, y, -1, CycloneV::DATAIN, 1); + + for (int i = 0; i < 10; i++) { + create_alm(this, x, y, i, lab_idx); + } +} + +// Cell handling and annotation functions +namespace { +ControlSig get_ctrlsig(const Context *ctx, const CellInfo *cell, IdString port, bool explicit_const = false) +{ + ControlSig result; + result.net = get_net_or_empty(cell, port); + if (result.net == nullptr && explicit_const) { + // For ENA, 1 (and 0) are explicit control set choices even though they aren't routed, as "no ENA" still + // consumes a clock+ENA pair + CellPinState st = PIN_1; + result.net = ctx->nets.at((st == PIN_1) ? ctx->id("$PACKER_VCC_NET") : ctx->id("$PACKER_GND_NET")).get(); + } + if (cell->pin_data.count(port)) + result.inverted = cell->pin_data.at(port).state == PIN_INV; + else + result.inverted = false; + return result; +} +} // namespace + +bool Arch::is_comb_cell(IdString cell_type) const +{ + // Return true if a cell is a combinational cell type, to be a placed at a MISTRAL_COMB location + switch (cell_type.index) { + case ID_MISTRAL_ALUT6: + case ID_MISTRAL_ALUT5: + case ID_MISTRAL_ALUT4: + case ID_MISTRAL_ALUT3: + case ID_MISTRAL_ALUT2: + case ID_MISTRAL_NOT: + case ID_MISTRAL_CONST: + case ID_MISTRAL_ALUT_ARITH: + return true; + default: + return false; + } +} + +void Arch::assign_comb_info(CellInfo *cell) const +{ + cell->combInfo.is_carry = false; + cell->combInfo.is_shared = false; + cell->combInfo.is_extended = false; + cell->combInfo.carry_start = false; + cell->combInfo.carry_end = false; + cell->combInfo.chain_shared_input_count = 0; + + if (cell->type == id_MISTRAL_ALUT_ARITH) { + cell->combInfo.is_carry = true; + cell->combInfo.lut_input_count = 5; + cell->combInfo.lut_bits_count = 32; + + // This is a special case in terms of naming + const std::array<IdString, 5> arith_pins{id_A, id_B, id_C, id_D0, id_D1}; + { + int i = 0; + for (auto pin : arith_pins) { + cell->combInfo.lut_in[i++] = get_net_or_empty(cell, pin); + } + } + + const NetInfo *ci = get_net_or_empty(cell, id_CI); + const NetInfo *co = get_net_or_empty(cell, id_CO); + + cell->combInfo.comb_out = get_net_or_empty(cell, id_SO); + cell->combInfo.carry_start = (ci == nullptr) || (ci->driver.cell == nullptr); + cell->combInfo.carry_end = (co == nullptr) || (co->users.empty()); + + // Compute cross-ALM routing sharing - only check the z=0 case inside ALMs + if (cell->constr_z > 0 && ((cell->constr_z % 2) == 0) && ci) { + const CellInfo *prev = ci->driver.cell; + if (prev != nullptr) { + for (int i = 0; i < 5; i++) { + const NetInfo *a = get_net_or_empty(cell, arith_pins[i]); + if (a == nullptr) + continue; + const NetInfo *b = get_net_or_empty(prev, arith_pins[i]); + if (a == b) + ++cell->combInfo.chain_shared_input_count; + } + } + } + + } else { + cell->combInfo.lut_input_count = 0; + switch (cell->type.index) { + case ID_MISTRAL_ALUT6: + ++cell->combInfo.lut_input_count; + cell->combInfo.lut_in[5] = get_net_or_empty(cell, id_F); + [[fallthrough]]; + case ID_MISTRAL_ALUT5: + ++cell->combInfo.lut_input_count; + cell->combInfo.lut_in[4] = get_net_or_empty(cell, id_E); + [[fallthrough]]; + case ID_MISTRAL_ALUT4: + ++cell->combInfo.lut_input_count; + cell->combInfo.lut_in[3] = get_net_or_empty(cell, id_D); + [[fallthrough]]; + case ID_MISTRAL_ALUT3: + ++cell->combInfo.lut_input_count; + cell->combInfo.lut_in[2] = get_net_or_empty(cell, id_C); + [[fallthrough]]; + case ID_MISTRAL_ALUT2: + ++cell->combInfo.lut_input_count; + cell->combInfo.lut_in[1] = get_net_or_empty(cell, id_B); + [[fallthrough]]; + case ID_MISTRAL_BUF: // used to route through to FFs etc + case ID_MISTRAL_NOT: // used for inverters that map to LUTs + ++cell->combInfo.lut_input_count; + cell->combInfo.lut_in[0] = get_net_or_empty(cell, id_A); + [[fallthrough]]; + case ID_MISTRAL_CONST: + // MISTRAL_CONST is a nextpnr-inserted cell type for 0-input, constant-generating LUTs + break; + default: + log_error("unexpected combinational cell type %s\n", getCtx()->nameOf(cell->type)); + } + // Note that this relationship won't hold for extended mode, when that is supported + cell->combInfo.lut_bits_count = (1 << cell->combInfo.lut_input_count); + } + cell->combInfo.used_lut_input_count = 0; + for (int i = 0; i < cell->combInfo.lut_input_count; i++) + if (cell->combInfo.lut_in[i]) + ++cell->combInfo.used_lut_input_count; +} + +void Arch::assign_ff_info(CellInfo *cell) const +{ + cell->ffInfo.ctrlset.clk = get_ctrlsig(getCtx(), cell, id_CLK); + cell->ffInfo.ctrlset.ena = get_ctrlsig(getCtx(), cell, id_ENA, true); + cell->ffInfo.ctrlset.aclr = get_ctrlsig(getCtx(), cell, id_ACLR); + cell->ffInfo.ctrlset.sclr = get_ctrlsig(getCtx(), cell, id_SCLR); + cell->ffInfo.ctrlset.sload = get_ctrlsig(getCtx(), cell, id_SLOAD); + // If SCLR is used, but SLOAD isn't, then it seems like we need to pretend as if SLOAD is connected GND (so set + // [BT]SLOAD_EN inside the ALMs, and clear SLOAD_INV) + if (cell->ffInfo.ctrlset.sclr.net != nullptr && cell->ffInfo.ctrlset.sload.net == nullptr) { + cell->ffInfo.ctrlset.sload.net = nets.at(id("$PACKER_GND_NET")).get(); + cell->ffInfo.ctrlset.sload.inverted = false; + } + + cell->ffInfo.sdata = get_net_or_empty(cell, id_SDATA); + cell->ffInfo.datain = get_net_or_empty(cell, id_DATAIN); +} + +// Validity checking functions +bool Arch::is_alm_legal(uint32_t lab, uint8_t alm) const +{ + auto &alm_data = labs.at(lab).alms.at(alm); + // Get cells into an array for fast access + std::array<const CellInfo *, 2> luts{getBoundBelCell(alm_data.lut_bels[0]), getBoundBelCell(alm_data.lut_bels[1])}; + std::array<const CellInfo *, 4> ffs{getBoundBelCell(alm_data.ff_bels[0]), getBoundBelCell(alm_data.ff_bels[1]), + getBoundBelCell(alm_data.ff_bels[2]), getBoundBelCell(alm_data.ff_bels[3])}; + int used_lut_bits = 0; + + int total_lut_inputs = 0; + // TODO: for more complex modes like extended/arithmetic, it might not always be possible for any LUT input to map + // to any of the ALM half inputs particularly shared and extended mode will need more thought and probably for this + // to be revisited + for (int i = 0; i < 2; i++) { + if (!luts[i]) + continue; + total_lut_inputs += luts[i]->combInfo.lut_input_count; + used_lut_bits += luts[i]->combInfo.lut_bits_count; + } + // An ALM only has 64 bits of storage. In theory some of these cases might be legal because of overlap between the + // two functions, but the current placer is unlikely to stumble upon these cases frequently without anything to + // guide it, and the cost of checking them here almost certainly outweighs any marginal benefit in supporting them, + // at least for now. + if (used_lut_bits > 64) + return false; + + if (total_lut_inputs > 8) { + NPNR_ASSERT(luts[0] && luts[1]); // something has gone badly wrong if this fails! + // Make sure that LUT inputs are not overprovisioned + int shared_lut_inputs = 0; + // Even though this N^2 search looks inefficient, it's unlikely a set lookup or similar is going to be much + // better given the low N. + for (int i = 0; i < luts[1]->combInfo.lut_input_count; i++) { + const NetInfo *sig = luts[1]->combInfo.lut_in[i]; + for (int j = 0; j < luts[0]->combInfo.lut_input_count; j++) { + if (sig == luts[0]->combInfo.lut_in[j]) { + ++shared_lut_inputs; + break; + } + } + } + if ((total_lut_inputs - shared_lut_inputs) > 8) + return false; + } + + bool carry_mode = false; + + // No mixing of carry and non-carry + if (luts[0] && luts[1] && luts[0]->combInfo.is_carry != luts[1]->combInfo.is_carry) + return false; + + // For each ALM half; check FF control set sharing and input routeability + for (int i = 0; i < 2; i++) { + // There are two ways to route from the fabric into FF data - either routing through a LUT or using the E/F + // signals and SLOAD=1 (*PKREF*) + bool route_thru_lut_avail = !luts[i] && !carry_mode && (total_lut_inputs < 8) && (used_lut_bits < 64); + // E/F is available if this LUT is using 3 or fewer inputs - this is conservative and sharing can probably + // improve this situation. (1 - i) because the F input to EF_SEL is mirrored. + bool ef_available = (!luts[1 - i] || (luts[1 - i]->combInfo.used_lut_input_count <= 2)); + // Control set checking + bool found_ff = false; + + FFControlSet ctrlset; + for (int j = 0; j < 2; j++) { + const CellInfo *ff = ffs[i * 2 + j]; + if (!ff) + continue; + if (j == 1) + return false; // TODO: why are these FFs broken? + if (found_ff) { + // Two FFs in the same half with an incompatible control set + if (ctrlset != ff->ffInfo.ctrlset) + return false; + } else { + ctrlset = ff->ffInfo.ctrlset; + } + // SDATA must use the E/F input + // TODO: rare case of two FFs with the same SDATA in the same ALM half + if (ff->ffInfo.sdata) { + if (!ef_available) + return false; + ef_available = false; + } + // Find a way of routing the input through fabric, if it's not driven by the LUT + if (ff->ffInfo.datain && (!luts[i] || (ff->ffInfo.datain != luts[i]->combInfo.comb_out))) { + if (route_thru_lut_avail) + route_thru_lut_avail = false; + else if (ef_available) + ef_available = false; + else + return false; + } + found_ff = true; + } + } + + return true; +} + +void Arch::update_alm_input_count(uint32_t lab, uint8_t alm) +{ + // TODO: duplication with above + auto &alm_data = labs.at(lab).alms.at(alm); + // Get cells into an array for fast access + std::array<const CellInfo *, 2> luts{getBoundBelCell(alm_data.lut_bels[0]), getBoundBelCell(alm_data.lut_bels[1])}; + std::array<const CellInfo *, 4> ffs{getBoundBelCell(alm_data.ff_bels[0]), getBoundBelCell(alm_data.ff_bels[1]), + getBoundBelCell(alm_data.ff_bels[2]), getBoundBelCell(alm_data.ff_bels[3])}; + int total_inputs = 0; + int total_lut_inputs = 0; + for (int i = 0; i < 2; i++) { + if (!luts[i]) + continue; + total_lut_inputs += luts[i]->combInfo.used_lut_input_count - luts[i]->combInfo.chain_shared_input_count; + } + int shared_lut_inputs = 0; + if (luts[0] && luts[1]) { + for (int i = 0; i < luts[1]->combInfo.lut_input_count; i++) { + const NetInfo *sig = luts[1]->combInfo.lut_in[i]; + if (!sig) + continue; + for (int j = 0; j < luts[0]->combInfo.lut_input_count; j++) { + if (sig == luts[0]->combInfo.lut_in[j]) { + ++shared_lut_inputs; + break; + } + } + if (shared_lut_inputs >= 2) { + // only 2 inputs have guaranteed sharing, without routeability based LUT permutation at least + break; + } + } + } + total_inputs = std::max(0, total_lut_inputs - shared_lut_inputs); + for (int i = 0; i < 4; i++) { + const CellInfo *ff = ffs[i]; + if (!ff) + continue; + if (ff->ffInfo.sdata) + ++total_inputs; + // FF input doesn't consume routing resources if driven by associated LUT + if (ff->ffInfo.datain && (!luts[i / 2] || ff->ffInfo.datain != luts[i / 2]->combInfo.comb_out)) + ++total_inputs; + } + alm_data.unique_input_count = total_inputs; +} + +bool Arch::check_lab_input_count(uint32_t lab) const +{ + // There are only 46 TD signals available to route signals from general routing to the ALM input. Currently, we + // check the total sum of ALM inputs is less than 42; 46 minus 4 FF control inputs. This is a conservative check for + // several reasons, because LD signals are also available for feedback routing from ALM output to input, and because + // TD signals may be shared if the same net routes to multiple ALMs. But these cases will need careful handling and + // LUT permutation during routing to be useful; and in any event conservative LAB packing will help nextpnr's + // currently perfunctory place and route algorithms to achieve satisfactory runtimes. + int count = 0; + auto &lab_data = labs.at(lab); + for (int i = 0; i < 10; i++) { + count += lab_data.alms.at(i).unique_input_count; + } + return (count <= 42); +} + +namespace { +bool check_assign_sig(ControlSig &sig_set, const ControlSig &sig) +{ + if (sig.net == nullptr) { + return true; + } else if (sig_set == sig) { + return true; + } else if (sig_set.net == nullptr) { + sig_set = sig; + return true; + } else { + return false; + } +}; + +template <size_t N> bool check_assign_sig(std::array<ControlSig, N> &sig_set, const ControlSig &sig) +{ + if (sig.net == nullptr) + return true; + for (size_t i = 0; i < N; i++) + if (sig_set[i] == sig) { + return true; + } else if (sig_set[i].net == nullptr) { + sig_set[i] = sig; + return true; + } + return false; +}; + +// DATAIN mapping rules - which LAB DATAIN signals can be used for ENA and ACLR +static constexpr std::array<int, 3> ena_datain{2, 3, 0}; +static constexpr std::array<int, 2> aclr_datain{3, 2}; + +struct LabCtrlSetWorker +{ + + ControlSig clk{}, sload{}, sclr{}; + std::array<ControlSig, 2> aclr{}; + std::array<ControlSig, 3> ena{}; + + std::array<ControlSig, 4> datain{}; + + bool run(const Arch *arch, uint32_t lab) + { + // Strictly speaking the constraint is up to 2 unique CLK and 3 CLK+ENA pairs. For now we simplify this to 1 CLK + // and 3 ENA though. + for (uint8_t alm = 0; alm < 10; alm++) { + for (uint8_t i = 0; i < 4; i++) { + const CellInfo *ff = arch->getBoundBelCell(arch->labs.at(lab).alms.at(alm).ff_bels.at(i)); + if (ff == nullptr) + continue; + + if (!check_assign_sig(clk, ff->ffInfo.ctrlset.clk)) + return false; + if (!check_assign_sig(sload, ff->ffInfo.ctrlset.sload)) + return false; + if (!check_assign_sig(sclr, ff->ffInfo.ctrlset.sclr)) + return false; + if (!check_assign_sig(aclr, ff->ffInfo.ctrlset.aclr)) + return false; + if (!check_assign_sig(ena, ff->ffInfo.ctrlset.ena)) + return false; + } + } + // Check for overuse of the shared, LAB-wide datain signals + if (clk.net != nullptr && !clk.net->is_global) + if (!check_assign_sig(datain[0], clk)) // CLK only needs DATAIN[0] if it's not global + return false; + if (!check_assign_sig(datain[1], sload)) + return false; + if (!check_assign_sig(datain[3], sclr)) + return false; + for (const auto &aclr_sig : aclr) { + // Check both possibilities that ACLR can map to + // TODO: ACLR could be global, too + if (check_assign_sig(datain[aclr_datain[0]], aclr_sig)) + continue; + if (check_assign_sig(datain[aclr_datain[1]], aclr_sig)) + continue; + // Failed to find any free ACLR-capable DATAIN + return false; + } + for (const auto &ena_sig : ena) { + // Check all 3 possibilities that ACLR can map to + // TODO: ACLR could be global, too + if (check_assign_sig(datain[ena_datain[0]], ena_sig)) + continue; + if (check_assign_sig(datain[ena_datain[1]], ena_sig)) + continue; + if (check_assign_sig(datain[ena_datain[2]], ena_sig)) + continue; + // Failed to find any free ENA-capable DATAIN + return false; + } + return true; + } +}; + +}; // namespace + +bool Arch::is_lab_ctrlset_legal(uint32_t lab) const +{ + LabCtrlSetWorker worker; + return worker.run(this, lab); +} + +void Arch::lab_pre_route() +{ + log_info("Preparing LABs for routing...\n"); + for (uint32_t lab = 0; lab < labs.size(); lab++) { + assign_control_sets(lab); + for (uint8_t alm = 0; alm < 10; alm++) { + reassign_alm_inputs(lab, alm); + } + } +} + +void Arch::assign_control_sets(uint32_t lab) +{ + // Set up reservations for checkPipAvail for control set signals + // This will be needed because clock and CE are routed together and must be kept together, there isn't free choice + // e.g. CLK0 & ENA0 must be use for one control set, and CLK1 & ENA1 for another, they can't be mixed and matched + // Similarly for how inverted & noninverted variants must be kept separate + LabCtrlSetWorker worker; + bool legal = worker.run(this, lab); + NPNR_ASSERT(legal); + auto &lab_data = labs.at(lab); + + for (int j = 0; j < 2; j++) { + lab_data.aclr_used[j] = false; + } + + for (uint8_t alm = 0; alm < 10; alm++) { + auto &alm_data = lab_data.alms.at(alm); + for (uint8_t i = 0; i < 4; i++) { + BelId ff_bel = alm_data.ff_bels.at(i); + const CellInfo *ff = getBoundBelCell(ff_bel); + if (ff == nullptr) + continue; + ControlSig ena_sig = ff->ffInfo.ctrlset.ena; + WireId clk_wire = getBelPinWire(ff_bel, id_CLK); + WireId ena_wire = getBelPinWire(ff_bel, id_ENA); + for (int j = 0; j < 3; j++) { + if (ena_sig == worker.datain[ena_datain[j]]) { + if (getCtx()->debug) { + log_info("Assigned CLK/ENA set %d to FF %s (%s)\n", i, nameOf(ff), getCtx()->nameOfBel(ff_bel)); + } + // TODO: lock clock according to ENA choice, too, when we support two clocks per ALM + reserve_route(lab_data.clk_wires[0], clk_wire); + reserve_route(lab_data.ena_wires[j], ena_wire); + alm_data.clk_ena_idx[i / 2] = j; + break; + } + } + + ControlSig aclr_sig = ff->ffInfo.ctrlset.aclr; + WireId aclr_wire = getBelPinWire(ff_bel, id_ACLR); + for (int j = 0; j < 2; j++) { + // TODO: could be global ACLR, too + if (aclr_sig == worker.datain[aclr_datain[j]]) { + if (getCtx()->debug) { + log_info("Assigned ACLR set %d to FF %s (%s)\n", i, nameOf(ff), getCtx()->nameOfBel(ff_bel)); + } + reserve_route(lab_data.aclr_wires[j], aclr_wire); + lab_data.aclr_used[j] = (aclr_sig.net != nullptr); + alm_data.aclr_idx[i / 2] = j; + break; + } + } + } + } +} + +namespace { +// Gets the name of logical LUT pin i for a given cell +static IdString get_lut_pin(CellInfo *cell, int i) +{ + const std::array<IdString, 6> log_pins{id_A, id_B, id_C, id_D, id_E, id_F}; + const std::array<IdString, 5> log_pins_arith{id_A, id_B, id_C, id_D0, id_D1}; + return (cell->type == id_MISTRAL_ALUT_ARITH) ? log_pins_arith.at(i) : log_pins.at(i); +} + +static void assign_lut6_inputs(CellInfo *cell, int lut) +{ + std::array<IdString, 6> phys_pins{id_A, id_B, id_C, id_D, (lut == 1) ? id_E1 : id_E0, (lut == 1) ? id_F1 : id_F0}; + int phys_idx = 0; + for (int i = 0; i < 6; i++) { + IdString log = get_lut_pin(cell, i); + if (!cell->ports.count(log) || cell->ports.at(log).net == nullptr) + continue; + cell->pin_data[log].bel_pins.clear(); + cell->pin_data[log].bel_pins.push_back(phys_pins.at(phys_idx++)); + } +} +} // namespace + +void Arch::reassign_alm_inputs(uint32_t lab, uint8_t alm) +{ + // Based on the usage of LUTs inside the ALM, set up cell-bel pin map for the combinational cells in the ALM + // so that each physical bel pin is only used for one net; and the logical functions can be implemented correctly. + // This function should also insert route-through LUTs to legalise flipflop inputs as needed. + auto &alm_data = labs.at(lab).alms.at(alm); + alm_data.l6_mode = false; + std::array<CellInfo *, 2> luts{getBoundBelCell(alm_data.lut_bels[0]), getBoundBelCell(alm_data.lut_bels[1])}; + std::array<CellInfo *, 4> ffs{getBoundBelCell(alm_data.ff_bels[0]), getBoundBelCell(alm_data.ff_bels[1]), + getBoundBelCell(alm_data.ff_bels[2]), getBoundBelCell(alm_data.ff_bels[3])}; + + for (int i = 0; i < 2; i++) { + // Currently we treat LUT6s as a special case, as they never share inputs + if (luts[i] != nullptr && luts[i]->type == id_MISTRAL_ALUT6) { + alm_data.l6_mode = true; + NPNR_ASSERT(luts[1 - i] == nullptr); // only allow one LUT6 per ALM and no other LUTs + assign_lut6_inputs(luts[i], i); + } + } + + if (!alm_data.l6_mode) { + // In L5 mode; which is what we use in this case + // - A and B are shared + // - C, E0, and F0 are exclusive to the top LUT5 secion + // - D, E1, and F1 are exclusive to the bottom LUT5 section + // First find up to two shared inputs + std::unordered_map<IdString, int> shared_nets; + if (luts[0] && luts[1]) { + for (int i = 0; i < luts[0]->combInfo.lut_input_count; i++) { + for (int j = 0; j < luts[1]->combInfo.lut_input_count; j++) { + if (luts[0]->combInfo.lut_in[i] == nullptr) + continue; + if (luts[0]->combInfo.lut_in[i] != luts[1]->combInfo.lut_in[j]) + continue; + IdString net = luts[0]->combInfo.lut_in[i]->name; + if (shared_nets.count(net)) + continue; + int idx = int(shared_nets.size()); + shared_nets[net] = idx; + if (shared_nets.size() >= 2) + goto shared_search_done; + } + } + shared_search_done:; + } + // A and B can be used for half-specific nets if not assigned to shared nets + bool a_avail = shared_nets.size() == 0, b_avail = shared_nets.size() <= 1; + // Do the actual port assignment + for (int i = 0; i < 2; i++) { + if (!luts[i]) + continue; + // Work out which physical ports are available + std::vector<IdString> avail_phys_ports; + // D/C always available and dedicated to the half, in L5 mode + avail_phys_ports.push_back((i == 1) ? id_D : id_C); + // In arithmetic mode, Ei can only be used for D0 and Fi can only be used for D1 + // otherwise, these are general and dedicated to one half + if (!luts[i]->combInfo.is_carry) { + avail_phys_ports.push_back((i == 1) ? id_E1 : id_E0); + avail_phys_ports.push_back((i == 1) ? id_F1 : id_F0); + } + // A and B might be used for shared signals, or already used by the other half + if (b_avail) + avail_phys_ports.push_back(id_B); + if (a_avail) + avail_phys_ports.push_back(id_A); + int phys_idx = 0; + + for (int j = 0; j < luts[i]->combInfo.lut_input_count; j++) { + IdString log = get_lut_pin(luts[i], j); + auto &bel_pins = luts[i]->pin_data[log].bel_pins; + bel_pins.clear(); + + NetInfo *net = get_net_or_empty(luts[i], log); + if (net == nullptr) { + // Disconnected inputs don't need to be allocated a pin, because the router won't be routing these + continue; + } else if (shared_nets.count(net->name)) { + // This pin is to be allocated one of the shared nets + bel_pins.push_back(shared_nets.at(net->name) ? id_B : id_A); + } else if (log == id_D0) { + // Arithmetic + bel_pins.push_back((i == 1) ? id_E1 : id_E0); // reserved + } else if (log == id_D1) { + bel_pins.push_back((i == 1) ? id_F1 : id_F0); // reserved + } else { + // Allocate from the general pool of available physical pins + IdString phys = avail_phys_ports.at(phys_idx++); + bel_pins.push_back(phys); + // Mark A/B unavailable for the other LUT, if needed + if (phys == id_A) + a_avail = false; + else if (phys == id_B) + b_avail = false; + } + } + } + } + + // FF route-through insertion + for (int i = 0; i < 2; i++) { + // FF route-through will never be inserted if LUT is used + if (luts[i]) + continue; + for (int j = 0; j < 2; j++) { + CellInfo *ff = ffs[i * 2 + j]; + if (!ff || !ff->ffInfo.datain || alm_data.l6_mode) + continue; + CellInfo *rt_lut = createCell(id(stringf("%s$ROUTETHRU", nameOf(ff))), id_MISTRAL_BUF); + rt_lut->addInput(id_A); + rt_lut->addOutput(id_Q); + // Disconnect the original data input to the FF, and connect it to the route-thru LUT instead + NetInfo *datain = get_net_or_empty(ff, id_DATAIN); + disconnect_port(getCtx(), ff, id_DATAIN); + connect_port(getCtx(), datain, rt_lut, id_A); + connect_ports(getCtx(), rt_lut, id_Q, ff, id_DATAIN); + // Assign route-thru LUT physical ports, input goes to the first half-specific input + rt_lut->pin_data[id_A].bel_pins.push_back(i ? id_D : id_C); + rt_lut->pin_data[id_Q].bel_pins.push_back(id_COMBOUT); + assign_comb_info(rt_lut); + // Place the route-thru LUT at the relevant combinational bel + bindBel(alm_data.lut_bels[i], rt_lut, STRENGTH_STRONG); + break; + } + } + + // TODO: in the future, as well as the reassignment here we will also have pseudo PIPs in front of the ALM so that + // the router can permute LUTs for routeability; too. Here we will need to lock out some of those PIPs depending on + // the usage of the ALM, as not all inputs are always interchangeable. + // Get cells into an array for fast access +} + +// This default cell-bel pin mapping is used to provide estimates during placement only. It will have errors and +// overlaps and a correct mapping will be resolved twixt placement and routing +const std::unordered_map<IdString, IdString> Arch::comb_pinmap = { + {id_A, id_F0}, // fastest input first + {id_B, id_E0}, {id_C, id_D}, {id_D, id_C}, {id_D0, id_C}, {id_D1, id_B}, + {id_E, id_B}, {id_F, id_A}, {id_Q, id_COMBOUT}, {id_SO, id_COMBOUT}, +}; + +namespace { +// gets the value of the ith LUT init property of a given cell +uint64_t get_lut_init(const CellInfo *cell, int i) +{ + if (cell->type == id_MISTRAL_NOT) { + return 1; + } else if (cell->type == id_MISTRAL_BUF) { + return 2; + } else { + IdString prop; + if (cell->type == id_MISTRAL_ALUT_ARITH) + prop = (i == 1) ? id_LUT1 : id_LUT0; + else + prop = id_LUT; + auto fnd = cell->params.find(prop); + if (fnd == cell->params.end()) + return 0; + else + return fnd->second.as_int64(); + } +} +// gets the state of a physical pin when evaluating the a given bit of LUT init for +bool get_phys_pin_val(bool l6_mode, bool arith_mode, int bit, IdString pin) +{ + switch (pin.index) { + case ID_A: + return (bit >> 0) & 0x1; + case ID_B: + return (bit >> 1) & 0x1; + case ID_C: + return (l6_mode && bit >= 32) ? ((bit >> 3) & 0x1) : ((bit >> 2) & 0x1); + case ID_D: + return (l6_mode && bit < 32) ? ((bit >> 3) & 0x1) : ((bit >> 2) & 0x1); + case ID_E0: + case ID_E1: + return l6_mode ? ((bit >> 5) & 0x1) : ((bit >> 3) & 0x1); + case ID_F0: + case ID_F1: + return arith_mode ? ((bit >> 3) & 0x1) : ((bit >> 4) & 0x1); + default: + NPNR_ASSERT_FALSE("unknown physical pin!"); + } +} +} // namespace + +uint64_t Arch::compute_lut_mask(uint32_t lab, uint8_t alm) +{ + uint64_t mask = 0; + auto &alm_data = labs.at(lab).alms.at(alm); + std::array<CellInfo *, 2> luts{getBoundBelCell(alm_data.lut_bels[0]), getBoundBelCell(alm_data.lut_bels[1])}; + + for (int i = 0; i < 2; i++) { + CellInfo *lut = luts[i]; + if (!lut) + continue; + int offset = ((i == 1) && !alm_data.l6_mode) ? 32 : 0; + bool arith = lut->combInfo.is_carry; + for (int j = 0; j < (alm_data.l6_mode ? 64 : 32); j++) { + // Evaluate LUT function at this point + uint64_t init = get_lut_init(lut, (arith && j >= 16) ? 1 : 0); + int index = 0; + for (int k = 0; k < lut->combInfo.lut_input_count; k++) { + IdString log_pin = get_lut_pin(lut, k); + int init_idx = k; + if (arith) { + // D0 only affects lower half; D1 upper half + if (k == 3 && j >= 16) + continue; + if (k == 4) { + if (j < 16) + continue; + else + init_idx = 3; + } + } + CellPinState state = lut->get_pin_state(log_pin); + if (state == PIN_0) + continue; + else if (state == PIN_1) + index |= (1 << init_idx); + // Ignore if no associated physical pin + if (get_net_or_empty(lut, log_pin) == nullptr || lut->pin_data.at(log_pin).bel_pins.empty()) + continue; + // ALM inputs appear to be inverted by default (TODO: check!) + // so only invert if an inverter has _not_ been folded into the pin + bool inverted = (state != PIN_INV); + // Depermute physical pin + IdString phys_pin = lut->pin_data.at(log_pin).bel_pins.at(0); + if (get_phys_pin_val(alm_data.l6_mode, arith, j, phys_pin) != inverted) + index |= (1 << init_idx); + } + if ((init >> index) & 0x1) { + mask |= (1ULL << uint64_t(j + offset)); + } + } + } + + // TODO: always inverted, or just certain paths? + mask = ~mask; + +#if 1 + if (getCtx()->debug) { + auto pos = alm_data.lut_bels[0].pos; + log("ALM %03d.%03d.%d\n", CycloneV::pos2x(pos), CycloneV::pos2y(pos), alm); + for (int i = 0; i < 2; i++) { + log(" LUT%d: ", i); + if (luts[i]) { + log("%s:%s", nameOf(luts[i]), nameOf(luts[i]->type)); + for (auto &pin : luts[i]->pin_data) { + if (!luts[i]->ports.count(pin.first) || luts[i]->ports.at(pin.first).type != PORT_IN) + continue; + log(" %s:", nameOf(pin.first)); + if (pin.second.state == PIN_0) + log("0"); + else if (pin.second.state == PIN_1) + log("1"); + else if (pin.second.state == PIN_INV) + log("~"); + for (auto bp : pin.second.bel_pins) + log("%s", nameOf(bp)); + } + } else { + log("<null>"); + } + log("\n"); + } + log("INIT: %016lx\n", mask); + log("\n"); + } +#endif + + return mask; +} + +NEXTPNR_NAMESPACE_END diff --git a/mistral/main.cc b/mistral/main.cc new file mode 100644 index 00000000..9147a68b --- /dev/null +++ b/mistral/main.cc @@ -0,0 +1,105 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021 gatecat <gatecat@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. + * + */ + +#ifdef MAIN_EXECUTABLE + +#include <fstream> +#include "command.h" +#include "design_utils.h" +#include "jsonwrite.h" +#include "log.h" +#include "timing.h" + +USING_NEXTPNR_NAMESPACE + +class MistralCommandHandler : public CommandHandler +{ + public: + MistralCommandHandler(int argc, char **argv); + virtual ~MistralCommandHandler(){}; + std::unique_ptr<Context> createContext(std::unordered_map<std::string, Property> &values) override; + void setupArchContext(Context *ctx) override{}; + void customBitstream(Context *ctx) override; + void customAfterLoad(Context *ctx) override; + + protected: + po::options_description getArchOptions() override; +}; + +MistralCommandHandler::MistralCommandHandler(int argc, char **argv) : CommandHandler(argc, argv) {} + +po::options_description MistralCommandHandler::getArchOptions() +{ + po::options_description specific("Architecture specific options"); + specific.add_options()("mistral", po::value<std::string>(), "path to mistral root"); + specific.add_options()("device", po::value<std::string>(), "device name (e.g. 5CSEBA6U23I7)"); + specific.add_options()("qsf", po::value<std::string>(), "path to QSF constraints file"); + specific.add_options()("rbf", po::value<std::string>(), "RBF bitstream to write"); + + return specific; +} + +void MistralCommandHandler::customBitstream(Context *ctx) +{ + if (vm.count("rbf")) { + std::string filename = vm["rbf"].as<std::string>(); + ctx->build_bitstream(); + std::vector<uint8_t> data; + ctx->cyclonev->rbf_save(data); + + std::ofstream out(filename, std::ios::binary); + if (!out) + log_error("Failed to open output RBF file %s.\n", filename.c_str()); + out.write(reinterpret_cast<const char *>(data.data()), data.size()); + } +} + +std::unique_ptr<Context> MistralCommandHandler::createContext(std::unordered_map<std::string, Property> &values) +{ + ArchArgs chipArgs; + if (!vm.count("mistral")) { + log_error("mistral must be specified on the command line\n"); + } + if (!vm.count("device")) { + log_error("device must be specified on the command line (e.g. --device 5CSEBA6U23I7)\n"); + } + chipArgs.mistral_root = vm["mistral"].as<std::string>(); + chipArgs.device = vm["device"].as<std::string>(); + auto ctx = std::unique_ptr<Context>(new Context(chipArgs)); + return ctx; +} + +void MistralCommandHandler::customAfterLoad(Context *ctx) +{ + if (vm.count("qsf")) { + std::string filename = vm["qsf"].as<std::string>(); + std::ifstream in(filename); + if (!in) + log_error("Failed to open input QSF file %s.\n", filename.c_str()); + ctx->read_qsf(in); + } +} + +int main(int argc, char *argv[]) +{ + MistralCommandHandler handler(argc, argv); + return handler.exec(); +} + +#endif diff --git a/mistral/pack.cc b/mistral/pack.cc new file mode 100644 index 00000000..90fbfd78 --- /dev/null +++ b/mistral/pack.cc @@ -0,0 +1,365 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021 gatecat <gatecat@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 "design_utils.h" +#include "log.h" +#include "nextpnr.h" +#include "util.h" + +NEXTPNR_NAMESPACE_BEGIN +namespace { +struct MistralPacker +{ + MistralPacker(Context *ctx) : ctx(ctx){}; + Context *ctx; + + NetInfo *gnd_net, *vcc_net; + + void init_constant_nets() + { + CellInfo *gnd_drv = ctx->createCell(ctx->id("$PACKER_GND_DRV"), id_MISTRAL_CONST); + gnd_drv->params[id_LUT] = 0; + gnd_drv->addOutput(id_Q); + CellInfo *vcc_drv = ctx->createCell(ctx->id("$PACKER_VCC_DRV"), id_MISTRAL_CONST); + vcc_drv->params[id_LUT] = 1; + vcc_drv->addOutput(id_Q); + gnd_net = ctx->createNet(ctx->id("$PACKER_GND_NET")); + vcc_net = ctx->createNet(ctx->id("$PACKER_VCC_NET")); + connect_port(ctx, gnd_net, gnd_drv, id_Q); + connect_port(ctx, vcc_net, vcc_drv, id_Q); + } + + CellPinState get_pin_needed_muxval(CellInfo *cell, IdString port) + { + NetInfo *net = get_net_or_empty(cell, port); + if (net == nullptr || net->driver.cell == nullptr) { + // Pin is disconnected + // If a mux value exists already, honour it + CellPinState exist_mux = cell->get_pin_state(port); + if (exist_mux != PIN_SIG) + return exist_mux; + // Otherwise, look up the default value and use that + CellPinStyle pin_style = ctx->get_cell_pin_style(cell, port); + if ((pin_style & PINDEF_MASK) == PINDEF_0) + return PIN_0; + else if ((pin_style & PINDEF_MASK) == PINDEF_1) + return PIN_1; + else + return PIN_SIG; + } + // Look to see if the driver is an inverter or constant + IdString drv_type = net->driver.cell->type; + if (drv_type == id_MISTRAL_NOT) + return PIN_INV; + else if (drv_type == id_GND) + return PIN_0; + else if (drv_type == id_VCC) + return PIN_1; + else + return PIN_SIG; + } + + void uninvert_port(CellInfo *cell, IdString port) + { + // Rewire a port so it is driven by the input to an inverter + NetInfo *net = get_net_or_empty(cell, port); + NPNR_ASSERT(net != nullptr && net->driver.cell != nullptr && net->driver.cell->type == id_MISTRAL_NOT); + CellInfo *inv = net->driver.cell; + disconnect_port(ctx, cell, port); + + NetInfo *inv_a = get_net_or_empty(inv, id_A); + if (inv_a != nullptr) { + connect_port(ctx, inv_a, cell, port); + } + } + + void process_inv_constants(CellInfo *cell) + { + // TODO: we might need to create missing inputs here in some cases so we can tie them to the correct constant? + // Fold inverters and constants into a cell + for (auto &port : cell->ports) { + // Iterate over all inputs + if (port.second.type != PORT_IN) + continue; + IdString port_name = port.first; + + CellPinState req_mux = get_pin_needed_muxval(cell, port_name); + if (req_mux == PIN_SIG) { + // No special setting required, ignore + continue; + } + + CellPinStyle pin_style = ctx->get_cell_pin_style(cell, port_name); + + if (req_mux == PIN_INV) { + // Pin is inverted. If there is a hard inverter; then use it + if (pin_style & PINOPT_INV) { + uninvert_port(cell, port_name); + cell->pin_data[port_name].state = PIN_INV; + } + } else if (req_mux == PIN_0 || req_mux == PIN_1) { + // Pin is tied to a constant + // If there is a hard constant option; use it + if ((pin_style & int(req_mux)) == req_mux) { + disconnect_port(ctx, cell, port_name); + cell->pin_data[port_name].state = req_mux; + } else { + disconnect_port(ctx, cell, port_name); + // There is no hard constant, we need to connect it to the relevant soft-constant net + connect_port(ctx, (req_mux == PIN_1) ? vcc_net : gnd_net, cell, port_name); + } + } + } + } + + void trim_design() + { + // Remove unused inverters and high/low drivers + std::vector<IdString> trim_cells; + std::vector<IdString> trim_nets; + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (ci->type != id_MISTRAL_NOT && ci->type != id_GND && ci->type != id_VCC) + continue; + IdString port = (ci->type == id_MISTRAL_NOT) ? id_Q : id_Y; + NetInfo *out = get_net_or_empty(ci, port); + if (out == nullptr) { + trim_cells.push_back(ci->name); + continue; + } + if (!out->users.empty()) + continue; + + disconnect_port(ctx, ci, id_A); + + trim_cells.push_back(ci->name); + trim_nets.push_back(out->name); + } + + for (IdString rem_net : trim_nets) + ctx->nets.erase(rem_net); + for (IdString rem_cell : trim_cells) + ctx->cells.erase(rem_cell); + } + + void pack_constants() + { + // Iterate through cells + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + // Skip certain cells at this point + if (ci->type != id_MISTRAL_NOT && ci->type != id_GND && ci->type != id_VCC) + process_inv_constants(cell.second); + } + // Special case - SDATA can only be trimmed if SLOAD is low + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (ci->type != id_MISTRAL_FF) + continue; + if (ci->get_pin_state(id_SLOAD) != PIN_0) + continue; + disconnect_port(ctx, ci, id_SDATA); + } + // Remove superfluous inverters and constant drivers + trim_design(); + } + + void prepare_io() + { + // Find the actual IO buffer corresponding to a port; and copy attributes across to it + // Note that this relies on Yosys to do IO buffer inference, to avoid tristate issues once we get to synthesised + // JSON. In all cases the nextpnr-inserted IO buffers are removed as redundant. + for (auto &port : sorted_ref(ctx->ports)) { + if (!ctx->cells.count(port.first)) + log_error("Port '%s' doesn't seem to have a corresponding top level IO\n", ctx->nameOf(port.first)); + CellInfo *ci = ctx->cells.at(port.first).get(); + + PortRef top_port; + top_port.cell = nullptr; + bool is_npnr_iob = false; + + if (ci->type == ctx->id("$nextpnr_ibuf") || ci->type == ctx->id("$nextpnr_iobuf")) { + // Might have an input buffer (IB etc) connected to it + is_npnr_iob = true; + NetInfo *o = get_net_or_empty(ci, id_O); + if (o == nullptr) + ; + else if (o->users.size() > 1) + log_error("Top level pin '%s' has multiple input buffers\n", ctx->nameOf(port.first)); + else if (o->users.size() == 1) + top_port = o->users.at(0); + } + if (ci->type == ctx->id("$nextpnr_obuf") || ci->type == ctx->id("$nextpnr_iobuf")) { + // Might have an output buffer (OB etc) connected to it + is_npnr_iob = true; + NetInfo *i = get_net_or_empty(ci, id_I); + if (i != nullptr && i->driver.cell != nullptr) { + if (top_port.cell != nullptr) + log_error("Top level pin '%s' has multiple input/output buffers\n", ctx->nameOf(port.first)); + top_port = i->driver; + } + // Edge case of a bidirectional buffer driving an output pin + if (i->users.size() > 2) { + log_error("Top level pin '%s' has illegal buffer configuration\n", ctx->nameOf(port.first)); + } else if (i->users.size() == 2) { + if (top_port.cell != nullptr) + log_error("Top level pin '%s' has illegal buffer configuration\n", ctx->nameOf(port.first)); + for (auto &usr : i->users) { + if (usr.cell->type == ctx->id("$nextpnr_obuf") || usr.cell->type == ctx->id("$nextpnr_iobuf")) + continue; + top_port = usr; + break; + } + } + } + if (!is_npnr_iob) + log_error("Port '%s' doesn't seem to have a corresponding top level IO (internal cell type mismatch)\n", + ctx->nameOf(port.first)); + + if (top_port.cell == nullptr) { + log_info("Trimming port '%s' as it is unused.\n", ctx->nameOf(port.first)); + } else { + // Copy attributes to real IO buffer + if (ctx->io_attr.count(port.first)) { + for (auto &kv : ctx->io_attr.at(port.first)) { + top_port.cell->attrs[kv.first] = kv.second; + } + } + // Make sure that top level net is set correctly + port.second.net = top_port.cell->ports.at(top_port.port).net; + } + // Now remove the nextpnr-inserted buffer + disconnect_port(ctx, ci, id_I); + disconnect_port(ctx, ci, id_O); + ctx->cells.erase(port.first); + } + } + + void pack_io() + { + // Step 0: deal with top level inserted IO buffers + prepare_io(); + // Stage 1: apply constraints + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + // Iterate through all IO buffer primitives + if (!ctx->is_io_cell(ci->type)) + continue; + // We need all IO constrained at the moment, unconstrained IO are rare enough not to care + if (!ci->attrs.count(id_LOC)) + log_error("Found unconstrained IO '%s', these are currently unsupported\n", ctx->nameOf(ci)); + // Convert package pin constraint to bel constraint + std::string loc = ci->attrs.at(id_LOC).as_string(); + if (loc.compare(0, 4, "PIN_") != 0) + log_error("Expecting PIN_-prefixed pin for IO '%s', got '%s'\n", ctx->nameOf(ci), loc.c_str()); + auto pin_info = ctx->cyclonev->pin_find_name(loc.substr(4)); + if (pin_info == nullptr) + log_error("IO '%s' is constrained to invalid pin '%s'\n", ctx->nameOf(ci), loc.c_str()); + BelId bel = ctx->get_io_pin_bel(pin_info); + + if (bel == BelId()) { + log_error("IO '%s' is constrained to pin %s which is not a supported IO pin.\n", ctx->nameOf(ci), + loc.c_str()); + } else { + log_info("Constraining IO '%s' to pin %s (bel %s)\n", ctx->nameOf(ci), loc.c_str(), + ctx->nameOfBel(bel)); + ctx->bindBel(bel, ci, STRENGTH_LOCKED); + } + } + } + + void constrain_carries() + { + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (ci->type != id_MISTRAL_ALUT_ARITH) + continue; + const NetInfo *cin = get_net_or_empty(ci, id_CI); + if (cin != nullptr && cin->driver.cell != nullptr) + continue; // not the start of a chain + std::vector<CellInfo *> chain; + CellInfo *cursor = ci; + while (true) { + chain.push_back(cursor); + const NetInfo *co = get_net_or_empty(cursor, id_CO); + if (co == nullptr || co->users.empty()) + break; + if (co->users.size() > 1) + log_error("Carry net %s has more than one sink!\n", ctx->nameOf(co)); + auto &usr = co->users.at(0); + if (usr.port != id_CI) + log_error("Carry net %s drives port %s, expected CI\n", ctx->nameOf(co), ctx->nameOf(usr.port)); + cursor = usr.cell; + } + + chain.at(0)->constr_abs_z = true; + chain.at(0)->constr_z = 0; + chain.at(0)->cluster = chain.at(0)->name; + + for (int i = 1; i < int(chain.size()); i++) { + chain.at(i)->constr_x = 0; + chain.at(i)->constr_y = -(i / 20); + // 2 COMB, 4 FF per ALM + chain.at(i)->constr_z = ((i / 2) % 10) * 6 + (i % 2); + chain.at(i)->constr_abs_z = true; + chain.at(i)->cluster = chain.at(0)->name; + chain.at(0)->constr_children.push_back(chain.at(i)); + } + + if (ctx->debug) { + log_info("Chain: \n"); + for (int i = 0; i < int(chain.size()); i++) { + auto &c = chain.at(i); + log_info(" i=%d cell=%s dy=%d z=%d ci=%s co=%s\n", i, ctx->nameOf(c), c->constr_y, c->constr_z, + ctx->nameOf(get_net_or_empty(c, id_CI)), ctx->nameOf(get_net_or_empty(c, id_CO))); + } + } + } + // Check we reached all the cells in the above pass + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (ci->type != id_MISTRAL_ALUT_ARITH) + continue; + if (ci->cluster == ClusterId()) + log_error("Failed to include arith cell '%s' in any chain (CI=%s)\n", ctx->nameOf(ci), + ctx->nameOf(get_net_or_empty(ci, id_CI))); + } + } + + void run() + { + init_constant_nets(); + pack_constants(); + pack_io(); + constrain_carries(); + } +}; +}; // namespace + +bool Arch::pack() +{ + MistralPacker packer(getCtx()); + packer.run(); + + assignArchInfo(); + + return true; +} + +NEXTPNR_NAMESPACE_END diff --git a/mistral/pins.cc b/mistral/pins.cc new file mode 100644 index 00000000..c3637115 --- /dev/null +++ b/mistral/pins.cc @@ -0,0 +1,67 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021 gatecat <gatecat@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 "log.h" +#include "nextpnr.h" +#include "util.h" + +NEXTPNR_NAMESPACE_BEGIN + +const std::unordered_map<IdString, Arch::CellPinsData> Arch::cell_pins_db = { + // For combinational cells, inversion and tieing can be implemented by manipulating the LUT function + {id_MISTRAL_ALUT2, {{{}, PINSTYLE_COMB}}}, + {id_MISTRAL_ALUT3, {{{}, PINSTYLE_COMB}}}, + {id_MISTRAL_ALUT4, {{{}, PINSTYLE_COMB}}}, + {id_MISTRAL_ALUT5, {{{}, PINSTYLE_COMB}}}, + {id_MISTRAL_ALUT6, {{{}, PINSTYLE_COMB}}}, + {id_MISTRAL_ALUT_ARITH, + {// Leave carry chain alone, other than disconnecting a ground constant + {id_CI, PINSTYLE_CARRY}, + {{}, PINSTYLE_COMB}}}, + {id_MISTRAL_FF, + { + {id_CLK, PINSTYLE_CLK}, + {id_ENA, PINSTYLE_CE}, + {id_ACLR, PINSTYLE_RST}, + {id_SCLR, PINSTYLE_RST}, + {id_SLOAD, PINSTYLE_RST}, + {id_SDATA, PINSTYLE_DEDI}, + {id_DATAIN, PINSTYLE_INP}, + }}, +}; + +CellPinStyle Arch::get_cell_pin_style(const CellInfo *cell, IdString port) const +{ + // Look up the pin style in the cell database + auto fnd_cell = cell_pins_db.find(cell->type); + if (fnd_cell == cell_pins_db.end()) + return PINSTYLE_NONE; + auto fnd_port = fnd_cell->second.find(port); + if (fnd_port != fnd_cell->second.end()) + return fnd_port->second; + // If there isn't an exact port match, then the empty IdString + // represents a wildcard default match + auto fnd_default = fnd_cell->second.find({}); + if (fnd_default != fnd_cell->second.end()) + return fnd_default->second; + + return PINSTYLE_NONE; +} + +NEXTPNR_NAMESPACE_END diff --git a/mistral/qsf.cc b/mistral/qsf.cc new file mode 100644 index 00000000..9a128595 --- /dev/null +++ b/mistral/qsf.cc @@ -0,0 +1,281 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021 gatecat <gatecat@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 "log.h" +#include "nextpnr.h" +#include "util.h" + +#include <iterator> + +NEXTPNR_NAMESPACE_BEGIN + +namespace { + +struct QsfOption +{ + std::string name; // name, excluding the initial '-' + int arg_count; // number of arguments that follow the option + bool required; // error out if this option isn't passed +}; + +typedef std::unordered_map<std::string, std::vector<std::string>> option_map_t; + +struct QsfCommand +{ + std::string name; // name of the command + std::vector<QsfOption> options; // list of "-options" + int pos_arg_count; // number of positional arguments expected to follow the command, -1 for any + std::function<void(Context *ctx, const option_map_t &options, const std::vector<std::string> &pos_args)> func; +}; + +void set_location_assignment_cmd(Context *ctx, const option_map_t &options, const std::vector<std::string> &pos_args) +{ + ctx->io_attr[ctx->id(options.at("to").at(0))][id_LOC] = pos_args.at(0); +} + +void set_instance_assignment_cmd(Context *ctx, const option_map_t &options, const std::vector<std::string> &pos_args) +{ + ctx->io_attr[ctx->id(options.at("to").at(0))][ctx->id(options.at("name").at(0))] = pos_args.at(0); +} + +void set_global_assignment_cmd(Context *ctx, const option_map_t &options, const std::vector<std::string> &pos_args) +{ + // TODO +} + +static const std::vector<QsfCommand> commands = { + {"set_location_assignment", {{"to", 1, true}}, 1, set_location_assignment_cmd}, + {"set_instance_assignment", + {{"to", 1, true}, {"name", 1, true}, {"section_id", 1, false}}, + 1, + set_instance_assignment_cmd}, + {"set_global_assignment", + {{"name", 1, true}, {"section_id", 1, false}, {"rise", 0, false}, {"fall", 0, false}}, + 1, + set_global_assignment_cmd}, +}; + +struct QsfParser +{ + std::string buf; + int pos = 0; + int lineno = 0; + Context *ctx; + + QsfParser(const std::string &buf, Context *ctx) : buf(buf), ctx(ctx){}; + + inline bool eof() const { return pos == int(buf.size()); } + + inline char peek() const { return buf.at(pos); } + + inline char get() + { + char c = buf.at(pos++); + if (c == '\n') + ++lineno; + return c; + } + + std::string get(int n) + { + std::string s = buf.substr(pos, n); + pos += n; + return s; + } + + // If next char matches c, take it from the stream and return true + bool check_get(char c) + { + if (peek() == c) { + get(); + return true; + } else { + return false; + } + } + + // If next char matches any in chars, take it from the stream and return true + bool check_get_any(const std::string &chrs) + { + char c = peek(); + if (chrs.find(c) != std::string::npos) { + get(); + return true; + } else { + return false; + } + } + + inline void skip_blank(bool nl = false) + { + while (!eof() && check_get_any(nl ? " \t\n\r" : " \t")) + ; + } + + // Return true if end of line (or file) + inline bool skip_check_eol() + { + skip_blank(false); + if (eof()) + return true; + char c = peek(); + // Comments count as end of line + if (c == '#') { + get(); + while (!eof() && peek() != '\n' && peek() != '\r') + get(); + return true; + } + if (c == ';') { + // Forced end of line + get(); + return true; + } + return (c == '\n' || c == '\r'); + } + + // We need to distinguish between quoted and unquoted strings, the former don't count as options + struct StringVal + { + std::string str; + bool is_quoted = false; + }; + + inline StringVal get_str() + { + StringVal s; + skip_blank(false); + if (eof()) + return {"", false}; + + bool in_quotes = false, in_braces = false, escaped = false; + + char c = get(); + + if (c == '"') { + in_quotes = true; + s.is_quoted = true; + } else if (c == '{') { + in_braces = true; + s.is_quoted = true; + } else { + s.str += c; + } + + while (!eof()) { + char c = peek(); + if (!in_quotes && !in_braces && !escaped && (std::isblank(c) || c == '\n' || c == '\r')) { + break; + } + get(); + if (escaped) { + s.str += c; + escaped = false; + } else if ((in_quotes && c == '"') || (in_braces && c == '}')) { + break; + } else if (c == '\\') { + escaped = true; + } else { + s.str += c; + } + } + + return s; + } + + std::vector<StringVal> get_arguments() + { + std::vector<StringVal> args; + while (!skip_check_eol()) { + args.push_back(get_str()); + } + skip_blank(true); + return args; + } + + void evaluate(const std::vector<StringVal> &args) + { + if (args.empty()) + return; + auto cmd_name = args.at(0).str; + auto fnd_cmd = + std::find_if(commands.begin(), commands.end(), [&](const QsfCommand &c) { return c.name == cmd_name; }); + if (fnd_cmd == commands.end()) { + log_warning("Ignoring unknown command '%s' (line %d)\n", cmd_name.c_str(), lineno); + return; + } + option_map_t opt; + std::vector<std::string> pos_args; + for (size_t i = 1; i < args.size(); i++) { + auto arg = args.at(i); + if (arg.str.at(0) == '-' && !arg.is_quoted) { + for (auto &opt_data : fnd_cmd->options) { + if (arg.str.compare(1, std::string::npos, opt_data.name) != 0) + continue; + opt[opt_data.name]; // create empty entry, even if 0 arguments + for (int j = 0; j < opt_data.arg_count; j++) { + ++i; + if (i >= args.size()) + log_error("Unexpected end of argument list to option '%s' (line %d)\n", arg.str.c_str(), + lineno); + opt[opt_data.name].push_back(args.at(i).str); + } + goto done; + } + log_error("Unknown option '%s' to command '%s' (line %d)\n", arg.str.c_str(), cmd_name.c_str(), lineno); + done:; + } else { + // positional argument + pos_args.push_back(arg.str); + } + } + // Check positional argument count + if (int(pos_args.size()) != fnd_cmd->pos_arg_count && fnd_cmd->pos_arg_count != -1) { + log_error("Expected %d positional arguments to command '%s', got %d (line %d)\n", fnd_cmd->pos_arg_count, + cmd_name.c_str(), int(pos_args.size()), lineno); + } + // Check required options + for (auto &opt_data : fnd_cmd->options) { + if (opt_data.required && !opt.count(opt_data.name)) + log_error("Missing required option '%s' to command '%s' (line %d)\n", opt_data.name.c_str(), + cmd_name.c_str(), lineno); + } + // Execute + fnd_cmd->func(ctx, opt, pos_args); + } + + void operator()() + { + while (!eof()) { + skip_blank(true); + auto args = get_arguments(); + evaluate(args); + } + } +}; + +}; // namespace + +void Arch::read_qsf(std::istream &in) +{ + std::string buf(std::istreambuf_iterator<char>(in), {}); + QsfParser(buf, getCtx())(); +} + +NEXTPNR_NAMESPACE_END |