"""DSView/libsigrokdecode front end for SCALE gate-driver timing checks.""" import sigrokdecode as srd from common.setgui_decoders.gate_timing import TimingChecker class SamplerateError(Exception): pass class Ann(object): CONTROL = 0 DELAY_OK = 1 WIDTH_OK = 2 WARNING = 3 ERROR = 4 FAULT = 5 CYCLE_OK = 6 CYCLE_WARNING = 7 CYCLE_FAULT = 8 ANN_FOR_KIND = { 'control': Ann.CONTROL, 'delay_ok': Ann.DELAY_OK, 'width_ok': Ann.WIDTH_OK, 'delay_warn': Ann.WARNING, 'width_fail': Ann.ERROR, 'missing': Ann.ERROR, 'orphan': Ann.WARNING, 'fault': Ann.FAULT, 'cycle_ok': Ann.CYCLE_OK, 'cycle_warning': Ann.CYCLE_WARNING, 'cycle_fault': Ann.CYCLE_FAULT, } class Decoder(srd.Decoder): api_version = 3 id = 'gate_driver_timing' name = 'IGBT 1SP/1SD' longname = 'Power Integrations 1SP0635 / 1SD536F2 timing checker' desc = 'Checks driver Vin1/Vstat ACK timing, faults and full pulse cycles.' license = 'gplv2+' inputs = ['logic'] outputs = [] tags = ['Clock/timing', 'Util'] channels = () optional_channels = ( {'id': 'vin', 'name': 'Vin1', 'desc': 'First driver control input'}, {'id': 'vstat', 'name': 'Vstat', 'desc': 'Driver status/acknowledge output'}, ) options = ( {'id': 'profile', 'desc': 'Driver profile', 'default': '1SP0635', 'values': ('1SP0635', '1SD536F2', 'custom')}, {'id': 'vin_active', 'desc': 'Vin1 active level', 'default': 'high', 'values': ('high', 'low')}, {'id': 'vstat_active', 'desc': 'Vstat acknowledge/fault level', 'default': 'high', 'values': ('high', 'low')}, {'id': 'delay_tolerance_ns', 'desc': 'Warning tolerance around typical ACK delay (ns)', 'default': 100}, {'id': 'orphan_min_width_ns', 'desc': 'ORPHAN minimum pulse width (ns, 0 = off)', 'default': 0}, {'id': 'custom_delay_ns', 'desc': 'Custom typical ACK delay (ns)', 'default': 250}, {'id': 'custom_width_min_ns', 'desc': 'Custom ACK width minimum (ns)', 'default': 400}, {'id': 'custom_width_typ_ns', 'desc': 'Custom ACK width typical (ns)', 'default': 700}, {'id': 'custom_width_max_ns', 'desc': 'Custom ACK width maximum (ns)', 'default': 1050}, {'id': 'custom_fault_ns', 'desc': 'Custom fault pulse threshold (ns)', 'default': 1500}, ) annotations = ( ('control', 'Vin1 edge'), ('delay-ok', 'ACK delay pass'), ('width-ok', 'ACK width pass'), ('warning', 'Warning'), ('error', 'Timing failure'), ('fault', 'Driver fault'), ('cycle-ok', 'Cycle OK'), ('cycle-warning', 'Cycle WARNING'), ('cycle-fault', 'Cycle FAULT'), ) annotation_rows = ( ('cycle', 'Vin1 / ACK full cycle', (Ann.CYCLE_OK, Ann.CYCLE_WARNING, Ann.CYCLE_FAULT)), ('vin', 'Vin1', (Ann.CONTROL,)), ('measurements', 'Measurements', (Ann.DELAY_OK, Ann.WIDTH_OK)), ('checks', 'Warnings / failures', (Ann.WARNING, Ann.ERROR, Ann.FAULT)), ) def __init__(self): self.reset() # Состояние принадлежит одному запуску декодера. Сброс не должен # переносить незавершённый кадр или частоту из предыдущей записи. def reset(self): self.samplerate = None self.checker = None # DSView передаёт время в номерах отсчётов. Частота нужна общему ядру # для перевода тайм-аутов и измерений в физические единицы. def metadata(self, key, value): if key == srd.SRD_CONF_SAMPLERATE: self.samplerate = value # Выходы регистрируются в жизненном цикле sigrok, а не в конструкторе. # Здесь регистрируются аннотации для дорожек измерений и предупреждений. def start(self): self.out_ann = self.register(srd.OUTPUT_ANN) # Адаптер переводит настройки DSView в параметры общего TimingChecker. # Пороговые алгоритмы остаются в templates/python/logic_analyzer/decoders. def _make_checker(self): custom = { 'ack_delay_typ_ns': float(self.options['custom_delay_ns']), 'ack_width_min_ns': float(self.options['custom_width_min_ns']), 'ack_width_typ_ns': float(self.options['custom_width_typ_ns']), 'ack_width_max_ns': float(self.options['custom_width_max_ns']), 'fault_threshold_ns': float(self.options['custom_fault_ns']), } return TimingChecker( self.samplerate, profile=self.options['profile'], delay_tolerance_ns=float(self.options['delay_tolerance_ns']), vin_active_high=self.options['vin_active'] == 'high', vstat_active_high=self.options['vstat_active'] == 'high', orphan_min_width_ns=self.options.get('orphan_min_width_ns', 0), custom=custom, cycle_results=True) def _put_events(self, events): for event in events: ann = ANN_FOR_KIND[event['kind']] self.put(event['start'], max(event['start'], event['end']), self.out_ann, [ann, [event['text'], event['short']]]) def decode(self): if not self.samplerate: raise SamplerateError('Cannot decode without samplerate.') self.checker = self._make_checker() has_vin1 = self.has_channel(0) if not self.has_channel(1): raise ValueError('Connect Vstat for driver response analysis.') while True: conditions = [{0: 'e'}, {1: 'e'}] if has_vin1 else [{1: 'e'}] timer_bit = 1 << len(conditions) deadline = self.checker.next_deadline() if deadline is not None: conditions.append({'skip': max(1, deadline - self.samplenum)}) # DSView mutates and returns the same native tuple on every wait. # Release that tuple before waiting again (PyTuple_SetItem requires # exclusive ownership); tuple(...) would not make a copy. pins = list(self.wait(conditions)) vin, vstat = pins[:2] now = self.samplenum # Both channels may transition at the same sample; preserve both. if has_vin1 and self.matched & 0b001: self._put_events(self.checker.on_control_edge(now, vin)) if self.matched & (0b010 if has_vin1 else 0b001): self._put_events(self.checker.on_status_edge(now, vstat)) if deadline is not None and self.matched & timer_bit: self._put_events(self.checker.expire(now)) def end(self): if self.checker is not None: self._put_events(self.checker.finish_cycles(self.samplenum))