diff --git a/himbaechel/uarch/gowin/constids.inc b/himbaechel/uarch/gowin/constids.inc index c597a8cf..5aeac748 100644 --- a/himbaechel/uarch/gowin/constids.inc +++ b/himbaechel/uarch/gowin/constids.inc @@ -1197,7 +1197,13 @@ X(HCLK_OUT2) X(HCLK_OUT3) // BUFG, clock buffers stuff +X(CLKSEL) X(BUFG) X(CLOCK) - +X(DQCE) +X(DCS) +X(DCS_MODE) +X(DQCE_PIP) +X(DCS_USED) +X(SELFORCE) diff --git a/himbaechel/uarch/gowin/globals.cc b/himbaechel/uarch/gowin/globals.cc index 2504f8cc..011e2f37 100644 --- a/himbaechel/uarch/gowin/globals.cc +++ b/himbaechel/uarch/gowin/globals.cc @@ -49,9 +49,41 @@ struct GowinGlobalRouter return !wire_type.in(id_GLOBAL_CLK, id_IO_O, id_IO_I, id_PLL_O, id_PLL_I, id_TILE_CLK); }; - IdString src_type = ctx->getWireType(ctx->getPipSrcWire(pip)); + WireId src, dst; + src = ctx->getPipSrcWire(pip); + dst = ctx->getPipDstWire(pip); + IdString dst_name = ctx->getWireName(dst)[1]; + bool not_dsc_pip = dst_name != id_CLKOUT; + IdString src_type = ctx->getWireType(src); + IdString dst_type = ctx->getWireType(dst); + bool src_valid = not_dsc_pip && src_type.in(id_GLOBAL_CLK, id_IO_O, id_PLL_O, id_HCLK); + bool dst_valid = not_dsc_pip && dst_type.in(id_GLOBAL_CLK, id_TILE_CLK, id_PLL_I, id_IO_I, id_HCLK); + + bool res = (src_valid && dst_valid) || (src_valid && is_local(dst_type)) || (is_local(src_type) && dst_valid); + if (ctx->debug && false /*&& res*/) { + 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 res; + } + + bool global_DCS_pip_filter(PipId pip) const + { + auto is_local = [&](IdString wire_type) { + return !wire_type.in(id_GLOBAL_CLK, id_IO_O, id_IO_I, id_PLL_O, id_PLL_I, id_TILE_CLK); + }; + + WireId src = ctx->getPipSrcWire(pip); + IdString src_type = ctx->getWireType(src); + IdString src_name = ctx->getWireName(src)[1]; + bool src_is_spine = src_name.str(ctx).rfind("SPINE", 0) == 0; IdString dst_type = ctx->getWireType(ctx->getPipDstWire(pip)); - bool src_valid = src_type.in(id_GLOBAL_CLK, id_IO_O, id_PLL_O, id_HCLK); + bool src_valid = ((!src_is_spine) && src_type.in(id_GLOBAL_CLK, id_IO_O, id_PLL_O, id_HCLK)) || + src_name.in(id_SPINE6, id_SPINE7, id_SPINE14, id_SPINE15, id_SPINE22, id_SPINE23, id_SPINE30, + id_SPINE31); bool dst_valid = dst_type.in(id_GLOBAL_CLK, id_TILE_CLK, id_PLL_I, id_IO_I, id_HCLK); bool res = (src_valid && dst_valid) || (src_valid && is_local(dst_type)) || (is_local(src_type) && dst_valid); @@ -71,7 +103,6 @@ struct GowinGlobalRouter template bool backwards_bfs_route(NetInfo *net, WireId src, WireId dst, int iter_limit, bool strict, Tfilt pip_filter) { - // log_info("route arc %s:%s->%s\n", net->name.c_str(ctx), ctx->nameOfWire(src), ctx->nameOfWire(dst)); // Queue of wires to visit std::queue visit; // Wire -> upstream pip @@ -134,14 +165,12 @@ struct GowinGlobalRouter } pips.push_back(pip); cursor = ctx->getPipDstWire(pip); - // log_info(">> %s:%s\n", ctx->getPipName(pip).str(ctx).c_str(), ctx->nameOfWire(cursor)); } // Reverse that list std::reverse(pips.begin(), pips.end()); // Bind pips until we hit already-bound routing for (PipId pip : pips) { WireId dst = ctx->getPipDstWire(pip); - // log_info("bind pip %s:%s\n", ctx->getPipName(pip).str(ctx).c_str(), ctx->nameOfWire(dst)); if (ctx->getBoundWireNet(dst) == net) { break; } @@ -160,104 +189,9 @@ struct GowinGlobalRouter } } - enum RouteResult - { - NOT_ROUTED = 0, - ROUTED_PARTIALLY, - ROUTED_ALL - }; - - RouteResult route_direct_net(NetInfo *net) - { - // Lookup source and destination wires - WireId src = ctx->getNetinfoSourceWire(net); - if (src == WireId()) - log_error("Net '%s' has an invalid source port %s.%s\n", ctx->nameOf(net), ctx->nameOf(net->driver.cell), - ctx->nameOf(net->driver.port)); - - if (ctx->getBoundWireNet(src) != net) { - ctx->bindWire(src, net, STRENGTH_LOCKED); - } - - RouteResult routed = NOT_ROUTED; - for (auto usr : net->users.enumerate()) { - WireId dst = ctx->getNetinfoSinkWire(net, net->users.at(usr.index), 0); - if (dst == WireId()) { - log_error("Net '%s' has an invalid sink port %s.%s\n", ctx->nameOf(net), - ctx->nameOf(net->users.at(usr.index).cell), ctx->nameOf(net->users.at(usr.index).port)); - } - if (backwards_bfs_route(net, src, dst, 1000000, false, [&](PipId pip) { - return (is_relaxed_sink(usr.value) || global_pip_filter(pip)); - })) { - routed = routed == ROUTED_PARTIALLY ? routed : ROUTED_ALL; - } else { - routed = routed == NOT_ROUTED ? routed : ROUTED_PARTIALLY; - } - } - if (routed == NOT_ROUTED) { - ctx->unbindWire(src); - } - return routed; - } - - void route_buffered_net(NetInfo *net) - { - // a) route net after buf using the buf input as source - CellInfo *buf_ci = net->driver.cell; - WireId src = ctx->getBelPinWire(buf_ci->bel, id_I); - - NetInfo *net_before_buf = buf_ci->getPort(id_I); - NPNR_ASSERT(net_before_buf != nullptr); - - if (src == WireId()) { - log_error("Net '%s' has an invalid source port %s.%s\n", ctx->nameOf(net), ctx->nameOf(net->driver.cell), - ctx->nameOf(net->driver.port)); - } - ctx->bindWire(src, net, STRENGTH_LOCKED); - - RouteResult routed = NOT_ROUTED; - for (auto usr : net->users.enumerate()) { - WireId dst = ctx->getNetinfoSinkWire(net, net->users.at(usr.index), 0); - if (dst == WireId()) { - log_error("Net '%s' has an invalid sink port %s.%s\n", ctx->nameOf(net), - ctx->nameOf(net->users.at(usr.index).cell), ctx->nameOf(net->users.at(usr.index).port)); - } - // log_info(" usr wire: %s\n", ctx->nameOfWire(dst)); - if (backwards_bfs_route(net, src, dst, 1000000, false, [&](PipId pip) { - return (is_relaxed_sink(usr.value) || global_pip_filter(pip)); - })) { - routed = routed == ROUTED_PARTIALLY ? routed : ROUTED_ALL; - } else { - routed = routed == NOT_ROUTED ? routed : ROUTED_PARTIALLY; - } - } - if (routed == NOT_ROUTED) { - ctx->unbindWire(src); - } - - // b) route net before buf from whatever to the buf input - WireId dst = src; - CellInfo *true_src_ci = net_before_buf->driver.cell; - src = ctx->getBelPinWire(true_src_ci->bel, net_before_buf->driver.port); - ctx->bindWire(src, net, STRENGTH_LOCKED); - ctx->unbindWire(dst); - backwards_bfs_route(net, src, dst, 1000000, false, [&](PipId pip) { return true; }); - // remove net - buf_ci->movePortTo(id_O, true_src_ci, net_before_buf->driver.port); - net_before_buf->driver.cell = nullptr; - } - - void route_clk_net(NetInfo *net) - { - RouteResult res = route_direct_net(net); - if (res) { - log_info(" routed net '%s' using global resources %s.\n", ctx->nameOf(net), - res == ROUTED_ALL ? "only" : "partially"); - } - } - 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_clksrc(const PortRef &driver) { // dedicated pins @@ -292,12 +226,261 @@ struct GowinGlobalRouter return false; } + enum RouteResult + { + NOT_ROUTED = 0, + ROUTED_PARTIALLY, + ROUTED_ALL + }; + + RouteResult route_direct_net(NetInfo *net, WireId aux_src = WireId(), bool DCS_pip_only = false) + { + WireId src; + src = aux_src == WireId() ? ctx->getNetinfoSourceWire(net) : aux_src; + if (src == WireId()) { + log_error("Net '%s' has an invalid source port %s.%s\n", ctx->nameOf(net), ctx->nameOf(net->driver.cell), + ctx->nameOf(net->driver.port)); + } + + if (aux_src == WireId() && ctx->getBoundWireNet(src) != net) { + ctx->bindWire(src, net, STRENGTH_LOCKED); + } + + RouteResult routed = NOT_ROUTED; + for (auto usr : net->users) { + WireId dst = ctx->getNetinfoSinkWire(net, usr, 0); + if (dst == WireId()) { + log_error("Net '%s' has an invalid sink port %s.%s\n", ctx->nameOf(net), ctx->nameOf(usr.cell), + ctx->nameOf(usr.port)); + } + bool bfs_res; + if (DCS_pip_only) { + bfs_res = backwards_bfs_route(net, src, dst, 1000000, false, [&](PipId pip) { + return (is_relaxed_sink(usr) || global_DCS_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)); + }); + } + if (bfs_res) { + routed = routed == ROUTED_PARTIALLY ? routed : ROUTED_ALL; + } else { + routed = routed == NOT_ROUTED ? routed : ROUTED_PARTIALLY; + } + } + if (routed == NOT_ROUTED) { + if (aux_src == WireId()) { + ctx->unbindWire(src); + } + } + return routed; + } + + void route_dqce_net(NetInfo *net) + { + // route net after dqce using source of CLKIN net + CellInfo *dqce_ci = net->driver.cell; + + NetInfo *net_before_dqce = dqce_ci->getPort(id_CLKIN); + NPNR_ASSERT(net_before_dqce != nullptr); + + PortRef driver = net_before_dqce->driver; + NPNR_ASSERT_MSG(driver_is_buf(driver) || driver_is_clksrc(driver), + stringf("The input source for %s is not a clock.", ctx->nameOf(dqce_ci)).c_str()); + WireId src; + // use BUF input if there is one + if (driver_is_buf(driver)) { + src = ctx->getBelPinWire(driver.cell->bel, id_I); + } else { + src = ctx->getBelPinWire(driver.cell->bel, driver.port); + } + + RouteResult route_result = route_direct_net(net, src); + 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 DQCE managed networks.\n", + ctx->nameOf(net)); + } + + // In networks controlled by DQCE, the source can only connect to the + // "spine" wires. Here we not only check this fact, but also find out + // how many and what kind of "spine" wires were used for network + // roaming. + for (PipId pip : ctx->getPipsDownhill(src)) { + if (ctx->getBoundPipNet(pip) == nullptr) { + continue; + } + WireId dst = ctx->getPipDstWire(pip); + + BelId dqce_bel = gwu.get_dqce_bel(ctx->getWireName(dst)[1]); + NPNR_ASSERT(dqce_bel != BelId()); + + // One pseudo DQCE (either logical or custom, whatever you like) + // can be implemented as several hardware dqce - this is because + // each hardware dqce can control only one "spine", that is, a bus + // within one quadrant. Here we find suitable hardware dqces. + CellInfo *hw_dqce = ctx->getBoundBelCell(dqce_bel); + if (ctx->debug) { + log_info(" use %s spine and %s bel for '%s' hw cell.\n", ctx->nameOfWire(dst), + ctx->nameOfBel(dqce_bel), ctx->nameOf(hw_dqce)); + } + + hw_dqce->setAttr(id_DQCE_PIP, Property(ctx->getPipName(pip).str(ctx))); + ctx->unbindPip(pip); + ctx->bindWire(dst, net, STRENGTH_LOCKED); + + // The control network must connect the CE inputs of all hardware dqces. + dqce_ci->copyPortTo(id_CE, hw_dqce, id_CE); + } + net->driver.cell->disconnectPort(net->driver.port); + + // remove the virtual DQCE + dqce_ci->disconnectPort(id_CLKIN); + dqce_ci->disconnectPort(id_CE); + ctx->cells.erase(dqce_ci->name); + } + + void route_dcs_net(NetInfo *net) + { + // Since CLKOUT is responsible for only one quadrant, we will do + // routing not from it, but from any CLK0-3 input actually connected to + // the clock source. + CellInfo *dcs_ci = net->driver.cell; + NetInfo *net_before_dcs; + PortRef driver; + for (int i = 0; i < 4; ++i) { + net_before_dcs = dcs_ci->getPort(ctx->idf("CLK%d", i)); + if (net_before_dcs == nullptr) { + continue; + } + driver = net_before_dcs->driver; + if (driver_is_buf(driver) || driver_is_clksrc(driver)) { + break; + } + net_before_dcs = nullptr; + } + NPNR_ASSERT_MSG(net_before_dcs != nullptr, stringf("No clock inputs for %s.", ctx->nameOf(dcs_ci)).c_str()); + + WireId src; + // use BUF input if there is one + if (driver_is_buf(driver)) { + src = ctx->getBelPinWire(driver.cell->bel, id_I); + } else { + src = ctx->getBelPinWire(driver.cell->bel, driver.port); + } + + RouteResult route_result = route_direct_net(net, src, true); + 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 DCS managed networks.\n", + ctx->nameOf(net)); + } + + // In networks controlled by DCS, the source can only connect to the + // "spine" wires. Here we not only check this fact, but also find out + // how many and what kind of "spine" wires were used for network + // roaming. + for (PipId pip : ctx->getPipsDownhill(src)) { + if (ctx->getBoundPipNet(pip) == nullptr) { + continue; + } + WireId dst = ctx->getPipDstWire(pip); + BelId dcs_bel = gwu.get_dcs_bel(ctx->getWireName(dst)[1]); + NPNR_ASSERT(dcs_bel != BelId()); + + // One pseudo DCS (either logical or custom, whatever you like) + // can be implemented as several hardware dcs - this is because + // each hardware dcs can control only one "spine", that is, a bus + // within one quadrant. Here we find suitable hardware dcses. + CellInfo *hw_dcs = ctx->getBoundBelCell(dcs_bel); + if (ctx->debug) { + log_info(" use %s spine and %s bel for '%s' hw cell.\n", ctx->nameOfWire(dst), ctx->nameOfBel(dcs_bel), + ctx->nameOf(hw_dcs)); + } + if (dcs_ci->attrs.count(id_DCS_MODE) != 0) { + hw_dcs->setAttr(id_DCS_MODE, dcs_ci->attrs.at(id_DCS_MODE)); + } else { + hw_dcs->setAttr(id_DCS_MODE, Property("RISING")); + } + + // Need to release the fake internal DCS PIP which is the only + // downhill pip for DCS inputs + PipId fake_pip = *ctx->getPipsDownhill(dst).begin(); + WireId clkout_wire = ctx->getPipDstWire(fake_pip); + if (ctx->debug) { + log_info("fake pip:%s, CLKOUT src:%s\n", ctx->nameOfPip(fake_pip), ctx->nameOfWire(clkout_wire)); + } + ctx->unbindPip(fake_pip); + ctx->bindWire(clkout_wire, net, STRENGTH_LOCKED); + ctx->unbindWire(dst); + + // The input networks must bs same for all hardware dcs. + dcs_ci->copyPortTo(id_SELFORCE, hw_dcs, id_SELFORCE); + dcs_ci->copyPortBusTo(id_CLK, 0, false, hw_dcs, id_CLK, 0, false, 4); + dcs_ci->copyPortBusTo(id_CLKSEL, 0, true, hw_dcs, id_CLKSEL, 0, false, 4); + } + + // remove the virtual DCS + dcs_ci->disconnectPort(id_SELFORCE); + dcs_ci->disconnectPort(id_CLKOUT); + for (int i = 0; i < 4; ++i) { + dcs_ci->disconnectPort(ctx->idf("CLKSEL[%d]", i)); + dcs_ci->disconnectPort(ctx->idf("CLK%d", i)); + } + log_info(" '%s' net was routed.\n", ctx->nameOf(net)); + ctx->cells.erase(dcs_ci->name); + } + + void route_buffered_net(NetInfo *net) + { + // a) route net after buf using the buf input as source + CellInfo *buf_ci = net->driver.cell; + WireId src = ctx->getBelPinWire(buf_ci->bel, id_I); + + NetInfo *net_before_buf = buf_ci->getPort(id_I); + NPNR_ASSERT(net_before_buf != nullptr); + + RouteResult route_result = route_direct_net(net, 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)); + } + + // b) route net before buf from whatever to the buf input + WireId dst = src; + CellInfo *true_src_ci = net_before_buf->driver.cell; + src = ctx->getBelPinWire(true_src_ci->bel, net_before_buf->driver.port); + ctx->bindWire(src, net, STRENGTH_LOCKED); + backwards_bfs_route(net, src, dst, 1000000, false, [&](PipId pip) { return true; }); + // remove net + buf_ci->movePortTo(id_O, true_src_ci, net_before_buf->driver.port); + net_before_buf->driver.cell = nullptr; + + log_info(" '%s' net was routed.\n", ctx->nameOf(net)); + } + + void route_clk_net(NetInfo *net) + { + RouteResult route_result = route_direct_net(net); + 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"); + } + } + void run(void) { log_info("Routing globals...\n"); - std::vector routed_nets; - // buffered nets first + std::vector dqce_nets, dcs_nets, buf_nets, clk_nets; + + // Determining the priority of network routing for (auto &net : ctx->nets) { NetInfo *ni = net.second.get(); CellInfo *drv = ni->driver.cell; @@ -308,22 +491,43 @@ struct GowinGlobalRouter continue; } if (driver_is_buf(ni->driver)) { - if (ctx->verbose) { - log_info("route buffered net '%s'\n", ctx->nameOf(ni)); + buf_nets.push_back(net.first); + } else { + if (driver_is_clksrc(ni->driver)) { + clk_nets.push_back(net.first); + } else { + if (driver_is_dqce(ni->driver)) { + dqce_nets.push_back(net.first); + } else { + if (driver_is_dcs(ni->driver)) { + dcs_nets.push_back(net.first); + } + } } - route_buffered_net(ni); - routed_nets.push_back(net.first); - continue; } } - for (auto &net : ctx->nets) { - if (std::find(routed_nets.begin(), routed_nets.end(), net.first) != routed_nets.end()) { - if (ctx->debug) { - log_info("skip already routed net:%s\n", net.first.c_str(ctx)); - } - continue; + + // nets with DQCE + for (IdString net_name : dqce_nets) { + NetInfo *ni = ctx->nets.at(net_name).get(); + if (ctx->verbose) { + log_info("route dqce net '%s'\n", ctx->nameOf(ni)); } - NetInfo *ni = net.second.get(); + route_dqce_net(ni); + } + + // nets with DCS + for (IdString net_name : dcs_nets) { + NetInfo *ni = ctx->nets.at(net_name).get(); + if (ctx->verbose) { + log_info("route dcs net '%s'\n", ctx->nameOf(ni)); + } + route_dcs_net(ni); + } + + // buffered nets + for (IdString net_name : buf_nets) { + NetInfo *ni = ctx->nets.at(net_name).get(); CellInfo *drv = ni->driver.cell; if (drv == nullptr || ni->users.empty()) { if (ctx->debug) { @@ -331,13 +535,26 @@ struct GowinGlobalRouter } continue; } - if (driver_is_clksrc(ni->driver)) { - if (ctx->verbose) { - log_info("route clock net '%s'\n", ctx->nameOf(ni)); + if (ctx->verbose) { + log_info("route buffered net '%s'\n", ctx->nameOf(ni)); + } + route_buffered_net(ni); + } + + // clock nets + for (IdString net_name : clk_nets) { + NetInfo *ni = ctx->nets.at(net_name).get(); + CellInfo *drv = ni->driver.cell; + if (drv == nullptr || ni->users.empty()) { + if (ctx->debug) { + log_info("skip empty or driverless net:%s\n", ctx->nameOf(ni)); } - route_clk_net(ni); continue; } + if (ctx->verbose) { + log_info("route clock net '%s'\n", ctx->nameOf(ni)); + } + route_clk_net(ni); } } }; diff --git a/himbaechel/uarch/gowin/gowin.cc b/himbaechel/uarch/gowin/gowin.cc index 8942154b..7ae314de 100644 --- a/himbaechel/uarch/gowin/gowin.cc +++ b/himbaechel/uarch/gowin/gowin.cc @@ -186,7 +186,10 @@ void GowinImpl::init(Context *ctx) } // We do not allow the use of global wires that bypass a special router. -bool GowinImpl::checkPipAvail(PipId pip) const { return !gwu.is_global_pip(pip); } +bool GowinImpl::checkPipAvail(PipId pip) const +{ + return (ctx->getWireConstantValue(ctx->getPipSrcWire(pip)) != IdString()) || (!gwu.is_global_pip(pip)); +} void GowinImpl::pack() { diff --git a/himbaechel/uarch/gowin/gowin.h b/himbaechel/uarch/gowin/gowin.h index b2fc5f80..a9f247d8 100644 --- a/himbaechel/uarch/gowin/gowin.h +++ b/himbaechel/uarch/gowin/gowin.h @@ -90,10 +90,19 @@ NPNR_PACKED_STRUCT(struct Bottom_io_POD { RelSlice conditions; }); +NPNR_PACKED_STRUCT(struct Spine_bel_POD { + int32_t spine; + int32_t bel_x; + int32_t bel_y; + int32_t bel_z; +}); + 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; // chip flags static constexpr int32_t HAS_SP32 = 1; static constexpr int32_t NEED_SP_FIX = 2; @@ -134,6 +143,9 @@ enum VSS_Z = 278, BANDGAP_Z = 279, + DQCE_Z = 280, // : 286 reserve for 6 DQCEs + DCS_Z = 286, // : 287 reserve for 2 DCSs + // 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 // 18-bit equivalent. diff --git a/himbaechel/uarch/gowin/gowin_arch_gen.py b/himbaechel/uarch/gowin/gowin_arch_gen.py index a6cffd35..75fda033 100644 --- a/himbaechel/uarch/gowin/gowin_arch_gen.py +++ b/himbaechel/uarch/gowin/gowin_arch_gen.py @@ -49,6 +49,9 @@ VCC_Z = 277 GND_Z = 278 BANDGAP_Z = 279 +DQCE_Z = 280 # : 286 reserve for 6 DQCEs +DCS_Z = 286 # : 287 reserve for 2 DCSs + DSP_Z = 509 DSP_0_Z = 511 # DSP macro 0 @@ -135,12 +138,30 @@ class BottomIO(BBAStruct): def serialise(self, context: str, bba: BBAWriter): bba.slice(f"{context}_conditions", len(self.conditions)) +# spine -> bel for different bels +@dataclass +class SpineBel(BBAStruct): + spine: IdString + bel_x: int + bel_y: int + bel_z: int + + def serialise_lists(self, context: str, bba: BBAWriter): + pass + def serialise(self, context: str, bba: BBAWriter): + bba.u32(self.spine.index) + bba.u32(self.bel_x) + bba.u32(self.bel_y) + bba.u32(self.bel_z) + @dataclass class ChipExtraData(BBAStruct): strs: StringPool flags: int bottom_io: BottomIO 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) def create_bottom_io(self): self.bottom_io = BottomIO() @@ -151,16 +172,30 @@ class ChipExtraData(BBAStruct): def add_diff_io_type(self, diff_type: str): self.diff_io_types.append(self.strs.id(diff_type)) + 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)) + + def add_dcs_bel(self, spine: str, x: int, y: int, z: int): + self.dcs_bels.append(SpineBel(self.strs.id(spine), x, y, z)) + 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) + bba.label(f"{context}_dqce_bels") + for i, t in enumerate(self.dqce_bels): + t.serialise(f"{context}_dqce_bel{i}", bba) + bba.label(f"{context}_dcs_bels") + for i, t in enumerate(self.dcs_bels): + t.serialise(f"{context}_dcs_bel{i}", bba) def serialise(self, context: str, bba: BBAWriter): bba.u32(self.flags) self.bottom_io.serialise(f"{context}_bottom_io", bba) 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)) @dataclass class PadExtraData(BBAStruct): @@ -296,7 +331,10 @@ def create_switch_matrix(tt: TileType, db: chipdb, x: int, y: int): tt.create_wire(dst, get_wire_type(dst)) for src in srcs.keys(): if not tt.has_wire(src): - tt.create_wire(src, get_wire_type(src)) + if src in {"VSS", "VCC"}: + tt.create_wire(src, get_wire_type(src), const_value = src) + else: + tt.create_wire(src, get_wire_type(src)) tt.create_pip(src, dst) # clock wires @@ -320,6 +358,11 @@ def create_hclk_switch_matrix(tt: TileType, db: chipdb, x: int, y: int): tt.create_wire(src, "HCLK") tt.create_pip(src, dst) +# map spine -> dqce bel +dqce_bels = {} +# map spine -> dcs bel +dcs_bels = {} + def create_extra_funcs(tt: TileType, db: chipdb, x: int, y: int): if (y, x) not in db.extra_func: return @@ -328,7 +371,8 @@ def create_extra_funcs(tt: TileType, db: chipdb, x: int, y: int): osc_type = desc['type'] portmap = db.grid[y][x].bels[osc_type].portmap for port, wire in portmap.items(): - tt.create_wire(wire, port) + if not tt.has_wire(wire): + tt.create_wire(wire, port) bel = tt.create_bel(osc_type, osc_type, z = OSC_Z) for port, wire in portmap.items(): if 'OUT' in port: @@ -337,15 +381,58 @@ def create_extra_funcs(tt: TileType, db: chipdb, x: int, y: int): tt.add_bel_pin(bel, port, wire, PinType.INPUT) elif func == 'gsr': wire = desc['wire'] - tt.create_wire(wire, "GSRI") + if not tt.has_wire(wire): + tt.create_wire(wire) bel = tt.create_bel("GSR", "GSR", z = GSR_Z) tt.add_bel_pin(bel, "GSRI", wire, PinType.INPUT) elif func == 'bandgap': wire = desc['wire'] - tt.create_wire(wire, "BGEN") + if not tt.has_wire(wire): + tt.create_wire(wire) bel = tt.create_bel("BANDGAP", "BANDGAP", z = BANDGAP_Z) tt.add_bel_pin(bel, "BGEN", wire, PinType.INPUT) - if func == 'io16': + elif func == 'dqce': + for idx in range(6): + bel_z = DQCE_Z + idx + bel = tt.create_bel(f"DQCE{idx}", "DQCE", bel_z) + wire = desc[idx]['clkin'] + dqce_bels[wire] = (x, y, bel_z) + if not tt.has_wire(wire): + tt.create_wire(wire, "GLOBAL_CLK") + tt.add_bel_pin(bel, "CLKIN", wire, PinType.INPUT) + tt.add_bel_pin(bel, "CLKOUT", wire, PinType.OUTPUT) + wire = desc[idx]['ce'] + if not tt.has_wire(wire): + tt.create_wire(wire) + tt.add_bel_pin(bel, "CE", wire, PinType.INPUT) + elif func == 'dcs': + for idx in range(2): + if idx not in desc: + continue + bel_z = DCS_Z + idx + bel = tt.create_bel(f"DCS{idx}", "DCS", bel_z) + wire = desc[idx]['clkout'] + if not tt.has_wire(wire): + tt.create_wire(wire) + tt.add_bel_pin(bel, "CLKOUT", wire, PinType.OUTPUT) + clkout_wire = wire + for clk_idx, wire in enumerate(desc[idx]['clk']): + if not tt.has_wire(wire): + tt.create_wire(wire, "GLOBAL_CLK") + tt.add_bel_pin(bel, f"CLK{clk_idx}", wire, PinType.INPUT) + # This is a fake PIP that allows routing “through” this + # primitive from the CLK input to the CLKOUT output. + tt.create_pip(wire, clkout_wire) + dcs_bels[wire] = (x, y, bel_z) + for i, wire in enumerate(desc[idx]['clksel']): + if not tt.has_wire(wire): + tt.create_wire(wire) + tt.add_bel_pin(bel, f"CLKSEL{i}", wire, PinType.INPUT) + wire = desc[idx]['selforce'] + if not tt.has_wire(wire): + tt.create_wire(wire) + tt.add_bel_pin(bel, "SELFORCE", wire, PinType.INPUT) + elif func == 'io16': role = desc['role'] if role == 'MAIN': y_off, x_off = desc['pair'] @@ -367,13 +454,13 @@ def create_extra_funcs(tt: TileType, db: chipdb, x: int, y: int): tt.add_bel_pin(bel, port, wire, PinType.OUTPUT) else: tt.add_bel_pin(bel, port, wire, PinType.INPUT) - if func == 'buf': + elif func == 'buf': for buf_type, wires in desc.items(): for i, wire in enumerate(wires): if not tt.has_wire(wire): tt.create_wire(wire, "TILE_CLK") wire_out = f'{buf_type}{i}_O' - tt.create_wire(wire_out, "TILE_CLK") + tt.create_wire(wire_out, "BUFG_O") # XXX make Z from buf_type bel = tt.create_bel(f'{buf_type}{i}', buf_type, z = BUFG_Z + i) bel.flags = BEL_FLAG_GLOBAL @@ -464,11 +551,11 @@ def create_corner_tiletype(chip: Chip, db: chipdb, x: int, y: int, ttyp: int, td if x == 0 and y == 0: # GND is the logic low level generator - tt.create_wire('VSS', 'GND') + tt.create_wire('VSS', 'GND', const_value = 'VSS') gnd = tt.create_bel('GND', 'GND', z = GND_Z) tt.add_bel_pin(gnd, "G", "VSS", PinType.OUTPUT) # VCC is the logic high level generator - tt.create_wire('VCC', 'VCC') + tt.create_wire('VCC', 'VCC', const_value = 'VCC') gnd = tt.create_bel('VCC', 'VCC', z = VCC_Z) tt.add_bel_pin(gnd, "V", "VCC", PinType.OUTPUT) @@ -1005,6 +1092,12 @@ 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 spine->dqce bel map + for spine, bel in dqce_bels.items(): + chip.extra_data.add_dqce_bel(spine, bel[0], bel[1], bel[2]) + # create spine->dcs bel map + for spine, bel in dcs_bels.items(): + chip.extra_data.add_dcs_bel(spine, bel[0], bel[1], bel[2]) def main(): parser = argparse.ArgumentParser(description='Make Gowin BBA') diff --git a/himbaechel/uarch/gowin/gowin_utils.cc b/himbaechel/uarch/gowin/gowin_utils.cc index 37cff707..5bf398d9 100644 --- a/himbaechel/uarch/gowin/gowin_utils.cc +++ b/himbaechel/uarch/gowin/gowin_utils.cc @@ -64,6 +64,28 @@ BelId GowinUtils::get_pll_bel(BelId io_bel, IdString type) return BelId(); } +BelId GowinUtils::get_dqce_bel(IdString spine_name) +{ + const Extra_chip_data_POD *extra = reinterpret_cast(ctx->chip_info->extra_data.get()); + for (auto &spine_bel : extra->dqce_bels) { + if (IdString(spine_bel.spine) == spine_name) { + return ctx->getBelByLocation(Loc(spine_bel.bel_x, spine_bel.bel_y, spine_bel.bel_z)); + } + } + return BelId(); +} + +BelId GowinUtils::get_dcs_bel(IdString spine_name) +{ + const Extra_chip_data_POD *extra = reinterpret_cast(ctx->chip_info->extra_data.get()); + for (auto &spine_bel : extra->dcs_bels) { + if (IdString(spine_bel.spine) == spine_name) { + return ctx->getBelByLocation(Loc(spine_bel.bel_x, spine_bel.bel_y, spine_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 7b76ea9a..c280b271 100644 --- a/himbaechel/uarch/gowin/gowin_utils.h +++ b/himbaechel/uarch/gowin/gowin_utils.h @@ -33,6 +33,8 @@ struct GowinUtils bool is_simple_io_bel(BelId bel); Loc get_pair_iologic_bel(Loc loc); BelId get_io_bel_from_iologic(BelId bel); + BelId get_dqce_bel(IdString spine_name); + BelId get_dcs_bel(IdString spine_name); // BSRAM bool has_SP32(void); diff --git a/himbaechel/uarch/gowin/pack.cc b/himbaechel/uarch/gowin/pack.cc index 2faf1df0..c34a88cf 100644 --- a/himbaechel/uarch/gowin/pack.cc +++ b/himbaechel/uarch/gowin/pack.cc @@ -165,9 +165,9 @@ struct GowinPacker BelId bind_io(CellInfo &ci) { - BelId bel = ctx->getBelByName(IdStringList::parse(ctx, ci.attrs.at(id_BEL).as_string())); + BelId bel = ctx->getBelByNameStr(ci.attrs.at(id_BEL).as_string()); if (bel == BelId()) { - log_error("No bel named %s\n", IdStringList::parse(ctx, ci.attrs.at(id_BEL).as_string()).str(ctx).c_str()); + log_error("No bel named %s\n", ci.attrs.at(id_BEL).as_string().c_str()); } if (!ctx->checkBelAvail(bel)) { log_error("Can't place %s at %s because it's already taken by %s\n", ctx->nameOf(&ci), ctx->nameOfBel(bel), @@ -2999,18 +2999,83 @@ struct GowinPacker ctx->createCell(buf_name, id_BUFG); CellInfo *buf_ci = ctx->cells.at(buf_name).get(); buf_ci->addInput(id_I); - NetInfo *buf_ni = ctx->createNet(ctx->idf("$PACKER_BUF_%s", net.first.c_str(ctx))); - - if (ctx->verbose) { - log_info("Create buf '%s' with IN net '%s'\n", buf_name.c_str(ctx), buf_ni->name.c_str(ctx)); - } // move driver CellInfo *driver_cell = ni.driver.cell; IdString driver_port = ni.driver.port; driver_cell->movePortTo(driver_port, buf_ci, id_O); - buf_ci->connectPort(id_I, buf_ni); - driver_cell->connectPort(driver_port, buf_ni); + buf_ci->connectPorts(id_I, driver_cell, driver_port); + } + } + + // ========================================= + // Create DQCEs + // ========================================= + void pack_dqce() + { + // At the placement stage, nothing can be said definitively about DQCE, + // so we make user cells virtual but allocate all available bels by + // creating and placing cells - we will use some of them after, and + // delete the rest. + // We do this here because the decision about which physical DQCEs to + // use is made during routing, but some of the information (let’s say + // mapping cell pins -> bel pins) is filled in before routing. + bool grab_bels = false; + for (auto &cell : ctx->cells) { + auto &ci = *cell.second; + if (ci.type == id_DQCE) { + ci.pseudo_cell = std::make_unique(Loc(0, 0, 0)); + grab_bels = true; + } + } + if (grab_bels) { + for (int i = 0; i < 32; ++i) { + BelId dqce_bel = gwu.get_dqce_bel(ctx->idf("SPINE%d", i)); + if (dqce_bel != BelId()) { + IdString dqce_name = ctx->idf("$PACKER_DQCE_SPINE%d", i); + CellInfo *dqce = ctx->createCell(dqce_name, id_DQCE); + dqce->addInput(id_CE); + ctx->bindBel(dqce_bel, dqce, STRENGTH_LOCKED); + } + } + } + } + + // ========================================= + // Create DCSs + // ========================================= + void pack_dcs() + { + // At the placement stage, nothing can be said definitively about DCS, + // so we make user cells virtual but allocate all available bels by + // creating and placing cells - we will use some of them after, and + // delete the rest. + // We do this here because the decision about which physical DCEs to + // use is made during routing, but some of the information (let’s say + // mapping cell pins -> bel pins) is filled in before routing. + bool grab_bels = false; + for (auto &cell : ctx->cells) { + auto &ci = *cell.second; + if (ci.type == id_DCS) { + ci.pseudo_cell = std::make_unique(Loc(0, 0, 0)); + grab_bels = true; + } + } + if (grab_bels) { + for (int i = 0; i < 8; ++i) { + BelId dcs_bel = gwu.get_dcs_bel(ctx->idf("P%d%dA", 1 + (i % 4), 6 + (i >> 2))); + if (dcs_bel != BelId()) { + IdString dcs_name = ctx->idf("$PACKER_DCS_SPINE%d", 8 * (i % 4) + 6 + (i >> 2)); + CellInfo *dcs = ctx->createCell(dcs_name, id_DCS); + dcs->addInput(id_SELFORCE); + for (int j = 0; j < 4; ++j) { + dcs->addInput(ctx->idf("CLK%d", j)); + dcs->addInput(ctx->idf("CLKSEL%d", j)); + } + dcs->addOutput(id_CLKOUT); + ctx->bindBel(dcs_bel, dcs, STRENGTH_LOCKED); + } + } } } @@ -3061,6 +3126,13 @@ struct GowinPacker ctx->check(); pack_buffered_nets(); + ctx->check(); + + pack_dqce(); + ctx->check(); + + pack_dcs(); + ctx->check(); ctx->fixupHierarchy(); ctx->check();