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:
parent
7a827b1c78
commit
354b7daf12
@ -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)
|
||||||
|
227
gowin/pack.cc
227
gowin/pack.cc
@ -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:
|
||||||
|
Loading…
Reference in New Issue
Block a user