Merge remote-tracking branch 'private/dev' into dev

This commit is contained in:
mrg 2020-07-02 16:51:06 -07:00
commit 2a86f9015b
171 changed files with 7202 additions and 2611 deletions

View File

@ -1,7 +1,7 @@
before_script: before_script:
- . /home/gitlab-runner/setup-paths.sh - . /home/gitlab-runner/setup-paths.sh
- export OPENRAM_HOME="`pwd`/compiler" - export OPENRAM_HOME="`pwd`/compiler"
- export OPENRAM_TECH="`pwd`/technology" - export OPENRAM_TECH="`pwd`/technology:/home/PDKs/skywater-tech"
stages: stages:
- test - test
@ -25,6 +25,15 @@ scn4m_subm:
- .coverage.* - .coverage.*
expire_in: 1 week expire_in: 1 week
# s8:
# stage: test
# script:
# - coverage run -p $OPENRAM_HOME/tests/regress.py -t s8
# artifacts:
# paths:
# - .coverage.*
# expire_in: 1 week
coverage: coverage:
stage: coverage stage: coverage
script: script:

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

@ -7,7 +7,7 @@
# #
import hierarchy_design import hierarchy_design
import debug import debug
from tech import drc from tech import drc, layer
import tech import tech
from vector import vector from vector import vector
from sram_factory import factory from sram_factory import factory
@ -44,7 +44,8 @@ class contact(hierarchy_design.hierarchy_design):
self.add_comment("well_type: {}\n".format(well_type)) self.add_comment("well_type: {}\n".format(well_type))
self.is_well_contact = implant_type == well_type self.is_well_contact = implant_type == well_type
# If we have a special tap layer, use it
self.layer_stack = layer_stack self.layer_stack = layer_stack
self.dimensions = dimensions self.dimensions = dimensions
@ -53,6 +54,10 @@ class contact(hierarchy_design.hierarchy_design):
first_dir = "H" if self.get_preferred_direction(layer_stack[0])=="V" else "V" first_dir = "H" if self.get_preferred_direction(layer_stack[0])=="V" else "V"
second_dir = "H" if self.get_preferred_direction(layer_stack[2])=="V" else "V" second_dir = "H" if self.get_preferred_direction(layer_stack[2])=="V" else "V"
self.directions = (first_dir, second_dir) self.directions = (first_dir, second_dir)
# Preferred directions
elif directions == "pref":
self.directions = (tech.preferred_directions[layer_stack[0]],
tech.preferred_directions[layer_stack[2]])
# User directions # User directions
elif directions: elif directions:
self.directions = directions self.directions = directions
@ -149,7 +154,7 @@ class contact(hierarchy_design.hierarchy_design):
self.first_layer_vertical_enclosure = max(self.first_layer_enclosure, self.first_layer_vertical_enclosure = max(self.first_layer_enclosure,
(self.first_layer_minwidth - self.contact_array_height) / 2) (self.first_layer_minwidth - self.contact_array_height) / 2)
else: else:
debug.error("Invalid first layer direction.", -1) debug.error("Invalid first layer direction: ".format(self.directions[0]), -1)
# In some technologies, the minimum width may be larger # In some technologies, the minimum width may be larger
# than the overlap requirement around the via, so # than the overlap requirement around the via, so
@ -165,7 +170,7 @@ class contact(hierarchy_design.hierarchy_design):
self.second_layer_vertical_enclosure = max(self.second_layer_enclosure, self.second_layer_vertical_enclosure = max(self.second_layer_enclosure,
(self.second_layer_minwidth - self.contact_array_width) / 2) (self.second_layer_minwidth - self.contact_array_width) / 2)
else: else:
debug.error("Invalid second layer direction.", -1) debug.error("Invalid secon layer direction: ".format(self.directions[1]), -1)
def create_contact_array(self): def create_contact_array(self):
""" Create the contact array at the origin""" """ Create the contact array at the origin"""
@ -192,17 +197,19 @@ class contact(hierarchy_design.hierarchy_design):
if "npc" not in tech.layer: if "npc" not in tech.layer:
return return
npc_enclose_poly = drc("npc_enclose_poly")
npc_enclose_offset = vector(npc_enclose_poly, npc_enclose_poly)
# Only add for poly layers # Only add for poly layers
if self.first_layer_name == "poly": if self.first_layer_name == "poly":
self.add_rect(layer="npc", self.add_rect(layer="npc",
offset=self.first_layer_position, offset=self.first_layer_position - npc_enclose_offset,
width=self.first_layer_width, width=self.first_layer_width + 2 * npc_enclose_poly,
height=self.first_layer_height) height=self.first_layer_height + 2 * npc_enclose_poly)
elif self.second_layer_name == "poly": elif self.second_layer_name == "poly":
self.add_rect(layer="npc", self.add_rect(layer="npc",
offset=self.second_layer_position, offset=self.second_layer_position - npc_enclose_offset,
width=self.second_layer_width, width=self.second_layer_width + 2 * npc_enclose_poly,
height=self.second_layer_height) height=self.second_layer_height + 2 * npc_enclose_poly)
def create_first_layer_enclosure(self): def create_first_layer_enclosure(self):
# this is if the first and second layers are different # this is if the first and second layers are different
@ -214,7 +221,11 @@ class contact(hierarchy_design.hierarchy_design):
self.first_layer_minwidth) self.first_layer_minwidth)
self.first_layer_height = max(self.contact_array_height + 2 * self.first_layer_vertical_enclosure, self.first_layer_height = max(self.contact_array_height + 2 * self.first_layer_vertical_enclosure,
self.first_layer_minwidth) self.first_layer_minwidth)
self.add_rect(layer=self.first_layer_name, if self.is_well_contact and self.first_layer_name == "active" and "tap" in layer:
first_layer_name = "tap"
else:
first_layer_name = self.first_layer_name
self.add_rect(layer=first_layer_name,
offset=self.first_layer_position, offset=self.first_layer_position,
width=self.first_layer_width, width=self.first_layer_width,
height=self.first_layer_height) height=self.first_layer_height)

View File

@ -172,6 +172,20 @@ class design(hierarchy_design):
self.well_extend_active = max(self.well_extend_active, self.nwell_extend_active) self.well_extend_active = max(self.well_extend_active, self.nwell_extend_active)
if "pwell" in layer: if "pwell" in layer:
self.well_extend_active = max(self.well_extend_active, self.pwell_extend_active) self.well_extend_active = max(self.well_extend_active, self.pwell_extend_active)
# The active offset is due to the well extension
if "pwell" in layer:
self.pwell_enclose_active = drc("pwell_enclose_active")
else:
self.pwell_enclose_active = 0
if "nwell" in layer:
self.nwell_enclose_active = drc("nwell_enclose_active")
else:
self.nwell_enclose_active = 0
# Use the max of either so that the poly gates will align properly
self.well_enclose_active = max(self.pwell_enclose_active,
self.nwell_enclose_active,
self.active_space)
# These are for debugging previous manual rules # These are for debugging previous manual rules
if False: if False:
@ -191,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

@ -66,14 +66,17 @@ class geometry:
self.compute_boundary(self.offset, self.mirror, self.rotate) self.compute_boundary(self.offset, self.mirror, self.rotate)
def compute_boundary(self, offset=vector(0, 0), mirror="", rotate=0): def compute_boundary(self, offset=vector(0, 0), mirror="", rotate=0):
""" Transform with offset, mirror and rotation to get the absolute pin location. """
We must then re-find the ll and ur. The master is the cell instance. """ Transform with offset, mirror and rotation to get the absolute pin location.
We must then re-find the ll and ur. The master is the cell instance.
"""
if OPTS.netlist_only: if OPTS.netlist_only:
self.boundary = [vector(0,0), vector(0,0)] self.boundary = [vector(0, 0), vector(0, 0)]
return return
(ll, ur) = [vector(0, 0), vector(self.width, self.height)] (ll, ur) = [vector(0, 0), vector(self.width, self.height)]
# Mirroring is performed before rotation
if mirror == "MX": if mirror == "MX":
ll = ll.scale(1, -1) ll = ll.scale(1, -1)
ur = ur.scale(1, -1) ur = ur.scale(1, -1)
@ -83,8 +86,14 @@ class geometry:
elif mirror == "XY": elif mirror == "XY":
ll = ll.scale(-1, -1) ll = ll.scale(-1, -1)
ur = ur.scale(-1, -1) ur = ur.scale(-1, -1)
elif mirror == "" or mirror == "R0":
pass
else:
debug.error("Invalid mirroring: {}".format(mirror), -1)
if rotate == 90: if rotate == 0:
pass
elif rotate == 90:
ll = ll.rotate_scale(-1, 1) ll = ll.rotate_scale(-1, 1)
ur = ur.rotate_scale(-1, 1) ur = ur.rotate_scale(-1, 1)
elif rotate == 180: elif rotate == 180:
@ -93,6 +102,8 @@ class geometry:
elif rotate == 270: elif rotate == 270:
ll = ll.rotate_scale(1, -1) ll = ll.rotate_scale(1, -1)
ur = ur.rotate_scale(1, -1) ur = ur.rotate_scale(1, -1)
else:
debug.error("Invalid rotation: {}".format(rotate), -1)
self.boundary = [offset + ll, offset + ur] self.boundary = [offset + ll, offset + ur]
self.normalize() self.normalize()
@ -136,6 +147,10 @@ class geometry:
def cy(self): def cy(self):
""" Return the center y """ """ Return the center y """
return 0.5 * (self.boundary[0].y + self.boundary[1].y) return 0.5 * (self.boundary[0].y + self.boundary[1].y)
def center(self):
""" Return the center coordinate """
return vector(self.cx(), self.cy())
class instance(geometry): class instance(geometry):
@ -195,14 +210,13 @@ class instance(geometry):
blockages = [] blockages = []
blockages = self.mod.gds.getBlockages(lpp) blockages = self.mod.gds.getBlockages(lpp)
for b in blockages: for b in blockages:
new_blockages.append(self.transform_coords(b,self.offset, mirr, angle)) new_blockages.append(self.transform_coords(b, self.offset, mirr, angle))
else: else:
blockages = self.mod.get_blockages(lpp) blockages = self.mod.get_blockages(lpp)
for b in blockages: for b in blockages:
new_blockages.append(self.transform_coords(b,self.offset, mirr, angle)) new_blockages.append(self.transform_coords(b, self.offset, mirr, angle))
return new_blockages return new_blockages
def gds_write_file(self, new_layout): def gds_write_file(self, new_layout):
"""Recursively writes all the sub-modules in this instance""" """Recursively writes all the sub-modules in this instance"""
debug.info(4, "writing instance: " + self.name) debug.info(4, "writing instance: " + self.name)
@ -225,26 +239,25 @@ class instance(geometry):
self.update_boundary() self.update_boundary()
debug.info(3, "placing instance {}".format(self)) debug.info(3, "placing instance {}".format(self))
def get_pin(self, name, index=-1):
def get_pin(self,name,index=-1):
""" Return an absolute pin that is offset and transformed based on """ Return an absolute pin that is offset and transformed based on
this instance location. Index will return one of several pins.""" this instance location. Index will return one of several pins."""
import copy import copy
if index == -1: if index == -1:
pin = copy.deepcopy(self.mod.get_pin(name)) pin = copy.deepcopy(self.mod.get_pin(name))
pin.transform(self.offset,self.mirror,self.rotate) pin.transform(self.offset, self.mirror, self.rotate)
return pin return pin
else: else:
pins = copy.deepcopy(self.mod.get_pin(name)) pins = copy.deepcopy(self.mod.get_pin(name))
pin.transform(self.offset,self.mirror,self.rotate) pins.transform(self.offset, self.mirror, self.rotate)
return pin[index] return pin[index]
def get_num_pins(self, name): def get_num_pins(self, name):
""" Return the number of pins of a given name """ """ Return the number of pins of a given name """
return len(self.mod.get_pins(name)) return len(self.mod.get_pins(name))
def get_pins(self,name): def get_pins(self, name):
""" Return an absolute pin that is offset and transformed based on """ Return an absolute pin that is offset and transformed based on
this instance location. """ this instance location. """
@ -253,7 +266,7 @@ class instance(geometry):
new_pins = [] new_pins = []
for p in pin: for p in pin:
p.transform(self.offset,self.mirror,self.rotate) p.transform(self.offset, self.mirror, self.rotate)
new_pins.append(p) new_pins.append(p)
return new_pins return new_pins
@ -265,6 +278,7 @@ class instance(geometry):
""" override print function output """ """ override print function output """
return "( inst: " + self.name + " @" + str(self.offset) + " mod=" + self.mod.name + " " + self.mirror + " R=" + str(self.rotate) + ")" return "( inst: " + self.name + " @" + str(self.offset) + " mod=" + self.mod.name + " " + self.mirror + " R=" + str(self.rotate) + ")"
class path(geometry): class path(geometry):
"""Represents a Path""" """Represents a Path"""
@ -322,7 +336,7 @@ class label(geometry):
self.size = 0 self.size = 0
debug.info(4,"creating label " + self.text + " " + str(self.layerNumber) + " " + str(self.offset)) debug.info(4, "creating label " + self.text + " " + str(self.layerNumber) + " " + str(self.offset))
def gds_write_file(self, new_layout): def gds_write_file(self, new_layout):
"""Writes the text label to GDS""" """Writes the text label to GDS"""
@ -340,7 +354,7 @@ class label(geometry):
def __str__(self): def __str__(self):
""" override print function output """ """ override print function output """
return "label: " + self.text + " layer=" + str(self.layerNumber) + " purpose=" + str(self.layerPurpose) return "label: " + self.text + " layer=" + str(self.layerNumber) + " purpose=" + str(self.layerPurpose)
def __repr__(self): def __repr__(self):
""" override print function output """ """ override print function output """

View File

@ -7,11 +7,10 @@
# #
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
import tech
class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
""" """
@ -26,7 +25,12 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
# If we have a separate lvs directory, then all the lvs files # If we have a separate lvs directory, then all the lvs files
# should be in there (all or nothing!) # should be in there (all or nothing!)
lvs_dir = OPTS.openram_tech + "lvs_lib/" try:
lvs_subdir = tech.lvs_lib
except AttributeError:
lvs_subdir = "lvs_lib"
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:
@ -44,12 +48,13 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
if i.name == inst.name: if i.name == inst.name:
break break
else: else:
debug.error("Couldn't find instance {0}".format(inst_name), -1) debug.error("Couldn't find instance {0}".format(inst.name), -1)
inst_map = inst.mod.pin_map inst_map = inst.mod.pin_map
return inst_map return inst_map
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:
@ -89,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.
@ -112,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.
@ -180,7 +189,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
"""Given a list of nets, will compare the internal alias of a mod to determine """Given a list of nets, will compare the internal alias of a mod to determine
if the nets have a connection to this mod's net (but not inst). if the nets have a connection to this mod's net (but not inst).
""" """
if exclusion_set == None: if not exclusion_set:
exclusion_set = set() exclusion_set = set()
try: try:
self.name_dict self.name_dict

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
@ -42,27 +42,30 @@ class layout():
self.visited = [] # List of modules we have already visited self.visited = [] # List of modules we have already visited
self.is_library_cell = False # Flag for library cells self.is_library_cell = False # Flag for library cells
self.gds_read() self.gds_read()
try: try:
from tech import power_grid from tech import power_grid
self.pwr_grid_layer = power_grid[0] self.pwr_grid_layer = power_grid[0]
except ImportError: except ImportError:
self.pwr_grid_layer = "m3" self.pwr_grid_layer = "m3"
############################################################ ############################################################
# GDS layout # GDS layout
############################################################ ############################################################
def offset_all_coordinates(self): def offset_all_coordinates(self):
""" This function is called after everything is placed to """
shift the origin in the lowest left corner """ This function is called after everything is placed to
shift the origin in the lowest left corner
"""
offset = self.find_lowest_coords() offset = self.find_lowest_coords()
self.translate_all(offset) self.translate_all(offset)
return offset return offset
def get_gate_offset(self, x_offset, height, inv_num): def get_gate_offset(self, x_offset, height, inv_num):
"""Gets the base offset and y orientation of stacked rows of gates """
Gets the base offset and y orientation of stacked rows of gates
assuming a minwidth metal1 vdd/gnd rail. Input is which gate assuming a minwidth metal1 vdd/gnd rail. Input is which gate
in the stack from 0..n in the stack from 0..n
""" """
@ -120,6 +123,7 @@ class layout():
highesty2 = max(inst.uy() for inst in self.insts) highesty2 = max(inst.uy() for inst in self.insts)
else: else:
highestx2 = highesty2 = None highestx2 = highesty2 = None
if highestx1 == None and highestx2 == None: if highestx1 == None and highestx2 == None:
return None return None
elif highestx1 == None: elif highestx1 == None:
@ -188,7 +192,7 @@ class layout():
inst.offset = vector(inst.offset - offset) inst.offset = vector(inst.offset - offset)
# The instances have a precomputed boundary that we need to update. # The instances have a precomputed boundary that we need to update.
if inst.__class__.__name__ == "instance": if inst.__class__.__name__ == "instance":
inst.compute_boundary(inst.offset) inst.compute_boundary(inst.offset, inst.mirror, inst.rotate)
for pin_name in self.pin_map.keys(): for pin_name in self.pin_map.keys():
# All the pins are absolute coordinates that need to be updated. # All the pins are absolute coordinates that need to be updated.
pin_list = self.pin_map[pin_name] pin_list = self.pin_map[pin_name]
@ -219,8 +223,6 @@ class layout():
if not height: if not height:
height = drc["minwidth_{}".format(layer)] height = drc["minwidth_{}".format(layer)]
lpp = techlayer[layer] lpp = techlayer[layer]
if abs(offset[0]-5.16250)<0.01 and abs(offset[1]-8.70750)<0.01:
import pdb; pdb.set_trace()
self.objs.append(geometry.rectangle(lpp, self.objs.append(geometry.rectangle(lpp,
offset, offset,
width, width,
@ -244,27 +246,46 @@ class layout():
height)) height))
return self.objs[-1] return self.objs[-1]
def add_segment_center(self, layer, start, end): def add_segment_center(self, layer, start, end, width=None):
""" """
Add a min-width rectanglular segment using center Add a min-width rectanglular segment using center
line on the start to end point line on the start to end point
""" """
minwidth_layer = drc["minwidth_{}".format(layer)] if not width:
width = drc["minwidth_{}".format(layer)]
if start.x != end.x and start.y != end.y: if start.x != end.x and start.y != end.y:
debug.error("Nonrectilinear center rect!", -1) debug.error("Nonrectilinear center rect!", -1)
elif start.x != end.x: elif start.x != end.x:
offset = vector(0, 0.5 * minwidth_layer) offset = vector(0, 0.5 * width)
return self.add_rect(layer, return self.add_rect(layer,
start - offset, start - offset,
end.x - start.x, end.x - start.x,
minwidth_layer) width)
else: else:
offset = vector(0.5 * minwidth_layer, 0) offset = vector(0.5 * width, 0)
return self.add_rect(layer, return self.add_rect(layer,
start - offset, start - offset,
minwidth_layer, 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
@ -322,7 +343,7 @@ class layout():
for pin_name in self.pin_map.keys(): for pin_name in self.pin_map.keys():
self.copy_layout_pin(instance, pin_name, prefix + pin_name) self.copy_layout_pin(instance, pin_name, prefix + pin_name)
def add_layout_pin_segment_center(self, text, layer, start, end): def add_layout_pin_segment_center(self, text, layer, start, end, width=None):
""" """
Creates a path like pin with center-line convention Creates a path like pin with center-line convention
""" """
@ -331,27 +352,27 @@ class layout():
self.gds_write(file_name) self.gds_write(file_name)
debug.error("Cannot have a non-manhatten layout pin: {}".format(file_name), -1) debug.error("Cannot have a non-manhatten layout pin: {}".format(file_name), -1)
minwidth_layer = drc["minwidth_{}".format(layer)] if not width:
layer_width = drc["minwidth_{}".format(layer)]
else:
layer_width = width
# one of these will be zero # one of these will be zero
width = max(start.x, end.x) - min(start.x, end.x) bbox_width = max(start.x, end.x) - min(start.x, end.x)
height = max(start.y, end.y) - min(start.y, end.y) bbox_height = max(start.y, end.y) - min(start.y, end.y)
ll_offset = vector(min(start.x, end.x), min(start.y, end.y)) ll_offset = vector(min(start.x, end.x), min(start.y, end.y))
# Shift it down 1/2 a width in the 0 dimension # Shift it down 1/2 a width in the 0 dimension
if height == 0: if bbox_height == 0:
ll_offset -= vector(0, 0.5 * minwidth_layer) ll_offset -= vector(0, 0.5 * layer_width)
if width == 0: if bbox_width == 0:
ll_offset -= vector(0.5 * minwidth_layer, 0) ll_offset -= vector(0.5 * layer_width, 0)
# This makes sure it is long enough, but also it is not 0 width!
height = max(minwidth_layer, height)
width = max(minwidth_layer, width)
return self.add_layout_pin(text, return self.add_layout_pin(text=text,
layer, layer=layer,
ll_offset, offset=ll_offset,
width, width=bbox_width,
height) height=bbox_height)
def add_layout_pin_rect_center(self, text, layer, offset, width=None, height=None): def add_layout_pin_rect_center(self, text, layer, offset, width=None, height=None):
""" Creates a path like pin with center-line convention """ """ Creates a path like pin with center-line convention """
@ -448,24 +469,31 @@ class layout():
path=coordinates, path=coordinates,
layer_widths=layer_widths) layer_widths=layer_widths)
def add_zjog(self, layer, start, end, first_direction="H"): def add_zjog(self, layer, start, end, first_direction="H", var_offset=0.5, fixed_offset=None):
""" """
Add a simple jog at the halfway point. Add a simple jog at the halfway point.
If layer is a single value, it is a path. If layer is a single value, it is a path.
If layer is a tuple, it is a wire with preferred directions. If layer is a tuple, it is a wire with preferred directions.
""" """
neg_offset = 1.0 - var_offset
# vertical first # vertical first
if first_direction == "V": if first_direction == "V":
mid1 = vector(start.x, 0.5 * start.y + 0.5 * end.y) if fixed_offset:
mid1 = vector(start.x, fixed_offset)
else:
mid1 = vector(start.x, neg_offset * start.y + var_offset * end.y)
mid2 = vector(end.x, mid1.y) mid2 = vector(end.x, mid1.y)
# horizontal first # horizontal first
elif first_direction == "H": elif first_direction == "H":
mid1 = vector(0.5 * start.x + 0.5 * end.x, start.y) if fixed_offset:
mid1 = vector(fixed_offset, start.y)
else:
mid1 = vector(neg_offset * start.x + var_offset * end.x, start.y)
mid2 = vector(mid1, end.y) mid2 = vector(mid1, end.y)
else: else:
debug.error("Invalid direction for jog -- must be H or V.") debug.error("Invalid direction for jog -- must be H or V.")
if layer in layer_stacks: if layer in layer_stacks:
self.add_wire(layer, [start, mid1, mid2, end]) self.add_wire(layer, [start, mid1, mid2, end])
elif layer in techlayer: elif layer in techlayer:
@ -480,7 +508,7 @@ class layout():
mid1 = vector(0.5 * start.x + 0.5 * end.x, start.y) mid1 = vector(0.5 * start.x + 0.5 * end.x, start.y)
mid2 = vector(mid1, end.y) mid2 = vector(mid1, end.y)
self.add_path(layer, [start, mid1, mid2, end]) self.add_path(layer, [start, mid1, mid2, end])
def add_wire(self, layers, coordinates, widen_short_wires=True): def add_wire(self, layers, coordinates, widen_short_wires=True):
"""Connects a routing path on given layer,coordinates,width. """Connects a routing path on given layer,coordinates,width.
The layers are the (horizontal, via, vertical). """ The layers are the (horizontal, via, vertical). """
@ -494,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):
@ -540,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,
@ -567,60 +576,74 @@ 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:
return last_via # In the case where we have no vias added, make sure that there is at least
# a metal enclosure. This helps with center-line path routing.
self.add_rect_center(layer=from_layer,
offset=offset)
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."""
import ptx import ptx
@ -688,12 +711,19 @@ class layout():
# we should add a boundary just for DRC in some technologies # we should add a boundary just for DRC in some technologies
if not self.is_library_cell and not self.bounding_box: if not self.is_library_cell and not self.bounding_box:
# If there is a boundary layer, and we didn't create one, add one. # If there is a boundary layer, and we didn't create one, add one.
boundary_layers = []
if "boundary" in techlayer.keys():
boundary_layers.append("boundary")
if "stdc" in techlayer.keys(): if "stdc" in techlayer.keys():
boundary_layer = "stdc" boundary_layers.append("stdc")
boundary = [self.find_lowest_coords(), boundary = [self.find_lowest_coords(),
self.find_highest_coords()] self.find_highest_coords()]
height = boundary[1][1] - boundary[0][1] debug.check(boundary[0] and boundary[1], "No shapes to make a boundary.")
width = boundary[1][0] - boundary[0][0]
height = boundary[1][1] - boundary[0][1]
width = boundary[1][0] - boundary[0][0]
for boundary_layer in boundary_layers:
(layer_number, layer_purpose) = techlayer[boundary_layer] (layer_number, layer_purpose) = techlayer[boundary_layer]
gds_layout.addBox(layerNumber=layer_number, gds_layout.addBox(layerNumber=layer_number,
purposeNumber=layer_purpose, purposeNumber=layer_purpose,
@ -701,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)
@ -827,51 +857,57 @@ class layout():
if not pitch: if not pitch:
pitch = getattr(self, "{}_pitch".format(layer)) pitch = getattr(self, "{}_pitch".format(layer))
line_positions = {} pins = {}
if vertical: if vertical:
for i in range(len(names)): for i in range(len(names)):
line_offset = offset + vector(i * pitch, line_offset = offset + vector(i * pitch,
0) 0)
if make_pins: if make_pins:
self.add_layout_pin(text=names[i], new_pin = self.add_layout_pin(text=names[i],
layer=layer, layer=layer,
offset=line_offset, offset=line_offset,
height=length) height=length)
else: else:
self.add_rect(layer=layer, rect = self.add_rect(layer=layer,
offset=line_offset, offset=line_offset,
height=length) height=length)
line_positions[names[i]] = line_offset + vector(half_minwidth, 0) new_pin = pin_layout(names[i],
[rect.ll(), rect.ur()],
layer)
pins[names[i]] = new_pin
else: else:
for i in range(len(names)): for i in range(len(names)):
line_offset = offset + vector(0, line_offset = offset + vector(0,
i * pitch + half_minwidth) i * pitch + half_minwidth)
if make_pins: if make_pins:
self.add_layout_pin(text=names[i], new_pin = self.add_layout_pin(text=names[i],
layer=layer, layer=layer,
offset=line_offset, offset=line_offset,
width=length) width=length)
else: else:
self.add_rect(layer=layer, rect = self.add_rect(layer=layer,
offset=line_offset, offset=line_offset,
width=length) width=length)
# Make this the center of the rail new_pin = pin_layout(names[i],
line_positions[names[i]] = line_offset + vector(0.5 * length, [rect.ll(), rect.ur()],
half_minwidth) layer)
pins[names[i]] = new_pin
return line_positions return pins
def connect_horizontal_bus(self, mapping, inst, bus_offsets, def connect_horizontal_bus(self, mapping, inst, bus_pins,
layer_stack=("m1", "via1", "m2")): layer_stack=("m1", "via1", "m2")):
""" Horizontal version of connect_bus. """ """ Horizontal version of connect_bus. """
self.connect_bus(mapping, inst, bus_offsets, layer_stack, True) self.connect_bus(mapping, inst, bus_pins, layer_stack, True)
def connect_vertical_bus(self, mapping, inst, bus_offsets, def connect_vertical_bus(self, mapping, inst, bus_pins,
layer_stack=("m1", "via1", "m2")): layer_stack=("m1", "via1", "m2")):
""" Vertical version of connect_bus. """ """ Vertical version of connect_bus. """
self.connect_bus(mapping, inst, bus_offsets, layer_stack, False) self.connect_bus(mapping, inst, bus_pins, layer_stack, False)
def connect_bus(self, mapping, inst, bus_offsets, layer_stack, horizontal): def connect_bus(self, mapping, inst, bus_pins, layer_stack, horizontal):
""" """
Connect a mapping of pin -> name for a bus. This could be Connect a mapping of pin -> name for a bus. This could be
replaced with a channel router in the future. replaced with a channel router in the future.
@ -881,13 +917,15 @@ 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)
pin_pos = pin.center() pin_pos = pin.center()
bus_pos = bus_offsets[bus_name] bus_pos = bus_pins[bus_name].center()
if horizontal: if horizontal:
# up/down then left/right # up/down then left/right
@ -904,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"):
""" """
@ -973,313 +1012,91 @@ class layout():
self.add_via_stack_center(from_layer=vlayer, self.add_via_stack_center(from_layer=vlayer,
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)
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)
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
if not directions:
# 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]
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:
return return
boundary_layers = []
if "stdc" in techlayer.keys(): if "stdc" in techlayer.keys():
boundary_layer = "stdc" boundary_layers.append("stdc")
else: if "boundary" in techlayer.keys():
boundary_layer = "boundary" boundary_layers.append("boundary")
if not ur: # Save the last one as self.bounding_box
self.bounding_box = self.add_rect(layer=boundary_layer, for boundary_layer in boundary_layers:
offset=ll, if not ur:
height=self.height, self.bounding_box = self.add_rect(layer=boundary_layer,
width=self.width) offset=ll,
else: height=self.height,
self.bounding_box = self.add_rect(layer=boundary_layer, width=self.width)
offset=ll, else:
height=ur.y - ll.y, self.bounding_box = self.add_rect(layer=boundary_layer,
width=ur.x - ll.x) offset=ll,
height=ur.y - ll.y,
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.
@ -1292,12 +1109,9 @@ class layout():
pin.ll(), pin.ll(),
pin.width(), pin.width(),
pin.height()) pin.height())
elif pin.layer == "m1":
self.add_power_pin(name, pin.center()) elif add_vias:
else: self.add_power_pin(name, pin.center(), start_layer=pin.layer)
debug.warning("{0} pins of {1} should be on {2} or metal1 for "\
"supply router."
.format(name, inst.name, self.pwr_grid_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"):
""" """
@ -1306,20 +1120,21 @@ class layout():
which vias are needed. which vias are needed.
""" """
via = self.add_via_stack_center(from_layer=start_layer,
to_layer=self.pwr_grid_layer,
size=size,
offset=loc,
directions=directions)
if start_layer == self.pwr_grid_layer: if start_layer == self.pwr_grid_layer:
self.add_layout_pin_rect_center(text=name, self.add_layout_pin_rect_center(text=name,
layer=self.pwr_grid_layer, layer=self.pwr_grid_layer,
offset=loc) offset=loc)
else: else:
via = self.add_via_stack_center(from_layer=start_layer,
to_layer=self.pwr_grid_layer,
size=size,
offset=loc,
directions=directions)
# Hack for min area # Hack for min area
if OPTS.tech_name == "s8": if OPTS.tech_name == "sky130":
width = round_to_grid(sqrt(drc["minarea_m3"])) width = round_to_grid(sqrt(drc["minarea_m3"]))
height = round_to_grid(drc["minarea_m3"]/width) height = round_to_grid(drc["minarea_m3"] / width)
else: else:
width = via.width width = via.width
height = via.height height = via.height
@ -1329,6 +1144,42 @@ class layout():
width=width, width=width,
height=height) height=height)
def add_perimeter_pin(self, name, pin, side, bbox):
"""
Add a pin along the perimeter side specified by the bbox with
the given name and layer from the pin starting location.
"""
(ll, ur) = bbox
left = ll.x
bottom = ll.y
right = ur.x
top = ur.y
pin_loc = pin.center()
if side == "left":
peri_pin_loc = vector(left, pin_loc.y)
layer = "m3"
elif side == "right":
layer = "m3"
peri_pin_loc = vector(right, pin_loc.y)
elif side == "top":
layer = "m4"
peri_pin_loc = vector(pin_loc.x, top)
elif side == "bottom":
layer = "m4"
peri_pin_loc = vector(pin_loc.x, bottom)
self.add_via_stack_center(from_layer=pin.layer,
to_layer=layer,
offset=pin_loc)
self.add_path(layer,
[pin_loc, peri_pin_loc])
return self.add_layout_pin_rect_center(text=name,
layer=layer,
offset=peri_pin_loc)
def add_power_ring(self, bbox): def add_power_ring(self, bbox):
""" """
Create vdd and gnd power rings around an area of the bounding box Create vdd and gnd power rings around an area of the bounding box

View File

@ -10,9 +10,9 @@ import re
import os import os
import math import math
import tech import tech
from delay_data import * from delay_data import delay_data
from wire_spice_model import * from wire_spice_model import wire_spice_model
from power_data import * from power_data import power_data
import logical_effort import logical_effort
@ -40,6 +40,8 @@ class spice():
# THE CONNECTIONS MUST MATCH THE ORDER OF THE PINS (restriction imposed by the # THE CONNECTIONS MUST MATCH THE ORDER OF THE PINS (restriction imposed by the
# Spice format) # Spice format)
self.conns = [] self.conns = []
# If this is set, it will out output subckt or isntances of this (for row/col caps etc.)
self.no_instances = False
# Keep track of any comments to add the the spice # Keep track of any comments to add the the spice
try: try:
self.commments self.commments
@ -208,11 +210,12 @@ class spice():
# parses line into ports and remove subckt # parses line into ports and remove subckt
self.pins = subckt_line.split(" ")[2:] self.pins = subckt_line.split(" ")[2:]
else: else:
debug.info(4, "no spfile {0}".format(self.sp_file))
self.spice = [] self.spice = []
# We don't define self.lvs and will use self.spice if dynamically created # We don't define self.lvs and will use self.spice if dynamically created
# or they are the same file # or they are the same file
if self.lvs_file!=self.sp_file and os.path.isfile(self.lvs_file): if self.lvs_file != self.sp_file and os.path.isfile(self.lvs_file):
debug.info(3, "opening {0}".format(self.lvs_file)) debug.info(3, "opening {0}".format(self.lvs_file))
f = open(self.lvs_file) f = open(self.lvs_file)
self.lvs = f.readlines() self.lvs = f.readlines()
@ -262,7 +265,12 @@ class spice():
Recursive spice subcircuit write; Recursive spice subcircuit write;
Writes the spice subcircuit from the library or the dynamically generated one Writes the spice subcircuit from the library or the dynamically generated one
""" """
if not self.spice:
if self.no_instances:
return
elif not self.spice:
# If spice isn't defined, we dynamically generate one.
# recursively write the modules # recursively write the modules
for i in self.mods: for i in self.mods:
if self.contains(i, usedMODS): if self.contains(i, usedMODS):
@ -299,6 +307,9 @@ class spice():
# these are wires and paths # these are wires and paths
if self.conns[i] == []: if self.conns[i] == []:
continue continue
# Instance with no devices in it needs no subckt/instance
if self.insts[i].mod.no_instances:
continue
if lvs_netlist and hasattr(self.insts[i].mod, "lvs_device"): if lvs_netlist and hasattr(self.insts[i].mod, "lvs_device"):
sp.write(self.insts[i].mod.lvs_device.format(self.insts[i].name, sp.write(self.insts[i].mod.lvs_device.format(self.insts[i].name,
" ".join(self.conns[i]))) " ".join(self.conns[i])))
@ -315,7 +326,7 @@ class spice():
sp.write(".ENDS {0}\n".format(self.name)) sp.write(".ENDS {0}\n".format(self.name))
else: else:
# write the subcircuit itself # If spice is a hard module, output the spice file contents.
# Including the file path makes the unit test fail for other users. # Including the file path makes the unit test fail for other users.
# if os.path.isfile(self.sp_file): # if os.path.isfile(self.sp_file):
# sp.write("\n* {0}\n".format(self.sp_file)) # sp.write("\n* {0}\n".format(self.sp_file))
@ -355,7 +366,7 @@ class spice():
stage_effort = self.get_stage_effort(relative_cap) stage_effort = self.get_stage_effort(relative_cap)
# If it fails, then keep running with a valid object. # If it fails, then keep running with a valid object.
if stage_effort == None: if not stage_effort:
return delay_data(0.0, 0.0) return delay_data(0.0, 0.0)
abs_delay = stage_effort.get_absolute_delay() abs_delay = stage_effort.get_absolute_delay()

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):
@ -367,8 +370,17 @@ class pin_layout:
+ str(self.width()) + "x" + str(self.width()) + "x"
+ str(self.height()) + " @ " + str(self.ll())) + str(self.height()) + " @ " + str(self.ll()))
(layer_num, purpose) = layer[self.layer] (layer_num, purpose) = layer[self.layer]
try:
from tech import pin_purpose
except ImportError:
pin_purpose = purpose
try:
from tech import label_purpose
except ImportError:
label_purpose = purpose
newLayout.addBox(layerNumber=layer_num, newLayout.addBox(layerNumber=layer_num,
purposeNumber=purpose, purposeNumber=pin_purpose,
offsetInMicrons=self.ll(), offsetInMicrons=self.ll(),
width=self.width(), width=self.width(),
height=self.height(), height=self.height(),
@ -378,7 +390,7 @@ class pin_layout:
# imported into Magic. # imported into Magic.
newLayout.addText(text=self.name, newLayout.addText(text=self.name,
layerNumber=layer_num, layerNumber=layer_num,
purposeNumber=purpose, purposeNumber=label_purpose,
offsetInMicrons=self.center(), offsetInMicrons=self.center(),
magnification=GDS["zoom"], magnification=GDS["zoom"],
rotate=None) rotate=None)

View File

@ -7,10 +7,10 @@
# #
import debug import debug
import utils import utils
from tech import GDS, layer, parameter from tech import GDS, layer
from tech import cell_properties as props from tech import cell_properties as props
import bitcell_base import bitcell_base
from globals import OPTS
class bitcell(bitcell_base.bitcell_base): class bitcell(bitcell_base.bitcell_base):
""" """
@ -50,6 +50,8 @@ class bitcell(bitcell_base.bitcell_base):
self.pin_map = bitcell.pin_map self.pin_map = bitcell.pin_map
self.add_pin_types(self.type_list) self.add_pin_types(self.type_list)
self.nets_match = self.do_nets_exist(self.storage_nets) self.nets_match = self.do_nets_exist(self.storage_nets)
debug.check(OPTS.tech_name != "sky130", "sky130 does not yet support single port cells")
def get_all_wl_names(self): def get_all_wl_names(self):
""" Creates a list of all wordline pin names """ """ Creates a list of all wordline pin names """

View File

@ -0,0 +1,44 @@
# 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 debug
import utils
from tech import GDS, layer
from tech import cell_properties as props
import bitcell_base
class col_cap_bitcell_1rw_1r(bitcell_base.bitcell_base):
"""
todo"""
pin_names = [props.bitcell.cell_1rw1r.pin.bl0,
props.bitcell.cell_1rw1r.pin.br0,
props.bitcell.cell_1rw1r.pin.bl1,
props.bitcell.cell_1rw1r.pin.br1,
props.bitcell.cell_1rw1r.pin.vdd]
type_list = ["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT",
"POWER", "GROUND"]
(width, height) = utils.get_libcell_size("col_cap_cell_1rw_1r",
GDS["unit"],
layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names,
"col_cap_cell_1rw_1r",
GDS["unit"])
def __init__(self, name=""):
# Ignore the name argument
bitcell_base.bitcell_base.__init__(self, "col_cap_cell_1rw_1r")
debug.info(2, "Create col_cap bitcell 1rw+1r object")
self.width = col_cap_bitcell_1rw_1r.width
self.height = col_cap_bitcell_1rw_1r.height
self.pin_map = col_cap_bitcell_1rw_1r.pin_map
self.add_pin_types(self.type_list)
self.no_instances = True

View File

@ -0,0 +1,44 @@
# 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 debug
import utils
from tech import GDS, layer
from tech import cell_properties as props
import bitcell_base
class row_cap_bitcell_1rw_1r(bitcell_base.bitcell_base):
"""
A single bit cell which is forced to store a 0.
This module implements the single memory cell used in the design. It
is a hand-made cell, so the layout and netlist should be available in
the technology library. """
pin_names = [props.bitcell.cell_1rw1r.pin.wl0,
props.bitcell.cell_1rw1r.pin.wl1,
props.bitcell.cell_1rw1r.pin.gnd]
type_list = ["INPUT", "INPUT", "GROUND"]
(width, height) = utils.get_libcell_size("row_cap_cell_1rw_1r",
GDS["unit"],
layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names,
"row_cap_cell_1rw_1r",
GDS["unit"])
def __init__(self, name=""):
# Ignore the name argument
bitcell_base.bitcell_base.__init__(self, "row_cap_cell_1rw_1r")
debug.info(2, "Create row_cap bitcell 1rw+1r object")
self.width = row_cap_bitcell_1rw_1r.width
self.height = row_cap_bitcell_1rw_1r.height
self.pin_map = row_cap_bitcell_1rw_1r.pin_map
self.add_pin_types(self.type_list)
self.no_instances = True

View File

@ -1077,7 +1077,8 @@ class delay(simulation):
self.trimsp.set_configuration(self.num_banks, self.trimsp.set_configuration(self.num_banks,
self.num_rows, self.num_rows,
self.num_cols, self.num_cols,
self.word_size) self.word_size,
self.num_spare_rows)
self.trimsp.trim(self.probe_address,self.probe_data) self.trimsp.trim(self.probe_address,self.probe_data)
else: else:
# The non-reduced netlist file when it is disabled # The non-reduced netlist file when it is disabled

View File

@ -5,23 +5,18 @@
# (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 sys,re,shutil
import copy
import collections import collections
from design import design
import debug import debug
import math
import tech
import random import random
from .stimuli import * from .stimuli import *
from .charutils import * from .charutils import *
import utils
from globals import OPTS from globals import OPTS
from .simulation import simulation from .simulation import simulation
from .delay import delay # from .delay import delay
import graph_util import graph_util
from sram_factory import factory from sram_factory import factory
class functional(simulation): class functional(simulation):
""" """
Functions to write random data values to a random address then read them back and check Functions to write random data values to a random address then read them back and check
@ -40,6 +35,9 @@ class functional(simulation):
else: else:
self.num_wmasks = 0 self.num_wmasks = 0
if not self.num_spare_cols:
self.num_spare_cols = 0
self.set_corner(corner) self.set_corner(corner)
self.set_spice_constants() self.set_spice_constants()
self.set_stimulus_variables() self.set_stimulus_variables()
@ -57,7 +55,6 @@ class functional(simulation):
self.read_check = [] self.read_check = []
self.read_results = [] self.read_results = []
def run(self, feasible_period=None): def run(self, feasible_period=None):
if feasible_period: #period defaults to tech.py feasible period otherwise. if feasible_period: #period defaults to tech.py feasible period otherwise.
self.period = feasible_period self.period = feasible_period
@ -82,10 +79,11 @@ class functional(simulation):
for port in self.all_ports: for port in self.all_ports:
checks = [] checks = []
if port in self.read_ports: if port in self.read_ports:
checks.append((self.addr_value[port],"addr")) checks.append((self.addr_value[port], "addr"))
if port in self.write_ports: if port in self.write_ports:
checks.append((self.data_value[port],"data")) checks.append((self.data_value[port], "data"))
checks.append((self.wmask_value[port],"wmask")) checks.append((self.wmask_value[port], "wmask"))
checks.append((self.spare_wen_value[port], "spare_wen"))
for (val, name) in checks: for (val, name) in checks:
debug.check(len(self.cycle_times)==len(val), debug.check(len(self.cycle_times)==len(val),
@ -104,15 +102,15 @@ class functional(simulation):
r_ops = ["noop", "read"] r_ops = ["noop", "read"]
# First cycle idle is always an idle cycle # First cycle idle is always an idle cycle
comment = self.gen_cycle_comment("noop", "0"*self.word_size, "0"*self.addr_size, "0"*self.num_wmasks, 0, self.t_current) comment = self.gen_cycle_comment("noop", "0" * self.word_size, "0" * self.addr_size, "0" * self.num_wmasks, 0, self.t_current)
self.add_noop_all_ports(comment) self.add_noop_all_ports(comment)
# 1. Write all the write ports first to seed a bunch of locations. # 1. Write all the write ports first to seed a bunch of locations.
for port in self.write_ports: for port in self.write_ports:
addr = self.gen_addr() addr = self.gen_addr()
word = self.gen_data() word = self.gen_data()
comment = self.gen_cycle_comment("write", word, addr, "1"*self.num_wmasks, port, self.t_current) comment = self.gen_cycle_comment("write", word, addr, "1" * self.num_wmasks, port, self.t_current)
self.add_write_one_port(comment, addr, word, "1"*self.num_wmasks, port) self.add_write_one_port(comment, addr, word, "1" * self.num_wmasks, port)
self.stored_words[addr] = word self.stored_words[addr] = word
# All other read-only ports are noops. # All other read-only ports are noops.
@ -131,7 +129,7 @@ class functional(simulation):
if port in self.write_ports: if port in self.write_ports:
self.add_noop_one_port(port) self.add_noop_one_port(port)
else: else:
comment = self.gen_cycle_comment("read", word, addr, "0"*self.num_wmasks, port, self.t_current) comment = self.gen_cycle_comment("read", word, addr, "0" * self.num_wmasks, port, self.t_current)
self.add_read_one_port(comment, addr, port) self.add_read_one_port(comment, addr, port)
self.add_read_check(word, port) self.add_read_check(word, port)
self.cycle_times.append(self.t_current) self.cycle_times.append(self.t_current)
@ -160,13 +158,13 @@ class functional(simulation):
self.add_noop_one_port(port) self.add_noop_one_port(port)
else: else:
word = self.gen_data() word = self.gen_data()
comment = self.gen_cycle_comment("write", word, addr, "1"*self.num_wmasks, port, self.t_current) comment = self.gen_cycle_comment("write", word, addr, "1" * self.num_wmasks, port, self.t_current)
self.add_write_one_port(comment, addr, word, "1"*self.num_wmasks, port) self.add_write_one_port(comment, addr, word, "1" * self.num_wmasks, port)
self.stored_words[addr] = word self.stored_words[addr] = word
w_addrs.append(addr) w_addrs.append(addr)
elif op == "partial_write": elif op == "partial_write":
# write only to a word that's been written to # write only to a word that's been written to
(addr,old_word) = self.get_data() (addr, old_word) = self.get_data()
# two ports cannot write to the same address # two ports cannot write to the same address
if addr in w_addrs: if addr in w_addrs:
self.add_noop_one_port(port) self.add_noop_one_port(port)
@ -179,7 +177,7 @@ class functional(simulation):
self.stored_words[addr] = new_word self.stored_words[addr] = new_word
w_addrs.append(addr) w_addrs.append(addr)
else: else:
(addr,word) = random.choice(list(self.stored_words.items())) (addr, word) = random.choice(list(self.stored_words.items()))
# The write driver is not sized sufficiently to drive through the two # The write driver is not sized sufficiently to drive through the two
# bitcell access transistors to the read port. So, for now, we do not allow # bitcell access transistors to the read port. So, for now, we do not allow
# a simultaneous write and read to the same address on different ports. This # a simultaneous write and read to the same address on different ports. This
@ -187,7 +185,7 @@ class functional(simulation):
if addr in w_addrs: if addr in w_addrs:
self.add_noop_one_port(port) self.add_noop_one_port(port)
else: else:
comment = self.gen_cycle_comment("read", word, addr, "0"*self.num_wmasks, port, self.t_current) comment = self.gen_cycle_comment("read", word, addr, "0" * self.num_wmasks, port, self.t_current)
self.add_read_one_port(comment, addr, port) self.add_read_one_port(comment, addr, port)
self.add_read_check(word, port) self.add_read_check(word, port)
@ -195,7 +193,7 @@ class functional(simulation):
self.t_current += self.period self.t_current += self.period
# Last cycle idle needed to correctly measure the value on the second to last clock edge # Last cycle idle needed to correctly measure the value on the second to last clock edge
comment = self.gen_cycle_comment("noop", "0"*self.word_size, "0"*self.addr_size, "0"*self.num_wmasks, 0, self.t_current) comment = self.gen_cycle_comment("noop", "0" * self.word_size, "0" * self.addr_size, "0" * self.num_wmasks, 0, self.t_current)
self.add_noop_all_ports(comment) self.add_noop_all_ports(comment)
def gen_masked_data(self, old_word, word, wmask): def gen_masked_data(self, old_word, word, wmask):
@ -209,7 +207,7 @@ class functional(simulation):
if wmask[bit] == "0": if wmask[bit] == "0":
lower = bit * self.write_size lower = bit * self.write_size
upper = lower + self.write_size - 1 upper = lower + self.write_size - 1
new_word = new_word[:lower] + old_word[lower:upper+1] + new_word[upper + 1:] new_word = new_word[:lower] + old_word[lower:upper + 1] + new_word[upper + 1:]
return new_word return new_word
@ -219,15 +217,15 @@ class functional(simulation):
self.check self.check
except: except:
self.check = 0 self.check = 0
self.read_check.append([word, "{0}{1}".format(self.dout_name,port), self.t_current+self.period, self.check]) self.read_check.append([word, "{0}{1}".format(self.dout_name, port), self.t_current + self.period, self.check])
self.check += 1 self.check += 1
def read_stim_results(self): def read_stim_results(self):
# Extract dout values from spice timing.lis # Extract dout values from spice timing.lis
for (word, dout_port, eo_period, check) in self.read_check: for (word, dout_port, eo_period, check) in self.read_check:
sp_read_value = "" sp_read_value = ""
for bit in range(self.word_size): for bit in range(self.word_size + self.num_spare_cols):
value = parse_spice_list("timing", "v{0}.{1}ck{2}".format(dout_port.lower(),bit,check)) value = parse_spice_list("timing", "v{0}.{1}ck{2}".format(dout_port.lower(), bit, check))
if value > self.v_high: if value > self.v_high:
sp_read_value = "1" + sp_read_value sp_read_value = "1" + sp_read_value
elif value < self.v_low: elif value < self.v_low:
@ -278,17 +276,24 @@ class functional(simulation):
# wmask must be reversed since a python list goes right to left and sram bits go left to right. # wmask must be reversed since a python list goes right to left and sram bits go left to right.
return wmask[::-1] return wmask[::-1]
def gen_data(self): def gen_data(self):
""" Generates a random word to write. """ """ Generates a random word to write. """
random_value = random.randint(0,(2**self.word_size)-1) if not self.num_spare_cols:
data_bits = self.convert_to_bin(random_value,False) random_value = random.randint(0, (2 ** self.word_size) - 1)
else:
random_value1 = random.randint(0, (2 ** self.word_size) - 1)
random_value2 = random.randint(0, (2 ** self.num_spare_cols) - 1)
random_value = random_value1 + random_value2
data_bits = self.convert_to_bin(random_value, False)
return data_bits return data_bits
def gen_addr(self): def gen_addr(self):
""" Generates a random address value to write to. """ """ Generates a random address value to write to. """
random_value = random.randint(0,(2**self.addr_size)-1) if self.num_spare_rows==0:
addr_bits = self.convert_to_bin(random_value,True) random_value = random.randint(0, (2 ** self.addr_size) - 1)
else:
random_value = random.randint(0, ((2 ** (self.addr_size - 1) - 1)) + (self.num_spare_rows * self.words_per_row))
addr_bits = self.convert_to_bin(random_value, True)
return addr_bits return addr_bits
def get_data(self): def get_data(self):
@ -296,37 +301,36 @@ class functional(simulation):
# Used for write masks since they should be writing to previously written addresses # Used for write masks since they should be writing to previously written addresses
addr = random.choice(list(self.stored_words.keys())) addr = random.choice(list(self.stored_words.keys()))
word = self.stored_words[addr] word = self.stored_words[addr]
return (addr,word) return (addr, word)
def convert_to_bin(self,value,is_addr): def convert_to_bin(self, value, is_addr):
""" Converts addr & word to usable binary values. """ """ Converts addr & word to usable binary values. """
new_value = str.replace(bin(value),"0b","") new_value = str.replace(bin(value), "0b", "")
if(is_addr): if(is_addr):
expected_value = self.addr_size expected_value = self.addr_size
else: else:
expected_value = self.word_size + self.num_spare_cols
expected_value = self.word_size for i in range(expected_value - len(new_value)):
for i in range (expected_value - len(new_value)):
new_value = "0" + new_value new_value = "0" + new_value
#print("Binary Conversion: {} to {}".format(value, new_value)) # print("Binary Conversion: {} to {}".format(value, new_value))
return new_value return new_value
def write_functional_stimulus(self): def write_functional_stimulus(self):
""" Writes SPICE stimulus. """ """ Writes SPICE stimulus. """
temp_stim = "{0}/stim.sp".format(OPTS.openram_temp) temp_stim = "{0}/stim.sp".format(OPTS.openram_temp)
self.sf = open(temp_stim,"w") self.sf = open(temp_stim, "w")
self.sf.write("* Functional test stimulus file for {}ns period\n\n".format(self.period)) self.sf.write("* Functional test stimulus file for {}ns period\n\n".format(self.period))
self.stim = stimuli(self.sf,self.corner) self.stim = stimuli(self.sf, self.corner)
#Write include statements # Write include statements
self.stim.write_include(self.sp_file) self.stim.write_include(self.sp_file)
#Write Vdd/Gnd statements # Write Vdd/Gnd statements
self.sf.write("\n* Global Power Supplies\n") self.sf.write("\n* Global Power Supplies\n")
self.stim.write_supply() self.stim.write_supply()
#Instantiate the SRAM # Instantiate the SRAM
self.sf.write("\n* Instantiation of the SRAM\n") self.sf.write("\n* Instantiation of the SRAM\n")
self.stim.inst_model(pins=self.pins, self.stim.inst_model(pins=self.pins,
model_name=self.sram.name) model_name=self.sram.name)
@ -334,7 +338,7 @@ class functional(simulation):
# Add load capacitance to each of the read ports # Add load capacitance to each of the read ports
self.sf.write("\n* SRAM output loads\n") self.sf.write("\n* SRAM output loads\n")
for port in self.read_ports: for port in self.read_ports:
for bit in range(self.word_size): for bit in range(self.word_size + self.num_spare_cols):
sig_name="{0}{1}_{2} ".format(self.dout_name, port, bit) sig_name="{0}{1}_{2} ".format(self.dout_name, port, bit)
self.sf.write("CD{0}{1} {2} 0 {3}f\n".format(port, bit, sig_name, self.load)) self.sf.write("CD{0}{1} {2} 0 {3}f\n".format(port, bit, sig_name, self.load))
@ -351,10 +355,10 @@ class functional(simulation):
for comment in self.fn_cycle_comments: for comment in self.fn_cycle_comments:
self.sf.write("*{}\n".format(comment)) self.sf.write("*{}\n".format(comment))
# Generate data input bits # Generate data input bits
self.sf.write("\n* Generation of data and address signals\n") self.sf.write("\n* Generation of data and address signals\n")
for port in self.write_ports: for port in self.write_ports:
for bit in range(self.word_size): for bit in range(self.word_size + self.num_spare_cols):
sig_name="{0}{1}_{2} ".format(self.din_name, port, bit) sig_name="{0}{1}_{2} ".format(self.din_name, port, bit)
self.stim.gen_pwl(sig_name, self.cycle_times, self.data_values[port][bit], self.period, self.slew, 0.05) self.stim.gen_pwl(sig_name, self.cycle_times, self.data_values[port][bit], self.period, self.slew, 0.05)
@ -367,10 +371,10 @@ class functional(simulation):
# Generate control signals # Generate control signals
self.sf.write("\n * Generation of control signals\n") self.sf.write("\n * Generation of control signals\n")
for port in self.all_ports: for port in self.all_ports:
self.stim.gen_pwl("CSB{}".format(port), self.cycle_times , self.csb_values[port], self.period, self.slew, 0.05) self.stim.gen_pwl("CSB{}".format(port), self.cycle_times, self.csb_values[port], self.period, self.slew, 0.05)
for port in self.readwrite_ports: for port in self.readwrite_ports:
self.stim.gen_pwl("WEB{}".format(port), self.cycle_times , self.web_values[port], self.period, self.slew, 0.05) self.stim.gen_pwl("WEB{}".format(port), self.cycle_times, self.web_values[port], self.period, self.slew, 0.05)
# Generate wmask bits # Generate wmask bits
for port in self.write_ports: for port in self.write_ports:
@ -383,6 +387,15 @@ class functional(simulation):
self.stim.gen_pwl(sig_name, self.cycle_times, self.wmask_values[port][bit], self.period, self.stim.gen_pwl(sig_name, self.cycle_times, self.wmask_values[port][bit], self.period,
self.slew, 0.05) self.slew, 0.05)
# Generate spare enable bits (for spare cols)
for port in self.write_ports:
if self.num_spare_cols:
self.sf.write("\n* Generation of spare enable signals\n")
for bit in range(self.num_spare_cols):
sig_name = "SPARE_WEN{0}_{1} ".format(port, bit)
self.stim.gen_pwl(sig_name, self.cycle_times, self.spare_wen_values[port][bit], self.period,
self.slew, 0.05)
# Generate CLK signals # Generate CLK signals
for port in self.all_ports: for port in self.all_ports:
self.stim.gen_pulse(sig_name="{0}{1}".format("clk", port), self.stim.gen_pulse(sig_name="{0}{1}".format("clk", port),
@ -396,11 +409,11 @@ class functional(simulation):
# Generate dout value measurements # Generate dout value measurements
self.sf.write("\n * Generation of dout measurements\n") self.sf.write("\n * Generation of dout measurements\n")
for (word, dout_port, eo_period, check) in self.read_check: for (word, dout_port, eo_period, check) in self.read_check:
t_intital = eo_period - 0.01*self.period t_intital = eo_period - 0.01 * self.period
t_final = eo_period + 0.01*self.period t_final = eo_period + 0.01 * self.period
for bit in range(self.word_size): for bit in range(self.word_size + self.num_spare_cols):
self.stim.gen_meas_value(meas_name="V{0}_{1}ck{2}".format(dout_port,bit,check), self.stim.gen_meas_value(meas_name="V{0}_{1}ck{2}".format(dout_port, bit, check),
dout="{0}_{1}".format(dout_port,bit), dout="{0}_{1}".format(dout_port, bit),
t_intital=t_intital, t_intital=t_intital,
t_final=t_final) t_final=t_final)
@ -430,7 +443,7 @@ class functional(simulation):
# Generate new graph every analysis as edges might change depending on test bit # Generate new graph every analysis as edges might change depending on test bit
self.graph = graph_util.timing_graph() self.graph = graph_util.timing_graph()
self.sram_spc_name = "X{}".format(self.sram.name) self.sram_spc_name = "X{}".format(self.sram.name)
self.sram.build_graph(self.graph,self.sram_spc_name,self.pins) self.sram.build_graph(self.graph, self.sram_spc_name, self.pins)
# FIXME: refactor to share with delay.py # FIXME: refactor to share with delay.py
def set_internal_spice_names(self): def set_internal_spice_names(self):
@ -438,17 +451,17 @@ class functional(simulation):
# For now, only testing these using first read port. # For now, only testing these using first read port.
port = self.read_ports[0] port = self.read_ports[0]
self.graph.get_all_paths('{}{}'.format("clk", port), self.graph.get_all_paths('{}{}'.format("clk", port),
'{}{}_{}'.format(self.dout_name, port, 0).lower()) '{}{}_{}'.format(self.dout_name, port, 0).lower())
self.sen_name = self.get_sen_name(self.graph.all_paths) self.sen_name = self.get_sen_name(self.graph.all_paths)
debug.info(2,"s_en name = {}".format(self.sen_name)) debug.info(2, "s_en name = {}".format(self.sen_name))
self.bl_name,self.br_name = self.get_bl_name(self.graph.all_paths, port) self.bl_name, self.br_name = self.get_bl_name(self.graph.all_paths, port)
debug.info(2,"bl name={}, br name={}".format(self.bl_name,self.br_name)) debug.info(2, "bl name={}, br name={}".format(self.bl_name, self.br_name))
self.q_name,self.qbar_name = self.get_bit_name() self.q_name, self.qbar_name = self.get_bit_name()
debug.info(2,"q name={}\nqbar name={}".format(self.q_name,self.qbar_name)) debug.info(2, "q name={}\nqbar name={}".format(self.q_name, self.qbar_name))
def get_bit_name(self): def get_bit_name(self):
""" Get a bit cell name """ """ Get a bit cell name """
@ -456,10 +469,10 @@ class functional(simulation):
storage_names = cell_inst.mod.get_storage_net_names() storage_names = cell_inst.mod.get_storage_net_names()
debug.check(len(storage_names) == 2, ("Only inverting/non-inverting storage nodes" debug.check(len(storage_names) == 2, ("Only inverting/non-inverting storage nodes"
"supported for characterization. Storage nets={}").format(storage_names)) "supported for characterization. Storage nets={}").format(storage_names))
q_name = cell_name+'.'+str(storage_names[0]) q_name = cell_name + '.' + str(storage_names[0])
qbar_name = cell_name+'.'+str(storage_names[1]) qbar_name = cell_name + '.' + str(storage_names[1])
return (q_name,qbar_name) return (q_name, qbar_name)
# FIXME: refactor to share with delay.py # FIXME: refactor to share with delay.py
def get_sen_name(self, paths): def get_sen_name(self, paths):
@ -469,29 +482,28 @@ class functional(simulation):
""" """
sa_mods = factory.get_mods(OPTS.sense_amp) sa_mods = factory.get_mods(OPTS.sense_amp)
# Any sense amp instantiated should be identical, any change to that # Any sense amp instantiated should be identical, any change to that
# will require some identification to determine the mod desired. # will require some identification to determine the mod desired.
debug.check(len(sa_mods) == 1, "Only expected one type of Sense Amp. Cannot perform s_en checks.") debug.check(len(sa_mods) == 1, "Only expected one type of Sense Amp. Cannot perform s_en checks.")
enable_name = sa_mods[0].get_enable_name() enable_name = sa_mods[0].get_enable_name()
sen_name = self.get_alias_in_path(paths, enable_name, sa_mods[0]) sen_name = self.get_alias_in_path(paths, enable_name, sa_mods[0])
return sen_name return sen_name
# FIXME: refactor to share with delay.py # FIXME: refactor to share with delay.py
def get_bl_name(self, paths, port): def get_bl_name(self, paths, port):
"""Gets the signal name associated with the bitlines in the bank.""" """Gets the signal name associated with the bitlines in the bank."""
cell_mod = factory.create(module_type=OPTS.bitcell) cell_mod = factory.create(module_type=OPTS.bitcell)
cell_bl = cell_mod.get_bl_name(port) cell_bl = cell_mod.get_bl_name(port)
cell_br = cell_mod.get_br_name(port) cell_br = cell_mod.get_br_name(port)
bl_found = False
# Only a single path should contain a single s_en name. Anything else is an error. # Only a single path should contain a single s_en name. Anything else is an error.
bl_names = [] bl_names = []
exclude_set = self.get_bl_name_search_exclusions() exclude_set = self.get_bl_name_search_exclusions()
for int_net in [cell_bl, cell_br]: for int_net in [cell_bl, cell_br]:
bl_names.append(self.get_alias_in_path(paths, int_net, cell_mod, exclude_set)) bl_names.append(self.get_alias_in_path(paths, int_net, cell_mod, exclude_set))
return bl_names[0], bl_names[1] return bl_names[0], bl_names[1]
def get_bl_name_search_exclusions(self): def get_bl_name_search_exclusions(self):
"""Gets the mods as a set which should be excluded while searching for name.""" """Gets the mods as a set which should be excluded while searching for name."""
@ -500,9 +512,9 @@ class functional(simulation):
# so it makes the search awkward # so it makes the search awkward
return set(factory.get_mods(OPTS.replica_bitline)) return set(factory.get_mods(OPTS.replica_bitline))
def get_alias_in_path(self, paths, int_net, mod, exclusion_set=None): def get_alias_in_path(self, paths, int_net, mod, exclusion_set=None):
""" """
Finds a single alias for the int_net in given paths. Finds a single alias for the int_net in given paths.
More or less hits cause an error More or less hits cause an error
""" """
@ -510,14 +522,14 @@ class functional(simulation):
for path in paths: for path in paths:
aliases = self.sram.find_aliases(self.sram_spc_name, self.pins, path, int_net, mod, exclusion_set) aliases = self.sram.find_aliases(self.sram_spc_name, self.pins, path, int_net, mod, exclusion_set)
if net_found and len(aliases) >= 1: if net_found and len(aliases) >= 1:
debug.error('Found multiple paths with {} net.'.format(int_net),1) debug.error('Found multiple paths with {} net.'.format(int_net), 1)
elif len(aliases) > 1: elif len(aliases) > 1:
debug.error('Found multiple {} nets in single path.'.format(int_net),1) debug.error('Found multiple {} nets in single path.'.format(int_net), 1)
elif not net_found and len(aliases) == 1: elif not net_found and len(aliases) == 1:
path_net_name = aliases[0] path_net_name = aliases[0]
net_found = True net_found = True
if not net_found: if not net_found:
debug.error("Could not find {} net in timing paths.".format(int_net),1) debug.error("Could not find {} net in timing paths.".format(int_net), 1)
return path_net_name return path_net_name

View File

@ -592,7 +592,10 @@ class lib:
char_results = self.d.analytical_delay(self.slews,self.loads) char_results = self.d.analytical_delay(self.slews,self.loads)
self.char_sram_results, self.char_port_results = char_results self.char_sram_results, self.char_port_results = char_results
else: else:
probe_address = "1" * self.sram.addr_size if (self.sram.num_spare_rows == 0):
probe_address = "1" * self.sram.addr_size
else:
probe_address = "0" + "1" * (self.sram.addr_size - 1)
probe_data = self.sram.word_size - 1 probe_data = self.sram.word_size - 1
char_results = self.d.analyze(probe_address, probe_data, self.slews, self.loads) char_results = self.d.analyze(probe_address, probe_data, self.slews, self.loads)
self.char_sram_results, self.char_port_results = char_results self.char_sram_results, self.char_port_results = char_results
@ -654,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

@ -25,18 +25,23 @@ class simulation():
self.word_size = self.sram.word_size self.word_size = self.sram.word_size
self.addr_size = self.sram.addr_size self.addr_size = self.sram.addr_size
self.write_size = self.sram.write_size self.write_size = self.sram.write_size
self.num_spare_rows = self.sram.num_spare_rows
if not self.sram.num_spare_cols:
self.num_spare_cols = 0
else:
self.num_spare_cols = self.sram.num_spare_cols
self.sp_file = spfile self.sp_file = spfile
self.all_ports = self.sram.all_ports self.all_ports = self.sram.all_ports
self.readwrite_ports = self.sram.readwrite_ports self.readwrite_ports = self.sram.readwrite_ports
self.read_ports = self.sram.read_ports self.read_ports = self.sram.read_ports
self.write_ports = self.sram.write_ports self.write_ports = self.sram.write_ports
self.words_per_row = self.sram.words_per_row
if self.write_size: if self.write_size:
self.num_wmasks = int(self.word_size/self.write_size) self.num_wmasks = int(self.word_size/self.write_size)
else: else:
self.num_wmasks = 0 self.num_wmasks = 0
def set_corner(self,corner): def set_corner(self,corner):
""" Set the corner values """ """ Set the corner values """
self.corner = corner self.corner = corner
@ -59,10 +64,10 @@ class simulation():
self.pins = self.gen_pin_names(port_signal_names=(self.addr_name,self.din_name,self.dout_name), self.pins = self.gen_pin_names(port_signal_names=(self.addr_name,self.din_name,self.dout_name),
port_info=(len(self.all_ports),self.write_ports,self.read_ports), port_info=(len(self.all_ports),self.write_ports,self.read_ports),
abits=self.addr_size, abits=self.addr_size,
dbits=self.word_size) dbits=self.word_size + self.num_spare_cols)
debug.check(len(self.sram.pins) == len(self.pins), debug.check(len(self.sram.pins) == len(self.pins),
"Number of pins generated for characterization \ "Number of pins generated for characterization \
do match pins of SRAM\nsram.pins = {0}\npin_names = {1}".format(self.sram.pins, do not match pins of SRAM\nsram.pins = {0}\npin_names = {1}".format(self.sram.pins,
self.pins)) self.pins))
#This is TODO once multiport control has been finalized. #This is TODO once multiport control has been finalized.
#self.control_name = "CSB" #self.control_name = "CSB"
@ -80,11 +85,13 @@ class simulation():
self.addr_value = {port:[] for port in self.all_ports} self.addr_value = {port:[] for port in self.all_ports}
self.data_value = {port:[] for port in self.write_ports} self.data_value = {port:[] for port in self.write_ports}
self.wmask_value = {port:[] for port in self.write_ports} self.wmask_value = {port:[] for port in self.write_ports}
self.spare_wen_value = {port:[] for port in self.write_ports}
# Three dimensional list to handle each addr and data bits for each port over the number of checks # Three dimensional list to handle each addr and data bits for each port over the number of checks
self.addr_values = {port:[[] for bit in range(self.addr_size)] for port in self.all_ports} self.addr_values = {port:[[] for bit in range(self.addr_size)] for port in self.all_ports}
self.data_values = {port:[[] for bit in range(self.word_size)] for port in self.write_ports} self.data_values = {port:[[] for bit in range(self.word_size + self.num_spare_cols)] for port in self.write_ports}
self.wmask_values = {port:[[] for bit in range(self.num_wmasks)] for port in self.write_ports} self.wmask_values = {port:[[] for bit in range(self.num_wmasks)] for port in self.write_ports}
self.spare_wen_values = {port:[[] for bit in range(self.num_spare_cols)] for port in self.write_ports}
# For generating comments in SPICE stimulus # For generating comments in SPICE stimulus
self.cycle_comments = [] self.cycle_comments = []
@ -111,10 +118,10 @@ class simulation():
def add_data(self, data, port): def add_data(self, data, port):
""" Add the array of data values """ """ Add the array of data values """
debug.check(len(data)==self.word_size, "Invalid data word size.") debug.check(len(data)==(self.word_size + self.num_spare_cols), "Invalid data word size.")
self.data_value[port].append(data) self.data_value[port].append(data)
bit = self.word_size - 1 bit = self.word_size + self.num_spare_cols - 1
for c in data: for c in data:
if c=="0": if c=="0":
self.data_values[port][bit].append(0) self.data_values[port][bit].append(0)
@ -124,7 +131,6 @@ class simulation():
debug.error("Non-binary data string",1) debug.error("Non-binary data string",1)
bit -= 1 bit -= 1
def add_address(self, address, port): def add_address(self, address, port):
""" Add the array of address values """ """ Add the array of address values """
debug.check(len(address)==self.addr_size, "Invalid address size.") debug.check(len(address)==self.addr_size, "Invalid address size.")
@ -135,7 +141,7 @@ class simulation():
if c=="0": if c=="0":
self.addr_values[port][bit].append(0) self.addr_values[port][bit].append(0)
elif c=="1": elif c=="1":
self.addr_values[port][bit].append(1) self.addr_values[port][bit].append(1)
else: else:
debug.error("Non-binary address string",1) debug.error("Non-binary address string",1)
bit -= 1 bit -= 1
@ -156,7 +162,21 @@ class simulation():
debug.error("Non-binary wmask string", 1) debug.error("Non-binary wmask string", 1)
bit -= 1 bit -= 1
def add_spare_wen(self, spare_wen, port):
""" Add the array of spare write enable values (for spare cols) """
debug.check(len(spare_wen) == self.num_spare_cols, "Invalid spare enable size.")
self.spare_wen_value[port].append(spare_wen)
bit = self.num_spare_cols - 1
for c in spare_wen:
if c == "0":
self.spare_wen_values[port][bit].append(0)
elif c == "1":
self.spare_wen_values[port][bit].append(1)
else:
debug.error("Non-binary spare enable signal string", 1)
bit -= 1
def add_write(self, comment, address, data, wmask, port): def add_write(self, comment, address, data, wmask, port):
""" Add the control values for a write cycle. """ """ Add the control values for a write cycle. """
debug.check(port in self.write_ports, debug.check(port in self.write_ports,
@ -172,7 +192,8 @@ class simulation():
self.add_control_one_port(port, "write") self.add_control_one_port(port, "write")
self.add_data(data,port) self.add_data(data,port)
self.add_address(address,port) self.add_address(address,port)
self.add_wmask(wmask,port) self.add_wmask(wmask,port)
self.add_spare_wen("1" * self.num_spare_cols, port)
#Add noops to all other ports. #Add noops to all other ports.
for unselected_port in self.all_ports: for unselected_port in self.all_ports:
@ -191,19 +212,20 @@ class simulation():
self.cycle_times.append(self.t_current) self.cycle_times.append(self.t_current)
self.t_current += self.period self.t_current += self.period
self.add_control_one_port(port, "read") self.add_control_one_port(port, "read")
self.add_address(address, port) self.add_address(address, port)
# If the port is also a readwrite then add # If the port is also a readwrite then add
# the same value as previous cycle # the same value as previous cycle
if port in self.write_ports: if port in self.write_ports:
try: try:
self.add_data(self.data_value[port][-1], port) self.add_data(self.data_value[port][-1], port)
except: except:
self.add_data("0"*self.word_size, port) self.add_data("0"*(self.word_size + self.num_spare_cols), port)
try: try:
self.add_wmask(self.wmask_value[port][-1], port) self.add_wmask(self.wmask_value[port][-1], port)
except: except:
self.add_wmask("0"*self.num_wmasks, port) self.add_wmask("0"*self.num_wmasks, port)
self.add_spare_wen("0" * self.num_spare_cols, port)
#Add noops to all other ports. #Add noops to all other ports.
for unselected_port in self.all_ports: for unselected_port in self.all_ports:
@ -234,6 +256,7 @@ class simulation():
self.add_data(data, port) self.add_data(data, port)
self.add_address(address, port) self.add_address(address, port)
self.add_wmask(wmask, port) self.add_wmask(wmask, port)
self.add_spare_wen("1" * self.num_spare_cols, port)
def add_read_one_port(self, comment, address, port): def add_read_one_port(self, comment, address, port):
""" Add the control values for a read cycle. Does not increment the period. """ """ Add the control values for a read cycle. Does not increment the period. """
@ -245,23 +268,24 @@ class simulation():
self.add_control_one_port(port, "read") self.add_control_one_port(port, "read")
self.add_address(address, port) self.add_address(address, port)
# If the port is also a readwrite then add # If the port is also a readwrite then add
# the same value as previous cycle # the same value as previous cycle
if port in self.write_ports: if port in self.write_ports:
try: try:
self.add_data(self.data_value[port][-1], port) self.add_data(self.data_value[port][-1], port)
except: except:
self.add_data("0"*self.word_size, port) self.add_data("0"*(self.word_size + self.num_spare_cols), port)
try: try:
self.add_wmask(self.wmask_value[port][-1], port) self.add_wmask(self.wmask_value[port][-1], port)
except: except:
self.add_wmask("0"*self.num_wmasks, port) self.add_wmask("0"*self.num_wmasks, port)
self.add_spare_wen("0" * self.num_spare_cols, port)
def add_noop_one_port(self, port): def add_noop_one_port(self, port):
""" Add the control values for a noop to a single port. Does not increment the period. """ """ Add the control values for a noop to a single port. Does not increment the period. """
self.add_control_one_port(port, "noop") self.add_control_one_port(port, "noop")
try: try:
self.add_address(self.addr_value[port][-1], port) self.add_address(self.addr_value[port][-1], port)
except: except:
@ -273,12 +297,13 @@ class simulation():
try: try:
self.add_data(self.data_value[port][-1], port) self.add_data(self.data_value[port][-1], port)
except: except:
self.add_data("0"*self.word_size, port) self.add_data("0"*(self.word_size + self.num_spare_cols), port)
try: try:
self.add_wmask(self.wmask_value[port][-1], port) self.add_wmask(self.wmask_value[port][-1], port)
except: except:
self.add_wmask("0"*self.num_wmasks, port) self.add_wmask("0"*self.num_wmasks, port)
self.add_spare_wen("0" * self.num_spare_cols, port)
def add_noop_clock_one_port(self, port): def add_noop_clock_one_port(self, port):
""" Add the control values for a noop to a single port. Increments the period. """ """ Add the control values for a noop to a single port. Increments the period. """
debug.info(2, 'Clock only on port {}'.format(port)) debug.info(2, 'Clock only on port {}'.format(port))
@ -369,6 +394,11 @@ class simulation():
for port in write_index: for port in write_index:
for bit in range(self.num_wmasks): for bit in range(self.num_wmasks):
pin_names.append("WMASK{0}_{1}".format(port,bit)) pin_names.append("WMASK{0}_{1}".format(port,bit))
if self.num_spare_cols:
for port in write_index:
for bit in range(self.num_spare_cols):
pin_names.append("SPARE_WEN{0}_{1}".format(port,bit))
for read_output in read_index: for read_output in read_index:
for i in range(dbits): for i in range(dbits):

View File

@ -254,7 +254,7 @@ class stimuli():
includes = self.device_models + [circuit] includes = self.device_models + [circuit]
self.sf.write("* {} process corner\n".format(self.process)) self.sf.write("* {} process corner\n".format(self.process))
if OPTS.tech_name == "s8": if OPTS.tech_name == "sky130":
libraries = self.device_libraries libraries = self.device_libraries
for item in list(libraries): for item in list(libraries):
if os.path.isfile(item[0]): if os.path.isfile(item[0]):
@ -302,7 +302,9 @@ class stimuli():
OPTS.openram_temp) OPTS.openram_temp)
valid_retcode=0 valid_retcode=0
else: else:
# ngspice 27+ supports threading with "set num_threads=4" in the stimulus file or a .spiceinit # ngspice 27+ supports threading with "set num_threads=4" in the stimulus file or a .spiceinit
# Measurements can't be made with a raw file set in ngspice
# -r {2}timing.raw
cmd = "{0} -b -o {2}timing.lis {1}".format(OPTS.spice_exe, cmd = "{0} -b -o {2}timing.lis {1}".format(OPTS.spice_exe,
temp_stim, temp_stim,
OPTS.openram_temp) OPTS.openram_temp)

View File

@ -6,7 +6,7 @@
# All rights reserved. # All rights reserved.
# #
import debug import debug
from math import log from math import log,ceil
import re import re
class trim_spice(): class trim_spice():
@ -42,7 +42,7 @@ class trim_spice():
self.word_size = word_size self.word_size = word_size
self.words_per_row = self.num_columns / self.word_size self.words_per_row = self.num_columns / self.word_size
self.row_addr_size = int(log(self.num_rows, 2)) self.row_addr_size = ceil(log(self.num_rows, 2))
self.col_addr_size = int(log(self.words_per_row, 2)) self.col_addr_size = int(log(self.words_per_row, 2))
self.bank_addr_size = self.col_addr_size + self.row_addr_size self.bank_addr_size = self.col_addr_size + self.row_addr_size
self.addr_size = self.bank_addr_size + int(log(self.num_banks, 2)) self.addr_size = self.bank_addr_size + int(log(self.num_banks, 2))

147
compiler/custom/and2_dec.py Normal file
View File

@ -0,0 +1,147 @@
# 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 debug
from vector import vector
import design
from sram_factory import factory
from globals import OPTS
from tech import layer
class and2_dec(design.design):
"""
This is an AND with configurable drive strength.
"""
def __init__(self, name, size=1, height=None, add_wells=True):
design.design.__init__(self, name)
debug.info(1, "Creating and2_dec {}".format(name))
self.add_comment("size: {}".format(size))
self.size = size
self.height = height
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
def create_netlist(self):
self.add_pins()
self.create_modules()
self.create_insts()
def create_modules(self):
self.nand = factory.create(module_type="nand2_dec",
height=self.height)
self.inv = factory.create(module_type="inv_dec",
height=self.height,
size=self.size)
self.add_mod(self.nand)
self.add_mod(self.inv)
def create_layout(self):
if "li" in layer:
self.route_layer = "li"
else:
self.route_layer = "m1"
self.width = self.nand.width + self.inv.width
self.height = self.nand.height
self.place_insts()
self.add_wires()
self.add_layout_pins()
self.route_supply_rails()
self.add_boundary()
self.DRC_LVS()
def add_pins(self):
self.add_pin("A", "INPUT")
self.add_pin("B", "INPUT")
self.add_pin("Z", "OUTPUT")
self.add_pin("vdd", "POWER")
self.add_pin("gnd", "GROUND")
def create_insts(self):
self.nand_inst = self.add_inst(name="pand2_dec_nand",
mod=self.nand)
self.connect_inst(["A", "B", "zb_int", "vdd", "gnd"])
self.inv_inst = self.add_inst(name="pand2_dec_inv",
mod=self.inv)
self.connect_inst(["zb_int", "Z", "vdd", "gnd"])
def place_insts(self):
# Add NAND to the right
self.nand_inst.place(offset=vector(0, 0))
# Add INV to the right
self.inv_inst.place(offset=vector(self.nand_inst.rx(), 0))
def route_supply_rails(self):
""" Add vdd/gnd rails to the top, (middle), and bottom. """
if OPTS.tech_name == "sky130":
for name in ["vdd", "gnd"]:
for inst in [self.nand_inst, self.inv_inst]:
self.copy_layout_pin(inst, name)
else:
self.add_layout_pin_rect_center(text="gnd",
layer=self.route_layer,
offset=vector(0.5 * self.width, 0),
width=self.width)
self.add_layout_pin_rect_center(text="vdd",
layer=self.route_layer,
offset=vector(0.5 * self.width, self.height),
width=self.width)
def add_wires(self):
# nand Z to inv A
z1_pin = self.nand_inst.get_pin("Z")
a2_pin = self.inv_inst.get_pin("A")
if OPTS.tech_name == "sky130":
mid1_point = vector(a2_pin.cx(), z1_pin.cy())
else:
mid1_point = vector(z1_pin.cx(), a2_pin.cy())
self.add_path(self.route_layer,
[z1_pin.center(), mid1_point, a2_pin.center()])
def add_layout_pins(self):
pin = self.inv_inst.get_pin("Z")
self.add_layout_pin_rect_center(text="Z",
layer=pin.layer,
offset=pin.center(),
width=pin.width(),
height=pin.height())
for pin_name in ["A", "B"]:
pin = self.nand_inst.get_pin(pin_name)
self.add_layout_pin_rect_center(text=pin_name,
layer=pin.layer,
offset=pin.center(),
width=pin.width(),
height=pin.height())
def get_stage_efforts(self, external_cout, inp_is_rise=False):
"""Get the stage efforts of the A or B -> Z path"""
stage_effort_list = []
stage1_cout = self.inv.get_cin()
stage1 = self.nand.get_stage_effort(stage1_cout, inp_is_rise)
stage_effort_list.append(stage1)
last_stage_is_rise = stage1.is_rise
stage2 = self.inv.get_stage_effort(external_cout, last_stage_is_rise)
stage_effort_list.append(stage2)
return stage_effort_list
def get_cin(self):
"""Return the relative input capacitance of a single input"""
return self.nand.get_cin()

156
compiler/custom/and3_dec.py Normal file
View File

@ -0,0 +1,156 @@
# 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 debug
from vector import vector
import design
from sram_factory import factory
from globals import OPTS
from tech import layer
class and3_dec(design.design):
"""
This is an AND with configurable drive strength.
"""
def __init__(self, name, size=1, height=None, add_wells=True):
design.design.__init__(self, name)
debug.info(1, "Creating and3_dec {}".format(name))
self.add_comment("size: {}".format(size))
self.size = size
self.height = height
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
def create_netlist(self):
self.add_pins()
self.create_modules()
self.create_insts()
def create_modules(self):
self.nand = factory.create(module_type="nand3_dec",
height=self.height)
self.inv = factory.create(module_type="inv_dec",
height=self.height,
size=self.size)
self.add_mod(self.nand)
self.add_mod(self.inv)
def create_layout(self):
if "li" in layer:
self.route_layer = "li"
else:
self.route_layer = "m1"
self.width = self.nand.width + self.inv.width
self.height = self.nand.height
self.place_insts()
self.add_wires()
self.add_layout_pins()
self.route_supply_rails()
self.add_boundary()
self.DRC_LVS()
def add_pins(self):
self.add_pin("A", "INPUT")
self.add_pin("B", "INPUT")
self.add_pin("C", "INPUT")
self.add_pin("Z", "OUTPUT")
self.add_pin("vdd", "POWER")
self.add_pin("gnd", "GROUND")
def create_insts(self):
self.nand_inst = self.add_inst(name="pand3_dec_nand",
mod=self.nand)
self.connect_inst(["A", "B", "C", "zb_int", "vdd", "gnd"])
self.inv_inst = self.add_inst(name="pand3_dec_inv",
mod=self.inv)
self.connect_inst(["zb_int", "Z", "vdd", "gnd"])
def place_insts(self):
# Add NAND to the right
self.nand_inst.place(offset=vector(0, 0))
# Add INV to the right
self.inv_inst.place(offset=vector(self.nand_inst.rx(), 0))
def route_supply_rails(self):
""" Add vdd/gnd rails to the top, (middle), and bottom. """
if OPTS.tech_name == "sky130":
for name in ["vdd", "gnd"]:
for inst in [self.nand_inst, self.inv_inst]:
self.copy_layout_pin(inst, name)
else:
self.add_layout_pin_rect_center(text="gnd",
layer=self.route_layer,
offset=vector(0.5 * self.width, 0),
width=self.width)
self.add_layout_pin_rect_center(text="vdd",
layer=self.route_layer,
offset=vector(0.5 * self.width, self.height),
width=self.width)
def add_wires(self):
# nand Z to inv A
z1_pin = self.nand_inst.get_pin("Z")
a2_pin = self.inv_inst.get_pin("A")
if OPTS.tech_name == "sky130":
mid1_point = vector(a2_pin.cx(), z1_pin.cy())
else:
mid1_point = vector(z1_pin.cx(), a2_pin.cy())
self.add_path(self.route_layer,
[z1_pin.center(), mid1_point, a2_pin.center()])
def add_layout_pins(self):
pin = self.inv_inst.get_pin("Z")
self.add_layout_pin_rect_center(text="Z",
layer=pin.layer,
offset=pin.center(),
width=pin.width(),
height=pin.height())
for pin_name in ["A", "B", "C"]:
pin = self.nand_inst.get_pin(pin_name)
self.add_layout_pin_rect_center(text=pin_name,
layer=pin.layer,
offset=pin.center(),
width=pin.width(),
height=pin.height())
def analytical_delay(self, corner, slew, load=0.0):
""" Calculate the analytical delay of DFF-> INV -> INV """
nand_delay = self.nand.analytical_delay(corner,
slew=slew,
load=self.inv.input_load())
inv_delay = self.inv.analytical_delay(corner,
slew=nand_delay.slew,
load=load)
return nand_delay + inv_delay
def get_stage_efforts(self, external_cout, inp_is_rise=False):
"""Get the stage efforts of the A or B -> Z path"""
stage_effort_list = []
stage1_cout = self.inv.get_cin()
stage1 = self.nand.get_stage_effort(stage1_cout, inp_is_rise)
stage_effort_list.append(stage1)
last_stage_is_rise = stage1.is_rise
stage2 = self.inv.get_stage_effort(external_cout, last_stage_is_rise)
stage_effort_list.append(stage2)
return stage_effort_list
def get_cin(self):
"""Return the relative input capacitance of a single input"""
return self.nand.get_cin()

159
compiler/custom/and4_dec.py Normal file
View File

@ -0,0 +1,159 @@
# 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 debug
from vector import vector
import design
from sram_factory import factory
from globals import OPTS
from tech import layer
class and4_dec(design.design):
"""
This is an AND with configurable drive strength.
"""
def __init__(self, name, size=1, height=None, add_wells=True):
design.design.__init__(self, name)
debug.info(1, "Creating and4_dec {}".format(name))
self.add_comment("size: {}".format(size))
self.size = size
self.height = height
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
def create_netlist(self):
self.add_pins()
self.create_modules()
self.create_insts()
def create_modules(self):
self.nand = factory.create(module_type="nand4_dec",
height=self.height)
self.inv = factory.create(module_type="inv_dec",
height=self.height,
size=self.size)
self.add_mod(self.nand)
self.add_mod(self.inv)
def create_layout(self):
if "li" in layer:
self.route_layer = "li"
else:
self.route_layer = "m1"
self.width = self.nand.width + self.inv.width
self.height = self.nand.height
self.place_insts()
self.add_wires()
self.add_layout_pins()
self.route_supply_rails()
self.add_boundary()
self.DRC_LVS()
def add_pins(self):
self.add_pin("A", "INPUT")
self.add_pin("B", "INPUT")
self.add_pin("C", "INPUT")
self.add_pin("D", "INPUT")
self.add_pin("Z", "OUTPUT")
self.add_pin("vdd", "POWER")
self.add_pin("gnd", "GROUND")
def create_insts(self):
self.nand_inst = self.add_inst(name="pand4_dec_nand",
mod=self.nand)
self.connect_inst(["A", "B", "C", "D", "zb_int", "vdd", "gnd"])
self.inv_inst = self.add_inst(name="pand4_dec_inv",
mod=self.inv)
self.connect_inst(["zb_int", "Z", "vdd", "gnd"])
def place_insts(self):
# Add NAND to the right
self.nand_inst.place(offset=vector(0, 0))
# Add INV to the right
self.inv_inst.place(offset=vector(self.nand_inst.rx(), 0))
def route_supply_rails(self):
""" Add vdd/gnd rails to the top, (middle), and bottom. """
if OPTS.tech_name == "sky130":
for name in ["vdd", "gnd"]:
for inst in [self.nand_inst, self.inv_inst]:
self.copy_layout_pin(inst, name)
else:
self.add_layout_pin_rect_center(text="gnd",
layer=self.route_layer,
offset=vector(0.5 * self.width, 0),
width=self.width)
self.add_layout_pin_rect_center(text="vdd",
layer=self.route_layer,
offset=vector(0.5 * self.width, self.height),
width=self.width)
def add_wires(self):
# nand Z to inv A
z1_pin = self.nand_inst.get_pin("Z")
a2_pin = self.inv_inst.get_pin("A")
if OPTS.tech_name == "sky130":
mid1_point = vector(a2_pin.cx(), z1_pin.cy())
else:
mid1_point = vector(z1_pin.cx(), a2_pin.cy())
self.add_path(self.route_layer,
[z1_pin.center(), mid1_point, a2_pin.center()])
def add_layout_pins(self):
pin = self.inv_inst.get_pin("Z")
self.add_layout_pin_rect_center(text="Z",
layer=pin.layer,
offset=pin.center(),
width=pin.width(),
height=pin.height())
for pin_name in ["A", "B", "C"]:
pin = self.nand_inst.get_pin(pin_name)
self.add_layout_pin_rect_center(text=pin_name,
layer=pin.layer,
offset=pin.center(),
width=pin.width(),
height=pin.height())
def analytical_delay(self, corner, slew, load=0.0):
""" Calculate the analytical delay of DFF-> INV -> INV """
nand_delay = self.nand.analytical_delay(corner,
slew=slew,
load=self.inv.input_load())
inv_delay = self.inv.analytical_delay(corner,
slew=nand_delay.slew,
load=load)
return nand_delay + inv_delay
def get_stage_efforts(self, external_cout, inp_is_rise=False):
"""Get the stage efforts of the A or B -> Z path"""
stage_effort_list = []
stage1_cout = self.inv.get_cin()
stage1 = self.nand.get_stage_effort(stage1_cout, inp_is_rise)
stage_effort_list.append(stage1)
last_stage_is_rise = stage1.is_rise
stage2 = self.inv.get_stage_effort(external_cout, last_stage_is_rise)
stage_effort_list.append(stage2)
return stage_effort_list
def get_cin(self):
"""Return the relative input capacitance of a single input"""
return self.nand.get_cin()

View File

@ -61,7 +61,7 @@ class dff(design.design):
#Calculated in the tech file by summing the widths of all the gates and dividing by the minimum width. #Calculated in the tech file by summing the widths of all the gates and dividing by the minimum width.
return parameter["dff_clk_cin"] return parameter["dff_clk_cin"]
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

@ -0,0 +1,80 @@
# 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 design
from tech import GDS, layer, spice, parameter
import logical_effort
import utils
import debug
class inv_dec(design.design):
"""
INV for address decoders.
"""
pin_names = ["A", "Z", "vdd", "gnd"]
type_list = ["INPUT", "OUTPUT", "POWER", "GROUND"]
(width, height) = utils.get_libcell_size("inv_dec",
GDS["unit"],
layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "inv_dec", GDS["unit"])
def __init__(self, name="inv_dec", height=None):
design.design.__init__(self, name)
self.width = inv_dec.width
self.height = inv_dec.height
self.pin_map = inv_dec.pin_map
self.add_pin_types(self.type_list)
def analytical_power(self, corner, load):
"""Returns dynamic and leakage power. Results in nW"""
c_eff = self.calculate_effective_capacitance(load)
freq = spice["default_event_frequency"]
power_dyn = self.calc_dynamic_power(corner, c_eff, freq)
power_leak = spice["inv_leakage"]
total_power = self.return_power(power_dyn, power_leak)
return total_power
def calculate_effective_capacitance(self, load):
"""Computes effective capacitance. Results in fF"""
c_load = load
# In fF
c_para = spice["min_tx_drain_c"] * (self.nmos_size / parameter["min_tx_size"])
return transition_prob * (c_load + c_para)
def input_load(self):
"""
Return the capacitance of the gate connection in generic capacitive
units relative to the minimum width of a transistor
"""
return self.nmos_size + self.pmos_size
def get_stage_effort(self, cout, inp_is_rise=True):
"""
Returns an object representing the parameters for delay in tau units.
Optional is_rise refers to the input direction rise/fall.
Input inverted by this stage.
"""
parasitic_delay = 1
return logical_effort.logical_effort(self.name,
self.size,
self.input_load(),
cout,
parasitic_delay,
not inp_is_rise)
def build_graph(self, graph, inst_name, port_nets):
"""
Adds edges based on inputs/outputs.
Overrides base class function.
"""
self.add_graph_edges(graph, port_nets)

View File

@ -0,0 +1,85 @@
# 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 design
from tech import GDS, layer, spice, parameter, drc
import logical_effort
import utils
class nand2_dec(design.design):
"""
2-input NAND decoder for address decoders.
"""
pin_names = ["A", "B", "Z", "vdd", "gnd"]
type_list = ["INPUT", "INPUT", "OUTPUT", "POWER", "GROUND"]
(width, height) = utils.get_libcell_size("nand2_dec",
GDS["unit"],
layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "nand2_dec", GDS["unit"])
def __init__(self, name="nand2_dec", height=None):
design.design.__init__(self, name)
self.width = nand2_dec.width
self.height = nand2_dec.height
self.pin_map = nand2_dec.pin_map
self.add_pin_types(self.type_list)
# FIXME: For now...
size = 1
self.size = size
self.nmos_size = 2 * size
self.pmos_size = parameter["beta"] * size
self.nmos_width = self.nmos_size * drc("minwidth_tx")
self.pmos_width = self.pmos_size * drc("minwidth_tx")
def analytical_power(self, corner, load):
"""Returns dynamic and leakage power. Results in nW"""
c_eff = self.calculate_effective_capacitance(load)
freq = spice["default_event_frequency"]
power_dyn = self.calc_dynamic_power(corner, c_eff, freq)
power_leak = spice["nand2_leakage"]
total_power = self.return_power(power_dyn, power_leak)
return total_power
def calculate_effective_capacitance(self, load):
"""Computes effective capacitance. Results in fF"""
c_load = load
# In fF
c_para = spice["min_tx_drain_c"] * (self.nmos_size / parameter["min_tx_size"])
transition_prob = 0.1875
return transition_prob * (c_load + c_para)
def input_load(self):
"""Return the relative input capacitance of a single input"""
return self.nmos_size + self.pmos_size
def get_stage_effort(self, cout, inp_is_rise=True):
"""
Returns an object representing the parameters for delay in tau units.
Optional is_rise refers to the input direction rise/fall.
Input inverted by this stage.
"""
parasitic_delay = 2
return logical_effort.logical_effort(self.name,
self.size,
self.input_load(),
cout,
parasitic_delay,
not inp_is_rise)
def build_graph(self, graph, inst_name, port_nets):
"""
Adds edges based on inputs/outputs.
Overrides base class function.
"""
self.add_graph_edges(graph, port_nets)

View File

@ -0,0 +1,85 @@
# 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 design
from tech import GDS, layer, spice, parameter, drc
import logical_effort
import utils
class nand3_dec(design.design):
"""
3-input NAND decoder for address decoders.
"""
pin_names = ["A", "B", "C", "Z", "vdd", "gnd"]
type_list = ["INPUT", "INPUT", "INPUT", "OUTPUT", "POWER", "GROUND"]
(width, height) = utils.get_libcell_size("nand3_dec",
GDS["unit"],
layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "nand3_dec", GDS["unit"])
def __init__(self, name="nand3_dec", height=None):
design.design.__init__(self, name)
self.width = nand3_dec.width
self.height = nand3_dec.height
self.pin_map = nand3_dec.pin_map
self.add_pin_types(self.type_list)
# FIXME: For now...
size = 1
self.size = size
self.nmos_size = 2 * size
self.pmos_size = parameter["beta"] * size
self.nmos_width = self.nmos_size * drc("minwidth_tx")
self.pmos_width = self.pmos_size * drc("minwidth_tx")
def analytical_power(self, corner, load):
"""Returns dynamic and leakage power. Results in nW"""
c_eff = self.calculate_effective_capacitance(load)
freq = spice["default_event_frequency"]
power_dyn = self.calc_dynamic_power(corner, c_eff, freq)
power_leak = spice["nand3_leakage"]
total_power = self.return_power(power_dyn, power_leak)
return total_power
def calculate_effective_capacitance(self, load):
"""Computes effective capacitance. Results in fF"""
c_load = load
# In fF
c_para = spice["min_tx_drain_c"] * (self.nmos_size / parameter["min_tx_size"])
transition_prob = 0.1875
return transition_prob * (c_load + c_para)
def input_load(self):
"""Return the relative input capacitance of a single input"""
return self.nmos_size + self.pmos_size
def get_stage_effort(self, cout, inp_is_rise=True):
"""
Returns an object representing the parameters for delay in tau units.
Optional is_rise refers to the input direction rise/fall.
Input inverted by this stage.
"""
parasitic_delay = 2
return logical_effort.logical_effort(self.name,
self.size,
self.input_load(),
cout,
parasitic_delay,
not inp_is_rise)
def build_graph(self, graph, inst_name, port_nets):
"""
Adds edges based on inputs/outputs.
Overrides base class function.
"""
self.add_graph_edges(graph, port_nets)

View File

@ -0,0 +1,85 @@
# 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 design
from tech import GDS, layer, spice, parameter, drc
import logical_effort
import utils
class nand4_dec(design.design):
"""
2-input NAND decoder for address decoders.
"""
pin_names = ["A", "B", "C", "D", "Z", "vdd", "gnd"]
type_list = ["INPUT", "INPUT", "INPUT", "INPUT", "OUTPUT", "POWER", "GROUND"]
(width, height) = utils.get_libcell_size("nand4_dec",
GDS["unit"],
layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "nand4_dec", GDS["unit"])
def __init__(self, name="nand4_dec", height=None):
design.design.__init__(self, name)
self.width = nand4_dec.width
self.height = nand4_dec.height
self.pin_map = nand4_dec.pin_map
self.add_pin_types(self.type_list)
# FIXME: For now...
size = 1
self.size = size
self.nmos_size = 2 * size
self.pmos_size = parameter["beta"] * size
self.nmos_width = self.nmos_size * drc("minwidth_tx")
self.pmos_width = self.pmos_size * drc("minwidth_tx")
def analytical_power(self, corner, load):
"""Returns dynamic and leakage power. Results in nW"""
c_eff = self.calculate_effective_capacitance(load)
freq = spice["default_event_frequency"]
power_dyn = self.calc_dynamic_power(corner, c_eff, freq)
power_leak = spice["nand4_leakage"]
total_power = self.return_power(power_dyn, power_leak)
return total_power
def calculate_effective_capacitance(self, load):
"""Computes effective capacitance. Results in fF"""
c_load = load
# In fF
c_para = spice["min_tx_drain_c"] * (self.nmos_size / parameter["min_tx_size"])
transition_prob = 0.1875
return transition_prob * (c_load + c_para)
def input_load(self):
"""Return the relative input capacitance of a single input"""
return self.nmos_size + self.pmos_size
def get_stage_effort(self, cout, inp_is_rise=True):
"""
Returns an object representing the parameters for delay in tau units.
Optional is_rise refers to the input direction rise/fall.
Input inverted by this stage.
"""
parasitic_delay = 2
return logical_effort.logical_effort(self.name,
self.size,
self.input_load(),
cout,
parasitic_delay,
not inp_is_rise)
def build_graph(self, graph, inst_name, port_nets):
"""
Adds edges based on inputs/outputs.
Overrides base class function.
"""
self.add_graph_edges(graph, port_nets)

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

@ -52,11 +52,12 @@ import sram
class fake_sram(sram.sram): class fake_sram(sram.sram):
""" This is an SRAM that doesn't actually create itself, just computes """ This is an SRAM that doesn't actually create itself, just computes
the sizes. """ the sizes. """
def __init__(self, word_size, num_words, num_banks, name): def __init__(self, word_size, num_words, num_banks, name, num_spare_rows):
self.name = name self.name = name
self.word_size = word_size self.word_size = word_size
self.num_words = num_words self.num_words = num_words
self.num_banks = num_banks self.num_banks = num_banks
self.num_spare_rows = num_spare_rows
c = reload(__import__(OPTS.bitcell)) c = reload(__import__(OPTS.bitcell))
self.mod_bitcell = getattr(c, OPTS.bitcell) self.mod_bitcell = getattr(c, OPTS.bitcell)
self.bitcell = self.mod_bitcell() self.bitcell = self.mod_bitcell()
@ -75,7 +76,10 @@ d.period = period
# Set the load of outputs and slew of inputs # Set the load of outputs and slew of inputs
d.set_load_slew(load,slew) d.set_load_slew(load,slew)
# Set the probe address/bit # Set the probe address/bit
probe_address = "1" * sram.addr_size if (self.num_spare_rows == 0):
probe_address = "1" * sram.addr_size
else:
probe_address = "0" + ("1" * sram.addr_size - 1)
probe_data = sram.word_size - 1 probe_data = sram.word_size - 1
d.set_probe(probe_address, probe_data) d.set_probe(probe_address, probe_data)

View File

@ -99,6 +99,9 @@ def parse_args():
# Alias SCMOS to 180nm # Alias SCMOS to 180nm
if OPTS.tech_name == "scmos": if OPTS.tech_name == "scmos":
OPTS.tech_name = "scn4m_subm" OPTS.tech_name = "scn4m_subm"
# Alias s8 to sky130
if OPTS.tech_name == "s8":
OPTS.tech_name = "sky130"
return (options, args) return (options, args)
@ -214,25 +217,18 @@ def setup_bitcell():
if OPTS.num_r_ports > 0: if OPTS.num_r_ports > 0:
ports += "{}r".format(OPTS.num_r_ports) ports += "{}r".format(OPTS.num_r_ports)
OPTS.bitcell = "bitcell_"+ports if ports != "":
OPTS.replica_bitcell = "replica_bitcell_"+ports OPTS.bitcell_suffix = "_" + ports
OPTS.dummy_bitcell = "dummy_bitcell_"+ports OPTS.bitcell = "bitcell" + OPTS.bitcell_suffix
else:
OPTS.replica_bitcell = "replica_" + OPTS.bitcell
OPTS.replica_bitcell = "dummy_" + OPTS.bitcell
# See if bitcell exists # See if bitcell exists
try: try:
__import__(OPTS.bitcell) __import__(OPTS.bitcell)
__import__(OPTS.replica_bitcell)
__import__(OPTS.dummy_bitcell)
except ImportError: except ImportError:
# Use the pbitcell if we couldn't find a custom bitcell # Use the pbitcell if we couldn't find a custom bitcell
# or its custom replica bitcell # or its custom replica bitcell
# Use the pbitcell (and give a warning if not in unit test mode) # Use the pbitcell (and give a warning if not in unit test mode)
OPTS.bitcell = "pbitcell" OPTS.bitcell = "pbitcell"
OPTS.replica_bitcell = "replica_pbitcell"
OPTS.replica_bitcell = "dummy_pbitcell"
if not OPTS.is_unit_test: if not OPTS.is_unit_test:
debug.warning("Using the parameterized bitcell which may have suboptimal density.") debug.warning("Using the parameterized bitcell which may have suboptimal density.")
debug.info(1, "Using bitcell: {}".format(OPTS.bitcell)) debug.info(1, "Using bitcell: {}".format(OPTS.bitcell))
@ -539,7 +535,7 @@ def report_status():
debug.error("{0} is not an integer in config file.".format(OPTS.sram_size)) debug.error("{0} is not an integer in config file.".format(OPTS.sram_size))
if type(OPTS.write_size) is not int and OPTS.write_size is not None: if type(OPTS.write_size) is not int and OPTS.write_size is not None:
debug.error("{0} is not an integer in config file.".format(OPTS.write_size)) debug.error("{0} is not an integer in config file.".format(OPTS.write_size))
# If a write mask is specified by the user, the mask write size should be the same as # If a write mask is specified by the user, the mask write size should be the same as
# the word size so that an entire word is written at once. # the word size so that an entire word is written at once.
if OPTS.write_size is not None: if OPTS.write_size is not None:

View File

@ -8,8 +8,8 @@
import debug import debug
import design import design
from sram_factory import factory from sram_factory import factory
from math import log from math import log, ceil
from tech import drc from tech import drc, layer
from vector import vector from vector import vector
from globals import OPTS from globals import OPTS
@ -31,6 +31,9 @@ class bank(design.design):
else: else:
self.num_wmasks = 0 self.num_wmasks = 0
if not self.num_spare_cols:
self.num_spare_cols = 0
if name == "": if name == "":
name = "bank_{0}_{1}".format(self.word_size, self.num_words) name = "bank_{0}_{1}".format(self.word_size, self.num_words)
design.design.__init__(self, name) design.design.__init__(self, name)
@ -77,12 +80,12 @@ class bank(design.design):
def add_pins(self): def add_pins(self):
""" Adding pins for Bank module""" """ Adding pins for Bank module"""
for port in self.read_ports: for port in self.read_ports:
for bit in range(self.word_size): for bit in range(self.word_size + self.num_spare_cols):
self.add_pin("dout{0}_{1}".format(port, bit), "OUTPUT") self.add_pin("dout{0}_{1}".format(port, bit), "OUTPUT")
for port in self.all_ports: for port in self.all_ports:
self.add_pin(self.bitcell_array.get_rbl_bl_name(self.port_rbl_map[port]), "OUTPUT") self.add_pin(self.bitcell_array.get_rbl_bl_name(self.port_rbl_map[port]), "OUTPUT")
for port in self.write_ports: for port in self.write_ports:
for bit in range(self.word_size): for bit in range(self.word_size + self.num_spare_cols):
self.add_pin("din{0}_{1}".format(port, bit), "INPUT") self.add_pin("din{0}_{1}".format(port, bit), "INPUT")
for port in self.all_ports: for port in self.all_ports:
for bit in range(self.addr_size): for bit in range(self.addr_size):
@ -101,6 +104,8 @@ class bank(design.design):
self.add_pin("w_en{0}".format(port), "INPUT") self.add_pin("w_en{0}".format(port), "INPUT")
for bit in range(self.num_wmasks): for bit in range(self.num_wmasks):
self.add_pin("bank_wmask{0}_{1}".format(port, bit), "INPUT") self.add_pin("bank_wmask{0}_{1}".format(port, bit), "INPUT")
for bit in range(self.num_spare_cols):
self.add_pin("bank_spare_wen{0}_{1}".format(port, bit), "INPUT")
for port in self.all_ports: for port in self.all_ports:
self.add_pin("wl_en{0}".format(port), "INPUT") self.add_pin("wl_en{0}".format(port), "INPUT")
self.add_pin("vdd", "POWER") self.add_pin("vdd", "POWER")
@ -123,23 +128,25 @@ class bank(design.design):
def route_rbl(self, port): def route_rbl(self, port):
""" Route the rbl_bl and rbl_wl """ """ Route the rbl_bl and rbl_wl """
bl_pin_name = self.bitcell_array.get_rbl_bl_name(self.port_rbl_map[port]) # Connect the rbl to the port data pin
bl_pin = self.bitcell_array_inst.get_pin(bl_pin_name) bl_pin = self.port_data_inst[port].get_pin("rbl_bl")
# This will ensure the pin is only on the top or bottom edge
if port % 2: if port % 2:
via_offset = bl_pin.uc() + vector(0, 1.5 * self.m2_pitch) pin_pos = bl_pin.uc()
left_right_offset = vector(self.max_x_offset, via_offset.y) pin_offset = pin_pos + vector(0, self.m3_pitch)
left_right_offset = vector(self.max_x_offset, pin_offset.y)
else: else:
via_offset = bl_pin.bc() - vector(0, 1.5 * self.m2_pitch) pin_pos = bl_pin.bc()
left_right_offset = vector(self.min_x_offset, via_offset.y) pin_offset = pin_pos - vector(0, self.m3_pitch)
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=via_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,
end=via_offset) end=pin_offset)
def route_bitlines(self, port): def route_bitlines(self, port):
""" Route the bitlines depending on the port type rw, w, or r. """ """ Route the bitlines depending on the port type rw, w, or r. """
@ -213,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)
@ -254,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)
@ -288,13 +300,14 @@ class bank(design.design):
""" Computes the required sizes to create the bank """ """ Computes the required sizes to create the bank """
self.num_cols = int(self.words_per_row * self.word_size) self.num_cols = int(self.words_per_row * self.word_size)
self.num_rows = int(self.num_words / self.words_per_row) self.num_rows_temp = int(self.num_words / self.words_per_row)
self.num_rows = self.num_rows_temp + self.num_spare_rows
self.row_addr_size = int(log(self.num_rows, 2)) self.row_addr_size = ceil(log(self.num_rows, 2))
self.col_addr_size = int(log(self.words_per_row, 2)) self.col_addr_size = int(log(self.words_per_row, 2))
self.addr_size = self.col_addr_size + self.row_addr_size self.addr_size = self.col_addr_size + self.row_addr_size
debug.check(self.num_rows * self.num_cols==self.word_size * self.num_words, debug.check(self.num_rows_temp * self.num_cols==self.word_size * self.num_words,
"Invalid bank sizes.") "Invalid bank sizes.")
debug.check(self.addr_size==self.col_addr_size + self.row_addr_size, debug.check(self.addr_size==self.col_addr_size + self.row_addr_size,
"Invalid address break down.") "Invalid address break down.")
@ -303,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)])
@ -316,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 = []
@ -325,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:
@ -356,7 +370,7 @@ class bank(design.design):
self.add_mod(self.port_data[port]) self.add_mod(self.port_data[port])
self.port_address = factory.create(module_type="port_address", self.port_address = factory.create(module_type="port_address",
cols=self.num_cols, cols=self.num_cols + self.num_spare_cols,
rows=self.num_rows) rows=self.num_rows)
self.add_mod(self.port_address) self.add_mod(self.port_address)
@ -364,7 +378,7 @@ class bank(design.design):
self.num_rbl = len(self.all_ports) self.num_rbl = len(self.all_ports)
self.bitcell_array = factory.create(module_type="replica_bitcell_array", self.bitcell_array = factory.create(module_type="replica_bitcell_array",
cols=self.num_cols, cols=self.num_cols + self.num_spare_cols,
rows=self.num_rows, rows=self.num_rows,
left_rbl=1, left_rbl=1,
right_rbl=1 if len(self.all_ports)>1 else 0, right_rbl=1 if len(self.all_ports)>1 else 0,
@ -382,7 +396,7 @@ class bank(design.design):
mod=self.bitcell_array) mod=self.bitcell_array)
temp = [] temp = []
for col in range(self.num_cols): for col in range(self.num_cols + self.num_spare_cols):
for bitline in self.bitline_names: for bitline in self.bitline_names:
temp.append("{0}_{1}".format(bitline, col)) temp.append("{0}_{1}".format(bitline, col))
for rbl in range(self.num_rbl): for rbl in range(self.num_rbl):
@ -416,14 +430,14 @@ class bank(design.design):
rbl_br_name=self.bitcell_array.get_rbl_br_name(self.port_rbl_map[port]) rbl_br_name=self.bitcell_array.get_rbl_br_name(self.port_rbl_map[port])
temp.append(rbl_bl_name) temp.append(rbl_bl_name)
temp.append(rbl_br_name) temp.append(rbl_br_name)
for col in range(self.num_cols): for col in range(self.num_cols + self.num_spare_cols):
temp.append("{0}_{1}".format(self.bl_names[port], col)) temp.append("{0}_{1}".format(self.bl_names[port], col))
temp.append("{0}_{1}".format(self.br_names[port], col)) temp.append("{0}_{1}".format(self.br_names[port], col))
if port in self.read_ports: if port in self.read_ports:
for bit in range(self.word_size): for bit in range(self.word_size + self.num_spare_cols):
temp.append("dout{0}_{1}".format(port, bit)) temp.append("dout{0}_{1}".format(port, bit))
if port in self.write_ports: if port in self.write_ports:
for bit in range(self.word_size): for bit in range(self.word_size + self.num_spare_cols):
temp.append("din{0}_{1}".format(port, bit)) temp.append("din{0}_{1}".format(port, bit))
# Will be empty if no col addr lines # Will be empty if no col addr lines
sel_names = ["sel{0}_{1}".format(port, x) for x in range(self.num_col_addr_lines)] sel_names = ["sel{0}_{1}".format(port, x) for x in range(self.num_col_addr_lines)]
@ -435,6 +449,8 @@ class bank(design.design):
temp.append("w_en{0}".format(port)) temp.append("w_en{0}".format(port))
for bit in range(self.num_wmasks): for bit in range(self.num_wmasks):
temp.append("bank_wmask{0}_{1}".format(port, bit)) temp.append("bank_wmask{0}_{1}".format(port, bit))
for bit in range(self.num_spare_cols):
temp.append("bank_spare_wen{0}_{1}".format(port, bit))
temp.extend(["vdd", "gnd"]) temp.extend(["vdd", "gnd"])
self.connect_inst(temp) self.connect_inst(temp)
@ -490,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)
@ -569,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. """
@ -594,7 +626,7 @@ class bank(design.design):
# Connect the inverter output to the central bus # Connect the inverter output to the central bus
out_pos = self.bank_select_inst[port].get_pin(gated_bank_sel_signals[signal]).rc() out_pos = self.bank_select_inst[port].get_pin(gated_bank_sel_signals[signal]).rc()
name = self.control_signals[port][signal] name = self.control_signals[port][signal]
bus_pos = vector(self.bus_xoffset[port][name].x, out_pos.y) bus_pos = vector(self.bus_pins[port][name].cx(), out_pos.y)
self.add_path("m3", [out_pos, bus_pos]) self.add_path("m3", [out_pos, bus_pos])
self.add_via_center(layers=self.m2_stack, self.add_via_center(layers=self.m2_stack,
offset=bus_pos) offset=bus_pos)
@ -629,19 +661,20 @@ class bank(design.design):
# Overall central bus width. It includes all the column mux lines, # Overall central bus width. It includes all the column mux lines,
# and control lines. # and control lines.
self.bus_xoffset = [None] * len(self.all_ports) self.bus_pins = [None] * len(self.all_ports)
# Port 0 # Port 0
# The bank is at (0,0), so this is to the left of the y-axis. # The bank is at (0,0), so this is to the left of the y-axis.
# 2 pitches on the right for vias/jogs to access the inputs # 2 pitches on the right for vias/jogs to access the inputs
control_bus_offset = vector(-self.m3_pitch * self.num_control_lines[0] - self.m3_pitch, self.min_y_offset) control_bus_offset = vector(-self.m3_pitch * self.num_control_lines[0] - self.m3_pitch, self.min_y_offset)
# The control bus is routed up to two pitches below the bitcell array # The control bus is routed up to two pitches below the bitcell array
control_bus_length = self.main_bitcell_array_bottom - self.min_y_offset - 2 * self.m1_pitch control_bus_length = self.main_bitcell_array_bottom - self.min_y_offset - 2 * self.m1_pitch
self.bus_xoffset[0] = self.create_bus(layer="m2", self.bus_pins[0] = self.create_bus(layer="m2",
offset=control_bus_offset, offset=control_bus_offset,
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:
@ -650,12 +683,13 @@ class bank(design.design):
control_bus_offset = vector(self.bitcell_array_right + self.m3_pitch, control_bus_offset = vector(self.bitcell_array_right + self.m3_pitch,
self.max_y_offset - control_bus_length) self.max_y_offset - control_bus_length)
# The bus for the right port is reversed so that the rbl_wl is closest to the array # The bus for the right port is reversed so that the rbl_wl is closest to the array
self.bus_xoffset[1] = self.create_bus(layer="m2", self.bus_pins[1] = self.create_bus(layer="m2",
offset=control_bus_offset, offset=control_bus_offset,
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 """
@ -676,6 +710,11 @@ class bank(design.design):
inst1_br_name=inst1_br_name, inst1_br_name=inst1_br_name,
inst2_bl_name=inst2_bl_name, inst2_bl_name=inst2_bl_name,
inst2_br_name=inst2_br_name) inst2_br_name=inst2_br_name)
# connect spare bitlines
for i in range(self.num_spare_cols):
self.connect_bitline(inst1, inst2, inst1_bl_name.format(self.num_cols+i), "spare" + inst2_bl_name.format(i))
self.connect_bitline(inst1, inst2, inst1_br_name.format(self.num_cols+i), "spare" + inst2_br_name.format(i))
# Connect the replica bitlines # Connect the replica bitlines
rbl_bl_name=self.bitcell_array.get_rbl_bl_name(self.port_rbl_map[port]) rbl_bl_name=self.bitcell_array.get_rbl_bl_name(self.port_rbl_map[port])
@ -686,7 +725,7 @@ class bank(design.design):
def route_port_data_out(self, port): def route_port_data_out(self, port):
""" Add pins for the port data out """ """ Add pins for the port data out """
for bit in range(self.word_size): for bit in range(self.word_size + self.num_spare_cols):
data_pin = self.port_data_inst[port].get_pin("dout_{0}".format(bit)) data_pin = self.port_data_inst[port].get_pin("dout_{0}".format(bit))
self.add_layout_pin_rect_center(text="dout{0}_{1}".format(port, bit), self.add_layout_pin_rect_center(text="dout{0}_{1}".format(port, bit),
layer=data_pin.layer, layer=data_pin.layer,
@ -707,16 +746,21 @@ class bank(design.design):
def route_port_data_in(self, port): def route_port_data_in(self, port):
""" Connecting port data in """ """ Connecting port data in """
for row in range(self.word_size): for row in range(self.word_size + self.num_spare_cols):
data_name = "din_{}".format(row) data_name = "din_{}".format(row)
din_name = "din{0}_{1}".format(port, row) din_name = "din{0}_{1}".format(port, row)
self.copy_layout_pin(self.port_data_inst[port], data_name, din_name) self.copy_layout_pin(self.port_data_inst[port], data_name, din_name)
if self.word_size: if self.write_size:
for row in range(self.num_wmasks): for row in range(self.num_wmasks):
wmask_name = "bank_wmask_{}".format(row) wmask_name = "bank_wmask_{}".format(row)
bank_wmask_name = "bank_wmask{0}_{1}".format(port, row) bank_wmask_name = "bank_wmask{0}_{1}".format(port, row)
self.copy_layout_pin(self.port_data_inst[port], wmask_name, bank_wmask_name) self.copy_layout_pin(self.port_data_inst[port], wmask_name, bank_wmask_name)
for col in range(self.num_spare_cols):
sparecol_name = "bank_spare_wen{}".format(col)
bank_sparecol_name = "bank_spare_wen{0}_{1}".format(port, col)
self.copy_layout_pin(self.port_data_inst[port], sparecol_name, bank_sparecol_name)
def channel_route_bitlines(self, inst1, inst2, num_bits, def channel_route_bitlines(self, inst1, inst2, num_bits,
inst1_bl_name="bl_{}", inst1_br_name="br_{}", inst1_bl_name="bl_{}", inst1_br_name="br_{}",
@ -799,28 +843,47 @@ class bank(design.design):
for row in range(self.num_rows): for row in range(self.num_rows):
# The mid guarantees we exit the input cell to the right. # The mid guarantees we exit the input cell to the right.
driver_wl_pos = self.port_address_inst[port].get_pin("wl_{}".format(row)).rc() driver_wl_pin = self.port_address_inst[port].get_pin("wl_{}".format(row))
bitcell_wl_pos = self.bitcell_array_inst.get_pin(self.wl_names[port] + "_{}".format(row)).lc() driver_wl_pos = driver_wl_pin.rc()
bitcell_wl_pin = self.bitcell_array_inst.get_pin(self.wl_names[port] + "_{}".format(row))
bitcell_wl_pos = bitcell_wl_pin.lc()
mid1 = driver_wl_pos.scale(0, 1) + vector(0.5 * self.port_address_inst[port].rx() + 0.5 * self.bitcell_array_inst.lx(), 0) mid1 = driver_wl_pos.scale(0, 1) + vector(0.5 * self.port_address_inst[port].rx() + 0.5 * self.bitcell_array_inst.lx(), 0)
mid2 = mid1.scale(1, 0) + bitcell_wl_pos.scale(0.5, 1) mid2 = mid1.scale(1, 0) + bitcell_wl_pos.scale(0.5, 1)
self.add_path("m1", [driver_wl_pos, mid1, mid2, bitcell_wl_pos]) self.add_path(driver_wl_pin.layer, [driver_wl_pos, mid1, mid2, bitcell_wl_pos])
self.add_via_stack_center(from_layer=driver_wl_pin.layer,
to_layer=bitcell_wl_pin.layer,
offset=bitcell_wl_pos,
directions=("H", "H"))
def route_port_address_right(self, port): def route_port_address_right(self, port):
""" Connecting Wordline driver output to Bitcell WL connection """ """ Connecting Wordline driver output to Bitcell WL connection """
for row in range(self.num_rows): for row in range(self.num_rows):
# The mid guarantees we exit the input cell to the right. # The mid guarantees we exit the input cell to the right.
driver_wl_pos = self.port_address_inst[port].get_pin("wl_{}".format(row)).lc() driver_wl_pin = self.port_address_inst[port].get_pin("wl_{}".format(row))
bitcell_wl_pos = self.bitcell_array_inst.get_pin(self.wl_names[port] + "_{}".format(row)).rc() driver_wl_pos = driver_wl_pin.lc()
bitcell_wl_pin = self.bitcell_array_inst.get_pin(self.wl_names[port] + "_{}".format(row))
bitcell_wl_pos = bitcell_wl_pin.rc()
mid1 = driver_wl_pos.scale(0, 1) + vector(0.5 * self.port_address_inst[port].lx() + 0.5 * self.bitcell_array_inst.rx(), 0) mid1 = driver_wl_pos.scale(0, 1) + vector(0.5 * self.port_address_inst[port].lx() + 0.5 * self.bitcell_array_inst.rx(), 0)
mid2 = mid1.scale(1, 0) + bitcell_wl_pos.scale(0, 1) mid2 = mid1.scale(1, 0) + bitcell_wl_pos.scale(0, 1)
self.add_path("m1", [driver_wl_pos, mid1, mid2, bitcell_wl_pos]) self.add_path(driver_wl_pin.layer, [driver_wl_pos, mid1, mid2, bitcell_wl_pos])
self.add_via_stack_center(from_layer=driver_wl_pin.layer,
to_layer=bitcell_wl_pin.layer,
offset=bitcell_wl_pos,
directions=("H", "H"))
def route_column_address_lines(self, port): def route_column_address_lines(self, port):
""" Connecting the select lines of column mux to the address bus """ """ Connecting the select lines of column mux to the address bus """
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]
@ -840,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]
@ -852,7 +915,7 @@ class bank(design.design):
route_map = list(zip(decode_pins, column_mux_pins)) route_map = list(zip(decode_pins, column_mux_pins))
self.create_vertical_channel_route(route_map, self.create_vertical_channel_route(route_map,
offset, offset,
self.m1_stack) stack)
def add_lvs_correspondence_points(self): def add_lvs_correspondence_points(self):
""" """
@ -908,39 +971,32 @@ class bank(design.design):
# pre-decoder and this connection is in metal3 # pre-decoder and this connection is in metal3
connection = [] connection = []
connection.append((self.prefix + "p_en_bar{}".format(port), connection.append((self.prefix + "p_en_bar{}".format(port),
self.port_data_inst[port].get_pin("p_en_bar").lc(), self.port_data_inst[port].get_pin("p_en_bar")))
self.port_data_inst[port].get_pin("p_en_bar").layer))
rbl_wl_name = self.bitcell_array.get_rbl_wl_name(self.port_rbl_map[port]) rbl_wl_name = self.bitcell_array.get_rbl_wl_name(self.port_rbl_map[port])
connection.append((self.prefix + "wl_en{}".format(port), connection.append((self.prefix + "wl_en{}".format(port),
self.bitcell_array_inst.get_pin(rbl_wl_name).lc(), self.bitcell_array_inst.get_pin(rbl_wl_name)))
self.bitcell_array_inst.get_pin(rbl_wl_name).layer))
if port in self.write_ports: if port in self.write_ports:
if port % 2: connection.append((self.prefix + "w_en{}".format(port),
connection.append((self.prefix + "w_en{}".format(port), self.port_data_inst[port].get_pin("w_en")))
self.port_data_inst[port].get_pin("w_en").rc(),
self.port_data_inst[port].get_pin("w_en").layer))
else:
connection.append((self.prefix + "w_en{}".format(port),
self.port_data_inst[port].get_pin("w_en").lc(),
self.port_data_inst[port].get_pin("w_en").layer))
if port in self.read_ports: if port in self.read_ports:
connection.append((self.prefix + "s_en{}".format(port), connection.append((self.prefix + "s_en{}".format(port),
self.port_data_inst[port].get_pin("s_en").lc(), self.port_data_inst[port].get_pin("s_en")))
self.port_data_inst[port].get_pin("s_en").layer))
for (control_signal, pin_pos, pin_layer) in connection: for (control_signal, pin) in connection:
if port==0: control_pin = self.bus_pins[port][control_signal]
y_offset = self.min_y_offset control_pos = vector(control_pin.cx(), pin.cy())
else: # If the y doesn't overlap the bus, add a segment
y_offset = self.max_y_offset if pin.cy() < control_pin.by():
control_pos = vector(self.bus_xoffset[port][control_signal].x, y_offset) self.add_path("m2", [control_pos, control_pin.bc()])
if pin_layer == "m1": elif pin.cy() > control_pin.uy():
self.add_wire(self.m1_stack, [control_pos, pin_pos]) self.add_path("m2", [control_pos, control_pin.uc()])
elif pin_layer == "m3": self.add_path(pin.layer, [control_pos, pin.center()])
self.add_wire(self.m2_stack[::-1], [control_pos, pin_pos]) self.add_via_stack_center(from_layer=pin.layer,
to_layer="m2",
offset=control_pos)
# clk to wordline_driver # clk to wordline_driver
control_signal = self.prefix + "wl_en{}".format(port) control_signal = self.prefix + "wl_en{}".format(port)
@ -950,7 +1006,7 @@ class bank(design.design):
else: else:
pin_pos = self.port_address_inst[port].get_pin("wl_en").bc() pin_pos = self.port_address_inst[port].get_pin("wl_en").bc()
mid_pos = pin_pos - vector(0, 2 * self.m2_gap) # to route down to the top of the bus mid_pos = pin_pos - vector(0, 2 * self.m2_gap) # to route down to the top of the bus
control_x_offset = self.bus_xoffset[port][control_signal].x control_x_offset = self.bus_pins[port][control_signal].cx()
control_pos = vector(control_x_offset, mid_pos.y) control_pos = vector(control_x_offset, mid_pos.y)
self.add_wire(self.m1_stack, [pin_pos, mid_pos, control_pos]) self.add_wire(self.m1_stack, [pin_pos, mid_pos, control_pos])
self.add_via_center(layers=self.m1_stack, self.add_via_center(layers=self.m1_stack,

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,39 +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 s8 tech (03/20)
try:
bitcell_power_pin_directions = cell_properties.bitcell_power_pin_directions
except AttributeError:
bitcell_power_pin_directions = None
# 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)
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
@ -137,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):
@ -149,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:
@ -161,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

@ -0,0 +1,93 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2019 Regents of the University of California
# All rights reserved.
#
from bitcell_base_array import bitcell_base_array
from sram_factory import factory
from globals import OPTS
from tech import cell_properties
class col_cap_array(bitcell_base_array):
"""
Generate a dummy row/column for the replica array.
"""
def __init__(self, cols, rows, column_offset=0, mirror=0, name=""):
super().__init__(cols, rows, name, column_offset)
self.mirror = mirror
self.no_instances = True
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
def create_netlist(self):
""" Create and connect the netlist """
self.add_modules()
self.add_pins()
self.create_instances()
def create_layout(self):
self.place_array("dummy_r{0}_c{1}", self.mirror)
self.add_layout_pins()
self.add_boundary()
self.DRC_LVS()
def add_modules(self):
""" Add the modules used in this design """
self.dummy_cell = factory.create(module_type="col_cap_{}".format(OPTS.bitcell))
self.add_mod(self.dummy_cell)
self.cell = factory.create(module_type="bitcell")
def create_instances(self):
""" Create the module instances used in this design """
self.cell_inst = {}
for col in range(self.column_size):
for row in range(self.row_size):
name = "bit_r{0}_c{1}".format(row, col)
self.cell_inst[row, col]=self.add_inst(name=name,
mod=self.dummy_cell)
self.connect_inst(self.get_bitcell_pins(col, row))
def get_bitcell_pins(self, col, row):
"""
Creates a list of connections in the bitcell,
indexed by column and row, for instance use in bitcell_array
"""
pin_name = cell_properties.bitcell.cell_1rw1r.pin
bitcell_pins = ["{0}_{1}".format(pin_name.bl0, col),
"{0}_{1}".format(pin_name.br0, col),
"{0}_{1}".format(pin_name.bl1, col),
"{0}_{1}".format(pin_name.br1, col),
"vdd"]
return bitcell_pins
def add_layout_pins(self):
""" Add the layout pins """
column_list = self.cell.get_all_bitline_names()
for col in range(self.column_size):
for cell_column in column_list:
bl_pin = self.cell_inst[0, col].get_pin(cell_column)
self.add_layout_pin(text=cell_column + "_{0}".format(col),
layer=bl_pin.layer,
offset=bl_pin.ll().scale(1, 0),
width=bl_pin.width(),
height=self.height)
# Add vdd/gnd via stacks
for row in range(self.row_size):
for col in range(self.column_size):
inst = self.cell_inst[row, col]
for pin_name in ["vdd", "gnd"]:
for pin in inst.get_pins(pin_name):
self.add_power_pin(name=pin.name,
loc=pin.center(),
start_layer=pin.layer)

View File

@ -20,7 +20,7 @@ class control_logic(design.design):
Dynamically generated Control logic for the total SRAM circuit. Dynamically generated Control logic for the total SRAM circuit.
""" """
def __init__(self, num_rows, words_per_row, word_size, sram=None, port_type="rw", name=""): def __init__(self, num_rows, words_per_row, word_size, spare_columns=None, sram=None, port_type="rw", name=""):
""" Constructor """ """ Constructor """
name = "control_logic_" + port_type name = "control_logic_" + port_type
design.design.__init__(self, name) design.design.__init__(self, name)
@ -35,7 +35,12 @@ class control_logic(design.design):
self.word_size = word_size self.word_size = word_size
self.port_type = port_type self.port_type = port_type
self.num_cols = word_size * words_per_row if not spare_columns:
self.num_spare_cols = 0
else:
self.num_spare_cols = spare_columns
self.num_cols = word_size * words_per_row + self.num_spare_cols
self.num_words = num_rows * words_per_row self.num_words = num_rows * words_per_row
self.enable_delay_chain_resizing = False self.enable_delay_chain_resizing = False
@ -102,7 +107,7 @@ class control_logic(design.design):
# clk_buf drives a flop for every address # clk_buf drives a flop for every address
addr_flops = math.log(self.num_words, 2) + math.log(self.words_per_row, 2) addr_flops = math.log(self.num_words, 2) + math.log(self.words_per_row, 2)
# plus data flops and control flops # plus data flops and control flops
num_flops = addr_flops + self.word_size + self.num_control_signals num_flops = addr_flops + self.word_size + self.num_spare_cols + self.num_control_signals
# each flop internally has a FO 5 approximately # each flop internally has a FO 5 approximately
# plus about 5 fanouts for the control logic # plus about 5 fanouts for the control logic
clock_fanout = 5 * num_flops + 5 clock_fanout = 5 * num_flops + 5
@ -130,7 +135,7 @@ class control_logic(design.design):
# s_en drives every sense amp # s_en drives every sense amp
self.sen_and3 = factory.create(module_type="pand3", self.sen_and3 = factory.create(module_type="pand3",
size=self.word_size, size=self.word_size + self.num_spare_cols,
height=dff_height) height=dff_height)
self.add_mod(self.sen_and3) self.add_mod(self.sen_and3)
@ -358,7 +363,7 @@ class control_logic(design.design):
# list of output control signals (for making a vertical bus) # list of output control signals (for making a vertical bus)
if self.port_type == "rw": if self.port_type == "rw":
self.internal_bus_list = ["rbl_bl_delay_bar", "rbl_bl_delay", "gated_clk_bar", "gated_clk_buf", "we", "clk_buf", "we_bar", "cs"] self.internal_bus_list = ["rbl_bl_delay_bar", "rbl_bl_delay", "gated_clk_bar", "gated_clk_buf", "we", "we_bar", "clk_buf", "cs"]
elif self.port_type == "r": elif self.port_type == "r":
self.internal_bus_list = ["rbl_bl_delay_bar", "rbl_bl_delay", "gated_clk_bar", "gated_clk_buf", "clk_buf", "cs_bar", "cs"] self.internal_bus_list = ["rbl_bl_delay_bar", "rbl_bl_delay", "gated_clk_bar", "gated_clk_buf", "clk_buf", "cs_bar", "cs"]
else: else:
@ -384,10 +389,10 @@ class control_logic(design.design):
height = self.control_logic_center.y - self.m2_pitch height = self.control_logic_center.y - self.m2_pitch
offset = vector(self.ctrl_dff_array.width, 0) offset = vector(self.ctrl_dff_array.width, 0)
self.rail_offsets = self.create_vertical_bus("m2", self.input_bus = self.create_vertical_bus("m2",
offset, offset,
self.internal_bus_list, self.internal_bus_list,
height) height)
def create_instances(self): def create_instances(self):
""" Create all the instances """ """ Create all the instances """
@ -493,7 +498,7 @@ class control_logic(design.design):
# Connect to the rail level with the vdd rail # Connect to the rail level with the vdd rail
# Use pen since it is in every type of control logic # Use pen since it is in every type of control logic
vdd_ypos = self.p_en_bar_nand_inst.get_pin("vdd").by() vdd_ypos = self.p_en_bar_nand_inst.get_pin("vdd").by()
in_pos = vector(self.rail_offsets["rbl_bl_delay"].x, vdd_ypos) in_pos = vector(self.input_bus["rbl_bl_delay"].cx(), vdd_ypos)
mid1 = vector(out_pos.x, in_pos.y) mid1 = vector(out_pos.x, in_pos.y)
self.add_wire(self.m1_stack, [out_pos, mid1, in_pos]) self.add_wire(self.m1_stack, [out_pos, mid1, in_pos])
self.add_via_center(layers=self.m1_stack, self.add_via_center(layers=self.m1_stack,
@ -518,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")
@ -548,17 +553,23 @@ class control_logic(design.design):
def route_gated_clk_bar(self): def route_gated_clk_bar(self):
clkbuf_map = zip(["A"], ["clk_buf"]) clkbuf_map = zip(["A"], ["clk_buf"])
self.connect_vertical_bus(clkbuf_map, self.clk_bar_inst, self.rail_offsets) 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"])
self.connect_vertical_bus(clkbuf_map, self.connect_vertical_bus(clkbuf_map,
self.gated_clk_bar_inst, self.gated_clk_bar_inst,
self.rail_offsets, self.input_bus,
self.m2_stack[::-1]) self.m2_stack[::-1])
# The pin is on M1, so we need another via as well # The pin is on M1, so we need another via as well
b_pin = self.gated_clk_bar_inst.get_pin("B") b_pin = self.gated_clk_bar_inst.get_pin("B")
@ -586,12 +597,12 @@ class control_logic(design.design):
clkbuf_map = zip(["A", "B"], ["clk_buf", "cs"]) clkbuf_map = zip(["A", "B"], ["clk_buf", "cs"])
self.connect_vertical_bus(clkbuf_map, self.connect_vertical_bus(clkbuf_map,
self.gated_clk_buf_inst, self.gated_clk_buf_inst,
self.rail_offsets) self.input_bus)
clkbuf_map = zip(["Z"], ["gated_clk_buf"]) clkbuf_map = zip(["Z"], ["gated_clk_buf"])
self.connect_vertical_bus(clkbuf_map, self.connect_vertical_bus(clkbuf_map,
self.gated_clk_buf_inst, self.gated_clk_buf_inst,
self.rail_offsets, self.input_bus,
self.m2_stack[::-1]) self.m2_stack[::-1])
# The pin is on M1, so we need another via as well # The pin is on M1, so we need another via as well
z_pin = self.gated_clk_buf_inst.get_pin("Z") z_pin = self.gated_clk_buf_inst.get_pin("Z")
@ -614,7 +625,7 @@ class control_logic(design.design):
def route_wlen(self): def route_wlen(self):
wlen_map = zip(["A"], ["gated_clk_bar"]) wlen_map = zip(["A"], ["gated_clk_bar"])
self.connect_vertical_bus(wlen_map, self.wl_en_inst, self.rail_offsets) self.connect_vertical_bus(wlen_map, self.wl_en_inst, self.input_bus)
self.connect_output(self.wl_en_inst, "Z", "wl_en") self.connect_output(self.wl_en_inst, "Z", "wl_en")
@ -639,7 +650,7 @@ class control_logic(design.design):
def route_pen(self): def route_pen(self):
in_map = zip(["A", "B"], ["gated_clk_buf", "rbl_bl_delay"]) in_map = zip(["A", "B"], ["gated_clk_buf", "rbl_bl_delay"])
self.connect_vertical_bus(in_map, self.p_en_bar_nand_inst, self.rail_offsets) self.connect_vertical_bus(in_map, self.p_en_bar_nand_inst, self.input_bus)
out_pin = self.p_en_bar_nand_inst.get_pin("Z") out_pin = self.p_en_bar_nand_inst.get_pin("Z")
out_pos = out_pin.center() out_pos = out_pin.center()
@ -682,7 +693,7 @@ class control_logic(design.design):
input_name = "cs" input_name = "cs"
sen_map = zip(["A", "B", "C"], ["rbl_bl_delay", "gated_clk_bar", input_name]) sen_map = zip(["A", "B", "C"], ["rbl_bl_delay", "gated_clk_bar", input_name])
self.connect_vertical_bus(sen_map, self.s_en_gate_inst, self.rail_offsets) self.connect_vertical_bus(sen_map, self.s_en_gate_inst, self.input_bus)
self.connect_output(self.s_en_gate_inst, "Z", "s_en") self.connect_output(self.s_en_gate_inst, "Z", "s_en")
@ -706,7 +717,7 @@ class control_logic(design.design):
self.route_output_to_bus_jogged(self.rbl_bl_delay_inv_inst, "rbl_bl_delay_bar") self.route_output_to_bus_jogged(self.rbl_bl_delay_inv_inst, "rbl_bl_delay_bar")
rbl_map = zip(["A"], ["rbl_bl_delay"]) rbl_map = zip(["A"], ["rbl_bl_delay"])
self.connect_vertical_bus(rbl_map, self.rbl_bl_delay_inv_inst, self.rail_offsets) self.connect_vertical_bus(rbl_map, self.rbl_bl_delay_inv_inst, self.input_bus)
def create_wen_row(self): def create_wen_row(self):
@ -738,7 +749,7 @@ class control_logic(design.design):
input_name = "cs" input_name = "cs"
wen_map = zip(["A", "B", "C"], [input_name, "rbl_bl_delay_bar", "gated_clk_bar"]) wen_map = zip(["A", "B", "C"], [input_name, "rbl_bl_delay_bar", "gated_clk_bar"])
self.connect_vertical_bus(wen_map, self.w_en_gate_inst, self.rail_offsets) self.connect_vertical_bus(wen_map, self.w_en_gate_inst, self.input_bus)
self.connect_output(self.w_en_gate_inst, "Z", "w_en") self.connect_output(self.w_en_gate_inst, "Z", "w_en")
@ -761,13 +772,13 @@ class control_logic(design.design):
dff_out_map = zip(["dout_bar_0", "dout_0"], ["cs", "cs_bar"]) dff_out_map = zip(["dout_bar_0", "dout_0"], ["cs", "cs_bar"])
else: else:
dff_out_map = zip(["dout_bar_0"], ["cs"]) dff_out_map = zip(["dout_bar_0"], ["cs"])
self.connect_vertical_bus(dff_out_map, self.ctrl_dff_inst, self.rail_offsets, self.m2_stack[::-1]) self.connect_vertical_bus(dff_out_map, self.ctrl_dff_inst, self.input_bus, self.m2_stack[::-1])
# Connect the clock rail to the other clock rail # Connect the clock rail to the other clock rail
# by routing in the supply rail track to avoid channel conflicts # by routing in the supply rail track to avoid channel conflicts
in_pos = self.ctrl_dff_inst.get_pin("clk").uc() in_pos = self.ctrl_dff_inst.get_pin("clk").uc()
mid_pos = in_pos + vector(0, self.and2.height) mid_pos = in_pos + vector(0, self.and2.height)
rail_pos = vector(self.rail_offsets["clk_buf"].x, mid_pos.y) rail_pos = vector(self.input_bus["clk_buf"].cx(), mid_pos.y)
self.add_wire(self.m1_stack, [in_pos, mid_pos, rail_pos]) self.add_wire(self.m1_stack, [in_pos, mid_pos, rail_pos])
self.add_via_center(layers=self.m1_stack, self.add_via_center(layers=self.m1_stack,
offset=rail_pos) offset=rail_pos)
@ -791,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")
@ -999,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")
mid1 = vector(out_pos.x, out_pos.y - 0.25 * inst.mod.height) out_pos = out_pin.center()
mid2 = vector(self.rail_offsets[name].x, mid1.y) mid1 = vector(out_pos.x, out_pos.y - 0.4 * inst.mod.height)
bus_pos = self.rail_offsets[name] mid2 = vector(self.input_bus[name].cx(), mid1.y)
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

@ -7,7 +7,7 @@
# #
import debug import debug
import design import design
from tech import parameter from tech import parameter, layer
from tech import cell_properties as props from tech import cell_properties as props
from vector import vector from vector import vector
from globals import OPTS from globals import OPTS
@ -52,7 +52,6 @@ class dff_buf(design.design):
def create_layout(self): def create_layout(self):
self.place_instances() self.place_instances()
self.width = self.inv2_inst.rx() self.width = self.inv2_inst.rx()
self.height = self.dff.height self.height = self.dff.height
self.route_wires() self.route_wires()
self.add_layout_pins() self.add_layout_pins()
@ -120,39 +119,37 @@ class dff_buf(design.design):
except AttributeError: except AttributeError:
pass pass
self.inv1_inst.place(vector(self.dff_inst.rx() + well_spacing + self.well_extend_active, 0)) self.inv1_inst.place(vector(self.dff_inst.rx() + well_spacing + self.well_extend_active, 0))
# Add INV2 to the right # Add INV2 to the right
self.inv2_inst.place(vector(self.inv1_inst.rx(), 0)) self.inv2_inst.place(vector(self.inv1_inst.rx(), 0))
def route_wires(self): def route_wires(self):
if "li" in layer:
self.route_layer = "li"
else:
self.route_layer = "m1"
# Route dff q to inv1 a # Route dff q to inv1 a
q_pin = self.dff_inst.get_pin("Q") q_pin = self.dff_inst.get_pin("Q")
a1_pin = self.inv1_inst.get_pin("A") a1_pin = self.inv1_inst.get_pin("A")
mid_x_offset = 0.5 * (a1_pin.cx() + q_pin.cx()) mid1 = vector(a1_pin.cx(), q_pin.cy())
mid1 = vector(mid_x_offset, q_pin.cy()) self.add_path(q_pin.layer, [q_pin.center(), mid1, a1_pin.center()], width=q_pin.height())
mid2 = vector(mid_x_offset, a1_pin.cy()) self.add_via_stack_center(from_layer=a1_pin.layer,
self.add_path("m3", [q_pin.center(), mid1, mid2, a1_pin.center()]) to_layer=q_pin.layer,
self.add_via_center(layers=self.m2_stack, offset=a1_pin.center())
offset=q_pin.center())
self.add_via_center(layers=self.m2_stack,
offset=a1_pin.center())
self.add_via_center(layers=self.m1_stack,
offset=a1_pin.center())
# Route inv1 z to inv2 a # Route inv1 z to inv2 a
z1_pin = self.inv1_inst.get_pin("Z") z1_pin = self.inv1_inst.get_pin("Z")
a2_pin = self.inv2_inst.get_pin("A") a2_pin = self.inv2_inst.get_pin("A")
mid_x_offset = 0.5 * (z1_pin.cx() + a2_pin.cx()) self.mid_qb_pos = vector(0.5 * (z1_pin.cx() + a2_pin.cx()), z1_pin.cy())
self.mid_qb_pos = vector(mid_x_offset, z1_pin.cy()) self.add_zjog(z1_pin.layer, z1_pin.center(), a2_pin.center())
mid2 = vector(mid_x_offset, a2_pin.cy())
self.add_path("m1", [z1_pin.center(), self.mid_qb_pos, mid2, a2_pin.center()])
def add_layout_pins(self): def add_layout_pins(self):
# Continous vdd rail along with label. # Continous vdd rail along with label.
vdd_pin=self.dff_inst.get_pin("vdd") vdd_pin=self.dff_inst.get_pin("vdd")
self.add_layout_pin(text="vdd", self.add_layout_pin(text="vdd",
layer="m1", layer=vdd_pin.layer,
offset=vdd_pin.ll(), offset=vdd_pin.ll(),
width=self.width, width=self.width,
height=vdd_pin.height()) height=vdd_pin.height())
@ -160,7 +157,7 @@ class dff_buf(design.design):
# Continous gnd rail along with label. # Continous gnd rail along with label.
gnd_pin=self.dff_inst.get_pin("gnd") gnd_pin=self.dff_inst.get_pin("gnd")
self.add_layout_pin(text="gnd", self.add_layout_pin(text="gnd",
layer="m1", layer=gnd_pin.layer,
offset=gnd_pin.ll(), offset=gnd_pin.ll(),
width=self.width, width=self.width,
height=vdd_pin.height()) height=vdd_pin.height())
@ -180,22 +177,25 @@ class dff_buf(design.design):
height=din_pin.height()) height=din_pin.height())
dout_pin = self.inv2_inst.get_pin("Z") dout_pin = self.inv2_inst.get_pin("Z")
mid_pos = dout_pin.center() + vector(self.m1_nonpref_pitch, 0) mid_pos = dout_pin.center() + vector(self.m2_nonpref_pitch, 0)
q_pos = mid_pos - vector(0, self.m2_pitch) q_pos = mid_pos - vector(0, 2 * self.m2_nonpref_pitch)
self.add_layout_pin_rect_center(text="Q", self.add_layout_pin_rect_center(text="Q",
layer="m2", layer="m2",
offset=q_pos) offset=q_pos)
self.add_path("m1", [dout_pin.center(), mid_pos, q_pos]) self.add_path(self.route_layer, [dout_pin.center(), mid_pos, q_pos])
self.add_via_center(layers=self.m1_stack, self.add_via_stack_center(from_layer=dout_pin.layer,
offset=q_pos) to_layer="m2",
offset=q_pos)
qb_pos = self.mid_qb_pos + vector(0, self.m2_pitch) qb_pos = self.mid_qb_pos + vector(0, 2 * self.m2_nonpref_pitch)
self.add_layout_pin_rect_center(text="Qb", self.add_layout_pin_rect_center(text="Qb",
layer="m2", layer="m2",
offset=qb_pos) offset=qb_pos)
self.add_path("m1", [self.mid_qb_pos, qb_pos]) self.add_path(self.route_layer, [self.mid_qb_pos, qb_pos])
self.add_via_center(layers=self.m1_stack, a2_pin = self.inv2_inst.get_pin("A")
offset=qb_pos) self.add_via_stack_center(from_layer=a2_pin.layer,
to_layer="m2",
offset=qb_pos)
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""" """Return the total capacitance (in relative units) that the clock is loaded by in the dff"""

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

@ -38,20 +38,19 @@ class dummy_array(bitcell_base_array):
def add_modules(self): def add_modules(self):
""" Add the modules used in this design """ """ Add the modules used in this design """
self.dummy_cell = factory.create(module_type="dummy_bitcell") self.dummy_cell = factory.create(module_type="dummy_{}".format(OPTS.bitcell))
self.add_mod(self.dummy_cell) self.add_mod(self.dummy_cell)
self.cell = factory.create(module_type="bitcell") self.cell = factory.create(module_type="bitcell")
def create_instances(self): def create_instances(self):
""" Create the module instances used in this design """ """ Create the module instances used in this design """
self.cell_inst = {} self.cell_inst = {}
for col in range(self.column_size): for col in range(self.column_size):
for row in range(self.row_size): for row in range(self.row_size):
name = "bit_r{0}_c{1}".format(row, col) name = "bit_r{0}_c{1}".format(row, col)
self.cell_inst[row,col]=self.add_inst(name=name, self.cell_inst[row, col]=self.add_inst(name=name,
mod=self.dummy_cell) mod=self.dummy_cell)
self.connect_inst(self.get_bitcell_pins(col, row)) self.connect_inst(self.get_bitcell_pins(col, row))
def input_load(self): def input_load(self):
@ -60,7 +59,7 @@ class dummy_array(bitcell_base_array):
def get_wordline_cin(self): def get_wordline_cin(self):
"""Get the relative input capacitance from the wordline connections in all the bitcell""" """Get the relative input capacitance from the wordline connections in all the bitcell"""
#A single wordline is connected to all the bitcells in a single row meaning the capacitance depends on the # of columns # A single wordline is connected to all the bitcells in a single row meaning the capacitance depends on the # of columns
bitcell_wl_cin = self.cell.get_wl_cin() bitcell_wl_cin = self.cell.get_wl_cin()
total_cin = bitcell_wl_cin * self.column_size total_cin = bitcell_wl_cin * self.column_size
return total_cin return total_cin

View File

@ -11,8 +11,6 @@ import math
from sram_factory import factory from sram_factory import factory
from vector import vector from vector import vector
from globals import OPTS from globals import OPTS
from errors import drc_error
from tech import cell_properties, layer
class hierarchical_decoder(design.design): class hierarchical_decoder(design.design):
@ -28,12 +26,8 @@ class hierarchical_decoder(design.design):
self.pre3x8_inst = [] self.pre3x8_inst = []
b = factory.create(module_type="bitcell") b = factory.create(module_type="bitcell")
try: self.cell_height = b.height
self.cell_multiple = cell_properties.bitcell.decoder_bitcell_multiple
except AttributeError:
self.cell_multiple = 1
self.cell_height = self.cell_multiple * b.height
self.num_outputs = num_outputs self.num_outputs = num_outputs
self.num_inputs = math.ceil(math.log(self.num_outputs, 2)) self.num_inputs = math.ceil(math.log(self.num_outputs, 2))
(self.no_of_pre2x4, self.no_of_pre3x8)=self.determine_predecodes(self.num_inputs) (self.no_of_pre2x4, self.no_of_pre3x8)=self.determine_predecodes(self.num_inputs)
@ -41,41 +35,6 @@ class hierarchical_decoder(design.design):
self.create_netlist() self.create_netlist()
if not OPTS.netlist_only: if not OPTS.netlist_only:
self.create_layout() self.create_layout()
def find_decoder_height(self):
"""
Dead code. This would dynamically determine the bitcell multiple,
but I just decided to hard code it in the tech file if it is not 1
because a DRC tool would be required even to run in front-end mode.
"""
b = factory.create(module_type="bitcell")
# Old behavior
if OPTS.netlist_only:
return (b.height, 1)
# Search for the smallest multiple that works
cell_multiple = 1
while cell_multiple < 5:
cell_height = cell_multiple * b.height
# debug.info(2,"Trying mult = {0} height={1}".format(cell_multiple, cell_height))
try:
and3 = factory.create(module_type="pand3",
height=cell_height)
except drc_error:
# debug.info(1, "Incrementing decoder height by 1 bitcell height {}".format(b.height))
pass
else:
(drc_errors, lvs_errors) = and3.DRC_LVS(force_check=True)
total_errors = drc_errors + lvs_errors
if total_errors == 0:
debug.info(1, "Decoder height is multiple of {} bitcells.".format(cell_multiple))
return (cell_height, cell_multiple)
cell_multiple += 1
else:
debug.error("Couldn't find a valid decoder height multiple.", -1)
def create_netlist(self): def create_netlist(self):
self.add_modules() self.add_modules()
@ -88,24 +47,32 @@ class hierarchical_decoder(design.design):
self.setup_layout_constants() self.setup_layout_constants()
self.place_pre_decoder() self.place_pre_decoder()
self.place_row_decoder() self.place_row_decoder()
self.height = max(self.predecoder_height, self.row_decoder_height) + self.bus_space
self.route_inputs() self.route_inputs()
self.route_outputs() self.route_outputs()
self.route_decoder_bus() self.route_decoder_bus()
self.route_vdd_gnd() self.route_vdd_gnd()
self.offset_all_coordinates() self.offset_all_coordinates()
self.width = self.and_inst[0].rx() + self.m1_space
self.add_boundary() self.add_boundary()
self.DRC_LVS() self.DRC_LVS()
def add_modules(self): def add_modules(self):
self.inv = factory.create(module_type="pinv", self.and2 = factory.create(module_type="and2_dec",
height=self.cell_height)
self.add_mod(self.inv)
self.and2 = factory.create(module_type="pand2",
height=self.cell_height) height=self.cell_height)
self.add_mod(self.and2) self.add_mod(self.and2)
self.and3 = factory.create(module_type="pand3",
self.and3 = factory.create(module_type="and3_dec",
height=self.cell_height) height=self.cell_height)
self.add_mod(self.and3) self.add_mod(self.and3)
# TBD
# self.and4 = factory.create(module_type="and4_dec")
# self.add_mod(self.and4)
self.add_decoders() self.add_decoders()
@ -176,56 +143,49 @@ class hierarchical_decoder(design.design):
-1) -1)
# Calculates height and width of pre-decoder, # Calculates height and width of pre-decoder,
if self.no_of_pre3x8 > 0: # FIXME: Update with 4x16
if self.no_of_pre3x8 > 0 and self.no_of_pre2x4 > 0:
self.predecoder_width = max(self.pre3_8.width, self.pre2_4.width)
elif self.no_of_pre3x8 > 0:
self.predecoder_width = self.pre3_8.width self.predecoder_width = self.pre3_8.width
else: else:
self.predecoder_width = self.pre2_4.width self.predecoder_width = self.pre2_4.width
self.predecoder_height = self.pre2_4.height * self.no_of_pre2x4 + self.pre3_8.height * self.no_of_pre3x8
# We may have more than one bitcell per decoder row # How much space between each predecoder
self.num_rows = math.ceil(self.num_outputs / self.cell_multiple) self.predecoder_spacing = 2 * self.and2.height
# We will place this many final decoders per row self.predecoder_height = self.pre2_4.height * self.no_of_pre2x4 + self.pre3_8.height * self.no_of_pre3x8 \
self.decoders_per_row = math.ceil(self.num_outputs / self.num_rows) + (self.no_of_pre2x4 + self.no_of_pre3x8 - 1) * self.predecoder_spacing
# We will need to use M2 and M3 in the vertical bus if we have multiple decoders per row
if self.decoders_per_row == 1: # Inputs to cells are on input layer
self.decoder_bus_pitch = self.m2_pitch # Outputs from cells are on output layer
elif self.decoders_per_row == 2: if OPTS.tech_name == "sky130":
self.decoder_bus_pitch = self.m3_pitch self.bus_layer = "m1"
self.bus_directions = "nonpref"
self.bus_pitch = self.m1_pitch
self.bus_space = self.m2_space
self.input_layer = "m2"
self.output_layer = "li"
self.output_layer_pitch = self.li_pitch
else: else:
debug.error("Insufficient layers for multi-bit height decoder.", -1) self.bus_layer = "m2"
self.bus_directions = "pref"
self.bus_pitch = self.m2_pitch
self.bus_space = self.m2_space
# These two layers being the same requires a special jog
# to ensure to conflicts with the output layers
self.input_layer = "m1"
self.output_layer = "m3"
self.output_layer_pitch = self.m3_pitch
# Calculates height and width of row-decoder # Two extra pitches between modules on left and right
if (self.num_inputs == 4 or self.num_inputs == 5): self.internal_routing_width = self.total_number_of_predecoder_outputs * self.bus_pitch + self.bus_pitch
nand_width = self.and2.width self.row_decoder_height = self.and2.height * self.num_outputs
nand_inputs = 2
else:
nand_width = self.and3.width
nand_inputs = 3
self.internal_routing_width = self.decoder_bus_pitch * (self.total_number_of_predecoder_outputs + 1)
self.row_decoder_height = self.inv.height * self.num_rows
decoder_input_wire_height = self.decoders_per_row * nand_inputs * self.m2_pitch
# print(self.decoders_per_row, nand_inputs)
# print(decoder_input_wire_height, self.cell_height)
if decoder_input_wire_height > self.cell_height:
debug.warning("Cannot fit multi-bit decoder routes per row.")
# debug.check(decoder_input_wire_height < self.cell_height, "Cannot fit multi-bit decoder routes per row.")
self.input_routing_width = (self.num_inputs + 1) * self.m2_pitch # Extra bus space for supply contacts
self.input_routing_width = self.num_inputs * self.bus_pitch + self.bus_space
# Calculates height and width of hierarchical decoder
# Add extra pitch for good measure
self.height = max(self.predecoder_height, self.row_decoder_height) + self.m2_pitch
self.width = self.input_routing_width + self.predecoder_width \
+ self.internal_routing_width \
+ self.decoders_per_row * nand_width + self.inv.width
def route_inputs(self): def route_inputs(self):
""" Create input bus for the predecoders """ """ Create input bus for the predecoders """
# inputs should be as high as the decoders
input_height = self.no_of_pre2x4 * self.pre2_4.height + self.no_of_pre3x8 * self.pre3_8.height
# Find the left-most predecoder # Find the left-most predecoder
min_x = 0 min_x = 0
if self.no_of_pre2x4 > 0: if self.no_of_pre2x4 > 0:
@ -235,10 +195,10 @@ class hierarchical_decoder(design.design):
input_offset=vector(min_x - self.input_routing_width, 0) input_offset=vector(min_x - self.input_routing_width, 0)
input_bus_names = ["addr_{0}".format(i) for i in range(self.num_inputs)] input_bus_names = ["addr_{0}".format(i) for i in range(self.num_inputs)]
self.input_bus = self.create_vertical_pin_bus(layer="m2", self.input_bus = self.create_vertical_pin_bus(layer=self.bus_layer,
offset=input_offset, offset=input_offset,
names=input_bus_names, names=input_bus_names,
length=input_height) length=self.predecoder_height)
self.route_input_to_predecodes() self.route_input_to_predecodes()
@ -248,14 +208,12 @@ class hierarchical_decoder(design.design):
for i in range(2): for i in range(2):
index = pre_num * 2 + i index = pre_num * 2 + i
input_pos = self.input_bus["addr_{}".format(index)] input_pos = self.input_bus["addr_{}".format(index)].center()
in_name = "in_{}".format(i) in_name = "in_{}".format(i)
decoder_pin = self.pre2x4_inst[pre_num].get_pin(in_name) decoder_pin = self.pre2x4_inst[pre_num].get_pin(in_name)
# To prevent conflicts, we will offset each input connect so decoder_offset = decoder_pin.center()
# that it aligns with the vdd/gnd rails
decoder_offset = decoder_pin.bc() + vector(0, (i + 1) * (self.inv.height + self.m1_pitch))
input_offset = input_pos.scale(1, 0) + decoder_offset.scale(0, 1) input_offset = input_pos.scale(1, 0) + decoder_offset.scale(0, 1)
self.route_input_bus(decoder_offset, input_offset) self.route_input_bus(decoder_offset, input_offset)
@ -264,14 +222,12 @@ class hierarchical_decoder(design.design):
for i in range(3): for i in range(3):
index = pre_num * 3 + i + self.no_of_pre2x4 * 2 index = pre_num * 3 + i + self.no_of_pre2x4 * 2
input_pos = self.input_bus["addr_{}".format(index)] input_pos = self.input_bus["addr_{}".format(index)].center()
in_name = "in_{}".format(i) in_name = "in_{}".format(i)
decoder_pin = self.pre3x8_inst[pre_num].get_pin(in_name) decoder_pin = self.pre3x8_inst[pre_num].get_pin(in_name)
# To prevent conflicts, we will offset each input connect so decoder_offset = decoder_pin.center()
# that it aligns with the vdd/gnd rails
decoder_offset = decoder_pin.bc() + vector(0, (i + 1) * (self.inv.height + self.m1_pitch))
input_offset = input_pos.scale(1, 0) + decoder_offset.scale(0, 1) input_offset = input_pos.scale(1, 0) + decoder_offset.scale(0, 1)
self.route_input_bus(decoder_offset, input_offset) self.route_input_bus(decoder_offset, input_offset)
@ -282,13 +238,14 @@ class hierarchical_decoder(design.design):
vertical M2 coordinate to the predecode inputs vertical M2 coordinate to the predecode inputs
""" """
self.add_via_stack_center(from_layer="m2", self.add_via_stack_center(from_layer=self.bus_layer,
to_layer="m3", to_layer=self.input_layer,
offset=input_offset) offset=input_offset)
self.add_via_stack_center(from_layer="m2", self.add_via_stack_center(from_layer=self.bus_layer,
to_layer="m3", to_layer=self.input_layer,
offset=output_offset) offset=output_offset,
self.add_path("m3", [input_offset, output_offset]) directions=self.bus_directions)
self.add_path(self.input_layer, [input_offset, output_offset])
def add_pins(self): def add_pins(self):
""" Add the module pins """ """ Add the module pins """
@ -363,19 +320,19 @@ class hierarchical_decoder(design.design):
if (self.num_inputs == 2): if (self.num_inputs == 2):
base = vector(-self.pre2_4.width, 0) base = vector(-self.pre2_4.width, 0)
else: else:
base= vector(-self.pre2_4.width, num * self.pre2_4.height) base= vector(-self.pre2_4.width, num * (self.pre2_4.height + self.predecoder_spacing))
self.pre2x4_inst[num].place(base - vector(2 * self.m2_pitch, 0)) self.pre2x4_inst[num].place(base)
def place_pre3x8(self, num): def place_pre3x8(self, num):
""" Place 3x8 predecoder to the left of the origin and above any 2x4 decoders """ """ Place 3x8 predecoder to the left of the origin and above any 2x4 decoders """
if (self.num_inputs == 3): if (self.num_inputs == 3):
offset = vector(-self.pre_3_8.width, 0) offset = vector(-self.pre_3_8.width, 0)
else: else:
height = self.no_of_pre2x4 * self.pre2_4.height + num * self.pre3_8.height height = self.no_of_pre2x4 * (self.pre2_4.height + self.predecoder_spacing) + num * (self.pre3_8.height + self.predecoder_spacing)
offset = vector(-self.pre3_8.width, height) offset = vector(-self.pre3_8.width, height)
self.pre3x8_inst[num].place(offset - vector(2 * self.m2_pitch, 0)) self.pre3x8_inst[num].place(offset)
def create_row_decoder(self): def create_row_decoder(self):
""" Create the row-decoder by placing AND2/AND3 and Inverters """ Create the row-decoder by placing AND2/AND3 and Inverters
@ -431,7 +388,6 @@ class hierarchical_decoder(design.design):
if (self.num_inputs >= 4): if (self.num_inputs >= 4):
self.place_decoder_and_array() self.place_decoder_and_array()
def place_decoder_and_array(self): def place_decoder_and_array(self):
""" """
Add a column of AND gates for final decode. Add a column of AND gates for final decode.
@ -452,9 +408,7 @@ class hierarchical_decoder(design.design):
Add a column of AND gates for the decoder above the predecoders. Add a column of AND gates for the decoder above the predecoders.
""" """
for inst_index in range(self.num_outputs): for row in range(self.num_outputs):
row = math.floor(inst_index / self.decoders_per_row)
dec = inst_index % self.decoders_per_row
if ((row % 2) == 0): if ((row % 2) == 0):
y_off = and_mod.height * row y_off = and_mod.height * row
mirror = "R0" mirror = "R0"
@ -462,32 +416,16 @@ class hierarchical_decoder(design.design):
y_off = and_mod.height * (row + 1) y_off = and_mod.height * (row + 1)
mirror = "MX" mirror = "MX"
x_off = self.internal_routing_width + dec * and_mod.width x_off = self.internal_routing_width
self.and_inst[inst_index].place(offset=vector(x_off, y_off), self.and_inst[row].place(offset=vector(x_off, y_off),
mirror=mirror) mirror=mirror)
def route_outputs(self): def route_outputs(self):
""" Add the pins. """ """ Add the pins. """
max_xoffset = max(x.rx() for x in self.and_inst) for row in range(self.num_outputs):
and_inst = self.and_inst[row]
for output_index in range(self.num_outputs): self.copy_layout_pin(and_inst, "Z", "decode_{0}".format(row))
row_remainder = (output_index % self.decoders_per_row)
and_inst = self.and_inst[output_index]
z_pin = and_inst.get_pin("Z")
if row_remainder == 0 and self.decoders_per_row > 1:
layer = "m3"
self.add_via_stack_center(from_layer=z_pin.layer,
to_layer="m3",
offset=z_pin.center())
else:
layer = z_pin.layer
self.add_layout_pin_segment_center(text="decode_{0}".format(output_index),
layer=layer,
start=z_pin.center(),
end=vector(max_xoffset, z_pin.cy()))
def route_decoder_bus(self): def route_decoder_bus(self):
""" """
@ -498,9 +436,9 @@ class hierarchical_decoder(design.design):
if (self.num_inputs >= 4): if (self.num_inputs >= 4):
# This leaves an offset for the predecoder output jogs # This leaves an offset for the predecoder output jogs
input_bus_names = ["predecode_{0}".format(i) for i in range(self.total_number_of_predecoder_outputs)] input_bus_names = ["predecode_{0}".format(i) for i in range(self.total_number_of_predecoder_outputs)]
self.predecode_bus = self.create_vertical_pin_bus(layer="m2", self.predecode_bus = self.create_vertical_pin_bus(layer=self.bus_layer,
pitch=self.decoder_bus_pitch, pitch=self.bus_pitch,
offset=vector(0, 0), offset=vector(self.bus_pitch, 0),
names=input_bus_names, names=input_bus_names,
length=self.height) length=self.height)
@ -518,8 +456,9 @@ class hierarchical_decoder(design.design):
predecode_name = "predecode_{}".format(pre_num * 4 + i) predecode_name = "predecode_{}".format(pre_num * 4 + i)
out_name = "out_{}".format(i) out_name = "out_{}".format(i)
pin = self.pre2x4_inst[pre_num].get_pin(out_name) pin = self.pre2x4_inst[pre_num].get_pin(out_name)
x_offset = self.pre2x4_inst[pre_num].rx() + self.m2_pitch x_offset = self.pre2x4_inst[pre_num].rx() + self.output_layer_pitch
self.route_predecode_bus_inputs(predecode_name, pin, x_offset) y_offset = self.pre2x4_inst[pre_num].by() + i * self.cell_height
self.route_predecode_bus_inputs(predecode_name, pin, x_offset, y_offset)
# FIXME: convert to connect_bus # FIXME: convert to connect_bus
for pre_num in range(self.no_of_pre3x8): for pre_num in range(self.no_of_pre3x8):
@ -527,8 +466,9 @@ class hierarchical_decoder(design.design):
predecode_name = "predecode_{}".format(pre_num * 8 + i + self.no_of_pre2x4 * 4) predecode_name = "predecode_{}".format(pre_num * 8 + i + self.no_of_pre2x4 * 4)
out_name = "out_{}".format(i) out_name = "out_{}".format(i)
pin = self.pre3x8_inst[pre_num].get_pin(out_name) pin = self.pre3x8_inst[pre_num].get_pin(out_name)
x_offset = self.pre3x8_inst[pre_num].rx() + self.m2_pitch x_offset = self.pre3x8_inst[pre_num].rx() + self.output_layer_pitch
self.route_predecode_bus_inputs(predecode_name, pin, x_offset) y_offset = self.pre3x8_inst[pre_num].by() + i * self.cell_height
self.route_predecode_bus_inputs(predecode_name, pin, x_offset, y_offset)
def route_bus_to_decoder(self): def route_bus_to_decoder(self):
""" """
@ -542,12 +482,6 @@ class hierarchical_decoder(design.design):
and the 128th AND3 is connected to [3,7,15] and the 128th AND3 is connected to [3,7,15]
""" """
output_index = 0 output_index = 0
if "li" in layer:
self.decoder_layers = [self.m1_stack, self.m2_stack[::-1]]
else:
self.decoder_layers = [self.m2_stack[::-1]]
debug.check(self.decoders_per_row <= len(self.decoder_layers), "Must have more layers for multi-height decoder.")
if (self.num_inputs == 4 or self.num_inputs == 5): if (self.num_inputs == 4 or self.num_inputs == 5):
for index_B in self.predec_groups[1]: for index_B in self.predec_groups[1]:
@ -557,13 +491,11 @@ class hierarchical_decoder(design.design):
predecode_name = "predecode_{}".format(index_A) predecode_name = "predecode_{}".format(index_A)
self.route_predecode_bus_outputs(predecode_name, self.route_predecode_bus_outputs(predecode_name,
self.and_inst[output_index].get_pin("A"), self.and_inst[output_index].get_pin("A"),
output_index, output_index)
0)
predecode_name = "predecode_{}".format(index_B) predecode_name = "predecode_{}".format(index_B)
self.route_predecode_bus_outputs(predecode_name, self.route_predecode_bus_outputs(predecode_name,
self.and_inst[output_index].get_pin("B"), self.and_inst[output_index].get_pin("B"),
output_index, output_index)
1)
output_index = output_index + 1 output_index = output_index + 1
elif (self.num_inputs > 5): elif (self.num_inputs > 5):
@ -575,18 +507,15 @@ class hierarchical_decoder(design.design):
predecode_name = "predecode_{}".format(index_A) predecode_name = "predecode_{}".format(index_A)
self.route_predecode_bus_outputs(predecode_name, self.route_predecode_bus_outputs(predecode_name,
self.and_inst[output_index].get_pin("A"), self.and_inst[output_index].get_pin("A"),
output_index, output_index)
0)
predecode_name = "predecode_{}".format(index_B) predecode_name = "predecode_{}".format(index_B)
self.route_predecode_bus_outputs(predecode_name, self.route_predecode_bus_outputs(predecode_name,
self.and_inst[output_index].get_pin("B"), self.and_inst[output_index].get_pin("B"),
output_index, output_index)
1)
predecode_name = "predecode_{}".format(index_C) predecode_name = "predecode_{}".format(index_C)
self.route_predecode_bus_outputs(predecode_name, self.route_predecode_bus_outputs(predecode_name,
self.and_inst[output_index].get_pin("C"), self.and_inst[output_index].get_pin("C"),
output_index, output_index)
2)
output_index = output_index + 1 output_index = output_index + 1
def route_vdd_gnd(self): def route_vdd_gnd(self):
@ -594,90 +523,90 @@ class hierarchical_decoder(design.design):
Add a pin for each row of vdd/gnd which are Add a pin for each row of vdd/gnd which are
must-connects next level up. must-connects next level up.
""" """
if OPTS.tech_name == "sky130":
for n in ["vdd", "gnd"]:
pins = self.and_inst[0].get_pins(n)
for pin in pins:
self.add_rect(layer=pin.layer,
offset=pin.ll() + vector(0, self.bus_space),
width=pin.width(),
height=self.height - 2 * self.bus_space)
# The vias will be placed at the right of the cells. # This adds power vias at the top of each cell
xoffset = max(x.rx() for x in self.and_inst) # (except the last to keep them inside the boundary)
for num in range(0, self.num_outputs): for i in self.and_inst[:-1]:
# Only add the power pin for the 1st in each row pins = i.get_pins(n)
if num % self.decoders_per_row: for pin in pins:
continue self.add_power_pin(name=n,
loc=pin.uc(),
for pin_name in ["vdd", "gnd"]: start_layer=pin.layer)
# The nand and inv are the same height rows... self.add_power_pin(name=n,
supply_pin = self.and_inst[num].get_pin(pin_name) loc=pin.uc(),
pin_pos = vector(xoffset, supply_pin.cy()) start_layer=pin.layer)
self.add_path(supply_pin.layer,
[supply_pin.lc(), vector(xoffset, supply_pin.cy())]) for i in self.pre2x4_inst + self.pre3x8_inst:
self.add_power_pin(name=pin_name, self.copy_layout_pin(i, n)
loc=pin_pos, else:
start_layer=supply_pin.layer) # The vias will be placed at the right of the cells.
xoffset = max(x.rx() for x in self.and_inst) + 0.5 * self.m1_space
# Copy the pins from the predecoders for row in range(0, self.num_outputs):
for pre in self.pre2x4_inst + self.pre3x8_inst: for pin_name in ["vdd", "gnd"]:
self.copy_layout_pin(pre, "vdd") # The nand and inv are the same height rows...
self.copy_layout_pin(pre, "gnd") supply_pin = self.and_inst[row].get_pin(pin_name)
pin_pos = vector(xoffset, supply_pin.cy())
self.add_power_pin(name=pin_name,
loc=pin_pos,
start_layer=supply_pin.layer)
# Copy the pins from the predecoders
for pre in self.pre2x4_inst + self.pre3x8_inst:
for pin_name in ["vdd", "gnd"]:
self.copy_layout_pin(pre, pin_name)
def route_predecode_bus_outputs(self, rail_name, pin, output_index, pin_index): def route_predecode_bus_outputs(self, rail_name, pin, row):
""" """
Connect the routing rail to the given metal1 pin Connect the routing rail to the given metal1 pin
using a routing track at the given y_offset using a routing track at the given y_offset
""" """
row_index = math.floor(output_index / self.decoders_per_row)
row_remainder = (output_index % self.decoders_per_row)
row_offset = row_index * self.and_inst[0].height
pin_pos = pin.center() pin_pos = pin.center()
rail_pos = vector(self.predecode_bus[rail_name].cx(), pin_pos.y)
# y_offset is the same for both the M2 and M4 routes so that the rail self.add_path(self.input_layer, [rail_pos, pin_pos])
# contacts align and don't cause problems
if pin_index == 0:
# Bottom pitch
y_offset = row_offset
elif pin_index == 1:
# One pitch from top
y_offset = row_offset + self.and_inst[0].height - self.m3_pitch
elif pin_index == 2:
# One pitch from bottom
y_offset = row_offset + self.m3_pitch
else:
debug.error("Invalid decoder pitch.")
rail_pos = vector(self.predecode_bus[rail_name].x, y_offset)
mid_pos = vector(pin_pos.x, rail_pos.y)
self.add_wire(self.decoder_layers[row_remainder], [rail_pos, mid_pos, pin_pos])
self.add_via_stack_center(from_layer="m2", self.add_via_stack_center(from_layer=self.bus_layer,
to_layer=self.decoder_layers[row_remainder][0], to_layer=self.input_layer,
offset=rail_pos) offset=rail_pos,
directions=self.bus_directions)
self.add_via_stack_center(from_layer=pin.layer, self.add_via_stack_center(from_layer=pin.layer,
to_layer=self.decoder_layers[row_remainder][2], to_layer=self.input_layer,
offset=pin_pos, offset=pin_pos,
directions=("H", "H")) directions=("H", "H"))
def route_predecode_bus_inputs(self, rail_name, pin, x_offset): def route_predecode_bus_inputs(self, rail_name, pin, x_offset, y_offset):
""" """
Connect the routing rail to the given metal1 pin using a jog Connect the routing rail to the given metal1 pin using a jog
to the right of the cell at the given x_offset. to the right of the cell at the given x_offset.
""" """
# This routes the pin up to the rail, basically, to avoid conflicts. # This routes the pin up to the rail, basically, to avoid conflicts.
# It would be fixed with a channel router. # It would be fixed with a channel router.
# pin_pos = pin.center() pin_pos = pin.rc()
# mid_point1 = vector(x_offset, pin_pos.y) mid_point1 = vector(x_offset, pin_pos.y)
# mid_point2 = vector(x_offset, pin_pos.y + self.inv.height / 2) mid_point2 = vector(x_offset, y_offset)
# rail_pos = vector(self.predecode_bus[rail_name].x, mid_point2.y) rail_pos = vector(self.predecode_bus[rail_name].cx(), mid_point2.y)
# self.add_path("m1", [pin_pos, mid_point1, mid_point2, rail_pos]) self.add_path(self.output_layer, [pin_pos, mid_point1, mid_point2, rail_pos])
pin_pos = pin.center() # pin_pos = pin.center()
rail_pos = vector(self.predecode_bus[rail_name].x, pin_pos.y) # rail_pos = vector(self.predecode_bus[rail_name].cx(), pin_pos.y)
self.add_path("m1", [pin_pos, rail_pos]) # self.add_path(self.output_layer, [pin_pos, rail_pos])
self.add_via_stack_center(from_layer=pin.layer, self.add_via_stack_center(from_layer=pin.layer,
to_layer="m1", to_layer=self.output_layer,
offset=pin_pos) offset=pin_pos)
self.add_via_stack_center(from_layer="m1", self.add_via_stack_center(from_layer=self.bus_layer,
to_layer="m2", to_layer=self.output_layer,
offset=rail_pos) offset=rail_pos,
directions=self.bus_directions)
def input_load(self): def input_load(self):
if self.determine_predecodes(self.num_inputs)[1]==0: if self.determine_predecodes(self.num_inputs)[1]==0:

View File

@ -8,29 +8,28 @@
import debug import debug
import design import design
import math import math
import contact
from vector import vector from vector import vector
from sram_factory import factory from sram_factory import factory
from tech import cell_properties from globals import OPTS
class hierarchical_predecode(design.design): class hierarchical_predecode(design.design):
""" """
Pre 2x4 and 3x8 decoder shared code. Pre 2x4 and 3x8 and TBD 4x16 decoder shared code.
""" """
def __init__(self, name, input_number, height=None): 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
try: self.column_decoder = False
self.cell_multiple = cell_properties.bitcell.decoder_bitcell_multiple
except AttributeError:
self.cell_multiple = 1
self.cell_height = self.cell_multiple * b.height
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)
@ -44,34 +43,71 @@ 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 """
self.inv = factory.create(module_type="pinv", debug.check(self.number_of_inputs < 4,
height=self.cell_height) "Invalid number of predecode inputs: {}".format(self.number_of_inputs))
self.add_mod(self.inv)
if self.column_decoder:
self.add_and(self.number_of_inputs) and_type = "pand{}".format(self.number_of_inputs)
inv_type = "pinv"
else:
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)
def add_and(self, inputs): # This uses the pinv_dec parameterized cell
""" Create the NAND for the predecode input stage """ self.inv = factory.create(module_type=inv_type,
if inputs==2: height=self.cell_height,
self.and_mod = factory.create(module_type="pand2", size=1)
height=self.cell_height) self.add_mod(self.inv)
elif inputs==3:
self.and_mod = factory.create(module_type="pand3", def create_layout(self):
height=self.cell_height) """ The general organization is from left to right:
else: 1) a set of M2 rails for input signals
debug.error("Invalid number of predecode inputs: {}".format(inputs), -1) 2) a set of inverters to invert input signals
3) a set of M2 rails for the vdd, gnd, inverted inputs, inputs
4) a set of AND gates for inversion
"""
self.setup_layout_constraints()
self.route_rails()
self.place_input_inverters()
self.place_and_array()
self.route()
self.add_boundary()
self.DRC_LVS()
def setup_layout_constraints(self): def setup_layout_constraints(self):
# Inputs to cells are on input layer
# Outputs from cells are on output layer
if OPTS.tech_name == "sky130":
self.bus_layer = "m1"
self.bus_directions = "nonpref"
self.bus_pitch = self.m1_pitch
self.bus_space = 1.5 * self.m1_space
self.input_layer = "m2"
self.output_layer = "li"
self.output_layer_pitch = self.li_pitch
else:
self.bus_layer = "m2"
self.bus_directions = "pref"
self.bus_pitch = self.m2_pitch
self.bus_space = self.m2_space
# This requires a special jog to ensure to conflicts with the output layers
self.input_layer = "m1"
self.output_layer = "m1"
self.output_layer_pitch = self.m1_pitch
self.height = self.number_of_outputs * self.and_mod.height self.height = self.number_of_outputs * self.and_mod.height
# x offset for input inverters # x offset for input inverters
self.x_off_inv_1 = self.number_of_inputs * self.m2_pitch + self.m2_space # +1 input for spacing for supply rail contacts
self.x_off_inv_1 = (self.number_of_inputs + 1) * self.bus_pitch + self.bus_pitch
# x offset to AND decoder includes the left rails, mid rails and inverters, plus two extra m2 pitches # x offset to AND decoder includes the left rails, mid rails and inverters, plus two extra bus pitches
self.x_off_and = self.x_off_inv_1 + self.inv.width + (2 * self.number_of_inputs + 2) * self.m2_pitch self.x_off_and = self.x_off_inv_1 + self.inv.width + (2 * self.number_of_inputs + 2) * self.bus_pitch
# x offset to output inverters # x offset to output inverters
self.width = self.x_off_and + self.and_mod.width self.width = self.x_off_and + self.and_mod.width
@ -79,28 +115,30 @@ class hierarchical_predecode(design.design):
def route_rails(self): def route_rails(self):
""" Create all of the rails for the inputs and vdd/gnd/inputs_bar/inputs """ """ Create all of the rails for the inputs and vdd/gnd/inputs_bar/inputs """
input_names = ["in_{}".format(x) for x in range(self.number_of_inputs)] input_names = ["in_{}".format(x) for x in range(self.number_of_inputs)]
offset = vector(0.5 * self.m2_width, self.m3_pitch) # Offsets for the perimeter spacing to other modules
self.input_rails = self.create_vertical_pin_bus(layer="m2", # This uses m3 pitch to leave space for power routes
offset=offset, offset = vector(self.bus_pitch, self.bus_pitch)
names=input_names, self.input_rails = self.create_vertical_bus(layer=self.bus_layer,
length=self.height - 2 * self.m1_pitch) offset=offset,
names=input_names,
length=self.height - 2 * self.bus_pitch)
invert_names = ["Abar_{}".format(x) for x in range(self.number_of_inputs)] invert_names = ["Abar_{}".format(x) for x in range(self.number_of_inputs)]
non_invert_names = ["A_{}".format(x) for x in range(self.number_of_inputs)] non_invert_names = ["A_{}".format(x) for x in range(self.number_of_inputs)]
decode_names = invert_names + non_invert_names decode_names = invert_names + non_invert_names
offset = vector(self.x_off_inv_1 + self.inv.width + 2 * self.m2_pitch, self.m3_pitch) offset = vector(self.x_off_inv_1 + self.inv.width + self.bus_pitch, self.bus_pitch)
self.decode_rails = self.create_vertical_bus(layer="m2", self.decode_rails = self.create_vertical_bus(layer=self.bus_layer,
offset=offset, offset=offset,
names=decode_names, names=decode_names,
length=self.height - 2 * self.m1_pitch) length=self.height - 2 * self.bus_pitch)
def create_input_inverters(self): def create_input_inverters(self):
""" Create the input inverters to invert input signals for the decode stage. """ """ Create the input inverters to invert input signals for the decode stage. """
self.in_inst = [] self.inv_inst = []
for inv_num in range(self.number_of_inputs): for inv_num in range(self.number_of_inputs):
name = "pre_inv_{0}".format(inv_num) name = "pre_inv_{0}".format(inv_num)
self.in_inst.append(self.add_inst(name=name, self.inv_inst.append(self.add_inst(name=name,
mod=self.inv)) mod=self.inv))
self.connect_inst(["in_{0}".format(inv_num), self.connect_inst(["in_{0}".format(inv_num),
"inbar_{0}".format(inv_num), "inbar_{0}".format(inv_num),
"vdd", "gnd"]) "vdd", "gnd"])
@ -108,6 +146,7 @@ class hierarchical_predecode(design.design):
def place_input_inverters(self): def place_input_inverters(self):
""" Place the input inverters to invert input signals for the decode stage. """ """ Place the input inverters to invert input signals for the decode stage. """
for inv_num in range(self.number_of_inputs): for inv_num in range(self.number_of_inputs):
if (inv_num % 2 == 0): if (inv_num % 2 == 0):
y_off = inv_num * (self.inv.height) y_off = inv_num * (self.inv.height)
mirror = "R0" mirror = "R0"
@ -115,8 +154,8 @@ class hierarchical_predecode(design.design):
y_off = (inv_num + 1) * (self.inv.height) y_off = (inv_num + 1) * (self.inv.height)
mirror="MX" mirror="MX"
offset = vector(self.x_off_inv_1, y_off) offset = vector(self.x_off_inv_1, y_off)
self.in_inst[inv_num].place(offset=offset, self.inv_inst[inv_num].place(offset=offset,
mirror=mirror) mirror=mirror)
def create_and_array(self, connections): def create_and_array(self, connections):
""" Create the AND stage for the decodes """ """ Create the AND stage for the decodes """
@ -151,22 +190,31 @@ class hierarchical_predecode(design.design):
def route_inputs_to_rails(self): def route_inputs_to_rails(self):
""" Route the uninverted inputs to the second set of rails """ """ Route the uninverted inputs to the second set of rails """
top_and_gate = self.and_inst[-1]
for num in range(self.number_of_inputs): for num in range(self.number_of_inputs):
# route one signal next to each vdd/gnd rail since this is if num == 0:
# typically where the p/n devices are and there are no pin = top_and_gate.get_pin("A")
# pins in the and gates. elif num == 1:
y_offset = (num + self.number_of_inputs) * self.inv.height + contact.m2_via.width + self.m2_space pin = top_and_gate.get_pin("B")
elif num == 2:
pin = top_and_gate.get_pin("C")
elif num == 3:
pin = top_and_gate.get_pin("D")
else:
debug.error("Too many inputs for predecoder.", -1)
y_offset = pin.cy()
in_pin = "in_{}".format(num) in_pin = "in_{}".format(num)
a_pin = "A_{}".format(num) a_pin = "A_{}".format(num)
in_pos = vector(self.input_rails[in_pin].x, y_offset) in_pos = vector(self.input_rails[in_pin].cx(), y_offset)
a_pos = vector(self.decode_rails[a_pin].x, y_offset) a_pos = vector(self.decode_rails[a_pin].cx(), y_offset)
self.add_path("m1", [in_pos, a_pos]) self.add_path(self.input_layer, [in_pos, a_pos])
self.add_via_stack_center(from_layer="m1", self.add_via_stack_center(from_layer=self.input_layer,
to_layer="m2", to_layer=self.bus_layer,
offset=[self.input_rails[in_pin].x, y_offset]) offset=[self.input_rails[in_pin].cx(), y_offset])
self.add_via_stack_center(from_layer="m1", self.add_via_stack_center(from_layer=self.input_layer,
to_layer="m2", to_layer=self.bus_layer,
offset=[self.decode_rails[a_pin].x, y_offset]) offset=[self.decode_rails[a_pin].cx(), y_offset])
def route_output_and(self): def route_output_and(self):
""" """
@ -188,31 +236,43 @@ class hierarchical_predecode(design.design):
for inv_num in range(self.number_of_inputs): for inv_num in range(self.number_of_inputs):
out_pin = "Abar_{}".format(inv_num) out_pin = "Abar_{}".format(inv_num)
in_pin = "in_{}".format(inv_num) in_pin = "in_{}".format(inv_num)
inv_out_pin = self.inv_inst[inv_num].get_pin("Z")
# 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.
y_offset = (inv_num + 1) * self.inv.height - 3 * self.m1_space
inv_out_pin = self.in_inst[inv_num].get_pin("Z")
inv_out_pos = inv_out_pin.rc() inv_out_pos = inv_out_pin.rc()
right_pos = inv_out_pos + vector(self.inv.width - self.inv.get_pin("Z").lx(), 0) y_offset = (inv_num + 1) * self.inv.height - self.output_layer_pitch
rail_pos = vector(self.decode_rails[out_pin].x, y_offset) right_pos = inv_out_pos + vector(self.inv.width - self.inv.get_pin("Z").rx(), 0)
self.add_path(inv_out_pin.layer, [inv_out_pos, right_pos, vector(right_pos.x, y_offset), rail_pos]) rail_pos = vector(self.decode_rails[out_pin].cx(), y_offset)
self.add_path(self.output_layer, [inv_out_pos, right_pos, vector(right_pos.x, y_offset), rail_pos])
self.add_via_stack_center(from_layer=inv_out_pin.layer, self.add_via_stack_center(from_layer=inv_out_pin.layer,
to_layer="m2", to_layer=self.output_layer,
offset=rail_pos) offset=inv_out_pos)
self.add_via_stack_center(from_layer=self.output_layer,
to_layer=self.bus_layer,
offset=rail_pos,
directions=self.bus_directions)
# route input # route input
pin = self.in_inst[inv_num].get_pin("A") pin = self.inv_inst[inv_num].get_pin("A")
inv_in_pos = pin.lc() inv_in_pos = pin.center()
in_pos = vector(self.input_rails[in_pin].x, inv_in_pos.y) in_pos = vector(self.input_rails[in_pin].cx(), inv_in_pos.y)
self.add_path("m1", [in_pos, inv_in_pos]) self.add_path(self.input_layer, [in_pos, inv_in_pos])
self.add_via_stack_center(from_layer=pin.layer, self.add_via_stack_center(from_layer=pin.layer,
to_layer="m1", to_layer=self.input_layer,
offset=inv_in_pos) offset=inv_in_pos)
self.add_via_stack_center(from_layer="m1", via=self.add_via_stack_center(from_layer=self.input_layer,
to_layer="m2", to_layer=self.bus_layer,
offset=in_pos) offset=in_pos)
# Create the input pin at this location on the rail
self.add_layout_pin_rect_center(text=in_pin,
layer=self.bus_layer,
offset=in_pos,
height=via.mod.second_layer_height,
width=via.mod.second_layer_width)
def route_and_to_rails(self): def route_and_to_rails(self):
# This 2D array defines the connection mapping # This 2D array defines the connection mapping
@ -230,44 +290,68 @@ class hierarchical_predecode(design.design):
for rail_pin, gate_pin in zip(index_lst, gate_lst): for rail_pin, gate_pin in zip(index_lst, gate_lst):
pin = self.and_inst[k].get_pin(gate_pin) pin = self.and_inst[k].get_pin(gate_pin)
pin_pos = pin.center() pin_pos = pin.center()
rail_pos = vector(self.decode_rails[rail_pin].x, pin_pos.y) rail_pos = vector(self.decode_rails[rail_pin].cx(), pin_pos.y)
self.add_path("m1", [rail_pos, pin_pos]) self.add_path(self.input_layer, [rail_pos, pin_pos])
self.add_via_stack_center(from_layer="m1", self.add_via_stack_center(from_layer=self.input_layer,
to_layer="m2", to_layer=self.bus_layer,
offset=rail_pos) offset=rail_pos,
directions=self.bus_directions)
if gate_pin == "A": if gate_pin == "A":
direction = None direction = None
else: else:
direction = ("H", "H") direction = ("H", "H")
self.add_via_stack_center(from_layer=pin.layer, self.add_via_stack_center(from_layer=pin.layer,
to_layer="m1", to_layer=self.input_layer,
offset=pin_pos, offset=pin_pos,
directions=direction) directions=direction)
def route_vdd_gnd(self): def route_vdd_gnd(self):
""" 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. """
# Find the x offsets for where the vias/pins should be placed # In sky130, we use hand-made decoder cells with vertical power
in_xoffset = self.in_inst[0].rx() + self.m1_space if OPTS.tech_name == "sky130" and not self.column_decoder:
# out_xoffset = self.and_inst[0].cx() + self.m1_space
for num in range(0, self.number_of_outputs):
# this will result in duplicate polygons for rails, but who cares
# Route both supplies
for n in ["vdd", "gnd"]: for n in ["vdd", "gnd"]:
and_pin = self.and_inst[num].get_pin(n) # This makes a wire from top to bottom for both inv and and gates
supply_offset = and_pin.ll().scale(0, 1) for i in [self.inv_inst, self.and_inst]:
self.add_rect(layer=and_pin.layer, bot_pins = i[0].get_pins(n)
offset=supply_offset, top_pins = i[-1].get_pins(n)
width=self.and_inst[num].rx()) for (bot_pin, top_pin) in zip(bot_pins, top_pins):
self.add_rect(layer=bot_pin.layer,
offset=vector(bot_pin.lx(), self.bus_pitch),
width=bot_pin.width(),
height=top_pin.uy() - self.bus_pitch)
# This adds power vias at the top of each cell
# (except the last to keep them inside the boundary)
for i in self.inv_inst[:-1:2] + self.and_inst[:-1:2]:
pins = i.get_pins(n)
for pin in pins:
self.add_power_pin(name=n,
loc=pin.uc(),
start_layer=pin.layer)
self.add_power_pin(name=n,
loc=pin.uc(),
start_layer=pin.layer)
# In other techs, we are using standard cell decoder cells with horizontal power
else:
for num in range(0, self.number_of_outputs):
# Add pins in two locations # Route both supplies
for xoffset in [in_xoffset]: for n in ["vdd", "gnd"]:
pin_pos = vector(xoffset, and_pin.cy()) and_pins = self.and_inst[num].get_pins(n)
self.add_power_pin(name=n, for and_pin in and_pins:
loc=pin_pos, self.add_segment_center(layer=and_pin.layer,
start_layer=and_pin.layer) start=vector(0, and_pin.cy()),
end=vector(self.width, and_pin.cy()))
# Add pins in two locations
for xoffset in [self.inv_inst[0].lx() - self.bus_space,
self.and_inst[0].lx() - self.bus_space]:
pin_pos = vector(xoffset, and_pin.cy())
self.add_power_pin(name=n,
loc=pin_pos,
start_layer=and_pin.layer)

View File

@ -5,13 +5,10 @@
# (acting for and on behalf of Oklahoma State University) # (acting for and on behalf of Oklahoma State University)
# All rights reserved. # All rights reserved.
# #
from tech import drc
import debug
import design
from vector import vector
from hierarchical_predecode import hierarchical_predecode from hierarchical_predecode import hierarchical_predecode
from globals import OPTS from globals import OPTS
class hierarchical_predecode2x4(hierarchical_predecode): class hierarchical_predecode2x4(hierarchical_predecode):
""" """
Pre 2x4 decoder used in hierarchical_decoder. Pre 2x4 decoder used in hierarchical_decoder.
@ -33,21 +30,6 @@ class hierarchical_predecode2x4(hierarchical_predecode):
["in_0", "in_1", "out_3", "vdd", "gnd"]] ["in_0", "in_1", "out_3", "vdd", "gnd"]]
self.create_and_array(connections) self.create_and_array(connections)
def create_layout(self):
""" The general organization is from left to right:
1) a set of M2 rails for input signals
2) a set of inverters to invert input signals
3) a set of M2 rails for the vdd, gnd, inverted inputs, inputs
4) a set of AND gates for inversion
"""
self.setup_layout_constraints()
self.route_rails()
self.place_input_inverters()
self.place_and_array()
self.route()
self.add_boundary()
self.DRC_LVS()
def get_and_input_line_combination(self): def get_and_input_line_combination(self):
""" These are the decoder connections of the AND gates to the A,B pins """ """ These are the decoder connections of the AND gates to the A,B pins """
combination = [["Abar_0", "Abar_1"], combination = [["Abar_0", "Abar_1"],

View File

@ -5,13 +5,10 @@
# (acting for and on behalf of Oklahoma State University) # (acting for and on behalf of Oklahoma State University)
# All rights reserved. # All rights reserved.
# #
from tech import drc
import debug
import design
from vector import vector
from hierarchical_predecode import hierarchical_predecode from hierarchical_predecode import hierarchical_predecode
from globals import OPTS from globals import OPTS
class hierarchical_predecode3x8(hierarchical_predecode): class hierarchical_predecode3x8(hierarchical_predecode):
""" """
Pre 3x8 decoder used in hierarchical_decoder. Pre 3x8 decoder used in hierarchical_decoder.
@ -37,22 +34,6 @@ class hierarchical_predecode3x8(hierarchical_predecode):
["in_0", "in_1", "in_2", "out_7", "vdd", "gnd"]] ["in_0", "in_1", "in_2", "out_7", "vdd", "gnd"]]
self.create_and_array(connections) self.create_and_array(connections)
def create_layout(self):
"""
The general organization is from left to right:
1) a set of M2 rails for input signals
2) a set of inverters to invert input signals
3) a set of M2 rails for the vdd, gnd, inverted inputs, inputs
4) a set of NAND gates for inversion
"""
self.setup_layout_constraints()
self.route_rails()
self.place_input_inverters()
self.place_and_array()
self.route()
self.add_boundary()
self.DRC_LVS()
def get_and_input_line_combination(self): def get_and_input_line_combination(self):
""" These are the decoder connections of the NAND gates to the A,B,C pins """ """ These are the decoder connections of the NAND gates to the A,B,C pins """
combination = [["Abar_0", "Abar_1", "Abar_2"], combination = [["Abar_0", "Abar_1", "Abar_2"],

View File

@ -0,0 +1,64 @@
# 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.
#
from hierarchical_predecode import hierarchical_predecode
from globals import OPTS
class hierarchical_predecode4x16(hierarchical_predecode):
"""
Pre 4x16 decoder used in hierarchical_decoder.
"""
def __init__(self, name, height=None):
hierarchical_predecode.__init__(self, name, 4, height)
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
def create_netlist(self):
self.add_pins()
self.add_modules()
self.create_input_inverters()
connections=[["inbar_0", "inbar_1", "inbar_2", "inbar_3", "out_0", "vdd", "gnd"],
["in_0", "inbar_1", "inbar_2", "inbar_3", "out_1", "vdd", "gnd"],
["inbar_0", "in_1", "inbar_2", "inbar_3", "out_2", "vdd", "gnd"],
["in_0", "in_1", "inbar_2", "inbar_3", "out_3", "vdd", "gnd"],
["inbar_0", "inbar_1", "in_2", "inbar_3", "out_4", "vdd", "gnd"],
["in_0", "inbar_1", "in_2", "inbar_3", "out_5", "vdd", "gnd"],
["inbar_0", "in_1", "in_2", "inbar_3", "out_6", "vdd", "gnd"],
["in_0", "in_1", "in_2", "inbar_3", "out_7", "vdd", "gnd"],
["inbar_0", "inbar_1", "inbar_2", "in_3", "out_0", "vdd", "gnd"],
["in_0", "inbar_1", "inbar_2", "in_3", "out_1", "vdd", "gnd"],
["inbar_0", "in_1", "inbar_2", "in_3", "out_2", "vdd", "gnd"],
["in_0", "in_1", "inbar_2", "in_3", "out_3", "vdd", "gnd"],
["inbar_0", "inbar_1", "in_2", "in_3", "out_4", "vdd", "gnd"],
["in_0", "inbar_1", "in_2", "in_3", "out_5", "vdd", "gnd"],
["inbar_0", "in_1", "in_2", "in_3", "out_6", "vdd", "gnd"],
["in_0", "in_1", "in_2", "in_3", "out_7", "vdd", "gnd"] ]
self.create_and_array(connections)
def get_and_input_line_combination(self):
""" These are the decoder connections of the AND gates to the A,B pins """
combination = [["Abar_0", "Abar_1", "Abar_2", "Abar_3"],
["A_0", "Abar_1", "Abar_2", "Abar_3"],
["Abar_0", "A_1", "Abar_2", "Abar_3"],
["A_0", "A_1", "Abar_2", "Abar_3"],
["Abar_0", "Abar_1", "A_2" , "Abar_3"],
["A_0", "Abar_1", "A_2" , "Abar_3"],
["Abar_0", "A_1", "A_2" , "Abar_3"],
["A_0", "A_1", "A_2" , "Abar_3"],
["Abar_0", "Abar_1", "Abar_2", "A_3"],
["A_0", "Abar_1", "Abar_2", "A_3"],
["Abar_0", "A_1", "Abar_2", "A_3"],
["A_0", "A_1", "Abar_2", "A_3"],
["Abar_0", "Abar_1", "A_2", "A_3"],
["A_0", "Abar_1", "A_2", "A_3"],
["Abar_0", "A_1", "A_2", "A_3"],
["A_0", "A_1", "A_2", "A_3"]]
return combination

View File

@ -3,12 +3,12 @@
# Copyright (c) 2016-2019 Regents of the University of California # Copyright (c) 2016-2019 Regents of the University of California
# All rights reserved. # All rights reserved.
# #
from math import log from math import log, ceil
import debug import debug
import design import design
from sram_factory import factory from sram_factory import factory
from vector import vector from vector import vector
from tech import layer
from globals import OPTS from globals import OPTS
@ -21,7 +21,7 @@ class port_address(design.design):
self.num_cols = cols self.num_cols = cols
self.num_rows = rows self.num_rows = rows
self.addr_size = int(log(self.num_rows, 2)) self.addr_size = ceil(log(self.num_rows, 2))
if name == "": if name == "":
name = "port_address_{0}_{1}".format(cols, rows) name = "port_address_{0}_{1}".format(cols, rows)
@ -41,6 +41,10 @@ class port_address(design.design):
self.create_wordline_driver() self.create_wordline_driver()
def create_layout(self): def create_layout(self):
if "li" in layer:
self.route_layer = "li"
else:
self.route_layer = "m1"
self.place_instances() self.place_instances()
self.route_layout() self.route_layout()
self.DRC_LVS() self.DRC_LVS()
@ -85,11 +89,19 @@ class port_address(design.design):
def route_internal(self): def route_internal(self):
for row in range(self.num_rows): for row in range(self.num_rows):
# The pre/post is to access the pin from "outside" the cell to avoid DRCs # The pre/post is to access the pin from "outside" the cell to avoid DRCs
decoder_out_pos = self.row_decoder_inst.get_pin("decode_{}".format(row)).rc() decoder_out_pin = self.row_decoder_inst.get_pin("decode_{}".format(row))
driver_in_pos = self.wordline_driver_inst.get_pin("in_{}".format(row)).lc() decoder_out_pos = decoder_out_pin.rc()
mid1 = decoder_out_pos.scale(0.5, 1) + driver_in_pos.scale(0.5, 0) driver_in_pin = self.wordline_driver_inst.get_pin("in_{}".format(row))
mid2 = decoder_out_pos.scale(0.5, 0) + driver_in_pos.scale(0.5, 1) driver_in_pos = driver_in_pin.lc()
self.add_path("m1", [decoder_out_pos, mid1, mid2, driver_in_pos]) self.add_zjog(self.route_layer, decoder_out_pos, driver_in_pos, var_offset=0.3)
self.add_via_stack_center(from_layer=decoder_out_pin.layer,
to_layer=self.route_layer,
offset=decoder_out_pos)
self.add_via_stack_center(from_layer=driver_in_pin.layer,
to_layer=self.route_layer,
offset=driver_in_pos)
def add_modules(self): def add_modules(self):
@ -97,7 +109,7 @@ class port_address(design.design):
num_outputs=self.num_rows) num_outputs=self.num_rows)
self.add_mod(self.row_decoder) self.add_mod(self.row_decoder)
self.wordline_driver = factory.create(module_type="wordline_driver", self.wordline_driver = factory.create(module_type="wordline_driver_array",
rows=self.num_rows, rows=self.num_rows,
cols=self.num_cols) cols=self.num_cols)
self.add_mod(self.wordline_driver) self.add_mod(self.wordline_driver)
@ -139,7 +151,6 @@ class port_address(design.design):
row_decoder_offset = vector(0, 0) row_decoder_offset = vector(0, 0)
wordline_driver_offset = vector(self.row_decoder.width, 0) wordline_driver_offset = vector(self.row_decoder.width, 0)
self.wordline_driver_inst.place(wordline_driver_offset) self.wordline_driver_inst.place(wordline_driver_offset)
self.row_decoder_inst.place(row_decoder_offset) self.row_decoder_inst.place(row_decoder_offset)

View File

@ -19,7 +19,7 @@ class port_data(design.design):
""" """
def __init__(self, sram_config, port, name=""): def __init__(self, sram_config, port, name=""):
sram_config.set_local_config(self) sram_config.set_local_config(self)
self.port = port self.port = port
if self.write_size is not None: if self.write_size is not None:
@ -27,6 +27,9 @@ class port_data(design.design):
else: else:
self.num_wmasks = 0 self.num_wmasks = 0
if self.num_spare_cols is None:
self.num_spare_cols = 0
if name == "": if name == "":
name = "port_data_{0}".format(self.port) name = "port_data_{0}".format(self.port)
design.design.__init__(self, name) design.design.__init__(self, name)
@ -102,7 +105,7 @@ class port_data(design.design):
self.DRC_LVS() self.DRC_LVS()
def add_pins(self): def add_pins(self):
""" Adding pins for port address module""" """ Adding pins for port data module"""
self.add_pin("rbl_bl", "INOUT") self.add_pin("rbl_bl", "INOUT")
self.add_pin("rbl_br", "INOUT") self.add_pin("rbl_br", "INOUT")
@ -111,11 +114,17 @@ class port_data(design.design):
br_name = self.get_br_name(self.port) br_name = self.get_br_name(self.port)
self.add_pin("{0}_{1}".format(bl_name, bit), "INOUT") self.add_pin("{0}_{1}".format(bl_name, bit), "INOUT")
self.add_pin("{0}_{1}".format(br_name, bit), "INOUT") self.add_pin("{0}_{1}".format(br_name, bit), "INOUT")
for bit in range(self.num_spare_cols):
bl_name = self.get_bl_name(self.port)
br_name = self.get_br_name(self.port)
self.add_pin("spare{0}_{1}".format(bl_name, bit), "INOUT")
self.add_pin("spare{0}_{1}".format(br_name, bit), "INOUT")
if self.port in self.read_ports: if self.port in self.read_ports:
for bit in range(self.word_size): for bit in range(self.word_size + self.num_spare_cols):
self.add_pin("dout_{}".format(bit), "OUTPUT") self.add_pin("dout_{}".format(bit), "OUTPUT")
if self.port in self.write_ports: if self.port in self.write_ports:
for bit in range(self.word_size): for bit in range(self.word_size + self.num_spare_cols):
self.add_pin("din_{}".format(bit), "INPUT") self.add_pin("din_{}".format(bit), "INPUT")
# Will be empty if no col addr lines # Will be empty if no col addr lines
sel_names = ["sel_{}".format(x) for x in range(self.num_col_addr_lines)] sel_names = ["sel_{}".format(x) for x in range(self.num_col_addr_lines)]
@ -128,6 +137,8 @@ class port_data(design.design):
self.add_pin("w_en", "INPUT") self.add_pin("w_en", "INPUT")
for bit in range(self.num_wmasks): for bit in range(self.num_wmasks):
self.add_pin("bank_wmask_{}".format(bit), "INPUT") self.add_pin("bank_wmask_{}".format(bit), "INPUT")
for bit in range(self.num_spare_cols):
self.add_pin("bank_spare_wen{}".format(bit), "INPUT")
self.add_pin("vdd", "POWER") self.add_pin("vdd", "POWER")
self.add_pin("gnd", "GROUND") self.add_pin("gnd", "GROUND")
@ -178,21 +189,27 @@ class port_data(design.design):
# Extra column +1 is for RBL # Extra column +1 is for RBL
# Precharge will be shifted left if needed # Precharge will be shifted left if needed
# Column offset is set to port so extra column can be on left or right
# and mirroring happens correctly
self.precharge_array = factory.create(module_type="precharge_array", self.precharge_array = factory.create(module_type="precharge_array",
columns=self.num_cols + 1, columns=self.num_cols + self.num_spare_cols + 1,
bitcell_bl=self.bl_names[self.port], bitcell_bl=self.bl_names[self.port],
bitcell_br=self.br_names[self.port]) bitcell_br=self.br_names[self.port],
column_offset=self.port - 1)
self.add_mod(self.precharge_array) self.add_mod(self.precharge_array)
if self.port in self.read_ports: if self.port in self.read_ports:
# RBLs don't get a sense amp
self.sense_amp_array = factory.create(module_type="sense_amp_array", self.sense_amp_array = factory.create(module_type="sense_amp_array",
word_size=self.word_size, word_size=self.word_size,
words_per_row=self.words_per_row) words_per_row=self.words_per_row,
num_spare_cols=self.num_spare_cols)
self.add_mod(self.sense_amp_array) self.add_mod(self.sense_amp_array)
else: else:
self.sense_amp_array = None self.sense_amp_array = None
if self.col_addr_size > 0: if self.col_addr_size > 0:
# RBLs dont get a col mux
self.column_mux_array = factory.create(module_type="column_mux_array", self.column_mux_array = factory.create(module_type="column_mux_array",
columns=self.num_cols, columns=self.num_cols,
word_size=self.word_size, word_size=self.word_size,
@ -203,17 +220,19 @@ class port_data(design.design):
self.column_mux_array = None self.column_mux_array = None
if self.port in self.write_ports: if self.port in self.write_ports:
# RBLs dont get a write driver
self.write_driver_array = factory.create(module_type="write_driver_array", self.write_driver_array = factory.create(module_type="write_driver_array",
columns=self.num_cols, columns=self.num_cols,
word_size=self.word_size, word_size=self.word_size,
write_size=self.write_size) write_size=self.write_size,
num_spare_cols=self.num_spare_cols)
self.add_mod(self.write_driver_array) self.add_mod(self.write_driver_array)
if self.write_size is not None: if self.write_size is not None:
# RBLs don't get a write mask
self.write_mask_and_array = factory.create(module_type="write_mask_and_array", self.write_mask_and_array = factory.create(module_type="write_mask_and_array",
columns=self.num_cols, columns=self.num_cols,
word_size=self.word_size, word_size=self.word_size,
write_size=self.write_size, write_size=self.write_size)
port = self.port)
self.add_mod(self.write_mask_and_array) self.add_mod(self.write_mask_and_array)
else: else:
self.write_mask_and_array = None self.write_mask_and_array = None
@ -246,12 +265,6 @@ class port_data(design.design):
self.precharge = factory.create(module_type="precharge", self.precharge = factory.create(module_type="precharge",
bitcell_bl=self.bl_names[0], bitcell_bl=self.bl_names[0],
bitcell_br=self.br_names[0]) bitcell_br=self.br_names[0])
# We create a dummy here to get bl/br names to add those pins to this
# module, which happens before we create the real precharge_array
self.precharge_array = factory.create(module_type="precharge_array",
columns=self.num_cols + 1,
bitcell_bl=self.bl_names[self.port],
bitcell_br=self.br_names[self.port])
def create_precharge_array(self): def create_precharge_array(self):
""" Creating Precharge """ """ Creating Precharge """
@ -272,6 +285,10 @@ class port_data(design.design):
for bit in range(self.num_cols): for bit in range(self.num_cols):
temp.append("{0}_{1}".format(bl_name, bit)) temp.append("{0}_{1}".format(bl_name, bit))
temp.append("{0}_{1}".format(br_name, bit)) temp.append("{0}_{1}".format(br_name, bit))
for bit in range(self.num_spare_cols):
temp.append("spare{0}_{1}".format(bl_name, bit))
temp.append("spare{0}_{1}".format(br_name, bit))
# Use right BLs for RBL # Use right BLs for RBL
if self.port==1: if self.port==1:
@ -297,7 +314,6 @@ class port_data(design.design):
for col in range(self.num_cols): for col in range(self.num_cols):
temp.append("{0}_{1}".format(bl_name, col)) temp.append("{0}_{1}".format(bl_name, col))
temp.append("{0}_{1}".format(br_name, col)) temp.append("{0}_{1}".format(br_name, col))
for word in range(self.words_per_row): for word in range(self.words_per_row):
temp.append("sel_{}".format(word)) temp.append("sel_{}".format(word))
for bit in range(self.word_size): for bit in range(self.word_size):
@ -330,8 +346,14 @@ class port_data(design.design):
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))
temp.extend(["s_en", "vdd", "gnd"]) for bit in range(self.num_spare_cols):
temp.append("dout_{}".format(self.word_size + bit))
temp.append("spare{0}_{1}".format(bl_name, bit))
temp.append("spare{0}_{1}".format(br_name, bit))
temp.append("s_en")
temp.extend(["vdd", "gnd"])
self.connect_inst(temp) self.connect_inst(temp)
def place_sense_amp_array(self, offset): def place_sense_amp_array(self, offset):
@ -346,7 +368,7 @@ class port_data(design.design):
br_name = self.get_br_name(self.port) br_name = self.get_br_name(self.port)
temp = [] temp = []
for bit in range(self.word_size): for bit in range(self.word_size + self.num_spare_cols):
temp.append("din_{}".format(bit)) temp.append("din_{}".format(bit))
for bit in range(self.word_size): for bit in range(self.word_size):
@ -357,9 +379,20 @@ class port_data(design.design):
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):
temp.append("spare{0}_{1}".format(bl_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):
temp.append("wdriver_sel_{}".format(i)) temp.append("wdriver_sel_{}".format(i))
for i in range(self.num_spare_cols):
temp.append("bank_spare_wen{}".format(i))
elif self.num_spare_cols and not self.write_size:
temp.append("w_en")
for i in range(self.num_spare_cols):
temp.append("bank_spare_wen{}".format(i))
else: else:
temp.append("w_en") temp.append("w_en")
temp.extend(["vdd", "gnd"]) temp.extend(["vdd", "gnd"])
@ -444,8 +477,7 @@ class port_data(design.design):
def route_sense_amp_out(self, port): def route_sense_amp_out(self, port):
""" Add pins for the sense amp output """ """ Add pins for the sense amp output """
for bit in range(self.word_size + self.num_spare_cols):
for bit in range(self.word_size):
data_pin = self.sense_amp_array_inst.get_pin("data_{}".format(bit)) data_pin = self.sense_amp_array_inst.get_pin("data_{}".format(bit))
self.add_layout_pin_rect_center(text="dout_{0}".format(bit), self.add_layout_pin_rect_center(text="dout_{0}".format(bit),
layer=data_pin.layer, layer=data_pin.layer,
@ -456,7 +488,7 @@ class port_data(design.design):
def route_write_driver_in(self, port): def route_write_driver_in(self, port):
""" Connecting write driver """ """ Connecting write driver """
for row in range(self.word_size): for row in range(self.word_size + self.num_spare_cols):
data_name = "data_{}".format(row) data_name = "data_{}".format(row)
din_name = "din_{}".format(row) din_name = "din_{}".format(row)
self.copy_layout_pin(self.write_driver_array_inst, data_name, din_name) self.copy_layout_pin(self.write_driver_array_inst, data_name, din_name)
@ -469,45 +501,38 @@ class port_data(design.design):
bank_wmask_name = "bank_wmask_{}".format(bit) bank_wmask_name = "bank_wmask_{}".format(bit)
self.copy_layout_pin(self.write_mask_and_array_inst, wmask_in_name, bank_wmask_name) self.copy_layout_pin(self.write_mask_and_array_inst, wmask_in_name, bank_wmask_name)
def route_write_mask_and_array_to_write_driver(self,port): def route_write_mask_and_array_to_write_driver(self, port):
""" Routing of wdriver_sel_{} between write mask AND array and write driver array. Adds layout pin for write """
mask AND array output and via for write driver enable """ Routing of wdriver_sel_{} between write mask AND array and
write driver array. Adds layout pin for write
mask AND array output and via for write driver enable
"""
inst1 = self.write_mask_and_array_inst wmask_inst = self.write_mask_and_array_inst
inst2 = self.write_driver_array_inst wdriver_inst = self.write_driver_array_inst
loc = 0
for bit in range(self.num_wmasks): for bit in range(self.num_wmasks):
# Bring write mask AND array output pin to port data level # Bring write mask AND array output pin to port data level
self.copy_layout_pin(inst1, "wmask_out_{0}".format(bit), "wdriver_sel_{0}".format(bit)) self.copy_layout_pin(wmask_inst, "wmask_out_{0}".format(bit), "wdriver_sel_{0}".format(bit))
wmask_out_pin = inst1.get_pin("wmask_out_{0}".format(bit)) wmask_out_pin = wmask_inst.get_pin("wmask_out_{0}".format(bit))
wdriver_en_pin = inst2.get_pin("en_{0}".format(bit)) wdriver_en_pin = wdriver_inst.get_pin("en_{0}".format(bit))
# The metal2 wdriver_sel_{} wire must hit the en_{} pin after the closest bitline pin that's right of the wmask_pos = wmask_out_pin.center()
# the wdriver_sel_{} pin in the write driver AND array. wdriver_pos = wdriver_en_pin.rc() - vector(self.m2_pitch, 0)
if bit == 0: mid_pos = vector(wdriver_pos.x, wmask_pos.y)
# When the write mask output pin is right of the bitline, the target is found
while (wmask_out_pin.lx() + self.m2_pitch > inst2.get_pin("data_{0}".format(loc)).rx()):
loc += 1
length = inst2.get_pin("data_{0}".format(loc)).rx() + self.m2_pitch
debug.check(loc<=self.num_wmasks,
"Couldn't route the write mask select.")
else:
# Stride by the write size rather than finding the next pin to the right
loc += self.write_size
length = inst2.get_pin("data_{0}".format(loc)).rx() + self.m2_pitch
beg_pos = wmask_out_pin.center()
middle_pos = vector(length, wmask_out_pin.cy())
end_pos = vector(length, wdriver_en_pin.cy())
# Add driver on mask output
self.add_via_stack_center(from_layer=wmask_out_pin.layer,
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=end_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, [beg_pos, middle_pos, end_pos]) self.add_wire(self.m1_stack, [wmask_pos, mid_pos, wdriver_pos])
def route_column_mux_to_precharge_array(self, port): def route_column_mux_to_precharge_array(self, port):
""" Routing of BL and BR between col mux and precharge array """ """ Routing of BL and BR between col mux and precharge array """
@ -516,15 +541,12 @@ class port_data(design.design):
if self.col_addr_size==0: if self.col_addr_size==0:
return return
inst1 = self.column_mux_array_inst start_bit = 1 if self.port == 0 else 0
inst2 = self.precharge_array_inst
insn2_start_bit = 1 if self.port == 0 else 0 self.connect_bitlines(inst1=self.column_mux_array_inst,
inst2=self.precharge_array_inst,
self.connect_bitlines(inst1=inst1,
inst2=inst2,
num_bits=self.num_cols, num_bits=self.num_cols,
inst2_start_bit=insn2_start_bit) inst2_start_bit=start_bit)
def route_sense_amp_to_column_mux_or_precharge_array(self, port): def route_sense_amp_to_column_mux_or_precharge_array(self, port):
""" Routing of BL and BR between sense_amp and column mux or precharge array """ """ Routing of BL and BR between sense_amp and column mux or precharge array """
@ -545,12 +567,41 @@ class port_data(design.design):
else: else:
start_bit=0 start_bit=0
self.channel_route_bitlines(inst1=inst1, # spare cols connected to precharge array since they are read independently
inst1_bls_template=inst1_bls_templ, if self.num_spare_cols and self.col_addr_size>0:
inst2=inst2, if self.port==0:
num_bits=self.word_size, off = 1
inst1_start_bit=start_bit) else:
off = 0
self.channel_route_bitlines(inst1=self.column_mux_array_inst,
inst1_bls_template="{inst}_out_{bit}",
inst2=inst2,
num_bits=self.word_size,
inst1_start_bit=start_bit)
self.channel_route_bitlines(inst1=self.precharge_array_inst,
inst1_bls_template="{inst}_{bit}",
inst2=inst2,
num_bits=self.num_spare_cols,
inst1_start_bit=self.num_cols + off,
inst2_start_bit=self.word_size)
# This could be a channel route, but in some techs the bitlines
# are too close together.
elif OPTS.tech_name == "sky130":
self.connect_bitlines(inst1=inst1,
inst1_bls_template=inst1_bls_templ,
inst2=inst2,
num_bits=self.word_size,
inst1_start_bit=start_bit)
else:
self.channel_route_bitlines(inst1=inst1,
inst1_bls_template=inst1_bls_templ,
inst2=inst2,
num_bits=self.word_size + self.num_spare_cols,
inst1_start_bit=start_bit)
def route_write_driver_to_column_mux_or_precharge_array(self, port): def route_write_driver_to_column_mux_or_precharge_array(self, port):
""" Routing of BL and BR between sense_amp and column mux or precharge array """ """ Routing of BL and BR between sense_amp and column mux or precharge array """
inst2 = self.write_driver_array_inst inst2 = self.write_driver_array_inst
@ -569,10 +620,44 @@ class port_data(design.design):
else: else:
start_bit=0 start_bit=0
self.channel_route_bitlines(inst1=inst1, inst2=inst2, if self.port==0:
num_bits=self.word_size, off = 1
inst1_bls_template=inst1_bls_templ, else:
inst1_start_bit=start_bit) off = 0
# Channel route spare columns' bitlines
if self.num_spare_cols and self.col_addr_size>0:
if self.port==0:
off = 1
else:
off = 0
self.channel_route_bitlines(inst1=self.column_mux_array_inst,
inst1_bls_template="{inst}_out_{bit}",
inst2=inst2,
num_bits=self.word_size,
inst1_start_bit=start_bit)
self.channel_route_bitlines(inst1=self.precharge_array_inst,
inst1_bls_template="{inst}_{bit}",
inst2=inst2,
num_bits=self.num_spare_cols,
inst1_start_bit=self.num_cols + off,
inst2_start_bit=self.word_size)
# This could be a channel route, but in some techs the bitlines
# are too close together.
elif OPTS.tech_name == "sky130":
self.connect_bitlines(inst1=inst1, inst2=inst2,
num_bits=self.word_size,
inst1_bls_template=inst1_bls_templ,
inst1_start_bit=start_bit)
else:
self.channel_route_bitlines(inst1=inst1, inst2=inst2,
num_bits=self.word_size+self.num_spare_cols,
inst1_bls_template=inst1_bls_templ,
inst1_start_bit=start_bit)
def route_write_driver_to_sense_amp(self, port): def route_write_driver_to_sense_amp(self, port):
""" Routing of BL and BR between write driver and sense amp """ """ Routing of BL and BR between write driver and sense amp """
@ -580,11 +665,11 @@ class port_data(design.design):
inst1 = self.write_driver_array_inst inst1 = self.write_driver_array_inst
inst2 = self.sense_amp_array_inst inst2 = self.sense_amp_array_inst
# These should be pitch matched in the cell library, # This could be a channel route, but in some techs the bitlines
# but just in case, do a channel route. # are too close together.
self.channel_route_bitlines(inst1=inst1, self.connect_bitlines(inst1=inst1,
inst2=inst2, inst2=inst2,
num_bits=self.word_size) num_bits=self.word_size + self.num_spare_cols)
def route_bitline_pins(self): def route_bitline_pins(self):
""" Add the bitline pins for the given port """ """ Add the bitline pins for the given port """
@ -595,8 +680,8 @@ class port_data(design.design):
self.copy_layout_pin(self.precharge_array_inst, "br_0", "rbl_br") self.copy_layout_pin(self.precharge_array_inst, "br_0", "rbl_br")
bit_offset=1 bit_offset=1
elif self.port==1: elif self.port==1:
self.copy_layout_pin(self.precharge_array_inst, "bl_{}".format(self.num_cols), "rbl_bl") self.copy_layout_pin(self.precharge_array_inst, "bl_{}".format(self.num_cols + self.num_spare_cols), "rbl_bl")
self.copy_layout_pin(self.precharge_array_inst, "br_{}".format(self.num_cols), "rbl_br") self.copy_layout_pin(self.precharge_array_inst, "br_{}".format(self.num_cols + self.num_spare_cols), "rbl_br")
bit_offset=0 bit_offset=0
else: else:
bit_offset=0 bit_offset=0
@ -611,6 +696,19 @@ class port_data(design.design):
"br_{}".format(bit)) "br_{}".format(bit))
else: else:
debug.error("Didn't find precharge array.") debug.error("Didn't find precharge array.")
# Copy bitlines of spare columns
for bit in range(self.num_spare_cols):
if self.precharge_array_inst:
self.copy_layout_pin(self.precharge_array_inst,
"bl_{}".format(self.num_cols + bit + bit_offset),
"sparebl_{}".format(bit))
self.copy_layout_pin(self.precharge_array_inst,
"br_{}".format(self.num_cols + bit + bit_offset),
"sparebr_{}".format(bit))
else:
debug.error("Didn't find precharge array.")
def route_control_pins(self): def route_control_pins(self):
""" Add the control pins: s_en, p_en_bar, w_en """ """ Add the control pins: s_en, p_en_bar, w_en """
@ -621,12 +719,19 @@ class port_data(design.design):
for pin_name in sel_names: for pin_name in sel_names:
self.copy_layout_pin(self.column_mux_array_inst, pin_name) self.copy_layout_pin(self.column_mux_array_inst, pin_name)
if self.sense_amp_array_inst: if self.sense_amp_array_inst:
self.copy_layout_pin(self.sense_amp_array_inst, "en", "s_en") self.copy_layout_pin(self.sense_amp_array_inst, "en", "s_en")
if self.write_driver_array_inst: if self.write_driver_array_inst:
if self.write_mask_and_array_inst: if self.write_mask_and_array_inst:
for bit in range(self.num_wmasks): for bit in range(self.num_wmasks):
# Add write driver's en_{} pins # Add write driver's en_{} pins
self.copy_layout_pin(self.write_driver_array_inst, "en_{}".format(bit), "wdriver_sel_{}".format(bit)) self.copy_layout_pin(self.write_driver_array_inst, "en_{}".format(bit), "wdriver_sel_{}".format(bit))
for bit in range(self.num_spare_cols):
# Add spare columns' en_{} pins
self.copy_layout_pin(self.write_driver_array_inst, "en_{}".format(bit + self.num_wmasks), "bank_spare_wen{}".format(bit))
elif self.num_spare_cols and not self.write_mask_and_array_inst:
self.copy_layout_pin(self.write_driver_array_inst, "en_0", "w_en")
for bit in range(self.num_spare_cols):
self.copy_layout_pin(self.write_driver_array_inst, "en_{}".format(bit + 1), "bank_spare_wen{}".format(bit))
else: else:
self.copy_layout_pin(self.write_driver_array_inst, "en", "w_en") self.copy_layout_pin(self.write_driver_array_inst, "en", "w_en")
if self.write_mask_and_array_inst: if self.write_mask_and_array_inst:
@ -687,10 +792,9 @@ class port_data(design.design):
Route the bl and br of two modules using the channel router. Route the bl and br of two modules using the channel router.
""" """
bot_inst_group, top_inst_group = self._group_bitline_instances( bot_inst_group, top_inst_group = self._group_bitline_instances(inst1, inst2, num_bits,
inst1, inst2, num_bits, inst1_bls_template, inst1_start_bit,
inst1_bls_template, inst1_start_bit, inst2_bls_template, inst2_start_bit)
inst2_bls_template, inst2_start_bit)
# Channel route each mux separately since we don't minimize the number # Channel route each mux separately since we don't minimize the number
# of tracks in teh channel router yet. If we did, we could route all the bits at once! # of tracks in teh channel router yet. If we did, we could route all the bits at once!
@ -699,13 +803,8 @@ class port_data(design.design):
bottom_names = self._get_bitline_pins(bot_inst_group, bit) bottom_names = self._get_bitline_pins(bot_inst_group, bit)
top_names = self._get_bitline_pins(top_inst_group, bit) top_names = self._get_bitline_pins(top_inst_group, bit)
if bottom_names[0].layer == "m2":
bitline_dirs = ("H", "V")
elif bottom_names[0].layer == "m1":
bitline_dirs = ("V", "H")
route_map = list(zip(bottom_names, top_names)) route_map = list(zip(bottom_names, top_names))
self.create_horizontal_channel_route(route_map, offset, self.m1_stack, bitline_dirs) self.create_horizontal_channel_route(route_map, offset, self.m1_stack)
def connect_bitlines(self, inst1, inst2, num_bits, def connect_bitlines(self, inst1, inst2, num_bits,
inst1_bls_template="{inst}_{bit}", inst1_bls_template="{inst}_{bit}",
@ -717,11 +816,10 @@ class port_data(design.design):
This assumes that they have sufficient space to create a jog This assumes that they have sufficient space to create a jog
in the middle between the two modules (if needed). in the middle between the two modules (if needed).
""" """
bot_inst_group, top_inst_group = self._group_bitline_instances( bot_inst_group, top_inst_group = self._group_bitline_instances(inst1, inst2, num_bits,
inst1, inst2, num_bits, inst1_bls_template, inst1_start_bit,
inst1_bls_template, inst1_start_bit, inst2_bls_template, inst2_start_bit)
inst2_bls_template, inst2_start_bit)
for col in range(num_bits): for col in range(num_bits):
bot_bl_pin, bot_br_pin = self._get_bitline_pins(bot_inst_group, col) bot_bl_pin, bot_br_pin = self._get_bitline_pins(bot_inst_group, col)
@ -729,18 +827,11 @@ class port_data(design.design):
bot_bl, bot_br = bot_bl_pin.uc(), bot_br_pin.uc() bot_bl, bot_br = bot_bl_pin.uc(), bot_br_pin.uc()
top_bl, top_br = top_bl_pin.bc(), top_br_pin.bc() top_bl, top_br = top_bl_pin.bc(), top_br_pin.bc()
yoffset = 0.5 * (top_bl.y + bot_bl.y) layer_pitch = getattr(self, "{}_pitch".format(top_bl_pin.layer))
self.add_path("m2", [bot_bl, self.add_zjog(bot_bl_pin.layer, bot_bl, top_bl, "V", fixed_offset=top_bl_pin.by() - layer_pitch)
vector(bot_bl.x, yoffset), self.add_zjog(bot_br_pin.layer, bot_br, top_br, "V", fixed_offset=top_bl_pin.by() - 2 * layer_pitch)
vector(top_bl.x, yoffset),
top_bl])
self.add_path("m2", [bot_br,
vector(bot_br.x, yoffset),
vector(top_br.x, yoffset),
top_br])
def graph_exclude_precharge(self): def graph_exclude_precharge(self):
"""Precharge adds a loop between bitlines, can be excluded to reduce complexity""" """Precharge adds a loop between bitlines, can be excluded to reduce complexity"""
if self.precharge_array_inst: if self.precharge_array_inst:
self.graph_inst_exclude.add(self.precharge_array_inst) self.graph_inst_exclude.add(self.precharge_array_inst)

View File

@ -7,10 +7,10 @@
# #
import design import design
import debug import debug
from tech import drc
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
from tech import layer
class precharge_array(design.design): class precharge_array(design.design):
@ -19,7 +19,7 @@ class precharge_array(design.design):
of bit line columns, height is the height of the bit-cell array. of bit line columns, height is the height of the bit-cell array.
""" """
def __init__(self, name, columns, size=1, bitcell_bl="bl", bitcell_br="br"): def __init__(self, name, columns, size=1, bitcell_bl="bl", bitcell_br="br", column_offset=0):
design.design.__init__(self, name) design.design.__init__(self, name)
debug.info(1, "Creating {0}".format(self.name)) debug.info(1, "Creating {0}".format(self.name))
self.add_comment("cols: {0} size: {1} bl: {2} br: {3}".format(columns, size, bitcell_bl, bitcell_br)) self.add_comment("cols: {0} size: {1} bl: {2} br: {3}".format(columns, size, bitcell_bl, bitcell_br))
@ -28,7 +28,13 @@ class precharge_array(design.design):
self.size = size self.size = size
self.bitcell_bl = bitcell_bl self.bitcell_bl = bitcell_bl
self.bitcell_br = bitcell_br self.bitcell_br = bitcell_br
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()
@ -73,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)):
@ -106,7 +116,7 @@ class precharge_array(design.design):
xoffset = 0 xoffset = 0
for i in range(self.columns): for i in range(self.columns):
tempx = xoffset tempx = xoffset
if cell_properties.bitcell.mirror.y and (i + 1) % 2: if cell_properties.bitcell.mirror.y and (i + self.column_offset) % 2:
mirror = "MY" mirror = "MY"
tempx = tempx + self.pc_cell.width tempx = tempx + self.pc_cell.width
else: else:

View File

@ -1,12 +1,12 @@
# See LICENSE for licensing information. # See LICENSE for licensing information.
# #
# Copyright (c) 2016-2019 Regents of the University of California # Copyright (c) 2016-2019 Regents of the University of California
# All rights reserved. # All rights reserved.
# #
import debug import debug
import design import design
from tech import drc, spice from tech import drc, spice, cell_properties
from vector import vector from vector import vector
from globals import OPTS from globals import OPTS
from sram_factory import factory from sram_factory import factory
@ -32,22 +32,23 @@ class replica_bitcell_array(design.design):
self.right_rbl = right_rbl self.right_rbl = right_rbl
self.bitcell_ports = bitcell_ports self.bitcell_ports = bitcell_ports
debug.check(left_rbl+right_rbl==len(self.all_ports),"Invalid number of RBLs for port configuration.") debug.check(left_rbl + right_rbl == len(self.all_ports),
debug.check(left_rbl+right_rbl==len(self.bitcell_ports),"Bitcell ports must match total RBLs.") "Invalid number of RBLs for port configuration.")
debug.check(left_rbl + right_rbl == len(self.bitcell_ports),
"Bitcell ports must match total RBLs.")
# Two dummy rows/cols plus replica for each port # Two dummy rows/cols plus replica for each port
self.extra_rows = 2 + left_rbl + right_rbl self.extra_rows = 2 + left_rbl + right_rbl
self.extra_cols = 2 + left_rbl + right_rbl self.extra_cols = 2 + left_rbl + right_rbl
self.create_netlist() self.create_netlist()
if not OPTS.netlist_only: if not OPTS.netlist_only:
self.create_layout() self.create_layout()
# We don't offset this because we need to align # We don't offset this because we need to align
# the replica bitcell in the control logic # the replica bitcell in the control logic
#self.offset_all_coordinates() # self.offset_all_coordinates()
def create_netlist(self): def create_netlist(self):
""" Create and connect the netlist """ """ Create and connect the netlist """
self.add_modules() self.add_modules()
@ -55,15 +56,15 @@ class replica_bitcell_array(design.design):
self.create_instances() self.create_instances()
def add_modules(self): def add_modules(self):
""" Array and dummy/replica columns """ Array and dummy/replica columns
d or D = dummy cell (caps to distinguish grouping) d or D = dummy cell (caps to distinguish grouping)
r or R = replica cell (caps to distinguish grouping) r or R = replica cell (caps to distinguish grouping)
b or B = bitcell b or B = bitcell
replica columns 1 replica columns 1
v v v v
bdDDDDDDDDDDDDDDdb <- Dummy row bdDDDDDDDDDDDDDDdb <- Dummy row
bdDDDDDDDDDDDDDDrb <- Dummy row bdDDDDDDDDDDDDDDrb <- Dummy row
br--------------rb br--------------rb
br| Array |rb br| Array |rb
br| row x col |rb br| row x col |rb
@ -90,15 +91,17 @@ class replica_bitcell_array(design.design):
# Replica bitlines # Replica bitlines
self.replica_columns = {} self.replica_columns = {}
for bit in range(self.left_rbl+self.right_rbl): for bit in range(self.left_rbl + self.right_rbl):
# Creating left_rbl
if bit<self.left_rbl: if bit<self.left_rbl:
replica_bit = bit+1 replica_bit = bit + 1
# dummy column # dummy column
column_offset = 1 column_offset = self.left_rbl - bit
# Creating right_rbl
else: else:
replica_bit = bit+self.row_size+1 replica_bit = bit + self.row_size + 1
# dummy column + replica column + bitcell colums # dummy column + replica column + bitcell colums
column_offset = 3 + self.row_size column_offset = self.left_rbl - bit + self.row_size
self.replica_columns[bit] = factory.create(module_type="replica_column", self.replica_columns[bit] = factory.create(module_type="replica_column",
rows=self.row_size, rows=self.row_size,
left_rbl=self.left_rbl, left_rbl=self.left_rbl,
@ -106,7 +109,7 @@ class replica_bitcell_array(design.design):
column_offset=column_offset, column_offset=column_offset,
replica_bit=replica_bit) replica_bit=replica_bit)
self.add_mod(self.replica_columns[bit]) self.add_mod(self.replica_columns[bit])
# Dummy row # Dummy row
self.dummy_row = factory.create(module_type="dummy_array", self.dummy_row = factory.create(module_type="dummy_array",
cols=self.column_size, cols=self.column_size,
@ -116,57 +119,74 @@ class replica_bitcell_array(design.design):
mirror=0) mirror=0)
self.add_mod(self.dummy_row) self.add_mod(self.dummy_row)
# Dummy col (mirror starting at first if odd replica+dummy rows) # If there are bitcell end caps, replace the dummy cells on the edge of the bitcell array with end caps.
self.dummy_col_left = factory.create(module_type="dummy_array", try:
end_caps_enabled = cell_properties.bitcell.end_caps
except AttributeError:
end_caps_enabled = False
# Dummy Row or Col Cap, depending on bitcell array properties
edge_row_module_type = ("col_cap_array" if end_caps_enabled else "dummy_array")
self.edge_row = factory.create(module_type=edge_row_module_type,
cols=self.column_size,
rows=1,
# dummy column + left replica column(s)
column_offset=1 + self.left_rbl,
mirror=0)
self.add_mod(self.edge_row)
# Dummy Col or Row Cap, depending on bitcell array properties
edge_col_module_type = ("row_cap_array" if end_caps_enabled else "dummy_array")
self.edge_col_left = factory.create(module_type=edge_col_module_type,
cols=1, cols=1,
column_offset=0, column_offset=0,
rows=self.row_size + self.extra_rows, rows=self.row_size + self.extra_rows,
mirror=(self.left_rbl+1)%2) mirror=(self.left_rbl + 1) % 2)
self.add_mod(self.dummy_col_left) self.add_mod(self.edge_col_left)
self.dummy_col_right = factory.create(module_type="dummy_array", self.edge_col_right = factory.create(module_type=edge_col_module_type,
cols=1, cols=1,
# dummy column # dummy column
# + left replica column # + left replica column(s)
# + bitcell columns # + bitcell columns
# + right replica column # + right replica column(s)
column_offset=1 + self.left_rbl + self.column_size + self.right_rbl, column_offset = 1 + self.left_rbl + self.column_size + self.right_rbl,
rows=self.row_size + self.extra_rows, rows=self.row_size + self.extra_rows,
mirror=(self.left_rbl+1)%2) mirror=(self.left_rbl + 1) %2)
self.add_mod(self.dummy_col_right) self.add_mod(self.edge_col_right)
def add_pins(self): def add_pins(self):
self.bitcell_array_wl_names = self.bitcell_array.get_all_wordline_names() self.bitcell_array_wl_names = self.bitcell_array.get_all_wordline_names()
self.bitcell_array_bl_names = self.bitcell_array.get_all_bitline_names() self.bitcell_array_bl_names = self.bitcell_array.get_all_bitline_names()
# These are the non-indexed names # These are the non-indexed names
self.dummy_cell_wl_names = ["dummy_"+x for x in self.cell.get_all_wl_names()] self.dummy_cell_wl_names = ["dummy_" + x for x in self.cell.get_all_wl_names()]
self.dummy_cell_bl_names = ["dummy_"+x for x in self.cell.get_all_bitline_names()] self.dummy_cell_bl_names = ["dummy_" + x for x in self.cell.get_all_bitline_names()]
self.dummy_row_bl_names = self.bitcell_array_bl_names self.dummy_row_bl_names = self.bitcell_array_bl_names
# A dictionary because some ports may have nothing # A dictionary because some ports may have nothing
self.rbl_bl_names = {} self.rbl_bl_names = {}
self.rbl_br_names = {} self.rbl_br_names = {}
self.rbl_wl_names = {} self.rbl_wl_names = {}
# Create the full WL names include dummy, replica, and regular bit cells # Create the full WL names include dummy, replica, and regular bit cells
self.replica_col_wl_names = [] self.replica_col_wl_names = []
self.replica_col_wl_names.extend(["{0}_bot".format(x) for x in self.dummy_cell_wl_names]) self.replica_col_wl_names.extend(["{0}_bot".format(x) for x in self.dummy_cell_wl_names])
# Left port WLs (one dummy for each port when we allow >1 port) # Left port WLs (one dummy for each port when we allow >1 port)
for port in range(self.left_rbl): for port in range(self.left_rbl):
# Make names for all RBLs # Make names for all RBLs
wl_names=["rbl_{0}_{1}".format(self.cell.get_wl_name(x),port) for x in range(len(self.cell.get_all_wl_names()))] wl_names=["rbl_{0}_{1}".format(self.cell.get_wl_name(x), port) for x in range(len(self.cell.get_all_wl_names()))]
# Keep track of the pin that is the RBL # Keep track of the pin that is the RBL
self.rbl_wl_names[port]=wl_names[self.bitcell_ports[port]] self.rbl_wl_names[port]=wl_names[self.bitcell_ports[port]]
self.replica_col_wl_names.extend(wl_names) self.replica_col_wl_names.extend(wl_names)
# Regular WLs # Regular WLs
self.replica_col_wl_names.extend(self.bitcell_array_wl_names) self.replica_col_wl_names.extend(self.bitcell_array_wl_names)
# Right port WLs (one dummy for each port when we allow >1 port) # Right port WLs (one dummy for each port when we allow >1 port)
for port in range(self.left_rbl,self.left_rbl+self.right_rbl): for port in range(self.left_rbl, self.left_rbl + self.right_rbl):
# Make names for all RBLs # Make names for all RBLs
wl_names=["rbl_{0}_{1}".format(self.cell.get_wl_name(x),port) for x in range(len(self.cell.get_all_wl_names()))] wl_names=["rbl_{0}_{1}".format(self.cell.get_wl_name(x), port) for x in range(len(self.cell.get_all_wl_names()))]
# Keep track of the pin that is the RBL # Keep track of the pin that is the RBL
self.rbl_wl_names[port]=wl_names[self.bitcell_ports[port]] self.rbl_wl_names[port]=wl_names[self.bitcell_ports[port]]
self.replica_col_wl_names.extend(wl_names) self.replica_col_wl_names.extend(wl_names)
@ -175,14 +195,13 @@ class replica_bitcell_array(design.design):
# Left/right dummy columns are connected identically to the replica column # Left/right dummy columns are connected identically to the replica column
self.dummy_col_wl_names = self.replica_col_wl_names self.dummy_col_wl_names = self.replica_col_wl_names
# Per port bitline names # Per port bitline names
self.replica_bl_names = {} self.replica_bl_names = {}
self.replica_wl_names = {} self.replica_wl_names = {}
# Array of all port bitline names # Array of all port bitline names
for port in range(self.left_rbl+self.right_rbl): for port in range(self.left_rbl + self.right_rbl):
left_names=["rbl_{0}_{1}".format(self.cell.get_bl_name(x),port) for x in range(len(self.all_ports))] left_names=["rbl_{0}_{1}".format(self.cell.get_bl_name(x), port) for x in range(len(self.all_ports))]
right_names=["rbl_{0}_{1}".format(self.cell.get_br_name(x),port) for x in range(len(self.all_ports))] right_names=["rbl_{0}_{1}".format(self.cell.get_br_name(x), port) for x in range(len(self.all_ports))]
# Keep track of the left pins that are the RBL # Keep track of the left pins that are the RBL
self.rbl_bl_names[port]=left_names[self.bitcell_ports[port]] self.rbl_bl_names[port]=left_names[self.bitcell_ports[port]]
self.rbl_br_names[port]=right_names[self.bitcell_ports[port]] self.rbl_br_names[port]=right_names[self.bitcell_ports[port]]
@ -190,36 +209,33 @@ class replica_bitcell_array(design.design):
bl_names = [x for t in zip(left_names, right_names) for x in t] bl_names = [x for t in zip(left_names, right_names) for x in t]
self.replica_bl_names[port] = bl_names self.replica_bl_names[port] = bl_names
wl_names = ["rbl_{0}_{1}".format(x,port) for x in self.cell.get_all_wl_names()] wl_names = ["rbl_{0}_{1}".format(x, port) for x in self.cell.get_all_wl_names()]
#wl_names[port] = "rbl_wl{}".format(port)
self.replica_wl_names[port] = wl_names self.replica_wl_names[port] = wl_names
# External pins # External pins
self.add_pin_list(self.bitcell_array_bl_names, "INOUT") self.add_pin_list(self.bitcell_array_bl_names, "INOUT")
# Need to sort by port order since dictionary values may not be in order # Need to sort by port order since dictionary values may not be in order
bl_names = [self.rbl_bl_names[x] for x in sorted(self.rbl_bl_names.keys())] bl_names = [self.rbl_bl_names[x] for x in sorted(self.rbl_bl_names.keys())]
br_names = [self.rbl_br_names[x] for x in sorted(self.rbl_br_names.keys())] br_names = [self.rbl_br_names[x] for x in sorted(self.rbl_br_names.keys())]
for (bl_name,br_name) in zip(bl_names,br_names): for (bl_name, br_name) in zip(bl_names, br_names):
self.add_pin(bl_name,"OUTPUT") self.add_pin(bl_name, "OUTPUT")
self.add_pin(br_name,"OUTPUT") self.add_pin(br_name, "OUTPUT")
self.add_pin_list(self.bitcell_array_wl_names, "INPUT") self.add_pin_list(self.bitcell_array_wl_names, "INPUT")
# Need to sort by port order since dictionary values may not be in order # Need to sort by port order since dictionary values may not be in order
wl_names = [self.rbl_wl_names[x] for x in sorted(self.rbl_wl_names.keys())] wl_names = [self.rbl_wl_names[x] for x in sorted(self.rbl_wl_names.keys())]
for pin_name in wl_names: for pin_name in wl_names:
self.add_pin(pin_name,"INPUT") self.add_pin(pin_name, "INPUT")
self.add_pin("vdd", "POWER") self.add_pin("vdd", "POWER")
self.add_pin("gnd", "GROUND") self.add_pin("gnd", "GROUND")
def create_instances(self): def create_instances(self):
""" Create the module instances used in this design """ """ Create the module instances used in this design """
supplies = ["vdd", "gnd"] supplies = ["vdd", "gnd"]
# Used for names/dimensions only # Used for names/dimensions only
self.cell = factory.create(module_type="bitcell") self.cell = factory.create(module_type="bitcell")
# Main array # Main array
self.bitcell_array_inst=self.add_inst(name="bitcell_array", self.bitcell_array_inst=self.add_inst(name="bitcell_array",
mod=self.bitcell_array) mod=self.bitcell_array)
@ -227,88 +243,82 @@ class replica_bitcell_array(design.design):
# Replica columns # Replica columns
self.replica_col_inst = {} self.replica_col_inst = {}
for port in range(self.left_rbl+self.right_rbl): for port in range(self.left_rbl + self.right_rbl):
self.replica_col_inst[port]=self.add_inst(name="replica_col_{}".format(port), self.replica_col_inst[port]=self.add_inst(name="replica_col_{}".format(port),
mod=self.replica_columns[port]) mod=self.replica_columns[port])
self.connect_inst(self.replica_bl_names[port] + self.replica_col_wl_names + supplies) self.connect_inst(self.replica_bl_names[port] + self.replica_col_wl_names + supplies)
# Dummy rows under the bitcell array (connected with with the replica cell wl) # Dummy rows under the bitcell array (connected with with the replica cell wl)
self.dummy_row_replica_inst = {} self.dummy_row_replica_inst = {}
for port in range(self.left_rbl+self.right_rbl): for port in range(self.left_rbl + self.right_rbl):
self.dummy_row_replica_inst[port]=self.add_inst(name="dummy_row_{}".format(port), self.dummy_row_replica_inst[port]=self.add_inst(name="dummy_row_{}".format(port),
mod=self.dummy_row) mod=self.dummy_row)
self.connect_inst(self.dummy_row_bl_names + self.replica_wl_names[port] + supplies) self.connect_inst(self.dummy_row_bl_names + self.replica_wl_names[port] + supplies)
# Top/bottom dummy rows
self.dummy_row_bot_inst=self.add_inst(name="dummy_row_bot",
mod=self.dummy_row)
self.connect_inst(self.dummy_row_bl_names + [x+"_bot" for x in self.dummy_cell_wl_names] + supplies)
self.dummy_row_top_inst=self.add_inst(name="dummy_row_top",
mod=self.dummy_row)
self.connect_inst(self.dummy_row_bl_names + [x+"_top" for x in self.dummy_cell_wl_names] + supplies)
# Top/bottom dummy rows or col caps
self.dummy_row_bot_inst=self.add_inst(name="dummy_row_bot",
mod=self.edge_row)
self.connect_inst(self.dummy_row_bl_names + [x + "_bot" for x in self.dummy_cell_wl_names] + supplies)
self.dummy_row_top_inst=self.add_inst(name="dummy_row_top",
mod=self.edge_row)
self.connect_inst(self.dummy_row_bl_names + [x + "_top" for x in self.dummy_cell_wl_names] + supplies)
# Left/right Dummy columns # Left/right Dummy columns
self.dummy_col_left_inst=self.add_inst(name="dummy_col_left", self.dummy_col_left_inst=self.add_inst(name="dummy_col_left",
mod=self.dummy_col_left) mod=self.edge_col_left)
self.connect_inst([x+"_left" for x in self.dummy_cell_bl_names] + self.dummy_col_wl_names + supplies) self.connect_inst([x + "_left" for x in self.dummy_cell_bl_names] + self.dummy_col_wl_names + supplies)
self.dummy_col_right_inst=self.add_inst(name="dummy_col_right", self.dummy_col_right_inst=self.add_inst(name="dummy_col_right",
mod=self.dummy_col_right) mod=self.edge_col_right)
self.connect_inst([x+"_right" for x in self.dummy_cell_bl_names] + self.dummy_col_wl_names + supplies) self.connect_inst([x + "_right" for x in self.dummy_cell_bl_names] + self.dummy_col_wl_names + supplies)
def create_layout(self): def create_layout(self):
self.height = (self.row_size+self.extra_rows)*self.dummy_row.height self.height = (self.row_size + self.extra_rows) * self.dummy_row.height
self.width = (self.column_size+self.extra_cols)*self.cell.width self.width = (self.column_size + self.extra_cols) * self.cell.width
# This is a bitcell x bitcell offset to scale # This is a bitcell x bitcell offset to scale
offset = vector(self.cell.width, self.cell.height) offset = vector(self.cell.width, self.cell.height)
self.bitcell_array_inst.place(offset=[0,0]) self.bitcell_array_inst.place(offset=[0, 0])
# To the left of the bitcell array # To the left of the bitcell array
for bit in range(self.left_rbl): for bit in range(self.left_rbl):
self.replica_col_inst[bit].place(offset=offset.scale(-bit-1,-self.left_rbl-1)) self.replica_col_inst[bit].place(offset=offset.scale(-bit - 1, -self.left_rbl - 1))
# To the right of the bitcell array # To the right of the bitcell array
for bit in range(self.right_rbl): for bit in range(self.right_rbl):
self.replica_col_inst[self.left_rbl+bit].place(offset=offset.scale(bit,-self.left_rbl-1)+self.bitcell_array_inst.lr()) self.replica_col_inst[self.left_rbl + bit].place(offset=offset.scale(bit, -self.left_rbl - 1) + self.bitcell_array_inst.lr())
# FIXME: These depend on the array size itself
# Far top dummy row (first row above array is NOT flipped) # Far top dummy row (first row above array is NOT flipped)
flip_dummy = self.right_rbl%2 flip_dummy = self.right_rbl % 2
self.dummy_row_top_inst.place(offset=offset.scale(0,self.right_rbl+flip_dummy)+self.bitcell_array_inst.ul(), self.dummy_row_top_inst.place(offset=offset.scale(0, self.right_rbl + flip_dummy) + self.bitcell_array_inst.ul(),
mirror="MX" if flip_dummy else "R0") mirror="MX" if flip_dummy else "R0")
# FIXME: These depend on the array size itself
# Far bottom dummy row (first row below array IS flipped) # Far bottom dummy row (first row below array IS flipped)
flip_dummy = (self.left_rbl+1)%2 flip_dummy = (self.left_rbl + 1) % 2
self.dummy_row_bot_inst.place(offset=offset.scale(0,-self.left_rbl-1+flip_dummy), self.dummy_row_bot_inst.place(offset=offset.scale(0, -self.left_rbl - 1 + flip_dummy),
mirror="MX" if flip_dummy else "R0") mirror="MX" if flip_dummy else "R0")
# Far left dummy col # Far left dummy col
self.dummy_col_left_inst.place(offset=offset.scale(-self.left_rbl-1,-self.left_rbl-1)) self.dummy_col_left_inst.place(offset=offset.scale(-self.left_rbl - 1, -self.left_rbl - 1))
# Far right dummy col # Far right dummy col
self.dummy_col_right_inst.place(offset=offset.scale(self.right_rbl,-self.left_rbl-1)+self.bitcell_array_inst.lr()) self.dummy_col_right_inst.place(offset=offset.scale(self.right_rbl, -self.left_rbl - 1) + self.bitcell_array_inst.lr())
# Replica dummy rows # Replica dummy rows
for bit in range(self.left_rbl): for bit in range(self.left_rbl):
self.dummy_row_replica_inst[bit].place(offset=offset.scale(0,-bit-bit%2), self.dummy_row_replica_inst[bit].place(offset=offset.scale(0, -bit - bit % 2),
mirror="R0" if bit%2 else "MX") mirror="R0" if bit % 2 else "MX")
for bit in range(self.right_rbl): for bit in range(self.right_rbl):
self.dummy_row_replica_inst[self.left_rbl+bit].place(offset=offset.scale(0,bit+bit%2)+self.bitcell_array_inst.ul(), self.dummy_row_replica_inst[self.left_rbl + bit].place(offset=offset.scale(0, bit + bit % 2) + self.bitcell_array_inst.ul(),
mirror="MX" if bit%2 else "R0") mirror="MX" if bit % 2 else "R0")
self.translate_all(offset.scale(-1 - self.left_rbl, -1 - self.left_rbl))
self.translate_all(offset.scale(-1-self.left_rbl,-1-self.left_rbl))
self.add_layout_pins() self.add_layout_pins()
self.add_boundary() self.add_boundary()
self.DRC_LVS() self.DRC_LVS()
def add_layout_pins(self): def add_layout_pins(self):
""" Add the layout pins """ """ Add the layout pins """
@ -321,7 +331,7 @@ class replica_bitcell_array(design.design):
for pin in pin_list: for pin in pin_list:
self.add_layout_pin(text=pin_name, self.add_layout_pin(text=pin_name,
layer=pin.layer, layer=pin.layer,
offset=pin.ll().scale(0,1), offset=pin.ll().scale(0, 1),
width=self.width, width=self.width,
height=pin.height()) height=pin.height())
for bitline in self.bitcell_array_bl_names: for bitline in self.bitcell_array_bl_names:
@ -330,27 +340,26 @@ class replica_bitcell_array(design.design):
for pin in pin_list: for pin in pin_list:
self.add_layout_pin(text=pin_name, self.add_layout_pin(text=pin_name,
layer=pin.layer, layer=pin.layer,
offset=pin.ll().scale(1,0), offset=pin.ll().scale(1, 0),
width=pin.width(), width=pin.width(),
height=self.height) height=self.height)
# Replica wordlines # Replica wordlines
for port in range(self.left_rbl+self.right_rbl): for port in range(self.left_rbl + self.right_rbl):
inst = self.replica_col_inst[port] inst = self.replica_col_inst[port]
for (pin_name,wl_name) in zip(self.cell.get_all_wl_names(),self.replica_wl_names[port]): for (pin_name, wl_name) in zip(self.cell.get_all_wl_names(), self.replica_wl_names[port]):
# +1 for dummy row # +1 for dummy row
pin_bit = port+1 pin_bit = port + 1
# +row_size if above the array # +row_size if above the array
if port>=self.left_rbl: if port>=self.left_rbl:
pin_bit += self.row_size pin_bit += self.row_size
pin_name += "_{}".format(pin_bit) pin_name += "_{}".format(pin_bit)
pin = inst.get_pin(pin_name) pin = inst.get_pin(pin_name)
if wl_name in self.rbl_wl_names.values(): if wl_name in self.rbl_wl_names.values():
self.add_layout_pin(text=wl_name, self.add_layout_pin(text=wl_name,
layer=pin.layer, layer=pin.layer,
offset=pin.ll().scale(0,1), offset=pin.ll().scale(0, 1),
width=self.width, width=self.width,
height=pin.height()) height=pin.height())
@ -368,20 +377,28 @@ class replica_bitcell_array(design.design):
offset=pin.ll().scale(1, 0), offset=pin.ll().scale(1, 0),
width=pin.width(), width=pin.width(),
height=self.height) height=self.height)
# vdd/gnd are only connected in the perimeter cells
# replica column should only have a vdd/gnd in the dummy cell on top/bottom
supply_insts = [self.dummy_col_left_inst, self.dummy_col_right_inst,
self.dummy_row_top_inst, self.dummy_row_bot_inst]
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:
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 """
return self.rbl_wl_names[port] return self.rbl_wl_names[port]
def get_rbl_bl_name(self, port): def get_rbl_bl_name(self, port):
""" Return the BL for the given RBL port """ """ Return the BL for the given RBL port """
return self.rbl_bl_names[port] return self.rbl_bl_names[port]
@ -392,19 +409,17 @@ class replica_bitcell_array(design.design):
def analytical_power(self, corner, load): def analytical_power(self, corner, load):
"""Power of Bitcell array and bitline in nW.""" """Power of Bitcell array and bitline in nW."""
from tech import drc, parameter
# Dynamic Power from Bitline # Dynamic Power from Bitline
bl_wire = self.gen_bl_wire() bl_wire = self.gen_bl_wire()
cell_load = 2 * bl_wire.return_input_cap() cell_load = 2 * bl_wire.return_input_cap()
bl_swing = OPTS.rbl_delay_percentage bl_swing = OPTS.rbl_delay_percentage
freq = spice["default_event_frequency"] freq = spice["default_event_frequency"]
bitline_dynamic = self.calc_dynamic_power(corner, cell_load, freq, swing=bl_swing) bitline_dynamic = self.calc_dynamic_power(corner, cell_load, freq, swing=bl_swing)
#Calculate the bitcell power which currently only includes leakage # Calculate the bitcell power which currently only includes leakage
cell_power = self.cell.analytical_power(corner, load) cell_power = self.cell.analytical_power(corner, load)
#Leakage power grows with entire array and bitlines. # Leakage power grows with entire array and bitlines.
total_power = self.return_power(cell_power.dynamic + bitline_dynamic * self.column_size, total_power = self.return_power(cell_power.dynamic + bitline_dynamic * self.column_size,
cell_power.leakage * self.column_size * self.row_size) cell_power.leakage * self.column_size * self.row_size)
return total_power return total_power
@ -415,13 +430,13 @@ class replica_bitcell_array(design.design):
else: else:
height = self.height height = self.height
bl_pos = 0 bl_pos = 0
bl_wire = self.generate_rc_net(int(self.row_size-bl_pos), height, drc("minwidth_m1")) bl_wire = self.generate_rc_net(int(self.row_size - bl_pos), height, drc("minwidth_m1"))
bl_wire.wire_c =spice["min_tx_drain_c"] + bl_wire.wire_c # 1 access tx d/s per cell bl_wire.wire_c =spice["min_tx_drain_c"] + bl_wire.wire_c # 1 access tx d/s per cell
return bl_wire return bl_wire
def get_wordline_cin(self): def get_wordline_cin(self):
"""Get the relative input capacitance from the wordline connections in all the bitcell""" """Get the relative input capacitance from the wordline connections in all the bitcell"""
#A single wordline is connected to all the bitcells in a single row meaning the capacitance depends on the # of columns # A single wordline is connected to all the bitcells in a single row meaning the capacitance depends on the # of columns
bitcell_wl_cin = self.cell.get_wl_cin() bitcell_wl_cin = self.cell.get_wl_cin()
total_cin = bitcell_wl_cin * self.column_size total_cin = bitcell_wl_cin * self.column_size
return total_cin return total_cin
@ -429,13 +444,13 @@ class replica_bitcell_array(design.design):
def graph_exclude_bits(self, targ_row, targ_col): def graph_exclude_bits(self, targ_row, targ_col):
"""Excludes bits in column from being added to graph except target""" """Excludes bits in column from being added to graph except target"""
self.bitcell_array.graph_exclude_bits(targ_row, targ_col) self.bitcell_array.graph_exclude_bits(targ_row, targ_col)
def graph_exclude_replica_col_bits(self): def graph_exclude_replica_col_bits(self):
"""Exclude all replica/dummy cells in the replica columns except the replica bit.""" """Exclude all replica/dummy cells in the replica columns except the replica bit."""
for port in range(self.left_rbl+self.right_rbl): for port in range(self.left_rbl + self.right_rbl):
self.replica_columns[port].exclude_all_but_replica() self.replica_columns[port].exclude_all_but_replica()
def get_cell_name(self, inst_name, row, col): def get_cell_name(self, inst_name, row, col):
"""Gets the spice name of the target bitcell.""" """Gets the spice name of the target bitcell."""
return self.bitcell_array.get_cell_name(inst_name+'.x'+self.bitcell_array_inst.name, row, col) return self.bitcell_array.get_cell_name(inst_name + '.x' + self.bitcell_array_inst.name, row, col)

View File

@ -1,22 +1,22 @@
# See LICENSE for licensing information. # See LICENSE for licensing information.
# #
# Copyright (c) 2016-2019 Regents of the University of California # Copyright (c) 2016-2019 Regents of the University of California
# All rights reserved. # All rights reserved.
# #
import debug import debug
import design import design
from tech import drc from tech import cell_properties
import contact
from sram_factory import factory from sram_factory import factory
from vector import vector from vector import vector
from globals import OPTS from globals import OPTS
class replica_column(design.design): class replica_column(design.design):
""" """
Generate a replica bitline column for the replica array. Generate a replica bitline column for the replica array.
Rows is the total number of rows i the main array. Rows is the total number of rows i the main array.
Left_rbl and right_rbl are the number of left and right replica bitlines. Left_rbl and right_rbl are the number of left and right replica bitlines.
Replica bit specifies which replica column this is (to determine where to put the Replica bit specifies which replica column this is (to determine where to put the
replica cell. replica cell.
""" """
@ -29,25 +29,30 @@ class replica_column(design.design):
self.right_rbl = right_rbl self.right_rbl = right_rbl
self.replica_bit = replica_bit self.replica_bit = replica_bit
# left, right, regular rows plus top/bottom dummy cells # left, right, regular rows plus top/bottom dummy cells
self.total_size = self.left_rbl+rows+self.right_rbl+2 self.total_size = self.left_rbl + rows + self.right_rbl + 2
self.column_offset = column_offset self.column_offset = column_offset
debug.check(replica_bit!=0 and replica_bit!=rows,"Replica bit cannot be the dummy row.") debug.check(replica_bit != 0 and replica_bit != rows,
debug.check(replica_bit<=left_rbl or replica_bit>=self.total_size-right_rbl-1, "Replica bit cannot be the dummy row.")
"Replica bit cannot be in the regular array.") debug.check(replica_bit <= left_rbl or replica_bit >= self.total_size - right_rbl - 1,
"Replica bit cannot be in the regular array.")
if OPTS.tech_name == "sky130":
debug.check(rows % 2 == 0 and (left_rbl + 1) % 2 == 0,
"sky130 currently requires rows to be even and to start with X mirroring"
+ " (left_rbl must be odd) for LVS.")
self.create_netlist() self.create_netlist()
if not OPTS.netlist_only: if not OPTS.netlist_only:
self.create_layout() self.create_layout()
def create_netlist(self): def create_netlist(self):
self.add_modules() self.add_modules()
self.add_pins() self.add_pins()
self.create_instances() self.create_instances()
def create_layout(self): def create_layout(self):
self.height = self.total_size*self.cell.height self.height = self.total_size * self.cell.height
self.width = self.cell.width self.width = self.cell.width
self.place_instances() self.place_instances()
self.add_layout_pins() self.add_layout_pins()
@ -55,49 +60,70 @@ class replica_column(design.design):
self.DRC_LVS() self.DRC_LVS()
def add_pins(self): def add_pins(self):
for bl_name in self.cell.get_all_bitline_names(): for bl_name in self.cell.get_all_bitline_names():
# In the replica column, these are only outputs! # In the replica column, these are only outputs!
self.add_pin("{0}_{1}".format(bl_name,0), "OUTPUT") self.add_pin("{0}_{1}".format(bl_name, 0), "OUTPUT")
for row in range(self.total_size): for row in range(self.total_size):
for wl_name in self.cell.get_all_wl_names(): for wl_name in self.cell.get_all_wl_names():
self.add_pin("{0}_{1}".format(wl_name,row), "INPUT") self.add_pin("{0}_{1}".format(wl_name, row), "INPUT")
self.add_pin("vdd", "POWER") self.add_pin("vdd", "POWER")
self.add_pin("gnd", "GROUND") self.add_pin("gnd", "GROUND")
def add_modules(self): def add_modules(self):
self.replica_cell = factory.create(module_type="replica_bitcell") self.replica_cell = factory.create(module_type="replica_{}".format(OPTS.bitcell))
self.add_mod(self.replica_cell) self.add_mod(self.replica_cell)
self.dummy_cell = factory.create(module_type="dummy_bitcell") self.dummy_cell = factory.create(module_type="dummy_{}".format(OPTS.bitcell))
self.add_mod(self.dummy_cell) self.add_mod(self.dummy_cell)
try:
edge_module_type = ("col_cap" if cell_properties.bitcell.end_caps else "dummy")
except AttributeError:
edge_module_type = "dummy"
self.edge_cell = factory.create(module_type=edge_module_type + "_" + OPTS.bitcell)
self.add_mod(self.edge_cell)
# Used for pin names only # Used for pin names only
self.cell = factory.create(module_type="bitcell") self.cell = factory.create(module_type="bitcell")
def create_instances(self): def create_instances(self):
try:
end_caps_enabled = cell_properties.bitcell.end_caps
except AttributeError:
end_caps_enabled = False
self.cell_inst = {} self.cell_inst = {}
for row in range(self.total_size): for row in range(self.total_size):
name="rbc_{0}".format(row) name="rbc_{0}".format(row)
# Top/bottom cell are always dummy cells. # Top/bottom cell are always dummy cells.
# Regular array cells are replica cells (>left_rbl and <rows-right_rbl) # Regular array cells are replica cells (>left_rbl and <rows-right_rbl)
# Replic bit specifies which other bit (in the full range (0,rows) to make a replica cell. # Replic bit specifies which other bit (in the full range (0,rows) to make a replica cell.
if (row>self.left_rbl and row<self.total_size-self.right_rbl-1): if (row > self.left_rbl and row < self.total_size - self.right_rbl - 1):
self.cell_inst[row]=self.add_inst(name=name, self.cell_inst[row]=self.add_inst(name=name,
mod=self.replica_cell) mod=self.replica_cell)
self.connect_inst(self.get_bitcell_pins(0, row))
elif row==self.replica_bit: elif row==self.replica_bit:
self.cell_inst[row]=self.add_inst(name=name, self.cell_inst[row]=self.add_inst(name=name,
mod=self.replica_cell) mod=self.replica_cell)
self.connect_inst(self.get_bitcell_pins(0, row))
elif (row == 0 or row == self.total_size - 1):
self.cell_inst[row]=self.add_inst(name=name,
mod=self.edge_cell)
if end_caps_enabled:
self.connect_inst(self.get_bitcell_pins_col_cap(0, row))
else:
self.connect_inst(self.get_bitcell_pins(0, row))
else: else:
self.cell_inst[row]=self.add_inst(name=name, self.cell_inst[row]=self.add_inst(name=name,
mod=self.dummy_cell) mod=self.dummy_cell)
self.connect_inst(self.get_bitcell_pins(0, row)) self.connect_inst(self.get_bitcell_pins(0, row))
def place_instances(self): def place_instances(self):
from tech import cell_properties from tech import cell_properties
# Flip the mirrors if we have an odd number of replica+dummy rows at the bottom # Flip the mirrors if we have an odd number of replica+dummy rows at the bottom
# so that we will start with mirroring rather than not mirroring # so that we will start with mirroring rather than not mirroring
rbl_offset = (self.left_rbl+1)%2 rbl_offset = (self.left_rbl + 1) %2
# if our bitcells are mirrored on the y axis, check if we are in global # if our bitcells are mirrored on the y axis, check if we are in global
# column that needs to be flipped. # column that needs to be flipped.
@ -108,12 +134,10 @@ class replica_column(design.design):
xoffset = self.replica_cell.width xoffset = self.replica_cell.width
for row in range(self.total_size): for row in range(self.total_size):
dir_x = False # name = "bit_r{0}_{1}".format(row, "rbl")
name = "bit_r{0}_{1}".format(row,"rbl") dir_x = cell_properties.bitcell.mirror.x and (row + rbl_offset) % 2
if cell_properties.bitcell.mirror.x and (row+rbl_offset)%2:
dir_x = True
offset = vector(xoffset,self.cell.height*(row+(row+rbl_offset)%2)) offset = vector(xoffset, self.cell.height * (row + (row + rbl_offset) % 2))
if dir_x and dir_y: if dir_x and dir_y:
dir_key = "XY" dir_key = "XY"
@ -127,54 +151,79 @@ class replica_column(design.design):
self.cell_inst[row].place(offset=offset, self.cell_inst[row].place(offset=offset,
mirror=dir_key) mirror=dir_key)
def add_layout_pins(self): def add_layout_pins(self):
""" Add the layout pins """ """ Add the layout pins """
for bl_name in self.cell.get_all_bitline_names(): for bl_name in self.cell.get_all_bitline_names():
bl_pin = self.cell_inst[0].get_pin(bl_name) bl_pin = self.cell_inst[0].get_pin(bl_name)
self.add_layout_pin(text=bl_name, self.add_layout_pin(text=bl_name,
layer="m2", layer=bl_pin.layer,
offset=bl_pin.ll(), 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.total_size): try:
end_caps_enabled = cell_properties.bitcell.end_caps
except AttributeError:
end_caps_enabled = False
if end_caps_enabled:
row_range_max = self.total_size - 1
row_range_min = 1
else:
row_range_max = self.total_size
row_range_min = 0
for row in range(row_range_min, row_range_max):
for wl_name in self.cell.get_all_wl_names(): for wl_name in self.cell.get_all_wl_names():
wl_pin = self.cell_inst[row].get_pin(wl_name) wl_pin = self.cell_inst[row].get_pin(wl_name)
self.add_layout_pin(text="{0}_{1}".format(wl_name,row), self.add_layout_pin(text="{0}_{1}".format(wl_name, row),
layer="m1", 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 every second row and column, add a via for gnd and vdd # Supplies are only connected in the ends
for row in range(self.total_size): 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,
indexed by column and row, for instance use in bitcell_array """ indexed by column and row, for instance use in bitcell_array """
bitcell_pins = [] bitcell_pins = []
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")
return bitcell_pins return bitcell_pins
def get_bitcell_pins_col_cap(self, col, row):
""" Creates a list of connections in the bitcell,
indexed by column and row, for instance use in bitcell_array """
bitcell_pins = []
pin_names = self.cell.get_all_bitline_names()
for pin in pin_names:
bitcell_pins.append(pin + "_{0}".format(col))
bitcell_pins.append("vdd")
return bitcell_pins
def exclude_all_but_replica(self): def exclude_all_but_replica(self):
"""Excludes all bits except the replica cell (self.replica_bit).""" """Excludes all bits except the replica cell (self.replica_bit)."""
for row, cell in self.cell_inst.items(): for row, cell in self.cell_inst.items():
if row != self.replica_bit: if row != self.replica_bit:
self.graph_inst_exclude.add(cell) self.graph_inst_exclude.add(cell)

View File

@ -0,0 +1,117 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2019 Regents of the University of California
# All rights reserved.
#
from bitcell_base_array import bitcell_base_array
from sram_factory import factory
from globals import OPTS
from tech import cell_properties
class row_cap_array(bitcell_base_array):
"""
Generate a dummy row/column for the replica array.
"""
def __init__(self, cols, rows, column_offset=0, mirror=0, name=""):
super().__init__(cols, rows, name, column_offset)
self.mirror = mirror
self.no_instances = True
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
def create_netlist(self):
""" Create and connect the netlist """
self.add_modules()
self.add_pins()
self.create_instances()
def create_layout(self):
self.place_array("dummy_r{0}_c{1}", self.mirror)
self.add_layout_pins()
self.add_boundary()
self.DRC_LVS()
def add_modules(self):
""" Add the modules used in this design """
self.dummy_cell = factory.create(module_type="row_cap_{}".format(OPTS.bitcell))
self.add_mod(self.dummy_cell)
self.cell = factory.create(module_type="bitcell")
def create_instances(self):
""" Create the module instances used in this design """
self.cell_inst = {}
for col in range(self.column_size):
for row in range(1, self.row_size - 1):
name = "bit_r{0}_c{1}".format(row, col)
self.cell_inst[row, col]=self.add_inst(name=name,
mod=self.dummy_cell)
self.connect_inst(self.get_bitcell_pins(col, row))
def get_bitcell_pins(self, col, row):
"""
Creates a list of connections in the bitcell,
indexed by column and row, for instance use in bitcell_array
"""
pin_name = cell_properties.bitcell.cell_1rw1r.pin
bitcell_pins = ["{0}_{1}".format(pin_name.wl0, row),
"{0}_{1}".format(pin_name.wl1, row),
"gnd"]
return bitcell_pins
def place_array(self, name_template, row_offset=0):
# We increase it by a well enclosure so the precharges don't overlap our wells
self.height = self.row_size * self.cell.height
self.width = self.column_size * self.cell.width
xoffset = 0.0
for col in range(self.column_size):
yoffset = self.cell.height
tempx, dir_y = self._adjust_x_offset(xoffset, col, self.column_offset)
for row in range(1, self.row_size - 1):
tempy, dir_x = self._adjust_y_offset(yoffset, row, row_offset)
if dir_x and dir_y:
dir_key = "XY"
elif dir_x:
dir_key = "MX"
elif dir_y:
dir_key = "MY"
else:
dir_key = ""
self.cell_inst[row, col].place(offset=[tempx, tempy],
mirror=dir_key)
yoffset += self.cell.height
xoffset += self.cell.width
def add_layout_pins(self):
""" Add the layout pins """
row_list = self.cell.get_all_wl_names()
for row in range(1, self.row_size - 1):
for cell_row in row_list:
wl_pin = self.cell_inst[row, 0].get_pin(cell_row)
self.add_layout_pin(text=cell_row + "_{0}".format(row),
layer=wl_pin.layer,
offset=wl_pin.ll().scale(0, 1),
width=self.width,
height=wl_pin.height())
# Add vdd/gnd via stacks
for row in range(1, self.row_size - 1):
for col in range(self.column_size):
inst = self.cell_inst[row, col]
for pin_name in ["vdd", "gnd"]:
for pin in inst.get_pins(pin_name):
self.add_power_pin(name=pin.name,
loc=pin.center(),
start_layer=pin.layer)

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

@ -20,7 +20,8 @@ class sense_amp_array(design.design):
Dynamically generated sense amp array for all bitlines. Dynamically generated sense amp array for all bitlines.
""" """
def __init__(self, name, word_size, words_per_row): def __init__(self, name, word_size, words_per_row, num_spare_cols=None, column_offset=0):
design.design.__init__(self, name) design.design.__init__(self, name)
debug.info(1, "Creating {0}".format(self.name)) debug.info(1, "Creating {0}".format(self.name))
self.add_comment("word_size {0}".format(word_size)) self.add_comment("word_size {0}".format(word_size))
@ -28,8 +29,19 @@ class sense_amp_array(design.design):
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:
self.num_spare_cols = 0
else:
self.num_spare_cols = num_spare_cols
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()
@ -59,9 +71,9 @@ class sense_amp_array(design.design):
self.height = self.amp.height self.height = self.amp.height
if self.bitcell.width > self.amp.width: if self.bitcell.width > self.amp.width:
self.width = self.bitcell.width * self.word_size * self.words_per_row self.width = self.bitcell.width * (self.word_size * self.words_per_row + self.num_spare_cols)
else: else:
self.width = self.amp.width * self.word_size * self.words_per_row self.width = self.amp.width * (self.word_size * self.words_per_row + self.num_spare_cols)
self.place_sense_amp_array() self.place_sense_amp_array()
self.add_layout_pins() self.add_layout_pins()
@ -70,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): 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")
@ -89,8 +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): 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))
@ -101,42 +112,49 @@ 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 * self.words_per_row
else:
amp_spacing = self.amp.width * self.words_per_row
for i in range(0, self.word_size): for i in range(0, self.row_size, self.words_per_row):
xoffset = amp_spacing * i index = int(i / self.words_per_row)
xoffset = i * self.bitcell.width
# align the xoffset to the grid of bitcells. This way we
# know when to do the mirroring. if cell_properties.bitcell.mirror.y and (i + self.column_offset) % 2:
grid_x = int(xoffset / self.amp.width)
if cell_properties.bitcell.mirror.y and grid_x % 2:
mirror = "MY" mirror = "MY"
xoffset = xoffset + self.amp.width xoffset = xoffset + self.amp.width
else: else:
mirror = "" mirror = ""
amp_position = vector(xoffset, 0) amp_position = vector(xoffset, 0)
self.local_insts[i].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)
for i in range(0, self.num_spare_cols):
index = self.word_size + i
xoffset = ((self.word_size * self.words_per_row) + i) * self.bitcell.width
if cell_properties.bitcell.mirror.y and (i + self.column_offset) % 2:
mirror = "MY"
xoffset = xoffset + self.amp.width
else:
mirror = ""
amp_position = vector(xoffset, 0)
self.local_insts[index].place(offset=amp_position, mirror=mirror)
def add_layout_pins(self): def add_layout_pins(self):
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())
@ -160,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

@ -7,7 +7,7 @@
# #
import design import design
import debug import debug
from tech import layer from tech import layer, preferred_directions
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
@ -20,7 +20,7 @@ class single_level_column_mux_array(design.design):
Array of column mux to read the bitlines through the 6T. Array of column mux to read the bitlines through the 6T.
""" """
def __init__(self, name, columns, word_size, bitcell_bl="bl", bitcell_br="br"): def __init__(self, name, columns, word_size, bitcell_bl="bl", bitcell_br="br", column_offset=0):
design.design.__init__(self, name) design.design.__init__(self, name)
debug.info(1, "Creating {0}".format(self.name)) debug.info(1, "Creating {0}".format(self.name))
self.add_comment("cols: {0} word_size: {1} bl: {2} br: {3}".format(columns, word_size, bitcell_bl, bitcell_br)) self.add_comment("cols: {0} word_size: {1} bl: {2} br: {3}".format(columns, word_size, bitcell_bl, bitcell_br))
@ -30,13 +30,21 @@ class single_level_column_mux_array(design.design):
self.words_per_row = int(self.columns / self.word_size) self.words_per_row = int(self.columns / self.word_size)
self.bitcell_bl = bitcell_bl self.bitcell_bl = bitcell_bl
self.bitcell_br = bitcell_br self.bitcell_br = bitcell_br
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.li_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.sel_layer] == "V":
self.via_directions = ("H", "H")
else:
self.via_directions = "pref"
self.create_netlist() self.create_netlist()
if not OPTS.netlist_only: if not OPTS.netlist_only:
@ -90,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 = []
@ -112,7 +120,7 @@ class single_level_column_mux_array(design.design):
# For every column, add a pass gate # For every column, add a pass gate
for col_num in range(self.columns): for col_num in range(self.columns):
xoffset = col_num * self.mux.width xoffset = col_num * self.mux.width
if cell_properties.bitcell.mirror.y and col_num % 2: if cell_properties.bitcell.mirror.y and (col_num + self.column_offset) % 2:
mirror = "MY" mirror = "MY"
xoffset = xoffset + self.mux.width xoffset = xoffset + self.mux.width
else: else:
@ -149,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)
@ -171,75 +179,57 @@ 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)
self.add_path("poly", [offset, gate_offset]) self.add_path("poly", [offset, gate_offset])
def route_bitlines(self): def route_bitlines(self):
""" Connect the output bit-lines to form the appropriate width mux """ """ Connect the output bit-lines to form the appropriate width mux """
from tech import cell_properties
for j in range(self.columns): for j in range(self.columns):
bl_offset = self.mux_inst[j].get_pin("bl_out").bc()
br_offset = self.mux_inst[j].get_pin("br_out").bc()
bl_out_offset = bl_offset - vector(0, (self.words_per_row + 1) * self.col_mux_stack_pitch) bl_offset_begin = self.mux_inst[j].get_pin("bl_out").bc()
br_out_offset = br_offset - vector(0, (self.words_per_row + 2) * self.col_mux_stack_pitch) br_offset_begin = self.mux_inst[j].get_pin("br_out").bc()
bl_out_offset_end = bl_out_offset + vector(0, self.route_height) bl_out_offset_begin = bl_offset_begin - vector(0, (self.words_per_row + 1) * self.sel_pitch)
br_out_offset_end = br_out_offset + vector(0, self.route_height) br_out_offset_begin = br_offset_begin - vector(0, (self.words_per_row + 2) * self.sel_pitch)
if cell_properties.bitcell.mirror.y and j % 2: # Add the horizontal wires for the first bit
tmp_bl_out_end = br_out_offset_end if j % self.words_per_row == 0:
tmp_br_out_end = bl_out_offset_end bl_offset_end = self.mux_inst[j + self.words_per_row - 1].get_pin("bl_out").bc()
else: br_offset_end = self.mux_inst[j + self.words_per_row - 1].get_pin("br_out").bc()
tmp_bl_out_end = bl_out_offset_end bl_out_offset_end = bl_offset_end - vector(0, (self.words_per_row + 1) * self.sel_pitch)
tmp_br_out_end = br_out_offset_end br_out_offset_end = br_offset_end - vector(0, (self.words_per_row + 2) * self.sel_pitch)
if (j % self.words_per_row) == 0: self.add_path(self.sel_layer, [bl_out_offset_begin, bl_out_offset_end])
# Create the metal1 to connect the n-way mux output from the pass gate self.add_path(self.sel_layer, [br_out_offset_begin, br_out_offset_end])
# These will be located below the select lines. Yes, these are M2 width
# to ensure vias are enclosed and M1 min width rules.
width = self.m2_width + self.mux.width * (self.words_per_row - 1)
if cell_properties.bitcell.mirror.y and (j % 2) == 0:
bl = self.mux.get_pin("bl")
br = self.mux.get_pin("br")
dist = abs(bl.ll().x - br.ll().x)
else:
dist = 0
self.add_path(self.col_mux_stack[0], [bl_out_offset, bl_out_offset + vector(width + dist, 0)])
self.add_path(self.col_mux_stack[0], [br_out_offset, br_out_offset + vector(width - dist, 0)])
# 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_out_offset, start=bl_offset_begin,
end=tmp_bl_out_end) 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_out_offset, start=br_offset_begin,
end=tmp_br_out_end) end=br_out_offset_begin)
# This via is on the right of the wire
self.add_via_center(layers=self.col_mux_stack,
offset=bl_out_offset)
# This via is on the left of the wire
self.add_via_center(layers=self.col_mux_stack,
offset=br_out_offset)
else: else:
self.add_path(self.col_mux_stack[2], [bl_out_offset, bl_offset]) self.add_path(self.bitline_layer, [bl_out_offset_begin, bl_offset_begin])
self.add_path(self.col_mux_stack[2], [br_out_offset, br_offset]) 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) to_layer=self.sel_layer,
# This via is on the left of the wire offset=bl_out_offset_begin,
self.add_via_center(layers=self.col_mux_stack, directions=self.via_directions)
offset=br_out_offset)
# This via is on the left of the wire
self.add_via_stack_center(from_layer=self.bitline_layer,
to_layer=self.sel_layer,
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

@ -7,15 +7,12 @@
# #
import debug import debug
import design import design
import math from tech import drc, layer
from tech import drc
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
from tech import cell_properties
class wordline_driver_array(design.design):
class wordline_driver(design.design):
""" """
Creates a Wordline Driver Creates a Wordline Driver
Generates the wordline-driver to drive the bitcell Generates the wordline-driver to drive the bitcell
@ -26,21 +23,9 @@ class wordline_driver(design.design):
debug.info(1, "Creating {0}".format(self.name)) debug.info(1, "Creating {0}".format(self.name))
self.add_comment("rows: {0} cols: {1}".format(rows, cols)) self.add_comment("rows: {0} cols: {1}".format(rows, cols))
self.bitcell_rows = rows self.rows = rows
self.bitcell_cols = cols self.cols = cols
b = factory.create(module_type="bitcell")
try:
self.cell_multiple = cell_properties.bitcell.decoder_bitcell_multiple
except AttributeError:
self.cell_multiple = 1
self.cell_height = self.cell_multiple * b.height
# We may have more than one bitcell per decoder row
self.num_rows = math.ceil(self.bitcell_rows / self.cell_multiple)
# We will place this many final decoders per row
self.decoders_per_row = math.ceil(self.bitcell_rows / self.num_rows)
self.create_netlist() self.create_netlist()
if not OPTS.netlist_only: if not OPTS.netlist_only:
self.create_layout() self.create_layout()
@ -51,7 +36,10 @@ class wordline_driver(design.design):
self.create_drivers() self.create_drivers()
def create_layout(self): def create_layout(self):
self.setup_layout_constants() if "li" in layer:
self.route_layer = "li"
else:
self.route_layer = "m1"
self.place_drivers() self.place_drivers()
self.route_layout() self.route_layout()
self.route_vdd_gnd() self.route_vdd_gnd()
@ -61,104 +49,99 @@ class wordline_driver(design.design):
def add_pins(self): def add_pins(self):
# inputs to wordline_driver. # inputs to wordline_driver.
for i in range(self.bitcell_rows): for i in range(self.rows):
self.add_pin("in_{0}".format(i), "INPUT") self.add_pin("in_{0}".format(i), "INPUT")
# Outputs from wordline_driver. # Outputs from wordline_driver.
for i in range(self.bitcell_rows): for i in range(self.rows):
self.add_pin("wl_{0}".format(i), "OUTPUT") self.add_pin("wl_{0}".format(i), "OUTPUT")
self.add_pin("en", "INPUT") self.add_pin("en", "INPUT")
self.add_pin("vdd", "POWER") self.add_pin("vdd", "POWER")
self.add_pin("gnd", "GROUND") self.add_pin("gnd", "GROUND")
def add_modules(self): def add_modules(self):
self.and2 = factory.create(module_type="pand2", self.wl_driver = factory.create(module_type="wordline_driver",
height=self.cell_height, size=self.cols)
size=self.bitcell_cols) self.add_mod(self.wl_driver)
self.add_mod(self.and2)
def route_vdd_gnd(self): def route_vdd_gnd(self):
""" """
Add a pin for each row of vdd/gnd which Add a pin for each row of vdd/gnd which
are must-connects next level up. are must-connects next level up.
""" """
if OPTS.tech_name == "sky130":
# Find the x offsets for where the vias/pins should be placed
xoffset_list = [self.and_inst[0].lx()]
for num in range(self.bitcell_rows):
# this will result in duplicate polygons for rails, but who cares
# use the inverter offset even though it will be the and's too
(gate_offset, y_dir) = self.get_gate_offset(0,
self.and2.height,
num)
# Route both supplies
for name in ["vdd", "gnd"]: for name in ["vdd", "gnd"]:
supply_pin = self.and_inst[num].get_pin(name) supply_pins = self.wld_inst[0].get_pins(name)
for pin in supply_pins:
# Add pins in two locations self.add_layout_pin_segment_center(text=name,
for xoffset in xoffset_list: layer=pin.layer,
pin_pos = vector(xoffset, supply_pin.cy()) start=pin.bc(),
self.add_power_pin(name, pin_pos) end=vector(pin.cx(), self.height))
else:
# Find the x offsets for where the vias/pins should be placed
xoffset_list = [self.wld_inst[0].rx()]
for num in range(self.rows):
# this will result in duplicate polygons for rails, but who cares
# use the inverter offset even though it will be the and's too
(gate_offset, y_dir) = self.get_gate_offset(0,
self.wl_driver.height,
num)
# Route both supplies
for name in ["vdd", "gnd"]:
supply_pin = self.wld_inst[num].get_pin(name)
# Add pins in two locations
for xoffset in xoffset_list:
pin_pos = vector(xoffset, supply_pin.cy())
self.add_power_pin(name, pin_pos)
def create_drivers(self): def create_drivers(self):
self.and_inst = [] self.wld_inst = []
for row in range(self.bitcell_rows): for row in range(self.rows):
name_and = "wl_driver_and{}".format(row) name_and = "wl_driver_and{}".format(row)
# add and2 # add and2
self.and_inst.append(self.add_inst(name=name_and, self.wld_inst.append(self.add_inst(name=name_and,
mod=self.and2)) mod=self.wl_driver))
self.connect_inst(["in_{0}".format(row), self.connect_inst(["in_{0}".format(row),
"en", "en",
"wl_{0}".format(row), "wl_{0}".format(row),
"vdd", "gnd"]) "vdd", "gnd"])
def setup_layout_constants(self):
# We may have more than one bitcell per decoder row
self.driver_rows = math.ceil(self.bitcell_rows / self.cell_multiple)
# We will place this many final decoders per row
self.decoders_per_row = math.ceil(self.bitcell_rows / self.driver_rows)
def place_drivers(self): def place_drivers(self):
for row in range(self.rows):
if (row % 2):
y_offset = self.wl_driver.height * (row + 1)
inst_mirror = "MX"
else:
y_offset = self.wl_driver.height * row
inst_mirror = "R0"
and2_offset = [self.wl_driver.width, y_offset]
# add and2
self.wld_inst[row].place(offset=and2_offset,
mirror=inst_mirror)
# Leave a well gap to separate the bitcell array well from this well # Leave a well gap to separate the bitcell array well from this well
well_gap = 2 * drc("pwell_to_nwell") + drc("nwell_enclose_active") well_gap = 2 * drc("pwell_to_nwell") + drc("nwell_enclose_active")
self.width = self.wl_driver.width + well_gap
self.width = self.decoders_per_row * self.and2.width + well_gap self.height = self.wl_driver.height * self.rows
self.height = self.and2.height * self.driver_rows
for inst_index in range(self.bitcell_rows):
row = math.floor(inst_index / self.decoders_per_row)
dec = inst_index % self.decoders_per_row
if (row % 2):
y_offset = self.and2.height * (row + 1)
inst_mirror = "MX"
else:
y_offset = self.and2.height * row
inst_mirror = "R0"
x_offset = dec * self.and2.width
and2_offset = [x_offset, y_offset]
# add and2
self.and_inst[inst_index].place(offset=and2_offset,
mirror=inst_mirror)
def route_layout(self): def route_layout(self):
""" Route all of the signals """ """ Route all of the signals """
# Wordline enable connection # Wordline enable connection
en_pin = self.and_inst[0].get_pin("B") en_pin = self.wld_inst[0].get_pin("B")
en_bottom_pos = vector(en_pin.lx(), 0) en_bottom_pos = vector(en_pin.lx(), 0)
en_pin = self.add_layout_pin(text="en", en_pin = self.add_layout_pin(text="en",
layer="m2", layer="m2",
offset=en_bottom_pos, offset=en_bottom_pos,
height=self.height) height=self.height)
for inst_index in range(self.bitcell_rows): for row in range(self.rows):
and_inst = self.and_inst[inst_index] and_inst = self.wld_inst[row]
row = math.floor(inst_index / self.decoders_per_row)
# Drop a via # Drop a via
b_pin = and_inst.get_pin("B") b_pin = and_inst.get_pin("B")
@ -167,18 +150,12 @@ class wordline_driver(design.design):
offset=b_pin.center()) offset=b_pin.center())
# connect the decoder input pin to and2 A # connect the decoder input pin to and2 A
a_pin = and_inst.get_pin("A") self.copy_layout_pin(and_inst, "A", "in_{0}".format(row))
a_pos = a_pin.center()
# must under the clk line in M1
self.add_layout_pin_segment_center(text="in_{0}".format(row),
layer="m1",
start=vector(0, a_pos.y),
end=a_pos)
# output each WL on the right # output each WL on the right
wl_offset = and_inst.get_pin("Z").rc() wl_offset = and_inst.get_pin("Z").rc()
self.add_layout_pin_segment_center(text="wl_{0}".format(row), self.add_layout_pin_segment_center(text="wl_{0}".format(row),
layer="m1", layer=self.route_layer,
start=wl_offset, start=wl_offset,
end=wl_offset - vector(self.m1_width, 0)) end=wl_offset - vector(self.m1_width, 0))
@ -189,7 +166,7 @@ class wordline_driver(design.design):
""" """
stage_effort_list = [] stage_effort_list = []
stage1 = self.and2.get_stage_effort(external_cout, inp_is_rise) stage1 = self.wl_driver.get_stage_effort(external_cout, inp_is_rise)
stage_effort_list.append(stage1) stage_effort_list.append(stage1)
return stage_effort_list return stage_effort_list
@ -200,5 +177,5 @@ class wordline_driver(design.design):
the enable connections in the bank the enable connections in the bank
""" """
# The enable is connected to a and2 for every row. # The enable is connected to a and2 for every row.
total_cin = self.and2.get_cin() * self.rows total_cin = self.wl_driver.get_cin() * self.rows
return total_cin return total_cin

View File

@ -7,6 +7,7 @@
# #
import design import design
import debug import debug
from tech import drc
from sram_factory import factory from sram_factory import factory
from vector import vector from vector import vector
from globals import OPTS from globals import OPTS
@ -18,7 +19,8 @@ class write_driver_array(design.design):
Dynamically generated write driver array of all bitlines. Dynamically generated write driver array of all bitlines.
""" """
def __init__(self, name, columns, word_size, write_size=None): def __init__(self, name, columns, word_size, num_spare_cols=None, write_size=None, column_offset=0):
design.design.__init__(self, name) design.design.__init__(self, name)
debug.info(1, "Creating {0}".format(self.name)) debug.info(1, "Creating {0}".format(self.name))
self.add_comment("columns: {0}".format(columns)) self.add_comment("columns: {0}".format(columns))
@ -27,7 +29,12 @@ class write_driver_array(design.design):
self.columns = columns self.columns = columns
self.word_size = word_size self.word_size = word_size
self.write_size = write_size self.write_size = write_size
self.column_offset = column_offset
self.words_per_row = int(columns / word_size) self.words_per_row = int(columns / word_size)
if not num_spare_cols:
self.num_spare_cols = 0
else:
self.num_spare_cols = num_spare_cols
if self.write_size: if self.write_size:
self.num_wmasks = int(self.word_size / self.write_size) self.num_wmasks = int(self.word_size / self.write_size)
@ -60,9 +67,13 @@ class write_driver_array(design.design):
def create_layout(self): def create_layout(self):
if self.bitcell.width > self.driver.width: if self.bitcell.width > self.driver.width:
self.width = self.columns * self.bitcell.width self.width = (self.columns + self.num_spare_cols) * self.bitcell.width
self.width_regular_cols = self.columns * self.bitcell.width
self.single_col_width = self.bitcell.width
else: else:
self.width = self.columns * self.driver.width self.width = (self.columns + self.num_spare_cols) * self.driver.width
self.width_regular_cols = self.columns * self.driver.width
self.single_col_width = self.driver.width
self.height = self.driver.height self.height = self.driver.height
self.place_write_array() self.place_write_array()
@ -71,13 +82,16 @@ class write_driver_array(design.design):
self.DRC_LVS() self.DRC_LVS()
def add_pins(self): def add_pins(self):
for i in range(self.word_size): for i in range(self.word_size + self.num_spare_cols):
self.add_pin(self.data_name + "_{0}".format(i), "INPUT") self.add_pin(self.data_name + "_{0}".format(i), "INPUT")
for i in range(self.word_size): for i in range(self.word_size + self.num_spare_cols):
self.add_pin(self.get_bl_name() + "_{0}".format(i), "OUTPUT") self.add_pin(self.get_bl_name() + "_{0}".format(i), "OUTPUT")
self.add_pin(self.get_br_name() + "_{0}".format(i), "OUTPUT") self.add_pin(self.get_br_name() + "_{0}".format(i), "OUTPUT")
if self.write_size: if self.write_size:
for i in range(self.num_wmasks): for i in range(self.num_wmasks + self.num_spare_cols):
self.add_pin(self.en_name + "_{0}".format(i), "INPUT")
elif self.num_spare_cols and not self.write_size:
for i in range(self.num_spare_cols + 1):
self.add_pin(self.en_name + "_{0}".format(i), "INPUT") self.add_pin(self.en_name + "_{0}".format(i), "INPUT")
else: else:
self.add_pin(self.en_name, "INPUT") self.add_pin(self.en_name, "INPUT")
@ -112,12 +126,34 @@ class write_driver_array(design.design):
if w == self.write_size: if w == self.write_size:
w = 0 w = 0
windex+=1 windex+=1
elif self.num_spare_cols and not self.write_size:
self.connect_inst([self.data_name + "_{0}".format(index),
self.get_bl_name() + "_{0}".format(index),
self.get_br_name() + "_{0}".format(index),
self.en_name + "_{0}".format(0), "vdd", "gnd"])
else: else:
self.connect_inst([self.data_name + "_{0}".format(index), self.connect_inst([self.data_name + "_{0}".format(index),
self.get_bl_name() + "_{0}".format(index), self.get_bl_name() + "_{0}".format(index),
self.get_br_name() + "_{0}".format(index), self.get_br_name() + "_{0}".format(index),
self.en_name, "vdd", "gnd"]) self.en_name, "vdd", "gnd"])
for i in range(self.num_spare_cols):
index = self.word_size + i
if self.write_size:
offset = self.num_wmasks
else:
offset = 1
name = "write_driver{}".format(self.columns + i)
self.driver_insts[index]=self.add_inst(name=name,
mod=self.driver)
self.connect_inst([self.data_name + "_{0}".format(index),
self.get_bl_name() + "_{0}".format(index),
self.get_br_name() + "_{0}".format(index),
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:
@ -128,7 +164,7 @@ class write_driver_array(design.design):
index = int(i / self.words_per_row) index = int(i / self.words_per_row)
xoffset = i * self.driver_spacing xoffset = i * self.driver_spacing
if cell_properties.bitcell.mirror.y and i % 2: if cell_properties.bitcell.mirror.y and (i + self.column_offset) % 2:
mirror = "MY" mirror = "MY"
xoffset = xoffset + self.driver.width xoffset = xoffset + self.driver.width
else: else:
@ -137,8 +173,22 @@ class write_driver_array(design.design):
base = vector(xoffset, 0) base = vector(xoffset, 0)
self.driver_insts[index].place(offset=base, mirror=mirror) self.driver_insts[index].place(offset=base, mirror=mirror)
# place spare write drivers (if spare columns are specified)
for i in range(self.num_spare_cols):
index = self.word_size + i
xoffset = (self.columns + i) * self.driver_spacing
if cell_properties.bitcell.mirror.y and (i + self.column_offset) % 2:
mirror = "MY"
xoffset = xoffset + self.driver.width
else:
mirror = ""
base = vector(xoffset, 0)
self.driver_insts[index].place(offset=base, mirror=mirror)
def add_layout_pins(self): def add_layout_pins(self):
for i in range(self.word_size): for i in range(self.word_size + self.num_spare_cols):
inst = self.driver_insts[i] inst = self.driver_insts[i]
din_pin = inst.get_pin(inst.mod.din_name) din_pin = inst.get_pin(inst.mod.din_name)
self.add_layout_pin(text=self.data_name + "_{0}".format(i), self.add_layout_pin(text=self.data_name + "_{0}".format(i),
@ -183,6 +233,31 @@ class write_driver_array(design.design):
offset=en_pin.ll(), offset=en_pin.ll(),
width=wmask_en_len - en_gap, width=wmask_en_len - en_gap,
height=en_pin.height()) height=en_pin.height())
for i in range(self.num_spare_cols):
inst = self.driver_insts[self.word_size + i]
en_pin = inst.get_pin(inst.mod.en_name)
self.add_layout_pin(text=self.en_name + "_{0}".format(i + self.num_wmasks),
layer="m1",
offset=en_pin.lr() + vector(-drc("minwidth_m1"),0))
elif self.num_spare_cols and not self.write_size:
# shorten enable rail to accomodate those for spare write drivers
inst = self.driver_insts[0]
en_pin = inst.get_pin(inst.mod.en_name)
self.add_layout_pin(text=self.en_name + "_{0}".format(0),
layer="m1",
offset=en_pin.ll(),
width=self.width_regular_cols - self.words_per_row * en_pin.width())
# individual enables for every spare write driver
for i in range(self.num_spare_cols):
inst = self.driver_insts[self.word_size + i]
en_pin = inst.get_pin(inst.mod.en_name)
self.add_layout_pin(text=self.en_name + "_{0}".format(i + 1),
layer="m1",
offset=en_pin.lr() + vector(-drc("minwidth_m1"),0))
else: else:
inst = self.driver_insts[0] inst = self.driver_insts[0]
self.add_layout_pin(text=self.en_name, self.add_layout_pin(text=self.en_name,

View File

@ -10,7 +10,7 @@ import debug
from sram_factory import factory from sram_factory import factory
from vector import vector from vector import vector
from globals import OPTS from globals import OPTS
from tech import layer
class write_mask_and_array(design.design): class write_mask_and_array(design.design):
""" """
@ -18,7 +18,7 @@ class write_mask_and_array(design.design):
The write mask AND array goes between the write driver array and the sense amp array. The write mask AND array goes between the write driver array and the sense amp array.
""" """
def __init__(self, name, columns, word_size, write_size, port=0): def __init__(self, name, columns, word_size, write_size, column_offset=0):
design.design.__init__(self, name) design.design.__init__(self, name)
debug.info(1, "Creating {0}".format(self.name)) debug.info(1, "Creating {0}".format(self.name))
self.add_comment("columns: {0}".format(columns)) self.add_comment("columns: {0}".format(columns))
@ -28,7 +28,7 @@ class write_mask_and_array(design.design):
self.columns = columns self.columns = columns
self.word_size = word_size self.word_size = word_size
self.write_size = write_size self.write_size = write_size
self.port = port self.column_offset = column_offset
self.words_per_row = int(columns / word_size) self.words_per_row = int(columns / word_size)
self.num_wmasks = int(word_size / write_size) self.num_wmasks = int(word_size / write_size)
@ -60,7 +60,7 @@ class write_mask_and_array(design.design):
# Size the AND gate for the number of write drivers it drives, which is equal to the write size. # Size the AND gate for the number of write drivers it drives, which is equal to the write size.
# Assume stage effort of 3 to compute the size # Assume stage effort of 3 to compute the size
self.and2 = factory.create(module_type="pand2", self.and2 = factory.create(module_type="pand2",
size=self.write_size / 4.0) size=max(self.write_size / 4.0, 1))
self.add_mod(self.and2) self.add_mod(self.and2)
def create_and2_array(self): def create_and2_array(self):
@ -93,7 +93,7 @@ class write_mask_and_array(design.design):
self.width = self.bitcell.width * self.columns self.width = self.bitcell.width * self.columns
self.height = self.and2.height self.height = self.and2.height
for i in range(self.num_wmasks): for i in range(self.num_wmasks):
base = vector(i * self.wmask_en_len, 0) base = vector(i * self.wmask_en_len, 0)
self.and2_insts[i].place(base) self.and2_insts[i].place(base)
@ -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,16 +134,14 @@ 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)
self.add_power_pin(supply, supply_pin.center()) self.add_power_pin(supply, supply_pin.center(), start_layer=supply_pin.layer)
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)
supply_pin_right = self.and2_insts[self.num_wmasks - 1].get_pin(supply) supply_pin_right = self.and2_insts[self.num_wmasks - 1].get_pin(supply)
self.add_path("m1", [supply_pin_left.lc(), supply_pin_right.rc()]) self.add_path(supply_pin_left.layer, [supply_pin_left.lc(), supply_pin_right.rc()])
def get_cin(self): def get_cin(self):
"""Get the relative capacitance of all the input connections in the bank""" """Get the relative capacitance of all the input connections in the bank"""
# The enable is connected to an and2 for every row. # The enable is connected to an and2 for every row.
return self.and2.get_cin() * len(self.and2_insts) return self.and2.get_cin() * len(self.and2_insts)

View File

@ -53,7 +53,8 @@ from sram_config import sram_config
# Configure the SRAM organization # Configure the SRAM organization
c = sram_config(word_size=OPTS.word_size, c = sram_config(word_size=OPTS.word_size,
num_words=OPTS.num_words, num_words=OPTS.num_words,
write_size=OPTS.write_size) write_size=OPTS.write_size,
num_spare_rows=OPTS.num_spare_rows)
debug.print_raw("Words per row: {}".format(c.words_per_row)) debug.print_raw("Words per row: {}".format(c.words_per_row))
output_extensions = ["sp", "v", "lib", "py", "html", "log"] output_extensions = ["sp", "v", "lib", "py", "html", "log"]

View File

@ -21,10 +21,10 @@ class options(optparse.Values):
################### ###################
# This is the technology directory. # This is the technology directory.
openram_tech = "" openram_tech = ""
# This is the name of the technology. # This is the name of the technology.
tech_name = "" tech_name = ""
# Port configuration (1-2 ports allowed) # Port configuration (1-2 ports allowed)
num_rw_ports = 1 num_rw_ports = 1
num_r_ports = 0 num_r_ports = 0
@ -32,7 +32,7 @@ class options(optparse.Values):
# Write mask size, default will be overwritten with word_size if not user specified # Write mask size, default will be overwritten with word_size if not user specified
write_size = None write_size = None
# These will get initialized by the user or the tech file # These will get initialized by the user or the tech file
nominal_corner_only = False nominal_corner_only = False
supply_voltages = "" supply_voltages = ""
@ -44,23 +44,25 @@ class options(optparse.Values):
# word_size = 0 # word_size = 0
# You can manually specify banks, but it is better to auto-detect it. # You can manually specify banks, but it is better to auto-detect it.
num_banks = 1 num_banks = 1
num_spare_rows = 0
# num_spare_cols = 0
################### ###################
# Optimization options # Optimization options
################### ###################
# Approximate percentage of delay compared to bitlines # Approximate percentage of delay compared to bitlines
rbl_delay_percentage = 0.5 rbl_delay_percentage = 0.5
# Allow manual adjustment of the delay chain over automatic # Allow manual adjustment of the delay chain over automatic
use_tech_delay_chain_size = False use_tech_delay_chain_size = False
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.
################### ###################
# This is the temp directory where all intermediate results are stored. # This is the temp directory where all intermediate results are stored.
try: try:
# If user defined the temporary location in their environment, use it # If user defined the temporary location in their environment, use it
@ -91,7 +93,7 @@ class options(optparse.Values):
# Run with extracted parasitics # Run with extracted parasitics
use_pex = False use_pex = False
################### ###################
# Tool options # Tool options
################### ###################
@ -108,7 +110,9 @@ class options(optparse.Values):
drc_exe = None drc_exe = None
lvs_exe = None lvs_exe = None
pex_exe = None pex_exe = None
# For sky130, we need magic for filtering.
magic_exe = None
# Should we print out the banner at startup # Should we print out the banner at startup
print_banner = True print_banner = True
@ -121,9 +125,14 @@ class options(optparse.Values):
analytical_delay = True analytical_delay = True
# Purge the temp directory after a successful # Purge the temp directory after a successful
# run (doesn't purge on errors, anyhow) # run (doesn't purge on errors, anyhow)
# Route the input/output pins to the perimeter
perimeter_pins = False
purge_temp = True purge_temp = True
# These are the default modules that can be over-riden # These are the default modules that can be over-riden
bitcell_suffix = ""
bank_select = "bank_select" bank_select = "bank_select"
bitcell_array = "bitcell_array" bitcell_array = "bitcell_array"
bitcell = "bitcell" bitcell = "bitcell"
@ -133,10 +142,12 @@ class options(optparse.Values):
delay_chain = "delay_chain" delay_chain = "delay_chain"
dff_array = "dff_array" dff_array = "dff_array"
dff = "dff" dff = "dff"
dummy_bitcell = "dummy_bitcell" inv_dec = "pinv"
nand2_dec = "pnand2"
nand3_dec = "pnand3"
nand4_dec = "pnand4" # Not available right now
precharge_array = "precharge_array" precharge_array = "precharge_array"
ptx = "ptx" ptx = "ptx"
replica_bitcell = "replica_bitcell"
replica_bitline = "replica_bitline" replica_bitline = "replica_bitline"
sense_amp_array = "sense_amp_array" sense_amp_array = "sense_amp_array"
sense_amp = "sense_amp" sense_amp = "sense_amp"
@ -146,4 +157,3 @@ class options(optparse.Values):
write_driver_array = "write_driver_array" write_driver_array = "write_driver_array"
write_driver = "write_driver" write_driver = "write_driver"
write_mask_and_array = "write_mask_and_array" write_mask_and_array = "write_mask_and_array"

View File

@ -13,16 +13,16 @@ from sram_factory import factory
class pand2(pgate.pgate): class pand2(pgate.pgate):
""" """
This is a simple buffer used for driving loads. This is an AND (or NAND) with configurable drive strength.
""" """
def __init__(self, name, size=1, height=None): def __init__(self, name, size=1, height=None, vertical=False, add_wells=True):
debug.info(1, "Creating pnand2 {}".format(name)) debug.info(1, "Creating pand2 {}".format(name))
self.add_comment("size: {}".format(size)) self.add_comment("size: {}".format(size))
self.vertical = vertical
self.size = size self.size = size
# Creates the netlist and layout pgate.pgate.__init__(self, name, height, add_wells)
pgate.pgate.__init__(self, name, height)
def create_netlist(self): def create_netlist(self):
self.add_pins() self.add_pins()
@ -30,17 +30,25 @@ class pand2(pgate.pgate):
self.create_insts() self.create_insts()
def create_modules(self): def create_modules(self):
self.nand = factory.create(module_type="pnand2", height=self.height) self.nand = factory.create(module_type="pnand2",
self.add_mod(self.nand) height=self.height,
add_wells=self.vertical)
self.inv = factory.create(module_type="pdriver", self.inv = factory.create(module_type="pdriver",
neg_polarity=True, size_list=[self.size],
fanout=self.size, height=self.height,
height=self.height) add_wells=self.add_wells)
self.add_mod(self.nand)
self.add_mod(self.inv) self.add_mod(self.inv)
def create_layout(self): def create_layout(self):
self.width = self.nand.width + self.inv.width if self.vertical:
self.height = 2 * self.nand.height
self.width = max(self.nand.width, self.inv.width)
else:
self.width = self.nand.width + self.inv.width
self.place_insts() self.place_insts()
self.add_wires() self.add_wires()
self.add_layout_pins() self.add_layout_pins()
@ -68,17 +76,60 @@ class pand2(pgate.pgate):
# Add NAND to the right # Add NAND to the right
self.nand_inst.place(offset=vector(0, 0)) self.nand_inst.place(offset=vector(0, 0))
# Add INV to the right if self.vertical:
self.inv_inst.place(offset=vector(self.nand_inst.rx(), 0)) # Add INV above
self.inv_inst.place(offset=vector(self.inv.width,
2 * self.nand.height),
mirror="XY")
else:
# Add INV to the right
self.inv_inst.place(offset=vector(self.nand_inst.rx(), 0))
def route_supply_rails(self):
""" Add vdd/gnd rails to the top, (middle), and bottom. """
self.add_layout_pin_rect_center(text="gnd",
layer=self.route_layer,
offset=vector(0.5 * self.width, 0),
width=self.width)
# Second gnd of the inverter gate
if self.vertical:
self.add_layout_pin_rect_center(text="gnd",
layer=self.route_layer,
offset=vector(0.5 * self.width, self.height),
width=self.width)
if self.vertical:
# Shared between two gates
y_offset = 0.5 * self.height
else:
y_offset = self.height
self.add_layout_pin_rect_center(text="vdd",
layer=self.route_layer,
offset=vector(0.5 * self.width, y_offset),
width=self.width)
def add_wires(self): def add_wires(self):
# nand Z to inv A # nand Z to inv A
z1_pin = self.nand_inst.get_pin("Z") z1_pin = self.nand_inst.get_pin("Z")
a2_pin = self.inv_inst.get_pin("A") a2_pin = self.inv_inst.get_pin("A")
mid1_point = vector(0.5 * (z1_pin.cx() + a2_pin.cx()), z1_pin.cy()) if self.vertical:
mid2_point = vector(mid1_point, a2_pin.cy()) route_layer = "m2"
self.add_path(self.route_layer, self.add_via_stack_center(offset=z1_pin.center(),
[z1_pin.center(), mid1_point, mid2_point, a2_pin.center()]) from_layer=z1_pin.layer,
to_layer=route_layer)
self.add_zjog(route_layer,
z1_pin.uc(),
a2_pin.bc(),
"V")
self.add_via_stack_center(offset=a2_pin.center(),
from_layer=a2_pin.layer,
to_layer=route_layer)
else:
route_layer = self.route_layer
mid1_point = vector(z1_pin.cx(), a2_pin.cy())
self.add_path(route_layer,
[z1_pin.center(), mid1_point, a2_pin.center()])
def add_layout_pins(self): def add_layout_pins(self):
pin = self.inv_inst.get_pin("Z") pin = self.inv_inst.get_pin("Z")

View File

@ -15,14 +15,15 @@ class pand3(pgate.pgate):
""" """
This is a simple buffer used for driving loads. This is a simple buffer used for driving loads.
""" """
def __init__(self, name, size=1, height=None): def __init__(self, name, size=1, height=None, vertical=False, add_wells=True):
debug.info(1, "Creating pand3 {}".format(name)) debug.info(1, "Creating pand3 {}".format(name))
self.add_comment("size: {}".format(size)) self.add_comment("size: {}".format(size))
self.vertical = vertical
self.size = size self.size = size
# Creates the netlist and layout # Creates the netlist and layout
pgate.pgate.__init__(self, name, height) pgate.pgate.__init__(self, name, height, add_wells)
def create_netlist(self): def create_netlist(self):
self.add_pins() self.add_pins()
@ -31,16 +32,27 @@ class pand3(pgate.pgate):
def create_modules(self): def create_modules(self):
# Shield the cap, but have at least a stage effort of 4 # Shield the cap, but have at least a stage effort of 4
self.nand = factory.create(module_type="pnand3", height=self.height) self.nand = factory.create(module_type="pnand3",
self.add_mod(self.nand) height=self.height,
add_wells=self.vertical)
self.inv = factory.create(module_type="pinv", # Add the well tap to the inverter because when stacked
size=self.size, # vertically it is sometimes narrower
height=self.height) self.inv = factory.create(module_type="pdriver",
size_list=[self.size],
height=self.height,
add_wells=self.add_wells)
self.add_mod(self.nand)
self.add_mod(self.inv) self.add_mod(self.inv)
def create_layout(self): def create_layout(self):
self.width = self.nand.width + self.inv.width if self.vertical:
self.height = 2 * self.nand.height
self.width = max(self.nand.width, self.inv.width)
else:
self.width = self.nand.width + self.inv.width
self.place_insts() self.place_insts()
self.add_wires() self.add_wires()
self.add_layout_pins() self.add_layout_pins()
@ -69,18 +81,61 @@ class pand3(pgate.pgate):
# Add NAND to the right # Add NAND to the right
self.nand_inst.place(offset=vector(0, 0)) self.nand_inst.place(offset=vector(0, 0))
# Add INV to the right if self.vertical:
self.inv_inst.place(offset=vector(self.nand_inst.rx(), 0)) # Add INV above
self.inv_inst.place(offset=vector(self.inv.width,
2 * self.nand.height),
mirror="XY")
else:
# Add INV to the right
self.inv_inst.place(offset=vector(self.nand_inst.rx(), 0))
def route_supply_rails(self):
""" Add vdd/gnd rails to the top, (middle), and bottom. """
self.add_layout_pin_rect_center(text="gnd",
layer=self.route_layer,
offset=vector(0.5 * self.width, 0),
width=self.width)
# Second gnd of the inverter gate
if self.vertical:
self.add_layout_pin_rect_center(text="gnd",
layer=self.route_layer,
offset=vector(0.5 * self.width, self.height),
width=self.width)
if self.vertical:
# Shared between two gates
y_offset = 0.5 * self.height
else:
y_offset = self.height
self.add_layout_pin_rect_center(text="vdd",
layer=self.route_layer,
offset=vector(0.5 * self.width, y_offset),
width=self.width)
def add_wires(self): def add_wires(self):
# nand Z to inv A # nand Z to inv A
z1_pin = self.nand_inst.get_pin("Z") z1_pin = self.nand_inst.get_pin("Z")
a2_pin = self.inv_inst.get_pin("A") a2_pin = self.inv_inst.get_pin("A")
mid1_point = vector(0.5 * (z1_pin.cx()+a2_pin.cx()), z1_pin.cy()) if self.vertical:
mid2_point = vector(mid1_point, a2_pin.cy()) route_layer = "m2"
self.add_path(z1_pin.layer, self.add_via_stack_center(offset=z1_pin.center(),
[z1_pin.center(), mid1_point, mid2_point, a2_pin.center()]) from_layer=z1_pin.layer,
to_layer=route_layer)
self.add_zjog(route_layer,
z1_pin.uc(),
a2_pin.bc(),
"V")
self.add_via_stack_center(offset=a2_pin.center(),
from_layer=a2_pin.layer,
to_layer=route_layer)
else:
route_layer = self.route_layer
mid1_point = vector(z1_pin.cx(), a2_pin.cy())
self.add_path(route_layer,
[z1_pin.center(), mid1_point, a2_pin.center()])
def add_layout_pins(self): def add_layout_pins(self):
pin = self.inv_inst.get_pin("Z") pin = self.inv_inst.get_pin("Z")
self.add_layout_pin_rect_center(text="Z", self.add_layout_pin_rect_center(text="Z",

View File

@ -56,7 +56,8 @@ class pbuf(pgate.pgate):
self.inv2 = factory.create(module_type="pinv", self.inv2 = factory.create(module_type="pinv",
size=self.size, size=self.size,
height=self.height) height=self.height,
add_wells=False)
self.add_mod(self.inv2) self.add_mod(self.inv2)
def create_insts(self): def create_insts(self):

View File

@ -17,13 +17,13 @@ class pdriver(pgate.pgate):
sized for driving a load. sized for driving a load.
""" """
def __init__(self, name, neg_polarity=False, fanout=0, size_list=None, height=None): def __init__(self, name, inverting=False, fanout=0, size_list=None, height=None, add_wells=True):
debug.info(1, "creating pdriver {}".format(name)) debug.info(1, "creating pdriver {}".format(name))
self.stage_effort = 3 self.stage_effort = 3
self.height = height self.height = height
self.neg_polarity = neg_polarity self.inverting = inverting
self.size_list = size_list self.size_list = size_list
self.fanout = fanout self.fanout = fanout
@ -31,11 +31,11 @@ class pdriver(pgate.pgate):
debug.error("Either fanout or size list must be specified.", -1) debug.error("Either fanout or size list must be specified.", -1)
if self.size_list and self.fanout != 0: if self.size_list and self.fanout != 0:
debug.error("Cannot specify both size_list and fanout.", -1) debug.error("Cannot specify both size_list and fanout.", -1)
if self.size_list and self.neg_polarity: if self.size_list and self.inverting:
debug.error("Cannot specify both size_list and neg_polarity.", -1) debug.error("Cannot specify both size_list and inverting.", -1)
# Creates the netlist and layout # Creates the netlist and layout
pgate.pgate.__init__(self, name, height) pgate.pgate.__init__(self, name, height, add_wells)
def compute_sizes(self): def compute_sizes(self):
# size_list specified # size_list specified
@ -47,9 +47,9 @@ class pdriver(pgate.pgate):
int(round(self.fanout ** (1 / self.stage_effort)))) int(round(self.fanout ** (1 / self.stage_effort))))
# Increase the number of stages if we need to fix polarity # Increase the number of stages if we need to fix polarity
if self.neg_polarity and (self.num_stages % 2 == 0): if self.inverting and (self.num_stages % 2 == 0):
self.num_stages += 1 self.num_stages += 1
elif not self.neg_polarity and (self.num_stages % 2): elif not self.inverting and (self.num_stages % 2):
self.num_stages += 1 self.num_stages += 1
self.size_list = [] self.size_list = []
@ -73,9 +73,9 @@ class pdriver(pgate.pgate):
self.place_modules() self.place_modules()
self.route_wires() self.route_wires()
self.add_layout_pins() self.add_layout_pins()
self.width = self.inv_inst_list[-1].rx() self.width = self.inv_inst_list[-1].rx()
self.height = self.inv_inst_list[0].height self.height = self.inv_inst_list[0].height
self.extend_wells()
self.route_supply_rails() self.route_supply_rails()
self.add_boundary() self.add_boundary()
@ -87,10 +87,13 @@ class pdriver(pgate.pgate):
def add_modules(self): def add_modules(self):
self.inv_list = [] self.inv_list = []
add_well = self.add_wells
for size in self.size_list: for size in self.size_list:
temp_inv = factory.create(module_type="pinv", temp_inv = factory.create(module_type="pinv",
size=size, size=size,
height=self.height) height=self.height,
add_wells=add_well)
add_well=False
self.inv_list.append(temp_inv) self.inv_list.append(temp_inv)
self.add_mod(temp_inv) self.add_mod(temp_inv)

View File

@ -14,8 +14,8 @@ from tech import layer, drc
from vector import vector from vector import vector
from globals import OPTS from globals import OPTS
if(OPTS.tech_name == "s8"): 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):
@ -24,7 +24,7 @@ class pgate(design.design):
functions for parameterized gates. functions for parameterized gates.
""" """
def __init__(self, name, height=None): def __init__(self, name, height=None, add_wells=True):
""" Creates a generic cell """ """ Creates a generic cell """
design.design.__init__(self, name) design.design.__init__(self, name)
@ -33,7 +33,8 @@ class pgate(design.design):
elif not height: elif not height:
# By default, something simple # By default, something simple
self.height = 14 * self.m1_pitch self.height = 14 * self.m1_pitch
self.add_wells = add_wells
if "li" in layer: if "li" in layer:
self.route_layer = "li" self.route_layer = "li"
else: else:
@ -42,11 +43,14 @@ 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
# Assume the contact starts at the active edge contact_to_vdd_rail_space = 0.5 * self.route_layer_width + self.route_layer_space
contact_to_vdd_rail_space = 0.5 * self.m1_width + self.m1_space
# This is a poly-to-poly of a flipped cell # This is a poly-to-poly of a flipped cell
poly_to_poly_gate_space = self.poly_extend_active + self.poly_space poly_to_poly_gate_space = self.poly_extend_active + 0.5 * self.poly_space
self.top_bottom_space = max(contact_to_vdd_rail_space, self.top_bottom_space = max(contact_to_vdd_rail_space,
poly_to_poly_gate_space) poly_to_poly_gate_space)
@ -145,28 +149,31 @@ 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.well_width, width=self.width + 2 * self.well_extend_active,
height=nwell_height) height=nwell_height)
if "vtg" in layer: if "vtg" in layer:
self.add_rect(layer="vtg", self.add_rect(layer="vtg",
offset=nwell_position, offset=nwell_position,
width=self.well_width, width=self.width + 2 * self.well_extend_active,
height=nwell_height) height=nwell_height)
# Start this half a rail width below the cell # Start this half a rail width below the cell
@ -174,17 +181,20 @@ 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.well_width, width=self.width + 2 * self.well_extend_active,
height=pwell_height) height=pwell_height)
if "vtg" in layer: if "vtg" in layer:
self.add_rect(layer="vtg", self.add_rect(layer="vtg",
offset=pwell_position, offset=pwell_position,
width=self.well_width, 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. """
@ -239,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. """
@ -267,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
@ -302,10 +358,18 @@ class pgate(design.design):
def determine_width(self): def determine_width(self):
""" Determine the width based on the well contacts (assumed to be on the right side) """ """ Determine the width based on the well contacts (assumed to be on the right side) """
# It was already set or is left as default (minimum)
# Width is determined by well contact and spacing and allowing a supply via between each cell # Width is determined by well contact and spacing and allowing a supply via between each cell
self.width = max(self.nwell_contact.rx(), self.pwell_contact.rx()) + self.m1_space + 0.5 * contact.m1_via.width if self.add_wells:
self.well_width = self.width + 2 * self.nwell_enclose_active width = max(self.nwell_contact.rx(), self.pwell_contact.rx()) + self.m1_space + 0.5 * contact.m1_via.width
# Height is an input parameter, so it is not recomputed. # Height is an input parameter, so it is not recomputed.
else:
max_active_xoffset = self.find_highest_layer_coords("active").x
max_route_xoffset = self.find_highest_layer_coords(self.route_layer).x + 0.5 * self.m1_space
width = max(max_active_xoffset, max_route_xoffset)
self.width = width
@staticmethod @staticmethod
def bin_width(tx_type, target_width): def bin_width(tx_type, target_width):
@ -327,16 +391,20 @@ class pgate(design.design):
base_bins = [] base_bins = []
scaled_bins = [] scaled_bins = []
scaling_factors = [] scaling_factors = []
scaled_bins.append(bins[-1])
base_bins.append(bins[-1]) for width in bins:
scaling_factors.append(1)
for width in bins[0:-1]:
m = math.ceil(target_width / width) m = math.ceil(target_width / width)
base_bins.append(width) base_bins.append(width)
scaling_factors.append(m) scaling_factors.append(m)
scaled_bins.append(m * width) scaled_bins.append(m * width)
select = bisect_left(scaled_bins, target_width) select = -1
for i in reversed(range(0, len(scaled_bins))):
if abs(target_width - scaled_bins[i])/target_width <= 1-OPTS.accuracy_requirement:
select = i
break
if select == -1:
debug.error("failed to bin tx size {}, try reducing accuracy requirement".format(target_width), 1)
scaling_factor = scaling_factors[select] scaling_factor = scaling_factors[select]
scaled_bin = scaled_bins[select] scaled_bin = scaled_bins[select]
selected_bin = base_bins[select] selected_bin = base_bins[select]
@ -366,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

@ -19,8 +19,8 @@ import logical_effort
from sram_factory import factory from sram_factory import factory
from errors import drc_error from errors import drc_error
if(OPTS.tech_name == "s8"): 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,8 +31,11 @@ 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): def __init__(self, name, size=1, beta=parameter["beta"], height=None, add_wells=True):
debug.info(2, debug.info(2,
"creating pinv structure {0} with size of {1}".format(name, "creating pinv structure {0} with size of {1}".format(name,
@ -40,11 +43,12 @@ class pinv(pgate.pgate):
self.add_comment("size: {}".format(size)) self.add_comment("size: {}".format(size))
self.size = size self.size = size
debug.check(self.size >= 1, "Must have a size greater than or equal to 1.")
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) pgate.pgate.__init__(self, name, height, add_wells)
def create_netlist(self): def create_netlist(self):
""" Calls all functions related to the generation of the netlist """ """ Calls all functions related to the generation of the netlist """
@ -56,17 +60,18 @@ class pinv(pgate.pgate):
def create_layout(self): def create_layout(self):
""" Calls all functions related to the generation of the layout """ """ Calls all functions related to the generation of the layout """
self.place_ptx() self.place_ptx()
self.add_well_contacts() if self.add_wells:
self.add_well_contacts()
self.determine_width() self.determine_width()
self.extend_wells() self.extend_wells()
self.route_supply_rails()
self.connect_rails()
self.route_input_gate(self.pmos_inst, self.route_input_gate(self.pmos_inst,
self.nmos_inst, self.nmos_inst,
self.output_pos.y, self.output_pos.y,
"A", "A",
position="farleft") position="farleft")
self.route_outputs() self.route_outputs()
self.route_supply_rails()
self.connect_rails()
self.add_boundary() self.add_boundary()
def add_pins(self): def add_pins(self):
@ -86,7 +91,7 @@ class pinv(pgate.pgate):
self.tx_mults = 1 self.tx_mults = 1
self.nmos_width = self.nmos_size * drc("minwidth_tx") self.nmos_width = self.nmos_size * drc("minwidth_tx")
self.pmos_width = self.pmos_size * drc("minwidth_tx") self.pmos_width = self.pmos_size * drc("minwidth_tx")
if OPTS.tech_name == "s8": if OPTS.tech_name == "sky130":
(self.nmos_width, self.tx_mults) = self.bin_width("nmos", self.nmos_width) (self.nmos_width, self.tx_mults) = self.bin_width("nmos", self.nmos_width)
(self.pmos_width, self.tx_mults) = self.bin_width("pmos", self.pmos_width) (self.pmos_width, self.tx_mults) = self.bin_width("pmos", self.pmos_width)
return return
@ -131,7 +136,7 @@ class pinv(pgate.pgate):
# Determine the number of mults for each to fit width # Determine the number of mults for each to fit width
# into available space # into available space
if OPTS.tech_name != "s8": if OPTS.tech_name != "sky130":
self.nmos_width = self.nmos_size * drc("minwidth_tx") self.nmos_width = self.nmos_size * drc("minwidth_tx")
self.pmos_width = self.pmos_size * drc("minwidth_tx") self.pmos_width = self.pmos_size * drc("minwidth_tx")
nmos_required_mults = max(int(ceil(self.nmos_width / nmos_height_available)), 1) nmos_required_mults = max(int(ceil(self.nmos_width / nmos_height_available)), 1)
@ -164,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",

240
compiler/pgates/pinv_dec.py Normal file
View File

@ -0,0 +1,240 @@
# 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 contact
import pinv
import debug
from tech import drc, parameter, layer
from vector import vector
from globals import OPTS
from sram_factory import factory
if(OPTS.tech_name == "sky130"):
from tech import nmos_bins, pmos_bins
class pinv_dec(pinv.pinv):
"""
This is another version of pinv but with layout for the decoder.
Other stuff is the same (netlist, sizes, etc.)
"""
def __init__(self, name, size=1, beta=parameter["beta"], height=None, add_wells=True):
debug.info(2,
"creating pinv_dec structure {0} with size of {1}".format(name,
size))
if not height:
b = factory.create(module_type="bitcell")
self.cell_height = b.height
else:
self.cell_height = height
# Inputs to cells are on input layer
# Outputs from cells are on output layer
if OPTS.tech_name == "sky130":
self.supply_layer = "m1"
else:
self.supply_layer = "m2"
pinv.pinv.__init__(self, name, size, beta, self.cell_height, add_wells)
def determine_tx_mults(self):
"""
Determines the number of fingers needed to achieve the size within
the height constraint. This may fail if the user has a tight height.
"""
# This is always 1 tx, because we have horizontal transistors.
self.tx_mults = 1
self.nmos_width = self.nmos_size * drc("minwidth_tx")
self.pmos_width = self.pmos_size * drc("minwidth_tx")
if OPTS.tech_name == "sky130":
(self.nmos_width, self.tx_mults) = self.bin_width("nmos", self.nmos_width)
(self.pmos_width, self.tx_mults) = self.bin_width("pmos", self.pmos_width)
return
# Over-ride the route input gate to call the horizontal version.
# Other top-level netlist and layout functions are not changed.
def route_input_gate(self, pmos_inst, nmos_inst, ypos, name, position="left", directions=None):
"""
Route the input gate to the left side of the cell for access.
Position is actually ignored and is left to be compatible with the pinv.
"""
nmos_gate_pin = nmos_inst.get_pin("G")
pmos_gate_pin = pmos_inst.get_pin("G")
# Check if the gates are aligned and give an error if they aren't!
if nmos_gate_pin.ll().y != pmos_gate_pin.ll().y:
self.gds_write("unaliged_gates.gds")
debug.check(nmos_gate_pin.ll().y == pmos_gate_pin.ll().y,
"Connecting unaligned gates not supported. See unaligned_gates.gds.")
# Pick point on the left of NMOS and up to PMOS
nmos_gate_pos = nmos_gate_pin.rc()
pmos_gate_pos = pmos_gate_pin.lc()
self.add_path("poly", [nmos_gate_pos, pmos_gate_pos])
# Center is completely symmetric.
contact_width = contact.poly_contact.width
contact_offset = nmos_gate_pin.lc() \
- vector(self.poly_extend_active + 0.5 * contact_width, 0)
via = self.add_via_stack_center(from_layer="poly",
to_layer=self.route_layer,
offset=contact_offset,
directions=directions)
self.add_path("poly", [contact_offset, nmos_gate_pin.lc()])
self.add_layout_pin_rect_center(text=name,
layer=self.route_layer,
offset=contact_offset,
width=via.mod.second_layer_width,
height=via.mod.second_layer_height)
def determine_width(self):
self.width = self.pmos_inst.rx() + self.well_extend_active
def extend_wells(self):
""" Extend bottom to top for each well. """
if "pwell" in layer:
ll = (self.nmos_inst.ll() - vector(2 * [self.well_enclose_active])).scale(1, 0)
ur = self.nmos_inst.ur() + vector(2 * [self.well_enclose_active])
self.add_rect(layer="pwell",
offset=ll,
width=ur.x - ll.x,
height=self.height - ll.y + 0.5 * self.pwell_contact.height + self.well_enclose_active)
if "nwell" in layer:
ll = (self.pmos_inst.ll() - vector(2 * [self.well_enclose_active])).scale(1, 0)
ur = self.pmos_inst.ur() + vector(2 * [self.well_enclose_active])
self.add_rect(layer="nwell",
offset=ll,
width=ur.x - ll.x,
height=self.height - ll.y + 0.5 * self.nwell_contact.height + self.well_enclose_active)
def place_ptx(self):
"""
"""
# center the transistors in the y-dimension (it is rotated, so use the width)
y_offset = 0.5 * (self.height - self.nmos.width) + self.nmos.width
# offset so that the input contact is over from the left edge by poly spacing
x_offset = self.nmos.active_offset.y + contact.poly_contact.width + self.poly_space
self.nmos_pos = vector(x_offset, y_offset)
self.nmos_inst.place(self.nmos_pos,
rotate=270)
# place PMOS so it is half a poly spacing down from the top
xoffset = self.nmos_inst.rx() + 2 * self.poly_extend_active + 2 * self.well_extend_active + drc("pwell_to_nwell")
self.pmos_pos = vector(xoffset, y_offset)
self.pmos_inst.place(self.pmos_pos,
rotate=270)
# Output position will be in between the PMOS and NMOS drains
pmos_drain_pos = self.pmos_inst.get_pin("D").center()
nmos_drain_pos = self.nmos_inst.get_pin("D").center()
self.output_pos = vector(0.5 * (pmos_drain_pos.x + nmos_drain_pos.x), nmos_drain_pos.y)
if OPTS.tech_name == "sky130":
self.add_implants()
def add_implants(self):
"""
Add top-to-bottom implants for adjacency issues in s8.
"""
# Route to the bottom
ll = (self.nmos_inst.ll() - vector(2 * [self.implant_enclose_active])).scale(1, 0)
# Don't route to the top
ur = self.nmos_inst.ur() + vector(self.implant_enclose_active, 0)
self.add_rect("nimplant",
ll,
ur.x - ll.x,
ur.y - ll.y)
# Route to the bottom
ll = (self.pmos_inst.ll() - vector(2 * [self.implant_enclose_active])).scale(1, 0)
# Don't route to the top
ur = self.pmos_inst.ur() + vector(self.implant_enclose_active, 0)
self.add_rect("pimplant",
ll,
ur.x - ll.x,
ur.y - ll.y)
def route_outputs(self):
"""
Route the output (drains) together.
Optionally, routes output to edge.
"""
# Get the drain pin
nmos_drain_pin = self.nmos_inst.get_pin("D")
# Pick point at right most of NMOS and connect over to PMOS
nmos_drain_pos = nmos_drain_pin.lc()
right_side = vector(self.width, nmos_drain_pos.y)
self.add_layout_pin_segment_center("Z",
self.route_layer,
nmos_drain_pos,
right_side)
def add_well_contacts(self):
""" Add n/p well taps to the layout and connect to supplies """
source_pos = self.pmos_inst.get_pin("S").center()
contact_pos = vector(source_pos.x, self.height)
self.nwell_contact = self.add_via_center(layers=self.active_stack,
offset=contact_pos,
implant_type="n",
well_type="n")
self.add_via_stack_center(offset=contact_pos,
from_layer=self.active_stack[2],
to_layer=self.supply_layer)
source_pos = self.nmos_inst.get_pin("S").center()
contact_pos = vector(source_pos.x, self.height)
self.pwell_contact= self.add_via_center(layers=self.active_stack,
offset=contact_pos,
implant_type="p",
well_type="p")
self.add_via_stack_center(offset=contact_pos,
from_layer=self.active_stack[2],
to_layer=self.supply_layer)
def route_supply_rails(self):
pin = self.nmos_inst.get_pin("S")
source_pos = pin.center()
bottom_pos = source_pos.scale(1, 0)
top_pos = bottom_pos + vector(0, self.height)
self.add_layout_pin_segment_center("gnd",
self.supply_layer,
start=bottom_pos,
end=top_pos)
pin = self.pmos_inst.get_pin("S")
source_pos = pin.center()
bottom_pos = source_pos.scale(1, 0)
top_pos = bottom_pos + vector(0, self.height)
self.add_layout_pin_segment_center("vdd",
self.supply_layer,
start=bottom_pos,
end=top_pos)
def connect_rails(self):
""" Connect the nmos and pmos to its respective power rails """
source_pos = self.nmos_inst.get_pin("S").center()
self.add_via_stack_center(offset=source_pos,
from_layer=self.route_layer,
to_layer=self.supply_layer)
source_pos = self.pmos_inst.get_pin("S").center()
self.add_via_stack_center(offset=source_pos,
from_layer=self.route_layer,
to_layer=self.supply_layer)

View File

@ -9,7 +9,7 @@ import debug
import pgate import pgate
from vector import vector from vector import vector
from sram_factory import factory from sram_factory import factory
from tech import layer
class pinvbuf(pgate.pgate): class pinvbuf(pgate.pgate):
""" """
@ -111,33 +111,45 @@ class pinvbuf(pgate.pgate):
mirror="MX") mirror="MX")
def route_wires(self): def route_wires(self):
if "li" in layer:
route_stack = self.li_stack
else:
route_stack = self.m1_stack
# inv1 Z to inv2 A # inv1 Z to inv2 A
z1_pin = self.inv1_inst.get_pin("Z") z1_pin = self.inv1_inst.get_pin("Z")
a2_pin = self.inv2_inst.get_pin("A") a2_pin = self.inv2_inst.get_pin("A")
mid_point = vector(z1_pin.cx(), a2_pin.cy()) mid_point = vector(z1_pin.cx(), a2_pin.cy())
self.add_path("m1", [z1_pin.center(), mid_point, a2_pin.center()]) self.add_path(z1_pin.layer, [z1_pin.center(), mid_point, a2_pin.center()])
self.add_via_stack_center(from_layer=z1_pin.layer,
to_layer=a2_pin.layer,
offset=a2_pin.center())
# inv2 Z to inv3 A # inv2 Z to inv3 A
z2_pin = self.inv2_inst.get_pin("Z") z2_pin = self.inv2_inst.get_pin("Z")
a3_pin = self.inv3_inst.get_pin("A") a3_pin = self.inv3_inst.get_pin("A")
mid_point = vector(z2_pin.cx(), a3_pin.cy()) mid_point = vector(z2_pin.cx(), a3_pin.cy())
self.add_path("m1", [z2_pin.center(), mid_point, a3_pin.center()]) self.add_path(z2_pin.layer, [z2_pin.center(), mid_point, a3_pin.center()])
self.add_via_stack_center(from_layer=z2_pin.layer,
to_layer=a3_pin.layer,
offset=a3_pin.center())
# inv1 Z to inv4 A (up and over) # inv1 Z to inv4 A (up and over)
z1_pin = self.inv1_inst.get_pin("Z") z1_pin = self.inv1_inst.get_pin("Z")
a4_pin = self.inv4_inst.get_pin("A") a4_pin = self.inv4_inst.get_pin("A")
mid_point = vector(z1_pin.cx(), a4_pin.cy()) mid_point = vector(z1_pin.cx(), a4_pin.cy())
self.add_wire(self.m1_stack, self.add_wire(route_stack,
[z1_pin.center(), mid_point, a4_pin.center()]) [z1_pin.center(), mid_point, a4_pin.center()])
self.add_via_center(layers=self.m1_stack, self.add_via_stack_center(from_layer=z1_pin.layer,
offset=z1_pin.center()) to_layer=route_stack[2],
offset=z1_pin.center())
def add_layout_pins(self): def add_layout_pins(self):
# Continous vdd rail along with label. # Continous vdd rail along with label.
vdd_pin = self.inv1_inst.get_pin("vdd") vdd_pin = self.inv1_inst.get_pin("vdd")
self.add_layout_pin(text="vdd", self.add_layout_pin(text="vdd",
layer="m1", layer=vdd_pin.layer,
offset=vdd_pin.ll().scale(0, 1), offset=vdd_pin.ll().scale(0, 1),
width=self.width, width=self.width,
height=vdd_pin.height()) height=vdd_pin.height())
@ -145,7 +157,7 @@ class pinvbuf(pgate.pgate):
# Continous vdd rail along with label. # Continous vdd rail along with label.
gnd_pin = self.inv4_inst.get_pin("gnd") gnd_pin = self.inv4_inst.get_pin("gnd")
self.add_layout_pin(text="gnd", self.add_layout_pin(text="gnd",
layer="m1", layer=gnd_pin.layer,
offset=gnd_pin.ll().scale(0, 1), offset=gnd_pin.ll().scale(0, 1),
width=self.width, width=self.width,
height=gnd_pin.height()) height=gnd_pin.height())
@ -153,31 +165,25 @@ class pinvbuf(pgate.pgate):
# Continous gnd rail along with label. # Continous gnd rail along with label.
gnd_pin = self.inv1_inst.get_pin("gnd") gnd_pin = self.inv1_inst.get_pin("gnd")
self.add_layout_pin(text="gnd", self.add_layout_pin(text="gnd",
layer="m1", layer=gnd_pin.layer,
offset=gnd_pin.ll().scale(0, 1), offset=gnd_pin.ll().scale(0, 1),
width=self.width, width=self.width,
height=vdd_pin.height()) height=vdd_pin.height())
z_pin = self.inv4_inst.get_pin("Z") z_pin = self.inv4_inst.get_pin("Z")
self.add_layout_pin_rect_center(text="Z", self.add_layout_pin_rect_center(text="Z",
layer="m2", layer=z_pin.layer,
offset=z_pin.center()) offset=z_pin.center())
self.add_via_center(layers=self.m1_stack,
offset=z_pin.center())
zb_pin = self.inv3_inst.get_pin("Z") zb_pin = self.inv3_inst.get_pin("Z")
self.add_layout_pin_rect_center(text="Zb", self.add_layout_pin_rect_center(text="Zb",
layer="m2", layer=zb_pin.layer,
offset=zb_pin.center()) offset=zb_pin.center())
self.add_via_center(layers=self.m1_stack,
offset=zb_pin.center())
a_pin = self.inv1_inst.get_pin("A") a_pin = self.inv1_inst.get_pin("A")
self.add_layout_pin_rect_center(text="A", self.add_layout_pin_rect_center(text="A",
layer="m2", layer=a_pin.layer,
offset=a_pin.center()) offset=a_pin.center())
self.add_via_center(layers=self.m1_stack,
offset=a_pin.center())
def determine_clk_buf_stage_efforts(self, external_cout, inp_is_rise=False): def determine_clk_buf_stage_efforts(self, external_cout, inp_is_rise=False):
"""Get the stage efforts of the clk -> clk_buf path""" """Get the stage efforts of the clk -> clk_buf path"""

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 contact
import pgate import pgate
import debug import debug
from tech import drc, parameter, spice from tech import drc, parameter, spice
@ -13,6 +12,7 @@ from globals import OPTS
from vector import vector from vector import vector
import logical_effort import logical_effort
from sram_factory import factory from sram_factory import factory
import contact
class pnand2(pgate.pgate): class pnand2(pgate.pgate):
@ -20,7 +20,7 @@ class pnand2(pgate.pgate):
This module generates gds of a parametrically sized 2-input nand. This module generates gds of a parametrically sized 2-input nand.
This model use ptx to generate a 2-input nand within a cetrain height. This model use ptx to generate a 2-input nand within a cetrain height.
""" """
def __init__(self, name, size=1, height=None): def __init__(self, name, size=1, height=None, add_wells=True):
""" Creates a cell for a simple 2 input nand """ """ Creates a cell for a simple 2 input nand """
debug.info(2, debug.info(2,
@ -38,12 +38,12 @@ class pnand2(pgate.pgate):
debug.check(size == 1, "Size 1 pnand2 is only supported now.") debug.check(size == 1, "Size 1 pnand2 is only supported now.")
self.tx_mults = 1 self.tx_mults = 1
if OPTS.tech_name == "s8": if OPTS.tech_name == "sky130":
(self.nmos_width, self.tx_mults) = self.bin_width("nmos", self.nmos_width) (self.nmos_width, self.tx_mults) = self.bin_width("nmos", self.nmos_width)
(self.pmos_width, self.tx_mults) = self.bin_width("pmos", self.pmos_width) (self.pmos_width, self.tx_mults) = self.bin_width("pmos", self.pmos_width)
# Creates the netlist and layout # Creates the netlist and layout
pgate.pgate.__init__(self, name, height) pgate.pgate.__init__(self, name, height, add_wells)
def create_netlist(self): def create_netlist(self):
self.add_pins() self.add_pins()
@ -55,13 +55,14 @@ class pnand2(pgate.pgate):
self.setup_layout_constants() self.setup_layout_constants()
self.place_ptx() self.place_ptx()
self.add_well_contacts() if self.add_wells:
self.add_well_contacts()
self.route_output()
self.determine_width() self.determine_width()
self.route_supply_rails() self.route_supply_rails()
self.connect_rails() self.connect_rails()
self.extend_wells() self.extend_wells()
self.route_inputs() self.route_inputs()
self.route_output()
self.add_boundary() self.add_boundary()
def add_pins(self): def add_pins(self):
@ -175,33 +176,52 @@ class pnand2(pgate.pgate):
def route_inputs(self): def route_inputs(self):
""" Route the A and B inputs """ """ Route the A and B inputs """
# Top of NMOS drain # Top of NMOS drain
nmos_pin = self.nmos2_inst.get_pin("D") bottom_pin = self.nmos1_inst.get_pin("D")
bottom_pin_offset = nmos_pin.uy() # active contact metal to poly contact metal spacing
self.inputA_yoffset = bottom_pin_offset + self.m1_pitch active_contact_to_poly_contact = bottom_pin.uy() + self.route_layer_space + 0.5 * contact.poly_contact.second_layer_height
# active diffusion to poly contact spacing
self.inputB_yoffset = self.inputA_yoffset + self.m3_pitch # 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_to_poly_contact = active_top + self.poly_to_active + 0.5 * contact.poly_contact.first_layer_height
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,
active_to_poly_contact,
active_to_poly_contact2)
apin = self.route_input_gate(self.pmos1_inst,
self.nmos1_inst,
self.inputA_yoffset,
"A",
position="center")
self.inputB_yoffset = self.inputA_yoffset + 2 * self.m3_pitch
# # 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_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_contact2 = active_bottom - self.poly_contact_to_gate - 0.5 * self.route_layer_width
# self.inputB_yoffset = min(active_contact_to_poly_contact,
# active_to_poly_contact,
# 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"))
self.route_input_gate(self.pmos1_inst,
self.nmos1_inst,
self.inputA_yoffset,
"A",
position="center")
def route_output(self): def route_output(self):
""" Route the Z output """ """ Route the Z output """
# One routing track layer below the PMOS contacts # One routing track layer below the PMOS contacts
route_layer_offset = 0.5 * self.route_layer_width + self.route_layer_space route_layer_offset = 0.5 * contact.poly_contact.second_layer_height + self.route_layer_space
output_yoffset = self.pmos1_inst.get_pin("D").by() - route_layer_offset self.output_yoffset = self.pmos1_inst.get_pin("D").by() - route_layer_offset
# PMOS1 drain # PMOS1 drain
@ -213,7 +233,7 @@ class pnand2(pgate.pgate):
# Output pin # Output pin
out_offset = vector(nmos_pin.cx() + self.route_layer_pitch, out_offset = vector(nmos_pin.cx() + self.route_layer_pitch,
output_yoffset) self.output_yoffset)
# This routes on M2 # This routes on M2
# # Midpoints of the L routes go horizontal first then vertical # # Midpoints of the L routes go horizontal first then vertical

View File

@ -12,6 +12,7 @@ from vector import vector
import logical_effort import logical_effort
from sram_factory import factory from sram_factory import factory
from globals import OPTS from globals import OPTS
import contact
class pnand3(pgate.pgate): class pnand3(pgate.pgate):
@ -19,7 +20,7 @@ class pnand3(pgate.pgate):
This module generates gds of a parametrically sized 2-input nand. This module generates gds of a parametrically sized 2-input nand.
This model use ptx to generate a 2-input nand within a cetrain height. This model use ptx to generate a 2-input nand within a cetrain height.
""" """
def __init__(self, name, size=1, height=None): def __init__(self, name, size=1, height=None, add_wells=True):
""" Creates a cell for a simple 3 input nand """ """ Creates a cell for a simple 3 input nand """
debug.info(2, debug.info(2,
@ -40,12 +41,12 @@ class pnand3(pgate.pgate):
"Size 1 pnand3 is only supported now.") "Size 1 pnand3 is only supported now.")
self.tx_mults = 1 self.tx_mults = 1
if OPTS.tech_name == "s8": if OPTS.tech_name == "sky130":
(self.nmos_width, self.tx_mults) = self.bin_width("nmos", self.nmos_width) (self.nmos_width, self.tx_mults) = self.bin_width("nmos", self.nmos_width)
(self.pmos_width, self.tx_mults) = self.bin_width("pmos", self.pmos_width) (self.pmos_width, self.tx_mults) = self.bin_width("pmos", self.pmos_width)
# Creates the netlist and layout # Creates the netlist and layout
pgate.pgate.__init__(self, name, height) pgate.pgate.__init__(self, name, height, add_wells)
def add_pins(self): def add_pins(self):
""" Adds pins for spice netlist """ """ Adds pins for spice netlist """
@ -63,13 +64,14 @@ class pnand3(pgate.pgate):
self.setup_layout_constants() self.setup_layout_constants()
self.place_ptx() self.place_ptx()
self.add_well_contacts() if self.add_wells:
self.add_well_contacts()
self.route_inputs()
self.route_output()
self.determine_width() self.determine_width()
self.route_supply_rails() self.route_supply_rails()
self.connect_rails() self.connect_rails()
self.extend_wells() self.extend_wells()
self.route_inputs()
self.route_output()
self.add_boundary() self.add_boundary()
def add_ptx(self): def add_ptx(self):
@ -208,30 +210,45 @@ class pnand3(pgate.pgate):
def route_inputs(self): def route_inputs(self):
""" Route the A and B and C inputs """ """ Route the A and B and C inputs """
# We can use this pitch because the contacts and overlap won't be adjacent
non_contact_pitch = 0.5 * self.m1_width + self.m1_space + 0.5 * contact.poly_contact.second_layer_height
pmos_drain_bottom = self.pmos1_inst.get_pin("D").by() pmos_drain_bottom = self.pmos1_inst.get_pin("D").by()
self.output_yoffset = pmos_drain_bottom - 0.5 * self.route_layer_width - self.route_layer_space self.output_yoffset = pmos_drain_bottom - 0.5 * self.route_layer_width - self.route_layer_space
self.inputA_yoffset = self.output_yoffset - 0.5 * self.route_layer_width - self.route_layer_space bottom_pin = self.nmos1_inst.get_pin("D")
self.route_input_gate(self.pmos1_inst, # active contact metal to poly contact metal spacing
self.nmos1_inst, active_contact_to_poly_contact = bottom_pin.uy() + self.m1_space + 0.5 * contact.poly_contact.second_layer_height
self.inputA_yoffset, # active diffusion to poly contact spacing
"A", # doesn't use nmos uy because that is calculated using offset + poly height
position="left") 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_contact2 = active_top + self.poly_contact_to_gate + 0.5 * self.route_layer_width
self.inputA_yoffset = max(active_contact_to_poly_contact,
active_to_poly_contact,
active_to_poly_contact2)
apin = self.route_input_gate(self.pmos1_inst,
self.nmos1_inst,
self.inputA_yoffset,
"A",
position="left")
# Put B right on the well line self.inputB_yoffset = self.inputA_yoffset + self.m3_pitch
self.inputB_yoffset = self.inputA_yoffset - self.m1_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 - self.m1_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

@ -19,7 +19,7 @@ class pnor2(pgate.pgate):
This module generates gds of a parametrically sized 2-input nor. This module generates gds of a parametrically sized 2-input nor.
This model use ptx to generate a 2-input nor within a cetrain height. This model use ptx to generate a 2-input nor within a cetrain height.
""" """
def __init__(self, name, size=1, height=None): def __init__(self, name, size=1, height=None, add_wells=True):
""" Creates a cell for a simple 2 input nor """ """ Creates a cell for a simple 2 input nor """
debug.info(2, debug.info(2,
@ -37,12 +37,12 @@ class pnor2(pgate.pgate):
debug.check(size==1, "Size 1 pnor2 is only supported now.") debug.check(size==1, "Size 1 pnor2 is only supported now.")
self.tx_mults = 1 self.tx_mults = 1
if OPTS.tech_name == "s8": if OPTS.tech_name == "sky130":
(self.nmos_width, self.tx_mults) = self.bin_width("nmos", self.nmos_width) (self.nmos_width, self.tx_mults) = self.bin_width("nmos", self.nmos_width)
(self.pmos_width, self.tx_mults) = self.bin_width("pmos", self.pmos_width) (self.pmos_width, self.tx_mults) = self.bin_width("pmos", self.pmos_width)
# Creates the netlist and layout # Creates the netlist and layout
pgate.pgate.__init__(self, name, height) pgate.pgate.__init__(self, name, height, add_wells)
def create_netlist(self): def create_netlist(self):
self.add_pins() self.add_pins()
@ -54,13 +54,14 @@ class pnor2(pgate.pgate):
self.setup_layout_constants() self.setup_layout_constants()
self.place_ptx() self.place_ptx()
self.add_well_contacts() if self.add_wells:
self.add_well_contacts()
self.route_inputs()
self.route_output()
self.determine_width() self.determine_width()
self.route_supply_rails() self.route_supply_rails()
self.connect_rails() self.connect_rails()
self.extend_wells() self.extend_wells()
self.route_inputs()
self.route_output()
self.add_boundary() self.add_boundary()
def add_pins(self): def add_pins(self):
@ -194,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

@ -13,7 +13,7 @@ from tech import parameter
from vector import vector from vector import vector
from globals import OPTS from globals import OPTS
from sram_factory import factory from sram_factory import factory
from tech import drc from tech import drc, layer
class precharge(design.design): class precharge(design.design):
@ -80,7 +80,7 @@ class precharge(design.design):
""" """
Initializes the upper and lower pmos Initializes the upper and lower pmos
""" """
if(OPTS.tech_name == "s8"): if(OPTS.tech_name == "sky130"):
(self.ptx_width, self.ptx_mults) = pgate.bin_width("pmos", self.ptx_width) (self.ptx_width, self.ptx_mults) = pgate.bin_width("pmos", self.ptx_width)
self.pmos = factory.create(module_type="ptx", self.pmos = factory.create(module_type="ptx",
width=self.ptx_width, width=self.ptx_width,
@ -105,21 +105,16 @@ class precharge(design.design):
# center of vdd rail # center of vdd rail
pmos_vdd_pos = vector(pmos_pin.cx(), vdd_position.y) pmos_vdd_pos = vector(pmos_pin.cx(), vdd_position.y)
self.add_path("m1", [pmos_pin.uc(), pmos_vdd_pos]) self.add_path(self.en_layer, [pmos_pin.center(), pmos_vdd_pos])
# if enable is not on M1, the supply can be
if self.en_layer != "m1":
self.add_via_center(layers=self.m1_stack,
offset=pmos_vdd_pos)
self.add_power_pin("vdd", self.add_power_pin("vdd",
self.well_contact_pos, self.well_contact_pos,
directions=("V", "V")) directions=("V", "V"))
# Hack for li layers self.add_via_stack_center(from_layer=pmos_pin.layer,
if hasattr(self, "li_stack"): to_layer=self.en_layer,
self.add_via_center(layers=self.li_stack, offset=pmos_pin.center(),
offset=self.well_contact_pos) directions=("V", "V"))
def create_ptx(self): def create_ptx(self):
""" """
@ -148,13 +143,9 @@ class precharge(design.design):
# Compute the other pmos2 location, # Compute the other pmos2 location,
# but determining offset to overlap the source and drain pins # but determining offset to overlap the source and drain pins
overlap_offset = self.pmos.get_pin("D").ll() - self.pmos.get_pin("S").ll() overlap_offset = self.pmos.get_pin("D").ll() - self.pmos.get_pin("S").ll()
# This is how much the contact is placed inside the ptx active
contact_xdiff = self.pmos.get_pin("S").lx()
# adds the lower pmos to layout # adds the lower pmos to layout
bl_xoffset = self.bitcell_bl_pin.lx() self.lower_pmos_position = vector(self.well_enclose_active + 0.5 * self.m1_width,
self.lower_pmos_position = vector(max(bl_xoffset - contact_xdiff,
self.nwell_enclose_active),
self.initial_yoffset) self.initial_yoffset)
self.lower_pmos_inst.place(self.lower_pmos_position) self.lower_pmos_inst.place(self.lower_pmos_position)
@ -196,19 +187,17 @@ class precharge(design.design):
""" """
# adds the en contact to connect the gates to the en rail # adds the en contact to connect the gates to the en rail
# midway in the 4 M2 tracks pin_offset = self.lower_pmos_inst.get_pin("G").lr()
offset = self.lower_pmos_inst.get_pin("G").ul() \ # This is an extra space down for some techs with contact to active spacing
+ vector(0, 0.5 * self.m2_pitch) contact_space = max(self.poly_space,
self.add_via_center(layers=self.poly_stack, self.poly_contact_to_gate) + 0.5 * contact.poly_contact.first_layer_height
offset=offset) offset = pin_offset - vector(0, contact_space)
if self.en_layer == "m2": self.add_via_stack_center(from_layer="poly",
self.add_via_center(layers=self.m1_stack, to_layer=self.en_layer,
offset=offset) offset=offset)
if hasattr(self, "li_stack"): self.add_path("poly",
self.add_via_center(layers=self.li_stack, [self.lower_pmos_inst.get_pin("G").bc(), offset])
offset=offset) # adds the en rail
# adds the en rail on metal1
self.add_layout_pin_segment_center(text="en_bar", self.add_layout_pin_segment_center(text="en_bar",
layer=self.en_layer, layer=self.en_layer,
start=offset.scale(0, 1), start=offset.scale(0, 1),
@ -221,17 +210,17 @@ class precharge(design.design):
# adds the contact from active to metal1 # adds the contact from active to metal1
offset_height = self.upper_pmos1_inst.uy() + \ offset_height = self.upper_pmos1_inst.uy() + \
0.5 * contact.active_contact.height + \ contact.active_contact.height + \
self.nwell_extend_active self.nwell_extend_active
self.well_contact_pos = self.upper_pmos1_inst.get_pin("D").center().scale(1, 0) + \ self.well_contact_pos = self.upper_pmos1_inst.get_pin("D").center().scale(1, 0) + \
vector(0, offset_height) vector(0, offset_height)
self.add_via_center(layers=self.active_stack, self.well_contact = self.add_via_center(layers=self.active_stack,
offset=self.well_contact_pos, offset=self.well_contact_pos,
implant_type="n", implant_type="n",
well_type="n") well_type="n")
if hasattr(self, "li_stack"): self.add_via_stack_center(from_layer=self.active_stack[2],
self.add_via_center(layers=self.li_stack, to_layer=self.bitline_layer,
offset=self.well_contact_pos) offset=self.well_contact_pos)
self.height = self.well_contact_pos.y + contact.active_contact.height + self.m1_space self.height = self.well_contact_pos.y + contact.active_contact.height + self.m1_space
@ -245,11 +234,10 @@ class precharge(design.design):
""" """
Adds both bit-line and bit-line-bar to the module Adds both bit-line and bit-line-bar to the module
""" """
layer_width = drc("minwidth_" + self.bitline_layer) layer_pitch = getattr(self, "{}_pitch".format(self.bitline_layer))
layer_space = drc("{0}_to_{0}".format(self.bitline_layer))
# adds the BL # adds the BL
self.bl_xoffset = layer_space + 0.5 * layer_width self.bl_xoffset = layer_pitch
top_pos = vector(self.bl_xoffset, self.height) top_pos = vector(self.bl_xoffset, self.height)
pin_pos = vector(self.bl_xoffset, 0) pin_pos = vector(self.bl_xoffset, 0)
self.add_path(self.bitline_layer, [top_pos, pin_pos]) self.add_path(self.bitline_layer, [top_pos, pin_pos])
@ -259,7 +247,7 @@ class precharge(design.design):
end=top_pos) end=top_pos)
# adds the BR # adds the BR
self.br_xoffset = self.width - layer_space - 0.5 * layer_width self.br_xoffset = self.width - layer_pitch
top_pos = vector(self.br_xoffset, self.height) top_pos = vector(self.br_xoffset, self.height)
pin_pos = vector(self.br_xoffset, 0) pin_pos = vector(self.br_xoffset, 0)
self.add_path(self.bitline_layer, [top_pos, pin_pos]) self.add_path(self.bitline_layer, [top_pos, pin_pos])
@ -288,31 +276,19 @@ class precharge(design.design):
Adds contacts/via from metal1 to metal2 for bit-lines Adds contacts/via from metal1 to metal2 for bit-lines
""" """
# No contacts needed if M1
if self.bitline_layer == "m1":
return
# BL # BL
lower_pin = self.lower_pmos_inst.get_pin("S") for lower_pin in [self.lower_pmos_inst.get_pin("S"), self.lower_pmos_inst.get_pin("D")]:
self.lower_via = self.add_via_center(layers=self.m1_stack, self.add_via_stack_center(from_layer=lower_pin.layer,
offset=lower_pin.center(), to_layer=self.bitline_layer,
directions=("V", "V")) offset=lower_pin.center(),
directions=("V", "V"))
lower_pin = self.lower_pmos_inst.get_pin("D")
self.lower_via = self.add_via_center(layers=self.m1_stack,
offset=lower_pin.center(),
directions=("V", "V"))
# BR # BR
upper_pin = self.upper_pmos1_inst.get_pin("S") for upper_pin in [self.upper_pmos1_inst.get_pin("S"), self.upper_pmos2_inst.get_pin("D")]:
self.upper_via2 = self.add_via_center(layers=self.m1_stack, self.add_via_stack_center(from_layer=upper_pin.layer,
offset=upper_pin.center(), to_layer=self.bitline_layer,
directions=("V", "V")) offset=upper_pin.center(),
directions=("V", "V"))
upper_pin = self.upper_pmos2_inst.get_pin("D")
self.upper_via2 = self.add_via_center(layers=self.m1_stack,
offset=upper_pin.center(),
directions=("V", "V"))
def connect_pmos(self, pmos_pin, bit_xoffset): def connect_pmos(self, pmos_pin, bit_xoffset):
""" """

View File

@ -16,6 +16,7 @@ import os
from globals import OPTS from globals import OPTS
from pgate import pgate from pgate import pgate
class ptx(design.design): class ptx(design.design):
""" """
This module generates gds and spice of a parametrically NMOS or This module generates gds and spice of a parametrically NMOS or
@ -24,6 +25,10 @@ class ptx(design.design):
given width. Total width is therefore mults*width. Options allow given width. Total width is therefore mults*width. Options allow
you to connect the fingered gates and active for parallel devices. you to connect the fingered gates and active for parallel devices.
The add_*_contact option tells which layer to bring source/drain up to. The add_*_contact option tells which layer to bring source/drain up to.
ll, ur, width and height refer to the active area.
Wells and poly may extend beyond this.
""" """
def __init__(self, def __init__(self,
name="", name="",
@ -126,13 +131,13 @@ class ptx(design.design):
# be decided in the layout later. # be decided in the layout later.
area_sd = 2.5 * self.poly_width * self.tx_width area_sd = 2.5 * self.poly_width * self.tx_width
perimeter_sd = 2 * self.poly_width + 2 * self.tx_width perimeter_sd = 2 * self.poly_width + 2 * self.tx_width
if OPTS.tech_name == "s8": if OPTS.tech_name == "sky130" and OPTS.lvs_exe[0] == "calibre":
# s8 technology is in microns # sky130 simulation cannot use the mult parameter in simulation
(self.tx_width, self.mults) = pgate.bin_width(self.tx_type, self.tx_width) (self.tx_width, self.mults) = pgate.bin_width(self.tx_type, self.tx_width)
main_str = "M{{0}} {{1}} {0} m={1} w={2} l={3} ".format(spice[self.tx_type], main_str = "M{{0}} {{1}} {0} m={1} w={2} l={3} ".format(spice[self.tx_type],
self.mults, self.mults,
self.tx_width, self.tx_width,
drc("minwidth_poly")) drc("minwidth_poly"))
# Perimeters are in microns # Perimeters are in microns
# Area is in u since it is microns square # Area is in u since it is microns square
area_str = "pd={0:.2f} ps={0:.2f} as={1:.2f}u ad={1:.2f}u".format(perimeter_sd, area_str = "pd={0:.2f} ps={0:.2f} as={1:.2f}u ad={1:.2f}u".format(perimeter_sd,
@ -147,13 +152,17 @@ class ptx(design.design):
self.spice_device = main_str + area_str self.spice_device = main_str + area_str
self.spice.append("\n* ptx " + self.spice_device) self.spice.append("\n* ptx " + self.spice_device)
# LVS lib is always in SI units if OPTS.tech_name == "sky130" and OPTS.lvs_exe[0] == "calibre":
if os.path.exists(OPTS.openram_tech + "lvs_lib"): # sky130 requires mult parameter too
self.lvs_device = "M{{0}} {{1}} {0} m={1} w={2} l={3} mult={1}".format(spice[self.tx_type],
self.mults,
self.tx_width,
drc("minwidth_poly"))
else:
self.lvs_device = "M{{0}} {{1}} {0} m={1} w={2}u l={3}u ".format(spice[self.tx_type], self.lvs_device = "M{{0}} {{1}} {0} m={1} w={2}u l={3}u ".format(spice[self.tx_type],
self.mults, self.mults,
self.tx_width, self.tx_width,
drc("minwidth_poly")) drc("minwidth_poly"))
def setup_layout_constants(self): def setup_layout_constants(self):
""" """
@ -187,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)
@ -197,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
@ -205,38 +214,23 @@ class ptx(design.design):
# Poly height must include poly extension over active # Poly height must include poly extension over active
self.poly_height = self.tx_width + 2 * self.poly_extend_active self.poly_height = self.tx_width + 2 * self.poly_extend_active
# The active offset is due to the well extension self.active_offset = vector([self.well_enclose_active] * 2)
if "pwell" in layer:
pwell_enclose_active = drc("pwell_enclose_active")
else:
pwell_enclose_active = 0
if "nwell" in layer:
nwell_enclose_active = drc("nwell_enclose_active")
else:
nwell_enclose_active = 0
# Use the max of either so that the poly gates will align properly
well_enclose_active = max(pwell_enclose_active,
nwell_enclose_active)
self.active_offset = vector([well_enclose_active] * 2)
# Well enclosure of active, ensure minwidth as well # Well enclosure of active, ensure minwidth as well
well_name = "{}well".format(self.well_type) well_name = "{}well".format(self.well_type)
if well_name in layer: if well_name in layer:
well_width_rule = drc("minwidth_" + well_name) well_width_rule = drc("minwidth_" + well_name)
well_enclose_active = drc(well_name + "_enclose_active") self.well_width = max(self.active_width + 2 * self.well_enclose_active,
self.well_width = max(self.active_width + 2 * well_enclose_active,
well_width_rule) well_width_rule)
self.well_height = max(self.active_height + 2 * well_enclose_active, self.well_height = max(self.active_height + 2 * self.well_enclose_active,
well_width_rule) well_width_rule)
# We are going to shift the 0,0, so include that in the width and height
self.height = self.well_height - self.active_offset.y
self.width = self.well_width - self.active_offset.x
else: else:
# The well is not included in the height and width
self.height = self.poly_height
self.width = self.active_width
self.well_height = self.height self.well_height = self.height
self.well_width = self.width self.well_width = self.width
# We are going to shift the 0,0, so include that in the width and height
self.height = self.active_height
self.width = self.active_width
# This is the center of the first active contact offset (centered vertically) # This is the center of the first active contact offset (centered vertically)
self.contact_offset = self.active_offset + vector(0.5 * self.active_contact.width, self.contact_offset = self.active_offset + vector(0.5 * self.active_contact.width,
@ -327,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 = []
@ -360,18 +354,18 @@ class ptx(design.design):
""" """
Adding the diffusion (active region = diffusion region) Adding the diffusion (active region = diffusion region)
""" """
self.add_rect(layer="active", self.active = self.add_rect(layer="active",
offset=self.active_offset, offset=self.active_offset,
width=self.active_width, width=self.active_width,
height=self.active_height) height=self.active_height)
# If the implant must enclose the active, shift offset # If the implant must enclose the active, shift offset
# and increase width/height # and increase width/height
enclose_width = self.implant_enclose_active enclose_width = self.implant_enclose_active
enclose_offset = [enclose_width] * 2 enclose_offset = [enclose_width] * 2
self.add_rect(layer="{}implant".format(self.implant_type), self.implant = self.add_rect(layer="{}implant".format(self.implant_type),
offset=self.active_offset - enclose_offset, offset=self.active_offset - enclose_offset,
width=self.active_width + 2 * enclose_width, width=self.active_width + 2 * enclose_width,
height=self.active_height + 2 * enclose_width) height=self.active_height + 2 * enclose_width)
def add_well_implant(self): def add_well_implant(self):
""" """
@ -386,10 +380,12 @@ class ptx(design.design):
well_ll = center_pos - vector(0.5 * self.well_width, well_ll = center_pos - vector(0.5 * self.well_width,
0.5 * self.well_height) 0.5 * self.well_height)
if well_name in layer: if well_name in layer:
self.add_rect(layer=well_name, well = self.add_rect(layer=well_name,
offset=well_ll, offset=well_ll,
width=self.well_width, width=self.well_width,
height=self.well_height) height=self.well_height)
setattr(self, well_name, well)
if "vtg" in layer: if "vtg" in layer:
self.add_rect(layer="vtg", self.add_rect(layer="vtg",
offset=well_ll, offset=well_ll,

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

@ -11,6 +11,7 @@ from tech import drc, layer
from vector import vector from vector import vector
from sram_factory import factory from sram_factory import factory
import logical_effort import logical_effort
from globals import OPTS
class single_level_column_mux(pgate.pgate): class single_level_column_mux(pgate.pgate):
@ -44,13 +45,22 @@ class single_level_column_mux(pgate.pgate):
def create_layout(self): def create_layout(self):
self.pin_height = 2 * self.m2_width # If li exists, use li and m1 for the mux, otherwise use m1 and m2
if "li" in layer:
self.col_mux_stack = self.li_stack
else:
self.col_mux_stack = self.m1_stack
self.pin_layer = self.bitcell.get_pin(self.bitcell_bl).layer
self.pin_pitch = getattr(self, "{}_pitch".format(self.pin_layer))
self.pin_width = getattr(self, "{}_width".format(self.pin_layer))
self.pin_height = 2 * self.pin_width
self.width = self.bitcell.width self.width = self.bitcell.width
self.height = self.nmos_upper.uy() + self.pin_height self.height = self.nmos_upper.uy() + self.pin_height
self.connect_poly() self.connect_poly()
self.add_bitline_pins() self.add_bitline_pins()
self.connect_bitlines() self.connect_bitlines()
self.add_wells() self.add_pn_wells()
def add_modules(self): def add_modules(self):
self.bitcell = factory.create(module_type="bitcell") self.bitcell = factory.create(module_type="bitcell")
@ -58,9 +68,7 @@ class single_level_column_mux(pgate.pgate):
# Adds nmos_lower,nmos_upper to the module # Adds nmos_lower,nmos_upper to the module
self.ptx_width = self.tx_size * drc("minwidth_tx") self.ptx_width = self.tx_size * drc("minwidth_tx")
self.nmos = factory.create(module_type="ptx", self.nmos = factory.create(module_type="ptx",
width=self.ptx_width, width=self.ptx_width)
add_source_contact=False,
add_drain_contact=False)
self.add_mod(self.nmos) self.add_mod(self.nmos)
def add_pins(self): def add_pins(self):
@ -69,29 +77,26 @@ class single_level_column_mux(pgate.pgate):
def add_bitline_pins(self): def add_bitline_pins(self):
""" Add the top and bottom pins to this cell """ """ Add the top and bottom pins to this cell """
bl_pin=self.bitcell.get_pin(self.bitcell_bl) bl_pos = vector(self.pin_pitch, 0)
br_pin=self.bitcell.get_pin(self.bitcell_br) br_pos = vector(self.width - self.pin_pitch, 0)
bl_pos = vector(bl_pin.lx(), 0)
br_pos = vector(br_pin.lx(), 0)
# bl and br # bl and br
self.add_layout_pin(text="bl", self.add_layout_pin(text="bl",
layer=bl_pin.layer, layer=self.pin_layer,
offset=bl_pos + vector(0, self.height - self.pin_height), offset=bl_pos + vector(0, self.height - self.pin_height),
height=self.pin_height) height=self.pin_height)
self.add_layout_pin(text="br", self.add_layout_pin(text="br",
layer=br_pin.layer, layer=self.pin_layer,
offset=br_pos + vector(0, self.height - self.pin_height), offset=br_pos + vector(0, self.height - self.pin_height),
height=self.pin_height) height=self.pin_height)
# bl_out and br_out # bl_out and br_out
self.add_layout_pin(text="bl_out", self.add_layout_pin(text="bl_out",
layer=bl_pin.layer, layer=self.pin_layer,
offset=bl_pos, offset=bl_pos,
height=self.pin_height) height=self.pin_height)
self.add_layout_pin(text="br_out", self.add_layout_pin(text="br_out",
layer=br_pin.layer, layer=self.pin_layer,
offset=br_pos, offset=br_pos,
height=self.pin_height) height=self.pin_height)
@ -99,7 +104,7 @@ class single_level_column_mux(pgate.pgate):
""" Create the two pass gate NMOS transistors to switch the bitlines""" """ Create the two pass gate NMOS transistors to switch the bitlines"""
# Space it in the center # Space it in the center
nmos_lower_position = self.nmos.active_offset.scale(0,1) \ nmos_lower_position = self.nmos.active_offset.scale(0, 1) \
+ vector(0.5 * self.bitcell.width- 0.5 * self.nmos.active_width, 0) + vector(0.5 * self.bitcell.width- 0.5 * self.nmos.active_width, 0)
self.nmos_lower = self.add_inst(name="mux_tx1", self.nmos_lower = self.add_inst(name="mux_tx1",
mod=self.nmos, mod=self.nmos,
@ -108,18 +113,21 @@ class single_level_column_mux(pgate.pgate):
# This aligns it directly above the other tx with gates abutting # This aligns it directly above the other tx with gates abutting
nmos_upper_position = nmos_lower_position \ nmos_upper_position = nmos_lower_position \
+ vector(0, self.nmos.active_height + max(self.active_space,self.poly_space)) + vector(0, self.nmos.active_height + max(self.active_space, self.poly_space))
self.nmos_upper = self.add_inst(name="mux_tx2", self.nmos_upper = self.add_inst(name="mux_tx2",
mod=self.nmos, mod=self.nmos,
offset=nmos_upper_position) offset=nmos_upper_position)
self.connect_inst(["br", "sel", "br_out", "gnd"]) self.connect_inst(["br", "sel", "br_out", "gnd"])
if OPTS.tech_name == "sky130":
self.add_implants()
def connect_poly(self): def connect_poly(self):
""" Connect the poly gate of the two pass transistors """ """ Connect the poly gate of the two pass transistors """
# offset is the top of the lower nmos' diffusion # offset is the top of the lower nmos' diffusion
# height is the distance between the nmos' diffusions, which depends on max(self.active_space,self.poly_space) # height is the distance between the nmos' diffusions, which depends on max(self.active_space,self.poly_space)
offset = self.nmos_lower.get_pin("G").ul() - vector(0,self.poly_extend_active) offset = self.nmos_lower.get_pin("G").ul() - vector(0, self.poly_extend_active)
height = self.nmos_upper.get_pin("G").by() + self.poly_extend_active - offset.y height = self.nmos_upper.get_pin("G").by() + self.poly_extend_active - offset.y
self.add_rect(layer="poly", self.add_rect(layer="poly",
offset=offset, offset=offset,
@ -133,63 +141,30 @@ class single_level_column_mux(pgate.pgate):
def connect_bitlines(self): def connect_bitlines(self):
""" Connect the bitlines to the mux transistors """ """ Connect the bitlines to the mux transistors """
# If li exists, use li and m1 for the mux, otherwise use m1 and m2
if "li" in layer:
self.col_mux_stack = self.li_stack
else:
self.col_mux_stack = self.m1_stack
# These are on metal2
bl_pin = self.get_pin("bl") bl_pin = self.get_pin("bl")
br_pin = self.get_pin("br") br_pin = self.get_pin("br")
bl_out_pin = self.get_pin("bl_out") bl_out_pin = self.get_pin("bl_out")
br_out_pin = self.get_pin("br_out") br_out_pin = self.get_pin("br_out")
# These are on metal1
nmos_lower_s_pin = self.nmos_lower.get_pin("S") nmos_lower_s_pin = self.nmos_lower.get_pin("S")
nmos_lower_d_pin = self.nmos_lower.get_pin("D") nmos_lower_d_pin = self.nmos_lower.get_pin("D")
nmos_upper_s_pin = self.nmos_upper.get_pin("S") nmos_upper_s_pin = self.nmos_upper.get_pin("S")
nmos_upper_d_pin = self.nmos_upper.get_pin("D") nmos_upper_d_pin = self.nmos_upper.get_pin("D")
# Add vias to bl, br_out, nmos_upper/S, nmos_lower/D # Add vias to bl, br_out, nmos_upper/S, nmos_lower/D
self.add_via_center(layers=self.col_mux_stack, self.add_via_stack_center(from_layer=bl_pin.layer,
offset=bl_pin.bc(), to_layer=self.col_mux_stack[0],
directions=("V", "V")) offset=bl_pin.bc())
self.add_via_center(layers=self.col_mux_stack, self.add_via_stack_center(from_layer=br_out_pin.layer,
offset=br_out_pin.uc(), to_layer=self.col_mux_stack[0],
directions=("V", "V")) offset=br_out_pin.uc())
self.add_via_center(layers=self.col_mux_stack, self.add_via_stack_center(from_layer=nmos_upper_s_pin.layer,
offset=nmos_upper_s_pin.center(), to_layer=self.col_mux_stack[2],
directions=("V", "V")) offset=nmos_upper_s_pin.center())
self.add_via_center(layers=self.col_mux_stack, self.add_via_stack_center(from_layer=nmos_lower_d_pin.layer,
offset=nmos_lower_d_pin.center(), to_layer=self.col_mux_stack[2],
directions=("V", "V")) offset=nmos_lower_d_pin.center())
# Add diffusion contacts
# These were previously omitted with the options: add_source_contact=False, add_drain_contact=False
# They are added now and not previously so that they do not include m1 (which is usually included by default)
# This is only a concern when the local interconnect (li) layer is being used
self.add_via_center(layers=self.active_stack,
offset=nmos_upper_d_pin.center(),
directions=("V", "V"),
implant_type="n",
well_type="nwell")
self.add_via_center(layers=self.active_stack,
offset=nmos_lower_s_pin.center(),
directions=("V", "V"),
implant_type="n",
well_type="nwell")
self.add_via_center(layers=self.active_stack,
offset=nmos_upper_s_pin.center(),
directions=("V", "V"),
implant_type="n",
well_type="nwell")
self.add_via_center(layers=self.active_stack,
offset=nmos_lower_d_pin.center(),
directions=("V", "V"),
implant_type="n",
well_type="nwell")
# bl -> nmos_upper/D on metal1 # bl -> nmos_upper/D on metal1
# bl_out -> nmos_upper/S on metal2 # bl_out -> nmos_upper/S on metal2
@ -211,14 +186,27 @@ class single_level_column_mux(pgate.pgate):
vector(nmos_lower_s_pin.cx(), br_out_pin.uy()), vector(nmos_lower_s_pin.cx(), br_out_pin.uy()),
nmos_lower_s_pin.center()]) nmos_lower_s_pin.center()])
# halfway up, move over # halfway up, move over
mid1 = br_pin.bc().scale(1,0.5) \ mid1 = br_pin.bc().scale(1, 0.5) \
+ nmos_lower_d_pin.uc().scale(0,0.5) + nmos_lower_d_pin.uc().scale(0, 0.5)
mid2 = br_pin.bc().scale(0,0.5) \ mid2 = br_pin.bc().scale(0, 0.5) \
+ nmos_lower_d_pin.uc().scale(1,0.5) + nmos_lower_d_pin.uc().scale(1, 0.5)
self.add_path(self.col_mux_stack[2], self.add_path(self.col_mux_stack[2],
[br_pin.bc(), mid1, mid2, nmos_lower_d_pin.center()]) [br_pin.bc(), mid1, mid2, nmos_lower_d_pin.center()])
def add_wells(self): def add_implants(self):
"""
Add top-to-bottom implants for adjacency issues in s8.
"""
# Route to the bottom
ll = (self.nmos_lower.ll() - vector(2 * [self.implant_enclose_active])).scale(1, 0)
# Don't route to the top
ur = self.nmos_upper.ur() + vector(self.implant_enclose_active, 0)
self.add_rect("nimplant",
ll,
ur.x - ll.x,
ur.y - ll.y)
def add_pn_wells(self):
""" """
Add a well and implant over the whole cell. Also, add the Add a well and implant over the whole cell. Also, add the
pwell contact (if it exists) pwell contact (if it exists)
@ -237,8 +225,8 @@ class single_level_column_mux(pgate.pgate):
offset=active_pos) offset=active_pos)
# Add the M1->..->power_grid_layer stack # Add the M1->..->power_grid_layer stack
self.add_power_pin(name = "gnd", self.add_power_pin(name="gnd",
loc = active_pos, loc=active_pos,
start_layer="m1") start_layer="m1")
# Add well enclosure over all the tx and contact # Add well enclosure over all the tx and contact

View File

@ -0,0 +1,151 @@
# 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 debug
from vector import vector
import design
from sram_factory import factory
from globals import OPTS
from tech import layer
class wordline_driver(design.design):
"""
This is an AND (or NAND) with configurable drive strength to drive the wordlines.
It is matched to the bitcell height.
"""
def __init__(self, name, size=1, height=None):
debug.info(1, "Creating wordline_driver {}".format(name))
self.add_comment("size: {}".format(size))
design.design.__init__(self, name)
if height is None:
b = factory.create(module_type="bitcell")
self.height = b.height
else:
self.height = height
self.size = size
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
def create_netlist(self):
self.add_pins()
self.create_modules()
self.create_insts()
def create_modules(self):
self.nand = factory.create(module_type="nand2_dec",
height=self.height)
self.driver = factory.create(module_type="inv_dec",
size=self.size,
height=self.nand.height)
self.add_mod(self.nand)
self.add_mod(self.driver)
def create_layout(self):
self.width = self.nand.width + self.driver.width
if "li" in layer:
self.route_layer = "li"
else:
self.route_layer = "m1"
self.place_insts()
self.route_wires()
self.add_layout_pins()
self.route_supply_rails()
self.add_boundary()
self.DRC_LVS()
def add_pins(self):
self.add_pin("A", "INPUT")
self.add_pin("B", "INPUT")
self.add_pin("Z", "OUTPUT")
self.add_pin("vdd", "POWER")
self.add_pin("gnd", "GROUND")
def create_insts(self):
self.nand_inst = self.add_inst(name="wld_nand",
mod=self.nand)
self.connect_inst(["A", "B", "zb_int", "vdd", "gnd"])
self.driver_inst = self.add_inst(name="wl_driver",
mod=self.driver)
self.connect_inst(["zb_int", "Z", "vdd", "gnd"])
def place_insts(self):
# Add NAND to the right
self.nand_inst.place(offset=vector(0, 0))
# Add INV to the right
self.driver_inst.place(offset=vector(self.nand_inst.rx(), 0))
def route_supply_rails(self):
""" Add vdd/gnd rails to the top, (middle), and bottom. """
if OPTS.tech_name == "sky130":
for name in ["vdd", "gnd"]:
for inst in [self.nand_inst, self.driver_inst]:
self.copy_layout_pin(inst, name)
else:
self.add_layout_pin_rect_center(text="gnd",
layer=self.route_layer,
offset=vector(0.5 * self.width, 0),
width=self.width)
y_offset = self.height
self.add_layout_pin_rect_center(text="vdd",
layer=self.route_layer,
offset=vector(0.5 * self.width, y_offset),
width=self.width)
def route_wires(self):
# nand Z to inv A
z1_pin = self.nand_inst.get_pin("Z")
a2_pin = self.driver_inst.get_pin("A")
if OPTS.tech_name == "sky130":
mid1_point = vector(a2_pin.cx(), z1_pin.cy())
else:
mid1_point = vector(z1_pin.cx(), a2_pin.cy())
self.add_path(self.route_layer,
[z1_pin.center(), mid1_point, a2_pin.center()])
def add_layout_pins(self):
pin = self.driver_inst.get_pin("Z")
self.add_layout_pin_rect_center(text="Z",
layer=pin.layer,
offset=pin.center(),
width=pin.width(),
height=pin.height())
for pin_name in ["A", "B"]:
pin = self.nand_inst.get_pin(pin_name)
self.add_layout_pin_rect_center(text=pin_name,
layer=pin.layer,
offset=pin.center(),
width=pin.width(),
height=pin.height())
def get_stage_efforts(self, external_cout, inp_is_rise=False):
"""Get the stage efforts of the A or B -> Z path"""
stage_effort_list = []
stage1_cout = self.driver.get_cin()
stage1 = self.nand.get_stage_effort(stage1_cout, inp_is_rise)
stage_effort_list.append(stage1)
stage2 = self.driver.get_stage_effort(external_cout, stage1.is_rise)
stage_effort_list.append(stage2)
return stage_effort_list
def get_cin(self):
"""Return the relative input capacitance of a single input"""
return self.nand.get_cin()

View File

@ -9,7 +9,7 @@ import debug
from vector import vector from vector import vector
from sram_base import sram_base from sram_base import sram_base
from contact import m2_via from contact import m2_via
from globals import OPTS
class sram_1bank(sram_base): class sram_1bank(sram_base):
""" """
@ -39,6 +39,11 @@ class sram_1bank(sram_base):
else: else:
self.data_dff_insts = self.create_data_dff() self.data_dff_insts = self.create_data_dff()
if self.num_spare_cols:
self.spare_wen_dff_insts = self.create_spare_wen_dff()
else:
self.num_spare_cols = 0
def place_instances(self): def place_instances(self):
""" """
This places the instances for a single bank SRAM with control This places the instances for a single bank SRAM with control
@ -57,6 +62,7 @@ class sram_1bank(sram_base):
row_addr_pos = [None] * len(self.all_ports) row_addr_pos = [None] * len(self.all_ports)
col_addr_pos = [None] * len(self.all_ports) col_addr_pos = [None] * len(self.all_ports)
wmask_pos = [None] * len(self.all_ports) wmask_pos = [None] * len(self.all_ports)
spare_wen_pos = [None] * len(self.all_ports)
data_pos = [None] * len(self.all_ports) data_pos = [None] * len(self.all_ports)
# These positions utilize the channel route sizes. # These positions utilize the channel route sizes.
@ -64,61 +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.data_bus_gap
self.wmask_bus_gap = self.m2_nonpref_pitch * 2
self.wmask_bus_size = self.m2_nonpref_pitch * (max(self.num_wmasks + 1, self.col_addr_size + 1)) + self.wmask_bus_gap
else:
self.data_bus_gap = self.m3_nonpref_pitch * 2
self.data_bus_size = self.m3_nonpref_pitch * (max(self.word_size + 1, self.col_addr_size + 1)) + self.data_bus_gap
self.col_addr_bus_gap = self.m2_nonpref_pitch * 2 # Spare wen are on a separate layer so not included
self.col_addr_bus_size = self.m2_nonpref_pitch * (self.col_addr_size) + self.col_addr_bus_gap # Start with 1 track minimum
self.data_bus_size = [1] * len(self.all_ports)
for port in self.all_ports:
# All ports need the col addr flops
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:
# Add the write mask flops below the write mask AND array.
wmask_pos[port] = vector(self.bank.bank_array_ll.x,
- self.wmask_bus_size - self.dff.height)
self.wmask_dff_insts[port].place(wmask_pos[port])
# Add the data flops below the write mask flops.
data_pos[port] = vector(self.bank.bank_array_ll.x,
- self.data_bus_size - self.wmask_bus_size - 2 * self.dff.height)
self.data_dff_insts[port].place(data_pos[port])
else:
# Add the data flops below the bank to the right of the lower-left of bank array
# 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.
if port in self.write_ports:
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)
# 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,
-self.wmask_bus_size - self.col_addr_dff_insts[port].height)
else:
col_addr_pos[port] = vector(self.bank.bank_array_ll.x - self.col_addr_dff_insts[port].width - self.bank.m2_gap,
-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.
@ -128,48 +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:
# Add the write mask flops below the write mask AND array.
wmask_pos[port] = vector(self.bank.bank_array_ur.x - self.wmask_dff_insts[port].width,
self.bank.height + self.wmask_bus_size + self.dff.height)
self.wmask_dff_insts[port].place(wmask_pos[port], mirror="MX")
# Add the data flops below the write mask flops
data_pos[port] = vector(self.bank.bank_array_ur.x - self.data_dff_insts[port].width,
self.bank.height + self.wmask_bus_size + self.data_bus_size + 2 * self.dff.height)
self.data_dff_insts[port].place(data_pos[port], mirror="MX")
else:
# Add the data flops above the bank to the left of the upper-right of bank array
# 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 + self.wmask_bus_size + self.dff.height)
else:
col_addr_pos[port] = vector(self.bank.bank_array_ur.x + self.bank.m2_gap,
self.bank.height + 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.
@ -179,45 +166,171 @@ 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.
""" """
highest_coord = self.find_highest_coords()
lowest_coord = self.find_lowest_coords()
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
# Port 0 is left/bottom
# Port 1 is right/top
bottom_or_top = "bottom" if port==0 else "top"
left_or_right = "left" if port==0 else "right"
# Connect the control pins as inputs # Connect the control pins as inputs
for signal in self.control_logic_inputs[port] + ["clk"]: for signal in self.control_logic_inputs[port]:
if signal == "clk":
continue
if OPTS.perimeter_pins:
self.add_perimeter_pin(name=signal + "{}".format(port),
pin=self.control_logic_insts[port].get_pin(signal),
side=left_or_right,
bbox=bbox)
else:
self.copy_layout_pin(self.control_logic_insts[port],
signal,
signal + "{}".format(port))
if OPTS.perimeter_pins:
self.add_perimeter_pin(name="clk{}".format(port),
pin=self.control_logic_insts[port].get_pin("clk"),
side=bottom_or_top,
bbox=bbox)
else:
self.copy_layout_pin(self.control_logic_insts[port], self.copy_layout_pin(self.control_logic_insts[port],
signal, "clk",
signal + "{}".format(port)) "clk{}".format(port))
if port in self.read_ports:
for bit in range(self.word_size):
self.copy_layout_pin(self.bank_inst,
"dout{0}_{1}".format(port, bit),
"dout{0}[{1}]".format(port, bit))
# Lower address bits
for bit in range(self.col_addr_size):
self.copy_layout_pin(self.col_addr_dff_insts[port],
"din_{}".format(bit),
"addr{0}[{1}]".format(port, bit))
# Upper address bits
for bit in range(self.row_addr_size):
self.copy_layout_pin(self.row_addr_dff_insts[port],
"din_{}".format(bit),
"addr{0}[{1}]".format(port, bit + self.col_addr_size))
# Data input pins go to BOTTOM/TOP
din_ports = []
if port in self.write_ports: if port in self.write_ports:
for bit in range(self.word_size): for bit in range(self.word_size + self.num_spare_cols):
self.copy_layout_pin(self.data_dff_insts[port], if OPTS.perimeter_pins:
"din_{}".format(bit), p = self.add_perimeter_pin(name="din{0}[{1}]".format(port, bit),
"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),
pin=self.bank_inst.get_pin("dout{0}_{1}".format(port, bit)),
side=bottom_or_top,
bbox=bbox)
else:
self.copy_layout_pin(self.bank_inst,
"dout{0}_{1}".format(port, bit),
"dout{0}[{1}]".format(port, bit))
# Lower address bits go to BOTTOM/TOP
for bit in range(self.col_addr_size):
if OPTS.perimeter_pins:
self.add_perimeter_pin(name="addr{0}[{1}]".format(port, bit),
pin=self.col_addr_dff_insts[port].get_pin("din_{}".format(bit)),
side=bottom_or_top,
bbox=bbox)
else:
self.copy_layout_pin(self.col_addr_dff_insts[port],
"din_{}".format(bit),
"addr{0}[{1}]".format(port, bit))
# Upper address bits go to LEFT/RIGHT
for bit in range(self.row_addr_size):
if OPTS.perimeter_pins:
self.add_perimeter_pin(name="addr{0}[{1}]".format(port, bit + self.col_addr_size),
pin=self.row_addr_dff_insts[port].get_pin("din_{}".format(bit)),
side=left_or_right,
bbox=bbox)
else:
self.copy_layout_pin(self.row_addr_dff_insts[port],
"din_{}".format(bit),
"addr{0}[{1}]".format(port, bit + self.col_addr_size))
# Write mask pins go to BOTTOM/TOP
if port in self.write_ports:
if self.write_size: if self.write_size:
for bit in range(self.num_wmasks): for bit in range(self.num_wmasks):
self.copy_layout_pin(self.wmask_dff_insts[port], if OPTS.perimeter_pins:
self.add_perimeter_pin(name="wmask{0}[{1}]".format(port, bit),
pin=self.wmask_dff_insts[port].get_pin("din_{}".format(bit)),
side=bottom_or_top,
bbox=bbox)
else:
self.copy_layout_pin(self.wmask_dff_insts[port],
"din_{}".format(bit),
"wmask{0}[{1}]".format(port, bit))
# Spare wen pins go to BOTTOM/TOP
if port in self.write_ports:
for bit in range(self.num_spare_cols):
if OPTS.perimeter_pins:
self.add_perimeter_pin(name="spare_wen{0}[{1}]".format(port, bit),
pin=self.spare_wen_dff_insts[port].get_pin("din_{}".format(bit)),
side=left_or_right,
bbox=bbox)
else:
self.copy_layout_pin(self.spare_wen_dff_insts[port],
"din_{}".format(bit), "din_{}".format(bit),
"wmask{0}[{1}]".format(port, bit)) "spare_wen{0}[{1}]".format(port, bit))
def route_layout(self): def route_layout(self):
""" Route a single bank SRAM """ """ Route a single bank SRAM """
@ -230,21 +343,81 @@ 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()
def route_dff(self, port):
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 """
# This is the actual input to the SRAM # This is the actual input to the SRAM
for port in self.all_ports: for port in self.all_ports:
self.copy_layout_pin(self.control_logic_insts[port], "clk", "clk{}".format(port))
# Connect all of these clock pins to the clock in the central bus # Connect all of these clock pins to the clock in the central bus
# This is something like a "spine" clock distribution. The two spines # This is something like a "spine" clock distribution. The two spines
# are clk_buf and clk_buf_bar # are clk_buf and clk_buf_bar
@ -282,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)
@ -295,15 +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])
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 """
@ -320,7 +483,14 @@ class sram_1bank(sram_base):
# Only input (besides pins) is the replica bitline # Only input (besides pins) is the replica bitline
src_pin = self.control_logic_insts[port].get_pin("rbl_bl") src_pin = self.control_logic_insts[port].get_pin("rbl_bl")
dest_pin = self.bank_inst.get_pin("rbl_bl{}".format(port)) dest_pin = self.bank_inst.get_pin("rbl_bl{}".format(port))
self.connect_hbus(src_pin, dest_pin) self.add_wire(self.m2_stack[::-1],
[src_pin.center(), vector(src_pin.cx(), dest_pin.cy()), dest_pin.rc()])
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 """
@ -333,117 +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 self.write_size:
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)
else:
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)]
dff_pins = [self.data_dff_insts[port].get_pin(x) for x in dff_names]
if self.write_size:
for x in dff_names:
pin = 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)]
bank_pins = [self.bank_inst.get_pin(x) for x in bank_names]
if self.write_size:
for x in bank_names:
pin = self.bank_inst.get_pin(x)
if port % 2:
pin_offset = 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:
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 add_lvs_correspondence_points(self): def add_lvs_correspondence_points(self):
""" """
@ -466,6 +533,9 @@ class sram_1bank(sram_base):
if self.write_size: if self.write_size:
for inst in self.wmask_dff_insts: for inst in self.wmask_dff_insts:
self.graph_inst_exclude.add(inst) self.graph_inst_exclude.add(inst)
if self.num_spare_cols:
for inst in self.spare_wen_dff_insts:
self.graph_inst_exclude.add(inst)
def graph_exclude_addr_dff(self): def graph_exclude_addr_dff(self):
"""Removes data dff from search graph. """ """Removes data dff from search graph. """

View File

@ -37,6 +37,9 @@ class sram_base(design, verilog, lef):
else: else:
self.num_wmasks = 0 self.num_wmasks = 0
if not self.num_spare_cols:
self.num_spare_cols = 0
# For logical effort delay calculations. # For logical effort delay calculations.
self.all_mods_except_control_done = False self.all_mods_except_control_done = False
@ -44,7 +47,7 @@ class sram_base(design, verilog, lef):
""" Add pins for entire SRAM. """ """ Add pins for entire SRAM. """
for port in self.write_ports: for port in self.write_ports:
for bit in range(self.word_size): for bit in range(self.word_size + self.num_spare_cols):
self.add_pin("din{0}[{1}]".format(port, bit), "INPUT") self.add_pin("din{0}[{1}]".format(port, bit), "INPUT")
for port in self.all_ports: for port in self.all_ports:
@ -75,8 +78,10 @@ class sram_base(design, verilog, lef):
for port in self.write_ports: for port in self.write_ports:
for bit in range(self.num_wmasks): for bit in range(self.num_wmasks):
self.add_pin("wmask{0}[{1}]".format(port, bit), "INPUT") self.add_pin("wmask{0}[{1}]".format(port, bit), "INPUT")
for bit in range(self.num_spare_cols):
self.add_pin("spare_wen{0}[{1}]".format(port, bit), "INPUT")
for port in self.read_ports: for port in self.read_ports:
for bit in range(self.word_size): for bit in range(self.word_size + self.num_spare_cols):
self.add_pin("dout{0}[{1}]".format(port, bit), "OUTPUT") self.add_pin("dout{0}[{1}]".format(port, bit), "OUTPUT")
self.add_pin("vdd", "POWER") self.add_pin("vdd", "POWER")
@ -119,6 +124,8 @@ class sram_base(design, verilog, lef):
highest_coord = self.find_highest_coords() highest_coord = self.find_highest_coords()
self.width = highest_coord[0] self.width = highest_coord[0]
self.height = highest_coord[1] self.height = highest_coord[1]
self.add_boundary(ll=vector(0, 0),
ur=vector(self.width, self.height))
start_time = datetime.datetime.now() start_time = datetime.datetime.now()
# We only enable final verification if we have routed the design # We only enable final verification if we have routed the design
@ -137,7 +144,7 @@ class sram_base(design, verilog, lef):
for inst in self.insts: for inst in self.insts:
self.copy_power_pins(inst, "vdd") self.copy_power_pins(inst, "vdd")
self.copy_power_pins(inst, "gnd") self.copy_power_pins(inst, "gnd")
if not OPTS.route_supplies: if not OPTS.route_supplies:
# Do not route the power supply (leave as must-connect pins) # Do not route the power supply (leave as must-connect pins)
return return
@ -274,12 +281,16 @@ class sram_base(design, verilog, lef):
else: else:
self.col_addr_dff = None self.col_addr_dff = None
self.data_dff = factory.create("dff_array", module_name="data_dff", rows=1, columns=self.word_size) self.data_dff = factory.create("dff_array", module_name="data_dff", rows=1, columns=self.word_size + self.num_spare_cols)
self.add_mod(self.data_dff) self.add_mod(self.data_dff)
if self.write_size: if self.write_size:
self.wmask_dff = factory.create("dff_array", module_name="wmask_dff", rows=1, columns=self.num_wmasks) self.wmask_dff = factory.create("dff_array", module_name="wmask_dff", rows=1, columns=self.num_wmasks)
self.add_mod(self.wmask_dff) self.add_mod(self.wmask_dff)
if self.num_spare_cols:
self.spare_wen_dff = factory.create("dff_array", module_name="spare_wen_dff", rows=1, columns=self.num_spare_cols)
self.add_mod(self.spare_wen_dff)
# Create the bank module (up to four are instantiated) # Create the bank module (up to four are instantiated)
self.bank = factory.create("bank", sram_config=self.sram_config, module_name="bank") self.bank = factory.create("bank", sram_config=self.sram_config, module_name="bank")
@ -303,6 +314,7 @@ class sram_base(design, verilog, lef):
self.control_logic_rw = self.mod_control_logic(num_rows=self.num_rows, self.control_logic_rw = self.mod_control_logic(num_rows=self.num_rows,
words_per_row=self.words_per_row, words_per_row=self.words_per_row,
word_size=self.word_size, word_size=self.word_size,
spare_columns=self.num_spare_cols,
sram=self, sram=self,
port_type="rw") port_type="rw")
self.add_mod(self.control_logic_rw) self.add_mod(self.control_logic_rw)
@ -310,6 +322,7 @@ class sram_base(design, verilog, lef):
self.control_logic_w = self.mod_control_logic(num_rows=self.num_rows, self.control_logic_w = self.mod_control_logic(num_rows=self.num_rows,
words_per_row=self.words_per_row, words_per_row=self.words_per_row,
word_size=self.word_size, word_size=self.word_size,
spare_columns=self.num_spare_cols,
sram=self, sram=self,
port_type="w") port_type="w")
self.add_mod(self.control_logic_w) self.add_mod(self.control_logic_w)
@ -317,6 +330,7 @@ class sram_base(design, verilog, lef):
self.control_logic_r = self.mod_control_logic(num_rows=self.num_rows, self.control_logic_r = self.mod_control_logic(num_rows=self.num_rows,
words_per_row=self.words_per_row, words_per_row=self.words_per_row,
word_size=self.word_size, word_size=self.word_size,
spare_columns=self.num_spare_cols,
sram=self, sram=self,
port_type="r") port_type="r")
self.add_mod(self.control_logic_r) self.add_mod(self.control_logic_r)
@ -328,12 +342,12 @@ class sram_base(design, verilog, lef):
temp = [] temp = []
for port in self.read_ports: for port in self.read_ports:
for bit in range(self.word_size): for bit in range(self.word_size + self.num_spare_cols):
temp.append("dout{0}[{1}]".format(port, bit)) temp.append("dout{0}[{1}]".format(port, bit))
for port in self.all_ports: for port in self.all_ports:
temp.append("rbl_bl{0}".format(port)) temp.append("rbl_bl{0}".format(port))
for port in self.write_ports: for port in self.write_ports:
for bit in range(self.word_size): for bit in range(self.word_size + self.num_spare_cols):
temp.append("bank_din{0}[{1}]".format(port, bit)) temp.append("bank_din{0}[{1}]".format(port, bit))
for port in self.all_ports: for port in self.all_ports:
for bit in range(self.bank_addr_size): for bit in range(self.bank_addr_size):
@ -349,6 +363,8 @@ class sram_base(design, verilog, lef):
temp.append("w_en{0}".format(port)) temp.append("w_en{0}".format(port))
for bit in range(self.num_wmasks): for bit in range(self.num_wmasks):
temp.append("bank_wmask{}[{}]".format(port, bit)) temp.append("bank_wmask{}[{}]".format(port, bit))
for bit in range(self.num_spare_cols):
temp.append("bank_spare_wen{0}[{1}]".format(port, bit))
for port in self.all_ports: for port in self.all_ports:
temp.append("wl_en{0}".format(port)) temp.append("wl_en{0}".format(port))
temp.extend(["vdd", "gnd"]) temp.extend(["vdd", "gnd"])
@ -436,7 +452,7 @@ class sram_base(design, verilog, lef):
# inputs, outputs/output/bar # inputs, outputs/output/bar
inputs = [] inputs = []
outputs = [] outputs = []
for bit in range(self.word_size): for bit in range(self.word_size + self.num_spare_cols):
inputs.append("din{}[{}]".format(port, bit)) inputs.append("din{}[{}]".format(port, bit))
outputs.append("bank_din{}[{}]".format(port, bit)) outputs.append("bank_din{}[{}]".format(port, bit))
@ -465,7 +481,29 @@ class sram_base(design, verilog, lef):
self.connect_inst(inputs + outputs + ["clk_buf{}".format(port), "vdd", "gnd"]) self.connect_inst(inputs + outputs + ["clk_buf{}".format(port), "vdd", "gnd"])
return insts return insts
def create_spare_wen_dff(self):
""" Add all spare write enable flops """
insts = []
for port in self.all_ports:
if port in self.write_ports:
insts.append(self.add_inst(name="spare_wen_dff{}".format(port),
mod=self.spare_wen_dff))
else:
insts.append(None)
continue
# inputs, outputs/output/bar
inputs = []
outputs = []
for bit in range(self.num_spare_cols):
inputs.append("spare_wen{}[{}]".format(port, bit))
outputs.append("bank_spare_wen{}[{}]".format(port, bit))
self.connect_inst(inputs + outputs + ["clk_buf{}".format(port), "vdd", "gnd"])
return insts
def create_control_logic(self): def create_control_logic(self):
""" Add control logic instances """ """ Add control logic instances """

View File

@ -14,18 +14,18 @@ from sram_factory import factory
class sram_config: class sram_config:
""" This is a structure that is used to hold the SRAM configuration options. """ """ This is a structure that is used to hold the SRAM configuration options. """
def __init__(self, word_size, num_words, write_size = None, num_banks=1, words_per_row=None): def __init__(self, word_size, num_words, write_size = None, num_banks=1, words_per_row=None, num_spare_rows=0, num_spare_cols=None):
self.word_size = word_size self.word_size = word_size
self.num_words = num_words self.num_words = num_words
self.write_size = write_size self.write_size = write_size
self.num_banks = num_banks self.num_banks = num_banks
self.num_spare_rows = num_spare_rows
self.num_spare_cols = num_spare_cols
# This will get over-written when we determine the organization # This will get over-written when we determine the organization
self.words_per_row = words_per_row self.words_per_row = words_per_row
self.compute_sizes() self.compute_sizes()
def set_local_config(self, module): def set_local_config(self, module):
""" Copy all of the member variables to the given module for convenience """ """ Copy all of the member variables to the given module for convenience """
@ -78,11 +78,12 @@ class sram_config:
# Fix the number of columns and rows # Fix the number of columns and rows
self.num_cols = int(self.words_per_row*self.word_size) self.num_cols = int(self.words_per_row*self.word_size)
self.num_rows = int(self.num_words_per_bank/self.words_per_row) self.num_rows_temp = int(self.num_words_per_bank/self.words_per_row)
debug.info(1,"Rows: {} Cols: {}".format(self.num_rows,self.num_cols)) self.num_rows = self.num_rows_temp + self.num_spare_rows
debug.info(1,"Rows: {} Cols: {}".format(self.num_rows_temp,self.num_cols))
# Compute the address and bank sizes # Compute the address and bank sizes
self.row_addr_size = int(log(self.num_rows, 2)) self.row_addr_size = ceil(log(self.num_rows, 2))
self.col_addr_size = int(log(self.words_per_row, 2)) self.col_addr_size = int(log(self.words_per_row, 2))
self.bank_addr_size = self.col_addr_size + self.row_addr_size self.bank_addr_size = self.col_addr_size + self.row_addr_size
self.addr_size = self.bank_addr_size + int(log(self.num_banks, 2)) self.addr_size = self.bank_addr_size + int(log(self.num_banks, 2))

View File

@ -77,7 +77,9 @@ class sram_factory:
""" """
tech_module_type, tm_overridden = self.get_techmodule_type(module_type) tech_module_type, tm_overridden = self.get_techmodule_type(module_type)
user_module_type, um_overridden = self.get_usermodule_type(module_type) user_module_type, um_overridden = self.get_usermodule_type(module_type)
# print(module_type, tech_module_type, tm_overridden)
# print(module_type, user_module_type, um_overridden)
# overridden user modules have priority # overridden user modules have priority
if um_overridden: if um_overridden:
real_module_type = user_module_type real_module_type = user_module_type
@ -109,11 +111,12 @@ class sram_factory:
return obj_item return obj_item
# If no prefered module name is provided, we generate one. # If no prefered module name is provided, we generate one.
if module_name is None: if not module_name:
# Use the default name if there are default arguments # Use the default name for the first cell.
# This is especially for library cells so that the # This is especially for library cells so that the
# spice and gds files can be found. # spice and gds files can be found.
if len(kwargs) > 0: # Subsequent objects will get unique names to help with GDS limitation.
if len(self.objects[real_module_type]) > 0:
# Create a unique name and increment the index # Create a unique name and increment the index
module_name = "{0}_{1}".format(real_module_type, module_name = "{0}_{1}".format(real_module_type,
self.module_indices[real_module_type]) self.module_indices[real_module_type])

View File

@ -35,7 +35,7 @@ class library_lvs_test(openram_test):
debug.error("Missing GDS file {}".format(gds_name)) debug.error("Missing GDS file {}".format(gds_name))
if not os.path.isfile(sp_name): if not os.path.isfile(sp_name):
lvs_errors += 1 lvs_errors += 1
debug.error("Missing SPICE file {}".format(gds_name)) debug.error("Missing SPICE file {}".format(sp_name))
drc_errors += verify.run_drc(name, gds_name) drc_errors += verify.run_drc(name, gds_name)
lvs_errors += verify.run_lvs(f, gds_name, sp_name) lvs_errors += verify.run_lvs(f, gds_name, sp_name)

View File

@ -0,0 +1,42 @@
#!/usr/bin/env python3
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2019 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
import unittest
from testutils import *
import sys,os
sys.path.append(os.getenv("OPENRAM_HOME"))
import globals
from globals import OPTS
from sram_factory import factory
import debug
class and2_dec_test(openram_test):
def runTest(self):
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
globals.init_openram(config_file)
global verify
import verify
OPTS.num_rw_ports = 1
OPTS.num_r_ports = 1
OPTS.num_w_ports = 0
globals.setup_bitcell()
debug.info(2, "Testing and2_dec gate")
a = factory.create(module_type="and2_dec")
self.local_check(a)
globals.end_openram()
# instantiate a copdsay 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,42 @@
#!/usr/bin/env python3
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2019 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
import unittest
from testutils import *
import sys,os
sys.path.append(os.getenv("OPENRAM_HOME"))
import globals
from globals import OPTS
from sram_factory import factory
import debug
class and3_dec_test(openram_test):
def runTest(self):
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
globals.init_openram(config_file)
global verify
import verify
OPTS.num_rw_ports = 1
OPTS.num_r_ports = 1
OPTS.num_w_ports = 0
globals.setup_bitcell()
debug.info(2, "Testing and3_dec gate")
a = factory.create(module_type="and3_dec")
self.local_check(a)
globals.end_openram()
# instantiate a copdsay 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,44 @@
#!/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 04_and4_dec_test")
class and4_dec_test(openram_test):
def runTest(self):
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
globals.init_openram(config_file)
global verify
import verify
OPTS.num_rw_ports = 1
OPTS.num_r_ports = 1
OPTS.num_w_ports = 0
globals.setup_bitcell()
debug.info(2, "Testing and4_dec gate")
a = factory.create(module_type="and4_dec")
self.local_check(a)
globals.end_openram()
# instantiate a copdsay 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

@ -32,13 +32,13 @@ class pdriver_test(openram_test):
c = factory.create(module_type="pdriver", fanout = 50) c = factory.create(module_type="pdriver", fanout = 50)
self.local_check(c) self.local_check(c)
d = factory.create(module_type="pdriver", fanout = 50, neg_polarity = True) d = factory.create(module_type="pdriver", fanout = 50, inverting = True)
self.local_check(d) self.local_check(d)
e = factory.create(module_type="pdriver", fanout = 64) e = factory.create(module_type="pdriver", fanout = 64)
self.local_check(e) self.local_check(e)
f = factory.create(module_type="pdriver", fanout = 64, neg_polarity = True) f = factory.create(module_type="pdriver", fanout = 64, inverting = True)
self.local_check(f) self.local_check(f)
globals.end_openram() globals.end_openram()

View File

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

View File

@ -25,6 +25,11 @@ class pnand2_test(openram_test):
tx = factory.create(module_type="pnand2", size=1) tx = factory.create(module_type="pnand2", size=1)
self.local_check(tx) self.local_check(tx)
# debug.info(2, "Checking 2-input nand gate")
# tx = factory.create(module_type="pnand2", size=1, add_wells=False)
# # Only DRC because well contacts will fail LVS
# self.local_drc_check(tx)
globals.end_openram() globals.end_openram()

View File

@ -25,6 +25,11 @@ class pnand3_test(openram_test):
tx = factory.create(module_type="pnand3", size=1) tx = factory.create(module_type="pnand3", size=1)
self.local_check(tx) self.local_check(tx)
# debug.info(2, "Checking 3-input nand gate")
# tx = factory.create(module_type="pnand3", size=1, add_wells=False)
# # Only DRC because well contacts will fail LVS
# self.local_drc_check(tx)
globals.end_openram() globals.end_openram()

View File

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

View File

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

View File

View File

@ -15,7 +15,6 @@ from globals import OPTS
from sram_factory import factory from sram_factory import factory
import debug import debug
#@unittest.skip("SKIPPING 04_driver_test")
class single_level_column_mux_test(openram_test): class single_level_column_mux_test(openram_test):

View File

@ -25,7 +25,7 @@ class wordline_driver_test(openram_test):
# check wordline driver for single port # check wordline driver for single port
debug.info(2, "Checking driver") debug.info(2, "Checking driver")
tx = factory.create(module_type="wordline_driver", rows=8, cols=32) tx = factory.create(module_type="wordline_driver")
self.local_check(tx) self.local_check(tx)
globals.end_openram() globals.end_openram()

View File

@ -17,18 +17,16 @@ import debug
#@unittest.skip("SKIPPING 05_bitcell_1rw_1r_array_test") #@unittest.skip("SKIPPING 05_bitcell_1rw_1r_array_test")
class bitcell_1rw_1r_array_test(openram_test): class bitcell_array_1rw_1r_test(openram_test):
def runTest(self): def runTest(self):
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
globals.init_openram(config_file) globals.init_openram(config_file)
OPTS.bitcell = "bitcell_1rw_1r"
OPTS.replica_bitcell = "replica_bitcell_1rw_1r"
OPTS.dummy_bitcell="dummy_bitcell_1rw_1r"
OPTS.num_rw_ports = 1 OPTS.num_rw_ports = 1
OPTS.num_r_ports = 1 OPTS.num_r_ports = 1
OPTS.num_w_ports = 0 OPTS.num_w_ports = 0
globals.setup_bitcell()
debug.info(2, "Testing 4x4 array for cell_1rw_1r") debug.info(2, "Testing 4x4 array for cell_1rw_1r")
a = factory.create(module_type="bitcell_array", cols=4, rows=4) a = factory.create(module_type="bitcell_array", cols=4, rows=4)

View File

@ -0,0 +1,68 @@
#!/usr/bin/env python3
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2019 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
import unittest
from testutils import *
import sys,os
sys.path.append(os.getenv("OPENRAM_HOME"))
import globals
from globals import OPTS
from sram_factory import factory
import debug
class hierarchical_decoder_1rw_1r_test(openram_test):
def runTest(self):
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
globals.init_openram(config_file)
# Use the 2 port cell since it is usually bigger/easier
OPTS.num_rw_ports = 1
OPTS.num_r_ports = 1
OPTS.num_w_ports = 0
globals.setup_bitcell()
# Checks 2x4 and 2-input NAND decoder
debug.info(1, "Testing 16 row sample for hierarchical_decoder")
a = factory.create(module_type="hierarchical_decoder", num_outputs=16)
self.local_check(a)
# Checks 2x4 and 2-input NAND decoder with non-power-of-two
debug.info(1, "Testing 17 row sample for hierarchical_decoder")
a = factory.create(module_type="hierarchical_decoder", num_outputs=17)
self.local_check(a)
# Checks 2x4 with 3x8 and 2-input NAND decoder
debug.info(1, "Testing 32 row sample for hierarchical_decoder")
a = factory.create(module_type="hierarchical_decoder", num_outputs=32)
self.local_check(a)
# Checks 3 x 2x4 and 3-input NAND decoder
debug.info(1, "Testing 64 row sample for hierarchical_decoder")
a = factory.create(module_type="hierarchical_decoder", num_outputs=64)
self.local_check(a)
# Checks 2x4 and 2 x 3x8 and 3-input NAND with non-power-of-two
debug.info(1, "Testing 132 row sample for hierarchical_decoder")
a = factory.create(module_type="hierarchical_decoder", num_outputs=132)
self.local_check(a)
# Checks 3 x 3x8 and 3-input NAND decoder
debug.info(1, "Testing 512 row sample for hierarchical_decoder")
a = factory.create(module_type="hierarchical_decoder", num_outputs=512)
self.local_check(a)
globals.end_openram()
# run the test from the command line
if __name__ == "__main__":
(OPTS, args) = globals.parse_args()
del sys.argv[1:]
header(__file__, OPTS.tech_name)
unittest.main(testRunner=debugTestRunner())

View File

@ -21,11 +21,11 @@ class hierarchical_decoder_pbitcell_test(openram_test):
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
globals.init_openram(config_file) globals.init_openram(config_file)
# check hierarchical decoder for multi-port # check hierarchical decoder for multi-port
OPTS.bitcell = "pbitcell"
OPTS.num_rw_ports = 1 OPTS.num_rw_ports = 1
OPTS.num_w_ports = 0 OPTS.num_w_ports = 0
OPTS.num_r_ports = 0 OPTS.num_r_ports = 0
globals.setup_bitcell()
factory.reset() factory.reset()
debug.info(1, "Testing 16 row sample for hierarchical_decoder (multi-port case)") debug.info(1, "Testing 16 row sample for hierarchical_decoder (multi-port case)")
a = factory.create(module_type="hierarchical_decoder", num_outputs=16) a = factory.create(module_type="hierarchical_decoder", num_outputs=16)

View File

@ -25,7 +25,7 @@ class hierarchical_decoder_test(openram_test):
debug.info(1, "Testing 16 row sample for hierarchical_decoder") debug.info(1, "Testing 16 row sample for hierarchical_decoder")
a = factory.create(module_type="hierarchical_decoder", num_outputs=16) a = factory.create(module_type="hierarchical_decoder", num_outputs=16)
self.local_check(a) self.local_check(a)
# Checks 2x4 and 2-input NAND decoder with non-power-of-two # Checks 2x4 and 2-input NAND decoder with non-power-of-two
debug.info(1, "Testing 17 row sample for hierarchical_decoder") debug.info(1, "Testing 17 row sample for hierarchical_decoder")
a = factory.create(module_type="hierarchical_decoder", num_outputs=17) a = factory.create(module_type="hierarchical_decoder", num_outputs=17)

View File

@ -1,7 +1,9 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# See LICENSE for licensing information. # See LICENSE for licensing information.
# #
# Copyright (c) 2016-2019 Regents of the University of California # 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. # All rights reserved.
# #
import unittest import unittest
@ -13,21 +15,22 @@ from globals import OPTS
from sram_factory import factory from sram_factory import factory
import debug import debug
class replica_bitcell_array_test(openram_test):
class hierarchical_predecode2x4_1rw_1r_test(openram_test):
def runTest(self): def runTest(self):
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
globals.init_openram(config_file) globals.init_openram(config_file)
OPTS.num_rw_ports = 1 OPTS.num_rw_ports = 1
OPTS.num_r_ports = 0 OPTS.num_r_ports = 1
OPTS.num_w_ports = 0 OPTS.num_w_ports = 0
globals.setup_bitcell()
factory.reset()
debug.info(2, "Testing 4x4 array for bitcell")
a = factory.create(module_type="replica_bitcell_array", cols=4, rows=4, left_rbl=1, right_rbl=0, bitcell_ports=[0])
self.local_check(a)
debug.info(1, "Testing sample for hierarchy_predecode2x4")
a = factory.create(module_type="hierarchical_predecode2x4")
self.local_check(a)
globals.end_openram() globals.end_openram()
# run the test from the command line # run the test from the command line

View File

@ -22,11 +22,11 @@ class hierarchical_predecode2x4_pbitcell_test(openram_test):
globals.init_openram(config_file) globals.init_openram(config_file)
# checking hierarchical precode 2x4 for multi-port # checking hierarchical precode 2x4 for multi-port
OPTS.bitcell = "pbitcell"
OPTS.num_rw_ports = 1 OPTS.num_rw_ports = 1
OPTS.num_w_ports = 0 OPTS.num_w_ports = 0
OPTS.num_r_ports = 0 OPTS.num_r_ports = 0
globals.setup_bitcell()
debug.info(1, "Testing sample for hierarchy_predecode2x4 (multi-port case)") debug.info(1, "Testing sample for hierarchy_predecode2x4 (multi-port case)")
a = factory.create(module_type="hierarchical_predecode2x4") a = factory.create(module_type="hierarchical_predecode2x4")
self.local_check(a) self.local_check(a)

View File

@ -21,7 +21,6 @@ class hierarchical_predecode2x4_test(openram_test):
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
globals.init_openram(config_file) globals.init_openram(config_file)
# checking hierarchical precode 2x4 for single port
debug.info(1, "Testing sample for hierarchy_predecode2x4") debug.info(1, "Testing sample for hierarchy_predecode2x4")
a = factory.create(module_type="hierarchical_predecode2x4") a = factory.create(module_type="hierarchical_predecode2x4")
self.local_check(a) self.local_check(a)

Some files were not shown because too many files have changed in this diff Show More