blob: 959a6237d96b67ee195c996311ad8a76320e2fc9 [file] [log] [blame]
#!/usr/bin/env python3
#
#--------------------------------------------------------
# Padframe Editor and Core Floorplanner
#
#--------------------------------------------------------
# Written by Tim Edwards
# efabless, inc.
# April 24, 2019
# Version 0.5
# Based on https://github.com/YosysHQ/padring (requirement)
# Update: May 9, 2019 to add console message window
# Update: May 10, 2019 to incorporate core floorplanning
# Update: Jan 31, 2020 to allow batch operation
#--------------------------------------------------------
import os
import re
import sys
import glob
import json
import math
import shutil
import signal
import select
import subprocess
import faulthandler
import tkinter
from tkinter import ttk
from tkinter import filedialog
import tksimpledialog
from consoletext import ConsoleText
# User preferences file (if it exists)
prefsfile = '~/design/.profile/prefs.json'
#------------------------------------------------------
# Dialog for entering a pad
#------------------------------------------------------
class PadNameDialog(tksimpledialog.Dialog):
def body(self, master, warning=None, seed=None):
if warning:
ttk.Label(master, text=warning).grid(row = 0, columnspan = 2, sticky = 'wns')
ttk.Label(master, text="Enter new group name:").grid(row = 1, column = 0, sticky = 'wns')
self.nentry = ttk.Entry(master)
self.nentry.grid(row = 1, column = 1, sticky = 'ewns')
if seed:
self.nentry.insert(0, seed)
return self.nentry # initial focus
def apply(self):
return self.nentry.get()
#------------------------------------------------------
# Dialog for entering core dimensions
#------------------------------------------------------
class CoreSizeDialog(tksimpledialog.Dialog):
def body(self, master, warning="Chip core dimensions", seed=None):
if warning:
ttk.Label(master, text=warning).grid(row = 0, columnspan = 2, sticky = 'wns')
ttk.Label(master, text="Enter core width x height (microns):").grid(row = 1, column = 0, sticky = 'wns')
self.nentry = ttk.Entry(master)
self.nentry.grid(row = 1, column = 1, sticky = 'ewns')
if seed:
self.nentry.insert(0, seed)
return self.nentry # initial focus
def apply(self):
return self.nentry.get()
#------------------------------------------------
# SoC Floorplanner and Padframe Generator GUI
#------------------------------------------------
class SoCFloorplanner(ttk.Frame):
"""Open Galaxy Pad Frame Generator."""
def __init__(self, parent = None, *args, **kwargs):
'''See the __init__ for Tkinter.Toplevel.'''
ttk.Frame.__init__(self, parent, *args[1:], **kwargs)
self.root = parent
self.init_data()
if args[0] == True:
self.do_gui = True
self.init_gui()
else:
self.do_gui = False
self.use_console = False
def on_quit(self):
"""Exits program."""
quit()
def init_gui(self):
"""Builds GUI."""
global prefsfile
message = []
fontsize = 11
# Read user preferences file, get default font size from it.
prefspath = os.path.expanduser(prefsfile)
if os.path.exists(prefspath):
with open(prefspath, 'r') as f:
self.prefs = json.load(f)
if 'fontsize' in self.prefs:
fontsize = self.prefs['fontsize']
else:
self.prefs = {}
s = ttk.Style()
available_themes = s.theme_names()
s.theme_use(available_themes[0])
s.configure('normal.TButton', font=('Helvetica', fontsize), border = 3, relief = 'raised')
s.configure('title.TLabel', font=('Helvetica', fontsize, 'bold italic'),
foreground = 'brown', anchor = 'center')
s.configure('blue.TLabel', font=('Helvetica', fontsize), foreground = 'blue')
s.configure('normal.TLabel', font=('Helvetica', fontsize))
s.configure('normal.TCheckbutton', font=('Helvetica', fontsize))
s.configure('normal.TMenubutton', font=('Helvetica', fontsize))
s.configure('normal.TEntry', font=('Helvetica', fontsize), background='white')
s.configure('pad.TLabel', font=('Helvetica', fontsize), foreground = 'blue', relief = 'flat')
s.configure('select.TLabel', font=('Helvetica', fontsize, 'bold'), foreground = 'white',
background = 'blue', relief = 'flat')
# parent.withdraw()
self.root.title('Padframe Generator and Core Floorplanner')
self.root.option_add('*tearOff', 'FALSE')
self.pack(side = 'top', fill = 'both', expand = 'true')
self.root.protocol("WM_DELETE_WINDOW", self.on_quit)
pane = tkinter.PanedWindow(self, orient = 'vertical', sashrelief = 'groove',
sashwidth = 6)
pane.pack(side = 'top', fill = 'both', expand = 'true')
self.toppane = ttk.Frame(pane)
self.botpane = ttk.Frame(pane)
self.toppane.columnconfigure(0, weight = 1)
self.toppane.rowconfigure(0, weight = 1)
self.botpane.columnconfigure(0, weight = 1)
self.botpane.rowconfigure(0, weight = 1)
# Scrolled frame using canvas widget
self.pframe = tkinter.Frame(self.toppane)
self.pframe.grid(row = 0, column = 0, sticky = 'news')
self.pframe.rowconfigure(0, weight = 1)
self.pframe.columnconfigure(0, weight = 1)
# Add column on the left, listing all groups and the pads they belong to.
# This starts as just a frame to be filled. Use a canvas to create a
# scrolled frame.
# The primary frame holds the canvas
self.canvas = tkinter.Canvas(self.pframe, background = "white")
self.canvas.grid(row = 0, column = 0, sticky = 'news')
# Add Y scrollbar to pad list window
xscrollbar = ttk.Scrollbar(self.pframe, orient = 'horizontal')
xscrollbar.grid(row = 1, column = 0, sticky = 'news')
yscrollbar = ttk.Scrollbar(self.pframe, orient = 'vertical')
yscrollbar.grid(row = 0, column = 1, sticky = 'news')
self.canvas.config(xscrollcommand = xscrollbar.set)
xscrollbar.config(command = self.canvas.xview)
self.canvas.config(yscrollcommand = yscrollbar.set)
yscrollbar.config(command = self.canvas.yview)
self.canvas.bind("<Button-4>", self.on_scrollwheel)
self.canvas.bind("<Button-5>", self.on_scrollwheel)
# Configure callback
self.canvas.bind("<Configure>", self.frame_configure)
# Add a text window to capture output. Redirect print statements to it.
self.console = ttk.Frame(self.botpane)
self.console.grid(column = 0, row = 0, sticky = "news")
self.text_box = ConsoleText(self.console, wrap='word', height = 4)
self.text_box.pack(side='left', fill='both', expand='true')
console_scrollbar = ttk.Scrollbar(self.console)
console_scrollbar.pack(side='right', fill='y')
# Attach console to scrollbar
self.text_box.config(yscrollcommand = console_scrollbar.set)
console_scrollbar.config(command = self.text_box.yview)
# Add the bottom bar with buttons
self.bbar = ttk.Frame(self.botpane)
self.bbar.grid(column = 0, row = 1, sticky = "news")
self.bbar.import_button = ttk.Button(self.bbar, text='Import',
command=self.vlogimport, style='normal.TButton')
self.bbar.import_button.grid(column=0, row=0, padx = 5)
self.bbar.generate_button = ttk.Button(self.bbar, text='Generate',
command=self.generate, style='normal.TButton')
self.bbar.generate_button.grid(column=1, row=0, padx = 5)
self.bbar.save_button = ttk.Button(self.bbar, text='Save',
command=self.save, style='normal.TButton')
self.bbar.save_button.grid(column=2, row=0, padx = 5)
self.bbar.cancel_button = ttk.Button(self.bbar, text='Quit',
command=self.on_quit, style='normal.TButton')
self.bbar.cancel_button.grid(column=3, row=0, padx = 5)
pane.add(self.toppane)
pane.add(self.botpane)
pane.paneconfig(self.toppane, stretch='first')
def init_data(self):
self.vlogpads = []
self.corecells = []
self.Npads = []
self.Spads = []
self.Epads = []
self.Wpads = []
self.NEpad = []
self.NWpad = []
self.SEpad = []
self.SWpad = []
self.coregroup = []
self.celldefs = []
self.coredefs = []
self.selected = []
self.ioleflibs = []
self.llx = 0
self.lly = 0
self.urx = 0
self.ury = 0
self.event_data = {}
self.event_data['x0'] = 0
self.event_data['y0'] = 0
self.event_data['x'] = 0
self.event_data['y'] = 0
self.event_data['tag'] = None
self.scale = 1.0
self.margin = 10
self.pad_rotation = 0
self.init_messages = []
self.stdout = None
self.stderr = None
self.keep_cfg = False
self.ef_format = False
self.use_console = False
def init_padframe(self):
self.set_project()
self.vlogimport()
self.readplacement(precheck=True)
self.resolve()
self.generate(0)
# Local routines for handling printing to the text console
def print(self, message, file=None, end='\n', flush=True):
if not file:
if not self.use_console:
file = sys.stdout
else:
file = ConsoleText.StdoutRedirector(self.text_box)
if self.stdout:
print(message, file=file, end=end)
if flush:
self.stdout.flush()
self.update_idletasks()
else:
self.init_messages.append(message)
def text_to_console(self):
# Redirect stdout and stderr to the console as the last thing to do. . .
# Otherwise errors in the GUI get sucked into the void.
self.stdout = sys.stdout
self.stderr = sys.stderr
if self.use_console:
sys.stdout = ConsoleText.StdoutRedirector(self.text_box)
sys.stderr = ConsoleText.StderrRedirector(self.text_box)
if len(self.init_messages) > 0:
for message in self.init_messages:
self.print(message)
self.init_messages = []
# Set the project name(s). This is the name of the top-level verilog.
# The standard protocol is that the project directory contains a file
# project.json that defines a name 'ip-name' that is the same as the
# layout name, the verilog module name, etc.
def set_project(self):
# Check pwd
pwdname = self.projectpath if self.projectpath else os.getcwd()
subdir = os.path.split(pwdname)[1]
if subdir == 'mag' or subdir == 'verilog':
projectpath = os.path.split(pwdname)[0]
else:
projectpath = pwdname
projectroot = os.path.split(projectpath)[0]
projectdirname = os.path.split(projectpath)[1]
# Check for efabless format. This is probably more complicated than
# it deserves to be. Option -ef_format is the best way to specify
# efabless format. However, if it is not specified, then check the
# technology PDK directory and the project directory for the tell-tale
# ".ef-config" (efabless format) directory vs. ".config" (not efabless
# format).
if not self.ef_format:
if os.path.exists(projectpath + '/.ef-config'):
self.ef_format = True
elif self.techpath:
if os.path.exists(self.techpath + '/.ef-config'):
self.ef_format = True
else:
# Do a quick consistency check. Honor the -ef_format option but warn if
# there is an apparent inconsistency.
if os.path.exists(projectpath + '/.config'):
self.print('Warning: -ef_format used in apparently non-efabless setup.')
elif self.techpath:
if os.path.exists(self.techpath + '/.config'):
self.print('Warning: -ef_format used in apparently non-efabless setup.')
# Check for project.json
jsonname = None
if os.path.isfile(projectpath + '/project.json'):
jsonname = projectpath + '/project.json'
elif os.path.isfile(projectroot + '/' + projectdirname + '.json'):
jsonname = projectroot + '/' + projectdirname + '.json'
if os.path.isfile(projectroot + '/project.json'):
# Just in case this was started from some other subdirectory
projectpath = projectroot
jsonname = projectroot + '/project.json'
if jsonname:
self.print('Reading project JSON file ' + jsonname)
with open(jsonname, 'r') as ifile:
topdata = json.load(ifile)
if 'data-sheet' in topdata:
dsheet = topdata['data-sheet']
if 'ip-name' in dsheet:
self.project = dsheet['ip-name']
self.projectpath = projectpath
else:
self.print('No project JSON file; using directory name as the project name.')
self.project = os.path.split(projectpath)[1]
self.projectpath = projectpath
self.print('Project name is ' + self.project + ' (' + self.projectpath + ')')
# Functions for drag-and-drop capability
def add_draggable(self, tag):
self.canvas.tag_bind(tag, '<ButtonPress-1>', self.on_button_press)
self.canvas.tag_bind(tag, '<ButtonRelease-1>', self.on_button_release)
self.canvas.tag_bind(tag, '<B1-Motion>', self.on_button_motion)
self.canvas.tag_bind(tag, '<ButtonPress-2>', self.on_button2_press)
self.canvas.tag_bind(tag, '<ButtonPress-3>', self.on_button3_press)
def on_button_press(self, event):
'''Begining drag of an object'''
# Find the closest item, then record its tag.
locx = event.x + self.canvas.canvasx(0)
locy = event.y + self.canvas.canvasy(0)
item = self.canvas.find_closest(locx, locy)[0]
self.event_data['tag'] = self.canvas.gettags(item)[0]
self.event_data['x0'] = event.x
self.event_data['y0'] = event.y
self.event_data['x'] = event.x
self.event_data['y'] = event.y
def on_button2_press(self, event):
'''Flip an object (excluding corners)'''
locx = event.x + self.canvas.canvasx(0)
locy = event.y + self.canvas.canvasy(0)
item = self.canvas.find_closest(locx, locy)[0]
tag = self.canvas.gettags(item)[0]
try:
corecell = next(item for item in self.coregroup if item['name'] == tag)
except:
try:
pad = next(item for item in self.Npads if item['name'] == tag)
except:
pad = None
if not pad:
try:
pad = next(item for item in self.Epads if item['name'] == tag)
except:
pad = None
if not pad:
try:
pad = next(item for item in self.Spads if item['name'] == tag)
except:
pad = None
if not pad:
try:
pad = next(item for item in self.Wpads if item['name'] == tag)
except:
pad = None
if not pad:
self.print('Error: Object cannot be flipped.')
else:
# Flip the pad (in the only way meaningful for the pad).
orient = pad['o']
if orient == 'N':
pad['o'] = 'FN'
elif orient == 'E':
pad['o'] = 'FW'
elif orient == 'S':
pad['o'] = 'FS'
elif orient == 'W':
pad['o'] = 'FE'
elif orient == 'FN':
pad['o'] = 'N'
elif orient == 'FE':
pad['o'] = 'W'
elif orient == 'FS':
pad['o'] = 'S'
elif orient == 'FW':
pad['o'] = 'E'
else:
# Flip the cell. Use the DEF meaning of flip, which is to
# add or subtract 'F' from the orientation.
orient = corecell['o']
if not 'F' in orient:
corecell['o'] = 'F' + orient
else:
corecell['o'] = orient[1:]
# Redraw
self.populate(0)
def on_button3_press(self, event):
'''Rotate a core object (no pads) '''
locx = event.x + self.canvas.canvasx(0)
locy = event.y + self.canvas.canvasy(0)
item = self.canvas.find_closest(locx, locy)[0]
tag = self.canvas.gettags(item)[0]
try:
corecell = next(item for item in self.coregroup if item['name'] == tag)
except:
self.print('Error: Object cannot be rotated.')
else:
# Modify its orientation
orient = corecell['o']
if orient == 'N':
corecell['o'] = 'E'
elif orient == 'E':
corecell['o'] = 'S'
elif orient == 'S':
corecell['o'] = 'W'
elif orient == 'W':
corecell['o'] = 'N'
elif orient == 'FN':
corecell['o'] = 'FW'
elif orient == 'FW':
corecell['o'] = 'FS'
elif orient == 'FS':
corecell['o'] = 'FE'
elif orient == 'FE':
corecell['o'] = 'FN'
# rewrite the core DEF file
self.write_core_def()
# Redraw
self.populate(0)
def on_button_motion(self, event):
'''Handle dragging of an object'''
# compute how much the mouse has moved
delta_x = event.x - self.event_data['x']
delta_y = event.y - self.event_data['y']
# move the object the appropriate amount
self.canvas.move(self.event_data['tag'], delta_x, delta_y)
# record the new position
self.event_data['x'] = event.x
self.event_data['y'] = event.y
def on_button_release(self, event):
'''End drag of an object'''
# Find the pad associated with the tag and update its position information
tag = self.event_data['tag']
# Collect pads in clockwise order. Note that E and S rows are not clockwise
allpads = []
allpads.extend(self.Npads)
allpads.extend(self.NEpad)
allpads.extend(reversed(self.Epads))
allpads.extend(self.SEpad)
allpads.extend(reversed(self.Spads))
allpads.extend(self.SWpad)
allpads.extend(self.Wpads)
allpads.extend(self.NWpad)
# Create a list of row references (also in clockwise order, but no reversing)
padrows = [self.Npads, self.NEpad, self.Epads, self.SEpad, self.Spads, self.SWpad, self.Wpads, self.NWpad]
# Record the row or corner where this pad was located before the move
for row in padrows:
try:
pad = next(item for item in row if item['name'] == tag)
except:
pass
else:
padrow = row
break
# Currently there is no procedure to move a pad out of the corner
# position; corners are fixed by definition.
if padrow == self.NEpad or padrow == self.SEpad or padrow == self.SWpad or padrow == self.NWpad:
# Easier to run generate() than to put the pad back. . .
self.generate(0)
return
# Find the original center point of the pad being moved
padllx = pad['x']
padlly = pad['y']
if pad['o'] == 'N' or pad['o'] == 'S':
padurx = padllx + pad['width']
padury = padlly + pad['height']
else:
padurx = padllx + pad['height']
padury = padlly + pad['width']
padcx = (padllx + padurx) / 2
padcy = (padlly + padury) / 2
# Add distance from drag information (note that drag position in y
# is negative relative to the chip dimensions)
padcx += (self.event_data['x'] - self.event_data['x0']) / self.scale
padcy -= (self.event_data['y'] - self.event_data['y0']) / self.scale
# reset the drag information
self.event_data['tag'] = None
self.event_data['x'] = 0
self.event_data['y'] = 0
self.event_data['x0'] = 0
self.event_data['y0'] = 0
# Find the distance from the pad to all other pads, and get the two
# closest entries.
wwidth = self.urx - self.llx
dist0 = wwidth
dist1 = wwidth
pad0 = None
pad1 = None
for npad in allpads:
if npad == pad:
continue
npadllx = npad['x']
npadlly = npad['y']
if npad['o'] == 'N' or npad['o'] == 'S':
npadurx = npadllx + npad['width']
npadury = npadlly + npad['height']
else:
npadurx = npadllx + npad['height']
npadury = npadlly + npad['width']
npadcx = (npadllx + npadurx) / 2
npadcy = (npadlly + npadury) / 2
deltx = npadcx - padcx
delty = npadcy - padcy
pdist = math.sqrt(deltx * deltx + delty * delty)
if pdist < dist0:
dist1 = dist0
pad1 = pad0
dist0 = pdist
pad0 = npad
elif pdist < dist1:
dist1 = pdist
pad1 = npad
# Diagnostic
# self.print('Two closest pads to pad ' + pad['name'] + ' (' + pad['cell'] + '): ')
# self.print(pad0['name'] + ' (' + pad0['cell'] + ') dist = ' + str(dist0))
# self.print(pad1['name'] + ' (' + pad1['cell'] + ') dist = ' + str(dist1))
# Record the row or corner where these pads are
for row in padrows:
try:
testpad = next(item for item in row if item['name'] == pad0['name'])
except:
pass
else:
padrow0 = row
break
for row in padrows:
try:
testpad = next(item for item in row if item['name'] == pad1['name'])
except:
pass
else:
padrow1 = row
break
# Remove pad from its own row
padrow.remove(pad)
# Insert pad into new row. Watch for wraparound from the last entry to the first
padidx0 = allpads.index(pad0)
padidx1 = allpads.index(pad1)
if padidx0 == 0 and padidx1 > 2:
padidx1 = -1
if padidx1 > padidx0:
padafter = pad1
rowafter = padrow1
padbefore = pad0
rowbefore = padrow0
else:
padafter = pad0
rowafter = padrow0
padbefore = pad1
rowbefore = padrow1
# Do not replace corner positions (? may be necessary ?)
if rowafter == self.NWpad:
self.Wpads.append(pad)
elif rowafter == self.NWpad:
self.Npads.append(pad)
elif rowafter == self.SEpad:
self.Epads.insert(0, pad)
elif rowafter == self.SWpad:
self.Spads.insert(0, pad)
elif rowafter == self.Wpads or rowafter == self.Npads:
idx = rowafter.index(padafter)
rowafter.insert(idx, pad)
elif rowbefore == self.NEpad:
self.Epads.append(pad)
elif rowbefore == self.SEpad:
self.Spads.append(pad)
else:
# rows E and S are ordered counterclockwise
idx = rowbefore.index(padbefore)
rowbefore.insert(idx, pad)
# Re-run padring
self.generate(0)
def on_scrollwheel(self, event):
if event.num == 4:
zoomval = 1.1;
elif event.num == 5:
zoomval = 0.9;
else:
zoomval = 1.0;
self.scale *= zoomval
self.canvas.scale('all', -15 * zoomval, -15 * zoomval, zoomval, zoomval)
self.event_data['x'] *= zoomval
self.event_data['y'] *= zoomval
self.event_data['x0'] *= zoomval
self.event_data['y0'] *= zoomval
self.frame_configure(event)
# Callback functions similar to the pad event callbacks above, but for
# core cells. Unlike pad cells, core cells can be rotated and flipped
# arbitrarily, and they do not force a recomputation of the padframe
# unless their position forces the padframe to expand
def add_core_draggable(self, tag):
self.canvas.tag_bind(tag, '<ButtonPress-1>', self.on_button_press)
self.canvas.tag_bind(tag, '<ButtonRelease-1>', self.core_on_button_release)
self.canvas.tag_bind(tag, '<B1-Motion>', self.on_button_motion)
self.canvas.tag_bind(tag, '<ButtonPress-2>', self.on_button2_press)
self.canvas.tag_bind(tag, '<ButtonPress-3>', self.on_button3_press)
def core_on_button_release(self, event):
'''End drag of a core cell'''
# Find the pad associated with the tag and update its position information
tag = self.event_data['tag']
try:
corecell = next(item for item in self.coregroup if item['name'] == tag)
except:
self.print('Error: cell ' + item['name'] + ' is not in coregroup!')
else:
# Modify its position values
corex = corecell['x']
corey = corecell['y']
# Add distance from drag information (note that drag position in y
# is negative relative to the chip dimensions)
deltax = (self.event_data['x'] - self.event_data['x0']) / self.scale
deltay = (self.event_data['y'] - self.event_data['y0']) / self.scale
corecell['x'] = corex + deltax
corecell['y'] = corey - deltay
# rewrite the core DEF file
self.write_core_def()
# reset the drag information
self.event_data['tag'] = None
self.event_data['x'] = 0
self.event_data['y'] = 0
self.event_data['x0'] = 0
self.event_data['y0'] = 0
# Critically needed or else frame does not resize to scrollbars!
def grid_configure(self, padx, pady):
pass
# Redraw the chip frame view in response to changes in the pad list
def redraw_frame(self):
self.canvas.coords('boundary', self.llx, self.urx, self.lly, self.ury)
# Update the canvas scrollregion to incorporate all the interior windows
def frame_configure(self, event):
if self.do_gui == False:
return
self.update_idletasks()
bbox = self.canvas.bbox("all")
try:
newbbox = (-15, -15, bbox[2] + 15, bbox[3] + 15)
except:
pass
else:
self.canvas.configure(scrollregion = newbbox)
# Fill the GUI entries with resident data
def populate(self, level):
if self.do_gui == False:
return
if level > 1:
self.print('Recursion error: Returning now.')
return
self.print('Populating floorplan view.')
# Remove all entries from the canvas
self.canvas.delete('all')
allpads = self.Npads + self.NEpad + self.Epads + self.SEpad + self.Spads + self.SWpad + self.Wpads + self.NWpad
notfoundlist = []
for pad in allpads:
if 'x' not in pad:
self.print('Error: Pad ' + pad['name'] + ' has no placement information.')
continue
llx = int(pad['x'])
lly = int(pad['y'])
pado = pad['o']
try:
padcell = next(item for item in self.celldefs if item['name'] == pad['cell'])
except:
# This should not happen (failsafe)
if pad['cell'] not in notfoundlist:
self.print('Warning: there is no cell named ' + pad['cell'] + ' in the libraries.')
notfoundlist.append(pad['cell'])
continue
padw = padcell['width']
padh = padcell['height']
if 'N' in pado or 'S' in pado:
urx = int(llx + padw)
ury = int(lly + padh)
else:
urx = int(llx + padh)
ury = int(lly + padw)
pad['llx'] = llx
pad['lly'] = lly
pad['urx'] = urx
pad['ury'] = ury
# Note that the DEF coordinate system is reversed in Y from the canvas. . .
height = self.ury - self.lly
for pad in allpads:
llx = pad['llx']
lly = height - pad['lly']
urx = pad['urx']
ury = height - pad['ury']
tag_id = pad['name']
if 'subclass' in pad:
if pad['subclass'] == 'POWER':
pad_color = 'orange2'
elif pad['subclass'] == 'INOUT':
pad_color = 'yellow'
elif pad['subclass'] == 'OUTPUT':
pad_color = 'powder blue'
elif pad['subclass'] == 'INPUT':
pad_color = 'goldenrod1'
elif pad['subclass'] == 'SPACER':
pad_color = 'green yellow'
elif pad['class'] == 'ENDCAP':
pad_color = 'green yellow'
elif pad['subclass'] == '' or pad['class'] == ';':
pad_color = 'khaki1'
else:
self.print('Unhandled pad class ' + pad['class'])
pad_color = 'gray'
else:
pad_color = 'gray'
sllx = self.scale * llx
slly = self.scale * lly
surx = self.scale * urx
sury = self.scale * ury
self.canvas.create_rectangle((sllx, slly), (surx, sury), fill=pad_color, tags=[tag_id])
cx = (sllx + surx) / 2
cy = (slly + sury) / 2
s = 10 if pad['width'] >= 10 else pad['width']
if pad['height'] < s:
s = pad['height']
# Create an indicator line at the bottom left corner of the cell
if pad['o'] == 'N':
allx = sllx
ally = slly - s
aurx = sllx + s
aury = slly
elif pad['o'] == 'E':
allx = sllx
ally = sury + s
aurx = sllx + s
aury = sury
elif pad['o'] == 'S':
allx = surx
ally = sury + s
aurx = surx - s
aury = sury
elif pad['o'] == 'W':
allx = surx
ally = slly - s
aurx = surx - s
aury = slly
elif pad['o'] == 'FN':
allx = surx
ally = slly - s
aurx = surx - s
aury = slly
elif pad['o'] == 'FE':
allx = surx
ally = sury + s
aurx = surx - s
aury = sury
elif pad['o'] == 'FS':
allx = sllx
ally = sury + s
aurx = sllx + s
aury = sury
elif pad['o'] == 'FW':
allx = sllx
ally = slly - s
aurx = sllx + s
aury = slly
self.canvas.create_line((allx, ally), (aurx, aury), tags=[tag_id])
# Rotate text on top and bottom rows if the tkinter version allows it.
if tkinter.TclVersion >= 8.6:
if pad['o'] == 'N' or pad['o'] == 'S':
angle = 90
else:
angle = 0
self.canvas.create_text((cx, cy), text=pad['name'], angle=angle, tags=[tag_id])
else:
self.canvas.create_text((cx, cy), text=pad['name'], tags=[tag_id])
# Make the pad draggable
self.add_draggable(tag_id)
# Now add the core cells
for cell in self.coregroup:
if 'x' not in cell:
self.print('Error: Core cell ' + cell['name'] + ' has no placement information.')
continue
# else:
# self.print('Diagnostic: Creating object for core cell ' + cell['name'])
llx = int(cell['x'])
lly = int(cell['y'])
cello = cell['o']
try:
corecell = next(item for item in self.coredefs if item['name'] == cell['cell'])
except:
# This should not happen (failsafe)
if cell['cell'] not in notfoundlist:
self.print('Warning: there is no cell named ' + cell['cell'] + ' in the libraries.')
notfoundlist.append(cell['cell'])
continue
cellw = corecell['width']
cellh = corecell['height']
if 'N' in cello or 'S' in cello:
urx = int(llx + cellw)
ury = int(lly + cellh)
else:
urx = int(llx + cellh)
ury = int(lly + cellw)
print('NOTE: cell ' + corecell['name'] + ' is rotated, w = ' + str(urx - llx) + '; h = ' + str(ury - lly))
cell['llx'] = llx
cell['lly'] = lly
cell['urx'] = urx
cell['ury'] = ury
# Watch for out-of-window position in core cells.
corellx = self.llx
corelly = self.lly
coreurx = self.urx
coreury = self.ury
for cell in self.coregroup:
if 'llx' not in cell:
# Error message for this was handled above
continue
llx = cell['llx']
lly = height - cell['lly']
urx = cell['urx']
ury = height - cell['ury']
# Check for out-of-window cell
if llx < corellx:
corellx = llx
if lly < corelly:
corelly = lly
if urx > coreurx:
coreurx = urx
if ury > coreury:
coreury = ury
tag_id = cell['name']
cell_color = 'gray40'
sllx = self.scale * llx
slly = self.scale * lly
surx = self.scale * urx
sury = self.scale * ury
self.canvas.create_rectangle((sllx, slly), (surx, sury), fill=cell_color, tags=[tag_id])
cx = (sllx + surx) / 2
cy = (slly + sury) / 2
s = 10 if cell['width'] >= 10 else cell['width']
if cell['height'] < s:
s = cell['height']
# Create an indicator line at the bottom left corner of the cell
if cell['o'] == 'N':
allx = sllx
ally = slly - s
aurx = sllx + s
aury = slly
elif cell['o'] == 'E':
allx = sllx
ally = sury + s
aurx = sllx + s
aury = sury
elif cell['o'] == 'S':
allx = surx
ally = sury + s
aurx = surx - s
aury = sury
elif cell['o'] == 'W':
allx = surx
ally = slly - s
aurx = surx - s
aury = slly
elif cell['o'] == 'FN':
allx = surx
ally = slly - s
aurx = surx - s
aury = slly
elif cell['o'] == 'FE':
allx = surx
ally = sury + s
aurx = surx - s
aury = sury
elif cell['o'] == 'FS':
allx = sllx
ally = sury + s
aurx = sllx + s
aury = sury
elif cell['o'] == 'FW':
allx = sllx
ally = slly - s
aurx = sllx + s
aury = slly
self.canvas.create_line((allx, ally), (aurx, aury), tags=[tag_id])
# self.print('Created entry for cell ' + cell['name'] + ' at {0:g}, {1:g}'.format(cx, cy))
# Rotate text on top and bottom rows if the tkinter version allows it.
if tkinter.TclVersion >= 8.6:
if 'N' in cell['o'] or 'S' in cell['o']:
angle = 90
else:
angle = 0
self.canvas.create_text((cx, cy), text=cell['name'], angle=angle, tags=[tag_id])
else:
self.canvas.create_text((cx, cy), text=cell['name'], tags=[tag_id])
# Make the core cell draggable
self.add_core_draggable(tag_id)
# Is there a boundary size defined?
if self.urx > self.llx and self.ury > self.lly:
self.create_boundary()
# Did the core extend into negative X or Y? If so, adjust all canvas
# coordinates to fit in the window, or else objects cannot be reached
# even by zooming out (since zooming is pinned on the top corner).
offsetx = 0
offsety = 0
# NOTE: Probably want to check if the core exceeds the inner
# dimension of the pad ring, not the outer (to check and to do).
if corellx < self.llx:
offsetx = self.llx - corellx
if corelly < self.lly:
offsety = self.lly - corelly
if offsetx > 0 or offsety > 0:
self.canvas.move("all", offsetx, offsety)
# An offset implies that the chip is core limited, and the
# padframe requires additional space. This can be accomplished
# simply by running "Generate". NOTE: Since generate() calls
# populate(), be VERY SURE that this does not infinitely recurse!
self.generate(level)
# Generate a DEF file of the core area
def write_core_def(self):
self.print('Writing core placementment information in DEF file "core.def".')
mag_path = self.projectpath + '/mag'
# The core cells must always clear the I/O pads on the left and
# bottom (with the ad-hoc margin of self.margin). If core cells have
# been moved to the left or down past the padframe edge, then the
# entire core needs to be repositioned.
# To be done: draw a boundary around the core, let the edges of that
# boundary be draggable, and let the difference between the boundary
# and the core area define the margin.
if self.SWpad != []:
corellx = self.SWpad[0]['x'] + self.SWpad[0]['width'] + self.margin
corelly = self.SWpad[0]['y'] + self.SWpad[0]['height'] + self.margin
else:
corellx = self.Wpads[0]['x'] + self.Wpads[0]['height'] + self.margin
corelly = self.Spads[0]['x'] + self.Spads[0]['height'] + self.margin
offsetx = 0
offsety = 0
for corecell in self.coregroup:
if corecell['x'] < corellx:
if corellx - corecell['x'] > offsetx:
offsetx = corellx - corecell['x']
if corecell['y'] < corelly:
if corelly - corecell['y'] > offsety:
offsety = corelly - corecell['y']
if offsetx > 0 or offsety > 0:
for corecell in self.coregroup:
corecell['x'] += offsetx
corecell['y'] += offsety
# Now write the core DEF file
with open(mag_path + '/core.def', 'w') as ofile:
print('DESIGN CORE ;', file=ofile)
print('UNITS DISTANCE MICRONS 1000 ;', file=ofile)
print('COMPONENTS {0:d} ;'.format(len(self.coregroup)), file=ofile)
for corecell in self.coregroup:
print(' - ' + corecell['name'] + ' ' + corecell['cell'], file=ofile)
print(' + PLACED ( {0:d} {1:d} ) {2:s} ;'.format(int(corecell['x'] * 1000), int(corecell['y'] * 1000), corecell['o']), file=ofile)
print ('END COMPONENTS', file=ofile)
print ('END DESIGN', file=ofile)
# Create the chip boundary area
def create_boundary(self):
scale = self.scale
llx = (self.llx - 10) * scale
lly = (self.lly - 10) * scale
urx = (self.urx + 10) * scale
ury = (self.ury + 10) * scale
pad_color = 'plum1'
tag_id = 'boundary'
self.canvas.create_rectangle((llx, lly), (urx, ury), outline=pad_color, width=2, tags=[tag_id])
# Add text to the middle representing the chip and core areas
cx = ((self.llx + self.urx) / 2) * scale
cy = ((self.lly + self.ury) / 2) * scale
width = self.urx - self.llx
height = self.ury - self.lly
areatext = 'Chip dimensions (um): {0:g} x {1:g}'.format(width, height)
tag_id = 'chiparea'
self.canvas.create_text((cx, cy), text=areatext, tags=[tag_id])
# Rotate orientation according to self.pad_rotation.
def rotate_orientation(self, orient_in):
orient_v = ['N', 'E', 'S', 'W', 'N', 'E', 'S', 'W']
idxadd = int(self.pad_rotation / 90)
idx = orient_v.index(orient_in)
return orient_v[idx + idxadd]
# Read a list of cell macros (name, size, class) from a LEF library
def read_lef_macros(self, libpath, libname = None, libtype = 'iolib'):
if libtype == 'iolib':
libtext = 'I/O '
elif libtype == 'celllib':
libtext = 'core '
else:
libtext = ''
macros = []
if libname:
if os.path.splitext(libname)[1] == '':
libname += '.lef'
leffiles = glob.glob(libpath + '/' + libname)
else:
leffiles = glob.glob(libpath + '/*.lef')
if leffiles == []:
if libname:
self.print('WARNING: No file ' + libpath + '/' + libname + '.lef')
else:
self.print('WARNING: No files ' + libpath + '/*.lef')
for leffile in leffiles:
libpath = os.path.split(leffile)[0]
libname = os.path.split(libpath)[1]
self.print('Reading LEF ' + libtext + 'library ' + leffile)
with open(leffile, 'r') as ifile:
ilines = ifile.read().splitlines()
in_macro = False
for iline in ilines:
iparse = iline.split()
if iparse == []:
continue
elif iparse[0] == 'MACRO':
in_macro = True
newmacro = {}
newmacro['name'] = iparse[1]
newmacro[libtype] = leffile
macros.append(newmacro)
elif in_macro:
if iparse[0] == 'END':
if len(iparse) > 1 and iparse[1] == newmacro['name']:
in_macro = False
elif iparse[0] == 'CLASS':
newmacro['class'] = iparse[1]
if len(iparse) > 2:
newmacro['subclass'] = iparse[2]
# Use the 'ENDCAP' class to identify pad rotations
# other than BOTTOMLEFT. This is somewhat ad-hoc
# depending on the foundry; may not be generally
# applicable.
if newmacro['class'] == 'ENDCAP':
if newmacro['subclass'] == 'TOPLEFT':
self.pad_rotation = 90
elif newmacro['subclass'] == 'TOPRIGHT':
self.pad_rotation = 180
elif newmacro['subclass'] == 'BOTTOMRIGHT':
self.pad_rotation = 270
else:
newmacro['subclass'] = None
elif iparse[0] == 'SIZE':
newmacro['width'] = float(iparse[1])
newmacro['height'] = float(iparse[3])
elif iparse[0] == 'ORIGIN':
newmacro['x'] = float(iparse[1])
newmacro['y'] = float(iparse[2])
return macros
# Read a list of cell names from a verilog file
# If filename is relative, then check in the same directory as the verilog
# top-level netlist (vlogpath) and in the subdirectory 'source/' of the top-
# level directory. Also check in the ~/design/ip/ directory. These are
# common include paths for the simulation.
def read_verilog_lib(self, incpath, vlogpath):
iocells = []
if not os.path.isfile(incpath) and incpath[0] != '/':
locincpath = vlogpath + '/' + incpath
if not os.path.isfile(locincpath):
locincpath = vlogpath + '/source/' + incpath
if not os.path.isfile(locincpath):
projectpath = os.path.split(vlogpath)[0]
designpath = os.path.split(projectpath)[0]
locincpath = designpath + '/ip/' + incpath
else:
locincpath = incpath
if not os.path.isfile(locincpath):
self.print('File ' + incpath + ' not found (at ' + locincpath + ')!')
else:
self.print('Reading verilog library ' + locincpath)
with open(locincpath, 'r') as ifile:
ilines = ifile.read().splitlines()
for iline in ilines:
iparse = re.split('[\t ()]', iline)
while '' in iparse:
iparse.remove('')
if iparse == []:
continue
elif iparse[0] == 'module':
iocells.append(iparse[1])
return iocells
# Generate a LEF abstract view from a magic layout. If "outpath" is not
# "None", then write output to outputpath (this is required if the input
# file is in a read-only directory).
def write_lef_file(self, magfile, outpath=None):
mag_path = os.path.split(magfile)[0]
magfullname = os.path.split(magfile)[1]
module = os.path.splitext(magfullname)[0]
if outpath:
write_path = outpath
else:
write_path = mag_path
self.print('Generating LEF view from layout for module ' + module)
with open(write_path + '/pfg_write_lef.tcl', 'w') as ofile:
print('drc off', file=ofile)
print('box 0 0 0 0', file=ofile)
# NOTE: Using "-force" option in case an IP with a different but
# compatible tech is used (e.g., EFHX035A IP inside EFXH035C). This
# is not checked for legality!
if outpath:
print('load ' + magfile + ' -force', file=ofile)
else:
print('load ' + module + ' -force', file=ofile)
print('lef write -hide', file=ofile)
print('quit', file=ofile)
magicexec = self.magic_path if self.magic_path else 'magic'
mproc = subprocess.Popen([magicexec, '-dnull', '-noconsole',
'pfg_write_lef.tcl'],
stdin = subprocess.PIPE, stdout = subprocess.PIPE,
stderr = subprocess.PIPE, cwd = write_path, universal_newlines = True)
self.watch(mproc)
os.remove(write_path + '/pfg_write_lef.tcl')
# Watch a running process, polling for output and updating the GUI message
# window as output arrives. Return only when the process has exited.
# Note that this process cannot handle stdin(), so any input to the process
# must be passed from a file.
def watch(self, process):
if process == None:
return
while True:
status = process.poll()
if status != None:
try:
outputpair = process.communicate(timeout=1)
except ValueError:
self.print("Process forced stop, status " + str(status))
else:
for line in outputpair[0].splitlines():
self.print(line)
for line in outputpair[1].splitlines():
self.print(line, file=sys.stderr)
break
else:
sresult = select.select([process.stdout, process.stderr], [], [], 0)[0]
if process.stdout in sresult:
outputline = process.stdout.readline().strip()
self.print(outputline)
elif process.stderr in sresult:
outputline = process.stderr.readline().strip()
self.print(outputline, file=sys.stderr)
else:
self.update_idletasks()
# Reimport the pad list by reading the top-level verilog netlist. Determine
# what pads are listed in the file, and check against the existing pad list.
# The verilog/ directory should have a .v file containing a module of the
# same name as self.project (ip-name). The .v filename should have the
# same name as well (but not necessarily). To do: Handle import of
# projects having a top-level schematic instead of a verilog netlist.
def vlogimport(self):
if self.ef_format:
config_dir = '/.ef-config'
else:
config_dir = '/.config'
# First find the process PDK name for this project. Read the nodeinfo.json
# file and find the list of I/O cell libraries.
if self.techpath:
pdkpath = self.techpath
elif os.path.islink(self.projectpath + config_dir + '/techdir'):
pdkpath = os.path.realpath(self.projectpath + config_dir + '/techdir')
else:
self.print('Error: Cannot determine path to PDK. Try using option -tech-path=')
return
self.print('Importing verilog sources.')
nodeinfopath = pdkpath + config_dir + '/nodeinfo.json'
ioleflist = []
if os.path.exists(nodeinfopath):
self.print('Reading known I/O cell libraries from ' + nodeinfopath)
with open(nodeinfopath, 'r') as ifile:
itop = json.load(ifile)
if 'iocells' in itop:
ioleflist = []
for iolib in itop['iocells']:
if '/' in iolib:
# Entries <lib>/<cell> refer to specific files
if self.ef_format:
iolibpath = pdkpath + '/libs.ref/lef/' + iolib
else:
iolibpath = pdkpath + '/libs.ref/' + iolib
if os.path.splitext(iolib)[1] == '':
if not os.path.exists(iolibpath):
iolibpath = iolibpath + '.lib'
if not os.path.exists(iolibpath):
self.print('Warning: nodeinfo.json bad I/O library path ' + iolibpath)
ioleflist.append(iolibpath)
else:
# All other entries refer to everything in the directory.
if self.ef_format:
iolibpath = pdkpath + '/libs.ref/lef/' + iolib
else:
iolibpath = pdkpath + '/libs.ref/' + iolib + '/lef/'
iolibfiles = glob.glob(iolibpath + '/*.lef')
if len(iolibfiles) == 0:
self.print('Warning: nodeinfo.json bad I/O library path ' + iolibpath)
ioleflist.extend(iolibfiles)
else:
# Diagnostic
self.print('Cannot read PDK information file ' + nodeinfopath)
# Fallback behavior: List everything in libs.ref/lef/ beginning with "IO"
if len(ioleflist) == 0:
if self.ef_format:
ioleflist = glob.glob(pdkpath + '/libs.ref/lef/IO*/*.lef')
else:
ioleflist = glob.glob(pdkpath + '/libs.ref/IO*/lef/*.lef')
if len(ioleflist) == 0:
self.print('Cannot find any I/O cell libraries for this technology')
return
# Read the LEF libraries to get a list of all available cells. Keep
# this list of cells in "celldefs".
celldefs = []
ioliblist = []
ioleflibs = []
for iolib in ioleflist:
iolibpath = os.path.split(iolib)[0]
iolibfile = os.path.split(iolib)[1]
ioliblist.append(os.path.split(iolibpath)[1])
celldefs.extend(self.read_lef_macros(iolibpath, iolibfile, 'iolib'))
verilogcells = []
newpadlist = []
coredefs = []
corecells = []
corecelllist = []
lefprocessed = []
busrex = re.compile('.*\[[ \t]*([0-9]+)[ \t]*:[ \t]*([0-9]+)[ \t]*\]')
vlogpath = self.projectpath + '/verilog'
vlogfile = vlogpath + '/' + self.project + '.v'
# Verilog netlists are too difficult to parse from a simple script.
# Use qflow tools to convert to SPICE, if they are available. Parse
# the verilog only for "include" statements to find the origin of
# the various IP blocks, and then parse the SPICE file to get the
# full list of instances.
#
# (to be done)
if os.path.isfile(vlogfile):
with open(vlogfile, 'r') as ifile:
vloglines = ifile.read().splitlines()
for vlogline in vloglines:
vlogparse = re.split('[\t ()]', vlogline)
while '' in vlogparse:
vlogparse.remove('')
if vlogparse == []:
continue
elif vlogparse[0] == '//':
continue
elif vlogparse[0] == '`include':
incpath = vlogparse[1].strip('"')
libpath = os.path.split(incpath)[0]
libname = os.path.split(libpath)[1]
libfile = os.path.split(incpath)[1]
# Read the verilog library for module names to match
# against macro names in celldefs.
modulelist = self.read_verilog_lib(incpath, vlogpath)
matching = list(item for item in celldefs if item['name'] in modulelist)
for imatch in matching:
verilogcells.append(imatch['name'])
leffile = imatch['iolib']
if leffile not in ioleflibs:
ioleflibs.append(leffile)
# Read a corresponding LEF file entry for non-I/O macros, if one
# can be found (this handles files in the PDK).
if len(matching) == 0:
if libname != '':
# (NOTE: Assumes full path starting with '/')
lefpath = libpath.replace('verilog', 'lef')
lefname = libfile.replace('.v', '.lef')
if not os.path.exists(lefpath + '/' + lefname):
leffiles = glob.glob(lefpath + '/*.lef')
else:
leffiles = [lefpath + '/' + lefname]
for leffile in leffiles:
if leffile in ioleflibs:
continue
elif leffile in lefprocessed:
continue
else:
lefprocessed.append(leffile)
lefname = os.path.split(leffile)[1]
newcoredefs = self.read_lef_macros(lefpath, lefname, 'celllib')
coredefs.extend(newcoredefs)
corecells.extend(list(item['name'] for item in newcoredefs))
if leffiles == []:
maglefname = libfile.replace('.v', '.mag')
# Handle PDK files with a maglef/ view but no LEF file.
maglefpath = libpath.replace('verilog', 'maglef')
if not os.path.exists(maglefpath + '/' + maglefname):
magleffiles = glob.glob(maglefpath + '/*.mag')
else:
magleffiles = [maglefpath + '/' + maglefname]
if magleffiles == []:
# Handle user ip/ files with a maglef/ view but
# no LEF file.
maglefpath = libpath.replace('verilog', 'maglef')
designpath = os.path.split(self.projectpath)[0]
maglefpath = designpath + '/ip/' + maglefpath
if not os.path.exists(maglefpath + '/' + maglefname):
magleffiles = glob.glob(maglefpath + '/*.mag')
else:
magleffiles = [maglefpath + '/' + maglefname]
for magleffile in magleffiles:
# Generate LEF file. Since PDK and ip/ entries
# are not writeable, write into the project mag/
# directory.
magpath = self.projectpath + '/mag'
magname = os.path.split(magleffile)[1]
magroot = os.path.splitext(magname)[0]
leffile = magpath + '/' + magroot + '.lef'
if not os.path.isfile(leffile):
self.write_lef_file(magleffile, magpath)
if leffile in ioleflibs:
continue
elif leffile in lefprocessed:
continue
else:
lefprocessed.append(leffile)
lefname = os.path.split(leffile)[1]
newcoredefs = self.read_lef_macros(magpath, lefname, 'celllib')
coredefs.extend(newcoredefs)
corecells.extend(list(item['name'] for item in newcoredefs))
# LEF files generated on-the-fly are not needed
# after they have been parsed.
# os.remove(leffile)
# Check if all modules in modulelist are represented by
# corresponding LEF macros. If not, then go looking for a LEF
# file in the mag/ or maglef/ directory. Then, go looking for
# a .mag file in the mag/ or maglef/ directory, and build a
# LEF macro from it.
matching = list(item['name'] for item in coredefs if item['name'] in modulelist)
for module in modulelist:
if module not in matching:
lefpath = self.projectpath + '/lef'
magpath = self.projectpath + '/mag'
maglefpath = self.projectpath + '/mag'
lefname = libfile.replace('.v', '.lef')
# If the verilog file root name is not the same as
# the module name, then make a quick check for a
# LEF file with the same root name as the verilog.
# That indicates that the module does not exist in
# the LEF file, probably because it is a primary
# module that does not correspond to any layout.
leffile = lefpath + '/' + lefname
if os.path.exists(leffile):
self.print('Diagnostic: module ' + module + ' is not in ' + leffile + ' (probably a primary module)')
continue
leffile = magpath + '/' + lefname
istemp = False
if not os.path.exists(leffile):
magname = libfile.replace('.v', '.mag')
magfile = magpath + '/' + magname
if os.path.exists(magfile):
self.print('Diagnostic: Found a .mag file for ' + module + ' in ' + magfile)
self.write_lef_file(magfile)
istemp = True
else:
magleffile = maglefpath + '/' + lefname
if not os.path.exists(magleffile):
self.print('Diagnostic: (module ' + module + ') has no LEF file ' + leffile + ' or ' + magleffile)
magleffile = maglefpath + '/' + magname
if os.path.exists(magleffile):
self.print('Diagnostic: Found a .mag file for ' + module + ' in ' + magleffile)
if os.access(maglefpath, os.W_OK):
self.write_lef_file(magleffile)
leffile = magleffile
istemp = True
else:
self.write_lef_file(magleffile, magpath)
else:
self.print('Did not find a file ' + magfile)
# self.print('Warning: module ' + module + ' has no LEF or .mag views')
pass
else:
self.print('Diagnostic: Found a LEF file for ' + module + ' in ' + magleffile)
leffile = magleffile
else:
self.print('Diagnostic: Found a LEF file for ' + module + ' in ' + leffile)
if os.path.exists(leffile):
if leffile in lefprocessed:
continue
else:
lefprocessed.append(leffile)
newcoredefs = self.read_lef_macros(magpath, lefname, 'celllib')
# The LEF file generated on-the-fly is not needed
# any more after parsing the macro(s).
# if istemp:
# os.remove(leffile)
coredefs.extend(newcoredefs)
corecells.extend(list(item['name'] for item in newcoredefs))
else:
# self.print('Failed to find a LEF view for module ' + module)
pass
elif vlogparse[0] in verilogcells:
# Check for array of pads
bushigh = buslow = -1
if len(vlogparse) >= 3:
bmatch = busrex.match(vlogline)
if bmatch:
bushigh = int(bmatch.group(1))
buslow = int(bmatch.group(2))
for i in range(buslow, bushigh + 1):
newpad = {}
if i >= 0:
newpad['name'] = vlogparse[1] + '[' + str(i) + ']'
else:
newpad['name'] = vlogparse[1]
newpad['cell'] = vlogparse[0]
padcell = next(item for item in celldefs if item['name'] == vlogparse[0])
newpad['iolib'] = padcell['iolib']
newpad['class'] = padcell['class']
newpad['subclass'] = padcell['subclass']
newpad['width'] = padcell['width']
newpad['height'] = padcell['height']
newpadlist.append(newpad)
elif vlogparse[0] in corecells:
# Check for array of cells
bushigh = buslow = -1
if len(vlogparse) >= 3:
bmatch = busrex.match(vlogline)
if bmatch:
bushigh = int(bmatch.group(1))
buslow = int(bmatch.group(2))
for i in range(buslow, bushigh + 1):
newcorecell = {}
if i >= 0:
newcorecell['name'] = vlogparse[1] + '[' + str(i) + ']'
else:
newcorecell['name'] = vlogparse[1]
newcorecell['cell'] = vlogparse[0]
corecell = next(item for item in coredefs if item['name'] == vlogparse[0])
newcorecell['celllib'] = corecell['celllib']
newcorecell['class'] = corecell['class']
newcorecell['subclass'] = corecell['subclass']
newcorecell['width'] = corecell['width']
newcorecell['height'] = corecell['height']
corecelllist.append(newcorecell)
self.print('')
self.print('Source file information:')
self.print('Source filename: ' + vlogfile)
self.print('Number of I/O libraries is ' + str(len(ioleflibs)))
self.print('Number of library cells in I/O libraries used: ' + str(len(verilogcells)))
self.print('Number of core celldefs is ' + str(len(coredefs)))
self.print('')
self.print('Number of I/O cells in design: ' + str(len(newpadlist)))
self.print('Number of core cells in design: ' + str(len(corecelllist)))
self.print('')
# Save the results
self.celldefs = celldefs
self.coredefs = coredefs
self.vlogpads = newpadlist
self.corecells = corecelllist
self.ioleflibs = ioleflibs
# Check self.vlogpads, which was created during import (above) against
# self.(N,S,W,E)pads, which was read from the DEF file (if there was one)
# Also check self.corecells, which was created during import against
# self.coregroup, which was read from the DEF file.
def resolve(self):
self.print('Resolve differences in verilog and LEF views.')
samepads = []
addedpads = []
removedpads = []
# (1) Create list of entries that are in both self.vlogpads and self.()pads
# (2) Create list of entries that are in self.vlogpads but not in self.()pads
allpads = self.Npads + self.NEpad + self.Epads + self.SEpad + self.Spads + self.SWpad + self.Wpads + self.NWpad
for pad in self.vlogpads:
newpadname = pad['name']
try:
lpad = next(item for item in allpads if item['name'] == newpadname)
except:
addedpads.append(pad)
else:
samepads.append(lpad)
# (3) Create list of entries that are in allpads but not in self.vlogpads
for pad in allpads:
newpadname = pad['name']
try:
lpad = next(item for item in self.vlogpads if item['name'] == newpadname)
except:
removedpads.append(pad)
# Print results
if len(addedpads) > 0:
self.print('Added pads:')
for pad in addedpads:
self.print(pad['name'] + ' (' + pad['cell'] + ')')
if len(removedpads) > 0:
plist = []
nspacers = 0
for pad in removedpads:
if 'subclass' in pad:
if pad['subclass'] != 'SPACER':
plist.append(pad)
else:
nspacers += 1
if nspacers > 0:
self.print(str(nspacers) + ' spacer cells ignored.')
if len(plist) > 0:
self.print('Removed pads:')
for pad in removedpads:
self.print(pad['name'] + ' (' + pad['cell'] + ')')
if len(addedpads) + len(removedpads) == 0:
self.print('Pad list has not changed.')
# Remove all cells from the "removed" list, with comment
allpads = [self.Npads, self.NEpad, self.Epads, self.SEpad, self.Spads, self.SWpad, self.Wpads, self.NWpad]
for pad in removedpads:
rname = pad['name']
for row in allpads:
try:
rpad = next(item for item in row if item['name'] == rname)
except:
rpad = None
else:
row.remove(rpad)
# Now the verilog file has no placement information, so the old padlist
# entries (if they exist) are preferred. Add to these the new padlist
# entries
# First pass for unassigned pads: Use of "CLASS ENDCAP" is preferred
# for identifying corner pads. Otherwise, if 'CORNER' or 'corner' is
# in the pad name, then make sure there is one per row in the first
# position. This is not foolproof and depends on the cell library
# using the text 'corner' in the name of the corner cell. However,
# if the ad hoc methods fail, the user can still manually move the
# corner cells to the right place (to be done: Identify if library
# uses ENDCAP designation for corner cells up front; don't go
# looking for 'corner' text if the cells are easily identifiable by
# LEF class).
for pad in addedpads[:]:
iscorner = False
if 'class' in pad and pad['class'] == 'ENDCAP':
iscorner = True
elif 'CORNER' in pad['cell'].upper():
iscorner = True
if iscorner:
if self.NWpad == []:
self.NWpad.append(pad)
pad['o'] = 'E'
addedpads.remove(pad)
elif self.NEpad == []:
self.NEpad.append(pad)
pad['o'] = 'S'
addedpads.remove(pad)
elif self.SEpad == []:
self.SEpad.append(pad)
pad['o'] = 'W'
addedpads.remove(pad)
elif self.SWpad == []:
self.SWpad.append(pad)
pad['o'] = 'N'
addedpads.remove(pad)
numN = len(self.Npads)
numS = len(self.Spads)
numE = len(self.Epads)
numW = len(self.Wpads)
minnum = min(numN, numS, numE, numW)
minnum = max(minnum, int(len(addedpads) / 4))
# Add pads in clockwise order. Note that S and E pads are defined counterclockwise
for pad in addedpads:
if numN < minnum:
self.Npads.append(pad)
numN += 1
pad['o'] = 'S'
self.print("Adding pad " + pad['name'] + " to Npads")
elif numE < minnum:
self.Epads.insert(0, pad)
numE += 1
pad['o'] = 'W'
self.print("Adding pad " + pad['name'] + " to Epads")
elif numS < minnum:
self.Spads.insert(0, pad)
numS += 1
pad['o'] = 'N'
self.print("Adding pad " + pad['name'] + " to Spads")
# elif numW < minnum:
else:
self.Wpads.append(pad)
numW += 1
pad['o'] = 'E'
self.print("Adding pad " + pad['name'] + " to Wpads")
minnum = min(numN, numS, numE, numW)
minnum = max(minnum, int(len(addedpads) / 4))
# Make sure all pads have included information from the cell definition
allpads = self.Npads + self.NEpad + self.Epads + self.SEpad + self.Spads + self.SWpad + self.Wpads + self.NWpad
for pad in allpads:
if 'width' not in pad:
try:
celldef = next(item for item in celldefs if item['name'] == pad['cell'])
except:
self.print('Cell ' + pad['cell'] + ' not found!')
else:
pad['width'] = celldef['width']
pad['height'] = celldef['height']
pad['class'] = celldef['class']
pad['subclass'] = celldef['subclass']
# Now treat the core cells in the same way (resolve list parsed from verilog
# against the list parsed from DEF)
# self.print('Diagnostic: ')
# self.print('self.corecells = ' + str(self.corecells))
# self.print('self.coregroup = ' + str(self.coregroup))
samecore = []
addedcore = []
removedcore = []
# (1) Create list of entries that are in both self.corecells and self.coregroup
# (2) Create list of entries that are in self.corecells but not in self.coregroup
for cell in self.corecells:
newcellname = cell['name']
try:
lcore = next(item for item in self.coregroup if item['name'] == newcellname)
except:
addedcore.append(cell)
else:
samecore.append(lcore)
# (3) Create list of entries that are in self.coregroup but not in self.corecells
for cell in self.coregroup:
newcellname = cell['name']
try:
lcore = next(item for item in self.corecells if item['name'] == newcellname)
except:
removedcore.append(cell)
# Print results
if len(addedcore) > 0:
self.print('Added core cells:')
for cell in addedcore:
self.print(cell['name'] + ' (' + cell['cell'] + ')')
if len(removedcore) > 0:
clist = []
for cell in removedcore:
clist.append(cell)
if len(clist) > 0:
self.print('Removed core cells:')
for cell in removedcore:
self.print(cell['name'] + ' (' + cell['cell'] + ')')
if len(addedcore) + len(removedcore) == 0:
self.print('Core cell list has not changed.')
# Remove all cells from the "removed" list
coregroup = self.coregroup
for cell in removedcore:
rname = cell['name']
try:
rcell = next(item for item in coregroup if item['name'] == rname)
except:
rcell = None
else:
coregroup.remove(rcell)
# Add all cells from the "added" list to coregroup
for cell in addedcore:
rname = cell['name']
try:
rcell = next(item for item in coregroup if item['name'] == rname)
except:
coregroup.append(cell)
if not 'o' in cell:
cell['o'] = 'N'
if not 'x' in cell:
if len(self.Wpads) > 0:
pad = self.Wpads[0]
padx = pad['x'] if 'x' in pad else 0
cell['x'] = padx + pad['height'] + self.margin
else:
cell['x'] = self.margin
if not 'y' in cell:
if len(self.Spads) > 0:
pad = self.Spads[0]
pady = pad['y'] if 'y' in pad else 0
cell['y'] = pady + pad['height'] + self.margin
else:
cell['y'] = self.margin
else:
rcell = None
# Make sure all core cells have included information from the cell definition
for cell in coregroup:
if 'width' not in cell:
try:
coredef = next(item for item in coredefs if item['name'] == cell['cell'])
except:
self.print('Cell ' + cell['cell'] + ' not found!')
else:
cell['width'] = coredef['width']
cell['height'] = coredef['height']
cell['class'] = coredef['class']
cell['subclass'] = coredef['subclass']
# Generate a new padframe by writing the configuration file, running
# padring, reading back the DEF file, and (re)poplulating the workspace
def generate(self, level):
self.print('Generate legal padframe using padring')
# Write out the configuration file
self.writeconfig()
# Run the padring app
self.runpadring()
# Rotate pads in the output if pad orientations are different from
# padring's expectations
self.rotate_pads_in_def()
# Read the placement information back from the generated DEF file
self.readplacement()
# Resolve differences (e.g., remove spacers)
self.resolve()
# Recreate and draw the padframe view on the canvas
self.populate(level + 1)
self.frame_configure(None)
# Write a new configuration file
def writeconfig(self):
mag_path = self.projectpath + '/mag'
if not os.path.exists(mag_path):
self.print('Error: No project path /mag directory exists. Cannot write config file.')
return
self.print('Writing padring configuration file.')
# Determine cell width and height from pad sizes.
# NOTE: This compresses the chip to the minimum dimensions
# allowed by the arrangement of pads. Use a "core" block to
# force the area larger than minimum (not yet implemented)
topwidth = 0
for pad in self.Npads:
if 'width' not in pad:
self.print('No width: pad = ' + str(pad))
topwidth += pad['width']
# Add in the corner cells
if self.NWpad != []:
topwidth += self.NWpad[0]['height']
if self.NEpad != []:
topwidth += self.NEpad[0]['width']
botwidth = 0
for pad in self.Spads:
botwidth += pad['width']
# Add in the corner cells
if self.SWpad != []:
botwidth += self.SWpad[0]['width']
if self.SEpad != []:
botwidth += self.SEpad[0]['height']
width = max(botwidth, topwidth)
# if width < self.urx - self.llx:
# width = self.urx - self.llx
leftheight = 0
for pad in self.Wpads:
leftheight += pad['width']
# Add in the corner cells
if self.NWpad != []:
leftheight += self.NWpad[0]['height']
if self.SWpad != []:
leftheight += self.SWpad[0]['width']
rightheight = 0
for pad in self.Epads:
rightheight += pad['width']
# Add in the corner cells
if self.NEpad != []:
rightheight += self.NEpad[0]['width']
if self.SEpad != []:
rightheight += self.SEpad[0]['height']
height = max(leftheight, rightheight)
# Check the dimensions of the core cells. If they exceed the available
# padframe area, then expand the padframe to accomodate the core.
corellx = coreurx = (self.llx + self.urx) / 2
corelly = coreury = (self.lly + self.ury) / 2
for corecell in self.coregroup:
corient = corecell['o']
if 'S' in corient or 'N' in corient:
cwidth = corecell['width']
cheight = corecell['height']
else:
cwidth = corecell['height']
cheight = corecell['width']
if corecell['x'] < corellx:
corellx = corecell['x']
if corecell['x'] + cwidth > coreurx:
coreurx = corecell['x'] + cwidth
if corecell['y'] < corelly:
corelly = corecell['y']
if corecell['y'] + cheight > coreury:
coreury = corecell['y'] + cheight
coreheight = coreury - corelly
corewidth = coreurx - corellx
# Ignoring the possibility of overlaps with nonstandard-sized pads,
# assuming that the user has visually separated them. Only check
# the core bounds against the standard padframe inside edge.
if self.SWpad != [] and self.SEpad != []:
if corewidth > width - self.SWpad[0]['width'] - self.SEpad[0]['width']:
width = corewidth + self.SWpad[0]['width'] + self.SEpad[0]['width']
if self.NWpad != [] and self.SWpad != []:
if coreheight > height - self.NWpad[0]['height'] - self.SWpad[0]['height']:
height = coreheight + self.NWpad[0]['height'] + self.SWpad[0]['height']
# Core cells are given a margin of self.margin from the pad inside edge, so the
# core area passed to the padring app is 2 * self.margin larger than the
# measured size of the core area.
width += 2 * self.margin
height += 2 * self.margin
if self.keep_cfg == False or not os.path.exists(mag_path + '/padframe.cfg'):
if os.path.exists(mag_path + '/padframe.cfg'):
# Copy the previous padframe.cfg file to a backup. In case something
# goes badly wrong, this should be the only file overwritten, and can
# be recovered from the backup.
shutil.copy(mag_path + '/padframe.cfg', mag_path + '/padframe.cfg.bak')
with open(mag_path + '/padframe.cfg', 'w') as ofile:
print('AREA ' + str(int(width)) + ' ' + str(int(height)) + ' ;',
file=ofile)
print('', file=ofile)
for pad in self.NEpad:
print('CORNER ' + pad['name'] + ' SW ' + pad['cell'] + ' ;',
file=ofile)
for pad in self.SEpad:
print('CORNER ' + pad['name'] + ' NW ' + pad['cell'] + ' ;',
file=ofile)
for pad in self.SWpad:
print('CORNER ' + pad['name'] + ' NE ' + pad['cell'] + ' ;',
file=ofile)
for pad in self.NWpad:
print('CORNER ' + pad['name'] + ' SE ' + pad['cell'] + ' ;',
file=ofile)
for pad in self.Npads:
flip = 'F ' if 'F' in pad['o'] else ''
print('PAD ' + pad['name'] + ' N ' + flip + pad['cell'] + ' ;',
file=ofile)
for pad in self.Epads:
flip = 'F ' if 'F' in pad['o'] else ''
print('PAD ' + pad['name'] + ' E ' + flip + pad['cell'] + ' ;',
file=ofile)
for pad in self.Spads:
flip = 'F ' if 'F' in pad['o'] else ''
print('PAD ' + pad['name'] + ' S ' + flip + pad['cell'] + ' ;',
file=ofile)
for pad in self.Wpads:
flip = 'F ' if 'F' in pad['o'] else ''
print('PAD ' + pad['name'] + ' W ' + flip + pad['cell'] + ' ;',
file=ofile)
# Run the padring app.
def runpadring(self):
mag_path = self.projectpath + '/mag'
if not os.path.exists(mag_path):
self.print('No path /mag exists in project space; cannot run padring.')
return
self.print('Running padring')
if self.padring_path:
padringopts = [self.padring_path]
else:
padringopts = ['padring']
# Diagnostic
# self.print('Used libraries (self.ioleflibs) = ' + str(self.ioleflibs))
for iolib in self.ioleflibs:
padringopts.append('-L')
padringopts.append(iolib)
padringopts.append('--def')
padringopts.append('padframe.def')
padringopts.append('padframe.cfg')
self.print('Running ' + str(padringopts))
p = subprocess.Popen(padringopts, stdout = subprocess.PIPE,
stderr = subprocess.PIPE, cwd = mag_path)
self.watch(p)
# Read placement information from the DEF file generated by padring.
def readplacement(self, precheck=False):
self.print('Reading placement information from DEF file')
mag_path = self.projectpath + '/mag'
if not os.path.isfile(mag_path + '/padframe.def'):
if not precheck:
self.print('No file padframe.def: pad frame was not generated.')
return False
# Very simple DEF file parsing. The placement DEF only contains a
# COMPONENTS section. Certain assumptions are made about the syntax
# that depends on the way 'padring' writes its output. This is not
# a rigorous DEF parser!
units = 1000
in_components = False
Npadlist = []
Spadlist = []
Epadlist = []
Wpadlist = []
NEpad = []
NWpad = []
SEpad = []
SWpad = []
coregroup = []
# Reset bounds
self.llx = self.lly = self.urx = self.ury = 0
corners = 0
with open(mag_path + '/padframe.def', 'r') as ifile:
deflines = ifile.read().splitlines()
for line in deflines:
if 'UNITS DISTANCE MICRONS' in line:
units = line.split()[3]
elif in_components:
lparse = line.split()
if lparse[0] == '-':
instname = lparse[1]
cellname = lparse[2]
elif lparse[0] == '+':
if lparse[1] == 'PLACED':
placex = lparse[3]
placey = lparse[4]
placeo = lparse[6]
newpad = {}
newpad['name'] = instname
newpad['cell'] = cellname
try:
celldef = next(item for item in self.celldefs if item['name'] == cellname)
except:
celldef = None
else:
newpad['iolib'] = celldef['iolib']
newpad['width'] = celldef['width']
newpad['height'] = celldef['height']
newpad['class'] = celldef['class']
newpad['subclass'] = celldef['subclass']
newpad['x'] = float(placex) / float(units)
newpad['y'] = float(placey) / float(units)
newpad['o'] = placeo
# Adjust bounds
if celldef:
if newpad['x'] < self.llx:
self.llx = newpad['x']
if newpad['y'] < self.lly:
self.lly = newpad['y']
if newpad['o'] == 'N' or newpad['o'] == 'S':
padurx = newpad['x'] + celldef['width']
padury = newpad['y'] + celldef['height']
else:
padurx = newpad['x'] + celldef['height']
padury = newpad['y'] + celldef['width']
if padurx > self.urx:
self.urx = padurx
if padury > self.ury:
self.ury = padury
# First four entries in the DEF file are corners
# padring puts the lower left corner at zero, so
# use the zero coordinates to determine which pads
# are which. Note that padring assumes the corner
# pad is drawn in the SW corner position!
if corners < 4:
if newpad['x'] == 0 and newpad['y'] == 0:
SWpad.append(newpad)
elif newpad['x'] == 0:
NWpad.append(newpad)
elif newpad['y'] == 0:
SEpad.append(newpad)
else:
NEpad.append(newpad)
corners += 1
else:
# Place according to orientation. If orientation
# is not standard, be sure to make it standard!
placeo = self.rotate_orientation(placeo)
if placeo == 'N':
Spadlist.append(newpad)
elif placeo == 'E':
Wpadlist.append(newpad)
elif placeo == 'S':
Npadlist.append(newpad)
else:
Epadlist.append(newpad)
elif 'END COMPONENTS' in line:
in_components = False
elif 'COMPONENTS' in line:
in_components = True
self.Npads = Npadlist
self.Wpads = Wpadlist
self.Spads = Spadlist
self.Epads = Epadlist
self.NWpad = NWpad
self.NEpad = NEpad
self.SWpad = SWpad
self.SEpad = SEpad
# The padframe has its own DEF file from the padring app, but the core
# does not. The core needs to be floorplanned in a very similar manner.
# This will be done by searching for a DEF file of the project top-level
# layout. If none exists, it is created by generating it from the layout.
# If the top-level layout does not exist, then all core cells are placed
# at the origin, and the origin placed at the padframe inside corner.
mag_path = self.projectpath + '/mag'
if not os.path.isfile(mag_path + '/' + self.project + '.def'):
if os.path.isfile(mag_path + '/' + self.project + '.mag'):
# Create a DEF file from the layout
with open(mag_path + '/pfg_write_def.tcl', 'w') as ofile:
print('drc off', file=ofile)
print('box 0 0 0 0', file=ofile)
print('load ' + self.project, file=ofile)
print('def write', file=ofile)
print('quit', file=ofile)
magicexec = self.magic_path if self.magic_path else 'magic'
mproc = subprocess.Popen([magicexec, '-dnull', '-noconsole',
'pfg_write_def.tcl'],
stdin = subprocess.PIPE, stdout = subprocess.PIPE,
stderr = subprocess.PIPE, cwd = mag_path, universal_newlines = True)
self.watch(mproc)
os.remove(mag_path + '/pfg_write_def.tcl')
elif not os.path.isfile(mag_path + '/core.def'):
# With no other information available, copy the corecells
# (from the verilog file) into the coregroup list.
# Position all core cells starting at the padframe top left
# inside corner, and arranging in rows without overlapping.
# Note that no attempt is made to organize the cells or
# otherwise produce an efficient layout. Any dimension larger
# than the current padframe overruns to the right or bottom.
if self.SWpad != []:
corellx = SWpad[0]['x'] + SWpad[0]['width'] + self.margin
corelly = SWpad[0]['y'] + SWpad[0]['height'] + self.margin
else:
corellx = Wpadlist[0]['x'] + Wpadlist[0]['height'] + self.margin
corelly = Spadlist[0]['x'] + Spadlist[0]['height'] + self.margin
if self.NEpad != []:
coreurx = NEpad[0]['x'] - self.margin
coreury = NEpad[0]['y'] - self.margin
else:
coreurx = Epadlist[0]['x'] - self.margin
coreury = Npadlist[0]['x'] - self.margin
locllx = corellx
testllx = corellx
loclly = corelly
testlly = corelly
nextlly = corelly
for cell in self.corecells:
testllx = locllx + cell['width']
if testllx > coreurx:
locllx = corellx
corelly = nextlly
loclly = nextlly
newcore = cell
newcore['x'] = locllx
newcore['y'] = loclly
newcore['o'] = 'N'
locllx += cell['width'] + self.margin
testlly = corelly + cell['height'] + self.margin
if testlly > nextlly:
nextlly = testlly
coregroup.append(newcore)
self.coregroup = coregroup
if os.path.isfile(mag_path + '/' + self.project + '.def'):
# Read the top-level DEF, and use it to position the core cells.
self.print('Reading the top-level cell DEF for core cell placement.')
units = 1000
in_components = False
with open(mag_path + '/' + self.project + '.def', 'r') as ifile:
deflines = ifile.read().splitlines()
for line in deflines:
if 'UNITS DISTANCE MICRONS' in line:
units = line.split()[3]
elif in_components:
lparse = line.split()
if lparse[0] == '-':
instname = lparse[1]
# NOTE: Magic should not drop the entire path to the
# cell for the cellname; this needs to be fixed! To
# work around it, remove any path components.
cellpath = lparse[2]
cellname = os.path.split(cellpath)[1]
elif lparse[0] == '+':
if lparse[1] == 'PLACED':
placex = lparse[3]
placey = lparse[4]
placeo = lparse[6]
newcore = {}
newcore['name'] = instname
newcore['cell'] = cellname
try:
celldef = next(item for item in self.coredefs if item['name'] == cellname)
except:
celldef = None
else:
newcore['celllib'] = celldef['celllib']
newcore['width'] = celldef['width']
newcore['height'] = celldef['height']
newcore['class'] = celldef['class']
newcore['subclass'] = celldef['subclass']
newcore['x'] = float(placex) / float(units)
newcore['y'] = float(placey) / float(units)
newcore['o'] = placeo
coregroup.append(newcore)
elif 'END COMPONENTS' in line:
in_components = False
elif 'COMPONENTS' in line:
in_components = True
self.coregroup = coregroup
elif os.path.isfile(mag_path + '/core.def'):
# No DEF or .mag file, so fallback position is the last core.def
# file generated by this script.
self.read_core_def(precheck=precheck)
return True
# Read placement information from the "padframe.def" file and rotate
# all cells according to self.pad_rotation. This accounts for the
# problem that the default orientation of pads is arbitrarily defined
# by the foundry, while padring assumes that the corner pad is drawn
# in the lower-left position and other pads are drawn with the pad at
# the bottom and the buses at the top.
def rotate_pads_in_def(self):
if self.pad_rotation == 0:
return
self.print('Rotating pads in padframe DEF file.')
mag_path = self.projectpath + '/mag'
if not os.path.isfile(mag_path + '/padframe.def'):
self.print('No file padframe.def: Cannot modify pad rotations.')
return
deflines = []
with open(mag_path + '/padframe.def', 'r') as ifile:
deflines = ifile.read().splitlines()
outlines = []
in_components = False
for line in deflines:
if in_components:
lparse = line.split()
if lparse[0] == '+':
if lparse[1] == 'PLACED':
neworient = self.rotate_orientation(lparse[6])
lparse[6] = neworient
line = ' '.join(lparse)
elif 'END COMPONENTS' in line:
in_components = False
elif 'COMPONENTS' in line:
in_components = True
outlines.append(line)
with open(mag_path + '/padframe.def', 'w') as ofile:
for line in outlines:
print(line, file=ofile)
# Read placement information from the DEF file for the core (created by
# a previous run of this script)
def read_core_def(self, precheck=False):
self.print('Reading placement information from core DEF file.')
mag_path = self.projectpath + '/mag'
if not os.path.isfile(mag_path + '/core.def'):
if not precheck:
self.print('No file core.def: core placement was not generated.')
return False
# Very simple DEF file parsing, similar to the padframe.def reading
# routine above.
units = 1000
in_components = False
coregroup = []
with open(mag_path + '/core.def', 'r') as ifile:
deflines = ifile.read().splitlines()
for line in deflines:
if 'UNITS DISTANCE MICRONS' in line:
units = line.split()[3]
elif in_components:
lparse = line.split()
if lparse[0] == '-':
instname = lparse[1]
cellname = lparse[2]
elif lparse[0] == '+':
if lparse[1] == 'PLACED':
placex = lparse[3]
placey = lparse[4]
placeo = lparse[6]
newcore = {}
newcore['name'] = instname
newcore['cell'] = cellname
try:
celldef = next(item for item in self.coredefs if item['name'] == cellname)
except:
celldef = None
else:
newcore['celllib'] = celldef['celllib']
newcore['width'] = celldef['width']
newcore['height'] = celldef['height']
newcore['class'] = celldef['class']
newcore['subclass'] = celldef['subclass']
newcore['x'] = float(placex) / float(units)
newcore['y'] = float(placey) / float(units)
newcore['o'] = placeo
coregroup.append(newcore)
elif 'END COMPONENTS' in line:
in_components = False
elif 'COMPONENTS' in line:
in_components = True
self.coregroup = coregroup
return True
# Save the layout to a Magic database file (to be completed)
def save(self):
self.print('Saving results in a magic layout database.')
# Generate a list of (unique) LEF libraries for all padframe and core cells
leflist = []
for pad in self.celldefs:
if pad['iolib'] not in leflist:
leflist.append(pad['iolib'])
for core in self.coredefs:
if core['celllib'] not in leflist:
leflist.append(core['celllib'])
# Run magic, and generate the padframe with a series of commands
mag_path = self.projectpath + '/mag'
with open(mag_path + '/pfg_write_mag.tcl', 'w') as ofile:
print('drc off', file=ofile)
print('box 0 0 0 0', file=ofile)
for leffile in leflist:
print('lef read ' + leffile, file=ofile)
print('def read padframe', file=ofile)
print('select top cell', file=ofile)
print('select area', file=ofile)
print('select save padframe', file=ofile)
print('delete', file=ofile)
print('def read core', file=ofile)
print('getcell padframe', file=ofile)
print('save ' + self.project, file=ofile)
print('writeall force ' + self.project, file=ofile)
print('quit', file=ofile)
magicexec = self.magic_path if self.magic_path else 'magic'
mproc = subprocess.Popen([magicexec, '-dnull', '-noconsole',
'pfg_write_mag.tcl'],
stdin = subprocess.PIPE, stdout = subprocess.PIPE,
stderr = subprocess.PIPE, cwd = mag_path, universal_newlines = True)
self.watch(mproc)
os.remove(mag_path + '/pfg_write_mag.tcl')
self.print('Done writing layout ' + self.project + '.mag')
# Write the core DEF file if it does not exist yet.
if not os.path.isfile(mag_path + '/core.def'):
self.write_core_def()
if __name__ == '__main__':
faulthandler.register(signal.SIGUSR2)
options = []
arguments = []
for item in sys.argv[1:]:
if item.find('-', 0) == 0:
options.append(item)
else:
arguments.append(item)
if '-help' in options:
print(sys.argv[0] + ' [options]')
print('')
print('options:')
print(' -noc Print output to terminal, not the gui window')
print(' -nog No graphics, run in batch mode')
print(' -cfg Use existing padframe.cfg, do not regenerate')
print(' -padring-path=<path> path to padring executable')
print(' -magic-path=<path> path to magic executable')
print(' -tech-path=<path> path to tech root folder')
print(' -project-path=<path> path to project root folder')
print(' -help Print this usage information')
print('')
sys.exit(0)
root = tkinter.Tk()
do_gui = False if ('-nog' in options or '-nogui' in options) else True
app = SoCFloorplanner(root, do_gui)
# Allow option -noc to bypass the text-to-console redirection, so crash
# information doesn't disappear with the app.
app.use_console = False if ('-noc' in options or '-noconsole' in options) else True
if do_gui == False:
app.use_console = False
# efabless format can be specified on the command line, but note that it
# is otherwise auto-detected by checking for .config vs. .ef-config in
# the project space.
app.ef_format = True if '-ef_format' in options else False
app.keep_cfg = True if '-cfg' in options else False
app.padring_path = None
app.magic_path = None
app.techpath = None
app.projectpath = None
for option in options:
if option.split('=')[0] == '-padring-path':
app.padring_path = option.split('=')[1]
elif option.split('=')[0] == '-magic-path':
app.magic_path = option.split('=')[1]
elif option.split('=')[0] == '-tech-path':
app.techpath = option.split('=')[1]
elif option.split('=')[0] == '-project-path':
app.projectpath = option.split('=')[1]
app.projectpath = app.projectpath[:-1] if app.projectpath[-1] == '/' else app.projectpath
app.text_to_console()
app.init_padframe()
if app.do_gui:
root.mainloop()
else:
# Run 'save' in non-GUI mode
app.save()
sys.exit(0)