Skip to content

Commit 68cc9bc

Browse files
authored
Merge pull request #4516 from The-OpenROAD-Project-staging/sky130hd-lvs-fix
Updates to fix sky130hd LVS issues
2 parents 8e12884 + 5eebdc0 commit 68cc9bc

3 files changed

Lines changed: 299 additions & 15 deletions

File tree

flow/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -766,6 +766,7 @@ ifneq ($(KLAYOUT_LVS_FILE),)
766766
$(RUN_CMD) --log $(abspath $(LOG_DIR)/6_lvs.log) --tee -- \
767767
$(SCRIPTS_DIR)/klayout.sh -b -rd in_gds="$<" \
768768
-rd cdl_file=$(abspath $(OBJECTS_DIR)/6_final_concat.cdl) \
769+
-rd top_cell="$(DESIGN_NAME)" \
769770
-rd report_file=$(abspath $@) \
770771
-r $(KLAYOUT_LVS_FILE)
771772
else

flow/platforms/sky130hd/cdl/sky130hd.cdl

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4942,8 +4942,8 @@ MI5 Y A net35 VNB nfet_01v8 m=1 w=0.55 l=0.15 mult=1 sa=0.265 sb=0.265
49424942

49434943
.SUBCKT sky130_fd_sc_hd__conb_1 VGND VNB VPB VPWR HI LO
49444944
*.PININFO VGND:I VNB:I VPB:I VPWR:I HI:O LO:O
4945-
rI12 VGND LO short
4946-
rI11 HI VPWR short
4945+
rI12 VGND LO 0
4946+
rI11 HI VPWR 0
49474947
.ENDS sky130_fd_sc_hd__conb_1
49484948

49494949

@@ -11166,13 +11166,13 @@ M1021 VPWR a_714_47# a_620_911# VPB pfet_01v8_hvt w=790000u l=150000u
1116611166

1116711167
.SUBCKT sky130_fd_sc_hd__macro_sparecell VGND VNB VPB VPWR LO
1116811168
*.PININFO VGND:I VNB:I VPB:I VPWR:I LO:O
11169-
XI1 VGND VNB VPB VPWR net59 LO / sky130_fd_sc_hd__conb_1
11170-
XI2 LO LO VGND VNB VPB VPWR nd2right / sky130_fd_sc_hd__nand2_2
11171-
XI3 LO LO VGND VNB VPB VPWR nd2left / sky130_fd_sc_hd__nand2_2
11172-
XI4 nd2right nd2right VGND VNB VPB VPWR nor2right / sky130_fd_sc_hd__nor2_2
11173-
XI5 nd2left nd2left VGND VNB VPB VPWR nor2left / sky130_fd_sc_hd__nor2_2
11174-
XI6 nor2right VGND VNB VPB VPWR invright / sky130_fd_sc_hd__inv_2
11175-
XI7 nor2left VGND VNB VPB VPWR invleft / sky130_fd_sc_hd__inv_2
11169+
XI1 VGND VNB VPB VPWR net59 LO sky130_fd_sc_hd__conb_1
11170+
XI2 LO LO VGND VNB VPB VPWR nd2right sky130_fd_sc_hd__nand2_2
11171+
XI3 LO LO VGND VNB VPB VPWR nd2left sky130_fd_sc_hd__nand2_2
11172+
XI4 nd2right nd2right VGND VNB VPB VPWR nor2right sky130_fd_sc_hd__nor2_2
11173+
XI5 nd2left nd2left VGND VNB VPB VPWR nor2left sky130_fd_sc_hd__nor2_2
11174+
XI6 nor2right VGND VNB VPB VPWR invright sky130_fd_sc_hd__inv_2
11175+
XI7 nor2left VGND VNB VPB VPWR invleft sky130_fd_sc_hd__inv_2
1117611176
.ENDS sky130_fd_sc_hd__macro_sparecell
1117711177

1117811178

@@ -17409,7 +17409,7 @@ MMIN1 Ab A VGND VNB nfet_01v8 m=3 w=0.65 l=0.15 mult=1 sa=0.265
1740917409
+ sb=0.265 sd=0.28 topography=normal area=0.063 perim=1.14
1741017410
MMIN2 net29 Ab VGND VNB nfet_01v8 m=8 w=0.65 l=0.15 mult=1 sa=0.265
1741117411
+ sb=0.265 sd=0.28 topography=normal area=0.063 perim=1.14
17412-
rI112 net29 X short
17412+
rI112 net29 X 0
1741317413
.ENDS sky130_fd_sc_hd__probe_p_8
1741417414

1741517415

@@ -17441,9 +17441,9 @@ MMIN1 Ab A VGND VNB nfet_01v8 m=3 w=0.65 l=0.15 mult=1 sa=0.265
1744117441
+ sb=0.265 sd=0.28 topography=normal area=0.063 perim=1.14
1744217442
MMIN2 net33 Ab VGND VNB nfet_01v8 m=8 w=0.65 l=0.15 mult=1 sa=0.265
1744317443
+ sb=0.265 sd=0.28 topography=normal area=0.063 perim=1.14
17444-
rI112 net33 X short
17445-
rI120 VGND met5vgnd short
17446-
rI119 VPWR met5vpwr short
17444+
rI112 net33 X 0
17445+
rI120 VGND met5vgnd 0
17446+
rI119 VPWR met5vpwr 0
1744717447
.ENDS sky130_fd_sc_hd__probec_p_8
1744817448

1744917449

flow/platforms/sky130hd/lvs/sky130hd.lylvs

Lines changed: 285 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ if $in_gds
2525
end
2626

2727
if $report_file
28-
report($report_file)
28+
report_lvs($report_file)
2929
else
3030
report_lvs("lvs_report.lvsdb")
3131
end
@@ -211,9 +211,278 @@ connect(MET5, MET5TXT)
211211
# Global
212212
connect_global(SUB, "VNB")
213213

214+
# Merge 0-ohm tie resistors in the schematic into direct net shorts,
215+
# so their structure matches the physical layout (which has no discrete
216+
# device for these ties, just a metal/contact short).
217+
schematic.each_circuit do |c|
218+
next unless c.name == "SKY130_FD_SC_HD__CONB_1"
219+
to_remove = []
220+
c.each_device do |d|
221+
dc = d.device_class
222+
if dc.name == "RES" &amp;&amp; d.parameter("R") == 0.0
223+
na = d.net_for_terminal(dc.terminal_id("A"))
224+
nb = d.net_for_terminal(dc.terminal_id("B"))
225+
c.join_nets(na, nb) if na &amp;&amp; nb &amp;&amp; na.name != nb.name
226+
to_remove &lt;&lt; d
227+
end
228+
end
229+
to_remove.each { |d| c.remove_device(d) }
230+
end
231+
232+
same_circuits("sky130_fd_sc_hd__conb_1", "SKY130_FD_SC_HD__CONB_1")
233+
234+
#
235+
# Required to match transistors properly
236+
#
237+
# NOTE: real GDS-extracted layout often ties 3+ transistor fingers to a single
238+
# shared internal diffusion node (interdigitated/folded layout), whereas the
239+
# idealized schematic CDL always has exactly 2 terminals per internal fold
240+
# node. The original version of this function required terms.size == 2,
241+
# which silently skipped any internal net with more fanout than that,
242+
# leaving some fingers permanently unmerged no matter how many convergence
243+
# iterations ran.
244+
#
245+
# This version builds a signature for EVERY qualifying terminal on a net
246+
# (any count), then requires the ENTIRE sorted multiset of a net's terminal
247+
# signatures to match another net's before considering them the same
248+
# electrical node and joining them. This generalizes cleanly to N-terminal
249+
# nodes while still refusing to conflate two nets that only partially
250+
# overlap in connectivity (e.g. two distinct internal series junctions in a
251+
# 3-stage stack) -- an earlier, more permissive per-terminal-match version
252+
# caused exactly that kind of incorrect over-merge.
253+
#
254+
# Signature keys go through net_key() (see above), not raw .name, because
255+
# on the layout side unnamed internal nets all key as "" until something
256+
# merges/labels them -- two genuinely different anonymous nets can
257+
# otherwise collide on that empty string and get incorrectly folded
258+
# together (observed: two separate 2-terminal PFET fold nodes merged into
259+
# one bogus 4-terminal net purely because their neighboring nets were
260+
# still unnamed at merge time).
261+
#
262+
#
263+
# Signature key for a net. On the SCHEMATIC side every net already has a
264+
# real, stable name pulled from the CDL, so using .name directly is safe.
265+
# On the LAYOUT side, internal nets extracted from raw geometry are
266+
# anonymous ("" name) until something merges/labels them -- which means
267+
# two genuinely DIFFERENT unnamed internal nets can both key as "" and
268+
# collide in the signature below, causing a false-positive merge between
269+
# structurally distinct nodes (observed: two separate 2-terminal PFET
270+
# fold nodes getting joined into one bogus 4-terminal net, purely because
271+
# their neighboring "opposite" and "gate" nets were both still unnamed at
272+
# merge time). Falling back to Ruby object identity for unnamed nets
273+
# guarantees distinct anonymous nets never share a key.
274+
#
275+
#
276+
# IMPORTANT: neither net.object_id NOR RBA::Net#== can be trusted as a
277+
# stable identity for the same underlying net across different retrieval
278+
# paths. Direct signature tracing proved BOTH broken in turn:
279+
# 1) object_id: a net's own terminal reported one object_id, while the
280+
# SAME physical net, reached via a neighboring terminal's
281+
# net_for_terminal(...) lookup, reported a DIFFERENT object_id.
282+
# 2) ==: even after switching to an ==-based cache, two independently
283+
# -obtained wrapper references to the IDENTICAL physical net (PNDA,
284+
# looked up separately from its two different neighbors) did not
285+
# compare as == to each other, so each got assigned a distinct label
286+
# instead of being recognized as the same net.
287+
# KLayout's RBA bindings evidently hand back a freshly-constructed Ruby
288+
# wrapper object on each indirect lookup, and neither Ruby-level identity
289+
# nor the RBA-level equality operator sees through that to the underlying
290+
# net for this API/version.
291+
#
292+
# The fix that actually works: give every unnamed net a real, PERSISTENT
293+
# name up front, via net.name=. Naming is stored as state on the
294+
# underlying net object itself (that's what makes a net's name durable at
295+
# all) -- so every subsequent lookup of that same net, through WHATEVER
296+
# retrieval path, reports the identical name string back. This sidesteps
297+
# wrapper identity entirely and puts anonymous nets on the same footing
298+
# as the schematic side, where real persisted names never had this
299+
# problem in the first place.
300+
#
301+
def merge_parallel_series_stacks(circuit, device_class_names)
302+
synth_counter = 0
303+
circuit.each_net do |n|
304+
if n.name.nil? || n.name.empty?
305+
synth_counter += 1
306+
n.name = "synth_#{synth_counter}"
307+
end
308+
end
309+
310+
net_key = lambda do |net|
311+
next "unconnected" if net.nil?
312+
net.name
313+
end
314+
315+
chains = []
316+
circuit.each_net do |net|
317+
next if net.each_pin.any? || net.each_subcircuit_pin.any?
318+
319+
# Build a signature for every qualifying S/D terminal on this net (any
320+
# count -- not just exactly 2, since real GDS-extracted layout can tie
321+
# 3+ fingers to one shared diffusion node). Two nets are only considered
322+
# interchangeable if their ENTIRE terminal signature multisets match --
323+
# not just one terminal in common -- otherwise nodes with genuinely
324+
# different roles in the stack (e.g. two separate internal series
325+
# junctions) can get incorrectly unioned just because one terminal
326+
# happens to coincide.
327+
# Hard guard: a genuine multi-finger diffusion fold node is purely an
328+
# internal S/D connection and NEVER also drives a gate. A net that does
329+
# double duty as both a drain/source AND a gate input elsewhere (e.g. a
330+
# cross-coupled latch's storage node, which drives the gates of the
331+
# opposite inverter) is a real, electrically distinct circuit node --
332+
# not a layout-folding artifact -- even if its S/D signature happens to
333+
# look symmetric with another node. Skip any net where any device (of
334+
# ANY class, not just the merge-target classes) has a gate terminal here.
335+
has_gate_terminal = net.each_terminal.any? do |t|
336+
dc = t.device.device_class
337+
begin
338+
t.terminal_id == dc.terminal_id("G")
339+
rescue
340+
false
341+
end
342+
end
343+
next if has_gate_terminal
344+
345+
term_sigs = []
346+
net.each_terminal.each do |t|
347+
d = t.device
348+
dc = d.device_class
349+
next unless device_class_names.any? { |n| n.downcase == dc.name.downcase }
350+
351+
tid_s = dc.terminal_id("S")
352+
tid_g = dc.terminal_id("G")
353+
tid_d = dc.terminal_id("D")
354+
tid_b = dc.terminal_id("B")
355+
356+
role = t.terminal_id
357+
next unless [tid_s, tid_d].include?(role)
358+
opp = (role == tid_s) ? tid_d : tid_s
359+
360+
# NOTE: role is intentionally NOT included in the signature. MOSFETs
361+
# are physically symmetric devices -- S and D are interchangeable for
362+
# connectivity purposes. KLayout's raw device extraction can assign
363+
# the S/D role oppositely between two structurally-identical devices
364+
# that are simple mirror images of each other in the layout (e.g. two
365+
# parallel drive-strength fingers of the same gate). Including role
366+
# as a literal signature field caused exactly that: two electrically
367+
# identical fold-node terminals differed only in role (S vs D) and so
368+
# never matched, leaving genuinely parallel finger-doubled legs
369+
# unmerged (observed: a two-leg B1-C1 pull-up in a211oi_4 where one
370+
# leg's C1 PFET had opposite=Y via its D terminal and the other leg's
371+
# via its S terminal -- same device topology, different raw role).
372+
term_sigs &lt;&lt; [net_key.call(d.net_for_terminal(opp)), net_key.call(d.net_for_terminal(tid_g)), net_key.call(d.net_for_terminal(tid_b)), dc.name.downcase]
373+
end
374+
next if term_sigs.size &lt; 2
375+
376+
whole_net_sig = term_sigs.sort
377+
chains &lt;&lt; [whole_net_sig, net]
378+
end
379+
380+
groups = {}
381+
chains.each { |sig, net| (groups[sig] ||= []) &lt;&lt; net }
382+
383+
# A net can appear only once here (one whole-net signature per net now,
384+
# not one per terminal), but joining still mutates the netlist as we go,
385+
# so guard against reusing a net object already consumed by an earlier
386+
# join in this same pass -- reusing a stale reference corrupts KLayout's
387+
# internal net registry and crashes the SPICE writer later. Anything left
388+
# over is picked up cleanly on the next converge_merge iteration.
389+
joined = {}
390+
groups.each_value do |nets_in_group|
391+
uniq_nets = nets_in_group.uniq.reject { |n| joined[n.object_id] }
392+
next if uniq_nets.size &lt; 2
393+
keep = uniq_nets[0]
394+
uniq_nets.each { |n| joined[n.object_id] = true }
395+
uniq_nets[1..-1].each { |other| circuit.join_nets(keep, other) }
396+
end
397+
end
398+
399+
#
400+
# Iteratively merge parallel/series stacks until the device count
401+
# stops shrinking (a single pass can leave multi-finger devices
402+
# partially folded, e.g. sky130 cells where a "wide" transistor is
403+
# laid out as several minimum-width fingers in parallel).
404+
#
405+
def converge_merge(circuit, nmos_classes, pmos_classes, max_iters = 10)
406+
prev_count = -1
407+
max_iters.times do
408+
merge_parallel_series_stacks(circuit, nmos_classes)
409+
merge_parallel_series_stacks(circuit, pmos_classes)
410+
circuit.combine_devices
411+
count = circuit.each_device.to_a.size
412+
return if count == prev_count
413+
prev_count = count
414+
end
415+
puts "WARNING: converge_merge did not stabilize for #{circuit.name} after #{max_iters} iterations"
416+
end
417+
418+
#
419+
# Apply to all transistor types
420+
#
421+
nmos_classes = ["nfet_01v8", "nfet_01v8_lvt", "nfet_g5v0d10v5", "nfet_01v8_nvt"]
422+
pmos_classes = ["pfet_01v8", "pfet_01v8_hvt", "pfet_g5v0d10v5"]
423+
424+
schematic.each_circuit do |c|
425+
converge_merge(c, nmos_classes, pmos_classes)
426+
end
427+
214428
# Actually performs the extraction
215429
netlist # ... not really required
216430

431+
# Keep diode in both netlist and schematic, so it matches
432+
c1 = netlist.circuit_by_name("sky130_fd_sc_hd__conb_1")
433+
c1.dont_purge = true if c1
434+
c2 = schematic.circuit_by_name("SKY130_FD_SC_HD__CONB_1")
435+
c2.dont_purge = true if c2
436+
437+
split_gates("nfet_01v8")
438+
split_gates("nfet_01v8_lvt")
439+
split_gates("nfet_g5v0d10v5")
440+
split_gates("nfet_01v8_nvt")
441+
split_gates("pfet_01v8")
442+
split_gates("pfet_01v8_hvt")
443+
split_gates("pfet_g5v0d10v5")
444+
445+
#
446+
# Same multi-finger folding applies to the layout-extracted netlist.
447+
# Empirically this must run AFTER split_gates, not before: running it
448+
# earlier (against the raw, still-undecomposed extracted device
449+
# representation) produced MORE fragmented results, not fewer.
450+
#
451+
netlist.each_circuit do |c|
452+
converge_merge(c, nmos_classes, pmos_classes)
453+
end
454+
455+
456+
# Ties VNB and VSS across all cells.
457+
#
458+
# NOTE: net_by_name("VSS") does an EXACT match. But KLayout's own
459+
# extraction can already merge the ground net together with tied-off
460+
# signal pins before this code runs -- e.g. constant-0 outputs like
461+
# alert_major_o/alert_minor_o/data_addr_o[0:1]/instr_addr_o[0:1] that RTL
462+
# wires directly to ground for a disabled feature or unused bit. Once
463+
# merged, KLayout renames the net to a concatenated string like
464+
# "VSS,data_addr_o[0],instr_addr_o[0],..." (the same convention that
465+
# produced "VNB,VSS" earlier), which no longer equals "VSS" exactly --
466+
# so net_by_name("VSS") silently returns nil, the VNB/VSS join never
467+
# fires, and VNB is left stranded on its own to fail comparison (observed
468+
# at the ibex_core top level: VNB unmatched by itself, VSS bundled into
469+
# a separate large tied-off-signal net). Find VSS by checking each net's
470+
# comma-separated name components instead of requiring an exact match.
471+
#
472+
def find_net_by_name_component(circuit, component)
473+
circuit.each_net.find do |n|
474+
nm = n.name
475+
nm &amp;&amp; nm.split(",").include?(component)
476+
end
477+
end
478+
479+
netlist.each_circuit do |c|
480+
next unless c.name == $top_cell
481+
n_vnb = find_net_by_name_component(c, "VNB")
482+
n_vss = find_net_by_name_component(c, "VSS")
483+
c.join_nets(n_vnb, n_vss) if n_vnb &amp;&amp; n_vss &amp;&amp; n_vnb.name != n_vss.name
484+
end
485+
217486
# Flatten cells which are present in one netlist only
218487
align
219488
# SIMPLIFICATION of the netlist
@@ -222,7 +491,7 @@ align
222491
#netlist.purge
223492
#netlist.purge_nets
224493
netlist.simplify
225-
#schematic.simplify
494+
schematic.simplify
226495

227496
# Tolerances for the devices extracted parameters
228497
# tolerance(device_class_name, parameter_name [, :absolute =&gt; absolute_tolerance] [, :relative =&gt; relative_tolerance])
@@ -245,6 +514,19 @@ equivalent_pins("*AND3_4", "A", "B", "C")
245514
equivalent_pins("*AND4_1", "A", "B", "C", "D")
246515
equivalent_pins("*AND4_2", "A", "B", "C", "D")
247516
equivalent_pins("*AND4_4", "A", "B", "C", "D")
517+
equivalent_pins("*A21OI_1", "A1", "A2")
518+
equivalent_pins("*A21OI_2", "A1", "A2")
519+
equivalent_pins("*A21OI_4", "A1", "A2")
520+
equivalent_pins("*A211OI_1", "A1", "A2")
521+
equivalent_pins("*A211OI_1", "B1", "C1")
522+
equivalent_pins("*A211OI_2", "A1", "A2")
523+
equivalent_pins("*A211OI_2", "B1", "C1")
524+
equivalent_pins("*A211OI_4", "A1", "A2")
525+
equivalent_pins("*A211OI_4", "B1", "C1")
526+
equivalent_pins("*A221OI_1", "A1", "A2")
527+
equivalent_pins("*A221OI_1", "B1", "B2")
528+
equivalent_pins("*A22OI_1", "A1", "A2")
529+
equivalent_pins("*A22OI_1", "B1", "B2")
248530
equivalent_pins("*NAND2_1", "A", "B")
249531
equivalent_pins("*NAND2_2", "A", "B")
250532
equivalent_pins("*NAND2_4", "A", "B")
@@ -254,6 +536,7 @@ equivalent_pins("*NAND3_4", "A", "B", "C")
254536
equivalent_pins("*NAND4_X1", "A", "B", "C", "D")
255537
equivalent_pins("*NAND4_2", "A", "B", "C", "D")
256538
equivalent_pins("*NAND4_4", "A", "B", "C", "D")
539+
257540
equivalent_pins("*OR2_1", "A", "B")
258541
equivalent_pins("*OR2_2", "A", "B")
259542
equivalent_pins("*OR2_4", "A", "B")

0 commit comments

Comments
 (0)