OpenRAM/compiler/hierarchical_predecode.py

285 lines
14 KiB
Python

import debug
import design
import math
from tech import drc
from contact import contact
from pinv import pinv
from vector import vector
from globals import OPTS
class hierarchical_predecode(design.design):
"""
Pre 2x4 and 3x8 decoder shared code.
"""
def __init__(self, nmos_width, cellname, input_number):
design.design.__init__(self, cellname)
c = reload(__import__(OPTS.config.bitcell))
self.mod_bitcell = getattr(c, OPTS.config.bitcell)
self.bitcell_height = self.mod_bitcell.chars["height"]
self.nmos_width = nmos_width
self.number_of_inputs = input_number
self.number_of_outputs = int(math.pow(2, self.number_of_inputs))
def add_pins(self):
for k in range(self.number_of_inputs):
self.add_pin("A[{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):
layer_stack = ("metal1", "via1", "metal2")
self.m1m2_via = contact(layer_stack=layer_stack)
self.inv = pinv(name="a_inv_1",
nmos_width=drc["minwidth_tx"],
beta=2,
height=self.bitcell_height)
self.add_mod(self.inv)
# create_nand redefine in sub class based on number of inputs
self.create_nand()
self.add_mod(self.nand)
def set_up_constrain(self):
self.via_shift = (self.m1m2_via.second_layer_width - self.m1m2_via.first_layer_width) / 2
self.metal2_extend_contact = (self.m1m2_via.second_layer_height - self.m1m2_via.contact_width) / 2
self.via_shift = (self.m1m2_via.second_layer_width
- self.m1m2_via.first_layer_width) / 2
self.metal2_extend_contact = (self.m1m2_via.second_layer_height
- self.m1m2_via.contact_width) / 2
self.gap_between_rails = (self.metal2_extend_contact
+ drc["metal2_to_metal2"])
self.gap_between_rail_offset = (self.gap_between_rails
+ drc["minwidth_metal2"])
def setup_constrains(self):
self.via_shift = (self.m1m2_via.second_layer_width - self.m1m2_via.first_layer_width) / 2
self.metal2_extend_contact = (self.m1m2_via.second_layer_height - self.m1m2_via.contact_width) / 2
# Contact shift used connecting NAND3 inputs to the rail
self.contact_shift = (self.m1m2_via.first_layer_width - self.m1m2_via.contact_width) / 2
self.gap_between_rails = self.metal2_extend_contact + drc["metal2_to_metal2"]
self.gap_between_rail_offset = self.gap_between_rails + drc["minwidth_metal2"]
self.rails_x_offset = []
# set_rail_height redefine in sub class
self.set_rail_height()
# Creating the left hand side metal2 rails for input connections
for hrail_1 in range(self.number_of_inputs):
xoffset_1 = (self.metal2_extend_contact
+ (hrail_1 * self.gap_between_rail_offset))
self.rails_x_offset.append(xoffset_1)
# x offset for Xpre2x4_inv
self.x_off_inv_1 = self.rails_x_offset[-1] + self.gap_between_rail_offset
# Creating the right hand side metal2 rails for output connections
for hrail_2 in range(2 * self.number_of_inputs + 2):
xoffset_2 = self.x_off_inv_1 + self.inv.width + self.gap_between_rails + (hrail_2 * self.gap_between_rail_offset)
self.rails_x_offset.append(xoffset_2)
self.xoffset_2=self.rails_x_offset[-1]
self.x_off_nand = self.xoffset_2 + self.gap_between_rail_offset
self.x_off_inv_2 = self.x_off_nand + self.nand.width
self.update_size()
def update_size(self):
self.width = self.x_off_inv_2 + self.inv.width
self.set_height()
self.size = vector(self.width, self.height)
correct =vector(0, 0.5 * drc["minwidth_metal1"])
self.vdd_position = self.size - correct - vector(0, self.inv.height)
self.gnd_position = self.size - correct
def create_rails(self):
for x_off in self.rails_x_offset:
self.add_rect(layer="metal2",
offset=[x_off, 0],
width=drc["minwidth_metal2"],
height=self.rail_height)
def add_output_inverters(self):
self.decode_out_positions = []
for inv_2x4 in range(self.number_of_outputs):
name = "Xpre2x4_nand_inv[{0}]".format(inv_2x4)
if (inv_2x4 % 2 == 0):
y_factor = inv_2x4
mirror = "R0"
correct = self.inv.Z_position
else:
y_factor =inv_2x4 + 1
mirror = "MX"
correct = self.inv.Z_position.scale(1,-1) - vector(0,
drc["minwidth_metal1"])
base = vector(self.x_off_inv_2, self.inv.height * y_factor)
self.add_inst(name=name,
mod=self.inv,
offset=base,
mirror=mirror)
self.connect_inst(["Z[{0}]".format(inv_2x4),
"out[{0}]".format(inv_2x4),
"vdd", "gnd"])
output_inv_out_offset = base + correct
self.decode_out_positions.append(output_inv_out_offset)
def add_nand(self,connections):
for nand_input in range(self.number_of_outputs):
inout = str(self.number_of_inputs)+"x"+str(self.number_of_outputs)
name = "Xpre"+inout+"_nand[{0}]".format(nand_input)
if (nand_input % 2 == 0):
y_off = nand_input * (self.nand.height)
mirror = "R0"
offset = [self.x_off_nand + self.nand.width,
y_off + self.nand.Z_position.y]
else:
y_off = (nand_input + 1) * (self.nand.height)
mirror = "MX"
offset =[self.x_off_nand + self.nand.width,
y_off - self.nand.Z_position.y - drc["minwidth_metal1"]]
self.add_inst(name=name,
mod=self.nand,
offset=[self.x_off_nand, y_off],
mirror=mirror)
self.add_rect(layer="metal1",
offset=offset,
width=drc["minwidth_metal1"],
height=drc["minwidth_metal1"])
self.connect_inst(connections[nand_input])
def route(self):
self.route_input_inverters()
self.route_nand_to_rails()
self.route_vdd_gnd_from_rails_to_gates()
def route_input_inverters(self):
# All conections of the inputs inverters [Inputs, outputs, vdd, gnd]
output_shift = self.set_output_shift()
for inv_rout in range(self.number_of_inputs):
setup = self.setup_route_input_inverter(inv_rout,output_shift)
y_dir,inv_in_offset,inv_out_offset,inv_vdd_offset,inv_gnd_offset = setup
#add output
correct = y_dir * (output_shift + drc["minwidth_metal1"])
output_metal = self.cal_input_inverters_output(setup,output_shift,inv_rout)
offset1,offset2=output_metal[0]
offset3,offset4=output_metal[1]
self.add_rect(layer="metal1",
offset=offset1,
width=drc["minwidth_metal1"],
height=offset2.y - offset1.y)
self.add_rect(layer="metal1",
offset=offset3,
width=offset4.x - offset3.x,
height=drc["minwidth_metal1"])
off_via = [self.rails_x_offset[inv_rout + self.number_of_inputs+2] + self.gap_between_rails,
inv_vdd_offset.y- self.via_shift - correct]
self.add_via(layers = ("metal1", "via1", "metal2"),
offset=off_via,
rotate=90)
#route input
self.add_rect(layer="metal1",
offset=[self.rails_x_offset[inv_rout],
inv_in_offset.y],
width=inv_in_offset.x - self.rails_x_offset[inv_rout] + drc["minwidth_metal2"],
height=drc["minwidth_metal1"])
self.add_via(layers=("metal1", "via1", "metal2"),
offset=[self.rails_x_offset[inv_rout] + self.gap_between_rails,
inv_in_offset.y - self.via_shift],
rotate=90)
# route vdd
self.add_rect(layer="metal1",
offset=inv_vdd_offset,
width=self.rails_x_offset[self.number_of_inputs] - inv_vdd_offset.x + drc["minwidth_metal2"],
height=drc["minwidth_metal1"])
# route gnd
self.add_rect(layer="metal1",
offset=inv_gnd_offset,
width=self.rails_x_offset[self.number_of_inputs+1] - inv_gnd_offset.x + drc["minwidth_metal2"],
height=drc["minwidth_metal1"])
def setup_route_input_inverter(self, inv_rout, output_shift):
# add Inputs, vdd, gnd of the inputs inverters
if (inv_rout % 2 == 0):
base_offset=[self.x_off_inv_1, inv_rout * self.inv.height ]
y_dir = 1
else:
base_offset=[self.x_off_inv_1, 2 * self.inv.height - drc["minwidth_metal1"]]
y_dir = -1
inv_out_offset = base_offset+self.inv.Z_position.scale(1,y_dir)
inv_in_offset = base_offset+self.inv.A_position.scale(1,y_dir)
inv_vdd_offset = base_offset+self.inv.vdd_position.scale(1,y_dir)
inv_gnd_offset = base_offset+self.inv.gnd_position.scale(1,y_dir)
#return info to create output of the input inverter
return [y_dir,inv_in_offset,inv_out_offset,inv_vdd_offset,inv_gnd_offset]
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]
line_x_offset = []
for index in index_lst:
line_x_offset.append(self.rails_x_offset[index])
# create y offset list
yoffset_nand_in, correct= self.create_y_offsets(k)
# connect based on the two list
for i in range(self.number_of_inputs):
x_offset = line_x_offset[i]
y_offset = yoffset_nand_in[i]
# Connecting the i-th input of Nand3 gate
self.add_rect(layer="metal1",
offset=[x_offset, y_offset],
width=self.x_off_nand - x_offset,
height=drc["minwidth_metal1"])
self.add_via(layers=("metal1", "via1", "metal2"),
offset=[x_offset+ self.gap_between_rails,
y_offset - self.via_shift - correct[i]],
rotate=90)
# Extended of the top NAND2 to the left hand side input rails
if(k == self.number_of_outputs - 1):
x_offset = self.rails_x_offset[i]
self.add_rect(layer="metal1",
offset=[x_offset, y_offset],
width=self.x_off_nand - x_offset,
height=drc["minwidth_metal1"])
self.add_via(layers = ("metal1", "via1", "metal2"),
offset=[x_offset + self.gap_between_rails,
y_offset - self.via_shift],
rotate=90)
def route_vdd_gnd_from_rails_to_gates(self):
via_correct = self.get_via_correct()
for k in range(self.number_of_outputs):
power_line_index = self.number_of_inputs + 1 - (k%2)
yoffset = k * self.inv.height - 0.5 * drc["minwidth_metal1"]
self.add_rect(layer="metal1",
offset=[self.rails_x_offset[power_line_index],
yoffset],
width=self.x_off_nand - self.rails_x_offset[power_line_index],
height=drc["minwidth_metal1"])
self.add_via(layers = ("metal1", "via1", "metal2"),
offset=[self.rails_x_offset[power_line_index] + self.gap_between_rails,
yoffset - via_correct.y],
rotate=90)
yoffset = (self.number_of_outputs * self.inv.height
- 0.5 * drc["minwidth_metal1"])
v_metal = self.get_vertical_metal()
via_y = self.get_via_y()
index = self.number_of_inputs + 1
self.add_rect(layer="metal1",
offset=[self.rails_x_offset[index], yoffset],
width=self.x_off_nand - self.rails_x_offset[index],
height=drc["minwidth_metal1"])
self.add_rect(layer=v_metal,
offset=[self.rails_x_offset[index], self.rail_height],
width=drc["minwidth_"+v_metal],
height=yoffset - self.rail_height)
self.add_via(layers = ("metal1", "via1", "metal2"),
offset=[self.rails_x_offset[index] + self.gap_between_rails,
via_y] - via_correct,
rotate=90)