def __init__(self, temp_db, lib_name, params, used_names, **kwargs): # Call TemplateBase's constructor TemplateBase.__init__(self, temp_db, lib_name, params, used_names, **kwargs) self.tech = self.grid.tech_info self._res = .001 # set basic grid size to be 1nm # Create a dictionary that holds all objects required to construct the layout self._db = { 'rect': [], 'via': [], 'prim_via': [], 'instance': [], 'template': [] } # Create an empty database that will store only the relevant layout objects self.loc = {} # Manage the tracks in a track manager self.tracks = TrackManager.from_routing_grid(self.grid) # Pull default layout parameters self.params = self.__class__.get_default_param_values() # Override defaults and add provided parameters for key in params: self.params[key] = params[key] # Set up properties for BAG black-boxing self.temp_boundary = Rectangle(xy=[[0, 0], [.1, .1]], layer='M1', virtual=True) # TODO: Make process agnostic self.prim_bound_box = None self.prim_top_layer = None
def copy_rect(self, rect: Rectangle, layer=None, # type: Union[str, Tuple[str, str]] virtual: bool = False ) -> Rectangle: """ Creates a copy of the given rectangle and adds it to the local db Args: rect (Rectangle): rectangle object to be copied layer (str): layer that the copied rectangle should be drawn on. If None, the copied rectangle will use the same layer as the provided rectangle virtual (bool): If true, the rectangle object will be created but will not be drawn in the final layout. If false, the rectangle will be drawn as normal in the final layout Returns: (Rectangle): a new rectangle object copied from provided rectangle """ temp = rect.copy(layer=layer, virtual=virtual) self._db['rect'].append(temp) return self._db['rect'][-1]
def add_rect(self, layer: Union[str, Tuple[str, str], List[str]], xy=None, virtual: bool = False) -> Rectangle: """ Instantiates a rectangle, adds the Rectangle object to local db, and returns it for further user manipulation Args: layer (str): layer that the rectangle should be drawn on xy (Tuple[[float, float], [float, float]]): list of xy coordinates representing the lower left and upper right corner of the rectangle. If None, select default size of 100nm by 100nm at origin virtual (bool): If true, the rectangle object will be created but will not be drawn in the final layout. If false, the rectangle will be drawn as normal in the final layout Returns: (Rectangle): the created rectangle object """ if xy is None: xy = [[0, 0], [.1, .1]] temp = Rectangle(xy, layer, virtual=virtual) self._db['rect'].append(temp) return self._db['rect'][-1]
def from_metals(cls, rect1: Rectangle, rect2: Rectangle, size: Tuple[int, int] = (None, None) ) -> "Via": """ Generates a via instance from two rectangles """ via_id0 = 'V' + rect1.layer + '_' + rect2.layer via_id1 = 'V' + rect2.layer + '_' + rect1.layer if via_id0 in cls.vias: return cls(bbox=rect1.get_overlap(rect2), via_id=via_id0, size=size) elif via_id1 in cls.vias: return cls(bbox=rect2.get_overlap(rect1), via_id=via_id1, size=size) else: raise ValueError(f"A single via cannot be created between {rect1.layer} and {rect2.layer}")
def add_rect(self, layer: Optional[Union[str, Tuple[str, str], List[str]]] = None, xy=None, virtual: bool = False, *, index: Optional[int] = None ) -> Rectangle: """ Instantiates a rectangle, adds the Rectangle object to local db, and returns it for further user manipulation Parameters ---------- layer : Optional[str] layer that the rectangle should be drawn on xy : Tuple[[float, float], [float, float]] list of xy coordinates representing the lower left and upper right corner of the rectangle. If None, select default size of 100nm by 100nm at origin virtual : bool If true, the rectangle object will be created but will not be drawn in the final layout. If false, the rectangle will be drawn as normal in the final layout index : Optional[int] If provided, will look up the layer name associated with the index, and then draw the rectangle on that layer. Returns ------- rect: Rectangle the created rectangle object """ # If a layer index is provided, reverse-lookup the layer name if index is not None: layer = self.grid.tech_info.get_layer_name(index) # Only try default sizing if the user does not provide xy coordinate if xy is None: layer_params = tech_info.tech_info['metal_tech']['metals'] # This variable holds the name of the metal to be looked up in the tech params layer_lookup = layer if isinstance(layer, list): layer_lookup = layer[0] # If we can find our layer in the metal tech params, use its default width if layer_lookup in layer_params: metal_params = tech_info.tech_info['metal_tech']['metals'][layer_lookup] default_w = metal_params['min_width'] # Otherwise just default to 100nm width else: default_w = 0.1 xy = [[0, 0], [default_w, default_w]] # Otherwise just directly create the rectangle according to the users specification self._db['rect'].append(Rectangle(xy, layer=layer, virtual=virtual)) return self._db['rect'][-1]
def _commit_inst(self) -> None: """ Takes in all inst in the db and creates standard BAG equivalents """ for inst in self._db['instance']: # Get the boundary of the instance try: bound = inst.master.temp_boundary.shift_origin(origin=inst.origin, orient=inst.orient) except AttributeError: # TODO: Get the size properly bound = Rectangle(xy=[[0, 0], [.1, .1]], layer='M1', virtual=True) self.temp_boundary = self.temp_boundary.get_enclosure(bound) TemplateBase.add_instance(self, inst.master, inst_name=inst.inst_name, loc=inst.origin, orient=inst.orient)
def _parse_rects(self) -> None: """ Takes all of the rectangles from the raw content and generates new virtual rectangles from them. """ for _, rect in self._raw_content['rects'].items(): # Extract the information from the raw content layer = self._parse_layer(rect['layer']) xy = rect['bBox'] # Generate a new virtual rect to mirror this cadence rectangle new_rect = Rectangle(layer=layer, xy=xy, virtual=True) if layer[0] not in self._rect_list: self._rect_list[layer[0]] = [new_rect] else: self._rect_list[layer[0]].append(new_rect)
class AyarLayoutGenerator(TemplateBase, metaclass=abc.ABCMeta): """ The AyarLayoutGenerator class implements functions and variables for full-custom layout generations on physical grids """ def __init__(self, temp_db, lib_name, params, used_names, **kwargs): # Call TemplateBase's constructor TemplateBase.__init__(self, temp_db, lib_name, params, used_names, **kwargs) self.tech = self.grid.tech_info self._res = .001 # set basic grid size to be 1nm # Create a dictionary that holds all objects required to construct the layout self._db = { 'rect': [], 'via': [], 'prim_via': [], 'instance': [], 'template': [] } # Create an empty database that will store only the relevant layout objects self.loc = {} # Manage the tracks in a track manager self.tracks = TrackManager.from_routing_grid(self.grid) # Pull default layout parameters self.params = self.__class__.get_default_param_values() # Override defaults and add provided parameters for key in params: self.params[key] = params[key] # Set up properties for BAG black-boxing self.temp_boundary = Rectangle(xy=[[0, 0], [.1, .1]], layer='M1', virtual=True) # TODO: Make process agnostic self.prim_bound_box = None self.prim_top_layer = None """ REQUIRED METHODS """ """ You must implement these methods for BAG to work """ @classmethod @abc.abstractmethod def get_params_info(cls) -> dict: """ Return a dictionary describing all parameters in the layout generator """ return dict() @classmethod def get_default_param_values(cls) -> dict: """ Return a dictionary of all default parameter values """ return dict() def export_locations(self) -> dict: """ Returns a dictionary of shapes/inst in the layout. It is recommended to override this method and only return relevant shapes in a dict() with easily interpretable key names """ return self.loc @abc.abstractmethod def layout_procedure(self): """ Implement this method to describe how the layout is drawn """ pass """ DRAWING TOOLS """ """ Call these methods to craft your layout """ """ DO NOT OVERRIDE """ def add_rect(self, layer: Optional[Union[str, Tuple[str, str], List[str]]] = None, xy=None, virtual: bool = False, *, index: Optional[int] = None ) -> Rectangle: """ Instantiates a rectangle, adds the Rectangle object to local db, and returns it for further user manipulation Parameters ---------- layer : Optional[str] layer that the rectangle should be drawn on xy : Tuple[[float, float], [float, float]] list of xy coordinates representing the lower left and upper right corner of the rectangle. If None, select default size of 100nm by 100nm at origin virtual : bool If true, the rectangle object will be created but will not be drawn in the final layout. If false, the rectangle will be drawn as normal in the final layout index : Optional[int] If provided, will look up the layer name associated with the index, and then draw the rectangle on that layer. Returns ------- rect: Rectangle the created rectangle object """ # If a layer index is provided, reverse-lookup the layer name if index is not None: layer = self.grid.tech_info.get_layer_name(index) # Only try default sizing if the user does not provide xy coordinate if xy is None: layer_params = tech_info.tech_info['metal_tech']['metals'] # This variable holds the name of the metal to be looked up in the tech params layer_lookup = layer if isinstance(layer, list): layer_lookup = layer[0] # If we can find our layer in the metal tech params, use its default width if layer_lookup in layer_params: metal_params = tech_info.tech_info['metal_tech']['metals'][layer_lookup] default_w = metal_params['min_width'] # Otherwise just default to 100nm width else: default_w = 0.1 xy = [[0, 0], [default_w, default_w]] # Otherwise just directly create the rectangle according to the users specification self._db['rect'].append(Rectangle(xy, layer=layer, virtual=virtual)) return self._db['rect'][-1] def copy_rect(self, rect: Rectangle, layer=None, # type: Union[str, Tuple[str, str]] virtual: bool = False ) -> Rectangle: """ Creates a copy of the given rectangle and adds it to the local db Args: rect (Rectangle): rectangle object to be copied layer (str): layer that the copied rectangle should be drawn on. If None, the copied rectangle will use the same layer as the provided rectangle virtual (bool): If true, the rectangle object will be created but will not be drawn in the final layout. If false, the rectangle will be drawn as normal in the final layout Returns: (Rectangle): a new rectangle object copied from provided rectangle """ temp = rect.copy(layer=layer, virtual=virtual) self._db['rect'].append(temp) return self._db['rect'][-1] def add_track(self, name: str, dim: str, spacing: float, origin: float = 0) -> Track: """ Creates and returns a track object for alignment use Parameters ---------- name Name to use for the added track dim: 'x' for a horizontal track and 'y' for a vertical track spacing: number representing the space between tracks origin: coordinate for the 0th track Returns ------- Track: track object for user manipulation """ self.tracks.add_track(name=name, dim=dim, spacing=spacing, origin=origin) return self.tracks[name] def new_template(self, params: dict = None, temp_cls=None, debug: bool = False, **kwargs): """ Generates a layout master of specified class and parameter set Args: params (dict): dictionary of parameters to specify the layout to be created temp_cls: the layout generator class to be used debug (bool): True to print debug messages """ return TemplateBase.new_template(self, params=params, temp_cls=temp_cls, debug=debug, **kwargs) def add_instance(self, master, inst_name=None, loc=(0, 0), orient="R0", ) -> VirtualInst: """ Adds a single instance from a provided template master """ temp = VirtualInst(master, inst_name=inst_name) temp.shift_origin(loc, orient=orient) # Move virtual instance to desired location/orientation self._db['instance'].append(temp) # Add the instance to the list return temp def import_layout(self, libname: str, cellname: str, yaml_root: str = None, export_pins: bool = True ) -> 'LayoutAbstract': """ Creates an abstract layout master from the provided virtuoso libname and cellname. Adds pin shapes from the yaml root to the location dictionary for easy access. Parameters ---------- libname : str virtuoso design library name cellname : str virtuoso cell name; must be contained within the provided virtuoso design library yaml_root : str path to yaml file containing pin shapes. These shapes will be added to the location dictionary export_pins : bool if True, will draw all pin shapes provided in yaml root Returns ------- abstract_temp : LayoutAbstract A design master containing the generated layout_abstract """ params = { 'libname': libname, 'cellname': cellname, 'yaml_root': yaml_root, 'export_pins': export_pins } abstract_temp = self.new_template(params=params, temp_cls=LayoutAbstract) return abstract_temp def import_cadence_layout(self, libname: str, cellname: str ) -> 'AyarLayoutGenerator': """ This method will extract the layout specified by libname, cellname from Cadence, and return a new LayoutAbstract master that can be placed in the current db. This method will also analyze the layout for its pins and automatically add them to the location dictionary. Parameters ---------- libname : str Cadence library name where the cell will be located cellname : str Cadence cell name of the layout to be imported Returns ------- master : LayoutAbstract Newly created master that will contain the imported layout and pin information """ # Grab layout information from Cadence through SKILL interface temp_file_name = 'test.yaml' expr = 'parse_cad_layout( "%s" "%s" "%s" )' % (libname, cellname, temp_file_name) self.template_db._prj.impl_db._eval_skill(expr) # Grab the raw layout data, then delete the temp file afterward with open(temp_file_name, 'r') as f: data = yaml.load(f) os.remove(temp_file_name) # Create the new master return self.new_template(params={'libname': libname, 'cellname': cellname, 'data': data}, temp_cls=CadenceLayout) def connect_wires(self, rect1: Rectangle, rect2: Rectangle, size: Tuple[int, int] = (None, None), extend: bool = False, prim: bool = False ) -> Union[ViaStack, Via]: """ Creates a via stack between the two provided rectangles. This method requires that the provided rectangles overlap. No knowledge of which rectangle is higher/lower in the metal stack is needed. Parameters ---------- rect1: Rectangle first rectangle to be connected rect2: Rectangle second rectangle to be connected size: Tuple[int, int] number of vias to be placed in the x and y dimension respectively. If (None, None), vias will be placed to fill the enclosure extend: bool Represents whether the enclosure will be extended in the x or y direction to meet drc rules prim: bool if True, will attempt to create a single primitive via instead of a via stack """ # Only create a via if the two rectangles are actually on different layers # TODO: Add support for returning empty vias when the two layers are the same if rect1.layer != rect2.layer: if prim: temp = Via.from_metals(rect1, rect2, size=size) self._db['prim_via'].append(temp) else: temp = ViaStack(rect1, rect2, size=size, extend=extend) self._db['via'].append(temp) return temp def add_prim_via(self, via_id: str, rect: Rectangle, size: Tuple[int, int] = (None, None), ) -> Via: """ Creates a via stack between the two provided rectangles. This method requires that the provided rectangles overlap. No knowledge of which rectangle is higher/lower in the metal stack is needed. Parameters ---------- via_id: str id for the via to be drawn rect: Rectangle the rectangle bounding the region to draw the via size: Tuple[int, int] number of vias to be placed in the x and y dimension respectively. If (None, None), vias will be placed to fill the enclosure """ temp = Via(via_id, bbox=rect, size=size) self._db['prim_via'].append(temp) return temp def create_label(self, label, rect, purpose=None, show=True): if purpose is not None: self.add_rect([rect.layer, purpose], rect.xy) if show is True: TemplateBase.add_label(self, label, rect.layer, rect.to_bbox()) TemplateBase.add_pin_primitive(self, net_name=label, layer=rect.layer, bbox=rect.to_bbox(), show=False) """ INTERNAL METHODS """ """ DO NOT CALL OR OVERRIDE """ def draw_layout(self) -> None: """ Called by higher level BAG functions to create layout """ self.layout_procedure() # Perform the user determined layout process self._commit_shapes() # Take all of the created shapes and actually push them to the bag db def parse_yaml(self, pathname) -> dict: """ returns the parsed yaml file TODO: change the reference function to something not in bag.core """ return bag.core._parse_yaml_file(pathname) def _commit_shapes(self) -> None: """ Takes all shapes in local db and creates standard BAG equivalents """ self._commit_rect() self._commit_inst() self._commit_via() # Set the properties required for BAG primitive black boxing self.prim_bound_box = self.temp_boundary.to_bbox() self.prim_top_layer = self.grid.tech_info.get_layer_id(self.temp_boundary.layer) # for layer_num in range(1, self.prim_top_layer + 1): # self.mark_bbox_used(layer_num, self.prim_bound_box) def _commit_rect(self) -> None: """ Takes in all rectangles in the db and creates standard BAG equivalents """ for shape in self._db['rect']: self.temp_boundary = self.temp_boundary.get_enclosure(shape) if shape.virtual is False: TemplateBase.add_rect(self, shape.lpp, shape.to_bbox()) def _commit_inst(self) -> None: """ Takes in all inst in the db and creates standard BAG equivalents """ for inst in self._db['instance']: # Get the boundary of the instance try: bound = inst.master.temp_boundary.shift_origin(origin=inst.origin, orient=inst.orient) except AttributeError: # TODO: Get the size properly bound = Rectangle(xy=[[0, 0], [.1, .1]], layer='M1', virtual=True) self.temp_boundary = self.temp_boundary.get_enclosure(bound) TemplateBase.add_instance(self, inst.master, inst_name=inst.inst_name, loc=inst.origin, orient=inst.orient) def _commit_via(self) -> None: """ Takes in all vias in the db and creates standard BAG equivalents """ for via in self._db['via']: for connection in via.metal_pairs: TemplateBase.add_via(self, bbox=via.loc['overlap'].to_bbox(), bot_layer=connection[0], top_layer=connection[1], bot_dir=connection[2], extend=via.extend) for via in self._db['prim_via']: TemplateBase.add_via_primitive(self, via_type=via.via_id, loc=via.location, num_rows=via.num_rows, num_cols=via.num_cols, sp_rows=via.sp_rows, sp_cols=via.sp_cols, enc1=via.enc_bot, enc2=via.enc_top, orient=via.orient)