mirror of https://github.com/VLSIDA/OpenRAM.git
1280 lines
56 KiB
Python
1280 lines
56 KiB
Python
import sys
|
|
from tech import drc, parameter
|
|
import debug
|
|
import design
|
|
import math
|
|
from math import log,sqrt,ceil
|
|
import contact
|
|
import pgates
|
|
from sram_factory import factory
|
|
from vector import vector
|
|
|
|
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.
|
|
This can create up to two ports in any combination: rw, w, r.
|
|
"""
|
|
|
|
def __init__(self, sram_config, name=""):
|
|
|
|
sram_config.set_local_config(self)
|
|
|
|
if name == "":
|
|
name = "bank_{0}_{1}".format(self.word_size, self.num_words)
|
|
design.design.__init__(self, name)
|
|
debug.info(2, "create sram of size {0} with {1} words".format(self.word_size,self.num_words))
|
|
|
|
# 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.create_netlist()
|
|
if not OPTS.netlist_only:
|
|
debug.check(len(self.all_ports)<=2,"Bank layout cannot handle more than two ports.")
|
|
self.create_layout()
|
|
|
|
|
|
def create_netlist(self):
|
|
self.compute_sizes()
|
|
self.add_pins()
|
|
self.add_modules()
|
|
self.create_instances()
|
|
|
|
|
|
def create_layout(self):
|
|
self.place_instances()
|
|
self.setup_routing_constraints()
|
|
self.route_layout()
|
|
|
|
# Can remove the following, but it helps for debug!
|
|
#self.add_lvs_correspondence_points()
|
|
|
|
# Remember the bank center for further placement
|
|
self.bank_array_ll = self.offset_all_coordinates().scale(-1,-1)
|
|
self.bank_array_ur = self.bitcell_array_inst.ur()
|
|
|
|
self.DRC_LVS()
|
|
|
|
def add_pins(self):
|
|
""" Adding pins for Bank module"""
|
|
for port in self.read_ports:
|
|
for bit in range(self.word_size):
|
|
self.add_pin("dout{0}_{1}".format(port,bit),"OUT")
|
|
for port in self.write_ports:
|
|
for bit in range(self.word_size):
|
|
self.add_pin("din{0}_{1}".format(port,bit),"IN")
|
|
for port in self.all_ports:
|
|
for bit in range(self.addr_size):
|
|
self.add_pin("addr{0}_{1}".format(port,bit),"INPUT")
|
|
|
|
# For more than one bank, we have a bank select and name
|
|
# the signals gated_*.
|
|
if self.num_banks > 1:
|
|
for port in self.all_ports:
|
|
self.add_pin("bank_sel{}".format(port),"INPUT")
|
|
for port in self.read_ports:
|
|
self.add_pin("s_en{0}".format(port), "INPUT")
|
|
for port in self.read_ports:
|
|
self.add_pin("p_en_bar{0}".format(port), "INPUT")
|
|
for port in self.write_ports:
|
|
self.add_pin("w_en{0}".format(port), "INPUT")
|
|
for port in self.all_ports:
|
|
self.add_pin("wl_en{0}".format(port), "INPUT")
|
|
self.add_pin("vdd","POWER")
|
|
self.add_pin("gnd","GROUND")
|
|
|
|
|
|
def route_layout(self):
|
|
""" Create routing amoung the modules """
|
|
self.route_central_bus()
|
|
|
|
for port in self.all_ports:
|
|
self.route_bitlines(port)
|
|
self.route_wordline_driver(port)
|
|
self.route_row_decoder(port)
|
|
self.route_column_address_lines(port)
|
|
self.route_control_lines(port)
|
|
if self.num_banks > 1:
|
|
self.route_bank_select(port)
|
|
|
|
self.route_supplies()
|
|
|
|
def route_bitlines(self, port):
|
|
""" Route the bitlines depending on the port type rw, w, or r. """
|
|
|
|
if port in self.readwrite_ports:
|
|
# write_driver -> sense_amp -> (column_mux) -> precharge -> bitcell_array
|
|
self.route_write_driver_in(port)
|
|
self.route_sense_amp_out(port)
|
|
self.route_write_driver_to_sense_amp(port)
|
|
self.route_sense_amp_to_column_mux_or_precharge_array(port)
|
|
self.route_column_mux_to_precharge_array(port)
|
|
self.route_precharge_to_bitcell_array(port)
|
|
elif port in self.read_ports:
|
|
# sense_amp -> (column_mux) -> precharge -> bitcell_array
|
|
self.route_sense_amp_out(port)
|
|
self.route_sense_amp_to_column_mux_or_precharge_array(port)
|
|
self.route_column_mux_to_precharge_array(port)
|
|
self.route_precharge_to_bitcell_array(port)
|
|
else:
|
|
# write_driver -> (column_mux) -> bitcell_array
|
|
self.route_write_driver_in(port)
|
|
self.route_write_driver_to_column_mux_or_bitcell_array(port)
|
|
self.route_column_mux_to_bitcell_array(port)
|
|
|
|
def create_instances(self):
|
|
""" Create the instances of the netlist. """
|
|
|
|
self.create_bitcell_array()
|
|
|
|
self.create_precharge_array()
|
|
self.create_column_mux_array()
|
|
self.create_sense_amp_array()
|
|
self.create_write_driver_array()
|
|
|
|
self.create_row_decoder()
|
|
self.create_wordline_driver()
|
|
self.create_column_decoder()
|
|
|
|
self.create_bank_select()
|
|
|
|
def compute_instance_offsets(self):
|
|
"""
|
|
Compute the empty instance offsets for port0 and port1 (if needed)
|
|
"""
|
|
|
|
# These are created even if the port type (e.g. read only)
|
|
# doesn't need the instance (e.g. write driver).
|
|
|
|
# Create the bottom-up and left to right order of components in each port
|
|
# which deepends on the port type rw, w, r
|
|
self.vertical_port_order = []
|
|
self.vertical_port_offsets = []
|
|
for port in self.all_ports:
|
|
self.vertical_port_order.append([])
|
|
self.vertical_port_offsets.append([None]*4)
|
|
|
|
# For later placement, these are fixed in the order: write driver,
|
|
# sense amp, clumn mux, precharge, even if the item is not used
|
|
# in a given port (it will be None then)
|
|
self.vertical_port_order[port].append(self.write_driver_array_inst[port])
|
|
self.vertical_port_order[port].append(self.sense_amp_array_inst[port])
|
|
self.vertical_port_order[port].append(self.column_mux_array_inst[port])
|
|
self.vertical_port_order[port].append(self.precharge_array_inst[port])
|
|
|
|
# For the odd ones they will go on top, so reverse in place
|
|
if port%2:
|
|
self.vertical_port_order[port]=self.vertical_port_order[port][::-1]
|
|
|
|
self.write_driver_offsets = [None]*len(self.all_ports)
|
|
self.sense_amp_offsets = [None]*len(self.all_ports)
|
|
self.column_mux_offsets = [None]*len(self.all_ports)
|
|
self.precharge_offsets = [None]*len(self.all_ports)
|
|
|
|
self.wordline_driver_offsets = [None]*len(self.all_ports)
|
|
self.row_decoder_offsets = [None]*len(self.all_ports)
|
|
|
|
self.column_decoder_offsets = [None]*len(self.all_ports)
|
|
self.bank_select_offsets = [None]*len(self.all_ports)
|
|
|
|
self.compute_instance_port0_offsets()
|
|
if len(self.all_ports)==2:
|
|
self.compute_instance_port1_offsets()
|
|
|
|
|
|
def compute_instance_port0_offsets(self):
|
|
"""
|
|
Compute the instance offsets for port0.
|
|
"""
|
|
|
|
port = 0
|
|
|
|
# UPPER RIGHT QUADRANT
|
|
# Bitcell array is placed at (0,0)
|
|
self.bitcell_array_offset = vector(0,0)
|
|
|
|
# LOWER RIGHT QUADRANT
|
|
# Below the bitcell array
|
|
y_height = 0
|
|
for p in self.vertical_port_order[port]:
|
|
if p==None:
|
|
continue
|
|
y_height += p.height + self.m2_gap
|
|
|
|
y_offset = -y_height
|
|
for i,p in enumerate(self.vertical_port_order[port]):
|
|
if p==None:
|
|
continue
|
|
self.vertical_port_offsets[port][i]=vector(0,y_offset)
|
|
y_offset += (p.height + self.m2_gap)
|
|
|
|
self.write_driver_offsets[port] = self.vertical_port_offsets[port][0]
|
|
self.sense_amp_offsets[port] = self.vertical_port_offsets[port][1]
|
|
self.column_mux_offsets[port] = self.vertical_port_offsets[port][2]
|
|
self.precharge_offsets[port] = self.vertical_port_offsets[port][3]
|
|
|
|
# UPPER LEFT QUADRANT
|
|
# To the left of the bitcell array
|
|
# The wordline driver is placed to the right of the main decoder width.
|
|
x_offset = self.m2_gap + self.wordline_driver.width
|
|
self.wordline_driver_offsets[port] = vector(-x_offset,0)
|
|
x_offset += self.row_decoder.width + self.m2_gap
|
|
self.row_decoder_offsets[port] = vector(-x_offset,0)
|
|
|
|
# LOWER LEFT QUADRANT
|
|
# Place the col decoder left aligned with wordline driver plus halfway under row decoder
|
|
# Place the col decoder left aligned with row decoder (x_offset doesn't change)
|
|
# Below the bitcell array with well spacing
|
|
x_offset = self.central_bus_width[port] + self.wordline_driver.width
|
|
if self.col_addr_size > 0:
|
|
x_offset += self.column_decoder.width + self.col_addr_bus_width
|
|
y_offset = self.m2_gap + self.column_decoder.height
|
|
else:
|
|
y_offset = 0
|
|
y_offset += 2*drc("well_to_well")
|
|
self.column_decoder_offsets[port] = vector(-x_offset,-y_offset)
|
|
|
|
# Bank select gets placed below the column decoder (x_offset doesn't change)
|
|
if self.col_addr_size > 0:
|
|
y_offset = min(self.column_decoder_offsets[port].y, self.column_mux_offsets[port].y)
|
|
else:
|
|
y_offset = self.row_decoder_offsets[port].y
|
|
if self.num_banks > 1:
|
|
y_offset += self.bank_select.height + drc("well_to_well")
|
|
self.bank_select_offsets[port] = vector(-x_offset,-y_offset)
|
|
|
|
def compute_instance_port1_offsets(self):
|
|
"""
|
|
Compute the instance offsets for port1 on the top of the bank.
|
|
"""
|
|
|
|
port=1
|
|
|
|
# The center point for these cells are the upper-right corner of
|
|
# the bitcell array.
|
|
# The decoder/driver logic is placed on the right and mirrored on Y-axis.
|
|
# The write/sense/precharge/mux is placed on the top and mirrored on the X-axis.
|
|
|
|
# LOWER LEFT QUADRANT
|
|
# Bitcell array is placed at (0,0)
|
|
|
|
# UPPER LEFT QUADRANT
|
|
# Above the bitcell array
|
|
y_offset = self.bitcell_array.height + self.m2_gap
|
|
for i,p in enumerate(self.vertical_port_order[port]):
|
|
if p==None:
|
|
continue
|
|
y_offset += (p.height + self.m2_gap)
|
|
self.vertical_port_offsets[port][i]=vector(0,y_offset)
|
|
|
|
# Reversed order
|
|
self.write_driver_offsets[port] = self.vertical_port_offsets[port][3]
|
|
self.sense_amp_offsets[port] = self.vertical_port_offsets[port][2]
|
|
self.column_mux_offsets[port] = self.vertical_port_offsets[port][1]
|
|
self.precharge_offsets[port] = self.vertical_port_offsets[port][0]
|
|
|
|
# LOWER RIGHT QUADRANT
|
|
# To the left of the bitcell array
|
|
# The wordline driver is placed to the right of the main decoder width.
|
|
x_offset = self.bitcell_array.width + self.m2_gap + self.wordline_driver.width
|
|
self.wordline_driver_offsets[port] = vector(x_offset,0)
|
|
x_offset += self.row_decoder.width + self.m2_gap
|
|
self.row_decoder_offsets[port] = vector(x_offset,0)
|
|
|
|
# UPPER RIGHT QUADRANT
|
|
# Place the col decoder right aligned with wordline driver plus halfway under row decoder
|
|
# Above the bitcell array with a well spacing
|
|
x_offset = self.bitcell_array.width + self.central_bus_width[port] + self.wordline_driver.width
|
|
if self.col_addr_size > 0:
|
|
x_offset += self.column_decoder.width + self.col_addr_bus_width
|
|
y_offset = self.bitcell_array.height + self.column_decoder.height + self.m2_gap
|
|
else:
|
|
y_offset = self.bitcell_array.height
|
|
y_offset += 2*drc("well_to_well")
|
|
self.column_decoder_offsets[port] = vector(x_offset,y_offset)
|
|
|
|
# Bank select gets placed above the column decoder (x_offset doesn't change)
|
|
if self.col_addr_size > 0:
|
|
y_offset = max(self.column_decoder_offsets[port].y + self.column_decoder.height,
|
|
self.column_mux_offsets[port].y + self.column_mux_array[port].height)
|
|
else:
|
|
y_offset = self.row_decoder_offsets[port].y
|
|
self.bank_select_offsets[port] = vector(x_offset,y_offset)
|
|
|
|
def place_instances(self):
|
|
""" Place the instances. """
|
|
|
|
self.compute_instance_offsets()
|
|
|
|
# UPPER RIGHT QUADRANT
|
|
self.place_bitcell_array(self.bitcell_array_offset)
|
|
|
|
# LOWER RIGHT QUADRANT
|
|
# These are fixed in the order: write driver, sense amp, clumn mux, precharge,
|
|
# even if the item is not used in a given port (it will be None then)
|
|
self.place_write_driver_array(self.write_driver_offsets)
|
|
self.place_sense_amp_array(self.sense_amp_offsets)
|
|
self.place_column_mux_array(self.column_mux_offsets)
|
|
self.place_precharge_array(self.precharge_offsets)
|
|
|
|
# UPPER LEFT QUADRANT
|
|
self.place_row_decoder(self.row_decoder_offsets)
|
|
self.place_wordline_driver(self.wordline_driver_offsets)
|
|
|
|
# LOWER LEFT QUADRANT
|
|
self.place_column_decoder(self.column_decoder_offsets)
|
|
self.place_bank_select(self.bank_select_offsets)
|
|
|
|
|
|
def compute_sizes(self):
|
|
""" Computes the required sizes to create the bank """
|
|
|
|
self.num_cols = int(self.words_per_row*self.word_size)
|
|
self.num_rows = int(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
|
|
# FIXME: This spacing should be width dependent...
|
|
self.supply_rail_pitch = self.supply_rail_width + 4*self.m2_space
|
|
|
|
# The order of the control signals on the control bus:
|
|
self.input_control_signals = []
|
|
port_num = 0
|
|
for port in range(OPTS.num_rw_ports):
|
|
self.input_control_signals.append(["wl_en{}".format(port_num), "w_en{}".format(port_num), "s_en{}".format(port_num), "p_en_bar{}".format(port_num)])
|
|
port_num += 1
|
|
for port in range(OPTS.num_w_ports):
|
|
self.input_control_signals.append(["wl_en{}".format(port_num), "w_en{}".format(port_num)])
|
|
port_num += 1
|
|
for port in range(OPTS.num_r_ports):
|
|
self.input_control_signals.append(["wl_en{}".format(port_num), "s_en{}".format(port_num), "p_en_bar{}".format(port_num)])
|
|
port_num += 1
|
|
|
|
# Number of control lines in the bus for each port
|
|
self.num_control_lines = [len(x) for x in self.input_control_signals]
|
|
|
|
# The width of this bus is needed to place other modules (e.g. decoder) for each port
|
|
self.central_bus_width = [self.m2_pitch*x + self.m2_width for x in self.num_control_lines]
|
|
|
|
# These will be outputs of the gaters if this is multibank, if not, normal signals.
|
|
self.control_signals = []
|
|
for port in self.all_ports:
|
|
if self.num_banks > 1:
|
|
self.control_signals.append(["gated_"+str for str in self.input_control_signals[port]])
|
|
else:
|
|
self.control_signals.append(self.input_control_signals[port])
|
|
|
|
# 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
|
|
else:
|
|
self.num_col_addr_lines = 0
|
|
self.col_addr_bus_width = self.m2_pitch*self.num_col_addr_lines
|
|
|
|
# A space for wells or jogging m2
|
|
self.m2_gap = max(2*drc("pwell_to_nwell") + drc("well_enclosure_active"),
|
|
3*self.m2_pitch)
|
|
|
|
|
|
def add_modules(self):
|
|
""" Add all the modules using the class loader """
|
|
|
|
|
|
self.bitcell_array = factory.create(module_type="bitcell_array",
|
|
cols=self.num_cols,
|
|
rows=self.num_rows)
|
|
self.add_mod(self.bitcell_array)
|
|
|
|
# create arrays of bitline and bitline_bar names for read, write, or all ports
|
|
self.bitcell = factory.create(module_type="bitcell")
|
|
self.bl_names = self.bitcell.list_all_bl_names()
|
|
self.br_names = self.bitcell.list_all_br_names()
|
|
self.wl_names = self.bitcell.list_all_wl_names()
|
|
self.bitline_names = self.bitcell.list_all_bitline_names()
|
|
|
|
self.precharge_array = []
|
|
for port in self.all_ports:
|
|
if port in self.read_ports:
|
|
temp_pre = factory.create(module_type="precharge_array",
|
|
columns=self.num_cols,
|
|
bitcell_bl=self.bl_names[port],
|
|
bitcell_br=self.br_names[port])
|
|
self.precharge_array.append(temp_pre)
|
|
self.add_mod(self.precharge_array[port])
|
|
else:
|
|
self.precharge_array.append(None)
|
|
|
|
if self.col_addr_size > 0:
|
|
self.column_mux_array = []
|
|
for port in self.all_ports:
|
|
temp_col = factory.create(module_type="column_mux_array",
|
|
columns=self.num_cols,
|
|
word_size=self.word_size,
|
|
bitcell_bl=self.bl_names[port],
|
|
bitcell_br=self.br_names[port])
|
|
self.column_mux_array.append(temp_col)
|
|
self.add_mod(self.column_mux_array[port])
|
|
|
|
|
|
self.sense_amp_array = factory.create(module_type="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 = factory.create(module_type="write_driver_array",
|
|
columns=self.num_cols,
|
|
word_size=self.word_size)
|
|
self.add_mod(self.write_driver_array)
|
|
|
|
self.row_decoder = factory.create(module_type="decoder",
|
|
rows=self.num_rows)
|
|
self.add_mod(self.row_decoder)
|
|
|
|
self.wordline_driver = factory.create(module_type="wordline_driver",
|
|
rows=self.num_rows,
|
|
cols=self.num_cols)
|
|
self.add_mod(self.wordline_driver)
|
|
|
|
self.inv = factory.create(module_type="pinv")
|
|
self.add_mod(self.inv)
|
|
|
|
if(self.num_banks > 1):
|
|
self.bank_select = factory.create(module_type="bank_select")
|
|
self.add_mod(self.bank_select)
|
|
|
|
|
|
def create_bitcell_array(self):
|
|
""" Creating Bitcell Array """
|
|
|
|
self.bitcell_array_inst=self.add_inst(name="bitcell_array",
|
|
mod=self.bitcell_array)
|
|
|
|
|
|
temp = []
|
|
for col in range(self.num_cols):
|
|
for bitline in self.bitline_names:
|
|
temp.append(bitline+"_{0}".format(col))
|
|
for row in range(self.num_rows):
|
|
for wordline in self.wl_names:
|
|
temp.append(wordline+"_{0}".format(row))
|
|
temp.append("vdd")
|
|
temp.append("gnd")
|
|
self.connect_inst(temp)
|
|
|
|
|
|
def place_bitcell_array(self, offset):
|
|
""" Placing Bitcell Array """
|
|
self.bitcell_array_inst.place(offset)
|
|
|
|
|
|
def create_precharge_array(self):
|
|
""" Creating Precharge """
|
|
|
|
self.precharge_array_inst = [None]*len(self.all_ports)
|
|
for port in self.read_ports:
|
|
self.precharge_array_inst[port]=self.add_inst(name="precharge_array{}".format(port),
|
|
mod=self.precharge_array[port])
|
|
temp = []
|
|
for i in range(self.num_cols):
|
|
temp.append(self.bl_names[port]+"_{0}".format(i))
|
|
temp.append(self.br_names[port]+"_{0}".format(i))
|
|
temp.extend([self.prefix+"p_en_bar{0}".format(port), "vdd"])
|
|
self.connect_inst(temp)
|
|
|
|
|
|
def place_precharge_array(self, offsets):
|
|
""" Placing Precharge """
|
|
|
|
debug.check(len(offsets)>=len(self.all_ports), "Insufficient offsets to place precharge array.")
|
|
|
|
for port in self.read_ports:
|
|
if port%2 == 1:
|
|
mirror = "MX"
|
|
else:
|
|
mirror = "R0"
|
|
self.precharge_array_inst[port].place(offset=offsets[port], mirror=mirror)
|
|
|
|
|
|
def create_column_mux_array(self):
|
|
""" Creating Column Mux when words_per_row > 1 . """
|
|
self.column_mux_array_inst = [None]*len(self.all_ports)
|
|
|
|
if self.col_addr_size == 0:
|
|
return
|
|
|
|
for port in self.all_ports:
|
|
self.column_mux_array_inst[port] = self.add_inst(name="column_mux_array{}".format(port),
|
|
mod=self.column_mux_array[port])
|
|
|
|
temp = []
|
|
for col in range(self.num_cols):
|
|
temp.append(self.bl_names[port]+"_{0}".format(col))
|
|
temp.append(self.br_names[port]+"_{0}".format(col))
|
|
for word in range(self.words_per_row):
|
|
temp.append("sel{0}_{1}".format(port,word))
|
|
for bit in range(self.word_size):
|
|
temp.append(self.bl_names[port]+"_out_{0}".format(bit))
|
|
temp.append(self.br_names[port]+"_out_{0}".format(bit))
|
|
temp.append("gnd")
|
|
self.connect_inst(temp)
|
|
|
|
|
|
|
|
def place_column_mux_array(self, offsets):
|
|
""" Placing Column Mux when words_per_row > 1 . """
|
|
if self.col_addr_size == 0:
|
|
return
|
|
|
|
debug.check(len(offsets)>=len(self.all_ports), "Insufficient offsets to place column mux array.")
|
|
|
|
for port in self.all_ports:
|
|
if port%2 == 1:
|
|
mirror = "MX"
|
|
else:
|
|
mirror = "R0"
|
|
self.column_mux_array_inst[port].place(offset=offsets[port], mirror=mirror)
|
|
|
|
|
|
def create_sense_amp_array(self):
|
|
""" Creating Sense amp """
|
|
|
|
self.sense_amp_array_inst = [None]*len(self.all_ports)
|
|
for port in self.read_ports:
|
|
self.sense_amp_array_inst[port] = self.add_inst(name="sense_amp_array{}".format(port),
|
|
mod=self.sense_amp_array)
|
|
|
|
temp = []
|
|
for bit in range(self.word_size):
|
|
temp.append("dout{0}_{1}".format(port,bit))
|
|
if self.words_per_row == 1:
|
|
temp.append(self.bl_names[port]+"_{0}".format(bit))
|
|
temp.append(self.br_names[port]+"_{0}".format(bit))
|
|
else:
|
|
temp.append(self.bl_names[port]+"_out_{0}".format(bit))
|
|
temp.append(self.br_names[port]+"_out_{0}".format(bit))
|
|
|
|
temp.extend([self.prefix+"s_en{}".format(port), "vdd", "gnd"])
|
|
self.connect_inst(temp)
|
|
|
|
|
|
def place_sense_amp_array(self, offsets):
|
|
""" Placing Sense amp """
|
|
|
|
debug.check(len(offsets)>=len(self.read_ports), "Insufficient offsets to place sense amp array.")
|
|
for port in self.read_ports:
|
|
if port%2 == 1:
|
|
mirror = "MX"
|
|
else:
|
|
mirror = "R0"
|
|
self.sense_amp_array_inst[port].place(offset=offsets[port], mirror=mirror)
|
|
|
|
|
|
def create_write_driver_array(self):
|
|
""" Creating Write Driver """
|
|
|
|
self.write_driver_array_inst = [None]*len(self.all_ports)
|
|
for port in self.write_ports:
|
|
self.write_driver_array_inst[port] = self.add_inst(name="write_driver_array{}".format(port),
|
|
mod=self.write_driver_array)
|
|
|
|
temp = []
|
|
for bit in range(self.word_size):
|
|
temp.append("din{0}_{1}".format(port,bit))
|
|
for bit in range(self.word_size):
|
|
if (self.words_per_row == 1):
|
|
temp.append(self.bl_names[port]+"_{0}".format(bit))
|
|
temp.append(self.br_names[port]+"_{0}".format(bit))
|
|
else:
|
|
temp.append(self.bl_names[port]+"_out_{0}".format(bit))
|
|
temp.append(self.br_names[port]+"_out_{0}".format(bit))
|
|
temp.extend([self.prefix+"w_en{0}".format(port), "vdd", "gnd"])
|
|
self.connect_inst(temp)
|
|
|
|
|
|
def place_write_driver_array(self, offsets):
|
|
""" Placing Write Driver """
|
|
|
|
debug.check(len(offsets)>=len(self.write_ports), "Insufficient offsets to place write driver array.")
|
|
|
|
for port in self.write_ports:
|
|
if port%2 == 1:
|
|
mirror = "MX"
|
|
else:
|
|
mirror = "R0"
|
|
self.write_driver_array_inst[port].place(offset=offsets[port], mirror=mirror)
|
|
|
|
|
|
def create_row_decoder(self):
|
|
""" Create the hierarchical row decoder """
|
|
|
|
self.row_decoder_inst = [None]*len(self.all_ports)
|
|
for port in self.all_ports:
|
|
self.row_decoder_inst[port] = self.add_inst(name="row_decoder{}".format(port),
|
|
mod=self.row_decoder)
|
|
|
|
temp = []
|
|
for bit in range(self.row_addr_size):
|
|
temp.append("addr{0}_{1}".format(port,bit+self.col_addr_size))
|
|
for row in range(self.num_rows):
|
|
temp.append("dec_out{0}_{1}".format(port,row))
|
|
temp.extend(["vdd", "gnd"])
|
|
self.connect_inst(temp)
|
|
|
|
|
|
def place_row_decoder(self, offsets):
|
|
""" Place the hierarchical row decoder """
|
|
|
|
debug.check(len(offsets)>=len(self.all_ports), "Insufficient offsets to place row decoder array.")
|
|
|
|
# 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.
|
|
|
|
for port in self.all_ports:
|
|
if port%2 == 1:
|
|
mirror = "MY"
|
|
else:
|
|
mirror = "R0"
|
|
self.row_decoder_inst[port].place(offset=offsets[port], mirror=mirror)
|
|
|
|
|
|
def create_wordline_driver(self):
|
|
""" Create the Wordline Driver """
|
|
|
|
self.wordline_driver_inst = [None]*len(self.all_ports)
|
|
for port in self.all_ports:
|
|
self.wordline_driver_inst[port] = self.add_inst(name="wordline_driver{}".format(port),
|
|
mod=self.wordline_driver)
|
|
|
|
temp = []
|
|
for row in range(self.num_rows):
|
|
temp.append("dec_out{0}_{1}".format(port,row))
|
|
for row in range(self.num_rows):
|
|
temp.append(self.wl_names[port]+"_{0}".format(row))
|
|
temp.append(self.prefix+"wl_en{0}".format(port))
|
|
temp.append("vdd")
|
|
temp.append("gnd")
|
|
self.connect_inst(temp)
|
|
|
|
|
|
def place_wordline_driver(self, offsets):
|
|
""" Place the Wordline Driver """
|
|
|
|
debug.check(len(offsets)>=len(self.all_ports), "Insufficient offsets to place wordline driver array.")
|
|
|
|
for port in self.all_ports:
|
|
if port%2 == 1:
|
|
mirror = "MY"
|
|
else:
|
|
mirror = "R0"
|
|
self.wordline_driver_inst[port].place(offset=offsets[port], mirror=mirror)
|
|
|
|
|
|
def create_column_decoder(self):
|
|
"""
|
|
Create a 2:4 or 3:8 column address decoder.
|
|
"""
|
|
|
|
dff = factory.create(module_type="dff")
|
|
|
|
if self.col_addr_size == 0:
|
|
return
|
|
elif self.col_addr_size == 1:
|
|
self.column_decoder = factory.create(module_type="pinvbuf", height=dff.height)
|
|
elif self.col_addr_size == 2:
|
|
self.column_decoder = factory.create(module_type="hierarchical_predecode2x4", height=dff.height)
|
|
elif self.col_addr_size == 3:
|
|
self.column_decoder = factory.create(module_type="hierarchical_predecode3x8", height=dff.height)
|
|
else:
|
|
# No error checking before?
|
|
debug.error("Invalid column decoder?",-1)
|
|
self.add_mod(self.column_decoder)
|
|
|
|
self.column_decoder_inst = [None]*len(self.all_ports)
|
|
for port in self.all_ports:
|
|
self.column_decoder_inst[port] = self.add_inst(name="col_address_decoder{}".format(port),
|
|
mod=self.column_decoder)
|
|
|
|
temp = []
|
|
for bit in range(self.col_addr_size):
|
|
temp.append("addr{0}_{1}".format(port,bit))
|
|
for bit in range(self.num_col_addr_lines):
|
|
temp.append("sel{0}_{1}".format(port,bit))
|
|
temp.extend(["vdd", "gnd"])
|
|
self.connect_inst(temp)
|
|
|
|
|
|
def place_column_decoder(self, offsets):
|
|
"""
|
|
Place a 2:4 or 3:8 column address decoder.
|
|
"""
|
|
if self.col_addr_size == 0:
|
|
return
|
|
|
|
debug.check(len(offsets)>=len(self.all_ports), "Insufficient offsets to place column decoder.")
|
|
|
|
for port in self.all_ports:
|
|
if port%2 == 1:
|
|
mirror = "XY"
|
|
else:
|
|
mirror = "R0"
|
|
self.column_decoder_inst[port].place(offset=offsets[port], mirror=mirror)
|
|
|
|
|
|
|
|
def create_bank_select(self):
|
|
""" Create the bank select logic. """
|
|
|
|
if not self.num_banks > 1:
|
|
return
|
|
|
|
self.bank_select_inst = [None]*len(self.all_ports)
|
|
for port in self.all_ports:
|
|
self.bank_select_inst[port] = self.add_inst(name="bank_select{}".format(port),
|
|
mod=self.bank_select)
|
|
|
|
temp = []
|
|
temp.extend(self.input_control_signals[port])
|
|
temp.append("bank_sel{}".format(port))
|
|
temp.extend(self.control_signals[port])
|
|
temp.extend(["vdd", "gnd"])
|
|
self.connect_inst(temp)
|
|
|
|
|
|
def place_bank_select(self, offsets):
|
|
""" Place the bank select logic. """
|
|
|
|
if not self.num_banks > 1:
|
|
return
|
|
|
|
debug.check(len(offsets)>=len(self.all_ports), "Insufficient offsets to place bank select logic.")
|
|
|
|
for port in self.all_ports:
|
|
self.bank_select_inst[port].place(offsets[port])
|
|
|
|
|
|
def route_supplies(self):
|
|
""" Propagate all vdd/gnd pins up to this level for all modules """
|
|
for inst in self.insts:
|
|
self.copy_power_pins(inst,"vdd")
|
|
self.copy_power_pins(inst,"gnd")
|
|
|
|
def route_bank_select(self, port):
|
|
""" Route the bank select logic. """
|
|
|
|
if self.port_id[port] == "rw":
|
|
bank_sel_signals = ["clk_buf", "w_en", "s_en", "p_en_bar", "bank_sel"]
|
|
gated_bank_sel_signals = ["gated_clk_buf", "gated_w_en", "gated_s_en", "gated_p_en_bar"]
|
|
elif self.port_id[port] == "w":
|
|
bank_sel_signals = ["clk_buf", "w_en", "bank_sel"]
|
|
gated_bank_sel_signals = ["gated_clk_buf", "gated_w_en"]
|
|
else:
|
|
bank_sel_signals = ["clk_buf", "s_en", "p_en_bar", "bank_sel"]
|
|
gated_bank_sel_signals = ["gated_clk_buf", "gated_s_en", "gated_p_en_bar"]
|
|
|
|
copy_control_signals = self.input_control_signals[port]+["bank_sel{}".format(port)]
|
|
for signal in range(len(copy_control_signals)):
|
|
self.copy_layout_pin(self.bank_select_inst[port], bank_sel_signals[signal], copy_control_signals[signal])
|
|
|
|
for signal in range(len(gated_bank_sel_signals)):
|
|
# Connect the inverter output to the central bus
|
|
out_pos = self.bank_select_inst[port].get_pin(gated_bank_sel_signals[signal]).rc()
|
|
name = self.control_signals[port][signal]
|
|
bus_pos = vector(self.bus_xoffset[port][name].x, 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_routing_constraints(self):
|
|
"""
|
|
After the modules are instantiated, find the dimensions for the
|
|
control bus, power ring, etc.
|
|
"""
|
|
|
|
self.max_y_offset = max([x.uy() for x in self.insts]) + 3*self.m1_width
|
|
self.min_y_offset = min([x.by() for x in self.insts])
|
|
|
|
self.max_x_offset = max([x.rx() for x in self.insts]) + 3*self.m1_width
|
|
self.min_x_offset = min([x.lx() for x in self.insts])
|
|
|
|
# # Create the core bbox for the power rings
|
|
ur = vector(self.max_x_offset, self.max_y_offset)
|
|
ll = vector(self.min_x_offset, self.min_y_offset)
|
|
self.core_bbox = [ll, ur]
|
|
|
|
self.height = ur.y - ll.y
|
|
self.width = ur.x - ll.x
|
|
|
|
|
|
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,
|
|
# and control lines.
|
|
|
|
self.bus_xoffset = [None]*len(self.all_ports)
|
|
# Port 0
|
|
# 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_offset = vector(-self.m2_pitch * self.num_control_lines[0] - self.m2_width, self.min_y_offset)
|
|
# The control bus is routed up to two pitches below the bitcell array
|
|
control_bus_length = -2*self.m1_pitch - self.min_y_offset
|
|
self.bus_xoffset[0] = self.create_bus(layer="metal2",
|
|
pitch=self.m2_pitch,
|
|
offset=control_bus_offset,
|
|
names=self.control_signals[0],
|
|
length=control_bus_length,
|
|
vertical=True,
|
|
make_pins=(self.num_banks==1))
|
|
|
|
# Port 1
|
|
if len(self.all_ports)==2:
|
|
# The other control bus is routed up to two pitches above the bitcell array
|
|
control_bus_length = self.max_y_offset - self.bitcell_array.height - 2*self.m1_pitch
|
|
control_bus_offset = vector(self.bitcell_array.width + self.m2_width,
|
|
self.max_y_offset - control_bus_length)
|
|
|
|
self.bus_xoffset[1] = self.create_bus(layer="metal2",
|
|
pitch=self.m2_pitch,
|
|
offset=control_bus_offset,
|
|
names=self.control_signals[1],
|
|
length=control_bus_length,
|
|
vertical=True,
|
|
make_pins=(self.num_banks==1))
|
|
|
|
|
|
def route_precharge_to_bitcell_array(self, port):
|
|
""" Routing of BL and BR between pre-charge and bitcell array """
|
|
|
|
inst2 = self.precharge_array_inst[port]
|
|
inst1 = self.bitcell_array_inst
|
|
inst1_bl_name = self.bl_names[port]+"_{}"
|
|
inst1_br_name = self.br_names[port]+"_{}"
|
|
self.connect_bitlines(inst1=inst1, inst2=inst2, num_bits=self.num_cols,
|
|
inst1_bl_name=inst1_bl_name, inst1_br_name=inst1_br_name)
|
|
|
|
|
|
|
|
def route_column_mux_to_precharge_array(self, port):
|
|
""" Routing of BL and BR between col mux and precharge array """
|
|
|
|
# Only do this if we have a column mux!
|
|
if self.col_addr_size==0:
|
|
return
|
|
|
|
inst1 = self.column_mux_array_inst[port]
|
|
inst2 = self.precharge_array_inst[port]
|
|
self.connect_bitlines(inst1, inst2, self.num_cols)
|
|
|
|
def route_column_mux_to_bitcell_array(self, port):
|
|
""" Routing of BL and BR between col mux bitcell array """
|
|
|
|
# Only do this if we have a column mux!
|
|
if self.col_addr_size==0:
|
|
return
|
|
|
|
inst2 = self.column_mux_array_inst[port]
|
|
inst1 = self.bitcell_array_inst
|
|
inst1_bl_name = self.bl_names[port]+"_{}"
|
|
inst1_br_name = self.br_names[port]+"_{}"
|
|
|
|
# The column mux is constructed to match the bitline pitch, so we can directly connect
|
|
# here and not channel route the bitlines.
|
|
self.connect_bitlines(inst1=inst1, inst2=inst2, num_bits=self.num_cols,
|
|
inst1_bl_name=inst1_bl_name, inst1_br_name=inst1_br_name)
|
|
|
|
|
|
|
|
def route_sense_amp_to_column_mux_or_precharge_array(self, port):
|
|
""" Routing of BL and BR between sense_amp and column mux or precharge array """
|
|
inst2 = self.sense_amp_array_inst[port]
|
|
|
|
if self.col_addr_size>0:
|
|
# Sense amp is connected to the col mux
|
|
inst1 = self.column_mux_array_inst[port]
|
|
inst1_bl_name = "bl_out_{}"
|
|
inst1_br_name = "br_out_{}"
|
|
else:
|
|
# Sense amp is directly connected to the precharge array
|
|
inst1 = self.precharge_array_inst[port]
|
|
inst1_bl_name = "bl_{}"
|
|
inst1_br_name = "br_{}"
|
|
|
|
self.channel_route_bitlines(inst1=inst1, inst2=inst2, num_bits=self.word_size,
|
|
inst1_bl_name=inst1_bl_name, inst1_br_name=inst1_br_name)
|
|
|
|
def route_write_driver_to_column_mux_or_bitcell_array(self, port):
|
|
""" Routing of BL and BR between sense_amp and column mux or bitcell array """
|
|
inst2 = self.write_driver_array_inst[port]
|
|
|
|
if self.col_addr_size>0:
|
|
# Write driver is connected to the col mux
|
|
inst1 = self.column_mux_array_inst[port]
|
|
inst1_bl_name = "bl_out_{}"
|
|
inst1_br_name = "br_out_{}"
|
|
else:
|
|
# Write driver is directly connected to the bitcell array
|
|
inst1 = self.bitcell_array_inst
|
|
inst1_bl_name = self.bl_names[port]+"_{}"
|
|
inst1_br_name = self.br_names[port]+"_{}"
|
|
|
|
self.channel_route_bitlines(inst1=inst1, inst2=inst2, num_bits=self.word_size,
|
|
inst1_bl_name=inst1_bl_name, inst1_br_name=inst1_br_name)
|
|
|
|
def route_write_driver_to_sense_amp(self, port):
|
|
""" Routing of BL and BR between write driver and sense amp """
|
|
|
|
inst1 = self.write_driver_array_inst[port]
|
|
inst2 = self.sense_amp_array_inst[port]
|
|
|
|
# These should be pitch matched in the cell library,
|
|
# but just in case, do a channel route.
|
|
self.channel_route_bitlines(inst1=inst1, inst2=inst2, num_bits=self.word_size)
|
|
|
|
|
|
|
|
def route_sense_amp_out(self, port):
|
|
""" Add pins for the sense amp output """
|
|
|
|
for bit in range(self.word_size):
|
|
data_pin = self.sense_amp_array_inst[port].get_pin("data_{}".format(bit))
|
|
self.add_layout_pin_rect_center(text="dout{0}_{1}".format(port,bit),
|
|
layer=data_pin.layer,
|
|
offset=data_pin.center(),
|
|
height=data_pin.height(),
|
|
width=data_pin.width())
|
|
|
|
|
|
def route_row_decoder(self, port):
|
|
""" Routes the row decoder inputs and supplies """
|
|
|
|
# Create inputs for the row address lines
|
|
for row in range(self.row_addr_size):
|
|
addr_idx = row + self.col_addr_size
|
|
decoder_name = "addr_{}".format(row)
|
|
addr_name = "addr{0}_{1}".format(port,addr_idx)
|
|
self.copy_layout_pin(self.row_decoder_inst[port], decoder_name, addr_name)
|
|
|
|
|
|
def route_write_driver_in(self, port):
|
|
""" Connecting write driver """
|
|
|
|
for row in range(self.word_size):
|
|
data_name = "data_{}".format(row)
|
|
din_name = "din{0}_{1}".format(port,row)
|
|
self.copy_layout_pin(self.write_driver_array_inst[port], data_name, din_name)
|
|
|
|
def channel_route_bitlines(self, inst1, inst2, num_bits,
|
|
inst1_bl_name="bl_{}", inst1_br_name="br_{}",
|
|
inst2_bl_name="bl_{}", inst2_br_name="br_{}"):
|
|
"""
|
|
Route the bl and br of two modules using the channel router.
|
|
"""
|
|
|
|
# determine top and bottom automatically.
|
|
# since they don't overlap, we can just check the bottom y coordinate.
|
|
if inst1.by() < inst2.by():
|
|
(bottom_inst, bottom_bl_name, bottom_br_name) = (inst1, inst1_bl_name, inst1_br_name)
|
|
(top_inst, top_bl_name, top_br_name) = (inst2, inst2_bl_name, inst2_br_name)
|
|
else:
|
|
(bottom_inst, bottom_bl_name, bottom_br_name) = (inst2, inst2_bl_name, inst2_br_name)
|
|
(top_inst, top_bl_name, top_br_name) = (inst1, inst1_bl_name, inst1_br_name)
|
|
|
|
|
|
# Channel route each mux separately since we don't minimize the number
|
|
# of tracks in teh channel router yet. If we did, we could route all the bits at once!
|
|
offset = bottom_inst.ul() + vector(0,self.m1_pitch)
|
|
for bit in range(num_bits):
|
|
bottom_names = [bottom_inst.get_pin(bottom_bl_name.format(bit)), bottom_inst.get_pin(bottom_br_name.format(bit))]
|
|
top_names = [top_inst.get_pin(top_bl_name.format(bit)), top_inst.get_pin(top_br_name.format(bit))]
|
|
route_map = list(zip(bottom_names, top_names))
|
|
self.create_horizontal_channel_route(route_map, offset)
|
|
|
|
|
|
def connect_bitlines(self, inst1, inst2, num_bits,
|
|
inst1_bl_name="bl_{}", inst1_br_name="br_{}",
|
|
inst2_bl_name="bl_{}", inst2_br_name="br_{}"):
|
|
"""
|
|
Connect the bl and br of two modules.
|
|
This assumes that they have sufficient space to create a jog
|
|
in the middle between the two modules (if needed).
|
|
"""
|
|
|
|
# determine top and bottom automatically.
|
|
# since they don't overlap, we can just check the bottom y coordinate.
|
|
if inst1.by() < inst2.by():
|
|
(bottom_inst, bottom_bl_name, bottom_br_name) = (inst1, inst1_bl_name, inst1_br_name)
|
|
(top_inst, top_bl_name, top_br_name) = (inst2, inst2_bl_name, inst2_br_name)
|
|
else:
|
|
(bottom_inst, bottom_bl_name, bottom_br_name) = (inst2, inst2_bl_name, inst2_br_name)
|
|
(top_inst, top_bl_name, top_br_name) = (inst1, inst1_bl_name, inst1_br_name)
|
|
|
|
for col in range(num_bits):
|
|
bottom_bl = bottom_inst.get_pin(bottom_bl_name.format(col)).uc()
|
|
bottom_br = bottom_inst.get_pin(bottom_br_name.format(col)).uc()
|
|
top_bl = top_inst.get_pin(top_bl_name.format(col)).bc()
|
|
top_br = top_inst.get_pin(top_br_name.format(col)).bc()
|
|
|
|
yoffset = 0.5*(top_bl.y+bottom_bl.y)
|
|
self.add_path("metal2",[bottom_bl, vector(bottom_bl.x,yoffset),
|
|
vector(top_bl.x,yoffset), top_bl])
|
|
self.add_path("metal2",[bottom_br, vector(bottom_br.x,yoffset),
|
|
vector(top_br.x,yoffset), top_br])
|
|
|
|
|
|
def route_wordline_driver(self, port):
|
|
""" Connect Wordline driver to bitcell array wordline """
|
|
if port%2:
|
|
self.route_wordline_driver_right(port)
|
|
else:
|
|
self.route_wordline_driver_left(port)
|
|
|
|
def route_wordline_driver_left(self, port):
|
|
""" Connecting Wordline driver output to Bitcell WL connection """
|
|
|
|
for row 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[port].get_pin("decode_{}".format(row)).rc()
|
|
driver_in_pos = self.wordline_driver_inst[port].get_pin("in_{}".format(row)).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[port].get_pin("wl_{}".format(row)).rc()
|
|
bitcell_wl_pos = self.bitcell_array_inst.get_pin(self.wl_names[port]+"_{}".format(row)).lc()
|
|
mid1 = driver_wl_pos.scale(0,1) + vector(0.5*self.wordline_driver_inst[port].rx() + 0.5*self.bitcell_array_inst.lx(),0)
|
|
mid2 = mid1.scale(1,0)+bitcell_wl_pos.scale(0.5,1)
|
|
self.add_path("metal1", [driver_wl_pos, mid1, mid2, bitcell_wl_pos])
|
|
|
|
|
|
def route_wordline_driver_right(self, port):
|
|
""" Connecting Wordline driver output to Bitcell WL connection """
|
|
|
|
for row 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[port].get_pin("decode_{}".format(row)).lc()
|
|
driver_in_pos = self.wordline_driver_inst[port].get_pin("in_{}".format(row)).rc()
|
|
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[port].get_pin("wl_{}".format(row)).lc()
|
|
bitcell_wl_pos = self.bitcell_array_inst.get_pin(self.wl_names[port]+"_{}".format(row)).rc()
|
|
mid1 = driver_wl_pos.scale(0,1) + vector(0.5*self.wordline_driver_inst[port].lx() + 0.5*self.bitcell_array_inst.rx(),0)
|
|
mid2 = mid1.scale(1,0)+bitcell_wl_pos.scale(0,1)
|
|
self.add_path("metal1", [driver_wl_pos, mid1, mid2, bitcell_wl_pos])
|
|
|
|
def route_column_address_lines(self, port):
|
|
""" Connecting the select lines of column mux to the address bus """
|
|
if not self.col_addr_size>0:
|
|
return
|
|
|
|
if self.col_addr_size == 1:
|
|
|
|
# Connect to sel[0] and sel[1]
|
|
decode_names = ["Zb", "Z"]
|
|
|
|
# The Address LSB
|
|
self.copy_layout_pin(self.column_decoder_inst[port], "A", "addr{}_0".format(port))
|
|
|
|
elif self.col_addr_size > 1:
|
|
decode_names = []
|
|
for i in range(self.num_col_addr_lines):
|
|
decode_names.append("out_{}".format(i))
|
|
|
|
for i in range(self.col_addr_size):
|
|
decoder_name = "in_{}".format(i)
|
|
addr_name = "addr{0}_{1}".format(port,i)
|
|
self.copy_layout_pin(self.column_decoder_inst[port], decoder_name, addr_name)
|
|
|
|
if port%2:
|
|
offset = self.column_decoder_inst[port].ll() - vector(self.num_col_addr_lines*self.m2_pitch, 0)
|
|
else:
|
|
offset = self.column_decoder_inst[port].lr() + vector(self.m2_pitch, 0)
|
|
|
|
decode_pins = [self.column_decoder_inst[port].get_pin(x) for x in decode_names]
|
|
|
|
sel_names = ["sel_{}".format(x) for x in range(self.num_col_addr_lines)]
|
|
column_mux_pins = [self.column_mux_array_inst[port].get_pin(x) for x in sel_names]
|
|
|
|
route_map = list(zip(decode_pins, column_mux_pins))
|
|
self.create_vertical_channel_route(route_map, offset)
|
|
|
|
|
|
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.center())
|
|
|
|
# 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.center())
|
|
self.add_label(text=br_name,
|
|
layer="metal2",
|
|
offset=br_pin.center())
|
|
|
|
# # 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="sa_out_{}".format(i),
|
|
# layer="metal2",
|
|
# offset=data_pin.center())
|
|
|
|
# Add labels on the decoder
|
|
for port in self.write_ports:
|
|
for i in range(self.word_size):
|
|
data_name = "dec_out_{}".format(i)
|
|
pin_name = "in_{}".format(i)
|
|
data_pin = self.wordline_driver_inst[port].get_pin(pin_name)
|
|
self.add_label(text=data_name,
|
|
layer="metal1",
|
|
offset=data_pin.center())
|
|
|
|
|
|
def route_control_lines(self, port):
|
|
""" 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
|
|
write_inst = 0
|
|
read_inst = 0
|
|
|
|
connection = []
|
|
if port in self.read_ports:
|
|
connection.append((self.prefix+"p_en_bar{}".format(port), self.precharge_array_inst[port].get_pin("en_bar").lc()))
|
|
|
|
if port in self.write_ports:
|
|
connection.append((self.prefix+"w_en{}".format(port), self.write_driver_array_inst[port].get_pin("en").lc()))
|
|
|
|
if port in self.read_ports:
|
|
connection.append((self.prefix+"s_en{}".format(port), self.sense_amp_array_inst[port].get_pin("en").lc()))
|
|
|
|
for (control_signal, pin_pos) in connection:
|
|
control_pos = vector(self.bus_xoffset[port][control_signal].x ,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+"wl_en{}".format(port)
|
|
if port%2:
|
|
pin_pos = self.wordline_driver_inst[port].get_pin("en_bar").uc()
|
|
mid_pos = pin_pos + vector(0,self.m2_gap) # to route down to the top of the bus
|
|
else:
|
|
pin_pos = self.wordline_driver_inst[port].get_pin("en_bar").bc()
|
|
mid_pos = pin_pos - vector(0,self.m2_gap) # to route down to the top of the bus
|
|
control_x_offset = self.bus_xoffset[port][control_signal].x
|
|
control_pos = vector(control_x_offset, mid_pos.y)
|
|
self.add_wire(("metal1","via1","metal2"),[pin_pos, mid_pos, control_pos])
|
|
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
|
offset=control_pos,
|
|
rotate=90)
|
|
|
|
|
|
def analytical_delay(self, corner, slew, load):
|
|
""" return analytical delay of the bank"""
|
|
results = []
|
|
|
|
decoder_delay = self.row_decoder.analytical_delay(corner, slew, self.wordline_driver.input_load())
|
|
|
|
word_driver_delay = self.wordline_driver.analytical_delay(corner,
|
|
decoder_delay.slew,
|
|
self.bitcell_array.input_load())
|
|
|
|
#FIXME: Array delay is the same for every port.
|
|
bitcell_array_delay = self.bitcell_array.analytical_delay(corner, word_driver_delay.slew)
|
|
|
|
#This also essentially creates the same delay for each port. Good structure, no substance
|
|
for port in self.all_ports:
|
|
if self.words_per_row > 1:
|
|
column_mux_delay = self.column_mux_array[port].analytical_delay(corner,
|
|
bitcell_array_delay.slew,
|
|
self.sense_amp_array.input_load())
|
|
else:
|
|
column_mux_delay = self.return_delay(delay = 0.0, slew=word_driver_delay.slew)
|
|
|
|
bl_t_data_out_delay = self.sense_amp_array.analytical_delay(corner,
|
|
column_mux_delay.slew,
|
|
self.bitcell_array.output_load())
|
|
# output load of bitcell_array is set to be only small part of bl for sense amp.
|
|
results.append(decoder_delay + word_driver_delay + bitcell_array_delay + column_mux_delay + bl_t_data_out_delay)
|
|
|
|
return results
|
|
|
|
def determine_wordline_stage_efforts(self, external_cout, inp_is_rise=True):
|
|
"""Get the all the stage efforts for each stage in the path within the bank clk_buf to a wordline"""
|
|
#Decoder is assumed to have settled before the negative edge of the clock. Delay model relies on this assumption
|
|
stage_effort_list = []
|
|
wordline_cout = self.bitcell_array.get_wordline_cin() + external_cout
|
|
stage_effort_list += self.wordline_driver.determine_wordline_stage_efforts(wordline_cout,inp_is_rise)
|
|
|
|
return stage_effort_list
|
|
|
|
def get_wl_en_cin(self):
|
|
"""Get the relative capacitance of all the clk connections in the bank"""
|
|
#wl_en only used in the wordline driver.
|
|
return self.wordline_driver.get_wl_en_cin()
|
|
|
|
def get_w_en_cin(self):
|
|
"""Get the relative capacitance of all the clk connections in the bank"""
|
|
#wl_en only used in the wordline driver.
|
|
return self.write_driver.get_w_en_cin()
|
|
|
|
def get_clk_bar_cin(self):
|
|
"""Get the relative capacitance of all the clk_bar connections in the bank"""
|
|
#Current bank only uses clock bar (clk_buf_bar) as an enable for the precharge array.
|
|
|
|
#Precharges are the all the same in Mulitport, one is picked
|
|
port = self.read_ports[0]
|
|
return self.precharge_array[port].get_en_cin()
|
|
|
|
def get_sen_cin(self):
|
|
"""Get the relative capacitance of all the sense amp enable connections in the bank"""
|
|
#Current bank only uses sen as an enable for the sense amps.
|
|
return self.sense_amp_array.get_en_cin()
|