nabbla (nabbla1) wrote,
nabbla
nabbla1

Category:

Разгоняем QuatCore до 25 МГц, часть 5

Пора что ль за реальное "железо" браться, в смысле, верилоговско-схематические модули :)

И начать с самого весёлого, единого в трёх лицах QuatCorePC (источник, получатель и главная движущая сила):


Как всегда, стараемся внести минимальные изменения, хотя это может аукнуться, когда чего-то не учтём. Но думаю, во время отладки ВСЕХ написанных для QuatCore программ (да, целых 10 :)) все косяки всплывут.


Напомним, что за модули здесь находятся. В центре композиции: QuatCorePCreg - непосредственно счётчик инструкций (Program Counter). И мы продолжаем славную традицию реализовывать регистры как счётчики, а счётчики как регистры :) Здесь именно регистр, поскольку для переходов по относительному адресу нам в любом случае нужен сумматор, а если так, то и примитивные частные случаи прибавления нуля и единички можно реализовать в нём! А регистру лучше дать дополнительные входы для прыжков по абсолютным адресам и вызова процедур.

Справа внизу - этот самый сумматор, QuatCorePCadder. Наверху: QuatCorePCregisters, хранящий индексные регистры i,j,k, Inv, которые используются для организации циклов и косвенной адресации к памяти, в том числе "двумерной".

Давайте с этих регистров и начнём. Видно, что в этот модуль сейчас идёт два "блокирующих" сигнала, DestStall и DestDiscard. Действительно, если команда оказалась "не в том месте" (случайно её получили, а на самом деле прыгнули ещё 1-2 строки ранее!), то ни в коем случае нельзя заносить какие-то значения в регистры. И при подаче DestStall этого делать нельзя, т.к на шине данных уже могут быть ошибочные данные, которые мы запишем поверх правильных. А ещё ведь есть инкременты i++,j++,k++ (и декременты в iLOOP, jLOOP, kLOOP), а их надо делать ровно ОДИН РАЗ, иначе тоже швах.

А вот PipeStall (сокращённо от PipelineStall, запрос остановки конвейера) сюда не идёт, и это совершенно правильно. Пусть у нас окажется следующая команда:

i     [X+k]


Точнее, код был таким:
i     0
j     [X+k]

но реально, при выполнении, мы исполняем левую часть ТЕКУЩЕЙ команды и правую часть СЛЕДУЮЩЕЙ команды (или левую часть ПРЕДЫДУЩЕЙ и правую часть ТЕКУЩЕЙ, это одно и то же). Поэтому нолик на шину данных мы подготовили, а теперь должны занести его в i и готовить [X+k] для j. Вот и получаем

i     [X+k]

Сейчас приходит PipeStall, т.к [X+k] выполняется 2 такта. Но мы ОБЯЗАНЫ занести значение из шины данных в i. К следующему такту его там уже не будет, придёт какой-то мусор из QuatCoreMem. А ещё спустя такт - не мусор, а то что заказали.

Необходимость DestStall и вредность PipeStall для QuatCorePCregisters подтверждена :)

Теперь подумаем о QuatCorePCreg (сам счётчик инструкций). На каждом такте он защёлкивает одно из 3 значений:
- либо CallAddr, поступивший из таблицы адресов функций (процедур),
- либо AbsAddr, поступивший из шины данных, для безусловного перехода по абсолютному адресу,
- либо RelAddr, поступивший из сумматора, для всего остального. RelAddr - это текущий адрес, к которому прибавили либо ноль (остановка конвейера), либо единицу (нормальное выполнение), либо число с шины данных (переход по относительному адресу).

Других опций у него попросту нет (ещё по reset он устанавливает ноль, но про reset попозже поговорим...)

Занесение CallAddr и AbsAddr фатально, если данная команда выполняться не должна была, поэтому в комбинаторных блоках IsFuncCall и IsJMP мы добавили входы DestDiscard и SrcDiscard, соответственно.

Но мы не стали добавлять в IsFuncCall вход SrcStall. Причина в том, что "просьба об остановке" может прийти только со стороны DestAddr. А мы знаем, что она приходит В НАЧАЛЕ, а потом, на последнем такте выполнения длинной инструкции из DestAddr, нам разрешают поработать. Во время остановки всем плевать на содержимое PC, поэтому имеем полное право сразу положить туда адрес функции. Мы знаем, что на последнем такте мы сделаем ровно то же самое. И тем самым надобность в SrcStall в модуле QuatCorePC вообще исчезает, поскольку этот вход нужен для КОМАНД НА SrcAddr, ОБЛАДАЮЩИХ ПОБОЧНЫМИ ЭФФЕКТАМИ! Если всё, что делает команда - это подаёт значение на шину данных - мы совершенно спокойны. Побочными эффектами обладали вызовы процедур, с ними мы разобрались, можно жить спокойно :)

И по схожей причине мы не стали добавлять в IsJMP вход DestStall. Мы заранее понимаем, что он тут не сработает, ведь это команда безусловного прыжка, которая установит SrcDiscard=1, так что команда из SrcAddr сотрётся из нашей действительности.

Ну и на сладкое - модуль QuatCorePCadder. Он отвечает за остановку конвейера (PC застревает на месте) и за условные переходы.

Теперь он же будет отвечать за формирование DestDiscard и SrcDiscard. И сам же охотно учтёт DestDiscard в случае условных переходов.

Разберёмся, как условные переходы должны реагировать на DestStall. Для этого рассмотрим такую команду, или точнее две полукоманды, предыдущая и текущая:

JL   [X+k]


Если условия прыжка выполнены, то [X+k] будет объявлена вне закона (SrcDiscard=1), и DestStall возникнуть не сможет всё равно, всё так же, как и при безусловном прыжке. Но что, если условия не выполнены? В этом случае модуль QuatCorePCrelJumpDecision выдаст нолик, т.е с точки зрения QuatCorePCadder это будет самое обычное исполнение команды, без переходов. На первом такте PC останется стоять как вкопанный, поскольку PipeStall=1. А на следующем такте будет PipeStall=0, и к PC прибавится единичка, как и должна.

Мы видим, что и здесь DestStall на самом деле без надобности!

Этим размышлениям могло бы помешать, если условия для перехода вдруг "на ходу" изменились. Подумаем и об этом. АЛУ пока что устроено так, что просит всех замереть - "ВЫ МЕШАЕТЕ МНЕ ДУМАТЬ!". Так что в момент выполнения этой команды никакой "теневой активности" со стороны АЛУ ждать не приходится - команды JL/JGE/JO/JNO безопасны :) Регистры i/j/k пока устроены так, что могут меняться только командами, лежащими по DestAddr, так что за SrcAddr мы спокойны. Наконец, команды iLOOP/jLOOP/kLOOP, которые осуществляют прыжок и декремент, пока соответствующая переменная больше нуля, но оставляют переменную в покое и не делают прыжка в случае нуля, нам тоже НЕ СТРАШНЫ. Очевидно, если прыжка не было, то мы можем сколько угодно повторять iLOOP, он точно так же оставит i нулевым, и ВНЕЗАПНОГО ЖЕЛАНИЯ ПРЫГНУТЬ у него не появится.

Так что пока всё хорошо, но видно, по какому тонкому льду мы ходим. (ещё немножко - и MELTDOWN)


И ещё одна необходимая модификация: в мультиплексор QuatCorePCsrcMux на вход PC мы раньше подавали выход с сумматора, поскольку знали: в нормальной ситуации там окажется PC+1, а это и есть адрес возврата. Теперь же, из-за дополнительной задержки в один такт, САМ PC УЖЕ ЯВЛЯЕТСЯ АДРЕСОМ ВОЗВРАТА! (Осторожно, эээ, двери, эээ, закрываются, следующая, эээ, станция, эээ, а вот и она!)

Кстати, именно подключение входа к выходу сумматора было причиной возникновения комбинаторных обратных связей (см. недокументированная инструкция KILL), и во избежание чего я поставил костыль, введя внутрь этого сумматора провод DoCall. Как ни странно, этот провод теперь действительно понадобился для формирования сигналов DestDiscard и SrcDiscard. Но вот необходимость разрывать комбинаторный цикл сама собой исчезла - появилась защёлка и в мультиплексоре шины данных, и вместо выхода сумматора мы уже применяем выход самого регистра PC.

Приведём код этого модуля:
module QuatCorePCadder (input clk, input [RomAddrWidth-1:0] PC, input [15:0] D, input DoJump, input DoCall, input DoAbsAddr, input busy,
			output [RomAddrWidth-1:0] Q, output SrcDiscard, output reg DestDiscard = 1'b0);

parameter RomAddrWidth = 9;
							
wire [RomAddrWidth-1:0] incr = 	busy? 				{RomAddrWidth{1'b0}}:
				((~DestDiscard)&DoJump)?	D[RomAddrWidth-1:0]:
								{{(RomAddrWidth-1){1'b0}},1'b1};
		
lpm_add_sub adder (	.dataa (PC), 
			.datab (incr),
			.cin (1'b0),
			.result (Q));
	defparam
		adder.lpm_direction = "ADD",
		adder.lpm_hint = "ONE_INPUT_IS_CONSTANT=NO,CIN_USED=NO",
		adder.lpm_representation = "UNSIGNED",
		adder.lpm_type = "LPM_ADD_SUB",
		adder.lpm_width = RomAddrWidth;

reg SrcReg = 1'b0;
wire AnyJump = DoJump | DoAbsAddr;
assign SrcDiscard = SrcReg | AnyJump;
always @(posedge clk) if (~busy) begin
	SrcReg <= DoCall | AnyJump;
	DestDiscard <= SrcDiscard;
end
										
endmodule


Остальные не претерпели зверских изменений: IsJMP и IsFuncCall свой результат объединили по И с DestDiscard / SrcDiscard соответственно, ничего интересного. А в модуле QuatCorePCregisters новые "И" появились в выражении IsOurOp (удостоверяющее, что старшие биты адреса указывают конкретно на операции с индексными регистрами).


Надеюсь, самое страшное уже позади. Остальные модули не столь злы. Хотя на отладке, чувствую, хлебну ещё горя.
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