mirror of https://github.com/VLSIDA/OpenRAM.git
329 lines
13 KiB
Python
329 lines
13 KiB
Python
|
|
import sys
|
||
|
|
import globals
|
||
|
|
import tech
|
||
|
|
import stimuli
|
||
|
|
import debug
|
||
|
|
import charutils as ch
|
||
|
|
import ms_flop
|
||
|
|
|
||
|
|
OPTS = globals.get_opts()
|
||
|
|
|
||
|
|
vdd = tech.spice["supply_voltage"]
|
||
|
|
gnd = tech.spice["gnd_voltage"]
|
||
|
|
|
||
|
|
|
||
|
|
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_buf", "dout", "dout_bar", "clk_buf", "vdd", "gnd"]
|
||
|
|
self.output_name = "dout"
|
||
|
|
self.model_name = "ms_flop"
|
||
|
|
self.model_location = OPTS.openram_tech + "sp_lib/ms_flop.sp"
|
||
|
|
|
||
|
|
|
||
|
|
def check_arguments(self, correct_value, period):
|
||
|
|
"""Checks if given arguments for write_stimulus() meets requirements"""
|
||
|
|
if not isinstance(correct_value, float):
|
||
|
|
if not isinstance(correct_value, int):
|
||
|
|
debug.error("Given correct_value is not a valid number",1)
|
||
|
|
|
||
|
|
if not isinstance(period, float):
|
||
|
|
if not isinstance(period, int):
|
||
|
|
debug.error("Given period is not a valid number",1)
|
||
|
|
|
||
|
|
|
||
|
|
def write_stimulus(self, mode, target_time, correct_value, period, noise_margin):
|
||
|
|
"""Creates a stimulus file for SRAM setup/hold time calculation"""
|
||
|
|
self.check_arguments(correct_value,period)
|
||
|
|
|
||
|
|
# 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, period)
|
||
|
|
|
||
|
|
# 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.sf.write("\n")
|
||
|
|
|
||
|
|
# create a buffer for the inputs
|
||
|
|
self.sf.write("* Buffer subckt\n")
|
||
|
|
stimuli.create_buffer(stim_file=self.sf,
|
||
|
|
buffer_name="buffer",
|
||
|
|
size=[1, 1])
|
||
|
|
self.sf.write("\n")
|
||
|
|
|
||
|
|
self.write_data(mode=mode,
|
||
|
|
target_time=target_time,
|
||
|
|
period=period,
|
||
|
|
correct_value=correct_value)
|
||
|
|
|
||
|
|
self.write_clock(period)
|
||
|
|
|
||
|
|
self.write_measures(mode=mode,
|
||
|
|
correct_value=correct_value,
|
||
|
|
noise_margin=noise_margin,
|
||
|
|
period=period)
|
||
|
|
|
||
|
|
self.write_control(period=period)
|
||
|
|
|
||
|
|
self.sf.close()
|
||
|
|
|
||
|
|
def write_header(self, correct_value, period):
|
||
|
|
""" 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, period))
|
||
|
|
self.sf.write("\n")
|
||
|
|
|
||
|
|
# 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)
|
||
|
|
self.sf.write("\n")
|
||
|
|
|
||
|
|
# add vdd/gnd statements
|
||
|
|
self.sf.write("* Global Power Supplies\n")
|
||
|
|
stimuli.write_supply(stim_file=self.sf,
|
||
|
|
vdd_name=tech.spice["vdd_name"],
|
||
|
|
gnd_name=tech.spice["gnd_name"],
|
||
|
|
vdd_voltage=vdd,
|
||
|
|
gnd_voltage=gnd)
|
||
|
|
self.sf.write("\n")
|
||
|
|
|
||
|
|
|
||
|
|
def write_data(self, mode, period, target_time, correct_value):
|
||
|
|
""" Create the buffered data signals for setup/hold analysis """
|
||
|
|
self.sf.write("* Buffer for the DATA signal\n")
|
||
|
|
stimuli.add_buffer(stim_file=self.sf,
|
||
|
|
buffer_name="buffer",
|
||
|
|
signal_list=["DATA"])
|
||
|
|
self.sf.write("* Generation of the data and clk signals\n")
|
||
|
|
incorrect_value = stimuli.get_inverse_value(correct_value)
|
||
|
|
if mode=="HOLD":
|
||
|
|
start_value = correct_value
|
||
|
|
end_value = incorrect_value
|
||
|
|
else:
|
||
|
|
start_value = incorrect_value
|
||
|
|
end_value = correct_value
|
||
|
|
|
||
|
|
stimuli.gen_pulse(stim_file=self.sf,
|
||
|
|
sig_name="DATA",
|
||
|
|
v1=start_value,
|
||
|
|
v2=end_value,
|
||
|
|
offset=target_time,
|
||
|
|
period=2*period,
|
||
|
|
t_rise=tech.spice["rise_time"],
|
||
|
|
t_fall=tech.spice["fall_time"])
|
||
|
|
self.sf.write("\n")
|
||
|
|
|
||
|
|
def write_clock(self,period):
|
||
|
|
""" Create the buffered clock signal for setup/hold analysis """
|
||
|
|
self.sf.write("* Buffer for the clk signal\n")
|
||
|
|
stimuli.add_buffer(stim_file=self.sf,
|
||
|
|
buffer_name="buffer",
|
||
|
|
signal_list=["clk"])
|
||
|
|
self.sf.write("\n")
|
||
|
|
stimuli.gen_pulse(stim_file=self.sf,
|
||
|
|
sig_name="clk",
|
||
|
|
offset=period,
|
||
|
|
period=period,
|
||
|
|
t_rise=tech.spice["rise_time"],
|
||
|
|
t_fall=tech.spice["fall_time"])
|
||
|
|
self.sf.write("\n")
|
||
|
|
|
||
|
|
|
||
|
|
def write_measures(self, mode, correct_value, noise_margin, period):
|
||
|
|
""" Measure statements for setup/hold with right phases. """
|
||
|
|
|
||
|
|
if correct_value == vdd:
|
||
|
|
max_or_min = "MAX"
|
||
|
|
rise_or_fall = "RISE"
|
||
|
|
else:
|
||
|
|
max_or_min = "MIN"
|
||
|
|
rise_or_fall = "FALL"
|
||
|
|
|
||
|
|
incorrect_value = stimuli.get_inverse_value(correct_value)
|
||
|
|
|
||
|
|
self.sf.write("* Measure statements for pass/fail verification\n")
|
||
|
|
self.sf.write(".IC v({0})={1}\n".format(self.output_name, incorrect_value))
|
||
|
|
#self.sf.write(".MEASURE TRAN {0}VOUT {0} v({1}) GOAL={2}\n".format(max_or_min, output_name, noise_margin))
|
||
|
|
# above is the old cmd for hspice, below is the one work for both
|
||
|
|
self.sf.write(".MEASURE TRAN {0}VOUT {0} v({1}) from ={2}n to ={3}n\n".format(max_or_min,
|
||
|
|
self.output_name,
|
||
|
|
1.5*period,
|
||
|
|
2*period))
|
||
|
|
self.sf.write("\n")
|
||
|
|
|
||
|
|
|
||
|
|
def write_control(self, period):
|
||
|
|
# transient window
|
||
|
|
end_time = 2 * period
|
||
|
|
self.sf.write(".TRAN 1n {0}n\n".format(end_time))
|
||
|
|
self.sf.write(".OPTIONS POST=1 PROBE\n")
|
||
|
|
|
||
|
|
if OPTS.spice_version == "hspice":
|
||
|
|
self.sf.write(".probe V(*)\n")
|
||
|
|
# end the stimulus file
|
||
|
|
self.sf.write(".end\n")
|
||
|
|
else:
|
||
|
|
self.sf.write(".control\n")
|
||
|
|
self.sf.write("run\n")
|
||
|
|
self.sf.write("quit\n")
|
||
|
|
self.sf.write(".endc\n")
|
||
|
|
self.sf.write(".end\n")
|
||
|
|
|
||
|
|
|
||
|
|
def bidir_search(self, correct_value, noise_margin, measure_name, 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.
|
||
|
|
"""
|
||
|
|
period = tech.spice["feasible_period"]
|
||
|
|
|
||
|
|
# The clock will start being offset by a period, so we want to look before and after
|
||
|
|
# theis time.
|
||
|
|
if mode == "HOLD":
|
||
|
|
target_time = 1.5 * period
|
||
|
|
lower_bound = 0.5*period
|
||
|
|
upper_bound = 1.5 * period
|
||
|
|
else:
|
||
|
|
target_time = 0.5 * period
|
||
|
|
lower_bound = 0.5 * period
|
||
|
|
upper_bound = 1.5*period
|
||
|
|
|
||
|
|
previous_time = target_time
|
||
|
|
# Initial Check if reference setup time passes for correct_value
|
||
|
|
self.write_stimulus(mode=mode,
|
||
|
|
target_time=target_time,
|
||
|
|
correct_value=correct_value,
|
||
|
|
period=period,
|
||
|
|
noise_margin=noise_margin)
|
||
|
|
stimuli.run_sim()
|
||
|
|
output_value = ch.convert_to_float(ch.parse_output("timing", measure_name))
|
||
|
|
debug.info(3,"Correct: {0} Output: {1} NM: {2}".format(correct_value,output_value,noise_margin))
|
||
|
|
if mode == "HOLD":
|
||
|
|
setuphold_time = target_time - period
|
||
|
|
else:
|
||
|
|
setuphold_time = period - target_time
|
||
|
|
debug.info(3,"Target time: {0} Low: {1} Up: {2} Measured: {3}".format(target_time,
|
||
|
|
lower_bound,
|
||
|
|
upper_bound,
|
||
|
|
setuphold_time))
|
||
|
|
if not self.pass_fail_test(output_value, correct_value, noise_margin):
|
||
|
|
debug.error("Initial period/target hold time fails for data value",2)
|
||
|
|
|
||
|
|
# We already found it feasible, so advance one step first thing.
|
||
|
|
if mode == "HOLD":
|
||
|
|
target_time -= 0.5 * (upper_bound - lower_bound)
|
||
|
|
else:
|
||
|
|
target_time += 0.5 * (upper_bound - lower_bound)
|
||
|
|
while True:
|
||
|
|
self.write_stimulus(mode=mode,
|
||
|
|
target_time=target_time,
|
||
|
|
correct_value=correct_value,
|
||
|
|
period=period,
|
||
|
|
noise_margin=noise_margin)
|
||
|
|
if mode == "HOLD":
|
||
|
|
setuphold_time = target_time - period
|
||
|
|
else:
|
||
|
|
setuphold_time = period - target_time
|
||
|
|
debug.info(3,"Target time: {0} Low: {1} Up: {2} Measured: {3}".format(target_time,
|
||
|
|
lower_bound,
|
||
|
|
upper_bound,
|
||
|
|
setuphold_time))
|
||
|
|
|
||
|
|
stimuli.run_sim()
|
||
|
|
output_value = ch.convert_to_float(ch.parse_output("timing", measure_name))
|
||
|
|
debug.info(3,"Correct: {0} Output: {1} NM: {2}".format(correct_value,output_value,noise_margin))
|
||
|
|
if self.pass_fail_test(output_value,correct_value,noise_margin):
|
||
|
|
debug.info(3,"PASS")
|
||
|
|
if ch.relative_compare(target_time, previous_time):
|
||
|
|
debug.info(3,"CONVERGE " + str(target_time) + " " + str(previous_time))
|
||
|
|
break
|
||
|
|
previous_time = target_time
|
||
|
|
if mode == "HOLD":
|
||
|
|
upper_bound = target_time
|
||
|
|
target_time -= 0.5 * (upper_bound - lower_bound)
|
||
|
|
else:
|
||
|
|
lower_bound = target_time
|
||
|
|
target_time += 0.5 * (upper_bound - lower_bound)
|
||
|
|
else:
|
||
|
|
debug.info(3,"FAIL")
|
||
|
|
if mode == "HOLD":
|
||
|
|
lower_bound = target_time
|
||
|
|
target_time += 0.5 * (upper_bound - lower_bound)
|
||
|
|
else:
|
||
|
|
upper_bound = target_time
|
||
|
|
target_time -= 0.5 * (upper_bound - lower_bound)
|
||
|
|
#raw_input("Press Enter to continue...")
|
||
|
|
# the clock starts offset by one clock period,
|
||
|
|
# so we always measure our setup or hold relative to this time
|
||
|
|
if mode == "HOLD":
|
||
|
|
setuphold_time = target_time - period
|
||
|
|
else:
|
||
|
|
setuphold_time = period - target_time
|
||
|
|
return setuphold_time
|
||
|
|
|
||
|
|
|
||
|
|
def setup_time(self):
|
||
|
|
"""Calculates the setup time for low-to-high and high-to-low
|
||
|
|
transition for a D-flip-flop"""
|
||
|
|
|
||
|
|
one_found = self.bidir_search(vdd, 0.9*vdd, "maxvout", "SETUP")
|
||
|
|
|
||
|
|
zero_found = self.bidir_search(gnd, 0.1*vdd, "minvout", "SETUP")
|
||
|
|
|
||
|
|
return [one_found, zero_found]
|
||
|
|
|
||
|
|
def hold_time(self):
|
||
|
|
"""Calculates the hold time for low-to-high and high-to-low
|
||
|
|
transition for a D-flip-flop"""
|
||
|
|
|
||
|
|
one_found = self.bidir_search(vdd, 0.9*vdd, "maxvout", "HOLD")
|
||
|
|
|
||
|
|
zero_found = self.bidir_search(gnd, 0.1*vdd, "minvout", "HOLD")
|
||
|
|
|
||
|
|
return [one_found, zero_found]
|
||
|
|
|
||
|
|
|
||
|
|
def pass_fail_test(self,value,correct_value,noise_margin):
|
||
|
|
"""Function to Test if the output value reached the
|
||
|
|
noise_margin to determine if it passed or failed"""
|
||
|
|
if correct_value == vdd:
|
||
|
|
return True if value >= noise_margin else False
|
||
|
|
else:
|
||
|
|
return True if value <= noise_margin else False
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
def analyze(self):
|
||
|
|
"""main function to calculate both setup and hold time for the
|
||
|
|
d-flip-flop returns a dictionary that contains 4 times for both
|
||
|
|
setup/hold times for high_to_low and low_to_high transition
|
||
|
|
dictionary keys: setup_time_one (low_to_high), setup_time_zero
|
||
|
|
(high_to_low), hold_time_one (low_to_high), hold_time_zero
|
||
|
|
(high_to_low)
|
||
|
|
"""
|
||
|
|
|
||
|
|
[one_setup_time, zero_setup_time] = self.setup_time()
|
||
|
|
[one_hold_time, zero_hold_time] = self.hold_time()
|
||
|
|
debug.info(1, "Setup Time for low_to_high transistion: {0}".format(one_setup_time))
|
||
|
|
debug.info(1, "Setup Time for high_to_low transistion: {0}".format(zero_setup_time))
|
||
|
|
debug.info(1, "Hold Time for low_to_high transistion: {0}".format(one_hold_time))
|
||
|
|
debug.info(1, "Hold Time for high_to_low transistion: {0}".format(zero_hold_time))
|
||
|
|
times = {"setup_time_one": one_setup_time,
|
||
|
|
"setup_time_zero": zero_setup_time,
|
||
|
|
"hold_time_one": one_hold_time,
|
||
|
|
"hold_time_zero": zero_hold_time
|
||
|
|
}
|
||
|
|
return times
|
||
|
|
|