# -*- coding: utf-8 -*-
import numpy as np
import abc
from typing import Sequence, Mapping, Any, Tuple
from bag.typing import PointType
from bag.layout.template import TemplateDB, TemplateBase
from bag.layout.util import BBox
from bag.util.immutable import Param
from pybag.enum import PathStyle
[docs]def round_int(val_f: float) -> int:
return int(np.round(val_f))
[docs]def compute_vertices(n_sides: int, n_turns: int, radius_x: int, radius_y: int, width: int, spacing: int
) -> Sequence[Sequence[PointType]]:
# Compute vertices in anti-clockwise order starting from bottom right.
# In order to get 45 degree turns for octagonal case even when radius_x != radius_y,
# compute all vertices using radius_x and then shift top half based on radius_y
phase_step = 2 * np.pi / n_sides
phase_ini = - np.pi / 2 + phase_step / 2
off_x = radius_x + width // 2
vertices = [[] for _ in range(n_turns)]
for tidx in range(n_turns):
_rad_x = (radius_x - tidx * (width + spacing)) / np.cos(phase_step / 2)
for sidx in range(n_sides):
if n_sides // 4 <= sidx < 3 * n_sides // 4:
off_y = 2 * (radius_y - radius_x)
else:
off_y = 0
_phase = phase_ini + phase_step * sidx
vertices[tidx].append((off_x + round_int(_rad_x * np.cos(_phase)),
off_x + round_int(_rad_x * np.sin(_phase)) + off_y))
return vertices
[docs]class IndTemplate(TemplateBase, abc.ABC):
"""Inductor template with helper methods"""
def __init__(self, temp_db: TemplateDB, params: Param, **kwargs: Any) -> None:
TemplateBase.__init__(self, temp_db, params, **kwargs)
self._actual_bbox = BBox(0, 0, 0, 0)
@property
[docs] def actual_bbox(self) -> BBox:
return self._actual_bbox
[docs] def _draw_turn(self, lay_id: int, width: int, n_sides: int, vertices: Sequence[PointType], start_x: int,
stop_x: int, bridge_xl: int, bridge_xr: int, suf: str) -> Mapping[str, Sequence[PointType]]:
_mid = n_sides // 2
_turn_r = [(start_x, vertices[0][1]), (bridge_xr, vertices[_mid - 1][1])]
_turn_r[1:1] = vertices[:_mid]
if _turn_r[0] == _turn_r[1]:
_turn_r = _turn_r[1:]
_turn_l = [(bridge_xl, vertices[_mid][1]), (stop_x, vertices[-1][1])]
_turn_l[1:1] = vertices[_mid:]
if _turn_l[-1] == _turn_l[-2]:
_turn_l = _turn_l[:-1]
# cannot draw all paths in this layout because of mysterious C++ error.
# Create separate sub layouts with each turn.
path_list = [
dict(lay_id=lay_id, width=width, points=_turn_l, style=PathStyle.extend),
dict(lay_id=lay_id, width=width, points=_turn_r, style=PathStyle.extend),
]
_master: IndLayoutHelper = self.new_template(IndLayoutHelper, params=dict(path_list=path_list))
self.add_instance(_master, inst_name=f'IndTurn_{suf}')
return dict(left=_turn_l, right=_turn_r)
[docs] def _draw_bridge(self, coord_l: PointType, coord_r: PointType, layer_l: int, layer_r: int, layer_bridge: int,
width: int, style: PathStyle = PathStyle.round) -> None:
points = []
wext = int(width // 2 * np.sin(np.pi / 8))
# left
if layer_l == layer_bridge:
points.append(coord_l)
else:
points.append((coord_l[0] - width, coord_l[1]))
# draw via
via_bbox = BBox(coord_l[0] - width - wext, coord_l[1] - width // 2,
coord_l[0] + wext, coord_l[1] + width // 2)
if layer_l > layer_bridge:
bot_lay, top_lay = layer_bridge, layer_l
else:
bot_lay, top_lay = layer_l, layer_bridge
bot_lp = self.grid.tech_info.get_lay_purp_list(bot_lay)[0]
top_lp = self.grid.tech_info.get_lay_purp_list(top_lay)[0]
bot_dir = self.grid.get_direction(bot_lay)
self.add_via(via_bbox, bot_lp, top_lp, bot_dir, extend=False)
# mid
if coord_l[1] != coord_r[1]:
points.extend([(coord_l[0] + width, coord_l[1]), (coord_r[0] - width, coord_r[1])])
# right
if layer_r == layer_bridge:
points.append(coord_r)
else:
points.append((coord_r[0] + width, coord_r[1]))
# draw via
via_bbox = BBox(coord_r[0] - wext, coord_r[1] - width // 2,
coord_r[0] + width + wext, coord_r[1] + width // 2)
if layer_r > layer_bridge:
bot_lay, top_lay = layer_bridge, layer_r
else:
bot_lay, top_lay = layer_r, layer_bridge
bot_lp = self.grid.tech_info.get_lay_purp_list(bot_lay)[0]
top_lp = self.grid.tech_info.get_lay_purp_list(top_lay)[0]
bot_dir = self.grid.get_direction(bot_lay)
self.add_via(via_bbox, bot_lp, top_lp, bot_dir, extend=False)
# draw path
bridge_lp = self.grid.tech_info.get_lay_purp_list(layer_bridge)[0]
self.add_path(bridge_lp, width, points, style, join_style=PathStyle.round)
[docs] def _draw_leads(self, lay_id: int, width: int, term_coords: Sequence[PointType], res1_l: int, res2_l: int,
y_end: int = 0, up: bool = False) -> Tuple[BBox, BBox]:
# TODO: refactor using _draw_lead()
term_ext = width + max(res1_l, res2_l) + 2 * width
# BBox for lead metals
if up:
_lower = term_coords[0][1]
_upper = max(y_end, term_coords[0][1] + term_ext)
m_lower0 = m_lower1 = _lower + width
m_upper0 = _lower + width + res1_l
m_upper1 = _lower + width + res2_l
p_lower = _upper - width
p_upper = _upper
else:
_lower = min(y_end, term_coords[0][1] - term_ext)
_upper = term_coords[0][1]
m_lower0 = _upper - width - res1_l
m_lower1 = _upper - width - res2_l
m_upper0 = m_upper1 = _upper - width
p_lower = _lower
p_upper = _lower + width
_bbox0 = BBox(term_coords[0][0] - width // 2, _lower, term_coords[0][0] + width // 2, _upper)
_bbox1 = BBox(term_coords[1][0] - width // 2, _lower, term_coords[1][0] + width // 2, _upper)
lp = self.grid.tech_info.get_lay_purp_list(lay_id)[0]
self.add_rect(lp, _bbox0)
self.add_rect(lp, _bbox1)
# BBox for res_metal
term0_res_bbox = BBox(_bbox0.xl, m_lower0, _bbox0.xh, m_upper0)
term1_res_bbox = BBox(_bbox1.xl, m_lower1, _bbox1.xh, m_upper1)
self.add_res_metal(lay_id, term0_res_bbox)
self.add_res_metal(lay_id, term1_res_bbox)
# BBox for pins
term0 = BBox(_bbox0.xl, p_lower, _bbox0.xh, p_upper)
term1 = BBox(_bbox1.xl, p_lower, _bbox1.xh, p_upper)
return term0, term1
[docs] def _draw_lead(self, lay_id: int, width: int, term_coord: PointType, res_l: int, y_end: int = 0, up: bool = False
) -> BBox:
term_ext = width + res_l + 2 * width
# BBox for lead metals
if up:
_lower = term_coord[1]
_upper = max(y_end, term_coord[1] + term_ext)
m_lower = _lower + width
m_upper = _lower + width + res_l
p_lower = _upper - width
p_upper = _upper
else:
_lower = min(y_end, term_coord[1] - term_ext)
_upper = term_coord[1]
m_lower = _upper - width - res_l
m_upper = _upper - width
p_lower = _lower
p_upper = _lower + width
_bbox = BBox(term_coord[0] - width // 2, _lower, term_coord[0] + width // 2, _upper)
lp = self.grid.tech_info.get_lay_purp_list(lay_id)[0]
self.add_rect(lp, _bbox)
# BBox for res_metal
term_res_bbox = BBox(_bbox.xl, m_lower, _bbox.xh, m_upper)
self.add_res_metal(lay_id, term_res_bbox)
# BBox for pins
term = BBox(_bbox.xl, p_lower, _bbox.xh, p_upper)
return term
[docs] def _draw_fill(self, n_sides: int, fill_specs: Mapping[str, Any],
core_turn_coords: Sequence[Mapping[str, Sequence[PointType]]], width: int, dx: int, dy: int,
ring_turn_coords: Sequence[PointType], ring_width: int) -> None:
lay_id: int = fill_specs['lay_id']
fill_w: int = fill_specs['fill_w']
fill_sp: int = fill_specs['fill_sp']
inside_ring: bool = fill_specs.get('inside_ring', True)
outside_ring: bool = fill_specs.get('outside_ring', True)
lp = self.grid.tech_info.get_lay_purp_list(lay_id)[0]
w2 = width // 2
rw2 = ring_width // 2
if n_sides == 8:
# R0
# 1-0 5-4
# 2 3
# | |
# | |
# | |
# 3 2
# 4-5 0-1
# Step 1: draw inside ring
if inside_ring:
in_l = core_turn_coords[-1]['left']
in_r = core_turn_coords[-1]['right']
bbox_in = BBox(in_l[2][0] + w2 + fill_sp + dx, in_r[1][1] + w2 + fill_sp + dy,
in_r[2][0] - w2 - fill_sp + dx, in_l[1][1] - w2 - fill_sp + dy)
bbox_in2 = BBox(in_l[1][0] + w2 + dx, in_r[2][1] + w2 + dy, in_r[1][0] - w2 + dx, in_l[2][1] - w2 + dy)
tot_num_x = (bbox_in.w + fill_sp) // (fill_w + fill_sp)
tot_len_x = tot_num_x * (fill_w + fill_sp) - fill_sp
xl = bbox_in.xl + (bbox_in.w - tot_len_x) // 2
tot_num_y = (bbox_in.h + fill_sp) // (fill_w + fill_sp)
tot_len_y = tot_num_y * (fill_w + fill_sp) - fill_sp
yl = bbox_in.yl + (bbox_in.h - tot_len_y) // 2
for idx in range(tot_num_x):
for jdx in range(tot_num_y):
_xl = xl + idx * (fill_w + fill_sp)
_yl = yl + jdx * (fill_w + fill_sp)
if _xl + _yl < bbox_in.xl + bbox_in2.yl:
# lower left
continue
elif _yl + fill_w - _xl > bbox_in2.yh - bbox_in.xl:
# upper left
continue
elif _yl - _xl - fill_w < bbox_in2.yl - bbox_in.xh:
# lower right
continue
elif _xl + _yl + 2 * fill_w > bbox_in.xh + bbox_in2.yh:
# upper right
continue
self.add_rect(lp, BBox(_xl, _yl, _xl + fill_w, _yl + fill_w))
# Step 2: draw outside ring
if outside_ring:
out_l = core_turn_coords[0]['left']
out_r = core_turn_coords[0]['right']
bbox_out2 = BBox(out_l[1][0] - w2 - fill_sp + dx, out_r[2][1] - w2 - fill_sp + dy,
out_r[1][0] + w2 + fill_sp + dx, out_l[2][1] + w2 + fill_sp + dy)
bbox_out = BBox(out_l[2][0] - w2 + dx, out_r[1][1] - w2 + dy,
out_r[2][0] + w2 + dx, out_l[1][1] + w2 + dy)
if ring_turn_coords:
# R0
# 3-----2
# | |
# | |
# 4-5 0-1
rbbox = BBox(ring_turn_coords[3][0] + rw2 + fill_sp, ring_turn_coords[1][1] + rw2 + fill_sp,
ring_turn_coords[1][0] - rw2 - fill_sp, ring_turn_coords[3][1] - rw2 - fill_sp)
else:
rbbox = bbox_out
tot_num_x = (rbbox.w + fill_sp) // (fill_w + fill_sp)
tot_len_x = tot_num_x * (fill_w + fill_sp) - fill_sp
xl = rbbox.xl + (rbbox.w - tot_len_x) // 2
tot_num_y = (rbbox.h + fill_sp) // (fill_w + fill_sp)
tot_len_y = tot_num_y * (fill_w + fill_sp) - fill_sp
yl = rbbox.yl + (rbbox.h - tot_len_y) // 2
for idx in range(tot_num_x):
for jdx in range(tot_num_y):
_xl = xl + idx * (fill_w + fill_sp)
_yl = yl + jdx * (fill_w + fill_sp)
if bbox_out2.xl < _xl < bbox_out2.xh - fill_w and _yl + fill_w < bbox_out.yl - fill_sp:
# keep-out
continue
elif _xl + fill_w < bbox_out.xl - fill_sp:
# left
self.add_rect(lp, BBox(_xl, _yl, _xl + fill_w, _yl + fill_w))
elif _xl > bbox_out.xh + fill_sp:
# right
self.add_rect(lp, BBox(_xl, _yl, _xl + fill_w, _yl + fill_w))
elif _yl > bbox_out.yh + fill_sp:
# top
self.add_rect(lp, BBox(_xl, _yl, _xl + fill_w, _yl + fill_w))
elif _yl + fill_w < bbox_out.yl - fill_sp:
# bottom
self.add_rect(lp, BBox(_xl, _yl, _xl + fill_w, _yl + fill_w))
elif _xl + _yl + 2 * fill_w < bbox_out.xl + bbox_out2.yl:
# lower left
self.add_rect(lp, BBox(_xl, _yl, _xl + fill_w, _yl + fill_w))
elif _yl - _xl - fill_w > bbox_out2.yh - bbox_out.xl:
# upper left
self.add_rect(lp, BBox(_xl, _yl, _xl + fill_w, _yl + fill_w))
elif _yl + fill_w - _xl < bbox_out2.yl - bbox_out.xh:
# lower right
self.add_rect(lp, BBox(_xl, _yl, _xl + fill_w, _yl + fill_w))
elif _xl + _yl > bbox_out.xh + bbox_out2.yh:
# upper right
self.add_rect(lp, BBox(_xl, _yl, _xl + fill_w, _yl + fill_w))
elif n_sides == 4:
# R0
# 1-----0 3-----2
# | |
# | |
# | |
# 2-----3 0-----1
# Step 1: draw inside ring
if inside_ring:
in_l = core_turn_coords[-1]['left']
in_r = core_turn_coords[-1]['right']
bbox_in = BBox(in_l[1][0] + w2 + fill_sp + dx, in_r[1][1] + w2 + fill_sp + dy,
in_r[1][0] - w2 - fill_sp + dx, in_l[1][1] - w2 - fill_sp + dy)
tot_num_x = (bbox_in.w + fill_sp) // (fill_w + fill_sp)
tot_len_x = tot_num_x * (fill_w + fill_sp) - fill_sp
xl = bbox_in.xl + (bbox_in.w - tot_len_x) // 2
tot_num_y = (bbox_in.h + fill_sp) // (fill_w + fill_sp)
tot_len_y = tot_num_y * (fill_w + fill_sp) - fill_sp
yl = bbox_in.yl + (bbox_in.h - tot_len_y) // 2
for idx in range(tot_num_x):
for jdx in range(tot_num_y):
_xl = xl + idx * (fill_w + fill_sp)
_yl = yl + jdx * (fill_w + fill_sp)
self.add_rect(lp, BBox(_xl, _yl, _xl + fill_w, _yl + fill_w))
# Step 2: draw outside ring
if outside_ring:
out_l = core_turn_coords[0]['left']
out_r = core_turn_coords[0]['right']
bbox_out = BBox(out_l[1][0] - w2 - fill_sp + dx, out_r[1][1] - w2 - fill_sp + dy,
out_r[1][0] + w2 + fill_sp + dx, out_l[1][1] + w2 + fill_sp + dy)
if ring_turn_coords:
# R0
# 3-----2
# | |
# | |
# 4-5 0-1
rbbox = BBox(ring_turn_coords[3][0] + rw2 + fill_sp, ring_turn_coords[1][1] + rw2 + fill_sp,
ring_turn_coords[1][0] - rw2 - fill_sp, ring_turn_coords[3][1] - rw2 - fill_sp)
else:
return
tot_num_x = (rbbox.w + fill_sp) // (fill_w + fill_sp)
tot_len_x = tot_num_x * (fill_w + fill_sp) - fill_sp
xl = rbbox.xl + (rbbox.w - tot_len_x) // 2
tot_num_y = (rbbox.h + fill_sp) // (fill_w + fill_sp)
tot_len_y = tot_num_y * (fill_w + fill_sp) - fill_sp
yl = rbbox.yl + (rbbox.h - tot_len_y) // 2
for idx in range(tot_num_x):
for jdx in range(tot_num_y):
_xl = xl + idx * (fill_w + fill_sp)
_yl = yl + jdx * (fill_w + fill_sp)
if bbox_out.xl < _xl < bbox_out.xh - fill_w and _yl + fill_w < bbox_out.yl - fill_sp:
# keep-out
continue
elif _xl + fill_w < bbox_out.xl - fill_sp:
# left
self.add_rect(lp, BBox(_xl, _yl, _xl + fill_w, _yl + fill_w))
elif _xl > bbox_out.xh + fill_sp:
# right
self.add_rect(lp, BBox(_xl, _yl, _xl + fill_w, _yl + fill_w))
elif _yl > bbox_out.yh + fill_sp:
# top
self.add_rect(lp, BBox(_xl, _yl, _xl + fill_w, _yl + fill_w))
elif _yl + fill_w < bbox_out.yl - fill_sp:
# bottom
self.add_rect(lp, BBox(_xl, _yl, _xl + fill_w, _yl + fill_w))
else:
raise NotImplementedError(f'_draw_fill() is not implemented for n_sides={n_sides} yet.')
[docs]class IndLayoutHelper(TemplateBase):
"""Class for drawing various geometries. This is used as a hack because of the mysterious C++ error that happens
while drawing multi turn inductor in the normal way"""
def __init__(self, temp_db: TemplateDB, params: Param, **kwargs: Any) -> None:
TemplateBase.__init__(self, temp_db, params, **kwargs)
@classmethod
[docs] def get_params_info(cls) -> Mapping[str, str]:
return dict(
path_list='List of path specification dictionaries',
)
[docs] def draw_layout(self) -> None:
top_lay_id = -1
# draw paths
path_list: Sequence[Mapping[str, Any]] = self.params['path_list']
for _specs in path_list:
lay_id: int = _specs['lay_id']
lp = self.grid.tech_info.get_lay_purp_list(lay_id)[0]
style: PathStyle = _specs.get('style', PathStyle.round)
self.add_path(lp, _specs['width'], list(_specs['points']), style, join_style=PathStyle.round)
top_lay_id = max(top_lay_id, lay_id)
# set size
self.set_size_from_bound_box(top_lay_id, BBox(0, 0, 10, 10), round_up=True)