From 86e6a2225c57cf26961efa92ba82b4e59ad53fda Mon Sep 17 00:00:00 2001 From: David Shah Date: Wed, 2 Dec 2020 12:29:08 +0000 Subject: [PATCH] nexus: Add PLL support Signed-off-by: David Shah --- nexus/constids.inc | 68 +++++++++++++++++++++++++++++++++++++++++ nexus/fasm.cc | 56 ++++++++++++++++++++++++++++++++++ nexus/pack.cc | 76 ++++++++++++++++++++++++++++++++++++++++++++-- nexus/pins.cc | 16 +++++++++- 4 files changed, 213 insertions(+), 3 deletions(-) diff --git a/nexus/constids.inc b/nexus/constids.inc index 9b12d197..f8939263 100644 --- a/nexus/constids.inc +++ b/nexus/constids.inc @@ -375,3 +375,71 @@ X(SIGNED) X(SUM0) X(SUM1) X(CINPUT) + +X(PLL_CORE) +X(CLKOP) +X(CLKOS) +X(CLKOS2) +X(CLKOS3) +X(CLKOS4) +X(CLKOS5) +X(ENCLKOP) +X(ENCLKOS) +X(ENCLKOS2) +X(ENCLKOS3) +X(ENCLKOS4) +X(ENCLKOS5) +X(FBKCK) +X(LEGACY) +X(LMMICLK) +X(LMMIRESETN) +X(PLLRESET) +X(REFCK) +X(STDBY) +X(PLLPDN) +X(SCANRST) +X(SCANCLK) +X(ROTDEL) +X(DIRDEL) +X(ROTDELP1) +X(GRAYTEST0) +X(GRAYTEST1) +X(GRAYTEST2) +X(GRAYTEST3) +X(GRAYTEST4) +X(BINTEST0) +X(BINTEST1) +X(GRAYACT0) +X(GRAYACT1) +X(GRAYACT2) +X(GRAYACT3) +X(GRAYACT4) +X(BINACT0) +X(BINACT1) +X(LMMIWRRDN) +X(OPCGLDCK) +X(ZRSEL3) +X(ENEXT) + +X(PLL) +X(LMMIWRRD_N) +X(LMMIRESET_N) +X(PLLPOWERDOWN_N) + +X(FLOCK_EN) +X(FLOCK_CTRL) +X(FLOCK_SRC_SEL) + +X(DIV_DEL) +X(FBK_PI_RC) +X(FBK_PR_IC) + +X(DIVA) +X(DIVB) +X(DIVC) +X(DIVD) +X(DIVE) +X(DIVF) +X(REF_MMD_DIG) +X(FBK_MMD_DIG) +X(CLKMUX_FB) diff --git a/nexus/fasm.cc b/nexus/fasm.cc index 5da809c6..fcbe0b8c 100644 --- a/nexus/fasm.cc +++ b/nexus/fasm.cc @@ -542,6 +542,60 @@ struct NexusFasmWriter write_cell_muxes(cell); pop(); } + + // Which PLL params are 'word' values + /* clang-format off */ + const std::unordered_map pll_word_params = { + {"DIVA", 7}, {"DELA", 7}, {"PHIA", 3}, {"DIVB", 7}, + {"DELB", 7}, {"PHIB", 3}, {"DIVC", 7}, {"DELC", 7}, + {"PHIC", 3}, {"DIVD", 7}, {"DELD", 7}, {"PHID", 3}, + {"DIVE", 7}, {"DELE", 7}, {"PHIE", 3}, {"DIVF", 7}, + {"DELF", 7}, {"PHIF", 3}, {"BW_CTL_BIAS", 4}, + {"CLKOP_TRIM", 4}, {"CLKOS_TRIM", 4}, {"CLKOS2_TRIM", 4}, + {"CLKOS3_TRIM", 4}, {"CLKOS4_TRIM", 4}, {"CLKOS5_TRIM", 4}, + {"DIV_DEL", 7}, {"DYN_SEL", 3}, {"FBK_CUR_BLE", 8}, {"FBK_IF_TIMING_CTL", 2}, + {"FBK_MASK", 8}, {"FBK_MMD_DIG", 8}, {"FBK_MMD_PULS_CTL", 4}, + {"FBK_MODE", 2}, {"FBK_PI_RC", 4}, {"FBK_PR_CC", 4}, + {"FBK_PR_IC", 4}, {"FBK_RSV", 16}, + {"IPI_CMP", 4}, {"IPI_CMPN", 4}, + {"IPP_CTRL", 4}, {"IPP_SEL", 4}, + {"KP_VCO", 5}, + {"MFG_CTRL", 4}, {"MFGOUT1_SEL", 3}, {"MFGOUT2_SEL", 3}, + {"REF_MASK", 8}, {"REF_MMD_DIG", 8}, {"REF_MMD_IN", 8}, + {"REF_MMD_PULS_CTL", 4}, {"REF_TIMING_CTL", 2}, + {"RESERVED", 7}, {"SSC_DELTA", 15}, + {"SSC_DELTA_CTL", 2}, {"SSC_F_CODE", 15}, + {"SSC_N_CODE", 9}, {"SSC_REG_WEIGHTING_SEL", 3}, + {"SSC_STEP_IN", 7}, {"SSC_TBASE", 12}, + {"V2I_PP_ICTRL", 5}, + }; + /* clang-format on */ + + // Write out config for some kind of PLL cell + void write_pll(const CellInfo *cell) + { + BelId bel = cell->bel; + push_bel(bel); + write_bit("MODE.PLL_CORE"); + write_enum(cell, "CLKMUX_FB"); + write_cell_muxes(cell); + pop(); + push(stringf("IP_%s", ctx->nameOf(ctx->bel_data(bel).name))); + for (auto param : sorted_cref(cell->params)) { + const std::string &name = param.first.str(ctx); + if (is_mux_param(name) || name == "CLKMUX_FB" || name == "SEL_FBK") + continue; + auto fnd_word = pll_word_params.find(name); + if (fnd_word != pll_word_params.end()) { + write_int_vector(stringf("%s[%d:0]", name.c_str(), fnd_word->second - 1), + ctx->parse_lattice_param(cell, param.first, fnd_word->second, 0).as_int64(), + fnd_word->second); + } else { + write_bit(stringf("%s.%s", name.c_str(), param.second.as_string().c_str())); + } + } + pop(); + } // Write out FASM for unused bels where needed void write_unused() { @@ -654,6 +708,8 @@ struct NexusFasmWriter ci->type == id_MULT18X36_CORE || ci->type == id_MULT36_CORE || ci->type == id_REG18_CORE || ci->type == id_ACC54_CORE) write_dsp(ci); + else if (ci->type == id_PLL_CORE) + write_pll(ci); blank(); } // Write config for unused bels diff --git a/nexus/pack.cc b/nexus/pack.cc index b4c1566b5..eb1ac560 100644 --- a/nexus/pack.cc +++ b/nexus/pack.cc @@ -681,7 +681,8 @@ struct NexusPacker std::unordered_set seen_bels; BelId bel = get_bel_attr(cell); - NPNR_ASSERT(bel != BelId()); + if (bel == BelId()) + return; WireId start_wire = ctx->getBelPinWire(bel, port); NPNR_ASSERT(start_wire != WireId()); PortType dir = ctx->getBelPinType(bel, port); @@ -899,6 +900,8 @@ struct NexusPacker did_something |= preplace_singleton(ci); else if (ci->type == id_DCC) did_something |= preplace_prim(ci, id_CLKI, false); + else if (ci->type == id_PLL_CORE) + did_something |= preplace_prim(ci, id_REFCK, false); } } } @@ -1023,6 +1026,7 @@ struct NexusPacker static const std::unordered_map prim_map = { {id_OSCA, id_OSC_CORE}, {id_DP16K, id_DP16K_MODE}, {id_PDP16K, id_PDP16K_MODE}, {id_PDPSC16K, id_PDPSC16K_MODE}, {id_SP16K, id_SP16K_MODE}, {id_FIFO16K, id_FIFO16K_MODE}, + {id_PLL, id_PLL_CORE}, }; for (auto cell : sorted(ctx->cells)) { @@ -1723,6 +1727,16 @@ struct NexusPacker changed_nets.insert(net.first); } + auto get_period = [&](CellInfo *ci, IdString port, delay_t &period) { + if (!ci->ports.count(port)) + return false; + NetInfo *from = ci->ports.at(port).net; + if (from == nullptr || from->clkconstr == nullptr) + return false; + period = from->clkconstr->period.min_delay; + return true; + }; + auto set_period = [&](CellInfo *ci, IdString port, delay_t period) { if (!ci->ports.count(port)) return; @@ -1784,7 +1798,7 @@ struct NexusPacker std::unordered_set changed_cells; for (auto net : changed_nets) { for (auto &user : ctx->nets.at(net)->users) - if (user.port == id_CLKI) + if (user.port == id_CLKI || user.port == id_REFCK) changed_cells.insert(user.cell->name); auto &drv = ctx->nets.at(net)->driver; if (iter == 1 && drv.cell != nullptr && (drv.port == id_HFCLKOUT || drv.port == id_LFCLKOUT)) @@ -1799,11 +1813,68 @@ struct NexusPacker int div = int_or_default(ci->params, ctx->id("HF_CLK_DIV"), 128); set_period(ci, id_HFCLKOUT, delay_t((1.0e6 / 450) * (div + 1))); set_period(ci, id_LFCLKOUT, delay_t((1.0e3 / 10))); + } else if (ci->type == id_PLL_CORE) { + static const std::array div{id_DIVA, id_DIVB, id_DIVC, id_DIVD, id_DIVE, id_DIVF}; + static const std::array output{id_CLKOP, id_CLKOS, id_CLKOS2, + id_CLKOS3, id_CLKOS4, id_CLKOS5}; + + delay_t period_in; + if (!get_period(ci, id_REFCK, period_in)) + continue; + log_info(" Input frequency of PLL '%s' is constrained to %.1f MHz\n", ci->name.c_str(ctx), + MHz(period_in)); + + int input_div = ctx->parse_lattice_param(ci, id_REF_MMD_DIG, 8, 1).as_int64(); + period_in *= input_div; + int feedback_div = ctx->parse_lattice_param(ci, id_REF_MMD_DIG, 8, 1).as_int64(); + bool found_fbk = false; + std::string clkmux_fb = str_or_default(ci->params, id_CLKMUX_FB, "CMUX_CLKOP"); + for (int i = 0; i < 6; i++) { + // Find which output is being used for feedback + if (clkmux_fb != stringf("CMUX_%s", output[i].c_str(ctx))) + continue; + // Multiply feedback output divider with + feedback_div *= (ctx->parse_lattice_param(ci, div[i], 7, 0).as_int64() + 1); + found_fbk = true; + } + if (!found_fbk) { + log_warning("Unable to determine feedback path, skipping PLL timing constraint derivation for " + "'%s'\n", + ctx->nameOf(ci)); + continue; + } + delay_t vco_period = period_in / feedback_div; + log_info(" Derived VCO frequency of PLL '%s' is %.1f MHz\n", ci->name.c_str(ctx), + MHz(vco_period)); + for (int i = 0; i < 6; i++) { + set_period(ci, output[i], + (ctx->parse_lattice_param(ci, div[i], 7, 0).as_int64() + 1) * vco_period); + } } } } } + void pack_plls() + { + const std::unordered_map pll_defaults = { + {id_FLOCK_CTRL, "2X"}, {id_FLOCK_EN, "ENABLED"}, {id_FLOCK_SRC_SEL, "REFCLK"}, + {id_DIV_DEL, "0b0000001"}, {id_FBK_PI_RC, "0b1100"}, {id_FBK_PR_IC, "0b1000"}, + }; + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (ci->type == id_PLL_CORE) { + // Extra log to phys rules + rename_port(ctx, ci, id_PLLPOWERDOWN_N, id_PLLPDN); + rename_port(ctx, ci, id_LMMIWRRD_N, id_LMMIWRRDN); + rename_port(ctx, ci, id_LMMIRESET_N, id_LMMIRESETN); + for (auto &defparam : pll_defaults) + if (!ci->params.count(defparam.first)) + ci->params[defparam.first] = defparam.second; + } + } + } + explicit NexusPacker(Context *ctx) : ctx(ctx) {} void operator()() @@ -1816,6 +1887,7 @@ struct NexusPacker pack_carries(); pack_widefn(); pack_ffs(); + pack_plls(); pack_constants(); pack_luts(); promote_globals(); diff --git a/nexus/pins.cc b/nexus/pins.cc index 0587c032..05bffb1e 100644 --- a/nexus/pins.cc +++ b/nexus/pins.cc @@ -154,7 +154,21 @@ static const std::unordered_map base_cell_pin_data {id_CEO, PINSTYLE_CE}, {id_CIN, PINSTYLE_CIB}, {id_SFTCTRL0, PINSTYLE_PU}, {id_SFTCTRL1, PINSTYLE_PU}, {id_SFTCTRL2, PINSTYLE_PU}, {id_SFTCTRL3, PINSTYLE_PU}, {{}, PINSTYLE_DEDI}, - }}}; + }}, + {id_PLL_CORE, + { + {id_REFCK, PINSTYLE_DEDI}, + {id_FBKCK, PINSTYLE_DEDI}, + {id_SCANCLK, PINSTYLE_DEDI}, + {id_SCANRST, PINSTYLE_DEDI}, + {id_LMMICLK, PINSTYLE_CLK}, + {id_LMMIRESETN, PINSTYLE_CE}, + {id_OPCGLDCK, PINSTYLE_DEDI}, + {id_ZRSEL3, PINSTYLE_DEDI}, + {id_ENEXT, PINSTYLE_DEDI}, + {{}, PINSTYLE_CIB}, + }}, +}; } // namespace void Arch::init_cell_pin_data() { cell_pins_db = base_cell_pin_data; }