2019-04-26 21:21:50 +02:00
|
|
|
# See LICENSE for licensing information.
|
|
|
|
|
#
|
2021-01-22 20:23:28 +01:00
|
|
|
# Copyright (c) 2016-2021 Regents of the University of California and The Board
|
2019-06-14 17:43:41 +02:00
|
|
|
# of Regents for the Oklahoma Agricultural and Mechanical College
|
|
|
|
|
# (acting for and on behalf of Oklahoma State University)
|
|
|
|
|
# All rights reserved.
|
2019-04-26 21:21:50 +02:00
|
|
|
#
|
2018-12-20 03:33:06 +01:00
|
|
|
import debug
|
|
|
|
|
from tech import drc, parameter, spice
|
|
|
|
|
from abc import ABC, abstractmethod
|
|
|
|
|
from .stimuli import *
|
2018-12-21 00:54:56 +01:00
|
|
|
from .charutils import *
|
2018-12-20 03:33:06 +01:00
|
|
|
|
2022-09-02 01:19:14 +02:00
|
|
|
|
2018-12-20 03:33:06 +01:00
|
|
|
class spice_measurement(ABC):
|
|
|
|
|
"""Base class for spice stimulus measurements."""
|
2019-05-21 03:35:52 +02:00
|
|
|
def __init__(self, measure_name, measure_scale=None, has_port=True):
|
2022-09-02 01:19:14 +02:00
|
|
|
# Names must be unique for correct spice simulation, but not enforced here.
|
2018-12-20 03:33:06 +01:00
|
|
|
self.name = measure_name
|
2018-12-21 00:54:56 +01:00
|
|
|
self.measure_scale = measure_scale
|
2022-09-02 01:19:14 +02:00
|
|
|
self.has_port = has_port # Needed for error checking
|
|
|
|
|
# Some meta values used externally. variables are added here for consistency accross the objects
|
2020-11-03 15:29:17 +01:00
|
|
|
self.meta_str = None
|
2019-02-06 09:46:25 +01:00
|
|
|
self.meta_add_delay = False
|
2022-09-02 01:19:14 +02:00
|
|
|
|
2018-12-20 03:33:06 +01:00
|
|
|
@abstractmethod
|
|
|
|
|
def get_measure_function(self):
|
2020-11-03 15:29:17 +01:00
|
|
|
return None
|
|
|
|
|
|
2022-09-09 21:51:53 +02:00
|
|
|
@abstractmethod
|
|
|
|
|
def measure_function(self):
|
|
|
|
|
return None
|
|
|
|
|
|
2018-12-20 03:33:06 +01:00
|
|
|
@abstractmethod
|
|
|
|
|
def get_measure_values(self):
|
|
|
|
|
return None
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2018-12-20 03:33:06 +01:00
|
|
|
def write_measure(self, stim_obj, input_tuple):
|
|
|
|
|
measure_vals = self.get_measure_values(*input_tuple)
|
2022-09-09 21:51:53 +02:00
|
|
|
self.measure_func(stim_obj, *measure_vals)
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2019-05-21 03:35:52 +02:00
|
|
|
def retrieve_measure(self, port=None):
|
|
|
|
|
self.port_error_check(port)
|
2022-09-02 01:19:14 +02:00
|
|
|
if port is not None:
|
2020-11-03 15:29:17 +01:00
|
|
|
value = parse_spice_list("timing", "{0}{1}".format(self.name.lower(), port))
|
2019-05-21 03:35:52 +02:00
|
|
|
else:
|
2020-11-03 15:29:17 +01:00
|
|
|
value = parse_spice_list("timing", "{0}".format(self.name.lower()))
|
2022-09-02 01:19:14 +02:00
|
|
|
if type(value)!=float or self.measure_scale is None:
|
2018-12-21 00:54:56 +01:00
|
|
|
return value
|
|
|
|
|
else:
|
2022-09-02 01:19:14 +02:00
|
|
|
return value * self.measure_scale
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2019-05-21 03:35:52 +02:00
|
|
|
def port_error_check(self, port):
|
2022-09-02 01:19:14 +02:00
|
|
|
if self.has_port and port is None:
|
|
|
|
|
debug.error("Cannot retrieve measurement, port input was expected.", 1)
|
|
|
|
|
elif not self.has_port and port is not None:
|
|
|
|
|
debug.error("Unexpected port input received during measure retrieval.", 1)
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2021-05-15 01:16:25 +02:00
|
|
|
|
2018-12-20 03:33:06 +01:00
|
|
|
class delay_measure(spice_measurement):
|
|
|
|
|
"""Generates a spice measurement for the delay of 50%-to-50% points of two signals."""
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2021-05-15 01:16:25 +02:00
|
|
|
def __init__(self,
|
|
|
|
|
measure_name,
|
|
|
|
|
trig_name,
|
|
|
|
|
targ_name,
|
|
|
|
|
trig_dir_str,
|
|
|
|
|
targ_dir_str,
|
|
|
|
|
trig_vdd=0.5,
|
|
|
|
|
targ_vdd=0.5,
|
|
|
|
|
measure_scale=None,
|
|
|
|
|
has_port=True):
|
2020-08-12 21:10:12 +02:00
|
|
|
spice_measurement.__init__(self, measure_name, measure_scale, has_port)
|
2019-01-03 14:51:28 +01:00
|
|
|
self.set_meas_constants(trig_name, targ_name, trig_dir_str, targ_dir_str, trig_vdd, targ_vdd)
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2022-09-09 21:51:53 +02:00
|
|
|
def measure_function(self, stim_obj, meas_name, trig_name, targ_name, trig_val, targ_val, trig_dir, targ_dir, trig_td, targ_td):
|
|
|
|
|
""" Creates the .meas statement for the measurement of delay """
|
|
|
|
|
measure_string=".meas tran {0} TRIG v({1}) VAL={2} {3}=1 TD={4}n TARG v({5}) VAL={6} {7}=1 TD={8}n\n\n"
|
|
|
|
|
stim_obj.mf.write(measure_string.format(meas_name.lower(),
|
|
|
|
|
trig_name,
|
|
|
|
|
trig_val,
|
|
|
|
|
trig_dir,
|
|
|
|
|
trig_td,
|
|
|
|
|
targ_name,
|
|
|
|
|
targ_val,
|
|
|
|
|
targ_dir,
|
|
|
|
|
targ_td))
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2019-01-03 14:51:28 +01:00
|
|
|
def set_meas_constants(self, trig_name, targ_name, trig_dir_str, targ_dir_str, trig_vdd, targ_vdd):
|
|
|
|
|
"""Set the constants for this measurement: signal names, directions, and trigger scales"""
|
2018-12-20 03:33:06 +01:00
|
|
|
self.trig_dir_str = trig_dir_str
|
|
|
|
|
self.targ_dir_str = targ_dir_str
|
2020-11-03 15:29:17 +01:00
|
|
|
self.trig_val_of_vdd = trig_vdd
|
2019-01-03 14:51:28 +01:00
|
|
|
self.targ_val_of_vdd = targ_vdd
|
2018-12-20 03:33:06 +01:00
|
|
|
self.trig_name_no_port = trig_name
|
|
|
|
|
self.targ_name_no_port = targ_name
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2021-05-15 01:16:25 +02:00
|
|
|
# Time delays and ports are variant and needed as inputs when writing the measurement
|
2020-11-03 15:29:17 +01:00
|
|
|
|
|
|
|
|
def get_measure_values(self, trig_td, targ_td, vdd_voltage, port=None):
|
2018-12-20 03:33:06 +01:00
|
|
|
"""Constructs inputs to stimulus measurement function. Variant values are inputs here."""
|
2019-05-21 03:35:52 +02:00
|
|
|
self.port_error_check(port)
|
2018-12-20 03:33:06 +01:00
|
|
|
trig_val = self.trig_val_of_vdd * vdd_voltage
|
|
|
|
|
targ_val = self.targ_val_of_vdd * vdd_voltage
|
2019-05-21 03:35:52 +02:00
|
|
|
|
2022-09-02 01:19:14 +02:00
|
|
|
if port is not None:
|
2021-05-15 01:16:25 +02:00
|
|
|
# For dictionary indexing reasons, the name is formatted differently than the signals
|
2018-12-21 00:54:56 +01:00
|
|
|
meas_name = "{}{}".format(self.name, port)
|
2018-12-20 03:33:06 +01:00
|
|
|
trig_name = self.trig_name_no_port.format(port)
|
|
|
|
|
targ_name = self.targ_name_no_port.format(port)
|
|
|
|
|
else:
|
|
|
|
|
meas_name = self.name
|
|
|
|
|
trig_name = self.trig_name_no_port
|
|
|
|
|
targ_name = self.targ_name_no_port
|
2021-05-15 01:16:25 +02:00
|
|
|
return (meas_name, trig_name, targ_name, trig_val, targ_val, self.trig_dir_str, self.targ_dir_str, trig_td, targ_td)
|
|
|
|
|
|
2020-11-03 15:29:17 +01:00
|
|
|
|
|
|
|
|
class slew_measure(delay_measure):
|
2018-12-21 00:54:56 +01:00
|
|
|
|
2019-05-21 03:35:52 +02:00
|
|
|
def __init__(self, measure_name, signal_name, slew_dir_str, measure_scale=None, has_port=True):
|
2020-08-12 21:10:12 +02:00
|
|
|
spice_measurement.__init__(self, measure_name, measure_scale, has_port)
|
2018-12-20 03:33:06 +01:00
|
|
|
self.set_meas_constants(signal_name, slew_dir_str)
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2018-12-20 03:33:06 +01:00
|
|
|
def set_meas_constants(self, signal_name, slew_dir_str):
|
|
|
|
|
"""Set the values needed to generate a Spice measurement statement based on the name of the measurement."""
|
|
|
|
|
self.trig_dir_str = slew_dir_str
|
|
|
|
|
self.targ_dir_str = slew_dir_str
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2018-12-20 03:33:06 +01:00
|
|
|
if slew_dir_str == "RISE":
|
2020-11-03 15:29:17 +01:00
|
|
|
self.trig_val_of_vdd = 0.1
|
2018-12-20 03:33:06 +01:00
|
|
|
self.targ_val_of_vdd = 0.9
|
|
|
|
|
elif slew_dir_str == "FALL":
|
2020-11-03 15:29:17 +01:00
|
|
|
self.trig_val_of_vdd = 0.9
|
2018-12-20 03:33:06 +01:00
|
|
|
self.targ_val_of_vdd = 0.1
|
|
|
|
|
else:
|
2022-09-02 01:19:14 +02:00
|
|
|
debug.error("Unrecognised slew measurement direction={}".format(slew_dir_str), 1)
|
2018-12-20 03:33:06 +01:00
|
|
|
self.trig_name_no_port = signal_name
|
|
|
|
|
self.targ_name_no_port = signal_name
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2021-05-15 01:16:25 +02:00
|
|
|
# Time delays and ports are variant and needed as inputs when writing the measurement
|
|
|
|
|
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2018-12-21 00:54:56 +01:00
|
|
|
class power_measure(spice_measurement):
|
2019-01-03 14:51:28 +01:00
|
|
|
"""Generates a spice measurement for the average power between two time points."""
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2019-05-21 03:35:52 +02:00
|
|
|
def __init__(self, measure_name, power_type="", measure_scale=None, has_port=True):
|
2020-08-12 21:10:12 +02:00
|
|
|
spice_measurement.__init__(self, measure_name, measure_scale, has_port)
|
2018-12-21 00:54:56 +01:00
|
|
|
self.set_meas_constants(power_type)
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2022-09-09 21:51:53 +02:00
|
|
|
def measure_function(self, stim_obj, meas_name, t_initial, t_final):
|
|
|
|
|
""" Creates the .meas statement for the measurement of avg power """
|
|
|
|
|
# power mea cmd is different in different spice:
|
|
|
|
|
if OPTS.spice_name == "hspice":
|
|
|
|
|
power_exp = "power"
|
|
|
|
|
else:
|
|
|
|
|
power_exp = "par('(-1*v(" + str(self.vdd_name) + ")*I(v" + str(self.vdd_name) + "))')"
|
|
|
|
|
stim_obj.mf.write(".meas tran {0} avg {1} from={2}n to={3}n\n\n".format(meas_name.lower(),
|
|
|
|
|
power_exp,
|
|
|
|
|
t_initial,
|
|
|
|
|
t_final))
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2018-12-21 00:54:56 +01:00
|
|
|
def set_meas_constants(self, power_type):
|
|
|
|
|
"""Sets values useful for power simulations. This value is only meta related to the lib file (rise/fall)"""
|
2021-05-15 01:16:25 +02:00
|
|
|
# Not needed for power simulation
|
|
|
|
|
self.power_type = power_type # Expected to be "RISE"/"FALL"
|
2020-11-03 15:29:17 +01:00
|
|
|
|
|
|
|
|
def get_measure_values(self, t_initial, t_final, port=None):
|
2018-12-21 00:54:56 +01:00
|
|
|
"""Constructs inputs to stimulus measurement function. Variant values are inputs here."""
|
2019-05-21 03:35:52 +02:00
|
|
|
self.port_error_check(port)
|
2022-09-02 01:19:14 +02:00
|
|
|
if port is not None:
|
2018-12-21 00:54:56 +01:00
|
|
|
meas_name = "{}{}".format(self.name, port)
|
|
|
|
|
else:
|
|
|
|
|
meas_name = self.name
|
2021-05-15 01:16:25 +02:00
|
|
|
return (meas_name, t_initial, t_final)
|
|
|
|
|
|
2020-11-03 15:29:17 +01:00
|
|
|
|
|
|
|
|
class voltage_when_measure(spice_measurement):
|
2019-01-03 14:51:28 +01:00
|
|
|
"""Generates a spice measurement to measure the voltage of a signal based on the voltage of another."""
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2019-05-21 03:35:52 +02:00
|
|
|
def __init__(self, measure_name, trig_name, targ_name, trig_dir_str, trig_vdd, measure_scale=None, has_port=True):
|
2020-08-12 21:10:12 +02:00
|
|
|
spice_measurement.__init__(self, measure_name, measure_scale, has_port)
|
2019-01-03 14:51:28 +01:00
|
|
|
self.set_meas_constants(trig_name, targ_name, trig_dir_str, trig_vdd)
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2022-09-09 21:51:53 +02:00
|
|
|
def gen_meas_find_voltage(self, stim_obj, meas_name, trig_name, targ_name, trig_val, trig_dir, trig_td):
|
|
|
|
|
""" Creates the .meas statement for the measurement of delay """
|
|
|
|
|
measure_string=".meas tran {0} FIND v({1}) WHEN v({2})={3}v {4}=1 TD={5}n \n\n"
|
|
|
|
|
stim_obj.mf.write(measure_string.format(meas_name.lower(),
|
|
|
|
|
targ_name,
|
|
|
|
|
trig_name,
|
|
|
|
|
trig_val,
|
|
|
|
|
trig_dir,
|
|
|
|
|
trig_td))
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2019-01-03 14:51:28 +01:00
|
|
|
def set_meas_constants(self, trig_name, targ_name, trig_dir_str, trig_vdd):
|
|
|
|
|
"""Sets values useful for power simulations. This value is only meta related to the lib file (rise/fall)"""
|
|
|
|
|
self.trig_dir_str = trig_dir_str
|
2020-11-03 15:29:17 +01:00
|
|
|
self.trig_val_of_vdd = trig_vdd
|
2019-01-03 14:51:28 +01:00
|
|
|
self.trig_name_no_port = trig_name
|
|
|
|
|
self.targ_name_no_port = targ_name
|
2020-11-03 15:29:17 +01:00
|
|
|
|
|
|
|
|
def get_measure_values(self, trig_td, vdd_voltage, port=None):
|
2019-01-03 14:51:28 +01:00
|
|
|
"""Constructs inputs to stimulus measurement function. Variant values are inputs here."""
|
2019-05-21 03:35:52 +02:00
|
|
|
self.port_error_check(port)
|
2022-09-02 01:19:14 +02:00
|
|
|
if port is not None:
|
2021-05-15 01:16:25 +02:00
|
|
|
# For dictionary indexing reasons, the name is formatted differently than the signals
|
2019-01-03 14:51:28 +01:00
|
|
|
meas_name = "{}{}".format(self.name, port)
|
|
|
|
|
trig_name = self.trig_name_no_port.format(port)
|
|
|
|
|
targ_name = self.targ_name_no_port.format(port)
|
|
|
|
|
else:
|
|
|
|
|
meas_name = self.name
|
|
|
|
|
trig_name = self.trig_name_no_port
|
|
|
|
|
targ_name = self.targ_name_no_port
|
2021-05-15 01:16:25 +02:00
|
|
|
trig_voltage = self.trig_val_of_vdd * vdd_voltage
|
|
|
|
|
return (meas_name, trig_name, targ_name, trig_voltage, self.trig_dir_str, trig_td)
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2022-07-22 18:52:38 +02:00
|
|
|
|
2020-11-03 15:29:17 +01:00
|
|
|
class voltage_at_measure(spice_measurement):
|
2019-05-14 04:38:46 +02:00
|
|
|
"""Generates a spice measurement to measure the voltage at a specific time.
|
|
|
|
|
The time is considered variant with different periods."""
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2019-05-21 03:35:52 +02:00
|
|
|
def __init__(self, measure_name, targ_name, measure_scale=None, has_port=True):
|
2020-08-12 21:10:12 +02:00
|
|
|
spice_measurement.__init__(self, measure_name, measure_scale, has_port)
|
2019-05-14 04:38:46 +02:00
|
|
|
self.set_meas_constants(targ_name)
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2022-09-09 21:51:53 +02:00
|
|
|
def gen_meas_find_voltage_at_time(self, stim_obj, meas_name, targ_name, time_at):
|
|
|
|
|
""" Creates the .meas statement for voltage at time"""
|
|
|
|
|
measure_string=".meas tran {0} FIND v({1}) AT={2}n \n\n"
|
|
|
|
|
stim_obj.mf.write(measure_string.format(meas_name.lower(),
|
|
|
|
|
targ_name,
|
|
|
|
|
time_at))
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2019-05-14 04:38:46 +02:00
|
|
|
def set_meas_constants(self, targ_name):
|
|
|
|
|
"""Sets values useful for power simulations. This value is only meta related to the lib file (rise/fall)"""
|
|
|
|
|
self.targ_name_no_port = targ_name
|
2020-11-03 15:29:17 +01:00
|
|
|
|
|
|
|
|
def get_measure_values(self, time_at, port=None):
|
2019-05-14 04:38:46 +02:00
|
|
|
"""Constructs inputs to stimulus measurement function. Variant values are inputs here."""
|
2019-05-21 03:35:52 +02:00
|
|
|
self.port_error_check(port)
|
2022-09-02 01:19:14 +02:00
|
|
|
if port is not None:
|
2021-05-15 01:16:25 +02:00
|
|
|
# For dictionary indexing reasons, the name is formatted differently than the signals
|
2019-05-14 04:38:46 +02:00
|
|
|
meas_name = "{}{}".format(self.name, port)
|
|
|
|
|
targ_name = self.targ_name_no_port.format(port)
|
|
|
|
|
else:
|
|
|
|
|
meas_name = self.name
|
2020-11-03 15:29:17 +01:00
|
|
|
targ_name = self.targ_name_no_port
|
2021-05-15 01:16:25 +02:00
|
|
|
return (meas_name, targ_name, time_at)
|