From c565e364bc3fcf48db0ecb2c2891c9939fae5603 Mon Sep 17 00:00:00 2001 From: YRabbit Date: Wed, 1 Jan 2025 22:11:57 +1000 Subject: [PATCH] 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 * Gowin. Registers in IO Check for unconnected ports. Signed-off-by: YRabbit * 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 * Gowin. BUGFIX. Fix FFs compatibility. Flipflops with a fixed ClockEnable input cannot coexist with flipflops with a variable one. Signed-off-by: YRabbit * 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 * 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 * Gowin. Registers in IO. Fix style. Signed-off-by: YRabbit --------- Signed-off-by: YRabbit --- himbaechel/uarch/gowin/constids.inc | 21 ++ himbaechel/uarch/gowin/gowin.cc | 15 +- himbaechel/uarch/gowin/gowin.h | 4 + himbaechel/uarch/gowin/pack.cc | 363 +++++++++++++++++++++++++++- 4 files changed, 393 insertions(+), 10 deletions(-) diff --git a/himbaechel/uarch/gowin/constids.inc b/himbaechel/uarch/gowin/constids.inc index 6e294b8663..88c98fb9dc 100644 --- a/himbaechel/uarch/gowin/constids.inc +++ b/himbaechel/uarch/gowin/constids.inc @@ -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) + diff --git a/himbaechel/uarch/gowin/gowin.cc b/himbaechel/uarch/gowin/gowin.cc index e3a4225ff2..2afbadebb8 100644 --- a/himbaechel/uarch/gowin/gowin.cc +++ b/himbaechel/uarch/gowin/gowin.cc @@ -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 diff --git a/himbaechel/uarch/gowin/gowin.h b/himbaechel/uarch/gowin/gowin.h index e2903e9982..05bfaa30c7 100644 --- a/himbaechel/uarch/gowin/gowin.h +++ b/himbaechel/uarch/gowin/gowin.h @@ -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, diff --git a/himbaechel/uarch/gowin/pack.cc b/himbaechel/uarch/gowin/pack.cc index 21f9c2ea2a..7a82f8591d 100644 --- a/himbaechel/uarch/gowin/pack.cc +++ b/himbaechel/uarch/gowin/pack.cc @@ -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 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 cells_to_remove; + std::vector nets_to_remove; + std::vector> 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();