Ground-Signal-Ground routing

edited September 2017 in KLayout Support
Hi, I am doing some layout for an RF circuit. I need to create some routing from a component pad to a wirebond pad. The current "path" utility in KLayout creates a path of a defined width. Does someone have a script to create 3 paths (of specified width and spacing) to be able to create ground-signal-ground routing? The idea was to create a routing with a constant space between the two side ground wires to the central signal wire through the bends.

Any help or pointers would be appreciated.

Thanks,

Vikas

Comments

  • edited September 2017

    Paste the code below to new .lym file and choose Run On Startup. The code is really basic and doesn't do anything more than simple 90deg bends, and gets confused if it is fed non-manhattan geometry. You can expand it to do sine bends or whatever, if you like.

    As a bonus, the GSG lines are reconfigurable! Choose Partial tool and then drag an edge (don't drag a vertex, because you risk breaking the manhattan geometry, which breaks the code), and it recalculates the curved path and draws it anew. Or to change its parameters, use the Select tool, click on it, then press 'q' and change the spacing or width or whatever.

    David

    ## (PCell) GSG wires.lym
    ## By davidnhutch
    ## 
    ## Draws GSG wires. Only works with manhattan input paths for now.
    ## To use: Select a path. Edit > Selection > Convert to PCell > WgLib.GSGWires.
    ## Then drag edges around using the "Partial" tool. Don't drag vertices since
    ## this will often break the manhattan geometry.
    
    module WgLib
      include RBA
    
      WgLib.constants.member?(:GSGWires) && remove_const(:GSGWires)
      WgLib.constants.member?(:WgLib) && remove_const(:WgLib)
    
      class GSGWires < PCellDeclarationHelper
        include RBA
    
        def initialize
          super
    
          default_width = 1.0
          default_radius = 10.0
          default_npoints = 64
          default_gwidth = 20.0
          default_swidth = 10.0
          default_gap = 5.0
          len = 100.0
          points_backbone = [[0.0,0.0],[len,0.0],[len,len],[2*len,len]]
    
          points_x = points_backbone.transpose[0]
          points_y = points_backbone.transpose[1]
          points_x.map! { |v| "#{v}" }
          points_y.map! { |v| "#{v}" }
          default_numpts = points_backbone.length
          dpoints_backbone = []
          points_backbone.each { |p| dpoints_backbone.push(DPoint.new(p[0],p[1])) }
    
          param(:width, TypeDouble, "Width", :default => default_width, :unit => "um")
          param(:radius, TypeDouble, "Radius", :default => default_radius, :unit => "um")
          param(:npoints, TypeInt, "Number of points", :default => default_npoints)   
          param(:gwidth, TypeDouble, "Width of ground wires", :default => default_gwidth, :unit => "um")   
          param(:swidth, TypeDouble, "Width of signal wire", :default => default_swidth, :unit => "um") 
          param(:gap, TypeDouble, "Width of gap between wires", :default => default_gap, :unit => "um") 
          param(:l, TypeLayer, "Layer", :default => LayerInfo::new(1, 0))
    
          param(:xcoords, TypeList, "x coords", :default => points_x, :hidden => true)
          param(:ycoords, TypeList, "y coords", :default => points_y, :hidden => true)
          param(:xcoordsu, TypeList, "x coords handle tracker", :default => points_x, :hidden => true)
          param(:ycoordsu, TypeList, "y coords handle tracker", :default => points_y, :hidden => true)
    
          param(:s, TypeShape, "", :default => DPath::new(dpoints_backbone, default_width)) # Not sure why, but it seems this has to be a DPath not a Path.
    
        end
    
        def display_text_impl
          "GSGWires(R=#{radius.to_s},N=#{npoints.to_s})"
        end
    
        def coerce_parameters_impl  
          dbu = layout.dbu
    
          # Get the points that make up the wire's backbone
          ptarr = []
          s.each_point { |p| ptarr << p } # These are DPoints, because they come from the guiding shape which is a DPath
          npoints = ptarr.length # If the number of points has changed, update the npoints parameter.
    
          # Set the x and y coords
          x = []
          y = []
          dpath_arr = []
          ptarr.length.times { |i|
            x << "#{ptarr[i].x}"
            y << "#{ptarr[i].y}"
            xx = eval(x[i])
            yy = eval(y[i])
            dpath_arr << DPoint.new(xx,yy)
          }
          set_xcoords(x)
          set_ycoords(y)
          set_xcoordsu(x)
          set_ycoordsu(y)
          set_s(DPath.new(dpath_arr, width)) 
        end
    
        def can_create_from_shape_impl
          shape.is_path?
        end
    
        def parameters_from_shape_impl
          dbu = layout.dbu
    
          # Get the backbone points from the guiding shape
          ptarr = []
          shape.each_point { |p| ptarr << p }
          npoints = ptarr.length
    
          # Set the x and y coords
          x = []
          y = []
          dpath_arr = []
          ptarr.length.times { |i|
            x << "#{ptarr[i].x * dbu}"
            y << "#{ptarr[i].y * dbu}"
            xx = eval(x[i])
            yy = eval(y[i])
            dpath_arr << DPoint.new(xx,yy)
          }
          set_xcoords(x)
          set_ycoords(y)
          set_xcoordsu(x)
          set_ycoordsu(y)
    
          width = shape.path.width*dbu
          set_s(DPath.new(dpath_arr, width))
          set_width(width)
          set_l(layout.get_info(layer))
        end
    
        def transformation_from_shape_impl
          # Because it's a path, all its points are absolute, so there is no need 
          # for a transformation. So we set the transformation to the trivial one.
          Trans.new
        end
    
        # A custom function follows
        def get_rbend_curved_wg(pts_backbone, r, curve_num_pts)
          dbu = layout.dbu
          r = r/dbu
          # Get needed parameters
          i = 0
          pts = pts_backbone
          num_pts_backbone = pts_backbone.length
    
          curved_path=[]
          curved_path[0]=pts[0] # Write the first vertex
    
          if num_pts_backbone != 2 # As long as it's not a straight wire...
            counter = 0
    
            # In the following block we get a vector that includes the prev pt, the 
            # curr pt, and the next pt. Need all three to calculate the curve
            pts.each_cons(3) { |pcn|
              counter += 1
              p = pcn[0] # Previous
              c = pcn[1] # Current
              n = pcn[2] # Next
    
              pi=Math::PI
              # Figure out if the current line segment is traveling left to right,
              # top to bottom, etc...
              if p.x<c.x && p.y==c.y && c.x==n.x && c.y<n.y
                theta_start = 3*pi/2
                theta_end = 2*pi;
                dx = -r
                dy = r
              elsif p.x<c.x &&p.y==c.y && c.x==n.x && c.y>n.y
                theta_start = pi/2
                theta_end = 0
                dx = -r
                dy = -r
              elsif p.x==c.x && p.y<c.y && c.x<n.x && c.y==n.y
                theta_start = pi
                theta_end = pi/2
                dx = r
                dy = -r
              elsif p.x==c.x && p.y>c.y && c.x<n.x && c.y==n.y
                theta_start = pi
                theta_end = 3*pi/2
                dx = r
                dy = r
              elsif p.x==c.x && p.y>c.y && c.x>n.x && c.y==n.y
                theta_start = 2*pi
                theta_end = 3*pi/2
                dx = -r
                dy = r
              elsif p.x == c.x && p.y<c.y && c.x>n.x &&c.y==n.y
                theta_start = 0
                theta_end = pi/2
                dx = -r
                dy = -r
              elsif p.x>c.x && p.y==c.y && c.x==n.x && c.y>n.y
                theta_start = pi/2
                theta_end = pi
                dx = r
                dy = -r
              elsif p.x>c.x && p.y==c.y && c.x==n.x && c.y<n.y
                theta_start = 3*pi/2
                theta_end = pi
                dx = r
                dy = r
              else
                err_msg = "This shouldn't happen. You may have dragged one of the "\
                          "points resulting in a non-manhattan geometry. Press "\
                          "Undo, then drag an edge instead."
                MessageBox.info("Error", err_msg, MessageBox.b_ok)
              end
    
              # Make a linearly spaced array from theta_start to theta_end with 
              # curve_num_pts number of points
              dtheta = (theta_end-theta_start)/(curve_num_pts-1)
              theta = []
              for j in 0..curve_num_pts-1
                theta[j] = j*dtheta + theta_start
              end
    
              # Loop through each new curvy point for a given vertex
              theta.each { |angle|
                curved_path_x = (c.x + dx + r*Math::cos(angle))
                curved_path_y = (c.y + dy + r*Math::sin(angle))
                curved_path[i+1] = RBA::Point.new(curved_path_x,curved_path_y) 
                i+=1
              }
            }
            curved_path[i] = pts[pts.length - 1] #write the final vertex
    
            # Repair first vertex
            pt0 = pts[0]
            pt1 = pts[1]
            pt3 = pts[2]
    
            c0 = curved_path[0]
            c1 = curved_path[1]
    
            if (pt0.x == pt1.x && pt0.y < pt1.y && pt1.x < pt3.x && pt1.y == pt3.y) #if starts D->U, then bends to become L->R
              curved_path[1]   = Point.new(c0.x,c1.y)
            elsif (pt0.x == pt1.x && pt0.y > pt1.y && pt1.x > pt3.x && pt1.y == pt3.y) #if starts U->D, then bends to become R->L
              curved_path[1]   = Point.new(c0.x,c1.y)
            elsif (pt0.y == pt1.y && pt0.x < pt1.x && pt1.x == pt3.x && pt1.y < pt3.y) #if starts L->R, then bends to become D->U
              curved_path[1]   = Point.new(c1.x,c0.y) 
            elsif (pt0.y == pt1.y && pt0.x > pt1.x && pt1.x == pt3.x && pt1.y > pt3.y) #if starts R->L, then bends to become U->D
              curved_path[1]   = Point.new(c1.x,c0.y)
            elsif (pt0.x == pt1.x && pt0.y > pt1.y && pt1.x < pt3.x && pt1.y == pt3.y)  #if starts U->D, then bends to become L->R
              curved_path[1]   = Point.new(c0.x,c1.y)
            elsif (pt0.x == pt1.x && pt0.y < pt1.y && pt1.x > pt3.x && pt1.y == pt3.y) #if starts D->U, then bends to become R->L
              curved_path[1]   = Point.new(c0.x,c1.y)
            elsif (pt0.y == pt1.y && pt0.x > pt1.x && pt1.x == pt3.x && pt1.y < pt3.y) #if starts R->L, then bends to become D->U
              curved_path[1]   = Point.new(c1.x,c0.y)
            elsif (pt0.y == pt1.y && pt0.x < pt1.x && pt1.x == pt3.x && pt1.y > pt3.y) #if starts L->R, then bends to become U->D
              curved_path[1]   = Point.new(c1.x,c0.y)
            else
              p "First vertex is none of these; #{pt0.y} == #{pt1.y} && #{pt0.x} < #{pt1.x} && #{pt1.x} == #{pt3.x} && #{pt1.y} > #{pt3.y}"
            end
    
            # Repair last vertex
            pt0 = pts[pts.length - 3]
            pt1 = pts[pts.length - 2]
            pt3 = pts[pts.length - 1]
    
            ci1= curved_path[i-1]
            ci = curved_path[i]
    
            if (pt0.x == pt1.x && pt0.y < pt1.y && pt1.x < pt3.x && pt1.y == pt3.y) #if starts D->U, then bends to become L->R
              curved_path[i-1] = Point.new(ci1.x,ci.y)
            elsif (pt0.x == pt1.x && pt0.y > pt1.y && pt1.x > pt3.x && pt1.y == pt3.y) #if starts U->D, then bends to become R->L
              curved_path[i-1] = Point.new(ci1.x,ci.y)
            elsif (pt0.y == pt1.y && pt0.x < pt1.x && pt1.x == pt3.x && pt1.y < pt3.y) #if starts L->R, then bends to become D->U
              curved_path[i-1] = Point.new(ci.x,ci1.y)  
            elsif (pt0.y == pt1.y && pt0.x > pt1.x && pt1.x == pt3.x && pt1.y > pt3.y) #if starts R->L, then bends to become U->D
              curved_path[i-1] = Point.new(ci.x,ci1.y)
            elsif (pt0.x == pt1.x && pt0.y > pt1.y && pt1.x < pt3.x && pt1.y == pt3.y)  #if starts U->D, then bends to become L->R
              curved_path[i-1] = Point.new(ci1.x,ci.y)
            elsif (pt0.x == pt1.x && pt0.y < pt1.y && pt1.x > pt3.x && pt1.y == pt3.y) #if starts D->U, then bends to become R->L
              curved_path[i-1] = Point.new(ci1.x,ci.y)
            elsif (pt0.y == pt1.y && pt0.x > pt1.x && pt1.x == pt3.x && pt1.y < pt3.y) #if starts R->L, then bends to become D->U
              curved_path[i-1] = Point.new(ci.x,ci1.y)
            elsif (pt0.y == pt1.y && pt0.x < pt1.x && pt1.x == pt3.x && pt1.y > pt3.y) #if starts L->R, then bends to become U->D
              curved_path[i-1] = Point.new(ci.x,ci1.y)  
            else
              p "Final vertex is none of these; #{pt0.y} == #{pt1.y} && #{pt0.x} < #{pt1.x} && #{pt1.x} == #{pt3.x} && #{pt1.y} > #{pt3.y}"
            end
    
          else # If there are only two points in the wire
            curved_path[0] = pts[0]
            curved_path[1] = pts[1]
          end
          curved_path
        end
    
        def produce_impl
          dbu = layout.dbu
          set_xcoordsu(xcoords)
          set_ycoordsu(ycoords)
          # Fetch the parameters first
          xuarr = []
          yuarr = []
          numwirepts = xcoords.length
          numwirepts.times { |i|
            xuarr.push(xcoords[i])
            yuarr.push(ycoords[i])
          }
          xuarr.map! { |v| eval(v) / dbu }
          yuarr.map! { |v| eval(v) / dbu }
    
          ptarr = []
          numwirepts.times { |i| ptarr.push(Point.from_dpoint(DPoint::new(xuarr[i],yuarr[i]))) }
          ptarrcurved = get_rbend_curved_wg(ptarr, radius, npoints)
          #cell.shapes(l_layer).insert(Path.new(ptarrcurved,width/dbu))
    
          outer = Path.new(ptarrcurved, (2*gwidth + 2*gap + swidth)/dbu).polygon
          mid = Path.new(ptarrcurved, (2*gap + swidth)/dbu).polygon
          inner = Path.new(ptarrcurved, swidth/dbu).polygon
    
          ep = EdgeProcessor::new
          ground_wires = ep.boolean_p2p([outer],[mid], EdgeProcessor::ModeANotB, false, false)
          signal_wire = inner
    
          ground_wires.each { |ground_wire|
            cell.shapes(l_layer).insert(ground_wire)
          }
          cell.shapes(l_layer).insert(signal_wire)
    
        end
      end
    
      class WgLib < Library
        def initialize  
          self.description = "Waveguide library"
          layout.register_pcell("GSGWires", GSGWires::new)
          register("WgLib")
        end
      end
      WgLib::new
    end
    
  • edited November -1
    Hi David,

    I have only two comments for you:
    1) Thank you
    2) YOU ROCK!!

    Thanks,

    Vikas
  • edited November -1

    I'm impressed too :-)

    Matthias

Sign In or Register to comment.