blob: f8b1a449449e839477c93cc93721e7967a713bbe [file] [log] [blame]
emayecs5656b2b2021-08-04 12:44:13 -04001#!/usr/bin/env python3
emayecs5966a532021-07-29 10:07:02 -04002#
3# Simple ttk treeview with split view, scrollbar, and
4# row of callback buttons
5
6import os
7import re
8import itertools
9
10import tkinter
11from tkinter import ttk
12
13#------------------------------------------------------
14# Tree view used as a multi-column list box
15#------------------------------------------------------
16
17class TreeViewSplit(ttk.Frame):
18 def __init__(self, parent, fontsize=11, *args, **kwargs):
19 ttk.Frame.__init__(self, parent, *args, **kwargs)
20 s = ttk.Style()
21 s.configure('normal.TLabel', font=('Helvetica', fontsize))
22 s.configure('title.TLabel', font=('Helvetica', fontsize, 'bold'))
23 s.configure('normal.TButton', font=('Helvetica', fontsize),
24 border = 3, relief = 'raised')
25 s.configure('Treeview.Heading', font=('Helvetica', fontsize, 'bold'))
26 s.configure('Treeview.Column', font=('Helvetica', fontsize))
27 self.fontsize = fontsize
28
29 # Last item is a list of 2-item lists, each containing the name of a button
30 # to place along the button bar at the bottom, and a callback function to
31 # run when the button is pressed.
32
33 def populate(self, title1="", item1list=[], title2="", item2list=[], buttons=[], height=10):
34 self.item1list = item1list[:]
35 self.item2list = item2list[:]
36 columns = [0, 1]
37
38 treeFrame = ttk.Frame(self)
39 treeFrame.pack(side='top', padx=5, pady=5, fill='both', expand='true')
40
41 scrollBar = ttk.Scrollbar(treeFrame)
42 scrollBar.pack(side='right', fill='y')
43 self.treeView = ttk.Treeview(treeFrame, selectmode='browse', columns=columns, height=height)
44 self.treeView.pack(side='left', fill='both', expand='true')
45 scrollBar.config(command=self.treeView.yview)
46 self.treeView.config(yscrollcommand=scrollBar.set)
47 self.treeView.column('#0', width=120, stretch='false')
48 self.treeView.heading(0, text=title1, anchor='w')
49 self.treeView.heading(1, text=title2, anchor='w')
50 buttonFrame = ttk.Frame(self)
51 buttonFrame.pack(side='bottom', fill='x')
52
53 self.treeView.tag_configure('select',background='darkslategray',foreground='white')
54
55 # Test type tags
56 self.treeView.tag_configure('error', font=('Helvetica', self.fontsize - 1), foreground = 'red')
57 self.treeView.tag_configure('clean', font=('Helvetica', self.fontsize - 1), foreground = 'green3')
58 self.treeView.tag_configure('normal', font=('Helvetica', self.fontsize - 1), foreground = 'black')
59 self.treeView.tag_configure('prep', font=('Helvetica', self.fontsize, 'bold italic'),
60 foreground = 'black', anchor = 'center')
61 self.treeView.tag_configure('header1', font=('Helvetica', self.fontsize, 'bold italic'),
62 foreground = 'brown', anchor = 'center')
63 self.treeView.tag_configure('header2', font=('Helvetica', self.fontsize - 1, 'bold'),
64 foreground = 'blue', anchor = 'center')
65 self.treeView.tag_configure('header3', font=('Helvetica', self.fontsize - 1, 'bold'),
66 foreground = 'green2', anchor = 'center')
67 self.treeView.tag_configure('header4', font=('Helvetica', self.fontsize - 1),
68 foreground = 'purple', anchor = 'center')
69
70
71 self.func_buttons = []
72 for button in buttons:
73 func = button[2]
74 # Each func_buttons entry is a list of two items; first is the
75 # button widget, and the second is a boolean that is True if the
76 # button is to be present always, False if the button is only
77 # present when there are entries in the itemlists.
78 self.func_buttons.append([ttk.Button(buttonFrame, text=button[0],
79 style = 'normal.TButton',
80 command = lambda func=func: self.func_callback(func)),
81 button[1]])
82
83 self.selectcallback = None
84 self.lastselected = None
85 self.lasttag = None
86 self.repopulate(item1list, item2list)
87
88 def get_button(self, index):
89 if index >= 0 and index < len(self.func_buttons):
90 return self.func_buttons[index][0]
91 else:
92 return None
93
94 def set_title(self, title):
95 self.treeView.heading('#0', text=title, anchor='w')
96
97 def repopulate(self, item1list=[], item2list=[]):
98
99 # Remove all children of treeview
100 self.treeView.delete(*self.treeView.get_children())
101
102 self.item1list = item1list[:]
103 self.item2list = item2list[:]
104 lines = max(len(self.item1list), len(self.item2list))
105
106 # Parse the information coming from comp.out. This is preferably
107 # handled from inside netgen, but that requires development in netgen.
108 # Note: A top-level group is denoted by an empty string.
109
110 nested = ['']
111 if lines > 0:
112 # print("Create item ID 0 parent = ''")
113 self.treeView.insert(nested[-1], 'end', text='-', iid='0',
114 value=['Initialize', 'Initialize'], tags=['prep'])
115 nested.append('0')
116 tagstyle = 'header1'
117
118 emptyrec = re.compile('^[\t ]*$')
119 subrex = re.compile('Subcircuit summary')
120 cktrex = re.compile('Circuit[\t ]+[12]:[\t ]+([^ \t]+)')
121 netrex = re.compile('NET mismatches')
122 devrex = re.compile('DEVICE mismatches')
123 seprex = re.compile('-----')
124 sumrex = re.compile('Netlists ')
125 matchrex = re.compile('.*\*\*Mismatch\*\*')
126 incircuit = False
127 watchgroup = False
128 groupnum = 0
129
130 for item1, item2, index in zip(self.item1list, self.item2list, range(lines)):
131 # Remove blank lines from the display
132 lmatch = emptyrec.match(item1)
133 if lmatch:
134 lmatch = emptyrec.match(item2)
135 if lmatch:
136 continue
137 index = str(index + 1)
138 # Parse text to determine how to structure and display it.
139 tagstyle = 'normal'
140 nextnest = None
141 lmatch = subrex.match(item1)
142 if lmatch:
143 nested = [''] # pop back to topmost level
144 nextnest = index
145 tagstyle = 'header1'
146 incircuit = False
147 watchgroup = False
148 groupnum = 0
149 item1 = 'Layout compare'
150 item2 = 'Schematic compare'
151 cname1 = 'Layout' # Placeholder
152 cname2 = 'Schematic' # Placeholder
153 else:
154 lmatch = cktrex.match(item1)
155 if lmatch and not incircuit:
156 # Pick up circuit names and replace them in the title, then use them
157 # for all following titles.
158 cname1 = lmatch.group(1)
159 lmatch = cktrex.match(item2)
160 cname2 = lmatch.group(1)
161 print("Circuit names " + cname1 + " " + cname2)
162 # Rewrite title
163 cktitem = self.treeView.item(nested[-1], values=[cname1 + ' compare',
164 cname2 + ' compare'])
165 nextnest = index
166 tagstyle = 'header2'
167 incircuit = True
168 item1 = cname1 + ' Summary'
169 item2 = cname2 + ' Summary'
170 elif lmatch:
171 continue
172 else:
173 lmatch = netrex.match(item1)
174 if lmatch:
175 if watchgroup:
176 nested = nested[0:-1]
177 nested = nested[0:-1]
178 nextnest = index
179 tagstyle = 'header2'
180 groupnum = 1
181 watchgroup = True
182 item1 = cname1 + ' Net mismatches'
183 item2 = cname2 + ' Net mismatches'
184 else:
185 lmatch = devrex.match(item1)
186 if lmatch:
187 if watchgroup:
188 nested = nested[0:-1]
189 nested = nested[0:-1]
190 nextnest = index
191 tagstyle = 'header2'
192 groupnum = 1
193 watchgroup = True
194 item1 = cname1 + ' Device mismatches'
195 item2 = cname2 + ' Device mismatches'
196 else:
197 lmatch = seprex.match(item1)
198 if lmatch:
199 if watchgroup:
200 tagstyle = 'header3'
201 item1 = 'Group ' + str(groupnum)
202 item2 = 'Group ' + str(groupnum)
203 if groupnum > 1:
204 nested = nested[0:-1]
205 groupnum += 1
206 nextnest = index
207 watchgroup = False
208 else:
209 if groupnum > 0:
210 watchgroup = True
211 continue
212 else:
213 lmatch = sumrex.match(item1)
214 if lmatch:
215 if watchgroup:
216 nested = nested[0:-1]
217 nested = nested[0:-1]
218 watchgroup = False
219 tagstyle = 'header2'
220 groupnum = 0
221
222 lmatch1 = matchrex.match(item1)
223 lmatch2 = matchrex.match(item2)
224 if lmatch1 or lmatch2:
225 tagstyle='error'
226
227 # print("Create item ID " + str(index) + " parent = " + str(nested[-1]))
228 self.treeView.insert(nested[-1], 'end', text=index, iid=index, value=[item1, item2],
229 tags=[tagstyle])
230
231 if nextnest:
232 nested.append(nextnest)
233
234 for button in self.func_buttons:
235 button[0].pack_forget()
236
237 if lines == 0:
238 self.treeView.insert('', 'end', text='-', value=['(no items)', '(no items)'])
239 for button in self.func_buttons:
240 if button[1]:
241 button[0].pack(side='left', padx = 5)
242 else:
243 for button in self.func_buttons:
244 button[0].pack(side='left', padx = 5)
245
246 # Special routine to pull in the JSON file data produced by netgen-1.5.72
247 def json_repopulate(self, lvsdata):
248
249 # Remove all children of treeview
250 self.treeView.delete(*self.treeView.get_children())
251
252 # Parse the information coming from comp.out. This is preferably
253 # handled from inside netgen, but that requires development in netgen.
254 # Note: A top-level group is denoted by an empty string.
255
256 index = 0
257 errtotal = {}
258 errtotal['net'] = 0
259 errtotal['netmatch'] = 0
260 errtotal['device'] = 0
261 errtotal['devmatch'] = 0
262 errtotal['property'] = 0
263 errtotal['pin'] = 0
264 ncells = len(lvsdata)
265 for c in range(0, ncells):
266 cellrec = lvsdata[c]
267 if c == ncells - 1:
268 topcell = True
269 else:
270 topcell = False
271
272 errcell = {}
273 errcell['net'] = 0
274 errcell['netmatch'] = 0
275 errcell['device'] = 0
276 errcell['devmatch'] = 0
277 errcell['property'] = 0
278 errcell['pin'] = 0;
279
280 # cellrec is a dictionary. Parse the cell summary, then failing nets,
281 # devices, and properties, and finally pins.
282
283 if 'name' in cellrec:
284 names = cellrec['name']
285 cname1 = names[0]
286 cname2 = names[1]
287
288 item1 = cname1
289 item2 = cname2
290 tagstyle = 'header1'
291 index += 1
292 nest0 = index
293 self.treeView.insert('', 'end', text=index, iid=index, value=[item1, item2],
294 tags=[tagstyle])
295 else:
296 # Some cells have pin comparison but are missing names (needs to be
297 # fixed in netgen. Regardless, if there's no name, then ignore.
298 continue
299
300 if 'devices' in cellrec or 'nets' in cellrec:
301 item1 = cname1 + " Summary"
302 item2 = cname2 + " Summary"
303 tagstyle = 'header2'
304 index += 1
305 nest1 = index
306 self.treeView.insert(nest0, 'end', text=index, iid=index, value=[item1, item2],
307 tags=[tagstyle])
308
309 if 'devices' in cellrec:
310 item1 = cname1 + " Devices"
311 item2 = cname2 + " Devices"
312 tagstyle = 'header3'
313 index += 1
314 nest2 = index
315 self.treeView.insert(nest1, 'end', text=index, iid=index, value=[item1, item2],
316 tags=[tagstyle])
317
318 devices = cellrec['devices']
319 devlist = [val for pair in zip(devices[0], devices[1]) for val in pair]
320 devpair = list(devlist[p:p + 2] for p in range(0, len(devlist), 2))
321 for dev in devpair:
322 c1dev = dev[0]
323 c2dev = dev[1]
324
325 item1 = c1dev[0] + "(" + str(c1dev[1]) + ")"
326 item2 = c2dev[0] + "(" + str(c2dev[1]) + ")"
327
328 diffdevs = abs(c1dev[1] - c2dev[1])
329 if diffdevs == 0:
330 tagstyle = 'normal'
331 else:
332 tagstyle = 'error'
333 errcell['device'] += diffdevs
334 if topcell:
335 errtotal['device'] += diffdevs
336 index += 1
337 nest2 = index
338 self.treeView.insert(nest1, 'end', text=index, iid=index,
339 value=[item1, item2], tags=[tagstyle])
340
341 if 'nets' in cellrec:
342 item1 = cname1 + " Nets"
343 item2 = cname2 + " Nets"
344 tagstyle = 'header3'
345 index += 1
346 nest2 = index
347 self.treeView.insert(nest1, 'end', text=index, iid=index, value=[item1, item2],
348 tags=[tagstyle])
349
350 nets = cellrec['nets']
351
352 item1 = nets[0]
353 item2 = nets[1]
354 diffnets = abs(nets[0] - nets[1])
355 if diffnets == 0:
356 tagstyle = 'normal'
357 else:
358 tagstyle = 'error'
359 errcell['net'] = diffnets
360 if topcell:
361 errtotal['net'] += diffnets
362 index += 1
363 nest2 = index
364 self.treeView.insert(nest1, 'end', text=index, iid=index,
365 value=[item1, item2], tags=[tagstyle])
366
367 if 'badnets' in cellrec:
368 badnets = cellrec['badnets']
369
370 if len(badnets) > 0:
371 item1 = cname1 + " Net Mismatches"
372 item2 = cname2 + " Net Mismatches"
373 tagstyle = 'header2'
374 index += 1
375 nest1 = index
376 self.treeView.insert(nest0, 'end', text=index, iid=index,
377 value=[item1, item2], tags=[tagstyle])
378
379 groupnum = 0
380 for group in badnets:
381 groupc1 = group[0]
382 groupc2 = group[1]
383 nnets = len(groupc1)
384
385 groupnum += 1
386 tagstyle = 'header3'
387 index += 1
388 nest2 = index
389 item1 = "Group " + str(groupnum) + ' (' + str(nnets) + ' nets)'
390 self.treeView.insert(nest1, 'end', text=index, iid=index,
391 value=[item1, item1], tags=[tagstyle])
392
393 tagstyle = 'error'
394 errcell['netmatch'] += nnets
395 if topcell:
396 errtotal['netmatch'] += nnets
397
398 for netnum in range(0, nnets):
399 if netnum > 0:
400 item1 = ""
401 index += 1
402 nest3 = index
403 self.treeView.insert(nest2, 'end', text=index, iid=index,
404 value=[item1, item1], tags=[tagstyle])
405
406 net1 = groupc1[netnum]
407 net2 = groupc2[netnum]
408 tagstyle = 'header4'
409 item1 = net1[0]
410 item2 = net2[0]
411 index += 1
412 nest3 = index
413 self.treeView.insert(nest2, 'end', text=index, iid=index,
414 value=[item1, item2], tags=[tagstyle])
415
416 # Pad shorter device list to the length of the longer one
417 netdevs = list(itertools.zip_longest(net1[1], net2[1]))
418 for devpair in netdevs:
419 devc1 = devpair[0]
420 devc2 = devpair[1]
421 tagstyle = 'normal'
422 if devc1 and devc1[0] != "":
423 item1 = devc1[0] + '/' + devc1[1] + ' = ' + str(devc1[2])
424 else:
425 item1 = ""
426 if devc2 and devc2[0] != "":
427 item2 = devc2[0] + '/' + devc2[1] + ' = ' + str(devc2[2])
428 else:
429 item2 = ""
430 index += 1
431 nest3 = index
432 self.treeView.insert(nest2, 'end', text=index, iid=index,
433 value=[item1, item2], tags=[tagstyle])
434
435 if 'badelements' in cellrec:
436 badelements = cellrec['badelements']
437
438 if len(badelements) > 0:
439 item1 = cname1 + " Device Mismatches"
440 item2 = cname2 + " Device Mismatches"
441 tagstyle = 'header2'
442 index += 1
443 nest1 = index
444 self.treeView.insert(nest0, 'end', text=index, iid=index,
445 value=[item1, item2], tags=[tagstyle])
446
447 groupnum = 0
448 for group in badelements:
449 groupc1 = group[0]
450 groupc2 = group[1]
451 ndevs = len(groupc1)
452
453 groupnum += 1
454 tagstyle = 'header3'
455 index += 1
456 nest2 = index
457 item1 = "Group " + str(groupnum) + ' (' + str(ndevs) + ' devices)'
458 self.treeView.insert(nest1, 'end', text=index, iid=index,
459 value=[item1, item1], tags=[tagstyle])
460
461 tagstyle = 'error'
462 errcell['devmatch'] += ndevs
463 if topcell:
464 errtotal['devmatch'] += ndevs
465
466 for elemnum in range(0, ndevs):
467 if elemnum > 0:
468 item1 = ""
469 index += 1
470 nest3 = index
471 self.treeView.insert(nest2, 'end', text=index, iid=index,
472 value=[item1, item1], tags=[tagstyle])
473
474 elem1 = groupc1[elemnum]
475 elem2 = groupc2[elemnum]
476 tagstyle = 'header4'
477 item1 = elem1[0]
478 item2 = elem2[0]
479 index += 1
480 nest3 = index
481 self.treeView.insert(nest2, 'end', text=index, iid=index,
482 value=[item1, item2], tags=[tagstyle])
483
484 # Pad shorter pin list to the length of the longer one
485 elempins = list(itertools.zip_longest(elem1[1], elem2[1]))
486 for pinpair in elempins:
487 pinc1 = pinpair[0]
488 pinc2 = pinpair[1]
489 tagstyle = 'normal'
490 if pinc1 and pinc1[0] != "":
491 item1 = pinc1[0] + ' = ' + str(pinc1[1])
492 else:
493 item1 = ""
494 if pinc2 and pinc2[0] != "":
495 item2 = pinc2[0] + ' = ' + str(pinc2[1])
496 else:
497 item2 = ""
498 index += 1
499 nest3 = index
500 self.treeView.insert(nest2, 'end', text=index, iid=index,
501 value=[item1, item2], tags=[tagstyle])
502
503 if 'properties' in cellrec:
504 properties = cellrec['properties']
505 numproperr = len(properties)
506 if numproperr > 0:
507 item1 = cname1 + " Properties"
508 item2 = cname2 + " Properties"
509 tagstyle = 'header2'
510 index += 1
511 nest1 = index
512 self.treeView.insert(nest0, 'end', text=index, iid=index, value=[item1, item2],
513 tags=[tagstyle])
514 errcell['property'] = numproperr
515 errtotal['property'] += numproperr
516
517 for prop in properties:
518
519 if prop != properties[0]:
520 item1 = ""
521 index += 1
522 nest2 = index
523 self.treeView.insert(nest1, 'end', text=index, iid=index,
524 value=[item1, item1], tags=[tagstyle])
525
526 propc1 = prop[0]
527 propc2 = prop[1]
528
529 tagstyle = 'header3'
530 item1 = propc1[0]
531 item2 = propc2[0]
532 index += 1
533 nest2 = index
534 self.treeView.insert(nest1, 'end', text=index, iid=index,
535 value=[item1, item2], tags=[tagstyle])
536
537 # Pad shorter property list to the length of the longer one
538 elemprops = list(itertools.zip_longest(propc1[1], propc2[1]))
539 for proppair in elemprops:
540 perrc1 = proppair[0]
541 perrc2 = proppair[1]
542 tagstyle = 'normal'
543 if perrc1 and perrc1[0] != "":
544 item1 = perrc1[0] + ' = ' + str(perrc1[1])
545 else:
546 item1 = ""
547 if perrc2 and perrc2[0] != "":
548 item2 = perrc2[0] + ' = ' + str(perrc2[1])
549 else:
550 item2 = ""
551 index += 1
552 nest2 = index
553 self.treeView.insert(nest1, 'end', text=index, iid=index,
554 value=[item1, item2], tags=[tagstyle])
555
556 if 'pins' in cellrec:
557 item1 = cname1 + " Pins"
558 item2 = cname2 + " Pins"
559 tagstyle = 'header2'
560 index += 1
561 nest1 = index
562 self.treeView.insert(nest0, 'end', text=index, iid=index, value=[item1, item2],
563 tags=[tagstyle])
564
565 pins = cellrec['pins']
566 pinlist = [val for pair in zip(pins[0], pins[1]) for val in pair]
567 pinpair = list(pinlist[p:p + 2] for p in range(0, len(pinlist), 2))
568 for pin in pinpair:
569 item1 = re.sub('!$', '', pin[0].lower())
570 item2 = re.sub('!$', '', pin[1].lower())
571 if item1 == item2:
572 tagstyle = 'header4'
573 else:
574 tagstyle = 'error'
575 errcell['pin'] += 1
576 if topcell:
577 errtotal['pin'] += 1
578 index += 1
579 nest2 = index
580 self.treeView.insert(nest1, 'end', text=index, iid=index,
581 value=[item1, item2], tags=[tagstyle])
582
583 allcellerror = errcell['net'] + errcell['device'] + errcell['property'] + errcell['pin'] + errcell['netmatch'] + errcell['devmatch']
584 if allcellerror > 0:
585 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'])
586 tagstyle = 'error'
587 else:
588 item1 = 'LVS Clean'
589 tagstyle = 'clean'
590
591 item2 = ""
592 index += 1
593 nest0 = index
594 self.treeView.insert('', 'end', text=index, iid=index, value=[item1, item2],
595 tags=[tagstyle])
596
597 item1 = "Final LVS result:"
598 item2 = ""
599 tagstyle = 'header1'
600 index += 1
601 nest0 = index
602 self.treeView.insert('', 'end', text=index, iid=index, value=[item1, item2],
603 tags=[tagstyle])
604
605 allerror = errtotal['net'] + errtotal['device'] + errtotal['property'] + errtotal['pin'] + errtotal['netmatch'] + errtotal['devmatch']
606 if allerror > 0:
607 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'])
608 tagstyle = 'error'
609 else:
610 item1 = 'LVS Clean'
611 tagstyle = 'clean'
612
613 item2 = ""
614 index += 1
615 nest0 = index
616 self.treeView.insert('', 'end', text=index, iid=index, value=[item1, item2],
617 tags=[tagstyle])
618
619 for button in self.func_buttons:
620 button[0].pack_forget()
621
622 if index == 0:
623 self.treeView.insert('', 'end', text='-', value=['(no items)', '(no items)'])
624 for button in self.func_buttons:
625 if button[1]:
626 button[0].pack(side='left', padx = 5)
627 else:
628 for button in self.func_buttons:
629 button[0].pack(side='left', padx = 5)
630
631 # Return values from the treeview
632 def getlist(self):
633 return self.treeView.get_children()
634
635 def func_callback(self, callback, event=None):
636 callback(self.treeView.item(self.treeView.selection()))
637
638 def bindselect(self, callback):
639 self.selectcallback = callback
640
641 def setselect(self, value):
642 self.treeView.selection_set(value)
643
644 def selected(self):
645 value = self.treeView.item(self.treeView.selection())
646 if value['values']:
647 return value
648 else:
649 return None