Gowin. Implement MIPI IO.

Adds output (MIPI_OBUF and MIPI_OBUF_A) and input (MIPI_IBUF) primitives
to allow the use of “real” MIPI (not emulation) ports capable of
operating in both HS and LP modes.

Signed-off-by: YRabbit <rabbit@yrabbit.cyou>
This commit is contained in:
YRabbit 2025-01-23 19:17:31 +10:00
parent 1623243d50
commit 17089994f1
6 changed files with 245 additions and 10 deletions

View File

@ -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)

View File

@ -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<PipId> 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<PortRef> users;

View File

@ -1,5 +1,5 @@
#include <regex>
#include <map>
#include <regex>
#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<HimbaechelAPI> create(const std::string &device, const dict<std::string, std::string> &args) override
std::unique_ptr<HimbaechelAPI> create(const std::string &device,
const dict<std::string, std::string> &args) override
{
return std::make_unique<GowinImpl>();
}
@ -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);
}

View File

@ -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.

View File

@ -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):

View File

@ -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<std::unique_ptr<CellInfo>> 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();