mirror of https://github.com/VLSIDA/OpenRAM.git
1041 lines
47 KiB
Python
1041 lines
47 KiB
Python
import sys
|
|
from tech import drc, parameter
|
|
import debug
|
|
import design
|
|
import math
|
|
from math import log,sqrt,ceil
|
|
import contact
|
|
from pinv import pinv
|
|
from pnand2 import pnand2
|
|
from pnor2 import pnor2
|
|
from vector import vector
|
|
from pinvbuf import pinvbuf
|
|
|
|
from globals import OPTS
|
|
|
|
class bank(design.design):
|
|
"""
|
|
Dynamically generated a single bank including bitcell array,
|
|
hierarchical_decoder, precharge, (optional column_mux and column decoder),
|
|
write driver and sense amplifiers.
|
|
"""
|
|
|
|
def __init__(self, word_size, num_words, words_per_row, num_banks=1, name=""):
|
|
|
|
mod_list = ["tri_gate", "bitcell", "decoder", "ms_flop_array", "wordline_driver",
|
|
"bitcell_array", "sense_amp_array", "precharge_array",
|
|
"column_mux_array", "write_driver_array", "tri_gate_array",
|
|
"bank_select"]
|
|
for mod_name in mod_list:
|
|
config_mod_name = getattr(OPTS, mod_name)
|
|
class_file = reload(__import__(config_mod_name))
|
|
mod_class = getattr(class_file , config_mod_name)
|
|
setattr (self, "mod_"+mod_name, mod_class)
|
|
|
|
if name == "":
|
|
name = "bank_{0}_{1}".format(word_size, num_words)
|
|
design.design.__init__(self, name)
|
|
debug.info(2, "create sram of size {0} with {1} words".format(word_size,num_words))
|
|
|
|
self.word_size = word_size
|
|
self.num_words = num_words
|
|
self.words_per_row = words_per_row
|
|
self.num_banks = num_banks
|
|
|
|
# The local control signals are gated when we have bank select logic,
|
|
# so this prefix will be added to all of the input signals to create
|
|
# the internal gated signals.
|
|
if self.num_banks>1:
|
|
self.prefix="gated_"
|
|
else:
|
|
self.prefix=""
|
|
|
|
self.compute_sizes()
|
|
self.add_pins()
|
|
self.create_modules()
|
|
self.add_modules()
|
|
self.setup_layout_constraints()
|
|
|
|
self.add_power_ring(self.core_bbox)
|
|
|
|
# FIXME: Move this to the add modules function
|
|
self.add_bank_select()
|
|
|
|
self.route_layout()
|
|
|
|
|
|
# Can remove the following, but it helps for debug!
|
|
self.add_lvs_correspondence_points()
|
|
|
|
self.offset_all_coordinates()
|
|
|
|
self.DRC_LVS()
|
|
|
|
def add_pins(self):
|
|
""" Adding pins for Bank module"""
|
|
for i in range(self.word_size):
|
|
self.add_pin("DATA[{0}]".format(i),"INOUT")
|
|
for i in range(self.addr_size):
|
|
self.add_pin("A[{0}]".format(i),"INPUT")
|
|
|
|
# For more than one bank, we have a bank select and name
|
|
# the signals gated_*.
|
|
if self.num_banks > 1:
|
|
self.add_pin("bank_sel","INPUT")
|
|
for pin in ["s_en","w_en","tri_en_bar","tri_en",
|
|
"clk_buf_bar","clk_buf"]:
|
|
self.add_pin(pin,"INPUT")
|
|
self.add_pin("vdd","POWER")
|
|
self.add_pin("gnd","GROUND")
|
|
|
|
def route_layout(self):
|
|
""" Create routing amoung the modules """
|
|
self.route_central_bus()
|
|
self.route_precharge_to_bitcell_array()
|
|
self.route_sense_amp_to_trigate()
|
|
self.route_tri_gate_out()
|
|
self.route_wordline_driver()
|
|
self.route_row_decoder()
|
|
self.route_column_address_lines()
|
|
self.route_control_lines()
|
|
self.add_control_pins()
|
|
if self.num_banks > 1:
|
|
self.route_bank_select()
|
|
|
|
self.route_vdd_supply()
|
|
self.route_gnd_supply()
|
|
|
|
def add_modules(self):
|
|
""" Add modules. The order should not matter! """
|
|
|
|
# Above the bitcell array
|
|
self.add_bitcell_array()
|
|
self.add_precharge_array()
|
|
|
|
# Below the bitcell array
|
|
if self.col_addr_size > 0:
|
|
# The m2 width is because the 6T cell may have vias on the boundary edge for
|
|
# overlapping when making the array
|
|
self.column_mux_height = self.column_mux_array.height + 0.5*self.m2_width
|
|
self.add_column_mux_array()
|
|
else:
|
|
self.column_mux_height = 0
|
|
self.add_sense_amp_array()
|
|
self.add_write_driver_array()
|
|
self.add_msf_data_in()
|
|
self.add_tri_gate_array()
|
|
|
|
# To the left of the bitcell array
|
|
self.add_row_decoder()
|
|
self.add_wordline_driver()
|
|
self.add_column_decoder()
|
|
|
|
|
|
|
|
def compute_sizes(self):
|
|
""" Computes the required sizes to create the bank """
|
|
|
|
self.num_cols = self.words_per_row*self.word_size
|
|
self.num_rows = self.num_words / self.words_per_row
|
|
|
|
self.row_addr_size = int(log(self.num_rows, 2))
|
|
self.col_addr_size = int(log(self.words_per_row, 2))
|
|
self.addr_size = self.col_addr_size + self.row_addr_size
|
|
|
|
debug.check(self.num_rows*self.num_cols==self.word_size*self.num_words,"Invalid bank sizes.")
|
|
debug.check(self.addr_size==self.col_addr_size + self.row_addr_size,"Invalid address break down.")
|
|
|
|
# Width for the vdd/gnd rails
|
|
self.supply_rail_width = 4*self.m2_width
|
|
self.supply_rail_pitch = self.supply_rail_width + 1.5*self.m2_space
|
|
|
|
# Number of control lines in the bus
|
|
self.num_control_lines = 6
|
|
# The order of the control signals on the control bus:
|
|
self.input_control_signals = ["clk_buf", "tri_en_bar", "tri_en", "clk_buf_bar", "w_en", "s_en"]
|
|
# These will be outputs of the gaters if this is multibank, if not, normal signals.
|
|
if self.num_banks > 1:
|
|
self.control_signals = ["gated_"+str for str in self.input_control_signals]
|
|
else:
|
|
self.control_signals = self.input_control_signals
|
|
# The central bus is the column address (one hot) and row address (binary)
|
|
if self.col_addr_size>0:
|
|
self.num_col_addr_lines = 2**self.col_addr_size
|
|
self.num_addr_lines = self.num_col_addr_lines + self.row_addr_size
|
|
else:
|
|
self.num_col_addr_lines = 0
|
|
self.num_addr_lines = self.row_addr_size
|
|
|
|
# M1/M2 routing pitch is based on contacted pitch
|
|
self.m1_pitch = contact.m1m2.height + max(self.m1_space,self.m2_space)
|
|
self.m2_pitch = contact.m2m3.height + max(self.m2_space,self.m3_space)
|
|
|
|
# The width of this bus is needed to place other modules (e.g. decoder)
|
|
self.central_bus_width = self.m2_pitch * (self.num_control_lines + self.num_addr_lines + 1)
|
|
|
|
|
|
|
|
|
|
def create_modules(self):
|
|
""" Create all the modules using the class loader """
|
|
self.tri = self.mod_tri_gate()
|
|
self.bitcell = self.mod_bitcell()
|
|
|
|
self.bitcell_array = self.mod_bitcell_array(cols=self.num_cols,
|
|
rows=self.num_rows)
|
|
self.add_mod(self.bitcell_array)
|
|
|
|
self.precharge_array = self.mod_precharge_array(columns=self.num_cols)
|
|
self.add_mod(self.precharge_array)
|
|
|
|
if self.col_addr_size > 0:
|
|
self.column_mux_array = self.mod_column_mux_array(columns=self.num_cols,
|
|
word_size=self.word_size)
|
|
self.add_mod(self.column_mux_array)
|
|
|
|
|
|
self.sense_amp_array = self.mod_sense_amp_array(word_size=self.word_size,
|
|
words_per_row=self.words_per_row)
|
|
self.add_mod(self.sense_amp_array)
|
|
|
|
self.write_driver_array = self.mod_write_driver_array(columns=self.num_cols,
|
|
word_size=self.word_size)
|
|
self.add_mod(self.write_driver_array)
|
|
|
|
self.row_decoder = self.mod_decoder(rows=self.num_rows)
|
|
self.add_mod(self.row_decoder)
|
|
|
|
self.msf_data_in = self.mod_ms_flop_array(name="msf_data_in",
|
|
columns=self.num_cols,
|
|
word_size=self.word_size)
|
|
self.add_mod(self.msf_data_in)
|
|
|
|
self.tri_gate_array = self.mod_tri_gate_array(columns=self.num_cols,
|
|
word_size=self.word_size)
|
|
self.add_mod(self.tri_gate_array)
|
|
|
|
self.wordline_driver = self.mod_wordline_driver(rows=self.num_rows)
|
|
self.add_mod(self.wordline_driver)
|
|
|
|
self.inv = pinv()
|
|
self.add_mod(self.inv)
|
|
|
|
if(self.num_banks > 1):
|
|
self.bank_select = self.mod_bank_select()
|
|
self.add_mod(self.bank_select)
|
|
|
|
|
|
def add_bitcell_array(self):
|
|
""" Adding Bitcell Array """
|
|
|
|
self.bitcell_array_inst=self.add_inst(name="bitcell_array",
|
|
mod=self.bitcell_array,
|
|
offset=vector(0,0))
|
|
temp = []
|
|
for i in range(self.num_cols):
|
|
temp.append("bl[{0}]".format(i))
|
|
temp.append("br[{0}]".format(i))
|
|
for j in range(self.num_rows):
|
|
temp.append("wl[{0}]".format(j))
|
|
temp.extend(["vdd", "gnd"])
|
|
self.connect_inst(temp)
|
|
|
|
|
|
|
|
def add_precharge_array(self):
|
|
""" Adding Precharge """
|
|
|
|
# The wells must be far enough apart
|
|
# The enclosure is for the well and the spacing is to the bitcell wells
|
|
y_offset = self.bitcell_array.height + 2*drc["pwell_to_nwell"] + drc["well_enclosure_active"]
|
|
self.precharge_array_inst=self.add_inst(name="precharge_array",
|
|
mod=self.precharge_array,
|
|
offset=vector(0,y_offset))
|
|
temp = []
|
|
for i in range(self.num_cols):
|
|
temp.append("bl[{0}]".format(i))
|
|
temp.append("br[{0}]".format(i))
|
|
temp.extend([self.prefix+"clk_buf_bar", "vdd"])
|
|
self.connect_inst(temp)
|
|
|
|
def add_column_mux_array(self):
|
|
""" Adding Column Mux when words_per_row > 1 . """
|
|
|
|
y_offset = self.column_mux_height
|
|
self.col_mux_array_inst=self.add_inst(name="column_mux_array",
|
|
mod=self.column_mux_array,
|
|
offset=vector(0,y_offset).scale(-1,-1))
|
|
temp = []
|
|
for i in range(self.num_cols):
|
|
temp.append("bl[{0}]".format(i))
|
|
temp.append("br[{0}]".format(i))
|
|
for k in range(self.words_per_row):
|
|
temp.append("sel[{0}]".format(k))
|
|
for j in range(self.word_size):
|
|
temp.append("bl_out[{0}]".format(j))
|
|
temp.append("br_out[{0}]".format(j))
|
|
temp.append("gnd")
|
|
self.connect_inst(temp)
|
|
|
|
def add_sense_amp_array(self):
|
|
""" Adding Sense amp """
|
|
|
|
y_offset = self.column_mux_height + self.sense_amp_array.height
|
|
self.sense_amp_array_inst=self.add_inst(name="sense_amp_array",
|
|
mod=self.sense_amp_array,
|
|
offset=vector(0,y_offset).scale(-1,-1))
|
|
temp = []
|
|
for i in range(self.word_size):
|
|
temp.append("data_out[{0}]".format(i))
|
|
if self.words_per_row == 1:
|
|
temp.append("bl[{0}]".format(i))
|
|
temp.append("br[{0}]".format(i))
|
|
else:
|
|
temp.append("bl_out[{0}]".format(i))
|
|
temp.append("br_out[{0}]".format(i))
|
|
|
|
temp.extend([self.prefix+"s_en", "vdd", "gnd"])
|
|
self.connect_inst(temp)
|
|
|
|
def add_write_driver_array(self):
|
|
""" Adding Write Driver """
|
|
|
|
y_offset = self.sense_amp_array.height + self.column_mux_height + self.write_driver_array.height
|
|
self.write_driver_array_inst=self.add_inst(name="write_driver_array",
|
|
mod=self.write_driver_array,
|
|
offset=vector(0,y_offset).scale(-1,-1))
|
|
|
|
temp = []
|
|
for i in range(self.word_size):
|
|
temp.append("data_in[{0}]".format(i))
|
|
for i in range(self.word_size):
|
|
if (self.words_per_row == 1):
|
|
temp.append("bl[{0}]".format(i))
|
|
temp.append("br[{0}]".format(i))
|
|
else:
|
|
temp.append("bl_out[{0}]".format(i))
|
|
temp.append("br_out[{0}]".format(i))
|
|
temp.extend([self.prefix+"w_en", "vdd", "gnd"])
|
|
self.connect_inst(temp)
|
|
|
|
def add_msf_data_in(self):
|
|
""" data_in flip_flop """
|
|
|
|
y_offset= self.sense_amp_array.height + self.column_mux_height \
|
|
+ self.write_driver_array.height + self.msf_data_in.height
|
|
self.msf_data_in_inst=self.add_inst(name="data_in_flop_array",
|
|
mod=self.msf_data_in,
|
|
offset=vector(0,y_offset).scale(-1,-1))
|
|
|
|
temp = []
|
|
for i in range(self.word_size):
|
|
temp.append("DATA[{0}]".format(i))
|
|
for i in range(self.word_size):
|
|
temp.append("data_in[{0}]".format(i))
|
|
temp.append("data_in_bar[{0}]".format(i))
|
|
temp.extend([self.prefix+"clk_buf_bar", "vdd", "gnd"])
|
|
self.connect_inst(temp)
|
|
|
|
def add_tri_gate_array(self):
|
|
""" data tri gate to drive the data bus """
|
|
y_offset = self.sense_amp_array.height+self.column_mux_height \
|
|
+ self.write_driver_array.height + self.msf_data_in.height
|
|
self.tri_gate_array_inst=self.add_inst(name="tri_gate_array",
|
|
mod=self.tri_gate_array,
|
|
offset=vector(0,y_offset).scale(-1,-1),
|
|
mirror="MX")
|
|
temp = []
|
|
for i in range(self.word_size):
|
|
temp.append("data_out[{0}]".format(i))
|
|
for i in range(self.word_size):
|
|
temp.append("DATA[{0}]".format(i))
|
|
temp.extend([self.prefix+"tri_en", self.prefix+"tri_en_bar", "vdd", "gnd"])
|
|
self.connect_inst(temp)
|
|
|
|
def add_row_decoder(self):
|
|
""" Add the hierarchical row decoder """
|
|
|
|
|
|
# The address and control bus will be in between decoder and the main memory array
|
|
# This bus will route address bits to the decoder input and column mux inputs.
|
|
# The wires are actually routed after we placed the stuff on both sides.
|
|
# The predecoder is below the x-axis and the main decoder is above the x-axis
|
|
# The address flop and decoder are aligned in the x coord.
|
|
|
|
decoder_x_offset = self.row_decoder.width + self.central_bus_width
|
|
offset = vector(decoder_x_offset,
|
|
self.row_decoder.predecoder_height)
|
|
self.row_decoder_inst=self.add_inst(name="row_decoder",
|
|
mod=self.row_decoder,
|
|
offset=offset.scale(-1,-1))
|
|
|
|
temp = []
|
|
for i in range(self.row_addr_size):
|
|
temp.append("A[{0}]".format(i))
|
|
for j in range(self.num_rows):
|
|
temp.append("dec_out[{0}]".format(j))
|
|
temp.extend(["vdd", "gnd"])
|
|
self.connect_inst(temp)
|
|
|
|
def add_wordline_driver(self):
|
|
""" Wordline Driver """
|
|
|
|
# The wordline driver is placed to the right of the main decoder width.
|
|
# This means that it slightly overlaps with the hierarchical decoder,
|
|
# but it shares power rails. This may differ for other decoders later...
|
|
x_offset = self.row_decoder.width + self.central_bus_width - self.row_decoder.row_decoder_width
|
|
self.wordline_driver_inst=self.add_inst(name="wordline_driver",
|
|
mod=self.wordline_driver,
|
|
offset=vector(x_offset,0).scale(-1,-1))
|
|
|
|
temp = []
|
|
for i in range(self.num_rows):
|
|
temp.append("dec_out[{0}]".format(i))
|
|
for i in range(self.num_rows):
|
|
temp.append("wl[{0}]".format(i))
|
|
temp.append(self.prefix+"clk_buf")
|
|
temp.append("vdd")
|
|
temp.append("gnd")
|
|
self.connect_inst(temp)
|
|
|
|
|
|
def add_column_decoder_module(self):
|
|
"""
|
|
Create a 2:4 or 3:8 column address decoder.
|
|
"""
|
|
# Place the col decoder aligned left to row decoder
|
|
x_off = -(self.central_bus_width + self.row_decoder.width)
|
|
y_off = -(self.row_decoder.predecoder_height + self.col_decoder.height + 2*drc["well_to_well"])
|
|
self.col_decoder_inst=self.add_inst(name="col_address_decoder",
|
|
mod=self.col_decoder,
|
|
offset=vector(x_off,y_off))
|
|
|
|
temp = []
|
|
for i in range(self.col_addr_size):
|
|
temp.append("A[{0}]".format(i + self.row_addr_size))
|
|
for j in range(self.num_col_addr_lines):
|
|
temp.append("sel[{0}]".format(j))
|
|
temp.extend(["vdd", "gnd"])
|
|
self.connect_inst(temp)
|
|
|
|
def add_column_decoder(self):
|
|
"""
|
|
Create a decoder to decode column select lines. This could be an inverter/buffer for 1:2,
|
|
2:4 decoder, or 3:8 decoder.
|
|
"""
|
|
if self.col_addr_size == 0:
|
|
return
|
|
elif self.col_addr_size == 1:
|
|
self.col_decoder = pinvbuf()
|
|
self.add_mod(self.col_decoder)
|
|
elif self.col_addr_size == 2:
|
|
self.col_decoder = self.row_decoder.pre2_4
|
|
elif self.col_addr_size == 3:
|
|
self.col_decoder = self.row_decoder.pre3_8
|
|
else:
|
|
# No error checking before?
|
|
debug.error("Invalid column decoder?",-1)
|
|
|
|
self.add_column_decoder_module()
|
|
|
|
|
|
def add_bank_select(self):
|
|
""" Instantiate the bank select logic. """
|
|
|
|
if not self.num_banks > 1:
|
|
return
|
|
|
|
xoffset = -(self.central_bus_width + self.bank_select.width)
|
|
# extra space to allow vias
|
|
yoffset = self.min_point + 2*self.supply_rail_pitch + self.m1_space
|
|
self.bank_select_pos = vector(xoffset,yoffset)
|
|
self.bank_select_inst = self.add_inst(name="bank_select",
|
|
mod=self.bank_select,
|
|
offset=self.bank_select_pos)
|
|
|
|
temp = []
|
|
temp.extend(self.input_control_signals)
|
|
temp.extend(self.control_signals)
|
|
temp.extend(["vdd", "gnd"])
|
|
self.connect_inst(temp)
|
|
|
|
def route_bank_select(self):
|
|
""" Route the bank select logic. """
|
|
for input_name in self.input_control_signals+["bank_sel"]:
|
|
in_pos = self.bank_select_inst.get_pin(input_name).lc()
|
|
self.add_layout_pin_center_segment(text=input_name,
|
|
layer="metal3",
|
|
start=vector(self.left_gnd_x_offset,in_pos.y),
|
|
end=in_pos)
|
|
|
|
for gated_name in self.control_signals:
|
|
# Connect the inverter output to the central bus
|
|
out_pos = self.bank_select_inst.get_pin(gated_name).rc()
|
|
bus_pos = vector(self.bus_xoffset[gated_name], out_pos.y)
|
|
self.add_path("metal3",[out_pos, bus_pos])
|
|
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
|
offset=bus_pos,
|
|
rotate=90)
|
|
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
|
offset=out_pos,
|
|
rotate=90)
|
|
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
|
offset=out_pos,
|
|
rotate=90)
|
|
|
|
|
|
def setup_layout_constraints(self):
|
|
""" After the modules are instantiated, find the dimensions for the
|
|
control bus, power ring, etc. """
|
|
|
|
#The minimum point is either the bottom of the address flops,
|
|
#the column decoder (if there is one) or the tristate output
|
|
#driver.
|
|
# Leave room for the output below the tri gate.
|
|
tri_gate_min_point = self.tri_gate_array_inst.by() - 3*self.m2_pitch
|
|
row_decoder_min_point = self.row_decoder_inst.by()
|
|
if self.col_addr_size > 0:
|
|
col_decoder_min_point = self.col_decoder_inst.by()
|
|
else:
|
|
col_decoder_min_point = row_decoder_min_point
|
|
|
|
if self.num_banks>1:
|
|
# The control gating logic is below the decoder
|
|
# Min of the control gating logic and tri gate.
|
|
self.min_point = min(col_decoder_min_point - self.bank_select.height, tri_gate_min_point)
|
|
else:
|
|
# Just the min of the decoder logic logic and tri gate.
|
|
self.min_point = min(col_decoder_min_point, tri_gate_min_point)
|
|
|
|
# The max point is always the top of the precharge bitlines
|
|
# Add a vdd and gnd power rail above the array
|
|
self.max_point = self.precharge_array_inst.uy() + 3*self.m1_width
|
|
# Create the core bbox for the power rings
|
|
ur = vector(self.bitcell_array_inst.ur().x + 3*self.m1_width, self.max_point)
|
|
ll = vector(self.row_decoder_inst.lx(), self.min_point)
|
|
self.core_bbox = [ll, ur]
|
|
|
|
# Compute the vertical rail positions for later use
|
|
self.right_gnd_x_offset = ur.x
|
|
self.right_vdd_x_offset = self.right_gnd_x_offset + self.supply_rail_pitch
|
|
self.left_vdd_x_offset = ll.x - self.supply_rail_pitch
|
|
self.left_gnd_x_offset = self.left_vdd_x_offset - self.supply_rail_pitch
|
|
|
|
# Add a vdd and gnd power rail below the array
|
|
self.min_point -= 2*self.supply_rail_pitch
|
|
# Add a vdd and gnd power rail above the array
|
|
self.max_point += self.supply_rail_pitch + self.supply_rail_width
|
|
|
|
self.height = ur.y - ll.y + 4*self.supply_rail_pitch
|
|
self.width = ur.x - ll.x + 4*self.supply_rail_pitch
|
|
|
|
|
|
|
|
|
|
def route_central_bus(self):
|
|
""" Create the address, supply, and control signal central bus lines. """
|
|
|
|
# Overall central bus width. It includes all the column mux lines,
|
|
# control lines, and address flop to decoder lines.
|
|
# The bank is at (0,0), so this is to the left of the y-axis.
|
|
# 2 pitches on the right for vias/jogs to access the inputs
|
|
control_bus_x_offset = -self.m2_pitch * (self.num_control_lines)
|
|
address_bus_x_offset = control_bus_x_offset - self.m2_pitch*(self.num_addr_lines)
|
|
|
|
# Track the bus offsets for other modules to access
|
|
self.bus_xoffset = {}
|
|
|
|
# Control lines
|
|
for i in range(self.num_control_lines):
|
|
x_offset = control_bus_x_offset + i*self.m2_pitch
|
|
# Make the xoffset map the center of the rail
|
|
self.bus_xoffset[self.control_signals[i]]=x_offset + 0.5*self.m2_width
|
|
# Pins are added later if this is a single bank, so just add rectangle now
|
|
self.add_rect(layer="metal2",
|
|
offset=vector(x_offset, self.min_point),
|
|
width=self.m2_width,
|
|
height=self.height)
|
|
|
|
# Row address lines (to left of col address lines)
|
|
# goes from bottom of bitcell array down to the bottom of the column decoder/addresses
|
|
for i in range(self.row_addr_size):
|
|
addr_idx = i + self.col_addr_size
|
|
x_offset = address_bus_x_offset + i*self.m2_pitch
|
|
name = "A[{}]".format(addr_idx)
|
|
# Make the xoffset map the center of the rail
|
|
self.bus_xoffset[name]=x_offset + 0.5*self.m2_width
|
|
# Add a label pin for LVS correspondence and visual help inspecting the rail.
|
|
self.add_layout_pin(text=name,
|
|
layer="metal2",
|
|
offset=vector(x_offset, self.min_point),
|
|
width=self.m2_width,
|
|
height=-self.min_point)
|
|
|
|
# Column mux lines if there is column mux
|
|
# goes from bottom of bitcell array down to the bottom of the column decoder/addresses
|
|
if self.col_addr_size>0:
|
|
for i in range(self.num_col_addr_lines):
|
|
x_offset = address_bus_x_offset + (i+self.row_addr_size)*self.m2_pitch
|
|
name = "sel[{}]".format(i) # One hot select signals
|
|
# Make the xoffset map the center of the rail
|
|
self.bus_xoffset[name]=x_offset + 0.5*self.m2_width
|
|
# Add a label pin for LVS correspondence
|
|
self.add_label_pin(text=name,
|
|
layer="metal2",
|
|
offset=vector(x_offset, self.col_decoder_inst.by()),
|
|
width=self.m2_width,
|
|
height=-self.col_decoder_inst.by())
|
|
|
|
|
|
def route_precharge_to_bitcell_array(self):
|
|
""" Routing of BL and BR between pre-charge and bitcell array """
|
|
|
|
for i in range(self.num_cols):
|
|
precharge_bl = self.precharge_array_inst.get_pin("bl[{}]".format(i)).bc()
|
|
precharge_br = self.precharge_array_inst.get_pin("br[{}]".format(i)).bc()
|
|
bitcell_bl = self.bitcell_array_inst.get_pin("bl[{}]".format(i)).uc()
|
|
bitcell_br = self.bitcell_array_inst.get_pin("br[{}]".format(i)).uc()
|
|
|
|
self.add_path("metal2",[precharge_bl,bitcell_bl])
|
|
self.add_path("metal2",[precharge_br,bitcell_br])
|
|
|
|
def route_sense_amp_to_trigate(self):
|
|
""" Routing of sense amp output to tri_gate input """
|
|
|
|
for i in range(self.word_size):
|
|
|
|
|
|
# Connection of data_out of sense amp to data_ in of msf_data_out
|
|
tri_gate_in = self.tri_gate_array_inst.get_pin("in[{}]".format(i)).bc()
|
|
sa_data_out = self.sense_amp_array_inst.get_pin("data[{}]".format(i)).bc()
|
|
|
|
# if we need a bend or not
|
|
if tri_gate_in.x-sa_data_out.x>self.m2_pitch:
|
|
# We'll connect to the bottom of the SA pin
|
|
bendX = sa_data_out.x
|
|
else:
|
|
# We'll connect to the left of the SA pin
|
|
sa_data_out = self.sense_amp_array_inst.get_pin("data[{}]".format(i)).lc()
|
|
bendX = tri_gate_in.x - 3*self.m3_width
|
|
|
|
bendY = tri_gate_in.y - 2*self.m2_width
|
|
|
|
# Connection point of M2 and M3 paths, below the tri gate and
|
|
# to the left of the tri gate input
|
|
bend = vector(bendX, bendY)
|
|
|
|
# Connect an M2 path to the gate
|
|
mid3 = [tri_gate_in.x, bendY] # guarantee down then left
|
|
self.add_path("metal2", [bend, mid3, tri_gate_in])
|
|
|
|
# connect up then right to sense amp
|
|
mid1 = vector(bendX,sa_data_out.y)
|
|
self.add_path("metal3", [bend, mid1, sa_data_out])
|
|
|
|
|
|
offset = bend - vector([0.5*drc["minwidth_metal3"]] * 2)
|
|
self.add_via(("metal2", "via2", "metal3"),offset)
|
|
|
|
def route_tri_gate_out(self):
|
|
""" Metal 3 routing of tri_gate output data """
|
|
for i in range(self.word_size):
|
|
tri_gate_out_position = self.tri_gate_array_inst.get_pin("out[{}]".format(i)).ul()
|
|
data_line_position = vector(tri_gate_out_position.x, self.min_point)
|
|
self.add_via(("metal2", "via2", "metal3"), data_line_position)
|
|
self.add_rect(layer="metal3",
|
|
offset=data_line_position,
|
|
width=drc["minwidth_metal3"],
|
|
height=tri_gate_out_position.y - self.min_point)
|
|
self.add_layout_pin(text="DATA[{}]".format(i),
|
|
layer="metal2",
|
|
offset=data_line_position,
|
|
height=2*self.m2_width)
|
|
|
|
def route_row_decoder(self):
|
|
""" Routes the row decoder inputs and supplies """
|
|
|
|
for i in range(self.row_addr_size):
|
|
addr_idx = i + self.col_addr_size
|
|
# before this index, we are using 2x4 decoders
|
|
switchover_index = 2*self.row_decoder.no_of_pre2x4
|
|
# so decide what modulus to perform the height spacing
|
|
if i < switchover_index:
|
|
position_heights = i % 2
|
|
else:
|
|
position_heights = (i-switchover_index) % 3
|
|
|
|
# Connect the address rails to the decoder
|
|
# Note that the decoder inputs are long vertical rails so spread out the connections vertically.
|
|
decoder_in_position = self.row_decoder_inst.get_pin("A[{}]".format(i)).lr() \
|
|
+ vector(0,position_heights*self.bitcell.height+self.m2_pitch)
|
|
rail_position = vector(self.bus_xoffset["A[{}]".format(addr_idx)],
|
|
decoder_in_position.y)
|
|
self.add_path("metal1",[decoder_in_position,rail_position])
|
|
|
|
decoder_in_via = decoder_in_position - vector(0,0.5*self.m2_width)
|
|
self.add_via(layers=("metal1", "via1", "metal2"),
|
|
offset=decoder_in_via,
|
|
rotate=90)
|
|
|
|
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
|
offset=rail_position,
|
|
rotate=90)
|
|
|
|
# Route the power and ground, but only BELOW the y=0 since the
|
|
# others are connected with the wordline driver.
|
|
# These must be on M3 to not interfere with column mux address pins.
|
|
for gnd_pin in self.row_decoder_inst.get_pins("gnd"):
|
|
if gnd_pin.uy()>0:
|
|
continue
|
|
gnd_position = gnd_pin.rc()
|
|
left_rail_position = vector(self.left_gnd_x_center, gnd_position.y)
|
|
self.add_path("metal1", [left_rail_position, gnd_position])
|
|
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
|
offset=left_rail_position,
|
|
size = (1,3),
|
|
rotate=90)
|
|
|
|
# route the vdd rails
|
|
for vdd_pin in self.row_decoder_inst.get_pins("vdd"):
|
|
if vdd_pin.uy()>0:
|
|
continue
|
|
vdd_y_pos = vdd_pin.cy()
|
|
left_rail_position = vector(self.left_vdd_x_center, vdd_y_pos)
|
|
right_rail_position = vector(self.row_decoder_inst.ur().x, vdd_y_pos)
|
|
self.add_path("metal1", [left_rail_position, right_rail_position])
|
|
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
|
offset=left_rail_position,
|
|
size = (1,3),
|
|
rotate=90)
|
|
|
|
|
|
def route_wordline_driver(self):
|
|
""" Connecting Wordline driver output to Bitcell WL connection """
|
|
|
|
# we don't care about bends after connecting to the input pin, so let the path code decide.
|
|
for i in range(self.num_rows):
|
|
# The pre/post is to access the pin from "outside" the cell to avoid DRCs
|
|
decoder_out_pos = self.row_decoder_inst.get_pin("decode[{}]".format(i)).rc()
|
|
driver_in_pos = self.wordline_driver_inst.get_pin("in[{}]".format(i)).lc()
|
|
mid1 = decoder_out_pos.scale(0.5,1)+driver_in_pos.scale(0.5,0)
|
|
mid2 = decoder_out_pos.scale(0.5,0)+driver_in_pos.scale(0.5,1)
|
|
self.add_path("metal1", [decoder_out_pos, mid1, mid2, driver_in_pos])
|
|
|
|
# The mid guarantees we exit the input cell to the right.
|
|
driver_wl_pos = self.wordline_driver_inst.get_pin("wl[{}]".format(i)).rc()
|
|
bitcell_wl_pos = self.bitcell_array_inst.get_pin("wl[{}]".format(i)).lc()
|
|
mid1 = driver_wl_pos.scale(0.5,1)+bitcell_wl_pos.scale(0.5,0)
|
|
mid2 = driver_wl_pos.scale(0.5,0)+bitcell_wl_pos.scale(0.5,1)
|
|
self.add_path("metal1", [driver_wl_pos, mid1, mid2, bitcell_wl_pos])
|
|
|
|
|
|
|
|
|
|
|
|
def route_column_address_lines(self):
|
|
""" Connecting the select lines of column mux to the address bus """
|
|
if not self.col_addr_size>0:
|
|
return
|
|
|
|
# Connect the select lines to the column mux
|
|
# These must be in metal3 so that they don't overlap any gnd lines from decoders
|
|
for i in range(self.num_col_addr_lines):
|
|
name = "sel[{}]".format(i)
|
|
mux_addr_pos = self.col_mux_array_inst.get_pin(name).lc()
|
|
wire_pos = vector(self.bus_xoffset[name], mux_addr_pos.y)
|
|
self.add_path("metal1", [wire_pos,mux_addr_pos])
|
|
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
|
offset=wire_pos,
|
|
rotate=90)
|
|
|
|
if self.col_addr_size == 1:
|
|
|
|
decode_out_pos = self.col_decoder_inst.get_pin("Zb").rc()
|
|
selx_pos = vector(self.bus_xoffset["sel[0]"],decode_out_pos.y)
|
|
self.add_path("metal1",[decode_out_pos, selx_pos])
|
|
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
|
offset=selx_pos,
|
|
rotate=90)
|
|
decode_out_pos = self.col_decoder_inst.get_pin("Z").rc()
|
|
selx_pos = vector(self.bus_xoffset["sel[1]"],decode_out_pos.y)
|
|
self.add_path("metal1",[decode_out_pos, selx_pos])
|
|
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
|
offset=selx_pos,
|
|
rotate=90)
|
|
|
|
# The Address LSB
|
|
decode_in_pin = self.col_decoder_inst.get_pin("A")
|
|
pin_pos = vector(decode_in_pin.cx(), self.min_point)
|
|
self.add_layout_pin_center_segment(text="A[0]",
|
|
layer="metal2",
|
|
start=pin_pos,
|
|
end=decode_in_pin.bc())
|
|
|
|
elif self.col_addr_size > 1:
|
|
# Route the col decoder outputs to the col select bus
|
|
for i in range(self.num_col_addr_lines):
|
|
name = "sel[{}]".format(i)
|
|
decode_out_pos = self.col_decoder_inst.get_pin("out[{}]".format(i)).rc()
|
|
selx_pos = vector(self.bus_xoffset[name],decode_out_pos.y)
|
|
self.add_path("metal1",[decode_out_pos, selx_pos])
|
|
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
|
offset=selx_pos,
|
|
rotate=90)
|
|
|
|
# Route from the col decoder up to the address bus
|
|
for i in range(self.col_addr_size):
|
|
decoder_name = "in[{}]".format(i)
|
|
addr_name = "A[{}]".format(i)
|
|
decode_in_pin = self.col_decoder_inst.get_pin(decoder_name)
|
|
pin_pos = vector(decode_in_pin.cx(), self.min_point)
|
|
self.add_layout_pin_center_segment(text=addr_name,
|
|
layer="metal2",
|
|
start=pin_pos,
|
|
end=decode_in_pin.bc())
|
|
|
|
|
|
# route the gnd rails, add contact to rail as well
|
|
for gnd_pin in self.col_decoder_inst.get_pins("gnd"):
|
|
left_rail_pos = vector(self.left_gnd_x_center, gnd_pin.cy())
|
|
self.add_path("metal1", [left_rail_pos, gnd_pin.rc()])
|
|
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
|
offset=left_rail_pos,
|
|
size = (1,3),
|
|
rotate=90)
|
|
|
|
# route the vdd rails
|
|
for vdd_pin in self.col_decoder_inst.get_pins("vdd"):
|
|
left_rail_pos = vector(self.left_vdd_x_center, vdd_pin.cy())
|
|
self.add_path("metal1", [left_rail_pos, vdd_pin.rc()])
|
|
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
|
offset=left_rail_pos,
|
|
size = (1,3),
|
|
rotate=90)
|
|
|
|
|
|
|
|
|
|
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.
|
|
"""
|
|
# Add the wordline names
|
|
for i in range(self.num_rows):
|
|
wl_name = "wl[{}]".format(i)
|
|
wl_pin = self.bitcell_array_inst.get_pin(wl_name)
|
|
self.add_label(text=wl_name,
|
|
layer="metal1",
|
|
offset=wl_pin.ll())
|
|
|
|
# Add the bitline names
|
|
for i in range(self.num_cols):
|
|
bl_name = "bl[{}]".format(i)
|
|
br_name = "br[{}]".format(i)
|
|
bl_pin = self.bitcell_array_inst.get_pin(bl_name)
|
|
br_pin = self.bitcell_array_inst.get_pin(br_name)
|
|
self.add_label(text=bl_name,
|
|
layer="metal2",
|
|
offset=bl_pin.ll())
|
|
self.add_label(text=br_name,
|
|
layer="metal2",
|
|
offset=br_pin.ll())
|
|
|
|
# Add the data input names to the data flop output
|
|
for i in range(self.word_size):
|
|
dout_name = "dout[{}]".format(i)
|
|
dout_pin = self.msf_data_in_inst.get_pin(dout_name)
|
|
self.add_label(text="data_in[{}]".format(i),
|
|
layer="metal2",
|
|
offset=dout_pin.ll())
|
|
|
|
# Add the data output names to the sense amp output
|
|
for i in range(self.word_size):
|
|
data_name = "data[{}]".format(i)
|
|
data_pin = self.sense_amp_array_inst.get_pin(data_name)
|
|
self.add_label(text="data_out[{}]".format(i),
|
|
layer="metal3",
|
|
offset=data_pin.ll())
|
|
|
|
|
|
def route_control_lines(self):
|
|
""" Route the control lines of the entire bank """
|
|
|
|
# Make a list of tuples that we will connect.
|
|
# From control signal to the module pin
|
|
# Connection from the central bus to the main control block crosses
|
|
# pre-decoder and this connection is in metal3
|
|
connection = []
|
|
connection.append((self.prefix+"clk_buf_bar", self.msf_data_in_inst.get_pin("clk").lc()))
|
|
connection.append((self.prefix+"tri_en_bar", self.tri_gate_array_inst.get_pin("en_bar").lc()))
|
|
connection.append((self.prefix+"tri_en", self.tri_gate_array_inst.get_pin("en").lc()))
|
|
connection.append((self.prefix+"clk_buf_bar", self.precharge_array_inst.get_pin("en").lc()))
|
|
connection.append((self.prefix+"w_en", self.write_driver_array_inst.get_pin("en").lc()))
|
|
connection.append((self.prefix+"s_en", self.sense_amp_array_inst.get_pin("en").lc()))
|
|
|
|
for (control_signal, pin_pos) in connection:
|
|
control_pos = vector(self.bus_xoffset[control_signal], pin_pos.y)
|
|
self.add_path("metal1", [control_pos, pin_pos])
|
|
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
|
offset=control_pos,
|
|
rotate=90)
|
|
|
|
# clk to wordline_driver
|
|
control_signal = self.prefix+"clk_buf"
|
|
pin_pos = self.wordline_driver_inst.get_pin("en").uc()
|
|
mid_pos = pin_pos + vector(0,self.m1_pitch)
|
|
control_x_offset = self.bus_xoffset[control_signal]
|
|
control_pos = vector(control_x_offset + self.m1_width, mid_pos.y)
|
|
self.add_wire(("metal1","via1","metal2"),[pin_pos, mid_pos, control_pos])
|
|
control_via_pos = vector(control_x_offset, mid_pos.y)
|
|
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
|
offset=control_via_pos,
|
|
rotate=90)
|
|
|
|
|
|
def route_vdd_supply(self):
|
|
""" Route vdd for the precharge, sense amp, write_driver, data FF, tristate """
|
|
|
|
# Route the vdd rails to the RIGHT
|
|
modules = [self.precharge_array_inst, self.sense_amp_array_inst,
|
|
self.write_driver_array_inst, self.msf_data_in_inst,
|
|
self.tri_gate_array_inst]
|
|
for inst in modules:
|
|
for vdd_pin in inst.get_pins("vdd"):
|
|
self.add_rect(layer="metal1",
|
|
offset=vdd_pin.ll(),
|
|
width=self.right_vdd_x_center,
|
|
height=vdd_pin.height())
|
|
via_position = vector(self.right_vdd_x_center, vdd_pin.cy())
|
|
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
|
offset=via_position,
|
|
size = (1,3),
|
|
rotate=90)
|
|
|
|
# Route the vdd rails to the LEFT
|
|
for vdd_pin in self.wordline_driver_inst.get_pins("vdd"):
|
|
vdd_pos = vdd_pin.rc()
|
|
left_rail_pos = vector(self.left_vdd_x_center, vdd_pos.y)
|
|
right_rail_pos = vector(self.right_vdd_x_center, vdd_pos.y)
|
|
self.add_path("metal1", [left_rail_pos, right_rail_pos])
|
|
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
|
offset=left_rail_pos,
|
|
size = (1,3),
|
|
rotate=90)
|
|
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
|
offset=right_rail_pos,
|
|
size = (1,3),
|
|
rotate=90)
|
|
|
|
if self.num_banks>1:
|
|
for vdd_pin in self.bank_select_inst.get_pins("vdd"):
|
|
vdd_pos = vdd_pin.rc()
|
|
left_rail_pos = vector(self.left_vdd_x_center, vdd_pos.y)
|
|
self.add_path("metal1", [left_rail_pos, vdd_pos])
|
|
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
|
offset=left_rail_pos,
|
|
size = (1,3),
|
|
rotate=90)
|
|
|
|
|
|
def route_gnd_supply(self):
|
|
""" Route gnd rails"""
|
|
# Route the gnd rails to the RIGHT
|
|
# precharge is connected by abutment
|
|
modules = [ self.tri_gate_array_inst, self.sense_amp_array_inst, self.msf_data_in_inst, self.write_driver_array_inst]
|
|
for inst in modules:
|
|
for gnd_pin in inst.get_pins("gnd"):
|
|
if gnd_pin.layer != "metal1":
|
|
continue
|
|
# route to the right hand side of the big rail to reduce via overlaps
|
|
pin_pos = gnd_pin.lc()
|
|
gnd_offset = vector(self.right_gnd_x_offset + self.supply_rail_width, pin_pos.y)
|
|
self.add_path("metal1", [pin_pos, gnd_offset])
|
|
via_position = vector(self.right_gnd_x_center, gnd_pin.cy())
|
|
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
|
offset=via_position,
|
|
size = (1,3),
|
|
rotate=90)
|
|
|
|
# Route the gnd rails to the LEFT
|
|
modules = [self.wordline_driver_inst]
|
|
if self.num_banks>1:
|
|
modules.append(self.bank_select_inst)
|
|
for inst in modules:
|
|
for gnd_pin in inst.get_pins("gnd"):
|
|
gnd_pos = gnd_pin.rc()
|
|
left_rail_pos = vector(self.left_gnd_x_center, gnd_pos.y)
|
|
self.add_path("metal1", [left_rail_pos, gnd_pos])
|
|
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
|
offset=left_rail_pos,
|
|
size = (1,3),
|
|
rotate=90)
|
|
|
|
def add_control_pins(self):
|
|
""" Add the control signal input pins """
|
|
|
|
for ctrl in self.control_signals:
|
|
# xoffsets are the center of the rail
|
|
x_offset = self.bus_xoffset[ctrl] - 0.5*self.m2_width
|
|
if self.num_banks > 1:
|
|
# it's not an input pin if we have multiple banks
|
|
self.add_label_pin(text=ctrl,
|
|
layer="metal2",
|
|
offset=vector(x_offset, self.min_point),
|
|
width=self.m2_width,
|
|
height=self.height)
|
|
else:
|
|
self.add_layout_pin(text=ctrl,
|
|
layer="metal2",
|
|
offset=vector(x_offset, self.min_point),
|
|
width=self.m2_width,
|
|
height=self.height)
|
|
|
|
|
|
def connect_rail_from_right(self,inst, pin, rail):
|
|
""" Helper routine to connect an unrotated/mirrored oriented instance to the rails """
|
|
in_pin = inst.get_pin(pin).lc()
|
|
rail_pos = vector(self.rail_1_x_offsets[rail], in_pin.y)
|
|
self.add_wire(("metal3","via2","metal2"),[in_pin, rail_pos, rail_pos - vector(0,self.m2_pitch)])
|
|
# Bring it up to M2 for M2/M3 routing
|
|
self.add_via(layers=("metal1","via1","metal2"),
|
|
offset=in_pin + contact.m1m2.offset,
|
|
rotate=90)
|
|
self.add_via(layers=("metal2","via2","metal3"),
|
|
offset=in_pin + self.m2m3_via_offset,
|
|
rotate=90)
|
|
|
|
|
|
def connect_rail_from_left(self,inst, pin, rail):
|
|
""" Helper routine to connect an unrotated/mirrored oriented instance to the rails """
|
|
in_pin = inst.get_pin(pin).rc()
|
|
rail_pos = vector(self.rail_1_x_offsets[rail], in_pin.y)
|
|
self.add_wire(("metal3","via2","metal2"),[in_pin, rail_pos, rail_pos - vector(0,self.m2_pitch)])
|
|
self.add_via(layers=("metal1","via1","metal2"),
|
|
offset=in_pin + contact.m1m2.offset,
|
|
rotate=90)
|
|
self.add_via(layers=("metal2","via2","metal3"),
|
|
offset=in_pin + self.m2m3_via_offset,
|
|
rotate=90)
|
|
|
|
def analytical_delay(self, slew, load):
|
|
""" return analytical delay of the bank"""
|
|
msf_addr_delay = self.msf_address.analytical_delay(slew, self.row_decoder.input_load())
|
|
|
|
decoder_delay = self.row_decoder.analytical_delay(msf_addr_delay.slew, self.wordline_driver.input_load())
|
|
|
|
word_driver_delay = self.wordline_driver.analytical_delay(decoder_delay.slew, self.bitcell_array.input_load())
|
|
|
|
bitcell_array_delay = self.bitcell_array.analytical_delay(word_driver_delay.slew)
|
|
|
|
bl_t_data_out_delay = self.sense_amp_array.analytical_delay(bitcell_array_delay.slew,
|
|
self.bitcell_array.output_load())
|
|
# output load of bitcell_array is set to be only small part of bl for sense amp.
|
|
|
|
data_t_DATA_delay = self.tri_gate_array.analytical_delay(bl_t_data_out_delay.slew, load)
|
|
|
|
result = msf_addr_delay + decoder_delay + word_driver_delay \
|
|
+ bitcell_array_delay + bl_t_data_out_delay + data_t_DATA_delay
|
|
return result
|
|
|