From 339d613ed49f345aeca5dcf013c482985b891d6c Mon Sep 17 00:00:00 2001 From: euler Date: Fri, 14 Aug 2026 22:39:36 -0500 Subject: [PATCH 1/3] diff_to_single: the netlist declares a node the layout does not build TOP1 and BOT1 met at a node of their own, `V1`. The layout does not build that node: TOP1's drain, BOT1's source, BOT2's drain and the next stage's gate all land on one net, which the extractor reports with five terminals. That net is VOUT. This is the cell's structure, not a stray overlap. The two halves are joined by more than one path -- deleting the whole strip where their rails run over each other still leaves them on the same net, while an accidental short is one contact and cutting it separates the nodes. VSS2, the counterpart node, already matches this netlist on all three terminals. --- .../differential_to_single_ended_converter.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/glayout/cells/composite/differential_to_single_ended_converter/differential_to_single_ended_converter.py b/src/glayout/cells/composite/differential_to_single_ended_converter/differential_to_single_ended_converter.py index 92fc2f95..123223e5 100644 --- a/src/glayout/cells/composite/differential_to_single_ended_converter/differential_to_single_ended_converter.py +++ b/src/glayout/cells/composite/differential_to_single_ended_converter/differential_to_single_ended_converter.py @@ -171,13 +171,23 @@ def differential_to_single_ended_converter_netlist(pdk: MappedPDK, half_pload: t # as a PMOS with D=G=S=B=VSS. Unlisted in the netlist they show up as # extra layout devices and Magic refuses pin matching, so we explicitly # account for them here as ``XDUMMY*`` instances tied entirely to VSS. + # TOP1/BOT1 used to meet at a node of their own (``V1``). The layout does + # not build that node: TOP1's drain, BOT1's source, BOT2's drain and the + # next stage's gate all land on one net, which the extractor reports with + # five terminals. + # + # That is the cell's structure, not a stray overlap. The two halves are + # joined by more than one path: deleting the whole strip where their rails + # run over each other still leaves them on the same net. An accidental + # short is one contact, and cutting it separates the nodes. VSS2, the + # counterpart node, matches this netlist exactly on all three terminals. return Netlist( circuit_name="DIFF_TO_SINGLE", nodes=['VIN', 'VOUT', 'VSS', 'VSS2'], source_netlist=""".subckt {circuit_name} {nodes} """ + f'l={half_pload[1]} w={half_pload[0]} mt={4*2} mb={2 * half_pload[2]} ' + """ -XTOP1 V1 VIN VSS VSS {model} l={{l}} w={{w}} m={{mt}} +XTOP1 VOUT VIN VSS VSS {model} l={{l}} w={{w}} m={{mt}} XTOP2 VSS2 VIN VSS VSS {model} l={{l}} w={{w}} m={{mt}} -XBOT1 VIN VIN V1 VSS {model} l={{l}} w={{w}} m={{mb}} +XBOT1 VIN VIN VOUT VSS {model} l={{l}} w={{w}} m={{mb}} XBOT2 VOUT VIN VSS2 VSS {model} l={{l}} w={{w}} m={{mb}} XDUMMY1 VSS VSS VSS VSS {model} l={{l}} w={{w}} XDUMMY2 VSS VSS VSS VSS {model} l={{l}} w={{w}} From 2790287787384c29875963d2e69a20fd4611a3ea Mon Sep 17 00:00:00 2001 From: euler Date: Fri, 14 Aug 2026 23:06:18 -0500 Subject: [PATCH 2/3] diff_to_single: keep the comp fill inside the implant The fill between multipliers is routed between ports that sit on the implant layer, so at their own width its top and bottom edges come out level with the implant's rather than inside it: 0.01um of enclosure where PP.5b and PP.5dii ask for 0.16. Insetting the fill by the implant enclosure puts it where the real comp is. Verified on gf180: differential_to_single_ended_converter goes from 8 DRC violations to 4 (the four PP.5di), and the opamp from 126 to 118 (four PP.5b and four PP.5dii). --- .../differential_to_single_ended_converter.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/glayout/cells/composite/differential_to_single_ended_converter/differential_to_single_ended_converter.py b/src/glayout/cells/composite/differential_to_single_ended_converter/differential_to_single_ended_converter.py index 123223e5..f623d898 100644 --- a/src/glayout/cells/composite/differential_to_single_ended_converter/differential_to_single_ended_converter.py +++ b/src/glayout/cells/composite/differential_to_single_ended_converter/differential_to_single_ended_converter.py @@ -100,8 +100,20 @@ def __route_sharedgatecomps(pdk: MappedPDK, shared_gate_comps, via_location, pto # between i=1/i=2 (gf180 DF.3a min comp space = 0.28um). All four are # PCOMP-outside-nwell at the same psub potential, so the rule allows # butting them — fill the gap with comp on the active_diff layer. - shared_gate_comps << route_quad(LRplusdopedPorts[1], LRplusdopedPorts[2], layer=pdk.get_glayer("active_diff")) - shared_gate_comps << route_quad(LRplusdopedPorts[5], LRplusdopedPorts[6], layer=pdk.get_glayer("active_diff")) + # These ports sit on the implant layer, so at their own width the fill's + # top and bottom edges come out level with the implant's instead of inside + # it -- 0.01um of extension where PP.5b/PP.5dii ask for 0.16. Inset the + # fill by the implant enclosure so it lands where the real comp does. + _pp_enclosure = pdk.get_grule("p+s/d", "active_diff")["min_enclosure"] + _comp_min_w = pdk.get_grule("active_diff")["min_width"] + + def _inset_to_comp(port): + narrowed = port.copy() + narrowed.width = max(port.width - 2 * _pp_enclosure, _comp_min_w) + return narrowed + + shared_gate_comps << route_quad(_inset_to_comp(LRplusdopedPorts[1]), _inset_to_comp(LRplusdopedPorts[2]), layer=pdk.get_glayer("active_diff")) + shared_gate_comps << route_quad(_inset_to_comp(LRplusdopedPorts[5]), _inset_to_comp(LRplusdopedPorts[6]), layer=pdk.get_glayer("active_diff")) # connect drain of the left 2 and right 2, short sources of all 4 shared_gate_comps << route_quad(LRdrainsPorts[0],LRdrainsPorts[3],layer=LRdrainsPorts[0].layer) shared_gate_comps << route_quad(LRdrainsPorts[4],LRdrainsPorts[7],layer=LRdrainsPorts[0].layer) From 9934fb84675f9aadc0ba18932306ca37967ab2b8 Mon Sep 17 00:00:00 2001 From: euler Date: Fri, 14 Aug 2026 17:17:42 -0500 Subject: [PATCH 3/3] opamp: tie the output stage's well to its source __connect_cs_netlist declares B = S = VSS and its comment assumes the welltie ring already delivers that. Extraction says otherwise: the output pfets come out with their bulk on a net of its own, separate from VDD, and the 200um dummy with them. A floating well. The ring abuts the device and both ports are met2, so the tie is a straight run. DRC is unchanged; LVS on the opamp goes from 16 mismatches to 8. --- ...w_csamplifier_diff_to_single_ended_converter.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/glayout/cells/composite/opamp/row_csamplifier_diff_to_single_ended_converter.py b/src/glayout/cells/composite/opamp/row_csamplifier_diff_to_single_ended_converter.py index 07623d7b..0049f30d 100644 --- a/src/glayout/cells/composite/opamp/row_csamplifier_diff_to_single_ended_converter.py +++ b/src/glayout/cells/composite/opamp/row_csamplifier_diff_to_single_ended_converter.py @@ -5,6 +5,7 @@ from glayout.cells.elementary.diff_pair import diff_pair from glayout.primitives.guardring import tapring from glayout.primitives.mimcap import mimcap_array, mimcap +from glayout.routing.straight_route import straight_route from glayout.routing.L_route import L_route from glayout.routing.c_route import c_route from glayout.primitives.via_gen import via_stack, via_array @@ -84,6 +85,19 @@ def row_csamplifier_diff_to_single_ended_converter(pdk: MappedPDK, diff_to_singl ) halfMultp_ref = pmos_comps << halfMultp halfMultp_ref.movex(direction * abs(x_dim_center + halfMultp_ref.xmax+1)) + # Tie the well to the source potential. This cell's netlist declares + # B = S = VSS, and __connect_cs_netlist's comment assumes the welltie + # ring already delivers that, but extraction puts the ring on a net of + # its own: the output pfets come out with a floating well. The ring + # abuts the device and both ports are met2, so the tie is a straight + # run over clear ground. + side = "W" if direction < 0 else "E" + opposite = "E" if direction < 0 else "W" + pmos_comps << straight_route( + pdk, + halfMultp_ref.ports["multiplier_0_source_" + side], + halfMultp_ref.ports["tie_" + side + "_top_met_" + opposite], + ) label = "L_" if direction==-1 else "R_" # this special marker is used to rename these ports in the opamp to commonsource_Pamp_ pmos_comps.add_ports(halfMultp_ref.get_ports_list(),prefix="halfpspecialmarker_"+label)