blob: 6020bae8f9432977b7e2579bea29ff60926d5d4c [file] [log] [blame]
#!/usr/bin/env python3
# flake8: noqa
# Copyright 2020 Efabless Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# --------------------------------------------------------
# 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
# User preferences file (if it exists)
prefsfile = "~/design/.profile/prefs.json"
# ------------------------------------------------------
# Dialog for entering a pad
# ------------------------------------------------------
class ConsoleText(tkinter.Text):
linelimit = 500
class IORedirector(object):
"""A general class for redirecting I/O to this Text widget."""
def __init__(self, text_area):
self.text_area = text_area
class StdoutRedirector(IORedirector):
"""A class for redirecting stdout to this Text widget."""
def write(self, str):
self.text_area.write(str, False)
class StderrRedirector(IORedirector):
"""A class for redirecting stderr to this Text widget."""
def write(self, str):
self.text_area.write(str, True)
def __init__(self, master=None, cnf={}, **kw):
"""See the __init__ for Tkinter.Text."""
tkinter.Text.__init__(self, master, cnf, **kw)
self.tag_configure("stdout", background="white", foreground="black")
self.tag_configure("stderr", background="white", foreground="red")
# None of these works! Cannot change selected text background!
self.config(selectbackground="blue", selectforeground="white")
self.tag_configure("sel", background="blue", foreground="white")
def write(self, val, is_stderr=False):
lines = int(self.index("end-1c").split(".")[0])
if lines > self.linelimit:
self.delete("1.0", str(lines - self.linelimit) + ".0")
self.insert("end", val, "stderr" if is_stderr else "stdout")
self.see("end")
def limit(self, val):
self.linelimit = val
class Dialog(tkinter.Toplevel):
def __init__(
self, parent, message=None, title=None, seed=None, border="blue", **kwargs
):
tkinter.Toplevel.__init__(self, parent)
self.transient(parent)
if title:
self.title(title)
self.configure(background=border, padx=2, pady=2)
self.obox = ttk.Frame(self)
self.obox.pack(side="left", fill="both", expand="true")
self.parent = parent
self.result = None
body = ttk.Frame(self.obox)
self.initial_focus = self.body(body, message, seed, **kwargs)
body.pack(padx=5, pady=5)
self.buttonbox()
self.grab_set()
if not self.initial_focus:
self.initial_focus = self
self.protocol("WM_DELETE_WINDOW", self.cancel)
self.geometry("+%d+%d" % (parent.winfo_rootx() + 50, parent.winfo_rooty() + 50))
self.initial_focus.focus_set()
self.wait_window(self)
# Construction hooks
def body(self, master, **kwargs):
# Create dialog body. Return widget that should have
# initial focus. This method should be overridden
pass
def buttonbox(self):
# Add standard button box. Override if you don't want the
# standard buttons
box = ttk.Frame(self.obox)
self.okb = ttk.Button(
box, text="OK", width=10, command=self.ok, default="active"
)
self.okb.pack(side="left", padx=5, pady=5)
w = ttk.Button(box, text="Cancel", width=10, command=self.cancel)
w.pack(side="left", padx=5, pady=5)
self.bind("<Return>", self.ok)
self.bind("<Escape>", self.cancel)
box.pack(fill="x", expand="true")
# Standard button semantics
def ok(self, event=None):
if not self.validate():
self.initial_focus.focus_set() # put focus back
return
self.withdraw()
self.update_idletasks()
self.result = self.apply()
self.cancel()
def cancel(self, event=None):
# Put focus back to the parent window
self.parent.focus_set()
self.destroy()
def validate(self):
return 1 # Override this
def apply(self):
return None # Override this
class PadNameDialog(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(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 = 100
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 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 Exception:
try:
pad = next(item for item in self.Npads if item["name"] == tag)
except Exception:
pad = None
if not pad:
try:
pad = next(item for item in self.Epads if item["name"] == tag)
except Exception:
pad = None
if not pad:
try:
pad = next(item for item in self.Spads if item["name"] == tag)
except Exception:
pad = None
if not pad:
try:
pad = next(item for item in self.Wpads if item["name"] == tag)
except Exception:
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 Exception:
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 Exception:
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 Exception:
pass
else:
padrow0 = row
break
for row in padrows:
try:
testpad = next(item for item in row if item["name"] == pad1["name"])
except Exception:
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 Exception:
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 Exception:
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 Exception:
# 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 Exception:
# 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,
end="",
)
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", 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):
self.print("Importing verilog sources.")
# First find the process PDK name for this project. Read the nodeinfo.json
# file and find the list of I/O cell libraries.
techpath = self.techpath if self.techpath else self.projectpath
if os.path.exists(techpath + "/.config"):
config_dir = "/.config"
else:
config_dir = "/.ef-config"
if os.path.exists(techpath + config_dir):
self.ef_format = True
pdkpath = (
self.techpath
if self.techpath
else os.path.realpath(self.projectpath + config_dir + "/techdir")
)
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
libcell = iolib.split("/")
if self.ef_format:
iolibpath = pdkpath + "/libs.ref/lef/" + libcell[0]
else:
iolibpath = (
pdkpath + "/libs.ref/" + libcell[0] + "/lef/"
)
ioleflist.extend(
glob.glob(iolibpath + "/" + libcell[1] + ".lef")
)
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/"
print(iolibpath)
ioleflist.extend(glob.glob(iolibpath + "/*.lef"))
else:
# Diagnostic
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"
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]
# hack
newpad["name"] = newpad["name"].replace("\\", "")
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 Exception:
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 Exception:
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 Exception:
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 Exception:
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 Exception:
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 Exception:
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 Exception:
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 Exception:
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 Exception:
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"
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
# SCALE UP
# width *= 1.4
# height *= 1.4
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):
self.print("Running padring")
mag_path = self.projectpath + "/mag"
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] == "FIXED":
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 Exception:
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 Exception:
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":
lparse[1] = "FIXED"
neworient = 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 Exception:
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)