OpenRAM/compiler/characterizer/cacti.py

120 lines
4.9 KiB
Python

# See LICENSE for licensing information.
#
# Copyright (c) 2016-2022 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
import math
from openram import debug
from openram import tech
from openram import OPTS
from .simulation import simulation
class cacti(simulation):
"""
Delay model for the SRAM which which
"""
def __init__(self, sram, spfile, corner):
super().__init__(sram, spfile, corner)
# self.targ_read_ports = []
# self.targ_write_ports = []
# self.period = 0
# if self.write_size != self.word_size:
# self.num_wmasks = int(math.ceil(self.word_size / self.write_size))
# else:
# self.num_wmasks = 0
#self.set_load_slew(0, 0)
self.set_corner(corner)
self.create_signal_names()
self.add_graph_exclusions()
self.set_params()
def set_params(self):
"""Set parameters specific to the corner being simulated"""
self.params = {}
# Set the specific functions to use for timing defined in the SRAM module
self.params["model_name"] = OPTS.model_name
# Only parameter right now is r_on which is dependent on Vdd
self.params["r_nch_on"] = self.vdd_voltage / tech.spice["i_on_n"]
self.params["r_pch_on"] = self.vdd_voltage / tech.spice["i_on_p"]
def get_lib_values(self, load_slews):
"""
Return the analytical model results for the SRAM.
"""
if OPTS.num_rw_ports > 1 or OPTS.num_w_ports > 0 and OPTS.num_r_ports > 0:
debug.warning("In analytical mode, all ports have the timing of the first read port.")
# Probe set to 0th bit, does not matter for analytical delay.
self.set_probe('0' * self.bank_addr_size, 0)
self.create_graph()
self.set_internal_spice_names()
self.create_measurement_names()
port = self.read_ports[0]
self.graph.get_all_paths('{}{}'.format("clk", port),
'{}{}_{}'.format(self.dout_name, port, self.probe_data))
# Select the path with the bitline (bl)
bl_name, br_name = self.get_bl_name(self.graph.all_paths, port)
bl_path = [path for path in self.graph.all_paths if bl_name in path][0]
# Set delay/power for slews and loads
port_data = self.get_empty_measure_data_dict()
power = self.analytical_power(load_slews)
debug.info(1, 'Slew (ns), Load (fF), Delay(ns), Slew(ns)')
max_delay = 0.0
for load,slew in load_slews:
# Calculate delay based on slew and load
# Calculations expect Farad, input is Femto-Farad
load_farad = load*1e-15
#load_farad = 0.052275e-12
slew = 0
path_delays = self.graph.get_timing(bl_path, self.corner, slew, load_farad, self.params)
total_delay = self.sum_delays(path_delays)
delay_ns = total_delay.delay/1e-9
slew_ns = total_delay.slew/1e-9
max_delay = max(max_delay, total_delay.delay)
debug.info(1,'{}, {}, {}, {}'.format(slew,
load,
delay_ns,
slew_ns))
# Delay is only calculated on a single port and replicated for now.
for port in self.all_ports:
for mname in self.delay_meas_names + self.power_meas_names:
if "power" in mname:
port_data[port][mname].append(power.dynamic)
elif "delay" in mname and port in self.read_ports:
port_data[port][mname].append(total_delay.delay / 1e-9)
elif "slew" in mname and port in self.read_ports:
port_data[port][mname].append(total_delay.slew / 1e-9)
# Margin for error in period. Calculated by averaging required margin for a small and large
# memory. FIXME: margin is quite large, should be looked into.
period_margin = 1.85
sram_data = {"min_period": (max_delay / 1e-9) * 2 * period_margin,
"leakage_power": power.leakage}
debug.info(2, "SRAM Data:\n{}".format(sram_data))
debug.info(2, "Port Data:\n{}".format(port_data))
return (sram_data, port_data)
def analytical_power(self, load_slews):
"""Get the dynamic and leakage power from the SRAM"""
# slews unused, only last load is used
load = load_slews[-1][0]
power = self.sram.analytical_power(self.corner, 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))
return power