Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flipped VOltage Follower Pcell, Analog vibes, Chipathon 2024 #352

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
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
@@ -0,0 +1,53 @@
# FVF based super class AB OTA
This topology of class AB OTA uses flipped voltage followers as voltage shifters to boost gain and slew rate. It can provide **slew performance independent of bias current**. LCMFB is also used to boost the slew rate even more.
## Pcells used
![WhatsApp Image 2024-12-01 at 22 18 40](https://github.com/user-attachments/assets/99d3a1b1-7842-42dc-9033-9e7f452b4a54)
![otagds](https://github.com/user-attachments/assets/4da02a37-eacb-4d2e-9e33-c0ddd4d95a79)
### Flipped Voltage Follower
Used as voltage shifters. Also used to crete a low voltage current mirror for biasing. Pcell can be found under ``` glayout/flow/blocks/elementary/FVF/ ```
### Transmission gate
Due to unavailability of resistors in Glayout, trasmission gates were used as LCMFB resistors. However, this limits ther slew performance. Pcell can be found here ``` glayout/flow/blocks/elementary/trasmission_gate/ ```
### Low Voltage Current Mirror
Low voltage current mirror are used to set a **bias current of 10uA**. The python code can be found in this directory itself.
### Others
Some already existing pcells were used, like current mirrors and four_transistor_interdigitized block.
## Parameterization
```
def super_class_AB_OTA(
pdk: MappedPDK,
input_pair_params: tuple[float,float]=(4,2),
fvf_shunt_params: tuple[float,float]=(2.75,1),
local_current_bias_params: tuple[float,float]=(3.76,3.0),
diff_pair_load_params: tuple[float,float]=(9,1),
ratio: int=1,
current_mirror_params: tuple[float,float]=(2.25,1),
resistor_params: tuple[float,float,float,float]=(0.5,3,4,4),
global_current_bias_params: tuple[float,float,float]=(8.3,1.42,2)
) -> Component:
"""
creates a super class AB OTA using flipped voltage follower at biasing stage and local common mode feedback to give dynamic current and gain boost much less dependent on biasing current
input_pair_params: differential input pair(N-type) - (width,length), input nmoses of the fvf get the same dimensions
fvf_shunt_params: feedback fet of fvf - (width,length)
local_current_bias_params: local currrent mirror which directly biases each fvf - (width,length)
diff_pair_load_params: creates a p_block consisting of both input stage pmos loads and output stage pmoses - (width,length)
ratio: current mirroring ratio from input stage to output stage, set to 1 by default.
current_mirror_params: output stage N-type currrent mirrors - (width, length)
resistor_params: passgates are used as resistors for LCMFB - (width of nmos, width of pmos,length of nmos, length of pmos)
global_current_bias_params: A low voltage current mirror for biasing - consists of 5 nmoses of (W/L) and one nmos of (W'/L) - (W,W',L)
"""
```
## Layout generation, PEX and post-layout simulations
### sky130_ota_tapeout.py
This file is used to generate layout genration, PEX and post-layout simulations. [sky130_nist_tapeout.py](https://github.com/idea-fasoc/OpenFASOC/blob/main/openfasoc/generators/glayout/tapeout/tapeout_and_RL/sky130_nist_tapeout.py) was taken as a reference.
Run this command to see various modes in which it can be run
``` python3 sky130_ota_tapeout_py --h ```
#### gen_ota mode
This generates a complete layout, **with LVT layers and labels added by default**. Custom parameters can be given. Run ``` python3 sky130_ota_tapeout.py gen_ota --h ``` to see the options.

**N.B-**
_1. The default widths and lengths assume that the lvt layer is added. Different widths and lengths must be given to get an OTA with desired performance if no lvt layer is added.

2. There is an option to add pads. For now this is set as False by default._
#### test mode
This mode creates a complete layout with default parameter values, performs PEX (add --noparasitics to do just LVS but PEX is encouraged for better results) and then does some transient, ac, power and noise analysis to get results. The spice testbench ``` ota_perf_eval.sp ``` can be found in this directory itself.
Run python3 ``` sky130_ota_tapeout.py test --output_dir test ```. This puts the simulation results inside test directory.
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/bin/bash

# Actual
export PDK_ROOT=@@PDK_ROOT

# args:
# first arg = gds file to read
# second arg = name of top cell in gds file to read
# third arg (optional) = noparasitics (basically an LVS extraction)

paropt="@@@PAROPT"

if [ "$paropt" = "noparasitics" ]; then

magic -rcfile ./sky130A/sky130A.magicrc -noconsole -dnull << EOF
gds read $1
flatten $2
load $2
select top cell
extract do local
extract all
ext2sim labels on
ext2sim
ext2spice lvs
ext2spice cthresh 0
ext2spice -o $2_pex.spice
exit
EOF

else

magic -rcfile ./sky130A/sky130A.magicrc -noconsole -dnull << EOF
gds read $1
flatten $2
load $2
select top cell
extract do local
extract all
ext2sim labels on
ext2sim
extresist tolerance 10
extresist
ext2spice lvs
ext2spice cthresh 0
ext2spice extresist on
ext2spice -o $2_pex.spice
exit
EOF

fi

rm -f $2.nodes
rm -f $2.ext
rm -f $2.res.ext
rm -f $2.sim

Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
from glayout.flow.pdk.mappedpdk import MappedPDK
from glayout.flow.pdk.sky130_mapped import sky130_mapped_pdk
from gdsfactory.component import Component
from gdsfactory.component_reference import ComponentReference
from gdsfactory.cell import cell
from gdsfactory import Component
from gdsfactory.components import text_freetype, rectangle
from glayout.flow.primitives.fet import nmos, pmos, multiplier
from glayout.flow.pdk.util.comp_utils import evaluate_bbox, prec_center, align_comp_to_port, prec_ref_center
from glayout.flow.pdk.util.snap_to_grid import component_snap_to_grid
from glayout.flow.pdk.util.port_utils import rename_ports_by_orientation
from glayout.flow.routing.straight_route import straight_route
from glayout.flow.routing.c_route import c_route
from glayout.flow.routing.L_route import L_route
from glayout.flow.primitives.guardring import tapring
from glayout.flow.pdk.util.port_utils import add_ports_perimeter
from glayout.flow.spice.netlist import Netlist
from glayout.flow.blocks.elementary.FVF.fvf import fvf_netlist, flipped_voltage_follower
from glayout.flow.primitives.via_gen import via_stack
from typing import Optional


def low_voltage_cmirr_netlist(bias_fvf: Component, cascode_fvf: Component, fet_1_ref: ComponentReference, fet_2_ref: ComponentReference, fet_3_ref: ComponentReference, fet_4_ref: ComponentReference) -> Netlist:

netlist = Netlist(circuit_name='Low_voltage_current_mirror', nodes=['IBIAS1', 'IBIAS2', 'GND', 'IOUT1', 'IOUT2'])
netlist.connect_netlist(bias_fvf.info['netlist'], [('VIN','IBIAS1'),('VBULK','GND'),('Ib','IBIAS1')])
netlist.connect_netlist(cascode_fvf.info['netlist'], [('VIN','IBIAS1'),('VBULK','GND'),('Ib', 'IBIAS2')])
fet_1A_ref=netlist.connect_netlist(fet_2_ref.info['netlist'], [('D', 'IOUT1'),('G','IBIAS1'),('B','GND')])
fet_2A_ref=netlist.connect_netlist(fet_4_ref.info['netlist'], [('D', 'IOUT2'),('G','IBIAS1'),('B','GND')])
fet_1B_ref=netlist.connect_netlist(fet_1_ref.info['netlist'], [('G','IBIAS2'),('S', 'GND'),('B','GND')])
fet_2B_ref=netlist.connect_netlist(fet_3_ref.info['netlist'], [('G','IBIAS2'),('S', 'GND'),('B','GND')])
netlist.connect_subnets(
fet_1A_ref,
fet_1B_ref,
[('S', 'D')]
)
netlist.connect_subnets(
fet_2A_ref,
fet_2B_ref,
[('S', 'D')]
)

return netlist

@cell
def low_voltage_cmirror(
pdk: MappedPDK,
width: tuple[float,float] = (4.15,1.42),
length: float = 2,
fingers: tuple[int,int] = (2,1),
multipliers: tuple[int,int] = (1,1),
) -> Component:
"""
A low voltage N type current mirror
"""
#top level component
top_level = Component("Low_voltage_N-type_current_mirror")

#input branch 2
cascode_fvf = flipped_voltage_follower(pdk, width=(width[0],width[0]), length=(length,length), fingers=(fingers[0],fingers[0]), multipliers=(multipliers[0],multipliers[0]), with_dnwell=False)
cascode_fvf_ref = prec_ref_center(cascode_fvf)
top_level.add(cascode_fvf_ref)

#input branch 1
bias_fvf = flipped_voltage_follower(pdk, width=(width[0],width[1]), length=(length,length), fingers=(fingers[0],fingers[1]), multipliers=(multipliers[0],multipliers[1]), placement="vertical", with_dnwell=False)
bias_fvf_ref = prec_ref_center(bias_fvf)
bias_fvf_ref.movey(cascode_fvf_ref.ymin - 2 - (evaluate_bbox(bias_fvf)[1]/2))
top_level.add(bias_fvf_ref)

#creating fets for output branches
fet_1 = nmos(pdk, width=width[0], fingers=fingers[0], multipliers=multipliers[0], with_dummy=True, with_dnwell=False, with_substrate_tap=False, length=length)
fet_1_ref = prec_ref_center(fet_1)
fet_2_ref = prec_ref_center(fet_1)
fet_3_ref = prec_ref_center(fet_1)
fet_4_ref = prec_ref_center(fet_1)

fet_1_ref.movex(cascode_fvf_ref.xmin - (evaluate_bbox(fet_1)[0]/2) - pdk.util_max_metal_seperation())
fet_2_ref.movex(cascode_fvf_ref.xmin - (3*evaluate_bbox(fet_1)[0]/2) - 2*pdk.util_max_metal_seperation())
fet_3_ref.movex(cascode_fvf_ref.xmax + (evaluate_bbox(fet_1)[0]/2) + pdk.util_max_metal_seperation())
fet_4_ref.movex(cascode_fvf_ref.xmax + (3*evaluate_bbox(fet_1)[0]/2) + 2*pdk.util_max_metal_seperation())

top_level.add(fet_1_ref)
top_level.add(fet_2_ref)
top_level.add(fet_3_ref)
top_level.add(fet_4_ref)

top_level << c_route(pdk, bias_fvf_ref.ports["A_multiplier_0_gate_E"], bias_fvf_ref.ports["B_gate_bottom_met_E"])
top_level << c_route(pdk, cascode_fvf_ref.ports["A_multiplier_0_gate_W"], bias_fvf_ref.ports["A_multiplier_0_gate_W"])
top_level << straight_route(pdk, cascode_fvf_ref.ports["B_gate_bottom_met_E"], fet_3_ref.ports["multiplier_0_gate_W"])

#creating vias for routing
viam2m3 = via_stack(pdk, "met2", "met3", centered=True)
gate_1_via = top_level << viam2m3
gate_1_via.move(fet_1_ref.ports["multiplier_0_gate_W"].center).movex(-1)
gate_2_via = top_level << viam2m3
gate_2_via.move(fet_2_ref.ports["multiplier_0_gate_W"].center).movex(-1)
gate_3_via = top_level << viam2m3
gate_3_via.move(fet_3_ref.ports["multiplier_0_gate_E"].center).movex(1)
gate_4_via = top_level << viam2m3
gate_4_via.move(fet_4_ref.ports["multiplier_0_gate_E"].center).movex(1)

source_2_via = top_level << viam2m3
drain_1_via = top_level << viam2m3
source_2_via.move(fet_2_ref.ports["multiplier_0_source_E"].center).movex(1.5)
drain_1_via.move(fet_1_ref.ports["multiplier_0_drain_W"].center).movex(-1)

source_4_via = top_level << viam2m3
drain_3_via = top_level << viam2m3
source_4_via.move(fet_4_ref.ports["multiplier_0_source_W"].center).movex(-1)
drain_3_via.move(fet_3_ref.ports["multiplier_0_drain_E"].center).movex(1.5)

#routing
top_level << straight_route(pdk, fet_2_ref.ports["multiplier_0_source_E"], source_2_via.ports["bottom_met_W"])
top_level << straight_route(pdk, fet_1_ref.ports["multiplier_0_drain_W"], drain_1_via.ports["bottom_met_E"])
top_level << straight_route(pdk, fet_4_ref.ports["multiplier_0_source_W"], source_4_via.ports["bottom_met_E"])
top_level << straight_route(pdk, fet_3_ref.ports["multiplier_0_drain_E"], drain_3_via.ports["bottom_met_W"])
top_level << c_route(pdk, source_2_via.ports["top_met_N"], drain_1_via.ports["top_met_N"], extension=0.5*evaluate_bbox(fet_1)[1], width1=0.32, width2=0.32, cwidth=0.32, e1glayer="met3", e2glayer="met3", cglayer="met2")
top_level << c_route(pdk, source_4_via.ports["top_met_N"], drain_3_via.ports["top_met_N"], extension=0.5*evaluate_bbox(fet_1)[1], width1=0.32, width2=0.32, cwidth=0.32, e1glayer="met3", e2glayer="met3", cglayer="met2")
top_level << c_route(pdk, bias_fvf_ref.ports["A_multiplier_0_gate_E"], gate_4_via.ports["bottom_met_E"], width1=0.32, width2=0.32, cwidth=0.32)


top_level << straight_route(pdk, fet_1_ref.ports["multiplier_0_gate_W"], gate_1_via.ports["bottom_met_E"])
top_level << straight_route(pdk, fet_2_ref.ports["multiplier_0_gate_W"], gate_2_via.ports["bottom_met_E"])
top_level << straight_route(pdk, fet_3_ref.ports["multiplier_0_gate_E"], gate_3_via.ports["bottom_met_W"])
top_level << straight_route(pdk, fet_4_ref.ports["multiplier_0_gate_E"], gate_4_via.ports["bottom_met_W"])

top_level << c_route(pdk, gate_1_via.ports["top_met_S"], gate_3_via.ports["top_met_S"], extension=(1.2*width[0]+0.6), cglayer='met2')
top_level << c_route(pdk, gate_2_via.ports["top_met_S"], gate_4_via.ports["top_met_S"], extension=(1.2*width[0]-0.6), cglayer='met2')

top_level << straight_route(pdk, fet_1_ref.ports["multiplier_0_source_W"], fet_1_ref.ports["tie_W_top_met_W"], glayer1='met1', width=0.2)
top_level << straight_route(pdk, fet_3_ref.ports["multiplier_0_source_W"], fet_3_ref.ports["tie_W_top_met_W"], glayer1='met1', width=0.2)


top_level.add_ports(bias_fvf_ref.get_ports_list(), prefix="M_1_")
top_level.add_ports(cascode_fvf_ref.get_ports_list(), prefix="M_2_")
top_level.add_ports(fet_1_ref.get_ports_list(), prefix="M_3_B_")
top_level.add_ports(fet_2_ref.get_ports_list(), prefix="M_3_A_")
top_level.add_ports(fet_3_ref.get_ports_list(), prefix="M_4_B_")
top_level.add_ports(fet_4_ref.get_ports_list(), prefix="M_4_A_")

component = component_snap_to_grid(rename_ports_by_orientation(top_level))
component.info['netlist'] = low_voltage_cmirr_netlist(bias_fvf, cascode_fvf, fet_1_ref, fet_2_ref, fet_3_ref, fet_4_ref)

return component
Loading