mirror of
https://github.com/VLSIDA/OpenRAM.git
synced 2026-09-03 08:27:11 +02:00
Fixed merging issues with power branch
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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()
|
||||
@@ -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())
|
||||
|
||||
|
||||
@@ -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())
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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"])
|
||||
|
||||
@@ -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
|
||||
@@ -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())
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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
|
||||
|
||||
@@ -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'])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user