@@ -25,7 +25,7 @@ if $in_gds
2525end
2626
2727if $report_file
28- report ($report_file)
28+ report_lvs ($report_file)
2929else
3030 report_lvs("lvs_report.lvsdb")
3131end
@@ -211,9 +211,278 @@ connect(MET5, MET5TXT)
211211# Global
212212connect_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" && 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 && nb && na.name != nb.name
226+ to_remove << 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 << [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 < 2
375+
376+ whole_net_sig = term_sigs.sort
377+ chains << [whole_net_sig, net]
378+ end
379+
380+ groups = {}
381+ chains.each { |sig, net| (groups[sig] ||= []) << 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 < 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
215429netlist # ... 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 && 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 && n_vss && n_vnb.name != n_vss.name
484+ end
485+
217486# Flatten cells which are present in one netlist only
218487align
219488# SIMPLIFICATION of the netlist
@@ -222,7 +491,7 @@ align
222491#netlist.purge
223492#netlist.purge_nets
224493netlist.simplify
225- # schematic.simplify
494+ schematic.simplify
226495
227496# Tolerances for the devices extracted parameters
228497# tolerance(device_class_name, parameter_name [, :absolute => absolute_tolerance] [, :relative => relative_tolerance])
@@ -245,6 +514,19 @@ equivalent_pins("*AND3_4", "A", "B", "C")
245514equivalent_pins("*AND4_1", "A", "B", "C", "D")
246515equivalent_pins("*AND4_2", "A", "B", "C", "D")
247516equivalent_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")
248530equivalent_pins("*NAND2_1", "A", "B")
249531equivalent_pins("*NAND2_2", "A", "B")
250532equivalent_pins("*NAND2_4", "A", "B")
@@ -254,6 +536,7 @@ equivalent_pins("*NAND3_4", "A", "B", "C")
254536equivalent_pins("*NAND4_X1", "A", "B", "C", "D")
255537equivalent_pins("*NAND4_2", "A", "B", "C", "D")
256538equivalent_pins("*NAND4_4", "A", "B", "C", "D")
539+
257540equivalent_pins("*OR2_1", "A", "B")
258541equivalent_pins("*OR2_2", "A", "B")
259542equivalent_pins("*OR2_4", "A", "B")
0 commit comments