nabbla (nabbla1) wrote,
nabbla
nabbla1

Categories:

О двунаправленной шине в ПЛИС

Классические компьютерные архитектуры предполагают наличие шины данных, на которой висит достаточно много устройств. Каждое из них может переходить в третье, Z-состояние с высоким выходным сопротивлением, и тогда оно никому не мешает, и может получать данные с шины.

Также имеется шина адреса, в которой полноправный властитель - это управляющее устройство процессора. Остальные прочитывают адрес и определяют - "о, да это ж я! Надо что ль голос подать!".

Такая организация позволяет очень легко наращивать устройства: просто "сажаем их на шину данных и шину адреса" - и дальше размечаем адресное пространство. Где-то "вручную", где-то на автомате.

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

Причина тому - отсутствие двунаправленных шин "внутри ПЛИС", практически всех. Когда-то такие были, но затем от них отказались. Точно не знаю, почему, но уж так вот: ножки ПЛИС можно "на ходу" переключать с чтения на запись, а внутри входы остаются входами, а выходы - выходами, с двумя состояниями, 0 и 1 (без Z).

И всё бы хорошо, работает, но в итоге "периферия" встраивается в QuatCore как-то некрасиво, "ручками". Нужно мультиплексору добавлять новые входы, и цепляться к ним. А значит, и отдельным модулем само "ядро" так сходу не сделаешь - его всё время придётся "перекомпилировать".

Хочется попробовать представить его по классике, с двунаправленной шиной, и посмотреть, сможет ли синтезатор автоматически превратить её в мультиплексор, и насколько эффективно он это сделает.


Начнём переделку с модуля QuatCoreImm. Так он выглядел:
module QuatCoreIMM (input [7:0] SrcAddr, output [15:0] Q);

assign Q[6:0] = SrcAddr[6:0];
assign Q[15:7] = {9{SrcAddr[6]}}; //sign extension

endmodule


И синтезировался в 0 ЛЭ.

Теперь в него всё-таки нужно добавить немножко логики:

module QuatCoreBusImm (input [7:0] SrcAddr, inout [15:0] DataBus);

wire IsOurAddr = ~SrcAddr[7]; //адреса 0xxx_xxxx, или 00..7F - наши!

assign DataBus = IsOurAddr? {{9{SrcAddr[6]}}, SrcAddr[6:0]} : {16{1'bz}};

endmodule


Он сначала синтезируется в 0 ЛЭ, но потом фиттер (Place&Route) вставляет 16 ЛЭ.

Теперь возьмём QuatCorePC.

Так он выглядит сейчас:


Но поменять надо, по сути, лишь один модуль, QuatCorePCSrcMux (справа сверху). Вот его код:

module QuatCorePCsrcMux (	input [4:0] ireg, input [4:0] jreg, input [4:0] kreg, input invreg, 
				input [RomAddrWidth-1:0] PC, input [7:0] SrcAddr,
				output [15:0] Q, output [15:0] ijk);
							
parameter RomAddrWidth = 9;
parameter ijkEnabled = 1'b1;
localparam LeadingZeros = RomAddrWidth - 5;
localparam InvLeadingZeros = RomAddrWidth - 1;
wire [1:0] rg = SrcAddr[1:0];
wire isIJK = SrcAddr[2]&ijkEnabled;

assign ijk = {invreg, kreg, ireg, jreg};

assign Q = 	SrcAddr[4]? 	PC :
		isIJK? 		ijk :
		(rg == 2'b00)? 	{{LeadingZeros{1'b0}}, ireg} :
		(rg == 2'b01)?	{{LeadingZeros{1'b0}}, jreg} :
		(rg == 2'b10)?	{{LeadingZeros{1'b0}}, kreg} :
				{{InvLeadingZeros{1'b0}}, invreg};
endmodule


Меняем Q с output на inout, и "подаём голос", если адрес наш. До сих пор мы вообще не проверяли адрес SrcAddr в этом месте, т.к можем в любом случае что-нибудь выдавать, там дальше мультиплексор стоит, который наш сигнал не пропустит дальше. Теперь его нет, нужно самим :) Получается как-то так:

module QuatCoreBusPCsrcMux (	input [4:0] ireg, input [4:0] jreg, input [4:0] kreg, input invreg, 
				input [RomAddrWidth-1:0] PC, input [7:0] SrcAddr,
				inout [15:0] DataBus, output [15:0] ijk);
							
parameter RomAddrWidth = 9;
parameter ijkEnabled = 1'b0;
localparam LeadingZeros = RomAddrWidth - 5;
localparam InvLeadingZeros = RomAddrWidth - 1;

wire IsOurAddr = (SrcAddr[7:5] == 3'b101);
wire [1:0] rg = SrcAddr[1:0];
wire isIJK = SrcAddr[2]&ijkEnabled;

assign ijk = {invreg, kreg, ireg, jreg};

wire [15:0] Q  = 	SrcAddr[4]? 	PC :
			isIJK? 		ijk :
			(rg == 2'b00)? 	{{LeadingZeros{1'b0}}, ireg} :
			(rg == 2'b01)?	{{LeadingZeros{1'b0}}, jreg} :
			(rg == 2'b10)? 	{{LeadingZeros{1'b0}}, kreg} :
					{{InvLeadingZeros{1'b0}}, invreg};
									
assign DataBus = IsOurAddr? Q  : {16{1'bz}};

endmodule


Ну и схему QuatCorePC чуточку подправляем:


Все изменения в том, что соединили между собой D и Q, и обозвали получившееся DataBus, к тому же двунаправленным.

Следующий на очереди: QuatCoreALU



Меняем только модуль QuatCoreALUoutput:

//our ALU is too complex, decided to divide it into several submodules.
//this one sets output pins depending on SrcAddr and Acc contents.

//Only 2 least-sign bits of SrcAddr matter:
//0x - Acc (signed with saturation)
//10 - UAC (unsigned)
//11 - C (to store C register into stack)

module QuatCoreALUoutput (input [7:0] SrcAddr, input [16:0] Acc, input [15:0] C, output [15:0] Q, output isOFLO);

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;

endmodule

//theoretically, that's 1 LE for isOFLO, 
//                      2 LE for mode,
//                     16 LE for Q
// that is 19 LE...

//but synthesizer came up with 34, crazy bastard.
                        


Как обычно, нужно выход сделать двунаправленным, активировать его только когда "адрес наш":

//our ALU is too complex, decided to divide it into several submodules.
//this one sets output pins depending on SrcAddr and Acc contents.

//Only 2 least-sign bits of SrcAddr matter:
//0x - Acc (signed with saturation)
//10 - UAC (unsigned)
//11 - C (to store C register into stack)

//(and 4 most-sign. to activate ALU output)

module QuatCoreBusALUoutput (input [7:0] SrcAddr, input [16:0] Acc, input [15:0] C, inout [15:0] DataBus, output isOFLO);

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

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

wire IsOurAddr = (SrcAddr[7:5] == 3'b100);

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

wire [15:0] Q = (mode == 2'b00)? 	16'h8000 :
				(mode == 2'b01)? 	16'h7FFF :
				(mode == 2'b10)? 	Acc [15:0]:
									C;
									
assign DataBus = IsOurAddr? Q : {16{1'bz}};

endmodule                       


Практически "под копирку". И в самом модуле АЛУ заменяем D и Q на двунаправленный DataBus:


Можно уже собрать что-нибудь:


Пока всю логику конвейера не соединяем, посмотреть хотя бы - отсинтезируется ли, и насколько хорошо?

Да, отсинтезировалось легко и непринуждённо в 327 ЛЭ с максимальной частотой 30,77 МГц.

Но это ни о чём не говорит пока, самые долгие цепи были в модуле памяти QuatCoreMem. Давайте и его сюда припашем.

Так модуль выглядит сейчас (после того, как мы выкинули один мультиплексор, но всё-таки вправили его не место):


Как обычно, нужно поменять модуль, который подаёт данные на выход, это QuatCoreMemSquareMux:

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


Мы назвали его SquareMux, потому что он как бы выбирает между командами без квадратных скобок (выдать содержимое непосредственно регистров) и с квадратными скобками (выдать содержимое ПАМЯТИ по адресу из регистра!).

Нужно заменить output на inout и проверить адрес SrcAddr:
module QuatCoreMemBusSquareMux (input [15:0] Memory, input [RamWidth-1:0] Regs, input DoMemory, input [7:0] SrcAddr, inout [15:0] Q);

parameter RamWidth = 8;

wire isOurAddr = (SrcAddr[7:6] == 2'b11);

assign Q = isOurAddr? (DoMemory? Memory : Regs) : {16{1'bz}};

endmodule


И весь модуль для обращения с памятью чуть перекраиваем: убираем D,Q, заменяя их на один DataBus, да в общем-то и всё:


Встраиваем эту хреновину в процессор:


И запускаем синтез.

И что вы думаете: он успешно синтезировался, занял 482 ЛЭ (моя "обычная версия": 492 ЛЭ), причём синтезировался он "моментально", за минуту и 7 секунд на Intel Atom D510. "Обычная версия" надолго застревает на Place&Route, так что полное время синтеза 4..5 минут.

И самое главное, Timing Analyzer показывает максимальную тактовую частоту в 28,65 МГц, и это при том, что у нас вообще-то КОНВЕЙЕР НА ОДНУ СТУПЕНЬ КОРОЧЕ, т.е при такой шинной структуре мы получаем данные на шину, и тут же их куда-то отправляем, без задержки на один такт.

Как так получается, я не понимаю. По логике, синтезатор в лучшем случае мог бы преобразовать этот код в тот же самый мультиплексор, на котором у меня "без защёлок" выходило от силы 20 МГц. И по схожести числа ЛЭ нужно полагать, что нечто похожее он и поставил, недостающие 10 ЛЭ - это скорее всего недостающие "блокировки", которые здесь мы не "нарисовали".

И это в лучшем случае - ведь ему ещё сообразить надо было, что мы конфликта на шине никогда не возникает, поскольку адреса не пересекаются.

Объяснение пока могу дать лишь одно: разработчики синтезатора сочли такую ситуацию (двунаправленную шину "внутри ПЛИС", в которой этой шины на самом деле НЕТ) очень частой и важной, и оптимизировали "ручками" (в смысле, это не оптимизатор В ОБЩЕМ ВИДЕ, а лишь конкретной задачи построения двунаправленной шины), да так хорошо оптимизировали, что оптимизатор "общего вида" нервно курит в сторонке!

В общем, как упомянул на днях beerwolf_nik, многие знания - многие печали. Я такой подкованный, читал ТУ на ПЛИС, знаю все её потроха - и много ли это мне дало, если синтезатор "заточен" под студентов, которых всю жизнь учили архитектуре процессоров с двунаправленными шинами!?

Эх, но я уже отладил "свой вариант" с мультиплексором и защёлкой между Src и Dest. Делать с этой шиной - опять всю логику конвейера перекраивать. Наверное, теперь это будет проще, но всё равно неделю-другую займёт.


Пожалуй, пора взяться за ввод-вывод на "быстром QuatCore", и за видео. Давно пора, теперь они даже на одной частоте работают, делов за малым...

Poll #2101221 Двунаправленная шина внутри ПЛИС

Стоит ли сделать QuatCore с двунаправленной шиной?

Да
2(40.0%)
Да, но потом
2(40.0%)
Нет
1(20.0%)
Tags: ПЛИС, программки, работа, странные девайсы
Subscribe

  • Огари разговаривают

    Сегодня по пути на работу встретил огарей прямо в Лосином острове, на берегу Яузы. Эти были на удивление бесстрашны, занимались своими делами, не…

  • Очень запоздало о лыжах

    Давненько (с 16 февраля) не писал о лыжах, хотя каждую неделю катался. Первый раз - на Гремячий и назад, с разведкой нового пути к маленькому…

  • Воскресная прогулка в сторону Гремячего

    Не было особенных планов "доехать во что бы то ни стало", хотел просто посмотреть "что вообще творится"? после обильных снегопадов в пятницу-субботу.…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 10 comments

  • Огари разговаривают

    Сегодня по пути на работу встретил огарей прямо в Лосином острове, на берегу Яузы. Эти были на удивление бесстрашны, занимались своими делами, не…

  • Очень запоздало о лыжах

    Давненько (с 16 февраля) не писал о лыжах, хотя каждую неделю катался. Первый раз - на Гремячий и назад, с разведкой нового пути к маленькому…

  • Воскресная прогулка в сторону Гремячего

    Не было особенных планов "доехать во что бы то ни стало", хотел просто посмотреть "что вообще творится"? после обильных снегопадов в пятницу-субботу.…