1# Copyright (c) 2023-2024 Intel Corporation
2#
3# SPDX-License-Identifier: Apache-2.0
4
5import logging
6
7from math import ceil
8
9from twister_harness import DeviceAdapter
10
11logger = logging.getLogger(__name__)
12
13
14def do_analysis(test, stats, stats_count, config, sys_clock_hw_cycles_per_sec):
15    logger.info('====================================================')
16    logger.info(f'periodic timer behaviour using {test} mechanism:')
17
18    test_period = int(config['TIMER_TEST_PERIOD'])
19    test_samples = int(config['TIMER_TEST_SAMPLES'])
20
21    seconds = (test_period * test_samples) / 1_000_000
22
23    periods_sec = test_period / 1_000_000
24    ticks_per_sec = int(config['SYS_CLOCK_TICKS_PER_SEC'])
25    periods_tick = ceil(ticks_per_sec * periods_sec)
26
27    expected_period = test_period * sys_clock_hw_cycles_per_sec / 1_000_000
28    cyc_per_tick = sys_clock_hw_cycles_per_sec / ticks_per_sec
29    expected_period_drift = ((periods_tick * cyc_per_tick - expected_period) /
30                             sys_clock_hw_cycles_per_sec * 1_000_000)
31    expected_total_drift = expected_period_drift * test_samples / 1_000_000
32
33    period_max_drift = (int(config['TIMER_EXTERNAL_TEST_PERIOD_MAX_DRIFT_PPM'])
34                        / 1_000_000)
35    min_bound = (test_period - period_max_drift * test_period +
36                 expected_period_drift) / 1_000_000
37    max_bound = (test_period + period_max_drift * test_period +
38                 expected_period_drift) / 1_000_000
39
40    min_cyc = 1. / sys_clock_hw_cycles_per_sec
41    max_stddev = int(config['TIMER_TEST_MAX_STDDEV']) / 1_000_000
42    # Max STDDEV cannot be lower than clock single cycle
43    max_stddev = max(min_cyc, max_stddev)
44
45    max_drift_ppm = int(config['TIMER_EXTERNAL_TEST_MAX_DRIFT_PPM'])
46    time_diff = stats['total_time'] - seconds - expected_total_drift
47
48    logger.info(f'min:            {stats["min"] * 1_000_000:.6f} us')
49    logger.info(f'max:            {stats["max"] * 1_000_000:.6f} us')
50    logger.info(f'mean:           {stats["mean"] * 1_000_000:.6f} us')
51    logger.info(f'variance:       {stats["var"] * 1_000_000:.6f} us')
52    logger.info(f'stddev:         {stats["stddev"] * 1_000_000:.6f} us')
53    logger.info(f'total time:     {stats["total_time"] * 1_000_000:.6f} us')
54    logger.info(f'expected drift: {seconds * max_drift_ppm} us')
55    logger.info(f'real drift:     {time_diff * 1_000_000:.6f} us')
56    logger.info('====================================================')
57
58    logger.info('RECORD: {'
59                f'"testcase":"jitter_drift_timer"'
60                f', "mechanism":"{test}_external", "stats_count": {stats_count}, ' +
61                ', '.join(['"{}_us":{:.6f}'.format(k, v * 1_000_000) for k,v in stats.items()]) +
62                f', "expected_total_time_us":{seconds * 1_000_000:.6f}'
63                f', "expected_total_drift_us":{seconds * max_drift_ppm:.6f}'
64                f', "total_drift_us": {time_diff * 1_000_000:.6f}'
65                f', "min_bound_us":{min_bound * 1_000_000:.6f}'
66                f', "max_bound_us":{max_bound * 1_000_000:.6f}'
67                f', "expected_period_cycles":{expected_period:.0f}'
68                f', "MAX_STD_DEV":{max_stddev * 1_000_000:.0f}'
69                f', "sys_clock_hw_cycles_per_sec":{sys_clock_hw_cycles_per_sec}, ' +
70                ', '.join(['"CONFIG_{}":{}'.format(k, str(config[k]).rstrip()) for k in [
71                                            'SYS_CLOCK_HW_CYCLES_PER_SEC',
72                                            'SYS_CLOCK_TICKS_PER_SEC',
73                                            'TIMER_TEST_PERIOD',
74                                            'TIMER_TEST_SAMPLES',
75                                            'TIMER_EXTERNAL_TEST_PERIOD_MAX_DRIFT_PPM',
76                                            'TIMER_EXTERNAL_TEST_MAX_DRIFT_PPM'
77                                                                                 ]
78                          ]) + '}'
79              )
80
81    assert stats['stddev'] < max_stddev
82    assert stats['min'] >= min_bound
83    assert stats['max'] <= max_bound
84    assert abs(time_diff) < seconds * max_drift_ppm / 1_000_000
85
86
87def wait_sync_point(dut: DeviceAdapter, point):
88    dut.readlines_until(regex=f"===== {point} =====")
89
90
91def test_flash(dut: DeviceAdapter, tool, tool_options, config,
92               sys_clock_hw_cycles_per_sec):
93    tool = __import__(tool)
94
95    test_period = int(config['TIMER_TEST_PERIOD'])
96    test_samples = int(config['TIMER_TEST_SAMPLES'])
97    seconds = (test_period * test_samples) / 1_000_000
98
99    tests = ["builtin", "startdelay"]
100    for test in tests:
101        wait_sync_point(dut, test)
102        stats, stats_count = tool.run(seconds, tool_options)
103        assert stats_count
104        do_analysis(test, stats, stats_count, config, sys_clock_hw_cycles_per_sec)
105
106    # Let the running test's image output to be fully captured from device.
107    dut.readlines()
108