mirror of
https://github.com/VLSIDA/OpenRAM.git
synced 2026-09-05 01:01:33 +02:00
Fixed conflict in delay.py
This commit is contained in:
@@ -27,7 +27,7 @@ def parse_output(filename, key):
|
||||
|
||||
if val != None:
|
||||
debug.info(4, "Key = " + key + " Val = " + val.group(1))
|
||||
return val.group(1)
|
||||
return convert_to_float(val.group(1))
|
||||
else:
|
||||
return "Failed"
|
||||
|
||||
|
||||
+423
-253
@@ -1,34 +1,54 @@
|
||||
import sys
|
||||
import re
|
||||
import sys,re,shutil
|
||||
import debug
|
||||
import tech
|
||||
import math
|
||||
import stimuli
|
||||
from trim_spice import trim_spice
|
||||
import charutils as ch
|
||||
import utils
|
||||
from globals import OPTS
|
||||
|
||||
class delay():
|
||||
"""
|
||||
Functions to measure the delay of an SRAM at a given address and
|
||||
"""Functions to measure the delay and power of an SRAM at a given address and
|
||||
data bit.
|
||||
|
||||
In general, this will perform the following actions:
|
||||
1) Trim the netlist to remove unnecessary logic.
|
||||
2) Find a feasible clock period using max load/slew on the trimmed netlist.
|
||||
3) Characterize all loads/slews and consider fail when delay is greater than 5% of feasible delay using trimmed netlist.
|
||||
4) Measure the leakage during the last cycle of the trimmed netlist when there is no operation.
|
||||
5) Measure the leakage of the whole netlist (untrimmed) in each corner.
|
||||
6) Subtract the trimmed leakage and add the untrimmed leakage to the power.
|
||||
|
||||
Netlist trimming can be removed by setting OPTS.trim_netlist to
|
||||
False, but this is VERY slow.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,sram,spfile, corner):
|
||||
def __init__(self, sram, spfile, corner):
|
||||
self.sram = sram
|
||||
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
|
||||
self.word_size = self.sram.word_size
|
||||
self.addr_size = self.sram.addr_size
|
||||
self.num_cols = self.sram.num_cols
|
||||
self.num_rows = self.sram.num_rows
|
||||
self.num_banks = self.sram.num_banks
|
||||
self.sp_file = spfile
|
||||
|
||||
# These are the member variables for a simulation
|
||||
self.period = 0
|
||||
self.set_load_slew(0,0)
|
||||
self.set_corner(corner)
|
||||
|
||||
|
||||
def set_corner(self,corner):
|
||||
""" Set the corner values """
|
||||
self.corner = corner
|
||||
(self.process, self.vdd_voltage, self.temperature) = corner
|
||||
self.gnd_voltage = 0
|
||||
|
||||
def set_load_slew(self,load,slew):
|
||||
""" Set the load and slew """
|
||||
self.load = load
|
||||
self.slew = slew
|
||||
|
||||
def check_arguments(self):
|
||||
"""Checks if arguments given for write_stimulus() meets requirements"""
|
||||
@@ -43,27 +63,10 @@ class delay():
|
||||
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)
|
||||
|
||||
|
||||
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.
|
||||
"""
|
||||
self.check_arguments()
|
||||
|
||||
# obtains list of time-points for each rising clk edge
|
||||
self.obtain_cycle_times(period)
|
||||
|
||||
# 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))
|
||||
self.stim = stimuli.stimuli(self.sf, self.corner)
|
||||
# include files in stimulus file
|
||||
self.stim.write_include(self.sram_sp_file)
|
||||
def write_generic_stimulus(self):
|
||||
""" Create the instance, supplies, loads, and access transistors. """
|
||||
|
||||
# add vdd/gnd statements
|
||||
|
||||
self.sf.write("\n* Global Power Supplies\n")
|
||||
self.stim.write_supply()
|
||||
|
||||
@@ -75,52 +78,123 @@ class delay():
|
||||
|
||||
self.sf.write("\n* 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))
|
||||
self.sf.write("CD{0} d[{0}] 0 {1}f\n".format(i,self.load))
|
||||
|
||||
# add access transistors for data-bus
|
||||
self.sf.write("\n* Transmission Gates for data-bus and control signals\n")
|
||||
self.stim.inst_accesstx(dbits=self.word_size)
|
||||
|
||||
|
||||
def write_delay_stimulus(self):
|
||||
""" 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.
|
||||
"""
|
||||
self.check_arguments()
|
||||
|
||||
# obtains list of time-points for each rising clk edge
|
||||
self.obtain_cycle_times()
|
||||
|
||||
# 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("* Delay stimulus for period of {0}n load={1}fF slew={2}ns\n\n".format(self.period,
|
||||
self.load,
|
||||
self.slew))
|
||||
self.stim = stimuli.stimuli(self.sf, self.corner)
|
||||
# include files in stimulus file
|
||||
self.stim.write_include(self.trim_sp_file)
|
||||
|
||||
self.write_generic_stimulus()
|
||||
|
||||
# generate data and addr signals
|
||||
self.sf.write("\n* Generation of data and address signals\n")
|
||||
for i in range(self.word_size):
|
||||
if i == self.probe_data:
|
||||
self.gen_data(clk_times=self.cycle_times,
|
||||
sig_name="data[{0}]".format(i),
|
||||
period=period,
|
||||
slew=slew)
|
||||
sig_name="data[{0}]".format(i))
|
||||
|
||||
|
||||
else:
|
||||
self.stim.gen_constant(sig_name="d[{0}]".format(i),
|
||||
v_val=self.gnd_voltage)
|
||||
v_val=0)
|
||||
|
||||
self.gen_addr(clk_times=self.cycle_times,
|
||||
addr=self.probe_address,
|
||||
period=period,
|
||||
slew=slew)
|
||||
addr=self.probe_address)
|
||||
|
||||
|
||||
# generate control signals
|
||||
self.sf.write("\n* Generation of control signals\n")
|
||||
self.gen_csb(self.cycle_times, period, slew)
|
||||
self.gen_web(self.cycle_times, period, slew)
|
||||
self.gen_oeb(self.cycle_times, period, slew)
|
||||
self.gen_csb(self.cycle_times)
|
||||
self.gen_web(self.cycle_times)
|
||||
self.gen_oeb(self.cycle_times)
|
||||
|
||||
self.sf.write("\n* Generation of global clock signal\n")
|
||||
self.stim.gen_pulse(sig_name="CLK",
|
||||
v1=self.gnd_voltage,
|
||||
v1=0,
|
||||
v2=self.vdd_voltage,
|
||||
offset=period,
|
||||
period=period,
|
||||
t_rise=slew,
|
||||
t_fall=slew)
|
||||
offset=self.period,
|
||||
period=self.period,
|
||||
t_rise=self.slew,
|
||||
t_fall=self.slew)
|
||||
|
||||
self.write_measures(period)
|
||||
self.write_delay_measures()
|
||||
|
||||
# run until the end of the cycle time
|
||||
self.stim.write_control(self.cycle_times[-1] + period)
|
||||
self.stim.write_control(self.cycle_times[-1] + self.period)
|
||||
|
||||
self.sf.close()
|
||||
|
||||
def write_measures(self,period):
|
||||
|
||||
def write_power_stimulus(self, trim):
|
||||
""" Creates a stimulus file to measure leakage power only.
|
||||
This works on the *untrimmed netlist*.
|
||||
"""
|
||||
self.check_arguments()
|
||||
|
||||
# obtains list of time-points for each rising clk edge
|
||||
self.obtain_cycle_times()
|
||||
|
||||
# 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("* Power stimulus for period of {0}n\n\n".format(self.period))
|
||||
self.stim = stimuli.stimuli(self.sf, self.corner)
|
||||
|
||||
# include UNTRIMMED files in stimulus file
|
||||
if trim:
|
||||
self.stim.write_include(self.trim_sp_file)
|
||||
else:
|
||||
self.stim.write_include(self.sim_sp_file)
|
||||
|
||||
self.write_generic_stimulus()
|
||||
|
||||
# generate data and addr signals
|
||||
self.sf.write("\n* Generation of data and address signals\n")
|
||||
for i in range(self.word_size):
|
||||
self.stim.gen_constant(sig_name="d[{0}]".format(i),
|
||||
v_val=0)
|
||||
for i in range(self.addr_size):
|
||||
self.stim.gen_constant(sig_name="A[{0}]".format(i),
|
||||
v_val=0)
|
||||
|
||||
# generate control signals
|
||||
self.sf.write("\n* Generation of control signals\n")
|
||||
self.stim.gen_constant(sig_name="CSb", v_val=self.vdd_voltage)
|
||||
self.stim.gen_constant(sig_name="WEb", v_val=self.vdd_voltage)
|
||||
self.stim.gen_constant(sig_name="OEb", v_val=self.vdd_voltage)
|
||||
|
||||
self.sf.write("\n* Generation of global clock signal\n")
|
||||
self.stim.gen_constant(sig_name="CLK", v_val=0)
|
||||
|
||||
self.write_power_measures()
|
||||
|
||||
# run until the end of the cycle time
|
||||
self.stim.write_control(2*self.period)
|
||||
|
||||
self.sf.close()
|
||||
|
||||
def write_delay_measures(self):
|
||||
"""
|
||||
Write the measure statements to quantify the delay and power results.
|
||||
"""
|
||||
@@ -138,7 +212,7 @@ class delay():
|
||||
trig_val = targ_val = 0.5 * self.vdd_voltage
|
||||
|
||||
# Delay the target to measure after the negative edge
|
||||
self.stim.gen_meas_delay(meas_name="DELAY0",
|
||||
self.stim.gen_meas_delay(meas_name="DELAY_HL",
|
||||
trig_name=trig_name,
|
||||
targ_name=targ_name,
|
||||
trig_val=trig_val,
|
||||
@@ -146,9 +220,9 @@ class delay():
|
||||
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)
|
||||
targ_td=self.cycle_times[self.read0_cycle]+0.5*self.period)
|
||||
|
||||
self.stim.gen_meas_delay(meas_name="DELAY1",
|
||||
self.stim.gen_meas_delay(meas_name="DELAY_LH",
|
||||
trig_name=trig_name,
|
||||
targ_name=targ_name,
|
||||
trig_val=trig_val,
|
||||
@@ -156,9 +230,9 @@ class delay():
|
||||
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)
|
||||
targ_td=self.cycle_times[self.read1_cycle]+0.5*self.period)
|
||||
|
||||
self.stim.gen_meas_delay(meas_name="SLEW0",
|
||||
self.stim.gen_meas_delay(meas_name="SLEW_HL",
|
||||
trig_name=targ_name,
|
||||
targ_name=targ_name,
|
||||
trig_val=0.9*self.vdd_voltage,
|
||||
@@ -166,9 +240,9 @@ class delay():
|
||||
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)
|
||||
targ_td=self.cycle_times[self.read0_cycle]+0.5*self.period)
|
||||
|
||||
self.stim.gen_meas_delay(meas_name="SLEW1",
|
||||
self.stim.gen_meas_delay(meas_name="SLEW_LH",
|
||||
trig_name=targ_name,
|
||||
targ_name=targ_name,
|
||||
trig_val=0.1*self.vdd_voltage,
|
||||
@@ -176,7 +250,7 @@ class delay():
|
||||
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)
|
||||
targ_td=self.cycle_times[self.read1_cycle]+0.5*self.period)
|
||||
|
||||
# add measure statements for power
|
||||
t_initial = self.cycle_times[self.write0_cycle]
|
||||
@@ -202,8 +276,23 @@ class delay():
|
||||
self.stim.gen_meas_power(meas_name="READ1_POWER",
|
||||
t_initial=t_initial,
|
||||
t_final=t_final)
|
||||
|
||||
|
||||
def find_feasible_period(self, load, slew):
|
||||
def write_power_measures(self):
|
||||
"""
|
||||
Write the measure statements to quantify the leakage power only.
|
||||
"""
|
||||
|
||||
self.sf.write("\n* Measure statements for idle leakage power\n")
|
||||
|
||||
# add measure statements for power
|
||||
t_initial = self.period
|
||||
t_final = 2*self.period
|
||||
self.stim.gen_meas_power(meas_name="leakage_power",
|
||||
t_initial=t_initial,
|
||||
t_final=t_final)
|
||||
|
||||
def find_feasible_period(self):
|
||||
"""
|
||||
Uses an initial period and finds a feasible period before we
|
||||
run the binary search algorithm to find min period. We check if
|
||||
@@ -212,7 +301,7 @@ class delay():
|
||||
starting point.
|
||||
"""
|
||||
|
||||
feasible_period = tech.spice["feasible_period"]
|
||||
feasible_period = float(tech.spice["feasible_period"])
|
||||
time_out = 8
|
||||
while True:
|
||||
debug.info(1, "Trying feasible period: {0}ns".format(feasible_period))
|
||||
@@ -220,81 +309,134 @@ class delay():
|
||||
|
||||
if (time_out <= 0):
|
||||
debug.error("Timed out, could not find a feasible period.",2)
|
||||
|
||||
(success, feasible_delay1, feasible_slew1, feasible_delay0, feasible_slew0)=self.run_simulation(feasible_period,load,slew)
|
||||
self.period = feasible_period
|
||||
(success, results)=self.run_delay_simulation()
|
||||
if not success:
|
||||
feasible_period = 2 * feasible_period
|
||||
continue
|
||||
feasible_delay_lh = results["delay_lh"]
|
||||
feasible_slew_lh = results["slew_lh"]
|
||||
feasible_delay_hl = results["delay_hl"]
|
||||
feasible_slew_hl = results["slew_hl"]
|
||||
|
||||
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))
|
||||
return (feasible_period, feasible_delay1, feasible_delay0)
|
||||
debug.info(1, "Found feasible_period: {0}ns feasible_delay {1}ns/{2}ns slew {3}ns/{4}ns".format(feasible_period,
|
||||
feasible_delay_lh,
|
||||
feasible_delay_hl,
|
||||
feasible_slew_lh,
|
||||
feasible_slew_hl))
|
||||
self.period = feasible_period
|
||||
return (feasible_delay_lh, feasible_delay_hl)
|
||||
|
||||
|
||||
def run_simulation(self, period, load, slew):
|
||||
"""
|
||||
This tries to simulate a period and checks if the result
|
||||
works. If so, it returns True and the delays and slews.
|
||||
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.
|
||||
"""
|
||||
|
||||
# Checking from not data_value to data_value
|
||||
self.write_stimulus(period, load, slew)
|
||||
self.write_delay_stimulus()
|
||||
self.stim.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"))
|
||||
|
||||
# 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,"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))
|
||||
return (False,0,0,0,0)
|
||||
# Scale delays to ns (they previously could have not been floats)
|
||||
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)
|
||||
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))
|
||||
delay_hl = ch.parse_output("timing", "delay_hl")
|
||||
delay_lh = ch.parse_output("timing", "delay_lh")
|
||||
slew_hl = ch.parse_output("timing", "slew_hl")
|
||||
slew_lh = ch.parse_output("timing", "slew_lh")
|
||||
delays = (delay_hl, delay_lh, slew_hl, slew_lh)
|
||||
|
||||
read0_power=ch.parse_output("timing", "read0_power")
|
||||
write0_power=ch.parse_output("timing", "write0_power")
|
||||
read1_power=ch.parse_output("timing", "read1_power")
|
||||
write1_power=ch.parse_output("timing", "write1_power")
|
||||
|
||||
if not self.check_valid_delays(delays):
|
||||
return (False,{})
|
||||
|
||||
# For debug, you sometimes want to inspect each simulation.
|
||||
#key=raw_input("press return to continue")
|
||||
|
||||
# Scale results to ns and mw, respectively
|
||||
result = { "delay_hl" : delay_hl*1e9,
|
||||
"delay_lh" : delay_lh*1e9,
|
||||
"slew_hl" : slew_hl*1e9,
|
||||
"slew_lh" : slew_lh*1e9,
|
||||
"read0_power" : read0_power*1e3,
|
||||
"read1_power" : read1_power*1e3,
|
||||
"write0_power" : write0_power*1e3,
|
||||
"write1_power" : write1_power*1e3}
|
||||
|
||||
# The delay is from the negative edge for our SRAM
|
||||
return (True,delay1,slew1,delay0,slew0)
|
||||
return (True,result)
|
||||
|
||||
|
||||
def run_power_simulation(self):
|
||||
"""
|
||||
This simulates a disabled SRAM to get the leakage power when it is off.
|
||||
|
||||
"""
|
||||
|
||||
def find_min_period(self,feasible_period, load, slew, feasible_delay1, feasible_delay0):
|
||||
self.write_power_stimulus(trim=False)
|
||||
self.stim.run_sim()
|
||||
leakage_power=ch.parse_output("timing", "leakage_power")
|
||||
debug.check(leakage_power!="Failed","Could not measure leakage power.")
|
||||
|
||||
|
||||
self.write_power_stimulus(trim=True)
|
||||
self.stim.run_sim()
|
||||
trim_leakage_power=ch.parse_output("timing", "leakage_power")
|
||||
debug.check(trim_leakage_power!="Failed","Could not measure leakage power.")
|
||||
|
||||
# For debug, you sometimes want to inspect each simulation.
|
||||
#key=raw_input("press return to continue")
|
||||
return (leakage_power*1e3, trim_leakage_power*1e3)
|
||||
|
||||
def check_valid_delays(self, (delay_hl, delay_lh, slew_hl, slew_lh)):
|
||||
""" Check if the measurements are defined and if they are valid. """
|
||||
|
||||
# if it failed or the read was longer than a period
|
||||
if type(delay_hl)!=float or type(delay_lh)!=float or type(slew_lh)!=float or type(slew_hl)!=float:
|
||||
debug.info(2,"Failed simulation: period {0} load {1} slew {2}, delay_hl={3}n delay_lh={4}ns slew_hl={5}n slew_lh={6}n".format(self.period,
|
||||
self.load,
|
||||
self.slew,
|
||||
delay_hl,
|
||||
delay_lh,
|
||||
slew_hl,
|
||||
slew_lh))
|
||||
return False
|
||||
# Scale delays to ns (they previously could have not been floats)
|
||||
delay_hl *= 1e9
|
||||
delay_lh *= 1e9
|
||||
slew_hl *= 1e9
|
||||
slew_lh *= 1e9
|
||||
if delay_hl>self.period or delay_lh>self.period or slew_hl>self.period or slew_lh>self.period:
|
||||
debug.info(2,"UNsuccessful simulation: period {0} load {1} slew {2}, delay_hl={3}n delay_lh={4}ns slew_hl={5}n slew_lh={6}n".format(self.period,
|
||||
self.load,
|
||||
self.slew,
|
||||
delay_hl,
|
||||
delay_lh,
|
||||
slew_hl,
|
||||
slew_lh))
|
||||
return False
|
||||
else:
|
||||
debug.info(2,"Successful simulation: period {0} load {1} slew {2}, delay_hl={3}n delay_lh={4}ns slew_hl={5}n slew_lh={6}n".format(self.period,
|
||||
self.load,
|
||||
self.slew,
|
||||
delay_hl,
|
||||
delay_lh,
|
||||
slew_hl,
|
||||
slew_lh))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def find_min_period(self, feasible_delay_lh, feasible_delay_hl):
|
||||
"""
|
||||
Searches for the smallest period with output delays being within 5% of
|
||||
long period.
|
||||
"""
|
||||
|
||||
previous_period = ub_period = feasible_period
|
||||
previous_period = ub_period = self.period
|
||||
lb_period = 0.0
|
||||
|
||||
# Binary search algorithm to find the min period (max frequency) of design
|
||||
@@ -305,11 +447,12 @@ class delay():
|
||||
debug.error("Timed out, could not converge on minimum period.",2)
|
||||
|
||||
target_period = 0.5 * (ub_period + lb_period)
|
||||
self.period = target_period
|
||||
debug.info(1, "MinPeriod Search: {0}ns (ub: {1} lb: {2})".format(target_period,
|
||||
ub_period,
|
||||
lb_period))
|
||||
|
||||
if self.try_period(target_period, load, slew, feasible_delay1, feasible_delay0):
|
||||
if self.try_period(feasible_delay_lh, feasible_delay_hl):
|
||||
ub_period = target_period
|
||||
else:
|
||||
lb_period = target_period
|
||||
@@ -319,54 +462,54 @@ class delay():
|
||||
return ub_period
|
||||
|
||||
|
||||
def try_period(self, period, load, slew, feasible_delay1, feasible_delay0):
|
||||
def try_period(self, feasible_delay_lh, feasible_delay_hl):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
|
||||
# Checking from not data_value to data_value
|
||||
self.write_stimulus(period,load,slew)
|
||||
self.write_delay_stimulus()
|
||||
self.stim.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"))
|
||||
delay_hl = ch.parse_output("timing", "delay_hl")
|
||||
delay_lh = ch.parse_output("timing", "delay_lh")
|
||||
slew_hl = ch.parse_output("timing", "slew_hl")
|
||||
slew_lh = ch.parse_output("timing", "slew_lh")
|
||||
# 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))
|
||||
if type(delay_hl)!=float or type(delay_lh)!=float or type(slew_lh)!=float or type(slew_hl)!=float:
|
||||
debug.info(2,"Invalid measures: Period {0}, delay_hl={1}ns, delay_lh={2}ns slew_hl={3}ns slew_lh={4}ns".format(self.period,
|
||||
delay_hl,
|
||||
delay_lh,
|
||||
slew_hl,
|
||||
slew_lh))
|
||||
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))
|
||||
delay_hl *= 1e9
|
||||
delay_lh *= 1e9
|
||||
slew_hl *= 1e9
|
||||
slew_lh *= 1e9
|
||||
if delay_hl>self.period or delay_lh>self.period or slew_hl>self.period or slew_lh>self.period:
|
||||
debug.info(2,"Too long delay/slew: Period {0}, delay_hl={1}ns, delay_lh={2}ns slew_hl={3}ns slew_lh={4}ns".format(self.period,
|
||||
delay_hl,
|
||||
delay_lh,
|
||||
slew_hl,
|
||||
slew_lh))
|
||||
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))
|
||||
if not ch.relative_compare(delay_lh,feasible_delay_lh,error_tolerance=0.05):
|
||||
debug.info(2,"Delay too big {0} vs {1}".format(delay_lh,feasible_delay_lh))
|
||||
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))
|
||||
elif not ch.relative_compare(delay_hl,feasible_delay_hl,error_tolerance=0.05):
|
||||
debug.info(2,"Delay too big {0} vs {1}".format(delay_hl,feasible_delay_hl))
|
||||
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))
|
||||
debug.info(2,"Successful period {0}, delay_hl={1}ns, delay_lh={2}ns slew_hl={3}ns slew_lh={4}ns".format(self.period,
|
||||
delay_hl,
|
||||
delay_lh,
|
||||
slew_hl,
|
||||
slew_lh))
|
||||
return True
|
||||
|
||||
def set_probe(self,probe_address, probe_data):
|
||||
@@ -375,12 +518,35 @@ class delay():
|
||||
self.probe_address = probe_address
|
||||
self.probe_data = probe_data
|
||||
|
||||
self.prepare_netlist()
|
||||
|
||||
|
||||
def prepare_netlist(self):
|
||||
""" Prepare a trimmed netlist and regular netlist. """
|
||||
|
||||
# Set up to trim the netlist here if that is enabled
|
||||
if OPTS.trim_netlist:
|
||||
self.trim_sp_file = "{}reduced.sp".format(OPTS.openram_temp)
|
||||
self.trimsp=trim_spice(self.sp_file, self.trim_sp_file)
|
||||
self.trimsp.set_configuration(self.num_banks,
|
||||
self.num_rows,
|
||||
self.num_cols,
|
||||
self.word_size)
|
||||
self.trimsp.trim(self.probe_address,self.probe_data)
|
||||
else:
|
||||
# The non-reduced netlist file when it is disabled
|
||||
self.trim_sp_file = "{}sram.sp".format(OPTS.openram_temp)
|
||||
|
||||
# The non-reduced netlist file for power simulation
|
||||
self.sim_sp_file = "{}sram.sp".format(OPTS.openram_temp)
|
||||
# Make a copy in temp for debugging
|
||||
shutil.copy(self.sp_file, self.sim_sp_file)
|
||||
|
||||
|
||||
|
||||
def analyze(self,probe_address, probe_data, slews, loads):
|
||||
"""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
|
||||
"""
|
||||
Main function to characterize an SRAM for a table. Computes both delay and power characterization.
|
||||
"""
|
||||
|
||||
self.set_probe(probe_address, probe_data)
|
||||
@@ -388,58 +554,62 @@ class delay():
|
||||
# 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
|
||||
# feasible_delay_lh=0.059083183
|
||||
# feasible_delay_hl=0.17953789
|
||||
# load=1.6728
|
||||
# slew=0.04
|
||||
# self.try_period(target_period, load, slew, feasible_delay1, feasible_delay0)
|
||||
# self.try_period(target_period, feasible_delay_lh, feasible_delay_hl)
|
||||
# sys.exit(1)
|
||||
|
||||
|
||||
(feasible_period, feasible_delay1, feasible_delay0) = self.find_feasible_period(max(loads), max(slews))
|
||||
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"))
|
||||
# 1) Find a feasible period and it's corresponding delays using the trimmed array.
|
||||
self.load=max(loads)
|
||||
self.slew=max(slews)
|
||||
(feasible_delay_lh, feasible_delay_hl) = self.find_feasible_period()
|
||||
debug.check(feasible_delay_lh>0,"Negative delay may not be possible")
|
||||
debug.check(feasible_delay_hl>0,"Negative delay may not be possible")
|
||||
|
||||
LH_delay = []
|
||||
HL_delay = []
|
||||
LH_slew = []
|
||||
HL_slew = []
|
||||
# 2) Measure the delay, slew and power for all slew/load pairs.
|
||||
# Make a list for each type of measurement to append results to
|
||||
char_data = {}
|
||||
for m in ["delay_lh", "delay_hl", "slew_lh", "slew_hl", "read0_power",
|
||||
"read1_power", "write0_power", "write1_power", "leakage_power"]:
|
||||
char_data[m]=[]
|
||||
|
||||
# 2a) Find the leakage power of the trimmmed and UNtrimmed arrays.
|
||||
(full_array_leakage, trim_array_leakage)=self.run_power_simulation()
|
||||
char_data["leakage_power"]=full_array_leakage
|
||||
|
||||
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))
|
||||
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)
|
||||
self.set_load_slew(load,slew)
|
||||
# 2c) Find the delay, dynamic power, and leakage power of the trimmed array.
|
||||
(success, delay_results) = self.run_delay_simulation()
|
||||
debug.check(success,"Couldn't run a simulation. slew={0} load={1}\n".format(self.slew,self.load))
|
||||
for k,v in delay_results.items():
|
||||
if "power" in k:
|
||||
# Subtract partial array leakage and add full array leakage for the power measures
|
||||
char_data[k].append(v - trim_array_leakage + full_array_leakage)
|
||||
else:
|
||||
char_data[k].append(v)
|
||||
|
||||
|
||||
|
||||
# 3) Finds the minimum period without degrading the delays by X%
|
||||
self.set_load_slew(max(loads),max(slews))
|
||||
min_period = self.find_min_period(feasible_delay_lh, feasible_delay_hl)
|
||||
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))
|
||||
debug.info(1, "Min Period: {0}n with a delay of {1} / {2}".format(min_period, feasible_delay_lh, feasible_delay_hl))
|
||||
|
||||
# 4) Pack up the final measurements
|
||||
char_data["min_period"] = ch.round_time(min_period)
|
||||
|
||||
return char_data
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
return data
|
||||
|
||||
|
||||
|
||||
def obtain_cycle_times(self, period):
|
||||
def obtain_cycle_times(self):
|
||||
"""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."""
|
||||
@@ -451,135 +621,135 @@ class delay():
|
||||
# idle cycle, no operation
|
||||
msg = "Idle cycle (no clock)"
|
||||
self.cycle_comments.append("Cycle{0}\t{1}ns:\t{2}".format(0,
|
||||
t_current,
|
||||
msg))
|
||||
t_current,
|
||||
msg))
|
||||
self.cycle_times.append(t_current)
|
||||
t_current += period
|
||||
t_current += self.period
|
||||
|
||||
# One period
|
||||
msg = "W data 1 address 11..11 to initialize cell"
|
||||
self.cycle_times.append(t_current)
|
||||
self.cycle_comments.append("Cycle{0}\t{1}ns:\t{2}".format(len(self.cycle_times)-1,
|
||||
t_current,
|
||||
msg))
|
||||
t_current += period
|
||||
t_current,
|
||||
msg))
|
||||
t_current += self.period
|
||||
|
||||
# One period
|
||||
msg = "W data 0 address 11..11 (to ensure a write of value works)"
|
||||
self.cycle_times.append(t_current)
|
||||
self.write0_cycle=len(self.cycle_times)-1
|
||||
self.cycle_comments.append("Cycle{0}\t{1}ns:\t{2}".format(len(self.cycle_times)-1,
|
||||
t_current,
|
||||
msg))
|
||||
t_current += period
|
||||
t_current,
|
||||
msg))
|
||||
t_current += self.period
|
||||
|
||||
# One period
|
||||
msg = "W data 1 address 00..00 (to clear bus caps)"
|
||||
self.cycle_times.append(t_current)
|
||||
self.cycle_comments.append("Cycle{0}\t{1}ns:\t{2}".format(len(self.cycle_times)-1,
|
||||
t_current,
|
||||
msg))
|
||||
t_current += period
|
||||
t_current,
|
||||
msg))
|
||||
t_current += self.period
|
||||
|
||||
# One period
|
||||
msg = "R data 0 address 11..11 to check W0 worked"
|
||||
self.cycle_times.append(t_current)
|
||||
self.read0_cycle=len(self.cycle_times)-1
|
||||
self.cycle_comments.append("Cycle{0}\t{1}ns:\t{2}".format(len(self.cycle_times)-1,
|
||||
t_current,
|
||||
msg))
|
||||
t_current += period
|
||||
t_current,
|
||||
msg))
|
||||
t_current += self.period
|
||||
|
||||
# One period
|
||||
msg = "Idle cycle"
|
||||
msg = "Idle cycle (Read addr 00..00)"
|
||||
self.cycle_comments.append("Cycle{0}\t{1}ns:\t{2}".format(len(self.cycle_times)-1,
|
||||
t_current,
|
||||
msg))
|
||||
t_current,
|
||||
msg))
|
||||
self.cycle_times.append(t_current)
|
||||
t_current += period
|
||||
self.idle_cycle=len(self.cycle_times)-1
|
||||
t_current += self.period
|
||||
|
||||
# One period
|
||||
msg = "W data 1 address 11..11 (to ensure a write of value worked)"
|
||||
self.cycle_times.append(t_current)
|
||||
self.write1_cycle=len(self.cycle_times)-1
|
||||
self.cycle_comments.append("Cycle{0}\t{1}ns:\t{2}".format(len(self.cycle_times)-1,
|
||||
t_current,
|
||||
msg))
|
||||
t_current += period
|
||||
t_current,
|
||||
msg))
|
||||
t_current += self.period
|
||||
|
||||
# One period
|
||||
msg = "W data 0 address 00..00 (to clear bus caps)"
|
||||
self.cycle_times.append(t_current)
|
||||
self.cycle_comments.append("Cycle{0}\t{1}ns:\t{2}".format(len(self.cycle_times)-1,
|
||||
t_current,
|
||||
msg))
|
||||
t_current += period
|
||||
t_current,
|
||||
msg))
|
||||
t_current += self.period
|
||||
|
||||
# One period
|
||||
msg = "R data 1 address 11..11 to check W1 worked"
|
||||
self.cycle_times.append(t_current)
|
||||
self.read1_cycle=len(self.cycle_times)-1
|
||||
self.cycle_comments.append("Cycle{0}\t{1}ns:\t{2}".format(len(self.cycle_times)-1,
|
||||
t_current,
|
||||
msg))
|
||||
t_current += period
|
||||
t_current,
|
||||
msg))
|
||||
t_current += self.period
|
||||
|
||||
# One period
|
||||
msg = "Idle cycle"
|
||||
msg = "Idle cycle (Read addr 11..11)"
|
||||
self.cycle_comments.append("Cycle{0}\t{1}ns:\t{2}".format(len(self.cycle_times)-1,
|
||||
t_current,
|
||||
msg))
|
||||
t_current,
|
||||
msg))
|
||||
self.cycle_times.append(t_current)
|
||||
t_current += period
|
||||
t_current += self.period
|
||||
|
||||
|
||||
|
||||
def analytical_model(self,sram, slews, loads):
|
||||
def analytical_delay(self,sram, slews, loads):
|
||||
""" Just return the analytical model results for the SRAM.
|
||||
"""
|
||||
LH_delay = []
|
||||
HL_delay = []
|
||||
LH_slew = []
|
||||
HL_slew = []
|
||||
delay_lh = []
|
||||
delay_hl = []
|
||||
slew_lh = []
|
||||
slew_hl = []
|
||||
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)
|
||||
self.set_load_slew(load,slew)
|
||||
bank_delay = sram.analytical_delay(self.slew,self.load)
|
||||
delay_lh.append(bank_delay.delay/1e3)
|
||||
delay_hl.append(bank_delay.delay/1e3)
|
||||
slew_lh.append(bank_delay.slew/1e3)
|
||||
slew_hl.append(bank_delay.slew/1e3)
|
||||
|
||||
|
||||
power = sram.analytical_power(self.process, self.vdd_voltage, self.temperature, load)
|
||||
#convert from nW to mW
|
||||
power.dynamic /= 1e6
|
||||
power.leakage /= 1e6
|
||||
debug.info(1,"Dynamic Power: {0} mW".format(power.dynamic))
|
||||
debug.info(1,"Leakage Power: {0} mW".format(power.leakage))
|
||||
#print "Dynamic: ",power.dynamic," nW"
|
||||
#print "Leakage: ",power.leakage," nW"
|
||||
|
||||
data = {"min_period": 0,
|
||||
"delay1": LH_delay,
|
||||
"delay0": HL_delay,
|
||||
"slew1": LH_slew,
|
||||
"slew0": HL_slew,
|
||||
"delay_lh": delay_lh,
|
||||
"delay_hl": delay_hl,
|
||||
"slew_lh": slew_lh,
|
||||
"slew_hl": slew_hl,
|
||||
"read0_power": power.dynamic,
|
||||
"read1_power": power.leakage,
|
||||
"read1_power": power.dynamic,
|
||||
"write0_power": power.dynamic,
|
||||
"write1_power": power.leakage
|
||||
"write1_power": power.dynamic,
|
||||
"leakage_power": power.leakage
|
||||
}
|
||||
return data
|
||||
|
||||
def gen_data(self, clk_times, sig_name, period, slew):
|
||||
def gen_data(self, clk_times, sig_name):
|
||||
""" Generates the PWL data inputs for a simulation timing test. """
|
||||
# values for NOP, W1, W0, W1, R0, NOP, W1, W0, R1, NOP
|
||||
# we are asserting the opposite value on the other side of the tx gate during
|
||||
# the read to be "worst case". Otherwise, it can actually assist the read.
|
||||
values = [0, 1, 0, 1, 1, 1, 1, 0, 0, 0 ]
|
||||
self.stim.gen_pwl(sig_name, clk_times, values, period, slew, 0.05)
|
||||
self.stim.gen_pwl(sig_name, clk_times, values, self.period, self.slew, 0.05)
|
||||
|
||||
def gen_addr(self, clk_times, addr, period, slew):
|
||||
def gen_addr(self, clk_times, addr):
|
||||
"""
|
||||
Generates the address inputs for a simulation timing test.
|
||||
This alternates between all 1's and all 0's for the address.
|
||||
@@ -591,34 +761,34 @@ class delay():
|
||||
for i in range(len(addr)):
|
||||
sig_name = "A[{0}]".format(i)
|
||||
if addr[i]=="1":
|
||||
self.stim.gen_pwl(sig_name, clk_times, ones_values, period, slew, 0.05)
|
||||
self.stim.gen_pwl(sig_name, clk_times, ones_values, self.period, self.slew, 0.05)
|
||||
else:
|
||||
self.stim.gen_pwl(sig_name, clk_times, zero_values, period, slew, 0.05)
|
||||
self.stim.gen_pwl(sig_name, clk_times, zero_values, self.period, self.slew, 0.05)
|
||||
|
||||
|
||||
def gen_csb(self, clk_times, period, slew):
|
||||
def gen_csb(self, clk_times):
|
||||
""" Generates the PWL CSb signal """
|
||||
# values for NOP, W1, W0, W1, R0, NOP, W1, W0, R1, NOP
|
||||
# Keep CSb asserted in NOP for measuring >1 period
|
||||
values = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
self.stim.gen_pwl("csb", clk_times, values, period, slew, 0.05)
|
||||
self.stim.gen_pwl("csb", clk_times, values, self.period, self.slew, 0.05)
|
||||
|
||||
def gen_web(self, clk_times, period, slew):
|
||||
def gen_web(self, clk_times):
|
||||
""" Generates the PWL WEb signal """
|
||||
# values for NOP, W1, W0, W1, R0, NOP, W1, W0, R1, NOP
|
||||
# Keep WEb deasserted in NOP for measuring >1 period
|
||||
values = [1, 0, 0, 0, 1, 1, 0, 0, 1, 1]
|
||||
self.stim.gen_pwl("web", clk_times, values, period, slew, 0.05)
|
||||
self.stim.gen_pwl("web", clk_times, values, self.period, self.slew, 0.05)
|
||||
|
||||
# Keep acc_en deasserted in NOP for measuring >1 period
|
||||
values = [1, 0, 0, 0, 1, 1, 0, 0, 1, 1]
|
||||
self.stim.gen_pwl("acc_en", clk_times, values, period, slew, 0)
|
||||
self.stim.gen_pwl("acc_en", clk_times, values, self.period, self.slew, 0)
|
||||
values = [0, 1, 1, 1, 0, 0, 1, 1, 0, 0]
|
||||
self.stim.gen_pwl("acc_en_inv", clk_times, values, period, slew, 0)
|
||||
self.stim.gen_pwl("acc_en_inv", clk_times, values, self.period, self.slew, 0)
|
||||
|
||||
def gen_oeb(self, clk_times, period, slew):
|
||||
def gen_oeb(self, clk_times):
|
||||
""" Generates the PWL WEb signal """
|
||||
# values for NOP, W1, W0, W1, R0, W1, W0, R1, NOP
|
||||
# Keep OEb asserted in NOP for measuring >1 period
|
||||
values = [1, 1, 1, 1, 0, 0, 1, 1, 0, 0]
|
||||
self.stim.gen_pwl("oeb", clk_times, values, period, slew, 0.05)
|
||||
self.stim.gen_pwl("oeb", clk_times, values, self.period, self.slew, 0.05)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import os,sys,re,shutil
|
||||
import os,sys,re
|
||||
import debug
|
||||
import math
|
||||
import setup_hold
|
||||
@@ -6,7 +6,6 @@ import delay
|
||||
import charutils as ch
|
||||
import tech
|
||||
import numpy as np
|
||||
from trim_spice import trim_spice
|
||||
from globals import OPTS
|
||||
|
||||
class lib:
|
||||
@@ -18,30 +17,12 @@ class lib:
|
||||
self.sp_file = sp_file
|
||||
self.use_model = use_model
|
||||
|
||||
self.prepare_netlist()
|
||||
|
||||
self.prepare_tables()
|
||||
|
||||
self.create_corners()
|
||||
|
||||
self.characterize_corners()
|
||||
|
||||
def prepare_netlist(self):
|
||||
""" Determine whether to use regular or trimmed netlist. """
|
||||
|
||||
# Set up to trim the netlist here if that is enabled
|
||||
if OPTS.trim_netlist:
|
||||
self.sim_sp_file = "{}reduced.sp".format(OPTS.openram_temp)
|
||||
self.trimsp=trim_spice(self.sp_file, self.sim_sp_file)
|
||||
self.trimsp.set_configuration(self.sram.num_banks,
|
||||
self.sram.num_rows,
|
||||
self.sram.num_cols,
|
||||
self.sram.word_size)
|
||||
else:
|
||||
# Else, use the non-reduced netlist file for simulation
|
||||
self.sim_sp_file = "{}sram.sp".format(OPTS.openram_temp)
|
||||
# Make a copy in temp for debugging
|
||||
shutil.copy(self.sp_file, self.sim_sp_file)
|
||||
|
||||
def prepare_tables(self):
|
||||
""" Determine the load/slews if they aren't specified in the config file. """
|
||||
@@ -49,7 +30,7 @@ class lib:
|
||||
#self.load_scales = np.array([0.1, 0.25, 0.5, 1, 2, 4, 8])
|
||||
self.load_scales = np.array([0.25, 1, 8])
|
||||
#self.load_scales = np.array([0.25, 1])
|
||||
self.load = tech.spice["msflop_in_cap"]
|
||||
self.load = tech.spice["dff_in_cap"]
|
||||
self.loads = self.load_scales*self.load
|
||||
debug.info(1,"Loads: {0}".format(self.loads))
|
||||
|
||||
@@ -78,7 +59,7 @@ class lib:
|
||||
proc,
|
||||
volt,
|
||||
temp)
|
||||
self.corner_name = self.corner_name.replace(".","") # Remove decimals
|
||||
self.corner_name = self.corner_name.replace(".","p") # Remove decimals
|
||||
lib_name = self.out_dir+"{}.lib".format(self.corner_name)
|
||||
|
||||
# A corner is a tuple of PVT
|
||||
@@ -93,10 +74,15 @@ class lib:
|
||||
self.lib = open(lib_name, "w")
|
||||
debug.info(1,"Writing to {0}".format(lib_name))
|
||||
self.characterize()
|
||||
self.lib.close()
|
||||
|
||||
def characterize(self):
|
||||
""" Characterize the current corner. """
|
||||
|
||||
self.compute_delay()
|
||||
|
||||
self.compute_setup_hold()
|
||||
|
||||
self.write_header()
|
||||
|
||||
self.write_data_bus()
|
||||
@@ -106,8 +92,13 @@ class lib:
|
||||
self.write_control_pins()
|
||||
|
||||
self.write_clk()
|
||||
|
||||
self.write_footer()
|
||||
|
||||
|
||||
self.lib.close()
|
||||
def write_footer(self):
|
||||
""" Write the footer """
|
||||
self.lib.write("}\n")
|
||||
|
||||
def write_header(self):
|
||||
""" Write the header information """
|
||||
@@ -119,7 +110,7 @@ class lib:
|
||||
self.write_defaults()
|
||||
self.write_LUT_templates()
|
||||
|
||||
self.lib.write(" default_operating_conditions : TT; \n")
|
||||
self.lib.write(" default_operating_conditions : OC; \n")
|
||||
|
||||
self.write_bus()
|
||||
|
||||
@@ -127,14 +118,21 @@ class lib:
|
||||
self.lib.write("{\n")
|
||||
self.lib.write(" memory(){ \n")
|
||||
self.lib.write(" type : ram;\n")
|
||||
self.lib.write(" address_width : {0};\n".format(self.sram.addr_size))
|
||||
self.lib.write(" word_width : {0};\n".format(self.sram.word_size))
|
||||
self.lib.write(" address_width : {};\n".format(self.sram.addr_size))
|
||||
self.lib.write(" word_width : {};\n".format(self.sram.word_size))
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" interface_timing : true;\n")
|
||||
self.lib.write(" dont_use : true;\n")
|
||||
self.lib.write(" map_only : true;\n")
|
||||
self.lib.write(" dont_touch : true;\n")
|
||||
self.lib.write(" area : {0};\n\n".format(self.sram.width * self.sram.height))
|
||||
self.lib.write(" area : {};\n\n".format(self.sram.width * self.sram.height))
|
||||
|
||||
# Leakage is included in dynamic when macro is enabled
|
||||
self.lib.write(" leakage_power () {\n")
|
||||
self.lib.write(" when : \"CSb\";\n")
|
||||
self.lib.write(" value : {};\n".format(self.char_results["leakage_power"]))
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" cell_leakage_power : {};\n".format(0))
|
||||
|
||||
|
||||
def write_units(self):
|
||||
@@ -147,7 +145,8 @@ class lib:
|
||||
self.lib.write(" capacitive_load_unit(1 ,fF) ;\n")
|
||||
self.lib.write(" leakage_power_unit : \"1mW\" ;\n")
|
||||
self.lib.write(" pulling_resistance_unit :\"1kohm\" ;\n")
|
||||
self.lib.write(" operating_conditions({}){{\n".format(self.process))
|
||||
self.lib.write(" operating_conditions(OC){\n")
|
||||
self.lib.write(" process : {} ;\n".format(1.0)) # How to use TT, FF, SS?
|
||||
self.lib.write(" voltage : {} ;\n".format(self.voltage))
|
||||
self.lib.write(" temperature : {};\n".format(self.temperature))
|
||||
self.lib.write(" }\n\n")
|
||||
@@ -164,6 +163,10 @@ class lib:
|
||||
self.lib.write(" slew_lower_threshold_pct_rise : 10.0 ;\n")
|
||||
self.lib.write(" slew_upper_threshold_pct_rise : 90.0 ;\n\n")
|
||||
|
||||
self.lib.write(" nom_voltage : {};\n".format(tech.spice["nom_supply_voltage"]))
|
||||
self.lib.write(" nom_temperature : {};\n".format(tech.spice["nom_temperature"]))
|
||||
self.lib.write(" nom_process : {};\n".format(1.0))
|
||||
|
||||
self.lib.write(" default_cell_leakage_power : 0.0 ;\n")
|
||||
self.lib.write(" default_leakage_power_density : 0.0 ;\n")
|
||||
self.lib.write(" default_input_pin_cap : 1.0 ;\n")
|
||||
@@ -267,8 +270,6 @@ class lib:
|
||||
def write_FF_setuphold(self):
|
||||
""" Adds Setup and Hold timing results"""
|
||||
|
||||
self.compute_setup_hold()
|
||||
|
||||
self.lib.write(" timing(){ \n")
|
||||
self.lib.write(" timing_type : setup_rising; \n")
|
||||
self.lib.write(" related_pin : \"clk\"; \n")
|
||||
@@ -299,12 +300,12 @@ class lib:
|
||||
def write_data_bus(self):
|
||||
""" Adds data bus timing results."""
|
||||
|
||||
self.compute_delay()
|
||||
|
||||
self.lib.write(" bus(DATA){\n")
|
||||
self.lib.write(" bus_type : DATA; \n")
|
||||
self.lib.write(" direction : inout; \n")
|
||||
self.lib.write(" max_capacitance : {0}; \n".format(8*tech.spice["msflop_in_cap"]))
|
||||
# This is conservative, but limit to range that we characterized.
|
||||
self.lib.write(" max_capacitance : {0}; \n".format(max(self.loads)))
|
||||
self.lib.write(" min_capacitance : {0}; \n".format(min(self.loads)))
|
||||
self.lib.write(" three_state : \"!OEb & !clk\"; \n")
|
||||
self.lib.write(" memory_write(){ \n")
|
||||
self.lib.write(" address : ADDR; \n")
|
||||
@@ -313,53 +314,29 @@ class lib:
|
||||
self.lib.write(" memory_read(){ \n")
|
||||
self.lib.write(" address : ADDR; \n")
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" pin(DATA[{0}:0])".format(self.sram.word_size - 1))
|
||||
self.lib.write("{\n")
|
||||
|
||||
self.lib.write(" internal_power(){\n")
|
||||
self.lib.write(" when : \"OEb & !clk\"; \n")
|
||||
self.lib.write(" rise_power(scalar){\n")
|
||||
self.lib.write(" values(\"{0}\");\n".format(self.delay["write1_power"]))
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" fall_power(scalar){\n")
|
||||
self.lib.write(" values(\"{0}\");\n".format(self.delay["write0_power"]))
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" }\n")
|
||||
|
||||
self.lib.write(" pin(DATA[{0}:0]){{\n".format(self.sram.word_size - 1))
|
||||
self.write_FF_setuphold()
|
||||
|
||||
self.lib.write(" internal_power(){\n")
|
||||
self.lib.write(" when : \"!OEb & !clk\"; \n")
|
||||
self.lib.write(" rise_power(scalar){\n")
|
||||
self.lib.write(" values(\"{0}\");\n".format(self.delay["read1_power"]))
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" fall_power(scalar){\n")
|
||||
self.lib.write(" values(\"{0}\");\n".format(self.delay["read0_power"]))
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" timing(){ \n")
|
||||
self.lib.write(" timing_sense : non_unate; \n")
|
||||
self.lib.write(" related_pin : \"clk\"; \n")
|
||||
self.lib.write(" timing_type : falling_edge; \n")
|
||||
self.lib.write(" cell_rise(CELL_TABLE) {\n")
|
||||
rounded_values = map(ch.round_time,self.delay["delay1"])
|
||||
self.write_values(rounded_values,len(self.loads)," ")
|
||||
self.lib.write(" }\n")
|
||||
self.write_values(self.char_results["delay_lh"],len(self.loads)," ")
|
||||
self.lib.write(" }\n") # rise delay
|
||||
self.lib.write(" cell_fall(CELL_TABLE) {\n")
|
||||
rounded_values = map(ch.round_time,self.delay["delay0"])
|
||||
self.write_values(rounded_values,len(self.loads)," ")
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" rise_transition(CELL_TABLE) {\n")
|
||||
rounded_values = map(ch.round_time,self.delay["slew1"])
|
||||
self.write_values(rounded_values,len(self.loads)," ")
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" fall_transition(CELL_TABLE) {\n")
|
||||
rounded_values = map(ch.round_time,self.delay["slew0"])
|
||||
self.write_values(rounded_values,len(self.loads)," ")
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" }\n\n")
|
||||
self.write_values(self.char_results["delay_hl"],len(self.loads)," ")
|
||||
self.lib.write(" }\n") # fall delay
|
||||
self.lib.write(" rise_transition(CELL_TABLE) {\n")
|
||||
self.write_values(self.char_results["slew_lh"],len(self.loads)," ")
|
||||
self.lib.write(" }\n") # rise trans
|
||||
self.lib.write(" fall_transition(CELL_TABLE) {\n")
|
||||
self.write_values(self.char_results["slew_hl"],len(self.loads)," ")
|
||||
self.lib.write(" }\n") # fall trans
|
||||
self.lib.write(" }\n") # timing
|
||||
self.lib.write(" }\n") # pin
|
||||
self.lib.write(" }\n\n") # bus
|
||||
|
||||
|
||||
def write_addr_bus(self):
|
||||
@@ -368,9 +345,8 @@ class lib:
|
||||
self.lib.write(" bus(ADDR){\n")
|
||||
self.lib.write(" bus_type : ADDR; \n")
|
||||
self.lib.write(" direction : input; \n")
|
||||
self.lib.write(" capacitance : {0}; \n".format(tech.spice["msflop_in_cap"]))
|
||||
self.lib.write(" capacitance : {0}; \n".format(tech.spice["dff_in_cap"]))
|
||||
self.lib.write(" max_transition : {0};\n".format(self.slews[-1]))
|
||||
self.lib.write(" fanout_load : 1.000000;\n")
|
||||
self.lib.write(" pin(ADDR[{0}:0])".format(self.sram.addr_size - 1))
|
||||
self.lib.write("{\n")
|
||||
|
||||
@@ -387,7 +363,7 @@ class lib:
|
||||
self.lib.write(" pin({0})".format(i))
|
||||
self.lib.write("{\n")
|
||||
self.lib.write(" direction : input; \n")
|
||||
self.lib.write(" capacitance : {0}; \n".format(tech.spice["msflop_in_cap"]))
|
||||
self.lib.write(" capacitance : {0}; \n".format(tech.spice["dff_in_cap"]))
|
||||
self.write_FF_setuphold()
|
||||
self.lib.write(" }\n\n")
|
||||
|
||||
@@ -395,14 +371,50 @@ class lib:
|
||||
def write_clk(self):
|
||||
""" Adds clk pin timing results."""
|
||||
|
||||
self.compute_delay()
|
||||
|
||||
self.lib.write(" pin(clk){\n")
|
||||
self.lib.write(" clock : true;\n")
|
||||
self.lib.write(" direction : input; \n")
|
||||
self.lib.write(" capacitance : {0}; \n".format(tech.spice["msflop_in_cap"]))
|
||||
min_pulse_width = ch.round_time(self.delay["min_period"])/2.0
|
||||
min_period = ch.round_time(self.delay["min_period"])
|
||||
# This should actually be a min inverter cap, but ok...
|
||||
self.lib.write(" capacitance : {0}; \n".format(tech.spice["dff_in_cap"]))
|
||||
|
||||
# Find the average power of 1 and 0 bits for writes and reads over all loads/slews
|
||||
# Could make it a table, but this is fine for now.
|
||||
avg_write_power = np.mean(self.char_results["write1_power"] + self.char_results["write0_power"])
|
||||
avg_read_power = np.mean(self.char_results["read1_power"] + self.char_results["read0_power"])
|
||||
|
||||
# Equally divide read/write power between first and second half of clock period
|
||||
self.lib.write(" internal_power(){\n")
|
||||
self.lib.write(" when : \"!CSb & clk & !WEb\"; \n")
|
||||
self.lib.write(" rise_power(scalar){\n")
|
||||
self.lib.write(" values(\"{0}\");\n".format(avg_write_power/2.0))
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" fall_power(scalar){\n")
|
||||
self.lib.write(" values(\"{0}\");\n".format(avg_write_power/2.0))
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" }\n")
|
||||
|
||||
self.lib.write(" internal_power(){\n")
|
||||
self.lib.write(" when : \"!CSb & !clk & WEb\"; \n")
|
||||
self.lib.write(" rise_power(scalar){\n")
|
||||
self.lib.write(" values(\"{0}\");\n".format(avg_read_power/2.0))
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" fall_power(scalar){\n")
|
||||
self.lib.write(" values(\"{0}\");\n".format(avg_read_power/2.0))
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" }\n")
|
||||
# Have 0 internal power when disabled, this will be represented as leakage power.
|
||||
self.lib.write(" internal_power(){\n")
|
||||
self.lib.write(" when : \"CSb\"; \n")
|
||||
self.lib.write(" rise_power(scalar){\n")
|
||||
self.lib.write(" values(\"0\");\n")
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" fall_power(scalar){\n")
|
||||
self.lib.write(" values(\"0\");\n")
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" }\n")
|
||||
|
||||
min_pulse_width = ch.round_time(self.char_results["min_period"])/2.0
|
||||
min_period = ch.round_time(self.char_results["min_period"])
|
||||
self.lib.write(" timing(){ \n")
|
||||
self.lib.write(" timing_type :\"min_pulse_width\"; \n")
|
||||
self.lib.write(" related_pin : clk; \n")
|
||||
@@ -425,23 +437,20 @@ class lib:
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write("}\n")
|
||||
|
||||
def compute_delay(self):
|
||||
""" Do the analysis if we haven't characterized the SRAM yet """
|
||||
try:
|
||||
self.d
|
||||
except AttributeError:
|
||||
self.d = delay.delay(self.sram, self.sim_sp_file, self.corner)
|
||||
self.d = delay.delay(self.sram, self.sp_file, self.corner)
|
||||
if self.use_model:
|
||||
self.delay = self.d.analytical_model(self.sram,self.slews,self.loads)
|
||||
self.char_results = self.d.analytical_delay(self.sram,self.slews,self.loads)
|
||||
else:
|
||||
probe_address = "1" * self.sram.addr_size
|
||||
probe_data = self.sram.word_size - 1
|
||||
# We must trim based on a specific address and data bit
|
||||
if OPTS.trim_netlist:
|
||||
self.trimsp.trim(probe_address,probe_data)
|
||||
self.delay = self.d.analyze(probe_address, probe_data, self.slews, self.loads)
|
||||
self.char_results = self.d.analyze(probe_address, probe_data, self.slews, self.loads)
|
||||
|
||||
|
||||
def compute_setup_hold(self):
|
||||
""" Do the analysis if we haven't characterized a FF yet """
|
||||
@@ -451,7 +460,7 @@ class lib:
|
||||
except AttributeError:
|
||||
self.sh = setup_hold.setup_hold(self.corner)
|
||||
if self.use_model:
|
||||
self.times = self.sh.analytical_model(self.slews,self.loads)
|
||||
self.times = self.sh.analytical_setuphold(self.slews,self.loads)
|
||||
else:
|
||||
self.times = self.sh.analyze(self.slews,self.slews)
|
||||
|
||||
|
||||
@@ -299,7 +299,7 @@ class setup_hold():
|
||||
}
|
||||
return times
|
||||
|
||||
def analytical_model(self,related_slews, constrained_slews):
|
||||
def analytical_setuphold(self,related_slews, constrained_slews):
|
||||
""" Just return the fixed setup/hold times from the technology.
|
||||
"""
|
||||
LH_setup = []
|
||||
|
||||
@@ -286,6 +286,7 @@ class stimuli():
|
||||
OPTS.openram_temp)
|
||||
valid_retcode=0
|
||||
else:
|
||||
# ngspice 27+ supports threading with "set num_threads=4" in the stimulus file or a .spiceinit
|
||||
cmd = "{0} -b -o {2}timing.lis {1}".format(OPTS.spice_exe,
|
||||
temp_stim,
|
||||
OPTS.openram_temp)
|
||||
|
||||
Reference in New Issue
Block a user