Skip to content

Commit

Permalink
Merge pull request #359 from Subhampal9/OTA
Browse files Browse the repository at this point in the history
Files for FVF based OTA, Analog Vibes, Chipathon
  • Loading branch information
msaligane authored Feb 19, 2025
2 parents e7f4611 + 7aa2328 commit 595bb87
Show file tree
Hide file tree
Showing 12 changed files with 2,107 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# 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 the 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 for layout generation, 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.

#### drc report
```
using provided pdk_root
Defaulting to stale magic_commands.tcl
Magic 8.3 revision 486 - Compiled on Sat Jul 13 11:42:22 AM CEST 2024.
Starting magic under Tcl interpreter
Using the terminal as the console.
Using NULL graphics device.
Processing system .magicrc file
Sourcing design .magicrc for technology sky130A ...
2 Magic internal units = 1 Lambda
Input style sky130(): scaleFactor=2, multiplier=2
The following types are not handled by extraction and will be treated as non-electrical types:
ubm
Scaled tech values by 2 / 1 to match internal grid scaling
Loading sky130A Device Generator Menu ...
Loading "/tmp/tmpf6p8n7lv/magic_commands.tcl" from command line.
Warning: Calma reading is not undoable! I hope that's OK.
Library written using GDS-II Release 6.0
Library name: library
Reading "super_class_AB_OTA_01aaa4c6".
Reading "rectangle_82c0dd1f".
Reading "compass_82c0dd1f".
Reading "rectangle_82c0dd1f".
Reading "compass_82c0dd1f".
[INFO]: Loading Super_class_AB_OTA
Creating new cell
Loading DRC CIF style.
No errors found.
[INFO]: DONE with /tmp/tmpf6p8n7lv/Super_class_AB_OTA.rpt
```


**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 ../../../../../tapeout/tapeout_and_RL/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 ../../../../../tapeout/tapeout_and_RL/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,145 @@
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. It has two input brnaches and two output branches. It consists of total 8 nfets, 7 of them have the same W/L. One nfet has width of w' = w/3(theoretcially)
The default values are used to mirror 10uA.
"""
#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

0 comments on commit 595bb87

Please sign in to comment.