Merge branch 'dev' into bisr

This commit is contained in:
Aditi Sinha 2020-05-13 22:39:29 +00:00
commit a5c211bd90
53 changed files with 1540 additions and 1306 deletions

View File

@ -47,8 +47,16 @@ class contact(hierarchy_design.hierarchy_design):
self.layer_stack = layer_stack
self.dimensions = dimensions
if directions:
# Non-preferred directions
if directions == "nonpref":
first_dir = "H" if self.get_preferred_direction(layer_stack[0])=="V" else "V"
second_dir = "H" if self.get_preferred_direction(layer_stack[2])=="V" else "V"
self.directions = (first_dir, second_dir)
# User directions
elif directions:
self.directions = directions
# Preferred directions
else:
self.directions = (tech.preferred_directions[layer_stack[0]],
tech.preferred_directions[layer_stack[2]])

View File

@ -6,6 +6,7 @@
# All rights reserved.
#
from hierarchy_design import hierarchy_design
from utils import round_to_grid
import contact
from globals import OPTS
import re
@ -31,48 +32,98 @@ class design(hierarchy_design):
in many places in the compiler.
"""
from tech import layer_indices
import tech
for key in dir(tech):
# Single layer width rules
match = re.match(r".*_stack$", key)
if match:
for layer in layer_indices:
key = "{}_stack".format(layer)
# Set the stack as a local helper
try:
layer_stack = getattr(tech, key)
# Set the stack as a local helper
setattr(self, key, layer_stack)
except AttributeError:
pass
# Add the pitch
setattr(self,
"{}_pitch".format(layer_stack[0]),
self.compute_pitch(layer_stack))
# Skip computing the pitch for active
if layer == "active":
continue
# Add the pitch
setattr(self,
"{}_pitch".format(layer),
self.compute_pitch(layer, True))
# Add the non-preferrd pitch (which has vias in the "wrong" way)
setattr(self,
"{}_nonpref_pitch".format(layer),
self.compute_pitch(layer, False))
if False:
print("m1_pitch", self.m1_pitch)
print("m2_pitch", self.m2_pitch)
print("m3_pitch", self.m3_pitch)
from tech import preferred_directions
print(preferred_directions)
from tech import layer, layer_indices
for name in layer_indices:
if name == "active":
continue
try:
print("{0} width {1} space {2}".format(name,
getattr(self, "{}_width".format(name)),
getattr(self, "{}_space".format(name))))
print("pitch {0} nonpref {1}".format(getattr(self, "{}_pitch".format(name)),
getattr(self, "{}_nonpref_pitch".format(name))))
except AttributeError:
pass
import sys
sys.exit(1)
def compute_pitch(self, layer_stack):
def compute_pitch(self, layer, preferred=True):
"""
This is contact direction independent pitch,
i.e. we take the maximum contact dimension
This is the preferred direction pitch
i.e. we take the minimum or maximum contact dimension
"""
# Find the layer stacks this is used in
from tech import layer_stacks
pitches = []
for stack in layer_stacks:
# Compute the pitch with both vias above and below (if they exist)
if stack[0] == layer:
pitches.append(self.compute_layer_pitch(stack, preferred))
if stack[2] == layer:
pitches.append(self.compute_layer_pitch(stack[::-1], True))
return max(pitches)
def compute_layer_pitch(self, layer_stack, preferred):
(layer1, via, layer2) = layer_stack
try:
if layer1 == "poly" or layer1 == "active":
contact1 = getattr(contact, layer1 + "_contact")
else:
contact1 = getattr(contact, layer1 + "_via")
except AttributeError:
contact1 = getattr(contact, layer2 + "_via")
if layer1 == "poly" or layer1 == "active":
contact1 = getattr(contact, layer1 + "_contact")
if preferred:
if self.get_preferred_direction(layer1) == "V":
contact_width = contact1.first_layer_width
else:
contact_width = contact1.first_layer_height
else:
contact1 = getattr(contact, layer1 + "_via")
max_contact = max(contact1.width, contact1.height)
layer1_space = getattr(self, layer1 + "_space")
layer2_space = getattr(self, layer2 + "_space")
pitch = max_contact + max(layer1_space, layer2_space)
if self.get_preferred_direction(layer1) == "V":
contact_width = contact1.first_layer_height
else:
contact_width = contact1.first_layer_width
layer_space = getattr(self, layer1 + "_space")
return pitch
#print(layer_stack)
#print(contact1)
pitch = contact_width + layer_space
return round_to_grid(pitch)
def setup_drc_constants(self):
"""
These are some DRC constants used in many places

View File

@ -12,6 +12,7 @@ import debug
from math import sqrt
from tech import drc, GDS
from tech import layer as techlayer
from tech import layer_indices
from tech import layer_stacks
import os
from globals import OPTS
@ -41,12 +42,15 @@ class layout():
self.visited = [] # List of modules we have already visited
self.is_library_cell = False # Flag for library cells
self.gds_read()
try:
from tech import power_grid
self.pwr_grid_layer = power_grid[0]
except ImportError:
self.pwr_grid_layer = "m3"
############################################################
# GDS layout
############################################################
@ -444,7 +448,40 @@ class layout():
path=coordinates,
layer_widths=layer_widths)
def add_wire(self, layers, coordinates):
def add_zjog(self, layer, start, end, first_direction="H"):
"""
Add a simple jog at the halfway point.
If layer is a single value, it is a path.
If layer is a tuple, it is a wire with preferred directions.
"""
# vertical first
if first_direction == "V":
mid1 = vector(start.x, 0.5 * start.y + 0.5 * end.y)
mid2 = vector(end.x, mid1.y)
# horizontal first
elif first_direction == "H":
mid1 = vector(0.5 * start.x + 0.5 * end.x, start.y)
mid2 = vector(mid1, end.y)
else:
debug.error("Invalid direction for jog -- must be H or V.")
if layer in layer_stacks:
self.add_wire(layer, [start, mid1, mid2, end])
elif layer in techlayer:
self.add_path(layer, [start, mid1, mid2, end])
else:
debug.error("Could not find layer {}".format(layer))
def add_horizontal_zjog_path(self, layer, start, end):
""" Add a simple jog at the halfway point """
# horizontal first
mid1 = vector(0.5 * start.x + 0.5 * end.x, start.y)
mid2 = vector(mid1, end.y)
self.add_path(layer, [start, mid1, mid2, end])
def add_wire(self, layers, coordinates, widen_short_wires=True):
"""Connects a routing path on given layer,coordinates,width.
The layers are the (horizontal, via, vertical). """
import wire
@ -452,7 +489,8 @@ class layout():
# into rectangles and contacts
wire.wire(obj=self,
layer_stack=layers,
position_list=coordinates)
position_list=coordinates,
widen_short_wires=widen_short_wires)
def get_preferred_direction(self, layer):
""" Return the preferred routing directions """
@ -461,11 +499,6 @@ class layout():
def add_via(self, layers, offset, size=[1, 1], directions=None, implant_type=None, well_type=None):
""" Add a three layer via structure. """
if not directions:
directions = (self.get_preferred_direction(layers[0]),
self.get_preferred_direction(layers[2]))
from sram_factory import factory
via = factory.create(module_type="contact",
layer_stack=layers,
@ -486,11 +519,6 @@ class layout():
Add a three layer via structure by the center coordinate
accounting for mirroring and rotation.
"""
if not directions:
directions = (self.get_preferred_direction(layers[0]),
self.get_preferred_direction(layers[2]))
from sram_factory import factory
via = factory.create(module_type="contact",
layer_stack=layers,
@ -513,36 +541,47 @@ class layout():
return inst
def add_via_stack(self, offset, from_layer, to_layer,
direction=None,
size=[1, 1]):
directions=None,
size=[1, 1],
implant_type=None,
well_type=None):
"""
Punch a stack of vias from a start layer to a target layer.
"""
return self.__add_via_stack_internal(offset=offset,
direction=direction,
directions=directions,
from_layer=from_layer,
to_layer=to_layer,
via_func=self.add_via,
last_via=None,
size=size)
size=size,
implant_type=implant_type,
well_type=well_type)
def add_via_stack_center(self, offset, from_layer, to_layer,
direction=None,
size=[1, 1]):
def add_via_stack_center(self,
offset,
from_layer,
to_layer,
directions=None,
size=[1, 1],
implant_type=None,
well_type=None):
"""
Punch a stack of vias from a start layer to a target layer by the center
coordinate accounting for mirroring and rotation.
"""
return self.__add_via_stack_internal(offset=offset,
direction=direction,
directions=directions,
from_layer=from_layer,
to_layer=to_layer,
via_func=self.add_via_center,
last_via=None,
size=size)
size=size,
implant_type=implant_type,
well_type=well_type)
def __add_via_stack_internal(self, offset, direction, from_layer, to_layer,
via_func, last_via, size):
def __add_via_stack_internal(self, offset, directions, from_layer, to_layer,
via_func, last_via, size, implant_type=None, well_type=None):
"""
Punch a stack of vias from a start layer to a target layer. Here we
figure out whether to punch it up or down the stack.
@ -551,8 +590,8 @@ class layout():
if from_layer == to_layer:
return last_via
from_id = int(from_layer[1])
to_id = int(to_layer[1])
from_id = layer_indices[from_layer]
to_id = layer_indices[to_layer]
if from_id < to_id: # grow the stack up
search_id = 0
@ -563,19 +602,25 @@ class layout():
curr_stack = next(filter(lambda stack: stack[search_id] == from_layer, layer_stacks), None)
if curr_stack is None:
raise ValueError("Cannot create via from '{0}' to '{1}'." \
"Layer '{0}' not defined"
.format(from_layer, to_layer))
raise ValueError("Cannot create via from '{0}' to '{1}'."
"Layer '{0}' not defined".format(from_layer, to_layer))
via = via_func(layers=curr_stack, size=size, offset=offset, directions=direction)
return self.__add_via_stack_internal(offset=offset,
direction=direction,
from_layer=curr_stack[next_id],
to_layer=to_layer,
via_func=via_func,
last_via=via,
size=size)
via = via_func(layers=curr_stack,
size=size,
offset=offset,
directions=directions,
implant_type=implant_type,
well_type=well_type)
via = self.__add_via_stack_internal(offset=offset,
directions=directions,
from_layer=curr_stack[next_id],
to_layer=to_layer,
via_func=via_func,
last_via=via,
size=size)
return via
def add_ptx(self, offset, mirror="R0", rotate=0, width=1, mults=1, tx_type="nmos"):
"""Adds a ptx module to the design."""
import ptx
@ -729,47 +774,47 @@ class layout():
return blockages
def create_horizontal_pin_bus(self, layer, pitch, offset, names, length):
def create_horizontal_pin_bus(self, layer, offset, names, length, pitch=None):
""" Create a horizontal bus of pins. """
return self.create_bus(layer,
pitch,
offset,
names,
length,
vertical=False,
make_pins=True)
make_pins=True,
pitch=pitch)
def create_vertical_pin_bus(self, layer, pitch, offset, names, length):
def create_vertical_pin_bus(self, layer, offset, names, length, pitch=None):
""" Create a horizontal bus of pins. """
return self.create_bus(layer,
pitch,
offset,
names,
length,
vertical=True,
make_pins=True)
make_pins=True,
pitch=pitch)
def create_vertical_bus(self, layer, pitch, offset, names, length):
def create_vertical_bus(self, layer, offset, names, length, pitch=None):
""" Create a horizontal bus. """
return self.create_bus(layer,
pitch,
offset,
names,
length,
vertical=True,
make_pins=False)
make_pins=False,
pitch=pitch)
def create_horizontal_bus(self, layer, pitch, offset, names, length):
def create_horizontal_bus(self, layer, offset, names, length, pitch=None):
""" Create a horizontal bus. """
return self.create_bus(layer,
pitch,
offset,
names,
length,
vertical=False,
make_pins=False)
make_pins=False,
pitch=pitch)
def create_bus(self, layer, pitch, offset, names, length, vertical, make_pins):
def create_bus(self, layer, offset, names, length, vertical, make_pins, pitch=None):
"""
Create a horizontal or vertical bus. It can be either just rectangles, or actual
layout pins. It returns an map of line center line positions indexed by name.
@ -779,11 +824,14 @@ class layout():
# half minwidth so we can return the center line offsets
half_minwidth = 0.5 * drc["minwidth_{}".format(layer)]
if not pitch:
pitch = getattr(self, "{}_pitch".format(layer))
line_positions = {}
if vertical:
for i in range(len(names)):
line_offset = offset + vector(i * pitch, 0)
line_offset = offset + vector(i * pitch,
0)
if make_pins:
self.add_layout_pin(text=names[i],
layer=layer,
@ -848,8 +896,10 @@ class layout():
# left/right then up/down
mid_pos = vector(bus_pos.x, pin_pos.y)
# Don't widen short wires because pin_pos and mid_pos could be really close
self.add_wire(layer_stack,
[bus_pos, mid_pos, pin_pos])
[bus_pos, mid_pos, pin_pos],
widen_short_wires=False)
# Connect to the pin on the instances with a via if it is
# not on the right layer
@ -866,33 +916,76 @@ class layout():
offset=bus_pos,
rotate=90)
def connect_vbus(self, src_pin, dest_pin, hlayer="m3", vlayer="m2"):
"""
Helper routine to connect an instance to a vertical bus.
Routes horizontal then vertical L shape.
"""
if src_pin.cx()<dest_pin.cx():
in_pos = src_pin.rc()
else:
in_pos = src_pin.lc()
if src_pin.cy() < dest_pin.cy():
out_pos = dest_pin.bc()
else:
out_pos = dest_pin.uc()
# move horizontal first on layer stack
mid_pos = vector(out_pos.x, in_pos.y)
self.add_via_stack_center(from_layer=src_pin.layer,
to_layer=hlayer,
offset=in_pos)
self.add_path(hlayer, [in_pos, mid_pos])
self.add_via_stack_center(from_layer=hlayer,
to_layer=vlayer,
offset=mid_pos)
self.add_path(vlayer, [mid_pos, out_pos])
self.add_via_stack_center(from_layer=vlayer,
to_layer=dest_pin.layer,
offset=out_pos)
def connect_hbus(self, src_pin, dest_pin, hlayer="m3", vlayer="m2"):
"""
Helper routine to connect an instance to a horizontal bus.
Routes horizontal then vertical L shape.
"""
if src_pin.cx()<dest_pin.cx():
in_pos = src_pin.rc()
else:
in_pos = src_pin.lc()
if src_pin.cy() < dest_pin.cy():
out_pos = dest_pin.lc()
else:
out_pos = dest_pin.rc()
# move horizontal first
mid_pos = vector(out_pos.x, in_pos.y)
self.add_via_stack_center(from_layer=src_pin.layer,
to_layer=hlayer,
offset=in_pos)
self.add_path(hlayer, [in_pos, mid_pos])
self.add_via_stack_center(from_layer=hlayer,
to_layer=vlayer,
offset=mid_pos)
self.add_path(vlayer, [mid_pos, out_pos])
self.add_via_stack_center(from_layer=vlayer,
to_layer=dest_pin.layer,
offset=out_pos)
def get_layer_pitch(self, layer):
""" Return the track pitch on a given layer """
if layer == "m1":
return (self.m1_pitch,
self.m1_pitch - self.m1_space,
self.m1_space)
elif layer == "m2":
return (self.m2_pitch,
self.m2_pitch - self.m2_space,
self.m2_space)
elif layer == "m3":
return (self.m3_pitch,
self.m3_pitch - self.m3_space,
self.m3_space)
elif layer == "m4":
from tech import layer as tech_layer
if "m4" in tech_layer:
return (self.m3_pitch,
self.m3_pitch - self.m4_space,
self.m4_space)
else:
return (self.m3_pitch,
self.m3_pitch - self.m3_space,
self.m3_space)
else:
debug.error("Cannot find layer pitch.")
try:
# FIXME: Using non-pref pitch here due to overlap bug in VCG constraints.
# It should just result in inefficient channel width but will work.
pitch = getattr(self, "{}_pitch".format(layer))
nonpref_pitch = getattr(self, "{}_nonpref_pitch".format(layer))
space = getattr(self, "{}_space".format(layer))
except AttributeError:
debug.error("Cannot find layer pitch.", -1)
return (nonpref_pitch, pitch, pitch - space, space)
def add_horizontal_trunk_route(self,
pins,
trunk_offset,
@ -906,7 +999,7 @@ class layout():
min_x = min([pin.center().x for pin in pins])
# if we are less than a pitch, just create a non-preferred layer jog
if max_x-min_x <= pitch:
if max_x - min_x <= pitch:
half_layer_width = 0.5 * drc["minwidth_{0}".format(self.vertical_layer)]
# Add the horizontal trunk on the vertical layer!
@ -975,14 +1068,15 @@ class layout():
def create_channel_route(self, netlist,
offset,
layer_stack,
layer_dirs=None,
directions=None,
vertical=False):
"""
The net list is a list of the nets. Each net is a list of pins
to be connected. Offset is the lower-left of where the
The net list is a list of the nets with each net being a list of pins
to be connected. The offset is the lower-left of where the
routing channel will start. This does NOT try to minimize the
number of tracks -- instead, it picks an order to avoid the
vertical conflicts between pins.
vertical conflicts between pins. The track size must be the number of
nets times the *nonpreferred* routing of the non-track layer pitch.
"""
def remove_net_from_graph(pin, g):
@ -999,12 +1093,17 @@ class layout():
g[other_pin]=conflicts
return g
def vcg_nets_overlap(net1, net2, vertical, pitch):
def vcg_nets_overlap(net1, net2, vertical):
"""
Check all the pin pairs on two nets and return a pin
overlap if any pin overlaps.
"""
if vertical:
pitch = self.horizontal_nonpref_pitch
else:
pitch = self.vertical_nonpref_pitch
for pin1 in net1:
for pin2 in net2:
if vcg_pin_overlap(pin1, pin2, vertical, pitch):
@ -1017,8 +1116,7 @@ class layout():
# FIXME: If the pins are not in a row, this may break.
# However, a top pin shouldn't overlap another top pin,
# for example, so the
# extra comparison *shouldn't* matter.
# for example, so the extra comparison *shouldn't* matter.
# Pin 1 must be in the "BOTTOM" set
x_overlap = pin1.by() < pin2.by() and abs(pin1.center().x - pin2.center().x) < pitch
@ -1028,7 +1126,7 @@ class layout():
overlaps = (not vertical and x_overlap) or (vertical and y_overlap)
return overlaps
if not layer_dirs:
if not directions:
# Use the preferred layer directions
if self.get_preferred_direction(layer_stack[0]) == "V":
self.vertical_layer = layer_stack[0]
@ -1039,8 +1137,8 @@ class layout():
else:
# Use the layer directions specified to the router rather than
# the preferred directions
debug.check(layer_dirs[0] != layer_dirs[1], "Must have unique layer directions.")
if layer_dirs[0] == "V":
debug.check(directions[0] != directions[1], "Must have unique layer directions.")
if directions[0] == "V":
self.vertical_layer = layer_stack[0]
self.horizontal_layer = layer_stack[2]
else:
@ -1048,9 +1146,10 @@ class layout():
self.vertical_layer = layer_stack[2]
layer_stuff = self.get_layer_pitch(self.vertical_layer)
(self.vertical_pitch, self.vertical_width, self.vertical_space) = layer_stuff
(self.vertical_nonpref_pitch, self.vertical_pitch, self.vertical_width, self.vertical_space) = layer_stuff
layer_stuff = self.get_layer_pitch(self.horizontal_layer)
(self.horizontal_pitch, self.horizontal_width, self.horizontal_space) = layer_stuff
(self.horizontal_nonpref_pitch, self.horizontal_pitch, self.horizontal_width, self.horizontal_space) = layer_stuff
# FIXME: Must extend this to a horizontal conflict graph
# too if we want to minimize the
@ -1070,6 +1169,10 @@ class layout():
index += 1
nets[net_name] = pin_list
# print("Nets:")
# for net_name in nets:
# print(net_name, [x.name for x in nets[net_name]])
# Find the vertical pin conflicts
# FIXME: O(n^2) but who cares for now
for net_name1 in nets:
@ -1081,21 +1184,15 @@ class layout():
# Skip yourself
if net_name1 == net_name2:
continue
if vertical and vcg_nets_overlap(nets[net_name1],
nets[net_name2],
vertical,
self.vertical_pitch):
vcg[net_name2].append(net_name1)
elif not vertical and vcg_nets_overlap(nets[net_name1],
nets[net_name2],
vertical,
self.horizontal_pitch):
if vcg_nets_overlap(nets[net_name1],
nets[net_name2],
vertical):
vcg[net_name2].append(net_name1)
# list of routes to do
while vcg:
# from pprint import pformat
# print("VCG:\n",pformat(vcg))
# print("VCG:\n", pformat(vcg))
# get a route from conflict graph with empty fanout set
net_name = None
for net_name, conflicts in vcg.items():
@ -1119,26 +1216,28 @@ class layout():
self.add_vertical_trunk_route(pin_list,
offset,
layer_stack,
self.vertical_pitch)
offset += vector(self.vertical_pitch, 0)
self.vertical_nonpref_pitch)
# This accounts for the via-to-via spacings
offset += vector(self.horizontal_nonpref_pitch, 0)
else:
self.add_horizontal_trunk_route(pin_list,
offset,
layer_stack,
self.horizontal_pitch)
offset += vector(0, self.horizontal_pitch)
self.horizontal_nonpref_pitch)
# This accounts for the via-to-via spacings
offset += vector(0, self.vertical_nonpref_pitch)
def create_vertical_channel_route(self, netlist, offset, layer_stack, layer_dirs=None):
def create_vertical_channel_route(self, netlist, offset, layer_stack, directions=None):
"""
Wrapper to create a vertical channel route
"""
self.create_channel_route(netlist, offset, layer_stack, layer_dirs, vertical=True)
self.create_channel_route(netlist, offset, layer_stack, directions, vertical=True)
def create_horizontal_channel_route(self, netlist, offset, layer_stack, layer_dirs=None):
def create_horizontal_channel_route(self, netlist, offset, layer_stack, directions=None):
"""
Wrapper to create a horizontal channel route
"""
self.create_channel_route(netlist, offset, layer_stack, layer_dirs, vertical=False)
self.create_channel_route(netlist, offset, layer_stack, directions, vertical=False)
def add_boundary(self, ll=vector(0, 0), ur=None):
""" Add boundary for debugging dimensions """
@ -1200,27 +1299,18 @@ class layout():
"supply router."
.format(name, inst.name, self.pwr_grid_layer))
def add_power_pin(self, name, loc, size=[1, 1], vertical=False, start_layer="m1"):
def add_power_pin(self, name, loc, size=[1, 1], directions=None, start_layer="m1"):
"""
Add a single power pin from the lowest power_grid layer down to M1 (or li) at
the given center location. The starting layer is specified to determine
which vias are needed.
"""
# Force vdd/gnd via stack to be vertically or horizontally oriented
# Default: None, uses prefered metal directions
if vertical:
direction = ("V", "V")
elif not vertical and vertical is not None:
direction = ("H", "H")
else:
direction = None
via = self.add_via_stack_center(from_layer=start_layer,
to_layer=self.pwr_grid_layer,
size=size,
offset=loc,
direction=direction)
directions=directions)
if start_layer == self.pwr_grid_layer:
self.add_layout_pin_rect_center(text=name,
layer=self.pwr_grid_layer,

View File

@ -19,11 +19,14 @@ class wire(wire_path):
not, it will always go down first.
The points are the center of the wire.
The layer stack is the vertical, contact/via, and horizontal layers, respectively.
The widen option will avoid via-to-via spacing problems for really short segments
(added as an option so we can disable it in bus connections)
"""
def __init__(self, obj, layer_stack, position_list):
def __init__(self, obj, layer_stack, position_list, widen_short_wires=True):
self.obj = obj
self.layer_stack = layer_stack
self.position_list = position_list
self.widen_short_wires = widen_short_wires
self.pins = [] # used for matching parm lengths
self.switch_pos_list = []
@ -114,7 +117,7 @@ class wire(wire_path):
line_length = pl[index + 1][0] - pl[index][0]
# Make the wire wider to avoid via-to-via spacing problems
# But don't make it wider if it is shorter than one via
if abs(line_length) < self.pitch and abs(line_length) > self.horiz_layer_contact_width:
if self.widen_short_wires and abs(line_length) < self.pitch and abs(line_length) > self.horiz_layer_contact_width:
width = self.horiz_layer_contact_width
else:
width = self.horiz_layer_width
@ -134,7 +137,7 @@ class wire(wire_path):
line_length = pl[index + 1][1] - pl[index][1]
# Make the wire wider to avoid via-to-via spacing problems
# But don't make it wider if it is shorter than one via
if abs(line_length) < self.pitch and abs(line_length) > self.vert_layer_contact_width:
if self.widen_short_wires and abs(line_length) < self.pitch and abs(line_length) > self.vert_layer_contact_width:
width = self.vert_layer_contact_width
else:
width = self.vert_layer_width

View File

@ -24,11 +24,12 @@ def create_rectilinear_route(my_list):
my_list.append(vector(pl[index][0], pl[index + 1][1]))
my_list.append(vector(pl[-1]))
return my_list
class wire_path():
"""
Object metal wire_path; given the layer type
Add a wire_path of minimium metal width between a set of points.
Add a wire_path of minimium metal width between a set of points.
The points should be rectilinear to control the bend points. If
not, it will always go down first. The points are the center of the wire_path.
If width is not given, it uses minimum layer width.
@ -37,7 +38,7 @@ class wire_path():
self.obj = obj
self.layer_name = layer
self.layer_id = techlayer[layer]
if width==None:
if width == None:
self.layer_width = drc["minwidth_{0}".format(layer)]
else:
self.layer_width = width
@ -46,7 +47,6 @@ class wire_path():
self.switch_pos_list = []
self.create_layout()
def create_layout(self):
self.create_rectilinear()
self.connect_corner()
@ -60,9 +60,9 @@ class wire_path():
def connect_corner(self):
""" Add a corner square at every corner of the wire_path."""
from itertools import tee,islice
nwise = lambda g,n=2: zip(*(islice(g,i,None) for i,g in enumerate(tee(g,n))))
threewise=nwise(self.position_list,3)
from itertools import tee, islice
nwise = lambda g, n=2: zip(*(islice(g, i, None) for i, g in enumerate(tee(g, n))))
threewise=nwise(self.position_list, 3)
for (a, offset, c) in list(threewise):
# add a exceptions to prevent a corner when we retrace back in the same direction
@ -74,7 +74,6 @@ class wire_path():
offset[1] - 0.5 * self.layer_width]
self.draw_corner_wire(corner_offset)
def draw_corner_wire(self, offset):
""" This function adds the corner squares since the center
line convention only draws to the center of the corner."""
@ -117,7 +116,7 @@ class wire_path():
def add_line(self, layer_name, length, offset, orientation, layer_width):
"""
straight line object with layer_minwidth
straight line object with layer_minwidth
(orientation: "vertical" or "horizontal") default is vertical
"""

View File

@ -50,7 +50,7 @@ class bitcell(bitcell_base.bitcell_base):
self.pin_map = bitcell.pin_map
self.add_pin_types(self.type_list)
self.nets_match = self.do_nets_exist(self.storage_nets)
def get_all_wl_names(self):
""" Creates a list of all wordline pin names """
if props.bitcell.split_wl:

View File

@ -264,7 +264,7 @@ class pbitcell(bitcell_base.bitcell_base):
+ 2 * self.implant_enclose_active \
+ 0.5*(self.inverter_pmos.active_contact.height - self.m1_width)
metal1_constraint = max(inverter_pmos_contact_extension, 0) + self.m1_space
self.vdd_offset = max(implant_constraint, metal1_constraint) + 0.5*self.m1_width
self.vdd_offset = max(implant_constraint, metal1_constraint) + self.m1_width
# read port dimensions
width_reduction = self.read_nmos.active_width - self.read_nmos.get_pin("D").cx()
@ -275,7 +275,7 @@ class pbitcell(bitcell_base.bitcell_base):
Calculate positions that describe the edges
and dimensions of the cell
"""
self.botmost_ypos = self.m1_offset - self.total_ports * self.m1_pitch
self.botmost_ypos = self.m1_offset - self.total_ports * self.m1_nonpref_pitch
self.topmost_ypos = self.inverter_nmos_ypos \
+ self.inverter_nmos.active_height \
+ self.inverter_gap \
@ -378,14 +378,15 @@ class pbitcell(bitcell_base.bitcell_base):
+ 0.5 * contact.poly_contact.height,
self.cross_couple_upper_ypos)
self.add_via_center(layers=self.poly_stack,
offset=contact_offset_left)
offset=contact_offset_left,
directions=("H", "H"))
contact_offset_right = vector(self.inverter_nmos_right.get_pin("S").lc().x \
- 0.5*contact.poly_contact.height,
self.cross_couple_lower_ypos)
self.add_via_center(layers=self.poly_stack,
offset=contact_offset_right)
offset=contact_offset_right,
directions=("H", "H"))
# connect contacts to gate poly (cross couple connections)
gate_offset_right = vector(self.inverter_nmos_right.get_pin("G").lc().x,
@ -399,12 +400,12 @@ class pbitcell(bitcell_base.bitcell_base):
def route_rails(self):
""" Adds gnd and vdd rails and connects them to the inverters """
# Add rails for vdd and gnd
gnd_ypos = self.m1_offset - self.total_ports * self.m1_pitch
gnd_ypos = self.m1_offset - self.total_ports * self.m1_nonpref_pitch
self.gnd_position = vector(0, gnd_ypos)
self.add_rect_center(layer="m1",
offset=self.gnd_position,
width=self.width)
self.add_power_pin("gnd", vector(0, gnd_ypos))
self.add_power_pin("gnd", vector(0, gnd_ypos), directions=("H", "H"))
vdd_ypos = self.inverter_nmos_ypos \
@ -416,7 +417,7 @@ class pbitcell(bitcell_base.bitcell_base):
self.add_rect_center(layer="m1",
offset=self.vdd_position,
width=self.width)
self.add_power_pin("vdd", vector(0, vdd_ypos))
self.add_power_pin("vdd", vector(0, vdd_ypos), directions=("H", "H"))
def create_readwrite_ports(self):
"""
@ -483,7 +484,7 @@ class pbitcell(bitcell_base.bitcell_base):
self.port_ypos])
# add pin for RWWL
rwwl_ypos = self.m1_offset - k * self.m1_pitch
rwwl_ypos = self.m1_offset - k * self.m1_nonpref_pitch
self.rwwl_positions[k] = vector(0, rwwl_ypos)
self.add_layout_pin_rect_center(text=self.rw_wl_names[k],
layer="m1",
@ -579,8 +580,8 @@ class pbitcell(bitcell_base.bitcell_base):
# add pin for WWL
wwl_ypos = rwwl_ypos = self.m1_offset \
- self.num_rw_ports * self.m1_pitch \
- k * self.m1_pitch
- self.num_rw_ports * self.m1_nonpref_pitch \
- k * self.m1_nonpref_pitch
self.wwl_positions[k] = vector(0, wwl_ypos)
self.add_layout_pin_rect_center(text=self.w_wl_names[k],
layer="m1",
@ -705,9 +706,9 @@ class pbitcell(bitcell_base.bitcell_base):
# add pin for RWL
rwl_ypos = rwwl_ypos = self.m1_offset \
- self.num_rw_ports * self.m1_pitch \
- self.num_w_ports * self.m1_pitch \
- k * self.m1_pitch
- self.num_rw_ports * self.m1_nonpref_pitch \
- self.num_w_ports * self.m1_nonpref_pitch \
- k * self.m1_nonpref_pitch
self.rwl_positions[k] = vector(0, rwl_ypos)
self.add_layout_pin_rect_center(text=self.r_wl_names[k],
layer="m1",
@ -771,6 +772,7 @@ class pbitcell(bitcell_base.bitcell_base):
self.add_via_center(layers=self.poly_stack,
offset=port_contact_offset)
self.add_path("poly", [gate_offset, port_contact_offset])
self.add_path("m1",
[port_contact_offset, wl_contact_offset])
@ -821,7 +823,8 @@ class pbitcell(bitcell_base.bitcell_base):
# Leave bitline disconnected if a dummy cell
if not self.dummy_bitcell:
self.add_via_center(layers=self.m1_stack,
offset=port_contact_offest)
offset=port_contact_offest,
directions="nonpref")
self.add_path("m2",
[port_contact_offest, bl_offset], width=contact.m1_via.height)
@ -833,7 +836,8 @@ class pbitcell(bitcell_base.bitcell_base):
# Leave bitline disconnected if a dummy cell
if not self.dummy_bitcell:
self.add_via_center(layers=self.m1_stack,
offset=port_contact_offest)
offset=port_contact_offest,
directions="nonpref")
self.add_path("m2",
[port_contact_offest, br_offset], width=contact.m1_via.height)
@ -850,7 +854,9 @@ class pbitcell(bitcell_base.bitcell_base):
for position in nmos_contact_positions:
self.add_via_center(layers=self.m1_stack,
offset=position)
offset=position,
directions=("V", "V"))
if position.x > 0:
contact_correct = 0.5 * contact.m1_via.height
@ -859,7 +865,8 @@ class pbitcell(bitcell_base.bitcell_base):
supply_offset = vector(position.x + contact_correct,
self.gnd_position.y)
self.add_via_center(layers=self.m1_stack,
offset=supply_offset)
offset=supply_offset,
directions=("H", "H"))
self.add_path("m2", [position, supply_offset])
@ -924,13 +931,16 @@ class pbitcell(bitcell_base.bitcell_base):
- self.poly_to_contact - 0.5*contact.poly_contact.width,
self.cross_couple_upper_ypos)
self.add_via_center(layers=self.poly_stack,
offset=left_storage_contact)
offset=left_storage_contact,
directions=("H", "H"))
right_storage_contact = vector(self.inverter_nmos_right.get_pin("G").rc().x \
+ self.poly_to_contact + 0.5*contact.poly_contact.width,
self.cross_couple_upper_ypos)
self.add_via_center(layers=self.poly_stack,
offset=right_storage_contact)
offset=right_storage_contact,
directions=("H", "H"))
inverter_gate_offset_left = vector(self.inverter_nmos_left.get_pin("G").lc().x, self.cross_couple_upper_ypos)
self.add_path("poly", [left_storage_contact, inverter_gate_offset_left])
@ -1007,8 +1017,7 @@ class pbitcell(bitcell_base.bitcell_base):
well_height = self.vdd_position.y - inverter_well_ypos \
+ self.nwell_enclose_active + drc["minwidth_tx"]
# FIXME fudge factor xpos
offset = [inverter_well_xpos + 2*self.nwell_enclose_active, inverter_well_ypos]
offset = [inverter_well_xpos, inverter_well_ypos]
self.add_rect(layer="nwell",
offset=offset,
width=well_width,

View File

@ -86,10 +86,10 @@ class bank(design.design):
self.add_pin(self.bitcell_array.get_rbl_bl_name(self.port_rbl_map[port]), "OUTPUT")
for port in self.write_ports:
for bit in range(self.word_size + self.num_spare_cols):
self.add_pin("din{0}_{1}".format(port,bit), "INPUT")
self.add_pin("din{0}_{1}".format(port, bit), "INPUT")
for port in self.all_ports:
for bit in range(self.addr_size):
self.add_pin("addr{0}_{1}".format(port,bit), "INPUT")
self.add_pin("addr{0}_{1}".format(port, bit), "INPUT")
# For more than one bank, we have a bank select and name
# the signals gated_*.
@ -133,16 +133,14 @@ class bank(design.design):
bl_pin = self.bitcell_array_inst.get_pin(bl_pin_name)
# This will ensure the pin is only on the top or bottom edge
if port % 2:
via_offset = bl_pin.uc() + vector(0, self.m2_pitch)
via_offset = bl_pin.uc() + vector(0, 1.5 * self.m2_pitch)
left_right_offset = vector(self.max_x_offset, via_offset.y)
else:
via_offset = bl_pin.bc() - vector(0, self.m2_pitch)
via_offset = bl_pin.bc() - vector(0, 1.5 * self.m2_pitch)
left_right_offset = vector(self.min_x_offset, via_offset.y)
if bl_pin == "m1":
self.add_via_center(layers=self.m1_stack,
offset=via_offset)
self.add_via_center(layers=self.m2_stack,
offset=via_offset)
self.add_via_stack_center(from_layer=bl_pin.layer,
to_layer="m3",
offset=via_offset)
self.add_layout_pin_segment_center(text="rbl_bl{0}".format(port),
layer="m3",
start=left_right_offset,
@ -643,11 +641,10 @@ class bank(design.design):
# Port 0
# The bank is at (0,0), so this is to the left of the y-axis.
# 2 pitches on the right for vias/jogs to access the inputs
control_bus_offset = vector(-self.m2_pitch * self.num_control_lines[0] - self.m2_pitch, self.min_y_offset)
control_bus_offset = vector(-self.m3_pitch * self.num_control_lines[0] - self.m3_pitch, self.min_y_offset)
# The control bus is routed up to two pitches below the bitcell array
control_bus_length = self.main_bitcell_array_bottom - self.min_y_offset - 2 * self.m1_pitch
self.bus_xoffset[0] = self.create_bus(layer="m2",
pitch=self.m2_pitch,
offset=control_bus_offset,
names=self.control_signals[0],
length=control_bus_length,
@ -658,11 +655,10 @@ class bank(design.design):
if len(self.all_ports)==2:
# The other control bus is routed up to two pitches above the bitcell array
control_bus_length = self.max_y_offset - self.main_bitcell_array_top - 2 * self.m1_pitch
control_bus_offset = vector(self.bitcell_array_right + self.m2_pitch,
control_bus_offset = vector(self.bitcell_array_right + self.m3_pitch,
self.max_y_offset - control_bus_length)
# The bus for the right port is reversed so that the rbl_wl is closest to the array
self.bus_xoffset[1] = self.create_bus(layer="m2",
pitch=self.m2_pitch,
offset=control_bus_offset,
names=list(reversed(self.control_signals[1])),
length=control_bus_length,
@ -862,9 +858,9 @@ class bank(design.design):
self.copy_layout_pin(self.column_decoder_inst[port], decoder_name, addr_name)
if port % 2:
offset = self.column_decoder_inst[port].ll() - vector(self.num_col_addr_lines * self.m2_pitch, 0)
offset = self.column_decoder_inst[port].ll() - vector(self.num_col_addr_lines * self.m2_nonpref_pitch, 0)
else:
offset = self.column_decoder_inst[port].lr() + vector(self.m2_pitch, 0)
offset = self.column_decoder_inst[port].lr() + vector(self.m2_nonpref_pitch, 0)
decode_pins = [self.column_decoder_inst[port].get_pin(x) for x in decode_names]
@ -872,7 +868,9 @@ class bank(design.design):
column_mux_pins = [self.port_data_inst[port].get_pin(x) for x in sel_names]
route_map = list(zip(decode_pins, column_mux_pins))
self.create_vertical_channel_route(route_map, offset, self.m1_stack)
self.create_vertical_channel_route(route_map,
offset,
self.m1_stack)
def add_lvs_correspondence_points(self):
"""
@ -928,30 +926,39 @@ class bank(design.design):
# pre-decoder and this connection is in metal3
connection = []
connection.append((self.prefix + "p_en_bar{}".format(port),
self.port_data_inst[port].get_pin("p_en_bar").lc()))
self.port_data_inst[port].get_pin("p_en_bar").lc(),
self.port_data_inst[port].get_pin("p_en_bar").layer))
rbl_wl_name = self.bitcell_array.get_rbl_wl_name(self.port_rbl_map[port])
connection.append((self.prefix + "wl_en{}".format(port),
self.bitcell_array_inst.get_pin(rbl_wl_name).lc()))
self.bitcell_array_inst.get_pin(rbl_wl_name).lc(),
self.bitcell_array_inst.get_pin(rbl_wl_name).layer))
if port in self.write_ports:
if port % 2:
connection.append((self.prefix + "w_en{}".format(port),
self.port_data_inst[port].get_pin("w_en").rc()))
self.port_data_inst[port].get_pin("w_en").rc(),
self.port_data_inst[port].get_pin("w_en").layer))
else:
connection.append((self.prefix + "w_en{}".format(port),
self.port_data_inst[port].get_pin("w_en").lc()))
self.port_data_inst[port].get_pin("w_en").lc(),
self.port_data_inst[port].get_pin("w_en").layer))
if port in self.read_ports:
connection.append((self.prefix + "s_en{}".format(port),
self.port_data_inst[port].get_pin("s_en").lc()))
self.port_data_inst[port].get_pin("s_en").lc(),
self.port_data_inst[port].get_pin("s_en").layer))
for (control_signal, pin_pos) in connection:
control_mid_pos = self.bus_xoffset[port][control_signal]
control_pos = vector(self.bus_xoffset[port][control_signal].x, pin_pos.y)
self.add_wire(self.m1_stack, [control_mid_pos, control_pos, pin_pos])
self.add_via_center(layers=self.m1_stack,
offset=control_pos)
for (control_signal, pin_pos, pin_layer) in connection:
if port==0:
y_offset = self.min_y_offset
else:
y_offset = self.max_y_offset
control_pos = vector(self.bus_xoffset[port][control_signal].x, y_offset)
if pin_layer == "m1":
self.add_wire(self.m1_stack, [control_pos, pin_pos])
elif pin_layer == "m3":
self.add_wire(self.m2_stack[::-1], [control_pos, pin_pos])
# clk to wordline_driver
control_signal = self.prefix + "wl_en{}".format(port)

View File

@ -205,7 +205,7 @@ class bank_select(design.design):
bank_sel_line_end = vector(xoffset_bank_sel, self.yoffset_maxpoint)
self.add_path("m2", [bank_sel_line_pos, bank_sel_line_end])
self.add_via_center(layers=self.m1_stack,
offset=bank_sel_inv_pin.lc())
offset=bank_sel_inv_pin.center())
# Route the pin to the left edge as well
bank_sel_pin_pos=vector(0, 0)
@ -242,30 +242,31 @@ class bank_select(design.design):
# Connect the logic output to inverter input
out_pin = logic_inst.get_pin("Z")
out_pos = out_pin.rc()
out_pos = out_pin.center()
in_pin = inv_inst.get_pin("A")
in_pos = in_pin.lc()
in_pos = in_pin.center()
mid1_pos = vector(0.5 * (out_pos.x + in_pos.x), out_pos.y)
mid2_pos = vector(0.5 * (out_pos.x + in_pos.x), in_pos.y)
self.add_path("m1", [out_pos, mid1_pos, mid2_pos, in_pos])
# Connect the logic B input to bank_sel / bank_sel_bar
logic_pos = logic_inst.get_pin("B").lc() - vector(0.5 * contact.m1_via.height, 0)
logic_pin = logic_inst.get_pin("B")
logic_pos = logic_pin.center()
input_pos = vector(xoffset_bank_signal, logic_pos.y)
self.add_path("m2", [logic_pos, input_pos])
self.add_via_center(layers=self.m1_stack,
offset=logic_pos,
directions=("H", "H"))
self.add_path("m3", [logic_pos, input_pos])
self.add_via_center(self.m2_stack,
input_pos)
self.add_via_stack_center(from_layer=logic_pin.layer,
to_layer="m3",
offset=logic_pos)
# Connect the logic A input to the input pin
logic_pos = logic_inst.get_pin("A").lc()
logic_pin = logic_inst.get_pin("A")
logic_pos = logic_pin.center()
input_pos = vector(0, logic_pos.y)
self.add_via_center(layers=self.m1_stack,
offset=logic_pos,
directions=("H", "H"))
self.add_via_center(layers=self.m2_stack,
offset=logic_pos,
directions=("H", "H"))
self.add_via_stack_center(from_layer=logic_pin.layer,
to_layer="m3",
offset=logic_pos)
self.add_layout_pin_segment_center(text=input_name,
layer="m3",
start=input_pos,

View File

@ -104,9 +104,9 @@ class bitcell_base_array(design.design):
# For non-square via stacks, vertical/horizontal direction refers to the stack orientation in 2d space
# Default uses prefered directions for each layer; this cell property is only currently used by s8 tech (03/20)
try:
force_power_pins_vertical = cell_properties.bitcell_force_power_pins_vertical
bitcell_power_pin_directions = cell_properties.bitcell_power_pin_directions
except AttributeError:
force_power_pins_vertical = None
bitcell_power_pin_directions = None
# Add vdd/gnd via stacks
for row in range(self.row_size):
@ -114,7 +114,10 @@ class bitcell_base_array(design.design):
inst = self.cell_inst[row,col]
for pin_name in ["vdd", "gnd"]:
for pin in inst.get_pins(pin_name):
self.add_power_pin(name=pin_name, loc=pin.center(), vertical=force_power_pins_vertical, start_layer=pin.layer)
self.add_power_pin(name=pin_name,
loc=pin.center(),
directions=bitcell_power_pin_directions,
start_layer=pin.layer)
def _adjust_x_offset(self, xoffset, col, col_offset):
tempx = xoffset

View File

@ -384,7 +384,10 @@ class control_logic(design.design):
height = self.control_logic_center.y - self.m2_pitch
offset = vector(self.ctrl_dff_array.width, 0)
self.rail_offsets = self.create_vertical_bus("m2", self.m2_pitch, offset, self.internal_bus_list, height)
self.rail_offsets = self.create_vertical_bus("m2",
offset,
self.internal_bus_list,
height)
def create_instances(self):
""" Create all the instances """
@ -432,7 +435,7 @@ class control_logic(design.design):
row += 1
if (self.port_type == "rw") or (self.port_type == "w"):
self.place_rbl_delay_row(row)
row += 1
row += 1
if (self.port_type == "rw") or (self.port_type == "r"):
self.place_sen_row(row)
row += 1
@ -522,16 +525,8 @@ class control_logic(design.design):
self.add_via_center(layers=self.m1_stack,
offset=clk_pos)
# Connect this at the bottom of the buffer
out_pos = self.clk_buf_inst.get_pin("Z").center()
mid1 = vector(out_pos.x, 2 * self.m2_pitch)
mid2 = vector(self.rail_offsets["clk_buf"].x, mid1.y)
bus_pos = self.rail_offsets["clk_buf"]
self.add_wire(self.m2_stack[::-1], [out_pos, mid1, mid2, bus_pos])
# The pin is on M1, so we need another via as well
self.add_via_center(layers=self.m1_stack,
offset=self.clk_buf_inst.get_pin("Z").center())
self.route_output_to_bus_jogged(self.clk_buf_inst,
"clk_buf")
self.connect_output(self.clk_buf_inst, "Z", "clk_buf")
def create_gated_clk_bar_row(self):
@ -541,7 +536,7 @@ class control_logic(design.design):
self.gated_clk_bar_inst = self.add_inst(name="and2_gated_clk_bar",
mod=self.and2)
self.connect_inst(["cs", "clk_bar", "gated_clk_bar", "vdd", "gnd"])
self.connect_inst(["clk_bar", "cs", "gated_clk_bar", "vdd", "gnd"])
def place_gated_clk_bar_row(self, row):
x_offset = self.control_x_offset
@ -554,31 +549,26 @@ class control_logic(design.design):
def route_gated_clk_bar(self):
clkbuf_map = zip(["A"], ["clk_buf"])
self.connect_vertical_bus(clkbuf_map, self.clk_bar_inst, self.rail_offsets)
out_pos = self.clk_bar_inst.get_pin("Z").center()
in_pos = self.gated_clk_bar_inst.get_pin("B").center()
mid1 = vector(in_pos.x, out_pos.y)
self.add_path("m1", [out_pos, mid1, in_pos])
in_pos = self.gated_clk_bar_inst.get_pin("A").center()
self.add_zjog("m1", out_pos, in_pos)
# This is the second gate over, so it needs to be on M3
clkbuf_map = zip(["A"], ["cs"])
clkbuf_map = zip(["B"], ["cs"])
self.connect_vertical_bus(clkbuf_map,
self.gated_clk_bar_inst,
self.rail_offsets,
self.m2_stack[::-1])
# The pin is on M1, so we need another via as well
self.add_via_center(layers=self.m1_stack,
offset=self.gated_clk_bar_inst.get_pin("A").center())
b_pin = self.gated_clk_bar_inst.get_pin("B")
self.add_via_stack_center(from_layer=b_pin.layer,
to_layer="m3",
offset=b_pin.center())
# This is the second gate over, so it needs to be on M3
clkbuf_map = zip(["Z"], ["gated_clk_bar"])
self.connect_vertical_bus(clkbuf_map,
self.gated_clk_bar_inst,
self.rail_offsets,
self.m2_stack[::-1])
# The pin is on M1, so we need another via as well
self.add_via_center(layers=self.m1_stack,
offset=self.gated_clk_bar_inst.get_pin("Z").center())
self.route_output_to_bus_jogged(self.gated_clk_bar_inst,
"gated_clk_bar")
def create_gated_clk_buf_row(self):
self.gated_clk_buf_inst = self.add_inst(name="and2_gated_clk_buf",
@ -594,7 +584,9 @@ class control_logic(design.design):
def route_gated_clk_buf(self):
clkbuf_map = zip(["A", "B"], ["clk_buf", "cs"])
self.connect_vertical_bus(clkbuf_map, self.gated_clk_buf_inst, self.rail_offsets)
self.connect_vertical_bus(clkbuf_map,
self.gated_clk_buf_inst,
self.rail_offsets)
clkbuf_map = zip(["Z"], ["gated_clk_buf"])
self.connect_vertical_bus(clkbuf_map,
@ -602,8 +594,10 @@ class control_logic(design.design):
self.rail_offsets,
self.m2_stack[::-1])
# The pin is on M1, so we need another via as well
self.add_via_center(layers=self.m1_stack,
offset=self.gated_clk_buf_inst.get_pin("Z").center())
z_pin = self.gated_clk_buf_inst.get_pin("Z")
self.add_via_stack_center(from_layer=z_pin.layer,
to_layer="m2",
offset=z_pin.center())
def create_wlen_row(self):
# input pre_p_en, output: wl_en
@ -647,11 +641,16 @@ class control_logic(design.design):
in_map = zip(["A", "B"], ["gated_clk_buf", "rbl_bl_delay"])
self.connect_vertical_bus(in_map, self.p_en_bar_nand_inst, self.rail_offsets)
out_pos = self.p_en_bar_nand_inst.get_pin("Z").rc()
in_pos = self.p_en_bar_driver_inst.get_pin("A").lc()
mid1 = vector(out_pos.x, in_pos.y)
self.add_wire(self.m1_stack, [out_pos, mid1, in_pos])
out_pin = self.p_en_bar_nand_inst.get_pin("Z")
out_pos = out_pin.center()
in_pin = self.p_en_bar_driver_inst.get_pin("A")
in_pos = in_pin.center()
mid1 = vector(in_pos.x, out_pos.y)
self.add_path(out_pin.layer, [out_pos, mid1, in_pos])
self.add_via_stack_center(from_layer=out_pin.layer,
to_layer=in_pin.layer,
offset=in_pin.center())
self.connect_output(self.p_en_bar_driver_inst, "Z", "p_en_bar")
def create_sen_row(self):
@ -704,11 +703,7 @@ class control_logic(design.design):
# Connect from delay line
# Connect to rail
rbl_map = zip(["Z"], ["rbl_bl_delay_bar"])
self.connect_vertical_bus(rbl_map, self.rbl_bl_delay_inv_inst, self.rail_offsets, ("m3", "via2", "m2"))
# The pin is on M1, so we need another via as well
self.add_via_center(layers=self.m1_stack,
offset=self.rbl_bl_delay_inv_inst.get_pin("Z").center())
self.route_output_to_bus_jogged(self.rbl_bl_delay_inv_inst, "rbl_bl_delay_bar")
rbl_map = zip(["A"], ["rbl_bl_delay"])
self.connect_vertical_bus(rbl_map, self.rbl_bl_delay_inv_inst, self.rail_offsets)
@ -766,11 +761,12 @@ class control_logic(design.design):
dff_out_map = zip(["dout_bar_0", "dout_0"], ["cs", "cs_bar"])
else:
dff_out_map = zip(["dout_bar_0"], ["cs"])
self.connect_vertical_bus(dff_out_map, self.ctrl_dff_inst, self.rail_offsets, ("m3", "via2", "m2"))
self.connect_vertical_bus(dff_out_map, self.ctrl_dff_inst, self.rail_offsets, self.m2_stack[::-1])
# Connect the clock rail to the other clock rail
# by routing in the supply rail track to avoid channel conflicts
in_pos = self.ctrl_dff_inst.get_pin("clk").uc()
mid_pos = in_pos + vector(0, 2 * self.m2_pitch)
mid_pos = in_pos + vector(0, self.and2.height)
rail_pos = vector(self.rail_offsets["clk_buf"].x, mid_pos.y)
self.add_wire(self.m1_stack, [in_pos, mid_pos, rail_pos])
self.add_via_center(layers=self.m1_stack,
@ -823,10 +819,10 @@ class control_logic(design.design):
self.add_path("m1", [row_loc, pin_loc])
self.copy_layout_pin(self.delay_inst, "gnd")
self.copy_layout_pin(self.delay_inst, "vdd")
self.copy_layout_pin(self.delay_inst, "vdd")
self.copy_layout_pin(self.ctrl_dff_inst, "gnd")
self.copy_layout_pin(self.ctrl_dff_inst, "vdd")
self.copy_layout_pin(self.ctrl_dff_inst, "vdd")
def add_lvs_correspondence_points(self):
""" This adds some points for easier debugging if LVS goes wrong.
@ -1000,3 +996,15 @@ class control_logic(design.design):
offset = vector(x_offset, y_offset)
inst.place(offset, mirror)
return x_offset + inst.width
def route_output_to_bus_jogged(self, inst, name):
# Connect this at the bottom of the buffer
out_pos = inst.get_pin("Z").center()
mid1 = vector(out_pos.x, out_pos.y - 0.25 * inst.mod.height)
mid2 = vector(self.rail_offsets[name].x, mid1.y)
bus_pos = self.rail_offsets[name]
self.add_wire(self.m2_stack[::-1], [out_pos, mid1, mid2, bus_pos])
# The pin is on M1, so we need another via as well
self.add_via_center(layers=self.m1_stack,
offset=out_pos)

View File

@ -62,7 +62,7 @@ class delay_chain(design.design):
self.add_pin("gnd", "GROUND")
def add_modules(self):
self.inv = factory.create(module_type="pinv", route_output=False)
self.inv = factory.create(module_type="pinv")
self.add_mod(self.inv)
def create_inverters(self):

View File

@ -180,7 +180,7 @@ class dff_buf(design.design):
height=din_pin.height())
dout_pin = self.inv2_inst.get_pin("Z")
mid_pos = dout_pin.center() + vector(self.m1_pitch, 0)
mid_pos = dout_pin.center() + vector(self.m1_nonpref_pitch, 0)
q_pos = mid_pos - vector(0, self.m2_pitch)
self.add_layout_pin_rect_center(text="Q",
layer="m2",

View File

@ -12,7 +12,7 @@ from sram_factory import factory
from vector import vector
from globals import OPTS
from errors import drc_error
from tech import cell_properties
from tech import cell_properties, layer
class hierarchical_decoder(design.design):
@ -89,6 +89,7 @@ class hierarchical_decoder(design.design):
self.place_pre_decoder()
self.place_row_decoder()
self.route_inputs()
self.route_outputs()
self.route_decoder_bus()
self.route_vdd_gnd()
self.offset_all_coordinates()
@ -103,7 +104,7 @@ class hierarchical_decoder(design.design):
height=self.cell_height)
self.add_mod(self.and2)
self.and3 = factory.create(module_type="pand3",
height=self.cell_height)
height=self.cell_height)
self.add_mod(self.and3)
self.add_decoders()
@ -186,19 +187,36 @@ class hierarchical_decoder(design.design):
self.num_rows = math.ceil(self.num_outputs / self.cell_multiple)
# We will place this many final decoders per row
self.decoders_per_row = math.ceil(self.num_outputs / self.num_rows)
# We will need to use M2 and M3 in the vertical bus if we have multiple decoders per row
if self.decoders_per_row == 1:
self.decoder_bus_pitch = self.m2_pitch
elif self.decoders_per_row == 2:
self.decoder_bus_pitch = self.m3_pitch
else:
debug.error("Insufficient layers for multi-bit height decoder.", -1)
# Calculates height and width of row-decoder
if (self.num_inputs == 4 or self.num_inputs == 5):
nand_width = self.and2.width
nand_inputs = 2
else:
nand_width = self.and3.width
self.internal_routing_width = self.m2_pitch * (self.total_number_of_predecoder_outputs + 1)
nand_inputs = 3
self.internal_routing_width = self.decoder_bus_pitch * (self.total_number_of_predecoder_outputs + 1)
self.row_decoder_height = self.inv.height * self.num_rows
decoder_input_wire_height = self.decoders_per_row * nand_inputs * self.m2_pitch
# print(self.decoders_per_row, nand_inputs)
# print(decoder_input_wire_height, self.cell_height)
if decoder_input_wire_height > self.cell_height:
debug.warning("Cannot fit multi-bit decoder routes per row.")
# debug.check(decoder_input_wire_height < self.cell_height, "Cannot fit multi-bit decoder routes per row.")
self.input_routing_width = (self.num_inputs + 1) * self.m2_pitch
# Calculates height and width of hierarchical decoder
# Add extra pitch for good measure
self.height = max(self.predecoder_height, self.row_decoder_height) + self.m3_pitch
self.height = max(self.predecoder_height, self.row_decoder_height) + self.m2_pitch
self.width = self.input_routing_width + self.predecoder_width \
+ self.internal_routing_width \
+ self.decoders_per_row * nand_width + self.inv.width
@ -218,7 +236,6 @@ class hierarchical_decoder(design.design):
input_bus_names = ["addr_{0}".format(i) for i in range(self.num_inputs)]
self.input_bus = self.create_vertical_pin_bus(layer="m2",
pitch=self.m2_pitch,
offset=input_offset,
names=input_bus_names,
length=input_height)
@ -238,7 +255,7 @@ class hierarchical_decoder(design.design):
# To prevent conflicts, we will offset each input connect so
# that it aligns with the vdd/gnd rails
decoder_offset = decoder_pin.bc() + vector(0, (i + 1) * self.inv.height)
decoder_offset = decoder_pin.bc() + vector(0, (i + 1) * (self.inv.height + self.m1_pitch))
input_offset = input_pos.scale(1, 0) + decoder_offset.scale(0, 1)
self.route_input_bus(decoder_offset, input_offset)
@ -254,7 +271,7 @@ class hierarchical_decoder(design.design):
# To prevent conflicts, we will offset each input connect so
# that it aligns with the vdd/gnd rails
decoder_offset = decoder_pin.bc() + vector(0, (i + 1) * self.inv.height)
decoder_offset = decoder_pin.bc() + vector(0, (i + 1) * (self.inv.height + self.m1_pitch))
input_offset = input_pos.scale(1, 0) + decoder_offset.scale(0, 1)
self.route_input_bus(decoder_offset, input_offset)
@ -265,11 +282,13 @@ class hierarchical_decoder(design.design):
vertical M2 coordinate to the predecode inputs
"""
self.add_via_center(layers=self.m2_stack,
offset=input_offset)
self.add_via_center(layers=self.m2_stack,
offset=output_offset)
self.add_path(("m3"), [input_offset, output_offset])
self.add_via_stack_center(from_layer="m2",
to_layer="m3",
offset=input_offset)
self.add_via_stack_center(from_layer="m2",
to_layer="m3",
offset=output_offset)
self.add_path("m3", [input_offset, output_offset])
def add_pins(self):
""" Add the module pins """
@ -411,7 +430,7 @@ class hierarchical_decoder(design.design):
"""
if (self.num_inputs >= 4):
self.place_decoder_and_array()
self.route_decoder()
def place_decoder_and_array(self):
"""
@ -447,16 +466,28 @@ class hierarchical_decoder(design.design):
self.and_inst[inst_index].place(offset=vector(x_off, y_off),
mirror=mirror)
def route_decoder(self):
def route_outputs(self):
""" Add the pins. """
for output in range(self.num_outputs):
z_pin = self.and_inst[output].get_pin("Z")
self.add_layout_pin(text="decode_{0}".format(output),
layer="m1",
offset=z_pin.ll(),
width=z_pin.width(),
height=z_pin.height())
max_xoffset = max(x.rx() for x in self.and_inst)
for output_index in range(self.num_outputs):
row_remainder = (output_index % self.decoders_per_row)
and_inst = self.and_inst[output_index]
z_pin = and_inst.get_pin("Z")
if row_remainder == 0 and self.decoders_per_row > 1:
layer = "m3"
self.add_via_stack_center(from_layer=z_pin.layer,
to_layer="m3",
offset=z_pin.center())
else:
layer = z_pin.layer
self.add_layout_pin_segment_center(text="decode_{0}".format(output_index),
layer=layer,
start=z_pin.center(),
end=vector(max_xoffset, z_pin.cy()))
def route_decoder_bus(self):
"""
@ -468,7 +499,7 @@ class hierarchical_decoder(design.design):
# This leaves an offset for the predecoder output jogs
input_bus_names = ["predecode_{0}".format(i) for i in range(self.total_number_of_predecoder_outputs)]
self.predecode_bus = self.create_vertical_pin_bus(layer="m2",
pitch=self.m2_pitch,
pitch=self.decoder_bus_pitch,
offset=vector(0, 0),
names=input_bus_names,
length=self.height)
@ -512,22 +543,27 @@ class hierarchical_decoder(design.design):
"""
output_index = 0
if "li" in layer:
self.decoder_layers = [self.m1_stack, self.m2_stack[::-1]]
else:
self.decoder_layers = [self.m2_stack[::-1]]
debug.check(self.decoders_per_row <= len(self.decoder_layers), "Must have more layers for multi-height decoder.")
if (self.num_inputs == 4 or self.num_inputs == 5):
for index_B in self.predec_groups[1]:
for index_A in self.predec_groups[0]:
# FIXME: convert to connect_bus?
if (output_index < self.num_outputs):
row_index = math.floor(output_index / self.decoders_per_row)
row_remainder = (output_index % self.decoders_per_row)
row_offset = row_index * self.and_inst[0].height + (2 * row_remainder + 1) * self.m3_pitch
predecode_name = "predecode_{}".format(index_A)
self.route_predecode_bus_outputs(predecode_name,
self.and_inst[output_index].get_pin("A"),
row_offset)
output_index,
0)
predecode_name = "predecode_{}".format(index_B)
self.route_predecode_bus_outputs(predecode_name,
self.and_inst[output_index].get_pin("B"),
row_offset + self.m3_pitch)
output_index,
1)
output_index = output_index + 1
elif (self.num_inputs > 5):
@ -536,21 +572,21 @@ class hierarchical_decoder(design.design):
for index_A in self.predec_groups[0]:
# FIXME: convert to connect_bus?
if (output_index < self.num_outputs):
row_index = math.floor(output_index / self.decoders_per_row)
row_remainder = (output_index % self.decoders_per_row)
row_offset = row_index * self.and_inst[0].height + (3 * row_remainder + 1) * self.m3_pitch
predecode_name = "predecode_{}".format(index_A)
self.route_predecode_bus_outputs(predecode_name,
self.and_inst[output_index].get_pin("A"),
row_offset)
output_index,
0)
predecode_name = "predecode_{}".format(index_B)
self.route_predecode_bus_outputs(predecode_name,
self.and_inst[output_index].get_pin("B"),
row_offset + self.m3_pitch)
output_index,
1)
predecode_name = "predecode_{}".format(index_C)
self.route_predecode_bus_outputs(predecode_name,
self.and_inst[output_index].get_pin("C"),
row_offset + 2 * self.m3_pitch)
output_index,
2)
output_index = output_index + 1
def route_vdd_gnd(self):
@ -570,39 +606,56 @@ class hierarchical_decoder(design.design):
# The nand and inv are the same height rows...
supply_pin = self.and_inst[num].get_pin(pin_name)
pin_pos = vector(xoffset, supply_pin.cy())
self.add_path("m1",
self.add_path(supply_pin.layer,
[supply_pin.lc(), vector(xoffset, supply_pin.cy())])
self.add_power_pin(name=pin_name,
loc=pin_pos)
loc=pin_pos,
start_layer=supply_pin.layer)
# Copy the pins from the predecoders
for pre in self.pre2x4_inst + self.pre3x8_inst:
self.copy_layout_pin(pre, "vdd")
self.copy_layout_pin(pre, "gnd")
def route_predecode_bus_outputs(self, rail_name, pin, y_offset):
def route_predecode_bus_outputs(self, rail_name, pin, output_index, pin_index):
"""
Connect the routing rail to the given metal1 pin
using a routing track at the given y_offset
"""
pin_pos = pin.center()
# If we have a single decoder per row, we can route on M1
if self.decoders_per_row == 1:
rail_pos = vector(self.predecode_bus[rail_name].x, pin_pos.y)
self.add_path("m1", [rail_pos, pin_pos])
self.add_via_center(layers=self.m1_stack,
offset=rail_pos)
# If not, we must route over the decoder cells on M3
else:
rail_pos = vector(self.predecode_bus[rail_name].x, y_offset)
mid_pos = vector(pin_pos.x, rail_pos.y)
self.add_wire(self.m2_stack[::-1], [rail_pos, mid_pos, pin_pos])
self.add_via_center(layers=self.m2_stack,
offset=rail_pos)
self.add_via_center(layers=self.m1_stack,
offset=pin_pos)
row_index = math.floor(output_index / self.decoders_per_row)
row_remainder = (output_index % self.decoders_per_row)
row_offset = row_index * self.and_inst[0].height
pin_pos = pin.center()
# y_offset is the same for both the M2 and M4 routes so that the rail
# contacts align and don't cause problems
if pin_index == 0:
# Bottom pitch
y_offset = row_offset
elif pin_index == 1:
# One pitch from top
y_offset = row_offset + self.and_inst[0].height - self.m3_pitch
elif pin_index == 2:
# One pitch from bottom
y_offset = row_offset + self.m3_pitch
else:
debug.error("Invalid decoder pitch.")
rail_pos = vector(self.predecode_bus[rail_name].x, y_offset)
mid_pos = vector(pin_pos.x, rail_pos.y)
self.add_wire(self.decoder_layers[row_remainder], [rail_pos, mid_pos, pin_pos])
self.add_via_stack_center(from_layer="m2",
to_layer=self.decoder_layers[row_remainder][0],
offset=rail_pos)
self.add_via_stack_center(from_layer=pin.layer,
to_layer=self.decoder_layers[row_remainder][2],
offset=pin_pos,
directions=("H", "H"))
def route_predecode_bus_inputs(self, rail_name, pin, x_offset):
"""
Connect the routing rail to the given metal1 pin using a jog
@ -610,13 +663,21 @@ class hierarchical_decoder(design.design):
"""
# This routes the pin up to the rail, basically, to avoid conflicts.
# It would be fixed with a channel router.
# pin_pos = pin.center()
# mid_point1 = vector(x_offset, pin_pos.y)
# mid_point2 = vector(x_offset, pin_pos.y + self.inv.height / 2)
# rail_pos = vector(self.predecode_bus[rail_name].x, mid_point2.y)
# self.add_path("m1", [pin_pos, mid_point1, mid_point2, rail_pos])
pin_pos = pin.center()
mid_point1 = vector(x_offset, pin_pos.y)
mid_point2 = vector(x_offset, pin_pos.y + self.inv.height / 2)
rail_pos = vector(self.predecode_bus[rail_name].x, mid_point2.y)
self.add_wire(self.m1_stack, [pin_pos, mid_point1, mid_point2, rail_pos])
self.add_via_center(layers=self.m1_stack,
offset=rail_pos)
rail_pos = vector(self.predecode_bus[rail_name].x, pin_pos.y)
self.add_path("m1", [pin_pos, rail_pos])
self.add_via_stack_center(from_layer=pin.layer,
to_layer="m1",
offset=pin_pos)
self.add_via_stack_center(from_layer="m1",
to_layer="m2",
offset=rail_pos)
def input_load(self):
if self.determine_predecodes(self.num_inputs)[1]==0:

View File

@ -11,6 +11,7 @@ import math
import contact
from vector import vector
from sram_factory import factory
from tech import cell_properties
class hierarchical_predecode(design.design):
@ -19,7 +20,17 @@ class hierarchical_predecode(design.design):
"""
def __init__(self, name, input_number, height=None):
self.number_of_inputs = input_number
self.cell_height = height
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
else:
self.cell_height = height
self.number_of_outputs = int(math.pow(2, self.number_of_inputs))
design.design.__init__(self, name)
@ -57,10 +68,10 @@ class hierarchical_predecode(design.design):
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.x_off_inv_1 = self.number_of_inputs * self.m2_pitch + self.m2_space
# 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
self.x_off_and = self.x_off_inv_1 + self.inv.width + (2 * self.number_of_inputs + 2) * self.m2_pitch
# x offset to output inverters
self.width = self.x_off_and + self.and_mod.width
@ -68,9 +79,8 @@ 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.m1_pitch)
offset = vector(0.5 * self.m2_width, self.m3_pitch)
self.input_rails = self.create_vertical_pin_bus(layer="m2",
pitch=self.m2_pitch,
offset=offset,
names=input_names,
length=self.height - 2 * self.m1_pitch)
@ -78,9 +88,8 @@ class hierarchical_predecode(design.design):
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.m1_pitch)
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",
pitch=self.m2_pitch,
offset=offset,
names=decode_names,
length=self.height - 2 * self.m1_pitch)
@ -146,16 +155,18 @@ class hierarchical_predecode(design.design):
# 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.m1_via.width + self.m1_space
y_offset = (num + self.number_of_inputs) * self.inv.height + contact.m2_via.width + self.m2_space
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_center(layers=self.m1_stack,
offset=[self.input_rails[in_pin].x, y_offset])
self.add_via_center(layers=self.m1_stack,
offset=[self.decode_rails[a_pin].x, y_offset])
self.add_via_stack_center(from_layer="m1",
to_layer="m2",
offset=[self.input_rails[in_pin].x, y_offset])
self.add_via_stack_center(from_layer="m1",
to_layer="m2",
offset=[self.decode_rails[a_pin].x, y_offset])
def route_output_and(self):
"""
@ -165,37 +176,43 @@ class hierarchical_predecode(design.design):
z_pin = self.and_inst[num].get_pin("Z")
self.add_layout_pin(text="out_{}".format(num),
layer="m1",
layer=z_pin.layer,
offset=z_pin.ll(),
height=z_pin.height(),
width=z_pin.width())
def route_input_inverters(self):
"""
Route all conections of the inputs inverters [Inputs, outputs, vdd, gnd]
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
# 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_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.decode_rails[out_pin].x,y_offset)
self.add_path("m1", [inv_out_pos, right_pos, vector(right_pos.x, y_offset), rail_pos])
self.add_via_center(layers = self.m1_stack,
offset=rail_pos)
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])
self.add_via_stack_center(from_layer=inv_out_pin.layer,
to_layer="m2",
offset=rail_pos)
#route input
inv_in_pos = self.in_inst[inv_num].get_pin("A").lc()
in_pos = vector(self.input_rails[in_pin].x,inv_in_pos.y)
# route input
pin = self.in_inst[inv_num].get_pin("A")
inv_in_pos = pin.lc()
in_pos = vector(self.input_rails[in_pin].x, inv_in_pos.y)
self.add_path("m1", [in_pos, inv_in_pos])
self.add_via_center(layers=self.m1_stack,
offset=in_pos)
self.add_via_stack_center(from_layer=pin.layer,
to_layer="m1",
offset=inv_in_pos)
self.add_via_stack_center(from_layer="m1",
to_layer="m2",
offset=in_pos)
def route_and_to_rails(self):
# This 2D array defines the connection mapping
@ -205,17 +222,28 @@ class hierarchical_predecode(design.design):
index_lst= and_input_line_combination[k]
if self.number_of_inputs == 2:
gate_lst = ["A","B"]
gate_lst = ["A", "B"]
else:
gate_lst = ["A","B","C"]
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.and_inst[k].get_pin(gate_pin).lc()
for rail_pin, gate_pin in zip(index_lst, gate_lst):
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_center(layers=self.m1_stack,
offset=rail_pos)
self.add_via_stack_center(from_layer="m1",
to_layer="m2",
offset=rail_pos)
if gate_pin == "A":
direction = None
else:
direction = ("H", "H")
self.add_via_stack_center(from_layer=pin.layer,
to_layer="m1",
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. """
@ -230,14 +258,16 @@ class hierarchical_predecode(design.design):
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="m1",
self.add_rect(layer=and_pin.layer,
offset=supply_offset,
width=self.and_inst[num].rx())
# Add pins in two locations
for xoffset in [in_xoffset]:
pin_pos = vector(xoffset, and_pin.cy())
self.add_power_pin(n, pin_pos)
self.add_power_pin(name=n,
loc=pin_pos,
start_layer=and_pin.layer)

View File

@ -3,8 +3,6 @@
# Copyright (c) 2016-2019 Regents of the University of California
# All rights reserved.
#
import sys
from tech import drc, parameter
from math import log, ceil
import debug
import design
@ -13,6 +11,7 @@ from vector import vector
from globals import OPTS
class port_address(design.design):
"""
Create the address port (row decoder and wordline driver)..
@ -25,17 +24,16 @@ class port_address(design.design):
self.addr_size = ceil(log(self.num_rows, 2))
if name == "":
name = "port_address_{0}_{1}".format(cols,rows)
name = "port_address_{0}_{1}".format(cols, rows)
design.design.__init__(self, name)
debug.info(2, "create data port of cols {0} rows {1}".format(cols,rows))
debug.info(2, "create data port of cols {0} rows {1}".format(cols, rows))
self.create_netlist()
if not OPTS.netlist_only:
debug.check(len(self.all_ports)<=2,"Bank layout cannot handle more than two ports.")
debug.check(len(self.all_ports) <= 2, "Bank layout cannot handle more than two ports.")
self.create_layout()
self.add_boundary()
def create_netlist(self):
self.add_pins()
self.add_modules()
@ -51,16 +49,15 @@ class port_address(design.design):
""" Adding pins for port address module"""
for bit in range(self.addr_size):
self.add_pin("addr_{0}".format(bit),"INPUT")
self.add_pin("addr_{0}".format(bit), "INPUT")
self.add_pin("wl_en", "INPUT")
for bit in range(self.num_rows):
self.add_pin("wl_{0}".format(bit),"OUTPUT")
self.add_pin("wl_{0}".format(bit), "OUTPUT")
self.add_pin("vdd","POWER")
self.add_pin("gnd","GROUND")
self.add_pin("vdd", "POWER")
self.add_pin("gnd", "GROUND")
def route_layout(self):
""" Create routing amoung the modules """
@ -71,8 +68,8 @@ class port_address(design.design):
def route_supplies(self):
""" Propagate all vdd/gnd pins up to this level for all modules """
for inst in self.insts:
self.copy_power_pins(inst,"vdd")
self.copy_power_pins(inst,"gnd")
self.copy_power_pins(inst, "vdd")
self.copy_power_pins(inst, "gnd")
def route_pins(self):
for row in range(self.addr_size):
@ -119,12 +116,10 @@ class port_address(design.design):
temp.extend(["vdd", "gnd"])
self.connect_inst(temp)
def create_wordline_driver(self):
""" Create the Wordline Driver """
self.wordline_driver_inst = self.add_inst(name="wordline_driver",
self.wordline_driver_inst = self.add_inst(name="wordline_driver",
mod=self.wordline_driver)
temp = []
@ -137,19 +132,13 @@ class port_address(design.design):
temp.append("gnd")
self.connect_inst(temp)
def place_instances(self):
"""
Compute the offsets and place the instances.
"""
# A space for wells or jogging m2
self.m2_gap = max(2*drc("pwell_to_nwell") + drc("nwell_enclose_active"),
3*self.m2_pitch)
row_decoder_offset = vector(0,0)
wordline_driver_offset = vector(self.row_decoder.width + self.m2_gap,0)
row_decoder_offset = vector(0, 0)
wordline_driver_offset = vector(self.row_decoder.width, 0)
self.wordline_driver_inst.place(wordline_driver_offset)
self.row_decoder_inst.place(row_decoder_offset)

View File

@ -789,7 +789,7 @@ class port_data(design.design):
# Channel route each mux separately since we don't minimize the number
# of tracks in teh channel router yet. If we did, we could route all the bits at once!
offset = bot_inst_group.inst.ul() + vector(0, self.m1_pitch)
offset = bot_inst_group.inst.ul() + vector(0, self.m1_nonpref_pitch)
for bit in range(num_bits):
bottom_names = self._get_bitline_pins(bot_inst_group, bit)
top_names = self._get_bitline_pins(top_inst_group, bit)

View File

@ -355,32 +355,30 @@ class replica_bitcell_array(design.design):
width=self.width,
height=pin.height())
# Replica bitlines
for port in range(self.left_rbl+self.right_rbl):
for port in range(self.left_rbl + self.right_rbl):
inst = self.replica_col_inst[port]
for (pin_name, bl_name) in zip(self.cell.get_all_bitline_names(),self.replica_bl_names[port]):
for (pin_name, bl_name) in zip(self.cell.get_all_bitline_names(), self.replica_bl_names[port]):
pin = inst.get_pin(pin_name)
if bl_name in self.rbl_bl_names or bl_name in self.rbl_br_names:
name = bl_name
else:
name = "rbl_{0}_{1}".format(pin_name,port)
name = "rbl_{0}_{1}".format(pin_name, port)
self.add_layout_pin(text=name,
layer=pin.layer,
offset=pin.ll().scale(1,0),
offset=pin.ll().scale(1, 0),
width=pin.width(),
height=self.height)
for pin_name in ["vdd","gnd"]:
for pin_name in ["vdd", "gnd"]:
for inst in self.insts:
pin_list = inst.get_pins(pin_name)
for pin in pin_list:
self.add_power_pin(name=pin_name, loc=pin.center(), vertical=True, start_layer=pin.layer)
self.add_power_pin(name=pin_name,
loc=pin.center(),
directions=("V", "V"),
start_layer=pin.layer)
def get_rbl_wl_name(self, port):
""" Return the WL for the given RBL port """
return self.rbl_wl_names[port]
@ -399,7 +397,7 @@ class replica_bitcell_array(design.design):
# Dynamic Power from Bitline
bl_wire = self.gen_bl_wire()
cell_load = 2 * bl_wire.return_input_cap()
cell_load = 2 * bl_wire.return_input_cap()
bl_swing = OPTS.rbl_delay_percentage
freq = spice["default_event_frequency"]
bitline_dynamic = self.calc_dynamic_power(corner, cell_load, freq, swing=bl_swing)

View File

@ -151,13 +151,13 @@ class sense_amp_array(design.design):
self.add_power_pin(name="gnd",
loc=gnd_pin.center(),
start_layer=gnd_pin.layer,
vertical=True)
directions=("V", "V"))
vdd_pin = inst.get_pin("vdd")
self.add_power_pin(name="vdd",
loc=vdd_pin.center(),
start_layer=vdd_pin.layer,
vertical=True)
directions=("V", "V"))
bl_pin = inst.get_pin(inst.mod.get_bl_names())
br_pin = inst.get_pin(inst.mod.get_br_names())

View File

@ -8,7 +8,7 @@
import debug
import design
import math
import contact
from tech import drc
from vector import vector
from sram_factory import factory
from globals import OPTS
@ -26,8 +26,8 @@ class wordline_driver(design.design):
debug.info(1, "Creating {0}".format(self.name))
self.add_comment("rows: {0} cols: {1}".format(rows, cols))
self.rows = rows
self.cols = cols
self.bitcell_rows = rows
self.bitcell_cols = cols
b = factory.create(module_type="bitcell")
try:
@ -36,6 +36,11 @@ class wordline_driver(design.design):
self.cell_multiple = 1
self.cell_height = self.cell_multiple * b.height
# We may have more than one bitcell per decoder row
self.num_rows = math.ceil(self.bitcell_rows / self.cell_multiple)
# We will place this many final decoders per row
self.decoders_per_row = math.ceil(self.bitcell_rows / self.num_rows)
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
@ -56,10 +61,10 @@ class wordline_driver(design.design):
def add_pins(self):
# inputs to wordline_driver.
for i in range(self.rows):
for i in range(self.bitcell_rows):
self.add_pin("in_{0}".format(i), "INPUT")
# Outputs from wordline_driver.
for i in range(self.rows):
for i in range(self.bitcell_rows):
self.add_pin("wl_{0}".format(i), "OUTPUT")
self.add_pin("en", "INPUT")
self.add_pin("vdd", "POWER")
@ -68,7 +73,7 @@ class wordline_driver(design.design):
def add_modules(self):
self.and2 = factory.create(module_type="pand2",
height=self.cell_height,
size=self.cols)
size=self.bitcell_cols)
self.add_mod(self.and2)
def route_vdd_gnd(self):
@ -79,7 +84,7 @@ class wordline_driver(design.design):
# Find the x offsets for where the vias/pins should be placed
xoffset_list = [self.and_inst[0].lx()]
for num in range(self.rows):
for num in range(self.bitcell_rows):
# this will result in duplicate polygons for rails, but who cares
# use the inverter offset even though it will be the and's too
@ -97,32 +102,34 @@ class wordline_driver(design.design):
def create_drivers(self):
self.and_inst = []
for row in range(self.rows):
for row in range(self.bitcell_rows):
name_and = "wl_driver_and{}".format(row)
# add and2
self.and_inst.append(self.add_inst(name=name_and,
mod=self.and2))
self.connect_inst(["en",
"in_{0}".format(row),
self.connect_inst(["in_{0}".format(row),
"en",
"wl_{0}".format(row),
"vdd", "gnd"])
def setup_layout_constants(self):
# We may have more than one bitcell per decoder row
self.num_rows = math.ceil(self.rows / self.cell_multiple)
self.driver_rows = math.ceil(self.bitcell_rows / self.cell_multiple)
# We will place this many final decoders per row
self.decoders_per_row = math.ceil(self.rows / self.num_rows)
self.decoders_per_row = math.ceil(self.bitcell_rows / self.driver_rows)
def place_drivers(self):
and2_xoffset = 2 * self.m1_width + 5 * self.m1_space
# Leave a well gap to separate the bitcell array well from this well
well_gap = 2 * drc("pwell_to_nwell") + drc("nwell_enclose_active")
self.width = and2_xoffset + self.and2.width
self.height = self.and2.height * self.num_rows
self.width = self.decoders_per_row * self.and2.width + well_gap
self.height = self.and2.height * self.driver_rows
for row in range(self.rows):
#row = math.floor(inst_index / self.decoders_per_row)
#dec = inst_index % self.decoders_per_row
for inst_index in range(self.bitcell_rows):
row = math.floor(inst_index / self.decoders_per_row)
dec = inst_index % self.decoders_per_row
if (row % 2):
y_offset = self.and2.height * (row + 1)
@ -131,63 +138,42 @@ class wordline_driver(design.design):
y_offset = self.and2.height * row
inst_mirror = "R0"
# x_off = self.internal_routing_width + dec * and_mod.width
and2_offset = [and2_xoffset, y_offset]
x_offset = dec * self.and2.width
and2_offset = [x_offset, y_offset]
# add and2
self.and_inst[row].place(offset=and2_offset,
mirror=inst_mirror)
self.and_inst[inst_index].place(offset=and2_offset,
mirror=inst_mirror)
def route_layout(self):
""" Route all of the signals """
# Wordline enable connection
en_offset = [self.m1_width + 2 * self.m1_space, 0]
en_pin = self.and_inst[0].get_pin("B")
en_bottom_pos = vector(en_pin.lx(), 0)
en_pin = self.add_layout_pin(text="en",
layer="m2",
offset=en_offset,
width=self.m2_width,
offset=en_bottom_pos,
height=self.height)
for row in range(self.rows):
and_inst = self.and_inst[row]
# en connection
a_pin = and_inst.get_pin("A")
a_pos = a_pin.lc()
clk_offset = vector(en_pin.bc().x, a_pos.y)
self.add_segment_center(layer="m1",
start=clk_offset,
end=a_pos)
self.add_via_center(layers=self.m1_stack,
offset=clk_offset)
for inst_index in range(self.bitcell_rows):
and_inst = self.and_inst[inst_index]
row = math.floor(inst_index / self.decoders_per_row)
# connect the decoder input pin to and2 B
# Drop a via
b_pin = and_inst.get_pin("B")
b_pos = b_pin.lc()
# needs to move down since B and 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)
base_offset = vector(clk_offset.x, input_offset.y)
contact_offset = vector(0.5 * self.m2_width + self.m2_space + 0.5 * contact.m1_via.width, 0)
mid_via_offset = base_offset + contact_offset
self.add_via_stack_center(from_layer=b_pin.layer,
to_layer="m2",
offset=b_pin.center())
# connect the decoder input pin to and2 A
a_pin = and_inst.get_pin("A")
a_pos = a_pin.center()
# must under the clk line in M1
self.add_layout_pin_segment_center(text="in_{0}".format(row),
layer="m1",
start=input_offset,
end=mid_via_offset)
self.add_via_center(layers=self.m1_stack,
offset=mid_via_offset,
directions=("V", "V"))
# now connect to the and2 B
self.add_path("m2", [mid_via_offset, b_pos])
contact_offset = b_pos - vector(0.5 * contact.m1_via.height, 0)
self.add_via_center(layers=self.m1_stack,
offset=contact_offset,
directions=("H", "H"))
start=vector(0, a_pos.y),
end=a_pos)
# output each WL on the right
wl_offset = and_inst.get_pin("Z").rc()

View File

@ -209,7 +209,7 @@ class write_driver_array(design.design):
for pin in pin_list:
self.add_power_pin(name=n,
loc=pin.center(),
vertical=True,
directions=("V", "V"),
start_layer=pin.layer)
if self.write_size:
for bit in range(self.num_wmasks):

View File

@ -5,9 +5,7 @@
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
from math import log
import design
from tech import drc
import debug
from sram_factory import factory
from vector import vector
@ -43,33 +41,28 @@ class write_mask_and_array(design.design):
self.add_pins()
self.create_and2_array()
def create_layout(self):
self.place_and2_array()
spacing = self.wmask_en_len - self.and2.width
self.width = (self.num_wmasks*self.and2.width) + ((self.num_wmasks-1)*spacing)
self.height = self.and2.height
self.add_layout_pins()
self.add_boundary()
self.DRC_LVS()
def add_pins(self):
for bit in range(self.num_wmasks):
self.add_pin("wmask_in_{}".format(bit),"INPUT")
self.add_pin("wmask_in_{}".format(bit), "INPUT")
self.add_pin("en", "INPUT")
for bit in range(self.num_wmasks):
self.add_pin("wmask_out_{}".format(bit),"OUTPUT")
self.add_pin("vdd","POWER")
self.add_pin("gnd","GROUND")
self.add_pin("wmask_out_{}".format(bit), "OUTPUT")
self.add_pin("vdd", "POWER")
self.add_pin("gnd", "GROUND")
def add_modules(self):
# Size the AND gate for the number of write drivers it drives, which is equal to the write size.
# Assume stage effort of 3 to compute the size
self.and2 = factory.create(module_type="pand2",
size=self.write_size/4.0)
size=self.write_size / 4.0)
self.add_mod(self.and2)
def create_and2_array(self):
self.and2_insts = {}
for bit in range(self.num_wmasks):
@ -81,7 +74,6 @@ class write_mask_and_array(design.design):
"wmask_out_{}".format(bit),
"vdd", "gnd"])
def place_and2_array(self):
# Place the write mask AND array at the start of each write driver enable length.
# This ensures the write mask AND array will be directly under the corresponding write driver enable wire.
@ -96,56 +88,45 @@ class write_mask_and_array(design.design):
self.wmask_en_len = self.words_per_row * (self.write_size * self.driver_spacing)
debug.check(self.wmask_en_len >= self.and2.width,
"Write mask AND is wider than the corresponding write drivers {0} vs {1}.".format(self.and2.width,self.wmask_en_len))
"Write mask AND is wider than the corresponding write drivers {0} vs {1}.".format(self.and2.width,
self.wmask_en_len))
self.width = self.bitcell.width * self.columns
self.height = self.and2.height
for i in range(self.num_wmasks):
base = vector(i * self.wmask_en_len, 0)
self.and2_insts[i].place(base)
def add_layout_pins(self):
# Create the enable pin that connects all write mask AND array's B pins
beg_en_pin = self.and2_insts[0].get_pin("B")
end_en_pin = self.and2_insts[self.num_wmasks-1].get_pin("B")
if self.port % 2:
# Extend metal3 to edge of AND array in multiport
en_to_edge = self.and2.width - beg_en_pin.cx()
self.add_layout_pin(text="en",
layer="m3",
offset=beg_en_pin.bc(),
width=end_en_pin.cx() - beg_en_pin.cx() + en_to_edge)
self.add_via_center(layers=self.m1_stack,
offset=vector(end_en_pin.cx() + en_to_edge, end_en_pin.cy()))
self.add_via_center(layers=self.m2_stack,
offset=vector(end_en_pin.cx() + en_to_edge, end_en_pin.cy()))
else:
self.add_layout_pin(text="en",
layer="m3",
offset=beg_en_pin.bc(),
width=end_en_pin.cx() - beg_en_pin.cx())
en_pin = self.and2_insts[0].get_pin("B")
self.add_layout_pin_segment_center(text="en",
layer="m3",
start=vector(0, en_pin.cy()),
end=vector(self.width, en_pin.cy()))
for i in range(self.num_wmasks):
# Copy remaining layout pins
self.copy_layout_pin(self.and2_insts[i],"A","wmask_in_{0}".format(i))
self.copy_layout_pin(self.and2_insts[i],"Z","wmask_out_{0}".format(i))
self.copy_layout_pin(self.and2_insts[i], "A", "wmask_in_{0}".format(i))
self.copy_layout_pin(self.and2_insts[i], "Z", "wmask_out_{0}".format(i))
# Add via connections to metal3 for AND array's B pin
en_pin = self.and2_insts[i].get_pin("B")
self.add_via_center(layers=self.m1_stack,
offset=en_pin.center())
self.add_via_center(layers=self.m2_stack,
offset=en_pin.center())
en_pos = en_pin.center()
self.add_via_stack_center(from_layer=en_pin.layer,
to_layer="m3",
offset=en_pos)
for supply in ["gnd", "vdd"]:
supply_pin=self.and2_insts[i].get_pin(supply)
self.add_power_pin(supply, supply_pin.center())
for supply in ["gnd", "vdd"]:
supply_pin_left = self.and2_insts[0].get_pin(supply)
supply_pin_right = self.and2_insts[self.num_wmasks-1].get_pin(supply)
self.add_path("m1",[supply_pin_left.lc(), supply_pin_right.rc()])
supply_pin_right = self.and2_insts[self.num_wmasks - 1].get_pin(supply)
self.add_path("m1", [supply_pin_left.lc(), supply_pin_right.rc()])
def get_cin(self):
"""Get the relative capacitance of all the input connections in the bank"""

View File

@ -44,6 +44,7 @@ class pand2(pgate.pgate):
self.place_insts()
self.add_wires()
self.add_layout_pins()
self.route_supply_rails()
self.add_boundary()
self.DRC_LVS()
@ -76,26 +77,10 @@ class pand2(pgate.pgate):
a2_pin = self.inv_inst.get_pin("A")
mid1_point = vector(0.5 * (z1_pin.cx() + a2_pin.cx()), z1_pin.cy())
mid2_point = vector(mid1_point, a2_pin.cy())
self.add_path("m1",
self.add_path(self.route_layer,
[z1_pin.center(), mid1_point, mid2_point, a2_pin.center()])
def add_layout_pins(self):
# Continous vdd rail along with label.
vdd_pin = self.inv_inst.get_pin("vdd")
self.add_layout_pin(text="vdd",
layer="m1",
offset=vdd_pin.ll().scale(0, 1),
width=self.width,
height=vdd_pin.height())
# Continous gnd rail along with label.
gnd_pin = self.inv_inst.get_pin("gnd")
self.add_layout_pin(text="gnd",
layer="m1",
offset=gnd_pin.ll().scale(0, 1),
width=self.width,
height=vdd_pin.height())
pin = self.inv_inst.get_pin("Z")
self.add_layout_pin_rect_center(text="Z",
layer=pin.layer,

View File

@ -44,6 +44,7 @@ class pand3(pgate.pgate):
self.place_insts()
self.add_wires()
self.add_layout_pins()
self.route_supply_rails()
self.add_boundary()
self.DRC_LVS()
@ -77,26 +78,10 @@ class pand3(pgate.pgate):
a2_pin = self.inv_inst.get_pin("A")
mid1_point = vector(0.5 * (z1_pin.cx()+a2_pin.cx()), z1_pin.cy())
mid2_point = vector(mid1_point, a2_pin.cy())
self.add_path("m1",
self.add_path(z1_pin.layer,
[z1_pin.center(), mid1_point, mid2_point, a2_pin.center()])
def add_layout_pins(self):
# Continous vdd rail along with label.
vdd_pin = self.inv_inst.get_pin("vdd")
self.add_layout_pin(text="vdd",
layer="m1",
offset=vdd_pin.ll().scale(0, 1),
width=self.width,
height=vdd_pin.height())
# Continous gnd rail along with label.
gnd_pin = self.inv_inst.get_pin("gnd")
self.add_layout_pin(text="gnd",
layer="m1",
offset=gnd_pin.ll().scale(0, 1),
width=self.width,
height=vdd_pin.height())
pin = self.inv_inst.get_pin("Z")
self.add_layout_pin_rect_center(text="Z",
layer=pin.layer,

View File

@ -37,6 +37,7 @@ class pbuf(pgate.pgate):
self.place_insts()
self.add_wires()
self.add_layout_pins()
self.route_supply_rails()
self.add_boundary()
def add_pins(self):
@ -78,26 +79,9 @@ class pbuf(pgate.pgate):
# inv1 Z to inv2 A
z1_pin = self.inv1_inst.get_pin("Z")
a2_pin = self.inv2_inst.get_pin("A")
mid_point = vector(z1_pin.cx(), a2_pin.cy())
self.add_path("m1", [z1_pin.center(), mid_point, a2_pin.center()])
self.add_zjog(self.route_layer, z1_pin.center(), a2_pin.center())
def add_layout_pins(self):
# Continous vdd rail along with label.
vdd_pin = self.inv1_inst.get_pin("vdd")
self.add_layout_pin(text="vdd",
layer="m1",
offset=vdd_pin.ll().scale(0, 1),
width=self.width,
height=vdd_pin.height())
# Continous gnd rail along with label.
gnd_pin = self.inv1_inst.get_pin("gnd")
self.add_layout_pin(text="gnd",
layer="m1",
offset=gnd_pin.ll().scale(0, 1),
width=self.width,
height=vdd_pin.height())
z_pin = self.inv2_inst.get_pin("Z")
self.add_layout_pin_rect_center(text="Z",
layer=z_pin.layer,

View File

@ -76,6 +76,7 @@ class pdriver(pgate.pgate):
self.width = self.inv_inst_list[-1].rx()
self.height = self.inv_inst_list[0].height
self.route_supply_rails()
self.add_boundary()
def add_pins(self):
@ -141,26 +142,11 @@ class pdriver(pgate.pgate):
z_inst_list.append(self.inv_inst_list[x].get_pin("Z"))
a_inst_list.append(self.inv_inst_list[x + 1].get_pin("A"))
mid_point = vector(z_inst_list[x].cx(), a_inst_list[x].cy())
self.add_path("m1",
self.add_path(self.route_layer,
[z_inst_list[x].center(), mid_point,
a_inst_list[x].center()])
def add_layout_pins(self):
# Continous vdd rail along with label.
vdd_pin = self.inv_inst_list[0].get_pin("vdd")
self.add_layout_pin(text="vdd",
layer="m1",
offset=vdd_pin.ll().scale(0, 1),
width=self.width,
height=vdd_pin.height())
# Continous gnd rail along with label.
gnd_pin = self.inv_inst_list[0].get_pin("gnd")
self.add_layout_pin(text="gnd",
layer="m1",
offset=gnd_pin.ll().scale(0, 1),
width=self.width,
height=vdd_pin.height())
z_pin = self.inv_inst_list[len(self.inv_inst_list) - 1].get_pin("Z")
self.add_layout_pin_rect_center(text="Z",

View File

@ -17,6 +17,7 @@ from globals import OPTS
if(OPTS.tech_name == "s8"):
from tech import nmos_bins, pmos_bins, accuracy_requirement
class pgate(design.design):
"""
This is a module that implements some shared
@ -30,9 +31,25 @@ class pgate(design.design):
if height:
self.height = height
elif not height:
# By default, we make it 10 M1 pitch tall
self.height = 10*self.m1_pitch
# By default, something simple
self.height = 14 * self.m1_pitch
if "li" in layer:
self.route_layer = "li"
else:
self.route_layer = "m1"
self.route_layer_width = getattr(self, "{}_width".format(self.route_layer))
self.route_layer_space = getattr(self, "{}_space".format(self.route_layer))
self.route_layer_pitch = getattr(self, "{}_pitch".format(self.route_layer))
# This is the space from a S/D contact to the supply rail
# Assume the contact starts at the active edge
contact_to_vdd_rail_space = 0.5 * self.m1_width + self.m1_space
# This is a poly-to-poly of a flipped cell
poly_to_poly_gate_space = self.poly_extend_active + self.poly_space
self.top_bottom_space = max(contact_to_vdd_rail_space,
poly_to_poly_gate_space)
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
@ -47,27 +64,27 @@ class pgate(design.design):
""" Pure virtual function """
debug.error("Must over-ride create_layout.", -1)
def connect_pin_to_rail(self, inst, pin, supply):
def connect_pin_to_rail(self, inst, pin_name, supply_name):
""" Connects a ptx pin to a supply rail. """
source_pin = inst.get_pin(pin)
supply_pin = self.get_pin(supply)
if supply_pin.overlaps(source_pin):
return
if supply == "gnd":
height = supply_pin.by() - source_pin.by()
elif supply == "vdd":
height = supply_pin.uy() - source_pin.by()
else:
debug.error("Invalid supply name.", -1)
supply_pin = self.get_pin(supply_name)
if abs(height) > 0:
self.add_rect(layer="m1",
source_pins = inst.get_pins(pin_name)
for source_pin in source_pins:
if supply_name == "gnd":
height = supply_pin.by() - source_pin.by()
elif supply_name == "vdd":
height = supply_pin.uy() - source_pin.by()
else:
debug.error("Invalid supply name.", -1)
debug.check(supply_pin.layer == source_pin.layer, "Supply pin is not on correct layer.")
self.add_rect(layer=source_pin.layer,
offset=source_pin.ll(),
height=height,
width=source_pin.width())
def route_input_gate(self, pmos_inst, nmos_inst, ypos, name, position="left"):
def route_input_gate(self, pmos_inst, nmos_inst, ypos, name, position="left", directions=None):
"""
Route the input gate to the left side of the cell for access.
Position specifies to place the contact the left, center, or
@ -93,8 +110,6 @@ class pgate(design.design):
# Center is completely symmetric.
contact_width = contact.poly_contact.width
contact_m1_width = contact.poly_contact.second_layer_width
contact_m1_height = contact.poly_contact.second_layer_height
if position == "center":
contact_offset = left_gate_offset \
@ -111,18 +126,16 @@ class pgate(design.design):
else:
debug.error("Invalid contact placement option.", -1)
if hasattr(self, "li_stack"):
self.add_via_center(layers=self.li_stack,
offset=contact_offset)
self.add_via_center(layers=self.poly_stack,
offset=contact_offset)
via = self.add_via_stack_center(from_layer="poly",
to_layer=self.route_layer,
offset=contact_offset,
directions=directions)
self.add_layout_pin_rect_center(text=name,
layer="m1",
layer=self.route_layer,
offset=contact_offset,
width=contact_m1_width,
height=contact_m1_height)
width=via.mod.second_layer_width,
height=via.mod.second_layer_height)
# This is to ensure that the contact is
# connected to the gate
mid_point = contact_offset.scale(0.5, 1) \
@ -194,12 +207,10 @@ class pgate(design.design):
self.nwell_contact = self.add_via_center(layers=layer_stack,
offset=contact_offset,
implant_type="n",
well_type="n")
if hasattr(self, "li_stack"):
self.add_via_center(layers=self.li_stack,
offset=contact_offset)
well_type="n",
directions=("V", "V"))
self.add_rect_center(layer="m1",
self.add_rect_center(layer=self.route_layer,
offset=contact_offset + vector(0, 0.5 * (self.height - contact_offset.y)),
width=self.nwell_contact.mod.second_layer_width,
height=self.height - contact_offset.y)
@ -249,13 +260,10 @@ class pgate(design.design):
self.pwell_contact= self.add_via_center(layers=layer_stack,
offset=contact_offset,
implant_type="p",
well_type="p")
if hasattr(self, "li_stack"):
self.add_via_center(layers=self.li_stack,
offset=contact_offset)
self.add_rect_center(layer="m1",
well_type="p",
directions=("V", "V"))
self.add_rect_center(layer=self.route_layer,
offset=contact_offset.scale(1, 0.5),
width=self.pwell_contact.mod.second_layer_width,
height=contact_offset.y)
@ -279,6 +287,18 @@ class pgate(design.design):
# offset=implant_offset,
# width=implant_width,
# height=implant_height)
def route_supply_rails(self):
""" Add vdd/gnd rails to the top and bottom. """
self.add_layout_pin_rect_center(text="gnd",
layer=self.route_layer,
offset=vector(0.5 * self.width, 0),
width=self.width)
self.add_layout_pin_rect_center(text="vdd",
layer=self.route_layer,
offset=vector(0.5 * self.width, self.height),
width=self.width)
def determine_width(self):
""" Determine the width based on the well contacts (assumed to be on the right side) """
@ -286,9 +306,10 @@ class pgate(design.design):
self.width = max(self.nwell_contact.rx(), self.pwell_contact.rx()) + self.m1_space + 0.5 * contact.m1_via.width
self.well_width = self.width + 2 * self.nwell_enclose_active
# Height is an input parameter, so it is not recomputed.
def bin_width(self, tx_type, target_width):
@staticmethod
def bin_width(tx_type, target_width):
if tx_type == "nmos":
bins = nmos_bins[drc("minwidth_poly")]
elif tx_type == "pmos":
@ -303,22 +324,24 @@ class pgate(design.design):
scaled_bin = bins[0] * scaling_factor
else:
base_bins = []
scaled_bins = []
scaling_factors = []
scaled_bins.append(bins[-1])
base_bins.append(bins[-1])
scaling_factors.append(1)
for width in bins[0:-1]:
m = math.ceil(target_width / width)
base_bins.append(width)
scaling_factors.append(m)
scaled_bins.append(m * width)
select = bisect_left(scaled_bins, target_width)
scaling_factor = scaling_factors[select]
scaled_bin = scaled_bins[select]
select = (select + 1) % len(scaled_bins)
selected_bin = bins[select]
selected_bin = base_bins[select]
debug.info(2, "binning {0} tx, target: {4}, found {1} x {2} = {3}".format(tx_type, selected_bin, scaling_factor, scaled_bin, target_width))
debug.info(2, "binning {0} tx, target: {4}, found {1} x {2} = {3}".format(tx_type, selected_bin, scaling_factor, selected_bin * scaling_factor, target_width))
return(selected_bin, scaling_factor)
@ -343,4 +366,4 @@ class pgate(design.design):
return(scaled_bins)
def bin_accuracy(self, ideal_width, width):
return abs(1-(ideal_width - width)/ideal_width)
return abs(1-(ideal_width - width)/ideal_width)

View File

@ -22,17 +22,17 @@ from errors import drc_error
if(OPTS.tech_name == "s8"):
from tech import nmos_bins, pmos_bins, accuracy_requirement
class pinv(pgate.pgate):
"""
Pinv generates gds of a parametrically sized inverter. The
size is specified as the drive size (relative to minimum NMOS) and
a beta value for choosing the pmos size. The inverter's cell
height is usually the same as the 6t library cell and is measured
from center of rail to rail.. The route_output will route the
output to the right side of the cell for easier access.
from center of rail to rail.
"""
def __init__(self, name, size=1, beta=parameter["beta"], height=None, route_output=True):
def __init__(self, name, size=1, beta=parameter["beta"], height=None):
debug.info(2,
"creating pinv structure {0} with size of {1}".format(name,
@ -43,7 +43,6 @@ class pinv(pgate.pgate):
self.nmos_size = size
self.pmos_size = beta * size
self.beta = beta
self.route_output = False
pgate.pgate.__init__(self, name, height)
@ -108,13 +107,6 @@ class pinv(pgate.pgate):
min_channel = max(contact.poly_contact.width + self.m1_space,
contact.poly_contact.width + 2 * self.poly_to_active)
# This is the extra space needed to ensure DRC rules
# to the active contacts
extra_contact_space = max(-nmos.get_pin("D").by(), 0)
# This is a poly-to-poly of a flipped cell
self.top_bottom_space = max(0.5*self.m1_width + self.m1_space + extra_contact_space,
self.poly_extend_active + self.poly_space)
total_height = tx_height + min_channel + 2 * self.top_bottom_space
# debug.check(self.height > total_height,
# "Cell height {0} too small for simple min height {1}.".format(self.height,
@ -202,30 +194,22 @@ class pinv(pgate.pgate):
width=self.nmos_width,
mults=self.tx_mults,
tx_type="nmos",
add_source_contact=self.route_layer,
add_drain_contact=self.route_layer,
connect_poly=True,
connect_active=True)
connect_drain_active=True)
self.add_mod(self.nmos)
self.pmos = factory.create(module_type="ptx",
width=self.pmos_width,
mults=self.tx_mults,
tx_type="pmos",
add_source_contact=self.route_layer,
add_drain_contact=self.route_layer,
connect_poly=True,
connect_active=True)
connect_drain_active=True)
self.add_mod(self.pmos)
def route_supply_rails(self):
""" Add vdd/gnd rails to the top and bottom. """
self.add_layout_pin_rect_center(text="gnd",
layer="m1",
offset=vector(0.5 * self.width, 0),
width=self.width)
self.add_layout_pin_rect_center(text="vdd",
layer="m1",
offset=vector(0.5 * self.width, self.height),
width=self.width)
def create_ptx(self):
"""
Create the PMOS and NMOS netlist.
@ -266,7 +250,7 @@ class pinv(pgate.pgate):
Route the output (drains) together.
Optionally, routes output to edge.
"""
# Get the drain pins
nmos_drain_pin = self.nmos_inst.get_pin("D")
pmos_drain_pin = self.pmos_inst.get_pin("D")
@ -274,24 +258,16 @@ class pinv(pgate.pgate):
# Pick point at right most of NMOS and connect down to PMOS
nmos_drain_pos = nmos_drain_pin.bc()
pmos_drain_pos = vector(nmos_drain_pos.x, pmos_drain_pin.uc().y)
self.add_path("m1", [nmos_drain_pos, pmos_drain_pos])
self.add_path(self.route_layer, [nmos_drain_pos, pmos_drain_pos])
# Remember the mid for the output
mid_drain_offset = vector(nmos_drain_pos.x, self.output_pos.y)
if self.route_output:
# This extends the output to the edge of the cell
output_offset = mid_drain_offset.scale(0, 1) + vector(self.width, 0)
self.add_layout_pin_segment_center(text="Z",
layer="m1",
start=mid_drain_offset,
end=output_offset)
else:
# This leaves the output as an internal pin (min sized)
self.add_layout_pin_rect_center(text="Z",
layer="m1",
offset=mid_drain_offset \
+ vector(0.5 * self.m1_width, 0))
# This leaves the output as an internal pin (min sized)
output_offset = mid_drain_offset + vector(0.5 * self.route_layer_width, 0)
self.add_layout_pin_rect_center(text="Z",
layer=self.route_layer,
offset=output_offset)
def add_well_contacts(self):
""" Add n/p well taps to the layout and connect to supplies """

View File

@ -72,65 +72,45 @@ class pnand2(pgate.pgate):
def add_ptx(self):
""" Create the PMOS and NMOS transistors. """
self.nmos_nd = factory.create(module_type="ptx",
width=self.nmos_width,
mults=self.tx_mults,
tx_type="nmos",
add_drain_contact=False,
connect_poly=True,
connect_active=True)
self.add_mod(self.nmos_nd)
self.nmos_left = factory.create(module_type="ptx",
width=self.nmos_width,
mults=self.tx_mults,
tx_type="nmos",
add_source_contact=self.route_layer,
add_drain_contact="active")
self.add_mod(self.nmos_left)
self.nmos_ns = factory.create(module_type="ptx",
width=self.nmos_width,
mults=self.tx_mults,
tx_type="nmos",
add_source_contact=False,
connect_poly=True,
connect_active=True)
self.add_mod(self.nmos_ns)
self.nmos_right = factory.create(module_type="ptx",
width=self.nmos_width,
mults=self.tx_mults,
tx_type="nmos",
add_source_contact="active",
add_drain_contact=self.route_layer)
self.add_mod(self.nmos_right)
self.pmos = factory.create(module_type="ptx",
width=self.pmos_width,
mults=self.tx_mults,
tx_type="pmos",
connect_poly=True,
connect_active=True)
self.add_mod(self.pmos)
self.pmos_left = factory.create(module_type="ptx",
width=self.pmos_width,
mults=self.tx_mults,
tx_type="pmos",
add_source_contact=self.route_layer,
add_drain_contact=self.route_layer)
self.add_mod(self.pmos_left)
self.pmos_right = factory.create(module_type="ptx",
width=self.pmos_width,
mults=self.tx_mults,
tx_type="pmos",
add_source_contact=self.route_layer,
add_drain_contact=self.route_layer)
self.add_mod(self.pmos_right)
def setup_layout_constants(self):
""" Pre-compute some handy layout parameters. """
# metal spacing to allow contacts on any layer
self.input_spacing = max(self.poly_space + contact.poly_contact.first_layer_width,
self.m1_space + contact.m1_via.first_layer_width,
self.m2_space + contact.m2_via.first_layer_width,
self.m3_space + contact.m2_via.second_layer_width)
# 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()
# This is the extra space needed to ensure DRC rules
# to the active contacts
extra_contact_space = max(-self.nmos_nd.get_pin("D").by(), 0)
# This is a poly-to-poly of a flipped cell
self.top_bottom_space = max(0.5 * self.m1_width + self.m1_space + extra_contact_space,
self.poly_extend_active + self.poly_space)
def route_supply_rails(self):
""" Add vdd/gnd rails to the top and bottom. """
self.add_layout_pin_rect_center(text="gnd",
layer="m1",
offset=vector(0.5*self.width, 0),
width=self.width)
self.add_layout_pin_rect_center(text="vdd",
layer="m1",
offset=vector(0.5 * self.width, self.height),
width=self.width)
self.overlap_offset = self.pmos_left.get_pin("D").center() - self.pmos_left.get_pin("S").center()
def create_ptx(self):
"""
@ -138,19 +118,19 @@ class pnand2(pgate.pgate):
"""
self.pmos1_inst = self.add_inst(name="pnand2_pmos1",
mod=self.pmos)
mod=self.pmos_left)
self.connect_inst(["vdd", "A", "Z", "vdd"])
self.pmos2_inst = self.add_inst(name="pnand2_pmos2",
mod=self.pmos)
mod=self.pmos_right)
self.connect_inst(["Z", "B", "vdd", "vdd"])
self.nmos1_inst = self.add_inst(name="pnand2_nmos1",
mod=self.nmos_nd)
mod=self.nmos_left)
self.connect_inst(["Z", "B", "net1", "gnd"])
self.nmos2_inst = self.add_inst(name="pnand2_nmos2",
mod=self.nmos_ns)
mod=self.nmos_right)
self.connect_inst(["net1", "A", "gnd", "gnd"])
def place_ptx(self):
@ -159,35 +139,29 @@ class pnand2(pgate.pgate):
to provide maximum routing in channel
"""
pmos1_pos = vector(self.pmos.active_offset.x,
self.height - self.pmos.active_height \
pmos1_pos = vector(self.pmos_left.active_offset.x,
self.height - self.pmos_left.active_height \
- self.top_bottom_space)
self.pmos1_inst.place(pmos1_pos)
self.pmos2_pos = pmos1_pos + self.overlap_offset
self.pmos2_inst.place(self.pmos2_pos)
nmos1_pos = vector(self.pmos.active_offset.x,
nmos1_pos = vector(self.pmos_left.active_offset.x,
self.top_bottom_space)
self.nmos1_inst.place(nmos1_pos)
self.nmos2_pos = nmos1_pos + self.overlap_offset
self.nmos2_inst.place(self.nmos2_pos)
# Output position will be in between the PMOS and NMOS
self.output_pos = vector(0,
0.5 * (pmos1_pos.y + nmos1_pos.y + self.nmos_nd.active_height))
def add_well_contacts(self):
"""
Add n/p well taps to the layout and connect to supplies
AFTER the wells are created
"""
self.add_nwell_contact(self.pmos,
self.pmos2_pos + vector(self.m1_pitch, 0))
self.add_pwell_contact(self.nmos_nd,
self.nmos2_pos + vector(self.m1_pitch, 0))
self.add_nwell_contact(self.pmos_right, self.pmos2_pos)
self.add_pwell_contact(self.nmos_left, self.nmos2_pos)
def connect_rails(self):
""" Connect the nmos and pmos to its respective power rails """
@ -200,35 +174,46 @@ class pnand2(pgate.pgate):
def route_inputs(self):
""" Route the A and B inputs """
inputB_yoffset = self.nmos2_inst.uy() + 0.5 * contact.poly_contact.height
# Top of NMOS drain
nmos_pin = self.nmos2_inst.get_pin("D")
bottom_pin_offset = nmos_pin.uy()
self.inputA_yoffset = bottom_pin_offset + self.m1_pitch
self.inputB_yoffset = self.inputA_yoffset + self.m3_pitch
# This will help with the wells and the input/output placement
self.route_input_gate(self.pmos2_inst,
self.nmos2_inst,
inputB_yoffset,
self.inputB_yoffset,
"B",
position="center")
# This will help with the wells and the input/output placement
self.inputA_yoffset = self.pmos2_inst.by() - self.poly_extend_active \
- contact.poly_contact.height
self.route_input_gate(self.pmos1_inst,
self.nmos1_inst,
self.inputA_yoffset,
"A")
"A",
position="center")
def route_output(self):
""" Route the Z output """
# One routing track layer below the PMOS contacts
route_layer_offset = 0.5 * self.route_layer_width + self.route_layer_space
output_yoffset = self.pmos1_inst.get_pin("D").by() - route_layer_offset
# PMOS1 drain
pmos_pin = self.pmos1_inst.get_pin("D")
top_pin_offset = pmos_pin.center()
top_pin_offset = pmos_pin.bc()
# NMOS2 drain
nmos_pin = self.nmos2_inst.get_pin("D")
bottom_pin_offset = nmos_pin.center()
bottom_pin_offset = nmos_pin.uc()
# Output pin
c_pin = self.get_pin("B")
out_offset = vector(c_pin.cx() + self.m1_pitch,
self.inputA_yoffset)
out_offset = vector(nmos_pin.cx() + self.route_layer_pitch,
output_yoffset)
# This routes on M2
# # Midpoints of the L routes go horizontal first then vertical
@ -251,27 +236,22 @@ class pnand2(pgate.pgate):
# [top_pin_offset, mid1_offset, out_offset,
# mid2_offset, bottom_pin_offset])
# This routes on M1
# This routes on route_layer
# Midpoints of the L routes goes vertical first then horizontal
mid1_offset = vector(top_pin_offset.x, out_offset.y)
# Midpoints of the L routes goes horizontal first then vertical
mid2_offset = vector(out_offset.x, bottom_pin_offset.y)
top_mid_offset = vector(top_pin_offset.x, out_offset.y)
# Top transistors
self.add_path(self.route_layer,
[top_pin_offset, top_mid_offset, out_offset])
self.add_path("m1",
[top_pin_offset, mid1_offset, out_offset])
# Route in two segments to have the width rule
self.add_path("m1",
[bottom_pin_offset, mid2_offset + vector(0.5 * self.m1_width, 0)],
width=nmos_pin.height())
self.add_path("m1",
[mid2_offset, out_offset])
bottom_mid_offset = bottom_pin_offset + vector(0, self.route_layer_pitch)
# Bottom transistors
self.add_path(self.route_layer,
[out_offset, bottom_mid_offset, bottom_pin_offset])
# This extends the output to the edge of the cell
self.add_layout_pin_rect_center(text="Z",
layer="m1",
offset=out_offset,
width=contact.m1_via.first_layer_width,
height=contact.m1_via.first_layer_height)
layer=self.route_layer,
offset=out_offset)
def analytical_power(self, corner, load):
"""Returns dynamic and leakage power. Results in nW"""

View File

@ -5,7 +5,6 @@
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
import contact
import pgate
import debug
from tech import drc, parameter, spice
@ -14,6 +13,7 @@ import logical_effort
from sram_factory import factory
from globals import OPTS
class pnand3(pgate.pgate):
"""
This module generates gds of a parametrically sized 2-input nand.
@ -74,68 +74,64 @@ class pnand3(pgate.pgate):
def add_ptx(self):
""" Create the PMOS and NMOS transistors. """
self.nmos_nsnd = factory.create(module_type="ptx",
self.nmos_center = factory.create(module_type="ptx",
width=self.nmos_width,
mults=self.tx_mults,
tx_type="nmos",
add_source_contact="active",
add_drain_contact="active")
self.add_mod(self.nmos_center)
self.nmos_right = factory.create(module_type="ptx",
width=self.nmos_width,
mults=self.tx_mults,
tx_type="nmos",
add_source_contact="active",
add_drain_contact=self.route_layer)
self.add_mod(self.nmos_right)
self.nmos_left = factory.create(module_type="ptx",
width=self.nmos_width,
mults=self.tx_mults,
tx_type="nmos",
add_source_contact=False,
add_drain_contact=False,
connect_poly=True,
connect_active=True)
self.add_mod(self.nmos_nsnd)
self.nmos_ns = factory.create(module_type="ptx",
width=self.nmos_width,
mults=self.tx_mults,
tx_type="nmos",
add_source_contact=False,
connect_poly=True,
connect_active=True)
self.add_mod(self.nmos_ns)
self.nmos_nd = factory.create(module_type="ptx",
width=self.nmos_width,
mults=self.tx_mults,
tx_type="nmos",
add_drain_contact=False,
connect_poly=True,
connect_active=True)
self.add_mod(self.nmos_nd)
add_source_contact=self.route_layer,
add_drain_contact="active")
self.add_mod(self.nmos_left)
self.pmos = factory.create(module_type="ptx",
width=self.pmos_width,
mults=self.tx_mults,
tx_type="pmos",
connect_poly=True,
connect_active=True)
self.add_mod(self.pmos)
self.pmos_left = factory.create(module_type="ptx",
width=self.pmos_width,
mults=self.tx_mults,
tx_type="pmos",
add_source_contact=self.route_layer,
add_drain_contact=self.route_layer)
self.add_mod(self.pmos_left)
self.pmos_center = factory.create(module_type="ptx",
width=self.pmos_width,
mults=self.tx_mults,
tx_type="pmos",
add_source_contact=self.route_layer,
add_drain_contact=self.route_layer)
self.add_mod(self.pmos_center)
self.pmos_right = factory.create(module_type="ptx",
width=self.pmos_width,
mults=self.tx_mults,
tx_type="pmos",
add_source_contact=self.route_layer,
add_drain_contact=self.route_layer)
self.add_mod(self.pmos_right)
def setup_layout_constants(self):
""" Pre-compute some handy layout parameters. """
# Compute the overlap of the source and drain pins
overlap_xoffset = self.pmos.get_pin("D").ll().x - self.pmos.get_pin("S").ll().x
self.ptx_offset = vector(overlap_xoffset, 0)
self.ptx_offset = self.pmos_left.get_pin("D").center() - self.pmos_left.get_pin("S").center()
# This is the extra space needed to ensure DRC rules
# to the active contacts
nmos = factory.create(module_type="ptx", tx_type="nmos")
extra_contact_space = max(-nmos.get_pin("D").by(), 0)
# This is a poly-to-poly of a flipped cell
self.top_bottom_space = max(0.5 * self.m1_width + self.m1_space + extra_contact_space,
self.poly_extend_active + self.poly_space)
def route_supply_rails(self):
""" Add vdd/gnd rails to the top and bottom. """
self.add_layout_pin_rect_center(text="gnd",
layer="m1",
offset=vector(0.5 * self.width, 0),
width=self.width)
self.add_layout_pin_rect_center(text="vdd",
layer="m1",
offset=vector(0.5 * self.width, self.height),
width=self.width)
def create_ptx(self):
"""
@ -143,27 +139,27 @@ class pnand3(pgate.pgate):
"""
self.pmos1_inst = self.add_inst(name="pnand3_pmos1",
mod=self.pmos)
mod=self.pmos_left)
self.connect_inst(["vdd", "A", "Z", "vdd"])
self.pmos2_inst = self.add_inst(name="pnand3_pmos2",
mod=self.pmos)
mod=self.pmos_center)
self.connect_inst(["Z", "B", "vdd", "vdd"])
self.pmos3_inst = self.add_inst(name="pnand3_pmos3",
mod=self.pmos)
mod=self.pmos_right)
self.connect_inst(["Z", "C", "vdd", "vdd"])
self.nmos1_inst = self.add_inst(name="pnand3_nmos1",
mod=self.nmos_nd)
mod=self.nmos_left)
self.connect_inst(["Z", "C", "net1", "gnd"])
self.nmos2_inst = self.add_inst(name="pnand3_nmos2",
mod=self.nmos_nsnd)
mod=self.nmos_center)
self.connect_inst(["net1", "B", "net2", "gnd"])
self.nmos3_inst = self.add_inst(name="pnand3_nmos3",
mod=self.nmos_ns)
mod=self.nmos_right)
self.connect_inst(["net2", "A", "gnd", "gnd"])
def place_ptx(self):
@ -172,8 +168,8 @@ class pnand3(pgate.pgate):
and lowest position to provide maximum routing in channel
"""
pmos1_pos = vector(self.pmos.active_offset.x,
self.height - self.pmos.active_height - self.top_bottom_space)
pmos1_pos = vector(self.pmos_left.active_offset.x,
self.height - self.pmos_left.active_height - self.top_bottom_space)
self.pmos1_inst.place(pmos1_pos)
pmos2_pos = pmos1_pos + self.ptx_offset
@ -182,7 +178,7 @@ class pnand3(pgate.pgate):
self.pmos3_pos = pmos2_pos + self.ptx_offset
self.pmos3_inst.place(self.pmos3_pos)
nmos1_pos = vector(self.pmos.active_offset.x,
nmos1_pos = vector(self.pmos_left.active_offset.x,
self.top_bottom_space)
self.nmos1_inst.place(nmos1_pos)
@ -195,9 +191,9 @@ class pnand3(pgate.pgate):
def add_well_contacts(self):
""" Add n/p well taps to the layout and connect to supplies """
self.add_nwell_contact(self.pmos,
self.add_nwell_contact(self.pmos_right,
self.pmos3_pos + vector(self.m1_pitch, 0))
self.add_pwell_contact(self.nmos_ns,
self.add_pwell_contact(self.nmos_right,
self.nmos3_pos + vector(self.m1_pitch, 0))
def connect_rails(self):
@ -212,37 +208,34 @@ class pnand3(pgate.pgate):
def route_inputs(self):
""" Route the A and B and C inputs """
m1_pitch = self.m1_space + contact.m1_via.first_layer_height
# Put B right on the well line
self.inputB_yoffset = self.nwell_y_offset
self.route_input_gate(self.pmos2_inst,
self.nmos2_inst,
self.inputB_yoffset,
"B",
position="center")
# FIXME: constant hack
self.inputC_yoffset = self.inputB_yoffset - 1.15 * m1_pitch
self.route_input_gate(self.pmos3_inst,
self.nmos3_inst,
self.inputC_yoffset,
"C",
position="right")
pmos_drain_bottom = self.pmos1_inst.get_pin("D").by()
self.output_yoffset = pmos_drain_bottom - 0.5 * self.route_layer_width - self.route_layer_space
# FIXME: constant hack
if OPTS.tech_name == "s8":
self.inputA_yoffset = self.inputB_yoffset + 1.15 * m1_pitch
else:
self.inputA_yoffset = self.inputB_yoffset + 1.12 * m1_pitch
self.inputA_yoffset = self.output_yoffset - 0.5 * self.route_layer_width - self.route_layer_space
self.route_input_gate(self.pmos1_inst,
self.nmos1_inst,
self.inputA_yoffset,
"A",
position="left")
# Put B right on the well line
self.inputB_yoffset = self.inputA_yoffset - self.m1_pitch
self.route_input_gate(self.pmos2_inst,
self.nmos2_inst,
self.inputB_yoffset,
"B",
position="center")
self.inputC_yoffset = self.inputB_yoffset - self.m1_pitch
self.route_input_gate(self.pmos3_inst,
self.nmos3_inst,
self.inputC_yoffset,
"C",
position="right")
def route_output(self):
""" Route the Z output """
# PMOS1 drain
pmos1_pin = self.pmos1_inst.get_pin("D")
# PMOS3 drain
@ -250,14 +243,9 @@ class pnand3(pgate.pgate):
# NMOS3 drain
nmos3_pin = self.nmos3_inst.get_pin("D")
# midpoint for routing
mid_offset = vector(nmos3_pin.cx() + self.m1_pitch,
self.inputA_yoffset)
out_offset = vector(nmos3_pin.cx() + self.route_layer_pitch,
self.output_yoffset)
# Aligned with the well taps
out_offset = vector(self.nwell_contact.cx(),
self.inputA_yoffset)
# Go up to metal2 for ease on all output pins
# self.add_via_center(layers=self.m1_stack,
# offset=pmos1_pin.center(),
@ -282,26 +270,24 @@ class pnand3(pgate.pgate):
bottom_pin_offset = nmos3_pin.center()
# PMOS1 to output
self.add_path("m1", [top_left_pin_offset,
vector(top_left_pin_offset.x, out_offset.y),
out_offset])
self.add_path(self.route_layer, [top_left_pin_offset,
vector(top_left_pin_offset.x, out_offset.y),
out_offset])
# PMOS3 to output
self.add_path("m1", [top_right_pin_offset,
vector(top_right_pin_offset.x, mid_offset.y),
mid_offset])
self.add_path(self.route_layer, [top_right_pin_offset,
vector(top_right_pin_offset.x, out_offset.y),
out_offset])
# NMOS3 to output
mid2_offset = vector(mid_offset.x, bottom_pin_offset.y)
self.add_path("m1",
mid2_offset = vector(out_offset.x, bottom_pin_offset.y)
self.add_path(self.route_layer,
[bottom_pin_offset, mid2_offset],
width=nmos3_pin.height())
mid3_offset = vector(mid_offset.x, nmos3_pin.by())
self.add_path("m1", [mid3_offset, mid_offset])
mid3_offset = vector(out_offset.x, nmos3_pin.by())
self.add_path(self.route_layer, [mid3_offset, out_offset])
self.add_layout_pin_rect_center(text="Z",
layer="m1",
offset=out_offset,
width=contact.m1_via.first_layer_width,
height=contact.m1_via.first_layer_height)
layer=self.route_layer,
offset=out_offset)
def analytical_power(self, corner, load):
"""Returns dynamic and leakage power. Results in nW"""

View File

@ -71,74 +71,54 @@ class pnor2(pgate.pgate):
def add_ptx(self):
""" Create the PMOS and NMOS transistors. """
self.nmos = factory.create(module_type="ptx",
width=self.nmos_width,
mults=self.tx_mults,
tx_type="nmos",
connect_poly=True,
connect_active=True)
self.add_mod(self.nmos)
self.nmos_left = factory.create(module_type="ptx",
width=self.nmos_width,
mults=self.tx_mults,
tx_type="nmos",
add_source_contact=self.route_layer,
add_drain_contact=self.route_layer)
self.add_mod(self.nmos_left)
self.pmos_nd = factory.create(module_type="ptx",
width=self.pmos_width,
mults=self.tx_mults,
tx_type="pmos",
add_drain_contact=False,
connect_poly=True,
connect_active=True)
self.add_mod(self.pmos_nd)
self.nmos_right = factory.create(module_type="ptx",
width=self.nmos_width,
mults=self.tx_mults,
tx_type="nmos",
add_source_contact=self.route_layer,
add_drain_contact=self.route_layer)
self.add_mod(self.nmos_right)
self.pmos_left = factory.create(module_type="ptx",
width=self.pmos_width,
mults=self.tx_mults,
tx_type="pmos",
add_source_contact=self.route_layer,
add_drain_contact="active")
self.add_mod(self.pmos_left)
self.pmos_ns = factory.create(module_type="ptx",
width=self.pmos_width,
mults=self.tx_mults,
tx_type="pmos",
add_source_contact=False,
connect_poly=True,
connect_active=True)
self.add_mod(self.pmos_ns)
self.pmos_right = factory.create(module_type="ptx",
width=self.pmos_width,
mults=self.tx_mults,
tx_type="pmos",
add_source_contact="active",
add_drain_contact=self.route_layer)
self.add_mod(self.pmos_right)
def setup_layout_constants(self):
""" Pre-compute some handy layout parameters. """
# metal spacing to allow contacts on any layer
self.input_spacing = max(self.poly_space + contact.poly_contact.first_layer_width,
self.m1_space + contact.m1_via.first_layer_width,
self.m2_space + contact.m2_via.first_layer_width,
self.m3_space + contact.m2_via.second_layer_width)
# Compute the other pmos2 location, but determining
# offset to overlap the source and drain pins
self.overlap_offset = self.pmos_ns.get_pin("D").ll() - self.pmos_nd.get_pin("S").ll()
self.overlap_offset = self.pmos_right.get_pin("D").center() - self.pmos_left.get_pin("S").center()
# Two PMOS devices and a well contact. Separation between each.
# Enclosure space on the sides.
self.width = 2 * self.pmos_ns.active_width \
+ self.pmos_ns.active_contact.width \
self.width = 2 * self.pmos_right.active_width \
+ self.pmos_right.active_contact.width \
+ 2 * self.active_space \
+ 0.5 * self.nwell_enclose_active
self.well_width = self.width + 2 * self.nwell_enclose_active
# Height is an input parameter, so it is not recomputed.
# This is the extra space needed to ensure DRC rules
# to the active contacts
extra_contact_space = max(-self.nmos.get_pin("D").by(), 0)
# This is a poly-to-poly of a flipped cell
self.top_bottom_space = max(0.5 * self.m1_width + self.m1_space + extra_contact_space,
self.poly_extend_active,
self.poly_space)
def route_supply_rails(self):
""" Add vdd/gnd rails to the top and bottom. """
self.add_layout_pin_rect_center(text="gnd",
layer="m1",
offset=vector(0.5 * self.width, 0),
width=self.width)
self.add_layout_pin_rect_center(text="vdd",
layer="m1",
offset=vector(0.5 * self.width, self.height),
width=self.width)
def create_ptx(self):
"""
Add PMOS and NMOS to the layout at the upper-most and lowest position
@ -146,19 +126,19 @@ class pnor2(pgate.pgate):
"""
self.pmos1_inst = self.add_inst(name="pnor2_pmos1",
mod=self.pmos_nd)
mod=self.pmos_left)
self.connect_inst(["vdd", "A", "net1", "vdd"])
self.pmos2_inst = self.add_inst(name="pnor2_pmos2",
mod=self.pmos_ns)
mod=self.pmos_right)
self.connect_inst(["net1", "B", "Z", "vdd"])
self.nmos1_inst = self.add_inst(name="pnor2_nmos1",
mod=self.nmos)
mod=self.nmos_left)
self.connect_inst(["Z", "A", "gnd", "gnd"])
self.nmos2_inst = self.add_inst(name="pnor2_nmos2",
mod=self.nmos)
mod=self.nmos_right)
self.connect_inst(["Z", "B", "gnd", "gnd"])
def place_ptx(self):
@ -166,30 +146,35 @@ class pnor2(pgate.pgate):
Add PMOS and NMOS to the layout at the upper-most and lowest position
to provide maximum routing in channel
"""
# Some of the S/D contacts may extend beyond the active,
# but this needs to be done in the gate itself
contact_extend_active_space = max(-self.nmos_right.get_pin("D").by(), 0)
# Assume the contact starts at the active edge
contact_to_vdd_rail_space = 0.5 * self.m1_width + self.m1_space + contact_extend_active_space
# This is a poly-to-poly of a flipped cell
poly_to_poly_gate_space = self.poly_extend_active + self.poly_space
# Recompute this since it has a small txwith the added contact extend active spacing
self.top_bottom_space = max(contact_to_vdd_rail_space,
poly_to_poly_gate_space)
pmos1_pos = vector(self.pmos_ns.active_offset.x,
self.height - self.pmos_ns.active_height \
- self.top_bottom_space)
pmos1_pos = vector(self.pmos_right.active_offset.x,
self.height - self.pmos_right.active_height - self.top_bottom_space)
self.pmos1_inst.place(pmos1_pos)
self.pmos2_pos = pmos1_pos + self.overlap_offset
self.pmos2_inst.place(self.pmos2_pos)
nmos1_pos = vector(self.pmos_ns.active_offset.x, self.top_bottom_space)
nmos1_pos = vector(self.pmos_right.active_offset.x, self.top_bottom_space)
self.nmos1_inst.place(nmos1_pos)
self.nmos2_pos = nmos1_pos + self.overlap_offset
self.nmos2_inst.place(self.nmos2_pos)
# Output position will be in between the PMOS and NMOS
self.output_pos = vector(0,
0.5 * (pmos1_pos.y + nmos1_pos.y + self.nmos.active_height))
def add_well_contacts(self):
""" Add n/p well taps to the layout and connect to supplies """
self.add_nwell_contact(self.pmos_ns, self.pmos2_pos)
self.add_pwell_contact(self.nmos, self.nmos2_pos)
self.add_nwell_contact(self.pmos_right, self.pmos2_pos)
self.add_pwell_contact(self.nmos_right, self.nmos2_pos)
def connect_rails(self):
""" Connect the nmos and pmos to its respective power rails """
@ -202,53 +187,53 @@ class pnor2(pgate.pgate):
def route_inputs(self):
""" Route the A and B inputs """
# Use M2 spaces so we can drop vias on the pins later!
inputB_yoffset = self.nmos2_inst.uy() + contact.poly_contact.height
# Top of NMOS drain
nmos_pin = self.nmos2_inst.get_pin("D")
bottom_pin_offset = nmos_pin.uy()
self.inputB_yoffset = bottom_pin_offset + self.m1_nonpref_pitch
self.inputA_yoffset = self.inputB_yoffset + self.m1_nonpref_pitch
self.route_input_gate(self.pmos2_inst,
self.nmos2_inst,
inputB_yoffset,
self.inputB_yoffset,
"B",
position="center")
position="right",
directions=("V", "V"))
# This will help with the wells and the input/output placement
self.inputA_yoffset = inputB_yoffset + self.input_spacing
self.route_input_gate(self.pmos1_inst,
self.nmos1_inst,
self.inputA_yoffset,
"A")
"A",
directions=("V", "V"))
self.output_yoffset = self.inputA_yoffset + self.m1_nonpref_pitch
def route_output(self):
""" Route the Z output """
# PMOS2 drain
# PMOS2 (right) drain
pmos_pin = self.pmos2_inst.get_pin("D")
# NMOS1 drain
# NMOS1 (left) drain
nmos_pin = self.nmos1_inst.get_pin("D")
# NMOS2 drain (for output via placement)
# NMOS2 (right) drain (for output via placement)
nmos2_pin = self.nmos2_inst.get_pin("D")
# Go up to metal2 for ease on all output pins
self.add_via_center(layers=self.m1_stack,
offset=pmos_pin.center())
m1m2_contact = self.add_via_center(layers=self.m1_stack,
offset=nmos_pin.center())
mid1_offset = vector(pmos_pin.center().x, nmos2_pin.center().y)
mid2_offset = vector(pmos_pin.center().x, self.inputA_yoffset)
mid3_offset = mid2_offset + vector(self.m2_width, 0)
# self.add_via_center(layers=self.m1_stack,
# offset=pmos_pin.center())
# m1m2_contact = self.add_via_center(layers=self.m1_stack,
# offset=nmos_pin.center())
mid1_offset = vector(nmos_pin.center().x, self.output_yoffset)
mid2_offset = vector(pmos_pin.center().x, self.output_yoffset)
# PMOS1 to mid-drain to NMOS2 drain
self.add_path("m2",
[pmos_pin.center(), mid2_offset, mid3_offset])
self.add_path("m2",
[nmos_pin.rc(), mid1_offset, mid2_offset])
# This extends the output to the edge of the cell
self.add_via_center(layers=self.m1_stack,
offset=mid3_offset)
self.add_path(self.route_layer,
[nmos_pin.center(), mid1_offset, mid2_offset, pmos_pin.center()])
self.add_layout_pin_rect_center(text="Z",
layer="m1",
offset=mid3_offset,
width=contact.m1_via.first_layer_height,
height=contact.m1_via.first_layer_width)
layer=self.route_layer,
offset=mid2_offset)
def analytical_power(self, corner, load):
"""Returns dynamic and leakage power. Results in nW"""

View File

@ -81,7 +81,7 @@ class precharge(design.design):
Initializes the upper and lower pmos
"""
if(OPTS.tech_name == "s8"):
(self.ptx_width, self.ptx_mults) = pgate.bin_width(self, "pmos", self.ptx_width)
(self.ptx_width, self.ptx_mults) = pgate.bin_width("pmos", self.ptx_width)
self.pmos = factory.create(module_type="ptx",
width=self.ptx_width,
mults=self.ptx_mults,
@ -114,7 +114,7 @@ class precharge(design.design):
self.add_power_pin("vdd",
self.well_contact_pos,
vertical=True)
directions=("V", "V"))
# Hack for li layers
if hasattr(self, "li_stack"):
@ -159,7 +159,7 @@ class precharge(design.design):
self.lower_pmos_inst.place(self.lower_pmos_position)
# adds the upper pmos(s) to layout with 2 M2 tracks
ydiff = self.pmos.height + self.m2_pitch
ydiff = self.pmos.height + 2 * self.m2_pitch
self.upper_pmos1_pos = self.lower_pmos_position + vector(0, ydiff)
self.upper_pmos1_inst.place(self.upper_pmos1_pos)

View File

@ -79,9 +79,6 @@ class ptristate_inv(pgate.pgate):
self.width = self.well_width + 0.5 * self.m1_space
# Height is an input parameter, so it is not recomputed.
# Make sure we can put a well above and below
self.top_bottom_space = max(contact.active_contact.width, contact.active_contact.height)
def add_ptx(self):
""" Create the PMOS and NMOS transistors. """
self.nmos = factory.create(module_type="ptx",

View File

@ -14,7 +14,7 @@ import contact
import logical_effort
import os
from globals import OPTS
from pgate import pgate
class ptx(design.design):
"""
@ -23,32 +23,47 @@ class ptx(design.design):
the transistor width. Mults is the number of transistors of the
given width. Total width is therefore mults*width. Options allow
you to connect the fingered gates and active for parallel devices.
The add_*_contact option tells which layer to bring source/drain up to.
"""
def __init__(self,
name="",
width=drc("minwidth_tx"),
mults=1,
tx_type="nmos",
add_source_contact=True,
add_drain_contact=True,
add_source_contact=None,
add_drain_contact=None,
series_devices=False,
connect_active=False,
connect_drain_active=False,
connect_source_active=False,
connect_poly=False,
num_contacts=None):
if "li" in layer:
self.route_layer = "li"
else:
self.route_layer = "m1"
# Default contacts are the lowest layer
if not add_source_contact:
add_source_contact = self.route_layer
# Default contacts are the lowest layer
if not add_drain_contact:
add_drain_contact = self.route_layer
# We need to keep unique names because outputting to GDSII
# will use the last record with a given name. I.e., you will
# over-write a design in GDS if one has and the other doesn't
# have poly connected, for example.
name = "{0}_m{1}_w{2:.3f}".format(tx_type, mults, width)
if not add_source_contact:
name += "_ns"
if not add_drain_contact:
name += "_nd"
name += "_s{}".format(add_source_contact)
name += "_d{}".format(add_drain_contact)
if series_devices:
name += "_sd"
if connect_active:
name += "_a"
if connect_drain_active:
name += "_da"
if connect_source_active:
name += "_sa"
if connect_poly:
name += "_p"
if num_contacts:
@ -61,13 +76,17 @@ class ptx(design.design):
self.tx_type = tx_type
self.mults = mults
self.tx_width = width
self.connect_active = connect_active
self.connect_drain_active = connect_drain_active
self.connect_source_active = connect_source_active
self.connect_poly = connect_poly
self.add_source_contact = add_source_contact
self.add_drain_contact = add_drain_contact
self.series_devices = series_devices
self.num_contacts = num_contacts
self.route_layer_width = drc("minwidth_{}".format(self.route_layer))
self.route_layer_space = drc("{0}_to_{0}".format(self.route_layer))
# Since it has variable height, it is not a pgate.
self.create_netlist()
# We must always create ptx layout for pbitcell
@ -109,6 +128,7 @@ class ptx(design.design):
perimeter_sd = 2 * self.poly_width + 2 * self.tx_width
if OPTS.tech_name == "s8":
# s8 technology is in microns
(self.tx_width, self.mults) = pgate.bin_width(self.tx_type, self.tx_width)
main_str = "M{{0}} {{1}} {0} m={1} w={2} l={3} ".format(spice[self.tx_type],
self.mults,
self.tx_width,
@ -265,55 +285,42 @@ class ptx(design.design):
width=poly_width,
height=self.poly_width)
def connect_fingered_active(self, drain_positions, source_positions):
def connect_fingered_active(self, positions, pin_name, top):
"""
Connect each contact up/down to a source or drain pin
"""
if len(positions) <= 1:
return
layer_space = getattr(self, "{}_space".format(self.route_layer))
layer_width = getattr(self, "{}_width".format(self.route_layer))
# This is the distance that we must route up or down from the center
# of the contacts to avoid DRC violations to the other contacts
pin_offset = vector(0,
0.5 * self.active_contact.second_layer_height + self.m1_space + 0.5 * self.m1_width)
0.5 * self.active_contact.second_layer_height + layer_space + 0.5 * layer_width)
# This is the width of a m1 extend the ends of the pin
end_offset = vector(self.m1_width / 2.0, 0)
end_offset = vector(layer_width / 2.0, 0)
# drains always go to the MIDDLE of the cell,
# so top of NMOS, bottom of PMOS
# so reverse the directions for NMOS compared to PMOS.
if self.tx_type == "pmos":
drain_dir = -1
source_dir = 1
# We move the opposite direction from the bottom
if not top:
offset = pin_offset.scale(-1, -1)
else:
drain_dir = 1
source_dir = -1
if len(source_positions) > 1:
source_offset = pin_offset.scale(source_dir, source_dir)
# remove the individual connections
self.remove_layout_pin("S")
# Add each vertical segment
for a in source_positions:
self.add_path(("m1"),
[a, a + pin_offset.scale(source_dir,
source_dir)])
# Add a single horizontal pin
self.add_layout_pin_segment_center(text="S",
layer="m1",
start=source_positions[0] + source_offset - end_offset,
end=source_positions[-1] + source_offset + end_offset)
offset = pin_offset
# remove the individual connections
self.remove_layout_pin(pin_name)
# Add each vertical segment
for a in positions:
self.add_path(self.route_layer,
[a, a + offset])
# Add a single horizontal pin
self.add_layout_pin_segment_center(text=pin_name,
layer=self.route_layer,
start=positions[0] + offset - end_offset,
end=positions[-1] + offset + end_offset)
if len(drain_positions)>1:
drain_offset = pin_offset.scale(drain_dir,drain_dir)
self.remove_layout_pin("D") # remove the individual connections
# Add each vertical segment
for a in drain_positions:
self.add_path(("m1"), [a,a+drain_offset])
# Add a single horizontal pin
self.add_layout_pin_segment_center(text="D",
layer="m1",
start=drain_positions[0] + drain_offset - end_offset,
end=drain_positions[-1] + drain_offset + end_offset)
def add_poly(self):
"""
Add the poly gates(s) and (optionally) connect them.
@ -432,12 +439,12 @@ class ptx(design.design):
label = "S"
source_positions.append(pos)
if (label=="S" and self.add_source_contact) or (label=="D" and self.add_drain_contact):
if (label=="S" and self.add_source_contact):
contact = self.add_diff_contact(label, pos)
if label == "S":
self.source_contacts.append(contact)
else:
self.drain_contacts.append(contact)
self.source_contacts.append(contact)
elif (label=="D" and self.add_drain_contact):
contact = self.add_diff_contact(label, pos)
self.drain_contacts.append(contact)
else:
self.add_layout_pin_rect_center(text=label,
layer="active",
@ -453,19 +460,22 @@ class ptx(design.design):
label = "S"
source_positions.append(pos)
if (label=="S" and self.add_source_contact) or (label=="D" and self.add_drain_contact):
if (label=="S" and self.add_source_contact):
contact = self.add_diff_contact(label, pos)
if label == "S":
self.source_contacts.append(contact)
else:
self.drain_contacts.append(contact)
self.source_contacts.append(contact)
elif (label=="D" and self.add_drain_contact):
contact = self.add_diff_contact(label, pos)
self.drain_contacts.append(contact)
else:
self.add_layout_pin_rect_center(text=label,
layer="active",
offset=pos)
if self.connect_active:
self.connect_fingered_active(drain_positions, source_positions)
if self.connect_source_active:
self.connect_fingered_active(source_positions, "S", top=(self.tx_type=="pmos"))
if self.connect_drain_active:
self.connect_fingered_active(drain_positions, "D", top=(self.tx_type=="nmos"))
def get_stage_effort(self, cout):
"""Returns an object representing the parameters for delay in tau units."""
@ -488,34 +498,39 @@ class ptx(design.design):
return self.mults * self.tx_width / drc("minwidth_tx")
def add_diff_contact(self, label, pos):
contact=self.add_via_center(layers=self.active_stack,
offset=pos,
size=(1, self.num_contacts),
directions=("V", "V"),
implant_type=self.implant_type,
well_type=self.well_type)
if hasattr(self, "li_stack"):
contact=self.add_via_center(layers=self.li_stack,
offset=pos,
directions=("V", "V"))
# contact_area = contact.mod.second_layer_width * contact.mod.second_layer_height
# min_area = drc("minarea_m1")
# width = contact.mod.second_layer_width
# if contact_area < min_area:
# height = min_area / width
# else:
# height = contact.mod.second_layer_height
width = contact.mod.second_layer_width
height = contact.mod.second_layer_height
if label == "S":
layer = self.add_source_contact
elif label == "D":
layer = self.add_drain_contact
else:
debug.error("Invalid source drain name.")
if layer != "active":
via=self.add_via_stack_center(offset=pos,
from_layer="active",
to_layer=layer,
size=(1, self.num_contacts),
directions=("V", "V"),
implant_type=self.implant_type,
well_type=self.well_type)
pin_height = via.mod.second_layer_height
pin_width = via.mod.second_layer_width
else:
via = None
pin_height = None
pin_width = None
# Source drain vias are all vertical
self.add_layout_pin_rect_center(text=label,
layer="m1",
layer=layer,
offset=pos,
width=width,
height=height)
width=pin_width,
height=pin_height)
return(contact)
return(via)
def get_cin(self):
"""Returns the relative gate cin of the tx"""

View File

@ -18,17 +18,13 @@ class router_tech:
"""
def __init__(self, layers, rail_track_width):
"""
Allows us to change the layers that we are routing on. First layer
is always horizontal, middle is via, and last is always
vertical.
Allows us to change the layers that we are routing on.
This uses the preferreed directions.
"""
self.layers = layers
self.rail_track_width = rail_track_width
if len(self.layers) == 1:
if preferred_directions[self.layers[0]] != "H":
debug.warning("Using '{}' for horizontal routing, but it " \
"prefers vertical routing".format(self.layers[0]))
self.horiz_layer_name = self.vert_layer_name = self.layers[0]
self.horiz_lpp = self.vert_lpp = layer[self.layers[0]]
@ -42,13 +38,21 @@ class router_tech:
# figure out wich of the two layers prefers horizontal/vertical
# routing
if preferred_directions[try_horiz_layer] == "H" and preferred_directions[try_vert_layer] == "V":
self.horiz_layer_name = None
self.vert_layer_name = None
if preferred_directions[try_horiz_layer] == "H":
self.horiz_layer_name = try_horiz_layer
else:
self.horiz_layer_name = try_vert_layer
if preferred_directions[try_vert_layer] == "V":
self.vert_layer_name = try_vert_layer
else:
raise ValueError("Layer '{}' and '{}' are using the wrong " \
"preferred_directions '{}' and '{}'. Only "\
"('H', 'V') are supported")
self.vert_layer_name = try_horiz_layer
if not self.horiz_layer_name or not self.vert_layer_name:
raise ValueError("Layer '{}' and '{}' are using the wrong "
"preferred_directions '{}' and '{}'.")
via_connect = contact(self.layers, (1, 1))
max_via_size = max(via_connect.width,via_connect.height)

View File

@ -5,21 +5,15 @@
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
import gdsMill
import tech
import math
import debug
from globals import OPTS,print_time
from contact import contact
from pin_group import pin_group
from pin_layout import pin_layout
from vector3d import vector3d
from globals import print_time
from vector3d import vector3d
from router import router
from direction import direction
from datetime import datetime
import grid
import grid_utils
class supply_grid_router(router):
"""
A router class to read an obstruction map from a gds and
@ -44,14 +38,13 @@ class supply_grid_router(router):
self.supply_rail_tracks = {}
print_time("Init supply router", datetime.now(), start_time, 3)
def create_routing_grid(self):
"""
Create a sprase routing grid with A* expansion functions.
"""
size = self.ur - self.ll
debug.info(1,"Size: {0} x {1}".format(size.x,size.y))
debug.info(1, "Size: {0} x {1}".format(size.x, size.y))
import supply_grid
self.rg = supply_grid.supply_grid(self.ll, self.ur, self.track_width)
@ -60,12 +53,12 @@ class supply_grid_router(router):
"""
Add power supply rails and connect all pins to these rails.
"""
debug.info(1,"Running supply router on {0} and {1}...".format(vdd_name, gnd_name))
debug.info(1, "Running supply router on {0} and {1}...".format(vdd_name, gnd_name))
self.vdd_name = vdd_name
self.gnd_name = gnd_name
# Clear the pins if we have previously routed
if (hasattr(self,'rg')):
if (hasattr(self, 'rg')):
self.clear_pins()
else:
# Creat a routing grid over the entire area
@ -76,32 +69,32 @@ class supply_grid_router(router):
# Get the pin shapes
start_time = datetime.now()
self.find_pins_and_blockages([self.vdd_name, self.gnd_name])
print_time("Finding pins and blockages",datetime.now(), start_time, 3)
print_time("Finding pins and blockages", datetime.now(), start_time, 3)
# Add the supply rails in a mesh network and connect H/V with vias
start_time = datetime.now()
# Block everything
self.prepare_blockages(self.gnd_name)
# Determine the rail locations
self.route_supply_rails(self.gnd_name,0)
self.route_supply_rails(self.gnd_name, 0)
# Block everything
self.prepare_blockages(self.vdd_name)
# Determine the rail locations
self.route_supply_rails(self.vdd_name,1)
print_time("Routing supply rails",datetime.now(), start_time, 3)
self.route_supply_rails(self.vdd_name, 1)
print_time("Routing supply rails", datetime.now(), start_time, 3)
start_time = datetime.now()
self.route_simple_overlaps(vdd_name)
self.route_simple_overlaps(gnd_name)
print_time("Simple overlap routing",datetime.now(), start_time, 3)
print_time("Simple overlap routing", datetime.now(), start_time, 3)
# Route the supply pins to the supply rails
# Route vdd first since we want it to be shorter
start_time = datetime.now()
self.route_pins_to_rails(vdd_name)
self.route_pins_to_rails(gnd_name)
print_time("Maze routing supplies",datetime.now(), start_time, 3)
#self.write_debug_gds("final.gds",False)
print_time("Maze routing supplies", datetime.now(), start_time, 3)
# self.write_debug_gds("final.gds", False)
# Did we route everything??
if not self.check_all_routed(vdd_name):
@ -111,9 +104,8 @@ class supply_grid_router(router):
return True
def check_all_routed(self, pin_name):
"""
"""
Check that all pin groups are routed.
"""
for pg in self.pin_groups[pin_name]:
@ -125,7 +117,7 @@ class supply_grid_router(router):
This checks for simple cases where a pin component already overlaps a supply rail.
It will add an enclosure to ensure the overlap in wide DRC rule cases.
"""
debug.info(1,"Routing simple overlap pins for {0}".format(pin_name))
debug.info(1, "Routing simple overlap pins for {0}".format(pin_name))
# These are the wire tracks
wire_tracks = self.supply_rail_tracks[pin_name]
@ -142,10 +134,10 @@ class supply_grid_router(router):
continue
# Else, if we overlap some of the space track, we can patch it with an enclosure
#pg.create_simple_overlap_enclosure(pg.grids)
#pg.add_enclosure(self.cell)
# pg.create_simple_overlap_enclosure(pg.grids)
# pg.add_enclosure(self.cell)
debug.info(1,"Routed {} simple overlap pins".format(routed_count))
debug.info(1, "Routed {} simple overlap pins".format(routed_count))
def finalize_supply_rails(self, name):
"""
@ -158,7 +150,7 @@ class supply_grid_router(router):
connections = set()
via_areas = []
for i1,r1 in enumerate(all_rails):
for i1, r1 in enumerate(all_rails):
# Only consider r1 horizontal rails
e = next(iter(r1))
if e.z==1:
@ -166,9 +158,9 @@ class supply_grid_router(router):
# We need to move this rail to the other layer for the z indices to match
# during the intersection. This also makes a copy.
new_r1 = {vector3d(i.x,i.y,1) for i in r1}
new_r1 = {vector3d(i.x, i.y, 1) for i in r1}
for i2,r2 in enumerate(all_rails):
for i2, r2 in enumerate(all_rails):
# Never compare to yourself
if i1==i2:
continue
@ -184,16 +176,16 @@ class supply_grid_router(router):
# the overlap area for placement of a via
overlap = new_r1 & r2
if len(overlap) >= 1:
debug.info(3,"Via overlap {0} {1}".format(len(overlap),overlap))
connections.update([i1,i2])
debug.info(3, "Via overlap {0} {1}".format(len(overlap),overlap))
connections.update([i1, i2])
via_areas.append(overlap)
# Go through and add the vias at the center of the intersection
for area in via_areas:
ll = grid_utils.get_lower_left(area)
ur = grid_utils.get_upper_right(area)
center = (ll + ur).scale(0.5,0.5,0)
self.add_via(center,1)
center = (ll + ur).scale(0.5, 0.5, 0)
self.add_via(center, 1)
# Determien which indices were not connected to anything above
missing_indices = set([x for x in range(len(self.supply_rails[name]))])
@ -204,13 +196,12 @@ class supply_grid_router(router):
for rail_index in sorted(missing_indices, reverse=True):
ll = grid_utils.get_lower_left(all_rails[rail_index])
ur = grid_utils.get_upper_right(all_rails[rail_index])
debug.info(1,"Removing disconnected supply rail {0} .. {1}".format(ll,ur))
debug.info(1, "Removing disconnected supply rail {0} .. {1}".format(ll, ur))
self.supply_rails[name].pop(rail_index)
# Make the supply rails into a big giant set of grids for easy blockages.
# Must be done after we determine which ones are connected.
self.create_supply_track_set(name)
def add_supply_rails(self, name):
"""
@ -223,7 +214,7 @@ class supply_grid_router(router):
ur = grid_utils.get_upper_right(rail)
z = ll.z
pin = self.compute_pin_enclosure(ll, ur, z, name)
debug.info(3,"Adding supply rail {0} {1}->{2} {3}".format(name,ll,ur,pin))
debug.info(3, "Adding supply rail {0} {1}->{2} {3}".format(name, ll, ur, pin))
self.cell.add_layout_pin(text=name,
layer=pin.layer,
offset=pin.ll(),
@ -243,19 +234,18 @@ class supply_grid_router(router):
max_xoffset = self.rg.ur.x
min_yoffset = self.rg.ll.y
min_xoffset = self.rg.ll.x
# Horizontal supply rails
start_offset = min_yoffset + supply_number
for offset in range(start_offset, max_yoffset, 2):
# Seed the function at the location with the given width
wave = [vector3d(min_xoffset,offset,0)]
wave = [vector3d(min_xoffset, offset, 0)]
# While we can keep expanding east in this horizontal track
while wave and wave[0].x < max_xoffset:
added_rail = self.find_supply_rail(name, wave, direction.EAST)
if not added_rail:
# Just seed with the next one
wave = [x+vector3d(1,0,0) for x in wave]
wave = [x+vector3d(1, 0, 0) for x in wave]
else:
# Seed with the neighbor of the end of the last rail
wave = added_rail.neighbor(direction.EAST)
@ -264,15 +254,15 @@ class supply_grid_router(router):
start_offset = min_xoffset + supply_number
for offset in range(start_offset, max_xoffset, 2):
# Seed the function at the location with the given width
wave = [vector3d(offset,min_yoffset,1)]
wave = [vector3d(offset, min_yoffset, 1)]
# While we can keep expanding north in this vertical track
while wave and wave[0].y < max_yoffset:
added_rail = self.find_supply_rail(name, wave, direction.NORTH)
if not added_rail:
# Just seed with the next one
wave = [x+vector3d(0,1,0) for x in wave]
wave = [x + vector3d(0, 1, 0) for x in wave]
else:
# Seed with the neighbor of the end of the last rail
# Seed with the neighbor of the end of the last rail
wave = added_rail.neighbor(direction.NORTH)
def find_supply_rail(self, name, seed_wave, direct):
@ -294,7 +284,6 @@ class supply_grid_router(router):
# Return the rail whether we approved it or not,
# as it will be used to find the next start location
return wave_path
def probe_supply_rail(self, name, start_wave, direct):
"""
@ -328,23 +317,19 @@ class supply_grid_router(router):
data structure. Return whether it was added or not.
"""
# We must have at least 2 tracks to drop plus 2 tracks for a via
if len(wave_path)>=4*self.rail_track_width:
if len(wave_path) >= 4 * self.rail_track_width:
grid_set = wave_path.get_grids()
self.supply_rails[name].append(grid_set)
return True
return False
def route_supply_rails(self, name, supply_number):
"""
Route the horizontal and vertical supply rails across the entire design.
Must be done with lower left at 0,0
"""
debug.info(1,"Routing supply rail {0}.".format(name))
debug.info(1, "Routing supply rail {0}.".format(name))
# Compute the grid locations of the supply rails
self.compute_supply_rails(name, supply_number)
@ -355,7 +340,6 @@ class supply_grid_router(router):
# Add the rails themselves
self.add_supply_rails(name)
def create_supply_track_set(self, pin_name):
"""
Make a single set of all the tracks for the rail and wire itself.
@ -364,24 +348,22 @@ class supply_grid_router(router):
for rail in self.supply_rails[pin_name]:
rail_set.update(rail)
self.supply_rail_tracks[pin_name] = rail_set
def route_pins_to_rails(self, pin_name):
"""
This will route each of the remaining pin components to the supply rails.
This will route each of the remaining pin components to the supply rails.
After it is done, the cells are added to the pin blockage list.
"""
remaining_components = sum(not x.is_routed() for x in self.pin_groups[pin_name])
debug.info(1,"Maze routing {0} with {1} pin components to connect.".format(pin_name,
remaining_components))
debug.info(1, "Maze routing {0} with {1} pin components to connect.".format(pin_name,
remaining_components))
for index,pg in enumerate(self.pin_groups[pin_name]):
for index, pg in enumerate(self.pin_groups[pin_name]):
if pg.is_routed():
continue
debug.info(3,"Routing component {0} {1}".format(pin_name, index))
debug.info(3, "Routing component {0} {1}".format(pin_name, index))
# Clear everything in the routing grid.
self.rg.reinit()
@ -400,28 +382,26 @@ class supply_grid_router(router):
# Actually run the A* router
if not self.run_router(detour_scale=5):
self.write_debug_gds("debug_route.gds",False)
self.write_debug_gds("debug_route.gds", False)
#if index==3 and pin_name=="vdd":
# self.write_debug_gds("route.gds",False)
# if index==3 and pin_name=="vdd":
# self.write_debug_gds("route.gds",False)
def add_supply_rail_target(self, pin_name):
"""
Add the supply rails of given name as a routing target.
"""
debug.info(4,"Add supply rail target {}".format(pin_name))
debug.info(4, "Add supply rail target {}".format(pin_name))
# Add the wire itself as the target
self.rg.set_target(self.supply_rail_tracks[pin_name])
# But unblock all the rail tracks including the space
self.rg.set_blocked(self.supply_rail_tracks[pin_name],False)
self.rg.set_blocked(self.supply_rail_tracks[pin_name], False)
def set_supply_rail_blocked(self, value=True):
"""
Add the supply rails of given name as a routing target.
"""
debug.info(4,"Blocking supply rail")
debug.info(4, "Blocking supply rail")
for rail_name in self.supply_rail_tracks:
self.rg.set_blocked(self.supply_rail_tracks[rail_name])

View File

@ -59,14 +59,23 @@ class sram_1bank(sram_base):
wmask_pos = [None] * len(self.all_ports)
data_pos = [None] * len(self.all_ports)
# These positions utilize the channel route sizes.
# FIXME: Auto-compute these rather than manual computation.
# If a horizontal channel, they rely on the vertical channel non-preferred (contacted) pitch.
# If a vertical channel, they rely on the horizontal channel non-preferred (contacted) pitch.
# So, m3 non-pref pitch means that this is routed on the m2 layer.
if self.write_size:
max_gap_size = self.m3_pitch * self.word_size + 2 * self.m1_pitch
max_gap_size_wmask = self.m2_pitch * max(self.num_wmasks + 1, self.col_addr_size + 1) + 2 * self.m1_pitch
self.data_bus_gap = self.m4_nonpref_pitch * 2
self.data_bus_size = self.m4_nonpref_pitch * (self.word_size) + self.data_bus_gap
self.wmask_bus_gap = self.m2_nonpref_pitch * 2
self.wmask_bus_size = self.m2_nonpref_pitch * (max(self.num_wmasks + 1, self.col_addr_size + 1)) + self.wmask_bus_gap
else:
# This is M2 pitch even though it is on M1 to help stem via spacings on the trunk
# The M1 pitch is for supply rail spacings
max_gap_size = self.m2_pitch * max(self.word_size + 1,self.col_addr_size + 1) + 2 * self.m1_pitch
self.data_bus_gap = self.m3_nonpref_pitch * 2
self.data_bus_size = self.m3_nonpref_pitch * (max(self.word_size + 1, self.col_addr_size + 1)) + self.data_bus_gap
self.col_addr_bus_gap = self.m2_nonpref_pitch * 2
self.col_addr_bus_size = self.m2_nonpref_pitch * (self.col_addr_size) + self.col_addr_bus_gap
# Port 0
port = 0
@ -74,12 +83,12 @@ class sram_1bank(sram_base):
if self.write_size:
# Add the write mask flops below the write mask AND array.
wmask_pos[port] = vector(self.bank.bank_array_ll.x,
-max_gap_size_wmask - self.dff.height)
- self.wmask_bus_size - self.dff.height)
self.wmask_dff_insts[port].place(wmask_pos[port])
# Add the data flops below the write mask flops.
data_pos[port] = vector(self.bank.bank_array_ll.x,
-max_gap_size - max_gap_size_wmask - 2 * self.dff.height)
- self.data_bus_size - self.wmask_bus_size - 2 * self.dff.height)
self.data_dff_insts[port].place(data_pos[port])
else:
# Add the data flops below the bank to the right of the lower-left of bank array
@ -89,7 +98,7 @@ class sram_1bank(sram_base):
# sense amps.
if port in self.write_ports:
data_pos[port] = vector(self.bank.bank_array_ll.x,
-max_gap_size - self.dff.height)
-self.data_bus_size - self.dff.height)
self.data_dff_insts[port].place(data_pos[port])
else:
wmask_pos[port] = vector(self.bank.bank_array_ll.x, 0)
@ -99,10 +108,10 @@ class sram_1bank(sram_base):
if self.col_addr_dff:
if self.write_size:
col_addr_pos[port] = vector(self.bank.bank_array_ll.x - self.col_addr_dff_insts[port].width - self.bank.m2_gap,
-max_gap_size_wmask - self.col_addr_dff_insts[port].height)
-self.wmask_bus_size - self.col_addr_dff_insts[port].height)
else:
col_addr_pos[port] = vector(self.bank.bank_array_ll.x - self.col_addr_dff_insts[port].width - self.bank.m2_gap,
-max_gap_size - self.col_addr_dff_insts[port].height)
-self.data_bus_size - self.col_addr_dff_insts[port].height)
self.col_addr_dff_insts[port].place(col_addr_pos[port])
else:
col_addr_pos[port] = vector(self.bank.bank_array_ll.x, 0)
@ -127,12 +136,12 @@ class sram_1bank(sram_base):
if self.write_size:
# Add the write mask flops below the write mask AND array.
wmask_pos[port] = vector(self.bank.bank_array_ur.x - self.wmask_dff_insts[port].width,
self.bank.height + max_gap_size_wmask + self.dff.height)
self.bank.height + self.wmask_bus_size + self.dff.height)
self.wmask_dff_insts[port].place(wmask_pos[port], mirror="MX")
# Add the data flops below the write mask flops
data_pos[port] = vector(self.bank.bank_array_ur.x - self.data_dff_insts[port].width,
self.bank.height + max_gap_size_wmask + max_gap_size + 2 * self.dff.height)
self.bank.height + self.wmask_bus_size + self.data_bus_size + 2 * self.dff.height)
self.data_dff_insts[port].place(data_pos[port], mirror="MX")
else:
# Add the data flops above the bank to the left of the upper-right of bank array
@ -141,17 +150,17 @@ class sram_1bank(sram_base):
# These flops go below the sensing and leave a gap to channel route to the
# sense amps.
data_pos[port] = vector(self.bank.bank_array_ur.x - self.data_dff_insts[port].width,
self.bank.height + max_gap_size + self.dff.height)
self.bank.height + self.data_bus_size + self.dff.height)
self.data_dff_insts[port].place(data_pos[port], mirror="MX")
# Add the col address flops above the bank to the right of the upper-right of bank array
if self.col_addr_dff:
if self.write_size:
col_addr_pos[port] = vector(self.bank.bank_array_ur.x + self.bank.m2_gap,
self.bank.height + max_gap_size_wmask + self.dff.height)
self.bank.height + self.wmask_bus_size + self.dff.height)
else:
col_addr_pos[port] = vector(self.bank.bank_array_ur.x + self.bank.m2_gap,
self.bank.height + max_gap_size + self.dff.height)
self.bank.height + self.data_bus_size + self.dff.height)
self.col_addr_dff_insts[port].place(col_addr_pos[port], mirror="MX")
else:
col_addr_pos[port] = self.bank_inst.ur()
@ -245,7 +254,7 @@ class sram_1bank(sram_base):
# This uses a metal2 track to the right (for port0) of the control/row addr DFF
# to route vertically. For port1, it is to the left.
row_addr_clk_pin = self.row_addr_dff_insts[port].get_pin("clk")
if port%2:
if port % 2:
control_clk_buf_pos = control_clk_buf_pin.lc()
row_addr_clk_pos = row_addr_clk_pin.lc()
mid1_pos = vector(self.row_addr_dff_insts[port].lx() - self.m2_pitch,
@ -258,19 +267,20 @@ class sram_1bank(sram_base):
# This is the steiner point where the net branches out
clk_steiner_pos = vector(mid1_pos.x, control_clk_buf_pos.y)
self.add_path("m1", [control_clk_buf_pos, clk_steiner_pos])
self.add_via_center(layers=self.m1_stack,
offset=clk_steiner_pos)
self.add_path(control_clk_buf_pin.layer, [control_clk_buf_pos, clk_steiner_pos])
self.add_via_stack_center(from_layer=control_clk_buf_pin.layer,
to_layer="m2",
offset=clk_steiner_pos)
# Note, the via to the control logic is taken care of above
self.add_wire(("m3", "via2", "m2"),
self.add_wire(self.m2_stack[::-1],
[row_addr_clk_pos, mid1_pos, clk_steiner_pos])
if self.col_addr_dff:
dff_clk_pin = self.col_addr_dff_insts[port].get_pin("clk")
dff_clk_pos = dff_clk_pin.center()
mid_pos = vector(clk_steiner_pos.x, dff_clk_pos.y)
self.add_wire(("m3", "via2", "m2"),
self.add_wire(self.m2_stack[::-1],
[dff_clk_pos, mid_pos, clk_steiner_pos])
if port in self.write_ports:
@ -282,7 +292,7 @@ class sram_1bank(sram_base):
self.add_path("m2",
[mid_pos, clk_steiner_pos],
width=max(m2_via.width, m2_via.height))
self.add_wire(("m3", "via2", "m2"),
self.add_wire(self.m2_stack[::-1],
[data_dff_clk_pos, mid_pos, clk_steiner_pos])
if self.write_size:
@ -292,7 +302,7 @@ class sram_1bank(sram_base):
# In some designs, the steiner via will be too close to the mid_pos via
# so make the wire as wide as the contacts
self.add_path("m2", [mid_pos, clk_steiner_pos], width=max(m2_via.width, m2_via.height))
self.add_wire(("m3", "via2", "m2"), [wmask_dff_clk_pos, mid_pos, clk_steiner_pos])
self.add_wire(self.m2_stack[::-1], [wmask_dff_clk_pos, mid_pos, clk_steiner_pos])
def route_control_logic(self):
""" Route the control logic pins that are not inputs """
@ -304,14 +314,13 @@ class sram_1bank(sram_base):
continue
src_pin = self.control_logic_insts[port].get_pin(signal)
dest_pin = self.bank_inst.get_pin(signal + "{}".format(port))
self.connect_vbus_m2m3(src_pin, dest_pin)
self.connect_vbus(src_pin, dest_pin)
for port in self.all_ports:
# Only input (besides pins) is the replica bitline
src_pin = self.control_logic_insts[port].get_pin("rbl_bl")
dest_pin = self.bank_inst.get_pin("rbl_bl{}".format(port))
self.connect_hbus_m2m3(src_pin, dest_pin)
self.connect_hbus(src_pin, dest_pin)
def route_row_addr_dff(self):
""" Connect the output of the row flops to the bank pins """
@ -324,33 +333,37 @@ class sram_1bank(sram_base):
flop_pos = flop_pin.center()
bank_pos = bank_pin.center()
mid_pos = vector(bank_pos.x, flop_pos.y)
self.add_wire(("m3", "via2", "m2"),
self.add_wire(self.m2_stack[::-1],
[flop_pos, mid_pos, bank_pos])
self.add_via_center(layers=self.m2_stack,
offset=flop_pos)
self.add_via_stack_center(from_layer=flop_pin.layer,
to_layer="m3",
offset=flop_pos)
def route_col_addr_dff(self):
""" Connect the output of the col flops to the bank pins """
for port in self.all_ports:
if port%2:
offset = self.col_addr_dff_insts[port].ll() - vector(0, (self.col_addr_size + 2) * self.m1_pitch)
if port % 2:
offset = self.col_addr_dff_insts[port].ll() - vector(0, self.col_addr_bus_size)
else:
offset = self.col_addr_dff_insts[port].ul() + vector(0, 2 * self.m1_pitch)
offset = self.col_addr_dff_insts[port].ul() + vector(0, self.col_addr_bus_gap)
bus_names = ["addr_{}".format(x) for x in range(self.col_addr_size)]
col_addr_bus_offsets = self.create_horizontal_bus(layer="m1",
pitch=self.m1_pitch,
offset=offset,
names=bus_names,
length=self.col_addr_dff_insts[port].width)
dff_names = ["dout_{}".format(x) for x in range(self.col_addr_size)]
data_dff_map = zip(dff_names, bus_names)
self.connect_horizontal_bus(data_dff_map, self.col_addr_dff_insts[port], col_addr_bus_offsets)
self.connect_horizontal_bus(data_dff_map,
self.col_addr_dff_insts[port],
col_addr_bus_offsets)
bank_names = ["addr{0}_{1}".format(port, x) for x in range(self.col_addr_size)]
data_bank_map = zip(bank_names, bus_names)
self.connect_horizontal_bus(data_bank_map, self.bank_inst, col_addr_bus_offsets)
self.connect_horizontal_bus(data_bank_map,
self.bank_inst,
col_addr_bus_offsets)
def route_data_dff(self):
""" Connect the output of the data flops to the write driver """
@ -358,48 +371,47 @@ class sram_1bank(sram_base):
for port in self.write_ports:
if self.write_size:
if port % 2:
offset = self.data_dff_insts[port].ll() - vector(0, (self.word_size + 2) * self.m3_pitch)
offset = self.data_dff_insts[port].ll() - vector(0, self.data_bus_size)
else:
offset = self.data_dff_insts[port].ul() + vector(0, 2 * self.m3_pitch)
offset = self.data_dff_insts[port].ul() + vector(0, self.data_bus_gap)
else:
if port % 2:
offset = self.data_dff_insts[port].ll() - vector(0, (self.word_size + 2) * self.m1_pitch)
offset = self.data_dff_insts[port].ll() - vector(0, self.data_bus_size)
else:
offset = self.data_dff_insts[port].ul() + vector(0, 2 * self.m1_pitch)
offset = self.data_dff_insts[port].ul() + vector(0, self.data_bus_gap)
dff_names = ["dout_{}".format(x) for x in range(self.word_size)]
dff_pins = [self.data_dff_insts[port].get_pin(x) for x in dff_names]
if self.write_size:
for x in dff_names:
pin_offset = self.data_dff_insts[port].get_pin(x).center()
pin = self.data_dff_insts[port].get_pin(x)
pin_offset = pin.center()
self.add_via_center(layers=self.m1_stack,
offset=pin_offset,
directions=("V", "V"))
self.add_via_center(layers=self.m2_stack,
offset=pin_offset)
self.add_via_center(layers=self.m3_stack,
offset=pin_offset)
self.add_via_stack_center(from_layer="m2",
to_layer="m4",
offset=pin_offset)
bank_names = ["din{0}_{1}".format(port, x) for x in range(self.word_size)]
bank_pins = [self.bank_inst.get_pin(x) for x in bank_names]
if self.write_size:
for x in bank_names:
pin = self.bank_inst.get_pin(x)
if port % 2:
pin_offset = self.bank_inst.get_pin(x).uc()
pin_offset = pin.uc()
else:
pin_offset = self.bank_inst.get_pin(x).bc()
self.add_via_center(layers=self.m1_stack,
offset=pin_offset)
self.add_via_center(layers=self.m2_stack,
offset=pin_offset)
self.add_via_center(layers=self.m3_stack,
offset=pin_offset)
pin_offset = pin.bc()
self.add_via_stack_center(from_layer=pin.layer,
to_layer="m4",
offset=pin_offset)
route_map = list(zip(bank_pins, dff_pins))
if self.write_size:
layer_stack = self.m3_stack
else:
layer_stack = self.m1_stack
self.create_horizontal_channel_route(netlist=route_map,
offset=offset,
layer_stack=layer_stack)
@ -409,9 +421,9 @@ class sram_1bank(sram_base):
# This is where the channel will start (y-dimension at least)
for port in self.write_ports:
if port % 2:
offset = self.wmask_dff_insts[port].ll() - vector(0, (self.num_wmasks + 2) * self.m1_pitch)
offset = self.wmask_dff_insts[port].ll() - vector(0, self.wmask_bus_size)
else:
offset = self.wmask_dff_insts[port].ul() + vector(0, 2 * self.m1_pitch)
offset = self.wmask_dff_insts[port].ul() + vector(0, self.wmask_bus_gap)
dff_names = ["dout_{}".format(x) for x in range(self.num_wmasks)]
dff_pins = [self.wmask_dff_insts[port].get_pin(x) for x in dff_names]

View File

@ -7,6 +7,7 @@
#
import datetime
import debug
from math import log
from importlib import reload
from vector import vector
from globals import OPTS, print_time
@ -137,7 +138,6 @@ class sram_base(design, verilog, lef):
self.copy_power_pins(inst, "vdd")
self.copy_power_pins(inst, "gnd")
import tech
if not OPTS.route_supplies:
# Do not route the power supply (leave as must-connect pins)
return
@ -148,6 +148,7 @@ class sram_base(design, verilog, lef):
grid_stack = power_grid
except ImportError:
# if no power_grid is specified by tech we use sensible defaults
import tech
if "m4" in tech.layer:
# Route a M3/M4 grid
grid_stack = self.m3_stack
@ -496,70 +497,6 @@ class sram_base(design, verilog, lef):
self.connect_inst(temp)
return insts
def connect_vbus_m2m3(self, src_pin, dest_pin):
"""
Helper routine to connect an instance to a vertical bus.
Routes horizontal then vertical L shape.
Dest pin is assumed to be on M2.
Src pin can be on M1/M2/M3.
"""
if src_pin.cx()<dest_pin.cx():
in_pos = src_pin.rc()
else:
in_pos = src_pin.lc()
if src_pin.cy() < dest_pin.cy():
out_pos = dest_pin.bc()
else:
out_pos = dest_pin.uc()
# move horizontal first
self.add_wire(("m3", "via2", "m2"),
[in_pos,
vector(out_pos.x, in_pos.y),
out_pos])
if src_pin.layer=="m1":
self.add_via_center(layers=self.m1_stack,
offset=in_pos)
if src_pin.layer in ["m1", "m2"]:
self.add_via_center(layers=self.m2_stack,
offset=in_pos)
def connect_hbus_m2m3(self, src_pin, dest_pin):
"""
Helper routine to connect an instance to a horizontal bus.
Routes horizontal then vertical L shape.
Dest pin is on M1/M2/M3.
Src pin can be on M1/M2/M3.
"""
if src_pin.cx()<dest_pin.cx():
in_pos = src_pin.rc()
else:
in_pos = src_pin.lc()
if src_pin.cy() < dest_pin.cy():
out_pos = dest_pin.lc()
else:
out_pos = dest_pin.rc()
# move horizontal first
self.add_wire(("m3", "via2", "m2"),
[in_pos,
vector(out_pos.x, in_pos.y),
out_pos])
if src_pin.layer=="m1":
self.add_via_center(layers=self.m1_stack,
offset=in_pos)
if src_pin.layer in ["m1", "m2"]:
self.add_via_center(layers=self.m2_stack,
offset=in_pos)
if dest_pin.layer=="m1":
self.add_via_center(layers=self.m1_stack,
offset=out_pos)
if dest_pin.layer=="m3":
self.add_via_center(layers=self.m2_stack,
offset=out_pos)
def sp_write(self, sp_name, lvs_netlist=False):
# Write the entire spice of the object to the file

View File

@ -27,7 +27,8 @@ class ptx_3finger_nmos_test(openram_test):
width=tech.drc["minwidth_tx"],
mults=3,
tx_type="nmos",
connect_active=True,
connect_source_active=True,
connect_drain_active=True,
connect_poly=True)
self.local_drc_check(fet)

View File

@ -27,7 +27,8 @@ class ptx_3finger_pmos_test(openram_test):
width=tech.drc["minwidth_tx"],
mults=3,
tx_type="pmos",
connect_active=True,
connect_source_active=True,
connect_drain_active=True,
connect_poly=True)
self.local_drc_check(fet)

View File

@ -23,11 +23,12 @@ class ptx_4finger_nmos_test(openram_test):
import tech
debug.info(2, "Checking three fingers NMOS")
fet = factory.create(module_type="ptx",
fet = factory.create(module_type="ptx",
width= tech.drc["minwidth_tx"],
mults=4,
tx_type="nmos",
connect_active=True,
connect_source_active=True,
connect_drain_active=True,
connect_poly=True)
self.local_drc_check(fet)

View File

@ -23,11 +23,12 @@ class ptx_test(openram_test):
import tech
debug.info(2, "Checking three fingers PMOS")
fet = factory.create(module_type="ptx",
fet = factory.create(module_type="ptx",
width=tech.drc["minwidth_tx"],
mults=4,
tx_type="pmos",
connect_active=True,
connect_source_active=True,
connect_drain_active=True,
connect_poly=True)
self.local_drc_check(fet)

View File

@ -0,0 +1,60 @@
#!/usr/bin/env python3
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2019 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
import unittest
from testutils import *
import sys,os
sys.path.append(os.getenv("OPENRAM_HOME"))
import globals
from globals import OPTS
from sram_factory import factory
import debug
class ptx_no_contacts_test(openram_test):
def runTest(self):
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
globals.init_openram(config_file)
import tech
debug.info(2, "Checking single finger no source/drain")
fet = factory.create(module_type="ptx",
width=tech.drc["minwidth_tx"],
mults=1,
add_source_contact=False,
add_drain_contact=False,
tx_type="nmos")
self.local_drc_check(fet)
debug.info(2, "Checking multifinger no source/drain")
fet = factory.create(module_type="ptx",
width=tech.drc["minwidth_tx"],
mults=4,
add_source_contact=False,
add_drain_contact=False,
tx_type="nmos")
self.local_drc_check(fet)
debug.info(2, "Checking series ptx")
fet = factory.create(module_type="ptx",
width=tech.drc["minwidth_tx"],
mults=4,
series_devices=True,
tx_type="nmos")
self.local_drc_check(fet)
globals.end_openram()
# run the test from the command line
if __name__ == "__main__":
(OPTS, args) = globals.parse_args()
del sys.argv[1:]
header(__file__, OPTS.tech_name)
unittest.main(testRunner=debugTestRunner())

View File

@ -0,0 +1,36 @@
#!/usr/bin/env python3
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2019 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
import unittest
from testutils import *
import sys,os
sys.path.append(os.getenv("OPENRAM_HOME"))
import globals
from globals import OPTS
from sram_factory import factory
import debug
class pinv_test(openram_test):
def runTest(self):
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
globals.init_openram(config_file)
debug.info(2, "Checking 100x inverter")
tx = factory.create(module_type="pinv", size=100)
self.local_check(tx)
globals.end_openram()
# run the test from the command line
if __name__ == "__main__":
(OPTS, args) = globals.parse_args()
del sys.argv[1:]
header(__file__, OPTS.tech_name)
unittest.main(testRunner=debugTestRunner())

View File

@ -25,7 +25,7 @@ class pinv_test(openram_test):
tx = factory.create(module_type="pinv", size=1)
self.local_check(tx)
globals.end_openram()
globals.end_openram()
# run the test from the command line
if __name__ == "__main__":

View File

@ -21,35 +21,32 @@ class hierarchical_decoder_test(openram_test):
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
globals.init_openram(config_file)
# check hierarchical decoder for single port
# Checks 2x4 and 2-input NAND decoder
debug.info(1, "Testing 16 row sample for hierarchical_decoder")
a = factory.create(module_type="hierarchical_decoder", num_outputs=16)
self.local_check(a)
# Checks 2x4 and 2-input NAND decoder with non-power-of-two
debug.info(1, "Testing 17 row sample for hierarchical_decoder")
a = factory.create(module_type="hierarchical_decoder", num_outputs=17)
self.local_check(a)
debug.info(1, "Testing 23 row sample for hierarchical_decoder")
a = factory.create(module_type="hierarchical_decoder", num_outputs=23)
self.local_check(a)
# Checks 2x4 with 3x8 and 2-input NAND decoder
debug.info(1, "Testing 32 row sample for hierarchical_decoder")
a = factory.create(module_type="hierarchical_decoder", num_outputs=32)
self.local_check(a)
debug.info(1, "Testing 65 row sample for hierarchical_decoder")
a = factory.create(module_type="hierarchical_decoder", num_outputs=65)
# Checks 3 x 2x4 and 3-input NAND decoder
debug.info(1, "Testing 64 row sample for hierarchical_decoder")
a = factory.create(module_type="hierarchical_decoder", num_outputs=64)
self.local_check(a)
debug.info(1, "Testing 128 row sample for hierarchical_decoder")
a = factory.create(module_type="hierarchical_decoder", num_outputs=128)
self.local_check(a)
debug.info(1, "Testing 341 row sample for hierarchical_decoder")
a = factory.create(module_type="hierarchical_decoder", num_outputs=341)
# Checks 2x4 and 2 x 3x8 and 3-input NAND with non-power-of-two
debug.info(1, "Testing 132 row sample for hierarchical_decoder")
a = factory.create(module_type="hierarchical_decoder", num_outputs=132)
self.local_check(a)
# Checks 3 x 3x8 and 3-input NAND decoder
debug.info(1, "Testing 512 row sample for hierarchical_decoder")
a = factory.create(module_type="hierarchical_decoder", num_outputs=512)
self.local_check(a)

View File

@ -0,0 +1,36 @@
#!/usr/bin/env python3
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2019 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
import unittest
from testutils import *
import sys, os
sys.path.append(os.getenv("OPENRAM_HOME"))
import globals
from globals import OPTS
from sram_factory import factory
import debug
class control_logic_test(openram_test):
def runTest(self):
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
globals.init_openram(config_file)
debug.info(1, "Testing sample for control_logic_r")
a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=32, port_type="r")
self.local_check(a)
globals.end_openram()
# run the test from the command line
if __name__ == "__main__":
(OPTS, args) = globals.parse_args()
del sys.argv[1:]
header(__file__, OPTS.tech_name)
unittest.main(testRunner=debugTestRunner())

View File

@ -0,0 +1,36 @@
#!/usr/bin/env python3
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2019 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
import unittest
from testutils import *
import sys, os
sys.path.append(os.getenv("OPENRAM_HOME"))
import globals
from globals import OPTS
from sram_factory import factory
import debug
class control_logic_test(openram_test):
def runTest(self):
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
globals.init_openram(config_file)
debug.info(1, "Testing sample for control_logic_rw")
a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=32)
self.local_check(a)
globals.end_openram()
# run the test from the command line
if __name__ == "__main__":
(OPTS, args) = globals.parse_args()
del sys.argv[1:]
header(__file__, OPTS.tech_name)
unittest.main(testRunner=debugTestRunner())

View File

@ -20,20 +20,12 @@ class control_logic_test(openram_test):
def runTest(self):
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
globals.init_openram(config_file)
import control_logic
import tech
debug.info(1, "Testing sample for control_logic_rw")
a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=32)
self.local_check(a)
debug.info(1, "Testing sample for control_logic_r")
a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=32, port_type="r")
self.local_check(a)
debug.info(1, "Testing sample for control_logic_w")
a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=32, port_type="w")
self.local_check(a)
globals.end_openram()
# run the test from the command line
if __name__ == "__main__":

View File

@ -25,28 +25,28 @@ class single_bank_test(openram_test):
c = sram_config(word_size=4,
num_words=16)
c.words_per_row=1
factory.reset()
c.recompute_sizes()
debug.info(1, "No column mux")
a = factory.create("bank", sram_config=c)
self.local_check(a)
# c.words_per_row=1
# factory.reset()
# c.recompute_sizes()
# debug.info(1, "No column mux")
# a = factory.create("bank", sram_config=c)
# self.local_check(a)
c.num_words=32
c.words_per_row=2
factory.reset()
c.recompute_sizes()
debug.info(1, "Two way column mux")
a = factory.create("bank", sram_config=c)
self.local_check(a)
# c.num_words=32
# c.words_per_row=2
# factory.reset()
# c.recompute_sizes()
# debug.info(1, "Two way column mux")
# a = factory.create("bank", sram_config=c)
# self.local_check(a)
c.num_words=64
c.words_per_row=4
factory.reset()
c.recompute_sizes()
debug.info(1, "Four way column mux")
a = factory.create("bank", sram_config=c)
self.local_check(a)
# c.num_words=64
# c.words_per_row=4
# factory.reset()
# c.recompute_sizes()
# debug.info(1, "Four way column mux")
# a = factory.create("bank", sram_config=c)
# self.local_check(a)
c.word_size=2
c.num_words=128

View File

@ -70,20 +70,20 @@ class timing_sram_test(openram_test):
'min_period': 0.898,
'disabled_write1_power': [0.201411]}
elif OPTS.tech_name == "scn4m_subm":
golden_data = {'read1_power': [12.11658],
'write1_power': [10.52653],
'read0_power': [11.956710000000001],
'disabled_write0_power': [7.673665],
'disabled_write1_power': [7.981922000000001],
'slew_lh': [1.868836],
'slew_hl': [1.868836],
'delay_hl': [1.8598510000000001],
'delay_lh': [1.8598510000000001],
'leakage_power': 0.008613619,
'disabled_read0_power': [5.904712],
'min_period': 6.875,
'disabled_read1_power': [7.132159],
'write0_power': [13.406400000000001]}
golden_data = {'delay_hl': [1.8435739999999998],
'delay_lh': [1.8435739999999998],
'disabled_read0_power': [5.917947],
'disabled_read1_power': [7.154297],
'disabled_write0_power': [7.696351],
'disabled_write1_power': [7.999409000000001],
'leakage_power': 0.004809726,
'min_period': 6.875,
'read0_power': [11.833079999999999],
'read1_power': [11.99236],
'slew_hl': [1.8668490000000002],
'slew_lh': [1.8668490000000002],
'write0_power': [13.287510000000001],
'write1_power': [10.416369999999999]}
else:
self.assertTrue(False) # other techs fail

View File

@ -32,7 +32,7 @@ tech_modules = module_type()
cell_properties = cell_properties()
cell_properties.bitcell.mirror.x = True
cell_properties.bitcell.mirror.y = False
cell_properties.bitcell_power_pin_directions = ("V", "V")
###################################################
# GDS file info
@ -63,6 +63,13 @@ m1_stack = ("m1", "via1", "m2")
m2_stack = ("m2", "via2", "m3")
m3_stack = ("m3", "via3", "m4")
layer_indices = {"poly": 0,
"active": 0,
"m1": 1,
"m2": 2,
"m3": 3,
"m4": 4}
# The FEOL stacks get us up to m1
feol_stacks = [poly_stack,
active_stack]

View File

@ -60,6 +60,13 @@ m1_stack = ("m1", "via1", "m2")
m2_stack = ("m2", "via2", "m3")
m3_stack = ("m3", "via3", "m4")
layer_indices = {"poly": 0,
"active": 0,
"m1": 1,
"m2": 2,
"m3": 3,
"m4": 4}
# The FEOL stacks get us up to m1
feol_stacks = [poly_stack,
active_stack]