import odb from enum import Enum import random import math import copy def ErrorQuit(msg): print("ERROR: %s" %(msg)) exit(1) # bookshelf object type class BsType (Enum): MOVABLE_STD_INST = 0 MOVABLE_MACRO_INST = 1 FIXED_INST = 2 PRIMARY = 3 class Instance: def __init__(self, name, lx, ly, width, height, isFixed): self.name = name self.lx = lx self.ly = ly self.width = width self.height = height self.isFixed = isFixed self.numOutputs = 0 self.numInputs = 0 self.isFeasible = True # only used for FIXED insts to generate fake macro lef. # have (offsetX,offsetY,direction) pairs on each pin self.pins = [] # saves dbITerm objects self.odbIPins = [] self.odbOPins = [] self.odbIPinIdx = 0 self.odbOPinIdx = 0 # only used for FIXED insts to generate fake macro lef. # have (lx, ly, ux, uy) pairs on each obs. self.obses = [] # saves odbInst pointer self.odbInst = None # odbMaster will be used only for "FixedInsts" self.odbMaster = None # odbClkPin will be used to create clk net self.odbClkPin = None def SetLxLy(self, lx, ly): self.lx = lx self.ly = ly def GetUxUy(self): return self.lx + self.width, self.ly + self.height # for nets traversing def IncrOutPin(self): self.numOutputs += 1 def IncrInPin(self): self.numInputs += 1 def NumInputs(self): return self.numInputs def NumOutputs(self): return self.numOutputs def HasObs(self): return len(self.obses) != 0 # takes x, y from *.shapes -- coordinates (placedX, placedY) will be given def AddObs(self, shapeX, shapeY, width, height): placeX = shapeX - self.lx placeY = shapeY - self.ly self.obses.append([placeX, placeY, placeX + width, placeY + height]) # takes x, y from *.nets -- all offsets are based on cell center! def AddPin(self, netX, netY,direction): self.pins.append([netX + self.width/2.0, netY + self.height/2.0, direction]) # add Odb ITerm input pins def AddOdbIPin(self, odbIPin): self.odbIPins.append(odbIPin) # add Odb ITerm ouput pins def AddOdbOPin(self, odbOPin): self.odbOPins.append(odbOPin) # set Odb ITerm clk pins for FF def SetClkPin(self, odbClkPin): self.odbClkPin = odbClkPin # hashmap should be based on oPins/iPins def GetHashName(self): # return 10000 * self.width + 100* self.numOutputs + self.numInputs return 10000 * self.numInputs + self.width def IsFeasible(self): # there are weird cells in bookshelf # 1. numInputs = 0 # 2. numOutputs = 0 # 3. numOutputs = 2 # following will ignore these cases # note that cases 1 and 2 will cause # "huge insts removal" at commercial pnr flow return self.isFeasible and self.numInputs != 0 and self.numOutputs == 1 def SetIsFeasible(self, val): self.isFeasible = val # Previous A2A author set FF on the following types: def IsFF(self, minFFWidth = 20): return self.numInputs == 1 and self.numOutputs == 1 and self.width >= minFFWidth # update odbInst pointer def SetOdbInst(self, odbInst, ffClkPinList = None): self.odbInst = odbInst dbITerms = self.odbInst.getITerms() for dbITerm in dbITerms: if ffClkPinList != None: if dbITerm.getMTerm().getName() in ffClkPinList: self.SetClkPin(dbITerm) else: if dbITerm.getSigType() == "CLOCK": self.SetClkPin(dbITerm) if dbITerm.getSigType() == "SIGNAL": if dbITerm.getIoType() == "OUTPUT": self.AddOdbOPin(dbITerm) elif dbITerm.getIoType() == "INPUT": self.AddOdbIPin(dbITerm) #update odbMaster pointer for fixed insts def SetOdbMaster(self, odbMaster): self.odbMaster = odbMaster # used when mapping def RetrieveOdbInPin(self): if len(self.odbIPins) <= self.odbIPinIdx: print("WRONG(In):", self.name, self.odbIPins, self.odbIPinIdx) retPin = self.odbIPins[self.odbIPinIdx] self.odbIPinIdx += 1 return retPin def RetrieveOdbOutPin(self): if self.odbOPinIdx >= len(self.odbOPins): print("WRONG(Out):", self.name, self.odbOPinIdx, self.odbOPins) retPin = self.odbOPins[self.odbOPinIdx] self.odbOPinIdx += 1 return retPin class Primary: def __init__(self, name, x, y, pinDir): self.name = name self.x = x self.y = y self.pinDir = pinDir self.odbBTerm = None def SetDir(self, pinDir): self.pinDir = pinDir def SetXY(self, x, y): self.x = x self.y = y def SetOdbBTerm(self, odbBTerm): self.odbBTerm = odbBTerm class BookshelfToOdb: def __init__(self, opendbpy, opendb, file_dir, auxName, siteName, macroInstPinLayer = None, macroInstObsLayer = None, primaryLayer = None, mastersFileName = None, ffClkPinList = None, clkNetName = "clk", # minFFWidth = 20, targetFFRatio = 0.12, customFPRatio = 1.0): self.odbpy = opendbpy self.odb = opendb self.file_dir = file_dir self.auxName = file_dir + '/' + auxName self.designName = auxName.split(".")[0] self.siteName = siteName self.ffClkPinList = ffClkPinList self.clkNetName = clkNetName # self.minFFWidth = minFFWidth self.targetFFRatio = targetFFRatio self.customFPRatio = customFPRatio # *.shape determine self.bsShapeList = [] # 1. parse bookshelf self.ParseAux() # 2. init the odb object. self.InitOdb( macroInstPinLayer, macroInstObsLayer, primaryLayer) self.PostProcessInBookshelf() # 3. parse odb object self.ParseMasterUseList(mastersFileName) # 4. map bookshelf and odb self.ClassifyInsts() # 5. finally Fill in OpenDB self.FillOdb() def ParseAux(self): f = open(self.auxName, 'r') cont = f.read() f.close() print("Parsing %s ..." % (self.auxName) ) nodeName = "" netName = "" shapeName = "" sclName = "" plName = "" routeName = "" for curLine in cont.split("\n"): # should skip 1st, 2nd elements for curFile in curLine.strip().split()[2:]: if curFile.endswith("nodes"): nodeName = self.file_dir + '/' + curFile elif curFile.endswith("nets"): netName = self.file_dir + '/' + curFile elif curFile.endswith("shapes"): shapeName = self.file_dir + '/' + curFile elif curFile.endswith("scl"): sclName = self.file_dir + '/' + curFile elif curFile.endswith("pl"): plName = self.file_dir + '/' + curFile elif curFile.endswith("route"): routeName = self.file_dir + '/' + curFile elif curFile.endswith("wts"): print("[WARNING] *.wts will be ignored") if nodeName == "": ErrorQuit("*.nodes is missing") if netName == "": ErrorQuit("*.nets is missing") if sclName == "": ErrorQuit("*.scl is missing") if plName == "": ErrorQuit("*.pl is missing") self.ParseNodes(nodeName) self.ParsePl(plName) self.ParseNets(netName) self.ParseScl(sclName) # The *.shape is optional! if shapeName != "": self.ParseShapes(shapeName) if routeName != "": self.ParseRoutes(routeName) print("Bookshelf Parsing Done") def ParseNodes(self, nodeName): print("Parsing %s ..." % (nodeName) ) f = open(nodeName, 'r') cont = f.read() f.close() self.bsInstList = [] for curLine in cont.split("\n"): curLine = curLine.strip() if curLine.startswith("UCLA") or curLine.startswith("#") \ or curLine == "": continue if curLine.startswith("NumNodes"): numNodes = int(curLine.split(" ")[-1]) elif curLine.startswith("NumTerminals"): numTerminals = int(curLine.split(" ")[-1]) else: self.bsInstList.append(curLine.split()) print("From Nodes: NumTotalNodes: %d" % (numNodes)) print("From Nodes: NumTerminals: %d" % (numTerminals)) numInsts = 0 numFixedInsts = 0 numPrimary = 0 for curInst in self.bsInstList: if len(curInst) == 3: numInsts += 1 elif curInst[-1] == "terminal": numFixedInsts += 1 elif curInst[-1] == "terminal_NI": numPrimary += 1 print("Parsed Nodes: NumInsts: %d" % (numInsts)) print("Parsed Nodes: NumFixedInsts: %d" % (numFixedInsts)) print("Parsed Nodes: NumPrimary: %d" % (numPrimary)) print("") def ParseNets(self, netName): print("Parsing %s ..." % (netName) ) f = open(netName, 'r') cont = f.read() f.close() tmpNetlist = [] for curLine in cont.split("\n"): curLine = curLine.strip() if curLine.startswith("UCLA") or curLine.startswith("#") \ or curLine == "": continue if curLine.startswith("NumNets"): numNets = int(curLine.split(" ")[-1]) elif curLine.startswith("NumPins"): numPins = int(curLine.split(" ")[-1]) else: tmpNetlist.append(curLine.split()) print("From Nets: NumNets: %d" % (numNets)) print("From Nets: NumPins: %d" % (numPins)) self.bsNetList = [] for idx, curArr in enumerate(tmpNetlist): if curArr[0] == "NetDegree": numPinsInNet = int(curArr[2]) netName = curArr[3] pinArr = [l for l in tmpNetlist[(idx+1):(idx+numPinsInNet+1)]] self.bsNetList.append([netName, pinArr]) print("Parsed Nets: NumNets: %d" %(len(self.bsNetList))) print("") def ParseShapes(self, shapeName): print("Parsing %s ..." % (shapeName) ) f = open(shapeName, 'r') cont = f.read() f.close() tmpShapeList = [] for curLine in cont.split("\n"): curLine = curLine.strip() if curLine.startswith("UCLA") or curLine.startswith("#") \ or curLine == "": continue if curLine.startswith("NumNonRectangularNodes"): numNonRectNodes = int(curLine.split(" ")[-1]) else: tmpShapeList.append(curLine.split()) print("From Shapes: NumRectLinearInsts: %d" % (numNonRectNodes)) self.bsShapeList = [] for idx, curArr in enumerate(tmpShapeList): # find 'InstName : numShapes' string if curArr[1] == ":": instName = curArr[0] numShapesInInst = int(curArr[2]) shapeArr = tmpShapeList[(idx+1):(idx+numShapesInInst+1)] self.bsShapeList.append([instName,shapeArr]) print("Parsed Shapes: NumRectLinearInsts: %d" %(len(self.bsShapeList))) print("") def ParseScl(self, sclName): print("Parsing %s ..." % (sclName) ) f = open(sclName, 'r') cont = f.read() f.close() tmpRowList = [] for curLine in cont.split("\n"): curLine = curLine.strip() if curLine.startswith("UCLA") or curLine.startswith("#") \ or curLine == "": continue if curLine.startswith("NumRows") or curLine.startswith("Numrows"): numRows = int(curLine.split(" ")[-1]) else: tmpRowList.append(curLine.split()) print("From scl: NumRows: %d" % (numRows)) # extract indices on CoreRow/End coreRowIdxList = [idx for idx,tmpArr in enumerate(tmpRowList) if tmpArr[0] == "CoreRow"] endIdxList = [idx for idx,tmpArr in enumerate(tmpRowList) if tmpArr[0] == "End"] if len(coreRowIdxList) != len(endIdxList): ErrorQuit("The number of CoreRow and End is different in scl!") self.bsRowList = [] for idx1, idx2 in zip(coreRowIdxList, endIdxList): self.bsRowList.append(tmpRowList[idx1:idx2]) print("Parsed scl: NumRows: %d" %(len(self.bsRowList))) print("") def ParsePl(self, plName): print("Parsing %s ..." % (plName) ) f = open(plName, 'r') cont = f.read() f.close() self.bsPlList = [] for curLine in cont.split("\n"): curLine = curLine.strip() if curLine.startswith("UCLA") or curLine.startswith("#") \ or curLine == "": continue self.bsPlList.append(curLine.split()) print("Parsed pl: NumInstsInPl: %d" % (len(self.bsPlList))) numFixedInsts = [0 for curArr in self.bsPlList if curArr[-1] == "/FIXED"] numPrimary = [0 for curArr in self.bsPlList if curArr[-1] == "/FIXED_NI"] print("Parsed pl: NumFixedInsts: %d" % (len(numFixedInsts))) print("Parsed pl: NumPrimary: %d" % (len(numPrimary))) print("") def ParseRoutes(self, routeName): pass def IsFeasibleMaster(self, odbMaster): numOutputs = 0 numInputs = 0 for mTerm in odbMaster.getMTerms(): if mTerm.getSigType() == "SIGNAL": # skip for FF clk pin if self.ffClkPinList != None: if mTerm.getConstName() in self.ffClkPinList: continue if mTerm.getIoType() == "OUTPUT": numOutputs += 1 elif mTerm.getIoType() == "INPUT": numInputs += 1 # limit the cells as numOutputs == 1 # In particular, allow 2-pin outputs on FFs. (e.g., Q/QN pin cases) if odbMaster.isSequential(): isFeasible = (numOutputs in [1,2] and numInputs != 0) else: isFeasible = (numOutputs == 1 and numInputs != 0) if isFeasible == False: print("[WARNING] %s has I/O = %d/%d, so skipped" % \ (odbMaster.getName(), numInputs, numOutputs)) return isFeasible def ParseMasterUseList(self, mastersFileName): # parse master lists if exists self.masters = [] if mastersFileName != None: f = open(mastersFileName, 'r') cont = f.read() f.close() for curLine in cont.split("\n"): curLine = curLine.strip() if curLine == "": continue odbMaster = self.odb.findMaster(curLine) if odbMaster == None: print("[WARNING] cannot find master (%s) in OpenDB" % (curLine)) elif self.IsFeasibleMaster(odbMaster): self.masters.append(odbMaster) print("Parsed master use list: Total %d masters" % (len(self.masters))) # extract cell lists from odb if len(self.masters) == 0: libs = self.odb.getLibs() for lib in libs: masters = lib.getMasters() for master in masters: if self.IsFeasibleMaster(master): self.masters.append(master) if len(self.masters) == 0: ErrorQuit("Cannot find available master cells in OpenDB") print("Use all masters available in OpenDB: %d masters" % (len(self.masters))) ffArr = [0 for m in self.masters if m.isSequential()] if len(ffArr) == 0: ErrorQuit("Cannot find sequential cells in OpenDB") print("NumSequentialCells: %d" %(len(ffArr))) # retrive site info from ODB print("GivenSite: %s" % (self.siteName)) libs = self.odb.getLibs() for lib in libs: curSite = lib.findSite(self.siteName) if curSite != None: break if curSite == None: ErrorQuit("Cannot find site %s from OpenDB" %(self.siteName)) self.odbSite = curSite self.odbSiteWidth = int(curSite.getWidth()) self.odbSiteHeight = int(curSite.getHeight()) self.bsDbuRatio = 1.0 * int(self.odbSiteHeight) / int(self.rowHeight) * self.customFPRatio print("siteWidth: %d, siteHeight: %d" % (self.odbSiteWidth, self.odbSiteHeight)) print("BookshelfDbuRatio: %.2f" % (self.bsDbuRatio) ) print("") # Note that # we should match the cells that has smaller errors # on the following two values # # 1) (bookshelf) cell width # 2) (lef) cell width / self.bsDbuRatio # key: numInputs # val: list of [bsWidth, masters] # -- bsWidth is required as key to sort cells quickly. # # note that #output == 1. self.masterInstMap = {} self.masterFFMap = {} for master in self.masters: numInPins = 0 numOutPins = 0 for mTerm in master.getMTerms(): # only for SIGNAL pins if mTerm.getSigType() != "SIGNAL": continue # skip for clock pin list if self.ffClkPinList != None and mTerm.getConstName() in self.ffClkPinList: continue if mTerm.getIoType() == "INPUT": numInPins += 1 elif mTerm.getIoType() == "OUTPUT": numOutPins += 1 bsWidth = int( math.floor(master.getWidth() /self.bsDbuRatio)) # ff inst master --> update masterFFMap if master.isSequential(): if numInPins in self.masterFFMap: self.masterFFMap[numInPins].append([bsWidth, master]) else: self.masterFFMap[numInPins] = [[bsWidth, master]] # normal inst master --> update masterInstMap else: if numInPins in self.masterInstMap: self.masterInstMap[numInPins].append([bsWidth, master]) else: self.masterInstMap[numInPins] = [[bsWidth,master]] # sort val(bsWidth-master list) by bsWidth as increasing order. # for masterInstMap for key, val in self.masterInstMap.items(): self.masterInstMap[key] = sorted(val, key=lambda x: (x[0])) # for masterFFMap for key, val in self.masterFFMap.items(): self.masterFFMap[key] = sorted(val, key=lambda x: (x[0])) print("Available inst master info from OpenDB") for key, val in self.masterInstMap.items(): print("numInputs:", key, "minWidth:", val[0][0], "maxWidth:", val[-1][0], "masterCnt", len(val)) print("") print("Available ff master info from OpenDB") for key, val in self.masterFFMap.items(): print("numInputs:", key, "minWidth:", val[0][0], "maxWidth:", val[-1][0], "masterCnt", len(val)) print("") # on newblue, some inst has multi-height cells def IsMacro(self, bsInstHeight): return (bsInstHeight > self.rowHeight * 3) def PostProcessInBookshelf(self): print("Start Bookshelf post processing ...") self.movableStdInsts = [] self.movableMacroInsts = [] self.fixedInsts = [] self.primary = [] # Name to idx hash map self.movableStdInstDict = {} self.movableMacroInstDict = {} self.fixedInstDict = {} self.primaryDict = {} # Name to bsType Map # See details on class BsType self.typeDict = {} # extract first row height from bsRowList self.rowHeight = int(self.bsRowList[0][2][2]) print("SclRowHeight:", self.rowHeight) for curInst in self.bsInstList: if len(curInst) == 3: # normal STD cell if self.IsMacro(int(curInst[2])) == False: self.movableStdInstDict[curInst[0]] = len(self.movableStdInsts) self.movableStdInsts.append( Instance(name = curInst[0], lx = 0, ly = 0, width = int(curInst[1]), height = self.rowHeight, isFixed = False)) self.typeDict[curInst[0]] = BsType.MOVABLE_STD_INST # movable macro cell else: self.movableMacroInstDict[curInst[0]] = len(self.movableMacroInsts) self.movableMacroInsts.append( Instance(name = curInst[0], lx = 0, ly = 0, width = int(curInst[1]), height = int(curInst[2]), isFixed = False)) self.typeDict[curInst[0]] = BsType.MOVABLE_MACRO_INST elif len(curInst) == 4: # fixed primary (input/output) # on old bookshelf, terminal with size=(0,0) or (1,1) is primary. if curInst[3] == "terminal_NI" or \ (curInst[3] == "terminal" and int(curInst[1]) <= 1 and int(curInst[2]) <=1): self.primaryDict[curInst[0]] = len(self.primary) self.primary.append( Primary(name = curInst[0], x = 0, y = 0, pinDir = "NONE")) self.typeDict[curInst[0]] = BsType.PRIMARY # fixed insts (either std/macro) elif curInst[3] == "terminal": self.fixedInstDict[curInst[0]] = len(self.fixedInsts) self.fixedInsts.append( Instance(name = curInst[0], lx = 0, ly = 0, width = int(curInst[1]), height = int(curInst[2]), isFixed = True)) self.typeDict[curInst[0]] = BsType.FIXED_INST # traverse bsNetList to update instances for curNet in self.bsNetList: for curPin in curNet[1]: objName = curPin[0] objDir = curPin[1] bsType = self.typeDict[objName] if bsType == BsType.MOVABLE_STD_INST: idx = self.movableStdInstDict[objName] # update pin cnt # no need to handle offsets if objDir == "O": self.movableStdInsts[idx].IncrOutPin() elif objDir == "I": self.movableStdInsts[idx].IncrInPin() # Movable Macro cases elif bsType == BsType.MOVABLE_MACRO_INST: idx = self.movableMacroInstDict[objName] # update pin cnt if objDir == "O": self.movableMacroInsts[idx].IncrOutPin() elif objDir == "I": self.movableMacroInsts[idx].IncrInPin() # update pin locs if len(curPin) >= 5: coordiX = float(curPin[3]) coordiY = float(curPin[4]) else: coordiX = coordiY = 0 self.movableMacroInsts[idx].AddPin(coordiX,coordiY,objDir) # Fixed Macro cases elif bsType == BsType.FIXED_INST: idx = self.fixedInstDict[objName] if objDir == "O": self.fixedInsts[idx].IncrOutPin() elif objDir == "I": self.fixedInsts[idx].IncrInPin() # update pin locs if len(curPin) >= 5: coordiX = float(curPin[3]) coordiY = float(curPin[4]) else: coordiX = coordiY = 0 self.fixedInsts[idx].AddPin(coordiX,coordiY,objDir) # primary cases elif bsType == BsType.PRIMARY: idx = self.primaryDict[objName] self.primary[idx].SetDir(objDir) # traverse bsPl to update fixed locations for curPl in self.bsPlList: plObjName = curPl[0] # FixedInsts if curPl[-1] == "/FIXED": idx = self.fixedInstDict[plObjName] self.fixedInsts[idx].SetLxLy( float(curPl[1]), float(curPl[2])) # Primary elif curPl[-1] == "/FIXED_NI": idx = self.primaryDict[plObjName] self.primary[idx].SetXY( float(curPl[1]), float(curPl[2])) # traverse bsShapeList to fill in obs structure of fixedInsts for curInst in self.bsShapeList: instName = curInst[0] idx = self.fixedInstDict[instName] # add obs on fixedInsts for curShape in curInst[1]: self.fixedInsts[idx].AddObs( int(curShape[1]), int(curShape[2]), int(curShape[3]), int(curShape[4])) print("NumMovableStdInsts:", len(self.movableStdInsts)) print("NumMovableMacroInsts:", len(self.movableMacroInsts)) print("NumFixedInsts:", len(self.fixedInsts)) print("NumPrimary:", len(self.primary)) print("Post processing of Bookshelf is done") # determine FF minFFWidth def DetermineFFCondition(self, targetFFRatio = 0.12): # sort inst (numInputs == 1) by candidateInsts = [l for l in self.allStdInsts \ if l.numInputs == 1 and l.numOutputs == 1] candidateInstMap = {} for inst in candidateInsts: if inst.width in candidateInstMap: candidateInstMap[inst.width].append(inst) else: candidateInstMap[inst.width] = [inst] tmpKeys = list(candidateInstMap.keys()) tmpVals = list(candidateInstMap.values()) candidateInstList = [(key,val) for key,val in zip(tmpKeys,tmpVals)] candidateInstList.sort( reverse=True ) accmInsts = 0 prevMinFFWidth = 0 prevRatio = 0 for instPair in candidateInstList: accmInsts += len(instPair[1]) # exceeds the given ratio! ratio = float(accmInsts) / len(self.allStdInsts) if ratio >= targetFFRatio: # if there is cutting point where 0.09~0.12 exists, choose it if prevRatio >= 0.09: self.minFFWidth = prevMinFFWidth # if the cutting point < 0.09, takes > 0.12 point else: self.minFFWidth = instPair[0] break prevMinFFWidth = instPair[0] prevRatio = ratio # if #FF cells didn't reach the target FF ratio --> use all FF cell if float(accmInsts) / len(self.allStdInsts) < targetFFRatio: self.minFFWidth = 0 print("minFFCellWidth: %d" %(self.minFFWidth)) print("") def ClassifyInsts(self): self.pinNonFFInstDict = {} self.pinFFInstDict = {} self.numSkipInsts = 0 self.numFFInsts = 0 allFixedStdInsts = [l for l in self.fixedInsts if self.IsMacro(l.height) == False] self.allStdInsts = self.movableStdInsts + allFixedStdInsts # retrive minFFWidth self.DetermineFFCondition(self.targetFFRatio) for curInst in self.allStdInsts: # skip for weird cells in academic benchmark if curInst.IsFeasible() == False: self.numSkipInsts += 1 continue # skip for inst that are not available in masterUseList if curInst.IsFF(self.minFFWidth): if not curInst.numInputs in self.masterFFMap: self.numSkipInsts += 1 curInst.SetIsFeasible(False) continue else: if not curInst.numInputs in self.masterInstMap: self.numSkipInsts += 1 curInst.SetIsFeasible(False) continue # only outPin = 1 and inPin > 0 survived now. hashKey = curInst.GetHashName() # update FF map if curInst.IsFF(self.minFFWidth): self.numFFInsts += 1 if hashKey in self.pinFFInstDict: self.pinFFInstDict[hashKey].append(curInst) else: self.pinFFInstDict[hashKey] = [curInst] # update nonFF map else: if hashKey in self.pinNonFFInstDict: self.pinNonFFInstDict[hashKey].append(curInst) else: self.pinNonFFInstDict[hashKey] = [curInst] print("NumSkippedInsts: %d (%.2f percent)" % ( self.numSkipInsts, 1.0 * self.numSkipInsts / len(self.allStdInsts) * 100 )) print("NumAssignedFFs: %d (%.2f percent)" % ( self.numFFInsts, 1.0 * self.numFFInsts / (len(self.allStdInsts) - self.numSkipInsts) * 100) ) # convert 2d dict into 2d list to sort tmpKeys = list(self.pinNonFFInstDict.keys()) tmpVals = list(self.pinNonFFInstDict.values()) self.pinNonFFInstList = [(key,val) for key,val in zip(tmpKeys,tmpVals)] tmpKeys = list(self.pinFFInstDict.keys()) tmpVals = list(self.pinFFInstDict.values()) self.pinFFInstList = [(key,val) for key,val in zip(tmpKeys,tmpVals)] # sort self.pinNonFFInstList.sort() self.pinFFInstList.sort() print("NonFF Cell info from Bookshelf. Key = 10000 * numInputs + width)") for key, val in self.pinNonFFInstList: print("key", key, "numInst:", len(val)) print("FF Cell info from Bookshelf. Key = 10000 * numInputs + width)") for key, val in self.pinFFInstList: print("key", key, "numInst:", len(val)) # mode = 0: NonFFInst # mode = 1: FFInst def GetMasterMapList(self, key, numInsts, mode): numInputs = key // 10000 width = key % 10000 if mode == 0: masters = self.masterInstMap[numInputs][:] else: masters = self.masterFFMap[numInputs][:] #print("retrieved masters") #for l in masters: # print(l[0], l[1].getName()) # first - diff on cellWidth # second - odb master masters = [ [abs(width-l[0]),l[1]] for l in masters ] # get min on cellWidthDiff minX = min([ l[0] for l in masters ]) # extract all masters that has minX masters = [ l[1] for l in masters if l[0] == minX ] #print("Mode: %s " % ("FF" if mode == 0 else "INST"), # "key:", key, # "min(widthDiff):", minX, # "bsWidth:", width, # "masters:", ",".join([l.getName() for l in masters])) # quotient numInstsPerMacro = numInsts // len(masters) retArr = [l for l in masters for j in range(0,numInstsPerMacro)] # remainder problem. while len(retArr) < numInsts: retArr.append(masters[0]) # shuffle the master mapping random.seed(0) random.shuffle(retArr) return retArr def GetNonFFMasterMapList(self, key, numInsts): return self.GetMasterMapList(key, numInsts, mode = 0) def GetFFMasterMapList(self, key, numInsts): return self.GetMasterMapList(key, numInsts, mode = 1) # init chip and block obj def InitOdb(self, macroInstPinLayer, macroInstObsLayer, primaryLayer): # initialize dbChip chip = self.odb.getChip() if chip != None: print("[WARNING] Chip obj is already initialized") else: chip = self.odbpy.dbChip_create(self.odb) # initialize dbBlock block = chip.getBlock() if block != None: print("[WARNING] Block obj is already initialized") else: block = self.odbpy.dbBlock_create(chip, self.designName) self.odbBlock = block # default dbu is from LEF odbTech = self.odb.getTech() self.dbu = originalLEFDbu = odbTech.getDbUnitsPerMicron() odbTech.setDbUnitsPerMicron(originalLEFDbu) self.odbBlock.setDefUnits(self.dbu) self.manufacturingGrid = int(odbTech.getManufacturingGrid()) print("[INFO] ManuFacturingGrid:", self.manufacturingGrid) # retrieve layer info # 1. fixed macro insts pin layer # default: metal 3, 4 if macroInstPinLayer == None: odbMacroInstPinLayer = [odbTech.findRoutingLayer(l) for l in range(3,5)] horLayer = [l for l in odbMacroInstPinLayer if l.getDirection() == "HORIZONTAL"] verLayer = [l for l in odbMacroInstPinLayer if l.getDirection() == "VERTICAL"] if len(horLayer) != 1 or len(verLayer) != 1: print("[ERROR] Cannot find proper layers for macro inst pin layers.\n" + " Please put two routing layers for macro inst pin layers") exit(1) self.macroInstPinHorLayer = horLayer[0] self.macroInstPinVerLayer = verLayer[0] self.macroInstPinLowerLayer = odbMacroInstPinLayer[0] print("[WARNING] Fixed macro insts' pin layer is assigned as H: " + self.macroInstPinHorLayer.getName() + " V: " + self.macroInstPinVerLayer.getName()) elif len(macroInstPinLayer) != 2: print("[ERROR] Received " + str(len(macroInstPinLayer)) + " layers.\n" + " Please put two routing layers for macro inst pin layers" ) exit(1) else: odbMacroInstPinLayer = [odbTech.findLayer(l) for l in macroInstPinLayer] horLayer = [l for l in odbMacroInstPinLayer if l.getDirection() == "HORIZONTAL"] verLayer = [l for l in odbMacroInstPinLayer if l.getDirection() == "VERTICAL"] if len(horLayer) != 1 or len(verLayer) != 1: print("[ERROR] Cannot find proper layers for macro inst pin layers.\n" + " Please put two routing layers for macro inst pin layers") exit(1) self.macroInstPinHorLayer = horLayer[0] self.macroInstPinVerLayer = verLayer[0] self.macroInstPinLowerLayer = odbMacroInstPinLayer[0] print("[INFO] Fixed macro insts' pin layer is assigned as H: " + self.macroInstPinHorLayer.getName() + " V: " + self.macroInstPinVerLayer.getName()) # 2. fixed macro insts obs layer -- m1-m4 v1-v3 if macroInstObsLayer == None: self.macroInstObsLayer = [odbTech.findLayer(l) for l in range(1,6)] print("[WARNING] Fixed macro insts' pin layer is assigned as \n" + "\n".join([m.getName() for m in self.macroInstObsLayer]) + "\n") else: self.macroInstObsLayer = [odbTech.findLayer(l) for l in macroInstObsLayer] # 3. primary pins layer (input/output PINS in def) # default: metal 4 if primaryLayer == None: self.primaryLayer = odbTech.findRoutingLayer(4) print("[WARNING] primary pin layer is assigned as " + self.primaryLayer.getName()) else: self.primaryLayer = odbTech.findLayer(primaryLayer) # Create OVERLAP layer for rectilinear macro definition # this is only needed when *.shape is given if len(self.bsShapeList) != 0: if odbTech.findLayer("OVERLAP") == None: self.odbpy.dbTechLayer_create(odbTech, "OVERLAP", "OVERLAP") self.macroInstObsLayer.append(odbTech.findLayer("OVERLAP")) def FillOdb(self): self.FillOdbRows() self.FillOdbMacroLef() self.FillOdbStdInsts() self.FillOdbFixedMacroInsts() self.FillOdbMovableMacroInsts() self.FillOdbNets() self.FillOdbClockNet() # note that considering fragemented ROW is not acceptable # due to different site heights on different tech. # # Retrive lx, ly, ux, uy of coreArea and create def FillOdbRows(self): lx = 1e30 ly = 1e30 ux = -1e30 uy = -1e30 for curRow in self.bsRowList: rowLy = int(curRow[1][-1]) rowUy = rowLy + 1 rowLx = int(curRow[-1][2]) rowUx = rowLx + int(curRow[-1][-1]) lx = min(lx, rowLx) ly = min(ly, rowLy) ux = max(ux, rowUx) uy = max(uy, rowUy) print("BookshelfCore:", lx, ly, ux, uy) coreDbu = [lx, ly, ux, uy] coreDbu = [self.GetGridCoordi(int(l * self.bsDbuRatio)) for l in coreDbu] coreDbuStr = [str(l) for l in coreDbu] print("DEFCore:", " ".join(coreDbuStr)) dbuLx = coreDbu[0] dbuLy = coreDbu[1] dbuUx = coreDbu[2] dbuUy = coreDbu[3] numSites = int(round(float(dbuUx - dbuLx)/self.odbSiteWidth)) for idx, curLy in enumerate(range(dbuLy, dbuUy, self.odbSiteHeight)): self.odbpy.dbRow_create(self.odbBlock, "ROW_%d" % (idx), self.odbSite, dbuLx, curLy, "R0" if idx % 2 == 0 else "MX", "HORIZONTAL", numSites, self.odbSiteWidth) # update dbuUy if dbuUy < curLy + self.odbSiteHeight: dbuUy = curLy + self.odbSiteHeight print("Total %d Rows are created in OpenDB" % (len(self.odbBlock.getRows()))) # note that die to core spacing is equal to dbuLx and dbuLy dieUx = dbuUx + dbuLx dieUy = dbuUy + dbuLy # due to macro placement snapping, we need to increase the die area. # first, calculate macro placement offset. pitchY = self.macroInstPinHorLayer.getPitchY() offsetY = self.macroInstPinHorLayer.getOffsetY() self.macroInstHorLayerOffset = offsetY #= (pitchY + offsetY - (dbuLy % pitchY)) % pitchY pitchX = self.macroInstPinVerLayer.getPitchX() offsetX = self.macroInstPinVerLayer.getOffsetX() self.macroInstVerLayerOffset = offsetX # = (pitchX + offsetX - (dbuLx % pitchX)) % pitchX print("Vertical(X) Layer:", self.macroInstPinVerLayer.getName()) print("c2d", dbuLx, "pitch", self.macroInstPinVerLayer.getPitchX()) print("offset", self.macroInstPinVerLayer.getOffsetX()) print("Macro Place Offset - x: %d, y: %d " % (self.macroInstVerLayerOffset, self.macroInstHorLayerOffset)) dieUx = dieUx + self.macroInstVerLayerOffset dieUy = dieUy + self.macroInstHorLayerOffset rect = self.odbpy.Rect(0, 0, dieUx, dieUy) self.odbBlock.setDieArea(rect) print("DEFDie: %d %d %d %d" % (0, 0, dieUx, dieUy)) print("") def GetGridCoordi(self, coordi): return self.GetSnapCoordi(self.manufacturingGrid, coordi) def GetSiteCoordi(self, coordi): return self.GetSnapCoordi(self.odbSiteHeight, coordi) def GetSnapCoordi(self, gridUnit, coordi): return (coordi // gridUnit) * gridUnit # takes list of [x, y] and generate snapped pin locations def SnapFixedMacroPinLocations(self, pins, odbMaster): width = odbMaster.getWidth() height = odbMaster.getHeight() pitchX = self.macroInstPinVerLayer.getPitch() * 2 pitchY = self.macroInstPinHorLayer.getPitch() * 2 cntX = width // pitchX cntY = height // pitchY # make 2D grid grid = [] # 2D grid init for i in range(cntX): tmpGrid = [] for j in range(cntY): tmpGrid.append(-1) grid.append(tmpGrid) newPins = copy.deepcopy(pins) unplacedPins = [] # place pins on 2D grid for idx, pin in enumerate(pins): idxX = int(self.bsDbuRatio * pin[0]) // pitchX idxY = int(self.bsDbuRatio * pin[1]) // pitchY idxX = max(0, min(cntX-1, idxX)) idxY = max(0, min(cntY-1, idxY)) if grid[idxX][idxY] == -1: grid[idxX][idxY] = idx else: unplacedPins.append(idx) newUnplacedPins = [] for idx in unplacedPins: idxX = int(self.bsDbuRatio * pins[idx][0]) // pitchX idxY = int(self.bsDbuRatio * pins[idx][1]) // pitchY idxX = max(0, min(cntX-1, idxX)) idxY = max(0, min(cntY-1, idxY)) isFound = False for dist in range(0,100): # spiral for x in range(0, dist): y = dist - x for nx, ny in [[x,y],[x,-y],[-x,y],[-x,-y]]: nIdxX = max(0, min(cntX-1, idxX+nx)) nIdxY = max(0, min(cntY-1, idxY+ny)) if grid[nIdxX][nIdxY] == -1: grid[nIdxX][nIdxY] = idx isFound = True break if isFound: break if isFound == False: newUnplacedPins.append(idx) if len(newUnplacedPins) > 0: print("[ERROR] Cannot find 2D grid points on current macros.") exit(1) for idxX in range(0, cntX): for idxY in range(0, cntY): if grid[idxX][idxY] != -1: pIdx = grid[idxX][idxY] # the new pins will have ongrid location newPins[pIdx][0] = pitchX * idxX newPins[pIdx][1] = pitchY * idxY return newPins def FillOdbMacroLef(self): self.odbLib = self.odbpy.dbLib_create(self.odb, "fakeMacros") numCreatedMacros = 0 widthSnapGrid = self.macroInstPinVerLayer.getPitchX() heightSnapGrid = self.macroInstPinHorLayer.getPitchY() # create dbMaster obj for macroInst in self.fixedInsts + self.movableMacroInsts: if self.IsMacro(macroInst.height): odbMaster = self.odbpy.dbMaster_create(self.odbLib, "fake_macro_%s_%s" % (self.designName, macroInst.name.replace("/","_"))) odbMaster.setType("BLOCK") odbMaster.setOrigin(0,0) # macro width scaling -- aware manufacturingGrid odbMaster.setWidth( self.GetSnapCoordi( widthSnapGrid, int(self.bsDbuRatio * macroInst.width)) ) odbMaster.setHeight( self.GetSnapCoordi( heightSnapGrid, int(self.bsDbuRatio * macroInst.height)) ) odbMaster.setSymmetryX() odbMaster.setSymmetryY() # print(odbMaster.isBlock(), odbMaster.getMasterId()) macroInst.SetOdbMaster(odbMaster) numCreatedMacros += 1 if numCreatedMacros % 10000 == 0: print("%d macros are created in OpenDB" % (numCreatedMacros)) newPins = self.SnapFixedMacroPinLocations(macroInst.pins, odbMaster) # create PIN pinWidth = self.macroInstPinLowerLayer.getWidth() for idx, curPin in enumerate(newPins): pinDir = "OUTPUT" if curPin[2] == 'O' else "INPUT" if curPin[2] == 'I' else "INOUT" odbMTerm = self.odbpy.dbMTerm_create(odbMaster, "p%d" % (idx), pinDir, "SIGNAL") odbMPin = self.odbpy.dbMPin_create(odbMTerm) # fixed macro insts case # create PIN # note that all pins are already upscaled by bsDbuRatio on snapping function self.odbpy.dbBox_create(odbMPin, self.macroInstPinLowerLayer, int(self.GetGridCoordi(round(curPin[0] - pinWidth/2.0))), int(self.GetGridCoordi(round(curPin[1] - pinWidth/2.0))), int(self.GetGridCoordi(round(curPin[0] + pinWidth/2.0))), int(self.GetGridCoordi(round(curPin[1] + pinWidth/2.0)))) # create OBS only for Macro # on whole cell area if *.shape is not exist if len(macroInst.obses) == 0: for curLayer in self.macroInstObsLayer: self.odbpy.dbBox_create(odbMaster, curLayer, 0, 0, int(self.GetGridCoordi(round(self.bsDbuRatio * macroInst.width))), int(self.GetGridCoordi(round(self.bsDbuRatio * macroInst.height)))) # on partial rectilinear cell area if *.shape is exist else: for curLayer in self.macroInstObsLayer: for curObs in macroInst.obses: self.odbpy.dbBox_create(odbMaster, curLayer, int(self.GetGridCoordi(round(self.bsDbuRatio * curObs[0]))), int(self.GetGridCoordi(round(self.bsDbuRatio * curObs[1]))), int(self.GetGridCoordi(round(self.bsDbuRatio * curObs[2]))), int(self.GetGridCoordi(round(self.bsDbuRatio * curObs[3])))) # set Frozen to use in the Block section odbMaster.setFrozen() print("Total %d macros are created in OpenDB" % (numCreatedMacros)) # fill dbInst (components) on movable insts def FillOdbStdInsts(self): for key, insts in self.pinNonFFInstList: masterMapList = self.GetNonFFMasterMapList( key, len(insts) ) for inst, master in zip(insts, masterMapList): odbInst = self.odbpy.dbInst_create(self.odbBlock, master, inst.name ) inst.SetOdbInst(odbInst, ffClkPinList = None) for key, insts in self.pinFFInstList: masterMapList = self.GetFFMasterMapList( key, len(insts) ) for inst,master in zip(insts, masterMapList): odbInst = self.odbpy.dbInst_create(self.odbBlock, master, inst.name ) inst.SetOdbInst(odbInst, ffClkPinList = self.ffClkPinList) # fill dbInst (components) on Fixed insts def FillOdbFixedMacroInsts(self): widthSnapGrid = self.macroInstPinVerLayer.getPitchX() heightSnapGrid = self.macroInstPinHorLayer.getPitchY() for fixedInst in self.fixedInsts: # only for macros if self.IsMacro(fixedInst.height): odbInst = self.odbpy.dbInst_create(self.odbBlock, fixedInst.odbMaster, fixedInst.name) odbInst.setOrigin( self.GetSnapCoordi( widthSnapGrid, int(round(self.bsDbuRatio * fixedInst.lx))) + self.macroInstVerLayerOffset, self.GetSnapCoordi( heightSnapGrid, int(round(self.bsDbuRatio * fixedInst.ly))) + self.macroInstHorLayerOffset) odbInst.setPlacementStatus("LOCKED") fixedInst.SetOdbInst(odbInst, ffClkPinList = None) def FillOdbMovableMacroInsts(self): widthSnapGrid = self.macroInstPinVerLayer.getPitchX() heightSnapGrid = self.macroInstPinHorLayer.getPitchY() for macroInst in self.movableMacroInsts: print("Movable Macro Inst:", macroInst.name) # only for macros if self.IsMacro(macroInst.height): odbInst = self.odbpy.dbInst_create(self.odbBlock, macroInst.odbMaster, macroInst.name) odbInst.setPlacementStatus("UNPLACED") print("Macro SetOdbInst:", macroInst.name) macroInst.SetOdbInst(odbInst, ffClkPinList = None) # The dbBTerm (terminal_NI) instances must be initialized # during the net traversal because dbBTerm requires dbNet pointer. def FillOdbNets(self): netCnt = 0 for curNet in self.bsNetList: netName = curNet[0] dbNet = self.odbpy.dbNet_create(self.odbBlock, netName) netCnt += 1 if netCnt % 10000 == 0: print("%d nets are created in OpenDB" % (netCnt)) for curPin in curNet[1]: iName = curPin[0] iTermDir = curPin[1] bsType = self.typeDict[iName] # primary type if bsType == BsType.PRIMARY: curPrimary = self.primary[self.primaryDict[iName]] # primary -> dbBTerm create dbBTerm = self.odbpy.dbBTerm_create(dbNet, netName) # bookshelf benchmark has wrong pi/po pin connections sometimes # if duplicated PI/POs are met, ignore. if dbBTerm != None: dbBTerm.setIoType("INPUT" if iTermDir == "I" else "OUTPUT" if iTermDir == "O" else "INOUT") dbBPin = self.odbpy.dbBPin_create(dbBTerm) cx = self.bsDbuRatio * curPrimary.x cy = self.bsDbuRatio * curPrimary.y mWidth = self.primaryLayer.getWidth() # create bbox pin shapes self.odbpy.dbBox_create(dbBPin, self.primaryLayer, int(self.GetGridCoordi(round(cx - mWidth/2.0))), int(self.GetGridCoordi(round(cy - mWidth/2.0))), int(self.GetGridCoordi(round(cx + mWidth/2.0))), int(self.GetGridCoordi(round(cy + mWidth/2.0)))) dbBPin.setPlacementStatus("LOCKED") else: # retrive bsInst object. # movable instances -> std cell mapping if bsType == BsType.MOVABLE_STD_INST: curInst = self.movableStdInsts[self.movableStdInstDict[iName]] # macro instances elif bsType == BsType.MOVABLE_MACRO_INST: curInst = self.movableMacroInsts[self.movableMacroInstDict[iName]] elif bsType == BsType.FIXED_INST: curInst = self.fixedInsts[self.fixedInstDict[iName]] # skip for non-feasible standard cells. # either movable STD inst or fixed STD inst if bsType == BsType.MOVABLE_STD_INST or (bsType == BsType.FIXED_INST and self.IsMacro(curInst.height) == False): # if infeasible, skip to fill in the db if curInst.IsFeasible() == False: continue # direction on ITerm if iTermDir == "O": dbITerm = curInst.RetrieveOdbOutPin() elif iTermDir == "I": dbITerm = curInst.RetrieveOdbInPin() # connect to net self.odbpy.dbITerm.connect(dbITerm, dbNet) print("Total %d nets are created in OpenDB" % (netCnt)) def FillOdbClockNet(self): allFFInsts = [l for l in self.allStdInsts if l.odbClkPin != None] if len(allFFInsts) > 0: dbNet = self.odbpy.dbNet_create(self.odbBlock, self.clkNetName) for ffInst in allFFInsts: dbITerm = ffInst.odbClkPin self.odbpy.dbITerm.connect(dbITerm, dbNet) dbBTerm = self.odbpy.dbBTerm_create(dbNet, self.clkNetName) dbBTerm.setIoType("INPUT") dbBTerm.setSigType("CLOCK") dbNet.setSigType("CLOCK") def WriteMacroLef(self, lefName): self.odbpy.write_macro_lef(self.odbLib, lefName)