blob: f8b1a449449e839477c93cc93721e7967a713bbe [file] [log] [blame]
#!/usr/bin/env python3
#
# Simple ttk treeview with split view, scrollbar, and
# row of callback buttons
import os
import re
import itertools
import tkinter
from tkinter import ttk
#------------------------------------------------------
# Tree view used as a multi-column list box
#------------------------------------------------------
class TreeViewSplit(ttk.Frame):
def __init__(self, parent, fontsize=11, *args, **kwargs):
ttk.Frame.__init__(self, parent, *args, **kwargs)
s = ttk.Style()
s.configure('normal.TLabel', font=('Helvetica', fontsize))
s.configure('title.TLabel', font=('Helvetica', fontsize, 'bold'))
s.configure('normal.TButton', font=('Helvetica', fontsize),
border = 3, relief = 'raised')
s.configure('Treeview.Heading', font=('Helvetica', fontsize, 'bold'))
s.configure('Treeview.Column', font=('Helvetica', fontsize))
self.fontsize = fontsize
# Last item is a list of 2-item lists, each containing the name of a button
# to place along the button bar at the bottom, and a callback function to
# run when the button is pressed.
def populate(self, title1="", item1list=[], title2="", item2list=[], buttons=[], height=10):
self.item1list = item1list[:]
self.item2list = item2list[:]
columns = [0, 1]
treeFrame = ttk.Frame(self)
treeFrame.pack(side='top', padx=5, pady=5, fill='both', expand='true')
scrollBar = ttk.Scrollbar(treeFrame)
scrollBar.pack(side='right', fill='y')
self.treeView = ttk.Treeview(treeFrame, selectmode='browse', columns=columns, height=height)
self.treeView.pack(side='left', fill='both', expand='true')
scrollBar.config(command=self.treeView.yview)
self.treeView.config(yscrollcommand=scrollBar.set)
self.treeView.column('#0', width=120, stretch='false')
self.treeView.heading(0, text=title1, anchor='w')
self.treeView.heading(1, text=title2, anchor='w')
buttonFrame = ttk.Frame(self)
buttonFrame.pack(side='bottom', fill='x')
self.treeView.tag_configure('select',background='darkslategray',foreground='white')
# Test type tags
self.treeView.tag_configure('error', font=('Helvetica', self.fontsize - 1), foreground = 'red')
self.treeView.tag_configure('clean', font=('Helvetica', self.fontsize - 1), foreground = 'green3')
self.treeView.tag_configure('normal', font=('Helvetica', self.fontsize - 1), foreground = 'black')
self.treeView.tag_configure('prep', font=('Helvetica', self.fontsize, 'bold italic'),
foreground = 'black', anchor = 'center')
self.treeView.tag_configure('header1', font=('Helvetica', self.fontsize, 'bold italic'),
foreground = 'brown', anchor = 'center')
self.treeView.tag_configure('header2', font=('Helvetica', self.fontsize - 1, 'bold'),
foreground = 'blue', anchor = 'center')
self.treeView.tag_configure('header3', font=('Helvetica', self.fontsize - 1, 'bold'),
foreground = 'green2', anchor = 'center')
self.treeView.tag_configure('header4', font=('Helvetica', self.fontsize - 1),
foreground = 'purple', anchor = 'center')
self.func_buttons = []
for button in buttons:
func = button[2]
# Each func_buttons entry is a list of two items; first is the
# button widget, and the second is a boolean that is True if the
# button is to be present always, False if the button is only
# present when there are entries in the itemlists.
self.func_buttons.append([ttk.Button(buttonFrame, text=button[0],
style = 'normal.TButton',
command = lambda func=func: self.func_callback(func)),
button[1]])
self.selectcallback = None
self.lastselected = None
self.lasttag = None
self.repopulate(item1list, item2list)
def get_button(self, index):
if index >= 0 and index < len(self.func_buttons):
return self.func_buttons[index][0]
else:
return None
def set_title(self, title):
self.treeView.heading('#0', text=title, anchor='w')
def repopulate(self, item1list=[], item2list=[]):
# Remove all children of treeview
self.treeView.delete(*self.treeView.get_children())
self.item1list = item1list[:]
self.item2list = item2list[:]
lines = max(len(self.item1list), len(self.item2list))
# Parse the information coming from comp.out. This is preferably
# handled from inside netgen, but that requires development in netgen.
# Note: A top-level group is denoted by an empty string.
nested = ['']
if lines > 0:
# print("Create item ID 0 parent = ''")
self.treeView.insert(nested[-1], 'end', text='-', iid='0',
value=['Initialize', 'Initialize'], tags=['prep'])
nested.append('0')
tagstyle = 'header1'
emptyrec = re.compile('^[\t ]*$')
subrex = re.compile('Subcircuit summary')
cktrex = re.compile('Circuit[\t ]+[12]:[\t ]+([^ \t]+)')
netrex = re.compile('NET mismatches')
devrex = re.compile('DEVICE mismatches')
seprex = re.compile('-----')
sumrex = re.compile('Netlists ')
matchrex = re.compile('.*\*\*Mismatch\*\*')
incircuit = False
watchgroup = False
groupnum = 0
for item1, item2, index in zip(self.item1list, self.item2list, range(lines)):
# Remove blank lines from the display
lmatch = emptyrec.match(item1)
if lmatch:
lmatch = emptyrec.match(item2)
if lmatch:
continue
index = str(index + 1)
# Parse text to determine how to structure and display it.
tagstyle = 'normal'
nextnest = None
lmatch = subrex.match(item1)
if lmatch:
nested = [''] # pop back to topmost level
nextnest = index
tagstyle = 'header1'
incircuit = False
watchgroup = False
groupnum = 0
item1 = 'Layout compare'
item2 = 'Schematic compare'
cname1 = 'Layout' # Placeholder
cname2 = 'Schematic' # Placeholder
else:
lmatch = cktrex.match(item1)
if lmatch and not incircuit:
# Pick up circuit names and replace them in the title, then use them
# for all following titles.
cname1 = lmatch.group(1)
lmatch = cktrex.match(item2)
cname2 = lmatch.group(1)
print("Circuit names " + cname1 + " " + cname2)
# Rewrite title
cktitem = self.treeView.item(nested[-1], values=[cname1 + ' compare',
cname2 + ' compare'])
nextnest = index
tagstyle = 'header2'
incircuit = True
item1 = cname1 + ' Summary'
item2 = cname2 + ' Summary'
elif lmatch:
continue
else:
lmatch = netrex.match(item1)
if lmatch:
if watchgroup:
nested = nested[0:-1]
nested = nested[0:-1]
nextnest = index
tagstyle = 'header2'
groupnum = 1
watchgroup = True
item1 = cname1 + ' Net mismatches'
item2 = cname2 + ' Net mismatches'
else:
lmatch = devrex.match(item1)
if lmatch:
if watchgroup:
nested = nested[0:-1]
nested = nested[0:-1]
nextnest = index
tagstyle = 'header2'
groupnum = 1
watchgroup = True
item1 = cname1 + ' Device mismatches'
item2 = cname2 + ' Device mismatches'
else:
lmatch = seprex.match(item1)
if lmatch:
if watchgroup:
tagstyle = 'header3'
item1 = 'Group ' + str(groupnum)
item2 = 'Group ' + str(groupnum)
if groupnum > 1:
nested = nested[0:-1]
groupnum += 1
nextnest = index
watchgroup = False
else:
if groupnum > 0:
watchgroup = True
continue
else:
lmatch = sumrex.match(item1)
if lmatch:
if watchgroup:
nested = nested[0:-1]
nested = nested[0:-1]
watchgroup = False
tagstyle = 'header2'
groupnum = 0
lmatch1 = matchrex.match(item1)
lmatch2 = matchrex.match(item2)
if lmatch1 or lmatch2:
tagstyle='error'
# print("Create item ID " + str(index) + " parent = " + str(nested[-1]))
self.treeView.insert(nested[-1], 'end', text=index, iid=index, value=[item1, item2],
tags=[tagstyle])
if nextnest:
nested.append(nextnest)
for button in self.func_buttons:
button[0].pack_forget()
if lines == 0:
self.treeView.insert('', 'end', text='-', value=['(no items)', '(no items)'])
for button in self.func_buttons:
if button[1]:
button[0].pack(side='left', padx = 5)
else:
for button in self.func_buttons:
button[0].pack(side='left', padx = 5)
# Special routine to pull in the JSON file data produced by netgen-1.5.72
def json_repopulate(self, lvsdata):
# Remove all children of treeview
self.treeView.delete(*self.treeView.get_children())
# Parse the information coming from comp.out. This is preferably
# handled from inside netgen, but that requires development in netgen.
# Note: A top-level group is denoted by an empty string.
index = 0
errtotal = {}
errtotal['net'] = 0
errtotal['netmatch'] = 0
errtotal['device'] = 0
errtotal['devmatch'] = 0
errtotal['property'] = 0
errtotal['pin'] = 0
ncells = len(lvsdata)
for c in range(0, ncells):
cellrec = lvsdata[c]
if c == ncells - 1:
topcell = True
else:
topcell = False
errcell = {}
errcell['net'] = 0
errcell['netmatch'] = 0
errcell['device'] = 0
errcell['devmatch'] = 0
errcell['property'] = 0
errcell['pin'] = 0;
# cellrec is a dictionary. Parse the cell summary, then failing nets,
# devices, and properties, and finally pins.
if 'name' in cellrec:
names = cellrec['name']
cname1 = names[0]
cname2 = names[1]
item1 = cname1
item2 = cname2
tagstyle = 'header1'
index += 1
nest0 = index
self.treeView.insert('', 'end', text=index, iid=index, value=[item1, item2],
tags=[tagstyle])
else:
# Some cells have pin comparison but are missing names (needs to be
# fixed in netgen. Regardless, if there's no name, then ignore.
continue
if 'devices' in cellrec or 'nets' in cellrec:
item1 = cname1 + " Summary"
item2 = cname2 + " Summary"
tagstyle = 'header2'
index += 1
nest1 = index
self.treeView.insert(nest0, 'end', text=index, iid=index, value=[item1, item2],
tags=[tagstyle])
if 'devices' in cellrec:
item1 = cname1 + " Devices"
item2 = cname2 + " Devices"
tagstyle = 'header3'
index += 1
nest2 = index
self.treeView.insert(nest1, 'end', text=index, iid=index, value=[item1, item2],
tags=[tagstyle])
devices = cellrec['devices']
devlist = [val for pair in zip(devices[0], devices[1]) for val in pair]
devpair = list(devlist[p:p + 2] for p in range(0, len(devlist), 2))
for dev in devpair:
c1dev = dev[0]
c2dev = dev[1]
item1 = c1dev[0] + "(" + str(c1dev[1]) + ")"
item2 = c2dev[0] + "(" + str(c2dev[1]) + ")"
diffdevs = abs(c1dev[1] - c2dev[1])
if diffdevs == 0:
tagstyle = 'normal'
else:
tagstyle = 'error'
errcell['device'] += diffdevs
if topcell:
errtotal['device'] += diffdevs
index += 1
nest2 = index
self.treeView.insert(nest1, 'end', text=index, iid=index,
value=[item1, item2], tags=[tagstyle])
if 'nets' in cellrec:
item1 = cname1 + " Nets"
item2 = cname2 + " Nets"
tagstyle = 'header3'
index += 1
nest2 = index
self.treeView.insert(nest1, 'end', text=index, iid=index, value=[item1, item2],
tags=[tagstyle])
nets = cellrec['nets']
item1 = nets[0]
item2 = nets[1]
diffnets = abs(nets[0] - nets[1])
if diffnets == 0:
tagstyle = 'normal'
else:
tagstyle = 'error'
errcell['net'] = diffnets
if topcell:
errtotal['net'] += diffnets
index += 1
nest2 = index
self.treeView.insert(nest1, 'end', text=index, iid=index,
value=[item1, item2], tags=[tagstyle])
if 'badnets' in cellrec:
badnets = cellrec['badnets']
if len(badnets) > 0:
item1 = cname1 + " Net Mismatches"
item2 = cname2 + " Net Mismatches"
tagstyle = 'header2'
index += 1
nest1 = index
self.treeView.insert(nest0, 'end', text=index, iid=index,
value=[item1, item2], tags=[tagstyle])
groupnum = 0
for group in badnets:
groupc1 = group[0]
groupc2 = group[1]
nnets = len(groupc1)
groupnum += 1
tagstyle = 'header3'
index += 1
nest2 = index
item1 = "Group " + str(groupnum) + ' (' + str(nnets) + ' nets)'
self.treeView.insert(nest1, 'end', text=index, iid=index,
value=[item1, item1], tags=[tagstyle])
tagstyle = 'error'
errcell['netmatch'] += nnets
if topcell:
errtotal['netmatch'] += nnets
for netnum in range(0, nnets):
if netnum > 0:
item1 = ""
index += 1
nest3 = index
self.treeView.insert(nest2, 'end', text=index, iid=index,
value=[item1, item1], tags=[tagstyle])
net1 = groupc1[netnum]
net2 = groupc2[netnum]
tagstyle = 'header4'
item1 = net1[0]
item2 = net2[0]
index += 1
nest3 = index
self.treeView.insert(nest2, 'end', text=index, iid=index,
value=[item1, item2], tags=[tagstyle])
# Pad shorter device list to the length of the longer one
netdevs = list(itertools.zip_longest(net1[1], net2[1]))
for devpair in netdevs:
devc1 = devpair[0]
devc2 = devpair[1]
tagstyle = 'normal'
if devc1 and devc1[0] != "":
item1 = devc1[0] + '/' + devc1[1] + ' = ' + str(devc1[2])
else:
item1 = ""
if devc2 and devc2[0] != "":
item2 = devc2[0] + '/' + devc2[1] + ' = ' + str(devc2[2])
else:
item2 = ""
index += 1
nest3 = index
self.treeView.insert(nest2, 'end', text=index, iid=index,
value=[item1, item2], tags=[tagstyle])
if 'badelements' in cellrec:
badelements = cellrec['badelements']
if len(badelements) > 0:
item1 = cname1 + " Device Mismatches"
item2 = cname2 + " Device Mismatches"
tagstyle = 'header2'
index += 1
nest1 = index
self.treeView.insert(nest0, 'end', text=index, iid=index,
value=[item1, item2], tags=[tagstyle])
groupnum = 0
for group in badelements:
groupc1 = group[0]
groupc2 = group[1]
ndevs = len(groupc1)
groupnum += 1
tagstyle = 'header3'
index += 1
nest2 = index
item1 = "Group " + str(groupnum) + ' (' + str(ndevs) + ' devices)'
self.treeView.insert(nest1, 'end', text=index, iid=index,
value=[item1, item1], tags=[tagstyle])
tagstyle = 'error'
errcell['devmatch'] += ndevs
if topcell:
errtotal['devmatch'] += ndevs
for elemnum in range(0, ndevs):
if elemnum > 0:
item1 = ""
index += 1
nest3 = index
self.treeView.insert(nest2, 'end', text=index, iid=index,
value=[item1, item1], tags=[tagstyle])
elem1 = groupc1[elemnum]
elem2 = groupc2[elemnum]
tagstyle = 'header4'
item1 = elem1[0]
item2 = elem2[0]
index += 1
nest3 = index
self.treeView.insert(nest2, 'end', text=index, iid=index,
value=[item1, item2], tags=[tagstyle])
# Pad shorter pin list to the length of the longer one
elempins = list(itertools.zip_longest(elem1[1], elem2[1]))
for pinpair in elempins:
pinc1 = pinpair[0]
pinc2 = pinpair[1]
tagstyle = 'normal'
if pinc1 and pinc1[0] != "":
item1 = pinc1[0] + ' = ' + str(pinc1[1])
else:
item1 = ""
if pinc2 and pinc2[0] != "":
item2 = pinc2[0] + ' = ' + str(pinc2[1])
else:
item2 = ""
index += 1
nest3 = index
self.treeView.insert(nest2, 'end', text=index, iid=index,
value=[item1, item2], tags=[tagstyle])
if 'properties' in cellrec:
properties = cellrec['properties']
numproperr = len(properties)
if numproperr > 0:
item1 = cname1 + " Properties"
item2 = cname2 + " Properties"
tagstyle = 'header2'
index += 1
nest1 = index
self.treeView.insert(nest0, 'end', text=index, iid=index, value=[item1, item2],
tags=[tagstyle])
errcell['property'] = numproperr
errtotal['property'] += numproperr
for prop in properties:
if prop != properties[0]:
item1 = ""
index += 1
nest2 = index
self.treeView.insert(nest1, 'end', text=index, iid=index,
value=[item1, item1], tags=[tagstyle])
propc1 = prop[0]
propc2 = prop[1]
tagstyle = 'header3'
item1 = propc1[0]
item2 = propc2[0]
index += 1
nest2 = index
self.treeView.insert(nest1, 'end', text=index, iid=index,
value=[item1, item2], tags=[tagstyle])
# Pad shorter property list to the length of the longer one
elemprops = list(itertools.zip_longest(propc1[1], propc2[1]))
for proppair in elemprops:
perrc1 = proppair[0]
perrc2 = proppair[1]
tagstyle = 'normal'
if perrc1 and perrc1[0] != "":
item1 = perrc1[0] + ' = ' + str(perrc1[1])
else:
item1 = ""
if perrc2 and perrc2[0] != "":
item2 = perrc2[0] + ' = ' + str(perrc2[1])
else:
item2 = ""
index += 1
nest2 = index
self.treeView.insert(nest1, 'end', text=index, iid=index,
value=[item1, item2], tags=[tagstyle])
if 'pins' in cellrec:
item1 = cname1 + " Pins"
item2 = cname2 + " Pins"
tagstyle = 'header2'
index += 1
nest1 = index
self.treeView.insert(nest0, 'end', text=index, iid=index, value=[item1, item2],
tags=[tagstyle])
pins = cellrec['pins']
pinlist = [val for pair in zip(pins[0], pins[1]) for val in pair]
pinpair = list(pinlist[p:p + 2] for p in range(0, len(pinlist), 2))
for pin in pinpair:
item1 = re.sub('!$', '', pin[0].lower())
item2 = re.sub('!$', '', pin[1].lower())
if item1 == item2:
tagstyle = 'header4'
else:
tagstyle = 'error'
errcell['pin'] += 1
if topcell:
errtotal['pin'] += 1
index += 1
nest2 = index
self.treeView.insert(nest1, 'end', text=index, iid=index,
value=[item1, item2], tags=[tagstyle])
allcellerror = errcell['net'] + errcell['device'] + errcell['property'] + errcell['pin'] + errcell['netmatch'] + errcell['devmatch']
if allcellerror > 0:
item1 = 'Errors: Net = ' + str(errcell['net']) + ', Device = ' + str(errcell['device']) + ', Property = ' + str(errcell['property']) + ', Pin = ' + str(errcell['pin']) + ', Net match = ' + str(errcell['netmatch']) + ', Device match = ' + str(errcell['devmatch'])
tagstyle = 'error'
else:
item1 = 'LVS Clean'
tagstyle = 'clean'
item2 = ""
index += 1
nest0 = index
self.treeView.insert('', 'end', text=index, iid=index, value=[item1, item2],
tags=[tagstyle])
item1 = "Final LVS result:"
item2 = ""
tagstyle = 'header1'
index += 1
nest0 = index
self.treeView.insert('', 'end', text=index, iid=index, value=[item1, item2],
tags=[tagstyle])
allerror = errtotal['net'] + errtotal['device'] + errtotal['property'] + errtotal['pin'] + errtotal['netmatch'] + errtotal['devmatch']
if allerror > 0:
item1 = 'Errors: Net = ' + str(errtotal['net']) + ', Device = ' + str(errtotal['device']) + ', Property = ' + str(errtotal['property']) + ', Pin = ' + str(errtotal['pin']) + ', Net match = ' + str(errtotal['netmatch']) + ', Device match = ' + str(errtotal['devmatch'])
tagstyle = 'error'
else:
item1 = 'LVS Clean'
tagstyle = 'clean'
item2 = ""
index += 1
nest0 = index
self.treeView.insert('', 'end', text=index, iid=index, value=[item1, item2],
tags=[tagstyle])
for button in self.func_buttons:
button[0].pack_forget()
if index == 0:
self.treeView.insert('', 'end', text='-', value=['(no items)', '(no items)'])
for button in self.func_buttons:
if button[1]:
button[0].pack(side='left', padx = 5)
else:
for button in self.func_buttons:
button[0].pack(side='left', padx = 5)
# Return values from the treeview
def getlist(self):
return self.treeView.get_children()
def func_callback(self, callback, event=None):
callback(self.treeView.item(self.treeView.selection()))
def bindselect(self, callback):
self.selectcallback = callback
def setselect(self, value):
self.treeView.selection_set(value)
def selected(self):
value = self.treeView.item(self.treeView.selection())
if value['values']:
return value
else:
return None