mirror of https://github.com/VLSIDA/OpenRAM.git
1395 lines
63 KiB
Python
1395 lines
63 KiB
Python
# See LICENSE for licensing information.
|
|
#
|
|
# Copyright (c) 2016-2023 Regents of the University of California and The Board
|
|
# of Regents for the Oklahoma Agricultural and Mechanical College
|
|
# (acting for and on behalf of Oklahoma State University)
|
|
# All rights reserved.
|
|
#
|
|
import datetime
|
|
from math import ceil
|
|
from importlib import import_module, reload
|
|
from openram import debug
|
|
from openram.base import vector
|
|
from openram.base import channel_route
|
|
from openram.base import design
|
|
from openram.base import verilog
|
|
from openram.base import lef
|
|
from openram.router import router_tech
|
|
from openram.sram_factory import factory
|
|
from openram.tech import spice
|
|
from openram import OPTS, print_time
|
|
|
|
|
|
class sram_1bank(design, verilog, lef):
|
|
"""
|
|
Procedures specific to a one bank SRAM.
|
|
"""
|
|
def __init__(self, name, sram_config):
|
|
design.__init__(self, name)
|
|
lef.__init__(self, ["m1", "m2", "m3", "m4"])
|
|
verilog.__init__(self)
|
|
self.sram_config = sram_config
|
|
sram_config.set_local_config(self)
|
|
|
|
self.bank_insts = []
|
|
|
|
if self.write_size != self.word_size:
|
|
self.num_wmasks = int(ceil(self.word_size / self.write_size))
|
|
else:
|
|
self.num_wmasks = 0
|
|
|
|
if not self.num_spare_cols:
|
|
self.num_spare_cols = 0
|
|
|
|
try:
|
|
from openram.tech import power_grid
|
|
self.supply_stack = power_grid
|
|
except ImportError:
|
|
# if no power_grid is specified by tech we use sensible defaults
|
|
# Route a M3/M4 grid
|
|
self.supply_stack = self.m3_stack
|
|
|
|
# delay control logic does not have RBLs
|
|
self.has_rbl = OPTS.control_logic != "control_logic_delay"
|
|
|
|
def add_pins(self):
|
|
""" Add pins for entire SRAM. """
|
|
|
|
for port in self.write_ports:
|
|
for bit in range(self.word_size + self.num_spare_cols):
|
|
self.add_pin("din{0}[{1}]".format(port, bit), "INPUT")
|
|
for port in self.all_ports:
|
|
for bit in range(self.bank_addr_size):
|
|
self.add_pin("addr{0}[{1}]".format(port, bit), "INPUT")
|
|
|
|
# These are used to create the physical pins
|
|
self.control_logic_inputs = []
|
|
self.control_logic_outputs = []
|
|
for port in self.all_ports:
|
|
if port in self.readwrite_ports:
|
|
self.control_logic_inputs.append(self.control_logic_rw.get_inputs())
|
|
self.control_logic_outputs.append(self.control_logic_rw.get_outputs())
|
|
elif port in self.write_ports:
|
|
self.control_logic_inputs.append(self.control_logic_w.get_inputs())
|
|
self.control_logic_outputs.append(self.control_logic_w.get_outputs())
|
|
else:
|
|
self.control_logic_inputs.append(self.control_logic_r.get_inputs())
|
|
self.control_logic_outputs.append(self.control_logic_r.get_outputs())
|
|
|
|
for port in self.all_ports:
|
|
self.add_pin("csb{}".format(port), "INPUT")
|
|
for port in self.readwrite_ports:
|
|
self.add_pin("web{}".format(port), "INPUT")
|
|
for port in self.all_ports:
|
|
self.add_pin("clk{}".format(port), "INPUT")
|
|
# add the optional write mask pins
|
|
for port in self.write_ports:
|
|
for bit in range(self.num_wmasks):
|
|
self.add_pin("wmask{0}[{1}]".format(port, bit), "INPUT")
|
|
if self.num_spare_cols == 1:
|
|
self.add_pin("spare_wen{0}".format(port), "INPUT")
|
|
else:
|
|
for bit in range(self.num_spare_cols):
|
|
self.add_pin("spare_wen{0}[{1}]".format(port, bit), "INPUT")
|
|
for port in self.read_ports:
|
|
for bit in range(self.word_size + self.num_spare_cols):
|
|
self.add_pin("dout{0}[{1}]".format(port, bit), "OUTPUT")
|
|
|
|
# Standard supply and ground names
|
|
try:
|
|
self.vdd_name = spice["power"]
|
|
except KeyError:
|
|
self.vdd_name = "vdd"
|
|
try:
|
|
self.gnd_name = spice["ground"]
|
|
except KeyError:
|
|
self.gnd_name = "gnd"
|
|
|
|
self.add_pin(self.vdd_name, "POWER")
|
|
self.add_pin(self.gnd_name, "GROUND")
|
|
self.ext_supplies = [self.vdd_name, self.gnd_name]
|
|
self.ext_supply = {"vdd" : self.vdd_name, "gnd" : self.gnd_name}
|
|
|
|
def add_global_pex_labels(self):
|
|
"""
|
|
Add pex labels at the sram level for spice analysis
|
|
"""
|
|
|
|
|
|
|
|
# add pex labels for bitcells
|
|
for bank_num in range(len(self.bank_insts)):
|
|
bank = self.bank_insts[bank_num]
|
|
pex_data = bank.reverse_transformation_bitcell(self.bitcell.name)
|
|
|
|
bank_offset = pex_data[0] # offset bank relative to sram
|
|
Q_offset = pex_data[1] # offset of storage relative to bank
|
|
Q_bar_offset = pex_data[2] # offset of storage relative to bank
|
|
bl_offsets = pex_data[3]
|
|
br_offsets = pex_data[4]
|
|
bl_meta = pex_data[5]
|
|
br_meta = pex_data[6]
|
|
|
|
bl = []
|
|
br = []
|
|
|
|
storage_layer_name = "m1"
|
|
bitline_layer_name = self.bitcell.get_pin("bl").layer
|
|
|
|
for cell in range(len(bank_offset)):
|
|
Q = [bank_offset[cell][0] + Q_offset[cell][0],
|
|
bank_offset[cell][1] + Q_offset[cell][1]]
|
|
Q_bar = [bank_offset[cell][0] + Q_bar_offset[cell][0],
|
|
bank_offset[cell][1] + Q_bar_offset[cell][1]]
|
|
OPTS.words_per_row = self.words_per_row
|
|
row = int(cell % (OPTS.num_words / self.words_per_row))
|
|
col = int(cell / (OPTS.num_words))
|
|
self.add_layout_pin_rect_center("bitcell_Q_b{}_r{}_c{}".format(bank_num,
|
|
row,
|
|
col),
|
|
storage_layer_name,
|
|
Q)
|
|
self.add_layout_pin_rect_center("bitcell_Q_bar_b{}_r{}_c{}".format(bank_num,
|
|
row,
|
|
col),
|
|
storage_layer_name,
|
|
Q_bar)
|
|
|
|
for cell in range(len(bl_offsets)):
|
|
col = bl_meta[cell][0][2]
|
|
for bitline in range(len(bl_offsets[cell])):
|
|
bitline_location = [float(bank_offset[cell][0]) + bl_offsets[cell][bitline][0],
|
|
float(bank_offset[cell][1]) + bl_offsets[cell][bitline][1]]
|
|
bl.append([bitline_location, bl_meta[cell][bitline][3], col])
|
|
|
|
for cell in range(len(br_offsets)):
|
|
col = br_meta[cell][0][2]
|
|
for bitline in range(len(br_offsets[cell])):
|
|
bitline_location = [float(bank_offset[cell][0]) + br_offsets[cell][bitline][0],
|
|
float(bank_offset[cell][1]) + br_offsets[cell][bitline][1]]
|
|
br.append([bitline_location, br_meta[cell][bitline][3], col])
|
|
|
|
for i in range(len(bl)):
|
|
self.add_layout_pin_rect_center("bl{0}_{1}".format(bl[i][1], bl[i][2]),
|
|
bitline_layer_name, bl[i][0])
|
|
|
|
for i in range(len(br)):
|
|
self.add_layout_pin_rect_center("br{0}_{1}".format(br[i][1], br[i][2]),
|
|
bitline_layer_name, br[i][0])
|
|
|
|
# add pex labels for control logic
|
|
for i in range(len(self.control_logic_insts)):
|
|
instance = self.control_logic_insts[i]
|
|
control_logic_offset = instance.offset
|
|
for output in instance.mod.output_list:
|
|
pin = instance.mod.get_pin(output)
|
|
pin.transform([0, 0], instance.mirror, instance.rotate)
|
|
offset = [control_logic_offset[0] + pin.center()[0],
|
|
control_logic_offset[1] + pin.center()[1]]
|
|
self.add_layout_pin_rect_center("{0}{1}".format(pin.name, i),
|
|
storage_layer_name,
|
|
offset)
|
|
|
|
def create_netlist(self):
|
|
""" Netlist creation """
|
|
|
|
start_time = datetime.datetime.now()
|
|
|
|
# Must create the control logic before pins to get the pins
|
|
self.add_modules()
|
|
self.add_pins()
|
|
self.create_modules()
|
|
|
|
# This is for the lib file if we don't create layout
|
|
self.width=0
|
|
self.height=0
|
|
|
|
if not OPTS.is_unit_test:
|
|
print_time("Submodules", datetime.datetime.now(), start_time)
|
|
|
|
def create_layout(self):
|
|
""" Layout creation """
|
|
start_time = datetime.datetime.now()
|
|
self.place_instances()
|
|
if not OPTS.is_unit_test:
|
|
print_time("Placement", datetime.datetime.now(), start_time)
|
|
|
|
start_time = datetime.datetime.now()
|
|
self.route_layout()
|
|
|
|
if not OPTS.is_unit_test:
|
|
print_time("Routing", datetime.datetime.now(), start_time)
|
|
|
|
self.add_lvs_correspondence_points()
|
|
|
|
self.offset_all_coordinates()
|
|
|
|
highest_coord = self.find_highest_coords()
|
|
self.width = highest_coord[0]
|
|
self.height = highest_coord[1]
|
|
if OPTS.use_pex and OPTS.pex_exe[0] != "calibre":
|
|
debug.info(2, "adding global pex labels")
|
|
self.add_global_pex_labels()
|
|
self.add_boundary(ll=vector(0, 0),
|
|
ur=vector(self.width, self.height))
|
|
|
|
start_time = datetime.datetime.now()
|
|
if not OPTS.is_unit_test:
|
|
# We only enable final verification if we have routed the design
|
|
# Only run this if not a unit test, because unit test will also verify it.
|
|
self.DRC_LVS(final_verification=OPTS.route_supplies, force_check=OPTS.check_lvsdrc)
|
|
print_time("Verification", datetime.datetime.now(), start_time)
|
|
|
|
def create_modules(self):
|
|
debug.error("Must override pure virtual function.", -1)
|
|
|
|
def route_supplies(self, bbox=None):
|
|
""" Route the supply grid and connect the pins to them. """
|
|
|
|
# Copy the pins to the top level
|
|
# This will either be used to route or left unconnected.
|
|
for pin_name in ["vdd", "gnd"]:
|
|
for inst in self.insts:
|
|
self.copy_power_pins(inst, pin_name, self.ext_supply[pin_name])
|
|
|
|
if not OPTS.route_supplies:
|
|
# Do not route the power supply (leave as must-connect pins)
|
|
return
|
|
elif OPTS.route_supplies == "grid":
|
|
from openram.router import supply_grid_router as router
|
|
elif OPTS.route_supplies == "tree":
|
|
from openram.router import supply_tree_router as router
|
|
else:
|
|
from openram.router import graph_router as router
|
|
rtr=router(layers=self.supply_stack,
|
|
design=self,
|
|
bbox=bbox,
|
|
pin_type=OPTS.supply_pin_type)
|
|
|
|
rtr.route()
|
|
|
|
if OPTS.supply_pin_type in ["left", "right", "top", "bottom", "ring"]:
|
|
# Find the lowest leftest pin for vdd and gnd
|
|
for pin_name in ["vdd", "gnd"]:
|
|
# Copy the pin shape(s) to rectangles
|
|
for pin in self.get_pins(pin_name):
|
|
self.add_rect(pin.layer,
|
|
pin.ll(),
|
|
pin.width(),
|
|
pin.height())
|
|
|
|
# Remove the pin shape(s)
|
|
self.remove_layout_pin(pin_name)
|
|
|
|
# Get new pins
|
|
pins = rtr.get_new_pins(pin_name)
|
|
for pin in pins:
|
|
self.add_layout_pin(self.ext_supply[pin_name],
|
|
pin.layer,
|
|
pin.ll(),
|
|
pin.width(),
|
|
pin.height())
|
|
|
|
elif OPTS.route_supplies and OPTS.supply_pin_type == "single":
|
|
# Update these as we may have routed outside the region (perimeter pins)
|
|
lowest_coord = self.find_lowest_coords()
|
|
|
|
# Find the lowest leftest pin for vdd and gnd
|
|
for pin_name in ["vdd", "gnd"]:
|
|
# Copy the pin shape(s) to rectangles
|
|
for pin in self.get_pins(pin_name):
|
|
self.add_rect(pin.layer,
|
|
pin.ll(),
|
|
pin.width(),
|
|
pin.height())
|
|
|
|
# Remove the pin shape(s)
|
|
self.remove_layout_pin(pin_name)
|
|
|
|
# Get the lowest, leftest pin
|
|
pin = rtr.get_ll_pin(pin_name)
|
|
|
|
pin_width = 2 * getattr(self, "{}_width".format(pin.layer))
|
|
|
|
# Add it as an IO pin to the perimeter
|
|
route_width = pin.rx() - lowest_coord.x
|
|
pin_offset = vector(lowest_coord.x, pin.by())
|
|
self.add_rect(pin.layer,
|
|
pin_offset,
|
|
route_width,
|
|
pin.height())
|
|
|
|
self.add_layout_pin(self.ext_supply[pin_name],
|
|
pin.layer,
|
|
pin_offset,
|
|
pin_width,
|
|
pin.height())
|
|
else:
|
|
# Grid is left with many top level pins
|
|
pass
|
|
|
|
def route_escape_pins(self, bbox):
|
|
"""
|
|
Add the top-level pins for a single bank SRAM with control.
|
|
"""
|
|
|
|
# List of pin to new pin name
|
|
pins_to_route = []
|
|
for port in self.all_ports:
|
|
# Connect the control pins as inputs
|
|
for signal in self.control_logic_inputs[port]:
|
|
if signal.startswith("rbl"):
|
|
continue
|
|
if signal=="clk":
|
|
pins_to_route.append("{0}{1}".format(signal, port))
|
|
else:
|
|
pins_to_route.append("{0}{1}".format(signal, port))
|
|
|
|
if port in self.write_ports:
|
|
for bit in range(self.word_size + self.num_spare_cols):
|
|
pins_to_route.append("din{0}[{1}]".format(port, bit))
|
|
|
|
if port in self.readwrite_ports or port in self.read_ports:
|
|
for bit in range(self.word_size + self.num_spare_cols):
|
|
pins_to_route.append("dout{0}[{1}]".format(port, bit))
|
|
|
|
for bit in range(self.col_addr_size):
|
|
pins_to_route.append("addr{0}[{1}]".format(port, bit))
|
|
|
|
for bit in range(self.row_addr_size):
|
|
pins_to_route.append("addr{0}[{1}]".format(port, bit + self.col_addr_size))
|
|
|
|
if port in self.write_ports:
|
|
if self.write_size != self.word_size:
|
|
for bit in range(self.num_wmasks):
|
|
pins_to_route.append("wmask{0}[{1}]".format(port, bit))
|
|
|
|
if port in self.write_ports:
|
|
if self.num_spare_cols == 1:
|
|
pins_to_route.append("spare_wen{0}".format(port))
|
|
else:
|
|
for bit in range(self.num_spare_cols):
|
|
pins_to_route.append("spare_wen{0}[{1}]".format(port, bit))
|
|
|
|
from openram.router import signal_escape_router as router
|
|
rtr=router(layers=self.m3_stack,
|
|
design=self,
|
|
bbox=bbox)
|
|
rtr.escape_route(pins_to_route)
|
|
|
|
def compute_bus_sizes(self):
|
|
""" Compute the independent bus widths shared between two and four bank SRAMs """
|
|
|
|
# address size + control signals + one-hot bank select signals
|
|
self.num_vertical_line = self.bank_addr_size + self.control_size + 1# + log(self.num_banks, 2) + 1
|
|
# data bus size
|
|
self.num_horizontal_line = self.word_size
|
|
|
|
self.vertical_bus_width = self.m2_pitch * self.num_vertical_line
|
|
# vertical bus height depends on 2 or 4 banks
|
|
|
|
self.data_bus_height = self.m3_pitch * self.num_horizontal_line
|
|
self.data_bus_width = 2 * (self.bank.width + self.bank_to_bus_distance) + self.vertical_bus_width
|
|
|
|
self.control_bus_height = self.m1_pitch * (self.control_size + 2)
|
|
self.control_bus_width = self.bank.width + self.bank_to_bus_distance + self.vertical_bus_width
|
|
|
|
self.supply_bus_height = self.m1_pitch * 2 # 2 for vdd/gnd placed with control bus
|
|
self.supply_bus_width = self.data_bus_width
|
|
|
|
# Sanity check to ensure we can fit the control logic above a single bank (0.9 is a hack really)
|
|
debug.check(self.bank.width + self.vertical_bus_width > 0.9 * self.control_logic.width,
|
|
"Bank is too small compared to control logic.")
|
|
|
|
def add_busses(self):
|
|
""" Add the horizontal and vertical busses """
|
|
# Vertical bus
|
|
# The order of the control signals on the control bus:
|
|
self.control_bus_names = []
|
|
for port in self.all_ports:
|
|
self.control_bus_names[port] = ["clk_buf{}".format(port)]
|
|
wen = "w_en{}".format(port)
|
|
sen = "s_en{}".format(port)
|
|
pen = "p_en_bar{}".format(port)
|
|
if self.port_id[port] == "r":
|
|
self.control_bus_names[port].extend([sen, pen])
|
|
elif self.port_id[port] == "w":
|
|
self.control_bus_names[port].extend([wen, pen])
|
|
else:
|
|
self.control_bus_names[port].extend([sen, wen, pen])
|
|
self.vert_control_bus_positions = self.create_vertical_bus(layer="m2",
|
|
pitch=self.m2_pitch,
|
|
offset=self.vertical_bus_offset,
|
|
names=self.control_bus_names[port],
|
|
length=self.vertical_bus_height)
|
|
|
|
self.addr_bus_names=["A{0}[{1}]".format(port, i) for i in range(self.bank_addr_size)]
|
|
self.vert_control_bus_positions.update(self.create_vertical_pin_bus(layer="m2",
|
|
pitch=self.m2_pitch,
|
|
offset=self.addr_bus_offset,
|
|
names=self.addr_bus_names,
|
|
length=self.addr_bus_height))
|
|
|
|
# Horizontal data bus
|
|
self.data_bus_names = ["DATA{0}[{1}]".format(port, i) for i in range(self.word_size)]
|
|
self.data_bus_positions = self.create_horizontal_pin_bus(layer="m3",
|
|
pitch=self.m3_pitch,
|
|
offset=self.data_bus_offset,
|
|
names=self.data_bus_names,
|
|
length=self.data_bus_width)
|
|
|
|
# Horizontal control logic bus
|
|
# vdd/gnd in bus go along whole SRAM
|
|
# FIXME: Fatten these wires?
|
|
self.horz_control_bus_positions = self.create_horizontal_bus(layer="m1",
|
|
pitch=self.m1_pitch,
|
|
offset=self.supply_bus_offset,
|
|
names=["vdd"],
|
|
length=self.supply_bus_width)
|
|
# The gnd rail must not be the entire width since we protrude the right-most vdd rail up for
|
|
# the decoder in 4-bank SRAMs
|
|
self.horz_control_bus_positions.update(self.create_horizontal_bus(layer="m1",
|
|
pitch=self.m1_pitch,
|
|
offset=self.supply_bus_offset + vector(0, self.m1_pitch),
|
|
names=["gnd"],
|
|
length=self.supply_bus_width))
|
|
self.horz_control_bus_positions.update(self.create_horizontal_bus(layer="m1",
|
|
pitch=self.m1_pitch,
|
|
offset=self.control_bus_offset,
|
|
names=self.control_bus_names[port],
|
|
length=self.control_bus_width))
|
|
|
|
def add_modules(self):
|
|
self.bitcell = factory.create(module_type=OPTS.bitcell)
|
|
self.dff = factory.create(module_type="dff")
|
|
|
|
# Create the bank module (up to four are instantiated)
|
|
self.bank = factory.create("bank", sram_config=self.sram_config, module_name="bank")
|
|
|
|
self.num_spare_cols = self.bank.num_spare_cols
|
|
|
|
# Create the address and control flops (but not the clk)
|
|
self.row_addr_dff = factory.create("dff_array", module_name="row_addr_dff", rows=self.row_addr_size, columns=1)
|
|
|
|
if self.col_addr_size > 0:
|
|
self.col_addr_dff = factory.create("dff_array", module_name="col_addr_dff", rows=1, columns=self.col_addr_size)
|
|
else:
|
|
self.col_addr_dff = None
|
|
|
|
self.data_dff = factory.create("dff_array", module_name="data_dff", rows=1, columns=self.word_size + self.num_spare_cols)
|
|
|
|
if self.write_size != self.word_size:
|
|
self.wmask_dff = factory.create("dff_array", module_name="wmask_dff", rows=1, columns=self.num_wmasks)
|
|
|
|
if self.num_spare_cols:
|
|
self.spare_wen_dff = factory.create("dff_array", module_name="spare_wen_dff", rows=1, columns=self.num_spare_cols)
|
|
|
|
self.bank_count = 0
|
|
|
|
c = reload(import_module("." + OPTS.control_logic, "openram.modules"))
|
|
self.mod_control_logic = getattr(c, OPTS.control_logic)
|
|
|
|
# Create the control logic module for each port type
|
|
if len(self.readwrite_ports) > 0:
|
|
self.control_logic_rw = self.mod_control_logic(num_rows=self.num_rows,
|
|
words_per_row=self.words_per_row,
|
|
word_size=self.word_size,
|
|
spare_columns=self.num_spare_cols,
|
|
sram=self,
|
|
port_type="rw")
|
|
if len(self.writeonly_ports) > 0:
|
|
self.control_logic_w = self.mod_control_logic(num_rows=self.num_rows,
|
|
words_per_row=self.words_per_row,
|
|
word_size=self.word_size,
|
|
spare_columns=self.num_spare_cols,
|
|
sram=self,
|
|
port_type="w")
|
|
if len(self.readonly_ports) > 0:
|
|
self.control_logic_r = self.mod_control_logic(num_rows=self.num_rows,
|
|
words_per_row=self.words_per_row,
|
|
word_size=self.word_size,
|
|
spare_columns=self.num_spare_cols,
|
|
sram=self,
|
|
port_type="r")
|
|
|
|
def create_bank(self, bank_num):
|
|
""" Create a bank """
|
|
self.bank_insts.append(self.add_inst(name="bank{0}".format(bank_num),
|
|
mod=self.bank))
|
|
|
|
temp = []
|
|
for port in self.read_ports:
|
|
for bit in range(self.word_size + self.num_spare_cols):
|
|
temp.append("dout{0}[{1}]".format(port, bit))
|
|
if self.has_rbl:
|
|
for port in self.all_ports:
|
|
temp.append("rbl_bl{0}".format(port))
|
|
for port in self.write_ports:
|
|
for bit in range(self.word_size + self.num_spare_cols):
|
|
temp.append("bank_din{0}_{1}".format(port, bit))
|
|
for port in self.all_ports:
|
|
for bit in range(self.bank_addr_size):
|
|
temp.append("a{0}_{1}".format(port, bit))
|
|
for port in self.read_ports:
|
|
temp.append("s_en{0}".format(port))
|
|
for port in self.all_ports:
|
|
temp.append("p_en_bar{0}".format(port))
|
|
for port in self.write_ports:
|
|
temp.append("w_en{0}".format(port))
|
|
for bit in range(self.num_wmasks):
|
|
temp.append("bank_wmask{0}_{1}".format(port, bit))
|
|
for bit in range(self.num_spare_cols):
|
|
temp.append("bank_spare_wen{0}_{1}".format(port, bit))
|
|
for port in self.all_ports:
|
|
temp.append("wl_en{0}".format(port))
|
|
temp.extend(self.ext_supplies)
|
|
self.connect_inst(temp)
|
|
|
|
return self.bank_insts[-1]
|
|
|
|
def place_bank(self, bank_inst, position, x_flip, y_flip):
|
|
""" Place a bank at the given position with orientations """
|
|
|
|
# x_flip == 1 --> no flip in x_axis
|
|
# x_flip == -1 --> flip in x_axis
|
|
# y_flip == 1 --> no flip in y_axis
|
|
# y_flip == -1 --> flip in y_axis
|
|
|
|
# x_flip and y_flip are used for position translation
|
|
|
|
if x_flip == -1 and y_flip == -1:
|
|
bank_rotation = 180
|
|
else:
|
|
bank_rotation = 0
|
|
|
|
if x_flip == y_flip:
|
|
bank_mirror = "R0"
|
|
elif x_flip == -1:
|
|
bank_mirror = "MX"
|
|
elif y_flip == -1:
|
|
bank_mirror = "MY"
|
|
else:
|
|
bank_mirror = "R0"
|
|
|
|
bank_inst.place(offset=position,
|
|
mirror=bank_mirror,
|
|
rotate=bank_rotation)
|
|
|
|
return bank_inst
|
|
|
|
def create_row_addr_dff(self):
|
|
""" Add all address flops for the main decoder """
|
|
insts = []
|
|
for port in self.all_ports:
|
|
insts.append(self.add_inst(name="row_address{}".format(port),
|
|
mod=self.row_addr_dff))
|
|
|
|
# inputs, outputs/output/bar
|
|
inputs = []
|
|
outputs = []
|
|
for bit in range(self.row_addr_size):
|
|
inputs.append("addr{}[{}]".format(port, bit + self.col_addr_size))
|
|
outputs.append("a{}_{}".format(port, bit + self.col_addr_size))
|
|
|
|
self.connect_inst(inputs + outputs + ["clk_buf{}".format(port)] + self.ext_supplies)
|
|
|
|
return insts
|
|
|
|
def create_col_addr_dff(self):
|
|
""" Add and place all address flops for the column decoder """
|
|
insts = []
|
|
for port in self.all_ports:
|
|
insts.append(self.add_inst(name="col_address{}".format(port),
|
|
mod=self.col_addr_dff))
|
|
|
|
# inputs, outputs/output/bar
|
|
inputs = []
|
|
outputs = []
|
|
for bit in range(self.col_addr_size):
|
|
inputs.append("addr{}[{}]".format(port, bit))
|
|
outputs.append("a{}_{}".format(port, bit))
|
|
|
|
self.connect_inst(inputs + outputs + ["clk_buf{}".format(port)] + self.ext_supplies)
|
|
|
|
return insts
|
|
|
|
def create_data_dff(self):
|
|
""" Add and place all data flops """
|
|
insts = []
|
|
for port in self.all_ports:
|
|
if port in self.write_ports:
|
|
insts.append(self.add_inst(name="data_dff{}".format(port),
|
|
mod=self.data_dff))
|
|
else:
|
|
insts.append(None)
|
|
continue
|
|
|
|
# inputs, outputs/output/bar
|
|
inputs = []
|
|
outputs = []
|
|
for bit in range(self.word_size + self.num_spare_cols):
|
|
inputs.append("din{}[{}]".format(port, bit))
|
|
outputs.append("bank_din{}_{}".format(port, bit))
|
|
|
|
self.connect_inst(inputs + outputs + ["clk_buf{}".format(port)] + self.ext_supplies)
|
|
|
|
return insts
|
|
|
|
def create_wmask_dff(self):
|
|
""" Add and place all wmask flops """
|
|
insts = []
|
|
for port in self.all_ports:
|
|
if port in self.write_ports:
|
|
insts.append(self.add_inst(name="wmask_dff{}".format(port),
|
|
mod=self.wmask_dff))
|
|
else:
|
|
insts.append(None)
|
|
continue
|
|
|
|
# inputs, outputs/output/bar
|
|
inputs = []
|
|
outputs = []
|
|
for bit in range(self.num_wmasks):
|
|
inputs.append("wmask{}[{}]".format(port, bit))
|
|
outputs.append("bank_wmask{}_{}".format(port, bit))
|
|
|
|
self.connect_inst(inputs + outputs + ["clk_buf{}".format(port)] + self.ext_supplies)
|
|
|
|
return insts
|
|
|
|
def create_spare_wen_dff(self):
|
|
""" Add all spare write enable flops """
|
|
insts = []
|
|
for port in self.all_ports:
|
|
if port in self.write_ports:
|
|
insts.append(self.add_inst(name="spare_wen_dff{}".format(port),
|
|
mod=self.spare_wen_dff))
|
|
else:
|
|
insts.append(None)
|
|
continue
|
|
|
|
# inputs, outputs/output/bar
|
|
inputs = []
|
|
outputs = []
|
|
for bit in range(self.num_spare_cols):
|
|
inputs.append("spare_wen{}[{}]".format(port, bit))
|
|
outputs.append("bank_spare_wen{}_{}".format(port, bit))
|
|
|
|
self.connect_inst(inputs + outputs + ["clk_buf{}".format(port)] + self.ext_supplies)
|
|
|
|
return insts
|
|
|
|
def create_control_logic(self):
|
|
""" Add control logic instances """
|
|
|
|
insts = []
|
|
for port in self.all_ports:
|
|
if port in self.readwrite_ports:
|
|
mod = self.control_logic_rw
|
|
elif port in self.write_ports:
|
|
mod = self.control_logic_w
|
|
else:
|
|
mod = self.control_logic_r
|
|
|
|
insts.append(self.add_inst(name="control{}".format(port), mod=mod))
|
|
|
|
# Inputs
|
|
temp = ["csb{}".format(port)]
|
|
if port in self.readwrite_ports:
|
|
temp.append("web{}".format(port))
|
|
temp.append("clk{}".format(port))
|
|
if self.has_rbl:
|
|
temp.append("rbl_bl{}".format(port))
|
|
|
|
# Outputs
|
|
if port in self.read_ports:
|
|
temp.append("s_en{}".format(port))
|
|
if port in self.write_ports:
|
|
temp.append("w_en{}".format(port))
|
|
temp.append("p_en_bar{}".format(port))
|
|
temp.extend(["wl_en{}".format(port), "clk_buf{}".format(port)] + self.ext_supplies)
|
|
self.connect_inst(temp)
|
|
|
|
return insts
|
|
|
|
def sp_write(self, sp_name, lvs=False, trim=False):
|
|
# Write the entire spice of the object to the file
|
|
############################################################
|
|
# Spice circuit
|
|
############################################################
|
|
sp = open(sp_name, 'w')
|
|
|
|
sp.write("**************************************************\n")
|
|
sp.write("* OpenRAM generated memory.\n")
|
|
sp.write("* Words: {}\n".format(self.num_words))
|
|
sp.write("* Data bits: {}\n".format(self.word_size))
|
|
sp.write("* Banks: {}\n".format(self.num_banks))
|
|
sp.write("* Column mux: {}:1\n".format(self.words_per_row))
|
|
sp.write("* Trimmed: {}\n".format(trim))
|
|
sp.write("* LVS: {}\n".format(lvs))
|
|
sp.write("**************************************************\n")
|
|
# This causes unit test mismatch
|
|
|
|
# sp.write("* Created: {0}\n".format(datetime.datetime.now()))
|
|
# sp.write("* User: {0}\n".format(getpass.getuser()))
|
|
# sp.write(".global {0} {1}\n".format(spice["vdd_name"],
|
|
# spice["gnd_name"]))
|
|
usedMODS = list()
|
|
self.sp_write_file(sp, usedMODS, lvs=lvs, trim=trim)
|
|
del usedMODS
|
|
sp.close()
|
|
|
|
def graph_exclude_bits(self, targ_row, targ_col):
|
|
"""
|
|
Excludes bits in column from being added to graph except target
|
|
"""
|
|
self.bank.graph_exclude_bits(targ_row, targ_col)
|
|
|
|
def clear_exclude_bits(self):
|
|
"""
|
|
Clears the bit exclusions
|
|
"""
|
|
self.bank.clear_exclude_bits()
|
|
|
|
def graph_exclude_column_mux(self, column_include_num, port):
|
|
"""
|
|
Excludes all columns muxes unrelated to the target bit being simulated.
|
|
"""
|
|
self.bank.graph_exclude_column_mux(column_include_num, port)
|
|
|
|
def graph_clear_column_mux(self, port):
|
|
"""
|
|
Clear mux exclusions to allow different bit tests.
|
|
"""
|
|
self.bank.graph_clear_column_mux(port)
|
|
|
|
def create_modules(self):
|
|
"""
|
|
This adds the modules for a single bank SRAM with control
|
|
logic.
|
|
"""
|
|
|
|
self.bank_inst=self.create_bank(0)
|
|
|
|
self.control_logic_insts = self.create_control_logic()
|
|
|
|
self.row_addr_dff_insts = self.create_row_addr_dff()
|
|
|
|
if self.col_addr_dff:
|
|
self.col_addr_dff_insts = self.create_col_addr_dff()
|
|
|
|
if self.write_size != self.word_size:
|
|
self.wmask_dff_insts = self.create_wmask_dff()
|
|
self.data_dff_insts = self.create_data_dff()
|
|
else:
|
|
self.data_dff_insts = self.create_data_dff()
|
|
|
|
if self.num_spare_cols:
|
|
self.spare_wen_dff_insts = self.create_spare_wen_dff()
|
|
else:
|
|
self.num_spare_cols = 0
|
|
|
|
def place_instances(self):
|
|
"""
|
|
This places the instances for a single bank SRAM with control
|
|
logic and up to 2 ports.
|
|
"""
|
|
|
|
# No orientation or offset
|
|
self.place_bank(self.bank_inst, [0, 0], 1, 1)
|
|
|
|
# The control logic is placed such that the vertical center (between the delay/RBL and
|
|
# the actual control logic is aligned with the vertical center of the bank (between
|
|
# the sense amps/column mux and cell array)
|
|
# The x-coordinate is placed to allow a single clock wire (plus an extra pitch)
|
|
# up to the row address DFFs.
|
|
self.control_pos = [None] * len(self.all_ports)
|
|
self.row_addr_pos = [None] * len(self.all_ports)
|
|
|
|
# DFFs are placd on their own
|
|
self.col_addr_pos = [None] * len(self.all_ports)
|
|
self.wmask_pos = [None] * len(self.all_ports)
|
|
self.spare_wen_pos = [None] * len(self.all_ports)
|
|
self.data_pos = [None] * len(self.all_ports)
|
|
|
|
# These positions utilize the channel route sizes.
|
|
# FIXME: Auto-compute these rather than manual computation.
|
|
# If a horizontal channel, they rely on the vertical channel non-preferred (contacted) pitch.
|
|
# If a vertical channel, they rely on the horizontal channel non-preferred (contacted) pitch.
|
|
# So, m3 non-pref pitch means that this is routed on the m2 layer.
|
|
self.data_bus_gap = self.m4_nonpref_pitch * 2
|
|
|
|
# Spare wen are on a separate layer so not included
|
|
# Start with 1 track minimum
|
|
self.data_bus_size = [1] * len(self.all_ports)
|
|
self.col_addr_bus_size = [1] * len(self.all_ports)
|
|
for port in self.all_ports:
|
|
# The column address wires are routed separately from the data bus and will always be smaller.
|
|
# All ports need the col addr flops
|
|
self.col_addr_bus_size[port] = self.col_addr_size * self.m4_nonpref_pitch
|
|
# Write ports need the data input flops and write mask flops
|
|
if port in self.write_ports:
|
|
self.data_bus_size[port] += self.num_wmasks + self.word_size
|
|
# This is for the din pins that get routed in the same channel
|
|
# when we have dout and din together
|
|
if port in self.readwrite_ports:
|
|
self.data_bus_size[port] += self.word_size
|
|
# Convert to length
|
|
self.data_bus_size[port] *= self.m4_nonpref_pitch
|
|
# Add the gap in unit length
|
|
self.data_bus_size[port] += self.data_bus_gap
|
|
|
|
# The control and row addr flops are independent of any bus widths.
|
|
self.place_control()
|
|
self.place_row_addr_dffs()
|
|
|
|
# Place with an initial wide channel (from above)
|
|
self.place_dffs()
|
|
|
|
# Route the channel and set to the new data bus size
|
|
# We need to temporarily add some pins for the x offsets
|
|
# but we'll remove them so that they have the right y
|
|
# offsets after the DFF placement.
|
|
self.add_layout_pins(add_vias=False)
|
|
self.route_dffs(add_routes=False)
|
|
self.remove_layout_pins()
|
|
|
|
# Re-place with the new channel size
|
|
self.place_dffs()
|
|
|
|
def place_row_addr_dffs(self):
|
|
"""
|
|
Must be run after place control logic.
|
|
"""
|
|
port = 0
|
|
# The row address bits are placed above the control logic aligned on the right.
|
|
x_offset = self.control_logic_insts[port].rx() - self.row_addr_dff_insts[port].width
|
|
# It is above the control logic and the predecoder array
|
|
y_offset = max(self.control_logic_insts[port].uy(), self.bank.predecoder_top)
|
|
|
|
self.row_addr_pos[port] = vector(x_offset, y_offset)
|
|
self.row_addr_dff_insts[port].place(self.row_addr_pos[port])
|
|
|
|
if len(self.all_ports)>1:
|
|
port = 1
|
|
# The row address bits are placed above the control logic aligned on the left.
|
|
x_offset = self.control_pos[port].x - self.control_logic_insts[port].width + self.row_addr_dff_insts[port].width
|
|
# If it can be placed above the predecoder and below the control logic, do it
|
|
y_offset = self.bank.predecoder_bottom
|
|
self.row_addr_pos[port] = vector(x_offset, y_offset)
|
|
self.row_addr_dff_insts[port].place(self.row_addr_pos[port], mirror="XY")
|
|
|
|
def place_control(self):
|
|
port = 0
|
|
|
|
# This includes 2 M2 pitches for the row addr clock line.
|
|
# The delay line is aligned with the bitcell array while the control logic is aligned with the port_data
|
|
# using the control_logic_center value.
|
|
self.control_pos[port] = vector(-self.control_logic_insts[port].width - 2 * self.m2_pitch,
|
|
self.bank.bank_array_ll.y - self.control_logic_insts[port].mod.control_logic_center.y)
|
|
self.control_logic_insts[port].place(self.control_pos[port])
|
|
if len(self.all_ports) > 1:
|
|
port = 1
|
|
# This includes 2 M2 pitches for the row addr clock line
|
|
# The delay line is aligned with the bitcell array while the control logic is aligned with the port_data
|
|
# using the control_logic_center value.
|
|
self.control_pos[port] = vector(self.bank_inst.rx() + self.control_logic_insts[port].width + 2 * self.m2_pitch,
|
|
self.bank.bank_array_ur.y
|
|
+ self.control_logic_insts[port].height
|
|
- self.control_logic_insts[port].height
|
|
+ self.control_logic_insts[port].mod.control_logic_center.y)
|
|
self.control_logic_insts[port].place(self.control_pos[port], mirror="XY")
|
|
|
|
def place_dffs(self):
|
|
"""
|
|
Place the col addr, data, wmask, and spare data DFFs.
|
|
This can be run more than once after we recompute the channel width.
|
|
"""
|
|
|
|
port = 0
|
|
# Add the col address flops below the bank to the right of the control logic
|
|
x_offset = self.control_logic_insts[port].rx() + self.dff.width
|
|
# Place it a data bus below the x-axis, but at least as low as the control logic to not block
|
|
# the control logic signals
|
|
y_offset = min(-self.data_bus_size[port] - self.dff.height,
|
|
self.control_logic_insts[port].by())
|
|
if self.col_addr_dff:
|
|
self.col_addr_pos[port] = vector(x_offset,
|
|
y_offset)
|
|
self.col_addr_dff_insts[port].place(self.col_addr_pos[port])
|
|
x_offset = self.col_addr_dff_insts[port].rx()
|
|
else:
|
|
self.col_addr_pos[port] = vector(x_offset, 0)
|
|
|
|
if port in self.write_ports:
|
|
if self.write_size != self.word_size:
|
|
# Add the write mask flops below the write mask AND array.
|
|
self.wmask_pos[port] = vector(x_offset,
|
|
y_offset)
|
|
self.wmask_dff_insts[port].place(self.wmask_pos[port])
|
|
x_offset = self.wmask_dff_insts[port].rx()
|
|
|
|
# Add the data flops below the write mask flops.
|
|
self.data_pos[port] = vector(x_offset,
|
|
y_offset)
|
|
self.data_dff_insts[port].place(self.data_pos[port])
|
|
x_offset = self.data_dff_insts[port].rx()
|
|
|
|
# Add spare write enable flops to the right of data flops since the spare columns
|
|
# will be on the right
|
|
if self.num_spare_cols:
|
|
self.spare_wen_pos[port] = vector(x_offset,
|
|
y_offset)
|
|
self.spare_wen_dff_insts[port].place(self.spare_wen_pos[port])
|
|
x_offset = self.spare_wen_dff_insts[port].rx()
|
|
|
|
else:
|
|
self.wmask_pos[port] = vector(x_offset, y_offset)
|
|
self.data_pos[port] = vector(x_offset, y_offset)
|
|
self.spare_wen_pos[port] = vector(x_offset, y_offset)
|
|
|
|
if len(self.all_ports) > 1:
|
|
port = 1
|
|
|
|
# Add the col address flops below the bank to the right of the control logic
|
|
x_offset = self.control_logic_insts[port].lx() - 2 * self.dff.width
|
|
# Place it a data bus below the x-axis, but at least as high as the control logic to not block
|
|
# the control logic signals
|
|
y_offset = max(self.bank.height + self.data_bus_size[port] + self.dff.height,
|
|
self.control_logic_insts[port].uy() - self.dff.height)
|
|
if self.col_addr_dff:
|
|
self.col_addr_pos[port] = vector(x_offset,
|
|
y_offset)
|
|
self.col_addr_dff_insts[port].place(self.col_addr_pos[port], mirror="XY")
|
|
x_offset = self.col_addr_dff_insts[port].lx()
|
|
else:
|
|
self.col_addr_pos[port] = vector(x_offset, y_offset)
|
|
|
|
if port in self.write_ports:
|
|
# Add spare write enable flops to the right of the data flops since the spare
|
|
# columns will be on the left
|
|
if self.num_spare_cols:
|
|
self.spare_wen_pos[port] = vector(x_offset - self.spare_wen_dff_insts[port].width,
|
|
y_offset)
|
|
self.spare_wen_dff_insts[port].place(self.spare_wen_pos[port], mirror="MX")
|
|
x_offset = self.spare_wen_dff_insts[port].lx()
|
|
|
|
if self.write_size != self.word_size:
|
|
# Add the write mask flops below the write mask AND array.
|
|
self.wmask_pos[port] = vector(x_offset - self.wmask_dff_insts[port].width,
|
|
y_offset)
|
|
self.wmask_dff_insts[port].place(self.wmask_pos[port], mirror="MX")
|
|
x_offset = self.wmask_dff_insts[port].lx()
|
|
|
|
# Add the data flops below the write mask flops.
|
|
self.data_pos[port] = vector(x_offset - self.data_dff_insts[port].width,
|
|
y_offset)
|
|
self.data_dff_insts[port].place(self.data_pos[port], mirror="MX")
|
|
else:
|
|
self.wmask_pos[port] = vector(x_offset, y_offset)
|
|
self.data_pos[port] = vector(x_offset, y_offset)
|
|
self.spare_wen_pos[port] = vector(x_offset, y_offset)
|
|
|
|
def add_layout_pins(self, add_vias=True):
|
|
"""
|
|
Add the top-level pins for a single bank SRAM with control.
|
|
"""
|
|
for port in self.all_ports:
|
|
# Hack: If we are escape routing, set the pin layer to
|
|
# None so that we will start from the pin layer
|
|
# Otherwise, set it as the pin layer so that no vias are added.
|
|
# Otherwise, when we remove pins to move the dff array dynamically,
|
|
# we will leave some remaining vias when the pin locations change.
|
|
if add_vias:
|
|
pin_layer = None
|
|
else:
|
|
pin_layer = self.pwr_grid_layers[0]
|
|
|
|
# Connect the control pins as inputs
|
|
for signal in self.control_logic_inputs[port]:
|
|
if signal.startswith("rbl"):
|
|
continue
|
|
self.add_io_pin(self.control_logic_insts[port],
|
|
signal,
|
|
signal + "{}".format(port),
|
|
start_layer=pin_layer)
|
|
|
|
if port in self.write_ports:
|
|
for bit in range(self.word_size + self.num_spare_cols):
|
|
self.add_io_pin(self.data_dff_insts[port],
|
|
"din_{}".format(bit),
|
|
"din{0}[{1}]".format(port, bit),
|
|
start_layer=pin_layer)
|
|
|
|
if port in self.readwrite_ports or port in self.read_ports:
|
|
for bit in range(self.word_size + self.num_spare_cols):
|
|
self.add_io_pin(self.bank_inst,
|
|
"dout{0}_{1}".format(port, bit),
|
|
"dout{0}[{1}]".format(port, bit),
|
|
start_layer=pin_layer)
|
|
|
|
for bit in range(self.col_addr_size):
|
|
self.add_io_pin(self.col_addr_dff_insts[port],
|
|
"din_{}".format(bit),
|
|
"addr{0}[{1}]".format(port, bit),
|
|
start_layer=pin_layer)
|
|
|
|
for bit in range(self.row_addr_size):
|
|
self.add_io_pin(self.row_addr_dff_insts[port],
|
|
"din_{}".format(bit),
|
|
"addr{0}[{1}]".format(port, bit + self.col_addr_size),
|
|
start_layer=pin_layer)
|
|
|
|
if port in self.write_ports:
|
|
if self.write_size != self.word_size:
|
|
for bit in range(self.num_wmasks):
|
|
self.add_io_pin(self.wmask_dff_insts[port],
|
|
"din_{}".format(bit),
|
|
"wmask{0}[{1}]".format(port, bit),
|
|
start_layer=pin_layer)
|
|
|
|
if port in self.write_ports:
|
|
if self.num_spare_cols == 1:
|
|
self.add_io_pin(self.spare_wen_dff_insts[port],
|
|
"din_{}".format(0),
|
|
"spare_wen{0}".format(port),
|
|
start_layer=pin_layer)
|
|
else:
|
|
for bit in range(self.num_spare_cols):
|
|
self.add_io_pin(self.spare_wen_dff_insts[port],
|
|
"din_{}".format(bit),
|
|
"spare_wen{0}[{1}]".format(port, bit),
|
|
start_layer=pin_layer)
|
|
|
|
def route_layout(self):
|
|
""" Route a single bank SRAM """
|
|
|
|
self.route_clk()
|
|
|
|
self.route_control_logic()
|
|
|
|
self.route_row_addr_dff()
|
|
|
|
self.route_dffs()
|
|
|
|
# We add the vias to M3 before routing supplies because
|
|
# they might create some blockages
|
|
self.add_layout_pins()
|
|
|
|
# Some technologies have an isolation
|
|
self.add_dnwell(inflate=2.5)
|
|
|
|
# Route the supplies together and/or to the ring/stripes.
|
|
# This is done with the original bbox since the escape routes need to
|
|
# be outside of the ring for OpenLane
|
|
rt = router_tech(self.supply_stack, 1)
|
|
init_bbox = self.get_bbox(side="ring",
|
|
margin=rt.track_width)
|
|
|
|
# We need the initial bbox for the supply rings later
|
|
# because the perimeter pins will change the bbox
|
|
# Route the pins to the perimeter
|
|
if OPTS.perimeter_pins:
|
|
# We now route the escape routes far enough out so that they will
|
|
# reach past the power ring or stripes on the sides
|
|
bbox = self.get_bbox(side="ring",
|
|
margin=11*rt.track_width)
|
|
self.route_escape_pins(bbox)
|
|
|
|
self.route_supplies(init_bbox)
|
|
|
|
|
|
def route_dffs(self, add_routes=True):
|
|
|
|
for port in self.all_ports:
|
|
self.route_dff(port, add_routes)
|
|
|
|
def route_dff(self, port, add_routes):
|
|
|
|
# This is only done when we add_routes because the data channel will be larger
|
|
# so that can be used for area estimation.
|
|
if add_routes:
|
|
self.route_col_addr_dffs(port)
|
|
|
|
self.route_data_dffs(port, add_routes)
|
|
|
|
def route_col_addr_dffs(self, port):
|
|
|
|
route_map = []
|
|
|
|
# column mux dff is routed on it's own since it is to the far end
|
|
# decoder inputs are min pitch M2, so need to use lower layer stack
|
|
if self.col_addr_size > 0:
|
|
dff_names = ["dout_{}".format(x) for x in range(self.col_addr_size)]
|
|
dff_pins = [self.col_addr_dff_insts[port].get_pin(x) for x in dff_names]
|
|
bank_names = ["addr{0}_{1}".format(port, x) for x in range(self.col_addr_size)]
|
|
bank_pins = [self.bank_inst.get_pin(x) for x in bank_names]
|
|
route_map.extend(list(zip(bank_pins, dff_pins)))
|
|
|
|
if len(route_map) > 0:
|
|
|
|
# This layer stack must be different than the data dff layer stack
|
|
layer_stack = self.m1_stack
|
|
|
|
if port == 0:
|
|
offset = vector(self.control_logic_insts[port].rx() + self.dff.width,
|
|
- self.data_bus_size[port] + 2 * self.m3_pitch)
|
|
cr = channel_route(netlist=route_map,
|
|
offset=offset,
|
|
layer_stack=layer_stack,
|
|
parent=self)
|
|
# This causes problem in magic since it sometimes cannot extract connectivity of instances
|
|
# with no active devices.
|
|
self.add_inst(cr.name, cr)
|
|
self.connect_inst([])
|
|
# self.add_flat_inst(cr.name, cr)
|
|
else:
|
|
offset = vector(0,
|
|
self.bank.height + self.m3_pitch)
|
|
cr = channel_route(netlist=route_map,
|
|
offset=offset,
|
|
layer_stack=layer_stack,
|
|
parent=self)
|
|
# This causes problem in magic since it sometimes cannot extract connectivity of instances
|
|
# with no active devices.
|
|
self.add_inst(cr.name, cr)
|
|
self.connect_inst([])
|
|
# self.add_flat_inst(cr.name, cr)
|
|
|
|
def route_data_dffs(self, port, add_routes):
|
|
route_map = []
|
|
|
|
# wmask dff
|
|
if self.num_wmasks > 0 and port in self.write_ports:
|
|
dff_names = ["dout_{}".format(x) for x in range(self.num_wmasks)]
|
|
dff_pins = [self.wmask_dff_insts[port].get_pin(x) for x in dff_names]
|
|
bank_names = ["bank_wmask{0}_{1}".format(port, x) for x in range(self.num_wmasks)]
|
|
bank_pins = [self.bank_inst.get_pin(x) for x in bank_names]
|
|
route_map.extend(list(zip(bank_pins, dff_pins)))
|
|
|
|
if port in self.write_ports:
|
|
# synchronized inputs from data dff
|
|
dff_names = ["dout_{}".format(x) for x in range(self.word_size + self.num_spare_cols)]
|
|
dff_pins = [self.data_dff_insts[port].get_pin(x) for x in dff_names]
|
|
bank_names = ["din{0}_{1}".format(port, x) for x in range(self.word_size + self.num_spare_cols)]
|
|
bank_pins = [self.bank_inst.get_pin(x) for x in bank_names]
|
|
route_map.extend(list(zip(bank_pins, dff_pins)))
|
|
|
|
# spare wen dff
|
|
if self.num_spare_cols > 0 and port in self.write_ports:
|
|
dff_names = ["dout_{}".format(x) for x in range(self.num_spare_cols)]
|
|
dff_pins = [self.spare_wen_dff_insts[port].get_pin(x) for x in dff_names]
|
|
bank_names = ["bank_spare_wen{0}_{1}".format(port, x) for x in range(self.num_spare_cols)]
|
|
bank_pins = [self.bank_inst.get_pin(x) for x in bank_names]
|
|
route_map.extend(list(zip(bank_pins, dff_pins)))
|
|
|
|
if len(route_map) > 0:
|
|
|
|
# This layer stack must be different than the column addr dff layer stack
|
|
layer_stack = self.m3_stack
|
|
if port == 0:
|
|
# This is relative to the bank at 0,0 or the s_en which is routed on M3 also
|
|
if "s_en" in self.control_logic_insts[port].mod.pin_map:
|
|
y_bottom = min(0, self.control_logic_insts[port].get_pin("s_en").by())
|
|
else:
|
|
y_bottom = 0
|
|
|
|
y_offset = y_bottom - self.data_bus_size[port] + 2 * self.m3_pitch
|
|
offset = vector(self.control_logic_insts[port].rx() + self.dff.width,
|
|
y_offset)
|
|
cr = channel_route(netlist=route_map,
|
|
offset=offset,
|
|
layer_stack=layer_stack,
|
|
parent=self)
|
|
if add_routes:
|
|
# This causes problem in magic since it sometimes cannot extract connectivity of instances
|
|
# with no active devices.
|
|
self.add_inst(cr.name, cr)
|
|
self.connect_inst([])
|
|
# self.add_flat_inst(cr.name, cr)
|
|
else:
|
|
self.data_bus_size[port] = max(cr.height, self.col_addr_bus_size[port]) + self.data_bus_gap
|
|
else:
|
|
if "s_en" in self.control_logic_insts[port].mod.pin_map:
|
|
y_top = max(self.bank.height, self.control_logic_insts[port].get_pin("s_en").uy())
|
|
else:
|
|
y_top = self.bank.height
|
|
y_offset = y_top + self.m3_pitch
|
|
offset = vector(0,
|
|
y_offset)
|
|
cr = channel_route(netlist=route_map,
|
|
offset=offset,
|
|
layer_stack=layer_stack,
|
|
parent=self)
|
|
if add_routes:
|
|
# This causes problem in magic since it sometimes cannot extract connectivity of instances
|
|
# with no active devices.
|
|
self.add_inst(cr.name, cr)
|
|
self.connect_inst([])
|
|
# self.add_flat_inst(cr.name, cr)
|
|
else:
|
|
self.data_bus_size[port] = max(cr.height, self.col_addr_bus_size[port]) + self.data_bus_gap
|
|
|
|
def route_clk(self):
|
|
""" Route the clock network """
|
|
|
|
# This is the actual input to the SRAM
|
|
for port in self.all_ports:
|
|
# Connect all of these clock pins to the clock in the central bus
|
|
# This is something like a "spine" clock distribution. The two spines
|
|
# are clk_buf and clk_buf_bar
|
|
control_clk_buf_pin = self.control_logic_insts[port].get_pin("clk_buf")
|
|
control_clk_buf_pos = control_clk_buf_pin.center()
|
|
|
|
# This uses a metal2 track to the right (for port0) of the control/row addr DFF
|
|
# to route vertically. For port1, it is to the left.
|
|
row_addr_clk_pin = self.row_addr_dff_insts[port].get_pin("clk")
|
|
if port % 2:
|
|
control_clk_buf_pos = control_clk_buf_pin.lc()
|
|
row_addr_clk_pos = row_addr_clk_pin.lc()
|
|
mid1_pos = vector(self.row_addr_dff_insts[port].lx() - self.m2_pitch,
|
|
row_addr_clk_pos.y)
|
|
else:
|
|
control_clk_buf_pos = control_clk_buf_pin.rc()
|
|
row_addr_clk_pos = row_addr_clk_pin.rc()
|
|
mid1_pos = vector(self.row_addr_dff_insts[port].rx() + self.m2_pitch,
|
|
row_addr_clk_pos.y)
|
|
|
|
# This is the steiner point where the net branches out
|
|
clk_steiner_pos = vector(mid1_pos.x, control_clk_buf_pos.y)
|
|
self.add_path(control_clk_buf_pin.layer, [control_clk_buf_pos, clk_steiner_pos])
|
|
self.add_via_stack_center(from_layer=control_clk_buf_pin.layer,
|
|
to_layer="m2",
|
|
offset=clk_steiner_pos)
|
|
|
|
# Note, the via to the control logic is taken care of above
|
|
self.add_wire(self.m2_stack[::-1],
|
|
[row_addr_clk_pos, mid1_pos, clk_steiner_pos])
|
|
|
|
if self.col_addr_dff:
|
|
dff_clk_pin = self.col_addr_dff_insts[port].get_pin("clk")
|
|
dff_clk_pos = dff_clk_pin.center()
|
|
mid_pos = vector(clk_steiner_pos.x, dff_clk_pos.y)
|
|
self.add_wire(self.m2_stack[::-1],
|
|
[dff_clk_pos, mid_pos, clk_steiner_pos])
|
|
elif port in self.write_ports:
|
|
data_dff_clk_pin = self.data_dff_insts[port].get_pin("clk")
|
|
data_dff_clk_pos = data_dff_clk_pin.center()
|
|
mid_pos = vector(clk_steiner_pos.x, data_dff_clk_pos.y)
|
|
# In some designs, the steiner via will be too close to the mid_pos via
|
|
# so make the wire as wide as the contacts
|
|
self.add_path("m2",
|
|
[mid_pos, clk_steiner_pos],
|
|
width=max(self.m2_via.width, self.m2_via.height))
|
|
self.add_wire(self.m2_stack[::-1],
|
|
[data_dff_clk_pos, mid_pos, clk_steiner_pos])
|
|
|
|
def route_control_logic(self):
|
|
"""
|
|
Route the control logic pins that are not inputs
|
|
"""
|
|
|
|
for port in self.all_ports:
|
|
for signal in self.control_logic_outputs[port]:
|
|
# The clock gets routed separately and is not a part of the bank
|
|
if "clk" in signal:
|
|
continue
|
|
src_pin = self.control_logic_insts[port].get_pin(signal)
|
|
dest_pin = self.bank_inst.get_pin(signal + "{}".format(port))
|
|
self.connect_vbus(src_pin, dest_pin)
|
|
|
|
if self.has_rbl:
|
|
for port in self.all_ports:
|
|
# Only input (besides pins) is the replica bitline
|
|
src_pin = self.control_logic_insts[port].get_pin("rbl_bl")
|
|
dest_pin = self.bank_inst.get_pin("rbl_bl_{0}_{0}".format(port))
|
|
self.add_wire(self.m3_stack,
|
|
[src_pin.center(), vector(src_pin.cx(), dest_pin.cy()), dest_pin.rc()])
|
|
self.add_via_stack_center(from_layer=src_pin.layer,
|
|
to_layer="m4",
|
|
offset=src_pin.center())
|
|
self.add_via_stack_center(from_layer=dest_pin.layer,
|
|
to_layer="m3",
|
|
offset=dest_pin.center())
|
|
|
|
def route_row_addr_dff(self):
|
|
"""
|
|
Connect the output of the row flops to the bank pins
|
|
"""
|
|
for port in self.all_ports:
|
|
for bit in range(self.row_addr_size):
|
|
flop_name = "dout_{}".format(bit)
|
|
bank_name = "addr{0}_{1}".format(port, bit + self.col_addr_size)
|
|
flop_pin = self.row_addr_dff_insts[port].get_pin(flop_name)
|
|
bank_pin = self.bank_inst.get_pin(bank_name)
|
|
flop_pos = flop_pin.center()
|
|
bank_pos = bank_pin.center()
|
|
mid_pos = vector(bank_pos.x, flop_pos.y)
|
|
self.add_via_stack_center(from_layer=flop_pin.layer,
|
|
to_layer="m3",
|
|
offset=flop_pos)
|
|
self.add_path("m3", [flop_pos, mid_pos])
|
|
self.add_via_stack_center(from_layer=bank_pin.layer,
|
|
to_layer="m3",
|
|
offset=mid_pos)
|
|
self.add_path(bank_pin.layer, [mid_pos, bank_pos])
|
|
|
|
def add_lvs_correspondence_points(self):
|
|
"""
|
|
This adds some points for easier debugging if LVS goes wrong.
|
|
These should probably be turned off by default though, since extraction
|
|
will show these as ports in the extracted netlist.
|
|
"""
|
|
return
|
|
for n in self.control_logic_outputs[0]:
|
|
pin = self.control_logic_insts[0].get_pin(n)
|
|
self.add_label(text=n,
|
|
layer=pin.layer,
|
|
offset=pin.center())
|
|
|
|
def graph_exclude_data_dff(self):
|
|
"""
|
|
Removes data dff and wmask dff (if applicable) from search graph.
|
|
"""
|
|
# Data dffs and wmask dffs are only for writing so are not useful for evaluating read delay.
|
|
for inst in self.data_dff_insts:
|
|
self.graph_inst_exclude.add(inst)
|
|
if self.write_size != self.word_size:
|
|
for inst in self.wmask_dff_insts:
|
|
self.graph_inst_exclude.add(inst)
|
|
if self.num_spare_cols:
|
|
for inst in self.spare_wen_dff_insts:
|
|
self.graph_inst_exclude.add(inst)
|
|
|
|
def graph_exclude_addr_dff(self):
|
|
"""
|
|
Removes data dff from search graph.
|
|
"""
|
|
# Address is considered not part of the critical path, subjectively removed
|
|
for inst in self.row_addr_dff_insts:
|
|
self.graph_inst_exclude.add(inst)
|
|
|
|
if self.col_addr_dff:
|
|
for inst in self.col_addr_dff_insts:
|
|
self.graph_inst_exclude.add(inst)
|
|
|
|
def graph_exclude_ctrl_dffs(self):
|
|
"""
|
|
Exclude dffs for CSB, WEB, etc from graph
|
|
"""
|
|
# Insts located in control logic, exclusion function called here
|
|
for inst in self.control_logic_insts:
|
|
inst.mod.graph_exclude_dffs()
|
|
|
|
def get_cell_name(self, inst_name, row, col):
|
|
"""
|
|
Gets the spice name of the target bitcell.
|
|
"""
|
|
# Sanity check in case it was forgotten
|
|
if inst_name.find("x") != 0:
|
|
inst_name = "x" + inst_name
|
|
return self.bank_inst.mod.get_cell_name(inst_name + "{}x".format(OPTS.hier_seperator) + self.bank_inst.name, row, col)
|
|
|
|
def get_bank_num(self, inst_name, row, col):
|
|
return 0
|