It looks like you're new here. If you want to get involved, click one of these buttons!
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 :
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
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:
Running it prints in my case:
Matthias