Skip to content
Open
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
300 changes: 127 additions & 173 deletions blocks/elementary/current_mirror/current_mirror.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,219 +15,173 @@

import time

def add_cm_labels(cm_in: Component,
pdk: MappedPDK
) -> Component:

def add_cm_labels(cm_in: Component, pdk: MappedPDK) -> Component:
cm_in.unlock()

# list that will contain all port/comp info
move_info = list()
# create labels and append to info list
# vss
vsslabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.27,0.27),centered=True).copy()
move_info = []

# VSS
vsslabel = rectangle(layer=pdk.get_glayer("met2_pin"), size=(0.27, 0.27), centered=True).copy()
vsslabel.add_label(text="VSS",layer=pdk.get_glayer("met2_label"))
move_info.append((vsslabel,cm_in.ports["fet_A_source_E"],None))
# vref
vreflabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.27,0.27),centered=True).copy()
move_info.append((vsslabel, cm_in.ports["ref_multiplier_0_source_E"]))

# VREF
vreflabel = rectangle(layer=pdk.get_glayer("met2_pin"), size=(0.27, 0.27), centered=True).copy()
vreflabel.add_label(text="VREF",layer=pdk.get_glayer("met2_label"))
move_info.append((vreflabel,cm_in.ports["fet_A_drain_N"],None))
# vcopy
vcopylabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.27,0.27),centered=True).copy()
move_info.append((vreflabel, cm_in.ports["ref_multiplier_0_drain_N"]))

# VCOPY
vcopylabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.27, 0.27), centered=True).copy()
vcopylabel.add_label(text="VCOPY",layer=pdk.get_glayer("met2_label"))
move_info.append((vcopylabel,cm_in.ports["fet_B_drain_N"],None))

# VB
vblabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.5,0.5),centered=True).copy()
move_info.append((vcopylabel, cm_in.ports["out_multiplier_0_drain_N"]))

# VB (well tie)
vb_port = next(p for n, p in cm_in.ports.items() if n.startswith("well_"))
vblabel = rectangle(layer=pdk.get_glayer("met2_pin"), size=(0.5, 0.5), centered=True).copy()
vblabel.add_label(text="VB",layer=pdk.get_glayer("met2_label"))
move_info.append((vblabel,cm_in.ports["welltie_S_top_met_S"], None))

# move everything to position
for comp, prt, alignment in move_info:
alignment = ('c','b') if alignment is None else alignment
compref = align_comp_to_port(comp, prt, alignment=alignment)
cm_in.add(compref)
return cm_in.flatten()

def current_mirror_netlist(
pdk: MappedPDK,
width: float = 1,
length: float = None,
multipliers: int = 1,
with_dummy: bool = True,
n_or_p_fet: Optional[str] = 'nfet',
subckt_only: Optional[bool] = True,
) -> Netlist:
if length is None:
length = pdk.get_grule('poly')['min_width']
if width is None:
width = 3
mtop = multipliers if subckt_only else 1
model = pdk.models[n_or_p_fet]

source_netlist = """.subckt {circuit_name} {nodes} """ + f'l={length} w={width} m={mtop} ' + """
XA VREF VREF VSS VB {model} l={{l}} w={{w}} m={{m}}
XB VCOPY VREF VSS VB {model} l={{l}} w={{w}} m={{m}}"""
if with_dummy:
source_netlist += "\nXDUMMY VB VB VB VB {model} l={{l}} w={{w}} m={{2}}"
source_netlist += "\n.ends {circuit_name}"

instance_format = "X{name} {nodes} {circuit_name} l={length} w={width} m={mult}"

return Netlist(
circuit_name='CMIRROR',
nodes=['VREF', 'VCOPY', 'VSS', 'VB'],
source_netlist=source_netlist,
instance_format=instance_format,
parameters={
'model': model,
'width': width,
'length': length,
'mult': multipliers
}
)
move_info.append((vblabel, vb_port))

for label, port in move_info:
cm_in.add(align_comp_to_port(label, port, alignment=("c", "b")))

return cm_in.flatten()

def current_mirror_netlist(fetL: Component, fetR: Component) -> Netlist:
current_mirror_netlist = Netlist(circuit_name='CMIRROR', nodes=['VREF', 'VOUT', 'VSS', 'B'])
current_mirror_netlist.connect_netlist(
fetL.info['netlist'],
[('D', 'VREF'), ('G', 'VREF'), ('S', 'VSS'), ('B', 'B')]
)
current_mirror_netlist.connect_netlist(
fetR.info['netlist'],
[('D', 'VOUT'), ('G', 'VREF'), ('S', 'VSS'), ('B', 'B')]
)
return current_mirror_netlist

@cell
def current_mirror(
pdk: MappedPDK,
numcols: int = 3,
device: str = 'nfet',
with_dummy: Optional[bool] = True,
width: float = 3,
fingers: int = 1,
length: Optional[float] = None,
rmult: int = 1,
dummy: Union[bool, tuple[bool, bool]] = True,
with_substrate_tap: Optional[bool] = False,
with_tie: Optional[bool] = True,
tie_layers: tuple[str,str]=("met2","met1"),
tie_layers: tuple[str, str] = ("met2", "met1"),
subckt_only: Optional[bool] = True,
**kwargs
) -> Component:
"""An instantiable current mirror that returns a Component object. The current mirror is a two transistor interdigitized structure with a shorted source and gate. It can be instantiated with either nmos or pmos devices. It can also be instantiated with a dummy device, a substrate tap, and a tie layer, and is centered at the origin. Transistor A acts as the reference and Transistor B acts as the mirror fet
"""
An instantiable current mirror that returns a Component object.

The current mirror is a two-transistor structure with shorted
source and gate. It can be instantiated with either NMOS or PMOS
devices. Optional dummy devices, substrate tap, and tie ring
are supported. The layout is centered at the origin.

Transistor A acts as the reference device,
Transistor B acts as the mirror device.

Args:
pdk (MappedPDK): the process design kit to use
numcols (int): number of columns of the interdigitized fets
device (str): nfet or pfet (can only interdigitize one at a time with this option)
with_dummy (bool): True places dummies on either side of the interdigitized fets
with_substrate_tap (bool): boolean to decide whether to place a substrate tapring
with_tie (bool): boolean to decide whether to place a tapring for tielayer
tie_layers (tuple[str,str], optional): the layers to use for the tie. Defaults to ("met2","met1").
**kwargs: The keyword arguments are passed to the two_nfet_interdigitized or two_pfet_interdigitized functions and need to be valid arguments that can be accepted by the multiplier function
pdk (MappedPDK): Process design kit
numcols (int): Number of columns
device (str): 'nfet' or 'pfet'
width (float): Device width
fingers (int): Number of fingers
length (float): Channel length
rmult (int): Multiplier
dummy (bool | tuple): Dummy devices
with_substrate_tap (bool): Add substrate tap
with_tie (bool): Add well tie
tie_layers (tuple): Tie metal layers
subckt_only (bool): Netlist control
**kwargs: Passed to device generator

Returns:
Component: a current mirror component object
Component: Current mirror layout
"""
pdk.activate()
top_level = Component("current mirror")
if device.lower() in ['nmos', 'nfet']:
interdigitized_fets = two_nfet_interdigitized(
pdk,
numcols=numcols,
dummy=with_dummy,
with_substrate_tap=False,
with_tie=False,
**kwargs
)
elif device.lower() in ['pmos', 'pfet']:
interdigitized_fets = two_pfet_interdigitized(
pdk,
numcols=numcols,
dummy=with_dummy,
with_substrate_tap=False,
with_tie=False,
**kwargs
)
cmirror = Component("current_mirror")

# Handle dummy specification
if isinstance(dummy, bool):
dummy = (dummy, dummy)
well = None
if device.lower() in ["nmos", "nfet"]:
fetL = nmos(pdk, width=width, fingers=fingers, length=length, multipliers=1, with_tie=False, with_dummy=(dummy[0], False), with_dnwell=False, with_substrate_tap=False, rmult=rmult)
fetR = nmos(pdk, width=width, fingers=fingers, length=length, multipliers=1, with_tie=False, with_dummy=(False, dummy[1]), with_dnwell=False, with_substrate_tap=False, rmult=rmult)
min_spacing_x = (pdk.get_grule("n+s/d")["min_separation"] - 2 * (fetL.xmax - fetL.ports["multiplier_0_plusdoped_E"].center[0]))
well = "pwell"

elif device.lower() in ["pmos", "pfet"]:
fetL = pmos(pdk, width=width, fingers=fingers, length=length, multipliers=1, with_tie=False, with_dummy=(dummy[0], False), dnwell=False, with_substrate_tap=False, rmult=rmult)
fetR = pmos(pdk, width=width, fingers=fingers, length=length, multipliers=1, with_tie=False, with_dummy=(False, dummy[1]), dnwell=False, with_substrate_tap=False, rmult=rmult)
min_spacing_x = (pdk.get_grule("p+s/d")["min_separation"] - 2 * (fetL.xmax - fetL.ports["multiplier_0_plusdoped_E"].center[0]))
well = "nwell"

else:
raise ValueError(f"Device type {device} not recognized. Use 'nfet' or 'pfet'.")

top_level.add_ports(interdigitized_fets.get_ports_list(), prefix="fet_")
maxmet_sep = pdk.util_max_metal_seperation()
# short source of the fets
source_short = interdigitized_fets << c_route(pdk, interdigitized_fets.ports['A_source_E'], interdigitized_fets.ports['B_source_E'], extension=3*maxmet_sep, viaoffset=False)
# short gates of the fets
gate_short = interdigitized_fets << c_route(pdk, interdigitized_fets.ports['A_gate_W'], interdigitized_fets.ports['B_gate_W'], extension=3*maxmet_sep, viaoffset=False)
# short gate and drain of one of the reference
interdigitized_fets << L_route(pdk, interdigitized_fets.ports['A_drain_W'], gate_short.ports['con_N'], viaoffset=False, fullbottom=False)

top_level << interdigitized_fets
if with_tie:
if device.lower() in ['nmos','nfet']:
tap_layer = "p+s/d"
elif device.lower() in ['pmos','pfet']:
tap_layer = "n+s/d"
else:
raise ValueError(f"Device type {device} not recognized. Use 'nfet' or 'pfet'.")

tap_sep = max(
pdk.util_max_metal_seperation(),
pdk.get_grule("active_diff", "active_tap")["min_separation"],
)
tap_sep += pdk.get_grule(tap_layer, "active_tap")["min_enclosure"]
tap_encloses = (
2 * (tap_sep + interdigitized_fets.xmax),
2 * (tap_sep + interdigitized_fets.ymax),
)
tie_ref = top_level << tapring(pdk, enclosed_rectangle = tap_encloses, sdlayer = tap_layer, horizontal_glayer = tie_layers[0], vertical_glayer = tie_layers[1])
top_level.add_ports(tie_ref.get_ports_list(), prefix="welltie_")
try:
top_level << straight_route(pdk, top_level.ports[f"fet_B_{numcols - 1}_dummy_R_gsdcon_top_met_E"],top_level.ports["welltie_E_top_met_E"],glayer2="met1")
top_level << straight_route(pdk, top_level.ports["fet_A_0_dummy_L_gsdcon_top_met_W"],top_level.ports["welltie_W_top_met_W"],glayer2="met1")
except KeyError:
pass
try:
end_col = numcols - 1
port1 = f'B_{end_col}_dummy_R_gdscon_top_met_E'
top_level << straight_route(pdk, top_level.ports[port1], top_level.ports["welltie_E_top_met_E"], glayer2="met1")
except KeyError:
pass

raise ValueError(f"device must be either 'nmos' or 'pmos', got {device}")

viam2m3 = via_stack(pdk, "met2", "met3", centered=True)
metal_min_dim = max(pdk.get_grule("met2")["min_width"], pdk.get_grule("met3")["min_width"])
metal_space = max(pdk.get_grule("met2")["min_separation"], pdk.get_grule("met3")["min_separation"], metal_min_dim)
gate_route_os = (evaluate_bbox(viam2m3)[0] - fetL.ports["multiplier_0_gate_W"].width + metal_space)
min_spacing_y = metal_space + 2 * gate_route_os
min_spacing_y -= 2 * abs(fetL.ports["well_S"].center[1] - fetL.ports["multiplier_0_gate_S"].center[1])
ref_top = (cmirror << fetL).movey(fetL.ymax + min_spacing_y / 2).movex(-fetL.xmax)
# Output device (bottom, mirrored)
out_bot = (cmirror << fetR)
out_bot.mirror_y()
out_bot.movey(-fetR.ymax - min_spacing_y / 2).movex(-fetR.xmax)
cmirror.add_ports(ref_top.get_ports_list(), prefix="ref_")
cmirror.add_ports(out_bot.get_ports_list(), prefix="out_")
if with_substrate_tap:
tapref = cmirror << tapring(pdk, evaluate_bbox(cmirror, padding=1), horizontal_glayer="met1")
cmirror.add_ports(tapref.get_ports_list(), prefix="tap_")

for inst in [ref_top, out_bot]:
try:
cmirror << straight_route(pdk, inst.ports["multiplier_0_dummy_L_gsdcon_top_met_W"], cmirror.ports["tap_W_top_met_W"],glayer2="met1")
except KeyError:
pass
source_bar = cmirror << c_route(pdk, ref_top.ports["multiplier_0_source_E"], out_bot.ports["multiplier_0_source_E"], viaoffset=False)
cmirror.add_ports(source_bar.get_ports_list(), prefix="VSS_")
gate_bar = cmirror << c_route(pdk, ref_top.ports["multiplier_0_gate_W"], out_bot.ports["multiplier_0_gate_W"], viaoffset=False)
ref_drain = (ref_top.ports.get("multiplier_0_drain_W") or ref_top.ports.get("multiplier_0_drain_E"))
cmirror << L_route(pdk, ref_drain, gate_bar.ports["con_N"], viaoffset=False, fullbottom=False)
cmirror.add_ports([out_bot.ports["multiplier_0_drain_N"]], prefix="VOUT_")

# add a pwell
if device.lower() in ['nmos','nfet']:
top_level.add_padding(layers = (pdk.get_glayer("pwell"),), default = pdk.get_grule("pwell", "active_tap")["min_enclosure"], )
top_level = add_ports_perimeter(top_level, layer = pdk.get_glayer("pwell"), prefix="well_")
cmirror.add_padding(layers = (pdk.get_glayer("pwell"),), default = pdk.get_grule("pwell", "active_tap")["min_enclosure"], )
cmirror = add_ports_perimeter(cmirror, layer = pdk.get_glayer("pwell"), prefix="well_")
elif device.lower() in ['pmos','pfet']:
top_level.add_padding(layers = (pdk.get_glayer("nwell"),), default = pdk.get_grule("nwell", "active_tap")["min_enclosure"], )
top_level = add_ports_perimeter(top_level, layer = pdk.get_glayer("nwell"), prefix="well_")
cmirror.add_padding(layers = (pdk.get_glayer("nwell"),), default = pdk.get_grule("nwell", "active_tap")["min_enclosure"], )
cmirror = add_ports_perimeter(cmirror, layer = pdk.get_glayer("nwell"), prefix="well_")
else:
raise ValueError(f"Device type {device} not recognized. Use 'nfet' or 'pfet'.")
cmirror.info['netlist'] = current_mirror_netlist(fetL, fetR)
return cmirror


# add the substrate tap if specified
if with_substrate_tap:
subtap_sep = pdk.get_grule("dnwell", "active_tap")["min_separation"]
subtap_enclosure = (
2.5 * (subtap_sep + interdigitized_fets.xmax),
2.5 * (subtap_sep + interdigitized_fets.ymax),
)
subtap_ring = top_level << tapring(pdk, enclosed_rectangle = subtap_enclosure, sdlayer = "p+s/d", horizontal_glayer = "met2", vertical_glayer = "met1")
top_level.add_ports(subtap_ring.get_ports_list(), prefix="substrate_tap_")

top_level.add_ports(source_short.get_ports_list(), prefix='purposegndports')


top_level.info['netlist'] = current_mirror_netlist(
pdk,
width=kwargs.get('width', 3), length=kwargs.get('length'), multipliers=numcols, with_dummy=with_dummy,
n_or_p_fet = 'nfet' if device.lower() in ['nmos', 'nfet'] else 'pfet' if device.lower() in ['pmos', 'pfet'] else (_ for _ in ()).throw(ValueError(f"Device type {device} not recognized. Use 'nfet' or 'pfet'.")),
subckt_only=subckt_only
)
return top_level

import time
if __name__ == "__main__":
comp = current_mirror(sky130)
comp =current_mirror(sky130)
# comp.pprint_ports()
comp = add_cm_labels(comp,sky130)
comp =add_cm_labels(comp,sky130)
comp.name = "CM"
comp.show()
#print(comp.info['netlist'].generate_netlist())
print("...Running DRC...")
drc_result = sky130.drc_magic(comp, "CM")
print(drc_result)
## Klayout DRC
#drc_result = sky130.drc(comp)\n

time.sleep(5)

print("...Running LVS...")
lvs_res=sky130.lvs_netgen(comp, "CM")
lvs_res=sky130.lvs_netgen(comp, "CM", copy_intermediate_files=True, show_scripts=True)
print(lvs_res)
#print("...Saving GDS...")
#comp.write_gds('out_CMirror.gds')
#comp.write_gds('out_current_mirror,.gds')