January 15th, 2021

Sidious

Это про меня


(ссылка)

- Дилберт, ты возглавишь проект, который решит твою карьеру. Вся дальнейшая твоя жизнь зависит от того, как ты покажешь себя здесь.
- Каков мой бюджет?
- Никакого.


Пока сижу "глубоко в отладке" - вроде всё неплохо, но как вспоминаю, как это всё начиналось, и кто всем командует - злость берёт!

Collapse )
QuatCore

Супрематический алгоритм обнаружения на ассемблере готов!

Ладно, прекратим рассматривать два обособленных варианта, обойдёмся без AUP (Acquire UPdate, команда для видеопроцессора, чтобы не добавлять новое задание на обработку, а откорректировать уже отправленное).

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

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

Начнём с первой:

TaskPending proc
		ABS	C
		JNO	[SP]		;вот в чём прелесть стека без инкремента!
		;если дошли до сюда, заказываем отрезки на обработку
AckNoCheck:	Acc	[X+2j+k]
		DIV2S	[X+1]	;теперь в аккмуляторе у нас X - Ceil(D/2)
		ACQ	Acc		;первый отрезок
		ADD	[X+1]	;а вот теперь X + Floor(D/2)
		ACQ	Acc		;второй отрезок
		JMP	[SP]
TaskPending endp	


Collapse )

Попробуем хотя бы откомпилить всё это безобразие. Старая версия компилится в 206 слов кода (412 байт) и 329 слов данных (658 байт), это "весь проект": выставить тактовую частоту 25 МГц, инициализировать ЖК-экранчик и выдать на него "приветствие", обработать кадр и выдать в удобочитаемом виде результаты этой обработки, а затем передать дамп памяти, и его же передать в случае какого-то из прерываний или нехватки памяти.

Новый алгоритм больше не требует запоминать яркость обнаруженных точек (нам хватает, что она превышает порог), поэтому можно сократить каждый элемент списка с 5 до 4 слов.

Компилиться он сходу не захотел, возмутился на команду [Z+2j], которой у нас нет, есть взамен [Z+2j+k] (и мы решили, что k=0 на всём протяжении, также как и j=1). Да, ошибся малость.

Далее ему не понравилась строка
@@MidCycle:	%CheckHazards OFF


ну да, привередливый, поэтому сделал метку всё-таки по самой команде:
		%CheckHazards OFF
@@MidCycle:	X		[X+k]


Потом он не нашёл метку ActPointsStart, и правильно сделал: о собаках-то я и забыл, @@ActPointsStart. Затем не нашёл ProcessFrame::AckNoCheck, и снова был прав, эта метка называлась AcqNoCheck, и была без "собак" (а значит никаких ProcessFrame::). Та же фигня с TaskPending.

И, наконец, оно откомпилилось, выдав ещё 2 Hazard'a (и автоматически их исправив добавлением NOP). Как результат получается 248 слов кода (496 байт) и 297 слов данных (594 байта) Понятно, 64 байта сэкономили в оперативке (за счёт 32 элементов списка обнаруженных пятен, где по слову откусили), но вот код усложнился...

Но есть шанс, что он по крайней мере будет работать :)
QuatCore

Вот же бандура!

Весь код ProcessFrame.asm целиком:

(если у кого не отображается форматирование, по идее должно отобразиться если из ленты зайти конкретно на эту запись)

;вариант без инструкции AUP (Acquire UPdate)		
;более сложен из-за этого: мы должны запоминать, есть ли за нами "должок" по заданиям на обработку
		
ProcessFrame proc			
				j		1	;отныне и во веки веков!	
				;ждём кадрового синхроимпульса
				ACQ		VSync		;"застревает" до тех пор, пока кадровый импульс не придёт
				;пропускаем "пустые строки" вверху изображения (нужно только для аналоговой камеры, в цифровой такой фигни нет вроде бы...)
				k		TopRows		;TopRows должны быть определены в основном файле												
	@@topRows:		ACQ		HSync
				kLOOP		@@topRows
				;теперь k=0, пусть так и остаётся, наши [X+k], [Y+k], [Z+k] будут означать [X],[Y],[Z], очень надо для списков
				
				;теперь начинаются информативные строки изображения, сейчас их 720, позже может стать 1024. 
				[SP+2j]		0		;в [SP+2] храним номер строки.
				
				JMP		@@FinalRange	;чтобы СНАЧАЛА отправить запрос, а потом уже смотреть результат, так почему-то удобнее					
				;можем заменить на JNO, переполнения здесь случаться не должно

; -------------------------------------------- через эту линию код не идёт (только перепрыгивает) ------------------------------------

				;НАЧАЛО ЦИКЛА ПО СТРОКАМ
	@@newRow:		[SP+2j]		Acc
				X		ActivePoints	;начинаем цикл по всем активным точкам
				
	@@ResetPending:		C		0		;уж явно не 0x8000			
	;даже если список пуст, первую половину цикла мы пройдём, она касается отрезка, предшествующего точке из списка
	@@ActPointsStart:	[SP+2j+1]	GPUL		;яркость самой яркой точки на отрезке
				[SP+1]		GPUH		;соотв. координата
				Acc		Threshold	;поставили задом наперёд, чтобы избежать Hazard'а	
				SUB		[SP+2j+1]	;вычитаем порог, положит. значение свидетельствует о новом найденном "пятне"						
				[SP]		@@MidCycle
				JL		TaskPending	;пятна нет, не торопимся заказывать отрезок
				;пятно есть. Надо проверить: не сливается ли оно с пятном слева или с пятном справа
				;X сейчас указывает на точку, расположенную слева (возможно, фиктивную, нам не важно)
				;коорд. текущей точки лежит в Acc, её яркость в C
				
				;первым делом, проверяем, не сливается ли с пятном слева
				Acc 		[SP+1]
				SUB		[X+2j+k]
				DIV2S		D1
				DIV2S		[X+1]
				Z		[X+k]		;указатель на пятно "справа"
				JL		@@LeftMerge	;сейчас процедура слияния заметно усложнилась, приходится 2 отдельные ветви
				
				;если дошли досюда, значит с пятном слева не сливаемся, надо проверить теперь на пятно справа						
				Acc		[Z+2j+k]
				SUB		[SP+1]
				DIV2S		D1
				DIV2S		[Z+1]
				JL		@@RightMerge
				
				;если попали сюда, значит слияния не было, и точку всё-таки надо добавить!
				;но сначала проверим, нет ли у нас "долга" за предыдущую точку?
				[SP]		@@NewBlob
						
	;по сути, процедура, хотя вызываем мы её, укладывая адрес возврата в [SP], БЕЗ ИНКРЕМЕНТА
	;в регистре С лежит некий флаг, подсказывающий, нужно ли нам выдавать задания на обработку отрезков,
	;которые мы не выдали сразу в страхе, что произойдёт слияние пятен, и надо будет дать несколько другое задание,
	;а команды AUP у нас нет
	;Регистр X указывает на то самое пятно, по которому надо выдать задания
	;самый быстрый способ ветвиться по содержанию C - это заносить туда либо -32768, либо любое другое значение
	;брать ABS и смотреть на переполнение
	;пусть -32768 будет означать "выполняй!"
	TaskPending proc
		ABS		C
		JNO		[SP]		;вот в чём прелесть стека без инкремента!
		;если дошли до сюда, заказываем отрезки на обработку
AcqNoCheck:	Acc		[X+2j+k]
		DIV2S		[X+1]	;теперь в аккмуляторе у нас X - Ceil(D/2)
		ACQ		Acc		;первый отрезок
		ADD		[X+1]	;а вот теперь X + Floor(D/2)
		ACQ		Acc		;второй отрезок
		JMP		[SP]
	TaskPending endp	
					
; ------------------------------ Через эту линию код не проходит (только перепрыгивает) -----------------------------------------------------------------

											
				;[SP+2j+1]=[SP+3] указывает яркость этой точки, [SP+2j]=[SP+2]: Y-координату, [SP+1]: X-координата
				;а куда вставить, указывает рег. X
				;в Acc содержится модуль разности между правой точкой и текущей, за вычетом двух радиусов
	@@NewBlob:		Y		Heap									
				[SP]		@@AfterListOp
				NOP		0		;на следующей строке ListOp нам нужен и Y уже готовый, и доступ к памяти, так что Hazard по-любому будет какой-то...
				
				;добавит в X новую, пока что пустую точку.
				
						
;взять первый элемент из списка Y и перецепить его в начало списка X
;значения X,Y остаются неизменными,
;при вызове должно быть k=0, затираем регистр Z, Acc и флаг знака
;адрес возврата хранится в стеке, как положено
	ListOp proc			
			Z		[Y+k]
			ABS		[Y+k]
MergeBlobCase:		[Y+k]		[Z+k]
			JO		OutOfMemory									
			[Z+k]		[X+k]
			[X+k]		Z
			NOP		0		;избежать Memory hazard (одновременный доступ на чтение и запись)
			JMP		[SP]
	ListOp endp						

; -------------------------------- Через эту линию код не идёт (только перепрыгивает) ------------------------------------------------------------------------
													
	;обнаруженная точка примыкает к пятну слева от себя. Возможно, потребуется слияние и с пятном справа от себя
	;X указывает на пятно слева, Z - на пятно справа 
	@@LeftMerge:	Acc		[Z+2j+k]	;взяли X-координату правого пятна
			SUB		[X+2j+k]	;вычитаем X-координату левого
			DIV2S		[Z+1]		;вычитаем радиус правого пятна
			DIV2S		[X+1]		;и радиус левого пятна
			SUB		D1		;и минимально допустимый диаметр пятна
			JL		@@MergeBlobs	;делаем слияние ПЯТЕН
			;если дошли до этого места, значит пятна сливать не нужно, достаточно лишь уточнить параметры левого пятна
			;увы, аккумулятор совсем другим набит, нужно "с нуля" начинать
			Acc		[SP+1]	;Acc = x, т.е координата обнаруженной точки
			SUB		[X+2j+k]	;вычитаем X, т.е Acc = x - X - разность между обнаруженной точкой и центром левого пятна
			DIV2A		[X+1]		;прибавили D/2, получаем Acc = x-X+D/2 
			[X+1]		Acc		;обновили диаметр левого пятна.
			NOP		0
			;теперь подкорректировать X-координату
			Acc		[SP+1]	;берём координату обнаруженной точки
			DIV2S		[X+1]		;вычитаем половинку диаметра
			[X+2j+k]	Acc
			JMP		AcqNoCheck	;если слева от нас есть РЕАЛЬНОЕ пятно (фиктивное мы трогать не можем, его X-координата "-32768", условие заведомо не выполнится),
			;значит проверять "есть ли за нами должок" не нужно - точно есть!						
						
; ---------------------------------- Через эту линию код не идёт (только перепрыгивает) ---------------------------------------------------------------------------
	@@MergeBlobs:	Acc		[X+1]
			ADD		[Z+1]
			[X+1]		Acc	;диаметр равен сумме диаметров
			ZAcc		RoundZero
			DIV2A		[X+2j+k]
			DIV2A		[Z+2j+k]
			[X+2j+k]	Acc	;среднее арифметическое от центров точек
			;теперь удаляем правое пятно
			Y		X
			X		Heap
			[SP]		@@EndOfCycle
			JMP		MergeBlobCase
						
; ---------------------------------- Через эту линию код не идёт (только перепрыгивает) ----------------------------------------------------------------------------												
						
	@@RightMerge:	ADD		[Z+1]	;в аккумуляторе лежало x-X-D/2-D1/2. Теперь прибавили D, получили x-X+D/2-D1/2
			DIV2A		D1	;прибавили D1/2, получили x-X+D/2 - новый диаметр пятна
			[Z+1]		Acc	;ага, записали.
			;теперь подкорректировать X-координату
			DIV2		Acc
			ADD		[SP+1]
			[Z+2j+k]	Acc
	;а теперь с чистой совестью отправляем задание на обработку точки
			JMP		TaskPending

; --------------------------------- Через эту линию код не идёт (только перепрыгивает) ---------------------------------------------------------------------------

			;если добрались досюда, значит памяти хватило. X содержит адрес указателя на новую точку, а вот Z содержит адрес самой точки!
			;наполним её содержимым!						
			;на первую строку всегда попадаем в результате прыжка!
	@@AfterListOp:	[Z+2j+k]	[SP+1]	;X-координата точки
			X		Z		;для дальнейшей работы (после MidCycle)... Чтобы мы не взялись за только что добавленную точку!
			[Z+2j+1]	[SP+2j]
			[Z+1]		D1		;гориз. размер точки (будет расти при слияниях, а поначалу D, т.к мы сюда включаем и )
			;и теперь ещё нужно выдать задания на обработку, СРАЗУ.
			[SP]		CALL(AcqNoCheck)

	;теперь надо посмотреть - а есть ли точка? Или мы уже прошли последний отрезок между точками?
	;но у нас в конце списка правая фиктивная точка - её надо игнорировать!			
	;улучшенный вариант:
			%CheckHazards OFF
	@@MidCycle:	X		[X+k]
			Y		X
			%CheckHazards ON
			ABS		[X+k]
			JO		@@FinalRange
									
	;запрашиваем отрезок, где ожидаем точку. Первой придёт яркость, как всегда
	@@EndOfCycle:	[SP+2j+1]	GPUL			;яркость
			[SP+1]	GPUH			;пока не забыли - сразу второе слово запрашиваем (коорд точки), чтобы не сбить весь FIFO 
			C		Nil			;по умолчанию ставим метку "за нами должок!"				
			;входит ли этот отрезок в уже найденное пятно?
			Acc		[SP+1]		;координата точки (из списка)
			DIV2S		[X+1]			;прибавить половинку её диаметра
			SUB		[X+2j+1]		;вычесть текущий номер строки
			JL		@@ActPointsStart
			;возможно, мы ещё не очень далеко ушли от пятна, и как оказывается, пятно здесь продолжается
			SUB		D1
			JL		@@RemoveBlob	;уже далеко, пора его удалять!
			;да, мы ещё близко. Но превышает ли яркость порог?
			Acc		Threshold		;чем выше значение - тем ЧЕРНЕЕ
			SUB		[SP+2j+1]		;поэтому если получится значение меньше нуля - значит точка тусклая, расширять пятно не стоит
			JL		@@RemoveBlob
			;раз оба раза перехода не было, значит пора пятно сдвигать немного вниз!
			;просто прибавим единицу и к диаметру, и к Y-координате
			Acc		[X+2j+1]
			ADD		1
			[X+2j+1]	Acc
			NOP		0
			Acc		[X+1]
			ADD		1
			[X+1]		Acc
			JMP		@@ActPointsStart
				
; ------------------------------------------ Через эту линию код не идёт (только перепрыгивает) ------------------------------------------------------------------
										
			;пора эту точку перетаскивать в другой список... 
	@@RemoveBlob:	X		AllPoints
			[SP]		CALL(ListOp)
			X		Y			;теперь [X] ссылается уже не на обработанную и удалённую точку, а на следующую, так что всё верно
			JMP		@@ResetPending	;ура...					
			;до PixelsProcessed проскрипели, теперь последний участок запросить на обработку, и синхроимпульс для кучи
				
; ------------------------------------------ Через эту линию код не идёт (только перепрыгивает) ------------------------------------------------------------------												
				
	@@FinalRange:	ACQ		WholeRow
			ACQ		HSync
			Acc		[SP+2j]
			SUB		ImgHeight
			ADD		ImgHeightP1
				
		;на @@NewRow мы переопределим и Y, и X, и [SP], так что можно...
			;это приготовления уже к transfer, но чтобы избежать хазарда, пришлось сюда перенести. Вообще, это нам сжирает лишних 3600 тактов = 144 мкс. 
			Y		ActivePoints	;это заведомо фиктивная, и указывает либо на другую фиктивную, то ли на реальную
			X		AllPoints
			Y		[Y+k]			;это уже может быть реальная точка, а может быть правой фиктивной
			[SP]		@@Transfer																				
				
			JL		@@newRow
			;усё, отстрелялись...
			


	@@Transfer:	ABS		[Y+k]
			JNO		ListOp		;если [Y+k]=NULL, значит пора закругляться											
ProcessFrame endp


Ещё чуть-чуть это дело причесал, уменьшив размер с 248 до 245 слов кода после компиляции. Конретно этот файл (алгоритм обнаружения точек) компилируется в 124 слова данных, если нигде не обсчитался (оно не компилируется "само по себе" из-за огромного количества констант заданных "выше", а при общей компиляции я пока не получаю "разбивки по файлам", хотя можно было бы сделать...)
Collapse )

Самое время делать ставки, сколько времени уйдёт на отладку этого добра?

Poll #2107906 Алгоритм обнаружения ярких пятен на изображении

Сколько времени займёт его отладка?

неделя или меньше
3(33.3%)
1-2 недели
1(11.1%)
свыше 2 недель
5(55.6%)
он так и не заработает
0(0.0%)


UPD. Синтезируется нормально, срабатывает и fitter, и по таймингам проходим - уже радостно.
QuatCore

Прыжок веры

Взять - и прошить сразу "в железо" - вдруг прям возьмёт и зафурычит?

Но ещё нужно отдать "технический долг" - сделать наконец-то, чтобы можно было и дамп оперативной памяти запросить (внутренней памяти, каковой сейчас 512 слов, или 1024 байта), и картинку передать (она сидит во внешней, статической памяти, объёмом 1 мегабайт), потому как без этого сложно будет хоть о чём-то говорить...

Вот существующий код:
;рано или поздно мы попадаем сюда...				
;endless:		JMP	endless	
DebugLoop:		SIO	UART
			ERL	0
			ERH	0	;заблаговременно, чтобы не было IO Hazard	
			NOP	IN
;чуть позже будем определять, какой символ нам дали, а пока тупо отправим картинку
				
			;самое смешное: у нас в 16-битный регистр не влезет то число, до которого делать итерации!
			;либо применить FMA, либо тупо 2 вложенных цикла
			; j	29
	; @@OuterLoop:	Acc	24575		;24576 * 30 = 1024 * 720 
	; @@InnerLoop:	OUT	[ER++]
			; SUB	1
			; JGE	@@InnerLoop
			; jLOOP	@@OuterLoop
				
			;а теперь давайте дамп передадим
			SP	0x400	;кривовато, мы привязываемся к разрядности адреса, но для сельской местности сойдёт
			Acc	1023
@@Loop:			OUT	[SP++]
			SUB	1
			JGE	@@Loop
				
			JMP	DebugLoop


Как видно, код для передачи изображения попросту "закомментирован", а код для передачи дампа памяти дописан.

Не хватает только разбора информации, приходящей по UART, пусть даже простейшего...

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

А попробуем-ка мы картинку запросить:


Не знаю, что это такое.

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