# 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 various latches."""
from typing import Any, Dict, Optional, Type, Tuple, Mapping
from pybag.enum import MinLenMode, RoundMode
from bag.util.math import HalfInt
from bag.util.immutable import Param, ImmutableSortedDict
from bag.design.module import Module
from bag.layout.routing.base import TrackID
from bag.layout.template import TemplateDB
from xbase.layout.enum import MOSWireType
from xbase.layout.mos.base import MOSBasePlaceInfo, MOSBase
from ...schematic.sr_latch_symmetric import bag3_digital__sr_latch_symmetric
[docs]class SRLatchSymmetricHalf(MOSBase):
"""Half of symmetric SR latch
"""
def __init__(self, temp_db: TemplateDB, params: Param, **kwargs: Any) -> None:
MOSBase.__init__(self, temp_db, params, **kwargs)
zero = HalfInt(0)
self._q_tr_info = (0, zero, zero)
self._sr_hm_tr_info = self._q_tr_info
self._sr_vm_tr_info = self._q_tr_info
@property
[docs] def q_tr_info(self) -> Tuple[int, HalfInt, HalfInt]:
return self._q_tr_info
@property
[docs] def sr_hm_tr_info(self) -> Tuple[int, HalfInt, HalfInt]:
return self._sr_hm_tr_info
@property
[docs] def sr_vm_tr_info(self) -> Tuple[int, HalfInt, HalfInt]:
return self._sr_vm_tr_info
@classmethod
[docs] def get_params_info(cls) -> Dict[str, str]:
return dict(
pinfo='placement information object.',
seg_dict='segments dictionary.',
w_dict='widths dictionary.',
ridx_n='bottom nmos row index.',
ridx_p='pmos row index.',
has_rstb='True to add rstb functionality.',
has_outbuf='True to add output buffers.',
has_inbuf='True to add input buffers.',
out_pitch='output wire pitch from center.',
vertical_sup='True to have supply unconnected on conn_layer; False by default',
)
@classmethod
[docs] def get_default_param_values(cls) -> Dict[str, Any]:
return dict(
w_dict={},
ridx_n=0,
ridx_p=-1,
has_rstb=False,
has_outbuf=True,
has_inbuf=True,
out_pitch=0.5,
vertical_sup=False,
)
[docs] def draw_layout(self):
place_info = MOSBasePlaceInfo.make_place_info(self.grid, self.params['pinfo'])
self.draw_base(place_info)
seg_dict: ImmutableSortedDict[str, int] = self.params['seg_dict']
ridx_n: int = self.params['ridx_n']
ridx_p: int = self.params['ridx_p']
has_rstb: bool = self.params['has_rstb']
has_outbuf: bool = self.params['has_outbuf']
has_inbuf: bool = self.params['has_inbuf']
out_pitch: HalfInt = HalfInt.convert(self.params['out_pitch'])
vertical_sup: bool = self.params['vertical_sup']
w_dict, th_dict = self._get_w_th_dict(ridx_n, ridx_p, has_rstb)
seg_fb = seg_dict['fb']
seg_ps = seg_dict['ps']
seg_nr = seg_dict['nr']
seg_obuf = seg_dict['obuf'] if has_outbuf else 0
seg_ibuf = seg_dict['ibuf'] if has_inbuf else 0
w_pfb = w_dict['pfb']
w_nfb = w_dict['nfb']
w_ps = w_dict['ps']
w_nr = w_dict['nr']
w_rst = w_dict.get('pr', 0)
w_nbuf = w_nr
w_pbuf = w_ps
sch_seg_dict = dict(nfb=seg_fb, pfb=seg_fb, ps=seg_ps, nr=seg_nr)
if has_rstb:
sch_seg_dict['pr'] = seg_rst = seg_dict['rst']
else:
seg_rst = 0
if seg_ps & 1 or seg_nr & 1 or seg_rst & 1 or seg_obuf & 1 or seg_ibuf & 1:
raise ValueError('ps, nr, rst, and buf must have even number of segments')
# placement
min_sep = self.min_sep_col
# use even step size to maintain supply conn_layer wires parity.
min_sep += (min_sep & 1)
hm_layer = self.conn_layer + 1
vm_layer = hm_layer + 1
grid = self.grid
arr_info = self.arr_info
tr_manager = self.tr_manager
hm_w = tr_manager.get_width(hm_layer, 'sig')
vm_w = tr_manager.get_width(vm_layer, 'sig')
hm_sep_col = self.get_hm_sp_le_sep_col(ntr=hm_w)
mid_sep = max(hm_sep_col - 2, 0)
mid_sep = (mid_sep + 1) // 2
if has_inbuf:
m_nibuf = self.add_mos(ridx_n, mid_sep, seg_ibuf, w=w_nbuf)
m_pibuf = self.add_mos(ridx_p, mid_sep, seg_ibuf, w=w_pbuf)
cur_col = mid_sep + seg_ibuf
psrb_list = [m_nibuf.g, m_pibuf.g]
else:
m_nibuf = m_pibuf = None
cur_col = mid_sep
psrb_list = []
nr_col = cur_col
m_nr = self.add_mos(ridx_n, cur_col, seg_nr, w=w_nr)
m_ps = self.add_mos(ridx_p, cur_col, seg_ps, w=w_ps)
psrb_list.append(m_ps.g)
pcol = cur_col + seg_ps
if has_rstb:
m_rst = self.add_mos(ridx_p, pcol, seg_rst, w=w_rst)
pcol += seg_rst
else:
m_rst = None
cur_col = max(cur_col + seg_nr, pcol)
if has_outbuf:
m_pinv = self.add_mos(ridx_p, cur_col, seg_obuf, w=w_pbuf)
m_ninv = self.add_mos(ridx_n, cur_col, seg_obuf, w=w_nbuf)
cur_col += seg_obuf
else:
m_pinv = m_ninv = None
cur_col += min_sep
fb_col = cur_col
m_pfb = self.add_mos(ridx_p, cur_col, seg_fb, w=w_pfb, g_on_s=True, stack=2, sep_g=True)
m_nfb = self.add_mos(ridx_n, cur_col, seg_fb, w=w_nfb, g_on_s=True, stack=2, sep_g=True)
self.set_mos_size()
# track planning
nbuf_tid = self.get_track_id(ridx_n, MOSWireType.DS, wire_name='sig', wire_idx=-2)
nq_tid = self.get_track_id(ridx_n, MOSWireType.DS, wire_name='sig', wire_idx=-1)
psr_tid = self.get_track_id(ridx_p, MOSWireType.G, wire_name='sig', wire_idx=-1)
pq_tid = self.get_track_id(ridx_p, MOSWireType.DS, wire_name='sig', wire_idx=0)
pbuf_tid = self.get_track_id(ridx_p, MOSWireType.DS, wire_name='sig', wire_idx=1)
# try to spread out gate wires to lower parasitics on differential Q wires
ng_lower = self.get_track_index(ridx_n, MOSWireType.G, wire_name='sig', wire_idx=0)
ng_upper = self.get_track_index(ridx_n, MOSWireType.G, wire_name='sig', wire_idx=-1)
g_idx_list = tr_manager.spread_wires(hm_layer, ['sig', 'sig_hs', 'sig_hs', 'sig'],
ng_lower, ng_upper, ('sig_hs', 'sig_hs'), alignment=0)
self._q_tr_info = (hm_w, g_idx_list[2], g_idx_list[1])
self._sr_hm_tr_info = (hm_w, g_idx_list[3], g_idx_list[0])
if has_rstb:
rst_tid = self.get_track_id(ridx_p, MOSWireType.G, wire_name='sig', wire_idx=-2)
pq_conn_list = [m_ps.d, m_rst.d, m_pfb.s]
vdd_list = [m_ps.s, m_rst.s, m_pfb.d]
vss_list = [m_nr.s, m_nfb.d]
rstb = self.connect_to_tracks(m_rst.g, rst_tid, min_len_mode=MinLenMode.MIDDLE)
rst_vm_tidx = grid.coord_to_track(vm_layer, rstb.middle, mode=RoundMode.GREATER_EQ)
rstb_vm = self.connect_to_tracks(rstb, TrackID(vm_layer, rst_vm_tidx, width=vm_w),
min_len_mode=MinLenMode.MIDDLE)
self.add_pin('rstb', rstb_vm)
else:
pq_conn_list = [m_ps.d, m_pfb.s]
vdd_list = [m_ps.s, m_pfb.d]
vss_list = [m_nr.s, m_nfb.d]
self.add_pin('nsr', m_nr.g)
self.add_pin('nsrb', m_nfb.g[0::2])
nq = self.connect_to_tracks([m_nr.d, m_nfb.s], nq_tid)
pq = self.connect_to_tracks(pq_conn_list, pq_tid)
psrb = self.connect_to_tracks(psrb_list, psr_tid, min_len_mode=MinLenMode.UPPER)
psr = self.connect_to_tracks(m_pfb.g[0::2], psr_tid, min_len_mode=MinLenMode.LOWER)
qb = self.connect_wires([m_nfb.g[1::2], m_pfb.g[1::2]])
self.add_pin('qb', qb)
if has_inbuf:
vdd_list.append(m_pibuf.s)
vss_list.append(m_nibuf.s)
nbuf = self.connect_to_tracks(m_nibuf.d, nbuf_tid, min_len_mode=MinLenMode.UPPER)
pbuf = self.connect_to_tracks(m_pibuf.d, pbuf_tid, min_len_mode=MinLenMode.UPPER)
vm_tidx = grid.coord_to_track(vm_layer, nbuf.middle, mode=RoundMode.LESS_EQ)
buf = self.connect_to_tracks([nbuf, pbuf], TrackID(vm_layer, vm_tidx, width=vm_w))
self.add_pin('sr_buf', buf)
out_p_htr = out_pitch.dbl_value
vm_ref = grid.coord_to_track(vm_layer, 0)
srb_vm_tidx = arr_info.col_to_track(vm_layer, nr_col + 1, mode=RoundMode.GREATER_EQ)
if has_outbuf:
vdd_list.append(m_pinv.s)
vss_list.append(m_ninv.s)
nbuf = self.connect_to_tracks(m_ninv.d, nbuf_tid, min_len_mode=MinLenMode.MIDDLE)
pbuf = self.connect_to_tracks(m_pinv.d, pbuf_tid, min_len_mode=MinLenMode.MIDDLE)
vm_delta = grid.coord_to_track(vm_layer, nbuf.middle, mode=RoundMode.LESS_EQ) - vm_ref
vm_htr = -(-vm_delta.dbl_value // out_p_htr) * out_p_htr
vm_tidx = vm_ref + HalfInt(vm_htr)
buf = self.connect_to_tracks([nbuf, pbuf], TrackID(vm_layer, vm_tidx, width=vm_w))
self.add_pin('buf_out', buf)
buf_in = self.connect_wires([m_ninv.g, m_pinv.g])
self.add_pin('buf_in', buf_in)
q_vm_tidx = tr_manager.get_next_track(vm_layer, vm_tidx, 'sig', 'sig')
else:
vm_delta = tr_manager.get_next_track(vm_layer, srb_vm_tidx, 'sig', 'sig') - vm_ref
vm_htr = -(-vm_delta.dbl_value // out_p_htr) * out_p_htr
q_vm_tidx = vm_ref + HalfInt(vm_htr)
sr_vm_tidx = arr_info.col_to_track(vm_layer, fb_col, mode=RoundMode.LESS_EQ)
self._sr_vm_tr_info = (vm_w, sr_vm_tidx, srb_vm_tidx)
q_vm = self.connect_to_tracks([nq, pq], TrackID(vm_layer, q_vm_tidx, width=vm_w))
self.add_pin('q_vm', q_vm)
self.add_pin('psr', psr)
self.add_pin('psrb', psrb)
if vertical_sup:
vss = vss_list
vdd = vdd_list
else:
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')
vss = self.connect_to_tracks(vss_list, vss_tid)
vdd = self.connect_to_tracks(vdd_list, vdd_tid)
self.add_pin('VDD', vdd)
self.add_pin('VSS', vss)
lch = arr_info.lch
buf_params = ImmutableSortedDict(dict(
lch=lch,
w_p=w_pbuf,
w_n=w_nbuf,
th_p=th_dict['ps'],
th_n=th_dict['nr'],
seg=seg_obuf,
))
obuf_params = buf_params if has_outbuf else None
ibuf_params = buf_params.copy(append=dict(seg=seg_ibuf)) if has_inbuf else None
self.sch_params = dict(
core_params=ImmutableSortedDict(dict(
lch=lch,
seg_dict=ImmutableSortedDict(sch_seg_dict),
w_dict=w_dict,
th_dict=th_dict,
)),
outbuf_params=obuf_params,
inbuf_params=ibuf_params,
has_rstb=has_rstb,
)
[docs] def _get_w_th_dict(self, ridx_n: int, ridx_p: int, has_rstb: bool
) -> Tuple[ImmutableSortedDict[str, int], ImmutableSortedDict[str, str]]:
w_dict: Mapping[str, int] = self.params['w_dict']
w_ans = {}
th_ans = {}
for row_idx, name_list in [(ridx_n, ['nfb', 'nr']),
(ridx_p, ['pfb', 'ps'])]:
rinfo = self.get_row_info(row_idx, 0)
for name in name_list:
w = w_dict.get(name, 0)
if w == 0:
w = rinfo.width
w_ans[name] = w
th_ans[name] = rinfo.threshold
if has_rstb:
w_ans['pr'] = w_ans['ps']
th_ans['pr'] = th_ans['ps']
return ImmutableSortedDict(w_ans), ImmutableSortedDict(th_ans)
[docs]class SRLatchSymmetric(MOSBase):
"""Symmetric SR latch. Mainly designed to be used with strongarm.
"""
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_digital__sr_latch_symmetric
@classmethod
[docs] def get_params_info(cls) -> Dict[str, str]:
ans = SRLatchSymmetricHalf.get_params_info()
ans['swap_outbuf'] = 'True to swap output buffers, so outp is on opposite side of inp.'
return ans
@classmethod
[docs] def get_default_param_values(cls) -> Dict[str, Any]:
ans = SRLatchSymmetricHalf.get_default_param_values()
ans['swap_outbuf'] = False
return ans
[docs] def draw_layout(self) -> None:
master: SRLatchSymmetricHalf = self.new_template(SRLatchSymmetricHalf, params=self.params)
self.draw_base(master.draw_base_info)
swap_outbuf: bool = self.params['swap_outbuf']
hm_w, q_tidx, qb_tidx = master.q_tr_info
_, sr_hm_top, sr_hm_bot = master.sr_hm_tr_info
vm_w, sr_vm_tidx, srb_vm_tidx = master.sr_vm_tr_info
# placement
nhalf = master.num_cols
corel = self.add_tile(master, 0, nhalf, flip_lr=True)
corer = self.add_tile(master, 0, nhalf)
self.set_mos_size(num_cols=2 * nhalf)
hm_layer = self.conn_layer + 1
vm_layer = hm_layer + 1
arr_info = self.arr_info
vm0 = arr_info.col_to_track(vm_layer, 0)
vmh = arr_info.col_to_track(vm_layer, nhalf)
vmdr = vmh - vm0
vmdl = vmh + vm0
pr = corel.get_pin('psr')
psb = corel.get_pin('psrb')
nr = corel.get_pin('nsr')
nsb = corel.get_pin('nsrb')
ps = corer.get_pin('psr')
prb = corer.get_pin('psrb')
ns = corer.get_pin('nsr')
nrb = corer.get_pin('nsrb')
nr, nsb = self.connect_differential_tracks(nr, nsb, hm_layer, sr_hm_top, sr_hm_bot,
width=hm_w)
ns, nrb = self.connect_differential_tracks(ns, nrb, hm_layer, sr_hm_bot, sr_hm_top,
width=hm_w)
sb = self.connect_to_tracks([psb, nsb], TrackID(vm_layer, vmdl - srb_vm_tidx, width=vm_w))
r = self.connect_to_tracks([pr, nr], TrackID(vm_layer, vmdl - sr_vm_tidx, width=vm_w),
track_lower=sb.lower)
s = self.connect_to_tracks([ps, ns], TrackID(vm_layer, vmdr + sr_vm_tidx, width=vm_w))
rb = self.connect_to_tracks([prb, nrb], TrackID(vm_layer, vmdr + srb_vm_tidx, width=vm_w),
track_lower=s.lower)
self.add_pin('sb', sb)
self.add_pin('rb', rb)
if corel.has_port('sr_buf'):
sbuf = corel.get_pin('sr_buf')
rbuf = corer.get_pin('sr_buf')
self.connect_to_tracks(sbuf, ns.track_id, track_upper=ns.upper)
self.connect_to_tracks(rbuf, nr.track_id, track_lower=nr.lower)
else:
self.add_pin('s', s)
self.add_pin('r', r)
q_list = [corel.get_pin('q_vm'), corer.get_pin('qb')]
qb_list = [corer.get_pin('q_vm'), corel.get_pin('qb')]
if corel.has_port('buf_out'):
if swap_outbuf:
self.reexport(corel.get_port('buf_out'), net_name='qb')
self.reexport(corer.get_port('buf_out'), net_name='q')
q_list.append(corel.get_pin('buf_in'))
qb_list.append(corer.get_pin('buf_in'))
else:
self.reexport(corel.get_port('buf_out'), net_name='q')
self.reexport(corer.get_port('buf_out'), net_name='qb')
q_list.append(corer.get_pin('buf_in'))
qb_list.append(corel.get_pin('buf_in'))
else:
self.add_pin('q', q_list[0])
self.add_pin('qb', qb_list[0])
self.connect_differential_tracks(q_list, qb_list, self.conn_layer + 1,
q_tidx, qb_tidx, width=hm_w)
if corel.has_port('rstb'):
self.reexport(corel.get_port('rstb'), net_name='rsthb')
self.reexport(corer.get_port('rstb'), net_name='rstlb')
self.add_pin('VDD', self.connect_wires(corel.get_all_port_pins('VDD') + corer.get_all_port_pins('VDD')))
self.add_pin('VSS', self.connect_wires(corel.get_all_port_pins('VSS') + corer.get_all_port_pins('VSS')))
self.sch_params = master.sch_params