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