|
| 1 | +/* |
| 2 | + * yosys -- Yosys Open SYnthesis Suite |
| 3 | + * |
| 4 | + * Copyright (C) 2026 Silimate Inc. |
| 5 | + * |
| 6 | + * Permission to use, copy, modify, and/or distribute this software for any |
| 7 | + * purpose with or without fee is hereby granted, provided that the above |
| 8 | + * copyright notice and this permission notice appear in all copies. |
| 9 | + * |
| 10 | + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| 11 | + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| 12 | + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
| 13 | + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| 14 | + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
| 15 | + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
| 16 | + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| 17 | + * |
| 18 | + */ |
| 19 | + |
| 20 | +#include "kernel/yosys.h" |
| 21 | +#include "kernel/sigtools.h" |
| 22 | + |
| 23 | +USING_YOSYS_NAMESPACE |
| 24 | +PRIVATE_NAMESPACE_BEGIN |
| 25 | + |
| 26 | +struct InferIcgPass : public Pass { |
| 27 | + InferIcgPass() : Pass("infer_icg", "infer $icg cells from latch-based clock gates") { } |
| 28 | + |
| 29 | + void help() override |
| 30 | + { |
| 31 | + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| |
| 32 | + log("\n"); |
| 33 | + log(" infer_icg [selection]\n"); |
| 34 | + log("\n"); |
| 35 | + log("This pass recognizes latch-based integrated clock gate structures and\n"); |
| 36 | + log("replaces the gated-clock output with an internal $icg cell.\n"); |
| 37 | + log("\n"); |
| 38 | + log("The recognized positive-edge pattern is a one-bit $dlatch that is\n"); |
| 39 | + log("transparent while the clock is low, feeding a one-bit $and or $logic_and\n"); |
| 40 | + log("with the same clock. The latch can be active-low on the clock or\n"); |
| 41 | + log("active-high on an inverted copy of the clock.\n"); |
| 42 | + log("\n"); |
| 43 | + log("The complementary high-idle pattern is also recognized: a latch that is\n"); |
| 44 | + log("transparent while the clock is high, feeding a one-bit $or or $logic_or\n"); |
| 45 | + log("with the same clock and an inverted latched enable. This is rewritten as\n"); |
| 46 | + log("a $icg on the inverted clock plus an output inverter.\n"); |
| 47 | + log("\n"); |
| 48 | + log("The scan-enable leg is optional. If the effective latch data is driven\n"); |
| 49 | + log("by a one-bit $or or $logic_or, its inputs are used as the $icg EN and\n"); |
| 50 | + log("SE ports; otherwise the data is used as EN and SE is tied low.\n"); |
| 51 | + log("\n"); |
| 52 | + } |
| 53 | + |
| 54 | + static bool is_one_bit_binary(Cell *cell) |
| 55 | + { |
| 56 | + return cell->getParam(ID::A_WIDTH).as_int() == 1 && |
| 57 | + cell->getParam(ID::B_WIDTH).as_int() == 1 && |
| 58 | + cell->getParam(ID::Y_WIDTH).as_int() == 1; |
| 59 | + } |
| 60 | + |
| 61 | + static bool is_and(Cell *cell) |
| 62 | + { |
| 63 | + return cell->type.in(ID($and), ID($logic_and)) && is_one_bit_binary(cell); |
| 64 | + } |
| 65 | + |
| 66 | + static bool is_or(Cell *cell) |
| 67 | + { |
| 68 | + return cell->type.in(ID($or), ID($logic_or)) && is_one_bit_binary(cell); |
| 69 | + } |
| 70 | + |
| 71 | + static bool is_not(Cell *cell) |
| 72 | + { |
| 73 | + if (cell->type == ID($_NOT_)) |
| 74 | + return true; |
| 75 | + return cell->type.in(ID($not), ID($logic_not)) && |
| 76 | + cell->getParam(ID::A_WIDTH).as_int() == 1 && |
| 77 | + cell->getParam(ID::Y_WIDTH).as_int() == 1; |
| 78 | + } |
| 79 | + |
| 80 | + static void add_driver(dict<SigBit, Cell *> &drivers, pool<SigBit> &multi_driver_bits, SigMap &sigmap, Cell *cell) |
| 81 | + { |
| 82 | + for (auto &conn : cell->connections()) { |
| 83 | + if (!cell->output(conn.first)) |
| 84 | + continue; |
| 85 | + for (auto bit : sigmap(conn.second)) { |
| 86 | + if (bit.is_wire()) { |
| 87 | + if (drivers.count(bit) && drivers.at(bit) != cell) |
| 88 | + multi_driver_bits.insert(bit); |
| 89 | + else |
| 90 | + drivers[bit] = cell; |
| 91 | + } |
| 92 | + } |
| 93 | + } |
| 94 | + } |
| 95 | + |
| 96 | + static bool get_driver(dict<SigBit, Cell *> &drivers, pool<SigBit> &multi_driver_bits, SigMap &sigmap, SigSpec sig, Cell *&driver) |
| 97 | + { |
| 98 | + sig = sigmap(sig); |
| 99 | + if (GetSize(sig) != 1 || !sig[0].is_wire() || multi_driver_bits.count(sig[0]) || !drivers.count(sig[0])) |
| 100 | + return false; |
| 101 | + driver = drivers.at(sig[0]); |
| 102 | + return true; |
| 103 | + } |
| 104 | + |
| 105 | + static bool same_sig(SigMap &sigmap, SigSpec a, SigSpec b) |
| 106 | + { |
| 107 | + return sigmap(a) == sigmap(b); |
| 108 | + } |
| 109 | + |
| 110 | + static bool get_not_input(dict<SigBit, Cell *> &drivers, pool<SigBit> &multi_driver_bits, SigMap &sigmap, |
| 111 | + SigSpec sig, SigSpec &input, Cell *¬_cell) |
| 112 | + { |
| 113 | + Cell *driver = nullptr; |
| 114 | + if (!get_driver(drivers, multi_driver_bits, sigmap, sig, driver) || !is_not(driver)) |
| 115 | + return false; |
| 116 | + |
| 117 | + not_cell = driver; |
| 118 | + input = sigmap(driver->getPort(ID::A)); |
| 119 | + return true; |
| 120 | + } |
| 121 | + |
| 122 | + static bool latch_transparent_when(Cell *latch, SigSpec clk, bool high_when_transparent, |
| 123 | + dict<SigBit, Cell *> &drivers, pool<SigBit> &multi_driver_bits, SigMap &sigmap) |
| 124 | + { |
| 125 | + SigSpec latch_en = sigmap(latch->getPort(ID::EN)); |
| 126 | + bool latch_en_pol = latch->getParam(ID::EN_POLARITY).as_bool(); |
| 127 | + |
| 128 | + if (same_sig(sigmap, latch_en, clk)) |
| 129 | + return latch_en_pol == high_when_transparent; |
| 130 | + |
| 131 | + SigSpec inv_input; |
| 132 | + Cell *not_cell = nullptr; |
| 133 | + if (get_not_input(drivers, multi_driver_bits, sigmap, latch_en, inv_input, not_cell) && |
| 134 | + same_sig(sigmap, inv_input, clk)) |
| 135 | + return latch_en_pol != high_when_transparent; |
| 136 | + |
| 137 | + return false; |
| 138 | + } |
| 139 | + |
| 140 | + void execute(std::vector<std::string> args, RTLIL::Design *design) override |
| 141 | + { |
| 142 | + log_header(design, "Executing INFER_ICG pass (infer $icg cells from latch-based clock gates).\n"); |
| 143 | + |
| 144 | + size_t argidx; |
| 145 | + for (argidx = 1; argidx < args.size(); argidx++) |
| 146 | + break; |
| 147 | + extra_args(args, argidx, design); |
| 148 | + |
| 149 | + int total_count = 0; |
| 150 | + |
| 151 | + for (auto module : design->selected_modules()) { |
| 152 | + SigMap sigmap(module); |
| 153 | + dict<SigBit, Cell *> drivers; |
| 154 | + pool<SigBit> multi_driver_bits; |
| 155 | + dict<SigBit, int> users; |
| 156 | + pool<Cell *> selected_cells; |
| 157 | + |
| 158 | + for (auto wire : module->wires()) |
| 159 | + if (wire->port_output) |
| 160 | + for (auto bit : sigmap(wire)) |
| 161 | + if (bit.is_wire()) |
| 162 | + users[bit]++; |
| 163 | + |
| 164 | + for (auto cell : module->cells()) { |
| 165 | + if (design->selected(module, cell)) |
| 166 | + selected_cells.insert(cell); |
| 167 | + |
| 168 | + add_driver(drivers, multi_driver_bits, sigmap, cell); |
| 169 | + |
| 170 | + for (auto &conn : cell->connections()) { |
| 171 | + if (!cell->input(conn.first)) |
| 172 | + continue; |
| 173 | + for (auto bit : sigmap(conn.second)) |
| 174 | + if (bit.is_wire()) |
| 175 | + users[bit]++; |
| 176 | + } |
| 177 | + } |
| 178 | + |
| 179 | + struct Match { |
| 180 | + Cell *gate_cell; |
| 181 | + Cell *latch; |
| 182 | + Cell *enable_or_cell; |
| 183 | + SigSpec clk; |
| 184 | + SigSpec en; |
| 185 | + SigSpec se; |
| 186 | + SigSpec gclk; |
| 187 | + SigSpec latched_en; |
| 188 | + bool invert_clk_and_gclk; |
| 189 | + }; |
| 190 | + std::vector<Match> matches; |
| 191 | + pool<Cell *> consumed_gate_cells; |
| 192 | + |
| 193 | + for (auto gate_cell : module->selected_cells()) { |
| 194 | + if (consumed_gate_cells.count(gate_cell) || (!is_and(gate_cell) && !is_or(gate_cell))) |
| 195 | + continue; |
| 196 | + |
| 197 | + bool gate_is_and = is_and(gate_cell); |
| 198 | + SigSpec gate_a = sigmap(gate_cell->getPort(ID::A)); |
| 199 | + SigSpec gate_b = sigmap(gate_cell->getPort(ID::B)); |
| 200 | + SigSpec gclk = gate_cell->getPort(ID::Y); |
| 201 | + |
| 202 | + for (int clk_port = 0; clk_port < 2; clk_port++) { |
| 203 | + SigSpec clk = clk_port == 0 ? gate_a : gate_b; |
| 204 | + SigSpec latched_en = clk_port == 0 ? gate_b : gate_a; |
| 205 | + Cell *latch = nullptr; |
| 206 | + bool invert_clk_and_gclk = false; |
| 207 | + |
| 208 | + if (!gate_is_and) { |
| 209 | + SigSpec not_input; |
| 210 | + Cell *not_cell = nullptr; |
| 211 | + if (!get_not_input(drivers, multi_driver_bits, sigmap, latched_en, not_input, not_cell)) |
| 212 | + continue; |
| 213 | + latched_en = not_input; |
| 214 | + invert_clk_and_gclk = true; |
| 215 | + } |
| 216 | + |
| 217 | + if (!get_driver(drivers, multi_driver_bits, sigmap, latched_en, latch)) |
| 218 | + continue; |
| 219 | + if (!selected_cells.count(latch) || latch->type != ID($dlatch)) |
| 220 | + continue; |
| 221 | + if (latch->getParam(ID::WIDTH).as_int() != 1) |
| 222 | + continue; |
| 223 | + |
| 224 | + if (!latch_transparent_when(latch, clk, !gate_is_and, drivers, multi_driver_bits, sigmap)) |
| 225 | + continue; |
| 226 | + |
| 227 | + SigSpec latch_d = sigmap(latch->getPort(ID::D)); |
| 228 | + SigSpec en = latch_d, se = State::S0; |
| 229 | + Cell *enable_or_cell = nullptr; |
| 230 | + |
| 231 | + if (get_driver(drivers, multi_driver_bits, sigmap, latch_d, enable_or_cell) && |
| 232 | + selected_cells.count(enable_or_cell) && is_or(enable_or_cell)) { |
| 233 | + en = sigmap(enable_or_cell->getPort(ID::A)); |
| 234 | + se = sigmap(enable_or_cell->getPort(ID::B)); |
| 235 | + } else { |
| 236 | + enable_or_cell = nullptr; |
| 237 | + } |
| 238 | + |
| 239 | + matches.push_back({gate_cell, latch, enable_or_cell, clk, en, se, gclk, latched_en, invert_clk_and_gclk}); |
| 240 | + consumed_gate_cells.insert(gate_cell); |
| 241 | + break; |
| 242 | + } |
| 243 | + } |
| 244 | + |
| 245 | + for (auto &match : matches) { |
| 246 | + Cell *cell = match.gate_cell; |
| 247 | + Cell *icg = module->addCell(NEW_ID2_SUFFIX("icg"), ID($icg)); |
| 248 | + SigSpec icg_clk = match.clk; |
| 249 | + SigSpec icg_gclk = match.gclk; |
| 250 | + |
| 251 | + if (match.invert_clk_and_gclk) { |
| 252 | + icg_clk = module->Not(NEW_ID2_SUFFIX("icg_clk_n"), match.clk); |
| 253 | + icg_gclk = module->addWire(NEW_ID2_SUFFIX("icg_gclk")); |
| 254 | + } |
| 255 | + |
| 256 | + icg->setPort(ID::CLK, icg_clk); |
| 257 | + icg->setPort(ID::EN, match.en); |
| 258 | + icg->setPort(ID::SE, match.se); |
| 259 | + icg->setPort(ID::GCLK, icg_gclk); |
| 260 | + icg->add_strpool_attribute(ID::src, match.gate_cell->get_strpool_attribute(ID::src)); |
| 261 | + icg->add_strpool_attribute(ID::src, match.latch->get_strpool_attribute(ID::src)); |
| 262 | + if (match.enable_or_cell) |
| 263 | + icg->add_strpool_attribute(ID::src, match.enable_or_cell->get_strpool_attribute(ID::src)); |
| 264 | + |
| 265 | + if (match.invert_clk_and_gclk) |
| 266 | + module->addNot(NEW_ID2_SUFFIX("icg_gclk_n"), icg_gclk, match.gclk, false, match.gate_cell->get_src_attribute()); |
| 267 | + |
| 268 | + log("Inferred $icg cell %s.%s from latch %s and gate %s.\n", |
| 269 | + log_id(module), log_id(icg), log_id(match.latch), log_id(match.gate_cell)); |
| 270 | + |
| 271 | + module->remove(match.gate_cell); |
| 272 | + |
| 273 | + SigSpec latched_en = sigmap(match.latched_en); |
| 274 | + if (GetSize(latched_en) == 1 && latched_en[0].is_wire() && users[latched_en[0]] == 1) { |
| 275 | + module->remove(match.latch); |
| 276 | + |
| 277 | + if (match.enable_or_cell) { |
| 278 | + SigSpec or_y = sigmap(match.enable_or_cell->getPort(ID::Y)); |
| 279 | + if (GetSize(or_y) == 1 && or_y[0].is_wire() && users[or_y[0]] == 1) |
| 280 | + module->remove(match.enable_or_cell); |
| 281 | + } |
| 282 | + } |
| 283 | + |
| 284 | + total_count++; |
| 285 | + } |
| 286 | + } |
| 287 | + |
| 288 | + log("Inferred %d $icg cells.\n", total_count); |
| 289 | + } |
| 290 | +} InferIcgPass; |
| 291 | + |
| 292 | +PRIVATE_NAMESPACE_END |
0 commit comments