Merge remote-tracking branch 'private/dev' into dev

This commit is contained in:
Matt Guthaus 2019-04-17 15:18:36 -07:00
commit e0b0661273
23 changed files with 399 additions and 271 deletions

View File

@ -4,13 +4,13 @@
[![License: BSD 3-clause](./images/license_badge.svg)](./LICENSE) [![License: BSD 3-clause](./images/license_badge.svg)](./LICENSE)
Master: Master:
[![Pipeline Status](https://scone.soe.ucsc.edu:8888/mrg/OpenRAM/badges/master/pipeline.svg?private_token=ynB6rSFLzvKUseoBPcwV)](https://github.com/VLSIDA/OpenRAM/commits/master) [![Pipeline Status](https://scone.soe.ucsc.edu:8888/mrg/OpenRAM/badges/master/pipeline.svg)](https://github.com/VLSIDA/OpenRAM/commits/master)
![Coverage](https://scone.soe.ucsc.edu:8888/mrg/OpenRAM/badges/master/coverage.svg?private_token=ynB6rSFLzvKUseoBPcwV) ![Coverage](https://scone.soe.ucsc.edu:8888/mrg/OpenRAM/badges/master/coverage.svg)
[![Download](./images/download-stable-blue.svg)](https://github.com/VLSIDA/OpenRAM/archive/master.zip) [![Download](./images/download-stable-blue.svg)](https://github.com/VLSIDA/OpenRAM/archive/master.zip)
Dev: Dev:
[![Pipeline Status](https://scone.soe.ucsc.edu:8888/mrg/OpenRAM/badges/dev/pipeline.svg?private_token=ynB6rSFLzvKUseoBPcwV)](https://github.com/VLSIDA/OpenRAM/commits/dev) [![Pipeline Status](https://scone.soe.ucsc.edu:8888/mrg/OpenRAM/badges/dev/pipeline.svg)](https://github.com/VLSIDA/OpenRAM/commits/dev)
![Coverage](https://scone.soe.ucsc.edu:8888/mrg/OpenRAM/badges/dev/coverage.svg?private_token=ynB6rSFLzvKUseoBPcwV) ![Coverage](https://scone.soe.ucsc.edu:8888/mrg/OpenRAM/badges/dev/coverage.svg)
[![Download](./images/download-unstable-blue.svg)](https://github.com/VLSIDA/OpenRAM/archive/dev.zip) [![Download](./images/download-unstable-blue.svg)](https://github.com/VLSIDA/OpenRAM/archive/dev.zip)
An open-source static random access memory (SRAM) compiler. An open-source static random access memory (SRAM) compiler.

View File

@ -235,13 +235,8 @@ class spice():
modeling it as a resistance driving a capacitance modeling it as a resistance driving a capacitance
""" """
swing_factor = abs(math.log(1-swing)) # time constant based on swing swing_factor = abs(math.log(1-swing)) # time constant based on swing
proc,vdd,temp = corner
#FIXME: type of delay is needed to know which process to use.
proc_mult = max(self.get_process_delay_factor(proc))
volt_mult = self.get_voltage_delay_factor(vdd)
temp_mult = self.get_temp_delay_factor(temp)
delay = swing_factor * r * c #c is in ff and delay is in fs delay = swing_factor * r * c #c is in ff and delay is in fs
delay = delay * proc_mult * volt_mult * temp_mult delay = self.apply_corners_analytically(delay, corner)
delay = delay * 0.001 #make the unit to ps delay = delay * 0.001 #make the unit to ps
# Output slew should be linear to input slew which is described # Output slew should be linear to input slew which is described
@ -254,6 +249,15 @@ class spice():
slew = delay * 0.6 * 2 + 0.005 * slew slew = delay * 0.6 * 2 + 0.005 * slew
return delay_data(delay = delay, slew = slew) return delay_data(delay = delay, slew = slew)
def apply_corners_analytically(self, delay, corner):
"""Multiply delay by corner factors"""
proc,vdd,temp = corner
#FIXME: type of delay is needed to know which process to use.
proc_mult = max(self.get_process_delay_factor(proc))
volt_mult = self.get_voltage_delay_factor(vdd)
temp_mult = self.get_temp_delay_factor(temp)
return delay * proc_mult * volt_mult * temp_mult
def get_process_delay_factor(self, proc): def get_process_delay_factor(self, proc):
"""Returns delay increase estimate based off process """Returns delay increase estimate based off process
Currently does +/-10 for fast/slow corners.""" Currently does +/-10 for fast/slow corners."""

View File

@ -2,6 +2,7 @@ import design
import debug import debug
import utils import utils
from tech import GDS,layer,parameter,drc from tech import GDS,layer,parameter,drc
import logical_effort
class bitcell(design.design): class bitcell(design.design):
""" """
@ -23,19 +24,12 @@ class bitcell(design.design):
self.width = bitcell.width self.width = bitcell.width
self.height = bitcell.height self.height = bitcell.height
self.pin_map = bitcell.pin_map self.pin_map = bitcell.pin_map
def analytical_delay(self, corner, slew, load=0, swing = 0.5): def analytical_delay(self, corner, slew, load=0, swing = 0.5):
# delay of bit cell is not like a driver(from WL) parasitic_delay = 1
# so the slew used should be 0 size = 0.5 #This accounts for bitline being drained thought the access TX and internal node
# it should not be slew dependent? cin = 3 #Assumes always a minimum sizes inverter. Could be specified in the tech.py file.
# because the value is there return logical_effort.logical_effort('bitline', size, cin, load, parasitic_delay, False)
# the delay is only over half transsmission gate
from tech import spice
r = spice["min_tx_r"]*3
c_para = spice["min_tx_drain_c"]
result = self.cal_delay_with_rc(corner, r = r, c = c_para+load, slew = slew, swing = swing)
return result
def list_bitcell_pins(self, col, row): def list_bitcell_pins(self, col, row):
""" Creates a list of connections in the bitcell, indexed by column and row, for instance use in bitcell_array """ """ Creates a list of connections in the bitcell, indexed by column and row, for instance use in bitcell_array """

View File

@ -2,6 +2,7 @@ import design
import debug import debug
import utils import utils
from tech import GDS,layer,parameter,drc from tech import GDS,layer,parameter,drc
import logical_effort
class bitcell_1rw_1r(design.design): class bitcell_1rw_1r(design.design):
""" """
@ -25,18 +26,12 @@ class bitcell_1rw_1r(design.design):
self.pin_map = bitcell_1rw_1r.pin_map self.pin_map = bitcell_1rw_1r.pin_map
def analytical_delay(self, corner, slew, load=0, swing = 0.5): def analytical_delay(self, corner, slew, load=0, swing = 0.5):
# delay of bit cell is not like a driver(from WL) parasitic_delay = 1
# so the slew used should be 0 size = 0.5 #This accounts for bitline being drained thought the access TX and internal node
# it should not be slew dependent? cin = 3 #Assumes always a minimum sizes inverter. Could be specified in the tech.py file.
# because the value is there read_port_load = 0.5 #min size NMOS gate load
# the delay is only over half transsmission gate return logical_effort.logical_effort('bitline', size, cin, load+read_port_load, parasitic_delay, False)
from tech import spice
r = spice["min_tx_r"]*3
c_para = spice["min_tx_drain_c"]
result = self.cal_delay_with_rc(corner, r = r, c = c_para+load, slew = slew, swing = swing)
return result
def list_bitcell_pins(self, col, row): def list_bitcell_pins(self, col, row):
""" Creates a list of connections in the bitcell, indexed by column and row, for instance use in bitcell_array """ """ Creates a list of connections in the bitcell, indexed by column and row, for instance use in bitcell_array """
bitcell_pins = ["bl0_{0}".format(col), bitcell_pins = ["bl0_{0}".format(col),

View File

@ -2,6 +2,7 @@ import design
import debug import debug
import utils import utils
from tech import GDS,layer,parameter,drc from tech import GDS,layer,parameter,drc
import logical_effort
class bitcell_1w_1r(design.design): class bitcell_1w_1r(design.design):
""" """
@ -25,18 +26,12 @@ class bitcell_1w_1r(design.design):
self.pin_map = bitcell_1w_1r.pin_map self.pin_map = bitcell_1w_1r.pin_map
def analytical_delay(self, corner, slew, load=0, swing = 0.5): def analytical_delay(self, corner, slew, load=0, swing = 0.5):
# delay of bit cell is not like a driver(from WL) parasitic_delay = 1
# so the slew used should be 0 size = 0.5 #This accounts for bitline being drained thought the access TX and internal node
# it should not be slew dependent? cin = 3 #Assumes always a minimum sizes inverter. Could be specified in the tech.py file.
# because the value is there read_port_load = 0.5 #min size NMOS gate load
# the delay is only over half transsmission gate return logical_effort.logical_effort('bitline', size, cin, load+read_port_load, parasitic_delay, False)
from tech import spice
r = spice["min_tx_r"]*3
c_para = spice["min_tx_drain_c"]
result = self.cal_delay_with_rc(corner, r = r, c = c_para+load, slew = slew, swing = swing)
return result
def list_bitcell_pins(self, col, row): def list_bitcell_pins(self, col, row):
""" Creates a list of connections in the bitcell, indexed by column and row, for instance use in bitcell_array """ """ Creates a list of connections in the bitcell, indexed by column and row, for instance use in bitcell_array """
bitcell_pins = ["bl0_{0}".format(col), bitcell_pins = ["bl0_{0}".format(col),

View File

@ -5,6 +5,7 @@ from tech import drc, parameter, spice
from vector import vector from vector import vector
from ptx import ptx from ptx import ptx
from globals import OPTS from globals import OPTS
import logical_effort
class pbitcell(design.design): class pbitcell(design.design):
""" """
@ -867,12 +868,16 @@ class pbitcell(design.design):
self.add_path("metal1", [Q_bar_pos, vdd_pos]) self.add_path("metal1", [Q_bar_pos, vdd_pos])
def analytical_delay(self, corner, slew, load=0, swing = 0.5): def analytical_delay(self, corner, slew, load=0, swing = 0.5):
#FIXME: Delay copied exactly over from bitcell parasitic_delay = 1
from tech import spice size = 0.5 #This accounts for bitline being drained thought the access TX and internal node
r = spice["min_tx_r"]*3 cin = 3 #Assumes always a minimum sizes inverter. Could be specified in the tech.py file.
c_para = spice["min_tx_drain_c"]
result = self.cal_delay_with_rc(corner, r = r, c = c_para+load, slew = slew, swing = swing) #Internal loads due to port configs are halved. This is to account for the size already being halved
return result #for stacked TXs, but internal loads do not see this size estimation.
write_port_load = self.num_w_ports*logical_effort.convert_farad_to_relative_c(parameter['bitcell_drain_cap'])/2
read_port_load = self.num_r_ports/2 #min size NMOS gate load
total_load = load+read_port_load+write_port_load
return logical_effort.logical_effort('bitline', size, cin, load+read_port_load, parasitic_delay, False)
def analytical_power(self, corner, load): def analytical_power(self, corner, load):
"""Bitcell power in nW. Only characterizes leakage.""" """Bitcell power in nW. Only characterizes leakage."""

View File

@ -9,6 +9,7 @@ import utils
from globals import OPTS from globals import OPTS
from .simulation import simulation from .simulation import simulation
from .measurements import * from .measurements import *
import logical_effort
class delay(simulation): class delay(simulation):
"""Functions to measure the delay and power of an SRAM at a given address and """Functions to measure the delay and power of an SRAM at a given address and
@ -904,8 +905,9 @@ class delay(simulation):
self.create_measurement_names() self.create_measurement_names()
power = self.analytical_power(slews, loads) power = self.analytical_power(slews, loads)
port_data = self.get_empty_measure_data_dict() port_data = self.get_empty_measure_data_dict()
relative_loads = [logical_effort.convert_farad_to_relative_c(c_farad) for c_farad in loads]
for slew in slews: for slew in slews:
for load in loads: for load in relative_loads:
self.set_load_slew(load,slew) self.set_load_slew(load,slew)
bank_delay = self.sram.analytical_delay(self.corner, self.slew,self.load) bank_delay = self.sram.analytical_delay(self.corner, self.slew,self.load)
for port in self.all_ports: for port in self.all_ports:

View File

@ -9,6 +9,7 @@ class logical_effort():
beta = parameter["beta"] beta = parameter["beta"]
min_inv_cin = 1+beta min_inv_cin = 1+beta
pinv=parameter["min_inv_para_delay"] pinv=parameter["min_inv_para_delay"]
tau = parameter['le_tau']
def __init__(self, name, size, cin, cout, parasitic, out_is_rise=True): def __init__(self, name, size, cin, cout, parasitic, out_is_rise=True):
self.name = name self.name = name
@ -30,30 +31,44 @@ class logical_effort():
def get_stage_effort(self): def get_stage_effort(self):
return self.logical_effort*self.eletrical_effort return self.logical_effort*self.eletrical_effort
def get_parasitic_delay(self, pinv): def get_parasitic_delay(self):
return pinv * self.parasitic_scale return logical_effort.pinv * self.parasitic_scale
def get_stage_delay(self, pinv): def get_stage_delay(self):
return self.get_stage_effort()+self.get_parasitic_delay(pinv) return self.get_stage_effort()+self.get_parasitic_delay()
def calculate_delays(stage_effort_list, pinv): def get_absolute_delay(self):
return logical_effort.tau*self.get_stage_delay()
def calculate_delays(stage_effort_list):
"""Convert stage effort objects to list of delay values""" """Convert stage effort objects to list of delay values"""
return [stage.get_stage_delay(pinv) for stage in stage_effort_list] return [stage.get_stage_delay() for stage in stage_effort_list]
def calculate_relative_delay(stage_effort_list, pinv=parameter["min_inv_para_delay"]): def calculate_relative_delay(stage_effort_list):
"""Calculates the total delay of a given delay path made of a list of logical effort objects.""" """Calculates the total delay of a given delay path made of a list of logical effort objects."""
total_rise_delay, total_fall_delay = calculate_relative_rise_fall_delays(stage_effort_list, pinv) total_rise_delay, total_fall_delay = calculate_relative_rise_fall_delays(stage_effort_list)
return total_rise_delay + total_fall_delay return total_rise_delay + total_fall_delay
def calculate_absolute_delay(stage_effort_list):
"""Calculates the total delay of a given delay path made of a list of logical effort objects."""
total_delay = 0
for stage in stage_effort_list:
total_delay+=stage.get_absolute_delay()
return total_delay
def calculate_relative_rise_fall_delays(stage_effort_list, pinv=parameter["min_inv_para_delay"]): def calculate_relative_rise_fall_delays(stage_effort_list):
"""Calculates the rise/fall delays of a given delay path made of a list of logical effort objects.""" """Calculates the rise/fall delays of a given delay path made of a list of logical effort objects."""
debug.info(2, "Calculating rise/fall relative delays") debug.info(2, "Calculating rise/fall relative delays")
total_rise_delay, total_fall_delay = 0,0 total_rise_delay, total_fall_delay = 0,0
for stage in stage_effort_list: for stage in stage_effort_list:
debug.info(2, stage) debug.info(2, stage)
if stage.is_rise: if stage.is_rise:
total_rise_delay += stage.get_stage_delay(pinv) total_rise_delay += stage.get_stage_delay()
else: else:
total_fall_delay += stage.get_stage_delay(pinv) total_fall_delay += stage.get_stage_delay()
return total_rise_delay, total_fall_delay return total_rise_delay, total_fall_delay
def convert_farad_to_relative_c(c_farad):
"""Converts capacitance in Femto-Farads to relative capacitance."""
return c_farad*parameter['cap_relative_per_ff']

View File

@ -18,15 +18,18 @@ class model_check(delay):
""" """
def __init__(self, sram, spfile, corner): def __init__(self, sram, spfile, corner, custom_delaychain=False):
delay.__init__(self,sram,spfile,corner) delay.__init__(self,sram,spfile,corner)
self.period = tech.spice["feasible_period"] self.period = tech.spice["feasible_period"]
self.create_data_names() self.create_data_names()
self.custom_delaychain=custom_delaychain
def create_data_names(self): def create_data_names(self):
self.wl_meas_name, self.wl_model_name = "wl_measures", "wl_model" self.wl_meas_name, self.wl_model_name = "wl_measures", "wl_model"
self.sae_meas_name, self.sae_model_name = "sae_measures", "sae_model" self.sae_meas_name, self.sae_model_name = "sae_measures", "sae_model"
self.wl_slew_name, self.sae_slew_name = "wl_slews", "sae_slews" self.wl_slew_name, self.sae_slew_name = "wl_slews", "sae_slews"
self.bl_meas_name, self.bl_slew_name = "bl_measures", "bl_slews"
self.power_name = "total_power"
def create_measurement_names(self): def create_measurement_names(self):
"""Create measurement names. The names themselves currently define the type of measurement""" """Create measurement names. The names themselves currently define the type of measurement"""
@ -34,21 +37,33 @@ class model_check(delay):
wl_en_driver_delay_names = ["delay_wl_en_dvr_{}".format(stage) for stage in range(1,self.get_num_wl_en_driver_stages())] wl_en_driver_delay_names = ["delay_wl_en_dvr_{}".format(stage) for stage in range(1,self.get_num_wl_en_driver_stages())]
wl_driver_delay_names = ["delay_wl_dvr_{}".format(stage) for stage in range(1,self.get_num_wl_driver_stages())] wl_driver_delay_names = ["delay_wl_dvr_{}".format(stage) for stage in range(1,self.get_num_wl_driver_stages())]
sen_driver_delay_names = ["delay_sen_dvr_{}".format(stage) for stage in range(1,self.get_num_sen_driver_stages())] sen_driver_delay_names = ["delay_sen_dvr_{}".format(stage) for stage in range(1,self.get_num_sen_driver_stages())]
dc_delay_names = ["delay_delay_chain_stage_{}".format(stage) for stage in range(1,self.get_num_delay_stages()+1)] if self.custom_delaychain:
dc_delay_names = ['delay_dc_out_final']
else:
dc_delay_names = ["delay_delay_chain_stage_{}".format(stage) for stage in range(1,self.get_num_delay_stages()+1)]
self.wl_delay_meas_names = wl_en_driver_delay_names+["delay_wl_en", "delay_wl_bar"]+wl_driver_delay_names+["delay_wl"] self.wl_delay_meas_names = wl_en_driver_delay_names+["delay_wl_en", "delay_wl_bar"]+wl_driver_delay_names+["delay_wl"]
self.rbl_delay_meas_names = ["delay_gated_clk_nand", "delay_delay_chain_in"]+dc_delay_names self.rbl_delay_meas_names = ["delay_gated_clk_nand", "delay_delay_chain_in"]+dc_delay_names
self.sae_delay_meas_names = ["delay_pre_sen"]+sen_driver_delay_names+["delay_sen"] self.sae_delay_meas_names = ["delay_pre_sen"]+sen_driver_delay_names+["delay_sen"]
# if self.custom_delaychain:
# self.delay_chain_indices = (len(self.rbl_delay_meas_names), len(self.rbl_delay_meas_names)+1)
# else:
self.delay_chain_indices = (len(self.rbl_delay_meas_names)-len(dc_delay_names), len(self.rbl_delay_meas_names)) self.delay_chain_indices = (len(self.rbl_delay_meas_names)-len(dc_delay_names), len(self.rbl_delay_meas_names))
#Create slew measurement names #Create slew measurement names
wl_en_driver_slew_names = ["slew_wl_en_dvr_{}".format(stage) for stage in range(1,self.get_num_wl_en_driver_stages())] wl_en_driver_slew_names = ["slew_wl_en_dvr_{}".format(stage) for stage in range(1,self.get_num_wl_en_driver_stages())]
wl_driver_slew_names = ["slew_wl_dvr_{}".format(stage) for stage in range(1,self.get_num_wl_driver_stages())] wl_driver_slew_names = ["slew_wl_dvr_{}".format(stage) for stage in range(1,self.get_num_wl_driver_stages())]
sen_driver_slew_names = ["slew_sen_dvr_{}".format(stage) for stage in range(1,self.get_num_sen_driver_stages())] sen_driver_slew_names = ["slew_sen_dvr_{}".format(stage) for stage in range(1,self.get_num_sen_driver_stages())]
dc_slew_names = ["slew_delay_chain_stage_{}".format(stage) for stage in range(1,self.get_num_delay_stages()+1)] if self.custom_delaychain:
dc_slew_names = ['slew_dc_out_final']
else:
dc_slew_names = ["slew_delay_chain_stage_{}".format(stage) for stage in range(1,self.get_num_delay_stages()+1)]
self.wl_slew_meas_names = ["slew_wl_gated_clk_bar"]+wl_en_driver_slew_names+["slew_wl_en", "slew_wl_bar"]+wl_driver_slew_names+["slew_wl"] self.wl_slew_meas_names = ["slew_wl_gated_clk_bar"]+wl_en_driver_slew_names+["slew_wl_en", "slew_wl_bar"]+wl_driver_slew_names+["slew_wl"]
self.rbl_slew_meas_names = ["slew_rbl_gated_clk_bar","slew_gated_clk_nand", "slew_delay_chain_in"]+dc_slew_names self.rbl_slew_meas_names = ["slew_rbl_gated_clk_bar","slew_gated_clk_nand", "slew_delay_chain_in"]+dc_slew_names
self.sae_slew_meas_names = ["slew_replica_bl0", "slew_pre_sen"]+sen_driver_slew_names+["slew_sen"] self.sae_slew_meas_names = ["slew_replica_bl0", "slew_pre_sen"]+sen_driver_slew_names+["slew_sen"]
self.bitline_meas_names = ["delay_wl_to_bl", "delay_bl_to_dout"]
self.power_meas_names = ['read0_power']
def create_signal_names(self): def create_signal_names(self):
"""Creates list of the signal names used in the spice file along the wl and sen paths. """Creates list of the signal names used in the spice file along the wl and sen paths.
Names are re-harded coded here; i.e. the names are hardcoded in most of OpenRAM and are Names are re-harded coded here; i.e. the names are hardcoded in most of OpenRAM and are
@ -59,7 +74,10 @@ class model_check(delay):
wl_en_driver_signals = ["Xsram.Xcontrol0.Xbuf_wl_en.Zb{}_int".format(stage) for stage in range(1,self.get_num_wl_en_driver_stages())] wl_en_driver_signals = ["Xsram.Xcontrol0.Xbuf_wl_en.Zb{}_int".format(stage) for stage in range(1,self.get_num_wl_en_driver_stages())]
wl_driver_signals = ["Xsram.Xbank0.Xwordline_driver0.Xwl_driver_inv{}.Zb{}_int".format(self.wordline_row, stage) for stage in range(1,self.get_num_wl_driver_stages())] wl_driver_signals = ["Xsram.Xbank0.Xwordline_driver0.Xwl_driver_inv{}.Zb{}_int".format(self.wordline_row, stage) for stage in range(1,self.get_num_wl_driver_stages())]
sen_driver_signals = ["Xsram.Xcontrol0.Xbuf_s_en.Zb{}_int".format(stage) for stage in range(1,self.get_num_sen_driver_stages())] sen_driver_signals = ["Xsram.Xcontrol0.Xbuf_s_en.Zb{}_int".format(stage) for stage in range(1,self.get_num_sen_driver_stages())]
delay_chain_signal_names = ["Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_{}".format(stage) for stage in range(1,self.get_num_delay_stages())] if self.custom_delaychain:
delay_chain_signal_names = []
else:
delay_chain_signal_names = ["Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_{}".format(stage) for stage in range(1,self.get_num_delay_stages())]
self.wl_signal_names = ["Xsram.Xcontrol0.gated_clk_bar"]+\ self.wl_signal_names = ["Xsram.Xcontrol0.gated_clk_bar"]+\
wl_en_driver_signals+\ wl_en_driver_signals+\
@ -70,17 +88,31 @@ class model_check(delay):
self.rbl_en_signal_names = pre_delay_chain_names+\ self.rbl_en_signal_names = pre_delay_chain_names+\
delay_chain_signal_names+\ delay_chain_signal_names+\
["Xsram.Xcontrol0.Xreplica_bitline.delayed_en"] ["Xsram.Xcontrol0.Xreplica_bitline.delayed_en"]
self.sae_signal_names = ["Xsram.Xcontrol0.Xreplica_bitline.bl0_0", "Xsram.Xcontrol0.pre_s_en"]+\ self.sae_signal_names = ["Xsram.Xcontrol0.Xreplica_bitline.bl0_0", "Xsram.Xcontrol0.pre_s_en"]+\
sen_driver_signals+\ sen_driver_signals+\
["Xsram.s_en0"] ["Xsram.s_en0"]
dout_name = "{0}{1}_{2}".format(self.dout_name,"{}",self.probe_data) #Empty values are the port and probe data bit
self.bl_signal_names = ["Xsram.Xbank0.wl_{}".format(self.wordline_row),\
"Xsram.Xbank0.bl_{}".format(self.bitline_column),\
dout_name]
def create_measurement_objects(self): def create_measurement_objects(self):
"""Create the measurements used for read and write ports""" """Create the measurements used for read and write ports"""
self.create_wordline_measurement_objects() self.create_wordline_meas_objs()
self.create_sae_measurement_objects() self.create_sae_meas_objs()
self.all_measures = self.wl_meas_objs+self.sae_meas_objs self.create_bl_meas_objs()
self.create_power_meas_objs()
self.all_measures = self.wl_meas_objs+self.sae_meas_objs+self.bl_meas_objs+self.power_meas_objs
def create_wordline_measurement_objects(self): def create_power_meas_objs(self):
"""Create power measurement object. Only one."""
self.power_meas_objs = []
self.power_meas_objs.append(power_measure(self.power_meas_names[0], "FALL", measure_scale=1e3))
def create_wordline_meas_objs(self):
"""Create the measurements to measure the wordline path from the gated_clk_bar signal""" """Create the measurements to measure the wordline path from the gated_clk_bar signal"""
self.wl_meas_objs = [] self.wl_meas_objs = []
trig_dir = "RISE" trig_dir = "RISE"
@ -102,7 +134,19 @@ class model_check(delay):
targ_dir = temp_dir targ_dir = temp_dir
self.wl_meas_objs.append(slew_measure(self.wl_slew_meas_names[-1], self.wl_signal_names[-1], trig_dir, measure_scale=1e9)) self.wl_meas_objs.append(slew_measure(self.wl_slew_meas_names[-1], self.wl_signal_names[-1], trig_dir, measure_scale=1e9))
def create_sae_measurement_objects(self): def create_bl_meas_objs(self):
"""Create the measurements to measure the bitline to dout, static stages"""
#Bitline has slightly different measurements, objects appends hardcoded.
self.bl_meas_objs = []
trig_dir, targ_dir = "RISE", "FALL" #Only check read 0
self.bl_meas_objs.append(delay_measure(self.bitline_meas_names[0],
self.bl_signal_names[0],
self.bl_signal_names[-1],
trig_dir,
targ_dir,
measure_scale=1e9))
def create_sae_meas_objs(self):
"""Create the measurements to measure the sense amp enable path from the gated_clk_bar signal. The RBL splits this path into two.""" """Create the measurements to measure the sense amp enable path from the gated_clk_bar signal. The RBL splits this path into two."""
self.sae_meas_objs = [] self.sae_meas_objs = []
@ -123,6 +167,13 @@ class model_check(delay):
temp_dir = trig_dir temp_dir = trig_dir
trig_dir = targ_dir trig_dir = targ_dir
targ_dir = temp_dir targ_dir = temp_dir
if self.custom_delaychain: #Hack for custom delay chains
self.sae_meas_objs[-2] = delay_measure(self.rbl_delay_meas_names[-1],
self.rbl_en_signal_names[-2],
self.rbl_en_signal_names[-1],
"RISE",
"RISE",
measure_scale=1e9)
self.sae_meas_objs.append(slew_measure(self.rbl_slew_meas_names[-1], self.sae_meas_objs.append(slew_measure(self.rbl_slew_meas_names[-1],
self.rbl_en_signal_names[-1], self.rbl_en_signal_names[-1],
trig_dir, trig_dir,
@ -131,7 +182,6 @@ class model_check(delay):
#Add measurements from rbl_out to sae. Trigger directions do not invert from previous stage due to RBL. #Add measurements from rbl_out to sae. Trigger directions do not invert from previous stage due to RBL.
trig_dir = "FALL" trig_dir = "FALL"
targ_dir = "RISE" targ_dir = "RISE"
#Add measurements from gated_clk_bar to RBL
for i in range(1, len(self.sae_signal_names)): for i in range(1, len(self.sae_signal_names)):
self.sae_meas_objs.append(delay_measure(self.sae_delay_meas_names[i-1], self.sae_meas_objs.append(delay_measure(self.sae_delay_meas_names[i-1],
self.sae_signal_names[i-1], self.sae_signal_names[i-1],
@ -169,10 +219,22 @@ class model_check(delay):
or port to port (time delays)""" or port to port (time delays)"""
#Return value is intended to match the delay measure format: trig_td, targ_td, vdd, port #Return value is intended to match the delay measure format: trig_td, targ_td, vdd, port
#Assuming only read 0 for now #Assuming only read 0 for now
if not (type(measure_obj) is delay_measure or type(measure_obj) is slew_measure): debug.info(3,"Power measurement={}".format(measure_obj))
if (type(measure_obj) is delay_measure or type(measure_obj) is slew_measure):
meas_cycle_delay = self.cycle_times[self.measure_cycles[port]["read0"]] + self.period/2
return (meas_cycle_delay, meas_cycle_delay, self.vdd_voltage, port)
elif type(measure_obj) is power_measure:
return self.get_power_measure_variants(port, measure_obj, "read")
else:
debug.error("Measurement not recognized by the model checker.",1) debug.error("Measurement not recognized by the model checker.",1)
meas_cycle_delay = self.cycle_times[self.measure_cycles[port]["read0"]] + self.period/2
return (meas_cycle_delay, meas_cycle_delay, self.vdd_voltage, port) def get_power_measure_variants(self, port, power_obj, operation):
"""Get the measurement values that can either vary port to port (time delays)"""
#Return value is intended to match the power measure format: t_initial, t_final, port
t_initial = self.cycle_times[self.measure_cycles[port]["read0"]]
t_final = self.cycle_times[self.measure_cycles[port]["read0"]+1]
return (t_initial, t_final, port)
def write_measures_read_port(self, port): def write_measures_read_port(self, port):
""" """
@ -186,7 +248,8 @@ class model_check(delay):
def get_measurement_values(self, meas_objs, port): def get_measurement_values(self, meas_objs, port):
"""Gets the delays and slews from a specified port from the spice output file and returns them as lists.""" """Gets the delays and slews from a specified port from the spice output file and returns them as lists."""
delay_meas_list = [] delay_meas_list = []
slew_meas_list = [] slew_meas_list = []
power_meas_list=[]
for measure in meas_objs: for measure in meas_objs:
measure_value = measure.retrieve_measure(port=port) measure_value = measure.retrieve_measure(port=port)
if type(measure_value) != float: if type(measure_value) != float:
@ -195,9 +258,11 @@ class model_check(delay):
delay_meas_list.append(measure_value) delay_meas_list.append(measure_value)
elif type(measure)is slew_measure: elif type(measure)is slew_measure:
slew_meas_list.append(measure_value) slew_meas_list.append(measure_value)
elif type(measure)is power_measure:
power_meas_list.append(measure_value)
else: else:
debug.error("Measurement object not recognized.",1) debug.error("Measurement object not recognized.",1)
return delay_meas_list, slew_meas_list return delay_meas_list, slew_meas_list,power_meas_list
def run_delay_simulation(self): def run_delay_simulation(self):
""" """
@ -213,6 +278,9 @@ class model_check(delay):
wl_slew_result = [[] for i in self.all_ports] wl_slew_result = [[] for i in self.all_ports]
sae_delay_result = [[] for i in self.all_ports] sae_delay_result = [[] for i in self.all_ports]
sae_slew_result = [[] for i in self.all_ports] sae_slew_result = [[] for i in self.all_ports]
bl_delay_result = [[] for i in self.all_ports]
bl_slew_result = [[] for i in self.all_ports]
power_result = [[] for i in self.all_ports]
# Checking from not data_value to data_value # Checking from not data_value to data_value
self.write_delay_stimulus() self.write_delay_stimulus()
@ -221,9 +289,11 @@ class model_check(delay):
#Retrieve the results from the output file #Retrieve the results from the output file
for port in self.targ_read_ports: for port in self.targ_read_ports:
#Parse and check the voltage measurements #Parse and check the voltage measurements
wl_delay_result[port], wl_slew_result[port] = self.get_measurement_values(self.wl_meas_objs, port) wl_delay_result[port], wl_slew_result[port],_ = self.get_measurement_values(self.wl_meas_objs, port)
sae_delay_result[port], sae_slew_result[port] = self.get_measurement_values(self.sae_meas_objs, port) sae_delay_result[port], sae_slew_result[port],_ = self.get_measurement_values(self.sae_meas_objs, port)
return (True,wl_delay_result, sae_delay_result, wl_slew_result, sae_slew_result) bl_delay_result[port], bl_slew_result[port],_ = self.get_measurement_values(self.bl_meas_objs, port)
_,__,power_result[port] = self.get_measurement_values(self.power_meas_objs, port)
return (True,wl_delay_result, sae_delay_result, wl_slew_result, sae_slew_result, bl_delay_result, bl_slew_result, power_result)
def get_model_delays(self, port): def get_model_delays(self, port):
"""Get model delays based on port. Currently assumes single RW port.""" """Get model delays based on port. Currently assumes single RW port."""
@ -306,23 +376,28 @@ class model_check(delay):
self.targ_read_ports = [read_port] self.targ_read_ports = [read_port]
self.targ_write_ports = [self.write_ports[0]] self.targ_write_ports = [self.write_ports[0]]
debug.info(1,"Model test: corner {}".format(self.corner)) debug.info(1,"Model test: corner {}".format(self.corner))
(success, wl_delays, sae_delays, wl_slews, sae_slews)=self.run_delay_simulation() (success, wl_delays, sae_delays, wl_slews, sae_slews, bl_delays, bl_slews, powers)=self.run_delay_simulation()
debug.check(success, "Model measurements Failed: period={}".format(self.period)) debug.check(success, "Model measurements Failed: period={}".format(self.period))
wl_model_delays, sae_model_delays = self.get_model_delays(read_port)
debug.info(1,"Measured Wordline delays (ns):\n\t {}".format(wl_delays[read_port])) debug.info(1,"Measured Wordline delays (ns):\n\t {}".format(wl_delays[read_port]))
debug.info(1,"Wordline model delays:\n\t {}".format(wl_model_delays))
debug.info(1,"Measured Wordline slews:\n\t {}".format(wl_slews[read_port])) debug.info(1,"Measured Wordline slews:\n\t {}".format(wl_slews[read_port]))
debug.info(1,"Measured SAE delays (ns):\n\t {}".format(sae_delays[read_port])) debug.info(1,"Measured SAE delays (ns):\n\t {}".format(sae_delays[read_port]))
debug.info(1,"SAE model delays:\n\t {}".format(sae_model_delays))
debug.info(1,"Measured SAE slews:\n\t {}".format(sae_slews[read_port])) debug.info(1,"Measured SAE slews:\n\t {}".format(sae_slews[read_port]))
debug.info(1,"Measured Bitline delays (ns):\n\t {}".format(bl_delays[read_port]))
data_dict[self.wl_meas_name] = wl_delays[read_port] data_dict[self.wl_meas_name] = wl_delays[read_port]
data_dict[self.wl_model_name] = wl_model_delays
data_dict[self.sae_meas_name] = sae_delays[read_port] data_dict[self.sae_meas_name] = sae_delays[read_port]
data_dict[self.sae_model_name] = sae_model_delays
data_dict[self.wl_slew_name] = wl_slews[read_port] data_dict[self.wl_slew_name] = wl_slews[read_port]
data_dict[self.sae_slew_name] = sae_slews[read_port] data_dict[self.sae_slew_name] = sae_slews[read_port]
data_dict[self.bl_meas_name] = bl_delays[read_port]
data_dict[self.power_name] = powers[read_port]
if not OPTS.use_tech_delay_chain_size: #Model is not used in this case
wl_model_delays, sae_model_delays = self.get_model_delays(read_port)
debug.info(1,"Wordline model delays:\n\t {}".format(wl_model_delays))
debug.info(1,"SAE model delays:\n\t {}".format(sae_model_delays))
data_dict[self.wl_model_name] = wl_model_delays
data_dict[self.sae_model_name] = sae_model_delays
#Some evaluations of the model and measured values #Some evaluations of the model and measured values
# debug.info(1, "Comparing wordline measurements and model.") # debug.info(1, "Comparing wordline measurements and model.")
@ -337,11 +412,17 @@ class model_check(delay):
name_dict = {} name_dict = {}
#Signal names are more descriptive than the measurement names, first value trimmed to match size of measurements names. #Signal names are more descriptive than the measurement names, first value trimmed to match size of measurements names.
name_dict[self.wl_meas_name] = self.wl_signal_names[1:] name_dict[self.wl_meas_name] = self.wl_signal_names[1:]
name_dict[self.wl_model_name] = name_dict["wl_measures"] #model uses same names as measured.
name_dict[self.sae_meas_name] = self.rbl_en_signal_names[1:]+self.sae_signal_names[1:] name_dict[self.sae_meas_name] = self.rbl_en_signal_names[1:]+self.sae_signal_names[1:]
name_dict[self.sae_model_name] = name_dict["sae_measures"]
name_dict[self.wl_slew_name] = self.wl_slew_meas_names name_dict[self.wl_slew_name] = self.wl_slew_meas_names
name_dict[self.sae_slew_name] = self.rbl_slew_meas_names+self.sae_slew_meas_names name_dict[self.sae_slew_name] = self.rbl_slew_meas_names+self.sae_slew_meas_names
name_dict[self.bl_meas_name] = self.bitline_meas_names[0:1]
name_dict[self.power_name] = self.power_meas_names
#name_dict[self.wl_slew_name] = self.wl_slew_meas_names
if not OPTS.use_tech_delay_chain_size:
name_dict[self.wl_model_name] = name_dict["wl_measures"] #model uses same names as measured.
name_dict[self.sae_model_name] = name_dict["sae_measures"]
return name_dict return name_dict

View File

@ -1215,37 +1215,39 @@ class bank(design.design):
offset=control_pos, offset=control_pos,
rotate=90) rotate=90)
def analytical_delay(self, corner, slew, load, port):
def analytical_delay(self, corner, slew, load): """ return analytical delay of the bank. This will track the clock to output path"""
""" return analytical delay of the bank""" #FIXME: This delay is determined in the control logic. Should be moved here.
results = [] # word_driver_delay = self.wordline_driver.analytical_delay(corner,
# slew,
decoder_delay = self.row_decoder.analytical_delay(corner, slew, self.wordline_driver.input_load()) # self.bitcell_array.input_load())
word_driver_delay = self.wordline_driver.analytical_delay(corner,
decoder_delay.slew,
self.bitcell_array.input_load())
#FIXME: Array delay is the same for every port. #FIXME: Array delay is the same for every port.
bitcell_array_delay = self.bitcell_array.analytical_delay(corner, word_driver_delay.slew) word_driver_slew = 0
if self.words_per_row > 1:
bitline_ext_load = self.column_mux_array[port].get_drain_cin()
else:
bitline_ext_load = self.sense_amp_array.get_drain_cin()
bitcell_array_delay = self.bitcell_array.analytical_delay(corner, word_driver_slew, bitline_ext_load)
bitcell_array_slew = 0
#This also essentially creates the same delay for each port. Good structure, no substance #This also essentially creates the same delay for each port. Good structure, no substance
for port in self.all_ports: if self.words_per_row > 1:
if self.words_per_row > 1: sa_load = self.sense_amp_array.get_drain_cin()
column_mux_delay = self.column_mux_array[port].analytical_delay(corner, column_mux_delay = self.column_mux_array[port].analytical_delay(corner,
bitcell_array_delay.slew, bitcell_array_slew,
self.sense_amp_array.input_load()) sa_load)
else: else:
column_mux_delay = self.return_delay(delay = 0.0, slew=word_driver_delay.slew) column_mux_delay = []
bl_t_data_out_delay = self.sense_amp_array.analytical_delay(corner, column_mux_slew = 0
column_mux_delay.slew, sense_amp_delay = self.sense_amp_array.analytical_delay(corner,
self.bitcell_array.output_load()) column_mux_slew,
# output load of bitcell_array is set to be only small part of bl for sense amp. load)
results.append(decoder_delay + word_driver_delay + bitcell_array_delay + column_mux_delay + bl_t_data_out_delay) # output load of bitcell_array is set to be only small part of bl for sense amp.
return bitcell_array_delay + column_mux_delay + sense_amp_delay
return results
def determine_wordline_stage_efforts(self, external_cout, inp_is_rise=True): def determine_wordline_stage_efforts(self, external_cout, inp_is_rise=True):
"""Get the all the stage efforts for each stage in the path within the bank clk_buf to a wordline""" """Get the all the stage efforts for each stage in the path within the bank clk_buf to a wordline"""
#Decoder is assumed to have settled before the negative edge of the clock. Delay model relies on this assumption #Decoder is assumed to have settled before the negative edge of the clock. Delay model relies on this assumption

View File

@ -4,6 +4,7 @@ from tech import drc, spice
from vector import vector from vector import vector
from globals import OPTS from globals import OPTS
from sram_factory import factory from sram_factory import factory
import logical_effort
class bitcell_array(design.design): class bitcell_array(design.design):
""" """
@ -127,26 +128,18 @@ class bitcell_array(design.design):
inst = self.cell_inst[row,col] inst = self.cell_inst[row,col]
for pin_name in ["vdd", "gnd"]: for pin_name in ["vdd", "gnd"]:
for pin in inst.get_pins(pin_name): for pin in inst.get_pins(pin_name):
self.add_power_pin(pin_name, pin.center(), 0, pin.layer) self.add_power_pin(pin_name, pin.center(), 0, pin.layer)
def analytical_delay(self, corner, slew, load):
def analytical_delay(self, corner, slew, load=0): """Returns relative delay of the bitline in the bitcell array"""
from tech import drc from tech import parameter
wl_wire = self.gen_wl_wire() #The load being driven/drained is mostly the bitline but could include the sense amp or the column mux.
wl_wire.return_delay_over_wire(slew) #The load from the bitlines is due to the drain capacitances from all the other bitlines and wire parasitics.
drain_load = logical_effort.convert_farad_to_relative_c(parameter['bitcell_drain_cap'])
wl_to_cell_delay = wl_wire.return_delay_over_wire(slew) wire_unit_load = .05 * drain_load #Wires add 5% to this.
# hypothetical delay from cell to bl end without sense amp bitline_load = (drain_load+wire_unit_load)*self.row_size
bl_wire = self.gen_bl_wire() return [self.cell.analytical_delay(corner, slew, load+bitline_load)]
cell_load = 2 * bl_wire.return_input_cap() # we ingore the wire r
# hence just use the whole c
bl_swing = 0.1
cell_delay = self.cell.analytical_delay(corner, wl_to_cell_delay.slew, cell_load, swing = bl_swing)
#we do not consider the delay over the wire for now
return self.return_delay(cell_delay.delay+wl_to_cell_delay.delay,
wl_to_cell_delay.slew)
def analytical_power(self, corner, load): def analytical_power(self, corner, load):
"""Power of Bitcell array and bitline in nW.""" """Power of Bitcell array and bitline in nW."""
from tech import drc, parameter from tech import drc, parameter

View File

@ -33,10 +33,10 @@ class control_logic(design.design):
self.num_words = num_rows*words_per_row self.num_words = num_rows*words_per_row
self.enable_delay_chain_resizing = True self.enable_delay_chain_resizing = True
self.inv_parasitic_delay = logical_effort.logical_effort.pinv
#Determines how much larger the sen delay should be. Accounts for possible error in model. #Determines how much larger the sen delay should be. Accounts for possible error in model.
self.wl_timing_tolerance = 1 self.wl_timing_tolerance = 1
self.parasitic_inv_delay = parameter["min_inv_para_delay"]
self.wl_stage_efforts = None self.wl_stage_efforts = None
self.sen_stage_efforts = None self.sen_stage_efforts = None
@ -219,7 +219,7 @@ class control_logic(design.design):
def get_dynamic_delay_chain_size(self, previous_stages, previous_fanout): def get_dynamic_delay_chain_size(self, previous_stages, previous_fanout):
"""Determine the size of the delay chain used for the Sense Amp Enable using path delays""" """Determine the size of the delay chain used for the Sense Amp Enable using path delays"""
from math import ceil from math import ceil
previous_delay_chain_delay = (previous_fanout+1+self.parasitic_inv_delay)*previous_stages previous_delay_chain_delay = (previous_fanout+1+self.inv_parasitic_delay)*previous_stages
debug.info(2, "Previous delay chain produced {} delay units".format(previous_delay_chain_delay)) debug.info(2, "Previous delay chain produced {} delay units".format(previous_delay_chain_delay))
delay_fanout = 3 # This can be anything >=2 delay_fanout = 3 # This can be anything >=2
@ -227,7 +227,7 @@ class control_logic(design.design):
#inverter adds 1 unit of delay (due to minimum size). This also depends on the pinv value #inverter adds 1 unit of delay (due to minimum size). This also depends on the pinv value
required_delay = self.wl_delay*self.wl_timing_tolerance - (self.sen_delay-previous_delay_chain_delay) required_delay = self.wl_delay*self.wl_timing_tolerance - (self.sen_delay-previous_delay_chain_delay)
debug.check(required_delay > 0, "Cannot size delay chain to have negative delay") debug.check(required_delay > 0, "Cannot size delay chain to have negative delay")
delay_stages = ceil(required_delay/(delay_fanout+1+self.parasitic_inv_delay)) delay_stages = ceil(required_delay/(delay_fanout+1+self.inv_parasitic_delay))
if delay_stages%2 == 1: #force an even number of stages. if delay_stages%2 == 1: #force an even number of stages.
delay_stages+=1 delay_stages+=1
#Fanout can be varied as well but is a little more complicated but potentially optimal. #Fanout can be varied as well but is a little more complicated but potentially optimal.
@ -237,7 +237,7 @@ class control_logic(design.design):
def get_dynamic_delay_fanout_list(self, previous_stages, previous_fanout): def get_dynamic_delay_fanout_list(self, previous_stages, previous_fanout):
"""Determine the size of the delay chain used for the Sense Amp Enable using path delays""" """Determine the size of the delay chain used for the Sense Amp Enable using path delays"""
previous_delay_chain_delay = (previous_fanout+1+self.parasitic_inv_delay)*previous_stages previous_delay_chain_delay = (previous_fanout+1+self.inv_parasitic_delay)*previous_stages
debug.info(2, "Previous delay chain produced {} delay units".format(previous_delay_chain_delay)) debug.info(2, "Previous delay chain produced {} delay units".format(previous_delay_chain_delay))
fanout_rise = fanout_fall = 2 # This can be anything >=2 fanout_rise = fanout_fall = 2 # This can be anything >=2
@ -284,9 +284,9 @@ class control_logic(design.design):
def calculate_stages_with_fixed_fanout(self, required_delay, fanout): def calculate_stages_with_fixed_fanout(self, required_delay, fanout):
from math import ceil from math import ceil
#Delay being negative is not an error. It implies that any amount of stages would have a negative effect on the overall delay #Delay being negative is not an error. It implies that any amount of stages would have a negative effect on the overall delay
if required_delay <= 3+self.parasitic_inv_delay: #3 is the minimum delay per stage (with pinv=0). if required_delay <= 3+self.inv_parasitic_delay: #3 is the minimum delay per stage (with pinv=0).
return 1 return 1
delay_stages = ceil(required_delay/(fanout+1+self.parasitic_inv_delay)) delay_stages = ceil(required_delay/(fanout+1+self.inv_parasitic_delay))
return delay_stages return delay_stages
def calculate_stage_list(self, total_stages, fanout_rise, fanout_fall): def calculate_stage_list(self, total_stages, fanout_rise, fanout_fall):
@ -850,14 +850,14 @@ class control_logic(design.design):
def get_delays_to_wl(self): def get_delays_to_wl(self):
"""Get the delay (in delay units) of the clk to a wordline in the bitcell array""" """Get the delay (in delay units) of the clk to a wordline in the bitcell array"""
debug.check(self.sram.all_mods_except_control_done, "Cannot calculate sense amp enable delay unless all module have been added.") debug.check(self.sram.all_mods_except_control_done, "Cannot calculate sense amp enable delay unless all module have been added.")
self.wl_stage_efforts = self.determine_wordline_stage_efforts() self.wl_stage_efforts = self.get_wordline_stage_efforts()
clk_to_wl_rise,clk_to_wl_fall = logical_effort.calculate_relative_rise_fall_delays(self.wl_stage_efforts, self.parasitic_inv_delay) clk_to_wl_rise,clk_to_wl_fall = logical_effort.calculate_relative_rise_fall_delays(self.wl_stage_efforts)
total_delay = clk_to_wl_rise + clk_to_wl_fall total_delay = clk_to_wl_rise + clk_to_wl_fall
debug.info(1, "Clock to wl delay is rise={:.3f}, fall={:.3f}, total={:.3f} in delay units".format(clk_to_wl_rise, clk_to_wl_fall,total_delay)) debug.info(1, "Clock to wl delay is rise={:.3f}, fall={:.3f}, total={:.3f} in delay units".format(clk_to_wl_rise, clk_to_wl_fall,total_delay))
return clk_to_wl_rise,clk_to_wl_fall return clk_to_wl_rise,clk_to_wl_fall
def determine_wordline_stage_efforts(self): def get_wordline_stage_efforts(self):
"""Follows the gated_clk_bar -> wl_en -> wordline signal for the total path efforts""" """Follows the gated_clk_bar -> wl_en -> wordline signal for the total path efforts"""
stage_effort_list = [] stage_effort_list = []
@ -871,7 +871,7 @@ class control_logic(design.design):
last_stage_is_rise = stage_effort_list[-1].is_rise last_stage_is_rise = stage_effort_list[-1].is_rise
#Then ask the sram for the other path delays (from the bank) #Then ask the sram for the other path delays (from the bank)
stage_effort_list += self.sram.determine_wordline_stage_efforts(last_stage_is_rise) stage_effort_list += self.sram.get_wordline_stage_efforts(last_stage_is_rise)
return stage_effort_list return stage_effort_list
@ -880,17 +880,15 @@ class control_logic(design.design):
This does not incorporate the delay of the replica bitline. This does not incorporate the delay of the replica bitline.
""" """
debug.check(self.sram.all_mods_except_control_done, "Cannot calculate sense amp enable delay unless all module have been added.") debug.check(self.sram.all_mods_except_control_done, "Cannot calculate sense amp enable delay unless all module have been added.")
self.sen_stage_efforts = self.determine_sa_enable_stage_efforts() self.sen_stage_efforts = self.get_sa_enable_stage_efforts()
clk_to_sen_rise, clk_to_sen_fall = logical_effort.calculate_relative_rise_fall_delays(self.sen_stage_efforts, self.parasitic_inv_delay) clk_to_sen_rise, clk_to_sen_fall = logical_effort.calculate_relative_rise_fall_delays(self.sen_stage_efforts)
total_delay = clk_to_sen_rise + clk_to_sen_fall total_delay = clk_to_sen_rise + clk_to_sen_fall
debug.info(1, "Clock to s_en delay is rise={:.3f}, fall={:.3f}, total={:.3f} in delay units".format(clk_to_sen_rise, clk_to_sen_fall,total_delay)) debug.info(1, "Clock to s_en delay is rise={:.3f}, fall={:.3f}, total={:.3f} in delay units".format(clk_to_sen_rise, clk_to_sen_fall,total_delay))
return clk_to_sen_rise, clk_to_sen_fall return clk_to_sen_rise, clk_to_sen_fall
def determine_sa_enable_stage_efforts(self): def get_sa_enable_stage_efforts(self):
"""Follows the gated_clk_bar signal to the sense amp enable signal adding each stages stage effort to a list""" """Follows the gated_clk_bar signal to the sense amp enable signal adding each stages stage effort to a list"""
stage_effort_list = [] stage_effort_list = []
#Calculate the load on clk_buf_bar
ext_clk_buf_cout = self.sram.get_clk_bar_cin()
#Initial direction of clock signal for this path #Initial direction of clock signal for this path
last_stage_rise = True last_stage_rise = True
@ -917,7 +915,54 @@ class control_logic(design.design):
"""Gets a list of the stages and delays in order of their path.""" """Gets a list of the stages and delays in order of their path."""
if self.sen_stage_efforts == None or self.wl_stage_efforts == None: if self.sen_stage_efforts == None or self.wl_stage_efforts == None:
debug.error("Model delays not calculated for SRAM.", 1) debug.error("Model delays not calculated for SRAM.", 1)
wl_delays = logical_effort.calculate_delays(self.wl_stage_efforts, self.parasitic_inv_delay) wl_delays = logical_effort.calculate_delays(self.wl_stage_efforts)
sen_delays = logical_effort.calculate_delays(self.sen_stage_efforts, self.parasitic_inv_delay) sen_delays = logical_effort.calculate_delays(self.sen_stage_efforts)
return wl_delays, sen_delays return wl_delays, sen_delays
def analytical_delay(self, corner, slew, load):
"""Gets the analytical delay from clk input to wl_en output"""
stage_effort_list = []
#Calculate the load on clk_buf_bar
ext_clk_buf_cout = self.sram.get_clk_bar_cin()
#Operations logic starts on negative edge
last_stage_rise = False
#First stage(s), clk -(pdriver)-> clk_buf.
clk_buf_cout = self.replica_bitline.get_en_cin()
stage_effort_list += self.clk_buf_driver.get_stage_efforts(clk_buf_cout, last_stage_rise)
last_stage_rise = stage_effort_list[-1].is_rise
#Second stage, clk_buf -(inv)-> clk_bar
clk_bar_cout = self.and2.get_cin()
stage_effort_list += self.and2.get_stage_efforts(clk_bar_cout, last_stage_rise)
last_stage_rise = stage_effort_list[-1].is_rise
#Third stage clk_bar -(and)-> gated_clk_bar
gated_clk_bar_cin = self.get_gated_clk_bar_cin()
stage_effort_list.append(self.inv.get_stage_effort(gated_clk_bar_cin, last_stage_rise))
last_stage_rise = stage_effort_list[-1].is_rise
#Stages from gated_clk_bar -------> wordline
stage_effort_list += self.get_wordline_stage_efforts()
return stage_effort_list
def get_clk_buf_cin(self):
"""Get the loads that are connected to the buffered clock.
Includes all the DFFs and some logic."""
#Control logic internal load
int_clk_buf_cap = self.inv.get_cin() + self.ctrl_dff_array.get_clk_cin() + self.and2.get_cin()
#Control logic external load (in the other parts of the SRAM)
ext_clk_buf_cap = self.sram.get_clk_bar_cin()
return int_clk_buf_cap + ext_clk_buf_cap
def get_gated_clk_bar_cin(self):
"""Get intermediates net gated_clk_bar's capacitance"""
total_cin = 0
total_cin += self.wl_en_driver.get_cin()
if self.port_type == 'rw':
total_cin +=self.and2.get_cin()
return total_cin

View File

@ -2,6 +2,7 @@ import design
import debug import debug
import utils import utils
from tech import GDS,layer, parameter,drc from tech import GDS,layer, parameter,drc
import logical_effort
class sense_amp(design.design): class sense_amp(design.design):
""" """
@ -31,12 +32,13 @@ class sense_amp(design.design):
bitline_pmos_size = 8 #FIXME: This should be set somewhere and referenced. Probably in tech file. bitline_pmos_size = 8 #FIXME: This should be set somewhere and referenced. Probably in tech file.
return spice["min_tx_drain_c"]*(bitline_pmos_size/parameter["min_tx_size"])#ff return spice["min_tx_drain_c"]*(bitline_pmos_size/parameter["min_tx_size"])#ff
def analytical_delay(self, corner, slew, load=0.0): def analytical_delay(self, corner, slew, load):
from tech import spice #Delay of the sense amp will depend on the size of the amp and the output load.
r = spice["min_tx_r"]/(10) parasitic_delay = 1
c_para = spice["min_tx_drain_c"] cin = (parameter["sa_inv_pmos_size"] + parameter["sa_inv_nmos_size"])/drc("minwidth_tx")
result = self.cal_delay_with_rc(corner, r = r, c = c_para+load, slew = slew) sa_size = parameter["sa_inv_nmos_size"]/drc("minwidth_tx")
return self.return_delay(result.delay, result.slew) cc_inv_cin = cin
return logical_effort.logical_effort('column_mux', sa_size, cin, load+cc_inv_cin, parasitic_delay, False)
def analytical_power(self, corner, load): def analytical_power(self, corner, load):
"""Returns dynamic and leakage power. Results in nW""" """Returns dynamic and leakage power. Results in nW"""

View File

@ -4,6 +4,7 @@ from vector import vector
from sram_factory import factory from sram_factory import factory
import debug import debug
from globals import OPTS from globals import OPTS
import logical_effort
class sense_amp_array(design.design): class sense_amp_array(design.design):
""" """
@ -136,10 +137,17 @@ class sense_amp_array(design.design):
def input_load(self): def input_load(self):
return self.amp.input_load() return self.amp.input_load()
def analytical_delay(self, corner, slew, load=0.0): def analytical_delay(self, corner, slew, load):
return self.amp.analytical_delay(corner, slew=slew, load=load) return [self.amp.analytical_delay(corner, slew=slew, load=load)]
def get_en_cin(self): def get_en_cin(self):
"""Get the relative capacitance of all the sense amp enable connections in the array""" """Get the relative capacitance of all the sense amp enable connections in the array"""
sense_amp_en_cin = self.amp.get_en_cin() sense_amp_en_cin = self.amp.get_en_cin()
return sense_amp_en_cin * self.word_size return sense_amp_en_cin * self.word_size
def get_drain_cin(self):
"""Get the relative capacitance of the drain of the PMOS isolation TX"""
from tech import parameter
#Bitcell drain load being used to estimate PMOS drain load
drain_load = logical_effort.convert_farad_to_relative_c(parameter['bitcell_drain_cap'])
return drain_load

View File

@ -7,6 +7,7 @@ import math
from vector import vector from vector import vector
from sram_factory import factory from sram_factory import factory
from globals import OPTS from globals import OPTS
import logical_effort
class single_level_column_mux_array(design.design): class single_level_column_mux_array(design.design):
""" """
@ -215,15 +216,18 @@ class single_level_column_mux_array(design.design):
self.add_via(layers=("metal1", "via1", "metal2"), self.add_via(layers=("metal1", "via1", "metal2"),
offset= br_out_offset, offset= br_out_offset,
rotate=90) rotate=90)
def analytical_delay(self, corner, vdd, slew, load=0.0): def analytical_delay(self, corner, slew, load):
from tech import spice, parameter from tech import parameter
proc,vdd,temp = corner """Returns relative delay that the column mux adds"""
r = spice["min_tx_r"]/(self.mux.ptx_width/parameter["min_tx_size"]) #Single level column mux will add parasitic loads from other mux pass transistors and the sense amp.
#Drains of mux transistors make up capacitance. drain_load = logical_effort.convert_farad_to_relative_c(parameter['bitcell_drain_cap'])
c_para = spice["min_tx_drain_c"]*(self.mux.ptx_width/parameter["min_tx_size"])*self.words_per_row#ff array_load = drain_load*self.words_per_row
volt_swing = spice["v_threshold_typical"]/vdd return [self.mux.analytical_delay(corner, slew, load+array_load)]
result = self.cal_delay_with_rc(corner, r = r, c = c_para+load, slew = slew, swing = volt_swing) def get_drain_cin(self):
return self.return_delay(result.delay, result.slew) """Get the relative capacitance of the drain of the NMOS pass TX"""
from tech import parameter
#Bitcell drain load being used to estimate mux NMOS drain load
drain_load = logical_effort.convert_farad_to_relative_c(parameter['bitcell_drain_cap'])
return drain_load

View File

@ -125,3 +125,8 @@ class pand2(pgate.pgate):
stage_effort_list.append(stage2) stage_effort_list.append(stage2)
return stage_effort_list return stage_effort_list
def get_cin(self):
"""Return the relative input capacitance of a single input"""
return self.nand.get_cin()

View File

@ -19,6 +19,8 @@ class pdriver(pgate.pgate):
self.size_list = size_list self.size_list = size_list
self.fanout = fanout self.fanout = fanout
if size_list == None and self.fanout == 0:
debug.error("Either fanout or size list must be specified.", -1)
if self.size_list and self.fanout != 0: if self.size_list and self.fanout != 0:
debug.error("Cannot specify both size_list and fanout.", -1) debug.error("Cannot specify both size_list and fanout.", -1)
if self.size_list and self.neg_polarity: if self.size_list and self.neg_polarity:

View File

@ -5,6 +5,7 @@ from vector import vector
import contact import contact
from globals import OPTS from globals import OPTS
from sram_factory import factory from sram_factory import factory
import logical_effort
class single_level_column_mux(design.design): class single_level_column_mux(design.design):
""" """
@ -180,5 +181,9 @@ class single_level_column_mux(design.design):
width=self.bitcell.width, width=self.bitcell.width,
height=self.height) height=self.height)
def analytical_delay(self, corner, slew, load):
"""Returns relative delay that the column mux. Difficult to convert to LE model."""
parasitic_delay = 1
cin = 2*self.tx_size #This is not CMOS, so using this may be incorrect.
return logical_effort.logical_effort('column_mux', self.tx_size, cin, load, parasitic_delay, False)

View File

@ -11,6 +11,7 @@ from design import design
from verilog import verilog from verilog import verilog
from lef import lef from lef import lef
from sram_factory import factory from sram_factory import factory
import logical_effort
class sram_base(design, verilog, lef): class sram_base(design, verilog, lef):
""" """
@ -505,10 +506,28 @@ class sram_base(design, verilog, lef):
def analytical_delay(self, corner, slew,load): def analytical_delay(self, corner, slew,load):
""" LH and HL are the same in analytical model. """ """ Estimates the delay from clk -> DOUT
return self.bank.analytical_delay(corner,slew,load) LH and HL are the same in analytical model. """
delays = {}
for port in self.all_ports:
if port in self.readonly_ports:
control_logic = self.control_logic_r
elif port in self.readwrite_ports:
control_logic = self.control_logic_rw
else:
continue
clk_to_wlen_delays = control_logic.analytical_delay(corner, slew, load)
wlen_to_dout_delays = self.bank.analytical_delay(corner,slew,load,port) #port should probably be specified...
all_delays = clk_to_wlen_delays+wlen_to_dout_delays
total_delay = logical_effort.calculate_absolute_delay(all_delays)
total_delay = self.apply_corners_analytically(total_delay, corner)
last_slew = .1*all_delays[-1].get_absolute_delay() #slew approximated as 10% of delay
last_slew = self.apply_corners_analytically(last_slew, corner)
delays[port] = self.return_delay(delay=total_delay, slew=last_slew)
return delays
def determine_wordline_stage_efforts(self, inp_is_rise=True): def get_wordline_stage_efforts(self, inp_is_rise=True):
"""Get the all the stage efforts for each stage in the path from clk_buf to a wordline""" """Get the all the stage efforts for each stage in the path from clk_buf to a wordline"""
stage_effort_list = [] stage_effort_list = []
@ -546,4 +565,12 @@ class sram_base(design, verilog, lef):
return self.bank.get_sen_cin() return self.bank.get_sen_cin()
def get_dff_clk_buf_cin(self):
"""Get the relative capacitance of the clk_buf signal.
Does not get the control logic loading but everything else"""
total_cin = 0
total_cin += self.row_addr_dff.get_clk_cin()
total_cin += self.data_dff.get_clk_cin()
if self.col_addr_size > 0:
total_cin += self.col_addr_dff.get_clk_cin()
return total_cin

View File

@ -12,24 +12,21 @@ from globals import OPTS
from sram_factory import factory from sram_factory import factory
import debug import debug
class delay_model_test(openram_test): class model_delay_sram_test(openram_test):
def runTest(self): def runTest(self):
globals.init_openram("config_{0}".format(OPTS.tech_name)) globals.init_openram("config_{0}".format(OPTS.tech_name))
OPTS.spice_name="hspice"
OPTS.analytical_delay = False OPTS.analytical_delay = False
OPTS.netlist_only = True OPTS.netlist_only = True
OPTS.trim_netlist = False
debug.info(1, "Trimming disabled for this test. Simulation could be slow.")
# This is a hack to reload the characterizer __init__ with the spice version # This is a hack to reload the characterizer __init__ with the spice version
from importlib import reload from importlib import reload
import characterizer import characterizer
reload(characterizer) reload(characterizer)
from characterizer import delay
from characterizer import model_check from sram import sram
from sram_config import sram_config from sram_config import sram_config
c = sram_config(word_size=4, c = sram_config(word_size=1,
num_words=16, num_words=16,
num_banks=1) num_banks=1)
c.words_per_row=1 c.words_per_row=1
@ -45,15 +42,32 @@ class delay_model_test(openram_test):
debug.info(1, "Probe address {0} probe data bit {1}".format(probe_address, probe_data)) debug.info(1, "Probe address {0} probe data bit {1}".format(probe_address, probe_data))
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
mc = model_check(s.s, tempspice, corner) d = delay(s.s, tempspice, corner)
import tech import tech
loads = [tech.spice["msflop_in_cap"]*4] loads = [tech.spice["msflop_in_cap"]*4]
slews = [tech.spice["rise_time"]*2] slews = [tech.spice["rise_time"]*2]
sram_data = mc.analyze(probe_address, probe_data, slews, loads) spice_data, port_data = d.analyze(probe_address, probe_data, slews, loads)
#Combine info about port into all data spice_data.update(port_data[0])
#debug.info(1,"Data:\n{}".format(wl_data)) model_data, port_data = d.analytical_delay(slews, loads)
model_data.update(port_data[0])
#Only compare the delays
spice_delays = {key:value for key, value in spice_data.items() if 'delay' in key}
model_delays = {key:value for key, value in model_data.items() if 'delay' in key}
debug.info(1,"Spice Delays={}".format(spice_delays))
debug.info(1,"Model Delays={}".format(model_delays))
if OPTS.tech_name == "freepdk45":
error_tolerance = .25
elif OPTS.tech_name == "scn4m_subm":
error_tolerance = .25
else:
self.assertTrue(False) # other techs fail
# Check if no too many or too few results
self.assertTrue(len(spice_delays.keys())==len(model_delays.keys()))
self.assertTrue(self.check_golden_data(spice_delays,model_delays,error_tolerance))
globals.end_openram() globals.end_openram()
# run the test from the command line # run the test from the command line

View File

@ -1,80 +0,0 @@
#!/usr/bin/env python3
"""
Run a regression test on various srams
"""
import unittest
from testutils import header,openram_test
import sys,os
sys.path.append(os.path.join(sys.path[0],".."))
import globals
from globals import OPTS
from sram_factory import factory
import debug
@unittest.skip("SKIPPING 27_worst_case_delay_test")
class worst_case_timing_sram_test(openram_test):
def runTest(self):
OPTS.tech_name = "freepdk45"
globals.init_openram("config_{0}".format(OPTS.tech_name))
OPTS.spice_name="hspice"
OPTS.analytical_delay = False
OPTS.netlist_only = True
OPTS.trim_netlist = False
# This is a hack to reload the characterizer __init__ with the spice version
from importlib import reload
import characterizer
reload(characterizer)
from characterizer import worst_case
if not OPTS.spice_exe:
debug.error("Could not find {} simulator.".format(OPTS.spice_name),-1)
word_size, num_words, num_banks = 2, 16, 1
from sram_config import sram_config
c = sram_config(word_size=word_size,
num_words=num_words,
num_banks=num_banks)
c.words_per_row=1
c.recompute_sizes()
debug.info(1, "Testing the timing different bitecells inside a {}bit, {} words SRAM with {} bank".format(
word_size, num_words, num_banks))
s = factory.create(module_type="sram", sram_config=c)
sp_netlist_file = OPTS.openram_temp + "temp.sp"
s.sp_write(sp_netlist_file)
if OPTS.use_pex:
gdsname = OPTS.output_path + s.name + ".gds"
s.gds_write(gdsname)
import verify
reload(verify)
# Output the extracted design if requested
sp_pex_file = OPTS.output_path + s.name + "_pex.sp"
verify.run_pex(s.name, gdsname, sp_netlist_file, output=sp_pex_file)
sp_sim_file = sp_pex_file
debug.info(1, "Performing spice simulations with backannotated spice file.")
else:
sp_sim_file = sp_netlist_file
debug.info(1, "Performing spice simulations with spice netlist.")
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
wc = worst_case(s.s, sp_sim_file, corner)
import tech
loads = [tech.spice["msflop_in_cap"]*4]
slews = [tech.spice["rise_time"]*2]
probe_address = "1" * s.s.addr_size
probe_data = s.s.word_size - 1
wc.analyze(probe_address, probe_data, slews, loads)
globals.end_openram()
# run the test from the command line
if __name__ == "__main__":
(OPTS, args) = globals.parse_args()
del sys.argv[1:]
header(__file__, OPTS.tech_name)
unittest.main()

View File

@ -336,6 +336,8 @@ spice["nand3_transition_prob"] = .1094 # Transition probability of 3-input na
spice["nor2_transition_prob"] = .1875 # Transition probability of 2-input nor. spice["nor2_transition_prob"] = .1875 # Transition probability of 2-input nor.
#Parameters related to sense amp enable timing and delay chain/RBL sizing #Parameters related to sense amp enable timing and delay chain/RBL sizing
parameter['le_tau'] = 2.25 #In pico-seconds.
parameter['cap_relative_per_ff'] = 7.5 #Units of Relative Capacitance/ Femto-Farad
parameter["static_delay_stages"] = 4 parameter["static_delay_stages"] = 4
parameter["static_fanout_per_stage"] = 3 parameter["static_fanout_per_stage"] = 3
parameter["static_fanout_list"] = parameter["static_delay_stages"]*[parameter["static_fanout_per_stage"]] parameter["static_fanout_list"] = parameter["static_delay_stages"]*[parameter["static_fanout_per_stage"]]
@ -344,7 +346,10 @@ parameter["6tcell_wl_cin"] = 3 #relative capacitance
parameter["min_inv_para_delay"] = 2.4 #Tau delay units parameter["min_inv_para_delay"] = 2.4 #Tau delay units
parameter["sa_en_pmos_size"] = .72 #micro-meters parameter["sa_en_pmos_size"] = .72 #micro-meters
parameter["sa_en_nmos_size"] = .27 #micro-meters parameter["sa_en_nmos_size"] = .27 #micro-meters
parameter["sa_inv_pmos_size"] = .54 #micro-meters
parameter["sa_inv_nmos_size"] = .27 #micro-meters
parameter["rbl_height_percentage"] = .5 #Height of RBL compared to bitcell array parameter["rbl_height_percentage"] = .5 #Height of RBL compared to bitcell array
parameter['bitcell_drain_cap'] = 0.1 #In Femto-Farad, approximation of drain capacitance
################################################### ###################################################
##END Spice Simulation Parameters ##END Spice Simulation Parameters

View File

@ -302,15 +302,20 @@ spice["nand3_transition_prob"] = .1094 # Transition probability of 3-input na
spice["nor2_transition_prob"] = .1875 # Transition probability of 2-input nor. spice["nor2_transition_prob"] = .1875 # Transition probability of 2-input nor.
#Logical Effort relative values for the Handmade cells #Logical Effort relative values for the Handmade cells
parameter['le_tau'] = 23 #In pico-seconds.
parameter["min_inv_para_delay"] = .73 #In relative delay units
parameter['cap_relative_per_ff'] = .91 #Units of Relative Capacitance/ Femto-Farad
parameter["static_delay_stages"] = 4 parameter["static_delay_stages"] = 4
parameter["static_fanout_per_stage"] = 3 parameter["static_fanout_per_stage"] = 3
parameter["static_fanout_list"] = parameter["static_delay_stages"]*[parameter["static_fanout_per_stage"]] parameter["static_fanout_list"] = parameter["static_delay_stages"]*[parameter["static_fanout_per_stage"]]
parameter["dff_clk_cin"] = 27.5 parameter["dff_clk_cin"] = 27.5 #In relative capacitance units
parameter["6tcell_wl_cin"] = 2 parameter["6tcell_wl_cin"] = 2 #In relative capacitance units
parameter["min_inv_para_delay"] = .5
parameter["sa_en_pmos_size"] = 24*_lambda_ parameter["sa_en_pmos_size"] = 24*_lambda_
parameter["sa_en_nmos_size"] = 9*_lambda_ parameter["sa_en_nmos_size"] = 9*_lambda_
parameter["sa_inv_pmos_size"] = 18*_lambda_
parameter["sa_inv_nmos_size"] = 9*_lambda_
parameter["rbl_height_percentage"] = .5 #Height of RBL compared to bitcell array parameter["rbl_height_percentage"] = .5 #Height of RBL compared to bitcell array
parameter['bitcell_drain_cap'] = 0.2 #In Femto-Farad, approximation of drain capacitance
################################################### ###################################################
##END Spice Simulation Parameters ##END Spice Simulation Parameters