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..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) @@ -171,13 +183,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}} 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)