This commit is contained in:
Matt Guthaus 2019-02-13 17:01:41 -08:00
commit 2553439447
27 changed files with 1484 additions and 260 deletions

1
.gitignore vendored
View File

@ -5,3 +5,4 @@
*.out
*.toc
*.synctex.gz
**/model_data

View File

@ -216,7 +216,7 @@ If I forgot to add you, please let me know!
[Github issues]: https://github.com/VLSIDA/PrivateRAM/issues
[Github pull request]: https://github.com/VLSIDA/PrivateRAM/pulls
[Github projects]: https://github.com/VLSIDA/PrivateRAM/projects
[Github projects]: https://github.com/VLSIDA/PrivateRAM
[email me]: mailto:mrg+openram@ucsc.edu
[dev-group]: mailto:openram-dev-group@ucsc.edu

View File

@ -9,6 +9,8 @@ from .functional import *
from .worst_case import *
from .simulation import *
from .bitline_delay import *
from .measurements import *
from .model_check import *
debug.info(1,"Initializing characterizer...")
OPTS.spice_exe = ""

View File

@ -22,11 +22,24 @@ class bitline_delay(delay):
self.period = tech.spice["feasible_period"]
self.is_bitline_measure = True
def create_signal_names(self):
delay.create_signal_names(self)
self.bl_signal_names = ["Xsram.Xbank0.bl", "Xsram.Xbank0.br"]
self.sen_name = "Xsram.s_en"
def create_measurement_names(self):
"""Create measurement names. The names themselves currently define the type of measurement"""
#Altering the names will crash the characterizer. TODO: object orientated approach to the measurements.
self.bitline_meas_names = ["bl_volt", "br_volt"]
self.bl_volt_meas_names = ["volt_bl", "volt_br"]
self.bl_delay_meas_names = ["delay_bl", "delay_br"] #only used in SPICE simulation
self.bl_delay_result_name = "delay_bl_vth" #Used in the return value
def set_probe(self,probe_address, probe_data):
""" Probe address and data can be set separately to utilize other
functions in this characterizer besides analyze."""
delay.set_probe(self,probe_address, probe_data)
self.bitline_column = self.get_data_bit_column_number(probe_address, probe_data)
def write_delay_measures(self):
"""
Write the measure statements to quantify the bitline voltage at sense amp enable 50%.
@ -38,26 +51,52 @@ class bitline_delay(delay):
self.sf.write("* {}\n".format(comment))
for read_port in self.targ_read_ports:
self.write_bitline_measures_read_port(read_port)
self.write_bitline_voltage_measures(read_port)
self.write_bitline_delay_measures(read_port)
def write_bitline_measures_read_port(self, port):
def write_bitline_voltage_measures(self, port):
"""
Add measurments to capture the bitline voltages at 50% Sense amp enable
"""
debug.info(2, "Measuring bitline column={}, port={}".format(self.bitline_column,port))
if len(self.all_ports) == 1: #special naming case for single port sram bitlines
bitline_port = ""
else:
bitline_port = str(port)
sen_port_name = "{}{}".format(self.sen_name,port)
for (measure_name, bl_signal_name) in zip(self.bl_volt_meas_names, self.bl_signal_names):
bl_port_name = "{}{}_{}".format(bl_signal_name, bitline_port, self.bitline_column)
measure_port_name = "{}{}".format(measure_name,port)
self.stim.gen_meas_find_voltage(measure_port_name, sen_port_name, bl_port_name, .5, "RISE", self.cycle_times[self.measure_cycles[port]["read0"]])
def write_bitline_delay_measures(self, port):
"""
Write the measure statements to quantify the delay and power results for a read port.
"""
# add measure statements for delays/slews
measure_bitline = self.get_data_bit_column_number(self.probe_address, self.probe_data)
debug.info(2, "Measuring bitline column={}".format(measure_bitline))
for port in self.targ_read_ports:
if len(self.all_ports) == 1: #special naming case for single port sram bitlines
bitline_port = ""
else:
bitline_port = str(port)
sen_name = "Xsram.s_en{}".format(port)
bl_name = "Xsram.Xbank0.bl{}_{}".format(bitline_port, measure_bitline)
br_name = "Xsram.Xbank0.br{}_{}".format(bitline_port, measure_bitline)
self.stim.gen_meas_find_voltage("bl_volt", sen_name, bl_name, .5, "RISE", self.cycle_times[self.measure_cycles[port]["read0"]])
self.stim.gen_meas_find_voltage("br_volt", sen_name, br_name, .5, "RISE", self.cycle_times[self.measure_cycles[port]["read0"]])
for (measure_name, bl_signal_name) in zip(self.bl_delay_meas_names, self.bl_signal_names):
meas_values = self.get_delay_meas_values(measure_name, bl_signal_name, port)
self.stim.gen_meas_delay(*meas_values)
def get_delay_meas_values(self, delay_name, bitline_name, port):
"""Get the values needed to generate a Spice measurement statement based on the name of the measurement."""
if len(self.all_ports) == 1: #special naming case for single port sram bitlines
bitline_port = ""
else:
bitline_port = str(port)
meas_name="{0}{1}".format(delay_name, port)
targ_name = "{0}{1}_{2}".format(bitline_name,bitline_port,self.bitline_column)
half_vdd = 0.5 * self.vdd_voltage
trig_val = half_vdd
targ_val = self.vdd_voltage-tech.spice["v_threshold_typical"]
trig_name = "clk{0}".format(port)
trig_dir="FALL"
targ_dir="FALL"
#Half period added to delay measurement to negative clock edge
trig_td = targ_td = self.cycle_times[self.measure_cycles[port]["read0"]] + self.period/2
return (meas_name,trig_name,targ_name,trig_val,targ_val,trig_dir,targ_dir,trig_td,targ_td)
def gen_test_cycles_one_port(self, read_port, write_port):
"""Sets a list of key time-points [ns] of the waveform (each rising edge)
@ -89,6 +128,7 @@ class bitline_delay(delay):
self.add_read("R data 0 address {} to check W0 worked".format(self.probe_address),
self.probe_address,data_zeros,read_port)
self.measure_cycles[read_port]["read0"] = len(self.cycle_times)-1
def get_data_bit_column_number(self, probe_address, probe_data):
"""Calculates bitline column number of data bit under test using bit position and mux size"""
if self.sram.col_addr_size>0:
@ -115,19 +155,70 @@ class bitline_delay(delay):
self.stim.run_sim() #running sim prodoces spice output file.
for port in self.targ_read_ports:
bitlines_meas_vals = {}
for mname in self.bitline_meas_names:
bitlines_meas_vals[mname] = parse_spice_list("timing", mname)
#Check that power parsing worked.
for name, val in bitlines_meas_vals.items():
if type(val)!=float:
debug.error("Failed to Parse Bitline Values:\n\t\t{0}".format(bitlines_meas_vals),1) #Printing the entire dict looks bad.
result[port].update(bitlines_meas_vals)
#Parse and check the voltage measurements
bl_volt_meas_dict = {}
for mname in self.bl_volt_meas_names:
mname_port = "{}{}".format(mname,port)
volt_meas_val = parse_spice_list("timing", mname_port)
if type(volt_meas_val)!=float:
debug.error("Failed to Parse Bitline Voltage:\n\t\t{0}={1}".format(mname,volt_meas_val),1)
bl_volt_meas_dict[mname] = volt_meas_val
result[port].update(bl_volt_meas_dict)
#Parse and check the delay measurements. Intended that one measurement will fail, save the delay that did not fail.
bl_delay_meas_dict = {}
values_added = 0 #For error checking
for mname in self.bl_delay_meas_names: #Parse
mname_port = "{}{}".format(mname,port)
delay_meas_val = parse_spice_list("timing", mname_port)
if type(delay_meas_val)==float: #Only add if value is float, do not error.
bl_delay_meas_dict[self.bl_delay_result_name] = delay_meas_val * 1e9 #convert to ns
values_added+=1
debug.check(values_added>0, "Bitline delay measurements failed in SPICE simulation.")
debug.check(values_added<2, "Both bitlines experienced a Vth drop, check simulation results.")
result[port].update(bl_delay_meas_dict)
# The delay is from the negative edge for our SRAM
return (True,result)
def check_bitline_all_results(self, results):
"""Checks the bitline values measured for each tested port"""
for port in self.targ_read_ports:
self.check_bitline_port_results(results[port])
def check_bitline_port_results(self, port_results):
"""Performs three different checks for the bitline values: functionality, bitline swing from vdd, and differential bit swing"""
bl_volt, br_volt = port_results["volt_bl"], port_results["volt_br"]
self.check_functionality(bl_volt,br_volt)
self.check_swing_from_vdd(bl_volt,br_volt)
self.check_differential_swing(bl_volt,br_volt)
def check_functionality(self, bl_volt, br_volt):
"""Checks whether the read failed or not. Measured values are hardcoded with the intention of reading a 0."""
if bl_volt > br_volt:
debug.error("Read failure. Value 1 was read instead of 0.",1)
def check_swing_from_vdd(self, bl_volt, br_volt):
"""Checks difference on discharging bitline from VDD to see if it is within margin of the RBL height parameter."""
if bl_volt < br_volt:
discharge_volt = bl_volt
else:
discharge_volt = br_volt
desired_bl_volt = tech.parameter["rbl_height_percentage"]*self.vdd_voltage
debug.info(1, "Active bitline={:.3f}v, Desired bitline={:.3f}v".format(discharge_volt,desired_bl_volt))
vdd_error_margin = .2 #20% of vdd margin for bitline, a little high for now.
if abs(discharge_volt - desired_bl_volt) > vdd_error_margin*self.vdd_voltage:
debug.warning("Bitline voltage is not within {}% Vdd margin. Delay chain/RBL could need resizing.".format(vdd_error_margin*100))
def check_differential_swing(self, bl_volt, br_volt):
"""This check looks at the difference between the bitline voltages. This needs to be large enough to prevent
sensing errors."""
bitline_swing = abs(bl_volt-br_volt)
debug.info(1,"Bitline swing={:.3f}v".format(bitline_swing))
vdd_error_margin = .2 #20% of vdd margin for bitline, a little high for now.
if bitline_swing < vdd_error_margin*self.vdd_voltage:
debug.warning("Bitline swing less than {}% Vdd margin. Sensing errors more likely to occur.".format(vdd_error_margin))
def analyze(self, probe_address, probe_data, slews, loads):
"""Measures the bitline swing of the differential bitlines (bl/br) at 50% s_en """
self.set_probe(probe_address, probe_data)
@ -141,10 +232,10 @@ class bitline_delay(delay):
debug.info(1,"Bitline swing test: corner {}".format(self.corner))
(success, results)=self.run_delay_simulation()
debug.check(success, "Bitline Failed: period {}".format(self.period))
for mname in self.bitline_meas_names:
bitline_swings[mname] = results[read_port][mname]
debug.info(1,"Bitline values (bl/br): {}".format(bitline_swings))
return bitline_swings
debug.info(1,"Bitline values (voltages/delays):\n\t {}".format(results[read_port]))
self.check_bitline_all_results(results)
return results

View File

@ -80,3 +80,10 @@ def convert_to_float(number):
debug.error("Invalid number: {0}".format(number),1)
return float_value
def check_dict_values_is_float(dict):
"""Checks if all the values are floats. Useful for checking failed Spice measurements."""
for key, value in dict.items():
if type(value)!=float:
return False
return True

View File

@ -8,6 +8,7 @@ from .charutils import *
import utils
from globals import OPTS
from .simulation import simulation
from .measurements import *
class delay(simulation):
"""Functions to measure the delay and power of an SRAM at a given address and
@ -35,17 +36,92 @@ class delay(simulation):
self.period = 0
self.set_load_slew(0,0)
self.set_corner(corner)
self.create_signal_names()
#Create global measure names. Should maybe be an input at some point.
self.create_measurement_names()
def create_measurement_names(self):
"""Create measurement names. The names themselves currently define the type of measurement"""
#Altering the names will crash the characterizer. TODO: object orientated approach to the measurements.
self.delay_meas_names = ["delay_lh", "delay_hl", "slew_lh", "slew_hl"]
self.power_meas_names = ["read0_power", "read1_power", "write0_power", "write1_power"]
self.voltage_when_names = ["volt_bl", "volt_br"]
self.bitline_delay_names = ["delay_bl", "delay_br"]
def create_measurement_objects(self):
"""Create the measurements used for read and write ports"""
self.create_read_port_measurement_objects()
self.create_write_port_measurement_objects()
def create_read_port_measurement_objects(self):
"""Create the measurements used for read ports: delays, slews, powers"""
self.read_meas_objs = []
trig_delay_name = "clk{0}"
targ_name = "{0}{1}_{2}".format(self.dout_name,"{}",self.probe_data) #Empty values are the port and probe data bit
self.read_meas_objs.append(delay_measure("delay_lh", trig_delay_name, targ_name, "RISE", "RISE", measure_scale=1e9))
self.read_meas_objs[-1].meta_str = "read1" #Used to index time delay values when measurements written to spice file.
self.read_meas_objs.append(delay_measure("delay_hl", trig_delay_name, targ_name, "FALL", "FALL", measure_scale=1e9))
self.read_meas_objs[-1].meta_str = "read0"
self.read_meas_objs.append(slew_measure("slew_lh", targ_name, "RISE", measure_scale=1e9))
self.read_meas_objs[-1].meta_str = "read1"
self.read_meas_objs.append(slew_measure("slew_hl", targ_name, "FALL", measure_scale=1e9))
self.read_meas_objs[-1].meta_str = "read0"
self.read_meas_objs.append(power_measure("read1_power", "RISE", measure_scale=1e3))
self.read_meas_objs[-1].meta_str = "read1"
self.read_meas_objs.append(power_measure("read0_power", "FALL", measure_scale=1e3))
self.read_meas_objs[-1].meta_str = "read0"
#This will later add a half-period to the spice time delay. Only for reading 0.
for obj in self.read_meas_objs:
if obj.meta_str is "read0":
obj.meta_add_delay = True
trig_name = "Xsram.s_en{}" #Sense amp enable
if len(self.all_ports) == 1: #special naming case for single port sram bitlines which does not include the port in name
port_format = ""
else:
port_format = "{}"
bl_name = "Xsram.Xbank0.bl{}_{}".format(port_format, self.bitline_column)
br_name = "Xsram.Xbank0.br{}_{}".format(port_format, self.bitline_column)
self.read_meas_objs.append(voltage_when_measure(self.voltage_when_names[0], trig_name, bl_name, "RISE", .5))
self.read_meas_objs.append(voltage_when_measure(self.voltage_when_names[1], trig_name, br_name, "RISE", .5))
#These are read values but need to be separated for unique error checking.
self.create_bitline_delay_measurement_objects()
def create_bitline_delay_measurement_objects(self):
"""Create the measurements used for bitline delay values. Due to unique error checking, these are separated from other measurements.
These measurements are only associated with read values
"""
self.bitline_delay_objs = []
trig_name = "clk{0}"
if len(self.all_ports) == 1: #special naming case for single port sram bitlines which does not include the port in name
port_format = ""
else:
port_format = "{}"
bl_name = "Xsram.Xbank0.bl{}_{}".format(port_format, self.bitline_column)
br_name = "Xsram.Xbank0.br{}_{}".format(port_format, self.bitline_column)
targ_val = (self.vdd_voltage - tech.spice["v_threshold_typical"])/self.vdd_voltage #Calculate as a percentage of vdd
targ_name = "{0}{1}_{2}".format(self.dout_name,"{}",self.probe_data) #Empty values are the port and probe data bit
self.bitline_delay_objs.append(delay_measure(self.bitline_delay_names[0], trig_name, bl_name, "FALL", "FALL", targ_vdd=targ_val, measure_scale=1e9))
self.bitline_delay_objs[-1].meta_str = "read0"
self.bitline_delay_objs.append(delay_measure(self.bitline_delay_names[1], trig_name, br_name, "FALL", "FALL", targ_vdd=targ_val, measure_scale=1e9))
self.bitline_delay_objs[-1].meta_str = "read1"
#Enforces the time delay on the bitline measurements for read0 or read1
for obj in self.bitline_delay_objs:
obj.meta_add_delay = True
def create_write_port_measurement_objects(self):
"""Create the measurements used for read ports: delays, slews, powers"""
self.write_meas_objs = []
self.write_meas_objs.append(power_measure("write1_power", "RISE", measure_scale=1e3))
self.write_meas_objs[-1].meta_str = "write1"
self.write_meas_objs.append(power_measure("write0_power", "FALL", measure_scale=1e3))
self.write_meas_objs[-1].meta_str = "write0"
def create_signal_names(self):
self.addr_name = "A"
self.din_name = "DIN"
@ -198,86 +274,75 @@ class delay(simulation):
self.sf.close()
def get_delay_meas_values(self, delay_name, port):
"""Get the values needed to generate a Spice measurement statement based on the name of the measurement."""
debug.check('lh' in delay_name or 'hl' in delay_name, "Measure command {0} does not contain direction (lh/hl)")
trig_clk_name = "clk{0}".format(port)
meas_name="{0}{1}".format(delay_name, port)
targ_name = "{0}".format("{0}{1}_{2}".format(self.dout_name,port,self.probe_data))
half_vdd = 0.5 * self.vdd_voltage
trig_slew_low = 0.1 * self.vdd_voltage
targ_slew_high = 0.9 * self.vdd_voltage
if 'delay' in delay_name:
trig_val = half_vdd
targ_val = half_vdd
trig_name = trig_clk_name
if 'lh' in delay_name:
trig_dir="RISE"
targ_dir="RISE"
trig_td = targ_td = self.cycle_times[self.measure_cycles[port]["read1"]]
else:
trig_dir="FALL"
targ_dir="FALL"
trig_td = targ_td = self.cycle_times[self.measure_cycles[port]["read0"]]
elif 'slew' in delay_name:
trig_name = targ_name
if 'lh' in delay_name:
trig_val = trig_slew_low
targ_val = targ_slew_high
targ_dir = trig_dir = "RISE"
trig_td = targ_td = self.cycle_times[self.measure_cycles[port]["read1"]]
else:
trig_val = targ_slew_high
targ_val = trig_slew_low
targ_dir = trig_dir = "FALL"
trig_td = targ_td = self.cycle_times[self.measure_cycles[port]["read0"]]
def get_read_measure_variants(self, port, measure_obj):
"""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)
elif meas_type is power_measure:
return 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)
else:
debug.error(1, "Measure command {0} not recognized".format(delay_name))
return (meas_name,trig_name,targ_name,trig_val,targ_val,trig_dir,targ_dir,trig_td,targ_td)
debug.error("Input function not defined for measurement type={}".format(meas_type))
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
#vdd is arguably constant as that is true for a single lib file.
if delay_obj.meta_str == "read0":
#Falling delay are measured starting from neg. clk edge. Delay adjusted to that.
meas_cycle_delay = self.cycle_times[self.measure_cycles[port][delay_obj.meta_str]]
elif delay_obj.meta_str == "read1":
meas_cycle_delay = self.cycle_times[self.measure_cycles[port][delay_obj.meta_str]]
else:
debug.error("Unrecognised delay Index={}".format(delay_obj.meta_str),1)
if delay_obj.meta_add_delay:
meas_cycle_delay += self.period/2
return (meas_cycle_delay, meas_cycle_delay, self.vdd_voltage, port)
def get_power_measure_variants(self, port, power_obj, operation):
"""Get the measurement values that can either vary port to port (time delays)"""
#Return value is intended to match the power measure format: t_initial, t_final, port
t_initial = self.cycle_times[self.measure_cycles[port][power_obj.meta_str]]
t_final = self.cycle_times[self.measure_cycles[port][power_obj.meta_str]+1]
return (t_initial, t_final, port)
def get_volt_when_measure_variants(self, port, power_obj):
"""Get the measurement values that can either vary port to port (time delays)"""
#Only checking 0 value reads for now.
t_trig = meas_cycle_delay = self.cycle_times[self.measure_cycles[port]["read0"]]
return (t_trig, self.vdd_voltage, port)
def write_delay_measures_read_port(self, port):
"""
Write the measure statements to quantify the delay and power results for a read port.
"""
# add measure statements for delays/slews
for dname in self.delay_meas_names:
meas_values = self.get_delay_meas_values(dname, port)
self.stim.gen_meas_delay(*meas_values)
for measure in self.read_meas_objs+self.bitline_delay_objs:
measure_variant_inp_tuple = self.get_read_measure_variants(port, measure)
measure.write_measure(self.stim, measure_variant_inp_tuple)
def get_write_measure_variants(self, port, measure_obj):
"""Checks the measurement object and calls respective function for related measurement inputs."""
meas_type = type(measure_obj)
if meas_type is power_measure:
return self.get_power_measure_variants(port, measure_obj, "write")
else:
debug.error("Input function not defined for measurement type={}".format(meas_type))
# add measure statements for power
for pname in self.power_meas_names:
if "read" not in pname:
continue
#Different naming schemes are used for the measure cycle dict and measurement names.
#TODO: make them the same so they can be indexed the same.
if '1' in pname:
t_initial = self.cycle_times[self.measure_cycles[port]["read1"]]
t_final = self.cycle_times[self.measure_cycles[port]["read1"]+1]
elif '0' in pname:
t_initial = self.cycle_times[self.measure_cycles[port]["read0"]]
t_final = self.cycle_times[self.measure_cycles[port]["read0"]+1]
self.stim.gen_meas_power(meas_name="{0}{1}".format(pname, port),
t_initial=t_initial,
t_final=t_final)
def write_delay_measures_write_port(self, port):
"""
Write the measure statements to quantify the power results for a write port.
"""
# add measure statements for power
for pname in self.power_meas_names:
if "write" not in pname:
continue
t_initial = self.cycle_times[self.measure_cycles[port]["write0"]]
t_final = self.cycle_times[self.measure_cycles[port]["write0"]+1]
if '1' in pname:
t_initial = self.cycle_times[self.measure_cycles[port]["write1"]]
t_final = self.cycle_times[self.measure_cycles[port]["write1"]+1]
self.stim.gen_meas_power(meas_name="{0}{1}".format(pname, port),
t_initial=t_initial,
t_final=t_final)
for measure in self.write_meas_objs:
measure_variant_inp_tuple = self.get_write_measure_variants(port, measure)
measure.write_measure(self.stim, measure_variant_inp_tuple)
def write_delay_measures(self):
"""
@ -385,28 +450,7 @@ class delay(simulation):
previous_period = self.period
debug.info(1, "Found feasible_period: {0}ns".format(self.period))
return feasible_delays
def parse_values(self, values_names, port, mult = 1.0):
"""Parse multiple values in the timing output file. Optional multiplier.
Return a dict of the input names and values. Port used for parsing file.
"""
values = []
all_values_floats = True
for vname in values_names:
#ngspice converts all measure characters to lowercase, not tested on other sims
value = parse_spice_list("timing", "{0}{1}".format(vname.lower(), port))
#Check if any of the values fail to parse
if type(value)!=float:
all_values_floats = False
values.append(value)
#Apply Multiplier only if all values are floats. Let other check functions handle this error.
if all_values_floats:
return {values_names[i]:values[i]*mult for i in range(len(values))}
else:
return {values_names[i]:values[i] for i in range(len(values))}
def run_delay_simulation(self):
"""
This tries to simulate a period and checks if the result works. If
@ -427,33 +471,45 @@ class delay(simulation):
#Too much duplicate code here. Try reducing
for port in self.targ_read_ports:
debug.info(2, "Check delay values for port {}".format(port))
delay_names = [mname for mname in self.delay_meas_names]
delays = self.parse_values(delay_names, port, 1e9) # scale delays to ns
if not self.check_valid_delays(delays):
return (False,{})
result[port].update(delays)
read_port_dict = {}
#Get measurements from output file
for measure in self.read_meas_objs:
read_port_dict[measure.name] = measure.retrieve_measure(port=port)
#Check timing for read ports. Power is only checked if it was read correctly
if not self.check_valid_delays(read_port_dict):
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.
result[port].update(read_port_dict)
bitline_delay_dict = self.evaluate_bitline_delay(port)
result[port].update(bitline_delay_dict)
power_names = [mname for mname in self.power_meas_names if 'read' in mname]
powers = self.parse_values(power_names, port, 1e3) # scale power to mw
#Check that power parsing worked.
for name, power in powers.items():
if type(power)!=float:
debug.error("Failed to Parse Power Values:\n\t\t{0}".format(powers),1) #Printing the entire dict looks bad.
result[port].update(powers)
for port in self.targ_write_ports:
power_names = [mname for mname in self.power_meas_names if 'write' in mname]
powers = self.parse_values(power_names, port, 1e3) # scale power to mw
#Check that power parsing worked.
for name, power in powers.items():
if type(power)!=float:
debug.error("Failed to Parse Power Values:\n\t\t{0}".format(powers),1) #Printing the entire dict looks bad.
result[port].update(powers)
write_port_dict = {}
for measure in self.write_meas_objs:
write_port_dict[measure.name] = measure.retrieve_measure(port=port)
if not check_dict_values_is_float(write_port_dict):
debug.error("Failed to Measure Write Port Values:\n\t\t{0}".format(write_port_dict),1) #Printing the entire dict looks bad.
result[port].update(write_port_dict)
# The delay is from the negative edge for our SRAM
return (True,result)
def evaluate_bitline_delay(self, port):
"""Parse and check the bitline delay. One of the measurements is expected to fail which warrants its own function."""
bl_delay_meas_dict = {}
values_added = 0 #For error checking
for measure in self.bitline_delay_objs:
bl_delay_val = measure.retrieve_measure(port=port)
if type(bl_delay_val) != float or 0 > bl_delay_val or bl_delay_val > self.period/2: #Only add if value is valid, do not error.
debug.error("Bitline delay measurement failed: half-period={}, {}={}".format(self.period/2, measure.name, bl_delay_val),1)
bl_delay_meas_dict[measure.name] = bl_delay_val
return bl_delay_meas_dict
def run_power_simulation(self):
"""
This simulates a disabled SRAM to get the leakage power when it is off.
@ -478,13 +534,13 @@ class delay(simulation):
#key=raw_input("press return to continue")
return (leakage_power*1e3, trim_leakage_power*1e3)
def check_valid_delays(self, delay_dict):
def check_valid_delays(self, result_dict):
""" Check if the measurements are defined and if they are valid. """
#Hard coded names currently
delay_hl = delay_dict["delay_hl"]
delay_lh = delay_dict["delay_lh"]
slew_hl = delay_dict["slew_hl"]
slew_lh = delay_dict["slew_lh"]
delay_hl = result_dict["delay_hl"]
delay_lh = result_dict["delay_lh"]
slew_hl = result_dict["slew_hl"]
slew_lh = result_dict["slew_lh"]
period_load_slew_str = "period {0} load {1} slew {2}".format(self.period,self.load, self.slew)
# if it failed or the read was longer than a period
@ -610,9 +666,22 @@ class delay(simulation):
functions in this characterizer besides analyze."""
self.probe_address = probe_address
self.probe_data = probe_data
self.bitline_column = self.get_data_bit_column_number(probe_address, probe_data)
self.wordline_row = self.get_address_row_number(probe_address)
self.prepare_netlist()
def get_data_bit_column_number(self, probe_address, probe_data):
"""Calculates bitline column number of data bit under test using bit position and mux size"""
if self.sram.col_addr_size>0:
col_address = int(probe_address[0:self.sram.col_addr_size],2)
else:
col_address = 0
bl_column = int(self.sram.words_per_row*probe_data + col_address)
return bl_column
def get_address_row_number(self, probe_address):
"""Calculates wordline row number of data bit under test using address and column mux size"""
return int(probe_address[self.sram.col_addr_size:],2)
def prepare_netlist(self):
""" Prepare a trimmed netlist and regular netlist. """
@ -645,6 +714,9 @@ class delay(simulation):
char_sram_data = {}
self.set_probe(probe_address, probe_data)
self.create_signal_names()
self.create_measurement_names()
self.create_measurement_objects()
self.load=max(loads)
self.slew=max(slews)
@ -828,7 +900,8 @@ class delay(simulation):
"""
if OPTS.num_rw_ports > 1 or OPTS.num_w_ports > 0 and OPTS.num_r_ports > 0:
debug.warning("Analytical characterization results are not supported for multiport.")
self.create_signal_names()
self.create_measurement_names()
power = self.analytical_power(slews, loads)
port_data = self.get_empty_measure_data_dict()
for slew in slews:
@ -845,7 +918,9 @@ class delay(simulation):
port_data[port][mname].append(bank_delay[port].slew/1e3)
else:
debug.error("Measurement name not recognized: {}".format(mname),1)
sram_data = { "min_period": 0,
period_margin = 0.1
risefall_delay = bank_delay[self.read_ports[0]].delay/1e3
sram_data = { "min_period":risefall_delay*2*period_margin,
"leakage_power": power.leakage}
return (sram_data,port_data)
@ -890,7 +965,7 @@ class delay(simulation):
def get_empty_measure_data_dict(self):
"""Make a dict of lists for each type of delay and power measurement to append results to"""
measure_names = self.delay_meas_names + self.power_meas_names
measure_names = self.delay_meas_names + self.power_meas_names + self.voltage_when_names + self.bitline_delay_names
#Create list of dicts. List lengths is # of ports. Each dict maps the measurement names to lists.
measure_data = [{mname:[] for mname in measure_names} for i in self.all_ports]
return measure_data

View File

@ -10,7 +10,8 @@ class logical_effort():
min_inv_cin = 1+beta
pinv=parameter["min_inv_para_delay"]
def __init__(self, size, cin, cout, parasitic, out_is_rise=True):
def __init__(self, name, size, cin, cout, parasitic, out_is_rise=True):
self.name = name
self.cin = cin
self.cout = cout
self.logical_effort = (self.cin/size)/logical_effort.min_inv_cin
@ -19,8 +20,13 @@ class logical_effort():
self.is_rise = out_is_rise
def __str__(self):
return "g=" + str(self.logical_effort) + ", h=" + str(self.eletrical_effort) + ", p=" + str(self.parasitic_scale)+"*pinv, rise_delay="+str(self.is_rise)
return "Name={}, g={}, h={}, p={}*pinv, rise_delay={}".format(self.name,
self.logical_effort,
self.eletrical_effort,
self.parasitic_scale,
self.is_rise
)
def get_stage_effort(self):
return self.logical_effort*self.eletrical_effort
@ -29,6 +35,10 @@ class logical_effort():
def get_stage_delay(self, pinv):
return self.get_stage_effort()+self.get_parasitic_delay(pinv)
def calculate_delays(stage_effort_list, pinv):
"""Convert stage effort objects to list of delay values"""
return [stage.get_stage_delay(pinv) for stage in stage_effort_list]
def calculate_relative_delay(stage_effort_list, pinv=parameter["min_inv_para_delay"]):
"""Calculates the total delay of a given delay path made of a list of logical effort objects."""
@ -40,7 +50,7 @@ def calculate_relative_rise_fall_delays(stage_effort_list, pinv=parameter["min_i
debug.info(2, "Calculating rise/fall relative delays")
total_rise_delay, total_fall_delay = 0,0
for stage in stage_effort_list:
debug.info(3, stage)
debug.info(2, stage)
if stage.is_rise:
total_rise_delay += stage.get_stage_delay(pinv)
else:

View File

@ -0,0 +1,159 @@
import debug
from tech import drc, parameter, spice
from abc import ABC, abstractmethod
from .stimuli import *
from .charutils import *
class spice_measurement(ABC):
"""Base class for spice stimulus measurements."""
def __init__(self, measure_name, measure_scale=None):
#Names must be unique for correct spice simulation, but not enforced here.
self.name = measure_name
self.measure_scale = measure_scale
#Some meta values used externally. variables are added here for consistency accross the objects
self.meta_str = None
self.meta_add_delay = False
@abstractmethod
def get_measure_function(self):
return None
@abstractmethod
def get_measure_values(self):
return None
def write_measure(self, stim_obj, input_tuple):
measure_func = self.get_measure_function()
if measure_func == None:
debug.error("Did not set measure function",1)
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))
if type(value)!=float or self.measure_scale == None:
return value
else:
return value*self.measure_scale
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)
self.set_meas_constants(trig_name, targ_name, trig_dir_str, targ_dir_str, trig_vdd, targ_vdd)
def get_measure_function(self):
return stimuli.gen_meas_delay
def set_meas_constants(self, trig_name, targ_name, trig_dir_str, targ_dir_str, trig_vdd, targ_vdd):
"""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
#Time delays and ports are variant and needed as inputs when writing the 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."""
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)
trig_name = self.trig_name_no_port.format(port)
targ_name = self.targ_name_no_port.format(port)
else:
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)
self.set_meas_constants(signal_name, slew_dir_str)
def set_meas_constants(self, signal_name, slew_dir_str):
"""Set the values needed to generate a Spice measurement statement based on the name of the measurement."""
self.trig_dir_str = slew_dir_str
self.targ_dir_str = slew_dir_str
if slew_dir_str == "RISE":
self.trig_val_of_vdd = 0.1
self.targ_val_of_vdd = 0.9
elif slew_dir_str == "FALL":
self.trig_val_of_vdd = 0.9
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
#Time delays and ports are variant and needed as inputs when writing the measurement
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)
self.set_meas_constants(power_type)
def get_measure_function(self):
return stimuli.gen_meas_power
def set_meas_constants(self, power_type):
"""Sets values useful for power simulations. This value is only meta related to the lib file (rise/fall)"""
#Not needed for power simulation
self.power_type = power_type #Expected to be "RISE"/"FALL"
def get_measure_values(self, t_initial, t_final, port=None):
"""Constructs inputs to stimulus measurement function. Variant values are inputs here."""
if port != None:
meas_name = "{}{}".format(self.name, port)
else:
meas_name = self.name
return (meas_name,t_initial,t_final)
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)
self.set_meas_constants(trig_name, targ_name, trig_dir_str, trig_vdd)
def get_measure_function(self):
return stimuli.gen_meas_find_voltage
def set_meas_constants(self, trig_name, targ_name, trig_dir_str, trig_vdd):
"""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."""
if port != None:
#For dictionary indexing reasons, the name is formatted differently than the signals
meas_name = "{}{}".format(self.name, port)
trig_name = self.trig_name_no_port.format(port)
targ_name = self.targ_name_no_port.format(port)
else:
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
return (meas_name,trig_name,targ_name,trig_voltage,self.trig_dir_str,trig_td)

View File

@ -0,0 +1,336 @@
import sys,re,shutil
import debug
import tech
import math
from .stimuli import *
from .trim_spice import *
from .charutils import *
import utils
from globals import OPTS
from .delay import delay
from .measurements import *
class model_check(delay):
"""Functions to test for the worst case delay in a target SRAM
The current worst case determines a feasible period for the SRAM then tests
several bits and record the delay and differences between the bits.
"""
def __init__(self, sram, spfile, corner):
delay.__init__(self,sram,spfile,corner)
self.period = tech.spice["feasible_period"]
self.create_data_names()
def create_data_names(self):
self.wl_meas_name, self.wl_model_name = "wl_measures", "wl_model"
self.sae_meas_name, self.sae_model_name = "sae_measures", "sae_model"
def create_measurement_names(self):
"""Create measurement names. The names themselves currently define the type of measurement"""
#Create delay measurement names
wl_en_driver_delay_names = ["delay_wl_en_dvr{}_".format(stage) for stage in range(1,self.get_num_wl_en_driver_stages())]
wl_driver_delay_names = ["delay_wl_dvr{}_".format(stage) for stage in range(1,self.get_num_wl_driver_stages())]
sen_driver_delay_names = ["delay_sen_dvr{}_".format(stage) for stage in range(1,self.get_num_sen_driver_stages())]
dc_delay_names = ["delay_delay_chain_stage{}_".format(stage) for stage in range(1,self.get_num_delay_stages()+1)]
self.wl_delay_meas_names = wl_en_driver_delay_names+["delay_wl_en", "delay_wl_bar"]+wl_driver_delay_names+["delay_wl"]
self.rbl_delay_meas_names = ["delay_gated_clk_nand", "delay_delay_chain_in"]+dc_delay_names
self.sae_delay_meas_names = ["delay_pre_sen"]+sen_driver_delay_names+["delay_sen"]
self.delay_chain_indices = (len(self.rbl_delay_meas_names)-len(dc_delay_names), len(self.rbl_delay_meas_names))
#Create slew measurement names
wl_en_driver_slew_names = ["slew_wl_en_dvr{}_".format(stage) for stage in range(1,self.get_num_wl_en_driver_stages())]
wl_driver_slew_names = ["slew_wl_dvr{}_".format(stage) for stage in range(1,self.get_num_wl_driver_stages())]
sen_driver_slew_names = ["slew_sen_dvr{}_".format(stage) for stage in range(1,self.get_num_sen_driver_stages())]
dc_slew_names = ["slew_delay_chain_stage{}_".format(stage) for stage in range(1,self.get_num_delay_stages()+1)]
self.wl_slew_meas_names = ["slew_wl_gated_clk_bar"]+wl_en_driver_slew_names+["slew_wl_en", "slew_wl_bar"]+wl_driver_slew_names+["slew_wl"]
self.rbl_slew_meas_names = ["slew_rbl_gated_clk_bar","slew_gated_clk_nand", "slew_delay_chain_in"]+dc_slew_names
self.sae_slew_meas_names = ["slew_replica_bl0", "slew_pre_sen"]+sen_driver_slew_names+["slew_sen"]
def create_signal_names(self):
"""Creates list of the signal names used in the spice file along the wl and sen paths."""
delay.create_signal_names(self)
#Signal names are all hardcoded, need to update to make it work for probe address and different configurations.
wl_en_driver_signals = ["Xsram.Xcontrol0.Xbuf_wl_en.Zb{}_int".format(stage) for stage in range(1,self.get_num_wl_en_driver_stages())]
wl_driver_signals = ["Xsram.Xbank0.Xwordline_driver0.Xwl_driver_inv{}.Zb{}_int".format(self.wordline_row, stage) for stage in range(1,self.get_num_wl_driver_stages())]
sen_driver_signals = ["Xsram.Xcontrol0.Xbuf_s_en.Zb{}_int".format(stage) for stage in range(1,self.get_num_sen_driver_stages())]
delay_chain_signal_names = ["Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_{}".format(stage) for stage in range(1,self.get_num_delay_stages())]
self.wl_signal_names = ["Xsram.Xcontrol0.gated_clk_bar"]+\
wl_en_driver_signals+\
["Xsram.wl_en0", "Xsram.Xbank0.Xwordline_driver0.wl_bar_{}".format(self.wordline_row)]+\
wl_driver_signals+\
["Xsram.Xbank0.wl_{}".format(self.wordline_row)]
pre_delay_chain_names = ["Xsram.Xcontrol0.gated_clk_bar", "Xsram.Xcontrol0.Xand2_rbl_in.zb_int", "Xsram.Xcontrol0.rbl_in"]
self.rbl_en_signal_names = pre_delay_chain_names+\
delay_chain_signal_names+\
["Xsram.Xcontrol0.Xreplica_bitline.delayed_en"]
self.sae_signal_names = ["Xsram.Xcontrol0.Xreplica_bitline.bl0_0", "Xsram.Xcontrol0.pre_s_en"]+\
sen_driver_signals+\
["Xsram.s_en0"]
def create_measurement_objects(self):
"""Create the measurements used for read and write ports"""
self.create_wordline_measurement_objects()
self.create_sae_measurement_objects()
self.all_measures = self.wl_meas_objs+self.sae_meas_objs
def create_wordline_measurement_objects(self):
"""Create the measurements to measure the wordline path from the gated_clk_bar signal"""
self.wl_meas_objs = []
trig_dir = "RISE"
targ_dir = "FALL"
for i in range(1, len(self.wl_signal_names)):
self.wl_meas_objs.append(delay_measure(self.wl_delay_meas_names[i-1],
self.wl_signal_names[i-1],
self.wl_signal_names[i],
trig_dir,
targ_dir,
measure_scale=1e9))
self.wl_meas_objs.append(slew_measure(self.wl_slew_meas_names[i-1],
self.wl_signal_names[i-1],
trig_dir,
measure_scale=1e9))
temp_dir = trig_dir
trig_dir = targ_dir
targ_dir = temp_dir
self.wl_meas_objs.append(slew_measure(self.wl_slew_meas_names[-1], self.wl_signal_names[-1], trig_dir, measure_scale=1e9))
def create_sae_measurement_objects(self):
"""Create the measurements to measure the sense amp enable path from the gated_clk_bar signal. The RBL splits this path into two."""
self.sae_meas_objs = []
trig_dir = "RISE"
targ_dir = "FALL"
#Add measurements from gated_clk_bar to RBL
for i in range(1, len(self.rbl_en_signal_names)):
self.sae_meas_objs.append(delay_measure(self.rbl_delay_meas_names[i-1],
self.rbl_en_signal_names[i-1],
self.rbl_en_signal_names[i],
trig_dir,
targ_dir,
measure_scale=1e9))
self.sae_meas_objs.append(slew_measure(self.rbl_slew_meas_names[i-1],
self.rbl_en_signal_names[i-1],
trig_dir,
measure_scale=1e9))
temp_dir = trig_dir
trig_dir = targ_dir
targ_dir = temp_dir
self.sae_meas_objs.append(slew_measure(self.rbl_slew_meas_names[-1],
self.rbl_en_signal_names[-1],
trig_dir,
measure_scale=1e9))
#Add measurements from rbl_out to sae. Trigger directions do not invert from previous stage due to RBL.
trig_dir = "FALL"
targ_dir = "RISE"
#Add measurements from gated_clk_bar to RBL
for i in range(1, len(self.sae_signal_names)):
self.sae_meas_objs.append(delay_measure(self.sae_delay_meas_names[i-1],
self.sae_signal_names[i-1],
self.sae_signal_names[i],
trig_dir,
targ_dir,
measure_scale=1e9))
self.sae_meas_objs.append(slew_measure(self.sae_slew_meas_names[i-1],
self.sae_signal_names[i-1],
trig_dir,
measure_scale=1e9))
temp_dir = trig_dir
trig_dir = targ_dir
targ_dir = temp_dir
self.sae_meas_objs.append(slew_measure(self.sae_slew_meas_names[-1],
self.sae_signal_names[-1],
trig_dir,
measure_scale=1e9))
def write_delay_measures(self):
"""
Write the measure statements to quantify the delay and power results for all targeted ports.
"""
self.sf.write("\n* Measure statements for delay and power\n")
# Output some comments to aid where cycles start and what is happening
for comment in self.cycle_comments:
self.sf.write("* {}\n".format(comment))
for read_port in self.targ_read_ports:
self.write_measures_read_port(read_port)
def get_delay_measure_variants(self, port, measure_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
#Assuming only read 0 for now
if not (type(measure_obj) is delay_measure or type(measure_obj) is slew_measure):
debug.error("Measurement not recognized by the model checker.",1)
meas_cycle_delay = self.cycle_times[self.measure_cycles[port]["read0"]] + self.period/2
return (meas_cycle_delay, meas_cycle_delay, self.vdd_voltage, port)
def write_measures_read_port(self, port):
"""
Write the measure statements for all nodes along the wordline path.
"""
# add measure statements for delays/slews
for measure in self.all_measures:
measure_variant_inp_tuple = self.get_delay_measure_variants(port, measure)
measure.write_measure(self.stim, measure_variant_inp_tuple)
def get_measurement_values(self, meas_objs, port):
"""Gets the delays and slews from a specified port from the spice output file and returns them as lists."""
delay_meas_list = []
slew_meas_list = []
for measure in meas_objs:
measure_value = measure.retrieve_measure(port=port)
if type(measure_value) != float:
debug.error("Failed to Measure Value:\n\t\t{}={}".format(measure.name, measure_value),1)
if type(measure) is delay_measure:
delay_meas_list.append(measure_value)
elif type(measure)is slew_measure:
slew_meas_list.append(measure_value)
else:
debug.error("Measurement object not recognized.",1)
return delay_meas_list, slew_meas_list
def run_delay_simulation(self):
"""
This tries to simulate a period and checks if the result works. If
so, it returns True and the delays, slews, and powers. It
works on the trimmed netlist by default, so powers do not
include leakage of all cells.
"""
#Sanity Check
debug.check(self.period > 0, "Target simulation period non-positive")
wl_delay_result = [[] for i in self.all_ports]
wl_slew_result = [[] for i in self.all_ports]
sae_delay_result = [[] for i in self.all_ports]
sae_slew_result = [[] for i in self.all_ports]
# Checking from not data_value to data_value
self.write_delay_stimulus()
self.stim.run_sim() #running sim prodoces spice output file.
#Retrieve the results from the output file
for port in self.targ_read_ports:
#Parse and check the voltage measurements
wl_delay_result[port], wl_slew_result[port] = self.get_measurement_values(self.wl_meas_objs, port)
sae_delay_result[port], sae_slew_result[port] = self.get_measurement_values(self.sae_meas_objs, port)
return (True,wl_delay_result, sae_delay_result, wl_slew_result, sae_slew_result)
def get_model_delays(self, port):
"""Get model delays based on port. Currently assumes single RW port."""
return self.sram.control_logic_rw.get_wl_sen_delays()
def get_num_delay_stages(self):
"""Gets the number of stages in the delay chain from the control logic"""
return len(self.sram.control_logic_rw.replica_bitline.delay_fanout_list)
def get_num_delay_stage_fanout(self):
"""Gets fanout in each stage in the delay chain. Assumes each stage is the same"""
return self.sram.control_logic_rw.replica_bitline.delay_fanout_list[0]
def get_num_wl_en_driver_stages(self):
"""Gets the number of stages in the wl_en driver from the control logic"""
return self.sram.control_logic_rw.wl_en_driver.num_stages
def get_num_sen_driver_stages(self):
"""Gets the number of stages in the sen driver from the control logic"""
return self.sram.control_logic_rw.s_en_driver.num_stages
def get_num_wl_driver_stages(self):
"""Gets the number of stages in the wordline driver from the control logic"""
return self.sram.bank.wordline_driver.inv.num_stages
def scale_delays(self, delay_list):
"""Takes in a list of measured delays and convert it to simple units to easily compare to model values."""
converted_values = []
#Calculate average
total = 0
for meas_value in delay_list:
total+=meas_value
average = total/len(delay_list)
#Convert values
for meas_value in delay_list:
converted_values.append(meas_value/average)
return converted_values
def min_max_normalization(self, value_list):
"""Re-scales input values on a range from 0-1 where min(list)=0, max(list)=1"""
scaled_values = []
min_max_diff = max(value_list) - min(value_list)
average = sum(value_list)/len(value_list)
for value in value_list:
scaled_values.append((value-average)/(min_max_diff))
return scaled_values
def calculate_error_l2_norm(self, list_a, list_b):
"""Calculates error between two lists using the l2 norm"""
error_list = []
for val_a, val_b in zip(list_a, list_b):
error_list.append((val_a-val_b)**2)
return error_list
def compare_measured_and_model(self, measured_vals, model_vals):
"""First scales both inputs into similar ranges and then compares the error between both."""
scaled_meas = self.min_max_normalization(measured_vals)
debug.info(1, "Scaled measurements:\n{}".format(scaled_meas))
scaled_model = self.min_max_normalization(model_vals)
debug.info(1, "Scaled model:\n{}".format(scaled_model))
errors = self.calculate_error_l2_norm(scaled_meas, scaled_model)
debug.info(1, "Errors:\n{}\n".format(errors))
def analyze(self, probe_address, probe_data, slews, loads):
"""Measures entire delay path along the wordline and sense amp enable and compare it to the model delays."""
self.load=max(loads)
self.slew=max(slews)
self.set_probe(probe_address, probe_data)
self.create_signal_names()
self.create_measurement_names()
self.create_measurement_objects()
data_dict = {}
read_port = self.read_ports[0] #only test the first read port
self.targ_read_ports = [read_port]
self.targ_write_ports = [self.write_ports[0]]
debug.info(1,"Model test: corner {}".format(self.corner))
(success, wl_delays, sae_delays, wl_slews, sae_slews)=self.run_delay_simulation()
debug.check(success, "Model measurements Failed: period={}".format(self.period))
wl_model_delays, sae_model_delays = self.get_model_delays(read_port)
debug.info(1,"Measured Wordline delays (ns):\n\t {}".format(wl_delays[read_port]))
debug.info(1,"Wordline model delays:\n\t {}".format(wl_model_delays))
debug.info(1,"Measured Wordline slews:\n\t {}".format(wl_slews[read_port]))
debug.info(1,"Measured SAE delays (ns):\n\t {}".format(sae_delays[read_port]))
debug.info(1,"SAE model delays:\n\t {}".format(sae_model_delays))
debug.info(1,"Measured SAE slews:\n\t {}".format(sae_slews[read_port]))
data_dict[self.wl_meas_name] = wl_delays[read_port]
data_dict[self.wl_model_name] = wl_model_delays
data_dict[self.sae_meas_name] = sae_delays[read_port]
data_dict[self.sae_model_name] = sae_model_delays
#Some evaluations of the model and measured values
debug.info(1, "Comparing wordline measurements and model.")
self.compare_measured_and_model(wl_delays[read_port], wl_model_delays)
debug.info(1, "Comparing SAE measurements and model")
self.compare_measured_and_model(sae_delays[read_port], sae_model_delays)
return data_dict
def get_all_signal_names(self):
"""Returns all signals names as a dict indexed by hardcoded names. Useful for writing the head of the CSV."""
name_dict = {}
#Signal names are more descriptive than the measurement names, first value trimmed to match size of measurements names.
name_dict[self.wl_meas_name] = self.wl_signal_names[1:]
name_dict[self.wl_model_name] = name_dict["wl_measures"] #model uses same names as measured.
name_dict[self.sae_meas_name] = self.rbl_en_signal_names[1:]+self.sae_signal_names[1:]
name_dict[self.sae_model_name] = name_dict["sae_measures"]
return name_dict

View File

@ -30,13 +30,15 @@ class control_logic(design.design):
self.port_type = port_type
self.num_cols = word_size*words_per_row
self.num_words = num_rows * words_per_row
self.num_words = num_rows*words_per_row
self.enable_delay_chain_resizing = False
self.enable_delay_chain_resizing = True
#self.sram=None #disable re-sizing for debugging, FIXME: resizing is not working, needs to be adjusted for new control logic.
self.wl_timing_tolerance = 1 #Determines how much larger the sen delay should be. Accounts for possible error in model.
self.parasitic_inv_delay = parameter["min_inv_para_delay"] #Keeping 0 for now until further testing.
#Determines how much larger the sen delay should be. Accounts for possible error in model.
self.wl_timing_tolerance = 1
self.parasitic_inv_delay = parameter["min_inv_para_delay"]
self.wl_stage_efforts = None
self.sen_stage_efforts = None
if self.port_type == "rw":
self.num_control_signals = 2
@ -130,43 +132,62 @@ class control_logic(design.design):
self.add_mod(self.p_en_bar_driver)
if (self.port_type == "rw") or (self.port_type == "r"):
delay_stages_heuristic, delay_fanout_heuristic = self.get_heuristic_delay_chain_size()
bitcell_loads = int(math.ceil(self.num_rows / 2.0))
self.replica_bitline = factory.create(module_type="replica_bitline",
delay_fanout_list=[delay_fanout_heuristic]*delay_stages_heuristic,
bitcell_loads=bitcell_loads)
if self.sram != None:
self.set_sen_wl_delays()
if self.sram != None and self.enable_delay_chain_resizing and not self.does_sen_total_timing_match(): #check condition based on resizing method
#This resizes to match fall and rise delays, can make the delay chain weird sizes.
# stage_list = self.get_dynamic_delay_fanout_list(delay_stages_heuristic, delay_fanout_heuristic)
# self.replica_bitline = replica_bitline(stage_list, bitcell_loads, name="replica_bitline_resized_"+self.port_type)
#This resizes based on total delay.
delay_stages, delay_fanout = self.get_dynamic_delay_chain_size(delay_stages_heuristic, delay_fanout_heuristic)
from importlib import reload
self.delay_chain_resized = False
c = reload(__import__(OPTS.replica_bitline))
replica_bitline = getattr(c, OPTS.replica_bitline)
bitcell_loads = int(math.ceil(self.num_rows * parameter["rbl_height_percentage"]))
#Use a model to determine the delays with that heuristic
if OPTS.use_tech_delay_chain_size: #Use tech parameters if set.
delay_stages = parameter["static_delay_stages"]
delay_fanout = parameter["static_fanout_per_stage"]
debug.info(1, "Using tech parameters to size delay chain: stages={}, fanout={}".format(delay_stages,delay_fanout))
self.replica_bitline = factory.create(module_type="replica_bitline",
delay_fanout_list=[delay_fanout]*delay_stages,
bitcell_loads=bitcell_loads)
self.sen_delay_rise,self.sen_delay_fall = self.get_delays_to_sen() #get the new timing
if self.sram != None: #Calculate model value even for specified sizes
self.set_sen_wl_delays()
else: #Otherwise, use a heuristic and/or model based sizing.
#First use a heuristic
delay_stages_heuristic, delay_fanout_heuristic = self.get_heuristic_delay_chain_size()
self.replica_bitline = factory.create(module_type="replica_bitline",
delay_fanout_list=[delay_fanout_heuristic]*delay_stages_heuristic,
bitcell_loads=bitcell_loads)
#Resize if necessary, condition depends on resizing method
if self.sram != None and self.enable_delay_chain_resizing and not self.does_sen_rise_fall_timing_match():
#This resizes to match fall and rise delays, can make the delay chain weird sizes.
stage_list = self.get_dynamic_delay_fanout_list(delay_stages_heuristic, delay_fanout_heuristic)
self.replica_bitline = factory.create(module_type="replica_bitline",
delay_fanout_list=stage_list,
bitcell_loads=bitcell_loads)
#This resizes based on total delay.
# delay_stages, delay_fanout = self.get_dynamic_delay_chain_size(delay_stages_heuristic, delay_fanout_heuristic)
# self.replica_bitline = factory.create(module_type="replica_bitline",
# delay_fanout_list=[delay_fanout]*delay_stages,
# bitcell_loads=bitcell_loads)
self.sen_delay_rise,self.sen_delay_fall = self.get_delays_to_sen() #get the new timing
self.delay_chain_resized = True
self.add_mod(self.replica_bitline)
def get_heuristic_delay_chain_size(self):
"""Use a basic heuristic to determine the size of the delay chain used for the Sense Amp Enable """
# FIXME: These should be tuned according to the additional size parameters
delay_fanout = 3 # This can be anything >=2
# Delay stages Must be non-inverting
if self.words_per_row >= 4:
#FIXME: The minimum was 2 fanout, now it will not pass DRC unless it is 3. Why?
delay_fanout = 3 # This can be anything >=3
# Model poorly captures delay of the column mux. Be pessismistic for column mux
if self.words_per_row >= 2:
delay_stages = 8
elif self.words_per_row == 2:
delay_stages = 6
else:
delay_stages = 4
delay_stages = 2
#Read ports have a shorter s_en delay. The model is not accurate enough to catch this difference
#on certain sram configs.
if self.port_type == "r":
delay_stages+=2
return (delay_stages, delay_fanout)
def set_sen_wl_delays(self):
@ -227,14 +248,25 @@ class control_logic(design.design):
required_delay_rise = self.wl_delay_rise*self.wl_timing_tolerance - (self.sen_delay_rise-previous_delay_chain_delay/2)
debug.info(2,"Required delays from chain: fall={}, rise={}".format(required_delay_fall,required_delay_rise))
#If the fanout is different between rise/fall by this amount. Stage algorithm is made more pessimistic.
WARNING_FANOUT_DIFF = 5
stages_close = False
#The stages need to be equal (or at least a even number of stages with matching rise/fall delays)
while True:
stages_fall = self.calculate_stages_with_fixed_fanout(required_delay_fall,fanout_fall)
stages_rise = self.calculate_stages_with_fixed_fanout(required_delay_rise,fanout_rise)
debug.info(1,"Fall stages={}, rise stages={}".format(stages_fall,stages_rise))
if abs(stages_fall-stages_rise) == 1 and not stages_close:
stages_close = True
safe_fanout_rise = fanout_rise
safe_fanout_fall = fanout_fall
if stages_fall == stages_rise:
break
elif abs(stages_fall-stages_rise) == 1:
elif abs(stages_fall-stages_rise) == 1 and WARNING_FANOUT_DIFF < abs(fanout_fall-fanout_rise):
debug.info(1, "Delay chain fanouts between stages are large. Making chain size larger for safety.")
fanout_rise = safe_fanout_rise
fanout_fall = safe_fanout_fall
break
#There should also be a condition to make sure the fanout does not get too large.
#Otherwise, increase the fanout of delay with the most stages, calculate new stages
@ -819,8 +851,8 @@ class control_logic(design.design):
def get_delays_to_wl(self):
"""Get the delay (in delay units) of the clk to a wordline in the bitcell array"""
debug.check(self.sram.all_mods_except_control_done, "Cannot calculate sense amp enable delay unless all module have been added.")
stage_efforts = self.determine_wordline_stage_efforts()
clk_to_wl_rise,clk_to_wl_fall = logical_effort.calculate_relative_rise_fall_delays(stage_efforts, self.parasitic_inv_delay)
self.wl_stage_efforts = self.determine_wordline_stage_efforts()
clk_to_wl_rise,clk_to_wl_fall = logical_effort.calculate_relative_rise_fall_delays(self.wl_stage_efforts, self.parasitic_inv_delay)
total_delay = clk_to_wl_rise + clk_to_wl_fall
debug.info(1, "Clock to wl delay is rise={:.3f}, fall={:.3f}, total={:.3f} in delay units".format(clk_to_wl_rise, clk_to_wl_fall,total_delay))
return clk_to_wl_rise,clk_to_wl_fall
@ -849,8 +881,8 @@ class control_logic(design.design):
This does not incorporate the delay of the replica bitline.
"""
debug.check(self.sram.all_mods_except_control_done, "Cannot calculate sense amp enable delay unless all module have been added.")
stage_efforts = self.determine_sa_enable_stage_efforts()
clk_to_sen_rise, clk_to_sen_fall = logical_effort.calculate_relative_rise_fall_delays(stage_efforts, self.parasitic_inv_delay)
self.sen_stage_efforts = self.determine_sa_enable_stage_efforts()
clk_to_sen_rise, clk_to_sen_fall = logical_effort.calculate_relative_rise_fall_delays(self.sen_stage_efforts, self.parasitic_inv_delay)
total_delay = clk_to_sen_rise + clk_to_sen_fall
debug.info(1, "Clock to s_en delay is rise={:.3f}, fall={:.3f}, total={:.3f} in delay units".format(clk_to_sen_rise, clk_to_sen_fall,total_delay))
return clk_to_sen_rise, clk_to_sen_fall
@ -881,4 +913,12 @@ class control_logic(design.design):
last_stage_rise = stage_effort_list[-1].is_rise
return stage_effort_list
def get_wl_sen_delays(self):
"""Gets a list of the stages and delays in order of their path."""
if self.sen_stage_efforts == None or self.wl_stage_efforts == None:
debug.error("Model delays not calculated for SRAM.", 1)
wl_delays = logical_effort.calculate_delays(self.wl_stage_efforts, self.parasitic_inv_delay)
sen_delays = logical_effort.calculate_delays(self.sen_stage_efforts, self.parasitic_inv_delay)
return wl_delays, sen_delays

View File

@ -48,4 +48,5 @@ class sense_amp(design.design):
"""Get the relative capacitance of sense amp enable gate cin"""
pmos_cin = parameter["sa_en_pmos_size"]/drc("minwidth_tx")
nmos_cin = parameter["sa_en_nmos_size"]/drc("minwidth_tx")
#sen is connected to 2 pmos isolation TX and 1 nmos per sense amp.
return 2*pmos_cin + nmos_cin

View File

@ -142,4 +142,4 @@ class sense_amp_array(design.design):
def get_en_cin(self):
"""Get the relative capacitance of all the sense amp enable connections in the array"""
sense_amp_en_cin = self.amp.get_en_cin()
return sense_amp_en_cin * self.words_per_row
return sense_amp_en_cin * self.word_size

View File

@ -71,6 +71,9 @@ class options(optparse.Values):
# You can manually specify banks, but it is better to auto-detect it.
num_banks = 1
#Uses the delay chain size in the tech.py file rather automatic sizing.
use_tech_delay_chain_size = False
# These are the default modules that can be over-riden
bank_select = "bank_select"
bitcell_array = "bitcell_array"

View File

@ -196,17 +196,16 @@ class pdriver(pgate.pgate):
def get_stage_efforts(self, external_cout, inp_is_rise=False):
"""Get the stage efforts of the A -> Z path"""
cout_list = {}
cout_list = []
for prev_inv,inv in zip(self.inv_list, self.inv_list[1:]):
cout_list[prev_inv]=inv.get_cin()
cout_list[self.inv_list[-1]]=external_cout
cout_list.append(inv.get_cin())
cout_list.append(external_cout)
stage_effort_list = []
last_inp_is_rise = inp_is_rise
for inv in self.inv_list:
stage = inv.get_stage_effort(cout_list[inv], last_inp_is_rise)
for inv,cout in zip(self.inv_list,cout_list):
stage = inv.get_stage_effort(cout, last_inp_is_rise)
stage_effort_list.append(stage)
last_inp_is_rise = stage.is_rise

View File

@ -292,4 +292,4 @@ class pinv(pgate.pgate):
Optional is_rise refers to the input direction rise/fall. Input inverted by this stage.
"""
parasitic_delay = 1
return logical_effort.logical_effort(self.size, self.get_cin(), cout, parasitic_delay, not inp_is_rise)
return logical_effort.logical_effort(self.name, self.size, self.get_cin(), cout, parasitic_delay, not inp_is_rise)

View File

@ -258,4 +258,4 @@ class pnand2(pgate.pgate):
Optional is_rise refers to the input direction rise/fall. Input inverted by this stage.
"""
parasitic_delay = 2
return logical_effort.logical_effort(self.size, self.get_cin(), cout, parasitic_delay, not inp_is_rise)
return logical_effort.logical_effort(self.name, self.size, self.get_cin(), cout, parasitic_delay, not inp_is_rise)

View File

@ -4,6 +4,7 @@ import debug
from tech import drc, parameter, spice
from vector import vector
from globals import OPTS
import logical_effort
from sram_factory import factory
class pnand3(pgate.pgate):
@ -270,4 +271,4 @@ class pnand3(pgate.pgate):
Optional is_rise refers to the input direction rise/fall. Input inverted by this stage.
"""
parasitic_delay = 3
return logical_effort.logical_effort(self.size, self.get_cin(), cout, parasitic_delay, not inp_is_rise)
return logical_effort.logical_effort(self.name, self.size, self.get_cin(), cout, parasitic_delay, not inp_is_rise)

View File

@ -7,13 +7,13 @@ from sram_factory import factory
class sram_config:
""" This is a structure that is used to hold the SRAM configuration options. """
def __init__(self, word_size, num_words, num_banks=1):
def __init__(self, word_size, num_words, num_banks=1, words_per_row=None):
self.word_size = word_size
self.num_words = num_words
self.num_banks = num_banks
# This will get over-written when we determine the organization
self.words_per_row = None
self.words_per_row = words_per_row
self.compute_sizes()

View File

@ -36,17 +36,21 @@ class control_logic_test(openram_test):
# Check port specific control logic
OPTS.num_rw_ports = 1
OPTS.num_w_ports = 1
OPTS.num_r_ports = 1
OPTS.num_w_ports = 0
OPTS.num_r_ports = 0
debug.info(1, "Testing sample for control_logic for multiport, only write control logic")
a = control_logic.control_logic(num_rows=128, words_per_row=1, word_size=8, port_type="rw")
self.local_check(a)
OPTS.num_rw_ports = 0
OPTS.num_w_ports = 1
debug.info(1, "Testing sample for control_logic for multiport, only write control logic")
a = control_logic.control_logic(num_rows=128, words_per_row=1, word_size=8, port_type="w")
self.local_check(a)
OPTS.num_w_ports = 0
OPTS.num_r_ports = 1
debug.info(1, "Testing sample for control_logic for multiport, only read control logic")
a = control_logic.control_logic(num_rows=128, words_per_row=1, word_size=8, port_type="r")
self.local_check(a)

View File

@ -23,7 +23,7 @@ class timing_sram_test(openram_test):
from importlib import reload
import characterizer
reload(characterizer)
from characterizer import delay, bitline_delay
from characterizer import delay
from sram import sram
from sram_config import sram_config
c = sram_config(word_size=1,
@ -43,37 +43,44 @@ class timing_sram_test(openram_test):
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
d = delay(s.s, tempspice, corner)
bl = bitline_delay(s.s, tempspice, corner)
import tech
loads = [tech.spice["msflop_in_cap"]*4]
slews = [tech.spice["rise_time"]*2]
data, port_data = d.analyze(probe_address, probe_data, slews, loads)
#bitline_swing = bl.analyze(probe_address, probe_data, slews, loads)
#Combine info about port into all data
data.update(port_data[0])
if OPTS.tech_name == "freepdk45":
golden_data = {'delay_hl': [0.2152017],
'delay_lh': [0.2152017],
'leakage_power': 0.0022907,
'min_period': 0.488,
'read0_power': [0.47437749999999995],
'read1_power': [0.45026109999999997],
'slew_hl': [0.0846786],
'slew_lh': [0.0846786],
'write0_power': [0.40809259999999997],
'write1_power': [0.4078904]}
golden_data = {'delay_bl': [0.1980959],
'delay_br': [0.1946091],
'delay_hl': [0.2121267],
'delay_lh': [0.2121267],
'leakage_power': 0.0023761999999999998,
'min_period': 0.43,
'read0_power': [0.5139368],
'read1_power': [0.48940979999999995],
'slew_hl': [0.0516745],
'slew_lh': [0.0516745],
'volt_bl': [0.5374525],
'volt_br': [1.1058],
'write0_power': [0.46267169999999996],
'write1_power': [0.4670826]}
elif OPTS.tech_name == "scn4m_subm":
golden_data = {'delay_hl': [1.4333000000000002],
'delay_lh': [1.4333000000000002],
'leakage_power': 0.0271847,
'min_period': 2.891,
'read0_power': [15.714200000000002],
'read1_power': [14.9848],
'slew_hl': [0.6819276999999999],
'slew_lh': [0.6819276999999999],
'write0_power': [13.9658],
'write1_power': [14.8422]}
golden_data = {'delay_bl': [1.1029],
'delay_br': [0.9656455999999999],
'delay_hl': [1.288],
'delay_lh': [1.288],
'leakage_power': 0.0273896,
'min_period': 2.578,
'read0_power': [16.9996],
'read1_power': [16.2616],
'slew_hl': [0.47891700000000004],
'slew_lh': [0.47891700000000004],
'volt_bl': [4.2155],
'volt_br': [5.8142],
'write0_power': [16.0656],
'write1_power': [16.2616]}
else:
self.assertTrue(False) # other techs fail
# Check if no too many or too few results

View File

@ -51,27 +51,36 @@ class timing_sram_test(openram_test):
data.update(port_data[0])
if OPTS.tech_name == "freepdk45":
golden_data = {'delay_hl': [0.221699],
'delay_lh': [0.221699],
'leakage_power': 0.001467648,
'min_period': 0.605,
'read0_power': [0.3879335],
'read1_power': [0.3662724],
'slew_hl': [0.08562444999999999],
'slew_lh': [0.08562444999999999],
'write0_power': [0.3362456],
'write1_power': [0.3372035]}
golden_data = {'delay_bl': [0.2003652],
'delay_br': [0.198698],
'delay_hl': [0.2108836],
'delay_lh': [0.2108836],
'leakage_power': 0.001564799,
'min_period': 0.508,
'read0_power': [0.43916689999999997],
'read1_power': [0.4198608],
'slew_hl': [0.0455126],
'slew_lh': [0.0455126],
'volt_bl': [0.6472883],
'volt_br': [1.114024],
'write0_power': [0.40681890000000004],
'write1_power': [0.4198608]}
elif OPTS.tech_name == "scn4m_subm":
golden_data = {'delay_hl': [1.7951730000000001],
'delay_lh': [1.7951730000000001],
'leakage_power': 0.001669513,
'min_period': 3.594,
'read0_power': [17.03022],
'read1_power': [16.55897],
'slew_hl': [0.7079951],
'slew_lh': [0.7079951],
'write0_power': [15.16726],
'write1_power': [16.13527]}
golden_data = {'delay_bl': [1.3937359999999999],
'delay_br': [1.2596429999999998],
'delay_hl': [1.5747600000000002],
'delay_lh': [1.5747600000000002],
'leakage_power': 0.00195795,
'min_period': 3.281,
'read0_power': [14.92874],
'read1_power': [14.369810000000001],
'slew_hl': [0.49631959999999997],
'slew_lh': [0.49631959999999997],
'volt_bl': [4.132618],
'volt_br': [5.573099],
'write0_power': [13.79953],
'write1_power': [14.369810000000001]}
else:
self.assertTrue(False) # other techs fail

View File

@ -0,0 +1,64 @@
#!/usr/bin/env python3
"""
Run a regression test on various srams
"""
import unittest
from testutils import header,openram_test
import sys,os
sys.path.append(os.path.join(sys.path[0],".."))
import globals
from globals import OPTS
import debug
class delay_model_test(openram_test):
def runTest(self):
globals.init_openram("config_20_{0}".format(OPTS.tech_name))
OPTS.spice_name="hspice"
OPTS.analytical_delay = False
OPTS.netlist_only = True
OPTS.trim_netlist = False
debug.info(1, "Trimming disabled for this test. Simulation could be slow.")
# This is a hack to reload the characterizer __init__ with the spice version
from importlib import reload
import characterizer
reload(characterizer)
from characterizer import model_check
from sram import sram
from sram_config import sram_config
c = sram_config(word_size=4,
num_words=16,
num_banks=1)
c.words_per_row=1
c.recompute_sizes()
debug.info(1, "Testing timing for sample 1bit, 16words SRAM with 1 bank")
s = sram(c, name="sram1")
tempspice = OPTS.openram_temp + "temp.sp"
s.sp_write(tempspice)
probe_address = "1" * s.s.addr_size
probe_data = s.s.word_size - 1
debug.info(1, "Probe address {0} probe data bit {1}".format(probe_address, probe_data))
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
mc = model_check(s.s, tempspice, corner)
import tech
loads = [tech.spice["msflop_in_cap"]*4]
slews = [tech.spice["rise_time"]*2]
sram_data = mc.analyze(probe_address, probe_data, slews, loads)
#Combine info about port into all data
#debug.info(1,"Data:\n{}".format(wl_data))
globals.end_openram()
# run the test from the command line
if __name__ == "__main__":
(OPTS, args) = globals.parse_args()
del sys.argv[1:]
header(__file__, OPTS.tech_name)
unittest.main()

11
compiler/tests/config_data.py Executable file
View File

@ -0,0 +1,11 @@
#Config file used for collecting data.
word_size = 1
num_words = 16
tech_name = "freepdk45"
#process_corners = ["TT", "FF", "SS", "SF", "FS"]
process_corners = ["TT", "FF", "SS"]
supply_voltages = [1.0]
temperatures = [25]

View File

@ -0,0 +1,384 @@
#!/usr/bin/env python3
"""
Run a regression test on various srams
"""
import csv,sys,os
import pandas as pd
import matplotlib.pyplot as plt
import unittest
from testutils import header,openram_test
sys.path.append(os.path.join(sys.path[0],".."))
import globals
from globals import OPTS
import debug
from sram import sram
from sram_config import sram_config
MODEL_DIR = "model_data/"
class data_collection(openram_test):
def runTest(self):
#Uncomment this for model evaluation
# ratio_data = self.calculate_delay_ratios_of_srams()
# self.display_data(ratio_data)
self.run_delay_chain_analysis()
globals.end_openram()
def run_delay_chain_analysis(self):
"""Generates sram with different delay chain configs over different corners and
analyzes delay average and variation."""
OPTS.use_tech_delay_chain_size = True
#Constant sram config for this test
word_size, num_words, words_per_row = 1, 16, 1
#Only change delay chain
dc_config_list = [(2,3), (3,3), (3,4), (4,2), (4,3), (4,4), (2,4), (2,5)]
#dc_config_list = [(2,3), (3,3)]
dc_avgs = []
dc_vars = []
for stages,fanout in dc_config_list:
self.init_data_gen()
self.set_delay_chain(stages,fanout)
self.save_data_sram_corners(word_size, num_words, words_per_row)
wl_dataframe, sae_dataframe = self.get_csv_data()
delay_sums = self.get_delay_chain_sums(sae_dataframe)
dc_avgs.append(self.get_average(delay_sums))
dc_vars.append(self.get_variance(delay_sums))
debug.info(1,"DC config={}: avg={} variance={}".format((stages,fanout), dc_avgs[-1], dc_vars[-1]))
#plot data
self.plot_two_data_sets(dc_config_list, dc_avgs, dc_vars)
#self.plot_data(dc_config_list, dc_avgs)
#self.plot_data(dc_config_list, dc_vars)
def get_delay_chain_sums(self, sae_dataframe):
"""Calculate the total delay of the delay chain over different corners"""
(start_dc, end_dc) = self.delay_obj.delay_chain_indices
start_data_pos = len(self.config_fields)+1 #items before this point are configuration related
delay_sums = []
row_count = 0
#Get delay sums over different corners
for sae_row in sae_dataframe.itertuples():
dc_delays = sae_row[start_data_pos+start_dc:start_data_pos+end_dc]
delay_sums.append(sum(dc_delays))
return delay_sums
def get_variance(self, nums):
avg = self.get_average(nums)
delay_variance = sum((xi - avg) ** 2 for xi in nums) / len(nums)
return delay_variance
def get_average(self,nums):
return sum(nums) / len(nums)
def plot_data(self, x_labels, y_values):
"""Display a plot using matplot lib.
Assumes input x values are just labels and y values are actual data."""
data_range = [i+1 for i in range(len(x_labels))]
plt.xticks(data_range, x_labels)
plt.plot(data_range, y_values, 'ro')
plt.show()
def plot_two_data_sets(self, x_labels, y1_values, y2_values):
"""Plots two data sets on the same x-axis."""
data_range = [i for i in range(len(x_labels))]
fig, ax1 = plt.subplots()
color = 'tab:red'
ax1.set_xlabel('DC (Stages,Fanout)')
ax1.set_ylabel('Average Delay (ns)', color=color)
ax1.plot(data_range, y1_values, marker='o', color=color, linestyle='')
ax1.tick_params(axis='y', labelcolor=color)
ax2 = ax1.twinx() # instantiate a second axes that shares the same x-axis
color = 'tab:blue'
#ax2.set_xticks(data_range, x_labels)
ax2.set_ylabel('Delay Variance (ns)', color=color) # we already handled the x-label with ax1
ax2.plot(data_range, y2_values, marker='*', color=color, linestyle='')
ax2.tick_params(axis='y', labelcolor=color)
fig.tight_layout() # otherwise the right y-label is slightly clipped
plt.xticks(data_range, x_labels)
plt.show()
def calculate_delay_ratios_of_srams(self):
"""Runs delay measurements on several sram configurations.
Computes the delay ratio for each one."""
delay_ratio_data = {}
config_tuple_list = [(32, 1024, None)]
#config_tuple_list = [(1, 16, 1),(4, 16, 1), (16, 16, 1), (32, 32, 1)]
for sram_config in config_tuple_list:
word_size, num_words, words_per_row = sram_config
self.init_data_gen()
self.save_data_sram_corners(word_size, num_words, words_per_row)
model_delay_ratios, meas_delay_ratios, ratio_error = self.compare_model_to_measure()
delay_ratio_data[sram_config] = ratio_error
debug.info(1, "Ratio percentage error={}".format(ratio_error))
return delay_ratio_data
def get_csv_data(self):
"""Hardcoded Hack to get the measurement data from the csv into lists. """
wl_files_name = [file_name for file_name in self.file_names if "wl_measures" in file_name][0]
sae_files_name = [file_name for file_name in self.file_names if "sae_measures" in file_name][0]
wl_dataframe = pd.read_csv(wl_files_name,encoding='utf-8')
sae_dataframe = pd.read_csv(sae_files_name,encoding='utf-8')
return wl_dataframe,sae_dataframe
def evaluate_data(self, wl_dataframe, sae_dataframe):
"""Analyze the delay error and variation error"""
delay_error = self.calculate_delay_error(wl_dataframe, sae_dataframe)
debug.info(1, "Delay errors:{}".format(delay_error))
variation_error = self.calculate_delay_variation_error(wl_dataframe, sae_dataframe)
debug.info(1, "Variation errors:{}".format(variation_error))
def compare_model_to_measure(self):
"""Uses the last 4 recent data sets (wl_meas, sen_meas, wl_model, sen_model)
and compare the wl-sen delay ratio between model and measured.
"""
model_delay_ratios = {}
meas_delay_ratios = {}
ratio_error = {}
#The full file name contains unrelated portions, separate them into the four that are needed
wl_meas_df = [pd.read_csv(file_name,encoding='utf-8') for file_name in self.file_names if "wl_measures" in file_name][0]
sae_meas_df = [pd.read_csv(file_name,encoding='utf-8') for file_name in self.file_names if "sae_measures" in file_name][0]
wl_model_df = [pd.read_csv(file_name,encoding='utf-8') for file_name in self.file_names if "wl_model" in file_name][0]
sae_model_df = [pd.read_csv(file_name,encoding='utf-8') for file_name in self.file_names if "sae_model" in file_name][0]
#Assume each csv has the same corners (and the same row order), use one of the dfs for corners
proc_pos, volt_pos, temp_pos = wl_meas_df.columns.get_loc('process'), wl_meas_df.columns.get_loc('voltage'), wl_meas_df.columns.get_loc('temp')
wl_sum_pos = wl_meas_df.columns.get_loc('sum')
sae_sum_pos = sae_meas_df.columns.get_loc('sum')
df_zip = zip(wl_meas_df.itertuples(),sae_meas_df.itertuples(),wl_model_df.itertuples(),sae_model_df.itertuples())
for wl_meas,sae_meas,wl_model,sae_model in df_zip:
#Use previously calculated position to index the df row.
corner = (wl_meas[proc_pos+1], wl_meas[volt_pos+1], wl_meas[temp_pos+1])
meas_delay_ratios[corner] = wl_meas[wl_sum_pos+1]/sae_meas[sae_sum_pos+1]
model_delay_ratios[corner] = wl_model[wl_sum_pos+1]/sae_model[sae_sum_pos+1]
#Not using absolute error, positive error means model was larger, negative error means it was smaller.
ratio_error[corner] = 100*(model_delay_ratios[corner]-meas_delay_ratios[corner])/meas_delay_ratios[corner]
return model_delay_ratios, meas_delay_ratios, ratio_error
def display_data(self, data):
"""Displays the ratio data using matplotlib (requires graphics)"""
config_data = []
xticks = []
#Organize data
#First key level if the sram configuration (wordsize, num words, words per row)
for config,corner_data_dict in data.items():
#Second level is the corner data for that configuration.
for corner, corner_data in corner_data_dict.items():
#Right now I am only testing with a single corner, will not work with more than 1 corner
config_data.append(corner_data)
xticks.append("{}b,{}w,{}wpr".format(*config))
#plot data
data_range = [i+1 for i in range(len(data))]
shapes = ['ro', 'bo', 'go', 'co', 'mo']
plt.xticks(data_range, xticks)
plt.plot(data_range, config_data, 'ro')
plt.show()
def calculate_delay_error(self, wl_dataframe, sae_dataframe):
"""Calculates the percentage difference in delays between the wordline and sense amp enable"""
start_data_pos = len(self.config_fields) #items before this point are configuration related
error_list = []
row_count = 0
for wl_row, sae_row in zip(wl_dataframe.itertuples(), sae_dataframe.itertuples()):
debug.info(2, "wl_row:{}".format(wl_row))
wl_sum = sum(wl_row[start_data_pos+1:])
debug.info(2, "wl_sum:{}".format(wl_sum))
sae_sum = sum(sae_row[start_data_pos+1:])
error_list.append(abs((wl_sum-sae_sum)/wl_sum))
return error_list
def calculate_delay_variation_error(self, wl_dataframe, sae_dataframe):
"""Measures a base delay from the first corner then the variations from that base"""
start_data_pos = len(self.config_fields)
variation_error_list = []
count = 0
for wl_row, sae_row in zip(wl_dataframe.itertuples(), sae_dataframe.itertuples()):
if count == 0:
#Create a base delay, variation is defined as the difference between this base
wl_base = sum(wl_row[start_data_pos+1:])
debug.info(1, "wl_sum base:{}".format(wl_base))
sae_base = sum(sae_row[start_data_pos+1:])
variation_error_list.append(0.0)
else:
#Calculate the variation from the respective base and then difference between the variations
wl_sum = sum(wl_row[start_data_pos+1:])
wl_base_diff = abs((wl_base-wl_sum)/wl_base)
sae_sum = sum(sae_row[start_data_pos+1:])
sae_base_diff = abs((sae_base-sae_sum)/sae_base)
variation_diff = abs((wl_base_diff-sae_base_diff)/wl_base_diff)
variation_error_list.append(variation_diff)
count+=1
return variation_error_list
def save_data_sram_corners(self, word_size, num_words, words_per_row):
"""Performs corner analysis on a single SRAM configuration"""
self.create_sram(word_size, num_words, words_per_row)
#Setting to none forces SRAM to determine the value. Must be checked after sram creation
if not words_per_row:
words_per_row = self.sram.s.words_per_row
#Run on one size to initialize CSV writing (csv names come from return value). Strange, but it is okay for now.
corner_gen = self.corner_combination_generator()
init_corner = next(corner_gen)
sram_data = self.get_sram_data(init_corner)
dc_resized = self.was_delay_chain_resized()
self.initialize_csv_file(word_size, num_words, words_per_row)
self.add_sram_data_to_csv(sram_data, word_size, num_words, words_per_row, dc_resized, init_corner)
#Run openRAM for all corners
for corner in corner_gen:
sram_data = self.get_sram_data(corner)
self.add_sram_data_to_csv(sram_data, word_size, num_words, words_per_row, dc_resized, corner)
self.close_files()
debug.info(1,"Data Generated")
def init_data_gen(self):
"""Initialization for the data test to run"""
globals.init_openram("config_data")
from tech import parameter
global parameter
if OPTS.tech_name == "scmos":
debug.warning("Device models not up to date with scn4m technology.")
OPTS.spice_name="hspice" #Much faster than ngspice.
OPTS.trim_netlist = False
OPTS.netlist_only = True
OPTS.analytical_delay = False
#OPTS.use_tech_delay_chain_size = True
# This is a hack to reload the characterizer __init__ with the spice version
from importlib import reload
import characterizer
reload(characterizer)
def set_delay_chain(self, stages, fanout):
"""Force change the parameter in the tech file to specify a delay chain configuration"""
parameter["static_delay_stages"] = stages
parameter["static_fanout_per_stage"] = fanout
def close_files(self):
"""Closes all files stored in the file dict"""
for key,file in self.csv_files.items():
file.close()
def corner_combination_generator(self):
processes = OPTS.process_corners
voltages = OPTS.supply_voltages
temperatures = OPTS.temperatures
"""Generates corner using a combination of values from config file"""
for proc in processes:
for volt in voltages:
for temp in temperatures:
yield (proc, volt, temp)
def get_sram_configs(self):
"""Generate lists of wordsizes, number of words, and column mux size (words per row) to be tested."""
min_word_size = 1
max_word_size = 16
min_num_words_log2 = 4
max_num_words_log2 = 8
word_sizes = [i for i in range(min_word_size,max_word_size+1)]
num_words = [2**i for i in range(min_num_words_log2,max_num_words_log2+1)]
words_per_row = [1]
return word_sizes, num_words, words_per_row
def add_sram_data_to_csv(self, sram_data, word_size, num_words, words_per_row, dc_resized, corner):
"""Writes data to its respective CSV file. There is a CSV for each measurement target
(wordline, sense amp enable, and models)"""
sram_specs = [word_size,num_words,words_per_row,dc_resized,*corner]
for data_name, data_values in sram_data.items():
other_values = self.calculate_other_data_values(data_values)
self.csv_writers[data_name].writerow(sram_specs+sram_data[data_name]+other_values)
debug.info(2,"Data Added to CSV file.")
def calculate_other_data_values(self, sram_data_list):
"""A function to calculate extra values related to the data. Only does the sum for now"""
data_sum = sum(sram_data_list)
return [data_sum]
def initialize_csv_file(self, word_size, num_words, words_per_row):
"""Opens a CSV file and writer for every data set being written (wl/sae measurements and model values)"""
#CSV File writing
header_dict = self.delay_obj.get_all_signal_names()
self.csv_files = {}
self.csv_writers = {}
self.file_names = []
delay_stages = self.delay_obj.get_num_delay_stages()
delay_stage_fanout = self.delay_obj.get_num_delay_stage_fanout()
for data_name, header_list in header_dict.items():
file_name = '{}data_{}b_{}word_{}way_dc{}x{}_{}.csv'.format(MODEL_DIR,
word_size,
num_words,
words_per_row,
delay_stages,
delay_stage_fanout,
data_name)
self.file_names.append(file_name)
self.csv_files[data_name] = open(file_name, 'w')
self.config_fields = ['word_size', 'num_words', 'words_per_row', 'dc_resized', 'process', 'voltage', 'temp']
self.other_data_fields = ['sum']
fields = (*self.config_fields, *header_list, *self.other_data_fields)
self.csv_writers[data_name] = csv.writer(self.csv_files[data_name], lineterminator = '\n')
self.csv_writers[data_name].writerow(fields)
def create_sram(self, word_size, num_words, words_per_row):
"""Generates the SRAM based on input configuration."""
c = sram_config(word_size=word_size,
num_words=num_words,
num_banks=1,
words_per_row=words_per_row)
debug.info(1, "Creating SRAM: {} bit, {} words, with 1 bank".format(word_size, num_words))
self.sram = sram(c, name="sram_{}ws_{}words".format(word_size, num_words))
self.sram_spice = OPTS.openram_temp + "temp.sp"
self.sram.sp_write(self.sram_spice)
def get_sram_data(self, corner):
"""Generates the delay object using the corner and runs a simulation for data."""
from characterizer import model_check
self.delay_obj = model_check(self.sram.s, self.sram_spice, corner)
import tech
#Only 1 at a time
probe_address = "1" * self.sram.s.addr_size
probe_data = self.sram.s.word_size - 1
loads = [tech.spice["msflop_in_cap"]*4]
slews = [tech.spice["rise_time"]*2]
sram_data = self.delay_obj.analyze(probe_address,probe_data,slews,loads)
return sram_data
def remove_lists_from_dict(self, dict):
"""Check all the values in the dict and replaces the list items with its first value."""
#This is useful because the tests performed here only generate 1 value but a list
#with 1 item makes writing it to a csv later harder.
for key in dict.keys():
if type(dict[key]) is list:
if len(dict[key]) > 0:
dict[key] = dict[key][0]
else:
del dict[key]
def was_delay_chain_resized(self):
"""Accesses the dc resize boolean in the control logic module."""
#FIXME:assumes read/write port only
return self.sram.s.control_logic_rw.delay_chain_resized
# instantiate a copdsay of the class to actually run the test
if __name__ == "__main__":
(OPTS, args) = globals.parse_args()
del sys.argv[1:]
header(__file__, OPTS.tech_name)
unittest.main()

View File

@ -21,7 +21,8 @@ class openram_test(unittest.TestCase):
if result != 0:
self.fail("DRC failed: {}".format(w.name))
self.cleanup()
if OPTS.purge_temp:
self.cleanup()
def local_check(self, a, final_verification=False):
@ -62,6 +63,9 @@ class openram_test(unittest.TestCase):
delay_obj.set_load_slew(load, slew)
delay_obj.set_probe(probe_address="1"*sram.addr_size, probe_data=(sram.word_size-1))
test_port = delay_obj.read_ports[0] #Only test one port, assumes other ports have similar period.
delay_obj.create_signal_names()
delay_obj.create_measurement_names()
delay_obj.create_measurement_objects()
delay_obj.find_feasible_period_one_port(test_port)
return delay_obj.period

View File

@ -271,7 +271,12 @@ spice["fet_models"] = { "TT" : [SPICE_MODEL_DIR+"/models_nom/PMOS_VTG.inc",SPICE
"FF" : [SPICE_MODEL_DIR+"/models_ff/PMOS_VTG.inc",SPICE_MODEL_DIR+"/models_ff/NMOS_VTG.inc"],
"SF" : [SPICE_MODEL_DIR+"/models_ss/PMOS_VTG.inc",SPICE_MODEL_DIR+"/models_ff/NMOS_VTG.inc"],
"FS" : [SPICE_MODEL_DIR+"/models_ff/PMOS_VTG.inc",SPICE_MODEL_DIR+"/models_ss/NMOS_VTG.inc"],
"SS" : [SPICE_MODEL_DIR+"/models_ss/PMOS_VTG.inc",SPICE_MODEL_DIR+"/models_ss/NMOS_VTG.inc"]}
"SS" : [SPICE_MODEL_DIR+"/models_ss/PMOS_VTG.inc",SPICE_MODEL_DIR+"/models_ss/NMOS_VTG.inc"],
"ST" : [SPICE_MODEL_DIR+"/models_ss/PMOS_VTG.inc",SPICE_MODEL_DIR+"/models_nom/NMOS_VTG.inc"],
"TS" : [SPICE_MODEL_DIR+"/models_nom/PMOS_VTG.inc",SPICE_MODEL_DIR+"/models_ss/NMOS_VTG.inc"],
"FT" : [SPICE_MODEL_DIR+"/models_ff/PMOS_VTG.inc",SPICE_MODEL_DIR+"/models_nom/NMOS_VTG.inc"],
"TF" : [SPICE_MODEL_DIR+"/models_nom/PMOS_VTG.inc",SPICE_MODEL_DIR+"/models_ff/NMOS_VTG.inc"],
}
#spice stimulus related variables
spice["feasible_period"] = 5 # estimated feasible period in ns
@ -328,12 +333,15 @@ spice["nand2_transition_prob"] = .1875 # Transition probability of 2-input na
spice["nand3_transition_prob"] = .1094 # Transition probability of 3-input nand.
spice["nor2_transition_prob"] = .1875 # Transition probability of 2-input nor.
#Logical Effort relative values for the Handmade cells
#Parameters related to sense amp enable timing and delay chain/RBL sizing
parameter["static_delay_stages"] = 4
parameter["static_fanout_per_stage"] = 3
parameter["dff_clk_cin"] = 30.6 #relative capacitance
parameter["6tcell_wl_cin"] = 3 #relative capacitance
parameter["min_inv_para_delay"] = .5 #Tau delay units
parameter["min_inv_para_delay"] = 2.4 #Tau delay units
parameter["sa_en_pmos_size"] = .72 #micro-meters
parameter["sa_en_nmos_size"] = .27 #micro-meters
parameter["rbl_height_percentage"] = .5 #Height of RBL compared to bitcell array
###################################################
##END Spice Simulation Parameters

View File

@ -236,7 +236,12 @@ spice["fet_models"] = { "TT" : [SPICE_MODEL_DIR+"/nom/pmos.sp",SPICE_MODEL_DIR+"
"FF" : [SPICE_MODEL_DIR+"/ff/pmos.sp",SPICE_MODEL_DIR+"/ff/nmos.sp"],
"FS" : [SPICE_MODEL_DIR+"/ff/pmos.sp",SPICE_MODEL_DIR+"/ss/nmos.sp"],
"SF" : [SPICE_MODEL_DIR+"/ss/pmos.sp",SPICE_MODEL_DIR+"/ff/nmos.sp"],
"SS" : [SPICE_MODEL_DIR+"/ss/pmos.sp",SPICE_MODEL_DIR+"/ss/nmos.sp"] }
"SS" : [SPICE_MODEL_DIR+"/ss/pmos.sp",SPICE_MODEL_DIR+"/ss/nmos.sp"],
"ST" : [SPICE_MODEL_DIR+"/ss/pmos.sp",SPICE_MODEL_DIR+"/nom/nmos.sp"],
"TS" : [SPICE_MODEL_DIR+"/nom/pmos.sp",SPICE_MODEL_DIR+"/ss/nmos.sp"],
"FT" : [SPICE_MODEL_DIR+"/ff/pmos.sp",SPICE_MODEL_DIR+"/nom/nmos.sp"],
"TF" : [SPICE_MODEL_DIR+"/nom/pmos.sp",SPICE_MODEL_DIR+"/ff/nmos.sp"],
}
#spice stimulus related variables
@ -295,11 +300,14 @@ spice["nand3_transition_prob"] = .1094 # Transition probability of 3-input na
spice["nor2_transition_prob"] = .1875 # Transition probability of 2-input nor.
#Logical Effort relative values for the Handmade cells
parameter["static_delay_stages"] = 4
parameter["static_fanout_per_stage"] = 3
parameter["dff_clk_cin"] = 27.5
parameter["6tcell_wl_cin"] = 2
parameter["min_inv_para_delay"] = .5
parameter["sa_en_pmos_size"] = 24*_lambda_
parameter["sa_en_nmos_size"] = 9*_lambda_
parameter["rbl_height_percentage"] = .5 #Height of RBL compared to bitcell array
###################################################
##END Spice Simulation Parameters