2018-02-21 22:38:43 +01:00
import sys , re , shutil
2016-11-08 18:57:35 +01:00
import debug
import tech
import math
2018-05-12 01:32:00 +02:00
from . stimuli import *
from . trim_spice import *
from . charutils import *
2016-11-11 18:41:43 +01:00
import utils
2017-11-16 22:52:58 +01:00
from globals import OPTS
2016-11-08 18:57:35 +01:00
class delay ( ) :
2018-02-21 22:38:43 +01:00
""" Functions to measure the delay and power of an SRAM at a given address and
2016-11-08 18:57:35 +01:00
data bit .
2018-02-21 22:38:43 +01:00
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 .
2016-11-08 18:57:35 +01:00
"""
2018-02-21 22:38:43 +01:00
def __init__ ( self , sram , spfile , corner ) :
self . sram = sram
2016-11-08 18:57:35 +01:00
self . name = sram . name
2018-02-21 22:38:43 +01:00
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
2017-11-14 22:24:14 +01:00
2018-02-22 20:14:58 +01:00
# These are the member variables for a simulation
self . period = 0
2018-02-26 17:54:35 +01:00
self . set_load_slew ( 0 , 0 )
2018-02-10 00:33:03 +01:00
self . set_corner ( corner )
2018-02-26 17:54:35 +01:00
2018-02-10 00:33:03 +01:00
def set_corner ( self , corner ) :
""" Set the corner values """
self . corner = corner
( self . process , self . vdd_voltage , self . temperature ) = corner
2018-02-26 17:54:35 +01:00
def set_load_slew ( self , load , slew ) :
""" Set the load and slew """
self . load = load
self . slew = slew
2017-11-23 00:57:29 +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 )
2018-08-28 22:01:35 +02:00
#Adding port options here which the characterizer cannot handle. Some may be added later like ROM
2018-08-30 09:11:14 +02:00
if len ( self . targ_readwrite_ports ) == 0 and len ( self . targ_write_ports ) == 0 and len ( self . targ_read_ports ) == 0 :
debug . error ( " No ports selected for characterization. " , 1 )
2018-08-31 02:08:34 +02:00
if len ( self . readwrite_ports ) == 0 and len ( self . read_ports ) == 0 :
2018-08-28 22:01:35 +02:00
debug . error ( " Characterizer does not currently support SRAMs without read ports. " , 1 )
2018-08-31 02:08:34 +02:00
if len ( self . readwrite_ports ) == 0 and len ( self . write_ports ) == 0 :
2018-08-28 22:01:35 +02:00
debug . error ( " Characterizer does not currently support SRAMs without write ports. " , 1 )
2016-11-08 18:57:35 +01:00
2018-02-22 20:14:58 +01:00
def write_generic_stimulus ( self ) :
2018-02-21 22:38:43 +01:00
""" Create the instance, supplies, loads, and access transistors. """
2016-11-08 18:57:35 +01:00
# add vdd/gnd statements
2018-02-07 23:04:18 +01:00
self . sf . write ( " \n * Global Power Supplies \n " )
2018-02-10 00:33:03 +01:00
self . stim . write_supply ( )
2016-11-08 18:57:35 +01:00
# instantiate the sram
2018-02-07 23:04:18 +01:00
self . sf . write ( " \n * Instantiation of the SRAM \n " )
2018-02-10 00:33:03 +01:00
self . stim . inst_sram ( abits = self . addr_size ,
dbits = self . word_size ,
2018-08-28 21:09:02 +02:00
port_names = ( self . readwrite_ports , self . read_ports , self . write_ports ) ,
2018-02-10 00:33:03 +01:00
sram_name = self . name )
2016-11-08 18:57:35 +01:00
2018-02-07 23:04:18 +01:00
self . sf . write ( " \n * SRAM output loads \n " )
2018-08-23 08:45:43 +02:00
for readwrite_output in range ( OPTS . rw_ports ) :
for i in range ( self . word_size ) :
self . sf . write ( " CD_RWP {0} {1} DOUT_RWP {0} [ {1} ] 0 {2} f \n " . format ( readwrite_output , i , self . load ) )
for read_port in range ( OPTS . r_ports ) :
for i in range ( self . word_size ) :
self . sf . write ( " CD_RP {0} {1} DOUT_RP {0} [ {1} ] 0 {2} f \n " . format ( read_port , i , self . load ) )
2018-02-21 22:38:43 +01:00
2016-11-08 18:57:35 +01:00
2018-02-22 20:14:58 +01:00
def write_delay_stimulus ( self ) :
2018-02-21 22:38:43 +01:00
""" 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
2018-07-27 23:06:59 +02:00
self . create_test_cycles ( )
2016-11-08 18:57:35 +01:00
2018-02-21 22:38:43 +01:00
# creates and opens stimulus file for writing
temp_stim = " {0} /stim.sp " . format ( OPTS . openram_temp )
self . sf = open ( temp_stim , " w " )
2018-02-22 20:14:58 +01:00
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 ) )
2018-05-12 01:32:00 +02:00
self . stim = stimuli ( self . sf , self . corner )
2018-02-21 22:38:43 +01:00
# include files in stimulus file
self . stim . write_include ( self . trim_sp_file )
2018-02-22 20:14:58 +01:00
self . write_generic_stimulus ( )
2018-02-21 22:38:43 +01:00
2016-11-08 18:57:35 +01:00
# generate data and addr signals
2018-02-07 23:04:18 +01:00
self . sf . write ( " \n * Generation of data and address signals \n " )
2018-07-27 20:36:17 +02:00
self . gen_data ( )
self . gen_addr ( )
2018-02-22 20:14:58 +01:00
2016-11-08 18:57:35 +01:00
2017-07-06 17:42:25 +02:00
# generate control signals
2018-02-07 23:04:18 +01:00
self . sf . write ( " \n * Generation of control signals \n " )
2018-07-27 20:36:17 +02:00
self . gen_control ( )
2016-11-08 18:57:35 +01:00
2018-02-07 23:04:18 +01:00
self . sf . write ( " \n * Generation of global clock signal \n " )
2018-02-10 00:33:03 +01:00
self . stim . gen_pulse ( sig_name = " CLK " ,
2018-02-21 22:38:43 +01:00
v1 = 0 ,
2018-02-10 00:33:03 +01:00
v2 = self . vdd_voltage ,
2018-02-22 20:14:58 +01:00
offset = self . period ,
period = self . period ,
t_rise = self . slew ,
t_fall = self . slew )
2017-07-06 17:42:25 +02:00
2018-02-22 20:14:58 +01:00
self . write_delay_measures ( )
2017-07-06 17:42:25 +02:00
2018-02-05 23:07:12 +01:00
# run until the end of the cycle time
2018-02-22 20:14:58 +01:00
self . stim . write_control ( self . cycle_times [ - 1 ] + self . period )
2016-11-08 18:57:35 +01:00
2017-07-06 17:42:25 +02:00
self . sf . close ( )
2018-02-21 22:38:43 +01:00
2018-02-22 20:14:58 +01:00
def write_power_stimulus ( self , trim ) :
2018-02-21 22:38:43 +01:00
""" 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
2018-07-27 23:06:59 +02:00
self . create_test_cycles ( )
2018-02-21 22:38:43 +01:00
# creates and opens stimulus file for writing
temp_stim = " {0} /stim.sp " . format ( OPTS . openram_temp )
self . sf = open ( temp_stim , " w " )
2018-02-22 20:14:58 +01:00
self . sf . write ( " * Power stimulus for period of {0} n \n \n " . format ( self . period ) )
2018-07-26 22:58:50 +02:00
self . stim = stimuli ( self . sf , self . corner )
2018-02-21 22:38:43 +01:00
# 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 )
2018-02-22 20:14:58 +01:00
self . write_generic_stimulus ( )
2018-02-21 22:38:43 +01:00
# generate data and addr signals
self . sf . write ( " \n * Generation of data and address signals \n " )
2018-08-23 08:45:43 +02:00
for readwrite_input in range ( OPTS . rw_ports ) :
for i in range ( self . word_size ) :
self . stim . gen_constant ( sig_name = " DIN_RWP {0} [ {1} ] " . format ( readwrite_input , i ) ,
v_val = 0 )
for write_port in range ( OPTS . w_ports ) :
for i in range ( self . word_size ) :
self . stim . gen_constant ( sig_name = " DIN_WP {0} [ {1} ] " . format ( write_port , i ) ,
v_val = 0 )
2018-02-21 22:38:43 +01:00
for i in range ( self . addr_size ) :
self . stim . gen_constant ( sig_name = " A[ {0} ] " . format ( i ) ,
v_val = 0 )
2018-08-23 23:49:56 +02:00
for readwrite_addr in range ( OPTS . rw_ports ) :
for i in range ( self . addr_size ) :
self . stim . gen_constant ( sig_name = " A_RWP {0} [ {1} ] " . format ( readwrite_addr , i ) ,
v_val = 0 )
for write_addr in range ( OPTS . w_ports ) :
for i in range ( self . addr_size ) :
self . stim . gen_constant ( sig_name = " A_WP {0} [ {1} ] " . format ( write_addr , i ) ,
v_val = 0 )
for read_addr in range ( OPTS . r_ports ) :
for i in range ( self . addr_size ) :
self . stim . gen_constant ( sig_name = " A_RP {0} [ {1} ] " . format ( read_addr , i ) ,
v_val = 0 )
2018-02-21 22:38:43 +01:00
# 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 . sf . write ( " \n * Generation of global clock signal \n " )
self . stim . gen_constant ( sig_name = " CLK " , v_val = 0 )
2018-02-22 20:14:58 +01:00
self . write_power_measures ( )
2018-02-21 22:38:43 +01:00
# run until the end of the cycle time
2018-02-22 20:14:58 +01:00
self . stim . write_control ( 2 * self . period )
2018-02-21 22:38:43 +01:00
self . sf . close ( )
2018-08-29 09:43:27 +02:00
def write_delay_measures_read_port ( self , port ) :
2018-02-07 23:04:18 +01:00
"""
2018-08-29 09:43:27 +02:00
Write the measure statements to quantify the delay and power results for a read port .
2018-02-07 23:04:18 +01:00
"""
# Trigger on the clk of the appropriate cycle
2017-07-06 17:42:25 +02:00
trig_name = " clk "
2018-08-23 08:45:43 +02:00
#Target name should be an input to the function or a member variable. That way, the ports can be singled out for testing
2018-08-29 09:01:22 +02:00
targ_name = " {0} " . format ( " DOUT_ {0} [ {1} ] " . format ( port , self . probe_data ) )
2018-02-10 00:33:03 +01:00
trig_val = targ_val = 0.5 * self . vdd_voltage
2018-02-07 23:04:18 +01:00
# Delay the target to measure after the negative edge
2018-08-29 09:01:22 +02:00
self . stim . gen_meas_delay ( meas_name = " DELAY_HL_ {0} " . format ( port ) ,
2018-02-10 00:33:03 +01:00
trig_name = trig_name ,
targ_name = targ_name ,
trig_val = trig_val ,
targ_val = targ_val ,
2018-07-26 22:58:50 +02:00
trig_dir = " RISE " ,
2018-02-10 00:33:03 +01:00
targ_dir = " FALL " ,
2018-08-30 00:13:31 +02:00
trig_td = self . cycle_times [ self . measure_cycles [ " read0_ {0} " . format ( port ) ] ] ,
targ_td = self . cycle_times [ self . measure_cycles [ " read0_ {0} " . format ( port ) ] ] )
2018-02-10 00:33:03 +01:00
2018-08-29 09:01:22 +02:00
self . stim . gen_meas_delay ( meas_name = " DELAY_LH_ {0} " . format ( port ) ,
2018-02-10 00:33:03 +01:00
trig_name = trig_name ,
targ_name = targ_name ,
trig_val = trig_val ,
targ_val = targ_val ,
2018-07-26 22:58:50 +02:00
trig_dir = " RISE " ,
2018-02-10 00:33:03 +01:00
targ_dir = " RISE " ,
2018-08-30 00:13:31 +02:00
trig_td = self . cycle_times [ self . measure_cycles [ " read1_ {0} " . format ( port ) ] ] ,
targ_td = self . cycle_times [ self . measure_cycles [ " read1_ {0} " . format ( port ) ] ] )
2018-02-10 00:33:03 +01:00
2018-08-29 09:01:22 +02:00
self . stim . gen_meas_delay ( meas_name = " SLEW_HL_ {0} " . format ( port ) ,
2018-02-10 00:33:03 +01:00
trig_name = targ_name ,
targ_name = targ_name ,
trig_val = 0.9 * self . vdd_voltage ,
targ_val = 0.1 * self . vdd_voltage ,
trig_dir = " FALL " ,
targ_dir = " FALL " ,
2018-08-30 00:13:31 +02:00
trig_td = self . cycle_times [ self . measure_cycles [ " read0_ {0} " . format ( port ) ] ] ,
targ_td = self . cycle_times [ self . measure_cycles [ " read0_ {0} " . format ( port ) ] ] )
2018-02-10 00:33:03 +01:00
2018-08-29 09:01:22 +02:00
self . stim . gen_meas_delay ( meas_name = " SLEW_LH_ {0} " . format ( port ) ,
2018-02-10 00:33:03 +01:00
trig_name = targ_name ,
targ_name = targ_name ,
trig_val = 0.1 * self . vdd_voltage ,
targ_val = 0.9 * self . vdd_voltage ,
trig_dir = " RISE " ,
targ_dir = " RISE " ,
2018-08-30 00:13:31 +02:00
trig_td = self . cycle_times [ self . measure_cycles [ " read1_ {0} " . format ( port ) ] ] ,
targ_td = self . cycle_times [ self . measure_cycles [ " read1_ {0} " . format ( port ) ] ] )
2017-07-06 17:42:25 +02:00
2016-11-08 18:57:35 +01:00
# add measure statements for power
2018-08-30 00:13:31 +02:00
t_initial = self . cycle_times [ self . measure_cycles [ " read0_ {0} " . format ( port ) ] ]
t_final = self . cycle_times [ self . measure_cycles [ " read0_ {0} " . format ( port ) ] + 1 ]
2018-08-29 09:01:22 +02:00
self . stim . gen_meas_power ( meas_name = " READ0_POWER_ {0} " . format ( port ) ,
2018-02-10 00:33:03 +01:00
t_initial = t_initial ,
t_final = t_final )
2016-11-08 18:57:35 +01:00
2018-08-30 00:13:31 +02:00
t_initial = self . cycle_times [ self . measure_cycles [ " read1_ {0} " . format ( port ) ] ]
t_final = self . cycle_times [ self . measure_cycles [ " read1_ {0} " . format ( port ) ] + 1 ]
2018-08-29 09:01:22 +02:00
self . stim . gen_meas_power ( meas_name = " READ1_POWER_ {0} " . format ( port ) ,
2018-02-10 00:33:03 +01:00
t_initial = t_initial ,
t_final = t_final )
2018-08-29 09:43:27 +02:00
def write_delay_measures_write_port ( self , port ) :
"""
Write the measure statements to quantify the power results for a write port .
"""
# add measure statements for power
2018-08-30 00:13:31 +02:00
t_initial = self . cycle_times [ self . measure_cycles [ " write0_ {0} " . format ( port ) ] ]
t_final = self . cycle_times [ self . measure_cycles [ " write0_ {0} " . format ( port ) ] + 1 ]
2018-08-29 09:43:27 +02:00
self . stim . gen_meas_power ( meas_name = " WRITE0_POWER_ {0} " . format ( port ) ,
t_initial = t_initial ,
t_final = t_final )
2018-08-30 00:13:31 +02:00
t_initial = self . cycle_times [ self . measure_cycles [ " write1_ {0} " . format ( port ) ] ]
t_final = self . cycle_times [ self . measure_cycles [ " write1_ {0} " . format ( port ) ] + 1 ]
2018-08-29 09:43:27 +02:00
self . stim . gen_meas_power ( meas_name = " WRITE1_POWER_ {0} " . format ( port ) ,
t_initial = t_initial ,
t_final = t_final )
2018-02-21 22:38:43 +01:00
2018-08-29 09:01:22 +02:00
def write_delay_measures ( self ) :
"""
2018-08-31 02:08:34 +02:00
Write the measure statements to quantify the delay and power results for all targeted ports .
2018-08-29 09:01:22 +02:00
"""
self . sf . write ( " \n * Measure statements for delay and power \n " )
# Output some comments to aid where cycles start and
# what is happening
for comment in self . cycle_comments :
self . sf . write ( " * {} \n " . format ( comment ) )
2018-08-31 02:08:34 +02:00
for readwrite_port in self . targ_readwrite_ports :
2018-08-29 09:43:27 +02:00
self . write_delay_measures_read_port ( readwrite_port )
self . write_delay_measures_write_port ( readwrite_port )
2018-08-31 02:08:34 +02:00
for read_port in self . targ_read_ports :
2018-08-29 09:43:27 +02:00
self . write_delay_measures_read_port ( read_port )
2018-08-31 02:08:34 +02:00
for write_port in self . targ_write_ports :
2018-08-29 09:43:27 +02:00
self . write_delay_measures_write_port ( write_port )
2018-02-21 22:38:43 +01:00
2018-08-29 09:01:22 +02:00
2018-02-22 20:14:58 +01:00
def write_power_measures ( self ) :
2018-02-21 22:38:43 +01:00
"""
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
2018-02-22 20:14:58 +01:00
t_initial = self . period
t_final = 2 * self . period
2018-02-22 00:45:49 +01:00
self . stim . gen_meas_power ( meas_name = " leakage_power " ,
2018-02-21 22:38:43 +01:00
t_initial = t_initial ,
t_final = t_final )
2017-07-06 17:42:25 +02:00
2018-02-22 20:14:58 +01:00
def find_feasible_period ( self ) :
2018-02-07 23:04:18 +01:00
"""
Uses an initial period and finds a feasible period before we
2016-11-08 18:57:35 +01:00
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
2018-02-07 23:04:18 +01:00
starting point .
"""
2018-08-31 07:43:56 +02:00
feasible_delays_lh = { }
feasible_delays_hl = { }
2018-02-25 20:13:43 +01:00
feasible_period = float ( tech . spice [ " feasible_period " ] )
2018-08-22 23:19:09 +02:00
#feasible_period = float(2.5)#What happens if feasible starting point is wrong?
2018-08-30 09:11:14 +02:00
time_out = 9
2016-11-08 18:57:35 +01:00
while True :
time_out - = 1
if ( time_out < = 0 ) :
debug . error ( " Timed out, could not find a feasible period. " , 2 )
2018-08-30 09:11:14 +02:00
#Clear any write target ports
self . targ_write_ports = [ ]
success = False
2018-08-31 02:08:34 +02:00
#Loops through all the ports checks if the feasible period works. Everything restarts it if does not.
#Write ports do not produce delays which is why they are not included here.
2018-08-30 09:11:14 +02:00
for port in self . readwrite_ports + self . read_ports :
debug . info ( 1 , " Trying feasible period: {0} ns on Port {1} " . format ( feasible_period , port ) )
self . period = feasible_period
#Test one port at a time. Using this weird logic to avoid two for loops. Will likely change later.
if port in self . readwrite_ports :
self . targ_readwrite_ports = [ port ]
else :
self . targ_read_ports = [ port ]
( success , results ) = self . run_delay_simulation ( )
#Clear these target ports after every simulation
self . targ_readwrite_ports = [ ]
self . targ_read_ports = [ ]
if not success :
feasible_period = 2 * feasible_period
break
feasible_delay_lh = results [ " delay_lh_ {0} " . format ( port ) ]
feasible_delay_hl = results [ " delay_hl_ {0} " . format ( port ) ]
2018-08-31 07:43:56 +02:00
feasible_slew_lh = results [ " slew_lh_ {0} " . format ( port ) ]
2018-08-30 09:11:14 +02:00
feasible_slew_hl = results [ " slew_hl_ {0} " . format ( port ) ]
delay_str = " feasible_delay {0:.4f} ns/ {1:.4f} ns " . format ( feasible_delay_lh , feasible_delay_hl )
slew_str = " slew {0:.4f} ns/ {1:.4f} ns " . format ( feasible_slew_lh , feasible_slew_hl )
2018-08-31 02:08:34 +02:00
debug . info ( 2 , " feasible_period passed for Port {3} : {0} ns {1} {2} " . format ( feasible_period ,
2018-08-30 09:11:14 +02:00
delay_str ,
slew_str ,
port ) )
2018-08-31 07:43:56 +02:00
#Add feasible delays of each port to dict
feasible_delays_lh [ port ] = feasible_delay_lh
feasible_delays_hl [ port ] = feasible_delay_hl
2018-08-30 09:11:14 +02:00
if success :
debug . info ( 1 , " Found feasible_period: {0} ns " . format ( feasible_period ) )
self . period = feasible_period
2018-08-31 07:43:56 +02:00
return ( feasible_delays_lh , feasible_delays_hl )
2016-11-08 18:57:35 +01:00
2018-08-30 07:16:42 +02:00
def parse_values ( self , values_names , mult = 1.0 ) :
2018-08-31 00:11:54 +02:00
""" Parse multiple values in the timing output file. Optional multiplier. """
2018-08-30 07:16:42 +02:00
values = [ ]
2018-08-31 00:11:54 +02:00
all_values_floats = True
2018-08-30 07:16:42 +02:00
for vname in values_names :
2018-08-31 00:11:54 +02:00
#ngspice converts all measure characters to lowercase, not tested on other sims
value = parse_spice_list ( " timing " , vname . lower ( ) )
#Check if any of the values fail to parse
if type ( value ) != float :
all_values_floats = False
2018-08-30 07:16:42 +02:00
values . append ( value )
2018-08-31 00:11:54 +02:00
#Apply Multiplier only if all values are floats. Let other check functions handle this error.
if all_values_floats :
return { values_names [ i ] : values [ i ] * mult for i in range ( len ( values ) ) }
else :
2018-08-31 02:08:34 +02:00
return { values_names [ i ] : values [ i ] for i in range ( len ( values ) ) }
2018-08-31 00:11:54 +02:00
2018-02-22 20:14:58 +01:00
def run_delay_simulation ( self ) :
2018-02-21 22:38:43 +01:00
"""
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 .
2018-02-07 23:04:18 +01:00
"""
2018-08-29 09:01:22 +02:00
result = { }
2016-11-08 18:57:35 +01:00
# Checking from not data_value to data_value
2018-02-22 20:14:58 +01:00
self . write_delay_stimulus ( )
2018-07-26 00:50:49 +02:00
2018-02-10 00:33:03 +01:00
self . stim . run_sim ( )
2018-08-29 09:01:22 +02:00
2018-08-31 02:08:34 +02:00
#Loop through all targeted ports and collect delays and powers. Logic kept to a single for loop to reduce code but logic is inefficient. Should be changed.
#Separating into 3 for loops would be efficient but look ugly.
for port in self . targ_readwrite_ports + self . targ_read_ports + self . targ_write_ports :
#Currently, write ports do not produce delays. Only the read ports.
if port not in self . targ_write_ports :
delay_names = [ " delay_hl_ {0} " . format ( port ) , " delay_lh_ {0} " . format ( port ) ,
" slew_hl_ {0} " . format ( port ) , " slew_lh_ {0} " . format ( port ) ]
delays = self . parse_values ( delay_names , 1e9 ) # scale delays to ns
if not self . check_valid_delays ( ( delays [ delay_names [ 0 ] ] , delays [ delay_names [ 1 ] ] , delays [ delay_names [ 2 ] ] , delays [ delay_names [ 3 ] ] ) ) :
return ( False , { } )
result . update ( delays )
2018-08-30 07:16:42 +02:00
2018-08-31 02:08:34 +02:00
#Determine port type, inefficient logic.
power_names = [ ]
if port in self . targ_readwrite_ports :
power_names = [ " read0_power_ {0} " . format ( port ) , " write0_power_ {0} " . format ( port ) ,
" read1_power_ {0} " . format ( port ) , " write1_power_ {0} " . format ( port ) ]
elif port in self . targ_read_ports :
power_names = [ " read0_power_ {0} " . format ( port ) , " read1_power_ {0} " . format ( port ) ]
else : #Write port
power_names = [ " write0_power_ {0} " . format ( port ) , " write1_power_ {0} " . format ( port ) ]
2018-08-30 10:18:34 +02:00
powers = self . parse_values ( power_names , 1e3 ) # scale power to mw
2018-08-31 00:11:54 +02:00
#Check that power parsing worked.
for key , value in powers . items ( ) :
if type ( value ) != float :
read_power_str = " {3} = {0} {4} = {1} " . format ( powers [ power_names [ 0 ] ] , powers [ power_names [ 2 ] ] , power_names [ 0 ] , power_names [ 2 ] )
write_power_str = " {3} = {0} {4} = {1} " . format ( powers [ power_names [ 1 ] ] , powers [ power_names [ 3 ] ] , power_names [ 1 ] , power_names [ 3 ] )
2018-08-31 02:08:34 +02:00
debug . error ( " Failed to Parse Power Values: \n \t \t {0} " . format ( powers ) , 1 ) #Printing the entire dict looks bad.
2018-08-30 07:16:42 +02:00
result . update ( powers )
2018-08-31 02:08:34 +02:00
2017-07-06 17:42:25 +02:00
# The delay is from the negative edge for our SRAM
2018-02-21 22:38:43 +01:00
return ( True , result )
2018-02-22 20:14:58 +01:00
def run_power_simulation ( self ) :
2018-02-21 22:38:43 +01:00
"""
This simulates a disabled SRAM to get the leakage power when it is off .
2016-11-08 18:57:35 +01:00
2018-02-21 22:38:43 +01:00
"""
2018-02-22 20:14:58 +01:00
self . write_power_stimulus ( trim = False )
2018-02-21 22:38:43 +01:00
self . stim . run_sim ( )
2018-06-29 18:45:07 +02:00
leakage_power = parse_spice_list ( " timing " , " leakage_power " )
2018-02-21 22:38:43 +01:00
debug . check ( leakage_power != " Failed " , " Could not measure leakage power. " )
2018-02-22 20:14:58 +01:00
self . write_power_stimulus ( trim = True )
2018-02-21 22:38:43 +01:00
self . stim . run_sim ( )
2018-06-29 18:45:07 +02:00
trim_leakage_power = parse_spice_list ( " timing " , " leakage_power " )
2018-02-21 22:38:43 +01:00
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 )
2018-05-12 01:32:00 +02:00
def check_valid_delays ( self , delay_tuple ) :
2018-02-21 22:38:43 +01:00
""" Check if the measurements are defined and if they are valid. """
2016-11-08 18:57:35 +01:00
2018-05-12 01:32:00 +02:00
( delay_hl , delay_lh , slew_hl , slew_lh ) = delay_tuple
2018-07-27 23:06:59 +02:00
period_load_slew_str = " period {0} load {1} slew {2} " . format ( self . period , self . load , self . slew )
2018-05-12 01:32:00 +02:00
2016-11-08 18:57:35 +01:00
# if it failed or the read was longer than a period
2018-02-22 01:03:49 +01:00
if type ( delay_hl ) != float or type ( delay_lh ) != float or type ( slew_lh ) != float or type ( slew_hl ) != float :
2018-07-27 23:06:59 +02:00
delays_str = " delay_hl= {0} delay_lh= {1} " . format ( delay_hl , delay_lh )
slews_str = " slew_hl= {0} slew_lh= {1} " . format ( slew_hl , slew_lh )
debug . info ( 2 , " Failed simulation (in sec): \n \t \t {0} \n \t \t {1} \n \t \t {2} " . format ( period_load_slew_str ,
delays_str ,
slews_str ) )
2018-02-21 22:38:43 +01:00
return False
2018-08-30 07:16:42 +02:00
2018-07-27 23:06:59 +02:00
delays_str = " delay_hl= {0} delay_lh= {1} " . format ( delay_hl , delay_lh )
slews_str = " slew_hl= {0} slew_lh= {1} " . format ( slew_hl , slew_lh )
2018-02-22 20:14:58 +01:00
if delay_hl > self . period or delay_lh > self . period or slew_hl > self . period or slew_lh > self . period :
2018-07-27 23:06:59 +02:00
debug . info ( 2 , " UNsuccessful simulation (in ns): \n \t \t {0} \n \t \t {1} \n \t \t {2} " . format ( period_load_slew_str ,
delays_str ,
slews_str ) )
2018-02-21 22:38:43 +01:00
return False
2016-11-08 18:57:35 +01:00
else :
2018-07-27 23:06:59 +02:00
debug . info ( 2 , " Successful simulation (in ns): \n \t \t {0} \n \t \t {1} \n \t \t {2} " . format ( period_load_slew_str ,
delays_str ,
slews_str ) )
2016-11-08 18:57:35 +01:00
2018-02-21 22:38:43 +01:00
return True
2016-11-08 18:57:35 +01:00
2018-08-31 07:43:56 +02:00
def find_min_period ( self , feasible_delays_lh , feasible_delays_hl ) :
2018-02-07 23:04:18 +01:00
"""
Searches for the smallest period with output delays being within 5 % of
long period .
"""
2017-07-06 17:42:25 +02:00
2018-02-22 20:14:58 +01:00
previous_period = ub_period = self . period
2016-11-08 18:57:35 +01:00
lb_period = 0.0
2018-08-31 07:43:56 +02:00
target_period = 0.5 * ( ub_period + lb_period )
#Find the minimum period for all ports. Start at one port and perform binary search then use that delay as a starting position.
for port in self . readwrite_ports :
# 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 )
self . period = target_period
debug . info ( 1 , " MinPeriod Search: Port {3} {0} ns (ub: {1} lb: {2} ) " . format ( target_period ,
ub_period ,
lb_period ,
port ) )
if self . try_period ( feasible_delays_lh , feasible_delays_hl ) :
ub_period = target_period
else :
lb_period = target_period
2016-11-08 18:57:35 +01:00
2018-08-31 07:43:56 +02:00
if relative_compare ( ub_period , lb_period , error_tolerance = 0.05 ) :
# ub_period is always feasible. When done with a port, set the target period of the next port as the lower bound
# and reset the upperbound
target_period = lb_period = ub_period
ub_period = previous_period
break
#Update target
target_period = 0.5 * ( ub_period + lb_period )
return target_period
def try_period ( self , feasible_delays_lh , feasible_delays_hl ) :
2018-02-07 23:04:18 +01:00
"""
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 .
"""
2018-08-30 09:11:14 +02:00
#For debug purpose
self . targ_readwrite_ports = self . readwrite_ports
2018-08-31 07:43:56 +02:00
# Run Delay simulation but Power results not used.
( success , results ) = self . run_delay_simulation ( )
if not success :
return False
#Check the values of target readwrite and read ports. Write ports do not produce delays in this current version
for port in self . targ_readwrite_ports + self . targ_read_ports :
delay_hl = results [ " delay_hl_ {0} " . format ( port ) ]
delay_lh = results [ " delay_lh_ {0} " . format ( port ) ]
slew_hl = results [ " slew_hl_ {0} " . format ( port ) ]
slew_lh = results [ " slew_lh_ {0} " . format ( port ) ]
if not relative_compare ( delay_lh , feasible_delays_lh [ port ] , error_tolerance = 0.05 ) :
debug . info ( 2 , " Delay too big {0} vs {1} " . format ( delay_lh , feasible_delays_lh [ port ] ) )
2017-07-06 17:42:25 +02:00
return False
2018-08-31 07:43:56 +02:00
elif not relative_compare ( delay_hl , feasible_delays_hl [ port ] , error_tolerance = 0.05 ) :
debug . info ( 2 , " Delay too big {0} vs {1} " . format ( delay_hl , feasible_delays_hl [ port ] ) )
2017-07-06 17:42:25 +02:00
return False
2018-08-29 09:01:22 +02:00
#key=raw_input("press return to continue")
debug . info ( 2 , " Successful period {0} , Port {5} , 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 ,
2018-08-31 07:43:56 +02:00
port ) )
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
2018-02-21 22:38:43 +01:00
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 )
2017-11-14 22:24:14 +01:00
2017-07-06 17:42:25 +02:00
def analyze ( self , probe_address , probe_data , slews , loads ) :
2018-02-21 22:38:43 +01:00
"""
Main function to characterize an SRAM for a table . Computes both delay and power characterization .
2016-11-08 18:57:35 +01:00
"""
2018-07-27 23:06:59 +02:00
# Data structure for all the characterization values
char_data = { }
2017-07-06 17:42:25 +02:00
2016-11-08 18:57:35 +01:00
self . set_probe ( probe_address , probe_data )
2018-08-28 00:56:42 +02:00
2018-08-28 03:17:02 +02:00
#A helper functions to set port names for the characterizer. Actually, I should change this to not confuse with the
#already existing functions with similar names...
2018-08-28 00:56:42 +02:00
self . gen_port_names ( )
2017-11-23 00:57:29 +01:00
# This is for debugging a full simulation
# debug.info(0,"Debug simulation running...")
# target_period=50.0
2018-02-22 01:03:49 +01:00
# feasible_delay_lh=0.059083183
# feasible_delay_hl=0.17953789
2017-11-23 00:57:29 +01:00
# load=1.6728
# slew=0.04
2018-02-22 20:14:58 +01:00
# self.try_period(target_period, feasible_delay_lh, feasible_delay_hl)
2017-11-23 00:57:29 +01:00
# sys.exit(1)
2018-02-21 22:38:43 +01:00
# 1) Find a feasible period and it's corresponding delays using the trimmed array.
2018-02-22 20:14:58 +01:00
self . load = max ( loads )
self . slew = max ( slews )
2018-08-31 07:43:56 +02:00
( feasible_delays_lh , feasible_delays_hl ) = self . find_feasible_period ( )
#Check all the delays
for k , v in feasible_delays_lh . items ( ) :
debug . check ( v > 0 , " Negative delay may not be possible " )
for k , v in feasible_delays_hl . items ( ) :
debug . check ( v > 0 , " Negative delay may not be possible " )
2017-11-23 00:57:29 +01:00
2018-07-27 23:06:59 +02:00
# 2) Finds the minimum period without degrading the delays by X%
self . set_load_slew ( max ( loads ) , max ( slews ) )
2018-08-31 07:43:56 +02:00
min_period = self . find_min_period ( feasible_delays_lh , feasible_delays_hl )
2018-07-27 23:06:59 +02:00
debug . check ( type ( min_period ) == float , " Couldn ' t find minimum period. " )
2018-08-31 07:43:56 +02:00
debug . info ( 1 , " Min Period Found: {0} ns " . format ( min_period ) )
#debug.info(1, "Min Period: {0}n with a delay of {1} / {2}".format(min_period, feasible_delay_lh, feasible_delay_hl))
2018-07-27 23:06:59 +02:00
char_data [ " min_period " ] = round_time ( min_period )
2018-02-21 22:38:43 +01:00
# Make a list for each type of measurement to append results to
2018-08-30 10:18:34 +02:00
for port in self . readwrite_ports + self . read_ports + self . write_ports :
for m in [ " delay_lh " , " delay_hl " , " slew_lh " , " slew_hl " , " read0_power " ,
" read1_power " , " write0_power " , " write1_power " , " leakage_power " ] :
char_data [ " {0} _ {1} " . format ( m , port ) ] = [ ]
2018-02-24 00:20:52 +01:00
2018-07-27 23:06:59 +02:00
# 3) Find the leakage power of the trimmmed and UNtrimmed arrays.
2018-02-24 00:20:52 +01:00
( full_array_leakage , trim_array_leakage ) = self . run_power_simulation ( )
char_data [ " leakage_power " ] = full_array_leakage
2018-02-22 00:45:49 +01:00
2018-07-27 23:06:59 +02:00
# 4) At the minimum period, measure the delay, slew and power for all slew/load pairs.
2018-08-30 10:18:34 +02:00
#Set the target simulation ports to all available ports. This make sims slower but failed sims exit anyways.
self . targ_readwrite_ports = self . readwrite_ports
self . targ_read_ports = self . read_ports
self . targ_write_ports = self . write_ports
2017-07-06 17:42:25 +02:00
for slew in slews :
for load in loads :
2018-02-26 17:54:35 +01:00
self . set_load_slew ( load , slew )
2018-07-27 23:06:59 +02:00
# Find the delay, dynamic power, and leakage power of the trimmed array.
2018-02-22 20:14:58 +01:00
( 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 ) )
2018-02-21 22:38:43 +01:00
for k , v in delay_results . items ( ) :
if " power " in k :
# Subtract partial array leakage and add full array leakage for the power measures
2018-02-24 00:20:52 +01:00
char_data [ k ] . append ( v - trim_array_leakage + full_array_leakage )
2018-02-21 22:38:43 +01:00
else :
char_data [ k ] . append ( v )
2018-02-22 00:45:49 +01:00
2018-02-21 22:38:43 +01:00
return char_data
2016-11-08 18:57:35 +01:00
2018-07-27 20:36:17 +02:00
2018-08-28 00:56:42 +02:00
def add_data ( self , data , port ) :
2018-07-27 20:36:17 +02:00
""" Add the array of data values """
debug . check ( len ( data ) == self . word_size , " Invalid data word size. " )
index = 0
for c in data :
if c == " 0 " :
2018-08-28 00:56:42 +02:00
self . data_values [ port ] [ index ] . append ( 0 )
2018-07-27 20:36:17 +02:00
elif c == " 1 " :
2018-08-28 00:56:42 +02:00
self . data_values [ port ] [ index ] . append ( 1 )
2018-07-27 20:36:17 +02:00
else :
debug . error ( " Non-binary data string " , 1 )
index + = 1
2018-08-28 00:56:42 +02:00
def add_address ( self , address , port ) :
2018-07-27 20:36:17 +02:00
""" Add the array of address values """
debug . check ( len ( address ) == self . addr_size , " Invalid address size. " )
index = 0
for c in address :
if c == " 0 " :
2018-08-28 00:56:42 +02:00
self . addr_values [ port ] [ index ] . append ( 0 )
2018-07-27 20:36:17 +02:00
elif c == " 1 " :
2018-08-28 00:56:42 +02:00
self . addr_values [ port ] [ index ] . append ( 1 )
2018-07-27 20:36:17 +02:00
else :
debug . error ( " Non-binary address string " , 1 )
index + = 1
2018-07-27 23:06:59 +02:00
2018-08-31 00:11:54 +02:00
def add_noop_one_port ( self , address , data , port ) :
2018-08-28 00:56:42 +02:00
""" Add the control values for a noop to a single port. """
#This is to be used as a helper function for the other add functions. Cycle and comments are omitted.
2018-08-28 03:17:02 +02:00
if port in self . web_values and port in self . csb_values :
self . web_values [ port ] . append ( 1 )
self . csb_values [ port ] . append ( 1 )
2018-08-28 05:35:29 +02:00
self . add_data ( data , port )
2018-08-28 03:17:02 +02:00
elif port in self . rpenb_values :
self . rpenb_values [ port ] . append ( 1 )
2018-08-28 05:35:29 +02:00
elif port in self . wpenb_values :
self . add_data ( data , port )
self . wpenb_values [ port ] . append ( 1 )
2018-08-28 03:17:02 +02:00
else :
debug . error ( " Port selected with no control signals " , 1 )
2018-08-28 05:35:29 +02:00
2018-08-28 00:56:42 +02:00
self . add_address ( address , port )
def add_noop_all_ports ( self , comment , address , data ) :
""" Add the control values for a noop to all ports. """
2018-08-28 01:23:23 +02:00
self . cycle_comments . append ( " Cycle {0:2d} \t Port All \t {1:5.2f} ns: \t {2} " . format ( len ( self . cycle_times ) ,
2018-07-27 23:06:59 +02:00
self . t_current ,
comment ) )
self . cycle_times . append ( self . t_current )
self . t_current + = self . period
2018-08-28 00:56:42 +02:00
2018-08-28 05:35:29 +02:00
for port in self . readwrite_ports + self . read_ports + self . write_ports :
2018-08-31 00:11:54 +02:00
self . add_noop_one_port ( address , data , port )
2018-07-27 20:36:17 +02:00
2018-08-28 00:56:42 +02:00
def add_read ( self , comment , address , data , port ) :
2018-07-27 20:36:17 +02:00
""" Add the control values for a read cycle. """
2018-08-28 01:23:23 +02:00
self . cycle_comments . append ( " Cycle {0:2d} \t Port {3} \t {1:5.2f} ns: \t {2} " . format ( len ( self . cycle_comments ) ,
2018-07-27 23:06:59 +02:00
self . t_current ,
2018-08-28 01:23:23 +02:00
comment ,
port ) )
2018-07-27 20:36:17 +02:00
self . cycle_times . append ( self . t_current )
self . t_current + = self . period
2018-08-28 03:17:02 +02:00
if port in self . web_values and port in self . csb_values :
self . web_values [ port ] . append ( 1 )
self . csb_values [ port ] . append ( 0 )
2018-08-28 05:35:29 +02:00
self . add_data ( data , port )
2018-08-28 03:17:02 +02:00
elif port in self . rpenb_values :
self . rpenb_values [ port ] . append ( 0 )
else :
debug . error ( " Port selected with no control signals " , 1 )
2018-08-28 00:56:42 +02:00
self . add_address ( address , port )
2018-07-27 20:36:17 +02:00
2018-08-31 00:16:54 +02:00
#This value is hard coded here. Possibly change to member variable or set in add_noop_one_port
2018-08-31 00:11:54 +02:00
noop_data = " 0 " * self . word_size
2018-08-28 00:56:42 +02:00
#Add noops to all other ports.
2018-08-28 05:35:29 +02:00
for unselected_port in self . readwrite_ports + self . read_ports + self . write_ports :
2018-08-28 03:17:02 +02:00
if unselected_port != port :
2018-08-31 00:11:54 +02:00
self . add_noop_one_port ( address , noop_data , unselected_port )
2018-07-27 20:36:17 +02:00
2018-08-28 00:56:42 +02:00
def add_write ( self , comment , address , data , port ) :
""" Add the control values for a write cycle. """
2018-08-28 01:23:23 +02:00
self . cycle_comments . append ( " Cycle {0:2d} \t Port {3} \t {1:5.2f} ns: \t {2} " . format ( len ( self . cycle_comments ) ,
2018-07-27 23:06:59 +02:00
self . t_current ,
2018-08-28 01:23:23 +02:00
comment ,
port ) )
2018-07-27 20:36:17 +02:00
self . cycle_times . append ( self . t_current )
self . t_current + = self . period
2018-08-28 05:35:29 +02:00
if port in self . web_values and port in self . csb_values :
self . web_values [ port ] . append ( 0 )
self . csb_values [ port ] . append ( 0 )
elif port in self . wpenb_values :
self . wpenb_values [ port ] . append ( 0 )
else :
debug . error ( " Port selected with no control signals " , 1 )
2018-08-28 00:56:42 +02:00
self . add_data ( data , port )
self . add_address ( address , port )
2018-08-31 00:16:54 +02:00
#This value is hard coded here. Possibly change to member variable or set in add_noop_one_port
noop_data = " 0 " * self . word_size
2018-08-28 00:56:42 +02:00
#Add noops to all other ports.
2018-08-28 05:35:29 +02:00
for readwrite_port in self . readwrite_ports + self . read_ports + self . write_ports :
2018-08-28 00:56:42 +02:00
if readwrite_port != port :
2018-08-31 00:16:54 +02:00
self . add_noop_one_port ( address , noop_data , readwrite_port )
2018-08-28 09:30:15 +02:00
def gen_test_cycles_one_port ( self , read_port , write_port ) :
""" Intended but not implemented: Returns a list of key time-points [ns] of the waveform (each rising edge)
of the cycles to do a timing evaluation of a single port . Current : Values overwritten for multiple calls """
2018-08-28 22:01:35 +02:00
2018-08-28 09:30:15 +02:00
# Create the inverse address for a scratch address
inverse_address = " "
for c in self . probe_address :
if c == " 0 " :
inverse_address + = " 1 "
elif c == " 1 " :
inverse_address + = " 0 "
else :
debug . error ( " Non-binary address string " , 1 )
# For now, ignore data patterns and write ones or zeros
data_ones = " 1 " * self . word_size
data_zeros = " 0 " * self . word_size
2018-07-27 20:36:17 +02:00
2018-08-28 09:30:15 +02:00
if self . t_current == 0 :
self . add_noop_all_ports ( " Idle cycle (no positive clock edge) " ,
inverse_address , data_zeros )
self . add_write ( " W data 1 address 0..00 " ,
inverse_address , data_ones , write_port )
self . add_write ( " W data 0 address 11..11 to write value " ,
self . probe_address , data_zeros , write_port )
2018-08-30 00:13:31 +02:00
self . measure_cycles [ " write0_ {0} " . format ( write_port ) ] = len ( self . cycle_times ) - 1
#self.write0_cycle=len(self.cycle_times)-1 # Remember for power measure
2018-08-28 09:30:15 +02:00
# This also ensures we will have a H->L transition on the next read
self . add_read ( " R data 1 address 00..00 to set DOUT caps " ,
inverse_address , data_zeros , read_port )
self . add_read ( " R data 0 address 11..11 to check W0 worked " ,
2018-08-30 00:13:31 +02:00
self . probe_address , data_zeros , read_port )
self . measure_cycles [ " read0_ {0} " . format ( read_port ) ] = len ( self . cycle_times ) - 1
#self.read0_cycle=len(self.cycle_times)-1 # Remember for power measure
2018-08-28 09:30:15 +02:00
self . add_noop_all_ports ( " Idle cycle (if read takes >1 cycle) " ,
inverse_address , data_zeros )
2018-08-30 00:13:31 +02:00
#Does not seem like is is used anywhere commenting out for now.
#self.idle_cycle=len(self.cycle_times)-1 # Remember for power measure
2018-08-28 09:30:15 +02:00
self . add_write ( " W data 1 address 11..11 to write value " ,
self . probe_address , data_ones , write_port )
2018-08-30 00:13:31 +02:00
self . measure_cycles [ " write1_ {0} " . format ( write_port ) ] = len ( self . cycle_times ) - 1
#self.write1_cycle=len(self.cycle_times)-1 # Remember for power measure
2018-08-28 09:30:15 +02:00
self . add_write ( " W data 0 address 00..00 to clear DIN caps " ,
inverse_address , data_zeros , write_port )
# This also ensures we will have a L->H transition on the next read
self . add_read ( " R data 0 address 00..00 to clear DOUT caps " ,
inverse_address , data_zeros , read_port )
self . add_read ( " R data 1 address 11..11 to check W1 worked " ,
self . probe_address , data_zeros , read_port )
2018-08-30 00:13:31 +02:00
self . measure_cycles [ " read1_ {0} " . format ( read_port ) ] = len ( self . cycle_times ) - 1
#self.read1_cycle=len(self.cycle_times)-1 # Remember for power measure
2018-08-28 09:30:15 +02:00
self . add_noop_all_ports ( " Idle cycle (if read takes >1 cycle)) " ,
self . probe_address , data_zeros )
2018-08-30 09:11:14 +02:00
def get_availabe_port ( self , get_read_port ) :
""" Returns the first accessible read or write port """
if len ( self . readwrite_ports ) > 0 :
return self . readwrite_ports [ 0 ]
if get_read_port and len ( self . read_ports ) > 0 :
return self . read_ports [ 0 ]
elif not get_read_port and len ( self . write_ports ) > 0 :
return self . write_ports [ 0 ]
return None
2018-07-27 23:06:59 +02:00
def create_test_cycles ( self ) :
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 . """
2018-07-27 23:06:59 +02:00
# Start at time 0
2018-07-27 20:36:17 +02:00
self . t_current = 0
2018-07-27 23:06:59 +02:00
# Cycle times (positive edge) with comment
2018-08-28 05:35:29 +02:00
self . cycle_comments = [ ]
2017-07-06 17:42:25 +02:00
self . cycle_times = [ ]
2018-08-30 00:13:31 +02:00
self . measure_cycles = { }
2018-02-07 21:58:47 +01:00
2018-08-28 00:56:42 +02:00
# Readwrite port Control logic signals each cycle
self . web_values = { readwrite_port : [ ] for readwrite_port in self . readwrite_ports }
self . csb_values = { readwrite_port : [ ] for readwrite_port in self . readwrite_ports }
2018-08-28 03:17:02 +02:00
2018-08-28 09:30:15 +02:00
#Most, values changes to dict, kind of bad for performance. Maybe change to lists
2018-08-28 03:17:02 +02:00
# Read port control signals
self . rpenb_values = { read_port : [ ] for read_port in self . read_ports }
2018-08-28 05:35:29 +02:00
# Write port control signals
self . wpenb_values = { write_port : [ ] for write_port in self . write_ports }
2018-08-28 00:56:42 +02:00
# Address and data values for each address/data bit. A dict of 2d lists of size #ports x bits x cycles.
2018-08-28 05:35:29 +02:00
self . data_values = { port : [ [ ] for i in range ( self . word_size ) ] for port in self . readwrite_ports + self . write_ports }
2018-08-28 00:56:42 +02:00
#for i in range(self.word_size):
# self.data_values.append([])
2018-08-28 05:35:29 +02:00
self . addr_values = { port : [ [ ] for i in range ( self . addr_size ) ] for port in self . readwrite_ports + self . read_ports + self . write_ports }
2018-08-28 00:56:42 +02:00
#for i in range(self.addr_size):
# self.addr_values.append([])
2018-07-27 20:36:17 +02:00
2018-08-30 02:16:11 +02:00
#Temporary logic. Loop through all target readwrite ports with characterize logic.
for readwrite_port in self . targ_readwrite_ports :
2018-08-28 09:30:15 +02:00
self . gen_test_cycles_one_port ( readwrite_port , readwrite_port )
cur_write_port = readwrite_port
2018-08-30 09:11:14 +02:00
#Get any available read/write port in case only a single write or read ports is being characterized.
cur_read_port = self . get_availabe_port ( get_read_port = True )
cur_write_port = self . get_availabe_port ( get_read_port = False )
2018-08-28 09:30:15 +02:00
2018-08-30 02:16:11 +02:00
#Characterizing the remaining target ports. Not the final design.
2018-08-28 09:30:15 +02:00
write_pos = 0
read_pos = 0
while True :
2018-08-30 00:13:31 +02:00
#Exit when all ports have been characterized
2018-08-30 02:16:11 +02:00
if write_pos > = len ( self . targ_write_ports ) and read_pos > = len ( self . targ_read_ports ) :
2018-08-28 09:30:15 +02:00
break
2018-08-30 00:13:31 +02:00
2018-08-30 02:16:11 +02:00
#Select new write and/or read ports for the next cycle. Use previous port if none remaining.
if write_pos < len ( self . targ_write_ports ) :
cur_write_port = self . targ_write_ports [ write_pos ]
2018-08-28 09:30:15 +02:00
write_pos + = 1
2018-08-30 02:16:11 +02:00
if read_pos < len ( self . targ_read_ports ) :
cur_read_port = self . targ_read_ports [ read_pos ]
2018-08-28 09:30:15 +02:00
read_pos + = 1
2018-08-28 22:01:35 +02:00
#Add test cycle of read/write port pair. One port could have been used already, but the other has not.
#Above logic does not guarantee ports exists, but check_arguments should prevent that situation.
2018-08-28 09:30:15 +02:00
self . gen_test_cycles_one_port ( cur_read_port , cur_write_port )
2018-02-07 21:58:47 +01:00
2018-02-21 22:38:43 +01:00
def analytical_delay ( self , sram , slews , loads ) :
2017-11-09 20:13:44 +01:00
""" Just return the analytical model results for the SRAM.
"""
2018-02-22 01:03:49 +01:00
delay_lh = [ ]
delay_hl = [ ]
slew_lh = [ ]
slew_hl = [ ]
2017-11-09 20:13:44 +01:00
for slew in slews :
for load in loads :
2018-02-26 17:54:35 +01:00
self . set_load_slew ( load , slew )
2018-02-22 20:14:58 +01:00
bank_delay = sram . analytical_delay ( self . slew , self . load )
2018-02-28 21:18:41 +01:00
# Convert from ps to ns
2018-02-22 01:03:49 +01:00
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 )
2017-11-09 20:13:44 +01:00
2018-02-27 01:32:28 +01:00
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 ) )
2018-02-27 22:02:22 +01:00
2017-11-09 20:13:44 +01:00
data = { " min_period " : 0 ,
2018-02-22 01:03:49 +01:00
" delay_lh " : delay_lh ,
" delay_hl " : delay_hl ,
" slew_lh " : slew_lh ,
" slew_hl " : slew_hl ,
2018-02-27 01:32:28 +01:00
" read0_power " : power . dynamic ,
2018-02-27 22:02:22 +01:00
" read1_power " : power . dynamic ,
2018-02-27 01:32:28 +01:00
" write0_power " : power . dynamic ,
2018-02-27 22:02:22 +01:00
" write1_power " : power . dynamic ,
" leakage_power " : power . leakage
2017-11-09 20:13:44 +01:00
}
return data
2018-07-27 20:36:17 +02:00
def gen_data ( self ) :
2018-02-07 23:04:18 +01:00
""" Generates the PWL data inputs for a simulation timing test. """
2018-08-28 00:56:42 +02:00
for readwrite_input in self . readwrite_ports :
2018-08-23 08:45:43 +02:00
for i in range ( self . word_size ) :
2018-08-28 00:56:42 +02:00
sig_name = " DIN_ {0} [ {1} ] " . format ( readwrite_input , i )
self . stim . gen_pwl ( sig_name , self . cycle_times , self . data_values [ readwrite_input ] [ i ] , self . period , self . slew , 0.05 )
2018-08-28 05:35:29 +02:00
for write_port in self . write_ports :
for i in range ( self . word_size ) :
sig_name = " DIN_ {0} [ {1} ] " . format ( write_port , i )
self . stim . gen_pwl ( sig_name , self . cycle_times , self . data_values [ write_port ] [ i ] , self . period , self . slew , 0.05 )
2018-02-07 21:58:47 +01:00
2018-07-27 20:36:17 +02:00
def gen_addr ( self ) :
2018-02-07 23:04:18 +01:00
"""
Generates the address inputs for a simulation timing test .
This alternates between all 1 ' s and all 0 ' s for the address .
2018-02-07 21:58:47 +01:00
"""
2018-08-28 00:56:42 +02:00
for readwrite_addr in self . readwrite_ports :
2018-08-23 23:49:56 +02:00
for i in range ( self . addr_size ) :
2018-08-28 00:56:42 +02:00
sig_name = " A_ {0} [ {1} ] " . format ( readwrite_addr , i )
self . stim . gen_pwl ( sig_name , self . cycle_times , self . addr_values [ readwrite_addr ] [ i ] , self . period , self . slew , 0.05 )
2018-08-28 05:35:29 +02:00
for write_addr in self . write_ports :
for i in range ( self . addr_size ) :
sig_name = " A_ {0} [ {1} ] " . format ( write_addr , i )
self . stim . gen_pwl ( sig_name , self . cycle_times , self . addr_values [ write_addr ] [ i ] , self . period , self . slew , 0.05 )
2018-08-28 03:17:02 +02:00
for read_addr in self . read_ports :
for i in range ( self . addr_size ) :
sig_name = " A_ {0} [ {1} ] " . format ( read_addr , i )
self . stim . gen_pwl ( sig_name , self . cycle_times , self . addr_values [ read_addr ] [ i ] , self . period , self . slew , 0.05 )
2018-02-07 21:58:47 +01:00
2018-07-27 20:36:17 +02:00
def gen_control ( self ) :
""" Generates the control signals """
2018-08-24 02:46:24 +02:00
#Multiport changes to control signals. This will most likely be changed at some point when control signals are better determined.
2018-08-28 00:56:42 +02:00
for readwrite_port in self . readwrite_ports :
self . stim . gen_pwl ( " CSB_ {0} " . format ( readwrite_port ) , self . cycle_times , self . csb_values [ readwrite_port ] , self . period , self . slew , 0.05 )
self . stim . gen_pwl ( " WEB_ {0} " . format ( readwrite_port ) , self . cycle_times , self . web_values [ readwrite_port ] , self . period , self . slew , 0.05 )
2018-08-28 03:17:02 +02:00
for read_port in self . read_ports :
self . stim . gen_pwl ( " ENB_ {0} " . format ( read_port ) , self . cycle_times , self . rpenb_values [ read_port ] , self . period , self . slew , 0.05 )
2018-08-28 05:35:29 +02:00
for write_port in self . write_ports :
self . stim . gen_pwl ( " ENB_ {0} " . format ( write_port ) , self . cycle_times , self . wpenb_values [ write_port ] , self . period , self . slew , 0.05 )
2018-08-24 02:46:24 +02:00
2018-08-28 00:56:42 +02:00
def gen_port_names ( self ) :
2018-08-30 02:16:11 +02:00
""" Generates the port names to be used in characterization and sets default simulation target ports """
2018-08-28 00:56:42 +02:00
self . readwrite_ports = [ ]
self . write_ports = [ ]
self . read_ports = [ ]
2018-08-30 02:16:11 +02:00
#Generate the port names
2018-08-28 00:56:42 +02:00
for readwrite_port in range ( OPTS . rw_ports ) :
self . readwrite_ports . append ( " RWP {0} " . format ( readwrite_port ) )
for write_port in range ( OPTS . w_ports ) :
self . write_ports . append ( " WP {0} " . format ( write_port ) )
for read_port in range ( OPTS . r_ports ) :
2018-08-30 02:16:11 +02:00
self . read_ports . append ( " RP {0} " . format ( read_port ) )
#Set the default target ports for simulation. Default is all the ports.
self . targ_readwrite_ports = self . readwrite_ports
self . targ_read_ports = self . read_ports
self . targ_write_ports = self . write_ports