mirror of https://github.com/VLSIDA/OpenRAM.git
Merge branch 'dev' into automated_analytical_model
This commit is contained in:
commit
77d7e3b1cf
|
|
@ -6,8 +6,11 @@
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
#
|
#
|
||||||
|
|
||||||
class _cell:
|
class cell:
|
||||||
def __init__(self, port_order, port_types, port_map=None, hard_cell=True, boundary_layer="boundary"):
|
def __init__(self, port_order, port_types, port_map=None, body_bias=None, hard_cell=True, boundary_layer="boundary"):
|
||||||
|
|
||||||
|
# Some cells may have body bias (well taps) exposed as ports
|
||||||
|
self._body_bias = body_bias
|
||||||
|
|
||||||
# Specifies if this is a hard (i.e. GDS) cell
|
# Specifies if this is a hard (i.e. GDS) cell
|
||||||
self._hard_cell = hard_cell
|
self._hard_cell = hard_cell
|
||||||
|
|
@ -20,7 +23,8 @@ class _cell:
|
||||||
# by default it is 1:1
|
# by default it is 1:1
|
||||||
if not port_map:
|
if not port_map:
|
||||||
self._port_map = {x: x for x in port_order}
|
self._port_map = {x: x for x in port_order}
|
||||||
|
else:
|
||||||
|
self._port_map = port_map
|
||||||
# Update mapping of names
|
# Update mapping of names
|
||||||
self._original_port_order = port_order
|
self._original_port_order = port_order
|
||||||
self._port_order = port_order
|
self._port_order = port_order
|
||||||
|
|
@ -47,14 +51,24 @@ class _cell:
|
||||||
return self._port_order
|
return self._port_order
|
||||||
|
|
||||||
@port_order.setter
|
@port_order.setter
|
||||||
def port_order(self, x):
|
def port_order(self, port_order):
|
||||||
self._port_order = x
|
# If we are going to redefine more ports (i.e. well biases) don't init stuff
|
||||||
# Update ordered name list in the new order
|
old_port_len = len(self._port_order)
|
||||||
self._port_names = [self._port_map[x] for x in self._port_order]
|
if old_port_len == len(port_order):
|
||||||
# Update ordered type list in the new order
|
self._port_order = port_order
|
||||||
self._port_types = [self._port_types_map[x] for x in self._port_order]
|
# Update ordered name list in the new order
|
||||||
# Update the index array
|
self._port_names = [self._port_map[x] for x in self._port_order]
|
||||||
self._port_indices = [self._port_order.index(x) for x in self._original_port_order]
|
# Update ordered type list in the new order
|
||||||
|
self._port_types = [self._port_types_map[x] for x in self._port_order]
|
||||||
|
# Update the index array
|
||||||
|
self._port_indices = [self._port_order.index(x) for x in self._original_port_order]
|
||||||
|
else:
|
||||||
|
# Do the default constructor again except for types stuff which hasn't been set yet
|
||||||
|
self._port_order = port_order
|
||||||
|
self._original_port_order = self._port_order
|
||||||
|
self._port_map = {x: x for x in self._port_order}
|
||||||
|
self._port_indices = [self._port_order.index(x) for x in self._original_port_order]
|
||||||
|
self._port_names = [self._port_map[x] for x in self._port_order]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def port_indices(self):
|
def port_indices(self):
|
||||||
|
|
@ -65,15 +79,36 @@ class _cell:
|
||||||
return self._port_map
|
return self._port_map
|
||||||
|
|
||||||
@port_map.setter
|
@port_map.setter
|
||||||
def port_map(self, x):
|
def port_map(self, port_map):
|
||||||
self._port_map = x
|
self._port_map = port_map
|
||||||
# Update ordered name list to use the new names
|
# Update ordered name list to use the new names
|
||||||
self._port_names = [self.port_map[x] for x in self._port_order]
|
self._port_names = [self._port_map[x] for x in self._port_order]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def body_bias(self):
|
||||||
|
return self._body_bias
|
||||||
|
|
||||||
|
@body_bias.setter
|
||||||
|
def body_bias(self, body_bias):
|
||||||
|
# It is assumed it is [nwell, pwell]
|
||||||
|
self._body_bias = body_bias
|
||||||
|
self._port_map['vnb'] = body_bias[0]
|
||||||
|
self._port_types['vnb'] = "POWER"
|
||||||
|
self._port_map['vpb'] = body_bias[1]
|
||||||
|
self._port_types['vpb'] = "GROUND"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def port_types(self):
|
def port_types(self):
|
||||||
return self._port_types
|
return self._port_types
|
||||||
|
|
||||||
|
@port_types.setter
|
||||||
|
def port_types(self, port_types):
|
||||||
|
self._port_types = port_types
|
||||||
|
# Specifies the port directions
|
||||||
|
self._port_types_map = {x: y for (x, y) in zip(self._port_order, self._port_types)}
|
||||||
|
# Update ordered type list
|
||||||
|
self._port_types = [self._port_types_map[x] for x in self._port_order]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def boundary_layer(self):
|
def boundary_layer(self):
|
||||||
return self._boundary_layer
|
return self._boundary_layer
|
||||||
|
|
@ -108,7 +143,7 @@ class _pgate:
|
||||||
self.add_implants = add_implants
|
self.add_implants = add_implants
|
||||||
|
|
||||||
|
|
||||||
class _bitcell(_cell):
|
class bitcell(cell):
|
||||||
def __init__(self, port_order, port_types, port_map=None, storage_nets=["Q", "Q_bar"], mirror=None, end_caps=False):
|
def __init__(self, port_order, port_types, port_map=None, storage_nets=["Q", "Q_bar"], mirror=None, end_caps=False):
|
||||||
super().__init__(port_order, port_types, port_map)
|
super().__init__(port_order, port_types, port_map)
|
||||||
|
|
||||||
|
|
@ -147,37 +182,43 @@ class cell_properties():
|
||||||
|
|
||||||
self._pgate = _pgate(add_implants=False)
|
self._pgate = _pgate(add_implants=False)
|
||||||
|
|
||||||
self._inv_dec = _cell(["A", "Z", "vdd", "gnd"],
|
self._inv_dec = cell(["A", "Z", "vdd", "gnd"],
|
||||||
["INPUT", "OUTPUT", "POWER", "GROUND"])
|
["INPUT", "OUTPUT", "POWER", "GROUND"])
|
||||||
|
|
||||||
self._nand2_dec = _cell(["A", "B", "Z", "vdd", "gnd"],
|
self._nand2_dec = cell(["A", "B", "Z", "vdd", "gnd"],
|
||||||
["INPUT", "INPUT", "OUTPUT", "POWER", "GROUND"])
|
["INPUT", "INPUT", "OUTPUT", "POWER", "GROUND"])
|
||||||
|
|
||||||
self._nand3_dec = _cell(["A", "B", "C", "Z", "vdd", "gnd"],
|
self._nand3_dec = cell(["A", "B", "C", "Z", "vdd", "gnd"],
|
||||||
["INPUT", "INPUT", "INPUT", "OUTPUT", "POWER", "GROUND"])
|
["INPUT", "INPUT", "INPUT", "OUTPUT", "POWER", "GROUND"])
|
||||||
|
|
||||||
self._nand4_dec = _cell(["A", "B", "C", "D", "Z", "vdd", "gnd"],
|
self._nand4_dec = cell(["A", "B", "C", "D", "Z", "vdd", "gnd"],
|
||||||
["INPUT", "INPUT", "INPUT", "INPUT", "OUTPUT", "POWER", "GROUND"])
|
["INPUT", "INPUT", "INPUT", "INPUT", "OUTPUT", "POWER", "GROUND"])
|
||||||
|
|
||||||
self._dff = _cell(["D", "Q", "clk", "vdd", "gnd"],
|
self._dff = cell(["D", "Q", "clk", "vdd", "gnd"],
|
||||||
["INPUT", "OUTPUT", "INPUT", "POWER", "GROUND"])
|
["INPUT", "OUTPUT", "INPUT", "POWER", "GROUND"])
|
||||||
|
|
||||||
self._write_driver = _cell(['din', 'bl', 'br', 'en', 'vdd', 'gnd'],
|
self._write_driver = cell(['din', 'bl', 'br', 'en', 'vdd', 'gnd'],
|
||||||
["INPUT", "OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"])
|
["INPUT", "OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"])
|
||||||
|
|
||||||
self._sense_amp = _cell(['bl', 'br', 'dout', 'en', 'vdd', 'gnd'],
|
self._sense_amp = cell(['bl', 'br', 'dout', 'en', 'vdd', 'gnd'],
|
||||||
["INPUT", "INPUT", "OUTPUT", "INPUT", "POWER", "GROUND"])
|
["INPUT", "INPUT", "OUTPUT", "INPUT", "POWER", "GROUND"])
|
||||||
|
|
||||||
self._bitcell_1port = _bitcell(["bl", "br", "wl", "vdd", "gnd"],
|
self._bitcell_1port = bitcell(["bl", "br", "wl", "vdd", "gnd"],
|
||||||
["OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"])
|
["OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"])
|
||||||
|
|
||||||
self._bitcell_2port = _bitcell(["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"],
|
self._bitcell_2port = bitcell(["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"],
|
||||||
["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT", "INPUT", "INPUT", "POWER", "GROUND"])
|
["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT", "INPUT", "INPUT", "POWER", "GROUND"])
|
||||||
|
|
||||||
self._col_cap_2port = _bitcell(["bl0", "br0", "bl1", "br1", "vdd"],
|
self._col_cap_1port = bitcell(["bl", "br", "vdd"],
|
||||||
|
["OUTPUT", "OUTPUT", "POWER"])
|
||||||
|
|
||||||
|
self._row_cap_1port = bitcell(["wl", "gnd"],
|
||||||
|
["INPUT", "POWER", "GROUND"])
|
||||||
|
|
||||||
|
self._col_cap_2port = bitcell(["bl0", "br0", "bl1", "br1", "vdd"],
|
||||||
["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT", "POWER"])
|
["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT", "POWER"])
|
||||||
|
|
||||||
self._row_cap_2port = _bitcell(["wl0", "wl1", "gnd"],
|
self._row_cap_2port = bitcell(["wl0", "wl1", "gnd"],
|
||||||
["INPUT", "INPUT", "POWER", "GROUND"])
|
["INPUT", "INPUT", "POWER", "GROUND"])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -224,6 +265,14 @@ class cell_properties():
|
||||||
def bitcell_2port(self):
|
def bitcell_2port(self):
|
||||||
return self._bitcell_2port
|
return self._bitcell_2port
|
||||||
|
|
||||||
|
@property
|
||||||
|
def col_cap_1port(self):
|
||||||
|
return self._col_cap_1port
|
||||||
|
|
||||||
|
@property
|
||||||
|
def row_cap_1port(self):
|
||||||
|
return self._row_cap_1port
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def col_cap_2port(self):
|
def col_cap_2port(self):
|
||||||
return self._col_cap_2port
|
return self._col_cap_2port
|
||||||
|
|
|
||||||
|
|
@ -167,8 +167,8 @@ class design(hierarchy_design):
|
||||||
|
|
||||||
from tech import layer_indices
|
from tech import layer_indices
|
||||||
import tech
|
import tech
|
||||||
for layer in layer_indices:
|
for layer_id in layer_indices:
|
||||||
key = "{}_stack".format(layer)
|
key = "{}_stack".format(layer_id)
|
||||||
|
|
||||||
# Set the stack as a local helper
|
# Set the stack as a local helper
|
||||||
try:
|
try:
|
||||||
|
|
@ -177,24 +177,24 @@ class design(hierarchy_design):
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Skip computing the pitch for active
|
# Skip computing the pitch for non-routing layers
|
||||||
if layer == "active":
|
if layer_id in ["active", "nwell"]:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Add the pitch
|
# Add the pitch
|
||||||
setattr(design,
|
setattr(design,
|
||||||
"{}_pitch".format(layer),
|
"{}_pitch".format(layer_id),
|
||||||
design.compute_pitch(layer, True))
|
design.compute_pitch(layer_id, True))
|
||||||
|
|
||||||
# Add the non-preferrd pitch (which has vias in the "wrong" way)
|
# Add the non-preferrd pitch (which has vias in the "wrong" way)
|
||||||
setattr(design,
|
setattr(design,
|
||||||
"{}_nonpref_pitch".format(layer),
|
"{}_nonpref_pitch".format(layer_id),
|
||||||
design.compute_pitch(layer, False))
|
design.compute_pitch(layer_id, False))
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
from tech import preferred_directions
|
from tech import preferred_directions
|
||||||
print(preferred_directions)
|
print(preferred_directions)
|
||||||
from tech import layer, layer_indices
|
from tech import layer_indices
|
||||||
for name in layer_indices:
|
for name in layer_indices:
|
||||||
if name == "active":
|
if name == "active":
|
||||||
continue
|
continue
|
||||||
|
|
|
||||||
|
|
@ -118,6 +118,14 @@ class timing_graph():
|
||||||
|
|
||||||
return delays
|
return delays
|
||||||
|
|
||||||
|
def get_edge_mods(self, path):
|
||||||
|
"""Return all edge mods associated with path"""
|
||||||
|
|
||||||
|
if len(path) == 0:
|
||||||
|
return []
|
||||||
|
|
||||||
|
return [self.edge_mods[(path[i], path[i+1])] for i in range(len(path)-1)]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
""" override print function output """
|
""" override print function output """
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,7 @@
|
||||||
import hierarchy_layout
|
import hierarchy_layout
|
||||||
import hierarchy_spice
|
import hierarchy_spice
|
||||||
import debug
|
import debug
|
||||||
import os
|
|
||||||
from globals import OPTS
|
from globals import OPTS
|
||||||
import tech
|
|
||||||
|
|
||||||
|
|
||||||
class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
|
class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
|
||||||
|
|
@ -72,10 +70,6 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
|
||||||
"LVS failed for {0} with {1} errors(s)".format(self.cell_name,
|
"LVS failed for {0} with {1} errors(s)".format(self.cell_name,
|
||||||
self.lvs_errors))
|
self.lvs_errors))
|
||||||
|
|
||||||
if not OPTS.keep_temp:
|
|
||||||
os.remove(tempspice)
|
|
||||||
os.remove(tempgds)
|
|
||||||
|
|
||||||
def DRC(self, final_verification=False):
|
def DRC(self, final_verification=False):
|
||||||
"""Checks DRC for a module"""
|
"""Checks DRC for a module"""
|
||||||
import verify
|
import verify
|
||||||
|
|
@ -96,9 +90,6 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
|
||||||
"DRC failed for {0} with {1} error(s)".format(self.cell_name,
|
"DRC failed for {0} with {1} error(s)".format(self.cell_name,
|
||||||
num_errors))
|
num_errors))
|
||||||
|
|
||||||
if not OPTS.keep_temp:
|
|
||||||
os.remove(tempgds)
|
|
||||||
|
|
||||||
def LVS(self, final_verification=False):
|
def LVS(self, final_verification=False):
|
||||||
"""Checks LVS for a module"""
|
"""Checks LVS for a module"""
|
||||||
import verify
|
import verify
|
||||||
|
|
@ -118,9 +109,6 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
|
||||||
debug.check(num_errors == 0,
|
debug.check(num_errors == 0,
|
||||||
"LVS failed for {0} with {1} error(s)".format(self.cell_name,
|
"LVS failed for {0} with {1} error(s)".format(self.cell_name,
|
||||||
num_errors))
|
num_errors))
|
||||||
if not OPTS.keep_temp:
|
|
||||||
os.remove(tempspice)
|
|
||||||
os.remove(tempgds)
|
|
||||||
|
|
||||||
def init_graph_params(self):
|
def init_graph_params(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ class spice():
|
||||||
else:
|
else:
|
||||||
self.lvs_file = self.sp_file
|
self.lvs_file = self.sp_file
|
||||||
|
|
||||||
self.valid_signal_types = ["INOUT", "INPUT", "OUTPUT", "POWER", "GROUND"]
|
self.valid_signal_types = ["INOUT", "INPUT", "OUTPUT", "BIAS", "POWER", "GROUND"]
|
||||||
# Holds subckts/mods for this module
|
# Holds subckts/mods for this module
|
||||||
self.mods = []
|
self.mods = []
|
||||||
# Holds the pins for this module (in order)
|
# Holds the pins for this module (in order)
|
||||||
|
|
|
||||||
|
|
@ -22,41 +22,14 @@ class bitcell_1port(bitcell_base.bitcell_base):
|
||||||
super().__init__(name, prop=props.bitcell_1port)
|
super().__init__(name, prop=props.bitcell_1port)
|
||||||
debug.info(2, "Create bitcell")
|
debug.info(2, "Create bitcell")
|
||||||
|
|
||||||
def get_all_wl_names(self):
|
|
||||||
""" Creates a list of all wordline pin names """
|
|
||||||
row_pins = ["wl"]
|
|
||||||
return row_pins
|
|
||||||
|
|
||||||
def get_all_bitline_names(self):
|
|
||||||
""" Creates a list of all bitline pin names (both bl and br) """
|
|
||||||
return ["bl", "br"]
|
|
||||||
|
|
||||||
def get_all_bl_names(self):
|
|
||||||
""" Creates a list of all bl pins names """
|
|
||||||
return ["bl"]
|
|
||||||
|
|
||||||
def get_all_br_names(self):
|
|
||||||
""" Creates a list of all br pins names """
|
|
||||||
return ["br"]
|
|
||||||
|
|
||||||
def get_bl_name(self, port=0):
|
|
||||||
"""Get bl name"""
|
|
||||||
debug.check(port == 0, "One port for bitcell only.")
|
|
||||||
return "bl"
|
|
||||||
|
|
||||||
def get_br_name(self, port=0):
|
|
||||||
"""Get bl name"""
|
|
||||||
debug.check(port == 0, "One port for bitcell only.")
|
|
||||||
return "br"
|
|
||||||
|
|
||||||
def get_wl_name(self, port=0):
|
|
||||||
"""Get wl name"""
|
|
||||||
debug.check(port == 0, "One port for bitcell only.")
|
|
||||||
return "wl"
|
|
||||||
|
|
||||||
def build_graph(self, graph, inst_name, port_nets):
|
def build_graph(self, graph, inst_name, port_nets):
|
||||||
"""
|
"""
|
||||||
Adds edges based on inputs/outputs.
|
Adds edges based on inputs/outputs.
|
||||||
Overrides base class function.
|
Overrides base class function.
|
||||||
"""
|
"""
|
||||||
self.add_graph_edges(graph, port_nets)
|
self.add_graph_edges(graph, port_nets)
|
||||||
|
|
||||||
|
def is_non_inverting(self):
|
||||||
|
"""Return input to output polarity for module"""
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,6 @@ class bitcell_base(design.design):
|
||||||
self.mirror = prop.mirror
|
self.mirror = prop.mirror
|
||||||
self.end_caps = prop.end_caps
|
self.end_caps = prop.end_caps
|
||||||
|
|
||||||
self.supplies = ["vdd", "gnd"]
|
|
||||||
|
|
||||||
def get_stage_effort(self, load):
|
def get_stage_effort(self, load):
|
||||||
parasitic_delay = 1
|
parasitic_delay = 1
|
||||||
# This accounts for bitline being drained
|
# This accounts for bitline being drained
|
||||||
|
|
@ -95,7 +93,7 @@ class bitcell_base(design.design):
|
||||||
labels for pex simulation.
|
labels for pex simulation.
|
||||||
"""
|
"""
|
||||||
# If we generated the bitcell, we already know where Q and Q_bar are
|
# If we generated the bitcell, we already know where Q and Q_bar are
|
||||||
if OPTS.bitcell is not "pbitcell":
|
if OPTS.bitcell != "pbitcell":
|
||||||
self.storage_net_offsets = []
|
self.storage_net_offsets = []
|
||||||
for i in range(len(self.get_storage_net_names())):
|
for i in range(len(self.get_storage_net_names())):
|
||||||
for text in self.gds.getTexts(layer["m1"]):
|
for text in self.gds.getTexts(layer["m1"]):
|
||||||
|
|
@ -105,7 +103,6 @@ class bitcell_base(design.design):
|
||||||
for i in range(len(self.storage_net_offsets)):
|
for i in range(len(self.storage_net_offsets)):
|
||||||
self.storage_net_offsets[i] = tuple([self.gds.info["units"][0] * x for x in self.storage_net_offsets[i]])
|
self.storage_net_offsets[i] = tuple([self.gds.info["units"][0] * x for x in self.storage_net_offsets[i]])
|
||||||
|
|
||||||
|
|
||||||
return(self.storage_net_offsets)
|
return(self.storage_net_offsets)
|
||||||
|
|
||||||
def get_bitline_offset(self):
|
def get_bitline_offset(self):
|
||||||
|
|
@ -150,7 +147,7 @@ class bitcell_base(design.design):
|
||||||
of the bitcell. This is useful for making sense of offsets outside
|
of the bitcell. This is useful for making sense of offsets outside
|
||||||
of the bitcell.
|
of the bitcell.
|
||||||
"""
|
"""
|
||||||
if OPTS.bitcell is not "pbitcell":
|
if OPTS.bitcell != "pbitcell":
|
||||||
normalized_storage_net_offset = self.get_storage_net_offset()
|
normalized_storage_net_offset = self.get_storage_net_offset()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
@ -160,12 +157,12 @@ class bitcell_base(design.design):
|
||||||
Q_bar_x = net_offset[1][0] - self.leftmost_xpos
|
Q_bar_x = net_offset[1][0] - self.leftmost_xpos
|
||||||
Q_bar_y = net_offset[1][1] - self.botmost_ypos
|
Q_bar_y = net_offset[1][1] - self.botmost_ypos
|
||||||
|
|
||||||
normalized_storage_net_offset = [[Q_x,Q_y],[Q_bar_x,Q_bar_y]]
|
normalized_storage_net_offset = [[Q_x, Q_y], [Q_bar_x, Q_bar_y]]
|
||||||
|
|
||||||
return normalized_storage_net_offset
|
return normalized_storage_net_offset
|
||||||
|
|
||||||
def get_normalized_bitline_offset(self):
|
def get_normalized_bitline_offset(self):
|
||||||
return self.get_bitline_offset()
|
return self.get_bitline_offset()
|
||||||
|
|
||||||
def build_graph(self, graph, inst_name, port_nets):
|
def build_graph(self, graph, inst_name, port_nets):
|
||||||
"""
|
"""
|
||||||
|
|
@ -173,3 +170,36 @@ class bitcell_base(design.design):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def get_all_wl_names(self):
|
||||||
|
""" Creates a list of all wordline pin names """
|
||||||
|
row_pins = ["wl"]
|
||||||
|
return row_pins
|
||||||
|
|
||||||
|
def get_all_bitline_names(self):
|
||||||
|
""" Creates a list of all bitline pin names (both bl and br) """
|
||||||
|
return ["bl", "br"]
|
||||||
|
|
||||||
|
def get_all_bl_names(self):
|
||||||
|
""" Creates a list of all bl pins names """
|
||||||
|
return ["bl"]
|
||||||
|
|
||||||
|
def get_all_br_names(self):
|
||||||
|
""" Creates a list of all br pins names """
|
||||||
|
return ["br"]
|
||||||
|
|
||||||
|
def get_bl_name(self, port=0):
|
||||||
|
"""Get bl name"""
|
||||||
|
debug.check(port == 0, "One port for bitcell only.")
|
||||||
|
return "bl"
|
||||||
|
|
||||||
|
def get_br_name(self, port=0):
|
||||||
|
"""Get bl name"""
|
||||||
|
debug.check(port == 0, "One port for bitcell only.")
|
||||||
|
return "br"
|
||||||
|
|
||||||
|
def get_wl_name(self, port=0):
|
||||||
|
"""Get wl name"""
|
||||||
|
debug.check(port == 0, "One port for bitcell only.")
|
||||||
|
return "wl"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,3 +48,8 @@ class replica_bitcell_1port(bitcell_base.bitcell_base):
|
||||||
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)
|
||||||
|
|
||||||
|
def is_non_inverting(self):
|
||||||
|
"""Return input to output polarity for module"""
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
@ -113,6 +113,7 @@ class delay(simulation):
|
||||||
read_measures.append(self.create_bitline_measurement_objects())
|
read_measures.append(self.create_bitline_measurement_objects())
|
||||||
read_measures.append(self.create_debug_measurement_objects())
|
read_measures.append(self.create_debug_measurement_objects())
|
||||||
read_measures.append(self.create_read_bit_measures())
|
read_measures.append(self.create_read_bit_measures())
|
||||||
|
read_measures.append(self.create_sen_and_bitline_path_measures())
|
||||||
|
|
||||||
return read_measures
|
return read_measures
|
||||||
|
|
||||||
|
|
@ -235,6 +236,98 @@ class delay(simulation):
|
||||||
|
|
||||||
return {bit_polarity.NONINVERTING: q_meas, bit_polarity.INVERTING: qbar_meas}
|
return {bit_polarity.NONINVERTING: q_meas, bit_polarity.INVERTING: qbar_meas}
|
||||||
|
|
||||||
|
def create_sen_and_bitline_path_measures(self):
|
||||||
|
"""Create measurements for the s_en and bitline paths for individual delays per stage."""
|
||||||
|
|
||||||
|
# FIXME: There should be a default_read_port variable in this case, pathing is done with this
|
||||||
|
# but is never mentioned otherwise
|
||||||
|
port = self.read_ports[0]
|
||||||
|
sen_and_port = self.sen_name+str(port)
|
||||||
|
bl_and_port = self.bl_name.format(port) # bl_name contains a '{}' for the port
|
||||||
|
# Isolate the s_en and bitline paths
|
||||||
|
debug.info(1, "self.bl_name = {}".format(self.bl_name))
|
||||||
|
debug.info(1, "self.graph.all_paths = {}".format(self.graph.all_paths))
|
||||||
|
sen_paths = [path for path in self.graph.all_paths if sen_and_port in path]
|
||||||
|
bl_paths = [path for path in self.graph.all_paths if bl_and_port in path]
|
||||||
|
debug.check(len(sen_paths)==1, 'Found {} paths which contain the s_en net.'.format(len(sen_paths)))
|
||||||
|
debug.check(len(bl_paths)==1, 'Found {} paths which contain the bitline net.'.format(len(bl_paths)))
|
||||||
|
sen_path = sen_paths[0]
|
||||||
|
bitline_path = bl_paths[0]
|
||||||
|
|
||||||
|
# Get the measures
|
||||||
|
self.sen_path_meas = self.create_delay_path_measures(sen_path)
|
||||||
|
self.bl_path_meas = self.create_delay_path_measures(bitline_path)
|
||||||
|
all_meas = self.sen_path_meas + self.bl_path_meas
|
||||||
|
|
||||||
|
# Paths could have duplicate measurements, remove them before they go to the stim file
|
||||||
|
all_meas = self.remove_duplicate_meas_names(all_meas)
|
||||||
|
# FIXME: duplicate measurements still exist in the member variables, since they have the same
|
||||||
|
# name it will still work, but this could cause an issue in the future.
|
||||||
|
|
||||||
|
return all_meas
|
||||||
|
|
||||||
|
def remove_duplicate_meas_names(self, measures):
|
||||||
|
"""Returns new list of measurements without duplicate names"""
|
||||||
|
|
||||||
|
name_set = set()
|
||||||
|
unique_measures = []
|
||||||
|
for meas in measures:
|
||||||
|
if meas.name not in name_set:
|
||||||
|
name_set.add(meas.name)
|
||||||
|
unique_measures.append(meas)
|
||||||
|
|
||||||
|
return unique_measures
|
||||||
|
|
||||||
|
def create_delay_path_measures(self, path):
|
||||||
|
"""Creates measurements for each net along given path."""
|
||||||
|
|
||||||
|
# Determine the directions (RISE/FALL) of signals
|
||||||
|
path_dirs = self.get_meas_directions(path)
|
||||||
|
|
||||||
|
# Create the measurements
|
||||||
|
path_meas = []
|
||||||
|
for i in range(len(path)-1):
|
||||||
|
cur_net, next_net = path[i], path[i+1]
|
||||||
|
cur_dir, next_dir = path_dirs[i], path_dirs[i+1]
|
||||||
|
meas_name = "delay_{}_to_{}".format(cur_net, next_net)
|
||||||
|
if i+1 != len(path)-1:
|
||||||
|
path_meas.append(delay_measure(meas_name, cur_net, next_net, cur_dir, next_dir, measure_scale=1e9, has_port=False))
|
||||||
|
else: # Make the last measurement always measure on FALL because is a read 0
|
||||||
|
path_meas.append(delay_measure(meas_name, cur_net, next_net, cur_dir, "FALL", measure_scale=1e9, has_port=False))
|
||||||
|
# Some bitcell logic is hardcoded for only read zeroes, force that here as well.
|
||||||
|
path_meas[-1].meta_str = sram_op.READ_ZERO
|
||||||
|
path_meas[-1].meta_add_delay = True
|
||||||
|
|
||||||
|
return path_meas
|
||||||
|
|
||||||
|
def get_meas_directions(self, path):
|
||||||
|
"""Returns SPICE measurements directions based on path."""
|
||||||
|
|
||||||
|
# Get the edges modules which define the path
|
||||||
|
edge_mods = self.graph.get_edge_mods(path)
|
||||||
|
|
||||||
|
# Convert to booleans based on function of modules (inverting/non-inverting)
|
||||||
|
mod_type_bools = [mod.is_non_inverting() for mod in edge_mods]
|
||||||
|
|
||||||
|
#FIXME: obtuse hack to differentiate s_en input from bitline in sense amps
|
||||||
|
if self.sen_name in path:
|
||||||
|
# Force the sense amp to be inverting for s_en->DOUT.
|
||||||
|
# bitline->DOUT is non-inverting, but the module cannot differentiate inputs.
|
||||||
|
s_en_index = path.index(self.sen_name)
|
||||||
|
mod_type_bools[s_en_index] = False
|
||||||
|
debug.info(2,'Forcing sen->dout to be inverting.')
|
||||||
|
|
||||||
|
# Use these to determine direction list assuming delay start on neg. edge of clock (FALL)
|
||||||
|
# Also, use shorthand that 'FALL' == False, 'RISE' == True to simplify logic
|
||||||
|
bool_dirs = [False]
|
||||||
|
cur_dir = False # All Paths start on FALL edge of clock
|
||||||
|
for mod_bool in mod_type_bools:
|
||||||
|
cur_dir = (cur_dir == mod_bool)
|
||||||
|
bool_dirs.append(cur_dir)
|
||||||
|
|
||||||
|
# Convert from boolean to string
|
||||||
|
return ['RISE' if dbool else 'FALL' for dbool in bool_dirs]
|
||||||
|
|
||||||
def set_load_slew(self, load, slew):
|
def set_load_slew(self, load, slew):
|
||||||
""" Set the load and slew """
|
""" Set the load and slew """
|
||||||
|
|
||||||
|
|
@ -652,6 +745,8 @@ class delay(simulation):
|
||||||
|
|
||||||
result[port].update(read_port_dict)
|
result[port].update(read_port_dict)
|
||||||
|
|
||||||
|
self.check_path_measures()
|
||||||
|
|
||||||
return (True, result)
|
return (True, result)
|
||||||
|
|
||||||
def check_sen_measure(self, port):
|
def check_sen_measure(self, port):
|
||||||
|
|
@ -752,6 +847,21 @@ class delay(simulation):
|
||||||
debug.info(1, "min_dicharge={}, min_diff={}".format(min_dicharge, min_diff))
|
debug.info(1, "min_dicharge={}, min_diff={}".format(min_dicharge, min_diff))
|
||||||
return (min_dicharge and min_diff)
|
return (min_dicharge and min_diff)
|
||||||
|
|
||||||
|
def check_path_measures(self):
|
||||||
|
"""Get and check all the delays along the sen and bitline paths"""
|
||||||
|
|
||||||
|
# Get and set measurement, no error checking done other than prints.
|
||||||
|
debug.info(2, "Checking measures in Delay Path")
|
||||||
|
value_dict = {}
|
||||||
|
for meas in self.sen_path_meas+self.bl_path_meas:
|
||||||
|
val = meas.retrieve_measure()
|
||||||
|
debug.info(2, '{}={}'.format(meas.name, val))
|
||||||
|
if type(val) != float or val > self.period/2:
|
||||||
|
debug.info(1,'Failed measurement:{}={}'.format(meas.name, val))
|
||||||
|
value_dict[meas.name] = val
|
||||||
|
|
||||||
|
return value_dict
|
||||||
|
|
||||||
def run_power_simulation(self):
|
def run_power_simulation(self):
|
||||||
"""
|
"""
|
||||||
This simulates a disabled SRAM to get the leakage power when it is off.
|
This simulates a disabled SRAM to get the leakage power when it is off.
|
||||||
|
|
@ -1020,7 +1130,7 @@ class delay(simulation):
|
||||||
|
|
||||||
# The inverse address needs to share the same bitlines as the probe address as the trimming will remove all other bitlines
|
# The inverse address needs to share the same bitlines as the probe address as the trimming will remove all other bitlines
|
||||||
# This is only an issue when there is a column mux and the address maps to different bitlines.
|
# This is only an issue when there is a column mux and the address maps to different bitlines.
|
||||||
column_addr = self.probe_address[:self.sram.col_addr_size] # do not invert this part
|
column_addr = self.get_column_addr() # do not invert this part
|
||||||
inverse_address = ""
|
inverse_address = ""
|
||||||
for c in self.probe_address[self.sram.col_addr_size:]: # invert everything else
|
for c in self.probe_address[self.sram.col_addr_size:]: # invert everything else
|
||||||
if c=="0":
|
if c=="0":
|
||||||
|
|
|
||||||
|
|
@ -412,7 +412,7 @@ class model_check(delay):
|
||||||
data_dict[self.bl_meas_name] = bl_delays[read_port]
|
data_dict[self.bl_meas_name] = bl_delays[read_port]
|
||||||
data_dict[self.power_name] = powers[read_port]
|
data_dict[self.power_name] = powers[read_port]
|
||||||
|
|
||||||
if not OPTS.use_tech_delay_chain_size: #Model is not used in this case
|
if OPTS.auto_delay_chain_sizing: #Model is not used in this case
|
||||||
wl_model_delays, sae_model_delays = self.get_model_delays(read_port)
|
wl_model_delays, sae_model_delays = self.get_model_delays(read_port)
|
||||||
debug.info(1,"Wordline model delays:\n\t {}".format(wl_model_delays))
|
debug.info(1,"Wordline model delays:\n\t {}".format(wl_model_delays))
|
||||||
debug.info(1,"SAE model delays:\n\t {}".format(sae_model_delays))
|
debug.info(1,"SAE model delays:\n\t {}".format(sae_model_delays))
|
||||||
|
|
@ -439,7 +439,7 @@ class model_check(delay):
|
||||||
name_dict[self.power_name] = self.power_meas_names
|
name_dict[self.power_name] = self.power_meas_names
|
||||||
#name_dict[self.wl_slew_name] = self.wl_slew_meas_names
|
#name_dict[self.wl_slew_name] = self.wl_slew_meas_names
|
||||||
|
|
||||||
if not OPTS.use_tech_delay_chain_size:
|
if OPTS.auto_delay_chain_sizing:
|
||||||
name_dict[self.wl_model_name] = name_dict["wl_measures"] #model uses same names as measured.
|
name_dict[self.wl_model_name] = name_dict["wl_measures"] #model uses same names as measured.
|
||||||
name_dict[self.sae_model_name] = name_dict["sae_measures"]
|
name_dict[self.sae_model_name] = name_dict["sae_measures"]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -444,6 +444,10 @@ class simulation():
|
||||||
pin_names.append("{0}".format("gnd"))
|
pin_names.append("{0}".format("gnd"))
|
||||||
return pin_names
|
return pin_names
|
||||||
|
|
||||||
|
def get_column_addr(self):
|
||||||
|
"""Returns column address of probe bit"""
|
||||||
|
return self.probe_address[:self.sram.col_addr_size]
|
||||||
|
|
||||||
def add_graph_exclusions(self):
|
def add_graph_exclusions(self):
|
||||||
"""
|
"""
|
||||||
Exclude portions of SRAM from timing graph which are not relevant
|
Exclude portions of SRAM from timing graph which are not relevant
|
||||||
|
|
@ -476,10 +480,11 @@ class simulation():
|
||||||
|
|
||||||
debug.info(2, "s_en name = {}".format(self.sen_name))
|
debug.info(2, "s_en name = {}".format(self.sen_name))
|
||||||
|
|
||||||
|
column_addr = self.get_column_addr()
|
||||||
bl_name_port, br_name_port = self.get_bl_name(self.graph.all_paths, port)
|
bl_name_port, br_name_port = self.get_bl_name(self.graph.all_paths, port)
|
||||||
port_pos = -1 - len(str(self.probe_data)) - len(str(port))
|
port_pos = -1 - len(str(column_addr)) - len(str(port))
|
||||||
|
|
||||||
if bl_name_port.endswith(str(port) + "_" + str(self.probe_data)):
|
if bl_name_port.endswith(str(port) + "_" + str(column_addr)):
|
||||||
self.bl_name = bl_name_port[:port_pos] + "{}" + bl_name_port[port_pos + len(str(port)):]
|
self.bl_name = bl_name_port[:port_pos] + "{}" + bl_name_port[port_pos + len(str(port)):]
|
||||||
elif not bl_name_port[port_pos].isdigit(): # single port SRAM case, bl will not be numbered eg bl_0
|
elif not bl_name_port[port_pos].isdigit(): # single port SRAM case, bl will not be numbered eg bl_0
|
||||||
self.bl_name = bl_name_port
|
self.bl_name = bl_name_port
|
||||||
|
|
@ -487,7 +492,7 @@ class simulation():
|
||||||
self.bl_name = bl_name_port
|
self.bl_name = bl_name_port
|
||||||
debug.warning("Error occurred while determining bitline names. Can cause faults in simulation.")
|
debug.warning("Error occurred while determining bitline names. Can cause faults in simulation.")
|
||||||
|
|
||||||
if br_name_port.endswith(str(port) + "_" + str(self.probe_data)):
|
if br_name_port.endswith(str(port) + "_" + str(column_addr)):
|
||||||
self.br_name = br_name_port[:port_pos] + "{}" + br_name_port[port_pos + len(str(port)):]
|
self.br_name = br_name_port[:port_pos] + "{}" + br_name_port[port_pos + len(str(port)):]
|
||||||
elif not br_name_port[port_pos].isdigit(): # single port SRAM case, bl will not be numbered eg bl_0
|
elif not br_name_port[port_pos].isdigit(): # single port SRAM case, bl will not be numbered eg bl_0
|
||||||
self.br_name = br_name_port
|
self.br_name = br_name_port
|
||||||
|
|
|
||||||
|
|
@ -73,3 +73,9 @@ class sense_amp(design.design):
|
||||||
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)
|
||||||
|
|
||||||
|
def is_non_inverting(self):
|
||||||
|
"""Return input to output polarity for module"""
|
||||||
|
|
||||||
|
#FIXME: This only applied to bl/br -> dout and not s_en->dout
|
||||||
|
return True
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,8 @@ class Gds2reader:
|
||||||
recordLength = struct.unpack(">h",recordLengthAscii) #gives us a tuple with a short int inside
|
recordLength = struct.unpack(">h",recordLengthAscii) #gives us a tuple with a short int inside
|
||||||
offset_int = int(recordLength[0]) # extract length
|
offset_int = int(recordLength[0]) # extract length
|
||||||
offset += offset_int # count offset
|
offset += offset_int # count offset
|
||||||
#print(offset) #print out the record numbers for de-bugging
|
if(self.debugToTerminal==1):
|
||||||
|
print("Offset: " + str(offset)) #print out the record numbers for de-bugging
|
||||||
record = self.fileHandle.read(recordLength[0]-2) #read the rest of it (first 2 bytes were already read)
|
record = self.fileHandle.read(recordLength[0]-2) #read the rest of it (first 2 bytes were already read)
|
||||||
return record
|
return record
|
||||||
|
|
||||||
|
|
@ -176,7 +177,7 @@ class Gds2reader:
|
||||||
def readBoundary(self):
|
def readBoundary(self):
|
||||||
##reads in a boundary type structure = a filled polygon
|
##reads in a boundary type structure = a filled polygon
|
||||||
if(self.debugToTerminal==1):
|
if(self.debugToTerminal==1):
|
||||||
print("\t\t\tBeginBoundary")
|
print("\t\tBeginBoundary")
|
||||||
thisBoundary=GdsBoundary()
|
thisBoundary=GdsBoundary()
|
||||||
while 1:
|
while 1:
|
||||||
record = self.readNextRecord()
|
record = self.readNextRecord()
|
||||||
|
|
@ -214,13 +215,13 @@ class Gds2reader:
|
||||||
print("\t\t\tXY Point: "+str(x)+","+str(y))
|
print("\t\t\tXY Point: "+str(x)+","+str(y))
|
||||||
elif(idBits==b'\x11\x00'): #End Of Element
|
elif(idBits==b'\x11\x00'): #End Of Element
|
||||||
if(self.debugToTerminal==1):
|
if(self.debugToTerminal==1):
|
||||||
print("\t\t\tEndBoundary")
|
print("\t\tEndBoundary")
|
||||||
break;
|
break;
|
||||||
return thisBoundary
|
return thisBoundary
|
||||||
|
|
||||||
def readPath(self): #reads in a path structure
|
def readPath(self): #reads in a path structure
|
||||||
if(self.debugToTerminal==1):
|
if(self.debugToTerminal==1):
|
||||||
print("\t\t\tBeginPath")
|
print("\t\tBeginPath")
|
||||||
|
|
||||||
thisPath=GdsPath()
|
thisPath=GdsPath()
|
||||||
while 1:
|
while 1:
|
||||||
|
|
@ -274,7 +275,7 @@ class Gds2reader:
|
||||||
print("\t\t\tXY Point: "+str(x)+","+str(y))
|
print("\t\t\tXY Point: "+str(x)+","+str(y))
|
||||||
elif(idBits==b'\x11\x00'): #End Of Element
|
elif(idBits==b'\x11\x00'): #End Of Element
|
||||||
if(self.debugToTerminal==1):
|
if(self.debugToTerminal==1):
|
||||||
print("\t\t\tEndPath")
|
print("\t\tEndPath")
|
||||||
break;
|
break;
|
||||||
return thisPath
|
return thisPath
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -600,7 +600,7 @@ class VlsiLayout:
|
||||||
|
|
||||||
shapes = self.getAllShapes(lpp)
|
shapes = self.getAllShapes(lpp)
|
||||||
if len(shapes) != 1:
|
if len(shapes) != 1:
|
||||||
debug.warning("More than one boundary found in cell: {}".format(structure))
|
debug.warning("More than one or no boundaries found in cell: {}".format(structure))
|
||||||
debug.check(len(shapes) != 0,
|
debug.check(len(shapes) != 0,
|
||||||
"Error: "+str(structure)+".cell_size information not found yet")
|
"Error: "+str(structure)+".cell_size information not found yet")
|
||||||
max_boundary = None
|
max_boundary = None
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import re
|
||||||
import copy
|
import copy
|
||||||
import importlib
|
import importlib
|
||||||
|
|
||||||
VERSION = "1.1.6"
|
VERSION = "1.1.7"
|
||||||
NAME = "OpenRAM v{}".format(VERSION)
|
NAME = "OpenRAM v{}".format(VERSION)
|
||||||
USAGE = "openram.py [options] <config file>\nUse -h for help.\n"
|
USAGE = "openram.py [options] <config file>\nUse -h for help.\n"
|
||||||
|
|
||||||
|
|
@ -354,6 +354,21 @@ def end_openram():
|
||||||
verify.print_pex_stats()
|
verify.print_pex_stats()
|
||||||
|
|
||||||
|
|
||||||
|
def purge_temp():
|
||||||
|
""" Remove the temp directory. """
|
||||||
|
debug.info(1,
|
||||||
|
"Purging temp directory: {}".format(OPTS.openram_temp))
|
||||||
|
# This annoyingly means you have to re-cd into
|
||||||
|
# the directory each debug iteration
|
||||||
|
# shutil.rmtree(OPTS.openram_temp, ignore_errors=True)
|
||||||
|
contents = [os.path.join(OPTS.openram_temp, i) for i in os.listdir(OPTS.openram_temp)]
|
||||||
|
for i in contents:
|
||||||
|
if os.path.isfile(i) or os.path.islink(i):
|
||||||
|
os.remove(i)
|
||||||
|
else:
|
||||||
|
shutil.rmtree(i)
|
||||||
|
|
||||||
|
|
||||||
def cleanup_paths():
|
def cleanup_paths():
|
||||||
"""
|
"""
|
||||||
We should clean up the temp directory after execution.
|
We should clean up the temp directory after execution.
|
||||||
|
|
@ -364,17 +379,7 @@ def cleanup_paths():
|
||||||
"Preserving temp directory: {}".format(OPTS.openram_temp))
|
"Preserving temp directory: {}".format(OPTS.openram_temp))
|
||||||
return
|
return
|
||||||
elif os.path.exists(OPTS.openram_temp):
|
elif os.path.exists(OPTS.openram_temp):
|
||||||
debug.info(1,
|
purge_temp()
|
||||||
"Purging temp directory: {}".format(OPTS.openram_temp))
|
|
||||||
# This annoyingly means you have to re-cd into
|
|
||||||
# the directory each debug iteration
|
|
||||||
# shutil.rmtree(OPTS.openram_temp, ignore_errors=True)
|
|
||||||
contents = [os.path.join(OPTS.openram_temp, i) for i in os.listdir(OPTS.openram_temp)]
|
|
||||||
for i in contents:
|
|
||||||
if os.path.isfile(i) or os.path.islink(i):
|
|
||||||
os.remove(i)
|
|
||||||
else:
|
|
||||||
shutil.rmtree(i)
|
|
||||||
|
|
||||||
|
|
||||||
def setup_paths():
|
def setup_paths():
|
||||||
|
|
@ -427,15 +432,17 @@ def find_exe(check_exe):
|
||||||
|
|
||||||
def init_paths():
|
def init_paths():
|
||||||
""" Create the temp and output directory if it doesn't exist """
|
""" Create the temp and output directory if it doesn't exist """
|
||||||
|
if os.path.exists(OPTS.openram_temp):
|
||||||
# make the directory if it doesn't exist
|
purge_temp()
|
||||||
try:
|
else:
|
||||||
debug.info(1,
|
# make the directory if it doesn't exist
|
||||||
"Creating temp directory: {}".format(OPTS.openram_temp))
|
try:
|
||||||
os.makedirs(OPTS.openram_temp, 0o750)
|
debug.info(1,
|
||||||
except OSError as e:
|
"Creating temp directory: {}".format(OPTS.openram_temp))
|
||||||
if e.errno == 17: # errno.EEXIST
|
os.makedirs(OPTS.openram_temp, 0o750)
|
||||||
os.chmod(OPTS.openram_temp, 0o750)
|
except OSError as e:
|
||||||
|
if e.errno == 17: # errno.EEXIST
|
||||||
|
os.chmod(OPTS.openram_temp, 0o750)
|
||||||
|
|
||||||
# Don't delete the output dir, it may have other files!
|
# Don't delete the output dir, it may have other files!
|
||||||
# make the directory if it doesn't exist
|
# make the directory if it doesn't exist
|
||||||
|
|
|
||||||
|
|
@ -729,7 +729,6 @@ class bank(design.design):
|
||||||
inst2_bl_name=inst2_bl_name,
|
inst2_bl_name=inst2_bl_name,
|
||||||
inst2_br_name=inst2_br_name)
|
inst2_br_name=inst2_br_name)
|
||||||
|
|
||||||
|
|
||||||
# Connect the replica bitlines
|
# Connect the replica bitlines
|
||||||
for (array_name, data_name) in zip(["rbl_bl_{0}_{0}".format(port), "rbl_br_{0}_{0}".format(port)], ["rbl_bl", "rbl_br"]):
|
for (array_name, data_name) in zip(["rbl_bl_{0}_{0}".format(port), "rbl_br_{0}_{0}".format(port)], ["rbl_bl", "rbl_br"]):
|
||||||
self.connect_bitline(inst1, inst2, array_name, data_name)
|
self.connect_bitline(inst1, inst2, array_name, data_name)
|
||||||
|
|
@ -876,15 +875,14 @@ class bank(design.design):
|
||||||
mid1 = driver_wl_pos.scale(0, 1) + vector(0.5 * port_address_pos + 0.5 * bitcell_array_pos, 0)
|
mid1 = driver_wl_pos.scale(0, 1) + vector(0.5 * port_address_pos + 0.5 * bitcell_array_pos, 0)
|
||||||
mid2 = mid1.scale(1, 0) + bitcell_wl_pos.scale(0, 1)
|
mid2 = mid1.scale(1, 0) + bitcell_wl_pos.scale(0, 1)
|
||||||
if driver_wl_pin.layer != bitcell_wl_pin.layer:
|
if driver_wl_pin.layer != bitcell_wl_pin.layer:
|
||||||
self.add_path(driver_wl_pin.layer, [driver_wl_pos, mid1, mid2])
|
self.add_path(driver_wl_pin.layer, [driver_wl_pos, mid1])
|
||||||
self.add_via_stack_center(from_layer=driver_wl_pin.layer,
|
self.add_via_stack_center(from_layer=driver_wl_pin.layer,
|
||||||
to_layer=bitcell_wl_pin.layer,
|
to_layer=bitcell_wl_pin.layer,
|
||||||
offset=mid2)
|
offset=mid1)
|
||||||
self.add_path(bitcell_wl_pin.layer, [mid2, bitcell_wl_pos])
|
self.add_path(bitcell_wl_pin.layer, [mid1, mid2, bitcell_wl_pos])
|
||||||
else:
|
else:
|
||||||
self.add_path(bitcell_wl_pin.layer, [driver_wl_pos, mid1, mid2, bitcell_wl_pos])
|
self.add_path(bitcell_wl_pin.layer, [driver_wl_pos, mid1, mid2, bitcell_wl_pos])
|
||||||
|
|
||||||
|
|
||||||
def route_port_address_right(self, port):
|
def route_port_address_right(self, port):
|
||||||
""" Connecting Wordline driver output to Bitcell WL connection """
|
""" Connecting Wordline driver output to Bitcell WL connection """
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,11 +35,6 @@ class bitcell_base_array(design.design):
|
||||||
self.rbl_wordline_names = [[] for port in self.all_ports]
|
self.rbl_wordline_names = [[] for port in self.all_ports]
|
||||||
self.all_rbl_wordline_names = []
|
self.all_rbl_wordline_names = []
|
||||||
|
|
||||||
# The supply pin names
|
|
||||||
self.bitcell_supplies = self.cell.supplies
|
|
||||||
# If the technology needs renaming of the supplies
|
|
||||||
self.supplies = ["vdd", "gnd"]
|
|
||||||
|
|
||||||
def create_all_bitline_names(self):
|
def create_all_bitline_names(self):
|
||||||
for col in range(self.column_size):
|
for col in range(self.column_size):
|
||||||
for port in self.all_ports:
|
for port in self.all_ports:
|
||||||
|
|
@ -63,8 +58,8 @@ class bitcell_base_array(design.design):
|
||||||
self.add_pin(bl_name, "INOUT")
|
self.add_pin(bl_name, "INOUT")
|
||||||
for wl_name in self.get_wordline_names():
|
for wl_name in self.get_wordline_names():
|
||||||
self.add_pin(wl_name, "INPUT")
|
self.add_pin(wl_name, "INPUT")
|
||||||
self.add_pin(self.supplies[0], "POWER")
|
self.add_pin("vdd", "POWER")
|
||||||
self.add_pin(self.supplies[1], "GROUND")
|
self.add_pin("gnd", "GROUND")
|
||||||
|
|
||||||
def get_bitcell_pins(self, row, col):
|
def get_bitcell_pins(self, row, col):
|
||||||
"""
|
"""
|
||||||
|
|
@ -75,8 +70,8 @@ class bitcell_base_array(design.design):
|
||||||
for port in self.all_ports:
|
for port in self.all_ports:
|
||||||
bitcell_pins.extend([x for x in self.get_bitline_names(port) if x.endswith("_{0}".format(col))])
|
bitcell_pins.extend([x for x in self.get_bitline_names(port) if x.endswith("_{0}".format(col))])
|
||||||
bitcell_pins.extend([x for x in self.all_wordline_names if x.endswith("_{0}".format(row))])
|
bitcell_pins.extend([x for x in self.all_wordline_names if x.endswith("_{0}".format(row))])
|
||||||
bitcell_pins.append(self.bitcell_supplies[0])
|
bitcell_pins.append("vdd")
|
||||||
bitcell_pins.append(self.bitcell_supplies[1])
|
bitcell_pins.append("gnd")
|
||||||
|
|
||||||
return bitcell_pins
|
return bitcell_pins
|
||||||
|
|
||||||
|
|
@ -166,8 +161,8 @@ class bitcell_base_array(design.design):
|
||||||
for row in range(self.row_size):
|
for row in range(self.row_size):
|
||||||
for col in range(self.column_size):
|
for col in range(self.column_size):
|
||||||
inst = self.cell_inst[row, col]
|
inst = self.cell_inst[row, col]
|
||||||
for (pin_name, new_name) in zip(self.bitcell_supplies, self.supplies):
|
for pin_name in ["vdd", "gnd"]:
|
||||||
self.copy_layout_pin(inst, pin_name, new_name)
|
self.copy_layout_pin(inst, pin_name)
|
||||||
|
|
||||||
def _adjust_x_offset(self, xoffset, col, col_offset):
|
def _adjust_x_offset(self, xoffset, col, col_offset):
|
||||||
tempx = xoffset
|
tempx = xoffset
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,10 @@ class col_cap_array(bitcell_base_array):
|
||||||
|
|
||||||
self.place_array("dummy_r{0}_c{1}", self.mirror)
|
self.place_array("dummy_r{0}_c{1}", self.mirror)
|
||||||
self.add_layout_pins()
|
self.add_layout_pins()
|
||||||
|
|
||||||
|
self.height = self.dummy_cell.height
|
||||||
|
self.width = self.column_size * self.cell.width
|
||||||
|
|
||||||
self.add_boundary()
|
self.add_boundary()
|
||||||
self.DRC_LVS()
|
self.DRC_LVS()
|
||||||
|
|
||||||
|
|
@ -96,7 +100,7 @@ class col_cap_array(bitcell_base_array):
|
||||||
inst = self.cell_inst[row, col]
|
inst = self.cell_inst[row, col]
|
||||||
for pin_name in ["vdd", "gnd"]:
|
for pin_name in ["vdd", "gnd"]:
|
||||||
for pin in inst.get_pins(pin_name):
|
for pin in inst.get_pins(pin_name):
|
||||||
self.add_power_pin(name=pin.name,
|
self.add_power_pin(name=pin_name,
|
||||||
loc=pin.center(),
|
loc=pin.center(),
|
||||||
start_layer=pin.layer)
|
start_layer=pin.layer)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,18 +34,9 @@ class column_mux_array(design.design):
|
||||||
self.column_offset = column_offset
|
self.column_offset = column_offset
|
||||||
|
|
||||||
self.sel_layer = layer_props.column_mux_array.select_layer
|
self.sel_layer = layer_props.column_mux_array.select_layer
|
||||||
self.sel_pitch = getattr(self, layer_props.column_mux_array.select_pitch)
|
self.sel_pitch = getattr(self, self.sel_layer + "_pitch")
|
||||||
self.bitline_layer = layer_props.column_mux_array.bitline_layer
|
self.bitline_layer = layer_props.column_mux_array.bitline_layer
|
||||||
|
|
||||||
# if OPTS.tech_name == "sky130":
|
|
||||||
# self.sel_layer = "m3"
|
|
||||||
# self.sel_pitch = self.m3_pitch
|
|
||||||
# self.bitline_layer = "m1"
|
|
||||||
# else:
|
|
||||||
# self.sel_layer = "m1"
|
|
||||||
# self.sel_pitch = self.m2_pitch
|
|
||||||
# self.bitline_layer = "m2"
|
|
||||||
|
|
||||||
if preferred_directions[self.sel_layer] == "V":
|
if preferred_directions[self.sel_layer] == "V":
|
||||||
self.via_directions = ("H", "H")
|
self.via_directions = ("H", "H")
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -268,9 +268,11 @@ class hierarchical_predecode(design.design):
|
||||||
height=via.mod.second_layer_height,
|
height=via.mod.second_layer_height,
|
||||||
width=via.mod.second_layer_width)
|
width=via.mod.second_layer_width)
|
||||||
|
|
||||||
if layer_props.hierarchical_predecode.vertical_supply:
|
# This is a hack to fix via-to-via spacing issues, but it is currently
|
||||||
below_rail = vector(self.decode_rails[out_pin].cx(), y_offset - (self.cell_height / 2))
|
# causing its own DRC problems.
|
||||||
self.add_path(self.bus_layer, [rail_pos, below_rail], width=self.li_width + self.m1_enclose_mcon * 2)
|
# if layer_props.hierarchical_predecode.vertical_supply:
|
||||||
|
# below_rail = vector(self.decode_rails[out_pin].cx(), y_offset - (self.cell_height / 2))
|
||||||
|
# self.add_path(self.bus_layer, [rail_pos, below_rail], width=self.li_width + self.m1_enclose_mcon * 2)
|
||||||
|
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -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 tech import layer, drc
|
||||||
from globals import OPTS
|
from globals import OPTS
|
||||||
from tech import layer_properties as layer_props
|
from tech import layer_properties as layer_props
|
||||||
|
|
||||||
|
|
@ -86,6 +86,13 @@ class port_address(design.design):
|
||||||
else:
|
else:
|
||||||
self.add_power_pin("vdd", rbl_vdd_pin.lc())
|
self.add_power_pin("vdd", rbl_vdd_pin.lc())
|
||||||
|
|
||||||
|
# Also connect the B input of the RBL and_dec to vdd
|
||||||
|
if OPTS.local_array_size == 0:
|
||||||
|
rbl_b_pin = self.rbl_driver_inst.get_pin("B")
|
||||||
|
rbl_loc = rbl_b_pin.center() - vector(3 * self.m1_pitch, 0)
|
||||||
|
self.add_path(rbl_b_pin.layer, [rbl_b_pin.center(), rbl_loc])
|
||||||
|
self.add_power_pin("vdd", rbl_loc, start_layer=rbl_b_pin.layer)
|
||||||
|
|
||||||
def route_pins(self):
|
def route_pins(self):
|
||||||
for row in range(self.addr_size):
|
for row in range(self.addr_size):
|
||||||
decoder_name = "addr_{}".format(row)
|
decoder_name = "addr_{}".format(row)
|
||||||
|
|
@ -157,11 +164,13 @@ class port_address(design.design):
|
||||||
b = factory.create(module_type=OPTS.bitcell)
|
b = factory.create(module_type=OPTS.bitcell)
|
||||||
|
|
||||||
if local_array_size > 0:
|
if local_array_size > 0:
|
||||||
|
# The local wordline driver will change the polarity
|
||||||
self.rbl_driver = factory.create(module_type="inv_dec",
|
self.rbl_driver = factory.create(module_type="inv_dec",
|
||||||
size=driver_size,
|
size=driver_size,
|
||||||
height=b.height)
|
height=b.height)
|
||||||
else:
|
else:
|
||||||
self.rbl_driver = factory.create(module_type="buf_dec",
|
# There is no local wordline driver
|
||||||
|
self.rbl_driver = factory.create(module_type="and2_dec",
|
||||||
size=driver_size,
|
size=driver_size,
|
||||||
height=b.height)
|
height=b.height)
|
||||||
|
|
||||||
|
|
@ -189,6 +198,8 @@ class port_address(design.design):
|
||||||
|
|
||||||
temp = []
|
temp = []
|
||||||
temp.append("wl_en")
|
temp.append("wl_en")
|
||||||
|
if OPTS.local_array_size == 0:
|
||||||
|
temp.append("vdd")
|
||||||
temp.append("rbl_wl")
|
temp.append("rbl_wl")
|
||||||
temp.append("vdd")
|
temp.append("vdd")
|
||||||
temp.append("gnd")
|
temp.append("gnd")
|
||||||
|
|
@ -221,7 +232,10 @@ class port_address(design.design):
|
||||||
wordline_driver_array_offset = vector(self.row_decoder_inst.rx(), 0)
|
wordline_driver_array_offset = vector(self.row_decoder_inst.rx(), 0)
|
||||||
self.wordline_driver_array_inst.place(wordline_driver_array_offset)
|
self.wordline_driver_array_inst.place(wordline_driver_array_offset)
|
||||||
|
|
||||||
x_offset = self.wordline_driver_array_inst.rx() - self.rbl_driver.width - self.m1_pitch
|
# The wordline driver also had an extra gap on the right, so use this offset
|
||||||
|
well_gap = 2 * drc("pwell_to_nwell") + drc("nwell_enclose_active")
|
||||||
|
x_offset = self.wordline_driver_array_inst.rx() - well_gap - self.rbl_driver.width
|
||||||
|
|
||||||
if self.port == 0:
|
if self.port == 0:
|
||||||
rbl_driver_offset = vector(x_offset,
|
rbl_driver_offset = vector(x_offset,
|
||||||
0)
|
0)
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
import debug
|
import debug
|
||||||
from bitcell_base_array import bitcell_base_array
|
from bitcell_base_array import bitcell_base_array
|
||||||
from tech import drc, spice, cell_properties
|
from tech import drc, spice
|
||||||
from vector import vector
|
from vector import vector
|
||||||
from globals import OPTS
|
from globals import OPTS
|
||||||
from sram_factory import factory
|
from sram_factory import factory
|
||||||
|
|
@ -121,18 +121,18 @@ class replica_bitcell_array(bitcell_base_array):
|
||||||
# the array.
|
# the array.
|
||||||
# These go from the top (where the bitcell array starts ) down
|
# These go from the top (where the bitcell array starts ) down
|
||||||
replica_bit = self.rbl[0] - port
|
replica_bit = self.rbl[0] - port
|
||||||
|
column_offset = self.rbl[0]
|
||||||
|
|
||||||
elif port in self.right_rbl:
|
elif port in self.right_rbl:
|
||||||
|
|
||||||
# We will always have self.rbl[0] rows of replica wordlines below
|
# We will always have self.rbl[0] rows of replica wordlines below
|
||||||
# the array.
|
# the array.
|
||||||
# These go from the bottom up
|
# These go from the bottom up
|
||||||
replica_bit = self.rbl[0] + self.row_size + port
|
replica_bit = self.rbl[0] + self.row_size + port
|
||||||
|
column_offset = self.rbl[0] + self.column_size + 1
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# If we have an odd numer on the bottom
|
|
||||||
column_offset = self.rbl[0] + 1
|
|
||||||
|
|
||||||
self.replica_columns[port] = factory.create(module_type="replica_column",
|
self.replica_columns[port] = factory.create(module_type="replica_column",
|
||||||
rows=self.row_size,
|
rows=self.row_size,
|
||||||
rbl=self.rbl,
|
rbl=self.rbl,
|
||||||
|
|
@ -313,19 +313,14 @@ class replica_bitcell_array(bitcell_base_array):
|
||||||
def create_layout(self):
|
def create_layout(self):
|
||||||
|
|
||||||
# We will need unused wordlines grounded, so we need to know their layer
|
# We will need unused wordlines grounded, so we need to know their layer
|
||||||
|
# and create a space on the left and right for the vias to connect to ground
|
||||||
pin = self.cell.get_pin(self.cell.get_all_wl_names()[0])
|
pin = self.cell.get_pin(self.cell.get_all_wl_names()[0])
|
||||||
pin_layer = pin.layer
|
pin_layer = pin.layer
|
||||||
self.unused_pitch = 1.5 * getattr(self, "{}_pitch".format(pin_layer))
|
self.unused_pitch = 1.5 * getattr(self, "{}_pitch".format(pin_layer))
|
||||||
self.unused_offset = vector(self.unused_pitch, 0)
|
self.unused_offset = vector(self.unused_pitch, 0)
|
||||||
|
|
||||||
# Add extra width on the left and right for the unused WLs
|
|
||||||
self.height = (self.row_size + self.extra_rows) * self.dummy_row.height
|
|
||||||
self.width = (self.column_size + self.extra_cols) * self.cell.width + 2 * self.unused_pitch
|
|
||||||
|
|
||||||
|
|
||||||
# This is a bitcell x bitcell offset to scale
|
# This is a bitcell x bitcell offset to scale
|
||||||
self.bitcell_offset = vector(self.cell.width, self.cell.height)
|
self.bitcell_offset = vector(self.cell.width, self.cell.height)
|
||||||
self.strap_offset = vector(0, 0)
|
|
||||||
self.col_end_offset = vector(self.cell.width, self.cell.height)
|
self.col_end_offset = vector(self.cell.width, self.cell.height)
|
||||||
self.row_end_offset = vector(self.cell.width, self.cell.height)
|
self.row_end_offset = vector(self.cell.width, self.cell.height)
|
||||||
|
|
||||||
|
|
@ -336,12 +331,16 @@ class replica_bitcell_array(bitcell_base_array):
|
||||||
|
|
||||||
self.add_end_caps()
|
self.add_end_caps()
|
||||||
|
|
||||||
|
|
||||||
# Array was at (0, 0) but move everything so it is at the lower left
|
# Array was at (0, 0) but move everything so it is at the lower left
|
||||||
# We move DOWN the number of left RBL even if we didn't add the column to this bitcell array
|
# We move DOWN the number of left RBL even if we didn't add the column to this bitcell array
|
||||||
|
# Note that this doesn't include the row/col cap
|
||||||
array_offset = self.bitcell_offset.scale(1 + len(self.left_rbl), 1 + self.rbl[0])
|
array_offset = self.bitcell_offset.scale(1 + len(self.left_rbl), 1 + self.rbl[0])
|
||||||
self.translate_all(array_offset.scale(-1, -1))
|
self.translate_all(array_offset.scale(-1, -1))
|
||||||
|
|
||||||
|
# Add extra width on the left and right for the unused WLs
|
||||||
|
self.width = self.dummy_col_insts[1].rx() + self.unused_offset.x
|
||||||
|
self.height = self.dummy_row_insts[1].uy()
|
||||||
|
|
||||||
self.add_layout_pins()
|
self.add_layout_pins()
|
||||||
|
|
||||||
self.route_unused_wordlines()
|
self.route_unused_wordlines()
|
||||||
|
|
@ -374,19 +373,11 @@ class replica_bitcell_array(bitcell_base_array):
|
||||||
|
|
||||||
# Grow from left to right, toward the array
|
# Grow from left to right, toward the array
|
||||||
for bit, port in enumerate(self.left_rbl):
|
for bit, port in enumerate(self.left_rbl):
|
||||||
if not self.cell.end_caps:
|
offset = self.bitcell_offset.scale(-len(self.left_rbl) + bit, -self.rbl[0] - 1) + self.unused_offset
|
||||||
offset = self.bitcell_offset.scale(-len(self.left_rbl) + bit, -self.rbl[0] - 1) + self.strap_offset.scale(-len(self.left_rbl) + bit, 0) + self.unused_offset
|
|
||||||
else:
|
|
||||||
offset = self.bitcell_offset.scale(-len(self.left_rbl) + bit, -self.rbl[0] - (self.col_end_offset.y/self.cell.height)) + self.strap_offset.scale(-len(self.left_rbl) + bit, 0) + self.unused_offset
|
|
||||||
|
|
||||||
self.replica_col_insts[bit].place(offset)
|
self.replica_col_insts[bit].place(offset)
|
||||||
# Grow to the right of the bitcell array, array outward
|
# Grow to the right of the bitcell array, array outward
|
||||||
for bit, port in enumerate(self.right_rbl):
|
for bit, port in enumerate(self.right_rbl):
|
||||||
if not self.cell.end_caps:
|
offset = self.bitcell_array_inst.lr() + self.bitcell_offset.scale(bit, -self.rbl[0] - 1)
|
||||||
offset = self.bitcell_array_inst.lr() + self.bitcell_offset.scale(bit, -self.rbl[0] - 1) + self.strap_offset.scale(bit, -self.rbl[0] - 1)
|
|
||||||
else:
|
|
||||||
offset = self.bitcell_array_inst.lr() + self.bitcell_offset.scale(bit, -self.rbl[0] - (self.col_end_offset.y/self.cell.height)) + self.strap_offset.scale(bit, -self.rbl[0] - 1)
|
|
||||||
|
|
||||||
self.replica_col_insts[self.rbl[0] + bit].place(offset)
|
self.replica_col_insts[self.rbl[0] + bit].place(offset)
|
||||||
|
|
||||||
# Replica dummy rows
|
# Replica dummy rows
|
||||||
|
|
@ -408,37 +399,24 @@ class replica_bitcell_array(bitcell_base_array):
|
||||||
# FIXME: These depend on the array size itself
|
# FIXME: These depend on the array size itself
|
||||||
# Far top dummy row (first row above array is NOT flipped)
|
# Far top dummy row (first row above array is NOT flipped)
|
||||||
flip_dummy = self.rbl[1] % 2
|
flip_dummy = self.rbl[1] % 2
|
||||||
if not self.cell.end_caps:
|
dummy_row_offset = self.bitcell_offset.scale(0, self.rbl[1] + flip_dummy) + self.bitcell_array_inst.ul()
|
||||||
dummy_row_offset = self.bitcell_offset.scale(0, self.rbl[1] + flip_dummy) + self.bitcell_array_inst.ul()
|
|
||||||
else:
|
|
||||||
dummy_row_offset = self.bitcell_offset.scale(0, self.rbl[1] + flip_dummy) + self.bitcell_array_inst.ul()
|
|
||||||
|
|
||||||
self.dummy_row_insts[1].place(offset=dummy_row_offset,
|
self.dummy_row_insts[1].place(offset=dummy_row_offset,
|
||||||
mirror="MX" if flip_dummy else "R0")
|
mirror="MX" if flip_dummy else "R0")
|
||||||
|
|
||||||
# FIXME: These depend on the array size itself
|
# FIXME: These depend on the array size itself
|
||||||
# Far bottom dummy row (first row below array IS flipped)
|
# Far bottom dummy row (first row below array IS flipped)
|
||||||
flip_dummy = (self.rbl[0] + 1) % 2
|
flip_dummy = (self.rbl[0] + 1) % 2
|
||||||
if not self.cell.end_caps:
|
dummy_row_offset = self.bitcell_offset.scale(0, -self.rbl[0] - 1 + flip_dummy) + self.unused_offset
|
||||||
dummy_row_offset = self.bitcell_offset.scale(0, -self.rbl[0] - 1 + flip_dummy) + self.unused_offset
|
|
||||||
else:
|
|
||||||
dummy_row_offset = self.bitcell_offset.scale(0, -self.rbl[0] - (self.col_end_offset.y/self.cell.height) + flip_dummy) + self.unused_offset
|
|
||||||
self.dummy_row_insts[0].place(offset=dummy_row_offset,
|
self.dummy_row_insts[0].place(offset=dummy_row_offset,
|
||||||
mirror="MX" if flip_dummy else "R0")
|
mirror="MX" if flip_dummy else "R0")
|
||||||
# Far left dummy col
|
# Far left dummy col
|
||||||
# Shifted down by the number of left RBLs even if we aren't adding replica column to this bitcell array
|
# Shifted down by the number of left RBLs even if we aren't adding replica column to this bitcell array
|
||||||
if not self.cell.end_caps:
|
dummy_col_offset = self.bitcell_offset.scale(-len(self.left_rbl) - 1, -self.rbl[0] - 1) + self.unused_offset
|
||||||
dummy_col_offset = self.bitcell_offset.scale(-len(self.left_rbl) - 1, -self.rbl[0] - 1) + self.unused_offset
|
|
||||||
else:
|
|
||||||
dummy_col_offset = self.bitcell_offset.scale(-(len(self.left_rbl)*(1+self.strap_offset.x/self.cell.width)) - (self.row_end_offset.x/self.cell.width), -len(self.left_rbl) - (self.col_end_offset.y/self.cell.height))
|
|
||||||
|
|
||||||
self.dummy_col_insts[0].place(offset=dummy_col_offset)
|
self.dummy_col_insts[0].place(offset=dummy_col_offset)
|
||||||
|
|
||||||
# Far right dummy col
|
# Far right dummy col
|
||||||
# Shifted down by the number of left RBLs even if we aren't adding replica column to this bitcell array
|
# Shifted down by the number of left RBLs even if we aren't adding replica column to this bitcell array
|
||||||
if not self.cell.end_caps:
|
dummy_col_offset = self.bitcell_offset.scale(len(self.right_rbl), -self.rbl[0] - 1) + self.bitcell_array_inst.lr()
|
||||||
dummy_col_offset = self.bitcell_offset.scale(len(self.right_rbl), -self.rbl[0] - 1) + self.bitcell_array_inst.lr()
|
|
||||||
else:
|
|
||||||
dummy_col_offset = self.bitcell_offset.scale(len(self.right_rbl)*(1+self.strap_offset.x/self.cell.width), -self.rbl[0] - (self.col_end_offset.y/self.cell.height)) + self.bitcell_array_inst.lr()
|
|
||||||
|
|
||||||
self.dummy_col_insts[1].place(offset=dummy_col_offset)
|
self.dummy_col_insts[1].place(offset=dummy_col_offset)
|
||||||
|
|
||||||
def add_layout_pins(self):
|
def add_layout_pins(self):
|
||||||
|
|
@ -455,6 +433,7 @@ class replica_bitcell_array(bitcell_base_array):
|
||||||
offset=pin.ll().scale(0, 1),
|
offset=pin.ll().scale(0, 1),
|
||||||
width=self.width,
|
width=self.width,
|
||||||
height=pin.height())
|
height=pin.height())
|
||||||
|
|
||||||
# Replica wordlines (go by the row instead of replica column because we may have to add a pin
|
# Replica wordlines (go by the row instead of replica column because we may have to add a pin
|
||||||
# even though the column is in another local bitcell array)
|
# even though the column is in another local bitcell array)
|
||||||
for (names, inst) in zip(self.rbl_wordline_names, self.dummy_row_replica_insts):
|
for (names, inst) in zip(self.rbl_wordline_names, self.dummy_row_replica_insts):
|
||||||
|
|
@ -549,8 +528,7 @@ class replica_bitcell_array(bitcell_base_array):
|
||||||
self.add_power_pin("gnd", right_loc, directions=("H", "H"))
|
self.add_power_pin("gnd", right_loc, directions=("H", "H"))
|
||||||
|
|
||||||
# Add a path to connect to the array
|
# Add a path to connect to the array
|
||||||
self.add_path(pin_layer, [left_loc, left_pin_loc])
|
self.add_path(pin_layer, [left_loc, right_loc], width=pin.height())
|
||||||
self.add_path(pin_layer, [right_loc, right_pin_loc])
|
|
||||||
|
|
||||||
def gen_bl_wire(self):
|
def gen_bl_wire(self):
|
||||||
if OPTS.netlist_only:
|
if OPTS.netlist_only:
|
||||||
|
|
|
||||||
|
|
@ -14,37 +14,38 @@ from tech import layer_properties as layer_props
|
||||||
class replica_column(bitcell_base_array):
|
class replica_column(bitcell_base_array):
|
||||||
"""
|
"""
|
||||||
Generate a replica bitline column for the replica array.
|
Generate a replica bitline column for the replica array.
|
||||||
Rows is the total number of rows i the main array.
|
Rows is the total number of rows in the main array.
|
||||||
rbl is a tuple with the number of left and right replica bitlines.
|
rbl is a tuple with the number of left and right replica bitlines.
|
||||||
Replica bit specifies which replica column this is (to determine where to put the
|
Replica bit specifies which replica column this is (to determine where to put the
|
||||||
replica cell relative to the bottom (including the dummy bit at 0).
|
replica cell relative to the bottom (including the dummy bit at 0).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name, rows, rbl, replica_bit, column_offset=0):
|
def __init__(self, name, rows, rbl, replica_bit, column_offset=0):
|
||||||
super().__init__(rows=sum(rbl) + rows + 2, cols=1, column_offset=column_offset, name=name)
|
# Used for pin names and properties
|
||||||
|
self.cell = factory.create(module_type=OPTS.bitcell)
|
||||||
|
# Row size is the number of rows with word lines
|
||||||
|
self.row_size = sum(rbl) + rows
|
||||||
|
# Start of regular word line rows
|
||||||
|
self.row_start = rbl[0] + 1
|
||||||
|
# End of regular word line rows
|
||||||
|
self.row_end = self.row_start + rows
|
||||||
|
if not self.cell.end_caps:
|
||||||
|
self.row_size += 2
|
||||||
|
super().__init__(rows=self.row_size, cols=1, column_offset=column_offset, name=name)
|
||||||
|
|
||||||
self.rows = rows
|
self.rows = rows
|
||||||
self.left_rbl = rbl[0]
|
self.left_rbl = rbl[0]
|
||||||
self.right_rbl = rbl[1]
|
self.right_rbl = rbl[1]
|
||||||
self.replica_bit = replica_bit
|
self.replica_bit = replica_bit
|
||||||
# left, right, regular rows plus top/bottom dummy cells
|
|
||||||
self.total_size = self.left_rbl + rows + self.right_rbl
|
|
||||||
|
|
||||||
# Used for pin names and properties
|
# Total size includes the replica rows and column cap rows
|
||||||
self.cell = factory.create(module_type=OPTS.bitcell)
|
self.total_size = self.left_rbl + rows + self.right_rbl + 2
|
||||||
|
|
||||||
# For end caps
|
|
||||||
try:
|
|
||||||
if not self.cell.end_caps:
|
|
||||||
self.total_size += 2
|
|
||||||
except AttributeError:
|
|
||||||
self.total_size += 2
|
|
||||||
|
|
||||||
self.column_offset = column_offset
|
self.column_offset = column_offset
|
||||||
|
|
||||||
debug.check(replica_bit != 0 and replica_bit != rows,
|
debug.check(replica_bit != 0 and replica_bit != self.total_size - 1,
|
||||||
"Replica bit cannot be the dummy row.")
|
"Replica bit cannot be the dummy/cap row.")
|
||||||
debug.check(replica_bit <= self.left_rbl or replica_bit >= self.total_size - self.right_rbl - 1,
|
debug.check(replica_bit < self.row_start or replica_bit >= self.row_end,
|
||||||
"Replica bit cannot be in the regular array.")
|
"Replica bit cannot be in the regular array.")
|
||||||
if layer_props.replica_column.even_rows:
|
if layer_props.replica_column.even_rows:
|
||||||
debug.check(rows % 2 == 0 and (self.left_rbl + 1) % 2 == 0,
|
debug.check(rows % 2 == 0 and (self.left_rbl + 1) % 2 == 0,
|
||||||
|
|
@ -61,18 +62,20 @@ class replica_column(bitcell_base_array):
|
||||||
self.create_instances()
|
self.create_instances()
|
||||||
|
|
||||||
def create_layout(self):
|
def create_layout(self):
|
||||||
self.height = self.total_size * self.cell.height
|
|
||||||
self.width = self.cell.width
|
|
||||||
|
|
||||||
self.place_instances()
|
self.place_instances()
|
||||||
|
|
||||||
|
self.height = self.cell_inst[-1].uy()
|
||||||
|
self.width = self.cell_inst[0].rx()
|
||||||
|
|
||||||
self.add_layout_pins()
|
self.add_layout_pins()
|
||||||
|
|
||||||
self.add_boundary()
|
self.add_boundary()
|
||||||
self.DRC_LVS()
|
self.DRC_LVS()
|
||||||
|
|
||||||
def add_pins(self):
|
def add_pins(self):
|
||||||
|
|
||||||
self.create_all_bitline_names()
|
self.create_all_bitline_names()
|
||||||
self.create_all_wordline_names(self.total_size)
|
self.create_all_wordline_names(self.row_size)
|
||||||
|
|
||||||
self.add_pin_list(self.all_bitline_names, "OUTPUT")
|
self.add_pin_list(self.all_bitline_names, "OUTPUT")
|
||||||
self.add_pin_list(self.all_wordline_names, "INPUT")
|
self.add_pin_list(self.all_wordline_names, "INPUT")
|
||||||
|
|
@ -93,32 +96,32 @@ class replica_column(bitcell_base_array):
|
||||||
self.add_mod(self.edge_cell)
|
self.add_mod(self.edge_cell)
|
||||||
|
|
||||||
def create_instances(self):
|
def create_instances(self):
|
||||||
self.cell_inst = {}
|
self.cell_inst = []
|
||||||
|
|
||||||
for row in range(self.total_size):
|
for row in range(self.total_size):
|
||||||
name="rbc_{0}".format(row)
|
name="rbc_{0}".format(row)
|
||||||
# Top/bottom cell are always dummy cells.
|
real_row = row
|
||||||
# Regular array cells are replica cells (>left_rbl and <rows-right_rbl)
|
if self.cell.end_caps:
|
||||||
# Replic bit specifies which other bit (in the full range (0,rows) to make a replica cell.
|
real_row -= 1
|
||||||
if (row > self.left_rbl and row < self.total_size - self.right_rbl - 1):
|
|
||||||
self.cell_inst[row]=self.add_inst(name=name,
|
# Regular array cells are replica cells
|
||||||
mod=self.replica_cell)
|
# Replic bit specifies which other bit (in the full range (0,total_size) to make a replica cell.
|
||||||
self.connect_inst(self.get_bitcell_pins(row, 0))
|
if (row == 0 or row == self.total_size - 1):
|
||||||
elif row==self.replica_bit:
|
self.cell_inst.append(self.add_inst(name=name,
|
||||||
self.cell_inst[row]=self.add_inst(name=name,
|
mod=self.edge_cell))
|
||||||
mod=self.replica_cell)
|
|
||||||
self.connect_inst(self.get_bitcell_pins(row, 0))
|
|
||||||
elif (row == 0 or row == self.total_size - 1):
|
|
||||||
self.cell_inst[row]=self.add_inst(name=name,
|
|
||||||
mod=self.edge_cell)
|
|
||||||
if self.cell.end_caps:
|
if self.cell.end_caps:
|
||||||
self.connect_inst(self.get_bitcell_pins_col_cap(row, 0))
|
self.connect_inst(self.get_bitcell_pins_col_cap(real_row, 0))
|
||||||
else:
|
else:
|
||||||
self.connect_inst(self.get_bitcell_pins(row, 0))
|
self.connect_inst(self.get_bitcell_pins(real_row, 0))
|
||||||
|
elif (row==self.replica_bit) or (row >= self.row_start and row < self.row_end):
|
||||||
|
self.cell_inst.append(self.add_inst(name=name,
|
||||||
|
mod=self.replica_cell))
|
||||||
|
self.connect_inst(self.get_bitcell_pins(real_row, 0))
|
||||||
else:
|
else:
|
||||||
self.cell_inst[row]=self.add_inst(name=name,
|
# Top/bottom cell are always dummy/cap cells.
|
||||||
mod=self.dummy_cell)
|
self.cell_inst.append(self.add_inst(name=name,
|
||||||
self.connect_inst(self.get_bitcell_pins(row, 0))
|
mod=self.dummy_cell))
|
||||||
|
self.connect_inst(self.get_bitcell_pins(real_row, 0))
|
||||||
|
|
||||||
def place_instances(self):
|
def place_instances(self):
|
||||||
# Flip the mirrors if we have an odd number of replica+dummy rows at the bottom
|
# Flip the mirrors if we have an odd number of replica+dummy rows at the bottom
|
||||||
|
|
@ -184,7 +187,7 @@ class replica_column(bitcell_base_array):
|
||||||
height=wl_pin.height())
|
height=wl_pin.height())
|
||||||
|
|
||||||
# Supplies are only connected in the ends
|
# Supplies are only connected in the ends
|
||||||
for (index, inst) in self.cell_inst.items():
|
for (index, inst) in enumerate(self.cell_inst):
|
||||||
for pin_name in ["vdd", "gnd"]:
|
for pin_name in ["vdd", "gnd"]:
|
||||||
if inst in [self.cell_inst[0], self.cell_inst[self.total_size - 1]]:
|
if inst in [self.cell_inst[0], self.cell_inst[self.total_size - 1]]:
|
||||||
self.copy_power_pins(inst, pin_name)
|
self.copy_power_pins(inst, pin_name)
|
||||||
|
|
@ -231,7 +234,7 @@ class replica_column(bitcell_base_array):
|
||||||
Excludes all bits except the replica cell (self.replica_bit).
|
Excludes all bits except the replica cell (self.replica_bit).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for row, cell in self.cell_inst.items():
|
for row, cell in enumerate(self.cell_inst):
|
||||||
if row != self.replica_bit:
|
if row != self.replica_bit:
|
||||||
self.graph_inst_exclude.add(cell)
|
self.graph_inst_exclude.add(cell)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,10 @@ class row_cap_array(bitcell_base_array):
|
||||||
|
|
||||||
self.place_array("dummy_r{0}_c{1}", self.mirror)
|
self.place_array("dummy_r{0}_c{1}", self.mirror)
|
||||||
self.add_layout_pins()
|
self.add_layout_pins()
|
||||||
|
|
||||||
|
self.width = max([x.rx() for x in self.insts])
|
||||||
|
self.height = max([x.uy() for x in self.insts])
|
||||||
|
|
||||||
self.add_boundary()
|
self.add_boundary()
|
||||||
self.DRC_LVS()
|
self.DRC_LVS()
|
||||||
|
|
||||||
|
|
@ -48,7 +52,7 @@ class row_cap_array(bitcell_base_array):
|
||||||
""" Create the module instances used in this design """
|
""" Create the module instances used in this design """
|
||||||
self.cell_inst = {}
|
self.cell_inst = {}
|
||||||
for col in range(self.column_size):
|
for col in range(self.column_size):
|
||||||
for row in range(1, self.row_size - 1):
|
for row in range(0, self.row_size):
|
||||||
name = "bit_r{0}_c{1}".format(row, col)
|
name = "bit_r{0}_c{1}".format(row, col)
|
||||||
self.cell_inst[row, col]=self.add_inst(name=name,
|
self.cell_inst[row, col]=self.add_inst(name=name,
|
||||||
mod=self.dummy_cell)
|
mod=self.dummy_cell)
|
||||||
|
|
@ -67,17 +71,13 @@ class row_cap_array(bitcell_base_array):
|
||||||
return bitcell_pins
|
return bitcell_pins
|
||||||
|
|
||||||
def place_array(self, name_template, row_offset=0):
|
def place_array(self, name_template, row_offset=0):
|
||||||
# We increase it by a well enclosure so the precharges don't overlap our wells
|
|
||||||
self.height = self.row_size * self.cell.height
|
|
||||||
self.width = self.column_size * self.cell.width
|
|
||||||
|
|
||||||
xoffset = 0.0
|
xoffset = 0.0
|
||||||
for col in range(self.column_size):
|
for col in range(self.column_size):
|
||||||
yoffset = self.cell.height
|
yoffset = self.cell.height
|
||||||
tempx, dir_y = self._adjust_x_offset(xoffset, col, self.column_offset)
|
tempx, dir_y = self._adjust_x_offset(xoffset, col, self.column_offset)
|
||||||
|
|
||||||
for row in range(1, self.row_size - 1):
|
for row in range(self.row_size):
|
||||||
tempy, dir_x = self._adjust_y_offset(yoffset, row, row_offset)
|
tempy, dir_x = self._adjust_y_offset(yoffset, row + 1, row_offset)
|
||||||
|
|
||||||
if dir_x and dir_y:
|
if dir_x and dir_y:
|
||||||
dir_key = "XY"
|
dir_key = "XY"
|
||||||
|
|
@ -113,7 +113,7 @@ class row_cap_array(bitcell_base_array):
|
||||||
inst = self.cell_inst[row, col]
|
inst = self.cell_inst[row, col]
|
||||||
for pin_name in ["vdd", "gnd"]:
|
for pin_name in ["vdd", "gnd"]:
|
||||||
for pin in inst.get_pins(pin_name):
|
for pin in inst.get_pins(pin_name):
|
||||||
self.add_power_pin(name=pin.name,
|
self.add_power_pin(name=pin_name,
|
||||||
loc=pin.center(),
|
loc=pin.center(),
|
||||||
start_layer=pin.layer)
|
start_layer=pin.layer)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import optparse
|
||||||
import getpass
|
import getpass
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
class options(optparse.Values):
|
class options(optparse.Values):
|
||||||
"""
|
"""
|
||||||
Class for holding all of the OpenRAM options. All
|
Class for holding all of the OpenRAM options. All
|
||||||
|
|
@ -61,7 +60,7 @@ class options(optparse.Values):
|
||||||
rbl_delay_percentage = 0.5
|
rbl_delay_percentage = 0.5
|
||||||
|
|
||||||
# Allow manual adjustment of the delay chain over automatic
|
# Allow manual adjustment of the delay chain over automatic
|
||||||
use_tech_delay_chain_size = False
|
auto_delay_chain_sizing = False
|
||||||
delay_chain_stages = 9
|
delay_chain_stages = 9
|
||||||
delay_chain_fanout_per_stage = 4
|
delay_chain_fanout_per_stage = 4
|
||||||
|
|
||||||
|
|
@ -103,7 +102,7 @@ class options(optparse.Values):
|
||||||
# Run with extracted parasitics
|
# Run with extracted parasitics
|
||||||
use_pex = False
|
use_pex = False
|
||||||
# Output config with all options
|
# Output config with all options
|
||||||
output_extended_config = False
|
output_extended_config = True
|
||||||
|
|
||||||
|
|
||||||
###################
|
###################
|
||||||
|
|
@ -153,6 +152,8 @@ class options(optparse.Values):
|
||||||
bitcell = "bitcell"
|
bitcell = "bitcell"
|
||||||
buf_dec = "pbuf"
|
buf_dec = "pbuf"
|
||||||
column_mux_array = "column_mux_array"
|
column_mux_array = "column_mux_array"
|
||||||
|
col_cap = "col_cap"
|
||||||
|
col_cap_array = "col_cap_array"
|
||||||
control_logic = "control_logic"
|
control_logic = "control_logic"
|
||||||
decoder = "hierarchical_decoder"
|
decoder = "hierarchical_decoder"
|
||||||
delay_chain = "delay_chain"
|
delay_chain = "delay_chain"
|
||||||
|
|
@ -165,6 +166,8 @@ class options(optparse.Values):
|
||||||
precharge_array = "precharge_array"
|
precharge_array = "precharge_array"
|
||||||
ptx = "ptx"
|
ptx = "ptx"
|
||||||
replica_bitline = "replica_bitline"
|
replica_bitline = "replica_bitline"
|
||||||
|
row_cap = "row_cap"
|
||||||
|
row_cap_array = "row_cap_array"
|
||||||
sense_amp_array = "sense_amp_array"
|
sense_amp_array = "sense_amp_array"
|
||||||
sense_amp = "sense_amp"
|
sense_amp = "sense_amp"
|
||||||
tri_gate_array = "tri_gate_array"
|
tri_gate_array = "tri_gate_array"
|
||||||
|
|
|
||||||
|
|
@ -146,3 +146,8 @@ class pand2(pgate.pgate):
|
||||||
offset=pin.center(),
|
offset=pin.center(),
|
||||||
width=pin.width(),
|
width=pin.width(),
|
||||||
height=pin.height())
|
height=pin.height())
|
||||||
|
|
||||||
|
def is_non_inverting(self):
|
||||||
|
"""Return input to output polarity for module"""
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
@ -337,3 +337,8 @@ class pinv(pgate.pgate):
|
||||||
Overrides base class function.
|
Overrides base class function.
|
||||||
"""
|
"""
|
||||||
self.add_graph_edges(graph, port_nets)
|
self.add_graph_edges(graph, port_nets)
|
||||||
|
|
||||||
|
def is_non_inverting(self):
|
||||||
|
"""Return input to output polarity for module"""
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
|
||||||
|
|
@ -314,3 +314,8 @@ class pnand2(pgate.pgate):
|
||||||
Overrides base class function.
|
Overrides base class function.
|
||||||
"""
|
"""
|
||||||
self.add_graph_edges(graph, port_nets)
|
self.add_graph_edges(graph, port_nets)
|
||||||
|
|
||||||
|
def is_non_inverting(self):
|
||||||
|
"""Return input to output polarity for module"""
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
@ -347,3 +347,8 @@ class pnand3(pgate.pgate):
|
||||||
Overrides base class function.
|
Overrides base class function.
|
||||||
"""
|
"""
|
||||||
self.add_graph_edges(graph, port_nets)
|
self.add_graph_edges(graph, port_nets)
|
||||||
|
|
||||||
|
def is_non_inverting(self):
|
||||||
|
"""Return input to output polarity for module"""
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
@ -13,7 +13,6 @@ from sram_factory import factory
|
||||||
import contact
|
import contact
|
||||||
import logical_effort
|
import logical_effort
|
||||||
from globals import OPTS
|
from globals import OPTS
|
||||||
from pgate import pgate
|
|
||||||
from tech import cell_properties as cell_props
|
from tech import cell_properties as cell_props
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -151,14 +150,14 @@ class ptx(design.design):
|
||||||
self.spice.append("\n* spice ptx " + self.spice_device)
|
self.spice.append("\n* spice ptx " + self.spice_device)
|
||||||
|
|
||||||
if cell_props.ptx.model_is_subckt and OPTS.lvs_exe and OPTS.lvs_exe[0] == "calibre":
|
if cell_props.ptx.model_is_subckt and OPTS.lvs_exe and OPTS.lvs_exe[0] == "calibre":
|
||||||
# sky130 requires mult parameter too
|
# sky130 requires mult parameter too. It is not the same as m, but I don't understand it.
|
||||||
# self.lvs_device = "X{{0}} {{1}} {0} m={1} w={2} l={3} mult={1}".format(spice[self.tx_type],
|
# self.lvs_device = "X{{0}} {{1}} {0} m={1} w={2} l={3} mult=1".format(spice[self.tx_type],
|
||||||
# self.mults,
|
# self.mults,
|
||||||
# self.tx_width,
|
# self.tx_width,
|
||||||
# drc("minwidth_poly"))
|
# drc("minwidth_poly"))
|
||||||
# TEMP FIX: Use old device names if using Calibre.
|
# TEMP FIX: Use old device names if using Calibre.
|
||||||
|
|
||||||
self.lvs_device = "M{{0}} {{1}} {0} m={1} w={2} l={3} mult={1}".format("nshort" if self.tx_type == "nmos" else "pshort",
|
self.lvs_device = "M{{0}} {{1}} {0} m={1} w={2} l={3} mult=1".format("nshort" if self.tx_type == "nmos" else "pshort",
|
||||||
self.mults,
|
self.mults,
|
||||||
self.tx_width,
|
self.tx_width,
|
||||||
drc("minwidth_poly"))
|
drc("minwidth_poly"))
|
||||||
|
|
@ -549,3 +548,7 @@ class ptx(design.design):
|
||||||
"""
|
"""
|
||||||
self.add_graph_edges(graph, port_nets)
|
self.add_graph_edges(graph, port_nets)
|
||||||
|
|
||||||
|
def is_non_inverting(self):
|
||||||
|
"""Return input to output polarity for module"""
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from gdsMill import gdsMill
|
||||||
|
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print("Usage: {0} file.gds".format(sys.argv[0]))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
gds_file = sys.argv[1]
|
||||||
|
arrayCellLayout = gdsMill.VlsiLayout()
|
||||||
|
reader = gdsMill.Gds2reader(arrayCellLayout,debugToTerminal = 1)
|
||||||
|
reader.loadFromFile(gds_file)
|
||||||
|
|
||||||
|
|
@ -24,6 +24,12 @@ class sram():
|
||||||
|
|
||||||
sram_config.set_local_config(self)
|
sram_config.set_local_config(self)
|
||||||
|
|
||||||
|
# FIXME: adjust this to not directly change OPTS.
|
||||||
|
# Word-around to have values relevant to OPTS be displayed if not directly set.
|
||||||
|
OPTS.words_per_row = self.words_per_row
|
||||||
|
debug.info(1, "Changed OPTS wpr={}".format(self.words_per_row))
|
||||||
|
debug.info(1, "OPTS wpr={}".format(OPTS.words_per_row))
|
||||||
|
|
||||||
# reset the static duplicate name checker for unit tests
|
# reset the static duplicate name checker for unit tests
|
||||||
# in case we create more than one SRAM
|
# in case we create more than one SRAM
|
||||||
from design import design
|
from design import design
|
||||||
|
|
|
||||||
|
|
@ -25,14 +25,14 @@ class replica_bitcell_array_1rw_1r_test(openram_test):
|
||||||
OPTS.num_w_ports = 0
|
OPTS.num_w_ports = 0
|
||||||
globals.setup_bitcell()
|
globals.setup_bitcell()
|
||||||
|
|
||||||
debug.info(2, "Testing 4x4 non-replica array for cell_1rw_1r")
|
debug.info(2, "Testing 4x4 non-replica array for dp cell")
|
||||||
a = factory.create(module_type="replica_bitcell_array",
|
a = factory.create(module_type="replica_bitcell_array",
|
||||||
cols=4,
|
cols=4,
|
||||||
rows=4,
|
rows=4,
|
||||||
rbl=[1, 1])
|
rbl=[1, 1])
|
||||||
self.local_check(a)
|
self.local_check(a)
|
||||||
|
|
||||||
debug.info(2, "Testing 4x4 left replica array for cell_1rw_1r")
|
debug.info(2, "Testing 4x4 left replica array for dp cell")
|
||||||
a = factory.create(module_type="replica_bitcell_array",
|
a = factory.create(module_type="replica_bitcell_array",
|
||||||
cols=4,
|
cols=4,
|
||||||
rows=4,
|
rows=4,
|
||||||
|
|
@ -40,7 +40,7 @@ class replica_bitcell_array_1rw_1r_test(openram_test):
|
||||||
left_rbl=[0])
|
left_rbl=[0])
|
||||||
self.local_check(a)
|
self.local_check(a)
|
||||||
|
|
||||||
debug.info(2, "Testing 4x4 array left and right replica for cell_1rw_1r")
|
debug.info(2, "Testing 4x4 array left and right replica for dp cell")
|
||||||
a = factory.create(module_type="replica_bitcell_array",
|
a = factory.create(module_type="replica_bitcell_array",
|
||||||
cols=4,
|
cols=4,
|
||||||
rows=4,
|
rows=4,
|
||||||
|
|
@ -52,7 +52,7 @@ class replica_bitcell_array_1rw_1r_test(openram_test):
|
||||||
|
|
||||||
# Sky 130 has restrictions on the symmetries
|
# Sky 130 has restrictions on the symmetries
|
||||||
if OPTS.tech_name != "sky130":
|
if OPTS.tech_name != "sky130":
|
||||||
debug.info(2, "Testing 4x4 array right only replica for cell_1rw_1r")
|
debug.info(2, "Testing 4x4 array right only replica for dp cell")
|
||||||
a = factory.create(module_type="replica_bitcell_array",
|
a = factory.create(module_type="replica_bitcell_array",
|
||||||
cols=4,
|
cols=4,
|
||||||
rows=4,
|
rows=4,
|
||||||
|
|
|
||||||
|
|
@ -25,16 +25,20 @@ class replica_column_test(openram_test):
|
||||||
OPTS.num_w_ports = 0
|
OPTS.num_w_ports = 0
|
||||||
globals.setup_bitcell()
|
globals.setup_bitcell()
|
||||||
|
|
||||||
debug.info(2, "Testing replica column for 6t_cell")
|
debug.info(2, "Testing one left replica column for dual port")
|
||||||
a = factory.create(module_type="replica_column", rows=4, rbl=[1, 0], replica_bit=1)
|
a = factory.create(module_type="replica_column", rows=4, rbl=[1, 0], replica_bit=1)
|
||||||
self.local_check(a)
|
self.local_check(a)
|
||||||
|
|
||||||
debug.info(2, "Testing replica column for 6t_cell")
|
debug.info(2, "Testing one right replica column for dual port")
|
||||||
a = factory.create(module_type="replica_column", rows=4, rbl=[1, 1], replica_bit=6)
|
a = factory.create(module_type="replica_column", rows=4, rbl=[0, 1], replica_bit=5)
|
||||||
self.local_check(a)
|
self.local_check(a)
|
||||||
|
|
||||||
debug.info(2, "Testing replica column for 6t_cell")
|
debug.info(2, "Testing two (left, right) replica columns for dual port")
|
||||||
a = factory.create(module_type="replica_column", rows=4, rbl=[2, 0], replica_bit=2)
|
a = factory.create(module_type="replica_column", rows=4, rbl=[1, 1], replica_bit=1)
|
||||||
|
self.local_check(a)
|
||||||
|
|
||||||
|
debug.info(2, "Testing two (left, right) replica columns for dual port")
|
||||||
|
a = factory.create(module_type="replica_column", rows=4, rbl=[1, 1], replica_bit=6)
|
||||||
self.local_check(a)
|
self.local_check(a)
|
||||||
|
|
||||||
globals.end_openram()
|
globals.end_openram()
|
||||||
|
|
|
||||||
|
|
@ -20,18 +20,10 @@ class replica_column_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)
|
||||||
|
|
||||||
debug.info(2, "Testing replica column for cell_6t")
|
debug.info(2, "Testing replica column for single port")
|
||||||
a = factory.create(module_type="replica_column", rows=4, rbl=[1, 0], replica_bit=1)
|
a = factory.create(module_type="replica_column", rows=4, rbl=[1, 0], replica_bit=1)
|
||||||
self.local_check(a)
|
self.local_check(a)
|
||||||
|
|
||||||
debug.info(2, "Testing replica column for cell_1rw_1r")
|
|
||||||
a = factory.create(module_type="replica_column", rows=4, rbl=[1, 1], replica_bit=6)
|
|
||||||
self.local_check(a)
|
|
||||||
|
|
||||||
debug.info(2, "Testing replica column for cell_1rw_1r")
|
|
||||||
a = factory.create(module_type="replica_column", rows=4, rbl=[2, 0], replica_bit=2)
|
|
||||||
self.local_check(a)
|
|
||||||
|
|
||||||
globals.end_openram()
|
globals.end_openram()
|
||||||
|
|
||||||
# run the test from the command line
|
# run the test from the command line
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,6 @@ class timing_sram_test(openram_test):
|
||||||
# num_words=256,
|
# num_words=256,
|
||||||
# num_banks=1)
|
# num_banks=1)
|
||||||
# c.words_per_row=2
|
# c.words_per_row=2
|
||||||
# OPTS.use_tech_delay_chain_size = True
|
|
||||||
c.recompute_sizes()
|
c.recompute_sizes()
|
||||||
debug.info(1, "Testing timing for sample 1bit, 16words SRAM with 1 bank")
|
debug.info(1, "Testing timing for sample 1bit, 16words SRAM with 1 bank")
|
||||||
s = factory.create(module_type="sram", sram_config=c)
|
s = factory.create(module_type="sram", sram_config=c)
|
||||||
|
|
|
||||||
|
|
@ -103,10 +103,10 @@ def write_lvs_script(cell_name, gds_name, sp_name, final_verification=False, out
|
||||||
'cmnTranscriptEchoToFile': 1,
|
'cmnTranscriptEchoToFile': 1,
|
||||||
'lvsRecognizeGates': 'NONE',
|
'lvsRecognizeGates': 'NONE',
|
||||||
}
|
}
|
||||||
# FIXME: Remove when vdd/gnd connected
|
# FIXME: Remove when vdd/gnd connected
|
||||||
#'cmnVConnectNamesState' : 'ALL', #connects all nets with the same namee
|
# 'cmnVConnectNamesState' : 'ALL', #connects all nets with the same namee
|
||||||
# FIXME: Remove when vdd/gnd connected
|
# FIXME: Remove when vdd/gnd connected
|
||||||
#'lvsAbortOnSupplyError' : 0
|
# 'lvsAbortOnSupplyError' : 0
|
||||||
|
|
||||||
if not final_verification or not OPTS.route_supplies:
|
if not final_verification or not OPTS.route_supplies:
|
||||||
lvs_runset['cmnVConnectReport']=1
|
lvs_runset['cmnVConnectReport']=1
|
||||||
|
|
@ -115,8 +115,6 @@ def write_lvs_script(cell_name, gds_name, sp_name, final_verification=False, out
|
||||||
else:
|
else:
|
||||||
lvs_runset['lvsAbortOnSupplyError']=1
|
lvs_runset['lvsAbortOnSupplyError']=1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# write the runset file
|
# write the runset file
|
||||||
f = open(output_path + "lvs_runset", "w")
|
f = open(output_path + "lvs_runset", "w")
|
||||||
for k in sorted(iter(lvs_runset.keys())):
|
for k in sorted(iter(lvs_runset.keys())):
|
||||||
|
|
@ -127,8 +125,6 @@ def write_lvs_script(cell_name, gds_name, sp_name, final_verification=False, out
|
||||||
run_file = output_path + "run_lvs.sh"
|
run_file = output_path + "run_lvs.sh"
|
||||||
f = open(run_file, "w")
|
f = open(run_file, "w")
|
||||||
f.write("#!/bin/sh\n")
|
f.write("#!/bin/sh\n")
|
||||||
PDK_DIR=os.environ.get("PDK_DIR")
|
|
||||||
f.write("export PDK_DIR={}\n".format(PDK_DIR))
|
|
||||||
cmd = "{0} -gui -lvs {1}lvs_runset -batch".format(OPTS.lvs_exe[1],
|
cmd = "{0} -gui -lvs {1}lvs_runset -batch".format(OPTS.lvs_exe[1],
|
||||||
output_path)
|
output_path)
|
||||||
f.write(cmd)
|
f.write(cmd)
|
||||||
|
|
|
||||||
|
|
@ -81,25 +81,28 @@ def write_drc_script(cell_name, gds_name, extract, final_verification, output_pa
|
||||||
run_file = output_path + "run_drc.sh"
|
run_file = output_path + "run_drc.sh"
|
||||||
f = open(run_file, "w")
|
f = open(run_file, "w")
|
||||||
f.write("#!/bin/sh\n")
|
f.write("#!/bin/sh\n")
|
||||||
|
f.write('export OPENRAM_TECH="{}"\n'.format(os.environ['OPENRAM_TECH']))
|
||||||
|
f.write('echo "$(date): Starting DRC using Magic {}"\n'.format(OPTS.drc_exe[1]))
|
||||||
|
f.write('\n')
|
||||||
f.write("{} -dnull -noconsole << EOF\n".format(OPTS.drc_exe[1]))
|
f.write("{} -dnull -noconsole << EOF\n".format(OPTS.drc_exe[1]))
|
||||||
f.write("gds polygon subcell true\n")
|
f.write("gds polygon subcell true\n")
|
||||||
f.write("gds warning default\n")
|
f.write("gds warning default\n")
|
||||||
f.write("gds flatten true\n")
|
f.write("gds flatten true\n")
|
||||||
f.write("gds readonly true\n")
|
f.write("gds readonly true\n")
|
||||||
|
f.write("gds ordering true\n")
|
||||||
f.write("gds read {}\n".format(gds_name))
|
f.write("gds read {}\n".format(gds_name))
|
||||||
|
f.write('puts "Finished reading gds {}"\n'.format(gds_name))
|
||||||
f.write("load {}\n".format(cell_name))
|
f.write("load {}\n".format(cell_name))
|
||||||
# Flatten the cell to get rid of DRCs spanning multiple layers
|
f.write('puts "Finished loading cell {}"\n'.format(cell_name))
|
||||||
# (e.g. with routes)
|
|
||||||
#f.write("flatten {}_new\n".format(cell_name))
|
|
||||||
#f.write("load {}_new\n".format(cell_name))
|
|
||||||
#f.write("cellname rename {0}_new {0}\n".format(cell_name))
|
|
||||||
#f.write("load {}\n".format(cell_name))
|
|
||||||
f.write("cellname delete \\(UNNAMED\\)\n")
|
f.write("cellname delete \\(UNNAMED\\)\n")
|
||||||
f.write("writeall force\n")
|
f.write("writeall force\n")
|
||||||
f.write("select top cell\n")
|
f.write("select top cell\n")
|
||||||
f.write("expand\n")
|
f.write("expand\n")
|
||||||
|
f.write('puts "Finished expanding"\n')
|
||||||
f.write("drc check\n")
|
f.write("drc check\n")
|
||||||
|
f.write('puts "Finished drc check"\n')
|
||||||
f.write("drc catchup\n")
|
f.write("drc catchup\n")
|
||||||
|
f.write('puts "Finished drc catchup"\n')
|
||||||
f.write("drc count total\n")
|
f.write("drc count total\n")
|
||||||
f.write("drc count\n")
|
f.write("drc count\n")
|
||||||
if not sp_name:
|
if not sp_name:
|
||||||
|
|
@ -116,6 +119,7 @@ def write_drc_script(cell_name, gds_name, extract, final_verification, output_pa
|
||||||
if OPTS.tech_name=="sky130":
|
if OPTS.tech_name=="sky130":
|
||||||
f.write(pre + "extract style ngspice(si)\n")
|
f.write(pre + "extract style ngspice(si)\n")
|
||||||
f.write(pre + "extract\n")
|
f.write(pre + "extract\n")
|
||||||
|
f.write('puts "Finished extract"\n')
|
||||||
# f.write(pre + "ext2spice hierarchy on\n")
|
# f.write(pre + "ext2spice hierarchy on\n")
|
||||||
# f.write(pre + "ext2spice scale off\n")
|
# f.write(pre + "ext2spice scale off\n")
|
||||||
# lvs exists in 8.2.79, but be backword compatible for now
|
# lvs exists in 8.2.79, but be backword compatible for now
|
||||||
|
|
@ -134,8 +138,12 @@ def write_drc_script(cell_name, gds_name, extract, final_verification, output_pa
|
||||||
# but they all seem compatible enough.
|
# but they all seem compatible enough.
|
||||||
f.write(pre + "ext2spice format ngspice\n")
|
f.write(pre + "ext2spice format ngspice\n")
|
||||||
f.write(pre + "ext2spice {}\n".format(cell_name))
|
f.write(pre + "ext2spice {}\n".format(cell_name))
|
||||||
|
f.write('puts "Finished ext2spice"\n')
|
||||||
f.write("quit -noprompt\n")
|
f.write("quit -noprompt\n")
|
||||||
f.write("EOF\n")
|
f.write("EOF\n")
|
||||||
|
f.write("magic_retcode=$?\n")
|
||||||
|
f.write('echo "$(date): Finished ($magic_retcode) DRC using Magic {}"\n'.format(OPTS.drc_exe[1]))
|
||||||
|
f.write("exit $magic_retcode\n")
|
||||||
|
|
||||||
f.close()
|
f.close()
|
||||||
os.system("chmod u+x {}".format(run_file))
|
os.system("chmod u+x {}".format(run_file))
|
||||||
|
|
@ -210,12 +218,17 @@ def write_lvs_script(cell_name, gds_name, sp_name, final_verification=False, out
|
||||||
run_file = output_path + "/run_lvs.sh"
|
run_file = output_path + "/run_lvs.sh"
|
||||||
f = open(run_file, "w")
|
f = open(run_file, "w")
|
||||||
f.write("#!/bin/sh\n")
|
f.write("#!/bin/sh\n")
|
||||||
|
f.write('export OPENRAM_TECH="{}"\n'.format(os.environ['OPENRAM_TECH']))
|
||||||
|
f.write('echo "$(date): Starting LVS using Netgen {}"\n'.format(OPTS.lvs_exe[1]))
|
||||||
f.write("{} -noconsole << EOF\n".format(OPTS.lvs_exe[1]))
|
f.write("{} -noconsole << EOF\n".format(OPTS.lvs_exe[1]))
|
||||||
# f.write("readnet spice {0}.spice\n".format(cell_name))
|
# f.write("readnet spice {0}.spice\n".format(cell_name))
|
||||||
# f.write("readnet spice {0}\n".format(sp_name))
|
# f.write("readnet spice {0}\n".format(sp_name))
|
||||||
f.write("lvs {{{0}.spice {0}}} {{{1} {0}}} {2} {0}.lvs.report -full -json\n".format(cell_name, sp_name, setup_file))
|
f.write("lvs {{{0}.spice {0}}} {{{1} {0}}} {2} {0}.lvs.report -full -json\n".format(cell_name, sp_name, setup_file))
|
||||||
f.write("quit\n")
|
f.write("quit\n")
|
||||||
f.write("EOF\n")
|
f.write("EOF\n")
|
||||||
|
f.write("magic_retcode=$?\n")
|
||||||
|
f.write('echo "$(date): Finished ($magic_retcode) LVS using Netgen {}"\n'.format(OPTS.lvs_exe[1]))
|
||||||
|
f.write("exit $magic_retcode\n")
|
||||||
f.close()
|
f.close()
|
||||||
os.system("chmod u+x {}".format(run_file))
|
os.system("chmod u+x {}".format(run_file))
|
||||||
|
|
||||||
|
|
@ -408,7 +421,9 @@ def write_script_pex_rule(gds_name, cell_name, sp_name, output):
|
||||||
run_file = OPTS.openram_temp + "run_pex.sh"
|
run_file = OPTS.openram_temp + "run_pex.sh"
|
||||||
f = open(run_file, "w")
|
f = open(run_file, "w")
|
||||||
f.write("#!/bin/sh\n")
|
f.write("#!/bin/sh\n")
|
||||||
f.write("{} -dnull -noconsole << eof\n".format(OPTS.drc_exe[1]))
|
f.write('export OPENRAM_TECH="{}"\n'.format(os.environ['OPENRAM_TECH']))
|
||||||
|
f.write('echo "$(date): Starting PEX using Magic {}"\n'.format(OPTS.drc_exe[1]))
|
||||||
|
f.write("{} -dnull -noconsole << EOF\n".format(OPTS.drc_exe[1]))
|
||||||
f.write("gds polygon subcell true\n")
|
f.write("gds polygon subcell true\n")
|
||||||
f.write("gds warning default\n")
|
f.write("gds warning default\n")
|
||||||
f.write("gds read {}\n".format(gds_name))
|
f.write("gds read {}\n".format(gds_name))
|
||||||
|
|
@ -434,8 +449,11 @@ def write_script_pex_rule(gds_name, cell_name, sp_name, output):
|
||||||
f.write("ext2spice extresist on\n")
|
f.write("ext2spice extresist on\n")
|
||||||
f.write("ext2spice {}\n".format(cell_name))
|
f.write("ext2spice {}\n".format(cell_name))
|
||||||
f.write("quit -noprompt\n")
|
f.write("quit -noprompt\n")
|
||||||
f.write("eof\n")
|
f.write("EOF\n")
|
||||||
|
f.write("magic_retcode=$?\n")
|
||||||
f.write("mv {0}.spice {1}\n".format(cell_name, output))
|
f.write("mv {0}.spice {1}\n".format(cell_name, output))
|
||||||
|
f.write('echo "$(date): Finished PEX using Magic {}"\n'.format(OPTS.drc_exe[1]))
|
||||||
|
f.write("exit $magic_retcode\n")
|
||||||
|
|
||||||
f.close()
|
f.close()
|
||||||
os.system("chmod u+x {}".format(run_file))
|
os.system("chmod u+x {}".format(run_file))
|
||||||
|
|
|
||||||
|
|
@ -11,24 +11,66 @@ Some baseline functions to run scripts.
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import debug
|
import debug
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
from globals import OPTS
|
from globals import OPTS
|
||||||
|
|
||||||
|
|
||||||
def run_script(cell_name, script="lvs"):
|
def run_script(cell_name, script="lvs"):
|
||||||
""" Run script and create output files. """
|
""" Run script and create output files. """
|
||||||
|
|
||||||
|
echo_cmd_output = OPTS.verbose_level > 1
|
||||||
|
|
||||||
cwd = os.getcwd()
|
cwd = os.getcwd()
|
||||||
os.chdir(OPTS.openram_temp)
|
os.chdir(OPTS.openram_temp)
|
||||||
errfile = "{0}{1}.{2}.err".format(OPTS.openram_temp, cell_name, script)
|
errfile = "{0}{1}.{2}.err".format(OPTS.openram_temp, cell_name, script)
|
||||||
outfile = "{0}{1}.{2}.out".format(OPTS.openram_temp, cell_name, script)
|
outfile = "{0}{1}.{2}.out".format(OPTS.openram_temp, cell_name, script)
|
||||||
resultsfile = "{0}{1}.{2}.report".format(OPTS.openram_temp, cell_name, script)
|
resultsfile = "{0}{1}.{2}.report".format(OPTS.openram_temp, cell_name, script)
|
||||||
|
|
||||||
cmd = "{0}run_{1}.sh 2> {2} 1> {3}".format(OPTS.openram_temp,
|
scriptpath = '{0}run_{1}.sh'.format(OPTS.openram_temp, script)
|
||||||
script,
|
|
||||||
errfile,
|
debug.info(2, "Starting {}".format(scriptpath))
|
||||||
outfile)
|
start = time.time()
|
||||||
debug.info(2, cmd)
|
with open(outfile, 'wb') as fo, open(errfile, 'wb') as fe:
|
||||||
os.system(cmd)
|
p = subprocess.Popen(
|
||||||
|
[scriptpath], stdout=fo, stderr=fe, cwd=OPTS.openram_temp)
|
||||||
|
|
||||||
|
if echo_cmd_output:
|
||||||
|
tailo = subprocess.Popen([
|
||||||
|
'tail',
|
||||||
|
'-f', # Follow the output
|
||||||
|
'--pid', str(p.pid), # Close when this pid exits
|
||||||
|
outfile,
|
||||||
|
])
|
||||||
|
taile = subprocess.Popen([
|
||||||
|
'tail',
|
||||||
|
'-f', # Follow the output
|
||||||
|
'--pid', str(p.pid), # Close when this pid exits
|
||||||
|
errfile,
|
||||||
|
])
|
||||||
|
|
||||||
|
lastoutput = start
|
||||||
|
while p.poll() == None:
|
||||||
|
runningfor = time.time() - start
|
||||||
|
outputdelta = time.time() - lastoutput
|
||||||
|
if outputdelta > 30:
|
||||||
|
lastoutput = time.time()
|
||||||
|
debug.info(1, "Still running {} ({:.0f} seconds)".format(scriptpath, runningfor))
|
||||||
|
time.sleep(1)
|
||||||
|
assert p.poll() != None, (p.poll(), p)
|
||||||
|
p.wait()
|
||||||
|
|
||||||
|
# Kill the tail commands if they haven't finished.
|
||||||
|
if echo_cmd_output:
|
||||||
|
if tailo.poll() != None:
|
||||||
|
tailo.kill()
|
||||||
|
tailo.wait()
|
||||||
|
if taile.poll() != None:
|
||||||
|
taile.kill()
|
||||||
|
taile.wait()
|
||||||
|
|
||||||
|
debug.info(2, "Finished {} with {}".format(scriptpath, p.returncode))
|
||||||
|
|
||||||
os.chdir(cwd)
|
os.chdir(cwd)
|
||||||
|
|
||||||
return (outfile, errfile, resultsfile)
|
return (outfile, errfile, resultsfile)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue