nabbla (nabbla1) wrote,
nabbla
nabbla1

Category:

QuatCore: очередная дурацкая идея (PC+Inv)

В сентябре мне шлея под хвост попала - захотелось во что бы то ни стало организовать "байтовый доступ" к памяти, где до того была лишь адресация по 16-битным словам. Мог пойти по "интеловскому" пути, где минимальной адресуемой единицей остаётся байт, и тут же начинаются "весёлости" с little-endian / big-endian (как именно интерпретировать последовательно идущие байты в памяти), сложнейшей системой "частичных регистров" (так-то вроде и нормально, не считая того что загружая байт в AL, процессор не трогал AH, загружая в AX, процессор не трогал старшие биты EAX, а вот загружая в EAX в 64-битном режиме, процессор ЗАНУЛЯЕТ старшие биты RAX! Но внутренняя реализация при наличии длиннющего конвейера и register renaming это что-то адское!), весёлым ассемблером и т.д.

Можно было просто "забить" на байтовый доступ, вместо этого в кои-то веки введя логические операции, чтобы из слова "вырезать" отдельные кусочки, а при необходимости "склеивать" всё назад. Сейчас-то у меня и не АЛУ по сути, а "АУ", с очень урезанной "логикой".

Но в итоге, разумеется чтобы ВЫПЕНДРИТЬСЯ, сделал разновидность Bit Banding, только Byte Banding (не путать с Bit Banging, это совсем другое и совершенно бесполезное в случае ПЛИС), когда чтение или запись в определённые области адресного пространства обеспечивают доступ к отдельным битам (в моём случае байтам), которые обычным методом отдельно не адресовались. И до сих пор доволен как слон! Вот уже сколько я ковыряюсь с тех пор - и не испытываю никаких неудобств в работе со байтовыми строками. Наоборот, получаю удовольствие, как можно одной и той же процедуре print дать адрес байтовой строки (для вывода текста по UART или на ЖК-экранчик), а можно ей же "подсунуть" строку из 16-битных слов, чтобы в дополнение к тексту посылать ЖК-экранчику команды (очистить экран, перейти на заданную позицию и пр). Как-то оно "уютно" получилось - вроде бы пишу ассемблерный код "как обычно", где-то ставлю db, а где-то dw, ставлю обычные метки в коде (безо всяких BYTE PTR []) - и оно тупо работает!

А сейчас вот мысль пришла - не объединить ли Program Counter (PC) и 1-битный Inv в ОДИН РЕГИСТР?


Давненько я с этим Inv не имел дела - данный регистр вводился для работы с комплексными числами, векторами и кватернионами, позволяя заменять комплексное число или кватернион на сопряжённое число или кватернион, не меняя кода, или поменять порядок векторов при векторном произведении (получая результат со знаком "минус"), да и в других местах, где чередуется "плюс" и "минус", этот регистр бывает очень кстати. На команды ADD, SUB, MUL, FMA (Fused Multiply-Add), FMS (Fused Multiply-Subtract) и другие он не влияет, а вот команда PM (Plus-Minus) осуществляет сложение, если Inv=0, и вычитание если Inv=1. Куда хитрее ведёт себя FMPM (Fused Multiply - Plus-Minus), которая выставляет у очередного слагаемого знак в зависимости от Inv, а также регистров i,j, чтобы получить "таблицу знаков кватернионов", частными случаями которой являются знаки для произведения комплексных чисел (берём i,j от 0 до 1) и векторного произведения (берём i,j от 1 до 3).

До сих пор у меня была команда Inv "на запись" (то есть DestAddr) и "на чтение" (SrcAddr), и это, по большому счёту, было всё, что можно было сделать с этим регистром, да больше ничего и не требовалось. Так, к примеру выглядит код поворота вектора с помощью кватерниона:

;Процедура поворота вектора с помощью кватерниона.
;Состояние регистров при вызове:
;X – адрес исходного вектора (вектор – это кватернион с произвольной скалярной частью)
;Y – адрес кватерниона
;Z – адрес результата
;i,j,k, Inv – любые.
;Адрес исходного и результирующего вектора может совпадать, вычисления будут корректными. 
;После вызова, значения регистров не изменятся, кроме Inv, который станет нулевым (мы могли бы и его сохранять, но скорее всего нет необходимости)

;использует 2 слова стека (адрес возврата + значение X) + вызывает QuatMultiply, так что в целом нужно 8 слов. 
RotateVecByQuat proc
			Inv 	1     		;чтобы было умножение на сопряжённый
			CALL 	QuatMultiply 	
;теперь по адресу Z лежит X * Conj(Y)
;осталось домножить его слева на Y
;для чего нужно поставить X=Y, Y=Z
;но X сохраним, чтобы потом восстановить
			[SP++]	X
			Inv	0   		;сопряжение больше не нужно  
			X	Y
			Y	Z
			CALL 	QuatMultiply
			;восстанавливаем Y
			Y	X
			;и наконец, X
			X	[--SP]
			JMP	[--SP]		;RET
RotateVecByQuat endp


И затем процедура перемножения кватернионов - вот так:

;Процедура перемножения кватернионов.
;Состояние регистров при вызове:
;X – адрес левого операнда
;Y – адрес правого операнда
;Z – адрес результата
;i,j,k - произвольное значение
;Inv=1, если правый операнд нужно взять сопряжённым, =0 в противном случае.
;На адреса операндов и результата не накладывается ограничений (допустимо Z=X или Z=Y, или X=Y, вычисления будут корректными). 
;Значения регистров после вызова не изменятся.

;эта процедура не вызывает других, но требует 8 слов на стеке (включая адрес возврата)
QuatMultiply proc
		[SP++]	C
		[SP++]	i 		;заносим в стек все индекс. регистры разом
		[SP++]	j
		[SP++]	k
		j 	3		;j-номер компонента, который вычисляем в данный момент
		i	3    		;k-номер слагаемого
@@row_loop:	ZAcc	RoundZero	;обнуляем аккумулятор
@@col_loop:	C	[X+i^j]		;загрузили левый операнд
		FMPM	[Y+i] 		;помножили на правый, с нужным знаком
		iLOOP	@@col_loop
		k	j
		[SP+k]	Acc 		;храним ответы на стеке
;потому что если X=Z или X=Y (обновляем кватернион),
;то занесение сразу в Z исказит последующие вычисл.
		i	3
		jLOOP	@@row_loop
@@out_loop:	[Z+i]	[SP+i] 	;помещаем ответ куда надо
		iLOOP	@@out_loop
		k	[--SP]
		j	[--SP]
		i	[--SP]		;восстанавливаем исх. значения регистров
		C	[--SP]
		JMP	[--SP]		;возврат из процедуры
QuatMultiply endp


Как видно, даже чтение из Inv нам ещё не пригодилось - только запись в него.

Что, если Inv будет являться старшим битом регистра PC?

На выборку команд это никак не повлияет, если ширина шины данных будет ровно столько, сколько нам надо - если программа умещается в 256 слов, будет память на эти самые 256 слов и шина в 8 бит. А девятый бит как бы "сам по себе".

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

И ещё нужно ввести два дополнительных бита во все наши "метки":
00 - бит Inv остаётся неизменным,
01 - бит Inv сбрасывается,
10 - бит Inv устанавливается,
11 - не использовано (хотя если вдруг пригодится, можно сделать инверсию бита, как в JK-триггерах)

По умолчанию повсюду будет стоять 00, и значит, выполнение программы не будет менять бит Inv. Тогда, если процедуру RotateVecByQuat мы каким-то синтаксисом объявим, как устанавливающую Inv=1, то удастся избавиться от первой строки (Inv 1), а если затем в конце QuatMultiply вместо

iLOOP	@@out_loop


будет

iLOOP	Inv0 | @@out_loop


(где Inv0 - константа, состоящая из тех самых битов 01, но сдвинутых на сколько надо влево)

то по завершении работы QuatMultiply всегда будет устанавливаться Inv=0 (но НЕ РАНЬШЕ), так что и строка "Inv 0" станет ненужной.

И таким способом, я уверен, можно поступить в каждом месте, где мы используем Inv, ведь мы ровно для того его и используем, чтобы повторно использовать одну строку, т.е какие-то прыжки определённо используются, и в этот момент можно заодно и настроить, что нам надо! (Если прыжков нет, то нет никакого смысла в Inv, будем использовать обычные ADD/SUB/FMA/FMS).

Конечно, экономия довольно-таки копеечная, имеющийся аффинный алгоритм сокращается аж на 4 слова, т.е 8 байт, при своём исходном размере в 199 слов (398 байт), правда это ещё вместе с "библиотекой работы с кватернионами". То есть, на 2%. Будет также небольшой выигрыш по быстродействию - не будут тратиться лишние такты. И даже чуть упростится выходной мультиплексор QuatCorePC и декодер его команд, поскольку отдельная команда Inv оттуда уйдёт.


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

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

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

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

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

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

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

  • Ковыряемся с сантехникой

    Наконец-то закрыл сколько-нибудь пристойно трубы, подводящие к смесителю, в квартире в Москве: А в воскресенье побывал на даче, там очередная…

  • Мартовское велосипедное

    Продолжаю кататься на работу и с работы на велосипеде, а также в РКК Энергию и на дачу. Хотя на две недели случился перерыв, очередная поломка,…

  • Обнаружение на новом GPU - первые 16 мс

    Закончилась симуляция. UFLO и OFLO ни разу не возникли, что не может не радовать. За это время мы дошли до строки 0x10F = 271. Поглядим дамп памяти:…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 2 comments