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(rPLL)
X(RPLLA) X(RPLLA)
X(PLLVR) X(PLLVR)
X(PLLA)
// primitive attributes // primitive attributes
X(INIT) X(INIT)
@ -1145,7 +1146,15 @@ X(P47B)
X(P47C) X(P47C)
X(P47D) X(P47D)
// PLL parameters // PLL
X(CLKOUT0)
X(CLKOUT1)
X(CLKOUT2)
X(CLKOUT3)
X(CLKOUT4)
X(CLKOUT5)
X(CLKOUT6)
X(CLKOUT7)
X(CLKOUTPS) X(CLKOUTPS)
X(CLKOUTDIV) X(CLKOUTDIV)
X(CLKOUTDIV3) X(CLKOUTDIV3)

View File

@ -77,12 +77,12 @@ struct GowinGlobalRouter
IdString dst_type = ctx->getWireType(dst); 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_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 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; bool res;
if (src == src_wire && (src_type == id_PLL_O || (!src_is_outpin))) { if (src == src_wire && (src_type == id_PLL_O || (!src_is_outpin))) {
bool dst_is_spine = dst_name.str(ctx).rfind("SPINE", 0) == 0; 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 { } else {
res = (src_valid && dst_valid) || (src_valid && is_local(dst_type)) || (is_local(src_type) && dst_valid); 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): if not tt.has_wire(dst):
tt.create_wire(dst, get_wire_type(dst)) tt.create_wire(dst, get_wire_type(dst))
for src in srcs.keys(): for src in srcs.keys():
if src not in db.wire_delay: assert src in db.wire_delay, f"No timing info for {src} wire"
continue
if not tt.has_wire(src): if not tt.has_wire(src):
if src in {"VSS", "VCC"}: if src in {"VSS", "VCC"}:
tt.create_wire(src, get_wire_type(src), const_value = src) 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): if not tt.has_wire(wire):
tt.create_wire(wire, "PINCFG_IN") tt.create_wire(wire, "PINCFG_IN")
tt.add_bel_pin(bel, port, wire, PinType.INPUT) 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): def set_wire_flags(tt: TileType, tdesc: TypeDesc):
if tdesc.extra_func and 'clock_gates' in tdesc.extra_func: 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 return tt
# PLL main tile # 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): def create_pll_tiletype(chip: Chip, db: chipdb, x: int, y: int, ttyp: int, tdesc: TypeDesc):
typename = "PLL" typename = "PLL"
tiletype = f"{typename}_{ttyp}" 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 = chip.create_tile_type(tiletype)
tt.extra_data = TileExtraData(chip.strs.id(typename)) tt.extra_data = TileExtraData(chip.strs.id(typename))
# wires # wires
pll_outputs = {'CLKOUT', 'LOCK', 'CLKOUTP', 'CLKOUTD', 'CLKOUTD3'}
if chip.name == 'GW1NS-4': if chip.name == 'GW1NS-4':
pll_name = 'PLLVR' pll_name = 'PLLVR'
bel_type = '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 = tt.create_bel("PLL", bel_type, z = PLL_Z)
pll.flags = BEL_FLAG_GLOBAL pll.flags = BEL_FLAG_GLOBAL
for pin, wire in portmap.items(): for pin, wire in portmap.items():
if pin in _pll_inputs: if pin in pll_outputs:
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}"
tt.create_wire(wire, "PLL_O") tt.create_wire(wire, "PLL_O")
tt.add_bel_pin(pll, pin, wire, PinType.OUTPUT) 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 tdesc.tiletype = tiletype
return tt return tt

View File

@ -48,8 +48,9 @@ bool GowinUtils::driver_is_clksrc(const PortRef &driver)
} }
} }
// PLL outputs // PLL outputs
if (driver.cell->type.in(id_rPLL, id_PLLVR)) { if (driver.cell->type.in(id_rPLL, id_PLLVR, id_PLLA)) {
if (driver.port.in(id_CLKOUT, id_CLKOUTD, id_CLKOUTD3, id_CLKOUTP)) { 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 (ctx->debug) {
if (driver.cell->bel != BelId()) { if (driver.cell->bel != BelId()) {
log_info("PLL out bel:%s:%s\n", ctx->nameOfBel(driver.cell->bel), driver.port.c_str(ctx)); 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) { for (auto &cell : ctx->cells) {
auto &ci = *cell.second; 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 // 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) { for (int i = 0; i < 6; ++i) {
ci.renamePort(ctx->idf("FBDSEL[%d]", i), ctx->idf("FBDSEL%d", 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)); 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)); ci.renamePort(ctx->idf("FDLY[%d]", i), ctx->idf("FDLY%d", i));
} }
} }
}
// If CLKIN is connected to a special pin, then it makes sense // 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 try to place the PLL so that it uses a direct connection
// to this pin. // to this pin.