nabbla (nabbla1) wrote,
nabbla
nabbla1

Categories:

Тестируем "МКО" с CRC

Вроде как написали самую полную версию "протокольного контроллера МКО" (хоть и вместо настоящего МКО он пока использует UART), а ещё полудуплексный 16-битный приёмопередатчик с модулем CRC. Пора бы посмотреть, что они между собой дружат.

Для начала на симуляции. Но для этого нужно в ПЛИС "подружить" два приёмопередатчика между собой, чтобы они думали, что посередине линия передачи RS485. Т.е с одной стороны в этот модулёк будут поступать провода rxd (выход данных с приёмника), txd (вход данных на передатчик) и RW (0, если нужно принимать данные, 1, если нужно передавать). И с другой стороны то же самое. Причём rxd - двунаправленная линия, при RW=1 приёмник должен перевести свой выход на rxd в Z-состояние (высокое выходное сопротивление).

Как-то так:
module RS485dummyLine (input RW0, txd0, inout rxd0,
			input RW1, txd1, inout rxd1);
					
assign rxd0 = RW0? 1'bz : txd1;
assign rxd1 = RW1? 1'bz : txd0;					
									
endmodule


Фактически, передатчик одного модуля соединяется с приёмником соседнего (и наоборот), но приёмник "слепнет" во время работы собственного передатчика, тем самым моделируя полудуплекс.

И ещё "нарисуем" схему всего модуля информационного обмена (протокольный контроллер + память + часы реального времени + приёмопередатчик + CRC) в сборе:


Большинство выводов этой схемы - отладочные, посмотреть "что там творится". А по сути, "наружу" выходят:
- txd, rxd и RW - интерфейс с "физическим уровнем" приёмопередатчика,
- Mark - сигнал скорее всего от процессора (назначим ему отдельный адрес и декодер), что половина экспозиции кадра завершена. Устанавливает на выход часов реального времени метку, когда это произошло.

Весь остальной обмен должен будет идти через "общую" память, это сделаем, как только хреновину отладим. Хотя, также в отладочных целях, я наверное введу ещё остановку процессора в ожидании прихода "Синхронизация (с СД)", не готов я к тому, что процессор будет жить своей жизнью, перемалывая по 25 кадров в секунду, а мы будем получать данные "с какого повезёт".

Синтезируется такая схема в 240 ЛЭ, предельная частота 52,91 МГц. Злой рок: первое число неумолимо растёт, второе столь же неумолимо падает!

Впрочем, ещё одну вещь мы позабыли: контроллеру нужно как-то отличать между собой слова данных и командные слова! Он ожидает, что ему это сообщат прямым текстом, по проводу RXisData, руководствуясь полярностью синхроимпульса в МКО (Mil-Std 1553). Но в UART у нас нет полярности синхроимпульса, надо придумать костыль.


На ум приходит отмечать командное слово длинной паузой перед ним. То есть, если сообщений давно не шло - у нас некий регистр устанавливается в единичку: "следующее слово будет командным". А как только оно придёт - он сбросится, поэтому все остальные слова будут сочтены словами данных.

И у меня дурацкое желание задействовать здесь "часы реального времени", чтобы не городить очередной довольно крупный счётчик только ради этой задачи. На передачу одного 16-битного слова по UART на скорости 921600 бод уходит 21,7 мкс, если без пауз. В наших "часах реального времени" самый младший делитель выдаёт импульсы каждые 195 мкс.

Чтобы заведомо обнаружить паузу, нужно получить два таких импульса. Одного может быть недостаточно: если нам так повезло получить очередное сообщение как раз за несколько тактов до него, то мы не должны решить, что уже прошла длинная пауза. А вот если пришло два импульса, то значит как минимум 195 мкс сообщений не было, а может и все 390 мкс, тут уж как повезёт. Сформировать сигнал RXisData сможет такой вот небольшой модуль:

module RXisDataForUART (input clk, input WordReceived, input tick, output RXisData);

reg r0, r1;

always @(posedge clk) begin
	r0 <= WordReceived? 1'b1 : r0 & ~tick;
	r1 <= WordReceived? 1'b1 : r1 & (~tick | r0);
end

assign RXisData = r1;

endmodule


У нас два 1-битных регистра, r0 и r1, изначально они предполагаются нулевыми. r1 и подаётся на выход в качестве сигнала RXisData (1: приняли слово данных. 0: приняли командное слово или ответное слово). То есть мы ожидаем, что придёт командное слово. Когда оно приходит (WordReceived=1), на текущем такте ещё сохраняется RXisData=0, т.е протокольный контроллер его воспримет как командное.

А уже к следующему такту установится r0=r1=1, т.е теперь, мы считаем, будут идти слова данных. Пока tick=0, будет продолжаться r0=r1=1. Первый раз, как придёт tick=1, r0 сбросится в ноль. А во второй раз, в ноль сбросится и r1, если только по ходу дела не будут приходить WordReceived=1, возвращая оба регистра в единицу.

Синтезируется аж в 2 ЛЭ, шикарно. Ещё нужно вывести сигнал tick из наших "часов реального времени", ну это халява. И получаем вот такую общую схему:


Теперь уже 243 ЛЭ и 52,08 МГц, но пока сойдёт.

Всё это хозяйство превращаем в схемотехнический символ - и соединяем его с имитацией линии передачи данных и контроллером шины:


Входы и выходы, относящиеся к контроллеру шины, мы пометили префиксом BC (Bus Controller), а относящиеся к оконечному устройству (именно его и пытаемся сделать) - RT, Remote Terminal.

При синтезе квартус успешно разобрался с Z-состоянием (понял, что оно успешно заменяется мультиплексором), и отсинтезировал это всё безобразие в 313 ЛЭ, предельная частота 50,76 МГц.

И давайте, наконец, начнём симуляцию!

1. Сообщение на чужой адрес, например, адрес 0.


Как будто бы всё правильно, но много "нюансов". Во-первых, когда обе стороны переходят на приём, они начинают гонять по кругу всякий мусор. Этот наш RS485dummyLine, так выходит, соединяет их полным дуплексом: выход передатчика одного в входу приёмника другого. А там ради упрощения нашего полудуплексного передатчика, допускается подача "мусора" на txd, если RW=0. Считается, что передатчик всё равно отключён и проигнорирует, что туда идёт. Надо сделать именно так, чтобы не пугаться странного поведения:

module RS485dummyLine (input RW0, txd0, inout rxd0,
					input RW1, txd1, inout rxd1);
					
//передаёшь сам - отключись от приёма. Передаёт сосед - слушай его. Никто не передаёт - будет единичка.					
assign rxd0 = RW0? 1'bz : RW1? txd1 : 1'b1;	
assign rxd1 = RW1? 1'bz : RW0? txd0 : 1'b1;
									
endmodule


А ещё давайте "вытащим" наружу состояние конечного автомата протокольного контроллера, State. Это можно сделать в самом Vector Waveform File, надо только выбрать не входы/выходы (pins), а Registers:post-fitting, и там найти. И время моделирования увеличим до 2 мс, т.к теперь нам придётся делать огромные паузы между сообщениями, чтобы RXisData сбрасывалась в ноль.


Уже лучше. Видим, что state остаётся нулевым, т.е sIdle, т.к слово адресовано не нам. Видим, что вскоре по завершении передачи слова (два раза по 8 бит), RXisDataWord устанавливается в единицу.

Проследим, как долго она держится в единице:


Почти 523 мкс. Причина опять в своеобразном запуске нашего делителя частоты в QuatCoreRTC. Поскольку загружать константу в счётчик (sset) и считать вплоть до cout=1 сильно проще, чем сравнивать его выход с константой, мы так и делаем. 13-битный счётчик (от 0 до 8191), но счёт он начинает с 3309, что и даёт примерно 195 мкс (40 нс * (8191-3309)). Но при первом включении он всё же считает от нуля, поэтому отсчитывает аж 40 нс * 8192 ≈ 328 мкс. И это мы дождались лишь первого импульса! Потом ещё второй должен прийти, через 195 мкс, вот и выходит 523 мкс...

Можно ещё разобраться, почему так интересно ведёт себя RT_Addr (адрес в памяти, который запрашивает оконечное устройство). Дело в том, что в состоянии sIdle младшая часть адреса (5 бит) непрерывно сбрасываются в ноль, а старшая - непрерывно устанавливается из младших 4 бит подадреса, взятого из принятого слова. А это сдвиговый регистр приёмопередатчика, в который данные "вдвигаются" со старшего бита. Поэтому, первые 5 принятых бит "замещают" адрес, 6-й - признак "приём/передача", 7-й - старший бит подадреса, и только последний, 8-й из первого байта оказывает влияние, сбрасывая единичку. Было 0x1E0 = 1_1110_0000 (а первоначально мы этот сдвиговый регистр в единицы установили, "для красоты"), стало 0x0E0 = 0_1110_0000.

Затем этот 0x0E0 продолжается на стоповом бите, и до серединки стартового бита. Там опять вдвигаются нули, теперь по одному. За 0x0E0 = 0_1110_0000 выходит 0x060 = 0_0110_0000, затем 0x020 = 0_0010_0000, и наконец 0x000 = 0_0000_0000. (младшие 5 бит всегда остаются нулевыми). Да, всё так. А на RT_MemIn приходит содержание памяти по заданному адресу. Я туда зачем-то позапихал 0xDEAD, чтобы подчеркнуть - "сюда так ничего и не смогли записать".

2. "Получить полётное задание".

Сообщение с нашим адресом 6, подадресом 100012 = 0x11, число слов данных: 4 (для теста, так-то их побольше обычно должно быть). Должны быть переданы слова:
0x3224 (командное),
0xAAAA (заголовок массива FT, Flight Task),
0x0001,
0x0002,
0x0003


Что-то мне подсказывает, что CRC от 0xAAAA, 0x0001 и 0x0002 будет вовсе не 0x0003, и должно будет прийти ответное слово с ошибкой. Ща проверим:


Слово мы получили - и переключились в состояние 1, т.е sReceive. Запросили адрес 0x020 = 0_0010_0000, всё верно: младшие 5 бит - это "номер слова в сообщении", старшие - "подадрес", тут подадрес 1. Было послано MemRdReq=1, через несколько тактов вернулось MemReady=1. Это у нас "проверка заголовков ПОПРОЩЕ" (сверка с заголовком, хранящимся в памяти). Но вместо корректного заголовка 0xAAAA, из памяти приходит всё тот же 0xDEAD. Посмотрим, что дальше:



Приходят 4 слова, мы всё это время сидим в состоянии 1 (sReceive). Адрес памяти инкрементируется, но сигналы MemWrReq=1 не идут, т.к мы уже по заголовку решили, что сообщение неправильное!

Глянем повнимательнее, что происходит в момент приёма последнего слова:


Из состояния 1 (sReceive) буквально на 1 такт переходим в состояние 2 (sReply), но этого такта хватает, чтобы отправить ответное слово, перейти в состояние 3 (sTransmit), но тут же сообразить, что делать там нечего - и возвратиться в 0 (sIdle).

Очень интересно поведение приёмопередатчика. Он буквально тактом ранее завершил приём сообщения. При поступлении команды на передачу, на линии сразу должен был появиться нолик! Но он запоздал на 12 тактов. Похоже, это произошло из-за того, что контроллер шины "не отпустил" линию! Он считает свою работу завершённой (слово переданным), когда честно прошёл стоповый бит до самого окончания. А приёмнику достаточно убедиться, что в серединке стопового бита действительно единица - и он уже выдаёт результат. И вот по этому результату мы уже успеваем перейти в другое состояние, инициировать передачу - а ведь ещё стоповый бит идёт!

Похоже, "в модели" нам повезло: даже несмотря на запоздалую реакцию приёмопередатчика "контроллера шины" (он заканчивал передачу, и только потом перешёл на приём, когда уже половина стартового бита прошла), ответное слово приняли верно:


0x3400 = 0011_0100_0000_0000. Старшие 5 бит 00110 = 6 - это адрес оконечного устройства, всё верно. Затем идёт признак ошибки, потому как нам заголовок массива не понравился!

Забыли ещё один должок: ввести паузу перед отправкой ответного слова! Как видно, она даже в UART не помешает, а в МКО всё ещё строже. Пауза между концом сообщения и ответным словом должна составлять от 4 до 12 мкс, только вот измеряется эта пауза специфически в ГОСТ Р 52070-2003:



Берётся последний переход между уровнями на предыдущем сообщении и первый переход (в синхроимпульсе) в ответном слове. А если понимать паузу "по-человечески", выйдет и вовсе от 2 до 10 мкс.

И всё-таки, что там у нас с CRC? Давайте наконец вместо 0xDEAD по адресу 0x020 поставим 0xAAAA, и попробуем ещё разок:


Что-то странное происходит... Когда я не увидел запросов MemWrReq и здесь, стал смотреть сигнал MessageError, он включается в единицу сразу же по переходу в состояние sReceive. Впрочем, более пристальный взгляд показывает, ошибка включается ещё тактом позже:


И причиной тому: CRCerror=1.


С нахрапу "прикрутить" CRC не получилось, немножко не рассчитал. И ещё всплыл старый должок с обеспечением паузы между приёмом и передачей ответного слова. Что ж, завтра будем исправлять.
Tags: ПЛИС, работа, странные девайсы
Subscribe

  • Тестируем atan1 на QuatCore

    Пора уже перебираться на "железо" потихоньку. Решил начать с самого первого алгоритма, поскольку он уже был написан на ассемблере. В программу внёс…

  • Формулы приведения, что б их... (и atan на ТРЁХ умножениях)

    Формулу арктангенса на 4 умножениях ещё немножко оптимизировал с помощью алгоритма Ремеза: Ошибка уменьшилась с 4,9 до 4,65 угловой секунды, и…

  • Алгоритм Ремеза в экселе

    Вот и до него руки дошли, причина станет ясна в следующем посте. Изучать чужие библиотеки было лениво (в том же BOOSTе сам чёрт ногу сломит), писать…

  • atan на ЧЕТЫРЁХ умножениях

    Мишка такой человек — ему обязательно надо, чтоб от всего была польза. Когда у него бывают лишние деньги, он идёт в магазин и покупает какую-нибудь…

  • Ай да Пафнутий Львович!

    Решил ещё немного поковыряться со своим арктангенсом. Хотел применить алгоритм Ремеза, но начал с узлов Чебышёва. И для начала со своего "линейного…

  • atan(y/x) на двух умножениях!

    Чего-то никак меня не отпустит эта тема, всё кажется, что есть очень простой и эффективный метод, надо только его найти! Сейчас вот такое…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 0 comments