# -*- coding: utf-8 -*-
# SPDX-License-Identifier: BSD-3-Clause AND Apache-2.0
# Copyright 2018 Regents of the University of California
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""This module defines various passive high-pass filter generators
"""
from typing import Dict, Set, Any, cast, List, Optional, Tuple, Mapping
import numpy as np
from pybag.enum import RoundMode, Direction, Orientation, MinLenMode
from pybag.core import Transform, BBox
from bag.util.immutable import Param
from bag.util.search import BinaryIterator
from bag.design.module import Module
from bag.layout.util import BBox
from bag.layout.routing.base import TrackID, WireArray
from bag.layout.template import TemplateDB, TemplateBase
from xbase.layout.res.base import ResBasePlaceInfo, ResArrayBase
from xbase.layout.array.top import ArrayBaseWrapper
from ...schematic.high_pass_diff import bag3_analog__high_pass_diff
from ...schematic.high_pass_array import bag3_analog__high_pass_array
from ...schematic.high_pass_clk import bag3_analog__high_pass_clk
from ...schematic.high_pass_column import bag3_analog__high_pass_column
[docs]class HighPassDiffCore(ResArrayBase):
"""A differential RC high-pass filter.
"""
def __init__(self, temp_db: TemplateDB, params: Param, **kwargs: Any) -> None:
ResArrayBase.__init__(self, temp_db, params, **kwargs)
self._sup_name = None
@classmethod
[docs] def get_schematic_class(cls) -> Optional[Module]:
return bag3_analog__high_pass_diff
@classmethod
[docs] def get_params_info(cls) -> Dict[str, str]:
return dict(
pinfo='The ResBasePlaceInfo object.',
nx_dum='Number of dummies on each side, X direction',
ny_dum='Number of dummies on each side, Y direction',
h_unit='total height, in resolution units.',
sub_type='the substrate type.',
threshold='the substrate threshold flavor.',
in_tr_info='Input track info.',
out_tr_info='Output track info.',
bias_idx='Bias port index.',
vdd_tr_info='Supply track info.',
res_type='Resistor intent',
res_options='Configuration dictionary for ResArrayBase.',
cap_spx='Capacitor horizontal separation, in resolution units.',
cap_spy='Capacitor vertical space from resistor ports, in resolution units.',
cap_margin='Capacitor space from edge, in resolution units.',
show_pins='True to show pins.',
cap_val='Schematic value for analogLib cap',
)
@classmethod
[docs] def get_default_param_values(cls) -> Dict[str, Any]:
return dict(
cap_val=1e-9,
bias_idx=0,
# vdd_tr_info=None,
# res_type='standard',
# res_options=None,
# cap_spx=0,
# cap_spy=0,
# cap_margin=0,
# half_blk_x=True,
# show_pins=True,
)
[docs] def draw_layout(self):
h_unit = self.params['h_unit']
sub_type = self.params['sub_type']
threshold = self.params['threshold']
in_tr_info = self.params['in_tr_info']
out_tr_info = self.params['out_tr_info']
bias_idx = self.params['bias_idx']
vdd_tr_info = self.params['vdd_tr_info']
res_type = self.params['res_type']
res_options = self.params['res_options']
cap_spx = self.params['cap_spx']
cap_spy = self.params['cap_spy']
cap_margin = self.params['cap_margin']
show_pins = self.params['show_pins']
# if res_options is None:
# my_options = dict(well_end_mode=2)
#
# else:
# my_options = res_options.copy()
# my_options['well_end_mode'] = 2
# find resistor length
# info = ResArrayBaseInfo(self.grid, sub_type, threshold, top_layer=top_layer,
# res_type=res_type, grid_type=None, ext_dir='y', options=my_options,
# connect_up=True, half_blk_x=half_blk_x, half_blk_y=True)
#
# lmin, lmax = info.get_res_length_bounds()
# bin_iter = BinaryIterator(lmin, lmax, step=2)
# while bin_iter.has_next():
# lcur = bin_iter.get_next()
# htot = info.get_place_info(lcur, w_unit, 1, 1)[3]
# if htot < h_unit:
# bin_iter.save()
# bin_iter.up()
# else:
# bin_iter.down()
#
# # draw resistor
# l_unit = bin_iter.get_last_save()
# TODO: add methods to find length?
pinfo = cast(ResBasePlaceInfo,
ResBasePlaceInfo.make_place_info(self.grid, self.params['pinfo']))
# Draw resistor
self.draw_base(pinfo)
conn_layer = self.conn_layer
hm_layer = conn_layer + 1
vm_layer = hm_layer + 1
xm_layer = vm_layer + 1
prim_lay_purp = self.tech_cls.tech_info.get_lay_purp_list(conn_layer)[0]
assert self.top_layer % 2 == 0, "Top layer must be even"
# connect resistors
vdd, biasp, biasn, outp_h, outn_h, xl, xr = self.connect_resistors(bias_idx)
# draw MOM cap
nser = self.place_info.nx // 2 - self.params['nx_dum']
tmp = self.draw_mom_cap(nser, xl, xr, cap_spx, cap_spy, cap_margin)
caplp, capln, caprp, caprn, caplvm, caprvm = tmp
# Connect caps to resistors
self.connect_to_track_wires(outp_h, caplvm)
self.connect_to_track_wires(outn_h, caprvm)
# connect outputs to horizontal tracks and draw metal resistors
pin_layer = caplp.layer_id + 1
assert pin_layer == self.top_layer
# TODO: Metal resistors should not have the same size
pidx, nidx, tr_w = in_tr_info
inp, inn = self.connect_differential_tracks(caplp, caprp, pin_layer, pidx, nidx, width=tr_w)
_res_w_l, _res_w_h = self.grid.get_wire_bounds(pin_layer, pidx, tr_w)
res_in_w = _res_w_h - _res_w_l
tr_lower, tr_upper = inp.lower, inp.upper
self.add_res_metal_warr(pin_layer, pidx, tr_lower - res_in_w, tr_lower, width=tr_w)
self.add_res_metal_warr(pin_layer, nidx, tr_lower - res_in_w, tr_lower, width=tr_w)
inp = self.add_wires(pin_layer, pidx, tr_lower - 2 * res_in_w, tr_lower - res_in_w, width=tr_w)
inn = self.add_wires(pin_layer, nidx, tr_lower - 2 * res_in_w, tr_lower - res_in_w, width=tr_w)
pidx, nidx, tr_w = out_tr_info
outp, outn = self.connect_differential_tracks(capln, caprn, pin_layer, pidx, nidx, track_lower=tr_lower,
track_upper=tr_upper, width=tr_w)
_res_w_l, _res_w_h = self.grid.get_wire_bounds(pin_layer, pidx, tr_w)
res_out_w = _res_w_h - _res_w_l
tr_lower, tr_upper = outp.lower, outp.upper
self.add_res_metal_warr(pin_layer, pidx, tr_upper, tr_upper + res_out_w, width=tr_w)
self.add_res_metal_warr(pin_layer, nidx, tr_upper, tr_upper + res_out_w, width=tr_w)
outp = self.add_wires(pin_layer, pidx, tr_upper + res_out_w, tr_upper + 2 * res_out_w,
width=tr_w)
outn = self.add_wires(pin_layer, nidx, tr_upper + res_out_w, tr_upper + 2 * res_out_w,
width=tr_w)
# Draw substrate connects at edges
# TODO: if we don't have substrate taps we need to bias elsewhere?
if self.has_substrate_port:
# Connect to conn layer at edges
hm_tidx_l = self.grid.coord_to_track(hm_layer, self.bound_box.yl, mode=RoundMode.GREATER_EQ)
hm_tidx_h = self.grid.coord_to_track(hm_layer, self.bound_box.yh, mode=RoundMode.LESS_EQ)
hm_tid_l = TrackID(hm_layer, hm_tidx_l)
hm_tid_h = TrackID(hm_layer, hm_tidx_h)
sub_conn = []
for xidx in range(self.nx):
port = self.get_device_port(xidx, 0, "BULK")
sub_conn.append(self.connect_bbox_to_tracks(Direction.LOWER, prim_lay_purp, port, hm_tid_l))
sub_conn.append(self.connect_bbox_to_tracks(Direction.LOWER, prim_lay_purp, port, hm_tid_h))
self.connect_to_track_wires(sub_conn, vdd)
# connect/export vdd
self._sup_name = 'VDD' if \
cast(ResBasePlaceInfo, self.place_info).res_config['sub_type_default'] == 'ntap' else 'VSS'
if not vdd_tr_info:
self.add_pin(f'{self._sup_name}_vm', vdd, label=self._sup_name, show=show_pins, connect=True)
else:
self.add_pin(f'{self._sup_name}_vm', vdd, label=self._sup_name, show=False)
for tr_info in vdd_tr_info:
tid = TrackID(xm_layer, tr_info[0], width=tr_info[1])
self.add_pin(self._sup_name, self.connect_to_tracks(vdd, tid), show=show_pins, connect=True)
# add pins
self.add_pin('biasp', biasp, show=show_pins)
self.add_pin('biasn', biasn, show=show_pins)
self.add_pin('outp', outp, show=show_pins)
self.add_pin('outn', outn, show=show_pins)
self.add_pin('inp', inp, show=show_pins)
self.add_pin('inn', inn, show=show_pins)
self._sch_params = dict(
l=pinfo.l_res,
w=pinfo.w_res,
intent=res_type,
nser=nser * self.place_info.ny,
ndum=(self.params['nx_dum'], self.place_info.ny),
res_in_info=(pin_layer, res_in_w, res_in_w),
res_out_info=(pin_layer, res_out_w, res_out_w),
cap_val=self.params['cap_val'],
sub_name=self._sup_name
)
[docs] def connect_resistors(self, bias_idx) -> Tuple[WireArray, WireArray, WireArray, WireArray, WireArray, int, int]:
"""Connect the resistors in series. bias_idx sets which track to draw the vm bias wire on"""
nx = self._info.nx
ny = self._info.ny
nx_dum = self.params['nx_dum']
conn_layer = self.place_info.conn_layer
hm_layer = conn_layer + 1
vm_layer = hm_layer + 1
tr_w_hm = self.tr_manager.get_width(hm_layer, 'mid') # TODO: width
w_sup_vm = self.tr_manager.get_width(vm_layer, 'sup')
prim_lay_purp = self.tech_cls.tech_info.get_lay_purp_list(conn_layer)[0]
biasp = []
biasn = []
outp = outn = None
# Add all dummies ports to biases
for idx in range(nx_dum):
biasp.append(self.get_res_ports(0, idx)[0])
biasp.append(self.get_res_ports(ny - 1, idx)[ny % 2])
biasn.append(self.get_res_ports(0, nx - 1 - idx)[0])
biasn.append(self.get_res_ports(ny - 1, nx - 1 - idx)[ny % 2])
for row in range(ny - 1):
row_odd = bool(row & 1)
for col in range(nx):
bot_res_ports = self.get_res_ports(row, col)
top_res_ports = self.get_res_ports(row + 1, col)
if row_odd:
bbox_series_conn = bot_res_ports[0].extend(y=top_res_ports[0].yl)
else:
bbox_series_conn = bot_res_ports[1].extend(y=top_res_ports[1].yl)
self.add_rect(prim_lay_purp, bbox_series_conn)
# Snake together the resistors in series
for idx in range(nx_dum, nx // 2):
idx_rel_dum = idx - nx_dum
row = 0 if idx_rel_dum % 2 == 0 else ny - 1
cpl = self.get_res_ports(row, idx)
cpr = self.get_res_ports(row, nx - 1 - idx)
conn_par = 0 if idx_rel_dum % 2 == 0 else (1 + row) % 2
# If we're still on the last dummy, add one terminal to the bias
if idx == nx_dum:
tmpl = self.get_res_ports(ny - 1 - row, idx)
tmpr = self.get_res_ports(ny - 1 - row, nx - 1 - idx)
biasp.append(tmpl[ny % 2])
biasn.append(tmpr[ny % 2])
if idx == nx // 2 - 1:
# Get the centermost as the out terminals
outp = cpl[conn_par]
outn = cpr[conn_par]
if isinstance(outp, WireArray):
raise RuntimeError("WireArray primitives currently not support")
else:
# Bring up to hm_layer, as WireArrays
tidx = self.grid.coord_to_track(hm_layer, outp.ym, mode=RoundMode.NEAREST)
outp = self.connect_bbox_to_tracks(Direction.LOWER, prim_lay_purp, outp,
TrackID(hm_layer, tidx, tr_w_hm))
tidx = self.grid.coord_to_track(hm_layer, outn.ym, mode=RoundMode.NEAREST)
outn = self.connect_bbox_to_tracks(Direction.LOWER, prim_lay_purp, outn,
TrackID(hm_layer, tidx, tr_w_hm))
else:
# Snake together the resistors in series
# Assume the upper ports are all aligned vertically, as are the lower powers
npl = self.get_res_ports(row, idx + 1)
npr = self.get_res_ports(row, nx - 2 - idx)
# Code for handling either BBox or WireArray
if isinstance(npl[conn_par], WireArray):
if npl[conn_par].layer_id == conn_layer:
self.connect_wires([npl[conn_par], cpl[conn_par]])
self.connect_wires([npr[conn_par], cpr[conn_par]])
else:
# TODO: add code to connect lower layers up
raise RuntimeError("Not supported yet")
else:
pairs = [(npl[conn_par], cpl[conn_par]), (npr[conn_par], cpr[conn_par])]
for np, cp in pairs:
# Get a track to connect the ports
tidx = self.grid.coord_to_track(hm_layer, np.ym, mode=RoundMode.NEAREST)
assert tidx == self.grid.coord_to_track(
hm_layer, cp.ym, mode=RoundMode.NEAREST), "Expected tracks to align"
# Connect ports to track
tid = TrackID(hm_layer, tidx, tr_w_hm)
warr1 = self.connect_bbox_to_tracks(Direction.LOWER, prim_lay_purp, np, tid)
warr2 = self.connect_bbox_to_tracks(Direction.LOWER, prim_lay_purp, cp, tid)
self.connect_wires([warr1, warr2])
# connect bias wires to vertical tracks
t0 = self.grid.find_next_track(vm_layer, self.bound_box.xl, half_track=True, mode=RoundMode.GREATER_EQ)
t1 = self.grid.find_next_track(vm_layer, self.bound_box.xh, half_track=True, mode=RoundMode.LESS_EQ)
bp_tid = TrackID(vm_layer, t0 + bias_idx + 1, w_sup_vm)
bn_tid = TrackID(vm_layer, t1 - bias_idx - 1, w_sup_vm)
if isinstance(biasp[0], WireArray):
raise RuntimeError("Currently not supported")
# biasp = self.connect_wires(biasp)
# biasn = self.connect_wires(biasn)
# biasp = self.connect_to_tracks(biasp, bp_tid)
# biasn = self.connect_to_tracks(biasn, bn_tid)
else:
# Find track ids in the middle of the ports and connect up to hm_layer
# Since P and N refer to L/R, we need to separately handle T/B.
ptidx_list = [self.grid.coord_to_track(hm_layer, bp.ym, mode=RoundMode.NEAREST) for bp in biasp]
ntidx_list = [self.grid.coord_to_track(hm_layer, bn.ym, mode=RoundMode.NEAREST) for bn in biasn]
bp_hm = [self.connect_bbox_to_tracks(
Direction.LOWER, prim_lay_purp, bp, TrackID(hm_layer, ptidx, tr_w_hm))
for ptidx, bp in zip(ptidx_list, biasp)]
bn_hm = [self.connect_bbox_to_tracks(
Direction.LOWER, prim_lay_purp, bn, TrackID(hm_layer, ntidx, tr_w_hm))
for ntidx, bn in zip(ntidx_list, biasn)]
# Connect up to vm_layer
biasp = self.connect_to_tracks(bp_hm, bp_tid)
biasn = self.connect_to_tracks(bn_hm, bn_tid)
vdd = self.add_wires(vm_layer, t0, biasp.lower, biasp.upper, num=2, pitch=t1 - t0, width=w_sup_vm)
xl = self.grid.get_wire_bounds(vm_layer, t0 + 2)[1]
xr = self.grid.get_wire_bounds(vm_layer, t1 - 2)[0]
return vdd, biasp, biasn, outp, outn, xl, xr
[docs] def draw_mom_cap(self, nser, xl, xr, cap_spx, cap_spy, cap_margin
) -> Tuple[WireArray, WireArray, WireArray, WireArray, WireArray, WireArray]:
# get port location
ny = self._info.ny
bot_pin = self.get_res_ports(0, 0)[0]
top_pin = self.get_res_ports(ny - 1, 0)[ny % 2]
if isinstance(bot_pin, BBox):
bot_pin_box = bot_pin
top_pin_box = top_pin
else:
bot_pin_box = bot_pin.bound_box
top_pin_box = top_pin.bound_box
bnd_box = self.bound_box
cap_yb_list = [bnd_box.yl + cap_spy, bot_pin_box.yh + cap_spy,
top_pin_box.yh + cap_spy]
cap_yt_list = [bot_pin_box.yl - cap_spy, top_pin_box.yl - cap_spy,
bnd_box.yh - cap_spy]
# draw MOM cap
xc = bnd_box.xm
bot_layer = self.place_info.conn_layer + 1
top_layer = self.place_info.top_layer - 1
num_layer = top_layer - bot_layer + 1
# set bottom parity based on number of resistors to avoid via-to-via spacing errors
if nser % 2 == 0:
bot_par_list = [True, False, True]
else:
bot_par_list = [False, True, False]
spx_le = self.grid.get_line_end_space(bot_layer, 1)
spx_le2 = -(-spx_le // 2)
cap_spx2 = max(cap_spx // 2, spx_le2)
cap_xl_list = [xl + cap_margin, xc + cap_spx2]
cap_xr_list = [xc - cap_spx2, xr - cap_margin]
rects = []
capp_list, capn_list, capr_list = [], [], []
# TODO: clean port flipping code
port_parity = {top_layer: False}
if top_layer != bot_layer + 1:
port_parity[bot_layer + 1] = False
for cap_xl, cap_xr in zip(cap_xl_list, cap_xr_list):
curp_list, curn_list, curr_list = [], [], []
for idx, (cap_yb, cap_yt, bot_par) in enumerate(zip(cap_yb_list, cap_yt_list,
bot_par_list)):
for layer in range(bot_layer, top_layer):
if (layer - bot_layer) % 2 == 0:
port_parity[layer] = bot_par
cap_box = BBox(cap_xl, cap_yb, cap_xr, cap_yt)
try:
if idx == 1:
ports = self.add_mom_cap(cap_box, bot_layer, num_layer,
port_plow=port_parity, cap_wires_list=rects)
# rects.extend(cur_rects[-1])
else:
ports = self.add_mom_cap(cap_box, bot_layer, num_layer, port_plow=port_parity)
except ValueError:
# This is to catch when we can't fit the momcaps
continue
# Top level ports
capp, capn = ports[top_layer]
if len(capp) > 1:
curp_list.append(capp[port_parity[top_layer]])
curn_list.append(capn[not port_parity[top_layer]])
else:
curp_list.append(capp[0])
curn_list.append(capn[0])
# Connections to the resistors
capr = ports[bot_layer + 1][1]
if len(capr) > 1:
curr_list.append(capr[not port_parity[bot_layer + 1]])
else:
curr_list.append(capr[0])
capp_list.append(curp_list)
capn_list.append(curn_list)
capr_list.append(curr_list)
port_parity[top_layer] = True
port_parity[bot_layer + 1] = True
caplp = self.connect_wires(capp_list[0])[0]
caprp = self.connect_wires(capp_list[1])[0]
capln = self.connect_wires(capn_list[0])[0]
caprn = self.connect_wires(capn_list[1])[0]
caplr = self.connect_wires(capr_list[0])[0]
caprr = self.connect_wires(capr_list[1])[0]
return caplp, capln, caprp, caprn, caplr, caprr
[docs]class HighPassArrayCore(ResArrayBase):
"""An row array of RC high-pass filter.
"""
def __init__(self, temp_db: TemplateDB, params: Param, **kwargs: Any) -> None:
ResArrayBase.__init__(self, temp_db, params, **kwargs)
self._sup_name = None
@classmethod
[docs] def get_schematic_class(cls) -> Optional[Module]:
return bag3_analog__high_pass_array
@classmethod
[docs] def get_params_info(cls) -> Dict[str, str]:
return dict(
pinfo='The ResBasePlaceInfo object.',
h_unit='total height, in resolution units.',
narr='Number of high-pass filters.',
sub_type='the substrate type.',
threshold='the substrate threshold flavor.',
nser='number of resistors in series in a branch.',
ndum='number of dummy resistors.',
cap_h_list='List of capacitor heights, in resolution units.',
port_tr_w='port widths, in number of tracks.',
res_type='Resistor intent',
res_options='Configuration dictionary for ResArrayBase.',
cap_spx='Capacitor horizontal separation, in resolution units.',
cap_spy='Capacitor vertical margin, in resolution units.',
show_pins='True to show pins.',
cap_val='Schematic value for analogLib cap',
)
@classmethod
[docs] def get_default_param_values(cls) -> Dict[str, Any]:
return dict(
cap_val=1e-9,
port_tr_w=1,
res_type='standard',
res_options=None,
cap_spx=0,
cap_spy=0,
)
[docs] def draw_layout(self):
h_unit = self.params['h_unit']
narr = self.params['narr']
sub_type = self.params['sub_type']
threshold = self.params['threshold']
nser = self.params['nser']
ndum = self.params['ndum']
port_tr_w = self.params['port_tr_w']
res_type = self.params['res_type']
res_options = self.params['res_options']
cap_spx = self.params['cap_spx']
cap_spy = self.params['cap_spy']
cap_h_list = self.params['cap_h_list']
show_pins = self.params['show_pins']
if nser % 2 != 0:
raise ValueError('This generator only supports even nser.')
if narr > len(cap_h_list):
raise ValueError('cap_h_list should contain at least narr entries')
assert self.params['pinfo']['ny'] == 1, "Only ny=1 supported right now"
assert narr * nser + ndum * 2 == self.params['pinfo']['nx'], "Set nx based on calculation"
# res = self.grid.resolution
# lay_unit = self.grid.layout_unit
# w_unit = int(round(w / lay_unit / res))
#
# if res_options is None:
# my_options = dict(well_end_mode=2)
# else:
# my_options = res_options.copy()
# my_options['well_end_mode'] = 2
# # find resistor length
# info = ResArrayBaseInfo(self.grid, sub_type, threshold, top_layer=top_layer,
# res_type=res_type, ext_dir='y', options=my_options,
# connect_up=True, half_blk_x=half_blk_x, half_blk_y=True)
#
# lmin, lmax = info.get_res_length_bounds()
# bin_iter = BinaryIterator(lmin, lmax, step=2)
# while bin_iter.has_next():
# lcur = bin_iter.get_next()
# htot = info.get_place_info(lcur, w_unit, 1, 1)[3]
# if htot < h_unit:
# bin_iter.save()
# bin_iter.up()
# else:
# bin_iter.down()
# draw resistor
# l_unit = bin_iter.get_last_save()
# nx = 2 * ndum + narr * nser
# self.draw_array(l_unit * lay_unit * res, w, sub_type, threshold, nx=nx, ny=1,
# top_layer=top_layer, res_type=res_type, grid_type=None, ext_dir='y',
# options=my_options, connect_up=True, half_blk_x=half_blk_x,
# half_blk_y=True, min_height=h_unit)
# TODO: add methods to find length?
pinfo = cast(ResBasePlaceInfo,
ResBasePlaceInfo.make_place_info(self.grid, self.params['pinfo']))
# Draw resistor
self.draw_base(pinfo)
conn_layer = self.conn_layer
hm_layer = conn_layer + 1
vm_layer = hm_layer + 1
top_layer = self.top_layer
# Well tap connection
self._sup_name = 'VDD' if \
cast(ResBasePlaceInfo, self.place_info).res_config['sub_type_default'] == 'ntap' else 'VSS'
# get cap settings
bot_layer = self.conn_layer + 2
for lay in range(bot_layer, top_layer + 1):
# TODO: why?
if self.grid.get_direction(lay) == 'x':
cap_spx = max(cap_spx, self.grid.get_line_end_space(lay, 1))
# connect resistors and draw MOM caps
tmp = self._connect_resistors(narr, nser, ndum, cap_spx, port_tr_w, show_pins)
rout_list, cap_x_list = tmp
tmp = self._draw_mom_cap(cap_x_list, bot_layer, top_layer, cap_spy, cap_h_list,
port_tr_w, show_pins)
cout_list, ores_info, cres_info = tmp
# connect bias resistor to cap
for rout, cout in zip(rout_list, cout_list):
self.connect_to_track_wires(rout, cout)
# set schematic parameters
self._sch_params = dict(
narr=narr,
ndum=ndum * 2,
hp_params=dict(
l=pinfo.l_res,
w=pinfo.w_res,
intent=res_type,
nser=nser,
ndum=0, # no dummies per core HPF
res_in_info=cres_info,
res_out_info=ores_info,
sub_name=self._sup_name,
cap_val=self.params['cap_val']
),
)
[docs] def _connect_resistors(self, narr, nser, ndum, cap_spx, port_tr_w, show_pins):
nx = 2 * ndum + narr * nser
hm_layer = self.conn_layer + 1
vm_layer = hm_layer + 1
prim_lp = self.grid.tech_info.get_lay_purp_list(self.conn_layer)[0]
# connect bulk
# If we have bulk, connect bulk
# Assume that the bulk port is on conn_layer and can be simply connected
# to the conn_layer supply lines.
bulk_hm = []
if self.has_substrate_port:
# Hack to check port layer assumption
assert self._unit.get_port('BULK').get_single_layer() == prim_lp[0]
# get_device_port does not return multiple bound boxes. This is work around.
bulk_bbox_list = self._unit.get_port('BULK').get_pins()
bulk_hm = [
TrackID(hm_layer, self.grid.coord_to_track(hm_layer, self.bound_box.yl, mode=RoundMode.GREATER_EQ)),
TrackID(hm_layer, self.grid.coord_to_track(hm_layer, self.bound_box.yh, mode=RoundMode.LESS_EQ)),
]
for yidx in range(self.ny):
for xidx in range(nx):
for bbox in bulk_bbox_list:
bulk_bbox = cast(BBox, bbox).get_transform(self._get_transform(xidx, yidx))
self.connect_bbox_to_tracks(Direction.LOWER, prim_lp, bulk_bbox, bulk_hm[-1],
track_lower=self.bound_box.xl, track_upper=self.bound_box.xh)
self.connect_bbox_to_tracks(Direction.LOWER, prim_lp, bulk_bbox, bulk_hm[0],
track_lower=self.bound_box.xl, track_upper=self.bound_box.xh)
# connect dummies
supl_list = []
supr_list = []
for idx in range(ndum):
# Connect ports to hm, then add to list to get connected to supplies
port_bot = self.get_res_ports(0, idx)
port_bot_hm = self.connect_port_hm(port_bot)
supl_list.extend(port_bot_hm)
port_top = self.get_res_ports(0, nx - 1 - idx)
port_top_hm = self.connect_port_hm(port_top)
supr_list.extend(port_top_hm)
supl_list = self.connect_wires(supl_list)
supr_list = self.connect_wires(supr_list)
sup_vm = self._connect_supplies(supl_list, supr_list)
[self.connect_to_tracks(sup_vm, hm_tid) for hm_tid in bulk_hm]
# get line-end margin so we can know capacitor horizontal spacing
hm_sp_le = self.grid.get_line_end_space(hm_layer, 1)
hm_vext = self.grid.get_via_extensions(Direction.LOWER, hm_layer, 1, 1)[0]
hm_margin = hm_sp_le + hm_vext
if isinstance(port_tr_w, Mapping):
bias_spx = self.grid.get_space(vm_layer, port_tr_w[vm_layer])
else:
bias_spx = self.grid.get_space(vm_layer, port_tr_w)
# get capacitor X interval, connect resistors, and get ports
out_list = []
cap_x_list = []
cap_spx2 = cap_spx // 2
for res_idx in range(narr):
rl_idx = ndum + res_idx * nser
# connect series resistors
for idx in range(nser - 1):
conn_par = idx % 2
rcur_idx = rl_idx + idx
portl = self.connect_port_hm(self.get_res_ports(0, rcur_idx))
portr = self.connect_port_hm(self.get_res_ports(0, rcur_idx + 1))
self.connect_wires([portl[conn_par], portr[conn_par]])
# record ports
rr_idx = rl_idx + nser - 1
xl = self.get_res_bbox(0, rl_idx).xl
xr = self.get_res_bbox(0, rr_idx).xh
if res_idx % 2 == 1:
out_list.append(self.connect_port_hm(self.get_res_ports(0, rl_idx))[1])
bias = self.connect_port_hm(self.get_res_ports(0, rr_idx))[1]
bias_tr = self.grid.find_next_track(vm_layer, xr - hm_margin, half_track=True,
mode=RoundMode.LESS_EQ)
wl = self.grid.get_wire_bounds(vm_layer, bias_tr, width=1)[0]
cap_xl = xl + cap_spx2
cap_xr = min(xr - cap_spx2, wl - bias_spx)
else:
out_list.append(self.connect_port_hm(self.get_res_ports(0, rr_idx))[1])
bias = self.connect_port_hm(self.get_res_ports(0, rl_idx))[1]
bias_tr = self.grid.find_next_track(vm_layer, xl + hm_margin, half_track=True,
mode=RoundMode.GREATER_EQ)
wr = self.grid.get_wire_bounds(vm_layer, bias_tr, width=1)[1]
cap_xl = max(xl + cap_spx2, wr + bias_spx)
cap_xr = xr - cap_spx2
cap_x_list.append((cap_xl, cap_xr))
bias = self.connect_to_tracks(bias, TrackID(vm_layer, bias_tr),
min_len_mode=RoundMode.GREATER_EQ)
self.add_pin('bias<%d>' % res_idx, bias, show=show_pins)
return out_list, cap_x_list
[docs] def _connect_supplies(self, supl_list, supr_list):
vm_layer = self.conn_layer + 2
xm_layer = vm_layer + 1
sup_vm = []
for sup_list, mode, name in ((supl_list, RoundMode.LESS_EQ, f'{self._sup_name}L'),
(supr_list, RoundMode.GREATER_EQ, f'{self._sup_name}R')):
xc = sup_list[0].middle
vm_tr = self.grid.coord_to_track(vm_layer, xc, mode=mode)
sup = self.connect_to_tracks(sup_list, TrackID(vm_layer, vm_tr))
sup_vm.append(sup)
xm_tr = self.grid.coord_to_track(xm_layer, sup.middle)
sup = self.connect_to_tracks(sup, TrackID(xm_layer, xm_tr), min_len_mode=MinLenMode.MIDDLE)
self.add_pin(name, sup, connect=True, label=f'{self._sup_name}:')
return sup_vm
[docs] def _draw_mom_cap(self, cap_x_list, bot_layer, top_layer, cap_spy, cap_h_list,
port_tr_w, show_pins):
# get port location
bnd_box = self.bound_box
cap_yt = bnd_box.yh - cap_spy
# draw MOM cap
num_layer = top_layer - bot_layer + 1
out_list = []
out_res_info = in_res_info = None
for cap_idx, ((cap_xl, cap_xr), cap_h) in enumerate(zip(cap_x_list, cap_h_list)):
cap_yb = max(bnd_box.yl + cap_spy, cap_yt - cap_h)
cap_box = BBox(cap_xl, cap_yb, cap_xr, cap_yt)
# TODO: clean port flipping code
parity = cap_idx % 2
port_par = {}
for layer in range(bot_layer, top_layer + 1):
if layer % 2:
port_par[layer] = bool(parity)
else:
port_par[layer] = False
ports = self.add_mom_cap(cap_box, bot_layer, num_layer,
port_widths={top_layer: port_tr_w, bot_layer: port_tr_w},
port_plow=port_par)
# TODO: document how this works...
warr_in = ports[top_layer][0][0]
out = ports[bot_layer][1][1 - parity]
out_list.append(out)
# draw output metal resistor and port
out_port, out_res_info = self._add_metal_res(out, go_up=True)
self.add_pin('out<%d>' % cap_idx, out_port, show=show_pins)
# draw clock metal resistor and port
in_port, in_res_info = self._add_metal_res(warr_in, go_up=False)
self.add_pin('in<%d>' % cap_idx, in_port, show=show_pins)
# return ports
return out_list, out_res_info, in_res_info
[docs]class HighPassArrayClkCore(TemplateBase):
"""An array of clock RC high-pass filters. Drops down a HPF array and connects all the inputs
together on a horizontal bar.
"""
def __init__(self, temp_db: TemplateDB, params: Param, **kwargs: Any) -> None:
super().__init__(temp_db, params, **kwargs)
self._sup_name = None
@classmethod
[docs] def get_schematic_class(cls) -> Optional[Module]:
return bag3_analog__high_pass_clk
@classmethod
[docs] def get_params_info(cls) -> Dict[str, str]:
return HighPassArrayCore.get_params_info()
@classmethod
[docs] def get_default_param_values(cls) -> Dict[str, Any]:
return HighPassArrayCore.get_default_param_values()
[docs] def draw_layout(self):
# TODO: I'm not sure this is the right wrapper for this...
wrap_params = dict(
cls_name='bag3_analog.layout.highpass.HighPassArrayCore',
params=self.params
)
template: ArrayBaseWrapper = self.new_template(params=wrap_params, temp_cls=ArrayBaseWrapper)
self._sup_name = template.core._sup_name
top_layer = template.core.place_info.top_layer
narr = self.params['narr']
tr_manager = template.core.tr_manager
pidx, nidx, y0 = self._place_clock_wires(template.core, narr, top_layer, tr_manager)
xm_layer = top_layer + 1
xm_w = tr_manager.get_width(xm_layer, 'clk')
loc = Transform(0, y0)
inst = self.add_instance(template, inst_name='XARR', xform=loc)
bnd_box = inst.bound_box.extend(y=0)
self.set_size_from_bound_box(xm_layer, bnd_box, round_up=True)
self.array_box = bnd_box
self.add_cell_boundary(self.bound_box)
# re-export/connect clocks
vssl = inst.get_pin(f'{self._sup_name}L')
vssl = self.extend_wires(vssl, lower=0)
vssr = inst.get_pin(f'{self._sup_name}R')
vssr = self.extend_wires(vssr, upper=bnd_box.xh)
self.add_pin(f'{self._sup_name}L', vssl, label=f'{self._sup_name}:', connect=True)
self.add_pin(f'{self._sup_name}R', vssr, label=f'{self._sup_name}:', connect=True)
clkp_list = []
clkn_list = []
for idx in range(narr):
suf = '<%d>' % idx
self.reexport(inst.get_port('bias' + suf))
self.reexport(inst.get_port('out' + suf))
parity = idx % 4
if parity == 0 or parity == 3:
clkp_list.append(inst.get_pin('in' + suf))
else:
clkn_list.append(inst.get_pin('in' + suf))
clkp, clkn = self.connect_differential_tracks(clkp_list, clkn_list, xm_layer, pidx, nidx,
width=xm_w)
self.add_pin('clkp', clkp)
self.add_pin('clkn', clkn)
self._sch_params = template.sch_params
[docs] def _place_clock_wires(self, template, narr, top_layer, tr_manager):
"""Position the two clock wires below the array. Determine the required y-shift
in the array to keep the clock wires in the 1st quadrant"""
yb_min = template.bound_box.yh
for idx in range(narr):
yb_min = min(yb_min, template.get_port('in<%d>' % idx).get_pins()[0].lower)
xm_layer = top_layer + 1
xm_w = tr_manager.get_width(xm_layer, 'clk')
pidx = self.grid.find_next_track(xm_layer, yb_min, tr_width=xm_w, mode=RoundMode.LESS_EQ)
nidx = tr_manager.get_next_track(xm_layer, pidx, 'clk', 'clk', up=False)
# Version from BAG2. # TODO: Why?
# edge_tr2 = int(round(2 * nidx)) - xm_w
# if edge_tr2 < -1:
# tr_pitch = self.grid.get_track_pitch(xm_layer)
# dy = (edge_tr2 + 1) * tr_pitch // 2
# blk_h = self.grid.get_block_size(top_layer)[1]
# dy = -(-dy // blk_h) * blk_h
# tr_delta = dy / tr_pitch
# pidx += tr_delta
# nidx += tr_delta
# dy = -dy
if nidx < 0:
coord0 = self.grid.track_to_coord(xm_layer, nidx)
tr1 = self.grid.coord_to_track(xm_layer, 0, mode=RoundMode.GREATER)
coord1 = self.grid.track_to_coord(
xm_layer, tr1)
dy = coord1 - coord0
# Quantize to block pitch
blk_h = self.grid.get_block_size(top_layer)[1]
dy = -(-dy // blk_h) * blk_h
nidx = tr1
pidx = tr_manager.get_next_track(xm_layer, nidx, 'clk', 'clk')
else:
dy = 0
return pidx, nidx, dy
[docs]class HighPassColumn(TemplateBase):
"""A column of differential high-pass RC filters.
"""
@classmethod
[docs] def get_schematic_class(cls) -> Optional[Module]:
return bag3_analog__high_pass_column
@classmethod
[docs] def get_params_info(cls) -> Dict[str, str]:
# TODO: can I replace this with HPF core's function?
return dict(
pinfo='The ResBasePlaceInfo object.',
narr='Number of high-pass filters.',
# w='unit resistor width, in meters.',
h_unit='total height, in resolution units.',
# lch='channel length, in meters.',
# ptap_w='NMOS substrate width, in meters/number of fins.',
threshold='the substrate threshold flavor.',
sub_type='the substrate type.',
# top_layer='The top layer ID',
nx_dum='Number of dummies on each side, X direction',
ny_dum='Number of dummies on each side, Y direction',
in_tr_info='Input track info.',
out_tr_info='Output track info.',
vdd_tr_info='Supply track info.',
# tr_widths='Track width dictionary.',
# tr_spaces='Track spacing dictionary.',
res_type='Resistor intent',
res_options='Configuration dictionary for ResArrayBase.',
cap_spx='Capacitor horizontal separation, in resolution units.',
cap_spy='Capacitor vertical space from resistor ports, in resolution units.',
cap_margin='Capacitor space from edge, in resolution units.',
cap_val='Schematic value for analogLib cap',
)
@classmethod
[docs] def get_default_param_values(cls) -> Dict[str, Any]:
return dict(
cap_val=1e-9,
res_type='standard',
sub_type='ptap',
res_options=None,
nx_dum=1,
ny_dum=0,
cap_spx=0,
cap_spy=0,
cap_margin=0,
)
[docs] def draw_layout(self):
# rc_params = dict(w=w, h_unit=h_unit, sub_w=ptap_w, sub_lch=lch, sub_type=sub_type,
# threshold=threshold, top_layer=top_layer, nser=nser, ndum=ndum,
# in_tr_info=in_tr_info, out_tr_info=out_tr_info, bias_idx=0,
# vdd_tr_info=vdd_tr_info, res_type=res_type, res_options=res_options,
# cap_spx=cap_spx, cap_spy=cap_spy, cap_margin=cap_margin, end_mode=12,
# sub_tr_w=sub_tr_w, sub_tids=sub_tids, show_pins=False, fill_dummy=fill_dummy,)
# TODO: is this the best way to wrap stuff?
rc0_params = dict(
cls_name='bag3_analog.layout.highpass.HighPassDiffCore',
params=self.params.copy(),
)
master0: ArrayBaseWrapper = self.new_template(params=rc0_params, temp_cls=ArrayBaseWrapper)
rc1_params = dict(
cls_name='bag3_analog.layout.highpass.HighPassDiffCore',
params=self.params.copy(append={'bias_idx': 1}),
)
master1: ArrayBaseWrapper = self.new_template(params=rc1_params, temp_cls=ArrayBaseWrapper)
# fg_sub = master0.fg_sub
fg_sub = 0 # I think this is gone...
top_layer = master0.core.top_layer
# place instances
if fg_sub > 0:
raise RuntimeError("Currently not supported")
# TODO: support
# end_params = dict(lch=lch, fg=fg_sub, sub_type=sub_type, threshold=threshold,
# top_layer=top_layer, end_mode=0b11, guard_ring_nf=0,
# options=ana_options,)
# end_master = self.new_template(params=end_params, temp_cls=AnalogBaseEnd)
# end_row_box = end_master.array_box
#
# bot_xform = Transform(0, 0)
# bot_inst = self.add_instance(end_master, inst_name='XROWB', xform=bot_xform)
# ycur = end_row_box.top_unit
# ycur, inst_list = self._place_instances(ycur, master0, master1)
# ycur += end_row_box.top_unit
# top_xform = Transform(0, ycur, Orientation.MX)
# top_inst = self.add_instance(end_master, inst_name='XROWT', xform=top_xform)
# self.fill_box = inst_list[0].bound_box.merge(inst_list[-1].bound_box)
# bound_box = bot_inst.bound_box.merge(top_inst.bound_box)
else:
_, inst_list = self._place_instances(0, master0, master1)
self.fill_box = bound_box = inst_list[0].bound_box.merge(inst_list[-1].bound_box)
vdd_list = []
vss_list = []
vdd_vm_list = []
for idx, inst in enumerate(inst_list):
suffix = '<%d>' % idx
for name in inst.port_names_iter():
if name == 'VDD':
vdd_list.extend(inst.port_pins_iter('VDD'))
elif name == 'VSS':
vss_list.extend(inst.port_pins_iter('VSS'))
elif name == 'VDD_vm':
vdd_vm_list.extend(inst.port_pins_iter('VDD_vm'))
else:
self.reexport(inst.get_port(name), net_name=name + suffix)
vdd_vm_list = self.connect_wires(vdd_vm_list)
self.add_pin('VDD', vdd_list, label='VDD', connect=True)
self.add_pin('VSS', vss_list, label='VSS:', connect=True)
self.add_pin('VDD_vm', vdd_vm_list, label='VDD', connect=True)
# set size
self.set_size_from_bound_box(top_layer, bound_box)
# self.array_box = bound_box
# set schematic parameters
self._sch_params = dict(
narr=self.params['narr'],
hp_params=master0.sch_params
)
[docs] def _place_instances(self, ycur, master0, master1):
inst_list = []
h_unit = self.params['h_unit']
h_unit = np.atleast_1d(h_unit)
assert master0.bound_box.yh == master1.bound_box.yh
hp_height = master0.bound_box.yh
assert np.all(h_unit >= hp_height)
for idx in range(self.params['narr']):
if idx % 2 == 0:
master = master0
orient = Orientation.R0
dy = 0
else:
master = master1
orient = Orientation.MX
dy = hp_height
xform = Transform(0, ycur + dy, orient)
inst = self.add_instance(master, inst_name='X%d' % idx, xform=xform)
inst_list.append(inst)
ycur += h_unit[idx % h_unit.size]
return ycur, inst_list
[docs]def merge_all_bboxes(lb: List[BBox]):
ans = BBox.get_invalid_bbox()
for box in lb:
ans = ans.get_merge(box)
return ans