Extract reusable device protocols, ports and logic analyzers from SETGUI
This commit is contained in:
181
python/logic_analyzer/decoders/gate_timing.py
Normal file
181
python/logic_analyzer/decoders/gate_timing.py
Normal file
@@ -0,0 +1,181 @@
|
||||
"""Pure timing checker used by the DSView decoder and unit tests.
|
||||
|
||||
All time values in profiles are nanoseconds. The ACK delay in the vendor
|
||||
data sheets is a typical value only, so the delay window is intentionally an
|
||||
engineering warning threshold. ACK pulse width has specified limits and is
|
||||
therefore checked as PASS/FAIL.
|
||||
"""
|
||||
|
||||
from collections import deque
|
||||
|
||||
|
||||
PROFILES = {
|
||||
'1SP0635': {
|
||||
'ack_delay_typ_ns': 250.0,
|
||||
'ack_width_min_ns': 400.0,
|
||||
'ack_width_typ_ns': 700.0,
|
||||
'ack_width_max_ns': 1050.0,
|
||||
'fault_threshold_ns': 1500.0,
|
||||
},
|
||||
'1SD536F2': {
|
||||
'ack_delay_typ_ns': 380.0,
|
||||
'ack_width_min_ns': 600.0,
|
||||
'ack_width_typ_ns': 900.0,
|
||||
'ack_width_max_ns': 1800.0,
|
||||
# The application manual calls >1.5 us a fault, while individual
|
||||
# data sheets allow ACK pulses up to 1.8 us. A correlated pulse in
|
||||
# the specified ACK range wins; otherwise the longer limit is used.
|
||||
'fault_threshold_ns': 1800.0,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def format_ns(value_ns):
|
||||
if value_ns >= 1000000.0:
|
||||
return '%.3f ms' % (value_ns / 1000000.0)
|
||||
if value_ns >= 1000.0:
|
||||
return '%.3f us' % (value_ns / 1000.0)
|
||||
return '%.1f ns' % value_ns
|
||||
|
||||
|
||||
class TimingChecker(object):
|
||||
"""Match Vin edges to Vstat pulses and return annotation dictionaries."""
|
||||
|
||||
def __init__(self, samplerate, profile='1SP0635', delay_tolerance_ns=100.0,
|
||||
vin_active_high=True, vstat_active_high=True,
|
||||
custom=None):
|
||||
if not samplerate:
|
||||
raise ValueError('samplerate is required')
|
||||
if profile == 'custom':
|
||||
if not custom:
|
||||
raise ValueError('custom profile values are required')
|
||||
self.spec = dict(custom)
|
||||
else:
|
||||
self.spec = dict(PROFILES[profile])
|
||||
self.profile = profile
|
||||
self.samplerate = float(samplerate)
|
||||
self.delay_tolerance_ns = float(delay_tolerance_ns)
|
||||
self.vin_active_high = bool(vin_active_high)
|
||||
self.vstat_active_high = bool(vstat_active_high)
|
||||
self.pending = deque()
|
||||
self.status_start = None
|
||||
self.status_control = None
|
||||
|
||||
# Long enough to avoid calling a merely late ACK "missing", while
|
||||
# still producing a useful annotation during a capture.
|
||||
self.ack_timeout_ns = max(
|
||||
self.spec['ack_delay_typ_ns'] + 3.0 * self.delay_tolerance_ns,
|
||||
self.spec['ack_delay_typ_ns'] + self.spec['ack_width_max_ns'])
|
||||
|
||||
def samples_to_ns(self, samples):
|
||||
return float(samples) * 1000000000.0 / self.samplerate
|
||||
|
||||
def ns_to_samples(self, value_ns):
|
||||
return max(1, int(round(float(value_ns) * self.samplerate / 1000000000.0)))
|
||||
|
||||
def next_deadline(self):
|
||||
if not self.pending:
|
||||
return None
|
||||
return self.pending[0]['sample'] + self.ns_to_samples(self.ack_timeout_ns)
|
||||
|
||||
def on_control_edge(self, sample, level):
|
||||
state_on = bool(level) == self.vin_active_high
|
||||
item = {
|
||||
'sample': int(sample),
|
||||
'level': int(level),
|
||||
'edge': 'ON' if state_on else 'OFF',
|
||||
}
|
||||
self.pending.append(item)
|
||||
return [{
|
||||
'kind': 'control', 'start': int(sample), 'end': int(sample),
|
||||
'text': 'Vin %s' % item['edge'], 'short': item['edge'],
|
||||
}]
|
||||
|
||||
def expire(self, sample):
|
||||
events = []
|
||||
timeout_samples = self.ns_to_samples(self.ack_timeout_ns)
|
||||
while self.pending and int(sample) >= self.pending[0]['sample'] + timeout_samples:
|
||||
item = self.pending.popleft()
|
||||
end = item['sample'] + timeout_samples
|
||||
events.append({
|
||||
'kind': 'missing', 'start': item['sample'], 'end': end,
|
||||
'text': 'FAIL: no Vstat ACK after Vin %s (timeout %s)' %
|
||||
(item['edge'], format_ns(self.ack_timeout_ns)),
|
||||
'short': 'NO ACK',
|
||||
})
|
||||
return events
|
||||
|
||||
def on_status_edge(self, sample, level):
|
||||
sample = int(sample)
|
||||
is_active = bool(level) == self.vstat_active_high
|
||||
events = self.expire(sample)
|
||||
|
||||
if is_active:
|
||||
# Ignore a second active edge caused by an inconsistent trace.
|
||||
if self.status_start is not None:
|
||||
return events
|
||||
self.status_start = sample
|
||||
self.status_control = self.pending.popleft() if self.pending else None
|
||||
if self.status_control is not None:
|
||||
delay_ns = self.samples_to_ns(sample - self.status_control['sample'])
|
||||
typ_ns = self.spec['ack_delay_typ_ns']
|
||||
delta_ns = delay_ns - typ_ns
|
||||
in_window = abs(delta_ns) <= self.delay_tolerance_ns
|
||||
events.append({
|
||||
'kind': 'delay_ok' if in_window else 'delay_warn',
|
||||
'start': self.status_control['sample'], 'end': sample,
|
||||
'delay_ns': delay_ns,
|
||||
'text': '%s: ACK delay %s (typ %s, delta %+0.1f ns)' %
|
||||
('PASS' if in_window else 'WARN', format_ns(delay_ns),
|
||||
format_ns(typ_ns), delta_ns),
|
||||
'short': '%s %s' % ('OK' if in_window else 'WARN',
|
||||
format_ns(delay_ns)),
|
||||
})
|
||||
return events
|
||||
|
||||
if self.status_start is None:
|
||||
events.append({
|
||||
'kind': 'orphan', 'start': sample, 'end': sample,
|
||||
'text': 'Unexpected inactive Vstat edge', 'short': 'Vstat?',
|
||||
})
|
||||
return events
|
||||
|
||||
start = self.status_start
|
||||
control = self.status_control
|
||||
width_ns = self.samples_to_ns(sample - start)
|
||||
self.status_start = None
|
||||
self.status_control = None
|
||||
lo = self.spec['ack_width_min_ns']
|
||||
hi = self.spec['ack_width_max_ns']
|
||||
|
||||
if control is not None and lo <= width_ns <= hi:
|
||||
events.append({
|
||||
'kind': 'width_ok', 'start': start, 'end': sample,
|
||||
'width_ns': width_ns,
|
||||
'text': 'PASS: ACK width %s (limit %s...%s)' %
|
||||
(format_ns(width_ns), format_ns(lo), format_ns(hi)),
|
||||
'short': 'ACK %s' % format_ns(width_ns),
|
||||
})
|
||||
elif width_ns > self.spec['fault_threshold_ns']:
|
||||
events.append({
|
||||
'kind': 'fault', 'start': start, 'end': sample,
|
||||
'width_ns': width_ns,
|
||||
'text': 'FAULT: Vstat active for %s' % format_ns(width_ns),
|
||||
'short': 'FAULT %s' % format_ns(width_ns),
|
||||
})
|
||||
elif control is None:
|
||||
events.append({
|
||||
'kind': 'orphan', 'start': start, 'end': sample,
|
||||
'width_ns': width_ns,
|
||||
'text': 'Unexpected Vstat pulse %s (no Vin edge)' % format_ns(width_ns),
|
||||
'short': 'ORPHAN %s' % format_ns(width_ns),
|
||||
})
|
||||
else:
|
||||
events.append({
|
||||
'kind': 'width_fail', 'start': start, 'end': sample,
|
||||
'width_ns': width_ns,
|
||||
'text': 'FAIL: ACK width %s outside %s...%s' %
|
||||
(format_ns(width_ns), format_ns(lo), format_ns(hi)),
|
||||
'short': 'BAD ACK %s' % format_ns(width_ns),
|
||||
})
|
||||
return events
|
||||
Reference in New Issue
Block a user