From 455ebeaca3e612e566f2c3f4fe2ae7ff21d2ab03 Mon Sep 17 00:00:00 2001 From: Alex Tait Date: Fri, 21 Jun 2019 14:26:33 -0600 Subject: [PATCH] checking for invalid masks and tested --- lymask/invocation.py | 4 +- lymask/steps.py | 70 ++++++++++++------- lymask/utilities.py | 1 - requirements-test.txt | 1 + .../tech/example_tech/dataprep/bad_masks.yml | 9 +++ tests/test_example.py | 24 +++++-- 6 files changed, 74 insertions(+), 35 deletions(-) create mode 100644 tests/tech/example_tech/dataprep/bad_masks.yml diff --git a/lymask/invocation.py b/lymask/invocation.py index 18abf8a..dcbc269 100644 --- a/lymask/invocation.py +++ b/lymask/invocation.py @@ -12,7 +12,7 @@ active_technology, set_active_technology, \ tech_layer_properties, \ lys, reload_lys -from lymask.steps import all_func_dict +from lymask.steps import all_func_dict, assert_valid_step_list parser = argparse.ArgumentParser(description="Command line mask dataprep") @@ -34,10 +34,10 @@ def cm_main(): def _main(layout, ymlfile, tech_obj=None): # todo: figure out which technology we will be using and its layer properties - # todo: reload lys using technology with open(ymlfile) as fx: step_list = yaml.load(fx) reload_lys(tech_obj, dataprep=True) + assert_valid_step_list(step_list) for func_info in step_list: func = all_func_dict[func_info[0]] try: diff --git a/lymask/steps.py b/lymask/steps.py index 740f27b..cb0a1ee 100644 --- a/lymask/steps.py +++ b/lymask/steps.py @@ -180,44 +180,47 @@ def precomp(cell, **kwargs): def mask_map(cell, clear_others=False, **kwargs): ''' lyp_file is relative to the yml file. If it is None, the same layer properties will be used. kwarg keys are destination layers and values are source layers, which can include "+" + + There is a problem if you have 101 defined in your file and then another layer that is not defined. ''' mask_layer_index = 101 + assert_valid_mask_map(kwargs) # merging and moving to new layers for dest_layer, src_expression in kwargs.items(): - new_layinfo = pya.LayerInfo(mask_layer_index, 0, dest_layer) - lys[dest_layer] = new_layinfo - cell.layout().layer(new_layinfo) + if not dest_layer in lys.keys(): + new_layinfo = pya.LayerInfo(mask_layer_index, 0, dest_layer) + lys[dest_layer] = new_layinfo + cell.layout().layer(new_layinfo) + components = src_expression.split('+') for comp in components: - try: - comp_lay_info = lys[comp.strip()] - except KeyError as err: - message_loud('Source layer [{}] not found in existing designer or dataprep layerset.'.format(comp)) - raise + comp_lay_info = lys[comp.strip()] cell.copy(comp_lay_info, lys[dest_layer]) mask_layer_index += 1 - # if isGUI(): - # try: - # lv = gui_view() - # add_tab = True - # except UserWarning: - # # No view is selected. We might be in batch mode - # add_tab = False - # if add_tab: - # insert_layer_tab(tab_name='Masks') - # for dest_layer in kwargs.keys(): - # lay_prop = pya.LayerProperties() - # lay_prop.source_name = lys.get_as_LayerInfo(dest_layer).name - # lay_prop.source_layer = lys.get_as_LayerInfo(dest_layer).layer - # lay_prop.source_datatype = lys.get_as_LayerInfo(dest_layer).datatype - # lv.init_layer_properties(lay_prop) - # lv.insert_layer(lv.end_layers(), lay_prop) + if clear_others: for any_layer in lys.keys(): if any_layer not in kwargs.keys() and any_layer != 'FLOORPLAN': cell.clear(lys[any_layer]) +def assert_valid_mask_map(mapping): + for dest_layer, src_expression in mapping.items(): + try: + lys[dest_layer] + except KeyError as err: + message_loud('Warning: Destination layer [{}] not found in mask layerset. We will make it...'.format(dest_layer)) + pass # This is allowed + + components = src_expression.split('+') + for comp in components: + try: + comp_lay_info = lys[comp.strip()] + except KeyError as err: + message_loud('Error: Source layer [{}] not found in existing designer or dataprep layerset.'.format(comp)) + raise + + @dpStep def clear_nonmask(cell): ''' Gets rid of everything except 101--199. That is what we have decided are mask layers. @@ -230,7 +233,6 @@ def clear_nonmask(cell): cell.clear(lys[any_layer]) - @dpStep def align_corners(cell): ''' Puts little boxes in the corners so lithography tools all see the same @@ -261,3 +263,21 @@ def align_corners(cell): mark = corner_mark.moved(east_west, north_south) if not cell.shapes(ly.layer(marked_layer)).is_empty(): cell.shapes(ly.layer(marked_layer)).insert(mark) + + +def assert_valid_step_list(step_list): + ''' This runs before starting calculations to make sure there aren't typos + that only show up after waiting for for all of the long steps + ''' + + # check function names + for func_info in step_list: + try: + func = all_func_dict[func_info[0]] + except KeyError as err: + message_loud('Function not supported. Available are {}'.format(all_func_dict.keys())) + raise + + # check mask layers + if func is mask_map: + assert_valid_mask_map(func_info[1]) diff --git a/lymask/utilities.py b/lymask/utilities.py index 1d6574d..c5d1233 100644 --- a/lymask/utilities.py +++ b/lymask/utilities.py @@ -213,6 +213,5 @@ def reload_lys(technology=None, clear=False, dataprep=False): lv.transaction('Bump transaction') lv.current_layer_list = 0 - # reload_lys() diff --git a/requirements-test.txt b/requirements-test.txt index e92611e..1b5ac39 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1 +1,2 @@ lytest +klayout \ No newline at end of file diff --git a/tests/tech/example_tech/dataprep/bad_masks.yml b/tests/tech/example_tech/dataprep/bad_masks.yml new file mode 100644 index 0000000..3d36b3d --- /dev/null +++ b/tests/tech/example_tech/dataprep/bad_masks.yml @@ -0,0 +1,9 @@ +# One of the source layers in mask map does not exist in the klayout_layers.lyp +# This means it should throw a KeyError before running nw_sleeve. +# The test will expect this KeyError, but not a TypeError +--- +- - nw_sleeve + - Delta: 'not a float' # This will cause a TypeError if it runs, so the test will fail +- - mask_map + - mask_nw_ebeam: m2_nw_ebeam + not_in_lyp +... diff --git a/tests/test_example.py b/tests/test_example.py index af1cbd5..4d75f6d 100644 --- a/tests/test_example.py +++ b/tests/test_example.py @@ -1,5 +1,8 @@ import os, sys import subprocess +import pytest +import pya + os.environ['KLAYOUT_HOME'] = os.path.dirname(os.path.realpath(__file__)) import lymask @@ -26,13 +29,20 @@ def test_from_technology(): run_xor(outfile, reffile) -# def test_cm(): -# command = ['lymask'] -# command += [layout_file] -# command += [dataprep_file] -# command += ['-o', outfile] -# subprocess.check_call(command) -# run_xor(outfile, reffile) +def test_lyp_loading(): + from lymask.utilities import set_active_technology, reload_lys, lys + layout = pya.Layout() + layout.read(layout_file) + lys.active_layout = layout + + lymask.set_active_technology('example_tech') + lymask.utilities.reload_lys() + + assert lys['m5_wiring'] is lys('m5_wiring') is lys.m5_wiring + + with pytest.raises(KeyError): + batch_main(layout_file, ymlspec='bad_masks', technology='example_tech') + def test_cm_from_tech(): # this also checks that it defaults to default.yml