Gowin. Add GW5AST-138C chip. (#1631)

* Gowin. Add GW5AST-138C chip.

The ability to perform P&R for the largest GW5A series chip currently
available has been added, which has its own characteristics:

  - the need to invert pin function configuration signals - these
    signals are not part of the design, but are nextpnr command line
    keys  for specifying the activation of alternative pin functions such as
    I2C;

  - some clock PIPs are encoded not by fuses, but by applying VCC/GND to
    special inputs. This is also not part of the design and is not a
    dynamic clock selection primitive - it is simply an addition to the
    fuses.

  - added check for DFF and SSRAM placement in upper slots - prior to
    this chip, SSRAM was not supported and there was no need for this
    check.

  - since the chip is divided into two parts in terms of the global
    clock network, a flag is introduced to indicate which part the wire
    belongs to. This is only requested for clock wires.

Signed-off-by: YRabbit <rabbit@yrabbit.cyou>

* Gowin. Fix style.

Use C++ type cast.

Signed-off-by: YRabbit <rabbit@yrabbit.cyou>

---------

Signed-off-by: YRabbit <rabbit@yrabbit.cyou>
This commit is contained in:
YRabbit 2026-01-31 22:01:22 +10:00 committed by GitHub
parent 8c6278170b
commit b4da86edce
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 236 additions and 28 deletions

View File

@ -20,7 +20,7 @@ add_nextpnr_himbaechel_microarchitecture(${uarch}
CORE_SOURCES ${SOURCES}
)
set(ALL_HIMBAECHEL_GOWIN_DEVICES GW1N-1 GW1NZ-1 GW1N-4 GW1N-9 GW1N-9C GW1NS-4 GW2A-18 GW2A-18C GW5A-25A)
set(ALL_HIMBAECHEL_GOWIN_DEVICES GW1N-1 GW1NZ-1 GW1N-4 GW1N-9 GW1N-9C GW1NS-4 GW2A-18 GW2A-18C GW5A-25A GW5AST-138C)
set(HIMBAECHEL_GOWIN_DEVICES ${ALL_HIMBAECHEL_GOWIN_DEVICES} CACHE STRING
"Include support for these Gowin devices (available: ${ALL_HIMBAECHEL_GOWIN_DEVICES})")
if (HIMBAECHEL_GOWIN_DEVICES STREQUAL "all")

View File

@ -1237,6 +1237,7 @@ X(FB_C)
X(BOTTOM_IO_PORT_A)
X(BOTTOM_IO_PORT_B)
X(IOLOGIC_DUMMY)
X(SPINE_SELECT)
// User Flash
X(INUSEN)

View File

@ -1187,6 +1187,76 @@ struct GowinGlobalRouter
}
}
// Enable clocked spines by connecting magic wires to VCC/GND if necessary.
void enable_spines(void)
{
if (ctx->verbose) {
log_info("Check for spine select wires.\n");
}
NetInfo *vcc_net = ctx->nets.at(ctx->id("$PACKER_VCC")).get();
NetInfo *vss_net = ctx->nets.at(ctx->id("$PACKER_GND")).get();
std::unique_ptr<CellInfo> top_ci = gwu.create_cell(ctx->id("spine_select$top"), id_SPINE_SELECT);
top_ci->pseudo_cell = std::make_unique<RegionPlug>(Loc(0, 0, 0));
std::unique_ptr<CellInfo> bottom_ci = gwu.create_cell(ctx->id("spine_select$bottom"), id_SPINE_SELECT);
bottom_ci->pseudo_cell = std::make_unique<RegionPlug>(Loc(0, 0, 0));
pool<WireId> seen_spines;
dict<IdString, int> top_connections;
dict<IdString, int> bottom_connections;
for (auto &net : ctx->nets) {
const NetInfo *ni = net.second.get();
for (auto &wire : ni->wires) {
WireId spine = wire.first;
IdString spine_name = ctx->getWireName(spine)[1];
if (spine_name.str(ctx).rfind("SPINE", 0) == 0 && seen_spines.count(spine) == 0) {
seen_spines.insert(spine);
std::vector<std::pair<WireId, int>> wires;
if (gwu.get_spine_select_wire(spine, wires)) {
int sfx = 0; // To activate a single spine, it may be necessary to connect an unknown number of
// wires.
CellInfo *select_cell = top_ci.get();
auto connections = &top_connections;
if (gwu.wire_in_bottom_half(spine)) {
select_cell = bottom_ci.get();
connections = &bottom_connections;
}
for (auto gate : wires) {
IdString port_name = ctx->idf("%s.%d", spine_name.c_str(ctx), sfx);
select_cell->addInput(port_name);
RegionPlug *rp = dynamic_cast<RegionPlug *>(select_cell->pseudo_cell.get());
rp->port_wires[port_name] = gate.first;
(*connections)[port_name] = gate.second;
++sfx;
if (ctx->verbose) {
log_info(" %s->%s\n", port_name.c_str(ctx), ctx->nameOfWire(gate.first));
}
}
}
}
}
}
// realy connect nets
if (!top_connections.empty()) {
for (auto conn : top_connections) {
top_ci->connectPort(conn.first, conn.second ? vcc_net : vss_net);
}
ctx->cells[top_ci->name] = std::move(top_ci);
}
if (!bottom_connections.empty()) {
for (auto conn : bottom_connections) {
bottom_ci->connectPort(conn.first, conn.second ? vcc_net : vss_net);
}
ctx->cells[bottom_ci->name] = std::move(bottom_ci);
}
}
// Route all
void run(void)
{
@ -1295,6 +1365,14 @@ struct GowinGlobalRouter
if (gwu.get_segments_count() != 0) {
route_segmented(seg_nets);
}
// In some GW5 series chips, in addition to the mechanism for
// enabling/disabling individual clock spines using fuses, which is
// invisible to nextpnr, it is necessary to enable them by connecting
// some ports of the mysterious MUX to VSS/GND.
if (gwu.has_spine_enable_nets()) {
enable_spines();
}
}
};

View File

@ -143,7 +143,8 @@ void GowinImpl::init_database(Arch *arch)
std::regex devicere = std::regex("GW5A(T|ST)?-LV(25|60|138)[A-Z]*.*");
std::smatch match;
if (std::regex_match(args.device, match, devicere)) {
family = stringf("GW5A%s-%sA", match[1].str().c_str(), match[2].str().c_str());
family = stringf("GW5A%s-%s%s", match[1].str().c_str(), match[2].str().c_str(),
match[2].str() == "25" ? "A" : "C");
} else {
std::regex devicere = std::regex("GW1N([SZ]?)[A-Z]*-(LV|UV|UX)([0-9])(C?).*");
std::smatch match;
@ -1012,6 +1013,12 @@ bool GowinImpl::slice_valid(int x, int y, int z) const
ctx->getBoundBelCell(ctx->getBelByLocation(Loc(x, y, 5 * 2 + 1)))) {
return false;
}
if (gwu.has_DFF67()) {
if (ctx->getBoundBelCell(ctx->getBelByLocation(Loc(x, y, 6 * 2 + 1))) ||
ctx->getBoundBelCell(ctx->getBelByLocation(Loc(x, y, 7 * 2 + 1)))) {
return false;
}
}
// ALU/LUTs in slices 4, 5, 6, 7 are not allowed
for (int i = 4; i < 8; ++i) {
if (ctx->getBoundBelCell(ctx->getBelByLocation(Loc(x, y, i * 2)))) {

View File

@ -176,6 +176,14 @@ NPNR_PACKED_STRUCT(struct Segment_POD {
RelSlice<uint32_t> bottom_gate_wire;
});
NPNR_PACKED_STRUCT(struct SpineSelectWire_POD {
uint32_t spine;
int16_t x;
int16_t y;
uint32_t wire;
uint32_t vcc_gnd;
});
NPNR_PACKED_STRUCT(struct Constraint_POD {
int32_t net;
int32_t row;
@ -196,6 +204,9 @@ NPNR_PACKED_STRUCT(struct Extra_chip_data_POD {
RelSlice<Wire_bel_POD> dhcen_bels;
RelSlice<Io_dlldly_bel_POD> io_dlldly_bels;
RelSlice<Segment_POD> segments;
RelSlice<SpineSelectWire_POD> spine_select_wires_top;
RelSlice<SpineSelectWire_POD> spine_select_wires_bottom;
// chip flags
static constexpr int32_t HAS_SP32 = 1;
static constexpr int32_t NEED_SP_FIX = 2;
@ -209,6 +220,8 @@ NPNR_PACKED_STRUCT(struct Extra_chip_data_POD {
static constexpr int32_t HAS_CIN_MUX = 512;
static constexpr int32_t NEED_BSRAM_RESET_FIX = 1024;
static constexpr int32_t NEED_SDP_FIX = 2048;
static constexpr int32_t NEED_CFGPINS_INVERSION = 4096;
static constexpr int32_t HAS_I2CCFG = 8192;
});
} // namespace

View File

@ -15,21 +15,24 @@ from apycula import chipdb
BEL_FLAG_SIMPLE_IO = 0x100
# Wire flags
WIRE_FLAG_CLOCK_GATE = 0x1
WIRE_FLAG_CLOCK_GATE = 0x1
WIRE_FLAG_BOTTOM_HALF = 0x2 # the wire is located in the bottom half of the chip
# Chip flags
CHIP_HAS_SP32 = 0x1
CHIP_NEED_SP_FIX = 0x2
CHIP_NEED_BSRAM_OUTREG_FIX = 0x4
CHIP_NEED_BLKSEL_FIX = 0x8
CHIP_HAS_BANDGAP = 0x10
CHIP_HAS_PLL_HCLK = 0x20
CHIP_HAS_CLKDIV_HCLK = 0x40
CHIP_HAS_PINCFG = 0x80
CHIP_HAS_DFF67 = 0x100
CHIP_HAS_CIN_MUX = 0x200
CHIP_NEED_BSRAM_RESET_FIX = 0x400
CHIP_NEED_SDP_FIX = 0x800
CHIP_HAS_SP32 = 0x1
CHIP_NEED_SP_FIX = 0x2
CHIP_NEED_BSRAM_OUTREG_FIX = 0x4
CHIP_NEED_BLKSEL_FIX = 0x8
CHIP_HAS_BANDGAP = 0x10
CHIP_HAS_PLL_HCLK = 0x20
CHIP_HAS_CLKDIV_HCLK = 0x40
CHIP_HAS_PINCFG = 0x80
CHIP_HAS_DFF67 = 0x100
CHIP_HAS_CIN_MUX = 0x200
CHIP_NEED_BSRAM_RESET_FIX = 0x400
CHIP_NEED_SDP_FIX = 0x800
CHIP_NEED_CFGPINS_INVERSION = 0x1000
CHIP_HAS_I2CCFG = 0x2000
# Tile flags
TILE_I3C_CAPABLE_IO = 0x1
@ -265,6 +268,23 @@ class Segment(BBAStruct):
bba.slice(f"{context}_top_gate_wire", len(self.top_gate_wire))
bba.slice(f"{context}_bottom_gate_wire", len(self.bottom_gate_wire))
@dataclass
class SpineSelectWire(BBAStruct):
spine: IdString
x: int
y: int
wire: IdString
vcc_gnd: int
def serialise_lists(self, context: str, bba: BBAWriter):
pass
def serialise(self, context: str, bba: BBAWriter):
bba.u32(self.spine.index)
bba.u16(self.x)
bba.u16(self.y)
bba.u32(self.wire.index)
bba.u32(self.vcc_gnd)
@dataclass
class ChipExtraData(BBAStruct):
strs: StringPool
@ -277,6 +297,8 @@ class ChipExtraData(BBAStruct):
dhcen_bels: list[WireBel] = field(default_factory = list)
io_dlldly_bels: list[IoBel] = field(default_factory = list)
segments: list[Segment] = field(default_factory = list)
spine_select_wires_top: list[SpineSelectWire] = field(default_factory = list)
spine_select_wires_bottom: list[SpineSelectWire] = field(default_factory = list)
def set_dcs_prefix(self, prefix: str):
self.dcs_prefix = self.strs.id(prefix)
@ -301,6 +323,7 @@ class ChipExtraData(BBAStruct):
def add_io_dlldly_bel(self, io: str, dlldly: str):
self.io_dlldly_bels.append(IoBel(self.strs.id(io), self.strs.id(dlldly)))
def add_segment(self, x: int, seg_idx: int, min_x: int, min_y: int, max_x: int, max_y: int,
top_row: int, bottom_row: int, top_wire: str, bottom_wire: str, top_gate_wire: list, bottom_gate_wire: list):
new_seg = Segment(x, seg_idx, min_x, min_y, max_x, max_y, top_row, bottom_row,
@ -316,6 +339,12 @@ class ChipExtraData(BBAStruct):
new_seg.bottom_gate_wire.append(self.strs.id(''))
self.segments.append(new_seg)
def add_spine_select_wire_top(self, spine: str, x: int, y: int, wire: str, vcc_gnd: int):
self.spine_select_wires_top.append(SpineSelectWire(self.strs.id(spine), x, y, self.strs.id(wire), vcc_gnd))
def add_spine_select_wire_bottom(self, spine: str, x: int, y: int, wire: str, vcc_gnd: int):
self.spine_select_wires_bottom.append(SpineSelectWire(self.strs.id(spine), x, y, self.strs.id(wire), vcc_gnd))
def serialise_lists(self, context: str, bba: BBAWriter):
self.bottom_io.serialise_lists(f"{context}_bottom_io", bba)
for i, t in enumerate(self.segments):
@ -338,6 +367,12 @@ class ChipExtraData(BBAStruct):
bba.label(f"{context}_segments")
for i, t in enumerate(self.segments):
t.serialise(f"{context}_segment{i}", bba)
bba.label(f"{context}_spine_select_wires_top")
for i, t in enumerate(self.spine_select_wires_top):
t.serialise(f"{context}_spine_select_wire_top{i}", bba)
bba.label(f"{context}_spine_select_wires_bottom")
for i, t in enumerate(self.spine_select_wires_bottom):
t.serialise(f"{context}_spine_select_wire_bottom{i}", bba)
def serialise(self, context: str, bba: BBAWriter):
bba.u32(self.flags)
@ -349,6 +384,8 @@ class ChipExtraData(BBAStruct):
bba.slice(f"{context}_dhcen_bels", len(self.dhcen_bels))
bba.slice(f"{context}_io_dlldly_bels", len(self.io_dlldly_bels))
bba.slice(f"{context}_segments", len(self.segments))
bba.slice(f"{context}_spine_select_wires_top", len(self.spine_select_wires_top))
bba.slice(f"{context}_spine_select_wires_bottom", len(self.spine_select_wires_bottom))
@dataclass
class PackageExtraData(BBAStruct):
@ -527,12 +564,17 @@ def create_switch_matrix(tt: TileType, db: chipdb, x: int, y: int):
tt.create_pip(src, dst, get_tm_class(db, src))
# clock wires
# always mark clock wires with location flag
for dst, srcs in db.grid[y][x].clock_pips.items():
if not tt.has_wire(dst):
tt.create_wire(dst, "GLOBAL_CLK")
wire = tt.create_wire(dst, "GLOBAL_CLK")
if hasattr(db, "last_top_row") and y > db.last_top_row:
wire.flags |= WIRE_FLAG_BOTTOM_HALF
for src in srcs.keys():
if not tt.has_wire(src):
tt.create_wire(src, "GLOBAL_CLK")
wire = tt.create_wire(src, "GLOBAL_CLK")
if hasattr(db, "last_top_row") and y > db.last_top_row:
wire.flags |= WIRE_FLAG_BOTTOM_HALF
src_tm_class = get_tm_class(db, src)
tt.create_pip(src, dst, src_tm_class)
@ -1466,6 +1508,17 @@ def create_extra_data(chip: Chip, db: chipdb, chip_flags: int):
node.append(NodeWire(col, row, f'LB{idx}1'))
chip.add_node(node)
chip.add_node(lt_node)
# create spine select wires
if hasattr(db, "spine_select_wires"):
if 'top' in db.spine_select_wires:
for spine, wire_desc in db.spine_select_wires['top'].items():
for y, x, wire, vcc_gnd in wire_desc:
chip.extra_data.add_spine_select_wire_top(spine, x, y, wire, vcc_gnd)
if 'bottom' in db.spine_select_wires:
for spine, wire_desc in db.spine_select_wires['bottom'].items():
for y, x, wire, vcc_gnd in wire_desc:
chip.extra_data.add_spine_select_wire_bottom(spine, x, y, wire, vcc_gnd)
def create_timing_info(chip: Chip, db: chipdb.Device):
def group_to_timingvalue(group):
@ -1636,6 +1689,10 @@ def main():
chip_flags |= CHIP_NEED_BSRAM_RESET_FIX;
if "NEED_SDP_FIX" in db.chip_flags:
chip_flags |= CHIP_NEED_SDP_FIX;
if "NEED_CFGPINS_INVERSION" in db.chip_flags:
chip_flags |= CHIP_NEED_CFGPINS_INVERSION;
if "CHIP_HAS_I2CCFG" in db.chip_flags:
chip_flags |= CHIP_HAS_I2CCFG;
X = db.cols;
Y = db.rows;

View File

@ -350,18 +350,51 @@ bool GowinUtils::has_BANDGAP(void)
return extra->chip_flags & Extra_chip_data_POD::HAS_BANDGAP;
}
bool GowinUtils::has_PINCFG(void)
bool GowinUtils::has_PINCFG(void) const
{
const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
return extra->chip_flags & Extra_chip_data_POD::HAS_PINCFG;
}
bool GowinUtils::need_CFGPINS_INVERSION(void) const
{
const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
return extra->chip_flags & Extra_chip_data_POD::NEED_CFGPINS_INVERSION;
}
bool GowinUtils::has_I2CCFG(void) const
{
const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
return extra->chip_flags & Extra_chip_data_POD::HAS_I2CCFG;
}
bool GowinUtils::has_DFF67(void) const
{
const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
return extra->chip_flags & Extra_chip_data_POD::HAS_DFF67;
}
bool GowinUtils::has_spine_enable_nets(void) const
{
const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
return extra->spine_select_wires_top.ssize() || extra->spine_select_wires_bottom.ssize();
}
bool GowinUtils::get_spine_select_wire(WireId spine, std::vector<std::pair<WireId, int>> &wires)
{
const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
wires.clear();
for (auto &rec : wire_in_bottom_half(spine) ? extra->spine_select_wires_bottom : extra->spine_select_wires_top) {
if (IdString(rec.spine) == ctx->getWireName(spine)[1]) {
IdString tile = ctx->idf("X%dY%d", rec.x, rec.y);
IdStringList name = IdStringList::concat(tile, IdString(rec.wire));
wires.push_back(std::make_pair(ctx->getWireByName(name), rec.vcc_gnd));
}
}
return !wires.empty();
}
bool GowinUtils::has_CIN_MUX(void) const
{
const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());

View File

@ -14,7 +14,8 @@ static constexpr uint32_t FLAG_SIMPLE_IO = 0x100;
namespace WireFlags {
static constexpr uint32_t FLAG_CLOCK_GATE = 0x1;
}
static constexpr uint32_t FLAG_BOTTOM_HALF = 0x2;
} // namespace WireFlags
struct GowinUtils
{
@ -96,6 +97,12 @@ struct GowinUtils
return chip_wire_info(ctx->chip_info, wire).flags & WireFlags::FLAG_CLOCK_GATE;
}
bool wire_in_bottom_half(WireId wire) const
{
return chip_wire_info(ctx->chip_info, wire).flags & WireFlags::FLAG_BOTTOM_HALF;
}
bool get_spine_select_wire(WireId spine, std::vector<std::pair<WireId, int>> &);
// BSRAM
bool has_SP32(void);
bool need_SP_fix(void);
@ -110,7 +117,9 @@ struct GowinUtils
bool has_BANDGAP(void);
// Pin function configuration via wires
bool has_PINCFG(void);
bool has_PINCFG(void) const;
bool need_CFGPINS_INVERSION(void) const;
bool has_I2CCFG(void) const;
// Logic cell structure
bool has_DFF67(void) const;
@ -118,6 +127,9 @@ struct GowinUtils
// ALU
bool has_CIN_MUX(void) const;
// Clock MUX
bool has_spine_enable_nets(void) const;
// DSP
inline int get_dsp_18_z(int z) const { return z & (~3); }
inline int get_dsp_9_idx(int z) const { return z & 3; }

View File

@ -3959,10 +3959,15 @@ struct GowinPacker
auto pincfg_cell = std::make_unique<CellInfo>(ctx, id_PINCFG, id_PINCFG);
for (int i = 0; i < 5; ++i) {
const int pin_cnt = gwu.has_I2CCFG() ? 5 : 4;
for (int i = 0; i < pin_cnt; ++i) {
IdString port = ctx->idf("UNK%d_VCC", i);
pincfg_cell->addInput(port);
pincfg_cell->connectPort(port, ctx->nets.at(ctx->id("$PACKER_VCC")).get());
if (i && gwu.need_CFGPINS_INVERSION()) {
pincfg_cell->connectPort(port, ctx->nets.at(ctx->id("$PACKER_GND")).get());
} else {
pincfg_cell->connectPort(port, ctx->nets.at(ctx->id("$PACKER_VCC")).get());
}
}
const ArchArgs &args = ctx->args;
@ -3975,12 +3980,14 @@ struct GowinPacker
pincfg_cell->connectPort(id_SSPI, ctx->nets.at(ctx->id("$PACKER_GND")).get());
}
pincfg_cell->addInput(id_I2C);
if (args.options.count("i2c_as_gpio")) {
pincfg_cell->connectPort(id_I2C, ctx->nets.at(ctx->id("$PACKER_VCC")).get());
pincfg_cell->setParam(id_I2C, 1);
} else {
pincfg_cell->connectPort(id_I2C, ctx->nets.at(ctx->id("$PACKER_GND")).get());
if (gwu.has_I2CCFG()) {
pincfg_cell->addInput(id_I2C);
if (args.options.count("i2c_as_gpio")) {
pincfg_cell->connectPort(id_I2C, ctx->nets.at(ctx->id("$PACKER_VCC")).get());
pincfg_cell->setParam(id_I2C, 1);
} else {
pincfg_cell->connectPort(id_I2C, ctx->nets.at(ctx->id("$PACKER_GND")).get());
}
}
ctx->cells[pincfg_cell->name] = std::move(pincfg_cell);
}