mirror of
https://github.com/VLSIDA/OpenRAM.git
synced 2026-09-04 00:40:09 +02:00
Merge branch 'dev' into datasheet_gen
This commit is contained in:
@@ -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 = ""
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
+199
-124
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user