nabbla (nabbla1) wrote,
nabbla
nabbla1

Новая команда QuatCore: "S"

Нечасто у нас команды новые появлялись, особенно в АЛУ, а не в периферии.

"Схема" АЛУ на данный момент:



Это команда со стороны SrcAddr, т.е один из адресов, принадлежащих АЛУ, с которого мы можем получить какие-то данные. Когда-то у нас там было "зарезервировано" 32 команды, от 0x80 до 0x9F. Потом решили половину "откусить" на команду IN, поэтому для АЛУ осталось 16 команд, 0x80..0x8F. Затем и ещё 8 "откусили" для видеообработчика, там прописались GPUL, GPUH, GPUPL, GPUPH (GPU - Luminance/Low, Horizontal/High, Product Low, Product High), а для АЛУ осталось 0x80..0x87.

Но в действительности команд там было всего 3:
- Acc (адреса 0x80, 0x81, 0x84, 0x85, т.е 1000_0x0x) - выдать значение аккумулятора, с насыщением результата. Аккумулятор имеет один дополнительный старший бит, поэтому в действительности может представлять значения от -65536 до 65535, но при использовании команды Acc всё что выше 32767, "обрежется" до 32767, и всё что ниже -32768 - до -32768. Очень удобно для операций с кватернионами, позволяет задействовать все 16 бит.
- UAC (Unsigned Acc, адреса 0x82, 0x86, т.е 1000_0x10) - выдать значение аккумулятора "как есть", игнорируя самый старший бит. Обычно используется для беззнаковых операций, где вполне законно получить значение выше 32767, и не нужно его обрезать!
- С (адреса 0x83, 0x87, т.е 1000_0x11) - прочитать соответствующий регистр.

Вот теперь будет и четвёртый, S - выдать флаг знака.

Как видно, команды SrcAddr и DestAddr практически изолированы друг от друга. Если шина DestAddr[7:0] подключена к модулю QuatCorePipelineALUcontrol (самая сложная часть, на мой взгляд, реализующая 31 команду, не прибегая к микрокоду), то SrcAddr[7:0] - к модулю QuatCorePipelineALUoutput. Он гораздо проще, приведём его код:
module QuatCorePipelineALUoutput 
(input clk, input [7:0] SrcAddr, input [16:0] Acc, input [15:0] C, input SrcDiscard, input SrcStall, input busy, 
output [15:0] Q, output isOFLO, output DestStallReq, output reg BypassEn = 1'b0);

assign isOFLO = (Acc[16] != Acc[15]);

wire [1:0] mode;
//00: Q = -32768
//01: Q = 32767
//10: Q = Acc[15:0]
//11: Q = C

assign mode[1] = SrcAddr[1] | ~isOFLO;
assign mode[0] = SrcAddr[1] & SrcAddr[0] | (~SrcAddr[1]) & Acc[15] & (~Acc[16]); 

assign Q = (mode == 2'b00)? 16'h8000 :
           (mode == 2'b01)? 16'h7FFF :
           (mode == 2'b10)? Acc [15:0]:
							C;

wire isOurAddr = (SrcAddr[7:3] == 5'b1000_0) & (~SrcDiscard) & (~SrcStall);
assign DestStallReq = isOurAddr & busy;

always @(posedge clk)
	BypassEn <= isOurAddr & (SrcAddr[1:0] != 2'b11);

endmodule


Да, убеждаемся, что адрес действительно "наш", т.е лежащий в диапазоне 0x80..0x87. Убедиться в этом нужно для формирования DestStallReq (остановиться и подождать, пока АЛУ завершит работу - и только после этого выдать запрошенный результат) и BypassEn (когда на вход АЛУ, как оказывается, мы подаём его же собственный выход, то вместе с 16 битами, идущими "штатно" через шину данных, мы соединяем и младшие биты, чтобы поднять точность. Данный механизм необходим, чтобы не допускать ошибок при взятии обратной величины и, в дальнейшем, при разложении Холецкого).

На выход, как видно, подаётся либо значение с аккумулятора, либо с регистра C, либо значения -32768 и +32767.

А регистр знака (он же флаг знака) сюда вообще никоим образом не подключён, он сидит сам по себе и подсоединён "в обход" шины данных напрямую в модуль QuatCorePC (Program Counter), где участвует в работе условных переходов, JL и JGE.

Я надеялся, что на каждый выходной бит по сути придётся 1 ЛЭ. Его входы будут: Acc, C, mode[0], mode[1], как раз влезает. Итого, 16 ЛЭ на выход Q. Далее, нужно по 1 ЛЭ на mode[0] и mode[1], ещё по одному на isOFLO, 2 на isOurAddr (зависит от 7 входов), и ещё по 1 на DestStallReq и BypassEn. Итого: 23 ЛЭ.

Если этот модуль синтезировать "в одиночку", он сожрёт 39 ЛЭ, что меня очень печалило. Но сейчас решил посмотреть, сколько он занимает "в составе всего проекта":


24 ЛЭ, очень недурственно!

Видимо, пока модуль с комбинаторной логикой синтезируется "сам по себе", ставится задача достичь минимальной задержки, и "общая логика" вставляется индивидуально в каждый выходной бит, если это приводит к ускорению. А когда становится ясно, что данный модуль впихнут между двумя регистрами, и "времени вагон" - уже расслабляется и оптимизирует по числу ЛЭ.

Хотя бы для 14 выходных бит хочется сохранить данную структуру, благо, это требует лишь переписать mode[0] и mode[1]: они должны будут выдать вариант "-32768" = 0x8000 не только в случае переполнения при запросе Acc, но и при запросе "S".

Мне показалось самым логичным приписать новой команде адрес 0x81, чтобы все 4 команды компактно "занимали" всего 2 бита на SrcAddr, в данном случае это упрощает их декодирование - тупо нужно меньше входов!

Написал такой вот длиннющий код, не самый красивый, но тут в явном виде объясняется, по какой команде что подавать:

//два младших бита SrcAddr[]:
//00 - Acc (с насыщением)
//01 - S (флаг знака)
//10 - UAC ("как есть")
//11 - C (выдать регистр C)

//наши адреса сейчас: 0x80 .. 0x87
//(1000_0000 .. 1000_0111, или 1000_0xxx)
module QuatCorePipelineALUoutput (
input clk, input [7:0] SrcAddr, input [16:0] Acc, input [15:0] C, input SrcDiscard, input SrcStall, input busy, input S,
output [15:0] Q, output isOFLO, output DestStallReq, output reg BypassEn = 1'b0);

assign isOFLO = (Acc[16] != Acc[15]);

wire [1:0] mode;
//00: Q = -32768
//01: Q = 32767
//10: Q = Acc[15:0]
//11: Q = C

//assign mode[1] = (SrcAddr[1] | ~isOFLO) & SrcAddr[0];

assign mode[1] = 	(SrcAddr[1:0] == 2'b00)? ~isOFLO:	//команда "Acc"
			(SrcAddr[1:0] == 2'b01)? 1'b0:		//команда "S" 
						 1'b1;		//команды "UAC" и "C"

assign mode[0] = 	(SrcAddr[1:0] == 2'b00)? Acc[15] & ~Acc[16]:	//1 только при переполнении с положительным знаком
			(SrcAddr[1:0] == 2'b01)? 1'b0:	//используем mode[1:0] для 14 разрядов, т.е кроме самого старшего и самого младшего. Для них это равносильно 0.
			(SrcAddr[1:0] == 2'b10)? 1'b0:	//при команде "UAC" коммутируем Acc напрямую, без каких-либо условий
						 1'b1;	//команда "C", безусловно коммутируем C. 

//assign mode[0] = SrcAddr[1] & SrcAddr[0] | (~SrcAddr[1]) & Acc[15] & (~Acc[16]); 

//львиная доля
assign Q[14:1] = 	(mode == 2'b00)? 14'h0000 :
			(mode == 2'b01)? 14'h3FFF :
			(mode == 2'b10)? Acc [14:1]:
					 C[14:1];
									 
//старший бит
wire mode0msb = (SrcAddr[1:0] == 2'b00)? Acc[15] & ~Acc[16]:
		(SrcAddr[1:0] == 2'b01)? 1'b1:	//когда "S", нужен вариант "32767", чтобы старший разряд стал нулевым
		(SrcAddr[1:0] == 2'b10)? 1'b0:
					 1'b1;
										 
wire [1:0] MsbMode = {mode[1], mode0msb};
										 
assign Q[15] = 	(MsbMode == 2'b00)? 1'b1 :
		(MsbMode == 2'b01)? 1'b0 :
		(MsbMode == 2'b10)? Acc[15] :
				C[15];
									
//младший бит
wire mode0lsb = (SrcAddr[1:0] == 2'b00)? Acc[15] & ~Acc[16]:
		(SrcAddr[1:0] == 2'b01)? S:	//непосредственно флаг знака сюда засунется
		(SrcAddr[1:0] == 2'b10)? 1'b0:
					 1'b1;

wire [1:0] LsbMode = {mode[1], mode0lsb};

assign Q[0] = 	(MsbMode == 2'b00)? 1'b0 :
		(MsbMode == 2'b01)? 1'b1 :
		(MsbMode == 2'b10)? Acc[0] :
				    C[0];
									 									 

wire isOurAddr = (SrcAddr[7:3] == 5'b1000_0) & (~SrcDiscard) & (~SrcStall);
assign DestStallReq = isOurAddr & busy;

always @(posedge clk)
	BypassEn <= isOurAddr & (SrcAddr[1:0] != 2'b11);

endmodule
                        


И на схему добавляется один проводок, от выхода isNeg, т.е того самого флага знака:


При синтезе всего вместе, количество ЛЭ для QuatCorePipelineOutput не изменилось, так и осталось 24, что не может не радовать!

Далее, нужно дополнить файл конфигурации компилятора этой новой командой.

Вот оно, описание:
  object TQuatCoreCommand
    Key = 'S'
    Code = 129
    Mask = 251
    Description = 'Sign flag'
    Place = [cpSrc]
    Resources = []
    DataType = dtNumeric
  end


Запускаем компилятор - и получаем предупреждение:
Загружаем файл конфигурации транслятора
ПРЕДУПРЕЖДЕНИЕ: SrcAddr=81 занят одновременно командами Acc и S
ПРЕДУПРЕЖДЕНИЕ: SrcAddr=85 занят одновременно командами Acc и S
Файл конфигурации прочитан, готовы к работе


Всё верно, надо ещё маску Acc подправить, там было 250 = 0xFA = 1111_1010, т.е два бита на самом деле не участвовали в декодировании команды. Теперь самый младший участвует, так что нужно переправить на 251 = 0xFB = 1111_1011:
  object TQuatCoreCommand
    Key = 'Acc'
    Code = 128
    Mask = 251
    Description = 'Value of accumulator, saturated to 16 bits'
    Place = [cpSrc]
    Resources = [crAcc]
    DataType = dtNumeric
  end


Всё, предупреждения исчезли.

Попробуем откомпилить ту же самую программу, что и раньше:




Работает, выдаёт те же результаты, что и раньше. С "убитым" АЛУ такого быть не могло, высоки шансы, что мы ничего не поломали. И даже лишних ЛЭ не добавилось!

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

Recent Posts from This Journal

  • Тестируем atan1 на QuatCore

    Пора уже перебираться на "железо" потихоньку. Решил начать с самого первого алгоритма, поскольку он уже был написан на ассемблере. В программу внёс…

  • Формулы приведения, что б их... (и atan на ТРЁХ умножениях)

    Формулу арктангенса на 4 умножениях ещё немножко оптимизировал с помощью алгоритма Ремеза: Ошибка уменьшилась с 4,9 до 4,65 угловой секунды, и…

  • Алгоритм Ремеза в экселе

    Вот и до него руки дошли, причина станет ясна в следующем посте. Изучать чужие библиотеки было лениво (в том же BOOSTе сам чёрт ногу сломит), писать…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 0 comments