mirror of https://github.com/KLayout/klayout.git
WIP: split DRC into multiple files, bug fixed from lym management.
This commit is contained in:
parent
717e7ca0ab
commit
04f0edc814
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,368 @@
|
|||
# $autorun-early
|
||||
|
||||
module DRC
|
||||
|
||||
# The netter object
|
||||
|
||||
# %DRC%
|
||||
# @scope
|
||||
# @name Netter
|
||||
# @brief DRC Reference: Netter object
|
||||
# The Netter object provides services related to network extraction
|
||||
# from a layout. The relevant methods of this object are available
|
||||
# as global functions too where they act on a default incarnation
|
||||
# of the netter. Usually it's not required to instantiate a Netter
|
||||
# object, but it serves as a container for this functionality.
|
||||
#
|
||||
# An individual netter object can be created, if the netter results
|
||||
# need to be kept for multiple extractions. If you really need
|
||||
# a Netter object, use the global \netter function:
|
||||
#
|
||||
# @code
|
||||
# # create a new Netter object:
|
||||
# nx = netter
|
||||
# nx.connect(poly, contact)
|
||||
# ...
|
||||
# @/code
|
||||
#
|
||||
# Network formation:
|
||||
#
|
||||
# A basic Service the Netter object provides is the formation of
|
||||
# connected networks of conductive shapes. To do so, the Netter
|
||||
# must be given a connection specification. This happens by calling
|
||||
# "connect" with two polygon layers. The Netter will then regard all
|
||||
# overlaps of shapes on these layers as connections between the
|
||||
# respective materials. Networks are the basis for netlist extraction,
|
||||
# network geometry deduction and the antenna check.
|
||||
#
|
||||
# Connections can be cleared with "clear_connections". If not,
|
||||
# connections add atop of the already defined ones. Here is an
|
||||
# example for the antenna check:
|
||||
#
|
||||
# @code
|
||||
# # build connction of poly+gate to metal1
|
||||
# connect(gate, poly)
|
||||
# connect(poly, contact)
|
||||
# connect(contact, metal1)
|
||||
#
|
||||
# # runs an antenna check for metal1 with a ratio of 50
|
||||
# m1_antenna_errors = antenna_check(gate, metal1, 50.0)
|
||||
#
|
||||
# # add connections to metal2
|
||||
# connect(metal1, via1)
|
||||
# connect(via1, metal2)
|
||||
#
|
||||
# # runs an antenna check for metal2 with a ratio of 70.0
|
||||
# m2_antenna_errors = antenna_check(gate, metal2, 70.0)
|
||||
#
|
||||
# # this will remove all connections made
|
||||
# clear_connections
|
||||
# ...
|
||||
# @/code
|
||||
#
|
||||
# Further functionality of the Netter object:
|
||||
#
|
||||
# More methods will be added in the future to support network-related features.
|
||||
|
||||
class DRCNetter
|
||||
|
||||
def initialize(engine)
|
||||
@engine = engine
|
||||
clear_connections
|
||||
end
|
||||
|
||||
# %DRC%
|
||||
# @name connect
|
||||
# @brief Specifies a connection between two layers
|
||||
# @synopsis connect(a, b)
|
||||
# a and b must be polygon layers. After calling this function, the
|
||||
# Netter regards all overlapping or touching shapes on these layers
|
||||
# to form an electrical connection between the materials formed by
|
||||
# these layers. This also implies intra-layer connections: shapes
|
||||
# on these layers touching or overlapping other shapes on these
|
||||
# layers will form bigger, electrically connected areas.
|
||||
#
|
||||
# Multiple connect calls must be made to form larger connectivity
|
||||
# stacks across multiple layers. Such stacks may include forks and
|
||||
# joins.
|
||||
#
|
||||
# Connections are accumulated. The connections defined so far
|
||||
# can be cleared with \clear_connections.
|
||||
|
||||
def connect(a, b)
|
||||
a.is_a?(DRC::DRCLayer) || raise("First argument of Netter#connect must be a layer")
|
||||
b.is_a?(DRC::DRCLayer) || raise("Second argument of Netter#connect must be a layer")
|
||||
a.requires_region("Netter#connect (first argument)")
|
||||
b.requires_region("Netter#connect (second argument)")
|
||||
[ a, b ].each { |l| @layers[l.data.data_id] = l.data }
|
||||
@connections << [ a, b ].collect { |l| l.data.data_id }
|
||||
modified
|
||||
end
|
||||
|
||||
# %DRC%
|
||||
# @name connect_global
|
||||
# @brief Connects a layer with a global net
|
||||
# @synopsis connect_global(l, name)
|
||||
# Connects the shapes from the given layer l to a global net with the given name.
|
||||
# Global nets are common to all cells. Global nets automatically connect to parent
|
||||
# cells throughs implied pins. An example is the substrate (bulk) net which connects
|
||||
# to shapes belonging to tie-down diodes.
|
||||
|
||||
def connect_global(l, name)
|
||||
l.is_a?(DRC::DRCLayer) || raise("Layer argument of Netter#connect_global must be a layer")
|
||||
l.requires_region("Netter#connect_global (layer argument)")
|
||||
@layers[l.data.data_id] = l.data
|
||||
@global_connections << [ l.data.data_id, name.to_s ]
|
||||
end
|
||||
|
||||
# %DRC%
|
||||
# @name extract_devices
|
||||
# @brief Extracts devices based on the given extractor class, name and device layer selection
|
||||
# @synopsis extract_devices(extractor, layer_hash)
|
||||
# Runs the device extraction for given device extractor class.
|
||||
#
|
||||
# The device extractor is either an instance of one of the predefined extractor
|
||||
# classes (e.g. RBA::DeviceExtractorMOS4Transistor) or a custom class. It provides the
|
||||
# algorithms for deriving the device parameters from the device geometry. It needs
|
||||
# several device recognition layers which are passed in the layer hash.
|
||||
#
|
||||
# Each device class (e.g. n-MOS/p-MOS or high Vt/low Vt) needs it's own instance
|
||||
# of device extractor. The device extractor beside the algorithm and specific
|
||||
# extraction settings defines the name of the device to be built.
|
||||
#
|
||||
# The layer hash is a map of device type specific functional names (key) and
|
||||
# polygon layers (value). Here is an example:
|
||||
#
|
||||
# @code
|
||||
# deep
|
||||
#
|
||||
# nwell = input(1, 0)
|
||||
# active = input(2, 0)
|
||||
# poly = input(3, 0)
|
||||
# bulk = make_layer # renders an empty layer used for putting the terminals on
|
||||
#
|
||||
# nactive = active - nwell # active area of NMOS
|
||||
# nsd = nactive - poly # source/drain area
|
||||
# gate = nactive & poly # gate area
|
||||
#
|
||||
# mos4_ex = RBA::DeviceExtractorMOS4Transistor::new("NMOS4")
|
||||
# extract_devices(mos4_ex, { :SD => nsd, :G => gate, :P => poly, :W => bulk })
|
||||
# @/code
|
||||
|
||||
def extract_devices(devex, layer_selection)
|
||||
|
||||
devex.is_a?(RBA::DeviceExtractorBase) || raise("First argument of Netter#extract_devices must be a device extractor instance")
|
||||
layer_selection.is_a?(Hash) || raise("Second argument of Netter#extract_devices must be a hash")
|
||||
|
||||
ls = {}
|
||||
layer_selection.each do |n,l|
|
||||
l.requires_region("Netter#extract_devices (#{n} layer)")
|
||||
@layers[l.data.data_id] = l.data
|
||||
ls[n.to_s] = l.data
|
||||
end
|
||||
|
||||
@devices_to_extract << [ devex, ls ]
|
||||
modified
|
||||
|
||||
end
|
||||
|
||||
# %DRC%
|
||||
# @name clear_connections
|
||||
# @brief Clears all connections stored so far
|
||||
# @synopsis clear_connections
|
||||
# See \connect for more details.
|
||||
|
||||
def clear_connections
|
||||
@devices_to_extract = []
|
||||
@connections = []
|
||||
@global_connections = []
|
||||
@layers = {}
|
||||
@join_nets = ""
|
||||
modified
|
||||
end
|
||||
|
||||
# %DRC%
|
||||
# @name join_nets
|
||||
# @brief Specifies a search pattern for labels which create implicit net connections
|
||||
# @synopsis join_nets(label_pattern)
|
||||
# Use this method to supply a glob pattern for labels which create implicit net connections
|
||||
# on the top level circuit. This feature is useful to connect identically labelled nets
|
||||
# while a component isn't integrated yet. If the component is integrated, net may be connected
|
||||
# on a higher hierarchy level - e.g. by a power mesh. Inside the component this net consists
|
||||
# of individual islands. To properly perform netlist extraction and comparison, these islands
|
||||
# need to be connected even though there isn't a physical connection. "join_nets" can
|
||||
# achive this if these islands are labelled with the same text on the top level of the
|
||||
# component.
|
||||
#
|
||||
# Glob pattern are used which resemble shell file pattern: "*" is for all labels, "VDD"
|
||||
# for all "VDD" labels (pattern act case sensitive). "VDD*" is for all labels beginning
|
||||
# with "VDD" (still different labels will be connected to different nets!). "{VDD,VSS}"
|
||||
# is either "VDD" or "VSS".
|
||||
#
|
||||
# The search pattern is applied on the next net extraction. The search pattern is cleared
|
||||
# on "clear_connections".
|
||||
|
||||
def join_nets(arg)
|
||||
@join_nets = arg
|
||||
modified
|
||||
end
|
||||
|
||||
# %DRC%
|
||||
# @brief Performs an antenna check
|
||||
# @name antenna_check
|
||||
# @synopsis antenna_check(gate, metal, ratio, [ diode_specs ... ])
|
||||
#
|
||||
# The antenna check is used to avoid plasma induced damage. Physically,
|
||||
# the damage happes if during the manufacturing of a metal layer with
|
||||
# plasma etching charge accumulates on the metal islands. On reaching a
|
||||
# certain threshold, this charge may discarge over gate oxide attached of
|
||||
# devices attached to such metal areas hence damaging it.
|
||||
#
|
||||
# Antenna checks are performed by collecting all connected nets up to
|
||||
# a certain metal layer and then computing the area of all metal shapes
|
||||
# and all connected gates of a certain kind (e.g. thin and thick oxide gates).
|
||||
# The ratio of metal area divided by the gate area must not exceed a certain
|
||||
# threshold.
|
||||
#
|
||||
# A simple antenna check is this:
|
||||
#
|
||||
# @code
|
||||
# poly = ... # poly layer
|
||||
# diff = ... # diffusion layer
|
||||
# contact = ... # contact layer
|
||||
# metal1 = ... # metal layer
|
||||
#
|
||||
# # compute gate area
|
||||
# gate = poly & diff
|
||||
#
|
||||
# # note that gate and poly have to be included - gate is
|
||||
# # a subset of poly, but forms the sensitive area
|
||||
# connect(gate, poly)
|
||||
# connect(poly, contact)
|
||||
# connect(contact, metal1)
|
||||
# errors = antenna_check(gate, metal1, 50.0)
|
||||
# @/code
|
||||
#
|
||||
# Plasma induced damage can be rectified by including diodes
|
||||
# which create a safe current path for discharging the metal
|
||||
# islands. Such diodes can be identified with a recognition layer
|
||||
# (usually the diffusion area of a certain kind). You can include
|
||||
# such diode recognition layers in the antenna check. If a connection
|
||||
# is detected to a diode, the respective network is skipped:
|
||||
#
|
||||
# @code
|
||||
# ...
|
||||
# diode = ... # diode recognition layer
|
||||
#
|
||||
# connect(diode, contact)
|
||||
# errors = antenna_check(gate, metal1, 50.0, diode)
|
||||
# @/code
|
||||
#
|
||||
# You can also make diode connections decreases the
|
||||
# sensitivity of the antenna check depending on the size
|
||||
# of the diode. The following specification makes
|
||||
# diode connections increase the ratio threshold by
|
||||
# 10 per square micrometer of diode area:
|
||||
#
|
||||
# @code
|
||||
# ...
|
||||
# diode = ... # diode recognition layer
|
||||
#
|
||||
# connect(diode, contact)
|
||||
# # each square micrometer of diode area connected to a network
|
||||
# # will add 10 to the ratio:
|
||||
# errors = antenna_check(gate, metal1, 50.0, [ diode, 10.0 ])
|
||||
# @/code
|
||||
#
|
||||
# Multiple diode specifications are allowed. Just add them
|
||||
# to the antenna_check call.
|
||||
#
|
||||
# The error shapes produced by the antenna check are copies
|
||||
# of the metal shapes on the metal layers of each network
|
||||
# violating the antenna rule.
|
||||
|
||||
def antenna_check(gate, metal, ratio, *diodes)
|
||||
|
||||
gate.is_a?(DRC::DRCLayer) || raise("gate argument of Netter#antenna_check must be a layer")
|
||||
gate.requires_region("Netter#antenna_check (gate argument)")
|
||||
|
||||
metal.is_a?(DRC::DRCLayer) || raise("metal argument of Netter#antenna_check must be a layer")
|
||||
metal.requires_region("Netter#antenna_check (metal argument)")
|
||||
|
||||
if !ratio.is_a?(1.class) && !ratio.is_a?(Float)
|
||||
raise("ratio argument Netter#antenna_check is not a number")
|
||||
end
|
||||
|
||||
dl = diodes.collect do |d|
|
||||
if d.is_a?(Array)
|
||||
d.size == 2 || raise("diode specification pair expects two elements")
|
||||
d[0].requires_region("Netter#antenna_check (diode layer)")
|
||||
[ d[0].data, d[1].to_f ]
|
||||
else
|
||||
d.requires_region("Netter#antenna_check (diode layer)")
|
||||
[ d.data, 0.0 ]
|
||||
end
|
||||
end
|
||||
|
||||
@l2n || make_l2n
|
||||
DRC::DRCLayer::new(@engine, @engine._cmd(@l2n, :antenna_check, gate.data, metal.data, ratio, dl))
|
||||
|
||||
end
|
||||
|
||||
# %DRC%
|
||||
# @name l2n_data
|
||||
# @brief Gets the internal RBA::LayoutToNetlist object
|
||||
# @synopsis l2n_data
|
||||
# The RBA::LayoutToNetlist object provides access to the internal details of
|
||||
# the netter object.
|
||||
|
||||
def l2n_data
|
||||
@l2n || make_l2n
|
||||
@l2n
|
||||
end
|
||||
|
||||
def _finish
|
||||
clear_connections
|
||||
# cleans up the L2N object
|
||||
modified
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def modified
|
||||
@l2n && @l2n._destroy
|
||||
@l2n = nil
|
||||
end
|
||||
|
||||
def make_l2n
|
||||
|
||||
if @engine._dss
|
||||
# TODO: check whether all layers are deep and come from the dss and layout index,
|
||||
# then use this layout index. This will remove the need for this check:
|
||||
@engine._dss.is_singular? || raise("The DRC script features more than one or no layout source - network extraction cannot be performed in such configurations")
|
||||
@l2n = RBA::LayoutToNetlist::new(@engine._dss)
|
||||
else
|
||||
layout = @engine.source.layout
|
||||
@l2n = RBA::LayoutToNetlist::new(layout.top_cell.name, layout.dbu)
|
||||
end
|
||||
|
||||
@layers.each { |id,l| @l2n.register(l, "l" + id.to_s) }
|
||||
|
||||
@devices_to_extract.each do |devex,ls|
|
||||
@engine._cmd(@l2n, :extract_devices, devex, ls)
|
||||
end
|
||||
|
||||
@layers.each { |id,l| @l2n.connect(l) }
|
||||
@connections.each { |a,b| @l2n.connect(@layers[a], @layers[b]) }
|
||||
@global_connections.each { |l,n| @l2n.connect_global(@layers[l], n) }
|
||||
|
||||
# run extraction in a timed environment
|
||||
@engine._cmd(@l2n, :extract_netlist, @join_nets)
|
||||
@l2n
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
# $autorun-early
|
||||
|
||||
# Extend the Float class by methods which convert
|
||||
# values with units, i.e. 1.3nm gives 0.0013
|
||||
|
||||
1.0.class.class_eval do
|
||||
|
||||
class << self
|
||||
@dbu = nil
|
||||
def _dbu=(dbu)
|
||||
@dbu = dbu
|
||||
end
|
||||
def _dbu
|
||||
@dbu
|
||||
end
|
||||
end
|
||||
|
||||
def um
|
||||
self
|
||||
end
|
||||
def micron
|
||||
self
|
||||
end
|
||||
def degree
|
||||
self
|
||||
end
|
||||
def nm
|
||||
self*0.001
|
||||
end
|
||||
def mm
|
||||
self*1000.0
|
||||
end
|
||||
def m
|
||||
self*1000000.0
|
||||
end
|
||||
def nm2
|
||||
self*1e-6
|
||||
end
|
||||
def um2
|
||||
self
|
||||
end
|
||||
def micron2
|
||||
self
|
||||
end
|
||||
def mm2
|
||||
self*1.0e6
|
||||
end
|
||||
def m2
|
||||
self*1.0e12
|
||||
end
|
||||
def dbu
|
||||
self.class._dbu || raise("No layout loaded - cannot determine database unit")
|
||||
self*self.class._dbu
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# Extend the Fixnum class, so it is possible to
|
||||
# convert a value to Float with a unit spec, i.e.
|
||||
# 5.nm -> 0.005. A spec with ".dbu" gives the
|
||||
# Fixnum value itself. This indicates a value in
|
||||
# database units for most methods of the DRC
|
||||
# framework.
|
||||
|
||||
1.class.class_eval do
|
||||
|
||||
class << self
|
||||
@dbu = nil
|
||||
def _dbu=(dbu)
|
||||
@dbu = dbu
|
||||
end
|
||||
def _dbu
|
||||
@dbu
|
||||
end
|
||||
end
|
||||
|
||||
def um
|
||||
self.to_f
|
||||
end
|
||||
def micron
|
||||
self.to_f
|
||||
end
|
||||
def degree
|
||||
self.to_f
|
||||
end
|
||||
def nm
|
||||
self*0.001
|
||||
end
|
||||
def mm
|
||||
self*1000.0
|
||||
end
|
||||
def m
|
||||
self*1000000.0
|
||||
end
|
||||
def nm2
|
||||
self*1.0e-6
|
||||
end
|
||||
def um2
|
||||
self.to_f
|
||||
end
|
||||
def micron2
|
||||
self.to_f
|
||||
end
|
||||
def mm2
|
||||
self*1.0e6
|
||||
end
|
||||
def m2
|
||||
self*1.0e12
|
||||
end
|
||||
def dbu
|
||||
self.class._dbu || raise("No layout loaded - cannot determine database unit")
|
||||
self*self.class._dbu
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
|
@ -0,0 +1,405 @@
|
|||
# $autorun-early
|
||||
|
||||
module DRC
|
||||
|
||||
# A layout source representative object.
|
||||
# This object describes an input. It consists of a layout reference plus
|
||||
# some attributes describing how input is to be gathered.
|
||||
|
||||
# %DRC%
|
||||
# @scope
|
||||
# @name Source
|
||||
# @brief DRC Reference: Source Object
|
||||
# The layer object represents a collection of polygons, edges or edge pairs.
|
||||
# A source specifies where to take layout from. That includes the actual layout,
|
||||
# the top cell and options such as clip/query boxes, cell filters etc.
|
||||
|
||||
class DRCSource
|
||||
|
||||
def initialize(engine, layout, layout_var, cell)
|
||||
@engine = engine
|
||||
@layout = layout
|
||||
@layout_var = layout_var
|
||||
@cell = cell
|
||||
@inside = nil
|
||||
@box = nil
|
||||
@layers = nil
|
||||
@sel = []
|
||||
@clip = false
|
||||
@overlapping = false
|
||||
@tmp_layers = []
|
||||
end
|
||||
|
||||
# %DRC%
|
||||
# @name layout
|
||||
# @brief Returns the RBA::Layout object associated with this source
|
||||
# @synopsis layout
|
||||
|
||||
def layout
|
||||
@layout
|
||||
end
|
||||
|
||||
# %DRC%
|
||||
# @name cell_name
|
||||
# @brief Returns the name of the currently selected cell
|
||||
# @synopsis cell_name
|
||||
|
||||
def cell_name
|
||||
@cell.name
|
||||
end
|
||||
|
||||
# %DRC%
|
||||
# @name cell_obj
|
||||
# @brief Returns the RBA::Cell object of the currently selected cell
|
||||
# @synopsis cell_obj
|
||||
|
||||
def cell_obj
|
||||
@cell
|
||||
end
|
||||
|
||||
def finish
|
||||
@tmp_layers.each do |li|
|
||||
@layout.delete_layer(li)
|
||||
end
|
||||
end
|
||||
|
||||
def set_box(method, *args)
|
||||
box = nil
|
||||
if args.size == 1
|
||||
box = args[0]
|
||||
box.is_a?(RBA::DBox) || raise("'#{method}' method requires a box specification")
|
||||
elsif args.size == 2
|
||||
(args[0].is_a?(RBA::DPoint) && args[1].is_a?(RBA::DPoint)) || raise("'#{method}' method requires a box specification with two points")
|
||||
box = RBA::DBox::new(args[0], args[1])
|
||||
elsif args.size == 4
|
||||
box = RBA::DBox::new(*args)
|
||||
else
|
||||
raise("Invalid number of arguments for '#{method}' method")
|
||||
end
|
||||
@box = RBA::Box::from_dbox(box * (1.0 / @layout.dbu))
|
||||
self
|
||||
end
|
||||
|
||||
def inplace_clip(*args)
|
||||
set_box("clip", *args)
|
||||
@clip = true
|
||||
@overlapping = true
|
||||
end
|
||||
|
||||
def inplace_touching(*args)
|
||||
set_box("touching", *args)
|
||||
@clip = false
|
||||
@overlapping = false
|
||||
end
|
||||
|
||||
def inplace_overlapping(*args)
|
||||
set_box("overlapping", *args)
|
||||
@clip = false
|
||||
@overlapping = true
|
||||
end
|
||||
|
||||
def inplace_cell(arg)
|
||||
@cell = layout.cell(arg)
|
||||
@cell ||= layout.create_cell(arg)
|
||||
self
|
||||
end
|
||||
|
||||
def inplace_select(*args)
|
||||
args.each do |a|
|
||||
a.is_a?(String) || raise("Invalid arguments to 'select' method - must be strings")
|
||||
@sel.push(a)
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
# %DRC%
|
||||
# @name select
|
||||
# @brief Adds cell name expressions to the cell filters
|
||||
# @synopsis source.select(filter1, filter2, ...)
|
||||
# This method will construct a new source object with the given cell filters
|
||||
# applied.
|
||||
# Cell filters will enable or disable cells plus their subtree.
|
||||
# Cells can be switched on and off, which makes the hierarchy traversal
|
||||
# stop or begin delivering shapes at the given cell. The arguments of
|
||||
# the select method form a sequence of enabling or disabling instructions
|
||||
# using cell name pattern in the glob notation ("*" as the wildcard, like shell).
|
||||
# Disabling instructions start with a "-", enabling instructions with a "+" or
|
||||
# no specification.
|
||||
#
|
||||
# The following options are available:
|
||||
#
|
||||
# @ul
|
||||
# @li @tt+@/tt @i name_filter @/i: Cells matching the name filter will be enabled @/li
|
||||
# @li @i name_filter @/i: Same as "+name_filter" @/li
|
||||
# @li @tt-@/tt @i name_filter @/i: Cells matching the name filter will be disabled @/li
|
||||
# @/ul
|
||||
#
|
||||
# To disable the TOP cell but enabled a hypothetical cell B below the top cell, use that
|
||||
# code:
|
||||
#
|
||||
# @code
|
||||
# layout_with_selection = layout.select("-TOP", "+B")
|
||||
# l1 = layout_with_selection.input(1, 0)
|
||||
# ...
|
||||
# @/code
|
||||
#
|
||||
# Please note that the sample above will deliver the children of "B" because there is
|
||||
# nothing said about how to proceed with cells other than "TOP" or "B".
|
||||
# The following code will just select "B" without it's children, because in the
|
||||
# first "-*" selection, all cells including the children of "B" are disabled:
|
||||
#
|
||||
# @code
|
||||
# layout_with_selection = layout.select("-*", "+B")
|
||||
# l1 = layout_with_selection.input(1, 0)
|
||||
# ...
|
||||
# @/code
|
||||
|
||||
# %DRC%
|
||||
# @name cell
|
||||
# @brief Specifies input from a specific cell
|
||||
# @synopsis source.cell(name)
|
||||
# This method will create a new source that delivers shapes from the
|
||||
# specified cell.
|
||||
|
||||
# %DRC%
|
||||
# @name clip
|
||||
# @brief Specifies clipped input
|
||||
# @synopsis source.clip(box)
|
||||
# @synopsis source.clip(p1, p2)
|
||||
# @synopsis source.clip(l, b, r, t)
|
||||
# Creates a source which represents a rectangular part of the
|
||||
# original input. Three ways are provided to specify the rectangular
|
||||
# region: a single RBA::DBox object (micron units), two RBA::DPoint
|
||||
# objects (lower/left and upper/right coordinate in micron units)
|
||||
# or four coordinates: left, bottom, right and top coordinate.
|
||||
#
|
||||
# This method will create a new source which delivers the shapes
|
||||
# from that region clipped to the rectangle. A method doing the
|
||||
# same but without clipping is \touching or \overlapping.
|
||||
|
||||
# %DRC%
|
||||
# @name touching
|
||||
# @brief Specifies input selected from a region in touching mode
|
||||
# @synopsis source.touching(box)
|
||||
# @synopsis source.touching(p1, p2)
|
||||
# @synopsis source.touching(l, b, r, t)
|
||||
# Like \clip, this method will create a new source delivering shapes
|
||||
# from a specified rectangular region. In contrast to clip, all shapes
|
||||
# touching the region with their bounding boxes are delivered as a whole
|
||||
# and are not clipped. Hence shapes may extent beyond the limits of
|
||||
# the specified rectangle.
|
||||
#
|
||||
# \overlapping is a similar method which delivers shapes overlapping
|
||||
# the search region with their bounding box (and not just touching)
|
||||
|
||||
# %DRC%
|
||||
# @name overlapping
|
||||
# @brief Specifies input selected from a region in overlapping mode
|
||||
# @synopsis source.overlapping(...)
|
||||
# Like \clip, this method will create a new source delivering shapes
|
||||
# from a specified rectangular region. In contrast to clip, all shapes
|
||||
# overlapping the region with their bounding boxes are delivered as a whole
|
||||
# and are not clipped. Hence shapes may extent beyond the limits of
|
||||
# the specified rectangle.
|
||||
#
|
||||
# \touching is a similar method which delivers shapes touching
|
||||
# the search region with their bounding box (without the requirement to overlap)
|
||||
|
||||
# export inplace_* as * out-of-place
|
||||
%w(select cell clip touching overlapping).each do |f|
|
||||
eval <<"CODE"
|
||||
def #{f}(*args)
|
||||
s = self.dup
|
||||
s.inplace_#{f}(*args)
|
||||
s
|
||||
end
|
||||
CODE
|
||||
end
|
||||
|
||||
# %DRC%
|
||||
# @name extent
|
||||
# @brief Returns a layer with the bounding box of the selected layout
|
||||
# @synopsis source.extent
|
||||
# The extent function is useful to invert a layer:
|
||||
#
|
||||
# @code
|
||||
# inverse_1 = extent.sized(100.0) - input(1, 0)
|
||||
# @/code
|
||||
|
||||
def extent
|
||||
layer = input
|
||||
if @box
|
||||
layer.insert(RBA::DBox::from_ibox(@box) * @layout.dbu)
|
||||
else
|
||||
layer.insert(RBA::DBox::from_ibox(@cell.bbox) * @layout.dbu)
|
||||
end
|
||||
layer
|
||||
end
|
||||
|
||||
# %DRC%
|
||||
# @name input
|
||||
# @brief Specifies input from a source
|
||||
# @synopsis source.input(layer)
|
||||
# @synopsis source.input(layer, datatype)
|
||||
# @synopsis source.input(layer_into)
|
||||
# @synopsis source.input(filter, ...)
|
||||
# Creates a layer with the shapes from the given layer of the source.
|
||||
# The layer can be specified by layer and optionally datatype, by a RBA::LayerInfo
|
||||
# object or by a sequence of filters.
|
||||
# Filters are expressions describing ranges
|
||||
# of layers and/or datatype numbers or layer names. Multiple filters
|
||||
# can be given and all layers matching at least one of these filter
|
||||
# expressions are joined to render the input layer for the DRC engine.
|
||||
#
|
||||
# Some filter expressions are:
|
||||
#
|
||||
# @ul
|
||||
# @li @tt 1/0-255 @/tt: Datatypes 0 to 255 for layer 1 @/li
|
||||
# @li @tt 1-10 @/tt: Layers 1 to 10, datatype 0 @/li
|
||||
# @li @tt METAL @/tt: A layer named "METAL" @/li
|
||||
# @li @tt METAL (17/0) @/tt: A layer named "METAL" or layer 17, datatype 0 (for GDS, which does
|
||||
# not have names)@/li
|
||||
# @/ul
|
||||
#
|
||||
# Layers created with "input" contain both texts and polygons. There is a subtle
|
||||
# difference between flat and deep mode: in flat mode, texts are not visible in polygon
|
||||
# operations. In deep mode, texts appear as small 2x2 DBU rectangles. In flat mode,
|
||||
# some operations such as clipping are not fully supported for texts. Also, texts will
|
||||
# vanish in most polygon operations such as booleans etc.
|
||||
#
|
||||
# Texts can later be selected on the layer returned by "input" with the \Layer#texts method.
|
||||
#
|
||||
# If you don't want to see texts, use \polygons to create an input layer with polygon data
|
||||
# only. If you only want to see texts, use \labels to create an input layer with texts only.
|
||||
#
|
||||
# Use the global version of "input" without a source object to address the default source.
|
||||
|
||||
def input(*args)
|
||||
layers = parse_input_layers(*args)
|
||||
DRCLayer::new(@engine, @engine._cmd(@engine, :_input, @layout_var, @cell.cell_index, layers, @sel, @box, @clip, @overlapping, RBA::Shapes::SAll))
|
||||
end
|
||||
|
||||
# %DRC%
|
||||
# @name labels
|
||||
# @brief Gets the labels (texts) from an input layer
|
||||
# @synopsis source.labels(layer)
|
||||
# @synopsis source.labels(layer, datatype)
|
||||
# @synopsis source.labels(layer_into)
|
||||
# @synopsis source.labels(filter, ...)
|
||||
#
|
||||
# Creates a layer with the labels from the given layer of the source.
|
||||
#
|
||||
# This method is identical to \input, but takes only texts from the given input
|
||||
# layer.
|
||||
#
|
||||
# Use the global version of "labels" without a source object to address the default source.
|
||||
|
||||
def labels(*args)
|
||||
layers = parse_input_layers(*args)
|
||||
DRCLayer::new(@engine, @engine._cmd(@engine, :_input, @layout_var, @cell.cell_index, layers, @sel, @box, @clip, @overlapping, RBA::Shapes::STexts))
|
||||
end
|
||||
|
||||
# %DRC%
|
||||
# @name polygons
|
||||
# @brief Gets the polygon shapes (or shapes that can be converted polygons) from an input layer
|
||||
# @synopsis source.polygons(layer)
|
||||
# @synopsis source.polygons(layer, datatype)
|
||||
# @synopsis source.polygons(layer_into)
|
||||
# @synopsis source.polygons(filter, ...)
|
||||
#
|
||||
# Creates a layer with the polygon shapes from the given layer of the source.
|
||||
# With "polygon shapes" we mean all kind of shapes that can be converted to polygons.
|
||||
# Those are boxes, paths and real polygons.
|
||||
#
|
||||
# This method is identical to \input with respect to the options supported.
|
||||
#
|
||||
# Use the global version of "polygons" without a source object to address the default source.
|
||||
|
||||
def polygons(*args)
|
||||
layers = parse_input_layers(*args)
|
||||
DRCLayer::new(@engine, @engine._cmd(@engine, :_input, @layout_var, @cell.cell_index, layers, @sel, @box, @clip, @overlapping, RBA::Shapes::SBoxes | RBA::Shapes::SPaths | RBA::Shapes::SPolygons | RBA::Shapes::SEdgePairs))
|
||||
end
|
||||
|
||||
# %DRC%
|
||||
# @name make_layer
|
||||
# @brief Creates an empty polygon layer based on the hierarchy of the layout
|
||||
# @synopsis make_layer
|
||||
# This method delivers a new empty original layer.
|
||||
|
||||
def make_layer
|
||||
layers = []
|
||||
DRCLayer::new(@engine, @engine._cmd(@engine, :_input, @layout_var, @cell.cell_index, layers, @sel, @box, @clip, @overlapping, RBA::Shapes::SAll))
|
||||
end
|
||||
|
||||
# %DRC%
|
||||
# @name layers
|
||||
# @brief Gets the layers the source contains
|
||||
# @synopsis source.layers
|
||||
# Delivers a list of RBA::LayerInfo objects representing the layers
|
||||
# inside the source.
|
||||
#
|
||||
# One application is to read all layers from a source. In the following
|
||||
# example, the "and" operation is used to perform a clip with the given
|
||||
# rectangle. Note that this solution is not efficient - it's provided
|
||||
# as an example only:
|
||||
#
|
||||
# @code
|
||||
# output_cell("Clipped")
|
||||
#
|
||||
# clip_box = polygon_layer
|
||||
# clip_box.insert(box(0.um, -4.um, 4.um, 0.um))
|
||||
#
|
||||
# layers.each { |l| (input(l) & clip_box).output(l) }
|
||||
# @/code
|
||||
|
||||
def layers
|
||||
@layout.layer_indices.collect { |li| @layout.get_info(li) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_input_layers(*args)
|
||||
|
||||
layers = []
|
||||
|
||||
if args.size == 0
|
||||
|
||||
li = @layout.insert_layer(RBA::LayerInfo::new)
|
||||
li && layers.push(li)
|
||||
li && @tmp_layers.push(li)
|
||||
|
||||
elsif (args.size == 1 && args[0].is_a?(RBA::LayerInfo))
|
||||
|
||||
li = @layout.find_layer(args[0])
|
||||
li && layers.push(li)
|
||||
|
||||
elsif (args.size == 1 || args.size == 2) && args[0].is_a?(1.class)
|
||||
|
||||
li = @layout.find_layer(args[0], args[1] || 0)
|
||||
li && layers.push(li)
|
||||
|
||||
else
|
||||
|
||||
args.each do |a|
|
||||
if a.is_a?(String)
|
||||
# use the LayerMap class to fetch the matching layers
|
||||
lm = RBA::LayerMap::new
|
||||
lm.map(a, 0)
|
||||
@layout.layer_indices.each do |li|
|
||||
if lm.is_mapped?(@layout.get_info(li))
|
||||
layers.push(li)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
layers
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
# $autorun-early
|
||||
|
||||
module DRC
|
||||
|
||||
# A wrapper for a named value which is stored in
|
||||
# a variable for delayed execution
|
||||
class DRCVar
|
||||
def initialize(name)
|
||||
@name = name
|
||||
end
|
||||
def inspect
|
||||
@name
|
||||
end
|
||||
def to_s
|
||||
@name
|
||||
end
|
||||
end
|
||||
|
||||
# A wrapper for the sizing mode value
|
||||
class DRCSizingMode
|
||||
attr_accessor :value
|
||||
def initialize(v)
|
||||
self.value = v
|
||||
end
|
||||
end
|
||||
|
||||
# A wrapper for the join flag for extended
|
||||
class DRCJoinFlag
|
||||
attr_accessor :value
|
||||
def initialize(v)
|
||||
self.value = v
|
||||
end
|
||||
end
|
||||
|
||||
# A wrapper for the angle limit
|
||||
# The purpose of this wrapper is to identify the
|
||||
# angle limit specification
|
||||
class DRCAngleLimit
|
||||
attr_accessor :value
|
||||
def initialize(v)
|
||||
self.value = v
|
||||
end
|
||||
end
|
||||
|
||||
# A wrapper for a metrics constant
|
||||
# The purpose of this wrapper is to identify the
|
||||
# metrics constant by the class.
|
||||
class DRCMetrics
|
||||
attr_accessor :value
|
||||
def initialize(v)
|
||||
self.value = v
|
||||
end
|
||||
end
|
||||
|
||||
# A wrapper for the "whole edges" flag for
|
||||
# the DRC functions. The purpose of this class
|
||||
# is to identify the value by the class.
|
||||
class DRCWholeEdges
|
||||
attr_accessor :value
|
||||
def initialize(v)
|
||||
self.value = v
|
||||
end
|
||||
end
|
||||
|
||||
# A wrapper for the "as_dots" or "as_boxes" flag for
|
||||
# some DRC functions. The purpose of this class
|
||||
# is to identify the value by the class.
|
||||
class DRCAsDots
|
||||
attr_accessor :value
|
||||
def initialize(v)
|
||||
self.value = v
|
||||
end
|
||||
end
|
||||
|
||||
# A wrapper for a glob-pattern style text selection for
|
||||
# some DRC functions. The purpose of this class
|
||||
# is to identify the value by the class.
|
||||
class DRCPattern
|
||||
attr_accessor :as_pattern
|
||||
attr_accessor :pattern
|
||||
def initialize(f, p)
|
||||
self.as_pattern = f
|
||||
self.pattern = p
|
||||
end
|
||||
end
|
||||
|
||||
# A wrapper for a pair of limit values
|
||||
# This class is used to identify projection limits for DRC
|
||||
# functions
|
||||
class DRCProjectionLimits
|
||||
attr_accessor :min
|
||||
attr_accessor :max
|
||||
def initialize(*a)
|
||||
if a.size > 2 || a.size == 0
|
||||
raise("A projection limits specification requires a maximum of two values and at least one argument")
|
||||
elsif a.size == 1
|
||||
if !a[0].is_a?(Range) || (!a[0].min.is_a?(Float) && !a[0].min.is_a?(1.class))
|
||||
raise("A projection limit requires an interval of two length values or two individual length values")
|
||||
end
|
||||
self.min = a[0].min
|
||||
self.max = a[0].max
|
||||
elsif a.size == 2
|
||||
if a[0] && !a[0].is_a?(Float) && !a[0].is_a?(1.class)
|
||||
raise("First argument to a projection limit must be either nil or a length value")
|
||||
end
|
||||
if a[1] && !a[1].is_a?(Float) && !a[1].is_a?(1.class)
|
||||
raise("Second argument to a projection limit must be either nil or a length value")
|
||||
end
|
||||
self.min = a[0]
|
||||
self.max = a[1]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
|
@ -1,6 +1,11 @@
|
|||
<RCC>
|
||||
<qresource prefix="/built-in-macros">
|
||||
<file alias="drc.lym">built-in-macros/drc.lym</file>
|
||||
<file alias="_drc_engine.rb">built-in-macros/_drc_engine.rb</file>
|
||||
<file alias="_drc_layer.rb">built-in-macros/_drc_layer.rb</file>
|
||||
<file alias="_drc_netter.rb">built-in-macros/_drc_netter.rb</file>
|
||||
<file alias="_drc_patch.rb">built-in-macros/_drc_patch.rb</file>
|
||||
<file alias="_drc_source.rb">built-in-macros/_drc_source.rb</file>
|
||||
<file alias="_drc_tags.rb">built-in-macros/_drc_tags.rb</file>
|
||||
<file alias="drc_interpreters.lym">built-in-macros/drc_interpreters.lym</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
|
|||
|
|
@ -684,16 +684,19 @@ void Macro::sync_properties_with_text ()
|
|||
|
||||
for (size_t i = 0; i < sizeof (property_fields) / sizeof (property_fields[0]); ++i) {
|
||||
|
||||
tl::Extractor pex = ex;
|
||||
|
||||
const PropertyField *pf = property_fields + i;
|
||||
if (ex.test (pf->name)) {
|
||||
if (pex.test (pf->name) && (pex.at_end () || pex.test (":"))) {
|
||||
|
||||
if (pf->string_setter) {
|
||||
ex.test (":");
|
||||
(this->*(pf->string_setter)) (unescape_pta_string (ex.skip ()));
|
||||
(this->*(pf->string_setter)) (unescape_pta_string (pex.skip ()));
|
||||
} else if (pf->bool_setter) {
|
||||
(this->*(pf->bool_setter)) (true);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1334,6 +1337,7 @@ void MacroCollection::scan (const std::string &path)
|
|||
|
||||
ResourceWithChildren res (tl::to_qstring (path));
|
||||
QStringList children = res.children ();
|
||||
children.sort ();
|
||||
|
||||
for (QStringList::const_iterator c = children.begin (); c != children.end (); ++c) {
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue