Script to extract the pads center location and pads names into a file

edited July 2013 in Ruby Scripting

To package any die, you need a bonding diagram based on the pads center location and pads names. This script extract them from the layout and write a file based on :

  • the input layer for the pads
  • the input layer and hierarchy depth for the pads name
  • the input of a file name to save the extracted list

If you find the script useful or if you have remarks, please, tell me so that I know if it is OK for you or if I should try (at least :) to improve it.

Note that the script may be long, so I first request all the inputs, including the filename to save the extracted list, so that you can let the script run for a long time ... at lunch or evening time.

Laurent

#############################################################################
#
# DESCRIPTION: Save the pads center location and name into a file
#
# Run the script with
#   klayout -rm pads_location.rbm ...
# or put the script as "pads_location.rbm" into the installation path (on Unix for version <=0.21:
# set $KLAYOUTPATH to the installation folder).
#

# convert the layer specification into a LayerInfo structure
# format: "l", "l/d", "n(l/d)" or "n".
def string_to_layerinfo(layer_spec)
  ls = nil
  if (layer_spec =~ /^(\d+)$/) 
    ls = RBA::LayerInfo.new($1.to_i, 0)
  elsif (layer_spec =~ /^(\d+)\/(\d+)$/) 
    ls = RBA::LayerInfo.new($1.to_i, $2.to_i)
  elsif (layer_spec =~ /^(.*)\s*\((\d+)\/(\d+)\)$/) 
    ls = RBA::LayerInfo.new($2.to_i, $3.to_i, $1)
  elsif (layer_spec =~ /^(\d+)\/(\d+)(@)(\d+)$/)
    ls = RBA::LayerInfo.new($1.to_i, $2.to_i, $4)
  else
    raise "The format for the layer is not good."
  end
  return ls
end

include RBA
require 'ostruct'

def find_texts(lo, cl, layer_idx, recursion_limit)
  texts = {}
  nbr_pads = 0
  cl.each_shape(layer_idx) do |shape|
    if shape.is_text?
      text = shape.text
      point = text.trans.trans(Point.new(0,0))
      name = ([text.string]).join('.')
      texts[nbr_pads.to_s] = OpenStruct.new(:point=>point, :pad_name=>text.string)
      nbr_pads = nbr_pads + 1
    end
  end

  if (recursion_limit>0)
    cl.each_inst do |inst|
      child_cell = lo.cell(inst.cell_index)
      cell_name = child_cell.basic_name
      puts "Descending into cell #{cell_name}"
      child_texts = find_texts(lo, child_cell, layer_idx, recursion_limit-1)
      puts "Finished cell #{cell_name}"
      child_texts.each do |pad_nbr, data|
        texts[nbr_pads.to_s] = OpenStruct.new(:point=>inst.trans.trans(child_texts[pad_nbr].point), :pad_name=>data.pad_name)
        nbr_pads = nbr_pads + 1
      end
    end
  end
  return texts
end

def find_polygons(lo, cl, layer_idx, recursion_limit=0)
  polygons = {}
  nbr_pads = 0
  cl.each_shape(layer_idx) do |shape|
    if shape.is_polygon?
      polygons[nbr_pads.to_s] = OpenStruct.new(:point1=>shape.bbox.p1, :point2=>shape.bbox.p2)
      nbr_pads = nbr_pads + 1
    end
  end

  # if recursion_limit > 0 # the recusion limit was used only for debug, for usage it is unlimited
    cl.each_inst do |inst|
      child_cell = lo.cell(inst.cell_index)
      cell_name = child_cell.basic_name
      puts "Descending into cell #{cell_name}"
      child_polygons = find_polygons(lo, child_cell, layer_idx, recursion_limit-1)
      puts "Finished cell #{cell_name}"
      child_polygons.each do |name, point1, point2|
        polygons[nbr_pads.to_s] = OpenStruct.new(:point1=>inst.trans.trans(child_polygons[name].point1), :point2=>inst.trans.trans(child_polygons[name].point2))
        nbr_pads = nbr_pads + 1
      end
    end
  # end # the recusion limit was used only for debug, for usage it is unlimited
  return polygons
end


$pads_location = MenuAction.new( "Pads name/location", "" ) do 
  app = RBA::Application.instance
  mw = app.main_window

  lv = mw.current_view
  if lv == nil
    raise "No view selected"
  end

  cv = lv.active_cellview
  if !cv.is_valid?
    raise "No cell or no layout found"
  end

  cl = cv.cell
  ci = cv.cell_index
  lo = cv.layout

  ### Layer used to draw each pad opening
  pad_layer = RBA::InputDialog.get_string(" Layer of the pad opening","Enter the GDS number and datatype (ex: 1 or 1/0)\n of the layer of the pad opening :", "*")
  if !pad_layer.has_value?
    return
  end

  # The layer specification string is either "l", "l/d", "n(l/d)" or "n".
  lp = string_to_layerinfo(pad_layer.to_s)

  # locate the layer if it already exists
  pad_layer_index = nil
  lp_found = 0
  (0..(lo.layers-1)).each do |i|
    if lo.is_valid_layer?(i) && lo.get_info(i).is_equivalent?(lp)
      pad_layer_index = i
      lp_found = 1
      break
    end
  end
  if lp_found == 0
    raise "Layer not found !"
  end
  lp_found = 0
  iter = lv.begin_layers
  while !iter.at_end?
    lp = iter.current
    # if lp.cellview == ci && lp.layer_index == pad_layer_index
    if lp.layer_index == pad_layer_index
      pad_layerPropertiesNode = lp
      lp_found = 1 
    end
    iter.next
  end
  if lp_found == 0
    raise "Layer not found!"
  end

  ### Layer used to set the names of each pad
  name_layer = RBA::InputDialog.get_string(" Layer of the pad names","Enter the GDS number and datatype (ex: 1 or 1/0)\n of the layer of the pad names :", "*")
  if !name_layer.has_value?
    return
  end
  # The layer specification string is either "l", "l/d", "n(l/d)" or "n".
  lp = string_to_layerinfo(name_layer.to_s)

  # locate the layer if it already exists
  name_layer_index = nil
  lp_found = 0
  (0..(lo.layers-1)).each do |i|
    if lo.is_valid_layer?(i) && lo.get_info(i).is_equivalent?(lp)
      name_layer_index = i
      lp_found = 1
      break
    end
  end
  if lp_found == 0
    raise "Layer not found !"
  end
  lp_found = 0
  iter = lv.begin_layers
  while !iter.at_end?
    lp = iter.current
    if lp.layer_index == name_layer_index
      lp_found = 1
      name_layerPropertiesNode = lp 
    end
    iter.next
  end
  if lp_found == 0
    raise "Layer not found!"
  end

  name_recursion = RBA::InputDialog.get_string(" Hierarchy of the pad names","Enter the depth in the hierarchy to find the pad names \n 0 ( = TopLevel ) should be OK :", "0")
  if !name_recursion.has_value?
    return
  end
  # Ask for the output file name 
  filename = RBA::FileDialog.get_save_file_name("Pads location file ", ".", "All files (*)")
  if !filename.has_value?
    return
  end

  confirm = RBA::MessageBox.info("Take your time !","Pad Layer   : #{pad_layerPropertiesNode.name}\nNames layer :  #{name_layerPropertiesNode.name}\n\nHierarchy to find the name : #{name_recursion.to_s}\n\nThe routine will look for the pads and their names.\nIt may need several minutes, please, be patient.", RBA::MessageBox.b_ok + RBA::MessageBox.b_cancel)
  if confirm == RBA::MessageBox.b_cancel
    raise "Operation aborted"
  end

  texts = find_texts(lo, cl, name_layer_index, name_recursion.to_s.to_i)
  polygons = find_polygons(lo, cl, pad_layer_index, 1)

  pad_layer = lo.get_info(pad_layer_index)
  name_layer = lo.get_info(name_layer_index)

  # save the data in the file :
  File.open(filename.value, "w") do |file|
    file.puts("*****************************************************************")
    file.puts("*\n*  PADS LOCATION extracted with KLayout.de")
    file.puts("*  Date : #{Time.now.strftime("%d/%m/%Y %H:%M")}")
    file.puts("*\n*  GDS filename : #{cv.name}")
    file.puts("*\n*  TopCell name : #{lo.cell_name(ci)}")
    file.puts("*\n*  Pad Layer    : #{pad_layerPropertiesNode.name}")
    file.puts("*  Names layer  : #{name_layerPropertiesNode.name}")
    file.puts("*\n*  Hierarchy depth to find the names :  #{name_recursion.to_s}")
    file.puts("*\n*****************************************************************\n\n")

    pad_count = 0
    polygons.each do |textpl, datapl|
      find_padname = 0
      pad_count = pad_count + 1
      texts.each do |textnm, datanm|
        if ([datapl.point1.x, datapl.point2.x].min<datanm.point.x && [datapl.point1.x, datapl.point2.x].max>datanm.point.x && [datapl.point1.y,datapl.point2.y].min<datanm.point.y && [datapl.point1.y,datapl.point2.y].max>datanm.point.y)
          find_padname = 1
          file.puts(" #{pad_count} : #{datanm.pad_name} : #{((datapl.point1.x+datapl.point2.x)*lo.dbu/2).round} , #{((datapl.point1.y+datapl.point2.y)*lo.dbu/2).round}")
          break
        end
      end
      if (find_padname == 0)
        file.puts(" #{pad_count} : no_name : #{((datapl.point1.x+datapl.point2.x)*lo.dbu/2).round} , #{((datapl.point1.y+datapl.point2.y)*lo.dbu/2).round}")
      end
    end
  end

end

app = RBA::Application.instance
mw = app.main_window

menu = mw.menu
menu.insert_separator("tools_menu.end", "name")
menu.insert_item("tools_menu.end", "pads_location", $pads_location)

Comments

  • edited November -1

    Hi Laurent,

    thanks for sharing the script with us.

    I took the liberty to nicely format your nice script :-)

    The forum supports Markdown formatting - if the Markdown checkbox is checked, code can be formatted by indenting it with four spaces.

    Thanks,

    Matthias

  • This thread is 11 years old, but hopefully someone can help me with a couple of edits (I'll put them in different threads)

    The main difference between this code and what I'm trying to achieve is that in my case, the "pad" is defined by the overlap of 2 layers. So, I'm not searching for pad_layer, I'm searching for (pad_layer AND pad_layer2).

    I have tried the naive approach of: (1) creating pad_layer3 = pad_layer and pad_layer2; (2) using pad_layer3 in the pad extraction; (3) deleting pad_layer3 (using polygon_layer.output(...) ), but this seems to cause more problems than it solves. So, I'm hoping that someone will be able to suggest how to directly search for pad_layer1 and pad_layer2 (the function find_polygons would have an extra input parameter).

    I'm not interested in the fine details of the GUI, so don't worry about the additional input commands for the window. Just interested in how to correctly alter find_polygons.

    Thanks in advance,
    Brian

  • You might make this easier by "baking in" features to the pad
    layout cells or PCells. A placed-by-construction text is easy to
    grab origin, of. If you made the text value inherited (is this
    possible?) you might even get padName tied to PadCtrX and
    PadCtrY just by querying placed-object properties?

    You can sometimes make things a lot simpler with a little
    fromt end work.

  • This is good practice going forward, but for now I've inherited several layouts that don't have this feature. I think there does have to be a Boolean involved.

  • In the last 10 years a lot has happened :)

    Here is one solution: use a special DRC script. Assume, layer 1/0 is your top metal and 2/0 is your passivation opening that defines the pad (or any other second layer).

    Here is my test case:

    Here is the script:

    last_metal = input(1, 0)
    pad_open = input(2, 0)
    
    (last_metal & pad_open).merged.each do |poly|
      puts poly.bbox.center
    end
    

    Running it prints in my case:

    Matthias

Sign In or Register to comment.