Files
templates/tools/dsview/tests/test_gate_timing.py

157 lines
7.0 KiB
Python

import importlib.util
from pathlib import Path
import unittest
MODULE_PATH = (Path(__file__).resolve().parents[3] / 'python/logic_analyzer/decoders/gate_timing.py')
SPEC = importlib.util.spec_from_file_location('gate_driver_timing_core', MODULE_PATH)
TIMING = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(TIMING)
def kinds(events):
return [item['kind'] for item in events]
class TimingCheckerTests(unittest.TestCase):
def test_1sp0635_nominal_ack_at_100_mhz(self):
checker = TIMING.TimingChecker(100_000_000, profile='1SP0635')
checker.on_control_edge(100, 1)
self.assertEqual(kinds(checker.on_status_edge(125, 1)), ['delay_ok'])
result = checker.on_status_edge(195, 0)
self.assertEqual(kinds(result), ['width_ok'])
self.assertEqual(result[0]['width_ns'], 700.0)
def test_1sd536f2_nominal_ack_at_100_mhz_active_low(self):
checker = TIMING.TimingChecker(
100_000_000, profile='1SD536F2', vstat_active_high=False)
checker.on_control_edge(20, 1)
self.assertEqual(kinds(checker.on_status_edge(58, 0)), ['delay_ok'])
result = checker.on_status_edge(148, 1)
self.assertEqual(kinds(result), ['width_ok'])
self.assertEqual(result[0]['width_ns'], 900.0)
def test_late_delay_is_warning_but_valid_width_passes(self):
checker = TIMING.TimingChecker(100_000_000, profile='1SP0635')
checker.on_control_edge(0, 1)
self.assertEqual(kinds(checker.on_status_edge(50, 1)), ['delay_warn'])
self.assertEqual(kinds(checker.on_status_edge(120, 0)), ['width_ok'])
def test_too_short_ack_fails(self):
checker = TIMING.TimingChecker(100_000_000, profile='1SP0635')
checker.on_control_edge(0, 1)
checker.on_status_edge(25, 1)
self.assertEqual(kinds(checker.on_status_edge(55, 0)), ['width_fail'])
def test_long_status_is_fault(self):
checker = TIMING.TimingChecker(100_000_000, profile='1SP0635')
checker.on_control_edge(0, 1)
checker.on_status_edge(25, 1)
self.assertEqual(kinds(checker.on_status_edge(225, 0)), ['fault'])
def test_missing_ack_expires(self):
checker = TIMING.TimingChecker(100_000_000, profile='1SP0635')
checker.on_control_edge(10, 0)
deadline = checker.next_deadline()
self.assertIsNotNone(deadline)
self.assertEqual(kinds(checker.expire(deadline)), ['missing'])
if __name__ == '__main__':
unittest.main()
class InputTimingTests(unittest.TestCase):
def test_both_directions_and_independent_minimums(self):
c = TIMING.InputTimingChecker(1e9, vin1_mintime_ns=100, vin2_mintime_ns=200)
c.update(0, 0, 0)
c.update(10, 1, 0)
self.assertEqual(kinds(c.update(110, 0, 0)), ['mintime_ok'])
event = c.update(140, 0, 1)[0]
self.assertEqual(event['duration_ns'], 30)
self.assertIn('Vin1 -> Vin2', event['text'])
self.assertEqual(kinds(c.update(240, 0, 0)), ['mintime_fail'])
event = c.update(280, 1, 0)[0]
self.assertEqual(event['duration_ns'], 40)
self.assertIn('Vin2 -> Vin1', event['text'])
def test_atomic_handover_and_active_low(self):
c = TIMING.InputTimingChecker(1e9, False, False)
c.update(0, 1, 1)
c.update(10, 0, 1)
events = c.update(20, 1, 0)
self.assertEqual(kinds(events), ['mintime_ok', 'deadtime'])
self.assertEqual(events[1]['duration_ns'], 0)
self.assertEqual(kinds(c.update(30, 0, 1)), ['mintime_ok', 'deadtime'])
def test_overlap_and_capture_boundaries(self):
c = TIMING.InputTimingChecker(1e9)
c.update(0, 1, 0)
c.update(10, 1, 1)
events = c.update(25, 0, 1)
self.assertEqual(kinds(events), ['overlap'])
self.assertEqual(events[0]['duration_ns'], 15)
c.update(30, 1, 1)
self.assertEqual(kinds(c.finish(50)), ['overlap'])
c = TIMING.InputTimingChecker(1e9)
c.update(0, 0, 0)
self.assertEqual(c.update(10, 1, 0), [])
self.assertEqual(c.finish(20), [])
def test_no_stale_deadtime_after_same_input_restarts(self):
c = TIMING.InputTimingChecker(1e9)
for t, a, b in [(0, 0, 0), (10, 1, 0), (20, 0, 0), (30, 1, 0), (40, 0, 0)]:
c.update(t, a, b)
self.assertEqual(c.update(50, 0, 1)[0]['duration_ns'], 10)
def test_invalid_minimums(self):
for value in (-1, float('nan'), float('inf')):
with self.assertRaises(ValueError):
TIMING.InputTimingChecker(1e9, vin2_mintime_ns=value)
class CycleResultTests(unittest.TestCase):
def cycle(self, checker, origin=0, delay=250, width=700, inverted=False):
events = checker.on_control_edge(origin + 100, not inverted)
events += checker.on_status_edge(origin + 100 + delay, 1)
events += checker.on_status_edge(origin + 100 + delay + width, 0)
events += checker.on_control_edge(origin + 3000, inverted)
events += checker.on_status_edge(origin + 3250, 1)
events += checker.on_status_edge(origin + 3950, 0)
return [e for e in events if e['kind'].startswith('cycle_')]
def test_complete_cycle_range_and_active_low(self):
for inverted in (False, True):
checker = TIMING.TimingChecker(1e9, cycle_results=True, vin_active_high=not inverted)
events = self.cycle(checker, inverted=inverted)
self.assertEqual(len(events), 1)
self.assertEqual((events[0]['kind'], events[0]['start'], events[0]['end']), ('cycle_ok', 100, 3950))
def test_warning_fault_and_next_cycle_recovery(self):
for delay, width, verdict in [(500, 700, 'warning'), (250, 100, 'fault'), (250, 2000, 'fault')]:
checker = TIMING.TimingChecker(1e9, cycle_results=True)
self.assertEqual(self.cycle(checker, delay=delay, width=width)[0]['kind'], 'cycle_' + verdict)
self.assertEqual(self.cycle(checker, origin=10000)[0]['kind'], 'cycle_ok')
def test_missing_ack_and_clipped_capture(self):
checker = TIMING.TimingChecker(1e9, cycle_results=True)
checker.on_control_edge(100, 1)
checker.expire(1500)
checker.on_control_edge(3000, 0)
checker.on_status_edge(3250, 1)
events = checker.on_status_edge(3950, 0)
self.assertEqual(events[-1]['kind'], 'cycle_fault')
checker.on_control_edge(10000, 1)
self.assertEqual(checker.finish_cycles(10100)[0]['kind'], 'cycle_warning')
self.assertEqual(checker.finish_cycles(10100), [])
def test_no_ok_before_second_ack_finishes(self):
checker = TIMING.TimingChecker(1e9, cycle_results=True)
events = checker.on_control_edge(100, 1)
events += checker.on_status_edge(350, 1)
events += checker.on_status_edge(1050, 0)
events += checker.on_control_edge(3000, 0)
events += checker.on_status_edge(3250, 1)
self.assertFalse(any(e['kind'].startswith('cycle_') for e in events))
self.assertEqual(checker.on_status_edge(3950, 0)[-1]['kind'], 'cycle_ok')