mirror of https://github.com/VLSIDA/OpenRAM.git
323 lines
14 KiB
Python
323 lines
14 KiB
Python
import sys
|
|
import tech
|
|
import stimuli
|
|
import debug
|
|
import charutils as ch
|
|
import ms_flop
|
|
from globals import OPTS
|
|
|
|
|
|
class setup_hold():
|
|
"""
|
|
Functions to calculate the setup and hold times of the SRAM
|
|
(Bisection Methodology)
|
|
"""
|
|
|
|
def __init__(self):
|
|
# This must match the spice model order
|
|
self.pins = ["data", "dout", "dout_bar", "clk", "vdd", "gnd"]
|
|
self.model_name = "ms_flop"
|
|
self.model_location = OPTS.openram_tech + "sp_lib/ms_flop.sp"
|
|
self.period = tech.spice["feasible_period"]
|
|
self.vdd = tech.spice["supply_voltage"]
|
|
self.gnd = tech.spice["gnd_voltage"]
|
|
|
|
debug.info(2,"Feasible period from technology file: {0} ".format(self.period))
|
|
|
|
|
|
|
|
|
|
def write_stimulus(self, mode, target_time, correct_value):
|
|
"""Creates a stimulus file for SRAM setup/hold time calculation"""
|
|
|
|
# creates and opens the stimulus file for writing
|
|
temp_stim = OPTS.openram_temp + "stim.sp"
|
|
self.sf = open(temp_stim, "w")
|
|
|
|
self.write_header(correct_value)
|
|
|
|
# instantiate the master-slave d-flip-flop
|
|
self.sf.write("* Instantiation of the Master-Slave D-flip-flop\n")
|
|
stimuli.inst_model(stim_file=self.sf,
|
|
pins=self.pins,
|
|
model_name=self.model_name)
|
|
|
|
self.write_data(mode=mode,
|
|
target_time=target_time,
|
|
correct_value=correct_value)
|
|
|
|
self.write_clock()
|
|
|
|
self.write_measures(mode=mode,
|
|
correct_value=correct_value)
|
|
|
|
|
|
stimuli.write_control(self.sf,4*self.period)
|
|
|
|
self.sf.close()
|
|
|
|
def write_header(self, correct_value):
|
|
""" Write the header file with all the models and the power supplies. """
|
|
self.sf.write("* Stimulus for setup/hold: data {0} period {1}n\n".format(correct_value, self.period))
|
|
|
|
# include files in stimulus file
|
|
self.model_list = tech.spice["fet_models"] + [self.model_location]
|
|
stimuli.write_include(stim_file=self.sf,
|
|
models=self.model_list)
|
|
|
|
# add vdd/gnd statements
|
|
self.sf.write("* Global Power Supplies\n")
|
|
stimuli.write_supply(self.sf)
|
|
|
|
|
|
def write_data(self, mode, target_time, correct_value):
|
|
"""Create the data signals for setup/hold analysis. First period is to
|
|
initialize it to the opposite polarity. Second period is used for
|
|
characterization.
|
|
|
|
"""
|
|
self.sf.write("* Generation of the data and clk signals\n")
|
|
incorrect_value = stimuli.get_inverse_value(correct_value)
|
|
if mode=="HOLD":
|
|
init_value = incorrect_value
|
|
start_value = correct_value
|
|
end_value = incorrect_value
|
|
else:
|
|
init_value = incorrect_value
|
|
start_value = incorrect_value
|
|
end_value = correct_value
|
|
|
|
stimuli.gen_pwl(stim_file=self.sf,
|
|
sig_name="data",
|
|
clk_times=[self.period, target_time],
|
|
data_values=[init_value, start_value, end_value],
|
|
period=target_time,
|
|
slew=self.constrained_input_slew,
|
|
setup=0)
|
|
|
|
def write_clock(self):
|
|
""" Create the clock signal for setup/hold analysis. First period initializes the FF
|
|
while the second is used for characterization."""
|
|
|
|
stimuli.gen_pwl(stim_file=self.sf,
|
|
sig_name="clk",
|
|
# initial clk edge is right after the 0 time to initialize a flop
|
|
# without using .IC on an internal node.
|
|
# Return input to value after one period.
|
|
# The second pulse is the characterization one at 2*period
|
|
clk_times=[0.1*self.period,self.period,2*self.period],
|
|
data_values=[0, 1, 0, 1],
|
|
period=2*self.period,
|
|
slew=self.constrained_input_slew,
|
|
setup=0)
|
|
|
|
|
|
|
|
def write_measures(self, mode, correct_value):
|
|
""" Measure statements for setup/hold with right phases. """
|
|
|
|
if correct_value == 1:
|
|
dout_rise_or_fall = "RISE"
|
|
else:
|
|
dout_rise_or_fall = "FALL"
|
|
|
|
# in SETUP mode, the input mirrors what the output should be
|
|
if mode == "SETUP":
|
|
din_rise_or_fall = dout_rise_or_fall
|
|
else:
|
|
# in HOLD mode, however, the input should be opposite of the output
|
|
if correct_value == 1:
|
|
din_rise_or_fall = "FALL"
|
|
else:
|
|
din_rise_or_fall = "RISE"
|
|
|
|
|
|
self.sf.write("* Measure statements for pass/fail verification\n")
|
|
trig_name = "clk"
|
|
targ_name = "dout"
|
|
trig_val = targ_val = 0.5 * self.vdd
|
|
# Start triggers right before the clock edge at 2*period
|
|
stimuli.gen_meas_delay(stim_file=self.sf,
|
|
meas_name="clk2q_delay",
|
|
trig_name=trig_name,
|
|
targ_name=targ_name,
|
|
trig_val=trig_val,
|
|
targ_val=targ_val,
|
|
trig_dir="RISE",
|
|
targ_dir=dout_rise_or_fall,
|
|
td=1.9*self.period)
|
|
|
|
targ_name = "data"
|
|
# Start triggers right after initialize value is returned to normal
|
|
# at one period
|
|
stimuli.gen_meas_delay(stim_file=self.sf,
|
|
meas_name="setup_hold_time",
|
|
trig_name=trig_name,
|
|
targ_name=targ_name,
|
|
trig_val=trig_val,
|
|
targ_val=targ_val,
|
|
trig_dir="RISE",
|
|
targ_dir=din_rise_or_fall,
|
|
td=1.2*self.period)
|
|
|
|
|
|
|
|
|
|
def bidir_search(self, correct_value, mode):
|
|
""" This will perform a bidirectional search for either setup or hold times.
|
|
It starts with the feasible priod and looks a half period beyond or before it
|
|
depending on whether we are doing setup or hold.
|
|
"""
|
|
|
|
# NOTE: The feasible bound is always feasible. This is why they are different for setup and hold.
|
|
# The clock will always be offset by 2*period from the start, so we want to look before and after
|
|
# this time. They are also unbalanced so that the average won't be right on the clock edge in the
|
|
# first iteration.
|
|
if mode == "SETUP":
|
|
feasible_bound = 1.25*self.period
|
|
infeasible_bound = 2.5*self.period
|
|
else:
|
|
infeasible_bound = 1.5*self.period
|
|
feasible_bound = 2.75*self.period
|
|
|
|
# Initial check if reference feasible bound time passes for correct_value, if not, we can't start the search!
|
|
self.write_stimulus(mode=mode,
|
|
target_time=feasible_bound,
|
|
correct_value=correct_value)
|
|
stimuli.run_sim()
|
|
ideal_clk_to_q = ch.convert_to_float(ch.parse_output("timing", "clk2q_delay"))
|
|
setuphold_time = ch.convert_to_float(ch.parse_output("timing", "setup_hold_time"))
|
|
debug.info(2,"*** {0} CHECK: {1} Ideal Clk-to-Q: {2} Setup/Hold: {3}".format(mode, correct_value,ideal_clk_to_q,setuphold_time))
|
|
|
|
if type(ideal_clk_to_q)!=float or type(setuphold_time)!=float:
|
|
debug.error("Initial hold time fails for data value feasible bound {0} Clk-to-Q {1} Setup/Hold {2}".format(feasible_bound,ideal_clk_to_q,setuphold_time),2)
|
|
|
|
if mode == "SETUP": # SETUP is clk-din, not din-clk
|
|
setuphold_time *= -1e9
|
|
else:
|
|
setuphold_time *= 1e9
|
|
|
|
passing_setuphold_time = setuphold_time
|
|
debug.info(2,"Checked initial {0} time {1}, data at {2}, clock at {3} ".format(mode,
|
|
setuphold_time,
|
|
feasible_bound,
|
|
2*self.period))
|
|
#raw_input("Press Enter to continue...")
|
|
|
|
while True:
|
|
target_time = (feasible_bound + infeasible_bound)/2
|
|
self.write_stimulus(mode=mode,
|
|
target_time=target_time,
|
|
correct_value=correct_value)
|
|
|
|
debug.info(2,"{0} value: {1} Target time: {2} Infeasible: {3} Feasible: {4}".format(mode,
|
|
correct_value,
|
|
target_time,
|
|
infeasible_bound,
|
|
feasible_bound))
|
|
|
|
|
|
stimuli.run_sim()
|
|
clk_to_q = ch.convert_to_float(ch.parse_output("timing", "clk2q_delay"))
|
|
setuphold_time = ch.convert_to_float(ch.parse_output("timing", "setup_hold_time"))
|
|
if type(clk_to_q)==float and (clk_to_q<1.1*ideal_clk_to_q) and type(setuphold_time)==float:
|
|
if mode == "SETUP": # SETUP is clk-din, not din-clk
|
|
setuphold_time *= -1e9
|
|
else:
|
|
setuphold_time *= 1e9
|
|
|
|
debug.info(2,"PASS Clk-to-Q: {0} Setup/Hold: {1}".format(clk_to_q,setuphold_time))
|
|
passing_setuphold_time = setuphold_time
|
|
feasible_bound = target_time
|
|
else:
|
|
debug.info(2,"FAIL Clk-to-Q: {0} Setup/Hold: {1}".format(clk_to_q,setuphold_time))
|
|
infeasible_bound = target_time
|
|
|
|
#raw_input("Press Enter to continue...")
|
|
if ch.relative_compare(feasible_bound, infeasible_bound, error_tolerance=0.001):
|
|
debug.info(3,"CONVERGE {0} vs {1}".format(feasible_bound,infeasible_bound))
|
|
break
|
|
|
|
|
|
debug.info(2,"Converged on {0} time {1}.".format(mode,passing_setuphold_time))
|
|
return passing_setuphold_time
|
|
|
|
|
|
def setup_LH_time(self):
|
|
"""Calculates the setup time for low-to-high transition for a DFF
|
|
"""
|
|
return self.bidir_search(1, "SETUP")
|
|
|
|
|
|
def setup_HL_time(self):
|
|
"""Calculates the setup time for high-to-low transition for a DFF
|
|
"""
|
|
return self.bidir_search(0, "SETUP")
|
|
|
|
def hold_LH_time(self):
|
|
"""Calculates the hold time for low-to-high transition for a DFF
|
|
"""
|
|
return self.bidir_search(1, "HOLD")
|
|
|
|
def hold_HL_time(self):
|
|
"""Calculates the hold time for high-to-low transition for a DFF
|
|
"""
|
|
return self.bidir_search(0, "HOLD")
|
|
|
|
|
|
def analyze(self, related_slews, constrained_slews):
|
|
"""main function to calculate both setup and hold time for the
|
|
DFF and returns a dictionary that contains 4 lists for both
|
|
setup/hold times for high_to_low and low_to_high transitions
|
|
for all the slew combinations of the data and clock.
|
|
"""
|
|
LH_setup = []
|
|
HL_setup = []
|
|
LH_hold = []
|
|
HL_hold = []
|
|
for self.related_input_slew in related_slews:
|
|
for self.constrained_input_slew in constrained_slews:
|
|
debug.info(1, "Clock slew: {0} Data slew: {1}".format(self.related_input_slew,self.constrained_input_slew))
|
|
LH_setup_time = self.setup_LH_time()
|
|
debug.info(1, " Setup Time for low_to_high transistion: {0}".format(LH_setup_time))
|
|
HL_setup_time = self.setup_HL_time()
|
|
debug.info(1, " Setup Time for high_to_low transistion: {0}".format(HL_setup_time))
|
|
LH_hold_time = self.hold_LH_time()
|
|
debug.info(1, " Hold Time for low_to_high transistion: {0}".format(LH_hold_time))
|
|
HL_hold_time = self.hold_HL_time()
|
|
debug.info(1, " Hold Time for high_to_low transistion: {0}".format(HL_hold_time))
|
|
LH_setup.append(LH_setup_time)
|
|
HL_setup.append(HL_setup_time)
|
|
LH_hold.append(LH_hold_time)
|
|
HL_hold.append(HL_hold_time)
|
|
|
|
times = {"setup_times_LH": LH_setup,
|
|
"setup_times_HL": HL_setup,
|
|
"hold_times_LH": LH_hold,
|
|
"hold_times_HL": HL_hold
|
|
}
|
|
return times
|
|
|
|
def analytical_model(self,related_slews, constrained_slews):
|
|
""" Just return the fixed setup/hold times from the technology.
|
|
"""
|
|
LH_setup = []
|
|
HL_setup = []
|
|
LH_hold = []
|
|
HL_hold = []
|
|
|
|
for self.related_input_slew in related_slews:
|
|
for self.constrained_input_slew in constrained_slews:
|
|
# convert from ps to ns
|
|
LH_setup.append(tech.spice["msflop_setup"]/1e3)
|
|
HL_setup.append(tech.spice["msflop_setup"]/1e3)
|
|
LH_hold.append(tech.spice["msflop_hold"]/1e3)
|
|
HL_hold.append(tech.spice["msflop_hold"]/1e3)
|
|
|
|
times = {"setup_times_LH": LH_setup,
|
|
"setup_times_HL": HL_setup,
|
|
"hold_times_LH": LH_hold,
|
|
"hold_times_HL": HL_hold
|
|
}
|
|
return times
|