def as_region(cell, layname): ''' Mostly a convenience brevity function. If a layer isn't in the layer set, return an empty region instead of crashing ''' try: return pya.Region(cell.shapes(lys[layname])) except KeyError: return pya.Region()
def metal_pedestal(cell, pedestal_layer='wg_full_photo', offset=0, keepout=None): metal_region = pya.Region() for layname in ['m5_wiring', 'm5_gnd', 'gp_photo']: try: metal_region += as_region(cell, layname) except: pass valid_metal = metal_region - fast_sized(as_region(cell, lys.wg_deep), offset / dbu) pedestal_region = fast_sized(valid_metal, offset / dbu) if keepout is not None: if not isinstance(keepout, (list, tuple)): keepout = [keepout] for ko_layer in keepout: pedestal_region -= as_region(cell, ko_layer) cell.shapes(lys[pedestal_layer]).insert(pedestal_region)
def fast_sized(input_region, xsize): # if something goes wrong, you can fall back to regular here by uncommenting if _thread_count is None: return input_region.sized(xsize) else: output_region = pya.Region() tp = pya.TilingProcessor() tp.input('in1', input_region) tp.output('out1', output_region) tp.queue("_output(out1, in1.sized({}))".format(xsize)) tp.tile_size(2000., 2000.) tp.tile_border(2 * xsize, 2 * xsize) tp.threads = _thread_count tp.execute('Sizing job') return output_region
def turbo(input_region, meth_name, meth_args, tile_border=1, job_name='Tiling job'): ''' Speeds things up by tiling. Parameters are determined by _thread_count and _tiles if _thread_count is 1, it does not invoke tile processor at all tile_border is in microns. Recommended that you make it 1.1 * the critical dimension. args is a list. ''' if not isinstance(meth_args, (list, tuple)): meth_args = [meth_args] if _thread_count is None: return getattr(input_region, meth_name)(*meth_args) else: output_region = pya.Region() tp = pya.TilingProcessor() tp.input('in1', input_region) tp.output('out1', output_region) clean_args = list() for arg in meth_args: if arg is True: clean_args.append('true') elif arg is False: clean_args.append('false') elif arg is None: clean_args.append('nil') elif isinstance(arg, (pya.Region, pya.EdgePairs)): tp.input('in2', arg) clean_args.append('in2') else: clean_args.append(str(arg)) job_str = '_output(out1, in1.{}({}))'.format(meth_name, ', '.join(clean_args)) tp.queue(job_str) tp.tile_border(tile_border, tile_border) tp.tiles(_tiles, _tiles) tp.threads = _thread_count tp.execute(job_name) return output_region
def ground_plane(cell, Delta_gp=15.0, points_per_circle=100, air_open=None): Delta_gp /= dbu cell.clear(lys.gp_photo) # Accumulate everything that we don't want to cover in metal gp_exclusion_things = pya.Region() for layname in ['wg_deep', 'wg_deep_photo', 'wg_shallow', 'm1_nwpad', 'm4_ledpad', 'm3_res', 'm5_wiring', 'm2_nw', 'GP_KO']: try: gp_exclusion_things += fast_smoothed(as_region(cell, layname)) except KeyError: pass # Where ground plane is explicitly connected to wires, cut it out of the exclusion region gnd_explicit = as_region(cell, 'm5_gnd') gp_exclusion_tight = gp_exclusion_things - fast_sized(gnd_explicit, Delta_gp) # Inflate the buffer around excluded things and pour gp_exclusion_zone = fast_sized(gp_exclusion_tight, Delta_gp) gp_region = as_region(cell, 'FLOORPLAN') - gp_exclusion_zone # Connect to ground pads gp_region.merge() gp_region = fast_sized(gp_region, 1 / dbu) # kill narrow spaces gp_region = fast_sized(gp_region, -2 / dbu) # kill narrow widths gp_region = fast_sized(gp_region, 1 / dbu) gp_region += as_region(cell, 'm5_gnd') gp_region.round_corners(Delta_gp / 5, Delta_gp / 3, points_per_circle) gp_region = gp_region.smoothed(.001) # avoid some bug in pya gp_region.merge() cell.shapes(lys.gp_photo).insert(gp_region) # Open up to the air if air_open is not None: Delta_air = 5 fp_safe = as_region(cell, 'FLOORPLAN') air_rects = fp_safe - fp_safe.sized(0, -air_open / dbu, 0) air_region = air_rects & gp_region air_region = fast_sized(air_region, -Delta_air / dbu) air_region = fast_sized(air_region, 4 / dbu) # kill narrow spaces air_region = fast_sized(air_region, -8 / dbu) # kill narrow widths air_region = fast_sized(air_region, 4 / dbu) air_region.round_corners(Delta_gp / 5, Delta_gp / 3, points_per_circle) cell.shapes(lys.gp_v5).insert(air_region)
def fast_smoothed(unfiltered_region, deviation=0.1): ''' Removes any points that would change the shape by less than this deviation. This is used to significantly decrease the number of points prior to sizing Note: multicore does not work well with this, but it turns out to be pretty fast ''' # if something goes wrong, you can fall back to regular here by uncommenting return _normal_smoothed(unfiltered_region, deviation) temp_region = unfiltered_region.dup() temp_region.merged_semantics = False output_region = pya.Region() tp = pya.TilingProcessor() tp.input('in1', temp_region) tp.output('out1', output_region) tp.queue("_output(out1, in1.smoothed({}))".format(deviation / dbu)) tp.tile_size(2000., 2000.) tp.tile_border(5 * deviation, 5 * deviation) tp.threads = _thread_count tp.execute('Smoothing job') return output_region
def fast_space(input_region, spacing, angle=90): # if something goes wrong, you can fall back to regular here by uncommenting if _thread_count is None: return input_region.space_check(spacing) else: output_edge_pairs = pya.Region() tp = pya.TilingProcessor() tp.input('in1', input_region) tp.output('out1', output_edge_pairs) # tp.queue("_output(out1, in1.space_check({}))".format(spacing)) tp.queue( "_output(out1, in1.space_check({}, false, nil, {}, nil, nil))". format(spacing, angle)) border = 1.1 * spacing tp.tile_border(border, border) tp.tiles(_tiles, _tiles) # bbox = input_region.bbox() # die_wh = [bbox.width(), bbox.height()] # tile_wh = [dim / _tiles for dim in die_wh] # border_area = 2 * (tile_wh[0] + tile_wh[1]) * border tp.threads = _thread_count tp.execute('Spacing job') return output_edge_pairs
def run_xor(file1, file2, tolerance=1, verbose=False): ''' Returns nothing. Raises a GeometryDifference if there are differences detected ''' l1 = pya.Layout() l1.read(file1) l2 = pya.Layout() l2.read(file2) # Check that same set of layers are present layer_pairs = [] for ll1 in l1.layer_indices(): li1 = l1.get_info(ll1) ll2 = l2.find_layer(l1.get_info(ll1)) if ll2 is None: raise GeometryDifference( "Layer {} of layout {} not present in layout {}.".format( li1, file1, file2)) layer_pairs.append((ll1, ll2)) for ll2 in l2.layer_indices(): li2 = l2.get_info(ll2) ll1 = l1.find_layer(l2.get_info(ll2)) if ll1 is None: raise GeometryDifference( "Layer {} of layout {} not present in layout {}.".format( li2, file2, file1)) # Check that topcells are the same tc1_names = [tc.name for tc in l1.top_cells()] tc2_names = [tc.name for tc in l2.top_cells()] tc1_names.sort() tc2_names.sort() if not tc1_names == tc2_names: raise GeometryDifference( "Missing topcell on one of the layouts, or name differs:\n{}\n{}". format(tc1_names, tc2_names)) topcell_pairs = [] for tc1_n in tc1_names: topcell_pairs.append((l1.cell(tc1_n), l2.cell(tc1_n))) # Check that dbu are the same if (l1.dbu - l2.dbu) > 1e-6: raise GeometryDifference( "Database unit of layout {} ({}) differs from that of layout {} ({})." .format(file1, l1.dbu, file2, l2.dbu)) # Run the difftool diff = False for tc1, tc2 in topcell_pairs: for ll1, ll2 in layer_pairs: r1 = pya.Region(tc1.begin_shapes_rec(ll1)) r2 = pya.Region(tc2.begin_shapes_rec(ll2)) rxor = r1 ^ r2 if tolerance > 0: rxor.size(-tolerance) if not rxor.is_empty(): diff = True if verbose: print("{} differences found in {} on layer {}.".format( rxor.size(), tc1.name, l1.get_info(ll1))) else: if verbose: print("No differences found in {} on layer {}.".format( tc1.name, l1.get_info(ll1))) if diff: fn_abgd = [] for fn in [file1, file2]: head, tail = os.path.split(fn) abgd = os.path.join(os.path.basename(head), tail) fn_abgd.append(abgd) raise GeometryDifference( "Differences found between layouts {} and {}".format(*fn_abgd))