From 05ff80a24eec8b2305a672a4c0d4977d41be3201 Mon Sep 17 00:00:00 2001 From: YRabbit Date: Tue, 13 Aug 2024 21:28:06 +1000 Subject: [PATCH] Gowin. Add DHCEN primitive. This primitive allows you to dynamically turn off and turn on the networks of high-speed clocks. This is done tracking the routes to the sinks and if the route passes through a special HCLK MUX (this may be the input MUX or the output MUX, as well as the interbank MUX), then the control signal of this MUX is used. Signed-off-by: YRabbit --- himbaechel/uarch/gowin/constids.inc | 4 +- himbaechel/uarch/gowin/globals.cc | 133 +++++++++++++++++++---- himbaechel/uarch/gowin/gowin.h | 15 ++- himbaechel/uarch/gowin/gowin_arch_gen.py | 51 ++++++++- himbaechel/uarch/gowin/gowin_utils.cc | 15 +++ himbaechel/uarch/gowin/gowin_utils.h | 4 + himbaechel/uarch/gowin/pack.cc | 29 +++++ 7 files changed, 223 insertions(+), 28 deletions(-) diff --git a/himbaechel/uarch/gowin/constids.inc b/himbaechel/uarch/gowin/constids.inc index fcadd941..dbe1daeb 100644 --- a/himbaechel/uarch/gowin/constids.inc +++ b/himbaechel/uarch/gowin/constids.inc @@ -1234,10 +1234,12 @@ X(BUFG) X(CLOCK) X(DQCE) X(DCS) -X(DCS_MODE) X(DQCE_PIP) +X(DHCEN_USED) X(DCS_USED) X(SELFORCE) +X(DHCEN) +X(DCS_MODE) //HCLK Bels X(CLKDIV) diff --git a/himbaechel/uarch/gowin/globals.cc b/himbaechel/uarch/gowin/globals.cc index e510174b..4e1f8f71 100644 --- a/himbaechel/uarch/gowin/globals.cc +++ b/himbaechel/uarch/gowin/globals.cc @@ -134,7 +134,8 @@ struct GowinGlobalRouter // Dedicated backwards BFS routing for global networks template - bool backwards_bfs_route(NetInfo *net, WireId src, WireId dst, int iter_limit, bool strict, Tfilt pip_filter) + bool backwards_bfs_route(NetInfo *net, WireId src, WireId dst, int iter_limit, bool strict, Tfilt pip_filter, + std::vector *path = nullptr) { // Queue of wires to visit std::queue visit; @@ -208,6 +209,9 @@ struct GowinGlobalRouter break; } ctx->bindPip(pip, net, STRENGTH_LOCKED); + if (path != nullptr) { + path->push_back(pip); + } } return true; } else { @@ -225,6 +229,7 @@ struct GowinGlobalRouter bool driver_is_buf(const PortRef &driver) { return CellTypePort(driver) == CellTypePort(id_BUFG, id_O); } bool driver_is_dqce(const PortRef &driver) { return CellTypePort(driver) == CellTypePort(id_DQCE, id_CLKOUT); } bool driver_is_dcs(const PortRef &driver) { return CellTypePort(driver) == CellTypePort(id_DCS, id_CLKOUT); } + bool driver_is_dhcen(const PortRef &driver) { return CellTypePort(driver) == CellTypePort(id_DHCEN, id_CLKOUT); } bool driver_is_clksrc(const PortRef &driver) { // dedicated pins @@ -276,7 +281,9 @@ struct GowinGlobalRouter ROUTED_ALL }; - RouteResult route_direct_net(NetInfo *net, WireId aux_src = WireId(), bool DCS_pips = false, bool DQCE_pips = false) + template + RouteResult route_direct_net(NetInfo *net, Tfilter pip_filter, WireId aux_src = WireId(), + std::vector *path = nullptr) { WireId src; src = aux_src == WireId() ? ctx->getNetinfoSourceWire(net) : aux_src; @@ -297,21 +304,9 @@ struct GowinGlobalRouter ctx->nameOf(usr.port)); } bool bfs_res; - if (DCS_pips) { - bfs_res = backwards_bfs_route(net, src, dst, 1000000, false, [&](PipId pip) { - return (is_relaxed_sink(usr) || global_DCS_pip_filter(pip)); - }); - } else { - if (DQCE_pips) { - bfs_res = backwards_bfs_route(net, src, dst, 1000000, false, [&](PipId pip) { - return (is_relaxed_sink(usr) || global_DQCE_pip_filter(pip)); - }); - } else { - bfs_res = backwards_bfs_route(net, src, dst, 1000000, false, [&](PipId pip) { - return (is_relaxed_sink(usr) || global_pip_filter(pip)); - }); - } - } + bfs_res = backwards_bfs_route( + net, src, dst, 1000000, false, [&](PipId pip) { return (is_relaxed_sink(usr) || pip_filter(pip)); }, + path); if (bfs_res) { routed = routed == ROUTED_PARTIALLY ? routed : ROUTED_ALL; } else { @@ -345,7 +340,8 @@ struct GowinGlobalRouter src = ctx->getBelPinWire(driver.cell->bel, driver.port); } - RouteResult route_result = route_direct_net(net, src, false, true); + RouteResult route_result = route_direct_net( + net, [&](PipId pip) { return global_DQCE_pip_filter(pip); }, src); if (route_result == NOT_ROUTED) { log_error("Can't route the %s network.\n", ctx->nameOf(net)); } @@ -422,7 +418,8 @@ struct GowinGlobalRouter src = ctx->getBelPinWire(driver.cell->bel, driver.port); } - RouteResult route_result = route_direct_net(net, src, true); + RouteResult route_result = route_direct_net( + net, [&](PipId pip) { return global_DCS_pip_filter(pip); }, src); if (route_result == NOT_ROUTED) { log_error("Can't route the %s network.\n", ctx->nameOf(net)); } @@ -487,6 +484,84 @@ struct GowinGlobalRouter ctx->cells.erase(dcs_ci->name); } + void route_dhcen_net(NetInfo *net) + { + // route net after dhcen source of CLKIN net + CellInfo *dhcen_ci = net->driver.cell; + + NetInfo *net_before_dhcen = dhcen_ci->getPort(id_CLKIN); + NPNR_ASSERT(net_before_dhcen != nullptr); + + PortRef driver = net_before_dhcen->driver; + NPNR_ASSERT_MSG(driver_is_buf(driver) || driver_is_clksrc(driver), + stringf("The input source for %s is not a clock.", ctx->nameOf(dhcen_ci)).c_str()); + + IdString port; + // use BUF input if there is one + if (driver_is_buf(driver)) { + port = id_I; + } else { + port = driver.port; + } + WireId src = ctx->getBelPinWire(driver.cell->bel, port); + + std::vector path; + RouteResult route_result = route_direct_net( + net, [&](PipId pip) { return global_pip_filter(pip); }, src, &path); + if (route_result == NOT_ROUTED) { + log_error("Can't route the %s network.\n", ctx->nameOf(net)); + } + if (route_result == ROUTED_PARTIALLY) { + log_error("It was not possible to completely route the %s net using only global resources. This is not " + "allowed for dhcen managed networks.\n", + ctx->nameOf(net)); + } + + // In networks controlled by dhcen we disable/enable only HCLK - if + // there are ordinary cells among the sinks, then they are not affected + // by this primitive. + for (PipId pip : path) { + // move to upper level net + ctx->unbindPip(pip); + ctx->bindPip(pip, net_before_dhcen, STRENGTH_LOCKED); + + WireId dst = ctx->getPipDstWire(pip); + IdString side; + BelId dhcen_bel = gwu.get_dhcen_bel(dst, side); + if (dhcen_bel == BelId()) { + continue; + } + + // One pseudo dhcen can be implemented as several hardware dhcen. + // Here we find suitable hardware dhcens. + CellInfo *hw_dhcen = ctx->getBoundBelCell(dhcen_bel); + if (ctx->debug) { + log_info(" use %s wire and %s bel for '%s' hw cell.\n", ctx->nameOfWire(dst), + ctx->nameOfBel(dhcen_bel), ctx->nameOf(hw_dhcen)); + } + + // The control network must connect the CE inputs of all hardware dhcens. + hw_dhcen->setAttr(id_DHCEN_USED, 1); + dhcen_ci->copyPortTo(id_CE, hw_dhcen, id_CE); + } + + // connect all users to upper level net + std::vector users; + for (auto &cell_port : net->users) { + users.push_back(cell_port); + } + for (PortRef &user : users) { + user.cell->disconnectPort(user.port); + user.cell->connectPort(user.port, net_before_dhcen); + } + + // remove the virtual dhcen + dhcen_ci->disconnectPort(id_CLKOUT); + dhcen_ci->disconnectPort(id_CLKIN); + dhcen_ci->disconnectPort(id_CE); + ctx->cells.erase(dhcen_ci->name); + } + void route_buffered_net(NetInfo *net) { // a) route net after buf using the buf input as source @@ -496,7 +571,8 @@ struct GowinGlobalRouter NetInfo *net_before_buf = buf_ci->getPort(id_I); NPNR_ASSERT(net_before_buf != nullptr); - RouteResult route_result = route_direct_net(net, src); + RouteResult route_result = route_direct_net( + net, [&](PipId pip) { return global_pip_filter(pip); }, src); if (route_result == NOT_ROUTED || route_result == ROUTED_PARTIALLY) { log_error("Can't route the %s net. It might be worth removing the BUFG buffer flag.\n", ctx->nameOf(net)); } @@ -516,7 +592,7 @@ struct GowinGlobalRouter void route_clk_net(NetInfo *net) { - RouteResult route_result = route_direct_net(net); + RouteResult route_result = route_direct_net(net, [&](PipId pip) { return global_pip_filter(pip); }); if (route_result != NOT_ROUTED) { log_info(" '%s' net was routed using global resources %s.\n", ctx->nameOf(net), route_result == ROUTED_ALL ? "only" : "partially"); @@ -527,7 +603,7 @@ struct GowinGlobalRouter { log_info("Routing globals...\n"); - std::vector dqce_nets, dcs_nets, buf_nets, clk_nets; + std::vector dhcen_nets, dqce_nets, dcs_nets, buf_nets, clk_nets; // Determining the priority of network routing for (auto &net : ctx->nets) { @@ -550,12 +626,25 @@ struct GowinGlobalRouter } else { if (driver_is_dcs(ni->driver)) { dcs_nets.push_back(net.first); + } else { + if (driver_is_dhcen(ni->driver)) { + dhcen_nets.push_back(net.first); + } } } } } } + // nets with DHCEN + for (IdString net_name : dhcen_nets) { + NetInfo *ni = ctx->nets.at(net_name).get(); + if (ctx->verbose) { + log_info("route dhcen net '%s'\n", ctx->nameOf(ni)); + } + route_dhcen_net(ni); + } + // nets with DQCE for (IdString net_name : dqce_nets) { NetInfo *ni = ctx->nets.at(net_name).get(); diff --git a/himbaechel/uarch/gowin/gowin.h b/himbaechel/uarch/gowin/gowin.h index c14949d6..544476dd 100644 --- a/himbaechel/uarch/gowin/gowin.h +++ b/himbaechel/uarch/gowin/gowin.h @@ -108,12 +108,22 @@ NPNR_PACKED_STRUCT(struct Spine_bel_POD { int32_t bel_z; }); +NPNR_PACKED_STRUCT(struct Wire_bel_POD { + int32_t wire_xy; + int32_t wire_name; + int32_t bel_x; + int32_t bel_y; + int32_t bel_z; + int32_t side; +}); + NPNR_PACKED_STRUCT(struct Extra_chip_data_POD { int32_t chip_flags; Bottom_io_POD bottom_io; RelSlice diff_io_types; RelSlice dqce_bels; RelSlice dcs_bels; + RelSlice dhcen_bels; // chip flags static constexpr int32_t HAS_SP32 = 1; static constexpr int32_t NEED_SP_FIX = 2; @@ -154,8 +164,9 @@ enum VSS_Z = 278, BANDGAP_Z = 279, - DQCE_Z = 280, // : 286 reserve for 6 DQCEs - DCS_Z = 286, // : 287 reserve for 2 DCSs + DQCE_Z = 280, // : 286 reserve for 6 DQCEs + DCS_Z = 286, // : 287 reserve for 2 DCSs + DHCEN_Z = 288, // : 298 // The two least significant bits encode Z for 9-bit adders and // multipliers, if they are equal to 0, then we get Z of their common diff --git a/himbaechel/uarch/gowin/gowin_arch_gen.py b/himbaechel/uarch/gowin/gowin_arch_gen.py index 54d2e979..b4b64c26 100644 --- a/himbaechel/uarch/gowin/gowin_arch_gen.py +++ b/himbaechel/uarch/gowin/gowin_arch_gen.py @@ -51,6 +51,7 @@ BANDGAP_Z = 279 DQCE_Z = 280 # : 286 reserve for 6 DQCEs DCS_Z = 286 # : 287 reserve for 2 DCSs +DHCEN_Z = 288 # : 298 DSP_Z = 509 @@ -165,6 +166,26 @@ class SpineBel(BBAStruct): bba.u32(self.bel_y) bba.u32(self.bel_z) +# wire -> bel for DHCEN bels +@dataclass +class WireBel(BBAStruct): + wire_xy: IdString + wire_name: IdString + bel_x: int + bel_y: int + bel_z: int + hclk_side: IdString + + def serialise_lists(self, context: str, bba: BBAWriter): + pass + def serialise(self, context: str, bba: BBAWriter): + bba.u32(self.wire_xy.index) + bba.u32(self.wire_name.index) + bba.u32(self.bel_x) + bba.u32(self.bel_y) + bba.u32(self.bel_z) + bba.u32(self.hclk_side.index) + @dataclass class ChipExtraData(BBAStruct): strs: StringPool @@ -173,6 +194,7 @@ class ChipExtraData(BBAStruct): diff_io_types: list[IdString] = field(default_factory = list) dqce_bels: list[SpineBel] = field(default_factory = list) dcs_bels: list[SpineBel] = field(default_factory = list) + dhcen_bels: list[WireBel] = field(default_factory = list) def create_bottom_io(self): self.bottom_io = BottomIO() @@ -183,6 +205,9 @@ class ChipExtraData(BBAStruct): def add_diff_io_type(self, diff_type: str): self.diff_io_types.append(self.strs.id(diff_type)) + def add_dhcen_bel(self, wire_xy: str, wire_name: str, x: int, y: int, z: int, side: str): + self.dhcen_bels.append(WireBel(self.strs.id(wire_xy), self.strs.id(wire_name), x, y, z, self.strs.id(side))) + def add_dqce_bel(self, spine: str, x: int, y: int, z: int): self.dqce_bels.append(SpineBel(self.strs.id(spine), x, y, z)) @@ -200,6 +225,9 @@ class ChipExtraData(BBAStruct): bba.label(f"{context}_dcs_bels") for i, t in enumerate(self.dcs_bels): t.serialise(f"{context}_dcs_bel{i}", bba) + bba.label(f"{context}_dhcen_bels") + for i, t in enumerate(self.dhcen_bels): + t.serialise(f"{context}_dhcen_bel{i}", bba) def serialise(self, context: str, bba: BBAWriter): bba.u32(self.flags) @@ -207,6 +235,7 @@ class ChipExtraData(BBAStruct): bba.slice(f"{context}_diff_io_types", len(self.diff_io_types)) bba.slice(f"{context}_dqce_bels", len(self.dqce_bels)) bba.slice(f"{context}_dcs_bels", len(self.dcs_bels)) + bba.slice(f"{context}_dhcen_bels", len(self.dhcen_bels)) @dataclass class PadExtraData(BBAStruct): @@ -368,7 +397,7 @@ def create_hclk_switch_matrix(tt: TileType, db: chipdb, x: int, y: int): if not tt.has_wire(src): tt.create_wire(src, "HCLK") tt.create_pip(src, dst) - + hclk_bel_zs = { "CLKDIV2_HCLK0_SECT0": CLKDIV2_0_Z, "CLKDIV2_HCLK0_SECT1": CLKDIV2_1_Z, @@ -379,7 +408,7 @@ def create_hclk_switch_matrix(tt: TileType, db: chipdb, x: int, y: int): "CLKDIV_HCLK1_SECT0": CLKDIV_2_Z, "CLKDIV_HCLK1_SECT1": CLKDIV_3_Z } - + for bel_name, bel_props in db.grid[y][x].bels.items(): if (bel_name not in hclk_bel_zs): continue @@ -391,7 +420,7 @@ def create_hclk_switch_matrix(tt: TileType, db: chipdb, x: int, y: int): bel_type = "CLKDIV" this_bel = tt.create_bel(bel_name, bel_type, hclk_bel_zs[bel_name]) - if (bel_name in ["CLKDIV_HCLK0_SECT1", "CLKDIV_HCLK1_SECT1"]): + if (bel_name in ["CLKDIV_HCLK0_SECT1", "CLKDIV_HCLK1_SECT1"]): this_bel.flags |= BEL_FLAG_HIDDEN if bel_type=="CLKDIV": this_bel.flags |= BEL_FLAG_GLOBAL @@ -415,6 +444,9 @@ dqce_bels = {} # map spine -> dcs bel dcs_bels = {} +# map HCLKIN wire -> dhcen bel +dhcen_bels = {} + def create_extra_funcs(tt: TileType, db: chipdb, x: int, y: int): if (y, x) not in db.extra_func: return @@ -443,6 +475,16 @@ def create_extra_funcs(tt: TileType, db: chipdb, x: int, y: int): tt.create_wire(wire) bel = tt.create_bel("BANDGAP", "BANDGAP", z = BANDGAP_Z) tt.add_bel_pin(bel, "BGEN", wire, PinType.INPUT) + elif func == 'dhcen': + for idx, dhcen in enumerate(desc): + wire = dhcen['ce'] + if not tt.has_wire(wire): + tt.create_wire(wire) + bel_z = DHCEN_Z + idx + bel = tt.create_bel(f"DHCEN{idx}", "DHCEN", z = bel_z) + tt.add_bel_pin(bel, "CE", wire, PinType.INPUT) + wire_xy, wire_name, side = dhcen['wire'] + dhcen_bels[wire_xy, wire_name] = (x, y, bel_z, side) elif func == 'dqce': for idx in range(6): bel_z = DQCE_Z + idx @@ -1144,6 +1186,9 @@ def create_extra_data(chip: Chip, db: chipdb, chip_flags: int): 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) + # create hclk wire->dhcen bel map + for wire, bel in dhcen_bels.items(): + chip.extra_data.add_dhcen_bel(wire[0], wire[1], bel[0], bel[1], bel[2], bel[3]) # create spine->dqce bel map for spine, bel in dqce_bels.items(): chip.extra_data.add_dqce_bel(spine, bel[0], bel[1], bel[2]) diff --git a/himbaechel/uarch/gowin/gowin_utils.cc b/himbaechel/uarch/gowin/gowin_utils.cc index 0d6fb8b8..2d6b54da 100644 --- a/himbaechel/uarch/gowin/gowin_utils.cc +++ b/himbaechel/uarch/gowin/gowin_utils.cc @@ -87,6 +87,21 @@ BelId GowinUtils::get_dcs_bel(IdString spine_name) return BelId(); } +BelId GowinUtils::get_dhcen_bel(WireId hclkin_wire, IdString &side) +{ + const Extra_chip_data_POD *extra = reinterpret_cast(ctx->chip_info->extra_data.get()); + for (auto &wire_bel : extra->dhcen_bels) { + IdStringList wire_name = IdStringList::concat(IdString(wire_bel.wire_xy), IdString(wire_bel.wire_name)); + WireId wire = normalize_wire(ctx->getWireByName(wire_name)); + + if (wire == hclkin_wire) { + side = IdString(wire_bel.side); + return ctx->getBelByLocation(Loc(wire_bel.bel_x, wire_bel.bel_y, wire_bel.bel_z)); + } + } + return BelId(); +} + bool GowinUtils::is_simple_io_bel(BelId bel) { return chip_bel_info(ctx->chip_info, bel).flags & BelFlags::FLAG_SIMPLE_IO; diff --git a/himbaechel/uarch/gowin/gowin_utils.h b/himbaechel/uarch/gowin/gowin_utils.h index 77b75082..e72bf2c2 100644 --- a/himbaechel/uarch/gowin/gowin_utils.h +++ b/himbaechel/uarch/gowin/gowin_utils.h @@ -35,6 +35,10 @@ struct GowinUtils BelId get_io_bel_from_iologic(BelId bel); BelId get_dqce_bel(IdString spine_name); BelId get_dcs_bel(IdString spine_name); + BelId get_dhcen_bel(WireId hclkin_wire, IdString &side); + + // Wires + WireId normalize_wire(WireId wire) { return ctx->normalise_wire(wire.tile, wire.index); } // BSRAM bool has_SP32(void); diff --git a/himbaechel/uarch/gowin/pack.cc b/himbaechel/uarch/gowin/pack.cc index ec1ef8f1..58499d8f 100644 --- a/himbaechel/uarch/gowin/pack.cc +++ b/himbaechel/uarch/gowin/pack.cc @@ -3072,6 +3072,32 @@ struct GowinPacker } } + // ========================================= + // Create DHCENs + // ========================================= + void pack_dhcens() + { + // Allocate all available dhcen bels; we will find out which of them + // will actually be used during the routing process. + bool grab_bels = false; + for (auto &cell : ctx->cells) { + auto &ci = *cell.second; + if (ci.type == id_DHCEN) { + ci.pseudo_cell = std::make_unique(Loc(0, 0, 0)); + grab_bels = true; + } + } + if (grab_bels) { + int i = 0; + for (auto &bel : ctx->getBelsInBucket(ctx->getBelBucketForCellType(id_DHCEN))) { + IdString dhcen_name = ctx->idf("$PACKER_DHCEN_%d", ++i); + CellInfo *dhcen = ctx->createCell(dhcen_name, id_DHCEN); + dhcen->addInput(id_CE); + ctx->bindBel(bel, dhcen, STRENGTH_LOCKED); + } + } + } + void run(void) { handle_constants(); @@ -3124,6 +3150,9 @@ struct GowinPacker pack_buffered_nets(); ctx->check(); + pack_dhcens(); + ctx->check(); + pack_dqce(); ctx->check();