Does RBA::Layout.read change existing PCells in a layout?

Unfortunately I can't easily create a small test case to show this behaviour because it is integrated in quite a big script, but I wonder whether anyone has come across this as well.

Basically, the steps are as follows:

  1. Create a layout = RBA::Layout.new(true)
  2. Use layout.read(gds_file) to populate layout with the contents of a GDS file
  3. Manipulate layout with the usual API methods such as layout.add_pcell_variant

This seems to work as expected, but if we swap steps 2 and 3, i.e. the "scripted" layout manipulation happens before the layout.read call, this call causes the parameters of a PCell instance previously created via scripting to change.

Could it be that RBA::Layout.read accidentally overwrites/changes some PCell parameters of already existing PCell variants?

Would it be safer to always read GDS files before manipulating the layout via a Ruby script?

Comments

  • edited May 2016

    Hi,

    this is a trick question isn't it :-)

    Basically I think, first reading and then manipulating is the only valid way. I mean, you don't first bake a cake and then add the ingredients, do you?

    Honestly, I don't know what is happening when you first create cells and then read a PCell-loaded GDS file over that. I have created a simple testcase with two circles in separate files: of file containing a cell A with a circle of diameter 1µm and another file also with cell A containing a circle with a diameter of 2µm. Now I join both files with

    l = RBA::Layout::new
    l.read("file1.gds")
    l.read("file2.gds")
    l.write("both.gds")
    

    this case works for me. So I'd say basically reading a file over an existing layout should preserve PCell parameters.

    Matthias

  • edited May 2016

    Hi Matthias,

    Thanks for your input. Not so much of a trick question but more of a slight uncertainty about expected PCell behaviour :-)

    When I talked to my colleagues about the cake question, they also seem to think that having the ingredients first is the right way to go. However, my experience is (not only with KLayout) that some side-effects caused by strange behaviours are - once discovered - shamelessly put to use in production ;-)

    The best example off the top of my head for reading in a GDS later in the flow would be to create a complex layout with standard logic cell dummies and subsequently reading in the GDS with the "real" cells to replace these dummies. At least this is something we do rather regularly using a post-processing step not involving KLayout, so I'm not sure whether it would even work with KLayout, but maybe you can think of similar "tricks" that might serve a real use case for KLayout.

    If we ignore these corner cases, I'd probably go for "read first then edit" as a general rule and wait until someone complains about that :-)

    Thanks again for this great Open Source tool!

    Chris

  • edited May 2016

    Matthias,

    After a bit of research and spending some time on writing a rather minimal example exposing the problem, here it finally is:

    Object.send(:remove_const, :PCell_gds_read_bug) if Object.constants.include?(:PCell_gds_read_bug)
    module PCell_gds_read_bug
    
        ['ABC', 'XYZ'].each {|txt|
          ly = RBA::Application.instance.main_window.create_layout(1).layout
          top = ly.add_cell("top#{txt}")
          layer = RBA::LayerInfo.new(2, 0)
          layer_idx = ly.layer(layer)
          pcv = ly.create_cell('TEXT', 'Basic', {"text" => txt, "layer" => layer})
          trans = RBA::Trans::new(200, 100)
          ly.cell(top).insert(RBA::CellInstArray::new(pcv.cell_index(), trans))
          lv = RBA::Application.instance.main_window.current_view
          ly.write("Text_#{txt}.gds")
        }
    
        ly = RBA::Application.instance.main_window.create_layout(1).layout
        ly.read("Text_ABC.gds")
        ly.read("Text_XYZ.gds")
    end
    

    As you can see, first two layouts are created, each with its own top cell which holds a Basic.TEXT PCell instance. One of layouts creates a PCell instance in its top cell with ABC as text, the other one uses XYZ. These layouts are saved as ''Text_ABC.gds'' and ''Text_XYZ.gds'', respectively.

    Then, another (empty) layout is created and its read(gds_file) method is used to read the just-created GDS files in.

    At least in my version of KLayout (0.24.6 on Win64), this resulting layout does have the two top-level cells from the GDS files, but these top cells refer to only one (the XYZ) instance of the Basic.TEXT PCell, the ABC PCell is gone.

    Is this a bug or did I make a mistake in using the API in the code above?

    Chris

  • edited May 2016

    Hi Chris,

    Amazing test case, thanks a lot!

    The test script can be written even shorter and well reproduces the problem:

    ['ABC', 'XYZ'].each {|txt|
      ly = RBA::Layout::new
      top = ly.create_cell("top#{txt}")
      layer = RBA::LayerInfo.new(2, 0)
      pcv = ly.create_cell('TEXT', 'Basic', {"text" => txt, "layer" => layer})
      trans = RBA::Trans::new(200, 100)
      top.insert(RBA::CellInstArray::new(pcv.cell_index(), trans))
      ly.write("Text_#{txt}.gds")
    }
    
    ly = RBA::Layout::new
    ly.read("Text_ABC.gds")
    ly.read("Text_XYZ.gds")
    ly.write("Text_merged.gds")
    

    I'd also consider that a bug but I'll have to see whether there will be solution. The basic problem is rooted within the GDS format: this format does not support PCells per se. Instead, PCells are written as ordinary GDS cells with some hidden metadata to backannotate the ordinary cells to PCells. It turns out now, that both "Text_ABC" and "Text_XYZ" use the same name for Basic.TEXT (simply "TEXT") and hence the "piggy-back" read will identify these cells as being the same.

    I generally discourage from using the "piggy-back" read approach unless you really know what you are doing. There is always a risk of cells being joined which are not supposed to be. Imagine you add some "real cells" from a library and your library cells contain other cells themselves - for example, at PMOS transistor in a cell called "PMOS". Imagine different inverters for example with different fan-out like "INV1", "INV2", "INV4" etc. The transistors used in these inverters will be different because they have to be stronger for higher fan-out. If each inverter is kept as a separate GDS file, each of them is valid with it's private "PMOS" cell, but if you merge them together in the simple piggy-back read, the PMOS transistor cells will get joined and your layout will become a mess.

    The safe way I advise in general is to use "Cell#copy_tree" which takes care of creating unique cells. In your case the script using "Cell#copy_tree" looks like this:

    ['ABC', 'XYZ'].each {|txt|
      ly = RBA::Layout::new
      top = ly.create_cell("top#{txt}")
      layer = RBA::LayerInfo.new(2, 0)
      pcv = ly.create_cell('TEXT', 'Basic', {"text" => txt, "layer" => layer})
      trans = RBA::Trans::new(200, 100)
      top.insert(RBA::CellInstArray::new(pcv.cell_index(), trans))
      ly.write("Text_#{txt}.gds")
    }
    
    # (NOTE: "true" makes the layout editable which is a requirement for the "copy_tree" below)
    ly = RBA::Layout::new(true)
    ly.read("Text_ABC.gds")
    
    # First read the second layout into a separate layout
    ly2 = RBA::Layout::new
    ly2.read("Text_XYZ.gds")
    
    # Then copy the new layout's top over to the first layout
    new_top = ly.create_cell(ly2.top_cell.name)
    new_top.copy_tree(ly2.top_cell)
    
    ly.write("Text_merged.gds")
    

    This gives a better result: now the XYZ text appears below topXZY and the ABC text appears as a subcell of topABC.

    BUT: currently, "copy_tree" (and the related methods) does not support transferring the PCell identity. It will only transfer the geometry and hence the PCell identity of the "XYZ" text gets lost. That means you will see the "XYZ" text as it's supposed to be, but you cannot edit it simply by changing the PCell parameters. But geometrically, the result will be correct. So this solution is a valid approach for some kind of post processing where keeping PCells as such is less important.

    Best regards,

    Matthias

  • edited May 2016

    Hi Matthias,

    Thanks a lot for your helpful reply!

    As we tend to often have multiple top cells in intermediate steps while assembling the final layouts, I changed the test case construction code to create more interesting GDS files:

    ['ABC', 'XYZ'].each {|txt|
      ly = RBA::Application.instance.main_window.create_layout(1).layout
      layer = RBA::LayerInfo.new(2, 0)
      to_merge = ly.create_cell("to_merge")
      box = RBA::Box::new(RBA::Point::new(0, 800), RBA::Point::new(txt == 'ABC' ? 500 : 4300, 900))
      to_merge.shapes(ly.layer(layer)).insert(box)
      ['L', 'M'].each {|topname|
        top = ly.create_cell("#{topname}top#{txt}")
        top.insert(RBA::CellInstArray::new(to_merge.cell_index, RBA::Trans::new(0, 0)))
        [1, 2].each {|inst|
          pcv = ly.create_cell('TEXT', 'Basic', {"text" => "#{txt}-#{inst}-#{topname}", "layer" => layer})
          trans = RBA::Trans::new(200, inst * 1000)
          top.insert(RBA::CellInstArray::new(pcv.cell_index(), trans))
        }
      }
      ly.write("Text_#{txt}.gds")
    }
    

    Your great example helped me a lot to make the script handle any number of source layouts and multiple top cells like so:

    ly = RBA::Layout::new(true) # destination layout
    ["Text_ABC.gds", "Text_XYZ.gds"].each do |srcname|
      src_ly = RBA::Layout::new
      src_ly.read(srcname)
      src_ly.each_top_cell do |cellindex|
        src_top = src_ly.cell(cellindex)
        top = ly.create_cell(src_top.name)
        top.copy_tree(src_top)
      end    
    end
    

    This works fine but as you mentioned, it makes the resulting layout miss all PCell info from the sources. Also there are multiple to_merge cell instances in the result, even though there are only two to_merge source cells (one in each GDS).

    So far this doesn't seem to matter too much, so we'll keep it like that for now - until someone complains about the redundant cells I suppose :-)

    Thanks again and best regards,
    Chris

Sign In or Register to comment.