Source code for bag3_analog.layout.amplifier.diffamp

# SPDX-License-Identifier: Apache-2.0
# Copyright 2019 Blue Cheetah Analog Design Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""This module contains layout generators for differential amplifiers."""

from typing import Any, Dict, Type, Optional, List, Sequence

from pybag.enum import MinLenMode, RoundMode

from bag.typing import TrackType
from bag.util.immutable import Param
from bag.layout.template import TemplateDB
from bag.layout.routing.base import TrackID, WireArray
from bag.layout.enum import DrawTaps
from bag.util.math import HalfInt
from bag.design.module import Module

from xbase.layout.enum import MOSWireType, MOSPortType
from xbase.layout.mos.base import MOSBasePlaceInfo, MOSBase
from xbase.layout.mos.guardring import GuardRing

from bag3_digital.layout.stdcells.gates import InvChainCore

from ...schematic.diffamp_self_biased import bag3_analog__diffamp_self_biased
from ...schematic.diffamp_self_biased_buffer import bag3_analog__diffamp_self_biased_buffer


[docs]class DiffAmpSelfBiased(MOSBase): """The core of the self biased differential amplifier """ def __init__(self, temp_db: TemplateDB, params: Param, **kwargs: Any) -> None: MOSBase.__init__(self, temp_db, params, **kwargs) @classmethod
[docs] def get_schematic_class(cls) -> Optional[Type[Module]]: return bag3_analog__diffamp_self_biased
@classmethod
[docs] def get_params_info(cls) -> Dict[str, str]: return dict( pinfo='The MOSBasePlaceInfo object.', seg_dict='Dictionary of segments', w_p='pmos width.', w_n='nmos width.', ridx_ntail='index for nmos row with tail transistor', ridx_ngm='index for nmos row with gm transistors', ridx_pgm='index for pmos row with gm transistors', ridx_ptail='index for pmos row with head transistors', show_pins='True to show pins', flip_tile='True to flip all tiles', draw_taps='LEFT or RIGHT or BOTH or NONE', sig_locs='Signal locations for top horizontal metal layer pins',
) @classmethod
[docs] def get_default_param_values(cls) -> Dict[str, Any]: return dict( w_p=0, w_n=0, ridx_ntail=0, ridx_ngm=1, ridx_pgm=-2, ridx_ptail=-1, show_pins=False, flip_tile=False, draw_taps='NONE', sig_locs={},
)
[docs] def draw_layout(self) -> None: pinfo = MOSBasePlaceInfo.make_place_info(self.grid, self.params['pinfo']) self.draw_base(pinfo, flip_tile=self.params['flip_tile']) _pinfo = self.get_tile_pinfo(tile_idx=1) seg_dict: Dict[str, int] = self.params['seg_dict'] w_p: int = self.params['w_p'] w_n: int = self.params['w_n'] ridx_ntail: int = self.params['ridx_ntail'] ridx_ngm: int = self.params['ridx_ngm'] ridx_pgm: int = self.params['ridx_pgm'] ridx_ptail: int = self.params['ridx_ptail'] draw_taps: DrawTaps = DrawTaps[self.params['draw_taps']] sig_locs: Dict[str, TrackType] = self.params['sig_locs'] for key, val in seg_dict.items(): if val % 2: raise ValueError(f'This generator does not support odd number of segments ' f'{key} = {val}') hm_layer = self.conn_layer + 1 vm_layer = hm_layer + 1 xm_layer = vm_layer + 1 seg_tailn = seg_dict['tail_n'] seg_gmn = seg_dict['gm_n'] seg_gmp = seg_dict['gm_p'] seg_tailp = seg_dict['tail_p'] # taps sub_sep = self.sub_sep_col sup_info = self.get_supply_column_info(xm_layer) num_taps = 0 tap_offset = 0 tap_left = tap_right = False if draw_taps in DrawTaps.RIGHT | DrawTaps.BOTH: num_taps += 1 tap_right = True if draw_taps in DrawTaps.LEFT | DrawTaps.BOTH: num_taps += 1 tap_offset += sup_info.ncol + sub_sep // 2 tap_left = True # set total number of columns # Total width can be limited by either transistor size or by vertical metal size num, locs = self.tr_manager.place_wires(vm_layer, ['sig_in', 'sig', 'sig', 'sig_in']) sig_vm_w = self.tr_manager.get_width(vm_layer, 'sig_in') vm_coord = self.grid.get_wire_bounds(vm_layer, locs[-1], width=sig_vm_w)[1] vm_col = _pinfo.coord_to_col(vm_coord, RoundMode.GREATER_EQ) vm_col += vm_col & 1 seg_max = max(seg_tailn, 2 * seg_gmn, 2 * seg_gmp, seg_tailp, vm_col) seg_tot = seg_max + (sup_info.ncol + sub_sep // 2) * num_taps self.set_mos_size(seg_tot) seg_tot2 = seg_max // 2 + tap_offset # --- Placement --- # if (seg_tailn // 2) % 2 == 1: sup_term_n, share_term_n = MOSPortType.S, MOSPortType.D g_on_s_n = True else: sup_term_n, share_term_n = MOSPortType.D, MOSPortType.S g_on_s_n = False # nmos tail ntail_inst = self.add_mos(ridx_ntail, seg_tot2 - seg_tailn // 2, seg_tailn, w=w_n, g_on_s=g_on_s_n) _row_info = _pinfo.get_row_place_info(ridx_ntail).row_info w_tailn, th_tailn = _row_info.width, _row_info.threshold # nmos gm cells ngm_left_inst = self.add_mos(ridx_ngm, seg_tot2, seg_gmn, w=w_n, flip_lr=True) ngm_right_inst = self.add_mos(ridx_ngm, seg_tot2, seg_gmn, w=w_n) _row_info = _pinfo.get_row_place_info(ridx_ngm).row_info w_gmn, th_gmn = _row_info.width, _row_info.threshold # pmos gm cells pgm_left_inst = self.add_mos(ridx_pgm, seg_tot2, seg_gmp, w=w_p, flip_lr=True) pgm_right_inst = self.add_mos(ridx_pgm, seg_tot2, seg_gmp, w=w_p) _row_info = _pinfo.get_row_place_info(ridx_pgm).row_info w_gmp, th_gmp = _row_info.width, _row_info.threshold if (seg_tailp // 2) % 2 == 1: sup_term_p, share_term_p = MOSPortType.S, MOSPortType.D g_on_s_p = True else: sup_term_p, share_term_p = MOSPortType.D, MOSPortType.S g_on_s_p = False # pmos tail ptail_inst = self.add_mos(ridx_ptail, seg_tot2 - seg_tailp // 2, seg_tailp, w=w_p, g_on_s=g_on_s_p) _row_info = _pinfo.get_row_place_info(ridx_ptail).row_info w_tailp, th_tailp = _row_info.width, _row_info.threshold # add taps lay_range = range(self.conn_layer, xm_layer + 1) vdd_table: Dict[int, List[WireArray]] = {lay: [] for lay in lay_range} vss_table: Dict[int, List[WireArray]] = {lay: [] for lay in lay_range} if tap_left: self.add_supply_column(sup_info, 0, vdd_table, vss_table) if tap_right: self.add_supply_column(sup_info, seg_tot, vdd_table, vss_table, flip_lr=True) # --- Routing --- # # 1. source terminals of tail transistors go to supplies vdd_tid = self.get_track_id(ridx_ptail, MOSWireType.DS, wire_name='sup') vdd = self.connect_to_tracks(ptail_inst[sup_term_p], vdd_tid) vdd_table[hm_layer].append(vdd) vdd_hm = self.connect_wires(vdd_table[hm_layer]) self.add_pin('VDD_conn', vdd_table[self.conn_layer], hide=True) self.add_pin('VDD_hm', vdd_hm, hide=True) self.add_pin('VDD_vm', vdd_table[vm_layer], hide=True) self.add_pin('VDD', self.connect_wires(vdd_table[xm_layer])) vss_tid = self.get_track_id(ridx_ntail, MOSWireType.DS, wire_name='sup') vss = self.connect_to_tracks(ntail_inst[sup_term_n], vss_tid) vss_table[hm_layer].append(vss) vss_hm = self.connect_wires(vss_table[hm_layer]) self.add_pin('VSS_conn', vss_table[self.conn_layer], hide=True) self.add_pin('VSS_hm', vss_hm, hide=True) self.add_pin('VSS_vm', vss_table[vm_layer], hide=True) self.add_pin('VSS', self.connect_wires(vss_table[xm_layer])) # 2. s terminals of gm transistors connect to drain terminals of tail tail_p_tid = self.get_track_id(ridx_ptail, MOSWireType.DS, wire_name='sig_in') self.connect_to_tracks([pgm_left_inst.s, pgm_right_inst.s, ptail_inst[share_term_p]], tail_p_tid) tail_n_tid = self.get_track_id(ridx_ntail, MOSWireType.DS, wire_name='sig_in') self.connect_to_tracks([ngm_left_inst.s, ngm_right_inst.s, ntail_inst[share_term_n]], tail_n_tid) # 3. Connect d terminals of gm transistors to gate of tail gate_n_tid = self.get_track_id(ridx_ntail, MOSWireType.G, wire_name='sig_in') self.connect_to_tracks(ngm_left_inst.d, gate_n_tid) self.connect_to_tracks(ntail_inst.g, gate_n_tid) gate_p_tid = self.get_track_id(ridx_ptail, MOSWireType.G, wire_name='sig_in') self.connect_to_tracks(pgm_left_inst.d, gate_p_tid) self.connect_to_tracks(ptail_inst.g, gate_p_tid) # 4. get drain connections of gm transistors on horizontal metal clk_hm_w = self.tr_manager.get_width(hm_layer, 'sig') drain_n_idx0 = self.get_track_index(ridx_ngm, MOSWireType.DS, wire_name='sig', wire_idx=0) drain_n_idx1 = self.get_track_index(ridx_ngm, MOSWireType.DS, wire_name='sig', wire_idx=1) drain_left_n, drain_right_n = self.connect_differential_tracks(ngm_left_inst.d, ngm_right_inst.d, hm_layer, drain_n_idx0, drain_n_idx1, width=clk_hm_w) drain_p_idx0 = self.get_track_index(ridx_pgm, MOSWireType.DS, wire_name='sig', wire_idx=0) drain_p_idx1 = self.get_track_index(ridx_pgm, MOSWireType.DS, wire_name='sig', wire_idx=1) drain_left_p, drain_right_p = self.connect_differential_tracks(pgm_left_inst.d, pgm_right_inst.d, hm_layer, drain_p_idx0, drain_p_idx1, width=clk_hm_w) # 5. get gate connections of gm transistors on horizontal metal sig_hm_w = self.tr_manager.get_width(hm_layer, 'sig_in') gate_n_idx0 = self.get_track_index(ridx_ngm, MOSWireType.G, wire_name='sig_in', wire_idx=0) gate_n_idx1 = self.get_track_index(ridx_ngm, MOSWireType.G, wire_name='sig_in', wire_idx=1) gate_left_n, gate_right_n = self.connect_differential_tracks(ngm_left_inst.g, ngm_right_inst.g, hm_layer, gate_n_idx0, gate_n_idx1, width=sig_hm_w) gate_p_idx0 = self.get_track_index(ridx_pgm, MOSWireType.G, wire_name='sig_in', wire_idx=0) gate_p_idx1 = self.get_track_index(ridx_pgm, MOSWireType.G, wire_name='sig_in', wire_idx=1) gate_left_p, gate_right_p = self.connect_differential_tracks(pgm_left_inst.g, pgm_right_inst.g, hm_layer, gate_p_idx0, gate_p_idx1, width=sig_hm_w) # 6. vertical metal connections # a) find available tracks vm_mid = self.grid.coord_to_track(vm_layer, drain_left_n.middle, mode=RoundMode.NEAREST) try: loc_mid = (locs[0] + locs[-1]) / 2 except ValueError: loc_mid = (locs[0] + locs[-1]) // 2 + HalfInt(1) vm_offset = vm_mid - loc_mid inp_idx = locs[0] + vm_offset inn_idx = locs[-1] + vm_offset tail_g_idx = locs[1] + vm_offset out_idx = locs[-2] + vm_offset # b) connect inp, inn = self.connect_differential_tracks([gate_left_p, gate_left_n], [gate_right_p, gate_right_n], vm_layer, inp_idx, inn_idx, width=sig_vm_w) clk_vm_w = self.tr_manager.get_width(vm_layer, 'sig') tail_g, out = self.connect_differential_tracks([drain_left_p, drain_left_n], [drain_right_p, drain_right_n], vm_layer, tail_g_idx, out_idx, width=clk_vm_w) # 7. get all pins on top horizontal metal out_idx = sig_locs.get('out', self.grid.coord_to_track(xm_layer, out.middle, mode=RoundMode.NEAREST)) out = self.connect_to_tracks(out, TrackID(xm_layer, out_idx, width=self.tr_manager.get_width(xm_layer, 'sig')), min_len_mode=MinLenMode.UPPER) inp_idx = sig_locs.get('inp', self.tr_manager.get_next_track(xm_layer, out_idx, 'sig', 'sig_in', up=True)) inn_idx = sig_locs.get('inn', self.tr_manager.get_next_track(xm_layer, out_idx, 'sig', 'sig_in', up=False)) inp, inn = self.connect_differential_tracks(inp, inn, xm_layer, inp_idx, inn_idx, width=self.tr_manager.get_width(xm_layer, 'sig_in')) # pins self.add_pin('v_inp', inp, show=self.show_pins) self.add_pin('v_inn', inn, show=self.show_pins) self.add_pin('v_out', out, show=self.show_pins) # set properties self.sch_params = dict( lch=_pinfo.lch, seg_dict=dict( gm_n=seg_gmn, gm_p=seg_gmp, tail_n=seg_tailn, tail_p=seg_tailp, ), w_dict=dict( gm_n=w_gmn, gm_p=w_gmp, tail_n=w_tailn, tail_p=w_tailp, ), th_dict=dict( gm_n=th_gmn, gm_p=th_gmp, tail_n=th_tailn, tail_p=th_tailp,
), )
[docs]class DiffAmpSelfBiasedBuffer(MOSBase): """self biased differential amplifier followed by buffer """ def __init__(self, temp_db: TemplateDB, params: Param, **kwargs: Any) -> None: MOSBase.__init__(self, temp_db, params, **kwargs) @classmethod
[docs] def get_schematic_class(cls) -> Optional[Type[Module]]: return bag3_analog__diffamp_self_biased_buffer
@classmethod
[docs] def get_params_info(cls) -> Dict[str, str]: return dict( pinfo='The MOSBasePlaceInfo object.', seg_dict='Dictionary of segments for diffamp core', segp_list='List of pmos segments per stage of InvChain', segn_list='List of nmos segments per stage of InvChain', w_p='pmos width.', w_n='nmos width.', ridx_ntail='index for nmos row with tail transistor', ridx_ngm='index for nmos row with gm transistors', ridx_pgm='index for pmos row with gm transistors', ridx_ptail='index for pmos row with head transistors', show_pins='True to show pins', flip_tile='True to flip all tiles', draw_taps='LEFT or RIGHT or BOTH or NONE', sig_locs='Signal locations for top horizontal metal layer pins', export_mid='True to export mid; False by default',
) @classmethod
[docs] def get_default_param_values(cls) -> Dict[str, Any]: return dict( w_p=0, w_n=0, ridx_ntail=0, ridx_ngm=1, ridx_pgm=-2, ridx_ptail=-1, show_pins=False, flip_tile=False, draw_taps='NONE', sig_locs={}, export_mid=False,
)
[docs] def draw_layout(self) -> None: pinfo = MOSBasePlaceInfo.make_place_info(self.grid, self.params['pinfo']) self.draw_base(pinfo) draw_taps: DrawTaps = DrawTaps[self.params['draw_taps']] export_mid: bool = self.params['export_mid'] ridx_ngm: int = self.params['ridx_ngm'] ridx_pgm: int = self.params['ridx_pgm'] # create masters diffamp_params = self.params.copy(remove=['draw_taps'], append=dict(show_pins=False)) diffamp_master = self.new_template(DiffAmpSelfBiased, params=diffamp_params) diffamp_ncol = diffamp_master.num_cols segp_list: Sequence[int] = self.params['segp_list'] segn_list: Sequence[int] = self.params['segn_list'] if len(segp_list) % 2 == 1 or len(segn_list) % 2 == 1: raise ValueError('Buffer should have even length to preserve polarity.') buf_params = self.params.copy(append=dict(is_guarded=True, show_pins=False, ridx_p=ridx_pgm, ridx_n=ridx_ngm, vertical_sup=True)) buf_master = self.new_template(InvChainCore, params=buf_params) buf_ncol = buf_master.num_cols hm_layer = self.conn_layer + 1 vm_layer = hm_layer + 1 xm_layer = vm_layer + 1 sep = max(self.min_sep_col, self.get_hm_sp_le_sep_col()) # taps sub_sep = self.sub_sep_col sup_info = self.get_supply_column_info(xm_layer) num_taps = 0 tap_offset = 0 tap_left = tap_right = False if draw_taps in DrawTaps.RIGHT | DrawTaps.BOTH: num_taps += 1 tap_right = True if draw_taps in DrawTaps.LEFT | DrawTaps.BOTH: num_taps += 1 tap_offset += sup_info.ncol + sub_sep // 2 tap_left = True # set total number of columns seg_tot = diffamp_ncol + sep + buf_ncol + (sup_info.ncol + sub_sep // 2) * num_taps self.set_mos_size(seg_tot) # --- Placement --- # cur_col = tap_offset diffamp_inst = self.add_tile(diffamp_master, 0, cur_col) cur_col += diffamp_ncol + sep buf_inst = self.add_tile(buf_master, 0, cur_col) # add taps lay_range = range(self.conn_layer, xm_layer + 1) vdd_table: Dict[int, List[WireArray]] = {lay: [] for lay in lay_range} vss_table: Dict[int, List[WireArray]] = {lay: [] for lay in lay_range} if tap_left: self.add_supply_column(sup_info, 0, vdd_table, vss_table) if tap_right: self.add_supply_column(sup_info, seg_tot, vdd_table, vss_table, flip_lr=True) # --- Routing --- # # 1. supplies vdd_table[hm_layer].append(diffamp_inst.get_pin('VDD_hm')) self.connect_to_track_wires(buf_inst.get_all_port_pins('VDD'), vdd_table[hm_layer]) self.add_pin('VDD_conn', vdd_table[self.conn_layer], hide=True) self.add_pin('VDD_hm', vdd_table[hm_layer], hide=True) if vdd_table[vm_layer]: self.add_pin('VDD_vm', vdd_table[vm_layer], hide=True) self.add_pin('VDD', self.connect_wires(vdd_table[xm_layer])) vss_table[hm_layer].append(diffamp_inst.get_pin('VSS_hm')) self.connect_to_track_wires(buf_inst.get_all_port_pins('VSS'), vss_table[hm_layer]) self.add_pin('VSS_conn', vss_table[self.conn_layer], hide=True) self.add_pin('VSS_hm', vss_table[hm_layer], hide=True) if vss_table[vm_layer]: self.add_pin('VSS_vm', vss_table[vm_layer], hide=True) self.add_pin('VSS', self.connect_wires(vss_table[xm_layer])) # 2. export inp, inn self.add_pin('v_inp', self.extend_wires(diffamp_inst.get_pin('v_inp'), lower=0)) self.add_pin('v_inn', self.extend_wires(diffamp_inst.get_pin('v_inn'), lower=0)) # 3. connect diffamp output to buffer input mid_vm = diffamp_inst.get_pin('v_out') mid = self.connect_to_track_wires(buf_inst.get_pin('in'), mid_vm) self.add_pin('v_mid', mid, hide=not export_mid) # 4. get final output on top horizontal metal out = self.connect_to_tracks(buf_inst.get_pin('out'), mid_vm.track_id, track_upper=self.bound_box.w) self.add_pin('v_out', out) # set properties self.sch_params = dict( diffamp_params=diffamp_master.sch_params, buf_params=buf_master.sch_params, export_mid=export_mid,
)
[docs]class DiffAmpSelfBiasedBufferGuardRing(GuardRing): def __init__(self, temp_db: TemplateDB, params: Param, **kwargs: Any) -> None: GuardRing.__init__(self, temp_db, params, **kwargs) @classmethod
[docs] def get_params_info(cls) -> Dict[str, str]: ans = DiffAmpSelfBiasedBuffer.get_params_info() ans.update( pmos_gr='pmos guard ring tile name.', nmos_gr='nmos guard ring tile name.', edge_ncol='Number of columns on guard ring edge. Use 0 for default.', ) return ans
@classmethod
[docs] def get_default_param_values(cls) -> Dict[str, Any]: ans = DiffAmpSelfBiasedBuffer.get_default_param_values() ans.update( pmos_gr='pgr', nmos_gr='ngr', edge_ncol=0, ) return ans
[docs] def get_layout_basename(self) -> str: return self.__class__.__name__
[docs] def draw_layout(self) -> None: params = self.params pmos_gr: str = params['pmos_gr'] nmos_gr: str = params['nmos_gr'] edge_ncol: int = params['edge_ncol'] core_params = params.copy(remove=['pmos_gr', 'nmos_gr', 'edge_ncol']) master: DiffAmpSelfBiasedBuffer = self.new_template(DiffAmpSelfBiasedBuffer, params=core_params) hm_layer = master.conn_layer + 1 vm_layer = hm_layer + 1 xm_layer = vm_layer + 1 sub_sep = master.sub_sep_col gr_sub_sep = master.gr_sub_sep_col sep_ncol_left = sep_ncol_right = sub_sep draw_taps: DrawTaps = DrawTaps[params['draw_taps']] if draw_taps in DrawTaps.RIGHT | DrawTaps.BOTH: sep_ncol_right = gr_sub_sep - sub_sep // 2 if draw_taps in DrawTaps.LEFT | DrawTaps.BOTH: sep_ncol_left = gr_sub_sep - sub_sep // 2 sep_ncol = (sep_ncol_left, sep_ncol_right) inst, sup_list = self.draw_guard_ring(master, pmos_gr, nmos_gr, sep_ncol, edge_ncol) vdd_hm_list, vss_hm_list = [], [] for (vss_list, vdd_list) in sup_list: vss_hm_list.extend(vss_list) vdd_hm_list.extend(vdd_list) if inst.has_port('VSS_vm'): self.connect_to_track_wires(vss_hm_list, inst.get_all_port_pins('VSS_vm')) self.connect_to_track_wires(vdd_hm_list, inst.get_all_port_pins('VDD_vm')) else: # get supplies on vm_layer vm_l = self.grid.coord_to_track(vm_layer, self.bound_box.xl, RoundMode.GREATER_EQ) _, vm_locs0 = self.tr_manager.place_wires(vm_layer, ['sup', 'sup'], vm_l, 0) vm_r = self.grid.coord_to_track(vm_layer, self.bound_box.xh, RoundMode.LESS_EQ) _, vm_locs1 = self.tr_manager.place_wires(vm_layer, ['sup', 'sup'], vm_r, -1) w_sup_vm = self.tr_manager.get_width(vm_layer, 'sup') vss_vm = self.connect_to_tracks(vss_hm_list + inst.get_all_port_pins('VSS_hm'), TrackID(vm_layer, vm_locs0[0], w_sup_vm, 2, vm_locs1[-1] - vm_locs0[0])) vdd_vm = self.connect_to_tracks(vdd_hm_list + inst.get_all_port_pins('VDD_hm'), TrackID(vm_layer, vm_locs0[-1], w_sup_vm, 2, vm_locs1[0] - vm_locs0[-1])) # get supplies on xm_layer w_sup_xm = self.tr_manager.get_width(xm_layer, 'sup') xm_b = self.grid.coord_to_track(xm_layer, self.bound_box.yl, RoundMode.GREATER_EQ) vss_xm = self.connect_to_tracks(vss_vm, TrackID(xm_layer, xm_b, w_sup_xm)) self.add_pin('VSS', vss_xm) xm_t = self.grid.coord_to_track(xm_layer, self.bound_box.yh, RoundMode.LESS_EQ) vdd_xm = self.connect_to_tracks(vdd_vm, TrackID(xm_layer, xm_t, w_sup_xm)) self.add_pin('VDD', vdd_xm)