nabbla (nabbla1) wrote,
nabbla
nabbla1

Categories:

Байтовые строки в 16-битном QuatCore

Опять мне вожжа шлея под хвост попала - захотелось, чтобы по UART на "терминал" на компьютере пришли не просто двоичные данные, которые придётся в очередной раз разбирать байт за байтом, а в нормальном человеческом виде параметры обнаруженных "пятен". Для этого стал вводить текстовые строки ("Яркость:", "Диаметр:", "X:" и т.д.) - и оглянуться не успел, как память в 512 байт (256 слов по 16 бит) оказалась заполненной "под завязку", ведь до сих пор на кодирование одного символа мы тратили всё слово целиком, так гораздо проще с ними обращаться.

Заставить компилятор упаковать строки "покомпактнее", по 2 символа на каждое слово можно, но потом целая эпопея такие строки отобразить: наше АЛУ правильнее было бы назвать АУ - Арифметическое Устройство, ибо Логических операций в нём кот наплакал: ADD Acc (прибавить к аккумулятору "себя самого") худо-бедно сойдёт за SHL 1 (битовый сдвиг влево на 1 позицию), а DIV2 сойдёт за SAR 1 (АРИФМЕТИЧЕСКИЙ сдвиг вправо на 1 позицию). Как бы и всё - никаких тебе AND, OR, XOR, NOT (его хоть можно получить с помощью вычитания) или сдвигов на произвольное число позиций. Поэтому "распаковать" слово из 2 байт - вещь не самая тривиальная и быстрая...

Хотя у нас хотя бы умножение есть - можно помножить на 1/256 - и тем самым переместить старший байт в младший. (у нас умножение работает в представлении 1.15, т.е считается что числа имеют диапазон от -1 до 1-2-15, поэтому с помощью умножения как раз эффективно можно делать сдвиги ВПРАВО, но тоже не очень быстро, 18 тактов)

Но возникла ещё одна мысль, как легко и непринуждённо заставить СТАРУЮ процедуру print работать с "упакованными строками", не вводя никаких новых команд ни по обращению к памяти (там всё под завязку, по 64 на DestAddr и SrcAddr) или в АЛУ, вообще "систему команд" никоим образом не меняя...


Идея в том, что у нас ещё "прорва адресного пространства" в оперативной памяти. Мы могли бы использовать 16 бит, что соответствует 65536 адресам, а в реальности задействуем только свои 8 бит, чтобы обратиться к одному из 256 слов.

Почему бы не добавить 9-й бит, который будет "признаком байтовой адресации". Т.е у нас есть 256 "реальных слов" данных, они имеют адреса 0x00..0xFF.

А затем мы введём 256 слов, 0x100..0x1FF, при обращении к которым мы на самом деле будем получать отдельные байты, лежащие по адресам 0x00..0x7F. То есть:
0x100 выдаст старший байт из 0x00,
0x101 выдаст младший байт из 0x00,
0x102 выдаст старший байт из 0x01,
0x103 выдаст младший байт из 0x01,
и т.д.


При этом в старший байт шины данных будут поступать нули.

С точки зрения программиста, достаточно будет, указывая строку, вместо
X     HelloStr


написать

X    BYTE PTR HelloStr


и компилятор должным образом сформирует адрес, передаваемый в регистр X.

Впрочем, реализация конкретно такой идеи будет кривоватой: у нас и так долго формируется "эффективный адрес", берётся базовый регистр, к нему прибавляется два индексных с нужными множителями. Если же на выходе "вишенкой на торте" поставим ещё один мультиплексор, который должен этот адрес сдвигать вправо, а "выскочивший" младший бит использовать для выбора между "верхним" и "нижним" байтами, можем опять не вписаться в заветные 25 МГц.

Можно и по-другому. Адрес слова в памяти мы всё-таки оставляем без изменения, чтобы не удлинять путь, который наверняка у нас "критический" (т.е именно он определяет предельно допустимую частоту). Но ещё два дополнительных бита адреса будут управлять "режимом доступа":
00 - обычный доступ по целым словам,
10 - вместо всего слова подаётся младший байт, а старший "зануляется",
11 - вместо всего слова подаётся старший байт на месте младшего, а старший байт шины данных "зануляется",
01 - "зарезервировано"


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


Декодер команд QuatCoreFastMemDecoder, не трогаем.
Формирователь эффективного адреса состоит из мультиплексора первого и второго индексных регистров, QuatCoreFirstIndexMux и QuatCoreSecondIndexMux, двух сумматоров QuatCoreMemIndexAdder и QuatCoreMemMainAdder - ничего из этого не трогаем!, что тоже приятно.

Подкорректировать придётся мультиплексор QuatCoreMemSquareMux. Он так называется, потому что переключается в зависимости от наличия квадратных скобок в мнемоники команды. Например, по команде "X" он переключается из памяти в базовые регистры, чтобы выдать значение регистра X. А вот [X+k] - и он переключится в память, поскольку команда означает "получить значение, лежащее В ПАМЯТИ по заданному адресу". Он был очень простым:

module QuatCoreMemSquareMux (input [15:0] Memory, input [RamWidth-1:0] Regs, input DoMemory, output [15:0] Q);

parameter RamWidth = 8;

assign Q = DoMemory? Memory : Regs;

endmodule


И не сильно усложнится: он как выдавал нули в старших битах в случае выдачи регистров, так и будет выдавать нули, просто в бОльшем числе вариантов. Можно и здесь пожадничать и даже для расширенного регистра X выдать только "обычные" биты адреса, а 2 дополнительных оставить. В итоге выйдет нечто такое:

//ByteMode[0] = 0: Word
//ByteMode[0] = 1: Byte
//ByteMode[1] = 0: Low
//ByteMode[1] = 1: High

module QuatCoreMemSquareByteMux (input [15:0] Memory, input [RamWidth-1:0] Regs, input DoMemory, input [1:0] ByteMode, output [15:0] Q);

parameter RamWidth = 8;
parameter EnableByteAccess = 1'b1;

assign Q = 	(~DoMemory)? 				Regs :
		(~(ByteMode[0]&EnableByteAccess))? 	Memory :
		(~ByteMode[1])?				{8'h00, Memory[7:0]} :
							{8'h00, Memory[15:8]};
							

endmodule


Добавляется 9 ЛЭ, что не так уж страшно. С помощью параметра EnableByteAccess можно будет начисто отключить эту "опцию".

Далее, поменяем QuatCoreMemReg. По отсутствию в названии слов Fast, Ultimate, всяких "V2" и пр. - видно, что этот модуль "долгожитель", он последний раз был изменён 31 декабря 2019 года, в 5 утра. Вот его код:

module QuatCoreMemReg (	input clk, input [15:0] D, input WriteX, input WriteY, input WriteZ, input WriteSP, input CountSP, input SPup,
			output reg [RamWidth-1:0] X = 1'b0, output reg [RamWidth-1:0] Y = 1'b0, output reg [RamWidth-1:0] Z=1'b0, output [RamWidth-1:0] SP);

parameter RamWidth = 8;

always @(posedge clk) begin
	if (WriteX)
		X <= D[RamWidth-1:0];
	if (WriteY)
		Y <= D[RamWidth-1:0];
	if (WriteZ)
		Z <= D[RamWidth-1:0];
end

lpm_counter SPreg (	.clock (clk),
			.data (D[RamWidth-1:0]),
			.sload (WriteSP),
			.cnt_en (CountSP),
			.updown(SPup),
			.Q (SP));
defparam
	SPreg.lpm_type = "LPM_COUNTER",
	SPreg.lpm_width = RamWidth;

endmodule


Какая прелесть :) Всё так легко. Внесём небольшие изменения:

module QuatCoreMemRegWBand (	input clk, input [15:0] D, input WriteX, input WriteY, input WriteZ, input WriteSP, input CountSP, input SPup,
				output [RamWidth+1:0] X, output reg [RamWidth-1:0] Y = 1'b0, output reg [RamWidth-1:0] Z=1'b0, output [RamWidth-1:0] SP);

parameter RamWidth = 8;
parameter EnableByteAccess = 1'b1;

localparam XWidth = RamWidth + 2 * (EnableByteAccess);
reg [XWidth-1:0] Xreg = 1'b0;
assign X = Xreg;


always @(posedge clk) begin
	if (WriteX)
		Xreg <= D[XWidth-1:0];
	if (WriteY)
		Y <= D[RamWidth-1:0];
	if (WriteZ)
		Z <= D[RamWidth-1:0];
end

lpm_counter SPreg (	.clock (clk),
			.data (D[RamWidth-1:0]),
			.sload (WriteSP),
			.cnt_en (CountSP),
			.updown(SPup),
			.Q (SP));
defparam
	SPreg.lpm_type = "LPM_COUNTER",
	SPreg.lpm_width = RamWidth;

endmodule


Ширина регистра X возрастает на 2 бита, если EnableByteAccess=1. Если же EnableByteAccess=0, то регистр становится на 2 бита короче, но менять ширину "вывода" в зависимости от ДВУХ параметров (RamWidth и EnableByteAccess) СТРАШНО НЕУДОБНО, поэтому он всегда будет пошире. Когда EnableByteAcess=0, синтезатор быстро сообразит, что в верхних двух битах всегда нули, и порежет всю ненужную логику.
если так подумать, в мультиплексоре мы могли бы параметр не вводить - он бы сам догадался, что ByteMode[0] всегда нулевой!

И осталось подправить мультиплексор базового адреса. Таким он был:

module QuatCoreMemBaseMux (	input [RamWidth-1:0] X, input [RamWidth-1:0] Y, input [RamWidth-1:0] Z, input [RamWidth-1:0] SP,
				input [1:0] adr, output [RamWidth-1:0] Q);

parameter RamWidth = 8;
							
assign Q = 	(adr == 2'b00)? X :
		(adr == 2'b01)? Y :
		(adr == 2'b10)? Z :
				SP;
							
endmodule


Последнее изменение 31 декабря 2019 года, 17:30. Как же просто всё было в 2019 году... Сейчас станет самую чуточку сложнее:

module QuatCoreMemBaseMuxWBand (input [RamWidth+1:0] X, input [RamWidth-1:0] Y, input [RamWidth-1:0] Z, input [RamWidth-1:0] SP,
				input [1:0] adr, output [RamWidth-1:0] Q, output [1:0] ByteMode);

parameter RamWidth = 8;
							
assign Q = 	(adr == 2'b00)? X[RamWidth-1:0] :
		(adr == 2'b01)? Y :
		(adr == 2'b10)? Z :
				SP;
							
assign ByteMode = (adr == 2'b00)? X[RamWidth+1:RamWidth] : 2'b00;
							
endmodule


И теперь выход ByteMode того мультиплексора, который формирует эффективный адрес (т.е нижнего по схеме), соединяем с входом ByteMode мультиплексора QuatCoreMemSquareByteMux - и дело в шляпе!

Так выглядит итоговая схема:


Старая схема QuatCoreMem синтезировалась в 155 ЛЭ, новая с EnableByteAccess=0 - в те же 155, что намекает, что "задача сводится к предыдущей", а с EnableByteAccess=1 - в 175 ЛЭ, т.е добавилось 20 ЛЭ. Больше, чем я ожидал, но терпимо...

Наконец, вычислительное ядро QuatCore "в сборе" (PC, ALU, MEM, IMM) синтезируется в 509 ЛЭ со старым модулем и в 522 с новым, т.е разница в 13 ЛЭ - да это ближе к моим ожиданиям :)

Но самое интересное - что у нас с предельной частотой. При EnableByteAccess=0 это 28,17 МГц, а при EnableByteAccess=1: 28,57 МГц, даже чуть больше. Это "приколы" Place&Route, нормальное явление.

И опять странное поведение Quartus II: когда я меняю параметр в Top Level, он его не распространяет "вниз", о чём даже честно пишет во вкладке Parameters. А почему он этого не делает - не знаю. "Забывает"...

Теперь самое весёлое: научить компилятор новым трюкам.


Poll #2104820 Возня с отдельными байтами

Как с ними лучше работать?

Bit Banding (в нашем случае Byte Banding) - байты "зеркалятся" в адресном пространстве
0(0.0%)
Нормальный АЛУ со сдвигами и логическими операциями
0(0.0%)
Специальные команды или регистры (как в х86 можно использовать eax, ax, ah и al)
0(0.0%)
Лучше с ними вообще не работать!
0(0.0%)
Tags: ПЛИС, программки, работа, странные девайсы
Subscribe

Recent Posts from This Journal

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

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

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

    Вчера я чуть поторопился отсинтезировать проект,параметры не поменял: 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 

  • 6 comments