From 0399b8865ea9cc7da4bf0e4bc6c97a75cedf47a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miodrag=20Milanovi=C4=87?= Date: Tue, 2 Sep 2025 08:03:22 +0200 Subject: [PATCH] gatemate: Enable placing RAM halfs (#1544) * gatemate: Split BRAMs into halfs * Cleanups * move code arround * optmize remapping halfs * Name RAM cells * fix cluster setting for cascade mode * attach ECC pins * rewire global clocks * bump chip database version * Fix KEEPER setting * Fix conflict check * cleanup --- himbaechel/uarch/gatemate/constids.inc | 342 ++++++++++++++++++++++ himbaechel/uarch/gatemate/delay.cc | 6 +- himbaechel/uarch/gatemate/extra_data.h | 4 + himbaechel/uarch/gatemate/gatemate.cc | 48 ++- himbaechel/uarch/gatemate/gatemate.h | 5 +- himbaechel/uarch/gatemate/gen/arch_gen.py | 4 +- himbaechel/uarch/gatemate/pack.cc | 5 +- himbaechel/uarch/gatemate/pack.h | 6 +- himbaechel/uarch/gatemate/pack_bram.cc | 335 +++++++++++++-------- himbaechel/uarch/gatemate/pack_io.cc | 2 +- 10 files changed, 621 insertions(+), 136 deletions(-) diff --git a/himbaechel/uarch/gatemate/constids.inc b/himbaechel/uarch/gatemate/constids.inc index 024e068f..b7caeec2 100644 --- a/himbaechel/uarch/gatemate/constids.inc +++ b/himbaechel/uarch/gatemate/constids.inc @@ -1751,6 +1751,346 @@ X(F_RSTN) //X(CLOCK3) //X(CLOCK4) +// hardware primitive RAM_HALF_U +X(RAM_HALF_U) +// RAM_HALF_U pins +//X(CLKA[0]) +//X(ENA[0]) +//X(GLWEA[0]) +//X(CLKB[0]) +//X(ENB[0]) +//X(GLWEB[0]) +//X(WEA[0]) +//X(WEA[1]) +//X(WEA[2]) +//X(WEA[3]) +//X(WEA[4]) +//X(WEA[5]) +//X(WEA[6]) +//X(WEA[7]) +//X(WEA[8]) +//X(WEA[9]) +//X(WEA[10]) +//X(WEA[11]) +//X(WEA[12]) +//X(WEA[13]) +//X(WEA[14]) +//X(WEA[15]) +//X(WEA[16]) +//X(WEA[17]) +//X(WEA[18]) +//X(WEA[19]) +//X(WEB[0]) +//X(WEB[1]) +//X(WEB[2]) +//X(WEB[3]) +//X(WEB[4]) +//X(WEB[5]) +//X(WEB[6]) +//X(WEB[7]) +//X(WEB[8]) +//X(WEB[9]) +//X(WEB[10]) +//X(WEB[11]) +//X(WEB[12]) +//X(WEB[13]) +//X(WEB[14]) +//X(WEB[15]) +//X(WEB[16]) +//X(WEB[17]) +//X(WEB[18]) +//X(WEB[19]) +//X(ADDRA0[0]) +//X(ADDRA0[1]) +//X(ADDRA0[2]) +//X(ADDRA0[3]) +//X(ADDRA0[4]) +//X(ADDRA0[5]) +//X(ADDRA0[6]) +//X(ADDRA0[7]) +//X(ADDRA0[8]) +//X(ADDRA0[9]) +//X(ADDRA0[10]) +//X(ADDRA0[11]) +//X(ADDRA0[12]) +//X(ADDRA0[13]) +//X(ADDRA0[14]) +//X(ADDRA0[15]) +//X(ADDRB0[0]) +//X(ADDRB0[1]) +//X(ADDRB0[2]) +//X(ADDRB0[3]) +//X(ADDRB0[4]) +//X(ADDRB0[5]) +//X(ADDRB0[6]) +//X(ADDRB0[7]) +//X(ADDRB0[8]) +//X(ADDRB0[9]) +//X(ADDRB0[10]) +//X(ADDRB0[11]) +//X(ADDRB0[12]) +//X(ADDRB0[13]) +//X(ADDRB0[14]) +//X(ADDRB0[15]) +//X(DIA[0]) +//X(DIA[1]) +//X(DIA[2]) +//X(DIA[3]) +//X(DIA[4]) +//X(DIA[5]) +//X(DIA[6]) +//X(DIA[7]) +//X(DIA[8]) +//X(DIA[9]) +//X(DIA[10]) +//X(DIA[11]) +//X(DIA[12]) +//X(DIA[13]) +//X(DIA[14]) +//X(DIA[15]) +//X(DIA[16]) +//X(DIA[17]) +//X(DIA[18]) +//X(DIA[19]) +//X(DIB[0]) +//X(DIB[1]) +//X(DIB[2]) +//X(DIB[3]) +//X(DIB[4]) +//X(DIB[5]) +//X(DIB[6]) +//X(DIB[7]) +//X(DIB[8]) +//X(DIB[9]) +//X(DIB[10]) +//X(DIB[11]) +//X(DIB[12]) +//X(DIB[13]) +//X(DIB[14]) +//X(DIB[15]) +//X(DIB[16]) +//X(DIB[17]) +//X(DIB[18]) +//X(DIB[19]) +//X(DOA[0]) +//X(DOA[1]) +//X(DOA[2]) +//X(DOA[3]) +//X(DOA[4]) +//X(DOA[5]) +//X(DOA[6]) +//X(DOA[7]) +//X(DOA[8]) +//X(DOA[9]) +//X(DOA[10]) +//X(DOA[11]) +//X(DOA[12]) +//X(DOA[13]) +//X(DOA[14]) +//X(DOA[15]) +//X(DOA[16]) +//X(DOA[17]) +//X(DOA[18]) +//X(DOA[19]) +//X(DOB[0]) +//X(DOB[1]) +//X(DOB[2]) +//X(DOB[3]) +//X(DOB[4]) +//X(DOB[5]) +//X(DOB[6]) +//X(DOB[7]) +//X(DOB[8]) +//X(DOB[9]) +//X(DOB[10]) +//X(DOB[11]) +//X(DOB[12]) +//X(DOB[13]) +//X(DOB[14]) +//X(DOB[15]) +//X(DOB[16]) +//X(DOB[17]) +//X(DOB[18]) +//X(DOB[19]) +//X(ECC1B_ERRA[0]) +//X(ECC1B_ERRB[0]) +//X(ECC2B_ERRA[0]) +//X(ECC2B_ERRB[0]) +//X(CLOCK1) +//X(CLOCK2) +//X(CLOCK3) +//X(CLOCK4) + +// hardware primitive RAM_HALF_L +X(RAM_HALF_L) +// RAM_HALF_L pins +//X(CLKA[0]) +//X(ENA[0]) +//X(GLWEA[0]) +//X(CLKB[0]) +//X(ENB[0]) +//X(GLWEB[0]) +//X(WEA[0]) +//X(WEA[1]) +//X(WEA[2]) +//X(WEA[3]) +//X(WEA[4]) +//X(WEA[5]) +//X(WEA[6]) +//X(WEA[7]) +//X(WEA[8]) +//X(WEA[9]) +//X(WEA[10]) +//X(WEA[11]) +//X(WEA[12]) +//X(WEA[13]) +//X(WEA[14]) +//X(WEA[15]) +//X(WEA[16]) +//X(WEA[17]) +//X(WEA[18]) +//X(WEA[19]) +//X(WEB[0]) +//X(WEB[1]) +//X(WEB[2]) +//X(WEB[3]) +//X(WEB[4]) +//X(WEB[5]) +//X(WEB[6]) +//X(WEB[7]) +//X(WEB[8]) +//X(WEB[9]) +//X(WEB[10]) +//X(WEB[11]) +//X(WEB[12]) +//X(WEB[13]) +//X(WEB[14]) +//X(WEB[15]) +//X(WEB[16]) +//X(WEB[17]) +//X(WEB[18]) +//X(WEB[19]) +//X(ADDRA0[0]) +//X(ADDRA0[1]) +//X(ADDRA0[2]) +//X(ADDRA0[3]) +//X(ADDRA0[4]) +//X(ADDRA0[5]) +//X(ADDRA0[6]) +//X(ADDRA0[7]) +//X(ADDRA0[8]) +//X(ADDRA0[9]) +//X(ADDRA0[10]) +//X(ADDRA0[11]) +//X(ADDRA0[12]) +//X(ADDRA0[13]) +//X(ADDRA0[14]) +//X(ADDRA0[15]) +//X(ADDRB0[0]) +//X(ADDRB0[1]) +//X(ADDRB0[2]) +//X(ADDRB0[3]) +//X(ADDRB0[4]) +//X(ADDRB0[5]) +//X(ADDRB0[6]) +//X(ADDRB0[7]) +//X(ADDRB0[8]) +//X(ADDRB0[9]) +//X(ADDRB0[10]) +//X(ADDRB0[11]) +//X(ADDRB0[12]) +//X(ADDRB0[13]) +//X(ADDRB0[14]) +//X(ADDRB0[15]) +//X(DIA[0]) +//X(DIA[1]) +//X(DIA[2]) +//X(DIA[3]) +//X(DIA[4]) +//X(DIA[5]) +//X(DIA[6]) +//X(DIA[7]) +//X(DIA[8]) +//X(DIA[9]) +//X(DIA[10]) +//X(DIA[11]) +//X(DIA[12]) +//X(DIA[13]) +//X(DIA[14]) +//X(DIA[15]) +//X(DIA[16]) +//X(DIA[17]) +//X(DIA[18]) +//X(DIA[19]) +//X(DIB[0]) +//X(DIB[1]) +//X(DIB[2]) +//X(DIB[3]) +//X(DIB[4]) +//X(DIB[5]) +//X(DIB[6]) +//X(DIB[7]) +//X(DIB[8]) +//X(DIB[9]) +//X(DIB[10]) +//X(DIB[11]) +//X(DIB[12]) +//X(DIB[13]) +//X(DIB[14]) +//X(DIB[15]) +//X(DIB[16]) +//X(DIB[17]) +//X(DIB[18]) +//X(DIB[19]) +//X(DOA[0]) +//X(DOA[1]) +//X(DOA[2]) +//X(DOA[3]) +//X(DOA[4]) +//X(DOA[5]) +//X(DOA[6]) +//X(DOA[7]) +//X(DOA[8]) +//X(DOA[9]) +//X(DOA[10]) +//X(DOA[11]) +//X(DOA[12]) +//X(DOA[13]) +//X(DOA[14]) +//X(DOA[15]) +//X(DOA[16]) +//X(DOA[17]) +//X(DOA[18]) +//X(DOA[19]) +//X(DOB[0]) +//X(DOB[1]) +//X(DOB[2]) +//X(DOB[3]) +//X(DOB[4]) +//X(DOB[5]) +//X(DOB[6]) +//X(DOB[7]) +//X(DOB[8]) +//X(DOB[9]) +//X(DOB[10]) +//X(DOB[11]) +//X(DOB[12]) +//X(DOB[13]) +//X(DOB[14]) +//X(DOB[15]) +//X(DOB[16]) +//X(DOB[17]) +//X(DOB[18]) +//X(DOB[19]) +//X(ECC1B_ERRA[0]) +//X(ECC1B_ERRB[0]) +//X(ECC2B_ERRA[0]) +//X(ECC2B_ERRB[0]) +//X(CLOCK1) +//X(CLOCK2) +//X(CLOCK3) +//X(CLOCK4) + // hardware primitive SERDES X(SERDES) // SERDES pins @@ -2283,6 +2623,8 @@ X(L2T4_UPPER) X(CPE_MX8) X(CPE_BRIDGE) X(MULT_INVERT) +X(RAM_HALF) +X(RAM_HALF_DUMMY) // Timing X(timing_ADDF2x_IN5_8_comb2) diff --git a/himbaechel/uarch/gatemate/delay.cc b/himbaechel/uarch/gatemate/delay.cc index 1334914e..d87a8a82 100644 --- a/himbaechel/uarch/gatemate/delay.cc +++ b/himbaechel/uarch/gatemate/delay.cc @@ -153,7 +153,7 @@ bool GateMateImpl::getCellDelay(const CellInfo *cell, IdString fromPort, IdStrin return get_delay_from_tmg_db(ctx->idf("timing_clkin_%s_%s", fromPort.c_str(ctx), toPort.c_str(ctx)), delay); } else if (cell->type.in(id_GLBOUT)) { return get_delay_from_tmg_db(ctx->idf("timing_glbout_%s_%s", fromPort.c_str(ctx), toPort.c_str(ctx)), delay); - } else if (cell->type.in(id_RAM)) { + } else if (cell->type.in(id_RAM, id_RAM_HALF)) { return false; } return false; @@ -257,7 +257,7 @@ TimingPortClass GateMateImpl::getPortTimingClass(const CellInfo *cell, IdString if (port.in(id_CLK)) return TMG_CLOCK_INPUT; return TMG_IGNORE; - } else if (cell->type == id_RAM) { + } else if (cell->type.in(id_RAM, id_RAM_HALF)) { std::string name = port.str(ctx); if (boost::starts_with(name, "CLKA[") || boost::starts_with(name, "CLKB[") || boost::starts_with(name, "CLOCK")) return TMG_CLOCK_INPUT; @@ -326,7 +326,7 @@ TimingClockingInfo GateMateImpl::getPortClockingInfo(const CellInfo *cell, IdStr get_delay_from_tmg_db(id_timing_del_CPE_CP_Q, delay); info.clockToQ += delay; } - } else if (cell->type == id_RAM) { + } else if (cell->type.in(id_RAM, id_RAM_HALF)) { std::string name = port.str(ctx); if (boost::starts_with(name, "CLOCK")) get_delay_from_tmg_db(id_timing_RAM_NOECC_IOPATH_1, info.clockToQ); diff --git a/himbaechel/uarch/gatemate/extra_data.h b/himbaechel/uarch/gatemate/extra_data.h index 9252a389..59aeabcb 100644 --- a/himbaechel/uarch/gatemate/extra_data.h +++ b/himbaechel/uarch/gatemate/extra_data.h @@ -103,6 +103,10 @@ enum CPE_Z CPE_COMP_Z = 6, CPE_CPLINES_Z = 7, CPE_LT_FULL_Z = 8, + + RAM_FULL_Z = 10, + RAM_HALF_U_Z = 11, + RAM_HALF_L_Z = 12, }; enum ClusterPlacement diff --git a/himbaechel/uarch/gatemate/gatemate.cc b/himbaechel/uarch/gatemate/gatemate.cc index 647b11cc..a60fc024 100644 --- a/himbaechel/uarch/gatemate/gatemate.cc +++ b/himbaechel/uarch/gatemate/gatemate.cc @@ -172,10 +172,10 @@ bool GateMateImpl::isBelLocationValid(BelId bel, bool explain_invalid) const ctx->getBelByLocation(Loc(loc.x, loc.y, loc.z == CPE_FF_L_Z ? CPE_FF_U_Z : CPE_FF_L_Z))); if (adj_half) { const auto &half_data = fast_cell_info.at(cell->flat_index); - if (half_data.dff_used) { + if (half_data.used) { const auto &adj_data = fast_cell_info.at(adj_half->flat_index); - if (adj_data.dff_used) { - if (adj_data.ff_config != half_data.ff_config) + if (adj_data.used) { + if (adj_data.config != half_data.config) return false; if (adj_data.ff_en != half_data.ff_en) return false; @@ -187,7 +187,24 @@ bool GateMateImpl::isBelLocationValid(BelId bel, bool explain_invalid) const } } return true; + } else if (ctx->getBelBucketForBel(bel) == id_RAM_HALF) { + Loc loc = ctx->getBelLocation(bel); + const CellInfo *adj_half = + ctx->getBoundBelCell(ctx->getBelByLocation(Loc(loc.x, loc.z == RAM_HALF_L_Z ? loc.y - 8 : loc.y + 8, + loc.z == RAM_HALF_L_Z ? RAM_HALF_U_Z : RAM_HALF_L_Z))); + if (adj_half) { + const auto &half_data = fast_cell_info.at(cell->flat_index); + if (half_data.used) { + const auto &adj_data = fast_cell_info.at(adj_half->flat_index); + if (adj_data.used) { + if (adj_data.config != half_data.config) + return false; + } + } + } + return true; } + return true; } @@ -299,6 +316,15 @@ int GateMateImpl::get_dff_config(CellInfo *dff) const return val; } +int GateMateImpl::get_ram_config(CellInfo *ram) const +{ + int val = 0; + val |= int_or_default(ram->params, id_RAM_cfg_ecc_enable, 0); + val <<= 2; + val |= int_or_default(ram->params, id_RAM_cfg_sram_mode, 0); + return val; +} + void GateMateImpl::assign_cell_info() { fast_cell_info.resize(ctx->cells.size()); @@ -309,8 +335,12 @@ void GateMateImpl::assign_cell_info() fc.ff_en = ci->getPort(id_EN); fc.ff_clk = ci->getPort(id_CLK); fc.ff_sr = ci->getPort(id_SR); - fc.ff_config = get_dff_config(ci); - fc.dff_used = true; + fc.config = get_dff_config(ci); + fc.used = true; + } + if (ci->type == id_RAM_HALF) { + fc.config = get_ram_config(ci); + fc.used = true; } } } @@ -327,6 +357,8 @@ IdString GateMateImpl::getBelBucketForCellType(IdString cell_type) const return id_CPE_FF; else if (cell_type.in(id_CPE_RAMIO, id_CPE_RAMI, id_CPE_RAMO)) return id_CPE_RAMIO; + else if (cell_type.in(id_RAM_HALF, id_RAM_HALF_DUMMY)) + return id_RAM_HALF; else return cell_type; } @@ -340,6 +372,8 @@ BelBucketId GateMateImpl::getBelBucketForBel(BelId bel) const return id_CPE_FF; else if (bel_type.in(id_CPE_RAMIO_U, id_CPE_RAMIO_L)) return id_CPE_RAMIO; + else if (bel_type.in(id_RAM_HALF_U, id_RAM_HALF_L)) + return id_RAM_HALF; return bel_type; } @@ -359,6 +393,10 @@ bool GateMateImpl::isValidBelForCellType(IdString cell_type, BelId bel) const return cell_type.in(id_CPE_FF_L, id_CPE_FF, id_CPE_LATCH); else if (bel_type.in(id_CPE_RAMIO_U, id_CPE_RAMIO_L)) return cell_type.in(id_CPE_RAMIO, id_CPE_RAMI, id_CPE_RAMO); + else if (bel_type == id_RAM_HALF_U) + return cell_type.in(id_RAM_HALF, id_RAM_HALF_DUMMY); + else if (bel_type == id_RAM_HALF_L) + return cell_type.in(id_RAM_HALF, id_RAM_HALF_DUMMY); else return (bel_type == cell_type); } diff --git a/himbaechel/uarch/gatemate/gatemate.h b/himbaechel/uarch/gatemate/gatemate.h index d7fe2e76..d8524351 100644 --- a/himbaechel/uarch/gatemate/gatemate.h +++ b/himbaechel/uarch/gatemate/gatemate.h @@ -68,6 +68,7 @@ struct GateMateImpl : HimbaechelAPI const GateMateTileExtraDataPOD *tile_extra_data(int tile) const; int get_dff_config(CellInfo *dff) const; + int get_ram_config(CellInfo *ram) const; std::set available_pads; std::map bel_to_pad; @@ -105,9 +106,9 @@ struct GateMateImpl : HimbaechelAPI { // slice info const NetInfo *ff_en = nullptr, *ff_clk = nullptr, *ff_sr = nullptr; - int ff_config = 0; + int config = 0; int signal_used = -1; - bool dff_used = false; + bool used = false; }; std::vector fast_cell_info; std::map> pin_to_constr; diff --git a/himbaechel/uarch/gatemate/gen/arch_gen.py b/himbaechel/uarch/gatemate/gen/arch_gen.py index 522e726c..72c713d3 100644 --- a/himbaechel/uarch/gatemate/gen/arch_gen.py +++ b/himbaechel/uarch/gatemate/gen/arch_gen.py @@ -193,7 +193,7 @@ def set_timings(ch): # assert k in timing, f"pip class {k} not found in timing data" # tmg.set_pip_class(grade=speed, name=k, delay=convert_timing(timing[k])) -EXPECTED_VERSION = 1.4 +EXPECTED_VERSION = 1.5 def main(): # Range needs to be +1, but we are adding +2 more to coordinates, since @@ -233,7 +233,7 @@ def main(): tt.create_wire(wire.name, wire.type) for prim in sorted(die.get_primitives_for_type(type_name)): bel = tt.create_bel(prim.name, prim.type, prim.z) - if (prim.name in ["CPE_LT_FULL"]): + if (prim.name in ["CPE_LT_FULL", "RAM"]): bel.flags |= BEL_FLAG_HIDDEN extra = BelExtraData() for constr in sorted(die.get_pins_constraint(type_name, prim.name, prim.type)): diff --git a/himbaechel/uarch/gatemate/pack.cc b/himbaechel/uarch/gatemate/pack.cc index f2caef1d..bba5cd92 100644 --- a/himbaechel/uarch/gatemate/pack.cc +++ b/himbaechel/uarch/gatemate/pack.cc @@ -293,7 +293,7 @@ void GateMatePacker::rename_param(CellInfo *cell, IdString name, IdString new_na } } -void GateMatePacker::repack() +void GateMatePacker::repack_cpe() { log_info("Repacking CPEs..\n"); for (auto &cell : ctx->cells) { @@ -407,7 +407,8 @@ void GateMateImpl::pack() void GateMateImpl::repack() { GateMatePacker packer(ctx, this); - packer.repack(); + packer.repack_ram(); + packer.repack_cpe(); } NEXTPNR_NAMESPACE_END diff --git a/himbaechel/uarch/gatemate/pack.h b/himbaechel/uarch/gatemate/pack.h index b589563e..8736aa6e 100644 --- a/himbaechel/uarch/gatemate/pack.h +++ b/himbaechel/uarch/gatemate/pack.h @@ -66,7 +66,8 @@ struct GateMatePacker void remove_clocking(); void cleanup(); - void repack(); + void repack_cpe(); + void repack_ram(); private: void rename_param(CellInfo *cell, IdString name, IdString new_name, int width); @@ -82,6 +83,7 @@ struct GateMatePacker void optimize_ff(); void count_cell(CellInfo &ci); void move_connections(NetInfo *from_net, NetInfo *to_net); + void remap_ram_half(CellInfo *half, CellInfo *cell, int num); PllCfgRecord get_pll_settings(double f_ref, double f_core, int mode, int low_jitter, bool pdiv0_mux, bool feedback); @@ -102,7 +104,7 @@ struct GateMatePacker // Cell creating CellInfo *create_cell_ptr(IdString type, IdString name); void flush_cells(); - void pack_ram_cell(CellInfo &ci, CellInfo *cell, int num, bool is_split); + void pack_ram_cell(CellInfo &ci, CellInfo *cell, bool is_split); void copy_constraint(NetInfo *in_net, NetInfo *out_net); pool packed_cells; diff --git a/himbaechel/uarch/gatemate/pack_bram.cc b/himbaechel/uarch/gatemate/pack_bram.cc index 80778d77..6c9c3ba3 100644 --- a/himbaechel/uarch/gatemate/pack_bram.cc +++ b/himbaechel/uarch/gatemate/pack_bram.cc @@ -118,7 +118,7 @@ static void rename_or_move(CellInfo *main, CellInfo *other, IdString port, IdStr main->movePortTo(port, other, other_port); } -void GateMatePacker::pack_ram_cell(CellInfo &ci, CellInfo *cell, int num, bool is_split) +void GateMatePacker::pack_ram_cell(CellInfo &ci, CellInfo *cell, bool is_split) { // Port Widths int a_rd_width = int_or_default(cell->params, id_A_RD_WIDTH, 0); @@ -161,98 +161,71 @@ void GateMatePacker::pack_ram_cell(CellInfo &ci, CellInfo *cell, int num, bool i uint8_t a_we = ram_ctrl_signal(cell, id_A_WE, false); uint8_t b_we = ram_ctrl_signal(cell, id_B_WE, false); - if (num) { + ci.params[id_RAM_cfg_forward_a0_clk] = Property(cfg_a, 8); + if (!is_split) ci.params[id_RAM_cfg_forward_a1_clk] = Property(cfg_a, 8); + + ci.params[id_RAM_cfg_forward_b0_clk] = Property(cfg_b, 8); + if (!is_split) ci.params[id_RAM_cfg_forward_b1_clk] = Property(cfg_b, 8); - ci.params[id_RAM_cfg_forward_a1_en] = Property(a_en, 8); - ci.params[id_RAM_cfg_forward_b1_en] = Property(b_en, 8); + ci.params[id_RAM_cfg_forward_a0_en] = Property(a_en, 8); + ci.params[id_RAM_cfg_forward_b0_en] = Property(b_en, 8); - ci.params[id_RAM_cfg_forward_a1_we] = Property(a_we, 8); - ci.params[id_RAM_cfg_forward_b1_we] = Property(b_we, 8); + ci.params[id_RAM_cfg_forward_a0_we] = Property(a_we, 8); + ci.params[id_RAM_cfg_forward_b0_we] = Property(b_we, 8); - ci.params[id_RAM_cfg_input_config_a1] = Property(width_to_config(a_wr_width), 3); - ci.params[id_RAM_cfg_input_config_b1] = Property(width_to_config(b_wr_width), 3); - ci.params[id_RAM_cfg_output_config_a1] = Property(width_to_config(a_rd_width), 3); - ci.params[id_RAM_cfg_output_config_b1] = Property(width_to_config(b_rd_width), 3); + ci.params[id_RAM_cfg_input_config_a0] = Property(width_to_config(a_wr_width), 3); + ci.params[id_RAM_cfg_input_config_b0] = Property(width_to_config(b_wr_width), 3); + ci.params[id_RAM_cfg_output_config_a0] = Property(width_to_config(a_rd_width), 3); + ci.params[id_RAM_cfg_output_config_b0] = Property(width_to_config(b_rd_width), 3); - ci.params[id_RAM_cfg_a1_writemode] = Property(a_wr_mode, 1); - ci.params[id_RAM_cfg_b1_writemode] = Property(b_wr_mode, 1); + ci.params[id_RAM_cfg_a0_writemode] = Property(a_wr_mode, 1); + ci.params[id_RAM_cfg_b0_writemode] = Property(b_wr_mode, 1); - ci.params[id_RAM_cfg_a1_set_outputreg] = Property(a_do_reg, 1); - ci.params[id_RAM_cfg_b1_set_outputreg] = Property(b_do_reg, 1); + ci.params[id_RAM_cfg_a0_set_outputreg] = Property(a_do_reg, 1); + ci.params[id_RAM_cfg_b0_set_outputreg] = Property(b_do_reg, 1); - ci.params[id_RAM_cfg_inversion_a1] = Property(a_inv, 3); - ci.params[id_RAM_cfg_inversion_b1] = Property(b_inv, 3); - } else { - ci.params[id_RAM_cfg_forward_a0_clk] = Property(cfg_a, 8); - if (!is_split) - ci.params[id_RAM_cfg_forward_a1_clk] = Property(cfg_a, 8); + ci.params[id_RAM_cfg_inversion_a0] = Property(a_inv, 3); + ci.params[id_RAM_cfg_inversion_b0] = Property(b_inv, 3); - ci.params[id_RAM_cfg_forward_b0_clk] = Property(cfg_b, 8); - if (!is_split) - ci.params[id_RAM_cfg_forward_b1_clk] = Property(cfg_b, 8); - - ci.params[id_RAM_cfg_forward_a0_en] = Property(a_en, 8); - ci.params[id_RAM_cfg_forward_b0_en] = Property(b_en, 8); - - ci.params[id_RAM_cfg_forward_a0_we] = Property(a_we, 8); - ci.params[id_RAM_cfg_forward_b0_we] = Property(b_we, 8); - - ci.params[id_RAM_cfg_input_config_a0] = Property(width_to_config(a_wr_width), 3); - ci.params[id_RAM_cfg_input_config_b0] = Property(width_to_config(b_wr_width), 3); - ci.params[id_RAM_cfg_output_config_a0] = Property(width_to_config(a_rd_width), 3); - ci.params[id_RAM_cfg_output_config_b0] = Property(width_to_config(b_rd_width), 3); - - ci.params[id_RAM_cfg_a0_writemode] = Property(a_wr_mode, 1); - ci.params[id_RAM_cfg_b0_writemode] = Property(b_wr_mode, 1); - - ci.params[id_RAM_cfg_a0_set_outputreg] = Property(a_do_reg, 1); - ci.params[id_RAM_cfg_b0_set_outputreg] = Property(b_do_reg, 1); - - ci.params[id_RAM_cfg_inversion_a0] = Property(a_inv, 3); - ci.params[id_RAM_cfg_inversion_b0] = Property(b_inv, 3); - } - - int index = (num == 0) ? 0 : 2; - rename_or_move(cell, &ci, id_A_CLK, ctx->idf("CLKA[%d]", index)); - rename_or_move(cell, &ci, id_B_CLK, ctx->idf("CLKB[%d]", index)); - rename_or_move(cell, &ci, id_A_EN, ctx->idf("ENA[%d]", index)); - rename_or_move(cell, &ci, id_B_EN, ctx->idf("ENB[%d]", index)); - rename_or_move(cell, &ci, id_A_WE, ctx->idf("GLWEA[%d]", index)); - rename_or_move(cell, &ci, id_B_WE, ctx->idf("GLWEB[%d]", index)); + rename_or_move(cell, &ci, id_A_CLK, ctx->id("CLKA[0]")); + rename_or_move(cell, &ci, id_B_CLK, ctx->id("CLKB[0]")); + rename_or_move(cell, &ci, id_A_EN, ctx->id("ENA[0]")); + rename_or_move(cell, &ci, id_B_EN, ctx->id("ENB[0]")); + rename_or_move(cell, &ci, id_A_WE, ctx->id("GLWEA[0]")); + rename_or_move(cell, &ci, id_B_WE, ctx->id("GLWEB[0]")); if (is_split) { - rename_or_move(cell, &ci, id_ECC_1B_ERR, ctx->idf("ECC1B_ERRA[%d]", index)); - rename_or_move(cell, &ci, id_ECC_2B_ERR, ctx->idf("ECC2B_ERRA[%d]", index)); + rename_or_move(cell, &ci, id_ECC_1B_ERR, ctx->id("ECC1B_ERRA[0]")); + rename_or_move(cell, &ci, id_ECC_2B_ERR, ctx->id("ECC2B_ERRA[0]")); } else { - rename_or_move(cell, &ci, id_A_ECC_1B_ERR, ctx->idf("ECC1B_ERRA[%d]", index)); - rename_or_move(cell, &ci, id_B_ECC_1B_ERR, ctx->idf("ECC1B_ERRB[%d]", index)); - rename_or_move(cell, &ci, id_A_ECC_2B_ERR, ctx->idf("ECC2B_ERRA[%d]", index)); - rename_or_move(cell, &ci, id_B_ECC_2B_ERR, ctx->idf("ECC2B_ERRB[%d]", index)); + rename_or_move(cell, &ci, id_A_ECC_1B_ERR, ctx->id("ECC1B_ERRA[0]")); + rename_or_move(cell, &ci, id_B_ECC_1B_ERR, ctx->id("ECC1B_ERRB[0]")); + rename_or_move(cell, &ci, id_A_ECC_2B_ERR, ctx->id("ECC2B_ERRA[0]")); + rename_or_move(cell, &ci, id_B_ECC_2B_ERR, ctx->id("ECC2B_ERRB[0]")); } int items = is_split ? 20 : 40; for (int i = 0; i < items; i++) { - rename_or_move(cell, &ci, ctx->idf("A_BM[%d]", i), ctx->idf("WEA[%d]", i + num * 20)); - rename_or_move(cell, &ci, ctx->idf("B_BM[%d]", i), ctx->idf("WEB[%d]", i + num * 20)); + rename_or_move(cell, &ci, ctx->idf("A_BM[%d]", i), ctx->idf("WEA[%d]", i)); + rename_or_move(cell, &ci, ctx->idf("B_BM[%d]", i), ctx->idf("WEB[%d]", i)); } for (int i = 0; i < 16; i++) { - rename_or_move(cell, &ci, ctx->idf("A_ADDR[%d]", i), ctx->idf("ADDRA%d[%d]", num, i)); - rename_or_move(cell, &ci, ctx->idf("B_ADDR[%d]", i), ctx->idf("ADDRB%d[%d]", num, i)); + rename_or_move(cell, &ci, ctx->idf("A_ADDR[%d]", i), ctx->idf("ADDRA0[%d]", i)); + rename_or_move(cell, &ci, ctx->idf("B_ADDR[%d]", i), ctx->idf("ADDRB0[%d]", i)); } for (int i = 0; i < items; i++) { - rename_or_move(cell, &ci, ctx->idf("A_DI[%d]", i), ctx->idf("DIA[%d]", i + num * 20)); - rename_or_move(cell, &ci, ctx->idf("A_DO[%d]", i), ctx->idf("DOA[%d]", i + num * 20)); - rename_or_move(cell, &ci, ctx->idf("B_DI[%d]", i), ctx->idf("DIB[%d]", i + num * 20)); - rename_or_move(cell, &ci, ctx->idf("B_DO[%d]", i), ctx->idf("DOB[%d]", i + num * 20)); + rename_or_move(cell, &ci, ctx->idf("A_DI[%d]", i), ctx->idf("DIA[%d]", i)); + rename_or_move(cell, &ci, ctx->idf("A_DO[%d]", i), ctx->idf("DOA[%d]", i)); + rename_or_move(cell, &ci, ctx->idf("B_DI[%d]", i), ctx->idf("DIB[%d]", i)); + rename_or_move(cell, &ci, ctx->idf("B_DO[%d]", i), ctx->idf("DOB[%d]", i)); } } void GateMatePacker::pack_ram() { - std::vector> rams; - std::vector> rams_merged[2]; + std::vector rams; std::map ram_cascade; log_info("Packing RAMs..\n"); for (auto &cell : ctx->cells) { @@ -263,7 +236,6 @@ void GateMatePacker::pack_ram() std::string ram_mode_str = str_or_default(ci.params, id_RAM_MODE, "SDP"); if (ram_mode_str != "SDP" && ram_mode_str != "TDP") log_error("Unknown RAM_MODE parameter value '%s' for cell %s.\n", ram_mode_str.c_str(), ci.name.c_str(ctx)); - int ram_mode = ram_mode_str == "SDP" ? 1 : 0; std::string cas = str_or_default(ci.params, id_CAS, "NONE"); if (cas != "NONE" && !ci.type.in(id_CC_BRAM_40K)) log_error("Cascade feature only supported for CC_BRAM_40K.\n"); @@ -277,17 +249,7 @@ void GateMatePacker::pack_ram() } if (split) { - bool added = false; - if (!rams_merged[ram_mode].empty()) { - auto &last = rams_merged[ram_mode].back(); - if (last.second == nullptr) { - last.second = &ci; - packed_cells.insert(ci.name); - added = true; - } - } - if (!added) - rams_merged[ram_mode].push_back(std::make_pair(&ci, nullptr)); + rams.push_back(&ci); } else { CellInfo *upper = nullptr; CellInfo *lower = nullptr; @@ -316,18 +278,16 @@ void GateMatePacker::pack_ram() log_error("RAM cell '%s' already cascaded to different RAM block.\n", ci.name.c_str(ctx)); ram_cascade[lower] = upper; - rams.push_back(std::make_pair(&ci, nullptr)); + rams.push_back(&ci); } } - rams.insert(rams.end(), rams_merged[0].begin(), rams_merged[0].end()); - rams.insert(rams.end(), rams_merged[1].begin(), rams_merged[1].end()); for (auto item : rams) { - CellInfo &ci = *item.first; + CellInfo &ci = *item; int split = ci.type.in(id_CC_BRAM_20K) ? 1 : 0; bool is_fifo = ci.type.in(id_CC_FIFO_40K); - ci.type = id_RAM; + ci.type = split ? id_RAM_HALF : id_RAM; ci.cluster = ci.name; // Location format: D(0..N-1)X(0..3)Y(0..7) or UNPLACED @@ -358,6 +318,21 @@ void GateMatePacker::pack_ram() log_error("Unknown CAS parameter value '%s' for cell %s.\n", cas.c_str(), ci.name.c_str(ctx)); } + if (!split) { + CellInfo *cell = ctx->createCell(ctx->idf("%s$dummy$u", ci.name.c_str(ctx)), id_RAM_HALF_DUMMY); + ci.constr_children.push_back(cell); + cell->constr_abs_z = true; + cell->constr_z = RAM_HALF_U_Z; + cell->cluster = ci.cluster; + + cell = ctx->createCell(ctx->idf("%s$dummy$l", ci.name.c_str(ctx)), id_RAM_HALF_DUMMY); + ci.constr_children.push_back(cell); + cell->constr_abs_z = true; + cell->constr_y = +8; + cell->constr_z = RAM_HALF_L_Z; + cell->cluster = ci.cluster; + } + // RAM and Write Modes std::string ram_mode_str = str_or_default(ci.params, id_RAM_MODE, "SDP"); if (ram_mode_str != "SDP" && ram_mode_str != "TDP") @@ -367,42 +342,21 @@ void GateMatePacker::pack_ram() // Error Checking and Correction int a_ecc_en = int_or_default(ci.params, id_A_ECC_EN, 0); int b_ecc_en = int_or_default(ci.params, id_B_ECC_EN, 0); + if (ci.params.count(id_ECC_EN)) { + a_ecc_en = int_or_default(ci.params, id_ECC_EN, 0); + } + ci.params[id_RAM_cfg_ecc_enable] = Property(b_ecc_en << 1 | a_ecc_en, 2); ci.params[id_RAM_cfg_forward_a_addr] = Property(0b00000000, 8); ci.params[id_RAM_cfg_forward_b_addr] = Property(0b00000000, 8); ci.params[id_RAM_cfg_sram_mode] = Property(ram_mode << 1 | split, 2); - pack_ram_cell(ci, item.first, 0, split); - if (item.second) { - pack_ram_cell(ci, item.second, 1, split); - } - if (split) { - for (int i = 63; i >= 0; i--) { - std::vector orig_first = - item.first->params.at(ctx->idf("INIT_%02X", i)).extract(0, 320).as_bits(); - std::vector orig_second; - if (item.second) - orig_second = item.second->params.at(ctx->idf("INIT_%02X", i)).extract(0, 320).as_bits(); - std::string init[2]; + ci.params[id_RAM_cfg_sram_delay] = Property(0b000101, 6); // Always set to default + // id_RAM_cfg_datbm_sel + ci.params[id_RAM_cfg_cascade_enable] = Property(cascade, 2); - for (int j = 0; j < 2; j++) { - for (int k = 0; k < 4; k++) { - for (int l = 0; l < 40; l++) { - if (item.second) - init[j].push_back(orig_second.at(319 - (l + k * 40 + j * 160)) ? '1' : '0'); - else - init[j].push_back('0'); - } - for (int l = 0; l < 40; l++) { - init[j].push_back(orig_first.at(319 - (l + k * 40 + j * 160)) ? '1' : '0'); - } - } - } - ci.params[ctx->idf("INIT_%02X", i * 2 + 1)] = Property::from_string(init[0]); - ci.params[ctx->idf("INIT_%02X", i * 2 + 0)] = Property::from_string(init[1]); - } - } + pack_ram_cell(ci, item, split); if (is_fifo) { int a_rd_width = int_or_default(ci.params, id_A_WIDTH, 0); @@ -442,11 +396,6 @@ void GateMatePacker::pack_ram() Property(int_or_default(ci.params, id_F_ALMOST_FULL_OFFSET, 0), 15); } - ci.params[id_RAM_cfg_ecc_enable] = Property(b_ecc_en << 1 | a_ecc_en, 2); - ci.params[id_RAM_cfg_sram_delay] = Property(0b000101, 6); // Always set to default - // id_RAM_cfg_datbm_sel - ci.params[id_RAM_cfg_cascade_enable] = Property(cascade, 2); - for (int i = 0; i < 40; i++) { move_ram_o(&ci, ctx->idf("WEA[%d]", i)); move_ram_o(&ci, ctx->idf("WEB[%d]", i)); @@ -470,6 +419,10 @@ void GateMatePacker::pack_ram() move_ram_o(&ci, ctx->idf("ENB[%d]", i)); move_ram_o(&ci, ctx->idf("GLWEA[%d]", i)); move_ram_o(&ci, ctx->idf("GLWEB[%d]", i)); + move_ram_o(&ci, ctx->idf("ECC1B_ERRA[%d]", i)); + move_ram_o(&ci, ctx->idf("ECC1B_ERRB[%d]", i)); + move_ram_o(&ci, ctx->idf("ECC2B_ERRA[%d]", i)); + move_ram_o(&ci, ctx->idf("ECC2B_ERRB[%d]", i)); } if (is_fifo) { @@ -509,4 +462,148 @@ void GateMatePacker::pack_ram() flush_cells(); } +void GateMatePacker::remap_ram_half(CellInfo *half, CellInfo *cell, int num) +{ + int index = num ? 2 : 0; + + rename_or_move(half, cell, ctx->id("CLKA[0]"), ctx->idf("CLKA[%d]", index)); + rename_or_move(half, cell, ctx->id("CLKB[0]"), ctx->idf("CLKB[%d]", index)); + rename_or_move(half, cell, ctx->id("ENA[0]"), ctx->idf("ENA[%d]", index)); + rename_or_move(half, cell, ctx->id("ENB[0]"), ctx->idf("ENB[%d]", index)); + rename_or_move(half, cell, ctx->id("GLWEA[0]"), ctx->idf("GLWEA[%d]", index)); + rename_or_move(half, cell, ctx->id("GLWEB[0]"), ctx->idf("GLWEB[%d]", index)); + for (int i = 0; i < 20; i++) { + rename_or_move(half, cell, ctx->idf("WEA[%d]", i), ctx->idf("WEA[%d]", i + 20 * num)); + rename_or_move(half, cell, ctx->idf("WEB[%d]", i), ctx->idf("WEB[%d]", i + 20 * num)); + rename_or_move(half, cell, ctx->idf("DIA[%d]", i), ctx->idf("DIA[%d]", i + 20 * num)); + rename_or_move(half, cell, ctx->idf("DIB[%d]", i), ctx->idf("DIB[%d]", i + 20 * num)); + rename_or_move(half, cell, ctx->idf("DOA[%d]", i), ctx->idf("DOA[%d]", i + 20 * num)); + rename_or_move(half, cell, ctx->idf("DOB[%d]", i), ctx->idf("DOB[%d]", i + 20 * num)); + } + for (int i = 0; i < 16; i++) { + rename_or_move(half, cell, ctx->idf("ADDRA0[%d]", i), ctx->idf("ADDRA%d[%d]", num, i)); + rename_or_move(half, cell, ctx->idf("ADDRB0[%d]", i), ctx->idf("ADDRB%d[%d]", num, i)); + } + + index = num ? 1 : 0; + rename_or_move(half, cell, ctx->id("ECC1B_ERRA[0]"), ctx->idf("ECC1B_ERRA[%d]", index)); + rename_or_move(half, cell, ctx->id("ECC1B_ERRB[0]"), ctx->idf("ECC1B_ERRB[%d]", index)); + rename_or_move(half, cell, ctx->id("ECC2B_ERRA[0]"), ctx->idf("ECC2B_ERRA[%d]", index)); + rename_or_move(half, cell, ctx->id("ECC2B_ERRB[0]"), ctx->idf("ECC2B_ERRB[%d]", index)); + + for (int i = 1; i < 5; i++) + if (!cell->getPort(ctx->idf("CLOCK%d", i))) + rename_or_move(half, cell, ctx->idf("CLOCK%d", i), ctx->idf("CLOCK%d", i)); + + static dict map_params = { + {id_RAM_cfg_forward_a0_clk, id_RAM_cfg_forward_a1_clk}, + {id_RAM_cfg_forward_b0_clk, id_RAM_cfg_forward_b1_clk}, + + {id_RAM_cfg_forward_a0_en, id_RAM_cfg_forward_a1_en}, + {id_RAM_cfg_forward_b0_en, id_RAM_cfg_forward_b1_en}, + + {id_RAM_cfg_forward_a0_we, id_RAM_cfg_forward_a1_we}, + {id_RAM_cfg_forward_b0_we, id_RAM_cfg_forward_b1_we}, + + {id_RAM_cfg_input_config_a0, id_RAM_cfg_input_config_a1}, + {id_RAM_cfg_input_config_b0, id_RAM_cfg_input_config_b1}, + {id_RAM_cfg_output_config_a0, id_RAM_cfg_output_config_a1}, + {id_RAM_cfg_output_config_b0, id_RAM_cfg_output_config_b1}, + + {id_RAM_cfg_a0_writemode, id_RAM_cfg_a1_writemode}, + {id_RAM_cfg_b0_writemode, id_RAM_cfg_b1_writemode}, + + {id_RAM_cfg_a0_set_outputreg, id_RAM_cfg_a1_set_outputreg}, + {id_RAM_cfg_b0_set_outputreg, id_RAM_cfg_b1_set_outputreg}, + + {id_RAM_cfg_inversion_a0, id_RAM_cfg_inversion_a1}, + {id_RAM_cfg_inversion_b0, id_RAM_cfg_inversion_b1}, + + // This is for both halfs and it is same + {id_RAM_cfg_forward_a_addr, id_RAM_cfg_forward_a_addr}, + {id_RAM_cfg_forward_b_addr, id_RAM_cfg_forward_b_addr}, + {id_RAM_cfg_sram_mode, id_RAM_cfg_sram_mode}, + {id_RAM_cfg_ecc_enable, id_RAM_cfg_ecc_enable}, + {id_RAM_cfg_sram_delay, id_RAM_cfg_sram_delay}, + {id_RAM_cfg_cascade_enable, id_RAM_cfg_cascade_enable}, + }; + + for (auto &p : map_params) { + if (map_params.count(p.first)) { + cell->params[num ? p.second : p.first] = half->params[p.first]; + } + } +} + +void GateMatePacker::repack_ram() +{ + log_info("Repacking RAMs..\n"); + dict> rams; + for (auto &cell : ctx->cells) { + if (cell.second->type.in(id_RAM_HALF)) { + Loc l = ctx->getBelLocation(cell.second->bel); + if (l.z == RAM_HALF_U_Z) { + rams[Loc(l.x, l.y, 0)].first = cell.second.get(); + } else { + rams[Loc(l.x, l.y - 8, 0)].second = cell.second.get(); + } + } else if (cell.second->type.in(id_RAM_HALF_DUMMY)) + packed_cells.insert(cell.second->name); + } + int id = 0; + for (auto &ram : rams) { + IdString name = ctx->idf("$ram$merged$id%d", id); + if (!ram.second.first) + name = ctx->idf("%s$full", ram.second.second->name.c_str(ctx)); + if (!ram.second.second) + name = ctx->idf("%s$full", ram.second.first->name.c_str(ctx)); + + CellInfo *cell = ctx->createCell(name, id_RAM); + BelId bel = ctx->getBelByLocation({ram.first.x, ram.first.y, RAM_FULL_Z}); + ctx->bindBel(bel, cell, PlaceStrength::STRENGTH_FIXED); + + if (ram.second.first) { + remap_ram_half(ram.second.first, cell, 0); + packed_cells.insert(ram.second.first->name); + } + if (ram.second.second) { + remap_ram_half(ram.second.second, cell, 1); + packed_cells.insert(ram.second.second->name); + } + + for (int i = 63; i >= 0; i--) { + std::vector orig_first; + if (ram.second.first) + orig_first = ram.second.first->params.at(ctx->idf("INIT_%02X", i)).extract(0, 320).as_bits(); + std::vector orig_second; + if (ram.second.second) + orig_second = ram.second.second->params.at(ctx->idf("INIT_%02X", i)).extract(0, 320).as_bits(); + std::string init[2]; + + for (int j = 0; j < 2; j++) { + for (int k = 0; k < 4; k++) { + for (int l = 0; l < 40; l++) { + if (ram.second.second) + init[j].push_back(orig_second.at(319 - (l + k * 40 + j * 160)) ? '1' : '0'); + else + init[j].push_back('0'); + } + for (int l = 0; l < 40; l++) { + if (ram.second.first) + init[j].push_back(orig_first.at(319 - (l + k * 40 + j * 160)) ? '1' : '0'); + else + init[j].push_back('0'); + } + } + } + cell->params[ctx->idf("INIT_%02X", i * 2 + 1)] = Property::from_string(init[0]); + cell->params[ctx->idf("INIT_%02X", i * 2 + 0)] = Property::from_string(init[1]); + } + + id++; + } + flush_cells(); + ctx->assignArchInfo(); +} + NEXTPNR_NAMESPACE_END diff --git a/himbaechel/uarch/gatemate/pack_io.cc b/himbaechel/uarch/gatemate/pack_io.cc index 111b7e3e..e6300c7e 100644 --- a/himbaechel/uarch/gatemate/pack_io.cc +++ b/himbaechel/uarch/gatemate/pack_io.cc @@ -233,11 +233,11 @@ void GateMatePacker::pack_io() ci.params[id_SLEW] = Property(Property::State::S1); int keeper = int_or_default(ci.params, id_KEEPER, 0); + ci.unsetParam(id_KEEPER); if (keeper && (int_or_default(ci.params, id_PULLUP, 0) + int_or_default(ci.params, id_PULLDOWN, 0) > 1)) log_error("PULLUP/PULLDOWN and KEEPER are mutually exclusive parameters, issue for '%s' cell.\n", ci.name.c_str(ctx)); if (keeper) { - ci.unsetParam(id_KEEPER); ci.params[id_PULLUP] = Property(Property::State::S1); ci.params[id_PULLDOWN] = Property(Property::State::S1); }