Source code for bag3_digital.layout.stdcells.levelshifter

# 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.

from typing import Any, Dict, List, Mapping, Optional, Union, Type, cast

from pybag.enum import RoundMode, MinLenMode

from bag.util.math import HalfInt
from bag.util.immutable import Param
from bag.design.database import ModuleDB
from bag.design.module import Module
from bag.layout.template import TemplateDB, PyLayInstance
from bag.layout.routing.base import TrackID

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

from bag3_digital.layout.stdcells.gates import InvChainCore


[docs]class LevelShifterCore(MOSBase): """Core of level shifter, with differential inputs and no output buffers. """ def __init__(self, temp_db: TemplateDB, params: Param, **kwargs: Any) -> None: MOSBase.__init__(self, temp_db, params, **kwargs) self._center_col: int = -1 @property
[docs] def center_col(self) -> int: """int: The centerline column index.""" return self._center_col
@property
[docs] def out_vertical(self) -> bool: """bool: True if outputs are on vm_layer.""" return self.params['has_rst'] or self.params['is_guarded']
@classmethod
[docs] def get_schematic_class(cls) -> Optional[Type[Module]]: # noinspection PyTypeChecker return ModuleDB.get_schematic_class('bag3_digital', 'lvshift_core')
@classmethod
[docs] def get_params_info(cls) -> Dict[str, str]: return dict( pinfo='The MOSBasePlaceInfo object.', seg_dict='dictionary of number of segments.', w_dict='dictionary of number of fins.', ridx_p='pmos row index.', ridx_n='nmos row index.', vertical_rst='List of reset pins to draw on vm layer.', is_guarded='True if it there should be guard ring around the cell', in_upper='True to make the input connected to the upper transistor in the stack', inp_on_right='True to connect inp pin on right.', has_rst='True to enable reset pins.', stack_p='PMOS number of stacks.', sig_locs='Optional dictionary of user defined signal locations', rst_b_wire_idx='Optional parameter to move the horizontal wire for reset connections',
) @classmethod
[docs] def get_default_param_values(cls) -> Dict[str, Any]: return dict( w_dict={}, ridx_p=-1, ridx_n=0, vertical_rst=[], is_guarded=False, in_upper=False, inp_on_right=False, has_rst=True, stack_p=1, sig_locs={}, rst_b_wire_idx=2,
)
[docs] def draw_layout(self): params = self.params pinfo = MOSBasePlaceInfo.make_place_info(self.grid, params['pinfo']) self.draw_base(pinfo) tr_manager = self.tr_manager seg_dict: Mapping[str, int] = params['seg_dict'] w_dict: Mapping[str, int] = params['w_dict'] ridx_p: int = params['ridx_p'] ridx_n: int = params['ridx_n'] vertical_rst: List[str] = params['vertical_rst'] is_guarded: bool = params['is_guarded'] in_upper: bool = params['in_upper'] inp_on_right: bool = params['inp_on_right'] has_rst: bool = params['has_rst'] stack_p: int = params['stack_p'] sig_locs: Mapping[str, Union[float, HalfInt]] = params['sig_locs'] rst_b_wire_idx: int = params['rst_b_wire_idx'] # breakpoint() if stack_p != 1 and stack_p != 2: raise ValueError('Only support stack_p = 1 or stack_p = 2') if stack_p == 2: if not has_rst: raise ValueError('stack_p = 2 only allowed if has_rst = True') if not in_upper: raise ValueError('stack_p = 2 only allowed if in_upper = True') hm_layer = self.conn_layer + 1 vm_layer = hm_layer + 1 sig_w_hm = tr_manager.get_width(hm_layer, 'sig') seg_pu = seg_dict['pu'] seg_pd = seg_dict['pd'] seg_rst = seg_dict.get('rst', 0) seg_prst = seg_dict.get('prst', 0) default_wp = self.get_row_info(ridx_p).width default_wn = self.get_row_info(ridx_n).width w_pd = w_dict.get('pd', default_wn) w_pu = w_dict.get('pu', default_wp) sch_w_dict = dict(pd=w_pd, pu=w_pu) vss_tid = self.get_track_id(ridx_n, MOSWireType.DS, wire_name='sup') vdd_tid = self.get_track_id(ridx_p, MOSWireType.DS, wire_name='sup') pg_midl_tidx = self.get_track_index(ridx_p, MOSWireType.G, 'sig', wire_idx=-2) pg_midr_tidx = self.get_track_index(ridx_p, MOSWireType.G, 'sig', wire_idx=-1) nd_mid_tidx = self.get_track_index(ridx_n, MOSWireType.DS, 'sig', wire_idx=-1) inn_tidx = self.get_track_index(ridx_n, MOSWireType.G, 'sig', wire_idx=0) inp_tidx = self.get_track_index(ridx_n, MOSWireType.G, 'sig', wire_idx=1) inn_tidx = sig_locs.get('inb', inn_tidx) inp_tidx = sig_locs.get('in', inp_tidx) inn_tid = TrackID(hm_layer, inn_tidx, width=sig_w_hm) inp_tid = TrackID(hm_layer, inp_tidx, width=sig_w_hm) if in_upper: rst_tid = inn_tid else: rst_tid = None nd_mid_tid = TrackID(hm_layer, nd_mid_tidx) # floorplanning number of columns min_sep_odd = self.min_sep_col min_sep_even = min_sep_odd + (min_sep_odd & 1) mid_sep = min_sep_even mid_sep2 = mid_sep // 2 pmos_fg = seg_pu * stack_p pmos_col = pmos_fg + (pmos_fg & 1) if has_rst: rst_sep = min_sep_odd nmos_fg = 2 * seg_pd nmos_col = seg_rst + rst_sep + nmos_fg if nmos_col & 1: rst_sep += 1 nmos_col += 1 if stack_p == 2: if pmos_fg > nmos_fg: raise ValueError('pmos reset placement code will break in this case') num_core_col = max(nmos_col, pmos_col) else: rst_sep = 0 nmos_fg = seg_pd num_core_col = max(seg_pd + (seg_pd & 1), pmos_col) self._center_col = col_mid = num_core_col + mid_sep2 seg_tot = 2 * col_mid self.set_mos_size(num_cols=seg_tot) # --- Placement --- # # rst export_mid = sep_g_pmos = (stack_p != 1) load_l = self.add_mos(ridx_p, col_mid - mid_sep2, seg_pu, g_on_s=True, w=w_pu, stack=stack_p, sep_g=sep_g_pmos, export_mid=export_mid, flip_lr=True) load_r = self.add_mos(ridx_p, col_mid + mid_sep2, seg_pu, g_on_s=True, w=w_pu, stack=stack_p, sep_g=sep_g_pmos, export_mid=export_mid) vdd_list = [load_l.s, load_r.s] if has_rst: if seg_rst == 0: raise ValueError('seg_rst cannot be 0') w_rst = w_dict.get('rst', default_wn) sch_w_dict['rst'] = w_rst rst_delta = mid_sep2 + nmos_fg + rst_sep + seg_rst rst_l = self.add_mos(ridx_n, col_mid - rst_delta, seg_rst, w=w_rst) rst_r = self.add_mos(ridx_n, col_mid + rst_delta, seg_rst, w=w_rst, flip_lr=True) in_l = self.add_mos(ridx_n, col_mid - mid_sep2, seg_pd, g_on_s=True, w=w_pd, stack=2, sep_g=True, flip_lr=True) in_r = self.add_mos(ridx_n, col_mid + mid_sep2, seg_pd, g_on_s=True, w=w_pd, stack=2, sep_g=True) vss_list = [in_l.s, in_r.s, rst_r.s, rst_l.s] nd_l = [rst_l.d, in_l.d] nd_r = [rst_r.d, in_r.d] if stack_p == 2: if seg_prst == 0: raise ValueError('seg_prst cannot be 0') prst_sep = min_sep_odd + ((min_sep_odd & 1) ^ ((seg_prst & 1) == 0)) prst_delta = mid_sep2 + pmos_fg + prst_sep + seg_prst prst_l = self.add_mos(ridx_p, col_mid - prst_delta, seg_prst, w=w_rst) prst_r = self.add_mos(ridx_p, col_mid + prst_delta, seg_prst, w=w_rst, flip_lr=True) else: prst_l = prst_r = None else: prst_l = prst_r = rst_l = rst_r = None in_l = self.add_mos(ridx_n, col_mid - mid_sep2, seg_pd, g_on_s=True, w=w_pd, flip_lr=True) in_r = self.add_mos(ridx_n, col_mid + mid_sep2, seg_pd, g_on_s=True, w=w_pd) vss_list = [in_l.s, in_r.s] nd_l = [in_l.d] nd_r = [in_r.d] # --- Routing --- # # vdd/vss vdd = self.connect_to_tracks(vdd_list, vdd_tid) vss = self.connect_to_tracks(vss_list, vss_tid) self.add_pin('VDD', vdd) self.add_pin('VSS', vss) if has_rst or is_guarded: # use vm_layer to connect nmos and pmos drains left_coord = self.grid.track_to_coord(self.conn_layer, in_l.s[0].track_id.base_index) right_coord = self.grid.track_to_coord(self.conn_layer, in_r.s[0].track_id.base_index) dleft_tidx = self.grid.coord_to_track(vm_layer, left_coord, RoundMode.NEAREST) dright_tidx = self.grid.coord_to_track(vm_layer, right_coord, RoundMode.NEAREST) dleft_tidx = tr_manager.get_next_track(vm_layer, dleft_tidx, 'sig', 'sig', up=False) dright_tidx = tr_manager.get_next_track(vm_layer, dright_tidx, 'sig', 'sig', up=True) # connect nmos drains together nd_midl = self.connect_to_tracks(nd_l, nd_mid_tid) nd_midr = self.connect_to_tracks(nd_r, nd_mid_tid) # pmos cross coupling connection if stack_p == 1: pg_midl, pg_midr = self.connect_differential_tracks(load_l.d, load_r.d, hm_layer, pg_midl_tidx, pg_midr_tidx) pg_midl, pg_midr = self.connect_differential_wires(load_r.g, load_l.g, pg_midl, pg_midr) hm_midl_list = [nd_midl, pg_midl] hm_midr_list = [nd_midr, pg_midr] else: pm_tid = self.get_track_id(ridx_p, MOSWireType.DS, wire_name='sig', wire_idx=0) pd_tid = self.get_track_id(ridx_p, MOSWireType.DS, wire_name='sig', wire_idx=1) pd_midl = self.connect_to_tracks([prst_l.d, load_l.d], pd_tid) pd_midr = self.connect_to_tracks([prst_r.d, load_r.d], pd_tid) self.connect_to_tracks([prst_l.s, load_l.m], pm_tid) self.connect_to_tracks([prst_r.s, load_r.m], pm_tid) self.connect_wires([load_l.g[1::2], in_l.g[1::2]]) self.connect_wires([load_r.g[1::2], in_r.g[1::2]]) pg_midl, pg_midr = self.connect_differential_tracks( load_r.g[0::2], load_l.g[0::2], hm_layer, pg_midl_tidx, pg_midr_tidx, track_lower=pd_midl.lower, track_upper=pd_midr.upper) hm_midl_list = [nd_midl, pd_midl, pg_midl] hm_midr_list = [nd_midr, pd_midr, pg_midr] vm_w = tr_manager.get_width(vm_layer, 'sig') midl_vm_tid = TrackID(vm_layer, dleft_tidx, width=vm_w) midr_vm_tid = TrackID(vm_layer, dright_tidx, width=vm_w) midl = self.connect_to_tracks(hm_midl_list, midl_vm_tid) midr = self.connect_to_tracks(hm_midr_list, midr_vm_tid) midl, midr = self.extend_wires([midl, midr], lower=min(midl.lower, midr.lower), upper=max(midl.upper, midr.upper)) if has_rst: # reset connections if in_upper: inl = in_l.g1 inr = in_r.g1 rst_midl_b = in_l.g0 rst_midr_b = in_r.g0 else: inl = in_l.g0 inr = in_r.g0 rst_midl_b = in_l.g1 rst_midr_b = in_r.g1 # connect rst gates rst_b_tidx = self.get_track_index(ridx_n, MOSWireType.G, 'sig', wire_idx=rst_b_wire_idx) rst_b_tidx = sig_locs.get('rst_casc', rst_b_tidx) rst_b_tid = TrackID(hm_layer, rst_b_tidx, width=sig_w_hm) if rst_tid is None: rst_tid = rst_b_tid # breakpoint() rst_b_wires = [rst_midl_b, rst_midr_b] if prst_l is not None: rst_b_wires.append(prst_l.g) rst_b_wires.append(prst_r.g) rst_b_warr = self.connect_to_tracks(rst_b_wires, rst_b_tid) # rst_tid has some value now, convert that to rst_outn_tid / rst_outp_tid based # on sig_locs rst_outn_tidx = sig_locs.get('rst_outb', None) if rst_outn_tidx: rst_outn_tid = TrackID(hm_layer, rst_outn_tidx, width=sig_w_hm) else: rst_outn_tid = rst_tid rst_outp_tidx = sig_locs.get('rst_out', None) if rst_outp_tidx: rst_outp_tid = TrackID(hm_layer, rst_outp_tidx, width=sig_w_hm) else: rst_outp_tid = rst_tid rst_outp_gwarrs = rst_l.g if inp_on_right else rst_r.g rst_outn_gwarrs = rst_r.g if inp_on_right else rst_l.g rst_outp_warr = self.connect_to_tracks(rst_outp_gwarrs, rst_outp_tid) rst_outn_warr = self.connect_to_tracks(rst_outn_gwarrs, rst_outn_tid) rst_list = [('rst_outn', rst_outn_warr), ('rst_outp', rst_outp_warr), ('rst_casc', rst_b_warr)] for name, wire in rst_list: if name in vertical_rst: if name == 'rst_casc': vm_tid = self.grid.coord_to_track(vm_layer, wire.middle, RoundMode.NEAREST) elif (name == 'rst_outn') ^ inp_on_right: vm_tid = self.grid.coord_to_track(vm_layer, wire.upper, RoundMode.GREATER_EQ) else: vm_tid = self.grid.coord_to_track(vm_layer, wire.lower, RoundMode.LESS_EQ) wire = self.connect_to_tracks(wire, TrackID(vm_layer, vm_tid), min_len_mode=MinLenMode.MIDDLE) self.add_pin(name, wire) else: inl = in_l.g inr = in_r.g else: # use conn_layer to connect nmos and pmos drains pg_midl, pg_midr = self.connect_differential_tracks(nd_l, nd_r, hm_layer, pg_midl_tidx, pg_midr_tidx) pg_midl, pg_midr = self.connect_differential_wires(load_l.d, load_r.d, pg_midl, pg_midr) pg_midl, pg_midr = self.connect_differential_wires(load_r.g, load_l.g, pg_midl, pg_midr) midl = pg_midl midr = pg_midr inl = in_l.g inr = in_r.g # connect and export input and output pins self.add_pin('poutr', pg_midr, hide=True) self.add_pin('poutl', pg_midl, hide=True) if inp_on_right: inp, inn = self.connect_differential_tracks(inr, inl, inp_tid.layer_id, inp_tid.base_index, inn_tid.base_index, width=inp_tid.width) self.add_pin('outn', midr) self.add_pin('outp', midl) else: inp, inn = self.connect_differential_tracks(inl, inr, inp_tid.layer_id, inp_tid.base_index, inn_tid.base_index, width=inp_tid.width) self.add_pin('outp', midr) self.add_pin('outn', midl) self.add_pin('inp', inp) self.add_pin('inn', inn) # compute schematic parameters default_thp = self.get_row_info(ridx_p).threshold default_thn = self.get_row_info(ridx_n).threshold self.sch_params = dict( lch=self.place_info.lch, seg_dict=seg_dict, w_dict=sch_w_dict, intent_dict=dict( nch=default_thn, pch=default_thp, ), in_upper=in_upper, has_rst=has_rst, stack_p=stack_p,
)
[docs]class LevelShifterCoreOutBuffer(MOSBase): """Level shifter with output buffers. """ def __init__(self, temp_db: TemplateDB, params: Param, **kwargs: Any) -> None: MOSBase.__init__(self, temp_db, params, **kwargs) self._center_col: int = -1 self._outr_inverted: bool = False self._mid_vertical: bool = False @property
[docs] def center_col(self) -> int: """int: The centerline column index.""" return self._center_col
@property
[docs] def outr_inverted(self) -> bool: return self._outr_inverted
@property
[docs] def mid_vertical(self) -> bool: """bool: True if level shifter core outputs are on vm_layer.""" return self._mid_vertical
@property
[docs] def dual_output(self) -> bool: return self.params['dual_output']
@property
[docs] def is_guarded(self) -> bool: return self.params['is_guarded']
@classmethod
[docs] def get_schematic_class(cls) -> Optional[Type[Module]]: # noinspection PyTypeChecker return ModuleDB.get_schematic_class('bag3_digital', 'lvshift_core_w_drivers')
@classmethod
[docs] def get_params_info(cls) -> Dict[str, str]: return dict( pinfo='The MOSBasePlaceInfo object.', seg_dict='dictionary of number of segments.', buf_seg_list='list of number of segments for output buffers.', buf_segn_list='list of number of segments for output buffers.', buf_segp_list='list of number of segments for output buffers.', w_dict='dictionary of number of fins.', ridx_p='pmos row index.', ridx_n='nmos row index.', vertical_rst='whether rst pins is vertical', is_guarded='True if it there should be guard ring around the cell', dual_output='True to have complementary outputs.', invert_out='True to export flip output parity.', vertical_out='True to have inverter chain output on vertical metal', in_upper='True to make the input connected to the upper transistor in the stack', has_rst='True to enable reset pins.', stack_p='PMOS number of stacks.', sig_locs='Optional dictionary of user defined signal locations', num_col_tot='Total number of columns.', export_pins='Defaults to False. True to export simulation pins.', rst_b_wire_idx='Wire index for the reset signal',
) @classmethod
[docs] def get_default_param_values(cls) -> Dict[str, Any]: return dict( w_dict={}, ridx_p=-1, ridx_n=0, vertical_rst=[], buf_seg_list=[], buf_segn_list=[], buf_segp_list=[], is_guarded=False, dual_output=True, invert_out=False, vertical_out=True, in_upper=False, has_rst=True, stack_p=1, sig_locs={}, num_col_tot=0, export_pins=False, rst_b_wire_idx=2,
)
[docs] def draw_layout(self): params = self.params pinfo = MOSBasePlaceInfo.make_place_info(self.grid, params['pinfo']) self.draw_base(pinfo) seg_dict: Mapping[str, int] = params['seg_dict'] buf_seg_list: List[int] = params['buf_seg_list'] buf_segn_list: List[int] = params['buf_segn_list'] buf_segp_list: List[int] = params['buf_segp_list'] w_dict: Mapping[str, int] = params['w_dict'] ridx_p: int = params['ridx_p'] ridx_n: int = params['ridx_n'] vertical_rst: List[str] = params['vertical_rst'] is_guarded: bool = params['is_guarded'] dual_output: bool = params['dual_output'] invert_out: bool = params['invert_out'] vertical_out: bool = params['vertical_out'] in_upper: bool = params['in_upper'] has_rst: bool = params['has_rst'] stack_p: int = params['stack_p'] sig_locs: Mapping[str, Union[float, HalfInt]] = params['sig_locs'] num_col_tot: int = params['num_col_tot'] export_pins: bool = params['export_pins'] rst_b_wire_idx: int = params['rst_b_wire_idx'] hm_layer = self.conn_layer + 1 vm_layer = hm_layer + 1 # placement and track computations # create level shifter core if not buf_segn_list or not buf_segp_list: if not buf_seg_list: raise RuntimeError('Not segment list provided') buf_segn_list = buf_seg_list buf_segp_list = buf_seg_list buf_nstage = len(buf_segn_list) buf_invert = (buf_nstage % 2 == 1) invert_in = (buf_invert ^ invert_out) if buf_invert ^ invert_in: self._outr_inverted = True outl_name = 'out' outr_name = 'outb' else: self._outr_inverted = False outl_name = 'outb' outr_name = 'out' default_wp = self.get_row_info(ridx_p).width default_wn = self.get_row_info(ridx_n).width core_params = dict( pinfo=pinfo, seg_dict=seg_dict, w_dict=w_dict, ridx_p=ridx_p, ridx_n=ridx_n, vertical_rst=vertical_rst, is_guarded=is_guarded, in_upper=in_upper, has_rst=has_rst, stack_p=stack_p, inp_on_right=invert_in, sig_locs=sig_locs, rst_b_wire_idx=rst_b_wire_idx, ) core_master: LevelShifterCore = self.new_template(LevelShifterCore, params=core_params) self._mid_vertical = core_master.out_vertical # get buffer track indices buf_inl_tidx = core_master.get_port('poutl').get_pins()[0].track_id.base_index buf_inr_tidx = core_master.get_port('poutr').get_pins()[0].track_id.base_index buf_pout0_tidx = self.get_track_index(ridx_p, MOSWireType.DS, 'sig', wire_idx=1) buf_pout1_tidx = self.get_track_index(ridx_p, MOSWireType.DS, 'sig', wire_idx=0) buf_nout0_tidx = self.get_track_index(ridx_n, MOSWireType.DS, 'sig', wire_idx=-2) buf_nout1_tidx = self.get_track_index(ridx_n, MOSWireType.DS, 'sig', wire_idx=-1) # create buffer master sig_locs_l = dict( pout0=buf_pout0_tidx, pout1=buf_pout1_tidx, nout0=buf_nout0_tidx, nout1=buf_nout1_tidx, ) sig_locs_r = sig_locs_l.copy() if is_guarded: # TODO: this code work with InvChainCore's gate index hack sig_locs_l['pin1'] = sig_locs_r['pin0'] = buf_inl_tidx sig_locs_l['pin0'] = sig_locs_r['pin1'] = buf_inr_tidx else: sig_locs_l['nin0'] = sig_locs_r['nin1'] = buf_inl_tidx sig_locs_l['nin1'] = sig_locs_r['nin0'] = buf_inr_tidx w_invp = w_dict.get('invp', default_wp) w_invn = w_dict.get('invn', default_wn) invr_params = dict( pinfo=pinfo, segn_list=buf_segn_list, segp_list=buf_segp_list, is_guarded=is_guarded, ridx_n=ridx_n, ridx_p=ridx_p, w_n=w_invn, w_p=w_invp, sig_locs=sig_locs_r, vertical_out=vertical_out, ) invr_master = self.new_template(InvChainCore, params=invr_params) sch_buf_params = invr_master.sch_params.copy(remove=['dual_output']) # place instances inv_sep = self.min_sep_col inv_sep += (inv_sep & 1) inv_col = invr_master.num_cols inv_gap = (inv_col & 1) inv_col_even = inv_col + inv_gap core_col = core_master.num_cols vdd_list = [] vss_list = [] if dual_output: invl_params = invr_params.copy() invl_params['sig_locs'] = sig_locs_l invl_master = self.new_template(InvChainCore, params=invl_params) cur_tot = core_col + 2 * inv_col_even num_col_tot = max(num_col_tot, cur_tot + 2 * inv_sep) sep_l = (num_col_tot - cur_tot) // 2 sep_l += (sep_l & 1) sep_r = num_col_tot - cur_tot - sep_l inv_l = self.add_tile(invl_master, 0, inv_col_even, flip_lr=True, commit=False) self._update_buf_inst(inv_l, vm_layer, sig_locs_l, sig_locs, 'l') vdd_list.append(inv_l.get_pin('VDD')) vss_list.append(inv_l.get_pin('VSS')) cur_col = inv_col_even + sep_l core = self.add_tile(core_master, 0, cur_col) self._center_col = core_master.center_col + cur_col cur_col += core_col + sep_r self.connect_wires([core.get_pin('poutl'), inv_l.get_pin('pin')]) self._export_buf(inv_l, vertical_out, outl_name, buf_invert) else: cur_tot = core_col + inv_col + inv_gap num_col_tot = max(num_col_tot, cur_tot + inv_sep) sep = num_col_tot - cur_tot sep += (sep & 1) core = self.add_tile(core_master, 0, 0) self._center_col = core_master.center_col cur_col = core_col + sep vdd_list.append(core.get_pin('VDD')) vss_list.append(core.get_pin('VSS')) inv_r = self.add_tile(invr_master, 0, cur_col, commit=False) self._update_buf_inst(inv_r, vm_layer, sig_locs_r, sig_locs, 'r') vdd_list.append(inv_r.get_pin('VDD')) vss_list.append(inv_r.get_pin('VSS')) self._export_buf(inv_r, vertical_out, outr_name, buf_invert) self.set_mos_size(num_cols=cur_col + inv_col_even) # export supplies self.add_pin('VDD', self.connect_wires(vdd_list)) self.add_pin('VSS', self.connect_wires(vss_list)) # connect core output to buffer input self.connect_wires([core.get_pin('poutr'), inv_r.get_pin('pin')]) # reexport core pins if core.has_port('rst_casc'): self.reexport(core.get_port('rst_casc')) self.reexport(core.get_port('rst_outp'), net_name='rst_out') self.reexport(core.get_port('rst_outn'), net_name='rst_outb') self.reexport(core.get_port('inp'), net_name='in') self.reexport(core.get_port('inn'), net_name='inb') midp = core.get_pin('outp') midn = core.get_pin('outn') self.add_pin('midp', midp, hide=not export_pins) self.add_pin('midn', midn, hide=not export_pins) if invert_in: self.add_pin('midr', midn, hide=True) self.add_pin('midl', midp, hide=True) else: self.add_pin('midr', midp, hide=True) self.add_pin('midl', midn, hide=True) # compute schematic parameters self.sch_params = dict( core_params=core_master.sch_params, buf_params=sch_buf_params, dual_output=dual_output, invert_out=invert_out,
)
[docs] def _export_buf(self, inst: Optional[PyLayInstance], vertical_out: bool, name: str, buf_invert: bool) -> None: if inst is not None: pin_name = 'outb' if buf_invert else 'out' if vertical_out: self.reexport(inst.get_port(pin_name), net_name=name) else: self.reexport(inst.get_port(f'p{pin_name}'), net_name=name, connect=True) self.reexport(inst.get_port(f'n{pin_name}'), net_name=name, connect=True) self.reexport(inst.get_port(f'p{pin_name}'), net_name=f'p{name}') self.reexport(inst.get_port(f'n{pin_name}'), net_name=f'n{name}')
[docs] def _update_buf_inst(self, inst: PyLayInstance, vm_layer: int, sig_locs_inst: Dict[str, Any], sig_locs: Mapping[str, Any], suffix: str) -> None: xform = inst.transformation.get_inverse() test = sig_locs.get('out' + suffix, None) if test is not None: key = 'outb' if cast(InvChainCore, inst.master).out_invert else 'out' sig_locs_inst[key] = self.grid.transform_track(vm_layer, test, xform) inst.new_master_with(sig_locs=sig_locs_inst) inst.commit()
[docs]class LevelShifter(MOSBase): """Level shifter with single-ended input and output buffers. This generator uses two tiles, with the bottom tile mirrored, to separate the two supply domains. """ def __init__(self, temp_db: TemplateDB, params: Param, **kwargs: Any) -> None: MOSBase.__init__(self, temp_db, params, **kwargs) self._ridx_p = -1 self._ridx_n = 0 @property
[docs] def ridx_p(self) -> int: return self._ridx_p
@property
[docs] def ridx_n(self) -> int: return self._ridx_n
@classmethod
[docs] def get_schematic_class(cls) -> Optional[Type[Module]]: # noinspection PyTypeChecker return ModuleDB.get_schematic_class('bag3_digital', 'lvshift')
@classmethod
[docs] def get_params_info(cls) -> Dict[str, str]: return dict( pinfo='The MOSBasePlaceInfo object.', lv_params='level shifter with output buffer parameters.', in_buf_params='input buffer parameters.', export_pins='Defaults to False. True to export simulation pins.',
) @classmethod
[docs] def get_default_param_values(cls) -> Dict[str, Any]: return dict( export_pins=False,
)
[docs] def draw_layout(self): params = self.params pinfo = MOSBasePlaceInfo.make_place_info(self.grid, params['pinfo']) self.draw_base(pinfo, flip_tile=True) lv_params: Param = params['lv_params'] in_buf_params: Param = params['in_buf_params'] export_pins = params['export_pins'] # create masters lv_params = lv_params.copy(dict(pinfo=pinfo, export_pins=export_pins)) lv_master: LevelShifterCoreOutBuffer = self.new_template(LevelShifterCoreOutBuffer, params=lv_params) self._ridx_p = lv_master.params['ridx_p'] self._ridx_n = lv_master.params['ridx_n'] in_buf_params = in_buf_params.copy(dict(pinfo=pinfo, dual_output=True, vertical_output=True, is_guarded=lv_master.is_guarded), remove=['sig_locs']) buf_master: InvChainCore = self.new_template(InvChainCore, params=in_buf_params) if buf_master.num_stages != 2: raise ValueError('Must have exactly two stages in input buffer.') # make sure buffer outb output is next to out, to avoid collision vm_layer = self.conn_layer + 2 out_tidx = buf_master.get_port('out').get_pins()[0].track_id.base_index prev_tidx = self.tr_manager.get_next_track(vm_layer, out_tidx, 'sig', 'sig', up=False) buf_master = cast(InvChainCore, buf_master.new_template_with(sig_locs=dict(outb=prev_tidx))) # placement lv = self.add_tile(lv_master, 1, 0) buf_ncol = buf_master.num_cols if lv_master.mid_vertical: tid = lv.get_pin('midr').track_id tidx = self.tr_manager.get_next_track(vm_layer, tid.base_index, 'sig', 'sig', up=True) col_idx = self.arr_info.track_to_col(vm_layer, tidx, mode=RoundMode.GREATER_EQ) buf_idx = col_idx + buf_ncol else: lv_center = lv_master.center_col buf_idx = lv_center + buf_ncol # make sure supply on even number buf_idx += (buf_idx & 1) buf = self.add_tile(buf_master, 0, buf_idx, flip_lr=True) num_cols_tot = max(buf_idx, lv_master.num_cols) self.set_mos_size(num_cols=num_cols_tot) # connect wires self.add_pin('VSS', self.connect_wires([lv.get_pin('VSS'), buf.get_pin('VSS')])) self.connect_differential_wires(buf.get_pin('out'), buf.get_pin('outb'), lv.get_pin('in'), lv.get_pin('inb')) # breakpoint() # reexport pins self.reexport(buf.get_port('VDD'), net_name='VDD_in') self.reexport(lv.get_port('VDD')) self.reexport(buf.get_port('in')) if export_pins: self.add_pin('inb_buf', buf.get_pin('outb')) self.add_pin('in_buf', buf.get_pin('out')) self.reexport(lv.get_port('midn')) self.reexport(lv.get_port('midp')) for name in ['out', 'outb', 'rst_out', 'rst_outb', 'rst_casc']: if lv.has_port(name): self.reexport(lv.get_port(name)) buf_sch_params = buf_master.sch_params.copy(remove=['dual_output']) lv_sch_params = lv_master.sch_params.copy(remove=['dual_output', 'invert_out']) self.sch_params = dict( lev_params=lv_sch_params, buf_params=buf_sch_params, dual_output=lv_master.dual_output, invert_out=lv_master.outr_inverted, export_pins=export_pins,
)