Skip to content

Commit d3c5591

Browse files
authored
Merge pull request #171 from Silimate/infer_icg
infer_icg pass
2 parents 7ea578a + 2bb1083 commit d3c5591

3 files changed

Lines changed: 481 additions & 0 deletions

File tree

passes/silimate/Makefile.inc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ OBJS += passes/silimate/cone_partition.o
2121
OBJS += passes/silimate/clkmerge.o
2222
OBJS += passes/silimate/opt_boundary.o
2323
OBJS += passes/silimate/opt_vps.o
24+
OBJS += passes/silimate/infer_icg.o
2425

2526
OBJS += passes/silimate/opt_expand.o
2627
GENFILES += passes/silimate/peepopt_expand.h

passes/silimate/infer_icg.cc

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
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 *&not_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

Comments
 (0)