Category: производство

Category was added automatically. Read all entries about "производство".

QuatCore

Испытания новой АЛУ, перемножение матриц

Третья часть "аффинного алгоритма", умножение матрицы 3x4 (из ПЗУ, посчитана заранее) на матрицу 4x2, составленную из наших 4 точек, обнаруженных на фотоприёмной матрице и правильным образом расставленных. Результатом, очевидно, становится матрица 3x2 аффинного преобразования: "матричная часть" 2х2, отвечающая за масштаб, крен и ракурсы, и "векторная" 1x2, отвечающая за параллельный перенос. Вот наш код:

;состояние регистров к этому моменту
;Y = Points2D
;X = Fx3
;Z = Fx2
;i=j=k=0
;Inv неизвестен
;C, Acc - неважно
Compute4PointAffine	proc
		X		AffineMat
		Z		Matrix ;вообще AfTransf, но это промежуточная матрица, её занесём сюда (пока не наступило сопровождение, эти ячейки напрочь не нужны!)
	;по сути, Z = X * Y, где все операнды - матрицы.
		k		1	;номер строки результата (и строки AffineMat)
@@k_loop:	j		2	;номер столбца результата (и столбца Points2D)
@@j_loop:	i		3	;номер столбца AffineMat и строки Points2D
		ZAcc		RoundZero	;обнулить до 1/2 мл. разр
@@i_loop:	C		[X+4j+i]
		FMA		[Y+2i+k]
		iLOOP		@@i_loop
	;очередной результат готов...
		[Z+2j+k]	Acc
		jLOOP		@@j_loop
		kLOOP		@@k_loop
;вот, какбе и всё...	
Compute4PointAffine	endp


Чувствую, что здесь новая АЛУ покажет свой потенциал!
Collapse )

С первого раза всё выполнилось правильно (результаты совпали до бита) и заняло 498 тактов вместо 607, или на 18% меньше! Это даже лучше, чем я ожидал.
[Spoiler (click to open)]Похоже, на старом АЛУ мы здесь собрали все "невзгоды" сброса конвейера: когда мы прыгали в начало цикла, первой начинала "в одиночку" выполняться команда SrcAddr: [X+4j+i], занимая 2 такта. Затем выполнялась [Y+2i+k], также 2 такта, задерживая команду "С" на лишний такт, и только потом начиналось умножение с накоплением. Только когда оно целиком заканчивалось, мы выполняли iLOOP, затем один такт уходил целиком "на помойку" (не выполнялась ни SrcAddr, ни DestAddr), итого на одну итерацию получалось 24 такта, а с новой АЛУ время сократилось до 18 тактов, т.е без учёта всех прочих "накладных расходов". Похоже, считать надо количество прыжков из внутреннего цикла, их число равно 3 * 3 * 2 = 18. В каждом из них мы сэкономим по 6 тактов (24 - 18), что даёт 108 тактов. И ещё один экономится на переносе "Z Matrix" внутрь цикла, итого 109. Как в аптеке!

Следующая на очереди: процедура выделения крена из этой матрицы аффинного преобразования.
QuatCore

Function inlining, QuatCore-style

Вчера к вечеру меня немного "перемкнуло", я не смог сосчитать до 7. Именно на столько отрезков оказалась поделена строка 11. Собственно, посчитать их легко: 2N+1, где N- количество обнаруженных "точек", в смысле пятен. Поэтому немудрено, что случилось переполнение буфера: из этих 7 точек мы одну сразу запросили, ещё 5 уложились в буфер, а последней ткнуться было уже некуда...

Так что в принципе всё вчера отработало правильно, но всё равно не так быстро, как мне хотелось бы. Всё-таки, именно этот код есть основания вылизывать до блеска, ведь он будет выполняться свыше тысячи раз за кадр, и как минимум 10 кадров в секунду, он обязан быть быстрым!

В голову пришли ещё способы, как повысить быстродействие, и наиболее дурацкий - запихнуть одну процедуру внутрь другой, чтобы при вызове хотя бы из одного места не нужно было в действительности куда-то прыгать!

Collapse )

Сейчас возьмёмся за 11-ю строку тестового изображения, и если с ней всё в порядке, то нужно будет уже по-быстренькому "промотать" до самого конца, и затем уже по содержанию памяти разобраться, что же у нас получилось в итоге.
QuatCore

QuatCoreImmTable - по-простому

Описанный вчера алгоритм пока не реализовал, больно он мудрёный, для начала "подготовил для него почву", т.е реализовал пока самый дурацкий алгоритм, и хочу убедиться, что весь код генерируется верно, хоть и не очень эффективно. А именно, сейчас генерится такой вот модуль:

//таблица непосредственных значений, сгенерированная под конкретный исполняемый код
module QuatCoreImmTable (input [7:0] SrcAddr, output [15:0] Q);
	wire[5:0] adr = SrcAddr[5:0];
	assign Q = 
		(adr==5'h00)?	16'h005D:
		(adr==5'h01)?	16'h0018:
		(adr==5'h02)?	16'h0000:
		(adr==5'h03)?	16'h0003:
		(adr==5'h04)?	16'h0001:
		(adr==5'h05)?	16'h0019:
		(adr==5'h06)?	16'h0017:
		(adr==5'h07)?	16'h00F0:
		(adr==5'h08)?	16'h001A:
		(adr==5'h09)?	16'h001C:
		(adr==5'h0A)?	16'h001E:
		(adr==5'h0B)?	16'h0031:
		(adr==5'h0C)?	16'h00C7:
		(adr==5'h0D)?	16'h2B02:
		(adr==5'h0E)?	16'h0016:
		(adr==5'h0F)?	16'h00F2:
		(adr==5'h10)?	16'h4000:
		(adr==5'h11)?	16'h00CD:
		(adr==5'h12)?	16'h0013:
		(adr==5'h13)?	16'h54FE:
		(adr==5'h14)?	16'hAA80:
		(adr==5'h15)?	16'h00EC:
		(adr==5'h16)?	16'h00CB:
		(adr==5'h17)?	16'h00EE:
		(adr==5'h18)?	16'h001B:
				16'hxxxx;
endmodule


То есть, мы просто выкладываем значения одно за другим, вместо того, чтобы долго и упорно придумывать их расположение в доступных 128 ячейках. Хорошо хоть, программе хватает ума понять, что из 7 бит SrcAddr, "отданных" под непосредственные значения, достаточно взять 5, что несколько снизит расходы логических элементов.

И как ни странно, такое безобразие синтезируется в 32 ЛЭ - не шибко хорошо, но и не шибко плохо, жить можно...

Collapse )

Какая-то прямо Agile-методология получается - ломать, но аккуратно, чтобы в течение недели восстановить функционирование, но уже с дополнительными "пряниками" :)

Но сразу возникает желание отложить экономию 40 ЛЭ на потом, как бы запомнить, что "здесь можно сделать существенно лучше", а пока взяться за программу обработки видео в реальном времени.

Вообще, даже сейчас обмен вышел довольно выгодный: мы поменяли 10 слов кода и данных = 160 бит, на лишние 31 ЛЭ (до всей эпопеи с переделкой счётчика инструкций и таблицы непосредственных команд, ядро занимало 519 ЛЭ, а сейчас 550). Но можно-то вообще шикарно, и даже понятно, как :)
Poll #2103007 Суперский алгоритм составления таблицы QuatCoreImmTable

Стоит ли его реализовывать прямо сейчас?

Да
0(0.0%)
Нет
0(0.0%)
QuatCore

Ввод-вывод для быстрого QuatCore

Первый QuatCore у нас работал на 4 МГц (на ПЛИС 5576ХС4Т) - достаточно для выполнения целевой задачи, но страшно неудобно его соединять с видеообработчиком, который просто обязан иметь тактовую частоту хотя бы 25 МГц, иначе просто не будет успевать получать пиксели из видеопотока "высокой чёткости".

В итоге мы его успешно ускорили до 25 МГц, после чего доработали компилятор, чтобы он автоматически обнаруживал так называемые Hazard'ы (конфликт соседних команд, из-за того, что они на самом деле выполняются "параллельно", одна записывает данные, а другая считывает "устаревшие") и исправлял их. Кроме того, доработали программу алгоритма захвата ("аффинного алгоритма"), чтобы она хорошо работала на этом процессоре. Как результат, она сократилась в размере (благодаря возможности пересылок "память-память" и упрощения кода в нескольких местах) и ускорилась в 5,5 раз.

Дальше я зачем-то попробовал поставить двунаправленную шину, и оно получилось ещё лучше (в теории. Для такого варианта надо было бы опять всю логику конвейера перекраивать, и Hazard'ы впридачу, поэтому провести симуляцию и получить конкретные результаты - это очень муторно), но сейчас я решил: остановлюсь на том, что есть. В дальнейшем, будет смысл ещё сильнее ускориться, до 50..80 МГц, для того, чтобы максимально быстро получать картинку с фотоприёмной матрицы 1205ХВ014. В этом один-единственный смысл: убрать эффект Rolling Shutter, насколько это вообще возможно. А "укороченный конвейер" на 25 МГц - это в любом случае полумера, нам главное, чтобы в принципе на 25 МГц работало, а выигрыш в несколько микросекунд за счёт более совершенной архитектуры совсем неинтересен (хотя всё равно червь грызёт, это уже психиатрическое).

Так что пока "фиксируем" имеющуюся архитектуру. Мы испытали "голое" ядро, теперь нужно присоединить к нему периферию: UART, SPI и контроллер ЖК-экранчика. Их нужно встроить в нашу "конвейерную логику", а именно, правильно прицепить к ним сигналы SrcDiscard, SrcStall, DestStall, SrcStallReq и DestStallReq...

А первым делом всё-таки оформим "ядро" QuatCore в отдельный модуль:


Collapse )

Самое время сдуть пыль с программы HelloWorld.asm и попробовать запустить, сначала на симуляторе, а потом и "в железе"...
QuatCore

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

Пора текстовые описания части 2 и части 3 "скомпилировать" в что-то похожее на принципиальную схему.

Это "фиктивная" схема, просто я понял, что "рисовать" в том же квартусе мне проще, чем в пэйнте, и даже на бумажке :)

Кстати, забавно, что квартус допускает модуль, не имеющий ни одного ВЫХОДНОГО пина (а значит, вещь в себе, никак не влияющая на схему в целом), и даже модуль ВООБЩЕ БЕЗ ПИНОВ! Спорим, вы ТАКОГО ещё не видели :)

Collapse )

Похоже на правду... Теперь нужно реальные модули подправить - и попробовать запустить на том же "аффинном алгоритме"...

UPD. Для модуля MemSrc есть существенная разница между входами stall и discard. Когда поступает stall=1, мы можем быть уверены, что когда он сбросится в ноль, это будет ТА ЖЕ САМАЯ КОМАНДА, поскольку конвейер был остановлен. А значит, те данные, что мы приготовили - те, что нужно. Если же discard=1, то да, мы имеем право подавать на выход всё что захотим. Но главное, когда снова будет discard=0 - мы не можем быть уверены, что прочитали ровно то, что от нас просили, поэтому нужно будет снова установить stall_req=1 и выдать данные только на следующем такте.

Именно поэтому мы не стали эти сигналы объединять по ИЛИ, как сделали на стороне DestAddr.

Да, мозги продолжают плавиться.
QuatCore

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

Лучше день потерять, а потом за 500 мкс долететь!

Увы, перечисления из прошлой части по-прежнему недостаточно, "циклические ссылки", или точнее, комбинаторные обратные связи (combinatorial loops) по-прежнему зловеще нависли над этим проектом. Скажем, пришли две очередные половинки команды, одна в DestAddr, другая в SrcAddr. В одной, DestAddr, лежит FMA - Fused Multiply-Add - умножение с накоплением, и запрашивает остановить конвейер на ближайшие 17 тактов. А в другой, SrcAddr, лежит CALL0 - вызов "нулевой" процедуры (у нас их может быть 16 штук, от 0 до 15, лежат в таблице QuatCoreCallTable.v), которая "хочет" поскорее перепрыгнуть на новое место, а последующую команду забраковать.

Или ещё интереснее - в DestAddr лежит прыжок, а в SrcAddr - чтение из памяти. Первый хочет объявить второго недействительным, а второй первого - остановить на один такт, пока правильные данные не придут.

Вот из-за таких хитростей у меня "самовозбуд" начался, пора разобраться. Всё не так сложно, как кажется.

Collapse )

Фух, что-то начинает проясняться, но мозги плавятся...
QuatCore

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

Начинается самое интересное - организация слаженной работы процессора "в конвейере".

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

Всё интересное происходит, если на какой-то из трёх ступеней случается "неполадка". На этапе выборки команды может оказаться, что был выполнен прыжок, и только что поступившие SrcAddr и ждущие своего часа DestAddr нужно ВЫКИНУТЬ, ни в коем случае не выполняя!

На этапе выборки данных может возникнуть заминка - либо мы читаем из памяти, а не из регистра, и нужно лишний такт подождать. А может, мы вообще читаем из SPI или UART, и тогда ожидание может затянуться и подольше (мы бы не стали СПЕЦИАЛЬНО усложнять логику процессора ради SPI/UART, если бы, к примеру, выборка ВСЕГДА занимала один такт, но раз уж есть исключение - теперь их будет 3).

И наконец, может затянуться запись/обработка данных, от 3 до 18 тактов в случае АЛУ, и даже больше в случае устройств ввода-вывода, тех же SPI, UART и LCD.

Поехали...

Collapse )

Вроде разобрались. Теперь осталось весь этот текст "оттранслировать" в код или схемы и надеяться, что комбинаторных обратных связей и самозаблокирования не возникнет. Когда я это сделал "по наитию", тут же возникло и одно, и другое :)
QuatCore

QuatCore начинает фурычить (финал!)

Часть 1, часть 2, часть 3, часть 4

Пора заканчивать этот Hello, World, точнее, FindMDD3 - программу для нахождения самой отдалённой точки из четырёх. Мы уже видим, что она считает нечто осмысленное - находит суммы квадратов длин от каждой точки до всех остальных, и записывает индекс точки, для которой эта сумма максимальна. Проверим, правильно ли завершается работа?

Collapse )

Музей Горьковского автозавода

Боюсь, что не буду излишне оригинальным - в отличие от технического музея и музея Нижегородской радиолаборатории, про музей ГАЗ уже есть много обзоров с хорошими фотографиями и описанием каждого экспоната. Дело осложняется еще и тем, что мы шли без экскурсовода - заявились вдвоем в понедельник, ровно к открытию музея (по выходным он не работает) и некоторое время бродили в гордом одиночестве.

DSC_0872.JPG
Тем не менее, почему бы не выложить, машинки красивые.

Collapse )

Оренбургские ветряки

С 29 января по 3 февраля по приглашению уважаемого al_kolesnikov я провел в Оренбурге, а также в поселках Каргала и Тюльган, где собирают ветряные электростанции, высоковольтные размыкатели и другие интересные штуки. Впечатлений масса, фотографий тоже довольно много.

(Щелкните для увеличения, 24Мп)


Collapse )