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:
parent
1623243d50
commit
17089994f1
@ -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)
|
||||
|
@ -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(
|
||||
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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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):
|
||||
|
@ -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();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user