157 lines
7.0 KiB
Python
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')
|