gowin: implement differential IO primitives (#1159)

* gowin: implement differential IO primitives

Adds missing TLVDS_IBUF/IOBUF/TBUF primitives, as well as all kinds of
LVDS emulation primitives.

Signed-off-by: YRabbit <rabbit@yrabbit.cyou>

* gowin: fix build

Signed-off-by: YRabbit <rabbit@yrabbit.cyou>

* gowin: support as differential not only pins A and B

The GW1N-1 and GW1NZ-1 chips have cells with pins up to I, we provide
support for such pins.

Signed-off-by: YRabbit <rabbit@yrabbit.cyou>

---------

Signed-off-by: YRabbit <rabbit@yrabbit.cyou>
This commit is contained in:
YRabbit 2023-05-19 16:59:19 +10:00 committed by GitHub
parent 7a827b1c78
commit 354b7daf12
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 194 additions and 42 deletions

View File

@ -638,6 +638,7 @@ X(O)
X(IO) X(IO)
X(OE) X(OE)
X(OB) X(OB)
X(IB)
// bels // bels
X(DFF0) X(DFF0)
@ -888,6 +889,13 @@ X(OBUF)
X(IOBUF) X(IOBUF)
X(TBUF) X(TBUF)
X(TLVDS_OBUF) X(TLVDS_OBUF)
X(TLVDS_TBUF)
X(TLVDS_IBUF)
X(TLVDS_IOBUF)
X(ELVDS_OBUF)
X(ELVDS_TBUF)
X(ELVDS_IBUF)
X(ELVDS_IOBUF)
// global set/reset // global set/reset
X(GSR) X(GSR)
@ -918,6 +926,7 @@ X(BEL)
X(DIFF) X(DIFF)
X(DIFF_TYPE) X(DIFF_TYPE)
X(DEVICE) X(DEVICE)
X(IOLOGIC_IOB)
// ports // ports
X(EN) X(EN)

View File

@ -30,6 +30,46 @@
NEXTPNR_NAMESPACE_BEGIN NEXTPNR_NAMESPACE_BEGIN
static bool check_availability(const Context *ctx, IdString type)
{
switch (type.hash()) {
case ID_ELVDS_IBUF:
if (ctx->device != "GW1NZ-1") {
return true;
}
break;
case ID_ELVDS_IOBUF:
if (ctx->device == "GW1NZ-1") {
return true;
}
break;
case ID_TLVDS_IBUF:
if (ctx->device != "GW1NZ-1") {
return true;
}
break;
case ID_TLVDS_OBUF:
if (ctx->device != "GW1NZ-1" && ctx->device != "GW1N-1") {
return true;
}
break;
case ID_TLVDS_TBUF:
if (ctx->device != "GW1NZ-1" && ctx->device != "GW1N-1") {
return true;
}
break;
case ID_TLVDS_IOBUF:
if (ctx->device == "GW1N-4") {
return true;
}
break;
default:
return true;
}
log_info("%s is not supported for device %s.\n", type.c_str(ctx), ctx->device.c_str());
return false;
}
static void make_dummy_alu(Context *ctx, int alu_idx, CellInfo *ci, CellInfo *packed_head, static void make_dummy_alu(Context *ctx, int alu_idx, CellInfo *ci, CellInfo *packed_head,
std::vector<std::unique_ptr<CellInfo>> &new_cells) std::vector<std::unique_ptr<CellInfo>> &new_cells)
{ {
@ -803,6 +843,13 @@ static bool is_gowin_diff_iob(const Context *ctx, const CellInfo *cell)
{ {
switch (cell->type.index) { switch (cell->type.index) {
case ID_TLVDS_OBUF: case ID_TLVDS_OBUF:
case ID_TLVDS_TBUF:
case ID_TLVDS_IBUF:
case ID_TLVDS_IOBUF:
case ID_ELVDS_OBUF:
case ID_ELVDS_TBUF:
case ID_ELVDS_IBUF:
case ID_ELVDS_IOBUF:
return true; return true;
default: default:
return false; return false;
@ -886,6 +933,26 @@ static void get_next_oser16_loc(std::string device, Loc &loc)
} }
} }
// create IOB connections for gowin_pack
static void make_iob_nets(Context *ctx, CellInfo *iob)
{
for (const auto &port : iob->ports) {
const NetInfo *net = iob->getPort(port.first);
std::string connected_net = "NET";
if (net != nullptr) {
if (ctx->verbose) {
log_info("%s: %s - %s\n", ctx->nameOf(iob), port.first.c_str(ctx), ctx->nameOf(net));
}
if (net->name == ctx->id("$PACKER_VCC_NET")) {
connected_net = "VCC";
} else if (net->name == ctx->id("$PACKER_GND_NET")) {
connected_net = "GND";
}
iob->setParam(ctx->idf("NET_%s", port.first.c_str(ctx)), connected_net);
}
}
}
// Pack IO logic // Pack IO logic
static void pack_iologic(Context *ctx) static void pack_iologic(Context *ctx)
{ {
@ -899,6 +966,9 @@ static void pack_iologic(Context *ctx)
CellInfo *ci = cell.second.get(); CellInfo *ci = cell.second.get();
if (ctx->verbose) if (ctx->verbose)
log_info("cell '%s' is of type '%s'\n", ctx->nameOf(ci), ci->type.c_str(ctx)); log_info("cell '%s' is of type '%s'\n", ctx->nameOf(ci), ci->type.c_str(ctx));
if (ci->type == id_IOB) {
make_iob_nets(ctx, ci);
}
if (is_gowin_iologic(ctx, ci)) { if (is_gowin_iologic(ctx, ci)) {
CellInfo *q0_dst = nullptr; CellInfo *q0_dst = nullptr;
CellInfo *q1_dst = nullptr; CellInfo *q1_dst = nullptr;
@ -953,12 +1023,18 @@ static void pack_iologic(Context *ctx)
break; break;
} }
ci->setParam(ctx->id("OUTMODE"), out_mode); ci->setParam(ctx->id("OUTMODE"), out_mode);
// mark IOB as used by IOLOGIC
q0_dst->setParam(id_IOLOGIC_IOB, 1);
bool use_diff_io = false; bool use_diff_io = false;
if (q0_dst->attrs.count(id_DIFF_TYPE)) { if (q0_dst->attrs.count(id_DIFF_TYPE)) {
ci->setAttr(id_OBUF_TYPE, std::string("DBUF")); ci->setAttr(id_OBUF_TYPE, std::string("DBUF")); // XXX compatibility
ci->setParam(id_OBUF_TYPE, std::string("DBUF"));
use_diff_io = true; use_diff_io = true;
} else { } else {
ci->setAttr(id_OBUF_TYPE, std::string("SBUF")); ci->setAttr(id_OBUF_TYPE, std::string("SBUF")); // XXX compatibility
ci->setParam(id_OBUF_TYPE, std::string("SBUF"));
} }
// disconnect Q output: it is wired internally // disconnect Q output: it is wired internally
@ -987,19 +1063,7 @@ static void pack_iologic(Context *ctx)
ci->disconnectPort(output_1); ci->disconnectPort(output_1);
ci->setAttr(id_IOBUF, 1); ci->setAttr(id_IOBUF, 1);
} }
} else {
// force OEN = 0 in order to enable output
// XXX check for IOBUF and TBUF
if (ci->type == id_OSER4 || ci->type == id_OSER8) {
int port_num = ci->type == id_OSER4 ? 2 : 4;
for (int i = 0; i < port_num; ++i) {
IdString port = ctx->idf("TX%d", i);
ci->disconnectPort(port);
ci->connectPort(port, ctx->nets[ctx->id("$PACKER_GND_NET")].get());
}
}
} }
ci->setAttr(id_IOBUF, 0); ci->setAttr(id_IOBUF, 0);
ci->setAttr(id_IOLOGIC_TYPE, ci->type.str(ctx)); ci->setAttr(id_IOLOGIC_TYPE, ci->type.str(ctx));
@ -1087,12 +1151,18 @@ static void pack_iologic(Context *ctx)
break; break;
} }
ci->setParam(ctx->id("INMODE"), in_mode); ci->setParam(ctx->id("INMODE"), in_mode);
// mark IOB as used by IOLOGIC
d_src->setParam(id_IOLOGIC_IOB, 1);
bool use_diff_io = false; bool use_diff_io = false;
if (d_src->attrs.count(id_DIFF_TYPE)) { if (d_src->attrs.count(id_DIFF_TYPE)) {
ci->setAttr(id_IBUF_TYPE, std::string("DBUF")); ci->setAttr(id_IBUF_TYPE, std::string("DBUF")); // XXX compatibility
ci->setParam(id_IBUF_TYPE, std::string("DBUF"));
use_diff_io = true; use_diff_io = true;
} else { } else {
ci->setAttr(id_IBUF_TYPE, std::string("SBUF")); ci->setAttr(id_IBUF_TYPE, std::string("SBUF")); // XXX compatibility
ci->setParam(id_IBUF_TYPE, std::string("SBUF"));
} }
// disconnect D input: it is wired internally // disconnect D input: it is wired internally
@ -1168,12 +1238,17 @@ static void pack_iologic(Context *ctx)
iob_bel->second.as_string().c_str()); iob_bel->second.as_string().c_str());
} }
// mark IOB as used by IOLOGIC
q0_dst->setParam(id_IOLOGIC_IOB, 1);
bool use_diff_io = false; bool use_diff_io = false;
if (q0_dst->attrs.count(id_DIFF_TYPE)) { if (q0_dst->attrs.count(id_DIFF_TYPE)) {
ci->setAttr(id_OBUF_TYPE, std::string("DBUF")); ci->setAttr(id_OBUF_TYPE, std::string("DBUF")); // compatibility
ci->setParam(id_OBUF_TYPE, std::string("DBUF"));
use_diff_io = true; use_diff_io = true;
} else { } else {
ci->setAttr(id_OBUF_TYPE, std::string("SBUF")); ci->setAttr(id_OBUF_TYPE, std::string("SBUF")); // compatibility
ci->setParam(id_OBUF_TYPE, std::string("SBUF"));
} }
// disconnect Q output: it is wired internally // disconnect Q output: it is wired internally
@ -1297,13 +1372,17 @@ static void pack_iologic(Context *ctx)
log_error("No bel for %s at %s. Can't place IDES/OSER here\n", ctx->nameOf(ci), log_error("No bel for %s at %s. Can't place IDES/OSER here\n", ctx->nameOf(ci),
iob_bel->second.as_string().c_str()); iob_bel->second.as_string().c_str());
} }
// mark IOB as used by IOLOGIC
d_src->setParam(id_IOLOGIC_IOB, 1);
bool use_diff_io = false; bool use_diff_io = false;
if (d_src->attrs.count(id_DIFF_TYPE)) { if (d_src->attrs.count(id_DIFF_TYPE)) {
ci->setAttr(id_IBUF_TYPE, std::string("DBUF")); ci->setAttr(id_IBUF_TYPE, std::string("DBUF")); // XXX compatibility
ci->setParam(id_IBUF_TYPE, std::string("DBUF"));
use_diff_io = true; use_diff_io = true;
} else { } else {
ci->setAttr(id_IBUF_TYPE, std::string("SBUF")); ci->setAttr(id_IBUF_TYPE, std::string("SBUF")); // XXX compatibility
ci->setParam(id_IBUF_TYPE, std::string("SBUF"));
} }
// disconnect D input: it is wired internally // disconnect D input: it is wired internally
delete_nets.insert(ci->getPort(id_D)->name); delete_nets.insert(ci->getPort(id_D)->name);
@ -1430,39 +1509,103 @@ static void pack_diff_io(Context *ctx)
CellInfo *iob_p = nullptr; CellInfo *iob_p = nullptr;
CellInfo *iob_n = nullptr; CellInfo *iob_n = nullptr;
switch (ci->type.index) { switch (ci->type.index) {
case ID_ELVDS_IOBUF: /* fall-through*/
case ID_ELVDS_IBUF: /* fall-through*/
case ID_ELVDS_TBUF: /* fall-through*/
case ID_ELVDS_OBUF: /* fall-through*/
case ID_TLVDS_IOBUF: /* fall-through*/
case ID_TLVDS_IBUF: /* fall-through*/
case ID_TLVDS_TBUF: /* fall-through*/
case ID_TLVDS_OBUF: { case ID_TLVDS_OBUF: {
iob_p = net_only_drives(ctx, ci->ports.at(id_O).net, is_iob, id_I); NPNR_ASSERT(check_availability(ctx, ci->type));
iob_n = net_only_drives(ctx, ci->ports.at(id_OB).net, is_iob, id_I); if (ci->type.in(id_TLVDS_TBUF, id_TLVDS_OBUF, id_TLVDS_IOBUF, id_ELVDS_TBUF, id_ELVDS_OBUF,
id_ELVDS_IOBUF)) {
iob_p = net_only_drives(ctx, ci->ports.at(id_O).net, is_iob, id_I);
iob_n = net_only_drives(ctx, ci->ports.at(id_OB).net, is_iob, id_I);
} else {
iob_p = net_driven_by(ctx, ci->ports.at(id_I).net, is_iob, id_O);
iob_n = net_driven_by(ctx, ci->ports.at(id_IB).net, is_iob, id_O);
}
NPNR_ASSERT(iob_p != nullptr); NPNR_ASSERT(iob_p != nullptr);
NPNR_ASSERT(iob_n != nullptr); NPNR_ASSERT(iob_n != nullptr);
auto iob_p_bel_a = iob_p->attrs.find(id_BEL); auto iob_p_bel_a = iob_p->attrs.find(id_BEL);
if (iob_p_bel_a == iob_p->attrs.end()) { if (iob_p_bel_a == iob_p->attrs.end()) {
log_error("LVDS '%s' must be restricted.\n", ctx->nameOf(ci)); log_error("LVDS '%s' must be restricted.\n", ctx->nameOf(ci));
continue;
} }
BelId iob_p_bel = ctx->getBelByNameStr(iob_p_bel_a->second.as_string()); BelId iob_p_bel = ctx->getBelByNameStr(iob_p_bel_a->second.as_string());
Loc loc_p = ctx->getBelLocation(iob_p_bel); Loc loc_p = ctx->getBelLocation(iob_p_bel);
if (loc_p.z != 0) {
log_error("LVDS '%s' positive pin is not A.\n", ctx->nameOf(ci));
continue;
}
// restrict the N buffer // restrict the N buffer
loc_p.z = 1; ++loc_p.z;
iob_n->attrs[id_BEL] = ctx->getBelName(ctx->getBelByLocation(loc_p)).str(ctx); BelId n_bel = ctx->getBelByLocation(loc_p);
if (n_bel == BelId()) {
log_error("Invalid pin for '%s'.\n", ctx->nameOf(ci));
}
iob_n->attrs[id_BEL] = ctx->getBelName(n_bel).str(ctx);
iob_n->type = iob_p->type;
// mark IOBs as part of DS pair // mark IOBs as part of DS pair
iob_n->attrs[id_DIFF] = std::string("N"); std::string io_type = ci->type.c_str(ctx);
iob_n->attrs[id_DIFF_TYPE] = std::string("TLVDS_OBUF"); // XXX compatibility
iob_p->attrs[id_DIFF] = std::string("P"); iob_n->setAttr(id_DIFF, std::string("N"));
iob_p->attrs[id_DIFF_TYPE] = std::string("TLVDS_OBUF"); iob_n->setAttr(id_DIFF_TYPE, io_type);
// disconnect N input: it is wired internally iob_p->setAttr(id_DIFF, std::string("P"));
delete_nets.insert(iob_n->ports.at(id_I).net->name); iob_p->setAttr(id_DIFF_TYPE, io_type);
iob_n->disconnectPort(id_I);
ci->disconnectPort(id_OB); iob_n->setParam(id_DIFF, std::string("N"));
// disconnect P output iob_n->setParam(id_DIFF_TYPE, io_type);
delete_nets.insert(ci->ports.at(id_O).net->name); iob_p->setParam(id_DIFF, std::string("P"));
ci->disconnectPort(id_O); iob_p->setParam(id_DIFF_TYPE, io_type);
// connect TLVDS input to P input
ci->movePortTo(id_I, iob_p, id_I); if (ci->type.in(id_TLVDS_TBUF, id_TLVDS_OBUF, id_ELVDS_TBUF, id_ELVDS_OBUF)) {
// disconnect N input: it is wired internally
delete_nets.insert(iob_n->ports.at(id_I).net->name);
iob_n->disconnectPort(id_I);
ci->disconnectPort(id_OB);
// disconnect P output
delete_nets.insert(ci->ports.at(id_O).net->name);
ci->disconnectPort(id_O);
// connect TLVDS input to P input
ci->movePortTo(id_I, iob_p, id_I);
if (ci->type.in(id_TLVDS_TBUF, id_ELVDS_TBUF)) {
if (iob_p->type == id_IOBS) {
iob_p->disconnectPort(id_OEN);
iob_n->disconnectPort(id_OEN);
}
ci->movePortTo(id_OEN, iob_p, id_OEN);
}
}
if (ci->type.in(id_TLVDS_IBUF, id_ELVDS_IBUF)) {
// disconnect N input: it is wired internally
delete_nets.insert(iob_n->ports.at(id_O).net->name);
iob_n->disconnectPort(id_O);
ci->disconnectPort(id_IB);
// disconnect P input
delete_nets.insert(ci->ports.at(id_I).net->name);
ci->disconnectPort(id_I);
// connect TLVDS output to P output
ci->movePortTo(id_O, iob_p, id_O);
}
if (ci->type.in(id_TLVDS_IOBUF, id_ELVDS_IOBUF)) {
// disconnect N io: it is wired internally
// O port is missing after iopadmap so leave it as is
delete_nets.insert(iob_n->getPort(id_I)->name);
iob_n->disconnectPort(id_I);
iob_n->disconnectPort(id_OEN);
ci->disconnectPort(id_IOB);
// disconnect P io
delete_nets.insert(ci->getPort(id_IO)->name);
iob_p->disconnectPort(id_I);
iob_p->disconnectPort(id_OEN);
ci->disconnectPort(id_IO);
ci->movePortTo(id_I, iob_p, id_I);
ci->movePortTo(id_O, iob_p, id_O);
// OEN
if (iob_p->type == id_IOBS) {
iob_p->disconnectPort(id_OEN);
iob_n->disconnectPort(id_OEN);
}
ci->movePortTo(id_OEN, iob_p, id_OEN);
}
packed_cells.insert(ci->name); packed_cells.insert(ci->name);
} break; } break;
default: default: