From f4230553901de30f07be5906471b219ed255cb6e Mon Sep 17 00:00:00 2001 From: gatecat Date: Thu, 4 Aug 2022 09:04:02 +0200 Subject: fabulous: Add a viaduct uarch Signed-off-by: gatecat --- generic/viaduct/fabulous/constids.inc | 88 +++++++ generic/viaduct/fabulous/fab_cfg.h | 102 ++++++++ generic/viaduct/fabulous/fab_defs.h | 39 +++ generic/viaduct/fabulous/fabric_parsing.h | 175 +++++++++++++ generic/viaduct/fabulous/fabulous.cc | 396 +++++++++++++++++++++++++++++ generic/viaduct/fabulous/fasm.cc | 191 ++++++++++++++ generic/viaduct/fabulous/fasm.h | 32 +++ generic/viaduct/fabulous/pack.cc | 253 ++++++++++++++++++ generic/viaduct/fabulous/pack.h | 32 +++ generic/viaduct/fabulous/validity_check.cc | 201 +++++++++++++++ generic/viaduct/fabulous/validity_check.h | 127 +++++++++ 11 files changed, 1636 insertions(+) create mode 100644 generic/viaduct/fabulous/constids.inc create mode 100644 generic/viaduct/fabulous/fab_cfg.h create mode 100644 generic/viaduct/fabulous/fab_defs.h create mode 100644 generic/viaduct/fabulous/fabric_parsing.h create mode 100644 generic/viaduct/fabulous/fabulous.cc create mode 100644 generic/viaduct/fabulous/fasm.cc create mode 100644 generic/viaduct/fabulous/fasm.h create mode 100644 generic/viaduct/fabulous/pack.cc create mode 100644 generic/viaduct/fabulous/pack.h create mode 100644 generic/viaduct/fabulous/validity_check.cc create mode 100644 generic/viaduct/fabulous/validity_check.h (limited to 'generic/viaduct') 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 + * + * 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 +#include + +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 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 + * + * 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 +#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 + * + * 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 +#include +#include +#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::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 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 ∈ + 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 + * + * 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 + +#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 + +NEXTPNR_NAMESPACE_BEGIN + +namespace { +struct FabulousImpl : ViaductAPI +{ + FabulousImpl(const dict &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(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 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 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 &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 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() ? "" : 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 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 create(const dict &args) + { + return std::make_unique(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 + * + * 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 +#include +#include + +#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 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 &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 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 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 ¶m : 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 + * + * 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 + * + * 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 lut_types; + std::vector 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 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 vcc_params = {{id_INIT, Property(0x3, 2)}}; + const dict 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 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 + * + * 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 + * + * 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(cfg.lc_per_clb); + if (cfg.split_lc) { + ff = std::make_unique(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(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(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 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 &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 + * + * 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 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 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 lc_comb; + // In split-LC mode only, this maps FF bel (in CLB) index to cell + std::unique_ptr ff; + // If there is (a) separate mux bel(s), map them to cells + std::unique_ptr mux; + bool check_validity(const LogicConfig &cfg, const CellTagger &cell_data); +}; + +struct BlockTracker +{ + Context *ctx; + const FabricConfig &cfg; + std::vector 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 clb; + // ... + }; + std::vector> tiles; + bool check_validity(BelId bel, const FabricConfig &cfg, const CellTagger &cell_data); +}; + +NEXTPNR_NAMESPACE_END + +#endif -- cgit v1.2.3