OpenRAM/compiler/hierarchical_decoder.py

560 lines
22 KiB
Python

from tech import drc
import debug
import design
from math import log
from math import sqrt
import math
from contact import contact
from nand_2 import nand_2
from nand_3 import nand_3
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.config.bitcell))
self.mod_bitcell = getattr(c, OPTS.config.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()
# We only need to call the offset_all_coordinate function when there
# are vertical metal rails.
#if (self.num_inputs >= 4):
# self.offset_all_coordinates()
def add_modules(self):
self.inv = pinv()
self.add_mod(self.inv)
self.nand2 = nand_2()
self.add_mod(self.nand2)
self.nand3 = nand_3()
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):
self.m1m2_via = contact(layer_stack=("metal1", "via1", "metal2"))
# Vertical metal rail gap definition
self.metal2_extend_contact = (self.m1m2_via.second_layer_height - self.m1m2_via.contact_width) / 2
self.metal2_spacing = self.metal2_extend_contact + drc["metal2_to_metal2"]
self.metal2_pitch = self.metal2_spacing + drc["minwidth_metal2"]
self.via_shift = (self.m1m2_via.second_layer_width - self.m1m2_via.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_and_pins()
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."""
z_pin = nand_mod.get_pin("Z")
a_pin = self.inv.get_pin("A")
rect_height = z_pin.uy()-a_pin.by()
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"
rect_offset = vector(self.routing_width + nand_mod.width, y_off + z_pin.uy() - rect_height)
else:
y_off = self.predecoder_height + nand_mod.height*(row + 1)
y_dir = -1
mirror = "MX"
rect_offset =vector(self.routing_width + nand_mod.width, y_off - z_pin.uy())
self.add_inst(name=name,
mod=nand_mod,
offset=[self.routing_width, y_off],
mirror=mirror)
self.add_rect(layer="metal1",
offset=rect_offset,
width=drc["minwidth_metal1"],
height=rect_height)
def add_decoder_inv_array_and_pins(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
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.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)
self.add_layout_pin(text="decode[{0}]".format(row),
layer="metal1",
offset=offset+z_pin.ll().scale(1,y_dir),
width=z_pin.width(),
height=y_dir*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)
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 i in range(self.no_of_pre2x4):
self.connect_rails_to_pre2x4(i)
for i in range(self.no_of_pre3x8):
self.connect_rails_to_pre3x8(i)
def connect_rails_to_pre2x4(self, predecode_num):
""" Connects the 2x4 predecoder outputs to the vertical rails """
z_pin = self.inv.get_pin("Z")
pin = z_pin.ll()
for i in range(4):
index = predecode_num * 4 + i
current_inv_height = predecode_num*self.pre2_4.height + i*self.inv.height
if (i % 2 == 0):
pin_y = pin.y
else:
pin_y = self.inv.height - drc["minwidth_metal1"] - pin.y
self.connect_rail(vector(self.rail_x_offsets[index], current_inv_height + pin_y))
def connect_rails_to_pre3x8(self, predecode_num):
""" Connects the 3x8 predecoder outputs to the vertical rails """
z_pin = self.inv.get_pin("Z")
pin = z_pin.ll()
for i in range(8):
index = predecode_num * 8 + i + self.no_of_pre2x4 * 4
current_inv_height = predecode_num*self.pre3_8.height \
+ i*self.inv.height \
+ self.no_of_pre2x4*self.pre2_4.height
if (i % 2 == 0):
pin_y = pin.y
else:
pin_y = self.inv.height - drc["minwidth_metal1"] - pin.y
self.connect_rail(vector(self.rail_x_offsets[index], current_inv_height + pin_y))
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):
a_pin = self.nand2.get_pin("A")
b_pin = self.nand2.get_pin("B")
for index_A in self.predec_groups[0]:
for index_B in self.predec_groups[1]:
current_inv_height = self.predecoder_height + row_index*self.inv.height
if (row_index % 2 == 0):
yoffset_A = current_inv_height + a_pin.by()
yoffset_B = current_inv_height + b_pin.by()
else:
base = current_inv_height + self.inv.height - drc["minwidth_metal1"]
yoffset_A = base - a_pin.by()
yoffset_B = base - b_pin.by()
row_index = row_index + 1
self.connect_rail(vector(self.rail_x_offsets[index_A], yoffset_A))
self.connect_rail(vector(self.rail_x_offsets[index_B], yoffset_B))
elif (self.num_inputs > 5):
a_pin = self.nand3.get_pin("A")
b_pin = self.nand3.get_pin("B")
c_pin = self.nand3.get_pin("C")
for index_A in self.predec_groups[0]:
for index_B in self.predec_groups[1]:
for index_C in self.predec_groups[2]:
current_inv_height = self.predecoder_height + row_index*self.inv.height
if (row_index % 2 == 0):
yoffset_A = current_inv_height + a_pin.by()
yoffset_B = current_inv_height + b_pin.by()
yoffset_C = current_inv_height + c_pin.by()
else:
base = current_inv_height + self.inv.height - drc["minwidth_metal1"]
yoffset_A = base - a_pin.by()
yoffset_B = base - b_pin.by()
yoffset_C = base - c_pin.by()
row_index = row_index + 1
self.connect_rail(vector(self.rail_x_offsets[index_A], yoffset_A))
self.connect_rail(vector(self.rail_x_offsets[index_B], yoffset_B))
self.connect_rail(vector(self.rail_x_offsets[index_C], yoffset_C))
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, offset,contact_yoffset=0):
""" Adds a via at location and extends to self.routing_width """
self.add_rect(layer="metal1",
offset=offset,
width=self.routing_width-offset.x,
height=drc["minwidth_metal1"])
if contact_yoffset!=0:
yoffset = contact_yoffset
self.add_via(layers=("metal1", "via1", "metal2"),
offset=offset + vector(self.metal2_spacing,-self.via_shift),
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 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()