Gowin. GW5A series PLLs. (#1557)

PLLA-type PLLs are implemented, which are used in GW5A-25A chips.

These are six powerful PLLs, each of which can generate seven
independent frequencies.

Since these devices have an unusual configuration—their fuse bits are
located outside the main grid and therefore their Bels do not have
specific “correct” coordinates—the extra bel functions mechanism is used
to describe them. But all the complexity falls on the apicula part.

Signed-off-by: YRabbit <rabbit@yrabbit.cyou>
This commit is contained in:
YRabbit 2025-09-20 15:51:01 +10:00 committed by GitHub
parent 4ab735c690
commit 1742d09edb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 56 additions and 29 deletions

View File

@ -1033,6 +1033,7 @@ X(OSCO)
X(rPLL)
X(RPLLA)
X(PLLVR)
X(PLLA)
// primitive attributes
X(INIT)
@ -1145,7 +1146,15 @@ X(P47B)
X(P47C)
X(P47D)
// PLL parameters
// PLL
X(CLKOUT0)
X(CLKOUT1)
X(CLKOUT2)
X(CLKOUT3)
X(CLKOUT4)
X(CLKOUT5)
X(CLKOUT6)
X(CLKOUT7)
X(CLKOUTPS)
X(CLKOUTDIV)
X(CLKOUTDIV3)

View File

@ -77,12 +77,12 @@ struct GowinGlobalRouter
IdString dst_type = ctx->getWireType(dst);
bool src_is_outpin = src_type.in(id_IO_O, id_PLL_O, id_HCLK, id_DLLDLY, id_OSCOUT);
bool src_valid = not_dcs_pip && (src_type == id_GLOBAL_CLK || src_is_outpin);
bool dst_valid = not_dcs_pip && dst_type.in(id_GLOBAL_CLK, id_TILE_CLK, id_PLL_I, id_IO_I, id_HCLK);
bool dst_valid = not_dcs_pip && dst_type.in(id_GLOBAL_CLK, id_TILE_CLK, id_PLL_I, id_PLL_O, id_IO_I, id_HCLK);
bool res;
if (src == src_wire && (src_type == id_PLL_O || (!src_is_outpin))) {
bool dst_is_spine = dst_name.str(ctx).rfind("SPINE", 0) == 0;
res = src_valid && dst_is_spine;
res = src_valid && (dst_is_spine || dst_type == id_PLL_O);
} else {
res = (src_valid && dst_valid) || (src_valid && is_local(dst_type)) || (is_local(src_type) && dst_valid);
}

View File

@ -510,8 +510,7 @@ def create_switch_matrix(tt: TileType, db: chipdb, x: int, y: int):
if not tt.has_wire(dst):
tt.create_wire(dst, get_wire_type(dst))
for src in srcs.keys():
if src not in db.wire_delay:
continue
assert src in db.wire_delay, f"No timing info for {src} wire"
if not tt.has_wire(src):
if src in {"VSS", "VCC"}:
tt.create_wire(src, get_wire_type(src), const_value = src)
@ -770,6 +769,15 @@ def create_extra_funcs(tt: TileType, db: chipdb, x: int, y: int):
if not tt.has_wire(wire):
tt.create_wire(wire, "PINCFG_IN")
tt.add_bel_pin(bel, port, wire, PinType.INPUT)
elif func == 'pll':
pll = tt.create_bel("PLL", "PLLA", z = PLL_Z)
pll.flags = BEL_FLAG_GLOBAL
for pin, wire in desc['outputs'].items():
tt.create_wire(wire, "PLL_O")
tt.add_bel_pin(pll, pin, wire, PinType.OUTPUT)
for pin, wire in desc['inputs'].items():
tt.create_wire(wire, "PLL_I")
tt.add_bel_pin(pll, pin, wire, PinType.INPUT)
def set_wire_flags(tt: TileType, tdesc: TypeDesc):
if tdesc.extra_func and 'clock_gates' in tdesc.extra_func:
@ -1314,13 +1322,6 @@ def create_dsp_tiletype(chip: Chip, db: chipdb, x: int, y: int, ttyp: int, tdesc
return tt
# PLL main tile
_pll_inputs = {'CLKFB', 'FBDSEL0', 'FBDSEL1', 'FBDSEL2', 'FBDSEL3',
'FBDSEL4', 'FBDSEL5', 'IDSEL0', 'IDSEL1', 'IDSEL2', 'IDSEL3',
'IDSEL4', 'IDSEL5', 'ODSEL0', 'ODSEL1', 'ODSEL2', 'ODSEL3',
'ODSEL4', 'ODSEL5', 'RESET', 'RESET_P', 'PSDA0', 'PSDA1',
'PSDA2', 'PSDA3', 'DUTYDA0', 'DUTYDA1', 'DUTYDA2', 'DUTYDA3',
'FDLY0', 'FDLY1', 'FDLY2', 'FDLY3', 'CLKIN', 'VREN'}
_pll_outputs = {'CLKOUT', 'LOCK', 'CLKOUTP', 'CLKOUTD', 'CLKOUTD3'}
def create_pll_tiletype(chip: Chip, db: chipdb, x: int, y: int, ttyp: int, tdesc: TypeDesc):
typename = "PLL"
tiletype = f"{typename}_{ttyp}"
@ -1337,8 +1338,8 @@ def create_pll_tiletype(chip: Chip, db: chipdb, x: int, y: int, ttyp: int, tdesc
tt = chip.create_tile_type(tiletype)
tt.extra_data = TileExtraData(chip.strs.id(typename))
# wires
pll_outputs = {'CLKOUT', 'LOCK', 'CLKOUTP', 'CLKOUTD', 'CLKOUTD3'}
if chip.name == 'GW1NS-4':
pll_name = 'PLLVR'
bel_type = 'PLLVR'
@ -1349,13 +1350,12 @@ def create_pll_tiletype(chip: Chip, db: chipdb, x: int, y: int, ttyp: int, tdesc
pll = tt.create_bel("PLL", bel_type, z = PLL_Z)
pll.flags = BEL_FLAG_GLOBAL
for pin, wire in portmap.items():
if pin in _pll_inputs:
tt.create_wire(wire, "PLL_I")
tt.add_bel_pin(pll, pin, wire, PinType.INPUT)
else:
assert pin in _pll_outputs, f"Unknown PLL pin {pin}"
if pin in pll_outputs:
tt.create_wire(wire, "PLL_O")
tt.add_bel_pin(pll, pin, wire, PinType.OUTPUT)
else:
tt.create_wire(wire, "PLL_I")
tt.add_bel_pin(pll, pin, wire, PinType.INPUT)
tdesc.tiletype = tiletype
return tt

View File

@ -48,8 +48,9 @@ bool GowinUtils::driver_is_clksrc(const PortRef &driver)
}
}
// PLL outputs
if (driver.cell->type.in(id_rPLL, id_PLLVR)) {
if (driver.port.in(id_CLKOUT, id_CLKOUTD, id_CLKOUTD3, id_CLKOUTP)) {
if (driver.cell->type.in(id_rPLL, id_PLLVR, id_PLLA)) {
if (driver.port.in(id_CLKOUT, id_CLKOUTD, id_CLKOUTD3, id_CLKOUTP, id_CLKOUT0, id_CLKOUT1, id_CLKOUT2,
id_CLKOUT3, id_CLKOUT4, id_CLKOUT5, id_CLKOUT6, id_CLKOUT7)) {
if (ctx->debug) {
if (driver.cell->bel != BelId()) {
log_info("PLL out bel:%s:%s\n", ctx->nameOfBel(driver.cell->bel), driver.port.c_str(ctx));

View File

@ -3905,8 +3905,24 @@ struct GowinPacker
for (auto &cell : ctx->cells) {
auto &ci = *cell.second;
if (ci.type.in(id_rPLL, id_PLLVR)) {
if (ci.type.in(id_rPLL, id_PLLVR, id_PLLA)) {
// pin renaming for compatibility
if (ci.type == id_PLLA) {
for (int i = 0; i < 8; ++i) {
ci.renamePort(ctx->idf("MDWDI[%d]", i), ctx->idf("MDWDI%d", i));
ci.renamePort(ctx->idf("MDRDO[%d]", i), ctx->idf("MDRDO%d", i));
if (i < 7) {
ci.renamePort(ctx->idf("SSCMDSEL[%d]", i), ctx->idf("SSCMDSEL%d", i));
if (i < 3) {
ci.renamePort(ctx->idf("SSCMDSEL_FRAC[%d]", i), ctx->idf("SSCMDSEL_FRAC%d", i));
ci.renamePort(ctx->idf("PSSEL[%d]", i), ctx->idf("PSSEL%d", i));
}
if (i < 2) {
ci.renamePort(ctx->idf("MDOPC[%d]", i), ctx->idf("MDOPC%d", i));
}
}
}
} else {
for (int i = 0; i < 6; ++i) {
ci.renamePort(ctx->idf("FBDSEL[%d]", i), ctx->idf("FBDSEL%d", i));
ci.renamePort(ctx->idf("IDSEL[%d]", i), ctx->idf("IDSEL%d", i));
@ -3917,6 +3933,7 @@ struct GowinPacker
ci.renamePort(ctx->idf("FDLY[%d]", i), ctx->idf("FDLY%d", i));
}
}
}
// If CLKIN is connected to a special pin, then it makes sense
// to try to place the PLL so that it uses a direct connection
// to this pin.