def get_fill_size(self, top_layer: int, fill_config: FillConfigType, *, include_private: bool = False, half_blk_x: bool = True, half_blk_y: bool = True) -> Tuple[int, int]: """Returns unit block size given the top routing layer and power fill configuration. Parameters ---------- top_layer : int the top layer ID. fill_config : Dict[int, Tuple[int, int, int, int]] the fill configuration dictionary. include_private : bool True to include private layers in block size calculation. half_blk_x : bool True to allow half-block widths. half_blk_y : bool True to allow half-block heights. Returns ------- block_width : int the block width in resolution units. block_height : int the block height in resolution units. """ blk_w, blk_h = self.get_block_size(top_layer, include_private=include_private, half_blk_x=half_blk_x, half_blk_y=half_blk_y) dim_list = [[blk_w], [blk_h]] for lay, (tr_w, tr_sp, _, _) in fill_config.items(): if lay <= top_layer: cur_pitch = self.get_track_pitch(lay) cur_dim = (tr_w + tr_sp) * cur_pitch * 2 dim_list[1 - self.get_direction(lay).value].append(cur_dim) blk_w = lcm(dim_list[0]) blk_h = lcm(dim_list[1]) return blk_w, blk_h
def _get_row_specs(self, row_types, row_orientations, row_thresholds, row_kwargs, num_g_tracks, num_gb_tracks, num_ds_tracks): lch = self._laygo_info.lch lch_unit = int( round(lch / self.grid.layout_unit / self.grid.resolution)) w_sub = self._laygo_info['w_sub'] w_n = self._laygo_info['w_n'] w_p = self._laygo_info['w_p'] min_sub_tracks = self._laygo_info['min_sub_tracks'] min_n_tracks = self._laygo_info['min_n_tracks'] min_p_tracks = self._laygo_info['min_p_tracks'] mos_pitch = self._laygo_info.mos_pitch row_specs = [] for row_type, row_orient, row_thres, kwargs, ng, ngb, nds in \ zip(row_types, row_orientations, row_thresholds, row_kwargs, num_g_tracks, num_gb_tracks, num_ds_tracks): # get information dictionary if row_type == 'nch': mos_info = self._tech_cls.get_laygo_mos_info( lch_unit, w_n, row_type, row_thres, 'general', **kwargs) min_tracks = min_n_tracks elif row_type == 'pch': mos_info = self._tech_cls.get_laygo_mos_info( lch_unit, w_p, row_type, row_thres, 'general', **kwargs) min_tracks = min_p_tracks elif row_type == 'ptap': mos_info = self._tech_cls.get_laygo_sub_info( lch_unit, w_sub, row_type, row_thres, **kwargs) min_tracks = min_sub_tracks elif row_type == 'ntap': mos_info = self._tech_cls.get_laygo_sub_info( lch_unit, w_sub, row_type, row_thres, **kwargs) min_tracks = min_sub_tracks else: raise ValueError('Unknown row type: %s' % row_type) row_pitch = min_row_height = mos_pitch for layer, num_tr in min_tracks: tr_pitch = self.grid.get_track_pitch(layer, unit_mode=True) min_row_height = max(min_row_height, num_tr * tr_pitch) row_pitch = lcm([row_pitch, tr_pitch]) row_specs.append((row_type, row_orient, mos_info, min_row_height, row_pitch, (ng, ngb, nds))) return row_specs
def get_mos_base_end_info(self, pinfo: MOSBasePlaceInfo, blk_pitch: int) -> MOSBaseEndInfo: blk_pitch = lcm([self.blk_h_pitch, blk_pitch]) bot_rpinfo = pinfo.get_row_place_info(0) top_rpinfo = pinfo.get_row_place_info(-1) h_ext_bot = bot_rpinfo.yb_blk - bot_rpinfo.yb h_ext_top = top_rpinfo.yt - top_rpinfo.yt_blk h_end_min = self.end_h_min h_mos_end_bot, h_blk_bot = _get_end_block_info(h_ext_bot, h_end_min, blk_pitch) h_mos_end_top, h_blk_top = _get_end_block_info(h_ext_top, h_end_min, blk_pitch) return MOSBaseEndInfo((h_mos_end_bot, h_mos_end_top), (h_blk_bot, h_blk_top))
def get_block_pitch(cls, grid, top_layer, **kwargs): integ_htr = kwargs.get('integ_htr', False) if top_layer is not None: blk_pitch = grid.get_block_size(top_layer, unit_mode=True)[1] if integ_htr: hm_layer = top_layer while grid.get_direction(hm_layer) != 'x': hm_layer -= 1 blk_pitch = lcm([ blk_pitch, grid.get_track_pitch(hm_layer, unit_mode=True) ]) else: blk_pitch = 1 return blk_pitch
def get_block_size(cls, grid, route_layer, bias_config, nwire, width=1, space_sig=0): # type: (RoutingGrid, int, Dict[int, Tuple[int, ...]], int, int, int) -> Tuple[int, int] bot_layer = route_layer - 1 top_layer = route_layer + 1 route_dir = grid.get_direction(route_layer) is_horiz = route_dir == 'x' # calculate dimension bot_pitch2 = grid.get_track_pitch(bot_layer, unit_mode=True) // 2 route_pitch2 = grid.get_track_pitch(route_layer, unit_mode=True) // 2 top_pitch2 = grid.get_track_pitch(top_layer, unit_mode=True) // 2 bot_np2, bot_w, bot_spe2, _ = bias_config[bot_layer] route_np2, route_w, route_spe2, _ = bias_config[route_layer] top_np2, top_w, top_spe2, _ = bias_config[top_layer] bot_unit = bot_np2 * bot_pitch2 route_unit = route_np2 * route_pitch2 top_unit = top_np2 * top_pitch2 dim_par = lcm([bot_unit, top_unit]) tr_manager = TrackManager(grid, {'sig': { route_layer: width }}, {('sig', ''): { route_layer: space_sig }}, half_space=True) tmp = [route_w] nwire = -(-nwire // 2) * 2 route_list = list(chain(tmp, repeat('sig', nwire), tmp)) _, locs = tr_manager.place_wires(route_layer, route_list) num_htr = int(2 * (locs[nwire + 1] - locs[0])) + 2 * route_spe2 dim_perp = -(-(num_htr * route_pitch2) // route_unit) * route_unit if is_horiz: return dim_par, dim_perp else: return dim_perp, dim_par
def get_sub_yloc_info(self, lch_unit, w, **kwargs): # type: (int, int, **kwargs) -> Dict[str, Any] """Get substrate layout information. Layout is quite simple. We use M0PO to short adjacent S/D together, so dummies can be connected using only M2 or below. Strategy: 1. Find bottom M0_PO and bottom OD coordinates from spacing rules. #. Find template top coordinate by enforcing symmetry around OD center. #. Round up template height to blk_pitch, then recenter OD. #. make sure MD/M1 are centered on OD. """ blk_pitch = kwargs['blk_pitch'] mos_constants = self.get_mos_tech_constants(lch_unit) fin_p = mos_constants['mos_pitch'] od_spy = mos_constants['od_spy'] mp_cpo_sp_sub = mos_constants['mp_cpo_sp_sub'] mp_h_sub = mos_constants['mp_h_sub'] mp_spy_sub = mos_constants['mp_spy_sub'] mp_md_sp_sub = mos_constants['mp_md_sp_sub'] cpo_h = mos_constants['cpo_h'] md_h_min = mos_constants['md_h_min'] md_od_exty = mos_constants['md_od_exty'] md_spy = mos_constants['md_spy'] # compute gate/drain connection parameters g_conn_info = self.get_conn_drc_info(lch_unit, 'g') g_m1_sple = g_conn_info[1]['sp_le'] d_conn_info = self.get_conn_drc_info(lch_unit, 'd') d_m3_sple = d_conn_info[3]['sp_le'] od_h = self.get_od_h(lch_unit, w) md_h = max(od_h + 2 * md_od_exty, md_h_min) # figure out Y coordinate of bottom CPO cpo_bot_yt = cpo_h // 2 # find bottom M0_PO coordinate mp_yb = max(mp_spy_sub // 2, cpo_bot_yt + mp_cpo_sp_sub) mp_yt = mp_yb + mp_h_sub # find first-pass OD coordinate od_yb = mp_yt + mp_md_sp_sub + md_h // 2 - od_h // 2 od_yb = self.snap_od_edge(lch_unit, od_yb, False, True) od_yt = od_yb + od_h cpo_top_yc = od_yt + od_yb # fix substrate height quantization, then recenter OD location blk_pitch = lcm([blk_pitch, fin_p]) blk_h = -(-cpo_top_yc // blk_pitch) * blk_pitch cpo_top_yc = blk_h od_yb = blk_h // 2 - od_h // 2 od_yb = self.snap_od_edge(lch_unit, od_yb, False, True) od_yt = od_yb + od_h od_yc = (od_yb + od_yt) // 2 # find MD Y coordinates md_yb = od_yc - md_h // 2 md_yt = md_yb + md_h blk_yb, blk_yt = 0, cpo_top_yc conn_info = self.get_conn_yloc_info(lch_unit, (od_yb, od_yt), (md_yb, md_yt), True) m1_yb, m1_yt = conn_info['d_y_list'][0] m3_yb, m3_yt = conn_info['d_y_list'][2] return dict( blk=(blk_yb, blk_yt), po=(blk_yb + cpo_h // 2, blk_yt - cpo_h // 2), od=(od_yb, od_yt), md=(md_yb, md_yt), top_margins=dict(od=(blk_yt - od_yt, od_spy), md=(blk_yt - md_yt, md_spy), m1=(blk_yt - m1_yt, g_m1_sple), m3=(blk_yt - m3_yt, d_m3_sple)), bot_margins=dict( od=(od_yb - blk_yb, od_spy), md=(md_yb - blk_yb, md_spy), m1=(m1_yb - blk_yb, g_m1_sple), m3=(m3_yb - blk_yb, d_m3_sple), ), fill_info={}, g_conn_y=(m3_yb, m3_yt), d_conn_y=(m3_yb, m3_yt), )
def get_laygo_sub_yloc_info(self, lch_unit, w, **kwargs): # type: (int, float, **kwargs) -> Dict[str, Any] dnw_mode = kwargs.get('dnw_mode', '') blk_pitch = kwargs.get('blk_pitch', 1) mos_pitch = self.get_mos_pitch(unit_mode=True) md_min_len = self.get_md_min_len(lch_unit) mos_constants = self.get_mos_tech_constants(lch_unit) od_spy = mos_constants['od_spy'] imp_od_ency = mos_constants['imp_od_ency'] po_spy = mos_constants['po_spy'] d_via_info = mos_constants['laygo_d_via'] nw_dnw_ovl = mos_constants['nw_dnw_ovl'] nw_dnw_ext = mos_constants['nw_dnw_ext'] sub_m1_enc_le = mos_constants['sub_m1_enc_le'] layout_unit = self.config['layout_unit'] res = self.res od_h = int(round(w / layout_unit / (2 * res))) * 2 # step 0: figure out implant/OD enclosure if dnw_mode: imp_od_ency = max(imp_od_ency, (nw_dnw_ovl + nw_dnw_ext - od_h) // 2) # step 1: find OD coordinate od_yb = imp_od_ency od_yt = od_yb + od_h blk_yt = od_yt + imp_od_ency # fix substrate height quantization, then recenter OD location blk_pitch = lcm([blk_pitch, mos_pitch]) blk_yt = -(-blk_yt // blk_pitch) * blk_pitch od_yb = (blk_yt - od_h) // 2 od_yt = od_yb + od_h od_yc = (od_yb + od_yt) // 2 # step 2: find metal height drc_info = self.get_conn_drc_info(lch_unit, 'd', is_laygo=True) mx_spy = max((info['sp_le'] for info in drc_info.values())) d_v0_h = d_via_info['dim'][0][1] d_v0_sp = d_via_info['sp'][0] d_v0_od_ency = d_via_info['bot_enc_le'][0] d_v0_n = (od_h - 2 * d_v0_od_ency + d_v0_sp) // (d_v0_h + d_v0_sp) d_v0_arrh = d_v0_n * (d_v0_h + d_v0_sp) - d_v0_sp #mx_h = max(md_min_len, d_v0_arrh + 2 * sub_m1_enc_le) mx_h = max(md_min_len, d_v0_arrh + 2 * sub_m1_enc_le, od_h) d_mx_yb = od_yc - mx_h // 2 d_mx_yt = d_mx_yb + mx_h mx_y = (d_mx_yb, d_mx_yt) return dict( blk=(0, blk_yt), po=(od_yb, od_yb), od=(od_yb, od_yt), top_margins=dict( od=(blk_yt - od_yt, od_spy), po=(blk_yt, po_spy), #mx=(blk_yt - d_mx_yt, mx_spy), m1=(blk_yt - d_mx_yt, mx_spy), ), bot_margins=dict( od=(od_yb, od_spy), po=(blk_yt, po_spy), #mx=(d_mx_yb, mx_spy), m1=(d_mx_yb, mx_spy), ), fill_info={}, g_conn_y=mx_y, d_conn_y=mx_y, )
def get_core_track_info( cls, # type: ResTech grid, # type: RoutingGrid min_tracks, # type: Tuple[int, ...] em_specs # type: Dict[str, Any] ): # type: (...) -> Tuple[List[int], List[int], Tuple[int, int], Tuple[int, int]] """Calculate resistor core size/track information based on given specs. This method calculate the track width/spacing on each routing layer from EM specifications, then compute minimum width/height and block pitch of resistor blocks from given constraints. Parameters ---------- grid : RoutingGrid the RoutingGrid object. min_tracks : List[int] minimum number of tracks on each layer. em_specs : Dict[str, Any] EM specification dictionary. Returns ------- track_widths : List[int] the track width on each layer that satisfies EM specs. track_spaces : List[int] the track space on each layer. min_size : Tuple[int, int] a tuple of minimum width and height of the core in resolution units. blk_pitch : Tuple[int, int] a tuple of width and height pitch of the core in resolution units. """ track_widths = [] track_spaces = [] prev_width = -1 min_w = min_h = 0 cur_layer = cls.get_bot_layer() for min_num_tr in min_tracks: tr_w, tr_sp = grid.get_track_info(cur_layer, unit_mode=True) cur_width = grid.get_min_track_width(cur_layer, bot_w=prev_width, unit_mode=True, **em_specs) cur_space = grid.get_num_space_tracks(cur_layer, cur_width) track_widths.append(cur_width) track_spaces.append(cur_space) cur_width_lay = cur_width * tr_w + (cur_width - 1) * tr_sp cur_space_lay = (cur_space + 1) * tr_sp + cur_space * tr_w min_dim = min_num_tr * (cur_width_lay + cur_space_lay) if grid.get_direction(cur_layer) == 'x': min_h = max(min_h, min_dim) else: min_w = max(min_w, min_dim) cur_layer += 1 prev_width = cur_width_lay cur_layer -= 1 wblk, hblk = grid.get_block_size(cur_layer, unit_mode=True) rwblk, rhblk = cls.get_block_pitch() wblk = lcm([wblk, rwblk]) hblk = lcm([hblk, rhblk]) min_w = -(-min_w // wblk) * wblk min_h = -(-min_h // hblk) * hblk return track_widths, track_spaces, (min_w, min_h), (wblk, hblk)
def get_core_track_info( self, # type: ResTech grid, # type: RoutingGrid min_tracks, # type: Tuple[int, ...] em_specs, # type: Dict[str, Any] connect_up=False, # type: bool ): # type: (...) -> Tuple[List[int], List[Union[int, float]], Tuple[int, int], Tuple[int, int]] """Calculate resistor core size/track information based on given specs. This method calculate the track width/spacing on each routing layer from EM specifications, then compute minimum width/height and block pitch of resistor blocks from given constraints. Parameters ---------- grid : RoutingGrid the RoutingGrid object. min_tracks : Tuple[int, ...] minimum number of tracks on each layer. em_specs : Dict[str, Any] EM specification dictionary. connect_up : bool True if the last used layer needs to be able to connect to the layer above. This options will make sure that the width of the last track is wide enough to support the inter-layer via. Returns ------- track_widths : List[int] the track width on each layer that satisfies EM specs. track_spaces : List[Union[int, float]] the track space on each layer. min_size : Tuple[int, int] a tuple of minimum width and height of the core in resolution units. blk_pitch : Tuple[int, int] a tuple of width and height pitch of the core in resolution units. """ track_widths = [] track_spaces = [] prev_width = -1 min_w = min_h = 0 cur_layer = self.get_bot_layer() for idx, min_num_tr in enumerate(min_tracks): # make sure that current layer can connect to next layer if idx < len(min_tracks) - 1 or connect_up: top_tr_w = grid.get_min_track_width(cur_layer + 1, unit_mode=True, **em_specs) top_w = grid.get_track_width(cur_layer + 1, top_tr_w, unit_mode=True) else: top_w = -1 tr_p = grid.get_track_pitch(cur_layer, unit_mode=True) cur_width = grid.get_min_track_width(cur_layer, bot_w=prev_width, top_w=top_w, unit_mode=True, **em_specs) cur_space = grid.get_num_space_tracks(cur_layer, cur_width, half_space=True) track_widths.append(cur_width) track_spaces.append(cur_space) cur_ntr = min_num_tr * (cur_width + cur_space) if isinstance(cur_space, float): cur_ntr += 0.5 min_dim = int(round(tr_p * cur_ntr)) if grid.get_direction(cur_layer) == 'x': min_h = max(min_h, min_dim) else: min_w = max(min_w, min_dim) prev_width = grid.get_track_width(cur_layer, cur_width, unit_mode=True) cur_layer += 1 # get block size wblk, hblk = grid.get_block_size(cur_layer - 1, unit_mode=True, include_private=True, half_blk_x=False, half_blk_y=False) wblk_drc, hblk_drc = self.get_block_pitch() wblk = lcm([wblk, wblk_drc]) hblk = lcm([hblk, hblk_drc]) min_w = -(-min_w // wblk) * wblk min_h = -(-min_h // hblk) * hblk return track_widths, track_spaces, (min_w, min_h), (wblk, hblk)
def draw_layout(self) -> None: pinfo = MOSBasePlaceInfo.make_place_info(self.grid, self.params['pinfo']) self.draw_base(pinfo) se_params: Param = self.params['se_params'] flop_params: Param = self.params['flop_params'] inv_params: Param = self.params['inv_params'] vm_pitch: HalfInt = HalfInt.convert(self.params['vm_pitch']) # create masters flop_pinfo = self.get_draw_base_sub_pattern(2, 4) flop_params = flop_params.copy( append=dict(pinfo=flop_pinfo, out_pitch=vm_pitch)) se_params = se_params.copy(append=dict(pinfo=self.get_tile_pinfo(0), vertical_out=False, vertical_in=False)) inv_params = inv_params.copy( append=dict(pinfo=self.get_tile_pinfo(2), ridx_n=0, ridx_p=-1)) flop_master = self.new_template(FlopStrongArm, params=flop_params) se_master = self.new_template(SingleToDiff, params=se_params) inv_master = self.new_template(InvCore, params=inv_params) # floorplanning tr_manager = self.tr_manager hm_layer = self.conn_layer + 1 vm_layer = hm_layer + 1 xm_layer = vm_layer + 1 vm_w = tr_manager.get_width(vm_layer, 'sig') vm_w_hs = tr_manager.get_width(vm_layer, 'sig_hs') # get flop column quantization sd_pitch = self.sd_pitch vm_coord_pitch = int(vm_pitch * self.grid.get_track_pitch(vm_layer)) sep_half = max(-(-self.min_sep_col // 2), -(-vm_coord_pitch // sd_pitch)) blk_ncol = lcm([sd_pitch, vm_coord_pitch]) // sd_pitch sep_ncol = self.min_sep_col flop_ncol2 = flop_master.num_cols // 2 se_col = sep_half center_col = sep_half + inv_master.num_cols + sep_ncol + flop_ncol2 center_col = -(-center_col // blk_ncol) * blk_ncol cur_col = center_col - flop_ncol2 if (cur_col & 1) != (se_col & 1): se_col += 1 se = self.add_tile(se_master, 0, se_col) inv = self.add_tile(inv_master, 2, cur_col - sep_ncol, flip_lr=True) flop = self.add_tile(flop_master, 2, cur_col) cur_col += flop_master.num_cols + self.sub_sep_col // 2 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} sup_info = self.get_supply_column_info(xm_layer) for tile_idx in range(self.num_tile_rows): self.add_supply_column(sup_info, cur_col, vdd_table, vss_table, ridx_p=-1, ridx_n=0, tile_idx=tile_idx, flip_lr=False) self.set_mos_size() # connections # supplies for lay in range(hm_layer, xm_layer + 1, 2): vss = vss_table[lay] vdd = vdd_table[lay] if lay == hm_layer: for inst in [inv, flop, se]: vdd.extend(inst.get_all_port_pins('VDD')) vss.extend(inst.get_all_port_pins('VSS')) vdd = self.connect_wires(vdd) vss = self.connect_wires(vss) self.add_pin(f'VDD_{lay}', vdd, hide=True) self.add_pin(f'VSS_{lay}', vss, hide=True) inp = flop.get_pin('inp') inn = flop.get_pin('inn') loc_list = tr_manager.place_wires(vm_layer, ['sig_hs'] * 2, center_coord=inp.middle)[1] inp, inn = self.connect_differential_tracks(inp, inn, vm_layer, loc_list[0], loc_list[1], width=vm_w_hs) self.add_pin('sa_inp', inp) self.add_pin('sa_inn', inn) in_vm_ref = self.grid.coord_to_track(vm_layer, 0) in_vm_tidx = tr_manager.get_next_track(vm_layer, in_vm_ref, 'sig', 'sig', up=True) vm_phtr = vm_pitch.dbl_value in_vm_dhtr = -(-(in_vm_tidx - in_vm_ref).dbl_value // vm_phtr) * vm_phtr in_vm_tidx = in_vm_ref + HalfInt(in_vm_dhtr) in_warr = self.connect_to_tracks(se.get_all_port_pins('in'), TrackID(vm_layer, in_vm_tidx, width=vm_w), track_lower=0) self.add_pin('in', in_warr) out = flop.get_pin('outp') self.add_pin('out', self.extend_wires(out, upper=self.bound_box.yh)) self.reexport(flop.get_port('clkl'), net_name='clk', hide=False) self.reexport(flop.get_port('rstlb')) self.reexport(se.get_port('outp'), net_name='midp') self.reexport(se.get_port('outn'), net_name='midn') self.reexport(inv.get_port('in'), net_name='dum') self.sch_params = dict( se_params=se_master.sch_params, flop_params=flop_master.sch_params, inv_params=inv_master.sch_params, )
def make_wire_specs(cls, conn_layer: int, top_layer: int, tr_manager: TrackManager, wire_specs: Mapping[int, Any], min_size: Tuple[int, int] = (1, 1), blk_pitch: Tuple[int, int] = (1, 1), align_default: Alignment = Alignment.LOWER_COMPACT, ptype_default: str = '') -> WireSpecs: """Read wire specifications from a dictionary. WireSpec dictionary format: WireSpec = Dict[int, WireData] WireData = Union[WireGraph, {data=WireGraph, align=Alignment, shared=List[str]}] WireGraph = Union[WireGroup, List[WireGroup]] WireGroup = Union[WireList, {wires=WireList, align=Alignment}] WireList = List[Wire] Wire = Union[name, (name, placement_type), (name, placement_type, wire_type)] keys of WireSpec is delta layer from conn_layer, so key of 1 is the same as conn_layer + 1. Parameters ---------- conn_layer : int the connection layer ID. top_layer : int the top layer, used to compute block quantization. tr_manager : TrackManager the TrackManager instance. wire_specs : Mapping[int, Any] the wire specification dictionary to parse. min_size : Tuple[int, int] minimum width/height. blk_pitch : Tuple[int, int] width/height quantization on top of routing grid quantization. align_default : Alignment default alignment enum. ptype_default : str default placement type. Returns ------- wire_specs : WireSpecs the wire specification dataclass. """ w_min, h_min = min_size blk_w_res, blk_h_res = blk_pitch grid = tr_manager.grid half_blk_x = half_blk_y = True graph_list = [] for delta_layer, wire_data in wire_specs.items(): cur_layer = conn_layer + delta_layer wd = WireData.make_wire_data(wire_data, align_default, ptype_default) cur_graph = WireGraph.make_wire_graph(cur_layer, tr_manager, wd) cur_graph.place_compact(cur_layer, tr_manager) graph_list.append((cur_layer, cur_graph)) if grid.is_horizontal(cur_layer): half_blk_y = half_blk_y and not cur_graph.has_center h_min = max(h_min, cur_graph.upper) else: half_blk_x = half_blk_x and not cur_graph.has_center w_min = max(w_min, cur_graph.upper) blk_w, blk_h = grid.get_block_size(top_layer, half_blk_x=half_blk_x, half_blk_y=half_blk_y) blk_w = lcm([blk_w, blk_w_res]) blk_h = lcm([blk_h, blk_h_res]) w_min = -(-w_min // blk_w) * blk_w h_min = -(-h_min // blk_h) * blk_h return WireSpecs((w_min, h_min), (blk_w, blk_h), graph_list)