aboutsummaryrefslogtreecommitdiffstats
path: root/generic/viaduct/fabulous
diff options
context:
space:
mode:
authorgatecat <gatecat@ds0.me>2022-08-04 09:04:02 +0200
committergatecat <gatecat@ds0.me>2022-09-09 14:48:57 +0200
commitf4230553901de30f07be5906471b219ed255cb6e (patch)
tree601ada70dbe68cd9b34451840752868700de9612 /generic/viaduct/fabulous
parentb9b16eaa5358dbdf15368eb145be2c70b72c42a9 (diff)
downloadnextpnr-f4230553901de30f07be5906471b219ed255cb6e.tar.gz
nextpnr-f4230553901de30f07be5906471b219ed255cb6e.tar.bz2
nextpnr-f4230553901de30f07be5906471b219ed255cb6e.zip
fabulous: Add a viaduct uarch
Signed-off-by: gatecat <gatecat@ds0.me>
Diffstat (limited to 'generic/viaduct/fabulous')
-rw-r--r--generic/viaduct/fabulous/constids.inc88
-rw-r--r--generic/viaduct/fabulous/fab_cfg.h102
-rw-r--r--generic/viaduct/fabulous/fab_defs.h39
-rw-r--r--generic/viaduct/fabulous/fabric_parsing.h175
-rw-r--r--generic/viaduct/fabulous/fabulous.cc396
-rw-r--r--generic/viaduct/fabulous/fasm.cc191
-rw-r--r--generic/viaduct/fabulous/fasm.h32
-rw-r--r--generic/viaduct/fabulous/pack.cc253
-rw-r--r--generic/viaduct/fabulous/pack.h32
-rw-r--r--generic/viaduct/fabulous/validity_check.cc201
-rw-r--r--generic/viaduct/fabulous/validity_check.h127
11 files changed, 1636 insertions, 0 deletions
diff --git a/generic/viaduct/fabulous/constids.inc b/generic/viaduct/fabulous/constids.inc
new file mode 100644
index 00000000..c46d5e6d
--- /dev/null
+++ b/generic/viaduct/fabulous/constids.inc
@@ -0,0 +1,88 @@
+X(FABULOUS_LC)
+X(FABULOUS_COMB)
+X(FABULOUS_FF)
+
+X(SET_NORESET)
+X(ASYNC_SR)
+X(NEG_CLK)
+X(FF)
+X(LATCH_NOFF)
+
+X(IO_1_bidirectional_frame_config_pass)
+X(InPass4_frame_config)
+X(OutPass4_frame_config)
+X(RegFile_32x4)
+X(MULADD)
+X(MUX8LUT_frame_config)
+
+X(CLK)
+X(I)
+X(T)
+X(O)
+X(Q)
+X(Ci)
+X(Co)
+
+X(X0Y0)
+
+X(REG_CLK)
+X(LUT_CLK)
+X(global_clock)
+
+X(Global_Clock)
+X(O2Q)
+
+X(WRITE_DATA)
+X(WRITE_ADDRESS)
+X(READ_DATA)
+X(READ_ADDRESS)
+X(DSP_DATA_OUT)
+X(DSP_DATA_IN)
+X(DSP_CLR)
+
+X(carry_in)
+X(carry_out)
+
+X(LUTFF)
+X(LUTFF_E)
+X(LUTFF_SR)
+X(LUTFF_SS)
+X(LUTFF_ESR)
+X(LUTFF_ESS)
+X(LUTFF_R)
+X(LUTFF_S)
+X(LUTFF_ER)
+X(LUTFF_ES)
+
+X(LUTFF_N)
+X(LUTFF_NE)
+X(LUTFF_NSR)
+X(LUTFF_NSS)
+X(LUTFF_NESR)
+X(LUTFF_NESS)
+X(LUTFF_NR)
+X(LUTFF_NS)
+X(LUTFF_NER)
+X(LUTFF_NES)
+
+X(clr)
+
+X(__disconnected)
+X(INIT)
+
+X(E)
+X(C)
+X(D)
+X(S)
+X(R)
+X(SR)
+X(EN)
+
+X(BEL)
+X(PAD)
+X(LUT1)
+
+X(BelBegin)
+X(BelEnd)
+X(GlobalClk)
+X(CFG)
diff --git a/generic/viaduct/fabulous/fab_cfg.h b/generic/viaduct/fabulous/fab_cfg.h
new file mode 100644
index 00000000..dc487fbd
--- /dev/null
+++ b/generic/viaduct/fabulous/fab_cfg.h
@@ -0,0 +1,102 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2021-22 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 FAB_CFG_H
+#define FAB_CFG_H
+
+#include "fab_defs.h"
+#include "hashlib.h"
+#include "idstring.h"
+#include "nextpnr_assertions.h"
+#include "nextpnr_namespaces.h"
+
+#include <string>
+#include <vector>
+
+NEXTPNR_NAMESPACE_BEGIN
+
+/*
+This set of structures is designed to enumerate the different configurable options for a fabulous architecture,
+affecting the packer etc...
+*/
+
+struct ControlSetConfig
+{
+ /*
+ CLB signal routing masks for fast validity checking
+ for each unique CLK/CE/SR input to a CLB, add an entry to this vector, and set the bits to 1 for each ff that
+ signal can drive for a CLB with 8 FFs and 2 clocks split at halfway, the first entry would be 0x0F and the second
+ 0xF0
+ */
+ std::vector<route_mask_t> routing = {0b11111111}; // default 1 shared between all
+ bool have_signal = true;
+ bool can_invert = false;
+};
+
+struct LogicConfig
+{
+ // ** Core CLB config
+ unsigned lc_per_clb = 8; // number of logic cells per clb
+ bool split_lc = false; // whether to represent SLICE as a single bel or separate lut+ff (latter important if ff and
+ // lut can be used separately)
+
+ // ** LUT config
+ unsigned lut_k = 4; // base number of inputs for lookup table
+ enum LutType
+ {
+ SINGLE_LUT,
+ // ...
+ } lut_type = LutType::SINGLE_LUT; // different types of fracturable LUT structure
+
+ enum LutCascade
+ {
+ NO_CASCADE,
+ // ...
+ } lut_casc = LutCascade::NO_CASCADE; // different types of cascading between LUTs
+
+ // TODO: other features we might want to represent...
+ // TODO: fracLUT/FF/mux/carry output sharing matrices
+
+ // ** Carry config
+ enum CarryType
+ {
+ NO_CARRY, // no carry chain
+ HA_PRE_LUT, // half addder before LUT (classic fabulous LC)
+ PG_POST_LUT, // prop/gen logic after a fractured LUT
+ FA_POST_LUT, // full adder after a fractured LUT
+ } carry_type = CarryType::HA_PRE_LUT;
+ int carry_lut_frac = -1; // how the LUT is fractured for PG_POST_LUT/FA_POST_LUT, if the LUT fracturing is different
+ // (or only supported) for carry modes and not in general
+
+ // ** FF config
+ unsigned ff_per_lc = 1; // number of flipflops per logic cell
+ uint32_t dedi_ff_input = 0; // mask of flipflops in a LC that have dedicated inputs
+ uint32_t dedi_ff_output = 0; // mask of flipflops in a LC that have dedicated outputs
+
+ ControlSetConfig clk, sr, en; // flipflop control set routing
+};
+
+struct FabricConfig
+{
+ LogicConfig clb;
+ // DSP cascading, BRAM, IP rules, IO, clocking ...
+};
+
+NEXTPNR_NAMESPACE_END
+
+#endif
diff --git a/generic/viaduct/fabulous/fab_defs.h b/generic/viaduct/fabulous/fab_defs.h
new file mode 100644
index 00000000..bd026b18
--- /dev/null
+++ b/generic/viaduct/fabulous/fab_defs.h
@@ -0,0 +1,39 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2022 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 FAB_DEFS_H
+#define FAB_DEFS_H
+
+#include <cstdint>
+#include "nextpnr_namespaces.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+/*
+This defines some compile-time maximums for the fabulous arch
+*/
+
+static constexpr unsigned MAX_LUTK = 6; // max number of LUT inputs
+
+typedef uint64_t route_mask_t; // the width of this type defines the max number of FFs in a CLB (for defining control
+ // set route patterns)
+
+NEXTPNR_NAMESPACE_END
+
+#endif
diff --git a/generic/viaduct/fabulous/fabric_parsing.h b/generic/viaduct/fabulous/fabric_parsing.h
new file mode 100644
index 00000000..3fa263ca
--- /dev/null
+++ b/generic/viaduct/fabulous/fabric_parsing.h
@@ -0,0 +1,175 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2022 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 FABULOUS_PARSING_H
+#define FABULOUS_PARSING_H
+
+#include <iostream>
+#include <limits>
+#include <string>
+#include "idstring.h"
+#include "nextpnr_assertions.h"
+#include "nextpnr_namespaces.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+struct BaseCtx;
+
+// Lightweight NIH string_view
+struct parser_view
+{
+ char *m_ptr;
+ size_t m_length;
+ parser_view() : m_ptr(nullptr), m_length(0){};
+ explicit parser_view(std::string &str) : m_ptr(&(str[0])), m_length(str.size()){};
+ parser_view(char *ptr, size_t length) : m_ptr(ptr), m_length(length){};
+
+ static constexpr size_t npos = std::numeric_limits<size_t>::max();
+ char operator[](size_t idx)
+ {
+ NPNR_ASSERT(idx < m_length);
+ return m_ptr[idx];
+ }
+
+ size_t size() const { return m_length; }
+
+ bool empty() const { return m_length == 0; }
+
+ parser_view substr(size_t start, size_t length = npos)
+ {
+ NPNR_ASSERT(start <= m_length);
+ if (length == npos)
+ length = m_length - start;
+ NPNR_ASSERT(length <= m_length);
+ return parser_view(m_ptr + start, length);
+ }
+
+ size_t find(char tok) const
+ {
+ for (size_t i = 0; i < m_length; i++)
+ if (m_ptr[i] == tok)
+ return i;
+ return npos;
+ }
+
+ IdString to_id(const BaseCtx *ctx)
+ {
+ // This isn't really ideal, let's hope one day we can move to C++20 and have proper string_views instead :3
+ char tmp = m_ptr[m_length];
+ m_ptr[m_length] = '\0';
+ IdString id = IdString(ctx, m_ptr);
+ m_ptr[m_length] = tmp;
+ return id;
+ }
+
+ long to_int()
+ {
+ // This isn't really ideal, let's hope one day we can move to C++20 and have proper string_views instead :3
+ char tmp = m_ptr[m_length];
+ m_ptr[m_length] = '\0';
+ long l = strtol(m_ptr, nullptr, 0);
+ m_ptr[m_length] = tmp;
+ return l;
+ }
+
+ parser_view strip(const std::string &ws = " \r\n\t")
+ {
+ char *ptr = m_ptr;
+ size_t length = m_length;
+ while (length > 0) { // strip front
+ if (ws.find(*ptr) == std::string::npos) // not whitespace
+ break;
+ ptr++;
+ length--;
+ }
+ while (length > 0) { // strip back
+ if (ws.find(ptr[length - 1]) == std::string::npos) // not whitespace
+ break;
+ length--;
+ }
+ return parser_view(ptr, length);
+ }
+
+ char back()
+ {
+ NPNR_ASSERT(m_length > 0);
+ return m_ptr[m_length - 1];
+ }
+
+ parser_view back(size_t count)
+ {
+ NPNR_ASSERT(count <= m_length);
+ return parser_view(m_ptr + (m_length - count), count);
+ }
+
+ bool starts_with(const std::string &st)
+ {
+ if (m_length < st.size())
+ return false;
+ for (size_t i = 0; i < st.length(); i++)
+ if (m_ptr[i] != st[i])
+ return false;
+ return true;
+ }
+ std::pair<parser_view, parser_view> split(char delim) const
+ {
+ size_t pos = find(delim);
+ NPNR_ASSERT(pos != npos);
+ return std::make_pair(parser_view(m_ptr, pos), parser_view(m_ptr + pos + 1, m_length - (pos + 1)));
+ }
+};
+
+struct CsvParser
+{
+ explicit CsvParser(std::istream &in) : in(in){};
+ std::istream &in;
+ std::string buf;
+ parser_view view;
+ bool fetch_next_line()
+ {
+ while (!in.eof()) {
+ std::getline(in, buf);
+ view = parser_view(buf).strip();
+ size_t end_pos = view.find('#');
+ if (end_pos != parser_view::npos)
+ view = view.substr(0, end_pos);
+ view = view.strip();
+ if (!view.empty())
+ return true;
+ }
+ return false;
+ }
+ parser_view next_field()
+ {
+ size_t next_delim = view.find(',');
+ if (next_delim == parser_view::npos) {
+ parser_view result = view.substr(0, next_delim);
+ view = parser_view();
+ return result;
+ } else {
+ parser_view result = view.substr(0, next_delim);
+ view = view.substr(next_delim + 1);
+ return result;
+ }
+ }
+};
+
+NEXTPNR_NAMESPACE_END
+
+#endif
diff --git a/generic/viaduct/fabulous/fabulous.cc b/generic/viaduct/fabulous/fabulous.cc
new file mode 100644
index 00000000..e2adb894
--- /dev/null
+++ b/generic/viaduct/fabulous/fabulous.cc
@@ -0,0 +1,396 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2021-22 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 "fabric_parsing.h"
+#include "log.h"
+#include "nextpnr.h"
+#include "util.h"
+#include "viaduct_api.h"
+#include "viaduct_helpers.h"
+
+#include <fstream>
+
+#define GEN_INIT_CONSTIDS
+#define VIADUCT_CONSTIDS "viaduct/fabulous/constids.inc"
+#include "viaduct_constids.h"
+
+#include "fab_cfg.h"
+#include "fab_defs.h"
+#include "fasm.h"
+#include "pack.h"
+#include "validity_check.h"
+
+#include <boost/filesystem.hpp>
+
+NEXTPNR_NAMESPACE_BEGIN
+
+namespace {
+struct FabulousImpl : ViaductAPI
+{
+ FabulousImpl(const dict<std::string, std::string> &args)
+ {
+ for (auto a : args) {
+ if (a.first == "fasm")
+ fasm_file = a.second;
+ else
+ log_error("unrecognised fabulous option '%s'\n", a.first.c_str());
+ }
+ }
+ ~FabulousImpl(){};
+ void init(Context *ctx) override
+ {
+ init_uarch_constids(ctx);
+ ViaductAPI::init(ctx);
+ h.init(ctx);
+ fab_root = get_env_var("FAB_ROOT", ", set it to the fabulous build output or project path");
+ if (boost::filesystem::exists(fab_root + "/.FABulous"))
+ is_new_fab = true;
+ else
+ is_new_fab = false;
+ log_info("Detected FABulous %s format project.\n", is_new_fab ? "2.0" : "1.0");
+ // To consider: a faster serialised form of the device data (like bba that other arches use) so we don't have to
+ // go through the whole csv parsing malarkey each time
+ blk_trk = std::make_unique<BlockTracker>(ctx, cfg);
+ is_new_fab ? init_bels_v2() : init_bels_v1();
+ init_pips();
+ }
+
+ void pack() override { fabulous_pack(ctx, cfg); }
+
+ void postRoute() override
+ {
+ if (!fasm_file.empty())
+ fabulous_write_fasm(ctx, cfg, fasm_file);
+ }
+
+ void prePlace() override { assign_cell_info(); }
+ bool isBelLocationValid(BelId bel) const override { return blk_trk->check_validity(bel, cfg, cell_tags); }
+
+ private:
+ FabricConfig cfg; // TODO: non-default config
+ ViaductHelpers h;
+
+ WireId global_clk_wire;
+
+ std::string fasm_file;
+
+ std::unique_ptr<BlockTracker> blk_trk;
+
+ std::string get_env_var(const std::string &name, const std::string &prompt = "")
+ {
+ const char *var = getenv(name.c_str());
+ if (var == nullptr)
+ log_error("environment variable '%s' is not set%s\n", name.c_str(), prompt.c_str());
+ return std::string(var);
+ }
+
+ std::ifstream open_data_rel(const std::string &postfix)
+ {
+ const std::string filename(fab_root + postfix);
+ std::ifstream in(filename);
+ if (!in)
+ log_error("failed to open data file '%s' (is FAB_ROOT set correctly?)\n", filename.c_str());
+ return in;
+ }
+
+ std::string fab_root;
+ bool is_new_fab;
+
+ pool<IdString> warned_beltypes;
+
+ void add_pseudo_pip(WireId src, WireId dst, IdString pip_type)
+ {
+ const auto &src_data = ctx->wire_info(src);
+ IdStringList pip_name = IdStringList::concat(ctx->getWireName(src), ctx->getWireName(dst));
+ ctx->addPip(pip_name, pip_type, src, dst, ctx->getDelayFromNS(0.05), Loc(src_data.x, src_data.y, 0));
+ }
+
+ void handle_bel_ports(BelId bel, IdString tile, IdString bel_type, const std::vector<parser_view> &ports)
+ {
+ // TODO: improve the scalability here as we support more bel types
+ IdString idx = ctx->getBelName(bel)[1];
+ Loc loc = ctx->getBelLocation(bel);
+ if (bel_type == id_IO_1_bidirectional_frame_config_pass) {
+ for (parser_view p : ports) {
+ IdString port_id = p.to_id(ctx);
+ WireId port_wire = get_wire(tile, port_id, ctx->idf("W_IO_%s", port_id.c_str(ctx)));
+ IdString pin = p.back(1).to_id(ctx);
+ ctx->addBelPin(bel, pin, port_wire, pin.in(id_I, id_T) ? PORT_IN : PORT_OUT);
+ }
+ } else if (bel_type.in(id_InPass4_frame_config, id_OutPass4_frame_config)) {
+ for (parser_view p : ports) {
+ IdString port_id = p.to_id(ctx);
+ WireId port_wire = get_wire(tile, port_id, port_id);
+ IdString pin = p.back(2).to_id(ctx);
+ ctx->addBelPin(bel, pin, port_wire, bel_type == id_OutPass4_frame_config ? PORT_IN : PORT_OUT);
+ }
+ } else if (bel_type == id_RegFile_32x4) {
+ WireId clk_wire = get_wire(tile, id_CLK, id_REG_CLK);
+ ctx->addBelInput(bel, id_CLK, clk_wire);
+ add_pseudo_pip(global_clk_wire, clk_wire, id_global_clock);
+ for (parser_view p : ports) {
+ IdString port_id = p.to_id(ctx);
+ // TODO: nicer way of determining port type?
+ if (p[0] == 'D') {
+ ctx->addBelInput(bel, port_id, get_wire(tile, port_id, id_WRITE_DATA));
+ } else if (p[0] == 'W') {
+ ctx->addBelInput(bel, port_id, get_wire(tile, port_id, id_WRITE_ADDRESS));
+ } else if (p[1] == 'D') {
+ ctx->addBelOutput(bel, port_id, get_wire(tile, port_id, id_READ_DATA));
+ } else {
+ ctx->addBelInput(bel, port_id, get_wire(tile, port_id, id_READ_ADDRESS));
+ }
+ }
+ } else if (bel_type == id_MULADD) {
+ // TODO: do DSPs need a clock too like regfiles?
+ for (parser_view p : ports) {
+ IdString port_id = p.to_id(ctx);
+ if (p[0] == 'Q') {
+ ctx->addBelOutput(bel, port_id, get_wire(tile, port_id, id_DSP_DATA_OUT));
+ } else if (port_id == id_clr) {
+ ctx->addBelInput(bel, port_id, get_wire(tile, port_id, id_DSP_CLR));
+ } else {
+ ctx->addBelInput(bel, port_id, get_wire(tile, port_id, id_DSP_DATA_IN));
+ }
+ }
+ } else if (bel_type == id_MUX8LUT_frame_config) {
+ for (parser_view p : ports) {
+ IdString port_id = p.to_id(ctx);
+ ctx->addBelPin(bel, port_id, get_wire(tile, port_id, ctx->idf("LUTMUX_%s", port_id.c_str(ctx))),
+ p[0] == 'M' ? PORT_OUT : PORT_IN);
+ }
+ } else if (bel_type == id_FABULOUS_LC) {
+ // TODO: split LC mode, LUT permutation pseudo-switchbox, LUT thru pseudo-pips
+ WireId clk_wire = get_wire(tile, ctx->idf("L%s_CLK", idx.c_str(ctx)), id_LUT_CLK);
+ ctx->addBelInput(bel, id_CLK, clk_wire);
+ add_pseudo_pip(global_clk_wire, clk_wire, id_global_clock);
+ blk_trk->set_bel_type(bel, BelFlags::BLOCK_CLB, BelFlags::FUNC_LC_COMB, loc.z);
+ for (parser_view p : ports) {
+ IdString port_id = p.to_id(ctx);
+ WireId port_wire = get_wire(tile, port_id, ctx->idf("LUT_%s", port_id.c_str(ctx)));
+ // TODO: more robust port name handling
+ if (p[3] == 'S' || p[3] == 'E' || p[3] == 'I') { // set/reset, enable, LUT input
+ ctx->addBelInput(bel, p.substr(3).to_id(ctx), port_wire);
+ } else if (p[3] == 'O') { // LUT otuput
+ ctx->addBelOutput(bel, p.substr(3, 1).to_id(ctx), port_wire);
+ } else if (p[3] == 'C') { // carry chain
+ if (p[4] == 'i') {
+ ctx->addBelInput(bel, id_Ci, port_wire);
+ } else {
+ NPNR_ASSERT(p[4] == 'o');
+ ctx->addBelOutput(bel, id_Co, port_wire);
+ }
+ } else {
+ log_error("don't know what to do with LC port '%s'\n", port_id.c_str(ctx));
+ }
+ }
+ } else {
+ // ...
+ if (!warned_beltypes.count(bel_type) && !ports.empty()) {
+ log_warning("don't know how to handle ports for bel type '%s'\n", bel_type.c_str(ctx));
+ warned_beltypes.insert(bel_type);
+ }
+ }
+ }
+
+ void init_global_clock()
+ {
+ // TODO: how do we extend this to more complex clocking topologies?
+ BelId global_clk_bel =
+ ctx->addBel(IdStringList::concat(ctx->id("X0Y0"), id_CLK), id_Global_Clock, Loc(0, 0, 0), true, false);
+ global_clk_wire = ctx->addWire(IdStringList::concat(ctx->id("X0Y0"), id_CLK), id_CLK, 0, 0);
+ ctx->addBelOutput(global_clk_bel, id_CLK, global_clk_wire);
+ }
+
+ // TODO: this is for legacy fabulous only, the new code path can be a lot simpler
+ void init_bels_v1()
+ {
+ std::ifstream in = open_data_rel("/npnroutput/bel.txt");
+ CsvParser csv(in);
+ init_global_clock();
+ while (csv.fetch_next_line()) {
+ IdString tile = csv.next_field().to_id(ctx);
+ int bel_x = csv.next_field().substr(1).to_int();
+ int bel_y = csv.next_field().substr(1).to_int();
+ auto bel_idx = csv.next_field();
+ IdString bel_type = csv.next_field().to_id(ctx);
+ NPNR_ASSERT(bel_idx.size() == 1);
+ int bel_z = bel_idx[0] - 'A';
+ NPNR_ASSERT(bel_z >= 0 && bel_z < 26);
+ /*
+ In the future we will need to handle optionally splitting SLICEs into separate LUT/COMB and FF bels
+ This is the preferred approach in nextpnr for arches where the LUT and FF can be used separately of
+ each other (e.g. there is a way of routing the LUT and FF outputs individually, and some extra
+ optional FF input).
+ While this isn't yet the standard fabulous SLICE, it should be considered as a future option in fabulous.
+ */
+ Loc loc(bel_x, bel_y, bel_z);
+ BelId bel = ctx->addBel(IdStringList::concat(tile, bel_idx.to_id(ctx)), bel_type, loc, false, false);
+ std::vector<parser_view> ports;
+ parser_view port;
+ while (!(port = csv.next_field()).empty()) {
+ ports.push_back(port);
+ }
+ handle_bel_ports(bel, tile, bel_type, ports);
+ }
+ postprocess_bels();
+ }
+
+ void init_bels_v2()
+ {
+ std::ifstream in = open_data_rel("/.FABulous/bel.v2.txt");
+ CsvParser csv(in);
+ init_global_clock();
+ BelId curr_bel;
+ while (csv.fetch_next_line()) {
+ IdString cmd = csv.next_field().to_id(ctx);
+ if (cmd == id_BelBegin) {
+ IdString tile = csv.next_field().to_id(ctx);
+ auto bel_idx = csv.next_field();
+ IdString bel_type = csv.next_field().to_id(ctx);
+ NPNR_ASSERT(bel_idx.size() == 1);
+ int bel_z = bel_idx[0] - 'A';
+ NPNR_ASSERT(bel_z >= 0 && bel_z < 26);
+ Loc loc = tile_loc(tile);
+ curr_bel = ctx->addBel(IdStringList::concat(tile, bel_idx.to_id(ctx)), bel_type,
+ Loc(loc.x, loc.y, bel_z), false, false);
+ } else if (cmd.in(id_I, id_O)) {
+ IdString port = csv.next_field().to_id(ctx);
+ auto wire_name = csv.next_field().split('.');
+ WireId wire =
+ get_wire(wire_name.first.to_id(ctx), wire_name.second.to_id(ctx), wire_name.second.to_id(ctx));
+ ctx->addBelPin(curr_bel, port, wire, cmd == id_O ? PORT_OUT : PORT_IN);
+ } else if (cmd == id_GlobalClk) {
+ IdStringList bel_name = ctx->getBelName(curr_bel);
+ WireId clk_wire = get_wire(bel_name[0], ctx->idf("%s_CLK", bel_name[1].c_str(ctx)), id_REG_CLK);
+ ctx->addBelInput(curr_bel, id_CLK, clk_wire);
+ add_pseudo_pip(global_clk_wire, clk_wire, id_global_clock);
+ } else if (cmd == id_CFG) {
+ // TODO...
+ } else if (cmd == id_BelEnd) {
+ curr_bel = BelId();
+ } else if (cmd != IdString()) {
+ log_error("unsupported command %s in definition of bel %s\n", cmd.c_str(ctx),
+ curr_bel == BelId() ? "<none>" : ctx->nameOfBel(curr_bel));
+ }
+ }
+ postprocess_bels();
+ }
+
+ void postprocess_bels()
+ {
+ // This does some post-processing on bels to make them useful for nextpnr place-and-route regardless of the code
+ // path that creates them. In the future, splitting muxes and creating split LCs would be done here, too
+ for (auto bel : ctx->getBels()) {
+ auto &data = ctx->bel_info(bel);
+ if (data.type == id_FABULOUS_LC) {
+ if (!data.pins.count(id_Q)) {
+ // Add a Q pseudo-pin and pseudo-pip from Q to O
+ WireId o_wire = ctx->getBelPinWire(bel, id_O);
+ IdString q_name = ctx->idf("%s_Q", data.name[1].c_str(ctx));
+ WireId q_wire = get_wire(data.name[0], q_name, q_name);
+ ctx->addBelOutput(bel, id_Q, q_wire);
+ // Pseudo-pip for FF mode
+ add_pseudo_pip(q_wire, o_wire, id_O2Q);
+ }
+ } else if (data.type == id_IO_1_bidirectional_frame_config_pass) {
+ if (!data.pins.count(id_PAD)) {
+ // Add a PAD pseudo-pin for the top level
+ ctx->addBelInout(bel, id_PAD,
+ get_wire(data.name[0], ctx->idf("PAD_%s", data.name[1].c_str(ctx)), id_PAD));
+ }
+ }
+ }
+ }
+
+ void init_pips()
+ {
+ std::ifstream in = open_data_rel(is_new_fab ? "/.FABulous/pips.txt" : "/npnroutput/pips.txt");
+ CsvParser csv(in);
+ while (csv.fetch_next_line()) {
+ IdString src_tile = csv.next_field().to_id(ctx);
+ IdString src_port = csv.next_field().to_id(ctx);
+ IdString dst_tile = csv.next_field().to_id(ctx);
+ IdString dst_port = csv.next_field().to_id(ctx);
+ int delay = csv.next_field().to_int();
+ IdString pip_name = csv.next_field().to_id(ctx);
+ WireId src_wire = get_wire(src_tile, src_port, src_port);
+ WireId dst_wire = get_wire(dst_tile, dst_port, dst_port);
+ ctx->addPip(IdStringList::concat(src_tile, pip_name), pip_name, src_wire, dst_wire,
+ ctx->getDelayFromNS(0.01 * delay), tile_loc(src_tile));
+ }
+ }
+
+ // Fast lookup of tile names to XY pairs
+ dict<IdString, Loc> tile2loc;
+ Loc tile_loc(IdString tile)
+ {
+ if (!tile2loc.count(tile)) {
+ std::string tile_name = tile.str(ctx);
+ parser_view view(tile_name);
+ NPNR_ASSERT(view[0] == 'X');
+ size_t ypos = view.find('Y');
+ NPNR_ASSERT(ypos != parser_view::npos);
+ int x = view.substr(1, ypos - 1).to_int();
+ int y = view.substr(ypos + 1).to_int();
+ tile2loc[tile] = Loc(x, y, 0);
+ }
+ return tile2loc.at(tile);
+ }
+
+ // Create a wire if it doesn't exist, otherwise just return it
+ WireId get_wire(IdString tile, IdString wire, IdString type)
+ {
+ // Create a wire name by using the built-in IdStringList mechanism to store a (tile, wire) pair
+ // this way we don't store a full string in memory of every concatenated wire name, reducing the memory
+ // footprint and start time significantly beyond the ~1k LUT scale
+ auto wire_name = IdStringList::concat(tile, wire);
+ auto found = ctx->wire_by_name.find(wire_name);
+ if (found != ctx->wire_by_name.end())
+ return found->second;
+ // doesn't exist
+ Loc loc = tile_loc(tile);
+ return ctx->addWire(wire_name, type, loc.x, loc.y);
+ }
+
+ CellTagger cell_tags;
+ void assign_cell_info()
+ {
+ for (auto &cell : ctx->cells) {
+ cell_tags.assign_for(ctx, cfg, cell.second.get());
+ }
+ }
+ void notifyBelChange(BelId bel, CellInfo *cell)
+ {
+ CellInfo *old = ctx->getBoundBelCell(bel);
+ blk_trk->update_bel(bel, old, cell);
+ }
+};
+
+struct FabulousArch : ViaductArch
+{
+ FabulousArch() : ViaductArch("fabulous"){};
+ std::unique_ptr<ViaductAPI> create(const dict<std::string, std::string> &args)
+ {
+ return std::make_unique<FabulousImpl>(args);
+ }
+} fabulousArch;
+} // namespace
+
+NEXTPNR_NAMESPACE_END
diff --git a/generic/viaduct/fabulous/fasm.cc b/generic/viaduct/fabulous/fasm.cc
new file mode 100644
index 00000000..1405766e
--- /dev/null
+++ b/generic/viaduct/fabulous/fasm.cc
@@ -0,0 +1,191 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2021-22 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 "fasm.h"
+#include "log.h"
+#include "nextpnr.h"
+#include "util.h"
+
+#include <algorithm>
+#include <boost/range/adaptor/reversed.hpp>
+#include <fstream>
+
+#define VIADUCT_CONSTIDS "viaduct/fabulous/constids.inc"
+#include "viaduct_constids.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+namespace {
+struct FabFasmWriter
+{
+ FabFasmWriter(const Context *ctx, const FabricConfig &cfg, const std::string &filename)
+ : ctx(ctx), cfg(cfg), out(filename)
+ {
+ if (!out)
+ log_error("failed to open fasm file '%s' for writing\n", filename.c_str());
+ }
+ std::string format_name(IdStringList name)
+ {
+ std::string result;
+ for (IdString entry : name) {
+ if (!result.empty())
+ result += ".";
+ result += entry.str(ctx);
+ }
+ return result;
+ }
+ void write_pip(PipId pip)
+ {
+ auto &data = ctx->pip_info(pip);
+ if (data.type.in(id_global_clock, id_O2Q))
+ return; // pseudo-pips with no underlying bitstream bits
+ // write pip name but with '.' instead of '/' for separator
+ out << format_name(data.name) << std::endl;
+ }
+ void write_routing(const NetInfo *net)
+ {
+ std::vector<PipId> sorted_pips;
+ for (auto &w : net->wires)
+ if (w.second.pip != PipId())
+ sorted_pips.push_back(w.second.pip);
+ std::sort(sorted_pips.begin(), sorted_pips.end());
+ out << stringf("# routing for net '%s'\n", ctx->nameOf(net)) << std::endl;
+ for (auto pip : sorted_pips)
+ write_pip(pip);
+ out << std::endl;
+ }
+
+ std::string prefix;
+
+ // Write a FASM bitvector; optionally inverting the values in the process
+ void write_vector(const std::string &name, const std::vector<bool> &value, bool invert = false)
+ {
+ out << prefix << name << " = " << int(value.size()) << "'b";
+ for (auto bit : boost::adaptors::reverse(value))
+ out << ((bit ^ invert) ? '1' : '0');
+ out << std::endl;
+ }
+ // Write a FASM bitvector given an integer value
+ void write_int_vector(const std::string &name, uint64_t value, int width, bool invert = false)
+ {
+ std::vector<bool> bits(width, false);
+ for (int i = 0; i < width; i++)
+ bits[i] = (value & (1ULL << i)) != 0;
+ write_vector(name, bits, invert);
+ }
+ // Write an int vector param
+ void write_int_vector_param(const CellInfo *cell, const std::string &name, uint64_t defval, int width,
+ bool invert = false)
+ {
+ uint64_t value = int_or_default(cell->params, ctx->id(name), defval);
+ std::vector<bool> bits(width, false);
+ for (int i = 0; i < width; i++)
+ bits[i] = (value & (1ULL << i)) != 0;
+ write_vector(stringf("%s[%d:0]", name.c_str(), width - 1), bits, invert);
+ }
+
+ void write_bool(const CellInfo *cell, const std::string &name)
+ {
+ if (bool_or_default(cell->params, ctx->id(name))) {
+ out << prefix << name << std::endl;
+ }
+ }
+
+ void write_logic(const CellInfo *lc)
+ {
+ prefix = format_name(ctx->getBelName(lc->bel)) + ".";
+ if (lc->type.in(id_FABULOUS_LC, id_FABULOUS_COMB)) {
+ write_int_vector_param(lc, "INIT", 0U, 1U << cfg.clb.lut_k); // todo lut depermute and thru
+ }
+ if (lc->type == id_FABULOUS_LC) {
+ write_bool(lc, "FF");
+ }
+ if (lc->type.in(id_FABULOUS_LC, id_FABULOUS_FF)) {
+ write_bool(lc, "SET_NORESET");
+ write_bool(lc, "NEG_CLK");
+ write_bool(lc, "NEG_EN");
+ write_bool(lc, "NEG_SR");
+ write_bool(lc, "ASYNC_SR");
+ }
+ }
+
+ void write_io(const CellInfo *io)
+ {
+ write_bool(io, "INPUT_USED");
+ write_bool(io, "OUTPUT_USED");
+ write_bool(io, "ENABLE_USED");
+ }
+
+ void write_generic_cell(const CellInfo *ci)
+ {
+ prefix = format_name(ctx->getBelName(ci->bel)) + ".";
+ for (auto &param : ci->params) {
+ // TODO: better parameter type auto-detection
+ if (param.second.is_string) {
+ // enum type parameter
+ out << prefix << param.first.c_str(ctx) << "." << param.second.str << std::endl;
+ } else if (param.second.str.size() == 1) {
+ // boolean type parameter
+ if (param.second.intval != 0)
+ out << prefix << param.first.c_str(ctx) << std::endl;
+ } else {
+ // vector type parameter
+ int msb = int(param.second.str.size()) - 1;
+ out << prefix << param.first.c_str(ctx) << "[" << msb << ":0] = ";
+ for (auto bit : boost::adaptors::reverse(param.second.str))
+ out << bit;
+ out << std::endl;
+ }
+ }
+ }
+
+ void write_cell(const CellInfo *ci)
+ {
+ out << stringf("# config for cell '%s'\n", ctx->nameOf(ci)) << std::endl;
+ if (ci->type.in(id_FABULOUS_COMB, id_FABULOUS_FF, id_FABULOUS_LC))
+ write_logic(ci);
+ else if (ci->type == id_IO_1_bidirectional_frame_config_pass)
+ write_io(ci);
+ else
+ write_generic_cell(ci);
+ // TODO: other cell types
+ out << std::endl;
+ }
+
+ void write_fasm()
+ {
+ for (const auto &net : ctx->nets)
+ write_routing(net.second.get());
+ for (const auto &cell : ctx->cells)
+ write_cell(cell.second.get());
+ }
+
+ const Context *ctx;
+ const FabricConfig &cfg;
+ std::ofstream out;
+};
+} // namespace
+
+void fabulous_write_fasm(const Context *ctx, const FabricConfig &cfg, const std::string &filename)
+{
+ FabFasmWriter wr(ctx, cfg, filename);
+ wr.write_fasm();
+}
+
+NEXTPNR_NAMESPACE_END
diff --git a/generic/viaduct/fabulous/fasm.h b/generic/viaduct/fabulous/fasm.h
new file mode 100644
index 00000000..0f152c64
--- /dev/null
+++ b/generic/viaduct/fabulous/fasm.h
@@ -0,0 +1,32 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2021-22 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 FABULOUS_FASM_H
+#define FABULOUS_FASM_H
+
+#include "fab_cfg.h"
+#include "nextpnr.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+void fabulous_write_fasm(const Context *ctx, const FabricConfig &cfg, const std::string &filename);
+
+NEXTPNR_NAMESPACE_END
+
+#endif
diff --git a/generic/viaduct/fabulous/pack.cc b/generic/viaduct/fabulous/pack.cc
new file mode 100644
index 00000000..0273a28a
--- /dev/null
+++ b/generic/viaduct/fabulous/pack.cc
@@ -0,0 +1,253 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2021-22 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 "pack.h"
+#include "log.h"
+#include "util.h"
+
+#define VIADUCT_CONSTIDS "viaduct/fabulous/constids.inc"
+#include "viaduct_constids.h"
+#include "viaduct_helpers.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+namespace {
+struct FabulousPacker
+{
+ Context *ctx;
+ const FabricConfig &cfg;
+ ViaductHelpers h;
+
+ dict<IdString, unsigned> lut_types;
+ std::vector<IdString> lut_inputs;
+
+ FabulousPacker(Context *ctx, const FabricConfig &cfg) : ctx(ctx), cfg(cfg)
+ {
+ // Set up some structures for faster lookups
+ for (unsigned i = 0; i < cfg.clb.lut_k; i++) {
+ lut_types[ctx->idf("LUT%d", i + 1)] = i + 1;
+ lut_inputs.push_back(ctx->idf("I%d", i));
+ }
+ h.init(ctx);
+ }
+
+ void pack_luts()
+ {
+ // Pack LUTs into FABULOUS_COMB (split-LUTFF mode) or FABULOUS_LC (packed-LUTFF mode)
+ // TODO: fracturable LUT handling
+ for (auto &cell : ctx->cells) {
+ CellInfo *ci = cell.second.get();
+ auto fnd_lut = lut_types.find(ci->type);
+ if (fnd_lut == lut_types.end())
+ continue;
+ unsigned lut_n = fnd_lut->second;
+ // convert to the necessary type
+ ci->type = cfg.clb.split_lc ? id_FABULOUS_COMB : id_FABULOUS_LC;
+ // add disconnected unused inputs
+ for (unsigned i = 0; i < cfg.clb.lut_k; i++)
+ if (!ci->ports.count(lut_inputs.at(i)))
+ ci->addInput(lut_inputs.at(i));
+ // replicate the INIT value so the unused MSBs become don't-cares
+ auto inst_init = get_or_default(ci->params, id_INIT, Property(0));
+ unsigned orig_init_len = 1U << lut_n, prim_len = 1U << cfg.clb.lut_k;
+ Property new_init(0, prim_len);
+ for (unsigned i = 0; i < prim_len; i += orig_init_len) {
+ auto chunk = inst_init.extract(0, orig_init_len);
+ for (unsigned j = 0; j < orig_init_len; j++)
+ new_init.str.at(i + j) = chunk.str.at(j);
+ }
+ new_init.update_intval();
+ ci->params[id_INIT] = new_init;
+ }
+ }
+
+ // Two-stage flipflop packing. First convert all the random primitives into a much easier-to-handle FABULOUS_FF
+ // Then for split-LC mode, cluster it to connected LUTs; separate-LC mode, pack it into a connected or new LC
+
+ void prepare_ffs()
+ {
+ for (auto &cell : ctx->cells) {
+ CellInfo *ci = cell.second.get();
+ const std::string &type_str = ci->type.str(ctx);
+ if (type_str.size() < 5 || type_str.substr(0, 5) != "LUTFF")
+ continue;
+ ci->type = id_FABULOUS_FF;
+ // parse config string and unify
+ size_t idx = 5;
+ if (idx < type_str.size() && type_str.at(idx) == '_')
+ ++idx;
+ // clock inversion
+ if (idx < type_str.size() && type_str.at(idx) == 'N') {
+ ci->params[id_NEG_CLK] = 1;
+ ++idx;
+ } else {
+ ci->params[id_NEG_CLK] = 0;
+ }
+ // clock enable
+ if (idx < type_str.size() && type_str.at(idx) == 'E')
+ ++idx;
+ if (ci->ports.count(id_E))
+ ci->renamePort(id_E, id_EN);
+ else
+ ci->addInput(id_EN); // autocreate emtpy enable port if enable missing or unused
+ // sr presence and type
+ std::string srt = type_str.substr(idx);
+ if (srt == "S") {
+ ci->params[id_SET_NORESET] = 1;
+ ci->params[id_ASYNC_SR] = 1;
+ } else if (srt == "R") {
+ ci->params[id_SET_NORESET] = 0;
+ ci->params[id_ASYNC_SR] = 1;
+ } else if (srt == "SS") {
+ ci->params[id_SET_NORESET] = 1;
+ ci->params[id_ASYNC_SR] = 0;
+ } else if (srt == "SR" || srt == "") {
+ ci->params[id_SET_NORESET] = 0;
+ ci->params[id_ASYNC_SR] = 0;
+ } else {
+ NPNR_ASSERT_FALSE("unhandled FF type");
+ }
+ if (ci->ports.count(id_S))
+ ci->renamePort(id_S, id_SR);
+ else if (ci->ports.count(id_R))
+ ci->renamePort(id_R, id_SR);
+ if (!ci->ports.count(id_SR))
+ ci->addInput(id_SR); // autocreate emtpy enable port if enable missing or unused
+ }
+ }
+
+ void pack_ffs()
+ {
+ pool<IdString> to_delete;
+ for (auto &cell : ctx->cells) {
+ CellInfo *ci = cell.second.get();
+ if (ci->type != id_FABULOUS_FF)
+ continue;
+ NetInfo *d = ci->getPort(id_D);
+ if (!d || !d->driver.cell)
+ continue;
+ CellInfo *drv = d->driver.cell;
+ if (drv->type != (cfg.clb.split_lc ? id_FABULOUS_COMB : id_FABULOUS_LC) || d->driver.port != id_O)
+ continue;
+ if (!cfg.clb.split_lc && d->users.entries() > 1)
+ continue; // TODO: could also resolve by duplicating LUT
+ if (drv->cluster != ClusterId()) {
+ // TODO: actually we can pack these often, we just have to be more careful to check control sets
+ continue;
+ }
+ // we can pack them together
+ if (cfg.clb.split_lc) {
+ // create/modify cluster and add constraints. copy from an arch where we do this already...
+ NPNR_ASSERT_FALSE("unimplemented");
+ } else {
+ to_delete.insert(ci->name);
+ // this connection is packed inside the LC
+ ci->disconnectPort(id_D);
+ drv->disconnectPort(id_O);
+ // move other ports/params
+ ci->movePortTo(id_CLK, drv, id_CLK);
+ ci->movePortTo(id_SR, drv, id_SR);
+ ci->movePortTo(id_EN, drv, id_EN);
+ ci->movePortTo(id_O, drv, id_Q);
+ drv->params[id_NEG_CLK] = ci->params[id_NEG_CLK];
+ drv->params[id_ASYNC_SR] = ci->params[id_ASYNC_SR];
+ drv->params[id_SET_NORESET] = ci->params[id_SET_NORESET];
+ drv->params[id_FF] = 1;
+ for (auto &attr : ci->attrs)
+ drv->attrs[attr.first] = attr.second;
+ }
+ }
+ for (auto del : to_delete)
+ ctx->cells.erase(del);
+ if (!cfg.clb.split_lc) {
+ // convert remaining ffs to their own lc
+ for (auto &cell : ctx->cells) {
+ CellInfo *ci = cell.second.get();
+ if (ci->type != id_FABULOUS_FF)
+ continue;
+ ci->type = id_FABULOUS_LC;
+ ci->renamePort(id_D, lut_inputs.at(0));
+ ci->renamePort(id_O, id_Q);
+ // configure LUT as a thru
+ Property init(1U << cfg.clb.lut_k);
+ for (unsigned i = 0; i < (1U << cfg.clb.lut_k); i += 2) {
+ init.str[i] = Property::S0;
+ init.str[i + 1] = Property::S1;
+ }
+ init.update_intval();
+ ci->params[id_INIT] = init;
+ ci->params[id_FF] = 1;
+ }
+ }
+ }
+
+ void update_bel_attrs()
+ {
+ // This new arch uses the new IdStringList system with a / separator
+ // old fabulous arches used a dot separator in bel names
+ // update old attributes for maximum cross-compat
+ for (auto &cell : ctx->cells) {
+ CellInfo *ci = cell.second.get();
+ if (!ci->attrs.count(id_BEL))
+ continue;
+ std::string &bel = ci->attrs.at(id_BEL).str;
+ if (bel.find('/') != std::string::npos) // new style
+ continue;
+ size_t dot_pos = bel.find('.');
+ if (dot_pos != std::string::npos)
+ bel[dot_pos] = '/';
+ }
+ }
+
+ void handle_constants()
+ {
+ const dict<IdString, Property> vcc_params = {{id_INIT, Property(0x3, 2)}};
+ const dict<IdString, Property> gnd_params = {{id_INIT, Property(0x0, 2)}};
+ h.replace_constants(CellTypePort(id_LUT1, id_O), CellTypePort(id_LUT1, id_O), vcc_params, gnd_params);
+ }
+
+ void handle_io()
+ {
+ // As per the preferred approach for new nextpnr flows, we require IO to be inserted by Yosys
+ // pre-place-and-route, or just manually instantiated
+ const pool<CellTypePort> top_ports{
+ CellTypePort(id_IO_1_bidirectional_frame_config_pass, id_PAD),
+ };
+ h.remove_nextpnr_iobs(top_ports);
+ }
+
+ void run()
+ {
+ update_bel_attrs();
+ handle_constants();
+ handle_io();
+ pack_luts();
+ prepare_ffs();
+ pack_ffs();
+ }
+};
+} // namespace
+
+void fabulous_pack(Context *ctx, const FabricConfig &cfg)
+{
+ FabulousPacker packer(ctx, cfg);
+ packer.run();
+}
+
+NEXTPNR_NAMESPACE_END
diff --git a/generic/viaduct/fabulous/pack.h b/generic/viaduct/fabulous/pack.h
new file mode 100644
index 00000000..0ee35e0c
--- /dev/null
+++ b/generic/viaduct/fabulous/pack.h
@@ -0,0 +1,32 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2021-22 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 FABULOUS_PACK_H
+#define FABULOUS_PACK_H
+
+#include "fab_cfg.h"
+#include "nextpnr.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+void fabulous_pack(Context *ctx, const FabricConfig &cfg);
+
+NEXTPNR_NAMESPACE_END
+
+#endif
diff --git a/generic/viaduct/fabulous/validity_check.cc b/generic/viaduct/fabulous/validity_check.cc
new file mode 100644
index 00000000..91dba689
--- /dev/null
+++ b/generic/viaduct/fabulous/validity_check.cc
@@ -0,0 +1,201 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2021-22 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 "validity_check.h"
+#include "log.h"
+#include "util.h"
+
+#define VIADUCT_CONSTIDS "viaduct/fabulous/constids.inc"
+#include "viaduct_constids.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+CLBState::CLBState(const LogicConfig &cfg)
+{
+ // TODO: more than one per LC if in split-SLICE mode with fracturable LUTs
+ lc_comb = std::make_unique<CellInfo *[]>(cfg.lc_per_clb);
+ if (cfg.split_lc) {
+ ff = std::make_unique<CellInfo *[]>(cfg.lc_per_clb * cfg.ff_per_lc);
+ }
+ // TODO: mux
+}
+
+void CellTagger::assign_for(const Context *ctx, const FabricConfig &cfg, const CellInfo *ci)
+{
+ if (int(data.size()) <= ci->flat_index)
+ data.resize(ci->flat_index + 1);
+ auto &t = data.at(ci->flat_index);
+ // Use the same logic to handle both packed and split LC modes
+ if (ci->type.in(id_FABULOUS_COMB, id_FABULOUS_LC)) {
+ unsigned lut_input_count = 0;
+ for (unsigned i = 0; i < cfg.clb.lut_k; i++)
+ if (ci->getPort(ctx->idf("I%d", i)))
+ lut_input_count = i + 1;
+ t.comb.lut_inputs = SSOArray<IdString, MAX_LUTK>(lut_input_count, IdString());
+ for (unsigned i = 0; i < lut_input_count; i++) {
+ const NetInfo *sig = ci->getPort(ctx->idf("I%d", i));
+ t.comb.lut_inputs[i] = sig ? sig->name : IdString();
+ }
+ t.comb.carry_used = false; // TODO
+ t.comb.lut_out = ci->getPort(id_O);
+ }
+ if (ci->type.in(id_FABULOUS_FF, id_FABULOUS_LC)) {
+ if (ci->type == id_FABULOUS_FF || bool_or_default(ci->params, id_FF)) {
+ t.ff.ff_used = true;
+ auto get_ctrlsig = [&](IdString name) {
+ const NetInfo *sig = ci->getPort(name);
+ bool invert = sig && bool_or_default(ci->params, ctx->idf("NEG_%s", name.c_str(ctx)));
+ return ControlSig(sig ? sig->name : id___disconnected, invert);
+ };
+ t.ff.clk = get_ctrlsig(id_CLK);
+ t.ff.sr = get_ctrlsig(id_SR);
+ t.ff.en = get_ctrlsig(id_EN);
+ t.ff.async = bool_or_default(ci->params, id_ASYNC_SR);
+ t.ff.latch = bool_or_default(ci->params, id_LATCH_NOFF);
+ t.ff.d = ci->getPort(id_D);
+ t.ff.q = ci->getPort(id_Q);
+ } else {
+ t.ff.ff_used = false;
+ }
+ }
+}
+
+void BlockTracker::set_bel_type(BelId bel, BelFlags::BlockType block, BelFlags::FuncType func, uint8_t index)
+{
+ Loc loc = ctx->getBelLocation(bel);
+ if (int(tiles.size()) <= loc.y)
+ tiles.resize(loc.y + 1);
+ auto &row = tiles.at(loc.y);
+ if (int(row.size()) <= loc.x)
+ row.resize(loc.x + 1);
+ auto &tile = row.at(loc.x);
+ if (block == BelFlags::BLOCK_CLB) {
+ if (!tile.clb)
+ tile.clb = std::make_unique<CLBState>(cfg.clb);
+ }
+ if (int(bel_data.size()) <= bel.index)
+ bel_data.resize(bel.index + 1);
+ auto &flags = bel_data.at(bel.index);
+ flags.block = block;
+ flags.func = func;
+ flags.index = index;
+}
+
+void BlockTracker::update_bel(BelId bel, CellInfo *old_cell, CellInfo *new_cell)
+{
+ if (bel.index >= int(bel_data.size()))
+ return; // some kind of bel not being tracked
+ auto flags = bel_data.at(bel.index);
+ if (flags.block == BelFlags::BLOCK_OTHER)
+ return; // no structures to update
+ Loc loc = ctx->getBelLocation(bel);
+ if (loc.y >= int(tiles.size()))
+ return; // some kind of bel not being tracked
+ const auto &row = tiles.at(loc.y);
+ if (loc.x >= int(row.size()))
+ return; // some kind of bel not being tracked
+ const auto &entry = row.at(loc.x);
+ if (flags.block == BelFlags::BLOCK_CLB) {
+ NPNR_ASSERT(entry.clb);
+ // TODO: incremental validity check updates might care about this in the future, hence keeping it in the
+ // interface for now
+ NPNR_UNUSED(old_cell);
+ if (flags.func == BelFlags::FUNC_LC_COMB)
+ entry.clb->lc_comb[flags.index] = new_cell;
+ else if (flags.func == BelFlags::FUNC_FF)
+ entry.clb->ff[flags.index] = new_cell;
+ else if (flags.func == BelFlags::FUNC_MUX)
+ entry.clb->mux[flags.index] = new_cell;
+ }
+}
+
+bool CLBState::check_validity(const LogicConfig &cfg, const CellTagger &cell_data)
+{
+ SSOArray<ControlSig, 2> used_clk(cfg.clk.routing.size()), used_sr(cfg.sr.routing.size()),
+ used_en(cfg.en.routing.size());
+ auto check_ctrlsig = [&](unsigned idx, ControlSig actual, const ControlSetConfig &ctrl,
+ SSOArray<ControlSig, 2> &used) {
+ // see if we have an already-matching signal available
+ for (unsigned i = 0; i < ctrl.routing.size(); i++) {
+ // doesn't route to this pin
+ if (((ctrl.routing.at(i) >> unsigned(idx)) & 0x1U) == 0)
+ continue;
+ if (used[i] == actual)
+ return true;
+ }
+ // see if we have a free slot available
+ for (unsigned i = 0; i < ctrl.routing.size(); i++) {
+ // doesn't route to this pin
+ if (((ctrl.routing.at(i) >> unsigned(idx)) & 0x1U) == 0)
+ continue;
+ if (used[i] != ControlSig())
+ continue;
+ used[i] = actual;
+ return true;
+ }
+ // no option available
+ return false;
+ };
+ for (unsigned z = 0; z < cfg.lc_per_clb; z++) {
+ // flipflop control set checking
+ if (cfg.split_lc) {
+ NPNR_ASSERT_FALSE("unimplemented!"); // TODO
+ } else {
+ NPNR_ASSERT(cfg.ff_per_lc == 1); // split-slice mode must be used for more
+ const CellInfo *lc = lc_comb[z];
+ if (!lc)
+ continue;
+ auto &lct = cell_data.get(lc);
+ if (lct.ff.ff_used) {
+ // check shared control signals
+ if (!check_ctrlsig(z, lct.ff.clk, cfg.clk, used_clk))
+ return false;
+ if (cfg.en.have_signal && !check_ctrlsig(z, lct.ff.en, cfg.en, used_en))
+ return false;
+ if (cfg.sr.have_signal && !check_ctrlsig(z, lct.ff.sr, cfg.sr, used_sr))
+ return false;
+ }
+ }
+ }
+ // TODO: other checks...
+ return true;
+}
+
+bool BlockTracker::check_validity(BelId bel, const FabricConfig &cfg, const CellTagger &cell_data)
+{
+ if (bel.index >= int(bel_data.size()))
+ return true; // some kind of bel not being tracked
+ auto flags = bel_data.at(bel.index);
+ if (flags.block == BelFlags::BLOCK_OTHER)
+ return true; // no structures to update
+ Loc loc = ctx->getBelLocation(bel);
+ if (loc.y >= int(tiles.size()))
+ return true; // some kind of bel not being tracked
+ const auto &row = tiles.at(loc.y);
+ if (loc.x >= int(row.size()))
+ return true; // some kind of bel not being tracked
+ const auto &entry = row.at(loc.x);
+ if (flags.block == BelFlags::BLOCK_CLB) {
+ return entry.clb->check_validity(cfg.clb, cell_data);
+ } else {
+ return true;
+ }
+}
+
+NEXTPNR_NAMESPACE_END
diff --git a/generic/viaduct/fabulous/validity_check.h b/generic/viaduct/fabulous/validity_check.h
new file mode 100644
index 00000000..43291f6e
--- /dev/null
+++ b/generic/viaduct/fabulous/validity_check.h
@@ -0,0 +1,127 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2021-22 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 VALIDITY_CHECKING_H
+#define VALIDITY_CHECKING_H
+
+#include "fab_cfg.h"
+#include "fab_defs.h"
+#include "sso_array.h"
+
+#include "nextpnr.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+// The validity checking engine for the fabulous configurable CLB
+
+// data that we tag onto cells for fast lookup, so we aren't doing slow hash map accesses in the inner-loop-critical
+// validity checking code
+struct ControlSig
+{
+ ControlSig() : net(), invert(false){};
+ ControlSig(IdString net, bool invert) : net(net), invert(invert){};
+ IdString net;
+ bool invert;
+ bool operator==(const ControlSig &other) const { return net == other.net && invert == other.invert; }
+ bool operator!=(const ControlSig &other) const { return net != other.net || invert != other.invert; }
+};
+
+struct CellTags
+{
+ struct
+ {
+ SSOArray<IdString, MAX_LUTK> lut_inputs; // for checking fracturable LUTs
+ bool carry_used = false;
+ const NetInfo *lut_out = nullptr;
+ // ...
+ } comb; // data for LUTs, or the LUT part of combined LUT+FF cells
+ struct
+ {
+ ControlSig clk, sr, en;
+ bool ff_used = false;
+ bool async = false;
+ bool latch = false;
+ const NetInfo *d = nullptr, *q = nullptr;
+ } ff; // data for FFs, or the FF part of combined LUT+FF cells
+};
+
+// map between cell and tags, using the flat_index that viaduct defines for this purpose
+struct CellTagger
+{
+ std::vector<CellTags> data;
+ const CellTags &get(const CellInfo *ci) const { return data.at(ci->flat_index); }
+ void assign_for(const Context *ctx, const FabricConfig &cfg, const CellInfo *ci);
+};
+
+// we need to add some extra data to CLB bels to track what they do, so we can update CLBState accordingly
+struct BelFlags
+{
+ enum BlockType : uint8_t
+ {
+ BLOCK_OTHER,
+ BLOCK_CLB,
+ // ...
+ } block = BlockType::BLOCK_OTHER;
+ enum FuncType : uint8_t
+ {
+ FUNC_LC_COMB,
+ FUNC_FF,
+ FUNC_MUX,
+ FUNC_OTHER,
+ } func;
+ uint8_t index;
+ // ...
+};
+
+// state of a CLB, for fast bel->cell lookup
+// TODO: add valid/dirty tracking for incremental validity re-checking, important once we have bigger/more complex CLBs
+// (cf. xilinx/intel arches in nextpnr)
+struct CLBState
+{
+ explicit CLBState(const LogicConfig &cfg);
+ // In combined-LC mode (LC bel contains LUT and FF), this indexes the entire LC bel to cell. In separate mode, this
+ // indexes the combinational part (LUT or LUT+carry only).
+ std::unique_ptr<CellInfo *[]> lc_comb;
+ // In split-LC mode only, this maps FF bel (in CLB) index to cell
+ std::unique_ptr<CellInfo *[]> ff;
+ // If there is (a) separate mux bel(s), map them to cells
+ std::unique_ptr<CellInfo *[]> mux;
+ bool check_validity(const LogicConfig &cfg, const CellTagger &cell_data);
+};
+
+struct BlockTracker
+{
+ Context *ctx;
+ const FabricConfig &cfg;
+ std::vector<BelFlags> bel_data;
+
+ BlockTracker(Context *ctx, const FabricConfig &cfg) : ctx(ctx), cfg(cfg){};
+ void set_bel_type(BelId bel, BelFlags::BlockType block, BelFlags::FuncType func, uint8_t index);
+ void update_bel(BelId bel, CellInfo *old_cell, CellInfo *new_cell);
+ struct TileData
+ {
+ std::unique_ptr<CLBState> clb;
+ // ...
+ };
+ std::vector<std::vector<TileData>> tiles;
+ bool check_validity(BelId bel, const FabricConfig &cfg, const CellTagger &cell_data);
+};
+
+NEXTPNR_NAMESPACE_END
+
+#endif