automated python test frameworks for hardware verification and validation
DESCRIPTION
Codemash 2012TRANSCRIPT
Automated Python Test Frameworks for Hardware Validation and Verification
Barbara Jones
Boeing 787 Fatigue Test
Rack of instruments to measure strain
Definitions & Theory Strain gauge Thermocouple Channel Sample rate Waveform Waveform generator
Why test hardware?
VerificationValidation
Software Techniques Don’t Help
Unit TestsMockingTDD
What does help?
Spec-based testingTest stand automationA good test framework
Bed-of-nails tester
Engineeringtest rig
Test Framework Requirements Easily design
broad tests from spec
Control external equipment
Easy math handling
def module_test_1(suite, cl, slave_cls): def verify(result, success, args): if not success: return False if result[-1] != args: return False return True arg1 = 0.1 arg2 = 0.2 calls = [Call(cl.InstrumentSpecific.ThisIsAMethod, arg1, arg2), Set(cl.InstrumentSpecific, "ThisIsAProperty", arg1), Get(cl.InstrumentSpecific, "ThisIsAProperty")] suite.add(calls, verify, arg1) def add(suite, cl, slave_cls): module_test_1(suite, cl, slave_cls)
Designing tests based on hardware specsPhase alignment on multiple instruments must
be within 0.000010 arc seconds
𝑋𝑘=∑𝑛=0
𝑁− 1
𝑥𝑛𝑒−2𝜋 𝑘
𝑁𝑛
𝐴𝑘=|𝑋𝑘|=√ℜ ( 𝑋𝑘)2+ℑ (𝑋𝑘 )2
Discrete Fourier Transform
Amplitude
Phase
𝜙𝑘=atan 2( ℑ (𝑋𝑘 ) ,ℜ (𝑋𝑘 ) )
Phase Alignment
Aligned
Out of Phase
Controlling external equipment
Agilent 33220A Waveform Generator
Specifications IEEE 488.2 (1975) SCPI (1990) VXI-11 (1995)
from vxi11client import * class ag33120(TCPVXI11Client): timeout = 10000 def __init__(self, hostname, gpib): TCPVXI11Client.__init__(self, hostname, 1, gpib) def query(self, str): self.device_write(self.lid, self.timeout, self.timeout, 0, str) return self.device_read(self.lid, 256, self.timeout, self.timeout, 0x80, 0x0a) def check_error(self): res = self.query(":SYST:ERR?\n") err, msg = res.data.split(",") if int(err) != 0: raise Exception(res.data) def reset(self): self.device_write(self.lid, self.timeout, self.timeout, 0, "*RST;\n") def apply(self, func, freq=None, amp=None, off=None): freq = self._defopt(freq) amp = self._defopt(amp) off = self._defopt(off) return self.query("APPLY:%s %s, %s, %s; *OPC?\n" % (func, freq, amp, off)) def _defopt(self, arg): if arg == None: return "DEF" return "%12.7e" % arg
Controlling external equipment
def dc(self, off=None): return self.query("APPLY:DC 1, 1, %s; *OPC?\n" % (off)) def set_highimpedance_mode(self): return self.query("OUTP:LOAD 9.9E37; *OPC?\n") def sine(self, freq=None, amp=None, off=None): return self.apply("SIN", freq, amp, off) def square(self, freq=None, amp=None, off=None): return self.apply("SQU", freq, amp, off) def triangle(self, freq=None, amp=None, off=None): return self.apply("TRI", freq, amp, off) def ramp(self, freq=None, amp=None, off=None): return self.apply("RAMP", freq, amp, off)
Controlling external equipment
Phase Alignment Test
# Data acquisition characteristics: FILTER_TYPE = ex1629constants.IIRFILTER_TYPE_NONE SAMP_FREQ = 1000.0 SAMPLE_COUNT = 1000 # Expected input: 1V peak (2V peak-to-peak) sine wave at 10Hz. INPUT_AMPLITUDE = 1 INPUT_FREQ = 10 # Maximum tolerated skew (in seconds): MAX_PERMISSIBLE_SKEW = 0.000010 def add_phase_test(suite, cl, slave_cls, wavegen) def add(suite, cl, slave_cls): wavegen = require_slaves(slave_cls, "ag33120") slaves = require_slaves(slave_cls, "ex1629") add_phase_test(suite, cl, slaves, wavegen)
def add_phase_test(suite, cl, slave_cls, wavegen): # Constant parameters PI = 4*atan(1) if suite.test_level == test.FULL_TEST: NUM_INNER_LOOPS = 2000 # approximately 13 hours with two devices else: NUM_INNER_LOOPS = 10 # approximately 4 minutes with 2 devices def verify(result, success, args): # Since the actual data analysis was performed in analyze_data, # we only need to check the function call returns to determine # overall success/failure. for call_result in result: if call_result == False: return False return success # set up the waveform generator # Sine wave at 10Hz, 1V peak (2V peak-to-peak), no offset calls = [Call(wavegen.reset), Call(wavegen.set_highimpedance_mode), Call(wavegen.sine(INPUT_FREQ, INPUT_AMPLITUDE, 0)] suite.add(calls) for config_name in config_sequence
Setup & Verify
for config_name in config_sequence: calls = [] # Configure the master & slave devices calls += configure_device(cl) calls += configure_device_trigger(cl, configs[config_name]['master']) for slave_cl in slave_cls: calls += configure_device(slave_cl) calls += configure_device_trigger(slave_cl, configs[config_name]['slave']) suite.add(calls, verify, None) for inner_loop in range(0, NUM_INNER_LOOPS): calls = [] # Instruct the master device to issue a sync pulse calls += [Call(cl.soft_sync)] # Start acquiring data on each slave device for slave_cl in slave_cls: calls += [Call(slave_cl.trig_init)] # Start acquiring data on the master device calls += [Call(cl.trig_init)] # Issue a soft trigger command to the master device calls += [Call(cl.soft_trig)] # Read the data calls += [Call(analyze_data, cl, slave_cls, config_name)] suite.add(calls, verify, None) # Reset the slave devices & the master device calls = [] for slave_cl in slave_cls: calls += [Call(slave_cl.reset)] calls += [Call(cl.reset)] suite.add(calls, verify, None)
Test Sequence
N = len(device_result.datapages) n = INPUT_FREQ * N / SAMP_FREQ val_dft_real = val_dft_imag = 0.0 k = 0 for page in device_result.datapages: val_dft_real += page.dataset[board][0].data[0] * cos(2*PI*n*k/N) val_dft_imag -= page.dataset[board][0].data[0] * sin(2*PI*n*k/N) k += 1 val_magnitude = sqrt(pow(val_dft_imag, 2) + pow(val_dft_real, 2))/N val_phase = atan2(val_dft_imag , val_dft_real)
DFT Calculation
Phase Alignment Test Data
Questions?