mirror of https://github.com/VLSIDA/OpenRAM.git
Merge branch 'dev' of https://github.com/VLSIDA/PrivateRAM into dev
This commit is contained in:
commit
2553439447
|
|
@ -5,3 +5,4 @@
|
|||
*.out
|
||||
*.toc
|
||||
*.synctex.gz
|
||||
**/model_data
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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]
|
||||
|
||||
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue