diff --git a/klayout_dot_config/python/SiEPIC/scripts.py b/klayout_dot_config/python/SiEPIC/scripts.py index 63609255..a30a1c0a 100644 --- a/klayout_dot_config/python/SiEPIC/scripts.py +++ b/klayout_dot_config/python/SiEPIC/scripts.py @@ -3089,22 +3089,34 @@ def layout_diff(cell1, cell2, tol = 1, verbose=True): Based on https://github.com/atait/lytest ''' - if not cell1.layout() == cell2.layout(): - raise Exception ('SiEPIC.scripts.layout_diff is only implement for cells in the same layout.') + # Get a list of the layers + layers = [] + layout1 = cell1.layout() + layout2 = cell2.layout() + if layout1 == layout2: + # cells from the same layout + for li in layout1.layer_indices(): + layers.append ( (li,li) ) + else: + # cells from different layouts + #raise Exception ('SiEPIC.scripts.layout_diff is only implement for cells in the same layout.') + for ll1 in layout1.layer_indices(): + li1 = layout1.get_info(ll1) + ll2 = layout2.find_layer(layout1.get_info(ll1)) + if ll2 is None: + raise Exception( + f"Layer {li1} in cell1 is not present in cell2." + ) + + layers.append((ll1, ll2)) # Count the differences diff_count = 0 - # Get a list of the layers - layers = [] - layout = cell1.layout() - for li in layout.layer_indices(): - layers.append ( li ) - # Do geometry checks on each layer - for li in layers: - r1 = pya.Region(cell1.begin_shapes_rec(li)) - r2 = pya.Region(cell2.begin_shapes_rec(li)) + for li1,li2 in layers: + r1 = pya.Region(cell1.begin_shapes_rec(li1)) + r2 = pya.Region(cell2.begin_shapes_rec(li2)) rxor = r1 ^ r2 @@ -3115,22 +3127,31 @@ def layout_diff(cell1, cell2, tol = 1, verbose=True): diff_count += rxor.size() if verbose: print( - f" - SiEPIC.scripts.layout_diff: {rxor.size()} differences found in {cell1.name} on layer {layout.get_info(li)}." + f" - SiEPIC.scripts.layout_diff: {rxor.size()} differences found in {cell1.name} on layer {layout1.get_info(li1)}." ) + print(r1) + print(r2) + print(rxor) return diff_count -def replace_cell(layout, cell_x_name, cell_y_name, cell_y_file=None, cell_y_library=None, Exact = True, RequiredCharacter = '$', run_layout_diff = True, debug = False): +def replace_cell(layout, cell_x_name = None, cell_y_name=None, cell_y_file=None, cell_y_library=None, cell_ref_bb = None, Exact = True, RequiredCharacter = '$', run_layout_diff = True, debug = False): ''' SiEPIC-Tools: scripts.replace_cell Search and replace: cell_x with cell_y useful for blackbox IP cell replacement + - load layout containing cell_y_name from cell_y_file or cell_y_library - replace all cell_x_name* instances with cell_y - Exact = True: the cell name must match exactly = False: the cell_y_name appears at the beginning of the cells to be replaced and RequiredCharacter appears directly after, e.g,. '$' as KLayout appends during merging (exact match is still included) + run_layout_diff = True: + perform an xor with the black box cell in the layout, versus the original (reference) black box + requires cell_ref_bb + cell_ref_bb: the black box cell, which will be compared with the cell_x + check_bbox = True: make sure the bounding box for the two cells are the same Black box True geometry Basename_BB, Basename_BB* YES: Basename @@ -3144,6 +3165,18 @@ def replace_cell(layout, cell_x_name, cell_y_name, cell_y_file=None, cell_y_libr log = '' log += "- cell replacement for: %s, with cell %s (%s or %s)\n" % (cell_x_name, cell_y_name, cell_y_file, cell_y_library) + # Find the cell name from the cell_ref_bb + if not cell_x_name: + if cell_ref_bb: + cell_x_name = cell_ref_bb.name + else: + raise Exception ('missing replacement cell name') + + # Make sure we can run the layout diff check. + if run_layout_diff: + if not cell_ref_bb: + raise Exception ('missing reference black box cell, required for layout diff check') + # Find the cells that need replacement (cell_x) # find cell name exactly matching cell_x_name cells_x = [layout.cell(cell_x_name)] @@ -3165,7 +3198,7 @@ def replace_cell(layout, cell_x_name, cell_y_name, cell_y_file=None, cell_y_libr if debug: print(" - none found: %s" % cell_x_name) log += " - none found: %s" % cell_x_name - return + return log, None, False if Exact: if debug: @@ -3176,6 +3209,12 @@ def replace_cell(layout, cell_x_name, cell_y_name, cell_y_file=None, cell_y_libr print(" - non-exact match: %s" % ([c.name for c in cells_x]) ) log += " - non-exact match: %s" % ([c.name for c in cells_x]) + # if you don't provide the cell name, get it from the file + if not cell_y_name: + layout1 = pya.Layout() + layout1.read(cell_y_file) + cell_y_name = layout1.top_cell().name + # Load the new cell: if cell_y_file: cell_y = layout.cell(cell_y_name) @@ -3200,6 +3239,10 @@ def replace_cell(layout, cell_x_name, cell_y_name, cell_y_file=None, cell_y_libr if cells_x: log += " - replacing cells: %s\n" % ([c.name for c in cells_x]) + + # Perform replacement + count = 0 + error = False for cell_x in cells_x: if debug: print(" - replace_cell: found cells to be replaced: %s" % (cell_x.name)) @@ -3224,10 +3267,10 @@ def replace_cell(layout, cell_x_name, cell_y_name, cell_y_file=None, cell_y_libr # Check if the BB cells are the same, by doing an XOR operation # from . import layout_diff if run_layout_diff: - if layout_diff(inst.cell, cell_x, tol=0): - if debug: - print(" - black box cells are different: %s vs %s" % (inst.cell.name, cell_x.name)) - raise Exception (" - black box cells are different: %s vs %s" % (inst.cell.name, cell_x.name)) + if layout_diff(cell_ref_bb, cell_x, tol=0, verbose=True): + print(" - ERROR: black box cells are different: %s vs %s" % (inst.cell.name, cell_x.name)) + error = True + # raise Exception (" - black box cells are different: %s vs %s" % (inst.cell.name, cell_x.name)) break; # replace with CELL_Y if inst.is_regular_array(): @@ -3235,13 +3278,15 @@ def replace_cell(layout, cell_x_name, cell_y_name, cell_y_file=None, cell_y_libr print(" - checked, and replaced %s in %s, with cell array: %s" % (cell_x.name, cc.name, cell_y.name)) ci = inst.cell_inst cc.replace(inst, pya.CellInstArray(cell_y.cell_index(),inst.trans, ci.a, ci.b, ci.na, ci.nb)) + count += 1 else: if debug: print(" - replacing %s in %s, with cell: %s" % (cell_x.name, cc.name, cell_y.name)) cc.replace(inst, pya.CellInstArray(cell_y.cell_index(),inst.trans)) + count += 1 inst = next(itr, None) - return log + return log, count, error def svg_from_cell(verbose=True): @@ -3435,7 +3480,7 @@ def instantiate_all_library_cells(topcell, terminator_cells = None, terminator_l else: print('Error in: %s' % n) p.inc() - x, y = xmax, 0 + x, y = xmax, 0 # all the fixed cells for c in li.layout().each_top_cell(): diff --git a/klayout_dot_config/python/SiEPIC/tests/test_replace_cell/example_bb.gds b/klayout_dot_config/python/SiEPIC/tests/test_replace_cell/example_bb.gds new file mode 100644 index 00000000..cb67c385 Binary files /dev/null and b/klayout_dot_config/python/SiEPIC/tests/test_replace_cell/example_bb.gds differ diff --git a/klayout_dot_config/python/SiEPIC/tests/test_replace_cell/ip_library_bb.gds b/klayout_dot_config/python/SiEPIC/tests/test_replace_cell/ip_library_bb.gds new file mode 100644 index 00000000..29e40748 Binary files /dev/null and b/klayout_dot_config/python/SiEPIC/tests/test_replace_cell/ip_library_bb.gds differ diff --git a/klayout_dot_config/python/SiEPIC/tests/test_replace_cell/ip_library_bb2.gds b/klayout_dot_config/python/SiEPIC/tests/test_replace_cell/ip_library_bb2.gds new file mode 100644 index 00000000..634d9cb3 Binary files /dev/null and b/klayout_dot_config/python/SiEPIC/tests/test_replace_cell/ip_library_bb2.gds differ diff --git a/klayout_dot_config/python/SiEPIC/tests/test_replace_cell/ip_library_wb.gds b/klayout_dot_config/python/SiEPIC/tests/test_replace_cell/ip_library_wb.gds new file mode 100644 index 00000000..46b7ec5a Binary files /dev/null and b/klayout_dot_config/python/SiEPIC/tests/test_replace_cell/ip_library_wb.gds differ diff --git a/klayout_dot_config/python/SiEPIC/tests/test_replace_cell/test_scripts_replace_cell.py b/klayout_dot_config/python/SiEPIC/tests/test_replace_cell/test_scripts_replace_cell.py new file mode 100644 index 00000000..b5343aa4 --- /dev/null +++ b/klayout_dot_config/python/SiEPIC/tests/test_replace_cell/test_scripts_replace_cell.py @@ -0,0 +1,160 @@ +""" +Test for SiEPIC.scripts.replace_cell + +by Lukas Chrostowski 2024 + +""" + +def test_replace_cell(): + ''' + + ''' + + import pya + + import SiEPIC + from SiEPIC._globals import Python_Env + from SiEPIC.utils.layout import new_layout + + import os + + if Python_Env == 'Script': + # For external Python mode, when installed using pip install siepic_ebeam_pdk + import GSiP + + tech_name = 'GSiP' + + from packaging import version + if version.parse(SiEPIC.__version__) < version.parse("0.5.4"): + raise Exception("Errors", "This example requires SiEPIC-Tools version 0.5.4 or greater.") + + + from SiEPIC.scripts import replace_cell, delete_extra_topcells + + # The circuit layout that contains BB cells + path = os.path.dirname(os.path.realpath(__file__)) + file_in = os.path.join(path,'example_bb.gds') + layout = pya.Layout() + layout.read(file_in) + top_cell = layout.top_cell() + + # the individual BB reference cell + file_bb = os.path.join(path,'ip_library_bb.gds') + ly_bb = pya.Layout() + ly_bb.read(file_bb) + cell_bb = ly_bb.top_cell() + + # the individual WB cell + file_wb = os.path.join(path,'ip_library_wb.gds') + ''' + ly_wb = pya.Layout() + ly_wb.read(file_wb) + cell_wb = ly_wb.top_cell() + ''' + def check_bb_geometries(layout): + ''' + check if there are any Black Box layers in the layout + ''' + layer_bb = layout.layer(pya.LayerInfo(998,0)) # hard coded for the GSiP PDK + r1 = pya.Region(top_cell.begin_shapes_rec(layer_bb)) + diff_count = 0 + if not r1.is_empty(): + diff_count = r1.size() + print( + f" - SiEPIC.scripts.layout_diff: {r1.size()} Black Box geometry(ies) found in {top_cell.name} on layer {layout.get_info(layer_bb)}." + ) + return diff_count + + + # Check -- exact replacement (without $) + if 1: + text_out, count, error = replace_cell(layout, + cell_ref_bb = cell_bb, + cell_y_file = file_wb, + Exact = True, + run_layout_diff = False, + debug = True, + ) + print('replaced %s' %count) + assert count == 1 + assert check_bb_geometries(layout) == 1 + + file_out = os.path.join(path,'example_replaced.gds') + delete_extra_topcells(layout, top_cell.name) + layout.write(file_out) + try: + # Display the layout in KLayout, using KLayout Package "klive", which needs to be installed in the KLayout Application + if Python_Env == 'Script': + from SiEPIC.utils import klive + klive.show(file_out, technology='EBeam') + except: + pass + os.remove(file_out) + + + # Check -- non-exact replacement (with $) + if 1: + layout = pya.Layout() + layout.read(file_in) + top_cell = layout.top_cell() + text_out, count, error = replace_cell(layout, + cell_ref_bb = cell_bb, + cell_y_file = file_wb, + Exact = False, RequiredCharacter='$', + run_layout_diff = False, + debug = True, + ) + print('replaced %s' %count) + assert count == 2 + assert check_bb_geometries(layout) == 0 + + file_out = os.path.join(path,'example_replaced2.gds') + delete_extra_topcells(layout, top_cell.name) + layout.write(file_out) + try: + # Display the layout in KLayout, using KLayout Package "klive", which needs to be installed in the KLayout Application + if Python_Env == 'Script': + from SiEPIC.utils import klive + klive.show(file_out, technology='EBeam') + except: + pass + os.remove(file_out) + + # Check -- Run BB reference vs. design layout difference, non-exact replacement (with $) + if 1: + layout = pya.Layout() + layout.read(file_in) + top_cell = layout.top_cell() + text_out, count, error = replace_cell(layout, + cell_y_file = file_wb, + Exact = False, RequiredCharacter='$', + cell_ref_bb = cell_bb, + run_layout_diff = True, + debug = True, + ) + print('replaced %s' %count) + assert count == 2 + assert check_bb_geometries(layout) == 0 + assert error == False + + # Check -- Run BB reference (changed) vs. design layout difference, non-exact replacement (with $) + if 1: + layout = pya.Layout() + layout.read(file_in) + top_cell = layout.top_cell() + # the (changed) BB reference cell + file_bb = os.path.join(path,'ip_library_bb2.gds') + ly_bb = pya.Layout() + ly_bb.read(file_bb) + cell_bb = ly_bb.top_cell() + text_out, count, error = replace_cell(layout, + cell_ref_bb = cell_bb, + cell_y_file = file_wb, + Exact = False, RequiredCharacter='$', + run_layout_diff = True, + debug = True, + ) + assert error == True + +if __name__ == "__main__": + test_replace_cell()