# See LICENSE for licensing information. # # Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz # All rights reserved. # from openram import debug from openram.base.geometry import instance,geometry from typing import List from typing import Optional from openram.base import design from openram.globals import OPTS from math import ceil, floor from copy import deepcopy class pattern(): """ This class is used to desribe the internals of a bitcell array. It describes instance modules, rotation, and ordering """ block = List[List[instance]] def __init__(self, parent_design: design, name:str, core_block:block, num_rows:int, num_cols:int, name_template, num_cores_x: Optional[int] = 0, num_cores_y: Optional[int] = 0, cores_per_x_block: int = 1, cores_per_y_block: int = 1, x_block: Optional[block] = None, y_block: Optional[block] = None, xy_block: Optional[block] = None, initial_x_block:bool = False, initial_y_block:bool = False, final_x_block:bool = False, final_y_block:bool = False, ): """ a "block" is a 2d list of instances core_block defines the main block that is tiled num_core defines the number of times the core block is to be tiled (i.e. bitcells in dimension / bitcells in core_block) x_block defines a block that is inserted to the right of the core_block y_block defines a block that is inserted below the core block xy_block defines a block that is a inserted where the x_block and y_block intercept the initial and final booleans determine whether the pattern begins and/or ends with x/y blocks """ self.parent_design = parent_design self.name = name self.core_block = core_block self.num_rows = num_rows self.num_cols = num_cols self.name_template = name_template self.num_cores_x = num_cores_x self.num_cores_y = num_cores_y if num_cores_x == 0: self.num_cores_x = ceil(num_cols/len(core_block[0])) if num_cores_y == 0: self.num_cores_y = ceil(num_rows/len(core_block)) self.cores_per_x_block = cores_per_x_block self.cores_per_y_block = cores_per_y_block self.x_block = x_block self.y_block = y_block self.xy_block = xy_block self.initial_x_block = initial_x_block self.initial_y_block = initial_y_block self.final_x_block = final_x_block self.final_y_block = final_y_block self.bits_per_row = ceil(self.num_rows/self.num_cores_x) self.bits_per_col = ceil(self.num_cols/self.num_cores_y) self.bit_rows = [] self.bit_cols = [] self.parent_design.all_inst = {} if not OPTS.netlist_only: self.verify_interblock_dimensions() def compute_and_verify_intrablock_dimensions(self, block: block) -> List[int]: for row in block: row_height = row[0].height for inst in row: debug.check(row_height == inst.height, "intrablock instances within the same row are different heights") for y in range(len(block[0])): debug.check(all([row[y].width for row in block]), "intrablock instances within the same column are different widths") block_width = sum([instance.width for instance in block[0]]) block_height = sum([row[0].height for row in block]) return [block_width, block_height] def verify_interblock_dimensions(self) -> None: """ Ensure the individual blocks are valid and interblock dimensions are valid """ debug.check(len(self.core_block) >= 1, "invalid core_block dimension: core_block rows must be >=1") debug.check(len(self.core_block[0]) >= 1, "invalid core_block dimension: core_block cols must be >=1") if self.x_block and self.y_block: debug.check(self.xy_block is not None, "must have xy_block if both x_block and y_block are provided") (self.core_block_width, self.core_block_height) = self.compute_and_verify_intrablock_dimensions(self.core_block) if self.x_block: (self.x_block_width, self.x_block_height) = self.compute_and_verify_intrablock_dimensions(self.x_block) if self.y_block: (self.y_block_width, self.y_block_height) = self.compute_and_verify_intrablock_dimensions(self.y_block) if self.xy_block: (self.xy_block_width, self.xy_block_height) = self.compute_and_verify_intrablock_dimensions(self.xy_block) if(self.x_block): debug.check(self.core_block_width * self.cores_per_x_block == self.x_block_width, "core_block does not align with x_block") if(self.y_block): debug.check(self.core_block_height * self.cores_per_y_block == self.y_block_height, "core_block does not aligns with y_block") if(self.xy_block): debug.check(self.xy_block_height == self.x_block_height, "xy_block does not align with x_block") debug.check(self.xy_block_width == self.y_block_width, "xy_block does not align with y_block") def connect_block(self, block: block, col: int, row: int): for dr in range(len(block)): row_done = False for dc in range(len(block[0])): if(self.bit_rows.count(self.num_rows) <= self.num_cols and self.bit_cols.count(self.num_cols) <= self.num_rows): inst = block[dr][dc] if(len(self.bit_rows) <= col + dc): self.bit_rows.append(0) if(len(self.bit_cols) <= row + dr): self.bit_cols.append(0) if(row_done or self.bit_cols[row+dr] >= self.num_cols): row_done = True continue if((self.bit_rows[col+dc] < self.num_rows) and (self.bit_cols[row+dr] < self.num_cols)): if(inst.is_bitcell): self.parent_design.cell_inst[self.bit_rows[col+dc], self.bit_cols[row+dr]] = self.parent_design.add_existing_inst(inst,self.name_template.format(row +dr, col+dc)) self.parent_design.all_inst[row + dr, col + dc] = self.parent_design.cell_inst[self.bit_rows[col+dc], self.bit_cols[row+dr]] self.parent_design.connect_inst(self.parent_design.get_bitcell_pins(self.bit_rows[col+dc], self.bit_cols[row+dr])) self.bit_rows[col+dc] += 1 self.bit_cols[row+dr] += 1 else: self.parent_design.all_inst[row + dr, col + dc] = self.parent_design.add_existing_inst(inst,self.name_template.format(row +dr, col+dc)) self.parent_design.connect_inst(self.parent_design.get_strap_pins(self.bit_rows[col+dc], self.bit_cols[row+dr])) else: row_done = True def connect_array(self) -> None: row = 0 col = 0 for i in range(self.num_cores_y): for j in range (self.num_cores_x): self.connect_block(self.core_block, col, row) col += len(self.core_block[0]) col = 0 row += len(self.core_block) def connect_array_raw(self) -> None: for row in range(self.num_rows): for col in range(self.num_cols): inst = self.core_block[row][col] if(len(self.bit_rows) <= col): self.bit_rows.append(0) if(len(self.bit_cols) <= row): self.bit_cols.append(0) if(inst.is_bitcell): self.parent_design.cell_inst[self.bit_rows[col], self.bit_cols[row]] = self.parent_design.add_existing_inst(inst,self.name_template.format(row, col)) self.parent_design.all_inst[row, col] = self.parent_design.cell_inst[self.bit_rows[col], self.bit_cols[row]] self.parent_design.connect_inst(self.parent_design.get_bitcell_pins(self.bit_rows[col], self.bit_cols[row])) self.bit_rows[col] += 1 self.bit_cols[row] += 1 else: self.parent_design.all_inst[row, col] = self.parent_design.add_existing_inst(inst,self.name_template.format(row, col)) self.parent_design.connect_inst(self.parent_design.get_strap_pins(self.bit_rows[col], self.bit_cols[row])) def place_inst(self, inst, offset) -> None: x = offset[0] y = offset[1] if "X" in inst.mirror: y += inst.height if "Y" in inst.mirror: x += inst.width #print('placing inst {} at {}'.format(inst, offset)) inst.place((x, y), inst.mirror, inst.rotate) def place_array(self): (self.row_max, self.col_max) = list(self.parent_design.all_inst.keys())[-1] y = 0 for row in range(self.row_max+1): x = 0 for col in range(self.col_max+1): inst = self.parent_design.all_inst[row, col] self.place_inst(inst, (x, y)) x += inst.width y += inst.height self.parent_design.width = max([x.rx() for x in self.parent_design.insts]) self.parent_design.height = max([x.uy() for x in self.parent_design.insts]) def append_row_to_block(block, row): block.append(row) def append_block_under_block(base_block, under_block): base_block = base_block + under_block def append_block_right_block(base_block, right_block): for row in base_block: row = row + right_block def rotate_list(lst, n): # Loop through the range of n positions to rotate for i in range(n): # Remove the last element of the list and insert it at the beginning lst.insert(0, lst.pop()) # Return the rotated list return lst