gowin: Add support for OSER primitives
* placement of OSER4, OVIDEO, OSER8 and SER10 primitives is supported; * primitives are implemented for the GW1N-1, GW1NZ-1, GW1NSR-4C, GW1NR-9, GW1NR-9C chips; * the initial support for special HCLK clock wires is implemented to the extent necessary for OSER primitives to function; * output to both regular IO and TLVDS_OBUF is supported; * tricks required for IOLOGIC to work on one side of the -9 and -9C chips are taken into account; * various edits, such as using idf() instead of the local buffer. Compatible with old apicula bases. Signed-off-by: YRabbit <rabbit@yrabbit.cyou>
This commit is contained in:
parent
b3c33bd0ab
commit
95ace0fade
159
gowin/arch.cc
159
gowin/arch.cc
@ -384,9 +384,6 @@ void Arch::addPip(IdString name, IdString type, IdString srcWire, IdString dstWi
|
||||
pi.delay = delay;
|
||||
pi.loc = loc;
|
||||
|
||||
// log_info("addpip %s->%s %.6f | %s name:%s\n" , srcWire.c_str(this), dstWire.c_str(this),
|
||||
// getDelayNS(delay.maxDelay()), srcWire.c_str(this), name.c_str(this));
|
||||
|
||||
wire_info(srcWire).downhill.push_back(name);
|
||||
wire_info(dstWire).uphill.push_back(name);
|
||||
pip_ids.push_back(name);
|
||||
@ -597,19 +594,33 @@ void Arch::addCellTimingClockToOut(IdString cell, IdString port, IdString clock,
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
IdString Arch::apply_local_aliases(int row, int col, const DatabasePOD *db, IdString &wire)
|
||||
{
|
||||
const TilePOD *tile = db->grid[row * db->cols + col].get();
|
||||
auto local_alias = pairLookup(tile->aliases.get(), tile->num_aliases, wire.index);
|
||||
IdString res_wire = IdString();
|
||||
if (local_alias != nullptr) {
|
||||
wire = IdString(local_alias->src_id);
|
||||
res_wire = idf("R%dC%d_%s", row + 1, col + 1, wire.c_str(this));
|
||||
}
|
||||
return res_wire;
|
||||
}
|
||||
|
||||
// TODO represent wires more intelligently.
|
||||
IdString Arch::wireToGlobal(int &row, int &col, const DatabasePOD *db, IdString &wire)
|
||||
{
|
||||
const std::string &wirename = wire.str(this);
|
||||
char buf[32];
|
||||
if (wirename == "VCC" || wirename == "VSS") {
|
||||
row = 0;
|
||||
col = 0;
|
||||
return wire;
|
||||
}
|
||||
if (!isdigit(wirename[1]) || !isdigit(wirename[2]) || !isdigit(wirename[3])) {
|
||||
snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, wirename.c_str());
|
||||
return id(buf);
|
||||
IdString res_wire = apply_local_aliases(row, col, db, wire);
|
||||
if (res_wire == IdString()) {
|
||||
return idf("R%dC%d_%s", row + 1, col + 1, wirename.c_str());
|
||||
}
|
||||
return res_wire;
|
||||
}
|
||||
char direction = wirename[0];
|
||||
int num = std::stoi(wirename.substr(1, 2));
|
||||
@ -628,8 +639,7 @@ IdString Arch::wireToGlobal(int &row, int &col, const DatabasePOD *db, IdString
|
||||
col += segment;
|
||||
break;
|
||||
default:
|
||||
snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, wirename.c_str());
|
||||
return id(buf);
|
||||
return idf("R%dC%d_%s", row + 1, col + 1, wirename.c_str());
|
||||
break;
|
||||
}
|
||||
// wires wrap around the edges
|
||||
@ -647,18 +657,13 @@ IdString Arch::wireToGlobal(int &row, int &col, const DatabasePOD *db, IdString
|
||||
col = 2 * db->cols - 1 - col;
|
||||
direction = 'E';
|
||||
}
|
||||
snprintf(buf, 32, "%c%d0", direction, num);
|
||||
wire = id(buf);
|
||||
wire = idf("%c%d0", direction, num);
|
||||
// local aliases
|
||||
const TilePOD *tile = db->grid[row * db->cols + col].get();
|
||||
auto local_alias = pairLookup(tile->aliases.get(), tile->num_aliases, wire.index);
|
||||
if (local_alias != nullptr) {
|
||||
wire = IdString(local_alias->src_id);
|
||||
snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, wire.c_str(this));
|
||||
} else {
|
||||
snprintf(buf, 32, "R%dC%d_%c%d", row + 1, col + 1, direction, num);
|
||||
IdString res_wire = apply_local_aliases(row, col, db, wire);
|
||||
if (res_wire == IdString()) {
|
||||
res_wire = idf("R%dC%d_%c%d", row + 1, col + 1, direction, num);
|
||||
}
|
||||
return id(buf);
|
||||
return res_wire;
|
||||
}
|
||||
|
||||
const PairPOD *pairLookup(const PairPOD *list, const size_t len, const int dest)
|
||||
@ -1581,6 +1586,10 @@ Arch::Arch(ArchArgs args) : args(args)
|
||||
snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this));
|
||||
addBelInput(belname, id_XXX_VSS1, id(buf));
|
||||
}
|
||||
if (!z && device_id == id("GW1NR-9C")) {
|
||||
addBelInput(belname, id_XXX_0, idf("R%dC%d_C6", row + 1, col + 1));
|
||||
addBelInput(belname, id_XXX_1, idf("R%dC%d_D6", row + 1, col + 1));
|
||||
}
|
||||
} break;
|
||||
// Simplified IO
|
||||
case ID_IOBJS:
|
||||
@ -1627,7 +1636,7 @@ Arch::Arch(ArchArgs args) : args(args)
|
||||
case ID_ODDRA: {
|
||||
snprintf(buf, 32, "R%dC%d_ODDR%s%c", row + 1, col + 1, oddrc ? "C" : "", 'A' + z - (oddrc ? 2 : 0));
|
||||
belname = id(buf);
|
||||
addBel(belname, id_ODDR, Loc(col, row, BelZ::iologic_0_z + z), false);
|
||||
addBel(belname, id_ODDR, Loc(col, row, BelZ::oddr_0_z + z), false);
|
||||
|
||||
portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_D0)->src_id);
|
||||
snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this));
|
||||
@ -1681,6 +1690,64 @@ Arch::Arch(ArchArgs args) : args(args)
|
||||
addWire(q1_name, id_q1, row, col);
|
||||
addBelOutput(belname, id_Q1, q1_name);
|
||||
} break;
|
||||
case ID_IOLOGICB:
|
||||
z++; /* fall-through*/
|
||||
case ID_IOLOGICA: {
|
||||
belname = idf("R%dC%d_IOLOGIC%c", row + 1, col + 1, 'A' + z);
|
||||
addBel(belname, id_IOLOGIC, Loc(col, row, BelZ::iologic_z + z), false);
|
||||
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
if (i < 4) {
|
||||
// TX
|
||||
IdString const tx[] = {id_TX0, id_TX1, id_TX2, id_TX3};
|
||||
portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, tx[i].hash())->src_id);
|
||||
addBelInput(belname, tx[i], idf("R%dC%d_%s", row + 1, col + 1, portname.c_str(this)));
|
||||
}
|
||||
// D
|
||||
IdString const d[] = {id_D0, id_D1, id_D2, id_D3, id_D4, id_D5, id_D6, id_D7, id_D8, id_D9};
|
||||
portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, d[i].hash())->src_id);
|
||||
addBelInput(belname, d[i], idf("R%dC%d_%s", row + 1, col + 1, portname.c_str(this)));
|
||||
}
|
||||
portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_PCLK)->src_id);
|
||||
addBelInput(belname, id_PCLK, idf("R%dC%d_%s", row + 1, col + 1, portname.c_str(this)));
|
||||
auto fclk = pairLookup(bel->ports.get(), bel->num_ports, ID_FCLK);
|
||||
// XXX as long as there is no special processing of the pins
|
||||
if (fclk != nullptr) {
|
||||
portname = IdString(fclk->src_id);
|
||||
IdString wire = idf("R%dC%d_%s", row + 1, col + 1, portname.c_str(this));
|
||||
if (wires.count(wire) == 0) {
|
||||
GlobalAliasPOD alias;
|
||||
alias.dest_col = col;
|
||||
alias.dest_row = row;
|
||||
alias.dest_id = portname.hash();
|
||||
auto alias_src = genericLookup(db->aliases.get(), db->num_aliases, alias, aliasCompare);
|
||||
if (alias_src != nullptr) {
|
||||
int srcrow = alias_src->src_row;
|
||||
int srccol = alias_src->src_col;
|
||||
IdString srcid = IdString(alias_src->src_id);
|
||||
wire = wireToGlobal(srcrow, srccol, db, srcid);
|
||||
if (wires.count(wire) == 0) {
|
||||
addWire(wire, srcid, srccol, srcrow);
|
||||
}
|
||||
addBelInput(belname, id_FCLK, wire);
|
||||
}
|
||||
// XXX here we are creating an
|
||||
// IOLOGIC with a missing FCLK input. This is so
|
||||
// because bels with the same type can be placed in
|
||||
// on the chip where there is no pin, so no
|
||||
// IOLOGIC makes sense. But since each type is
|
||||
// described only once in the database we can't really
|
||||
// mark these special bel somehow.
|
||||
// By creating an IOLOGIC without an FCLK input we
|
||||
// create a routing error later, so that "bad"
|
||||
// locations are handled.
|
||||
} else {
|
||||
addBelInput(belname, id_FCLK, idf("R%dC%d_%s", row + 1, col + 1, portname.c_str(this)));
|
||||
}
|
||||
}
|
||||
portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_RESET)->src_id);
|
||||
addBelInput(belname, id_RESET, idf("R%dC%d_%s", row + 1, col + 1, portname.c_str(this)));
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -2245,6 +2312,54 @@ void Arch::fix_pll_nets(Context *ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// mark with hclk is used
|
||||
void Arch::mark_used_hclk(Context *ctx)
|
||||
{
|
||||
for (auto &cell : ctx->cells) {
|
||||
CellInfo *ci = cell.second.get();
|
||||
if (ci->type != id_IOLOGIC) {
|
||||
continue;
|
||||
}
|
||||
if (ci->attrs.count(id_IOLOGIC_FCLK)) {
|
||||
continue;
|
||||
}
|
||||
// if it's an aux cell
|
||||
if (ci->attrs.count(id_IOLOGIC_MASTER_CELL)) {
|
||||
continue;
|
||||
}
|
||||
ci->setAttr(id_IOLOGIC_FCLK, Property("UNKNOWN"));
|
||||
|
||||
// *** FCLK
|
||||
if (port_used(ci, id_FCLK)) {
|
||||
NetInfo const *net = ci->getPort(id_FCLK);
|
||||
for (auto const &user : net->users) {
|
||||
if (user.cell != ci) {
|
||||
continue;
|
||||
}
|
||||
if (user.port != id_FCLK) {
|
||||
continue;
|
||||
}
|
||||
WireId dstWire = ctx->getNetinfoSinkWire(net, user, 0);
|
||||
if (ctx->verbose) {
|
||||
log_info(" Cell:%s, port:%s, wire:%s\n", user.cell->name.c_str(this), user.port.c_str(this),
|
||||
dstWire.c_str(this));
|
||||
}
|
||||
for (PipId const &pip : getPipsUphill(dstWire)) {
|
||||
if (!checkPipAvail(pip)) {
|
||||
WireId src_wire = getPipSrcWire(pip);
|
||||
ci->setAttr(id_IOLOGIC_FCLK, Property(wire_info(src_wire).type.str(this)));
|
||||
if (ci->attrs.count(id_IOLOGIC_AUX_CELL)) {
|
||||
IdString aux_cell_name = ctx->id(ci->attrs[id_IOLOGIC_AUX_CELL].as_string());
|
||||
ctx->cells[aux_cell_name]->setAttr(id_IOLOGIC_FCLK,
|
||||
Property(wire_info(src_wire).type.str(this)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Arch::pre_route(Context *ctx)
|
||||
{
|
||||
fix_pll_nets(ctx);
|
||||
@ -2253,7 +2368,11 @@ void Arch::pre_route(Context *ctx)
|
||||
}
|
||||
}
|
||||
|
||||
void Arch::post_route(Context *ctx) { fix_longwire_bels(); }
|
||||
void Arch::post_route(Context *ctx)
|
||||
{
|
||||
fix_longwire_bels();
|
||||
mark_used_hclk(ctx);
|
||||
}
|
||||
|
||||
bool Arch::route()
|
||||
{
|
||||
|
29
gowin/arch.h
29
gowin/arch.h
@ -401,6 +401,11 @@ struct Arch : BaseArch<ArchRanges>
|
||||
PortType getBelPinType(BelId bel, IdString pin) const override;
|
||||
std::vector<IdString> getBelPins(BelId bel) const override;
|
||||
std::array<IdString, 1> getBelPinsForCellPin(const CellInfo *cell_info, IdString pin) const override;
|
||||
// Placement validity checks
|
||||
virtual bool isValidBelForCellType(IdString cell_type, BelId bel) const override
|
||||
{
|
||||
return cell_type == id_DUMMY_CELL || cell_type == this->getBelType(bel);
|
||||
}
|
||||
|
||||
WireId getWireByName(IdStringList name) const override;
|
||||
IdStringList getWireName(WireId wire) const override;
|
||||
@ -490,6 +495,9 @@ struct Arch : BaseArch<ArchRanges>
|
||||
bool is_GCLKT_iob(const CellInfo *cell);
|
||||
void bind_pll_to_bel(CellInfo *ci, PLL loc);
|
||||
|
||||
void mark_used_hclk(Context *ctx);
|
||||
IdString apply_local_aliases(int row, int col, const DatabasePOD *db, IdString &wire);
|
||||
|
||||
GowinGlobalRouter globals_router;
|
||||
void mark_gowin_globals(Context *ctx);
|
||||
void route_gowin_globals(Context *ctx);
|
||||
@ -524,16 +532,17 @@ struct Arch : BaseArch<ArchRanges>
|
||||
namespace BelZ {
|
||||
enum
|
||||
{
|
||||
mux_0_z = 10, // start Z for the MUX2LUT5 bels
|
||||
iologic_0_z = 20, // start Z for the IOLOGIC bels
|
||||
lutram_0_z = 30, // start Z for the IOLOGIC bels
|
||||
vcc_0_z = 277, // virtual VCC bel Z
|
||||
gnd_0_z = 278, // virtual VSS bel Z
|
||||
osc_z = 280, // Z for the oscillator bels
|
||||
bufs_0_z = 281, // Z for long wire buffer bel
|
||||
pll_z = 289, // PLL
|
||||
pllvr_z = 290, // PLLVR
|
||||
free_z = 291 // Must be the last, one can use z starting from this value, adjust accordingly.
|
||||
mux_0_z = 10, // start Z for the MUX2LUT5 bels
|
||||
oddr_0_z = 20, // XXX start Z for the ODDR bels
|
||||
lutram_0_z = 30, // start Z for the LUTRAM bels
|
||||
vcc_0_z = 277, // virtual VCC bel Z
|
||||
gnd_0_z = 278, // virtual VSS bel Z
|
||||
osc_z = 280, // Z for the oscillator bels
|
||||
bufs_0_z = 281, // Z for long wire buffer bel
|
||||
pll_z = 289, // PLL
|
||||
pllvr_z = 290, // PLLVR
|
||||
iologic_z = 291, // IOLOGIC
|
||||
free_z = 293 // Must be the last, one can use z starting from this value, adjust accordingly.
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -105,6 +105,10 @@ std::unique_ptr<CellInfo> create_generic_cell(Context *ctx, IdString type, std::
|
||||
new_cell->addOutput(id_CLKOUTD);
|
||||
new_cell->addOutput(id_CLKOUTD3);
|
||||
new_cell->addOutput(id_LOCK);
|
||||
} else if (type == id_IOLOGIC) {
|
||||
new_cell->addInput(id_PCLK);
|
||||
new_cell->addInput(id_RESET);
|
||||
} else if (type == id_DUMMY_CELL) {
|
||||
} else {
|
||||
log_error("unable to create generic cell of type %s\n", type.c_str(ctx));
|
||||
}
|
||||
|
@ -105,7 +105,7 @@ inline bool is_lc(const BaseCtx *ctx, const CellInfo *cell) { return cell->type
|
||||
|
||||
inline bool is_sram(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == id_RAM16SDP4; }
|
||||
|
||||
inline bool is_iob(const Context *ctx, const CellInfo *cell) { return (cell->type.index == ID_IOB); }
|
||||
inline bool is_iob(const Context *ctx, const CellInfo *cell) { return (cell->type == id_IOB || cell->type == id_IOBS); }
|
||||
|
||||
// Convert a LUT primitive to (part of) an GENERIC_SLICE, swapping ports
|
||||
// as needed. Set no_dff if a DFF is not being used, so that the output
|
||||
|
@ -667,6 +667,9 @@ X(IOBH)
|
||||
X(IOBI)
|
||||
X(IOBJ)
|
||||
|
||||
// misc
|
||||
X(DUMMY_CELL)
|
||||
|
||||
// simplified iobs
|
||||
X(IOBS)
|
||||
X(IOBAS)
|
||||
@ -757,10 +760,18 @@ X(LWO7)
|
||||
|
||||
// IOLOGIC
|
||||
X(TX)
|
||||
X(TX0)
|
||||
X(TX1)
|
||||
X(TX2)
|
||||
X(TX3)
|
||||
X(FCLK)
|
||||
X(PCLK)
|
||||
X(XXX_VSS)
|
||||
X(XXX_VCC)
|
||||
X(XXX_VSS0)
|
||||
X(XXX_VSS1)
|
||||
X(XXX_0)
|
||||
X(XXX_1)
|
||||
X(OBUF_TYPE)
|
||||
X(SBUF)
|
||||
X(DBUF)
|
||||
@ -770,6 +781,20 @@ X(ODDRA)
|
||||
X(ODDRB)
|
||||
X(ODDRCA)
|
||||
X(ODDRCB)
|
||||
X(OSER4)
|
||||
X(OSER8)
|
||||
X(OSER10)
|
||||
X(OVIDEO)
|
||||
X(OSER16)
|
||||
X(IOLOGIC)
|
||||
X(IOLOGICA)
|
||||
X(IOLOGICB)
|
||||
X(IOLOGIC_TYPE)
|
||||
X(IOLOGIC_FCLK)
|
||||
X(IOLOGIC_MASTER_CELL)
|
||||
X(IOLOGIC_AUX_CELL)
|
||||
X(D8)
|
||||
X(D9)
|
||||
|
||||
// Wide LUTs
|
||||
X(MUX2_LUT5)
|
||||
|
159
gowin/pack.cc
159
gowin/pack.cc
@ -812,8 +812,12 @@ static bool is_gowin_diff_iob(const Context *ctx, const CellInfo *cell)
|
||||
static bool is_gowin_iologic(const Context *ctx, const CellInfo *cell)
|
||||
{
|
||||
switch (cell->type.index) {
|
||||
case ID_ODDR: /* fall-through*/
|
||||
case ID_ODDRC:
|
||||
case ID_ODDR: /* fall-through*/
|
||||
case ID_ODDRC: /* fall-through*/
|
||||
case ID_OSER4: /* fall-through*/
|
||||
case ID_OSER8: /* fall-through*/
|
||||
case ID_OSER10: /* fall-through*/
|
||||
case ID_OVIDEO:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
@ -836,7 +840,7 @@ static void pack_iologic(Context *ctx)
|
||||
if (is_gowin_iologic(ctx, ci)) {
|
||||
CellInfo *q0_dst = nullptr;
|
||||
CellInfo *q1_dst = nullptr;
|
||||
switch (ci->type.index) {
|
||||
switch (ci->type.hash()) {
|
||||
case ID_ODDRC: /* fall-through*/
|
||||
case ID_ODDR: {
|
||||
q0_dst = net_only_drives(ctx, ci->ports.at(id_Q0).net, is_iob, id_I);
|
||||
@ -851,7 +855,7 @@ static void pack_iologic(Context *ctx)
|
||||
if (iob_bel != q0_dst->attrs.end()) {
|
||||
// already know there to place, no need of any cluster stuff
|
||||
Loc loc = ctx->getBelLocation(ctx->getBelByNameStr(iob_bel->second.as_string()));
|
||||
loc.z += BelZ::iologic_0_z;
|
||||
loc.z += BelZ::oddr_0_z;
|
||||
ci->attrs[id_BEL] = ctx->getBelName(ctx->getBelByLocation(loc)).str(ctx);
|
||||
} else {
|
||||
// make cluster from ODDR and OBUF
|
||||
@ -864,7 +868,7 @@ static void pack_iologic(Context *ctx)
|
||||
q0_dst->cluster = ci->name;
|
||||
q0_dst->constr_x = 0;
|
||||
q0_dst->constr_y = 0;
|
||||
q0_dst->constr_z = -BelZ::iologic_0_z;
|
||||
q0_dst->constr_z = -BelZ::oddr_0_z;
|
||||
q0_dst->constr_abs_z = false;
|
||||
}
|
||||
|
||||
@ -891,13 +895,142 @@ static void pack_iologic(Context *ctx)
|
||||
ci->addInput(id_XXX_VCC);
|
||||
ci->connectPort(id_XXX_VCC, ctx->nets[ctx->id("$PACKER_VCC_NET")].get());
|
||||
}
|
||||
if (ctx->gw1n9_quirk && iob_bel != q0_dst->attrs.end()) {
|
||||
bool have_XXX_VSS0 =
|
||||
ctx->bels[ctx->getBelByNameStr(iob_bel->second.as_string())].pins.count(id_XXX_VSS0);
|
||||
if (have_XXX_VSS0) {
|
||||
if (iob_bel != q0_dst->attrs.end()) {
|
||||
IdString io_bel_name = ctx->getBelByNameStr(iob_bel->second.as_string());
|
||||
if (ctx->gw1n9_quirk && ctx->bels[io_bel_name].pins.count(id_XXX_VSS0)) {
|
||||
q0_dst->disconnectPort(id_XXX_VSS0);
|
||||
q0_dst->connectPort(id_XXX_VSS0, ctx->nets[ctx->id("$PACKER_VCC_NET")].get());
|
||||
}
|
||||
if (ctx->bels[io_bel_name].pins.count(id_XXX_1)) {
|
||||
q0_dst->disconnectPort(id_XXX_1);
|
||||
q0_dst->connectPort(id_XXX_1, ctx->nets[ctx->id("$PACKER_VCC_NET")].get());
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case ID_OSER4: /* fall-through */
|
||||
case ID_OSER8: /* fall-through */
|
||||
case ID_OSER10: /* fall-through */
|
||||
case ID_OVIDEO: {
|
||||
IdString output = id_Q;
|
||||
IdString output_1 = IdString();
|
||||
if (ci->type == id_OSER4 || ci->type == id_OSER8) {
|
||||
output = id_Q0;
|
||||
output_1 = id_Q1;
|
||||
}
|
||||
q0_dst = net_only_drives(ctx, ci->ports.at(output).net, is_iob, id_I);
|
||||
NPNR_ASSERT(q0_dst != nullptr);
|
||||
|
||||
auto iob_bel = q0_dst->attrs.find(id_BEL);
|
||||
if (iob_bel == q0_dst->attrs.end()) {
|
||||
log_info("No constraints for %s\n", ctx->nameOf(q0_dst));
|
||||
NPNR_ASSERT_FALSE("The pins for IDES/OSER must be specified explicitly.");
|
||||
}
|
||||
|
||||
Loc loc = ctx->getBelLocation(ctx->getBelByNameStr(iob_bel->second.as_string()));
|
||||
loc.z += BelZ::iologic_z;
|
||||
ci->setAttr(id_BEL, ctx->getBelName(ctx->getBelByLocation(loc)).str(ctx));
|
||||
BelId bel = ctx->getBelByLocation(loc);
|
||||
if (bel == BelId()) {
|
||||
log_info("No bel for %s at %s\n", ctx->nameOf(ci), iob_bel->second.as_string().c_str());
|
||||
NPNR_ASSERT_FALSE("Can't place IDES/OSER here");
|
||||
}
|
||||
|
||||
std::string out_mode;
|
||||
switch (ci->type.hash()) {
|
||||
case ID_OSER4:
|
||||
out_mode = "ODDRX2";
|
||||
break;
|
||||
case ID_OSER8:
|
||||
out_mode = "ODDRX4";
|
||||
break;
|
||||
case ID_OSER10:
|
||||
out_mode = "ODDRX5";
|
||||
break;
|
||||
case ID_OVIDEO:
|
||||
out_mode = "VIDEORX";
|
||||
break;
|
||||
}
|
||||
ci->setParam(ctx->id("OUTMODE"), out_mode);
|
||||
bool use_diff_io = false;
|
||||
if (q0_dst->attrs.count(id_DIFF_TYPE)) {
|
||||
ci->setAttr(id_OBUF_TYPE, std::string("DBUF"));
|
||||
use_diff_io = true;
|
||||
} else {
|
||||
ci->setAttr(id_OBUF_TYPE, std::string("SBUF"));
|
||||
}
|
||||
|
||||
// disconnect Q output: it is wired internally
|
||||
delete_nets.insert(ci->ports.at(output).net->name);
|
||||
q0_dst->disconnectPort(id_I);
|
||||
ci->disconnectPort(output);
|
||||
bool have_XXX = ctx->bels[ctx->getBelByNameStr(iob_bel->second.as_string())].pins.count(id_XXX_1);
|
||||
if (have_XXX) {
|
||||
q0_dst->disconnectPort(id_XXX_1);
|
||||
q0_dst->connectPort(id_XXX_1, ctx->nets[ctx->id("$PACKER_VCC_NET")].get());
|
||||
}
|
||||
|
||||
// if Q1 is connected then disconnet it too
|
||||
if (output_1 != IdString() && port_used(ci, output_1)) {
|
||||
q1_dst = net_only_drives(ctx, ci->ports.at(output_1).net, is_iob, id_OEN);
|
||||
if (q1_dst != nullptr) {
|
||||
delete_nets.insert(ci->ports.at(output_1).net->name);
|
||||
q0_dst->disconnectPort(id_OEN);
|
||||
ci->disconnectPort(output_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_IOLOGIC_TYPE, ci->type.str(ctx));
|
||||
|
||||
if (ci->type == id_OSER4) {
|
||||
ci->type = id_IOLOGIC;
|
||||
// two OSER4 share FCLK, check it
|
||||
Loc other_loc = loc;
|
||||
other_loc.z = 1 - loc.z + 2 * BelZ::iologic_z;
|
||||
BelId other_bel = ctx->getBelByLocation(other_loc);
|
||||
CellInfo *other_cell = ctx->getBoundBelCell(other_bel);
|
||||
if (other_cell != nullptr) {
|
||||
NPNR_ASSERT(other_cell->type == id_OSER4);
|
||||
if (ci->ports.at(id_FCLK).net != other_cell->ports.at(id_FCLK).net) {
|
||||
log_error("%s and %s have differnet FCLK nets\n", ctx->nameOf(ci), ctx->nameOf(other_cell));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
std::unique_ptr<CellInfo> dummy =
|
||||
create_generic_cell(ctx, id_DUMMY_CELL, ci->name.str(ctx) + "_DUMMY_IOLOGIC_IO");
|
||||
loc.z = 1 - loc.z + BelZ::iologic_z;
|
||||
if (!use_diff_io) {
|
||||
dummy->setAttr(id_BEL, ctx->getBelName(ctx->getBelByLocation(loc)).str(ctx));
|
||||
new_cells.push_back(std::move(dummy));
|
||||
}
|
||||
loc.z += BelZ::iologic_z;
|
||||
|
||||
std::unique_ptr<CellInfo> aux_cell =
|
||||
create_generic_cell(ctx, id_IOLOGIC, ci->name.str(ctx) + "_AUX");
|
||||
ci->setAttr(ctx->id("IOLOGIC_AUX_CELL"), ci->name.str(ctx) + "_AUX");
|
||||
aux_cell->setParam(ctx->id("OUTMODE"), std::string("DDRENABLE"));
|
||||
aux_cell->setAttr(ctx->id("IOLOGIC_MASTER_CELL"), ci->name.str(ctx));
|
||||
aux_cell->setAttr(id_BEL, ctx->getBelName(ctx->getBelByLocation(loc)).str(ctx));
|
||||
if (port_used(ci, id_RESET)) {
|
||||
aux_cell->connectPort(id_RESET, ci->ports.at(id_RESET).net);
|
||||
}
|
||||
if (port_used(ci, id_PCLK)) {
|
||||
aux_cell->connectPort(id_PCLK, ci->ports.at(id_PCLK).net);
|
||||
}
|
||||
new_cells.push_back(std::move(aux_cell));
|
||||
ci->type = id_IOLOGIC;
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
@ -1139,6 +1272,7 @@ static void pack_io(Context *ctx)
|
||||
IdString new_cell_type = id_IOB;
|
||||
std::string constr_bel_name = std::string("");
|
||||
bool have_xxx_port = false;
|
||||
bool have_xxx0_port = false;
|
||||
// check whether the given IO is limited to simplified IO cells
|
||||
auto constr_bel = ci->attrs.find(id_BEL);
|
||||
if (constr_bel != ci->attrs.end()) {
|
||||
@ -1157,6 +1291,7 @@ static void pack_io(Context *ctx)
|
||||
if (ctx->gw1n9_quirk) {
|
||||
have_xxx_port = ctx->bels[constr_bel].pins.count(id_XXX_VSS0) != 0;
|
||||
}
|
||||
have_xxx0_port = ctx->bels[constr_bel].pins.count(id_XXX_0) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1172,6 +1307,12 @@ static void pack_io(Context *ctx)
|
||||
gwiob->addInput(id_XXX_VSS1);
|
||||
gwiob->connectPort(id_XXX_VSS1, ctx->nets[ctx->id("$PACKER_GND_NET")].get());
|
||||
}
|
||||
if (have_xxx0_port && ci->type != id_IBUF) {
|
||||
gwiob->addInput(id_XXX_0);
|
||||
gwiob->connectPort(id_XXX_0, ctx->nets[ctx->id("$PACKER_GND_NET")].get());
|
||||
gwiob->addInput(id_XXX_1);
|
||||
gwiob->connectPort(id_XXX_1, ctx->nets[ctx->id("$PACKER_GND_NET")].get());
|
||||
}
|
||||
|
||||
packed_cells.insert(ci->name);
|
||||
if (iob != nullptr) {
|
||||
|
Loading…
Reference in New Issue
Block a user