nabbla (nabbla1) wrote,
nabbla
nabbla1

Categories:

QuatCore: магические квадраты

Ещё нужно две вещи подправить, каждая из них - квадрат 4х4 с интересными значениями.

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


Со стеком мы как будто бы уже разобрались в модуле QuatCoreMem, там мы предложили такие 64 адреса ("команды"):


Как будто и для работы со стеком тут есть всё необходимое. Есть инкремент и декремент, причём один из них префиксный, второй постфиксный, именно так нам и надо для осуществления PUSH/POP операций. К примеру, чтобы занести элемент в стек, мы СНАЧАЛА прибавляем к адресу единицу, затем по этому адресу заносим значение. А чтобы вытащить его из стека, мы СНАЧАЛА считываем значение, а только потом вычитаем единичку.

Почему я остановился именно на [++SP] и [SP--] - потому что сейчас мы стараемся выдать значение из памяти В ТОТ ЖЕ ТАКТ, и поэтому реверсивный счётчик может нам дать лишь [SP++] и [SP--]. Поэтому на самом деле мы "жульничаем", а именно выдаём значение [1+(SP++)], и - о чудо, получается в точности то же самое, что и [++SP]. А прибавление единицы у нас и так было в загашнике.

Но сейчас, когда переделывал "бортовую программу" под новый набор инструкций, увидел, до чего же это неудобно! Ведь теперь по адресу [SP] уже хранится адрес возврата, и его затирать ни в коем случае нельзя! И "нахаляву" у нас оказывается всего одна локальная переменная, [SP+1]. Можно задействовать оба адреса, если в начале каждой процедуры подвинуть SP вперёд, тем же [++SP], но в конце надо обязательно вернуть назад, т.е опять дополнительные потери. Та же фигня с [SP+k], [SP+i] и пр. - там настолько удобно делать циклы до нуля, а тут мы опять упираемся в [SP], где лежит адрес возврата.

Ещё есть вариант всё-таки заставить стек расти "вниз", т.е по [SP--] мы заносим данные в стек, по [++SP] - извлекаем. Но тогда вообще все эти адреса ([SP], [SP+1], [SP+i] и пр.) будут указывать на чужие данные, пока мы не двинем SP на нужное количество вниз, "зарезервировав" себе место на стеке. Да, это совершенно нормальная операция в x86, ещё и с BP:
push BP
mov BP, SP
sub SP, 2
...

mov SP, BP
pop BP


но у нас такая простая операция sub SP, 2 (вычесть два из указателя стека) превращается в:
ACC SP
SUB 2
SP  ACC

И потом то же самое, когда прибавим назад.

Расточительно всё это - такими темпами можно и в килобайт не влезть :)

Забавная всё-таки архитектура. Как кватернионы помножить за 16 команд (32 байта)- это пожалуйста, вектор повернуть ещё за 10 - легко, попробуй это сделать на обычном процессоре, вот паскалевская процедура умножения кватернионов в целых числах занимает целых 734 байта в машинных кодах. Это правда, со всеми проверками на переполнение, но так у меня и в QuatCore проверка на переполнение есть - аппаратная. Называется "арифметика с насыщением" :) А на простые вещи иногда на удивление много кода идёт.

Поэтому хочется всё же использовать [SP++] для занесения в стек и [--SP] для извлечения из него - тогда всё получалось вполне элегантно, хоть и укуренно.

Если помните, эффективный адрес составляется из 3 компонент: базового регистра и ещё двух слагаемых. Эти два слагаемых мы постарались сделать максимально "ортогональными", то есть нет разницы, какой регистр выбран базовым, идёт ли чтение или запись, а слагаемое остаётся одним и тем же. До конца не получилось - из 4 вариантов, 3 всегда одинаковые для всех регистров, а четвёртый - "уникальный". Где-то это 4j, где-то Treug[j] = j(j+1)/2, а для стека - просто нули.

И сейчас хочется сохранить этот принцип, и при этом иметь адреса [SP], [SP+1], [SP++] и [--SP]. Последнее эквивалентно [-1+(SP--)].

Оказывается, это возможно, хотя немного необычным способом:



Необычность, что один из нулей получается сложением единицы и минус единицы!

Изменения в QuatCoreMem минимальны. Правим модуль QuatCoreFirstIndexMux:

module QuatCoreFirstIndexMux (input [15:0] ijk, input [1:0] Base, input [1:0] adr, 
			      output [7:0] Q);
								
wire [4:0] i = ijk[9:5];
wire [4:0] j = ijk[4:0];
wire [2:0] smj = j[2:0]; //for TreugNum
								
wire [3:0] TreugNum;
assign TreugNum[3] = smj[2];
assign TreugNum[2:0] = 	(smj == 3'd0)? 	3'd0 :
			(smj == 3'd1)? 	3'd1 :
			(smj == 3'd2)? 	3'd3 :
			(smj == 3'd3)? 	3'd6 :
			(smj == 3'd4)? 	3'd2 :
			(smj == 3'd5)? 	3'd7 :
										3'bxxx;
										
assign Q = 	(adr == 2'b00)? 8'b00000000 :		//0
		(adr == 2'b01)? {2'b0, i, 1'b0} : 	//2i
		(adr == 2'b10)? {2'b0, j, 1'b0} : 	//2j
		Base[0]?	{1'b0, j, 2'b0}		: 	//4j for X or Z
		Base[1]?	8'b11111111 :		//-1 for SP
				{4'b0, TreugNum};	//TreugNum for Y
endmodule


Заменили нолик на минус единичку, а чтобы она правильно работала, расширили выход с 7 до 8 бит. Затем следующему сумматору дали на единичку большую ширину - и вуаля!

Почему-то количество ЛЭ в итоге только уменьшилось, с 143 до 137. С 8-битными значениями ему всё-таки работать комфортнее, до этого у него не получалось правильно организовать переносы. Любопытная вещь, нужно будет иметь в виду.


Второй квадрат - это таблица знаков для входа АЛУ PM (Plus-Minus). Если команда "короткая" (скорее всего операция PM, "плюс-минус"), то мы просто присоединяем на выход регистр Inv. А вот если команда "длинная" (скорее всего FMPM - Fused Multiply- Plus-Minus), то при Inv=0 знак должен зависеть от регистров i,k следующим образом:



а если Inv=1, то три правых столбца должны поменять знак:



Пока что доверимся верилогу, не будем пытаться оптимизировать вручную:

module NewQuatSigns (input [4:0] ireg, input [4:0] kreg, input Inv, input isLongOp, output PM);

wire [1:0] i = ireg[1:0];
wire [1:0] k = kreg[1:0];
					                  
wire PM_norm = 	(i == 0)? (
				(k == 0)? 1'b0:
				(k == 1)? 1'b1:
				(k == 2)? 1'b1:
					  1'b1) :
		(i == 1)? (
				(k == 0)? 1'b0:
				(k == 1)? 1'b0:
				(k == 2)? 1'b1:
					  1'b0) :
		(i == 2)? (
				(k == 0)? 1'b0:
				(k == 1)? 1'b0:
				(k == 2)? 1'b0:
					  1'b1) :
			(
				(k == 0)? 1'b0:
				(k == 1)? 1'b1:
				(k == 2)? 1'b0:
				          1'b0);
wire PM_inv = 	(i == 0)? (
				(k == 0)? 1'b0:
				(k == 1)? 1'b0:
				(k == 2)? 1'b0:
					  1'b0) :
		(i == 1)? (
				(k == 0)? 1'b0:
				(k == 1)? 1'b1:
				(k == 2)? 1'b0:
					  1'b1) :
		(i == 2)? (
				(k == 0)? 1'b0:
				(k == 1)? 1'b1:
				(k == 2)? 1'b1:
					  1'b0) :
			(
				(k == 0)? 1'b0:
				(k == 1)? 1'b0:
				(k == 2)? 1'b1:
				          1'b1);					          
					          
assign PM = isLongOp? (Inv? PM_inv : PM_norm) : Inv;
					          					          
endmodule


Синтезируется в 7 ЛЭ, когда модуль "сам по себе", но когда он подсоединяется на своё место, добавка получается в 3-4 ЛЭ - нормально.

На этом схема QuatCore кажется законченной:



(выход PM из QuatCorePC соединился с входом PM в QuatCoreALU)

осталось всю эту хреновину отладить.

А потом ещё, когда оно худо-бедно зашевелится, начнётся эпопея по повышению тактовой частоты. Сейчас, на этом довольно медленном кристалле (аналог альтеровского speed grade "-3") этот процессор может работать не быстрее 5,5 МГц. В общем-то, этого хватает, но видеообработчик должен явно работать на бОльшей частоте, и тогда начнётся веселуха с clock domain crossing.
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