Files
templates/tools/dsview/decoders/plates_pbus/pd.py
T
Andrey a5e46d4627 Расширить обмен по шинам, CAN-адаптеры и обработку графиков
Добавить очереди операций PBUS/SBUS, общую консоль, настройки Candle и SLCAN, декодеры DSView и обработку плотных графиков. Обновить протокол Бальзам и проверки общих компонентов.
2026-10-17 14:36:14 +03:00

141 lines
18 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""PBus по ParallelBusSlave_ver_2_0.vhd; Python 3.6+, sigrok API 3.""" # Времена выборки определяются ACK аппаратного ведомого.
import sigrokdecode as srd # DSView предоставляет цифровые отсчёты и выходные аннотации.
# CE ограничивает транзакцию, три перехода ACK подтверждают адрес, данные и их инверсию.
class Decoder(srd.Decoder): # Протокол работает непосредственно с захватом логических уровней.
api_version = 3 # API ожидания событий поддерживается libsigrokdecode4DSL.
id = 'plates_pbus' # Имя пакета не пересекается со штатным parallel.
name = 'Plates PBus' # Название в списке протоколов DSView.
longname = 'Plates 96-pin PBus' # Название связывает декодер с интерфейсом плат.
desc = 'PBus reads with address complement, ACK and inverted data checks.' # Текущий HDL реализует внешнее чтение PBus.
license = 'gplv3+' # Допустимый идентификатор лицензии libsigrokdecode.
inputs = ['logic'] # Входные данные — цифровые каналы анализатора.
outputs = ['plates_pbus'] # Структурированные результаты доступны следующему уровню.
tags = ['Embedded/industrial'] # Категория промышленного обмена.
channels = ( # Обязательные сигналы не подменяются нулём при отсутствии захвата.
{'id': 'ce', 'name': 'PBCE', 'desc': 'Active-low transaction enable'}, # Индекс 0: CE=0 разрешает обмен согласно HDL.
{'id': 'ack', 'name': 'PBACK', 'desc': 'Address / data / inverse acknowledge'}, # Индекс 1: переходы 0,1,0 задают три фазы.
) + tuple({'id': 'd%d' % i, 'name': 'D%d' % i, 'desc': 'PBDATA bit %d' % i} for i in range(16)) # Индексы 2…17 соответствуют физическим разрядам D0…D15.
optional_channels = ( # Для выборки достаточно ACK, поэтому тактовая линия необязательна.
{'id': 'clk', 'name': 'PBCLK', 'desc': 'Optional phase validation clock'}, # Индекс 18: данные ожидаются при CLK=0, инверсия — при CLK=1.
) # При наличии CLK проверяется согласованность фаз с текущим HDL.
annotations = ( # Числовые типы совместимы с форматом аннотаций DSView.
('1', 'device', 'Device'), # Индекс 0: старшая тетрада восьмибитного адреса.
('2', 'register', 'Register'), # Индекс 1: младшая тетрада адреса.
('3', 'data', 'Data'), # Индекс 2: прочитанное 16-битное значение.
('4', 'check', 'Checks'), # Индекс 3: инверсная защита адреса и ответа.
('5', 'frame', 'Transaction'), # Индекс 4: итог полного или оборванного обращения.
('1000', 'warning', 'Warnings'), # Индекс 5: ошибки адреса, ответа, фаз и отсутствующих ACK.
) # Индекс класса в put() отличается от числового типа в первом поле.
annotation_rows = ( # Раздельные строки сохраняют читаемость при перекрытии полей.
('device', 'Device', (0,)), # Устройство и регистр передаются одновременно.
('register', 'Register', (1,)), # Поэтому регистр располагается на своей строке.
('data', 'Data', (2,)), # Данные видны рядом с проверкой инверсной копии.
('checks', 'Checks', (3,)), # Результаты проверок не теряются за итогом кадра.
('frames', 'Transactions', (4,)), # Итог содержит адрес, значение и состояние обмена.
('warnings', 'Warnings', (5,)), # Ошибки выделяются штатным типом DSView.
) # Все поля выводятся без зависимости от карт регистров PlatesConf.
# Экземпляр декодера начинает без активной транзакции.
def __init__(self): # Вызывается загрузчиком протоколов.
self.reset() # Повторный проход использует такой же сброс.
# Незавершённое состояние предыдущего захвата не переносится в следующий.
def reset(self): # Сбрасывает только внутренний автомат.
self.packet = None # Словарь операции появляется на спаде CE.
self.phase = None # Ожидаемая фаза: address, data, inverse или done.
self.ss = 0 # Начало транзакции задаётся в отсчётах захвата.
self.phase_start = 0 # Начало текущего ожидания нужно для границ аннотаций.
# Аннотации и структурированные кадры регистрируются до обработки сигналов.
def start(self): # Вызывается средой DSView при запуске декодирования.
self.out_ann = self.register(srd.OUTPUT_ANN) # Текстовые поля отображаются над захватом.
self.out_python = self.register(srd.OUTPUT_PYTHON) # Пакеты сохраняют сырые значения и результаты проверок.
# Каждая аннотация привязана к фактическим границам текущей фазы.
def annotate(self, start, end, kind, text): # kind — индекс annotations, а не числовой тип DSView.
self.put(start, max(start, end), self.out_ann, [kind, [text]]) # Одноточечные переходы также допустимы.
# Порядок назначения каналов DSView задаёт значение разрядов без перестановки байтов.
def word(self, pins): # На входе полный набор сигналов текущего отсчёта.
return sum(pins[2 + i] << i for i in range(16)) # D0 имеет вес 1, D15 — 32768; результат беззнаковый.
# Ошибка добавляется и в выходной пакет, и в видимую строку предупреждений.
def warning(self, text, end): # end обозначает отсчёт обнаружения ошибки.
self.packet['errors'].append(text) # Причины остаются доступны потребителю Python-выхода.
self.annotate(self.phase_start, end, 5, text) # Предупреждение относится к текущей фазе обмена.
# Адрес находится в старшем байте; младший должен быть его инверсией согласно HDL.
def address(self, word, end): # Выборка выполняется на первом низком ACK либо при закрытии запроса без ACK.
address = word >> 8 # Ровно восемь бит делятся на два четырёхбитных адреса.
self.packet.update(dev=address >> 4, reg=address & 15, address_word=word, address_ok=((word & 255) == (address ^ 255))) # Маска 255 ограничивает инверсию одним байтом.
self.annotate(self.ss, end, 0, 'DEV 0x%X' % self.packet['dev']) # Старшая тетрада определяет устройство 0…15.
self.annotate(self.ss, end, 1, 'REG 0x%X' % self.packet['reg']) # Младшая тетрада определяет регистр 0…15.
self.annotate(self.ss, end, 3, 'ADDR 0x%04X %s' % (word, 'OK' if self.packet['address_ok'] else 'BAD')) # Видна и исходная инверсная копия адреса.
if not self.packet['address_ok']: # Несогласованность байтов не маскируется правильными данными ответа.
self.warning('Address complement mismatch', end) # На исправном ведомом такой адрес не должен получать ACK.
# Итог формируется даже при обрыве, но неполная операция никогда не считается успешной.
def emit_frame(self, end, complete): # complete означает получение всех трёх фаз ACK.
self.packet['complete'] = complete # Полнота отделена от результата проверок содержимого.
self.packet['ok'] = complete and not self.packet['errors'] # Любая ошибка проверки остаётся явной.
value = self.packet['value'] # При отсутствии фазы данных значение остаётся None.
text = 'READ %X:%X = %s %s' % (self.packet['dev'], self.packet['reg'], '?' if value is None else '0x%04X' % value, 'OK' if self.packet['ok'] else 'BAD / incomplete') # Формат не выдаёт предположение за данные шины.
self.annotate(self.ss, end, 4, text) # Итог покрывает весь интервал обращения.
self.put(self.ss, end, self.out_python, ['FRAME', self.packet]) # Даже повторное чтение того же значения остаётся отдельной операцией.
# Возврат CE либо конец захвата закрывает незаконченное ожидание ACK.
def close(self, end): # Завершённая операция уже выведена на ACK инверсии и не дублируется.
if self.packet is not None and self.phase != 'done': # Пустой захват или уже завершённый кадр не создают ошибок.
if self.phase == 'address': # При отсутствии первого ACK сохраняем хотя бы запрос на спаде CE.
self.address(self.packet['address_word'], end) # Это наблюдавшийся адрес, а не восстановление по ответу.
self.warning('Incomplete PBus: missing %s ACK' % self.phase, end) # Сообщение различает отсутствие адресного ответа, данных и инверсии.
self.emit_frame(end, False) # Частичные данные доступны с complete=False.
self.packet = None # После закрытия CE предыдущая операция больше не активна.
self.phase = None # Новая операция должна начаться новым спадом CE.
# Последний незакрытый запрос виден и при обрыве самого файла захвата.
def end(self): # Метод вызывается DSView после последнего блока данных.
self.close(getattr(self, 'last_samplenum', getattr(self, 'samplenum', 0))) # Используем последнюю известную границу захвата.
# Состояния автомата соответствуют аппаратной последовательности ACK 0 -> 1 -> 0.
def decode(self): # Ожидание событий завершается самой средой декодирования.
if not all(self.has_channel(i) for i in range(18)): # Для полного слова обязательны CE, ACK и все 16 разрядов данных.
raise ValueError('PBus requires PBCE, PBACK and all D0..D15 channels') # Отсутствующий разряд не заменяется ложным нулём.
have_clock = self.has_channel(18) # PBCLK улучшает диагностику, но не нужен для выборки по ACK.
previous = self.wait({}) # Начало посреди активного CE игнорируется до следующей полной операции.
while True: # Изменения самих данных не вызывают лишние пробуждения декодера.
pins = self.wait([{0: 'e'}, {1: 'e'}]) # Фазы ограничиваются только CE и ACK, а данные читаются в точке подтверждения.
now = self.samplenum # Все границы аннотаций задаются в отсчётах.
if pins[0]: # CE=1 завершает текущий обмен независимо от уровня ACK.
self.close(now) # Незаконченная фаза становится явной ошибкой.
else: # Только низкий CE разрешает принимать ACK ведомого.
if previous[0]: # Спад CE начинает новую операцию даже при одновременном переходе ACK.
self.ss = self.phase_start = now # Начало фазы адреса совпадает с разрешением обмена.
self.packet = {'bus': 'PBus', 'operation': 'read', 'dev': None, 'reg': None, 'address_word': self.word(pins), 'address_ok': None, 'value': None, 'inverse': None, 'inverse_ok': None, 'errors': []} # Пропущенные фазы имеют явное значение None.
self.phase = 'address' # Первым ожидается низкий адресный ACK.
falling = previous[1] == 1 and pins[1] == 0 # ACK=0 подтверждает адрес или инверсную копию в зависимости от фазы.
rising = previous[1] == 0 and pins[1] == 1 # ACK=1 подтверждает прямые данные ответа.
if self.packet is not None and self.phase == 'address' and falling: # Первый спад ACK фиксирует защищённый адрес.
self.address(self.word(pins), now) # Адрес читается после готовности приёмника, а не по произвольному фронту CLK.
self.phase, self.phase_start = 'data', now # Следующий ACK должен стать высоким при готовности данных.
elif self.packet is not None and self.phase == 'data' and rising: # Высокий ACK отмечает прямое слово ответа.
self.packet['value'] = self.word(pins) # Сохраняется всё 16-битное значение PBus.
self.annotate(self.phase_start, now, 2, 'DATA 0x%04X' % self.packet['value']) # Число показывается в шестнадцатеричном виде с ведущими нулями.
if have_clock and pins[18] != 0: # В HDL TransmitData выдаёт ACK только при низком PBCLK.
self.warning('Data ACK while PBCLK is not low', now) # Ошибка фазы помогает выявить неверное подключение либо нарушение обмена.
self.phase, self.phase_start = 'inverse', now # Далее ожидается низкий ACK инверсного слова.
elif self.packet is not None and self.phase == 'inverse' and falling: # Последний спад ACK завершает проверяемый обмен.
inverse = self.word(pins) # Инверсия выбирается отдельно от прямых данных.
valid = inverse == (self.packet['value'] ^ 65535) # Маска 65535 ограничивает инверсию ровно 16 разрядами HDL.
self.packet.update(inverse=inverse, inverse_ok=valid) # Сохраняем и сырое слово, и результат проверки.
self.annotate(self.phase_start, now, 3, 'INVERSE 0x%04X %s' % (inverse, 'OK' if valid else 'BAD')) # Проверяем содержимое, а не только наличие третьего ACK.
if not valid: # Повреждённый ответ не становится успешным из-за полного числа фаз.
self.warning('Data complement mismatch', now) # Ошибка относится к данным ответа.
if have_clock and pins[18] != 1: # В HDL TransmitCheck выдаёт ACK при высоком PBCLK.
self.warning('Inverse ACK while PBCLK is not high', now) # Проверка включается только при назначении необязательного канала.
self.emit_frame(now, True) # Итог доступен сразу после третьего подтверждения.
self.phase = 'done' # Возврат CE в единицу не должен дублировать кадр.
previous = pins # Следующий переход сравнивается с фактическими последними уровнями CE и ACK.