Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -171,13 +183,17 @@ 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 puts
# them, BOT2's drain and the next stage's gate on one net. Structure, not
# a stray overlap: deleting the whole strip where the rails overlap still
# leaves them connected, and VSS2 already matched 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}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
print_ports,
set_port_orientation,
rename_component_ports,
no_pin_labels,
)
from glayout.util.snap_to_grid import component_snap_to_grid
from pydantic import validate_arguments
Expand Down Expand Up @@ -89,14 +90,17 @@ def diff_pair_ibias(
# rings so klayout extracts the dummies' G/S/D on bulk (B). sky130
# always wants 'B' too — passing it unconditionally is correct on
# both PDKs because it matches the magic-merged extraction.
center_diffpair_comp = diff_pair(
pdk,
width=half_diffpair_params[0],
length=half_diffpair_params[1],
fingers=half_diffpair_params[2],
rmult=rmult,
dum_net='B',
)
# VTAIL is a top-level pin of a standalone diff_pair but an internal net
# here, so the inherited label would extract as an extra top-level pin.
with no_pin_labels():
center_diffpair_comp = diff_pair(
pdk,
width=half_diffpair_params[0],
length=half_diffpair_params[1],
fingers=half_diffpair_params[2],
rmult=rmult,
dum_net='B',
)
# add antenna diodes if that option was specified
diffpair_centered_ref = prec_ref_center(center_diffpair_comp)
diffpair_i_.add(diffpair_centered_ref)
Expand Down Expand Up @@ -194,23 +198,16 @@ def diff_pair_ibias(
viaoffset=None,
)
cmirror.add_ports(srcshort.get_ports_list(), prefix="purposegndports")
# current mirror netlist — gf180 needs `dummies_tied_to_bulk=False`
# because here we use raw two_nfet_interdigitized + custom routing,
# NOT current_mirror, so the standalone-cell's straight_route from
# dummy gsdcon to welltie never gets drawn; klayout extracts the
# cmirror dummies on a per-cell floating net. sky130 magic merges
# the floating dummies into the bulk so the schematic must keep
# them tied to VB or magic counts an extra net.
## HACK: Note that this is a hack for magic LVS, and it's likely incorrect
## we probably want to fix it properly
_dummies_tied = (pdk.name.lower() == "sky130")
# Current mirror netlist. The dummies share the composite's pwell/tap
# context, so both extractors report their G/S/D on the bulk net. Tying
# them to it here matches that; a separate net would not exist in layout.
cmirror.info['netlist'] = current_mirror_netlist(
pdk,
width=diffpair_bias[0],
length=diffpair_bias[1],
fingers=1,
multipliers=diffpair_bias[2],
dummies_tied_to_bulk=_dummies_tied,
dummies_tied_to_bulk=True,
)

# add cmirror — bump y-offset enough that the LVPWELL paddings of the
Expand Down Expand Up @@ -277,6 +274,14 @@ def diff_pair_ibias(
("VSS", "ibias_purposegndport", "met4"),
("B", "tap_N_top_met_S", "met1"),
]
# These labels let the cell pass LVS on its own. Inside a composite the
# same nets are internal, so inheriting the labels extracts them as
# top-level pins the parent's schematic does not have -- that is what
# leaves the opamp with VDD1, VDD2, VN|VP, IBIAS and B to spare. Same
# switch diff_pair, current_mirror and fvf already honour.
import os as _os_pins
if _os_pins.environ.get("GLAYOUT_NO_PIN_LABELS"):
_pin_specs = []
for _text, _portname, _glayer in _pin_specs:
_port = diffpair_i_.ports[_portname]
_alignment = _orient_to_align[round(_port.orientation) % 360]
Expand Down
10 changes: 8 additions & 2 deletions src/glayout/cells/composite/opamp/diff_pair_stackedcmirror.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,17 @@

from glayout.cells.composite.diffpair_cmirror_bias import diff_pair_ibias
from glayout.cells.composite.stacked_current_mirror import stacked_nfet_current_mirror
from glayout.util.port_utils import no_pin_labels
from glayout.cells.composite.differential_to_single_ended_converter import differential_to_single_ended_converter
from glayout.cells.composite.opamp.row_csamplifier_diff_to_single_ended_converter import row_csamplifier_diff_to_single_ended_converter


@validate_arguments
def __add_diff_pair_and_bias(pdk: MappedPDK, toplevel_stacked: Component, half_diffpair_params: tuple[float, float, int], diffpair_bias: tuple[float, float, int], rmult: int, with_antenna_diode_on_diffinputs: int) -> Component:
clear_cache()
diffpair_i_ref = diff_pair_ibias(pdk, half_diffpair_params, diffpair_bias, rmult, with_antenna_diode_on_diffinputs)
# Inside the opamp, diff_pair_ibias's pins are internal nets.
with no_pin_labels():
diffpair_i_ref = diff_pair_ibias(pdk, half_diffpair_params, diffpair_bias, rmult, with_antenna_diode_on_diffinputs)
toplevel_stacked.add(diffpair_i_ref)
toplevel_stacked.add_ports(diffpair_i_ref.get_ports_list(),prefix="diffpair_")

Expand Down Expand Up @@ -76,7 +79,10 @@ def __route_bottom_ncomps_except_drain_nbias(pdk: MappedPDK, toplevel_stacked: C
# leaving a sliver gap that trips m2.2a. Stamp an m2 patch at each
# corner that overlaps both polygons so they merge in DRC.
if pdk.name.lower() == "gf180":
from gdsfactory.components.rectangle import rectangle as _rect
# use the backend's rectangle (already imported at module level), not
# gdsfactory's: importing it directly bypasses the backend abstraction
# and yields a component the active backend cannot reference.
_rect = rectangle
_m2 = pdk.get_glayer("met2")
# Use cmirror_ref_L's drain_E port (center.x is the inner edge of
# cmirror_ref_L's leftmost drain column on m2; mirror for R) and
Expand Down
20 changes: 12 additions & 8 deletions src/glayout/cells/composite/opamp/opamp_twostage.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,18 +122,22 @@ def __add_mimcap_arr(pdk: MappedPDK, opamp_top: Component, mim_cap_size, mim_cap
displace_fact = max(max_metalsep,pdk.get_grule("capmet")["min_separation"]) + 0.3 # Hack
mimcaps_ref.movex(pdk.snap_to_2xgrid(opamp_top.xmax + displace_fact + mim_cap_size[0]/2))
mimcaps_ref.movey(pdk.snap_to_2xgrid(ymin + mim_cap_size[1]/2))
# connect mimcap. Match OpenFASOC reference: use the cap plates' native
# routing layers — V2 (cap_metalbottom) is glayout met4, V1 (cap_metaltop)
# is glayout met5. The c_route to V2 lands on met4 and the L_route to V1
# lands on met5, so both via stacks contact the cap plates directly.
# connect mimcap. The routes have to land on the layers the cap plates are
# actually drawn on, and those come from the PDK: sky130 puts the MIM
# between met4 and met5, gf180's option A puts it between met2 and met3.
# Hardcoding met4/met5 left the whole array floating on gf180 -- the routes
# arrived two levels above the plates with no via3 to step down.
capmettop = pdk.layer_to_glayer(pdk.get_grule("capmet")["capmettop"])
capmetbottom = pdk.layer_to_glayer(pdk.get_grule("capmet")["capmetbottom"])
# the corridor layer has to stay clear of both plates
cglayer = "met4" if capmettop != "met4" else "met5"
port1 = opamp_top.ports["pcomps_mimcap_connection_con_N"]
port2 = mimcaps_ref.ports["row"+str(int(mim_cap_rows)-1)+"_col0_top_met_N"]
cref2_extension = max_metalsep + opamp_top.ymax - max(port1.center[1], port2.center[1])
opamp_top << c_route(pdk,port1,port2, extension=cref2_extension, fullbottom=True, e1glayer="met3", e2glayer="met5", cglayer="met4", width2=5.0) # A Hack
opamp_top << c_route(pdk,port1,port2, extension=cref2_extension, fullbottom=True, e1glayer="met3", e2glayer=capmettop, cglayer=cglayer, width2=5.0) # A Hack
intermediate_output = set_port_orientation(n_to_p_output_route.ports["con_S"],"N")
# opamp_top << L_route(pdk, mimcaps_ref.ports["row0_col0_top_met_N"], intermediate_output, hwidth=1, hglayer="met4", vglayer="met4")
# C route up right up to reach the mimcap port, extension is
opamp_top << L_route(pdk, intermediate_output, mimcaps_ref.ports["row0_col0_bottom_met_E"], fullbottom=True, vglayer="met5", hglayer="met4")
# C route up right up to reach the mimcap port, extension is
opamp_top << L_route(pdk, intermediate_output, mimcaps_ref.ports["row0_col0_bottom_met_E"], fullbottom=True, vglayer=cglayer, hglayer=capmetbottom)
opamp_top.add_ports(mimcaps_ref.get_ports_list(),prefix="mimcap_")
# add the cs output as a port
opamp_top.add_port(name="commonsource_output_E", port=intermediate_output)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -84,6 +85,18 @@ 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))
# Well to the source potential. This cell's netlist declares B = S =
# VSS and __connect_cs_netlist assumes the welltie ring already does
# that, but extraction puts the ring on a net of its own and the
# output pfets end up with a floating well. The ring sits against the
# device and both ports are met2, so this is a straight run.
side = "W" if direction < 0 else "E"
far = "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_" + far],
)
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)
Expand Down