DRC: get centerline of selected edges

edited December 2025 in Verification

I'm able to select Horizontal edges nicly :
TRpairs = TR.drc(width < 6.um).edges.with_angle(90)
I now need to get the centerline between these edges.
I tried a way by using ten centers of the first/second edges but not succeed yet.
Is there a smarter solution to calculate the centerline ?

Comments

  • edited December 2025

    Hi @andyL,

    what do you mean by "centerline between these edges"? Can you provide some sketch or some sample?

    Thanks,

    Matthias

  • Hello Matthias,
    it's been a while, hope your doing well.
    I add a sketch to the post:

  • One additional qustion: I read some slides about the Open Design Rule Format for DRC rules. Can you share some more information on this very intersting topic ?
    Best regards,
    Andreas

  • edited January 10

    Hi Andy,

    thanks for the image, I got the idea now.

    The general strategy I'd like to suggest is to create edge pairs of vertical edge pairs and then compute the centerlines of the two involved edges.

    For getting the edge pairs, you can use the "width" function with a reasonable maximum width and "projection" limit. This will create edge pairs connecting opposite edges. The minimum viable solution is this:

    report("Discussion #2835")
    
    # NOTE: This also works in deep mode in case you need it:
    # deep
    
    # Input
    l1 = input(1, 0)
    
    # Select vertical edges only
    vedges = l1.edges.with_angle(90.0)
    
    # NOTE: put some reasonable threshold here
    pairs = vedges.width(10.um, projection)
    
    pairs.output("Edge Pairs")
    

    This works nicely with a first test case:

    I assume that no feature of your layout is bigger than 10µm here. All technologies I work with have such a maximum limit for "line-like" features which is usually much smaller.

    However, there is a problem for an "U"-shaped polygon as a bigger marker is generated by "width" without seeing the notch:

    Furthermore, the actual solution needs to split the marker into an upper half (with the two arms) and a lower half (with the full width). This is called "shielding" of the width check, but shielding is not a standard DRC feature for edges, as typically you're interested in detecting minimum width violations.

    We can emulate it by eliminating edge parts which are already considered by markers entirely inside the polygon and running the check again. Basically you could have several levels of nesting, so we need to repeat that loop until all edges are covered:

    report("Discussion #2835")
    
    # NOTE: This also works in deep mode in case you need it:
    # deep
    
    # Input
    l1 = input(1, 0)
    
    # Select vertical edges only
    vedges = l1.edges.with_angle(90.0)
    
    # we will collect all edge pairs here
    pairs = edge_pairs
    
    loop do 
    
      # NOTE: put some reasonable threshold here
      new_pairs = vedges.width(10.um, projection)
    
      # split edge pairs for parts that are not shielded
      # and take the respective edges out from the original 
      # edge set. Stop if no new edge pairs are found
    
      new_pairs = new_pairs.inside(l1)
    
      break if new_pairs.is_empty?
    
      pairs += new_pairs
      vedges -= new_pairs.edges
    
    end
    
    pairs.output("Edge Pairs")
    

    This also gives nice results for the "U":

    Once we have the edge pairs, we can compute the centerlines. The solution is not straightforward as there is no centerline operation built in. "extent_refs" would be a place for such a feature. It could compute the centerline of an edge pair for example.

    We can emulate such a function using the API function EdgePair#processed. There is a DRC function called "collect_to_edges" which provides the same functionality in a more convenient fashion, but it's not enabled for edge pairs right now and targeted towards edge or polygon operations.

    The solution with EdgePair#processed is this:

    ... compute "pairs" as above
    
    # see https://www.klayout.de/doc-qt5/code/class_EdgePairToEdgeOperator.html#k_1
    class CenterlineGenerator < RBA::EdgePairToEdgeOperator
      def initialize
        self.is_isotropic_and_scale_invariant   # orientation and scale do not matter
      end
      def process(ep)
        # NOTE: doing the computation this way provides a somewhat lower risk
        # of integer wrapping with 32 bit coordinates ("a+(b-a)/2" instead of "(a+b)/2")
        tc = ep.first.p2 + (ep.second.p1 - ep.first.p2) / 2
        bc = ep.second.p2 + (ep.first.p1 - ep.second.p2) / 2
        [ RBA::Edge::new(bc, tc) ]
      end
    end
    
    # NOTE: pairs.collect_to_edges would be easier to use,
    # but currently (0.30.5) it does not work with edge pairs
    # This is the low-level method: "data" gives the EdgePairs
    # object representing the edge pairs and "process" is an
    # manipulator that takes a RBA::EdgePairToEdgeProcessorBase
    # (aka RBA::EdgePairToEdgeOperator) object to implement the operation. 
    pairs.data = pairs.data.processed(CenterlineGenerator.new)
    
    pairs.output("Centerlines")
    

    And voilà, here is what we get:

    In case you check the documentation: there is a flaw in the documentation of "EdgePair#process": it says that it takes a EdgePairToEdgeProcessorBase object, but that is the internal object. The implementation class is EdgePairToEdgeOperator.

    As (slow) scripted code parts are involved here, performance will not be top notch. But if you use deep mode, the effect will be mitigated by caching and hierarchy compression.

    Hope this helps.

    Matthias

    P.S. "Open Design Rule Format" does not ring a bell. Is that the TU Dresden project? I did not author something with that name. I have a theory on my own for the best approach on DRC rule standardization, but that does not involve conventional rule scripting.

  • Small update: "extent_refs" (see https://www.klayout.de/doc-qt5/about/drc_ref_layer.html#extent_refs) can actually be used, if you first convert the edge pairs to rectangular polygons using "extents" and use "raw" to switch the layer into "raw mode". That avoids that the boxes get merged:

    ... compute "pairs" as above
    
    centerlines = pairs.extents.raw.extent_refs(0.5, 0.0, 0.5, 1.0, as_edges)
    centerlines.output("Centerlines")
    

    That achieves the same result with fewer lines.

    Matthias

  • WOW - Thanks a lot for this detailed reply!
    This solved my problem of a DRC rule deck migration where I can use Klayout now as default rule deck following your guidelines from FSiC2023 Klayout best practices.
    I'm still impressed about the capabilities available with Klayout. Great work you put in place - Thanks for this.

Sign In or Register to comment.