diff --git a/himbaechel/uarch/gowin/constids.inc b/himbaechel/uarch/gowin/constids.inc index 88c98fb9..055c6dc2 100644 --- a/himbaechel/uarch/gowin/constids.inc +++ b/himbaechel/uarch/gowin/constids.inc @@ -1313,6 +1313,16 @@ X(LSREN) // EMCU X(EMCU) +// MIPI +X(IL) +X(OH) +X(OL) +X(OENB) +X(MIPI_IBUF) +X(MIPI_OBUF) +X(MIPI_OBUF_A) +X(MODESEL) + // Register placement options X(IREG_IN_IOB) X(OREG_IN_IOB) diff --git a/himbaechel/uarch/gowin/globals.cc b/himbaechel/uarch/gowin/globals.cc index 3a91752c..f71af38c 100644 --- a/himbaechel/uarch/gowin/globals.cc +++ b/himbaechel/uarch/gowin/globals.cc @@ -243,6 +243,10 @@ struct GowinGlobalRouter bool driver_is_dqce(const PortRef &driver) { return CellTypePort(driver) == CellTypePort(id_DQCE, id_CLKOUT); } bool driver_is_dcs(const PortRef &driver) { return CellTypePort(driver) == CellTypePort(id_DCS, id_CLKOUT); } bool driver_is_dhcen(const PortRef &driver) { return CellTypePort(driver) == CellTypePort(id_DHCEN, id_CLKOUT); } + bool driver_is_mipi(const PortRef &driver) + { + return CellTypePort(driver) == CellTypePort(id_IOBUF, id_O) && driver.cell->params.count(id_MIPI_IBUF); + } bool driver_is_clksrc(const PortRef &driver) { // dedicated pins @@ -506,7 +510,7 @@ struct GowinGlobalRouter NPNR_ASSERT(net_before_dhcen != nullptr); PortRef driver = net_before_dhcen->driver; - NPNR_ASSERT_MSG(driver_is_buf(driver) || driver_is_clksrc(driver), + NPNR_ASSERT_MSG(driver_is_buf(driver) || driver_is_clksrc(driver) || driver_is_mipi(driver), stringf("The input source for %s is not a clock.", ctx->nameOf(dhcen_ci)).c_str()); IdString port; @@ -519,8 +523,14 @@ struct GowinGlobalRouter WireId src = ctx->getBelPinWire(driver.cell->bel, port); std::vector path; - RouteResult route_result = route_direct_net( - net, [&](PipId pip, WireId src_wire) { return global_pip_filter(pip, src); }, src, &path); + RouteResult route_result; + if (driver_is_mipi(driver)) { + route_result = route_direct_net(net, [&](PipId pip, WireId src_wire) { return true; }, src, &path); + } else { + route_result = route_direct_net( + net, [&](PipId pip, WireId src_wire) { return global_pip_filter(pip, src); }, src, &path); + } + if (route_result == NOT_ROUTED) { log_error("Can't route the %s network.\n", ctx->nameOf(net)); } @@ -557,6 +567,9 @@ struct GowinGlobalRouter hw_dhcen->setAttr(id_DHCEN_USED, 1); dhcen_ci->copyPortTo(id_CE, hw_dhcen, id_CE); } + if (driver_is_mipi(driver)) { + ctx->bindWire(src, net_before_dhcen, STRENGTH_LOCKED); + } // connect all users to upper level net std::vector users; diff --git a/himbaechel/uarch/gowin/gowin.cc b/himbaechel/uarch/gowin/gowin.cc index 1f163034..aa70669e 100644 --- a/himbaechel/uarch/gowin/gowin.cc +++ b/himbaechel/uarch/gowin/gowin.cc @@ -1,5 +1,5 @@ -#include #include +#include #include "himbaechel_api.h" #include "himbaechel_helpers.h" @@ -110,7 +110,8 @@ struct GowinArch : HimbaechelArch bool match_device(const std::string &device) override { return device.size() > 2 && device.substr(0, 2) == "GW"; } - std::unique_ptr create(const std::string &device, const dict &args) override + std::unique_ptr create(const std::string &device, + const dict &args) override { return std::make_unique(); } @@ -639,6 +640,9 @@ IdString GowinImpl::getBelBucketForCellType(IdString cell_type) const if (cell_type.in(id_IBUF, id_OBUF)) { return id_IOB; } + if (cell_type.in(id_MIPI_OBUF, id_MIPI_OBUF_A)) { + return id_MIPI_OBUF; + } if (type_is_lut(cell_type)) { return id_LUT4; } @@ -676,6 +680,9 @@ bool GowinImpl::isValidBelForCellType(IdString cell_type, BelId bel) const if (bel_type == id_IOB) { return cell_type.in(id_IBUF, id_OBUF); } + if (bel_type == id_MIPI_OBUF) { + return cell_type.in(id_MIPI_OBUF, id_MIPI_OBUF_A); + } if (bel_type == id_LUT4) { return type_is_lut(cell_type); } diff --git a/himbaechel/uarch/gowin/gowin.h b/himbaechel/uarch/gowin/gowin.h index 05bfaa30..c9a0781b 100644 --- a/himbaechel/uarch/gowin/gowin.h +++ b/himbaechel/uarch/gowin/gowin.h @@ -32,6 +32,10 @@ inline bool type_is_diffio(IdString cell_type) } inline bool is_diffio(const CellInfo *cell) { return type_is_diffio(cell->type); } +// MIPI +inline bool type_is_mipi(IdString cell_type) { return cell_type.in(id_MIPI_OBUF, id_MIPI_OBUF_A, id_MIPI_IBUF); } +inline bool is_mipi(const CellInfo *cell) { return type_is_mipi(cell->type); } + // IOLOGIC input and output separately inline bool type_is_iologico(IdString cell_type) @@ -199,6 +203,9 @@ enum EMCU_Z = 300, + MIPIOBUF_Z = 301, + MIPIIBUF_Z = 302, + // 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 2cf743e9..9bd27e00 100644 --- a/himbaechel/uarch/gowin/gowin_arch_gen.py +++ b/himbaechel/uarch/gowin/gowin_arch_gen.py @@ -55,9 +55,11 @@ DHCEN_Z = 288 # : 298 USERFLASH_Z = 298 - EMCU_Z = 300 +MIPIOBUF_Z = 301 +MIPIIBUF_Z = 302 + DSP_Z = 509 DSP_0_Z = 511 # DSP macro 0 @@ -580,6 +582,23 @@ 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) + elif func == 'mipi_obuf': + bel = tt.create_bel('MIPI_OBUF', 'MIPI_OBUF', MIPIOBUF_Z) + elif func == 'mipi_ibuf': + bel = tt.create_bel('MIPI_IBUF', 'MIPI_IBUF', MIPIIBUF_Z) + wire = desc['HSREN'] + if not tt.has_wire(wire): + tt.create_wire(wire) + tt.add_bel_pin(bel, 'HSREN', wire, PinType.INPUT) + wire = 'MIPIOL' + if not tt.has_wire(wire): + tt.create_wire(wire) + tt.add_bel_pin(bel, 'OL', wire, PinType.OUTPUT) + for i in range(2): + wire = f'MIPIEN{i}' + if not tt.has_wire(wire): + tt.create_wire(wire) + tt.add_bel_pin(bel, f'MIPIEN{i}', wire, PinType.INPUT) elif func == 'buf': for buf_type, wires in desc.items(): for i, wire in enumerate(wires): diff --git a/himbaechel/uarch/gowin/pack.cc b/himbaechel/uarch/gowin/pack.cc index 8aa1227c..250e3b28 100644 --- a/himbaechel/uarch/gowin/pack.cc +++ b/himbaechel/uarch/gowin/pack.cc @@ -266,6 +266,14 @@ struct GowinPacker iob_n->disconnectPort(id_OEN); iob_p->disconnectPort(id_OEN); ci.movePortTo(id_OEN, iob_p, id_OEN); + + // MIPI + if (ci.params.count(id_MIPI_OBUF)) { + iob_p->setParam(id_MIPI_OBUF, 1); + iob_n->setParam(id_MIPI_OBUF, 1); + ci.movePortTo(id_IB, iob_n, id_I); + iob_p->copyPortTo(id_OEN, iob_n, id_OEN); + } } iob_p->disconnectPort(id_I); ci.movePortTo(id_I, iob_p, id_I); @@ -300,6 +308,152 @@ struct GowinPacker } } + // =================================== + // MIPI IO + // =================================== + void pack_mipi(void) + { + log_info("Pack MIPI IOs...\n"); + std::vector> new_cells; + + for (auto &cell : ctx->cells) { + CellInfo &ci = *cell.second; + if (!is_mipi(&ci)) { + continue; + } + switch (ci.type.hash()) { + case ID_MIPI_OBUF_A: /* fallt-hrough */ + case ID_MIPI_OBUF: { + // check for MIPI-capable pin + CellInfo *out_iob = net_only_drives(ctx, ci.ports.at(id_O).net, is_iob, id_I, true); + if (out_iob == nullptr || out_iob->bel == BelId()) { + log_error("MIPI %s is not connected to the output pin or the pin is not constrained.\n", + ctx->nameOf(&ci)); + } + BelId iob_bel = out_iob->bel; + Loc iob_loc = ctx->getBelLocation(iob_bel); + iob_loc.z = BelZ::MIPIOBUF_Z; + BelId mipi_bel = ctx->getBelByLocation(iob_loc); + if (mipi_bel == BelId()) { + log_error("Can't place MIPI %s at X%dY%d/IOBA.\n", ctx->nameOf(&ci), iob_loc.x, iob_loc.y); + } + + if (ci.type == id_MIPI_OBUF_A) { + // if serialization is used then IL and input of serializator must be in the same network + NetInfo *i_net = ci.getPort(id_I); + NetInfo *il_net = ci.getPort(id_IL); + if (i_net != il_net) { + if (i_net != nullptr && is_iologico(i_net->driver.cell)) { + if (i_net->driver.cell->getPort(id_D0) != ci.getPort(id_IL)) { + log_error("MIPI %s port IL and IOLOGIC %s port D0 are in differrent networks!\n", + ctx->nameOf(&ci), ctx->nameOf(i_net->driver.cell)); + } + } else { + log_error("MIPI %s ports IL and I are in differrent networks!\n", ctx->nameOf(&ci)); + } + } + ci.disconnectPort(id_IL); + } + + ctx->bindBel(mipi_bel, &ci, PlaceStrength::STRENGTH_LOCKED); + + // Create TBUF with additional input IB + IdString mipi_tbuf_name = gwu.create_aux_name(ci.name); + new_cells.push_back(gwu.create_cell(mipi_tbuf_name, id_TLVDS_TBUF)); + + CellInfo *mipi_tbuf = new_cells.back().get(); + mipi_tbuf->addInput(id_I); + mipi_tbuf->addInput(id_IB); + mipi_tbuf->addOutput(id_O); + mipi_tbuf->addOutput(id_OB); + mipi_tbuf->addInput(id_OEN); + ci.movePortTo(id_I, mipi_tbuf, id_I); + ci.movePortTo(id_IB, mipi_tbuf, id_IB); + ci.movePortTo(id_O, mipi_tbuf, id_O); + ci.movePortTo(id_OB, mipi_tbuf, id_OB); + ci.movePortTo(id_MODESEL, mipi_tbuf, id_OEN); + + mipi_tbuf->setParam(id_MIPI_OBUF, 1); + } break; + case ID_MIPI_IBUF: { + // check for MIPI-capable pin A + CellInfo *in_iob = net_only_drives(ctx, ci.ports.at(id_IO).net, is_iob, id_I); + if (in_iob == nullptr || in_iob->bel == BelId()) { + log_error("MIPI %s IO is not connected to the input pin or the pin is not constrained.\n", + ctx->nameOf(&ci)); + } + // check A IO placing + BelId iob_bel = in_iob->bel; + Loc iob_loc = ctx->getBelLocation(iob_bel); + if (iob_loc.z != BelZ::IOBA_Z) { + log_error("MIPI %s IO pin must be connected to the A IO pin.\n", ctx->nameOf(&ci)); + } + + iob_loc.z = BelZ::MIPIIBUF_Z; + BelId mipi_bel = ctx->getBelByLocation(iob_loc); + if (mipi_bel == BelId()) { + log_error("Can't place MIPI %s at X%dY%d/IOBA.\n", ctx->nameOf(&ci), iob_loc.x, iob_loc.y); + } + + // check for MIPI-capable pin B + CellInfo *inb_iob = net_only_drives(ctx, ci.ports.at(id_IOB).net, is_iob, id_I); + if (inb_iob == nullptr || inb_iob->bel == BelId()) { + log_error("MIPI %s IOB is not connected to the input pin or the pin is not constrained.\n", + ctx->nameOf(&ci)); + } + // check B IO placing + BelId iobb_bel = inb_iob->bel; + Loc iobb_loc = ctx->getBelLocation(iobb_bel); + if (iobb_loc.z != BelZ::IOBB_Z || iobb_loc.x != iob_loc.x || iobb_loc.y != iob_loc.y) { + log_error("MIPI %s IOB pin must be connected to the B IO pin.\n", ctx->nameOf(&ci)); + } + // MIPI IBUF uses next pair of IOs too + Loc iob_next_loc(iob_loc); + ++iob_next_loc.x; + iob_next_loc.z = BelZ::IOBA_Z; + CellInfo *inc_iob = ctx->getBoundBelCell(ctx->getBelByLocation(iob_next_loc)); + iob_next_loc.z = BelZ::IOBB_Z; + CellInfo *other_cell_b = ctx->getBoundBelCell(ctx->getBelByLocation(iob_next_loc)); + if (inc_iob != nullptr || other_cell_b != nullptr) { + log_error("MIPI %s cannot be placed in same IO with %s.\n", ctx->nameOf(&ci), + inc_iob == nullptr ? ctx->nameOf(other_cell_b) : ctx->nameOf(inc_iob)); + } + + ctx->bindBel(mipi_bel, &ci, PlaceStrength::STRENGTH_LOCKED); + + // reconnect wires + // A + ci.disconnectPort(id_IO); + in_iob->disconnectPort(id_I); + ci.movePortTo(id_I, in_iob, id_I); + ci.movePortTo(id_OH, in_iob, id_O); + in_iob->disconnectPort(id_OEN); + ci.movePortTo(id_OEN, in_iob, id_OEN); + // B + ci.disconnectPort(id_IO); + inb_iob->disconnectPort(id_I); + ci.movePortTo(id_IB, inb_iob, id_I); + ci.movePortTo(id_OB, inb_iob, id_O); + inb_iob->disconnectPort(id_OEN); + ci.movePortTo(id_OENB, inb_iob, id_OEN); + // MIPI enable (?) + ci.addInput(ctx->id("MIPIEN0")); + ci.connectPort(ctx->id("MIPIEN0"), ctx->nets.at(ctx->id("$PACKER_GND")).get()); + ci.addInput(ctx->id("MIPIEN1")); + ci.connectPort(ctx->id("MIPIEN1"), ctx->nets.at(ctx->id("$PACKER_VCC")).get()); + + in_iob->setParam(id_MIPI_IBUF, 1); + inb_iob->setParam(id_MIPI_IBUF, 1); + } break; + default: + log_error("MIPI %s is not implemented.\n", ci.type.c_str(ctx)); + } + } + for (auto &ncell : new_cells) { + ctx->cells[ncell->name] = std::move(ncell); + } + } + void pack_diff_iobs(void) { log_info("Pack diff IOBs...\n"); @@ -432,7 +586,8 @@ struct GowinPacker } // mark IOB as used by IOLOGIC out_iob->setParam(id_IOLOGIC_IOB, 1); - check_iologic_placement(ci, ctx->getBelLocation(iob_bel), out_iob->params.count(id_DIFF_TYPE)); + check_iologic_placement(ci, ctx->getBelLocation(iob_bel), + out_iob->params.count(id_DIFF_TYPE) || out_iob->params.count(id_MIPI_OBUF)); if (!ctx->checkBelAvail(l_bel)) { log_error("Can't place %s at %s because it's already taken by %s\n", ctx->nameOf(&ci), @@ -471,6 +626,20 @@ struct GowinPacker nets_to_remove.push_back(ci.getPort(tx_port)->name); out_iob->disconnectPort(id_OEN); ci.disconnectPort(tx_port); + } else { // disconnect TXx ports, ignore these nets + switch (ci.type.hash()) { + case ID_OSER8: + ci.disconnectPort(id_TX3); + ci.disconnectPort(id_TX2); /* fall-through */ + case ID_OSER4: + ci.disconnectPort(id_TX1); + ci.disconnectPort(id_TX0); + break; + case ID_ODDR: /* fall-through */ + case ID_ODDRC: /* fall-through */ + ci.disconnectPort(id_TX); + break; + } } make_iob_nets(*out_iob); } @@ -489,7 +658,8 @@ struct GowinPacker } // mark IOB as used by IOLOGIC out_iob->setParam(id_IOLOGIC_IOB, 1); - check_iologic_placement(ci, ctx->getBelLocation(iob_bel), out_iob->params.count(id_DIFF_TYPE)); + check_iologic_placement(ci, ctx->getBelLocation(iob_bel), + out_iob->params.count(id_DIFF_TYPE) || out_iob->params.count(id_MIPI_OBUF)); if (!ctx->checkBelAvail(l_bel)) { log_error("Can't place %s at %s because it's already taken by %s\n", ctx->nameOf(&ci), @@ -534,6 +704,11 @@ struct GowinPacker } bool is_diff_io(BelId bel) { return ctx->getBoundBelCell(bel)->params.count(id_DIFF_TYPE) != 0; } + bool is_mipi_io(BelId bel) + { + return ctx->getBoundBelCell(bel)->params.count(id_MIPI_IBUF) || + ctx->getBoundBelCell(bel)->params.count(id_MIPI_OBUF); + } CellInfo *create_aux_iologic_cell(CellInfo &ci, IdString mode, bool io16 = false, int idx = 0) { @@ -545,7 +720,7 @@ struct GowinPacker BelId bel = get_aux_iologic_bel(ci); BelId io_bel = gwu.get_io_bel_from_iologic(bel); if (!ctx->checkBelAvail(io_bel)) { - if (!is_diff_io(io_bel)) { + if (!(is_diff_io(io_bel) || is_mipi_io(io_bel))) { log_error("Can't place %s at %s because of a conflict with another IO %s\n", ctx->nameOf(&ci), ctx->nameOfBel(bel), ctx->nameOf(ctx->getBoundBelCell(io_bel))); } @@ -609,7 +784,8 @@ struct GowinPacker } // mark IOB as used by IOLOGIC in_iob->setParam(id_IOLOGIC_IOB, 1); - check_iologic_placement(ci, ctx->getBelLocation(iob_bel), in_iob->params.count(id_DIFF_TYPE)); + check_iologic_placement(ci, ctx->getBelLocation(iob_bel), + in_iob->params.count(id_DIFF_TYPE) || in_iob->params.count(id_MIPI_IBUF)); if (!ctx->checkBelAvail(l_bel)) { log_error("Can't place %s at %s because it's already taken by %s\n", ctx->nameOf(&ci), @@ -3932,6 +4108,9 @@ struct GowinPacker pack_iobs(); ctx->check(); + pack_mipi(); + ctx->check(); + pack_diff_iobs(); ctx->check();