nabbla (nabbla1) wrote,
nabbla
nabbla1

Categories:

QuatCore: упрощаем адресацию

Вот ведь бывают команды - "любимчики", без которых элементарно можно обойтись, но уже вдолбилось в голову, что они должны быть - и никак не выкинешь!

У меня так произошло с адресацией i^k (взять XOR, искл. ИЛИ между регистрами i, k). Именно с неё началась сколько-нибудь реальная разработка QuatCore. Потом пошла тоже "привычная" адресация 2i+k и 2j+k, и как-то так оказалось, что нужно ещё и реализовать i^j, как меньшее из зол, потому что у нас так повелось, что регистр i наиболее универсальный - может входить в выражение как одинарный, так и удвоенный, а вот регистр j всегда удваивается (либо вообще учетверяется), а регистр k - всегда одинарный. И если остаться с этими правилами и адресацией i^k, то "красиво" умножить матрицу 2х2 на матрицу поворота, которая "на лету" строится из значений co, si (синус и косинус угла крена) не выходит ну никак!

В итоге родился компромисс: для регистра Y мы используем одно выражение, для регистров X,Z - другое:


А вот что можно распрощаться с i^k - как-то и в голову не пришло...


И не пришло бы, не клюнь жареный петух: сообразил вдруг, что мне же тогда и сигнал PM (plus-minus) для команды АЛУ FMPM (Fused Multiply-Plus-Minus) тоже придётся формировать иногда по значениям i,k, а иногда по значениям i,j. И вроде бы и можно, но как-то совсем неаккуратненько получается! Сейчас этот сигнал формируется в модуле QuatCorePC (program counter), поскольку именно там сидят индексные регистры и регистр Inv. Но даже сейчас приходится брать один бит из DestAddr, чтобы определить, какая команда АЛУ сейчас исполняется - "длинная" (например, FMPM) или "короткая" (например, PM). А если теперь придётся ещё и декодировать SrcAddr, чтобы понять, какой из видов адресации мы сейчас используем, это выйдет совсем через задницу.

Сначала решил ещё разок "помытариться" с умножением матриц 2х2:


Умножение можно описать 4 выражениями:





Это исполняется с помощью трёх вложенных циклов, по i, j, k:


Скажем, мы ищем элемент Bjk = B[2j+k], для чего устраиваем внутренний цикл по i от 0 до 1. Обзовём co = R[0], si = R[1]. Тогда при конкретных i,j,k, мы должны взять R[i^j] и помножить на Aik=A[2i+k].

Вот и приходим к тому, с чего начали: тот из индексов (i,j,k), который не может умножаться на 2, НЕ ДОЛЖЕН входить в выражение для R[]. У нас это индекс k, поэтому мы и были вынуждены взять выражение i^j.

А вот с умножением кватернионов картина ГОРАЗДО ПРОЩЕ: там всего 2 вложенных цикла! Выражение выглядит так:



И "традиционно" (этой традиции около полгода) я обозначал номер элемента, который мы в данный момент считаем, i, а внутренний цикл шёл по k. Отсюда индексация NU[i], MU[k], Lambda[i^k]. Да, здесь оба индекса должны становиться "одинарными", чего по нашей таблице адресации происходить не может.

Незадача, однако. Более-менее стройная таблица индексаций рушится напрочь.

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

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

Поэтому мы всё-таки можем применить вариант адресации i^j. Получается MU[i], Lambda[i^j], а вместо несуществующей NU[j] получается [--SP] для предварительно сдвинутого куда надо указателя стека. Может, чуть эффективнее будет в кои-то веки пройтись по j не "вниз" (от 3 до 0), а "вверх", с помощью j++. А для организации цикла "параллельно" вести счёт по k "вниз", потому что иначе это замучаешься - сначала перегонять j в аккумулятор, потом вычитать 3, и затем по признаку отрицательного числа оканчивать итерации.

Но и это всё блажь. На самом деле, мы ТУПО ДОБАВЛЯЕМ ОДНУ СТРОКУ в программный код:
k    j

и обращаемся как [SP+k].
Всё. Причём эта строка нужна во внешнем цикле, т.е добавляется целых 4 такта к общему времени выполнения. И потеряли на всё про всё 2 байта.

Нам нужно подправить код "мультиплексера для второго индекса" QuatCoreMemSecondIndexMux. Таким он был:
module QuatCoreMemSecondIndexMux (input [15:0] ijk, input [1:0] Base, input [1:0] adr, output [4:0] Q);

wire [4:0] i = ijk[9:5];
wire [4:0] j = ijk[4:0];
wire [4:0] k = ijk[14:10];

assign Q = 	(adr == 2'b00)? 5'b00001 :
		(adr == 2'b01)? i :
		(adr == 2'b10)? k :
		(~Base[0])?	i^k:
		Base[1]?	5'b00000 :
				i^j;
endmodule

и занимал 20 ЛЭ.

А таким становится:
module QuatCoreMemSecondIndexMux (input [15:0] ijk, input [1:0] Base, input [1:0] adr, output [4:0] Q);

wire [4:0] i = ijk[9:5];
wire [4:0] j = ijk[4:0];
wire [4:0] k = ijk[14:10];

assign Q = 	(adr == 2'b00)? 	5'b00001 :
		(adr == 2'b01)? 	i :
		(adr == 2'b10)? 	k :
		(Base[0]&Base[1])?	5'b00000 :
					i^j;
endmodule

и занимает 12 ЛЭ.

Весь QuatCore, при ширине адреса кода в 7 бит, занимал 453 ЛЭ, а теперь - 445 ЛЭ. Но надо понимать, что исходный вариант был почти бесполезен, т.к при адресации i^j он по-прежнему задавал знак для FMPM, опираясь на i, k, и исправление этого рушило бы нашу логику даже сильнее, и добавило бы ещё десяток ЛЭ.


Завтра испытаем это безобразие на процедуре нахождения крена и его кватерниона...

Жаль только, что процедура умножения кватернионов, с которой всё и началось, с каждым разом становится всё длиннее и длиннее.
[Spoiler (click to open)]
Помните, первая была такой:
Procedure QuatMultiply
        Acc 0
loop:   B [Y+i^j]
        FMPM [X+i++]
        JiNZ loop
        [SP+j++] Acc
        JjNZ QuatMultiply
output: [Z+j] [SP+j++]
        JjNZ output
        JMP [--SP]
End procedure

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

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

Здесь уже 21 слово, или 42 байта. Ну да ладно.
Tags: ПЛИС, математика, программки, работа, странные девайсы
Subscribe

Recent Posts from This Journal

  • Ремонт лыжных мостиков

    Вернулся с сегодняшнего субботника. Очень продуктивно: отремонтировали все ТРИ мостика! Правда, для этого надо было разделиться, благо народу…

  • Гетто-байк

    В субботу во время Великой Октябрьской резни бензопилой умудрился петуха сломать в велосипеде. По счастью, уже на следующий день удалось купить…

  • А всё-таки есть польза от ковариаций

    Вчера опробовал "сценарий", когда варьируем дальность от 1 метра до 11 метров. Получилось, что грамотное усреднение - это взять с огромными весами…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 0 comments