Added bitcell check to storage nodes.

This commit is contained in:
Hunter Nichols 2019-05-20 18:35:52 -07:00
parent 412f9bb463
commit 099bc4e258
9 changed files with 181 additions and 45 deletions

View File

@ -182,6 +182,23 @@ class spice():
else:
self.spice = []
def check_net_in_spice(self, net_name):
"""Checks if a net name exists in the current. Intended to be check nets in hand-made cells."""
#Remove spaces and lower case then add spaces. Nets are separated by spaces.
net_formatted = ' '+net_name.lstrip().rstrip().lower()+' '
for line in self.spice:
#Lowercase the line and remove any part of the line that is a comment.
line = line.lower().split('*')[0]
#Skip .subckt or .ENDS lines
if line.find('.') == 0:
continue
if net_formatted in line:
return True
return False
def contains(self, mod, modlist):
for x in modlist:
if x.name == mod.name:

View File

@ -20,6 +20,7 @@ class bitcell(design.design):
"""
pin_names = ["bl", "br", "wl", "vdd", "gnd"]
internal_nets = ['Q', 'Qbar']
type_list = ["OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"]
(width,height) = utils.get_libcell_size("cell_6t", GDS["unit"], layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "cell_6t", GDS["unit"])
@ -33,6 +34,7 @@ class bitcell(design.design):
self.height = bitcell.height
self.pin_map = bitcell.pin_map
self.add_pin_types(self.type_list)
self.nets_match = self.check_internal_nets()
def analytical_delay(self, corner, slew, load=0, swing = 0.5):
parasitic_delay = 1
@ -77,6 +79,22 @@ class bitcell(design.design):
total_power = self.return_power(dynamic, leakage)
return total_power
def check_internal_nets(self):
"""For handmade cell, checks sp file contains the storage nodes."""
nets_match = True
for net in self.internal_nets:
nets_match = nets_match and self.check_net_in_spice(net)
return nets_match
def get_storage_net_names(self):
"""Returns names of storage nodes in bitcell in [non-inverting, inverting] format."""
#Checks that they do exist
if self.nets_match:
return self.internal_nets
else:
debug.info(1,"Storage nodes={} not found in spice file.".format(self.internal_nets))
return None
def get_wl_cin(self):
"""Return the relative capacitance of the access transistor gates"""
#This is a handmade cell so the value must be entered in the tech.py file or estimated.

View File

@ -15,7 +15,11 @@ class sram_op(Enum):
READ_ONE = 1
WRITE_ZERO = 2
WRITE_ONE = 3
class bit_polarity(Enum):
NONINVERTING = 0
INVERTING = 1
def relative_compare(value1,value2,error_tolerance=0.001):
""" This is used to compare relative values for convergence. """
return (abs(value1 - value2) / abs(max(value1,value2)) <= error_tolerance)
@ -38,7 +42,6 @@ def parse_spice_list(filename, key):
f.close()
# val = re.search(r"{0}\s*=\s*(-?\d+.?\d*\S*)\s+.*".format(key), contents)
val = re.search(r"{0}\s*=\s*(-?\d+.?\d*[e]?[-+]?[0-9]*\S*)\s+.*".format(key), contents)
if val != None:
debug.info(4, "Key = " + key + " Val = " + val.group(1))
return convert_to_float(val.group(1))

View File

@ -60,7 +60,19 @@ class delay(simulation):
"""Create the measurements used for read and write ports"""
self.read_meas_lists = self.create_read_port_measurement_objects()
self.write_meas_lists = self.create_write_port_measurement_objects()
self.check_meas_names(self.read_meas_lists+self.write_meas_lists)
def check_meas_names(self, measures_lists):
"""Given measurements (in 2d list), checks that their names are unique.
Spice sim will fail otherwise."""
name_set = set()
for meas_list in measures_lists:
for meas in meas_list:
name = meas.name.lower()
debug.check(name not in name_set,("SPICE measurements must have unique names. "
"Duplicate name={}").format(name))
name_set.add(name)
def create_read_port_measurement_objects(self):
"""Create the measurements used for read ports: delays, slews, powers"""
@ -104,6 +116,7 @@ class delay(simulation):
#Other measurements associated with the read port not included in the liberty file
read_measures.append(self.create_bitline_measurement_objects())
read_measures.append(self.create_debug_measurement_objects())
read_measures.append(self.create_read_bit_measures())
return read_measures
@ -164,6 +177,37 @@ class delay(simulation):
self.debug_volt_meas[-1].meta_str = debug_meas.meta_str
return self.debug_delay_meas+self.debug_volt_meas
def create_read_bit_measures(self):
"""Adds bit measurements for read0 and read1 cycles"""
self.bit_meas = {bit_polarity.NONINVERTING:[], bit_polarity.INVERTING:[]}
meas_cycles = (sram_op.READ_ZERO, sram_op.READ_ONE)
for cycle in meas_cycles:
meas_tag = "a{}_b{}_{}".format(self.probe_address, self.probe_data, cycle.name)
single_bit_meas = self.get_bit_measures(meas_tag, self.probe_address, self.probe_data)
for polarity,meas in single_bit_meas.items():
meas.meta_str = cycle
self.bit_meas[polarity].append(meas)
#Dictionary values are lists, reduce to a single list of measurements
return [meas for meas_list in self.bit_meas.values() for meas in meas_list]
def get_bit_measures(self, meas_tag, probe_address, probe_data):
"""Creates measurements for the q/qbar of input bit position.
meas_tag is a unique identifier for the measurement."""
bit_col = self.get_data_bit_column_number(probe_address, probe_data)
bit_row = self.get_address_row_number(probe_address)
(cell_name, cell_inst) = self.sram.get_cell_name(self.sram.name, bit_row, bit_col)
storage_names = cell_inst.mod.get_storage_net_names()
debug.check(len(storage_names) == 2, ("Only inverting/non-inverting storage nodes"
"supported for characterization. Storage nets={}").format(storage_names))
q_name = cell_name+'.'+str(storage_names[0])
qbar_name = cell_name+'.'+str(storage_names[1])
#Bit measures, measurements times to be defined later. The measurement names must be unique
# but they is enforced externally
q_meas = voltage_at_measure("v_q_{}".format(meas_tag), q_name, has_port=False)
qbar_meas = voltage_at_measure("v_qbar_{}".format(meas_tag), qbar_name, has_port=False)
return {bit_polarity.NONINVERTING:q_meas, bit_polarity.INVERTING:qbar_meas}
def set_load_slew(self,load,slew):
""" Set the load and slew """
@ -211,6 +255,11 @@ class delay(simulation):
self.br_name = [bl for bl in preconv_names if 'br' in bl][0]
debug.info(1,"bl_name={}".format(self.bl_name))
(cell_name, cell_inst) = self.sram.get_cell_name(self.sram.name, self.wordline_row, self.bitline_column)
debug.info(1, "cell_name={}".format(cell_name))
def check_arguments(self):
"""Checks if arguments given for write_stimulus() meets requirements"""
try:
@ -350,16 +399,22 @@ class delay(simulation):
"""Checks the measurement object and calls respective function for related measurement inputs."""
meas_type = type(measure_obj)
if meas_type is delay_measure or meas_type is slew_measure:
return self.get_delay_measure_variants(port, measure_obj)
variant_tuple = self.get_delay_measure_variants(port, measure_obj)
elif meas_type is power_measure:
return self.get_power_measure_variants(port, measure_obj, "read")
variant_tuple = self.get_power_measure_variants(port, measure_obj, "read")
elif meas_type is voltage_when_measure:
return self.get_volt_when_measure_variants(port, measure_obj)
variant_tuple = self.get_volt_when_measure_variants(port, measure_obj)
elif meas_type is voltage_at_measure:
return self.get_volt_at_measure_variants(port, measure_obj)
variant_tuple = self.get_volt_at_measure_variants(port, measure_obj)
else:
debug.error("Input function not defined for measurement type={}".format(meas_type))
#Removes port input from any object which does not use it. This shorthand only works if
#the measurement has port as the last input. Could be implemented by measurement type or
#remove entirely from measurement classes.
if not measure_obj.has_port:
variant_tuple = variant_tuple[:-1]
return variant_tuple
def get_delay_measure_variants(self, port, delay_obj):
"""Get the measurement values that can either vary from simulation to simulation (vdd, address) or port to port (time delays)"""
#Return value is intended to match the delay measure format: trig_td, targ_td, vdd, port
@ -567,10 +622,10 @@ class delay(simulation):
#Get measurements from output file
for measure in self.read_lib_meas:
read_port_dict[measure.name] = measure.retrieve_measure(port=port)
debug_passed = self.check_debug_measures(port, read_port_dict)
success = self.check_debug_measures(port, read_port_dict)
success = success and self.check_bit_measures()
#Check timing for read ports. Power is only checked if it was read correctly
if not self.check_valid_delays(read_port_dict) or not debug_passed:
if not self.check_valid_delays(read_port_dict) or not success:
return (False,{})
if not check_dict_values_is_float(read_port_dict):
debug.error("Failed to Measure Read Port Values:\n\t\t{0}".format(read_port_dict),1) #Printing the entire dict looks bad.
@ -648,7 +703,32 @@ class delay(simulation):
return success
def check_bit_measures(self):
"""Checks the measurements which represent the internal storage voltages
at the end of the read cycle."""
success = True
for polarity, meas_list in self.bit_meas.items():
for meas in meas_list:
val = meas.retrieve_measure()
debug.info(1,"{}={}".format(meas.name, val))
if type(val) != float:
continue
meas_cycle = meas.meta_str
if (meas_cycle == sram_op.READ_ZERO and polarity == bit_polarity.NONINVERTING) or\
(meas_cycle == sram_op.READ_ONE and polarity == bit_polarity.INVERTING):
success = val < self.vdd_voltage*.1
elif (meas_cycle == sram_op.READ_ZERO and polarity == bit_polarity.INVERTING) or\
(meas_cycle == sram_op.READ_ONE and polarity == bit_polarity.NONINVERTING):
success = val > self.vdd_voltage*.9
if not success:
debug.info(1,("Wrong value detected on probe bit during read cycle. "
"Check writes and control logic for bugs.\n measure={}, op={}, "
"bit_storage={}").format(meas.name, meas_cycle.name, polarity.name))
return success
def check_bitline_meas(self, v_discharged_bl, v_charged_bl):
"""Checks the value of the discharging bitline. Confirms s_en timing errors.
Returns true if the bitlines are at there expected value."""

View File

@ -13,10 +13,11 @@ from .charutils import *
class spice_measurement(ABC):
"""Base class for spice stimulus measurements."""
def __init__(self, measure_name, measure_scale=None):
def __init__(self, measure_name, measure_scale=None, has_port=True):
#Names must be unique for correct spice simulation, but not enforced here.
self.name = measure_name
self.measure_scale = measure_scale
self.has_port = has_port #Needed for error checking
#Some meta values used externally. variables are added here for consistency accross the objects
self.meta_str = None
self.meta_add_delay = False
@ -35,18 +36,29 @@ class spice_measurement(ABC):
measure_vals = self.get_measure_values(*input_tuple)
measure_func(stim_obj, *measure_vals)
def retrieve_measure(self, port=""):
value = parse_spice_list("timing", "{0}{1}".format(self.name.lower(), port))
def retrieve_measure(self, port=None):
self.port_error_check(port)
if port != None:
value = parse_spice_list("timing", "{0}{1}".format(self.name.lower(), port))
else:
value = parse_spice_list("timing", "{0}".format(self.name.lower()))
if type(value)!=float or self.measure_scale == None:
return value
else:
return value*self.measure_scale
def port_error_check(self, port):
if self.has_port and port == None:
debug.error("Cannot retrieve measurement, port input was expected.",1)
elif not self.has_port and port != None:
debug.error("Unexpected port input received during measure retrieval.",1)
class delay_measure(spice_measurement):
"""Generates a spice measurement for the delay of 50%-to-50% points of two signals."""
def __init__(self, measure_name, trig_name, targ_name, trig_dir_str, targ_dir_str, trig_vdd=0.5, targ_vdd=0.5, measure_scale=None):
spice_measurement.__init__(self, measure_name, measure_scale)
def __init__(self, measure_name, trig_name, targ_name, trig_dir_str, targ_dir_str,\
trig_vdd=0.5, targ_vdd=0.5, measure_scale=None, has_port=True):
spice_measurement.__init__(self, measure_name, measure_scale, has_port)
self.set_meas_constants(trig_name, targ_name, trig_dir_str, targ_dir_str, trig_vdd, targ_vdd)
def get_measure_function(self):
@ -56,10 +68,8 @@ class delay_measure(spice_measurement):
"""Set the constants for this measurement: signal names, directions, and trigger scales"""
self.trig_dir_str = trig_dir_str
self.targ_dir_str = targ_dir_str
self.trig_val_of_vdd = trig_vdd
self.targ_val_of_vdd = targ_vdd
self.trig_name_no_port = trig_name
self.targ_name_no_port = targ_name
@ -67,9 +77,10 @@ class delay_measure(spice_measurement):
def get_measure_values(self, trig_td, targ_td, vdd_voltage, port=None):
"""Constructs inputs to stimulus measurement function. Variant values are inputs here."""
self.port_error_check(port)
trig_val = self.trig_val_of_vdd * vdd_voltage
targ_val = self.targ_val_of_vdd * vdd_voltage
if port != None:
#For dictionary indexing reasons, the name is formatted differently than the signals
meas_name = "{}{}".format(self.name, port)
@ -79,13 +90,12 @@ class delay_measure(spice_measurement):
meas_name = self.name
trig_name = self.trig_name_no_port
targ_name = self.targ_name_no_port
return (meas_name,trig_name,targ_name,trig_val,targ_val,self.trig_dir_str,self.targ_dir_str,trig_td,targ_td)
class slew_measure(delay_measure):
def __init__(self, measure_name, signal_name, slew_dir_str, measure_scale=None):
spice_measurement.__init__(self, measure_name, measure_scale)
def __init__(self, measure_name, signal_name, slew_dir_str, measure_scale=None, has_port=True):
spice_measurement.__init__(self, measure_name, measure_scale, has_port)
self.set_meas_constants(signal_name, slew_dir_str)
def set_meas_constants(self, signal_name, slew_dir_str):
@ -101,7 +111,6 @@ class slew_measure(delay_measure):
self.targ_val_of_vdd = 0.1
else:
debug.error("Unrecognised slew measurement direction={}".format(slew_dir_str),1)
self.trig_name_no_port = signal_name
self.targ_name_no_port = signal_name
@ -110,8 +119,8 @@ class slew_measure(delay_measure):
class power_measure(spice_measurement):
"""Generates a spice measurement for the average power between two time points."""
def __init__(self, measure_name, power_type="", measure_scale=None):
spice_measurement.__init__(self, measure_name, measure_scale)
def __init__(self, measure_name, power_type="", measure_scale=None, has_port=True):
spice_measurement.__init__(self, measure_name, measure_scale, has_port)
self.set_meas_constants(power_type)
def get_measure_function(self):
@ -124,6 +133,7 @@ class power_measure(spice_measurement):
def get_measure_values(self, t_initial, t_final, port=None):
"""Constructs inputs to stimulus measurement function. Variant values are inputs here."""
self.port_error_check(port)
if port != None:
meas_name = "{}{}".format(self.name, port)
else:
@ -133,8 +143,8 @@ class power_measure(spice_measurement):
class voltage_when_measure(spice_measurement):
"""Generates a spice measurement to measure the voltage of a signal based on the voltage of another."""
def __init__(self, measure_name, trig_name, targ_name, trig_dir_str, trig_vdd, measure_scale=None):
spice_measurement.__init__(self, measure_name, measure_scale)
def __init__(self, measure_name, trig_name, targ_name, trig_dir_str, trig_vdd, measure_scale=None, has_port=True):
spice_measurement.__init__(self, measure_name, measure_scale, has_port)
self.set_meas_constants(trig_name, targ_name, trig_dir_str, trig_vdd)
def get_measure_function(self):
@ -144,13 +154,12 @@ class voltage_when_measure(spice_measurement):
"""Sets values useful for power simulations. This value is only meta related to the lib file (rise/fall)"""
self.trig_dir_str = trig_dir_str
self.trig_val_of_vdd = trig_vdd
self.trig_name_no_port = trig_name
self.targ_name_no_port = targ_name
def get_measure_values(self, trig_td, vdd_voltage, port=None):
"""Constructs inputs to stimulus measurement function. Variant values are inputs here."""
self.port_error_check(port)
if port != None:
#For dictionary indexing reasons, the name is formatted differently than the signals
meas_name = "{}{}".format(self.name, port)
@ -160,17 +169,15 @@ class voltage_when_measure(spice_measurement):
meas_name = self.name
trig_name = self.trig_name_no_port
targ_name = self.targ_name_no_port
trig_voltage = self.trig_val_of_vdd*vdd_voltage
trig_voltage = self.trig_val_of_vdd*vdd_voltage
return (meas_name,trig_name,targ_name,trig_voltage,self.trig_dir_str,trig_td)
class voltage_at_measure(spice_measurement):
"""Generates a spice measurement to measure the voltage at a specific time.
The time is considered variant with different periods."""
def __init__(self, measure_name, targ_name, measure_scale=None):
spice_measurement.__init__(self, measure_name, measure_scale)
def __init__(self, measure_name, targ_name, measure_scale=None, has_port=True):
spice_measurement.__init__(self, measure_name, measure_scale, has_port)
self.set_meas_constants(targ_name)
def get_measure_function(self):
@ -182,13 +189,13 @@ class voltage_at_measure(spice_measurement):
def get_measure_values(self, time_at, port=None):
"""Constructs inputs to stimulus measurement function. Variant values are inputs here."""
self.port_error_check(port)
if port != None:
#For dictionary indexing reasons, the name is formatted differently than the signals
meas_name = "{}{}".format(self.name, port)
targ_name = self.targ_name_no_port.format(port)
else:
meas_name = self.name
targ_name = self.targ_name_no_port
targ_name = self.targ_name_no_port
return (meas_name,targ_name,time_at)

View File

@ -1286,4 +1286,8 @@ class bank(design.design):
"""Precharge adds a loop between bitlines, can be excluded to reduce complexity"""
for inst in self.precharge_array_inst:
if inst != None:
self.graph_inst_exclude.add(inst)
self.graph_inst_exclude.add(inst)
def get_cell_name(self, inst_name, row, col):
"""Gets the spice name of the target bitcell."""
return self.bitcell_array_inst.mod.get_cell_name(inst_name+'.x'+self.bitcell_array_inst.name, row, col)

View File

@ -210,4 +210,6 @@ class bitcell_array(design.design):
continue
self.graph_inst_exclude.add(self.cell_inst[row,col])
def get_cell_name(self, inst_name, row, col):
"""Gets the spice name of the target bitcell."""
return inst_name+'.x'+self.cell_inst[row,col].name, self.cell_inst[row,col]

View File

@ -343,9 +343,12 @@ class sram_1bank(sram_base):
debug.error("Signal={} not contained in control logic connections={}"\
.format(sen_name, control_conns))
if sen_name in self.pins:
debug.error("Internal signal={} contained in port list. Name defined by the parent.")
debug.info(1,"pins={}".format(self.pins))
debug.info(1,"cl conns={}".format(control_conns))
debug.error("Internal signal={} contained in port list. Name defined by the parent.")
return "X{}.{}".format(sram_name, sen_name)
def get_cell_name(self, inst_name, row, col):
"""Gets the spice name of the target bitcell."""
#Sanity check in case it was forgotten
if inst_name.find('x') != 0:
inst_name = 'x'+inst_name
return self.bank_inst.mod.get_cell_name(inst_name+'.x'+self.bank_inst.name, row, col)

View File

@ -45,7 +45,9 @@ class timing_sram_test(openram_test):
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)
#import sys
#sys.exit(1)
tempspice = OPTS.openram_temp + "temp.sp"
s.sp_write(tempspice)