nexus: Initial support for DDR3 memory interfaces

Signed-off-by: gatecat <gatecat@ds0.me>
This commit is contained in:
gatecat 2026-07-19 17:24:32 +02:00
parent ddc6c8c8b9
commit c2cbdc7efa
6 changed files with 334 additions and 9 deletions

View File

@ -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

View File

@ -1501,6 +1501,7 @@ struct Arch : BaseArch<ArchRanges>
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

View File

@ -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)

View File

@ -412,6 +412,7 @@ struct NexusFasmWriter
bool lvds_used = false;
bool slvs_used = false;
bool dphy_used = false;
std::array<bool, 2> vref_used{false, false};
};
std::map<int, BankConfig> 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

View File

@ -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<IdString, XFormRule> 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<IdString, int> 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)) {

View File

@ -269,6 +269,11 @@ static const dict<IdString, Arch::CellPinsData> 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,