From ca74f47c3f0f0d3f22b7a7e9920dafa589cc2f3e Mon Sep 17 00:00:00 2001 From: Leo Moser Date: Thu, 9 Apr 2026 11:38:36 +0200 Subject: [PATCH] Improvements to FABulous (#1692) * fabulous: fix I0mux naming Signed-off-by: Leo Moser * fabulous: pack more FF types: reset before enable Signed-off-by: Leo Moser * fabulous: fix block tracking of FABULOUS_LC, improve debug messages, fix masking of 1 Signed-off-by: Leo Moser * fabulous: add 'corner' argument Signed-off-by: Leo Moser --------- Signed-off-by: Leo Moser --- generic/viaduct/fabulous/fab_cfg.h | 7 +- generic/viaduct/fabulous/fabulous.cc | 63 ++++++++++++-- generic/viaduct/fabulous/fasm.cc | 2 +- generic/viaduct/fabulous/pack.cc | 96 ++++++++++++++++++---- generic/viaduct/fabulous/validity_check.cc | 41 ++++++--- generic/viaduct/fabulous/validity_check.h | 4 +- 6 files changed, 176 insertions(+), 37 deletions(-) diff --git a/generic/viaduct/fabulous/fab_cfg.h b/generic/viaduct/fabulous/fab_cfg.h index 17f066cc..029e9ef7 100644 --- a/generic/viaduct/fabulous/fab_cfg.h +++ b/generic/viaduct/fabulous/fab_cfg.h @@ -45,7 +45,12 @@ struct ControlSetConfig */ std::vector routing; // default 1 shared between all bool have_signal = true; - int can_mask = -1; + enum MaskType + { + MASK_NONE = -1, + MASK_ZERO = 0, + MASK_ONE = 1 + } can_mask = MaskType::MASK_NONE; bool can_invert = false; }; diff --git a/generic/viaduct/fabulous/fabulous.cc b/generic/viaduct/fabulous/fabulous.cc index 6ed5802f..e3b9bc65 100644 --- a/generic/viaduct/fabulous/fabulous.cc +++ b/generic/viaduct/fabulous/fabulous.cc @@ -53,6 +53,8 @@ struct FabulousImpl : ViaductAPI cfg.clb.lut_k = std::stoi(a.second); else if (a.first == "pcf") pcf_file = a.second; + else if (a.first == "corner") + corner = a.second; else log_error("unrecognised fabulous option '%s'\n", a.first.c_str()); } @@ -87,16 +89,16 @@ struct FabulousImpl : ViaductAPI { // TODO: loading from file or something uint64_t default_routing = (1ULL << (cfg.clb.lc_per_clb * cfg.clb.ff_per_lc)) - 1; - auto setup_cfg = [&](ControlSetConfig &ctrl, int mask) { + auto setup_cfg = [&](ControlSetConfig &ctrl, ControlSetConfig::MaskType mask) { ctrl.routing.clear(); ctrl.routing.push_back(default_routing); ctrl.can_mask = mask; ctrl.can_invert = false; }; - setup_cfg(cfg.clb.clk, -1); - setup_cfg(cfg.clb.en, 1); - setup_cfg(cfg.clb.sr, 0); + setup_cfg(cfg.clb.clk, ControlSetConfig::MaskType::MASK_NONE); // clk can not be masked + setup_cfg(cfg.clb.en, ControlSetConfig::MaskType::MASK_ONE); // en can be masked with 1 + setup_cfg(cfg.clb.sr, ControlSetConfig::MaskType::MASK_ZERO); // sr can be masked with 0 } void update_cell_timing(Context *ctx) @@ -154,9 +156,38 @@ struct FabulousImpl : ViaductAPI assign_cell_info(); update_cell_timing(ctx); } + + void postPlace() override + { + if (ctx->debug) { + log_info("================== Final Placement ==================\n"); + for (auto &cell : ctx->cells) { + auto ci = cell.second.get(); + if (ci->bel != BelId()) { + log_info("%s: %s\n", ctx->nameOfBel(ci->bel), ctx->nameOf(ci)); + if (ctx->getBelType(ci->bel).in(id_FABULOUS_LC)) { + for (IdString port : {id_CLK, id_SR, id_EN}) { + if (ci->ports.count(port)) { + WireId wire = ctx->getBelPinWire(ci->bel, port); + PortInfo pi = ci->ports[port]; + if (pi.net) { + log_info("- %s/%s: %s\n", ctx->getWireName(wire)[0].c_str(ctx), + ctx->getWireName(wire)[1].c_str(ctx), pi.net->name.c_str(ctx)); + } + } + } + } + } else { + log_info("unknown: %s\n", ctx->nameOf(ci)); + } + } + log_break(); + } + } + bool isBelLocationValid(BelId bel, bool explain_invalid) const override { - return blk_trk->check_validity(bel, cfg, cell_tags); + return blk_trk->check_validity(bel, cfg, cell_tags, explain_invalid); } private: @@ -167,6 +198,8 @@ struct FabulousImpl : ViaductAPI std::string pcf_file; + std::string corner; + std::unique_ptr blk_trk; std::string get_env_var(const std::string &name, const std::string &prompt = "") @@ -318,6 +351,7 @@ struct FabulousImpl : ViaductAPI // TODO: this is for legacy fabulous only, the new code path can be a lot simpler void init_bels_v1() { + log_info("Reading BELs file: /npnroutput/bel.txt\n"); std::ifstream in = open_data_rel("/npnroutput/bel.txt"); CsvParser csv(in); while (csv.fetch_next_line()) { @@ -357,6 +391,7 @@ struct FabulousImpl : ViaductAPI void init_bels_v2() { + log_info("Reading BELs file: /.FABulous/bel.v2.txt\n"); std::ifstream in = open_data_rel("/.FABulous/bel.v2.txt"); CsvParser csv(in); BelId curr_bel; @@ -382,6 +417,11 @@ struct FabulousImpl : ViaductAPI Loc loc = tile_loc(tile); curr_bel = ctx->addBel(IdStringList::concat(tile, bel_name), bel_type, Loc(loc.x, loc.y, bel_z), false, false); + + // add FABULOUS_LC to the block tracker to check the control set + if (bel_type.in(id_FABULOUS_LC)) { + blk_trk->set_bel_type(curr_bel, BelFlags::BLOCK_CLB, BelFlags::FUNC_LC_COMB, bel_z); + } } else if (cmd.in(id_I, id_O)) { IdString port = csv.next_field().to_id(ctx); auto wire_name = csv.next_field().split('.'); @@ -476,7 +516,18 @@ struct FabulousImpl : ViaductAPI int max_x = 0, max_y = 0; void init_pips() { - std::ifstream in = open_data_rel(is_new_fab ? "/.FABulous/pips.txt" : "/npnroutput/pips.txt"); + // PIP file selection + std::string pips_file = "/npnroutput/pips.txt"; + if (is_new_fab) { + if (!corner.empty()) { + pips_file = stringf("/.FABulous/pips.%s.txt", corner.c_str()); + } else { + pips_file = "/.FABulous/pips.txt"; + } + } + + log_info("Reading PIPs file: %s\n", pips_file.c_str()); + std::ifstream in = open_data_rel(pips_file); CsvParser csv(in); while (csv.fetch_next_line()) { IdString src_tile = csv.next_field().to_id(ctx); diff --git a/generic/viaduct/fabulous/fasm.cc b/generic/viaduct/fabulous/fasm.cc index a2733502..650511bc 100644 --- a/generic/viaduct/fabulous/fasm.cc +++ b/generic/viaduct/fabulous/fasm.cc @@ -170,7 +170,7 @@ struct FabFasmWriter unsigned width = 1U << cfg.clb.lut_k; write_int_vector(stringf("INIT[%d:0]", width - 1), init, width); // todo lut depermute and thru if (bool_or_default(lc->params, id_I0MUX, false)) - add_feature("IOmux"); // typo in FABulous? + add_feature("I0mux"); } if (lc->type == id_FABULOUS_LC) { write_bool(lc, "FF"); diff --git a/generic/viaduct/fabulous/pack.cc b/generic/viaduct/fabulous/pack.cc index 468ccc99..0e47b316 100644 --- a/generic/viaduct/fabulous/pack.cc +++ b/generic/viaduct/fabulous/pack.cc @@ -97,16 +97,44 @@ struct FabulousPacker void prepare_ffs() { + // The following LUTFF are supported: + // Enable before reset: LUTFF_[N][E][AS|AR|SS|SR] + // Reset before enable: LUTFF_[N][AS|AR|SS|SR][E] + + // Note: A simple LUTFF will have SR and EN disconnected, + // ensure that the default value is SR=0 and EN=1. + + // N ... clock inversion (NEG_CLK=1) + // E ... clock enable (EN port) + // AS ... async set (SET_NORESET=1, ASYNC_SR=1) + // AR ... async reset (SET_NORESET=0, ASYNC_SR=1) + // SS ... sync set (SET_NORESET=1, ASYNC_SR=0) + // SR ... sync reset (SET_NORESET=0, ASYNC_SR=0) + + // The ports of FABULOUS_FF are: + // - CLK: clock + // - SR: set/reset + // - EN: enable + + // The parameters of FABULOUS_FF are: + // - SET_NORESET: the value to load into the flip-flop when SR=1 + // - ASYNC_SR: used to indicate asynchronous load + // - NEG_CLK: invert the clock + + // Note: Both ASYNC_SR and NEG_CLK are currently unused by the default FABulous fabric. + 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; @@ -114,36 +142,70 @@ struct FabulousPacker } else { ci->params[id_NEG_CLK] = 0; } - // clock enable + + // clock enable (enable before reset) 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") { + + // default settings + ci->params[id_SET_NORESET] = 0; + ci->params[id_ASYNC_SR] = 0; + + // synchronous set + if (idx < type_str.size() - 1 && type_str.substr(idx, 2) == "SS") { ci->params[id_SET_NORESET] = 1; ci->params[id_ASYNC_SR] = 0; - } else if (srt == "SR" || srt == "") { + idx += 2; + } + + // synchronous reset + if (idx < type_str.size() - 1 && type_str.substr(idx, 2) == "SR") { ci->params[id_SET_NORESET] = 0; ci->params[id_ASYNC_SR] = 0; - } else { + idx += 2; + } + + // asynchronous set + if (idx < type_str.size() - 1 && type_str.substr(idx, 2) == "AS") { + ci->params[id_SET_NORESET] = 1; + ci->params[id_ASYNC_SR] = 1; + idx += 2; + } + + // asynchronous reset + if (idx < type_str.size() - 1 && type_str.substr(idx, 2) == "AR") { + ci->params[id_SET_NORESET] = 0; + ci->params[id_ASYNC_SR] = 1; + idx += 2; + } + + // clock enable (reset before enable) + if (idx < type_str.size() && type_str.at(idx) == 'E') + ++idx; + + // check that we are the end of the string + if (idx != type_str.size()) { + log_error("unhandled FF type %s of cell %s\n", type_str.c_str(), ci->name.c_str(ctx)); NPNR_ASSERT_FALSE("unhandled FF type"); } + + // Rename S/R ports to SR 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); + + // Rename E port to EN + if (ci->ports.count(id_E)) + ci->renamePort(id_E, id_EN); + + // autocreate empty set/reset port if enable missing or unused if (!ci->ports.count(id_SR)) - ci->addInput(id_SR); // autocreate emtpy enable port if enable missing or unused + ci->addInput(id_SR); + + // autocreate empty enable port if enable missing or unused + if (!ci->ports.count(id_EN)) + ci->addInput(id_EN); } } diff --git a/generic/viaduct/fabulous/validity_check.cc b/generic/viaduct/fabulous/validity_check.cc index 10b67c3e..7201bb08 100644 --- a/generic/viaduct/fabulous/validity_check.cc +++ b/generic/viaduct/fabulous/validity_check.cc @@ -130,16 +130,18 @@ void BlockTracker::update_bel(BelId bel, CellInfo *old_cell, CellInfo *new_cell) } } -bool CLBState::check_validity(const LogicConfig &cfg, const CellTagger &cell_data) +bool CLBState::check_validity(const LogicConfig &cfg, const CellTagger &cell_data, bool explain_invalid) { 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) { - if (ctrl.can_mask != -1) { + if (ctrl.can_mask != ControlSetConfig::MaskType::MASK_NONE) { // Using the per-entry control signal masking - if (actual.net == id___disconnected || (actual.net == id__CONST0 && ctrl.can_mask == 0) || - (actual.net == id__CONST1 && ctrl.can_mask == 0)) { + if (actual.net == id___disconnected || + (actual.net == id__CONST0 && ctrl.can_mask == ControlSetConfig::MaskType::MASK_ZERO) || + (actual.net == id__CONST1 && ctrl.can_mask == ControlSetConfig::MaskType::MASK_ONE)) { return true; } } @@ -164,6 +166,7 @@ bool CLBState::check_validity(const LogicConfig &cfg, const CellTagger &cell_dat // no option available return false; }; + for (unsigned z = 0; z < cfg.lc_per_clb; z++) { // flipflop control set checking if (cfg.split_lc) { @@ -176,15 +179,28 @@ bool CLBState::check_validity(const LogicConfig &cfg, const CellTagger &cell_dat 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)) + if (!check_ctrlsig(z, lct.ff.clk, cfg.clk, used_clk)) { + if (explain_invalid) { + log_nonfatal_error("CLK control signal invalid.\n"); + } return false; - if (cfg.en.have_signal && !check_ctrlsig(z, lct.ff.en, cfg.en, used_en)) + } + if (cfg.en.have_signal && !check_ctrlsig(z, lct.ff.en, cfg.en, used_en)) { + if (explain_invalid) { + log_nonfatal_error("EN control signal invalid.\n"); + } return false; - if (cfg.sr.have_signal && !check_ctrlsig(z, lct.ff.sr, cfg.sr, used_sr)) + } + if (cfg.sr.have_signal && !check_ctrlsig(z, lct.ff.sr, cfg.sr, used_sr)) { + if (explain_invalid) { + log_nonfatal_error("SR control signal invalid.\n"); + } return false; + } } } } + // don't allow mixed MUX types in the classic fabulous arch where ctrl sigs are shared int tile_mux_type = 0; for (unsigned z = 0; z < cfg.lc_per_clb; z++) { @@ -202,14 +218,19 @@ bool CLBState::check_validity(const LogicConfig &cfg, const CellTagger &cell_dat NPNR_ASSERT_FALSE("unknown mux type"); if (tile_mux_type == 0) tile_mux_type = this_mux; - else if (tile_mux_type != this_mux) + else if (tile_mux_type != this_mux) { + if (explain_invalid) { + log_nonfatal_error("Invalid mux type.\n"); + } return false; + } } // TODO: other checks... + return true; } -bool BlockTracker::check_validity(BelId bel, const FabricConfig &cfg, const CellTagger &cell_data) +bool BlockTracker::check_validity(BelId bel, const FabricConfig &cfg, const CellTagger &cell_data, bool explain_invalid) { if (bel.index >= int(bel_data.size())) return true; // some kind of bel not being tracked @@ -224,7 +245,7 @@ bool BlockTracker::check_validity(BelId bel, const FabricConfig &cfg, const Cell 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); + return entry.clb->check_validity(cfg.clb, cell_data, explain_invalid); } else { return true; } diff --git a/generic/viaduct/fabulous/validity_check.h b/generic/viaduct/fabulous/validity_check.h index bb74de59..240d2279 100644 --- a/generic/viaduct/fabulous/validity_check.h +++ b/generic/viaduct/fabulous/validity_check.h @@ -101,7 +101,7 @@ struct CLBState 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); + bool check_validity(const LogicConfig &cfg, const CellTagger &cell_data, bool explain_invalid = false); }; struct BlockTracker @@ -119,7 +119,7 @@ struct BlockTracker // ... }; std::vector> tiles; - bool check_validity(BelId bel, const FabricConfig &cfg, const CellTagger &cell_data); + bool check_validity(BelId bel, const FabricConfig &cfg, const CellTagger &cell_data, bool explain_invalid); }; struct PseudoPipTags