2016-11-08 18:57:35 +01:00
|
|
|
import sys
|
2016-11-09 21:20:52 +01:00
|
|
|
from tech import drc, parameter
|
2016-11-08 18:57:35 +01:00
|
|
|
import debug
|
|
|
|
|
import design
|
|
|
|
|
import math
|
|
|
|
|
from math import log,sqrt,ceil
|
|
|
|
|
from contact import contact
|
|
|
|
|
from pinv import pinv
|
|
|
|
|
from nand_2 import nand_2
|
|
|
|
|
from nor_2 import nor_2
|
|
|
|
|
from vector import vector
|
|
|
|
|
from globals import OPTS
|
|
|
|
|
|
|
|
|
|
class bank(design.design):
|
|
|
|
|
"""
|
|
|
|
|
Dynamically generated a single Bank including bitcell array,
|
|
|
|
|
hierarchical_decoder, precharge, column_mux, 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",
|
2017-08-24 00:02:15 +02:00
|
|
|
"column_mux_array", "write_driver_array", "tri_gate_array"]
|
2016-11-08 18:57:35 +01:00
|
|
|
for mod_name in mod_list:
|
|
|
|
|
config_mod_name = getattr(OPTS.config, 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 == "":
|
2017-08-24 00:02:15 +02:00
|
|
|
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))
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
self.word_size = word_size
|
|
|
|
|
self.num_words = num_words
|
|
|
|
|
self.words_per_row = words_per_row
|
|
|
|
|
self.num_banks = num_banks
|
|
|
|
|
|
|
|
|
|
self.compute_sizes()
|
|
|
|
|
self.add_pins()
|
|
|
|
|
self.create_modules()
|
|
|
|
|
self.add_modules()
|
2017-08-24 00:02:15 +02:00
|
|
|
|
2016-11-08 18:57:35 +01:00
|
|
|
self.setup_layout_constraints()
|
|
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
self.route_layout()
|
2016-11-08 18:57:35 +01:00
|
|
|
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))
|
|
|
|
|
for i in range(self.addr_size):
|
|
|
|
|
self.add_pin("ADDR[{0}]".format(i))
|
|
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
# For more than one bank, we have a bank select and name
|
|
|
|
|
# the signals gated_*
|
2016-11-08 18:57:35 +01:00
|
|
|
if(self.num_banks > 1):
|
|
|
|
|
self.add_pin("bank_select")
|
2017-08-24 00:02:15 +02:00
|
|
|
prefix = "gated_"
|
2016-11-08 18:57:35 +01:00
|
|
|
else:
|
2017-08-24 00:02:15 +02:00
|
|
|
prefix = ""
|
|
|
|
|
self.add_pin(prefix+"s_en")
|
|
|
|
|
self.add_pin(prefix+"w_en")
|
|
|
|
|
self.add_pin(prefix+"tri_en_bar")
|
|
|
|
|
self.add_pin(prefix+"tri_en")
|
|
|
|
|
self.add_pin(prefix+"clk_bar")
|
|
|
|
|
self.add_pin(prefix+"clk")
|
2016-11-08 18:57:35 +01:00
|
|
|
self.add_pin("vdd")
|
|
|
|
|
self.add_pin("gnd")
|
|
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
def route_layout(self):
|
2016-11-08 18:57:35 +01:00
|
|
|
""" Create routing amoung the modules """
|
|
|
|
|
self.create_central_bus()
|
2017-08-24 00:02:15 +02:00
|
|
|
self.route_precharge_to_bitcell_array()
|
|
|
|
|
self.route_sense_amp_to_trigate()
|
2016-11-08 18:57:35 +01:00
|
|
|
self.route_tri_gate_out()
|
2017-08-24 00:02:15 +02:00
|
|
|
self.route_wordline_driver()
|
|
|
|
|
self.route_row_decoder()
|
2016-11-08 18:57:35 +01:00
|
|
|
self.route_column_address_lines()
|
2017-08-24 00:02:15 +02:00
|
|
|
self.route_msf_address()
|
2016-11-08 18:57:35 +01:00
|
|
|
self.route_control_lines()
|
2017-08-24 00:02:15 +02:00
|
|
|
self.add_control_pins()
|
|
|
|
|
self.route_vdd_supply()
|
|
|
|
|
self.route_gnd_supply()
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
#self.offset_all_coordinates()
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
def add_modules(self):
|
2017-08-24 00:02:15 +02:00
|
|
|
""" Add modules. The order should not matter! """
|
2016-11-08 18:57:35 +01:00
|
|
|
self.add_bitcell_array()
|
|
|
|
|
self.add_precharge_array()
|
2017-08-24 00:02:15 +02:00
|
|
|
|
|
|
|
|
if self.col_addr_size > 0:
|
|
|
|
|
self.add_column_mux_array()
|
|
|
|
|
if self.col_addr_size > 1: # size 1 is from addr FF
|
|
|
|
|
self.add_column_decoder()
|
|
|
|
|
|
2016-11-08 18:57:35 +01:00
|
|
|
self.add_sense_amp_array()
|
|
|
|
|
self.add_write_driver_array()
|
|
|
|
|
self.add_msf_data_in()
|
|
|
|
|
self.add_tri_gate_array()
|
2017-08-24 00:02:15 +02:00
|
|
|
self.add_row_decoder()
|
2016-11-08 18:57:35 +01:00
|
|
|
self.add_wordline_driver()
|
|
|
|
|
self.add_msf_address()
|
2017-08-24 00:02:15 +02:00
|
|
|
|
|
|
|
|
if(self.num_banks > 1):
|
|
|
|
|
self.add_bank_select()
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
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.")
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
# Width for left gnd rail
|
2017-08-24 00:02:15 +02:00
|
|
|
self.vdd_rail_width = 5*drc["minwidth_metal2"]
|
|
|
|
|
self.gnd_rail_width = 5*drc["minwidth_metal2"]
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
# Number of control lines in the bus
|
2017-08-24 00:02:15 +02:00
|
|
|
self.num_control_lines = 6
|
|
|
|
|
# The order of the control signals on the control bus:
|
|
|
|
|
self.control_lines = ["clk", "tri_en_bar", "tri_en", "clk_bar", "w_en", "s_en"]
|
|
|
|
|
# The central bus is the column address (both polarities), row address
|
|
|
|
|
if self.col_addr_size>0:
|
|
|
|
|
self.num_addr_lines = 2**self.col_addr_size + self.row_addr_size
|
|
|
|
|
else:
|
|
|
|
|
self.num_addr_lines = self.row_addr_size
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
# M1/M2 routing pitch is based on contacted pitch
|
|
|
|
|
self.m1m2_via = contact(layer_stack=("metal1", "via1", "metal2"))
|
|
|
|
|
self.m2m3_via = contact(layer_stack=("metal2", "via2", "metal3"))
|
|
|
|
|
self.m1_pitch = self.m1m2_via.width + max(drc["metal1_to_metal1"],drc["metal2_to_metal2"])
|
|
|
|
|
self.m2_pitch = self.m2m3_via.width + max(drc["metal2_to_metal2"],drc["metal3_to_metal3"])
|
|
|
|
|
self.m2_width = drc["minwidth_metal2"]
|
|
|
|
|
self.m3_width = drc["minwidth_metal3"]
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
# Overall central bus gap. It includes all the column mux lines,
|
2016-11-08 18:57:35 +01:00
|
|
|
# control lines, address flop to decoder lines and a GND power rail in M2
|
2017-08-24 00:02:15 +02:00
|
|
|
# one pitch on the right on the right of the control lines
|
|
|
|
|
self.start_of_right_central_bus = -self.m2_pitch * (self.num_control_lines + 1)
|
|
|
|
|
# one pitch on the right on the addr lines and one on the right of the gnd rail
|
|
|
|
|
self.start_of_left_central_bus = self.start_of_right_central_bus - self.m2_pitch*(self.num_addr_lines+2) - self.gnd_rail_width
|
|
|
|
|
# add a pitch on each end and around the gnd rail
|
|
|
|
|
self.overall_central_bus_width = self.m2_pitch * (self.num_control_lines + self.num_addr_lines + 5) + self.gnd_rail_width
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
# Array for control lines
|
2017-08-24 00:02:15 +02:00
|
|
|
self.control_signals = ["s_en",
|
|
|
|
|
"w_en",
|
2016-11-08 18:57:35 +01:00
|
|
|
"clk_bar",
|
2017-08-24 00:02:15 +02:00
|
|
|
"tri_en",
|
|
|
|
|
"tri_en_bar",
|
2016-11-08 18:57:35 +01:00
|
|
|
"clk"]
|
2017-08-24 00:02:15 +02:00
|
|
|
self.gated_control_signals = ["gated_s_en",
|
|
|
|
|
"gated_w_en",
|
2016-11-08 18:57:35 +01:00
|
|
|
"gated_clk_bar",
|
|
|
|
|
"gated_tri_en", "gated_tri_en_bar",
|
|
|
|
|
"gated_clk"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_modules(self):
|
|
|
|
|
""" Create all the modules using the class loader """
|
2017-08-24 00:02:15 +02:00
|
|
|
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)
|
2016-11-08 18:57:35 +01:00
|
|
|
self.add_mod(self.bitcell_array)
|
|
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
self.precharge_array = self.mod_precharge_array(columns=self.num_cols,
|
2016-11-08 18:57:35 +01:00
|
|
|
ptx_width=drc["minwidth_tx"])
|
|
|
|
|
self.add_mod(self.precharge_array)
|
|
|
|
|
|
|
|
|
|
if(self.col_addr_size > 0):
|
2017-08-24 00:02:15 +02:00
|
|
|
self.column_mux_array = self.mod_column_mux_array(columns=self.num_cols,
|
2016-11-08 18:57:35 +01:00
|
|
|
word_size=self.word_size)
|
|
|
|
|
self.add_mod(self.column_mux_array)
|
|
|
|
|
|
|
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
self.sense_amp_array = self.mod_sense_amp_array(word_size=self.word_size,
|
2016-11-08 18:57:35 +01:00
|
|
|
words_per_row=self.words_per_row)
|
2017-08-24 00:02:15 +02:00
|
|
|
self.add_mod(self.sense_amp_array)
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
self.decoder = self.mod_decoder(rows=self.num_rows)
|
2016-11-08 18:57:35 +01:00
|
|
|
self.add_mod(self.decoder)
|
|
|
|
|
|
|
|
|
|
self.msf_address = self.mod_ms_flop_array(name="msf_address",
|
|
|
|
|
columns=self.row_addr_size+self.col_addr_size,
|
|
|
|
|
word_size=self.row_addr_size+self.col_addr_size)
|
|
|
|
|
self.add_mod(self.msf_address)
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
self.wordline_driver = self.mod_wordline_driver(rows=self.num_rows)
|
2016-11-08 18:57:35 +01:00
|
|
|
self.add_mod(self.wordline_driver)
|
|
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
self.inv = pinv()
|
2016-11-08 18:57:35 +01:00
|
|
|
self.add_mod(self.inv)
|
|
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
# 4x Inverter
|
|
|
|
|
self.inv4x = pinv(nmos_width=4*drc["minwidth_tx"])
|
2016-11-08 18:57:35 +01:00
|
|
|
self.add_mod(self.inv4x)
|
|
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
self.nor2 = nor_2()
|
|
|
|
|
self.add_mod(self.nor2)
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
# Vertical metal rail gap definition
|
|
|
|
|
self.metal2_extend_contact = (self.m1m2_via.second_layer_height
|
|
|
|
|
- self.m1m2_via.contact_width) / 2
|
|
|
|
|
self.gap_between_rails = self.metal2_extend_contact + drc["metal2_to_metal2"]
|
|
|
|
|
self.gap_between_rail_offset = self.gap_between_rails + drc["minwidth_metal2"]
|
|
|
|
|
self.via_shift = (self.m1m2_via.second_layer_width
|
|
|
|
|
- self.m1m2_via.first_layer_width) / 2
|
|
|
|
|
|
|
|
|
|
def add_bitcell_array(self):
|
|
|
|
|
""" Adding Bitcell Array """
|
|
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
self.bitcell_array_inst=self.add_inst(name="bitcell_array",
|
|
|
|
|
mod=self.bitcell_array,
|
|
|
|
|
offset=vector(0,0))
|
2016-11-08 18:57:35 +01:00
|
|
|
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))
|
2017-08-24 00:02:15 +02:00
|
|
|
temp.extend(["vdd", "gnd"])
|
2016-11-08 18:57:35 +01:00
|
|
|
self.connect_inst(temp)
|
|
|
|
|
|
|
|
|
|
def add_precharge_array(self):
|
2017-08-24 00:02:15 +02:00
|
|
|
""" Adding Precharge """
|
|
|
|
|
|
|
|
|
|
# The wells must be far enough apart
|
|
|
|
|
# We use two well spacings because the bitcells tend to have a shared rail in the height
|
|
|
|
|
y_offset = self.bitcell_array.height + 2*drc["pwell_to_nwell"]
|
|
|
|
|
self.precharge_array_inst=self.add_inst(name="precharge_array",
|
|
|
|
|
mod=self.precharge_array,
|
|
|
|
|
offset=vector(0,y_offset))
|
2016-11-08 18:57:35 +01:00
|
|
|
temp = []
|
|
|
|
|
for i in range(self.num_cols):
|
|
|
|
|
temp.append("bl[{0}]".format(i))
|
|
|
|
|
temp.append("br[{0}]".format(i))
|
2017-08-24 00:02:15 +02:00
|
|
|
temp.extend(["clk_bar", "vdd"])
|
2016-11-08 18:57:35 +01:00
|
|
|
self.connect_inst(temp)
|
|
|
|
|
|
|
|
|
|
def add_column_mux_array(self):
|
|
|
|
|
""" Adding Column Mux when words_per_row > 1 . """
|
|
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
y_offset = self.column_mux_array.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 j in range(self.word_size):
|
|
|
|
|
temp.append("bl_out[{0}]".format(j*self.words_per_row))
|
|
|
|
|
temp.append("br_out[{0}]".format(j*self.words_per_row))
|
|
|
|
|
if self.words_per_row == 2:
|
|
|
|
|
temp.extend(["A_bar[{}]".format(self.addr_size),"A[{}]".format(self.addr_size)])
|
|
|
|
|
else:
|
2016-11-08 18:57:35 +01:00
|
|
|
for k in range(self.words_per_row):
|
|
|
|
|
temp.append("sel[{0}]".format(k))
|
2017-08-24 00:02:15 +02:00
|
|
|
temp.append("gnd")
|
|
|
|
|
self.connect_inst(temp)
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
def add_sense_amp_array(self):
|
|
|
|
|
""" Adding Sense amp """
|
|
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
y_offset = self.column_mux_array.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))
|
2016-11-08 18:57:35 +01:00
|
|
|
temp = []
|
2017-08-24 00:02:15 +02:00
|
|
|
for i in range(0,self.num_cols,self.words_per_row):
|
|
|
|
|
temp.append("data_out[{0}]".format(i/self.words_per_row))
|
|
|
|
|
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(["s_en", "vdd", "gnd"])
|
2016-11-08 18:57:35 +01:00
|
|
|
self.connect_inst(temp)
|
|
|
|
|
|
|
|
|
|
def add_write_driver_array(self):
|
|
|
|
|
""" Adding Write Driver """
|
|
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
y_offset = self.sense_amp_array.height + self.column_mux_array.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))
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
temp = []
|
2017-08-24 00:02:15 +02:00
|
|
|
for i in range(0,self.num_cols,self.words_per_row):
|
|
|
|
|
temp.append("data_in[{0}]".format(i/self.words_per_row))
|
|
|
|
|
temp.append("bl[{0}]".format(i))
|
|
|
|
|
temp.append("br[{0}]".format(i))
|
|
|
|
|
temp.extend(["w_en", "vdd", "gnd"])
|
2016-11-08 18:57:35 +01:00
|
|
|
self.connect_inst(temp)
|
|
|
|
|
|
|
|
|
|
def add_msf_data_in(self):
|
|
|
|
|
""" data_in flip_flop """
|
|
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
y_offset= self.sense_amp_array.height + self.column_mux_array.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))
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
temp = []
|
|
|
|
|
for i in range(self.word_size):
|
|
|
|
|
temp.append("DATA[{0}]".format(i))
|
|
|
|
|
temp.append("data_in[{0}]".format(i))
|
|
|
|
|
temp.append("data_in_bar[{0}]".format(i))
|
2017-08-24 00:02:15 +02:00
|
|
|
temp.extend(["clk_bar", "vdd", "gnd"])
|
2016-11-08 18:57:35 +01:00
|
|
|
self.connect_inst(temp)
|
|
|
|
|
|
|
|
|
|
def add_tri_gate_array(self):
|
|
|
|
|
""" data tri gate to drive the data bus """
|
2017-08-24 00:02:15 +02:00
|
|
|
y_offset = self.sense_amp_array.height+self.column_mux_array.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")
|
2016-11-08 18:57:35 +01:00
|
|
|
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))
|
2017-08-24 00:02:15 +02:00
|
|
|
temp.extend(["tri_en", "tri_en_bar", "vdd", "gnd"])
|
2016-11-08 18:57:35 +01:00
|
|
|
self.connect_inst(temp)
|
|
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
def add_row_decoder(self):
|
|
|
|
|
""" Add the hierarchical row decoder """
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
# 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.decoder.width + self.overall_central_bus_width
|
|
|
|
|
addr_x_offset = self.msf_address.height
|
|
|
|
|
offset = vector(max(decoder_x_offset, addr_x_offset),
|
|
|
|
|
self.decoder.predecoder_height)
|
|
|
|
|
self.row_decoder_inst=self.add_inst(name="row_decoder",
|
|
|
|
|
mod=self.decoder,
|
|
|
|
|
offset=offset.scale(-1,-1))
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
temp = []
|
|
|
|
|
for i in range(self.row_addr_size):
|
|
|
|
|
temp.append("A[{0}]".format(i))
|
|
|
|
|
for j in range(self.num_rows):
|
|
|
|
|
temp.append("decode_out[{0}]".format(j))
|
2017-08-24 00:02:15 +02:00
|
|
|
temp.extend(["vdd", "gnd"])
|
2016-11-08 18:57:35 +01:00
|
|
|
self.connect_inst(temp)
|
|
|
|
|
|
|
|
|
|
def add_wordline_driver(self):
|
|
|
|
|
""" Wordline Driver """
|
|
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
# 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.decoder.width + self.overall_central_bus_width - self.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))
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
temp = []
|
|
|
|
|
for i in range(self.num_rows):
|
|
|
|
|
temp.append("decode_out[{0}]".format(i))
|
|
|
|
|
for i in range(self.num_rows):
|
|
|
|
|
temp.append("wl[{0}]".format(i))
|
|
|
|
|
|
|
|
|
|
if(self.num_banks > 1):
|
|
|
|
|
temp.append("gated_clk")
|
|
|
|
|
else:
|
|
|
|
|
temp.append("clk")
|
|
|
|
|
temp.append("vdd")
|
|
|
|
|
temp.append("gnd")
|
|
|
|
|
self.connect_inst(temp)
|
|
|
|
|
|
|
|
|
|
def add_msf_address(self):
|
|
|
|
|
""" Adding address Flip-flops """
|
|
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
# A gap between the hierarchical decoder and addr flops
|
|
|
|
|
gap = max(drc["pwell_to_nwell"], 2*self.m2_pitch)
|
|
|
|
|
|
|
|
|
|
# The address flops go below the hierarchical decoder
|
|
|
|
|
decoder_x_offset = self.decoder.width + self.overall_central_bus_width
|
|
|
|
|
addr_x_offset = self.msf_address.height + self.overall_central_bus_width
|
|
|
|
|
# msf_address is not in the y-coord because it is rotated
|
|
|
|
|
offset = vector(max(decoder_x_offset, addr_x_offset),
|
|
|
|
|
self.decoder.predecoder_height + gap)
|
|
|
|
|
self.msf_address_inst=self.add_inst(name="address_flop_array",
|
|
|
|
|
mod=self.msf_address,
|
|
|
|
|
offset=offset.scale(-1,-1),
|
|
|
|
|
rotate=270)
|
|
|
|
|
temp = []
|
|
|
|
|
for i in range(self.row_addr_size+self.col_addr_size):
|
|
|
|
|
temp.append("ADDR[{0}]".format(i))
|
|
|
|
|
temp.append("A[{0}]".format(i))
|
|
|
|
|
temp.append("A_bar[{0}]".format(i))
|
|
|
|
|
if(self.num_banks > 1):
|
|
|
|
|
temp.append("gated_clk")
|
2016-11-08 18:57:35 +01:00
|
|
|
else:
|
2017-08-24 00:02:15 +02:00
|
|
|
temp.append("clk")
|
|
|
|
|
temp.extend(["vdd", "gnd"])
|
|
|
|
|
self.connect_inst(temp)
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
def add_column_decoder(self):
|
|
|
|
|
""" Create a 2:4 decoder to decode column select lines if the col_addr_size = 4 """
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
# FIXME: Should just load this rather than reference a level down
|
|
|
|
|
if self.col_addr_size == 1:
|
|
|
|
|
return # This is done from the FF outputs directly
|
|
|
|
|
if self.col_addr_size == 2:
|
2016-11-08 18:57:35 +01:00
|
|
|
self.col_decoder = self.decoder.pre2_4
|
2017-08-24 00:02:15 +02:00
|
|
|
elif self.col_addr_size == 3:
|
|
|
|
|
self.col_decoder = self.decoder.pre3_8
|
|
|
|
|
else:
|
|
|
|
|
# No error checking before?
|
|
|
|
|
debug.error("Invalid column decoder?",-1)
|
|
|
|
|
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
# Place the col decoder just to the left of the control bus
|
|
|
|
|
x_off = self.m2_pitch + self.overall_central_bus_width + self.col_decoder.width
|
|
|
|
|
# Place the col decoder below the the address flops which are below the row decoder (lave some space for wells)
|
|
|
|
|
vertical_gap = max(drc["pwell_to_nwell"], 2*self.m2_pitch)
|
|
|
|
|
y_off = self.decoder.predecoder_height + self.msf_address.width + self.col_decoder.height + 2*vertical_gap
|
|
|
|
|
self.col_decoder_inst=self.add_inst(name="col_address_decoder",
|
|
|
|
|
mod=self.col_decoder,
|
|
|
|
|
offset=vector(x_off,y_off).scale(-1,-1))
|
|
|
|
|
temp = []
|
|
|
|
|
for i in range(self.col_addr_size):
|
|
|
|
|
temp.append("A[{0}]".format(i + self.row_addr_size))
|
|
|
|
|
for j in range(2**self.col_addr_size):
|
|
|
|
|
temp.append("sel[{0}]".format(j))
|
|
|
|
|
temp.extend(["vdd", "gnd"])
|
|
|
|
|
self.connect_inst(temp)
|
|
|
|
|
|
|
|
|
|
def add_bank_select(self):
|
|
|
|
|
"""Create a bank select signal that is combined with an array of
|
|
|
|
|
NOR+INV gates to gate the control signals in case of multiple
|
|
|
|
|
banks are created in upper level SRAM module
|
|
|
|
|
"""
|
|
|
|
|
assert 0
|
|
|
|
|
xoffset_nor = - self.start_of_left_central_bus - self.nor2.width - self.inv4x.width
|
|
|
|
|
xoffset_inv = xoffset_nor + self.nor2.width
|
|
|
|
|
self.bank_select_or_position = vector(xoffset_nor, self.min_point)
|
|
|
|
|
|
|
|
|
|
# bank select inverter
|
|
|
|
|
self.bank_select_inv_position = vector(self.bank_select_or_position.x
|
|
|
|
|
- 5 * drc["minwidth_metal2"]
|
|
|
|
|
- self.inv4x.width,
|
|
|
|
|
self.min_point)
|
|
|
|
|
self.add_inst(name="bank_select_inv",
|
|
|
|
|
mod=self.inv4x,
|
|
|
|
|
offset=self.bank_select_inv_position)
|
|
|
|
|
self.connect_inst(["bank_select", "bank_select_bar", "vdd", "gnd"])
|
|
|
|
|
|
|
|
|
|
for i in range(self.numb_control_lines):
|
|
|
|
|
# central control bus index
|
|
|
|
|
# 5 = clk,4 = tri_en_bar,3 = tri_en,2 = clk_bar,1 = w_en,0 = s_en
|
|
|
|
|
name_nor = "bank_selector_nor_{0}".format(i)
|
|
|
|
|
name_inv = "bank_selector_inv_{0}".format(i)
|
|
|
|
|
nor2_inv_connection_height = self.inv4x.A_position.y - self.nor2.Z_position.y + 0.5 * drc["minwidth_metal1"]
|
|
|
|
|
|
|
|
|
|
if (i % 2):
|
|
|
|
|
y_offset = self.min_point + self.inv.height*(i + 1)
|
|
|
|
|
mod_dir = "MX"
|
2016-11-08 18:57:35 +01:00
|
|
|
# nor2 output to inv input
|
2017-08-24 00:02:15 +02:00
|
|
|
y_correct = self.nor2.Z_position.y + nor2_inv_connection_height - 0.5 * drc["minwidth_metal1"]
|
|
|
|
|
else:
|
|
|
|
|
y_offset = self.min_point + self.inv.height*i
|
|
|
|
|
mod_dir = "R0"
|
|
|
|
|
# nor2 output to inv input
|
|
|
|
|
y_correct = 0.5 * drc["minwidth_metal1"] - self.nor2.Z_position.y
|
|
|
|
|
connection = vector(xoffset_inv, y_offset - y_correct)
|
|
|
|
|
|
|
|
|
|
if i == 3:
|
|
|
|
|
self.add_inst(name=name_nor,
|
|
|
|
|
mod=self.nor2,
|
|
|
|
|
offset=[xoffset_nor, y_offset],
|
|
|
|
|
mirror=mod_dir)
|
|
|
|
|
self.connect_inst(["gated_tri_en_bar",
|
|
|
|
|
"bank_select_bar",
|
|
|
|
|
self.control_signals[i].format(i),
|
|
|
|
|
"vdd",
|
|
|
|
|
"gnd"])
|
|
|
|
|
# connect the metal1 layer to connect to the old inv output
|
|
|
|
|
offset = connection - vector(0, 0.5*drc["minwidth_metal1"])
|
|
|
|
|
self.add_rect(layer="metal1",
|
|
|
|
|
offset=offset,
|
|
|
|
|
width=self.inv4x.width,
|
|
|
|
|
height=drc["minwidth_metal1"])
|
|
|
|
|
elif i == 5:
|
|
|
|
|
offset = [xoffset_nor, y_offset - self.nor2.A_position.y
|
|
|
|
|
- 0.5*drc["minwidth_metal1"]]
|
2016-11-08 18:57:35 +01:00
|
|
|
self.add_rect(layer="metal1",
|
2017-08-24 00:02:15 +02:00
|
|
|
offset=offset,
|
|
|
|
|
width=self.nor2.width + self.inv4x.width,
|
|
|
|
|
height=drc["minwidth_metal1"])
|
|
|
|
|
else:
|
|
|
|
|
self.add_inst(name=name_nor,
|
|
|
|
|
mod=self.nor2,
|
|
|
|
|
offset=[xoffset_nor, y_offset],
|
|
|
|
|
mirror=mod_dir)
|
|
|
|
|
self.connect_inst([self.gated_control_signals[i],
|
|
|
|
|
"bank_select_bar",
|
|
|
|
|
"net_block_nor_inv[{0}]".format(i),
|
|
|
|
|
"vdd",
|
|
|
|
|
"gnd"])
|
|
|
|
|
|
|
|
|
|
self.add_inst(name=name_inv,
|
|
|
|
|
mod=self.inv4x,
|
|
|
|
|
offset=[xoffset_inv, y_offset],
|
|
|
|
|
mirror=mod_dir)
|
|
|
|
|
self.connect_inst(["net_block_nor_inv[{0}]".format(i),
|
|
|
|
|
self.control_signals[i],
|
|
|
|
|
"vdd",
|
|
|
|
|
"gnd"])
|
|
|
|
|
|
|
|
|
|
# nor2 output to inv input
|
|
|
|
|
for i in range(self.numb_control_lines - 1):
|
|
|
|
|
nor2_inv_connection_height = (self.inv4x.A_position.y
|
|
|
|
|
- self.nor2.Z_position.y
|
|
|
|
|
+ 0.5 * drc["minwidth_metal1"])
|
|
|
|
|
|
|
|
|
|
if (i % 2):
|
|
|
|
|
y_offset = self.min_point + self.inv.height * (i + 1)
|
|
|
|
|
mod_dir = "MX"
|
|
|
|
|
y_correct = (-self.nor2.Z_position.y + 0.5 * drc["minwidth_metal1"]
|
|
|
|
|
- nor2_inv_connection_height)
|
|
|
|
|
else:
|
|
|
|
|
y_offset = self.min_point + self.inv.height*i
|
|
|
|
|
mod_dir = "R0"
|
|
|
|
|
y_correct = self.nor2.Z_position.y - 0.5 * drc["minwidth_metal1"]
|
|
|
|
|
# nor2 output to inv input
|
|
|
|
|
connection = vector(xoffset_inv, y_offset + y_correct)
|
|
|
|
|
self.add_rect(layer="metal1",
|
|
|
|
|
offset=connection,
|
|
|
|
|
width=drc["minwidth_metal1"],
|
|
|
|
|
height=nor2_inv_connection_height)
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
def setup_layout_constraints(self):
|
2017-08-24 00:02:15 +02:00
|
|
|
""" Calculating layout constraints, width, height 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.ll().y - 3*self.m2_pitch
|
|
|
|
|
addr_min_point = self.msf_address_inst.ll().y - 2*self.m2_pitch
|
|
|
|
|
if self.col_addr_size >1:
|
|
|
|
|
decoder_min_point = self.col_decoder_inst.ll().y
|
|
|
|
|
else:
|
|
|
|
|
decoder_min_point = 0
|
|
|
|
|
self.min_point = min(tri_gate_min_point, addr_min_point, decoder_min_point)
|
|
|
|
|
if self.num_banks>1:
|
|
|
|
|
self.min_point -= self.num_control_lines * self.bitcell.height
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
# The max point is always the top of the precharge bitlines
|
|
|
|
|
self.max_point = self.precharge_array_inst.uy()
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
self.height = self.max_point - self.min_point
|
|
|
|
|
|
|
|
|
|
# Add an extra gap between the bitcell and the rail
|
|
|
|
|
self.right_vdd_x_offset = self.bitcell_array_inst.ur().x + 3 * drc["minwidth_metal1"]
|
|
|
|
|
offset = vector(self.right_vdd_x_offset, self.min_point)
|
2016-11-08 18:57:35 +01:00
|
|
|
self.add_layout_pin(text="vdd",
|
|
|
|
|
layer="metal1",
|
2017-08-24 00:02:15 +02:00
|
|
|
offset=offset,
|
|
|
|
|
width=self.vdd_rail_width,
|
|
|
|
|
height=self.height)
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
# from the edge of the decoder is another 2 times minwidth metal1
|
|
|
|
|
self.left_vdd_x_offset = min(self.msf_address_inst.ll().x, self.row_decoder_inst.ll().x) - self.vdd_rail_width - 2*drc["minwidth_metal1"]
|
|
|
|
|
offset = vector(self.left_vdd_x_offset, self.min_point)
|
2016-11-08 18:57:35 +01:00
|
|
|
self.add_layout_pin(text="vdd",
|
|
|
|
|
layer="metal1",
|
2017-08-24 00:02:15 +02:00
|
|
|
offset=offset,
|
|
|
|
|
width=self.vdd_rail_width,
|
|
|
|
|
height=self.height)
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
self.gnd_x_offset = self.start_of_right_central_bus - self.gnd_rail_width - self.m2_pitch
|
|
|
|
|
offset = vector(self.gnd_x_offset, self.min_point)
|
2016-11-08 18:57:35 +01:00
|
|
|
self.add_layout_pin(text="gnd",
|
|
|
|
|
layer="metal2",
|
2017-08-24 00:02:15 +02:00
|
|
|
offset=offset,
|
|
|
|
|
width=self.gnd_rail_width,
|
|
|
|
|
height=self.height)
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
self.width = self.right_vdd_x_offset - self.left_vdd_x_offset + self.vdd_rail_width
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
def create_central_bus(self):
|
2017-08-24 00:02:15 +02:00
|
|
|
""" Create the address, supply, and control signal central bus lines. """
|
|
|
|
|
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
# Address lines in central line connection are 2*col_addr_size
|
|
|
|
|
# number of connections for the column mux (for both signal and _bar) and row_addr_size (no _bar)
|
|
|
|
|
|
|
|
|
|
self.central_line_xoffset = {}
|
|
|
|
|
|
|
|
|
|
# Control lines (to the right of the GND rail)
|
|
|
|
|
for i in range(self.num_control_lines):
|
|
|
|
|
x_offset = self.start_of_right_central_bus + i * self.m2_pitch
|
|
|
|
|
self.central_line_xoffset[self.control_lines[i]]=x_offset
|
2016-11-08 18:57:35 +01:00
|
|
|
self.add_rect(layer="metal2",
|
2017-08-24 00:02:15 +02:00
|
|
|
offset=vector(x_offset, self.min_point),
|
|
|
|
|
width=self.m2_width,
|
|
|
|
|
height=self.height)
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
# row address lines (to the left of the column mux or GND rail)
|
|
|
|
|
# goes from 0 down to the min point
|
2016-11-08 18:57:35 +01:00
|
|
|
for i in range(self.row_addr_size):
|
2017-08-24 00:02:15 +02:00
|
|
|
x_offset = self.start_of_left_central_bus + i * self.m2_pitch
|
|
|
|
|
self.central_line_xoffset["A[{}]".format(i)]=x_offset
|
2016-11-08 18:57:35 +01:00
|
|
|
self.add_rect(layer="metal2",
|
2017-08-24 00:02:15 +02:00
|
|
|
offset=vector(x_offset, self.min_point),
|
|
|
|
|
width=self.m2_width,
|
|
|
|
|
height=-self.min_point)
|
|
|
|
|
|
|
|
|
|
# column mux lines if there is column mux [2 or 4 lines] (to the left of the GND rail)
|
|
|
|
|
# goes from 0 down to the min point
|
|
|
|
|
if self.col_addr_size>0:
|
|
|
|
|
for i in range(2**self.col_addr_size):
|
|
|
|
|
x_offset = self.start_of_left_central_bus + (i + self.row_addr_size) * self.m2_pitch
|
|
|
|
|
self.central_line_xoffset["sel[{}]".format(i)]=x_offset
|
|
|
|
|
self.add_rect(layer="metal2",
|
|
|
|
|
offset=vector(x_offset, self.min_point),
|
|
|
|
|
width=self.m2_width,
|
|
|
|
|
height=-self.min_point)
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def route_precharge_to_bitcell_array(self):
|
2016-11-08 18:57:35 +01:00
|
|
|
""" Routing of BL and BR between pre-charge and bitcell array """
|
2017-08-24 00:02:15 +02:00
|
|
|
|
2016-11-08 18:57:35 +01:00
|
|
|
for i in range(self.num_cols):
|
2017-08-24 00:02:15 +02:00
|
|
|
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):
|
2016-11-08 18:57:35 +01:00
|
|
|
""" Routing of sense amp output to tri_gate input """
|
2017-08-24 00:02:15 +02:00
|
|
|
|
2016-11-08 18:57:35 +01:00
|
|
|
for i in range(self.word_size):
|
|
|
|
|
# Connection of data_out of sense amp to data_ in of msf_data_out
|
2017-08-24 00:02:15 +02:00
|
|
|
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)).rc() # rc to get enough overlap
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
startY = self.tri_gate_array_inst.ll().y - 2*drc["minwidth_metal3"] + 0.5*drc["minwidth_metal1"]
|
|
|
|
|
start = vector(tri_gate_in.x - 3 * drc["minwidth_metal3"], startY)
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
m3_min = vector([drc["minwidth_metal3"]] * 2)
|
2017-08-24 00:02:15 +02:00
|
|
|
mid1 = tri_gate_in.scale(1,0) + sa_data_out.scale(0,1) + m3_min.scale(-3, 1)
|
|
|
|
|
mid2 = sa_data_out + m3_min.scale(0.5, 1)
|
2016-11-08 18:57:35 +01:00
|
|
|
self.add_path("metal3", [start, mid1, mid2])
|
|
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
mid3 = [tri_gate_in.x, startY]
|
|
|
|
|
self.add_path("metal2", [start, mid3, tri_gate_in])
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
offset = start - 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):
|
2017-08-24 00:02:15 +02:00
|
|
|
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)
|
2016-11-08 18:57:35 +01:00
|
|
|
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)
|
2017-08-24 00:02:15 +02:00
|
|
|
self.add_layout_pin(text="DATA[{}]".format(i),
|
|
|
|
|
layer="metal2",
|
|
|
|
|
offset=data_line_position)
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
def route_row_decoder(self):
|
|
|
|
|
""" Routes the row decoder inputs and supplies """
|
|
|
|
|
|
|
|
|
|
for i in range(self.row_addr_size):
|
|
|
|
|
# Connect the address rails to the decoder
|
|
|
|
|
# Note that the decoder inputs are long vertical rails so spread out the connections verically one per row height
|
|
|
|
|
decoder_in_position = self.row_decoder_inst.get_pin("A[{}]".format(i)).ll() + vector(0,(i+1)*self.bitcell.height-2*self.m2_pitch)
|
|
|
|
|
rail_position = vector(self.central_line_xoffset["A[{}]".format(i)]+drc["minwidth_metal2"],decoder_in_position.y)
|
|
|
|
|
self.add_rect(layer="metal1",
|
|
|
|
|
offset=decoder_in_position,
|
|
|
|
|
width=rail_position.x-decoder_in_position.x,
|
|
|
|
|
height=drc["minwidth_metal1"])
|
|
|
|
|
rail_via = vector(self.central_line_xoffset["A[{}]".format(i)],decoder_in_position.y - drc["minwidth_metal2"])
|
|
|
|
|
self.add_via(layers=("metal1", "via1", "metal2"),
|
|
|
|
|
offset=rail_via)
|
|
|
|
|
self.add_via(layers=("metal1", "via1", "metal2"),
|
|
|
|
|
offset=decoder_in_position)
|
|
|
|
|
|
|
|
|
|
# Route the power and ground, but only BELOW the y=0 since the
|
|
|
|
|
# others are connected with the wordline driver.
|
|
|
|
|
for gnd_pin in self.row_decoder_inst.get_pins("gnd"):
|
|
|
|
|
if gnd_pin.uy()>0:
|
|
|
|
|
continue
|
|
|
|
|
driver_gnd_position = gnd_pin.rc()
|
|
|
|
|
gnd_rail_via = vector(self.gnd_x_offset, driver_gnd_position.y + 0.5*self.m1m2_via.width)
|
|
|
|
|
gnd_rail_position = vector(self.gnd_x_offset, driver_gnd_position.y)
|
|
|
|
|
self.add_path("metal1", [driver_gnd_position, gnd_rail_position])
|
|
|
|
|
self.add_via(layers=("metal1","via1","metal2"),
|
|
|
|
|
offset=gnd_rail_via,
|
|
|
|
|
rotate=270)
|
|
|
|
|
|
|
|
|
|
# route the vdd rails
|
|
|
|
|
for vdd_pin in self.row_decoder_inst.get_pins("vdd"):
|
|
|
|
|
if vdd_pin.uy()>0:
|
|
|
|
|
continue
|
|
|
|
|
y_offset = vdd_pin.rc().y
|
|
|
|
|
left_rail_position = vector(self.left_vdd_x_offset, y_offset)
|
|
|
|
|
right_rail_position = vector(self.row_decoder_inst.ur().x, y_offset)
|
|
|
|
|
self.add_path("metal1", [left_rail_position, right_rail_position])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def route_wordline_driver(self):
|
2016-11-08 18:57:35 +01:00
|
|
|
""" Connecting Wordline driver output to Bitcell WL connection """
|
2017-08-24 00:02:15 +02:00
|
|
|
|
|
|
|
|
# we don't care about bends after connecting to the input pin, so let the path code decide.
|
2016-11-08 18:57:35 +01:00
|
|
|
for i in range(self.num_rows):
|
2017-08-24 00:02:15 +02:00
|
|
|
# The pre/post is to access the pin from "outside" the cell to avoid DRCs
|
|
|
|
|
pre = self.row_decoder_inst.get_pin("decode[{}]".format(i)).lc()
|
|
|
|
|
decoder_out_position = self.row_decoder_inst.get_pin("decode[{}]".format(i)).rc() + vector(0.5*drc["minwidth_metal1"],0)
|
|
|
|
|
driver_in_position = self.wordline_driver_inst.get_pin("in[{}]".format(i)).lc() + vector(0.5*drc["minwidth_metal1"],0)
|
|
|
|
|
post = self.wordline_driver_inst.get_pin("in[{}]".format(i)).rc()
|
|
|
|
|
self.add_path("metal1", [pre, decoder_out_position, driver_in_position, post])
|
|
|
|
|
|
|
|
|
|
# The mid guarantees we exit the input cell to the right.
|
|
|
|
|
driver_wl_position = self.wordline_driver_inst.get_pin("wl[{}]".format(i)).rc()
|
|
|
|
|
mid = driver_wl_position + vector(self.m1_pitch,0)
|
|
|
|
|
bitcell_wl_position = self.bitcell_array_inst.get_pin("wl[{}]".format(i)).lc()
|
|
|
|
|
self.add_path("metal1", [driver_wl_position, mid, bitcell_wl_position])
|
|
|
|
|
|
|
|
|
|
# route the gnd rails, add contact to rail as well
|
|
|
|
|
for gnd_pin in self.wordline_driver_inst.get_pins("gnd"):
|
|
|
|
|
driver_gnd_position = gnd_pin.rc()
|
|
|
|
|
right_rail_position = vector(self.bitcell_array_inst.ll().x, driver_gnd_position.y)
|
|
|
|
|
self.add_path("metal1", [driver_gnd_position, right_rail_position])
|
|
|
|
|
gnd_rail_position = vector(self.gnd_x_offset, driver_gnd_position.y + 0.5*self.m1m2_via.width)
|
|
|
|
|
self.add_via(layers=("metal1","via1","metal2"),
|
|
|
|
|
offset=gnd_rail_position,
|
|
|
|
|
rotate=270)
|
|
|
|
|
|
|
|
|
|
# route the vdd rails
|
|
|
|
|
for vdd_pin in self.wordline_driver_inst.get_pins("vdd"):
|
|
|
|
|
y_offset = vdd_pin.rc().y
|
|
|
|
|
left_rail_position = vector(self.left_vdd_x_offset, y_offset)
|
|
|
|
|
right_rail_position = vector(self.right_vdd_x_offset+self.vdd_rail_width, y_offset)
|
|
|
|
|
self.add_path("metal1", [left_rail_position, right_rail_position])
|
|
|
|
|
|
|
|
|
|
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
def route_column_address_lines(self):
|
|
|
|
|
""" Connecting the select lines of column mux to the address bus """
|
2017-08-24 00:02:15 +02:00
|
|
|
if not self.col_addr_size>0:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Connect the select lines to the column mux
|
|
|
|
|
for i in range(2**self.col_addr_size):
|
|
|
|
|
name = "sel[{}]".format(i)
|
|
|
|
|
col_addr_line_position = self.col_mux_array_inst.get_pin(name).ll()
|
|
|
|
|
wire_offset = vector(self.central_line_xoffset[name], col_addr_line_position.y)
|
|
|
|
|
contact_offset = vector(self.central_line_xoffset[name], col_addr_line_position.y - drc["minwidth_metal2"])
|
|
|
|
|
connection_width = col_addr_line_position.x - self.central_line_xoffset[name]
|
2016-11-08 18:57:35 +01:00
|
|
|
self.add_via(layers=("metal1", "via1", "metal2"),
|
|
|
|
|
offset=contact_offset)
|
|
|
|
|
self.add_rect(layer="metal1",
|
2017-08-24 00:02:15 +02:00
|
|
|
offset=wire_offset,
|
2016-11-08 18:57:35 +01:00
|
|
|
width=connection_width,
|
|
|
|
|
height=drc["minwidth_metal1"])
|
2017-08-24 00:02:15 +02:00
|
|
|
|
2016-11-08 18:57:35 +01:00
|
|
|
# Take care of the column address decoder routing
|
|
|
|
|
# If there is a 2:4 decoder for column select lines
|
2017-08-24 00:02:15 +02:00
|
|
|
# or TODO 3:8 decoder should work too!
|
|
|
|
|
if self.col_addr_size > 1:
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
# connections between outputs of decoder to the extension of
|
|
|
|
|
# main address bus
|
|
|
|
|
for i in range(2**self.col_addr_size):
|
|
|
|
|
name = "sel[{}]".format(i)
|
|
|
|
|
x_offset = self.central_line_xoffset[name]
|
|
|
|
|
decode_out_position = self.col_decoder_inst.get_pin("out[{}]".format(i)).rc()
|
|
|
|
|
selx_position = vector(self.central_line_xoffset[name]+drc["minwidth_metal2"],decode_out_position.y)
|
|
|
|
|
self.add_path("metal1",[decode_out_position, selx_position])
|
|
|
|
|
|
|
|
|
|
# via on end
|
|
|
|
|
decode_out_via = self.col_decoder_inst.get_pin("out[{}]".format(i)).br()
|
|
|
|
|
selx_via = vector(self.central_line_xoffset[name],decode_out_via.y - drc["minwidth_metal2"])
|
2016-11-08 18:57:35 +01:00
|
|
|
self.add_via(layers=("metal1", "via1", "metal2"),
|
2017-08-24 00:02:15 +02:00
|
|
|
offset=selx_via)
|
|
|
|
|
|
|
|
|
|
# route the gnd rails, add contact to rail as well
|
|
|
|
|
for gnd_pin in self.col_decoder_inst.get_pins("gnd"):
|
|
|
|
|
driver_gnd_position = gnd_pin.rc()
|
|
|
|
|
right_rail_position = vector(self.gnd_x_offset, driver_gnd_position.y)
|
|
|
|
|
self.add_path("metal1", [driver_gnd_position, right_rail_position])
|
|
|
|
|
gnd_rail_position = vector(self.gnd_x_offset, driver_gnd_position.y + 0.5*self.m1m2_via.width)
|
|
|
|
|
self.add_via(layers=("metal1","via1","metal2"),
|
|
|
|
|
offset=gnd_rail_position,
|
|
|
|
|
rotate=270)
|
|
|
|
|
|
|
|
|
|
# route the vdd rails
|
|
|
|
|
for vdd_pin in self.col_decoder_inst.get_pins("vdd"):
|
|
|
|
|
y_offset = vdd_pin.rc().y
|
|
|
|
|
left_rail_position = vector(self.left_vdd_x_offset, y_offset)
|
|
|
|
|
right_rail_position = vector(self.gnd_x_offset, y_offset)
|
|
|
|
|
self.add_path("metal1", [left_rail_position, right_rail_position])
|
|
|
|
|
|
|
|
|
|
# The connection between last address flops to the input
|
|
|
|
|
# of the column_mux line decoder
|
|
|
|
|
for i in range(self.col_addr_size):
|
|
|
|
|
ff_index = i + self.row_addr_size
|
|
|
|
|
dout_position = self.msf_address_inst.get_pin("dout[{}]".format(ff_index)).rc()
|
|
|
|
|
in_position = self.col_decoder_inst.get_pin("in[{}]".format(i)).uc()
|
|
|
|
|
mid_position = vector(in_position.x,dout_position.y)
|
|
|
|
|
self.add_path("metal3",[dout_position, mid_position, in_position])
|
|
|
|
|
|
|
|
|
|
dout_via = self.msf_address_inst.get_pin("dout[{}]".format(ff_index)).br()
|
|
|
|
|
in_via = self.col_decoder_inst.get_pin("in[{}]".format(i)).ul()
|
|
|
|
|
self.add_via(layers=("metal2", "via2", "metal3"),
|
|
|
|
|
offset=dout_via,
|
|
|
|
|
rotate=90)
|
|
|
|
|
self.add_via(layers=("metal2", "via2", "metal3"),
|
|
|
|
|
offset=in_via)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
# if there are only two column select lines we just connect the dout_bar of the last FF
|
|
|
|
|
# to only select line and dout of that FF to the other select line
|
2017-08-24 00:02:15 +02:00
|
|
|
elif self.col_addr_size == 1:
|
|
|
|
|
|
|
|
|
|
dout_bar_position = self.msf_address_inst.get_pin("dout_bar[{}]".format(self.row_addr_size)).rc()
|
|
|
|
|
sel0_position = vector(self.central_line_xoffset["sel[0]"]+drc["minwidth_metal2"],dout_bar_position.y)
|
|
|
|
|
self.add_path("metal1",[dout_bar_position, sel0_position])
|
|
|
|
|
|
|
|
|
|
# two vias on both ends
|
|
|
|
|
dout_bar_via = self.msf_address_inst.get_pin("dout_bar[{}]".format(self.row_addr_size)).br()
|
|
|
|
|
sel0_via = vector(self.central_line_xoffset["sel[0]"],dout_bar_via.y - drc["minwidth_metal2"])
|
|
|
|
|
self.add_via(layers=("metal1", "via1", "metal2"),
|
|
|
|
|
offset=dout_bar_via,
|
|
|
|
|
rotate=90)
|
|
|
|
|
self.add_via(layers=("metal1", "via1", "metal2"),
|
|
|
|
|
offset=sel0_via)
|
|
|
|
|
|
|
|
|
|
dout_position = self.msf_address_inst.get_pin("dout[{}]".format(self.row_addr_size)).rc()
|
|
|
|
|
sel1_position = vector(self.central_line_xoffset["sel[1]"]+drc["minwidth_metal2"],dout_position.y)
|
|
|
|
|
self.add_path("metal1",[dout_position, sel1_position])
|
|
|
|
|
# two vias on both ends
|
|
|
|
|
dout_via = self.msf_address_inst.get_pin("dout[{}]".format(self.row_addr_size)).br()
|
|
|
|
|
sel1_via = vector(self.central_line_xoffset["sel[1]"],dout_via.y)
|
|
|
|
|
self.add_via(layers=("metal1", "via1", "metal2"),
|
|
|
|
|
offset=dout_via,
|
|
|
|
|
rotate=90)
|
|
|
|
|
self.add_via(layers=("metal1", "via1", "metal2"),
|
|
|
|
|
offset=sel1_via)
|
|
|
|
|
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
def route_msf_address(self):
|
2016-11-08 18:57:35 +01:00
|
|
|
""" Routing the row address lines from the address ms-flop array to the row-decoder """
|
|
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
# Create the address input pins
|
|
|
|
|
for i in range(self.addr_size):
|
|
|
|
|
msf_din_position = self.msf_address_inst.get_pin("din[{}]".format(i)).ll()
|
|
|
|
|
address_position = vector(self.left_vdd_x_offset, msf_din_position.y)
|
|
|
|
|
self.add_layout_pin(text="ADDR[{}]".format(i),
|
|
|
|
|
layer="metal2",
|
|
|
|
|
offset=address_position,
|
|
|
|
|
width=msf_din_position.x - self.left_vdd_x_offset,
|
|
|
|
|
height=drc["minwidth_metal2"])
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
for i in range(self.row_addr_size):
|
|
|
|
|
|
|
|
|
|
# Connect the ff outputs to the rails
|
|
|
|
|
dout_position = self.msf_address_inst.get_pin("dout[{}]".format(i)).rc()
|
|
|
|
|
rail_position = vector(self.central_line_xoffset["A[{}]".format(i)]+drc["minwidth_metal2"],dout_position.y)
|
|
|
|
|
self.add_path("metal1",[dout_position, rail_position])
|
|
|
|
|
dout_via = self.msf_address_inst.get_pin("dout[{}]".format(i)).br()
|
|
|
|
|
self.add_via(layers=("metal1", "via1", "metal2"),
|
|
|
|
|
offset=dout_via,
|
|
|
|
|
rotate=90)
|
|
|
|
|
|
|
|
|
|
rail_via = vector(self.central_line_xoffset["A[{}]".format(i)],dout_position.y - drc["minwidth_metal2"])
|
2016-11-08 18:57:35 +01:00
|
|
|
self.add_via(layers=("metal1", "via1", "metal2"),
|
2017-08-24 00:02:15 +02:00
|
|
|
offset=rail_via)
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
# Connect address FF gnd
|
|
|
|
|
for gnd_pin in self.msf_address_inst.get_pins("gnd"):
|
|
|
|
|
gnd_via = gnd_pin.br()
|
2016-11-08 18:57:35 +01:00
|
|
|
self.add_via(layers=("metal2", "via2", "metal3"),
|
2017-08-24 00:02:15 +02:00
|
|
|
offset=gnd_via,
|
|
|
|
|
rotate=90)
|
|
|
|
|
gnd_offset = gnd_pin.rc()
|
|
|
|
|
rail_offset = vector(self.gnd_x_offset,gnd_offset.y)
|
|
|
|
|
self.add_path("metal3",[gnd_offset,rail_offset])
|
|
|
|
|
rail_via = vector(self.gnd_x_offset, gnd_offset.y + 0.5*drc["minwidth_metal3"])
|
2016-11-08 18:57:35 +01:00
|
|
|
self.add_via(layers=("metal2", "via2", "metal3"),
|
2017-08-24 00:02:15 +02:00
|
|
|
offset=rail_via,
|
|
|
|
|
rotate=270)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Connect address FF vdd
|
|
|
|
|
for vdd_pin in self.msf_address_inst.get_pins("vdd"):
|
|
|
|
|
vdd_offset = vdd_pin.bc()
|
|
|
|
|
mid = vector(vdd_offset.x, vdd_offset.y - self.m1_pitch)
|
|
|
|
|
rail_offset = vector(self.left_vdd_x_offset, mid.y)
|
|
|
|
|
self.add_path("metal1", [vdd_offset,mid,rail_offset])
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
|
2016-11-08 18:57:35 +01:00
|
|
|
def route_control_lines(self):
|
2017-08-24 00:02:15 +02:00
|
|
|
""" Rout the control lines of the entire bank """
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
# 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(("clk", self.msf_data_in_inst.get_pin("clk").ll()))
|
|
|
|
|
connection.append(("tri_en_bar", self.tri_gate_array_inst.get_pin("en_bar").ll()))
|
|
|
|
|
connection.append(("tri_en", self.tri_gate_array_inst.get_pin("en").ll()))
|
|
|
|
|
connection.append(("clk", self.precharge_array_inst.get_pin("clk").ll()))
|
|
|
|
|
connection.append(("w_en", self.write_driver_array_inst.get_pin("wen").ll()))
|
|
|
|
|
connection.append(("s_en", self.sense_amp_array_inst.get_pin("sclk").ll()))
|
|
|
|
|
|
|
|
|
|
for (control_signal, pin_position) in connection:
|
|
|
|
|
control_x_offset = self.central_line_xoffset[control_signal]
|
|
|
|
|
control_position = vector(control_x_offset, pin_position.y)
|
|
|
|
|
connection_width = pin_position.x - control_x_offset
|
|
|
|
|
via_offset = vector(control_x_offset, pin_position.y)
|
2016-11-08 18:57:35 +01:00
|
|
|
self.add_rect(layer="metal1",
|
2017-08-24 00:02:15 +02:00
|
|
|
offset=control_position,
|
|
|
|
|
width=connection_width,
|
2016-11-08 18:57:35 +01:00
|
|
|
height=drc["minwidth_metal1"])
|
|
|
|
|
self.add_via(layers=("metal1", "via1", "metal2"),
|
2017-08-24 00:02:15 +02:00
|
|
|
offset=via_offset)
|
|
|
|
|
self.add_via(layers=("metal1", "via1", "metal2"),
|
|
|
|
|
offset=via_offset)
|
|
|
|
|
|
|
|
|
|
# clk to msf address
|
|
|
|
|
control_signal = "clk"
|
|
|
|
|
pin_position = self.msf_address_inst.get_pin("clk").uc()
|
|
|
|
|
mid_position = pin_position + vector(0,self.m1_pitch)
|
|
|
|
|
control_x_offset = self.central_line_xoffset[control_signal]
|
|
|
|
|
control_position = vector(control_x_offset + drc["minwidth_metal1"], mid_position.y)
|
|
|
|
|
self.add_path("metal1",[pin_position, mid_position, control_position])
|
|
|
|
|
control_via_position = vector(control_x_offset, mid_position.y-0.5*drc["minwidth_metal2"])
|
2016-11-08 18:57:35 +01:00
|
|
|
self.add_via(layers=("metal1", "via1", "metal2"),
|
2017-08-24 00:02:15 +02:00
|
|
|
offset=control_via_position)
|
|
|
|
|
|
|
|
|
|
# clk to wordline_driver
|
|
|
|
|
control_signal = "clk"
|
|
|
|
|
pin_position = self.wordline_driver_inst.get_pin("en").uc()
|
|
|
|
|
mid_position = pin_position + vector(0,self.m1_pitch)
|
|
|
|
|
control_x_offset = self.central_line_xoffset[control_signal]
|
|
|
|
|
control_position = vector(control_x_offset + drc["minwidth_metal1"], mid_position.y)
|
|
|
|
|
self.add_wire(("metal1","via1","metal2"),[pin_position, mid_position, control_position])
|
|
|
|
|
control_via_position = vector(control_x_offset, mid_position.y-0.5*drc["minwidth_metal2"])
|
2016-11-08 18:57:35 +01:00
|
|
|
self.add_via(layers=("metal1", "via1", "metal2"),
|
2017-08-24 00:02:15 +02:00
|
|
|
offset=control_via_position)
|
|
|
|
|
|
|
|
|
|
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
def route_bank_select_or2_gates(self):
|
|
|
|
|
""" Route array of or gates to gate the control signals in case
|
|
|
|
|
of multiple banks are created in upper level SRAM module """
|
|
|
|
|
bank_select_line_xoffset = (self.bank_select_or_position.x
|
|
|
|
|
- 3*drc["minwidth_metal2"])
|
|
|
|
|
self.add_rect(layer="metal2",
|
|
|
|
|
offset=[bank_select_line_xoffset,
|
|
|
|
|
self.bank_select_or_position.y],
|
|
|
|
|
width=drc["minwidth_metal2"],
|
2017-08-24 00:02:15 +02:00
|
|
|
height=self.num_control_lines*self.inv.height)
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
# bank select inverter routing
|
|
|
|
|
# output side
|
|
|
|
|
start = self.bank_select_inv_position + self.inv4x.Z_position
|
2017-08-24 00:02:15 +02:00
|
|
|
end = self.bank_select_or_position + self.nor2.B_position
|
2016-11-08 18:57:35 +01:00
|
|
|
mid = vector(start.x, end.y)
|
|
|
|
|
self.add_path("metal1", [start, mid, end])
|
|
|
|
|
|
|
|
|
|
# input side
|
|
|
|
|
start = self.bank_select_inv_position + self.inv4x.A_position
|
|
|
|
|
end = vector(self.left_vdd_x_offset, start.y + 3 * drc["minwidth_metal3"])
|
|
|
|
|
mid = vector(start.x, end.y)
|
2016-11-17 23:05:50 +01:00
|
|
|
self.add_wire(("metal2", "via1", "metal1"), [start, mid, end])
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
# save position
|
|
|
|
|
self.bank_select_position = end - vector(0, 0.5 * drc["minwidth_metal2"])
|
|
|
|
|
self.add_via(layers=("metal2", "via2", "metal3"),
|
|
|
|
|
offset=self.bank_select_position)
|
|
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
x_offset = (self.bank_select_or_position.x + self.nor2.width
|
2016-11-08 18:57:35 +01:00
|
|
|
+ self.inv4x.width - drc["minwidth_metal1"])
|
2017-08-24 00:02:15 +02:00
|
|
|
for i in range(self.num_control_lines):
|
2016-11-08 18:57:35 +01:00
|
|
|
base = self.bank_select_or_position.y + self.inv.height * i
|
|
|
|
|
if(i % 2):
|
|
|
|
|
Z_y_offset = (base + self.inv.height - self.inv4x.Z_position.y
|
|
|
|
|
- drc["minwidth_metal1"])
|
2017-08-24 00:02:15 +02:00
|
|
|
B_y_offset = (base + self.inv.height - self.nor2.B_position.y
|
2016-11-08 18:57:35 +01:00
|
|
|
- 0.5 * drc["minwidth_metal1"])
|
2017-08-24 00:02:15 +02:00
|
|
|
A_y_offset = (base + self.inv.height - self.nor2.A_position.y
|
2016-11-08 18:57:35 +01:00
|
|
|
- 0.5 * drc["minwidth_metal1"])
|
|
|
|
|
else:
|
|
|
|
|
Z_y_offset = (base + self.inv4x.Z_position.y)
|
2017-08-24 00:02:15 +02:00
|
|
|
B_y_offset = (base + self.nor2.B_position.y
|
2016-11-08 18:57:35 +01:00
|
|
|
- 0.5 * drc["minwidth_metal1"])
|
2017-08-24 00:02:15 +02:00
|
|
|
A_y_offset = (base + self.nor2.A_position.y
|
2016-11-08 18:57:35 +01:00
|
|
|
+ 0.5 * drc["minwidth_metal1"]
|
|
|
|
|
- self.m1m2_via.width)
|
|
|
|
|
|
|
|
|
|
# output
|
|
|
|
|
self.add_rect(layer="metal3",
|
|
|
|
|
offset=[x_offset, Z_y_offset],
|
|
|
|
|
width=self.central_line_xoffset[i] - x_offset,
|
|
|
|
|
height=drc["minwidth_metal3"])
|
|
|
|
|
self.add_via(layers=("metal1", "via1", "metal2"),
|
|
|
|
|
offset=[x_offset, Z_y_offset])
|
|
|
|
|
self.add_via(layers=("metal2", "via2", "metal3"),
|
|
|
|
|
offset=[x_offset, Z_y_offset])
|
|
|
|
|
self.add_via(layers=("metal2", "via2", "metal3"),
|
|
|
|
|
offset=[self.central_line_xoffset[i], Z_y_offset])
|
|
|
|
|
|
|
|
|
|
# B_input
|
|
|
|
|
if i != 5:
|
|
|
|
|
self.add_rect(layer="metal1",
|
|
|
|
|
offset=[bank_select_line_xoffset, B_y_offset],
|
|
|
|
|
width=(self.bank_select_or_position.x
|
|
|
|
|
- bank_select_line_xoffset),
|
|
|
|
|
height=drc["minwidth_metal1"])
|
|
|
|
|
self.add_via(layers=("metal1", "via1", "metal2"),
|
|
|
|
|
offset=[bank_select_line_xoffset, B_y_offset])
|
|
|
|
|
|
|
|
|
|
# A_input
|
|
|
|
|
if i != 3:
|
|
|
|
|
self.add_rect(layer="metal3",
|
|
|
|
|
offset=[self.left_vdd_x_offset, A_y_offset],
|
|
|
|
|
width=(self.bank_select_or_position.x
|
|
|
|
|
- self.left_vdd_x_offset),
|
|
|
|
|
height=drc["minwidth_metal3"])
|
|
|
|
|
self.add_via(layers=("metal1", "via1", "metal2"),
|
|
|
|
|
offset=[self.bank_select_or_position.x
|
|
|
|
|
+ drc["minwidth_metal1"],
|
|
|
|
|
A_y_offset],
|
2017-08-24 00:02:15 +02:00
|
|
|
rotate=90)
|
2016-11-08 18:57:35 +01:00
|
|
|
self.add_via(layers=("metal2", "via2", "metal3"),
|
|
|
|
|
offset=[self.bank_select_or_position.x
|
|
|
|
|
+ drc["minwidth_metal1"],
|
|
|
|
|
A_y_offset],
|
2017-08-24 00:02:15 +02:00
|
|
|
rotate=90)
|
2016-11-08 18:57:35 +01:00
|
|
|
else:
|
|
|
|
|
# connect A to last A, both are tri_en_bar
|
|
|
|
|
via_offset = vector(self.bank_select_or_position.x
|
|
|
|
|
+ drc["minwidth_metal1"],
|
|
|
|
|
A_y_offset)
|
|
|
|
|
self.add_via(layers=("metal1", "via1", "metal2"),
|
|
|
|
|
offset=via_offset,
|
2017-08-24 00:02:15 +02:00
|
|
|
rotate=90)
|
2016-11-08 18:57:35 +01:00
|
|
|
self.add_via(layers=("metal2", "via2", "metal3"),
|
|
|
|
|
offset=via_offset,
|
2017-08-24 00:02:15 +02:00
|
|
|
rotate=90)
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
start = via_offset + vector(0, 0.5 * self.m1m2_via.width)
|
|
|
|
|
mid = [self.left_vdd_x_offset - self.left_vdd_x_offset
|
|
|
|
|
- drc["minwidth_metal2"] - drc["metal2_to_metal2"]
|
|
|
|
|
+ bank_select_line_xoffset,
|
|
|
|
|
start.y]
|
2017-08-24 00:02:15 +02:00
|
|
|
correct_y = (2 * self.nor2.A_position.y + drc["minwidth_metal1"]
|
2016-11-08 18:57:35 +01:00
|
|
|
- self.m1m2_via.width)
|
|
|
|
|
end = start + vector(0, correct_y)
|
2016-11-17 23:05:50 +01:00
|
|
|
self.add_wire(("metal3", "via2", "metal2"), [start, mid, end])
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
# Save position
|
|
|
|
|
setattr(self,"{0}_position".format(self.control_signals[i]),
|
|
|
|
|
[self.left_vdd_x_offset, A_y_offset])
|
|
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
def route_vdd_supply(self):
|
|
|
|
|
""" Route vdd for the precharge, sense amp, write_driver, data FF, tristate """
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
for inst in [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 vdd_pin in inst.get_pins("vdd"):
|
2016-11-08 18:57:35 +01:00
|
|
|
self.add_rect(layer="metal1",
|
2017-08-24 00:02:15 +02:00
|
|
|
offset=vdd_pin.ll(),
|
|
|
|
|
width=self.right_vdd_x_offset - vdd_pin.lx(),
|
2016-11-08 18:57:35 +01:00
|
|
|
height=drc["minwidth_metal1"])
|
|
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
return
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
# Connect bank_select_and2_array vdd
|
2016-11-08 18:57:35 +01:00
|
|
|
if(self.num_banks > 1):
|
2017-08-24 00:02:15 +02:00
|
|
|
for i in range(self.num_control_lines):
|
2016-11-08 18:57:35 +01:00
|
|
|
if(i % 2):
|
|
|
|
|
self.add_rect(layer="metal1",
|
|
|
|
|
offset=[self.left_vdd_x_offset,
|
|
|
|
|
self.bank_select_or_position.y
|
|
|
|
|
+ i * self.inv.height
|
|
|
|
|
- 0.5 * drc["minwidth_metal1"]],
|
|
|
|
|
width=(self.bank_select_or_position.x
|
|
|
|
|
- self.left_vdd_x_offset),
|
|
|
|
|
height=drc["minwidth_metal1"])
|
|
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
def route_gnd_supply(self):
|
|
|
|
|
""" Route gnd for the precharge, sense amp, write_driver, data FF, tristate """
|
|
|
|
|
# precharge is connected by abutment
|
|
|
|
|
# msf_data_in is by abutment
|
|
|
|
|
for inst in [ self.tri_gate_array_inst, self.sense_amp_array_inst, self.write_driver_array_inst]:
|
|
|
|
|
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
|
|
|
|
|
gnd_offset = vector(self.gnd_x_offset+self.gnd_rail_width-self.m1m2_via.width, gnd_pin.by())
|
|
|
|
|
self.add_rect(layer="metal1",
|
|
|
|
|
offset=gnd_offset,
|
|
|
|
|
width=gnd_pin.lx() - self.gnd_x_offset,
|
|
|
|
|
height=drc["minwidth_metal1"])
|
2016-11-08 18:57:35 +01:00
|
|
|
self.add_via(layers=("metal1", "via1", "metal2"),
|
2017-08-24 00:02:15 +02:00
|
|
|
offset=gnd_offset)
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
|
|
|
|
|
# Connect bank_select_or2_array gnd
|
2016-11-08 18:57:35 +01:00
|
|
|
if(self.num_banks > 1):
|
|
|
|
|
self.bank_select_inv_position
|
|
|
|
|
self.add_rect(layer="metal1",
|
|
|
|
|
offset=(self.bank_select_inv_position
|
|
|
|
|
+ self.inv4x.gnd_position),
|
|
|
|
|
width=(self.bank_select_or_position.x
|
|
|
|
|
- self.bank_select_inv_position.x),
|
|
|
|
|
height=drc["minwidth_metal1"])
|
|
|
|
|
|
|
|
|
|
x_offset = (self.bank_select_or_position.x
|
2017-08-24 00:02:15 +02:00
|
|
|
+ self.nor2.width + self.inv4x.width)
|
|
|
|
|
for i in range(self.num_control_lines):
|
2016-11-08 18:57:35 +01:00
|
|
|
if(i % 2 == 0):
|
|
|
|
|
y_offset = self.bank_select_or_position.y + i*self.inv.height \
|
|
|
|
|
- 0.5*drc["minwidth_metal1"]
|
|
|
|
|
#both M1 & M2 are horizontal, cannot be replaced with wire
|
|
|
|
|
self.add_rect(layer="metal1",
|
|
|
|
|
offset=[x_offset, y_offset],
|
|
|
|
|
width=drc["minwidth_metal1"],
|
|
|
|
|
height=drc["minwidth_metal1"])
|
|
|
|
|
self.add_rect(layer="metal2",
|
|
|
|
|
offset=[x_offset, y_offset],
|
|
|
|
|
width=self.left_gnd_x_offset \
|
|
|
|
|
- x_offset + self.power_rail_width,
|
|
|
|
|
height=drc["minwidth_metal2"])
|
|
|
|
|
self.add_via(layers=("metal1", "via1", "metal2"),
|
|
|
|
|
offset=[x_offset + drc["minwidth_metal1"],
|
|
|
|
|
y_offset],
|
2017-08-24 00:02:15 +02:00
|
|
|
rotate=90)
|
|
|
|
|
|
|
|
|
|
def add_control_pins(self):
|
|
|
|
|
""" Add the control signal input pins """
|
|
|
|
|
|
|
|
|
|
if self.num_banks==1:
|
|
|
|
|
# If we are a single bank, just add duplicate pin shapes
|
|
|
|
|
# on the existing the control bus
|
|
|
|
|
for ctrl in self.control_signals:
|
|
|
|
|
x_offset = self.central_line_xoffset[ctrl]
|
|
|
|
|
self.add_layout_pin(text=ctrl,
|
|
|
|
|
layer="metal2",
|
|
|
|
|
offset=vector(x_offset, self.min_point),
|
|
|
|
|
width=self.m2_width,
|
|
|
|
|
height=self.height)
|
|
|
|
|
else:
|
|
|
|
|
# If we are gating the signals, they must be the inputs to the gating logic
|
|
|
|
|
# Then route the outputs to the control bus
|
|
|
|
|
self.route_bank_select_or2_gates()
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
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_position = vector(self.rail_1_x_offsets[rail], in_pin.y)
|
|
|
|
|
self.add_wire(("metal3","via2","metal2"),[in_pin, rail_position, rail_position - vector(0,self.m2_pitch)])
|
|
|
|
|
# Bring it up to M2 for M2/M3 routing
|
|
|
|
|
self.add_via(layers=("metal1","via1","metal2"),
|
|
|
|
|
offset=in_pin + self.m1m2_via_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_position = vector(self.rail_1_x_offsets[rail], in_pin.y)
|
|
|
|
|
self.add_wire(("metal3","via2","metal2"),[in_pin, rail_position, rail_position - vector(0,self.m2_pitch)])
|
|
|
|
|
self.add_via(layers=("metal1","via1","metal2"),
|
|
|
|
|
offset=in_pin + self.m1m2_via_offset,
|
|
|
|
|
rotate=90)
|
|
|
|
|
self.add_via(layers=("metal2","via2","metal3"),
|
|
|
|
|
offset=in_pin + self.m2m3_via_offset,
|
|
|
|
|
rotate=90)
|
|
|
|
|
|
2017-07-06 17:42:25 +02:00
|
|
|
def delay(self, slew, load):
|
2017-05-30 21:50:07 +02:00
|
|
|
""" return analytical delay of the bank"""
|
2017-07-06 17:42:25 +02:00
|
|
|
msf_addr_delay = self.msf_address.delay(slew, self.decoder.input_load())
|
2017-05-30 21:50:07 +02:00
|
|
|
|
2017-07-06 17:42:25 +02:00
|
|
|
decoder_delay = self.decoder.delay(msf_addr_delay.slew, self.wordline_driver.input_load())
|
2017-05-30 21:50:07 +02:00
|
|
|
|
2017-07-06 17:42:25 +02:00
|
|
|
word_driver_delay = self.wordline_driver.delay(decoder_delay.slew, self.bitcell_array.input_load())
|
2017-05-30 21:50:07 +02:00
|
|
|
|
2017-07-06 17:42:25 +02:00
|
|
|
bitcell_array_delay = self.bitcell_array.delay(word_driver_delay.slew)
|
2017-05-30 21:50:07 +02:00
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
bl_t_data_out_delay = self.sense_amp_array.delay(bitcell_array_delay.slew,
|
2017-05-30 21:50:07 +02:00
|
|
|
self.bitcell_array.output_load())
|
|
|
|
|
# output load of bitcell_array is set to be only small part of bl for sense amp.
|
|
|
|
|
|
2017-07-06 17:42:25 +02:00
|
|
|
data_t_DATA_delay = self.tri_gate_array.delay(bl_t_data_out_delay.slew, load)
|
2017-05-30 21:50:07 +02:00
|
|
|
|
|
|
|
|
result = msf_addr_delay + decoder_delay + word_driver_delay \
|
|
|
|
|
+ bitcell_array_delay + bl_t_data_out_delay + data_t_DATA_delay
|
|
|
|
|
return result
|