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:
- . /home/gitlab-runner/setup-paths.sh
- export OPENRAM_HOME="`pwd`/compiler"
- export OPENRAM_TECH="`pwd`/technology"
- export OPENRAM_TECH="`pwd`/technology:/home/PDKs/skywater-tech"
stages:
- test
@ -25,6 +25,15 @@ scn4m_subm:
- .coverage.*
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:
stage: coverage
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 debug
from tech import drc
from tech import drc, layer
import tech
from vector import vector
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.is_well_contact = implant_type == well_type
# If we have a special tap layer, use it
self.layer_stack = layer_stack
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"
second_dir = "H" if self.get_preferred_direction(layer_stack[2])=="V" else "V"
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
elif 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_minwidth - self.contact_array_height) / 2)
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
# 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_minwidth - self.contact_array_width) / 2)
else:
debug.error("Invalid second layer direction.", -1)
debug.error("Invalid secon layer direction: ".format(self.directions[1]), -1)
def create_contact_array(self):
""" Create the contact array at the origin"""
@ -192,17 +197,19 @@ class contact(hierarchy_design.hierarchy_design):
if "npc" not in tech.layer:
return
npc_enclose_poly = drc("npc_enclose_poly")
npc_enclose_offset = vector(npc_enclose_poly, npc_enclose_poly)
# Only add for poly layers
if self.first_layer_name == "poly":
self.add_rect(layer="npc",
offset=self.first_layer_position,
width=self.first_layer_width,
height=self.first_layer_height)
offset=self.first_layer_position - npc_enclose_offset,
width=self.first_layer_width + 2 * npc_enclose_poly,
height=self.first_layer_height + 2 * npc_enclose_poly)
elif self.second_layer_name == "poly":
self.add_rect(layer="npc",
offset=self.second_layer_position,
width=self.second_layer_width,
height=self.second_layer_height)
offset=self.second_layer_position - npc_enclose_offset,
width=self.second_layer_width + 2 * npc_enclose_poly,
height=self.second_layer_height + 2 * npc_enclose_poly)
def create_first_layer_enclosure(self):
# 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_height = max(self.contact_array_height + 2 * self.first_layer_vertical_enclosure,
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,
width=self.first_layer_width,
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)
if "pwell" in layer:
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
if False:
@ -191,7 +205,8 @@ class design(hierarchy_design):
print("poly_to_active", self.poly_to_active)
print("poly_extend_active", self.poly_extend_active)
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("implant_enclose_active", self.implant_enclose_active)
print("implant_space", self.implant_space)

View File

@ -66,14 +66,17 @@ class geometry:
self.compute_boundary(self.offset, self.mirror, self.rotate)
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:
self.boundary = [vector(0,0), vector(0,0)]
self.boundary = [vector(0, 0), vector(0, 0)]
return
(ll, ur) = [vector(0, 0), vector(self.width, self.height)]
# Mirroring is performed before rotation
if mirror == "MX":
ll = ll.scale(1, -1)
ur = ur.scale(1, -1)
@ -83,8 +86,14 @@ class geometry:
elif mirror == "XY":
ll = ll.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)
ur = ur.rotate_scale(-1, 1)
elif rotate == 180:
@ -93,6 +102,8 @@ class geometry:
elif rotate == 270:
ll = ll.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.normalize()
@ -136,6 +147,10 @@ class geometry:
def cy(self):
""" Return the center 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):
@ -195,14 +210,13 @@ class instance(geometry):
blockages = []
blockages = self.mod.gds.getBlockages(lpp)
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:
blockages = self.mod.get_blockages(lpp)
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
def gds_write_file(self, new_layout):
"""Recursively writes all the sub-modules in this instance"""
debug.info(4, "writing instance: " + self.name)
@ -225,26 +239,25 @@ class instance(geometry):
self.update_boundary()
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
this instance location. Index will return one of several pins."""
import copy
if index == -1:
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
else:
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]
def get_num_pins(self, name):
""" Return the number of pins of a given 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
this instance location. """
@ -253,7 +266,7 @@ class instance(geometry):
new_pins = []
for p in pin:
p.transform(self.offset,self.mirror,self.rotate)
p.transform(self.offset, self.mirror, self.rotate)
new_pins.append(p)
return new_pins
@ -265,6 +278,7 @@ class instance(geometry):
""" override print function output """
return "( inst: " + self.name + " @" + str(self.offset) + " mod=" + self.mod.name + " " + self.mirror + " R=" + str(self.rotate) + ")"
class path(geometry):
"""Represents a Path"""
@ -322,7 +336,7 @@ class label(geometry):
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):
"""Writes the text label to GDS"""
@ -340,7 +354,7 @@ class label(geometry):
def __str__(self):
""" 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):
""" override print function output """

View File

@ -7,11 +7,10 @@
#
import hierarchy_layout
import hierarchy_spice
import verify
import debug
import os
from globals import OPTS
import tech
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
# 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):
self.lvs_file = lvs_dir + name + ".sp"
else:
@ -44,12 +48,13 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
if i.name == inst.name:
break
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
return inst_map
def DRC_LVS(self, final_verification=False, force_check=False):
"""Checks both DRC and LVS for a module"""
import verify
# No layout to check
if OPTS.netlist_only:
@ -89,6 +94,8 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
def DRC(self, final_verification=False):
"""Checks DRC for a module"""
import verify
# Unit tests will check themselves.
# 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):
"""Checks LVS for a module"""
import verify
# Unit tests will check themselves.
# 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
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()
try:
self.name_dict

View File

@ -5,7 +5,6 @@
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
import collections
import geometry
import gdsMill
import debug
@ -14,6 +13,7 @@ from tech import drc, GDS
from tech import layer as techlayer
from tech import layer_indices
from tech import layer_stacks
from tech import preferred_directions
import os
from globals import OPTS
from vector import vector
@ -42,27 +42,30 @@ class layout():
self.visited = [] # List of modules we have already visited
self.is_library_cell = False # Flag for library cells
self.gds_read()
try:
from tech import power_grid
self.pwr_grid_layer = power_grid[0]
except ImportError:
self.pwr_grid_layer = "m3"
############################################################
# GDS layout
############################################################
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()
self.translate_all(offset)
return offset
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
in the stack from 0..n
"""
@ -120,6 +123,7 @@ class layout():
highesty2 = max(inst.uy() for inst in self.insts)
else:
highestx2 = highesty2 = None
if highestx1 == None and highestx2 == None:
return None
elif highestx1 == None:
@ -188,7 +192,7 @@ class layout():
inst.offset = vector(inst.offset - offset)
# The instances have a precomputed boundary that we need to update.
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():
# All the pins are absolute coordinates that need to be updated.
pin_list = self.pin_map[pin_name]
@ -219,8 +223,6 @@ class layout():
if not height:
height = drc["minwidth_{}".format(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,
offset,
width,
@ -244,27 +246,46 @@ class layout():
height))
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
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:
debug.error("Nonrectilinear center rect!", -1)
elif start.x != end.x:
offset = vector(0, 0.5 * minwidth_layer)
offset = vector(0, 0.5 * width)
return self.add_rect(layer,
start - offset,
end.x - start.x,
minwidth_layer)
width)
else:
offset = vector(0.5 * minwidth_layer, 0)
offset = vector(0.5 * width, 0)
return self.add_rect(layer,
start - offset,
minwidth_layer,
width,
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):
"""
Return the pin or list of pins
@ -322,7 +343,7 @@ class layout():
for pin_name in self.pin_map.keys():
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
"""
@ -331,27 +352,27 @@ class layout():
self.gds_write(file_name)
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
width = max(start.x, end.x) - min(start.x, end.x)
height = max(start.y, end.y) - min(start.y, end.y)
bbox_width = max(start.x, end.x) - min(start.x, end.x)
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))
# Shift it down 1/2 a width in the 0 dimension
if height == 0:
ll_offset -= vector(0, 0.5 * minwidth_layer)
if width == 0:
ll_offset -= vector(0.5 * minwidth_layer, 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)
if bbox_height == 0:
ll_offset -= vector(0, 0.5 * layer_width)
if bbox_width == 0:
ll_offset -= vector(0.5 * layer_width, 0)
return self.add_layout_pin(text,
layer,
ll_offset,
width,
height)
return self.add_layout_pin(text=text,
layer=layer,
offset=ll_offset,
width=bbox_width,
height=bbox_height)
def add_layout_pin_rect_center(self, text, layer, offset, width=None, height=None):
""" Creates a path like pin with center-line convention """
@ -448,24 +469,31 @@ class layout():
path=coordinates,
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.
If layer is a single value, it is a path.
If layer is a tuple, it is a wire with preferred directions.
"""
neg_offset = 1.0 - var_offset
# vertical first
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)
# horizontal first
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)
else:
debug.error("Invalid direction for jog -- must be H or V.")
if layer in layer_stacks:
self.add_wire(layer, [start, mid1, mid2, end])
elif layer in techlayer:
@ -480,7 +508,7 @@ class layout():
mid1 = vector(0.5 * start.x + 0.5 * end.x, start.y)
mid2 = vector(mid1, end.y)
self.add_path(layer, [start, mid1, mid2, end])
def add_wire(self, layers, coordinates, widen_short_wires=True):
"""Connects a routing path on given layer,coordinates,width.
The layers are the (horizontal, via, vertical). """
@ -494,7 +522,6 @@ class layout():
def get_preferred_direction(self, layer):
""" Return the preferred routing directions """
from tech import preferred_directions
return preferred_directions[layer]
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([])
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,
offset,
from_layer,
@ -567,60 +576,74 @@ class layout():
implant_type=None,
well_type=None):
"""
Punch a stack of vias from a start layer to a target layer by the center
coordinate accounting for mirroring and rotation.
"""
return self.__add_via_stack_internal(offset=offset,
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.
Punch a stack of vias from a start layer to a target layer by the center.
"""
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]
to_id = layer_indices[to_layer]
via = None
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
search_id = 0
next_id = 2
else: # grow the stack down
search_id = 2
next_id = 0
if from_id < to_id: # grow the stack up
search_id = 0
next_id = 2
else: # grow the stack down
search_id = 2
next_id = 0
curr_stack = next(filter(lambda stack: stack[search_id] == from_layer, layer_stacks), None)
if curr_stack is None:
raise ValueError("Cannot create via from '{0}' to '{1}'."
"Layer '{0}' not defined".format(from_layer, to_layer))
curr_stack = next(filter(lambda stack: stack[search_id] == cur_layer, layer_stacks), None)
via = self.add_via_center(layers=curr_stack,
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
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"):
"""Adds a ptx module to the design."""
import ptx
@ -688,12 +711,19 @@ class layout():
# we should add a boundary just for DRC in some technologies
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.
boundary_layers = []
if "boundary" in techlayer.keys():
boundary_layers.append("boundary")
if "stdc" in techlayer.keys():
boundary_layer = "stdc"
boundary = [self.find_lowest_coords(),
self.find_highest_coords()]
height = boundary[1][1] - boundary[0][1]
width = boundary[1][0] - boundary[0][0]
boundary_layers.append("stdc")
boundary = [self.find_lowest_coords(),
self.find_highest_coords()]
debug.check(boundary[0] and boundary[1], "No shapes to make a boundary.")
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]
gds_layout.addBox(layerNumber=layer_number,
purposeNumber=layer_purpose,
@ -701,7 +731,7 @@ class layout():
width=width,
height=height,
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)
@ -827,51 +857,57 @@ class layout():
if not pitch:
pitch = getattr(self, "{}_pitch".format(layer))
line_positions = {}
pins = {}
if vertical:
for i in range(len(names)):
line_offset = offset + vector(i * pitch,
0)
if make_pins:
self.add_layout_pin(text=names[i],
layer=layer,
offset=line_offset,
height=length)
new_pin = self.add_layout_pin(text=names[i],
layer=layer,
offset=line_offset,
height=length)
else:
self.add_rect(layer=layer,
offset=line_offset,
height=length)
line_positions[names[i]] = line_offset + vector(half_minwidth, 0)
rect = self.add_rect(layer=layer,
offset=line_offset,
height=length)
new_pin = pin_layout(names[i],
[rect.ll(), rect.ur()],
layer)
pins[names[i]] = new_pin
else:
for i in range(len(names)):
line_offset = offset + vector(0,
i * pitch + half_minwidth)
if make_pins:
self.add_layout_pin(text=names[i],
layer=layer,
offset=line_offset,
width=length)
new_pin = self.add_layout_pin(text=names[i],
layer=layer,
offset=line_offset,
width=length)
else:
self.add_rect(layer=layer,
offset=line_offset,
width=length)
# Make this the center of the rail
line_positions[names[i]] = line_offset + vector(0.5 * length,
half_minwidth)
rect = self.add_rect(layer=layer,
offset=line_offset,
width=length)
new_pin = pin_layout(names[i],
[rect.ll(), rect.ur()],
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")):
""" 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")):
""" 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
replaced with a channel router in the future.
@ -881,13 +917,15 @@ class layout():
(horizontal_layer, via_layer, vertical_layer) = layer_stack
if horizontal:
route_layer = vertical_layer
bys_layer = horizontal_layer
else:
route_layer = horizontal_layer
bus_layer = vertical_layer
for (pin_name, bus_name) in mapping:
pin = inst.get_pin(pin_name)
pin_pos = pin.center()
bus_pos = bus_offsets[bus_name]
bus_pos = bus_pins[bus_name].center()
if horizontal:
# up/down then left/right
@ -904,17 +942,18 @@ class layout():
# Connect to the pin on the instances with a via if it is
# not on the right layer
if pin.layer != route_layer:
self.add_via_center(layers=layer_stack,
offset=pin_pos)
self.add_via_stack_center(from_layer=pin.layer,
to_layer=route_layer,
offset=pin_pos)
# FIXME: output pins tend to not be rotate,
# but supply pins are. Make consistent?
# We only need a via if they happened to align perfectly
# 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):
self.add_via_center(layers=layer_stack,
offset=bus_pos,
rotate=90)
self.add_via_stack_center(from_layer=route_layer,
to_layer=bus_layer,
offset=bus_pos)
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,
to_layer=dest_pin.layer,
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):
"""
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):
"""
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):
""" Add boundary for debugging dimensions """
if OPTS.netlist_only:
return
boundary_layers = []
if "stdc" in techlayer.keys():
boundary_layer = "stdc"
else:
boundary_layer = "boundary"
if not ur:
self.bounding_box = self.add_rect(layer=boundary_layer,
offset=ll,
height=self.height,
width=self.width)
else:
self.bounding_box = self.add_rect(layer=boundary_layer,
offset=ll,
height=ur.y - ll.y,
width=ur.x - ll.x)
boundary_layers.append("stdc")
if "boundary" in techlayer.keys():
boundary_layers.append("boundary")
# Save the last one as self.bounding_box
for boundary_layer in boundary_layers:
if not ur:
self.bounding_box = self.add_rect(layer=boundary_layer,
offset=ll,
height=self.height,
width=self.width)
else:
self.bounding_box = self.add_rect(layer=boundary_layer,
offset=ll,
height=ur.y - ll.y,
width=ur.x - ll.x)
def add_enclosure(self, insts, layer="nwell"):
""" Add a layer that surrounds the given instances. Useful
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
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()
ymin = insts[0].by()
xmax = insts[0].rx()
ymax = insts[0].uy()
for inst in insts:
xmin = min(xmin, inst.lx())
ymin = min(ymin, inst.by())
xmax = max(xmax, inst.rx())
ymax = max(ymax, inst.uy())
if leftx != None:
xmin = leftx
else:
xmin = insts[0].lx()
for inst in insts:
xmin = min(xmin, inst.lx())
xmin = xmin - extend
if boty != None:
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,
offset=vector(xmin, ymin),
width=xmax - xmin,
height=ymax - ymin)
def copy_power_pins(self, inst, name):
rect = self.add_rect(layer=layer,
offset=vector(xmin, ymin),
width=xmax - xmin,
height=ymax - ymin)
return rect
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.
If it is on M1, it will add a power via too.
@ -1292,12 +1109,9 @@ class layout():
pin.ll(),
pin.width(),
pin.height())
elif pin.layer == "m1":
self.add_power_pin(name, pin.center())
else:
debug.warning("{0} pins of {1} should be on {2} or metal1 for "\
"supply router."
.format(name, inst.name, self.pwr_grid_layer))
elif add_vias:
self.add_power_pin(name, pin.center(), start_layer=pin.layer)
def add_power_pin(self, name, loc, size=[1, 1], directions=None, start_layer="m1"):
"""
@ -1306,20 +1120,21 @@ class layout():
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:
self.add_layout_pin_rect_center(text=name,
layer=self.pwr_grid_layer,
offset=loc)
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
if OPTS.tech_name == "s8":
if OPTS.tech_name == "sky130":
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:
width = via.width
height = via.height
@ -1329,6 +1144,42 @@ class layout():
width=width,
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):
"""
Create vdd and gnd power rings around an area of the bounding box

View File

@ -10,9 +10,9 @@ import re
import os
import math
import tech
from delay_data import *
from wire_spice_model import *
from power_data import *
from delay_data import delay_data
from wire_spice_model import wire_spice_model
from power_data import power_data
import logical_effort
@ -40,6 +40,8 @@ class spice():
# THE CONNECTIONS MUST MATCH THE ORDER OF THE PINS (restriction imposed by the
# Spice format)
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
try:
self.commments
@ -208,11 +210,12 @@ class spice():
# parses line into ports and remove subckt
self.pins = subckt_line.split(" ")[2:]
else:
debug.info(4, "no spfile {0}".format(self.sp_file))
self.spice = []
# We don't define self.lvs and will use self.spice if dynamically created
# 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))
f = open(self.lvs_file)
self.lvs = f.readlines()
@ -262,7 +265,12 @@ class spice():
Recursive spice subcircuit write;
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
for i in self.mods:
if self.contains(i, usedMODS):
@ -299,6 +307,9 @@ class spice():
# these are wires and paths
if self.conns[i] == []:
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"):
sp.write(self.insts[i].mod.lvs_device.format(self.insts[i].name,
" ".join(self.conns[i])))
@ -315,7 +326,7 @@ class spice():
sp.write(".ENDS {0}\n".format(self.name))
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.
# if os.path.isfile(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)
# 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)
abs_delay = stage_effort.get_absolute_delay()

View File

@ -8,7 +8,7 @@
import debug
from tech import GDS, drc
from vector import vector
from tech import layer
from tech import layer, layer_indices
import math
@ -31,12 +31,15 @@ class pin_layout:
debug.check(self.width() > 0, "Zero width 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 type(layer_name_pp) == str:
self._layer = layer_name_pp
# else it is required to be a lpp
else:
for (layer_name, lpp) in layer.items():
for (layer_name, lpp) in valid_layers.items():
if not lpp:
continue
if self.same_lpp(layer_name_pp, lpp):
@ -367,8 +370,17 @@ class pin_layout:
+ str(self.width()) + "x"
+ str(self.height()) + " @ " + str(self.ll()))
(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,
purposeNumber=purpose,
purposeNumber=pin_purpose,
offsetInMicrons=self.ll(),
width=self.width(),
height=self.height(),
@ -378,7 +390,7 @@ class pin_layout:
# imported into Magic.
newLayout.addText(text=self.name,
layerNumber=layer_num,
purposeNumber=purpose,
purposeNumber=label_purpose,
offsetInMicrons=self.center(),
magnification=GDS["zoom"],
rotate=None)

View File

@ -7,10 +7,10 @@
#
import debug
import utils
from tech import GDS, layer, parameter
from tech import GDS, layer
from tech import cell_properties as props
import bitcell_base
from globals import OPTS
class bitcell(bitcell_base.bitcell_base):
"""
@ -50,6 +50,8 @@ class bitcell(bitcell_base.bitcell_base):
self.pin_map = bitcell.pin_map
self.add_pin_types(self.type_list)
self.nets_match = self.do_nets_exist(self.storage_nets)
debug.check(OPTS.tech_name != "sky130", "sky130 does not yet support single port cells")
def get_all_wl_names(self):
""" 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.num_rows,
self.num_cols,
self.word_size)
self.word_size,
self.num_spare_rows)
self.trimsp.trim(self.probe_address,self.probe_data)
else:
# The non-reduced netlist file when it is disabled

View File

@ -5,23 +5,18 @@
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
import sys,re,shutil
import copy
import collections
from design import design
import debug
import math
import tech
import random
from .stimuli import *
from .charutils import *
import utils
from globals import OPTS
from .simulation import simulation
from .delay import delay
# from .delay import delay
import graph_util
from sram_factory import factory
class functional(simulation):
"""
Functions to write random data values to a random address then read them back and check
@ -40,6 +35,9 @@ class functional(simulation):
else:
self.num_wmasks = 0
if not self.num_spare_cols:
self.num_spare_cols = 0
self.set_corner(corner)
self.set_spice_constants()
self.set_stimulus_variables()
@ -57,7 +55,6 @@ class functional(simulation):
self.read_check = []
self.read_results = []
def run(self, feasible_period=None):
if feasible_period: #period defaults to tech.py feasible period otherwise.
self.period = feasible_period
@ -82,10 +79,11 @@ class functional(simulation):
for port in self.all_ports:
checks = []
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:
checks.append((self.data_value[port],"data"))
checks.append((self.wmask_value[port],"wmask"))
checks.append((self.data_value[port], "data"))
checks.append((self.wmask_value[port], "wmask"))
checks.append((self.spare_wen_value[port], "spare_wen"))
for (val, name) in checks:
debug.check(len(self.cycle_times)==len(val),
@ -104,15 +102,15 @@ class functional(simulation):
r_ops = ["noop", "read"]
# 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)
# 1. Write all the write ports first to seed a bunch of locations.
for port in self.write_ports:
addr = self.gen_addr()
word = self.gen_data()
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)
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.stored_words[addr] = word
# All other read-only ports are noops.
@ -131,7 +129,7 @@ class functional(simulation):
if port in self.write_ports:
self.add_noop_one_port(port)
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_check(word, port)
self.cycle_times.append(self.t_current)
@ -160,13 +158,13 @@ class functional(simulation):
self.add_noop_one_port(port)
else:
word = self.gen_data()
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)
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.stored_words[addr] = word
w_addrs.append(addr)
elif op == "partial_write":
# 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
if addr in w_addrs:
self.add_noop_one_port(port)
@ -179,7 +177,7 @@ class functional(simulation):
self.stored_words[addr] = new_word
w_addrs.append(addr)
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
# 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
@ -187,7 +185,7 @@ class functional(simulation):
if addr in w_addrs:
self.add_noop_one_port(port)
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_check(word, port)
@ -195,7 +193,7 @@ class functional(simulation):
self.t_current += self.period
# 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)
def gen_masked_data(self, old_word, word, wmask):
@ -209,7 +207,7 @@ class functional(simulation):
if wmask[bit] == "0":
lower = bit * self.write_size
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
@ -219,15 +217,15 @@ class functional(simulation):
self.check
except:
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
def read_stim_results(self):
# Extract dout values from spice timing.lis
for (word, dout_port, eo_period, check) in self.read_check:
sp_read_value = ""
for bit in range(self.word_size):
value = parse_spice_list("timing", "v{0}.{1}ck{2}".format(dout_port.lower(),bit,check))
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))
if value > self.v_high:
sp_read_value = "1" + sp_read_value
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.
return wmask[::-1]
def gen_data(self):
""" Generates a random word to write. """
random_value = random.randint(0,(2**self.word_size)-1)
data_bits = self.convert_to_bin(random_value,False)
if not self.num_spare_cols:
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
def gen_addr(self):
""" Generates a random address value to write to. """
random_value = random.randint(0,(2**self.addr_size)-1)
addr_bits = self.convert_to_bin(random_value,True)
if self.num_spare_rows==0:
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
def get_data(self):
@ -296,37 +301,36 @@ class functional(simulation):
# Used for write masks since they should be writing to previously written addresses
addr = random.choice(list(self.stored_words.keys()))
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. """
new_value = str.replace(bin(value),"0b","")
new_value = str.replace(bin(value), "0b", "")
if(is_addr):
expected_value = self.addr_size
else:
expected_value = self.word_size
for i in range (expected_value - len(new_value)):
expected_value = self.word_size + self.num_spare_cols
for i in range(expected_value - len(new_value)):
new_value = "0" + new_value
#print("Binary Conversion: {} to {}".format(value, new_value))
return new_value
# print("Binary Conversion: {} to {}".format(value, new_value))
return new_value
def write_functional_stimulus(self):
""" Writes SPICE stimulus. """
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.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)
#Write Vdd/Gnd statements
# Write Vdd/Gnd statements
self.sf.write("\n* Global Power Supplies\n")
self.stim.write_supply()
#Instantiate the SRAM
# Instantiate the SRAM
self.sf.write("\n* Instantiation of the SRAM\n")
self.stim.inst_model(pins=self.pins,
model_name=self.sram.name)
@ -334,7 +338,7 @@ class functional(simulation):
# Add load capacitance to each of the read ports
self.sf.write("\n* SRAM output loads\n")
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)
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:
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")
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)
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
self.sf.write("\n * Generation of control signals\n")
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:
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
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.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
for port in self.all_ports:
self.stim.gen_pulse(sig_name="{0}{1}".format("clk", port),
@ -396,11 +409,11 @@ class functional(simulation):
# Generate dout value measurements
self.sf.write("\n * Generation of dout measurements\n")
for (word, dout_port, eo_period, check) in self.read_check:
t_intital = eo_period - 0.01*self.period
t_final = eo_period + 0.01*self.period
for bit in range(self.word_size):
self.stim.gen_meas_value(meas_name="V{0}_{1}ck{2}".format(dout_port,bit,check),
dout="{0}_{1}".format(dout_port,bit),
t_intital = eo_period - 0.01 * self.period
t_final = eo_period + 0.01 * self.period
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),
dout="{0}_{1}".format(dout_port, bit),
t_intital=t_intital,
t_final=t_final)
@ -430,7 +443,7 @@ class functional(simulation):
# Generate new graph every analysis as edges might change depending on test bit
self.graph = graph_util.timing_graph()
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
def set_internal_spice_names(self):
@ -438,17 +451,17 @@ class functional(simulation):
# For now, only testing these using first read port.
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())
self.sen_name = self.get_sen_name(self.graph.all_paths)
debug.info(2,"s_en name = {}".format(self.sen_name))
self.sen_name = self.get_sen_name(self.graph.all_paths)
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)
debug.info(2,"bl name={}, br name={}".format(self.bl_name,self.br_name))
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))
self.q_name,self.qbar_name = self.get_bit_name()
debug.info(2,"q name={}\nqbar name={}".format(self.q_name,self.qbar_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))
def get_bit_name(self):
""" Get a bit cell name """
@ -456,10 +469,10 @@ class functional(simulation):
storage_names = cell_inst.mod.get_storage_net_names()
debug.check(len(storage_names) == 2, ("Only inverting/non-inverting storage nodes"
"supported for characterization. Storage nets={}").format(storage_names))
q_name = cell_name+'.'+str(storage_names[0])
qbar_name = cell_name+'.'+str(storage_names[1])
q_name = cell_name + '.' + str(storage_names[0])
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
def get_sen_name(self, paths):
@ -469,29 +482,28 @@ class functional(simulation):
"""
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.
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()
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
def get_bl_name(self, paths, port):
"""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_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.
bl_names = []
exclude_set = self.get_bl_name_search_exclusions()
for int_net in [cell_bl, cell_br]:
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):
"""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
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
"""
@ -510,14 +522,14 @@ class functional(simulation):
for path in paths:
aliases = self.sram.find_aliases(self.sram_spc_name, self.pins, path, int_net, mod, exclusion_set)
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:
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:
path_net_name = aliases[0]
net_found = True
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)
self.char_sram_results, self.char_port_results = char_results
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
char_results = self.d.analyze(probe_address, probe_data, self.slews, self.loads)
self.char_sram_results, self.char_port_results = char_results
@ -654,8 +657,12 @@ class lib:
))
# information of checks
(drc_errors, lvs_errors) = self.sram.DRC_LVS(final_verification=True)
datasheet.write("{0},{1},".format(drc_errors, lvs_errors))
# run it only the first time
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
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.addr_size = self.sram.addr_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.all_ports = self.sram.all_ports
self.readwrite_ports = self.sram.readwrite_ports
self.read_ports = self.sram.read_ports
self.write_ports = self.sram.write_ports
self.words_per_row = self.sram.words_per_row
if self.write_size:
self.num_wmasks = int(self.word_size/self.write_size)
else:
self.num_wmasks = 0
def set_corner(self,corner):
""" Set the corner values """
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),
port_info=(len(self.all_ports),self.write_ports,self.read_ports),
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),
"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))
#This is TODO once multiport control has been finalized.
#self.control_name = "CSB"
@ -80,11 +85,13 @@ class simulation():
self.addr_value = {port:[] for port in self.all_ports}
self.data_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
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.spare_wen_values = {port:[[] for bit in range(self.num_spare_cols)] for port in self.write_ports}
# For generating comments in SPICE stimulus
self.cycle_comments = []
@ -111,10 +118,10 @@ class simulation():
def add_data(self, data, port):
""" 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)
bit = self.word_size - 1
bit = self.word_size + self.num_spare_cols - 1
for c in data:
if c=="0":
self.data_values[port][bit].append(0)
@ -124,7 +131,6 @@ class simulation():
debug.error("Non-binary data string",1)
bit -= 1
def add_address(self, address, port):
""" Add the array of address values """
debug.check(len(address)==self.addr_size, "Invalid address size.")
@ -135,7 +141,7 @@ class simulation():
if c=="0":
self.addr_values[port][bit].append(0)
elif c=="1":
self.addr_values[port][bit].append(1)
self.addr_values[port][bit].append(1)
else:
debug.error("Non-binary address string",1)
bit -= 1
@ -156,7 +162,21 @@ class simulation():
debug.error("Non-binary wmask string", 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):
""" Add the control values for a write cycle. """
debug.check(port in self.write_ports,
@ -172,7 +192,8 @@ class simulation():
self.add_control_one_port(port, "write")
self.add_data(data,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.
for unselected_port in self.all_ports:
@ -191,19 +212,20 @@ class simulation():
self.cycle_times.append(self.t_current)
self.t_current += self.period
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
# the same value as previous cycle
if port in self.write_ports:
try:
self.add_data(self.data_value[port][-1], port)
except:
self.add_data("0"*self.word_size, port)
self.add_data("0"*(self.word_size + self.num_spare_cols), port)
try:
self.add_wmask(self.wmask_value[port][-1], port)
except:
self.add_wmask("0"*self.num_wmasks, port)
self.add_spare_wen("0" * self.num_spare_cols, port)
#Add noops to all other ports.
for unselected_port in self.all_ports:
@ -234,6 +256,7 @@ class simulation():
self.add_data(data, port)
self.add_address(address, 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):
""" 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_address(address, port)
# If the port is also a readwrite then add
# the same value as previous cycle
if port in self.write_ports:
try:
self.add_data(self.data_value[port][-1], port)
except:
self.add_data("0"*self.word_size, port)
self.add_data("0"*(self.word_size + self.num_spare_cols), port)
try:
self.add_wmask(self.wmask_value[port][-1], port)
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):
""" Add the control values for a noop to a single port. Does not increment the period. """
self.add_control_one_port(port, "noop")
try:
self.add_address(self.addr_value[port][-1], port)
except:
@ -273,12 +297,13 @@ class simulation():
try:
self.add_data(self.data_value[port][-1], port)
except:
self.add_data("0"*self.word_size, port)
self.add_data("0"*(self.word_size + self.num_spare_cols), port)
try:
self.add_wmask(self.wmask_value[port][-1], port)
except:
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):
""" Add the control values for a noop to a single port. Increments the period. """
debug.info(2, 'Clock only on port {}'.format(port))
@ -369,6 +394,11 @@ class simulation():
for port in write_index:
for bit in range(self.num_wmasks):
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 i in range(dbits):

View File

@ -254,7 +254,7 @@ class stimuli():
includes = self.device_models + [circuit]
self.sf.write("* {} process corner\n".format(self.process))
if OPTS.tech_name == "s8":
if OPTS.tech_name == "sky130":
libraries = self.device_libraries
for item in list(libraries):
if os.path.isfile(item[0]):
@ -302,7 +302,9 @@ class stimuli():
OPTS.openram_temp)
valid_retcode=0
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,
temp_stim,
OPTS.openram_temp)

View File

@ -6,7 +6,7 @@
# All rights reserved.
#
import debug
from math import log
from math import log,ceil
import re
class trim_spice():
@ -42,7 +42,7 @@ class trim_spice():
self.word_size = 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.bank_addr_size = self.col_addr_size + self.row_addr_size
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.
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."""
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(
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
pdb.set_trace()
assert return_value == 0

View File

@ -52,11 +52,12 @@ import sram
class fake_sram(sram.sram):
""" This is an SRAM that doesn't actually create itself, just computes
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.word_size = word_size
self.num_words = num_words
self.num_banks = num_banks
self.num_spare_rows = num_spare_rows
c = reload(__import__(OPTS.bitcell))
self.mod_bitcell = getattr(c, OPTS.bitcell)
self.bitcell = self.mod_bitcell()
@ -75,7 +76,10 @@ d.period = period
# Set the load of outputs and slew of inputs
d.set_load_slew(load,slew)
# 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
d.set_probe(probe_address, probe_data)

View File

@ -99,6 +99,9 @@ def parse_args():
# Alias SCMOS to 180nm
if OPTS.tech_name == "scmos":
OPTS.tech_name = "scn4m_subm"
# Alias s8 to sky130
if OPTS.tech_name == "s8":
OPTS.tech_name = "sky130"
return (options, args)
@ -214,25 +217,18 @@ def setup_bitcell():
if OPTS.num_r_ports > 0:
ports += "{}r".format(OPTS.num_r_ports)
OPTS.bitcell = "bitcell_"+ports
OPTS.replica_bitcell = "replica_bitcell_"+ports
OPTS.dummy_bitcell = "dummy_bitcell_"+ports
else:
OPTS.replica_bitcell = "replica_" + OPTS.bitcell
OPTS.replica_bitcell = "dummy_" + OPTS.bitcell
if ports != "":
OPTS.bitcell_suffix = "_" + ports
OPTS.bitcell = "bitcell" + OPTS.bitcell_suffix
# See if bitcell exists
try:
__import__(OPTS.bitcell)
__import__(OPTS.replica_bitcell)
__import__(OPTS.dummy_bitcell)
except ImportError:
# Use the pbitcell if we couldn't find a custom bitcell
# or its custom replica bitcell
# Use the pbitcell (and give a warning if not in unit test mode)
OPTS.bitcell = "pbitcell"
OPTS.replica_bitcell = "replica_pbitcell"
OPTS.replica_bitcell = "dummy_pbitcell"
if not OPTS.is_unit_test:
debug.warning("Using the parameterized bitcell which may have suboptimal density.")
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))
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))
# 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.
if OPTS.write_size is not None:

View File

@ -8,8 +8,8 @@
import debug
import design
from sram_factory import factory
from math import log
from tech import drc
from math import log, ceil
from tech import drc, layer
from vector import vector
from globals import OPTS
@ -31,6 +31,9 @@ class bank(design.design):
else:
self.num_wmasks = 0
if not self.num_spare_cols:
self.num_spare_cols = 0
if name == "":
name = "bank_{0}_{1}".format(self.word_size, self.num_words)
design.design.__init__(self, name)
@ -77,12 +80,12 @@ class bank(design.design):
def add_pins(self):
""" Adding pins for Bank module"""
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")
for port in self.all_ports:
self.add_pin(self.bitcell_array.get_rbl_bl_name(self.port_rbl_map[port]), "OUTPUT")
for port in self.write_ports:
for bit in range(self.word_size):
for bit in range(self.word_size + self.num_spare_cols):
self.add_pin("din{0}_{1}".format(port, bit), "INPUT")
for port in self.all_ports:
for bit in range(self.addr_size):
@ -101,6 +104,8 @@ class bank(design.design):
self.add_pin("w_en{0}".format(port), "INPUT")
for bit in range(self.num_wmasks):
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:
self.add_pin("wl_en{0}".format(port), "INPUT")
self.add_pin("vdd", "POWER")
@ -123,23 +128,25 @@ class bank(design.design):
def route_rbl(self, port):
""" Route the rbl_bl and rbl_wl """
bl_pin_name = self.bitcell_array.get_rbl_bl_name(self.port_rbl_map[port])
bl_pin = self.bitcell_array_inst.get_pin(bl_pin_name)
# This will ensure the pin is only on the top or bottom edge
# Connect the rbl to the port data pin
bl_pin = self.port_data_inst[port].get_pin("rbl_bl")
if port % 2:
via_offset = bl_pin.uc() + vector(0, 1.5 * self.m2_pitch)
left_right_offset = vector(self.max_x_offset, via_offset.y)
pin_pos = bl_pin.uc()
pin_offset = pin_pos + vector(0, self.m3_pitch)
left_right_offset = vector(self.max_x_offset, pin_offset.y)
else:
via_offset = bl_pin.bc() - vector(0, 1.5 * self.m2_pitch)
left_right_offset = vector(self.min_x_offset, via_offset.y)
pin_pos = bl_pin.bc()
pin_offset = pin_pos - vector(0, self.m3_pitch)
left_right_offset = vector(self.min_x_offset, pin_offset.y)
self.add_via_stack_center(from_layer=bl_pin.layer,
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),
layer="m3",
start=left_right_offset,
end=via_offset)
end=pin_offset)
def route_bitlines(self, port):
""" 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
# 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/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
if self.col_addr_size > 0:
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:
y_offset = 0
self.column_decoder_offsets[port] = vector(-x_offset, -y_offset)
@ -254,10 +262,14 @@ class bank(design.design):
# UPPER RIGHT QUADRANT
# Place the col decoder right aligned with wordline driver
# 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
if self.col_addr_size > 0:
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:
y_offset = self.bitcell_array_top
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 """
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.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.")
debug.check(self.addr_size==self.col_addr_size + self.row_addr_size,
"Invalid address break down.")
@ -303,7 +316,7 @@ class bank(design.design):
self.input_control_signals = []
port_num = 0
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
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)])
@ -316,7 +329,7 @@ class bank(design.design):
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
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.
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]])
else:
self.control_signals.append(self.input_control_signals[port])
# The central bus is the column address (one hot) and row address (binary)
if self.col_addr_size>0:
@ -356,7 +370,7 @@ class bank(design.design):
self.add_mod(self.port_data[port])
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)
self.add_mod(self.port_address)
@ -364,7 +378,7 @@ class bank(design.design):
self.num_rbl = len(self.all_ports)
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,
left_rbl=1,
right_rbl=1 if len(self.all_ports)>1 else 0,
@ -382,7 +396,7 @@ class bank(design.design):
mod=self.bitcell_array)
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:
temp.append("{0}_{1}".format(bitline, col))
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])
temp.append(rbl_bl_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.br_names[port], col))
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))
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))
# 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)]
@ -435,6 +449,8 @@ class bank(design.design):
temp.append("w_en{0}".format(port))
for bit in range(self.num_wmasks):
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"])
self.connect_inst(temp)
@ -490,18 +506,20 @@ class bank(design.design):
Create a 2:4 or 3:8 column address decoder.
"""
# Height is a multiple of DFF so that it can be staggered
# and rows do not align with the control logic module
self.dff = factory.create(module_type="dff")
self.dff =factory.create(module_type="dff")
if self.col_addr_size == 0:
return
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:
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:
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:
# No error checking before?
debug.error("Invalid column decoder?", -1)
@ -569,9 +587,23 @@ class bank(design.design):
def route_supplies(self):
""" 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:
self.copy_power_pins(inst, "vdd")
self.copy_power_pins(inst, "gnd")
self.copy_power_pins(inst, "vdd", add_vias=False)
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):
""" Route the bank select logic. """
@ -594,7 +626,7 @@ class bank(design.design):
# Connect the inverter output to the central bus
out_pos = self.bank_select_inst[port].get_pin(gated_bank_sel_signals[signal]).rc()
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_via_center(layers=self.m2_stack,
offset=bus_pos)
@ -629,19 +661,20 @@ class bank(design.design):
# Overall central bus width. It includes all the column mux lines,
# and control lines.
self.bus_xoffset = [None] * len(self.all_ports)
self.bus_pins = [None] * len(self.all_ports)
# Port 0
# The bank is at (0,0), so this is to the left of the y-axis.
# 2 pitches on the right for vias/jogs to access the inputs
control_bus_offset = vector(-self.m3_pitch * self.num_control_lines[0] - self.m3_pitch, self.min_y_offset)
# The control bus is routed up to two pitches below the bitcell array
control_bus_length = self.main_bitcell_array_bottom - self.min_y_offset - 2 * self.m1_pitch
self.bus_xoffset[0] = self.create_bus(layer="m2",
offset=control_bus_offset,
names=self.control_signals[0],
length=control_bus_length,
vertical=True,
make_pins=(self.num_banks==1))
self.bus_pins[0] = self.create_bus(layer="m2",
offset=control_bus_offset,
names=self.control_signals[0],
length=control_bus_length,
vertical=True,
make_pins=(self.num_banks==1),
pitch=self.m3_pitch)
# Port 1
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,
self.max_y_offset - control_bus_length)
# The bus for the right port is reversed so that the rbl_wl is closest to the array
self.bus_xoffset[1] = self.create_bus(layer="m2",
offset=control_bus_offset,
names=list(reversed(self.control_signals[1])),
length=control_bus_length,
vertical=True,
make_pins=(self.num_banks==1))
self.bus_pins[1] = self.create_bus(layer="m2",
offset=control_bus_offset,
names=list(reversed(self.control_signals[1])),
length=control_bus_length,
vertical=True,
make_pins=(self.num_banks==1),
pitch=self.m3_pitch)
def route_port_data_to_bitcell_array(self, port):
""" 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,
inst2_bl_name=inst2_bl_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
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):
""" 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))
self.add_layout_pin_rect_center(text="dout{0}_{1}".format(port, bit),
layer=data_pin.layer,
@ -707,16 +746,21 @@ class bank(design.design):
def route_port_data_in(self, port):
""" 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)
din_name = "din{0}_{1}".format(port, row)
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):
wmask_name = "bank_wmask_{}".format(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)
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,
inst1_bl_name="bl_{}", inst1_br_name="br_{}",
@ -799,28 +843,47 @@ class bank(design.design):
for row in range(self.num_rows):
# 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()
bitcell_wl_pos = self.bitcell_array_inst.get_pin(self.wl_names[port] + "_{}".format(row)).lc()
driver_wl_pin = self.port_address_inst[port].get_pin("wl_{}".format(row))
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)
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):
""" Connecting Wordline driver output to Bitcell WL connection """
for row in range(self.num_rows):
# 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()
bitcell_wl_pos = self.bitcell_array_inst.get_pin(self.wl_names[port] + "_{}".format(row)).rc()
driver_wl_pin = self.port_address_inst[port].get_pin("wl_{}".format(row))
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)
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):
""" Connecting the select lines of column mux to the address bus """
if not self.col_addr_size>0:
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:
# 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)
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:
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]
@ -852,7 +915,7 @@ class bank(design.design):
route_map = list(zip(decode_pins, column_mux_pins))
self.create_vertical_channel_route(route_map,
offset,
self.m1_stack)
stack)
def add_lvs_correspondence_points(self):
"""
@ -908,39 +971,32 @@ class bank(design.design):
# pre-decoder and this connection is in metal3
connection = []
connection.append((self.prefix + "p_en_bar{}".format(port),
self.port_data_inst[port].get_pin("p_en_bar").lc(),
self.port_data_inst[port].get_pin("p_en_bar").layer))
self.port_data_inst[port].get_pin("p_en_bar")))
rbl_wl_name = self.bitcell_array.get_rbl_wl_name(self.port_rbl_map[port])
connection.append((self.prefix + "wl_en{}".format(port),
self.bitcell_array_inst.get_pin(rbl_wl_name).lc(),
self.bitcell_array_inst.get_pin(rbl_wl_name).layer))
self.bitcell_array_inst.get_pin(rbl_wl_name)))
if port in self.write_ports:
if port % 2:
connection.append((self.prefix + "w_en{}".format(port),
self.port_data_inst[port].get_pin("w_en").rc(),
self.port_data_inst[port].get_pin("w_en").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))
connection.append((self.prefix + "w_en{}".format(port),
self.port_data_inst[port].get_pin("w_en")))
if port in self.read_ports:
connection.append((self.prefix + "s_en{}".format(port),
self.port_data_inst[port].get_pin("s_en").lc(),
self.port_data_inst[port].get_pin("s_en").layer))
self.port_data_inst[port].get_pin("s_en")))
for (control_signal, pin_pos, pin_layer) in connection:
if port==0:
y_offset = self.min_y_offset
else:
y_offset = self.max_y_offset
control_pos = vector(self.bus_xoffset[port][control_signal].x, y_offset)
if pin_layer == "m1":
self.add_wire(self.m1_stack, [control_pos, pin_pos])
elif pin_layer == "m3":
self.add_wire(self.m2_stack[::-1], [control_pos, pin_pos])
for (control_signal, pin) in connection:
control_pin = self.bus_pins[port][control_signal]
control_pos = vector(control_pin.cx(), pin.cy())
# If the y doesn't overlap the bus, add a segment
if pin.cy() < control_pin.by():
self.add_path("m2", [control_pos, control_pin.bc()])
elif pin.cy() > control_pin.uy():
self.add_path("m2", [control_pos, control_pin.uc()])
self.add_path(pin.layer, [control_pos, pin.center()])
self.add_via_stack_center(from_layer=pin.layer,
to_layer="m2",
offset=control_pos)
# clk to wordline_driver
control_signal = self.prefix + "wl_en{}".format(port)
@ -950,7 +1006,7 @@ class bank(design.design):
else:
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
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)
self.add_wire(self.m1_stack, [pin_pos, mid_pos, control_pos])
self.add_via_center(layers=self.m1_stack,

View File

@ -9,6 +9,7 @@ import debug
import design
from tech import cell_properties
class bitcell_base_array(design.design):
"""
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()
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()
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("gnd")
@ -85,39 +86,28 @@ class bitcell_base_array(design.design):
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),
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),
offset=bl_pin.ll().scale(1, 0),
width=bl_pin.width(),
height=self.height)
for row in range(self.row_size):
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),
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),
offset=wl_pin.ll().scale(0, 1),
width=self.width,
height=wl_pin.height())
# For non-square via stacks, vertical/horizontal direction refers to the stack orientation in 2d space
# Default uses prefered directions for each layer; this cell property is only currently used by s8 tech (03/20)
try:
bitcell_power_pin_directions = cell_properties.bitcell_power_pin_directions
except AttributeError:
bitcell_power_pin_directions = None
# Add vdd/gnd via stacks
# Copy a vdd/gnd layout pin from every cell
for row in range(self.row_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 in inst.get_pins(pin_name):
self.add_power_pin(name=pin_name,
loc=pin.center(),
directions=bitcell_power_pin_directions,
start_layer=pin.layer)
self.copy_layout_pin(inst, pin_name)
def _adjust_x_offset(self, xoffset, col, col_offset):
tempx = xoffset
@ -137,11 +127,10 @@ class bitcell_base_array(design.design):
dir_x = True
return (tempy, dir_x)
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
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):
@ -149,7 +138,6 @@ class bitcell_base_array(design.design):
tempx, dir_y = self._adjust_x_offset(xoffset, col, self.column_offset)
for row in range(self.row_size):
name = name_template.format(row, col)
tempy, dir_x = self._adjust_y_offset(yoffset, row, row_offset)
if dir_x and dir_y:
@ -161,7 +149,7 @@ class bitcell_base_array(design.design):
else:
dir_key = ""
self.cell_inst[row,col].place(offset=[tempx, tempy],
mirror=dir_key)
self.cell_inst[row, col].place(offset=[tempx, tempy],
mirror=dir_key)
yoffset += self.cell.height
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.
"""
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 """
name = "control_logic_" + port_type
design.design.__init__(self, name)
@ -35,7 +35,12 @@ class control_logic(design.design):
self.word_size = word_size
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.enable_delay_chain_resizing = False
@ -102,7 +107,7 @@ class control_logic(design.design):
# clk_buf drives a flop for every address
addr_flops = math.log(self.num_words, 2) + math.log(self.words_per_row, 2)
# 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
# plus about 5 fanouts for the control logic
clock_fanout = 5 * num_flops + 5
@ -130,7 +135,7 @@ class control_logic(design.design):
# s_en drives every sense amp
self.sen_and3 = factory.create(module_type="pand3",
size=self.word_size,
size=self.word_size + self.num_spare_cols,
height=dff_height)
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)
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":
self.internal_bus_list = ["rbl_bl_delay_bar", "rbl_bl_delay", "gated_clk_bar", "gated_clk_buf", "clk_buf", "cs_bar", "cs"]
else:
@ -384,10 +389,10 @@ class control_logic(design.design):
height = self.control_logic_center.y - self.m2_pitch
offset = vector(self.ctrl_dff_array.width, 0)
self.rail_offsets = self.create_vertical_bus("m2",
offset,
self.internal_bus_list,
height)
self.input_bus = self.create_vertical_bus("m2",
offset,
self.internal_bus_list,
height)
def create_instances(self):
""" Create all the instances """
@ -493,7 +498,7 @@ class control_logic(design.design):
# Connect to the rail level with the vdd rail
# Use pen since it is in every type of control logic
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)
self.add_wire(self.m1_stack, [out_pos, mid1, in_pos])
self.add_via_center(layers=self.m1_stack,
@ -518,12 +523,12 @@ class control_logic(design.design):
def route_clk_buf(self):
clk_pin = self.clk_buf_inst.get_pin("A")
clk_pos = clk_pin.center()
self.add_layout_pin_segment_center(text="clk",
layer="m2",
start=clk_pos,
end=clk_pos.scale(1, 0))
self.add_via_center(layers=self.m1_stack,
offset=clk_pos)
self.add_layout_pin_rect_center(text="clk",
layer="m2",
offset=clk_pos)
self.add_via_stack_center(from_layer=clk_pin.layer,
to_layer="m2",
offset=clk_pos)
self.route_output_to_bus_jogged(self.clk_buf_inst,
"clk_buf")
@ -548,17 +553,23 @@ class control_logic(design.design):
def route_gated_clk_bar(self):
clkbuf_map = zip(["A"], ["clk_buf"])
self.connect_vertical_bus(clkbuf_map, self.clk_bar_inst, self.rail_offsets)
self.connect_vertical_bus(clkbuf_map, self.clk_bar_inst, self.input_bus)
out_pos = self.clk_bar_inst.get_pin("Z").center()
in_pos = self.gated_clk_bar_inst.get_pin("A").center()
self.add_zjog("m1", out_pos, in_pos)
out_pin = self.clk_bar_inst.get_pin("Z")
out_pos = out_pin.center()
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
clkbuf_map = zip(["B"], ["cs"])
self.connect_vertical_bus(clkbuf_map,
self.gated_clk_bar_inst,
self.rail_offsets,
self.input_bus,
self.m2_stack[::-1])
# The pin is on M1, so we need another via as well
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"])
self.connect_vertical_bus(clkbuf_map,
self.gated_clk_buf_inst,
self.rail_offsets)
self.input_bus)
clkbuf_map = zip(["Z"], ["gated_clk_buf"])
self.connect_vertical_bus(clkbuf_map,
self.gated_clk_buf_inst,
self.rail_offsets,
self.input_bus,
self.m2_stack[::-1])
# The pin is on M1, so we need another via as well
z_pin = self.gated_clk_buf_inst.get_pin("Z")
@ -614,7 +625,7 @@ class control_logic(design.design):
def route_wlen(self):
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")
@ -639,7 +650,7 @@ class control_logic(design.design):
def route_pen(self):
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_pos = out_pin.center()
@ -682,7 +693,7 @@ class control_logic(design.design):
input_name = "cs"
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")
@ -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")
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):
@ -738,7 +749,7 @@ class control_logic(design.design):
input_name = "cs"
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")
@ -761,13 +772,13 @@ class control_logic(design.design):
dff_out_map = zip(["dout_bar_0", "dout_0"], ["cs", "cs_bar"])
else:
dff_out_map = zip(["dout_bar_0"], ["cs"])
self.connect_vertical_bus(dff_out_map, self.ctrl_dff_inst, self.rail_offsets, 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
# by routing in the supply rail track to avoid channel conflicts
in_pos = self.ctrl_dff_inst.get_pin("clk").uc()
mid_pos = in_pos + vector(0, 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_via_center(layers=self.m1_stack,
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. """
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,
layer="m1",
start=out_pin.center(),
layer="m2",
start=out_pos,
end=right_pos)
def route_supply(self):
""" 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])
for inst in self.row_end_inst:
pins = inst.get_pins("vdd")
for pin in pins:
if pin.layer == "m1":
if pin.layer == supply_layer:
row_loc = pin.rc()
pin_loc = vector(max_row_x_loc, pin.rc().y)
self.add_power_pin("vdd", pin_loc)
self.add_path("m1", [row_loc, pin_loc])
self.add_power_pin("vdd", pin_loc, start_layer=pin.layer)
self.add_path(supply_layer, [row_loc, pin_loc])
pins = inst.get_pins("gnd")
for pin in pins:
if pin.layer == "m1":
if pin.layer == supply_layer:
row_loc = pin.rc()
pin_loc = vector(max_row_x_loc, pin.rc().y)
self.add_power_pin("gnd", pin_loc)
self.add_path("m1", [row_loc, pin_loc])
self.add_power_pin("gnd", pin_loc, start_layer=pin.layer)
self.add_path(supply_layer, [row_loc, pin_loc])
self.copy_layout_pin(self.delay_inst, "gnd")
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):
# Connect this at the bottom of the buffer
out_pos = inst.get_pin("Z").center()
mid1 = vector(out_pos.x, out_pos.y - 0.25 * inst.mod.height)
mid2 = vector(self.rail_offsets[name].x, mid1.y)
bus_pos = self.rail_offsets[name]
out_pin = inst.get_pin("Z")
out_pos = out_pin.center()
mid1 = vector(out_pos.x, out_pos.y - 0.4 * inst.mod.height)
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])
# The pin is on M1, so we need another via as well
self.add_via_center(layers=self.m1_stack,
offset=out_pos)
self.add_via_stack_center(from_layer=out_pin.layer,
to_layer="m2",
offset=out_pos)

View File

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

View File

@ -7,12 +7,11 @@
#
import debug
import design
from tech import drc
from math import log
from vector import vector
from sram_factory import factory
from globals import OPTS
class dff_array(design.design):
"""
This is a simple row (or multiple rows) of flops.
@ -52,42 +51,41 @@ class dff_array(design.design):
self.add_mod(self.dff)
def add_pins(self):
for row in range(self.rows):
for row in range(self.rows):
for col in range(self.columns):
self.add_pin(self.get_din_name(row,col), "INPUT")
for row in range(self.rows):
self.add_pin(self.get_din_name(row, col), "INPUT")
for row in range(self.rows):
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("vdd", "POWER")
self.add_pin("gnd", "GROUND")
def create_dff_array(self):
self.dff_insts={}
for row in range(self.rows):
for row in range(self.rows):
for col in range(self.columns):
name = "dff_r{0}_c{1}".format(row,col)
self.dff_insts[row,col]=self.add_inst(name=name,
mod=self.dff)
instance_ports = [self.get_din_name(row,col),
self.get_dout_name(row,col)]
name = "dff_r{0}_c{1}".format(row, col)
self.dff_insts[row, col]=self.add_inst(name=name,
mod=self.dff)
instance_ports = [self.get_din_name(row, col),
self.get_dout_name(row, col)]
for port in self.dff.pin_names:
if port != 'D' and port != 'Q':
instance_ports.append(port)
self.connect_inst(instance_ports)
def place_dff_array(self):
for row in range(self.rows):
for row in range(self.rows):
for col in range(self.columns):
name = "dff_r{0}_c{1}".format(row,col)
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"
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"
self.dff_insts[row,col].place(offset=base,
mirror=mirror)
self.dff_insts[row, col].place(offset=base,
mirror=mirror)
def get_din_name(self, row, col):
if self.columns == 1:
@ -95,7 +93,7 @@ class dff_array(design.design):
elif self.rows == 1:
din_name = "din_{0}".format(col)
else:
din_name = "din_{0}_{1}".format(row,col)
din_name = "din_{0}_{1}".format(row, col)
return din_name
@ -105,61 +103,58 @@ class dff_array(design.design):
elif self.rows == 1:
dout_name = "dout_{0}".format(col)
else:
dout_name = "dout_{0}_{1}".format(row,col)
dout_name = "dout_{0}_{1}".format(row, col)
return dout_name
def add_layout_pins(self):
for row in range(self.rows):
for col in range(self.columns):
for col in range(self.columns):
# Continous vdd rail along with label.
vdd_pin=self.dff_insts[row,col].get_pin("vdd")
self.add_power_pin("vdd", vdd_pin.center())
vdd_pin=self.dff_insts[row, col].get_pin("vdd")
self.add_power_pin("vdd", vdd_pin.center(), start_layer=vdd_pin.layer)
# Continous gnd rail along with label.
gnd_pin=self.dff_insts[row,col].get_pin("gnd")
self.add_power_pin("gnd", gnd_pin.center())
gnd_pin=self.dff_insts[row, col].get_pin("gnd")
self.add_power_pin("gnd", gnd_pin.center(), start_layer=gnd_pin.layer)
for row in range(self.rows):
for col in range(self.columns):
din_pin = self.dff_insts[row,col].get_pin("D")
debug.check(din_pin.layer=="m2","DFF D pin not on metal2")
self.add_layout_pin(text=self.get_din_name(row,col),
for row in range(self.rows):
for col in range(self.columns):
din_pin = self.dff_insts[row, col].get_pin("D")
debug.check(din_pin.layer == "m2", "DFF D pin not on metal2")
self.add_layout_pin(text=self.get_din_name(row, col),
layer=din_pin.layer,
offset=din_pin.ll(),
width=din_pin.width(),
height=din_pin.height())
dout_pin = self.dff_insts[row,col].get_pin("Q")
debug.check(dout_pin.layer=="m2","DFF Q pin not on metal2")
self.add_layout_pin(text=self.get_dout_name(row,col),
dout_pin = self.dff_insts[row, col].get_pin("Q")
debug.check(dout_pin.layer == "m2", "DFF Q pin not on metal2")
self.add_layout_pin(text=self.get_dout_name(row, col),
layer=dout_pin.layer,
offset=dout_pin.ll(),
width=dout_pin.width(),
height=dout_pin.height())
# Create vertical spines to a single horizontal rail
clk_pin = self.dff_insts[0,0].get_pin(self.dff.clk_pin)
clk_ypos = 2*self.m3_pitch+self.m3_width
debug.check(clk_pin.layer=="m2","DFF clk pin not on metal2")
clk_pin = self.dff_insts[0, 0].get_pin(self.dff.clk_pin)
clk_ypos = 2 * self.m3_pitch + self.m3_width
debug.check(clk_pin.layer == "m2", "DFF clk pin not on metal2")
self.add_layout_pin_segment_center(text="clk",
layer="m3",
start=vector(0,clk_ypos),
end=vector(self.width,clk_ypos))
start=vector(0, clk_ypos),
end=vector(self.width, clk_ypos))
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
self.add_rect(layer="m2",
offset=clk_pin.ll().scale(1,0),
offset=clk_pin.ll().scale(1, 0),
width=self.m2_width,
height=self.height)
# Drop a via to the M3 pin
self.add_via_center(layers=self.m2_stack,
offset=vector(clk_pin.cx(),clk_ypos))
self.add_via_stack_center(from_layer=clk_pin.layer,
to_layer="m3",
offset=vector(clk_pin.cx(), clk_ypos))
def get_clk_cin(self):
"""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 design
from tech import parameter
from tech import parameter, layer
from tech import cell_properties as props
from vector import vector
from globals import OPTS
@ -52,7 +52,6 @@ class dff_buf(design.design):
def create_layout(self):
self.place_instances()
self.width = self.inv2_inst.rx()
self.height = self.dff.height
self.route_wires()
self.add_layout_pins()
@ -120,39 +119,37 @@ class dff_buf(design.design):
except AttributeError:
pass
self.inv1_inst.place(vector(self.dff_inst.rx() + well_spacing + self.well_extend_active, 0))
# Add INV2 to the right
self.inv2_inst.place(vector(self.inv1_inst.rx(), 0))
def route_wires(self):
if "li" in layer:
self.route_layer = "li"
else:
self.route_layer = "m1"
# Route dff q to inv1 a
q_pin = self.dff_inst.get_pin("Q")
a1_pin = self.inv1_inst.get_pin("A")
mid_x_offset = 0.5 * (a1_pin.cx() + q_pin.cx())
mid1 = vector(mid_x_offset, q_pin.cy())
mid2 = vector(mid_x_offset, a1_pin.cy())
self.add_path("m3", [q_pin.center(), mid1, mid2, a1_pin.center()])
self.add_via_center(layers=self.m2_stack,
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())
mid1 = vector(a1_pin.cx(), q_pin.cy())
self.add_path(q_pin.layer, [q_pin.center(), mid1, a1_pin.center()], width=q_pin.height())
self.add_via_stack_center(from_layer=a1_pin.layer,
to_layer=q_pin.layer,
offset=a1_pin.center())
# Route inv1 z to inv2 a
z1_pin = self.inv1_inst.get_pin("Z")
a2_pin = self.inv2_inst.get_pin("A")
mid_x_offset = 0.5 * (z1_pin.cx() + a2_pin.cx())
self.mid_qb_pos = vector(mid_x_offset, z1_pin.cy())
mid2 = vector(mid_x_offset, a2_pin.cy())
self.add_path("m1", [z1_pin.center(), self.mid_qb_pos, mid2, a2_pin.center()])
self.mid_qb_pos = vector(0.5 * (z1_pin.cx() + a2_pin.cx()), z1_pin.cy())
self.add_zjog(z1_pin.layer, z1_pin.center(), a2_pin.center())
def add_layout_pins(self):
# Continous vdd rail along with label.
vdd_pin=self.dff_inst.get_pin("vdd")
self.add_layout_pin(text="vdd",
layer="m1",
layer=vdd_pin.layer,
offset=vdd_pin.ll(),
width=self.width,
height=vdd_pin.height())
@ -160,7 +157,7 @@ class dff_buf(design.design):
# Continous gnd rail along with label.
gnd_pin=self.dff_inst.get_pin("gnd")
self.add_layout_pin(text="gnd",
layer="m1",
layer=gnd_pin.layer,
offset=gnd_pin.ll(),
width=self.width,
height=vdd_pin.height())
@ -180,22 +177,25 @@ class dff_buf(design.design):
height=din_pin.height())
dout_pin = self.inv2_inst.get_pin("Z")
mid_pos = dout_pin.center() + vector(self.m1_nonpref_pitch, 0)
q_pos = mid_pos - vector(0, self.m2_pitch)
mid_pos = dout_pin.center() + vector(self.m2_nonpref_pitch, 0)
q_pos = mid_pos - vector(0, 2 * self.m2_nonpref_pitch)
self.add_layout_pin_rect_center(text="Q",
layer="m2",
offset=q_pos)
self.add_path("m1", [dout_pin.center(), mid_pos, q_pos])
self.add_via_center(layers=self.m1_stack,
offset=q_pos)
self.add_path(self.route_layer, [dout_pin.center(), mid_pos, q_pos])
self.add_via_stack_center(from_layer=dout_pin.layer,
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",
layer="m2",
offset=qb_pos)
self.add_path("m1", [self.mid_qb_pos, qb_pos])
self.add_via_center(layers=self.m1_stack,
offset=qb_pos)
self.add_path(self.route_layer, [self.mid_qb_pos, qb_pos])
a2_pin = self.inv2_inst.get_pin("A")
self.add_via_stack_center(from_layer=a2_pin.layer,
to_layer="m2",
offset=qb_pos)
def get_clk_cin(self):
"""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):
# Continous vdd rail along with label.
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.
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):

View File

@ -38,20 +38,19 @@ class dummy_array(bitcell_base_array):
def add_modules(self):
""" 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.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.cell_inst[row, col]=self.add_inst(name=name,
mod=self.dummy_cell)
self.connect_inst(self.get_bitcell_pins(col, row))
def input_load(self):
@ -60,7 +59,7 @@ class dummy_array(bitcell_base_array):
def get_wordline_cin(self):
"""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()
total_cin = bitcell_wl_cin * self.column_size
return total_cin

View File

@ -11,8 +11,6 @@ import math
from sram_factory import factory
from vector import vector
from globals import OPTS
from errors import drc_error
from tech import cell_properties, layer
class hierarchical_decoder(design.design):
@ -28,12 +26,8 @@ class hierarchical_decoder(design.design):
self.pre3x8_inst = []
b = factory.create(module_type="bitcell")
try:
self.cell_multiple = cell_properties.bitcell.decoder_bitcell_multiple
except AttributeError:
self.cell_multiple = 1
self.cell_height = self.cell_multiple * b.height
self.cell_height = b.height
self.num_outputs = num_outputs
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)
@ -41,41 +35,6 @@ class hierarchical_decoder(design.design):
self.create_netlist()
if not OPTS.netlist_only:
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):
self.add_modules()
@ -88,24 +47,32 @@ class hierarchical_decoder(design.design):
self.setup_layout_constants()
self.place_pre_decoder()
self.place_row_decoder()
self.height = max(self.predecoder_height, self.row_decoder_height) + self.bus_space
self.route_inputs()
self.route_outputs()
self.route_decoder_bus()
self.route_vdd_gnd()
self.offset_all_coordinates()
self.width = self.and_inst[0].rx() + self.m1_space
self.add_boundary()
self.DRC_LVS()
def add_modules(self):
self.inv = factory.create(module_type="pinv",
height=self.cell_height)
self.add_mod(self.inv)
self.and2 = factory.create(module_type="pand2",
self.and2 = factory.create(module_type="and2_dec",
height=self.cell_height)
self.add_mod(self.and2)
self.and3 = factory.create(module_type="pand3",
self.and3 = factory.create(module_type="and3_dec",
height=self.cell_height)
self.add_mod(self.and3)
# TBD
# self.and4 = factory.create(module_type="and4_dec")
# self.add_mod(self.and4)
self.add_decoders()
@ -176,56 +143,49 @@ class hierarchical_decoder(design.design):
-1)
# 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
else:
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
self.num_rows = math.ceil(self.num_outputs / self.cell_multiple)
# We will place this many final decoders per row
self.decoders_per_row = math.ceil(self.num_outputs / self.num_rows)
# We will need to use M2 and M3 in the vertical bus if we have multiple decoders per row
if self.decoders_per_row == 1:
self.decoder_bus_pitch = self.m2_pitch
elif self.decoders_per_row == 2:
self.decoder_bus_pitch = self.m3_pitch
# How much space between each predecoder
self.predecoder_spacing = 2 * self.and2.height
self.predecoder_height = self.pre2_4.height * self.no_of_pre2x4 + self.pre3_8.height * self.no_of_pre3x8 \
+ (self.no_of_pre2x4 + self.no_of_pre3x8 - 1) * self.predecoder_spacing
# 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 = self.m2_space
self.input_layer = "m2"
self.output_layer = "li"
self.output_layer_pitch = self.li_pitch
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
if (self.num_inputs == 4 or self.num_inputs == 5):
nand_width = self.and2.width
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.")
# Two extra pitches between modules on left and right
self.internal_routing_width = self.total_number_of_predecoder_outputs * self.bus_pitch + self.bus_pitch
self.row_decoder_height = self.and2.height * self.num_outputs
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):
""" 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
min_x = 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_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,
names=input_bus_names,
length=input_height)
length=self.predecoder_height)
self.route_input_to_predecodes()
@ -248,14 +208,12 @@ class hierarchical_decoder(design.design):
for i in range(2):
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)
decoder_pin = self.pre2x4_inst[pre_num].get_pin(in_name)
# To prevent conflicts, we will offset each input connect so
# that it aligns with the vdd/gnd rails
decoder_offset = decoder_pin.bc() + vector(0, (i + 1) * (self.inv.height + self.m1_pitch))
decoder_offset = decoder_pin.center()
input_offset = input_pos.scale(1, 0) + decoder_offset.scale(0, 1)
self.route_input_bus(decoder_offset, input_offset)
@ -264,14 +222,12 @@ class hierarchical_decoder(design.design):
for i in range(3):
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)
decoder_pin = self.pre3x8_inst[pre_num].get_pin(in_name)
# To prevent conflicts, we will offset each input connect so
# that it aligns with the vdd/gnd rails
decoder_offset = decoder_pin.bc() + vector(0, (i + 1) * (self.inv.height + self.m1_pitch))
decoder_offset = decoder_pin.center()
input_offset = input_pos.scale(1, 0) + decoder_offset.scale(0, 1)
self.route_input_bus(decoder_offset, input_offset)
@ -282,13 +238,14 @@ class hierarchical_decoder(design.design):
vertical M2 coordinate to the predecode inputs
"""
self.add_via_stack_center(from_layer="m2",
to_layer="m3",
self.add_via_stack_center(from_layer=self.bus_layer,
to_layer=self.input_layer,
offset=input_offset)
self.add_via_stack_center(from_layer="m2",
to_layer="m3",
offset=output_offset)
self.add_path("m3", [input_offset, output_offset])
self.add_via_stack_center(from_layer=self.bus_layer,
to_layer=self.input_layer,
offset=output_offset,
directions=self.bus_directions)
self.add_path(self.input_layer, [input_offset, output_offset])
def add_pins(self):
""" Add the module pins """
@ -363,19 +320,19 @@ class hierarchical_decoder(design.design):
if (self.num_inputs == 2):
base = vector(-self.pre2_4.width, 0)
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):
""" Place 3x8 predecoder to the left of the origin and above any 2x4 decoders """
if (self.num_inputs == 3):
offset = vector(-self.pre_3_8.width, 0)
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)
self.pre3x8_inst[num].place(offset - vector(2 * self.m2_pitch, 0))
self.pre3x8_inst[num].place(offset)
def create_row_decoder(self):
""" Create the row-decoder by placing AND2/AND3 and Inverters
@ -431,7 +388,6 @@ class hierarchical_decoder(design.design):
if (self.num_inputs >= 4):
self.place_decoder_and_array()
def place_decoder_and_array(self):
"""
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.
"""
for inst_index in range(self.num_outputs):
row = math.floor(inst_index / self.decoders_per_row)
dec = inst_index % self.decoders_per_row
for row in range(self.num_outputs):
if ((row % 2) == 0):
y_off = and_mod.height * row
mirror = "R0"
@ -462,32 +416,16 @@ class hierarchical_decoder(design.design):
y_off = and_mod.height * (row + 1)
mirror = "MX"
x_off = self.internal_routing_width + dec * and_mod.width
self.and_inst[inst_index].place(offset=vector(x_off, y_off),
mirror=mirror)
x_off = self.internal_routing_width
self.and_inst[row].place(offset=vector(x_off, y_off),
mirror=mirror)
def route_outputs(self):
""" Add the pins. """
max_xoffset = max(x.rx() for x in self.and_inst)
for output_index in range(self.num_outputs):
row_remainder = (output_index % self.decoders_per_row)
and_inst = self.and_inst[output_index]
z_pin = and_inst.get_pin("Z")
if row_remainder == 0 and self.decoders_per_row > 1:
layer = "m3"
self.add_via_stack_center(from_layer=z_pin.layer,
to_layer="m3",
offset=z_pin.center())
else:
layer = z_pin.layer
self.add_layout_pin_segment_center(text="decode_{0}".format(output_index),
layer=layer,
start=z_pin.center(),
end=vector(max_xoffset, z_pin.cy()))
for row in range(self.num_outputs):
and_inst = self.and_inst[row]
self.copy_layout_pin(and_inst, "Z", "decode_{0}".format(row))
def route_decoder_bus(self):
"""
@ -498,9 +436,9 @@ class hierarchical_decoder(design.design):
if (self.num_inputs >= 4):
# This leaves an offset for the predecoder output jogs
input_bus_names = ["predecode_{0}".format(i) for i in range(self.total_number_of_predecoder_outputs)]
self.predecode_bus = self.create_vertical_pin_bus(layer="m2",
pitch=self.decoder_bus_pitch,
offset=vector(0, 0),
self.predecode_bus = self.create_vertical_pin_bus(layer=self.bus_layer,
pitch=self.bus_pitch,
offset=vector(self.bus_pitch, 0),
names=input_bus_names,
length=self.height)
@ -518,8 +456,9 @@ class hierarchical_decoder(design.design):
predecode_name = "predecode_{}".format(pre_num * 4 + i)
out_name = "out_{}".format(i)
pin = self.pre2x4_inst[pre_num].get_pin(out_name)
x_offset = self.pre2x4_inst[pre_num].rx() + self.m2_pitch
self.route_predecode_bus_inputs(predecode_name, pin, x_offset)
x_offset = self.pre2x4_inst[pre_num].rx() + self.output_layer_pitch
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
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)
out_name = "out_{}".format(i)
pin = self.pre3x8_inst[pre_num].get_pin(out_name)
x_offset = self.pre3x8_inst[pre_num].rx() + self.m2_pitch
self.route_predecode_bus_inputs(predecode_name, pin, x_offset)
x_offset = self.pre3x8_inst[pre_num].rx() + self.output_layer_pitch
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):
"""
@ -542,12 +482,6 @@ class hierarchical_decoder(design.design):
and the 128th AND3 is connected to [3,7,15]
"""
output_index = 0
if "li" in layer:
self.decoder_layers = [self.m1_stack, self.m2_stack[::-1]]
else:
self.decoder_layers = [self.m2_stack[::-1]]
debug.check(self.decoders_per_row <= len(self.decoder_layers), "Must have more layers for multi-height decoder.")
if (self.num_inputs == 4 or self.num_inputs == 5):
for index_B in self.predec_groups[1]:
@ -557,13 +491,11 @@ class hierarchical_decoder(design.design):
predecode_name = "predecode_{}".format(index_A)
self.route_predecode_bus_outputs(predecode_name,
self.and_inst[output_index].get_pin("A"),
output_index,
0)
output_index)
predecode_name = "predecode_{}".format(index_B)
self.route_predecode_bus_outputs(predecode_name,
self.and_inst[output_index].get_pin("B"),
output_index,
1)
output_index)
output_index = output_index + 1
elif (self.num_inputs > 5):
@ -575,18 +507,15 @@ class hierarchical_decoder(design.design):
predecode_name = "predecode_{}".format(index_A)
self.route_predecode_bus_outputs(predecode_name,
self.and_inst[output_index].get_pin("A"),
output_index,
0)
output_index)
predecode_name = "predecode_{}".format(index_B)
self.route_predecode_bus_outputs(predecode_name,
self.and_inst[output_index].get_pin("B"),
output_index,
1)
output_index)
predecode_name = "predecode_{}".format(index_C)
self.route_predecode_bus_outputs(predecode_name,
self.and_inst[output_index].get_pin("C"),
output_index,
2)
output_index)
output_index = output_index + 1
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
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.
xoffset = max(x.rx() for x in self.and_inst)
for num in range(0, self.num_outputs):
# Only add the power pin for the 1st in each row
if num % self.decoders_per_row:
continue
for pin_name in ["vdd", "gnd"]:
# The nand and inv are the same height rows...
supply_pin = self.and_inst[num].get_pin(pin_name)
pin_pos = vector(xoffset, supply_pin.cy())
self.add_path(supply_pin.layer,
[supply_pin.lc(), 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:
self.copy_layout_pin(pre, "vdd")
self.copy_layout_pin(pre, "gnd")
# This adds power vias at the top of each cell
# (except the last to keep them inside the boundary)
for i in self.and_inst[:-1]:
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)
for i in self.pre2x4_inst + self.pre3x8_inst:
self.copy_layout_pin(i, n)
else:
# 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
for row in range(0, self.num_outputs):
for pin_name in ["vdd", "gnd"]:
# The nand and inv are the same height rows...
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
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()
# y_offset is the same for both the M2 and M4 routes so that the rail
# contacts align and don't cause problems
if pin_index == 0:
# Bottom pitch
y_offset = row_offset
elif pin_index == 1:
# One pitch from top
y_offset = row_offset + self.and_inst[0].height - self.m3_pitch
elif pin_index == 2:
# One pitch from bottom
y_offset = row_offset + self.m3_pitch
else:
debug.error("Invalid decoder pitch.")
rail_pos = vector(self.predecode_bus[rail_name].x, y_offset)
mid_pos = vector(pin_pos.x, rail_pos.y)
self.add_wire(self.decoder_layers[row_remainder], [rail_pos, mid_pos, pin_pos])
rail_pos = vector(self.predecode_bus[rail_name].cx(), pin_pos.y)
self.add_path(self.input_layer, [rail_pos, pin_pos])
self.add_via_stack_center(from_layer="m2",
to_layer=self.decoder_layers[row_remainder][0],
offset=rail_pos)
self.add_via_stack_center(from_layer=self.bus_layer,
to_layer=self.input_layer,
offset=rail_pos,
directions=self.bus_directions)
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,
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
to the right of the cell at the given x_offset.
"""
# This routes the pin up to the rail, basically, to avoid conflicts.
# It would be fixed with a channel router.
# pin_pos = pin.center()
# mid_point1 = vector(x_offset, pin_pos.y)
# mid_point2 = vector(x_offset, pin_pos.y + self.inv.height / 2)
# rail_pos = vector(self.predecode_bus[rail_name].x, mid_point2.y)
# self.add_path("m1", [pin_pos, mid_point1, mid_point2, rail_pos])
pin_pos = pin.rc()
mid_point1 = vector(x_offset, pin_pos.y)
mid_point2 = vector(x_offset, y_offset)
rail_pos = vector(self.predecode_bus[rail_name].cx(), mid_point2.y)
self.add_path(self.output_layer, [pin_pos, mid_point1, mid_point2, rail_pos])
pin_pos = pin.center()
rail_pos = vector(self.predecode_bus[rail_name].x, pin_pos.y)
self.add_path("m1", [pin_pos, rail_pos])
# pin_pos = pin.center()
# rail_pos = vector(self.predecode_bus[rail_name].cx(), pin_pos.y)
# self.add_path(self.output_layer, [pin_pos, rail_pos])
self.add_via_stack_center(from_layer=pin.layer,
to_layer="m1",
to_layer=self.output_layer,
offset=pin_pos)
self.add_via_stack_center(from_layer="m1",
to_layer="m2",
offset=rail_pos)
self.add_via_stack_center(from_layer=self.bus_layer,
to_layer=self.output_layer,
offset=rail_pos,
directions=self.bus_directions)
def input_load(self):
if self.determine_predecodes(self.num_inputs)[1]==0:

View File

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

View File

@ -5,13 +5,10 @@
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
from tech import drc
import debug
import design
from vector import vector
from hierarchical_predecode import hierarchical_predecode
from globals import OPTS
class hierarchical_predecode2x4(hierarchical_predecode):
"""
Pre 2x4 decoder used in hierarchical_decoder.
@ -33,21 +30,6 @@ class hierarchical_predecode2x4(hierarchical_predecode):
["in_0", "in_1", "out_3", "vdd", "gnd"]]
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):
""" These are the decoder connections of the AND gates to the A,B pins """
combination = [["Abar_0", "Abar_1"],

View File

@ -5,13 +5,10 @@
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
from tech import drc
import debug
import design
from vector import vector
from hierarchical_predecode import hierarchical_predecode
from globals import OPTS
class hierarchical_predecode3x8(hierarchical_predecode):
"""
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"]]
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):
""" These are the decoder connections of the NAND gates to the A,B,C pins """
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
# All rights reserved.
#
from math import log
from math import log, ceil
import debug
import design
from sram_factory import factory
from vector import vector
from tech import layer
from globals import OPTS
@ -21,7 +21,7 @@ class port_address(design.design):
self.num_cols = cols
self.num_rows = rows
self.addr_size = int(log(self.num_rows, 2))
self.addr_size = ceil(log(self.num_rows, 2))
if name == "":
name = "port_address_{0}_{1}".format(cols, rows)
@ -41,6 +41,10 @@ class port_address(design.design):
self.create_wordline_driver()
def create_layout(self):
if "li" in layer:
self.route_layer = "li"
else:
self.route_layer = "m1"
self.place_instances()
self.route_layout()
self.DRC_LVS()
@ -85,11 +89,19 @@ class port_address(design.design):
def route_internal(self):
for row in range(self.num_rows):
# 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()
driver_in_pos = self.wordline_driver_inst.get_pin("in_{}".format(row)).lc()
mid1 = decoder_out_pos.scale(0.5, 1) + driver_in_pos.scale(0.5, 0)
mid2 = decoder_out_pos.scale(0.5, 0) + driver_in_pos.scale(0.5, 1)
self.add_path("m1", [decoder_out_pos, mid1, mid2, driver_in_pos])
decoder_out_pin = self.row_decoder_inst.get_pin("decode_{}".format(row))
decoder_out_pos = decoder_out_pin.rc()
driver_in_pin = self.wordline_driver_inst.get_pin("in_{}".format(row))
driver_in_pos = driver_in_pin.lc()
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):
@ -97,7 +109,7 @@ class port_address(design.design):
num_outputs=self.num_rows)
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,
cols=self.num_cols)
self.add_mod(self.wordline_driver)
@ -139,7 +151,6 @@ class port_address(design.design):
row_decoder_offset = vector(0, 0)
wordline_driver_offset = vector(self.row_decoder.width, 0)
self.wordline_driver_inst.place(wordline_driver_offset)
self.row_decoder_inst.place(row_decoder_offset)

View File

@ -19,7 +19,7 @@ class port_data(design.design):
"""
def __init__(self, sram_config, port, name=""):
sram_config.set_local_config(self)
self.port = port
if self.write_size is not None:
@ -27,6 +27,9 @@ class port_data(design.design):
else:
self.num_wmasks = 0
if self.num_spare_cols is None:
self.num_spare_cols = 0
if name == "":
name = "port_data_{0}".format(self.port)
design.design.__init__(self, name)
@ -102,7 +105,7 @@ class port_data(design.design):
self.DRC_LVS()
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_br", "INOUT")
@ -111,11 +114,17 @@ class port_data(design.design):
br_name = self.get_br_name(self.port)
self.add_pin("{0}_{1}".format(bl_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:
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")
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")
# Will be empty if no 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")
for bit in range(self.num_wmasks):
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("gnd", "GROUND")
@ -178,21 +189,27 @@ class port_data(design.design):
# Extra column +1 is for RBL
# 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",
columns=self.num_cols + 1,
columns=self.num_cols + self.num_spare_cols + 1,
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)
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",
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)
else:
self.sense_amp_array = None
if self.col_addr_size > 0:
# RBLs dont get a col mux
self.column_mux_array = factory.create(module_type="column_mux_array",
columns=self.num_cols,
word_size=self.word_size,
@ -203,17 +220,19 @@ class port_data(design.design):
self.column_mux_array = None
if self.port in self.write_ports:
# RBLs dont get a write driver
self.write_driver_array = factory.create(module_type="write_driver_array",
columns=self.num_cols,
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)
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",
columns=self.num_cols,
word_size=self.word_size,
write_size=self.write_size,
port = self.port)
write_size=self.write_size)
self.add_mod(self.write_mask_and_array)
else:
self.write_mask_and_array = None
@ -246,12 +265,6 @@ class port_data(design.design):
self.precharge = factory.create(module_type="precharge",
bitcell_bl=self.bl_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):
""" Creating Precharge """
@ -272,6 +285,10 @@ class port_data(design.design):
for bit in range(self.num_cols):
temp.append("{0}_{1}".format(bl_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
if self.port==1:
@ -297,7 +314,6 @@ class port_data(design.design):
for col in range(self.num_cols):
temp.append("{0}_{1}".format(bl_name, col))
temp.append("{0}_{1}".format(br_name, col))
for word in range(self.words_per_row):
temp.append("sel_{}".format(word))
for bit in range(self.word_size):
@ -330,8 +346,14 @@ class port_data(design.design):
else:
temp.append("{0}_out_{1}".format(bl_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)
def place_sense_amp_array(self, offset):
@ -346,7 +368,7 @@ class port_data(design.design):
br_name = self.get_br_name(self.port)
temp = []
for bit in range(self.word_size):
for bit in range(self.word_size + self.num_spare_cols):
temp.append("din_{}".format(bit))
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(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:
for i in range(self.num_wmasks):
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:
temp.append("w_en")
temp.extend(["vdd", "gnd"])
@ -444,8 +477,7 @@ class port_data(design.design):
def route_sense_amp_out(self, port):
""" Add pins for the sense amp output """
for bit in range(self.word_size):
for bit in range(self.word_size + self.num_spare_cols):
data_pin = self.sense_amp_array_inst.get_pin("data_{}".format(bit))
self.add_layout_pin_rect_center(text="dout_{0}".format(bit),
layer=data_pin.layer,
@ -456,7 +488,7 @@ class port_data(design.design):
def route_write_driver_in(self, port):
""" 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)
din_name = "din_{}".format(row)
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)
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):
""" 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 """
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
"""
inst1 = self.write_mask_and_array_inst
inst2 = self.write_driver_array_inst
wmask_inst = self.write_mask_and_array_inst
wdriver_inst = self.write_driver_array_inst
loc = 0
for bit in range(self.num_wmasks):
# 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))
wdriver_en_pin = inst2.get_pin("en_{0}".format(bit))
wmask_out_pin = wmask_inst.get_pin("wmask_out_{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
# the wdriver_sel_{} pin in the write driver AND array.
if bit == 0:
# 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())
wmask_pos = wmask_out_pin.center()
wdriver_pos = wdriver_en_pin.rc() - vector(self.m2_pitch, 0)
mid_pos = vector(wdriver_pos.x, wmask_pos.y)
# 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
self.add_via_center(layers=self.m1_stack,
offset=end_pos)
self.add_via_stack_center(from_layer=wdriver_en_pin.layer,
to_layer="m2",
offset=wdriver_pos)
# 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):
""" 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:
return
inst1 = self.column_mux_array_inst
inst2 = self.precharge_array_inst
start_bit = 1 if self.port == 0 else 0
insn2_start_bit = 1 if self.port == 0 else 0
self.connect_bitlines(inst1=inst1,
inst2=inst2,
self.connect_bitlines(inst1=self.column_mux_array_inst,
inst2=self.precharge_array_inst,
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):
""" Routing of BL and BR between sense_amp and column mux or precharge array """
@ -545,12 +567,41 @@ class port_data(design.design):
else:
start_bit=0
self.channel_route_bitlines(inst1=inst1,
inst1_bls_template=inst1_bls_templ,
inst2=inst2,
num_bits=self.word_size,
inst1_start_bit=start_bit)
# spare cols connected to precharge array since they are read independently
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,
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):
""" Routing of BL and BR between sense_amp and column mux or precharge array """
inst2 = self.write_driver_array_inst
@ -569,10 +620,44 @@ class port_data(design.design):
else:
start_bit=0
self.channel_route_bitlines(inst1=inst1, inst2=inst2,
num_bits=self.word_size,
inst1_bls_template=inst1_bls_templ,
inst1_start_bit=start_bit)
if self.port==0:
off = 1
else:
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):
""" 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
inst2 = self.sense_amp_array_inst
# These should be pitch matched in the cell library,
# but just in case, do a channel route.
self.channel_route_bitlines(inst1=inst1,
inst2=inst2,
num_bits=self.word_size)
# This could be a channel route, but in some techs the bitlines
# are too close together.
self.connect_bitlines(inst1=inst1,
inst2=inst2,
num_bits=self.word_size + self.num_spare_cols)
def route_bitline_pins(self):
""" 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")
bit_offset=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, "br_{}".format(self.num_cols), "rbl_br")
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 + self.num_spare_cols), "rbl_br")
bit_offset=0
else:
bit_offset=0
@ -611,6 +696,19 @@ class port_data(design.design):
"br_{}".format(bit))
else:
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):
""" 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:
self.copy_layout_pin(self.column_mux_array_inst, pin_name)
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_mask_and_array_inst:
for bit in range(self.num_wmasks):
# Add write driver's en_{} pins
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:
self.copy_layout_pin(self.write_driver_array_inst, "en", "w_en")
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.
"""
bot_inst_group, top_inst_group = self._group_bitline_instances(
inst1, inst2, num_bits,
inst1_bls_template, inst1_start_bit,
inst2_bls_template, inst2_start_bit)
bot_inst_group, top_inst_group = self._group_bitline_instances(inst1, inst2, num_bits,
inst1_bls_template, inst1_start_bit,
inst2_bls_template, inst2_start_bit)
# 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!
@ -699,13 +803,8 @@ class port_data(design.design):
bottom_names = self._get_bitline_pins(bot_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))
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,
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
in the middle between the two modules (if needed).
"""
bot_inst_group, top_inst_group = self._group_bitline_instances(
inst1, inst2, num_bits,
inst1_bls_template, inst1_start_bit,
inst2_bls_template, inst2_start_bit)
bot_inst_group, top_inst_group = self._group_bitline_instances(inst1, inst2, num_bits,
inst1_bls_template, inst1_start_bit,
inst2_bls_template, inst2_start_bit)
for col in range(num_bits):
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()
top_bl, top_br = top_bl_pin.bc(), top_br_pin.bc()
yoffset = 0.5 * (top_bl.y + bot_bl.y)
self.add_path("m2", [bot_bl,
vector(bot_bl.x, yoffset),
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])
layer_pitch = getattr(self, "{}_pitch".format(top_bl_pin.layer))
self.add_zjog(bot_bl_pin.layer, bot_bl, top_bl, "V", fixed_offset=top_bl_pin.by() - layer_pitch)
self.add_zjog(bot_br_pin.layer, bot_br, top_br, "V", fixed_offset=top_bl_pin.by() - 2 * layer_pitch)
def graph_exclude_precharge(self):
"""Precharge adds a loop between bitlines, can be excluded to reduce complexity"""
if self.precharge_array_inst:
self.graph_inst_exclude.add(self.precharge_array_inst)

View File

@ -7,10 +7,10 @@
#
import design
import debug
from tech import drc
from vector import vector
from sram_factory import factory
from globals import OPTS
from tech import layer
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.
"""
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)
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))
@ -28,7 +28,13 @@ class precharge_array(design.design):
self.size = size
self.bitcell_bl = bitcell_bl
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()
if not OPTS.netlist_only:
self.create_layout()
@ -73,14 +79,18 @@ class precharge_array(design.design):
def add_layout_pins(self):
en_bar_pin = self.pc_cell.get_pin("en_bar")
self.add_layout_pin(text="en_bar",
layer=en_bar_pin.layer,
offset=en_bar_pin.ll(),
width=self.width,
height=en_bar_pin.height())
en_pin = self.pc_cell.get_pin("en_bar")
start_offset = en_pin.lc().scale(0, 1)
end_offset = start_offset + vector(self.width, 0)
self.add_layout_pin_segment_center(text="en_bar",
layer=self.en_bar_layer,
start=start_offset,
end=end_offset)
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")
for i in range(len(self.local_insts)):
@ -106,7 +116,7 @@ class precharge_array(design.design):
xoffset = 0
for i in range(self.columns):
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"
tempx = tempx + self.pc_cell.width
else:

View File

@ -1,12 +1,12 @@
# 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.
#
import debug
import design
from tech import drc, spice
from tech import drc, spice, cell_properties
from vector import vector
from globals import OPTS
from sram_factory import factory
@ -32,22 +32,23 @@ class replica_bitcell_array(design.design):
self.right_rbl = right_rbl
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.bitcell_ports),"Bitcell ports must match total RBLs.")
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.bitcell_ports),
"Bitcell ports must match total RBLs.")
# Two dummy rows/cols plus replica for each port
self.extra_rows = 2 + left_rbl + right_rbl
self.extra_cols = 2 + left_rbl + right_rbl
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
# We don't offset this because we need to align
# the replica bitcell in the control logic
#self.offset_all_coordinates()
# self.offset_all_coordinates()
def create_netlist(self):
""" Create and connect the netlist """
self.add_modules()
@ -55,15 +56,15 @@ class replica_bitcell_array(design.design):
self.create_instances()
def add_modules(self):
""" Array and dummy/replica columns
""" Array and dummy/replica columns
d or D = dummy cell (caps to distinguish grouping)
r or R = replica cell (caps to distinguish grouping)
b or B = bitcell
replica columns 1
b or B = bitcell
replica columns 1
v v
bdDDDDDDDDDDDDDDdb <- Dummy row
bdDDDDDDDDDDDDDDrb <- Dummy row
bdDDDDDDDDDDDDDDdb <- Dummy row
bdDDDDDDDDDDDDDDrb <- Dummy row
br--------------rb
br| Array |rb
br| row x col |rb
@ -90,15 +91,17 @@ class replica_bitcell_array(design.design):
# Replica bitlines
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:
replica_bit = bit+1
replica_bit = bit + 1
# dummy column
column_offset = 1
column_offset = self.left_rbl - bit
# Creating right_rbl
else:
replica_bit = bit+self.row_size+1
replica_bit = bit + self.row_size + 1
# 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",
rows=self.row_size,
left_rbl=self.left_rbl,
@ -106,7 +109,7 @@ class replica_bitcell_array(design.design):
column_offset=column_offset,
replica_bit=replica_bit)
self.add_mod(self.replica_columns[bit])
# Dummy row
self.dummy_row = factory.create(module_type="dummy_array",
cols=self.column_size,
@ -116,57 +119,74 @@ class replica_bitcell_array(design.design):
mirror=0)
self.add_mod(self.dummy_row)
# Dummy col (mirror starting at first if odd replica+dummy rows)
self.dummy_col_left = factory.create(module_type="dummy_array",
# If there are bitcell end caps, replace the dummy cells on the edge of the bitcell array with end caps.
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,
column_offset=0,
rows=self.row_size + self.extra_rows,
mirror=(self.left_rbl+1)%2)
self.add_mod(self.dummy_col_left)
mirror=(self.left_rbl + 1) % 2)
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,
# dummy column
# + left replica column
# + left replica column(s)
# + bitcell columns
# + right replica column
column_offset=1 + self.left_rbl + self.column_size + self.right_rbl,
# + right replica column(s)
column_offset = 1 + self.left_rbl + self.column_size + self.right_rbl,
rows=self.row_size + self.extra_rows,
mirror=(self.left_rbl+1)%2)
self.add_mod(self.dummy_col_right)
mirror=(self.left_rbl + 1) %2)
self.add_mod(self.edge_col_right)
def add_pins(self):
self.bitcell_array_wl_names = self.bitcell_array.get_all_wordline_names()
self.bitcell_array_bl_names = self.bitcell_array.get_all_bitline_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_bl_names = ["dummy_"+x for x in self.cell.get_all_bitline_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_row_bl_names = self.bitcell_array_bl_names
# A dictionary because some ports may have nothing
self.rbl_bl_names = {}
self.rbl_br_names = {}
self.rbl_wl_names = {}
# Create the full WL names include dummy, replica, and regular bit cells
self.replica_col_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)
for port in range(self.left_rbl):
# 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
self.rbl_wl_names[port]=wl_names[self.bitcell_ports[port]]
self.replica_col_wl_names.extend(wl_names)
# Regular WLs
self.replica_col_wl_names.extend(self.bitcell_array_wl_names)
# 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
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
self.rbl_wl_names[port]=wl_names[self.bitcell_ports[port]]
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
self.dummy_col_wl_names = self.replica_col_wl_names
# Per port bitline names
self.replica_bl_names = {}
self.replica_wl_names = {}
# Array of all port bitline names
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))]
right_names=["rbl_{0}_{1}".format(self.cell.get_br_name(x),port) for x in range(len(self.all_ports))]
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))]
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
self.rbl_bl_names[port]=left_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]
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[port] = "rbl_wl{}".format(port)
wl_names = ["rbl_{0}_{1}".format(x, port) for x in self.cell.get_all_wl_names()]
self.replica_wl_names[port] = wl_names
# External pins
self.add_pin_list(self.bitcell_array_bl_names, "INOUT")
# 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())]
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):
self.add_pin(bl_name,"OUTPUT")
self.add_pin(br_name,"OUTPUT")
for (bl_name, br_name) in zip(bl_names, br_names):
self.add_pin(bl_name, "OUTPUT")
self.add_pin(br_name, "OUTPUT")
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())]
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("gnd", "GROUND")
def create_instances(self):
""" Create the module instances used in this design """
supplies = ["vdd", "gnd"]
# Used for names/dimensions only
self.cell = factory.create(module_type="bitcell")
# Main array
self.bitcell_array_inst=self.add_inst(name="bitcell_array",
mod=self.bitcell_array)
@ -227,88 +243,82 @@ class replica_bitcell_array(design.design):
# Replica columns
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),
mod=self.replica_columns[port])
mod=self.replica_columns[port])
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)
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),
mod=self.dummy_row)
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
self.dummy_col_left_inst=self.add_inst(name="dummy_col_left",
mod=self.dummy_col_left)
self.connect_inst([x+"_left" for x in self.dummy_cell_bl_names] + self.dummy_col_wl_names + supplies)
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.dummy_col_right_inst=self.add_inst(name="dummy_col_right",
mod=self.dummy_col_right)
self.connect_inst([x+"_right" for x in self.dummy_cell_bl_names] + self.dummy_col_wl_names + supplies)
mod=self.edge_col_right)
self.connect_inst([x + "_right" for x in self.dummy_cell_bl_names] + self.dummy_col_wl_names + supplies)
def create_layout(self):
self.height = (self.row_size+self.extra_rows)*self.dummy_row.height
self.width = (self.column_size+self.extra_cols)*self.cell.width
self.height = (self.row_size + self.extra_rows) * self.dummy_row.height
self.width = (self.column_size + self.extra_cols) * self.cell.width
# This is a bitcell x bitcell offset to scale
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
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
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)
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(),
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(),
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)
flip_dummy = (self.left_rbl+1)%2
self.dummy_row_bot_inst.place(offset=offset.scale(0,-self.left_rbl-1+flip_dummy),
flip_dummy = (self.left_rbl + 1) % 2
self.dummy_row_bot_inst.place(offset=offset.scale(0, -self.left_rbl - 1 + flip_dummy),
mirror="MX" if flip_dummy else "R0")
# 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
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
for bit in range(self.left_rbl):
self.dummy_row_replica_inst[bit].place(offset=offset.scale(0,-bit-bit%2),
mirror="R0" if bit%2 else "MX")
self.dummy_row_replica_inst[bit].place(offset=offset.scale(0, -bit - bit % 2),
mirror="R0" if bit % 2 else "MX")
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(),
mirror="MX" if bit%2 else "R0")
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")
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_boundary()
self.DRC_LVS()
def add_layout_pins(self):
""" Add the layout pins """
@ -321,7 +331,7 @@ class replica_bitcell_array(design.design):
for pin in pin_list:
self.add_layout_pin(text=pin_name,
layer=pin.layer,
offset=pin.ll().scale(0,1),
offset=pin.ll().scale(0, 1),
width=self.width,
height=pin.height())
for bitline in self.bitcell_array_bl_names:
@ -330,27 +340,26 @@ class replica_bitcell_array(design.design):
for pin in pin_list:
self.add_layout_pin(text=pin_name,
layer=pin.layer,
offset=pin.ll().scale(1,0),
offset=pin.ll().scale(1, 0),
width=pin.width(),
height=self.height)
# 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]
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
pin_bit = port+1
# +row_size if above the array
pin_bit = port + 1
# +row_size if above the array
if port>=self.left_rbl:
pin_bit += self.row_size
pin_name += "_{}".format(pin_bit)
pin = inst.get_pin(pin_name)
if wl_name in self.rbl_wl_names.values():
self.add_layout_pin(text=wl_name,
layer=pin.layer,
offset=pin.ll().scale(0,1),
offset=pin.ll().scale(0, 1),
width=self.width,
height=pin.height())
@ -368,20 +377,28 @@ class replica_bitcell_array(design.design):
offset=pin.ll().scale(1, 0),
width=pin.width(),
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 inst in self.insts:
for inst in supply_insts:
pin_list = inst.get_pins(pin_name)
for pin in pin_list:
self.add_power_pin(name=pin_name,
loc=pin.center(),
directions=("V", "V"),
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):
""" Return the WL for the given RBL port """
return self.rbl_wl_names[port]
def get_rbl_bl_name(self, port):
""" Return the BL for the given RBL port """
return self.rbl_bl_names[port]
@ -392,19 +409,17 @@ class replica_bitcell_array(design.design):
def analytical_power(self, corner, load):
"""Power of Bitcell array and bitline in nW."""
from tech import drc, parameter
# Dynamic Power from Bitline
bl_wire = self.gen_bl_wire()
cell_load = 2 * bl_wire.return_input_cap()
bl_swing = OPTS.rbl_delay_percentage
freq = spice["default_event_frequency"]
bitline_dynamic = self.calc_dynamic_power(corner, cell_load, freq, swing=bl_swing)
#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)
#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,
cell_power.leakage * self.column_size * self.row_size)
return total_power
@ -415,13 +430,13 @@ class replica_bitcell_array(design.design):
else:
height = self.height
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
return bl_wire
def get_wordline_cin(self):
"""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()
total_cin = bitcell_wl_cin * self.column_size
return total_cin
@ -429,13 +444,13 @@ class replica_bitcell_array(design.design):
def graph_exclude_bits(self, targ_row, targ_col):
"""Excludes bits in column from being added to graph except target"""
self.bitcell_array.graph_exclude_bits(targ_row, targ_col)
def graph_exclude_replica_col_bits(self):
"""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()
def get_cell_name(self, inst_name, row, col):
"""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.
#
# Copyright (c) 2016-2019 Regents of the University of California
# Copyright (c) 2016-2019 Regents of the University of California
# All rights reserved.
#
import debug
import design
from tech import drc
import contact
from tech import cell_properties
from sram_factory import factory
from vector import vector
from globals import OPTS
class replica_column(design.design):
"""
Generate a replica bitline column for the replica 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.
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.
"""
@ -29,25 +29,30 @@ class replica_column(design.design):
self.right_rbl = right_rbl
self.replica_bit = replica_bit
# 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
debug.check(replica_bit!=0 and replica_bit!=rows,"Replica bit cannot be the dummy row.")
debug.check(replica_bit<=left_rbl or replica_bit>=self.total_size-right_rbl-1,
"Replica bit cannot be in the regular array.")
debug.check(replica_bit != 0 and replica_bit != rows,
"Replica bit cannot be the dummy row.")
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()
if not OPTS.netlist_only:
self.create_layout()
def create_netlist(self):
self.add_modules()
self.add_pins()
self.create_instances()
def create_layout(self):
self.height = self.total_size*self.cell.height
self.width = self.cell.width
self.height = self.total_size * self.cell.height
self.width = self.cell.width
self.place_instances()
self.add_layout_pins()
@ -55,49 +60,70 @@ class replica_column(design.design):
self.DRC_LVS()
def add_pins(self):
for bl_name in self.cell.get_all_bitline_names():
# 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 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("gnd", "GROUND")
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.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)
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
self.cell = factory.create(module_type="bitcell")
def create_instances(self):
try:
end_caps_enabled = cell_properties.bitcell.end_caps
except AttributeError:
end_caps_enabled = False
self.cell_inst = {}
for row in range(self.total_size):
name="rbc_{0}".format(row)
# Top/bottom cell are always dummy cells.
# 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.
if (row>self.left_rbl and row<self.total_size-self.right_rbl-1):
# 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):
self.cell_inst[row]=self.add_inst(name=name,
mod=self.replica_cell)
self.connect_inst(self.get_bitcell_pins(0, row))
elif row==self.replica_bit:
self.cell_inst[row]=self.add_inst(name=name,
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:
self.cell_inst[row]=self.add_inst(name=name,
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):
from tech import cell_properties
# 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
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
# column that needs to be flipped.
@ -108,12 +134,10 @@ class replica_column(design.design):
xoffset = self.replica_cell.width
for row in range(self.total_size):
dir_x = False
name = "bit_r{0}_{1}".format(row,"rbl")
if cell_properties.bitcell.mirror.x and (row+rbl_offset)%2:
dir_x = True
# name = "bit_r{0}_{1}".format(row, "rbl")
dir_x = cell_properties.bitcell.mirror.x and (row + rbl_offset) % 2
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:
dir_key = "XY"
@ -127,54 +151,79 @@ class replica_column(design.design):
self.cell_inst[row].place(offset=offset,
mirror=dir_key)
def add_layout_pins(self):
""" Add the layout pins """
for bl_name in self.cell.get_all_bitline_names():
bl_pin = self.cell_inst[0].get_pin(bl_name)
self.add_layout_pin(text=bl_name,
layer="m2",
offset=bl_pin.ll(),
layer=bl_pin.layer,
offset=bl_pin.ll().scale(1, 0),
width=bl_pin.width(),
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():
wl_pin = self.cell_inst[row].get_pin(wl_name)
self.add_layout_pin(text="{0}_{1}".format(wl_name,row),
layer="m1",
offset=wl_pin.ll().scale(0,1),
self.add_layout_pin(text="{0}_{1}".format(wl_name, row),
layer=wl_pin.layer,
offset=wl_pin.ll().scale(0, 1),
width=self.width,
height=wl_pin.height())
# For every second row and column, add a via for gnd and vdd
for row in range(self.total_size):
inst = self.cell_inst[row]
# Supplies are only connected in the ends
for (index, inst) in self.cell_inst.items():
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):
""" 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 """
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(pin + "_{0}".format(col))
pin_names = self.cell.get_all_wl_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("gnd")
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):
"""Excludes all bits except the replica cell (self.replica_bit)."""
for row, cell in self.cell_inst.items():
if row != self.replica_bit:
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 debug
import utils
from tech import GDS,layer, parameter,drc
from tech import GDS, layer, parameter, drc
from tech import cell_properties as props
from globals import OPTS
import logical_effort
class sense_amp(design.design):
"""
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]
type_list = ["INPUT", "INPUT", "OUTPUT", "INPUT", "POWER", "GROUND"]
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"])
else:
(width, height) = (0,0)
(width, height) = (0, 0)
pin_map = []
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.
#Input load for the bitlines which are connected to the source/drain of a TX. Not the selects.
from tech import spice, parameter
# Input load for the bitlines which are connected to the source/drain of a TX. Not the selects.
from tech import spice
# Default is 8x. Per Samira and Hodges-Jackson book:
# "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.
return spice["min_tx_drain_c"]*(bitline_pmos_size)#ff
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
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
cin = (parameter["sa_inv_pmos_size"] + parameter["sa_inv_nmos_size"])/drc("minwidth_tx")
sa_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")
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):
"""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()
return total_power
def get_en_cin(self):
"""Get the relative capacitance of sense amp enable gate cin"""
pmos_cin = parameter["sa_en_pmos_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.
return 2*pmos_cin + nmos_cin
pmos_cin = parameter["sa_en_pmos_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.
return 2 * pmos_cin + nmos_cin
def get_enable_name(self):
"""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
debug.check(enable_name in self.pin_names, "Enable name {} not found in pin list".format(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."""
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.
"""
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)
debug.info(1, "Creating {0}".format(self.name))
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.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
if OPTS.tech_name == "sky130":
self.en_layer = "m3"
else:
self.en_layer = "m1"
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
@ -59,9 +71,9 @@ class sense_amp_array(design.design):
self.height = self.amp.height
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:
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.add_layout_pins()
@ -70,7 +82,7 @@ class sense_amp_array(design.design):
self.DRC_LVS()
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.get_bl_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):
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)
self.local_insts.append(self.add_inst(name=name,
mod=self.amp))
@ -101,42 +112,49 @@ class sense_amp_array(design.design):
def place_sense_amp_array(self):
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):
xoffset = amp_spacing * i
# align the xoffset to the grid of bitcells. This way we
# know when to do the mirroring.
grid_x = int(xoffset / self.amp.width)
if cell_properties.bitcell.mirror.y and grid_x % 2:
for i in range(0, self.row_size, self.words_per_row):
index = int(i / self.words_per_row)
xoffset = 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[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):
for i in range(len(self.local_insts)):
inst = self.local_insts[i]
gnd_pin = inst.get_pin("gnd")
self.add_power_pin(name="gnd",
loc=gnd_pin.center(),
start_layer=gnd_pin.layer,
directions=("V", "V"))
vdd_pin = inst.get_pin("vdd")
self.add_power_pin(name="vdd",
loc=vdd_pin.center(),
start_layer=vdd_pin.layer,
directions=("V", "V"))
for gnd_pin in inst.get_pins("gnd"):
self.add_power_pin(name="gnd",
loc=gnd_pin.center(),
start_layer=gnd_pin.layer,
directions=("V", "V"))
for vdd_pin in inst.get_pins("vdd"):
self.add_power_pin(name="vdd",
loc=vdd_pin.center(),
start_layer=vdd_pin.layer,
directions=("V", "V"))
bl_pin = inst.get_pin(inst.mod.get_bl_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())
def route_rails(self):
# add sclk rail across entire array
sclk = self.amp.get_pin(self.amp.en_name)
sclk_offset = self.amp.get_pin(self.amp.en_name).ll().scale(0, 1)
self.add_layout_pin(text=self.en_name,
layer=sclk.layer,
offset=sclk_offset,
width=self.width,
height=drc("minwidth_" + sclk.layer))
# Add enable across the array
en_pin = self.amp.get_pin(self.amp.en_name)
start_offset = en_pin.lc().scale(0, 1)
end_offset = start_offset + vector(self.width, 0)
self.add_layout_pin_segment_center(text=self.en_name,
layer=self.en_layer,
start=start_offset,
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):
return self.amp.input_load()

View File

@ -7,7 +7,7 @@
#
import design
import debug
from tech import layer
from tech import layer, preferred_directions
from vector import vector
from sram_factory import factory
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.
"""
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)
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))
@ -30,13 +30,21 @@ class single_level_column_mux_array(design.design):
self.words_per_row = int(self.columns / self.word_size)
self.bitcell_bl = bitcell_bl
self.bitcell_br = bitcell_br
self.column_offset = column_offset
if "li" in layer:
self.col_mux_stack = self.li_stack
self.col_mux_stack_pitch = self.li_pitch
if OPTS.tech_name == "sky130":
self.sel_layer = "m3"
self.sel_pitch = self.m3_pitch
self.bitline_layer = "m1"
else:
self.col_mux_stack = self.m1_stack
self.col_mux_stack_pitch = self.m1_pitch
self.sel_layer = "m1"
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()
if not OPTS.netlist_only:
@ -90,7 +98,7 @@ class single_level_column_mux_array(design.design):
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 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):
self.mux_inst = []
@ -112,7 +120,7 @@ class single_level_column_mux_array(design.design):
# For every column, add a pass gate
for col_num in range(self.columns):
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"
xoffset = xoffset + self.mux.width
else:
@ -149,11 +157,11 @@ class single_level_column_mux_array(design.design):
self.route_bitlines()
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):
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),
layer=self.col_mux_stack[0],
layer=self.sel_layer,
offset=offset,
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
offset = vector(gate_offset.x,
self.get_pin("sel_{}".format(sel_index)).cy())
# Add the poly contact with a shift to account for the rotation
self.add_via_center(layers=self.poly_stack,
offset=offset)
self.add_via_stack_center(from_layer="poly",
to_layer=self.sel_layer,
offset=offset,
directions=self.via_directions)
self.add_path("poly", [offset, gate_offset])
def route_bitlines(self):
""" Connect the output bit-lines to form the appropriate width mux """
from tech import cell_properties
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)
br_out_offset = br_offset - vector(0, (self.words_per_row + 2) * self.col_mux_stack_pitch)
bl_offset_begin = self.mux_inst[j].get_pin("bl_out").bc()
br_offset_begin = self.mux_inst[j].get_pin("br_out").bc()
bl_out_offset_end = bl_out_offset + vector(0, self.route_height)
br_out_offset_end = br_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_begin = br_offset_begin - vector(0, (self.words_per_row + 2) * self.sel_pitch)
if cell_properties.bitcell.mirror.y and j % 2:
tmp_bl_out_end = br_out_offset_end
tmp_br_out_end = bl_out_offset_end
else:
tmp_bl_out_end = bl_out_offset_end
tmp_br_out_end = br_out_offset_end
if (j % self.words_per_row) == 0:
# Create the metal1 to connect the n-way mux output from the pass gate
# 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)])
# Add the horizontal wires for the first bit
if j % self.words_per_row == 0:
bl_offset_end = self.mux_inst[j + self.words_per_row - 1].get_pin("bl_out").bc()
br_offset_end = self.mux_inst[j + self.words_per_row - 1].get_pin("br_out").bc()
bl_out_offset_end = bl_offset_end - vector(0, (self.words_per_row + 1) * self.sel_pitch)
br_out_offset_end = br_offset_end - vector(0, (self.words_per_row + 2) * self.sel_pitch)
self.add_path(self.sel_layer, [bl_out_offset_begin, bl_out_offset_end])
self.add_path(self.sel_layer, [br_out_offset_begin, br_out_offset_end])
# Extend the bitline output rails and gnd downward on the first bit of each n-way mux
self.add_layout_pin_segment_center(text="bl_out_{}".format(int(j / self.words_per_row)),
layer=self.col_mux_stack[2],
start=bl_out_offset,
end=tmp_bl_out_end)
layer=self.bitline_layer,
start=bl_offset_begin,
end=bl_out_offset_begin)
self.add_layout_pin_segment_center(text="br_out_{}".format(int(j / self.words_per_row)),
layer=self.col_mux_stack[2],
start=br_out_offset,
end=tmp_br_out_end)
# 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)
layer=self.bitline_layer,
start=br_offset_begin,
end=br_out_offset_begin)
else:
self.add_path(self.col_mux_stack[2], [bl_out_offset, bl_offset])
self.add_path(self.col_mux_stack[2], [br_out_offset, br_offset])
self.add_path(self.bitline_layer, [bl_out_offset_begin, bl_offset_begin])
self.add_path(self.bitline_layer, [br_out_offset_begin, br_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)
# This via is on the right of the wire
self.add_via_stack_center(from_layer=self.bitline_layer,
to_layer=self.sel_layer,
offset=bl_out_offset_begin,
directions=self.via_directions)
# 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):
"""Get the relative capacitance of the drain of the NMOS pass TX"""

View File

@ -7,15 +7,12 @@
#
import debug
import design
import math
from tech import drc
from tech import drc, layer
from vector import vector
from sram_factory import factory
from globals import OPTS
from tech import cell_properties
class wordline_driver(design.design):
class wordline_driver_array(design.design):
"""
Creates a Wordline Driver
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))
self.add_comment("rows: {0} cols: {1}".format(rows, cols))
self.bitcell_rows = rows
self.bitcell_cols = cols
self.rows = rows
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()
if not OPTS.netlist_only:
self.create_layout()
@ -51,7 +36,10 @@ class wordline_driver(design.design):
self.create_drivers()
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.route_layout()
self.route_vdd_gnd()
@ -61,104 +49,99 @@ class wordline_driver(design.design):
def add_pins(self):
# 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")
# 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("en", "INPUT")
self.add_pin("vdd", "POWER")
self.add_pin("gnd", "GROUND")
def add_modules(self):
self.and2 = factory.create(module_type="pand2",
height=self.cell_height,
size=self.bitcell_cols)
self.add_mod(self.and2)
self.wl_driver = factory.create(module_type="wordline_driver",
size=self.cols)
self.add_mod(self.wl_driver)
def route_vdd_gnd(self):
"""
Add a pin for each row of vdd/gnd which
are must-connects next level up.
"""
# Find the x offsets for where the vias/pins should be placed
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
if OPTS.tech_name == "sky130":
for name in ["vdd", "gnd"]:
supply_pin = self.and_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)
supply_pins = self.wld_inst[0].get_pins(name)
for pin in supply_pins:
self.add_layout_pin_segment_center(text=name,
layer=pin.layer,
start=pin.bc(),
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):
self.and_inst = []
for row in range(self.bitcell_rows):
self.wld_inst = []
for row in range(self.rows):
name_and = "wl_driver_and{}".format(row)
# add and2
self.and_inst.append(self.add_inst(name=name_and,
mod=self.and2))
self.wld_inst.append(self.add_inst(name=name_and,
mod=self.wl_driver))
self.connect_inst(["in_{0}".format(row),
"en",
"wl_{0}".format(row),
"vdd", "gnd"])
def setup_layout_constants(self):
# We may have more than one bitcell per decoder row
self.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):
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
well_gap = 2 * drc("pwell_to_nwell") + drc("nwell_enclose_active")
self.width = self.decoders_per_row * self.and2.width + well_gap
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)
self.width = self.wl_driver.width + well_gap
self.height = self.wl_driver.height * self.rows
def route_layout(self):
""" Route all of the signals """
# 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_pin = self.add_layout_pin(text="en",
layer="m2",
offset=en_bottom_pos,
height=self.height)
for inst_index in range(self.bitcell_rows):
and_inst = self.and_inst[inst_index]
row = math.floor(inst_index / self.decoders_per_row)
for row in range(self.rows):
and_inst = self.wld_inst[row]
# Drop a via
b_pin = and_inst.get_pin("B")
@ -167,18 +150,12 @@ class wordline_driver(design.design):
offset=b_pin.center())
# connect the decoder input pin to and2 A
a_pin = and_inst.get_pin("A")
a_pos = a_pin.center()
# must under the clk line in M1
self.add_layout_pin_segment_center(text="in_{0}".format(row),
layer="m1",
start=vector(0, a_pos.y),
end=a_pos)
self.copy_layout_pin(and_inst, "A", "in_{0}".format(row))
# output each WL on the right
wl_offset = and_inst.get_pin("Z").rc()
self.add_layout_pin_segment_center(text="wl_{0}".format(row),
layer="m1",
layer=self.route_layer,
start=wl_offset,
end=wl_offset - vector(self.m1_width, 0))
@ -189,7 +166,7 @@ class wordline_driver(design.design):
"""
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)
return stage_effort_list
@ -200,5 +177,5 @@ class wordline_driver(design.design):
the enable connections in the bank
"""
# 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

View File

@ -7,6 +7,7 @@
#
import design
import debug
from tech import drc
from sram_factory import factory
from vector import vector
from globals import OPTS
@ -18,7 +19,8 @@ class write_driver_array(design.design):
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)
debug.info(1, "Creating {0}".format(self.name))
self.add_comment("columns: {0}".format(columns))
@ -27,7 +29,12 @@ class write_driver_array(design.design):
self.columns = columns
self.word_size = word_size
self.write_size = write_size
self.column_offset = column_offset
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:
self.num_wmasks = int(self.word_size / self.write_size)
@ -60,9 +67,13 @@ class write_driver_array(design.design):
def create_layout(self):
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:
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.place_write_array()
@ -71,13 +82,16 @@ class write_driver_array(design.design):
self.DRC_LVS()
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")
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_br_name() + "_{0}".format(i), "OUTPUT")
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")
else:
self.add_pin(self.en_name, "INPUT")
@ -112,12 +126,34 @@ class write_driver_array(design.design):
if w == self.write_size:
w = 0
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:
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, "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):
from tech import cell_properties
if self.bitcell.width > self.driver.width:
@ -128,7 +164,7 @@ class write_driver_array(design.design):
index = int(i / self.words_per_row)
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"
xoffset = xoffset + self.driver.width
else:
@ -137,8 +173,22 @@ class write_driver_array(design.design):
base = vector(xoffset, 0)
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):
for i in range(self.word_size):
for i in range(self.word_size + self.num_spare_cols):
inst = self.driver_insts[i]
din_pin = inst.get_pin(inst.mod.din_name)
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(),
width=wmask_en_len - en_gap,
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:
inst = self.driver_insts[0]
self.add_layout_pin(text=self.en_name,

View File

@ -10,7 +10,7 @@ import debug
from sram_factory import factory
from vector import vector
from globals import OPTS
from tech import layer
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.
"""
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)
debug.info(1, "Creating {0}".format(self.name))
self.add_comment("columns: {0}".format(columns))
@ -28,7 +28,7 @@ class write_mask_and_array(design.design):
self.columns = columns
self.word_size = word_size
self.write_size = write_size
self.port = port
self.column_offset = column_offset
self.words_per_row = int(columns / word_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.
# Assume stage effort of 3 to compute the size
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)
def create_and2_array(self):
@ -93,7 +93,7 @@ class write_mask_and_array(design.design):
self.width = self.bitcell.width * self.columns
self.height = self.and2.height
for i in range(self.num_wmasks):
base = vector(i * self.wmask_en_len, 0)
self.and2_insts[i].place(base)
@ -108,8 +108,21 @@ class write_mask_and_array(design.design):
end=vector(self.width, en_pin.cy()))
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
self.copy_layout_pin(self.and2_insts[i], "A", "wmask_in_{0}".format(i))
self.copy_layout_pin(self.and2_insts[i], "Z", "wmask_out_{0}".format(i))
# Add via connections to metal3 for AND array's B pin
@ -121,16 +134,14 @@ class write_mask_and_array(design.design):
for supply in ["gnd", "vdd"]:
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"]:
supply_pin_left = self.and2_insts[0].get_pin(supply)
supply_pin_right = self.and2_insts[self.num_wmasks - 1].get_pin(supply)
self.add_path("m1", [supply_pin_left.lc(), supply_pin_right.rc()])
self.add_path(supply_pin_left.layer, [supply_pin_left.lc(), supply_pin_right.rc()])
def get_cin(self):
"""Get the relative capacitance of all the input connections in the bank"""
# The enable is connected to an and2 for every row.
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
c = sram_config(word_size=OPTS.word_size,
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))
output_extensions = ["sp", "v", "lib", "py", "html", "log"]

View File

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

View File

@ -13,16 +13,16 @@ from sram_factory import factory
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):
debug.info(1, "Creating pnand2 {}".format(name))
def __init__(self, name, size=1, height=None, vertical=False, add_wells=True):
debug.info(1, "Creating pand2 {}".format(name))
self.add_comment("size: {}".format(size))
self.vertical = vertical
self.size = size
# Creates the netlist and layout
pgate.pgate.__init__(self, name, height)
pgate.pgate.__init__(self, name, height, add_wells)
def create_netlist(self):
self.add_pins()
@ -30,17 +30,25 @@ class pand2(pgate.pgate):
self.create_insts()
def create_modules(self):
self.nand = factory.create(module_type="pnand2", height=self.height)
self.add_mod(self.nand)
self.nand = factory.create(module_type="pnand2",
height=self.height,
add_wells=self.vertical)
self.inv = factory.create(module_type="pdriver",
neg_polarity=True,
fanout=self.size,
height=self.height)
size_list=[self.size],
height=self.height,
add_wells=self.add_wells)
self.add_mod(self.nand)
self.add_mod(self.inv)
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.add_wires()
self.add_layout_pins()
@ -68,17 +76,60 @@ class pand2(pgate.pgate):
# 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))
if self.vertical:
# 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):
# nand Z to inv A
z1_pin = self.nand_inst.get_pin("Z")
a2_pin = self.inv_inst.get_pin("A")
mid1_point = vector(0.5 * (z1_pin.cx() + a2_pin.cx()), z1_pin.cy())
mid2_point = vector(mid1_point, a2_pin.cy())
self.add_path(self.route_layer,
[z1_pin.center(), mid1_point, mid2_point, a2_pin.center()])
if self.vertical:
route_layer = "m2"
self.add_via_stack_center(offset=z1_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):
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.
"""
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))
self.add_comment("size: {}".format(size))
self.vertical = vertical
self.size = size
# Creates the netlist and layout
pgate.pgate.__init__(self, name, height)
pgate.pgate.__init__(self, name, height, add_wells)
def create_netlist(self):
self.add_pins()
@ -31,16 +32,27 @@ class pand3(pgate.pgate):
def create_modules(self):
# Shield the cap, but have at least a stage effort of 4
self.nand = factory.create(module_type="pnand3", height=self.height)
self.add_mod(self.nand)
self.nand = factory.create(module_type="pnand3",
height=self.height,
add_wells=self.vertical)
self.inv = factory.create(module_type="pinv",
size=self.size,
height=self.height)
# Add the well tap to the inverter because when stacked
# vertically it is sometimes narrower
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)
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.add_wires()
self.add_layout_pins()
@ -69,18 +81,61 @@ class pand3(pgate.pgate):
# 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))
if self.vertical:
# 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):
# nand Z to inv A
z1_pin = self.nand_inst.get_pin("Z")
a2_pin = self.inv_inst.get_pin("A")
mid1_point = vector(0.5 * (z1_pin.cx()+a2_pin.cx()), z1_pin.cy())
mid2_point = vector(mid1_point, a2_pin.cy())
self.add_path(z1_pin.layer,
[z1_pin.center(), mid1_point, mid2_point, a2_pin.center()])
if self.vertical:
route_layer = "m2"
self.add_via_stack_center(offset=z1_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):
pin = self.inv_inst.get_pin("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",
size=self.size,
height=self.height)
height=self.height,
add_wells=False)
self.add_mod(self.inv2)
def create_insts(self):

View File

@ -17,13 +17,13 @@ class pdriver(pgate.pgate):
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))
self.stage_effort = 3
self.height = height
self.neg_polarity = neg_polarity
self.inverting = inverting
self.size_list = size_list
self.fanout = fanout
@ -31,11 +31,11 @@ class pdriver(pgate.pgate):
debug.error("Either fanout or size list must be specified.", -1)
if self.size_list and self.fanout != 0:
debug.error("Cannot specify both size_list and fanout.", -1)
if self.size_list and self.neg_polarity:
debug.error("Cannot specify both size_list and neg_polarity.", -1)
if self.size_list and self.inverting:
debug.error("Cannot specify both size_list and inverting.", -1)
# Creates the netlist and layout
pgate.pgate.__init__(self, name, height)
pgate.pgate.__init__(self, name, height, add_wells)
def compute_sizes(self):
# size_list specified
@ -47,9 +47,9 @@ class pdriver(pgate.pgate):
int(round(self.fanout ** (1 / self.stage_effort))))
# 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
elif not self.neg_polarity and (self.num_stages % 2):
elif not self.inverting and (self.num_stages % 2):
self.num_stages += 1
self.size_list = []
@ -73,9 +73,9 @@ class pdriver(pgate.pgate):
self.place_modules()
self.route_wires()
self.add_layout_pins()
self.width = self.inv_inst_list[-1].rx()
self.height = self.inv_inst_list[0].height
self.extend_wells()
self.route_supply_rails()
self.add_boundary()
@ -87,10 +87,13 @@ class pdriver(pgate.pgate):
def add_modules(self):
self.inv_list = []
add_well = self.add_wells
for size in self.size_list:
temp_inv = factory.create(module_type="pinv",
size=size,
height=self.height)
height=self.height,
add_wells=add_well)
add_well=False
self.inv_list.append(temp_inv)
self.add_mod(temp_inv)

View File

@ -14,8 +14,8 @@ from tech import layer, drc
from vector import vector
from globals import OPTS
if(OPTS.tech_name == "s8"):
from tech import nmos_bins, pmos_bins, accuracy_requirement
if(OPTS.tech_name == "sky130"):
from tech import nmos_bins, pmos_bins
class pgate(design.design):
@ -24,7 +24,7 @@ class pgate(design.design):
functions for parameterized gates.
"""
def __init__(self, name, height=None):
def __init__(self, name, height=None, add_wells=True):
""" Creates a generic cell """
design.design.__init__(self, name)
@ -33,7 +33,8 @@ class pgate(design.design):
elif not height:
# By default, something simple
self.height = 14 * self.m1_pitch
self.add_wells = add_wells
if "li" in layer:
self.route_layer = "li"
else:
@ -42,11 +43,14 @@ class pgate(design.design):
self.route_layer_space = getattr(self, "{}_space".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
# Assume the contact starts at the active edge
contact_to_vdd_rail_space = 0.5 * self.m1_width + self.m1_space
contact_to_vdd_rail_space = 0.5 * self.route_layer_width + self.route_layer_space
# This is a poly-to-poly of a flipped cell
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,
poly_to_poly_gate_space)
@ -145,28 +149,31 @@ class pgate(design.design):
height=contact.poly_contact.first_layer_width,
width=left_gate_offset.x - contact_offset.x)
return via
def extend_wells(self):
""" Extend the n/p wells to cover whole cell """
# This should match the cells in the cell library
self.nwell_y_offset = 0.48 * self.height
full_height = self.height + 0.5* self.m1_width
self.nwell_yoffset = 0.48 * self.height
full_height = self.height + 0.5 * self.m1_width
# FIXME: float rounding problem
if "nwell" in layer:
# 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,
full_height)
nwell_position = vector(0, self.nwell_y_offset) - vector(self.well_extend_active, 0)
nwell_height = nwell_max_offset - self.nwell_y_offset
nwell_position = vector(0, self.nwell_yoffset) - vector(self.well_extend_active, 0)
nwell_height = nwell_max_offset - self.nwell_yoffset
self.add_rect(layer="nwell",
offset=nwell_position,
width=self.well_width,
width=self.width + 2 * self.well_extend_active,
height=nwell_height)
if "vtg" in layer:
self.add_rect(layer="vtg",
offset=nwell_position,
width=self.well_width,
width=self.width + 2 * self.well_extend_active,
height=nwell_height)
# 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,
-0.5 * self.m1_width)
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",
offset=pwell_position,
width=self.well_width,
width=self.width + 2 * self.well_extend_active,
height=pwell_height)
if "vtg" in layer:
self.add_rect(layer="vtg",
offset=pwell_position,
width=self.well_width,
width=self.width + 2 * self.well_extend_active,
height=pwell_height)
if OPTS.tech_name == "sky130":
self.extend_implants()
def add_nwell_contact(self, pmos, pmos_pos):
""" Add an nwell contact next to the given pmos device. """
@ -239,6 +249,52 @@ class pgate(design.design):
# 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):
""" 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),
width=self.pwell_contact.mod.second_layer_width,
height=contact_offset.y)
# Now add the full active and implant for the NMOS
# active_offset = nmos_pos + vector(nmos.active_width,0)
# This might be needed if the spacing between the actives
@ -302,10 +358,18 @@ class pgate(design.design):
def determine_width(self):
""" 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
self.width = max(self.nwell_contact.rx(), self.pwell_contact.rx()) + self.m1_space + 0.5 * contact.m1_via.width
self.well_width = self.width + 2 * self.nwell_enclose_active
# Height is an input parameter, so it is not recomputed.
if self.add_wells:
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.
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
def bin_width(tx_type, target_width):
@ -327,16 +391,20 @@ class pgate(design.design):
base_bins = []
scaled_bins = []
scaling_factors = []
scaled_bins.append(bins[-1])
base_bins.append(bins[-1])
scaling_factors.append(1)
for width in bins[0:-1]:
for width in bins:
m = math.ceil(target_width / width)
base_bins.append(width)
scaling_factors.append(m)
scaled_bins.append(m * width)
select = bisect_left(scaled_bins, target_width)
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]
scaled_bin = scaled_bins[select]
selected_bin = base_bins[select]
@ -366,4 +434,4 @@ class pgate(design.design):
return(scaled_bins)
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 errors import drc_error
if(OPTS.tech_name == "s8"):
from tech import nmos_bins, pmos_bins, accuracy_requirement
if(OPTS.tech_name == "sky130"):
from tech import nmos_bins, pmos_bins
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
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,
"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.size = size
debug.check(self.size >= 1, "Must have a size greater than or equal to 1.")
self.nmos_size = size
self.pmos_size = beta * size
self.beta = beta
pgate.pgate.__init__(self, name, height)
pgate.pgate.__init__(self, name, height, add_wells)
def create_netlist(self):
""" Calls all functions related to the generation of the netlist """
@ -56,17 +60,18 @@ class pinv(pgate.pgate):
def create_layout(self):
""" Calls all functions related to the generation of the layout """
self.place_ptx()
self.add_well_contacts()
if self.add_wells:
self.add_well_contacts()
self.determine_width()
self.extend_wells()
self.route_supply_rails()
self.connect_rails()
self.route_input_gate(self.pmos_inst,
self.nmos_inst,
self.output_pos.y,
"A",
position="farleft")
self.route_outputs()
self.route_supply_rails()
self.connect_rails()
self.add_boundary()
def add_pins(self):
@ -86,7 +91,7 @@ class pinv(pgate.pgate):
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 == "s8":
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
@ -131,7 +136,7 @@ class pinv(pgate.pgate):
# Determine the number of mults for each to fit width
# into available space
if OPTS.tech_name != "s8":
if OPTS.tech_name != "sky130":
self.nmos_width = self.nmos_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)
@ -164,30 +169,37 @@ class pinv(pgate.pgate):
valid_pmos = []
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.sort(key = operator.itemgetter(1))
valid_nmos = []
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.sort(key = operator.itemgetter(1))
for bin in valid_pmos:
if bin[0]/bin[1] < pmos_height_available:
self.pmos_width = bin[0]/bin[1]
pmos_mults = valid_pmos[0][1]
pmos_mults = bin[1]
break
for bin in valid_nmos:
if bin[0]/bin[1] < nmos_height_available:
self.nmos_width = bin[0]/bin[1]
nmos_mults = valid_pmos[0][1]
nmos_mults = bin[1]
break
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):
""" Create the PMOS and NMOS transistors. """
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
from vector import vector
from sram_factory import factory
from tech import layer
class pinvbuf(pgate.pgate):
"""
@ -111,33 +111,45 @@ class pinvbuf(pgate.pgate):
mirror="MX")
def route_wires(self):
if "li" in layer:
route_stack = self.li_stack
else:
route_stack = self.m1_stack
# inv1 Z to inv2 A
z1_pin = self.inv1_inst.get_pin("Z")
a2_pin = self.inv2_inst.get_pin("A")
mid_point = vector(z1_pin.cx(), a2_pin.cy())
self.add_path("m1", [z1_pin.center(), mid_point, a2_pin.center()])
self.add_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
z2_pin = self.inv2_inst.get_pin("Z")
a3_pin = self.inv3_inst.get_pin("A")
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)
z1_pin = self.inv1_inst.get_pin("Z")
a4_pin = self.inv4_inst.get_pin("A")
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()])
self.add_via_center(layers=self.m1_stack,
offset=z1_pin.center())
self.add_via_stack_center(from_layer=z1_pin.layer,
to_layer=route_stack[2],
offset=z1_pin.center())
def add_layout_pins(self):
# Continous vdd rail along with label.
vdd_pin = self.inv1_inst.get_pin("vdd")
self.add_layout_pin(text="vdd",
layer="m1",
layer=vdd_pin.layer,
offset=vdd_pin.ll().scale(0, 1),
width=self.width,
height=vdd_pin.height())
@ -145,7 +157,7 @@ class pinvbuf(pgate.pgate):
# Continous vdd rail along with label.
gnd_pin = self.inv4_inst.get_pin("gnd")
self.add_layout_pin(text="gnd",
layer="m1",
layer=gnd_pin.layer,
offset=gnd_pin.ll().scale(0, 1),
width=self.width,
height=gnd_pin.height())
@ -153,31 +165,25 @@ class pinvbuf(pgate.pgate):
# Continous gnd rail along with label.
gnd_pin = self.inv1_inst.get_pin("gnd")
self.add_layout_pin(text="gnd",
layer="m1",
layer=gnd_pin.layer,
offset=gnd_pin.ll().scale(0, 1),
width=self.width,
height=vdd_pin.height())
z_pin = self.inv4_inst.get_pin("Z")
self.add_layout_pin_rect_center(text="Z",
layer="m2",
layer=z_pin.layer,
offset=z_pin.center())
self.add_via_center(layers=self.m1_stack,
offset=z_pin.center())
zb_pin = self.inv3_inst.get_pin("Z")
self.add_layout_pin_rect_center(text="Zb",
layer="m2",
layer=zb_pin.layer,
offset=zb_pin.center())
self.add_via_center(layers=self.m1_stack,
offset=zb_pin.center())
a_pin = self.inv1_inst.get_pin("A")
self.add_layout_pin_rect_center(text="A",
layer="m2",
layer=a_pin.layer,
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):
"""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)
# All rights reserved.
#
import contact
import pgate
import debug
from tech import drc, parameter, spice
@ -13,6 +12,7 @@ from globals import OPTS
from vector import vector
import logical_effort
from sram_factory import factory
import contact
class pnand2(pgate.pgate):
@ -20,7 +20,7 @@ class pnand2(pgate.pgate):
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.
"""
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 """
debug.info(2,
@ -38,12 +38,12 @@ class pnand2(pgate.pgate):
debug.check(size == 1, "Size 1 pnand2 is only supported now.")
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.pmos_width, self.tx_mults) = self.bin_width("pmos", self.pmos_width)
# Creates the netlist and layout
pgate.pgate.__init__(self, name, height)
pgate.pgate.__init__(self, name, height, add_wells)
def create_netlist(self):
self.add_pins()
@ -55,13 +55,14 @@ class pnand2(pgate.pgate):
self.setup_layout_constants()
self.place_ptx()
self.add_well_contacts()
if self.add_wells:
self.add_well_contacts()
self.route_output()
self.determine_width()
self.route_supply_rails()
self.connect_rails()
self.extend_wells()
self.route_inputs()
self.route_output()
self.add_boundary()
def add_pins(self):
@ -175,33 +176,52 @@ class pnand2(pgate.pgate):
def route_inputs(self):
""" Route the A and B inputs """
# Top of NMOS drain
nmos_pin = self.nmos2_inst.get_pin("D")
bottom_pin_offset = nmos_pin.uy()
self.inputA_yoffset = bottom_pin_offset + self.m1_pitch
self.inputB_yoffset = self.inputA_yoffset + self.m3_pitch
bottom_pin = self.nmos1_inst.get_pin("D")
# active contact metal to poly contact metal spacing
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
# 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
self.route_input_gate(self.pmos2_inst,
self.nmos2_inst,
self.inputB_yoffset,
"B",
position="center")
bpin = self.route_input_gate(self.pmos2_inst,
self.nmos2_inst,
self.inputB_yoffset,
"B",
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):
""" Route the Z output """
# One routing track layer below the PMOS contacts
route_layer_offset = 0.5 * self.route_layer_width + self.route_layer_space
output_yoffset = self.pmos1_inst.get_pin("D").by() - route_layer_offset
route_layer_offset = 0.5 * contact.poly_contact.second_layer_height + self.route_layer_space
self.output_yoffset = self.pmos1_inst.get_pin("D").by() - route_layer_offset
# PMOS1 drain
@ -213,7 +233,7 @@ class pnand2(pgate.pgate):
# Output pin
out_offset = vector(nmos_pin.cx() + self.route_layer_pitch,
output_yoffset)
self.output_yoffset)
# This routes on M2
# # Midpoints of the L routes go horizontal first then vertical

View File

@ -12,6 +12,7 @@ from vector import vector
import logical_effort
from sram_factory import factory
from globals import OPTS
import contact
class pnand3(pgate.pgate):
@ -19,7 +20,7 @@ class pnand3(pgate.pgate):
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.
"""
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 """
debug.info(2,
@ -40,12 +41,12 @@ class pnand3(pgate.pgate):
"Size 1 pnand3 is only supported now.")
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.pmos_width, self.tx_mults) = self.bin_width("pmos", self.pmos_width)
# Creates the netlist and layout
pgate.pgate.__init__(self, name, height)
pgate.pgate.__init__(self, name, height, add_wells)
def add_pins(self):
""" Adds pins for spice netlist """
@ -63,13 +64,14 @@ class pnand3(pgate.pgate):
self.setup_layout_constants()
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.route_supply_rails()
self.connect_rails()
self.extend_wells()
self.route_inputs()
self.route_output()
self.add_boundary()
def add_ptx(self):
@ -208,30 +210,45 @@ class pnand3(pgate.pgate):
def route_inputs(self):
""" 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()
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
self.route_input_gate(self.pmos1_inst,
self.nmos1_inst,
self.inputA_yoffset,
"A",
position="left")
bottom_pin = self.nmos1_inst.get_pin("D")
# active contact metal to poly contact metal spacing
active_contact_to_poly_contact = bottom_pin.uy() + self.m1_space + 0.5 * contact.poly_contact.second_layer_height
# active diffusion to poly contact spacing
# 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="left")
# Put B right on the well line
self.inputB_yoffset = self.inputA_yoffset - self.m1_pitch
self.route_input_gate(self.pmos2_inst,
self.nmos2_inst,
self.inputB_yoffset,
"B",
position="center")
self.inputB_yoffset = self.inputA_yoffset + self.m3_pitch
bpin = self.route_input_gate(self.pmos2_inst,
self.nmos2_inst,
self.inputB_yoffset,
"B",
position="center")
self.inputC_yoffset = self.inputB_yoffset - self.m1_pitch
self.route_input_gate(self.pmos3_inst,
self.nmos3_inst,
self.inputC_yoffset,
"C",
position="right")
self.inputC_yoffset = self.inputB_yoffset + self.m3_pitch
cpin = self.route_input_gate(self.pmos3_inst,
self.nmos3_inst,
self.inputC_yoffset,
"C",
position="right")
if OPTS.tech_name == "sky130":
self.add_enclosure([apin, bpin, cpin], "npc", drc("npc_enclose_poly"))
def route_output(self):
""" 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 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 """
debug.info(2,
@ -37,12 +37,12 @@ class pnor2(pgate.pgate):
debug.check(size==1, "Size 1 pnor2 is only supported now.")
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.pmos_width, self.tx_mults) = self.bin_width("pmos", self.pmos_width)
# Creates the netlist and layout
pgate.pgate.__init__(self, name, height)
pgate.pgate.__init__(self, name, height, add_wells)
def create_netlist(self):
self.add_pins()
@ -54,13 +54,14 @@ class pnor2(pgate.pgate):
self.setup_layout_constants()
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.route_supply_rails()
self.connect_rails()
self.extend_wells()
self.route_inputs()
self.route_output()
self.add_boundary()
def add_pins(self):
@ -194,22 +195,25 @@ class pnor2(pgate.pgate):
self.inputB_yoffset = bottom_pin_offset + self.m1_nonpref_pitch
self.inputA_yoffset = self.inputB_yoffset + self.m1_nonpref_pitch
self.route_input_gate(self.pmos2_inst,
self.nmos2_inst,
self.inputB_yoffset,
"B",
position="right",
directions=("V", "V"))
bpin = self.route_input_gate(self.pmos2_inst,
self.nmos2_inst,
self.inputB_yoffset,
"B",
position="right",
directions=("V", "V"))
# This will help with the wells and the input/output placement
self.route_input_gate(self.pmos1_inst,
self.nmos1_inst,
self.inputA_yoffset,
"A",
directions=("V", "V"))
apin = self.route_input_gate(self.pmos1_inst,
self.nmos1_inst,
self.inputA_yoffset,
"A",
directions=("V", "V"))
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):
""" Route the Z output """
# PMOS2 (right) drain

View File

@ -13,7 +13,7 @@ from tech import parameter
from vector import vector
from globals import OPTS
from sram_factory import factory
from tech import drc
from tech import drc, layer
class precharge(design.design):
@ -80,7 +80,7 @@ class precharge(design.design):
"""
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.pmos = factory.create(module_type="ptx",
width=self.ptx_width,
@ -105,21 +105,16 @@ class precharge(design.design):
# center of vdd rail
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.well_contact_pos,
directions=("V", "V"))
# Hack for li layers
if hasattr(self, "li_stack"):
self.add_via_center(layers=self.li_stack,
offset=self.well_contact_pos)
self.add_via_stack_center(from_layer=pmos_pin.layer,
to_layer=self.en_layer,
offset=pmos_pin.center(),
directions=("V", "V"))
def create_ptx(self):
"""
@ -148,13 +143,9 @@ class precharge(design.design):
# Compute the other pmos2 location,
# but determining offset to overlap the source and drain pins
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
bl_xoffset = self.bitcell_bl_pin.lx()
self.lower_pmos_position = vector(max(bl_xoffset - contact_xdiff,
self.nwell_enclose_active),
self.lower_pmos_position = vector(self.well_enclose_active + 0.5 * self.m1_width,
self.initial_yoffset)
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
# midway in the 4 M2 tracks
offset = self.lower_pmos_inst.get_pin("G").ul() \
+ vector(0, 0.5 * self.m2_pitch)
self.add_via_center(layers=self.poly_stack,
offset=offset)
if self.en_layer == "m2":
self.add_via_center(layers=self.m1_stack,
offset=offset)
if hasattr(self, "li_stack"):
self.add_via_center(layers=self.li_stack,
offset=offset)
# adds the en rail on metal1
pin_offset = self.lower_pmos_inst.get_pin("G").lr()
# This is an extra space down for some techs with contact to active spacing
contact_space = max(self.poly_space,
self.poly_contact_to_gate) + 0.5 * contact.poly_contact.first_layer_height
offset = pin_offset - vector(0, contact_space)
self.add_via_stack_center(from_layer="poly",
to_layer=self.en_layer,
offset=offset)
self.add_path("poly",
[self.lower_pmos_inst.get_pin("G").bc(), offset])
# adds the en rail
self.add_layout_pin_segment_center(text="en_bar",
layer=self.en_layer,
start=offset.scale(0, 1),
@ -221,17 +210,17 @@ class precharge(design.design):
# adds the contact from active to metal1
offset_height = self.upper_pmos1_inst.uy() + \
0.5 * contact.active_contact.height + \
contact.active_contact.height + \
self.nwell_extend_active
self.well_contact_pos = self.upper_pmos1_inst.get_pin("D").center().scale(1, 0) + \
vector(0, offset_height)
self.add_via_center(layers=self.active_stack,
offset=self.well_contact_pos,
implant_type="n",
well_type="n")
if hasattr(self, "li_stack"):
self.add_via_center(layers=self.li_stack,
offset=self.well_contact_pos)
self.well_contact = self.add_via_center(layers=self.active_stack,
offset=self.well_contact_pos,
implant_type="n",
well_type="n")
self.add_via_stack_center(from_layer=self.active_stack[2],
to_layer=self.bitline_layer,
offset=self.well_contact_pos)
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
"""
layer_width = drc("minwidth_" + self.bitline_layer)
layer_space = drc("{0}_to_{0}".format(self.bitline_layer))
layer_pitch = getattr(self, "{}_pitch".format(self.bitline_layer))
# 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)
pin_pos = vector(self.bl_xoffset, 0)
self.add_path(self.bitline_layer, [top_pos, pin_pos])
@ -259,7 +247,7 @@ class precharge(design.design):
end=top_pos)
# 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)
pin_pos = vector(self.br_xoffset, 0)
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
"""
# No contacts needed if M1
if self.bitline_layer == "m1":
return
# BL
lower_pin = self.lower_pmos_inst.get_pin("S")
self.lower_via = self.add_via_center(layers=self.m1_stack,
offset=lower_pin.center(),
directions=("V", "V"))
for lower_pin in [self.lower_pmos_inst.get_pin("S"), self.lower_pmos_inst.get_pin("D")]:
self.add_via_stack_center(from_layer=lower_pin.layer,
to_layer=self.bitline_layer,
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
upper_pin = self.upper_pmos1_inst.get_pin("S")
self.upper_via2 = self.add_via_center(layers=self.m1_stack,
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"))
for upper_pin in [self.upper_pmos1_inst.get_pin("S"), self.upper_pmos2_inst.get_pin("D")]:
self.add_via_stack_center(from_layer=upper_pin.layer,
to_layer=self.bitline_layer,
offset=upper_pin.center(),
directions=("V", "V"))
def connect_pmos(self, pmos_pin, bit_xoffset):
"""

View File

@ -16,6 +16,7 @@ import os
from globals import OPTS
from pgate import pgate
class ptx(design.design):
"""
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
you to connect the fingered gates and active for parallel devices.
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,
name="",
@ -126,13 +131,13 @@ class ptx(design.design):
# be decided in the layout later.
area_sd = 2.5 * self.poly_width * self.tx_width
perimeter_sd = 2 * self.poly_width + 2 * self.tx_width
if OPTS.tech_name == "s8":
# s8 technology is in microns
if OPTS.tech_name == "sky130" and OPTS.lvs_exe[0] == "calibre":
# sky130 simulation cannot use the mult parameter in simulation
(self.tx_width, self.mults) = pgate.bin_width(self.tx_type, self.tx_width)
main_str = "M{{0}} {{1}} {0} m={1} w={2} l={3} ".format(spice[self.tx_type],
self.mults,
self.tx_width,
drc("minwidth_poly"))
self.mults,
self.tx_width,
drc("minwidth_poly"))
# Perimeters are in microns
# 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,
@ -147,13 +152,17 @@ class ptx(design.design):
self.spice_device = main_str + area_str
self.spice.append("\n* ptx " + self.spice_device)
# LVS lib is always in SI units
if os.path.exists(OPTS.openram_tech + "lvs_lib"):
if OPTS.tech_name == "sky130" and OPTS.lvs_exe[0] == "calibre":
# 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.mults,
self.tx_width,
drc("minwidth_poly"))
def setup_layout_constants(self):
"""
@ -187,7 +196,7 @@ class ptx(design.design):
# This is the spacing between the poly gates
self.min_poly_pitch = self.poly_space + self.poly_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.contacted_poly_pitch,
self.contact_pitch)
@ -197,7 +206,7 @@ class ptx(design.design):
# Active width is determined by enclosure on both ends and contacted pitch,
# at least one poly and n-1 poly pitches
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
self.active_height = self.tx_width
@ -205,38 +214,23 @@ class ptx(design.design):
# Poly height must include poly extension over active
self.poly_height = self.tx_width + 2 * self.poly_extend_active
# The active offset is due to the well extension
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)
self.active_offset = vector([self.well_enclose_active] * 2)
# Well enclosure of active, ensure minwidth as well
well_name = "{}well".format(self.well_type)
if well_name in layer:
well_width_rule = drc("minwidth_" + well_name)
well_enclose_active = drc(well_name + "_enclose_active")
self.well_width = max(self.active_width + 2 * well_enclose_active,
self.well_width = max(self.active_width + 2 * self.well_enclose_active,
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)
# 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:
# 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_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)
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_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
self.poly_positions = []
@ -360,18 +354,18 @@ class ptx(design.design):
"""
Adding the diffusion (active region = diffusion region)
"""
self.add_rect(layer="active",
offset=self.active_offset,
width=self.active_width,
height=self.active_height)
self.active = self.add_rect(layer="active",
offset=self.active_offset,
width=self.active_width,
height=self.active_height)
# If the implant must enclose the active, shift offset
# and increase width/height
enclose_width = self.implant_enclose_active
enclose_offset = [enclose_width] * 2
self.add_rect(layer="{}implant".format(self.implant_type),
offset=self.active_offset - enclose_offset,
width=self.active_width + 2 * enclose_width,
height=self.active_height + 2 * enclose_width)
self.implant = self.add_rect(layer="{}implant".format(self.implant_type),
offset=self.active_offset - enclose_offset,
width=self.active_width + 2 * enclose_width,
height=self.active_height + 2 * enclose_width)
def add_well_implant(self):
"""
@ -386,10 +380,12 @@ class ptx(design.design):
well_ll = center_pos - vector(0.5 * self.well_width,
0.5 * self.well_height)
if well_name in layer:
self.add_rect(layer=well_name,
offset=well_ll,
width=self.well_width,
height=self.well_height)
well = self.add_rect(layer=well_name,
offset=well_ll,
width=self.well_width,
height=self.well_height)
setattr(self, well_name, well)
if "vtg" in layer:
self.add_rect(layer="vtg",
offset=well_ll,

View File

@ -160,11 +160,11 @@ class pwrite_driver(design.design):
track_xoff = self.get_m2_track(1)
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)
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)
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)
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)
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)
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
en_bar_loc = self.en_inst.get_pin("Z").uc()
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
bl_en_loc = self.bl_inst.get_pin("en_bar").center()
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_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
@ -237,21 +237,21 @@ class pwrite_driver(design.design):
en_loc = self.en_inst.get_pin("A").center()
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_via_stack("m2", "m3", en_rail)
self.add_via_stack_center("m2", "m3", en_rail)
# Start point in the track on the pin rail
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
bl_en_loc = self.bl_inst.get_pin("en").center()
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_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
self.add_wire(self.m3_stack,

View File

@ -11,6 +11,7 @@ from tech import drc, layer
from vector import vector
from sram_factory import factory
import logical_effort
from globals import OPTS
class single_level_column_mux(pgate.pgate):
@ -44,13 +45,22 @@ class single_level_column_mux(pgate.pgate):
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.height = self.nmos_upper.uy() + self.pin_height
self.connect_poly()
self.add_bitline_pins()
self.connect_bitlines()
self.add_wells()
self.add_pn_wells()
def add_modules(self):
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
self.ptx_width = self.tx_size * drc("minwidth_tx")
self.nmos = factory.create(module_type="ptx",
width=self.ptx_width,
add_source_contact=False,
add_drain_contact=False)
width=self.ptx_width)
self.add_mod(self.nmos)
def add_pins(self):
@ -69,29 +77,26 @@ class single_level_column_mux(pgate.pgate):
def add_bitline_pins(self):
""" Add the top and bottom pins to this cell """
bl_pin=self.bitcell.get_pin(self.bitcell_bl)
br_pin=self.bitcell.get_pin(self.bitcell_br)
bl_pos = vector(bl_pin.lx(), 0)
br_pos = vector(br_pin.lx(), 0)
bl_pos = vector(self.pin_pitch, 0)
br_pos = vector(self.width - self.pin_pitch, 0)
# bl and br
self.add_layout_pin(text="bl",
layer=bl_pin.layer,
layer=self.pin_layer,
offset=bl_pos + vector(0, self.height - self.pin_height),
height=self.pin_height)
self.add_layout_pin(text="br",
layer=br_pin.layer,
layer=self.pin_layer,
offset=br_pos + vector(0, self.height - self.pin_height),
height=self.pin_height)
# bl_out and br_out
self.add_layout_pin(text="bl_out",
layer=bl_pin.layer,
layer=self.pin_layer,
offset=bl_pos,
height=self.pin_height)
self.add_layout_pin(text="br_out",
layer=br_pin.layer,
layer=self.pin_layer,
offset=br_pos,
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"""
# 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)
self.nmos_lower = self.add_inst(name="mux_tx1",
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
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",
mod=self.nmos,
offset=nmos_upper_position)
self.connect_inst(["br", "sel", "br_out", "gnd"])
if OPTS.tech_name == "sky130":
self.add_implants()
def connect_poly(self):
""" Connect the poly gate of the two pass transistors """
# 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)
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
self.add_rect(layer="poly",
offset=offset,
@ -133,63 +141,30 @@ class single_level_column_mux(pgate.pgate):
def connect_bitlines(self):
""" 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")
br_pin = self.get_pin("br")
bl_out_pin = self.get_pin("bl_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_d_pin = self.nmos_lower.get_pin("D")
nmos_upper_s_pin = self.nmos_upper.get_pin("S")
nmos_upper_d_pin = self.nmos_upper.get_pin("D")
# Add vias to bl, br_out, nmos_upper/S, nmos_lower/D
self.add_via_center(layers=self.col_mux_stack,
offset=bl_pin.bc(),
directions=("V", "V"))
self.add_via_center(layers=self.col_mux_stack,
offset=br_out_pin.uc(),
directions=("V", "V"))
self.add_via_center(layers=self.col_mux_stack,
offset=nmos_upper_s_pin.center(),
directions=("V", "V"))
self.add_via_center(layers=self.col_mux_stack,
offset=nmos_lower_d_pin.center(),
directions=("V", "V"))
# 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")
self.add_via_stack_center(from_layer=bl_pin.layer,
to_layer=self.col_mux_stack[0],
offset=bl_pin.bc())
self.add_via_stack_center(from_layer=br_out_pin.layer,
to_layer=self.col_mux_stack[0],
offset=br_out_pin.uc())
self.add_via_stack_center(from_layer=nmos_upper_s_pin.layer,
to_layer=self.col_mux_stack[2],
offset=nmos_upper_s_pin.center())
self.add_via_stack_center(from_layer=nmos_lower_d_pin.layer,
to_layer=self.col_mux_stack[2],
offset=nmos_lower_d_pin.center())
# bl -> nmos_upper/D on metal1
# 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()),
nmos_lower_s_pin.center()])
# halfway up, move over
mid1 = br_pin.bc().scale(1,0.5) \
+ nmos_lower_d_pin.uc().scale(0,0.5)
mid2 = br_pin.bc().scale(0,0.5) \
+ nmos_lower_d_pin.uc().scale(1,0.5)
mid1 = br_pin.bc().scale(1, 0.5) \
+ nmos_lower_d_pin.uc().scale(0, 0.5)
mid2 = br_pin.bc().scale(0, 0.5) \
+ nmos_lower_d_pin.uc().scale(1, 0.5)
self.add_path(self.col_mux_stack[2],
[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
pwell contact (if it exists)
@ -237,8 +225,8 @@ class single_level_column_mux(pgate.pgate):
offset=active_pos)
# Add the M1->..->power_grid_layer stack
self.add_power_pin(name = "gnd",
loc = active_pos,
self.add_power_pin(name="gnd",
loc=active_pos,
start_layer="m1")
# 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 sram_base import sram_base
from contact import m2_via
from globals import OPTS
class sram_1bank(sram_base):
"""
@ -39,6 +39,11 @@ class sram_1bank(sram_base):
else:
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):
"""
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)
col_addr_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)
# 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 vertical channel, they rely on the horizontal channel non-preferred (contacted) pitch.
# So, m3 non-pref pitch means that this is routed on the m2 layer.
if self.write_size:
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.data_bus_gap = self.m4_nonpref_pitch * 2
self.col_addr_bus_gap = self.m2_nonpref_pitch * 2
self.col_addr_bus_size = self.m2_nonpref_pitch * (self.col_addr_size) + self.col_addr_bus_gap
# Spare wen are on a separate layer so not included
# 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
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.
# 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,
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])
# 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)
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:
# 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
# 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,
self.bank.bank_array_ur.y + self.control_logic_insts[port].height - \
(self.control_logic_insts[port].height - self.control_logic_insts[port].mod.control_logic_center.y)
+ 2 * self.bank.m2_gap)
self.bank.bank_array_ur.y
+ self.control_logic_insts[port].height
- 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")
# 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)
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):
"""
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:
# 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
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],
signal,
signal + "{}".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))
"clk",
"clk{}".format(port))
# Data input pins go to BOTTOM/TOP
din_ports = []
if port in self.write_ports:
for bit in range(self.word_size):
self.copy_layout_pin(self.data_dff_insts[port],
"din_{}".format(bit),
"din{0}[{1}]".format(port, bit))
for bit in range(self.word_size + self.num_spare_cols):
if OPTS.perimeter_pins:
p = self.add_perimeter_pin(name="din{0}[{1}]".format(port, bit),
pin=self.data_dff_insts[port].get_pin("din_{0}".format(bit)),
side=bottom_or_top,
bbox=bbox)
din_ports.append(p)
else:
self.copy_layout_pin(self.data_dff_insts[port],
"din_{}".format(bit),
"din{0}[{1}]".format(port, bit))
# Data output pins go to BOTTOM/TOP
if port in self.readwrite_ports and OPTS.perimeter_pins:
for bit in range(self.word_size + self.num_spare_cols):
# This should be routed next to the din pin
p = din_ports[bit]
self.add_layout_pin_rect_center(text="dout{0}[{1}]".format(port, bit),
layer=p.layer,
offset=p.center() + vector(self.m3_pitch, 0),
width=p.width(),
height=p.height())
elif port in self.read_ports:
for bit in range(self.word_size + self.num_spare_cols):
if OPTS.perimeter_pins:
# This should have a clear route to the perimeter if there are no din routes
self.add_perimeter_pin(name="dout{0}[{1}]".format(port, bit),
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:
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),
"wmask{0}[{1}]".format(port, bit))
"spare_wen{0}[{1}]".format(port, bit))
def route_layout(self):
""" Route a single bank SRAM """
@ -230,21 +343,81 @@ class sram_1bank(sram_base):
self.route_row_addr_dff()
if self.col_addr_dff:
self.route_col_addr_dff()
self.route_data_dff()
if self.write_size:
self.route_wmask_dff()
for port in self.all_ports:
self.route_dff(port)
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):
""" Route the clock network """
# This is the actual input to the SRAM
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
# This is something like a "spine" clock distribution. The two spines
# 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)
self.add_wire(self.m2_stack[::-1],
[dff_clk_pos, mid_pos, clk_steiner_pos])
if port in self.write_ports:
elif port in self.write_ports:
data_dff_clk_pin = self.data_dff_insts[port].get_pin("clk")
data_dff_clk_pos = data_dff_clk_pin.center()
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],
[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):
""" 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
src_pin = self.control_logic_insts[port].get_pin("rbl_bl")
dest_pin = self.bank_inst.get_pin("rbl_bl{}".format(port))
self.connect_hbus(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):
""" 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()
bank_pos = bank_pin.center()
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,
to_layer="m3",
offset=flop_pos)
def route_col_addr_dff(self):
""" Connect the output of the col flops to the bank pins """
for port in self.all_ports:
if port % 2:
offset = self.col_addr_dff_insts[port].ll() - vector(0, self.col_addr_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)
self.add_path("m3", [flop_pos, mid_pos])
self.add_via_stack_center(from_layer=bank_pin.layer,
to_layer="m3",
offset=mid_pos)
self.add_path(bank_pin.layer, [mid_pos, bank_pos])
def add_lvs_correspondence_points(self):
"""
@ -466,6 +533,9 @@ class sram_1bank(sram_base):
if self.write_size:
for inst in self.wmask_dff_insts:
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):
"""Removes data dff from search graph. """

View File

@ -37,6 +37,9 @@ class sram_base(design, verilog, lef):
else:
self.num_wmasks = 0
if not self.num_spare_cols:
self.num_spare_cols = 0
# For logical effort delay calculations.
self.all_mods_except_control_done = False
@ -44,7 +47,7 @@ class sram_base(design, verilog, lef):
""" Add pins for entire SRAM. """
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")
for port in self.all_ports:
@ -75,8 +78,10 @@ class sram_base(design, verilog, lef):
for port in self.write_ports:
for bit in range(self.num_wmasks):
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 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("vdd", "POWER")
@ -119,6 +124,8 @@ class sram_base(design, verilog, lef):
highest_coord = self.find_highest_coords()
self.width = highest_coord[0]
self.height = highest_coord[1]
self.add_boundary(ll=vector(0, 0),
ur=vector(self.width, self.height))
start_time = datetime.datetime.now()
# 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:
self.copy_power_pins(inst, "vdd")
self.copy_power_pins(inst, "gnd")
if not OPTS.route_supplies:
# Do not route the power supply (leave as must-connect pins)
return
@ -274,12 +281,16 @@ class sram_base(design, verilog, lef):
else:
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)
if self.write_size:
self.wmask_dff = factory.create("dff_array", module_name="wmask_dff", rows=1, columns=self.num_wmasks)
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)
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,
words_per_row=self.words_per_row,
word_size=self.word_size,
spare_columns=self.num_spare_cols,
sram=self,
port_type="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,
words_per_row=self.words_per_row,
word_size=self.word_size,
spare_columns=self.num_spare_cols,
sram=self,
port_type="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,
words_per_row=self.words_per_row,
word_size=self.word_size,
spare_columns=self.num_spare_cols,
sram=self,
port_type="r")
self.add_mod(self.control_logic_r)
@ -328,12 +342,12 @@ class sram_base(design, verilog, lef):
temp = []
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))
for port in self.all_ports:
temp.append("rbl_bl{0}".format(port))
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))
for port in self.all_ports:
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))
for bit in range(self.num_wmasks):
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:
temp.append("wl_en{0}".format(port))
temp.extend(["vdd", "gnd"])
@ -436,7 +452,7 @@ class sram_base(design, verilog, lef):
# inputs, outputs/output/bar
inputs = []
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))
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"])
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):
""" Add control logic instances """

View File

@ -14,18 +14,18 @@ from sram_factory import factory
class sram_config:
""" 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.num_words = num_words
self.write_size = write_size
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
self.words_per_row = words_per_row
self.compute_sizes()
self.compute_sizes()
def set_local_config(self, module):
""" 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
self.num_cols = int(self.words_per_row*self.word_size)
self.num_rows = 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_temp = int(self.num_words_per_bank/self.words_per_row)
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
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.bank_addr_size = self.col_addr_size + self.row_addr_size
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)
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
if um_overridden:
real_module_type = user_module_type
@ -109,11 +111,12 @@ class sram_factory:
return obj_item
# If no prefered module name is provided, we generate one.
if module_name is None:
# Use the default name if there are default arguments
if not module_name:
# Use the default name for the first cell.
# This is especially for library cells so that the
# 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
module_name = "{0}_{1}".format(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))
if not os.path.isfile(sp_name):
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)
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)
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)
e = factory.create(module_type="pdriver", fanout = 64)
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)
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)
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()

View File

@ -25,6 +25,11 @@ class pnand3_test(openram_test):
tx = factory.create(module_type="pnand3", size=1)
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()

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
import debug
#@unittest.skip("SKIPPING 04_driver_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
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)
globals.end_openram()

View File

@ -17,18 +17,16 @@ import debug
#@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):
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
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_r_ports = 1
OPTS.num_w_ports = 0
globals.setup_bitcell()
debug.info(2, "Testing 4x4 array for cell_1rw_1r")
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"))
globals.init_openram(config_file)
# check hierarchical decoder for multi-port
OPTS.bitcell = "pbitcell"
OPTS.num_rw_ports = 1
OPTS.num_w_ports = 0
OPTS.num_r_ports = 0
globals.setup_bitcell()
factory.reset()
debug.info(1, "Testing 16 row sample for hierarchical_decoder (multi-port case)")
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")
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)

View File

@ -1,7 +1,9 @@
#!/usr/bin/env python3
# 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.
#
import unittest
@ -13,21 +15,22 @@ from globals import OPTS
from sram_factory import factory
import debug
class replica_bitcell_array_test(openram_test):
class hierarchical_predecode2x4_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 = 0
OPTS.num_r_ports = 1
OPTS.num_w_ports = 0
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)
globals.setup_bitcell()
debug.info(1, "Testing sample for hierarchy_predecode2x4")
a = factory.create(module_type="hierarchical_predecode2x4")
self.local_check(a)
globals.end_openram()
# 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)
# checking hierarchical precode 2x4 for multi-port
OPTS.bitcell = "pbitcell"
OPTS.num_rw_ports = 1
OPTS.num_w_ports = 0
OPTS.num_r_ports = 0
globals.setup_bitcell()
debug.info(1, "Testing sample for hierarchy_predecode2x4 (multi-port case)")
a = factory.create(module_type="hierarchical_predecode2x4")
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"))
globals.init_openram(config_file)
# checking hierarchical precode 2x4 for single port
debug.info(1, "Testing sample for hierarchy_predecode2x4")
a = factory.create(module_type="hierarchical_predecode2x4")
self.local_check(a)

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