Merge branch 'dev' into automated_analytical_model

This commit is contained in:
Hunter Nichols 2020-12-07 14:24:04 -08:00
commit 77d7e3b1cf
39 changed files with 568 additions and 304 deletions

View File

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

View File

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

View File

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

View File

@ -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):
"""

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

14
compiler/printGDS.py Executable file
View File

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

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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)