nabbla (nabbla1) wrote,
nabbla
nabbla1

Categories:

Быстрый QuatCore: вычисление масштаба

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

Выиграть по числу команд не удалось, но по крайней мере удалось обойтись без NOPов и чуть-чуть ускориться, "развернув" один цикл на 2 итерации :)




Первый кусок - вычисление Манхэттенской метрики (метрика городских кварталов, сумма модулей разностей) и метрики Чебышева (максимум от модулей разностей). Так оно было:

				[SP]		0	;манхэттенская метрика, сумма модулей (метрика городских кварталов)
				[SP+1]	0	;чебышевская, максимум от модулей 
				;текущее значение будем хранить в регистре C - дешево и сердито!
				j		3					
				i		1
		@@loop:		Acc		[X+i^j]	;+Tyx (i=1), -Tyy (i=0)
				PM		[X+i]	;Txy (i=1), Txx (i=0)   ;поменяли местами чтобы проверить модуль
				ABS		Acc		;|Txy+Tyx| (i=1), |Txx-Tyy| (i=0)
				C		Acc		;сохранили это значение
				ADD		[SP]	
				[SP]		Acc	;обновили манхэттенскую
				Acc		[SP+1]
				SUB		C
				JGE		@@skipCheb ;лучше подошел бы JGE и поменять местами [SP+2] и [SP+1]. Разница при нуле-на 1 больше операций.
				[SP+1]	C	;обновили Чебышевскую	
		@@skipCheb:	Inv		1		;так что на следующей итерации будет "-"
				iLOOP		@@loop


Не очень удачно: мы объявляем i=1 и тут же используем это значение. По счастью, можно поменять местами операнды [X+i^j] и [X+i], т.к от их разности всё равно берётся модуль. А после этого можно поменять местами инициализацию i и j. А можно было и не мучаться, просто их перенести ещё до обнуления [SP] и [SP+1], тоже сойдёт.

Далее, у нас Hazard по одновременной записи и чтения из памяти, в строках

				[SP]		Acc	;обновили манхэттенскую
				Acc		[SP+1]


(каждый раз, как видим квадратные скобки "по главной диагонали" - жди беды!)

Если посмотреть, мы там узнаём, превышает ли только что посчитанный модуль разности текущую метрику Чебышева, и если да, надо её заменить. Текущая метрика как раз лежит в [SP+1], а только что посчитанный - в C. Мы загружали [SP+1], вычитали C, и если знак "+", заменять не надо. А можно всё наоборот: загрузить C, вычесть [SP+1], и если знак "-", то заменять не надо. Результат не изменится. Время выполнения увеличится на такт, если значения совпадали (тогда всё-таки будет замена на ТАКОЕ ЖЕ значение). Но это бывает редко, да и нет особого смысла оптимизировать СРЕДНЕЕ время выполнения, главное обеспечить НАИХУДШЕЕ время выполнения в рамках! (а вообще, мы могли бы хранить текущее значение метрики Чебышева в C, а текущий модуль убрать в [SP+1], но лениво).

Вот что получается по данному фрагменту:
				[SP]		0	;манхэттенская метрика, сумма модулей (метрика городских кварталов)
				[SP+1]	0	;чебышевская, максимум от модулей 
				;текущее значение будем хранить в регистре C - дешево и сердито!
				i		1
				j		3					
		@@loop:		Acc		[X+i]	;+Tyx (i=1), -Tyy (i=0)
				PM		[X+i^j]	;Txy (i=1), Txx (i=0)   ;поменяли местами чтобы проверить модуль
				ABS		Acc		;|Txy+Tyx| (i=1), |Txx-Tyy| (i=0)
				C		Acc		;сохранили это значение
				ADD		[SP]	
				[SP]		Acc	;обновили манхэттенскую
				Acc		C
				SUB		[SP+1]
				JL		@@skipCheb ;лучше подошел бы JGE и поменять местами C и [SP+1]. Разница при нуле-на 1 больше операций.
				[SP+1]	C	;обновили Чебышевскую	
		@@skipCheb:	Inv		1		;так что на следующей итерации будет "-"
				iLOOP		@@loop


Далее, мы находим масштаб, вот старый код:
	;кстати, у нас до сих пор j=3 :)
	;i=k=0
				Acc		[X+i]	;добавили Txx
				ADD		[X+i^j]	;и Tyy
	;теперь наши метрики берём с весами
				Z		MetricW
				i		1
		@@finExpr:	C		[SP+i]
				FMA		[Z+i]
				iLOOP		@@finExpr


Опять нам светит Hazard: объявили i=1 и сразу же используем. Поменяв местами с инициализацией Z, можно это исправить. Но ещё лучше совсем избавиться от цикла:
	;кстати, у нас до сих пор j=3 :)
	;i=k=0
				Acc		[X+i]	;добавили Txx
				ADD		[X+i^j]	;и Tyy
	;теперь наши метрики берём с весами
				Z		MetricW		
				C		[SP]
				FMA		[Z+i]
				C		[SP+1]
				FMA		[Z+1]


Команд осталось столько же, зато меньше накладных расходов.

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

				JO		@@endLoop
		@@shl1:		ADD		Acc
				j++		0
				JNO		@@shl1		
	;ага, сделали
		@@endLoop:	[SP+1]		Acc			;финт ушами: заносим значение 32767, ибо нефиг! (другими способами его сложно раздобыть)
;				UDIV2S		UAC	
				DIV2		UAC ;получается отрицательное значение от -16384 до -1 (число -1 при "делении на 2" так и останется -1)
				SUB		[SP+1] ;вычитает 32767, что даст отрицательное значение от -49151 до -32766. Но в UAC отобразится от 16385 до 32768.
					;если теперь найти обратную величину, это должно получиться в диапазоне 32768..65532
					;а если мы вычтем ещё половинку, может выйти от 16384 до 32768, и диапазон 32768..65536.
					;так что ну её нахрен, эту половинку?
					;или можем уже на этапе Ньютона этот случай проверить.
				DIV2S		1 ;это для нужд округления. 					
				[SP]		UAC	


И он нас целиком устраивает. Если вдруг захочется побороться за объём кода, можно будет выкинуть округление - оно ещё потом нам даёт лишнюю головную боль (раз, два). Но преждевременно жертвовать точностью не будем, хочется исполнять кодекс и всегда делать хоть сколько-нибудь грамотное округление.

Попробуем откомпилировать.

Добавим ещё в самый конец "отладочную строку":
   OUT  j

чтобы посмотреть, какая у нас получилась экспонента.

И запускаем трансляцию.

Получаем вот такой лог (я не стал дальше выписывать статистику по командам и полный перечень меток):

Загружаем файл конфигурации транслятора
Файл конфигурации прочитан, готовы к работе
Обрабатываем файл OurProgramForFastCPU.asm
Пытаемся оптимизировать таблицу вызова процедур
AffineAlgorithm = 0004 
NormSiCo        = 008B 
ShiftOrigin     = 0083 
SwapPoints      = 007C 

Бит 7 адреса
не повторяет предыдущих (сигнатура 6)
Сопоставление его биту SrcAddr[3] прошло успешно
Бит 6 адреса
не повторяет предыдущих (сигнатура 1)
Сопоставление его биту SrcAddr[2] прошло успешно
Бит 5 адреса
в точности повторяет бит 6...
Бит 4 адреса
в точности повторяет бит 6...
Бит 3 адреса
не повторяет предыдущих (сигнатура 5)
Сопоставление его биту SrcAddr[1] прошло успешно
Бит 2 адреса
не повторяет предыдущих (сигнатура 9)
Сопоставление его биту SrcAddr[0] прошло успешно
Бит 1 адреса
в точности повторяет бит 7...
Бит 0 адреса
в точности повторяет бит 7...
Компиляция завершена успешно

Ширина адреса сегмента кода (и регистра PC):           8   
Ширина адреса сегмента данных (и регистров X,Y,Z,SP):  8   
Количество инициализированных слов данных:             189 
Количество инициализированных слов кода:               150 
Количество адресов процедур:                           4   

Адреса процедур:
AffineAlgorithm = 0004(поле Src = B1) 
NormSiCo        = 008B(поле Src = BA) 
ShiftOrigin     = 0083(поле Src = B8) 
SwapPoints      = 007C(поле Src = B7) 


Мы ещё выкинули "вывод значений на экран" в процедуре main: данный код выдаёт значение в шину данных прямо на последней строке. Так что все адреса процедур "съехали" - адрес AffineAlgorithm "вверх", а всех остальных - "вниз", т.к добавился новый довольно большой фрагмент кода. Но модуль QuatCoreCallTable опять сформировался без логических элементов! Похоже, что по крайней мере для 4 процедур с 8-битными адресами это скорее правило, а не исключение. Может, можно доказать, что это всегда выполнимо, не знаю. Будем наблюдать :)

И ни одного Hazard'а не нашёл. Точнее, пока я не вставил "отладочную" OUT j, он находил Hazard между
[SP]   UAC


и

JMP   [--SP]


что не так уж важно, поскольку между ними ещё ожидается код нахождения вектора параллельного переноса.

Запускаем синтез схемы - и опять Can't find fit! Как же он меня задолбал! Попробовал поменять SEED в настройках фиттера, с единицы на двойку - то же самое. Включил опять те "агрессивные настройки" - и моментально оно синтезировалось, но не влезло в тайминги, 24,63 МГц предельная частота.

Наконец, изменил только одну настройку fitter, Aggressive routability optimizations: always, а вот auto packed registers: off, и Auto Global memory control signals: off (как по умолчанию) - вот тогда при синтезе он ОЧЕНЬ СЕРЬЁЗНО задумался, но всё же завершил работу и дал предельную частоту 26,18 МГц.

Так что да, можно поиграться, если самую чуточку не хватает.

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

Результат видим на "картинке для привлечения внимания". Совпадает с тем, что получали ранее, что не может не радовать, значит, всё работает, включая байпас.

Чтобы дойти до этого места, ушло 199,4 мкс, тогда как ранее уходило 1,105 мс. Стало в 5,54 раза быстрее - наращиваем преимущество :)


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

  • Нахождение двух самых отдалённых точек

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

  • Слишком общительный счётчик

    Вчера я чуть поторопился отсинтезировать проект,параметры не поменял: RomWidth = 8 вместо 7, RamWidth = 9 вместо 8, и ещё EnableByteAccess=1, чтобы…

  • Балансируем конвейер QuatCore

    В пятницу у нас всё замечательно сработало на симуляции, первые 16 миллисекунд полёт нормальный. А вот прошить весь проект на ПЛИС и попробовать "в…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 0 comments