Improvements to FABulous (#1692)

* fabulous: fix I0mux naming

Signed-off-by: Leo Moser <leomoser99@gmail.com>

* fabulous: pack more FF types: reset before enable

Signed-off-by: Leo Moser <leomoser99@gmail.com>

* fabulous: fix block tracking of FABULOUS_LC, improve debug messages, fix masking of 1

Signed-off-by: Leo Moser <leomoser99@gmail.com>

* fabulous: add 'corner' argument

Signed-off-by: Leo Moser <leomoser99@gmail.com>

---------

Signed-off-by: Leo Moser <leomoser99@gmail.com>
This commit is contained in:
Leo Moser 2026-04-09 11:38:36 +02:00 committed by GitHub
parent ae7843fbf0
commit ca74f47c3f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 176 additions and 37 deletions

View File

@ -45,7 +45,12 @@ struct ControlSetConfig
*/
std::vector<route_mask_t> 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;
};

View File

@ -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<BlockTracker> 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);

View File

@ -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");

View File

@ -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);
}
}

View File

@ -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<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) {
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;
}

View File

@ -101,7 +101,7 @@ struct CLBState
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);
bool check_validity(const LogicConfig &cfg, const CellTagger &cell_data, bool explain_invalid = false);
};
struct BlockTracker
@ -119,7 +119,7 @@ struct BlockTracker
// ...
};
std::vector<std::vector<TileData>> 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