mirror of
https://github.com/VLSIDA/OpenRAM.git
synced 2026-08-22 14:17:35 +02:00
merge conflict - port data
This commit is contained in:
+10
-1
@@ -1,7 +1,7 @@
|
|||||||
before_script:
|
before_script:
|
||||||
- . /home/gitlab-runner/setup-paths.sh
|
- . /home/gitlab-runner/setup-paths.sh
|
||||||
- export OPENRAM_HOME="`pwd`/compiler"
|
- export OPENRAM_HOME="`pwd`/compiler"
|
||||||
- export OPENRAM_TECH="`pwd`/technology"
|
- export OPENRAM_TECH="`pwd`/technology:/home/PDKs/skywater-tech"
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- test
|
- test
|
||||||
@@ -25,6 +25,15 @@ scn4m_subm:
|
|||||||
- .coverage.*
|
- .coverage.*
|
||||||
expire_in: 1 week
|
expire_in: 1 week
|
||||||
|
|
||||||
|
# s8:
|
||||||
|
# stage: test
|
||||||
|
# script:
|
||||||
|
# - coverage run -p $OPENRAM_HOME/tests/regress.py -t s8
|
||||||
|
# artifacts:
|
||||||
|
# paths:
|
||||||
|
# - .coverage.*
|
||||||
|
# expire_in: 1 week
|
||||||
|
|
||||||
coverage:
|
coverage:
|
||||||
stage: coverage
|
stage: coverage
|
||||||
script:
|
script:
|
||||||
|
|||||||
@@ -53,6 +53,10 @@ class contact(hierarchy_design.hierarchy_design):
|
|||||||
first_dir = "H" if self.get_preferred_direction(layer_stack[0])=="V" else "V"
|
first_dir = "H" if self.get_preferred_direction(layer_stack[0])=="V" else "V"
|
||||||
second_dir = "H" if self.get_preferred_direction(layer_stack[2])=="V" else "V"
|
second_dir = "H" if self.get_preferred_direction(layer_stack[2])=="V" else "V"
|
||||||
self.directions = (first_dir, second_dir)
|
self.directions = (first_dir, second_dir)
|
||||||
|
# Preferred directions
|
||||||
|
elif directions == "pref":
|
||||||
|
self.directions = (tech.preferred_directions[layer_stack[0]],
|
||||||
|
tech.preferred_directions[layer_stack[2]])
|
||||||
# User directions
|
# User directions
|
||||||
elif directions:
|
elif directions:
|
||||||
self.directions = directions
|
self.directions = directions
|
||||||
@@ -149,7 +153,7 @@ class contact(hierarchy_design.hierarchy_design):
|
|||||||
self.first_layer_vertical_enclosure = max(self.first_layer_enclosure,
|
self.first_layer_vertical_enclosure = max(self.first_layer_enclosure,
|
||||||
(self.first_layer_minwidth - self.contact_array_height) / 2)
|
(self.first_layer_minwidth - self.contact_array_height) / 2)
|
||||||
else:
|
else:
|
||||||
debug.error("Invalid first layer direction.", -1)
|
debug.error("Invalid first layer direction: ".format(self.directions[0]), -1)
|
||||||
|
|
||||||
# In some technologies, the minimum width may be larger
|
# In some technologies, the minimum width may be larger
|
||||||
# than the overlap requirement around the via, so
|
# than the overlap requirement around the via, so
|
||||||
@@ -165,7 +169,7 @@ class contact(hierarchy_design.hierarchy_design):
|
|||||||
self.second_layer_vertical_enclosure = max(self.second_layer_enclosure,
|
self.second_layer_vertical_enclosure = max(self.second_layer_enclosure,
|
||||||
(self.second_layer_minwidth - self.contact_array_width) / 2)
|
(self.second_layer_minwidth - self.contact_array_width) / 2)
|
||||||
else:
|
else:
|
||||||
debug.error("Invalid second layer direction.", -1)
|
debug.error("Invalid secon layer direction: ".format(self.directions[1]), -1)
|
||||||
|
|
||||||
def create_contact_array(self):
|
def create_contact_array(self):
|
||||||
""" Create the contact array at the origin"""
|
""" Create the contact array at the origin"""
|
||||||
|
|||||||
@@ -86,8 +86,14 @@ class geometry:
|
|||||||
elif mirror == "XY":
|
elif mirror == "XY":
|
||||||
ll = ll.scale(-1, -1)
|
ll = ll.scale(-1, -1)
|
||||||
ur = ur.scale(-1, -1)
|
ur = ur.scale(-1, -1)
|
||||||
|
elif mirror == "" or mirror == "R0":
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
debug.error("Invalid mirroring: {}".format(mirror), -1)
|
||||||
|
|
||||||
if rotate == 90:
|
if rotate == 0:
|
||||||
|
pass
|
||||||
|
elif rotate == 90:
|
||||||
ll = ll.rotate_scale(-1, 1)
|
ll = ll.rotate_scale(-1, 1)
|
||||||
ur = ur.rotate_scale(-1, 1)
|
ur = ur.rotate_scale(-1, 1)
|
||||||
elif rotate == 180:
|
elif rotate == 180:
|
||||||
@@ -96,6 +102,8 @@ class geometry:
|
|||||||
elif rotate == 270:
|
elif rotate == 270:
|
||||||
ll = ll.rotate_scale(1, -1)
|
ll = ll.rotate_scale(1, -1)
|
||||||
ur = ur.rotate_scale(1, -1)
|
ur = ur.rotate_scale(1, -1)
|
||||||
|
else:
|
||||||
|
debug.error("Invalid rotation: {}".format(rotate), -1)
|
||||||
|
|
||||||
self.boundary = [offset + ll, offset + ur]
|
self.boundary = [offset + ll, offset + ur]
|
||||||
self.normalize()
|
self.normalize()
|
||||||
@@ -139,6 +147,10 @@ class geometry:
|
|||||||
def cy(self):
|
def cy(self):
|
||||||
""" Return the center y """
|
""" Return the center y """
|
||||||
return 0.5 * (self.boundary[0].y + self.boundary[1].y)
|
return 0.5 * (self.boundary[0].y + self.boundary[1].y)
|
||||||
|
|
||||||
|
def center(self):
|
||||||
|
""" Return the center coordinate """
|
||||||
|
return vector(self.cx(), self.cy())
|
||||||
|
|
||||||
|
|
||||||
class instance(geometry):
|
class instance(geometry):
|
||||||
|
|||||||
@@ -244,25 +244,27 @@ class layout():
|
|||||||
height))
|
height))
|
||||||
return self.objs[-1]
|
return self.objs[-1]
|
||||||
|
|
||||||
def add_segment_center(self, layer, start, end):
|
def add_segment_center(self, layer, start, end, width=None):
|
||||||
"""
|
"""
|
||||||
Add a min-width rectanglular segment using center
|
Add a min-width rectanglular segment using center
|
||||||
line on the start to end point
|
line on the start to end point
|
||||||
"""
|
"""
|
||||||
minwidth_layer = drc["minwidth_{}".format(layer)]
|
if not width:
|
||||||
|
width = drc["minwidth_{}".format(layer)]
|
||||||
|
|
||||||
if start.x != end.x and start.y != end.y:
|
if start.x != end.x and start.y != end.y:
|
||||||
debug.error("Nonrectilinear center rect!", -1)
|
debug.error("Nonrectilinear center rect!", -1)
|
||||||
elif start.x != end.x:
|
elif start.x != end.x:
|
||||||
offset = vector(0, 0.5 * minwidth_layer)
|
offset = vector(0, 0.5 * width)
|
||||||
return self.add_rect(layer,
|
return self.add_rect(layer,
|
||||||
start - offset,
|
start - offset,
|
||||||
end.x - start.x,
|
end.x - start.x,
|
||||||
minwidth_layer)
|
width)
|
||||||
else:
|
else:
|
||||||
offset = vector(0.5 * minwidth_layer, 0)
|
offset = vector(0.5 * width, 0)
|
||||||
return self.add_rect(layer,
|
return self.add_rect(layer,
|
||||||
start - offset,
|
start - offset,
|
||||||
minwidth_layer,
|
width,
|
||||||
end.y - start.y)
|
end.y - start.y)
|
||||||
|
|
||||||
def get_pin(self, text):
|
def get_pin(self, text):
|
||||||
@@ -322,7 +324,7 @@ class layout():
|
|||||||
for pin_name in self.pin_map.keys():
|
for pin_name in self.pin_map.keys():
|
||||||
self.copy_layout_pin(instance, pin_name, prefix + pin_name)
|
self.copy_layout_pin(instance, pin_name, prefix + pin_name)
|
||||||
|
|
||||||
def add_layout_pin_segment_center(self, text, layer, start, end):
|
def add_layout_pin_segment_center(self, text, layer, start, end, width=None):
|
||||||
"""
|
"""
|
||||||
Creates a path like pin with center-line convention
|
Creates a path like pin with center-line convention
|
||||||
"""
|
"""
|
||||||
@@ -331,27 +333,27 @@ class layout():
|
|||||||
self.gds_write(file_name)
|
self.gds_write(file_name)
|
||||||
debug.error("Cannot have a non-manhatten layout pin: {}".format(file_name), -1)
|
debug.error("Cannot have a non-manhatten layout pin: {}".format(file_name), -1)
|
||||||
|
|
||||||
minwidth_layer = drc["minwidth_{}".format(layer)]
|
if not width:
|
||||||
|
layer_width = drc["minwidth_{}".format(layer)]
|
||||||
|
else:
|
||||||
|
layer_width = width
|
||||||
|
|
||||||
# one of these will be zero
|
# one of these will be zero
|
||||||
width = max(start.x, end.x) - min(start.x, end.x)
|
bbox_width = max(start.x, end.x) - min(start.x, end.x)
|
||||||
height = max(start.y, end.y) - min(start.y, end.y)
|
bbox_height = max(start.y, end.y) - min(start.y, end.y)
|
||||||
ll_offset = vector(min(start.x, end.x), min(start.y, end.y))
|
ll_offset = vector(min(start.x, end.x), min(start.y, end.y))
|
||||||
|
|
||||||
# Shift it down 1/2 a width in the 0 dimension
|
# Shift it down 1/2 a width in the 0 dimension
|
||||||
if height == 0:
|
if bbox_height == 0:
|
||||||
ll_offset -= vector(0, 0.5 * minwidth_layer)
|
ll_offset -= vector(0, 0.5 * layer_width)
|
||||||
if width == 0:
|
if bbox_width == 0:
|
||||||
ll_offset -= vector(0.5 * minwidth_layer, 0)
|
ll_offset -= vector(0.5 * layer_width, 0)
|
||||||
# This makes sure it is long enough, but also it is not 0 width!
|
|
||||||
height = max(minwidth_layer, height)
|
|
||||||
width = max(minwidth_layer, width)
|
|
||||||
|
|
||||||
return self.add_layout_pin(text,
|
return self.add_layout_pin(text=text,
|
||||||
layer,
|
layer=layer,
|
||||||
ll_offset,
|
offset=ll_offset,
|
||||||
width,
|
width=bbox_width,
|
||||||
height)
|
height=bbox_height)
|
||||||
|
|
||||||
def add_layout_pin_rect_center(self, text, layer, offset, width=None, height=None):
|
def add_layout_pin_rect_center(self, text, layer, offset, width=None, height=None):
|
||||||
""" Creates a path like pin with center-line convention """
|
""" Creates a path like pin with center-line convention """
|
||||||
@@ -692,6 +694,8 @@ class layout():
|
|||||||
boundary_layer = "stdc"
|
boundary_layer = "stdc"
|
||||||
boundary = [self.find_lowest_coords(),
|
boundary = [self.find_lowest_coords(),
|
||||||
self.find_highest_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]
|
height = boundary[1][1] - boundary[0][1]
|
||||||
width = boundary[1][0] - boundary[0][0]
|
width = boundary[1][0] - boundary[0][0]
|
||||||
(layer_number, layer_purpose) = techlayer[boundary_layer]
|
(layer_number, layer_purpose) = techlayer[boundary_layer]
|
||||||
@@ -1335,16 +1339,16 @@ class layout():
|
|||||||
which vias are needed.
|
which vias are needed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
via = self.add_via_stack_center(from_layer=start_layer,
|
|
||||||
to_layer=self.pwr_grid_layer,
|
|
||||||
size=size,
|
|
||||||
offset=loc,
|
|
||||||
directions=directions)
|
|
||||||
if start_layer == self.pwr_grid_layer:
|
if start_layer == self.pwr_grid_layer:
|
||||||
self.add_layout_pin_rect_center(text=name,
|
self.add_layout_pin_rect_center(text=name,
|
||||||
layer=self.pwr_grid_layer,
|
layer=self.pwr_grid_layer,
|
||||||
offset=loc)
|
offset=loc)
|
||||||
else:
|
else:
|
||||||
|
via = self.add_via_stack_center(from_layer=start_layer,
|
||||||
|
to_layer=self.pwr_grid_layer,
|
||||||
|
size=size,
|
||||||
|
offset=loc,
|
||||||
|
directions=directions)
|
||||||
# Hack for min area
|
# Hack for min area
|
||||||
if OPTS.tech_name == "s8":
|
if OPTS.tech_name == "s8":
|
||||||
width = round_to_grid(sqrt(drc["minarea_m3"]))
|
width = round_to_grid(sqrt(drc["minarea_m3"]))
|
||||||
|
|||||||
@@ -208,6 +208,7 @@ class spice():
|
|||||||
# parses line into ports and remove subckt
|
# parses line into ports and remove subckt
|
||||||
self.pins = subckt_line.split(" ")[2:]
|
self.pins = subckt_line.split(" ")[2:]
|
||||||
else:
|
else:
|
||||||
|
debug.info(4, "no spfile {0}".format(self.sp_file))
|
||||||
self.spice = []
|
self.spice = []
|
||||||
|
|
||||||
# We don't define self.lvs and will use self.spice if dynamically created
|
# We don't define self.lvs and will use self.spice if dynamically created
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
#
|
#
|
||||||
import debug
|
import debug
|
||||||
import utils
|
import utils
|
||||||
from tech import GDS, layer, parameter
|
from tech import GDS, layer
|
||||||
from tech import cell_properties as props
|
from tech import cell_properties as props
|
||||||
import bitcell_base
|
import bitcell_base
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ class dff(design.design):
|
|||||||
#Calculated in the tech file by summing the widths of all the gates and dividing by the minimum width.
|
#Calculated in the tech file by summing the widths of all the gates and dividing by the minimum width.
|
||||||
return parameter["dff_clk_cin"]
|
return parameter["dff_clk_cin"]
|
||||||
|
|
||||||
def build_graph(self, graph, inst_name, port_nets):
|
def build_graph(self, graph, inst_name, port_nets):
|
||||||
"""Adds edges based on inputs/outputs. Overrides base class function."""
|
"""Adds edges based on inputs/outputs. Overrides base class function."""
|
||||||
self.add_graph_edges(graph, port_nets)
|
self.add_graph_edges(graph, port_nets)
|
||||||
|
|
||||||
@@ -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)
|
||||||
@@ -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)
|
||||||
|
|
||||||
@@ -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)
|
||||||
|
|
||||||
@@ -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)
|
||||||
|
|
||||||
@@ -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()
|
||||||
|
|
||||||
@@ -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()
|
||||||
|
|
||||||
@@ -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()
|
||||||
|
|
||||||
@@ -12,7 +12,6 @@ from sram_factory import factory
|
|||||||
from vector import vector
|
from vector import vector
|
||||||
from globals import OPTS
|
from globals import OPTS
|
||||||
from errors import drc_error
|
from errors import drc_error
|
||||||
from tech import cell_properties, layer
|
|
||||||
|
|
||||||
|
|
||||||
class hierarchical_decoder(design.design):
|
class hierarchical_decoder(design.design):
|
||||||
@@ -28,12 +27,8 @@ class hierarchical_decoder(design.design):
|
|||||||
self.pre3x8_inst = []
|
self.pre3x8_inst = []
|
||||||
|
|
||||||
b = factory.create(module_type="bitcell")
|
b = factory.create(module_type="bitcell")
|
||||||
try:
|
self.cell_height = b.height
|
||||||
self.cell_multiple = cell_properties.bitcell.decoder_bitcell_multiple
|
|
||||||
except AttributeError:
|
|
||||||
self.cell_multiple = 1
|
|
||||||
self.cell_height = self.cell_multiple * b.height
|
|
||||||
|
|
||||||
self.num_outputs = num_outputs
|
self.num_outputs = num_outputs
|
||||||
self.num_inputs = math.ceil(math.log(self.num_outputs, 2))
|
self.num_inputs = math.ceil(math.log(self.num_outputs, 2))
|
||||||
(self.no_of_pre2x4, self.no_of_pre3x8)=self.determine_predecodes(self.num_inputs)
|
(self.no_of_pre2x4, self.no_of_pre3x8)=self.determine_predecodes(self.num_inputs)
|
||||||
@@ -41,41 +36,6 @@ class hierarchical_decoder(design.design):
|
|||||||
self.create_netlist()
|
self.create_netlist()
|
||||||
if not OPTS.netlist_only:
|
if not OPTS.netlist_only:
|
||||||
self.create_layout()
|
self.create_layout()
|
||||||
|
|
||||||
def find_decoder_height(self):
|
|
||||||
"""
|
|
||||||
Dead code. This would dynamically determine the bitcell multiple,
|
|
||||||
but I just decided to hard code it in the tech file if it is not 1
|
|
||||||
because a DRC tool would be required even to run in front-end mode.
|
|
||||||
"""
|
|
||||||
b = factory.create(module_type="bitcell")
|
|
||||||
|
|
||||||
# Old behavior
|
|
||||||
if OPTS.netlist_only:
|
|
||||||
return (b.height, 1)
|
|
||||||
|
|
||||||
# Search for the smallest multiple that works
|
|
||||||
cell_multiple = 1
|
|
||||||
while cell_multiple < 5:
|
|
||||||
cell_height = cell_multiple * b.height
|
|
||||||
# debug.info(2,"Trying mult = {0} height={1}".format(cell_multiple, cell_height))
|
|
||||||
try:
|
|
||||||
and3 = factory.create(module_type="pand3",
|
|
||||||
height=cell_height)
|
|
||||||
except drc_error:
|
|
||||||
# debug.info(1, "Incrementing decoder height by 1 bitcell height {}".format(b.height))
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
(drc_errors, lvs_errors) = and3.DRC_LVS(force_check=True)
|
|
||||||
total_errors = drc_errors + lvs_errors
|
|
||||||
if total_errors == 0:
|
|
||||||
debug.info(1, "Decoder height is multiple of {} bitcells.".format(cell_multiple))
|
|
||||||
return (cell_height, cell_multiple)
|
|
||||||
|
|
||||||
cell_multiple += 1
|
|
||||||
|
|
||||||
else:
|
|
||||||
debug.error("Couldn't find a valid decoder height multiple.", -1)
|
|
||||||
|
|
||||||
def create_netlist(self):
|
def create_netlist(self):
|
||||||
self.add_modules()
|
self.add_modules()
|
||||||
@@ -97,15 +57,22 @@ class hierarchical_decoder(design.design):
|
|||||||
self.DRC_LVS()
|
self.DRC_LVS()
|
||||||
|
|
||||||
def add_modules(self):
|
def add_modules(self):
|
||||||
self.inv = factory.create(module_type="pinv",
|
if OPTS.tech_name == "s8":
|
||||||
height=self.cell_height)
|
self.and2 = factory.create(module_type="pand2_dec")
|
||||||
self.add_mod(self.inv)
|
else:
|
||||||
self.and2 = factory.create(module_type="pand2",
|
self.and2 = factory.create(module_type="pand2_dec",
|
||||||
height=self.cell_height)
|
height=self.cell_height)
|
||||||
self.add_mod(self.and2)
|
self.add_mod(self.and2)
|
||||||
self.and3 = factory.create(module_type="pand3",
|
if OPTS.tech_name == "s8":
|
||||||
height=self.cell_height)
|
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)
|
self.add_mod(self.and3)
|
||||||
|
# TBD
|
||||||
|
# self.and4 = factory.create(module_type="pand4_dec")
|
||||||
|
# self.add_mod(self.and4)
|
||||||
|
|
||||||
self.add_decoders()
|
self.add_decoders()
|
||||||
|
|
||||||
@@ -176,56 +143,63 @@ class hierarchical_decoder(design.design):
|
|||||||
-1)
|
-1)
|
||||||
|
|
||||||
# Calculates height and width of pre-decoder,
|
# Calculates height and width of pre-decoder,
|
||||||
if self.no_of_pre3x8 > 0:
|
# FIXME: Update with 4x16
|
||||||
|
if self.no_of_pre3x8 > 0 and self.no_of_pre2x4 > 0:
|
||||||
|
self.predecoder_width = max(self.pre3_8.width, self.pre2_4.width)
|
||||||
|
elif self.no_of_pre3x8 > 0:
|
||||||
self.predecoder_width = self.pre3_8.width
|
self.predecoder_width = self.pre3_8.width
|
||||||
else:
|
else:
|
||||||
self.predecoder_width = self.pre2_4.width
|
self.predecoder_width = self.pre2_4.width
|
||||||
|
|
||||||
self.predecoder_height = self.pre2_4.height * self.no_of_pre2x4 + self.pre3_8.height * self.no_of_pre3x8
|
|
||||||
|
|
||||||
# We may have more than one bitcell per decoder row
|
# How much space between each predecoder
|
||||||
self.num_rows = math.ceil(self.num_outputs / self.cell_multiple)
|
self.predecoder_spacing = self.and2.height
|
||||||
# We will place this many final decoders per row
|
self.predecoder_height = self.pre2_4.height * self.no_of_pre2x4 + self.pre3_8.height * self.no_of_pre3x8 \
|
||||||
self.decoders_per_row = math.ceil(self.num_outputs / self.num_rows)
|
+ (self.no_of_pre2x4 + self.no_of_pre3x8 - 1) * self.predecoder_spacing
|
||||||
# We will need to use M2 and M3 in the vertical bus if we have multiple decoders per row
|
|
||||||
if self.decoders_per_row == 1:
|
# Inputs to cells are on input layer
|
||||||
self.decoder_bus_pitch = self.m2_pitch
|
# Outputs from cells are on output layer
|
||||||
elif self.decoders_per_row == 2:
|
if OPTS.tech_name == "s8":
|
||||||
self.decoder_bus_pitch = self.m3_pitch
|
self.bus_layer = "m1"
|
||||||
|
self.bus_directions = "nonpref"
|
||||||
|
self.bus_pitch = self.m1_pitch
|
||||||
|
self.bus_space = self.m2_space
|
||||||
|
self.input_layer = "m2"
|
||||||
|
self.output_layer = "li"
|
||||||
|
self.output_layer_pitch = self.li_pitch
|
||||||
else:
|
else:
|
||||||
debug.error("Insufficient layers for multi-bit height decoder.", -1)
|
self.bus_layer = "m2"
|
||||||
|
self.bus_directions = "pref"
|
||||||
|
self.bus_pitch = self.m2_pitch
|
||||||
|
self.bus_space = self.m2_space
|
||||||
|
# These two layers being the same requires a special jog
|
||||||
|
# to ensure to conflicts with the output layers
|
||||||
|
self.input_layer = "m1"
|
||||||
|
self.output_layer = "m3"
|
||||||
|
self.output_layer_pitch = self.m3_pitch
|
||||||
|
|
||||||
|
# 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
|
# 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
|
# Calculates height and width of hierarchical decoder
|
||||||
# Add extra pitch for good measure
|
# Add extra pitch for good measure
|
||||||
self.height = max(self.predecoder_height, self.row_decoder_height) + self.m2_pitch
|
self.height = max(self.predecoder_height, self.row_decoder_height) + self.bus_space
|
||||||
self.width = self.input_routing_width + self.predecoder_width \
|
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.internal_routing_width \
|
||||||
+ self.decoders_per_row * nand_width + self.inv.width
|
+ self.nand_width \
|
||||||
|
+ self.m1_space
|
||||||
|
|
||||||
def route_inputs(self):
|
def route_inputs(self):
|
||||||
""" Create input bus for the predecoders """
|
""" Create input bus for the predecoders """
|
||||||
# inputs should be as high as the decoders
|
|
||||||
input_height = self.no_of_pre2x4 * self.pre2_4.height + self.no_of_pre3x8 * self.pre3_8.height
|
|
||||||
|
|
||||||
# Find the left-most predecoder
|
# Find the left-most predecoder
|
||||||
min_x = 0
|
min_x = 0
|
||||||
if self.no_of_pre2x4 > 0:
|
if self.no_of_pre2x4 > 0:
|
||||||
@@ -235,10 +209,10 @@ class hierarchical_decoder(design.design):
|
|||||||
input_offset=vector(min_x - self.input_routing_width, 0)
|
input_offset=vector(min_x - self.input_routing_width, 0)
|
||||||
|
|
||||||
input_bus_names = ["addr_{0}".format(i) for i in range(self.num_inputs)]
|
input_bus_names = ["addr_{0}".format(i) for i in range(self.num_inputs)]
|
||||||
self.input_bus = self.create_vertical_pin_bus(layer="m2",
|
self.input_bus = self.create_vertical_pin_bus(layer=self.bus_layer,
|
||||||
offset=input_offset,
|
offset=input_offset,
|
||||||
names=input_bus_names,
|
names=input_bus_names,
|
||||||
length=input_height)
|
length=self.predecoder_height)
|
||||||
|
|
||||||
self.route_input_to_predecodes()
|
self.route_input_to_predecodes()
|
||||||
|
|
||||||
@@ -253,9 +227,7 @@ class hierarchical_decoder(design.design):
|
|||||||
in_name = "in_{}".format(i)
|
in_name = "in_{}".format(i)
|
||||||
decoder_pin = self.pre2x4_inst[pre_num].get_pin(in_name)
|
decoder_pin = self.pre2x4_inst[pre_num].get_pin(in_name)
|
||||||
|
|
||||||
# To prevent conflicts, we will offset each input connect so
|
decoder_offset = decoder_pin.center()
|
||||||
# that it aligns with the vdd/gnd rails
|
|
||||||
decoder_offset = decoder_pin.bc() + vector(0, (i + 1) * (self.inv.height + self.m1_pitch))
|
|
||||||
input_offset = input_pos.scale(1, 0) + decoder_offset.scale(0, 1)
|
input_offset = input_pos.scale(1, 0) + decoder_offset.scale(0, 1)
|
||||||
|
|
||||||
self.route_input_bus(decoder_offset, input_offset)
|
self.route_input_bus(decoder_offset, input_offset)
|
||||||
@@ -269,9 +241,7 @@ class hierarchical_decoder(design.design):
|
|||||||
in_name = "in_{}".format(i)
|
in_name = "in_{}".format(i)
|
||||||
decoder_pin = self.pre3x8_inst[pre_num].get_pin(in_name)
|
decoder_pin = self.pre3x8_inst[pre_num].get_pin(in_name)
|
||||||
|
|
||||||
# To prevent conflicts, we will offset each input connect so
|
decoder_offset = decoder_pin.center()
|
||||||
# that it aligns with the vdd/gnd rails
|
|
||||||
decoder_offset = decoder_pin.bc() + vector(0, (i + 1) * (self.inv.height + self.m1_pitch))
|
|
||||||
input_offset = input_pos.scale(1, 0) + decoder_offset.scale(0, 1)
|
input_offset = input_pos.scale(1, 0) + decoder_offset.scale(0, 1)
|
||||||
|
|
||||||
self.route_input_bus(decoder_offset, input_offset)
|
self.route_input_bus(decoder_offset, input_offset)
|
||||||
@@ -282,13 +252,14 @@ class hierarchical_decoder(design.design):
|
|||||||
vertical M2 coordinate to the predecode inputs
|
vertical M2 coordinate to the predecode inputs
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.add_via_stack_center(from_layer="m2",
|
self.add_via_stack_center(from_layer=self.bus_layer,
|
||||||
to_layer="m3",
|
to_layer=self.input_layer,
|
||||||
offset=input_offset)
|
offset=input_offset)
|
||||||
self.add_via_stack_center(from_layer="m2",
|
self.add_via_stack_center(from_layer=self.bus_layer,
|
||||||
to_layer="m3",
|
to_layer=self.input_layer,
|
||||||
offset=output_offset)
|
offset=output_offset,
|
||||||
self.add_path("m3", [input_offset, output_offset])
|
directions=self.bus_directions)
|
||||||
|
self.add_path(self.input_layer, [input_offset, output_offset])
|
||||||
|
|
||||||
def add_pins(self):
|
def add_pins(self):
|
||||||
""" Add the module pins """
|
""" Add the module pins """
|
||||||
@@ -363,19 +334,19 @@ class hierarchical_decoder(design.design):
|
|||||||
if (self.num_inputs == 2):
|
if (self.num_inputs == 2):
|
||||||
base = vector(-self.pre2_4.width, 0)
|
base = vector(-self.pre2_4.width, 0)
|
||||||
else:
|
else:
|
||||||
base= vector(-self.pre2_4.width, num * self.pre2_4.height)
|
base= vector(-self.pre2_4.width, num * (self.pre2_4.height + self.predecoder_spacing))
|
||||||
|
|
||||||
self.pre2x4_inst[num].place(base - vector(2 * self.m2_pitch, 0))
|
self.pre2x4_inst[num].place(base)
|
||||||
|
|
||||||
def place_pre3x8(self, num):
|
def place_pre3x8(self, num):
|
||||||
""" Place 3x8 predecoder to the left of the origin and above any 2x4 decoders """
|
""" Place 3x8 predecoder to the left of the origin and above any 2x4 decoders """
|
||||||
if (self.num_inputs == 3):
|
if (self.num_inputs == 3):
|
||||||
offset = vector(-self.pre_3_8.width, 0)
|
offset = vector(-self.pre_3_8.width, 0)
|
||||||
else:
|
else:
|
||||||
height = self.no_of_pre2x4 * self.pre2_4.height + num * self.pre3_8.height
|
height = self.no_of_pre2x4 * (self.pre2_4.height + self.predecoder_spacing) + num * (self.pre3_8.height + self.predecoder_spacing)
|
||||||
offset = vector(-self.pre3_8.width, height)
|
offset = vector(-self.pre3_8.width, height)
|
||||||
|
|
||||||
self.pre3x8_inst[num].place(offset - vector(2 * self.m2_pitch, 0))
|
self.pre3x8_inst[num].place(offset)
|
||||||
|
|
||||||
def create_row_decoder(self):
|
def create_row_decoder(self):
|
||||||
""" Create the row-decoder by placing AND2/AND3 and Inverters
|
""" Create the row-decoder by placing AND2/AND3 and Inverters
|
||||||
@@ -431,7 +402,6 @@ class hierarchical_decoder(design.design):
|
|||||||
if (self.num_inputs >= 4):
|
if (self.num_inputs >= 4):
|
||||||
self.place_decoder_and_array()
|
self.place_decoder_and_array()
|
||||||
|
|
||||||
|
|
||||||
def place_decoder_and_array(self):
|
def place_decoder_and_array(self):
|
||||||
"""
|
"""
|
||||||
Add a column of AND gates for final decode.
|
Add a column of AND gates for final decode.
|
||||||
@@ -452,9 +422,7 @@ class hierarchical_decoder(design.design):
|
|||||||
Add a column of AND gates for the decoder above the predecoders.
|
Add a column of AND gates for the decoder above the predecoders.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for inst_index in range(self.num_outputs):
|
for row in range(self.num_outputs):
|
||||||
row = math.floor(inst_index / self.decoders_per_row)
|
|
||||||
dec = inst_index % self.decoders_per_row
|
|
||||||
if ((row % 2) == 0):
|
if ((row % 2) == 0):
|
||||||
y_off = and_mod.height * row
|
y_off = and_mod.height * row
|
||||||
mirror = "R0"
|
mirror = "R0"
|
||||||
@@ -462,32 +430,16 @@ class hierarchical_decoder(design.design):
|
|||||||
y_off = and_mod.height * (row + 1)
|
y_off = and_mod.height * (row + 1)
|
||||||
mirror = "MX"
|
mirror = "MX"
|
||||||
|
|
||||||
x_off = self.internal_routing_width + dec * and_mod.width
|
x_off = self.internal_routing_width
|
||||||
self.and_inst[inst_index].place(offset=vector(x_off, y_off),
|
self.and_inst[row].place(offset=vector(x_off, y_off),
|
||||||
mirror=mirror)
|
mirror=mirror)
|
||||||
|
|
||||||
def route_outputs(self):
|
def route_outputs(self):
|
||||||
""" Add the pins. """
|
""" Add the pins. """
|
||||||
|
|
||||||
max_xoffset = max(x.rx() for x in self.and_inst)
|
for row in range(self.num_outputs):
|
||||||
|
and_inst = self.and_inst[row]
|
||||||
for output_index in range(self.num_outputs):
|
self.copy_layout_pin(and_inst, "Z", "decode_{0}".format(row))
|
||||||
row_remainder = (output_index % self.decoders_per_row)
|
|
||||||
|
|
||||||
and_inst = self.and_inst[output_index]
|
|
||||||
z_pin = and_inst.get_pin("Z")
|
|
||||||
if row_remainder == 0 and self.decoders_per_row > 1:
|
|
||||||
layer = "m3"
|
|
||||||
self.add_via_stack_center(from_layer=z_pin.layer,
|
|
||||||
to_layer="m3",
|
|
||||||
offset=z_pin.center())
|
|
||||||
else:
|
|
||||||
layer = z_pin.layer
|
|
||||||
|
|
||||||
self.add_layout_pin_segment_center(text="decode_{0}".format(output_index),
|
|
||||||
layer=layer,
|
|
||||||
start=z_pin.center(),
|
|
||||||
end=vector(max_xoffset, z_pin.cy()))
|
|
||||||
|
|
||||||
def route_decoder_bus(self):
|
def route_decoder_bus(self):
|
||||||
"""
|
"""
|
||||||
@@ -498,9 +450,9 @@ class hierarchical_decoder(design.design):
|
|||||||
if (self.num_inputs >= 4):
|
if (self.num_inputs >= 4):
|
||||||
# This leaves an offset for the predecoder output jogs
|
# This leaves an offset for the predecoder output jogs
|
||||||
input_bus_names = ["predecode_{0}".format(i) for i in range(self.total_number_of_predecoder_outputs)]
|
input_bus_names = ["predecode_{0}".format(i) for i in range(self.total_number_of_predecoder_outputs)]
|
||||||
self.predecode_bus = self.create_vertical_pin_bus(layer="m2",
|
self.predecode_bus = self.create_vertical_pin_bus(layer=self.bus_layer,
|
||||||
pitch=self.decoder_bus_pitch,
|
pitch=self.bus_pitch,
|
||||||
offset=vector(0, 0),
|
offset=vector(self.bus_pitch, 0),
|
||||||
names=input_bus_names,
|
names=input_bus_names,
|
||||||
length=self.height)
|
length=self.height)
|
||||||
|
|
||||||
@@ -518,8 +470,9 @@ class hierarchical_decoder(design.design):
|
|||||||
predecode_name = "predecode_{}".format(pre_num * 4 + i)
|
predecode_name = "predecode_{}".format(pre_num * 4 + i)
|
||||||
out_name = "out_{}".format(i)
|
out_name = "out_{}".format(i)
|
||||||
pin = self.pre2x4_inst[pre_num].get_pin(out_name)
|
pin = self.pre2x4_inst[pre_num].get_pin(out_name)
|
||||||
x_offset = self.pre2x4_inst[pre_num].rx() + self.m2_pitch
|
x_offset = self.pre2x4_inst[pre_num].rx() + self.output_layer_pitch
|
||||||
self.route_predecode_bus_inputs(predecode_name, pin, x_offset)
|
y_offset = self.pre2x4_inst[pre_num].by() + i * self.cell_height
|
||||||
|
self.route_predecode_bus_inputs(predecode_name, pin, x_offset, y_offset)
|
||||||
|
|
||||||
# FIXME: convert to connect_bus
|
# FIXME: convert to connect_bus
|
||||||
for pre_num in range(self.no_of_pre3x8):
|
for pre_num in range(self.no_of_pre3x8):
|
||||||
@@ -527,8 +480,9 @@ class hierarchical_decoder(design.design):
|
|||||||
predecode_name = "predecode_{}".format(pre_num * 8 + i + self.no_of_pre2x4 * 4)
|
predecode_name = "predecode_{}".format(pre_num * 8 + i + self.no_of_pre2x4 * 4)
|
||||||
out_name = "out_{}".format(i)
|
out_name = "out_{}".format(i)
|
||||||
pin = self.pre3x8_inst[pre_num].get_pin(out_name)
|
pin = self.pre3x8_inst[pre_num].get_pin(out_name)
|
||||||
x_offset = self.pre3x8_inst[pre_num].rx() + self.m2_pitch
|
x_offset = self.pre3x8_inst[pre_num].rx() + self.output_layer_pitch
|
||||||
self.route_predecode_bus_inputs(predecode_name, pin, x_offset)
|
y_offset = self.pre3x8_inst[pre_num].by() + i * self.cell_height
|
||||||
|
self.route_predecode_bus_inputs(predecode_name, pin, x_offset, y_offset)
|
||||||
|
|
||||||
def route_bus_to_decoder(self):
|
def route_bus_to_decoder(self):
|
||||||
"""
|
"""
|
||||||
@@ -542,12 +496,6 @@ class hierarchical_decoder(design.design):
|
|||||||
and the 128th AND3 is connected to [3,7,15]
|
and the 128th AND3 is connected to [3,7,15]
|
||||||
"""
|
"""
|
||||||
output_index = 0
|
output_index = 0
|
||||||
|
|
||||||
if "li" in layer:
|
|
||||||
self.decoder_layers = [self.m1_stack, self.m2_stack[::-1]]
|
|
||||||
else:
|
|
||||||
self.decoder_layers = [self.m2_stack[::-1]]
|
|
||||||
debug.check(self.decoders_per_row <= len(self.decoder_layers), "Must have more layers for multi-height decoder.")
|
|
||||||
|
|
||||||
if (self.num_inputs == 4 or self.num_inputs == 5):
|
if (self.num_inputs == 4 or self.num_inputs == 5):
|
||||||
for index_B in self.predec_groups[1]:
|
for index_B in self.predec_groups[1]:
|
||||||
@@ -557,13 +505,11 @@ class hierarchical_decoder(design.design):
|
|||||||
predecode_name = "predecode_{}".format(index_A)
|
predecode_name = "predecode_{}".format(index_A)
|
||||||
self.route_predecode_bus_outputs(predecode_name,
|
self.route_predecode_bus_outputs(predecode_name,
|
||||||
self.and_inst[output_index].get_pin("A"),
|
self.and_inst[output_index].get_pin("A"),
|
||||||
output_index,
|
output_index)
|
||||||
0)
|
|
||||||
predecode_name = "predecode_{}".format(index_B)
|
predecode_name = "predecode_{}".format(index_B)
|
||||||
self.route_predecode_bus_outputs(predecode_name,
|
self.route_predecode_bus_outputs(predecode_name,
|
||||||
self.and_inst[output_index].get_pin("B"),
|
self.and_inst[output_index].get_pin("B"),
|
||||||
output_index,
|
output_index)
|
||||||
1)
|
|
||||||
output_index = output_index + 1
|
output_index = output_index + 1
|
||||||
|
|
||||||
elif (self.num_inputs > 5):
|
elif (self.num_inputs > 5):
|
||||||
@@ -575,18 +521,15 @@ class hierarchical_decoder(design.design):
|
|||||||
predecode_name = "predecode_{}".format(index_A)
|
predecode_name = "predecode_{}".format(index_A)
|
||||||
self.route_predecode_bus_outputs(predecode_name,
|
self.route_predecode_bus_outputs(predecode_name,
|
||||||
self.and_inst[output_index].get_pin("A"),
|
self.and_inst[output_index].get_pin("A"),
|
||||||
output_index,
|
output_index)
|
||||||
0)
|
|
||||||
predecode_name = "predecode_{}".format(index_B)
|
predecode_name = "predecode_{}".format(index_B)
|
||||||
self.route_predecode_bus_outputs(predecode_name,
|
self.route_predecode_bus_outputs(predecode_name,
|
||||||
self.and_inst[output_index].get_pin("B"),
|
self.and_inst[output_index].get_pin("B"),
|
||||||
output_index,
|
output_index)
|
||||||
1)
|
|
||||||
predecode_name = "predecode_{}".format(index_C)
|
predecode_name = "predecode_{}".format(index_C)
|
||||||
self.route_predecode_bus_outputs(predecode_name,
|
self.route_predecode_bus_outputs(predecode_name,
|
||||||
self.and_inst[output_index].get_pin("C"),
|
self.and_inst[output_index].get_pin("C"),
|
||||||
output_index,
|
output_index)
|
||||||
2)
|
|
||||||
output_index = output_index + 1
|
output_index = output_index + 1
|
||||||
|
|
||||||
def route_vdd_gnd(self):
|
def route_vdd_gnd(self):
|
||||||
@@ -594,90 +537,90 @@ class hierarchical_decoder(design.design):
|
|||||||
Add a pin for each row of vdd/gnd which are
|
Add a pin for each row of vdd/gnd which are
|
||||||
must-connects next level up.
|
must-connects next level up.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if OPTS.tech_name == "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.
|
# This adds power vias at the top of each cell
|
||||||
xoffset = max(x.rx() for x in self.and_inst)
|
# (except the last to keep them inside the boundary)
|
||||||
for num in range(0, self.num_outputs):
|
for i in self.and_inst[:-1]:
|
||||||
# Only add the power pin for the 1st in each row
|
pins = i.get_pins(n)
|
||||||
if num % self.decoders_per_row:
|
for pin in pins:
|
||||||
continue
|
self.add_power_pin(name=n,
|
||||||
|
loc=pin.uc(),
|
||||||
for pin_name in ["vdd", "gnd"]:
|
start_layer=pin.layer)
|
||||||
# The nand and inv are the same height rows...
|
self.add_power_pin(name=n,
|
||||||
supply_pin = self.and_inst[num].get_pin(pin_name)
|
loc=pin.uc(),
|
||||||
pin_pos = vector(xoffset, supply_pin.cy())
|
start_layer=pin.layer)
|
||||||
self.add_path(supply_pin.layer,
|
|
||||||
[supply_pin.lc(), vector(xoffset, supply_pin.cy())])
|
for i in self.pre2x4_inst + self.pre3x8_inst:
|
||||||
self.add_power_pin(name=pin_name,
|
self.copy_layout_pin(i, n)
|
||||||
loc=pin_pos,
|
else:
|
||||||
start_layer=supply_pin.layer)
|
# The vias will be placed at the right of the cells.
|
||||||
|
xoffset = max(x.rx() for x in self.and_inst) + 0.5 * self.m1_space
|
||||||
# Copy the pins from the predecoders
|
for row in range(0, self.num_outputs):
|
||||||
for pre in self.pre2x4_inst + self.pre3x8_inst:
|
for pin_name in ["vdd", "gnd"]:
|
||||||
self.copy_layout_pin(pre, "vdd")
|
# The nand and inv are the same height rows...
|
||||||
self.copy_layout_pin(pre, "gnd")
|
supply_pin = self.and_inst[row].get_pin(pin_name)
|
||||||
|
pin_pos = vector(xoffset, supply_pin.cy())
|
||||||
|
self.add_power_pin(name=pin_name,
|
||||||
|
loc=pin_pos,
|
||||||
|
start_layer=supply_pin.layer)
|
||||||
|
|
||||||
|
# Copy the pins from the predecoders
|
||||||
|
for pre in self.pre2x4_inst + self.pre3x8_inst:
|
||||||
|
for pin_name in ["vdd", "gnd"]:
|
||||||
|
self.copy_layout_pin(pre, pin_name)
|
||||||
|
|
||||||
def route_predecode_bus_outputs(self, rail_name, pin, output_index, pin_index):
|
def route_predecode_bus_outputs(self, rail_name, pin, row):
|
||||||
"""
|
"""
|
||||||
Connect the routing rail to the given metal1 pin
|
Connect the routing rail to the given metal1 pin
|
||||||
using a routing track at the given y_offset
|
using a routing track at the given y_offset
|
||||||
"""
|
"""
|
||||||
|
|
||||||
row_index = math.floor(output_index / self.decoders_per_row)
|
|
||||||
row_remainder = (output_index % self.decoders_per_row)
|
|
||||||
row_offset = row_index * self.and_inst[0].height
|
|
||||||
|
|
||||||
pin_pos = pin.center()
|
pin_pos = pin.center()
|
||||||
|
rail_pos = vector(self.predecode_bus[rail_name].x, pin_pos.y)
|
||||||
# y_offset is the same for both the M2 and M4 routes so that the rail
|
self.add_path(self.input_layer, [rail_pos, pin_pos])
|
||||||
# contacts align and don't cause problems
|
|
||||||
if pin_index == 0:
|
|
||||||
# Bottom pitch
|
|
||||||
y_offset = row_offset
|
|
||||||
elif pin_index == 1:
|
|
||||||
# One pitch from top
|
|
||||||
y_offset = row_offset + self.and_inst[0].height - self.m3_pitch
|
|
||||||
elif pin_index == 2:
|
|
||||||
# One pitch from bottom
|
|
||||||
y_offset = row_offset + self.m3_pitch
|
|
||||||
else:
|
|
||||||
debug.error("Invalid decoder pitch.")
|
|
||||||
|
|
||||||
rail_pos = vector(self.predecode_bus[rail_name].x, y_offset)
|
|
||||||
mid_pos = vector(pin_pos.x, rail_pos.y)
|
|
||||||
self.add_wire(self.decoder_layers[row_remainder], [rail_pos, mid_pos, pin_pos])
|
|
||||||
|
|
||||||
self.add_via_stack_center(from_layer="m2",
|
self.add_via_stack_center(from_layer=self.bus_layer,
|
||||||
to_layer=self.decoder_layers[row_remainder][0],
|
to_layer=self.input_layer,
|
||||||
offset=rail_pos)
|
offset=rail_pos,
|
||||||
|
directions=self.bus_directions)
|
||||||
|
|
||||||
self.add_via_stack_center(from_layer=pin.layer,
|
self.add_via_stack_center(from_layer=pin.layer,
|
||||||
to_layer=self.decoder_layers[row_remainder][2],
|
to_layer=self.input_layer,
|
||||||
offset=pin_pos,
|
offset=pin_pos,
|
||||||
directions=("H", "H"))
|
directions=("H", "H"))
|
||||||
|
|
||||||
def route_predecode_bus_inputs(self, rail_name, pin, x_offset):
|
def route_predecode_bus_inputs(self, rail_name, pin, x_offset, y_offset):
|
||||||
"""
|
"""
|
||||||
Connect the routing rail to the given metal1 pin using a jog
|
Connect the routing rail to the given metal1 pin using a jog
|
||||||
to the right of the cell at the given x_offset.
|
to the right of the cell at the given x_offset.
|
||||||
"""
|
"""
|
||||||
# This routes the pin up to the rail, basically, to avoid conflicts.
|
# This routes the pin up to the rail, basically, to avoid conflicts.
|
||||||
# It would be fixed with a channel router.
|
# It would be fixed with a channel router.
|
||||||
# pin_pos = pin.center()
|
pin_pos = pin.rc()
|
||||||
# mid_point1 = vector(x_offset, pin_pos.y)
|
mid_point1 = vector(x_offset, pin_pos.y)
|
||||||
# mid_point2 = vector(x_offset, pin_pos.y + self.inv.height / 2)
|
mid_point2 = vector(x_offset, y_offset)
|
||||||
# rail_pos = vector(self.predecode_bus[rail_name].x, mid_point2.y)
|
rail_pos = vector(self.predecode_bus[rail_name].x, mid_point2.y)
|
||||||
# self.add_path("m1", [pin_pos, mid_point1, mid_point2, rail_pos])
|
self.add_path(self.output_layer, [pin_pos, mid_point1, mid_point2, rail_pos])
|
||||||
|
|
||||||
pin_pos = pin.center()
|
# pin_pos = pin.center()
|
||||||
rail_pos = vector(self.predecode_bus[rail_name].x, pin_pos.y)
|
# rail_pos = vector(self.predecode_bus[rail_name].x, pin_pos.y)
|
||||||
self.add_path("m1", [pin_pos, rail_pos])
|
# self.add_path(self.output_layer, [pin_pos, rail_pos])
|
||||||
self.add_via_stack_center(from_layer=pin.layer,
|
self.add_via_stack_center(from_layer=pin.layer,
|
||||||
to_layer="m1",
|
to_layer=self.output_layer,
|
||||||
offset=pin_pos)
|
offset=pin_pos)
|
||||||
self.add_via_stack_center(from_layer="m1",
|
self.add_via_stack_center(from_layer=self.bus_layer,
|
||||||
to_layer="m2",
|
to_layer=self.output_layer,
|
||||||
offset=rail_pos)
|
offset=rail_pos,
|
||||||
|
directions=self.bus_directions)
|
||||||
|
|
||||||
def input_load(self):
|
def input_load(self):
|
||||||
if self.determine_predecodes(self.num_inputs)[1]==0:
|
if self.determine_predecodes(self.num_inputs)[1]==0:
|
||||||
|
|||||||
@@ -8,29 +8,24 @@
|
|||||||
import debug
|
import debug
|
||||||
import design
|
import design
|
||||||
import math
|
import math
|
||||||
import contact
|
|
||||||
from vector import vector
|
from vector import vector
|
||||||
from sram_factory import factory
|
from sram_factory import factory
|
||||||
from tech import cell_properties
|
from globals import OPTS
|
||||||
|
|
||||||
|
|
||||||
class hierarchical_predecode(design.design):
|
class hierarchical_predecode(design.design):
|
||||||
"""
|
"""
|
||||||
Pre 2x4 and 3x8 decoder shared code.
|
Pre 2x4 and 3x8 and TBD 4x16 decoder shared code.
|
||||||
"""
|
"""
|
||||||
def __init__(self, name, input_number, height=None):
|
def __init__(self, name, input_number, height=None):
|
||||||
self.number_of_inputs = input_number
|
self.number_of_inputs = input_number
|
||||||
|
|
||||||
if not height:
|
if not height:
|
||||||
b = factory.create(module_type="bitcell")
|
b = factory.create(module_type="bitcell")
|
||||||
try:
|
self.cell_height = b.height
|
||||||
self.cell_multiple = cell_properties.bitcell.decoder_bitcell_multiple
|
|
||||||
except AttributeError:
|
|
||||||
self.cell_multiple = 1
|
|
||||||
self.cell_height = self.cell_multiple * b.height
|
|
||||||
else:
|
else:
|
||||||
self.cell_height = height
|
self.cell_height = height
|
||||||
|
|
||||||
self.number_of_outputs = int(math.pow(2, self.number_of_inputs))
|
self.number_of_outputs = int(math.pow(2, self.number_of_inputs))
|
||||||
design.design.__init__(self, name)
|
design.design.__init__(self, name)
|
||||||
|
|
||||||
@@ -44,34 +39,73 @@ class hierarchical_predecode(design.design):
|
|||||||
|
|
||||||
def add_modules(self):
|
def add_modules(self):
|
||||||
""" Add the INV and AND gate modules """
|
""" Add the INV and AND gate modules """
|
||||||
|
|
||||||
self.inv = factory.create(module_type="pinv",
|
|
||||||
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):
|
# FIXME: Default parms are required for hard cells for now.
|
||||||
""" Create the NAND for the predecode input stage """
|
if self.number_of_inputs == 2:
|
||||||
if inputs==2:
|
self.and_mod = factory.create(module_type="pand2_dec",
|
||||||
self.and_mod = factory.create(module_type="pand2",
|
|
||||||
height=self.cell_height)
|
height=self.cell_height)
|
||||||
elif inputs==3:
|
elif self.number_of_inputs == 3:
|
||||||
self.and_mod = factory.create(module_type="pand3",
|
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)
|
height=self.cell_height)
|
||||||
else:
|
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):
|
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
|
self.height = self.number_of_outputs * self.and_mod.height
|
||||||
|
|
||||||
# x offset for input inverters
|
# x offset for input inverters
|
||||||
self.x_off_inv_1 = self.number_of_inputs * self.m2_pitch + self.m2_space
|
# +1 input for spacing for supply rail contacts
|
||||||
|
self.x_off_inv_1 = (self.number_of_inputs + 1) * self.bus_pitch + self.bus_pitch
|
||||||
|
|
||||||
# x offset to AND decoder includes the left rails, mid rails and inverters, plus two extra m2 pitches
|
# x offset to AND decoder includes the left rails, mid rails and inverters, plus two extra bus pitches
|
||||||
self.x_off_and = self.x_off_inv_1 + self.inv.width + (2 * self.number_of_inputs + 2) * self.m2_pitch
|
self.x_off_and = self.x_off_inv_1 + self.inv.width + (2 * self.number_of_inputs + 2) * self.bus_pitch
|
||||||
|
|
||||||
# x offset to output inverters
|
# x offset to output inverters
|
||||||
self.width = self.x_off_and + self.and_mod.width
|
self.width = self.x_off_and + self.and_mod.width
|
||||||
@@ -79,28 +113,30 @@ class hierarchical_predecode(design.design):
|
|||||||
def route_rails(self):
|
def route_rails(self):
|
||||||
""" Create all of the rails for the inputs and vdd/gnd/inputs_bar/inputs """
|
""" Create all of the rails for the inputs and vdd/gnd/inputs_bar/inputs """
|
||||||
input_names = ["in_{}".format(x) for x in range(self.number_of_inputs)]
|
input_names = ["in_{}".format(x) for x in range(self.number_of_inputs)]
|
||||||
offset = vector(0.5 * self.m2_width, self.m3_pitch)
|
# Offsets for the perimeter spacing to other modules
|
||||||
self.input_rails = self.create_vertical_pin_bus(layer="m2",
|
# This uses m3 pitch to leave space for power routes
|
||||||
offset=offset,
|
offset = vector(self.bus_pitch, self.bus_pitch)
|
||||||
names=input_names,
|
self.input_rails = self.create_vertical_bus(layer=self.bus_layer,
|
||||||
length=self.height - 2 * self.m1_pitch)
|
offset=offset,
|
||||||
|
names=input_names,
|
||||||
|
length=self.height - 2 * self.bus_pitch)
|
||||||
|
|
||||||
invert_names = ["Abar_{}".format(x) for x in range(self.number_of_inputs)]
|
invert_names = ["Abar_{}".format(x) for x in range(self.number_of_inputs)]
|
||||||
non_invert_names = ["A_{}".format(x) for x in range(self.number_of_inputs)]
|
non_invert_names = ["A_{}".format(x) for x in range(self.number_of_inputs)]
|
||||||
decode_names = invert_names + non_invert_names
|
decode_names = invert_names + non_invert_names
|
||||||
offset = vector(self.x_off_inv_1 + self.inv.width + 2 * self.m2_pitch, self.m3_pitch)
|
offset = vector(self.x_off_inv_1 + self.inv.width + self.bus_pitch, self.bus_pitch)
|
||||||
self.decode_rails = self.create_vertical_bus(layer="m2",
|
self.decode_rails = self.create_vertical_bus(layer=self.bus_layer,
|
||||||
offset=offset,
|
offset=offset,
|
||||||
names=decode_names,
|
names=decode_names,
|
||||||
length=self.height - 2 * self.m1_pitch)
|
length=self.height - 2 * self.bus_pitch)
|
||||||
|
|
||||||
def create_input_inverters(self):
|
def create_input_inverters(self):
|
||||||
""" Create the input inverters to invert input signals for the decode stage. """
|
""" Create the input inverters to invert input signals for the decode stage. """
|
||||||
self.in_inst = []
|
self.inv_inst = []
|
||||||
for inv_num in range(self.number_of_inputs):
|
for inv_num in range(self.number_of_inputs):
|
||||||
name = "pre_inv_{0}".format(inv_num)
|
name = "pre_inv_{0}".format(inv_num)
|
||||||
self.in_inst.append(self.add_inst(name=name,
|
self.inv_inst.append(self.add_inst(name=name,
|
||||||
mod=self.inv))
|
mod=self.inv))
|
||||||
self.connect_inst(["in_{0}".format(inv_num),
|
self.connect_inst(["in_{0}".format(inv_num),
|
||||||
"inbar_{0}".format(inv_num),
|
"inbar_{0}".format(inv_num),
|
||||||
"vdd", "gnd"])
|
"vdd", "gnd"])
|
||||||
@@ -108,6 +144,7 @@ class hierarchical_predecode(design.design):
|
|||||||
def place_input_inverters(self):
|
def place_input_inverters(self):
|
||||||
""" Place the input inverters to invert input signals for the decode stage. """
|
""" Place the input inverters to invert input signals for the decode stage. """
|
||||||
for inv_num in range(self.number_of_inputs):
|
for inv_num in range(self.number_of_inputs):
|
||||||
|
|
||||||
if (inv_num % 2 == 0):
|
if (inv_num % 2 == 0):
|
||||||
y_off = inv_num * (self.inv.height)
|
y_off = inv_num * (self.inv.height)
|
||||||
mirror = "R0"
|
mirror = "R0"
|
||||||
@@ -115,8 +152,8 @@ class hierarchical_predecode(design.design):
|
|||||||
y_off = (inv_num + 1) * (self.inv.height)
|
y_off = (inv_num + 1) * (self.inv.height)
|
||||||
mirror="MX"
|
mirror="MX"
|
||||||
offset = vector(self.x_off_inv_1, y_off)
|
offset = vector(self.x_off_inv_1, y_off)
|
||||||
self.in_inst[inv_num].place(offset=offset,
|
self.inv_inst[inv_num].place(offset=offset,
|
||||||
mirror=mirror)
|
mirror=mirror)
|
||||||
|
|
||||||
def create_and_array(self, connections):
|
def create_and_array(self, connections):
|
||||||
""" Create the AND stage for the decodes """
|
""" Create the AND stage for the decodes """
|
||||||
@@ -151,21 +188,30 @@ class hierarchical_predecode(design.design):
|
|||||||
|
|
||||||
def route_inputs_to_rails(self):
|
def route_inputs_to_rails(self):
|
||||||
""" Route the uninverted inputs to the second set of rails """
|
""" Route the uninverted inputs to the second set of rails """
|
||||||
|
|
||||||
|
top_and_gate = self.and_inst[-1]
|
||||||
for num in range(self.number_of_inputs):
|
for num in range(self.number_of_inputs):
|
||||||
# route one signal next to each vdd/gnd rail since this is
|
if num == 0:
|
||||||
# typically where the p/n devices are and there are no
|
pin = top_and_gate.get_pin("A")
|
||||||
# pins in the and gates.
|
elif num == 1:
|
||||||
y_offset = (num + self.number_of_inputs) * self.inv.height + contact.m2_via.width + self.m2_space
|
pin = top_and_gate.get_pin("B")
|
||||||
|
elif num == 2:
|
||||||
|
pin = top_and_gate.get_pin("C")
|
||||||
|
elif num == 3:
|
||||||
|
pin = top_and_gate.get_pin("D")
|
||||||
|
else:
|
||||||
|
debug.error("Too many inputs for predecoder.", -1)
|
||||||
|
y_offset = pin.cy()
|
||||||
in_pin = "in_{}".format(num)
|
in_pin = "in_{}".format(num)
|
||||||
a_pin = "A_{}".format(num)
|
a_pin = "A_{}".format(num)
|
||||||
in_pos = vector(self.input_rails[in_pin].x, y_offset)
|
in_pos = vector(self.input_rails[in_pin].x, y_offset)
|
||||||
a_pos = vector(self.decode_rails[a_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_path(self.input_layer, [in_pos, a_pos])
|
||||||
self.add_via_stack_center(from_layer="m1",
|
self.add_via_stack_center(from_layer=self.input_layer,
|
||||||
to_layer="m2",
|
to_layer=self.bus_layer,
|
||||||
offset=[self.input_rails[in_pin].x, y_offset])
|
offset=[self.input_rails[in_pin].x, y_offset])
|
||||||
self.add_via_stack_center(from_layer="m1",
|
self.add_via_stack_center(from_layer=self.input_layer,
|
||||||
to_layer="m2",
|
to_layer=self.bus_layer,
|
||||||
offset=[self.decode_rails[a_pin].x, y_offset])
|
offset=[self.decode_rails[a_pin].x, y_offset])
|
||||||
|
|
||||||
def route_output_and(self):
|
def route_output_and(self):
|
||||||
@@ -188,31 +234,47 @@ class hierarchical_predecode(design.design):
|
|||||||
for inv_num in range(self.number_of_inputs):
|
for inv_num in range(self.number_of_inputs):
|
||||||
out_pin = "Abar_{}".format(inv_num)
|
out_pin = "Abar_{}".format(inv_num)
|
||||||
in_pin = "in_{}".format(inv_num)
|
in_pin = "in_{}".format(inv_num)
|
||||||
|
|
||||||
|
inv_out_pin = self.inv_inst[inv_num].get_pin("Z")
|
||||||
|
inv_out_pos = inv_out_pin.rc()
|
||||||
|
|
||||||
# add output so that it is just below the vdd or gnd rail
|
# add output so that it is just below the vdd or gnd rail
|
||||||
# since this is where the p/n devices are and there are no
|
# since this is where the p/n devices are and there are no
|
||||||
# pins in the and gates.
|
# pins in the and gates.
|
||||||
y_offset = (inv_num + 1) * self.inv.height - 3 * self.m1_space
|
if OPTS.tech_name == "s8":
|
||||||
inv_out_pin = self.in_inst[inv_num].get_pin("Z")
|
rail_pos = vector(self.decode_rails[out_pin].x, inv_out_pos.y)
|
||||||
inv_out_pos = inv_out_pin.rc()
|
self.add_path(self.output_layer, [inv_out_pos, rail_pos])
|
||||||
right_pos = inv_out_pos + vector(self.inv.width - self.inv.get_pin("Z").lx(), 0)
|
else:
|
||||||
rail_pos = vector(self.decode_rails[out_pin].x, y_offset)
|
y_offset = (inv_num + 1) * self.inv.height - self.output_layer_pitch
|
||||||
self.add_path(inv_out_pin.layer, [inv_out_pos, right_pos, vector(right_pos.x, y_offset), rail_pos])
|
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,
|
self.add_via_stack_center(from_layer=inv_out_pin.layer,
|
||||||
to_layer="m2",
|
to_layer=self.output_layer,
|
||||||
offset=rail_pos)
|
offset=inv_out_pos)
|
||||||
|
self.add_via_stack_center(from_layer=self.output_layer,
|
||||||
|
to_layer=self.bus_layer,
|
||||||
|
offset=rail_pos,
|
||||||
|
directions=self.bus_directions)
|
||||||
|
|
||||||
# route input
|
# route input
|
||||||
pin = self.in_inst[inv_num].get_pin("A")
|
pin = self.inv_inst[inv_num].get_pin("A")
|
||||||
inv_in_pos = pin.lc()
|
inv_in_pos = pin.center()
|
||||||
in_pos = vector(self.input_rails[in_pin].x, inv_in_pos.y)
|
in_pos = vector(self.input_rails[in_pin].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,
|
self.add_via_stack_center(from_layer=pin.layer,
|
||||||
to_layer="m1",
|
to_layer=self.input_layer,
|
||||||
offset=inv_in_pos)
|
offset=inv_in_pos)
|
||||||
self.add_via_stack_center(from_layer="m1",
|
via=self.add_via_stack_center(from_layer=self.input_layer,
|
||||||
to_layer="m2",
|
to_layer=self.bus_layer,
|
||||||
offset=in_pos)
|
offset=in_pos)
|
||||||
|
# Create the input pin at this location on the rail
|
||||||
|
self.add_layout_pin_rect_center(text=in_pin,
|
||||||
|
layer=self.bus_layer,
|
||||||
|
offset=in_pos,
|
||||||
|
height=via.mod.second_layer_height,
|
||||||
|
width=via.mod.second_layer_width)
|
||||||
|
|
||||||
def route_and_to_rails(self):
|
def route_and_to_rails(self):
|
||||||
# This 2D array defines the connection mapping
|
# This 2D array defines the connection mapping
|
||||||
@@ -231,43 +293,67 @@ class hierarchical_predecode(design.design):
|
|||||||
pin = self.and_inst[k].get_pin(gate_pin)
|
pin = self.and_inst[k].get_pin(gate_pin)
|
||||||
pin_pos = pin.center()
|
pin_pos = pin.center()
|
||||||
rail_pos = vector(self.decode_rails[rail_pin].x, pin_pos.y)
|
rail_pos = vector(self.decode_rails[rail_pin].x, pin_pos.y)
|
||||||
self.add_path("m1", [rail_pos, pin_pos])
|
self.add_path(self.input_layer, [rail_pos, pin_pos])
|
||||||
self.add_via_stack_center(from_layer="m1",
|
self.add_via_stack_center(from_layer=self.input_layer,
|
||||||
to_layer="m2",
|
to_layer=self.bus_layer,
|
||||||
offset=rail_pos)
|
offset=rail_pos,
|
||||||
|
directions=self.bus_directions)
|
||||||
if gate_pin == "A":
|
if gate_pin == "A":
|
||||||
direction = None
|
direction = None
|
||||||
else:
|
else:
|
||||||
direction = ("H", "H")
|
direction = ("H", "H")
|
||||||
|
|
||||||
self.add_via_stack_center(from_layer=pin.layer,
|
self.add_via_stack_center(from_layer=pin.layer,
|
||||||
to_layer="m1",
|
to_layer=self.input_layer,
|
||||||
offset=pin_pos,
|
offset=pin_pos,
|
||||||
directions=direction)
|
directions=direction)
|
||||||
|
|
||||||
def route_vdd_gnd(self):
|
def route_vdd_gnd(self):
|
||||||
""" Add a pin for each row of vdd/gnd which are must-connects next level up. """
|
""" Add a pin for each row of vdd/gnd which are must-connects next level up. """
|
||||||
|
|
||||||
# Find the x offsets for where the vias/pins should be placed
|
# In s8, we use hand-made decoder cells with vertical power
|
||||||
in_xoffset = self.in_inst[0].rx() + self.m1_space
|
if OPTS.tech_name == "s8":
|
||||||
# out_xoffset = self.and_inst[0].cx() + self.m1_space
|
|
||||||
for num in range(0, self.number_of_outputs):
|
|
||||||
# this will result in duplicate polygons for rails, but who cares
|
|
||||||
|
|
||||||
# Route both supplies
|
|
||||||
for n in ["vdd", "gnd"]:
|
for n in ["vdd", "gnd"]:
|
||||||
and_pin = self.and_inst[num].get_pin(n)
|
# This makes a wire from top to bottom for both inv and and gates
|
||||||
supply_offset = and_pin.ll().scale(0, 1)
|
for i in [self.inv_inst, self.and_inst]:
|
||||||
self.add_rect(layer=and_pin.layer,
|
bot_pins = i[0].get_pins(n)
|
||||||
offset=supply_offset,
|
top_pins = i[-1].get_pins(n)
|
||||||
width=self.and_inst[num].rx())
|
for (bot_pin, top_pin) in zip(bot_pins, top_pins):
|
||||||
|
self.add_rect(layer=bot_pin.layer,
|
||||||
|
offset=vector(bot_pin.lx(), self.bus_pitch),
|
||||||
|
width=bot_pin.width(),
|
||||||
|
height=top_pin.uy() - self.bus_pitch)
|
||||||
|
# This adds power vias at the top of each cell
|
||||||
|
# (except the last to keep them inside the boundary)
|
||||||
|
for i in self.inv_inst[:-1:2] + self.and_inst[:-1:2]:
|
||||||
|
pins = i.get_pins(n)
|
||||||
|
for pin in pins:
|
||||||
|
self.add_power_pin(name=n,
|
||||||
|
loc=pin.uc(),
|
||||||
|
start_layer=pin.layer)
|
||||||
|
self.add_power_pin(name=n,
|
||||||
|
loc=pin.uc(),
|
||||||
|
start_layer=pin.layer)
|
||||||
|
|
||||||
|
# In other techs, we are using standard cell decoder cells with horizontal power
|
||||||
|
else:
|
||||||
|
for num in range(0, self.number_of_outputs):
|
||||||
|
|
||||||
# Add pins in two locations
|
# Route both supplies
|
||||||
for xoffset in [in_xoffset]:
|
for n in ["vdd", "gnd"]:
|
||||||
pin_pos = vector(xoffset, and_pin.cy())
|
and_pins = self.and_inst[num].get_pins(n)
|
||||||
self.add_power_pin(name=n,
|
for and_pin in and_pins:
|
||||||
loc=pin_pos,
|
self.add_segment_center(layer=and_pin.layer,
|
||||||
start_layer=and_pin.layer)
|
start=vector(0, and_pin.cy()),
|
||||||
|
end=vector(self.width, and_pin.cy()))
|
||||||
|
|
||||||
|
# Add pins in two locations
|
||||||
|
for xoffset in [self.inv_inst[0].lx() - self.bus_space,
|
||||||
|
self.and_inst[0].lx() - self.bus_space]:
|
||||||
|
pin_pos = vector(xoffset, and_pin.cy())
|
||||||
|
self.add_power_pin(name=n,
|
||||||
|
loc=pin_pos,
|
||||||
|
start_layer=and_pin.layer)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,13 +5,10 @@
|
|||||||
# (acting for and on behalf of Oklahoma State University)
|
# (acting for and on behalf of Oklahoma State University)
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
#
|
#
|
||||||
from tech import drc
|
|
||||||
import debug
|
|
||||||
import design
|
|
||||||
from vector import vector
|
|
||||||
from hierarchical_predecode import hierarchical_predecode
|
from hierarchical_predecode import hierarchical_predecode
|
||||||
from globals import OPTS
|
from globals import OPTS
|
||||||
|
|
||||||
|
|
||||||
class hierarchical_predecode2x4(hierarchical_predecode):
|
class hierarchical_predecode2x4(hierarchical_predecode):
|
||||||
"""
|
"""
|
||||||
Pre 2x4 decoder used in hierarchical_decoder.
|
Pre 2x4 decoder used in hierarchical_decoder.
|
||||||
@@ -33,21 +30,6 @@ class hierarchical_predecode2x4(hierarchical_predecode):
|
|||||||
["in_0", "in_1", "out_3", "vdd", "gnd"]]
|
["in_0", "in_1", "out_3", "vdd", "gnd"]]
|
||||||
self.create_and_array(connections)
|
self.create_and_array(connections)
|
||||||
|
|
||||||
def create_layout(self):
|
|
||||||
""" The general organization is from left to right:
|
|
||||||
1) a set of M2 rails for input signals
|
|
||||||
2) a set of inverters to invert input signals
|
|
||||||
3) a set of M2 rails for the vdd, gnd, inverted inputs, inputs
|
|
||||||
4) a set of AND gates for inversion
|
|
||||||
"""
|
|
||||||
self.setup_layout_constraints()
|
|
||||||
self.route_rails()
|
|
||||||
self.place_input_inverters()
|
|
||||||
self.place_and_array()
|
|
||||||
self.route()
|
|
||||||
self.add_boundary()
|
|
||||||
self.DRC_LVS()
|
|
||||||
|
|
||||||
def get_and_input_line_combination(self):
|
def get_and_input_line_combination(self):
|
||||||
""" These are the decoder connections of the AND gates to the A,B pins """
|
""" These are the decoder connections of the AND gates to the A,B pins """
|
||||||
combination = [["Abar_0", "Abar_1"],
|
combination = [["Abar_0", "Abar_1"],
|
||||||
|
|||||||
@@ -5,13 +5,10 @@
|
|||||||
# (acting for and on behalf of Oklahoma State University)
|
# (acting for and on behalf of Oklahoma State University)
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
#
|
#
|
||||||
from tech import drc
|
|
||||||
import debug
|
|
||||||
import design
|
|
||||||
from vector import vector
|
|
||||||
from hierarchical_predecode import hierarchical_predecode
|
from hierarchical_predecode import hierarchical_predecode
|
||||||
from globals import OPTS
|
from globals import OPTS
|
||||||
|
|
||||||
|
|
||||||
class hierarchical_predecode3x8(hierarchical_predecode):
|
class hierarchical_predecode3x8(hierarchical_predecode):
|
||||||
"""
|
"""
|
||||||
Pre 3x8 decoder used in hierarchical_decoder.
|
Pre 3x8 decoder used in hierarchical_decoder.
|
||||||
@@ -37,22 +34,6 @@ class hierarchical_predecode3x8(hierarchical_predecode):
|
|||||||
["in_0", "in_1", "in_2", "out_7", "vdd", "gnd"]]
|
["in_0", "in_1", "in_2", "out_7", "vdd", "gnd"]]
|
||||||
self.create_and_array(connections)
|
self.create_and_array(connections)
|
||||||
|
|
||||||
def create_layout(self):
|
|
||||||
"""
|
|
||||||
The general organization is from left to right:
|
|
||||||
1) a set of M2 rails for input signals
|
|
||||||
2) a set of inverters to invert input signals
|
|
||||||
3) a set of M2 rails for the vdd, gnd, inverted inputs, inputs
|
|
||||||
4) a set of NAND gates for inversion
|
|
||||||
"""
|
|
||||||
self.setup_layout_constraints()
|
|
||||||
self.route_rails()
|
|
||||||
self.place_input_inverters()
|
|
||||||
self.place_and_array()
|
|
||||||
self.route()
|
|
||||||
self.add_boundary()
|
|
||||||
self.DRC_LVS()
|
|
||||||
|
|
||||||
def get_and_input_line_combination(self):
|
def get_and_input_line_combination(self):
|
||||||
""" These are the decoder connections of the NAND gates to the A,B,C pins """
|
""" These are the decoder connections of the NAND gates to the A,B,C pins """
|
||||||
combination = [["Abar_0", "Abar_1", "Abar_2"],
|
combination = [["Abar_0", "Abar_1", "Abar_2"],
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -8,7 +8,7 @@ import debug
|
|||||||
import design
|
import design
|
||||||
from sram_factory import factory
|
from sram_factory import factory
|
||||||
from vector import vector
|
from vector import vector
|
||||||
|
from tech import layer
|
||||||
from globals import OPTS
|
from globals import OPTS
|
||||||
|
|
||||||
|
|
||||||
@@ -41,6 +41,10 @@ class port_address(design.design):
|
|||||||
self.create_wordline_driver()
|
self.create_wordline_driver()
|
||||||
|
|
||||||
def create_layout(self):
|
def create_layout(self):
|
||||||
|
if "li" in layer:
|
||||||
|
self.route_layer = "li"
|
||||||
|
else:
|
||||||
|
self.route_layer = "m1"
|
||||||
self.place_instances()
|
self.place_instances()
|
||||||
self.route_layout()
|
self.route_layout()
|
||||||
self.DRC_LVS()
|
self.DRC_LVS()
|
||||||
@@ -85,11 +89,19 @@ class port_address(design.design):
|
|||||||
def route_internal(self):
|
def route_internal(self):
|
||||||
for row in range(self.num_rows):
|
for row in range(self.num_rows):
|
||||||
# The pre/post is to access the pin from "outside" the cell to avoid DRCs
|
# The pre/post is to access the pin from "outside" the cell to avoid DRCs
|
||||||
decoder_out_pos = self.row_decoder_inst.get_pin("decode_{}".format(row)).rc()
|
decoder_out_pin = self.row_decoder_inst.get_pin("decode_{}".format(row))
|
||||||
driver_in_pos = self.wordline_driver_inst.get_pin("in_{}".format(row)).lc()
|
decoder_out_pos = decoder_out_pin.rc()
|
||||||
mid1 = decoder_out_pos.scale(0.5, 1) + driver_in_pos.scale(0.5, 0)
|
driver_in_pin = self.wordline_driver_inst.get_pin("in_{}".format(row))
|
||||||
mid2 = decoder_out_pos.scale(0.5, 0) + driver_in_pos.scale(0.5, 1)
|
driver_in_pos = driver_in_pin.lc()
|
||||||
self.add_path("m1", [decoder_out_pos, mid1, mid2, driver_in_pos])
|
self.add_zjog(self.route_layer, decoder_out_pos, driver_in_pos)
|
||||||
|
|
||||||
|
self.add_via_stack_center(from_layer=decoder_out_pin.layer,
|
||||||
|
to_layer=self.route_layer,
|
||||||
|
offset=decoder_out_pos)
|
||||||
|
|
||||||
|
self.add_via_stack_center(from_layer=driver_in_pin.layer,
|
||||||
|
to_layer=self.route_layer,
|
||||||
|
offset=driver_in_pos)
|
||||||
|
|
||||||
def add_modules(self):
|
def add_modules(self):
|
||||||
|
|
||||||
@@ -97,7 +109,7 @@ class port_address(design.design):
|
|||||||
num_outputs=self.num_rows)
|
num_outputs=self.num_rows)
|
||||||
self.add_mod(self.row_decoder)
|
self.add_mod(self.row_decoder)
|
||||||
|
|
||||||
self.wordline_driver = factory.create(module_type="wordline_driver",
|
self.wordline_driver = factory.create(module_type="wordline_driver_array",
|
||||||
rows=self.num_rows,
|
rows=self.num_rows,
|
||||||
cols=self.num_cols)
|
cols=self.num_cols)
|
||||||
self.add_mod(self.wordline_driver)
|
self.add_mod(self.wordline_driver)
|
||||||
@@ -139,7 +151,6 @@ class port_address(design.design):
|
|||||||
|
|
||||||
row_decoder_offset = vector(0, 0)
|
row_decoder_offset = vector(0, 0)
|
||||||
wordline_driver_offset = vector(self.row_decoder.width, 0)
|
wordline_driver_offset = vector(self.row_decoder.width, 0)
|
||||||
|
|
||||||
self.wordline_driver_inst.place(wordline_driver_offset)
|
self.wordline_driver_inst.place(wordline_driver_offset)
|
||||||
self.row_decoder_inst.place(row_decoder_offset)
|
self.row_decoder_inst.place(row_decoder_offset)
|
||||||
|
|
||||||
|
|||||||
@@ -469,45 +469,37 @@ class port_data(design.design):
|
|||||||
bank_wmask_name = "bank_wmask_{}".format(bit)
|
bank_wmask_name = "bank_wmask_{}".format(bit)
|
||||||
self.copy_layout_pin(self.write_mask_and_array_inst, wmask_in_name, bank_wmask_name)
|
self.copy_layout_pin(self.write_mask_and_array_inst, wmask_in_name, bank_wmask_name)
|
||||||
|
|
||||||
def route_write_mask_and_array_to_write_driver(self,port):
|
def route_write_mask_and_array_to_write_driver(self, port):
|
||||||
""" Routing of wdriver_sel_{} between write mask AND array and write driver array. Adds layout pin for write
|
"""
|
||||||
mask AND array output and via for write driver enable """
|
Routing of wdriver_sel_{} between write mask AND array and
|
||||||
|
write driver array. Adds layout pin for write
|
||||||
|
mask AND array output and via for write driver enable
|
||||||
|
"""
|
||||||
|
|
||||||
inst1 = self.write_mask_and_array_inst
|
wmask_inst = self.write_mask_and_array_inst
|
||||||
inst2 = self.write_driver_array_inst
|
wdriver_inst = self.write_driver_array_inst
|
||||||
|
|
||||||
loc = 0
|
|
||||||
for bit in range(self.num_wmasks):
|
for bit in range(self.num_wmasks):
|
||||||
# Bring write mask AND array output pin to port data level
|
# Bring write mask AND array output pin to port data level
|
||||||
self.copy_layout_pin(inst1, "wmask_out_{0}".format(bit), "wdriver_sel_{0}".format(bit))
|
self.copy_layout_pin(wmask_inst, "wmask_out_{0}".format(bit), "wdriver_sel_{0}".format(bit))
|
||||||
|
|
||||||
wmask_out_pin = inst1.get_pin("wmask_out_{0}".format(bit))
|
wmask_out_pin = wmask_inst.get_pin("wmask_out_{0}".format(bit))
|
||||||
wdriver_en_pin = inst2.get_pin("en_{0}".format(bit))
|
wdriver_en_pin = wdriver_inst.get_pin("en_{0}".format(bit))
|
||||||
|
|
||||||
# The metal2 wdriver_sel_{} wire must hit the en_{} pin after the closest bitline pin that's right of the
|
wmask_pos = wmask_out_pin.center()
|
||||||
# the wdriver_sel_{} pin in the write driver AND array.
|
wdriver_pos = wdriver_en_pin.rc() - vector(self.m2_pitch, 0)
|
||||||
if bit == 0:
|
mid_pos = vector(wdriver_pos.x, wmask_pos.y)
|
||||||
# When the write mask output pin is right of the bitline, the target is found
|
|
||||||
while (wmask_out_pin.lx() + self.m2_pitch > inst2.get_pin("data_{0}".format(loc)).rx()):
|
|
||||||
loc += 1
|
|
||||||
length = inst2.get_pin("data_{0}".format(loc)).rx() + self.m2_pitch
|
|
||||||
debug.check(loc<=self.num_wmasks,
|
|
||||||
"Couldn't route the write mask select.")
|
|
||||||
else:
|
|
||||||
# Stride by the write size rather than finding the next pin to the right
|
|
||||||
loc += self.write_size
|
|
||||||
length = inst2.get_pin("data_{0}".format(loc)).rx() + self.m2_pitch
|
|
||||||
|
|
||||||
beg_pos = wmask_out_pin.center()
|
|
||||||
middle_pos = vector(length, wmask_out_pin.cy())
|
|
||||||
end_pos = vector(length, wdriver_en_pin.cy())
|
|
||||||
|
|
||||||
|
# Add driver on mask output
|
||||||
|
self.add_via_center(layers=self.m1_stack,
|
||||||
|
offset=wmask_pos)
|
||||||
# Add via for the write driver array's enable input
|
# Add via for the write driver array's enable input
|
||||||
self.add_via_center(layers=self.m1_stack,
|
self.add_via_center(layers=self.m1_stack,
|
||||||
offset=end_pos)
|
offset=wdriver_pos)
|
||||||
|
|
||||||
# Route between write mask AND array and write driver array
|
# Route between write mask AND array and write driver array
|
||||||
self.add_wire(self.m1_stack, [beg_pos, middle_pos, end_pos])
|
self.add_wire(self.m1_stack, [wmask_pos, mid_pos, wdriver_pos])
|
||||||
|
|
||||||
def route_column_mux_to_precharge_array(self, port):
|
def route_column_mux_to_precharge_array(self, port):
|
||||||
""" Routing of BL and BR between col mux and precharge array """
|
""" Routing of BL and BR between col mux and precharge array """
|
||||||
@@ -516,15 +508,12 @@ class port_data(design.design):
|
|||||||
if self.col_addr_size==0:
|
if self.col_addr_size==0:
|
||||||
return
|
return
|
||||||
|
|
||||||
inst1 = self.column_mux_array_inst
|
start_bit = 1 if self.port == 0 else 0
|
||||||
inst2 = self.precharge_array_inst
|
|
||||||
|
|
||||||
insn2_start_bit = 1 if self.port == 0 else 0
|
self.connect_bitlines(inst1=self.column_mux_array_inst,
|
||||||
|
inst2=self.precharge_array_inst,
|
||||||
self.channel_route_bitlines(inst1=inst1,
|
num_bits=self.num_cols,
|
||||||
inst2=inst2,
|
inst2_start_bit=start_bit)
|
||||||
num_bits=self.num_cols,
|
|
||||||
inst2_start_bit=insn2_start_bit)
|
|
||||||
|
|
||||||
def route_sense_amp_to_column_mux_or_precharge_array(self, port):
|
def route_sense_amp_to_column_mux_or_precharge_array(self, port):
|
||||||
""" Routing of BL and BR between sense_amp and column mux or precharge array """
|
""" Routing of BL and BR between sense_amp and column mux or precharge array """
|
||||||
|
|||||||
@@ -7,15 +7,12 @@
|
|||||||
#
|
#
|
||||||
import debug
|
import debug
|
||||||
import design
|
import design
|
||||||
import math
|
from tech import drc, layer
|
||||||
from tech import drc
|
|
||||||
from vector import vector
|
from vector import vector
|
||||||
from sram_factory import factory
|
from sram_factory import factory
|
||||||
from globals import OPTS
|
from globals import OPTS
|
||||||
from tech import cell_properties
|
|
||||||
|
|
||||||
|
class wordline_driver_array(design.design):
|
||||||
class wordline_driver(design.design):
|
|
||||||
"""
|
"""
|
||||||
Creates a Wordline Driver
|
Creates a Wordline Driver
|
||||||
Generates the wordline-driver to drive the bitcell
|
Generates the wordline-driver to drive the bitcell
|
||||||
@@ -26,21 +23,9 @@ class wordline_driver(design.design):
|
|||||||
debug.info(1, "Creating {0}".format(self.name))
|
debug.info(1, "Creating {0}".format(self.name))
|
||||||
self.add_comment("rows: {0} cols: {1}".format(rows, cols))
|
self.add_comment("rows: {0} cols: {1}".format(rows, cols))
|
||||||
|
|
||||||
self.bitcell_rows = rows
|
self.rows = rows
|
||||||
self.bitcell_cols = cols
|
self.cols = cols
|
||||||
|
|
||||||
b = factory.create(module_type="bitcell")
|
|
||||||
try:
|
|
||||||
self.cell_multiple = cell_properties.bitcell.decoder_bitcell_multiple
|
|
||||||
except AttributeError:
|
|
||||||
self.cell_multiple = 1
|
|
||||||
self.cell_height = self.cell_multiple * b.height
|
|
||||||
|
|
||||||
# We may have more than one bitcell per decoder row
|
|
||||||
self.num_rows = math.ceil(self.bitcell_rows / self.cell_multiple)
|
|
||||||
# We will place this many final decoders per row
|
|
||||||
self.decoders_per_row = math.ceil(self.bitcell_rows / self.num_rows)
|
|
||||||
|
|
||||||
self.create_netlist()
|
self.create_netlist()
|
||||||
if not OPTS.netlist_only:
|
if not OPTS.netlist_only:
|
||||||
self.create_layout()
|
self.create_layout()
|
||||||
@@ -51,7 +36,10 @@ class wordline_driver(design.design):
|
|||||||
self.create_drivers()
|
self.create_drivers()
|
||||||
|
|
||||||
def create_layout(self):
|
def create_layout(self):
|
||||||
self.setup_layout_constants()
|
if "li" in layer:
|
||||||
|
self.route_layer = "li"
|
||||||
|
else:
|
||||||
|
self.route_layer = "m1"
|
||||||
self.place_drivers()
|
self.place_drivers()
|
||||||
self.route_layout()
|
self.route_layout()
|
||||||
self.route_vdd_gnd()
|
self.route_vdd_gnd()
|
||||||
@@ -61,104 +49,99 @@ class wordline_driver(design.design):
|
|||||||
|
|
||||||
def add_pins(self):
|
def add_pins(self):
|
||||||
# inputs to wordline_driver.
|
# inputs to wordline_driver.
|
||||||
for i in range(self.bitcell_rows):
|
for i in range(self.rows):
|
||||||
self.add_pin("in_{0}".format(i), "INPUT")
|
self.add_pin("in_{0}".format(i), "INPUT")
|
||||||
# Outputs from wordline_driver.
|
# Outputs from wordline_driver.
|
||||||
for i in range(self.bitcell_rows):
|
for i in range(self.rows):
|
||||||
self.add_pin("wl_{0}".format(i), "OUTPUT")
|
self.add_pin("wl_{0}".format(i), "OUTPUT")
|
||||||
self.add_pin("en", "INPUT")
|
self.add_pin("en", "INPUT")
|
||||||
self.add_pin("vdd", "POWER")
|
self.add_pin("vdd", "POWER")
|
||||||
self.add_pin("gnd", "GROUND")
|
self.add_pin("gnd", "GROUND")
|
||||||
|
|
||||||
def add_modules(self):
|
def add_modules(self):
|
||||||
self.and2 = factory.create(module_type="pand2",
|
self.wl_driver = factory.create(module_type="wordline_driver",
|
||||||
height=self.cell_height,
|
size=self.cols)
|
||||||
size=self.bitcell_cols)
|
self.add_mod(self.wl_driver)
|
||||||
self.add_mod(self.and2)
|
|
||||||
|
|
||||||
def route_vdd_gnd(self):
|
def route_vdd_gnd(self):
|
||||||
"""
|
"""
|
||||||
Add a pin for each row of vdd/gnd which
|
Add a pin for each row of vdd/gnd which
|
||||||
are must-connects next level up.
|
are must-connects next level up.
|
||||||
"""
|
"""
|
||||||
|
if OPTS.tech_name == "s8":
|
||||||
# Find the x offsets for where the vias/pins should be placed
|
|
||||||
xoffset_list = [self.and_inst[0].lx()]
|
|
||||||
for num in range(self.bitcell_rows):
|
|
||||||
# this will result in duplicate polygons for rails, but who cares
|
|
||||||
|
|
||||||
# use the inverter offset even though it will be the and's too
|
|
||||||
(gate_offset, y_dir) = self.get_gate_offset(0,
|
|
||||||
self.and2.height,
|
|
||||||
num)
|
|
||||||
# Route both supplies
|
|
||||||
for name in ["vdd", "gnd"]:
|
for name in ["vdd", "gnd"]:
|
||||||
supply_pin = self.and_inst[num].get_pin(name)
|
supply_pins = self.wld_inst[0].get_pins(name)
|
||||||
|
for pin in supply_pins:
|
||||||
# Add pins in two locations
|
self.add_layout_pin_segment_center(text=name,
|
||||||
for xoffset in xoffset_list:
|
layer=pin.layer,
|
||||||
pin_pos = vector(xoffset, supply_pin.cy())
|
start=pin.bc(),
|
||||||
self.add_power_pin(name, pin_pos)
|
end=vector(pin.cx(), self.height))
|
||||||
|
else:
|
||||||
|
# Find the x offsets for where the vias/pins should be placed
|
||||||
|
xoffset_list = [self.wld_inst[0].rx()]
|
||||||
|
for num in range(self.rows):
|
||||||
|
# this will result in duplicate polygons for rails, but who cares
|
||||||
|
|
||||||
|
# use the inverter offset even though it will be the and's too
|
||||||
|
(gate_offset, y_dir) = self.get_gate_offset(0,
|
||||||
|
self.wl_driver.height,
|
||||||
|
num)
|
||||||
|
# Route both supplies
|
||||||
|
for name in ["vdd", "gnd"]:
|
||||||
|
supply_pin = self.wld_inst[num].get_pin(name)
|
||||||
|
|
||||||
|
# Add pins in two locations
|
||||||
|
for xoffset in xoffset_list:
|
||||||
|
pin_pos = vector(xoffset, supply_pin.cy())
|
||||||
|
self.add_power_pin(name, pin_pos)
|
||||||
|
|
||||||
def create_drivers(self):
|
def create_drivers(self):
|
||||||
self.and_inst = []
|
self.wld_inst = []
|
||||||
for row in range(self.bitcell_rows):
|
for row in range(self.rows):
|
||||||
name_and = "wl_driver_and{}".format(row)
|
name_and = "wl_driver_and{}".format(row)
|
||||||
|
|
||||||
# add and2
|
# add and2
|
||||||
self.and_inst.append(self.add_inst(name=name_and,
|
self.wld_inst.append(self.add_inst(name=name_and,
|
||||||
mod=self.and2))
|
mod=self.wl_driver))
|
||||||
self.connect_inst(["in_{0}".format(row),
|
self.connect_inst(["in_{0}".format(row),
|
||||||
"en",
|
"en",
|
||||||
"wl_{0}".format(row),
|
"wl_{0}".format(row),
|
||||||
"vdd", "gnd"])
|
"vdd", "gnd"])
|
||||||
|
|
||||||
def setup_layout_constants(self):
|
|
||||||
# We may have more than one bitcell per decoder row
|
|
||||||
self.driver_rows = math.ceil(self.bitcell_rows / self.cell_multiple)
|
|
||||||
# We will place this many final decoders per row
|
|
||||||
self.decoders_per_row = math.ceil(self.bitcell_rows / self.driver_rows)
|
|
||||||
|
|
||||||
def place_drivers(self):
|
def place_drivers(self):
|
||||||
|
|
||||||
|
for row in range(self.rows):
|
||||||
|
if (row % 2):
|
||||||
|
y_offset = self.wl_driver.height * (row + 1)
|
||||||
|
inst_mirror = "MX"
|
||||||
|
else:
|
||||||
|
y_offset = self.wl_driver.height * row
|
||||||
|
inst_mirror = "R0"
|
||||||
|
|
||||||
|
and2_offset = [self.wl_driver.width, y_offset]
|
||||||
|
|
||||||
|
# add and2
|
||||||
|
self.wld_inst[row].place(offset=and2_offset,
|
||||||
|
mirror=inst_mirror)
|
||||||
|
|
||||||
# Leave a well gap to separate the bitcell array well from this well
|
# Leave a well gap to separate the bitcell array well from this well
|
||||||
well_gap = 2 * drc("pwell_to_nwell") + drc("nwell_enclose_active")
|
well_gap = 2 * drc("pwell_to_nwell") + drc("nwell_enclose_active")
|
||||||
|
self.width = self.wl_driver.width + well_gap
|
||||||
self.width = self.decoders_per_row * self.and2.width + well_gap
|
self.height = self.wl_driver.height * self.rows
|
||||||
self.height = self.and2.height * self.driver_rows
|
|
||||||
|
|
||||||
for inst_index in range(self.bitcell_rows):
|
|
||||||
row = math.floor(inst_index / self.decoders_per_row)
|
|
||||||
dec = inst_index % self.decoders_per_row
|
|
||||||
|
|
||||||
if (row % 2):
|
|
||||||
y_offset = self.and2.height * (row + 1)
|
|
||||||
inst_mirror = "MX"
|
|
||||||
else:
|
|
||||||
y_offset = self.and2.height * row
|
|
||||||
inst_mirror = "R0"
|
|
||||||
|
|
||||||
x_offset = dec * self.and2.width
|
|
||||||
and2_offset = [x_offset, y_offset]
|
|
||||||
|
|
||||||
# add and2
|
|
||||||
self.and_inst[inst_index].place(offset=and2_offset,
|
|
||||||
mirror=inst_mirror)
|
|
||||||
|
|
||||||
def route_layout(self):
|
def route_layout(self):
|
||||||
""" Route all of the signals """
|
""" Route all of the signals """
|
||||||
|
|
||||||
# Wordline enable connection
|
# Wordline enable connection
|
||||||
en_pin = self.and_inst[0].get_pin("B")
|
en_pin = self.wld_inst[0].get_pin("B")
|
||||||
en_bottom_pos = vector(en_pin.lx(), 0)
|
en_bottom_pos = vector(en_pin.lx(), 0)
|
||||||
en_pin = self.add_layout_pin(text="en",
|
en_pin = self.add_layout_pin(text="en",
|
||||||
layer="m2",
|
layer="m2",
|
||||||
offset=en_bottom_pos,
|
offset=en_bottom_pos,
|
||||||
height=self.height)
|
height=self.height)
|
||||||
|
|
||||||
for inst_index in range(self.bitcell_rows):
|
for row in range(self.rows):
|
||||||
and_inst = self.and_inst[inst_index]
|
and_inst = self.wld_inst[row]
|
||||||
row = math.floor(inst_index / self.decoders_per_row)
|
|
||||||
|
|
||||||
# Drop a via
|
# Drop a via
|
||||||
b_pin = and_inst.get_pin("B")
|
b_pin = and_inst.get_pin("B")
|
||||||
@@ -167,18 +150,12 @@ class wordline_driver(design.design):
|
|||||||
offset=b_pin.center())
|
offset=b_pin.center())
|
||||||
|
|
||||||
# connect the decoder input pin to and2 A
|
# connect the decoder input pin to and2 A
|
||||||
a_pin = and_inst.get_pin("A")
|
self.copy_layout_pin(and_inst, "A", "in_{0}".format(row))
|
||||||
a_pos = a_pin.center()
|
|
||||||
# must under the clk line in M1
|
|
||||||
self.add_layout_pin_segment_center(text="in_{0}".format(row),
|
|
||||||
layer="m1",
|
|
||||||
start=vector(0, a_pos.y),
|
|
||||||
end=a_pos)
|
|
||||||
|
|
||||||
# output each WL on the right
|
# output each WL on the right
|
||||||
wl_offset = and_inst.get_pin("Z").rc()
|
wl_offset = and_inst.get_pin("Z").rc()
|
||||||
self.add_layout_pin_segment_center(text="wl_{0}".format(row),
|
self.add_layout_pin_segment_center(text="wl_{0}".format(row),
|
||||||
layer="m1",
|
layer=self.route_layer,
|
||||||
start=wl_offset,
|
start=wl_offset,
|
||||||
end=wl_offset - vector(self.m1_width, 0))
|
end=wl_offset - vector(self.m1_width, 0))
|
||||||
|
|
||||||
@@ -189,7 +166,7 @@ class wordline_driver(design.design):
|
|||||||
"""
|
"""
|
||||||
stage_effort_list = []
|
stage_effort_list = []
|
||||||
|
|
||||||
stage1 = self.and2.get_stage_effort(external_cout, inp_is_rise)
|
stage1 = self.wl_driver.get_stage_effort(external_cout, inp_is_rise)
|
||||||
stage_effort_list.append(stage1)
|
stage_effort_list.append(stage1)
|
||||||
|
|
||||||
return stage_effort_list
|
return stage_effort_list
|
||||||
@@ -200,5 +177,5 @@ class wordline_driver(design.design):
|
|||||||
the enable connections in the bank
|
the enable connections in the bank
|
||||||
"""
|
"""
|
||||||
# The enable is connected to a and2 for every row.
|
# The enable is connected to a and2 for every row.
|
||||||
total_cin = self.and2.get_cin() * self.rows
|
total_cin = self.wl_driver.get_cin() * self.rows
|
||||||
return total_cin
|
return total_cin
|
||||||
@@ -135,6 +135,10 @@ class options(optparse.Values):
|
|||||||
dff_array = "dff_array"
|
dff_array = "dff_array"
|
||||||
dff = "dff"
|
dff = "dff"
|
||||||
dummy_bitcell = "dummy_bitcell"
|
dummy_bitcell = "dummy_bitcell"
|
||||||
|
inv_dec = "pinv"
|
||||||
|
nand2_dec = "pnand2"
|
||||||
|
nand3_dec = "pnand3"
|
||||||
|
nand4_dec = "pnand4" # Not available right now
|
||||||
precharge_array = "precharge_array"
|
precharge_array = "precharge_array"
|
||||||
ptx = "ptx"
|
ptx = "ptx"
|
||||||
replica_bitcell = "replica_bitcell"
|
replica_bitcell = "replica_bitcell"
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from sram_factory import factory
|
|||||||
|
|
||||||
class pand2(pgate.pgate):
|
class pand2(pgate.pgate):
|
||||||
"""
|
"""
|
||||||
This is a simple buffer used for driving loads.
|
This is an AND (or NAND) with configurable drive strength.
|
||||||
"""
|
"""
|
||||||
def __init__(self, name, size=1, height=None, vertical=False, add_wells=True):
|
def __init__(self, name, size=1, height=None, vertical=False, add_wells=True):
|
||||||
debug.info(1, "Creating pand2 {}".format(name))
|
debug.info(1, "Creating pand2 {}".format(name))
|
||||||
|
|||||||
@@ -17,13 +17,13 @@ class pdriver(pgate.pgate):
|
|||||||
sized for driving a load.
|
sized for driving a load.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name, neg_polarity=False, fanout=0, size_list=None, height=None, 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))
|
debug.info(1, "creating pdriver {}".format(name))
|
||||||
|
|
||||||
self.stage_effort = 3
|
self.stage_effort = 3
|
||||||
self.height = height
|
self.height = height
|
||||||
self.neg_polarity = neg_polarity
|
self.inverting = inverting
|
||||||
self.size_list = size_list
|
self.size_list = size_list
|
||||||
self.fanout = fanout
|
self.fanout = fanout
|
||||||
|
|
||||||
@@ -31,8 +31,8 @@ class pdriver(pgate.pgate):
|
|||||||
debug.error("Either fanout or size list must be specified.", -1)
|
debug.error("Either fanout or size list must be specified.", -1)
|
||||||
if self.size_list and self.fanout != 0:
|
if self.size_list and self.fanout != 0:
|
||||||
debug.error("Cannot specify both size_list and fanout.", -1)
|
debug.error("Cannot specify both size_list and fanout.", -1)
|
||||||
if self.size_list and self.neg_polarity:
|
if self.size_list and self.inverting:
|
||||||
debug.error("Cannot specify both size_list and neg_polarity.", -1)
|
debug.error("Cannot specify both size_list and inverting.", -1)
|
||||||
|
|
||||||
# Creates the netlist and layout
|
# Creates the netlist and layout
|
||||||
pgate.pgate.__init__(self, name, height, add_wells)
|
pgate.pgate.__init__(self, name, height, add_wells)
|
||||||
@@ -47,9 +47,9 @@ class pdriver(pgate.pgate):
|
|||||||
int(round(self.fanout ** (1 / self.stage_effort))))
|
int(round(self.fanout ** (1 / self.stage_effort))))
|
||||||
|
|
||||||
# Increase the number of stages if we need to fix polarity
|
# Increase the number of stages if we need to fix polarity
|
||||||
if self.neg_polarity and (self.num_stages % 2 == 0):
|
if self.inverting and (self.num_stages % 2 == 0):
|
||||||
self.num_stages += 1
|
self.num_stages += 1
|
||||||
elif not self.neg_polarity and (self.num_stages % 2):
|
elif not self.inverting and (self.num_stages % 2):
|
||||||
self.num_stages += 1
|
self.num_stages += 1
|
||||||
|
|
||||||
self.size_list = []
|
self.size_list = []
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ class pgate(design.design):
|
|||||||
self.route_layer_pitch = getattr(self, "{}_pitch".format(self.route_layer))
|
self.route_layer_pitch = getattr(self, "{}_pitch".format(self.route_layer))
|
||||||
|
|
||||||
# This is the space from a S/D contact to the supply rail
|
# This is the space from a S/D contact to the supply rail
|
||||||
# Assume the contact starts at the active edge
|
|
||||||
contact_to_vdd_rail_space = 0.5 * self.m1_width + self.m1_space
|
contact_to_vdd_rail_space = 0.5 * self.m1_width + self.m1_space
|
||||||
# This is a poly-to-poly of a flipped cell
|
# This is a poly-to-poly of a flipped cell
|
||||||
poly_to_poly_gate_space = self.poly_extend_active + self.poly_space
|
poly_to_poly_gate_space = self.poly_extend_active + self.poly_space
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
@@ -212,7 +212,10 @@ class pnand3(pgate.pgate):
|
|||||||
pmos_drain_bottom = self.pmos1_inst.get_pin("D").by()
|
pmos_drain_bottom = self.pmos1_inst.get_pin("D").by()
|
||||||
self.output_yoffset = pmos_drain_bottom - 0.5 * self.route_layer_width - self.route_layer_space
|
self.output_yoffset = pmos_drain_bottom - 0.5 * self.route_layer_width - self.route_layer_space
|
||||||
|
|
||||||
|
# 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 - 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.route_input_gate(self.pmos1_inst,
|
||||||
self.nmos1_inst,
|
self.nmos1_inst,
|
||||||
self.inputA_yoffset,
|
self.inputA_yoffset,
|
||||||
|
|||||||
@@ -386,10 +386,12 @@ class ptx(design.design):
|
|||||||
well_ll = center_pos - vector(0.5 * self.well_width,
|
well_ll = center_pos - vector(0.5 * self.well_width,
|
||||||
0.5 * self.well_height)
|
0.5 * self.well_height)
|
||||||
if well_name in layer:
|
if well_name in layer:
|
||||||
self.add_rect(layer=well_name,
|
well = self.add_rect(layer=well_name,
|
||||||
offset=well_ll,
|
offset=well_ll,
|
||||||
width=self.well_width,
|
width=self.well_width,
|
||||||
height=self.well_height)
|
height=self.well_height)
|
||||||
|
setattr(self, well_name, well)
|
||||||
|
|
||||||
if "vtg" in layer:
|
if "vtg" in layer:
|
||||||
self.add_rect(layer="vtg",
|
self.add_rect(layer="vtg",
|
||||||
offset=well_ll,
|
offset=well_ll,
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
@@ -77,7 +77,9 @@ class sram_factory:
|
|||||||
"""
|
"""
|
||||||
tech_module_type, tm_overridden = self.get_techmodule_type(module_type)
|
tech_module_type, tm_overridden = self.get_techmodule_type(module_type)
|
||||||
user_module_type, um_overridden = self.get_usermodule_type(module_type)
|
user_module_type, um_overridden = self.get_usermodule_type(module_type)
|
||||||
|
#print(module_type, tech_module_type, tm_overridden)
|
||||||
|
#print(module_type, user_module_type, um_overridden)
|
||||||
|
|
||||||
# overridden user modules have priority
|
# overridden user modules have priority
|
||||||
if um_overridden:
|
if um_overridden:
|
||||||
real_module_type = user_module_type
|
real_module_type = user_module_type
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class library_lvs_test(openram_test):
|
|||||||
debug.error("Missing GDS file {}".format(gds_name))
|
debug.error("Missing GDS file {}".format(gds_name))
|
||||||
if not os.path.isfile(sp_name):
|
if not os.path.isfile(sp_name):
|
||||||
lvs_errors += 1
|
lvs_errors += 1
|
||||||
debug.error("Missing SPICE file {}".format(gds_name))
|
debug.error("Missing SPICE file {}".format(sp_name))
|
||||||
drc_errors += verify.run_drc(name, gds_name)
|
drc_errors += verify.run_drc(name, gds_name)
|
||||||
lvs_errors += verify.run_lvs(f, gds_name, sp_name)
|
lvs_errors += verify.run_lvs(f, gds_name, sp_name)
|
||||||
|
|
||||||
|
|||||||
Executable
+39
@@ -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())
|
||||||
Executable
+39
@@ -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())
|
||||||
Executable
+39
@@ -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())
|
||||||
@@ -32,13 +32,13 @@ class pdriver_test(openram_test):
|
|||||||
c = factory.create(module_type="pdriver", fanout = 50)
|
c = factory.create(module_type="pdriver", fanout = 50)
|
||||||
self.local_check(c)
|
self.local_check(c)
|
||||||
|
|
||||||
d = factory.create(module_type="pdriver", fanout = 50, neg_polarity = True)
|
d = factory.create(module_type="pdriver", fanout = 50, inverting = True)
|
||||||
self.local_check(d)
|
self.local_check(d)
|
||||||
|
|
||||||
e = factory.create(module_type="pdriver", fanout = 64)
|
e = factory.create(module_type="pdriver", fanout = 64)
|
||||||
self.local_check(e)
|
self.local_check(e)
|
||||||
|
|
||||||
f = factory.create(module_type="pdriver", fanout = 64, neg_polarity = True)
|
f = factory.create(module_type="pdriver", fanout = 64, inverting = True)
|
||||||
self.local_check(f)
|
self.local_check(f)
|
||||||
|
|
||||||
globals.end_openram()
|
globals.end_openram()
|
||||||
|
|||||||
Executable
+35
@@ -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())
|
||||||
+1
-1
@@ -25,7 +25,7 @@ class wordline_driver_test(openram_test):
|
|||||||
|
|
||||||
# check wordline driver for single port
|
# check wordline driver for single port
|
||||||
debug.info(2, "Checking driver")
|
debug.info(2, "Checking driver")
|
||||||
tx = factory.create(module_type="wordline_driver", rows=8, cols=32)
|
tx = factory.create(module_type="wordline_driver")
|
||||||
self.local_check(tx)
|
self.local_check(tx)
|
||||||
|
|
||||||
globals.end_openram()
|
globals.end_openram()
|
||||||
@@ -21,11 +21,17 @@ class hierarchical_decoder_test(openram_test):
|
|||||||
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
|
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
|
||||||
globals.init_openram(config_file)
|
globals.init_openram(config_file)
|
||||||
|
|
||||||
|
# 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
|
# Checks 2x4 and 2-input NAND decoder
|
||||||
debug.info(1, "Testing 16 row sample for hierarchical_decoder")
|
debug.info(1, "Testing 16 row sample for hierarchical_decoder")
|
||||||
a = factory.create(module_type="hierarchical_decoder", num_outputs=16)
|
a = factory.create(module_type="hierarchical_decoder", num_outputs=16)
|
||||||
self.local_check(a)
|
self.local_check(a)
|
||||||
|
|
||||||
# Checks 2x4 and 2-input NAND decoder with non-power-of-two
|
# Checks 2x4 and 2-input NAND decoder with non-power-of-two
|
||||||
debug.info(1, "Testing 17 row sample for hierarchical_decoder")
|
debug.info(1, "Testing 17 row sample for hierarchical_decoder")
|
||||||
a = factory.create(module_type="hierarchical_decoder", num_outputs=17)
|
a = factory.create(module_type="hierarchical_decoder", num_outputs=17)
|
||||||
|
|||||||
@@ -21,7 +21,12 @@ class hierarchical_predecode2x4_test(openram_test):
|
|||||||
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
|
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
|
||||||
globals.init_openram(config_file)
|
globals.init_openram(config_file)
|
||||||
|
|
||||||
# checking hierarchical precode 2x4 for single port
|
# 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")
|
debug.info(1, "Testing sample for hierarchy_predecode2x4")
|
||||||
a = factory.create(module_type="hierarchical_predecode2x4")
|
a = factory.create(module_type="hierarchical_predecode2x4")
|
||||||
self.local_check(a)
|
self.local_check(a)
|
||||||
|
|||||||
@@ -21,7 +21,12 @@ class hierarchical_predecode3x8_test(openram_test):
|
|||||||
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
|
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
|
||||||
globals.init_openram(config_file)
|
globals.init_openram(config_file)
|
||||||
|
|
||||||
# checking hierarchical precode 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")
|
debug.info(1, "Testing sample for hierarchy_predecode3x8")
|
||||||
a = factory.create(module_type="hierarchical_predecode3x8")
|
a = factory.create(module_type="hierarchical_predecode3x8")
|
||||||
self.local_check(a)
|
self.local_check(a)
|
||||||
|
|||||||
+42
@@ -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())
|
||||||
+2
-3
@@ -15,9 +15,8 @@ from globals import OPTS
|
|||||||
from sram_factory import factory
|
from sram_factory import factory
|
||||||
import debug
|
import debug
|
||||||
|
|
||||||
#@unittest.skip("SKIPPING 04_driver_test")
|
|
||||||
|
|
||||||
class wordline_driver_pbitcell_test(openram_test):
|
class wordline_driver_array_pbitcell_test(openram_test):
|
||||||
|
|
||||||
def runTest(self):
|
def runTest(self):
|
||||||
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
|
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
|
||||||
@@ -31,7 +30,7 @@ class wordline_driver_pbitcell_test(openram_test):
|
|||||||
|
|
||||||
factory.reset()
|
factory.reset()
|
||||||
debug.info(2, "Checking driver (multi-port case)")
|
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)
|
self.local_check(tx)
|
||||||
|
|
||||||
globals.end_openram()
|
globals.end_openram()
|
||||||
+37
@@ -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())
|
||||||
@@ -20,10 +20,10 @@ class port_data_1rw_1r_test(openram_test):
|
|||||||
globals.init_openram(config_file)
|
globals.init_openram(config_file)
|
||||||
from sram_config import sram_config
|
from sram_config import sram_config
|
||||||
|
|
||||||
OPTS.bitcell = "bitcell_1w_1r"
|
OPTS.bitcell = "bitcell_1rw_1r"
|
||||||
OPTS.num_rw_ports = 0
|
OPTS.num_rw_ports = 1
|
||||||
OPTS.num_r_ports = 1
|
OPTS.num_r_ports = 1
|
||||||
OPTS.num_w_ports = 1
|
OPTS.num_w_ports = 0
|
||||||
|
|
||||||
c = sram_config(word_size=4,
|
c = sram_config(word_size=4,
|
||||||
num_words=16)
|
num_words=16)
|
||||||
|
|||||||
@@ -25,28 +25,28 @@ class single_bank_test(openram_test):
|
|||||||
c = sram_config(word_size=4,
|
c = sram_config(word_size=4,
|
||||||
num_words=16)
|
num_words=16)
|
||||||
|
|
||||||
# c.words_per_row=1
|
c.words_per_row=1
|
||||||
# factory.reset()
|
factory.reset()
|
||||||
# c.recompute_sizes()
|
c.recompute_sizes()
|
||||||
# debug.info(1, "No column mux")
|
debug.info(1, "No column mux")
|
||||||
# a = factory.create("bank", sram_config=c)
|
a = factory.create("bank", sram_config=c)
|
||||||
# self.local_check(a)
|
self.local_check(a)
|
||||||
|
|
||||||
# c.num_words=32
|
c.num_words=32
|
||||||
# c.words_per_row=2
|
c.words_per_row=2
|
||||||
# factory.reset()
|
factory.reset()
|
||||||
# c.recompute_sizes()
|
c.recompute_sizes()
|
||||||
# debug.info(1, "Two way column mux")
|
debug.info(1, "Two way column mux")
|
||||||
# a = factory.create("bank", sram_config=c)
|
a = factory.create("bank", sram_config=c)
|
||||||
# self.local_check(a)
|
self.local_check(a)
|
||||||
|
|
||||||
# c.num_words=64
|
c.num_words=64
|
||||||
# c.words_per_row=4
|
c.words_per_row=4
|
||||||
# factory.reset()
|
factory.reset()
|
||||||
# c.recompute_sizes()
|
c.recompute_sizes()
|
||||||
# debug.info(1, "Four way column mux")
|
debug.info(1, "Four way column mux")
|
||||||
# a = factory.create("bank", sram_config=c)
|
a = factory.create("bank", sram_config=c)
|
||||||
# self.local_check(a)
|
self.local_check(a)
|
||||||
|
|
||||||
c.word_size=2
|
c.word_size=2
|
||||||
c.num_words=128
|
c.num_words=128
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ class openram_test(unittest.TestCase):
|
|||||||
#debug.info(0,"Archiving failed files to {}.zip".format(zip_file))
|
#debug.info(0,"Archiving failed files to {}.zip".format(zip_file))
|
||||||
#shutil.make_archive(zip_file, 'zip', OPTS.openram_temp)
|
#shutil.make_archive(zip_file, 'zip', OPTS.openram_temp)
|
||||||
debug.warning("DRC failed but LVS passed: {}".format(a.name))
|
debug.warning("DRC failed but LVS passed: {}".format(a.name))
|
||||||
|
#self.fail("DRC failed but LVS passed: {}".format(a.name))
|
||||||
elif drc_result != 0:
|
elif drc_result != 0:
|
||||||
#zip_file = "/tmp/{0}_{1}".format(a.name,os.getpid())
|
#zip_file = "/tmp/{0}_{1}".format(a.name,os.getpid())
|
||||||
#debug.info(0,"Archiving failed files to {}.zip".format(zip_file))
|
#debug.info(0,"Archiving failed files to {}.zip".format(zip_file))
|
||||||
|
|||||||
Reference in New Issue
Block a user