mirror of
https://github.com/VLSIDA/OpenRAM.git
synced 2026-09-03 08:27:11 +02:00
Thin-cell decoder changes.
Add hard decoder gates (nand, inv, pnan) Add conditions for routing using LI layer in s8. Generalize bus layers for decoders. Move custom cells to own directory. Fixed via directions, etc. Add 4x16 hierarchical decoder and test case
This commit is contained in:
@@ -8,29 +8,24 @@
|
||||
import debug
|
||||
import design
|
||||
import math
|
||||
import contact
|
||||
from vector import vector
|
||||
from sram_factory import factory
|
||||
from tech import cell_properties
|
||||
from globals import OPTS
|
||||
|
||||
|
||||
class hierarchical_predecode(design.design):
|
||||
"""
|
||||
Pre 2x4 and 3x8 decoder shared code.
|
||||
Pre 2x4 and 3x8 and TBD 4x16 decoder shared code.
|
||||
"""
|
||||
def __init__(self, name, input_number, height=None):
|
||||
self.number_of_inputs = input_number
|
||||
|
||||
if not height:
|
||||
b = factory.create(module_type="bitcell")
|
||||
try:
|
||||
self.cell_multiple = cell_properties.bitcell.decoder_bitcell_multiple
|
||||
except AttributeError:
|
||||
self.cell_multiple = 1
|
||||
self.cell_height = self.cell_multiple * b.height
|
||||
self.cell_height = b.height
|
||||
else:
|
||||
self.cell_height = height
|
||||
|
||||
|
||||
self.number_of_outputs = int(math.pow(2, self.number_of_inputs))
|
||||
design.design.__init__(self, name)
|
||||
|
||||
@@ -44,34 +39,73 @@ class hierarchical_predecode(design.design):
|
||||
|
||||
def add_modules(self):
|
||||
""" Add the INV and AND gate modules """
|
||||
|
||||
self.inv = factory.create(module_type="pinv",
|
||||
height=self.cell_height)
|
||||
self.add_mod(self.inv)
|
||||
|
||||
self.add_and(self.number_of_inputs)
|
||||
self.add_mod(self.and_mod)
|
||||
|
||||
def add_and(self, inputs):
|
||||
""" Create the NAND for the predecode input stage """
|
||||
if inputs==2:
|
||||
self.and_mod = factory.create(module_type="pand2",
|
||||
# FIXME: Default parms are required for hard cells for now.
|
||||
if self.number_of_inputs == 2:
|
||||
self.and_mod = factory.create(module_type="pand2_dec",
|
||||
height=self.cell_height)
|
||||
elif inputs==3:
|
||||
self.and_mod = factory.create(module_type="pand3",
|
||||
elif self.number_of_inputs == 3:
|
||||
self.and_mod = factory.create(module_type="pand3_dec",
|
||||
height=self.cell_height)
|
||||
elif self.number_of_inputs == 4:
|
||||
self.and_mod = factory.create(module_type="pand4_dec",
|
||||
height=self.cell_height)
|
||||
else:
|
||||
debug.error("Invalid number of predecode inputs: {}".format(inputs), -1)
|
||||
|
||||
debug.error("Invalid number of predecode inputs: {}".format(self.number_of_inputs), -1)
|
||||
self.add_mod(self.and_mod)
|
||||
|
||||
# This uses the pinv_dec parameterized cell
|
||||
self.inv = factory.create(module_type="inv_dec",
|
||||
height=self.cell_height,
|
||||
size=1)
|
||||
self.add_mod(self.inv)
|
||||
|
||||
|
||||
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 AND gates for inversion
|
||||
"""
|
||||
self.setup_layout_constraints()
|
||||
self.route_rails()
|
||||
self.place_input_inverters()
|
||||
self.place_and_array()
|
||||
self.route()
|
||||
self.add_boundary()
|
||||
self.DRC_LVS()
|
||||
|
||||
def setup_layout_constraints(self):
|
||||
|
||||
# Inputs to cells are on input layer
|
||||
# Outputs from cells are on output layer
|
||||
if OPTS.tech_name == "s8":
|
||||
self.bus_layer = "m1"
|
||||
self.bus_directions = None
|
||||
self.bus_pitch = self.m1_pitch
|
||||
self.bus_space = 1.5 * self.m1_space
|
||||
self.input_layer = "li"
|
||||
self.output_layer = "m2"
|
||||
self.output_layer_pitch = self.m2_pitch
|
||||
else:
|
||||
self.bus_layer = "m2"
|
||||
self.bus_directions = None
|
||||
self.bus_pitch = self.m2_pitch
|
||||
self.bus_space = self.m2_space
|
||||
# This requires a special jog to ensure to conflicts with the output layers
|
||||
self.input_layer = "m1"
|
||||
self.output_layer = "m1"
|
||||
self.output_layer_pitch = self.m1_pitch
|
||||
|
||||
self.height = self.number_of_outputs * self.and_mod.height
|
||||
|
||||
# x offset for input inverters
|
||||
self.x_off_inv_1 = self.number_of_inputs * self.m2_pitch + self.m2_space
|
||||
# +1 input for spacing for supply rail contacts
|
||||
self.x_off_inv_1 = (self.number_of_inputs + 1) * self.bus_pitch + self.bus_pitch
|
||||
|
||||
# x offset to AND decoder includes the left rails, mid rails and inverters, plus two extra m2 pitches
|
||||
self.x_off_and = self.x_off_inv_1 + self.inv.width + (2 * self.number_of_inputs + 2) * self.m2_pitch
|
||||
# x offset to AND decoder includes the left rails, mid rails and inverters, plus two extra bus pitches
|
||||
self.x_off_and = self.x_off_inv_1 + self.inv.width + (2 * self.number_of_inputs + 2) * self.bus_pitch
|
||||
|
||||
# x offset to output inverters
|
||||
self.width = self.x_off_and + self.and_mod.width
|
||||
@@ -79,28 +113,30 @@ class hierarchical_predecode(design.design):
|
||||
def route_rails(self):
|
||||
""" Create all of the rails for the inputs and vdd/gnd/inputs_bar/inputs """
|
||||
input_names = ["in_{}".format(x) for x in range(self.number_of_inputs)]
|
||||
offset = vector(0.5 * self.m2_width, self.m3_pitch)
|
||||
self.input_rails = self.create_vertical_pin_bus(layer="m2",
|
||||
offset=offset,
|
||||
names=input_names,
|
||||
length=self.height - 2 * self.m1_pitch)
|
||||
# Offsets for the perimeter spacing to other modules
|
||||
# This uses m3 pitch to leave space for power routes
|
||||
offset = vector(self.bus_pitch, self.bus_pitch)
|
||||
self.input_rails = self.create_vertical_bus(layer=self.bus_layer,
|
||||
offset=offset,
|
||||
names=input_names,
|
||||
length=self.height - 2 * self.bus_pitch)
|
||||
|
||||
invert_names = ["Abar_{}".format(x) for x in range(self.number_of_inputs)]
|
||||
non_invert_names = ["A_{}".format(x) for x in range(self.number_of_inputs)]
|
||||
decode_names = invert_names + non_invert_names
|
||||
offset = vector(self.x_off_inv_1 + self.inv.width + 2 * self.m2_pitch, self.m3_pitch)
|
||||
self.decode_rails = self.create_vertical_bus(layer="m2",
|
||||
offset = vector(self.x_off_inv_1 + self.inv.width + self.bus_pitch, self.bus_pitch)
|
||||
self.decode_rails = self.create_vertical_bus(layer=self.bus_layer,
|
||||
offset=offset,
|
||||
names=decode_names,
|
||||
length=self.height - 2 * self.m1_pitch)
|
||||
length=self.height - 2 * self.bus_pitch)
|
||||
|
||||
def create_input_inverters(self):
|
||||
""" Create the input inverters to invert input signals for the decode stage. """
|
||||
self.in_inst = []
|
||||
self.inv_inst = []
|
||||
for inv_num in range(self.number_of_inputs):
|
||||
name = "pre_inv_{0}".format(inv_num)
|
||||
self.in_inst.append(self.add_inst(name=name,
|
||||
mod=self.inv))
|
||||
self.inv_inst.append(self.add_inst(name=name,
|
||||
mod=self.inv))
|
||||
self.connect_inst(["in_{0}".format(inv_num),
|
||||
"inbar_{0}".format(inv_num),
|
||||
"vdd", "gnd"])
|
||||
@@ -108,6 +144,7 @@ class hierarchical_predecode(design.design):
|
||||
def place_input_inverters(self):
|
||||
""" Place the input inverters to invert input signals for the decode stage. """
|
||||
for inv_num in range(self.number_of_inputs):
|
||||
|
||||
if (inv_num % 2 == 0):
|
||||
y_off = inv_num * (self.inv.height)
|
||||
mirror = "R0"
|
||||
@@ -115,8 +152,8 @@ class hierarchical_predecode(design.design):
|
||||
y_off = (inv_num + 1) * (self.inv.height)
|
||||
mirror="MX"
|
||||
offset = vector(self.x_off_inv_1, y_off)
|
||||
self.in_inst[inv_num].place(offset=offset,
|
||||
mirror=mirror)
|
||||
self.inv_inst[inv_num].place(offset=offset,
|
||||
mirror=mirror)
|
||||
|
||||
def create_and_array(self, connections):
|
||||
""" Create the AND stage for the decodes """
|
||||
@@ -151,21 +188,30 @@ class hierarchical_predecode(design.design):
|
||||
|
||||
def route_inputs_to_rails(self):
|
||||
""" Route the uninverted inputs to the second set of rails """
|
||||
|
||||
top_and_gate = self.and_inst[-1]
|
||||
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 and gates.
|
||||
y_offset = (num + self.number_of_inputs) * self.inv.height + contact.m2_via.width + self.m2_space
|
||||
if num == 0:
|
||||
pin = top_and_gate.get_pin("A")
|
||||
elif num == 1:
|
||||
pin = top_and_gate.get_pin("B")
|
||||
elif num == 2:
|
||||
pin = top_and_gate.get_pin("C")
|
||||
elif num == 3:
|
||||
pin = top_and_gate.get_pin("D")
|
||||
else:
|
||||
debug.error("Too many inputs for predecoder.", -1)
|
||||
y_offset = pin.cy()
|
||||
in_pin = "in_{}".format(num)
|
||||
a_pin = "A_{}".format(num)
|
||||
in_pos = vector(self.input_rails[in_pin].x, y_offset)
|
||||
a_pos = vector(self.decode_rails[a_pin].x, y_offset)
|
||||
self.add_path("m1", [in_pos, a_pos])
|
||||
self.add_via_stack_center(from_layer="m1",
|
||||
to_layer="m2",
|
||||
self.add_path(self.input_layer, [in_pos, a_pos])
|
||||
self.add_via_stack_center(from_layer=self.input_layer,
|
||||
to_layer=self.bus_layer,
|
||||
offset=[self.input_rails[in_pin].x, y_offset])
|
||||
self.add_via_stack_center(from_layer="m1",
|
||||
to_layer="m2",
|
||||
self.add_via_stack_center(from_layer=self.input_layer,
|
||||
to_layer=self.bus_layer,
|
||||
offset=[self.decode_rails[a_pin].x, y_offset])
|
||||
|
||||
def route_output_and(self):
|
||||
@@ -188,31 +234,47 @@ class hierarchical_predecode(design.design):
|
||||
for inv_num in range(self.number_of_inputs):
|
||||
out_pin = "Abar_{}".format(inv_num)
|
||||
in_pin = "in_{}".format(inv_num)
|
||||
|
||||
inv_out_pin = self.inv_inst[inv_num].get_pin("Z")
|
||||
inv_out_pos = inv_out_pin.rc()
|
||||
|
||||
# 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 and gates.
|
||||
y_offset = (inv_num + 1) * self.inv.height - 3 * self.m1_space
|
||||
inv_out_pin = self.in_inst[inv_num].get_pin("Z")
|
||||
inv_out_pos = inv_out_pin.rc()
|
||||
right_pos = inv_out_pos + vector(self.inv.width - self.inv.get_pin("Z").lx(), 0)
|
||||
rail_pos = vector(self.decode_rails[out_pin].x, y_offset)
|
||||
self.add_path(inv_out_pin.layer, [inv_out_pos, right_pos, vector(right_pos.x, y_offset), rail_pos])
|
||||
if OPTS.tech_name == "s8":
|
||||
rail_pos = vector(self.decode_rails[out_pin].x, inv_out_pos.y)
|
||||
self.add_path(self.output_layer, [inv_out_pos, rail_pos])
|
||||
else:
|
||||
y_offset = (inv_num + 1) * self.inv.height - self.output_layer_pitch
|
||||
right_pos = inv_out_pos + vector(self.inv.width - self.inv.get_pin("Z").rx(), 0)
|
||||
rail_pos = vector(self.decode_rails[out_pin].x, y_offset)
|
||||
self.add_path(self.output_layer, [inv_out_pos, right_pos, vector(right_pos.x, y_offset), rail_pos])
|
||||
|
||||
self.add_via_stack_center(from_layer=inv_out_pin.layer,
|
||||
to_layer="m2",
|
||||
offset=rail_pos)
|
||||
to_layer=self.output_layer,
|
||||
offset=inv_out_pos)
|
||||
self.add_via_stack_center(from_layer=self.output_layer,
|
||||
to_layer=self.bus_layer,
|
||||
offset=rail_pos,
|
||||
directions=self.bus_directions)
|
||||
|
||||
# route input
|
||||
pin = self.in_inst[inv_num].get_pin("A")
|
||||
inv_in_pos = pin.lc()
|
||||
pin = self.inv_inst[inv_num].get_pin("A")
|
||||
inv_in_pos = pin.center()
|
||||
in_pos = vector(self.input_rails[in_pin].x, inv_in_pos.y)
|
||||
self.add_path("m1", [in_pos, inv_in_pos])
|
||||
self.add_path(self.input_layer, [in_pos, inv_in_pos])
|
||||
self.add_via_stack_center(from_layer=pin.layer,
|
||||
to_layer="m1",
|
||||
to_layer=self.input_layer,
|
||||
offset=inv_in_pos)
|
||||
self.add_via_stack_center(from_layer="m1",
|
||||
to_layer="m2",
|
||||
offset=in_pos)
|
||||
via=self.add_via_stack_center(from_layer=self.input_layer,
|
||||
to_layer=self.bus_layer,
|
||||
offset=in_pos)
|
||||
# Create the input pin at this location on the rail
|
||||
self.add_layout_pin_rect_center(text=in_pin,
|
||||
layer=self.bus_layer,
|
||||
offset=in_pos,
|
||||
height=via.mod.second_layer_height,
|
||||
width=via.mod.second_layer_width)
|
||||
|
||||
def route_and_to_rails(self):
|
||||
# This 2D array defines the connection mapping
|
||||
@@ -231,43 +293,67 @@ class hierarchical_predecode(design.design):
|
||||
pin = self.and_inst[k].get_pin(gate_pin)
|
||||
pin_pos = pin.center()
|
||||
rail_pos = vector(self.decode_rails[rail_pin].x, pin_pos.y)
|
||||
self.add_path("m1", [rail_pos, pin_pos])
|
||||
self.add_via_stack_center(from_layer="m1",
|
||||
to_layer="m2",
|
||||
offset=rail_pos)
|
||||
self.add_path(self.input_layer, [rail_pos, pin_pos])
|
||||
self.add_via_stack_center(from_layer=self.input_layer,
|
||||
to_layer=self.bus_layer,
|
||||
offset=rail_pos,
|
||||
directions=self.bus_directions)
|
||||
if gate_pin == "A":
|
||||
direction = None
|
||||
else:
|
||||
direction = ("H", "H")
|
||||
|
||||
self.add_via_stack_center(from_layer=pin.layer,
|
||||
to_layer="m1",
|
||||
to_layer=self.input_layer,
|
||||
offset=pin_pos,
|
||||
directions=direction)
|
||||
|
||||
def route_vdd_gnd(self):
|
||||
""" Add a pin for each row of vdd/gnd which are must-connects next level up. """
|
||||
|
||||
# Find the x offsets for where the vias/pins should be placed
|
||||
in_xoffset = self.in_inst[0].rx() + self.m1_space
|
||||
# out_xoffset = self.and_inst[0].cx() + self.m1_space
|
||||
for num in range(0, self.number_of_outputs):
|
||||
# this will result in duplicate polygons for rails, but who cares
|
||||
|
||||
# Route both supplies
|
||||
# In s8, we use hand-made decoder cells with vertical power
|
||||
if OPTS.tech_name == "s8":
|
||||
for n in ["vdd", "gnd"]:
|
||||
and_pin = self.and_inst[num].get_pin(n)
|
||||
supply_offset = and_pin.ll().scale(0, 1)
|
||||
self.add_rect(layer=and_pin.layer,
|
||||
offset=supply_offset,
|
||||
width=self.and_inst[num].rx())
|
||||
# This makes a wire from top to bottom for both inv and and gates
|
||||
for i in [self.inv_inst, self.and_inst]:
|
||||
bot_pins = i[0].get_pins(n)
|
||||
top_pins = i[-1].get_pins(n)
|
||||
for (bot_pin, top_pin) in zip(bot_pins, top_pins):
|
||||
self.add_rect(layer=bot_pin.layer,
|
||||
offset=vector(bot_pin.lx(), self.bus_pitch),
|
||||
width=bot_pin.width(),
|
||||
height=top_pin.uy() - self.bus_pitch)
|
||||
# This adds power vias at the top of each cell
|
||||
# (except the last to keep them inside the boundary)
|
||||
for i in self.inv_inst[:-1:2] + self.and_inst[:-1:2]:
|
||||
pins = i.get_pins(n)
|
||||
for pin in pins:
|
||||
self.add_power_pin(name=n,
|
||||
loc=pin.uc(),
|
||||
start_layer=pin.layer)
|
||||
self.add_power_pin(name=n,
|
||||
loc=pin.uc(),
|
||||
start_layer=pin.layer)
|
||||
|
||||
# In other techs, we are using standard cell decoder cells with horizontal power
|
||||
else:
|
||||
for num in range(0, self.number_of_outputs):
|
||||
|
||||
# Add pins in two locations
|
||||
for xoffset in [in_xoffset]:
|
||||
pin_pos = vector(xoffset, and_pin.cy())
|
||||
self.add_power_pin(name=n,
|
||||
loc=pin_pos,
|
||||
start_layer=and_pin.layer)
|
||||
# Route both supplies
|
||||
for n in ["vdd", "gnd"]:
|
||||
and_pins = self.and_inst[num].get_pins(n)
|
||||
for and_pin in and_pins:
|
||||
self.add_segment_center(layer=and_pin.layer,
|
||||
start=vector(0, and_pin.cy()),
|
||||
end=vector(self.width, and_pin.cy()))
|
||||
|
||||
# Add pins in two locations
|
||||
for xoffset in [self.inv_inst[0].lx() - self.bus_space,
|
||||
self.and_inst[0].lx() - self.bus_space]:
|
||||
pin_pos = vector(xoffset, and_pin.cy())
|
||||
self.add_power_pin(name=n,
|
||||
loc=pin_pos,
|
||||
start_layer=and_pin.layer)
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user