Thin-cell decoder changes.

Add hard decoder gates (nand, inv, pnan)
Add conditions for routing using LI layer in s8.
Generalize bus layers for decoders.
Move custom cells to own directory.
Fixed via directions, etc.
Add 4x16 hierarchical decoder and test case
This commit is contained in:
mrg 2020-05-14 11:20:37 -07:00
parent a305d788d7
commit 4a67f7dc71
49 changed files with 1991 additions and 552 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

@ -53,6 +53,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 +153,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 +169,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"""

View File

@ -86,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:
@ -96,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()
@ -139,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):

View File

@ -244,25 +244,27 @@ 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_pin(self, text):
@ -322,7 +324,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 +333,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 """
@ -692,6 +694,8 @@ class layout():
boundary_layer = "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]
(layer_number, layer_purpose) = techlayer[boundary_layer]
@ -1306,16 +1310,16 @@ 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":
width = round_to_grid(sqrt(drc["minarea_m3"]))

View File

@ -208,6 +208,7 @@ 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

View File

@ -7,7 +7,7 @@
#
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

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,77 @@
# 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
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)
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,77 @@
# 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
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)
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,77 @@
# 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
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)
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

@ -0,0 +1,138 @@
# 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 pgate
from sram_factory import factory
from globals import OPTS
class pand2_dec(pgate.pgate):
"""
This is an AND with configurable drive strength.
"""
def __init__(self, name, size=1, height=None, add_wells=True):
debug.info(1, "Creating pand2_dec {}".format(name))
self.add_comment("size: {}".format(size))
self.size = size
pgate.pgate.__init__(self, name, height, add_wells)
def create_netlist(self):
self.add_pins()
self.create_modules()
self.create_insts()
def create_modules(self):
if OPTS.tech_name == "s8":
self.nand = factory.create(module_type="nand2_dec")
else:
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):
self.width = self.nand.width + self.inv.width
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 == "s8":
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 == "s8":
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()

View File

@ -0,0 +1,149 @@
# 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 pgate
from sram_factory import factory
from globals import OPTS
class pand3_dec(pgate.pgate):
"""
This is an AND with configurable drive strength.
"""
def __init__(self, name, size=1, height=None, add_wells=True):
debug.info(1, "Creating pand3_dec {}".format(name))
self.add_comment("size: {}".format(size))
self.size = size
pgate.pgate.__init__(self, name, height, add_wells)
def create_netlist(self):
self.add_pins()
self.create_modules()
self.create_insts()
def create_modules(self):
if OPTS.tech_name == "s8":
self.nand = factory.create(module_type="nand3_dec")
else:
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):
self.width = self.nand.width + self.inv.width
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 == "s8":
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 == "s8":
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

@ -0,0 +1,149 @@
# 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 pgate
from sram_factory import factory
from globals import OPTS
class pand4_dec(pgate.pgate):
"""
This is an AND with configurable drive strength.
"""
def __init__(self, name, size=1, height=None, add_wells=True):
debug.info(1, "Creating pand4_dec {}".format(name))
self.add_comment("size: {}".format(size))
self.size = size
pgate.pgate.__init__(self, name, height, add_wells)
def create_netlist(self):
self.add_pins()
self.create_modules()
self.create_insts()
def create_modules(self):
if OPTS.tech_name == "s8":
self.nand = factory.create(module_type="nand4_dec")
else:
self.nand = factory.create(module_type="nand4_dec",
height=self.height)
self.inv = factory.create(module_type="inv_dec",
size=self.size)
self.add_mod(self.nand)
self.add_mod(self.inv)
def create_layout(self):
self.width = self.nand.width + self.inv.width
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 == "s8":
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 == "s8":
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

@ -12,7 +12,6 @@ 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 +27,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 +36,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()
@ -97,15 +57,22 @@ class hierarchical_decoder(design.design):
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",
height=self.cell_height)
if OPTS.tech_name == "s8":
self.and2 = factory.create(module_type="pand2_dec")
else:
self.and2 = factory.create(module_type="pand2_dec",
height=self.cell_height)
self.add_mod(self.and2)
self.and3 = factory.create(module_type="pand3",
height=self.cell_height)
if OPTS.tech_name == "s8":
self.and3 = factory.create(module_type="pand3_dec")
else:
self.and3 = factory.create(module_type="pand3_dec",
height=self.cell_height)
self.add_mod(self.and3)
# TBD
# self.and4 = factory.create(module_type="pand4_dec")
# self.add_mod(self.and4)
self.add_decoders()
@ -176,56 +143,63 @@ 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 = 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 == "s8":
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
# 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
# Extra bus space for supply contacts
self.input_routing_width = self.num_inputs * self.bus_pitch + self.bus_space
# 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.")
self.input_routing_width = (self.num_inputs + 1) * self.m2_pitch
# Calculates height and width of hierarchical decoder
# Add extra pitch for good measure
self.height = max(self.predecoder_height, self.row_decoder_height) + self.m2_pitch
self.width = self.input_routing_width + self.predecoder_width \
self.height = max(self.predecoder_height, self.row_decoder_height) + self.bus_space
if (self.num_inputs == 4 or self.num_inputs == 5):
self.nand_width = self.and2.width
else:
self.nand_width = self.and3.width
self.width = self.input_routing_width \
+ self.predecoder_width \
+ self.internal_routing_width \
+ self.decoders_per_row * nand_width + self.inv.width
+ self.nand_width \
+ self.m1_space
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 +209,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()
@ -253,9 +227,7 @@ class hierarchical_decoder(design.design):
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)
@ -269,9 +241,7 @@ class hierarchical_decoder(design.design):
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 +252,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 +334,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 +402,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 +422,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 +430,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 +450,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 +470,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 +480,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 +496,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 +505,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 +521,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 +537,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 == "s8":
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].x, 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].x, 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].x, 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,24 @@
import debug
import design
import math
import contact
from vector import vector
from sram_factory import factory
from tech import cell_properties
from globals import OPTS
class hierarchical_predecode(design.design):
"""
Pre 2x4 and 3x8 decoder shared code.
Pre 2x4 and 3x8 and TBD 4x16 decoder shared code.
"""
def __init__(self, name, input_number, height=None):
self.number_of_inputs = input_number
if not height:
b = factory.create(module_type="bitcell")
try:
self.cell_multiple = cell_properties.bitcell.decoder_bitcell_multiple
except AttributeError:
self.cell_multiple = 1
self.cell_height = self.cell_multiple * b.height
self.cell_height = b.height
else:
self.cell_height = height
self.number_of_outputs = int(math.pow(2, self.number_of_inputs))
design.design.__init__(self, name)
@ -44,34 +39,73 @@ class hierarchical_predecode(design.design):
def add_modules(self):
""" Add the INV and AND gate modules """
self.inv = factory.create(module_type="pinv",
height=self.cell_height)
self.add_mod(self.inv)
self.add_and(self.number_of_inputs)
self.add_mod(self.and_mod)
def add_and(self, inputs):
""" Create the NAND for the predecode input stage """
if inputs==2:
self.and_mod = factory.create(module_type="pand2",
# FIXME: Default parms are required for hard cells for now.
if self.number_of_inputs == 2:
self.and_mod = factory.create(module_type="pand2_dec",
height=self.cell_height)
elif inputs==3:
self.and_mod = factory.create(module_type="pand3",
elif self.number_of_inputs == 3:
self.and_mod = factory.create(module_type="pand3_dec",
height=self.cell_height)
elif self.number_of_inputs == 4:
self.and_mod = factory.create(module_type="pand4_dec",
height=self.cell_height)
else:
debug.error("Invalid number of predecode inputs: {}".format(inputs), -1)
debug.error("Invalid number of predecode inputs: {}".format(self.number_of_inputs), -1)
self.add_mod(self.and_mod)
# This uses the pinv_dec parameterized cell
self.inv = factory.create(module_type="inv_dec",
height=self.cell_height,
size=1)
self.add_mod(self.inv)
def create_layout(self):
""" The general organization is from left to right:
1) a set of M2 rails for input signals
2) a set of inverters to invert input signals
3) a set of M2 rails for the vdd, gnd, inverted inputs, inputs
4) a set of AND gates for inversion
"""
self.setup_layout_constraints()
self.route_rails()
self.place_input_inverters()
self.place_and_array()
self.route()
self.add_boundary()
self.DRC_LVS()
def setup_layout_constraints(self):
# Inputs to cells are on input layer
# Outputs from cells are on output layer
if OPTS.tech_name == "s8":
self.bus_layer = "m1"
self.bus_directions = None
self.bus_pitch = self.m1_pitch
self.bus_space = 1.5 * self.m1_space
self.input_layer = "li"
self.output_layer = "m2"
self.output_layer_pitch = self.m2_pitch
else:
self.bus_layer = "m2"
self.bus_directions = None
self.bus_pitch = self.m2_pitch
self.bus_space = self.m2_space
# This requires a special jog to ensure to conflicts with the output layers
self.input_layer = "m1"
self.output_layer = "m1"
self.output_layer_pitch = self.m1_pitch
self.height = self.number_of_outputs * self.and_mod.height
# x offset for input inverters
self.x_off_inv_1 = self.number_of_inputs * self.m2_pitch + self.m2_space
# +1 input for spacing for supply rail contacts
self.x_off_inv_1 = (self.number_of_inputs + 1) * self.bus_pitch + self.bus_pitch
# x offset to AND decoder includes the left rails, mid rails and inverters, plus two extra m2 pitches
self.x_off_and = self.x_off_inv_1 + self.inv.width + (2 * self.number_of_inputs + 2) * self.m2_pitch
# x offset to AND decoder includes the left rails, mid rails and inverters, plus two extra bus pitches
self.x_off_and = self.x_off_inv_1 + self.inv.width + (2 * self.number_of_inputs + 2) * self.bus_pitch
# x offset to output inverters
self.width = self.x_off_and + self.and_mod.width
@ -79,28 +113,30 @@ class hierarchical_predecode(design.design):
def route_rails(self):
""" Create all of the rails for the inputs and vdd/gnd/inputs_bar/inputs """
input_names = ["in_{}".format(x) for x in range(self.number_of_inputs)]
offset = vector(0.5 * self.m2_width, self.m3_pitch)
self.input_rails = self.create_vertical_pin_bus(layer="m2",
offset=offset,
names=input_names,
length=self.height - 2 * self.m1_pitch)
# Offsets for the perimeter spacing to other modules
# This uses m3 pitch to leave space for power routes
offset = vector(self.bus_pitch, self.bus_pitch)
self.input_rails = self.create_vertical_bus(layer=self.bus_layer,
offset=offset,
names=input_names,
length=self.height - 2 * self.bus_pitch)
invert_names = ["Abar_{}".format(x) for x in range(self.number_of_inputs)]
non_invert_names = ["A_{}".format(x) for x in range(self.number_of_inputs)]
decode_names = invert_names + non_invert_names
offset = vector(self.x_off_inv_1 + self.inv.width + 2 * self.m2_pitch, self.m3_pitch)
self.decode_rails = self.create_vertical_bus(layer="m2",
offset = vector(self.x_off_inv_1 + self.inv.width + self.bus_pitch, self.bus_pitch)
self.decode_rails = self.create_vertical_bus(layer=self.bus_layer,
offset=offset,
names=decode_names,
length=self.height - 2 * self.m1_pitch)
length=self.height - 2 * self.bus_pitch)
def create_input_inverters(self):
""" Create the input inverters to invert input signals for the decode stage. """
self.in_inst = []
self.inv_inst = []
for inv_num in range(self.number_of_inputs):
name = "pre_inv_{0}".format(inv_num)
self.in_inst.append(self.add_inst(name=name,
mod=self.inv))
self.inv_inst.append(self.add_inst(name=name,
mod=self.inv))
self.connect_inst(["in_{0}".format(inv_num),
"inbar_{0}".format(inv_num),
"vdd", "gnd"])
@ -108,6 +144,7 @@ class hierarchical_predecode(design.design):
def place_input_inverters(self):
""" Place the input inverters to invert input signals for the decode stage. """
for inv_num in range(self.number_of_inputs):
if (inv_num % 2 == 0):
y_off = inv_num * (self.inv.height)
mirror = "R0"
@ -115,8 +152,8 @@ class hierarchical_predecode(design.design):
y_off = (inv_num + 1) * (self.inv.height)
mirror="MX"
offset = vector(self.x_off_inv_1, y_off)
self.in_inst[inv_num].place(offset=offset,
mirror=mirror)
self.inv_inst[inv_num].place(offset=offset,
mirror=mirror)
def create_and_array(self, connections):
""" Create the AND stage for the decodes """
@ -151,21 +188,30 @@ class hierarchical_predecode(design.design):
def route_inputs_to_rails(self):
""" Route the uninverted inputs to the second set of rails """
top_and_gate = self.and_inst[-1]
for num in range(self.number_of_inputs):
# route one signal next to each vdd/gnd rail since this is
# typically where the p/n devices are and there are no
# pins in the and gates.
y_offset = (num + self.number_of_inputs) * self.inv.height + contact.m2_via.width + self.m2_space
if num == 0:
pin = top_and_gate.get_pin("A")
elif num == 1:
pin = top_and_gate.get_pin("B")
elif num == 2:
pin = top_and_gate.get_pin("C")
elif num == 3:
pin = top_and_gate.get_pin("D")
else:
debug.error("Too many inputs for predecoder.", -1)
y_offset = pin.cy()
in_pin = "in_{}".format(num)
a_pin = "A_{}".format(num)
in_pos = vector(self.input_rails[in_pin].x, y_offset)
a_pos = vector(self.decode_rails[a_pin].x, y_offset)
self.add_path("m1", [in_pos, a_pos])
self.add_via_stack_center(from_layer="m1",
to_layer="m2",
self.add_path(self.input_layer, [in_pos, a_pos])
self.add_via_stack_center(from_layer=self.input_layer,
to_layer=self.bus_layer,
offset=[self.input_rails[in_pin].x, y_offset])
self.add_via_stack_center(from_layer="m1",
to_layer="m2",
self.add_via_stack_center(from_layer=self.input_layer,
to_layer=self.bus_layer,
offset=[self.decode_rails[a_pin].x, y_offset])
def route_output_and(self):
@ -188,31 +234,47 @@ class hierarchical_predecode(design.design):
for inv_num in range(self.number_of_inputs):
out_pin = "Abar_{}".format(inv_num)
in_pin = "in_{}".format(inv_num)
inv_out_pin = self.inv_inst[inv_num].get_pin("Z")
inv_out_pos = inv_out_pin.rc()
# add output so that it is just below the vdd or gnd rail
# since this is where the p/n devices are and there are no
# pins in the and gates.
y_offset = (inv_num + 1) * self.inv.height - 3 * self.m1_space
inv_out_pin = self.in_inst[inv_num].get_pin("Z")
inv_out_pos = inv_out_pin.rc()
right_pos = inv_out_pos + vector(self.inv.width - self.inv.get_pin("Z").lx(), 0)
rail_pos = vector(self.decode_rails[out_pin].x, y_offset)
self.add_path(inv_out_pin.layer, [inv_out_pos, right_pos, vector(right_pos.x, y_offset), rail_pos])
if OPTS.tech_name == "s8":
rail_pos = vector(self.decode_rails[out_pin].x, inv_out_pos.y)
self.add_path(self.output_layer, [inv_out_pos, rail_pos])
else:
y_offset = (inv_num + 1) * self.inv.height - self.output_layer_pitch
right_pos = inv_out_pos + vector(self.inv.width - self.inv.get_pin("Z").rx(), 0)
rail_pos = vector(self.decode_rails[out_pin].x, y_offset)
self.add_path(self.output_layer, [inv_out_pos, right_pos, vector(right_pos.x, y_offset), rail_pos])
self.add_via_stack_center(from_layer=inv_out_pin.layer,
to_layer="m2",
offset=rail_pos)
to_layer=self.output_layer,
offset=inv_out_pos)
self.add_via_stack_center(from_layer=self.output_layer,
to_layer=self.bus_layer,
offset=rail_pos,
directions=self.bus_directions)
# route input
pin = self.in_inst[inv_num].get_pin("A")
inv_in_pos = pin.lc()
pin = self.inv_inst[inv_num].get_pin("A")
inv_in_pos = pin.center()
in_pos = vector(self.input_rails[in_pin].x, inv_in_pos.y)
self.add_path("m1", [in_pos, inv_in_pos])
self.add_path(self.input_layer, [in_pos, inv_in_pos])
self.add_via_stack_center(from_layer=pin.layer,
to_layer="m1",
to_layer=self.input_layer,
offset=inv_in_pos)
self.add_via_stack_center(from_layer="m1",
to_layer="m2",
offset=in_pos)
via=self.add_via_stack_center(from_layer=self.input_layer,
to_layer=self.bus_layer,
offset=in_pos)
# Create the input pin at this location on the rail
self.add_layout_pin_rect_center(text=in_pin,
layer=self.bus_layer,
offset=in_pos,
height=via.mod.second_layer_height,
width=via.mod.second_layer_width)
def route_and_to_rails(self):
# This 2D array defines the connection mapping
@ -231,43 +293,67 @@ class hierarchical_predecode(design.design):
pin = self.and_inst[k].get_pin(gate_pin)
pin_pos = pin.center()
rail_pos = vector(self.decode_rails[rail_pin].x, pin_pos.y)
self.add_path("m1", [rail_pos, pin_pos])
self.add_via_stack_center(from_layer="m1",
to_layer="m2",
offset=rail_pos)
self.add_path(self.input_layer, [rail_pos, pin_pos])
self.add_via_stack_center(from_layer=self.input_layer,
to_layer=self.bus_layer,
offset=rail_pos,
directions=self.bus_directions)
if gate_pin == "A":
direction = None
else:
direction = ("H", "H")
self.add_via_stack_center(from_layer=pin.layer,
to_layer="m1",
to_layer=self.input_layer,
offset=pin_pos,
directions=direction)
def route_vdd_gnd(self):
""" Add a pin for each row of vdd/gnd which are must-connects next level up. """
# Find the x offsets for where the vias/pins should be placed
in_xoffset = self.in_inst[0].rx() + self.m1_space
# out_xoffset = self.and_inst[0].cx() + self.m1_space
for num in range(0, self.number_of_outputs):
# this will result in duplicate polygons for rails, but who cares
# Route both supplies
# In s8, we use hand-made decoder cells with vertical power
if OPTS.tech_name == "s8":
for n in ["vdd", "gnd"]:
and_pin = self.and_inst[num].get_pin(n)
supply_offset = and_pin.ll().scale(0, 1)
self.add_rect(layer=and_pin.layer,
offset=supply_offset,
width=self.and_inst[num].rx())
# This makes a wire from top to bottom for both inv and and gates
for i in [self.inv_inst, self.and_inst]:
bot_pins = i[0].get_pins(n)
top_pins = i[-1].get_pins(n)
for (bot_pin, top_pin) in zip(bot_pins, top_pins):
self.add_rect(layer=bot_pin.layer,
offset=vector(bot_pin.lx(), self.bus_pitch),
width=bot_pin.width(),
height=top_pin.uy() - self.bus_pitch)
# This adds power vias at the top of each cell
# (except the last to keep them inside the boundary)
for i in self.inv_inst[:-1:2] + self.and_inst[:-1:2]:
pins = i.get_pins(n)
for pin in pins:
self.add_power_pin(name=n,
loc=pin.uc(),
start_layer=pin.layer)
self.add_power_pin(name=n,
loc=pin.uc(),
start_layer=pin.layer)
# In other techs, we are using standard cell decoder cells with horizontal power
else:
for num in range(0, self.number_of_outputs):
# Add pins in two locations
for xoffset in [in_xoffset]:
pin_pos = vector(xoffset, and_pin.cy())
self.add_power_pin(name=n,
loc=pin_pos,
start_layer=and_pin.layer)
# Route both supplies
for n in ["vdd", "gnd"]:
and_pins = self.and_inst[num].get_pins(n)
for and_pin in and_pins:
self.add_segment_center(layer=and_pin.layer,
start=vector(0, and_pin.cy()),
end=vector(self.width, and_pin.cy()))
# Add pins in two locations
for xoffset in [self.inv_inst[0].lx() - self.bus_space,
self.and_inst[0].lx() - self.bus_space]:
pin_pos = vector(xoffset, and_pin.cy())
self.add_power_pin(name=n,
loc=pin_pos,
start_layer=and_pin.layer)

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

@ -8,7 +8,7 @@ import debug
import design
from sram_factory import factory
from vector import vector
from tech import layer
from globals import OPTS
@ -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)
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

@ -469,45 +469,37 @@ 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))
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)
# 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())
# Add driver on mask output
self.add_via_center(layers=self.m1_stack,
offset=wmask_pos)
# Add via for the write driver array's enable input
self.add_via_center(layers=self.m1_stack,
offset=end_pos)
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 +508,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 """

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 == "s8":
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

@ -134,6 +134,10 @@ class options(optparse.Values):
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"

View File

@ -13,7 +13,7 @@ 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, vertical=False, add_wells=True):
debug.info(1, "Creating pand2 {}".format(name))

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, add_wells=True):
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,8 +31,8 @@ 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, add_wells)
@ -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 = []

View File

@ -44,7 +44,6 @@ class pgate(design.design):
self.route_layer_pitch = getattr(self, "{}_pitch".format(self.route_layer))
# This is the space from a S/D contact to the supply rail
# Assume the contact starts at the active edge
contact_to_vdd_rail_space = 0.5 * self.m1_width + self.m1_space
# This is a poly-to-poly of a flipped cell
poly_to_poly_gate_space = self.poly_extend_active + self.poly_space

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

@ -0,0 +1,217 @@
# 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
from vector import vector
from globals import OPTS
from sram_factory import factory
if(OPTS.tech_name == "s8"):
from tech import nmos_bins, pmos_bins, accuracy_requirement
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 == "s8":
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 == "s8":
(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. """
from tech import layer
if "pwell" in layer:
ll = self.nmos_inst.ll() - self.nmos_inst.mod.active_offset
ur = self.nmos_inst.ur() + self.nmos_inst.mod.active_offset
self.add_rect(layer="pwell",
offset=ll,
width=ur.x - ll.x,
height=self.height - ll.y)
if "nwell" in layer:
ll = self.pmos_inst.ll() - self.pmos_inst.mod.active_offset
ur = self.pmos_inst.ur() + self.pmos_inst.mod.active_offset
self.add_rect(layer="nwell",
offset=ll - vector(self.nwell_enclose_active, 0),
width=ur.x - ll.x + self.nwell_enclose_active,
height=self.height - ll.y + 2 * self.nwell_enclose_active)
def place_ptx(self):
"""
"""
# 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
# center the transistor in the y-dimension
y_offset = self.nmos.width + self.active_space
self.nmos_pos = vector(x_offset, y_offset)
self.nmos_inst.place(self.nmos_pos)
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.height + 2 * self.poly_extend_active + 2 * self.well_extend_active + drc("pwell_to_nwell")
self.pmos_pos = self.nmos_pos + vector(xoffset, 0)
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)
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

@ -212,7 +212,10 @@ class pnand3(pgate.pgate):
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
# This is a more compact offset, but the bottom one works better in the decoders to "center" the pins
# in the height of the gates
self.inputA_yoffset = self.output_yoffset - 0.5 * self.route_layer_width - self.route_layer_space
# self.inputA_yoffset = self.output_yoffset - self.m1_pitch
self.route_input_gate(self.pmos1_inst,
self.nmos1_inst,
self.inputA_yoffset,

View File

@ -386,10 +386,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

@ -0,0 +1,155 @@
# 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):
if OPTS.tech_name == "s8":
self.nand = factory.create(module_type="nand2_dec")
self.height = self.nand.height
else:
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 == "s8":
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 == "s8":
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

@ -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

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,39 @@
#!/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 pand2_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
import pand2_dec
debug.info(2, "Testing pand2 gate 4x")
a = pand2_dec.pand2_dec(name="pand2x4", size=4)
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,39 @@
#!/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 pand3_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
import pand3_dec
debug.info(2, "Testing pand3 gate 4x")
a = pand3_dec.pand3_dec(name="pand3x4", size=4)
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,39 @@
#!/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 pand3_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
import pand3_dec
debug.info(2, "Testing pand3 gate 4x")
a = pand3_dec.pand3_dec(name="pand3x4", size=4)
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,35 @@
#!/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)
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,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

@ -21,11 +21,17 @@ class hierarchical_decoder_test(openram_test):
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.bitcell = "bitcell_1rw_1r"
OPTS.num_rw_ports = 1
OPTS.num_r_ports = 1
OPTS.num_w_ports = 0
# 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)

View File

@ -21,7 +21,12 @@ 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
# Use the 2 port cell since it is usually bigger/easier
OPTS.bitcell = "bitcell_1rw_1r"
OPTS.num_rw_ports = 1
OPTS.num_r_ports = 1
OPTS.num_w_ports = 0
debug.info(1, "Testing sample for hierarchy_predecode2x4")
a = factory.create(module_type="hierarchical_predecode2x4")
self.local_check(a)

View File

@ -21,7 +21,12 @@ class hierarchical_predecode3x8_test(openram_test):
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
globals.init_openram(config_file)
# checking hierarchical precode 3x8 for single port
# Use the 2 port cell since it is usually bigger/easier
OPTS.bitcell = "bitcell_1rw_1r"
OPTS.num_rw_ports = 1
OPTS.num_r_ports = 1
OPTS.num_w_ports = 0
debug.info(1, "Testing sample for hierarchy_predecode3x8")
a = factory.create(module_type="hierarchical_predecode3x8")
self.local_check(a)

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
@unittest.skip("SKIPPING hierarchical_predecode4x16_test")
class hierarchical_predecode4x16_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.bitcell = "bitcell_1rw_1r"
OPTS.num_rw_ports = 1
OPTS.num_r_ports = 1
OPTS.num_w_ports = 0
debug.info(1, "Testing sample for hierarchy_predecode4x16")
a = factory.create(module_type="hierarchical_predecode4x16")
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

@ -15,9 +15,8 @@ from globals import OPTS
from sram_factory import factory
import debug
#@unittest.skip("SKIPPING 04_driver_test")
class wordline_driver_pbitcell_test(openram_test):
class wordline_driver_array_pbitcell_test(openram_test):
def runTest(self):
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
@ -31,7 +30,7 @@ class wordline_driver_pbitcell_test(openram_test):
factory.reset()
debug.info(2, "Checking driver (multi-port case)")
tx = factory.create(module_type="wordline_driver", rows=8, cols=64)
tx = factory.create(module_type="wordline_driver_array", rows=8, cols=64)
self.local_check(tx)
globals.end_openram()

View File

@ -0,0 +1,37 @@
#!/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 wordline_driver_array_test(openram_test):
def runTest(self):
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
globals.init_openram(config_file)
# check wordline driver for single port
debug.info(2, "Checking driver")
tx = factory.create(module_type="wordline_driver_array", rows=8, cols=32)
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

@ -20,10 +20,10 @@ class port_data_1rw_1r_test(openram_test):
globals.init_openram(config_file)
from sram_config import sram_config
OPTS.bitcell = "bitcell_1w_1r"
OPTS.num_rw_ports = 0
OPTS.bitcell = "bitcell_1rw_1r"
OPTS.num_rw_ports = 1
OPTS.num_r_ports = 1
OPTS.num_w_ports = 1
OPTS.num_w_ports = 0
c = sram_config(word_size=4,
num_words=16)

View File

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

View File

@ -60,6 +60,7 @@ class openram_test(unittest.TestCase):
#debug.info(0,"Archiving failed files to {}.zip".format(zip_file))
#shutil.make_archive(zip_file, 'zip', OPTS.openram_temp)
debug.warning("DRC failed but LVS passed: {}".format(a.name))
#self.fail("DRC failed but LVS passed: {}".format(a.name))
elif drc_result != 0:
#zip_file = "/tmp/{0}_{1}".format(a.name,os.getpid())
#debug.info(0,"Archiving failed files to {}.zip".format(zip_file))