2023-12-30 19:53:16 -05:00
#!/usr/bin/python3
import os
import sys
2023-12-30 20:44:57 -05:00
hasGDSTK = True
try :
import gdstk
except ImportError :
hasGDSTK = False
2023-12-30 19:53:16 -05:00
def stripComments ( line ) :
escape = False
inString = False
for i , c in enumerate ( line ) :
if not escape and ( c == " \" " or c == " \' " ) :
inString = not inString
elif inString and c == " \\ " :
escape = not escape
elif not inString and c == " # " :
return line [ 0 : i ]
else :
escape = False
return line
def loadActConf ( path ) :
result = dict ( )
stack = [ result ]
with open ( path , " r " ) as fptr :
for number , line in enumerate ( fptr ) :
if " # " in line :
line = line [ 0 : line . index ( " # " ) ]
args = [ arg . strip ( ) for arg in line . strip ( ) . split ( " " ) ]
if len ( args ) > 0 :
if args [ 0 ] == " include " :
result = result | loadActConf ( args [ 1 ] [ 1 : - 1 ] )
elif args [ 0 ] == " begin " :
stack [ - 1 ] [ args [ 1 ] ] = dict ( )
stack . append ( stack [ - 1 ] [ args [ 1 ] ] )
elif args [ 0 ] == " end " :
stack . pop ( )
elif args [ 0 ] == " string " :
stack [ - 1 ] [ args [ 1 ] ] = args [ 2 ] [ 1 : - 1 ]
elif args [ 0 ] == " int " :
stack [ - 1 ] [ args [ 1 ] ] = int ( args [ 2 ] )
elif args [ 0 ] == " real " :
stack [ - 1 ] [ args [ 1 ] ] = float ( args [ 2 ] )
elif args [ 0 ] == " int_table " :
stack [ - 1 ] [ args [ 1 ] ] = [ int ( arg ) for arg in args [ 2 : ] ]
elif args [ 0 ] == " string_table " :
stack [ - 1 ] [ args [ 1 ] ] = [ arg [ 1 : - 1 ] for arg in args [ 2 : ] ]
return result
def queryGDS ( conf , rectLayer ) :
gds = [ ]
gds_bloat = [ ]
if rectLayer in conf [ " materials " ] :
if " gds " in conf [ " materials " ] [ rectLayer ] :
gds = conf [ " materials " ] [ rectLayer ] [ " gds " ]
if " gds_bloat " in conf [ " materials " ] [ rectLayer ] :
gds_bloat = conf [ " materials " ] [ rectLayer ] [ " gds_bloat " ]
else :
if rectLayer + " _gds " in conf [ " materials " ] [ " metal " ] :
gds = conf [ " materials " ] [ " metal " ] [ rectLayer + " _gds " ]
if rectLayer + " _gds_bloat " in conf [ " materials " ] [ " metal " ] :
gds_bloat = conf [ " materials " ] [ " metal " ] [ rectLayer + " _gds_bloat " ]
return zip ( gds , gds_bloat )
TAP , FILL , CELL , BLOCK = range ( 4 )
class Rect :
def __init__ ( self , label , layer , bounds , hint = " " , isInput = False , isOutput = False ) :
self . label = label
self . layer = layer
self . bounds = [ int ( bound ) for bound in bounds ]
self . hint = hint
self . isInput = isInput
self . isOutput = isOutput
def isIn ( searches , string ) :
for search in searches :
if search in string :
return True
return False
class Cell :
def __init__ ( self , name , kind , bbox , rects ) :
self . name = name
self . kind = kind
self . bbox = bbox
self . rects = rects
def readCell ( path ) :
name = os . path . splitext ( os . path . basename ( path ) ) [ 0 ]
kind = BLOCK
if " welltap " in name . lower ( ) :
kind = TAP
elif " fill " in name . lower ( ) :
kind = FILL
elif " cell " in name . lower ( ) :
kind = CELL
# left, bottom, right, top
bbox = [ 0 , 0 , 1 , 1 ]
rects = [ ]
with open ( path , " r " ) as rf :
for number , line in enumerate ( rf ) :
args = [ arg . strip ( ) for arg in line . split ( " " ) ]
if args [ 0 ] == " bbox " :
bbox = [ int ( arg ) for arg in args [ 1 : ] ]
else :
isInput = ( args [ 0 ] == " inrect " )
isOutput = ( args [ 0 ] == " outrect " )
hint = " "
if len ( args ) > = 8 :
hint = args [ 7 ]
rects . append ( Rect ( args [ 1 ] , args [ 2 ] , [ int ( arg ) for arg in args [ 3 : 7 ] ] , hint , isInput , isOutput ) )
return Cell ( name , kind , bbox , rects )
def writeLayerMap ( path , conf ) :
layers = zip ( conf [ " gds " ] [ " layers " ] , conf [ " gds " ] [ " major " ] , conf [ " gds " ] [ " minor " ] )
with open ( path , " w " ) as fptr :
for layer in layers :
2024-01-01 10:44:51 -05:00
name = layer [ 0 ]
purpose = " "
if " . " in name :
name , purpose = layer [ 0 ] . rsplit ( " . " , 1 )
2023-12-30 19:53:16 -05:00
if " via " in name and purpose in [ " drawing " , " dg " , " drw " ] :
print ( f " { name } VIA { layer [ 1 ] } { layer [ 2 ] } " , file = fptr )
elif purpose in [ " drawing " , " dg " , " drw " ] :
print ( f " { name } LEFOBS { layer [ 1 ] } { layer [ 2 ] } " , file = fptr )
elif purpose in [ " label " , " ll " , " lbl " ] :
print ( f " NAME { name } /PINNAME { layer [ 1 ] } { layer [ 2 ] } " , file = fptr )
print ( f " NAME { name } /PIN { layer [ 1 ] } { layer [ 2 ] } " , file = fptr )
print ( f " NAME { name } /LEFPINNAME { layer [ 1 ] } { layer [ 2 ] } " , file = fptr )
print ( f " NAME { name } /LEFPIN { layer [ 1 ] } { layer [ 2 ] } " , file = fptr )
elif purpose in [ " net " , " nt " ] :
print ( f " { name } NET { layer [ 1 ] } { layer [ 2 ] } " , file = fptr )
elif purpose in [ " pin " , " pin1 " , " pn " ] :
print ( f " { name } PIN,LEFPIN { layer [ 1 ] } { layer [ 2 ] } " , file = fptr )
elif purpose in [ " blockage " , " be " , " blo " ] :
print ( f " { name } BLOCKAGE { layer [ 1 ] } { layer [ 2 ] } " , file = fptr )
if " prb " in name . lower ( ) :
print ( f " DIEAREA ALL { layer [ 1 ] } { layer [ 2 ] } " , file = fptr )
2023-12-30 20:44:57 -05:00
def writeGDS ( path , conf , cell ) :
if not hasGDSTK :
2023-12-30 20:54:05 -05:00
print ( " skipping gds, enable gds support by installing gdstk: " )
print ( " pip install gdstk " )
2023-12-30 20:44:57 -05:00
return
scale = conf [ " general " ] [ " scale " ]
numMetals = conf [ " general " ] [ " metals " ]
layers = { layer : ( major , minor ) for layer , major , minor in zip ( conf [ " gds " ] [ " layers " ] , conf [ " gds " ] [ " major " ] , conf [ " gds " ] [ " minor " ] ) }
2024-01-01 10:44:51 -05:00
lib = gdstk . Library ( unit = scale * 1e-9 , precision = scale * 1e-9 )
2023-12-30 20:44:57 -05:00
gdsCell = lib . new_cell ( cell . name )
bndry = None
for layer , idx in layers . items ( ) :
2024-01-01 10:44:51 -05:00
name = layer
purpose = " "
if " . " in name :
name , purpose = layer . rsplit ( " . " , 1 )
if ( name == " text " and purpose in [ " drawing " , " dg " , " drw " ] ) or name == " outline " or name == " areaid_sc " :
gdsCell . add ( gdstk . rectangle ( ( cell . bbox [ 0 ] , cell . bbox [ 1 ] ) , ( cell . bbox [ 2 ] , cell . bbox [ 3 ] ) , layer = idx [ 0 ] , datatype = idx [ 1 ] ) )
2023-12-30 20:44:57 -05:00
for rect in cell . rects :
gds = queryGDS ( conf , rect . layer )
labelWritten = False
for layerName , bloat in gds :
2024-01-01 10:44:51 -05:00
name = layerName
purpose = " "
if " . " in name :
name , purpose = layerName . rsplit ( " . " , 1 )
2023-12-30 20:44:57 -05:00
if layerName in layers :
idx = layers [ layerName ]
2024-01-01 10:44:51 -05:00
gdsCell . add ( gdstk . rectangle ( ( ( rect . bounds [ 0 ] - bloat ) , ( rect . bounds [ 1 ] - bloat ) ) , ( ( rect . bounds [ 2 ] + bloat ) , ( rect . bounds [ 3 ] + bloat ) ) , layer = idx [ 0 ] , datatype = idx [ 1 ] ) )
2023-12-30 20:44:57 -05:00
if rect . label and rect . label != " # " and not labelWritten :
2024-01-01 10:44:51 -05:00
gdsCell . add ( gdstk . Label ( rect . label , ( ( rect . bounds [ 0 ] + rect . bounds [ 2 ] ) / 2 , ( rect . bounds [ 1 ] + rect . bounds [ 3 ] ) / 2 ) , layer = idx [ 0 ] , texttype = idx [ 1 ] ) )
2023-12-30 20:44:57 -05:00
labelWritten = True
lib . write_gds ( path )
2023-12-30 19:53:16 -05:00
def writeLEF ( path , conf , cell ) :
2024-01-01 10:44:51 -05:00
# See https://github.com/KLayout/klayout/blob/766dd675c11d98b2461c448035197f6e934cb497/src/plugins/streamers/lefdef/db_plugin/dbLEFDEFImporter.cc#L1085
# Purpose Name = Placement Code
# LEFPIN = LEFPIN
# PIN = PIN
# LEFPINNAME = LEFLABEL
# PINNAME = LABEL
# FILL = FILL
# FILLOPC = FILLOPC
# LEFOBS = OBS
# SPNET = SPNET
# NET = NET
# VIA = VIA
# BLOCKAGE = BLK
# ALL = [LEFPIN, PIN, FILL, FILLOPC, OBS, SPNET, NET, VIA]
2023-12-30 19:53:16 -05:00
scale = conf [ " general " ] [ " scale " ]
numMetals = conf [ " general " ] [ " metals " ]
with open ( path , " w " ) as fptr :
print ( f " MACRO { cell . name } " , file = fptr )
if cell . kind == TAP :
print ( " CLASS CORE WELLTAP ; " , file = fptr )
elif cell . kind == FILL :
print ( " CLASS CORE SPACER ; " , file = fptr )
elif cell . kind == CELL :
print ( " CLASS CORE ; " , file = fptr )
else :
print ( " CLASS BLOCK ; " , file = fptr )
print ( f " \t ORIGIN { - cell . bbox [ 0 ] * scale } { - cell . bbox [ 1 ] * scale } ; " , file = fptr )
print ( f " \t FOREIGN { cell . name } { cell . bbox [ 0 ] * scale } { cell . bbox [ 1 ] * scale } ; " , file = fptr )
print ( f " \t SIZE { ( cell . bbox [ 2 ] - cell . bbox [ 0 ] ) * scale } BY { ( cell . bbox [ 3 ] - cell . bbox [ 1 ] ) * scale } ; " , file = fptr )
print ( f " \t SYMMETRY X Y ; " , file = fptr )
if cell . kind in [ FILL , TAP , CELL ] :
print ( " \t SITE CoreSite ; " , file = fptr )
for rect in cell . rects :
if rect . isInput or rect . isOutput :
direction = " INOUT "
if not rect . isInput :
direction = " OUTPUT "
elif not rect . isOutput :
direction = " INPUT "
print ( f " \t PIN { rect . label } " , file = fptr )
if isIn ( [ " vnsub " , " vpsub " , " vddsub " , " vsssub " , " vddb " , " vssb " ] , rect . label . lower ( ) ) :
print ( " \t \t DIRECTION INOUT ; " , file = fptr )
print ( " \t \t USE POWER ; " , file = fptr )
elif isIn ( [ " vdd " , " pwr " ] , rect . label . lower ( ) ) :
print ( " \t \t DIRECTION INOUT ; " , file = fptr )
print ( " \t \t USE POWER ; " , file = fptr )
if cell . kind in [ FILL , TAP , CELL ] :
print ( " \t \t SHAPE ABUTMENT ; " , file = fptr )
elif isIn ( [ " gnd " , " vss " ] , rect . label . lower ( ) ) :
print ( " \t \t DIRECTION INOUT ; " , file = fptr )
print ( " \t \t USE GROUND ; " , file = fptr )
if cell . kind in [ FILL , TAP , CELL ] :
print ( " \t \t SHAPE ABUTMENT ; " , file = fptr )
else :
print ( f " \t \t DIRECTION { direction } ; " , file = fptr )
print ( f " \t \t USE SIGNAL ; " , file = fptr )
print ( " \t \t PORT " , file = fptr )
gds = queryGDS ( conf , rect . layer )
for layer , bloat in gds :
2024-01-01 10:44:51 -05:00
name = layer
purpose = " "
if " . " in name :
name , purpose = layer . rsplit ( " . " , 1 )
2023-12-30 19:53:16 -05:00
print ( f " \t \t \t LAYER { name } ; " , file = fptr )
print ( f " \t \t \t \t RECT { ( rect . bounds [ 0 ] - bloat ) * scale } { ( rect . bounds [ 1 ] - bloat ) * scale } { ( rect . bounds [ 2 ] + bloat ) * scale } { ( rect . bounds [ 3 ] + bloat ) * scale } ; " , file = fptr )
print ( " \t \t END " , file = fptr )
print ( f " \t END { rect . label } " , file = fptr )
print ( " \t OBS " , file = fptr )
for rect in cell . rects :
#if not rect.isInput and not rect.isOutput:
gds = queryGDS ( conf , rect . layer )
for layer , bloat in gds :
2024-01-01 10:44:51 -05:00
name = layer
purpose = " "
if " . " in name :
name , purpose = layer . rsplit ( " . " , 1 )
2023-12-30 19:53:16 -05:00
print ( f " \t \t LAYER { name } ; " , file = fptr )
print ( f " \t \t \t RECT { ( rect . bounds [ 0 ] - bloat ) * scale } { ( rect . bounds [ 1 ] - bloat ) * scale } { ( rect . bounds [ 2 ] + bloat ) * scale } { ( rect . bounds [ 3 ] + bloat ) * scale } ; " , file = fptr )
print ( " \t END " , file = fptr )
print ( f " END { cell . name } " , file = fptr )
print ( " " , file = fptr )
def print_help ( ) :
2023-12-30 20:44:57 -05:00
print ( " Usage: rect2lef.py [options] <input.rect> " )
2023-12-30 19:53:16 -05:00
print ( " \t -T<tech> \t identify the technology used for this translation. " )
2023-12-30 20:44:57 -05:00
print ( " \t -lm \t emit the layermap " )
print ( " \t -gds \t write the gds. " )
2023-12-30 19:53:16 -05:00
if __name__ == " __main__ " :
if len ( sys . argv ) < = 2 or sys . argv [ 1 ] == ' --help ' or sys . argv [ 1 ] == ' -h ' :
print_help ( )
else :
rectPath = None
2023-12-30 20:44:57 -05:00
doLM = False
doGDS = False
2023-12-30 19:53:16 -05:00
techName = " sky130 "
actHome = os . environ . get ( ' ACT_HOME ' , " /opt/cad " )
for arg in sys . argv [ 1 : ] :
if arg [ 0 ] == ' - ' :
if arg [ 1 ] == ' T ' :
techName = arg [ 2 : ]
2023-12-30 20:44:57 -05:00
elif arg == " -lm " :
doLM = True
elif arg == " -gds " :
doGDS = True
2023-12-30 19:53:16 -05:00
else :
print ( f " error: unrecognized option ' { arg } ' " )
print ( " " )
print_help ( )
sys . exit ( )
elif not rectPath :
rectPath = arg
conf = loadActConf ( actHome + " /conf/ " + techName + " /layout.conf " )
2023-12-30 20:44:57 -05:00
if rectPath :
2023-12-30 19:53:16 -05:00
cell = readCell ( rectPath )
writeLEF ( cell . name + " .lef " , conf , cell )
2023-12-30 20:44:57 -05:00
if doLM :
writeLayerMap ( " layermap.txt " , conf )
if doGDS :
writeGDS ( cell . name + " .gds " , conf , cell )