2016-11-08 18:57:35 +01:00
|
|
|
import debug
|
|
|
|
|
import design
|
|
|
|
|
import math
|
2016-11-09 21:20:52 +01:00
|
|
|
from tech import drc
|
2016-11-08 18:57:35 +01:00
|
|
|
from contact import contact
|
|
|
|
|
from pinv import pinv
|
|
|
|
|
from vector import vector
|
|
|
|
|
from globals import OPTS
|
2017-08-24 00:02:15 +02:00
|
|
|
from nand_2 import nand_2
|
|
|
|
|
from nand_3 import nand_3
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class hierarchical_predecode(design.design):
|
|
|
|
|
"""
|
|
|
|
|
Pre 2x4 and 3x8 decoder shared code.
|
|
|
|
|
"""
|
2017-08-24 00:02:15 +02:00
|
|
|
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))
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
c = reload(__import__(OPTS.config.bitcell))
|
|
|
|
|
self.mod_bitcell = getattr(c, OPTS.config.bitcell)
|
2017-08-24 00:02:15 +02:00
|
|
|
self.bitcell_height = self.mod_bitcell.height
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def add_pins(self):
|
|
|
|
|
for k in range(self.number_of_inputs):
|
2017-08-24 00:02:15 +02:00
|
|
|
self.add_pin("in[{0}]".format(k))
|
2016-11-08 18:57:35 +01:00
|
|
|
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):
|
2017-08-24 00:02:15 +02:00
|
|
|
""" Create the INV and NAND gate """
|
|
|
|
|
|
|
|
|
|
self.inv = pinv()
|
2016-11-08 18:57:35 +01:00
|
|
|
self.add_mod(self.inv)
|
2017-08-24 00:02:15 +02:00
|
|
|
|
|
|
|
|
self.create_nand(self.number_of_inputs)
|
2016-11-08 18:57:35 +01:00
|
|
|
self.add_mod(self.nand)
|
|
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
def create_nand(self,inputs):
|
|
|
|
|
""" Create the NAND for the predecode input stage """
|
|
|
|
|
if inputs==2:
|
|
|
|
|
self.nand = nand_2()
|
|
|
|
|
elif inputs==3:
|
|
|
|
|
self.nand = nand_3()
|
|
|
|
|
else:
|
|
|
|
|
debug.error("Invalid number of predecode inputs.",-1)
|
|
|
|
|
|
|
|
|
|
def setup_constraints(self):
|
|
|
|
|
self.m1m2_via = contact(layer_stack=("metal1", "via1", "metal2"))
|
|
|
|
|
self.metal2_space = drc["metal2_to_metal2"]
|
|
|
|
|
self.metal1_space = drc["metal1_to_metal1"]
|
|
|
|
|
self.metal2_width = drc["minwidth_metal2"]
|
|
|
|
|
self.metal1_width = drc["minwidth_metal1"]
|
|
|
|
|
# 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.metal2_pitch = self.m1m2_via.height + 2*self.metal2_space
|
|
|
|
|
# This is to shift the rotated vias to be on m2 pitch
|
|
|
|
|
self.via_x_shift = self.m1m2_via.height + self.m1m2_via.via_layer_position.scale(0,-1).y
|
|
|
|
|
# This is to shift the via if the metal1 and metal2 overlaps are different
|
|
|
|
|
self.via_y_shift = self.m1m2_via.second_layer_position.x - self.m1m2_via.first_layer_position.x + self.m1m2_via.via_layer_position.scale(-0.5,0).x
|
|
|
|
|
|
|
|
|
|
# The rail offsets are indexed by the label
|
|
|
|
|
self.rails = {}
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
# Non inverted input rails
|
|
|
|
|
for rail_index in range(self.number_of_inputs):
|
|
|
|
|
xoffset = rail_index * self.metal2_pitch
|
|
|
|
|
self.rails["in[{}]".format(rail_index)]=xoffset
|
|
|
|
|
# x offset for input inverters
|
|
|
|
|
self.x_off_inv_1 = self.number_of_inputs*self.metal2_pitch
|
|
|
|
|
|
2016-11-08 18:57:35 +01:00
|
|
|
# Creating the right hand side metal2 rails for output connections
|
2017-08-24 00:02:15 +02:00
|
|
|
for rail_index in range(2 * self.number_of_inputs):
|
|
|
|
|
xoffset = self.x_off_inv_1 + self.inv.width + ((rail_index+1) * self.metal2_pitch)
|
|
|
|
|
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.metal2_pitch
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
|
|
|
|
|
# x offset to output inverters
|
2016-11-08 18:57:35 +01:00
|
|
|
self.x_off_inv_2 = self.x_off_nand + self.nand.width
|
|
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
# Height width are computed
|
2016-11-08 18:57:35 +01:00
|
|
|
self.width = self.x_off_inv_2 + self.inv.width
|
2017-08-24 00:02:15 +02:00
|
|
|
self.height = self.number_of_outputs * self.nand.height
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
def create_rails(self):
|
2017-08-24 00:02:15 +02:00
|
|
|
""" 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=[self.rails[label], 0],
|
|
|
|
|
width=self.metal2_width,
|
|
|
|
|
height=self.height - drc["metal2_to_metal2"])
|
|
|
|
|
else:
|
|
|
|
|
self.add_rect(layer="metal2",
|
|
|
|
|
offset=[self.rails[label], 0],
|
|
|
|
|
width=self.metal2_width,
|
|
|
|
|
height=self.height - drc["metal2_to_metal2"])
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
def add_input_inverters(self):
|
|
|
|
|
""" Create the input inverters to invert input signals for the decode stage. """
|
|
|
|
|
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)
|
|
|
|
|
offset = vector(self.x_off_inv_1, y_off)
|
|
|
|
|
mirror = "R0"
|
|
|
|
|
else:
|
|
|
|
|
y_off = (inv_num + 1) * (self.inv.height)
|
|
|
|
|
offset = vector(self.x_off_inv_1, y_off)
|
|
|
|
|
mirror="MX"
|
|
|
|
|
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"])
|
|
|
|
|
|
2016-11-08 18:57:35 +01:00
|
|
|
def add_output_inverters(self):
|
2017-08-24 00:02:15 +02:00
|
|
|
""" Create inverters for the inverted output decode signals. """
|
|
|
|
|
|
2016-11-08 18:57:35 +01:00
|
|
|
self.decode_out_positions = []
|
2017-08-24 00:02:15 +02:00
|
|
|
z_pin = self.inv.get_pin("Z")
|
|
|
|
|
for inv_num in range(self.number_of_outputs):
|
|
|
|
|
name = "Xpre2x4_nand_inv[{}]".format(inv_num)
|
|
|
|
|
if (inv_num % 2 == 0):
|
|
|
|
|
y_factor = inv_num
|
2016-11-08 18:57:35 +01:00
|
|
|
mirror = "R0"
|
2017-08-24 00:02:15 +02:00
|
|
|
y_dir = 1
|
2016-11-08 18:57:35 +01:00
|
|
|
else:
|
2017-08-24 00:02:15 +02:00
|
|
|
y_factor =inv_num + 1
|
|
|
|
|
mirror = "MX"
|
|
|
|
|
y_dir = -1
|
2016-11-08 18:57:35 +01:00
|
|
|
base = vector(self.x_off_inv_2, self.inv.height * y_factor)
|
|
|
|
|
self.add_inst(name=name,
|
|
|
|
|
mod=self.inv,
|
|
|
|
|
offset=base,
|
|
|
|
|
mirror=mirror)
|
2017-08-24 00:02:15 +02:00
|
|
|
self.connect_inst(["Z[{}]".format(inv_num),
|
|
|
|
|
"out[{}]".format(inv_num),
|
2016-11-08 18:57:35 +01:00
|
|
|
"vdd", "gnd"])
|
2017-08-24 00:02:15 +02:00
|
|
|
|
|
|
|
|
z_pin = self.inv.get_pin("Z")
|
|
|
|
|
self.add_layout_pin(text="out[{}]".format(inv_num),
|
|
|
|
|
layer="metal1",
|
|
|
|
|
offset=base+z_pin.ll().scale(1,y_dir),
|
|
|
|
|
width=z_pin.width(),
|
|
|
|
|
height=z_pin.height()*y_dir)
|
|
|
|
|
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
def add_nand(self,connections):
|
2017-08-24 00:02:15 +02:00
|
|
|
""" Create the NAND stage for the decodes """
|
|
|
|
|
z_pin = self.nand.get_pin("Z")
|
|
|
|
|
a_pin = self.inv.get_pin("A")
|
2016-11-08 18:57:35 +01:00
|
|
|
for nand_input in range(self.number_of_outputs):
|
2016-11-22 21:23:55 +01:00
|
|
|
inout = str(self.number_of_inputs)+"x"+str(self.number_of_outputs)
|
2017-08-24 00:02:15 +02:00
|
|
|
name = "Xpre{0}_nand[{1}]".format(inout,nand_input)
|
|
|
|
|
rect_height = z_pin.uy()-a_pin.by()
|
2016-11-08 18:57:35 +01:00
|
|
|
if (nand_input % 2 == 0):
|
|
|
|
|
y_off = nand_input * (self.nand.height)
|
|
|
|
|
mirror = "R0"
|
2017-08-24 00:02:15 +02:00
|
|
|
rect_offset = vector(self.x_off_nand + self.nand.width,
|
|
|
|
|
y_off + z_pin.uy() - rect_height)
|
2016-11-08 18:57:35 +01:00
|
|
|
else:
|
|
|
|
|
y_off = (nand_input + 1) * (self.nand.height)
|
|
|
|
|
mirror = "MX"
|
2017-08-24 00:02:15 +02:00
|
|
|
rect_offset =vector(self.x_off_nand + self.nand.width,
|
|
|
|
|
y_off - z_pin.uy())
|
2016-11-08 18:57:35 +01:00
|
|
|
self.add_inst(name=name,
|
|
|
|
|
mod=self.nand,
|
|
|
|
|
offset=[self.x_off_nand, y_off],
|
|
|
|
|
mirror=mirror)
|
|
|
|
|
self.add_rect(layer="metal1",
|
2017-08-24 00:02:15 +02:00
|
|
|
offset=rect_offset,
|
|
|
|
|
width=self.metal1_width,
|
|
|
|
|
height=rect_height)
|
2016-11-08 18:57:35 +01:00
|
|
|
self.connect_inst(connections[nand_input])
|
|
|
|
|
|
|
|
|
|
def route(self):
|
|
|
|
|
self.route_input_inverters()
|
2017-08-24 00:02:15 +02:00
|
|
|
self.route_inputs_to_rails()
|
2016-11-08 18:57:35 +01:00
|
|
|
self.route_nand_to_rails()
|
2017-08-24 00:02:15 +02:00
|
|
|
self.route_vdd_gnd()
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
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 + 2*self.metal1_space
|
|
|
|
|
in_pin = "in[{}]".format(num)
|
|
|
|
|
a_pin = "A[{}]".format(num)
|
|
|
|
|
self.add_rect(layer="metal1",
|
|
|
|
|
offset=[self.rails[in_pin],y_offset],
|
|
|
|
|
width=self.rails[a_pin] + self.metal2_width - self.rails[in_pin],
|
|
|
|
|
height=self.metal1_width)
|
|
|
|
|
self.add_via(layers = ("metal1", "via1", "metal2"),
|
|
|
|
|
offset=[self.rails[in_pin] + self.via_x_shift, y_offset + self.via_y_shift],
|
|
|
|
|
rotate=90)
|
|
|
|
|
self.add_via(layers = ("metal1", "via1", "metal2"),
|
|
|
|
|
offset=[self.rails[a_pin] + self.via_x_shift, y_offset + self.via_y_shift],
|
|
|
|
|
rotate=90)
|
|
|
|
|
|
2016-11-22 21:23:55 +01:00
|
|
|
def route_input_inverters(self):
|
2017-08-24 00:02:15 +02:00
|
|
|
"""
|
|
|
|
|
Route all conections of the inputs inverters [Inputs, outputs, vdd, gnd]
|
|
|
|
|
"""
|
|
|
|
|
for inv_num in range(self.number_of_inputs):
|
|
|
|
|
(inv_offset, y_dir) = self.get_gate_offset(self.x_off_inv_1, self.inv.height, inv_num)
|
|
|
|
|
|
|
|
|
|
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.metal1_space
|
|
|
|
|
inv_out_offset = inv_offset+self.inv.get_pin("Z").ur().scale(1,y_dir)-vector(0,self.metal1_width).scale(1,y_dir)
|
2016-11-22 21:23:55 +01:00
|
|
|
self.add_rect(layer="metal1",
|
2017-08-24 00:02:15 +02:00
|
|
|
offset=[inv_out_offset.x,y_offset],
|
|
|
|
|
width=self.rails[out_pin]-inv_out_offset.x + self.metal2_width,
|
|
|
|
|
height=self.metal1_width)
|
2016-11-22 21:23:55 +01:00
|
|
|
self.add_rect(layer="metal1",
|
2017-08-24 00:02:15 +02:00
|
|
|
offset=inv_out_offset,
|
|
|
|
|
width=self.metal1_width,
|
|
|
|
|
height=y_offset-inv_out_offset.y)
|
2016-11-22 21:23:55 +01:00
|
|
|
self.add_via(layers = ("metal1", "via1", "metal2"),
|
2017-08-24 00:02:15 +02:00
|
|
|
offset=[self.rails[out_pin] + self.via_x_shift, y_offset + self.via_y_shift],
|
2016-11-22 21:23:55 +01:00
|
|
|
rotate=90)
|
2017-08-24 00:02:15 +02:00
|
|
|
|
|
|
|
|
|
2016-11-22 21:23:55 +01:00
|
|
|
#route input
|
2017-08-24 00:02:15 +02:00
|
|
|
inv_in_offset = inv_offset+self.inv.get_pin("A").ll().scale(1,y_dir)
|
2016-11-22 21:23:55 +01:00
|
|
|
self.add_rect(layer="metal1",
|
2017-08-24 00:02:15 +02:00
|
|
|
offset=[self.rails[in_pin], inv_in_offset.y],
|
|
|
|
|
width=inv_in_offset.x - self.rails[in_pin],
|
|
|
|
|
height=self.metal1_width)
|
2016-11-22 21:23:55 +01:00
|
|
|
self.add_via(layers=("metal1", "via1", "metal2"),
|
2017-08-24 00:02:15 +02:00
|
|
|
offset=[self.rails[in_pin] + self.via_x_shift, inv_in_offset.y + self.via_y_shift],
|
2016-11-22 21:23:55 +01:00
|
|
|
rotate=90)
|
2017-08-24 00:02:15 +02:00
|
|
|
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2016-11-22 21:23:55 +01:00
|
|
|
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]
|
2017-08-24 00:02:15 +02:00
|
|
|
(nand_offset,y_dir) = self.get_gate_offset(self.x_off_nand,self.nand.height,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_offset = nand_offset+self.nand.get_pin(gate_pin).ll().scale(1,y_dir)
|
2016-11-22 21:23:55 +01:00
|
|
|
self.add_rect(layer="metal1",
|
2017-08-24 00:02:15 +02:00
|
|
|
offset=[self.rails[rail_pin], pin_offset.y],
|
|
|
|
|
width=pin_offset.x - self.rails[rail_pin],
|
|
|
|
|
height=self.metal1_width)
|
2016-11-22 21:23:55 +01:00
|
|
|
self.add_via(layers=("metal1", "via1", "metal2"),
|
2017-08-24 00:02:15 +02:00
|
|
|
offset=[self.rails[rail_pin] + self.via_x_shift, pin_offset.y + self.via_y_shift],
|
|
|
|
|
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 = 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.x_off_inv_2 + self.inv.width + self.metal2_width,
|
|
|
|
|
height=self.metal1_width)
|
|
|
|
|
|
|
|
|
|
# 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.x_off_inv_2 + self.inv.width + self.metal2_width,
|
|
|
|
|
height=self.metal1_width)
|
|
|
|
|
|
2016-11-22 21:23:55 +01:00
|
|
|
|
|
|
|
|
|