blob: 1358466ddc3527564ca6dc8faa502d2f9dcd9f35 [file] [log] [blame]
# main.py
import os
import time
import yaml
import numpy as np
# import test framework
import openhtf as htf
from openhtf.plugs.user_input import UserInput
from spintop_openhtf import TestPlan
from openhtf.util import conf
from openhtf.core import measurements
import SpiceInterface
import TestUtilities
from criteria import get_criteria
# import test parameters
with open(r'config.yml') as file:
config = yaml.load(file, Loader=yaml.FullLoader)
# This defines the name of the testbench.
plan = TestPlan('bandgap_core')
print("Saving results to: %s" % plan.history_path)
@plan.testcase('stability_ctat')
@htf.measures(get_criteria('dc_gain'))
@htf.measures(get_criteria('phase_margin'))
@htf.measures(get_criteria('gain_margin'))
def stability_ctat(test):
"""Measure the stability margins of the CTAT control loop"""
# create the test utility object
test_utilities_obj = TestUtilities.TestUtilities()
test_utilities_obj.netlist_generation(config['stability_ctat']['netlist'], "rundir")
# create the spice interface
spice_interface_obj = SpiceInterface.SpiceInterface(netlist_path="rundir/"+config['stability_ctat']['netlist'].split('.')[0]+".spice")
# loop through all corners
for corner in config['pvt']['corners']:
# set corner
spice_interface_obj.set_corner(corner)
# run the simulation
spice_interface_obj.run_simulation()
# save the response
node = 'v(ac)'
spice_interface_obj.plot_bode(node, display=False, title="CTAT Open Loop Response",
linewidth=1, alpha=0.5, append=True)
# get the signal and test DC gain
signal = spice_interface_obj.get_signal(node, complex_out=True)
test.measurements.dc_gain = 20*np.log10(abs(signal[0]))
# get the phase and gain margin
phase_margin, gain_margin, unity_bandwidth, inverted_frequency = spice_interface_obj.measure_phase_gain_margin(node)
print(phase_margin)
# test the margins
test.measurements.phase_margin = phase_margin
test.measurements.gain_margin = gain_margin
# save the plot to file
spice_interface_obj.fig.savefig("outputs/stability_ctat.svg")
@plan.testcase('stability_ptat')
@htf.measures(get_criteria('dc_gain'))
@htf.measures(get_criteria('phase_margin'))
@htf.measures(get_criteria('gain_margin'))
def stability_ptat(test):
"""Measure the stability margins of the PTAT control loop"""
# create the test utility object
test_utilities_obj = TestUtilities.TestUtilities()
test_utilities_obj.netlist_generation(config['stability_ptat']['netlist'], "rundir")
# create the spice interface
spice_interface_obj = SpiceInterface.SpiceInterface(netlist_path="rundir/"+config['stability_ptat']['netlist'].split('.')[0]+".spice")
# loop through all corners
for corner in config['pvt']['corners']:
# set corner
spice_interface_obj.set_corner(corner)
# run the simulation
spice_interface_obj.run_simulation()
# save the response
node = 'v(ac)'
spice_interface_obj.plot_bode(node, display=False, title="PTAT Open Loop Response",
linewidth=1, alpha=0.5, append=True, invert=True)
# linewidth=1, alpha=0.5, append=True)
# get the signal and test DC gain
signal = spice_interface_obj.get_signal(node, complex_out=True)
test.measurements.dc_gain = 20*np.log10(abs(signal[0]))
# get the phase and gain margin
# phase_margin, gain_margin, unity_bandwidth, inverted_frequency = spice_interface_obj.measure_phase_gain_margin(node)
phase_margin, gain_margin, unity_bandwidth, inverted_frequency = spice_interface_obj.measure_phase_gain_margin(node, invert=True)
print(phase_margin)
# test the margins
test.measurements.phase_margin = phase_margin
test.measurements.gain_margin = gain_margin
# save the plot to file
spice_interface_obj.fig.savefig("outputs/stability_ptat.svg")
@plan.testcase('temperature_sweep')
@htf.measures(get_criteria('current_regulation_temp').with_dimensions(htf.units.NO_DIMENSION, htf.units.VOLT))
@htf.measures(get_criteria('current_regulation_process').with_dimensions(htf.units.NO_DIMENSION, htf.units.VOLT))
@htf.measures(htf.Measurement('current').with_dimensions(htf.units.NO_DIMENSION, htf.units.VOLT, htf.units.DEGREE_CELSIUS))
def temperature_sweep(test):
"""Measure the response over temperature"""
# create the test utility object
test_utilities_obj = TestUtilities.TestUtilities()
test_utilities_obj.netlist_generation(config['temperature_sweep']['netlist'], "rundir")
# create the spice interface
spice_interface_obj = SpiceInterface.SpiceInterface(netlist_path="rundir/"+config['temperature_sweep']['netlist'].split('.')[0]+".spice")
spice_interface_obj.config['simulator']['shared'] = True
spice_interface_obj.config['simulator']['silent'] = True
# append the simulation command
spice_interface_obj.set_sim_command(config['temperature_sweep']['sim_command'])
# prepopulate
results = np.zeros((len(config['pvt']['corners']), len(config['pvt']['vdd']), 101))
# loop through all corners
for corner_i, corner in enumerate(config['pvt']['corners']):
# set corner
spice_interface_obj.set_corner(corner)
# reload the circuit
spice_interface_obj.restart_simulation()
# loop through the supply voltages
for vdd_i, vdd in enumerate(config['pvt']['vdd']):
# set the vdd voltage
spice_interface_obj.set_parameters([['vdd', vdd], ['en', vdd], ['start_n', vdd]])
# run the simulation
spice_interface_obj.run_simulation(new_instance=False)
# save the response
node = 'i(viout)'
sweepvar = 'temp-sweep'
spice_interface_obj.plot_dc_sweep(sweepvar, node, display=False, title="Output Current Over Temperature",
linewidth=1, alpha=0.5, append=True)
# get the output current
output_current = spice_interface_obj.get_signal(node)
temp = spice_interface_obj.get_signal(sweepvar)
test_signal = spice_interface_obj.get_signal('i(v.xdut.vmeas3)')
# print(" %0.1fC: %0.3f uA, %0.1fC: %0.3f uA, %0.1fC: %0.3f uA, " % (temp[0], output_current[0]*1e6, temp[40], output_current[40]*1e6, temp[-1], output_current[-1]*1e6))
print(" %0.1fC: %0.3f uA, %0.1fC: %0.3f uA, %0.1fC: %0.3f uA, " % (temp[0], 2*test_signal[0]*1e6, temp[40], 2*test_signal[40]*1e6, temp[-1], 2*test_signal[-1]*1e6))
# record the measurement
for i, value in enumerate(output_current):
dimensions = (corner, vdd, temp[i])
test.measurements['current'][dimensions] = value
# validate
test.measurements.current_regulation_temp[(corner, vdd)] = output_current
test.measurements.current_regulation_process[(corner, vdd)] = output_current
# save the plot to file
spice_interface_obj.fig.savefig("outputs/temperature_sweep.svg")
@plan.testcase('operating_point')
def operating_point(test):
"""Test the trimming control functionality"""
# create the test utility object
test_utilities_obj = TestUtilities.TestUtilities()
test_utilities_obj.netlist_generation(config['temperature_sweep']['netlist'], "rundir")
# create the spice interface
spice_interface_obj = SpiceInterface.SpiceInterface(netlist_path="rundir/"+config['temperature_sweep']['netlist'].split('.')[0]+".spice")
spice_interface_obj.config['simulator']['shared'] = True
spice_interface_obj.config['simulator']['silent'] = True
# append the simulation command
spice_interface_obj.set_sim_command(config['operating_point']['sim_command'])
# prepopulate
results = np.zeros((len(config['pvt']['corners']), len(config['pvt']['vdd']), 101))
# find all the devices
devices = spice_interface_obj.find_all_mosfets()
# insert the command to save the devices
spice_interface_obj.insert_op_save(devices, ['vsat_marg'])
exempt_list = ['xdut.xbmr.XMdiff_n3',
'xdut.XMcap_ptat', 'xdut.XMcap_ctat',
'xdut.xtrim_ptat.XMcurr_3','xdut.xtrim_ptat.XMcurr_2',
'xdut.xtrim_ptat.XMcurr_1','xdut.xtrim_ptat.XMcurr_0',
'xdut.xtrim_ctat.XMcurr_3','xdut.xtrim_ctat.XMcurr_2',
'xdut.xtrim_ctat.XMcurr_1','xdut.xtrim_ctat.XMcurr_0',
]
# loop through all corners
for corner_i, corner in enumerate(config['pvt']['corners']):
# set corner
spice_interface_obj.set_corner(corner)
# reload the circuit
spice_interface_obj.restart_simulation()
# loop through the supply voltages
for vdd_i, vdd in enumerate(config['pvt']['vdd']):
# set the vdd voltage
spice_interface_obj.set_parameters([['vdd', vdd], ['en', vdd], ['start_n', vdd]])
# check device operating regions
spice_interface_obj.check_op_region('temp-sweep', exempt_list=exempt_list, skip_insertion=True, devices=devices)
# @plan.testcase('startup')
# def startup(test):
# """Test the startup capability"""
# print('test2')
# time.sleep(.2)
# @plan.testcase('psrr')
# def psrr(test):
# """Measure the power supply rejection"""
# print('test2')
# time.sleep(.2)
# @plan.testcase('noise')
# def noise(test):
# """Measure the generated noise"""
# print('test2')
# time.sleep(.2)
# @plan.testcase('en')
# def en(test):
# """Test the enable functionality"""
# htf.Measurement('current_regulation').outcome = measurements.Outcome.PASS
# # create the test utility object
# test_utilities_obj = TestUtilities.TestUtilities()
# test_utilities_obj.netlist_generation(config['temperature_sweep']['netlist'], "rundir")
# # create the spice interface
# spice_interface_obj = SpiceInterface.SpiceInterface(netlist_path="rundir/"+config['temperature_sweep']['netlist'].split('.')[0]+".spice")
# spice_interface_obj.config['simulator']['shared'] = True
# # append the simulation command
# spice_interface_obj.set_sim_command(config['temperature_sweep']['sim_command'])
# # prepopulate
# results = np.zeros((len(config['pvt']['corners']), len(config['pvt']['vdd']), 101))
# # loop through all corners
# for corner_i, corner in enumerate(config['pvt']['corners']):
# # set corner
# spice_interface_obj.set_corner(corner)
# # reload the circuit
# spice_interface_obj.restart_simulation(silence=True)
# # loop through the supply voltages
# for vdd_i, vdd in enumerate(config['pvt']['vdd']):
# # set the vdd voltage
# spice_interface_obj.set_parameters([['vdd', vdd]])
# # run the simulation
# spice_interface_obj.run_simulation(new_instance=False, silence=True)
# # spice_interface_obj.run_simulation(new_instance=True, silence=True)
# # save the response
# node = 'i(viout)'
# sweepvar = 'temp-sweep'
# spice_interface_obj.plot_dc_sweep(sweepvar, node, display=False, title="PTAT Open Loop Response",
# linewidth=1, alpha=0.5, append=True)
# # get the output current
# output_current = spice_interface_obj.get_signal(node)
# temp = spice_interface_obj.get_signal(sweepvar)
# print("%0.1fC: %0.3f uA, %0.1fC: %0.3f uA, %0.1fC: %0.3f uA, " % (temp[0], output_current[0]*1e6, temp[40], output_current[40]*1e6, temp[-1], output_current[-1]*1e6))
# # record the measurement
# for i, value in enumerate(output_current):
# dimensions = (corner, vdd, temp[i])
# test.measurements['current'][dimensions] = value
# # validate
# test.measurements.current_regulation_temp = output_current
# test.measurements.current_regulation_process = output_current
# # results[corner_i][vdd_i][0] = temp
# # results[corner_i][vdd_i][1] = output_current
# # # test the margins
# # for current_temp in output_current:
# # if current_temp < config['temperature_sweep']['limits'][0] and current_temp > config['temperature_sweep']['limits'][1]:
# # htf.Measurement('current_regulation').outcome = measurements.Outcome.FAIL
# # test.measurements['current_regulation'][results] = 0
# # save the plot to file
# spice_interface_obj.fig.savefig("outputs/temperature_sweep.svg")
# @plan.testcase('ctl')
# def ctl(test):
# """Test the trimming control functionality"""
# print('test2')
# time.sleep(.2)
if __name__ == '__main__':
plan.no_trigger()
plan.run_console(once=True)