mirror of https://github.com/YosysHQ/nextpnr.git
fabulous: add generic way to set BEL timings and placer/router tunables
Add bel.v3.txt, which carries per-arc BEL timing (Delay/SetupHold/ ClkToOut/Clock) with config-bit conditions, on top of bel.v2's structural blocks. Timing is applied per instance: a representative per-type estimate in prePlace, then each placed BEL's own arcs in postPlace (bel_timing_by_bel, keyed by BelId). Add placement_estimate.txt carrying BEL timing estimates and nextpnr placer/router tunables (delayScale/delayOffset/delayEpsilon/ripupPenalty/ carryPredictDelay, each defaulting to nextpnr's historical hardcoded value). A more detailed description of the FABulous model files can be found here: https://fabulous.readthedocs.io/en/latest/user_guide/using_doc/pnr/nextpnr.html#bel-v3-txt-bel-timing bel.v3.txt and placement_estimate.txt were added as separate files to keep compatibility with older nextpnr versions and existing fabulous projects. A few cosmetics and comments added.
This commit is contained in:
parent
ddc6c8c8b9
commit
d53b7b8eed
|
|
@ -90,6 +90,10 @@ X(BelBegin)
|
|||
X(BelEnd)
|
||||
X(GlobalClk)
|
||||
X(CFG)
|
||||
X(Delay)
|
||||
X(SetupHold)
|
||||
X(ClkToOut)
|
||||
X(Clock)
|
||||
|
||||
X(FABULOUS_MUX2)
|
||||
X(FABULOUS_MUX4)
|
||||
|
|
|
|||
|
|
@ -58,8 +58,9 @@ 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)
|
||||
bool split_lc = false; // represent SLICE as one bel or separate lut+ff (latter matters if ff and lut are used
|
||||
// separately). Never enabled today, so the FABULOUS_COMB path (pack.cc) is unused and
|
||||
// cells are always packed as FABULOUS_LC.
|
||||
|
||||
// ** LUT config
|
||||
unsigned lut_k = 4; // base number of inputs for lookup table
|
||||
|
|
|
|||
|
|
@ -42,6 +42,39 @@
|
|||
NEXTPNR_NAMESPACE_BEGIN
|
||||
|
||||
namespace {
|
||||
|
||||
// A single predicate gating a timing arc, parsed from a bel.v3 condition field.
|
||||
// PARAM matches a config bit / cell parameter; PORT_ANY/NONE_CONNECTED match the
|
||||
// connectivity of one or more ports (the '/'-list expresses OR).
|
||||
struct TimingPredicate
|
||||
{
|
||||
enum Kind
|
||||
{
|
||||
PARAM,
|
||||
PORT_ANY_CONNECTED,
|
||||
PORT_NONE_CONNECTED
|
||||
} kind;
|
||||
IdString param;
|
||||
bool param_value = true;
|
||||
std::vector<IdString> ports;
|
||||
};
|
||||
|
||||
// One timing arc for a BEL type, mapping to an addCellTiming* call when its
|
||||
// (optional, AND-ed) conditions hold for a given cell.
|
||||
struct BelTimingArc
|
||||
{
|
||||
enum Kind
|
||||
{
|
||||
DELAY,
|
||||
SETUPHOLD,
|
||||
CLK2OUT,
|
||||
CLOCK
|
||||
} kind;
|
||||
IdString from, to;
|
||||
float v0 = 0, v1 = 0;
|
||||
std::vector<TimingPredicate> cond;
|
||||
};
|
||||
|
||||
struct FabulousImpl : ViaductAPI
|
||||
{
|
||||
FabulousImpl(const dict<std::string, std::string> &args)
|
||||
|
|
@ -76,13 +109,47 @@ struct FabulousImpl : ViaductAPI
|
|||
// To consider: a faster serialised form of the device data (like bba that other arches use) so we don't have to
|
||||
// go through the whole csv parsing malarkey each time
|
||||
blk_trk = std::make_unique<BlockTracker>(ctx, cfg);
|
||||
is_new_fab ? init_bels_v2() : init_bels_v1();
|
||||
bool has_v3 = is_new_fab && std::filesystem::exists(fab_root + "/.FABulous/bel.v3.txt");
|
||||
if (has_v3)
|
||||
init_bels_v2("/.FABulous/bel.v3.txt"); // same parser; v3 adds timing lines
|
||||
else if (is_new_fab)
|
||||
init_bels_v2("/.FABulous/bel.v2.txt");
|
||||
else
|
||||
init_bels_v1();
|
||||
init_pips();
|
||||
init_pseudo_constant_wires();
|
||||
setup_lut_permutation();
|
||||
ctx->setDelayScaling(3.0, 3.0);
|
||||
ctx->delay_epsilon = 0.25;
|
||||
ctx->ripup_penalty = 0.5;
|
||||
// bel.v3.txt and placement_estimate.txt are generated together; having
|
||||
// only one means a stale/partial .FABulous dir, so warn.
|
||||
bool has_estimate = std::filesystem::exists(fab_root + "/.FABulous/placement_estimate.txt");
|
||||
if (has_v3 != has_estimate)
|
||||
log_warning("only one of bel.v3.txt / placement_estimate.txt is present; regenerate both with "
|
||||
"gen_model_npnr\n");
|
||||
// placement_estimate.txt provides these tunables; each falls back to
|
||||
// nextpnr's historical hardcoded default when the file/key is absent
|
||||
// (legacy or unregenerated projects), so behaviour is unchanged without it.
|
||||
// delayScale/Offset are FABulous's precomputed average pip delay.
|
||||
dict<std::string, double> pe;
|
||||
read_placement_estimate(pe);
|
||||
pool<std::string> known_keys;
|
||||
auto pe_or = [&](const char *key, double def) {
|
||||
known_keys.insert(key);
|
||||
if (!pe.count(key)) {
|
||||
log_info("placement_estimate.txt: no value for '%s', using default %g\n", key, def);
|
||||
return def;
|
||||
}
|
||||
return pe.at(key);
|
||||
};
|
||||
double delay_scale = pe_or("delayScale", 3.0);
|
||||
double delay_offset = pe_or("delayOffset", 3.0);
|
||||
ctx->setDelayScaling(delay_scale, delay_offset);
|
||||
carry_predict_delay = pe_or("carryPredictDelay", 0.5);
|
||||
ctx->delay_epsilon = pe_or("delayEpsilon", 0.25);
|
||||
ctx->ripup_penalty = pe_or("ripupPenalty", 0.5);
|
||||
for (auto &kv : pe)
|
||||
if (!known_keys.count(kv.first))
|
||||
log_warning("unknown placement_estimate.txt key '%s' ignored\n", kv.first.c_str());
|
||||
seed_default_estimates();
|
||||
}
|
||||
|
||||
void init_default_ctrlset_cfg()
|
||||
|
|
@ -101,39 +168,55 @@ struct FabulousImpl : ViaductAPI
|
|||
setup_cfg(cfg.clb.sr, ControlSetConfig::MaskType::MASK_ZERO); // sr can be masked with 0
|
||||
}
|
||||
|
||||
void update_cell_timing(Context *ctx)
|
||||
// prePlace: give every cell its per-type timing estimate (from
|
||||
// placement_estimate.txt or the built-in defaults), so the placer has
|
||||
// timing to optimise against. postPlace replaces it per instance.
|
||||
void apply_preplace_timing_estimates(Context *ctx)
|
||||
{
|
||||
// These timings are not realistic. They just make sure nextpnr does some timing-driven optimisation...
|
||||
for (auto &cell : ctx->cells) {
|
||||
CellInfo *ci = cell.second.get();
|
||||
if (ci->type == id_FABULOUS_LC) {
|
||||
auto &lct = cell_tags.get(ci);
|
||||
if (lct.comb.carry_used) {
|
||||
ctx->addCellTimingDelay(ci->name, id_Ci, id_Co, 0.2);
|
||||
ctx->addCellTimingDelay(ci->name, ctx->id("I1"), id_Co, 1.0);
|
||||
ctx->addCellTimingDelay(ci->name, ctx->id("I2"), id_Co, 1.0);
|
||||
}
|
||||
if (lct.ff.ff_used) {
|
||||
ctx->addCellTimingClock(ci->name, id_CLK);
|
||||
for (unsigned i = 0; i < cfg.clb.lut_k; i++)
|
||||
ctx->addCellTimingSetupHold(ci->name, ctx->idf("I%d", i), id_CLK, 2.5, 0.1);
|
||||
ctx->addCellTimingClockToOut(ci->name, id_Q, id_CLK, 1.0);
|
||||
if (bool_or_default(ci->params, id_I0MUX))
|
||||
ctx->addCellTimingSetupHold(ci->name, id_Ci, id_CLK, 2.5, 0.1);
|
||||
} else {
|
||||
for (unsigned i = 0; i < cfg.clb.lut_k; i++)
|
||||
ctx->addCellTimingDelay(ci->name, ctx->idf("I%d", i), id_O, 3.0);
|
||||
if (bool_or_default(ci->params, id_I0MUX))
|
||||
ctx->addCellTimingDelay(ci->name, id_Ci, id_O, 3.0);
|
||||
}
|
||||
} else if (ci->type.in(id_OutPass4_frame_config, id_OutPass4_frame_config_mux)) {
|
||||
for (unsigned i = 0; i < 4; i++)
|
||||
ctx->addCellTimingSetupHold(ci->name, ctx->idf("I%d", i), id_CLK, 2.5, 0.1);
|
||||
} else if (ci->type.in(id_InPass4_frame_config, id_InPass4_frame_config_mux)) {
|
||||
for (unsigned i = 0; i < 4; i++)
|
||||
ctx->addCellTimingClockToOut(ci->name, ctx->idf("O%d", i), id_CLK, 2.5);
|
||||
auto est = estimate_by_type.find(ci->type);
|
||||
if (est == estimate_by_type.end())
|
||||
continue;
|
||||
for (const auto &arc : est->second)
|
||||
apply_arc(ctx, ci, arc);
|
||||
}
|
||||
}
|
||||
|
||||
// nextpnr's historical hardcoded dummy timings, seeded for any cell type
|
||||
// placement_estimate.txt doesn't describe (equivalent to a file containing
|
||||
// e.g. "Delay,I0,O,3.0,FF=0").
|
||||
void seed_default_estimates()
|
||||
{
|
||||
const TimingPredicate ff{TimingPredicate::PARAM, id_FF, true, {}};
|
||||
const TimingPredicate no_ff{TimingPredicate::PARAM, id_FF, false, {}};
|
||||
const TimingPredicate i0mux{TimingPredicate::PARAM, id_I0MUX, true, {}};
|
||||
const TimingPredicate carry{TimingPredicate::PORT_ANY_CONNECTED, IdString(), true, {id_Ci, id_Co}};
|
||||
if (!estimate_by_type.count(id_FABULOUS_LC)) {
|
||||
auto &lc = estimate_by_type[id_FABULOUS_LC];
|
||||
lc = {
|
||||
{BelTimingArc::CLOCK, id_CLK, IdString(), 0, 0, {ff}},
|
||||
{BelTimingArc::CLK2OUT, id_Q, id_CLK, 1.0, 0, {ff}},
|
||||
{BelTimingArc::DELAY, id_Ci, id_Co, 0.2, 0, {carry}},
|
||||
{BelTimingArc::DELAY, ctx->id("I1"), id_Co, 1.0, 0, {carry}},
|
||||
{BelTimingArc::DELAY, ctx->id("I2"), id_Co, 1.0, 0, {carry}},
|
||||
{BelTimingArc::DELAY, id_Ci, id_O, 3.0, 0, {no_ff, i0mux}},
|
||||
{BelTimingArc::SETUPHOLD, id_Ci, id_CLK, 2.5, 0.1, {ff, i0mux}},
|
||||
};
|
||||
for (unsigned i = 0; i < cfg.clb.lut_k; i++) {
|
||||
IdString in = ctx->idf("I%d", i);
|
||||
lc.push_back({BelTimingArc::DELAY, in, id_O, 3.0, 0, {no_ff}});
|
||||
lc.push_back({BelTimingArc::SETUPHOLD, in, id_CLK, 2.5, 0.1, {ff}});
|
||||
}
|
||||
}
|
||||
for (IdString t : {id_OutPass4_frame_config, id_OutPass4_frame_config_mux})
|
||||
if (!estimate_by_type.count(t))
|
||||
for (unsigned i = 0; i < 4; i++)
|
||||
estimate_by_type[t].push_back({BelTimingArc::SETUPHOLD, ctx->idf("I%d", i), id_CLK, 2.5, 0.1, {}});
|
||||
for (IdString t : {id_InPass4_frame_config, id_InPass4_frame_config_mux})
|
||||
if (!estimate_by_type.count(t))
|
||||
for (unsigned i = 0; i < 4; i++)
|
||||
estimate_by_type[t].push_back({BelTimingArc::CLK2OUT, ctx->idf("O%d", i), id_CLK, 2.5, 0, {}});
|
||||
}
|
||||
|
||||
void pack() override
|
||||
|
|
@ -154,11 +237,13 @@ struct FabulousImpl : ViaductAPI
|
|||
void prePlace() override
|
||||
{
|
||||
assign_cell_info();
|
||||
update_cell_timing(ctx);
|
||||
apply_preplace_timing_estimates(ctx);
|
||||
}
|
||||
|
||||
void postPlace() override
|
||||
{
|
||||
if (!bel_timing_by_bel.empty())
|
||||
apply_bel_timing_per_instance(ctx);
|
||||
if (ctx->debug) {
|
||||
log_info("================== Final Placement ==================\n");
|
||||
for (auto &cell : ctx->cells) {
|
||||
|
|
@ -216,10 +301,66 @@ struct FabulousImpl : ViaductAPI
|
|||
return open_ifstream_and_log_error(filename, "data file (is FAB_ROOT set correctly?)");
|
||||
}
|
||||
|
||||
// Reads `.FABulous/placement_estimate.txt`: numeric `key=value` tunables (see
|
||||
// init() for the keys) into `vals`, plus optional BEL-file-format timing lines
|
||||
// describing one representative BEL per type into `estimate_by_type` (the
|
||||
// prePlace estimate; see apply_preplace_timing_estimates). Timing lines are
|
||||
// scoped to a type by `BelBegin,<type>` ... `BelEnd` blocks. Missing
|
||||
// keys/lines/file leave init()'s defaults.
|
||||
void read_placement_estimate(dict<std::string, double> &vals)
|
||||
{
|
||||
std::string path = fab_root + "/.FABulous/placement_estimate.txt";
|
||||
if (!std::filesystem::exists(path))
|
||||
return;
|
||||
std::ifstream in(path);
|
||||
CsvParser csv(in);
|
||||
IdString curr_type;
|
||||
while (csv.fetch_next_line()) {
|
||||
parser_view first = csv.next_field();
|
||||
IdString cmd = first.to_id(ctx);
|
||||
if (cmd == id_BelBegin) {
|
||||
curr_type = csv.next_field().to_id(ctx);
|
||||
if (curr_type == IdString())
|
||||
log_error("placement_estimate.txt: BelBegin without a bel type\n");
|
||||
continue;
|
||||
}
|
||||
if (cmd == id_BelEnd) {
|
||||
curr_type = IdString();
|
||||
continue;
|
||||
}
|
||||
if (cmd.in(id_Delay, id_SetupHold, id_ClkToOut, id_Clock)) {
|
||||
if (curr_type == IdString())
|
||||
log_error("placement_estimate.txt: timing command %s outside a BelBegin/BelEnd block\n",
|
||||
cmd.c_str(ctx));
|
||||
estimate_by_type[curr_type].push_back(parse_one_arc(csv, cmd));
|
||||
continue;
|
||||
}
|
||||
// otherwise a numeric `key=value` line; strip() tolerates whitespace
|
||||
// around '=' (e.g. "key = value")
|
||||
if (first.find('=') == parser_view::npos)
|
||||
log_error("placement_estimate.txt: unsupported line starting with '%s'\n", cmd.c_str(ctx));
|
||||
auto kv = first.split('=');
|
||||
parser_view key_v = kv.first.strip();
|
||||
parser_view value_v = kv.second.strip();
|
||||
std::string key(key_v.m_ptr, key_v.m_length);
|
||||
vals[key] = parse_double(value_v);
|
||||
}
|
||||
}
|
||||
|
||||
// Representative per-cell-type prePlace estimates from placement_estimate.txt;
|
||||
// empty unless the file provides them (see update_cell_timing).
|
||||
dict<IdString, std::vector<BelTimingArc>> estimate_by_type;
|
||||
|
||||
std::string fab_root;
|
||||
bool is_new_fab;
|
||||
|
||||
// predictDelay's Co->Ci carry-chain estimate; set from placement_estimate.txt
|
||||
// in init(), default 0.5 (the old hardcoded value).
|
||||
double carry_predict_delay = 0.5;
|
||||
|
||||
pool<IdString> warned_beltypes;
|
||||
// cell types already warned about having no applicable bel.v3 timing arc
|
||||
pool<IdString> warned_untimed;
|
||||
|
||||
std::vector<PseudoPipTags> pp_tags;
|
||||
|
||||
|
|
@ -386,10 +527,12 @@ struct FabulousImpl : ViaductAPI
|
|||
postprocess_bels();
|
||||
}
|
||||
|
||||
void init_bels_v2()
|
||||
// Reads both bel.v2.txt and bel.v3.txt; the latter additionally carries
|
||||
// Delay/SetupHold/ClkToOut/Clock timing lines into bel_timing_by_bel.
|
||||
void init_bels_v2(const std::string &filename)
|
||||
{
|
||||
log_info("Reading BELs file: /.FABulous/bel.v2.txt\n");
|
||||
std::ifstream in = open_data_rel("/.FABulous/bel.v2.txt");
|
||||
log_info("Reading BELs file: %s\n", filename.c_str());
|
||||
std::ifstream in = open_data_rel(filename);
|
||||
CsvParser csv(in);
|
||||
BelId curr_bel;
|
||||
while (csv.fetch_next_line()) {
|
||||
|
|
@ -433,6 +576,10 @@ struct FabulousImpl : ViaductAPI
|
|||
add_pseudo_pip(global_clk_wire, clk_wire, id_global_clock);
|
||||
} else if (cmd == id_CFG) {
|
||||
// TODO...
|
||||
} else if (cmd.in(id_Delay, id_SetupHold, id_ClkToOut, id_Clock)) {
|
||||
if (curr_bel == BelId())
|
||||
log_error("timing command %s outside a BelBegin/BelEnd block\n", cmd.c_str(ctx));
|
||||
bel_timing_by_bel[curr_bel].push_back(parse_one_arc(csv, cmd));
|
||||
} else if (cmd == id_BelEnd) {
|
||||
curr_bel = BelId();
|
||||
} else if (cmd != IdString()) {
|
||||
|
|
@ -444,6 +591,178 @@ struct FabulousImpl : ViaductAPI
|
|||
postprocess_bels();
|
||||
}
|
||||
|
||||
// bel.v3 timing arcs, keyed by physical BEL. Empty for v1/v2 projects.
|
||||
// postPlace applies each placed cell its own BEL's arcs (per-instance).
|
||||
dict<BelId, std::vector<BelTimingArc>> bel_timing_by_bel;
|
||||
|
||||
static float parse_float(parser_view v)
|
||||
{
|
||||
std::string s(v.m_ptr, v.m_length);
|
||||
try {
|
||||
return std::stof(s);
|
||||
} catch (const std::exception &) {
|
||||
log_error("invalid bel.v3 timing value '%s'\n", s.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
static double parse_double(parser_view v)
|
||||
{
|
||||
std::string s(v.m_ptr, v.m_length);
|
||||
try {
|
||||
return std::stod(s);
|
||||
} catch (const std::exception &) {
|
||||
log_error("invalid placement_estimate.txt value '%s'\n", s.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// Parse a bel.v3 condition field (e.g. "FF=0&I0MUX=1" or "Ci/Co?") into a
|
||||
// list of AND-ed predicates. An empty field means the arc always applies.
|
||||
std::vector<TimingPredicate> parse_timing_condition(parser_view field)
|
||||
{
|
||||
std::vector<TimingPredicate> preds;
|
||||
if (field.empty())
|
||||
return preds;
|
||||
std::string cond(field.m_ptr, field.m_length);
|
||||
size_t start = 0;
|
||||
while (start <= cond.size()) {
|
||||
size_t amp = cond.find('&', start);
|
||||
std::string tok = cond.substr(start, amp == std::string::npos ? std::string::npos : amp - start);
|
||||
start = (amp == std::string::npos) ? cond.size() + 1 : amp + 1;
|
||||
if (tok.empty())
|
||||
continue;
|
||||
TimingPredicate p;
|
||||
char last = tok.back();
|
||||
if (last == '?' || last == '!') {
|
||||
p.kind = (last == '?') ? TimingPredicate::PORT_ANY_CONNECTED : TimingPredicate::PORT_NONE_CONNECTED;
|
||||
std::string ports = tok.substr(0, tok.size() - 1);
|
||||
size_t ps = 0;
|
||||
while (ps <= ports.size()) {
|
||||
size_t slash = ports.find('/', ps);
|
||||
std::string pn = ports.substr(ps, slash == std::string::npos ? std::string::npos : slash - ps);
|
||||
ps = (slash == std::string::npos) ? ports.size() + 1 : slash + 1;
|
||||
if (!pn.empty())
|
||||
p.ports.push_back(ctx->id(pn));
|
||||
}
|
||||
} else {
|
||||
size_t eq = tok.find('=');
|
||||
if (eq == std::string::npos)
|
||||
log_error("invalid bel.v3 timing condition '%s'\n", tok.c_str());
|
||||
p.kind = TimingPredicate::PARAM;
|
||||
p.param = ctx->id(tok.substr(0, eq));
|
||||
p.param_value = (tok.substr(eq + 1) != "0");
|
||||
}
|
||||
preds.push_back(std::move(p));
|
||||
}
|
||||
return preds;
|
||||
}
|
||||
|
||||
// Build one timing arc from the fields after the command (shared by the BEL
|
||||
// file and the placement_estimate.txt placement estimate).
|
||||
BelTimingArc parse_one_arc(CsvParser &csv, IdString cmd)
|
||||
{
|
||||
BelTimingArc arc;
|
||||
if (cmd == id_Delay) {
|
||||
arc.kind = BelTimingArc::DELAY;
|
||||
arc.from = csv.next_field().to_id(ctx);
|
||||
arc.to = csv.next_field().to_id(ctx);
|
||||
arc.v0 = parse_float(csv.next_field());
|
||||
} else if (cmd == id_SetupHold) {
|
||||
arc.kind = BelTimingArc::SETUPHOLD;
|
||||
arc.from = csv.next_field().to_id(ctx);
|
||||
arc.to = csv.next_field().to_id(ctx);
|
||||
arc.v0 = parse_float(csv.next_field());
|
||||
arc.v1 = parse_float(csv.next_field());
|
||||
} else if (cmd == id_ClkToOut) {
|
||||
arc.kind = BelTimingArc::CLK2OUT;
|
||||
arc.from = csv.next_field().to_id(ctx);
|
||||
arc.to = csv.next_field().to_id(ctx);
|
||||
arc.v0 = parse_float(csv.next_field());
|
||||
} else {
|
||||
NPNR_ASSERT(cmd == id_Clock);
|
||||
arc.kind = BelTimingArc::CLOCK;
|
||||
arc.from = csv.next_field().to_id(ctx);
|
||||
}
|
||||
arc.cond = parse_timing_condition(csv.next_field());
|
||||
return arc;
|
||||
}
|
||||
|
||||
// True if every predicate holds for this cell (empty cond always holds).
|
||||
bool timing_cond_holds(const CellInfo *ci, const std::vector<TimingPredicate> &cond) const
|
||||
{
|
||||
for (const auto &p : cond) {
|
||||
if (p.kind == TimingPredicate::PARAM) {
|
||||
if (bool_or_default(ci->params, p.param) != p.param_value)
|
||||
return false;
|
||||
} else if (p.kind == TimingPredicate::PORT_ANY_CONNECTED) {
|
||||
bool any = false;
|
||||
for (IdString port : p.ports)
|
||||
if (ci->getPort(port)) {
|
||||
any = true;
|
||||
break;
|
||||
}
|
||||
if (!any)
|
||||
return false;
|
||||
} else { // PORT_NONE_CONNECTED
|
||||
for (IdString port : p.ports)
|
||||
if (ci->getPort(port))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Register one arc on the cell as the matching addCellTiming* call, if its condition holds.
|
||||
void apply_arc(Context *ctx, CellInfo *ci, const BelTimingArc &arc)
|
||||
{
|
||||
if (!timing_cond_holds(ci, arc.cond))
|
||||
return;
|
||||
switch (arc.kind) {
|
||||
case BelTimingArc::DELAY:
|
||||
ctx->addCellTimingDelay(ci->name, arc.from, arc.to, arc.v0);
|
||||
break;
|
||||
case BelTimingArc::SETUPHOLD:
|
||||
ctx->addCellTimingSetupHold(ci->name, arc.from, arc.to, arc.v0, arc.v1);
|
||||
break;
|
||||
case BelTimingArc::CLK2OUT:
|
||||
ctx->addCellTimingClockToOut(ci->name, arc.from, arc.to, arc.v0);
|
||||
break;
|
||||
case BelTimingArc::CLOCK:
|
||||
ctx->addCellTimingClock(ci->name, arc.from);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// postPlace: replace each placed cell's timing with its BEL's own arcs.
|
||||
void apply_bel_timing_per_instance(Context *ctx)
|
||||
{
|
||||
for (auto &cell : ctx->cells) {
|
||||
CellInfo *ci = cell.second.get();
|
||||
if (ci->bel == BelId())
|
||||
continue;
|
||||
auto fnd = bel_timing_by_bel.find(ci->bel);
|
||||
if (fnd == bel_timing_by_bel.end())
|
||||
continue;
|
||||
std::vector<const BelTimingArc *> matching;
|
||||
for (const auto &arc : fnd->second)
|
||||
if (timing_cond_holds(ci, arc.cond))
|
||||
matching.push_back(&arc);
|
||||
// If no arc condition holds the cell would end up with no timing at
|
||||
// all; likely a broken condition in bel.v3.txt, so warn and keep the
|
||||
// prePlace per-type estimate instead.
|
||||
if (matching.empty()) {
|
||||
if (warned_untimed.insert(ci->type).second)
|
||||
log_warning("no bel.v3 timing arc applies to '%s' (type %s); keeping prePlace estimate\n",
|
||||
ctx->nameOf(ci), ci->type.c_str(ctx));
|
||||
continue;
|
||||
}
|
||||
// SetupHold/ClockToOut *append* their arcs, so clear the prePlace
|
||||
// per-type timing first or register arcs would double-register.
|
||||
ctx->cellTiming.erase(ci->name);
|
||||
for (const auto *arc : matching)
|
||||
apply_arc(ctx, ci, *arc);
|
||||
}
|
||||
}
|
||||
|
||||
void generate_split_mux8(BelId bel)
|
||||
{
|
||||
// _don't_ take a reference here because it might be invalidated by adding bels
|
||||
|
|
@ -721,7 +1040,7 @@ struct FabulousImpl : ViaductAPI
|
|||
delay_t predictDelay(BelId src_bel, IdString src_pin, BelId dst_bel, IdString dst_pin) const override
|
||||
{
|
||||
if (src_pin == id_Co && dst_pin == id_Ci)
|
||||
return 0.5;
|
||||
return carry_predict_delay;
|
||||
|
||||
auto driver_loc = ctx->getBelLocation(src_bel);
|
||||
auto sink_loc = ctx->getBelLocation(dst_bel);
|
||||
|
|
|
|||
Loading…
Reference in New Issue