Merge branch 'dev' into characterizer_bug_fixes

This commit is contained in:
Hunter Nichols 2020-07-02 18:00:41 -07:00
commit 206b02a7ee
45 changed files with 1319 additions and 1087 deletions

View File

@ -0,0 +1,309 @@
# 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 collections
import debug
from tech import drc
from vector import vector
import design
class channel_route(design.design):
unique_id = 0
def __init__(self,
netlist,
offset,
layer_stack,
directions=None,
vertical=False):
"""
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. The track size must be the number of
nets times the *nonpreferred* routing of the non-track layer pitch.
"""
name = "cr_{0}".format(channel_route.unique_id)
channel_route.unique_id += 1
design.design.__init__(self, name)
self.netlist = netlist
self.offset = offset
self.layer_stack = layer_stack
self.directions = directions
self.vertical = vertical
if not directions or directions == "pref":
# Use the preferred layer directions
if self.get_preferred_direction(layer_stack[0]) == "V":
self.vertical_layer = layer_stack[0]
self.horizontal_layer = layer_stack[2]
else:
self.vertical_layer = layer_stack[2]
self.horizontal_layer = layer_stack[0]
elif directions == "nonpref":
# Use the preferred layer directions
if self.get_preferred_direction(layer_stack[0]) == "V":
self.vertical_layer = layer_stack[2]
self.horizontal_layer = layer_stack[0]
else:
self.vertical_layer = layer_stack[0]
self.horizontal_layer = layer_stack[2]
else:
# Use the layer directions specified to the router rather than
# the preferred directions
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:
self.horizontal_layer = layer_stack[0]
self.vertical_layer = layer_stack[2]
layer_stuff = self.get_layer_pitch(self.vertical_layer)
(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_nonpref_pitch, self.horizontal_pitch, self.horizontal_width, self.horizontal_space) = layer_stuff
self.route()
def remove_net_from_graph(self, pin, g):
"""
Remove the pin from the graph and all conflicts
"""
g.pop(pin, None)
# Remove the pin from all conflicts
# FIXME: This is O(n^2), so maybe optimize it.
for other_pin, conflicts in g.items():
if pin in conflicts:
conflicts.remove(pin)
g[other_pin]=conflicts
return g
def vcg_nets_overlap(self, net1, net2):
"""
Check all the pin pairs on two nets and return a pin
overlap if any pin overlaps.
"""
if self.vertical:
pitch = self.horizontal_nonpref_pitch
else:
pitch = self.vertical_nonpref_pitch
for pin1 in net1:
for pin2 in net2:
if self.vcg_pin_overlap(pin1, pin2, pitch):
return True
return False
def route(self):
# FIXME: Must extend this to a horizontal conflict graph
# too if we want to minimize the
# number of tracks!
# hcg = {}
# Initialize the vertical conflict graph (vcg)
# and make a list of all pins
vcg = collections.OrderedDict()
# Create names for the nets for the graphs
nets = collections.OrderedDict()
index = 0
# print(netlist)
for pin_list in self.netlist:
net_name = "n{}".format(index)
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:
if net_name1 not in vcg.keys():
vcg[net_name1] = []
for net_name2 in nets:
if net_name2 not in vcg.keys():
vcg[net_name2] = []
# Skip yourself
if net_name1 == net_name2:
continue
if self.vcg_nets_overlap(nets[net_name1],
nets[net_name2]):
vcg[net_name2].append(net_name1)
current_offset = self.offset
# list of routes to do
while vcg:
# from pprint import pformat
# 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():
if len(conflicts) == 0:
vcg = self.remove_net_from_graph(net_name, vcg)
break
else:
# FIXME: We don't support cyclic VCGs right now.
debug.error("Cyclic VCG in channel router.", -1)
# These are the pins we'll have to connect
pin_list = nets[net_name]
# print("Routing:", net_name, [x.name for x in pin_list])
# Remove the net from other constriants in the VCG
vcg = self.remove_net_from_graph(net_name, vcg)
# Add the trunk routes from the bottom up for
# horizontal or the left to right for vertical
if self.vertical:
self.add_vertical_trunk_route(pin_list,
current_offset,
self.vertical_nonpref_pitch)
# This accounts for the via-to-via spacings
current_offset += vector(self.horizontal_nonpref_pitch, 0)
else:
self.add_horizontal_trunk_route(pin_list,
current_offset,
self.horizontal_nonpref_pitch)
# This accounts for the via-to-via spacings
current_offset += vector(0, self.vertical_nonpref_pitch)
# Return the size of the channel
if self.vertical:
self.width = 0
self.height = current_offset.y
return current_offset.y + self.vertical_nonpref_pitch - self.offset.y
else:
self.width = current_offset.x
self.height = 0
return current_offset.x + self.horizontal_nonpref_pitch - self.offset.x
def get_layer_pitch(self, layer):
""" Return the track pitch on a given layer """
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,
pitch):
"""
Create a trunk route for all pins with
the trunk located at the given y offset.
"""
max_x = max([pin.center().x for pin in pins])
min_x = min([pin.center().x for pin in pins])
# if we are less than a pitch, just create a non-preferred layer jog
non_preferred_route = max_x - min_x <= pitch
if non_preferred_route:
half_layer_width = 0.5 * drc["minwidth_{0}".format(self.vertical_layer)]
# Add the horizontal trunk on the vertical layer!
self.add_path(self.vertical_layer,
[vector(min_x - half_layer_width, trunk_offset.y),
vector(max_x + half_layer_width, trunk_offset.y)])
else:
# Add the horizontal trunk
self.add_path(self.horizontal_layer,
[vector(min_x, trunk_offset.y),
vector(max_x, trunk_offset.y)])
# Route each pin to the trunk
for pin in pins:
# Find the correct side of the pin
if pin.cy() < trunk_offset.y:
pin_pos = pin.uc()
else:
pin_pos = pin.bc()
mid = vector(pin_pos.x, trunk_offset.y)
self.add_path(self.vertical_layer, [pin_pos, mid])
if not non_preferred_route:
self.add_via_center(layers=self.layer_stack,
offset=mid,
directions=self.directions)
self.add_via_stack_center(from_layer=pin.layer,
to_layer=self.vertical_layer,
offset=pin_pos)
def add_vertical_trunk_route(self,
pins,
trunk_offset,
pitch):
"""
Create a trunk route for all pins with the
trunk located at the given x offset.
"""
max_y = max([pin.center().y for pin in pins])
min_y = min([pin.center().y for pin in pins])
# if we are less than a pitch, just create a non-preferred layer jog
non_preferred_route = max_y - min_y <= pitch
if non_preferred_route:
half_layer_width = 0.5 * drc["minwidth_{0}".format(self.horizontal_layer)]
# Add the vertical trunk on the horizontal layer!
self.add_path(self.horizontal_layer,
[vector(trunk_offset.x, min_y - half_layer_width),
vector(trunk_offset.x, max_y + half_layer_width)])
else:
# Add the vertical trunk
self.add_path(self.vertical_layer,
[vector(trunk_offset.x, min_y),
vector(trunk_offset.x, max_y)])
# Route each pin to the trunk
for pin in pins:
# Find the correct side of the pin
if pin.cx() < trunk_offset.x:
pin_pos = pin.rc()
else:
pin_pos = pin.lc()
mid = vector(trunk_offset.x, pin_pos.y)
self.add_path(self.horizontal_layer, [pin_pos, mid])
if not non_preferred_route:
self.add_via_center(layers=self.layer_stack,
offset=mid,
directions=self.directions)
self.add_via_stack_center(from_layer=pin.layer,
to_layer=self.horizontal_layer,
offset=pin_pos)
def vcg_pin_overlap(self, pin1, pin2, pitch):
""" Check for vertical or horizontal overlap of the two pins """
# 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.
# Pin 1 must be in the "BOTTOM" set
x_overlap = pin1.by() < pin2.by() and abs(pin1.center().x - pin2.center().x) < pitch
# Pin 1 must be in the "LEFT" set
y_overlap = pin1.lx() < pin2.lx() and abs(pin1.center().y - pin2.center().y) < pitch
overlaps = (not self.vertical and x_overlap) or (self.vertical and y_overlap)
return overlaps

View File

@ -205,7 +205,8 @@ class design(hierarchy_design):
print("poly_to_active", self.poly_to_active) print("poly_to_active", self.poly_to_active)
print("poly_extend_active", self.poly_extend_active) print("poly_extend_active", self.poly_extend_active)
print("poly_to_contact", self.poly_to_contact) print("poly_to_contact", self.poly_to_contact)
print("contact_to_gate", self.contact_to_gate) print("active_contact_to_gate", self.active_contact_to_gate)
print("poly_contact_to_gate", self.poly_contact_to_gate)
print("well_enclose_active", self.well_enclose_active) print("well_enclose_active", self.well_enclose_active)
print("implant_enclose_active", self.implant_enclose_active) print("implant_enclose_active", self.implant_enclose_active)
print("implant_space", self.implant_space) print("implant_space", self.implant_space)

View File

@ -7,7 +7,6 @@
# #
import hierarchy_layout import hierarchy_layout
import hierarchy_spice import hierarchy_spice
import verify
import debug import debug
import os import os
from globals import OPTS from globals import OPTS
@ -31,6 +30,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
except AttributeError: except AttributeError:
lvs_subdir = "lvs_lib" lvs_subdir = "lvs_lib"
lvs_dir = OPTS.openram_tech + lvs_subdir + "/" lvs_dir = OPTS.openram_tech + lvs_subdir + "/"
if os.path.exists(lvs_dir): if os.path.exists(lvs_dir):
self.lvs_file = lvs_dir + name + ".sp" self.lvs_file = lvs_dir + name + ".sp"
else: else:
@ -54,6 +54,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
def DRC_LVS(self, final_verification=False, force_check=False): def DRC_LVS(self, final_verification=False, force_check=False):
"""Checks both DRC and LVS for a module""" """Checks both DRC and LVS for a module"""
import verify
# No layout to check # No layout to check
if OPTS.netlist_only: if OPTS.netlist_only:
@ -93,6 +94,8 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
def DRC(self, final_verification=False): def DRC(self, final_verification=False):
"""Checks DRC for a module""" """Checks DRC for a module"""
import verify
# Unit tests will check themselves. # Unit tests will check themselves.
# Do not run if disabled in options. # Do not run if disabled in options.
@ -116,6 +119,8 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
def LVS(self, final_verification=False): def LVS(self, final_verification=False):
"""Checks LVS for a module""" """Checks LVS for a module"""
import verify
# Unit tests will check themselves. # Unit tests will check themselves.
# Do not run if disabled in options. # Do not run if disabled in options.

View File

@ -5,7 +5,6 @@
# (acting for and on behalf of Oklahoma State University) # (acting for and on behalf of Oklahoma State University)
# All rights reserved. # All rights reserved.
# #
import collections
import geometry import geometry
import gdsMill import gdsMill
import debug import debug
@ -14,6 +13,7 @@ from tech import drc, GDS
from tech import layer as techlayer from tech import layer as techlayer
from tech import layer_indices from tech import layer_indices
from tech import layer_stacks from tech import layer_stacks
from tech import preferred_directions
import os import os
from globals import OPTS from globals import OPTS
from vector import vector from vector import vector
@ -269,6 +269,23 @@ class layout():
width, width,
end.y - start.y) end.y - start.y)
def get_tx_insts(self, tx_type=None):
"""
Return a list of the instances of given tx type.
"""
tx_list = []
for i in self.insts:
try:
if tx_type and i.mod.tx_type == tx_type:
tx_list.append(i)
elif not tx_type:
if i.mod.tx_type == "nmos" or i.mod.tx_type == "pmos":
tx_list.append(i)
except AttributeError:
pass
return tx_list
def get_pin(self, text): def get_pin(self, text):
""" """
Return the pin or list of pins Return the pin or list of pins
@ -505,7 +522,6 @@ class layout():
def get_preferred_direction(self, layer): def get_preferred_direction(self, layer):
""" Return the preferred routing directions """ """ Return the preferred routing directions """
from tech import preferred_directions
return preferred_directions[layer] return preferred_directions[layer]
def add_via(self, layers, offset, size=[1, 1], directions=None, implant_type=None, well_type=None): def add_via(self, layers, offset, size=[1, 1], directions=None, implant_type=None, well_type=None):
@ -551,24 +567,6 @@ class layout():
self.connect_inst([]) self.connect_inst([])
return inst return inst
def add_via_stack(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.
"""
return self.__add_via_stack_internal(offset=offset,
directions=directions,
from_layer=from_layer,
to_layer=to_layer,
via_func=self.add_via,
last_via=None,
size=size,
implant_type=implant_type,
well_type=well_type)
def add_via_stack_center(self, def add_via_stack_center(self,
offset, offset,
from_layer, from_layer,
@ -578,24 +576,7 @@ class layout():
implant_type=None, implant_type=None,
well_type=None): well_type=None):
""" """
Punch a stack of vias from a start layer to a target layer by the center 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,
directions=directions,
from_layer=from_layer,
to_layer=to_layer,
via_func=self.add_via_center,
last_via=None,
size=size,
implant_type=implant_type,
well_type=well_type)
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.
""" """
if from_layer == to_layer: if from_layer == to_layer:
@ -603,38 +584,65 @@ class layout():
# a metal enclosure. This helps with center-line path routing. # a metal enclosure. This helps with center-line path routing.
self.add_rect_center(layer=from_layer, self.add_rect_center(layer=from_layer,
offset=offset) offset=offset)
return last_via return None
from_id = layer_indices[from_layer] via = None
to_id = layer_indices[to_layer] cur_layer = from_layer
while cur_layer != to_layer:
from_id = layer_indices[cur_layer]
to_id = layer_indices[to_layer]
if from_id < to_id: # grow the stack up if from_id < to_id: # grow the stack up
search_id = 0 search_id = 0
next_id = 2 next_id = 2
else: # grow the stack down else: # grow the stack down
search_id = 2 search_id = 2
next_id = 0 next_id = 0
curr_stack = next(filter(lambda stack: stack[search_id] == from_layer, layer_stacks), None) curr_stack = next(filter(lambda stack: stack[search_id] == cur_layer, layer_stacks), None)
if curr_stack is None:
raise ValueError("Cannot create via from '{0}' to '{1}'." via = self.add_via_center(layers=curr_stack,
"Layer '{0}' not defined".format(from_layer, to_layer)) size=size,
offset=offset,
directions=directions,
implant_type=implant_type,
well_type=well_type)
if cur_layer != from_layer:
self.add_min_area_rect_center(cur_layer,
offset,
via.mod.first_layer_width,
via.mod.first_layer_height)
cur_layer = curr_stack[next_id]
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 return via
def add_min_area_rect_center(self,
layer,
offset,
width=None,
height=None):
"""
Add a minimum area retcangle at the given point.
Either width or height should be fixed.
"""
min_area = drc("minarea_{}".format(layer))
if min_area == 0:
return
min_width = drc("minwidth_{}".format(layer))
if preferred_directions[layer] == "V":
height = max(min_area / width, min_width)
else:
width = max(min_area / height, min_width)
self.add_rect_center(layer=layer,
offset=offset,
width=width,
height=height)
def add_ptx(self, offset, mirror="R0", rotate=0, width=1, mults=1, tx_type="nmos"): def add_ptx(self, offset, mirror="R0", rotate=0, width=1, mults=1, tx_type="nmos"):
"""Adds a ptx module to the design.""" """Adds a ptx module to the design."""
@ -723,7 +731,7 @@ class layout():
width=width, width=width,
height=height, height=height,
center=False) center=False)
debug.info(2, "Adding {0} boundary {1}".format(self.name, boundary)) debug.info(4, "Adding {0} boundary {1}".format(self.name, boundary))
self.visited.append(self.name) self.visited.append(self.name)
@ -909,8 +917,10 @@ class layout():
(horizontal_layer, via_layer, vertical_layer) = layer_stack (horizontal_layer, via_layer, vertical_layer) = layer_stack
if horizontal: if horizontal:
route_layer = vertical_layer route_layer = vertical_layer
bys_layer = horizontal_layer
else: else:
route_layer = horizontal_layer route_layer = horizontal_layer
bus_layer = vertical_layer
for (pin_name, bus_name) in mapping: for (pin_name, bus_name) in mapping:
pin = inst.get_pin(pin_name) pin = inst.get_pin(pin_name)
@ -932,17 +942,18 @@ class layout():
# Connect to the pin on the instances with a via if it is # Connect to the pin on the instances with a via if it is
# not on the right layer # not on the right layer
if pin.layer != route_layer: if pin.layer != route_layer:
self.add_via_center(layers=layer_stack, self.add_via_stack_center(from_layer=pin.layer,
offset=pin_pos) to_layer=route_layer,
offset=pin_pos)
# FIXME: output pins tend to not be rotate, # FIXME: output pins tend to not be rotate,
# but supply pins are. Make consistent? # but supply pins are. Make consistent?
# We only need a via if they happened to align perfectly # We only need a via if they happened to align perfectly
# so the add_wire didn't add a via # so the add_wire didn't add a via
if (horizontal and bus_pos.y == pin_pos.y) or (not horizontal and bus_pos.x == pin_pos.x): if (horizontal and bus_pos.y == pin_pos.y) or (not horizontal and bus_pos.x == pin_pos.x):
self.add_via_center(layers=layer_stack, self.add_via_stack_center(from_layer=route_layer,
offset=bus_pos, to_layer=bus_layer,
rotate=90) offset=bus_pos)
def connect_vbus(self, src_pin, dest_pin, hlayer="m3", vlayer="m2"): def connect_vbus(self, src_pin, dest_pin, hlayer="m3", vlayer="m2"):
""" """
@ -1002,282 +1013,24 @@ class layout():
to_layer=dest_pin.layer, to_layer=dest_pin.layer,
offset=out_pos) offset=out_pos)
def get_layer_pitch(self, layer):
""" Return the track pitch on a given layer """
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,
layer_stack,
pitch):
"""
Create a trunk route for all pins with
the trunk located at the given y offset.
"""
max_x = max([pin.center().x for pin in pins])
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:
half_layer_width = 0.5 * drc["minwidth_{0}".format(self.vertical_layer)]
# Add the horizontal trunk on the vertical layer!
self.add_path(self.vertical_layer,
[vector(min_x - half_layer_width, trunk_offset.y),
vector(max_x + half_layer_width, trunk_offset.y)])
# Route each pin to the trunk
for pin in pins:
# No bend needed here
mid = vector(pin.center().x, trunk_offset.y)
self.add_path(self.vertical_layer, [pin.center(), mid])
else:
# Add the horizontal trunk
self.add_path(self.horizontal_layer,
[vector(min_x, trunk_offset.y),
vector(max_x, trunk_offset.y)])
# Route each pin to the trunk
for pin in pins:
mid = vector(pin.center().x, trunk_offset.y)
self.add_path(self.vertical_layer, [pin.center(), mid])
self.add_via_center(layers=layer_stack,
offset=mid,
directions=self.directions)
def add_vertical_trunk_route(self,
pins,
trunk_offset,
layer_stack,
pitch):
"""
Create a trunk route for all pins with the
trunk located at the given x offset.
"""
max_y = max([pin.center().y for pin in pins])
min_y = min([pin.center().y for pin in pins])
# if we are less than a pitch, just create a non-preferred layer jog
if max_y - min_y <= pitch:
half_layer_width = 0.5 * drc["minwidth_{0}".format(self.horizontal_layer)]
# Add the vertical trunk on the horizontal layer!
self.add_path(self.horizontal_layer,
[vector(trunk_offset.x, min_y - half_layer_width),
vector(trunk_offset.x, max_y + half_layer_width)])
# Route each pin to the trunk
for pin in pins:
# No bend needed here
mid = vector(trunk_offset.x, pin.center().y)
self.add_path(self.horizontal_layer, [pin.center(), mid])
else:
# Add the vertical trunk
self.add_path(self.vertical_layer,
[vector(trunk_offset.x, min_y),
vector(trunk_offset.x, max_y)])
# Route each pin to the trunk
for pin in pins:
mid = vector(trunk_offset.x, pin.center().y)
self.add_path(self.horizontal_layer, [pin.center(), mid])
self.add_via_center(layers=layer_stack,
offset=mid,
directions=self.directions)
def create_channel_route(self, netlist,
offset,
layer_stack,
directions=None,
vertical=False):
"""
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. 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):
"""
Remove the pin from the graph and all conflicts
"""
g.pop(pin, None)
# Remove the pin from all conflicts
# FIXME: This is O(n^2), so maybe optimize it.
for other_pin, conflicts in g.items():
if pin in conflicts:
conflicts.remove(pin)
g[other_pin]=conflicts
return g
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):
return True
return False
def vcg_pin_overlap(pin1, pin2, vertical, pitch):
""" Check for vertical or horizontal overlap of the two pins """
# 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.
# Pin 1 must be in the "BOTTOM" set
x_overlap = pin1.by() < pin2.by() and abs(pin1.center().x - pin2.center().x) < pitch
# Pin 1 must be in the "LEFT" set
y_overlap = pin1.lx() < pin2.lx() and abs(pin1.center().y - pin2.center().y) < pitch
overlaps = (not vertical and x_overlap) or (vertical and y_overlap)
return overlaps
self.directions = directions
if not directions or directions == "pref":
# Use the preferred layer directions
if self.get_preferred_direction(layer_stack[0]) == "V":
self.vertical_layer = layer_stack[0]
self.horizontal_layer = layer_stack[2]
else:
self.vertical_layer = layer_stack[2]
self.horizontal_layer = layer_stack[0]
elif directions == "nonpref":
# Use the preferred layer directions
if self.get_preferred_direction(layer_stack[0]) == "V":
self.vertical_layer = layer_stack[2]
self.horizontal_layer = layer_stack[0]
else:
self.vertical_layer = layer_stack[0]
self.horizontal_layer = layer_stack[2]
else:
# Use the layer directions specified to the router rather than
# the preferred directions
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:
self.horizontal_layer = layer_stack[0]
self.vertical_layer = layer_stack[2]
layer_stuff = self.get_layer_pitch(self.vertical_layer)
(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_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
# number of tracks!
# hcg = {}
# Initialize the vertical conflict graph (vcg)
# and make a list of all pins
vcg = collections.OrderedDict()
# Create names for the nets for the graphs
nets = collections.OrderedDict()
index = 0
# print(netlist)
for pin_list in netlist:
net_name = "n{}".format(index)
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:
if net_name1 not in vcg.keys():
vcg[net_name1] = []
for net_name2 in nets:
if net_name2 not in vcg.keys():
vcg[net_name2] = []
# Skip yourself
if net_name1 == net_name2:
continue
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))
# get a route from conflict graph with empty fanout set
net_name = None
for net_name, conflicts in vcg.items():
if len(conflicts) == 0:
vcg = remove_net_from_graph(net_name, vcg)
break
else:
# FIXME: We don't support cyclic VCGs right now.
debug.error("Cyclic VCG in channel router.", -1)
# These are the pins we'll have to connect
pin_list = nets[net_name]
# print("Routing:", net_name, [x.name for x in pin_list])
# Remove the net from other constriants in the VCG
vcg = remove_net_from_graph(net_name, vcg)
# Add the trunk routes from the bottom up for
# horizontal or the left to right for vertical
if vertical:
self.add_vertical_trunk_route(pin_list,
offset,
layer_stack,
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_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, directions=None): def create_vertical_channel_route(self, netlist, offset, layer_stack, directions=None):
""" """
Wrapper to create a vertical channel route Wrapper to create a vertical channel route
""" """
self.create_channel_route(netlist, offset, layer_stack, directions, vertical=True) import channel_route
cr = channel_route.channel_route(netlist, offset, layer_stack, directions, vertical=True)
self.add_inst("vc", cr)
self.connect_inst([])
def create_horizontal_channel_route(self, netlist, offset, layer_stack, directions=None): def create_horizontal_channel_route(self, netlist, offset, layer_stack, directions=None):
""" """
Wrapper to create a horizontal channel route Wrapper to create a horizontal channel route
""" """
self.create_channel_route(netlist, offset, layer_stack, directions, vertical=False) import channel_route
cr = channel_route.channel_route(netlist, offset, layer_stack, directions, vertical=False)
self.add_inst("hc", cr)
self.connect_inst([])
def add_boundary(self, ll=vector(0, 0), ur=None): def add_boundary(self, ll=vector(0, 0), ur=None):
""" Add boundary for debugging dimensions """ """ Add boundary for debugging dimensions """
if OPTS.netlist_only: if OPTS.netlist_only:
@ -1301,27 +1054,49 @@ class layout():
height=ur.y - ll.y, height=ur.y - ll.y,
width=ur.x - ll.x) width=ur.x - ll.x)
def add_enclosure(self, insts, layer="nwell"): def add_enclosure(self, insts, layer="nwell", extend=0, leftx=None, rightx=None, topy=None, boty=None):
""" Add a layer that surrounds the given instances. Useful """
Add a layer that surrounds the given instances. Useful
for creating wells, for example. Doesn't check for minimum widths or for creating wells, for example. Doesn't check for minimum widths or
spacings.""" spacings. Extra arg can force a dimension to one side left/right top/bot.
"""
xmin = insts[0].lx() if leftx != None:
ymin = insts[0].by() xmin = leftx
xmax = insts[0].rx() else:
ymax = insts[0].uy() xmin = insts[0].lx()
for inst in insts: for inst in insts:
xmin = min(xmin, inst.lx()) xmin = min(xmin, inst.lx())
ymin = min(ymin, inst.by()) xmin = xmin - extend
xmax = max(xmax, inst.rx()) if boty != None:
ymax = max(ymax, inst.uy()) ymin = boty
else:
ymin = insts[0].by()
for inst in insts:
ymin = min(ymin, inst.by())
ymin = ymin - extend
if rightx != None:
xmax = rightx
else:
xmax = insts[0].rx()
for inst in insts:
xmax = max(xmax, inst.rx())
xmax = xmax + extend
if topy != None:
ymax = topy
else:
ymax = insts[0].uy()
for inst in insts:
ymax = max(ymax, inst.uy())
ymax = ymax + extend
self.add_rect(layer=layer, rect = self.add_rect(layer=layer,
offset=vector(xmin, ymin), offset=vector(xmin, ymin),
width=xmax - xmin, width=xmax - xmin,
height=ymax - ymin) height=ymax - ymin)
return rect
def copy_power_pins(self, inst, name):
def copy_power_pins(self, inst, name, add_vias=True):
""" """
This will copy a power pin if it is on the lowest power_grid layer. This will copy a power pin if it is on the lowest power_grid layer.
If it is on M1, it will add a power via too. If it is on M1, it will add a power via too.
@ -1335,9 +1110,9 @@ class layout():
pin.width(), pin.width(),
pin.height()) pin.height())
else: elif add_vias:
self.add_power_pin(name, pin.center(), start_layer=pin.layer) self.add_power_pin(name, pin.center(), start_layer=pin.layer)
def add_power_pin(self, name, loc, size=[1, 1], directions=None, 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 Add a single power pin from the lowest power_grid layer down to M1 (or li) at
@ -1355,6 +1130,7 @@ class layout():
size=size, size=size,
offset=loc, offset=loc,
directions=directions) directions=directions)
# Hack for min area # Hack for min area
if OPTS.tech_name == "sky130": if OPTS.tech_name == "sky130":
width = round_to_grid(sqrt(drc["minarea_m3"])) width = round_to_grid(sqrt(drc["minarea_m3"]))
@ -1385,7 +1161,7 @@ class layout():
layer = "m3" layer = "m3"
elif side == "right": elif side == "right":
layer = "m3" layer = "m3"
peri_pin_loc = vector(right, pin_loc.x) peri_pin_loc = vector(right, pin_loc.y)
elif side == "top": elif side == "top":
layer = "m4" layer = "m4"
peri_pin_loc = vector(pin_loc.x, top) peri_pin_loc = vector(pin_loc.x, top)
@ -1400,13 +1176,9 @@ class layout():
self.add_path(layer, self.add_path(layer,
[pin_loc, peri_pin_loc]) [pin_loc, peri_pin_loc])
self.add_via_stack_center(from_layer=layer, return self.add_layout_pin_rect_center(text=name,
to_layer="m4", layer=layer,
offset=peri_pin_loc) offset=peri_pin_loc)
self.add_layout_pin_rect_center(text=name,
layer="m4",
offset=peri_pin_loc)
def add_power_ring(self, bbox): def add_power_ring(self, bbox):
""" """

View File

@ -8,7 +8,7 @@
import debug import debug
from tech import GDS, drc from tech import GDS, drc
from vector import vector from vector import vector
from tech import layer from tech import layer, layer_indices
import math import math
@ -31,12 +31,15 @@ class pin_layout:
debug.check(self.width() > 0, "Zero width pin.") debug.check(self.width() > 0, "Zero width pin.")
debug.check(self.height() > 0, "Zero height pin.") debug.check(self.height() > 0, "Zero height pin.")
# These are the valid pin layers
valid_layers = { x: layer[x] for x in layer_indices.keys()}
# if it's a string, use the name # if it's a string, use the name
if type(layer_name_pp) == str: if type(layer_name_pp) == str:
self._layer = layer_name_pp self._layer = layer_name_pp
# else it is required to be a lpp # else it is required to be a lpp
else: else:
for (layer_name, lpp) in layer.items(): for (layer_name, lpp) in valid_layers.items():
if not lpp: if not lpp:
continue continue
if self.same_lpp(layer_name_pp, lpp): if self.same_lpp(layer_name_pp, lpp):

View File

@ -657,8 +657,12 @@ class lib:
)) ))
# information of checks # information of checks
(drc_errors, lvs_errors) = self.sram.DRC_LVS(final_verification=True) # run it only the first time
datasheet.write("{0},{1},".format(drc_errors, lvs_errors)) try:
datasheet.write("{0},{1},".format(self.drc_errors, self.lvs_errors))
except AttributeError:
(self.drc_errors, self.lvs_errors) = self.sram.DRC_LVS(final_verification=True)
datasheet.write("{0},{1},".format(self.drc_errors, self.lvs_errors))
# write area # write area
datasheet.write(str(self.sram.width * self.sram.height) + ',') datasheet.write(str(self.sram.width * self.sram.height) + ',')

View File

@ -40,7 +40,7 @@ def error(str, return_value=0):
log("ERROR: file {0}: line {1}: {2}\n".format( log("ERROR: file {0}: line {1}: {2}\n".format(
os.path.basename(filename), line_number, str)) os.path.basename(filename), line_number, str))
if globals.OPTS.debug_level > 0: if globals.OPTS.debug_level > 0 and return_value != 0:
import pdb import pdb
pdb.set_trace() pdb.set_trace()
assert return_value == 0 assert return_value == 0

View File

@ -132,14 +132,17 @@ class bank(design.design):
# Connect the rbl to the port data pin # Connect the rbl to the port data pin
bl_pin = self.port_data_inst[port].get_pin("rbl_bl") bl_pin = self.port_data_inst[port].get_pin("rbl_bl")
if port % 2: if port % 2:
pin_offset = bl_pin.uc() pin_pos = bl_pin.uc()
pin_offset = pin_pos + vector(0, self.m3_pitch)
left_right_offset = vector(self.max_x_offset, pin_offset.y) left_right_offset = vector(self.max_x_offset, pin_offset.y)
else: else:
pin_offset = bl_pin.bc() pin_pos = bl_pin.bc()
pin_offset = pin_pos - vector(0, self.m3_pitch)
left_right_offset = vector(self.min_x_offset, pin_offset.y) left_right_offset = vector(self.min_x_offset, pin_offset.y)
self.add_via_stack_center(from_layer=bl_pin.layer, self.add_via_stack_center(from_layer=bl_pin.layer,
to_layer="m3", to_layer="m3",
offset=pin_offset) offset=pin_offset)
self.add_path(bl_pin.layer, [pin_offset, pin_pos])
self.add_layout_pin_segment_center(text="rbl_bl{0}".format(port), self.add_layout_pin_segment_center(text="rbl_bl{0}".format(port),
layer="m3", layer="m3",
start=left_right_offset, start=left_right_offset,
@ -217,11 +220,12 @@ class bank(design.design):
# Place the col decoder left aligned with wordline driver # Place the col decoder left aligned with wordline driver
# This is also placed so that it's supply rails do not align with the SRAM-level # This is also placed so that it's supply rails do not align with the SRAM-level
# control logic to allow control signals to easily pass over in M3 # control logic to allow control signals to easily pass over in M3
# by placing 1/2 a cell pitch down # by placing 1 1/4 a cell pitch down because both power connections and inputs/outputs
# may be routed in M3 or M4
x_offset = self.central_bus_width[port] + self.port_address.wordline_driver.width x_offset = self.central_bus_width[port] + self.port_address.wordline_driver.width
if self.col_addr_size > 0: if self.col_addr_size > 0:
x_offset += self.column_decoder.width + self.col_addr_bus_width x_offset += self.column_decoder.width + self.col_addr_bus_width
y_offset = 0.5 * self.dff.height + self.column_decoder.height y_offset = 1.25 * self.dff.height + self.column_decoder.height
else: else:
y_offset = 0 y_offset = 0
self.column_decoder_offsets[port] = vector(-x_offset, -y_offset) self.column_decoder_offsets[port] = vector(-x_offset, -y_offset)
@ -258,10 +262,14 @@ class bank(design.design):
# UPPER RIGHT QUADRANT # UPPER RIGHT QUADRANT
# Place the col decoder right aligned with wordline driver # Place the col decoder right aligned with wordline driver
# Above the bitcell array with a well spacing # Above the bitcell array with a well spacing
# This is also placed so that it's supply rails do not align with the SRAM-level
# control logic to allow control signals to easily pass over in M3
# by placing 1 1/4 a cell pitch down because both power connections and inputs/outputs
# may be routed in M3 or M4
x_offset = self.bitcell_array_right + self.central_bus_width[port] + self.port_address.wordline_driver.width x_offset = self.bitcell_array_right + self.central_bus_width[port] + self.port_address.wordline_driver.width
if self.col_addr_size > 0: if self.col_addr_size > 0:
x_offset += self.column_decoder.width + self.col_addr_bus_width x_offset += self.column_decoder.width + self.col_addr_bus_width
y_offset = self.bitcell_array_top + 0.5 * self.dff.height + self.column_decoder.height y_offset = self.bitcell_array_top + 1.25 * self.dff.height + self.column_decoder.height
else: else:
y_offset = self.bitcell_array_top y_offset = self.bitcell_array_top
self.column_decoder_offsets[port] = vector(x_offset, y_offset) self.column_decoder_offsets[port] = vector(x_offset, y_offset)
@ -308,7 +316,7 @@ class bank(design.design):
self.input_control_signals = [] self.input_control_signals = []
port_num = 0 port_num = 0
for port in range(OPTS.num_rw_ports): for port in range(OPTS.num_rw_ports):
self.input_control_signals.append(["w_en{}".format(port_num), "s_en{}".format(port_num), "p_en_bar{}".format(port_num), "wl_en{}".format(port_num)]) self.input_control_signals.append(["s_en{}".format(port_num), "w_en{}".format(port_num), "p_en_bar{}".format(port_num), "wl_en{}".format(port_num)])
port_num += 1 port_num += 1
for port in range(OPTS.num_w_ports): for port in range(OPTS.num_w_ports):
self.input_control_signals.append(["w_en{}".format(port_num), "p_en_bar{}".format(port_num), "wl_en{}".format(port_num)]) self.input_control_signals.append(["w_en{}".format(port_num), "p_en_bar{}".format(port_num), "wl_en{}".format(port_num)])
@ -321,7 +329,7 @@ class bank(design.design):
self.num_control_lines = [len(x) for x in self.input_control_signals] self.num_control_lines = [len(x) for x in self.input_control_signals]
# The width of this bus is needed to place other modules (e.g. decoder) for each port # The width of this bus is needed to place other modules (e.g. decoder) for each port
self.central_bus_width = [self.m2_pitch * x + self.m2_width for x in self.num_control_lines] self.central_bus_width = [self.m3_pitch * x + self.m3_width for x in self.num_control_lines]
# These will be outputs of the gaters if this is multibank, if not, normal signals. # These will be outputs of the gaters if this is multibank, if not, normal signals.
self.control_signals = [] self.control_signals = []
@ -330,6 +338,7 @@ class bank(design.design):
self.control_signals.append(["gated_" + str for str in self.input_control_signals[port]]) self.control_signals.append(["gated_" + str for str in self.input_control_signals[port]])
else: else:
self.control_signals.append(self.input_control_signals[port]) self.control_signals.append(self.input_control_signals[port])
# The central bus is the column address (one hot) and row address (binary) # The central bus is the column address (one hot) and row address (binary)
if self.col_addr_size>0: if self.col_addr_size>0:
@ -497,18 +506,20 @@ class bank(design.design):
Create a 2:4 or 3:8 column address decoder. Create a 2:4 or 3:8 column address decoder.
""" """
# Height is a multiple of DFF so that it can be staggered self.dff =factory.create(module_type="dff")
# and rows do not align with the control logic module
self.dff = factory.create(module_type="dff")
if self.col_addr_size == 0: if self.col_addr_size == 0:
return return
elif self.col_addr_size == 1: elif self.col_addr_size == 1:
self.column_decoder = factory.create(module_type="pinvbuf", height=self.dff.height) self.column_decoder = factory.create(module_type="pinvbuf",
height=self.dff.height)
elif self.col_addr_size == 2: elif self.col_addr_size == 2:
self.column_decoder = factory.create(module_type="hierarchical_predecode2x4", height=self.dff.height) self.column_decoder = factory.create(module_type="hierarchical_predecode2x4",
height=self.dff.height)
elif self.col_addr_size == 3: elif self.col_addr_size == 3:
self.column_decoder = factory.create(module_type="hierarchical_predecode3x8", height=self.dff.height) self.column_decoder = factory.create(module_type="hierarchical_predecode3x8",
height=self.dff.height)
else: else:
# No error checking before? # No error checking before?
debug.error("Invalid column decoder?", -1) debug.error("Invalid column decoder?", -1)
@ -576,9 +587,23 @@ class bank(design.design):
def route_supplies(self): def route_supplies(self):
""" Propagate all vdd/gnd pins up to this level for all modules """ """ Propagate all vdd/gnd pins up to this level for all modules """
# Copy only the power pins already on the power layer
# (this won't add vias to internal bitcell pins, for example)
for inst in self.insts: for inst in self.insts:
self.copy_power_pins(inst, "vdd") self.copy_power_pins(inst, "vdd", add_vias=False)
self.copy_power_pins(inst, "gnd") self.copy_power_pins(inst, "gnd", add_vias=False)
# If we use the pinvbuf as the decoder, we need to add power pins.
# Other decoders already have them.
if self.col_addr_size == 1:
for port in self.all_ports:
inst = self.column_decoder_inst[port]
for pin_name in ["vdd", "gnd"]:
pin_list = inst.get_pins(pin_name)
for pin in pin_list:
self.add_power_pin(pin_name,
pin.center(),
start_layer=pin.layer)
def route_bank_select(self, port): def route_bank_select(self, port):
""" Route the bank select logic. """ """ Route the bank select logic. """
@ -648,7 +673,8 @@ class bank(design.design):
names=self.control_signals[0], names=self.control_signals[0],
length=control_bus_length, length=control_bus_length,
vertical=True, vertical=True,
make_pins=(self.num_banks==1)) make_pins=(self.num_banks==1),
pitch=self.m3_pitch)
# Port 1 # Port 1
if len(self.all_ports)==2: if len(self.all_ports)==2:
@ -662,7 +688,8 @@ class bank(design.design):
names=list(reversed(self.control_signals[1])), names=list(reversed(self.control_signals[1])),
length=control_bus_length, length=control_bus_length,
vertical=True, vertical=True,
make_pins=(self.num_banks==1)) make_pins=(self.num_banks==1),
pitch=self.m3_pitch)
def route_port_data_to_bitcell_array(self, port): def route_port_data_to_bitcell_array(self, port):
""" Routing of BL and BR between port data and bitcell array """ """ Routing of BL and BR between port data and bitcell array """
@ -850,6 +877,13 @@ class bank(design.design):
if not self.col_addr_size>0: if not self.col_addr_size>0:
return return
if OPTS.tech_name == "sky130":
stack = self.m2_stack
pitch = self.m3_pitch
else:
stack = self.m1_stack
pitch = self.m2_pitch
if self.col_addr_size == 1: if self.col_addr_size == 1:
# Connect to sel[0] and sel[1] # Connect to sel[0] and sel[1]
@ -869,9 +903,9 @@ class bank(design.design):
self.copy_layout_pin(self.column_decoder_inst[port], decoder_name, addr_name) self.copy_layout_pin(self.column_decoder_inst[port], decoder_name, addr_name)
if port % 2: if port % 2:
offset = self.column_decoder_inst[port].ll() - vector(self.num_col_addr_lines * self.m2_nonpref_pitch, 0) offset = self.column_decoder_inst[port].ll() - vector((self.num_col_addr_lines + 1) * pitch, 0)
else: else:
offset = self.column_decoder_inst[port].lr() + vector(self.m2_nonpref_pitch, 0) offset = self.column_decoder_inst[port].lr() + vector(pitch, 0)
decode_pins = [self.column_decoder_inst[port].get_pin(x) for x in decode_names] decode_pins = [self.column_decoder_inst[port].get_pin(x) for x in decode_names]
@ -879,16 +913,9 @@ class bank(design.design):
column_mux_pins = [self.port_data_inst[port].get_pin(x) for x in sel_names] 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)) route_map = list(zip(decode_pins, column_mux_pins))
if "li" in layer:
stack = self.li_stack
directions = "nonpref"
else:
stack = self.m1_stack
directions = "pref"
self.create_vertical_channel_route(route_map, self.create_vertical_channel_route(route_map,
offset, offset,
stack, stack)
directions=directions)
def add_lvs_correspondence_points(self): def add_lvs_correspondence_points(self):
""" """

View File

@ -9,6 +9,7 @@ import debug
import design import design
from tech import cell_properties from tech import cell_properties
class bitcell_base_array(design.design): class bitcell_base_array(design.design):
""" """
Abstract base class for bitcell-arrays -- bitcell, dummy Abstract base class for bitcell-arrays -- bitcell, dummy
@ -68,10 +69,10 @@ class bitcell_base_array(design.design):
pin_names = self.cell.get_all_bitline_names() pin_names = self.cell.get_all_bitline_names()
for pin in pin_names: for pin in pin_names:
bitcell_pins.append(pin+"_{0}".format(col)) bitcell_pins.append(pin + "_{0}".format(col))
pin_names = self.cell.get_all_wl_names() pin_names = self.cell.get_all_wl_names()
for pin in pin_names: for pin in pin_names:
bitcell_pins.append(pin+"_{0}".format(row)) bitcell_pins.append(pin + "_{0}".format(row))
bitcell_pins.append("vdd") bitcell_pins.append("vdd")
bitcell_pins.append("gnd") bitcell_pins.append("gnd")
@ -85,46 +86,28 @@ class bitcell_base_array(design.design):
for col in range(self.column_size): for col in range(self.column_size):
for cell_column in column_list: for cell_column in column_list:
bl_pin = self.cell_inst[0,col].get_pin(cell_column) bl_pin = self.cell_inst[0, col].get_pin(cell_column)
self.add_layout_pin(text=cell_column+"_{0}".format(col), self.add_layout_pin(text=cell_column + "_{0}".format(col),
layer=bl_pin.layer, layer=bl_pin.layer,
offset=bl_pin.ll().scale(1,0), offset=bl_pin.ll().scale(1, 0),
width=bl_pin.width(), width=bl_pin.width(),
height=self.height) height=self.height)
for row in range(self.row_size): for row in range(self.row_size):
for cell_row in row_list: for cell_row in row_list:
wl_pin = self.cell_inst[row,0].get_pin(cell_row) wl_pin = self.cell_inst[row, 0].get_pin(cell_row)
self.add_layout_pin(text=cell_row+"_{0}".format(row), self.add_layout_pin(text=cell_row + "_{0}".format(row),
layer=wl_pin.layer, layer=wl_pin.layer,
offset=wl_pin.ll().scale(0,1), offset=wl_pin.ll().scale(0, 1),
width=self.width, width=self.width,
height=wl_pin.height()) height=wl_pin.height())
# For non-square via stacks, vertical/horizontal direction refers to the stack orientation in 2d space # Copy a vdd/gnd layout pin from every cell
# Default uses prefered directions for each layer; this cell property is only currently used by sky130 tech (03/20)
try:
bitcell_power_pin_directions = cell_properties.bitcell_power_pin_directions
except AttributeError:
bitcell_power_pin_directions = None
# For specific technologies, there is no vdd via within the bitcell. Instead vdd is connect via end caps.
try:
bitcell_no_vdd_pin = cell_properties.bitcell.no_vdd_via
except AttributeError:
bitcell_no_vdd_pin = False
# Add vdd/gnd via stacks
for row in range(self.row_size): for row in range(self.row_size):
for col in range(self.column_size): for col in range(self.column_size):
inst = self.cell_inst[row,col] inst = self.cell_inst[row, col]
for pin_name in ["vdd", "gnd"]: for pin_name in ["vdd", "gnd"]:
for pin in inst.get_pins(pin_name): self.copy_layout_pin(inst, pin_name)
if not (pin_name == "vdd" and bitcell_no_vdd_pin):
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): def _adjust_x_offset(self, xoffset, col, col_offset):
tempx = xoffset tempx = xoffset
@ -144,11 +127,10 @@ class bitcell_base_array(design.design):
dir_x = True dir_x = True
return (tempy, dir_x) return (tempy, dir_x)
def place_array(self, name_template, row_offset=0): def place_array(self, name_template, row_offset=0):
# We increase it by a well enclosure so the precharges don't overlap our wells # We increase it by a well enclosure so the precharges don't overlap our wells
self.height = self.row_size*self.cell.height self.height = self.row_size * self.cell.height
self.width = self.column_size*self.cell.width self.width = self.column_size * self.cell.width
xoffset = 0.0 xoffset = 0.0
for col in range(self.column_size): for col in range(self.column_size):
@ -156,7 +138,6 @@ class bitcell_base_array(design.design):
tempx, dir_y = self._adjust_x_offset(xoffset, col, self.column_offset) tempx, dir_y = self._adjust_x_offset(xoffset, col, self.column_offset)
for row in range(self.row_size): for row in range(self.row_size):
name = name_template.format(row, col)
tempy, dir_x = self._adjust_y_offset(yoffset, row, row_offset) tempy, dir_x = self._adjust_y_offset(yoffset, row, row_offset)
if dir_x and dir_y: if dir_x and dir_y:
@ -168,7 +149,7 @@ class bitcell_base_array(design.design):
else: else:
dir_key = "" dir_key = ""
self.cell_inst[row,col].place(offset=[tempx, tempy], self.cell_inst[row, col].place(offset=[tempx, tempy],
mirror=dir_key) mirror=dir_key)
yoffset += self.cell.height yoffset += self.cell.height
xoffset += self.cell.width xoffset += self.cell.width

View File

@ -523,12 +523,12 @@ class control_logic(design.design):
def route_clk_buf(self): def route_clk_buf(self):
clk_pin = self.clk_buf_inst.get_pin("A") clk_pin = self.clk_buf_inst.get_pin("A")
clk_pos = clk_pin.center() clk_pos = clk_pin.center()
self.add_layout_pin_segment_center(text="clk", self.add_layout_pin_rect_center(text="clk",
layer="m2", layer="m2",
start=clk_pos, offset=clk_pos)
end=clk_pos.scale(1, 0)) self.add_via_stack_center(from_layer=clk_pin.layer,
self.add_via_center(layers=self.m1_stack, to_layer="m2",
offset=clk_pos) offset=clk_pos)
self.route_output_to_bus_jogged(self.clk_buf_inst, self.route_output_to_bus_jogged(self.clk_buf_inst,
"clk_buf") "clk_buf")
@ -555,9 +555,15 @@ class control_logic(design.design):
clkbuf_map = zip(["A"], ["clk_buf"]) clkbuf_map = zip(["A"], ["clk_buf"])
self.connect_vertical_bus(clkbuf_map, self.clk_bar_inst, self.input_bus) self.connect_vertical_bus(clkbuf_map, self.clk_bar_inst, self.input_bus)
out_pos = self.clk_bar_inst.get_pin("Z").center() out_pin = self.clk_bar_inst.get_pin("Z")
in_pos = self.gated_clk_bar_inst.get_pin("A").center() out_pos = out_pin.center()
self.add_zjog("m1", out_pos, in_pos) in_pin = self.gated_clk_bar_inst.get_pin("A")
in_pos = in_pin.center()
self.add_zjog(out_pin.layer, out_pos, in_pos)
self.add_via_stack_center(from_layer=out_pin.layer,
to_layer=in_pin.layer,
offset=in_pos)
# This is the second gate over, so it needs to be on M3 # This is the second gate over, so it needs to be on M3
clkbuf_map = zip(["B"], ["cs"]) clkbuf_map = zip(["B"], ["cs"])
@ -796,32 +802,41 @@ class control_logic(design.design):
""" Create an output pin on the right side from the pin of a given instance. """ """ Create an output pin on the right side from the pin of a given instance. """
out_pin = inst.get_pin(pin_name) out_pin = inst.get_pin(pin_name)
right_pos = out_pin.center() + vector(self.width - out_pin.cx(), 0) out_pos = out_pin.center()
right_pos = out_pos + vector(self.width - out_pin.cx(), 0)
self.add_via_stack_center(from_layer=out_pin.layer,
to_layer="m2",
offset=out_pos)
self.add_layout_pin_segment_center(text=out_name, self.add_layout_pin_segment_center(text=out_name,
layer="m1", layer="m2",
start=out_pin.center(), start=out_pos,
end=right_pos) end=right_pos)
def route_supply(self): def route_supply(self):
""" Add vdd and gnd to the instance cells """ """ Add vdd and gnd to the instance cells """
if OPTS.tech_name == "sky130":
supply_layer = "li"
else:
supply_layer = "m1"
max_row_x_loc = max([inst.rx() for inst in self.row_end_inst]) max_row_x_loc = max([inst.rx() for inst in self.row_end_inst])
for inst in self.row_end_inst: for inst in self.row_end_inst:
pins = inst.get_pins("vdd") pins = inst.get_pins("vdd")
for pin in pins: for pin in pins:
if pin.layer == "m1": if pin.layer == supply_layer:
row_loc = pin.rc() row_loc = pin.rc()
pin_loc = vector(max_row_x_loc, pin.rc().y) pin_loc = vector(max_row_x_loc, pin.rc().y)
self.add_power_pin("vdd", pin_loc) self.add_power_pin("vdd", pin_loc, start_layer=pin.layer)
self.add_path("m1", [row_loc, pin_loc]) self.add_path(supply_layer, [row_loc, pin_loc])
pins = inst.get_pins("gnd") pins = inst.get_pins("gnd")
for pin in pins: for pin in pins:
if pin.layer == "m1": if pin.layer == supply_layer:
row_loc = pin.rc() row_loc = pin.rc()
pin_loc = vector(max_row_x_loc, pin.rc().y) pin_loc = vector(max_row_x_loc, pin.rc().y)
self.add_power_pin("gnd", pin_loc) self.add_power_pin("gnd", pin_loc, start_layer=pin.layer)
self.add_path("m1", [row_loc, pin_loc]) self.add_path(supply_layer, [row_loc, pin_loc])
self.copy_layout_pin(self.delay_inst, "gnd") self.copy_layout_pin(self.delay_inst, "gnd")
self.copy_layout_pin(self.delay_inst, "vdd") self.copy_layout_pin(self.delay_inst, "vdd")
@ -1004,12 +1019,13 @@ class control_logic(design.design):
def route_output_to_bus_jogged(self, inst, name): def route_output_to_bus_jogged(self, inst, name):
# Connect this at the bottom of the buffer # Connect this at the bottom of the buffer
out_pos = inst.get_pin("Z").center() out_pin = inst.get_pin("Z")
out_pos = out_pin.center()
mid1 = vector(out_pos.x, out_pos.y - 0.4 * inst.mod.height) mid1 = vector(out_pos.x, out_pos.y - 0.4 * inst.mod.height)
mid2 = vector(self.input_bus[name].cx(), mid1.y) mid2 = vector(self.input_bus[name].cx(), mid1.y)
bus_pos = self.input_bus[name].center() bus_pos = self.input_bus[name].center()
self.add_wire(self.m2_stack[::-1], [out_pos, mid1, mid2, bus_pos]) 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_stack_center(from_layer=out_pin.layer,
self.add_via_center(layers=self.m1_stack, to_layer="m2",
offset=out_pos) offset=out_pos)

View File

@ -140,21 +140,20 @@ class delay_chain(design.design):
for load in self.load_inst_map[inv]: for load in self.load_inst_map[inv]:
# Drop a via on each A pin # Drop a via on each A pin
a_pin = load.get_pin("A") a_pin = load.get_pin("A")
self.add_via_center(layers=self.m1_stack, self.add_via_stack_center(from_layer=a_pin.layer,
offset=a_pin.center()) to_layer="m3",
self.add_via_center(layers=self.m2_stack, offset=a_pin.center())
offset=a_pin.center())
# Route an M3 horizontal wire to the furthest # Route an M3 horizontal wire to the furthest
z_pin = inv.get_pin("Z") z_pin = inv.get_pin("Z")
a_pin = inv.get_pin("A") a_pin = inv.get_pin("A")
a_max = self.load_inst_map[inv][-1].get_pin("A") a_max = self.load_inst_map[inv][-1].get_pin("A")
self.add_via_center(layers=self.m1_stack, self.add_via_stack_center(from_layer=a_pin.layer,
offset=a_pin.center()) to_layer="m2",
self.add_via_center(layers=self.m1_stack, offset=a_pin.center())
offset=z_pin.center()) self.add_via_stack_center(from_layer=z_pin.layer,
self.add_via_center(layers=self.m2_stack, to_layer="m3",
offset=z_pin.center()) offset=z_pin.center())
self.add_path("m3", [z_pin.center(), a_max.center()]) self.add_path("m3", [z_pin.center(), a_max.center()])
# Route Z to the A of the next stage # Route Z to the A of the next stage
@ -178,17 +177,22 @@ class delay_chain(design.design):
load_list = self.load_inst_map[inst] load_list = self.load_inst_map[inst]
for pin_name in ["vdd", "gnd"]: for pin_name in ["vdd", "gnd"]:
pin = load_list[0].get_pin(pin_name) pin = load_list[0].get_pin(pin_name)
self.add_power_pin(pin_name, pin.rc() - vector(self.m1_pitch, 0)) self.add_power_pin(pin_name,
pin.rc() - vector(self.m1_pitch, 0),
start_layer=pin.layer)
pin = load_list[-1].get_pin(pin_name) pin = load_list[-2].get_pin(pin_name)
self.add_power_pin(pin_name, pin.rc() - vector(0.5 * self.m1_pitch, 0)) self.add_power_pin(pin_name,
pin.rc() - vector(self.m1_pitch, 0),
start_layer=pin.layer)
def add_layout_pins(self): def add_layout_pins(self):
# input is A pin of first inverter # input is A pin of first inverter
a_pin = self.driver_inst_list[0].get_pin("A") a_pin = self.driver_inst_list[0].get_pin("A")
self.add_via_center(layers=self.m1_stack, self.add_via_stack_center(from_layer=a_pin.layer,
offset=a_pin.center()) to_layer="m2",
offset=a_pin.center())
self.add_layout_pin(text="in", self.add_layout_pin(text="in",
layer="m2", layer="m2",
offset=a_pin.ll().scale(1, 0), offset=a_pin.ll().scale(1, 0),
@ -197,8 +201,9 @@ class delay_chain(design.design):
# output is A pin of last load inverter # output is A pin of last load inverter
last_driver_inst = self.driver_inst_list[-1] last_driver_inst = self.driver_inst_list[-1]
a_pin = self.load_inst_map[last_driver_inst][-1].get_pin("A") a_pin = self.load_inst_map[last_driver_inst][-1].get_pin("A")
self.add_via_center(layers=self.m1_stack, self.add_via_stack_center(from_layer=a_pin.layer,
offset=a_pin.center()) to_layer="m2",
offset=a_pin.center())
mid_point = vector(a_pin.cx() + 3 * self.m2_width, a_pin.cy()) mid_point = vector(a_pin.cx() + 3 * self.m2_width, a_pin.cy())
self.add_path("m2", [a_pin.center(), mid_point, mid_point.scale(1, 0)]) self.add_path("m2", [a_pin.center(), mid_point, mid_point.scale(1, 0)])
self.add_layout_pin_segment_center(text="out", self.add_layout_pin_segment_center(text="out",

View File

@ -7,12 +7,11 @@
# #
import debug import debug
import design import design
from tech import drc
from math import log
from vector import vector from vector import vector
from sram_factory import factory from sram_factory import factory
from globals import OPTS from globals import OPTS
class dff_array(design.design): class dff_array(design.design):
""" """
This is a simple row (or multiple rows) of flops. This is a simple row (or multiple rows) of flops.
@ -52,42 +51,41 @@ class dff_array(design.design):
self.add_mod(self.dff) self.add_mod(self.dff)
def add_pins(self): def add_pins(self):
for row in range(self.rows): for row in range(self.rows):
for col in range(self.columns): for col in range(self.columns):
self.add_pin(self.get_din_name(row,col), "INPUT") self.add_pin(self.get_din_name(row, col), "INPUT")
for row in range(self.rows): for row in range(self.rows):
for col in range(self.columns): for col in range(self.columns):
self.add_pin(self.get_dout_name(row,col), "OUTPUT") self.add_pin(self.get_dout_name(row, col), "OUTPUT")
self.add_pin("clk", "INPUT") self.add_pin("clk", "INPUT")
self.add_pin("vdd", "POWER") self.add_pin("vdd", "POWER")
self.add_pin("gnd", "GROUND") self.add_pin("gnd", "GROUND")
def create_dff_array(self): def create_dff_array(self):
self.dff_insts={} self.dff_insts={}
for row in range(self.rows): for row in range(self.rows):
for col in range(self.columns): for col in range(self.columns):
name = "dff_r{0}_c{1}".format(row,col) name = "dff_r{0}_c{1}".format(row, col)
self.dff_insts[row,col]=self.add_inst(name=name, self.dff_insts[row, col]=self.add_inst(name=name,
mod=self.dff) mod=self.dff)
instance_ports = [self.get_din_name(row,col), instance_ports = [self.get_din_name(row, col),
self.get_dout_name(row,col)] self.get_dout_name(row, col)]
for port in self.dff.pin_names: for port in self.dff.pin_names:
if port != 'D' and port != 'Q': if port != 'D' and port != 'Q':
instance_ports.append(port) instance_ports.append(port)
self.connect_inst(instance_ports) self.connect_inst(instance_ports)
def place_dff_array(self): def place_dff_array(self):
for row in range(self.rows): for row in range(self.rows):
for col in range(self.columns): for col in range(self.columns):
name = "dff_r{0}_c{1}".format(row,col)
if (row % 2 == 0): if (row % 2 == 0):
base = vector(col*self.dff.width,row*self.dff.height) base = vector(col * self.dff.width, row * self.dff.height)
mirror = "R0" mirror = "R0"
else: else:
base = vector(col*self.dff.width,(row+1)*self.dff.height) base = vector(col * self.dff.width, (row + 1) * self.dff.height)
mirror = "MX" mirror = "MX"
self.dff_insts[row,col].place(offset=base, self.dff_insts[row, col].place(offset=base,
mirror=mirror) mirror=mirror)
def get_din_name(self, row, col): def get_din_name(self, row, col):
if self.columns == 1: if self.columns == 1:
@ -95,7 +93,7 @@ class dff_array(design.design):
elif self.rows == 1: elif self.rows == 1:
din_name = "din_{0}".format(col) din_name = "din_{0}".format(col)
else: else:
din_name = "din_{0}_{1}".format(row,col) din_name = "din_{0}_{1}".format(row, col)
return din_name return din_name
@ -105,61 +103,58 @@ class dff_array(design.design):
elif self.rows == 1: elif self.rows == 1:
dout_name = "dout_{0}".format(col) dout_name = "dout_{0}".format(col)
else: else:
dout_name = "dout_{0}_{1}".format(row,col) dout_name = "dout_{0}_{1}".format(row, col)
return dout_name return dout_name
def add_layout_pins(self): def add_layout_pins(self):
for row in range(self.rows): for row in range(self.rows):
for col in range(self.columns): for col in range(self.columns):
# Continous vdd rail along with label. # Continous vdd rail along with label.
vdd_pin=self.dff_insts[row,col].get_pin("vdd") vdd_pin=self.dff_insts[row, col].get_pin("vdd")
self.add_power_pin("vdd", vdd_pin.center()) self.add_power_pin("vdd", vdd_pin.center(), start_layer=vdd_pin.layer)
# Continous gnd rail along with label. # Continous gnd rail along with label.
gnd_pin=self.dff_insts[row,col].get_pin("gnd") gnd_pin=self.dff_insts[row, col].get_pin("gnd")
self.add_power_pin("gnd", gnd_pin.center()) self.add_power_pin("gnd", gnd_pin.center(), start_layer=gnd_pin.layer)
for row in range(self.rows):
for row in range(self.rows): for col in range(self.columns):
for col in range(self.columns): din_pin = self.dff_insts[row, col].get_pin("D")
din_pin = self.dff_insts[row,col].get_pin("D") debug.check(din_pin.layer == "m2", "DFF D pin not on metal2")
debug.check(din_pin.layer=="m2","DFF D pin not on metal2") self.add_layout_pin(text=self.get_din_name(row, col),
self.add_layout_pin(text=self.get_din_name(row,col),
layer=din_pin.layer, layer=din_pin.layer,
offset=din_pin.ll(), offset=din_pin.ll(),
width=din_pin.width(), width=din_pin.width(),
height=din_pin.height()) height=din_pin.height())
dout_pin = self.dff_insts[row,col].get_pin("Q") dout_pin = self.dff_insts[row, col].get_pin("Q")
debug.check(dout_pin.layer=="m2","DFF Q pin not on metal2") debug.check(dout_pin.layer == "m2", "DFF Q pin not on metal2")
self.add_layout_pin(text=self.get_dout_name(row,col), self.add_layout_pin(text=self.get_dout_name(row, col),
layer=dout_pin.layer, layer=dout_pin.layer,
offset=dout_pin.ll(), offset=dout_pin.ll(),
width=dout_pin.width(), width=dout_pin.width(),
height=dout_pin.height()) height=dout_pin.height())
# Create vertical spines to a single horizontal rail # Create vertical spines to a single horizontal rail
clk_pin = self.dff_insts[0,0].get_pin(self.dff.clk_pin) clk_pin = self.dff_insts[0, 0].get_pin(self.dff.clk_pin)
clk_ypos = 2*self.m3_pitch+self.m3_width clk_ypos = 2 * self.m3_pitch + self.m3_width
debug.check(clk_pin.layer=="m2","DFF clk pin not on metal2") debug.check(clk_pin.layer == "m2", "DFF clk pin not on metal2")
self.add_layout_pin_segment_center(text="clk", self.add_layout_pin_segment_center(text="clk",
layer="m3", layer="m3",
start=vector(0,clk_ypos), start=vector(0, clk_ypos),
end=vector(self.width,clk_ypos)) end=vector(self.width, clk_ypos))
for col in range(self.columns): for col in range(self.columns):
clk_pin = self.dff_insts[0,col].get_pin(self.dff.clk_pin) clk_pin = self.dff_insts[0, col].get_pin(self.dff.clk_pin)
# Make a vertical strip for each column # Make a vertical strip for each column
self.add_rect(layer="m2", self.add_rect(layer="m2",
offset=clk_pin.ll().scale(1,0), offset=clk_pin.ll().scale(1, 0),
width=self.m2_width, width=self.m2_width,
height=self.height) height=self.height)
# Drop a via to the M3 pin # Drop a via to the M3 pin
self.add_via_center(layers=self.m2_stack, self.add_via_stack_center(from_layer=clk_pin.layer,
offset=vector(clk_pin.cx(),clk_ypos)) to_layer="m3",
offset=vector(clk_pin.cx(), clk_ypos))
def get_clk_cin(self): def get_clk_cin(self):
"""Return the total capacitance (in relative units) that the clock is loaded by in the dff array""" """Return the total capacitance (in relative units) that the clock is loaded by in the dff array"""

View File

@ -167,11 +167,11 @@ class dff_buf_array(design.design):
for col in range(self.columns): for col in range(self.columns):
# Continous vdd rail along with label. # Continous vdd rail along with label.
vdd_pin=self.dff_insts[row, col].get_pin("vdd") vdd_pin=self.dff_insts[row, col].get_pin("vdd")
self.add_power_pin("vdd", vdd_pin.lc()) self.add_power_pin("vdd", vdd_pin.lc(), start_layer=vdd_pin.layer)
# Continous gnd rail along with label. # Continous gnd rail along with label.
gnd_pin=self.dff_insts[row, col].get_pin("gnd") gnd_pin=self.dff_insts[row, col].get_pin("gnd")
self.add_power_pin("gnd", gnd_pin.lc()) self.add_power_pin("gnd", gnd_pin.lc(), start_layer=gnd_pin.layer)
def add_layout_pins(self): def add_layout_pins(self):

View File

@ -20,12 +20,16 @@ class hierarchical_predecode(design.design):
def __init__(self, name, input_number, height=None): def __init__(self, name, input_number, height=None):
self.number_of_inputs = input_number self.number_of_inputs = input_number
b = factory.create(module_type="bitcell")
if not height: if not height:
b = factory.create(module_type="bitcell")
self.cell_height = b.height self.cell_height = b.height
self.column_decoder = False
else: else:
self.cell_height = height self.cell_height = height
# If we are pitch matched to the bitcell, it's a predecoder
# otherwise it's a column decoder (out of pgates)
self.column_decoder = (height != b.height)
self.number_of_outputs = int(math.pow(2, self.number_of_inputs)) self.number_of_outputs = int(math.pow(2, self.number_of_inputs))
design.design.__init__(self, name) design.design.__init__(self, name)
@ -40,21 +44,21 @@ class hierarchical_predecode(design.design):
def add_modules(self): def add_modules(self):
""" Add the INV and AND gate modules """ """ Add the INV and AND gate modules """
if self.number_of_inputs == 2: debug.check(self.number_of_inputs < 4,
self.and_mod = factory.create(module_type="and2_dec", "Invalid number of predecode inputs: {}".format(self.number_of_inputs))
height=self.cell_height)
elif self.number_of_inputs == 3: if self.column_decoder:
self.and_mod = factory.create(module_type="and3_dec", and_type = "pand{}".format(self.number_of_inputs)
height=self.cell_height) inv_type = "pinv"
elif self.number_of_inputs == 4:
self.and_mod = factory.create(module_type="and4_dec",
height=self.cell_height)
else: else:
debug.error("Invalid number of predecode inputs: {}".format(self.number_of_inputs), -1) and_type = "and{}_dec".format(self.number_of_inputs)
inv_type = "inv_dec"
self.and_mod = factory.create(module_type=and_type,
height=self.cell_height)
self.add_mod(self.and_mod) self.add_mod(self.and_mod)
# This uses the pinv_dec parameterized cell # This uses the pinv_dec parameterized cell
self.inv = factory.create(module_type="inv_dec", self.inv = factory.create(module_type=inv_type,
height=self.cell_height, height=self.cell_height,
size=1) size=1)
self.add_mod(self.inv) self.add_mod(self.inv)
@ -80,7 +84,7 @@ class hierarchical_predecode(design.design):
# Outputs from cells are on output layer # Outputs from cells are on output layer
if OPTS.tech_name == "sky130": if OPTS.tech_name == "sky130":
self.bus_layer = "m1" self.bus_layer = "m1"
self.bus_directions = None self.bus_directions = "nonpref"
self.bus_pitch = self.m1_pitch self.bus_pitch = self.m1_pitch
self.bus_space = 1.5 * self.m1_space self.bus_space = 1.5 * self.m1_space
self.input_layer = "m2" self.input_layer = "m2"
@ -88,7 +92,7 @@ class hierarchical_predecode(design.design):
self.output_layer_pitch = self.li_pitch self.output_layer_pitch = self.li_pitch
else: else:
self.bus_layer = "m2" self.bus_layer = "m2"
self.bus_directions = None self.bus_directions = "pref"
self.bus_pitch = self.m2_pitch self.bus_pitch = self.m2_pitch
self.bus_space = self.m2_space self.bus_space = self.m2_space
# This requires a special jog to ensure to conflicts with the output layers # This requires a special jog to ensure to conflicts with the output layers
@ -238,10 +242,7 @@ class hierarchical_predecode(design.design):
# 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 # since this is where the p/n devices are and there are no
# pins in the and gates. # pins in the and gates.
if OPTS.tech_name == "sky130": inv_out_pos = inv_out_pin.rc()
inv_out_pos = inv_out_pin.lr()
else:
inv_out_pos = inv_out_pin.rc()
y_offset = (inv_num + 1) * self.inv.height - self.output_layer_pitch y_offset = (inv_num + 1) * self.inv.height - self.output_layer_pitch
right_pos = inv_out_pos + vector(self.inv.width - self.inv.get_pin("Z").rx(), 0) right_pos = inv_out_pos + vector(self.inv.width - self.inv.get_pin("Z").rx(), 0)
rail_pos = vector(self.decode_rails[out_pin].cx(), y_offset) rail_pos = vector(self.decode_rails[out_pin].cx(), y_offset)
@ -309,7 +310,7 @@ class hierarchical_predecode(design.design):
""" Add a pin for each row of vdd/gnd which are must-connects next level up. """ """ Add a pin for each row of vdd/gnd which are must-connects next level up. """
# In sky130, we use hand-made decoder cells with vertical power # In sky130, we use hand-made decoder cells with vertical power
if OPTS.tech_name == "sky130": if OPTS.tech_name == "sky130" and not self.column_decoder:
for n in ["vdd", "gnd"]: for n in ["vdd", "gnd"]:
# This makes a wire from top to bottom for both inv and and gates # This makes a wire from top to bottom for both inv and and gates
for i in [self.inv_inst, self.and_inst]: for i in [self.inv_inst, self.and_inst]:

View File

@ -377,11 +377,11 @@ class port_data(design.design):
temp.append("{0}_{1}".format(br_name, bit)) temp.append("{0}_{1}".format(br_name, bit))
else: else:
temp.append("{0}_out_{1}".format(bl_name, bit)) temp.append("{0}_out_{1}".format(bl_name, bit))
temp.append("{0}_out_{1}".format(br_name, bit)) temp.append("{0}_out_{1}".format(br_name, bit))
for bit in range(self.num_spare_cols): for bit in range(self.num_spare_cols):
temp.append("spare{0}_{1}".format(bl_name, bit)) temp.append("spare{0}_{1}".format(bl_name, bit))
temp.append("spare{0}_{1}".format(br_name, bit)) temp.append("spare{0}_{1}".format(br_name, bit))
if self.write_size is not None: if self.write_size is not None:
for i in range(self.num_wmasks): for i in range(self.num_wmasks):
@ -522,13 +522,14 @@ class port_data(design.design):
wdriver_pos = wdriver_en_pin.rc() - vector(self.m2_pitch, 0) wdriver_pos = wdriver_en_pin.rc() - vector(self.m2_pitch, 0)
mid_pos = vector(wdriver_pos.x, wmask_pos.y) mid_pos = vector(wdriver_pos.x, wmask_pos.y)
# Add driver on mask output # Add driver on mask output
self.add_via_center(layers=self.m1_stack, self.add_via_stack_center(from_layer=wmask_out_pin.layer,
offset=wmask_pos) to_layer="m1",
offset=wmask_pos)
# Add via for the write driver array's enable input # Add via for the write driver array's enable input
self.add_via_center(layers=self.m1_stack, self.add_via_stack_center(from_layer=wdriver_en_pin.layer,
offset=wdriver_pos) to_layer="m2",
offset=wdriver_pos)
# Route between write mask AND array and write driver array # Route between write mask AND array and write driver array
self.add_wire(self.m1_stack, [wmask_pos, mid_pos, wdriver_pos]) self.add_wire(self.m1_stack, [wmask_pos, mid_pos, wdriver_pos])

View File

@ -30,6 +30,11 @@ class precharge_array(design.design):
self.bitcell_br = bitcell_br self.bitcell_br = bitcell_br
self.column_offset = column_offset self.column_offset = column_offset
if OPTS.tech_name == "sky130":
self.en_bar_layer = "m3"
else:
self.en_bar_layer = "m1"
self.create_netlist() self.create_netlist()
if not OPTS.netlist_only: if not OPTS.netlist_only:
self.create_layout() self.create_layout()
@ -74,14 +79,18 @@ class precharge_array(design.design):
def add_layout_pins(self): def add_layout_pins(self):
en_bar_pin = self.pc_cell.get_pin("en_bar") en_pin = self.pc_cell.get_pin("en_bar")
self.add_layout_pin(text="en_bar", start_offset = en_pin.lc().scale(0, 1)
layer=en_bar_pin.layer, end_offset = start_offset + vector(self.width, 0)
offset=en_bar_pin.ll(), self.add_layout_pin_segment_center(text="en_bar",
width=self.width, layer=self.en_bar_layer,
height=en_bar_pin.height()) start=start_offset,
end=end_offset)
for inst in self.local_insts: for inst in self.local_insts:
self.add_via_stack_center(from_layer=en_pin.layer,
to_layer=self.en_bar_layer,
offset=inst.get_pin("en_bar").center())
self.copy_layout_pin(inst, "vdd") self.copy_layout_pin(inst, "vdd")
for i in range(len(self.local_insts)): for i in range(len(self.local_insts)):

View File

@ -378,21 +378,22 @@ class replica_bitcell_array(design.design):
width=pin.width(), width=pin.width(),
height=self.height) height=self.height)
# For specific technologies, there is no vdd via within the bitcell. Instead vdd is connect via end caps. # vdd/gnd are only connected in the perimeter cells
try: # replica column should only have a vdd/gnd in the dummy cell on top/bottom
bitcell_no_vdd_pin = cell_properties.bitcell.no_vdd_via supply_insts = [self.dummy_col_left_inst, self.dummy_col_right_inst,
except AttributeError: self.dummy_row_top_inst, self.dummy_row_bot_inst]
bitcell_no_vdd_pin = False
for pin_name in ["vdd", "gnd"]: for pin_name in ["vdd", "gnd"]:
for inst in self.insts: for inst in supply_insts:
pin_list = inst.get_pins(pin_name) pin_list = inst.get_pins(pin_name)
for pin in pin_list: for pin in pin_list:
if not (pin_name == "vdd" and bitcell_no_vdd_pin): self.add_power_pin(name=pin_name,
self.add_power_pin(name=pin_name, loc=pin.center(),
loc=pin.center(), directions=("V", "V"),
directions=("V", "V"), start_layer=pin.layer)
start_layer=pin.layer)
for inst in list(self.replica_col_inst.values()):
self.copy_layout_pin(inst, pin_name)
self.copy_layout_pin(inst, pin_name)
def get_rbl_wl_name(self, port): def get_rbl_wl_name(self, port):
""" Return the WL for the given RBL port """ """ Return the WL for the given RBL port """

View File

@ -183,11 +183,13 @@ class replica_column(design.design):
width=self.width, width=self.width,
height=wl_pin.height()) height=wl_pin.height())
# For every second row and column, add a via for gnd and vdd # Supplies are only connected in the ends
for row in range(row_range_min, row_range_max): for (index, inst) in self.cell_inst.items():
inst = self.cell_inst[row]
for pin_name in ["vdd", "gnd"]: for pin_name in ["vdd", "gnd"]:
self.copy_layout_pin(inst, pin_name) if inst in [self.cell_inst[0], self.cell_inst[self.total_size - 1]]:
self.copy_power_pins(inst, pin_name)
else:
self.copy_layout_pin(inst, pin_name)
def get_bitcell_pins(self, col, row): def get_bitcell_pins(self, col, row):
""" Creates a list of connections in the bitcell, """ Creates a list of connections in the bitcell,

View File

@ -8,11 +8,12 @@
import design import design
import debug import debug
import utils import utils
from tech import GDS,layer, parameter,drc from tech import GDS, layer, parameter, drc
from tech import cell_properties as props from tech import cell_properties as props
from globals import OPTS from globals import OPTS
import logical_effort import logical_effort
class sense_amp(design.design): class sense_amp(design.design):
""" """
This module implements the single sense amp cell used in the design. It This module implements the single sense amp cell used in the design. It
@ -28,10 +29,10 @@ class sense_amp(design.design):
props.sense_amp.pin.gnd] props.sense_amp.pin.gnd]
type_list = ["INPUT", "INPUT", "OUTPUT", "INPUT", "POWER", "GROUND"] type_list = ["INPUT", "INPUT", "OUTPUT", "INPUT", "POWER", "GROUND"]
if not OPTS.netlist_only: if not OPTS.netlist_only:
(width,height) = utils.get_libcell_size("sense_amp", GDS["unit"], layer["boundary"]) (width, height) = utils.get_libcell_size("sense_amp", GDS["unit"], layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "sense_amp", GDS["unit"]) pin_map = utils.get_libcell_pins(pin_names, "sense_amp", GDS["unit"])
else: else:
(width, height) = (0,0) (width, height) = (0, 0)
pin_map = [] pin_map = []
def get_bl_names(self): def get_bl_names(self):
@ -61,41 +62,41 @@ class sense_amp(design.design):
# FIXME: This input load will be applied to both the s_en timing and bitline timing. # FIXME: This input load will be applied to both the s_en timing and bitline timing.
#Input load for the bitlines which are connected to the source/drain of a TX. Not the selects. # Input load for the bitlines which are connected to the source/drain of a TX. Not the selects.
from tech import spice, parameter from tech import spice
# Default is 8x. Per Samira and Hodges-Jackson book: # Default is 8x. Per Samira and Hodges-Jackson book:
# "Column-mux transistors driven by the decoder must be sized for optimal speed" # "Column-mux transistors driven by the decoder must be sized for optimal speed"
bitline_pmos_size = 8 #FIXME: This should be set somewhere and referenced. Probably in tech file. bitline_pmos_size = 8 # FIXME: This should be set somewhere and referenced. Probably in tech file.
return spice["min_tx_drain_c"]*(bitline_pmos_size)#ff return spice["min_tx_drain_c"] * bitline_pmos_size # ff
def get_stage_effort(self, load): def get_stage_effort(self, load):
#Delay of the sense amp will depend on the size of the amp and the output load. # Delay of the sense amp will depend on the size of the amp and the output load.
parasitic_delay = 1 parasitic_delay = 1
cin = (parameter["sa_inv_pmos_size"] + parameter["sa_inv_nmos_size"])/drc("minwidth_tx") cin = (parameter["sa_inv_pmos_size"] + parameter["sa_inv_nmos_size"]) / drc("minwidth_tx")
sa_size = parameter["sa_inv_nmos_size"]/drc("minwidth_tx") sa_size = parameter["sa_inv_nmos_size"] / drc("minwidth_tx")
cc_inv_cin = cin cc_inv_cin = cin
return logical_effort.logical_effort('column_mux', sa_size, cin, load+cc_inv_cin, parasitic_delay, False) return logical_effort.logical_effort('column_mux', sa_size, cin, load + cc_inv_cin, parasitic_delay, False)
def analytical_power(self, corner, load): def analytical_power(self, corner, load):
"""Returns dynamic and leakage power. Results in nW""" """Returns dynamic and leakage power. Results in nW"""
#Power in this module currently not defined. Returns 0 nW (leakage and dynamic). # Power in this module currently not defined. Returns 0 nW (leakage and dynamic).
total_power = self.return_power() total_power = self.return_power()
return total_power return total_power
def get_en_cin(self): def get_en_cin(self):
"""Get the relative capacitance of sense amp enable gate cin""" """Get the relative capacitance of sense amp enable gate cin"""
pmos_cin = parameter["sa_en_pmos_size"]/drc("minwidth_tx") pmos_cin = parameter["sa_en_pmos_size"] / drc("minwidth_tx")
nmos_cin = parameter["sa_en_nmos_size"]/drc("minwidth_tx") nmos_cin = parameter["sa_en_nmos_size"] / drc("minwidth_tx")
#sen is connected to 2 pmos isolation TX and 1 nmos per sense amp. # sen is connected to 2 pmos isolation TX and 1 nmos per sense amp.
return 2*pmos_cin + nmos_cin return 2 * pmos_cin + nmos_cin
def get_enable_name(self): def get_enable_name(self):
"""Returns name used for enable net""" """Returns name used for enable net"""
#FIXME: A better programmatic solution to designate pins # FIXME: A better programmatic solution to designate pins
enable_name = self.en_name enable_name = self.en_name
debug.check(enable_name in self.pin_names, "Enable name {} not found in pin list".format(enable_name)) debug.check(enable_name in self.pin_names, "Enable name {} not found in pin list".format(enable_name))
return enable_name return enable_name
def build_graph(self, graph, inst_name, port_nets): def build_graph(self, graph, inst_name, port_nets):
"""Adds edges based on inputs/outputs. Overrides base class function.""" """Adds edges based on inputs/outputs. Overrides base class function."""
self.add_graph_edges(graph, port_nets) self.add_graph_edges(graph, port_nets)

View File

@ -28,7 +28,7 @@ class sense_amp_array(design.design):
self.add_comment("words_per_row: {0}".format(words_per_row)) self.add_comment("words_per_row: {0}".format(words_per_row))
self.word_size = word_size self.word_size = word_size
self.words_per_row = words_per_row self.words_per_row = words_per_row
if not num_spare_cols: if not num_spare_cols:
self.num_spare_cols = 0 self.num_spare_cols = 0
else: else:
@ -37,6 +37,11 @@ class sense_amp_array(design.design):
self.column_offset = column_offset self.column_offset = column_offset
self.row_size = self.word_size * self.words_per_row self.row_size = self.word_size * self.words_per_row
if OPTS.tech_name == "sky130":
self.en_layer = "m3"
else:
self.en_layer = "m1"
self.create_netlist() self.create_netlist()
if not OPTS.netlist_only: if not OPTS.netlist_only:
self.create_layout() self.create_layout()
@ -77,7 +82,7 @@ class sense_amp_array(design.design):
self.DRC_LVS() self.DRC_LVS()
def add_pins(self): def add_pins(self):
for i in range(0,self.word_size + self.num_spare_cols): for i in range(0, self.word_size + self.num_spare_cols):
self.add_pin(self.data_name + "_{0}".format(i), "OUTPUT") self.add_pin(self.data_name + "_{0}".format(i), "OUTPUT")
self.add_pin(self.get_bl_name() + "_{0}".format(i), "INPUT") self.add_pin(self.get_bl_name() + "_{0}".format(i), "INPUT")
self.add_pin(self.get_br_name() + "_{0}".format(i), "INPUT") self.add_pin(self.get_br_name() + "_{0}".format(i), "INPUT")
@ -96,7 +101,7 @@ class sense_amp_array(design.design):
def create_sense_amp_array(self): def create_sense_amp_array(self):
self.local_insts = [] self.local_insts = []
for i in range(0,self.word_size + self.num_spare_cols): for i in range(0, self.word_size + self.num_spare_cols):
name = "sa_d{0}".format(i) name = "sa_d{0}".format(i)
self.local_insts.append(self.add_inst(name=name, self.local_insts.append(self.add_inst(name=name,
mod=self.amp)) mod=self.amp))
@ -107,14 +112,10 @@ class sense_amp_array(design.design):
def place_sense_amp_array(self): def place_sense_amp_array(self):
from tech import cell_properties from tech import cell_properties
if self.bitcell.width > self.amp.width:
amp_spacing = self.bitcell.width
else:
amp_spacing = self.amp.width
for i in range(0, self.row_size, self.words_per_row): for i in range(0, self.row_size, self.words_per_row):
index = int(i / self.words_per_row) index = int(i / self.words_per_row)
xoffset = i * amp_spacing xoffset = i * self.bitcell.width
if cell_properties.bitcell.mirror.y and (i + self.column_offset) % 2: if cell_properties.bitcell.mirror.y and (i + self.column_offset) % 2:
mirror = "MY" mirror = "MY"
@ -126,9 +127,9 @@ class sense_amp_array(design.design):
self.local_insts[index].place(offset=amp_position, mirror=mirror) self.local_insts[index].place(offset=amp_position, mirror=mirror)
# place spare sense amps (will share the same enable as regular sense amps) # place spare sense amps (will share the same enable as regular sense amps)
for i in range(0,self.num_spare_cols): for i in range(0, self.num_spare_cols):
index = self.word_size + i index = self.word_size + i
xoffset = ((self.word_size * self.words_per_row) + i) * amp_spacing xoffset = ((self.word_size * self.words_per_row) + i) * self.bitcell.width
if cell_properties.bitcell.mirror.y and (i + self.column_offset) % 2: if cell_properties.bitcell.mirror.y and (i + self.column_offset) % 2:
mirror = "MY" mirror = "MY"
@ -143,17 +144,17 @@ class sense_amp_array(design.design):
for i in range(len(self.local_insts)): for i in range(len(self.local_insts)):
inst = self.local_insts[i] inst = self.local_insts[i]
gnd_pin = inst.get_pin("gnd") for gnd_pin in inst.get_pins("gnd"):
self.add_power_pin(name="gnd", self.add_power_pin(name="gnd",
loc=gnd_pin.center(), loc=gnd_pin.center(),
start_layer=gnd_pin.layer, start_layer=gnd_pin.layer,
directions=("V", "V")) directions=("V", "V"))
vdd_pin = inst.get_pin("vdd") for vdd_pin in inst.get_pins("vdd"):
self.add_power_pin(name="vdd", self.add_power_pin(name="vdd",
loc=vdd_pin.center(), loc=vdd_pin.center(),
start_layer=vdd_pin.layer, start_layer=vdd_pin.layer,
directions=("V", "V")) directions=("V", "V"))
bl_pin = inst.get_pin(inst.mod.get_bl_names()) bl_pin = inst.get_pin(inst.mod.get_bl_names())
br_pin = inst.get_pin(inst.mod.get_br_names()) br_pin = inst.get_pin(inst.mod.get_br_names())
@ -177,14 +178,18 @@ class sense_amp_array(design.design):
height=dout_pin.height()) height=dout_pin.height())
def route_rails(self): def route_rails(self):
# add sclk rail across entire array # Add enable across the array
sclk = self.amp.get_pin(self.amp.en_name) en_pin = self.amp.get_pin(self.amp.en_name)
sclk_offset = self.amp.get_pin(self.amp.en_name).ll().scale(0, 1) start_offset = en_pin.lc().scale(0, 1)
self.add_layout_pin(text=self.en_name, end_offset = start_offset + vector(self.width, 0)
layer=sclk.layer, self.add_layout_pin_segment_center(text=self.en_name,
offset=sclk_offset, layer=self.en_layer,
width=self.width, start=start_offset,
height=drc("minwidth_" + sclk.layer)) end=end_offset)
for inst in self.local_insts:
self.add_via_stack_center(from_layer=en_pin.layer,
to_layer=self.en_layer,
offset=inst.get_pin(self.amp.en_name).center())
def input_load(self): def input_load(self):
return self.amp.input_load() return self.amp.input_load()

View File

@ -32,14 +32,16 @@ class single_level_column_mux_array(design.design):
self.bitcell_br = bitcell_br self.bitcell_br = bitcell_br
self.column_offset = column_offset self.column_offset = column_offset
if "li" in layer: if OPTS.tech_name == "sky130":
self.col_mux_stack = self.li_stack self.sel_layer = "m3"
self.col_mux_stack_pitch = self.m1_pitch self.sel_pitch = self.m3_pitch
self.bitline_layer = "m1"
else: else:
self.col_mux_stack = self.m1_stack self.sel_layer = "m1"
self.col_mux_stack_pitch = self.m1_pitch self.sel_pitch = self.m2_pitch
self.bitline_layer = "m2"
if preferred_directions[self.col_mux_stack[0]] == "V": if preferred_directions[self.sel_layer] == "V":
self.via_directions = ("H", "H") self.via_directions = ("H", "H")
else: else:
self.via_directions = "pref" self.via_directions = "pref"
@ -96,7 +98,7 @@ class single_level_column_mux_array(design.design):
self.width = self.columns * self.mux.width self.width = self.columns * self.mux.width
# one set of metal1 routes for select signals and a pair to interconnect the mux outputs bl/br # one set of metal1 routes for select signals and a pair to interconnect the mux outputs bl/br
# one extra route pitch is to space from the sense amp # one extra route pitch is to space from the sense amp
self.route_height = (self.words_per_row + 3) * self.col_mux_stack_pitch self.route_height = (self.words_per_row + 3) * self.sel_pitch
def create_array(self): def create_array(self):
self.mux_inst = [] self.mux_inst = []
@ -155,11 +157,11 @@ class single_level_column_mux_array(design.design):
self.route_bitlines() self.route_bitlines()
def add_horizontal_input_rail(self): def add_horizontal_input_rail(self):
""" Create address input rails on M1 below the mux transistors """ """ Create address input rails below the mux transistors """
for j in range(self.words_per_row): for j in range(self.words_per_row):
offset = vector(0, self.route_height + (j - self.words_per_row) * self.col_mux_stack_pitch) offset = vector(0, self.route_height + (j - self.words_per_row) * self.sel_pitch)
self.add_layout_pin(text="sel_{}".format(j), self.add_layout_pin(text="sel_{}".format(j),
layer=self.col_mux_stack[0], layer=self.sel_layer,
offset=offset, offset=offset,
width=self.mux.width * self.columns) width=self.mux.width * self.columns)
@ -177,10 +179,10 @@ class single_level_column_mux_array(design.design):
# use the y offset from the sel pin and the x offset from the gate # use the y offset from the sel pin and the x offset from the gate
offset = vector(gate_offset.x, offset = vector(gate_offset.x,
self.get_pin("sel_{}".format(sel_index)).cy()) self.get_pin("sel_{}".format(sel_index)).cy())
# Add the poly contact with a shift to account for the rotation self.add_via_stack_center(from_layer="poly",
self.add_via_center(layers=self.poly_stack, to_layer=self.sel_layer,
offset=offset, offset=offset,
directions=self.via_directions) directions=self.via_directions)
self.add_path("poly", [offset, gate_offset]) self.add_path("poly", [offset, gate_offset])
def route_bitlines(self): def route_bitlines(self):
@ -190,42 +192,44 @@ class single_level_column_mux_array(design.design):
bl_offset_begin = self.mux_inst[j].get_pin("bl_out").bc() bl_offset_begin = self.mux_inst[j].get_pin("bl_out").bc()
br_offset_begin = self.mux_inst[j].get_pin("br_out").bc() br_offset_begin = self.mux_inst[j].get_pin("br_out").bc()
bl_out_offset_begin = bl_offset_begin - vector(0, (self.words_per_row + 1) * self.col_mux_stack_pitch) bl_out_offset_begin = bl_offset_begin - vector(0, (self.words_per_row + 1) * self.sel_pitch)
br_out_offset_begin = br_offset_begin - vector(0, (self.words_per_row + 2) * self.col_mux_stack_pitch) br_out_offset_begin = br_offset_begin - vector(0, (self.words_per_row + 2) * self.sel_pitch)
# Add the horizontal wires for the first bit # Add the horizontal wires for the first bit
if j % self.words_per_row == 0: if j % self.words_per_row == 0:
bl_offset_end = self.mux_inst[j + self.words_per_row - 1].get_pin("bl_out").bc() bl_offset_end = self.mux_inst[j + self.words_per_row - 1].get_pin("bl_out").bc()
br_offset_end = self.mux_inst[j + self.words_per_row - 1].get_pin("br_out").bc() br_offset_end = self.mux_inst[j + self.words_per_row - 1].get_pin("br_out").bc()
bl_out_offset_end = bl_offset_end - vector(0, (self.words_per_row + 1) * self.col_mux_stack_pitch) bl_out_offset_end = bl_offset_end - vector(0, (self.words_per_row + 1) * self.sel_pitch)
br_out_offset_end = br_offset_end - vector(0, (self.words_per_row + 2) * self.col_mux_stack_pitch) br_out_offset_end = br_offset_end - vector(0, (self.words_per_row + 2) * self.sel_pitch)
self.add_path(self.col_mux_stack[0], [bl_out_offset_begin, bl_out_offset_end]) self.add_path(self.sel_layer, [bl_out_offset_begin, bl_out_offset_end])
self.add_path(self.col_mux_stack[0], [br_out_offset_begin, br_out_offset_end]) self.add_path(self.sel_layer, [br_out_offset_begin, br_out_offset_end])
# Extend the bitline output rails and gnd downward on the first bit of each n-way mux # Extend the bitline output rails and gnd downward on the first bit of each n-way mux
self.add_layout_pin_segment_center(text="bl_out_{}".format(int(j / self.words_per_row)), self.add_layout_pin_segment_center(text="bl_out_{}".format(int(j / self.words_per_row)),
layer=self.col_mux_stack[2], layer=self.bitline_layer,
start=bl_offset_begin, start=bl_offset_begin,
end=bl_out_offset_begin) end=bl_out_offset_begin)
self.add_layout_pin_segment_center(text="br_out_{}".format(int(j / self.words_per_row)), self.add_layout_pin_segment_center(text="br_out_{}".format(int(j / self.words_per_row)),
layer=self.col_mux_stack[2], layer=self.bitline_layer,
start=br_offset_begin, start=br_offset_begin,
end=br_out_offset_begin) end=br_out_offset_begin)
else: else:
self.add_path(self.col_mux_stack[2], [bl_out_offset_begin, bl_offset_begin]) self.add_path(self.bitline_layer, [bl_out_offset_begin, bl_offset_begin])
self.add_path(self.col_mux_stack[2], [br_out_offset_begin, br_offset_begin]) self.add_path(self.bitline_layer, [br_out_offset_begin, br_offset_begin])
# This via is on the right of the wire # This via is on the right of the wire
self.add_via_center(layers=self.col_mux_stack, self.add_via_stack_center(from_layer=self.bitline_layer,
offset=bl_out_offset_begin, to_layer=self.sel_layer,
directions=self.via_directions) offset=bl_out_offset_begin,
directions=self.via_directions)
# This via is on the left of the wire # This via is on the left of the wire
self.add_via_center(layers=self.col_mux_stack, self.add_via_stack_center(from_layer=self.bitline_layer,
offset=br_out_offset_begin, to_layer=self.sel_layer,
directions=self.via_directions) offset=br_out_offset_begin,
directions=self.via_directions)
def get_drain_cin(self): def get_drain_cin(self):
"""Get the relative capacitance of the drain of the NMOS pass TX""" """Get the relative capacitance of the drain of the NMOS pass TX"""

View File

@ -154,7 +154,6 @@ class write_driver_array(design.design):
self.get_br_name() + "_{0}".format(index), self.get_br_name() + "_{0}".format(index),
self.en_name + "_{0}".format(i + offset), "vdd", "gnd"]) self.en_name + "_{0}".format(i + offset), "vdd", "gnd"])
def place_write_array(self): def place_write_array(self):
from tech import cell_properties from tech import cell_properties
if self.bitcell.width > self.driver.width: if self.bitcell.width > self.driver.width:

View File

@ -108,8 +108,21 @@ class write_mask_and_array(design.design):
end=vector(self.width, en_pin.cy())) end=vector(self.width, en_pin.cy()))
for i in range(self.num_wmasks): for i in range(self.num_wmasks):
# Route the A pin over to the left so that it doesn't conflict with the sense
# amp output which is usually in the center
a_pin = self.and2_insts[i].get_pin("A")
a_pos = a_pin.center()
in_pos = vector(self.and2_insts[i].lx(),
a_pos.y)
self.add_via_stack_center(from_layer=a_pin.layer,
to_layer="m2",
offset=in_pos)
self.add_layout_pin_rect_center(text="wmask_in_{0}".format(i),
layer="m2",
offset=in_pos)
self.add_path(a_pin.layer, [in_pos, a_pos])
# Copy remaining layout pins # 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], "Z", "wmask_out_{0}".format(i))
# Add via connections to metal3 for AND array's B pin # Add via connections to metal3 for AND array's B pin
@ -121,10 +134,7 @@ class write_mask_and_array(design.design):
for supply in ["gnd", "vdd"]: for supply in ["gnd", "vdd"]:
supply_pin=self.and2_insts[i].get_pin(supply) supply_pin=self.and2_insts[i].get_pin(supply)
if "li" in layer: self.add_power_pin(supply, supply_pin.center(), start_layer=supply_pin.layer)
self.add_power_pin(supply, supply_pin.center(), start_layer="li", directions = ("H", "H"))
else:
self.add_power_pin(supply, supply_pin.center())
for supply in ["gnd", "vdd"]: for supply in ["gnd", "vdd"]:
supply_pin_left = self.and2_insts[0].get_pin(supply) supply_pin_left = self.and2_insts[0].get_pin(supply)

View File

@ -58,7 +58,7 @@ class options(optparse.Values):
delay_chain_stages = 9 delay_chain_stages = 9
delay_chain_fanout_per_stage = 4 delay_chain_fanout_per_stage = 4
accuracy_requirement = 0.75
################### ###################
# Debug options. # Debug options.

View File

@ -15,7 +15,7 @@ from vector import vector
from globals import OPTS from globals import OPTS
if(OPTS.tech_name == "sky130"): if(OPTS.tech_name == "sky130"):
from tech import nmos_bins, pmos_bins, accuracy_requirement from tech import nmos_bins, pmos_bins
class pgate(design.design): class pgate(design.design):
@ -43,6 +43,9 @@ class pgate(design.design):
self.route_layer_space = getattr(self, "{}_space".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)) self.route_layer_pitch = getattr(self, "{}_pitch".format(self.route_layer))
# hack for enclosing input pin with npc
self.input_pin_vias = []
# This is the space from a S/D contact to the supply rail # This is the space from a S/D contact to the supply rail
contact_to_vdd_rail_space = 0.5 * self.route_layer_width + self.route_layer_space contact_to_vdd_rail_space = 0.5 * self.route_layer_width + self.route_layer_space
# This is a poly-to-poly of a flipped cell # This is a poly-to-poly of a flipped cell
@ -146,20 +149,23 @@ class pgate(design.design):
height=contact.poly_contact.first_layer_width, height=contact.poly_contact.first_layer_width,
width=left_gate_offset.x - contact_offset.x) width=left_gate_offset.x - contact_offset.x)
return via
def extend_wells(self): def extend_wells(self):
""" Extend the n/p wells to cover whole cell """ """ Extend the n/p wells to cover whole cell """
# This should match the cells in the cell library # This should match the cells in the cell library
self.nwell_y_offset = 0.48 * self.height self.nwell_yoffset = 0.48 * self.height
full_height = self.height + 0.5 * self.m1_width full_height = self.height + 0.5 * self.m1_width
# FIXME: float rounding problem # FIXME: float rounding problem
if "nwell" in layer: if "nwell" in layer:
# Add a rail width to extend the well to the top of the rail # Add a rail width to extend the well to the top of the rail
nwell_max_offset = max(self.find_highest_layer_coords("nwell").y, nwell_max_offset = max(self.find_highest_layer_coords("nwell").y,
full_height) full_height)
nwell_position = vector(0, self.nwell_y_offset) - vector(self.well_extend_active, 0) nwell_position = vector(0, self.nwell_yoffset) - vector(self.well_extend_active, 0)
nwell_height = nwell_max_offset - self.nwell_y_offset nwell_height = nwell_max_offset - self.nwell_yoffset
self.add_rect(layer="nwell", self.add_rect(layer="nwell",
offset=nwell_position, offset=nwell_position,
width=self.width + 2 * self.well_extend_active, width=self.width + 2 * self.well_extend_active,
@ -175,7 +181,7 @@ class pgate(design.design):
pwell_min_offset = min(self.find_lowest_layer_coords("pwell").y, pwell_min_offset = min(self.find_lowest_layer_coords("pwell").y,
-0.5 * self.m1_width) -0.5 * self.m1_width)
pwell_position = vector(-self.well_extend_active, pwell_min_offset) pwell_position = vector(-self.well_extend_active, pwell_min_offset)
pwell_height = self.nwell_y_offset - pwell_position.y pwell_height = self.nwell_yoffset - pwell_position.y
self.add_rect(layer="pwell", self.add_rect(layer="pwell",
offset=pwell_position, offset=pwell_position,
width=self.width + 2 * self.well_extend_active, width=self.width + 2 * self.well_extend_active,
@ -186,6 +192,9 @@ class pgate(design.design):
width=self.width + 2 * self.well_extend_active, width=self.width + 2 * self.well_extend_active,
height=pwell_height) height=pwell_height)
if OPTS.tech_name == "sky130":
self.extend_implants()
def add_nwell_contact(self, pmos, pmos_pos): def add_nwell_contact(self, pmos, pmos_pos):
""" Add an nwell contact next to the given pmos device. """ """ Add an nwell contact next to the given pmos device. """
@ -240,6 +249,52 @@ class pgate(design.design):
# Return the top of the well # Return the top of the well
def extend_implants(self):
"""
Add top-to-bottom implants for adjacency issues in s8.
"""
if self.add_wells:
rightx = None
else:
rightx = self.width
nmos_insts = self.get_tx_insts("nmos")
if len(nmos_insts) > 0:
self.add_enclosure(nmos_insts,
layer="nimplant",
extend=self.implant_enclose_active,
leftx=0,
rightx=rightx,
boty=0)
pmos_insts = self.get_tx_insts("pmos")
if len(pmos_insts) > 0:
self.add_enclosure(pmos_insts,
layer="pimplant",
extend=self.implant_enclose_active,
leftx=0,
rightx=rightx,
topy=self.height)
try:
ntap_insts = [self.nwell_contact]
self.add_enclosure(ntap_insts,
layer="nimplant",
extend=self.implant_enclose_active,
rightx=self.width,
topy=self.height)
except AttributeError:
pass
try:
ptap_insts = [self.pwell_contact]
self.add_enclosure(ptap_insts,
layer="pimplant",
extend=self.implant_enclose_active,
rightx=self.width,
boty=0)
except AttributeError:
pass
def add_pwell_contact(self, nmos, nmos_pos): def add_pwell_contact(self, nmos, nmos_pos):
""" Add an pwell contact next to the given nmos device. """ """ Add an pwell contact next to the given nmos device. """
@ -268,7 +323,7 @@ class pgate(design.design):
offset=contact_offset.scale(1, 0.5), offset=contact_offset.scale(1, 0.5),
width=self.pwell_contact.mod.second_layer_width, width=self.pwell_contact.mod.second_layer_width,
height=contact_offset.y) height=contact_offset.y)
# Now add the full active and implant for the NMOS # Now add the full active and implant for the NMOS
# active_offset = nmos_pos + vector(nmos.active_width,0) # active_offset = nmos_pos + vector(nmos.active_width,0)
# This might be needed if the spacing between the actives # This might be needed if the spacing between the actives
@ -345,7 +400,7 @@ class pgate(design.design):
select = -1 select = -1
for i in reversed(range(0, len(scaled_bins))): for i in reversed(range(0, len(scaled_bins))):
if abs(target_width - scaled_bins[i])/target_width <= 1-accuracy_requirement: if abs(target_width - scaled_bins[i])/target_width <= 1-OPTS.accuracy_requirement:
select = i select = i
break break
if select == -1: if select == -1:
@ -379,4 +434,4 @@ class pgate(design.design):
return(scaled_bins) return(scaled_bins)
def bin_accuracy(self, ideal_width, width): def bin_accuracy(self, ideal_width, width):
return abs(1-(ideal_width - width)/ideal_width) return 1-abs((ideal_width - width)/ideal_width)

View File

@ -20,7 +20,7 @@ from sram_factory import factory
from errors import drc_error from errors import drc_error
if(OPTS.tech_name == "sky130"): if(OPTS.tech_name == "sky130"):
from tech import nmos_bins, pmos_bins, accuracy_requirement from tech import nmos_bins, pmos_bins
class pinv(pgate.pgate): class pinv(pgate.pgate):
@ -31,6 +31,9 @@ class pinv(pgate.pgate):
height is usually the same as the 6t library cell and is measured height is usually the same as the 6t library cell and is measured
from center of rail to rail. from center of rail to rail.
""" """
# binning %error tracker
bin_count = 0
bin_error = 0
def __init__(self, name, size=1, beta=parameter["beta"], height=None, add_wells=True): def __init__(self, name, size=1, beta=parameter["beta"], height=None, add_wells=True):
@ -44,7 +47,7 @@ class pinv(pgate.pgate):
self.nmos_size = size self.nmos_size = size
self.pmos_size = beta * size self.pmos_size = beta * size
self.beta = beta self.beta = beta
pgate.pgate.__init__(self, name, height, add_wells) pgate.pgate.__init__(self, name, height, add_wells)
def create_netlist(self): def create_netlist(self):
@ -166,30 +169,37 @@ class pinv(pgate.pgate):
valid_pmos = [] valid_pmos = []
for bin in pmos_bins: for bin in pmos_bins:
if self.bin_accuracy(self.pmos_width, bin[0]) > accuracy_requirement: if abs(self.bin_accuracy(self.pmos_width, bin[0])) > OPTS.accuracy_requirement and abs(self.bin_accuracy(self.pmos_width, bin[0])) <= 1:
valid_pmos.append(bin) valid_pmos.append(bin)
valid_pmos.sort(key = operator.itemgetter(1)) valid_pmos.sort(key = operator.itemgetter(1))
valid_nmos = [] valid_nmos = []
for bin in nmos_bins: for bin in nmos_bins:
if self.bin_accuracy(self.nmos_width, bin[0]) > accuracy_requirement: if abs(self.bin_accuracy(self.nmos_width, bin[0])) > OPTS.accuracy_requirement and abs(self.bin_accuracy(self.nmos_width, bin[0])) <= 1:
valid_nmos.append(bin) valid_nmos.append(bin)
valid_nmos.sort(key = operator.itemgetter(1)) valid_nmos.sort(key = operator.itemgetter(1))
for bin in valid_pmos: for bin in valid_pmos:
if bin[0]/bin[1] < pmos_height_available: if bin[0]/bin[1] < pmos_height_available:
self.pmos_width = bin[0]/bin[1] self.pmos_width = bin[0]/bin[1]
pmos_mults = valid_pmos[0][1] pmos_mults = bin[1]
break break
for bin in valid_nmos: for bin in valid_nmos:
if bin[0]/bin[1] < nmos_height_available: if bin[0]/bin[1] < nmos_height_available:
self.nmos_width = bin[0]/bin[1] self.nmos_width = bin[0]/bin[1]
nmos_mults = valid_pmos[0][1] nmos_mults = bin[1]
break break
self.tx_mults = max(pmos_mults, nmos_mults) self.tx_mults = max(pmos_mults, nmos_mults)
debug.info(2, "prebinning {0} tx, target: {4}, found {1} x {2} = {3}".format("pmos", self.pmos_width, pmos_mults, self.pmos_width * pmos_mults, self.pmos_size * drc("minwidth_tx")))
debug.info(2, "prebinning {0} tx, target: {4}, found {1} x {2} = {3}".format("nmos", self.nmos_width, nmos_mults, self.nmos_width * nmos_mults, self.nmos_size * drc("minwidth_tx")))
pinv.bin_count += 1
pinv.bin_error += abs(((self.pmos_width * pmos_mults) - (self.pmos_size * drc("minwidth_tx")))/(self.pmos_size * drc("minwidth_tx")))
pinv.bin_count += 1
pinv.bin_error += abs(((self.nmos_width * nmos_mults) - (self.nmos_size * drc("minwidth_tx")))/(self.nmos_size * drc("minwidth_tx")))
debug.info(2, "pinv bin count: {0} pinv bin error: {1} percent error {2}".format(pinv.bin_count, pinv.bin_error, pinv.bin_error/pinv.bin_count))
def add_ptx(self): def add_ptx(self):
""" Create the PMOS and NMOS transistors. """ """ Create the PMOS and NMOS transistors. """
self.nmos = factory.create(module_type="ptx", self.nmos = factory.create(module_type="ptx",

View File

@ -14,7 +14,7 @@ from globals import OPTS
from sram_factory import factory from sram_factory import factory
if(OPTS.tech_name == "sky130"): if(OPTS.tech_name == "sky130"):
from tech import nmos_bins, pmos_bins, accuracy_requirement from tech import nmos_bins, pmos_bins
class pinv_dec(pinv.pinv): class pinv_dec(pinv.pinv):

View File

@ -184,33 +184,37 @@ class pnand2(pgate.pgate):
# doesn't use nmos uy because that is calculated using offset + poly height # doesn't use nmos uy because that is calculated using offset + poly height
active_top = self.nmos1_inst.by() + self.nmos1_inst.mod.active_height active_top = self.nmos1_inst.by() + self.nmos1_inst.mod.active_height
active_to_poly_contact = active_top + self.poly_to_active + 0.5 * contact.poly_contact.first_layer_height active_to_poly_contact = active_top + self.poly_to_active + 0.5 * contact.poly_contact.first_layer_height
active_to_poly_contact2 = active_top + drc("contact_to_gate") + 0.5 * self.route_layer_width active_to_poly_contact2 = active_top + self.poly_contact_to_gate + 0.5 * self.route_layer_width
self.inputA_yoffset = max(active_contact_to_poly_contact, self.inputA_yoffset = max(active_contact_to_poly_contact,
active_to_poly_contact, active_to_poly_contact,
active_to_poly_contact2) active_to_poly_contact2)
self.route_input_gate(self.pmos1_inst, apin = self.route_input_gate(self.pmos1_inst,
self.nmos1_inst, self.nmos1_inst,
self.inputA_yoffset, self.inputA_yoffset,
"A", "A",
position="center") position="center")
self.inputB_yoffset = self.inputA_yoffset + 2 * self.m3_pitch self.inputB_yoffset = self.inputA_yoffset + 2 * self.m3_pitch
# # active contact metal to poly contact metal spacing # # active contact metal to poly contact metal spacing
# active_contact_to_poly_contact = self.output_yoffset - self.route_layer_space - 0.5 * contact.poly_contact.second_layer_height # active_contact_to_poly_contact = self.output_yoffset - self.route_layer_space - 0.5 * contact.poly_contact.second_layer_height
# active_bottom = self.pmos1_inst.by() # active_bottom = self.pmos1_inst.by()
# active_to_poly_contact = active_bottom - self.poly_to_active - 0.5 * contact.poly_contact.first_layer_height # active_to_poly_contact = active_bottom - self.poly_to_active - 0.5 * contact.poly_contact.first_layer_height
# active_to_poly_contact2 = active_bottom - drc("contact_to_gate") - 0.5 * self.route_layer_width # active_to_poly_contact2 = active_bottom - self.poly_contact_to_gate - 0.5 * self.route_layer_width
# self.inputB_yoffset = min(active_contact_to_poly_contact, # self.inputB_yoffset = min(active_contact_to_poly_contact,
# active_to_poly_contact, # active_to_poly_contact,
# active_to_poly_contact2) # active_to_poly_contact2)
# This will help with the wells and the input/output placement # This will help with the wells and the input/output placement
self.route_input_gate(self.pmos2_inst, bpin = self.route_input_gate(self.pmos2_inst,
self.nmos2_inst, self.nmos2_inst,
self.inputB_yoffset, self.inputB_yoffset,
"B", "B",
position="center") position="center")
if OPTS.tech_name == "sky130":
self.add_enclosure([apin, bpin], "npc", drc("npc_enclose_poly"))
def route_output(self): def route_output(self):
""" Route the Z output """ """ Route the Z output """

View File

@ -222,31 +222,33 @@ class pnand3(pgate.pgate):
# doesn't use nmos uy because that is calculated using offset + poly height # doesn't use nmos uy because that is calculated using offset + poly height
active_top = self.nmos1_inst.by() + self.nmos1_inst.mod.active_height active_top = self.nmos1_inst.by() + self.nmos1_inst.mod.active_height
active_to_poly_contact = active_top + self.poly_to_active + 0.5 * contact.poly_contact.first_layer_height active_to_poly_contact = active_top + self.poly_to_active + 0.5 * contact.poly_contact.first_layer_height
active_to_poly_contact2 = active_top + drc("contact_to_gate") + 0.5 * self.route_layer_width active_to_poly_contact2 = active_top + self.poly_contact_to_gate + 0.5 * self.route_layer_width
self.inputA_yoffset = max(active_contact_to_poly_contact, self.inputA_yoffset = max(active_contact_to_poly_contact,
active_to_poly_contact, active_to_poly_contact,
active_to_poly_contact2) active_to_poly_contact2)
self.route_input_gate(self.pmos1_inst, apin = self.route_input_gate(self.pmos1_inst,
self.nmos1_inst, self.nmos1_inst,
self.inputA_yoffset, self.inputA_yoffset,
"A", "A",
position="left") position="left")
# Put B right on the well line self.inputB_yoffset = self.inputA_yoffset + self.m3_pitch
self.inputB_yoffset = self.inputA_yoffset + non_contact_pitch bpin = self.route_input_gate(self.pmos2_inst,
self.route_input_gate(self.pmos2_inst, self.nmos2_inst,
self.nmos2_inst, self.inputB_yoffset,
self.inputB_yoffset, "B",
"B", position="center")
position="center")
self.inputC_yoffset = self.inputB_yoffset + non_contact_pitch self.inputC_yoffset = self.inputB_yoffset + self.m3_pitch
self.route_input_gate(self.pmos3_inst, cpin = self.route_input_gate(self.pmos3_inst,
self.nmos3_inst, self.nmos3_inst,
self.inputC_yoffset, self.inputC_yoffset,
"C", "C",
position="right") position="right")
if OPTS.tech_name == "sky130":
self.add_enclosure([apin, bpin, cpin], "npc", drc("npc_enclose_poly"))
def route_output(self): def route_output(self):
""" Route the Z output """ """ Route the Z output """

View File

@ -195,22 +195,25 @@ class pnor2(pgate.pgate):
self.inputB_yoffset = bottom_pin_offset + self.m1_nonpref_pitch self.inputB_yoffset = bottom_pin_offset + self.m1_nonpref_pitch
self.inputA_yoffset = self.inputB_yoffset + self.m1_nonpref_pitch self.inputA_yoffset = self.inputB_yoffset + self.m1_nonpref_pitch
self.route_input_gate(self.pmos2_inst, bpin = self.route_input_gate(self.pmos2_inst,
self.nmos2_inst, self.nmos2_inst,
self.inputB_yoffset, self.inputB_yoffset,
"B", "B",
position="right", position="right",
directions=("V", "V")) directions=("V", "V"))
# This will help with the wells and the input/output placement # This will help with the wells and the input/output placement
self.route_input_gate(self.pmos1_inst, apin = self.route_input_gate(self.pmos1_inst,
self.nmos1_inst, self.nmos1_inst,
self.inputA_yoffset, self.inputA_yoffset,
"A", "A",
directions=("V", "V")) directions=("V", "V"))
self.output_yoffset = self.inputA_yoffset + self.m1_nonpref_pitch self.output_yoffset = self.inputA_yoffset + self.m1_nonpref_pitch
if OPTS.tech_name == "sky130":
self.add_enclosure([apin, bpin], "npc", drc("npc_enclose_poly"))
def route_output(self): def route_output(self):
""" Route the Z output """ """ Route the Z output """
# PMOS2 (right) drain # PMOS2 (right) drain

View File

@ -38,16 +38,10 @@ class precharge(design.design):
if self.bitcell_bl_pin.layer == "m1": if self.bitcell_bl_pin.layer == "m1":
self.bitline_layer = "m1" self.bitline_layer = "m1"
if "li" in layer: self.en_layer = "m2"
self.en_layer = "li"
else:
self.en_layer = "m2"
else: else:
self.bitline_layer = "m2" self.bitline_layer = "m2"
if "li" in layer: self.en_layer = "m1"
self.en_layer = "li"
else:
self.en_layer = "m1"
# Creates the netlist and layout # Creates the netlist and layout
# Since it has variable height, it is not a pgate. # Since it has variable height, it is not a pgate.
@ -196,7 +190,7 @@ class precharge(design.design):
pin_offset = self.lower_pmos_inst.get_pin("G").lr() pin_offset = self.lower_pmos_inst.get_pin("G").lr()
# This is an extra space down for some techs with contact to active spacing # This is an extra space down for some techs with contact to active spacing
contact_space = max(self.poly_space, contact_space = max(self.poly_space,
self.contact_to_gate) + 0.5 * contact.poly_contact.first_layer_height self.poly_contact_to_gate) + 0.5 * contact.poly_contact.first_layer_height
offset = pin_offset - vector(0, contact_space) offset = pin_offset - vector(0, contact_space)
self.add_via_stack_center(from_layer="poly", self.add_via_stack_center(from_layer="poly",
to_layer=self.en_layer, to_layer=self.en_layer,

View File

@ -196,7 +196,7 @@ class ptx(design.design):
# This is the spacing between the poly gates # This is the spacing between the poly gates
self.min_poly_pitch = self.poly_space + self.poly_width self.min_poly_pitch = self.poly_space + self.poly_width
self.contacted_poly_pitch = self.poly_space + contact.poly_contact.width self.contacted_poly_pitch = self.poly_space + contact.poly_contact.width
self.contact_pitch = 2 * self.contact_to_gate + self.poly_width + self.contact_width self.contact_pitch = 2 * self.active_contact_to_gate + self.poly_width + self.contact_width
self.poly_pitch = max(self.min_poly_pitch, self.poly_pitch = max(self.min_poly_pitch,
self.contacted_poly_pitch, self.contacted_poly_pitch,
self.contact_pitch) self.contact_pitch)
@ -206,7 +206,7 @@ class ptx(design.design):
# Active width is determined by enclosure on both ends and contacted pitch, # Active width is determined by enclosure on both ends and contacted pitch,
# at least one poly and n-1 poly pitches # at least one poly and n-1 poly pitches
self.active_width = 2 * self.end_to_contact + self.active_contact.width \ self.active_width = 2 * self.end_to_contact + self.active_contact.width \
+ 2 * self.contact_to_gate + self.poly_width + (self.mults - 1) * self.poly_pitch + 2 * self.active_contact_to_gate + self.poly_width + (self.mults - 1) * self.poly_pitch
# Active height is just the transistor width # Active height is just the transistor width
self.active_height = self.tx_width self.active_height = self.tx_width
@ -321,7 +321,7 @@ class ptx(design.design):
""" """
# poly is one contacted spacing from the end and down an extension # poly is one contacted spacing from the end and down an extension
poly_offset = self.contact_offset \ poly_offset = self.contact_offset \
+ vector(0.5 * self.active_contact.width + 0.5 * self.poly_width + self.contact_to_gate, 0) + vector(0.5 * self.active_contact.width + 0.5 * self.poly_width + self.active_contact_to_gate, 0)
# poly_positions are the bottom center of the poly gates # poly_positions are the bottom center of the poly gates
self.poly_positions = [] self.poly_positions = []

View File

@ -160,11 +160,11 @@ class pwrite_driver(design.design):
track_xoff = self.get_m2_track(1) track_xoff = self.get_m2_track(1)
din_loc = self.din_inst.get_pin("A").center() din_loc = self.din_inst.get_pin("A").center()
self.add_via_stack("m1", "m2", din_loc) self.add_via_stack_center("m1", "m2", din_loc)
din_track = vector(track_xoff,din_loc.y) din_track = vector(track_xoff,din_loc.y)
br_in = self.br_inst.get_pin("in").center() br_in = self.br_inst.get_pin("in").center()
self.add_via_stack("m1", "m2", br_in) self.add_via_stack_center("m1", "m2", br_in)
br_track = vector(track_xoff,br_in.y) br_track = vector(track_xoff,br_in.y)
din_in = vector(track_xoff,0) din_in = vector(track_xoff,0)
@ -181,11 +181,11 @@ class pwrite_driver(design.design):
track_xoff = self.get_m4_track(self.din_bar_track) track_xoff = self.get_m4_track(self.din_bar_track)
din_bar_in = self.din_inst.get_pin("Z").center() din_bar_in = self.din_inst.get_pin("Z").center()
self.add_via_stack("m1", "m3", din_bar_in) self.add_via_stack_center("m1", "m3", din_bar_in)
din_bar_track = vector(track_xoff,din_bar_in.y) din_bar_track = vector(track_xoff,din_bar_in.y)
bl_in = self.bl_inst.get_pin("in").center() bl_in = self.bl_inst.get_pin("in").center()
self.add_via_stack("m1", "m3", bl_in) self.add_via_stack_center("m1", "m3", bl_in)
bl_track = vector(track_xoff,bl_in.y) bl_track = vector(track_xoff,bl_in.y)
din_in = vector(track_xoff,0) din_in = vector(track_xoff,0)
@ -204,15 +204,15 @@ class pwrite_driver(design.design):
# This M2 pitch is a hack since the A and Z pins align horizontally # This M2 pitch is a hack since the A and Z pins align horizontally
en_bar_loc = self.en_inst.get_pin("Z").uc() en_bar_loc = self.en_inst.get_pin("Z").uc()
en_bar_track = vector(track_xoff, en_bar_loc.y) en_bar_track = vector(track_xoff, en_bar_loc.y)
self.add_via_stack("m1", "m3", en_bar_loc) self.add_via_stack_center("m1", "m3", en_bar_loc)
# This is a U route to the right down then left # This is a U route to the right down then left
bl_en_loc = self.bl_inst.get_pin("en_bar").center() bl_en_loc = self.bl_inst.get_pin("en_bar").center()
bl_en_track = vector(track_xoff, bl_en_loc.y) bl_en_track = vector(track_xoff, bl_en_loc.y)
self.add_via_stack("m1", "m3", bl_en_loc) self.add_via_stack_center("m1", "m3", bl_en_loc)
br_en_loc = self.br_inst.get_pin("en_bar").center() br_en_loc = self.br_inst.get_pin("en_bar").center()
br_en_track = vector(track_xoff, bl_en_loc.y) br_en_track = vector(track_xoff, bl_en_loc.y)
self.add_via_stack("m1", "m3", br_en_loc) self.add_via_stack_center("m1", "m3", br_en_loc)
# L shape # L shape
@ -237,21 +237,21 @@ class pwrite_driver(design.design):
en_loc = self.en_inst.get_pin("A").center() en_loc = self.en_inst.get_pin("A").center()
en_rail = vector(en_loc.x, vdd_yloc) en_rail = vector(en_loc.x, vdd_yloc)
self.add_via_stack("m1", "m2", en_loc) self.add_via_stack_center("m1", "m2", en_loc)
self.add_path("m2", [en_loc, en_rail]) self.add_path("m2", [en_loc, en_rail])
self.add_via_stack("m2", "m3", en_rail) self.add_via_stack_center("m2", "m3", en_rail)
# Start point in the track on the pin rail # Start point in the track on the pin rail
en_track = vector(track_xoff, vdd_yloc) en_track = vector(track_xoff, vdd_yloc)
self.add_via_stack("m3", "m4", en_track) self.add_via_stack_center("m3", "m4", en_track)
# This is a U route to the right down then left # This is a U route to the right down then left
bl_en_loc = self.bl_inst.get_pin("en").center() bl_en_loc = self.bl_inst.get_pin("en").center()
bl_en_track = vector(track_xoff, bl_en_loc.y) bl_en_track = vector(track_xoff, bl_en_loc.y)
self.add_via_stack("m1", "m3", bl_en_loc) self.add_via_stack_center("m1", "m3", bl_en_loc)
br_en_loc = self.br_inst.get_pin("en").center() br_en_loc = self.br_inst.get_pin("en").center()
br_en_track = vector(track_xoff, bl_en_loc.y) br_en_track = vector(track_xoff, bl_en_loc.y)
self.add_via_stack("m1", "m3", br_en_loc) self.add_via_stack_center("m1", "m3", br_en_loc)
# U shape # U shape
self.add_wire(self.m3_stack, self.add_wire(self.m3_stack,

View File

@ -70,96 +70,34 @@ class sram_1bank(sram_base):
# If a horizontal channel, they rely on the vertical channel non-preferred (contacted) pitch. # 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. # 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. # So, m3 non-pref pitch means that this is routed on the m2 layer.
if self.write_size: self.data_bus_gap = self.m4_nonpref_pitch * 2
self.data_bus_gap = self.m4_nonpref_pitch * 2
self.data_bus_size = self.m4_nonpref_pitch * (self.word_size + self.num_spare_cols) + 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
if self.num_spare_cols:
self.spare_wen_bus_gap = self.m2_nonpref_pitch * 2
self.spare_wen_bus_size = self.m2_nonpref_pitch * (max(self.num_spare_cols + 1, self.col_addr_size + 1)) + self.spare_wen_bus_gap
else:
self.spare_wen_bus_size = 0
elif self.num_spare_cols and not self.write_size:
self.data_bus_gap = self.m4_nonpref_pitch * 2
self.data_bus_size = self.m4_nonpref_pitch * (self.word_size + self.num_spare_cols) + self.data_bus_gap
self.spare_wen_bus_gap = self.m2_nonpref_pitch * 2
self.spare_wen_bus_size = self.m2_nonpref_pitch * (max(self.num_spare_cols + 1, self.col_addr_size + 1)) + self.spare_wen_bus_gap
else: # Spare wen are on a separate layer so not included
self.data_bus_gap = self.m3_nonpref_pitch * 2 # Start with 1 track minimum
self.data_bus_size = self.m3_nonpref_pitch * (max(self.word_size + 1, self.col_addr_size + 1)) + self.data_bus_gap self.data_bus_size = [1] * len(self.all_ports)
for port in self.all_ports:
self.col_addr_bus_gap = self.m2_nonpref_pitch * 2 # All ports need the col addr flops
self.col_addr_bus_size = self.m2_nonpref_pitch * (self.col_addr_size) + self.col_addr_bus_gap self.data_bus_size[port] += self.col_addr_size
# Write ports need the data input flops and write mask flops
if port in self.write_ports:
self.data_bus_size[port] += self.num_wmasks + self.word_size
# This is for the din pins that get routed in the same channel
# when we have dout and din together
if port in self.readwrite_ports:
self.data_bus_size[port] += self.word_size
# Convert to length
self.data_bus_size[port] *= self.m4_nonpref_pitch
# Add the gap in unit length
self.data_bus_size[port] += self.data_bus_gap
# Port 0 # Port 0
port = 0 port = 0
if port in self.write_ports:
if self.write_size:
bus_size = max(self.wmask_bus_size, self.spare_wen_bus_size)
# Add the write mask flops below the write mask AND array.
wmask_pos[port] = vector(self.bank.bank_array_ll.x,
- 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,
- self.data_bus_size - bus_size - 2 * self.dff.height)
self.data_dff_insts[port].place(data_pos[port])
#Add spare write enable flops to the right of write mask flops
if self.num_spare_cols:
spare_wen_pos[port] = vector(self.bank.bank_array_ll.x + self.wmask_dff_insts[port].width + self.bank.m2_gap,
- bus_size - self.dff.height)
self.spare_wen_dff_insts[port].place(spare_wen_pos[port])
elif self.num_spare_cols and not self.write_size:
# Add spare write enable flops below bank (lower right)
spare_wen_pos[port] = vector(self.bank.bank_array_ll.x,
- self.spare_wen_bus_size - self.dff.height)
self.spare_wen_dff_insts[port].place(spare_wen_pos[port])
# Add the data flops below the spare write enable flops.
data_pos[port] = vector(self.bank.bank_array_ll.x,
- self.data_bus_size - self.spare_wen_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
# This relies on the lower-left of the array of the bank
# decoder in upper left, bank in upper right, sensing in lower right.
# 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_ll.x,
-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)
data_pos[port] = vector(self.bank.bank_array_ll.x, 0)
spare_wen_pos[port] = vector(self.bank.bank_array_ll.x, 0)
# Add the col address flops below the bank to the left of the lower-left of bank array
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,
-bus_size - self.col_addr_dff_insts[port].height)
elif self.num_spare_cols and not 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,
-self.spare_wen_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,
-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)
# This includes 2 M2 pitches for the row addr clock line. # This includes 2 M2 pitches for the row addr clock line.
# The delay line is aligned with the bitcell array while the control logic is aligned with the port_data
# using the control_logic_center value.
control_pos[port] = vector(-self.control_logic_insts[port].width - 2 * self.m2_pitch, control_pos[port] = vector(-self.control_logic_insts[port].width - 2 * self.m2_pitch,
self.bank.bank_array_ll.y - self.control_logic_insts[port].mod.control_logic_center.y - 2 * self.bank.m2_gap) self.bank.bank_array_ll.y - self.control_logic_insts[port].mod.control_logic_center.y)
self.control_logic_insts[port].place(control_pos[port]) self.control_logic_insts[port].place(control_pos[port])
# The row address bits are placed above the control logic aligned on the right. # The row address bits are placed above the control logic aligned on the right.
@ -169,70 +107,56 @@ class sram_1bank(sram_base):
row_addr_pos[port] = vector(x_offset, y_offset) row_addr_pos[port] = vector(x_offset, y_offset)
self.row_addr_dff_insts[port].place(row_addr_pos[port]) self.row_addr_dff_insts[port].place(row_addr_pos[port])
# Add the col address flops below the bank to the right of the control logic
x_offset = self.control_logic_insts[port].rx() + self.dff.width
y_offset = - self.data_bus_size[port] - self.dff.height
if self.col_addr_dff:
col_addr_pos[port] = vector(x_offset,
y_offset)
self.col_addr_dff_insts[port].place(col_addr_pos[port])
x_offset = self.col_addr_dff_insts[port].rx()
else:
col_addr_pos[port] = vector(x_offset, 0)
if port in self.write_ports:
if self.write_size:
# Add the write mask flops below the write mask AND array.
wmask_pos[port] = vector(x_offset,
y_offset)
self.wmask_dff_insts[port].place(wmask_pos[port])
x_offset = self.wmask_dff_insts[port].rx()
# Add the data flops below the write mask flops.
data_pos[port] = vector(x_offset,
y_offset)
self.data_dff_insts[port].place(data_pos[port])
x_offset = self.data_dff_insts[port].rx()
# Add spare write enable flops to the right of data flops since the spare columns
# will be on the right
if self.num_spare_cols:
spare_wen_pos[port] = vector(x_offset,
y_offset)
self.spare_wen_dff_insts[port].place(spare_wen_pos[port])
x_offset = self.spare_wen_dff_insts[port].rx()
else:
wmask_pos[port] = vector(x_offset, y_offset)
data_pos[port] = vector(x_offset, y_offset)
spare_wen_pos[port] = vector(x_offset, y_offset)
if len(self.all_ports)>1: if len(self.all_ports)>1:
# Port 1 # Port 1
port = 1 port = 1
if port in self.write_ports:
if self.write_size:
bus_size = max(self.wmask_bus_size, self.spare_wen_bus_size)
# Add the write mask flops above the write mask AND array.
wmask_pos[port] = vector(self.bank.bank_array_ur.x - self.wmask_dff_insts[port].width,
self.bank.height + bus_size + self.dff.height)
self.wmask_dff_insts[port].place(wmask_pos[port], mirror="MX")
# Add the data flops above the write mask flops
data_pos[port] = vector(self.bank.bank_array_ur.x - self.data_dff_insts[port].width,
self.bank.height + bus_size + self.data_bus_size + 2 * self.dff.height)
self.data_dff_insts[port].place(data_pos[port], mirror="MX")
if self.num_spare_cols:
spare_wen_pos[port] = vector(self.bank.bank_array_ur.x - self.wmask_dff_insts[port].width
- self.spare_wen_dff_insts[port].width - self.bank.m2_gap,
self.bank.height + bus_size + self.dff.height)
self.spare_wen_dff_insts[port].place(spare_wen_pos[port], mirror="MX")
# Place dffs when spare cols is enabled
elif self.num_spare_cols and not self.write_size:
# Spare wen flops on the upper right, below data flops
spare_wen_pos[port] = vector(self.bank.bank_array_ur.x - self.spare_wen_dff_insts[port].width,
self.bank.height + self.spare_wen_bus_size + self.dff.height)
self.spare_wen_dff_insts[port].place(spare_wen_pos[port], mirror="MX")
# Add the data flops above the spare write enable flops
data_pos[port] = vector(self.bank.bank_array_ur.x - self.data_dff_insts[port].width,
self.bank.height + self.spare_wen_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
# This relies on the upper-right of the array of the bank
# decoder in upper left, bank in upper right, sensing in lower right.
# 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 + 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 + bus_size + self.dff.height)
elif self.num_spare_cols and not self.write_size:
col_addr_pos[port] = vector(self.bank.bank_array_ur.x + self.bank.m2_gap,
self.bank.height + self.spare_wen_bus_size + self.dff.height)
else:
col_addr_pos[port] = vector(self.bank.bank_array_ur.x + self.bank.m2_gap,
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()
# This includes 2 M2 pitches for the row addr clock line # This includes 2 M2 pitches for the row addr clock line
# The delay line is aligned with the bitcell array while the control logic is aligned with the port_data
# using the control_logic_center value.
control_pos[port] = vector(self.bank_inst.rx() + self.control_logic_insts[port].width + 2 * self.m2_pitch, control_pos[port] = vector(self.bank_inst.rx() + self.control_logic_insts[port].width + 2 * self.m2_pitch,
self.bank.bank_array_ur.y + self.control_logic_insts[port].height - \ self.bank.bank_array_ur.y
(self.control_logic_insts[port].height - self.control_logic_insts[port].mod.control_logic_center.y) + self.control_logic_insts[port].height
+ 2 * self.bank.m2_gap) - self.control_logic_insts[port].height
+ self.control_logic_insts[port].mod.control_logic_center.y)
self.control_logic_insts[port].place(control_pos[port], mirror="XY") self.control_logic_insts[port].place(control_pos[port], mirror="XY")
# The row address bits are placed above the control logic aligned on the left. # The row address bits are placed above the control logic aligned on the left.
@ -242,6 +166,42 @@ class sram_1bank(sram_base):
row_addr_pos[port] = vector(x_offset, y_offset) row_addr_pos[port] = vector(x_offset, y_offset)
self.row_addr_dff_insts[port].place(row_addr_pos[port], mirror="XY") self.row_addr_dff_insts[port].place(row_addr_pos[port], mirror="XY")
# Add the col address flops below the bank to the right of the control logic
x_offset = self.control_logic_insts[port].lx() - 2 * self.dff.width
y_offset = self.bank.height + self.data_bus_size[port] + self.dff.height
if self.col_addr_dff:
col_addr_pos[port] = vector(x_offset - self.col_addr_dff_insts[port].width,
y_offset)
self.col_addr_dff_insts[port].place(col_addr_pos[port], mirror="MX")
x_offset = self.col_addr_dff_insts[port].lx()
else:
col_addr_pos[port] = vector(x_offset, y_offset)
if port in self.write_ports:
# Add spare write enable flops to the right of the data flops since the spare
# columns will be on the left
if self.num_spare_cols:
spare_wen_pos[port] = vector(x_offset - self.spare_wen_dff_insts[port].width,
y_offset)
self.spare_wen_dff_insts[port].place(spare_wen_pos[port], mirror="MX")
x_offset = self.spare_wen_dff_insts[port].lx()
if self.write_size:
# Add the write mask flops below the write mask AND array.
wmask_pos[port] = vector(x_offset - self.wmask_dff_insts[port].width,
y_offset)
self.wmask_dff_insts[port].place(wmask_pos[port], mirror="MX")
x_offset = self.wmask_dff_insts[port].lx()
# Add the data flops below the write mask flops.
data_pos[port] = vector(x_offset - self.data_dff_insts[port].width,
y_offset)
self.data_dff_insts[port].place(data_pos[port], mirror="MX")
else:
wmask_pos[port] = vector(x_offset, y_offset)
data_pos[port] = vector(x_offset, y_offset)
spare_wen_pos[port] = vector(x_offset, y_offset)
def add_layout_pins(self): def add_layout_pins(self):
""" """
Add the top-level pins for a single bank SRAM with control. Add the top-level pins for a single bank SRAM with control.
@ -250,7 +210,6 @@ class sram_1bank(sram_base):
lowest_coord = self.find_lowest_coords() lowest_coord = self.find_lowest_coords()
bbox = [lowest_coord, highest_coord] bbox = [lowest_coord, highest_coord]
for port in self.all_ports: for port in self.all_ports:
# Depending on the port, use the bottom/top or left/right sides # Depending on the port, use the bottom/top or left/right sides
# Port 0 is left/bottom # Port 0 is left/bottom
@ -282,10 +241,35 @@ class sram_1bank(sram_base):
"clk", "clk",
"clk{}".format(port)) "clk{}".format(port))
# Data output pins go to BOTTOM/TOP # Data input pins go to BOTTOM/TOP
if port in self.read_ports: din_ports = []
if port in self.write_ports:
for bit in range(self.word_size + self.num_spare_cols): for bit in range(self.word_size + self.num_spare_cols):
if OPTS.perimeter_pins: if OPTS.perimeter_pins:
p = self.add_perimeter_pin(name="din{0}[{1}]".format(port, bit),
pin=self.data_dff_insts[port].get_pin("din_{0}".format(bit)),
side=bottom_or_top,
bbox=bbox)
din_ports.append(p)
else:
self.copy_layout_pin(self.data_dff_insts[port],
"din_{}".format(bit),
"din{0}[{1}]".format(port, bit))
# Data output pins go to BOTTOM/TOP
if port in self.readwrite_ports and OPTS.perimeter_pins:
for bit in range(self.word_size + self.num_spare_cols):
# This should be routed next to the din pin
p = din_ports[bit]
self.add_layout_pin_rect_center(text="dout{0}[{1}]".format(port, bit),
layer=p.layer,
offset=p.center() + vector(self.m3_pitch, 0),
width=p.width(),
height=p.height())
elif port in self.read_ports:
for bit in range(self.word_size + self.num_spare_cols):
if OPTS.perimeter_pins:
# This should have a clear route to the perimeter if there are no din routes
self.add_perimeter_pin(name="dout{0}[{1}]".format(port, bit), self.add_perimeter_pin(name="dout{0}[{1}]".format(port, bit),
pin=self.bank_inst.get_pin("dout{0}_{1}".format(port, bit)), pin=self.bank_inst.get_pin("dout{0}_{1}".format(port, bit)),
side=bottom_or_top, side=bottom_or_top,
@ -294,6 +278,8 @@ class sram_1bank(sram_base):
self.copy_layout_pin(self.bank_inst, self.copy_layout_pin(self.bank_inst,
"dout{0}_{1}".format(port, bit), "dout{0}_{1}".format(port, bit),
"dout{0}[{1}]".format(port, bit)) "dout{0}[{1}]".format(port, bit))
# Lower address bits go to BOTTOM/TOP # Lower address bits go to BOTTOM/TOP
for bit in range(self.col_addr_size): for bit in range(self.col_addr_size):
@ -319,19 +305,6 @@ class sram_1bank(sram_base):
"din_{}".format(bit), "din_{}".format(bit),
"addr{0}[{1}]".format(port, bit + self.col_addr_size)) "addr{0}[{1}]".format(port, bit + self.col_addr_size))
# Data input pins go to BOTTOM/TOP
if port in self.write_ports:
for bit in range(self.word_size + self.num_spare_cols):
if OPTS.perimeter_pins:
self.add_perimeter_pin(name="din{0}[{1}]".format(port, bit),
pin=self.data_dff_insts[port].get_pin("din_{}".format(bit)),
side=bottom_or_top,
bbox=bbox)
else:
self.copy_layout_pin(self.data_dff_insts[port],
"din_{}".format(bit),
"din{0}[{1}]".format(port, bit))
# Write mask pins go to BOTTOM/TOP # Write mask pins go to BOTTOM/TOP
if port in self.write_ports: if port in self.write_ports:
if self.write_size: if self.write_size:
@ -370,17 +343,76 @@ class sram_1bank(sram_base):
self.route_row_addr_dff() self.route_row_addr_dff()
if self.col_addr_dff: for port in self.all_ports:
self.route_col_addr_dff() self.route_dff(port)
self.route_data_dff()
if self.write_size:
self.route_wmask_dff()
if self.num_spare_cols: def route_dff(self, port):
self.route_spare_wen_dff()
route_map = []
# column mux dff
if self.col_addr_size > 0:
dff_names = ["dout_{}".format(x) for x in range(self.col_addr_size)]
dff_pins = [self.col_addr_dff_insts[port].get_pin(x) for x in dff_names]
bank_names = ["addr{0}_{1}".format(port, x) for x in range(self.col_addr_size)]
bank_pins = [self.bank_inst.get_pin(x) for x in bank_names]
route_map.extend(list(zip(bank_pins, dff_pins)))
# wmask dff
if self.num_wmasks > 0 and port in self.write_ports:
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]
bank_names = ["bank_wmask{0}_{1}".format(port, x) for x in range(self.num_wmasks)]
bank_pins = [self.bank_inst.get_pin(x) for x in bank_names]
route_map.extend(list(zip(bank_pins, dff_pins)))
if port in self.write_ports:
# synchronized inputs from data dff
dff_names = ["dout_{}".format(x) for x in range(self.word_size + self.num_spare_cols)]
dff_pins = [self.data_dff_insts[port].get_pin(x) for x in dff_names]
bank_names = ["din{0}_{1}".format(port, x) for x in range(self.word_size + self.num_spare_cols)]
bank_pins = [self.bank_inst.get_pin(x) for x in bank_names]
route_map.extend(list(zip(bank_pins, dff_pins)))
if port in self.readwrite_ports and OPTS.perimeter_pins:
# outputs from sense amp
# These are the output pins which had their pin placed on the perimeter, so route from the
# sense amp which should not align with write driver input
sram_names = ["dout{0}[{1}]".format(port, x) for x in range(self.word_size + self.num_spare_cols)]
sram_pins = [self.get_pin(x) for x in sram_names]
bank_names = ["dout{0}_{1}".format(port, x) for x in range(self.word_size + self.num_spare_cols)]
bank_pins = [self.bank_inst.get_pin(x) for x in bank_names]
route_map.extend(list(zip(bank_pins, sram_pins)))
if self.num_wmasks > 0 and port in self.write_ports:
layer_stack = self.m3_stack
else:
layer_stack = self.m1_stack
if port == 0:
offset = vector(self.control_logic_insts[port].rx() + self.dff.width,
- self.data_bus_size[port] + 2 * self.m1_pitch)
else:
offset = vector(0,
self.bank.height + 2 * self.m1_space)
if len(route_map) > 0:
self.create_horizontal_channel_route(netlist=route_map,
offset=offset,
layer_stack=layer_stack)
# Route these separately because sometimes the pin pitch on the write driver is too narrow for M3 (FreePDK45)
# spare wen dff
if self.num_spare_cols > 0 and port in self.write_ports:
dff_names = ["dout_{}".format(x) for x in range(self.num_spare_cols)]
dff_pins = [self.spare_wen_dff_insts[port].get_pin(x) for x in dff_names]
bank_names = ["bank_spare_wen{0}_{1}".format(port, x) for x in range(self.num_spare_cols)]
bank_pins = [self.bank_inst.get_pin(x) for x in bank_names]
route_map = zip(bank_pins, dff_pins)
self.create_horizontal_channel_route(netlist=route_map,
offset=offset,
layer_stack=self.m1_stack)
def route_clk(self): def route_clk(self):
""" Route the clock network """ """ Route the clock network """
@ -423,8 +455,7 @@ class sram_1bank(sram_base):
mid_pos = vector(clk_steiner_pos.x, dff_clk_pos.y) mid_pos = vector(clk_steiner_pos.x, dff_clk_pos.y)
self.add_wire(self.m2_stack[::-1], self.add_wire(self.m2_stack[::-1],
[dff_clk_pos, mid_pos, clk_steiner_pos]) [dff_clk_pos, mid_pos, clk_steiner_pos])
elif port in self.write_ports:
if port in self.write_ports:
data_dff_clk_pin = self.data_dff_insts[port].get_pin("clk") data_dff_clk_pin = self.data_dff_insts[port].get_pin("clk")
data_dff_clk_pos = data_dff_clk_pin.center() data_dff_clk_pos = data_dff_clk_pin.center()
mid_pos = vector(clk_steiner_pos.x, data_dff_clk_pos.y) mid_pos = vector(clk_steiner_pos.x, data_dff_clk_pos.y)
@ -436,24 +467,6 @@ class sram_1bank(sram_base):
self.add_wire(self.m2_stack[::-1], self.add_wire(self.m2_stack[::-1],
[data_dff_clk_pos, mid_pos, clk_steiner_pos]) [data_dff_clk_pos, mid_pos, clk_steiner_pos])
if self.write_size:
wmask_dff_clk_pin = self.wmask_dff_insts[port].get_pin("clk")
wmask_dff_clk_pos = wmask_dff_clk_pin.center()
mid_pos = vector(clk_steiner_pos.x, wmask_dff_clk_pos.y)
# 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(self.m2_stack[::-1], [wmask_dff_clk_pos, mid_pos, clk_steiner_pos])
if self.num_spare_cols:
spare_wen_dff_clk_pin = self.spare_wen_dff_insts[port].get_pin("clk")
spare_wen_dff_clk_pos = spare_wen_dff_clk_pin.center()
mid_pos = vector(clk_steiner_pos.x, spare_wen_dff_clk_pos.y)
# 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(self.m2_stack[::-1], [spare_wen_dff_clk_pos, mid_pos, clk_steiner_pos])
def route_control_logic(self): def route_control_logic(self):
""" Route the control logic pins that are not inputs """ """ Route the control logic pins that are not inputs """
@ -472,7 +485,12 @@ class sram_1bank(sram_base):
dest_pin = self.bank_inst.get_pin("rbl_bl{}".format(port)) dest_pin = self.bank_inst.get_pin("rbl_bl{}".format(port))
self.add_wire(self.m2_stack[::-1], self.add_wire(self.m2_stack[::-1],
[src_pin.center(), vector(src_pin.cx(), dest_pin.cy()), dest_pin.rc()]) [src_pin.center(), vector(src_pin.cx(), dest_pin.cy()), dest_pin.rc()])
# self.connect_hbus(src_pin, dest_pin) self.add_via_stack_center(from_layer=src_pin.layer,
to_layer="m2",
offset=src_pin.center())
self.add_via_stack_center(from_layer=dest_pin.layer,
to_layer="m2",
offset=dest_pin.center())
def route_row_addr_dff(self): def route_row_addr_dff(self):
""" Connect the output of the row flops to the bank pins """ """ Connect the output of the row flops to the bank pins """
@ -485,141 +503,14 @@ class sram_1bank(sram_base):
flop_pos = flop_pin.center() flop_pos = flop_pin.center()
bank_pos = bank_pin.center() bank_pos = bank_pin.center()
mid_pos = vector(bank_pos.x, flop_pos.y) mid_pos = vector(bank_pos.x, flop_pos.y)
self.add_wire(self.m2_stack[::-1],
[flop_pos, mid_pos, bank_pos])
self.add_via_stack_center(from_layer=flop_pin.layer, self.add_via_stack_center(from_layer=flop_pin.layer,
to_layer="m3", to_layer="m3",
offset=flop_pos) offset=flop_pos)
self.add_path("m3", [flop_pos, mid_pos])
def route_col_addr_dff(self): self.add_via_stack_center(from_layer=bank_pin.layer,
""" Connect the output of the col flops to the bank pins """ to_layer="m3",
for port in self.all_ports: offset=mid_pos)
if port % 2: self.add_path(bank_pin.layer, [mid_pos, bank_pos])
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, 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",
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)
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)
def route_data_dff(self):
""" Connect the output of the data flops to the write driver """
# This is where the channel will start (y-dimension at least)
for port in self.write_ports:
if port % 2:
offset = self.data_dff_insts[port].ll() - vector(0, self.data_bus_size)
else:
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 + self.num_spare_cols)]
dff_pins = [self.data_dff_insts[port].get_pin(x) for x in dff_names]
if self.write_size or self.num_spare_cols:
for x in dff_names:
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_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 + self.num_spare_cols)]
bank_pins = [self.bank_inst.get_pin(x) for x in bank_names]
if self.write_size or self.num_spare_cols:
for x in bank_names:
pin = self.bank_inst.get_pin(x)
if port % 2:
pin_offset = pin.uc()
else:
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 or self.num_spare_cols:
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)
def route_wmask_dff(self):
""" Connect the output of the wmask flops to the write mask AND array """
# 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.wmask_bus_size)
else:
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]
for x in dff_names:
offset_pin = self.wmask_dff_insts[port].get_pin(x).center()
self.add_via_center(layers=self.m1_stack,
offset=offset_pin,
directions=("V", "V"))
bank_names = ["bank_wmask{0}_{1}".format(port, x) for x in range(self.num_wmasks)]
bank_pins = [self.bank_inst.get_pin(x) for x in bank_names]
for x in bank_names:
offset_pin = self.bank_inst.get_pin(x).center()
self.add_via_center(layers=self.m1_stack,
offset=offset_pin)
route_map = list(zip(bank_pins, dff_pins))
self.create_horizontal_channel_route(netlist=route_map,
offset=offset,
layer_stack=self.m1_stack)
def route_spare_wen_dff(self):
""" Connect the output of the spare write enable flops to the spare write drivers """
# This is where the channel will start (y-dimension at least)
for port in self.write_ports:
if port % 2:
# for port 0
offset = self.spare_wen_dff_insts[port].ll() - vector(0, self.spare_wen_bus_size)
else:
offset = self.spare_wen_dff_insts[port].ul() + vector(0, self.spare_wen_bus_gap)
dff_names = ["dout_{}".format(x) for x in range(self.num_spare_cols)]
dff_pins = [self.spare_wen_dff_insts[port].get_pin(x) for x in dff_names]
for x in dff_names:
offset_pin = self.spare_wen_dff_insts[port].get_pin(x).center()
self.add_via_center(layers=self.m1_stack,
offset=offset_pin,
directions=("V", "V"))
bank_names = ["bank_spare_wen{0}_{1}".format(port, x) for x in range(self.num_spare_cols)]
bank_pins = [self.bank_inst.get_pin(x) for x in bank_names]
for x in bank_names:
offset_pin = self.bank_inst.get_pin(x).center()
self.add_via_center(layers=self.m1_stack,
offset=offset_pin)
route_map = list(zip(bank_pins, dff_pins))
self.create_horizontal_channel_route(netlist=route_map,
offset=offset,
layer_stack=self.m1_stack)
def add_lvs_correspondence_points(self): def add_lvs_correspondence_points(self):
""" """

0
compiler/tests/10_write_driver_array_1rw_1r_test.py Normal file → Executable file
View File

0
compiler/tests/10_write_mask_and_array_1rw_1r_test.py Normal file → Executable file
View File

View File

@ -28,9 +28,11 @@ class replica_bitcell_array_1rw_1r_test(openram_test):
a = factory.create(module_type="replica_bitcell_array", cols=4, rows=4, left_rbl=1, right_rbl=1, bitcell_ports=[0, 1]) a = factory.create(module_type="replica_bitcell_array", cols=4, rows=4, left_rbl=1, right_rbl=1, bitcell_ports=[0, 1])
self.local_check(a) self.local_check(a)
debug.info(2, "Testing 4x4 array for cell_1rw_1r") # Sky 130 has restrictions on the symmetries
a = factory.create(module_type="replica_bitcell_array", cols=4, rows=4, left_rbl=2, right_rbl=0, bitcell_ports=[0, 1]) if OPTS.tech_name != "sky130":
self.local_check(a) debug.info(2, "Testing 4x4 array for cell_1rw_1r")
a = factory.create(module_type="replica_bitcell_array", cols=4, rows=4, left_rbl=2, right_rbl=0, bitcell_ports=[0, 1])
self.local_check(a)
globals.end_openram() globals.end_openram()

View File

@ -0,0 +1,65 @@
#!/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
@unittest.skip("SKIPPING 50_riscv_func_test")
class riscv_func_test(openram_test):
def runTest(self):
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
globals.init_openram(config_file)
OPTS.analytical_delay = False
OPTS.netlist_only = True
OPTS.trim_netlist = False
OPTS.num_rw_ports = 1
OPTS.num_w_ports = 0
OPTS.num_r_ports = 1
globals.setup_bitcell()
# This is a hack to reload the characterizer __init__ with the spice version
from importlib import reload
import characterizer
reload(characterizer)
from characterizer import functional, delay
from sram_config import sram_config
c = sram_config(word_size=32,
write_size=8,
num_words=256,
num_banks=1)
c.words_per_row=1
c.recompute_sizes()
debug.info(1, "Functional test RISC-V memory"
"{} bit words, {} words, {} words per row, {} banks".format(c.word_size,
c.num_words,
c.words_per_row,
c.num_banks))
s = factory.create(module_type="sram", sram_config=c)
tempspice = OPTS.openram_temp + "sram.sp"
s.sp_write(tempspice)
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
f = functional(s.s, tempspice, corner)
(fail, error) = f.run()
self.assertTrue(fail,error)
globals.end_openram()
# instantiate a copy of the class to actually run the test
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,59 @@
#!/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
@unittest.skip("SKIPPING 50_riscv_phys_test")
class riscv_phys_test(openram_test):
def runTest(self):
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
globals.init_openram(config_file)
from sram_config import sram_config
OPTS.num_rw_ports = 1
OPTS.num_r_ports = 1
OPTS.num_w_ports = 0
globals.setup_bitcell()
OPTS.route_supplies=False
OPTS.perimeter_pins=False
c = sram_config(word_size=32,
write_size=8,
num_words=256,
num_banks=1)
c.words_per_row=2
c.recompute_sizes()
debug.info(1, "Layout test for {}rw,{}r,{}w sram "
"with {} bit words, {} words, {} words per "
"row, {} banks".format(OPTS.num_rw_ports,
OPTS.num_r_ports,
OPTS.num_w_ports,
c.word_size,
c.num_words,
c.words_per_row,
c.num_banks))
a = factory.create(module_type="sram", sram_config=c)
self.local_check(a, final_verification=True)
globals.end_openram()
# instantiate a copy of the class to actually run the test
if __name__ == "__main__":
(OPTS, args) = globals.parse_args()
del sys.argv[1:]
header(__file__, OPTS.tech_name)
unittest.main(testRunner=debugTestRunner())

View File

@ -48,18 +48,16 @@ class openram_test(unittest.TestCase):
# if we ignore things like minimum metal area of pins # if we ignore things like minimum metal area of pins
drc_result=verify.run_drc(a.name, tempgds, extract=True, final_verification=final_verification) drc_result=verify.run_drc(a.name, tempgds, extract=True, final_verification=final_verification)
# Always run LVS if we are using magic # We can still run LVS even if DRC fails in Magic OR Calibre
if "magic" in OPTS.drc_exe or drc_result == 0: lvs_result=verify.run_lvs(a.name, tempgds, tempspice, final_verification=final_verification)
lvs_result=verify.run_lvs(a.name, tempgds, tempspice, final_verification=final_verification)
# Only allow DRC to fail and LVS to pass if we are using magic # Only allow DRC to fail and LVS to pass if we are using magic
if "magic" in OPTS.drc_exe and lvs_result == 0 and drc_result != 0: if lvs_result == 0 and drc_result != 0:
# import shutil # import shutil
# zip_file = "/tmp/{0}_{1}".format(a.name, os.getpid()) # zip_file = "/tmp/{0}_{1}".format(a.name, os.getpid())
# debug.info(0, "Archiving failed files to {}.zip".format(zip_file)) # debug.info(0, "Archiving failed files to {}.zip".format(zip_file))
# shutil.make_archive(zip_file, 'zip', OPTS.openram_temp) # shutil.make_archive(zip_file, 'zip', OPTS.openram_temp)
debug.warning("DRC failed but LVS passed: {}".format(a.name)) self.fail("DRC failed but LVS passed: {}".format(a.name))
# self.fail("DRC failed but LVS passed: {}".format(a.name))
elif drc_result != 0: elif drc_result != 0:
# import shutil # import shutil
# zip_file = "/tmp/{0}_{1}".format(a.name, os.getpid()) # zip_file = "/tmp/{0}_{1}".format(a.name, os.getpid())

View File

@ -219,16 +219,14 @@ def run_drc(cell_name, gds_name, extract=False, final_verification=False):
errors = int(re.split(r'\W+', results[2])[5]) errors = int(re.split(r'\W+', results[2])[5])
# always display this summary # always display this summary
if errors > 0: result_str = "{0}\tGeometries: {1}\tChecks: {2}\tErrors: {3}".format(cell_name,
debug.error("{0}\tGeometries: {1}\tChecks: {2}\tErrors: {3}".format(cell_name,
geometries, geometries,
rulechecks, rulechecks,
errors)) errors)
if errors > 0:
debug.warning(result_str)
else: else:
debug.info(1, "{0}\tGeometries: {1}\tChecks: {2}\tErrors: {3}".format(cell_name, debug.info(1, result_str)
geometries,
rulechecks,
errors))
return errors return errors
@ -307,16 +305,15 @@ def run_lvs(cell_name, gds_name, sp_name, final_verification=False):
out_errors = len(stdouterrors) out_errors = len(stdouterrors)
total_errors = summary_errors + out_errors + ext_errors total_errors = summary_errors + out_errors + ext_errors
if total_errors > 0: # always display this summary
debug.error("{0}\tSummary: {1}\tOutput: {2}\tExtraction: {3}".format(cell_name, result_str = "{0}\tSummary: {1}\tOutput: {2}\tExtraction: {3}".format(cell_name,
summary_errors, summary_errors,
out_errors, out_errors,
ext_errors)) ext_errors)
if total_errors > 0:
debug.warning(result_str)
else: else:
debug.info(1, "{0}\tSummary: {1}\tOutput: {2}\tExtraction: {3}".format(cell_name, debug.info(1, result_str)
summary_errors,
out_errors,
ext_errors))
return total_errors return total_errors

View File

@ -200,13 +200,14 @@ def run_drc(cell_name, gds_name, extract=True, final_verification=False):
# always display this summary # always display this summary
result_str = "DRC Errors {0}\t{1}".format(cell_name, errors)
if errors > 0: if errors > 0:
for line in results: for line in results:
if "error tiles" in line: if "error tiles" in line:
debug.info(1,line.rstrip("\n")) debug.info(1,line.rstrip("\n"))
debug.error("DRC Errors {0}\t{1}".format(cell_name, errors)) debug.warning(result_str)
else: else:
debug.info(1, "DRC Errors {0}\t{1}".format(cell_name, errors)) debug.info(1, result_str)
return errors return errors

View File

@ -234,9 +234,9 @@ drc.add_enclosure("active",
enclosure = 0.005) enclosure = 0.005)
# CONTACT.6 Minimum spacing of contact and gate # CONTACT.6 Minimum spacing of contact and gate
drc["contact_to_gate"] = 0.0375 #changed from 0.035 drc["active_contact_to_gate"] = 0.0375 #changed from 0.035
# CONTACT.7 Minimum spacing of contact and poly # CONTACT.7 Minimum spacing of contact and poly
drc["contact_to_poly"] = 0.090 drc["poly_contact_to_gate"] = 0.090
# CONTACT.1 Minimum width of contact # CONTACT.1 Minimum width of contact
# CONTACT.2 Minimum spacing of contact # CONTACT.2 Minimum spacing of contact

View File

@ -217,9 +217,9 @@ drc.add_enclosure("active",
layer = "contact", layer = "contact",
enclosure = _lambda_) enclosure = _lambda_)
# Reserved for other technologies # Reserved for other technologies
drc["contact_to_gate"] = 2*_lambda_ drc["active_contact_to_gate"] = 2*_lambda_
# 5.4 Minimum spacing to gate of transistor # 5.4 Minimum spacing to gate of transistor
drc["contact_to_poly"] = 2*_lambda_ drc["poly_contact_to_gate"] = 2*_lambda_
# 6.1 Exact contact size # 6.1 Exact contact size
# 5.3 Minimum contact spacing # 5.3 Minimum contact spacing