Chamfer function for fun

edited July 2022 in Python scripting

Hello,

Some EDA tool has chamfer function, which can add extra pattern to prevent
metal trace from transition with 90 degree angle or less ( Maybe not often to see in VLSI ?)
Someone told me, it can be done by " round corner " function, but I tried and failed, due to
triangle compensation is preferred.. Share below code for fun, any smart method or comment will be appreciated.

BTW, the initial length of chamfer ( tear_drop ) is 20 " um "

import pya

cellview = pya.CellView.active()
layout = cellview.layout()
top = cellview.cell

metal = layout.layer(9 , 0)
tear_drop = layout.layer(6 , 0)

pya.Application.instance().main_window().current_view().add_missing_layers()

target_90 = pya.Region( layout.top_cell().begin_shapes_rec(metal)).with_angle(270, 360 , False)
count_90 = target_90.size() 
first_edge_iter_90 = target_90.first_edges().each()
second_edge_iter_90 = target_90.second_edges().each()
target_90_pairs_set = [ k for k in target_90.each() ]
center_90_set = []
start_point_set = []
end_point_set = []


def center_90():
    for i in range(0, count_90):
        center = target_90_pairs_set[i].first.crossing_point(target_90_pairs_set[i].second)
        center_90_set.append(center)

def start_point():
    for j in range(0, count_90):
        P1 = target_90_pairs_set[j].first.d().x 
        P2 = target_90_pairs_set[j].first.d().y
        P3 = target_90_pairs_set[j].first.length()
        P4 = center_90_set[j].x
        P5 = center_90_set[j].y
        length = 20000

        if P1 >= 0 and P2 == 0 and P3 >= length:
            start_point_set.append(pya.Point(P4 - length, P5))
        elif P1 >= 0 and P2 == 0 and P3 < length:
            start_point_set.append(pya.Point(P4 - P3, P5))
        elif P1 < 0 and P2 == 0 and P3 >= length:
            start_point_set.append(pya.Point(P4 + length, P5))
        elif P1 < 0 and P2 == 0 and P3 < length:
            start_point_set.append(pya.Point(P4 + P3, P5))
        elif P1 == 0 and P2 >= 0 and P3 >= length:
           start_point_set.append(pya.Point(P4 , P5 -length))
        elif P1 == 0 and P2 >= 0 and P3 < length:
           start_point_set.append(pya.Point(P4 , P5 - P3))
        elif P1 == 0 and P2 < 0 and P3 >= length:
           start_point_set.append(pya.Point(P4 , P5 + length))
        elif P1 == 0 and P2 < 0 and P3 < length:
           start_point_set.append(pya.Point(P4 , P5 + P3))        
        else:
           pass

def end_point():
    for j in range(0, count_90):
        P1 = target_90_pairs_set[j].second.d().x 
        P2 = target_90_pairs_set[j].second.d().y
        P3 = target_90_pairs_set[j].second.length()
        P4 = center_90_set[j].x
        P5 = center_90_set[j].y
        length = 20000

        if P1 >= 0 and P2 == 0 and P3 >= length:
            end_point_set.append(pya.Point(P4 + length, P5))
        elif P1 >= 0 and P2 == 0 and P3 < length:
            end_point_set.append(pya.Point(P4 + P3, P5))
        elif P1 < 0 and P2 == 0 and P3 >= length:
            end_point_set.append(pya.Point(P4 - length, P5))
        elif P1 < 0 and P2 == 0 and P3 < length:
            end_point_set.append(pya.Point(P4 - P3, P5))
        elif P1 == 0 and P2 >= 0 and P3 >= length:
            end_point_set.append(pya.Point(P4 , P5 + length))
        elif P1 == 0 and P2 >= 0 and P3 < length:
            end_point_set.append(pya.Point(P4 , P5 + P3))
        elif P1 == 0 and P2 < 0 and P3 >= length:
            end_point_set.append(pya.Point(P4 , P5 - length))
        elif P1 == 0 and P2 < 0 and P3 < length:
            end_point_set.append(pya.Point(P4 , P5 - P3))
        else:
           pass

def tear():
    for x in range(0, count_90):
        P1 = start_point_set[x]
        P2 = center_90_set[x]
        P3 = end_point_set[x]
        poly = pya.Polygon( [ P1, P2, P3 ] )
        top.shapes(tear_drop).insert(poly)

center_90()
start_point()
end_point()
tear()

Comments

  • edited July 2022

    Now you are challenging me :)

    Here is a simple solution that uses a DRC function with an intra-polygon space check with an above-90 angle limit to generate notch violation markers on inner 90 degree-corners. 1µm is the edge length of the marker:

    original = input(...)
    patches = original.notch(1.um, angle_limit(91.0))
    (original + patches.polygons).output(...)
    

    Result:

    This approach will not work properly on edges less than 1µm length because then the chamfering gets non-45 degree.

    The rounded-corner approach works as well (DRC) when you set the outer corner radius to 0 and use the octagon approximation (n=8):

    original = input(...)
    f = 1.0 + Math::sqrt(0.5)
    original.rounded_corners(f * 1.um, 0.um, 8).output(...)
    

    Caveat: the corner rounding does not apply full radius on edges shorter than f µm (f is 1.707...) but applies a smaller chamfering:

    So both approaches have a slight disadvantage that you may be able to mitigate with specific design rules.

    Matthias

  • hello Matthias,

    You should be kidding me.. :)
    Method one already works perfectly to me, I have no ruby background, so I missed many great information in the manual, even can not understand properly.You solution always hit the problem in 3 rows, it is magic.

    I am not sure why I can not upload a gds file on.. so please see below image:
    the solution also can make the tear for a trace connect to circle, it's already good enough to tape out mask if merged polygons, even better than what Cadence did, but I will keep trying to make it as " triangle " shape to reduce file size if convert to MEBES. Maybe will share another long code again, when it's done :D

  • Very good, thanks for the feedback :)

    You can upload GDS if you zip it. This forum does now know about our favourite file formats ...

    Matthias

  • edited July 2022

    Hello Matthias,

    It's my bad, I should notice that, updated test gds ok, and trying to solve it with math.(sin , cos, tan) friends..

  • edited July 2022

    Hello Matthias,

    Try & error, come out below code, it seems work,
    Thanks for your perfect reply and this creative tool :)

    Vincent

    from pya import *
    import math
    
    cellview = pya.CellView.active()
    layout = cellview.layout()
    top = cellview.cell
    
    metal = layout.layer(9 , 0)
    bump = layout.layer(6 , 0)
    tear_drop = layout.layer(5,0)
    
    pya.Application.instance().main_window().current_view().add_missing_layers()
    
    
    
    #############################################################
    
    tear_length = 30000
    
    #############################################################
    
    
    pillar = pya.Region(top.begin_shapes_rec(bump))
    metal_poly = pya.Region(top.begin_shapes_rec(metal))
    dummy = ( pillar + metal_poly).merged()
    #dummy = ( pillar + (pillar.sized(50000) & metal_poly)).merged()  # use this dummy instead of previous will enhance success rate 
    
    
    target = dummy.with_angle(181, 359 , False)
    target_pairs = [ k for k in target.each() ]
    
    for pair in target_pairs:
        if pair.first.length() > pair.second.length():
            P1 = pair.first.p2 + tear_length*(pair.first.p1 - pair.first.p2)/pair.first.length()
            P2 = pair.first.p2
            P3 = pair.second.p1 + tear_length*(pair.second.p2 - pair.second.p1)/pair.second.length()
            pillar_hull_coordinate = []
            pillar_corner_to_p3 = []
            for k in [ k for k in pillar ]:
                for l in range(k.num_points_hull()):
                    pillar_hull_coordinate.append(k.point_hull(l))
                    pillar_corner_to_p3.append(k.point_hull(l).distance(P3))
            for k in pillar_hull_coordinate:
                if k.distance(P3) == min(pillar_corner_to_p3):
                    P3 = k
            tear = pya.Polygon([ P1, P2, P3 ])
            top.shapes(tear_drop).insert(tear) 
    
        else:
            pass
    
    
        if pair.first.length() < pair.second.length():
            P1 = pair.first.p2 + tear_length*(pair.first.p1 - pair.first.p2)/pair.first.length()
            P2 = pair.first.p2
            P3 = pair.second.p1 + tear_length*(pair.second.p2 - pair.second.p1)/pair.second.length()
            pillar_hull_coordinate = []       
            pillar_corner_to_p1 = []
            for k in [ k for k in pillar ]:
                for l in range(k.num_points_hull()):
                    pillar_hull_coordinate.append(k.point_hull(l))
                    pillar_corner_to_p1.append(k.point_hull(l).distance(P1))
            for k in pillar_hull_coordinate:
                if k.distance(P1) == min(pillar_corner_to_p1):
                    P1 = k
            tear = pya.Polygon([ P1, P2, P3 ])
            top.shapes(tear_drop).insert(tear) 
    
  • edited July 2022

    Of course this solution is valid too and I really want encourage users to try their own ways. So please go ahead and thanks for sharing the code! :)

    There are a few things you're probably aware of:

    • It's slower because of the Python loop
    • You will see all patches cells on top level of the hierarchy. The DRC-based solutions can be made hierarchy-aware (patches pushed down into the hierarchy) by simply using "deep" mode.

    Kind regards,

    Matthias

Sign In or Register to comment.