mirror of https://github.com/VLSIDA/OpenRAM.git
428 lines
19 KiB
Python
428 lines
19 KiB
Python
import debug
|
|
import design
|
|
from tech import drc
|
|
from pinv import pinv
|
|
from contact import contact
|
|
from bitcell_array import bitcell_array
|
|
from nor_2 import nor_2
|
|
from ptx import ptx
|
|
from vector import vector
|
|
from globals import OPTS
|
|
|
|
class replica_bitline(design.design):
|
|
"""
|
|
Generate a module that simulate the delay of control logic
|
|
and bit line charging.
|
|
Used for memory timing control
|
|
"""
|
|
|
|
def __init__(self, name, rows):
|
|
design.design.__init__(self, "replica_bitline")
|
|
|
|
g = reload(__import__(OPTS.config.delay_chain))
|
|
self.mod_delay_chain = getattr(g, OPTS.config.delay_chain)
|
|
|
|
g = reload(__import__(OPTS.config.replica_bitcell))
|
|
self.mod_replica_bitcell = getattr(g, OPTS.config.replica_bitcell)
|
|
|
|
c = reload(__import__(OPTS.config.bitcell))
|
|
self.mod_bitcell = getattr(c, OPTS.config.bitcell)
|
|
self.bitcell_chars = self.mod_bitcell.chars
|
|
|
|
for pin in ["en", "out", "vdd", "gnd"]:
|
|
self.add_pin(pin)
|
|
self.rows = rows
|
|
|
|
self.create_modules()
|
|
self.cal_modules_offset()
|
|
self.add_modules()
|
|
self.route()
|
|
self.offset_all_coordinates()
|
|
|
|
self.DRC_LVS()
|
|
|
|
def cal_modules_offset(self):
|
|
pinv_error_offset = 0.025
|
|
# leave some room for metal1 routing
|
|
margin = 3 * drc["minwidth_metal1"]
|
|
# witdth + min_spacing of M1 & M2
|
|
m1rail_space = drc["minwidth_metal1"] + drc["metal1_to_metal1"]
|
|
m2rail_space = drc["minwidth_metal2"] + drc["metal2_to_metal2"]
|
|
# leave some margin as bit cell layout exceeds its own orgin
|
|
route_margin = 8 * m2rail_space
|
|
well_margin = 2 * drc["pwell_enclose_nwell"]
|
|
bitcell_array_spacing = max(route_margin, well_margin)
|
|
# now extra space for BL and WL of RBC
|
|
gnd_route_margin = 5 * m2rail_space
|
|
|
|
y_off = (self.inv.height * 2 + pinv_error_offset
|
|
+ max(drc["pwell_enclose_nwell"],
|
|
m1rail_space * 4))
|
|
self.delay_chain_offset = vector(self.delay_chain.height,y_off)
|
|
self.en_input_offset = vector(0, y_off - m2rail_space)
|
|
self.en_nor_offset = vector(self.nor.width + margin,
|
|
self.inv.height * 2)
|
|
self.BL_inv_offset = vector(self.en_nor_offset.x - self.inv.width, 0)
|
|
self.access_tx_offset = vector(self.en_nor_offset.x - self.nor.width
|
|
+ self.access_tx.height + margin,
|
|
self.inv.height * 0.5)
|
|
self.replica_bitline_offset = vector(self.delay_chain_offset.x
|
|
+ bitcell_array_spacing,
|
|
self.bitcell_chars["height"] + gnd_route_margin)
|
|
self.delay_inv_offset = vector(self.delay_chain_offset.x - self.inv.width,
|
|
self.inv.height * 2)
|
|
|
|
self.height = m1rail_space + max(self.delay_chain_offset.y + self.inv.height,
|
|
self.replica_bitline_offset.y
|
|
+ self.bitline_load.height
|
|
+ 0.5 * self.bitcell_chars["height"])
|
|
self.width = (self.replica_bitline_offset.x + self.replica_bitcell.width)
|
|
|
|
|
|
def create_modules(self):
|
|
""" create module """
|
|
self.replica_bitcell = self.mod_replica_bitcell()
|
|
self.add_mod(self.replica_bitcell)
|
|
|
|
# This is the replica bitline load column that is the same height as our array
|
|
self.bitline_load = bitcell_array(name="bitline_load",
|
|
cols=1,
|
|
rows=self.rows)
|
|
self.add_mod(self.bitline_load)
|
|
|
|
# FIXME: This just creates 3 1x inverters
|
|
self.delay_chain = self.mod_delay_chain("delay_chain",
|
|
[1, 1, 1])
|
|
self.add_mod(self.delay_chain)
|
|
|
|
self.inv = pinv(name="RBL_inv",
|
|
nmos_width=drc["minwidth_tx"])
|
|
self.add_mod(self.inv)
|
|
|
|
# These aren't for instantiating, but we use them to get the dimensions
|
|
self.poly_contact = contact(layer_stack=("poly", "contact", "metal1"))
|
|
self.m1m2_via = contact(layer_stack=("metal1", "via1", "metal2"))
|
|
self.m2m3_via = contact(layer_stack=("metal2", "via2", "metal3"))
|
|
|
|
self.nor = nor_2(name="replica_bitline_nor2",
|
|
nmos_width=drc["minwidth_tx"])
|
|
self.add_mod(self.nor)
|
|
|
|
self.access_tx = ptx(name="access_tx",
|
|
width=drc["minwidth_tx"],
|
|
mults=1,
|
|
tx_type="pmos")
|
|
self.add_mod(self.access_tx)
|
|
|
|
def add_modules(self):
|
|
"""add mod instance in layout """
|
|
self.add_inst(name="BL_inv",
|
|
mod=self.inv,
|
|
offset=self.BL_inv_offset)
|
|
self.connect_inst(["bl[0]", "out", "vdd", "gnd"])
|
|
|
|
self.add_inst(name="BL_access_tx",
|
|
mod=self.access_tx,
|
|
offset=self.access_tx_offset,
|
|
rotate=90)
|
|
# D, G, S, B
|
|
self.connect_inst(["vdd", "delayed_en", "bl[0]", "vdd"])
|
|
|
|
self.add_inst(name="delay_chain",
|
|
mod=self.delay_chain,
|
|
offset=self.delay_chain_offset,
|
|
rotate=90)
|
|
self.connect_inst(["en", "delayed_en", "vdd", "gnd"])
|
|
|
|
self.add_inst(name="bitcell",
|
|
mod=self.replica_bitcell,
|
|
offset=self.replica_bitline_offset,
|
|
mirror="MX")
|
|
self.connect_inst(["bl[0]", "br[0]", "delayed_en", "vdd", "gnd"])
|
|
|
|
self.add_loads()
|
|
self.expan_the_well_to_BL_inv()
|
|
|
|
def expan_the_well_to_BL_inv(self):
|
|
width = self.BL_inv_offset.x - self.access_tx_offset.x + self.inv.width
|
|
well_offset = self.access_tx_offset - vector(self.access_tx.width, 0)
|
|
for layer in ["nwell", "vtg"]:
|
|
self.add_rect(layer=layer,
|
|
offset=well_offset,
|
|
width=width,
|
|
height= 2*self.access_tx.width)
|
|
|
|
def add_loads(self):
|
|
self.add_inst(name="load",
|
|
mod=self.bitline_load,
|
|
offset=self.replica_bitline_offset)
|
|
temp = []
|
|
for i in range(1):
|
|
temp.append("bl[{0}]".format(i))
|
|
temp.append("br[{0}]".format(i))
|
|
for j in range(self.rows):
|
|
temp.append("gnd".format(j))
|
|
temp = temp + ["vdd", "gnd"]
|
|
self.connect_inst(temp)
|
|
|
|
def route(self):
|
|
"""connect modules together"""
|
|
# calculate pin offset
|
|
correct = vector(0, 0.5 * drc["minwidth_metal1"])
|
|
self.out_offset = self.BL_inv_offset + self.inv.Z_position + correct
|
|
self.add_via(layers=("metal1", "via1", "metal2"),
|
|
offset=self.out_offset)
|
|
m1_pin_offset = self.out_offset - correct
|
|
self.add_rect(layer="metal1",
|
|
offset=m1_pin_offset,
|
|
width=self.m1m2_via.width,
|
|
height=self.m1m2_via.height)
|
|
self.add_rect(layer="metal2",
|
|
offset=m1_pin_offset,
|
|
width=self.m2m3_via.width,
|
|
height=self.m2m3_via.height)
|
|
|
|
BL_inv_in = self.BL_inv_offset + self.inv.A_position + correct
|
|
BL_offset = self.replica_bitline_offset + vector(1,0).scale(self.bitcell_chars["BL"])
|
|
pin_offset = self.delay_chain.clk_out_offset.rotate().scale(-1,1)
|
|
delay_chain_output = self.delay_chain_offset + pin_offset
|
|
|
|
self.create_input()
|
|
|
|
self.route_BL_t_BL_inv(BL_offset, BL_inv_in)
|
|
self.route_access_tx(delay_chain_output, BL_inv_in)
|
|
self.route_vdd()
|
|
self.route_gnd()
|
|
# route loads after gnd and vdd created
|
|
self.route_loads()
|
|
self.route_RC()
|
|
|
|
def create_input(self):
|
|
# create routing module based on module offset
|
|
correct = vector(0.5 * drc["minwidth_metal1"], 0)
|
|
pin_offset = self.delay_chain.clk_in_offset.rotate().scale(-1,1)
|
|
input_offset = self.delay_chain_offset + pin_offset + correct
|
|
mid1 = [input_offset[0], self.en_input_offset[1]]
|
|
self.add_path("metal1", [self.en_input_offset, mid1, input_offset])
|
|
|
|
self.add_label(text="en",
|
|
layer="metal1",
|
|
offset=self.en_input_offset)
|
|
|
|
def route_nor2A_t_dc(self, nor_A, delayed_en_offset):
|
|
# delay chain output to m2
|
|
dc_offset = [delayed_en_offset[0], delayed_en_offset[1]]
|
|
mid1 = [dc_offset[0], self.en_nor_offset
|
|
[1] + 3 * drc["minwidth_metal2"]]
|
|
mid2 = [self.delay_chain_offset[0] + 3*drc["minwidth_metal2"],
|
|
dc_offset[1]]
|
|
mid3 = [mid2[0], nor_A[1]]
|
|
self.add_wire(("metal2", "via1", "metal1"),
|
|
[dc_offset, mid2, mid3, nor_A])
|
|
|
|
def route_nor2B_t_BL_inv(self, nor_B, BL_inv_out):
|
|
mid1 = [nor_B[0] + 0.5 * drc["metal2_to_metal2"], nor_B[1]]
|
|
self.add_wire(("metal2", "via1", "metal1"),
|
|
[nor_B, mid1, BL_inv_out])
|
|
|
|
def route_BL_t_BL_inv(self, BL_offset, BL_inv_in):
|
|
# BL_inv input to M3
|
|
mid1 = [BL_inv_in[0],
|
|
BL_inv_in[1] - drc["metal2_to_metal2"] - self.m1m2_via.width]
|
|
mid2 = [self.en_nor_offset[0] + 3*drc["metal1_to_metal1"],
|
|
mid1[1]]
|
|
mid3 = [mid2[0],
|
|
self.replica_bitline_offset[1] - self.replica_bitcell.height
|
|
- 0.5 * (self.m1m2_via.height + drc["metal1_to_metal1"])
|
|
- 2 * drc["metal1_to_metal1"]]
|
|
self.add_wire(("metal1", "via1", "metal2"),
|
|
[BL_inv_in, mid1, mid2, mid3])
|
|
|
|
# need to fix the mid point as this is done with two wire
|
|
# this seems to cover the metal1 error of the wire
|
|
offset = mid3 - vector( [0.5 * drc["minwidth_metal1"]] * 2)
|
|
self.add_rect(layer="metal1",
|
|
offset=offset,
|
|
width=drc["minwidth_metal1"],
|
|
height=drc["minwidth_metal1"])
|
|
|
|
mid4 = [BL_offset[0], mid3[1]]
|
|
self.add_wire(("metal2", "via1", "metal1"),
|
|
[BL_offset, mid4, mid3])
|
|
|
|
def route_access_tx(self, delay_chain_output, BL_inv_in):
|
|
self.route_tx_gate(delay_chain_output)
|
|
self.route_tx_drain()
|
|
self.route_tx_source(BL_inv_in)
|
|
|
|
def route_tx_gate(self, delay_chain_output):
|
|
# gate input for access tx
|
|
offset = (self.access_tx.poly_positions[0].rotate().scale(0,1)
|
|
+ self.access_tx_offset)
|
|
width = -6 * drc["minwidth_metal1"]
|
|
self.add_rect(layer="poly",
|
|
offset=offset,
|
|
width=width,
|
|
height=drc["minwidth_poly"])
|
|
y_off = 0.5 * (drc["minwidth_poly"] - self.poly_contact.height)
|
|
offset = offset + vector(width, y_off)
|
|
self.add_contact(layers=("poly", "contact", "metal1"),
|
|
offset=offset)
|
|
# route gate to delay_chain output
|
|
gate_offset = offset + vector(0.5 * drc["minwidth_metal1"],
|
|
0.5 * self.poly_contact.width)
|
|
self.route_access_tx_t_delay_chain(gate_offset, delay_chain_output)
|
|
self.route_access_tx_t_WL(gate_offset)
|
|
|
|
def route_access_tx_t_delay_chain(self, offset, delay_chain_output):
|
|
m2rail_space = (drc["minwidth_metal2"] + drc["metal2_to_metal2"])
|
|
mid1 = [offset[0], self.delay_chain_offset[1] - 3 * m2rail_space]
|
|
mid2 = [delay_chain_output[0], mid1[1]]
|
|
# Note the inverted wire stack
|
|
self.add_wire(("metal2", "via1", "metal1"),
|
|
[offset, mid1, mid2, delay_chain_output])
|
|
self.add_via(layers=("metal1", "via1", "metal2"),
|
|
offset=delay_chain_output,
|
|
mirror="MX")
|
|
|
|
def route_access_tx_t_WL(self, offset):
|
|
m1m2_via_offset = offset - vector(0.5 * self.m1m2_via.width,
|
|
0.5 * self.m1m2_via.height)
|
|
self.add_via(layers=("metal1", "via1", "metal2"),
|
|
offset=m1m2_via_offset)
|
|
# route gate to RC WL
|
|
RC_WL = self.replica_bitline_offset - vector(0,1).scale(self.bitcell_chars["WL"])
|
|
mid1 = [offset[0], 0]
|
|
mid2 = [self.en_nor_offset[0] + 3 * drc["metal1_to_metal1"],
|
|
mid1[1]]
|
|
mid3 = [RC_WL[0] - drc["minwidth_metal1"] - self.m1m2_via.height,
|
|
mid1[1]]
|
|
mid4 = [mid3[0], RC_WL[1]]
|
|
self.add_path("metal2", [offset, mid1, mid2, mid3, mid4])
|
|
|
|
offset = mid4 - vector([0.5 * drc["minwidth_metal1"]]*2)
|
|
width = RC_WL[0] - offset[0]
|
|
# enter the bit line array with metal1
|
|
via_offset = [mid4[0] - 0.5 * self.m1m2_via.width,
|
|
offset[1] - 0.5 * (self.m1m2_via.height
|
|
- drc["minwidth_metal1"])]
|
|
self.add_via(layers=("metal1", "via1", "metal2"),
|
|
offset=via_offset)
|
|
self.add_rect(layer="metal1",
|
|
offset=offset,
|
|
width=width,
|
|
height=drc["minwidth_metal1"])
|
|
|
|
def route_tx_drain(self):
|
|
# route drain to Vdd
|
|
active_offset = self.access_tx.active_contact_positions[1].rotate().scale(-1,1)
|
|
correct = vector(drc["minwidth_metal1"],
|
|
self.access_tx.active_contact.width).scale(-0.5, 0.5)
|
|
drain_offset = self.access_tx_offset + active_offset + correct
|
|
vdd_rail = [self.delay_chain_offset[0] + 9 * drc["minwidth_metal2"],
|
|
self.height]
|
|
close_Vdd_offset = self.BL_inv_offset + vector(0, self.inv.height)
|
|
self.add_path("metal1", [drain_offset, close_Vdd_offset])
|
|
|
|
mid = [vdd_rail[0], close_Vdd_offset[1]]
|
|
self.add_wire(("metal2", "via1", "metal1"),
|
|
[close_Vdd_offset, mid, vdd_rail])
|
|
|
|
def route_tx_source(self, BL_inv_in):
|
|
# route source to BL inv input which is connected to BL
|
|
active_offset = self.access_tx.active_contact_positions[0].rotate().scale(-1,1)
|
|
correct = vector(drc["minwidth_metal1"],
|
|
self.access_tx.active_contact.width).scale(-0.5, 0.5)
|
|
source_offset = self.access_tx_offset + active_offset + correct
|
|
self.add_path("metal1", [source_offset, BL_inv_in])
|
|
|
|
def route_vdd(self):
|
|
vdd_offset = vector(0, self.height)
|
|
self.add_layout_pin(text="vdd",
|
|
layer="metal1",
|
|
offset=vdd_offset,
|
|
width=self.width,
|
|
height=drc["minwidth_metal1"])
|
|
# delay chain vdd to vertical vdd rail and
|
|
start = self.delay_chain_offset - vector(0.5 * self.delay_chain.height, 0)
|
|
m1rail_space = (drc["minwidth_metal1"] + drc["metal1_to_metal1"])
|
|
mid1 = start - vector(0, m1rail_space)
|
|
mid2 = [self.delay_chain_offset[0] + 9 * drc["minwidth_metal2"],
|
|
mid1[1]]
|
|
end = [mid2[0], vdd_offset[1]]
|
|
self.add_path(("metal1"), [start, mid1, mid2])
|
|
self.add_wire(("metal2", "via1", "metal1"), [mid1, mid2, end])
|
|
|
|
def route_gnd(self):
|
|
"""route gnd of delay chain, en_nor, en_inv and BL_inv"""
|
|
# route delay chain gnd to BL_inv gnd
|
|
# gnd Node between BL_inv access tx and delay chain, and is below
|
|
# en_input
|
|
self.gnd_position = self.delay_chain_offset
|
|
BL_gnd_offset = self.BL_inv_offset
|
|
mid1 = vector(0, self.BL_inv_offset.y)
|
|
rail2_space = drc["minwidth_metal2"] + drc["metal2_to_metal2"]
|
|
y_off = self.gnd_position.y + self.delay_chain.width + rail2_space
|
|
mid2 = vector(mid1.x, y_off)
|
|
share_gnd = vector(self.gnd_position.x, mid2.y)
|
|
# Note the inverted stacks
|
|
self.add_wire(("metal2", "via1", "metal1"),
|
|
[BL_gnd_offset, mid1, mid2, share_gnd, self.gnd_position])
|
|
self.add_label(text="gnd",
|
|
layer="metal1",
|
|
offset=self.gnd_position)
|
|
# connect to the metal1 gnd of delay chain
|
|
offset = mid2 - vector(0.5 * drc["minwidth_metal1"], 0)
|
|
self.add_rect(layer="metal1",
|
|
offset=offset,
|
|
width=drc["minwidth_metal1"],
|
|
height=-self.delay_chain.width)
|
|
offset = [offset[0] + self.delay_chain.height,
|
|
mid2[1]]
|
|
self.add_rect(layer="metal1",
|
|
offset=offset,
|
|
width=drc["minwidth_metal1"],
|
|
height=-self.delay_chain.width)
|
|
|
|
def route_loads(self):
|
|
"""connect all the loads word line to gnd"""
|
|
vdd_offset = [self.delay_chain_offset[0] + 9*drc["minwidth_metal2"],
|
|
self.height]
|
|
self.add_via(layers=("metal1", "via1", "metal2"),
|
|
offset=vdd_offset,
|
|
mirror="MX")
|
|
gnd_offset = (self.delay_chain_offset
|
|
+ vector([drc["minwidth_metal1"]]*2).scale(-.5,.5))
|
|
for i in range(self.rows):
|
|
WL_offset = (self.replica_bitline_offset
|
|
+ self.bitline_load.WL_positions[i].scale(0,1))
|
|
mid = [self.delay_chain_offset[0] + 6 * drc["minwidth_metal2"],
|
|
gnd_offset[1]]
|
|
self.add_wire(("metal2", "via1", "metal1"), [gnd_offset, mid, WL_offset])
|
|
if i % 2 == 0:
|
|
load_vdd_offset = (self.replica_bitline_offset
|
|
+ self.bitline_load.vdd_positions[i])
|
|
mid = [vdd_offset[0], load_vdd_offset[1]]
|
|
self.add_wire(("metal2", "via1", "metal1"), [vdd_offset, mid, load_vdd_offset])
|
|
|
|
def route_RC(self):
|
|
"""route vdd gnd to the replica cell """
|
|
# connect vdd
|
|
RC_vdd = self.replica_bitline_offset + vector(1,-1).scale(self.bitcell_chars["vdd"])
|
|
vdd_offset = [self.delay_chain_offset[0] + 9 * drc["minwidth_metal2"],
|
|
self.height]
|
|
mid = [vdd_offset[0], RC_vdd[1]]
|
|
# Note the inverted stacks
|
|
self.add_wire(("metal2", "via1", "metal1"), [vdd_offset, mid, RC_vdd])
|
|
|
|
gnd_offset = self.BL_inv_offset - vector(self.inv.width, 0)
|
|
load_gnd = (self.replica_bitline_offset
|
|
+ vector(self.bitcell_chars["gnd"][0], self.bitline_load.height))
|
|
mid = [load_gnd[0], gnd_offset[1]]
|
|
self.add_wire(("metal2", "via1", "metal1"), [gnd_offset, mid, load_gnd])
|
|
|
|
load_gnd = (self.replica_bitline_offset
|
|
+ vector(0, self.bitline_load.height))
|
|
mid = [load_gnd[0], gnd_offset[1]]
|
|
self.add_wire(("metal2", "via1", "metal1"), [gnd_offset, mid, load_gnd])
|