Fixed merging issues with power branch

This commit is contained in:
Hunter Nichols
2018-02-14 15:21:42 -08:00
145 changed files with 3924 additions and 4543 deletions
File diff suppressed because it is too large Load Diff
+41
View File
@@ -0,0 +1,41 @@
import design
import debug
import utils
from tech import GDS,layer
class bitcell(design.design):
"""
A single bit cell (6T, 8T, etc.) This module implements the
single memory cell used in the design. It is a hand-made cell, so
the layout and netlist should be available in the technology
library.
"""
pin_names = ["BL", "BR", "WL", "vdd", "gnd"]
(width,height) = utils.get_libcell_size("cell_6t", GDS["unit"], layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "cell_6t", GDS["unit"], layer["boundary"])
def __init__(self):
design.design.__init__(self, "cell_6t")
debug.info(2, "Create bitcell")
self.width = bitcell.width
self.height = bitcell.height
self.pin_map = bitcell.pin_map
def analytical_delay(self, slew, load=0, swing = 0.5):
# delay of bit cell is not like a driver(from WL)
# so the slew used should be 0
# it should not be slew dependent?
# because the value is there
# the delay is only over half transsmission gate
from tech import spice
r = spice["min_tx_r"]*3
c_para = spice["min_tx_drain_c"]
result = self.cal_delay_with_rc(r = r, c = c_para+load, slew = slew, swing = swing)
return result
def analytical_power(self, slew, load=0, swing = 0.5):
#Power of the bitcell. Mostly known for leakage, but dynamic can also be factored in.
#Just skeleton code for now which returns a magic number.
return 5
+220
View File
@@ -0,0 +1,220 @@
import debug
import design
from tech import drc, spice
from vector import vector
from globals import OPTS
class bitcell_array(design.design):
"""
Creates a rows x cols array of memory cells. Assumes bit-lines
and word line is connected by abutment.
Connects the word lines and bit lines.
"""
def __init__(self, cols, rows, name="bitcell_array"):
design.design.__init__(self, name)
debug.info(1, "Creating {0} {1} x {2}".format(self.name, rows, cols))
self.column_size = cols
self.row_size = rows
c = reload(__import__(OPTS.bitcell))
self.mod_bitcell = getattr(c, OPTS.bitcell)
self.cell = self.mod_bitcell()
self.add_mod(self.cell)
# We increase it by a well enclosure so the precharges don't overlap our wells
self.height = self.row_size*self.cell.height + drc["well_enclosure_active"]
self.width = self.column_size*self.cell.width
self.add_pins()
self.create_layout()
self.add_layout_pins()
self.DRC_LVS()
def add_pins(self):
for col in range(self.column_size):
self.add_pin("bl[{0}]".format(col))
self.add_pin("br[{0}]".format(col))
for row in range(self.row_size):
self.add_pin("wl[{0}]".format(row))
self.add_pin("vdd")
self.add_pin("gnd")
def create_layout(self):
xoffset = 0.0
self.cell_inst = {}
for col in range(self.column_size):
yoffset = 0.0
for row in range(self.row_size):
name = "bit_r{0}_c{1}".format(row, col)
if row % 2:
tempy = yoffset + self.cell.height
dir_key = "MX"
else:
tempy = yoffset
dir_key = ""
self.cell_inst[row,col]=self.add_inst(name=name,
mod=self.cell,
offset=[xoffset, tempy],
mirror=dir_key)
self.connect_inst(["bl[{0}]".format(col),
"br[{0}]".format(col),
"wl[{0}]".format(row),
"vdd",
"gnd"])
yoffset += self.cell.height
xoffset += self.cell.width
def add_layout_pins(self):
# Our cells have multiple gnd pins for now.
# FIXME: fix for multiple vdd too
vdd_pin = self.cell.get_pin("vdd")
# shift it up by the overlap amount (gnd_pin) too
# must find the lower gnd pin to determine this overlap
lower_y = self.cell.height
gnd_pins = self.cell.get_pins("gnd")
for gnd_pin in gnd_pins:
if gnd_pin.layer=="metal2" and gnd_pin.by()<lower_y:
lower_y=gnd_pin.by()
# lower_y is negative, so subtract off double this amount for each pair of
# overlapping cells
full_height = self.height - 2*lower_y
vdd_pin = self.cell.get_pin("vdd")
lower_x = vdd_pin.lx()
# lower_x is negative, so subtract off double this amount for each pair of
# overlapping cells
full_width = self.width - 2*lower_x
offset = vector(0.0, 0.0)
for col in range(self.column_size):
# get the pin of the lower row cell and make it the full width
bl_pin = self.cell_inst[0,col].get_pin("BL")
br_pin = self.cell_inst[0,col].get_pin("BR")
self.add_layout_pin(text="bl[{0}]".format(col),
layer="metal2",
offset=bl_pin.ll(),
width=bl_pin.width(),
height=full_height)
self.add_layout_pin(text="br[{0}]".format(col),
layer="metal2",
offset=br_pin.ll(),
width=br_pin.width(),
height=full_height)
# gnd offset is 0 in our cell, but it be non-zero
gnd_pins = self.cell_inst[0,col].get_pins("gnd")
for gnd_pin in gnd_pins:
# avoid duplicates by only doing even rows
# also skip if it isn't the pin that spans the entire cell down to the bottom
if gnd_pin.layer=="metal2" and gnd_pin.by()==lower_y:
self.add_layout_pin(text="gnd",
layer="metal2",
offset=gnd_pin.ll(),
width=gnd_pin.width(),
height=full_height)
# increments to the next column width
offset.x += self.cell.width
offset.x = 0.0
for row in range(self.row_size):
wl_pin = self.cell_inst[row,0].get_pin("WL")
vdd_pins = self.cell_inst[row,0].get_pins("vdd")
gnd_pins = self.cell_inst[row,0].get_pins("gnd")
for gnd_pin in gnd_pins:
if gnd_pin.layer=="metal1":
self.add_layout_pin(text="gnd",
layer="metal1",
offset=gnd_pin.ll(),
width=full_width,
height=drc["minwidth_metal1"])
# add vdd label and offset
# only add to even rows to avoid duplicates
for vdd_pin in vdd_pins:
if row % 2 == 0 and vdd_pin.layer=="metal1":
self.add_layout_pin(text="vdd",
layer="metal1",
offset=vdd_pin.ll(),
width=full_width,
height=drc["minwidth_metal1"])
# add wl label and offset
self.add_layout_pin(text="wl[{0}]".format(row),
layer="metal1",
offset=wl_pin.ll(),
width=full_width,
height=wl_pin.height())
# increments to the next row height
offset.y += self.cell.height
def analytical_delay(self, slew, load=0):
from tech import drc
wl_wire = self.gen_wl_wire()
wl_wire.return_delay_over_wire(slew)
wl_to_cell_delay = wl_wire.return_delay_over_wire(slew)
# hypothetical delay from cell to bl end without sense amp
bl_wire = self.gen_bl_wire()
cell_load = 2 * bl_wire.return_input_cap() # we ingore the wire r
# hence just use the whole c
bl_swing = 0.1
cell_delay = self.cell.analytical_delay(wl_to_cell_delay.slew, cell_load, swing = bl_swing)
#we do not consider the delay over the wire for now
return self.return_delay(cell_delay.delay+wl_to_cell_delay.delay,
wl_to_cell_delay.slew)
def analytical_power(self, slew, load=0):
#This will be pretty bare bones as the power needs to be determined from the dynamic power
#of the word line, leakage power from the cell, and dynamic power of the bitlines as a few
#sources for power. These features are tbd.
from tech import drc
#calculate wl dynamic power, functions not implemented.
#wl_wire = self.gen_wl_wire()
#wl_to_cell_power = wl_wire.return_power_over_wire(slew)
# hypothetical delay from cell to bl end without sense amp
bl_wire = self.gen_bl_wire()
cell_load = 2 * bl_wire.return_input_cap() # we ingore the wire r
# hence just use the whole c
bl_swing = 0.1
#Calculate the bitcell power which can include leakage as well as bitline dynamic
cell_power = self.cell.analytical_power(slew, cell_load, swing = bl_swing)
#we do not consider the delay over the wire for now
return cell_power
def gen_wl_wire(self):
wl_wire = self.generate_rc_net(int(self.column_size), self.width, drc["minwidth_metal1"])
wl_wire.wire_c = 2*spice["min_tx_gate_c"] + wl_wire.wire_c # 2 access tx gate per cell
return wl_wire
def gen_bl_wire(self):
bl_pos = 0
bl_wire = self.generate_rc_net(int(self.row_size-bl_pos), self.height, drc["minwidth_metal1"])
bl_wire.wire_c =spice["min_tx_drain_c"] + bl_wire.wire_c # 1 access tx d/s per cell
return bl_wire
def output_load(self, bl_pos=0):
bl_wire = self.gen_bl_wire()
return bl_wire.wire_c # sense amp only need to charge small portion of the bl
# set as one segment for now
def input_load(self):
wl_wire = self.gen_wl_wire()
return wl_wire.return_input_cap()
+690
View File
@@ -0,0 +1,690 @@
from math import log
import design
from tech import drc, parameter
import debug
import contact
from pinv import pinv
from pnand2 import pnand2
from pnand3 import pnand3
from pnor2 import pnor2
import math
from vector import vector
from globals import OPTS
class control_logic(design.design):
"""
Dynamically generated Control logic for the total SRAM circuit.
"""
def __init__(self, num_rows):
""" Constructor """
design.design.__init__(self, "control_logic")
debug.info(1, "Creating {}".format(self.name))
self.num_rows = num_rows
self.create_layout()
self.DRC_LVS()
def create_layout(self):
""" Create layout and route between modules """
self.create_modules()
self.setup_layout_offsets()
self.add_modules()
self.add_routing()
def create_modules(self):
""" add all the required modules """
input_lst =["csb","web","oeb","clk"]
output_lst = ["s_en", "w_en", "tri_en", "tri_en_bar", "clk_bar", "clk_buf"]
rails = ["vdd", "gnd"]
for pin in input_lst + output_lst + rails:
self.add_pin(pin)
self.nand2 = pnand2()
self.add_mod(self.nand2)
self.nand3 = pnand3()
self.add_mod(self.nand3)
self.nor2 = pnor2()
self.add_mod(self.nor2)
# Special gates: inverters for buffering
self.inv = self.inv1 = pinv(1)
self.add_mod(self.inv1)
self.inv2 = pinv(2)
self.add_mod(self.inv2)
self.inv4 = pinv(4)
self.add_mod(self.inv4)
self.inv8 = pinv(8)
self.add_mod(self.inv8)
self.inv16 = pinv(16)
self.add_mod(self.inv16)
c = reload(__import__(OPTS.ms_flop_array))
ms_flop_array = getattr(c, OPTS.ms_flop_array)
self.msf_control = ms_flop_array(name="msf_control",
columns=3,
word_size=3)
self.add_mod(self.msf_control)
c = reload(__import__(OPTS.replica_bitline))
replica_bitline = getattr(c, OPTS.replica_bitline)
# FIXME: These should be tuned according to the size!
FO4_stages = 8
bitcell_loads = int(math.ceil(self.num_rows / 10.0))
self.replica_bitline = replica_bitline(FO4_stages, bitcell_loads)
self.add_mod(self.replica_bitline)
def setup_layout_offsets(self):
""" Setup layout offsets, determine the size of the busses etc """
# These aren't for instantiating, but we use them to get the dimensions
self.poly_contact_offset = vector(0.5*contact.poly.width,0.5*contact.poly.height)
# M1/M2 routing pitch is based on contacted pitch
self.m1_pitch = max(contact.m1m2.width,contact.m1m2.height) + max(drc["metal1_to_metal1"],drc["metal2_to_metal2"])
self.m2_pitch = max(contact.m2m3.width,contact.m2m3.height) + max(drc["metal2_to_metal2"],drc["metal3_to_metal3"])
# Have the cell gap leave enough room to route an M2 wire.
# Some cells may have pwell/nwell spacing problems too when the wells are different heights.
self.cell_gap = max(self.m2_pitch,drc["pwell_to_nwell"])
# First RAIL Parameters: gnd, oe, oebar, cs, we, clk_buf, clk_bar
self.rail_1_start_x = 0
self.num_rails_1 = 8
self.rail_1_names = ["clk_buf", "gnd", "oe_bar", "cs", "we", "vdd", "oe", "clk_bar"]
self.overall_rail_1_gap = (self.num_rails_1 + 2) * self.m2_pitch
self.rail_1_x_offsets = {}
# GAP between main control and replica bitline
self.replica_bitline_gap = 2*self.m2_pitch
def add_modules(self):
""" Place all the modules """
self.add_control_flops()
self.add_clk_buffer(0)
self.add_1st_row(0)
self.add_2nd_row(self.inv1.height)
self.add_3rd_row(2*self.inv1.height)
self.add_control_routing()
self.add_rbl(0)
self.add_layout_pins()
self.add_lvs_correspondence_points()
self.height = max(self.replica_bitline.width, 3 * self.inv1.height, self.msf_offset.y)
self.width = self.replica_bitline_offset.x + self.replica_bitline.height
def add_routing(self):
""" Routing between modules """
self.add_clk_routing()
self.add_trien_routing()
self.add_rblk_routing()
self.add_wen_routing()
self.add_sen_routing()
self.add_output_routing()
self.add_supply_routing()
def add_control_flops(self):
""" Add the control signal flops for OEb, WEb, CSb. """
self.msf_offset = vector(0, self.inv.height+self.msf_control.width+2*self.m2_pitch)
self.msf_inst=self.add_inst(name="msf_control",
mod=self.msf_control,
offset=self.msf_offset,
rotate=270)
# don't change this order. This pins are meant for internal connection of msf array inside the control logic.
# These pins are connecting the msf_array inside of control_logic.
temp = ["oeb", "csb", "web",
"oe_bar", "oe",
"cs_bar", "cs",
"we_bar", "we",
"clk_buf", "vdd", "gnd"]
self.connect_inst(temp)
def add_rbl(self,y_off):
""" Add the replica bitline """
# Add to the right of the control rows and routing channel
rows_end_x = max (self.row_1_end_x, self.row_2_end_x, self.row_3_end_x)
self.replica_bitline_offset = vector(rows_end_x , y_off)
self.rbl=self.add_inst(name="replica_bitline",
mod=self.replica_bitline,
offset=self.replica_bitline_offset,
mirror="MX",
rotate=90)
self.connect_inst(["rblk", "pre_s_en", "vdd", "gnd"])
def add_layout_pins(self):
""" Add the input/output layout pins. """
# Top to bottom: CS WE OE signal groups
pin_set = ["oeb","csb","web"]
for (i,pin_name) in zip(range(3),pin_set):
subpin_name="din[{}]".format(i)
pins=self.msf_inst.get_pins(subpin_name)
for pin in pins:
if pin.layer=="metal3":
self.add_layout_pin(text=pin_name,
layer="metal3",
offset=pin.ll(),
width=pin.width(),
height=pin.height())
pin=self.clk_inv1.get_pin("A")
self.add_layout_pin(text="clk",
layer="metal1",
offset=pin.ll().scale(0,1),
width=pin.rx(),
height=pin.height())
pin=self.clk_inv1.get_pin("gnd")
self.add_layout_pin(text="gnd",
layer="metal1",
offset=pin.ll(),
width=self.width)
pin=self.clk_inv1.get_pin("vdd")
self.add_layout_pin(text="vdd",
layer="metal1",
offset=pin.ll(),
width=self.width)
def add_clk_buffer(self,y_off):
""" Add the multistage clock buffer below the control flops """
# 4 stage clock buffer
self.clk_inv1_offset = vector(0, y_off)
self.clk_inv1=self.add_inst(name="inv_clk1_bar",
mod=self.inv2,
offset=self.clk_inv1_offset)
self.connect_inst(["clk", "clk1_bar", "vdd", "gnd"])
self.clk_inv2_offset = self.clk_inv1_offset + vector(self.inv2.width,0)
self.clk_inv2=self.add_inst(name="inv_clk2",
mod=self.inv4,
offset=self.clk_inv2_offset)
self.connect_inst(["clk1_bar", "clk2", "vdd", "gnd"])
self.clk_bar_offset = self.clk_inv2_offset + vector(self.inv4.width,0)
self.clk_bar=self.add_inst(name="inv_clk_bar",
mod=self.inv8,
offset=self.clk_bar_offset)
self.connect_inst(["clk2", "clk_bar", "vdd", "gnd"])
self.clk_buf_offset = self.clk_bar_offset + vector(self.inv8.width,0)
self.clk_buf=self.add_inst(name="inv_clk_buf",
mod=self.inv16,
offset=self.clk_buf_offset)
self.connect_inst(["clk_bar", "clk_buf", "vdd", "gnd"])
# Connect between the inverters
self.add_path("metal1", [self.clk_inv1.get_pin("Z").center(),
self.clk_inv2.get_pin("A").center()])
self.add_path("metal1", [self.clk_inv2.get_pin("Z").center(),
self.clk_bar.get_pin("A").center()])
self.add_path("metal1", [self.clk_bar.get_pin("Z").center(),
self.clk_buf.get_pin("A").center()])
# This is the first rail offset
self.rail_1_start_x = max(self.msf_offset.x + self.msf_control.height,self.clk_buf_offset.x+self.inv16.width) + self.m2_pitch
def add_1st_row(self,y_off):
x_off = self.rail_1_start_x + self.overall_rail_1_gap
# input: OE, clk_bar,CS output: rblk_bar
self.rblk_bar_offset = vector(x_off, y_off)
self.rblk_bar=self.add_inst(name="nand3_rblk_bar",
mod=self.nand3,
offset=self.rblk_bar_offset)
self.connect_inst(["clk_bar", "oe", "cs", "rblk_bar", "vdd", "gnd"])
x_off += self.nand3.width
# input: rblk_bar, output: rblk
self.rblk_offset = vector(x_off, y_off)
self.rblk=self.add_inst(name="inv_rblk",
mod=self.inv1,
offset=self.rblk_offset)
self.connect_inst(["rblk_bar", "rblk", "vdd", "gnd"])
#x_off += self.inv1.width
self.row_1_end_x = x_off
def add_2nd_row(self, y_off):
# start after first rails
x_off = self.rail_1_start_x + self.overall_rail_1_gap
y_off += self.inv1.height
# input: clk_buf, OE_bar output: tri_en
self.tri_en_offset = vector(x_off, y_off)
self.tri_en=self.add_inst(name="nor2_tri_en",
mod=self.nor2,
offset=self.tri_en_offset,
mirror="MX")
self.connect_inst(["clk_buf", "oe_bar", "tri_en", "vdd", "gnd"])
x_off += self.nor2.width + self.cell_gap
# input: OE, clk_bar output: tri_en_bar
self.tri_en_bar_offset = vector(x_off,y_off)
self.tri_en_bar=self.add_inst(name="nand2_tri_en",
mod=self.nand2,
offset=self.tri_en_bar_offset,
mirror="MX")
self.connect_inst(["clk_bar", "oe", "tri_en_bar", "vdd", "gnd"])
x_off += self.nand2.width
x_off += self.inv1.width + self.cell_gap
# BUFFER INVERTERS FOR S_EN
# input: input: pre_s_en_bar, output: s_en
self.s_en_offset = vector(x_off, y_off)
self.s_en=self.add_inst(name="inv_s_en",
mod=self.inv1,
offset=self.s_en_offset,
mirror="XY")
self.connect_inst(["pre_s_en_bar", "s_en", "vdd", "gnd"])
x_off += self.inv1.width
# input: pre_s_en, output: pre_s_en_bar
self.pre_s_en_bar_offset = vector(x_off, y_off)
self.pre_s_en_bar=self.add_inst(name="inv_pre_s_en_bar",
mod=self.inv1,
offset=self.pre_s_en_bar_offset,
mirror="XY")
self.connect_inst(["pre_s_en", "pre_s_en_bar", "vdd", "gnd"])
#x_off += self.inv1.width
self.row_2_end_x = x_off
def add_3rd_row(self, y_off):
# start after first rails
x_off = self.rail_1_start_x + self.overall_rail_1_gap
# This prevents some M2 outputs from overlapping (hack)
x_off += self.inv1.width
# input: WE, clk_bar, CS output: w_en_bar
self.w_en_bar_offset = vector(x_off, y_off)
self.w_en_bar=self.add_inst(name="nand3_w_en_bar",
mod=self.nand3,
offset=self.w_en_bar_offset)
self.connect_inst(["clk_bar", "cs", "we", "w_en_bar", "vdd", "gnd"])
x_off += self.nand3.width
# input: w_en_bar, output: pre_w_en
self.pre_w_en_offset = vector(x_off, y_off)
self.pre_w_en=self.add_inst(name="inv_pre_w_en",
mod=self.inv1,
offset=self.pre_w_en_offset)
self.connect_inst(["w_en_bar", "pre_w_en", "vdd", "gnd"])
x_off += self.inv1.width
# BUFFER INVERTERS FOR W_EN
# FIXME: Can we remove these two invs and size the previous one?
self.pre_w_en_bar_offset = vector(x_off, y_off)
self.pre_w_en_bar=self.add_inst(name="inv_pre_w_en_bar",
mod=self.inv1,
offset=self.pre_w_en_bar_offset)
self.connect_inst(["pre_w_en", "pre_w_en_bar", "vdd", "gnd"])
x_off += self.inv1.width
self.w_en_offset = vector(x_off, y_off)
self.w_en=self.add_inst(name="inv_w_en2",
mod=self.inv1,
offset=self.w_en_offset)
self.connect_inst(["pre_w_en_bar", "w_en", "vdd", "gnd"])
#x_off += self.inv1.width
self.row_3_end_x = x_off
def add_control_routing(self):
""" Route the vertical rails for internal control signals """
control_rail_height = max(3 * self.inv1.height, self.msf_offset.y)
for i in range(self.num_rails_1):
offset = vector(self.rail_1_start_x + (i+1) * self.m2_pitch,0)
if self.rail_1_names[i] in ["clk_buf", "clk_bar", "vdd", "gnd"]:
self.add_layout_pin(text=self.rail_1_names[i],
layer="metal2",
offset=offset,
width=drc["minwidth_metal2"],
height=control_rail_height)
else:
# just for LVS correspondence...
self.add_label_pin(text=self.rail_1_names[i],
layer="metal2",
offset=offset,
width=drc["minwidth_metal2"],
height=control_rail_height)
self.rail_1_x_offsets[self.rail_1_names[i]]=offset.x + 0.5*drc["minwidth_metal2"] # center offset
# pins are in order ["oeb","csb","web"] # 0 1 2
self.connect_rail_from_left_m2m3(self.msf_inst,"dout_bar[0]","oe")
self.connect_rail_from_left_m2m3(self.msf_inst,"dout[0]","oe_bar")
self.connect_rail_from_left_m2m3(self.msf_inst,"dout_bar[1]","cs")
self.connect_rail_from_left_m2m3(self.msf_inst,"dout_bar[2]","we")
# Connect the gnd and vdd of the control
gnd_pins = self.msf_inst.get_pins("gnd")
for p in gnd_pins:
if p.layer != "metal2":
continue
gnd_pin = p.rc()
gnd_rail_position = vector(self.rail_1_x_offsets["gnd"], gnd_pin.y)
self.add_wire(("metal3","via2","metal2"),[gnd_pin, gnd_rail_position])
self.add_via_center(layers=("metal2","via2","metal3"),
offset=gnd_pin,
rotate=90)
self.add_via_center(layers=("metal2","via2","metal3"),
offset=gnd_rail_position,
rotate=90)
vdd_pins = self.msf_inst.get_pins("vdd")
for p in vdd_pins:
if p.layer != "metal1":
continue
clk_vdd_position = vector(p.bc().x,self.clk_buf.get_pin("vdd").uy())
self.add_path("metal1",[p.bc(),clk_vdd_position])
def add_rblk_routing(self):
""" Connect the logic for the rblk generation """
self.connect_rail_from_right(self.rblk_bar,"A","clk_bar")
self.connect_rail_from_right(self.rblk_bar,"B","oe")
self.connect_rail_from_right(self.rblk_bar,"C","cs")
# Connect the NAND3 output to the inverter
# The pins are assumed to extend all the way to the cell edge
rblk_bar_pin = self.rblk_bar.get_pin("Z").center()
inv_in_pin = self.rblk.get_pin("A").center()
mid1 = vector(inv_in_pin.x,rblk_bar_pin.y)
self.add_path("metal1",[rblk_bar_pin,mid1,inv_in_pin])
# Connect the output to the RBL
rblk_pin = self.rblk.get_pin("Z").center()
rbl_in_pin = self.rbl.get_pin("en").center()
mid1 = vector(rblk_pin.x,rbl_in_pin.y)
self.add_path("metal1",[rblk_pin,mid1,rbl_in_pin])
def connect_rail_from_right(self,inst, pin, rail):
""" Helper routine to connect an unrotated/mirrored oriented instance to the rails """
in_pos = inst.get_pin(pin).center()
rail_pos = vector(self.rail_1_x_offsets[rail], in_pos.y)
self.add_wire(("metal1","via1","metal2"),[in_pos, rail_pos])
self.add_via_center(layers=("metal1","via1","metal2"),
offset=rail_pos,
rotate=90)
def connect_rail_from_right_m2m3(self,inst, pin, rail):
""" Helper routine to connect an unrotated/mirrored oriented instance to the rails """
in_pos = inst.get_pin(pin).center() - vector(contact.m1m2.height,0)
rail_pos = vector(self.rail_1_x_offsets[rail], in_pos.y)
self.add_wire(("metal3","via2","metal2"),[in_pos, rail_pos])
# Bring it up to M2 for M2/M3 routing
self.add_via_center(layers=("metal1","via1","metal2"),
offset=in_pos,
rotate=90)
self.add_via_center(layers=("metal2","via2","metal3"),
offset=in_pos,
rotate=90)
self.add_via_center(layers=("metal2","via2","metal3"),
offset=rail_pos,
rotate=90)
def connect_rail_from_left(self,inst, pin, rail):
""" Helper routine to connect an unrotated/mirrored oriented instance to the rails """
in_pos = inst.get_pin(pin).rc()
rail_pos = vector(self.rail_1_x_offsets[rail], in_pos.y)
self.add_wire(("metal1","via1","metal2"),[in_pos, rail_pos])
self.add_via_center(layers=("metal1","via1","metal2"),
offset=in_pos,
rotate=90)
self.add_via_center(layers=("metal2","via2","metal3"),
offset=rail_pos,
rotate=90)
def connect_rail_from_left_m2m3(self,inst, pin, rail):
""" Helper routine to connect an unrotated/mirrored oriented instance to the rails """
in_pos = inst.get_pin(pin).rc()
rail_pos = vector(self.rail_1_x_offsets[rail], in_pos.y)
self.add_wire(("metal3","via2","metal2"),[in_pos, rail_pos])
self.add_via_center(layers=("metal2","via2","metal3"),
offset=in_pos,
rotate=90)
self.add_via_center(layers=("metal2","via2","metal3"),
offset=rail_pos,
rotate=90)
def add_wen_routing(self):
self.connect_rail_from_right(self.w_en_bar,"A","clk_bar")
self.connect_rail_from_right(self.w_en_bar,"B","cs")
self.connect_rail_from_right(self.w_en_bar,"C","we")
# Connect the NAND3 output to the inverter
# The pins are assumed to extend all the way to the cell edge
w_en_bar_pin = self.w_en_bar.get_pin("Z").center()
inv_in_pin = self.pre_w_en.get_pin("A").center()
mid1 = vector(inv_in_pin.x,w_en_bar_pin.y)
self.add_path("metal1",[w_en_bar_pin,mid1,inv_in_pin])
self.add_path("metal1",[self.pre_w_en.get_pin("Z").center(), self.pre_w_en_bar.get_pin("A").center()])
self.add_path("metal1",[self.pre_w_en_bar.get_pin("Z").center(), self.w_en.get_pin("A").center()])
def add_trien_routing(self):
self.connect_rail_from_right(self.tri_en,"A","clk_buf")
self.connect_rail_from_right(self.tri_en,"B","oe_bar")
self.connect_rail_from_right_m2m3(self.tri_en_bar,"A","clk_bar")
self.connect_rail_from_right_m2m3(self.tri_en_bar,"B","oe")
def add_sen_routing(self):
rbl_out_pos = self.rbl.get_pin("out").ul()
in_pos = self.pre_s_en_bar.get_pin("A").rc()
mid1 = vector(rbl_out_pos.x,in_pos.y)
self.add_path("metal1",[rbl_out_pos,mid1,in_pos])
#s_en_pos = self.s_en.get_pin("Z").lc()
self.add_path("metal1",[self.pre_s_en_bar.get_pin("Z").center(), self.s_en.get_pin("A").center()])
def add_clk_routing(self):
""" Route the clk and clk_bar signal internally """
# clk_buf
clk_buf_pos = self.clk_buf.get_pin("Z").rc()
clk_buf_rail_position = vector(self.rail_1_x_offsets["clk_buf"], clk_buf_pos.y)
self.add_wire(("metal1","via1","metal2"),[clk_buf_pos, clk_buf_rail_position])
self.add_via_center(layers=("metal1","via1","metal2"),
offset=clk_buf_rail_position,
rotate=90)
# clk_bar, routes over the clock buffer vdd rail
clk_pin = self.clk_bar.get_pin("Z")
vdd_pin = self.clk_bar.get_pin("vdd")
# move the output pin up to metal2
self.add_via_center(layers=("metal1","via1","metal2"),
offset=clk_pin.rc(),
rotate=90)
# route to a position over the supply rail
in_pos = vector(clk_pin.rx(), vdd_pin.cy())
self.add_path("metal2",[clk_pin.rc(), in_pos])
# connect that position to the control bus
rail_pos = vector(self.rail_1_x_offsets["clk_bar"], in_pos.y)
self.add_wire(("metal3","via2","metal2"),[in_pos, rail_pos])
self.add_via_center(layers=("metal2","via2","metal3"),
offset=in_pos,
rotate=90)
self.add_via_center(layers=("metal2","via2","metal3"),
offset=rail_pos,
rotate=90)
# clk_buf to msf control flops
msf_clk_pos = self.msf_inst.get_pin("clk").bc()
mid1 = msf_clk_pos - vector(0,self.m2_pitch)
clk_buf_rail_position = vector(self.rail_1_x_offsets["clk_buf"], mid1.y)
# route on M2 to allow vdd connection
self.add_wire(("metal2","via1","metal1"),[msf_clk_pos, mid1, clk_buf_rail_position])
def connect_right_pin_to_output_pin(self, inst, pin_name, out_name):
""" Create an output pin on the bottom side from the pin of a given instance. """
out_pin = inst.get_pin(pin_name)
# shift it to the right side of the cell
right_pos=out_pin.center() + vector(inst.rx()-out_pin.cx(),0)
self.add_path("metal1",[out_pin.center(), right_pos])
self.add_via_center(layers=("metal1","via1","metal2"),
offset=right_pos)
self.add_layout_pin_center_segment(text=out_name,
layer="metal2",
start=right_pos.scale(1,0),
end=right_pos)
def connect_left_pin_to_output_pin(self, inst, pin_name, out_name):
""" Create an output pin on the bottom side from the pin of a given instance. """
out_pin = inst.get_pin(pin_name)
# shift it to the right side of the cell
left_pos=out_pin.center() - vector(out_pin.cx()-inst.lx(),0)
self.add_path("metal1",[out_pin.center(), left_pos])
self.add_via_center(layers=("metal1","via1","metal2"),
offset=left_pos)
self.add_layout_pin_center_segment(text=out_name,
layer="metal2",
start=left_pos.scale(1,0),
end=left_pos)
def add_output_routing(self):
""" Output pin routing """
self.connect_right_pin_to_output_pin(self.tri_en, "Z", "tri_en")
self.connect_right_pin_to_output_pin(self.tri_en_bar, "Z", "tri_en_bar")
self.connect_right_pin_to_output_pin(self.w_en, "Z", "w_en")
self.connect_left_pin_to_output_pin(self.s_en, "Z", "s_en")
def add_supply_routing(self):
rows_start = self.rail_1_start_x + self.overall_rail_1_gap
rows_end = max(self.row_1_end_x,self.row_2_end_x,self.row_3_end_x)
vdd_rail_position = vector(self.rail_1_x_offsets["vdd"], 0)
well_width = drc["minwidth_well"]
# M1 gnd rail from inv1 to max
start_offset = self.clk_inv1.get_pin("gnd").lc()
row1_gnd_end_offset = vector(rows_end,start_offset.y)
self.add_path("metal1",[start_offset,row1_gnd_end_offset])
rail_position = vector(self.rail_1_x_offsets["gnd"], start_offset.y)
self.add_wire(("metal1","via1","metal2"),[vector(rows_start,start_offset.y), rail_position, rail_position + vector(0,self.m2_pitch)])
# also add a well + around the rail
self.add_rect(layer="pwell",
offset=vector(rows_start,start_offset.y),
width=rows_end-rows_start,
height=well_width)
self.add_rect(layer="vtg",
offset=vector(rows_start,start_offset.y),
width=rows_end-rows_start,
height=well_width)
# M1 vdd rail from inv1 to max
start_offset = self.clk_inv1.get_pin("vdd").lc()
row1_vdd_end_offset = vector(rows_end,start_offset.y)
self.add_path("metal1",[start_offset,row1_vdd_end_offset])
rail_position = vector(self.rail_1_x_offsets["vdd"], start_offset.y)
self.add_wire(("metal1","via1","metal2"),[vector(rows_start,start_offset.y), rail_position, rail_position - vector(0,self.m2_pitch)])
# also add a well +- around the rail
self.add_rect(layer="nwell",
offset=vector(rows_start,start_offset.y)-vector(0,0.5*well_width),
width=rows_end-rows_start,
height=well_width)
self.add_rect(layer="vtg",
offset=vector(rows_start,start_offset.y)-vector(0,0.5*well_width),
width=rows_end-rows_start,
height=well_width)
# M1 gnd rail from inv1 to max
start_offset = vector(rows_start, self.tri_en.get_pin("gnd").lc().y)
row3_gnd_end_offset = vector(rows_end,start_offset.y)
self.add_path("metal1",[start_offset,row3_gnd_end_offset])
rail_position = vector(self.rail_1_x_offsets["gnd"], start_offset.y)
self.add_wire(("metal1","via1","metal2"),[vector(rows_start,start_offset.y), rail_position, rail_position - vector(0,self.m2_pitch)])
# also add a well +- around the rail
self.add_rect(layer="pwell",
offset=vector(rows_start,start_offset.y)-vector(0,0.5*well_width),
width=rows_end-rows_start,
height=well_width)
self.add_rect(layer="vtg",
offset=vector(rows_start,start_offset.y)-vector(0,0.5*well_width),
width=rows_end-rows_start,
height=well_width)
# M1 vdd rail from inv1 to max
start_offset = vector(rows_start, self.w_en_bar.get_pin("vdd").lc().y)
row3_vdd_end_offset = vector(rows_end,start_offset.y)
self.add_path("metal1",[start_offset,row3_vdd_end_offset])
rail_position = vector(self.rail_1_x_offsets["vdd"], start_offset.y)
self.add_wire(("metal1","via1","metal2"),[vector(rows_start,start_offset.y), rail_position, rail_position - vector(0,self.m2_pitch)])
# Now connect the vdd and gnd rails between the replica bitline and the control logic
(rbl_row3_gnd,rbl_row1_gnd) = self.rbl.get_pins("gnd")
(rbl_row3_vdd,rbl_row1_vdd) = self.rbl.get_pins("vdd")
self.add_path("metal1",[row1_gnd_end_offset,rbl_row1_gnd.lc()])
self.add_path("metal1",[row1_vdd_end_offset,rbl_row1_vdd.lc()])
self.add_path("metal1",[row3_gnd_end_offset,rbl_row3_gnd.lc()])
# row 3 may have a jog due to unequal row heights, so force the full overlap at the end
self.add_path("metal1",[row3_vdd_end_offset - vector(self.m1_pitch,0),row3_vdd_end_offset,rbl_row3_vdd.ul()])
# also add a well - around the rail
self.add_rect(layer="nwell",
offset=vector(rows_start,start_offset.y)-vector(0,well_width),
width=rows_end-rows_start,
height=well_width)
self.add_rect(layer="vtg",
offset=vector(rows_start,start_offset.y)-vector(0,well_width),
width=rows_end-rows_start,
height=well_width)
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.
"""
pin=self.clk_inv1.get_pin("Z")
self.add_label_pin(text="clk1_bar",
layer="metal1",
offset=pin.ll(),
height=pin.height(),
width=pin.width())
pin=self.clk_inv2.get_pin("Z")
self.add_label_pin(text="clk2",
layer="metal1",
offset=pin.ll(),
height=pin.height(),
width=pin.width())
pin=self.rbl.get_pin("out")
self.add_label_pin(text="out",
layer="metal1",
offset=pin.ll(),
height=pin.height(),
width=pin.width())
+210
View File
@@ -0,0 +1,210 @@
import debug
import design
from tech import drc
from pinv import pinv
from contact import contact
from vector import vector
from globals import OPTS
class delay_chain(design.design):
"""
Generate a delay chain with the given number of stages and fanout.
This automatically adds an extra inverter with no load on the input.
Input is a list contains the electrical effort of each stage.
"""
def __init__(self, fanout_list, name="delay_chain"):
"""init function"""
design.design.__init__(self, name)
# FIXME: input should be logic effort value
# and there should be functions to get
# area efficient inverter stage list
# number of inverters including any fanout loads.
self.fanout_list = fanout_list
self.num_inverters = 1 + sum(fanout_list)
self.num_top_half = round(self.num_inverters / 2.0)
c = reload(__import__(OPTS.bitcell))
self.mod_bitcell = getattr(c, OPTS.bitcell)
self.bitcell = self.mod_bitcell()
self.add_pins()
self.create_module()
self.route_inv()
self.add_layout_pins()
self.DRC_LVS()
def add_pins(self):
""" Add the pins of the delay chain"""
self.add_pin("in")
self.add_pin("out")
self.add_pin("vdd")
self.add_pin("gnd")
def create_module(self):
""" Add the inverter logical module """
self.create_inv_list()
self.inv = pinv(route_output=False)
self.add_mod(self.inv)
# half chain length is the width of the layout
# invs are stacked into 2 levels so input/output are close
# extra metal is for the gnd connection U
self.width = self.num_top_half * self.inv.width + 2*drc["metal1_to_metal1"] + 0.5*drc["minwidth_metal1"]
self.height = 2 * self.inv.height
self.add_inv_list()
def create_inv_list(self):
"""
Generate a list of inverters. Each inverter has a stage
number and a flag indicating if it is a dummy load. This is
the order that they will get placed too.
"""
# First stage is always 0 and is not a dummy load
self.inv_list=[[0,False]]
for stage_num,fanout_size in zip(range(len(self.fanout_list)),self.fanout_list):
for i in range(fanout_size-1):
# Add the dummy loads
self.inv_list.append([stage_num+1, True])
# Add the gate to drive the next stage
self.inv_list.append([stage_num+1, False])
def add_inv_list(self):
""" Add the inverters and connect them based on the stage list """
dummy_load_counter = 1
self.inv_inst_list = []
for i in range(self.num_inverters):
# First place the gates
if i < self.num_top_half:
# add top level that is upside down
inv_offset = vector(i * self.inv.width, 2 * self.inv.height)
inv_mirror="MX"
else:
# add bottom level from right to left
inv_offset = vector((self.num_inverters - i) * self.inv.width, 0)
inv_mirror="MY"
cur_inv=self.add_inst(name="dinv{}".format(i),
mod=self.inv,
offset=inv_offset,
mirror=inv_mirror)
# keep track of the inverter instances so we can use them to get the pins
self.inv_inst_list.append(cur_inv)
# Second connect them logically
cur_stage = self.inv_list[i][0]
next_stage = self.inv_list[i][0]+1
if i == 0:
input = "in"
else:
input = "s{}".format(cur_stage)
if i == self.num_inverters-1:
output = "out"
else:
output = "s{}".format(next_stage)
# if the gate is a dummy load don't connect the output
# else reset the counter
if self.inv_list[i][1]:
output = output+"n{0}".format(dummy_load_counter)
dummy_load_counter += 1
else:
dummy_load_counter = 1
self.connect_inst(args=[input, output, "vdd", "gnd"])
if i != 0:
self.add_via_center(layers=("metal1", "via1", "metal2"),
offset=cur_inv.get_pin("A").center())
def add_route(self, pin1, pin2):
""" This guarantees that we route from the top to bottom row correctly. """
pin1_pos = pin1.center()
pin2_pos = pin2.center()
if pin1_pos.y == pin2_pos.y:
self.add_path("metal2", [pin1_pos, pin2_pos])
else:
mid_point = vector(pin2_pos.x, 0.5*(pin1_pos.y+pin2_pos.y))
# Written this way to guarantee it goes right first if we are switching rows
self.add_path("metal2", [pin1_pos, vector(pin1_pos.x,mid_point.y), mid_point, vector(mid_point.x,pin2_pos.y), pin2_pos])
def route_inv(self):
""" Add metal routing for each of the fanout stages """
start_inv = end_inv = 0
for fanout in self.fanout_list:
# end inv number depends on the fan out number
end_inv = start_inv + fanout
start_inv_inst = self.inv_inst_list[start_inv]
self.add_via_center(layers=("metal1", "via1", "metal2"),
offset=start_inv_inst.get_pin("Z").center()),
# route from output to first load
start_inv_pin = start_inv_inst.get_pin("Z")
load_inst = self.inv_inst_list[start_inv+1]
load_pin = load_inst.get_pin("A")
self.add_route(start_inv_pin, load_pin)
next_inv = start_inv+2
while next_inv <= end_inv:
prev_load_inst = self.inv_inst_list[next_inv-1]
prev_load_pin = prev_load_inst.get_pin("A")
load_inst = self.inv_inst_list[next_inv]
load_pin = load_inst.get_pin("A")
self.add_route(prev_load_pin, load_pin)
next_inv += 1
# set the start of next one after current end
start_inv = end_inv
def add_layout_pins(self):
""" Add vdd and gnd rails and the input/output. Connect the gnd rails internally on
the top end with no input/output to obstruct. """
vdd_pin = self.inv.get_pin("vdd")
gnd_pin = self.inv.get_pin("gnd")
for i in range(3):
(offset,y_dir)=self.get_gate_offset(0, self.inv.height, i)
rail_width = self.num_top_half * self.inv.width
if i % 2:
self.add_layout_pin(text="vdd",
layer="metal1",
offset=offset + vdd_pin.ll().scale(1,y_dir),
width=rail_width,
height=drc["minwidth_metal1"])
else:
self.add_layout_pin(text="gnd",
layer="metal1",
offset=offset + gnd_pin.ll().scale(1,y_dir),
width=rail_width,
height=drc["minwidth_metal1"])
# Use the right most parts of the gnd rails and add a U connector
# We still have the two gnd pins, but it is an either-or connect
gnd_pins = self.get_pins("gnd")
gnd_start = gnd_pins[0].rc()
gnd_mid1 = gnd_start + vector(2*drc["metal1_to_metal1"],0)
gnd_end = gnd_pins[1].rc()
gnd_mid2 = gnd_end + vector(2*drc["metal1_to_metal1"],0)
#self.add_wire(("metal1","via1","metal2"), [gnd_start, gnd_mid1, gnd_mid2, gnd_end])
self.add_path("metal1", [gnd_start, gnd_mid1, gnd_mid2, gnd_end])
# input is A pin of first inverter
a_pin = self.inv_inst_list[0].get_pin("A")
self.add_layout_pin(text="in",
layer="metal1",
offset=a_pin.ll(),
width=a_pin.width(),
height=a_pin.height())
# output is Z pin of last inverter
z_pin = self.inv_inst_list[-1].get_pin("Z")
self.add_layout_pin(text="out",
layer="metal1",
offset=z_pin.ll().scale(0,1),
width=z_pin.lx())
+518
View File
@@ -0,0 +1,518 @@
from tech import drc
import debug
import design
from math import log
from math import sqrt
import math
import contact
from pnand2 import pnand2
from pnand3 import pnand3
from pinv import pinv
from hierarchical_predecode2x4 import hierarchical_predecode2x4 as pre2x4
from hierarchical_predecode3x8 import hierarchical_predecode3x8 as pre3x8
from vector import vector
from globals import OPTS
class hierarchical_decoder(design.design):
"""
Dynamically generated hierarchical decoder.
"""
def __init__(self, rows):
design.design.__init__(self, "hierarchical_decoder_{0}rows".format(rows))
c = reload(__import__(OPTS.bitcell))
self.mod_bitcell = getattr(c, OPTS.bitcell)
self.bitcell_height = self.mod_bitcell.height
self.pre2x4_inst = []
self.pre3x8_inst = []
self.rows = rows
self.num_inputs = int(math.log(self.rows, 2))
(self.no_of_pre2x4,self.no_of_pre3x8)=self.determine_predecodes(self.num_inputs)
self.create_layout()
self.DRC_LVS()
def create_layout(self):
self.add_modules()
self.setup_layout_constants()
self.add_pins()
self.create_pre_decoder()
self.create_row_decoder()
self.create_vertical_rail()
self.route_vdd_gnd()
def add_modules(self):
self.inv = pinv()
self.add_mod(self.inv)
self.nand2 = pnand2()
self.add_mod(self.nand2)
self.nand3 = pnand3()
self.add_mod(self.nand3)
# CREATION OF PRE-DECODER
self.pre2_4 = pre2x4()
self.add_mod(self.pre2_4)
self.pre3_8 = pre3x8()
self.add_mod(self.pre3_8)
def determine_predecodes(self,num_inputs):
"""Determines the number of 2:4 pre-decoder and 3:8 pre-decoder
needed based on the number of inputs"""
if (num_inputs == 2):
return (1,0)
elif (num_inputs == 3):
return(0,1)
elif (num_inputs == 4):
return(2,0)
elif (num_inputs == 5):
return(1,1)
elif (num_inputs == 6):
return(3,0)
elif (num_inputs == 7):
return(2,1)
elif (num_inputs == 8):
return(1,2)
elif (num_inputs == 9):
return(0,3)
else:
debug.error("Invalid number of inputs for hierarchical decoder",-1)
def setup_layout_constants(self):
# Vertical metal rail gap definition
self.metal2_extend_contact = (contact.m1m2.second_layer_height - contact.m1m2.contact_width) / 2
self.metal2_spacing = self.metal2_extend_contact + self.m2_space
self.metal2_pitch = self.metal2_spacing + self.m2_width
self.via_shift = (contact.m1m2.second_layer_width - contact.m1m2.first_layer_width) / 2
self.predec_groups = [] # This array is a 2D array.
# Distributing vertical rails to different groups. One group belongs to one pre-decoder.
# For example, for two 2:4 pre-decoder and one 3:8 pre-decoder, we will
# have total 16 output lines out of these 3 pre-decoders and they will
# be distributed as [ [0,1,2,3] ,[4,5,6,7], [8,9,10,11,12,13,14,15] ]
# in self.predec_groups
index = 0
for i in range(self.no_of_pre2x4):
lines = []
for j in range(4):
lines.append(index)
index = index + 1
self.predec_groups.append(lines)
for i in range(self.no_of_pre3x8):
lines = []
for j in range(8):
lines.append(index)
index = index + 1
self.predec_groups.append(lines)
self.calculate_dimensions()
def add_pins(self):
""" Add the module pins """
for i in range(self.num_inputs):
self.add_pin("A[{0}]".format(i))
for j in range(self.rows):
self.add_pin("decode[{0}]".format(j))
self.add_pin("vdd")
self.add_pin("gnd")
def calculate_dimensions(self):
""" Calculate the overal dimensions of the hierarchical decoder """
# If we have 4 or fewer rows, the predecoder is the decoder itself
if self.num_inputs>=4:
self.total_number_of_predecoder_outputs = 4*self.no_of_pre2x4 + 8*self.no_of_pre3x8
else:
self.total_number_of_predecoder_outputs = 0
debug.error("Not enough rows for a hierarchical decoder. Non-hierarchical not supported yet.",-1)
# Calculates height and width of pre-decoder,
if(self.no_of_pre3x8 > 0):
self.predecoder_width = self.pre3_8.width
else:
self.predecoder_width = self.pre2_4.width
self.predecoder_height = self.pre2_4.height*self.no_of_pre2x4 + self.pre3_8.height*self.no_of_pre3x8
# Calculates height and width of row-decoder
if (self.num_inputs == 4 or self.num_inputs == 5):
nand_width = self.nand2.width
else:
nand_width = self.nand3.width
self.routing_width = self.metal2_pitch*self.total_number_of_predecoder_outputs
self.row_decoder_width = nand_width + self.routing_width + self.inv.width
self.row_decoder_height = self.inv.height * self.rows
# Calculates height and width of hierarchical decoder
self.height = self.predecoder_height + self.row_decoder_height
self.width = self.predecoder_width + self.routing_width
def create_pre_decoder(self):
""" Creates pre-decoder and places labels input address [A] """
for i in range(self.no_of_pre2x4):
self.add_pre2x4(i)
for i in range(self.no_of_pre3x8):
self.add_pre3x8(i)
def add_pre2x4(self,num):
""" Add a 2x4 predecoder """
if (self.num_inputs == 2):
base = vector(self.routing_width,0)
mirror = "RO"
index_off1 = index_off2 = 0
else:
base= vector(self.routing_width+self.pre2_4.width, num * self.pre2_4.height)
mirror = "MY"
index_off1 = num * 2
index_off2 = num * 4
pins = []
for input_index in range(2):
pins.append("A[{0}]".format(input_index + index_off1))
for output_index in range(4):
pins.append("out[{0}]".format(output_index + index_off2))
pins.extend(["vdd", "gnd"])
self.pre2x4_inst.append(self.add_inst(name="pre[{0}]".format(num),
mod=self.pre2_4,
offset=base,
mirror=mirror))
self.connect_inst(pins)
self.add_pre2x4_pins(num)
def add_pre2x4_pins(self,num):
""" Add the input pins to the 2x4 predecoder """
for i in range(2):
pin = self.pre2x4_inst[num].get_pin("in[{}]".format(i))
pin_offset = pin.ll()
pin = self.pre2_4.get_pin("in[{}]".format(i))
self.add_layout_pin(text="A[{0}]".format(i + 2*num ),
layer="metal2",
offset=pin_offset,
width=pin.width(),
height=pin.height())
def add_pre3x8(self,num):
""" Add 3x8 numbered predecoder """
if (self.num_inputs == 3):
offset = vector(self.routing_width,0)
mirror ="R0"
else:
height = self.no_of_pre2x4*self.pre2_4.height + num*self.pre3_8.height
offset = vector(self.routing_width+self.pre3_8.width, height)
mirror="MY"
# If we had 2x4 predecodes, those are used as the lower
# decode output bits
in_index_offset = num * 3 + self.no_of_pre2x4 * 2
out_index_offset = num * 8 + self.no_of_pre2x4 * 4
pins = []
for input_index in range(3):
pins.append("A[{0}]".format(input_index + in_index_offset))
for output_index in range(8):
pins.append("out[{0}]".format(output_index + out_index_offset))
pins.extend(["vdd", "gnd"])
self.pre3x8_inst.append(self.add_inst(name="pre3x8[{0}]".format(num),
mod=self.pre3_8,
offset=offset,
mirror=mirror))
self.connect_inst(pins)
# The 3x8 predecoders will be stacked, so use yoffset
self.add_pre3x8_pins(num,offset)
def add_pre3x8_pins(self,num,offset):
""" Add the input pins to the 3x8 predecoder at the given offset """
for i in range(3):
pin = self.pre3x8_inst[num].get_pin("in[{}]".format(i))
pin_offset = pin.ll()
self.add_layout_pin(text="A[{0}]".format(i + 3*num + 2*self.no_of_pre2x4),
layer="metal2",
offset=pin_offset,
width=pin.width(),
height=pin.height())
def create_row_decoder(self):
""" Create the row-decoder by placing NAND2/NAND3 and Inverters
and add the primary decoder output pins. """
if (self.num_inputs >= 4):
self.add_decoder_nand_array()
self.add_decoder_inv_array()
self.route_decoder()
def add_decoder_nand_array(self):
""" Add a column of NAND gates for final decode """
# Row Decoder NAND GATE array for address inputs <5.
if (self.num_inputs == 4 or self.num_inputs == 5):
self.add_nand_array(nand_mod=self.nand2)
# FIXME: Can we convert this to the connect_inst with checks?
for i in range(len(self.predec_groups[0])):
for j in range(len(self.predec_groups[1])):
pins =["out[{0}]".format(i),
"out[{0}]".format(j + len(self.predec_groups[0])),
"Z[{0}]".format(len(self.predec_groups[1])*i + j),
"vdd", "gnd"]
self.connect_inst(args=pins, check=False)
# Row Decoder NAND GATE array for address inputs >5.
elif (self.num_inputs > 5):
self.add_nand_array(nand_mod=self.nand3,
correct=drc["minwidth_metal1"])
# This will not check that the inst connections match.
for i in range(len(self.predec_groups[0])):
for j in range(len(self.predec_groups[1])):
for k in range(len(self.predec_groups[2])):
Z_index = len(self.predec_groups[1])*len(self.predec_groups[2]) * i \
+ len(self.predec_groups[2])*j + k
pins = ["out[{0}]".format(i),
"out[{0}]".format(j + len(self.predec_groups[0])),
"out[{0}]".format(k + len(self.predec_groups[0]) + len(self.predec_groups[1])),
"Z[{0}]".format(Z_index),
"vdd", "gnd"]
self.connect_inst(args=pins, check=False)
def add_nand_array(self, nand_mod, correct=0):
""" Add a column of NAND gates for the decoder above the predecoders."""
self.nand_inst = []
for row in range(self.rows):
name = "DEC_NAND[{0}]".format(row)
if ((row % 2) == 0):
y_off = self.predecoder_height + nand_mod.height*row
y_dir = 1
mirror = "R0"
else:
y_off = self.predecoder_height + nand_mod.height*(row + 1)
y_dir = -1
mirror = "MX"
self.nand_inst.append(self.add_inst(name=name,
mod=nand_mod,
offset=[self.routing_width, y_off],
mirror=mirror))
def add_decoder_inv_array(self):
"""Add a column of INV gates for the decoder above the predecoders
and to the right of the NAND decoders."""
z_pin = self.inv.get_pin("Z")
if (self.num_inputs == 4 or self.num_inputs == 5):
x_off = self.routing_width + self.nand2.width
else:
x_off = self.routing_width + self.nand3.width
self.inv_inst = []
for row in range(self.rows):
name = "DEC_INV_[{0}]".format(row)
if (row % 2 == 0):
inv_row_height = self.inv.height * row
mirror = "R0"
y_dir = 1
else:
inv_row_height = self.inv.height * (row + 1)
mirror = "MX"
y_dir = -1
y_off = self.predecoder_height + inv_row_height
offset = vector(x_off,y_off)
self.inv_inst.append(self.add_inst(name=name,
mod=self.inv,
offset=offset,
mirror=mirror))
# This will not check that the inst connections match.
self.connect_inst(args=["Z[{0}]".format(row),
"decode[{0}]".format(row),
"vdd", "gnd"],
check=False)
def route_decoder(self):
""" Route the nand to inverter in the decoder and add the pins. """
for row in range(self.rows):
# route nand output to output inv input
zr_pos = self.nand_inst[row].get_pin("Z").rc()
al_pos = self.inv_inst[row].get_pin("A").lc()
# ensure the bend is in the middle
mid1_pos = vector(0.5*(zr_pos.x+al_pos.x), zr_pos.y)
mid2_pos = vector(0.5*(zr_pos.x+al_pos.x), al_pos.y)
self.add_path("metal1", [zr_pos, mid1_pos, mid2_pos, al_pos])
z_pin = self.inv_inst[row].get_pin("Z")
self.add_layout_pin(text="decode[{0}]".format(row),
layer="metal1",
offset=z_pin.ll(),
width=z_pin.width(),
height=z_pin.height())
def create_vertical_rail(self):
""" Creates vertical metal 2 rails to connect predecoder and decoder stages."""
# This is not needed for inputs <4 since they have no pre/decode stages.
if (self.num_inputs >= 4):
# Array for saving the X offsets of the vertical rails. These rail
# offsets are accessed with indices.
self.rail_x_offsets = []
for i in range(self.total_number_of_predecoder_outputs):
# The offsets go into the negative x direction
# assuming the predecodes are placed at (self.routing_width,0)
x_offset = self.metal2_pitch * i
self.rail_x_offsets.append(x_offset+0.5*self.m2_width)
self.add_rect(layer="metal2",
offset=vector(x_offset,0),
width=drc["minwidth_metal2"],
height=self.height)
self.connect_rails_to_predecodes()
self.connect_rails_to_decoder()
def connect_rails_to_predecodes(self):
""" Iterates through all of the predecodes and connects to the rails including the offsets """
for pre_num in range(self.no_of_pre2x4):
for i in range(4):
index = pre_num * 4 + i
out_name = "out[{}]".format(i)
pin = self.pre2x4_inst[pre_num].get_pin(out_name)
self.connect_rail(index, pin)
for pre_num in range(self.no_of_pre3x8):
for i in range(8):
index = pre_num * 8 + i + self.no_of_pre2x4 * 4
out_name = "out[{}]".format(i)
pin = self.pre3x8_inst[pre_num].get_pin(out_name)
self.connect_rail(index, pin)
def connect_rails_to_decoder(self):
""" Use the self.predec_groups to determine the connections to the decoder NAND gates.
Inputs of NAND2/NAND3 gates come from different groups.
For example for these groups [ [0,1,2,3] ,[4,5,6,7],
[8,9,10,11,12,13,14,15] ] the first NAND3 inputs are connected to
[0,4,8] and second NAND3 is connected to [0,4,9] ........... and the
128th NAND3 is connected to [3,7,15]
"""
row_index = 0
if (self.num_inputs == 4 or self.num_inputs == 5):
for index_A in self.predec_groups[0]:
for index_B in self.predec_groups[1]:
self.connect_rail(index_A, self.nand_inst[row_index].get_pin("A"))
self.connect_rail(index_B, self.nand_inst[row_index].get_pin("B"))
row_index = row_index + 1
elif (self.num_inputs > 5):
for index_A in self.predec_groups[0]:
for index_B in self.predec_groups[1]:
for index_C in self.predec_groups[2]:
self.connect_rail(index_A, self.nand_inst[row_index].get_pin("A"))
self.connect_rail(index_B, self.nand_inst[row_index].get_pin("B"))
self.connect_rail(index_C, self.nand_inst[row_index].get_pin("C"))
row_index = row_index + 1
def route_vdd_gnd(self):
""" Add a pin for each row of vdd/gnd which are must-connects next level up. """
for num in range(0,self.total_number_of_predecoder_outputs + self.rows):
# this will result in duplicate polygons for rails, but who cares
# use the inverter offset even though it will be the nand's too
(gate_offset, y_dir) = self.get_gate_offset(0, self.inv.height, num)
# route vdd
vdd_offset = gate_offset + self.inv.get_pin("vdd").ll().scale(1,y_dir)
self.add_layout_pin(text="vdd",
layer="metal1",
offset=vdd_offset,
width=self.width,
height=drc["minwidth_metal1"])
# route gnd
gnd_offset = gate_offset+self.inv.get_pin("gnd").ll().scale(1,y_dir)
self.add_layout_pin(text="gnd",
layer="metal1",
offset=gnd_offset,
width=self.width,
height=drc["minwidth_metal1"])
def connect_rail(self, rail_index, pin):
""" Connect the routing rail to the given metal1 pin """
rail_pos = vector(self.rail_x_offsets[rail_index],pin.lc().y)
self.add_path("metal1", [rail_pos, pin.lc()])
self.add_via_center(layers=("metal1", "via1", "metal2"),
offset=rail_pos,
rotate=90)
def analytical_delay(self, slew, load = 0.0):
# A -> out
if self.determine_predecodes(self.num_inputs)[1]==0:
pre = self.pre2_4
nand = self.nand2
else:
pre = self.pre3_8
nand = self.nand3
a_t_out_delay = pre.analytical_delay(slew=slew,load = nand.input_load())
# out -> z
out_t_z_delay = nand.analytical_delay(slew= a_t_out_delay.slew,
load = self.inv.input_load())
result = a_t_out_delay + out_t_z_delay
# Z -> decode_out
z_t_decodeout_delay = self.inv.analytical_delay(slew = out_t_z_delay.slew , load = load)
result = result + z_t_decodeout_delay
return result
def analytical_power(self, slew, load = 0.0):
# A -> out
if self.determine_predecodes(self.num_inputs)[1]==0:
pre = self.pre2_4
nand = self.nand2
else:
pre = self.pre3_8
nand = self.nand3
a_t_out_power = pre.analytical_power(slew=slew,load = nand.input_load())
out_t_z_power = nand.analytical_power(slew,
load = self.inv.input_load())
z_t_decodeout_power = self.inv.analytical_power(slew, load = load)
return a_t_out_power + out_t_z_power + z_t_decodeout_power
def input_load(self):
if self.determine_predecodes(self.num_inputs)[1]==0:
pre = self.pre2_4
else:
pre = self.pre3_8
return pre.input_load()
+292
View File
@@ -0,0 +1,292 @@
import debug
import design
import math
from tech import drc
import contact
from pinv import pinv
from vector import vector
from globals import OPTS
from pnand2 import pnand2
from pnand3 import pnand3
class hierarchical_predecode(design.design):
"""
Pre 2x4 and 3x8 decoder shared code.
"""
def __init__(self, input_number):
self.number_of_inputs = input_number
self.number_of_outputs = int(math.pow(2, self.number_of_inputs))
design.design.__init__(self, name="pre{0}x{1}".format(self.number_of_inputs,self.number_of_outputs))
c = reload(__import__(OPTS.bitcell))
self.mod_bitcell = getattr(c, OPTS.bitcell)
self.bitcell_height = self.mod_bitcell.height
def add_pins(self):
for k in range(self.number_of_inputs):
self.add_pin("in[{0}]".format(k))
for i in range(self.number_of_outputs):
self.add_pin("out[{0}]".format(i))
self.add_pin("vdd")
self.add_pin("gnd")
def create_modules(self):
""" Create the INV and NAND gate """
self.inv = pinv()
self.add_mod(self.inv)
self.create_nand(self.number_of_inputs)
self.add_mod(self.nand)
def create_nand(self,inputs):
""" Create the NAND for the predecode input stage """
if inputs==2:
self.nand = pnand2()
elif inputs==3:
self.nand = pnand3()
else:
debug.error("Invalid number of predecode inputs.",-1)
def setup_constraints(self):
# we are going to use horizontal vias, so use the via height
# use a conservative douple spacing just to get rid of annoying via DRCs
self.m2_pitch = contact.m1m2.height + 2*self.m2_space
# The rail offsets are indexed by the label
self.rails = {}
# Non inverted input rails
for rail_index in range(self.number_of_inputs):
xoffset = rail_index * self.m2_pitch + 0.5*self.m2_width
self.rails["in[{}]".format(rail_index)]=xoffset
# x offset for input inverters
self.x_off_inv_1 = self.number_of_inputs*self.m2_pitch
# Creating the right hand side metal2 rails for output connections
for rail_index in range(2 * self.number_of_inputs):
xoffset = self.x_off_inv_1 + self.inv.width + ((rail_index+1) * self.m2_pitch) + 0.5*self.m2_width
if rail_index < self.number_of_inputs:
self.rails["Abar[{}]".format(rail_index)]=xoffset
else:
self.rails["A[{}]".format(rail_index-self.number_of_inputs)]=xoffset
# x offset to NAND decoder includes the left rails, mid rails and inverters, plus an extra m2 pitch
self.x_off_nand = self.x_off_inv_1 + self.inv.width + (1 + 2*self.number_of_inputs) * self.m2_pitch
# x offset to output inverters
self.x_off_inv_2 = self.x_off_nand + self.nand.width
# Height width are computed
self.width = self.x_off_inv_2 + self.inv.width
self.height = self.number_of_outputs * self.nand.height
def create_rails(self):
""" Create all of the rails for the inputs and vdd/gnd/inputs_bar/inputs """
for label in self.rails.keys():
# these are not primary inputs, so they shouldn't have a
# label or LVS complains about different names on one net
if label.startswith("in"):
self.add_layout_pin(text=label,
layer="metal2",
offset=vector(self.rails[label] - 0.5*self.m2_width, 0),
width=self.m2_width,
height=self.height - 2*self.m2_space)
else:
self.add_rect(layer="metal2",
offset=vector(self.rails[label] - 0.5*self.m2_width, 0),
width=self.m2_width,
height=self.height - 2*self.m2_space)
def add_input_inverters(self):
""" Create the input inverters to invert input signals for the decode stage. """
self.in_inst = []
for inv_num in range(self.number_of_inputs):
name = "Xpre_inv[{0}]".format(inv_num)
if (inv_num % 2 == 0):
y_off = inv_num * (self.inv.height)
mirror = "R0"
else:
y_off = (inv_num + 1) * (self.inv.height)
mirror="MX"
offset = vector(self.x_off_inv_1, y_off)
self.in_inst.append(self.add_inst(name=name,
mod=self.inv,
offset=offset,
mirror=mirror))
self.connect_inst(["in[{0}]".format(inv_num),
"inbar[{0}]".format(inv_num),
"vdd", "gnd"])
def add_output_inverters(self):
""" Create inverters for the inverted output decode signals. """
self.inv_inst = []
for inv_num in range(self.number_of_outputs):
name = "Xpre_nand_inv[{}]".format(inv_num)
if (inv_num % 2 == 0):
y_off = inv_num * self.inv.height
mirror = "R0"
else:
y_off =(inv_num + 1)*self.inv.height
mirror = "MX"
offset = vector(self.x_off_inv_2, y_off)
self.inv_inst.append(self.add_inst(name=name,
mod=self.inv,
offset=offset,
mirror=mirror))
self.connect_inst(["Z[{}]".format(inv_num),
"out[{}]".format(inv_num),
"vdd", "gnd"])
def add_nand(self,connections):
""" Create the NAND stage for the decodes """
self.nand_inst = []
for nand_input in range(self.number_of_outputs):
inout = str(self.number_of_inputs)+"x"+str(self.number_of_outputs)
name = "Xpre{0}_nand[{1}]".format(inout,nand_input)
if (nand_input % 2 == 0):
y_off = nand_input * self.inv.height
mirror = "R0"
else:
y_off = (nand_input + 1) * self.inv.height
mirror = "MX"
offset = vector(self.x_off_nand, y_off)
self.nand_inst.append(self.add_inst(name=name,
mod=self.nand,
offset=offset,
mirror=mirror))
self.connect_inst(connections[nand_input])
def route(self):
self.route_input_inverters()
self.route_inputs_to_rails()
self.route_nand_to_rails()
self.route_output_inverters()
self.route_vdd_gnd()
def route_inputs_to_rails(self):
""" Route the uninverted inputs to the second set of rails """
for num in range(self.number_of_inputs):
# route one signal next to each vdd/gnd rail since this is
# typically where the p/n devices are and there are no
# pins in the nand gates.
y_offset = (num+self.number_of_inputs) * self.inv.height + contact.m1m2.width + self.m1_space
in_pin = "in[{}]".format(num)
a_pin = "A[{}]".format(num)
in_pos = vector(self.rails[in_pin],y_offset)
a_pos = vector(self.rails[a_pin],y_offset)
self.add_path("metal1",[in_pos, a_pos])
self.add_via_center(layers = ("metal1", "via1", "metal2"),
offset=[self.rails[in_pin], y_offset],
rotate=90)
self.add_via_center(layers = ("metal1", "via1", "metal2"),
offset=[self.rails[a_pin], y_offset],
rotate=90)
def route_output_inverters(self):
"""
Route all conections of the outputs inverters
"""
for num in range(self.number_of_outputs):
# route nand output to output inv input
zr_pos = self.nand_inst[num].get_pin("Z").rc()
al_pos = self.inv_inst[num].get_pin("A").lc()
# ensure the bend is in the middle
mid1_pos = vector(0.5*(zr_pos.x+al_pos.x), zr_pos.y)
mid2_pos = vector(0.5*(zr_pos.x+al_pos.x), al_pos.y)
self.add_path("metal1", [zr_pos, mid1_pos, mid2_pos, al_pos])
z_pos = self.inv_inst[num].get_pin("Z").rc()
self.add_layout_pin_center_segment(text="out[{}]".format(num),
layer="metal1",
start=z_pos,
end=z_pos + vector(self.inv.width - self.inv.get_pin("Z").rx(),0))
def route_input_inverters(self):
"""
Route all conections of the inputs inverters [Inputs, outputs, vdd, gnd]
"""
for inv_num in range(self.number_of_inputs):
out_pin = "Abar[{}]".format(inv_num)
in_pin = "in[{}]".format(inv_num)
#add output so that it is just below the vdd or gnd rail
# since this is where the p/n devices are and there are no
# pins in the nand gates.
y_offset = (inv_num+1) * self.inv.height - 3*self.m1_space
inv_out_pos = self.in_inst[inv_num].get_pin("Z").rc()
right_pos = inv_out_pos + vector(self.inv.width - self.inv.get_pin("Z").lx(),0)
rail_pos = vector(self.rails[out_pin],y_offset)
self.add_path("metal1", [inv_out_pos, right_pos, vector(right_pos.x, y_offset), rail_pos])
self.add_via_center(layers = ("metal1", "via1", "metal2"),
offset=rail_pos,
rotate=90)
#route input
inv_in_pos = self.in_inst[inv_num].get_pin("A").lc()
in_pos = vector(self.rails[in_pin],inv_in_pos.y)
self.add_path("metal1", [in_pos, inv_in_pos])
self.add_via_center(layers=("metal1", "via1", "metal2"),
offset=in_pos,
rotate=90)
def route_nand_to_rails(self):
# This 2D array defines the connection mapping
nand_input_line_combination = self.get_nand_input_line_combination()
for k in range(self.number_of_outputs):
# create x offset list
index_lst= nand_input_line_combination[k]
if self.number_of_inputs == 2:
gate_lst = ["A","B"]
else:
gate_lst = ["A","B","C"]
# this will connect pins A,B or A,B,C
for rail_pin,gate_pin in zip(index_lst,gate_lst):
pin_pos = self.nand_inst[k].get_pin(gate_pin).lc()
rail_pos = vector(self.rails[rail_pin], pin_pos.y)
self.add_path("metal1", [rail_pos, pin_pos])
self.add_via_center(layers=("metal1", "via1", "metal2"),
offset=rail_pos,
rotate=90)
def route_vdd_gnd(self):
""" Add a pin for each row of vdd/gnd which are must-connects next level up. """
for num in range(0,self.number_of_outputs):
# this will result in duplicate polygons for rails, but who cares
# use the inverter offset even though it will be the nand's too
(gate_offset, y_dir) = self.get_gate_offset(0, self.inv.height, num)
# route vdd
vdd_offset = self.nand_inst[num].get_pin("vdd").ll().scale(0,1)
self.add_layout_pin(text="vdd",
layer="metal1",
offset=vdd_offset,
width=self.inv_inst[num].rx())
# route gnd
gnd_offset = self.nand_inst[num].get_pin("gnd").ll().scale(0,1)
self.add_layout_pin(text="gnd",
layer="metal1",
offset=gnd_offset,
width=self.inv_inst[num].rx())
@@ -0,0 +1,71 @@
from tech import drc
import debug
import design
from vector import vector
from hierarchical_predecode import hierarchical_predecode
class hierarchical_predecode2x4(hierarchical_predecode):
"""
Pre 2x4 decoder used in hierarchical_decoder.
"""
def __init__(self):
hierarchical_predecode.__init__(self, 2)
self.add_pins()
self.create_modules()
self.setup_constraints()
self.create_layout()
self.DRC_LVS()
def create_layout(self):
""" The general organization is from left to right:
1) a set of M2 rails for input signals
2) a set of inverters to invert input signals
3) a set of M2 rails for the vdd, gnd, inverted inputs, inputs
4) a set of NAND gates for inversion
"""
self.create_rails()
self.add_input_inverters()
self.add_output_inverters()
connections =[["inbar[0]", "inbar[1]", "Z[0]", "vdd", "gnd"],
["in[0]", "inbar[1]", "Z[1]", "vdd", "gnd"],
["inbar[0]", "in[1]", "Z[2]", "vdd", "gnd"],
["in[0]", "in[1]", "Z[3]", "vdd", "gnd"]]
self.add_nand(connections)
self.route()
def get_nand_input_line_combination(self):
""" These are the decoder connections of the NAND gates to the A,B pins """
combination = [["Abar[0]", "Abar[1]"],
["A[0]", "Abar[1]"],
["Abar[0]", "A[1]"],
["A[0]", "A[1]"]]
return combination
def analytical_delay(self, slew, load = 0.0 ):
# in -> inbar
a_t_b_delay = self.inv.analytical_delay(slew=slew, load=self.nand.input_load())
# inbar -> z
b_t_z_delay = self.nand.analytical_delay(slew=a_t_b_delay.slew, load=self.inv.input_load())
# Z -> out
a_t_out_delay = self.inv.analytical_delay(slew=b_t_z_delay.slew, load=load)
return a_t_b_delay + b_t_z_delay + a_t_out_delay
def analytical_power(self, slew, load = 0.0 ):
# in -> inbar
a_t_b_power = self.inv.analytical_power(slew=slew, load=self.nand.input_load())
# inbar -> z
b_t_z_power = self.nand.analytical_power(slew, load=self.inv.input_load())
# Z -> out
a_t_out_power = self.inv.analytical_power(slew, load=load)
return a_t_b_power + b_t_z_power + a_t_out_power
def input_load(self):
return self.nand.input_load()
@@ -0,0 +1,79 @@
from tech import drc
import debug
import design
from vector import vector
from hierarchical_predecode import hierarchical_predecode
class hierarchical_predecode3x8(hierarchical_predecode):
"""
Pre 3x8 decoder used in hierarchical_decoder.
"""
def __init__(self):
hierarchical_predecode.__init__(self, 3)
self.add_pins()
self.create_modules()
self.setup_constraints()
self.create_layout()
self.DRC_LVS()
def create_layout(self):
""" The general organization is from left to right:
1) a set of M2 rails for input signals
2) a set of inverters to invert input signals
3) a set of M2 rails for the vdd, gnd, inverted inputs, inputs
4) a set of NAND gates for inversion
"""
self.create_rails()
self.add_input_inverters()
self.add_output_inverters()
connections=[["inbar[0]", "inbar[1]", "inbar[2]", "Z[0]", "vdd", "gnd"],
["in[0]", "inbar[1]", "inbar[2]", "Z[1]", "vdd", "gnd"],
["inbar[0]", "in[1]", "inbar[2]", "Z[2]", "vdd", "gnd"],
["in[0]", "in[1]", "inbar[2]", "Z[3]", "vdd", "gnd"],
["inbar[0]", "inbar[1]", "in[2]", "Z[4]", "vdd", "gnd"],
["in[0]", "inbar[1]", "in[2]", "Z[5]", "vdd", "gnd"],
["inbar[0]", "in[1]", "in[2]", "Z[6]", "vdd", "gnd"],
["in[0]", "in[1]", "in[2]", "Z[7]", "vdd", "gnd"]]
self.add_nand(connections)
self.route()
def get_nand_input_line_combination(self):
""" These are the decoder connections of the NAND gates to the A,B,C pins """
combination = [["Abar[0]", "Abar[1]", "Abar[2]"],
["A[0]", "Abar[1]", "Abar[2]"],
["Abar[0]", "A[1]", "Abar[2]"],
["A[0]", "A[1]", "Abar[2]"],
["Abar[0]", "Abar[1]", "A[2]"],
["A[0]", "Abar[1]", "A[2]"],
["Abar[0]", "A[1]", "A[2]"],
["A[0]", "A[1]", "A[2]"]]
return combination
def analytical_delay(self, slew, load = 0.0 ):
# A -> Abar
a_t_b_delay = self.inv.analytical_delay(slew=slew, load=self.nand.input_load())
# Abar -> z
b_t_z_delay = self.nand.analytical_delay(slew=a_t_b_delay.slew, load=self.inv.input_load())
# Z -> out
a_t_out_delay = self.inv.analytical_delay(slew=b_t_z_delay.slew, load=load)
return a_t_b_delay + b_t_z_delay + a_t_out_delay
def analytical_power(self, slew, load = 0.0 ):
# in -> inbar
a_t_b_power = self.inv.analytical_power(slew=slew, load=self.nand.input_load())
# inbar -> z
b_t_z_power = self.nand.analytical_power(slew, load=self.inv.input_load())
# Z -> out
a_t_out_power = self.inv.analytical_power(slew, load=load)
return a_t_b_power + b_t_z_power + a_t_out_power
def input_load(self):
return self.nand.input_load()
+32
View File
@@ -0,0 +1,32 @@
import globals
import design
from math import log
import design
from tech import GDS,layer
import utils
class ms_flop(design.design):
"""
Memory address flip-flop
"""
pin_names = ["din", "dout", "dout_bar", "clk", "vdd", "gnd"]
(width,height) = utils.get_libcell_size("ms_flop", GDS["unit"], layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "ms_flop", GDS["unit"], layer["boundary"])
def __init__(self, name="ms_flop"):
design.design.__init__(self, name)
self.width = ms_flop.width
self.height = ms_flop.height
self.pin_map = ms_flop.pin_map
def analytical_delay(self, slew, load = 0.0):
# dont know how to calculate this now, use constant in tech file
from tech import spice
result = self.return_delay(spice["msflop_delay"], spice["msflop_slew"])
return result
def analytical_power(self, slew, load = 0.0):
return 4
+139
View File
@@ -0,0 +1,139 @@
import debug
import design
from tech import drc
from math import log
from vector import vector
from globals import OPTS
class ms_flop_array(design.design):
"""
An Array of D-Flipflops used for to store Data_in & Data_out of
Write_driver & Sense_amp, address inputs of column_mux &
hierdecoder
"""
def __init__(self, columns, word_size, name=""):
self.columns = columns
self.word_size = word_size
if name=="":
name = "flop_array_c{0}_w{1}".format(columns,word_size)
design.design.__init__(self, name)
debug.info(1, "Creating {}".format(self.name))
c = reload(__import__(OPTS.ms_flop))
self.mod_ms_flop = getattr(c, OPTS.ms_flop)
self.ms = self.mod_ms_flop("ms_flop")
self.add_mod(self.ms)
self.width = self.columns * self.ms.width
self.height = self.ms.height
self.words_per_row = self.columns / self.word_size
self.create_layout()
def create_layout(self):
self.add_pins()
self.create_ms_flop_array()
self.add_layout_pins()
self.DRC_LVS()
def add_pins(self):
for i in range(self.word_size):
self.add_pin("din[{0}]".format(i))
for i in range(self.word_size):
self.add_pin("dout[{0}]".format(i))
self.add_pin("dout_bar[{0}]".format(i))
self.add_pin("clk")
self.add_pin("vdd")
self.add_pin("gnd")
def create_ms_flop_array(self):
self.ms_inst={}
for i in range(0,self.columns,self.words_per_row):
name = "Xdff{0}".format(i)
if (i % 2 == 0 or self.words_per_row>1):
base = vector(i*self.ms.width,0)
mirror = "R0"
else:
base = vector((i+1)*self.ms.width,0)
mirror = "MY"
self.ms_inst[i/self.words_per_row]=self.add_inst(name=name,
mod=self.ms,
offset=base,
mirror=mirror)
self.connect_inst(["din[{0}]".format(i/self.words_per_row),
"dout[{0}]".format(i/self.words_per_row),
"dout_bar[{0}]".format(i/self.words_per_row),
"clk",
"vdd", "gnd"])
def add_layout_pins(self):
for i in range(self.word_size):
for gnd_pin in self.ms_inst[i].get_pins("gnd"):
if gnd_pin.layer!="metal2":
continue
self.add_layout_pin(text="gnd",
layer="metal2",
offset=gnd_pin.ll(),
width=gnd_pin.width(),
height=gnd_pin.height())
din_pins = self.ms_inst[i].get_pins("din")
for din_pin in din_pins:
self.add_layout_pin(text="din[{}]".format(i),
layer=din_pin.layer,
offset=din_pin.ll(),
width=din_pin.width(),
height=din_pin.height())
dout_pin = self.ms_inst[i].get_pin("dout")
self.add_layout_pin(text="dout[{}]".format(i),
layer="metal2",
offset=dout_pin.ll(),
width=dout_pin.width(),
height=dout_pin.height())
doutbar_pin = self.ms_inst[i].get_pin("dout_bar")
self.add_layout_pin(text="dout_bar[{}]".format(i),
layer="metal2",
offset=doutbar_pin.ll(),
width=doutbar_pin.width(),
height=doutbar_pin.height())
# Continous clk rail along with label.
self.add_layout_pin(text="clk",
layer="metal1",
offset=self.ms_inst[0].get_pin("clk").ll().scale(0,1),
width=self.width,
height=drc["minwidth_metal1"])
# Continous vdd rail along with label.
for vdd_pin in self.ms_inst[i].get_pins("vdd"):
if vdd_pin.layer!="metal1":
continue
self.add_layout_pin(text="vdd",
layer="metal1",
offset=vdd_pin.ll().scale(0,1),
width=self.width,
height=drc["minwidth_metal1"])
# Continous gnd rail along with label.
for gnd_pin in self.ms_inst[i].get_pins("gnd"):
if gnd_pin.layer!="metal1":
continue
self.add_layout_pin(text="gnd",
layer="metal1",
offset=gnd_pin.ll().scale(0,1),
width=self.width,
height=drc["minwidth_metal1"])
def analytical_delay(self, slew, load=0.0):
return self.ms.analytical_delay(slew=slew, load=load)
def analytical_power(self, slew, load):
return self.ms.analytical_power(slew=slew, load=load)
+201
View File
@@ -0,0 +1,201 @@
import contact
import pgate
import debug
from tech import drc, parameter
from ptx import ptx
from vector import vector
from globals import OPTS
class precharge(pgate.pgate):
"""
Creates a single precharge cell
This module implements the precharge bitline cell used in the design.
"""
def __init__(self, name, size=1):
pgate.pgate.__init__(self, name)
debug.info(2, "create single precharge cell: {0}".format(name))
c = reload(__import__(OPTS.bitcell))
self.mod_bitcell = getattr(c, OPTS.bitcell)
self.bitcell = self.mod_bitcell()
self.beta = parameter["beta"]
self.ptx_width = self.beta*parameter["min_tx_size"]
self.width = self.bitcell.width
self.add_pins()
self.create_layout()
self.DRC_LVS()
def add_pins(self):
self.add_pin_list(["bl", "br", "en", "vdd"])
def create_layout(self):
self.create_ptx()
self.add_ptx()
self.connect_poly()
self.add_en()
self.add_nwell_and_contact()
self.add_vdd_rail()
self.add_bitlines()
self.connect_to_bitlines()
def create_ptx(self):
"""Initializes the upper and lower pmos"""
self.pmos = ptx(width=self.ptx_width,
tx_type="pmos")
self.add_mod(self.pmos)
# Compute the other pmos2 location, but determining offset to overlap the
# source and drain pins
self.overlap_offset = self.pmos.get_pin("D").ll() - self.pmos.get_pin("S").ll()
def add_vdd_rail(self):
"""Adds a vdd rail at the top of the cell"""
# adds the rail across the width of the cell
vdd_position = vector(0, self.height - self.m1_width)
self.add_layout_pin(text="vdd",
layer="metal1",
offset=vdd_position,
width=self.width,
height=self.m1_width)
self.connect_pin_to_rail(self.upper_pmos2_inst,"S","vdd")
def add_ptx(self):
"""Adds both the upper_pmos and lower_pmos to the module"""
# adds the lower pmos to layout
#base = vector(self.width - 2*self.pmos.width + self.overlap_offset.x, 0)
self.lower_pmos_position = vector(self.bitcell.get_pin("BL").lx(),
self.pmos.active_offset.y)
self.lower_pmos_inst=self.add_inst(name="lower_pmos",
mod=self.pmos,
offset=self.lower_pmos_position)
self.connect_inst(["bl", "en", "BR", "vdd"])
# adds the upper pmos(s) to layout
ydiff = self.pmos.height + 2*self.m1_space + contact.poly.width
self.upper_pmos1_pos = self.lower_pmos_position + vector(0, ydiff)
self.upper_pmos1_inst=self.add_inst(name="upper_pmos1",
mod=self.pmos,
offset=self.upper_pmos1_pos)
self.connect_inst(["bl", "en", "vdd", "vdd"])
upper_pmos2_pos = self.upper_pmos1_pos + self.overlap_offset
self.upper_pmos2_inst=self.add_inst(name="upper_pmos2",
mod=self.pmos,
offset=upper_pmos2_pos)
self.connect_inst(["br", "en", "vdd", "vdd"])
def connect_poly(self):
"""Connects the upper and lower pmos together"""
offset = self.lower_pmos_inst.get_pin("G").ll()
# connects the top and bottom pmos' gates together
ylength = self.upper_pmos1_inst.get_pin("G").ll().y - offset.y
self.add_rect(layer="poly",
offset=offset,
width=self.poly_width,
height=ylength)
# connects the two poly for the two upper pmos(s)
offset = offset + vector(0, ylength - self.poly_width)
xlength = self.upper_pmos2_inst.get_pin("G").lx() - self.upper_pmos1_inst.get_pin("G").lx() + self.poly_width
self.add_rect(layer="poly",
offset=offset,
width=xlength,
height=self.poly_width)
def add_en(self):
"""Adds the en input rail, en contact/vias, and connects to the pmos"""
# adds the en contact to connect the gates to the en rail on metal1
offset = self.lower_pmos_inst.get_pin("G").ul() + vector(0,0.5*self.poly_space)
self.add_contact_center(layers=("poly", "contact", "metal1"),
offset=offset,
rotate=90)
# adds the en rail on metal1
self.add_layout_pin_center_segment(text="en",
layer="metal1",
start=offset.scale(0,1),
end=offset.scale(0,1)+vector(self.width,0))
def add_nwell_and_contact(self):
"""Adds a nwell tap to connect to the vdd rail"""
# adds the contact from active to metal1
well_contact_pos = self.upper_pmos1_inst.get_pin("D").center().scale(1,0) \
+ vector(0, self.upper_pmos1_inst.uy() + contact.well.height/2 + drc["well_extend_active"])
self.add_contact_center(layers=("active", "contact", "metal1"),
offset=well_contact_pos,
implant_type="n",
well_type="n")
self.height = well_contact_pos.y + contact.well.height
self.add_rect(layer="nwell",
offset=vector(0,0),
width=self.width,
height=self.height)
def add_bitlines(self):
"""Adds both bit-line and bit-line-bar to the module"""
# adds the BL on metal 2
offset = vector(self.bitcell.get_pin("BL").cx(),0) - vector(0.5 * self.m2_width,0)
self.add_layout_pin(text="bl",
layer="metal2",
offset=offset,
width=drc['minwidth_metal2'],
height=self.height)
# adds the BR on metal 2
offset = vector(self.bitcell.get_pin("BR").cx(),0) - vector(0.5 * self.m2_width,0)
self.add_layout_pin(text="br",
layer="metal2",
offset=offset,
width=drc['minwidth_metal2'],
height=self.height)
def connect_to_bitlines(self):
self.add_bitline_contacts()
self.connect_pmos(self.lower_pmos_inst.get_pin("S"),self.get_pin("bl"))
self.connect_pmos(self.lower_pmos_inst.get_pin("D"),self.get_pin("br"))
self.connect_pmos(self.upper_pmos1_inst.get_pin("S"),self.get_pin("bl"))
self.connect_pmos(self.upper_pmos2_inst.get_pin("D"),self.get_pin("br"))
def add_bitline_contacts(self):
"""Adds contacts/via from metal1 to metal2 for bit-lines"""
stack=("metal1", "via1", "metal2")
pos = self.lower_pmos_inst.get_pin("S").center()
self.add_contact_center(layers=stack,
offset=pos)
pos = self.lower_pmos_inst.get_pin("D").center()
self.add_contact_center(layers=stack,
offset=pos)
pos = self.upper_pmos1_inst.get_pin("S").center()
self.add_contact_center(layers=stack,
offset=pos)
pos = self.upper_pmos2_inst.get_pin("D").center()
self.add_contact_center(layers=stack,
offset=pos)
def connect_pmos(self, pmos_pin, bit_pin):
""" Connect pmos pin to bitline pin """
ll_pos = vector(min(pmos_pin.lx(),bit_pin.lx()), pmos_pin.by())
ur_pos = vector(max(pmos_pin.rx(),bit_pin.rx()), pmos_pin.uy())
width = ur_pos.x-ll_pos.x
height = ur_pos.y-ll_pos.y
self.add_rect(layer="metal2",
offset=ll_pos,
width=width,
height=height)
+76
View File
@@ -0,0 +1,76 @@
import design
import debug
from tech import drc
from vector import vector
from precharge import precharge
class precharge_array(design.design):
"""
Dynamically generated precharge array of all bitlines. Cols is number
of bit line columns, height is the height of the bit-cell array.
"""
def __init__(self, columns, size=1):
design.design.__init__(self, "precharge_array")
debug.info(1, "Creating {0}".format(self.name))
self.columns = columns
self.pc_cell = precharge(name="precharge", size=size)
self.add_mod(self.pc_cell)
self.width = self.columns * self.pc_cell.width
self.height = self.pc_cell.height
self.add_pins()
self.create_layout()
self.DRC_LVS()
def add_pins(self):
"""Adds pins for spice file"""
for i in range(self.columns):
self.add_pin("bl[{0}]".format(i))
self.add_pin("br[{0}]".format(i))
self.add_pin("en")
self.add_pin("vdd")
def create_layout(self):
self.add_insts()
self.add_layout_pin(text="vdd",
layer="metal1",
offset=self.pc_cell.get_pin("vdd").ll(),
width=self.width,
height=drc["minwidth_metal1"])
self.add_layout_pin(text="en",
layer="metal1",
offset=self.pc_cell.get_pin("en").ll(),
width=self.width,
height=drc["minwidth_metal1"])
def add_insts(self):
"""Creates a precharge array by horizontally tiling the precharge cell"""
for i in range(self.columns):
name = "pre_column_{0}".format(i)
offset = vector(self.pc_cell.width * i, 0)
inst=self.add_inst(name=name,
mod=self.pc_cell,
offset=offset)
bl_pin = inst.get_pin("bl")
self.add_layout_pin(text="bl[{0}]".format(i),
layer="metal2",
offset=bl_pin.ll(),
width=drc["minwidth_metal2"],
height=bl_pin.height())
br_pin = inst.get_pin("br")
self.add_layout_pin(text="br[{0}]".format(i),
layer="metal2",
offset=br_pin.ll(),
width=drc["minwidth_metal2"],
height=bl_pin.height())
self.connect_inst(["bl[{0}]".format(i), "br[{0}]".format(i),
"en", "vdd"])
+23
View File
@@ -0,0 +1,23 @@
import design
import debug
import utils
from tech import GDS,layer
class replica_bitcell(design.design):
"""
A single bit cell (6T, 8T, etc.)
This module implements the single memory cell used in the design. It
is a hand-made cell, so the layout and netlist should be available in
the technology library. """
pin_names = ["BL", "BR", "WL", "vdd", "gnd"]
(width,height) = utils.get_libcell_size("replica_cell_6t", GDS["unit"], layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "replica_cell_6t", GDS["unit"], layer["boundary"])
def __init__(self):
design.design.__init__(self, "replica_cell_6t")
debug.info(2, "Create replica bitcell object")
self.width = replica_bitcell.width
self.height = replica_bitcell.height
self.pin_map = replica_bitcell.pin_map
+346
View File
@@ -0,0 +1,346 @@
import debug
import design
from tech import drc
from pinv import pinv
import contact
from bitcell_array import bitcell_array
from ptx import ptx
from vector import vector
from globals import OPTS
class replica_bitline(design.design):
"""
Generate a module that simulates the delay of control logic
and bit line charging. Stages is the depth of the FO4 delay
line and rows is the height of the replica bit loads.
"""
def __init__(self, FO4_stages, bitcell_loads, name="replica_bitline"):
design.design.__init__(self, name)
g = reload(__import__(OPTS.delay_chain))
self.mod_delay_chain = getattr(g, OPTS.delay_chain)
g = reload(__import__(OPTS.replica_bitcell))
self.mod_replica_bitcell = getattr(g, OPTS.replica_bitcell)
c = reload(__import__(OPTS.bitcell))
self.mod_bitcell = getattr(c, OPTS.bitcell)
for pin in ["en", "out", "vdd", "gnd"]:
self.add_pin(pin)
self.bitcell_loads = bitcell_loads
self.FO4_stages = FO4_stages
self.create_modules()
self.calculate_module_offsets()
self.add_modules()
self.route()
self.add_layout_pins()
self.add_lvs_correspondence_points()
self.DRC_LVS()
def calculate_module_offsets(self):
""" Calculate all the module offsets """
# These aren't for instantiating, but we use them to get the dimensions
self.poly_contact_offset = vector(0.5*contact.poly.width,0.5*contact.poly.height)
# M1/M2 routing pitch is based on contacted pitch
self.m1_pitch = max(contact.m1m2.width,contact.m1m2.height) + max(self.m1_space,self.m2_space)
self.m2_pitch = max(contact.m2m3.width,contact.m2m3.height) + max(self.m2_space,self.m3_space)
# This corrects the offset pitch difference between M2 and M1
self.offset_fix = vector(0.5*(self.m2_width-self.m1_width),0)
# delay chain will be rotated 90, so move it over a width
# we move it up a inv height just for some routing room
self.rbl_inv_offset = vector(self.delay_chain.height, self.inv.width)
# access TX goes right on top of inverter, leave space for an inverter which is
# about the same as a TX. We'll need to add rails though.
self.access_tx_offset = vector(1.25*self.inv.height,self.rbl_inv_offset.y) + vector(0,2.5*self.inv.width)
self.delay_chain_offset = self.rbl_inv_offset + vector(0,4*self.inv.width)
# Replica bitline and such are not rotated, but they must be placed far enough
# away from the delay chain/inverter with space for three M2 tracks
self.bitcell_offset = self.rbl_inv_offset + vector(2*self.m2_pitch, 0) + vector(0, self.bitcell.height + self.inv.width)
self.rbl_offset = self.bitcell_offset
self.height = self.rbl_offset.y + self.rbl.height + self.m2_pitch
self.width = self.rbl_offset.x + self.bitcell.width
def create_modules(self):
""" Create modules for later instantiation """
self.bitcell = self.replica_bitcell = self.mod_replica_bitcell()
self.add_mod(self.bitcell)
# This is the replica bitline load column that is the height of our array
self.rbl = bitcell_array(name="bitline_load", cols=1, rows=self.bitcell_loads)
self.add_mod(self.rbl)
# FIXME: The FO and depth of this should be tuned
self.delay_chain = self.mod_delay_chain([4]*self.FO4_stages)
self.add_mod(self.delay_chain)
self.inv = pinv()
self.add_mod(self.inv)
self.access_tx = ptx(tx_type="pmos")
self.add_mod(self.access_tx)
def add_modules(self):
""" Add all of the module instances in the logical netlist """
# This is the threshold detect inverter on the output of the RBL
self.rbl_inv_inst=self.add_inst(name="rbl_inv",
mod=self.inv,
offset=self.rbl_inv_offset+vector(0,self.inv.width),
rotate=270,
mirror="MX")
self.connect_inst(["bl[0]", "out", "vdd", "gnd"])
self.tx_inst=self.add_inst(name="rbl_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"])
# add the well and poly contact
self.dc_inst=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.rbc_inst=self.add_inst(name="bitcell",
mod=self.replica_bitcell,
offset=self.bitcell_offset,
mirror="MX")
self.connect_inst(["bl[0]", "br[0]", "delayed_en", "vdd", "gnd"])
self.rbl_inst=self.add_inst(name="load",
mod=self.rbl,
offset=self.rbl_offset)
self.connect_inst(["bl[0]", "br[0]"] + ["gnd"]*self.bitcell_loads + ["vdd", "gnd"])
def route(self):
""" Connect all the signals together """
self.route_gnd()
self.route_vdd()
self.route_access_tx()
def route_access_tx(self):
# GATE ROUTE
# 1. Add the poly contact and nwell enclosure
# Determines the y-coordinate of where to place the gate input poly pin
# (middle in between the pmos and nmos)
poly_pin = self.tx_inst.get_pin("G")
poly_offset = poly_pin.rc()
# This centers the contact on the poly
contact_offset = poly_offset.scale(0,1) + self.dc_inst.get_pin("out").bc().scale(1,0)
self.add_contact_center(layers=("poly", "contact", "metal1"),
offset=contact_offset)
self.add_rect(layer="poly",
offset=poly_pin.lr(),
width=contact_offset.x-poly_offset.x,
height=self.poly_width)
nwell_offset = self.rbl_inv_offset + vector(-self.inv.height,self.inv.width)
self.add_rect(layer="nwell",
offset=nwell_offset,
width=0.5*self.inv.height,
height=self.delay_chain_offset.y-nwell_offset.y)
# 2. Route delay chain output to access tx gate
delay_en_offset = self.dc_inst.get_pin("out").bc()
self.add_path("metal1", [delay_en_offset,contact_offset])
# 3. Route the mid-point of previous route to the bitcell WL
# route bend of previous net to bitcell WL
wl_offset = self.rbc_inst.get_pin("WL").lc()
wl_mid = vector(contact_offset.x,wl_offset.y)
self.add_path("metal1", [contact_offset, wl_mid, wl_offset])
# DRAIN ROUTE
# Route the drain to the vdd rail
drain_offset = self.tx_inst.get_pin("D").lc()
inv_vdd_offset = self.rbl_inv_inst.get_pin("vdd").uc()
vdd_offset = inv_vdd_offset.scale(1,0) + drain_offset.scale(0,1)
self.add_path("metal1", [drain_offset, vdd_offset])
# SOURCE ROUTE
# Route the source to the RBL inverter input
source_offset = self.tx_inst.get_pin("S").bc()
mid1 = source_offset.scale(1,0) + vector(0,self.rbl_inv_offset.y+self.inv.width+self.m2_pitch)
inv_A_offset = self.rbl_inv_inst.get_pin("A").uc()
mid2 = vector(inv_A_offset.x, mid1.y)
self.add_path("metal1",[source_offset, mid1, mid2, inv_A_offset])
# Route the connection of the source route (mid2) to the RBL bitline (left)
source_offset = mid2
# Route the M2 to the right of the vdd rail between rbl_inv and bitcell
gnd_pin = self.rbl_inv_inst.get_pin("gnd").ll()
mid1 = vector(gnd_pin.x+self.m2_pitch,source_offset.y)
# Via will go halfway down from the bitcell
bl_offset = self.rbc_inst.get_pin("BL").bc()
via_offset = bl_offset - vector(0,0.5*self.inv.width)
mid2 = vector(mid1.x,via_offset.y)
# self.add_contact(layers=("metal1", "via1", "metal2"),
# offset=via_offset - vector(0.5*self.m2_width,0.5*self.m1_width))
self.add_wire(("metal1","via1","metal2"),[source_offset,mid1,mid2,via_offset,bl_offset])
#self.add_path("metal2",[via_offset,bl_offset])
def route_vdd(self):
# Add a rail in M2 that is to the right of the inverter gnd pin
# The replica column may not fit in a single standard cell pitch, so add the vdd rail to the
# right of it.
vdd_start = vector(self.bitcell_offset.x + self.bitcell.width + self.m1_pitch,0)
# It is the height of the entire RBL and bitcell
self.add_layout_pin(text="vdd",
layer="metal1",
offset=vdd_start,
width=self.m1_width,
height=self.rbl.height+self.bitcell.height+2*self.inv.width+0.5*self.m1_width)
# Connect the vdd pins of the bitcell load directly to vdd
vdd_pins = self.rbl_inst.get_pins("vdd")
for pin in vdd_pins:
offset = vector(vdd_start.x,pin.by())
self.add_rect(layer="metal1",
offset=offset,
width=self.rbl_offset.x-vdd_start.x,
height=self.m1_width)
# Also connect the replica bitcell vdd pin to vdd
pin = self.rbc_inst.get_pin("vdd")
offset = vector(vdd_start.x,pin.by())
self.add_rect(layer="metal1",
offset=offset,
width=self.bitcell_offset.x-vdd_start.x,
height=self.m1_width)
# Add a second vdd pin. No need for full length. It is must connect at the next level.
inv_vdd_offset = self.rbl_inv_inst.get_pin("vdd").ll()
self.add_layout_pin(text="vdd",
layer="metal1",
offset=inv_vdd_offset.scale(1,0),
width=self.m1_width,
height=self.delay_chain_offset.y)
def route_gnd(self):
""" Route all signals connected to gnd """
gnd_start = self.rbl_inv_inst.get_pin("gnd").bc()
gnd_end = vector(gnd_start.x, self.rbl_inst.uy()+2*self.m2_pitch)
# Add a rail in M1 from bottom of delay chain to two above the RBL
# This prevents DRC errors with vias for the WL
dc_top = self.dc_inst.ur()
self.add_segment_center(layer="metal1",
start=vector(gnd_start.x, dc_top.y),
end=gnd_end)
# Add a rail in M2 from RBL inverter to two above the RBL
self.add_segment_center(layer="metal2",
start=gnd_start,
end=gnd_end)
# Add pin from bottom to RBL inverter
self.add_layout_pin_center_segment(text="gnd",
layer="metal1",
start=gnd_start.scale(1,0),
end=gnd_start)
# Connect the WL pins directly to gnd
gnd_pin = self.get_pin("gnd").rc()
for row in range(self.bitcell_loads):
wl = "wl[{}]".format(row)
pin = self.rbl_inst.get_pin(wl)
start = vector(gnd_pin.x,pin.cy())
self.add_segment_center(layer="metal1",
start=start,
end=pin.lc())
self.add_via_center(layers=("metal1", "via1", "metal2"),
offset=start)
# Add via for the delay chain
offset = self.dc_inst.get_pins("gnd")[0].bc() + vector(0.5*contact.m1m2.width,0.5*contact.m1m2.height)
self.add_via_center(layers=("metal1", "via1", "metal2"),
offset=offset)
# Add via for the inverter
offset = self.rbl_inv_inst.get_pin("gnd").bc() - vector(0,0.5*contact.m1m2.height)
self.add_via_center(layers=("metal1", "via1", "metal2"),
offset=offset)
# Connect the bitcell gnd pins to the rail
gnd_pins = self.get_pins("gnd")
gnd_start = gnd_pins[0].ul()
rbl_gnd_pins = self.rbl_inst.get_pins("gnd")
# Add L shapes to each vertical gnd rail
for pin in rbl_gnd_pins:
if pin.layer != "metal2":
continue
gnd_end = pin.uc()
gnd_mid = vector(gnd_end.x, gnd_start.y)
self.add_wire(("metal1","via1","metal2"), [gnd_start, gnd_mid, gnd_end])
gnd_start = gnd_mid
# Add a second gnd pin to the second delay chain rail. No need for full length.
dc_gnd_offset = self.dc_inst.get_pins("gnd")[1].ll()
self.add_layout_pin(text="gnd",
layer="metal1",
offset=dc_gnd_offset.scale(1,0),
width=self.m1_width,
height=self.delay_chain_offset.y)
def add_layout_pins(self):
""" Route the input and output signal """
en_offset = self.dc_inst.get_pin("in").ll()
self.add_layout_pin(text="en",
layer="metal1",
offset=en_offset.scale(1,0),
width=self.m1_width,
height=en_offset.y)
out_offset = self.rbl_inv_inst.get_pin("Z").ll()
self.add_layout_pin(text="out",
layer="metal1",
offset=out_offset.scale(1,0),
width=self.m1_width,
height=out_offset.y)
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.
"""
pin = self.rbl_inv_inst.get_pin("A")
self.add_label_pin(text="bl[0]",
layer=pin.layer,
offset=pin.ll(),
height=pin.height(),
width=pin.width())
pin = self.dc_inst.get_pin("out")
self.add_label_pin(text="delayed_en",
layer=pin.layer,
offset=pin.ll(),
height=pin.height(),
width=pin.width())
+36
View File
@@ -0,0 +1,36 @@
import design
import debug
import utils
from tech import GDS,layer
class sense_amp(design.design):
"""
This module implements the single sense amp cell used in the design. It
is a hand-made cell, so the layout and netlist should be available in
the technology library.
Sense amplifier to read a pair of bit-lines.
"""
pin_names = ["bl", "br", "dout", "en", "vdd", "gnd"]
(width,height) = utils.get_libcell_size("sense_amp", GDS["unit"], layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "sense_amp", GDS["unit"], layer["boundary"])
def __init__(self, name):
design.design.__init__(self, name)
debug.info(2, "Create sense_amp")
self.width = sense_amp.width
self.height = sense_amp.height
self.pin_map = sense_amp.pin_map
def analytical_delay(self, slew, load=0.0):
from tech import spice
r = spice["min_tx_r"]/(10)
c_para = spice["min_tx_drain_c"]
result = self.cal_delay_with_rc(r = r, c = c_para+load, slew = slew)
return self.return_delay(result.delay, result.slew)
def analytical_power(self, slew, load=0.0):
#This is just skeleton code which returns a magic number. The sense amp consumes static
#power during its operation and some dynamic power due to the switching.
return 2
+122
View File
@@ -0,0 +1,122 @@
import design
from tech import drc
from vector import vector
import debug
from globals import OPTS
class sense_amp_array(design.design):
"""
Array of sense amplifiers to read the bitlines through the column mux.
Dynamically generated sense amp array for all bitlines.
"""
def __init__(self, word_size, words_per_row):
design.design.__init__(self, "sense_amp_array")
debug.info(1, "Creating {0}".format(self.name))
c = reload(__import__(OPTS.sense_amp))
self.mod_sense_amp = getattr(c, OPTS.sense_amp)
self.amp = self.mod_sense_amp("sense_amp")
self.add_mod(self.amp)
self.word_size = word_size
self.words_per_row = words_per_row
self.row_size = self.word_size * self.words_per_row
self.height = self.amp.height
self.width = self.amp.width * self.word_size * self.words_per_row
self.add_pins()
self.create_layout()
self.DRC_LVS()
def add_pins(self):
for i in range(0,self.row_size,self.words_per_row):
self.add_pin("data[{0}]".format(i/self.words_per_row))
self.add_pin("bl[{0}]".format(i))
self.add_pin("br[{0}]".format(i))
self.add_pin("en")
self.add_pin("vdd")
self.add_pin("gnd")
def create_layout(self):
self.add_sense_amp()
self.connect_rails()
def add_sense_amp(self):
bl_pin = self.amp.get_pin("bl")
br_pin = self.amp.get_pin("br")
dout_pin = self.amp.get_pin("dout")
for i in range(0,self.row_size,self.words_per_row):
name = "sa_d{0}".format(i)
amp_position = vector(self.amp.width * i, 0)
bl_offset = amp_position + bl_pin.ll().scale(1,0)
br_offset = amp_position + br_pin.ll().scale(1,0)
dout_offset = amp_position + dout_pin.ll()
self.add_inst(name=name,
mod=self.amp,
offset=amp_position)
self.connect_inst(["bl[{0}]".format(i),"br[{0}]".format(i),
"data[{0}]".format(i/self.words_per_row),
"en", "vdd", "gnd"])
self.add_layout_pin(text="bl[{0}]".format(i),
layer="metal2",
offset=bl_offset,
width=bl_pin.width(),
height=bl_pin.height())
self.add_layout_pin(text="br[{0}]".format(i),
layer="metal2",
offset=br_offset,
width=br_pin.width(),
height=br_pin.height())
self.add_layout_pin(text="data[{0}]".format(i/self.words_per_row),
layer="metal3",
offset=dout_offset,
width=dout_pin.width(),
height=dout_pin.height())
def connect_rails(self):
# add vdd rail across entire array
vdd_offset = self.amp.get_pin("vdd").ll().scale(0,1)
self.add_layout_pin(text="vdd",
layer="metal1",
offset=vdd_offset,
width=self.width,
height=drc["minwidth_metal1"])
# NOTE:the gnd rails are vertical so it is not connected horizontally
# add gnd rail across entire array
gnd_offset = self.amp.get_pin("gnd").ll().scale(0,1)
self.add_layout_pin(text="gnd",
layer="metal1",
offset=gnd_offset,
width=self.width,
height=drc["minwidth_metal1"])
# add sclk rail across entire array
sclk_offset = self.amp.get_pin("en").ll().scale(0,1)
self.add_layout_pin(text="en",
layer="metal1",
offset=sclk_offset,
width=self.width,
height=drc["minwidth_metal1"])
def analytical_delay(self, slew, load=0.0):
return self.amp.analytical_delay(slew=slew, load=load)
def analytical_power(self, slew, load=0.0):
return self.amp.analytical_power(slew=slew, load=load)
+173
View File
@@ -0,0 +1,173 @@
import design
import debug
from tech import drc, info
from vector import vector
import contact
from ptx import ptx
from globals import OPTS
class single_level_column_mux(design.design):
"""
This module implements the columnmux bitline cell used in the design.
Creates a single columnmux cell.
"""
def __init__(self, tx_size):
name="single_level_column_mux_{}".format(tx_size)
design.design.__init__(self, name)
debug.info(2, "create single column mux cell: {0}".format(name))
c = reload(__import__(OPTS.bitcell))
self.mod_bitcell = getattr(c, OPTS.bitcell)
self.bitcell = self.mod_bitcell()
self.ptx_width = tx_size * drc["minwidth_tx"]
self.add_pin_list(["bl", "br", "bl_out", "br_out", "sel", "gnd"])
self.create_layout()
def create_layout(self):
self.add_ptx()
self.pin_height = 2*self.m2_width
self.width = self.bitcell.width
self.height = self.nmos2.uy() + self.pin_height
self.connect_poly()
self.add_gnd_rail()
self.add_bitline_pins()
self.connect_bitlines()
self.add_wells()
def add_bitline_pins(self):
""" Add the top and bottom pins to this cell """
bl_pos = vector(self.bitcell.get_pin("BL").lx(), 0)
br_pos = vector(self.bitcell.get_pin("BR").lx(), 0)
# bl and br
self.add_layout_pin(text="bl",
layer="metal2",
offset=bl_pos + vector(0,self.height - self.pin_height),
height=self.pin_height)
self.add_layout_pin(text="br",
layer="metal2",
offset=br_pos + vector(0,self.height - self.pin_height),
height=self.pin_height)
# bl_out and br_out
self.add_layout_pin(text="bl_out",
layer="metal2",
offset=bl_pos,
height=self.pin_height)
self.add_layout_pin(text="br_out",
layer="metal2",
offset=br_pos,
height=self.pin_height)
def add_ptx(self):
""" Create the two pass gate NMOS transistors to switch the bitlines"""
# Adds nmos1,nmos2 to the module
self.nmos = ptx(width=self.ptx_width)
self.add_mod(self.nmos)
# Space it in the center
nmos1_position = self.nmos.active_offset.scale(0,1) + vector(0.5*self.bitcell.width-0.5*self.nmos.active_width,0)
self.nmos1=self.add_inst(name="mux_tx1",
mod=self.nmos,
offset=nmos1_position)
self.connect_inst(["bl", "sel", "bl_out", "gnd"])
# This aligns it directly above the other tx with gates abutting
nmos2_position = nmos1_position + vector(0,self.nmos.active_height + self.poly_space)
self.nmos2=self.add_inst(name="mux_tx2",
mod=self.nmos,
offset=nmos2_position)
self.connect_inst(["br", "sel", "br_out", "gnd"])
def connect_poly(self):
""" Connect the poly gate of the two pass transistors """
height=self.nmos2.get_pin("G").uy() - self.nmos1.get_pin("G").by()
self.add_layout_pin(text="sel",
layer="poly",
offset=self.nmos1.get_pin("G").ll(),
height=height)
def connect_bitlines(self):
""" Connect the bitlines to the mux transistors """
# These are on metal2
bl_pin = self.get_pin("bl")
br_pin = self.get_pin("br")
bl_out_pin = self.get_pin("bl_out")
br_out_pin = self.get_pin("br_out")
# These are on metal1
nmos1_s_pin = self.nmos1.get_pin("S")
nmos1_d_pin = self.nmos1.get_pin("D")
nmos2_s_pin = self.nmos2.get_pin("S")
nmos2_d_pin = self.nmos2.get_pin("D")
# Add vias to bl, br_out, nmos2/S, nmos1/D
self.add_via_center(layers=("metal1","via1","metal2"),
offset=bl_pin.bc())
self.add_via_center(layers=("metal1","via1","metal2"),
offset=br_out_pin.uc())
self.add_via_center(layers=("metal1","via1","metal2"),
offset=nmos2_s_pin.center())
self.add_via_center(layers=("metal1","via1","metal2"),
offset=nmos1_d_pin.center())
# bl -> nmos2/D on metal1
# bl_out -> nmos2/S on metal2
self.add_path("metal1",[bl_pin.ll(), vector(nmos2_d_pin.cx(),bl_pin.by()), nmos2_d_pin.center()])
# halfway up, move over
mid1 = bl_out_pin.uc().scale(1,0.5)+nmos2_s_pin.bc().scale(0,0.5)
mid2 = bl_out_pin.uc().scale(0,0.5)+nmos2_s_pin.bc().scale(1,0.5)
self.add_path("metal2",[bl_out_pin.uc(), mid1, mid2, nmos2_s_pin.bc()])
# br -> nmos1/D on metal2
# br_out -> nmos1/S on metal1
self.add_path("metal1",[br_out_pin.uc(), vector(nmos1_s_pin.cx(),br_out_pin.uy()), nmos1_s_pin.center()])
# halfway up, move over
mid1 = br_pin.bc().scale(1,0.5)+nmos1_d_pin.uc().scale(0,0.5)
mid2 = br_pin.bc().scale(0,0.5)+nmos1_d_pin.uc().scale(1,0.5)
self.add_path("metal2",[br_pin.bc(), mid1, mid2, nmos1_d_pin.uc()])
def add_gnd_rail(self):
""" Add the gnd rails through the cell to connect to the bitcell array """
gnd_pins = self.bitcell.get_pins("gnd")
for gnd_pin in gnd_pins:
# only use vertical gnd pins that span the whole cell
if gnd_pin.layer == "metal2" and gnd_pin.height >= self.bitcell.height:
gnd_position = vector(gnd_pin.lx(), 0)
self.add_layout_pin(text="gnd",
layer="metal2",
offset=gnd_position,
height=self.height)
def add_wells(self):
""" Add a well and implant over the whole cell. Also, add the pwell contact (if it exists) """
# find right most gnd rail
gnd_pins = self.bitcell.get_pins("gnd")
right_gnd = None
for gnd_pin in gnd_pins:
if right_gnd == None or gnd_pin.lx()>right_gnd.lx():
right_gnd = gnd_pin
# Add to the right (first) gnd rail
m1m2_offset = right_gnd.bc() + vector(0,0.5*self.nmos.poly_height)
self.add_via_center(layers=("metal1", "via1", "metal2"),
offset=m1m2_offset)
active_offset = right_gnd.bc() + vector(0,0.5*self.nmos.poly_height)
self.add_via_center(layers=("active", "contact", "metal1"),
offset=active_offset,
implant_type="p",
well_type="p")
@@ -0,0 +1,213 @@
from math import log
import design
from single_level_column_mux import single_level_column_mux
import contact
from tech import drc
import debug
import math
from vector import vector
class single_level_column_mux_array(design.design):
"""
Dynamically generated column mux array.
Array of column mux to read the bitlines through the 6T.
"""
def __init__(self, columns, word_size):
design.design.__init__(self, "columnmux_array")
debug.info(1, "Creating {0}".format(self.name))
self.columns = columns
self.word_size = word_size
self.words_per_row = self.columns / self.word_size
self.add_pins()
self.create_layout()
self.DRC_LVS()
def add_pins(self):
for i in range(self.columns):
self.add_pin("bl[{}]".format(i))
self.add_pin("br[{}]".format(i))
for i in range(self.words_per_row):
self.add_pin("sel[{}]".format(i))
for i in range(self.word_size):
self.add_pin("bl_out[{}]".format(i))
self.add_pin("br_out[{}]".format(i))
self.add_pin("gnd")
def create_layout(self):
self.add_modules()
self.setup_layout_constants()
self.create_array()
self.add_routing()
# Find the highest shapes to determine height before adding well
highest = self.find_highest_coords()
self.height = highest.y
self.add_layout_pins()
self.add_enclosure(self.mux_inst, "pwell")
def add_modules(self):
# FIXME: Why is this 8x?
self.mux = single_level_column_mux(tx_size=8)
self.add_mod(self.mux)
def setup_layout_constants(self):
self.column_addr_size = num_of_inputs = int(self.words_per_row / 2)
self.width = self.columns * self.mux.width
self.m1_pitch = contact.m1m2.width + max(drc["metal1_to_metal1"],drc["metal2_to_metal2"])
# one set of metal1 routes for select signals and a pair to interconnect the mux outputs bl/br
# one extra route pitch is to space from the sense amp
self.route_height = (self.words_per_row + 3)*self.m1_pitch
def create_array(self):
self.mux_inst = []
# For every column, add a pass gate
for col_num in range(self.columns):
name = "XMUX{0}".format(col_num)
x_off = vector(col_num * self.mux.width, self.route_height)
self.mux_inst.append(self.add_inst(name=name,
mod=self.mux,
offset=x_off))
self.connect_inst(["bl[{}]".format(col_num),
"br[{}]".format(col_num),
"bl_out[{}]".format(int(col_num/self.words_per_row)),
"br_out[{}]".format(int(col_num/self.words_per_row)),
"sel[{}]".format(col_num % self.words_per_row),
"gnd"])
def add_layout_pins(self):
""" Add the pins after we determine the height. """
# For every column, add a pass gate
for col_num in range(self.columns):
mux_inst = self.mux_inst[col_num]
offset = mux_inst.get_pin("bl").ll()
self.add_layout_pin(text="bl[{}]".format(col_num),
layer="metal2",
offset=offset,
height=self.height-offset.y)
offset = mux_inst.get_pin("br").ll()
self.add_layout_pin(text="br[{}]".format(col_num),
layer="metal2",
offset=offset,
height=self.height-offset.y)
gnd_pins = mux_inst.get_pins("gnd")
for gnd_pin in gnd_pins:
# only do even colums to avoid duplicates
offset = gnd_pin.ll()
if col_num % 2 == 0:
self.add_layout_pin(text="gnd",
layer="metal2",
offset=offset.scale(1,0),
height=self.height)
def add_routing(self):
self.add_horizontal_input_rail()
self.add_vertical_poly_rail()
self.route_bitlines()
def add_horizontal_input_rail(self):
""" Create address input rails on M1 below the mux transistors """
for j in range(self.words_per_row):
offset = vector(0, self.route_height - (j+1)*self.m1_pitch)
self.add_layout_pin(text="sel[{}]".format(j),
layer="metal1",
offset=offset,
width=self.mux.width * self.columns,
height=contact.m1m2.width)
def add_vertical_poly_rail(self):
""" Connect the poly to the address rails """
# Offset to the first transistor gate in the pass gate
for col in range(self.columns):
# which select bit should this column connect to depends on the position in the word
sel_index = col % self.words_per_row
# Add the column x offset to find the right select bit
gate_offset = self.mux_inst[col].get_pin("sel").bc()
# height to connect the gate to the correct horizontal row
sel_height = self.get_pin("sel[{}]".format(sel_index)).by()
# use the y offset from the sel pin and the x offset from the gate
offset = vector(gate_offset.x,self.get_pin("sel[{}]".format(sel_index)).cy())
# Add the poly contact with a shift to account for the rotation
self.add_via_center(layers=("metal1", "contact", "poly"),
offset=offset,
rotate=90)
self.add_path("poly", [offset, gate_offset])
def route_bitlines(self):
""" Connect the output bit-lines to form the appropriate width mux """
for j in range(self.columns):
bl_offset = self.mux_inst[j].get_pin("bl_out").ll()
br_offset = self.mux_inst[j].get_pin("br_out").ll()
bl_out_offset = bl_offset - vector(0,(self.words_per_row+1)*self.m1_pitch)
br_out_offset = br_offset - vector(0,(self.words_per_row+2)*self.m1_pitch)
if (j % self.words_per_row) == 0:
# Create the metal1 to connect the n-way mux output from the pass gate
# These will be located below the select lines. Yes, these are M2 width
# to ensure vias are enclosed and M1 min width rules.
width = contact.m1m2.width + self.mux.width * (self.words_per_row - 1)
self.add_rect(layer="metal1",
offset=bl_out_offset,
width=width,
height=drc["minwidth_metal2"])
self.add_rect(layer="metal1",
offset=br_out_offset,
width=width,
height=drc["minwidth_metal2"])
# Extend the bitline output rails and gnd downward on the first bit of each n-way mux
self.add_layout_pin(text="bl_out[{}]".format(int(j/self.words_per_row)),
layer="metal2",
offset=bl_out_offset.scale(1,0),
width=drc['minwidth_metal2'],
height=self.route_height)
self.add_layout_pin(text="br_out[{}]".format(int(j/self.words_per_row)),
layer="metal2",
offset=br_out_offset.scale(1,0),
width=drc['minwidth_metal2'],
height=self.route_height)
# This via is on the right of the wire
self.add_via(layers=("metal1", "via1", "metal2"),
offset=bl_out_offset + vector(contact.m1m2.height,0),
rotate=90)
# This via is on the left of the wire
self.add_via(layers=("metal1", "via1", "metal2"),
offset= br_out_offset,
rotate=90)
else:
self.add_rect(layer="metal2",
offset=bl_out_offset,
width=drc['minwidth_metal2'],
height=self.route_height-bl_out_offset.y)
# This via is on the right of the wire
self.add_via(layers=("metal1", "via1", "metal2"),
offset=bl_out_offset + vector(contact.m1m2.height,0),
rotate=90)
self.add_rect(layer="metal2",
offset=br_out_offset,
width=drc['minwidth_metal2'],
height=self.route_height-br_out_offset.y)
# This via is on the left of the wire
self.add_via(layers=("metal1", "via1", "metal2"),
offset= br_out_offset,
rotate=90)
+43
View File
@@ -0,0 +1,43 @@
import debug
import design
import utils
from tech import GDS,layer
class tri_gate(design.design):
"""
This module implements the tri gate cell used in the design for
bit-line isolation. It is a hand-made cell, so the layout and
netlist should be available in the technology library.
"""
pin_names = ["in", "en", "en_bar", "out", "gnd", "vdd"]
(width,height) = utils.get_libcell_size("tri_gate", GDS["unit"], layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "tri_gate", GDS["unit"], layer["boundary"])
unique_id = 1
def __init__(self, name=""):
if name=="":
name = "tri{0}".format(tri_gate.unique_id)
tri_gate.unique_id += 1
design.design.__init__(self, name)
debug.info(2, "Create tri_gate")
self.width = tri_gate.width
self.height = tri_gate.height
self.pin_map = tri_gate.pin_map
def analytical_delay(self, slew, load=0.0):
from tech import spice
r = spice["min_tx_r"]
c_para = spice["min_tx_drain_c"]
return self.cal_delay_with_rc(r = r, c = c_para+load, slew = slew)
def analytical_power(self, slew, load=0.0):
#Skeleton code for the power of a trigate. Returns magic number for now.
return 2
def input_load(self):
return 9*spice["min_tx_gate_c"]
+116
View File
@@ -0,0 +1,116 @@
import debug
from tech import drc
import design
from vector import vector
from globals import OPTS
class tri_gate_array(design.design):
"""
Dynamically generated tri gate array of all bitlines. words_per_row
"""
def __init__(self, columns, word_size):
"""Intial function of tri gate array """
design.design.__init__(self, "tri_gate_array")
debug.info(1, "Creating {0}".format(self.name))
c = reload(__import__(OPTS.tri_gate))
self.mod_tri_gate = getattr(c, OPTS.tri_gate)
self.tri = self.mod_tri_gate("tri_gate")
self.add_mod(self.tri)
self.columns = columns
self.word_size = word_size
self.words_per_row = self.columns / self.word_size
self.width = (self.columns / self.words_per_row) * self.tri.width
self.height = self.tri.height
self.create_layout()
self.DRC_LVS()
def create_layout(self):
"""generate layout """
self.add_pins()
self.create_array()
self.add_layout_pins()
def add_pins(self):
"""create the name of pins depend on the word size"""
for i in range(self.word_size):
self.add_pin("in[{0}]".format(i))
for i in range(self.word_size):
self.add_pin("out[{0}]".format(i))
for pin in ["en", "en_bar", "vdd", "gnd"]:
self.add_pin(pin)
def create_array(self):
"""add tri gate to the array """
self.tri_inst = {}
for i in range(0,self.columns,self.words_per_row):
name = "Xtri_gate{0}".format(i)
base = vector(i*self.tri.width, 0)
self.tri_inst[i]=self.add_inst(name=name,
mod=self.tri,
offset=base)
self.connect_inst(["in[{0}]".format(i/self.words_per_row),
"out[{0}]".format(i/self.words_per_row),
"en", "en_bar", "vdd", "gnd"])
def add_layout_pins(self):
for i in range(0,self.columns,self.words_per_row):
in_pin = self.tri_inst[i].get_pin("in")
self.add_layout_pin(text="in[{0}]".format(i/self.words_per_row),
layer="metal2",
offset=in_pin.ll(),
width=in_pin.width(),
height=in_pin.height())
out_pin = self.tri_inst[i].get_pin("out")
self.add_layout_pin(text="out[{0}]".format(i/self.words_per_row),
layer="metal2",
offset=out_pin.ll(),
width=out_pin.width(),
height=out_pin.height())
width = self.tri.width * self.columns - (self.words_per_row - 1) * self.tri.width
en_pin = self.tri_inst[0].get_pin("en")
self.add_layout_pin(text="en",
layer="metal1",
offset=en_pin.ll().scale(0, 1),
width=width,
height=drc["minwidth_metal1"])
enbar_pin = self.tri_inst[0].get_pin("en_bar")
self.add_layout_pin(text="en_bar",
layer="metal1",
offset=enbar_pin.ll().scale(0, 1),
width=width,
height=drc["minwidth_metal1"])
vdd_pin = self.tri_inst[0].get_pin("vdd")
self.add_layout_pin(text="vdd",
layer="metal1",
offset=vdd_pin.ll().scale(0, 1),
width=width,
height=drc["minwidth_metal1"])
for gnd_pin in self.tri_inst[0].get_pins("gnd"):
if gnd_pin.layer=="metal1":
self.add_layout_pin(text="gnd",
layer="metal1",
offset=gnd_pin.ll().scale(0, 1),
width=width,
height=drc["minwidth_metal1"])
def analytical_delay(self, slew, load=0.0):
return self.tri.analytical_delay(slew = slew, load = load)
def analytical_power(self, slew, load=0.0):
return self.tri.analytical_power(slew = slew, load = load)
+219
View File
@@ -0,0 +1,219 @@
from tech import drc, parameter
import debug
import design
import contact
from math import log
from math import sqrt
import math
from pinv import pinv
from pnand2 import pnand2
from vector import vector
from globals import OPTS
class wordline_driver(design.design):
"""
Creates a Wordline Driver
Generates the wordline-driver to drive the bitcell
"""
def __init__(self, rows):
design.design.__init__(self, "wordline_driver")
self.rows = rows
self.add_pins()
self.design_layout()
self.DRC_LVS()
def add_pins(self):
# inputs to wordline_driver.
for i in range(self.rows):
self.add_pin("in[{0}]".format(i))
# Outputs from wordline_driver.
for i in range(self.rows):
self.add_pin("wl[{0}]".format(i))
self.add_pin("en")
self.add_pin("vdd")
self.add_pin("gnd")
def design_layout(self):
self.add_layout()
self.offsets_of_gates()
self.create_layout()
def add_layout(self):
self.inv = pinv()
self.add_mod(self.inv)
self.inv_no_output = pinv(route_output=False)
self.add_mod(self.inv_no_output)
self.nand2 = pnand2()
self.add_mod(self.nand2)
def offsets_of_gates(self):
self.x_offset0 = 2*self.m1_width + 5*self.m1_space
self.x_offset1 = self.x_offset0 + self.inv.width
self.x_offset2 = self.x_offset1 + self.nand2.width
self.width = self.x_offset2 + self.inv.width
self.height = self.inv.height * self.rows
def create_layout(self):
# Wordline enable connection
en_pin=self.add_layout_pin(text="en",
layer="metal2",
offset=[self.m1_width + 2*self.m1_space,0],
width=self.m2_width,
height=self.height)
self.add_layout_pin(text="gnd",
layer="metal1",
offset=[0, -0.5*self.m1_width],
width=self.x_offset0,
height=self.m1_width)
for row in range(self.rows):
name_inv1 = "wl_driver_inv_en{}".format(row)
name_nand = "wl_driver_nand{}".format(row)
name_inv2 = "wl_driver_inv{}".format(row)
inv_nand2B_connection_height = (abs(self.inv.get_pin("Z").ll().y
- self.nand2.get_pin("B").ll().y)
+ self.m1_width)
if (row % 2):
y_offset = self.inv.height*(row + 1)
inst_mirror = "MX"
cell_dir = vector(0,-1)
m1tm2_rotate=270
m1tm2_mirror="R0"
else:
y_offset = self.inv.height*row
inst_mirror = "R0"
cell_dir = vector(0,1)
m1tm2_rotate=90
m1tm2_mirror="MX"
name_inv1_offset = [self.x_offset0, y_offset]
nand2_offset=[self.x_offset1, y_offset]
inv2_offset=[self.x_offset2, y_offset]
base_offset = vector(self.width, y_offset)
# Extend vdd and gnd of wordline_driver
yoffset = (row + 1) * self.inv.height - 0.5 * self.m1_width
if (row % 2):
pin_name = "gnd"
else:
pin_name = "vdd"
self.add_layout_pin(text=pin_name,
layer="metal1",
offset=[0, yoffset],
width=self.x_offset0,
height=self.m1_width)
# add inv1 based on the info above
inv1_inst=self.add_inst(name=name_inv1,
mod=self.inv_no_output,
offset=name_inv1_offset,
mirror=inst_mirror )
self.connect_inst(["en",
"en_bar[{0}]".format(row),
"vdd", "gnd"])
# add nand 2
nand_inst=self.add_inst(name=name_nand,
mod=self.nand2,
offset=nand2_offset,
mirror=inst_mirror)
self.connect_inst(["en_bar[{0}]".format(row),
"in[{0}]".format(row),
"net[{0}]".format(row),
"vdd", "gnd"])
# add inv2
inv2_inst=self.add_inst(name=name_inv2,
mod=self.inv,
offset=inv2_offset,
mirror=inst_mirror)
self.connect_inst(["net[{0}]".format(row),
"wl[{0}]".format(row),
"vdd", "gnd"])
# en connection
a_pin = inv1_inst.get_pin("A")
a_pos = a_pin.lc()
clk_offset = vector(en_pin.bc().x,a_pos.y)
self.add_segment_center(layer="metal1",
start=clk_offset,
end=a_pos)
self.add_via_center(layers=("metal1", "via1", "metal2"),
offset=clk_offset)
# first inv to nand2 A
zb_pos = inv1_inst.get_pin("Z").bc()
zu_pos = inv1_inst.get_pin("Z").uc()
bl_pos = nand_inst.get_pin("A").lc()
br_pos = nand_inst.get_pin("A").rc()
self.add_path("metal1", [zb_pos, zu_pos, bl_pos, br_pos])
# Nand2 out to 2nd inv
zr_pos = nand_inst.get_pin("Z").rc()
al_pos = inv2_inst.get_pin("A").lc()
# ensure the bend is in the middle
mid1_pos = vector(0.5*(zr_pos.x+al_pos.x), zr_pos.y)
mid2_pos = vector(0.5*(zr_pos.x+al_pos.x), al_pos.y)
self.add_path("metal1", [zr_pos, mid1_pos, mid2_pos, al_pos])
# connect the decoder input pin to nand2 B
b_pin = nand_inst.get_pin("B")
b_pos = b_pin.lc()
# needs to move down since B nand input is nearly aligned with A inv input
up_or_down = self.m2_space if row%2 else -self.m2_space
input_offset = vector(0,b_pos.y + up_or_down)
mid_via_offset = vector(clk_offset.x,input_offset.y) + vector(0.5*self.m2_width+self.m2_space+0.5*contact.m1m2.width,0)
# must under the clk line in M1
self.add_layout_pin_center_segment(text="in[{0}]".format(row),
layer="metal1",
start=input_offset,
end=mid_via_offset)
self.add_via_center(layers=("metal1", "via1", "metal2"),
offset=mid_via_offset)
# now connect to the nand2 B
self.add_path("metal2", [mid_via_offset, b_pos])
self.add_via_center(layers=("metal1", "via1", "metal2"),
offset=b_pos - vector(0.5*contact.m1m2.height,0),
rotate=90)
# output each WL on the right
wl_offset = inv2_inst.get_pin("Z").rc()
self.add_layout_pin_center_segment(text="wl[{0}]".format(row),
layer="metal1",
start=wl_offset,
end=wl_offset-vector(self.m1_width,0))
def analytical_delay(self, slew, load=0):
# decode -> net
decode_t_net = self.nand2.analytical_delay(slew, self.inv.input_load())
# net -> wl
net_t_wl = self.inv.analytical_delay(decode_t_net.slew, load)
return decode_t_net + net_t_wl
def analytical_power(self, slew, load=0):
# decode -> net
decode_p_net = self.nand2.analytical_power(slew, self.inv.input_load())
# net -> wl
net_p_wl = self.inv.analytical_power(slew, load)
return decode_p_net + net_p_wl
def input_load(self):
return self.nand2.input_load()
+25
View File
@@ -0,0 +1,25 @@
import debug
import design
import utils
from tech import GDS,layer
class write_driver(design.design):
"""
Tristate write driver to be active during write operations only.
This module implements the write driver cell used in the design. It
is a hand-made cell, so the layout and netlist should be available in
the technology library.
"""
pin_names = ["din", "bl", "br", "en", "gnd", "vdd"]
(width,height) = utils.get_libcell_size("write_driver", GDS["unit"], layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "write_driver", GDS["unit"], layer["boundary"])
def __init__(self, name):
design.design.__init__(self, name)
debug.info(2, "Create write_driver")
self.width = write_driver.width
self.height = write_driver.height
self.pin_map = write_driver.pin_map
+105
View File
@@ -0,0 +1,105 @@
from math import log
import design
from tech import drc
import debug
from vector import vector
from globals import OPTS
class write_driver_array(design.design):
"""
Array of tristate drivers to write to the bitlines through the column mux.
Dynamically generated write driver array of all bitlines.
"""
def __init__(self, columns, word_size):
design.design.__init__(self, "write_driver_array")
debug.info(1, "Creating {0}".format(self.name))
c = reload(__import__(OPTS.write_driver))
self.mod_write_driver = getattr(c, OPTS.write_driver)
self.driver = self.mod_write_driver("write_driver")
self.add_mod(self.driver)
self.columns = columns
self.word_size = word_size
self.words_per_row = columns / word_size
self.width = self.columns * self.driver.width
self.height = self.height = self.driver.height
self.add_pins()
self.create_layout()
self.DRC_LVS()
def add_pins(self):
for i in range(self.word_size):
self.add_pin("data[{0}]".format(i))
for i in range(self.word_size):
self.add_pin("bl[{0}]".format(i))
self.add_pin("br[{0}]".format(i))
self.add_pin("en")
self.add_pin("vdd")
self.add_pin("gnd")
def create_layout(self):
self.create_write_array()
self.add_layout_pins()
def create_write_array(self):
self.driver_insts = {}
for i in range(0,self.columns,self.words_per_row):
name = "Xwrite_driver{}".format(i)
base = vector(i * self.driver.width,0)
self.driver_insts[i/self.words_per_row]=self.add_inst(name=name,
mod=self.driver,
offset=base)
self.connect_inst(["data[{0}]".format(i/self.words_per_row),
"bl[{0}]".format(i/self.words_per_row),
"br[{0}]".format(i/self.words_per_row),
"en", "vdd", "gnd"])
def add_layout_pins(self):
for i in range(self.word_size):
din_pin = self.driver_insts[i].get_pin("din")
self.add_layout_pin(text="data[{0}]".format(i),
layer="metal2",
offset=din_pin.ll(),
width=din_pin.width(),
height=din_pin.height())
bl_pin = self.driver_insts[i].get_pin("bl")
self.add_layout_pin(text="bl[{0}]".format(i),
layer="metal2",
offset=bl_pin.ll(),
width=bl_pin.width(),
height=bl_pin.height())
br_pin = self.driver_insts[i].get_pin("br")
self.add_layout_pin(text="br[{0}]".format(i),
layer="metal2",
offset=br_pin.ll(),
width=br_pin.width(),
height=br_pin.height())
self.add_layout_pin(text="en",
layer="metal1",
offset=self.driver_insts[0].get_pin("en").ll().scale(0,1),
width=self.width,
height=drc['minwidth_metal1'])
self.add_layout_pin(text="vdd",
layer="metal1",
offset=self.driver_insts[0].get_pin("vdd").ll().scale(0,1),
width=self.width,
height=drc['minwidth_metal1'])
self.add_layout_pin(text="gnd",
layer="metal1",
offset=self.driver_insts[0].get_pin("gnd").ll().scale(0,1),
width=self.width,
height=drc['minwidth_metal1'])