OpenRAM/compiler/characterizer/delay.py

483 lines
21 KiB
Python
Raw Normal View History

2016-11-08 18:57:35 +01:00
import sys
import re
import debug
import tech
import math
import stimuli
import charutils as ch
import utils
from globals import OPTS
2016-11-08 18:57:35 +01:00
class delay():
"""
Functions to measure the delay of the SRAM at a given address and
data bit.
"""
def __init__(self,sram,spfile):
self.name = sram.name
self.num_words = sram.num_words
self.word_size = sram.word_size
self.addr_size = sram.addr_size
self.sram_sp_file = spfile
2017-07-06 17:42:25 +02:00
self.vdd = tech.spice["supply_voltage"]
self.gnd = tech.spice["gnd_voltage"]
2016-11-08 18:57:35 +01:00
2016-11-08 18:57:35 +01:00
def check_arguments(self):
"""Checks if arguments given for write_stimulus() meets requirements"""
try:
int(self.probe_address, 2)
except ValueError:
debug.error("Probe Address is not of binary form: {0}".format(self.probe_address),1)
if len(self.probe_address) != self.addr_size:
debug.error("Probe Address's number of bits does not correspond to given SRAM",1)
if not isinstance(self.probe_data, int) or self.probe_data>self.word_size or self.probe_data<0:
debug.error("Given probe_data is not an integer to specify a data bit",1)
2017-07-06 17:42:25 +02:00
def write_stimulus(self, period, load, slew):
""" Creates a stimulus file for simulations to probe a bitcell at a given clock period.
Address and bit were previously set with set_probe().
Input slew (in ns) and output capacitive load (in fF) are required for charaterization.
2016-11-08 18:57:35 +01:00
"""
self.check_arguments()
# obtains list of time-points for each rising clk edge
2017-07-06 17:42:25 +02:00
self.obtain_cycle_times(period)
2016-11-08 18:57:35 +01:00
# creates and opens stimulus file for writing
temp_stim = "{0}/stim.sp".format(OPTS.openram_temp)
self.sf = open(temp_stim, "w")
self.sf.write("* Stimulus for period of {0}n load={1}fF slew={2}ns\n\n".format(period,load,slew))
2016-11-08 18:57:35 +01:00
# include files in stimulus file
model_list = tech.spice["fet_models"] + [self.sram_sp_file]
stimuli.write_include(stim_file=self.sf, models=model_list)
# add vdd/gnd statements
2017-07-06 17:42:25 +02:00
2016-11-08 18:57:35 +01:00
self.sf.write("* Global Power Supplies\n")
2017-07-06 17:42:25 +02:00
stimuli.write_supply(self.sf)
2016-11-08 18:57:35 +01:00
# instantiate the sram
self.sf.write("* Instantiation of the SRAM\n")
stimuli.inst_sram(stim_file=self.sf,
abits=self.addr_size,
dbits=self.word_size,
sram_name=self.name)
2017-07-06 17:42:25 +02:00
self.sf.write("* SRAM output loads\n")
for i in range(self.word_size):
self.sf.write("CD{0} d[{0}] 0 {1}f\n".format(i,load))
2017-07-06 17:42:25 +02:00
2016-11-08 18:57:35 +01:00
# add access transistors for data-bus
2017-07-06 17:42:25 +02:00
self.sf.write("* Transmission Gates for data-bus and control signals\n")
stimuli.inst_accesstx(stim_file=self.sf, dbits=self.word_size)
2016-11-08 18:57:35 +01:00
# generate data and addr signals
2017-07-06 17:42:25 +02:00
self.sf.write("* Generation of data and address signals\n")
2016-11-08 18:57:35 +01:00
for i in range(self.word_size):
if i == self.probe_data:
2017-07-06 17:42:25 +02:00
stimuli.gen_data(stim_file=self.sf,
clk_times=self.cycle_times,
sig_name="data[{0}]".format(i),
2017-07-06 17:42:25 +02:00
period=period,
slew=slew)
2016-11-08 18:57:35 +01:00
else:
stimuli.gen_constant(stim_file=self.sf,
sig_name="d[{0}]".format(i),
2017-07-06 17:42:25 +02:00
v_val=self.gnd)
2016-11-08 18:57:35 +01:00
2017-07-06 17:42:25 +02:00
stimuli.gen_addr(self.sf,
clk_times=self.cycle_times,
addr=self.probe_address,
period=period,
slew=slew)
2016-11-08 18:57:35 +01:00
2017-07-06 17:42:25 +02:00
# generate control signals
self.sf.write("* Generation of control signals\n")
stimuli.gen_csb(self.sf, self.cycle_times, period, slew)
stimuli.gen_web(self.sf, self.cycle_times, period, slew)
stimuli.gen_oeb(self.sf, self.cycle_times, period, slew)
2016-11-08 18:57:35 +01:00
self.sf.write("* Generation of global clock signal\n")
2017-07-06 17:42:25 +02:00
stimuli.gen_pulse(stim_file=self.sf,
sig_name="CLK",
v1=self.gnd,
v2=self.vdd,
offset=period,
period=period,
t_rise = slew,
t_fall = slew)
self.write_measures(period)
# run until the end of the cycle time
stimuli.write_control(self.sf,self.cycle_times[-1] + period)
2016-11-08 18:57:35 +01:00
2017-07-06 17:42:25 +02:00
self.sf.close()
def write_measures(self,period):
2016-11-08 18:57:35 +01:00
# meas statement for delay and power measurements
self.sf.write("* Measure statements for delay and power\n")
for comment in self.cycle_comments:
self.sf.write("* {}\n".format(comment))
2017-07-06 17:42:25 +02:00
trig_name = "clk"
targ_name = "{0}".format("d[{0}]".format(self.probe_data))
2017-07-06 17:42:25 +02:00
trig_val = targ_val = 0.5 * self.vdd
# add measure statments for delay0
# delay the target to measure after the negative edge
2016-11-08 18:57:35 +01:00
stimuli.gen_meas_delay(stim_file=self.sf,
2017-07-06 17:42:25 +02:00
meas_name="DELAY0",
2016-11-08 18:57:35 +01:00
trig_name=trig_name,
targ_name=targ_name,
trig_val=trig_val,
targ_val=targ_val,
2017-07-06 17:42:25 +02:00
trig_dir="FALL",
targ_dir="FALL",
trig_td=self.cycle_times[self.read0_cycle],
targ_td=self.cycle_times[self.read0_cycle]+0.5*period)
2016-11-08 18:57:35 +01:00
2017-07-06 17:42:25 +02:00
stimuli.gen_meas_delay(stim_file=self.sf,
meas_name="DELAY1",
trig_name=trig_name,
targ_name=targ_name,
trig_val=trig_val,
targ_val=targ_val,
trig_dir="FALL",
targ_dir="RISE",
trig_td=self.cycle_times[self.read1_cycle],
targ_td=self.cycle_times[self.read1_cycle]+0.5*period)
2017-07-06 17:42:25 +02:00
stimuli.gen_meas_delay(stim_file=self.sf,
meas_name="SLEW0",
trig_name=targ_name,
targ_name=targ_name,
trig_val=0.8*self.vdd,
targ_val=0.2*self.vdd,
2017-07-06 17:42:25 +02:00
trig_dir="FALL",
targ_dir="FALL",
trig_td=self.cycle_times[self.read0_cycle],
targ_td=self.cycle_times[self.read0_cycle]+0.5*period)
2017-07-06 17:42:25 +02:00
stimuli.gen_meas_delay(stim_file=self.sf,
meas_name="SLEW1",
trig_name=targ_name,
targ_name=targ_name,
trig_val=0.2*self.vdd,
targ_val=0.8*self.vdd,
2017-07-06 17:42:25 +02:00
trig_dir="RISE",
targ_dir="RISE",
trig_td=self.cycle_times[self.read1_cycle],
targ_td=self.cycle_times[self.read1_cycle]+0.5*period)
2017-07-06 17:42:25 +02:00
2016-11-08 18:57:35 +01:00
# add measure statements for power
2017-07-06 17:42:25 +02:00
t_initial = self.cycle_times[self.write0_cycle]
t_final = self.cycle_times[self.write0_cycle+1]
2016-11-08 18:57:35 +01:00
stimuli.gen_meas_power(stim_file=self.sf,
2017-07-06 17:42:25 +02:00
meas_name="WRITE0_POWER",
2016-11-08 18:57:35 +01:00
t_initial=t_initial,
t_final=t_final)
2017-07-06 17:42:25 +02:00
t_initial = self.cycle_times[self.write1_cycle]
t_final = self.cycle_times[self.write1_cycle+1]
2016-11-08 18:57:35 +01:00
stimuli.gen_meas_power(stim_file=self.sf,
2017-07-06 17:42:25 +02:00
meas_name="WRITE1_POWER",
t_initial=t_initial,
t_final=t_final)
t_initial = self.cycle_times[self.read0_cycle]
t_final = self.cycle_times[self.read0_cycle+1]
stimuli.gen_meas_power(stim_file=self.sf,
meas_name="READ0_POWER",
2016-11-08 18:57:35 +01:00
t_initial=t_initial,
t_final=t_final)
2017-07-06 17:42:25 +02:00
t_initial = self.cycle_times[self.read1_cycle]
t_final = self.cycle_times[self.read1_cycle+1]
stimuli.gen_meas_power(stim_file=self.sf,
meas_name="READ1_POWER",
t_initial=t_initial,
t_final=t_final)
def find_feasible_period(self, load, slew):
2016-11-08 18:57:35 +01:00
"""Uses an initial period and finds a feasible period before we
run the binary search algorithm to find min period. We check if
the given clock period is valid and if it's not, we continue to
double the period until we find a valid period to use as a
starting point. """
2017-07-06 17:42:25 +02:00
feasible_period = tech.spice["feasible_period"]
2016-11-08 18:57:35 +01:00
time_out = 8
while True:
debug.info(1, "Trying feasible period: {0}ns".format(feasible_period))
2016-11-08 18:57:35 +01:00
time_out -= 1
if (time_out <= 0):
debug.error("Timed out, could not find a feasible period.",2)
2017-07-06 17:42:25 +02:00
(success, feasible_delay1, feasible_slew1, feasible_delay0, feasible_slew0)=self.run_simulation(feasible_period,load,slew)
2016-11-08 18:57:35 +01:00
if not success:
feasible_period = 2 * feasible_period
continue
debug.info(1, "Found feasible_period: {0}ns feasible_delay1/0 {1}ns/{2}ns slew {3}ns/{4}ns".format(feasible_period,feasible_delay1,feasible_delay0,feasible_slew1,feasible_slew0))
2017-07-06 17:42:25 +02:00
return (feasible_period, feasible_delay1, feasible_delay0)
2016-11-08 18:57:35 +01:00
2017-07-06 17:42:25 +02:00
def run_simulation(self, period, load, slew):
2016-11-08 18:57:35 +01:00
""" This tries to simulate a period and checks if the result
2017-07-06 17:42:25 +02:00
works. If so, it returns True and the delays and slews."""
2016-11-08 18:57:35 +01:00
# Checking from not data_value to data_value
2017-07-06 17:42:25 +02:00
self.write_stimulus(period, load, slew)
2016-11-08 18:57:35 +01:00
stimuli.run_sim()
2017-07-06 17:42:25 +02:00
delay0 = ch.convert_to_float(ch.parse_output("timing", "delay0"))
delay1 = ch.convert_to_float(ch.parse_output("timing", "delay1"))
slew0 = ch.convert_to_float(ch.parse_output("timing", "slew0"))
slew1 = ch.convert_to_float(ch.parse_output("timing", "slew1"))
2016-11-08 18:57:35 +01:00
# if it failed or the read was longer than a period
2017-07-06 17:42:25 +02:00
if type(delay0)!=float or type(delay1)!=float or type(slew1)!=float or type(slew0)!=float:
debug.info(2,"Failed simulation: period {0} load {1} slew {2}, delay0={3}n delay1={4}ns slew0={5}n slew1={6}n".format(period,load,slew,delay0,delay1,slew0,slew1))
2017-07-06 17:42:25 +02:00
return (False,0,0,0,0)
delay0 *= 1e9
delay1 *= 1e9
slew0 *= 1e9
slew1 *= 1e9
if delay0>period or delay1>period or slew0>period or slew1>period:
debug.info(2,"UNsuccessful simulation: period {0} load {1} slew {2}, delay0={3}n delay1={4}ns slew0={5}n slew1={6}n".format(period,load,slew,delay0,delay1,slew0,slew1))
return (False,0,0,0,0)
2016-11-08 18:57:35 +01:00
else:
debug.info(2,"Successful simulation: period {0} load {1} slew {2}, delay0={3}n delay1={4}ns slew0={5}n slew1={6}n".format(period,load,slew,delay0,delay1,slew0,slew1))
2016-11-08 18:57:35 +01:00
#key=raw_input("press return to continue")
2017-07-06 17:42:25 +02:00
# The delay is from the negative edge for our SRAM
return (True,delay1,slew1,delay0,slew0)
2016-11-08 18:57:35 +01:00
2017-07-06 17:42:25 +02:00
def find_min_period(self,feasible_period, load, slew, feasible_delay1, feasible_delay0):
"""Searches for the smallest period with output delays being within 5% of
long period. """
2016-11-08 18:57:35 +01:00
previous_period = ub_period = feasible_period
lb_period = 0.0
# Binary search algorithm to find the min period (max frequency) of design
time_out = 25
while True:
time_out -= 1
if (time_out <= 0):
debug.error("Timed out, could not converge on minimum period.",2)
target_period = 0.5 * (ub_period + lb_period)
debug.info(1, "MinPeriod Search: {0}ns (ub: {1} lb: {2})".format(target_period,
ub_period,
lb_period))
2016-11-08 18:57:35 +01:00
2017-07-06 17:42:25 +02:00
if self.try_period(target_period, load, slew, feasible_delay1, feasible_delay0):
2016-11-08 18:57:35 +01:00
ub_period = target_period
else:
lb_period = target_period
2017-07-06 17:42:25 +02:00
if ch.relative_compare(ub_period, lb_period, error_tolerance=0.05):
# ub_period is always feasible
return ub_period
2016-11-08 18:57:35 +01:00
2017-07-06 17:42:25 +02:00
def try_period(self, period, load, slew, feasible_delay1, feasible_delay0):
""" This tries to simulate a period and checks if the result
works. If it does and the delay is within 5% still, it returns True."""
2016-11-08 18:57:35 +01:00
2017-07-06 17:42:25 +02:00
# Checking from not data_value to data_value
self.write_stimulus(period,load,slew)
stimuli.run_sim()
delay0 = ch.convert_to_float(ch.parse_output("timing", "delay0"))
delay1 = ch.convert_to_float(ch.parse_output("timing", "delay1"))
slew0 = ch.convert_to_float(ch.parse_output("timing", "slew0"))
slew1 = ch.convert_to_float(ch.parse_output("timing", "slew1"))
2017-07-06 17:42:25 +02:00
# if it failed or the read was longer than a period
if type(delay0)!=float or type(delay1)!=float or type(slew1)!=float or type(slew0)!=float:
debug.info(2,"Invalid measures: Period {0}, delay0={1}ns, delay1={2}ns slew0={3}ns slew1={4}ns".format(period, delay0, delay1, slew0, slew1))
return False
delay0 *= 1e9
delay1 *= 1e9
slew0 *= 1e9
slew1 *= 1e9
if delay0>period or delay1>period or slew0>period or slew1>period:
debug.info(2,"Too long delay/slew: Period {0}, delay0={1}ns, delay1={2}ns slew0={3}ns slew1={4}ns".format(period, delay0, delay1, slew0, slew1))
2017-07-06 17:42:25 +02:00
return False
else:
if not ch.relative_compare(delay1,feasible_delay1,error_tolerance=0.05):
debug.info(2,"Delay too big {0} vs {1}".format(delay1,feasible_delay1))
2017-07-06 17:42:25 +02:00
return False
elif not ch.relative_compare(delay0,feasible_delay0,error_tolerance=0.05):
debug.info(2,"Delay too big {0} vs {1}".format(delay0,feasible_delay0))
2017-07-06 17:42:25 +02:00
return False
#key=raw_input("press return to continue")
debug.info(2,"Successful period {0}, delay0={1}ns, delay1={2}ns slew0={3}ns slew1={4}ns".format(period, delay0, delay1, slew0, slew1))
2017-07-06 17:42:25 +02:00
return True
2016-11-08 18:57:35 +01:00
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."""
self.probe_address = probe_address
self.probe_data = probe_data
2017-07-06 17:42:25 +02:00
def analyze(self,probe_address, probe_data, slews, loads):
2016-11-08 18:57:35 +01:00
"""main function to calculate the min period for a low_to_high
transistion and a high_to_low transistion returns a dictionary
that contains all both the min period and associated delays
Dictionary Keys: min_period1, delay1, min_period0, delay0
"""
2017-07-06 17:42:25 +02:00
2016-11-08 18:57:35 +01:00
self.set_probe(probe_address, probe_data)
# This is for debugging a full simulation
# debug.info(0,"Debug simulation running...")
# target_period=50.0
# feasible_delay1=0.059083183
# feasible_delay0=0.17953789
# load=1.6728
# slew=0.04
# self.try_period(target_period, load, slew, feasible_delay1, feasible_delay0)
# sys.exit(1)
(feasible_period, feasible_delay1, feasible_delay0) = self.find_feasible_period(max(loads), max(slews))
2017-07-06 17:42:25 +02:00
debug.check(feasible_delay1>0,"Negative delay may not be possible")
debug.check(feasible_delay0>0,"Negative delay may not be possible")
# The power variables are just scalars. These use the final feasible period simulation
# which should have worked.
read0_power=ch.convert_to_float(ch.parse_output("timing", "read0_power"))
write0_power=ch.convert_to_float(ch.parse_output("timing", "write0_power"))
read1_power=ch.convert_to_float(ch.parse_output("timing", "read1_power"))
write1_power=ch.convert_to_float(ch.parse_output("timing", "write1_power"))
LH_delay = []
HL_delay = []
LH_slew = []
HL_slew = []
for slew in slews:
for load in loads:
(success, delay1, slew1, delay0, slew0) = self.run_simulation(feasible_period, load, slew)
debug.check(success,"Couldn't run a simulation. slew={0} load={1}\n".format(slew,load))
2017-07-06 17:42:25 +02:00
LH_delay.append(delay1)
HL_delay.append(delay0)
LH_slew.append(slew1)
HL_slew.append(slew0)
# finds the minimum period without degrading the delays by X%
min_period = self.find_min_period(feasible_period, max(loads), max(slews), feasible_delay1, feasible_delay0)
2017-07-06 17:42:25 +02:00
debug.check(type(min_period)==float,"Couldn't find minimum period.")
debug.info(1, "Min Period: {0}n with a delay of {1} / {2}".format(min_period, feasible_delay1, feasible_delay0))
2017-07-06 17:42:25 +02:00
data = {"min_period": ch.round_time(min_period),
"delay1": LH_delay,
"delay0": HL_delay,
"slew1": LH_slew,
"slew0": HL_slew,
"read0_power": read0_power*1e3,
"read1_power": read1_power*1e3,
"write0_power": write0_power*1e3,
"write1_power": write1_power*1e3
2016-11-08 18:57:35 +01:00
}
return data
2016-11-08 18:57:35 +01:00
2017-07-06 17:42:25 +02:00
def obtain_cycle_times(self, period):
2016-11-08 18:57:35 +01:00
"""Returns a list of key time-points [ns] of the waveform (each rising edge)
of the cycles to do a timing evaluation. The last time is the end of the simulation
and does not need a rising edge."""
self.cycle_comments = []
2017-07-06 17:42:25 +02:00
# idle cycle, no operation
t_current = period
self.cycle_times = []
# cycle0: W data 1 address 1111 to initialize cell to a value
2016-11-08 18:57:35 +01:00
self.cycle_times.append(t_current)
self.cycle_comments.append("Cycle0 {}ns: W data 1 address 11..11 to initialize cell".format(t_current))
2017-07-06 17:42:25 +02:00
t_current += period
2016-11-08 18:57:35 +01:00
2017-07-06 17:42:25 +02:00
# cycle1: W data 0 address 1111 (to ensure a write of value works)
self.cycle_times.append(t_current)
self.write0_cycle=1
self.cycle_comments.append("Cycle1 {}ns: W data 0 address 11..11 (to ensure a write of value works)".format(t_current))
2017-07-06 17:42:25 +02:00
t_current += period
# cycle2: W data 1 address 0000 (to clear the data bus cap)
2016-11-08 18:57:35 +01:00
self.cycle_times.append(t_current)
self.cycle_comments.append("Cycle2 {}ns: W data 1 address 00..00 (to clear bus caps)".format(t_current))
2017-07-06 17:42:25 +02:00
t_current += period
2016-11-08 18:57:35 +01:00
2017-07-06 17:42:25 +02:00
# cycle3: R data 0 address 1111 to check W0 works
2016-11-08 18:57:35 +01:00
self.cycle_times.append(t_current)
2017-07-06 17:42:25 +02:00
self.read0_cycle=3
self.cycle_comments.append("Cycle3 {}ns: R data 0 address 11..11 to check W0 worked".format(t_current))
2017-07-06 17:42:25 +02:00
t_current += period
2016-11-08 18:57:35 +01:00
2017-07-06 17:42:25 +02:00
# cycle4: W data 1 address 1111 (to ensure a write of value works)
2016-11-08 18:57:35 +01:00
self.cycle_times.append(t_current)
2017-07-06 17:42:25 +02:00
self.write1_cycle=4
self.cycle_comments.append("Cycle4 {}ns: W data 1 address 11..11 (to ensure a write of value worked)".format(t_current))
2017-07-06 17:42:25 +02:00
t_current += period
2016-11-08 18:57:35 +01:00
2017-07-06 17:42:25 +02:00
# cycle5: W data 0 address 0000 (to clear the data bus cap)
self.cycle_times.append(t_current)
self.cycle_comments.append("Cycle5 {}ns: W data 0 address 00..00 (to clear bus caps)".format(t_current))
2017-07-06 17:42:25 +02:00
t_current += period
# cycle6: R data 1 address 1111 to check W1 works
2016-11-08 18:57:35 +01:00
self.cycle_times.append(t_current)
2017-07-06 17:42:25 +02:00
self.read1_cycle=6
self.cycle_comments.append("Cycle6 {}ns: R data 1 address 11..11 to check W1 worked".format(t_current))
2017-07-06 17:42:25 +02:00
t_current += period
2016-11-08 18:57:35 +01:00
2017-07-06 17:42:25 +02:00
# cycle7: wait a clock period to end the simulation
2016-11-08 18:57:35 +01:00
self.cycle_times.append(t_current)
self.cycle_comments.append("Cycle7 {}ns: Idle period to end simulation".format(t_current))
2017-07-06 17:42:25 +02:00
t_current += period
2016-11-08 18:57:35 +01:00
def analytical_model(self,sram, slews, loads):
""" Just return the analytical model results for the SRAM.
"""
LH_delay = []
HL_delay = []
LH_slew = []
HL_slew = []
for slew in slews:
for load in loads:
bank_delay = sram.analytical_delay(slew,load)
# Convert from ps to ns
LH_delay.append(bank_delay.delay/1e3)
HL_delay.append(bank_delay.delay/1e3)
LH_slew.append(bank_delay.slew/1e3)
HL_slew.append(bank_delay.slew/1e3)
data = {"min_period": 0,
"delay1": LH_delay,
"delay0": HL_delay,
"slew1": LH_slew,
"slew0": HL_slew,
"read0_power": 0,
"read1_power": 0,
"write0_power": 0,
"write1_power": 0
}
return data