mirror of https://github.com/YosysHQ/nextpnr.git
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:
parent
ae7843fbf0
commit
ca74f47c3f
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue