use --recursive on git submodule not --remote - one does a "latest update"
[soclayout.git] / experiments7 / utils.py
1 # -*- coding: utf-8 -*-
2 from __future__ import print_function
3 import re
4
5 import Anabatic
6 import CRL
7 import Cfg
8 import Etesian
9 import Katana
10 from Hurricane import (
11 Breakpoint, DbU, DataBase, UpdateSession, Box, Transformation, Instance, Pad, Pin,
12 NetExternalComponents,
13 )
14 from plugins import rsave
15
16
17 class SessionManager(object):
18 """
19 Context manager for a GO update session. See Hurricane reference manual
20 for an info on Hurricane::UpdateSession class.
21 """
22
23 def __enter__(self):
24 UpdateSession.open()
25
26 def __exit__(self, *args):
27 UpdateSession.close()
28
29
30 class Module(object):
31
32 _layer_cache = {}
33 _instances = None
34
35 def __init__(self, cell, editor, width=None, height=None, submodules=None,
36 pin_width=2.0, pin_height=2.0, pin_suffix='.0',
37 pin_layer=None, north_pins=None, east_pins=None,
38 south_pins=None, west_pins=None, pads=None,
39 orientation=None, **kwargs):
40 """
41 Creates a module.
42
43 :param cell: cell name or Hurricane.Cell object,
44 :param editor: editor object when executing from cgt (or None),
45 :param width: module width,
46 :param height: module height,
47 :param submodules: submodules (Module objects)
48 or tuples of (submodule, x, y), where (x, y) is a submodule's
49 placement point in lambdas,
50 :param pin_width: default pin width,
51 :param pin_height: default pin height,
52 :param pin_suffix: default pin suffix,
53 :param pin_layer: default layer for placing pins,
54 :param north_pins: list of pin configuration dictionaries for placing
55 pins on the north side,
56 :param east_pins: ditto (for the east side),
57 :param south_pins: ditto (for the south side),
58 :param west_pins: ditto (for the west side),
59 :param pads: dictionary of {net: list of layers} for creating pads,
60 :param orientation: when placed, should be orientated/mirrored etc.
61 :param kwargs: extra parameters to be implemented in derived classes.
62 """
63 self.editor = editor
64 self.af = CRL.AllianceFramework.get()
65 self.db = DataBase.getDB()
66 self.width = width
67 self.height = height
68
69 if isinstance(cell, basestring):
70 self.cell = self.af.getCell(cell, CRL.Catalog.State.Logical)
71 else:
72 self.cell = cell
73
74 self.pin_width = pin_width
75 self.pin_height = pin_height
76 self.pin_suffix = pin_suffix
77 if pin_layer is None:
78 self.pin_layer = self.get_layer('METAL3')
79 elif isinstance(pin_layer, basestring):
80 self.pin_layer = self.get_layer(pin_layer)
81 else:
82 self.pin_layer = pin_layer
83 self.north_pins = north_pins or []
84 self.east_pins = east_pins or []
85 self.south_pins = south_pins or []
86 self.west_pins = west_pins or []
87
88 self.orientation = orientation or Transformation.Orientation.ID
89
90 self.pads = pads or {}
91
92 self._submodules = []
93 if submodules is not None:
94 for submodule in submodules:
95 self._submodules.append(
96 (submodule, None, None) if isinstance(submodule, Module)
97 else submodule
98 )
99
100
101 @property
102 def name(self):
103 return self.cell.getName()
104
105 @name.setter
106 def name(self, name):
107 self.cell.setName(name)
108
109 def __str__(self):
110 return self.name
111
112 @property
113 def ab(self):
114 """ The real abutment box. """
115 return self.cell.getAbutmentBox()
116
117 @ab.setter
118 def ab(self, ab):
119 self.cell.setAbutmentBox(ab)
120
121 @property
122 def instances(self):
123 """ Cached instances. """
124 if self._instances is None:
125 self._instances = self.cell.getInstances()
126
127 return self._instances
128
129 def get_layer(self, name):
130 """ Creates a new layer or returns it from cache. """
131 if name in self._layer_cache:
132 return self._layer_cache[name]
133
134 layer = self.db.getTechnology().getLayer(name)
135 self._layer_cache[name] = layer
136 return layer
137
138 @staticmethod
139 def to_dbu(lmb):
140 """
141 Convert lambdas to database units. (See Hurricane+Python Manual 3.4.)
142 """
143 return DbU.fromLambda(lmb)
144
145 @staticmethod
146 def from_dbu(dbu):
147 """
148 Convert lambdas to database units. (See Hurricane+Python Manual 3.4.)
149 """
150 return DbU.toLambda(dbu)
151
152 @property
153 def ab_x(self):
154 return self.from_dbu(self.ab.getXMin())
155
156 @property
157 def ab_y(self):
158 return self.from_dbu(self.ab.getYMin())
159
160 @property
161 def ab_width(self):
162 return self.from_dbu(self.ab.getWidth())
163
164 @property
165 def ab_height(self):
166 return self.from_dbu(self.ab.getXHeight())
167
168 @staticmethod
169 def match_instance(datapath_insts, op, plug_name, inst):
170 """
171 Guess the position of an instance from its nets connections,
172 and put it at the right place in the datapath vector.
173
174 :param datapath_insts: vector of bit slices,
175 :param op: operator name,
176 :param inst: instance to classify,
177 :param plug_name: name of the plug to use to guess the bit index,
178 :return: boolean, True if the instance has been matched.
179 """
180 if not inst.getMasterCell().getName().startswith(op):
181 return False
182 re_net_index = re.compile(r'[^(]+\((?P<index>[\d]+)\)$')
183 for plug in inst.getPlugs():
184 if plug.getMasterNet().getName() != plug_name:
185 continue
186 m = re_net_index.match(plug.getNet().getName())
187 if not m:
188 continue
189 bit_slice = datapath_insts[int(m.group('index'))]
190 for column in bit_slice:
191 if column[0] == op:
192 column[1] = inst
193 print ("match", plug_name, int(m.group('index')),
194 column, inst)
195 return True
196 break
197 return False
198
199 def compute_ab(self):
200 """ Compute default abutment box without placement. """
201 etesian = Etesian.EtesianEngine.create(self.cell)
202 etesian.setDefaultAb()
203 etesian.destroy()
204
205 def set_ab(self, width, height):
206 """
207 Let the user specify the abutment box. Bottom left corner always
208 sets to (0,0).
209 """
210 self.cell.setAbutmentBox(Box(self.to_dbu(0.0), self.to_dbu(0.0),
211 self.to_dbu(width), self.to_dbu(height)))
212
213 def place(self):
214 """ Places the current cell. """
215 etesian = Etesian.EtesianEngine.create(self.cell)
216 etesian.place()
217
218 def route(self):
219 """ Routes the current cell. """
220 katana = Katana.KatanaEngine.create(self.cell)
221 katana.digitalInit()
222 katana.runGlobalRouter(Katana.Flags.NoFlags)
223 katana.loadGlobalRouting(Anabatic.EngineLoadGrByNet)
224 katana.layerAssign(Anabatic.EngineNoNetLayerAssign)
225 katana.runNegociate(Katana.Flags.NoFlags)
226 #Breakpoint.stop(0, 'After routing {0}'.format(self.cell))
227 katana.finalizeLayout()
228 result = katana.isDetailedRoutingSuccess()
229 katana.destroy()
230 return result
231
232 def place_and_route(self):
233 """ Places and routes. """
234 self.place()
235 return self.route()
236
237 def create_pin_series(self, net, direction, name=None,
238 status=Pin.PlacementStatus.FIXED, layer=None,
239 x=None, y=None, width=2.0, height=2.0,
240 repeat=1, delta=0.0, external=True):
241 """
242 Creates a pin or a series of pins in a cell.
243
244 :param net: Hurricane.Net object name or name template, taking a pin
245 enumeration parameter, i. e. pin number,
246 :param name: pin name or name template taking a pin enumeration
247 parameter (net name or template + suffix),
248 :param direction: Pin.Direction value,
249 :param status: Pin.PlacementStatus value (default is FIXED),
250 :param layer: Hurricane.Layer object or name (METAL3),
251 :param x: starting pin position (left to right, 0.0),
252 :param y: starting pin position (bottom to top, 0.0),
253 :param width: pin width (2,0),
254 :param height: pin height (2.0),
255 :param repeat: a number of pins to be placed or an iterable containing
256 pin template parameters (i. e. pin number, 1),
257 :param delta: next pin position offset (0.0),
258 :param external: mark pin as external (yes),
259 :return: tuple of next pin coordinates, or just (x, y), if delta
260 was not provided.
261 """
262 if layer is None:
263 layer = self.pin_layer
264 elif isinstance(layer, basestring):
265 layer = self.get_layer(layer)
266
267 if isinstance(repeat, int):
268 if repeat > 1 and delta == 0.0:
269 raise Warning(
270 '{}: you are trying to place pins on each other.'.format(
271 self.name
272 )
273 )
274 iterator = range(repeat)
275 else:
276 iterator = repeat
277
278 if name is None:
279 name = net + self.pin_suffix
280
281 for i in iterator:
282 pin = Pin.create(self.cell.getNet(net.format(i)),
283 name.format(i), direction, status, layer,
284 self.to_dbu(x), self.to_dbu(y),
285 self.to_dbu(width), self.to_dbu(height))
286 if direction in (Pin.Direction.NORTH, Pin.Direction.SOUTH):
287 x += delta
288 else:
289 # EAST or WEST
290 y += delta
291
292 if external:
293 pin.getNet().setExternal(True)
294 NetExternalComponents.setExternal(pin)
295
296 return x, y
297
298 def init_ab(self):
299 """ Create the abutment box with object's initial values. """
300 if self.width and self.height:
301 self.ab = Box(
302 0, 0, self.to_dbu(self.width), self.to_dbu(self.height)
303 )
304 else:
305 raise Warning('{}: Module size is not set.'.format(self.name))
306
307 def create_pins(self):
308 """ Creates all pins set on Module object creation. """
309
310 def default_x():
311 if direction == Pin.Direction.EAST:
312 return self.from_dbu(self.ab.getWidth())
313 if direction == Pin.Direction.WEST:
314 return 0
315 return last_x
316
317 def default_y():
318 if direction == Pin.Direction.NORTH:
319 return self.from_dbu(self.ab.getHeight())
320 if direction == Pin.Direction.SOUTH:
321 return 0
322 return last_y
323
324 for pins, direction in (
325 (self.north_pins, Pin.Direction.NORTH),
326 (self.east_pins, Pin.Direction.EAST),
327 (self.south_pins, Pin.Direction.SOUTH),
328 (self.west_pins, Pin.Direction.WEST),
329 ):
330 last_x = last_y = 0.0
331 for pin_config in pins:
332 net = pin_config.pop('net')
333 name = pin_config.pop('name', net + self.pin_suffix)
334 layer = pin_config.pop('layer', self.pin_layer)
335 x = pin_config.pop('x', default_x())
336 y = pin_config.pop('y', default_y())
337 last_x, last_y = self.create_pin_series(
338 net, direction, name=name, layer=layer, x=x, y=y,
339 **pin_config)
340
341 def create_pads_for_net(self, net, layers):
342 """
343 Creates a series of pads for a given net.
344
345 :param net: net name or Hurricane.Net object to create pads for,
346 :param layers: list of layer names or Hurricane.Layer objects
347 to create pads on.
348 """
349 temp_ab = self.ab
350 temp_ab.inflate(self.to_dbu(-5.0))
351 if isinstance(net, basestring):
352 net = self.cell.getNet(net)
353
354 for layer in layers:
355 if isinstance(layer, basestring):
356 layer = self.get_layer(layer)
357 Pad.create(net, layer, temp_ab)
358
359 def create_pads(self):
360 """ Create all pads for a given Module object. """
361
362 for net, layers in self.pads.items():
363 self.create_pads_for_net(net, layers)
364
365 @property
366 def submodules(self):
367 """ Submodules iterator. """
368 return iter(submodule for submodule, x, y in self._submodules)
369
370 def find_submodule(self, name):
371 """
372 Returns first submodule matching `name`. Better give your submodules
373 unique names.
374
375 :param name: submodule name to match,
376 :return: `Module` object.
377 """
378 return next(s for s in self.submodules if s.name == name)
379
380 def build_submodules(self):
381 """
382 Execute submodules and gather their status.
383
384 :return: True if all submodules executed successfully, False otherwise.
385 """
386
387 for submodule in self.submodules:
388 if not submodule.build():
389 return False
390
391 return True
392
393 def place_submodule(self, submodule, x, y):
394 """
395 Places a submodule to a given location.
396
397 :param submodule: `Module` object,
398 :param x: placement coordinate,
399 :param y: placement coordinate.
400 """
401
402 # find instance
403 instance = [
404 inst for inst in self.instances if inst.getName().endswith(
405 submodule.name
406 )
407 ][0]
408
409 # place submodule
410 instance.setTransformation(Transformation(
411 self.to_dbu(x), self.to_dbu(y), submodule.orientation,
412 ))
413 instance.setPlacementStatus(Instance.PlacementStatus.FIXED)
414
415 def place_submodules(self):
416 """
417 Places the submodules in the current module using their initial
418 placement points, if set.
419 """
420 for submodule, x, y in self._submodules:
421 if x is not None and y is not None:
422 self.place_submodule(submodule, x, y)
423 else:
424 raise Warning((
425 '{}: cannot place {}, because its '
426 'initial placement point is not set.'
427 ).format(self.name, submodule.name))
428
429 def save(self):
430 """ Saves cell. """
431 rsave.scriptMain(editor=self.editor, cell=self.cell)
432
433 def build(self):
434 """ Main routine. """
435
436 raise NotImplementedError('You need to implement the `build` method.')
437
438 def get_net_connections(self, to_find, already_found):
439 inst = self.cell.getInstances()
440 return get_net_connections(inst, to_find, already_found)
441
442
443 class Config:
444
445 def __init__(self, priority=None):
446 self._priority = priority
447
448 def __enter__(self):
449 if self._priority is not None:
450 Cfg.Configuration.pushDefaultPriority(self._priority)
451 return self
452
453 def __setattr__(self, attr, val):
454 if attr.startswith("_"):
455 self.__dict__[attr] = val
456 return
457 attr = attr.replace("_", ".")
458 if isinstance(val, bool):
459 Cfg.getParamBool(attr).setBool(val)
460 elif isinstance(val, int):
461 p = Cfg.getParamInt(attr) # all params have a type
462 if p.type == 'Enumerate':
463 Cfg.getParamEnumerate(attr).setInt(val)
464 else:
465 Cfg.getParamInt(attr).setInt(val)
466 elif '%' in val:
467 Cfg.getParamPercentage(attr).setPercentage(float(val[:-1]))
468 else:
469 Cfg.getParamString(attr).setString(val)
470
471 def __exit__(self, *args):
472 if self._priority is not None:
473 Cfg.Configuration.popDefaultPriority()
474
475
476 def get_net_connections(instances, find, already_found):
477 res = set()
478 new = set()
479 search_more = []
480 for inst in instances:
481 if (inst.getPlacementStatus() !=
482 Instance.PlacementStatus.UNPLACED):
483 continue
484 #print ("instance", inst)
485 for plug in inst.getConnectedPlugs():
486 netname = plug.getNet().getName()
487 if netname in already_found:
488 continue
489 if plug.getNet().getName() in find:
490 #print ("plug", plug, plug.getNet().getName())
491 for p in plug.getNet().getPlugs():
492 c = p.getInstance()
493 if (c.getPlacementStatus() !=
494 Instance.PlacementStatus.UNPLACED):
495 continue
496 #print ("notplaced", c)
497 for pc in c.getConnectedPlugs():
498 #print ("plug", pc)
499 pn = pc.getNet().getName()
500 if pn not in find and pn not in already_found:
501 search_more.append(pn)
502 new.add(c)
503 res.update(new)
504 if search_more:
505 print("more", search_more)
506 new_found = find + already_found
507 more = get_net_connections(new, search_more, new_found)
508 res.update(more)
509
510 return res