Gowin. Add the ability to place registers in IOB (#1403)

* Gowin. Add the ability to place registers in IOB

IO blocks have registers: for input, for output and for OutputEnable
signal - IREG, OREG and TREG respectively.

Each of the registers has one implicit non-switched wire, which one
depends on the type of register (IREG has a Q wire, OREG has a D wire).
Although the registers can be activated independently of each other they
share the CLK, ClockEnable and LocalSetReset wires and this places
restrictions on the possible combinations of register types in a single
IO.

Register placement in IO blocks is enabled by specifying the command
line keys --vopt ireg_in_iob, --vopt oreg_in_iob, or --vopt ioreg_in_iob.

It should be noted that specifying these keys leads to attempts to place
registers in IO blocks, but no errors are generated in case of failure.

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

* Gowin. Registers in IO

Check for unconnected ports.

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

* Gowin. IO regs. Verbose warnings.

If an attempt to place an FF in an IO block fails, issue a warning
detailing the reason for the failure, whether it is a register type
conflict, a network requirement violation, or a control signal conflict.

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

* Gowin. BUGFIX. Fix FFs compatibility.

Flipflops with a fixed ClockEnable input cannot coexist with flipflops
with a variable one.

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

* Gowin. FFs in IO.  Changing diagnostic messages.

Placement modes are still specified by the command line keys
ireg_in_iob/oreg_in_iob/ioreg_in_iob, but also introduces more granular
control in the form of attributes at I/O ports:

  (* NOIOBFF *) - registers are never placed in this IO,

  (* IOBFF *) - registers must be placed in this IO, in case of failure
  a warning (not an error) with the reason for nonplacement is issued,

  _attribute_absence_ - no diagnostics will be issued: managed to place - good, failed - not bad either.

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

* Gowin. Registers in IO.

Change the logic for handling command line keys and attributes -
attributes allow routines to be placed in IO regardless of global mode.

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

* Gowin. Registers in IO. Fix style.

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

---------

Signed-off-by: YRabbit <rabbit@yrabbit.cyou>
This commit is contained in:
YRabbit 2025-01-01 22:11:57 +10:00 committed by GitHub
parent 0345b6e803
commit c565e364bc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 393 additions and 10 deletions

View File

@ -1312,3 +1312,24 @@ X(LSREN)
// EMCU
X(EMCU)
// Register placement options
X(IREG_IN_IOB)
X(OREG_IN_IOB)
X(IOREG_IN_IOB)
X(HAS_REG)
X(IREG_TYPE)
X(OREG_TYPE)
X(TREG_TYPE)
X(IREG_CLK_NET)
X(IREG_CE_NET)
X(IREG_LSR_NET)
X(OREG_CLK_NET)
X(OREG_CE_NET)
X(OREG_LSR_NET)
X(TREG_CLK_NET)
X(TREG_CE_NET)
X(TREG_LSR_NET)
X(NOIOBFF)
X(IOBFF)

View File

@ -196,6 +196,17 @@ void GowinImpl::init(Context *ctx)
if (args.options.count("cst")) {
ctx->settings[ctx->id("cst.filename")] = args.options.at("cst");
}
// place registers in IO blocks
if (args.options.count("ireg_in_iob")) {
ctx->settings[id_IREG_IN_IOB] = Property(1);
}
if (args.options.count("oreg_in_iob")) {
ctx->settings[id_OREG_IN_IOB] = Property(1);
}
if (args.options.count("ioreg_in_iob")) {
ctx->settings[id_IOREG_IN_IOB] = Property(1);
}
}
// We do not allow the use of global wires that bypass a special router.
@ -811,7 +822,9 @@ inline bool incompatible_ffs(const CellInfo *ff, const CellInfo *adj_ff)
(ff->type == id_DFFNRE && adj_ff->type != id_DFFNSE) ||
(ff->type == id_DFFNP && adj_ff->type != id_DFFNC) || (ff->type == id_DFFNC && adj_ff->type != id_DFFNP) ||
(ff->type == id_DFFNPE && adj_ff->type != id_DFFNCE) ||
(ff->type == id_DFFNCE && adj_ff->type != id_DFFNPE));
(ff->type == id_DFFNCE && adj_ff->type != id_DFFNPE) || (ff->type == id_DFF && adj_ff->type != id_DFF) ||
(ff->type == id_DFFE && adj_ff->type != id_DFFE) || (ff->type == id_DFFN && adj_ff->type != id_DFFN) ||
(ff->type == id_DFFNE && adj_ff->type != id_DFFNE));
}
// placement validation

View File

@ -21,6 +21,10 @@ inline bool is_dff(const CellInfo *cell) { return type_is_dff(cell->type); }
inline bool type_is_alu(IdString cell_type) { return cell_type == id_ALU; }
inline bool is_alu(const CellInfo *cell) { return type_is_alu(cell->type); }
// io
inline bool type_is_io(IdString cell_type) { return cell_type.in(id_IBUF, id_OBUF, id_IOBUF, id_TBUF); }
inline bool is_io(const CellInfo *cell) { return type_is_io(cell->type); }
inline bool type_is_diffio(IdString cell_type)
{
return cell_type.in(id_ELVDS_IOBUF, id_ELVDS_IBUF, id_ELVDS_TBUF, id_ELVDS_OBUF, id_TLVDS_IOBUF, id_TLVDS_IBUF,

View File

@ -184,8 +184,9 @@ struct GowinPacker
for (auto &cell : ctx->cells) {
CellInfo &ci = *cell.second;
if (!ci.type.in(id_IBUF, id_OBUF, id_TBUF, id_IOBUF))
if (!is_io(&ci)) {
continue;
}
if (ci.attrs.count(id_BEL) == 0) {
log_error("Unconstrained IO:%s\n", ctx->nameOf(&ci));
}
@ -204,10 +205,7 @@ struct GowinPacker
// ===================================
// Differential IO
// ===================================
static bool is_iob(const Context *ctx, CellInfo *cell)
{
return (cell->type.in(id_IBUF, id_OBUF, id_TBUF, id_IOBUF));
}
static bool is_iob(const Context *ctx, CellInfo *cell) { return is_io(cell); }
std::pair<CellInfo *, CellInfo *> get_pn_cells(const CellInfo &ci)
{
@ -352,7 +350,13 @@ struct GowinPacker
NPNR_ASSERT(iob->bel != BelId());
Loc loc = ctx->getBelLocation(iob->bel);
loc.z = loc.z - BelZ::IOBA_Z + BelZ::IOLOGICA_Z;
return ctx->getBelByLocation(loc);
BelId bel = ctx->getBelByLocation(loc);
if (bel != BelId()) {
if (ctx->getBelType(bel) == id_IOLOGICO) {
return bel;
}
}
return BelId();
}
BelId get_iologici_bel(CellInfo *iob)
@ -360,7 +364,13 @@ struct GowinPacker
NPNR_ASSERT(iob->bel != BelId());
Loc loc = ctx->getBelLocation(iob->bel);
loc.z = loc.z - BelZ::IOBA_Z + BelZ::IOLOGICA_Z + 2;
return ctx->getBelByLocation(loc);
BelId bel = ctx->getBelByLocation(loc);
if (bel != BelId()) {
if (ctx->getBelType(bel) == id_IOLOGICI) {
return bel;
}
}
return BelId();
}
void check_iologic_placement(CellInfo &ci, Loc iob_loc, int diff /* 1 - diff */)
@ -502,7 +512,9 @@ struct GowinPacker
out_iob->disconnectPort(id_I);
ci.disconnectPort(out_port);
if (ci.type == id_IOLOGICO_EMPTY) {
ci.movePortTo(id_D, out_iob, id_I);
if (ci.attrs.count(id_HAS_REG) == 0) {
ci.movePortTo(id_D, out_iob, id_I);
}
return;
}
set_daaj_nets(ci, iob_bel);
@ -630,7 +642,9 @@ struct GowinPacker
in_iob->disconnectPort(id_O);
ci.disconnectPort(in_port);
if (ci.type == id_IOLOGICI_EMPTY) {
ci.movePortTo(id_Q, in_iob, id_O);
if (ci.attrs.count(id_HAS_REG) == 0) {
ci.movePortTo(id_Q, in_iob, id_O);
}
return;
}
@ -640,6 +654,334 @@ struct GowinPacker
make_iob_nets(*in_iob);
}
static bool is_ff(const Context *ctx, CellInfo *cell) { return is_dff(cell); }
static bool incompatible_ffs(IdString type_a, IdString type_b)
{
return type_a != type_b &&
((type_a == id_DFFS && type_b != id_DFFR) || (type_a == id_DFFR && type_b != id_DFFS) ||
(type_a == id_DFFSE && type_b != id_DFFRE) || (type_a == id_DFFRE && type_b != id_DFFSE) ||
(type_a == id_DFFP && type_b != id_DFFC) || (type_a == id_DFFC && type_b != id_DFFP) ||
(type_a == id_DFFPE && type_b != id_DFFCE) || (type_a == id_DFFCE && type_b != id_DFFPE) ||
(type_a == id_DFFNS && type_b != id_DFFNR) || (type_a == id_DFFNR && type_b != id_DFFNS) ||
(type_a == id_DFFNSE && type_b != id_DFFNRE) || (type_a == id_DFFNRE && type_b != id_DFFNSE) ||
(type_a == id_DFFNP && type_b != id_DFFNC) || (type_a == id_DFFNC && type_b != id_DFFNP) ||
(type_a == id_DFFNPE && type_b != id_DFFNCE) || (type_a == id_DFFNCE && type_b != id_DFFNPE) ||
(type_a == id_DFF && type_b != id_DFF) || (type_a == id_DFFN && type_b != id_DFFN) ||
(type_a == id_DFFE && type_b != id_DFFE) || (type_a == id_DFFNE && type_b != id_DFFNE));
}
void pack_io_regs()
{
log_info("Pack FFs into IO cells...\n");
std::vector<IdString> cells_to_remove;
std::vector<IdString> nets_to_remove;
std::vector<std::unique_ptr<CellInfo>> new_cells;
for (auto &cell : ctx->cells) {
CellInfo &ci = *cell.second;
if (!is_io(&ci)) {
continue;
}
if (ci.attrs.count(id_NOIOBFF)) {
if (ctx->debug) {
log_info(" NOIOBFF attribute at %s. Skipping FF placement.\n", ctx->nameOf(&ci));
}
continue;
}
// In the case of placing multiple registers in the IO it should be
// noted that the CLK, ClockEnable and LocalSetReset nets must
// match.
const NetInfo *clk_net = nullptr;
const NetInfo *ce_net = nullptr;
const NetInfo *lsr_net = nullptr;
IdString reg_type;
// input reg in IO
CellInfo *iologic_i = nullptr;
if ((ci.type == id_IBUF && (ctx->settings.count(id_IREG_IN_IOB) || ci.attrs.count(id_IOBFF))) ||
(ci.type == id_IOBUF && (ctx->settings.count(id_IOREG_IN_IOB) || ci.attrs.count(id_IOBFF)))) {
if (ci.getPort(id_O) == nullptr) {
continue;
}
// OBUF O -> D FF
CellInfo *ff = net_only_drives(ctx, ci.ports.at(id_O).net, is_ff, id_D);
if (ff == nullptr) {
if (ci.attrs.count(id_IOBFF)) {
log_warning("Port O of %s is not connected to FF.\n", ctx->nameOf(&ci));
}
continue;
}
if (ci.ports.at(id_O).net->users.entries() != 1) {
if (ci.attrs.count(id_IOBFF)) {
log_warning("Port O of %s is the driver of %s multi-sink network.\n", ctx->nameOf(&ci),
ctx->nameOf(ci.ports.at(id_O).net));
}
continue;
}
BelId l_bel = get_iologici_bel(&ci);
if (l_bel == BelId()) {
continue;
}
if (ctx->debug) {
log_info(" trying %s ff as Input Register of %s IO\n", ctx->nameOf(ff), ctx->nameOf(&ci));
}
clk_net = ff->getPort(id_CLK);
ce_net = ff->getPort(id_CE);
for (IdString port : {id_SET, id_RESET, id_PRESET, id_CLEAR}) {
lsr_net = ff->getPort(port);
if (lsr_net != nullptr) {
break;
}
}
reg_type = ff->type;
// create IOLOGIC cell for flipflop
IdString iologic_name = gwu.create_aux_name(ci.name, 0, "_iobff$");
auto iologic_cell = gwu.create_cell(iologic_name, id_IOLOGICI_EMPTY);
new_cells.push_back(std::move(iologic_cell));
iologic_i = new_cells.back().get();
// move ports
for (auto &port : ff->ports) {
IdString port_name = port.first;
ff->movePortTo(port_name, iologic_i, port_name != id_Q ? port_name : id_Q4);
}
if (ctx->verbose) {
log_info(" place FF %s into IBUF %s, make iologic_i %s\n", ctx->nameOf(ff), ctx->nameOf(&ci),
ctx->nameOf(iologic_i));
}
iologic_i->setAttr(id_HAS_REG, 1);
iologic_i->setAttr(id_IREG_TYPE, ff->type.str(ctx));
cells_to_remove.push_back(ff->name);
}
// output reg in IO
CellInfo *iologic_o = nullptr;
if ((ci.type == id_OBUF && (ctx->settings.count(id_OREG_IN_IOB) || ci.attrs.count(id_IOBFF))) ||
(ci.type == id_IOBUF && (ctx->settings.count(id_IOREG_IN_IOB) || ci.attrs.count(id_IOBFF)))) {
do {
if (ci.getPort(id_I) == nullptr) {
break;
}
// OBUF I <- Q FF
CellInfo *ff = net_driven_by(ctx, ci.ports.at(id_I).net, is_ff, id_Q);
if (ff == nullptr) {
if (ci.attrs.count(id_IOBFF)) {
log_warning("Port I of %s is not connected to FF.\n", ctx->nameOf(&ci));
}
} else {
if (ci.ports.at(id_I).net->users.entries() != 1) {
if (ci.attrs.count(id_IOBFF)) {
log_warning("Port I of %s is not the only sink on the %s network.\n", ctx->nameOf(&ci),
ctx->nameOf(ci.ports.at(id_I).net));
}
break;
}
BelId l_bel = get_iologico_bel(&ci);
if (l_bel == BelId()) {
break;
}
const NetInfo *this_clk_net = ff->getPort(id_CLK);
const NetInfo *this_ce_net = ff->getPort(id_CE);
const NetInfo *this_lsr_net;
for (IdString port : {id_SET, id_RESET, id_PRESET, id_CLEAR}) {
this_lsr_net = ff->getPort(port);
if (this_lsr_net != nullptr) {
break;
}
}
// The IOBUF may already have registers placed
if (ci.type == id_IOBUF) {
if (iologic_i != nullptr) {
if (incompatible_ffs(ff->type, reg_type)) {
if (ci.attrs.count(id_IOBFF)) {
log_warning("OREG type conflict:%s:%s vs %s IREG:%s\n", ctx->nameOf(ff),
ff->type.c_str(ctx), ctx->nameOf(&ci), reg_type.c_str(ctx));
}
break;
} else {
if (clk_net != this_clk_net || ce_net != this_ce_net || lsr_net != this_lsr_net) {
if (clk_net != this_clk_net) {
if (ci.attrs.count(id_IOBFF)) {
log_warning("Conflicting OREG CLK nets at %s:'%s' vs '%s'\n",
ctx->nameOf(&ci), ctx->nameOf(clk_net),
ctx->nameOf(this_clk_net));
}
}
if (ce_net != this_ce_net) {
if (ci.attrs.count(id_IOBFF)) {
log_warning("Conflicting OREG CE nets at %s:'%s' vs '%s'\n",
ctx->nameOf(&ci), ctx->nameOf(ce_net),
ctx->nameOf(this_ce_net));
}
}
if (lsr_net != this_lsr_net) {
if (ci.attrs.count(id_IOBFF)) {
log_warning("Conflicting OREG LSR nets at %s:'%s' vs '%s'\n",
ctx->nameOf(&ci), ctx->nameOf(lsr_net),
ctx->nameOf(this_lsr_net));
}
}
break;
}
}
} else {
clk_net = this_clk_net;
ce_net = this_ce_net;
lsr_net = this_lsr_net;
reg_type = ff->type;
}
}
// create IOLOGIC cell for flipflop
IdString iologic_name = gwu.create_aux_name(ci.name, 1, "_iobff$");
auto iologic_cell = gwu.create_cell(iologic_name, id_IOLOGICO_EMPTY);
new_cells.push_back(std::move(iologic_cell));
iologic_o = new_cells.back().get();
// move ports
for (auto &port : ff->ports) {
IdString port_name = port.first;
ff->movePortTo(port_name, iologic_o, port_name != id_D ? port_name : id_D0);
}
if (ctx->verbose) {
log_info(" place FF %s into OBUF %s, make iologic_o %s\n", ctx->nameOf(ff),
ctx->nameOf(&ci), ctx->nameOf(iologic_o));
}
iologic_o->setAttr(id_HAS_REG, 1);
iologic_o->setAttr(id_OREG_TYPE, ff->type.str(ctx));
cells_to_remove.push_back(ff->name);
}
break;
} while (false);
}
// output enable reg in IO
if (ci.type == id_IOBUF && (ctx->settings.count(id_IOREG_IN_IOB) || ci.attrs.count(id_IOBFF))) {
do {
if (ci.getPort(id_OEN) == nullptr) {
break;
}
// IOBUF OEN <- Q FF
CellInfo *ff = net_driven_by(ctx, ci.ports.at(id_OEN).net, is_ff, id_Q);
if (ff != nullptr) {
if (ci.ports.at(id_OEN).net->users.entries() != 1) {
if (ci.attrs.count(id_IOBFF)) {
log_warning("Port OEN of %s is not the only sink on the %s network.\n",
ctx->nameOf(&ci), ctx->nameOf(ci.ports.at(id_OEN).net));
}
break;
}
BelId l_bel = get_iologico_bel(&ci);
if (l_bel == BelId()) {
break;
}
if (ctx->debug) {
log_info(" trying %s ff as Output Enable Register of %s IO\n", ctx->nameOf(ff),
ctx->nameOf(&ci));
}
const NetInfo *this_clk_net = ff->getPort(id_CLK);
const NetInfo *this_ce_net = ff->getPort(id_CE);
const NetInfo *this_lsr_net;
for (IdString port : {id_SET, id_RESET, id_PRESET, id_CLEAR}) {
this_lsr_net = ff->getPort(port);
if (this_lsr_net != nullptr) {
break;
}
}
// The IOBUF may already have registers placed
if (iologic_i != nullptr || iologic_o != nullptr) {
if (iologic_o == nullptr) {
iologic_o = iologic_i;
}
if (incompatible_ffs(ff->type, reg_type)) {
if (ci.attrs.count(id_IOBFF)) {
log_warning("TREG type conflict:%s:%s vs %s IREG/OREG:%s\n", ctx->nameOf(ff),
ff->type.c_str(ctx), ctx->nameOf(&ci), reg_type.c_str(ctx));
}
break;
} else {
if (clk_net != this_clk_net || ce_net != this_ce_net || lsr_net != this_lsr_net) {
if (clk_net != this_clk_net) {
if (ci.attrs.count(id_IOBFF)) {
log_warning("Conflicting TREG CLK nets at %s:'%s' vs '%s'\n",
ctx->nameOf(&ci), ctx->nameOf(clk_net),
ctx->nameOf(this_clk_net));
}
}
if (ce_net != this_ce_net) {
if (ci.attrs.count(id_IOBFF)) {
log_warning("Conflicting TREG CE nets at %s:'%s' vs '%s'\n",
ctx->nameOf(&ci), ctx->nameOf(ce_net),
ctx->nameOf(this_ce_net));
}
}
if (lsr_net != this_lsr_net) {
if (ci.attrs.count(id_IOBFF)) {
log_warning("Conflicting TREG LSR nets at %s:'%s' vs '%s'\n",
ctx->nameOf(&ci), ctx->nameOf(lsr_net),
ctx->nameOf(this_lsr_net));
}
}
break;
}
}
}
if (iologic_o == nullptr) {
// create IOLOGIC cell for flipflop
IdString iologic_name = gwu.create_aux_name(ci.name, 2, "_iobff$");
auto iologic_cell = gwu.create_cell(iologic_name, id_IOLOGICO_EMPTY);
new_cells.push_back(std::move(iologic_cell));
iologic_o = new_cells.back().get();
}
// move ports
for (auto &port : ff->ports) {
IdString port_name = port.first;
if (port_name == id_Q) {
continue;
}
ff->movePortTo(port_name, iologic_o, port_name != id_D ? port_name : id_TX);
}
nets_to_remove.push_back(ci.getPort(id_OEN)->name);
ci.disconnectPort(id_OEN);
ff->disconnectPort(id_Q);
if (ctx->verbose) {
log_info(" place FF %s into IOBUF %s, make iologic_o %s\n", ctx->nameOf(ff),
ctx->nameOf(&ci), ctx->nameOf(iologic_o));
}
iologic_o->setAttr(id_HAS_REG, 1);
iologic_o->setAttr(id_TREG_TYPE, ff->type.str(ctx));
cells_to_remove.push_back(ff->name);
}
break;
} while (false);
}
}
for (auto cell : cells_to_remove) {
ctx->cells.erase(cell);
}
for (auto &ncell : new_cells) {
ctx->cells[ncell->name] = std::move(ncell);
}
for (auto net : nets_to_remove) {
ctx->nets.erase(net);
}
}
void pack_iodelay()
{
log_info("Pack IODELAY...\n");
@ -3593,6 +3935,9 @@ struct GowinPacker
pack_diff_iobs();
ctx->check();
pack_io_regs();
ctx->check();
pack_iodelay();
ctx->check();