diff --git a/himbaechel/uarch/gowin/constids.inc b/himbaechel/uarch/gowin/constids.inc index b5eb981b..883961ed 100644 --- a/himbaechel/uarch/gowin/constids.inc +++ b/himbaechel/uarch/gowin/constids.inc @@ -1089,3 +1089,9 @@ X(XD3) X(XD4) X(XD5) +// HCLK wires +X(HCLK_OUT0) +X(HCLK_OUT1) +X(HCLK_OUT2) +X(HCLK_OUT3) + diff --git a/himbaechel/uarch/gowin/globals.cc b/himbaechel/uarch/gowin/globals.cc index ecb99e43..365924fc 100644 --- a/himbaechel/uarch/gowin/globals.cc +++ b/himbaechel/uarch/gowin/globals.cc @@ -52,12 +52,15 @@ struct GowinGlobalRouter bool src_valid = src_type.in(id_GLOBAL_CLK, id_IO_O, id_PLL_O); bool dst_valid = dst_type.in(id_GLOBAL_CLK, id_TILE_CLK, id_PLL_I, id_IO_I); - if (ctx->debug && false) { + bool res = (src_valid && dst_valid) || (src_valid && is_local(dst_type)) || (is_local(src_type) && dst_valid); + if (ctx->debug && res && false) { log_info("%s <- %s [%s <- %s]\n", ctx->getWireName(ctx->getPipDstWire(pip)).str(ctx).c_str(), ctx->getWireName(ctx->getPipSrcWire(pip)).str(ctx).c_str(), dst_type.c_str(ctx), src_type.c_str(ctx)); + // log_info("res:%d, src_valid:%d, dst_valid:%d, src local:%d, dst local:%d\n", res, src_valid, dst_valid, + // is_local(src_type), is_local(dst_type)); } - return (src_valid && dst_valid) || (src_valid && is_local(dst_type)) || (is_local(src_type) && dst_valid); + return res; } bool is_relaxed_sink(const PortRef &sink) const { return false; } diff --git a/himbaechel/uarch/gowin/gowin.cc b/himbaechel/uarch/gowin/gowin.cc index 846ef18b..cebefa89 100644 --- a/himbaechel/uarch/gowin/gowin.cc +++ b/himbaechel/uarch/gowin/gowin.cc @@ -29,6 +29,7 @@ struct GowinImpl : HimbaechelAPI void prePlace() override; void postPlace() override; void preRoute() override; + void postRoute() override; bool isBelLocationValid(BelId bel, bool explain_invalid) const override; @@ -37,6 +38,10 @@ struct GowinImpl : HimbaechelAPI bool isValidBelForCellType(IdString cell_type, BelId bel) const override; + // placer hits + void notifyBelChange(BelId bel, CellInfo *cell) override; + bool checkBelAvail(BelId bel) const override; + private: HimbaechelHelpers h; GowinUtils gwu; @@ -44,6 +49,8 @@ struct GowinImpl : HimbaechelAPI IdString chip; IdString partno; + std::set inactive_bels; + // Validity checking struct GowinCellInfo { @@ -154,8 +161,11 @@ void GowinImpl::postPlace() log_info("================== Final Placement ===================\n"); for (auto &cell : ctx->cells) { auto ci = cell.second.get(); - IdStringList bel = ctx->getBelName(ci->bel); - log_info("%s: %s\n", bel.str(ctx).c_str(), ctx->nameOf(ci)); + if (ci->bel != BelId()) { + log_info("%s: %s\n", ctx->nameOfBel(ci->bel), ctx->nameOf(ci)); + } else { + log_info("unknown: %s\n", ctx->nameOf(ci)); + } } log_break(); } @@ -163,6 +173,43 @@ void GowinImpl::postPlace() void GowinImpl::preRoute() { gowin_route_globals(ctx); } +void GowinImpl::postRoute() +{ + std::set visited_hclk_users; + + for (auto &cell : ctx->cells) { + auto ci = cell.second.get(); + if (is_iologic(ci) && !ci->type.in(id_ODDR, id_ODDRC, id_IDDR, id_IDDRC)) { + if (visited_hclk_users.find(ci->name) == visited_hclk_users.end()) { + // mark FCLK<-HCLK connections + ci->setAttr(id_IOLOGIC_FCLK, Property("UNKNOWN")); + const NetInfo *h_net = ci->getPort(id_FCLK); + if (h_net) { + for (auto const &user : h_net->users) { + if (user.port != id_FCLK) { + continue; + } + visited_hclk_users.insert(user.cell->name); + // XXX Based on the implementation, perhaps a function + // is needed to get Pip from a Wire + PipId up_pip = h_net->wires.at(ctx->getNetinfoSinkWire(h_net, user, 0)).pip; + IdString up_wire_name = ctx->getWireName(ctx->getPipSrcWire(up_pip))[1]; + if (up_wire_name.in(id_HCLK_OUT0, id_HCLK_OUT1, id_HCLK_OUT2, id_HCLK_OUT3)) { + ci->setAttr(id_IOLOGIC_FCLK, Property(up_wire_name.str(ctx))); + } + if (ctx->debug) { + log_info("HCLK user cell:%s, port:%s, wire:%s, pip:%s, up wire:%s\n", + ctx->nameOf(user.cell), user.port.c_str(ctx), + ctx->nameOfWire(ctx->getNetinfoSinkWire(h_net, user, 0)), ctx->nameOfPip(up_pip), + ctx->nameOfWire(ctx->getPipSrcWire(up_pip))); + } + } + } + } + } + } +} + bool GowinImpl::isBelLocationValid(BelId bel, bool explain_invalid) const { Loc l = ctx->getBelLocation(bel); @@ -198,6 +245,9 @@ IdString GowinImpl::getBelBucketForCellType(IdString cell_type) const if (type_is_ssram(cell_type)) { return id_RAM16SDP4; } + if (type_is_iologic(cell_type)) { + return id_IOLOGIC; + } if (cell_type == id_GOWIN_GND) { return id_GND; } @@ -222,6 +272,9 @@ bool GowinImpl::isValidBelForCellType(IdString cell_type, BelId bel) const if (bel_type == id_RAM16SDP4) { return type_is_ssram(cell_type); } + if (bel_type == id_IOLOGIC) { + return type_is_iologic(cell_type); + } if (bel_type == id_GND) { return cell_type == id_GOWIN_GND; } @@ -342,6 +395,29 @@ bool GowinImpl::slice_valid(int x, int y, int z) const } return true; } +// placer hits +void GowinImpl::notifyBelChange(BelId bel, CellInfo *cell) +{ + if (cell != nullptr) { + // OSER8 took both IOLOGIC bels in the tile + if (cell->type == id_OSER8) { + Loc loc = ctx->getBelLocation(bel); + loc.z = BelZ::IOLOGICA_Z + (1 - (loc.z - BelZ::IOLOGICA_Z)); + inactive_bels.insert(ctx->getBelByLocation(loc)); + } + } else { + // the unbind is about to happen + CellInfo *ci = ctx->getBoundBelCell(bel); + // OSER8 took both IOLOGIC bels in the tile + if (ci->type == id_OSER8) { + Loc loc = ctx->getBelLocation(bel); + loc.z = BelZ::IOLOGICA_Z + (1 - (loc.z - BelZ::IOLOGICA_Z)); + inactive_bels.erase(ctx->getBelByLocation(loc)); + } + } +} + +bool GowinImpl::checkBelAvail(BelId bel) const { return inactive_bels.find(bel) == inactive_bels.end(); } } // namespace diff --git a/himbaechel/uarch/gowin/gowin.h b/himbaechel/uarch/gowin/gowin.h index 8f21c23a..4051b075 100644 --- a/himbaechel/uarch/gowin/gowin.h +++ b/himbaechel/uarch/gowin/gowin.h @@ -25,6 +25,16 @@ inline bool is_dff(const CellInfo *cell) { return type_is_dff(cell->type); } inline bool type_is_alu(IdString cell_type) { return cell_type == id_ALU; } inline bool is_alu(const CellInfo *cell) { return type_is_alu(cell->type); } +inline bool type_is_diffio(IdString cell_type) +{ + return cell_type.in(id_ELVDS_IOBUF, id_ELVDS_IBUF, id_ELVDS_TBUF, id_ELVDS_OBUF, id_TLVDS_IOBUF, id_TLVDS_IBUF, + id_TLVDS_TBUF, id_TLVDS_OBUF); +} +inline bool is_diffio(const CellInfo *cell) { return type_is_diffio(cell->type); } + +inline bool type_is_iologic(IdString cell_type) { return cell_type.in(id_ODDR, id_ODDRC, id_OSER4, id_OSER8); } +inline bool is_iologic(const CellInfo *cell) { return type_is_iologic(cell->type); } + // Return true if a cell is a SSRAM inline bool type_is_ssram(IdString cell_type) { return cell_type.in(id_RAM16SDP1, id_RAM16SDP2, id_RAM16SDP4); } inline bool is_ssram(const CellInfo *cell) { return type_is_ssram(cell->type); } @@ -38,10 +48,26 @@ NPNR_PACKED_STRUCT(struct Bottom_io_cnd_POD { NPNR_PACKED_STRUCT(struct Bottom_io_POD { // simple OBUF static constexpr int8_t NORMAL = 0; + // DDR + static constexpr int8_t DDR = 1; RelSlice conditions; }); -NPNR_PACKED_STRUCT(struct Extra_chip_data_POD { Bottom_io_POD bottom_io; }); +NPNR_PACKED_STRUCT(struct Extra_chip_data_POD { + Bottom_io_POD bottom_io; + RelSlice diff_io_types; +}); + +inline bool is_diff_io_supported(const ChipInfoPOD *chip, IdString type) +{ + const Extra_chip_data_POD *extra = reinterpret_cast(chip->extra_data.get()); + for (auto &dtype : extra->diff_io_types) { + if (IdString(dtype) == type) { + return true; + } + } + return false; +} inline bool have_bottom_io_cnds(const ChipInfoPOD *chip) { @@ -61,10 +87,12 @@ inline IdString get_bottom_io_wire_b_net(const ChipInfoPOD *chip, int8_t conditi return IdString(extra->bottom_io.conditions[condition].wire_b_net); } -inline bool getBelSimpleIO(const ChipInfoPOD *chip, BelId bel) +// Bels and pips +inline bool is_simple_io_bel(const ChipInfoPOD *chip, BelId bel) { return chip_bel_info(chip, bel).flags & BelFlags::FLAG_SIMPLE_IO; } + } // namespace // Bels Z ranges. It is desirable that these numbers be synchronized with the chipdb generator @@ -83,6 +111,8 @@ enum IOBA_Z = 50, IOBB_Z = 51, // +IOBC...IOBL + IOLOGICA_Z = 70, + PLL_Z = 275, GSR_Z = 276, VCC_Z = 277, diff --git a/himbaechel/uarch/gowin/gowin_arch_gen.py b/himbaechel/uarch/gowin/gowin_arch_gen.py index d2482ad7..2f38a960 100644 --- a/himbaechel/uarch/gowin/gowin_arch_gen.py +++ b/himbaechel/uarch/gowin/gowin_arch_gen.py @@ -28,6 +28,8 @@ RAMW_Z = 36 # RAM16SDP4 IOBA_Z = 50 IOBB_Z = 51 +IOLOGICA_Z = 70 + PLL_Z = 275 GSR_Z = 276 VCC_Z = 277 @@ -74,6 +76,7 @@ class BottomIO(BBAStruct): class ChipExtraData(BBAStruct): strs: StringPool bottom_io: BottomIO + diff_io_types: list[IdString] = field(default_factory = list) def create_bottom_io(self): self.bottom_io = BottomIO() @@ -81,12 +84,21 @@ class ChipExtraData(BBAStruct): def add_bottom_io_cnd(self, net_a: str, net_b: str): self.bottom_io.conditions.append(BottomIOCnd(self.strs.id(net_a), self.strs.id(net_b))) + def add_diff_io_type(self, diff_type: str): + self.diff_io_types.append(self.strs.id(diff_type)) + def serialise_lists(self, context: str, bba: BBAWriter): self.bottom_io.serialise_lists(f"{context}_bottom_io", bba) + bba.label(f"{context}_diff_io_types") + for i, diff_io_type in enumerate(self.diff_io_types): + bba.u32(diff_io_type.index) + def serialise(self, context: str, bba: BBAWriter): self.bottom_io.serialise(f"{context}_bottom_io", bba) + bba.slice(f"{context}_diff_io_types", len(self.diff_io_types)) -created_tiletypes = set() +# { ttyp : {}} +created_tiletypes = {} # u-turn at the rim uturnlut = {'N': 'S', 'S': 'N', 'E': 'W', 'W': 'E'} @@ -172,6 +184,8 @@ def create_nodes(chip: Chip, db: chipdb): # add nodes from the apicula db for node_name, node_hdr in db.nodes.items(): wire_type, node = node_hdr + if len(node) < 2: + continue for y, x, wire in node: if wire_type: if not chip.tile_type_at(x, y).has_wire(wire): @@ -181,17 +195,11 @@ def create_nodes(chip: Chip, db: chipdb): new_node = NodeWire(x, y, wire) gl_nodes = global_nodes.setdefault(node_name, []) if new_node not in gl_nodes: - gl_nodes.append(NodeWire(x, y, wire)) + gl_nodes.append(NodeWire(x, y, wire)) for name, node in global_nodes.items(): chip.add_node(node) - -# About X and Y as parameters - in some cases, the type of manufacturer's tile -# is not different, but some wires are not physically present, that is, routing -# depends on the location of otherwise identical tiles. There are many options -# for taking this into account, but for now we make a distinction here, by -# coordinates. def create_switch_matrix(tt: TileType, db: chipdb, x: int, y: int): def get_wire_type(name): if name in {'XD0', 'XD1', 'XD2', 'XD3', 'XD4', 'XD5',}: @@ -205,6 +213,7 @@ def create_switch_matrix(tt: TileType, db: chipdb, x: int, y: int): if not tt.has_wire(src): tt.create_wire(src, get_wire_type(src)) tt.create_pip(src, dst) + # clock wires for dst, srcs in db.grid[y][x].pure_clock_pips.items(): if not tt.has_wire(dst): @@ -214,21 +223,58 @@ def create_switch_matrix(tt: TileType, db: chipdb, x: int, y: int): tt.create_wire(src, "GLOBAL_CLK") tt.create_pip(src, dst) -def create_null_tiletype(chip: Chip, db: chipdb, x: int, y: int, ttyp: int): +def create_hclk_switch_matrix(tt: TileType, db: chipdb, x: int, y: int): + # hclk wires + for dst, srcs in db.hclk_pips[(y, x)].items(): + if not tt.has_wire(dst): + tt.create_wire(dst, "HCLK") + for src in srcs.keys(): + if not tt.has_wire(src): + tt.create_wire(src, "HCLK") + tt.create_pip(src, dst) + +extra_type_cnt = 1 +def create_tiletype(create_func, chip: Chip, db: chipdb, x: int, y: int, ttyp: int): + global extra_type_cnt + create_hclk_pips = False + + # HCLK wires can be different in the same types of tiles, so if necessary, create a new type if ttyp in created_tiletypes: - return ttyp, None - typename = "NULL" - tt = chip.create_tile_type(f"{typename}_{ttyp}") - tt.extra_data = TileExtraData(chip.strs.id(typename)) + # check if we have HCLK pips + if (y, x) in db.hclk_pips and created_tiletypes[ttyp]['hclk_pips'] != db.hclk_pips[y, x]: + create_hclk_pips = True + ttyp = 100 * ttyp + extra_type_cnt + extra_type_cnt += 1 + else: + chip.set_tile_type(x, y, created_tiletypes[ttyp]['tiletype']) + return + elif (y, x) in db.hclk_pips: + create_hclk_pips = True + else: + created_tiletypes[ttyp] = {} + if create_hclk_pips: + created_tiletypes.setdefault(ttyp, {}).update({'hclk_pips': db.hclk_pips[y, x]}) + + tiletype, tt = create_func(chip, db, x, y, ttyp) + + if create_hclk_pips: + create_hclk_switch_matrix(tt, db, x, y) create_switch_matrix(tt, db, x, y) - return (ttyp, tt) + chip.set_tile_type(x, y, tiletype) + created_tiletypes.setdefault(ttyp, {}).update({'tiletype': tiletype}) + +def create_null_tiletype(chip: Chip, db: chipdb, x: int, y: int, ttyp: int): + typename = "NULL" + tiletype = f"{typename}_{ttyp}" + tt = chip.create_tile_type(tiletype) + tt.extra_data = TileExtraData(chip.strs.id(typename)) + return (tiletype, tt) # responsible nodes, there will be IO banks, configuration, etc. def create_corner_tiletype(chip: Chip, db: chipdb, x: int, y: int, ttyp: int): - if ttyp in created_tiletypes: - return ttyp, None typename = "CORNER" - tt = chip.create_tile_type(f"{typename}_{ttyp}") + tiletype = f"{typename}_{ttyp}" + tt = chip.create_tile_type(tiletype) tt.extra_data = TileExtraData(chip.strs.id(typename)) if x == 0 and y == 0: @@ -241,21 +287,19 @@ def create_corner_tiletype(chip: Chip, db: chipdb, x: int, y: int, ttyp: int): gnd = tt.create_bel('VCC', 'VCC', z = VCC_Z) tt.add_bel_pin(gnd, "V", "VCC", PinType.OUTPUT) # also here may be GSR - if 'GSR' in db.grid[y][x].bels.keys(): + if 'GSR' in db.grid[y][x].bels: portmap = db.grid[y][x].bels['GSR'].portmap tt.create_wire(portmap['GSRI'], "GSRI") io = tt.create_bel("GSR", "GSR", z = GSR_Z) tt.add_bel_pin(io, "GSRI", portmap['GSRI'], PinType.INPUT) - create_switch_matrix(tt, db, x, y) - return (ttyp, tt) + return (tiletype, tt) # Global set/reset. GW2A series has special cell for it def create_gsr_tiletype(chip: Chip, db: chipdb, x: int, y: int, ttyp: int): - if ttyp in created_tiletypes: - return ttyp, None typename = "GSR" - tt = chip.create_tile_type(f"{typename}_{ttyp}") + tiletype = f"{typename}_{ttyp}" + tt = chip.create_tile_type(tiletype) tt.extra_data = TileExtraData(chip.strs.id(typename)) portmap = db.grid[y][x].bels['GSR'].portmap @@ -263,15 +307,13 @@ def create_gsr_tiletype(chip: Chip, db: chipdb, x: int, y: int, ttyp: int): io = tt.create_bel("GSR", "GSR", z = GSR_Z) tt.add_bel_pin(io, "GSRI", portmap['GSRI'], PinType.INPUT) - create_switch_matrix(tt, db, x, y) - return (ttyp, tt) + return (tiletype, tt) -# simple IO - only A and B +# IO def create_io_tiletype(chip: Chip, db: chipdb, x: int, y: int, ttyp: int): - if ttyp in created_tiletypes: - return ttyp, None typename = "IO" - tt = chip.create_tile_type(f"{typename}_{ttyp}") + tiletype = f"{typename}_{ttyp}" + tt = chip.create_tile_type(tiletype) tt.extra_data = TileExtraData(chip.strs.id(typename)) simple_io = y in db.simplio_rows and chip.name in {'GW1N-1', 'GW1NZ-1'} @@ -282,7 +324,7 @@ def create_io_tiletype(chip: Chip, db: chipdb, x: int, y: int, ttyp: int): for i in range(rng): name = 'IOB' + 'ABCDEFGHIJ'[i] # XXX some IOBs excluded from generic chipdb for some reason - if name not in db.grid[y][x].bels.keys(): + if name not in db.grid[y][x].bels: continue # wires portmap = db.grid[y][x].bels[name].portmap @@ -294,25 +336,39 @@ def create_io_tiletype(chip: Chip, db: chipdb, x: int, y: int, ttyp: int): if simple_io: io.flags |= BEL_FLAG_SIMPLE_IO tt.add_bel_pin(io, "I", portmap['I'], PinType.INPUT) - tt.add_bel_pin(io, "OE", portmap['OE'], PinType.INPUT) + tt.add_bel_pin(io, "OEN", portmap['OE'], PinType.INPUT) tt.add_bel_pin(io, "O", portmap['O'], PinType.OUTPUT) # bottom io - if 'BOTTOM_IO_PORT_A' in portmap.keys(): + if 'BOTTOM_IO_PORT_A' in portmap: if not tt.has_wire(portmap['BOTTOM_IO_PORT_A']): tt.create_wire(portmap['BOTTOM_IO_PORT_A'], "IO_I") tt.create_wire(portmap['BOTTOM_IO_PORT_B'], "IO_I") tt.add_bel_pin(io, "BOTTOM_IO_PORT_A", portmap['BOTTOM_IO_PORT_A'], PinType.INPUT) tt.add_bel_pin(io, "BOTTOM_IO_PORT_B", portmap['BOTTOM_IO_PORT_B'], PinType.INPUT) - - create_switch_matrix(tt, db, x, y) - return (ttyp, tt) + # create IOLOGIC bels if any + for idx, name in {(IOLOGICA_Z, 'IOLOGICA'), (IOLOGICA_Z + 1, 'IOLOGICB')}: + if name not in db.grid[y][x].bels: + continue + iol = tt.create_bel(name, "IOLOGIC", z = idx) + for port, wire in db.grid[y][x].bels[name].portmap.items(): + if port == 'FCLK': # XXX compatibility + wire = f'FCLK{name[-1]}' + if not tt.has_wire(wire): + if port in {'CLK', 'PCLK'}: + tt.create_wire(wire, "TILE_CLK") + else: + tt.create_wire(wire, "IOL_PORT") + if port in {'Q', 'Q0', 'Q1', 'Q2', 'Q3', 'Q4', 'Q5', 'Q6', 'Q7', 'Q8', 'Q9', 'DF', 'LAG', 'LEAD'}: + tt.add_bel_pin(iol, port, wire, PinType.OUTPUT) + else: + tt.add_bel_pin(iol, port, wire, PinType.INPUT) + return (tiletype, tt) # logic: luts, dffs, alu etc def create_logic_tiletype(chip: Chip, db: chipdb, x: int, y: int, ttyp: int): - if ttyp in created_tiletypes: - return ttyp, None typename = "LOGIC" - tt = chip.create_tile_type(f"{typename}_{ttyp}") + tiletype = f"{typename}_{ttyp}" + tt = chip.create_tile_type(tiletype) tt.extra_data = TileExtraData(chip.strs.id(typename)) lut_inputs = ['A', 'B', 'C', 'D'] @@ -401,14 +457,11 @@ def create_logic_tiletype(chip: Chip, db: chipdb, x: int, y: int, ttyp: int): tt.add_bel_pin(ff, "O", f"OF7", PinType.OUTPUT) tt.add_bel_pin(ff, "S0", f"SEL7", PinType.INPUT) - create_switch_matrix(tt, db, x, y) - return (ttyp, tt) + return (tiletype, tt) def create_ssram_tiletype(chip: Chip, db: chipdb, x: int, y: int, ttyp: int): - if ttyp in created_tiletypes: - return ttyp, None # SSRAM is LUT based, so it's logic-like - ttyp, tt = create_logic_tiletype(chip, db, x, y, ttyp) + tiletype, tt = create_logic_tiletype(chip, db, x, y, ttyp) lut_inputs = ['A', 'B', 'C', 'D'] ff = tt.create_bel(f"RAM16SDP4", "RAM16SDP4", z = RAMW_Z) @@ -424,7 +477,7 @@ def create_ssram_tiletype(chip: Chip, db: chipdb, x: int, y: int, ttyp: int): tt.add_bel_pin(ff, f"CLK", "CLK2", PinType.INPUT) tt.add_bel_pin(ff, f"CE", "CE2", PinType.INPUT) tt.add_bel_pin(ff, f"WRE", "LSR2", PinType.INPUT) - return (ttyp, tt) + return (tiletype, tt) # PLL main tile _pll_inputs = {'CLKFB', 'FBDSEL0', 'FBDSEL1', 'FBDSEL2', 'FBDSEL3', @@ -435,10 +488,9 @@ _pll_inputs = {'CLKFB', 'FBDSEL0', 'FBDSEL1', 'FBDSEL2', 'FBDSEL3', 'FDLY0', 'FDLY1', 'FDLY2', 'FDLY3', 'CLKIN'} _pll_outputs = {'CLKOUT', 'LOCK', 'CLKOUTP', 'CLKOUTD', 'CLKOUTD3'} def create_pll_tiletype(chip: Chip, db: chipdb, x: int, y: int, ttyp: int): - if ttyp in created_tiletypes: - return ttyp, None typename = "PLL" - tt = chip.create_tile_type(f"{typename}_{ttyp}") + tiletype = f"{typename}_{ttyp}" + tt = chip.create_tile_type(tiletype) tt.extra_data = TileExtraData(chip.strs.id(typename)) # wires @@ -450,20 +502,16 @@ def create_pll_tiletype(chip: Chip, db: chipdb, x: int, y: int, ttyp: int): bel_type = 'rPLL' portmap = db.grid[y][x].bels[pll_name].portmap pll = tt.create_bel("PLL", bel_type, z = PLL_Z) - # Not sure how this will affect routing - PLLs are fixed and their outputs - # will be handled by a dedicated router - #pll.flags = BEL_FLAG_GLOBAL + 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}" + assert pin in _pll_outputs, f"Unknown PLL pin {pin}" tt.create_wire(wire, "PLL_O") tt.add_bel_pin(pll, pin, wire, PinType.OUTPUT) - - create_switch_matrix(tt, db, x, y) - return (ttyp, tt) + return (tiletype, tt) # pinouts, packages... _tbrlre = re.compile(r"IO([TBRL])(\d+)(\w)") @@ -508,6 +556,8 @@ def create_extra_data(chip: Chip, db: chipdb): chip.extra_data.create_bottom_io() for net_a, net_b in db.bottom_io[2]: chip.extra_data.add_bottom_io_cnd(net_a, net_b) + for diff_type in db.diff_io_types: + chip.extra_data.add_diff_io_type(diff_type) def main(): parser = argparse.ArgumentParser(description='Make Gowin BBA') @@ -536,43 +586,30 @@ def main(): # different routing or something like that). logic_tiletypes = db.tile_types['C'] io_tiletypes = db.tile_types['I'] - ssram_tiletypes = {17, 18, 19} + ssram_tiletypes = db.tile_types['M'] gsr_tiletypes = {1} pll_tiletypes = db.tile_types['P'] + # Setup tile grid for x in range(X): for y in range(Y): ttyp = db.grid[y][x].ttyp if (x == 0 or x == X - 1) and (y == 0 or y == Y - 1): assert ttyp not in created_tiletypes, "Duplication of corner types" - ttyp, _ = create_corner_tiletype(ch, db, x, y, ttyp) - created_tiletypes.add(ttyp) - ch.set_tile_type(x, y, f"CORNER_{ttyp}") + create_tiletype(create_corner_tiletype, ch, db, x, y, ttyp) continue if ttyp in gsr_tiletypes: - ttyp, _ = create_gsr_tiletype(ch, db, x, y, ttyp) - created_tiletypes.add(ttyp) - ch.set_tile_type(x, y, f"GSR_{ttyp}") + create_tiletype(create_gsr_tiletype, ch, db, x, y, ttyp) elif ttyp in logic_tiletypes: - ttyp, _ = create_logic_tiletype(ch, db, x, y, ttyp) - created_tiletypes.add(ttyp) - ch.set_tile_type(x, y, f"LOGIC_{ttyp}") + create_tiletype(create_logic_tiletype, ch, db, x, y, ttyp) elif ttyp in ssram_tiletypes: - ttyp, _ = create_ssram_tiletype(ch, db, x, y, ttyp) - created_tiletypes.add(ttyp) - ch.set_tile_type(x, y, f"LOGIC_{ttyp}") + create_tiletype(create_ssram_tiletype, ch, db, x, y, ttyp) elif ttyp in io_tiletypes: - ttyp, _ = create_io_tiletype(ch, db, x, y, ttyp) - created_tiletypes.add(ttyp) - ch.set_tile_type(x, y, f"IO_{ttyp}") + create_tiletype(create_io_tiletype, ch, db, x, y, ttyp) elif ttyp in pll_tiletypes: - ttyp, _ = create_pll_tiletype(ch, db, x, y, ttyp) - created_tiletypes.add(ttyp) - ch.set_tile_type(x, y, f"PLL_{ttyp}") + create_tiletype(create_pll_tiletype, ch, db, x, y, ttyp) else: - ttyp, _ = create_null_tiletype(ch, db, x, y, ttyp) - created_tiletypes.add(ttyp) - ch.set_tile_type(x, y, f"NULL_{ttyp}") + create_tiletype(create_null_tiletype, ch, db, x, y, ttyp) # Create nodes between tiles create_nodes(ch, db) diff --git a/himbaechel/uarch/gowin/pack.cc b/himbaechel/uarch/gowin/pack.cc index cd79f319..922134b0 100644 --- a/himbaechel/uarch/gowin/pack.cc +++ b/himbaechel/uarch/gowin/pack.cc @@ -1,3 +1,4 @@ +#include "design_utils.h" #include "log.h" #include "nextpnr.h" @@ -21,18 +22,39 @@ struct GowinPacker // =================================== // IO // =================================== + // create IOB connections for gowin_pack + // can be called repeatedly when switching inputs, disabled outputs do not change + void make_iob_nets(CellInfo &iob) + { + for (const auto &port : iob.ports) { + const NetInfo *net = iob.getPort(port.first); + std::string connected_net = "NET"; + if (net != nullptr) { + if (ctx->verbose) { + log_info("%s: %s - %s\n", ctx->nameOf(&iob), port.first.c_str(ctx), ctx->nameOf(net)); + } + if (net->name == ctx->id("$PACKER_VCC")) { + connected_net = "VCC"; + } else if (net->name == ctx->id("$PACKER_GND")) { + connected_net = "GND"; + } + iob.setParam(ctx->idf("NET_%s", port.first.c_str(ctx)), connected_net); + } + } + } + void config_simple_io(CellInfo &ci) { if (ci.type.in(id_TBUF, id_IOBUF)) { return; } log_info("simple:%s\n", ctx->nameOf(&ci)); - ci.addInput(id_OE); + ci.addInput(id_OEN); if (ci.type == id_OBUF) { - ci.connectPort(id_OE, ctx->nets[ctx->id("$PACKER_GND")].get()); + ci.connectPort(id_OEN, ctx->nets[ctx->id("$PACKER_GND")].get()); } else { NPNR_ASSERT(ci.type == id_IBUF); - ci.connectPort(id_OE, ctx->nets[ctx->id("$PACKER_VCC")].get()); + ci.connectPort(id_OEN, ctx->nets[ctx->id("$PACKER_VCC")].get()); } } @@ -44,7 +66,7 @@ struct GowinPacker if (!ci.type.in(id_OBUF, id_TBUF, id_IOBUF)) { return; } - if (cnd == Bottom_io_POD::NORMAL && loc.z != BelZ::IOBA_Z) { + if (loc.z != BelZ::IOBA_Z) { return; } auto connect_io_wire = [&](IdString port, IdString net_name) { @@ -60,6 +82,7 @@ struct GowinPacker } }; + log_info("cnd:%d\n", cnd); IdString wire_a_net = get_bottom_io_wire_a_net(ctx->chip_info, cnd); connect_io_wire(id_BOTTOM_IO_PORT_A, wire_a_net); @@ -74,6 +97,8 @@ struct GowinPacker const pool top_ports{ CellTypePort(id_IBUF, id_I), CellTypePort(id_OBUF, id_O), + CellTypePort(id_TBUF, id_O), + CellTypePort(id_IOBUF, id_IO), }; std::vector to_remove; for (auto &cell : ctx->cells) { @@ -100,8 +125,18 @@ struct GowinPacker } } } + NetInfo *io = ci.getPort(ctx->id("IO")); + if (io && io->driver.cell) { + if (!top_ports.count(CellTypePort(io->driver))) + log_error("Top-level port '%s' driven by illegal port %s.%s\n", ctx->nameOf(&ci), + ctx->nameOf(io->driver.cell), ctx->nameOf(io->driver.port)); + for (const auto &attr : ci.attrs) { + io->driver.cell->attrs[attr.first] = attr.second; + } + } ci.disconnectPort(ctx->id("I")); ci.disconnectPort(ctx->id("O")); + ci.disconnectPort(ctx->id("IO")); to_remove.push_back(ci.name); } for (IdString cell_name : to_remove) @@ -124,7 +159,7 @@ struct GowinPacker for (auto &cell : ctx->cells) { CellInfo &ci = *cell.second; - if (!ci.type.in(id_IBUF, id_OBUF, id_IOBUF)) // XXX TBUF + if (!ci.type.in(id_IBUF, id_OBUF, id_TBUF, id_IOBUF)) continue; if (ci.attrs.count(id_BEL) == 0) { log_error("Unconstrained IO:%s\n", ctx->nameOf(&ci)); @@ -134,9 +169,242 @@ struct GowinPacker if (io_loc.y == ctx->getGridDimY() - 1) { config_bottom_row(ci, io_loc); } - if (getBelSimpleIO(ctx->chip_info, io_bel)) { + if (is_simple_io_bel(ctx->chip_info, io_bel)) { config_simple_io(ci); } + make_iob_nets(ci); + } + } + + // =================================== + // Differential IO + // =================================== + static bool is_iob(const Context *ctx, CellInfo *cell) + { + return (cell->type.in(id_IBUF, id_OBUF, id_TBUF, id_IOBUF)); + } + + std::pair get_pn_cells(const CellInfo &ci) + { + CellInfo *p, *n; + switch (ci.type.hash()) { + case ID_ELVDS_TBUF: /* fall-through */ + case ID_TLVDS_TBUF: /* fall-through */ + case ID_ELVDS_OBUF: /* fall-through */ + case ID_TLVDS_OBUF: + p = net_only_drives(ctx, ci.ports.at(id_O).net, is_iob, id_I, true); + n = net_only_drives(ctx, ci.ports.at(id_OB).net, is_iob, id_I, true); + break; + case ID_ELVDS_IBUF: /* fall-through */ + case ID_TLVDS_IBUF: + p = net_driven_by(ctx, ci.ports.at(id_I).net, is_iob, id_O); + n = net_driven_by(ctx, ci.ports.at(id_IB).net, is_iob, id_O); + break; + case ID_ELVDS_IOBUF: /* fall-through */ + case ID_TLVDS_IOBUF: + p = net_only_drives(ctx, ci.ports.at(id_IO).net, is_iob, id_I); + n = net_only_drives(ctx, ci.ports.at(id_IOB).net, is_iob, id_I); + break; + default: + log_error("Bad diff IO '%s' type '%s'\n", ctx->nameOf(&ci), ci.type.c_str(ctx)); + } + return std::make_pair(p, n); + } + + void mark_iobs_as_diff(CellInfo &ci, std::pair &pn_cells) + { + pn_cells.first->setParam(id_DIFF, std::string("P")); + pn_cells.first->setParam(id_DIFF_TYPE, ci.type.str(ctx)); + pn_cells.second->setParam(id_DIFF, std::string("N")); + pn_cells.second->setParam(id_DIFF_TYPE, ci.type.str(ctx)); + } + + void switch_diff_ports(CellInfo &ci, std::pair &pn_cells, + std::vector &nets_to_remove) + { + CellInfo *iob_p = pn_cells.first; + CellInfo *iob_n = pn_cells.second; + + if (ci.type.in(id_TLVDS_TBUF, id_TLVDS_OBUF, id_ELVDS_TBUF, id_ELVDS_OBUF)) { + nets_to_remove.push_back(ci.getPort(id_O)->name); + ci.disconnectPort(id_O); + nets_to_remove.push_back(ci.getPort(id_OB)->name); + ci.disconnectPort(id_OB); + nets_to_remove.push_back(iob_n->getPort(id_I)->name); + iob_n->disconnectPort(id_I); + + if (ci.type.in(id_TLVDS_TBUF, id_ELVDS_TBUF)) { + nets_to_remove.push_back(iob_n->getPort(id_OEN)->name); + iob_n->disconnectPort(id_OEN); + iob_p->disconnectPort(id_OEN); + ci.movePortTo(id_OEN, iob_p, id_OEN); + } + iob_p->disconnectPort(id_I); + ci.movePortTo(id_I, iob_p, id_I); + return; + } + if (ci.type.in(id_TLVDS_IBUF, id_ELVDS_IBUF)) { + nets_to_remove.push_back(ci.getPort(id_I)->name); + ci.disconnectPort(id_I); + nets_to_remove.push_back(ci.getPort(id_IB)->name); + ci.disconnectPort(id_IB); + iob_n->disconnectPort(id_O); + iob_p->disconnectPort(id_O); + ci.movePortTo(id_O, iob_p, id_O); + return; + } + if (ci.type.in(id_TLVDS_IOBUF, id_ELVDS_IOBUF)) { + nets_to_remove.push_back(ci.getPort(id_IO)->name); + ci.disconnectPort(id_IO); + nets_to_remove.push_back(ci.getPort(id_IOB)->name); + ci.disconnectPort(id_IOB); + nets_to_remove.push_back(iob_n->getPort(id_I)->name); + iob_n->disconnectPort(id_I); + iob_n->disconnectPort(id_OEN); + + iob_p->disconnectPort(id_OEN); + ci.movePortTo(id_OEN, iob_p, id_OEN); + iob_p->disconnectPort(id_I); + ci.movePortTo(id_I, iob_p, id_I); + iob_p->disconnectPort(id_O); + ci.movePortTo(id_O, iob_p, id_O); + return; + } + } + + void pack_diff_iobs(void) + { + log_info("Pack diff IOBs...\n"); + std::vector cells_to_remove, nets_to_remove; + + for (auto &cell : ctx->cells) { + CellInfo &ci = *cell.second; + if (!is_diffio(&ci)) { + continue; + } + if (!is_diff_io_supported(ctx->chip_info, ci.type)) { + log_error("%s is not supported\n", ci.type.c_str(ctx)); + } + cells_to_remove.push_back(ci.name); + auto pn_cells = get_pn_cells(ci); + NPNR_ASSERT(pn_cells.first != nullptr && pn_cells.second != nullptr); + + mark_iobs_as_diff(ci, pn_cells); + switch_diff_ports(ci, pn_cells, nets_to_remove); + } + + for (auto cell : cells_to_remove) { + ctx->cells.erase(cell); + } + for (auto net : nets_to_remove) { + ctx->nets.erase(net); + } + } + + // =================================== + // IO logic + // =================================== + // the functions of these two inputs are yet to be discovered, so we set as observed + // in the exemplary images + void set_daaj_nets(CellInfo &ci, BelId bel) + { + std::vector pins = ctx->getBelPins(bel); + if (std::find(pins.begin(), pins.end(), id_DAADJ0) != pins.end()) { + ci.addInput(id_DAADJ0); + ci.connectPort(id_DAADJ0, ctx->nets[ctx->id("$PACKER_GND")].get()); + } + if (std::find(pins.begin(), pins.end(), id_DAADJ1) != pins.end()) { + ci.addInput(id_DAADJ1); + ci.connectPort(id_DAADJ1, ctx->nets[ctx->id("$PACKER_VCC")].get()); + } + } + + BelId get_iologic_bel(CellInfo *iob) + { + NPNR_ASSERT(iob->bel != BelId()); + Loc loc = ctx->getBelLocation(iob->bel); + loc.z = loc.z - BelZ::IOBA_Z + BelZ::IOLOGICA_Z; + return ctx->getBelByLocation(loc); + } + + void pack_bi_output_iol(CellInfo &ci, std::vector &cells_to_remove, std::vector &nets_to_remove) + { + // These primitives have an additional pin to control the tri-state iob - Q1. + IdString out_port = id_Q0; + IdString tx_port = id_Q1; + + CellInfo *out_iob = net_only_drives(ctx, ci.ports.at(out_port).net, is_iob, id_I); + NPNR_ASSERT(out_iob != nullptr && out_iob->bel != BelId()); + BelId iob_bel = out_iob->bel; + + BelId l_bel = get_iologic_bel(out_iob); + if (l_bel == BelId()) { + log_error("Can't place IOLOGIC %s at %s\n", ctx->nameOf(&ci), ctx->nameOfBel(iob_bel)); + } + + if (!ctx->checkBelAvail(l_bel)) { + log_error("Can't place %s at %s because it's already taken by %s\n", ctx->nameOf(&ci), + ctx->nameOfBel(l_bel), ctx->nameOf(ctx->getBoundBelCell(l_bel))); + } + ctx->bindBel(l_bel, &ci, PlaceStrength::STRENGTH_LOCKED); + std::string out_mode; + switch (ci.type.hash()) { + case ID_ODDR: + case ID_ODDRC: + out_mode = "ODDRX1"; + break; + case ID_OSER4: + out_mode = "ODDRX2"; + break; + case ID_OSER8: + out_mode = "ODDRX4"; + break; + } + ci.setParam(ctx->id("OUTMODE"), out_mode); + + // mark IOB as used by IOLOGIC + out_iob->setParam(id_IOLOGIC_IOB, 1); + // disconnect Q output: it is wired internally + nets_to_remove.push_back(ci.getPort(out_port)->name); + out_iob->disconnectPort(id_I); + ci.disconnectPort(out_port); + set_daaj_nets(ci, iob_bel); + config_bottom_row(*out_iob, ctx->getBelLocation(iob_bel), Bottom_io_POD::DDR); + + // if Q1 is connected then disconnect it too + if (port_used(&ci, tx_port)) { + NPNR_ASSERT(out_iob == net_only_drives(ctx, ci.ports.at(tx_port).net, is_iob, id_OEN)); + nets_to_remove.push_back(ci.getPort(tx_port)->name); + out_iob->disconnectPort(id_OEN); + ci.disconnectPort(tx_port); + } + make_iob_nets(*out_iob); + } + + void pack_iologic() + { + log_info("Pack IO logic...\n"); + std::vector cells_to_remove, nets_to_remove; + + for (auto &cell : ctx->cells) { + CellInfo &ci = *cell.second; + if (!is_iologic(&ci)) { + continue; + } + if (ctx->debug) { + log_info("pack %s of type %s.\n", ctx->nameOf(&ci), ci.type.c_str(ctx)); + } + if (ci.type.in(id_ODDR, id_ODDRC, id_OSER4, id_OSER8)) { + pack_bi_output_iol(ci, cells_to_remove, nets_to_remove); + continue; + } + } + + for (auto cell : cells_to_remove) { + ctx->cells.erase(cell); + } + for (auto net : nets_to_remove) { + ctx->nets.erase(net); } } @@ -656,12 +924,17 @@ struct GowinPacker { handle_constants(); pack_iobs(); + pack_diff_iobs(); + pack_iologic(); pack_gsr(); pack_wideluts(); pack_alus(); constrain_lutffs(); pack_pll(); pack_ram16sdp4(); + + ctx->fixupHierarchy(); + ctx->check(); } }; } // namespace