Category: космос

Category was added automatically. Read all entries about "космос".

QuatCore

"МКО" с CRC, работа над ошибками

В кои-то веки всё соединили вместе, начали тестировать и обнаружили две проблемы:

- не обеспечивается паузы между принятым сообщением и передаваемым. Доходит до того, что отвечать начинаем ещё до того, как передатчик окончит стоповый бит!
- нарушена логика обнаружения ошибки в сообщении: ещё во время приёма командного слова "зажигается" CRCerror и не "гаснет" вовремя.

Прежде чем двигаться дальше, надо это дело исправить, по возможности малой кровью.

Collapse )

Ладно, эти две проблемы устранили, потом обнаружили ещё одну, при передаче данных (CRC сбивается на 1 слово), её сейчас тоже обмозгуем. Должно быть решение, простое до безобразия, главное, его найти...
QuatCore

Тестируем "МКО" с 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 у нас нет полярности синхроимпульса, надо придумать костыль.

Collapse )

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

"МКО (Mil-Std1553) через UART", часть 1

Громко подумали, пора начать ковыряние.

Вообще, я очень надеюсь, что написанный сейчас модуль практически без изменений подойдёт для "нормального" МКО. Там, разве что, ещё логику резервирования надо будет продумать, когда два приёмника, два передатчика, и ответ надо посылать по той шине, по которой мы получили запрос.

Сейчас идея, что этот контроллер работает независимо от процессора, напрямую обращаясь к памяти. Это "Оконечное Устройство" (ОУ, оно же Remote Terminal), т.е оно само не может инициировать информационный обмен, только отвечать контроллеру шины (КШ, Bus Controller).

И сделаем "задел" для реализации по ГОСТ 52070-2003 задания адреса оконечного устройства с помощью перемычек, хотя на первое время они будут "виртуальными" внутри ПЛИС :)

Не буду пытаться сделать "универсальный модуль на все времена", потому как неизбежно получится что-то вроде 1895ВА2Т, на которую под 300 страниц документации, где ещё попробуй разберись! Реализую лишь те функции, что нужны мне конкретно в этом приборе.

Начинаем традиционно с заголовка, тут он очень упитанный:
module MilStdRemoteTerminalController (input clk,
		//интерфейс с приёмником МКО
		input [15:0] D, input RXisData, input DataValid,
		//интерфейс с передатчиком МКО
		output [15:0] Q, output TXisData, output start, input TxBusy,
		//интерфейс с оперативной памятью (малого объёма, около 1000 слов)
		output [MemWidth-1:0] Addr, input [15:0] MemIn, input MemReady, output MemWrReq, output MemRdReq,
		//интерфейс с часами реал времени
		output sync, input [15:0] TimeStamp,
		//интерфейс с адресной заглушкой
		input [4:0] OurAddr, input AddrParity);
		
parameter MemWidth = 8;




Collapse )

Нда, снова громко подумали, написав аж 15 строчек кода... Продолжение следует! Там уже возьмёмся всерьёз.
QuatCore

"Часы реального времени" для QuatCore

На мой приборчик должны каждые 100 мс прибывать сообщения "синхронизации со словом данных" по МКО (Mil-Std 1553), в которых будет передаваться число от 0 до 99, "номер текущего вычислительного такта". Просто по возрастанию, и после 99 последует 0. Тем самым, мы сможем обозначить время в интервале 10 секунд.

Получая кадр изображения, мы должны будем зафиксировать метку времени, когда он был получен (скорее даже "середину экспозиции", т.е какому моменту будут соответствовать те параметры сближения, что мы получили). По протоколу, это будет 16-битное число в формате UQ7.9, то есть беззнаковое с фиксированной запятой. Старшие 7 бит - это тот самый "номер вычислительного такта", от 0 до 99, но благодаря младшим битам можно будет отметить момент времени с точностью до ≈ 200 мкс, этого вполне достаточно, учитывая, что просто на считывания кадра с матричного фотоприёмника 1205ХВ014 будет уходить 10 мс, это при ПЛИС, "разогнанной" до 50 МГц (пока у меня 25 МГц), ну может 5 мс, если дойти до 100 МГц, и это уже предельное значение для этого фотоприёмника.

Вот думаю сделать отдельный модулёк, который будет вести счёт времени в этих 16 битах (используя обычную тактовую частоту), синхронизироваться по слову данных, а также отмечать момент получения кадра.

Collapse )

Как именно его "прикрутить" к остальному проекту - ближе к делу посмотрим. Не хочется из-за такой мелочи расширять мультиплексор шины данных QuatCore, он и так здоровенный. Лучше бы ближе к информационному обмену воткнуть...
QuatCore

Тест сортировки "слева направо"

Пора нашу программу проверить, хотя бы на симуляции.

Сразу же обнаружил ошибку, при вычислении вектора нужны были индексы 6 и 7, а я перепутал X с Z, и получились 5 и 8, зашибись.


Пока всё хорошо.
Collapse )



Отлично! Если бы мы просто сортировали по компоненте X, то точки 1 и 2 вышли бы в другом порядке. Но если мысленно провести линию между крайними точками и расположить точки вдоль неё, то получим ровно тот результат, что дал алгоритм.

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

Тестируем захват ближ. дист. на "сбалансированном" QuatCore (2)

В прошлый раз обнаружили, что нужно "протянуть" в модуль QuatCoreMem не только шины PreDestAddr и PreDestStall, но ещё и PipeStall, так что эти PreDestAddr будут "защёлкиваться" в DestAddr и прочие регистры только при условии PipeStall = 0. Исправляется элементарно, было так:
always @(posedge clk) begin
	MemWrite <= DestSquareBrac & isDest;
	RegWrite <= (~DestSquareBrac) & isDest;
	DestCountSP <= isDest & PreDestAddr[5] & PreDestAddr[4] & PreDestAddr[1] & PreDestAddr[0];
	DestAddr <= PreDestAddr[5:0];
end


а стало так:
always @(posedge clk) if (~PipeStall) begin
	MemWrite <= DestSquareBrac & isDest;
	RegWrite <= (~DestSquareBrac) & isDest;
	DestCountSP <= isDest & PreDestAddr[5] & PreDestAddr[4] & PreDestAddr[1] & PreDestAddr[0];
	DestAddr <= PreDestAddr[5:0];
end


Как ни странно, размер проекта даже уменьшился, раньше он синтезировался в 541 ЛЭ, из которых 204 регистра, а теперь - в 536 ЛЭ, из которых 198 регистров. Оно и ясно, Quartus понял, что 6-битный DestAddr здесь - это в точности те же регистры, что и младшие 6 бит DestAddr в QuatCoreCodeROM, и лишние выкинул.

Запускаем симуляцию, и картинка сразу обнадёживает:


Collapse )
Отлично! Две самые отдалённые точки действительно были перенесены в конец массива, как и было задумано. Ну и процессор, похоже, не "испортился" от нашего вмешательства, работает корректно. И команду ijk проверили - пашет, очень удобно бывает. Только её работу "на глаз" оценивать неудобно, с толку сбивают 5-битные регистры.

Испытания новой АЛУ, нахождение крена

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

;состояние регистров к этому моменту
;X = AffineMat (константная матрица 3х4, чтобы посчитать преобр)
;Y = Points2D
;Z = Matrix (матрица 2х2 и вектор 1х2, пока не AfTransf)
;i=j=k=0
;Inv неизвестен
;C, Acc - пофиг наверное
FindRoll	proc
	;нам понадобятся [Z]+[Z+3] и [Z+1]-[Z+2]
		Y		QuatY 	;в этом кватернионе построим,используя Y/Z значения как временные
					;(потом они занулятся)
					;а можно Y вместо X, если надо
		i		1
		j		3
@@sico:		Acc		[Z+i]
		Inv		i
		PM		[Z+i^j]
		[Y+i]		Acc
		iLOOP		@@sico
	
;теперь наших друзей надо отнормировать, причём динамический диапазон очень велик
;максимум 13 итераций, давайте столько и делать...
;здесь у нас появляется беззнаковое число, без него получалось бы очень грустно	

		CALL NormSiCo

;здесь у нас i=j=k=Inv=0
	
;теперь синус-косинус превращаем в кватернион, то есть в синус-косинус половинного угла
;если co>0, то
;QuatX = (1+abs(co))/2,
;QuatY = si/2
;в противном случае
;QuatX = si/2,
;QuatY = (1+abs(co))/2
	
;сейчас X = AffineMat (матрица 3х4, уже неактуальна)
;Y = QuatY (компоненты co / si),
;Z = Matrix (матрица 2х2 и вектор 1х2, к ним ещё вернёмся)
;i=j=k=Inv=0
					
		ABS		[Y+k]			
		DIV2		Acc
		ADD		16384

	;флаг S (sign) сейчас показывает знак co, что для нас очень полезно
		JGE		@@skip
		i		1		;выходит i=S
@@skip:		X		QuatA
		[X+i]		Acc
		j		1
		DIV2		[Y+1]
		Y		QuatA
		[X+i^j]		Acc		;по сути, Y+(~S)
		CALL		NormSiCo	;подготовили первые 2 компонента кватерниона
					
	;сейчас X=Y=QuatA (2 компоненты кватерниона),
	;Z = Matrix
	;теперь помножаем две матрицы, одна в явном виде - Matrix (Z)
	;вторая - задана неявно, в виде co / si (в QuatY)
	;результат идет в AfTransf
	;здесь i=1, k неизвестен, j=0, Inv=0
		Y		QuatY
		X		AfTransf	;сюда результат складируем
		i		1	;номер строки результата, и строки co/si
@@i_loop:	k		1	;номер столбца результата, и столбца AfTransf
@@k_loop:	j		1	;номер столбца co/si и строки AfTransf
		ZAcc		RoundZero	;обнулить до 1/2 мл. разр
@@j_loop:	C		[Y+i^j]	
		FMPM		[Z+2j+k]
		jLOOP		@@j_loop
		[X+2i+k]	Acc
		kLOOP		@@k_loop
		iLOOP		@@i_loop

	;здесь у нас i=j=k=0
	;теперь у нас должна получиться матрица, включающая в себя масштаб и ракурс (ужатие вдоль некоторой оси), но не крен...
	;ещё кватернион подчистим - по Y,Z может лежать всякий мусор
		[Y+k]		0
		[Y+1]		0
	FindRoll	endp


Как обычно, сначала прикинем, как можно этот код "оптимизировать" под новое АЛУ, не увеличивая его размера, и сравним выполнение на старом и новом АЛУ.

Collapse )

Что ж, целых две ошибки обнаружили - обе исправили. Выполнение самую чуточку ускорилось, и то хлеб.

Следующая часть: измерение масштаба.
QuatCore

Испытания новой АЛУ, сортировка против часовой стрелки

Опробуем вот этот код:

;состояние регистров к данному моменту:
;X = Y =Z = Points2D
;i = индекс наиболее отдалённой точки  (не понадобится)
;j=k=0
;Inv неизвестен
;C = [Fx0]  (не понадобится)
;Acc = разность полусумм квадратов (не понадобится)
SortCCW		proc
;Далее: нужно расставить оставшиеся точки против часовой стрелки
;Первый шаг к этому: переместить начало координат в МДД3, который сейчас по нулевому индексу
	X	Fx1	;чтобы индекс от 2 до 0 соотв. точкам (Fx1,Fy1) ... (Fx3, Fy3)
	Z	Fx2	;чтобы иметь сдвинутую на 1 адресацию
;потом вернём как ни в чём не бывало, у нас фикс. точка, все правила арифм. соблюдаются						
	CALL	ShiftOrigin
;вот теперь пора сортировать!
	j	1
	i	1
;Делаем "сдвинутую" адресацию [Fx2 + j], т.е j=1 указывает на 3-ю точку, j=0 - на 2-ю.
;но при этом, i = 1 указывает на 2-ю, i=0 - на 1-ю
;а нулевую пока вообще не рассматриваем - она на своём месте!
;хотим на позицию j+2 поставить "самую большую", для этого надо выбрать между j+1,...1
;здесь заведомо k=0, так что X+2i / X+2j не нужны!
@@loop:	C	[Z+2j+k]
	MUL	[X+2i+1]
	C	[Z+2j+1]
	FMS	[X+2i+k]	;нахождение "векторного произведения"
	JL	@@skip
	;меняем местами, как обычно, точки с коорд. i и с коорд. j
	CALL 	SwapPoints
@@skip:	iLOOP	@@loop
	jLOOP	@@loop
				
	CALL	ShiftOrigin		
	;а теперь ещё надо два последних поменять местами, для совместимости с алг. сопровождения
	;здесь i=j=k=0
	X	Fx3
	CALL	SwapPoints
SortCCW		endp


Но ещё здесь вызывается процедура ShiftOrigin:
;отражает точки относительно наиболее отдалённой. Повторное применение этой операции возврщает точки на место!
;состояние регистров к данному моменту:
;X = Points2D+2 = Fx1 (т.е указывает на точку с индексом 1)
;Y = Points2D (точка с индексом 0)
;Z неважен (вообще Fx2, точка с индексом 2)
;i неважен (вообще это индекс наиболее отдалённой точки, а потом 0)
;j,k неважны (вообще j=k=0)
;Inv неизвестен
;C неважен (вообще [Fx0] при первом вызове, одна из коорд. при втором) 
;Acc неважен (вобще разность полусумм квадратов при первом вызове, результат сравн. при втором)
ShiftOrigin proc
;надо из всех точек вычесть коорд. наиболее отдалённой (с индексом 0)
;кроме неё самой, разумеется
		k		1
@@k_loop:	i		2
@@i_loop:	Acc		[Y+k]
		SUB		[X+2i+k]
		[X+2i+k]	Acc
		iLOOP		@@i_loop
		kLOOP		@@k_loop
		JMP		[--SP]
ShiftOrigin endp


На старом АЛУ всё это безобразие выполняется (до выхода из нижнего ShiftOrigin) 13,08 мкс, или 327 тактов. Происходит 3 сравнения, и на самом первом из них (индексы 1 и 2) точки меняются местами.

Для начала сами прикинем, насколько быстрее здесь может оказаться новое АЛУ и как можно "разогнать" этот код, не увеличивая его размер. Потом запустим симуляцию.

Collapse )

Фух, исправили ещё две ошибки, сейчас оно работает правильно, и "сортировка" (второй этап аффинного алгоритма) выполнилась за 12,24 мкс, или 306 тактов, что на 21 такта меньше, чем раньше. Ровно столько мы и ожидали! Итого, улучшение на 6,4%, мелочь, а приятно. Самое драматическое улучшение мы ожидаем на алгоритме обнаружения пятен, ради которого всё это и затеяли. Но его куда тяжелее отлаживать, поэтому пока тренируемся на кошках.

Дальше на очереди: перемножение матриц для нахождения матрицы аффинного преобразования.
QuatCore

Новый АЛУ - работа над ошибками

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

Корректно работала команда Acc (положить значение в аккумулятор) и SUB (вычесть из аккумулятора), а вот с SQRD2 (возвести в квадрат и поделить на 2) возникла проблема - откуда-то возник знак "минус". Это заставляет повнимательнее приглядеться к самой длинной строке в модуле QuatCorePipelineALUcontrol.v:

FirstDivSign <= OpSign ^ ((DestAddr[4] & CisSignedC & (isFirstDiv | MulFirst)) | (D[15] & (~DestAddr[4]) & (~DestAddr[3]) & DestAddr[2]));


Которую мы "механически" получили из такой строки:

wire FirstDivSign = OpSign ^ ((isLongCommand & isSignedC & (isFirstDiv | MulFirst)) | (SeniorDataBit & (~isLongCommand) & (~DestAddr[3]) & DestAddr[2]));


Дело определённо в ней!

Collapse )

Ура, процедура поиска самой отдалённой точки выполнилась верно (её индекс оказался 3), и на выполнение ушло 44,71 мкс. На старом АЛУ уходило 47,36 мкс, т.е мы сэкономили 66 тактов (5,6%). Мы ожидали 65 тактов. Один лишний такт пришёлся на невыполнившийся переход JL, когда во время работы АЛУ у нас также шла выборка из памяти [SP].

Ещё и размер модуля ALUcontrol вернулся к своим родным 42 ЛЭ.

Жизнь начинает удаваться :) Сейчас, наверное, быстренько поглядим остальные этапы работы (сортировка точек против часовой стрелки, нахождение матрицы аффинного преобразования, нахождение крена, масштаба и вектора), и если всё хорошо - вернёмся к злополучному "обнаружению точек".
QuatCore

Долбаные циклические счётчики!

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

Когда-то их уже продвигали изо всех сил "наши дорогие коллеги" (очень дорогие) из Великого Новгорода, но по счастью от данного изделия удалось их отогнать.

Я считаю, что для МКО (он же МКИО, он же Mil-Std 1553, он же ГОСТ Р 52070-2003) эти счётчики - как зайцу стоп-сигнал. Понятно, когда речь идёт о передаче данных через здоровенную децентрализованную сеть вроде Интернета, где один пакет может пойти более "коротким" путём, другой - более длинным, третий застрять где-то на секунду и быть объявленным "потерянным", четвертый действительно пропадёт, и на приёмную сторону они придут не все и не в том порядке.

Тогда действительно, пронумеровав все пакеты в порядке их отправки, можно такую ситуацию отследить, запросить повторно утерянные пакеты - и собрать сообщение целиком, как надо. Но ведь в МКО таких проблем нет!


Collapse )

PS. А ведь даже в компьютерных играх, чтобы снизить задержки, повально с TCP переходят на UDP, лишь бы задержки снизить!

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