Skip to content

Commit 00fbf86

Browse files
committed
Add clock tree tracing to timing_sim_cpu and more SKY130 cells
- Add trace_clock_to_primary_input() function to timing_sim_cpu.rs that traces DFF CLK pins back through clock buffers/inverters to find the primary input. This fixes posedge detection for designs with clock trees. - Add SKY130 cell decompositions for a41o and a311o gates that were missing from the decomposer. The posedge_monitor was previously checking only the first pin on the clock net, which returns a clock buffer output rather than the primary input (gpio_in[38]). The new tracing follows the clock tree back through buffers to find the actual clock source. Co-developed-by: Claude Code v2.1.22 (claude-opus-4-5-20251101)
1 parent cf6a79b commit 00fbf86

File tree

2 files changed

+196
-7
lines changed

2 files changed

+196
-7
lines changed

src/bin/timing_sim_cpu.rs

Lines changed: 150 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,144 @@ struct TimingStats {
164164
worst_hold_slack: i64,
165165
}
166166

167+
/// Trace a clock pin back through buffers/inverters to find the primary input (cell 0).
168+
/// Returns Some(pinid) if a primary input is found, None otherwise.
169+
fn trace_clock_to_primary_input(
170+
netlistdb: &NetlistDB,
171+
start_pinid: usize,
172+
cell_library: CellLibrary,
173+
verbose: bool,
174+
) -> Option<usize> {
175+
let mut current_pinid = start_pinid;
176+
let mut visited = std::collections::HashSet::new();
177+
let mut depth = 0;
178+
179+
loop {
180+
if visited.contains(&current_pinid) {
181+
if verbose {
182+
clilog::debug!(" Clock trace: cycle detected at depth {}", depth);
183+
}
184+
return None;
185+
}
186+
visited.insert(current_pinid);
187+
188+
if visited.len() > 10000 {
189+
if verbose {
190+
clilog::debug!(" Clock trace: safety limit exceeded at depth {}", depth);
191+
}
192+
return None;
193+
}
194+
195+
// If this is an input pin, follow the net to its driver
196+
if netlistdb.pindirect[current_pinid] == Direction::I {
197+
let netid = netlistdb.pin2net[current_pinid];
198+
if Some(netid) == netlistdb.net_zero || Some(netid) == netlistdb.net_one {
199+
if verbose {
200+
clilog::debug!(" Clock trace: hit constant net at depth {}", depth);
201+
}
202+
return None;
203+
}
204+
205+
// Find driver pin on the net
206+
let net_pins_start = netlistdb.net2pin.start[netid];
207+
let net_pins_end = if netid + 1 < netlistdb.net2pin.start.len() {
208+
netlistdb.net2pin.start[netid + 1]
209+
} else {
210+
netlistdb.net2pin.items.len()
211+
};
212+
213+
let mut driver_pin = None;
214+
for &np in &netlistdb.net2pin.items[net_pins_start..net_pins_end] {
215+
// Check for output (driver) pin
216+
if netlistdb.pindirect[np] == Direction::O {
217+
driver_pin = Some(np);
218+
break;
219+
}
220+
// Check for primary input (cell 0)
221+
if netlistdb.pin2cell[np] == 0 {
222+
driver_pin = Some(np);
223+
break;
224+
}
225+
}
226+
227+
match driver_pin {
228+
Some(dp) => {
229+
current_pinid = dp;
230+
depth += 1;
231+
}
232+
None => {
233+
if verbose {
234+
clilog::debug!(" Clock trace: no driver found for net {} at depth {}", netid, depth);
235+
}
236+
return None;
237+
}
238+
}
239+
continue;
240+
}
241+
242+
// This is an output pin - check if it's from cell 0 (primary input)
243+
let cellid = netlistdb.pin2cell[current_pinid];
244+
if cellid == 0 {
245+
use netlistdb::GeneralPinName;
246+
if verbose && depth <= 5 {
247+
clilog::debug!(
248+
" Clock trace: found primary input {} at depth {}",
249+
netlistdb.pinnames[current_pinid].dbg_fmt_pin(),
250+
depth
251+
);
252+
}
253+
return Some(current_pinid);
254+
}
255+
256+
// Check if this cell is a buffer/inverter that we can trace through
257+
let celltype = netlistdb.celltypes[cellid].as_str();
258+
259+
let is_buffer_or_inv = match cell_library {
260+
CellLibrary::SKY130 => {
261+
let ct = extract_cell_type(celltype);
262+
ct.starts_with("inv")
263+
|| ct.starts_with("clkinv")
264+
|| ct.starts_with("buf")
265+
|| ct.starts_with("clkbuf")
266+
|| ct.starts_with("clkdlybuf")
267+
}
268+
_ => matches!(celltype, "INV" | "BUF"),
269+
};
270+
271+
if !is_buffer_or_inv {
272+
if verbose && depth <= 5 {
273+
clilog::debug!(
274+
" Clock trace: hit non-buffer cell {} ({}) at depth {}",
275+
cellid,
276+
celltype,
277+
depth
278+
);
279+
}
280+
return None;
281+
}
282+
283+
// Find the input pin "A" of the buffer/inverter
284+
let mut input_pin = None;
285+
for ipin in netlistdb.cell2pin.iter_set(cellid) {
286+
if netlistdb.pindirect[ipin] == Direction::I {
287+
let pin_name = netlistdb.pinnames[ipin].1.as_str();
288+
if pin_name == "A" {
289+
input_pin = Some(ipin);
290+
break;
291+
}
292+
}
293+
}
294+
295+
match input_pin {
296+
Some(ip) => {
297+
current_pinid = ip;
298+
depth += 1;
299+
}
300+
None => return None,
301+
}
302+
}
303+
}
304+
167305
fn main() {
168306
clilog::init_stderr_color_debug();
169307
clilog::set_max_print_count(clilog::Level::Warn, "NL_SV_LIT", 1);
@@ -252,13 +390,18 @@ fn main() {
252390
let is_clk = matches!(pin_name, "CLK" | "CLKin" | "PORT_R_CLK" | "PORT_W_CLK");
253391

254392
if is_clk {
255-
let netid = netlistdb.pin2net[pinid];
256-
if Some(netid) == netlistdb.net_zero || Some(netid) == netlistdb.net_one {
257-
continue;
258-
}
259-
let root = netlistdb.net2pin.items[netlistdb.net2pin.start[netid]];
260-
if netlistdb.pin2cell[root] == 0 {
261-
posedge_monitor.insert(root);
393+
// Trace clock pin back through buffers/inverters to primary input
394+
// Only log verbose for first few DFFs to avoid spam
395+
let log_this = args.verbose && posedge_monitor.is_empty();
396+
if let Some(primary_clk_pin) =
397+
trace_clock_to_primary_input(&netlistdb, pinid, cell_library, log_this)
398+
{
399+
posedge_monitor.insert(primary_clk_pin);
400+
} else if args.verbose && posedge_monitor.is_empty() {
401+
clilog::debug!(
402+
"Clock pin {} could not be traced to primary input",
403+
pinid
404+
);
262405
}
263406
}
264407
}

src/sky130_decomp.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,27 @@ pub fn decompose_sky130_cell(
719719
}
720720
}
721721

722+
// a311o: Y = (A1 & A2 & A3) | B1 | C1
723+
"a311o" => {
724+
let a1 = inputs.a1;
725+
let a2 = inputs.a2;
726+
let a3 = inputs.a3;
727+
let b1 = inputs.b1;
728+
let c1 = inputs.c1;
729+
// Build AND3 first: A1 & A2 & A3
730+
// Then OR3: result | B1 | C1 = !(!(result) & !B1 & !C1)
731+
DecompResult {
732+
and_gates: vec![
733+
(a1 as i64, a2 as i64),
734+
(-1, a3 as i64),
735+
(-2 ^ 1, (b1 ^ 1) as i64),
736+
(-3, (c1 ^ 1) as i64),
737+
],
738+
output_idx: -4,
739+
output_inverted: true,
740+
}
741+
}
742+
722743
// a311oi: Y = !((A1 & A2 & A3) | B1 | C1)
723744
"a311oi" => {
724745
let a1 = inputs.a1;
@@ -776,6 +797,31 @@ pub fn decompose_sky130_cell(
776797
}
777798
}
778799

800+
// a41o: Y = (A1 & A2 & A3 & A4) | B1
801+
"a41o" => {
802+
let a1 = inputs.a1;
803+
let a2 = inputs.a2;
804+
let a3 = inputs.a3;
805+
let a4 = inputs.a4;
806+
let b1 = inputs.b1;
807+
// Build AND4: A1 & A2 & A3 & A4
808+
// -1 = A1 & A2
809+
// -2 = A3 & A4
810+
// -3 = -1 & -2 = A1 & A2 & A3 & A4
811+
// Then OR with B1: Y = -3 | B1 = !(!-3 & !B1)
812+
// -4 = !-3 & !B1 (use inverted inputs)
813+
DecompResult {
814+
and_gates: vec![
815+
(a1 as i64, a2 as i64),
816+
(a3 as i64, a4 as i64),
817+
(-1, -2),
818+
(-3 ^ 1, (b1 ^ 1) as i64),
819+
],
820+
output_idx: -4,
821+
output_inverted: true,
822+
}
823+
}
824+
779825
// a41oi: Y = !((A1 & A2 & A3 & A4) | B1)
780826
"a41oi" => {
781827
let a1 = inputs.a1;

0 commit comments

Comments
 (0)