Flatten cell from script

edited January 2015 in Ruby Scripting

Hi, Matthias,
Thanks for such a great program that keeps getting even better.
Can you help me with this problem?
I am filling a circular wafer with instances of a square cell which is composed of smaller square cells. When an instance of the large square cell extends past the circle, I want to flatten it (1 hierarchy level) and remove instances of the smaller square cells that fall outside the circle. I can't figure out the correct syntax to flatten the instance from ruby (see code below):

(nr+1).times do |j|
  (nc+1).times do |i|
    # pattern if any part of reticle is on wafer
    centerx = (i-nc/2-0.5)*maincellx
    centery = (j-nr/2-0.5)*maincelly
    rtl = Math::sqrt((centerx-maincellx/2)**2 + (centery+maincelly/2)**2 )
    rbl = Math::sqrt((centerx-maincellx/2)**2 + (centery-maincelly/2)**2 )
    rtr = Math::sqrt((centerx+maincellx/2)**2 + (centery+maincelly/2)**2 )
    rbr = Math::sqrt((centerx+maincellx/2)**2 + (centery-maincelly/2)**2 )
    rcenter = Math::sqrt((centerx)**2 + (centery)**2 )
    if rtl < re or rbl < re or rtr < re or rbr < re then
      if (j - nro2 == 1 || i - nco2 == 1) && rcenter > ralignmin && rcenter < ralignmax then
        thisindex = cell_index2
      else
        thisindex = cell_index
      end
      thisinstance = RBA::CellInstArray::new(thisindex,Trans.new(0,(i-nc/2-0.5)*maincellx*dbu,(j-nr/2-0.5)*maincelly*dbu))
      thiscell = ly.cell(new_top).insert(thisinstance)
      # explode if cell extends past good area
      if rtl > re or rbl > re or rtr > re or rbr > re then
        # ly.cell(new_top).erase(thiscell) # this worked to delete cell, but not what I want
        ly.flatten(thisinstance.cell_index,1,true) # no error, but doesn't work 
      end
    end
  end
end

The line that's commented out

     # ly.cell(new_top).erase(thiscell) # this worked to delete cell, but not what I want

shows me that I am detecting the instance that I want to flatten (by erasing it) but that's not what I want to do.
The line that's active

     ly.flatten(thisinstance.cell_index,1,true) # no error, but doesn't work 

runs without producing an error, but doesn't actually flatten the instance that I want to flatten.

Comments

  • edited January 2015

    Hello,

    The flatten method removed the child hierarchy from a cell. That is, if a cell "A" calls child cells, for example "B" and "C", "A" will look the same after flattening, but "B" and "C" will be gone from "A". The shapes of "B" and "C" will have been propagated to the cell "A" applying a proper transformation to emulate the effect of the instance.

    As far as I understand your problem, this is not what you look for. You are looking for a way to flatten a single instance. Right now, there is no direct method to do that yet, but you can emulate this:

    class RBA::Instance
    
      # Flattens this instance into it's parent and removes one level of hierarchy.
      # After calling this method, the instance becomes invalid.
      def flatten_me
        parent = self.parent_cell
        child = self.cell
        t = self.cplx_trans
        self.layout.layer_indices.each do |li|
          child.shapes(li).each do |s| 
            parent.shapes(li).insert(s, t)
          end
        end
        child.each_inst do |i|
          parent.insert(i).transform(t)
        end
        self.delete
      end
    
    end
    

    This nice example Ruby's capabilities to dynamically modify a class at runtime will extend the "Instance" class, so it provides a new method "flatten_me" which will propagate the contents of the called cell to the parent and remove the instance.

    With this definition you can write:

    thisinstance.flatten_me
    

    to achieve the desired effect.

    Note that this method will not prune the cell called in the instance (thisindex in your case). If it is not instantiated any other way, it will remain as a separate top cell.

    There is also a potential performance pitfall here: deleting the instance will invalidate the hierarchy cache and obtaining the parent will refresh it again. So if you observe a poor performance, let me know. I'll look for a different solution then.

    Regards,

    Matthias

  • edited November -1
    Hi, Matthias,
    Thanks for your quick response!
    I still have a problem. In the code above, thisinstance is a CellInstArray, while the new method you coded is for an Instance.
    I think I should be able to create an Instance which can point to the CellInstArray that I want to flatten, but when I try

    thisinstpr = RBA::Instance::new
    thisinstptr.cell_inst = thisinstance

    I get this error:
    "Caught the following exception:
    Internal error: gsiDeclDbCell.cc:1977 inst->instances() != 0 was not true in
    Instance::cel_inst= (Class RuntimeError)"

    Part of the problem may be my inexperience with Ruby -- do I need to use a "pointer operator" as in C (like a = &b )?
  • edited January 2015

    Line with no spaces in front

    Line with four spaces in front
        Line with eight spaces in front
    
  • edited January 2015

    Not to answer your question but quick tip -- to format code, use four spaces in front of each code line. (Or more spaces for indents within the code.) You can do this manually or else just paste into something like Notepad++, select all, press tab to indent all lines, copy, and paste in to this web form.

    Here is the markdown spec: http://daringfireball.net/projects/markdown/syntax

    Matthias, perhaps might be worth putting a link to the Markdown spec right beside the "Markdown" radio button.

  • edited November -1

    Hi all of you,

    thanks for the MarkDown suggestion - I'll have to figure out how to make the Vanilla forum system do that ...

    Regarding the code issue above: You don't need to create RBA::Instance objects (you can since Ruby does not forbid that, but as you see they don't work). RBA::Instance objects are rather representing an instance inside the database. Instead, you create a RBA::CellInstArray object, fill that with the attributes you require and insert that one into the cell. Only then you get a RBA::Instance object which you can use to access and modify the instance inside the database.

    This may look strange, but there is a reason for that twofold identity: due to the constraining requirements for the layout database, an instance inside the database is not an independent object, but a part of a highly optimized data structure. The RBA::Instance class provides a way to manipulate the database (technically spoken, a proxy), while the RBA::CellInstArray is the standalone object to conveniently work with outside the database.

    BTW: there is a similar differentiation for shapes - the "outside" objects are RBA::Box, RBA::Path, RBA::Text, RBA::Polygon, the proxy object is RBA::Shape).

    Matthias

  • edited January 2015

    Thanks, Matthias--that works.
    (For completeness, here's the section which uses the new "flatten_me" method from above):

     (nr+1).times do |j|
       (nc+1).times do |i|
         # pattern if any part of reticle is on wafer
         centerx = (i-nc/2-0.5)*maincellx
         centery = (j-nr/2-0.5)*maincelly
         rtl = Math::sqrt((centerx-maincellx/2)**2 + (centery+maincelly/2)**2 )
         rbl = Math::sqrt((centerx-maincellx/2)**2 + (centery-maincelly/2)**2 )
         rtr = Math::sqrt((centerx+maincellx/2)**2 + (centery+maincelly/2)**2 )
         rbr = Math::sqrt((centerx+maincellx/2)**2 + (centery-maincelly/2)**2 )
         rcenter = Math::sqrt((centerx)**2 + (centery)**2 )
         if rtl < re or rbl < re or rtr < re or rbr < re then
           if (j - nro2 == 1 || i - nco2 == 1) && rcenter > ralignmin && rcenter < ralignmax then
             thisindex = cell_index2
           else
             thisindex = cell_index
           end
           thisinstance = RBA::CellInstArray::new(thisindex,Trans.new(0,(i-nc/2-0.5)*maincellx*dbu,(j-nr/2-0.5)*maincelly*dbu))
           thiscell = ly.cell(new_top).insert(thisinstance)
           # explode if cell extends past good area
           if rtl > re or rbl > re or rtr > re or rbr > re then
             thiscell.flatten_me           
           end
         end
       end
     end
    
  • edited February 2016

    Hi, Matthias,
    Thanks for all your work and support of this excellent program!
    I've come across a possible bug and am wondering if perhaps there is a better way to accomplish my purpose.
    I want to "fill the edge" of a circular wafer with rectangular cells.
    So far, I have a square "Reticle" with square "Chips".
    I am trying to cover the circular area with instances of the reticle, and then flatten instances which stick out.
    I am using the Cell.each_instance method to iterate through all instances, and then using the "flatten_me" method described above when I find an instance which needs to be flattened.
    I find that looping through the instances one time is not enough, but if I loop through them 3 times, I get all the edge instances flattened. (This suggests that I am damaging the internal structure of the Cell list by flattening some instances.)
    Then I want to loop through all the instances and delete those that are off the wafer.
    When I do so, eventually I get a KLayout crash. After proceeding through the crash, I find that the work has been partially done (some instances that are off the wafer have been deleted) but not all.
    Is there a better way to loop through the list of instances?
    Do I need to modify the "flatten_me" method so I don't damage the instance list?

    Here is the script I am running

    # modify instance class--add new method to flatten
    
    include RBA
    
    class RBA::Instance
    
    # Flattens this instance into its parent and removes one level of hierarchy.
    # After calling this method, the instance becomes invalid.
    def flatten_me_simple
      parent = self.parent_cell
      child = self.cell
      t = self.cplx_trans
      child.each_inst do |i|
        thisinst = parent.insert(i)
        thisinst.transform(t)
      end
      self.delete
    end
    
    end
    
    
    
    module MyMacro
    
    # parameters
    
    d = 150.0 # mm wafer diameter
    e = 0.8 # mm edge exclusion for "good" area 
    r = d/2
    re = r - e
    
    # convert to microns
    r *= 1000.0
    re *= 1000.0
    
    mw = RBA::Application::instance.main_window
    layout_view=mw.current_view
    ly = layout_view.active_cellview.layout
    dbu = 1.0/ly.dbu
    layout_view=mw.current_view
    cell_index = ly.cell_by_name("MAIN")
    cell_index2 = ly.cell_by_name("B")
    refinfo = RBA::LayerInfo::new(60,0)
    ly.insert_layer(refinfo)
    ref = ly.find_layer(refinfo)
    new_top = ly.add_cell("wafer6")
    # add first instance of MAIN
    firstmain = ly.cell(new_top).insert(RBA::CellInstArray::new(cell_index,RBA::CplxTrans::new(1,0,false,RBA::DPoint::new(0,0))))
    maincellx = ly.cell(new_top).bbox.width/dbu
    maincelly = ly.cell(new_top).bbox.height/dbu
    
    ly.cell(new_top).erase(firstmain)
    
    nc = ((d*1000.0) / maincellx).round
    nr = ((d*1000.0) / maincelly).round
    
    nco2 = (nc/2).round
    nro2 = (nr/2).round
    
    # draw wafer edge exclusion and dummy regions
    pts = []
    pts2 = []
    n = 1024
    da = Math::PI * 2 / n
    n.times do |i|
      j = i - n/8 
      # wafer edge
      pts.push(Point.from_dpoint(DPoint.new(r*dbu * Math::cos(j * da), r*dbu * Math::sin(j * da))))
      # edge exclusion
      pts2.push(Point.from_dpoint(DPoint.new(re*dbu * Math::cos(j * da), re*dbu * Math::sin(j * da))))
    end
    ly.cell(new_top).shapes(ref).insert(Polygon.new(pts))
    waf = RBA::Region::new(RBA::Polygon::new(pts))
    ly.cell(new_top).shapes(ref).insert(Polygon.new(pts2))
    rg = RBA::Region::new(RBA::Polygon::new(pts2))
    # create instances of MAIN
    (nr+1).times do |j|
      (nc+1).times do |i|
        # pattern if any part of reticle is on wafer
        centerx = (i-nc/2-0.5)*maincellx
        centery = (j-nr/2-0.5)*maincelly
        rtl = Math::sqrt((centerx-maincellx/2)**2 + (centery+maincelly/2)**2 )
        rbl = Math::sqrt((centerx-maincellx/2)**2 + (centery-maincelly/2)**2 )
        rtr = Math::sqrt((centerx+maincellx/2)**2 + (centery+maincelly/2)**2 )
        rbr = Math::sqrt((centerx+maincellx/2)**2 + (centery-maincelly/2)**2 )
        rcenter = Math::sqrt((centerx)**2 + (centery)**2 )
        if rtl < re or rbl < re or rtr < re or rbr < re then
          thisx = i - nco2 -1
          thisy = j - nro2  
          thisinstance = RBA::CellInstArray::new(cell_index,Trans.new(0,(i-nc/2-0.5)*maincellx*dbu,(j-nr/2-0.5)*maincelly*dbu))
          thiscell = ly.cell(new_top).insert(thisinstance)
        end
      end
    end
    ly.cell(new_top).each_inst do |thisinstance|
      if(thisinstance.is_valid?)
        if(thisinstance.cell.name == "MAIN")
          thisbb = RBA::Region::new(thisinstance.bbox)
          if !(thisbb.not_inside(waf).is_empty?)
            thisinstance.flatten_me_simple
            ly.update
          end
        end
      end
    end
    # run again since 1st time misses some
    ly.cell(new_top).each_inst do |thisinstance|
      if(thisinstance.is_valid?)
        if(thisinstance.cell.name == "MAIN")
          thisbb = RBA::Region::new(thisinstance.bbox)
          if !(thisbb.not_inside(waf).is_empty?)
            thisinstance.flatten_me_simple
            ly.update
          end
        end
      end
    end
    # third time is the charm
    ly.cell(new_top).each_inst do |thisinstance|
      if(thisinstance.is_valid?)
        if(thisinstance.cell.name == "MAIN")
          thisbb = RBA::Region::new(thisinstance.bbox)
          if !(thisbb.not_inside(waf).is_empty?)
            thisinstance.flatten_me_simple
            ly.update
          end
        end
      end
    end
    # remove off-wafer instances
    ly.cell(new_top).each_inst do |thisinstance|
      if(thisinstance.is_valid?)
        if(thisinstance.cell.name == "B")
          thisbb = RBA::Region::new(thisinstance.bbox)
          if !(thisbb.outside(waf).is_empty?)
            thisinstance.delete
            ly.update
          end
        end
      end
    end
    
    # fit the viewport to the extensions of our layout
    layout_view.select_cell(new_top, 0)
    layout_view.add_missing_layers
    layout_view.zoom_fit
    end
    

    Here's an example GDSII file that I am running this script on to demonstrate the behavior: gdsfile "simple GDSII example"

  • edited November -1

    Hi devengr,

    thanks for the nice testcase! It was very simple to reproduce, but in my case it even got stuck.

    I assume there is a different behaviour depending on the mode (edit or viewer), so that may explain the difference (I used edit mode).

    Anyway, the explanation is that: when you change the list of instances while you iterate it, you will basically get a mess - maybe you are destroying the instance you are currently at or something like that. The solution is pretty simple: first collect and then delete. Like this:

    insts_to_flatten = []
    ly.cell(new_top).each_inst do |thisinstance|
      if(thisinstance.is_valid?)
        if(thisinstance.cell.name == "MAIN")
          thisbb = RBA::Region::new(thisinstance.bbox)
          if !(thisbb.not_inside(waf).is_empty?)
            insts_to_flatten << thisinstance
          end
        end
      end
    end
    
    insts_to_flatten.each do |i|
      i.flatten_me_simple
    end
    

    In my case (edit mode) this solved the problem and a single pass is sufficient.

    Regards,

    Matthias

Sign In or Register to comment.