nabbla (nabbla1) wrote,
nabbla
nabbla1

Category:

В топку относительные адреса!

"Собрал" (нарисовал) схему для проверки связки QuatCore+GPU+I/O "на железе", в смысле на своей макетке. Теперь уже "внутренности" QuatCore (шины DestAddr, SrcAddr, DataBus, PC и пр) не торчали наружу, только UART, SPI, LCD (черно-белый ЖК экранчик) и SRAM.

Я надеялся, что теперь-то предельная частота выйдет на свои 25 МГц, которые необходимы нам для получения картинки "высокой четкости" с аналоговой камеры. Не тут-то было: 23 МГц максимум, и 736 failed paths. Такое ощущение, что подводит нас "потолстевшая" логика управления конвейером - это ЕДИНСТВЕННАЯ комбинаторная логика, которая нарастает по мере подключения новых и новых устройств.

И "основной удар" приходится на самый главный регистр, PC (Program Counter):


И если так подумать, новое значение PC действительно зависит от чересчур большого количества сигналов:
- SrcAddr[7:4] и SrcDiscard - эти сигналы определяют, выполняется ли вызов функции?
- DestAddr[7:3] и DestDiscard - эти сигналы определяют, выполняется ли прыжок по абсолютному адресу?
- DataBus (по одному биту на каждый бит PC) - адрес для прыжка по абсолютному адресу,
- выход Q из QuatCoreCallTable (по одному биту на каждый бит PC) - адрес для вызова процедуры,
- выход Q сумматора относительного адреса QuatCoreLighterPCadder (по одному биту на каждый бит PC) - адрес для всех остальных ситуаций: либо тот же самый (когда мы застреваем), либо +1 (нормальное выполнение), либо +DataBus (относительный прыжок). Но этот выход формируется "на ходу" (на том же такте) из того же DataBus, выхода PC, причём и DataBus подключён фактически через "мультиплексор", который может вместо DataBus выдать 0 или 1. А что именно он выдаст - опять зависит от множества сигналов - DestAddr, значения флагов, PipeStall и т.д.

Похоже, самое время упростить это безобразие, и весьма радикальным способом, вообще отказавшись от относительных адресов.


Когда я только начинал ковырять QuatCore, мне уже показалось, что два варианта прыжков (по абсолютным и по относительным адресам) - это уже перебор, и думал использовать только абсолютные адреса. То есть, PC действительно будет чистокровным СЧЁТЧИКОМ со входом параллельной загрузки. При нормальном выполнении команд (без прыжков) он сам прибавляет единичку, при каких-то остановках на него не поступает cnt_en (разрешение счёта), а для прыжков идёт параллельная загрузка с шины данных или из QuatCoreCallTable.

И всё бы хорошо, но для вызова процедуры нужно было отправить в стек (через шину данных) значение PC+1 (адрес возврата), причём отправить ПРЯМ ЩАС. Попытка сначала "как ни в чем не бывало" перейти на следующую команду, но не выполнять её, вместо этого поместить значение на шину данных, а только после этого прыгнуть куда просили - всё это получалось очень накладно.

Выходило, что нам всё-таки нужен сумматор, который будет прибавлять единичку, и это значение PC+1 отправлять в стек. А раз он всё равно есть - то зачем дублировать функциональность со счётчиком - пусть эта же PC+1 отправляется в регистр PC при нормальном выполнении кода, и как только этот сумматор появился - стало совсем просто приспособить его и для относительных адресов, "само собой вышло".

И только когда я ввёл "конвейер", оказалось, что PC и так бежит на единичку впереди исполняемой в данный момент "полукоманды" SrcAddr, поэтому именно PC можно напрямую отправить на шину данных при вызове процедуры, это и будет "адрес возврата".

Но "было уже поздно": я привык к наличию относительных адресов, компилятор их так замечательно высчитывал, да и существовавший на тот момент модуль непосредственных значений QuatCoreImm мог выдавать значения только от -64 до +63, а потому уже на сколько-нибудь длинной программе прыгать "куда угодно" становилось накладным.

И даже с введением QuatCoreImmTable, позволявшей хранить 128 ПРОИЗВОЛЬНЫХ 16-битных непосредственных значений, относительная адресация умудрилась уцелеть, хоть я и попытался упростить всю эту "машинерию", сделать счётчик команд счётчиком.

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

  AllowRelJumps = true


и если здесь поставить false, то повсюду, в том числе для команд JGE,JL,JO,JNO,а также iLOOP,jLOOP,kLOOP на этапе компиляции также будут формироваться абсолютные адреса (т.е вместо метки, куда прыгать, будет подставляться её адрес).

И уже посмотрел, что из этого получается. По крайней мере, в программе обнаружения точек на видеосигнале (та, которую мы свыше 2 недель отлаживали), таблица непосредственных значений не раздувается, и даже количество ЛЭ для QuatCoreImmTable остаётся тем же самым.

Боюсь, для более крупных программ ситуация может поменяться - чем больше будет однотипных циклов на 1-2-3 строк, тем больше будет преимущество относительной адресации. Но насколько большим будет это преимущество, можно определить только если сравнить оба варианта.

Так что всё-таки за работу!

Нам нужно заменить два эти модуля:

(QuatCoreLighterPCreg и QuatCoreLighterPCadder) одним, примерно с таким объявлением:

//Счётчик инструкций БЕЗ ОТНОСИТЕЛЬНЫХ ПРЫЖКОВ
//за счёт этого он должен упроститься, а главное - ускориться!

//только ситхи мыслят абсолютами
module QuatCoreSithPC (	input clk, input [RomAddrWidth-1:0] CallAddr, input [15:0] DataBus,
			input DoCall, input DoJMP, input DoConditional,
			input PipeStall, input DestStall,
			output [RomAddrWidth-1:0] PC, output SrcDiscard);
parameter RomAddrWidth = 8;


Вот теперь счётчик инструкций действительно будет счётчиком, от макушки и до пяток (не как в прошлый раз, когда счётчик только для верхних бит, а нижние - регистр+сумматор). Ему нужны входы разрешения счёта, а также вход параллельной загрузки и разрешения параллельной загрузки. От reset мы в своё время отказались, можно и сейчас отказаться, куда надёжнее повторно сконфигурировать всю ПЛИС, только тогда мы будем уверены, что содержание оперативной памяти также вернулось "в известное состояние". Впрочем, можно и добавить - для счётчика сброс штука простая.

Для начала нужно выбрать, что именно отправить во вход параллельной загрузки, т.е поставить мультиплексор:
wire [RomAddrWidth-1:0] D = DoCall? CallAddr : DataBus[RomAddrWidth-1:0];


Ещё давайте запишем отдельным проводом, в каких случаях должен происходить прыжок:
wire LoadEnable = DoCall | DoJMP | (DoConditional & (~DestStall));


Ну а остановка конвейера, PipeStall, должна предотвратить как нормальный ход выполнение (прибавление по единичке), так и прыжки, поэтому в кои-то веки данный сигнал будет управлять входом clk_en, который в отличие от cnt_en, блокирует и остальные синхронные операции (загрузку, сброс и пр).

Собственно, мы готовы разместить сам счётчик инструкций:
lpm_counter Counter (	.clock (clk),
			.clk_en (PipeStall),
			.data (D),
			.sload (LoadEnable),
			.Q (PC));
defparam
	Counter.lpm_type = "LPM_COUNTER",
	Counter.lpm_direction = "UP",
	Counter.lpm_port_updown = "PORT_UNUSED",
	Counter.lpm_width = RomAddrWidth;


Халява!

Ну и ещё один "должок" - сформировать сигнал DestDiscard. Как-то так вышло, что он формируется именно здесь. Не будем изобретать велосипед, возьмём код из модуля QuatCoreLighterPCadder.

В итоге, получаем такой модуль:
//Счётчик инструкций БЕЗ ОТНОСИТЕЛЬНЫХ ПРЫЖКОВ
//за счёт этого он должен упроститься, а главное - ускориться!

//только ситхи мыслят абсолютами
module QuatCoreSithPC (	input clk, input [RomAddrWidth-1:0] CallAddr, input [15:0] DataBus,
			input DoCall, input DoJMP, input DoConditional,
			input PipeStall, input DestStall,
			output [RomAddrWidth-1:0] PC, output SrcDiscard);
parameter RomAddrWidth = 8;

wire [RomAddrWidth-1:0] D = DoCall? CallAddr : DataBus[RomAddrWidth-1:0];

wire DoCondJump = (~DestStall)&DoConditional;
wire AnyJump = DoJMP | DoCondJump;

wire LoadEnable = DoCall | AnyJump;

lpm_counter Counter (	.clock (clk),
			.clk_en (PipeStall),
			.data (D),
			.sload (LoadEnable),
			.Q (PC));
defparam
	Counter.lpm_type = "LPM_COUNTER",
	Counter.lpm_direction = "UP",
	Counter.lpm_port_updown = "PORT_UNUSED",
	Counter.lpm_width = RomAddrWidth;

reg SrcReg = 1'b0;
assign SrcDiscard = SrcReg | AnyJump;
always @(posedge clk) if (~PipeStall)
	SrcReg <= DoCall | AnyJump;	
						
endmodule


Синтезируется в 21 ЛЭ в случае 8-битного счётчика. В целом, ожидаемо:
- 8 ЛЭ на сам счётчик,
- 8 ЛЭ на входной мультиплексор (между DataBus и CallAddr),
- 1 ЛЭ на регистр SrcReg,
- 1 ЛЭ на формирование SrcDiscard,
- 1 ЛЭ на формирование sload
и ещё куда-то уходит 2 ЛЭ - нормальная ситуация.

Когда мы втыкаем этот модуль вместо двух "старых", мы наконец-то успешно синтезируемся на 25 МГц:



Вроде получилось. Надо только проверить, что ничего не поломали, как обычно...

UPD. Одну ошибку нашёл, ещё даже не закончив синтез: в clk_en нужно отправить не PipeStall, а ~PipeStall. Может, надо было вообще все эти stall с обратным знаком делать, чтобы 1 означало "работай", а 0 "стой". Хотя вроде синтезатор и сам может кое-где биты инвертировать, если это ему покажется удобнее. Но не уверен...
Tags: ПЛИС, программки, работа, странные девайсы
Subscribe

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

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

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

    Вчера я чуть поторопился отсинтезировать проект,параметры не поменял: RomWidth = 8 вместо 7, RamWidth = 9 вместо 8, и ещё EnableByteAccess=1, чтобы…

  • Балансируем конвейер QuatCore

    В пятницу у нас всё замечательно сработало на симуляции, первые 16 миллисекунд полёт нормальный. А вот прошить весь проект на ПЛИС и попробовать "в…

  • Ковыряемся с сантехникой

    Наконец-то закрыл сколько-нибудь пристойно трубы, подводящие к смесителю, в квартире в Москве: А в воскресенье побывал на даче, там очередная…

  • Мартовское велосипедное

    Продолжаю кататься на работу и с работы на велосипеде, а также в РКК Энергию и на дачу. Хотя на две недели случился перерыв, очередная поломка,…

  • Обнаружение на новом GPU - первые 16 мс

    Закончилась симуляция. UFLO и OFLO ни разу не возникли, что не может не радовать. За это время мы дошли до строки 0x10F = 271. Поглядим дамп памяти:…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 5 comments