diff --git a/generic/CMakeLists.txt b/generic/CMakeLists.txt index ca2c458f..efa17023 100644 --- a/generic/CMakeLists.txt +++ b/generic/CMakeLists.txt @@ -21,6 +21,8 @@ set(SOURCES viaduct/fabulous/fabulous.cc viaduct/fabulous/fasm.cc viaduct/fabulous/fasm.h + viaduct/fabulous/bel_timing.cc + viaduct/fabulous/bel_timing.h viaduct/fabulous/pack.cc viaduct/fabulous/pack.h viaduct/fabulous/validity_check.cc diff --git a/generic/viaduct/fabulous/bel_timing.cc b/generic/viaduct/fabulous/bel_timing.cc new file mode 100644 index 00000000..e58a928c --- /dev/null +++ b/generic/viaduct/fabulous/bel_timing.cc @@ -0,0 +1,161 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021-2026 The FABulous maintainers + * + * 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 "bel_timing.h" + +#include "log.h" +#include "util.h" + +#define VIADUCT_CONSTIDS "viaduct/fabulous/constids.inc" +#include "viaduct_constids.h" + +NEXTPNR_NAMESPACE_BEGIN + +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()); + } +} + +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()); + } +} + +std::vector parse_timing_condition(Context *ctx, parser_view field) +{ + std::vector 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; +} + +BelTimingArc parse_one_arc(Context *ctx, 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(ctx, csv.next_field()); + return arc; +} + +bool timing_cond_holds(const CellInfo *ci, const std::vector &cond) +{ + 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; +} + +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; + } +} + +NEXTPNR_NAMESPACE_END diff --git a/generic/viaduct/fabulous/bel_timing.h b/generic/viaduct/fabulous/bel_timing.h new file mode 100644 index 00000000..795f36e0 --- /dev/null +++ b/generic/viaduct/fabulous/bel_timing.h @@ -0,0 +1,81 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021-2026 The FABulous maintainers + * + * 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_BEL_TIMING_H +#define FABULOUS_BEL_TIMING_H + +#include + +#include "fabric_parsing.h" +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +// 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 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 cond; +}; + +float parse_float(parser_view v); +double parse_double(parser_view v); + +// 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 parse_timing_condition(Context *ctx, parser_view field); + +// 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(Context *ctx, CsvParser &csv, IdString cmd); + +// True if every predicate holds for this cell (empty cond always holds). +bool timing_cond_holds(const CellInfo *ci, const std::vector &cond); + +// 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); + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/generic/viaduct/fabulous/fabulous.cc b/generic/viaduct/fabulous/fabulous.cc index 980a2e89..4846245c 100644 --- a/generic/viaduct/fabulous/fabulous.cc +++ b/generic/viaduct/fabulous/fabulous.cc @@ -32,6 +32,7 @@ #include "fab_cfg.h" #include "fab_defs.h" +#include "bel_timing.h" #include "fasm.h" #include "pack.h" #include "pcf.h" @@ -43,38 +44,6 @@ 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 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 cond; -}; - struct FabulousImpl : ViaductAPI { FabulousImpl(const dict &args) @@ -332,7 +301,7 @@ struct FabulousImpl : ViaductAPI 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)); + estimate_by_type[curr_type].push_back(parse_one_arc(ctx, csv, cmd)); continue; } // otherwise a numeric `key=value` line; strip() tolerates whitespace @@ -579,7 +548,7 @@ struct FabulousImpl : ViaductAPI } 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)); + bel_timing_by_bel[curr_bel].push_back(parse_one_arc(ctx, csv, cmd)); } else if (cmd == id_BelEnd) { curr_bel = BelId(); } else if (cmd != IdString()) { @@ -595,143 +564,6 @@ struct FabulousImpl : ViaductAPI // postPlace applies each placed cell its own BEL's arcs (per-instance). dict> 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 parse_timing_condition(parser_view field) - { - std::vector 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 &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) {