Добавить очереди операций PBUS/SBUS, общую консоль, настройки Candle и SLCAN, декодеры DSView и обработку плотных графиков. Обновить протокол Бальзам и проверки общих компонентов.
141 lines
18 KiB
Python
141 lines
18 KiB
Python
"""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.
|