blob: 31c6b8f2be935dcb1304b91e938fac64ebe64bc7 [file] [log] [blame]
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001#!/usr/bin/env python3
2#
3#--------------------------------------------------------
4# Padframe Editor and Core Floorplanner
5#
6#--------------------------------------------------------
7# Written by Tim Edwards
8# efabless, inc.
9# April 24, 2019
10# Version 0.5
11# Based on https://github.com/YosysHQ/padring (requirement)
12# Update: May 9, 2019 to add console message window
13# Update: May 10, 2019 to incorporate core floorplanning
14# Update: Jan 31, 2020 to allow batch operation
15#--------------------------------------------------------
16
17import os
18import re
19import sys
20import glob
21import json
22import math
23import shutil
24import signal
25import select
26import subprocess
27import faulthandler
28
29import tkinter
30from tkinter import ttk
31from tkinter import filedialog
32import tksimpledialog
33from consoletext import ConsoleText
34
35# User preferences file (if it exists)
36prefsfile = '~/design/.profile/prefs.json'
37
38#------------------------------------------------------
39# Dialog for entering a pad
40#------------------------------------------------------
41
42class PadNameDialog(tksimpledialog.Dialog):
43 def body(self, master, warning=None, seed=None):
44 if warning:
45 ttk.Label(master, text=warning).grid(row = 0, columnspan = 2, sticky = 'wns')
46 ttk.Label(master, text="Enter new group name:").grid(row = 1, column = 0, sticky = 'wns')
47 self.nentry = ttk.Entry(master)
48 self.nentry.grid(row = 1, column = 1, sticky = 'ewns')
49 if seed:
50 self.nentry.insert(0, seed)
51 return self.nentry # initial focus
52
53 def apply(self):
54 return self.nentry.get()
55
56#------------------------------------------------------
57# Dialog for entering core dimensions
58#------------------------------------------------------
59
60class CoreSizeDialog(tksimpledialog.Dialog):
61 def body(self, master, warning="Chip core dimensions", seed=None):
62 if warning:
63 ttk.Label(master, text=warning).grid(row = 0, columnspan = 2, sticky = 'wns')
64 ttk.Label(master, text="Enter core width x height (microns):").grid(row = 1, column = 0, sticky = 'wns')
65 self.nentry = ttk.Entry(master)
66 self.nentry.grid(row = 1, column = 1, sticky = 'ewns')
67
68
69 if seed:
70 self.nentry.insert(0, seed)
71 return self.nentry # initial focus
72
73 def apply(self):
74 return self.nentry.get()
75
76#------------------------------------------------
77# SoC Floorplanner and Padframe Generator GUI
78#------------------------------------------------
79
80class SoCFloorplanner(ttk.Frame):
81 """Open Galaxy Pad Frame Generator."""
82
83 def __init__(self, parent = None, *args, **kwargs):
84 '''See the __init__ for Tkinter.Toplevel.'''
85 ttk.Frame.__init__(self, parent, *args[1:], **kwargs)
86 self.root = parent
87 self.init_data()
88 if args[0] == True:
89 self.do_gui = True
90 self.init_gui()
91 else:
92 self.do_gui = False
93 self.use_console = False
94
95 def on_quit(self):
96 """Exits program."""
97 quit()
98
99 def init_gui(self):
100 """Builds GUI."""
101 global prefsfile
102
103 message = []
104 fontsize = 11
105
106 # Read user preferences file, get default font size from it.
107 prefspath = os.path.expanduser(prefsfile)
108 if os.path.exists(prefspath):
109 with open(prefspath, 'r') as f:
110 self.prefs = json.load(f)
111 if 'fontsize' in self.prefs:
112 fontsize = self.prefs['fontsize']
113 else:
114 self.prefs = {}
115
116 s = ttk.Style()
117
118 available_themes = s.theme_names()
119 s.theme_use(available_themes[0])
120
121 s.configure('normal.TButton', font=('Helvetica', fontsize), border = 3, relief = 'raised')
122 s.configure('title.TLabel', font=('Helvetica', fontsize, 'bold italic'),
123 foreground = 'brown', anchor = 'center')
124 s.configure('blue.TLabel', font=('Helvetica', fontsize), foreground = 'blue')
125 s.configure('normal.TLabel', font=('Helvetica', fontsize))
126 s.configure('normal.TCheckbutton', font=('Helvetica', fontsize))
127 s.configure('normal.TMenubutton', font=('Helvetica', fontsize))
128 s.configure('normal.TEntry', font=('Helvetica', fontsize), background='white')
129 s.configure('pad.TLabel', font=('Helvetica', fontsize), foreground = 'blue', relief = 'flat')
130 s.configure('select.TLabel', font=('Helvetica', fontsize, 'bold'), foreground = 'white',
131 background = 'blue', relief = 'flat')
132
133 # parent.withdraw()
134 self.root.title('Padframe Generator and Core Floorplanner')
135 self.root.option_add('*tearOff', 'FALSE')
136 self.pack(side = 'top', fill = 'both', expand = 'true')
137 self.root.protocol("WM_DELETE_WINDOW", self.on_quit)
138
139 pane = tkinter.PanedWindow(self, orient = 'vertical', sashrelief = 'groove',
140 sashwidth = 6)
141 pane.pack(side = 'top', fill = 'both', expand = 'true')
142
143 self.toppane = ttk.Frame(pane)
144 self.botpane = ttk.Frame(pane)
145
146 self.toppane.columnconfigure(0, weight = 1)
147 self.toppane.rowconfigure(0, weight = 1)
148 self.botpane.columnconfigure(0, weight = 1)
149 self.botpane.rowconfigure(0, weight = 1)
150
151 # Scrolled frame using canvas widget
152 self.pframe = tkinter.Frame(self.toppane)
153 self.pframe.grid(row = 0, column = 0, sticky = 'news')
154 self.pframe.rowconfigure(0, weight = 1)
155 self.pframe.columnconfigure(0, weight = 1)
156
157 # Add column on the left, listing all groups and the pads they belong to.
158 # This starts as just a frame to be filled. Use a canvas to create a
159 # scrolled frame.
160
161 # The primary frame holds the canvas
162 self.canvas = tkinter.Canvas(self.pframe, background = "white")
163 self.canvas.grid(row = 0, column = 0, sticky = 'news')
164
165 # Add Y scrollbar to pad list window
166 xscrollbar = ttk.Scrollbar(self.pframe, orient = 'horizontal')
167 xscrollbar.grid(row = 1, column = 0, sticky = 'news')
168 yscrollbar = ttk.Scrollbar(self.pframe, orient = 'vertical')
169 yscrollbar.grid(row = 0, column = 1, sticky = 'news')
170
171 self.canvas.config(xscrollcommand = xscrollbar.set)
172 xscrollbar.config(command = self.canvas.xview)
173 self.canvas.config(yscrollcommand = yscrollbar.set)
174 yscrollbar.config(command = self.canvas.yview)
175
176 self.canvas.bind("<Button-4>", self.on_scrollwheel)
177 self.canvas.bind("<Button-5>", self.on_scrollwheel)
178
179 # Configure callback
180 self.canvas.bind("<Configure>", self.frame_configure)
181
182 # Add a text window to capture output. Redirect print statements to it.
183 self.console = ttk.Frame(self.botpane)
184 self.console.grid(column = 0, row = 0, sticky = "news")
185 self.text_box = ConsoleText(self.console, wrap='word', height = 4)
186 self.text_box.pack(side='left', fill='both', expand='true')
187 console_scrollbar = ttk.Scrollbar(self.console)
188 console_scrollbar.pack(side='right', fill='y')
189 # Attach console to scrollbar
190 self.text_box.config(yscrollcommand = console_scrollbar.set)
191 console_scrollbar.config(command = self.text_box.yview)
192
193 # Add the bottom bar with buttons
194 self.bbar = ttk.Frame(self.botpane)
195 self.bbar.grid(column = 0, row = 1, sticky = "news")
196
197 self.bbar.import_button = ttk.Button(self.bbar, text='Import',
198 command=self.vlogimport, style='normal.TButton')
199 self.bbar.import_button.grid(column=0, row=0, padx = 5)
200
201 self.bbar.generate_button = ttk.Button(self.bbar, text='Generate',
202 command=self.generate, style='normal.TButton')
203 self.bbar.generate_button.grid(column=1, row=0, padx = 5)
204
205 self.bbar.save_button = ttk.Button(self.bbar, text='Save',
206 command=self.save, style='normal.TButton')
207 self.bbar.save_button.grid(column=2, row=0, padx = 5)
208
209 self.bbar.cancel_button = ttk.Button(self.bbar, text='Quit',
210 command=self.on_quit, style='normal.TButton')
211 self.bbar.cancel_button.grid(column=3, row=0, padx = 5)
212
213 pane.add(self.toppane)
214 pane.add(self.botpane)
215 pane.paneconfig(self.toppane, stretch='first')
216
217 def init_data(self):
218
219 self.vlogpads = []
220 self.corecells = []
221 self.Npads = []
222 self.Spads = []
223 self.Epads = []
224 self.Wpads = []
225 self.NEpad = []
226 self.NWpad = []
227 self.SEpad = []
228 self.SWpad = []
229 self.coregroup = []
230
231 self.celldefs = []
232 self.coredefs = []
233 self.selected = []
234 self.ioleflibs = []
235 self.llx = 0
236 self.lly = 0
237 self.urx = 0
238 self.ury = 0
239
240 self.event_data = {}
241 self.event_data['x0'] = 0
242 self.event_data['y0'] = 0
243 self.event_data['x'] = 0
244 self.event_data['y'] = 0
245 self.event_data['tag'] = None
246 self.scale = 1.0
247 self.margin = 10
248 self.pad_rotation = 0
249
250 self.init_messages = []
251 self.stdout = None
252 self.stderr = None
253
254 self.keep_cfg = False
255 self.ef_format = False
256 self.use_console = False
257
258 def init_padframe(self):
259 self.set_project()
260 self.vlogimport()
261 self.readplacement(precheck=True)
262 self.resolve()
Tim Edwardsd9fe0002020-07-14 21:53:24 -0400263 self.generate()
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400264
265 # Local routines for handling printing to the text console
266
267 def print(self, message, file=None, end='\n', flush=True):
268 if not file:
269 if not self.use_console:
270 file = sys.stdout
271 else:
272 file = ConsoleText.StdoutRedirector(self.text_box)
273 if self.stdout:
274 print(message, file=file, end=end)
275 if flush:
276 self.stdout.flush()
277 self.update_idletasks()
278 else:
279 self.init_messages.append(message)
280
281 def text_to_console(self):
282 # Redirect stdout and stderr to the console as the last thing to do. . .
283 # Otherwise errors in the GUI get sucked into the void.
284
285 self.stdout = sys.stdout
286 self.stderr = sys.stderr
287 if self.use_console:
288 sys.stdout = ConsoleText.StdoutRedirector(self.text_box)
289 sys.stderr = ConsoleText.StderrRedirector(self.text_box)
290
291 if len(self.init_messages) > 0:
292 for message in self.init_messages:
293 self.print(message)
294 self.init_messages = []
295
296 # Set the project name(s). This is the name of the top-level verilog.
297 # The standard protocol is that the project directory contains a file
298 # project.json that defines a name 'ip-name' that is the same as the
299 # layout name, the verilog module name, etc.
300
301 def set_project(self):
302 # Check pwd
303 pwdname = self.projectpath if self.projectpath else os.getcwd()
304
305 subdir = os.path.split(pwdname)[1]
306 if subdir == 'mag' or subdir == 'verilog':
307 projectpath = os.path.split(pwdname)[0]
308 else:
309 projectpath = pwdname
310
311 projectroot = os.path.split(projectpath)[0]
312 projectdirname = os.path.split(projectpath)[1]
313
314 # Check for efabless format. This is probably more complicated than
315 # it deserves to be. Option -ef_format is the best way to specify
316 # efabless format. However, if it is not specified, then check the
317 # technology PDK directory and the project directory for the tell-tale
318 # ".ef-config" (efabless format) directory vs. ".config" (not efabless
319 # format).
320
321 if not self.ef_format:
322 if os.path.exists(projectpath + '/.ef-config'):
323 self.ef_format = True
324 elif self.techpath:
325 if os.path.exists(self.techpath + '/.ef-config'):
326 self.ef_format = True
327 else:
328 # Do a quick consistency check. Honor the -ef_format option but warn if
329 # there is an apparent inconsistency.
330 if os.path.exists(projectpath + '/.config'):
331 self.print('Warning: -ef_format used in apparently non-efabless setup.')
332 elif self.techpath:
333 if os.path.exists(self.techpath + '/.config'):
334 self.print('Warning: -ef_format used in apparently non-efabless setup.')
335
336 # Check for project.json
337
338 jsonname = None
339 if os.path.isfile(projectpath + '/project.json'):
340 jsonname = projectpath + '/project.json'
341 elif os.path.isfile(projectroot + '/' + projectdirname + '.json'):
342 jsonname = projectroot + '/' + projectdirname + '.json'
343 if os.path.isfile(projectroot + '/project.json'):
344 # Just in case this was started from some other subdirectory
345 projectpath = projectroot
346 jsonname = projectroot + '/project.json'
347
348 if jsonname:
349 self.print('Reading project JSON file ' + jsonname)
350 with open(jsonname, 'r') as ifile:
351 topdata = json.load(ifile)
352 if 'data-sheet' in topdata:
353 dsheet = topdata['data-sheet']
354 if 'ip-name' in dsheet:
355 self.project = dsheet['ip-name']
356 self.projectpath = projectpath
357 else:
358 self.print('No project JSON file; using directory name as the project name.')
359 self.project = os.path.split(projectpath)[1]
360 self.projectpath = projectpath
361
362 self.print('Project name is ' + self.project + ' (' + self.projectpath + ')')
363
364 # Functions for drag-and-drop capability
365 def add_draggable(self, tag):
366 self.canvas.tag_bind(tag, '<ButtonPress-1>', self.on_button_press)
367 self.canvas.tag_bind(tag, '<ButtonRelease-1>', self.on_button_release)
368 self.canvas.tag_bind(tag, '<B1-Motion>', self.on_button_motion)
369 self.canvas.tag_bind(tag, '<ButtonPress-2>', self.on_button2_press)
370 self.canvas.tag_bind(tag, '<ButtonPress-3>', self.on_button3_press)
371
372 def on_button_press(self, event):
373 '''Begining drag of an object'''
374 # Find the closest item, then record its tag.
375 locx = event.x + self.canvas.canvasx(0)
376 locy = event.y + self.canvas.canvasy(0)
377 item = self.canvas.find_closest(locx, locy)[0]
378 self.event_data['tag'] = self.canvas.gettags(item)[0]
379 self.event_data['x0'] = event.x
380 self.event_data['y0'] = event.y
381 self.event_data['x'] = event.x
382 self.event_data['y'] = event.y
383
384 def on_button2_press(self, event):
385 '''Flip an object (excluding corners)'''
386 locx = event.x + self.canvas.canvasx(0)
387 locy = event.y + self.canvas.canvasy(0)
388 item = self.canvas.find_closest(locx, locy)[0]
389 tag = self.canvas.gettags(item)[0]
390
391 try:
392 corecell = next(item for item in self.coregroup if item['name'] == tag)
393 except:
394 try:
395 pad = next(item for item in self.Npads if item['name'] == tag)
396 except:
397 pad = None
398 if not pad:
399 try:
400 pad = next(item for item in self.Epads if item['name'] == tag)
401 except:
402 pad = None
403 if not pad:
404 try:
405 pad = next(item for item in self.Spads if item['name'] == tag)
406 except:
407 pad = None
408 if not pad:
409 try:
410 pad = next(item for item in self.Wpads if item['name'] == tag)
411 except:
412 pad = None
413 if not pad:
414 self.print('Error: Object cannot be flipped.')
415 else:
416 # Flip the pad (in the only way meaningful for the pad).
417 orient = pad['o']
418 if orient == 'N':
419 pad['o'] = 'FN'
420 elif orient == 'E':
421 pad['o'] = 'FW'
422 elif orient == 'S':
423 pad['o'] = 'FS'
424 elif orient == 'W':
425 pad['o'] = 'FE'
426 elif orient == 'FN':
427 pad['o'] = 'N'
428 elif orient == 'FE':
429 pad['o'] = 'W'
430 elif orient == 'FS':
431 pad['o'] = 'S'
432 elif orient == 'FW':
433 pad['o'] = 'E'
434 else:
435 # Flip the cell. Use the DEF meaning of flip, which is to
436 # add or subtract 'F' from the orientation.
437 orient = corecell['o']
438 if not 'F' in orient:
439 corecell['o'] = 'F' + orient
440 else:
441 corecell['o'] = orient[1:]
442
443 # Redraw
444 self.populate(0)
445
446 def on_button3_press(self, event):
447 '''Rotate a core object (no pads) '''
448 locx = event.x + self.canvas.canvasx(0)
449 locy = event.y + self.canvas.canvasy(0)
450 item = self.canvas.find_closest(locx, locy)[0]
451 tag = self.canvas.gettags(item)[0]
452
453 try:
454 corecell = next(item for item in self.coregroup if item['name'] == tag)
455 except:
456 self.print('Error: Object cannot be rotated.')
457 else:
458 # Modify its orientation
459 orient = corecell['o']
460 if orient == 'N':
461 corecell['o'] = 'E'
462 elif orient == 'E':
463 corecell['o'] = 'S'
464 elif orient == 'S':
465 corecell['o'] = 'W'
466 elif orient == 'W':
467 corecell['o'] = 'N'
468 elif orient == 'FN':
469 corecell['o'] = 'FW'
470 elif orient == 'FW':
471 corecell['o'] = 'FS'
472 elif orient == 'FS':
473 corecell['o'] = 'FE'
474 elif orient == 'FE':
475 corecell['o'] = 'FN'
476
477 # rewrite the core DEF file
478 self.write_core_def()
479
480 # Redraw
481 self.populate(0)
482
483 def on_button_motion(self, event):
484 '''Handle dragging of an object'''
485 # compute how much the mouse has moved
486 delta_x = event.x - self.event_data['x']
487 delta_y = event.y - self.event_data['y']
488 # move the object the appropriate amount
489 self.canvas.move(self.event_data['tag'], delta_x, delta_y)
490 # record the new position
491 self.event_data['x'] = event.x
492 self.event_data['y'] = event.y
493
494 def on_button_release(self, event):
495 '''End drag of an object'''
496
497 # Find the pad associated with the tag and update its position information
498 tag = self.event_data['tag']
499
500 # Collect pads in clockwise order. Note that E and S rows are not clockwise
501 allpads = []
502 allpads.extend(self.Npads)
503 allpads.extend(self.NEpad)
504 allpads.extend(reversed(self.Epads))
505 allpads.extend(self.SEpad)
506 allpads.extend(reversed(self.Spads))
507 allpads.extend(self.SWpad)
508 allpads.extend(self.Wpads)
509 allpads.extend(self.NWpad)
510
511 # Create a list of row references (also in clockwise order, but no reversing)
512 padrows = [self.Npads, self.NEpad, self.Epads, self.SEpad, self.Spads, self.SWpad, self.Wpads, self.NWpad]
513
514 # Record the row or corner where this pad was located before the move
515 for row in padrows:
516 try:
517 pad = next(item for item in row if item['name'] == tag)
518 except:
519 pass
520 else:
521 padrow = row
522 break
523
524 # Currently there is no procedure to move a pad out of the corner
525 # position; corners are fixed by definition.
526 if padrow == self.NEpad or padrow == self.SEpad or padrow == self.SWpad or padrow == self.NWpad:
527 # Easier to run generate() than to put the pad back. . .
Tim Edwardsd9fe0002020-07-14 21:53:24 -0400528 self.generate()
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400529 return
530
531 # Find the original center point of the pad being moved
532
533 padllx = pad['x']
534 padlly = pad['y']
535 if pad['o'] == 'N' or pad['o'] == 'S':
536 padurx = padllx + pad['width']
537 padury = padlly + pad['height']
538 else:
539 padurx = padllx + pad['height']
540 padury = padlly + pad['width']
541 padcx = (padllx + padurx) / 2
542 padcy = (padlly + padury) / 2
543
544 # Add distance from drag information (note that drag position in y
545 # is negative relative to the chip dimensions)
546 padcx += (self.event_data['x'] - self.event_data['x0']) / self.scale
547 padcy -= (self.event_data['y'] - self.event_data['y0']) / self.scale
548
549 # reset the drag information
550 self.event_data['tag'] = None
551 self.event_data['x'] = 0
552 self.event_data['y'] = 0
553 self.event_data['x0'] = 0
554 self.event_data['y0'] = 0
555
556 # Find the distance from the pad to all other pads, and get the two
557 # closest entries.
558
559 wwidth = self.urx - self.llx
560 dist0 = wwidth
561 dist1 = wwidth
562 pad0 = None
563 pad1 = None
564
565 for npad in allpads:
566 if npad == pad:
567 continue
568
569 npadllx = npad['x']
570 npadlly = npad['y']
571 if npad['o'] == 'N' or npad['o'] == 'S':
572 npadurx = npadllx + npad['width']
573 npadury = npadlly + npad['height']
574 else:
575 npadurx = npadllx + npad['height']
576 npadury = npadlly + npad['width']
577 npadcx = (npadllx + npadurx) / 2
578 npadcy = (npadlly + npadury) / 2
579
580 deltx = npadcx - padcx
581 delty = npadcy - padcy
582 pdist = math.sqrt(deltx * deltx + delty * delty)
583 if pdist < dist0:
584 dist1 = dist0
585 pad1 = pad0
586 dist0 = pdist
587 pad0 = npad
588
589 elif pdist < dist1:
590 dist1 = pdist
591 pad1 = npad
592
593 # Diagnostic
594 # self.print('Two closest pads to pad ' + pad['name'] + ' (' + pad['cell'] + '): ')
595 # self.print(pad0['name'] + ' (' + pad0['cell'] + ') dist = ' + str(dist0))
596 # self.print(pad1['name'] + ' (' + pad1['cell'] + ') dist = ' + str(dist1))
597
598 # Record the row or corner where these pads are
599 for row in padrows:
600 try:
601 testpad = next(item for item in row if item['name'] == pad0['name'])
602 except:
603 pass
604 else:
605 padrow0 = row
606 break
607
608 for row in padrows:
609 try:
610 testpad = next(item for item in row if item['name'] == pad1['name'])
611 except:
612 pass
613 else:
614 padrow1 = row
615 break
616
617 # Remove pad from its own row
618 padrow.remove(pad)
619
620 # Insert pad into new row. Watch for wraparound from the last entry to the first
621 padidx0 = allpads.index(pad0)
622 padidx1 = allpads.index(pad1)
623 if padidx0 == 0 and padidx1 > 2:
624 padidx1 = -1
625
626 if padidx1 > padidx0:
627 padafter = pad1
628 rowafter = padrow1
629 padbefore = pad0
630 rowbefore = padrow0
631 else:
632 padafter = pad0
633 rowafter = padrow0
634 padbefore = pad1
635 rowbefore = padrow1
636
637 # Do not replace corner positions (? may be necessary ?)
638 if rowafter == self.NWpad:
639 self.Wpads.append(pad)
640 elif rowafter == self.NWpad:
641 self.Npads.append(pad)
642 elif rowafter == self.SEpad:
643 self.Epads.insert(0, pad)
644 elif rowafter == self.SWpad:
645 self.Spads.insert(0, pad)
646 elif rowafter == self.Wpads or rowafter == self.Npads:
647 idx = rowafter.index(padafter)
648 rowafter.insert(idx, pad)
649 elif rowbefore == self.NEpad:
650 self.Epads.append(pad)
651 elif rowbefore == self.SEpad:
652 self.Spads.append(pad)
653 else:
654 # rows E and S are ordered counterclockwise
655 idx = rowbefore.index(padbefore)
656 rowbefore.insert(idx, pad)
657
658 # Re-run padring
Tim Edwardsd9fe0002020-07-14 21:53:24 -0400659 self.generate()
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400660
661 def on_scrollwheel(self, event):
662 if event.num == 4:
663 zoomval = 1.1;
664 elif event.num == 5:
665 zoomval = 0.9;
666 else:
667 zoomval = 1.0;
668
669 self.scale *= zoomval
670 self.canvas.scale('all', -15 * zoomval, -15 * zoomval, zoomval, zoomval)
671 self.event_data['x'] *= zoomval
672 self.event_data['y'] *= zoomval
673 self.event_data['x0'] *= zoomval
674 self.event_data['y0'] *= zoomval
675 self.frame_configure(event)
676
677 # Callback functions similar to the pad event callbacks above, but for
678 # core cells. Unlike pad cells, core cells can be rotated and flipped
679 # arbitrarily, and they do not force a recomputation of the padframe
680 # unless their position forces the padframe to expand
681
682 def add_core_draggable(self, tag):
683 self.canvas.tag_bind(tag, '<ButtonPress-1>', self.on_button_press)
684 self.canvas.tag_bind(tag, '<ButtonRelease-1>', self.core_on_button_release)
685 self.canvas.tag_bind(tag, '<B1-Motion>', self.on_button_motion)
686 self.canvas.tag_bind(tag, '<ButtonPress-2>', self.on_button2_press)
687 self.canvas.tag_bind(tag, '<ButtonPress-3>', self.on_button3_press)
688
689 def core_on_button_release(self, event):
690 '''End drag of a core cell'''
691
692 # Find the pad associated with the tag and update its position information
693 tag = self.event_data['tag']
694
695 try:
696 corecell = next(item for item in self.coregroup if item['name'] == tag)
697 except:
698 self.print('Error: cell ' + item['name'] + ' is not in coregroup!')
699 else:
700 # Modify its position values
701 corex = corecell['x']
702 corey = corecell['y']
703
704 # Add distance from drag information (note that drag position in y
705 # is negative relative to the chip dimensions)
706 deltax = (self.event_data['x'] - self.event_data['x0']) / self.scale
707 deltay = (self.event_data['y'] - self.event_data['y0']) / self.scale
708
709 corecell['x'] = corex + deltax
710 corecell['y'] = corey - deltay
711
712 # rewrite the core DEF file
713 self.write_core_def()
714
715 # reset the drag information
716 self.event_data['tag'] = None
717 self.event_data['x'] = 0
718 self.event_data['y'] = 0
719 self.event_data['x0'] = 0
720 self.event_data['y0'] = 0
721
722 # Critically needed or else frame does not resize to scrollbars!
723 def grid_configure(self, padx, pady):
724 pass
725
726 # Redraw the chip frame view in response to changes in the pad list
727 def redraw_frame(self):
728 self.canvas.coords('boundary', self.llx, self.urx, self.lly, self.ury)
729
730 # Update the canvas scrollregion to incorporate all the interior windows
731 def frame_configure(self, event):
732 if self.do_gui == False:
733 return
734 self.update_idletasks()
735 bbox = self.canvas.bbox("all")
736 try:
737 newbbox = (-15, -15, bbox[2] + 15, bbox[3] + 15)
738 except:
739 pass
740 else:
741 self.canvas.configure(scrollregion = newbbox)
742
743 # Fill the GUI entries with resident data
744 def populate(self, level):
745 if self.do_gui == False:
746 return
747
748 if level > 1:
749 self.print('Recursion error: Returning now.')
750 return
751
752 self.print('Populating floorplan view.')
753
754 # Remove all entries from the canvas
755 self.canvas.delete('all')
756
757 allpads = self.Npads + self.NEpad + self.Epads + self.SEpad + self.Spads + self.SWpad + self.Wpads + self.NWpad
758
759 notfoundlist = []
760
761 for pad in allpads:
762 if 'x' not in pad:
763 self.print('Error: Pad ' + pad['name'] + ' has no placement information.')
764 continue
765 llx = int(pad['x'])
766 lly = int(pad['y'])
767 pado = pad['o']
768 try:
769 padcell = next(item for item in self.celldefs if item['name'] == pad['cell'])
770 except:
771 # This should not happen (failsafe)
772 if pad['cell'] not in notfoundlist:
773 self.print('Warning: there is no cell named ' + pad['cell'] + ' in the libraries.')
774 notfoundlist.append(pad['cell'])
775 continue
776 padw = padcell['width']
777 padh = padcell['height']
778 if 'N' in pado or 'S' in pado:
779 urx = int(llx + padw)
780 ury = int(lly + padh)
781 else:
782 urx = int(llx + padh)
783 ury = int(lly + padw)
784
785 pad['llx'] = llx
786 pad['lly'] = lly
787 pad['urx'] = urx
788 pad['ury'] = ury
789
790 # Note that the DEF coordinate system is reversed in Y from the canvas. . .
791
792 height = self.ury - self.lly
793 for pad in allpads:
794
795 llx = pad['llx']
796 lly = height - pad['lly']
797 urx = pad['urx']
798 ury = height - pad['ury']
799
800 tag_id = pad['name']
801 if 'subclass' in pad:
802 if pad['subclass'] == 'POWER':
803 pad_color = 'orange2'
804 elif pad['subclass'] == 'INOUT':
805 pad_color = 'yellow'
806 elif pad['subclass'] == 'OUTPUT':
807 pad_color = 'powder blue'
808 elif pad['subclass'] == 'INPUT':
809 pad_color = 'goldenrod1'
810 elif pad['subclass'] == 'SPACER':
811 pad_color = 'green yellow'
812 elif pad['class'] == 'ENDCAP':
813 pad_color = 'green yellow'
814 elif pad['subclass'] == '' or pad['class'] == ';':
815 pad_color = 'khaki1'
816 else:
817 self.print('Unhandled pad class ' + pad['class'])
818 pad_color = 'gray'
819 else:
820 pad_color = 'gray'
821
822 sllx = self.scale * llx
823 slly = self.scale * lly
824 surx = self.scale * urx
825 sury = self.scale * ury
826
827 self.canvas.create_rectangle((sllx, slly), (surx, sury), fill=pad_color, tags=[tag_id])
828 cx = (sllx + surx) / 2
829 cy = (slly + sury) / 2
830
831 s = 10 if pad['width'] >= 10 else pad['width']
832 if pad['height'] < s:
833 s = pad['height']
834
835 # Create an indicator line at the bottom left corner of the cell
836 if pad['o'] == 'N':
837 allx = sllx
838 ally = slly - s
839 aurx = sllx + s
840 aury = slly
841 elif pad['o'] == 'E':
842 allx = sllx
843 ally = sury + s
844 aurx = sllx + s
845 aury = sury
846 elif pad['o'] == 'S':
847 allx = surx
848 ally = sury + s
849 aurx = surx - s
850 aury = sury
851 elif pad['o'] == 'W':
852 allx = surx
853 ally = slly - s
854 aurx = surx - s
855 aury = slly
856 elif pad['o'] == 'FN':
857 allx = surx
858 ally = slly - s
859 aurx = surx - s
860 aury = slly
861 elif pad['o'] == 'FE':
862 allx = surx
863 ally = sury + s
864 aurx = surx - s
865 aury = sury
866 elif pad['o'] == 'FS':
867 allx = sllx
868 ally = sury + s
869 aurx = sllx + s
870 aury = sury
871 elif pad['o'] == 'FW':
872 allx = sllx
873 ally = slly - s
874 aurx = sllx + s
875 aury = slly
876 self.canvas.create_line((allx, ally), (aurx, aury), tags=[tag_id])
877
878 # Rotate text on top and bottom rows if the tkinter version allows it.
879 if tkinter.TclVersion >= 8.6:
880 if pad['o'] == 'N' or pad['o'] == 'S':
881 angle = 90
882 else:
883 angle = 0
884 self.canvas.create_text((cx, cy), text=pad['name'], angle=angle, tags=[tag_id])
885 else:
886 self.canvas.create_text((cx, cy), text=pad['name'], tags=[tag_id])
887
888 # Make the pad draggable
889 self.add_draggable(tag_id)
890
891 # Now add the core cells
892 for cell in self.coregroup:
893 if 'x' not in cell:
894 self.print('Error: Core cell ' + cell['name'] + ' has no placement information.')
895 continue
896 # else:
897 # self.print('Diagnostic: Creating object for core cell ' + cell['name'])
898 llx = int(cell['x'])
899 lly = int(cell['y'])
900 cello = cell['o']
901 try:
902 corecell = next(item for item in self.coredefs if item['name'] == cell['cell'])
903 except:
904 # This should not happen (failsafe)
905 if cell['cell'] not in notfoundlist:
906 self.print('Warning: there is no cell named ' + cell['cell'] + ' in the libraries.')
907 notfoundlist.append(cell['cell'])
908 continue
909 cellw = corecell['width']
910 cellh = corecell['height']
911 if 'N' in cello or 'S' in cello:
912 urx = int(llx + cellw)
913 ury = int(lly + cellh)
914 else:
915 urx = int(llx + cellh)
916 ury = int(lly + cellw)
917 print('NOTE: cell ' + corecell['name'] + ' is rotated, w = ' + str(urx - llx) + '; h = ' + str(ury - lly))
918
919 cell['llx'] = llx
920 cell['lly'] = lly
921 cell['urx'] = urx
922 cell['ury'] = ury
923
924 # Watch for out-of-window position in core cells.
925 corellx = self.llx
926 corelly = self.lly
927 coreurx = self.urx
928 coreury = self.ury
929
930 for cell in self.coregroup:
931
932 if 'llx' not in cell:
933 # Error message for this was handled above
934 continue
935
936 llx = cell['llx']
937 lly = height - cell['lly']
938 urx = cell['urx']
939 ury = height - cell['ury']
940
941 # Check for out-of-window cell
942 if llx < corellx:
943 corellx = llx
944 if lly < corelly:
945 corelly = lly
946 if urx > coreurx:
947 coreurx = urx
948 if ury > coreury:
949 coreury = ury
950
951 tag_id = cell['name']
952 cell_color = 'gray40'
953
954 sllx = self.scale * llx
955 slly = self.scale * lly
956 surx = self.scale * urx
957 sury = self.scale * ury
958
959 self.canvas.create_rectangle((sllx, slly), (surx, sury), fill=cell_color, tags=[tag_id])
960 cx = (sllx + surx) / 2
961 cy = (slly + sury) / 2
962
963 s = 10 if cell['width'] >= 10 else cell['width']
964 if cell['height'] < s:
965 s = cell['height']
966
967 # Create an indicator line at the bottom left corner of the cell
968 if cell['o'] == 'N':
969 allx = sllx
970 ally = slly - s
971 aurx = sllx + s
972 aury = slly
973 elif cell['o'] == 'E':
974 allx = sllx
975 ally = sury + s
976 aurx = sllx + s
977 aury = sury
978 elif cell['o'] == 'S':
979 allx = surx
980 ally = sury + s
981 aurx = surx - s
982 aury = sury
983 elif cell['o'] == 'W':
984 allx = surx
985 ally = slly - s
986 aurx = surx - s
987 aury = slly
988 elif cell['o'] == 'FN':
989 allx = surx
990 ally = slly - s
991 aurx = surx - s
992 aury = slly
993 elif cell['o'] == 'FE':
994 allx = surx
995 ally = sury + s
996 aurx = surx - s
997 aury = sury
998 elif cell['o'] == 'FS':
999 allx = sllx
1000 ally = sury + s
1001 aurx = sllx + s
1002 aury = sury
1003 elif cell['o'] == 'FW':
1004 allx = sllx
1005 ally = slly - s
1006 aurx = sllx + s
1007 aury = slly
1008 self.canvas.create_line((allx, ally), (aurx, aury), tags=[tag_id])
1009
1010 # self.print('Created entry for cell ' + cell['name'] + ' at {0:g}, {1:g}'.format(cx, cy))
1011
1012 # Rotate text on top and bottom rows if the tkinter version allows it.
1013 if tkinter.TclVersion >= 8.6:
1014 if 'N' in cell['o'] or 'S' in cell['o']:
1015 angle = 90
1016 else:
1017 angle = 0
1018 self.canvas.create_text((cx, cy), text=cell['name'], angle=angle, tags=[tag_id])
1019 else:
1020 self.canvas.create_text((cx, cy), text=cell['name'], tags=[tag_id])
1021
1022 # Make the core cell draggable
1023 self.add_core_draggable(tag_id)
1024
1025 # Is there a boundary size defined?
1026 if self.urx > self.llx and self.ury > self.lly:
1027 self.create_boundary()
1028
1029 # Did the core extend into negative X or Y? If so, adjust all canvas
1030 # coordinates to fit in the window, or else objects cannot be reached
1031 # even by zooming out (since zooming is pinned on the top corner).
1032
1033 offsetx = 0
1034 offsety = 0
1035
1036 # NOTE: Probably want to check if the core exceeds the inner
1037 # dimension of the pad ring, not the outer (to check and to do).
1038
1039 if corellx < self.llx:
1040 offsetx = self.llx - corellx
1041 if corelly < self.lly:
1042 offsety = self.lly - corelly
1043 if offsetx > 0 or offsety > 0:
1044 self.canvas.move("all", offsetx, offsety)
1045 # An offset implies that the chip is core limited, and the
1046 # padframe requires additional space. This can be accomplished
1047 # simply by running "Generate". NOTE: Since generate() calls
1048 # populate(), be VERY SURE that this does not infinitely recurse!
1049 self.generate(level)
1050
1051 # Generate a DEF file of the core area
1052
1053 def write_core_def(self):
1054 self.print('Writing core placementment information in DEF file "core.def".')
1055
1056 mag_path = self.projectpath + '/mag'
1057
1058 # The core cells must always clear the I/O pads on the left and
1059 # bottom (with the ad-hoc margin of self.margin). If core cells have
1060 # been moved to the left or down past the padframe edge, then the
1061 # entire core needs to be repositioned.
1062
1063 # To be done: draw a boundary around the core, let the edges of that
1064 # boundary be draggable, and let the difference between the boundary
1065 # and the core area define the margin.
1066
1067 if self.SWpad != []:
1068 corellx = self.SWpad[0]['x'] + self.SWpad[0]['width'] + self.margin
1069 corelly = self.SWpad[0]['y'] + self.SWpad[0]['height'] + self.margin
1070 else:
1071 corellx = self.Wpads[0]['x'] + self.Wpads[0]['height'] + self.margin
1072 corelly = self.Spads[0]['x'] + self.Spads[0]['height'] + self.margin
1073
1074 offsetx = 0
1075 offsety = 0
1076 for corecell in self.coregroup:
1077 if corecell['x'] < corellx:
1078 if corellx - corecell['x'] > offsetx:
1079 offsetx = corellx - corecell['x']
1080 if corecell['y'] < corelly:
1081 if corelly - corecell['y'] > offsety:
1082 offsety = corelly - corecell['y']
1083 if offsetx > 0 or offsety > 0:
1084 for corecell in self.coregroup:
1085 corecell['x'] += offsetx
1086 corecell['y'] += offsety
1087
1088 # Now write the core DEF file
1089
1090 with open(mag_path + '/core.def', 'w') as ofile:
1091 print('DESIGN CORE ;', file=ofile)
1092 print('UNITS DISTANCE MICRONS 1000 ;', file=ofile)
1093 print('COMPONENTS {0:d} ;'.format(len(self.coregroup)), file=ofile)
1094 for corecell in self.coregroup:
1095 print(' - ' + corecell['name'] + ' ' + corecell['cell'], file=ofile)
1096 print(' + PLACED ( {0:d} {1:d} ) {2:s} ;'.format(int(corecell['x'] * 1000), int(corecell['y'] * 1000), corecell['o']), file=ofile)
1097 print ('END COMPONENTS', file=ofile)
1098 print ('END DESIGN', file=ofile)
1099
1100 # Create the chip boundary area
1101
1102 def create_boundary(self):
1103 scale = self.scale
1104 llx = (self.llx - 10) * scale
1105 lly = (self.lly - 10) * scale
1106 urx = (self.urx + 10) * scale
1107 ury = (self.ury + 10) * scale
1108
1109 pad_color = 'plum1'
1110 tag_id = 'boundary'
1111 self.canvas.create_rectangle((llx, lly), (urx, ury), outline=pad_color, width=2, tags=[tag_id])
1112 # Add text to the middle representing the chip and core areas
1113 cx = ((self.llx + self.urx) / 2) * scale
1114 cy = ((self.lly + self.ury) / 2) * scale
1115 width = self.urx - self.llx
1116 height = self.ury - self.lly
1117 areatext = 'Chip dimensions (um): {0:g} x {1:g}'.format(width, height)
1118 tag_id = 'chiparea'
1119 self.canvas.create_text((cx, cy), text=areatext, tags=[tag_id])
1120
1121 # Rotate orientation according to self.pad_rotation.
1122
1123 def rotate_orientation(self, orient_in):
1124 orient_v = ['N', 'E', 'S', 'W', 'N', 'E', 'S', 'W']
1125 idxadd = int(self.pad_rotation / 90)
1126 idx = orient_v.index(orient_in)
1127 return orient_v[idx + idxadd]
1128
1129 # Read a list of cell macros (name, size, class) from a LEF library
1130
1131 def read_lef_macros(self, libpath, libname = None, libtype = 'iolib'):
1132 if libtype == 'iolib':
1133 libtext = 'I/O '
1134 elif libtype == 'celllib':
1135 libtext = 'core '
1136 else:
1137 libtext = ''
1138
1139 macros = []
1140
1141 if libname:
1142 if os.path.splitext(libname)[1] == '':
1143 libname += '.lef'
1144 leffiles = glob.glob(libpath + '/' + libname)
1145 else:
1146 leffiles = glob.glob(libpath + '/*.lef')
1147 if leffiles == []:
1148 if libname:
1149 self.print('WARNING: No file ' + libpath + '/' + libname + '.lef')
1150 else:
1151 self.print('WARNING: No files ' + libpath + '/*.lef')
1152 for leffile in leffiles:
1153 libpath = os.path.split(leffile)[0]
1154 libname = os.path.split(libpath)[1]
1155 self.print('Reading LEF ' + libtext + 'library ' + leffile)
1156 with open(leffile, 'r') as ifile:
1157 ilines = ifile.read().splitlines()
1158 in_macro = False
1159 for iline in ilines:
1160 iparse = iline.split()
1161 if iparse == []:
1162 continue
1163 elif iparse[0] == 'MACRO':
1164 in_macro = True
1165 newmacro = {}
1166 newmacro['name'] = iparse[1]
1167 newmacro[libtype] = leffile
1168 macros.append(newmacro)
1169 elif in_macro:
1170 if iparse[0] == 'END':
1171 if len(iparse) > 1 and iparse[1] == newmacro['name']:
1172 in_macro = False
1173 elif iparse[0] == 'CLASS':
1174 newmacro['class'] = iparse[1]
1175 if len(iparse) > 2:
1176 newmacro['subclass'] = iparse[2]
1177
1178 # Use the 'ENDCAP' class to identify pad rotations
1179 # other than BOTTOMLEFT. This is somewhat ad-hoc
1180 # depending on the foundry; may not be generally
1181 # applicable.
1182
1183 if newmacro['class'] == 'ENDCAP':
1184 if newmacro['subclass'] == 'TOPLEFT':
1185 self.pad_rotation = 90
1186 elif newmacro['subclass'] == 'TOPRIGHT':
1187 self.pad_rotation = 180
1188 elif newmacro['subclass'] == 'BOTTOMRIGHT':
1189 self.pad_rotation = 270
1190 else:
1191 newmacro['subclass'] = None
1192 elif iparse[0] == 'SIZE':
1193 newmacro['width'] = float(iparse[1])
1194 newmacro['height'] = float(iparse[3])
1195 elif iparse[0] == 'ORIGIN':
1196 newmacro['x'] = float(iparse[1])
1197 newmacro['y'] = float(iparse[2])
1198 return macros
1199
1200 # Read a list of cell names from a verilog file
1201 # If filename is relative, then check in the same directory as the verilog
1202 # top-level netlist (vlogpath) and in the subdirectory 'source/' of the top-
1203 # level directory. Also check in the ~/design/ip/ directory. These are
1204 # common include paths for the simulation.
1205
1206 def read_verilog_lib(self, incpath, vlogpath):
1207 iocells = []
1208 if not os.path.isfile(incpath) and incpath[0] != '/':
1209 locincpath = vlogpath + '/' + incpath
1210 if not os.path.isfile(locincpath):
1211 locincpath = vlogpath + '/source/' + incpath
1212 if not os.path.isfile(locincpath):
1213 projectpath = os.path.split(vlogpath)[0]
1214 designpath = os.path.split(projectpath)[0]
1215 locincpath = designpath + '/ip/' + incpath
1216 else:
1217 locincpath = incpath
1218
1219 if not os.path.isfile(locincpath):
1220 self.print('File ' + incpath + ' not found (at ' + locincpath + ')!')
1221 else:
1222 self.print('Reading verilog library ' + locincpath)
1223 with open(locincpath, 'r') as ifile:
1224 ilines = ifile.read().splitlines()
1225 for iline in ilines:
1226 iparse = re.split('[\t ()]', iline)
1227 while '' in iparse:
1228 iparse.remove('')
1229 if iparse == []:
1230 continue
1231 elif iparse[0] == 'module':
1232 iocells.append(iparse[1])
1233 return iocells
1234
1235 # Generate a LEF abstract view from a magic layout. If "outpath" is not
1236 # "None", then write output to outputpath (this is required if the input
1237 # file is in a read-only directory).
1238
1239 def write_lef_file(self, magfile, outpath=None):
1240 mag_path = os.path.split(magfile)[0]
1241 magfullname = os.path.split(magfile)[1]
1242 module = os.path.splitext(magfullname)[0]
1243
1244 if outpath:
1245 write_path = outpath
1246 else:
1247 write_path = mag_path
1248
1249 self.print('Generating LEF view from layout for module ' + module)
1250
1251 with open(write_path + '/pfg_write_lef.tcl', 'w') as ofile:
1252 print('drc off', file=ofile)
1253 print('box 0 0 0 0', file=ofile)
1254 # NOTE: Using "-force" option in case an IP with a different but
1255 # compatible tech is used (e.g., EFHX035A IP inside EFXH035C). This
1256 # is not checked for legality!
1257 if outpath:
1258 print('load ' + magfile + ' -force', file=ofile)
1259 else:
1260 print('load ' + module + ' -force', file=ofile)
1261 print('lef write -hide', file=ofile)
1262 print('quit', file=ofile)
1263
1264 magicexec = self.magic_path if self.magic_path else 'magic'
1265 mproc = subprocess.Popen([magicexec, '-dnull', '-noconsole',
1266 'pfg_write_lef.tcl'],
1267 stdin = subprocess.PIPE, stdout = subprocess.PIPE,
1268 stderr = subprocess.PIPE, cwd = write_path, universal_newlines = True)
1269
1270 self.watch(mproc)
1271 os.remove(write_path + '/pfg_write_lef.tcl')
1272
1273 # Watch a running process, polling for output and updating the GUI message
1274 # window as output arrives. Return only when the process has exited.
1275 # Note that this process cannot handle stdin(), so any input to the process
1276 # must be passed from a file.
1277
1278 def watch(self, process):
1279 if process == None:
1280 return
1281
1282 while True:
1283 status = process.poll()
1284 if status != None:
1285 try:
1286 outputpair = process.communicate(timeout=1)
1287 except ValueError:
1288 self.print("Process forced stop, status " + str(status))
1289 else:
1290 for line in outputpair[0].splitlines():
1291 self.print(line)
1292 for line in outputpair[1].splitlines():
1293 self.print(line, file=sys.stderr)
1294 break
1295 else:
1296 sresult = select.select([process.stdout, process.stderr], [], [], 0)[0]
1297 if process.stdout in sresult:
1298 outputline = process.stdout.readline().strip()
1299 self.print(outputline)
1300 elif process.stderr in sresult:
1301 outputline = process.stderr.readline().strip()
1302 self.print(outputline, file=sys.stderr)
1303 else:
1304 self.update_idletasks()
1305
1306 # Reimport the pad list by reading the top-level verilog netlist. Determine
1307 # what pads are listed in the file, and check against the existing pad list.
1308
1309 # The verilog/ directory should have a .v file containing a module of the
1310 # same name as self.project (ip-name). The .v filename should have the
1311 # same name as well (but not necessarily). To do: Handle import of
1312 # projects having a top-level schematic instead of a verilog netlist.
1313
1314 def vlogimport(self):
1315
1316 if self.ef_format:
1317 config_dir = '/.ef-config'
1318 else:
1319 config_dir = '/.config'
1320
1321 # First find the process PDK name for this project. Read the nodeinfo.json
1322 # file and find the list of I/O cell libraries.
1323
1324 if self.techpath:
1325 pdkpath = self.techpath
1326 elif os.path.islink(self.projectpath + config_dir + '/techdir'):
1327 pdkpath = os.path.realpath(self.projectpath + config_dir + '/techdir')
1328 else:
1329 self.print('Error: Cannot determine path to PDK. Try using option -tech-path=')
1330 return
1331
1332 self.print('Importing verilog sources.')
1333
1334 nodeinfopath = pdkpath + config_dir + '/nodeinfo.json'
1335 ioleflist = []
1336 if os.path.exists(nodeinfopath):
1337 self.print('Reading known I/O cell libraries from ' + nodeinfopath)
1338 with open(nodeinfopath, 'r') as ifile:
1339 itop = json.load(ifile)
1340 if 'iocells' in itop:
1341 ioleflist = []
1342 for iolib in itop['iocells']:
1343 if '/' in iolib:
1344 # Entries <lib>/<cell> refer to specific files
1345 if self.ef_format:
1346 iolibpath = pdkpath + '/libs.ref/lef/' + iolib
1347 else:
1348 iolibpath = pdkpath + '/libs.ref/' + iolib
1349 if os.path.splitext(iolib)[1] == '':
1350 if not os.path.exists(iolibpath):
1351 iolibpath = iolibpath + '.lib'
1352 if not os.path.exists(iolibpath):
1353 self.print('Warning: nodeinfo.json bad I/O library path ' + iolibpath)
1354 ioleflist.append(iolibpath)
1355 else:
1356 # All other entries refer to everything in the directory.
1357 if self.ef_format:
1358 iolibpath = pdkpath + '/libs.ref/lef/' + iolib
1359 else:
1360 iolibpath = pdkpath + '/libs.ref/' + iolib + '/lef/'
1361 iolibfiles = glob.glob(iolibpath + '/*.lef')
1362 if len(iolibfiles) == 0:
1363 self.print('Warning: nodeinfo.json bad I/O library path ' + iolibpath)
1364 ioleflist.extend(iolibfiles)
1365 else:
1366 # Diagnostic
1367 self.print('Cannot read PDK information file ' + nodeinfopath)
1368
1369 # Fallback behavior: List everything in libs.ref/lef/ beginning with "IO"
1370 if len(ioleflist) == 0:
1371 if self.ef_format:
1372 ioleflist = glob.glob(pdkpath + '/libs.ref/lef/IO*/*.lef')
1373 else:
1374 ioleflist = glob.glob(pdkpath + '/libs.ref/IO*/lef/*.lef')
1375
1376 if len(ioleflist) == 0:
1377 self.print('Cannot find any I/O cell libraries for this technology')
1378 return
1379
1380 # Read the LEF libraries to get a list of all available cells. Keep
1381 # this list of cells in "celldefs".
1382
1383 celldefs = []
1384 ioliblist = []
1385 ioleflibs = []
1386 for iolib in ioleflist:
1387 iolibpath = os.path.split(iolib)[0]
1388 iolibfile = os.path.split(iolib)[1]
1389 ioliblist.append(os.path.split(iolibpath)[1])
1390 celldefs.extend(self.read_lef_macros(iolibpath, iolibfile, 'iolib'))
1391
1392 verilogcells = []
1393 newpadlist = []
1394 coredefs = []
1395 corecells = []
1396 corecelllist = []
1397 lefprocessed = []
1398
1399 busrex = re.compile('.*\[[ \t]*([0-9]+)[ \t]*:[ \t]*([0-9]+)[ \t]*\]')
1400
1401 vlogpath = self.projectpath + '/verilog'
1402 vlogfile = vlogpath + '/' + self.project + '.v'
1403
1404 # Verilog netlists are too difficult to parse from a simple script.
1405 # Use qflow tools to convert to SPICE, if they are available. Parse
1406 # the verilog only for "include" statements to find the origin of
1407 # the various IP blocks, and then parse the SPICE file to get the
1408 # full list of instances.
1409 #
1410 # (to be done)
1411
1412 if os.path.isfile(vlogfile):
1413 with open(vlogfile, 'r') as ifile:
1414 vloglines = ifile.read().splitlines()
1415 for vlogline in vloglines:
1416 vlogparse = re.split('[\t ()]', vlogline)
1417 while '' in vlogparse:
1418 vlogparse.remove('')
1419 if vlogparse == []:
1420 continue
1421 elif vlogparse[0] == '//':
1422 continue
1423 elif vlogparse[0] == '`include':
1424 incpath = vlogparse[1].strip('"')
1425 libpath = os.path.split(incpath)[0]
1426 libname = os.path.split(libpath)[1]
1427 libfile = os.path.split(incpath)[1]
1428
1429 # Read the verilog library for module names to match
1430 # against macro names in celldefs.
1431 modulelist = self.read_verilog_lib(incpath, vlogpath)
1432 matching = list(item for item in celldefs if item['name'] in modulelist)
1433 for imatch in matching:
1434 verilogcells.append(imatch['name'])
1435 leffile = imatch['iolib']
1436 if leffile not in ioleflibs:
1437 ioleflibs.append(leffile)
1438
1439 # Read a corresponding LEF file entry for non-I/O macros, if one
1440 # can be found (this handles files in the PDK).
1441 if len(matching) == 0:
1442 if libname != '':
1443 # (NOTE: Assumes full path starting with '/')
1444 lefpath = libpath.replace('verilog', 'lef')
1445 lefname = libfile.replace('.v', '.lef')
1446 if not os.path.exists(lefpath + '/' + lefname):
1447 leffiles = glob.glob(lefpath + '/*.lef')
1448 else:
1449 leffiles = [lefpath + '/' + lefname]
1450
1451 for leffile in leffiles:
1452 if leffile in ioleflibs:
1453 continue
1454 elif leffile in lefprocessed:
1455 continue
1456 else:
1457 lefprocessed.append(leffile)
1458
1459 lefname = os.path.split(leffile)[1]
1460
1461 newcoredefs = self.read_lef_macros(lefpath, lefname, 'celllib')
1462 coredefs.extend(newcoredefs)
1463 corecells.extend(list(item['name'] for item in newcoredefs))
1464
1465 if leffiles == []:
1466 maglefname = libfile.replace('.v', '.mag')
1467
1468 # Handle PDK files with a maglef/ view but no LEF file.
1469 maglefpath = libpath.replace('verilog', 'maglef')
1470 if not os.path.exists(maglefpath + '/' + maglefname):
1471 magleffiles = glob.glob(maglefpath + '/*.mag')
1472 else:
1473 magleffiles = [maglefpath + '/' + maglefname]
1474
1475 if magleffiles == []:
1476 # Handle user ip/ files with a maglef/ view but
1477 # no LEF file.
1478 maglefpath = libpath.replace('verilog', 'maglef')
1479 designpath = os.path.split(self.projectpath)[0]
1480 maglefpath = designpath + '/ip/' + maglefpath
1481
1482 if not os.path.exists(maglefpath + '/' + maglefname):
1483 magleffiles = glob.glob(maglefpath + '/*.mag')
1484 else:
1485 magleffiles = [maglefpath + '/' + maglefname]
1486
1487 for magleffile in magleffiles:
1488 # Generate LEF file. Since PDK and ip/ entries
1489 # are not writeable, write into the project mag/
1490 # directory.
1491 magpath = self.projectpath + '/mag'
1492 magname = os.path.split(magleffile)[1]
1493 magroot = os.path.splitext(magname)[0]
1494 leffile = magpath + '/' + magroot + '.lef'
1495 if not os.path.isfile(leffile):
1496 self.write_lef_file(magleffile, magpath)
1497
1498 if leffile in ioleflibs:
1499 continue
1500 elif leffile in lefprocessed:
1501 continue
1502 else:
1503 lefprocessed.append(leffile)
1504
1505 lefname = os.path.split(leffile)[1]
1506
1507 newcoredefs = self.read_lef_macros(magpath, lefname, 'celllib')
1508 coredefs.extend(newcoredefs)
1509 corecells.extend(list(item['name'] for item in newcoredefs))
1510 # LEF files generated on-the-fly are not needed
1511 # after they have been parsed.
1512 # os.remove(leffile)
1513
1514 # Check if all modules in modulelist are represented by
1515 # corresponding LEF macros. If not, then go looking for a LEF
1516 # file in the mag/ or maglef/ directory. Then, go looking for
1517 # a .mag file in the mag/ or maglef/ directory, and build a
1518 # LEF macro from it.
1519
1520 matching = list(item['name'] for item in coredefs if item['name'] in modulelist)
1521 for module in modulelist:
1522 if module not in matching:
1523 lefpath = self.projectpath + '/lef'
1524 magpath = self.projectpath + '/mag'
1525 maglefpath = self.projectpath + '/mag'
1526 lefname = libfile.replace('.v', '.lef')
1527
1528 # If the verilog file root name is not the same as
1529 # the module name, then make a quick check for a
1530 # LEF file with the same root name as the verilog.
1531 # That indicates that the module does not exist in
1532 # the LEF file, probably because it is a primary
1533 # module that does not correspond to any layout.
1534
1535 leffile = lefpath + '/' + lefname
1536 if os.path.exists(leffile):
1537 self.print('Diagnostic: module ' + module + ' is not in ' + leffile + ' (probably a primary module)')
1538 continue
1539
1540 leffile = magpath + '/' + lefname
1541 istemp = False
1542 if not os.path.exists(leffile):
1543 magname = libfile.replace('.v', '.mag')
1544 magfile = magpath + '/' + magname
1545 if os.path.exists(magfile):
1546 self.print('Diagnostic: Found a .mag file for ' + module + ' in ' + magfile)
1547 self.write_lef_file(magfile)
1548 istemp = True
1549 else:
1550 magleffile = maglefpath + '/' + lefname
1551 if not os.path.exists(magleffile):
1552 self.print('Diagnostic: (module ' + module + ') has no LEF file ' + leffile + ' or ' + magleffile)
1553 magleffile = maglefpath + '/' + magname
1554 if os.path.exists(magleffile):
1555 self.print('Diagnostic: Found a .mag file for ' + module + ' in ' + magleffile)
1556 if os.access(maglefpath, os.W_OK):
1557 self.write_lef_file(magleffile)
1558 leffile = magleffile
1559 istemp = True
1560 else:
1561 self.write_lef_file(magleffile, magpath)
1562 else:
1563 self.print('Did not find a file ' + magfile)
1564 # self.print('Warning: module ' + module + ' has no LEF or .mag views')
1565 pass
1566 else:
1567 self.print('Diagnostic: Found a LEF file for ' + module + ' in ' + magleffile)
1568 leffile = magleffile
1569 else:
1570 self.print('Diagnostic: Found a LEF file for ' + module + ' in ' + leffile)
1571
1572 if os.path.exists(leffile):
1573 if leffile in lefprocessed:
1574 continue
1575 else:
1576 lefprocessed.append(leffile)
1577
1578 newcoredefs = self.read_lef_macros(magpath, lefname, 'celllib')
1579 # The LEF file generated on-the-fly is not needed
1580 # any more after parsing the macro(s).
1581 # if istemp:
1582 # os.remove(leffile)
1583 coredefs.extend(newcoredefs)
1584 corecells.extend(list(item['name'] for item in newcoredefs))
1585 else:
1586 # self.print('Failed to find a LEF view for module ' + module)
1587 pass
1588
1589 elif vlogparse[0] in verilogcells:
1590 # Check for array of pads
1591 bushigh = buslow = -1
1592 if len(vlogparse) >= 3:
1593 bmatch = busrex.match(vlogline)
1594 if bmatch:
1595 bushigh = int(bmatch.group(1))
1596 buslow = int(bmatch.group(2))
1597
1598 for i in range(buslow, bushigh + 1):
1599 newpad = {}
1600 if i >= 0:
1601 newpad['name'] = vlogparse[1] + '[' + str(i) + ']'
1602 else:
1603 newpad['name'] = vlogparse[1]
1604 newpad['cell'] = vlogparse[0]
1605 padcell = next(item for item in celldefs if item['name'] == vlogparse[0])
1606 newpad['iolib'] = padcell['iolib']
1607 newpad['class'] = padcell['class']
1608 newpad['subclass'] = padcell['subclass']
1609 newpad['width'] = padcell['width']
1610 newpad['height'] = padcell['height']
1611 newpadlist.append(newpad)
1612
1613 elif vlogparse[0] in corecells:
1614 # Check for array of cells
1615 bushigh = buslow = -1
1616 if len(vlogparse) >= 3:
1617 bmatch = busrex.match(vlogline)
1618 if bmatch:
1619 bushigh = int(bmatch.group(1))
1620 buslow = int(bmatch.group(2))
1621
1622 for i in range(buslow, bushigh + 1):
1623 newcorecell = {}
1624 if i >= 0:
1625 newcorecell['name'] = vlogparse[1] + '[' + str(i) + ']'
1626 else:
1627 newcorecell['name'] = vlogparse[1]
1628 newcorecell['cell'] = vlogparse[0]
1629 corecell = next(item for item in coredefs if item['name'] == vlogparse[0])
1630 newcorecell['celllib'] = corecell['celllib']
1631 newcorecell['class'] = corecell['class']
1632 newcorecell['subclass'] = corecell['subclass']
1633 newcorecell['width'] = corecell['width']
1634 newcorecell['height'] = corecell['height']
1635 corecelllist.append(newcorecell)
1636
1637 self.print('')
1638 self.print('Source file information:')
1639 self.print('Source filename: ' + vlogfile)
1640 self.print('Number of I/O libraries is ' + str(len(ioleflibs)))
1641 self.print('Number of library cells in I/O libraries used: ' + str(len(verilogcells)))
1642 self.print('Number of core celldefs is ' + str(len(coredefs)))
1643 self.print('')
1644 self.print('Number of I/O cells in design: ' + str(len(newpadlist)))
1645 self.print('Number of core cells in design: ' + str(len(corecelllist)))
1646 self.print('')
1647
1648 # Save the results
1649 self.celldefs = celldefs
1650 self.coredefs = coredefs
1651 self.vlogpads = newpadlist
1652 self.corecells = corecelllist
1653 self.ioleflibs = ioleflibs
1654
1655 # Check self.vlogpads, which was created during import (above) against
1656 # self.(N,S,W,E)pads, which was read from the DEF file (if there was one)
1657 # Also check self.corecells, which was created during import against
1658 # self.coregroup, which was read from the DEF file.
1659
1660 def resolve(self):
1661 self.print('Resolve differences in verilog and LEF views.')
1662
1663 samepads = []
1664 addedpads = []
1665 removedpads = []
1666
1667 # (1) Create list of entries that are in both self.vlogpads and self.()pads
1668 # (2) Create list of entries that are in self.vlogpads but not in self.()pads
1669
1670 allpads = self.Npads + self.NEpad + self.Epads + self.SEpad + self.Spads + self.SWpad + self.Wpads + self.NWpad
1671
1672 for pad in self.vlogpads:
1673 newpadname = pad['name']
1674 try:
1675 lpad = next(item for item in allpads if item['name'] == newpadname)
1676 except:
1677 addedpads.append(pad)
1678 else:
1679 samepads.append(lpad)
1680
1681 # (3) Create list of entries that are in allpads but not in self.vlogpads
1682 for pad in allpads:
1683 newpadname = pad['name']
1684 try:
1685 lpad = next(item for item in self.vlogpads if item['name'] == newpadname)
1686 except:
1687 removedpads.append(pad)
1688
1689 # Print results
1690 if len(addedpads) > 0:
1691 self.print('Added pads:')
1692 for pad in addedpads:
1693 self.print(pad['name'] + ' (' + pad['cell'] + ')')
1694
1695 if len(removedpads) > 0:
1696 plist = []
1697 nspacers = 0
1698 for pad in removedpads:
1699 if 'subclass' in pad:
1700 if pad['subclass'] != 'SPACER':
1701 plist.append(pad)
1702 else:
1703 nspacers += 1
1704
1705 if nspacers > 0:
1706 self.print(str(nspacers) + ' spacer cells ignored.')
1707 if len(plist) > 0:
1708 self.print('Removed pads:')
1709 for pad in removedpads:
1710 self.print(pad['name'] + ' (' + pad['cell'] + ')')
1711
1712 if len(addedpads) + len(removedpads) == 0:
1713 self.print('Pad list has not changed.')
1714
1715 # Remove all cells from the "removed" list, with comment
1716
1717 allpads = [self.Npads, self.NEpad, self.Epads, self.SEpad, self.Spads, self.SWpad, self.Wpads, self.NWpad]
1718
1719 for pad in removedpads:
1720 rname = pad['name']
1721 for row in allpads:
1722 try:
1723 rpad = next(item for item in row if item['name'] == rname)
1724 except:
1725 rpad = None
1726 else:
1727 row.remove(rpad)
1728
1729 # Now the verilog file has no placement information, so the old padlist
1730 # entries (if they exist) are preferred. Add to these the new padlist
1731 # entries
1732
1733 # First pass for unassigned pads: Use of "CLASS ENDCAP" is preferred
1734 # for identifying corner pads. Otherwise, if 'CORNER' or 'corner' is
1735 # in the pad name, then make sure there is one per row in the first
1736 # position. This is not foolproof and depends on the cell library
1737 # using the text 'corner' in the name of the corner cell. However,
1738 # if the ad hoc methods fail, the user can still manually move the
1739 # corner cells to the right place (to be done: Identify if library
1740 # uses ENDCAP designation for corner cells up front; don't go
1741 # looking for 'corner' text if the cells are easily identifiable by
1742 # LEF class).
1743
1744 for pad in addedpads[:]:
1745 iscorner = False
1746 if 'class' in pad and pad['class'] == 'ENDCAP':
1747 iscorner = True
1748 elif 'CORNER' in pad['cell'].upper():
1749 iscorner = True
1750
1751 if iscorner:
1752 if self.NWpad == []:
1753 self.NWpad.append(pad)
1754 pad['o'] = 'E'
1755 addedpads.remove(pad)
1756 elif self.NEpad == []:
1757 self.NEpad.append(pad)
1758 pad['o'] = 'S'
1759 addedpads.remove(pad)
1760 elif self.SEpad == []:
1761 self.SEpad.append(pad)
1762 pad['o'] = 'W'
1763 addedpads.remove(pad)
1764 elif self.SWpad == []:
1765 self.SWpad.append(pad)
1766 pad['o'] = 'N'
1767 addedpads.remove(pad)
1768
1769 numN = len(self.Npads)
1770 numS = len(self.Spads)
1771 numE = len(self.Epads)
1772 numW = len(self.Wpads)
1773
1774 minnum = min(numN, numS, numE, numW)
1775 minnum = max(minnum, int(len(addedpads) / 4))
1776
1777 # Add pads in clockwise order. Note that S and E pads are defined counterclockwise
1778 for pad in addedpads:
1779 if numN < minnum:
1780 self.Npads.append(pad)
1781 numN += 1
1782 pad['o'] = 'S'
1783 self.print("Adding pad " + pad['name'] + " to Npads")
1784 elif numE < minnum:
1785 self.Epads.insert(0, pad)
1786 numE += 1
1787 pad['o'] = 'W'
1788 self.print("Adding pad " + pad['name'] + " to Epads")
1789 elif numS < minnum:
1790 self.Spads.insert(0, pad)
1791 numS += 1
1792 pad['o'] = 'N'
1793 self.print("Adding pad " + pad['name'] + " to Spads")
1794 # elif numW < minnum:
1795 else:
1796 self.Wpads.append(pad)
1797 numW += 1
1798 pad['o'] = 'E'
1799 self.print("Adding pad " + pad['name'] + " to Wpads")
1800
1801 minnum = min(numN, numS, numE, numW)
1802 minnum = max(minnum, int(len(addedpads) / 4))
1803
1804 # Make sure all pads have included information from the cell definition
1805
1806 allpads = self.Npads + self.NEpad + self.Epads + self.SEpad + self.Spads + self.SWpad + self.Wpads + self.NWpad
1807
1808 for pad in allpads:
1809 if 'width' not in pad:
1810 try:
1811 celldef = next(item for item in celldefs if item['name'] == pad['cell'])
1812 except:
1813 self.print('Cell ' + pad['cell'] + ' not found!')
1814 else:
1815 pad['width'] = celldef['width']
1816 pad['height'] = celldef['height']
1817 pad['class'] = celldef['class']
1818 pad['subclass'] = celldef['subclass']
1819
1820 # Now treat the core cells in the same way (resolve list parsed from verilog
1821 # against the list parsed from DEF)
1822
1823 # self.print('Diagnostic: ')
1824 # self.print('self.corecells = ' + str(self.corecells))
1825 # self.print('self.coregroup = ' + str(self.coregroup))
1826
1827 samecore = []
1828 addedcore = []
1829 removedcore = []
1830
1831 # (1) Create list of entries that are in both self.corecells and self.coregroup
1832 # (2) Create list of entries that are in self.corecells but not in self.coregroup
1833
1834 for cell in self.corecells:
1835 newcellname = cell['name']
1836 try:
1837 lcore = next(item for item in self.coregroup if item['name'] == newcellname)
1838 except:
1839 addedcore.append(cell)
1840 else:
1841 samecore.append(lcore)
1842
1843 # (3) Create list of entries that are in self.coregroup but not in self.corecells
1844 for cell in self.coregroup:
1845 newcellname = cell['name']
1846 try:
1847 lcore = next(item for item in self.corecells if item['name'] == newcellname)
1848 except:
1849 removedcore.append(cell)
1850
1851 # Print results
1852 if len(addedcore) > 0:
1853 self.print('Added core cells:')
1854 for cell in addedcore:
1855 self.print(cell['name'] + ' (' + cell['cell'] + ')')
1856
1857 if len(removedcore) > 0:
1858 clist = []
1859 for cell in removedcore:
1860 clist.append(cell)
1861
1862 if len(clist) > 0:
1863 self.print('Removed core cells:')
1864 for cell in removedcore:
1865 self.print(cell['name'] + ' (' + cell['cell'] + ')')
1866
1867 if len(addedcore) + len(removedcore) == 0:
1868 self.print('Core cell list has not changed.')
1869
1870 # Remove all cells from the "removed" list
1871
1872 coregroup = self.coregroup
1873 for cell in removedcore:
1874 rname = cell['name']
1875 try:
1876 rcell = next(item for item in coregroup if item['name'] == rname)
1877 except:
1878 rcell = None
1879 else:
1880 coregroup.remove(rcell)
1881
1882 # Add all cells from the "added" list to coregroup
1883
1884 for cell in addedcore:
1885 rname = cell['name']
1886 try:
1887 rcell = next(item for item in coregroup if item['name'] == rname)
1888 except:
1889 coregroup.append(cell)
1890 if not 'o' in cell:
1891 cell['o'] = 'N'
1892 if not 'x' in cell:
1893 if len(self.Wpads) > 0:
1894 pad = self.Wpads[0]
1895 padx = pad['x'] if 'x' in pad else 0
1896 cell['x'] = padx + pad['height'] + self.margin
1897 else:
1898 cell['x'] = self.margin
1899 if not 'y' in cell:
1900 if len(self.Spads) > 0:
1901 pad = self.Spads[0]
1902 pady = pad['y'] if 'y' in pad else 0
1903 cell['y'] = pady + pad['height'] + self.margin
1904 else:
1905 cell['y'] = self.margin
1906 else:
1907 rcell = None
1908
1909 # Make sure all core cells have included information from the cell definition
1910
1911 for cell in coregroup:
1912 if 'width' not in cell:
1913 try:
1914 coredef = next(item for item in coredefs if item['name'] == cell['cell'])
1915 except:
1916 self.print('Cell ' + cell['cell'] + ' not found!')
1917 else:
1918 cell['width'] = coredef['width']
1919 cell['height'] = coredef['height']
1920 cell['class'] = coredef['class']
1921 cell['subclass'] = coredef['subclass']
1922
1923 # Generate a new padframe by writing the configuration file, running
1924 # padring, reading back the DEF file, and (re)poplulating the workspace
1925
Tim Edwardsd9fe0002020-07-14 21:53:24 -04001926 def generate(self, level=0):
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001927 self.print('Generate legal padframe using padring')
1928
1929 # Write out the configuration file
1930 self.writeconfig()
1931 # Run the padring app
1932 self.runpadring()
1933 # Rotate pads in the output if pad orientations are different from
1934 # padring's expectations
1935 self.rotate_pads_in_def()
1936 # Read the placement information back from the generated DEF file
1937 self.readplacement()
1938 # Resolve differences (e.g., remove spacers)
1939 self.resolve()
1940 # Recreate and draw the padframe view on the canvas
1941 self.populate(level + 1)
1942 self.frame_configure(None)
1943
1944 # Write a new configuration file
1945
1946 def writeconfig(self):
1947 mag_path = self.projectpath + '/mag'
1948 if not os.path.exists(mag_path):
1949 self.print('Error: No project path /mag directory exists. Cannot write config file.')
1950 return
1951
1952 self.print('Writing padring configuration file.')
1953
1954 # Determine cell width and height from pad sizes.
1955 # NOTE: This compresses the chip to the minimum dimensions
1956 # allowed by the arrangement of pads. Use a "core" block to
1957 # force the area larger than minimum (not yet implemented)
1958
1959 topwidth = 0
1960 for pad in self.Npads:
1961 if 'width' not in pad:
1962 self.print('No width: pad = ' + str(pad))
1963 topwidth += pad['width']
1964
1965 # Add in the corner cells
1966 if self.NWpad != []:
1967 topwidth += self.NWpad[0]['height']
1968 if self.NEpad != []:
1969 topwidth += self.NEpad[0]['width']
1970
1971 botwidth = 0
1972 for pad in self.Spads:
1973 botwidth += pad['width']
1974
1975 # Add in the corner cells
1976 if self.SWpad != []:
1977 botwidth += self.SWpad[0]['width']
1978 if self.SEpad != []:
1979 botwidth += self.SEpad[0]['height']
1980
1981 width = max(botwidth, topwidth)
1982
1983 # if width < self.urx - self.llx:
1984 # width = self.urx - self.llx
1985
1986 leftheight = 0
1987 for pad in self.Wpads:
1988 leftheight += pad['width']
1989
1990 # Add in the corner cells
1991 if self.NWpad != []:
1992 leftheight += self.NWpad[0]['height']
1993 if self.SWpad != []:
1994 leftheight += self.SWpad[0]['width']
1995
1996 rightheight = 0
1997 for pad in self.Epads:
1998 rightheight += pad['width']
1999
2000 # Add in the corner cells
2001 if self.NEpad != []:
2002 rightheight += self.NEpad[0]['width']
2003 if self.SEpad != []:
2004 rightheight += self.SEpad[0]['height']
2005
2006 height = max(leftheight, rightheight)
2007
2008 # Check the dimensions of the core cells. If they exceed the available
2009 # padframe area, then expand the padframe to accomodate the core.
2010
2011 corellx = coreurx = (self.llx + self.urx) / 2
2012 corelly = coreury = (self.lly + self.ury) / 2
2013
2014 for corecell in self.coregroup:
2015 corient = corecell['o']
2016 if 'S' in corient or 'N' in corient:
2017 cwidth = corecell['width']
2018 cheight = corecell['height']
2019 else:
2020 cwidth = corecell['height']
2021 cheight = corecell['width']
2022
2023 if corecell['x'] < corellx:
2024 corellx = corecell['x']
2025 if corecell['x'] + cwidth > coreurx:
2026 coreurx = corecell['x'] + cwidth
2027 if corecell['y'] < corelly:
2028 corelly = corecell['y']
2029 if corecell['y'] + cheight > coreury:
2030 coreury = corecell['y'] + cheight
2031
2032 coreheight = coreury - corelly
2033 corewidth = coreurx - corellx
2034
2035 # Ignoring the possibility of overlaps with nonstandard-sized pads,
2036 # assuming that the user has visually separated them. Only check
2037 # the core bounds against the standard padframe inside edge.
2038
2039 if self.SWpad != [] and self.SEpad != []:
2040 if corewidth > width - self.SWpad[0]['width'] - self.SEpad[0]['width']:
2041 width = corewidth + self.SWpad[0]['width'] + self.SEpad[0]['width']
2042 if self.NWpad != [] and self.SWpad != []:
2043 if coreheight > height - self.NWpad[0]['height'] - self.SWpad[0]['height']:
2044 height = coreheight + self.NWpad[0]['height'] + self.SWpad[0]['height']
2045
2046 # Core cells are given a margin of self.margin from the pad inside edge, so the
2047 # core area passed to the padring app is 2 * self.margin larger than the
2048 # measured size of the core area.
2049 width += 2 * self.margin
2050 height += 2 * self.margin
2051
2052 if self.keep_cfg == False or not os.path.exists(mag_path + '/padframe.cfg'):
2053
2054 if os.path.exists(mag_path + '/padframe.cfg'):
2055 # Copy the previous padframe.cfg file to a backup. In case something
2056 # goes badly wrong, this should be the only file overwritten, and can
2057 # be recovered from the backup.
2058 shutil.copy(mag_path + '/padframe.cfg', mag_path + '/padframe.cfg.bak')
2059
2060 with open(mag_path + '/padframe.cfg', 'w') as ofile:
2061 print('AREA ' + str(int(width)) + ' ' + str(int(height)) + ' ;',
2062 file=ofile)
2063 print('', file=ofile)
2064 for pad in self.NEpad:
2065 print('CORNER ' + pad['name'] + ' SW ' + pad['cell'] + ' ;',
2066 file=ofile)
2067 for pad in self.SEpad:
2068 print('CORNER ' + pad['name'] + ' NW ' + pad['cell'] + ' ;',
2069 file=ofile)
2070 for pad in self.SWpad:
2071 print('CORNER ' + pad['name'] + ' NE ' + pad['cell'] + ' ;',
2072 file=ofile)
2073 for pad in self.NWpad:
2074 print('CORNER ' + pad['name'] + ' SE ' + pad['cell'] + ' ;',
2075 file=ofile)
2076 for pad in self.Npads:
2077 flip = 'F ' if 'F' in pad['o'] else ''
2078 print('PAD ' + pad['name'] + ' N ' + flip + pad['cell'] + ' ;',
2079 file=ofile)
2080 for pad in self.Epads:
2081 flip = 'F ' if 'F' in pad['o'] else ''
2082 print('PAD ' + pad['name'] + ' E ' + flip + pad['cell'] + ' ;',
2083 file=ofile)
2084 for pad in self.Spads:
2085 flip = 'F ' if 'F' in pad['o'] else ''
2086 print('PAD ' + pad['name'] + ' S ' + flip + pad['cell'] + ' ;',
2087 file=ofile)
2088 for pad in self.Wpads:
2089 flip = 'F ' if 'F' in pad['o'] else ''
2090 print('PAD ' + pad['name'] + ' W ' + flip + pad['cell'] + ' ;',
2091 file=ofile)
2092
2093 # Run the padring app.
2094
2095 def runpadring(self):
2096 mag_path = self.projectpath + '/mag'
2097 if not os.path.exists(mag_path):
2098 self.print('No path /mag exists in project space; cannot run padring.')
2099 return
2100
2101 self.print('Running padring')
2102
2103 if self.padring_path:
2104 padringopts = [self.padring_path]
2105 else:
2106 padringopts = ['padring']
2107
2108 # Diagnostic
2109 # self.print('Used libraries (self.ioleflibs) = ' + str(self.ioleflibs))
2110
2111 for iolib in self.ioleflibs:
2112 padringopts.append('-L')
2113 padringopts.append(iolib)
2114 padringopts.append('--def')
2115 padringopts.append('padframe.def')
2116 padringopts.append('padframe.cfg')
2117
2118 self.print('Running ' + str(padringopts))
2119
2120 p = subprocess.Popen(padringopts, stdout = subprocess.PIPE,
2121 stderr = subprocess.PIPE, cwd = mag_path)
2122 self.watch(p)
2123
2124 # Read placement information from the DEF file generated by padring.
2125
2126 def readplacement(self, precheck=False):
2127 self.print('Reading placement information from DEF file')
2128
2129 mag_path = self.projectpath + '/mag'
2130 if not os.path.isfile(mag_path + '/padframe.def'):
2131 if not precheck:
2132 self.print('No file padframe.def: pad frame was not generated.')
2133 return False
2134
2135 # Very simple DEF file parsing. The placement DEF only contains a
2136 # COMPONENTS section. Certain assumptions are made about the syntax
2137 # that depends on the way 'padring' writes its output. This is not
2138 # a rigorous DEF parser!
2139
2140 units = 1000
2141 in_components = False
2142 Npadlist = []
2143 Spadlist = []
2144 Epadlist = []
2145 Wpadlist = []
2146 NEpad = []
2147 NWpad = []
2148 SEpad = []
2149 SWpad = []
2150 coregroup = []
2151
2152 # Reset bounds
2153 self.llx = self.lly = self.urx = self.ury = 0
2154 corners = 0
2155
2156 with open(mag_path + '/padframe.def', 'r') as ifile:
2157 deflines = ifile.read().splitlines()
2158 for line in deflines:
2159 if 'UNITS DISTANCE MICRONS' in line:
2160 units = line.split()[3]
2161 elif in_components:
2162 lparse = line.split()
2163 if lparse[0] == '-':
2164 instname = lparse[1]
2165 cellname = lparse[2]
2166
2167 elif lparse[0] == '+':
2168 if lparse[1] == 'PLACED':
2169 placex = lparse[3]
2170 placey = lparse[4]
2171 placeo = lparse[6]
2172
2173 newpad = {}
2174 newpad['name'] = instname
2175 newpad['cell'] = cellname
2176
2177 try:
2178 celldef = next(item for item in self.celldefs if item['name'] == cellname)
2179 except:
2180 celldef = None
2181 else:
2182 newpad['iolib'] = celldef['iolib']
2183 newpad['width'] = celldef['width']
2184 newpad['height'] = celldef['height']
2185 newpad['class'] = celldef['class']
2186 newpad['subclass'] = celldef['subclass']
2187
2188 newpad['x'] = float(placex) / float(units)
2189 newpad['y'] = float(placey) / float(units)
2190 newpad['o'] = placeo
2191
2192 # Adjust bounds
2193 if celldef:
2194 if newpad['x'] < self.llx:
2195 self.llx = newpad['x']
2196 if newpad['y'] < self.lly:
2197 self.lly = newpad['y']
2198
2199 if newpad['o'] == 'N' or newpad['o'] == 'S':
2200 padurx = newpad['x'] + celldef['width']
2201 padury = newpad['y'] + celldef['height']
2202 else:
2203 padurx = newpad['x'] + celldef['height']
2204 padury = newpad['y'] + celldef['width']
2205
2206 if padurx > self.urx:
2207 self.urx = padurx
2208 if padury > self.ury:
2209 self.ury = padury
2210
2211 # First four entries in the DEF file are corners
2212 # padring puts the lower left corner at zero, so
2213 # use the zero coordinates to determine which pads
2214 # are which. Note that padring assumes the corner
2215 # pad is drawn in the SW corner position!
2216
2217 if corners < 4:
2218 if newpad['x'] == 0 and newpad['y'] == 0:
2219 SWpad.append(newpad)
2220 elif newpad['x'] == 0:
2221 NWpad.append(newpad)
2222 elif newpad['y'] == 0:
2223 SEpad.append(newpad)
2224 else:
2225 NEpad.append(newpad)
2226 corners += 1
2227 else:
2228 # Place according to orientation. If orientation
2229 # is not standard, be sure to make it standard!
2230 placeo = self.rotate_orientation(placeo)
2231 if placeo == 'N':
2232 Spadlist.append(newpad)
2233 elif placeo == 'E':
2234 Wpadlist.append(newpad)
2235 elif placeo == 'S':
2236 Npadlist.append(newpad)
2237 else:
2238 Epadlist.append(newpad)
2239
2240 elif 'END COMPONENTS' in line:
2241 in_components = False
2242 elif 'COMPONENTS' in line:
2243 in_components = True
2244
2245 self.Npads = Npadlist
2246 self.Wpads = Wpadlist
2247 self.Spads = Spadlist
2248 self.Epads = Epadlist
2249
2250 self.NWpad = NWpad
2251 self.NEpad = NEpad
2252 self.SWpad = SWpad
2253 self.SEpad = SEpad
2254
2255 # The padframe has its own DEF file from the padring app, but the core
2256 # does not. The core needs to be floorplanned in a very similar manner.
2257 # This will be done by searching for a DEF file of the project top-level
2258 # layout. If none exists, it is created by generating it from the layout.
2259 # If the top-level layout does not exist, then all core cells are placed
2260 # at the origin, and the origin placed at the padframe inside corner.
2261
2262 mag_path = self.projectpath + '/mag'
2263 if not os.path.isfile(mag_path + '/' + self.project + '.def'):
2264 if os.path.isfile(mag_path + '/' + self.project + '.mag'):
2265
2266 # Create a DEF file from the layout
2267 with open(mag_path + '/pfg_write_def.tcl', 'w') as ofile:
2268 print('drc off', file=ofile)
2269 print('box 0 0 0 0', file=ofile)
2270 print('load ' + self.project, file=ofile)
2271 print('def write', file=ofile)
2272 print('quit', file=ofile)
2273
2274 magicexec = self.magic_path if self.magic_path else 'magic'
2275 mproc = subprocess.Popen([magicexec, '-dnull', '-noconsole',
2276 'pfg_write_def.tcl'],
2277 stdin = subprocess.PIPE, stdout = subprocess.PIPE,
2278 stderr = subprocess.PIPE, cwd = mag_path, universal_newlines = True)
2279
2280 self.watch(mproc)
2281 os.remove(mag_path + '/pfg_write_def.tcl')
2282
2283 elif not os.path.isfile(mag_path + '/core.def'):
2284
2285 # With no other information available, copy the corecells
2286 # (from the verilog file) into the coregroup list.
2287 # Position all core cells starting at the padframe top left
2288 # inside corner, and arranging in rows without overlapping.
2289 # Note that no attempt is made to organize the cells or
2290 # otherwise produce an efficient layout. Any dimension larger
2291 # than the current padframe overruns to the right or bottom.
2292
2293 if self.SWpad != []:
2294 corellx = SWpad[0]['x'] + SWpad[0]['width'] + self.margin
2295 corelly = SWpad[0]['y'] + SWpad[0]['height'] + self.margin
2296 else:
2297 corellx = Wpadlist[0]['x'] + Wpadlist[0]['height'] + self.margin
2298 corelly = Spadlist[0]['x'] + Spadlist[0]['height'] + self.margin
2299 if self.NEpad != []:
2300 coreurx = NEpad[0]['x'] - self.margin
2301 coreury = NEpad[0]['y'] - self.margin
2302 else:
2303 coreurx = Epadlist[0]['x'] - self.margin
2304 coreury = Npadlist[0]['x'] - self.margin
2305 locllx = corellx
2306 testllx = corellx
2307 loclly = corelly
2308 testlly = corelly
2309 nextlly = corelly
2310
2311 for cell in self.corecells:
2312
2313 testllx = locllx + cell['width']
2314 if testllx > coreurx:
2315 locllx = corellx
2316 corelly = nextlly
2317 loclly = nextlly
2318
2319 newcore = cell
2320 newcore['x'] = locllx
2321 newcore['y'] = loclly
2322 newcore['o'] = 'N'
2323
2324 locllx += cell['width'] + self.margin
2325
2326 testlly = corelly + cell['height'] + self.margin
2327 if testlly > nextlly:
2328 nextlly = testlly
2329
2330 coregroup.append(newcore)
2331
2332 self.coregroup = coregroup
2333
2334 if os.path.isfile(mag_path + '/' + self.project + '.def'):
2335 # Read the top-level DEF, and use it to position the core cells.
2336 self.print('Reading the top-level cell DEF for core cell placement.')
2337
2338 units = 1000
2339 in_components = False
2340 with open(mag_path + '/' + self.project + '.def', 'r') as ifile:
2341 deflines = ifile.read().splitlines()
2342 for line in deflines:
2343 if 'UNITS DISTANCE MICRONS' in line:
2344 units = line.split()[3]
2345 elif in_components:
2346 lparse = line.split()
2347 if lparse[0] == '-':
2348 instname = lparse[1]
2349 # NOTE: Magic should not drop the entire path to the
2350 # cell for the cellname; this needs to be fixed! To
2351 # work around it, remove any path components.
2352 cellpath = lparse[2]
2353 cellname = os.path.split(cellpath)[1]
2354
2355 elif lparse[0] == '+':
2356 if lparse[1] == 'PLACED':
2357 placex = lparse[3]
2358 placey = lparse[4]
2359 placeo = lparse[6]
2360
2361 newcore = {}
2362 newcore['name'] = instname
2363 newcore['cell'] = cellname
2364
2365 try:
2366 celldef = next(item for item in self.coredefs if item['name'] == cellname)
2367 except:
2368 celldef = None
2369 else:
2370 newcore['celllib'] = celldef['celllib']
2371 newcore['width'] = celldef['width']
2372 newcore['height'] = celldef['height']
2373 newcore['class'] = celldef['class']
2374 newcore['subclass'] = celldef['subclass']
2375
2376 newcore['x'] = float(placex) / float(units)
2377 newcore['y'] = float(placey) / float(units)
2378 newcore['o'] = placeo
2379 coregroup.append(newcore)
2380
2381 elif 'END COMPONENTS' in line:
2382 in_components = False
2383 elif 'COMPONENTS' in line:
2384 in_components = True
2385
2386 self.coregroup = coregroup
2387
2388 elif os.path.isfile(mag_path + '/core.def'):
2389 # No DEF or .mag file, so fallback position is the last core.def
2390 # file generated by this script.
2391 self.read_core_def(precheck=precheck)
2392
2393 return True
2394
2395 # Read placement information from the "padframe.def" file and rotate
2396 # all cells according to self.pad_rotation. This accounts for the
2397 # problem that the default orientation of pads is arbitrarily defined
2398 # by the foundry, while padring assumes that the corner pad is drawn
2399 # in the lower-left position and other pads are drawn with the pad at
2400 # the bottom and the buses at the top.
2401
2402 def rotate_pads_in_def(self):
2403 if self.pad_rotation == 0:
2404 return
2405
2406 self.print('Rotating pads in padframe DEF file.')
2407 mag_path = self.projectpath + '/mag'
2408
2409 if not os.path.isfile(mag_path + '/padframe.def'):
2410 self.print('No file padframe.def: Cannot modify pad rotations.')
2411 return
2412
2413 deflines = []
2414 with open(mag_path + '/padframe.def', 'r') as ifile:
2415 deflines = ifile.read().splitlines()
2416
2417 outlines = []
2418 in_components = False
2419 for line in deflines:
2420 if in_components:
2421 lparse = line.split()
2422 if lparse[0] == '+':
2423 if lparse[1] == 'PLACED':
2424 neworient = self.rotate_orientation(lparse[6])
2425 lparse[6] = neworient
2426 line = ' '.join(lparse)
2427
2428 elif 'END COMPONENTS' in line:
2429 in_components = False
2430 elif 'COMPONENTS' in line:
2431 in_components = True
2432 outlines.append(line)
2433
2434 with open(mag_path + '/padframe.def', 'w') as ofile:
2435 for line in outlines:
2436 print(line, file=ofile)
2437
2438 # Read placement information from the DEF file for the core (created by
2439 # a previous run of this script)
2440
2441 def read_core_def(self, precheck=False):
2442 self.print('Reading placement information from core DEF file.')
2443
2444 mag_path = self.projectpath + '/mag'
2445
2446 if not os.path.isfile(mag_path + '/core.def'):
2447 if not precheck:
2448 self.print('No file core.def: core placement was not generated.')
2449 return False
2450
2451 # Very simple DEF file parsing, similar to the padframe.def reading
2452 # routine above.
2453
2454 units = 1000
2455 in_components = False
2456
2457 coregroup = []
2458
2459 with open(mag_path + '/core.def', 'r') as ifile:
2460 deflines = ifile.read().splitlines()
2461 for line in deflines:
2462 if 'UNITS DISTANCE MICRONS' in line:
2463 units = line.split()[3]
2464 elif in_components:
2465 lparse = line.split()
2466 if lparse[0] == '-':
2467 instname = lparse[1]
2468 cellname = lparse[2]
2469
2470 elif lparse[0] == '+':
2471 if lparse[1] == 'PLACED':
2472 placex = lparse[3]
2473 placey = lparse[4]
2474 placeo = lparse[6]
2475
2476 newcore = {}
2477 newcore['name'] = instname
2478 newcore['cell'] = cellname
2479
2480 try:
2481 celldef = next(item for item in self.coredefs if item['name'] == cellname)
2482 except:
2483 celldef = None
2484 else:
2485 newcore['celllib'] = celldef['celllib']
2486 newcore['width'] = celldef['width']
2487 newcore['height'] = celldef['height']
2488 newcore['class'] = celldef['class']
2489 newcore['subclass'] = celldef['subclass']
2490
2491 newcore['x'] = float(placex) / float(units)
2492 newcore['y'] = float(placey) / float(units)
2493 newcore['o'] = placeo
2494 coregroup.append(newcore)
2495
2496 elif 'END COMPONENTS' in line:
2497 in_components = False
2498 elif 'COMPONENTS' in line:
2499 in_components = True
2500
2501 self.coregroup = coregroup
2502
2503 return True
2504
2505 # Save the layout to a Magic database file (to be completed)
2506
2507 def save(self):
2508 self.print('Saving results in a magic layout database.')
2509
2510 # Generate a list of (unique) LEF libraries for all padframe and core cells
2511 leflist = []
2512 for pad in self.celldefs:
2513 if pad['iolib'] not in leflist:
2514 leflist.append(pad['iolib'])
2515
2516 for core in self.coredefs:
2517 if core['celllib'] not in leflist:
2518 leflist.append(core['celllib'])
2519
2520 # Run magic, and generate the padframe with a series of commands
2521 mag_path = self.projectpath + '/mag'
2522
2523 with open(mag_path + '/pfg_write_mag.tcl', 'w') as ofile:
2524 print('drc off', file=ofile)
2525 print('box 0 0 0 0', file=ofile)
2526 for leffile in leflist:
2527 print('lef read ' + leffile, file=ofile)
2528 print('def read padframe', file=ofile)
2529 print('select top cell', file=ofile)
2530 print('select area', file=ofile)
2531 print('select save padframe', file=ofile)
2532 print('delete', file=ofile)
2533 print('def read core', file=ofile)
2534 print('getcell padframe', file=ofile)
2535 print('save ' + self.project, file=ofile)
2536 print('writeall force ' + self.project, file=ofile)
2537 print('quit', file=ofile)
2538
2539 magicexec = self.magic_path if self.magic_path else 'magic'
2540 mproc = subprocess.Popen([magicexec, '-dnull', '-noconsole',
2541 'pfg_write_mag.tcl'],
2542 stdin = subprocess.PIPE, stdout = subprocess.PIPE,
2543 stderr = subprocess.PIPE, cwd = mag_path, universal_newlines = True)
2544 self.watch(mproc)
2545 os.remove(mag_path + '/pfg_write_mag.tcl')
2546 self.print('Done writing layout ' + self.project + '.mag')
2547
2548 # Write the core DEF file if it does not exist yet.
2549 if not os.path.isfile(mag_path + '/core.def'):
2550 self.write_core_def()
2551
2552if __name__ == '__main__':
2553 faulthandler.register(signal.SIGUSR2)
2554 options = []
2555 arguments = []
2556 for item in sys.argv[1:]:
2557 if item.find('-', 0) == 0:
2558 options.append(item)
2559 else:
2560 arguments.append(item)
2561
2562 if '-help' in options:
2563 print(sys.argv[0] + ' [options]')
2564 print('')
2565 print('options:')
2566 print(' -noc Print output to terminal, not the gui window')
2567 print(' -nog No graphics, run in batch mode')
2568 print(' -cfg Use existing padframe.cfg, do not regenerate')
2569 print(' -padring-path=<path> path to padring executable')
2570 print(' -magic-path=<path> path to magic executable')
2571 print(' -tech-path=<path> path to tech root folder')
2572 print(' -project-path=<path> path to project root folder')
2573 print(' -help Print this usage information')
2574 print('')
2575 sys.exit(0)
2576
2577 root = tkinter.Tk()
2578 do_gui = False if ('-nog' in options or '-nogui' in options) else True
2579 app = SoCFloorplanner(root, do_gui)
2580
2581 # Allow option -noc to bypass the text-to-console redirection, so crash
2582 # information doesn't disappear with the app.
2583
2584 app.use_console = False if ('-noc' in options or '-noconsole' in options) else True
2585 if do_gui == False:
2586 app.use_console = False
2587
2588 # efabless format can be specified on the command line, but note that it
2589 # is otherwise auto-detected by checking for .config vs. .ef-config in
2590 # the project space.
2591
2592 app.ef_format = True if '-ef_format' in options else False
2593 app.keep_cfg = True if '-cfg' in options else False
2594
2595 app.padring_path = None
2596 app.magic_path = None
2597 app.techpath = None
2598 app.projectpath = None
2599
2600 for option in options:
2601 if option.split('=')[0] == '-padring-path':
2602 app.padring_path = option.split('=')[1]
2603 elif option.split('=')[0] == '-magic-path':
2604 app.magic_path = option.split('=')[1]
2605 elif option.split('=')[0] == '-tech-path':
2606 app.techpath = option.split('=')[1]
2607 elif option.split('=')[0] == '-project-path':
2608 app.projectpath = option.split('=')[1]
2609 app.projectpath = app.projectpath[:-1] if app.projectpath[-1] == '/' else app.projectpath
2610
2611 app.text_to_console()
2612 app.init_padframe()
2613 if app.do_gui:
2614 root.mainloop()
2615 else:
2616 # Run 'save' in non-GUI mode
2617 app.save()
2618 sys.exit(0)
2619