mirror of https://github.com/VLSIDA/OpenRAM.git
Merge branch 'dev' into automated_analytical_model
This commit is contained in:
commit
77d7e3b1cf
|
|
@ -6,13 +6,16 @@
|
|||
# All rights reserved.
|
||||
#
|
||||
|
||||
class _cell:
|
||||
def __init__(self, port_order, port_types, port_map=None, hard_cell=True, boundary_layer="boundary"):
|
||||
class cell:
|
||||
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
|
||||
self._hard_cell = hard_cell
|
||||
self._boundary_layer = boundary_layer
|
||||
|
||||
|
||||
# Specifies the port directions
|
||||
self._port_types_map = {x: y for (x, y) in zip(port_order, port_types)}
|
||||
|
||||
|
|
@ -20,7 +23,8 @@ class _cell:
|
|||
# by default it is 1:1
|
||||
if not port_map:
|
||||
self._port_map = {x: x for x in port_order}
|
||||
|
||||
else:
|
||||
self._port_map = port_map
|
||||
# Update mapping of names
|
||||
self._original_port_order = port_order
|
||||
self._port_order = port_order
|
||||
|
|
@ -47,14 +51,24 @@ class _cell:
|
|||
return self._port_order
|
||||
|
||||
@port_order.setter
|
||||
def port_order(self, x):
|
||||
self._port_order = x
|
||||
# Update ordered name list in the new order
|
||||
self._port_names = [self._port_map[x] for x in self._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]
|
||||
def port_order(self, port_order):
|
||||
# If we are going to redefine more ports (i.e. well biases) don't init stuff
|
||||
old_port_len = len(self._port_order)
|
||||
if old_port_len == len(port_order):
|
||||
self._port_order = port_order
|
||||
# Update ordered name list in the new order
|
||||
self._port_names = [self._port_map[x] for x in self._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
|
||||
def port_indices(self):
|
||||
|
|
@ -65,15 +79,36 @@ class _cell:
|
|||
return self._port_map
|
||||
|
||||
@port_map.setter
|
||||
def port_map(self, x):
|
||||
self._port_map = x
|
||||
def port_map(self, port_map):
|
||||
self._port_map = port_map
|
||||
# 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
|
||||
def port_types(self):
|
||||
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
|
||||
def boundary_layer(self):
|
||||
return self._boundary_layer
|
||||
|
|
@ -108,7 +143,7 @@ class _pgate:
|
|||
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):
|
||||
super().__init__(port_order, port_types, port_map)
|
||||
|
||||
|
|
@ -147,39 +182,45 @@ class cell_properties():
|
|||
|
||||
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"])
|
||||
|
||||
self._nand2_dec = _cell(["A", "B", "Z", "vdd", "gnd"],
|
||||
self._nand2_dec = cell(["A", "B", "Z", "vdd", "gnd"],
|
||||
["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"])
|
||||
|
||||
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"])
|
||||
|
||||
self._dff = _cell(["D", "Q", "clk", "vdd", "gnd"],
|
||||
self._dff = cell(["D", "Q", "clk", "vdd", "gnd"],
|
||||
["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"])
|
||||
|
||||
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"])
|
||||
|
||||
self._bitcell_1port = _bitcell(["bl", "br", "wl", "vdd", "gnd"],
|
||||
self._bitcell_1port = bitcell(["bl", "br", "wl", "vdd", "gnd"],
|
||||
["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"])
|
||||
|
||||
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"])
|
||||
|
||||
self._row_cap_2port = _bitcell(["wl0", "wl1", "gnd"],
|
||||
self._row_cap_2port = bitcell(["wl0", "wl1", "gnd"],
|
||||
["INPUT", "INPUT", "POWER", "GROUND"])
|
||||
|
||||
|
||||
@property
|
||||
def ptx(self):
|
||||
return self._ptx
|
||||
|
|
@ -224,6 +265,14 @@ class cell_properties():
|
|||
def bitcell_2port(self):
|
||||
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
|
||||
def col_cap_2port(self):
|
||||
return self._col_cap_2port
|
||||
|
|
|
|||
|
|
@ -167,8 +167,8 @@ class design(hierarchy_design):
|
|||
|
||||
from tech import layer_indices
|
||||
import tech
|
||||
for layer in layer_indices:
|
||||
key = "{}_stack".format(layer)
|
||||
for layer_id in layer_indices:
|
||||
key = "{}_stack".format(layer_id)
|
||||
|
||||
# Set the stack as a local helper
|
||||
try:
|
||||
|
|
@ -177,24 +177,24 @@ class design(hierarchy_design):
|
|||
except AttributeError:
|
||||
pass
|
||||
|
||||
# Skip computing the pitch for active
|
||||
if layer == "active":
|
||||
# Skip computing the pitch for non-routing layers
|
||||
if layer_id in ["active", "nwell"]:
|
||||
continue
|
||||
|
||||
# Add the pitch
|
||||
setattr(design,
|
||||
"{}_pitch".format(layer),
|
||||
design.compute_pitch(layer, True))
|
||||
"{}_pitch".format(layer_id),
|
||||
design.compute_pitch(layer_id, True))
|
||||
|
||||
# Add the non-preferrd pitch (which has vias in the "wrong" way)
|
||||
setattr(design,
|
||||
"{}_nonpref_pitch".format(layer),
|
||||
design.compute_pitch(layer, False))
|
||||
"{}_nonpref_pitch".format(layer_id),
|
||||
design.compute_pitch(layer_id, False))
|
||||
|
||||
if False:
|
||||
from tech import preferred_directions
|
||||
print(preferred_directions)
|
||||
from tech import layer, layer_indices
|
||||
from tech import layer_indices
|
||||
for name in layer_indices:
|
||||
if name == "active":
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -117,6 +117,14 @@ class timing_graph():
|
|||
cur_slew = delays[-1].slew
|
||||
|
||||
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):
|
||||
""" override print function output """
|
||||
|
|
|
|||
|
|
@ -8,9 +8,7 @@
|
|||
import hierarchy_layout
|
||||
import hierarchy_spice
|
||||
import debug
|
||||
import os
|
||||
from globals import OPTS
|
||||
import tech
|
||||
|
||||
|
||||
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,
|
||||
self.lvs_errors))
|
||||
|
||||
if not OPTS.keep_temp:
|
||||
os.remove(tempspice)
|
||||
os.remove(tempgds)
|
||||
|
||||
def DRC(self, final_verification=False):
|
||||
"""Checks DRC for a module"""
|
||||
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,
|
||||
num_errors))
|
||||
|
||||
if not OPTS.keep_temp:
|
||||
os.remove(tempgds)
|
||||
|
||||
def LVS(self, final_verification=False):
|
||||
"""Checks LVS for a module"""
|
||||
import verify
|
||||
|
|
@ -118,9 +109,6 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
|
|||
debug.check(num_errors == 0,
|
||||
"LVS failed for {0} with {1} error(s)".format(self.cell_name,
|
||||
num_errors))
|
||||
if not OPTS.keep_temp:
|
||||
os.remove(tempspice)
|
||||
os.remove(tempgds)
|
||||
|
||||
def init_graph_params(self):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ class spice():
|
|||
else:
|
||||
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
|
||||
self.mods = []
|
||||
# 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)
|
||||
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):
|
||||
"""
|
||||
Adds edges based on inputs/outputs.
|
||||
Overrides base class function.
|
||||
"""
|
||||
self.add_graph_edges(graph, port_nets)
|
||||
|
||||
def is_non_inverting(self):
|
||||
"""Return input to output polarity for module"""
|
||||
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -26,9 +26,7 @@ class bitcell_base(design.design):
|
|||
self.nets_match = self.do_nets_exist(prop.storage_nets)
|
||||
self.mirror = prop.mirror
|
||||
self.end_caps = prop.end_caps
|
||||
|
||||
self.supplies = ["vdd", "gnd"]
|
||||
|
||||
|
||||
def get_stage_effort(self, load):
|
||||
parasitic_delay = 1
|
||||
# This accounts for bitline being drained
|
||||
|
|
@ -95,7 +93,7 @@ class bitcell_base(design.design):
|
|||
labels for pex simulation.
|
||||
"""
|
||||
# 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 = []
|
||||
for i in range(len(self.get_storage_net_names())):
|
||||
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)):
|
||||
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)
|
||||
|
||||
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.
|
||||
"""
|
||||
if OPTS.bitcell is not "pbitcell":
|
||||
if OPTS.bitcell != "pbitcell":
|
||||
normalized_storage_net_offset = self.get_storage_net_offset()
|
||||
|
||||
else:
|
||||
|
|
@ -160,12 +157,12 @@ class bitcell_base(design.design):
|
|||
Q_bar_x = net_offset[1][0] - self.leftmost_xpos
|
||||
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
|
||||
|
||||
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):
|
||||
"""
|
||||
|
|
@ -173,3 +170,36 @@ class bitcell_base(design.design):
|
|||
|
||||
"""
|
||||
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):
|
||||
"""Adds edges based on inputs/outputs. Overrides base class function."""
|
||||
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_debug_measurement_objects())
|
||||
read_measures.append(self.create_read_bit_measures())
|
||||
read_measures.append(self.create_sen_and_bitline_path_measures())
|
||||
|
||||
return read_measures
|
||||
|
||||
|
|
@ -234,6 +235,98 @@ class delay(simulation):
|
|||
qbar_meas = voltage_at_measure("v_qbar_{}".format(meas_tag), qbar_name)
|
||||
|
||||
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):
|
||||
""" Set the load and slew """
|
||||
|
|
@ -651,6 +744,8 @@ class delay(simulation):
|
|||
debug.error("Failed to Measure Read Port Values:\n\t\t{0}".format(read_port_dict), 1)
|
||||
|
||||
result[port].update(read_port_dict)
|
||||
|
||||
self.check_path_measures()
|
||||
|
||||
return (True, result)
|
||||
|
||||
|
|
@ -752,6 +847,21 @@ class delay(simulation):
|
|||
debug.info(1, "min_dicharge={}, min_diff={}".format(min_dicharge, 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):
|
||||
"""
|
||||
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
|
||||
# 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 = ""
|
||||
for c in self.probe_address[self.sram.col_addr_size:]: # invert everything else
|
||||
if c=="0":
|
||||
|
|
|
|||
|
|
@ -412,7 +412,7 @@ class model_check(delay):
|
|||
data_dict[self.bl_meas_name] = bl_delays[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)
|
||||
debug.info(1,"Wordline model delays:\n\t {}".format(wl_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.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.sae_model_name] = name_dict["sae_measures"]
|
||||
|
||||
|
|
|
|||
|
|
@ -444,6 +444,10 @@ class simulation():
|
|||
pin_names.append("{0}".format("gnd"))
|
||||
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):
|
||||
"""
|
||||
Exclude portions of SRAM from timing graph which are not relevant
|
||||
|
|
@ -475,11 +479,12 @@ class simulation():
|
|||
debug.warning("Error occurred while determining SEN name. Can cause faults in simulation.")
|
||||
|
||||
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)
|
||||
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)):]
|
||||
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
|
||||
|
|
@ -487,7 +492,7 @@ class simulation():
|
|||
self.bl_name = bl_name_port
|
||||
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)):]
|
||||
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
|
||||
|
|
|
|||
|
|
@ -73,3 +73,9 @@ class sense_amp(design.design):
|
|||
def build_graph(self, graph, inst_name, port_nets):
|
||||
"""Adds edges based on inputs/outputs. Overrides base class function."""
|
||||
self.add_graph_edges(graph, port_nets)
|
||||
|
||||
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
|
||||
offset_int = int(recordLength[0]) # extract length
|
||||
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)
|
||||
return record
|
||||
|
||||
|
|
@ -176,7 +177,7 @@ class Gds2reader:
|
|||
def readBoundary(self):
|
||||
##reads in a boundary type structure = a filled polygon
|
||||
if(self.debugToTerminal==1):
|
||||
print("\t\t\tBeginBoundary")
|
||||
print("\t\tBeginBoundary")
|
||||
thisBoundary=GdsBoundary()
|
||||
while 1:
|
||||
record = self.readNextRecord()
|
||||
|
|
@ -214,13 +215,13 @@ class Gds2reader:
|
|||
print("\t\t\tXY Point: "+str(x)+","+str(y))
|
||||
elif(idBits==b'\x11\x00'): #End Of Element
|
||||
if(self.debugToTerminal==1):
|
||||
print("\t\t\tEndBoundary")
|
||||
print("\t\tEndBoundary")
|
||||
break;
|
||||
return thisBoundary
|
||||
|
||||
def readPath(self): #reads in a path structure
|
||||
if(self.debugToTerminal==1):
|
||||
print("\t\t\tBeginPath")
|
||||
print("\t\tBeginPath")
|
||||
|
||||
thisPath=GdsPath()
|
||||
while 1:
|
||||
|
|
@ -274,7 +275,7 @@ class Gds2reader:
|
|||
print("\t\t\tXY Point: "+str(x)+","+str(y))
|
||||
elif(idBits==b'\x11\x00'): #End Of Element
|
||||
if(self.debugToTerminal==1):
|
||||
print("\t\t\tEndPath")
|
||||
print("\t\tEndPath")
|
||||
break;
|
||||
return thisPath
|
||||
|
||||
|
|
|
|||
|
|
@ -600,7 +600,7 @@ class VlsiLayout:
|
|||
|
||||
shapes = self.getAllShapes(lpp)
|
||||
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,
|
||||
"Error: "+str(structure)+".cell_size information not found yet")
|
||||
max_boundary = None
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import re
|
|||
import copy
|
||||
import importlib
|
||||
|
||||
VERSION = "1.1.6"
|
||||
VERSION = "1.1.7"
|
||||
NAME = "OpenRAM v{}".format(VERSION)
|
||||
USAGE = "openram.py [options] <config file>\nUse -h for help.\n"
|
||||
|
||||
|
|
@ -353,6 +353,21 @@ def end_openram():
|
|||
verify.print_lvs_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():
|
||||
"""
|
||||
|
|
@ -364,19 +379,9 @@ def cleanup_paths():
|
|||
"Preserving temp directory: {}".format(OPTS.openram_temp))
|
||||
return
|
||||
elif os.path.exists(OPTS.openram_temp):
|
||||
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)
|
||||
|
||||
purge_temp()
|
||||
|
||||
|
||||
def setup_paths():
|
||||
""" Set up the non-tech related paths. """
|
||||
debug.info(2, "Setting up paths...")
|
||||
|
|
@ -405,7 +410,7 @@ def setup_paths():
|
|||
OPTS.openram_temp += "/"
|
||||
debug.info(1, "Temporary files saved in " + OPTS.openram_temp)
|
||||
|
||||
|
||||
|
||||
def is_exe(fpath):
|
||||
""" Return true if the given is an executable file that exists. """
|
||||
return os.path.exists(fpath) and os.access(fpath, os.X_OK)
|
||||
|
|
@ -427,15 +432,17 @@ def find_exe(check_exe):
|
|||
|
||||
def init_paths():
|
||||
""" Create the temp and output directory if it doesn't exist """
|
||||
|
||||
# make the directory if it doesn't exist
|
||||
try:
|
||||
debug.info(1,
|
||||
"Creating temp directory: {}".format(OPTS.openram_temp))
|
||||
os.makedirs(OPTS.openram_temp, 0o750)
|
||||
except OSError as e:
|
||||
if e.errno == 17: # errno.EEXIST
|
||||
os.chmod(OPTS.openram_temp, 0o750)
|
||||
if os.path.exists(OPTS.openram_temp):
|
||||
purge_temp()
|
||||
else:
|
||||
# make the directory if it doesn't exist
|
||||
try:
|
||||
debug.info(1,
|
||||
"Creating temp directory: {}".format(OPTS.openram_temp))
|
||||
os.makedirs(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!
|
||||
# make the directory if it doesn't exist
|
||||
|
|
|
|||
|
|
@ -729,7 +729,6 @@ class bank(design.design):
|
|||
inst2_bl_name=inst2_bl_name,
|
||||
inst2_br_name=inst2_br_name)
|
||||
|
||||
|
||||
# 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"]):
|
||||
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)
|
||||
mid2 = mid1.scale(1, 0) + bitcell_wl_pos.scale(0, 1)
|
||||
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,
|
||||
to_layer=bitcell_wl_pin.layer,
|
||||
offset=mid2)
|
||||
self.add_path(bitcell_wl_pin.layer, [mid2, bitcell_wl_pos])
|
||||
offset=mid1)
|
||||
self.add_path(bitcell_wl_pin.layer, [mid1, mid2, bitcell_wl_pos])
|
||||
else:
|
||||
self.add_path(bitcell_wl_pin.layer, [driver_wl_pos, mid1, mid2, bitcell_wl_pos])
|
||||
|
||||
|
||||
def route_port_address_right(self, port):
|
||||
""" 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.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):
|
||||
for col in range(self.column_size):
|
||||
for port in self.all_ports:
|
||||
|
|
@ -63,8 +58,8 @@ class bitcell_base_array(design.design):
|
|||
self.add_pin(bl_name, "INOUT")
|
||||
for wl_name in self.get_wordline_names():
|
||||
self.add_pin(wl_name, "INPUT")
|
||||
self.add_pin(self.supplies[0], "POWER")
|
||||
self.add_pin(self.supplies[1], "GROUND")
|
||||
self.add_pin("vdd", "POWER")
|
||||
self.add_pin("gnd", "GROUND")
|
||||
|
||||
def get_bitcell_pins(self, row, col):
|
||||
"""
|
||||
|
|
@ -75,8 +70,8 @@ class bitcell_base_array(design.design):
|
|||
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.all_wordline_names if x.endswith("_{0}".format(row))])
|
||||
bitcell_pins.append(self.bitcell_supplies[0])
|
||||
bitcell_pins.append(self.bitcell_supplies[1])
|
||||
bitcell_pins.append("vdd")
|
||||
bitcell_pins.append("gnd")
|
||||
|
||||
return bitcell_pins
|
||||
|
||||
|
|
@ -166,8 +161,8 @@ class bitcell_base_array(design.design):
|
|||
for row in range(self.row_size):
|
||||
for col in range(self.column_size):
|
||||
inst = self.cell_inst[row, col]
|
||||
for (pin_name, new_name) in zip(self.bitcell_supplies, self.supplies):
|
||||
self.copy_layout_pin(inst, pin_name, new_name)
|
||||
for pin_name in ["vdd", "gnd"]:
|
||||
self.copy_layout_pin(inst, pin_name)
|
||||
|
||||
def _adjust_x_offset(self, xoffset, col, col_offset):
|
||||
tempx = xoffset
|
||||
|
|
|
|||
|
|
@ -39,6 +39,10 @@ class col_cap_array(bitcell_base_array):
|
|||
|
||||
self.place_array("dummy_r{0}_c{1}", self.mirror)
|
||||
self.add_layout_pins()
|
||||
|
||||
self.height = self.dummy_cell.height
|
||||
self.width = self.column_size * self.cell.width
|
||||
|
||||
self.add_boundary()
|
||||
self.DRC_LVS()
|
||||
|
||||
|
|
@ -96,7 +100,7 @@ class col_cap_array(bitcell_base_array):
|
|||
inst = self.cell_inst[row, col]
|
||||
for pin_name in ["vdd", "gnd"]:
|
||||
for pin in inst.get_pins(pin_name):
|
||||
self.add_power_pin(name=pin.name,
|
||||
self.add_power_pin(name=pin_name,
|
||||
loc=pin.center(),
|
||||
start_layer=pin.layer)
|
||||
|
||||
|
|
|
|||
|
|
@ -34,18 +34,9 @@ class column_mux_array(design.design):
|
|||
self.column_offset = column_offset
|
||||
|
||||
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
|
||||
|
||||
# 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":
|
||||
self.via_directions = ("H", "H")
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -268,9 +268,11 @@ class hierarchical_predecode(design.design):
|
|||
height=via.mod.second_layer_height,
|
||||
width=via.mod.second_layer_width)
|
||||
|
||||
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)
|
||||
# This is a hack to fix via-to-via spacing issues, but it is currently
|
||||
# causing its own DRC problems.
|
||||
# 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):
|
||||
# This 2D array defines the connection mapping
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import debug
|
|||
import design
|
||||
from sram_factory import factory
|
||||
from vector import vector
|
||||
from tech import layer
|
||||
from tech import layer, drc
|
||||
from globals import OPTS
|
||||
from tech import layer_properties as layer_props
|
||||
|
||||
|
|
@ -86,6 +86,13 @@ class port_address(design.design):
|
|||
else:
|
||||
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):
|
||||
for row in range(self.addr_size):
|
||||
decoder_name = "addr_{}".format(row)
|
||||
|
|
@ -157,11 +164,13 @@ class port_address(design.design):
|
|||
b = factory.create(module_type=OPTS.bitcell)
|
||||
|
||||
if local_array_size > 0:
|
||||
# The local wordline driver will change the polarity
|
||||
self.rbl_driver = factory.create(module_type="inv_dec",
|
||||
size=driver_size,
|
||||
height=b.height)
|
||||
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,
|
||||
height=b.height)
|
||||
|
||||
|
|
@ -189,6 +198,8 @@ class port_address(design.design):
|
|||
|
||||
temp = []
|
||||
temp.append("wl_en")
|
||||
if OPTS.local_array_size == 0:
|
||||
temp.append("vdd")
|
||||
temp.append("rbl_wl")
|
||||
temp.append("vdd")
|
||||
temp.append("gnd")
|
||||
|
|
@ -221,7 +232,10 @@ class port_address(design.design):
|
|||
wordline_driver_array_offset = vector(self.row_decoder_inst.rx(), 0)
|
||||
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:
|
||||
rbl_driver_offset = vector(x_offset,
|
||||
0)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import debug
|
||||
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 globals import OPTS
|
||||
from sram_factory import factory
|
||||
|
|
@ -121,18 +121,18 @@ class replica_bitcell_array(bitcell_base_array):
|
|||
# the array.
|
||||
# These go from the top (where the bitcell array starts ) down
|
||||
replica_bit = self.rbl[0] - port
|
||||
column_offset = self.rbl[0]
|
||||
|
||||
elif port in self.right_rbl:
|
||||
|
||||
# We will always have self.rbl[0] rows of replica wordlines below
|
||||
# the array.
|
||||
# These go from the bottom up
|
||||
replica_bit = self.rbl[0] + self.row_size + port
|
||||
column_offset = self.rbl[0] + self.column_size + 1
|
||||
else:
|
||||
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",
|
||||
rows=self.row_size,
|
||||
rbl=self.rbl,
|
||||
|
|
@ -313,19 +313,14 @@ class replica_bitcell_array(bitcell_base_array):
|
|||
def create_layout(self):
|
||||
|
||||
# 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_layer = pin.layer
|
||||
self.unused_pitch = 1.5 * getattr(self, "{}_pitch".format(pin_layer))
|
||||
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
|
||||
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.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()
|
||||
|
||||
|
||||
# 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
|
||||
# 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])
|
||||
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.route_unused_wordlines()
|
||||
|
|
@ -374,19 +373,11 @@ class replica_bitcell_array(bitcell_base_array):
|
|||
|
||||
# Grow from left to right, toward the array
|
||||
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.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
|
||||
|
||||
offset = self.bitcell_offset.scale(-len(self.left_rbl) + bit, -self.rbl[0] - 1) + self.unused_offset
|
||||
self.replica_col_insts[bit].place(offset)
|
||||
# Grow to the right of the bitcell array, array outward
|
||||
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) + 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)
|
||||
|
||||
offset = self.bitcell_array_inst.lr() + self.bitcell_offset.scale(bit, -self.rbl[0] - 1)
|
||||
self.replica_col_insts[self.rbl[0] + bit].place(offset)
|
||||
|
||||
# Replica dummy rows
|
||||
|
|
@ -408,37 +399,24 @@ class replica_bitcell_array(bitcell_base_array):
|
|||
# FIXME: These depend on the array size itself
|
||||
# Far top dummy row (first row above array is NOT flipped)
|
||||
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()
|
||||
else:
|
||||
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()
|
||||
self.dummy_row_insts[1].place(offset=dummy_row_offset,
|
||||
mirror="MX" if flip_dummy else "R0")
|
||||
|
||||
# FIXME: These depend on the array size itself
|
||||
# Far bottom dummy row (first row below array IS flipped)
|
||||
flip_dummy = (self.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
|
||||
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
|
||||
dummy_row_offset = self.bitcell_offset.scale(0, -self.rbl[0] - 1 + flip_dummy) + self.unused_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
|
||||
# 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
|
||||
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))
|
||||
|
||||
dummy_col_offset = self.bitcell_offset.scale(-len(self.left_rbl) - 1, -self.rbl[0] - 1) + self.unused_offset
|
||||
self.dummy_col_insts[0].place(offset=dummy_col_offset)
|
||||
|
||||
# Far right dummy col
|
||||
# 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()
|
||||
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()
|
||||
|
||||
dummy_col_offset = self.bitcell_offset.scale(len(self.right_rbl), -self.rbl[0] - 1) + self.bitcell_array_inst.lr()
|
||||
self.dummy_col_insts[1].place(offset=dummy_col_offset)
|
||||
|
||||
def add_layout_pins(self):
|
||||
|
|
@ -455,6 +433,7 @@ class replica_bitcell_array(bitcell_base_array):
|
|||
offset=pin.ll().scale(0, 1),
|
||||
width=self.width,
|
||||
height=pin.height())
|
||||
|
||||
# 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)
|
||||
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"))
|
||||
|
||||
# Add a path to connect to the array
|
||||
self.add_path(pin_layer, [left_loc, left_pin_loc])
|
||||
self.add_path(pin_layer, [right_loc, right_pin_loc])
|
||||
self.add_path(pin_layer, [left_loc, right_loc], width=pin.height())
|
||||
|
||||
def gen_bl_wire(self):
|
||||
if OPTS.netlist_only:
|
||||
|
|
|
|||
|
|
@ -14,37 +14,38 @@ from tech import layer_properties as layer_props
|
|||
class replica_column(bitcell_base_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.
|
||||
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).
|
||||
"""
|
||||
|
||||
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.left_rbl = rbl[0]
|
||||
self.right_rbl = rbl[1]
|
||||
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
|
||||
self.cell = factory.create(module_type=OPTS.bitcell)
|
||||
|
||||
# For end caps
|
||||
try:
|
||||
if not self.cell.end_caps:
|
||||
self.total_size += 2
|
||||
except AttributeError:
|
||||
self.total_size += 2
|
||||
# Total size includes the replica rows and column cap rows
|
||||
self.total_size = self.left_rbl + rows + self.right_rbl + 2
|
||||
|
||||
self.column_offset = column_offset
|
||||
|
||||
debug.check(replica_bit != 0 and replica_bit != rows,
|
||||
"Replica bit cannot be the dummy row.")
|
||||
debug.check(replica_bit <= self.left_rbl or replica_bit >= self.total_size - self.right_rbl - 1,
|
||||
debug.check(replica_bit != 0 and replica_bit != self.total_size - 1,
|
||||
"Replica bit cannot be the dummy/cap row.")
|
||||
debug.check(replica_bit < self.row_start or replica_bit >= self.row_end,
|
||||
"Replica bit cannot be in the regular array.")
|
||||
if layer_props.replica_column.even_rows:
|
||||
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()
|
||||
|
||||
def create_layout(self):
|
||||
self.height = self.total_size * self.cell.height
|
||||
self.width = self.cell.width
|
||||
|
||||
self.place_instances()
|
||||
|
||||
self.height = self.cell_inst[-1].uy()
|
||||
self.width = self.cell_inst[0].rx()
|
||||
|
||||
self.add_layout_pins()
|
||||
|
||||
self.add_boundary()
|
||||
self.DRC_LVS()
|
||||
|
||||
def add_pins(self):
|
||||
|
||||
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_wordline_names, "INPUT")
|
||||
|
|
@ -93,32 +96,32 @@ class replica_column(bitcell_base_array):
|
|||
self.add_mod(self.edge_cell)
|
||||
|
||||
def create_instances(self):
|
||||
self.cell_inst = {}
|
||||
self.cell_inst = []
|
||||
|
||||
for row in range(self.total_size):
|
||||
name="rbc_{0}".format(row)
|
||||
# Top/bottom cell are always dummy cells.
|
||||
# Regular array cells are replica cells (>left_rbl and <rows-right_rbl)
|
||||
# Replic bit specifies which other bit (in the full range (0,rows) to make a replica cell.
|
||||
if (row > self.left_rbl and row < self.total_size - self.right_rbl - 1):
|
||||
self.cell_inst[row]=self.add_inst(name=name,
|
||||
mod=self.replica_cell)
|
||||
self.connect_inst(self.get_bitcell_pins(row, 0))
|
||||
elif row==self.replica_bit:
|
||||
self.cell_inst[row]=self.add_inst(name=name,
|
||||
mod=self.replica_cell)
|
||||
self.connect_inst(self.get_bitcell_pins(row, 0))
|
||||
elif (row == 0 or row == self.total_size - 1):
|
||||
self.cell_inst[row]=self.add_inst(name=name,
|
||||
mod=self.edge_cell)
|
||||
real_row = row
|
||||
if self.cell.end_caps:
|
||||
real_row -= 1
|
||||
|
||||
# Regular array cells are replica cells
|
||||
# Replic bit specifies which other bit (in the full range (0,total_size) to make a replica cell.
|
||||
if (row == 0 or row == self.total_size - 1):
|
||||
self.cell_inst.append(self.add_inst(name=name,
|
||||
mod=self.edge_cell))
|
||||
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:
|
||||
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:
|
||||
self.cell_inst[row]=self.add_inst(name=name,
|
||||
mod=self.dummy_cell)
|
||||
self.connect_inst(self.get_bitcell_pins(row, 0))
|
||||
# Top/bottom cell are always dummy/cap cells.
|
||||
self.cell_inst.append(self.add_inst(name=name,
|
||||
mod=self.dummy_cell))
|
||||
self.connect_inst(self.get_bitcell_pins(real_row, 0))
|
||||
|
||||
def place_instances(self):
|
||||
# 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())
|
||||
|
||||
# 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"]:
|
||||
if inst in [self.cell_inst[0], self.cell_inst[self.total_size - 1]]:
|
||||
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).
|
||||
"""
|
||||
|
||||
for row, cell in self.cell_inst.items():
|
||||
for row, cell in enumerate(self.cell_inst):
|
||||
if row != self.replica_bit:
|
||||
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.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.DRC_LVS()
|
||||
|
||||
|
|
@ -48,7 +52,7 @@ class row_cap_array(bitcell_base_array):
|
|||
""" Create the module instances used in this design """
|
||||
self.cell_inst = {}
|
||||
for col in range(self.column_size):
|
||||
for row in range(1, self.row_size - 1):
|
||||
for row in range(0, self.row_size):
|
||||
name = "bit_r{0}_c{1}".format(row, col)
|
||||
self.cell_inst[row, col]=self.add_inst(name=name,
|
||||
mod=self.dummy_cell)
|
||||
|
|
@ -67,17 +71,13 @@ class row_cap_array(bitcell_base_array):
|
|||
return bitcell_pins
|
||||
|
||||
def place_array(self, name_template, row_offset=0):
|
||||
# We increase it by a well enclosure so the precharges don't overlap our wells
|
||||
self.height = self.row_size * self.cell.height
|
||||
self.width = self.column_size * self.cell.width
|
||||
|
||||
xoffset = 0.0
|
||||
for col in range(self.column_size):
|
||||
yoffset = self.cell.height
|
||||
tempx, dir_y = self._adjust_x_offset(xoffset, col, self.column_offset)
|
||||
|
||||
for row in range(1, self.row_size - 1):
|
||||
tempy, dir_x = self._adjust_y_offset(yoffset, row, row_offset)
|
||||
for row in range(self.row_size):
|
||||
tempy, dir_x = self._adjust_y_offset(yoffset, row + 1, row_offset)
|
||||
|
||||
if dir_x and dir_y:
|
||||
dir_key = "XY"
|
||||
|
|
@ -113,7 +113,7 @@ class row_cap_array(bitcell_base_array):
|
|||
inst = self.cell_inst[row, col]
|
||||
for pin_name in ["vdd", "gnd"]:
|
||||
for pin in inst.get_pins(pin_name):
|
||||
self.add_power_pin(name=pin.name,
|
||||
self.add_power_pin(name=pin_name,
|
||||
loc=pin.center(),
|
||||
start_layer=pin.layer)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import optparse
|
|||
import getpass
|
||||
import os
|
||||
|
||||
|
||||
class options(optparse.Values):
|
||||
"""
|
||||
Class for holding all of the OpenRAM options. All
|
||||
|
|
@ -61,7 +60,7 @@ class options(optparse.Values):
|
|||
rbl_delay_percentage = 0.5
|
||||
|
||||
# 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_fanout_per_stage = 4
|
||||
|
||||
|
|
@ -103,7 +102,7 @@ class options(optparse.Values):
|
|||
# Run with extracted parasitics
|
||||
use_pex = False
|
||||
# Output config with all options
|
||||
output_extended_config = False
|
||||
output_extended_config = True
|
||||
|
||||
|
||||
###################
|
||||
|
|
@ -124,7 +123,7 @@ class options(optparse.Values):
|
|||
pex_exe = None
|
||||
# For sky130, we need magic for filtering.
|
||||
magic_exe = None
|
||||
|
||||
|
||||
# Number of threads to use
|
||||
num_threads = 2
|
||||
|
||||
|
|
@ -153,6 +152,8 @@ class options(optparse.Values):
|
|||
bitcell = "bitcell"
|
||||
buf_dec = "pbuf"
|
||||
column_mux_array = "column_mux_array"
|
||||
col_cap = "col_cap"
|
||||
col_cap_array = "col_cap_array"
|
||||
control_logic = "control_logic"
|
||||
decoder = "hierarchical_decoder"
|
||||
delay_chain = "delay_chain"
|
||||
|
|
@ -165,6 +166,8 @@ class options(optparse.Values):
|
|||
precharge_array = "precharge_array"
|
||||
ptx = "ptx"
|
||||
replica_bitline = "replica_bitline"
|
||||
row_cap = "row_cap"
|
||||
row_cap_array = "row_cap_array"
|
||||
sense_amp_array = "sense_amp_array"
|
||||
sense_amp = "sense_amp"
|
||||
tri_gate_array = "tri_gate_array"
|
||||
|
|
|
|||
|
|
@ -146,3 +146,8 @@ class pand2(pgate.pgate):
|
|||
offset=pin.center(),
|
||||
width=pin.width(),
|
||||
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.
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
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 logical_effort
|
||||
from globals import OPTS
|
||||
from pgate import pgate
|
||||
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)
|
||||
|
||||
if cell_props.ptx.model_is_subckt and OPTS.lvs_exe and OPTS.lvs_exe[0] == "calibre":
|
||||
# sky130 requires mult parameter too
|
||||
# self.lvs_device = "X{{0}} {{1}} {0} m={1} w={2} l={3} mult={1}".format(spice[self.tx_type],
|
||||
# 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.mults,
|
||||
# self.tx_width,
|
||||
# drc("minwidth_poly"))
|
||||
# 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.tx_width,
|
||||
drc("minwidth_poly"))
|
||||
|
|
@ -549,3 +548,7 @@ class ptx(design.design):
|
|||
"""
|
||||
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)
|
||||
|
||||
|
|
@ -23,7 +23,13 @@ class sram():
|
|||
def __init__(self, sram_config, name):
|
||||
|
||||
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
|
||||
# in case we create more than one SRAM
|
||||
from design import design
|
||||
|
|
|
|||
|
|
@ -25,14 +25,14 @@ class replica_bitcell_array_1rw_1r_test(openram_test):
|
|||
OPTS.num_w_ports = 0
|
||||
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",
|
||||
cols=4,
|
||||
rows=4,
|
||||
rbl=[1, 1])
|
||||
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",
|
||||
cols=4,
|
||||
rows=4,
|
||||
|
|
@ -40,7 +40,7 @@ class replica_bitcell_array_1rw_1r_test(openram_test):
|
|||
left_rbl=[0])
|
||||
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",
|
||||
cols=4,
|
||||
rows=4,
|
||||
|
|
@ -52,7 +52,7 @@ class replica_bitcell_array_1rw_1r_test(openram_test):
|
|||
|
||||
# Sky 130 has restrictions on the symmetries
|
||||
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",
|
||||
cols=4,
|
||||
rows=4,
|
||||
|
|
|
|||
|
|
@ -25,18 +25,22 @@ class replica_column_test(openram_test):
|
|||
OPTS.num_w_ports = 0
|
||||
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)
|
||||
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=[0, 1], replica_bit=5)
|
||||
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=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)
|
||||
|
||||
debug.info(2, "Testing replica column for 6t_cell")
|
||||
a = factory.create(module_type="replica_column", rows=4, rbl=[2, 0], replica_bit=2)
|
||||
self.local_check(a)
|
||||
|
||||
|
||||
globals.end_openram()
|
||||
|
||||
# run the test from the command line
|
||||
|
|
|
|||
|
|
@ -20,18 +20,10 @@ class replica_column_test(openram_test):
|
|||
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
|
||||
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)
|
||||
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()
|
||||
|
||||
# run the test from the command line
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ class timing_sram_test(openram_test):
|
|||
# num_words=256,
|
||||
# num_banks=1)
|
||||
# c.words_per_row=2
|
||||
# OPTS.use_tech_delay_chain_size = True
|
||||
c.recompute_sizes()
|
||||
debug.info(1, "Testing timing for sample 1bit, 16words SRAM with 1 bank")
|
||||
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,
|
||||
'lvsRecognizeGates': 'NONE',
|
||||
}
|
||||
# FIXME: Remove when vdd/gnd connected
|
||||
#'cmnVConnectNamesState' : 'ALL', #connects all nets with the same namee
|
||||
# FIXME: Remove when vdd/gnd connected
|
||||
#'lvsAbortOnSupplyError' : 0
|
||||
# FIXME: Remove when vdd/gnd connected
|
||||
# 'cmnVConnectNamesState' : 'ALL', #connects all nets with the same namee
|
||||
# FIXME: Remove when vdd/gnd connected
|
||||
# 'lvsAbortOnSupplyError' : 0
|
||||
|
||||
if not final_verification or not OPTS.route_supplies:
|
||||
lvs_runset['cmnVConnectReport']=1
|
||||
|
|
@ -115,8 +115,6 @@ def write_lvs_script(cell_name, gds_name, sp_name, final_verification=False, out
|
|||
else:
|
||||
lvs_runset['lvsAbortOnSupplyError']=1
|
||||
|
||||
|
||||
|
||||
# write the runset file
|
||||
f = open(output_path + "lvs_runset", "w")
|
||||
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"
|
||||
f = open(run_file, "w")
|
||||
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],
|
||||
output_path)
|
||||
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"
|
||||
f = open(run_file, "w")
|
||||
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("gds polygon subcell true\n")
|
||||
f.write("gds warning default\n")
|
||||
f.write("gds flatten true\n")
|
||||
f.write("gds readonly true\n")
|
||||
f.write("gds ordering true\n")
|
||||
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))
|
||||
# Flatten the cell to get rid of DRCs spanning multiple layers
|
||||
# (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('puts "Finished loading cell {}"\n'.format(cell_name))
|
||||
f.write("cellname delete \\(UNNAMED\\)\n")
|
||||
f.write("writeall force\n")
|
||||
f.write("select top cell\n")
|
||||
f.write("expand\n")
|
||||
f.write('puts "Finished expanding"\n')
|
||||
f.write("drc check\n")
|
||||
f.write('puts "Finished drc check"\n')
|
||||
f.write("drc catchup\n")
|
||||
f.write('puts "Finished drc catchup"\n')
|
||||
f.write("drc count total\n")
|
||||
f.write("drc count\n")
|
||||
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":
|
||||
f.write(pre + "extract style ngspice(si)\n")
|
||||
f.write(pre + "extract\n")
|
||||
f.write('puts "Finished extract"\n')
|
||||
# f.write(pre + "ext2spice hierarchy on\n")
|
||||
# f.write(pre + "ext2spice scale off\n")
|
||||
# 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.
|
||||
f.write(pre + "ext2spice format ngspice\n")
|
||||
f.write(pre + "ext2spice {}\n".format(cell_name))
|
||||
f.write('puts "Finished ext2spice"\n')
|
||||
f.write("quit -noprompt\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()
|
||||
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"
|
||||
f = open(run_file, "w")
|
||||
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("readnet spice {0}.spice\n".format(cell_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("quit\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()
|
||||
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"
|
||||
f = open(run_file, "w")
|
||||
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 warning default\n")
|
||||
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 {}\n".format(cell_name))
|
||||
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('echo "$(date): Finished PEX using Magic {}"\n'.format(OPTS.drc_exe[1]))
|
||||
f.write("exit $magic_retcode\n")
|
||||
|
||||
f.close()
|
||||
os.system("chmod u+x {}".format(run_file))
|
||||
|
|
|
|||
|
|
@ -11,24 +11,66 @@ Some baseline functions to run scripts.
|
|||
|
||||
import os
|
||||
import debug
|
||||
import subprocess
|
||||
import time
|
||||
from globals import OPTS
|
||||
|
||||
|
||||
def run_script(cell_name, script="lvs"):
|
||||
""" Run script and create output files. """
|
||||
|
||||
echo_cmd_output = OPTS.verbose_level > 1
|
||||
|
||||
cwd = os.getcwd()
|
||||
os.chdir(OPTS.openram_temp)
|
||||
errfile = "{0}{1}.{2}.err".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)
|
||||
|
||||
cmd = "{0}run_{1}.sh 2> {2} 1> {3}".format(OPTS.openram_temp,
|
||||
script,
|
||||
errfile,
|
||||
outfile)
|
||||
debug.info(2, cmd)
|
||||
os.system(cmd)
|
||||
scriptpath = '{0}run_{1}.sh'.format(OPTS.openram_temp, script)
|
||||
|
||||
debug.info(2, "Starting {}".format(scriptpath))
|
||||
start = time.time()
|
||||
with open(outfile, 'wb') as fo, open(errfile, 'wb') as fe:
|
||||
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)
|
||||
|
||||
return (outfile, errfile, resultsfile)
|
||||
|
|
|
|||
Loading…
Reference in New Issue