From 210e0fa33b7502a1bb15dfcd3e5382aa2e4bcc0d Mon Sep 17 00:00:00 2001 From: YRabbit Date: Mon, 18 Mar 2024 22:08:52 +1000 Subject: [PATCH] gowin: Add support for DSP primitives. For the following primitives: - PADD9 - PADD18 - MULT9X9 - MULT18X18 - MULT36X36 - MULTALU18X18 - MULTALU36X18 - MULTADDALU18X18 - ALU54D packing and processing of fixed wires between macro and between DSP blocks is implemented. Clusters of DSP and macro blocks are processed using custom placement of cluster elements. Signed-off-by: YRabbit --- himbaechel/uarch/gowin/constids.inc | 42 + himbaechel/uarch/gowin/globals.cc | 13 +- himbaechel/uarch/gowin/gowin.cc | 263 ++++++- himbaechel/uarch/gowin/gowin.h | 57 +- himbaechel/uarch/gowin/gowin_arch_gen.py | 291 ++++++- himbaechel/uarch/gowin/gowin_utils.cc | 120 +++ himbaechel/uarch/gowin/gowin_utils.h | 24 + himbaechel/uarch/gowin/pack.cc | 945 ++++++++++++++++++++++- 8 files changed, 1725 insertions(+), 30 deletions(-) diff --git a/himbaechel/uarch/gowin/constids.inc b/himbaechel/uarch/gowin/constids.inc index db7ae20b..b3568720 100644 --- a/himbaechel/uarch/gowin/constids.inc +++ b/himbaechel/uarch/gowin/constids.inc @@ -917,6 +917,48 @@ X(WREB) X(CLKA) X(CLKB) +// DSP +X(ALU54D) +X(MULTADDALU18X18) +X(MULTALU18X18) +X(MULTALU36X18) +X(MULT36X36) +X(MULT18X18) +X(MULT9X9) +X(PADD18) +X(PADD9) +X(ASIGN) +X(BSIGN) +X(ASIGN0) +X(BSIGN0) +X(ASIGN1) +X(BSIGN1) +X(ASEL) +X(ASEL0) +X(ASEL1) +X(BSEL) +X(BSEL0) +X(BSEL1) +X(SOA_REG) +X(DSIGN) +X(ACCLOAD) +X(ACCLOAD0) +X(ACCLOAD1) +X(NET_ACCLOAD) +X(ALUSEL0) +X(ALUSEL1) +X(ALUSEL2) +X(ALUSEL3) +X(ALUSEL4) +X(ALUSEL5) +X(ALUSEL6) +X(USE_CASCADE_OUT) +X(USE_CASCADE_IN) +X(LAST_IN_CHAIN) +X(MULTALU18X18_MODE) +X(MULTADDALU18X18_MODE) +X(MULTALU36X18_MODE) + // IOB types X(IBUF) X(OBUF) diff --git a/himbaechel/uarch/gowin/globals.cc b/himbaechel/uarch/gowin/globals.cc index 86c3c63c..2504f8cc 100644 --- a/himbaechel/uarch/gowin/globals.cc +++ b/himbaechel/uarch/gowin/globals.cc @@ -215,6 +215,7 @@ struct GowinGlobalRouter } 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()) { @@ -222,8 +223,16 @@ struct GowinGlobalRouter 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)); - backwards_bfs_route(net, src, dst, 1000000, true, - [&](PipId pip) { return (is_relaxed_sink(usr.value) || global_pip_filter(pip)); }); + 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 diff --git a/himbaechel/uarch/gowin/gowin.cc b/himbaechel/uarch/gowin/gowin.cc index 97940def..1fc3e574 100644 --- a/himbaechel/uarch/gowin/gowin.cc +++ b/himbaechel/uarch/gowin/gowin.cc @@ -32,6 +32,7 @@ struct GowinImpl : HimbaechelAPI void postRoute() override; bool isBelLocationValid(BelId bel, bool explain_invalid) const override; + void notifyBelChange(BelId bel, CellInfo *cell) override; // Bel bucket functions IdString getBelBucketForCellType(IdString cell_type) const override; @@ -41,6 +42,11 @@ struct GowinImpl : HimbaechelAPI // wires bool checkPipAvail(PipId pip) const override; + // Cluster + bool isClusterStrict(const CellInfo *cell) const { return true; } + bool getClusterPlacement(ClusterId cluster, BelId root_bel, + std::vector> &placement) const; + private: HimbaechelHelpers h; GowinUtils gwu; @@ -53,15 +59,37 @@ struct GowinImpl : HimbaechelAPI // Validity checking struct GowinCellInfo { + // slice info const NetInfo *lut_f = nullptr; const NetInfo *ff_d = nullptr, *ff_ce = nullptr, *ff_clk = nullptr, *ff_lsr = nullptr; const NetInfo *alu_sum = nullptr; + // dsp info + const NetInfo *dsp_asign = nullptr, *dsp_bsign = nullptr, *dsp_asel = nullptr, *dsp_bsel = nullptr, + *dsp_ce = nullptr, *dsp_clk = nullptr, *dsp_reset = nullptr; + bool dsp_soa_reg; }; std::vector fast_cell_info; void assign_cell_info(); + // dsp control nets + // Each DSP and each macro has a small set of control wires that are + // allocated to internal primitives as needed. It is assumed that most + // primitives use the same signals for CE, CLK and especially RESET, so + // these wires are few and need to be controlled. + struct dsp_net_counters + { + dict ce; + dict clk; + dict reset; + }; + dict dsp_net_cnt; + dict dsp_bel2cell; // Remember the connection with cells + // since this information is already lost during unbinding + void adjust_dsp_pin_mapping(void); + // bel placement validation bool slice_valid(int x, int y, int z) const; + bool dsp_valid(Loc l, IdString bel_type, bool explain_invalid) const; }; struct GowinArch : HimbaechelArch @@ -175,6 +203,59 @@ void GowinImpl::pack() gowin_pack(ctx); } +// One DSP macro, in a rough approximation, consists of 5 large operating +// blocks (pre-adders, multipliers and alu), at almost every input (blocks +// usually have two of them) you can turn on registers, in addition, there are +// registers on a dedicated operand shift line between DSP and registers at +// the outputs. As we see, the number of registers is large, but the DSP has +// only four inputs for each of the CE, CLK and RESET signals, and here we tell +// gowin_pack which version of each signal is used by which block. +// We also indicate to the router which Bel's pin to use. +void GowinImpl::adjust_dsp_pin_mapping(void) +{ + for (auto b2c : dsp_bel2cell) { + BelId bel = b2c.first; + Loc loc = ctx->getBelLocation(bel); + CellInfo *ci = b2c.second; + const auto dsp_data = fast_cell_info.at(ci->flat_index); + + auto set_cell_bel_pin = [&](dict nets, IdString pin, IdString net_name, const char *fmt, + const char *fmt_double = nullptr) { + int i = 0; + for (auto net_cnt : nets) { + if (net_cnt.first == net_name) { + break; + } + ++i; + } + ci->cell_bel_pins.at(pin).clear(); + if (fmt_double == nullptr) { + ci->cell_bel_pins.at(pin).push_back(ctx->idf(fmt, i)); + } else { + ci->cell_bel_pins.at(pin).push_back(ctx->idf(fmt_double, i, 0)); + ci->cell_bel_pins.at(pin).push_back(ctx->idf(fmt_double, i, 1)); + } + ci->setAttr(pin, i); + }; + + if (dsp_data.dsp_reset != nullptr) { + BelId dsp = ctx->getBelByLocation(Loc(loc.x, loc.y, BelZ::DSP_Z)); + set_cell_bel_pin(dsp_net_cnt.at(dsp).reset, id_RESET, dsp_data.dsp_reset->name, "RESET%d", + ci->type == id_MULT36X36 ? "RESET%d%d" : nullptr); + } + if (dsp_data.dsp_ce != nullptr) { + BelId dsp = ctx->getBelByLocation(Loc(loc.x, loc.y, gwu.get_dsp_macro(loc.z))); + set_cell_bel_pin(dsp_net_cnt.at(dsp).ce, id_CE, dsp_data.dsp_ce->name, "CE%d", + ci->type == id_MULT36X36 ? "CE%d%d" : nullptr); + } + if (dsp_data.dsp_clk != nullptr) { + BelId dsp = ctx->getBelByLocation(Loc(loc.x, loc.y, gwu.get_dsp_macro(loc.z))); + set_cell_bel_pin(dsp_net_cnt.at(dsp).clk, id_CLK, dsp_data.dsp_clk->name, "CLK%d", + ci->type == id_MULT36X36 ? "CLK%d%d" : nullptr); + } + } +} + void GowinImpl::prePlace() { assign_cell_info(); } void GowinImpl::postPlace() { @@ -190,6 +271,9 @@ void GowinImpl::postPlace() } log_break(); } + + // adjust cell pin to bel pin mapping for DSP cells (CE, CLK and RESET pins) + adjust_dsp_pin_mapping(); } void GowinImpl::preRoute() { gowin_route_globals(ctx); } @@ -238,10 +322,10 @@ void GowinImpl::postRoute() bool GowinImpl::isBelLocationValid(BelId bel, bool explain_invalid) const { Loc l = ctx->getBelLocation(bel); + IdString bel_type = ctx->getBelType(bel); if (!ctx->getBoundBelCell(bel)) { return true; } - IdString bel_type = ctx->getBelType(bel); switch (bel_type.hash()) { case ID_LUT4: /* fall-through */ case ID_DFF: @@ -251,6 +335,16 @@ bool GowinImpl::isBelLocationValid(BelId bel, bool explain_invalid) const case ID_RAM16SDP4: // only slices 4 and 5 are critical for RAM return slice_valid(l.x, l.y, l.z - BelZ::RAMW_Z + 5) && slice_valid(l.x, l.y, l.z - BelZ::RAMW_Z + 4); + case ID_PADD9: /* fall-through */ + case ID_PADD18: /* fall-through */ + case ID_MULT9X9: /* fall-through */ + case ID_MULT18X18: /* fall-through */ + case ID_MULTADDALU18X18: /* fall-through */ + case ID_MULTALU18X18: /* fall-through */ + case ID_MULTALU36X18: /* fall-through */ + case ID_MULT36X36: /* fall-through */ + case ID_ALU54D: + return dsp_valid(l, bel_type, explain_invalid); } return true; } @@ -290,6 +384,10 @@ IdString GowinImpl::getBelBucketForCellType(IdString cell_type) const bool GowinImpl::isValidBelForCellType(IdString cell_type, BelId bel) const { + if (cell_type == id_DUMMY_CELL) { + return true; + } + IdString bel_type = ctx->getBelType(bel); if (bel_type == id_IOB) { return cell_type.in(id_IBUF, id_OBUF); @@ -347,6 +445,23 @@ void GowinImpl::assign_cell_info() fc.alu_sum = ci->getPort(id_SUM); continue; } + auto get_net = [&](IdString port_id) { + NetInfo *ni = ci->getPort(port_id); + if (ni != nullptr && ni->driver.cell == nullptr) { + ni = nullptr; + } + return ni; + }; + if (is_dsp(ci)) { + fc.dsp_reset = get_net(id_RESET); + fc.dsp_clk = get_net(id_CLK); + fc.dsp_ce = get_net(id_CE); + fc.dsp_asign = get_net(id_ASIGN); + fc.dsp_bsign = get_net(id_BSIGN); + fc.dsp_asel = get_net(id_ASEL); + fc.dsp_bsel = get_net(id_BSEL); + fc.dsp_soa_reg = ci->params.count(id_SOA_REG) && ci->params.at(id_SOA_REG).as_int64() == 1; + } } } @@ -367,6 +482,59 @@ inline bool incompatible_ffs(const CellInfo *ff, const CellInfo *adj_ff) } // placement validation +bool GowinImpl::dsp_valid(Loc l, IdString bel_type, bool explain_invalid) const +{ + const CellInfo *dsp = ctx->getBoundBelCell(ctx->getBelByLocation(l)); + const auto &dsp_data = fast_cell_info.at(dsp->flat_index); + // check for shift out register - there is only one for macro + if (dsp_data.dsp_soa_reg) { + if (l.z == BelZ::MULT18X18_0_1_Z || l.z == BelZ::MULT18X18_1_1_Z || l.z == BelZ::MULT9X9_0_0_Z || + l.z == BelZ::MULT9X9_0_1_Z || l.z == BelZ::MULT9X9_1_0_Z || l.z == BelZ::MULT9X9_1_1_Z) { + if (explain_invalid) { + log_nonfatal_error( + "It is not possible to place the DSP so that the SOA register is on the macro boundary.\n"); + } + return false; + } + } + + if (bel_type.in(id_MULT9X9, id_PADD9)) { + int pair_z = gwu.get_dsp_paired_9(l.z); + const CellInfo *adj_dsp9 = ctx->getBoundBelCell(ctx->getBelByLocation(Loc(l.x, l.y, pair_z))); + if (adj_dsp9 != nullptr) { + const auto &adj_dsp9_data = fast_cell_info.at(adj_dsp9->flat_index); + if ((dsp_data.dsp_asign != adj_dsp9_data.dsp_asign) || (dsp_data.dsp_bsign != adj_dsp9_data.dsp_bsign) || + (dsp_data.dsp_asel != adj_dsp9_data.dsp_asel) || (dsp_data.dsp_bsel != adj_dsp9_data.dsp_bsel) || + (dsp_data.dsp_reset != adj_dsp9_data.dsp_reset) || (dsp_data.dsp_ce != adj_dsp9_data.dsp_ce) || + (dsp_data.dsp_clk != adj_dsp9_data.dsp_clk)) { + if (explain_invalid) { + log_nonfatal_error("For 9bit primitives the control signals must be same.\n"); + } + return false; + } + } + } + // check for control nets "overflow" + BelId dsp_bel = ctx->getBelByLocation(Loc(l.x, l.y, BelZ::DSP_Z)); + if (dsp_net_cnt.at(dsp_bel).reset.size() > 4) { + if (explain_invalid) { + log_nonfatal_error("More than 4 different networks for RESET signals in one DSP are not allowed.\n"); + } + return false; + } + BelId dsp_macro_bel = ctx->getBelByLocation(Loc(l.x, l.y, gwu.get_dsp_macro(l.z))); + if (dsp_net_cnt.count(dsp_macro_bel)) { + if (dsp_net_cnt.at(dsp_macro_bel).ce.size() > 4 || dsp_net_cnt.at(dsp_macro_bel).clk.size() > 4) { + if (explain_invalid) { + log_nonfatal_error( + "More than 4 different networks for CE or CLK signals in one DSP macro are not allowed.\n"); + } + return false; + } + } + return true; +} + bool GowinImpl::slice_valid(int x, int y, int z) const { const CellInfo *lut = ctx->getBoundBelCell(ctx->getBelByLocation(Loc(x, y, z * 2))); @@ -433,6 +601,99 @@ bool GowinImpl::slice_valid(int x, int y, int z) const return true; } +// Cluster +bool GowinImpl::getClusterPlacement(ClusterId cluster, BelId root_bel, + std::vector> &placement) const +{ + CellInfo *root_ci = getClusterRootCell(cluster); + if (!root_ci->type.in(id_PADD9, id_MULT9X9, id_PADD18, id_MULT18X18, id_MULTALU18X18, id_MULTALU36X18, + id_MULTADDALU18X18, id_ALU54D)) { + return HimbaechelAPI::getClusterPlacement(cluster, root_bel, placement); + } + + NPNR_ASSERT(root_bel != BelId()); + if (!isValidBelForCellType(root_ci->type, root_bel)) { + return false; + } + + IdString bel_type = ctx->getBelType(root_bel); + // non-chain DSP + if (root_ci->constr_children.size() == 1 && bel_type.in(id_PADD9, id_MULT9X9)) { + return HimbaechelAPI::getClusterPlacement(cluster, root_bel, placement); + } + + placement.clear(); + Loc root_loc = ctx->getBelLocation(root_bel); + placement.emplace_back(root_ci, root_bel); + + Loc mult_loc = root_loc; + for (auto child : root_ci->constr_children) { + Loc child_loc; + child_loc.y = root_loc.y; + if (child->type == id_DUMMY_CELL) { + child_loc.x = mult_loc.x + child->constr_x; + child_loc.z = mult_loc.z + child->constr_z; + } else { + child_loc = gwu.get_dsp_next_in_chain(mult_loc, child->type); + mult_loc = child_loc; + } + + BelId child_bel = ctx->getBelByLocation(child_loc); + if (child_bel == BelId() || !isValidBelForCellType(child->type, child_bel)) + return false; + placement.emplace_back(child, child_bel); + } + return true; +} + +void GowinImpl::notifyBelChange(BelId bel, CellInfo *cell) +{ + if (cell != nullptr && !is_dsp(cell)) { + return; + } + if (cell == nullptr && dsp_bel2cell.count(bel) == 0) { + return; + } + + // trace DSP control networks + IdString cell_type = id_DUMMY_CELL; + if (cell != nullptr) { + cell_type = cell->type; + } + Loc loc = ctx->getBelLocation(bel); + Loc l = loc; + l.z = gwu.get_dsp(loc.z); + BelId dsp = ctx->getBelByLocation(l); + l.z = gwu.get_dsp_macro(loc.z); + BelId dsp_macro = ctx->getBelByLocation(l); + + if (cell) { + const auto &dsp_cell_data = fast_cell_info.at(cell->flat_index); + if (dsp_cell_data.dsp_reset != nullptr) { + dsp_net_cnt[dsp].reset[dsp_cell_data.dsp_reset->name]++; + } + if (dsp_cell_data.dsp_ce != nullptr) { + dsp_net_cnt[dsp_macro].ce[dsp_cell_data.dsp_ce->name]++; + } + if (dsp_cell_data.dsp_clk != nullptr) { + dsp_net_cnt[dsp_macro].clk[dsp_cell_data.dsp_clk->name]++; + } + dsp_bel2cell[bel] = cell; + } else { + const auto &dsp_cell_data = fast_cell_info.at(dsp_bel2cell.at(bel)->flat_index); + if (dsp_cell_data.dsp_reset != nullptr) { + dsp_net_cnt.at(dsp).reset.at(dsp_cell_data.dsp_reset->name)--; + } + if (dsp_cell_data.dsp_ce != nullptr) { + dsp_net_cnt.at(dsp_macro).ce.at(dsp_cell_data.dsp_ce->name)--; + } + if (dsp_cell_data.dsp_clk != nullptr) { + dsp_net_cnt.at(dsp_macro).clk.at(dsp_cell_data.dsp_clk->name)--; + } + dsp_bel2cell.erase(bel); + } +} + } // namespace NEXTPNR_NAMESPACE_END diff --git a/himbaechel/uarch/gowin/gowin.h b/himbaechel/uarch/gowin/gowin.h index 715f54e6..aa3dcd08 100644 --- a/himbaechel/uarch/gowin/gowin.h +++ b/himbaechel/uarch/gowin/gowin.h @@ -54,6 +54,14 @@ inline bool type_is_bsram(IdString cell_type) } inline bool is_bsram(const CellInfo *cell) { return type_is_bsram(cell->type); } +// Return true if a cell is a DSP +inline bool type_is_dsp(IdString cell_type) +{ + return cell_type.in(id_PADD9, id_PADD18, id_MULT9X9, id_MULT18X18, id_MULT36X36, id_ALU54D, id_MULTALU18X18, + id_MULTALU36X18, id_MULTADDALU18X18); +} +inline bool is_dsp(const CellInfo *cell) { return type_is_dsp(cell->type); } + // ========================================== // extra data in the chip db // ========================================== @@ -113,7 +121,54 @@ enum PLL_Z = 275, GSR_Z = 276, VCC_Z = 277, - VSS_Z = 278 + VSS_Z = 278, + + // 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. + DSP_Z = 509, // DSP + + DSP_0_Z = 511, // DSP macro 0 + PADD18_0_0_Z = 512, + PADD9_0_0_Z = 512 + 1, + PADD9_0_1_Z = 512 + 2, + PADD18_0_1_Z = 516, + PADD9_0_2_Z = 516 + 1, + PADD9_0_3_Z = 516 + 2, + + MULT18X18_0_0_Z = 520, + MULT9X9_0_0_Z = 520 + 1, + MULT9X9_0_1_Z = 520 + 2, + MULT18X18_0_1_Z = 524, + MULT9X9_0_2_Z = 524 + 1, + MULT9X9_0_3_Z = 524 + 2, + + ALU54D_0_Z = 524 + 3, + MULTALU18X18_0_Z = 528, + MULTALU36X18_0_Z = 528 + 1, + MULTADDALU18X18_0_Z = 528 + 2, + + MULT36X36_Z = 528 + 3, + + DSP_1_Z = 543, // DSP macro 1 + PADD18_1_0_Z = 544, + PADD9_1_0_Z = 544 + 1, + PADD9_1_1_Z = 544 + 2, + PADD18_1_1_Z = 548, + PADD9_1_2_Z = 548 + 1, + PADD9_1_3_Z = 548 + 2, + + MULT18X18_1_0_Z = 552, + MULT9X9_1_0_Z = 552 + 1, + MULT9X9_1_1_Z = 552 + 2, + MULT18X18_1_1_Z = 556, + MULT9X9_1_2_Z = 556 + 1, + MULT9X9_1_3_Z = 556 + 2, + + ALU54D_1_Z = 556 + 3, + MULTALU18X18_1_Z = 560, + MULTALU36X18_1_Z = 560 + 1, + MULTADDALU18X18_1_Z = 560 + 2 }; } diff --git a/himbaechel/uarch/gowin/gowin_arch_gen.py b/himbaechel/uarch/gowin/gowin_arch_gen.py index 2f96ee55..3ba94c52 100644 --- a/himbaechel/uarch/gowin/gowin_arch_gen.py +++ b/himbaechel/uarch/gowin/gowin_arch_gen.py @@ -44,6 +44,50 @@ GSR_Z = 276 VCC_Z = 277 GND_Z = 278 +DSP_Z = 509 + +DSP_0_Z = 511 # DSP macro 0 +PADD18_0_0_Z = 512 +PADD9_0_0_Z = 512 + 1 +PADD9_0_1_Z = 512 + 2 +PADD18_0_1_Z = 516 +PADD9_0_2_Z = 516 + 1 +PADD9_0_3_Z = 516 + 2 + +MULT18X18_0_0_Z = 520 +MULT9X9_0_0_Z = 520 + 1 +MULT9X9_0_1_Z = 520 + 2 +MULT18X18_0_1_Z = 524 +MULT9X9_0_2_Z = 524 + 1 +MULT9X9_0_3_Z = 524 + 2 + +ALU54D_0_Z = 524 + 3 +MULTALU18X18_0_Z = 528 +MULTALU36X18_0_Z = 528 + 1 +MULTADDALU18X18_0_Z = 528 + 2 + +MULT36X36_Z = 528 + 3 + +DSP_1_Z = 543 # DSP macro 1 +PADD18_1_0_Z = 544 +PADD9_1_0_Z = 544 + 1 +PADD9_1_1_Z = 544 + 2 +PADD18_1_1_Z = 548 +PADD9_1_2_Z = 548 + 1 +PADD9_1_3_Z = 548 + 2 + +MULT18X18_1_0_Z = 552 +MULT9X9_1_0_Z = 552 + 1 +MULT9X9_1_1_Z = 552 + 2 +MULT18X18_1_1_Z = 556 +MULT9X9_1_2_Z = 556 + 1 +MULT9X9_1_3_Z = 556 + 2 + +ALU54D_1_Z = 556 + 3 +MULTALU18X18_1_Z = 560 +MULTALU36X18_1_Z = 560 + 1 +MULTADDALU18X18_1_Z = 560 + 2 + # ======================================= # Chipdb additional info # ======================================= @@ -363,6 +407,15 @@ def create_tiletype(create_func, chip: Chip, db: chipdb, x: int, y: int, ttyp: i create_switch_matrix(tt, db, x, y) chip.set_tile_type(x, y, tdesc.tiletype) +def add_port_wire(tt, bel, portmap, name, wire_type, port_type): + wire = portmap[name] + if not tt.has_wire(wire): + if name.startswith('CLK'): + tt.create_wire(wire, "TILE_CLK") + else: + tt.create_wire(wire, wire_type) + tt.add_bel_pin(bel, name, wire, port_type) + def create_null_tiletype(chip: Chip, db: chipdb, x: int, y: int, ttyp: int, tdesc: TypeDesc): typename = "NULL" tiletype = f"{typename}_{ttyp}" @@ -586,29 +639,234 @@ def create_bsram_tiletype(chip: Chip, db: chipdb, x: int, y: int, ttyp: int, tde portmap = db.grid[y][x].bels['BSRAM'].portmap bsram = tt.create_bel("BSRAM", "BSRAM", z = BSRAM_Z) - def add_port_wire(tt, bel, name, wire_type = "BSRAM_I", port_type = PinType.INPUT): - wire = portmap[name] - if not tt.has_wire(wire): - if name.startswith('CLK'): - tt.create_wire(wire, "TILE_CLK") - else: - tt.create_wire(wire, wire_type) - tt.add_bel_pin(bel, name, wire, port_type) for sfx in {'', 'A', 'B'}: for inp in _bsram_inputs: - add_port_wire(tt, bsram, f"{inp}{sfx}") + add_port_wire(tt, bsram, portmap, f"{inp}{sfx}", "BSRAM_I", PinType.INPUT) for idx in range(3): - add_port_wire(tt, bsram, f"BLKSEL{sfx}{idx}") + add_port_wire(tt, bsram, portmap, f"BLKSEL{sfx}{idx}", "BSRAM_I", PinType.INPUT) for idx in range(14): - add_port_wire(tt, bsram, f"AD{sfx}{idx}") + add_port_wire(tt, bsram, portmap, f"AD{sfx}{idx}", "BSRAM_I", PinType.INPUT) for idx in range(18): - add_port_wire(tt, bsram, f"DI{sfx}{idx}") - add_port_wire(tt, bsram, f"DO{sfx}{idx}", "BSRAM_O", PinType.OUTPUT) + add_port_wire(tt, bsram, portmap, f"DI{sfx}{idx}", "BSRAM_I", PinType.INPUT) + add_port_wire(tt, bsram, portmap, f"DO{sfx}{idx}", "BSRAM_O", PinType.OUTPUT) if not sfx: for idx in range(18, 36): - add_port_wire(tt, bsram, f"DI{idx}") - add_port_wire(tt, bsram, f"DO{idx}", "BSRAM_O", PinType.OUTPUT) + add_port_wire(tt, bsram, portmap, f"DI{idx}", "BSRAM_I", PinType.INPUT) + add_port_wire(tt, bsram, portmap, f"DO{idx}", "BSRAM_O", PinType.OUTPUT) + + tdesc.tiletype = tiletype + return tt + +# DSP +_mult_inputs = {'ASEL', 'BSEL', 'ASIGN', 'BSIGN'} +def create_dsp_tiletype(chip: Chip, db: chipdb, x: int, y: int, ttyp: int, tdesc: TypeDesc): + typename = "DSP" + tiletype = f"{typename}_{ttyp}" + if tdesc.sfx != 0: + tiletype += f"_{tdesc.sfx}" + tt = chip.create_tile_type(tiletype) + tt.extra_data = TileExtraData(chip.strs.id(typename)) + + # create big DSP + belname = f'DSP' + portmap = db.grid[y][x].bels[belname].portmap + dsp = tt.create_bel(belname, "DSP", DSP_Z) + dsp.flags = BEL_FLAG_HIDDEN + + # create DSP macros + for idx in range(2): + belname = f'DSP{idx}' + portmap = db.grid[y][x].bels[belname].portmap + dsp = tt.create_bel(belname, "DSP", eval(f'DSP_{idx}_Z')) + dsp.flags = BEL_FLAG_HIDDEN + + # create pre-adders + for mac, idx in [(mac, idx) for mac in range(2) for idx in range(4)]: + belname = f'PADD9{mac}{idx}' + portmap = db.grid[y][x].bels[belname].portmap + dsp = tt.create_bel(belname, "PADD9", eval(f'PADD9_{mac}_{idx}_Z')) + + add_port_wire(tt, dsp, portmap, "ADDSUB", "DSP_I", PinType.INPUT) + for sfx in {'A', 'B'}: + for inp in range(9): + add_port_wire(tt, dsp, portmap, f"{sfx}{inp}", "DSP_I", PinType.INPUT) + for inp in range(9): + add_port_wire(tt, dsp, portmap, f"C{inp}", "DSP_I", PinType.INPUT) + for inp in range(4): + add_port_wire(tt, dsp, portmap, f"CE{inp}", "DSP_I", PinType.INPUT) + add_port_wire(tt, dsp, portmap, f"CLK{inp}", "DSP_I", PinType.INPUT) + add_port_wire(tt, dsp, portmap, f"RESET{inp}", "DSP_I", PinType.INPUT) + add_port_wire(tt, dsp, portmap, "ASEL", "DSP_I", PinType.INPUT) + for outp in range(9): + add_port_wire(tt, dsp, portmap, f"DOUT{outp}", "DSP_O", PinType.OUTPUT) + + for mac, idx in [(mac, idx) for mac in range(2) for idx in range(2)]: + belname = f'PADD18{mac}{idx}' + portmap = db.grid[y][x].bels[belname].portmap + dsp = tt.create_bel(belname, "PADD18", eval(f'PADD18_{mac}_{idx}_Z')) + + add_port_wire(tt, dsp, portmap, "ADDSUB", "DSP_I", PinType.INPUT) + for sfx in {'A', 'B'}: + for inp in range(18): + add_port_wire(tt, dsp, portmap, f"{sfx}{inp}", "DSP_I", PinType.INPUT) + for inp in range(18): + add_port_wire(tt, dsp, portmap, f"C{inp}", "DSP_I", PinType.INPUT) + for inp in range(4): + add_port_wire(tt, dsp, portmap, f"CE{inp}", "DSP_I", PinType.INPUT) + add_port_wire(tt, dsp, portmap, f"CLK{inp}", "DSP_I", PinType.INPUT) + add_port_wire(tt, dsp, portmap, f"RESET{inp}", "DSP_I", PinType.INPUT) + add_port_wire(tt, dsp, portmap, "ASEL", "DSP_I", PinType.INPUT) + for outp in range(18): + add_port_wire(tt, dsp, portmap, f"DOUT{outp}", "DSP_O", PinType.OUTPUT) + + # create multipliers + # mult 9x9 + for mac, idx in [(mac, idx) for mac in range(2) for idx in range(4)]: + belname = f'MULT9X9{mac}{idx}' + portmap = db.grid[y][x].bels[belname].portmap + dsp = tt.create_bel(belname, "MULT9X9", eval(f'MULT9X9_{mac}_{idx}_Z')) + + for sfx in {'A', 'B'}: + for inp in range(9): + add_port_wire(tt, dsp, portmap, f"{sfx}{inp}", "DSP_I", PinType.INPUT) + for inp in _mult_inputs: + add_port_wire(tt, dsp, portmap, inp, "DSP_I", PinType.INPUT) + for inp in range(4): + add_port_wire(tt, dsp, portmap, f"CE{inp}", "DSP_I", PinType.INPUT) + add_port_wire(tt, dsp, portmap, f"CLK{inp}", "DSP_I", PinType.INPUT) + add_port_wire(tt, dsp, portmap, f"RESET{inp}", "DSP_I", PinType.INPUT) + for outp in range(18): + add_port_wire(tt, dsp, portmap, f"DOUT{outp}", "DSP_O", PinType.OUTPUT) + + # mult 18x18 + for mac, idx in [(mac, idx) for mac in range(2) for idx in range(2)]: + belname = f'MULT18X18{mac}{idx}' + portmap = db.grid[y][x].bels[belname].portmap + dsp = tt.create_bel(belname, "MULT18X18", eval(f'MULT18X18_{mac}_{idx}_Z')) + + for sfx in {'A', 'B'}: + for inp in range(18): + add_port_wire(tt, dsp, portmap, f"{sfx}{inp}", "DSP_I", PinType.INPUT) + for inp in _mult_inputs: + add_port_wire(tt, dsp, portmap, inp, "DSP_I", PinType.INPUT) + for inp in range(4): + add_port_wire(tt, dsp, portmap, f"CE{inp}", "DSP_I", PinType.INPUT) + add_port_wire(tt, dsp, portmap, f"CLK{inp}", "DSP_I", PinType.INPUT) + add_port_wire(tt, dsp, portmap, f"RESET{inp}", "DSP_I", PinType.INPUT) + for outp in range(36): + add_port_wire(tt, dsp, portmap, f"DOUT{outp}", "DSP_O", PinType.OUTPUT) + + # mult 36x36 + belname = 'MULT36X36' + portmap = db.grid[y][x].bels[belname].portmap + dsp = tt.create_bel(belname, "MULT36X36", MULT36X36_Z) + + for i in range(2): + for sfx in {'A', 'B'}: + for inp in range(36): + add_port_wire(tt, dsp, portmap, f"{sfx}{inp}{i}", "DSP_I", PinType.INPUT) + for inp in {'ASIGN', 'BSIGN'}: + add_port_wire(tt, dsp, portmap, f"{inp}{i}", "DSP_I", PinType.INPUT) + for inp in range(4): + add_port_wire(tt, dsp, portmap, f"CE{inp}{i}", "DSP_I", PinType.INPUT) + add_port_wire(tt, dsp, portmap, f"CLK{inp}{i}", "DSP_I", PinType.INPUT) + add_port_wire(tt, dsp, portmap, f"RESET{inp}{i}", "DSP_I", PinType.INPUT) + for outp in range(72): + add_port_wire(tt, dsp, portmap, f"DOUT{outp}", "DSP_O", PinType.OUTPUT) + + # create alus + for mac in range(2): + belname = f'ALU54D{mac}' + portmap = db.grid[y][x].bels[belname].portmap + dsp = tt.create_bel(belname, "ALU54D", eval(f'ALU54D_{mac}_Z')) + + for sfx in {'A', 'B'}: + for inp in range(54): + add_port_wire(tt, dsp, portmap, f"{sfx}{inp}", "DSP_I", PinType.INPUT) + for inp in {'ASIGN', 'BSIGN'}: + add_port_wire(tt, dsp, portmap, inp, "DSP_I", PinType.INPUT) + for inp in range(4): + add_port_wire(tt, dsp, portmap, f"CE{inp}", "DSP_I", PinType.INPUT) + add_port_wire(tt, dsp, portmap, f"CLK{inp}", "DSP_I", PinType.INPUT) + add_port_wire(tt, dsp, portmap, f"RESET{inp}", "DSP_I", PinType.INPUT) + if inp < 2: + add_port_wire(tt, dsp, portmap, f"ACCLOAD{inp}", "DSP_I", PinType.INPUT) + for outp in range(54): + add_port_wire(tt, dsp, portmap, f"DOUT{outp}", "DSP_O", PinType.OUTPUT) + + # create multalus + # MULTALU18X18 + for mac in range(2): + belname = f'MULTALU18X18{mac}' + portmap = db.grid[y][x].bels[belname].portmap + dsp = tt.create_bel(belname, "MULTALU18X18", eval(f'MULTALU18X18_{mac}_Z')) + + for i in range(2): + for sfx in {'ASIGN', 'BSIGN'}: + add_port_wire(tt, dsp, portmap, f"{sfx}{i}", "DSP_I", PinType.INPUT) + for sfx in {'A', 'B'}: + for inp in range(18): + add_port_wire(tt, dsp, portmap, f"{sfx}{inp}{i}", "DSP_I", PinType.INPUT) + for sfx in {'C', 'D'}: + for inp in range(54): + add_port_wire(tt, dsp, portmap, f"{sfx}{inp}", "DSP_I", PinType.INPUT) + add_port_wire(tt, dsp, portmap, "DSIGN", "DSP_I", PinType.INPUT) + for inp in range(4): + add_port_wire(tt, dsp, portmap, f"CE{inp}", "DSP_I", PinType.INPUT) + add_port_wire(tt, dsp, portmap, f"CLK{inp}", "DSP_I", PinType.INPUT) + add_port_wire(tt, dsp, portmap, f"RESET{inp}", "DSP_I", PinType.INPUT) + if inp < 2: + add_port_wire(tt, dsp, portmap, f"ACCLOAD{inp}", "DSP_I", PinType.INPUT) + for outp in range(54): + add_port_wire(tt, dsp, portmap, f"DOUT{outp}", "DSP_O", PinType.OUTPUT) + + # MULTALU36X18 + for mac in range(2): + belname = f'MULTALU36X18{mac}' + portmap = db.grid[y][x].bels[belname].portmap + dsp = tt.create_bel(belname, "MULTALU36X18", eval(f'MULTALU36X18_{mac}_Z')) + + for i in range(2): + for sfx in {'ASIGN', 'BSIGN'}: + add_port_wire(tt, dsp, portmap, f"{sfx}{i}", "DSP_I", PinType.INPUT) + for inp in range(18): + add_port_wire(tt, dsp, portmap, f"A{inp}{i}", "DSP_I", PinType.INPUT) + for inp in range(7): + add_port_wire(tt, dsp, portmap, f"ALUSEL{inp}", "DSP_I", PinType.INPUT) + for inp in range(36): + add_port_wire(tt, dsp, portmap, f"B{inp}", "DSP_I", PinType.INPUT) + for inp in range(54): + add_port_wire(tt, dsp, portmap, f"C{inp}", "DSP_I", PinType.INPUT) + for inp in range(4): + add_port_wire(tt, dsp, portmap, f"CE{inp}", "DSP_I", PinType.INPUT) + add_port_wire(tt, dsp, portmap, f"CLK{inp}", "DSP_I", PinType.INPUT) + add_port_wire(tt, dsp, portmap, f"RESET{inp}", "DSP_I", PinType.INPUT) + for outp in range(54): + add_port_wire(tt, dsp, portmap, f"DOUT{outp}", "DSP_O", PinType.OUTPUT) + + # MULTADDALU18X18 + for mac in range(2): + belname = f'MULTADDALU18X18{mac}' + portmap = db.grid[y][x].bels[belname].portmap + dsp = tt.create_bel(belname, "MULTADDALU18X18", eval(f'MULTADDALU18X18_{mac}_Z')) + + for i in range(2): + for sfx in {'ASIGN', 'BSIGN', 'ASEL', 'BSEL'}: + add_port_wire(tt, dsp, portmap, f"{sfx}{i}", "DSP_I", PinType.INPUT) + for inp in range(18): + add_port_wire(tt, dsp, portmap, f"A{inp}{i}", "DSP_I", PinType.INPUT) + add_port_wire(tt, dsp, portmap, f"B{inp}{i}", "DSP_I", PinType.INPUT) + for inp in range(7): + add_port_wire(tt, dsp, portmap, f"ALUSEL{inp}", "DSP_I", PinType.INPUT) + for inp in range(54): + add_port_wire(tt, dsp, portmap, f"C{inp}", "DSP_I", PinType.INPUT) + for inp in range(4): + add_port_wire(tt, dsp, portmap, f"CE{inp}", "DSP_I", PinType.INPUT) + add_port_wire(tt, dsp, portmap, f"CLK{inp}", "DSP_I", PinType.INPUT) + add_port_wire(tt, dsp, portmap, f"RESET{inp}", "DSP_I", PinType.INPUT) + for outp in range(54): + add_port_wire(tt, dsp, portmap, f"DOUT{outp}", "DSP_O", PinType.OUTPUT) tdesc.tiletype = tiletype return tt @@ -739,6 +997,7 @@ def main(): ssram_tiletypes = db.tile_types['M'] pll_tiletypes = db.tile_types['P'] bsram_tiletypes = db.tile_types.get('B', set()) + dsp_tiletypes = db.tile_types.get('D', set()) # Setup tile grid for x in range(X): @@ -758,6 +1017,8 @@ def main(): create_tiletype(create_pll_tiletype, ch, db, x, y, ttyp) elif ttyp in bsram_tiletypes: create_tiletype(create_bsram_tiletype, ch, db, x, y, ttyp) + elif ttyp in dsp_tiletypes: + create_tiletype(create_dsp_tiletype, ch, db, x, y, ttyp) else: create_tiletype(create_null_tiletype, ch, db, x, y, ttyp) diff --git a/himbaechel/uarch/gowin/gowin_utils.cc b/himbaechel/uarch/gowin/gowin_utils.cc index 4aec5e50..df255527 100644 --- a/himbaechel/uarch/gowin/gowin_utils.cc +++ b/himbaechel/uarch/gowin/gowin_utils.cc @@ -105,4 +105,124 @@ std::unique_ptr GowinUtils::create_cell(IdString name, IdString type) return std::make_unique(ctx, name, type); } +// DSP +Loc GowinUtils::get_dsp_next_9_in_chain(Loc from) const +{ + Loc res; + res.y = from.y; + if (get_dsp_18_idx(from.z) == 0) { + res.x = from.x; + res.z = from.z + 4; + return res; + } + if (get_dsp_macro_num(from.z)) { + // next DSP + res.x = from.x + 9; + res.z = from.z & (~0x24); + } else { + // next macro + res.x = from.x; + res.z = get_dsp_next_macro(from.z) & (~4); + } + return res; +} + +Loc GowinUtils::get_dsp_next_macro_in_chain(Loc from) const +{ + Loc res; + res.y = from.y; + if (get_dsp_macro_num(from.z)) { + // next DSP + res.x = from.x + 9; + res.z = from.z & (~0x20); + } else { + // next macro + res.x = from.x; + res.z = get_dsp_next_macro(from.z); + } + return res; +} + +Loc GowinUtils::get_dsp_next_in_chain(Loc from, IdString dsp_type) const +{ + if (dsp_type.in(id_PADD9, id_PADD18, id_MULT9X9, id_MULT18X18)) { + return get_dsp_next_9_in_chain(from); + } + if (dsp_type.in(id_ALU54D, id_MULTALU18X18, id_MULTALU36X18, id_MULTADDALU18X18)) { + return get_dsp_next_macro_in_chain(from); + } + NPNR_ASSERT_FALSE("Unknown DSP cell type."); +} + +CellInfo *GowinUtils::dsp_bus_src(const CellInfo *ci, const char *bus_prefix, int wire_num) const +{ + bool connected_to_const = false; // and disconnected too + CellInfo *connected_to_cell = nullptr; + + for (int i = 0; i < wire_num; ++i) { + const NetInfo *net = ci->getPort(ctx->idf("%s[%d]", bus_prefix, i)); + if (connected_to_cell == nullptr) { + if (net == nullptr || net->driver.cell == nullptr || net->name == ctx->id("$PACKER_VCC") || + net->name == ctx->id("$PACKER_GND")) { + connected_to_const = true; + continue; + } else { + if (connected_to_const) { + log_error("The %s cell %s bus is connected simultaneously to constants and to another DSP.\n", + ctx->nameOf(ci), bus_prefix); + } + } + } + if (net == nullptr || !is_dsp(net->driver.cell)) { + log_error("The %s cell %s bus is not connected to another DSP.\n", ctx->nameOf(ci), bus_prefix); + } + if (connected_to_cell != nullptr && net->driver.cell != connected_to_cell) { + log_error("The %s cell %s bus is connected to different DSPs: %s and %s.\n", ctx->nameOf(ci), bus_prefix, + ctx->nameOf(connected_to_cell), ctx->nameOf(net->driver.cell)); + } + connected_to_cell = net->driver.cell; + } + if (connected_to_const) { + return nullptr; + } + return connected_to_cell; +} + +CellInfo *GowinUtils::dsp_bus_dst(const CellInfo *ci, const char *bus_prefix, int wire_num) const +{ + bool disconnected = false; // and disconnected too + CellInfo *connected_to_cell = nullptr; + + for (int i = 0; i < wire_num; ++i) { + const NetInfo *net = ci->getPort(ctx->idf("%s[%d]", bus_prefix, i)); + if (connected_to_cell == nullptr) { + if (net == nullptr || net->users.entries() == 0) { + disconnected = true; + continue; + } else { + if (disconnected) { + log_error("The %s cell %s bus is partially disconnected.\n", ctx->nameOf(ci), bus_prefix); + } + } + } + if (net->users.entries() > 1) { + log_error("Net %s has >1 users.\n", ctx->nameOf(net)); + } + + CellInfo *dst = (*net->users.begin()).cell; + if (net == nullptr || !is_dsp(dst)) { + log_error("The %s cell %s bus is not connected to another DSP.\n", ctx->nameOf(ci), bus_prefix); + } + if (connected_to_cell != nullptr && dst != connected_to_cell) { + log_error("The %s cell %s bus is connected to different DSPs: %s and %s.\n", ctx->nameOf(ci), bus_prefix, + ctx->nameOf(connected_to_cell), ctx->nameOf(dst)); + } + connected_to_cell = dst; + } + if (disconnected) { + return nullptr; + } + return connected_to_cell; +} + NEXTPNR_NAMESPACE_END diff --git a/himbaechel/uarch/gowin/gowin_utils.h b/himbaechel/uarch/gowin/gowin_utils.h index d93c6029..ea466b11 100644 --- a/himbaechel/uarch/gowin/gowin_utils.h +++ b/himbaechel/uarch/gowin/gowin_utils.h @@ -31,6 +31,30 @@ struct GowinUtils Loc get_pair_iologic_bel(Loc loc); BelId get_io_bel_from_iologic(BelId bel); + // 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; } + inline int get_dsp_18_idx(int z) const { return z & 4; } + inline int get_dsp_paired_9(int z) const { return (3 - get_dsp_9_idx(z)) | (z & (~3)); } + inline int get_dsp_mult_from_padd(int padd_z) const { return padd_z + 8; } + inline int get_dsp_padd_from_mult(int mult_z) const { return mult_z - 8; } + inline int get_dsp_next_macro(int z) const { return z + 32; } + inline int get_dsp(int z) const { return BelZ::DSP_Z; } + inline int get_dsp_macro(int z) const { return (z & 0x20) + BelZ::DSP_0_Z; } + inline int get_dsp_macro_num(int z) const { return (z & 0x20) >> 5; } + Loc get_dsp_next_9_in_chain(Loc from) const; + Loc get_dsp_next_macro_in_chain(Loc from) const; + Loc get_dsp_next_in_chain(Loc from, IdString dsp_type) const; + + // check bus. + // This is necessary to find the head in the DSP chain - these buses are + // not switched in the hardware, but in software you can leave them + // unconnected or connect them to VCC or VSS, which is the same - as I + // already said, they are hard-wired and we are only discovering the fact + // that they are not connected to another DSP in the chain. + CellInfo *dsp_bus_src(const CellInfo *ci, const char *bus_prefix, int wire_num) const; + CellInfo *dsp_bus_dst(const CellInfo *ci, const char *bus_prefix, int wire_num) const; + bool is_diff_io_supported(IdString type); bool have_bottom_io_cnds(void); IdString get_bottom_io_wire_a_net(int8_t condition); diff --git a/himbaechel/uarch/gowin/pack.cc b/himbaechel/uarch/gowin/pack.cc index 35604882..ad85fce1 100644 --- a/himbaechel/uarch/gowin/pack.cc +++ b/himbaechel/uarch/gowin/pack.cc @@ -508,13 +508,13 @@ struct GowinPacker make_iob_nets(*out_iob); } - IdString create_aux_iologic_name(IdString main_name, int idx = 0) + IdString create_aux_name(IdString main_name, int idx = 0) { std::string sfx(""); if (idx) { sfx = std::to_string(idx); } - return ctx->id(main_name.str(ctx) + std::string("_aux") + sfx); + return ctx->id(main_name.str(ctx) + std::string("_aux$") + sfx); } BelId get_aux_iologic_bel(const CellInfo &ci) @@ -529,7 +529,7 @@ struct GowinPacker if (ci.type.in(id_ODDR, id_ODDRC, id_OSER4, id_IDDR, id_IDDRC, id_IDES4)) { return nullptr; } - IdString aux_name = create_aux_iologic_name(ci.name, idx); + IdString aux_name = create_aux_name(ci.name, idx); BelId bel = get_aux_iologic_bel(ci); BelId io_bel = gwu.get_io_bel_from_iologic(bel); if (!ctx->checkBelAvail(io_bel)) { @@ -720,9 +720,9 @@ struct GowinPacker // to simplify packaging, the parts of the OSER16 are presented as IOLOGIC cells // and one of these aux cells is declared as main - IdString main_name = create_aux_iologic_name(ci.name); + IdString main_name = create_aux_name(ci.name); - IdString aux_name = create_aux_iologic_name(ci.name, 1); + IdString aux_name = create_aux_name(ci.name, 1); ctx->createCell(aux_name, id_IOLOGIC_DUMMY); CellInfo *aux = ctx->cells.at(aux_name).get(); @@ -794,9 +794,9 @@ struct GowinPacker // to simplify packaging, the parts of the IDES16 are presented as IOLOGIC cells // and one of these aux cells is declared as main - IdString main_name = create_aux_iologic_name(ci.name); + IdString main_name = create_aux_name(ci.name); - IdString aux_name = create_aux_iologic_name(ci.name, 1); + IdString aux_name = create_aux_name(ci.name, 1); ctx->createCell(aux_name, id_IOLOGIC_DUMMY); CellInfo *aux = ctx->cells.at(aux_name).get(); @@ -1646,12 +1646,932 @@ struct GowinPacker } } + // =================================== + // DSP + // =================================== + void pass_net_type(CellInfo *ci, IdString port) + { + const NetInfo *net = ci->getPort(port); + std::string connected_net = "NET"; + if (net != nullptr) { + if (net->name == ctx->id("$PACKER_VCC")) { + connected_net = "VCC"; + } else if (net->name == ctx->id("$PACKER_GND")) { + connected_net = "GND"; + } + ci->setAttr(ctx->idf("NET_%s", port.c_str(ctx)), connected_net); + } else { + ci->setAttr(ctx->idf("NET_%s", port.c_str(ctx)), std::string("")); + } + } + + void pack_dsp(void) + { + std::vector> new_cells; + log_info("Pack DSP...\n"); + + std::vector dsp_heads; + + for (auto &cell : ctx->cells) { + auto ci = cell.second.get(); + if (is_dsp(ci)) { + if (ctx->verbose) { + log_info(" pack %s %s\n", ci->type.c_str(ctx), ctx->nameOf(ci)); + } + switch (ci->type.hash()) { + case ID_PADD9: { + pass_net_type(ci, id_ASEL); + for (int i = 0; i < 9; ++i) { + ci->renamePort(ctx->idf("A[%d]", i), ctx->idf("A%d", i)); + ci->renamePort(ctx->idf("B[%d]", i), ctx->idf("B%d", i)); + } + for (int i = 0; i < 9; ++i) { + ci->renamePort(ctx->idf("DOUT[%d]", i), ctx->idf("DOUT%d", i)); + } + + // ADD_SUB wire + IdString add_sub_net = ctx->id("$PACKER_GND"); + if (ci->params.count(ctx->id("ADD_SUB"))) { + if (ci->params.at(ctx->id("ADD_SUB")).as_int64() == 1) { + add_sub_net = ctx->id("$PACKER_VCC"); + } + } + ci->addInput(ctx->id("ADDSUB")); + ci->connectPort(ctx->id("ADDSUB"), ctx->nets.at(add_sub_net).get()); + + // PADD does not have outputs to the outside of the DSP - + // it is always connected to the inputs of the multiplier; + // to emulate a separate PADD primitive, we use + // multiplication by input C, equal to 1. We can switch the + // multiplier to multiplication mode by C in gowin_pack, + // but we will have to generate the value 1 at input C + // here. + ci->addInput(ctx->id("C0")); + ci->connectPort(ctx->id("C0"), ctx->nets.at(ctx->id("$PACKER_VCC")).get()); + for (int i = 1; i < 9; ++i) { + ci->addInput(ctx->idf("C%d", i)); + ci->connectPort(ctx->idf("C%d", i), ctx->nets.at(ctx->id("$PACKER_GND")).get()); + } + // mark mult9x9 as used by making cluster + ci->cluster = ci->name; + ci->constr_abs_z = false; + ci->constr_x = 0; + ci->constr_y = 0; + ci->constr_y = 0; + + IdString mult_name = create_aux_name(ci->name); + std::unique_ptr mult_cell = gwu.create_cell(mult_name, id_DUMMY_CELL); + new_cells.push_back(std::move(mult_cell)); + CellInfo *mult_ci = new_cells.back().get(); + + mult_ci->cluster = ci->name; + mult_ci->constr_x = 0; + mult_ci->constr_y = 0; + mult_ci->constr_z = gwu.get_dsp_mult_from_padd(0); + + // DSP head? + if (gwu.dsp_bus_src(ci, "SI", 9) == nullptr && gwu.dsp_bus_dst(ci, "SBO", 9) == nullptr) { + for (int i = 0; i < 9; ++i) { + ci->disconnectPort(ctx->idf("SI[%d]", i)); + ci->disconnectPort(ctx->idf("SBO[%d]", i)); + } + dsp_heads.push_back(ci); + if (ctx->verbose) { + log_info(" found a DSP head: %s\n", ctx->nameOf(ci)); + } + } + } break; + case ID_PADD18: { + pass_net_type(ci, id_ASEL); + for (int i = 0; i < 18; ++i) { + ci->renamePort(ctx->idf("A[%d]", i), ctx->idf("A%d", i)); + ci->renamePort(ctx->idf("B[%d]", i), ctx->idf("B%d", i)); + } + for (int i = 0; i < 18; ++i) { + ci->renamePort(ctx->idf("DOUT[%d]", i), ctx->idf("DOUT%d", i)); + } + + // ADD_SUB wire + IdString add_sub_net = ctx->id("$PACKER_GND"); + if (ci->params.count(ctx->id("ADD_SUB"))) { + if (ci->params.at(ctx->id("ADD_SUB")).as_int64() == 1) { + add_sub_net = ctx->id("$PACKER_VCC"); + } + } + ci->addInput(ctx->id("ADDSUB")); + ci->connectPort(ctx->id("ADDSUB"), ctx->nets.at(add_sub_net).get()); + + // XXX form C as 1 + ci->addInput(ctx->id("C0")); + ci->connectPort(ctx->id("C0"), ctx->nets.at(ctx->id("$PACKER_VCC")).get()); + for (int i = 1; i < 18; ++i) { + ci->addInput(ctx->idf("C%d", i)); + ci->connectPort(ctx->idf("C%d", i), ctx->nets.at(ctx->id("$PACKER_GND")).get()); + } + // + // add padd9s and mult9s as a children + ci->cluster = ci->name; + ci->constr_abs_z = false; + ci->constr_x = 0; + ci->constr_y = 0; + ci->constr_z = 0; + ci->constr_children.clear(); + + for (int i = 0; i < 2; ++i) { + IdString padd_name = create_aux_name(ci->name, i * 2); + std::unique_ptr padd_cell = gwu.create_cell(padd_name, id_DUMMY_CELL); + new_cells.push_back(std::move(padd_cell)); + CellInfo *padd_ci = new_cells.back().get(); + + padd_ci->cluster = ci->name; + padd_ci->constr_abs_z = false; + padd_ci->constr_x = 0; + padd_ci->constr_y = 0; + padd_ci->constr_z = BelZ::PADD9_0_0_Z - BelZ::PADD18_0_0_Z + i; + + IdString mult_name = create_aux_name(ci->name, i * 2 + 1); + std::unique_ptr mult_cell = gwu.create_cell(mult_name, id_DUMMY_CELL); + new_cells.push_back(std::move(mult_cell)); + CellInfo *mult_ci = new_cells.back().get(); + + mult_ci->cluster = ci->name; + mult_ci->constr_abs_z = false; + mult_ci->constr_x = 0; + mult_ci->constr_y = 0; + mult_ci->constr_z = BelZ::MULT9X9_0_0_Z - BelZ::PADD18_0_0_Z + i; + } + // DSP head? + if (gwu.dsp_bus_src(ci, "SI", 18) == nullptr && gwu.dsp_bus_dst(ci, "SBO", 18) == nullptr) { + for (int i = 0; i < 18; ++i) { + ci->disconnectPort(ctx->idf("SI[%d]", i)); + ci->disconnectPort(ctx->idf("SBO[%d]", i)); + } + dsp_heads.push_back(ci); + if (ctx->verbose) { + log_info(" found a DSP head: %s\n", ctx->nameOf(ci)); + } + } + } break; + case ID_MULT9X9: { + pass_net_type(ci, id_ASEL); + pass_net_type(ci, id_BSEL); + for (int i = 0; i < 9; ++i) { + ci->renamePort(ctx->idf("A[%d]", i), ctx->idf("A%d", i)); + ci->renamePort(ctx->idf("B[%d]", i), ctx->idf("B%d", i)); + } + for (int i = 0; i < 18; ++i) { + ci->renamePort(ctx->idf("DOUT[%d]", i), ctx->idf("DOUT%d", i)); + } + // add padd9 as a child + ci->cluster = ci->name; + ci->constr_abs_z = false; + ci->constr_x = 0; + ci->constr_y = 0; + ci->constr_z = 0; + ci->constr_children.clear(); + + IdString padd_name = create_aux_name(ci->name); + std::unique_ptr padd_cell = gwu.create_cell(padd_name, id_DUMMY_CELL); + new_cells.push_back(std::move(padd_cell)); + CellInfo *padd_ci = new_cells.back().get(); + + padd_ci->cluster = ci->name; + padd_ci->constr_abs_z = false; + padd_ci->constr_x = 0; + padd_ci->constr_y = 0; + padd_ci->constr_z = BelZ::PADD9_0_0_Z - BelZ::MULT9X9_0_0_Z; + + // DSP head? + if (gwu.dsp_bus_src(ci, "SIA", 9) == nullptr && gwu.dsp_bus_src(ci, "SIB", 9) == nullptr) { + for (int i = 0; i < 9; ++i) { + ci->disconnectPort(ctx->idf("SIA[%d]", i)); + ci->disconnectPort(ctx->idf("SIB[%d]", i)); + } + dsp_heads.push_back(ci); + if (ctx->verbose) { + log_info(" found a DSP head: %s\n", ctx->nameOf(ci)); + } + } + } break; + case ID_MULT18X18: { + pass_net_type(ci, id_ASEL); + pass_net_type(ci, id_BSEL); + for (int i = 0; i < 18; ++i) { + ci->renamePort(ctx->idf("A[%d]", i), ctx->idf("A%d", i)); + ci->renamePort(ctx->idf("B[%d]", i), ctx->idf("B%d", i)); + } + for (int i = 0; i < 36; ++i) { + ci->renamePort(ctx->idf("DOUT[%d]", i), ctx->idf("DOUT%d", i)); + } + // add padd9s and mult9s as a children + ci->cluster = ci->name; + ci->constr_abs_z = false; + ci->constr_x = 0; + ci->constr_y = 0; + ci->constr_z = 0; + ci->constr_children.clear(); + + for (int i = 0; i < 2; ++i) { + IdString padd_name = create_aux_name(ci->name, i * 2); + std::unique_ptr padd_cell = gwu.create_cell(padd_name, id_DUMMY_CELL); + new_cells.push_back(std::move(padd_cell)); + CellInfo *padd_ci = new_cells.back().get(); + + padd_ci->cluster = ci->name; + padd_ci->constr_abs_z = false; + padd_ci->constr_x = 0; + padd_ci->constr_y = 0; + padd_ci->constr_z = BelZ::PADD9_0_0_Z - BelZ::MULT18X18_0_0_Z + i; + + IdString mult_name = create_aux_name(ci->name, i * 2 + 1); + std::unique_ptr mult_cell = gwu.create_cell(mult_name, id_DUMMY_CELL); + new_cells.push_back(std::move(mult_cell)); + CellInfo *mult_ci = new_cells.back().get(); + + mult_ci->cluster = ci->name; + mult_ci->constr_abs_z = false; + mult_ci->constr_x = 0; + mult_ci->constr_y = 0; + mult_ci->constr_z = BelZ::MULT9X9_0_0_Z - BelZ::MULT18X18_0_0_Z + i; + } + + // DSP head? + if (gwu.dsp_bus_src(ci, "SIA", 18) == nullptr && gwu.dsp_bus_src(ci, "SIB", 18) == nullptr) { + for (int i = 0; i < 18; ++i) { + ci->disconnectPort(ctx->idf("SIA[%d]", i)); + ci->disconnectPort(ctx->idf("SIB[%d]", i)); + } + dsp_heads.push_back(ci); + if (ctx->verbose) { + log_info(" found a DSP head: %s\n", ctx->nameOf(ci)); + } + } + } break; + case ID_ALU54D: { + pass_net_type(ci, id_ACCLOAD); + for (int i = 0; i < 54; ++i) { + ci->renamePort(ctx->idf("A[%d]", i), ctx->idf("A%d", i)); + ci->renamePort(ctx->idf("B[%d]", i), ctx->idf("B%d", i)); + } + // ACCLOAD - It looks like these wires are always connected to each other. + ci->cell_bel_pins.at(id_ACCLOAD).clear(); + ci->cell_bel_pins.at(id_ACCLOAD).push_back(id_ACCLOAD0); + ci->cell_bel_pins.at(id_ACCLOAD).push_back(id_ACCLOAD1); + + for (int i = 0; i < 54; ++i) { + ci->renamePort(ctx->idf("DOUT[%d]", i), ctx->idf("DOUT%d", i)); + } + // add padd9s and mult9s as a children + ci->cluster = ci->name; + ci->constr_abs_z = false; + ci->constr_x = 0; + ci->constr_y = 0; + ci->constr_z = 0; + ci->constr_children.clear(); + + for (int i = 0; i < 4; ++i) { + IdString padd_name = create_aux_name(ci->name, i * 2); + std::unique_ptr padd_cell = gwu.create_cell(padd_name, id_DUMMY_CELL); + new_cells.push_back(std::move(padd_cell)); + CellInfo *padd_ci = new_cells.back().get(); + + padd_ci->cluster = ci->name; + padd_ci->constr_abs_z = false; + padd_ci->constr_x = 0; + padd_ci->constr_y = 0; + padd_ci->constr_z = BelZ::PADD9_0_0_Z - BelZ::ALU54D_0_Z + 4 * (i / 2) + (i % 2); + + IdString mult_name = create_aux_name(ci->name, i * 2 + 1); + std::unique_ptr mult_cell = gwu.create_cell(mult_name, id_DUMMY_CELL); + new_cells.push_back(std::move(mult_cell)); + CellInfo *mult_ci = new_cells.back().get(); + + mult_ci->cluster = ci->name; + mult_ci->constr_abs_z = false; + mult_ci->constr_x = 0; + mult_ci->constr_y = 0; + mult_ci->constr_z = BelZ::MULT9X9_0_0_Z - BelZ::ALU54D_0_Z + 4 * (i / 2) + (i % 2); + } + + // DSP head? + if (gwu.dsp_bus_src(ci, "CASI", 55) == nullptr) { + for (int i = 0; i < 55; ++i) { + ci->disconnectPort(ctx->idf("CASI[%d]", i)); + } + dsp_heads.push_back(ci); + if (ctx->verbose) { + log_info(" found a DSP head: %s\n", ctx->nameOf(ci)); + } + } + } break; + case ID_MULTALU18X18: { + // Ports C and D conflict so we need to know the operating mode here. + if (ci->params.count(id_MULTALU18X18_MODE) == 0) { + ci->setParam(id_MULTALU18X18_MODE, 0); + } + int multalu18x18_mode = ci->params.at(id_MULTALU18X18_MODE).as_int64(); + NPNR_ASSERT_MSG(multalu18x18_mode >= 0 && multalu18x18_mode <= 2, + "MULTALU18X18_MODE is not in {0, 1, 2}"); + NetInfo *vss_net = ctx->nets.at(ctx->id("$PACKER_GND")).get(); + + for (int i = 0; i < 54; ++i) { + if (i < 18) { + if (multalu18x18_mode != 2) { + ci->renamePort(ctx->idf("A[%d]", i), ctx->idf("A%d1", i)); + ci->renamePort(ctx->idf("B[%d]", i), ctx->idf("B%d1", i)); + } else { + ci->renamePort(ctx->idf("A[%d]", i), ctx->idf("A%d0", i)); + ci->renamePort(ctx->idf("B[%d]", i), ctx->idf("B%d0", i)); + } + } + switch (multalu18x18_mode) { + case 0: + ci->renamePort(ctx->idf("C[%d]", i), ctx->idf("C%d", i)); + ci->disconnectPort(ctx->idf("D[%d]", i)); + break; + case 1: + ci->disconnectPort(ctx->idf("C[%d]", i)); + ci->disconnectPort(ctx->idf("D[%d]", i)); + break; + case 2: + ci->disconnectPort(ctx->idf("C[%d]", i)); + ci->renamePort(ctx->idf("D[%d]", i), ctx->idf("D%d", i)); + break; + default: + break; + } + } + if (multalu18x18_mode != 2) { + ci->renamePort(id_ASIGN, id_ASIGN1); + ci->renamePort(id_BSIGN, id_BSIGN1); + ci->addInput(id_ASIGN0); + ci->addInput(id_BSIGN0); + ci->connectPort(id_ASIGN0, vss_net); + ci->connectPort(id_BSIGN0, vss_net); + ci->disconnectPort(id_DSIGN); + } else { // BSIGN0 and DSIGN are the same wire + ci->renamePort(id_ASIGN, id_ASIGN0); + ci->addInput(id_ASIGN1); + ci->connectPort(id_ASIGN1, vss_net); + ci->renamePort(id_BSIGN, id_BSIGN0); + } + + // ACCLOAD - It looks like these wires are always connected to each other. + pass_net_type(ci, id_ACCLOAD); + ci->cell_bel_pins.at(id_ACCLOAD).clear(); + ci->cell_bel_pins.at(id_ACCLOAD).push_back(id_ACCLOAD0); + ci->cell_bel_pins.at(id_ACCLOAD).push_back(id_ACCLOAD1); + + for (int i = 0; i < 54; ++i) { + ci->renamePort(ctx->idf("DOUT[%d]", i), ctx->idf("DOUT%d", i)); + } + + // add padd9s and mult9s as a children + ci->cluster = ci->name; + ci->constr_abs_z = false; + ci->constr_x = 0; + ci->constr_y = 0; + ci->constr_z = 0; + ci->constr_children.clear(); + + for (int i = 0; i < 2; ++i) { + IdString padd_name = create_aux_name(ci->name, i * 2); + std::unique_ptr padd_cell = gwu.create_cell(padd_name, id_DUMMY_CELL); + new_cells.push_back(std::move(padd_cell)); + CellInfo *padd_ci = new_cells.back().get(); + + padd_ci->cluster = ci->name; + padd_ci->constr_abs_z = false; + padd_ci->constr_x = 0; + padd_ci->constr_y = 0; + padd_ci->constr_z = BelZ::PADD9_0_0_Z - BelZ::MULTALU18X18_0_Z + i; + + IdString mult_name = create_aux_name(ci->name, i * 2 + 1); + std::unique_ptr mult_cell = gwu.create_cell(mult_name, id_DUMMY_CELL); + new_cells.push_back(std::move(mult_cell)); + CellInfo *mult_ci = new_cells.back().get(); + + mult_ci->cluster = ci->name; + mult_ci->constr_abs_z = false; + mult_ci->constr_x = 0; + mult_ci->constr_y = 0; + mult_ci->constr_z = BelZ::MULT9X9_0_0_Z - BelZ::MULTALU18X18_0_Z + i; + } + + // DSP head? + if (gwu.dsp_bus_src(ci, "CASI", 55) == nullptr) { + for (int i = 0; i < 55; ++i) { + ci->disconnectPort(ctx->idf("CASI[%d]", i)); + } + dsp_heads.push_back(ci); + if (ctx->verbose) { + log_info(" found a DSP head: %s\n", ctx->nameOf(ci)); + } + } + } break; + case ID_MULTALU36X18: { + if (ci->params.count(id_MULTALU18X18_MODE) == 0) { + ci->setParam(id_MULTALU18X18_MODE, 0); + } + int multalu36x18_mode = ci->params.at(id_MULTALU36X18_MODE).as_int64(); + NPNR_ASSERT_MSG(multalu36x18_mode >= 0 && multalu36x18_mode <= 2, + "MULTALU36X18_MODE is not in {0, 1, 2}"); + NetInfo *vss_net = ctx->nets.at(ctx->id("$PACKER_GND")).get(); + + for (int i = 0; i < 36; ++i) { + if (i < 18) { + ci->cell_bel_pins.at(ctx->idf("A[%d]", i)).clear(); + ci->cell_bel_pins.at(ctx->idf("A[%d]", i)).push_back(ctx->idf("A%d0", i)); + ci->cell_bel_pins.at(ctx->idf("A[%d]", i)).push_back(ctx->idf("A%d1", i)); + } + ci->renamePort(ctx->idf("B[%d]", i), ctx->idf("B%d", i)); + } + for (int i = 0; i < 54; ++i) { + switch (multalu36x18_mode) { + case 0: + ci->renamePort(ctx->idf("C[%d]", i), ctx->idf("C%d", i)); + break; + case 1: /* fallthrough */ + case 2: + ci->disconnectPort(ctx->idf("C[%d]", i)); + break; + default: + break; + } + } + + // both A have sign bit + // only MSB part of B has sign bit + ci->cell_bel_pins.at(id_ASIGN).clear(); + ci->cell_bel_pins.at(id_ASIGN).push_back(id_ASIGN0); + ci->cell_bel_pins.at(id_ASIGN).push_back(id_ASIGN1); + ci->renamePort(id_BSIGN, id_BSIGN1); + ci->addInput(id_BSIGN0); + ci->connectPort(id_BSIGN0, vss_net); + + pass_net_type(ci, id_ACCLOAD); + if (multalu36x18_mode == 1) { + if (ci->attrs.at(id_NET_ACCLOAD).as_string() == "GND" || + ci->attrs.at(id_NET_ACCLOAD).as_string() == "VCC") { + ci->disconnectPort(id_ACCLOAD); + } else { + ci->addInput(id_ALUSEL4); + ci->addInput(id_ALUSEL6); + ci->cell_bel_pins.at(id_ACCLOAD).clear(); + ci->cell_bel_pins.at(id_ACCLOAD).push_back(id_ALUSEL4); + ci->cell_bel_pins.at(id_ACCLOAD).push_back(id_ALUSEL6); + } + } else { + ci->disconnectPort(id_ACCLOAD); + } + + for (int i = 0; i < 54; ++i) { + ci->renamePort(ctx->idf("DOUT[%d]", i), ctx->idf("DOUT%d", i)); + } + + // add padd9s and mult9s as a children + ci->cluster = ci->name; + ci->constr_abs_z = false; + ci->constr_x = 0; + ci->constr_y = 0; + ci->constr_z = 0; + ci->constr_children.clear(); + + for (int i = 0; i < 2; ++i) { + IdString padd_name = create_aux_name(ci->name, i * 2); + std::unique_ptr padd_cell = gwu.create_cell(padd_name, id_DUMMY_CELL); + new_cells.push_back(std::move(padd_cell)); + CellInfo *padd_ci = new_cells.back().get(); + + padd_ci->cluster = ci->name; + padd_ci->constr_abs_z = false; + padd_ci->constr_x = 0; + padd_ci->constr_y = 0; + padd_ci->constr_z = BelZ::PADD9_0_0_Z - BelZ::MULTALU36X18_0_Z + i; + + IdString mult_name = create_aux_name(ci->name, i * 2 + 1); + std::unique_ptr mult_cell = gwu.create_cell(mult_name, id_DUMMY_CELL); + new_cells.push_back(std::move(mult_cell)); + CellInfo *mult_ci = new_cells.back().get(); + + mult_ci->cluster = ci->name; + mult_ci->constr_abs_z = false; + mult_ci->constr_x = 0; + mult_ci->constr_y = 0; + mult_ci->constr_z = BelZ::MULT9X9_0_0_Z - BelZ::MULTALU36X18_0_Z + i; + } + + // DSP head? + if (gwu.dsp_bus_src(ci, "CASI", 55) == nullptr) { + for (int i = 0; i < 55; ++i) { + ci->disconnectPort(ctx->idf("CASI[%d]", i)); + } + dsp_heads.push_back(ci); + if (ctx->verbose) { + log_info(" found a DSP head: %s\n", ctx->nameOf(ci)); + } + } + } break; + case ID_MULTADDALU18X18: { + if (ci->params.count(id_MULTADDALU18X18_MODE) == 0) { + ci->setParam(id_MULTADDALU18X18_MODE, 0); + } + int multaddalu18x18_mode = ci->params.at(id_MULTADDALU18X18_MODE).as_int64(); + NPNR_ASSERT_MSG(multaddalu18x18_mode >= 0 && multaddalu18x18_mode <= 2, + "MULTADDALU18X18_MODE is not in {0, 1, 2}"); + for (int i = 0; i < 54; ++i) { + if (i < 18) { + ci->renamePort(ctx->idf("A0[%d]", i), ctx->idf("A%d0", i)); + ci->renamePort(ctx->idf("B0[%d]", i), ctx->idf("B%d0", i)); + ci->renamePort(ctx->idf("A1[%d]", i), ctx->idf("A%d1", i)); + ci->renamePort(ctx->idf("B1[%d]", i), ctx->idf("B%d1", i)); + } + if (multaddalu18x18_mode == 0) { + ci->renamePort(ctx->idf("C[%d]", i), ctx->idf("C%d", i)); + } else { + ci->disconnectPort(ctx->idf("C[%d]", i)); + } + } + for (int i = 0; i < 2; ++i) { + ci->renamePort(ctx->idf("ASIGN[%d]", i), ctx->idf("ASIGN%d", i)); + ci->renamePort(ctx->idf("BSIGN[%d]", i), ctx->idf("BSIGN%d", i)); + ci->renamePort(ctx->idf("ASEL[%d]", i), ctx->idf("ASEL%d", i)); + ci->renamePort(ctx->idf("BSEL[%d]", i), ctx->idf("BSEL%d", i)); + } + + pass_net_type(ci, id_ASEL0); + pass_net_type(ci, id_ASEL1); + pass_net_type(ci, id_BSEL0); + pass_net_type(ci, id_BSEL1); + pass_net_type(ci, id_ACCLOAD); + if (multaddalu18x18_mode == 1) { + if (ci->attrs.at(id_NET_ACCLOAD).as_string() == "GND" || + ci->attrs.at(id_NET_ACCLOAD).as_string() == "VCC") { + ci->disconnectPort(id_ACCLOAD); + } else { + ci->addInput(id_ALUSEL4); + ci->addInput(id_ALUSEL6); + ci->cell_bel_pins.at(id_ACCLOAD).clear(); + ci->cell_bel_pins.at(id_ACCLOAD).push_back(id_ALUSEL4); + ci->cell_bel_pins.at(id_ACCLOAD).push_back(id_ALUSEL6); + } + } else { + ci->disconnectPort(id_ACCLOAD); + } + + for (int i = 0; i < 54; ++i) { + ci->renamePort(ctx->idf("DOUT[%d]", i), ctx->idf("DOUT%d", i)); + } + + // add padd9s and mult9s as a children + ci->cluster = ci->name; + ci->constr_abs_z = false; + ci->constr_x = 0; + ci->constr_y = 0; + ci->constr_z = 0; + ci->constr_children.clear(); + + for (int i = 0; i < 2; ++i) { + IdString padd_name = create_aux_name(ci->name, i * 2); + std::unique_ptr padd_cell = gwu.create_cell(padd_name, id_DUMMY_CELL); + new_cells.push_back(std::move(padd_cell)); + CellInfo *padd_ci = new_cells.back().get(); + + padd_ci->cluster = ci->name; + padd_ci->constr_abs_z = false; + padd_ci->constr_x = 0; + padd_ci->constr_y = 0; + padd_ci->constr_z = BelZ::PADD9_0_0_Z - BelZ::MULTADDALU18X18_0_Z + i; + + IdString mult_name = create_aux_name(ci->name, i * 2 + 1); + std::unique_ptr mult_cell = gwu.create_cell(mult_name, id_DUMMY_CELL); + new_cells.push_back(std::move(mult_cell)); + CellInfo *mult_ci = new_cells.back().get(); + + mult_ci->cluster = ci->name; + mult_ci->constr_abs_z = false; + mult_ci->constr_x = 0; + mult_ci->constr_y = 0; + mult_ci->constr_z = BelZ::MULT9X9_0_0_Z - BelZ::MULTADDALU18X18_0_Z + i; + } + + // DSP head? This primitive has the ability to form chains using both SO[AB] -> SI[AB] and + // CASO->CASI + bool cas_head = false; + if (gwu.dsp_bus_src(ci, "CASI", 55) == nullptr) { + for (int i = 0; i < 55; ++i) { + ci->disconnectPort(ctx->idf("CASI[%d]", i)); + } + cas_head = true; + } + bool so_head = false; + if (gwu.dsp_bus_src(ci, "SIA", 18) == nullptr && gwu.dsp_bus_src(ci, "SIB", 18) == nullptr) { + for (int i = 0; i < 18; ++i) { + ci->disconnectPort(ctx->idf("SIA[%d]", i)); + ci->disconnectPort(ctx->idf("SIB[%d]", i)); + } + so_head = true; + } + if (cas_head && so_head) { + dsp_heads.push_back(ci); + if (ctx->verbose) { + log_info(" found a DSP head: %s\n", ctx->nameOf(ci)); + } + } + } break; + case ID_MULT36X36: { + for (int i = 0; i < 36; ++i) { + ci->cell_bel_pins.at(ctx->idf("A[%d]", i)).clear(); + ci->cell_bel_pins.at(ctx->idf("A[%d]", i)).push_back(ctx->idf("A%d0", i)); + ci->cell_bel_pins.at(ctx->idf("A[%d]", i)).push_back(ctx->idf("A%d1", i)); + ci->cell_bel_pins.at(ctx->idf("B[%d]", i)).clear(); + ci->cell_bel_pins.at(ctx->idf("B[%d]", i)).push_back(ctx->idf("B%d0", i)); + ci->cell_bel_pins.at(ctx->idf("B[%d]", i)).push_back(ctx->idf("B%d1", i)); + } + ci->cell_bel_pins.at(id_ASIGN).clear(); + ci->cell_bel_pins.at(id_ASIGN).push_back(id_ASIGN0); + ci->cell_bel_pins.at(id_ASIGN).push_back(id_ASIGN1); + ci->cell_bel_pins.at(id_BSIGN).clear(); + ci->cell_bel_pins.at(id_BSIGN).push_back(id_BSIGN0); + ci->cell_bel_pins.at(id_BSIGN).push_back(id_BSIGN1); + + for (int i = 0; i < 72; ++i) { + ci->renamePort(ctx->idf("DOUT[%d]", i), ctx->idf("DOUT%d", i)); + } + + // add padd9s and mult9s as a children + ci->cluster = ci->name; + ci->constr_abs_z = false; + ci->constr_x = 0; + ci->constr_y = 0; + ci->constr_z = 0; + ci->constr_children.clear(); + + for (int i = 0; i < 8; ++i) { + IdString padd_name = create_aux_name(ci->name, i * 2); + std::unique_ptr padd_cell = gwu.create_cell(padd_name, id_DUMMY_CELL); + new_cells.push_back(std::move(padd_cell)); + CellInfo *padd_ci = new_cells.back().get(); + + static int padd_z[] = {BelZ::PADD9_0_0_Z, BelZ::PADD9_0_2_Z, BelZ::PADD9_1_0_Z, + BelZ::PADD9_1_2_Z}; + padd_ci->cluster = ci->name; + padd_ci->constr_abs_z = false; + padd_ci->constr_x = 0; + padd_ci->constr_y = 0; + padd_ci->constr_z = padd_z[i / 2] - BelZ::MULT36X36_Z + i % 2; + + IdString mult_name = create_aux_name(ci->name, i * 2 + 1); + std::unique_ptr mult_cell = gwu.create_cell(mult_name, id_DUMMY_CELL); + new_cells.push_back(std::move(mult_cell)); + CellInfo *mult_ci = new_cells.back().get(); + + static int mult_z[] = {BelZ::MULT9X9_0_0_Z, BelZ::MULT9X9_0_2_Z, BelZ::MULT9X9_1_0_Z, + BelZ::MULT9X9_1_2_Z}; + mult_ci->cluster = ci->name; + mult_ci->constr_abs_z = false; + mult_ci->constr_x = 0; + mult_ci->constr_y = 0; + mult_ci->constr_z = mult_z[i / 2] - BelZ::MULT36X36_Z + i % 2; + } + } break; + default: + log_error("Unsupported DSP type '%s'\n", ci->type.c_str(ctx)); + } + } + } + + // add new cells + for (auto &cell : new_cells) { + if (cell->cluster != ClusterId()) { + IdString cluster_root = cell->cluster; + IdString cell_name = cell->name; + ctx->cells[cell_name] = std::move(cell); + ctx->cells.at(cluster_root).get()->constr_children.push_back(ctx->cells.at(cell_name).get()); + } else { + ctx->cells[cell->name] = std::move(cell); + } + } + + // DSP chains + for (CellInfo *head : dsp_heads) { + if (ctx->verbose) { + log_info("Process a DSP head: %s\n", ctx->nameOf(head)); + } + switch (head->type.hash()) { + case ID_PADD9: /* fallthrough */ + case ID_PADD18: { + int wire_num = 9; + if (head->type == id_PADD18) { + wire_num = 18; + } + + CellInfo *cur_dsp = head; + while (1) { + CellInfo *next_dsp_a = gwu.dsp_bus_dst(cur_dsp, "SO", wire_num); + CellInfo *next_dsp_b = gwu.dsp_bus_src(cur_dsp, "SBI", wire_num); + if (next_dsp_a != nullptr && next_dsp_b != nullptr && next_dsp_a != next_dsp_b) { + log_error("%s is the next for two different DSPs (%s and %s) in the chain.", + ctx->nameOf(cur_dsp), ctx->nameOf(next_dsp_a), ctx->nameOf(next_dsp_b)); + } + if (next_dsp_a == nullptr && next_dsp_b == nullptr) { + // End of chain + cur_dsp->setAttr(id_LAST_IN_CHAIN, 1); + for (int i = 0; i < wire_num; ++i) { + cur_dsp->disconnectPort(ctx->idf("SO[%d]", i)); + cur_dsp->disconnectPort(ctx->idf("SBI[%d]", i)); + } + break; + } + + next_dsp_a = next_dsp_a != nullptr ? next_dsp_a : next_dsp_b; + for (int i = 0; i < wire_num; ++i) { + cur_dsp->disconnectPort(ctx->idf("SO[%d]", i)); + cur_dsp->disconnectPort(ctx->idf("SBI[%d]", i)); + next_dsp_a->disconnectPort(ctx->idf("SI[%d]", i)); + next_dsp_a->disconnectPort(ctx->idf("SBO[%d]", i)); + } + cur_dsp = next_dsp_a; + if (ctx->verbose) { + log_info(" add %s to the chain.\n", ctx->nameOf(cur_dsp)); + } + if (head->cluster == ClusterId()) { + head->cluster = head->name; + } + cur_dsp->cluster = head->name; + head->constr_children.push_back(cur_dsp); + for (auto child : cur_dsp->constr_children) { + child->cluster = head->name; + head->constr_children.push_back(child); + } + cur_dsp->constr_children.clear(); + } + } break; + case ID_MULT9X9: /* fallthrough */ + case ID_MULT18X18: { + int wire_num = 9; + if (head->type == id_MULT18X18) { + wire_num = 18; + } + + CellInfo *cur_dsp = head; + while (1) { + CellInfo *next_dsp_a = gwu.dsp_bus_dst(cur_dsp, "SOA", wire_num); + CellInfo *next_dsp_b = gwu.dsp_bus_dst(cur_dsp, "SOB", wire_num); + if (next_dsp_a != nullptr && next_dsp_b != nullptr && next_dsp_a != next_dsp_b) { + log_error("%s is the source for two different DSPs (%s and %s) in the chain.", + ctx->nameOf(cur_dsp), ctx->nameOf(next_dsp_a), ctx->nameOf(next_dsp_b)); + } + if (next_dsp_a == nullptr && next_dsp_b == nullptr) { + // End of chain + for (int i = 0; i < wire_num; ++i) { + cur_dsp->disconnectPort(ctx->idf("SOA[%d]", i)); + cur_dsp->disconnectPort(ctx->idf("SOB[%d]", i)); + } + break; + } + + next_dsp_a = next_dsp_a != nullptr ? next_dsp_a : next_dsp_b; + for (int i = 0; i < wire_num; ++i) { + cur_dsp->disconnectPort(ctx->idf("SOA[%d]", i)); + cur_dsp->disconnectPort(ctx->idf("SOB[%d]", i)); + next_dsp_a->disconnectPort(ctx->idf("SIA[%d]", i)); + next_dsp_a->disconnectPort(ctx->idf("SIB[%d]", i)); + } + cur_dsp = next_dsp_a; + if (ctx->verbose) { + log_info(" add %s to the chain.\n", ctx->nameOf(cur_dsp)); + } + if (head->cluster == ClusterId()) { + head->cluster = head->name; + } + cur_dsp->cluster = head->name; + head->constr_children.push_back(cur_dsp); + for (auto child : cur_dsp->constr_children) { + child->cluster = head->name; + head->constr_children.push_back(child); + } + cur_dsp->constr_children.clear(); + } + } break; + case ID_MULTALU18X18: /* fallthrough */ + case ID_MULTALU36X18: /* fallthrough */ + case ID_ALU54D: { + int wire_num = 55; + CellInfo *cur_dsp = head; + while (1) { + CellInfo *next_dsp_a = gwu.dsp_bus_dst(cur_dsp, "CASO", wire_num); + if (next_dsp_a == nullptr) { + // End of chain + for (int i = 0; i < wire_num; ++i) { + cur_dsp->disconnectPort(ctx->idf("CASO[%d]", i)); + } + break; + } + for (int i = 0; i < wire_num; ++i) { + cur_dsp->disconnectPort(ctx->idf("CASO[%d]", i)); + next_dsp_a->disconnectPort(ctx->idf("CASI[%d]", i)); + } + cur_dsp->setAttr(id_USE_CASCADE_OUT, 1); + cur_dsp = next_dsp_a; + cur_dsp->setAttr(id_USE_CASCADE_IN, 1); + if (ctx->verbose) { + log_info(" add %s to the chain.\n", ctx->nameOf(cur_dsp)); + } + if (head->cluster == ClusterId()) { + head->cluster = head->name; + } + cur_dsp->cluster = head->name; + head->constr_children.push_back(cur_dsp); + for (auto child : cur_dsp->constr_children) { + child->cluster = head->name; + head->constr_children.push_back(child); + } + cur_dsp->constr_children.clear(); + } + } break; + case ID_MULTADDALU18X18: { + // This primitive has the ability to form chains using both SO[AB] -> SI[AB] and CASO->CASI + CellInfo *cur_dsp = head; + while (1) { + bool end_of_cas_chain = false; + int wire_num = 55; + CellInfo *next_dsp_a = gwu.dsp_bus_dst(cur_dsp, "CASO", wire_num); + if (next_dsp_a == nullptr) { + // End of CASO chain + for (int i = 0; i < wire_num; ++i) { + cur_dsp->disconnectPort(ctx->idf("CASO[%d]", i)); + } + end_of_cas_chain = true; + } else { + for (int i = 0; i < wire_num; ++i) { + cur_dsp->disconnectPort(ctx->idf("CASO[%d]", i)); + next_dsp_a->disconnectPort(ctx->idf("CASI[%d]", i)); + } + } + + bool end_of_so_chain = false; + wire_num = 18; + CellInfo *next_so_dsp_a = gwu.dsp_bus_dst(cur_dsp, "SOA", wire_num); + CellInfo *next_so_dsp_b = gwu.dsp_bus_dst(cur_dsp, "SOB", wire_num); + if (next_so_dsp_a != nullptr && next_so_dsp_b != nullptr && next_so_dsp_a != next_so_dsp_b) { + log_error("%s is the source for two different DSPs (%s and %s) in the chain.", + ctx->nameOf(cur_dsp), ctx->nameOf(next_so_dsp_a), ctx->nameOf(next_so_dsp_b)); + } + if (next_so_dsp_a == nullptr && next_so_dsp_b == nullptr) { + // End of SO chain + for (int i = 0; i < wire_num; ++i) { + cur_dsp->disconnectPort(ctx->idf("SOA[%d]", i)); + cur_dsp->disconnectPort(ctx->idf("SOB[%d]", i)); + } + end_of_so_chain = true; + } else { + next_so_dsp_a = next_so_dsp_a != nullptr ? next_so_dsp_a : next_so_dsp_b; + for (int i = 0; i < wire_num; ++i) { + cur_dsp->disconnectPort(ctx->idf("SOA[%d]", i)); + cur_dsp->disconnectPort(ctx->idf("SOB[%d]", i)); + next_so_dsp_a->disconnectPort(ctx->idf("SIA[%d]", i)); + next_so_dsp_a->disconnectPort(ctx->idf("SIB[%d]", i)); + } + } + if (end_of_cas_chain && end_of_so_chain) { + break; + } + + // to next + if (!end_of_cas_chain) { + cur_dsp->setAttr(id_USE_CASCADE_OUT, 1); + } + cur_dsp = next_dsp_a != nullptr ? next_dsp_a : next_so_dsp_a; + if (!end_of_cas_chain) { + cur_dsp->setAttr(id_USE_CASCADE_IN, 1); + } + if (ctx->verbose) { + log_info(" add %s to the chain. End of the SO chain:%d, end of the CAS chain:%d\n", + ctx->nameOf(cur_dsp), end_of_so_chain, end_of_cas_chain); + } + if (head->cluster == ClusterId()) { + head->cluster = head->name; + } + cur_dsp->cluster = head->name; + head->constr_children.push_back(cur_dsp); + for (auto child : cur_dsp->constr_children) { + child->cluster = head->name; + head->constr_children.push_back(child); + } + cur_dsp->constr_children.clear(); + } + } break; + } + } + } + // =================================== // Global set/reset // =================================== void pack_gsr(void) { - log_info("Pack GSR..\n"); + log_info("Pack GSR...\n"); bool user_gsr = false; for (auto &cell : ctx->cells) { @@ -1683,7 +2603,7 @@ struct GowinPacker // =================================== void pack_inv(void) { - log_info("Pack INV..\n"); + log_info("Pack INV...\n"); for (auto &cell : ctx->cells) { auto &ci = *cell.second; @@ -1702,7 +2622,7 @@ struct GowinPacker // =================================== void pack_pll(void) { - log_info("Pack PLL..\n"); + log_info("Pack PLL...\n"); for (auto &cell : ctx->cells) { auto &ci = *cell.second; @@ -1728,7 +2648,7 @@ struct GowinPacker // ========================================= void pack_buffered_nets() { - log_info("Pack buffered nets..\n"); + log_info("Pack buffered nets...\n"); for (auto &net : ctx->nets) { auto &ni = *net.second; @@ -1796,6 +2716,9 @@ struct GowinPacker pack_bsram(); ctx->check(); + pack_dsp(); + ctx->check(); + pack_buffered_nets(); ctx->fixupHierarchy();