nabbla (nabbla1) wrote,
nabbla
nabbla1

Categories:

"МКО через UART", часть 7, проверка заголовков массивов

В части 5 мы выполнили проверку командного слова, на соответствие полей "подадрес" и "приём/передача" - далеко не все их комбинации разрешены, и если пришло что-то не то, выставляем признак "ошибка в сообщении", слова данных не посылаем, а принимаемые слова данных никуда не записываем. На самом деле ещё зачастую проверяют, чтобы количество слов данных на приём/передачу соответствовало заранее оговоренному для каждого подадреса. Но мы пока что прописали в протоколе, что запрашивать можно любое количество. Знаешь, что на дальней дистанции с хорошей точностью только "активные" углы и дальность - запрашиваешь поменьше слов, только с ними. Подошли поближе и "появился" кватернион взаимной ориентации - запрашиваешь ещё на 4 слова больше. А если решили-таки выполнить фильтр Калмана по всей науке и нужна ещё ковариационная матрица шума измерений (см. прокрустово ложе 16 бит) - запрашиваешь ВСЕ СКОЛЬКО ЕСТЬ! В микросхеме 1895ВА2Т можно для каждого подадреса, отдельно на приём и на передачу, задать все возможные количества слов, которые допустимо передавать, ещё и отдельно на групповые сообщения и отдельно на "индивидуальные", хотя ГОСТ просит интерпретировать их одинаково.

Далее, если мы принимаем информацию (полётное задание, конфигурация мишени и пр.), надо проверить первое слово данных. Это уже не ГОСТ 52070-2003, а конкретно наш "протокол информационного обмена", где представители РКК Энергия решили ещё подстраховаться и предварять каждый массив данных "заголовком", уникальным для каждого подадреса.

Потом уже я, чтобы выпендриться, ввёл здесь коды Рида-Мюллера, позволяющие выбрать 32 различных 16-битных слова (каждое - на свой подадрес) таким образом, что они будут отличаться друг от друга МИНИМУМ на 8 бит!


Пожалуй, наиболее простая формулировка этих кодов такова. Мы вводим 5 слов по 16 бит каждое, так сказать "базисные функции":

f0=0101 0101 0101 0101,
f1=0011 0011 0011 0011,
f2=0000 1111 0000 1111,
f3=0000 0000 1111 1111,
f4=1111 1111 1111 1111


И затем, для подадреса состоящего из 5 битов b4b3b2b1b0 формируем слово по следующему закону:

f4b4 ^ f3b3 ^ f2b2 ^ f1b1 ^ f0b0


Тут ^ означает побитовое исключающее ИЛИ (как в верилоге или Си), а выражение вроде f0b0 - "умножение" 16-битного числа на однобитное, т.е когда b0=0, результатом становится 0000_0000_0000_0000, в противном случае f0 останется как было.

В результате получаем такую интересную простыню:
G(0)  = 0000 0000 0000 0000,
G(1)  = 0101 0101 0101 0101,
G(2)  = 0011 0011 0011 0011,
G(3)  = 0110 0110 0110 0110,
G(4)  = 0000 1111 0000 1111,
G(5)  = 0101 1010 0101 1010,
G(6)  = 0011 1100 0011 1100,
G(7)  = 0110 1001 0110 1001,
G(8)  = 0000 0000 1111 1111,
G(9)  = 0101 0101 1010 1010,
G(10) = 0011 0011 1100 1100,
G(11) = 0110 0110 1001 1001,
G(12) = 0000 1111 1111 0000,
G(13) = 0101 1010 1010 0101,
G(14) = 0011 1100 1100 0011,
G(15) = 0110 1001 1001 0110,
G(16) = 1111 1111 1111 1111,
G(17) = 1010 1010 1010 1010,
G(18) = 1100 1100 1100 1100,
G(19) = 1001 1001 1001 1001,
G(20) = 1111 0000 1111 0000,
G(21) = 1010 0101 1010 0101,
G(22) = 1100 0011 1100 0011,
G(23) = 1001 0110 1001 0110,
G(24) = 1111 1111 0000 0000,
G(25) = 1010 1010 0101 0101,
G(26) = 1100 1100 0011 0011,
G(27) = 1001 1001 0110 0110,
G(28) = 1111 0000 0000 1111,
G(29) = 1010 0101 0101 1010,
G(30) = 1100 0011 0011 1100,
G(31) = 1001 0110 0110 1001


И если "поиграться", посмотреть, насколько одно число от другого отличается, мы увидим - серьёзно отличается! Ну, G(0) ещё выглядит "подозрительно" (кажется вероятным получить все нули из-за какого-то сбоя), или G(16), но их у нас не будет. Поскольку мы ещё и две трети подадресов забраковали, у нас остаются только такие заголовки массивов:

FT = G(17) = 1010 1010 1010 1010 = 0xAAAA - полётное задание,
LT = G(18) = 1100 1100 1100 1100 = 0xCCCC - параметры МДД,
ST = G(3)  = 0110 0110 0110 0110 = 0x6666 - параметры МБД,
AF = G(20) = 1111 0000 1111 0000 = 0xF0F0 - матрица аффинного преобразования,
RA = G(5)  = 0101 1010 0101 1010 = 0x5A5A - Необработанные данные c соседнего прибора для стереорежима,
DA = G(6)  = 0011 1100 0011 1100 = 0x3C3C - целевая информация,
TM = G(24) = 1111 1111 0000 0000 = 0xFF00 - телеметрия,
IM = G(9)  = 0101 0101 1010 1010 = 0x55AA - изображение,
DU = G(10) = 0011 0011 1100 1100 = 0x33CC - дамп памяти.


Давайте подумаем, как можно проверить заголовок

Вроде бы "математика" здесь довольно проста. Если посмотреть на первые 4 "базисные функции", то увидим обычный двоичный счёт от 0 до 15, что позволяет сформировать логику в виде цикла:

module ReedMuller5_16 (input [4:0] D, output [15:0] Q);

genvar i;
generate
	for (i=0; i<16; i=i+1) begin		:another_stupid_name_for_Quartus
		assign Q[15-i] = ^(D&i)^D[4];
	end
endgenerate

endmodule

Синтезируется такая штуковина ровно в 16 ЛЭ, по одному ЛЭ на каждый выход, меньше уж никак нельзя!

И посмотрим результат симуляции, где просто перебираем возможные 32 входных значения:


Да, всё как в аптеке.

Так что первый вариант реализации - по полученному подадресу сгенерить ПРАВИЛЬНЫЙ заголовок массива и сравнить его с пришедшем по шине. Совпало - всё замечательно, не совпало - ставим признак "ошибка в сообщении", благодаря чему все последующие слова так и не будут записаны в память.

Вставим данный модуль в "протокольный контроллер":
wire [15:0]ExpectedHeader;

ReedMuller5_16 HeaderCheck (.D ({~(^HiMem), HiMem}),
			.Q (ExpectedHeader));
							


Мы взяли 4 младших бита подадреса, которые записали в регистр HiMem, а пятый бит "достроили" из чётности.

Теперь нужно выследить один конкретный момент: приход ПЕРВОГО СЛОВА ДАННЫХ в состоянии sReceive. Можно отследить это по LoMem == 0, но это проверка 5 бит, заведомо 2 ЛЭ, я предпочту завести отдельный регистр, который в состоянии sIdle будет единичным, а по приходу слова данных (DataValid==1, RXisData==1) в него будет защёлкиваться ноль. Таким образом, только на первом слове данных мы отловим правильный момент:

reg isFirstDataWord = 1'b0;
always @(posedge clk)
	isFirstDataWord <= isIdle? 1'b1 : (DataValid & RXisData)? 1'b0 : isFirstDataWord;


И наконец, дополняем код для регистра MessageError. У нас было так:
always @(posedge clk) if (isIdle)
	MessageError <= isCWerror;


isCWerror - "ошибка в командном слове", которая комбинаторно определяется по сочетанию признака "приём/передача" и подадреса.

Теперь надо, чтобы ошибка могла сформироваться и одним словом позже:

always @(posedge clk)
	MessageError <= isIdle? isCWerror : (isFirstDataWord & DataValid & RXisData)? (D == ExpectedHeader) : MessageError;


Синтезируем отдельно этот модуль, MilStdRemoteTerminalController. До введения проверки заголовков массивов, он занимал 91 ЛЭ, теперь 111 ЛЭ. Солидно, +20 ЛЭ, но в общем-то не так плохо, я ожидал гораздо худшего. И предельная частота просела только до 63,69 МГц, для меня этого пока за глаза (работаю на 25 МГц).

Ещё вдруг подумалось, можно логику проверки адреса ОУ упростить немного. Это чуть ли не первая написанная здесь строчка:
always @(posedge clk)
	OurAddrOK <= (OurAddr != 5'b11111) & (^OurAddr^AddrParity);


здесь мы в явном виде проверяли, не задан ли "широковещательный адрес" в качестве адреса ОУ? Но теперь видно, что можно эту проверку не делать, т.к даже если задать адресом ОУ "все единицы" (тот самый групповой, он же широковещательный адрес), у нас при любом групповом сообщении выставится регистр rBroadcast=1, и далее всё будет идти как при групповом сообщении, то есть ответного слова и слов данных посылать не будем. Упростим:
always @(posedge clk)
	OurAddrOK <= (^OurAddr^AddrParity);


Что ж, экономится аж 1 ЛЭ, но это только если адресная заглушка действительно "внешняя", сделана перемычками снаружи ПЛИС. А если её прямо в ПЛИС "сунуть", то вся эта логика схлопнется.

Давайте посмотрим на симуляции, как это всё работает

Конструкция в сборе (протокольный контроллер + адресная заглушка + часы реального времени + "фиктивный" передатчик) синтезируется в 165 ЛЭ, раньше было 147 ЛЭ, предельная частота 64,1 МГц, пока сойдёт.

Увы, началась какая-то фигня: самое первое сообщение ("передать полётное задание") прошло как надо, все данные записались, затем совершенно правильно мы выдали все запрошенные данные, совершенно правильно выдали ошибку при запросе данных по неправильным подадресам 0_0001 и 0_0010. А вот когда начали сыпать целую кучу HexSpeak'а по адресу телеметрии (куда запись идти не должна, только чтение) - оно в этот раз успешно записалось!

Тогда я наконец-то понял, что в выражении для MessageError допущено аж две ошибки:
always @(posedge clk)
	MessageError <= isIdle? isCWerror : (isFirstDataWord & DataValid & RXisData)? (D == ExpectedHeader) : MessageError;


Во-первых, поскольку MessageError=1 свидетельствует об ошибке, нужно вместо равенства поставить проверку на НЕРАВЕНСТВО, т.к именно это признак ошибки. И во-вторых, правильный заголовок может "сбросить" ранее обнаруженную ошибку в заголовке, чего допустить никак нельзя. Так что правильное выражение такое:

always @(posedge clk)
	MessageError <= isIdle? isCWerror : (isFirstDataWord & DataValid & RXisData)? MessageError | (D != ExpectedHeader) : MessageError;


Но увы, и после этого что-то идёт не так. Вот осциллограмма получения полётного задания:


Здесь я вывел для отладки "предполагаемый" заголовок массива. Видим, что вместо 0xAAAA он получился 0x5555. Ага, это я тоже поспешил с чётностью, вот здесь:

ReedMuller5_16 HeaderCheck (.D ({~(^HiMem), HiMem}),
			.Q (ExpectedHeader));


Инвертировать старший бит было не нужно, правильно так:
ReedMuller5_16 HeaderCheck (.D {^HiMem, HiMem}),
			.Q (ExpectedHeader));


Вот другое дело:


"На ходу" сгенерировали ровно тот заголовок, что и передавался, и поэтому мы разрешили запись сообщения в память. В прошлый раз MemWrReq не формировался.

А вот мы пытаемся положить всякие вкусности по адресу телеметрии:


Не проходит! А попробуем вместо BEEF отправить ожидавшийся FF00:


Ничего не поменялось, так и надо: заголовок правильный, но командное слово-нет!

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

Пока что опытным путём я понял: Quartus категорически не любит вот это выражение:
always @(posedge clk)
	isFirstDataWord <= isIdle? 1'b1 : (DataValid & RXisData)? 1'b0 : isFirstDataWord;


Возможно, не его одного, а в сочетании с MessageError. Срабатывает какая-то кривая эвристика, позволяющая ему утверждать: "здесь всегда будет единица", но только если непосредственно сигнал MessageError не выведен наружу. Когда я переправил его вот так:

always @(posedge clk) if (isIdle | DataValid & RXisData)
	isFirstDataWord <= isIdle;


проблема ушла.

Что же он там чудит, и является ли это проблемой симулятора или непосредственно синтеза - не знаю.

Ладно, теперь давайте в сообщении "Передача полётного задания" исказим заголовок, с 0xAAAA на 0xAAAB:


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


Что ж, проверка работает. Квартус подложил очередную свинью, точнее, Шрёдинбага (или это Гейзенбаг?), не знаю, как именно, но сейчас этой проблемы нет.

На самом деле, я думал и о более простом методе проверки заголовков, где мы не пытаемся самостоятельно генерировать коды Рида-Мюллера, а просто сверяться с ячейкой памяти. Может, если будет охота, опробую и его.

А тем временем протокол информационного обмена практически реализован. Остаётся проверка и генерация CRC, и, может быть, автоматическая (без участия процессора) выдача дампа памяти и изображения...
Tags: ПЛИС, работа, странные девайсы
Subscribe

Recent Posts from This Journal

  • Тестируем 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 

  • 1 comment