OpenRAM/compiler/modules/replica_bitcell_array.py

511 lines
25 KiB
Python

# See LICENSE for licensing information.
#
# Copyright (c) 2016-2019 Regents of the University of California
# All rights reserved.
#
import debug
import design
from tech import drc, spice, cell_properties
from vector import vector
from globals import OPTS
from sram_factory import factory
class replica_bitcell_array(design.design):
"""
Creates a bitcell arrow of cols x rows and then adds the replica
and dummy columns and rows. Replica columns are on the left and
right, respectively and connected to the given bitcell ports.
Dummy are the outside columns/rows with WL and BL tied to gnd.
Requires a regular bitcell array, replica bitcell, and dummy
bitcell (Bl/BR disconnected).
"""
def __init__(self, rows, cols, left_rbl, right_rbl, bitcell_ports, name, add_replica=True):
design.design.__init__(self, name)
debug.info(1, "Creating {0} {1} x {2}".format(self.name, rows, cols))
self.add_comment("rows: {0} cols: {1}".format(rows, cols))
self.column_size = cols
self.row_size = rows
self.left_rbl = left_rbl
self.right_rbl = right_rbl
self.bitcell_ports = bitcell_ports
# If set to false, we increase the height for the replica wordline row, but don't
# actually add the column to this array. This is so the height matches other
# banks that have the replica columns.
# Number of replica columns to actually add
if add_replica:
self.add_left_rbl = self.left_rbl
self.add_right_rbl = self.right_rbl
else:
self.add_left_rbl = 0
self.add_right_rbl = 0
debug.check(left_rbl + right_rbl == len(self.all_ports),
"Invalid number of RBLs for port configuration.")
debug.check(left_rbl + right_rbl == len(self.bitcell_ports),
"Bitcell ports must match total RBLs.")
# Two dummy rows plus replica even if we don't add the column
self.extra_rows = 2 + self.left_rbl + self.right_rbl
# Two dummy cols plus replica if we add the column
self.extra_cols = 2 + self.add_left_rbl + self.add_right_rbl
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
# We don't offset this because we need to align
# the replica bitcell in the control logic
# self.offset_all_coordinates()
def create_netlist(self):
""" Create and connect the netlist """
self.add_modules()
self.add_pins()
self.create_instances()
def add_modules(self):
""" Array and dummy/replica columns
d or D = dummy cell (caps to distinguish grouping)
r or R = replica cell (caps to distinguish grouping)
b or B = bitcell
replica columns 1
v v
bdDDDDDDDDDDDDDDdb <- Dummy row
bdDDDDDDDDDDDDDDrb <- Dummy row
br--------------rb
br| Array |rb
br| row x col |rb
br--------------rb
brDDDDDDDDDDDDDDdb <- Dummy row
bdDDDDDDDDDDDDDDdb <- Dummy row
^^^^^^^^^^^^^^^
dummy rows cols x 1
^ dummy columns ^
1 x (rows + 4)
"""
# Bitcell for port names only
self.cell = factory.create(module_type="bitcell")
# Bitcell array
self.bitcell_array = factory.create(module_type="bitcell_array",
column_offset=1 + self.add_left_rbl,
cols=self.column_size,
rows=self.row_size)
self.add_mod(self.bitcell_array)
# Replica bitlines
self.replica_columns = {}
for bit in range(self.add_left_rbl + self.add_right_rbl):
# Creating left_rbl
if bit<self.add_left_rbl:
replica_bit = bit + 1
# dummy column
column_offset = self.add_left_rbl - bit
# Creating right_rbl
else:
replica_bit = bit + self.row_size + 1
# dummy column + replica column + bitcell colums
column_offset = self.add_left_rbl - bit + self.row_size
self.replica_columns[bit] = factory.create(module_type="replica_column",
rows=self.row_size,
left_rbl=self.add_left_rbl,
right_rbl=self.add_right_rbl,
column_offset=column_offset,
replica_bit=replica_bit)
self.add_mod(self.replica_columns[bit])
# Dummy row
self.dummy_row = factory.create(module_type="dummy_array",
cols=self.column_size,
rows=1,
# dummy column + left replica column
column_offset=1 + self.add_left_rbl,
mirror=0)
self.add_mod(self.dummy_row)
# If there are bitcell end caps, replace the dummy cells on the edge of the bitcell array with end caps.
try:
end_caps_enabled = cell_properties.bitcell.end_caps
except AttributeError:
end_caps_enabled = False
# Dummy Row or Col Cap, depending on bitcell array properties
edge_row_module_type = ("col_cap_array" if end_caps_enabled else "dummy_array")
self.edge_row = factory.create(module_type=edge_row_module_type,
cols=self.column_size,
rows=1,
# dummy column + left replica column(s)
column_offset=1 + self.add_left_rbl,
mirror=0)
self.add_mod(self.edge_row)
# Dummy Col or Row Cap, depending on bitcell array properties
edge_col_module_type = ("row_cap_array" if end_caps_enabled else "dummy_array")
self.edge_col_left = factory.create(module_type=edge_col_module_type,
cols=1,
column_offset=0,
rows=self.row_size + self.extra_rows,
mirror=(self.left_rbl + 1) % 2)
self.add_mod(self.edge_col_left)
self.edge_col_right = factory.create(module_type=edge_col_module_type,
cols=1,
# dummy column
# + left replica column(s)
# + bitcell columns
# + right replica column(s)
column_offset = 1 + self.add_left_rbl + self.column_size + self.add_right_rbl,
rows=self.row_size + self.extra_rows,
mirror=(self.left_rbl + 1) %2)
self.add_mod(self.edge_col_right)
def add_pins(self):
self.bitcell_array_wl_names = self.bitcell_array.get_all_wordline_names()
self.bitcell_array_bl_names = self.bitcell_array.get_all_bitline_names()
# These are the non-indexed names
self.dummy_cell_wl_names = ["dummy_" + x for x in self.cell.get_all_wl_names()]
self.dummy_cell_bl_names = ["dummy_" + x for x in self.cell.get_all_bitline_names()]
self.dummy_row_bl_names = self.bitcell_array_bl_names
# A dictionary because some ports may have nothing
self.rbl_bl_names = {}
self.rbl_br_names = {}
self.rbl_wl_names = {}
# Create the full WL names include dummy, replica, and regular bit cells
self.replica_col_wl_names = []
self.replica_col_wl_names.extend(["{0}_bot".format(x) for x in self.dummy_cell_wl_names])
# Left port WLs (one dummy for each port when we allow >1 port)
for port in range(self.add_left_rbl):
# Make names for all RBLs
wl_names=["rbl_{0}_{1}".format(self.cell.get_wl_name(x), port) for x in range(len(self.cell.get_all_wl_names()))]
# Keep track of the pin that is the RBL
self.rbl_wl_names[port]=wl_names[self.bitcell_ports[port]]
self.replica_col_wl_names.extend(wl_names)
# Regular WLs
self.replica_col_wl_names.extend(self.bitcell_array_wl_names)
# Right port WLs (one dummy for each port when we allow >1 port)
for port in range(self.add_left_rbl, self.add_left_rbl + self.add_right_rbl):
# Make names for all RBLs
wl_names=["rbl_{0}_{1}".format(self.cell.get_wl_name(x), port) for x in range(len(self.cell.get_all_wl_names()))]
# Keep track of the pin that is the RBL
self.rbl_wl_names[port]=wl_names[self.bitcell_ports[port]]
self.replica_col_wl_names.extend(wl_names)
self.replica_col_wl_names.extend(["{0}_top".format(x) for x in self.dummy_cell_wl_names])
# Create the full WL names include dummy, replica, and regular bit cells
# Left/right dummy columns are connected identically to the replica column
self.dummy_col_wl_names = []
self.dummy_col_wl_names.extend(["{0}_bot".format(x) for x in self.dummy_cell_wl_names])
# Left port WLs (one dummy for each port when we allow >1 port)
for port in range(self.left_rbl):
# Make names for all RBLs
wl_names=["rbl_{0}_{1}".format(self.cell.get_wl_name(x), port) for x in range(len(self.cell.get_all_wl_names()))]
# Keep track of the pin that is the RBL
self.rbl_wl_names[port]=wl_names[self.bitcell_ports[port]]
self.dummy_col_wl_names.extend(wl_names)
# Regular WLs
self.dummy_col_wl_names.extend(self.bitcell_array_wl_names)
# Right port WLs (one dummy for each port when we allow >1 port)
for port in range(self.left_rbl, self.left_rbl + self.right_rbl):
# Make names for all RBLs
wl_names=["rbl_{0}_{1}".format(self.cell.get_wl_name(x), port) for x in range(len(self.cell.get_all_wl_names()))]
# Keep track of the pin that is the RBL
self.rbl_wl_names[port]=wl_names[self.bitcell_ports[port]]
self.dummy_col_wl_names.extend(wl_names)
self.dummy_col_wl_names.extend(["{0}_top".format(x) for x in self.dummy_cell_wl_names])
# Per port bitline names
self.replica_bl_names = {}
self.replica_wl_names = {}
# Array of all port bitline names
for port in range(self.add_left_rbl + self.add_right_rbl):
left_names=["rbl_{0}_{1}".format(self.cell.get_bl_name(x), port) for x in range(len(self.all_ports))]
right_names=["rbl_{0}_{1}".format(self.cell.get_br_name(x), port) for x in range(len(self.all_ports))]
# Keep track of the left pins that are the RBL
self.rbl_bl_names[port]=left_names[self.bitcell_ports[port]]
self.rbl_br_names[port]=right_names[self.bitcell_ports[port]]
# Interleave the left and right lists
bl_names = [x for t in zip(left_names, right_names) for x in t]
self.replica_bl_names[port] = bl_names
# Array of all port wl names
for port in range(self.left_rbl + self.right_rbl):
wl_names = ["rbl_{0}_{1}".format(x, port) for x in self.cell.get_all_wl_names()]
self.replica_wl_names[port] = wl_names
# External pins
self.add_pin_list(self.bitcell_array_bl_names, "INOUT")
# Need to sort by port order since dictionary values may not be in order
bl_names = [self.rbl_bl_names[x] for x in sorted(self.rbl_bl_names.keys())]
br_names = [self.rbl_br_names[x] for x in sorted(self.rbl_br_names.keys())]
for (bl_name, br_name) in zip(bl_names, br_names):
self.add_pin(bl_name, "OUTPUT")
self.add_pin(br_name, "OUTPUT")
self.add_pin_list(self.bitcell_array_wl_names, "INPUT")
# Need to sort by port order since dictionary values may not be in order
wl_names = [self.rbl_wl_names[x] for x in sorted(self.rbl_wl_names.keys())]
for pin_name in wl_names:
self.add_pin(pin_name, "INPUT")
self.add_pin("vdd", "POWER")
self.add_pin("gnd", "GROUND")
def create_instances(self):
""" Create the module instances used in this design """
supplies = ["vdd", "gnd"]
# Used for names/dimensions only
self.cell = factory.create(module_type="bitcell")
# Main array
self.bitcell_array_inst=self.add_inst(name="bitcell_array",
mod=self.bitcell_array)
self.connect_inst(self.bitcell_array_bl_names + self.bitcell_array_wl_names + supplies)
# Replica columns
self.replica_col_inst = {}
for port in range(self.add_left_rbl + self.add_right_rbl):
self.replica_col_inst[port]=self.add_inst(name="replica_col_{}".format(port),
mod=self.replica_columns[port])
self.connect_inst(self.replica_bl_names[port] + self.replica_col_wl_names + supplies)
# Dummy rows under the bitcell array (connected with with the replica cell wl)
self.dummy_row_replica_inst = {}
# Note, this is the number of left and right even if we aren't adding the columns to this bitcell array!
for port in range(self.left_rbl + self.right_rbl):
self.dummy_row_replica_inst[port]=self.add_inst(name="dummy_row_{}".format(port),
mod=self.dummy_row)
self.connect_inst(self.dummy_row_bl_names + self.replica_wl_names[port] + supplies)
# Top/bottom dummy rows or col caps
self.dummy_row_bot_inst=self.add_inst(name="dummy_row_bot",
mod=self.edge_row)
self.connect_inst(self.dummy_row_bl_names + [x + "_bot" for x in self.dummy_cell_wl_names] + supplies)
self.dummy_row_top_inst=self.add_inst(name="dummy_row_top",
mod=self.edge_row)
self.connect_inst(self.dummy_row_bl_names + [x + "_top" for x in self.dummy_cell_wl_names] + supplies)
# Left/right Dummy columns
self.dummy_col_left_inst=self.add_inst(name="dummy_col_left",
mod=self.edge_col_left)
self.connect_inst([x + "_left" for x in self.dummy_cell_bl_names] + self.dummy_col_wl_names + supplies)
self.dummy_col_right_inst=self.add_inst(name="dummy_col_right",
mod=self.edge_col_right)
self.connect_inst([x + "_right" for x in self.dummy_cell_bl_names] + self.dummy_col_wl_names + supplies)
def create_layout(self):
self.height = (self.row_size + self.extra_rows) * self.dummy_row.height
self.width = (self.column_size + self.extra_cols) * self.cell.width
# This is a bitcell x bitcell offset to scale
self.bitcell_offset = vector(self.cell.width, self.cell.height)
# Everything is computed with the main array at (0, 0) to start
self.bitcell_array_inst.place(offset=[0, 0])
self.add_replica_columns()
self.add_end_caps()
# Array was at (0, 0) but move everything so it is at the lower left
# We move DOWN the number of left RBL even if we didn't add the column to this bitcell array
self.translate_all(self.bitcell_offset.scale(-1 - self.add_left_rbl, -1 - self.left_rbl))
self.add_layout_pins()
self.add_boundary()
self.DRC_LVS()
def add_replica_columns(self):
""" Add replica columns on left and right of array """
# To the left of the bitcell array
for bit in range(self.add_left_rbl):
self.replica_col_inst[bit].place(offset=self.bitcell_offset.scale(-bit - 1, -self.add_left_rbl - 1))
# To the right of the bitcell array
for bit in range(self.add_right_rbl):
self.replica_col_inst[self.add_left_rbl + bit].place(offset=self.bitcell_offset.scale(bit, -self.add_left_rbl - 1) + self.bitcell_array_inst.lr())
# Replica dummy rows
# Add the dummy rows even if we aren't adding the replica column to this bitcell array
for bit in range(self.left_rbl):
self.dummy_row_replica_inst[bit].place(offset=self.bitcell_offset.scale(0, -bit - bit % 2),
mirror="R0" if bit % 2 else "MX")
for bit in range(self.right_rbl):
self.dummy_row_replica_inst[self.left_rbl + bit].place(offset=self.bitcell_offset.scale(0, bit + bit % 2) + self.bitcell_array_inst.ul(),
mirror="MX" if bit % 2 else "R0")
def add_end_caps(self):
""" Add dummy cells or end caps around the array """
# FIXME: These depend on the array size itself
# Far top dummy row (first row above array is NOT flipped)
flip_dummy = self.right_rbl % 2
dummy_row_offset = self.bitcell_offset.scale(0, self.right_rbl + flip_dummy) + self.bitcell_array_inst.ul()
self.dummy_row_top_inst.place(offset=dummy_row_offset,
mirror="MX" if flip_dummy else "R0")
# FIXME: These depend on the array size itself
# Far bottom dummy row (first row below array IS flipped)
flip_dummy = (self.left_rbl + 1) % 2
dummy_row_offset = self.bitcell_offset.scale(0, -self.left_rbl - 1 + flip_dummy)
self.dummy_row_bot_inst.place(offset=dummy_row_offset,
mirror="MX" if flip_dummy else "R0")
# Far left dummy col
# Shifted down by the number of left RBLs even if we aren't adding replica column to this bitcell array
dummy_col_offset = self.bitcell_offset.scale(-self.add_left_rbl - 1, -self.left_rbl - 1)
self.dummy_col_left_inst.place(offset=dummy_col_offset)
# Far right dummy col
# Shifted down by the number of left RBLs even if we aren't adding replica column to this bitcell array
dummy_col_offset = self.bitcell_offset.scale(self.add_right_rbl, -self.left_rbl - 1) + self.bitcell_array_inst.lr()
self.dummy_col_right_inst.place(offset=dummy_col_offset)
def add_layout_pins(self):
""" Add the layout pins """
# Main array wl and bl/br
pin_names = self.bitcell_array.get_pin_names()
for pin_name in pin_names:
for wl in self.bitcell_array_wl_names:
if wl in pin_name:
pin_list = self.bitcell_array_inst.get_pins(pin_name)
for pin in pin_list:
self.add_layout_pin(text=pin_name,
layer=pin.layer,
offset=pin.ll().scale(0, 1),
width=self.width,
height=pin.height())
for bitline in self.bitcell_array_bl_names:
if bitline in pin_name:
pin_list = self.bitcell_array_inst.get_pins(pin_name)
for pin in pin_list:
self.add_layout_pin(text=pin_name,
layer=pin.layer,
offset=pin.ll().scale(1, 0),
width=pin.width(),
height=self.height)
# Replica wordlines
for port in range(self.add_left_rbl + self.add_right_rbl):
inst = self.replica_col_inst[port]
for (pin_name, wl_name) in zip(self.cell.get_all_wl_names(), self.replica_wl_names[port]):
# +1 for dummy row
pin_bit = port + 1
# +row_size if above the array
if port>=self.add_left_rbl:
pin_bit += self.row_size
pin_name += "_{}".format(pin_bit)
pin = inst.get_pin(pin_name)
if wl_name in self.rbl_wl_names.values():
self.add_layout_pin(text=wl_name,
layer=pin.layer,
offset=pin.ll().scale(0, 1),
width=self.width,
height=pin.height())
# Replica bitlines
for port in range(self.add_left_rbl + self.add_right_rbl):
inst = self.replica_col_inst[port]
for (pin_name, bl_name) in zip(self.cell.get_all_bitline_names(), self.replica_bl_names[port]):
pin = inst.get_pin(pin_name)
if bl_name in self.rbl_bl_names or bl_name in self.rbl_br_names:
name = bl_name
else:
name = "rbl_{0}_{1}".format(pin_name, port)
self.add_layout_pin(text=name,
layer=pin.layer,
offset=pin.ll().scale(1, 0),
width=pin.width(),
height=self.height)
# vdd/gnd are only connected in the perimeter cells
# replica column should only have a vdd/gnd in the dummy cell on top/bottom
supply_insts = [self.dummy_col_left_inst, self.dummy_col_right_inst,
self.dummy_row_top_inst, self.dummy_row_bot_inst]
for pin_name in ["vdd", "gnd"]:
for inst in supply_insts:
pin_list = inst.get_pins(pin_name)
for pin in pin_list:
self.add_power_pin(name=pin_name,
loc=pin.center(),
directions=("V", "V"),
start_layer=pin.layer)
for inst in list(self.replica_col_inst.values()):
self.copy_layout_pin(inst, pin_name)
self.copy_layout_pin(inst, pin_name)
def get_rbl_wl_name(self, port):
""" Return the WL for the given RBL port """
return self.rbl_wl_names[port]
def get_rbl_bl_name(self, port):
""" Return the BL for the given RBL port """
return self.rbl_bl_names[port]
def get_rbl_br_name(self, port):
""" Return the BR for the given RBL port """
return self.rbl_br_names[port]
def analytical_power(self, corner, load):
"""Power of Bitcell array and bitline in nW."""
# Dynamic Power from Bitline
bl_wire = self.gen_bl_wire()
cell_load = 2 * bl_wire.return_input_cap()
bl_swing = OPTS.rbl_delay_percentage
freq = spice["default_event_frequency"]
bitline_dynamic = self.calc_dynamic_power(corner, cell_load, freq, swing=bl_swing)
# Calculate the bitcell power which currently only includes leakage
cell_power = self.cell.analytical_power(corner, load)
# Leakage power grows with entire array and bitlines.
total_power = self.return_power(cell_power.dynamic + bitline_dynamic * self.column_size,
cell_power.leakage * self.column_size * self.row_size)
return total_power
def gen_bl_wire(self):
if OPTS.netlist_only:
height = 0
else:
height = self.height
bl_pos = 0
bl_wire = self.generate_rc_net(int(self.row_size - bl_pos), height, drc("minwidth_m1"))
bl_wire.wire_c =spice["min_tx_drain_c"] + bl_wire.wire_c # 1 access tx d/s per cell
return bl_wire
def get_wordline_cin(self):
"""Get the relative input capacitance from the wordline connections in all the bitcell"""
# A single wordline is connected to all the bitcells in a single row meaning the capacitance depends on the # of columns
bitcell_wl_cin = self.cell.get_wl_cin()
total_cin = bitcell_wl_cin * self.column_size
return total_cin
def graph_exclude_bits(self, targ_row, targ_col):
"""Excludes bits in column from being added to graph except target"""
self.bitcell_array.graph_exclude_bits(targ_row, targ_col)
def graph_exclude_replica_col_bits(self):
"""Exclude all replica/dummy cells in the replica columns except the replica bit."""
for port in range(self.left_rbl + self.right_rbl):
self.replica_columns[port].exclude_all_but_replica()
def get_cell_name(self, inst_name, row, col):
"""Gets the spice name of the target bitcell."""
return self.bitcell_array.get_cell_name(inst_name + '.x' + self.bitcell_array_inst.name, row, col)