From c2cbdc7efac2b40e276c1c387106f9304287c3b7 Mon Sep 17 00:00:00 2001 From: gatecat Date: Sun, 19 Jul 2026 17:24:32 +0200 Subject: [PATCH] nexus: Initial support for DDR3 memory interfaces Signed-off-by: gatecat --- nexus/arch.cc | 20 ++++++ nexus/arch.h | 1 + nexus/constids.inc | 62 +++++++++++++++++- nexus/fasm.cc | 100 ++++++++++++++++++++++++++++- nexus/pack.cc | 155 +++++++++++++++++++++++++++++++++++++++++++-- nexus/pins.cc | 5 ++ 6 files changed, 334 insertions(+), 9 deletions(-) diff --git a/nexus/arch.cc b/nexus/arch.cc index 00e7c449..efaf2632 100644 --- a/nexus/arch.cc +++ b/nexus/arch.cc @@ -962,6 +962,26 @@ std::string Arch::get_pad_functions(const PadInfoPOD *pad) const return s; } +BelId Arch::get_bank_vref_io_bel(int bank, int vref) const +{ + IdString func = idf("VREF%d_%d", bank, vref); + for (auto &pad : chip_info->pads) { + if (pad.bank != bank) + continue; + bool found = false; + for (auto f : pad.func_strs) { + if (IdString(f) == func) { + found = true; + break; + } + } + if (!found) + continue; + return get_pad_pio_bel(&pad); + } + return BelId(); +} + // ----------------------------------------------------------------------- // Helper for cell timing lookups diff --git a/nexus/arch.h b/nexus/arch.h index 20604737..3a80b47b 100644 --- a/nexus/arch.h +++ b/nexus/arch.h @@ -1501,6 +1501,7 @@ struct Arch : BaseArch BelId get_pad_pio_bel(const PadInfoPOD *pad) const; const PadInfoPOD *get_bel_pad(BelId bel) const; std::string get_pad_functions(const PadInfoPOD *pad) const; + BelId get_bank_vref_io_bel(int bank, int vref) const; // ------------------------------------------------- // Data about different IO standard, mostly used by bitgen diff --git a/nexus/constids.inc b/nexus/constids.inc index 7cf2fdde..1717b549 100644 --- a/nexus/constids.inc +++ b/nexus/constids.inc @@ -110,7 +110,7 @@ X(CIB_LR) X(IO_TYPE) X(SLEWRATE) - +X(VREF) X(OSCA) X(OSC) @@ -534,6 +534,10 @@ X(STOP) X(ECLKIN) X(DIVOUT) X(ECLKOUT) +X(TESTINP0) +X(TESTINP1) +X(TESTINP2) +X(TESTINP3) X(IDDRX1) X(ODDRX1) @@ -559,6 +563,56 @@ X(TSHX2DQS) X(TSHX4DQ) X(TSHX4DQS) +X(DQSBUF) +X(DQSBUF_CORE) +X(DDRDLL) +X(DDRDLL_CORE) + +X(ENABLE_FIFO) +X(FORCE_READ) +X(FREE_WHEEL) +X(MODX) +X(MT_EN_READ) +X(MT_EN_WRITE) +X(MT_EN_WRITE_LEVELING) +X(RD_PNTR) +X(WR_PNTR) +X(READ_ENABLE) +X(RX_CENTERED) +X(S_READ) +X(S_WRITE) +X(SIGN_READ) +X(SIGN_WRITE) +X(UPDATE_QU) +X(WRITE_ENABLE) +X(SEL_READ_BIT_ENABLE_CYCLES) +X(BYPASS_WR_LEVEL_SMTH_LATCH) +X(BYPASS_WR_SMTH_LATCH) +X(BYPASS_READ_SMTH_LATCH) + +X(DQSI) +X(PAUSE) +X(RDCLKSEL3) +X(RDCLKSEL2) +X(RDCLKSEL1) +X(RDCLKSEL0) +X(RDDIR) +X(RDLOADN) +X(READ3) +X(READ2) +X(READ1) +X(READ0) +X(READCOUT) +X(READMOVE) +X(SELCLK) +X(WRDIR) +X(WRLOAD_N) +X(WRLVDIR) +X(WRLVLOAD_N) +X(WRLVMOVE) +X(WRMOVE) +X(RSTSMCNT) + X(TXDATA0) X(TXDATA1) X(TXDATA2) @@ -614,6 +668,12 @@ X(T2) X(T3) X(SCLK) +X(ENA_ROUNDOFF) +X(FORCE_MAX_DELAY) +X(CLKIN) +X(FREEZE) +X(UDDCNTL_N) + X(ALIGNWD) X(DQSR90) X(DQSW) diff --git a/nexus/fasm.cc b/nexus/fasm.cc index d943d29a..c535e8f1 100644 --- a/nexus/fasm.cc +++ b/nexus/fasm.cc @@ -412,6 +412,7 @@ struct NexusFasmWriter bool lvds_used = false; bool slvs_used = false; bool dphy_used = false; + std::array vref_used{false, false}; }; std::map bank_cfg; @@ -459,6 +460,13 @@ struct NexusFasmWriter const char *iodir = is_input ? "INPUT" : (is_output ? "OUTPUT" : "BIDIR"); write_bit(stringf("BASE_TYPE.%s_%s", iodir, str_or_default(cell->attrs, id_IO_TYPE, "LVCMOS18H").c_str())); write_ioattr(cell, "PULLMODE", "NONE"); + write_ioattr(cell, "VREF"); + auto vref_cfg = str_or_default(cell->attrs, id_VREF, ""); + if (vref_cfg == "VREF1_LOAD") { + bank.vref_used[0] = true; + } else if (vref_cfg == "VREF2_LOAD") { + bank.vref_used[0] = true; + } write_ioattr(cell, "SLEWRATE", str_or_default(cell->attrs, id_SLEWRATE, "MED").c_str()); pop(); write_cell_muxes(cell); @@ -586,6 +594,59 @@ struct NexusFasmWriter write_enum(cell, "GSR", "DISABLED"); pop(); } + // Write config for ECLKSYNC + void write_eclksync(const CellInfo *cell) + { + BelId bel = cell->bel; + push_bel(bel); + write_enum(cell, "STOP_EN"); + pop(); + } + // Write config for DQSBUF + void write_dqsbuf(const CellInfo *cell) + { + BelId bel = cell->bel; + push_bel(bel); + write_enum(cell, "ENABLE_FIFO"); + write_enum(cell, "GSR", "DISABLED"); + write_enum(cell, "ENABLE_FIFO"); + write_enum(cell, "FORCE_READ"); + write_enum(cell, "FREE_WHEEL"); + write_enum(cell, "MODX"); + write_enum(cell, "MT_EN_READ"); + write_enum(cell, "MT_EN_WRITE"); + write_enum(cell, "MT_EN_WRITE_LEVELING"); + write_enum(cell, "READ_ENABLE"); + write_enum(cell, "RX_CENTERED", "ENABLED"); + // write_enum(cell, "SIGN_READ", "POSITIVE"); + write_bit("SIGN_READ.COMPLEMENT"); + write_enum(cell, "SIGN_WRITE", "POSITIVE"); + write_enum(cell, "UPDATE_QU", "UP1_AND_UP0_SAME"); + write_enum(cell, "WRITE_ENABLE"); + write_enum(cell, "SEL_READ_BIT_ENABLE_CYCLES", "NORMAL"); + write_enum(cell, "BYPASS_WR_LEVEL_SMTH_LATCH", "SMOOTHING_PATH"); + write_enum(cell, "BYPASS_WR_SMTH_LATCH", "SMOOTHING_PATH"); + write_enum(cell, "BYPASS_READ_SMTH_LATCH", "SMOOTHING_PATH"); + write_bit("RSTMUX.RST"); // TODO: pins config + write_bit("RSTSMCNTMUX.RSTSMCNT"); + write_bit("PAUSEMUX.PAUSE"); + // TODO: S_READ, S_WRITE: where do these magic numbers come from? + write_bit("S_WRITE[8:0] = 9'b000100100"); + write_bit("S_READ[8:0] = 9'b110001010"); + + pop(); + } + void write_ddrdll(const CellInfo *cell) + { + BelId bel = cell->bel; + push_bel(bel); + write_enum(cell, "ENA_OSC", "ENABLED"); + write_enum(cell, "GSR", "DISABLED"); + write_enum(cell, "ENA_ROUNDOFF", "ENABLED"); + write_enum(cell, "FORCE_MAX_DELAY"); + write_bit("RSTMUX.RST"); // TODO: pins config + pop(); + } // Write config for an OXIDE_EBR cell void write_bram(const CellInfo *cell) { @@ -684,6 +745,7 @@ struct NexusFasmWriter if (cell->type == id_IOLOGIC) { write_enum(cell, "IDDRXN.DDRMODE"); write_enum(cell, "ODDRXN.DDRMODE"); + write_enum(cell, "MIDDRXN.DDRMODE"); write_enum(cell, "MODDRXN.DDRMODE"); write_enum(cell, "MTDDRXN.DDRMODE"); } @@ -1092,7 +1154,12 @@ struct NexusFasmWriter write_bit("LVDS_IO.ON", bank.lvds_used); write_bit("SLVS_IO.ON", bank.slvs_used); write_bit("MIPI_DPHY_IO.ON", bank.dphy_used); - + for (int vref = 1; vref <= 2; vref++) { + if (!bank.vref_used[vref - 1]) + continue; + write_bit(stringf("VREF%d_USED.ON", vref)); + write_bit("REF_IO.ON"); + } pop(); } else { if (is_lifcl_17 && (i != 0) && (i != 1)) @@ -1105,6 +1172,27 @@ struct NexusFasmWriter } } blank(); + for (int i = 3; i <= 5; i++) { + auto &bank = bank_cfg[i]; + for (int vref = 1; vref <= 2; vref++) { + if (!bank.vref_used[vref - 1]) + continue; + // Set VREF pin to VREFn_DRIVER + BelId vref_pio = ctx->get_bank_vref_io_bel(i, vref); + if (vref_pio == BelId()) + log_error("Unable to find pad for bank %d VREF%d\n", i, vref); + if (!ctx->checkBelAvail(vref_pio)) + log_error("IO %s is required for bank %d VREF%d but is occupied by cell '%s'.\n", + ctx->nameOfBel(vref_pio), i, vref, ctx->nameOf(ctx->getBoundBelCell(vref_pio))); + used_io.insert(vref_pio); + push_bel(vref_pio); + push("SEIO18"); + write_bit(stringf("VREF%d_DRIVER.ON", vref)); + pop(); + pop(); + blank(); + } + } } // Write out FASM for the whole design void operator()() @@ -1160,14 +1248,20 @@ struct NexusFasmWriter write_cfg_clkrst(ci); else if (ci->type == id_ECLKDIV_CORE) write_eclkdiv(ci); + else if (ci->type == id_ECLKSYNC_CORE) + write_eclksync(ci); + else if (ci->type == id_DQSBUF_CORE) + write_dqsbuf(ci); + else if (ci->type == id_DDRDLL_CORE) + write_ddrdll(ci); blank(); } // Handle DCC route-throughs write_dcc_thru(); - // Write config for unused bels - write_unused(); // Write bank config write_bankcfg(); + // Write config for unused bels + write_unused(); } }; } // namespace diff --git a/nexus/pack.cc b/nexus/pack.cc index cd45cb54..ce34dd24 100644 --- a/nexus/pack.cc +++ b/nexus/pack.cc @@ -801,6 +801,9 @@ struct NexusPacker return true; } + if (strict_routing) + return false; + // Unless in strict mode; check based on simple distance too BelId nearest = find_nearest_bel(pin_drv, cell->type, [&](BelId bel) { return !used_bels.count(bel); }); @@ -1093,6 +1096,8 @@ struct NexusPacker {id_CONFIG_LMMI, id_CONFIG_LMMI_CORE}, {id_ECLKSYNC, id_ECLKSYNC_CORE}, {id_ECLKDIV, id_ECLKDIV_CORE}, + {id_DQSBUF, id_DQSBUF_CORE}, + {id_DDRDLL, id_DDRDLL_CORE}, }; // extra prefix needed for this primitive for some reason @@ -1233,11 +1238,11 @@ struct NexusPacker void pack_iodelay() { dict base_iodelay_rules; - base_iodelay_rules[id_DELAYB].new_type = id_IOLOGIC; XFormRule delay_rule; delay_rule.param_xform[id_DEL_VALUE] = ctx->id("DELAY.DEL_VALUE"); delay_rule.param_xform[id_COARSE_DELAY] = ctx->id("DELAY.COARSE_DELAY"); + delay_rule.new_type = id_IOLOGIC; base_iodelay_rules[id_DELAYB] = delay_rule; base_iodelay_rules[id_DELAYA] = delay_rule; @@ -1330,6 +1335,64 @@ struct NexusPacker iol_rules[itype].port_xform[ctx->idf("Q%d", i)] = ctx->idf("RXDATA%d", (10 - 2 * gear) + i); } + for (int gear : {2, 4}) { + IdString oshxtype = ctx->idf("OSHX%d", gear); + iol_rules[oshxtype].new_type = id_IOLOGIC; + iol_rules[oshxtype].set_params.emplace_back(id_MODE, std::string("MIDDRXN_MODDRXN")); + iol_rules[oshxtype].set_params.emplace_back(ctx->id("MODDRXN.DDRMODE"), stringf("MOSHX%d", gear)); + iol_rules[oshxtype].port_xform[id_ECLK] = id_ECLK; + iol_rules[oshxtype].port_xform[id_SCLK] = id_SCLKOUT; + iol_rules[oshxtype].port_xform[id_RST] = id_LSROUT; + iol_rules[oshxtype].port_xform[id_Q] = id_DOUT; + for (int i = 0; i < gear; i++) + iol_rules[oshxtype].port_xform[ctx->idf("D%d", i)] = ctx->idf("TXDATA%d", i); + + for (std::string dq : {"DQ", "DQS"}) { + IdString tshxtype = ctx->idf("TSHX%d%s", gear, dq.c_str()); + iol_rules[tshxtype].new_type = id_IOLOGIC; + iol_rules[tshxtype].set_params.emplace_back(id_MODE, std::string("MIDDRXN_MODDRXN")); + iol_rules[tshxtype].set_params.emplace_back(ctx->id("MTDDRXN.DDRMODE"), stringf("MTSHX%d", gear)); + iol_rules[tshxtype].port_xform[id_ECLK] = id_ECLK; + iol_rules[tshxtype].port_xform[id_SCLK] = id_SCLKOUT; + iol_rules[tshxtype].port_xform[id_RST] = id_LSROUT; + iol_rules[tshxtype].port_xform[id_Q] = id_TOUT; + if (dq == "DQS") + iol_rules[tshxtype].port_xform[id_DQSW] = id_DQSW; + else + iol_rules[tshxtype].port_xform[id_DQSW270] = id_DQSW270; + for (int i = 0; i < gear; i++) + iol_rules[tshxtype].port_xform[ctx->idf("T%d", i)] = ctx->idf("TSDATA%d", i); + + IdString otype = ctx->idf("ODDRX%d%s", gear, dq.c_str()); + iol_rules[otype].new_type = id_IOLOGIC; + iol_rules[otype].set_params.emplace_back(id_MODE, std::string("MIDDRXN_MODDRXN")); + iol_rules[otype].set_params.emplace_back(ctx->id("MODDRXN.DDRMODE"), + stringf("MODDRX%d_DQSW%s", gear, (dq == "DQS") ? "" : "270")); + iol_rules[otype].port_xform[id_ECLK] = id_ECLK; + iol_rules[otype].port_xform[id_SCLK] = id_SCLKOUT; + iol_rules[otype].port_xform[id_RST] = id_LSROUT; + iol_rules[otype].port_xform[id_Q] = id_DOUT; + if (dq == "DQS") + iol_rules[otype].port_xform[id_DQSW] = id_DQSW; + else + iol_rules[otype].port_xform[id_DQSW270] = id_DQSW270; + for (int i = 0; i < 2 * gear; i++) + iol_rules[otype].port_xform[ctx->idf("D%d", i)] = ctx->idf("TXDATA%d", i); + } + + IdString itype = ctx->idf("IDDRX%dDQ", gear); + iol_rules[itype].new_type = id_IOLOGIC; + iol_rules[itype].set_params.emplace_back(id_MODE, std::string("MIDDRXN_MODDRXN")); + iol_rules[itype].set_params.emplace_back(ctx->id("MIDDRXN.DDRMODE"), stringf("MIDDRX%d", gear)); + iol_rules[itype].port_xform[id_ECLK] = id_ECLK; + iol_rules[itype].port_xform[id_SCLK] = id_SCLKIN; + iol_rules[itype].port_xform[id_RST] = id_LSRIN; + iol_rules[itype].port_xform[id_DQSR90] = id_DQSR90; + iol_rules[itype].port_xform[id_D] = id_DI; + for (int i = 0; i < 2 * gear; i++) + iol_rules[itype].port_xform[ctx->idf("Q%d", i)] = ctx->idf("RXDATA%d", (10 - 2 * gear) + i); + } + generic_xform(iol_rules, true); } @@ -1341,6 +1404,18 @@ struct NexusPacker base->params[param.first] = param.second; } for (auto &port : mergee->ports) { + if (base->getPort(port.first)) { + if (!port.second.net) + continue; + if (base->getPort(port.first) != port.second.net) { + log_error("Conflict between IOLOGIC cells '%s' and '%s' on port '%s': nets '%s' and '%s'\n", + ctx->nameOf(base), ctx->nameOf(mergee), ctx->nameOf(port.first), + ctx->nameOf(base->getPort(port.first)), ctx->nameOf(port.second.net)); + continue; + } + mergee->disconnectPort(port.first); + continue; + } mergee->movePortTo(port.first, base, port.first); } ctx->cells.erase(mergee->name); @@ -1386,7 +1461,9 @@ struct NexusPacker // Deal with interconnectivity if (is_output) { // Configure delay for use with DDR - logic_iol->params[id_DELAYMUX] = std::string("OUT_REG"); + std::string mode = str_or_default(logic_iol->params, id_MODE); + if (mode != "IDDRXN" && mode != "ODDRXN" && mode != "MIDDRXN_MODDRXN") + logic_iol->params[id_DELAYMUX] = std::string("OUT_REG"); NetInfo *out_net = delay_iol->getPort(id_DOUT); delay_iol->disconnectPort(id_DOUT); @@ -2566,7 +2643,8 @@ struct NexusPacker { auto buffer = insert_buffer(eclk, id_ECLKSYNC_CORE, "edge_clk", id_ECLKIN, id_ECLKOUT, [&](const PortRef &port) { - return port.port.in(id_ECLK, id_ECLKIN); // TODO: DDRDLL + return port.port.in(id_ECLK, id_ECLKIN) || + (port.cell->type == id_DDRDLL_CORE && port.port == id_CLKIN); }); buffer->addInput(id_STOP); buffer->connectPort(id_STOP, gnd_net); @@ -2589,6 +2667,31 @@ struct NexusPacker bank_eclkdiv[bank].insert(bel); } dict eclk_bank; + // Prioritise DQSBUF in case an ECLK crosses banks + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (ci->type != id_DQSBUF_CORE) + continue; + + NetInfo *eclk = ci->getPort(id_ECLKIN); + if (!eclk) + continue; + if (eclk_bank.count(eclk->name)) + continue; + + NetInfo *dqsi = ci->getPort(id_DQSI); + if (!dqsi || !dqsi->driver.cell) + continue; + CellInfo *pio = dqsi->driver.cell; + if (!pio->type.in(id_DIFFIO18_CORE, id_SEIO18_CORE)) + continue; + BelId pio_bel = ctx->getBelByNameStr(pio->attrs.at(id_BEL).as_string()); + Loc pad_loc = ctx->getBelLocation(pio_bel); + pad_loc.z = 0; + int bank = ctx->get_bel_pad(ctx->getBelByLocation(pad_loc))->bank; + eclk_bank[eclk->name] = bank; + } + for (auto &cell : ctx->cells) { CellInfo *iol = cell.second.get(); if (iol->type != id_IOLOGIC) @@ -2622,7 +2725,7 @@ struct NexusPacker log_error("Unable to place ECLKSYNC '%s': none remaining for bank %d.\n", ctx->nameOf(eclksync), eclk_pair.second); BelId eclksync_bel = *(eclksyncs.begin()); - ctx->bindBel(eclksync_bel, eclksync, STRENGTH_LOCKED); + eclksync->attrs[id_BEL] = ctx->getBelName(eclksync_bel).str(ctx); log_info(" placed ECLKSYNC '%s' at bel '%s'\n", ctx->nameOf(eclksync), ctx->nameOfBel(eclksync_bel)); eclksyncs.erase(eclksync_bel); @@ -2635,11 +2738,52 @@ struct NexusPacker log_error("Unable to place ECLKDIV '%s': none remaining for bank %d.\n", ctx->nameOf(usr.cell), eclk_pair.second); BelId eclkdiv_bel = *(eclkdivs.begin()); - ctx->bindBel(eclkdiv_bel, usr.cell, STRENGTH_LOCKED); + usr.cell->attrs[id_BEL] = ctx->getBelName(eclkdiv_bel).str(ctx); log_info(" placed ECLKDIV '%s' at bel '%s'\n", ctx->nameOf(usr.cell), ctx->nameOfBel(eclkdiv_bel)); eclkdivs.erase(eclkdiv_bel); } } + // Place DDRDLL based on ECLK + for (auto &cell : ctx->cells) { + CellInfo *ddrdll = cell.second.get(); + if (ddrdll->type != id_DDRDLL_CORE) + continue; + bool placed = preplace_prim(ddrdll, id_CLKIN, true); + if (!placed) + log_error("Failed to find a placement for DDRDLL '%s'.\n", ctx->nameOf(ddrdll)); + } + } + + void place_dqsbuf() + { + log_info("Placing DQSBUFs...\n"); + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (ci->type != id_DQSBUF_CORE) + continue; + NetInfo *dqsi = ci->getPort(id_DQSI); + if (!dqsi || !dqsi->driver.cell) + log_error("DQSBUF '%s' has disconnected DQSI.\n", ctx->nameOf(ci)); + CellInfo *pio = dqsi->driver.cell; + if (!pio->type.in(id_DIFFIO18_CORE, id_SEIO18_CORE)) + log_error("DQSBUF '%s' has DQSI driven by illegal cell '%s'.\n", ctx->nameOf(ci), ctx->nameOf(pio)); + BelId pio_bel = ctx->getBelByNameStr(pio->attrs.at(id_BEL).as_string()); + WireId pio_wire = ctx->getBelPinWire(pio_bel, dqsi->driver.port); + BelId dqsbuf_bel = BelId(); + for (PipId pip : ctx->getPipsDownhill(pio_wire)) { + for (BelPin bp : ctx->getWireBelPins(ctx->getPipDstWire(pip))) { + if (ctx->getBelType(bp.bel) == id_DQSBUF_CORE && bp.pin == id_DQSI) { + dqsbuf_bel = bp.bel; + goto done; + } + } + } + done: + if (dqsbuf_bel == BelId()) + log_error("Failed to find a placement for DQSBUF '%s'.\n", ctx->nameOf(ci)); + log_info(" constraining DQSBUF '%s' to bel '%s'\n", ctx->nameOf(ci), ctx->nameOfBel(dqsbuf_bel)); + ci->attrs[id_BEL] = ctx->getBelName(dqsbuf_bel).str(ctx); + } } FFControlSet gather_ff_settings(CellInfo *cell) @@ -2824,6 +2968,7 @@ struct NexusPacker pack_ip(); handle_iologic(); + place_dqsbuf(); place_eclk(); if (!bool_or_default(ctx->settings, id_no_pack_lutff)) { diff --git a/nexus/pins.cc b/nexus/pins.cc index 8168163f..120ac96b 100644 --- a/nexus/pins.cc +++ b/nexus/pins.cc @@ -269,6 +269,11 @@ static const dict base_cell_pin_data = { { {{id_ECLKIN}, PINSTYLE_DEDI}, {{id_DIVOUT}, PINSTYLE_DEDI}, + {{id_TESTINP0}, PINSTYLE_PU}, + {{id_TESTINP1}, PINSTYLE_PU}, + {{id_TESTINP2}, PINSTYLE_PU}, + {{id_TESTINP3}, PINSTYLE_PU}, + {{}, PINSTYLE_CIB}, }}, {id_ECLKSYNC_CORE,