From b22eebac30124a4fc550ff5c9cd9767de266d481 Mon Sep 17 00:00:00 2001 From: YRabbit Date: Wed, 18 Jan 2023 19:18:02 +1000 Subject: gowin: add a PLL primitive for the GW1NS-4 series * both instances of the new PLLVR type are supported; * primitive placement is optimized for the use of dedicated PLL clock pins; * all 4 outputs of each primitive can use the clock nets (only 5 lines in total at the same time so far). Signed-off-by: YRabbit --- gowin/arch.cc | 118 ++++++++++++++++++++++++++++++++++++++++++++++++----- gowin/arch.h | 7 +++- gowin/cells.cc | 37 +++++++++++++++++ gowin/cells.h | 3 +- gowin/constids.inc | 2 + gowin/globals.cc | 4 +- gowin/pack.cc | 69 ++++++++++++++++++++----------- 7 files changed, 202 insertions(+), 38 deletions(-) diff --git a/gowin/arch.cc b/gowin/arch.cc index 33f25405..a924f000 100644 --- a/gowin/arch.cc +++ b/gowin/arch.cc @@ -384,6 +384,9 @@ void Arch::addPip(IdString name, IdString type, IdString srcWire, IdString dstWi pi.delay = delay; pi.loc = loc; + // log_info("addpip %s->%s %.6f | %s name:%s\n" , srcWire.c_str(this), dstWire.c_str(this), + // getDelayNS(delay.maxDelay()), srcWire.c_str(this), name.c_str(this)); + wire_info(srcWire).downhill.push_back(name); wire_info(dstWire).uphill.push_back(name); pip_ids.push_back(name); @@ -1112,10 +1115,46 @@ void Arch::add_plla_ports(BelsPOD const *bel, IdString belname, int row, int col } } +void Arch::add_pllvr_ports(DatabasePOD const *db, BelsPOD const *bel, IdString belname, int row, int col) +{ + IdString portname; + + for (int pid : + {ID_CLKIN, ID_CLKFB, ID_FBDSEL0, ID_FBDSEL1, ID_FBDSEL2, ID_FBDSEL3, ID_FBDSEL4, ID_FBDSEL5, ID_IDSEL0, + ID_IDSEL1, ID_IDSEL2, ID_IDSEL3, ID_IDSEL4, ID_IDSEL5, ID_ODSEL0, ID_ODSEL1, ID_ODSEL2, ID_ODSEL3, + ID_ODSEL4, ID_ODSEL5, ID_VREN, ID_PSDA0, ID_PSDA1, ID_PSDA2, ID_PSDA3, ID_DUTYDA0, ID_DUTYDA1, + ID_DUTYDA2, ID_DUTYDA3, ID_FDLY0, ID_FDLY1, ID_FDLY2, ID_FDLY3, ID_RESET, ID_RESET_P}) { + portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, pid)->src_id); + IdString wire = idf("R%dC%d_%s", row + 1, col + 1, portname.c_str(this)); + if (wires.count(wire) == 0) { + GlobalAliasPOD alias; + alias.dest_col = col; + alias.dest_row = row; + alias.dest_id = portname.hash(); + auto alias_src = genericLookup(db->aliases.get(), db->num_aliases, alias, aliasCompare); + NPNR_ASSERT(alias_src != nullptr); + int srcrow = alias_src->src_row; + int srccol = alias_src->src_col; + IdString srcid = IdString(alias_src->src_id); + wire = wireToGlobal(srcrow, srccol, db, srcid); + // addWire(wire, portname, srccol, srcrow); + } + addBelInput(belname, IdString(pid), wire); + } + for (int pid : {ID_LOCK, ID_CLKOUT, ID_CLKOUTP, ID_CLKOUTD, ID_CLKOUTD3}) { + portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, pid)->src_id); + addBelOutput(belname, IdString(pid), idf("R%dC%d_%s", row + 1, col + 1, portname.c_str(this))); + } +} Arch::Arch(ArchArgs args) : args(args) { family = args.family; + max_clock = 5; + if (family == "GW1NZ-1") { + max_clock = 3; + } + // Load database std::string chipdb = stringf("gowin/chipdb-%s.bin", family.c_str()); auto db = reinterpret_cast(get_chipdb(chipdb)); @@ -1267,12 +1306,17 @@ Arch::Arch(ArchArgs args) : args(args) bool dff = true; bool oddrc = false; switch (static_cast(bel->type_id)) { - case ID_RPLLA: { + case ID_PLLVR: + belname = idf("R%dC%d_PLLVR", row + 1, col + 1); + addBel(belname, id_PLLVR, Loc(col, row, BelZ::pllvr_z), false); + add_pllvr_ports(db, bel, belname, row, col); + break; + case ID_RPLLA: snprintf(buf, 32, "R%dC%d_RPLLA", row + 1, col + 1); belname = id(buf); addBel(belname, id_RPLLA, Loc(col, row, BelZ::pll_z), false); add_plla_ports(bel, belname, row, col); - } break; + break; case ID_RPLLB: snprintf(buf, 32, "R%dC%d_RPLLB", row + 1, col + 1); belname = id(buf); @@ -2006,16 +2050,26 @@ static bool is_spec_iob(const Context *ctx, const CellInfo *cell, IdString pin_n return have_pin; } -static bool is_PLL_T_IN_iob(const Context *ctx, const CellInfo *cell) +static bool is_RPLL_T_IN_iob(const Context *ctx, const CellInfo *cell) { return is_spec_iob(ctx, cell, ctx->id("RPLL_T_IN")); } -static bool is_PLL_T_FB_iob(const Context *ctx, const CellInfo *cell) +static bool is_LPLL_T_IN_iob(const Context *ctx, const CellInfo *cell) +{ + return is_spec_iob(ctx, cell, ctx->id("LPLL_T_IN")); +} + +static bool is_RPLL_T_FB_iob(const Context *ctx, const CellInfo *cell) { return is_spec_iob(ctx, cell, ctx->id("RPLL_T_FB")); } +static bool is_LPLL_T_FB_iob(const Context *ctx, const CellInfo *cell) +{ + return is_spec_iob(ctx, cell, ctx->id("LPLL_T_FB")); +} + bool Arch::is_GCLKT_iob(const CellInfo *cell) { for (int i = 0; i < 6; ++i) { @@ -2032,7 +2086,7 @@ void Arch::fix_pll_nets(Context *ctx) { for (auto &cell : ctx->cells) { CellInfo *ci = cell.second.get(); - if (ci->type != id_RPLLA) { + if (ci->type != id_RPLLA && ci->type != id_PLLVR) { continue; } // *** CLKIN @@ -2046,10 +2100,54 @@ void Arch::fix_pll_nets(Context *ctx) ci->setParam(id_INSEL, Property("UNKNOWN")); break; } - if (net_driven_by(ctx, net, is_PLL_T_IN_iob, id_O) != nullptr) { - ci->disconnectPort(id_CLKIN); - ci->setParam(id_INSEL, Property("CLKIN0")); - break; + if (net_driven_by(ctx, net, is_RPLL_T_IN_iob, id_O) != nullptr) { + if (ci->type == id_RPLLA) { + ci->disconnectPort(id_CLKIN); + ci->setParam(id_INSEL, Property("CLKIN0")); + break; + } + BelId bel = id("R1C37_PLLVR"); + if (ci->type == id_PLLVR) { + if (checkBelAvail(bel) || ci->belStrength != STRENGTH_LOCKED) { + if (ci->bel == bel) { + unbindBel(bel); + } else { + if (!checkBelAvail(bel) && ci->belStrength != STRENGTH_LOCKED) { + CellInfo *other_ci = getBoundBelCell(bel); + unbindBel(bel); + BelId our_bel = ci->bel; + unbindBel(our_bel); + bindBel(our_bel, other_ci, STRENGTH_LOCKED); + } + } + ci->disconnectPort(id_CLKIN); + ci->setParam(id_INSEL, Property("CLKIN0")); + bindBel(bel, ci, STRENGTH_LOCKED); + break; + } + } + } + if (net_driven_by(ctx, net, is_LPLL_T_IN_iob, id_O) != nullptr) { + BelId bel = id("R1C28_PLLVR"); + if (ci->type == id_PLLVR) { + if (checkBelAvail(bel) || ci->belStrength != STRENGTH_LOCKED) { + if (ci->bel == bel) { + unbindBel(bel); + } else { + if (!checkBelAvail(bel) && ci->belStrength != STRENGTH_LOCKED) { + CellInfo *other_ci = getBoundBelCell(bel); + unbindBel(bel); + BelId our_bel = ci->bel; + unbindBel(our_bel); + bindBel(our_bel, other_ci, STRENGTH_LOCKED); + } + } + ci->disconnectPort(id_CLKIN); + ci->setParam(id_INSEL, Property("CLKIN0")); + bindBel(bel, ci, STRENGTH_LOCKED); + break; + } + } } // XXX do special bels (HCLK etc) // This is general routing through CLK0 pip @@ -2071,7 +2169,7 @@ void Arch::fix_pll_nets(Context *ctx) ci->setParam(id_FBSEL, Property("UNKNOWN")); continue; } - if (net_driven_by(ctx, net, is_PLL_T_FB_iob, id_O) != nullptr) { + if (net_driven_by(ctx, net, is_RPLL_T_FB_iob, id_O) != nullptr) { ci->disconnectPort(id_CLKFB); ci->setParam(id_FBSEL, Property("CLKFB2")); break; diff --git a/gowin/arch.h b/gowin/arch.h index a5c339cc..0dd5a62b 100644 --- a/gowin/arch.h +++ b/gowin/arch.h @@ -479,6 +479,7 @@ struct Arch : BaseArch void post_route(Context *ctx); void auto_longwires(); void add_plla_ports(BelsPOD const *bel, IdString belname, int row, int col); + void add_pllvr_ports(DatabasePOD const *db, BelsPOD const *bel, IdString belname, int row, int col); void fix_pll_nets(Context *ctx); bool is_GCLKT_iob(const CellInfo *cell); @@ -507,6 +508,9 @@ struct Arch : BaseArch // Permissible combinations of modes in a single slice std::map dff_comp_mode; + + // max global clock wires + int max_clock; }; // Bels Z range @@ -521,7 +525,8 @@ enum osc_z = 280, // Z for the oscillator bels bufs_0_z = 281, // Z for long wire buffer bel pll_z = 289, // PLL - free_z = 290 // Must be the last, one can use z starting from this value, adjust accordingly. + pllvr_z = 290, // PLLVR + free_z = 291 // Must be the last, one can use z starting from this value, adjust accordingly. }; } diff --git a/gowin/cells.cc b/gowin/cells.cc index a76ea1d8..ae420160 100644 --- a/gowin/cells.cc +++ b/gowin/cells.cc @@ -96,6 +96,19 @@ std::unique_ptr create_generic_cell(Context *ctx, IdString type, std:: new_cell->addOutput(id_CLKOUTD); new_cell->addOutput(id_CLKOUTD3); new_cell->addOutput(id_LOCK); + } else if (type == id_PLLVR) { + for (IdString iid : + {id_CLKIN, id_CLKFB, id_FBDSEL0, id_FBDSEL1, id_FBDSEL2, id_FBDSEL3, id_FBDSEL4, id_FBDSEL5, id_IDSEL0, + id_IDSEL1, id_IDSEL2, id_IDSEL3, id_IDSEL4, id_IDSEL5, id_ODSEL0, id_ODSEL1, id_ODSEL2, id_ODSEL3, + id_ODSEL4, id_ODSEL5, id_PSDA0, id_PSDA1, id_PSDA2, id_PSDA3, id_DUTYDA0, id_DUTYDA1, id_DUTYDA2, + id_DUTYDA3, id_FDLY0, id_FDLY1, id_FDLY2, id_FDLY3, id_RESET, id_RESET_P, id_VREN}) { + new_cell->addInput(iid); + } + new_cell->addOutput(id_CLKOUT); + new_cell->addOutput(id_CLKOUTP); + new_cell->addOutput(id_CLKOUTD); + new_cell->addOutput(id_CLKOUTD3); + new_cell->addOutput(id_LOCK); } else { log_error("unable to create generic cell of type %s\n", type.c_str(ctx)); } @@ -223,6 +236,30 @@ void reconnect_rpllb(Context *ctx, CellInfo *pll, CellInfo *pllb) pll->movePortTo(ctx->id("ODSEL[5]"), pllb, id_ODSEL5); } +void reconnect_pllvr(Context *ctx, CellInfo *pll, CellInfo *new_pll) +{ + pll->movePortTo(id_CLKIN, new_pll, id_CLKIN); + pll->movePortTo(id_VREN, new_pll, id_VREN); + pll->movePortTo(id_CLKFB, new_pll, id_CLKFB); + pll->movePortTo(id_RESET, new_pll, id_RESET); + pll->movePortTo(id_RESET_P, new_pll, id_RESET_P); + for (int i = 0; i < 6; ++i) { + pll->movePortTo(ctx->idf("FBDSEL[%d]", i), new_pll, ctx->idf("FBDSEL%d", i)); + pll->movePortTo(ctx->idf("IDSEL[%d]", i), new_pll, ctx->idf("IDSEL%d", i)); + pll->movePortTo(ctx->idf("ODSEL[%d]", i), new_pll, ctx->idf("ODSEL%d", i)); + if (i < 4) { + pll->movePortTo(ctx->idf("PSDA[%d]", i), new_pll, ctx->idf("PSDA%d", i)); + pll->movePortTo(ctx->idf("DUTYDA[%d]", i), new_pll, ctx->idf("DUTYDA%d", i)); + pll->movePortTo(ctx->idf("FDLY[%d]", i), new_pll, ctx->idf("FDLY%d", i)); + } + } + pll->movePortTo(id_CLKOUT, new_pll, id_CLKOUT); + pll->movePortTo(id_CLKOUTP, new_pll, id_CLKOUTP); + pll->movePortTo(id_CLKOUTD, new_pll, id_CLKOUTD); + pll->movePortTo(id_CLKOUTD3, new_pll, id_CLKOUTD3); + pll->movePortTo(id_LOCK, new_pll, id_LOCK); +} + void sram_to_ramw_split(Context *ctx, CellInfo *ram, CellInfo *ramw) { if (ramw->hierpath == IdString()) diff --git a/gowin/cells.h b/gowin/cells.h index ae475b77..78a746f3 100644 --- a/gowin/cells.h +++ b/gowin/cells.h @@ -121,7 +121,8 @@ void dff_to_lc(const Context *ctx, CellInfo *dff, CellInfo *lc, bool pass_thru_l // Convert a Gowin IO buffer to a IOB bel void gwio_to_iob(Context *ctx, CellInfo *nxio, CellInfo *sbio, pool &todelete_cells); -// Reconnect rPLL signals (B) +// Reconnect PLL signals (B) +void reconnect_pllvr(Context *ctx, CellInfo *pll, CellInfo *pllb); void reconnect_rplla(Context *ctx, CellInfo *pll, CellInfo *pllb); void reconnect_rpllb(Context *ctx, CellInfo *pll, CellInfo *pllb); diff --git a/gowin/constids.inc b/gowin/constids.inc index 610755bb..e3e451a1 100644 --- a/gowin/constids.inc +++ b/gowin/constids.inc @@ -313,6 +313,7 @@ X(COUT2) X(COUT3) X(COUT4) X(COUT5) +X(VREN) // wires // SN @@ -855,6 +856,7 @@ X(OSCF) X(rPLL) X(RPLLA) X(RPLLB) +X(PLLVR) // primitive attributes X(INIT) diff --git a/gowin/globals.cc b/gowin/globals.cc index 785a0111..6ed8f770 100644 --- a/gowin/globals.cc +++ b/gowin/globals.cc @@ -53,7 +53,7 @@ std::pair GowinGlobalRouter::clock_src(Context *ctx, PortRef cons } return std::make_pair(WireId(), BelId()); } - if (driver.cell->type == id_RPLLA) { + if (driver.cell->type == id_RPLLA || driver.cell->type == id_PLLVR) { if (driver.port == id_CLKOUT || driver.port == id_CLKOUTP || driver.port == id_CLKOUTD || driver.port == id_CLKOUTD3) { wire = bel.pins[driver.port].wire; @@ -294,7 +294,7 @@ void GowinGlobalRouter::mark_globals(Context *ctx) gather_clock_nets(ctx, clock_nets); // XXX we need to use the list of indexes of clocks from the database // use 6 clocks (XXX 3 for GW1NZ-1) - int max_clock = 3, cur_clock = -1; + int max_clock = ctx->max_clock, cur_clock = -1; for (auto &net : clock_nets) { // XXX only IO clock for now if (net.clock_wire == WireId()) { diff --git a/gowin/pack.cc b/gowin/pack.cc index e1119dde..e1372c47 100644 --- a/gowin/pack.cc +++ b/gowin/pack.cc @@ -989,11 +989,37 @@ static bool is_pll(const Context *ctx, const CellInfo *cell) switch (cell->type.hash()) { case ID_rPLL: return true; + case ID_PLLVR: + return true; default: return false; } } +static void pll_disable_unused_ports(Context *ctx, CellInfo *ci) +{ + // Unused ports will be disabled during image generation. Here we add flags for such ports. + Property pr_enable("ENABLE"), pr_disable("DISABLE"); + IdString ports[][2] = { + {id_CLKOUTP, id_CLKOUTPS}, {id_CLKOUTD, id_CLKOUTDIV}, {id_CLKOUTD3, id_CLKOUTDIV3}, {id_LOCK, id_FLOCK}}; + for (int i = 0; i < 4; ++i) { + ci->setParam(ports[i][1], port_used(ci, ports[i][0]) ? pr_enable : pr_disable); + } + // resets + NetInfo *net = ci->getPort(id_RESET); + ci->setParam(id_RSTEN, pr_enable); + if (!port_used(ci, id_RESET) || net->name == ctx->id("$PACKER_VCC_NET") || + net->name == ctx->id("$PACKER_GND_NET")) { + ci->setParam(id_RSTEN, pr_disable); + } + ci->setParam(id_PWDEN, pr_enable); + net = ci->getPort(id_RESET_P); + if (!port_used(ci, id_RESET_P) || net->name == ctx->id("$PACKER_VCC_NET") || + net->name == ctx->id("$PACKER_GND_NET")) { + ci->setParam(id_PWDEN, pr_disable); + } +} + // Pack PLLs static void pack_plls(Context *ctx) { @@ -1010,36 +1036,14 @@ static void pack_plls(Context *ctx) if (is_pll(ctx, ci)) { std::string parm_device = str_or_default(ci->params, id_DEVICE, "GW1N-1"); if (parm_device != ctx->device) { - log_error("Wrong PLL device:%s vs %s\n", parm_device.c_str(), ctx->device.c_str()); + log_error("Wrong PLL device:%s instead of %s\n", parm_device.c_str(), ctx->device.c_str()); continue; } switch (ci->type.hash()) { case ID_rPLL: { if (parm_device == "GW1N-1" || parm_device == "GW1NZ-1") { - // Unused ports will be disabled during image generation. Here we add flags for such ports. - Property pr_enable("ENABLE"), pr_disable("DISABLE"); - IdString ports[][2] = {{id_CLKOUTP, id_CLKOUTPS}, - {id_CLKOUTD, id_CLKOUTDIV}, - {id_CLKOUTD3, id_CLKOUTDIV3}, - {id_LOCK, id_FLOCK}}; - for (int i = 0; i < 4; ++i) { - ci->setParam(ports[i][1], port_used(ci, ports[i][0]) ? pr_enable : pr_disable); - } - // resets - NetInfo *net = ci->getPort(id_RESET); - ci->setParam(id_RSTEN, pr_enable); - if (!port_used(ci, id_RESET) || net->name == ctx->id("$PACKER_VCC_NET") || - net->name == ctx->id("$PACKER_GND_NET")) { - ci->setParam(id_RSTEN, pr_disable); - } - ci->setParam(id_PWDEN, pr_enable); - net = ci->getPort(id_RESET_P); - if (!port_used(ci, id_RESET_P) || net->name == ctx->id("$PACKER_VCC_NET") || - net->name == ctx->id("$PACKER_GND_NET")) { - ci->setParam(id_PWDEN, pr_disable); - } - + pll_disable_unused_ports(ctx, ci); // B half std::unique_ptr cell = create_generic_cell(ctx, id_RPLLB, ci->name.str(ctx) + "$rpllb"); reconnect_rpllb(ctx, ci, cell.get()); @@ -1061,6 +1065,23 @@ static void pack_plls(Context *ctx) log_error("PLL isn't supported for %s\n", ctx->device.c_str()); } } break; + case ID_PLLVR: { + if (parm_device == "GW1NSR-4C") { + pll_disable_unused_ports(ctx, ci); + std::unique_ptr cell = create_generic_cell(ctx, id_PLLVR, ci->name.str(ctx) + "$pllvr"); + reconnect_pllvr(ctx, ci, cell.get()); + new_cells.push_back(std::move(cell)); + auto pll_cell = new_cells.back().get(); + + // need params for gowin_pack + for (auto &parm : ci->params) { + pll_cell->setParam(parm.first, parm.second); + } + packed_cells.insert(ci->name); + } else { + log_error("PLL isn't supported for %s\n", ctx->device.c_str()); + } + } break; default: break; } -- cgit v1.2.3