nabbla (nabbla1) wrote,
nabbla
nabbla1

Categories:

QuatCore+GPU: всё чудесатее и чудесатее

Вот на чём мы пока остановились:


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

Тем временем, идёт обратный ход, после которого начнётся следующая строка.

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



Прямоугольниками выделены записи о точках. Начиная с Elem0 начинаются "нормальные", не перекрывающиеся записи о реально обнаруженных точках. Сначала все эти записи были объединены в список Heap ("свободные ячейки"), но при добавлении первой точки мы отцепили нулевой элемент от Heap и прицепили к списку ActivePoints. Этот список начинается с "левой фиктивной точки", следующей пока идёт обнаруженная точка (Elem0), которая в свою очередь ссылается на "правую фиктивную точку", а она уже ведёт "в пустоту", она же Nil, она же Null.

Повторим последний скриншот с предыдущего поста:
6thBlin0.png

Столбец PC=0x1E, SrcAddr=0xB6, CALL(ListOp), DestAddr=0xDD, Y. Как видим, в регистр Y заносится 0x8C13, но поскольку регистр 8-битный (и компилятор об этом знает), попадает только 0x13, т.е адрес Heap. И здесь же мы вызываем процедуру ListOp.

Столбец PC=0x3F, SrcAddr=0xED, Z, DestAddr=0xF3, [SP++]. В стек отправляется адрес возврата, 0x1E, на шину данных тащим значение регистра Z, хоть оно и не нужно никому на самом деле.

Столбец PC=0x40, SrcAddr=0xD8, [Y+k], DestAddr=0xCD, X. В регистр X ничего не заносим (эта команда "лишняя", шла следом за вызовом процедуры), а вот выборку [Y+k] делаем, это то, куда ссылается Heap, 0x1A.

С одной стороны, это очень плохо, что аккумулятор требует 3 такта на "короткие" арифметические команды, лучше бы ему было обходиться в одну, зато на "осциллограмме" они сразу видны невооружённым глазом, и можно коды не сверять! Вот сейчас видим, что [Y+k] заносится в аккумулятор, и тут же в регистр Z, из-за чего получилась дополнительная задержка на такт (борьба с RAW Hazard). Затем мы вычитаем нолик, чтобы узнать знак нашего "указателя". Переправляем указатель Heap, теперь он указывает не на Elem1 (0x1A), а на следующий за ним Elem2 (0x1F). Потом убеждаемся, что указатель Heap не был Nil/Null (в противном случае прыгнули бы в OutOfMem).

Переправляем указатель с Elem1 - раньше он шёл на Elem2, а теперь будет идти на 0x12, туда же, куда ссылался Elem0. Наконец, переправляем ссылку с Elem0 - теперь она указывает на 0x1A.

Затем идёт NOP, и наконец возвращение из процедуры. SrcAddr=0xFF - это [--SP], он выдаёт на шину данных 0x1E, наш адрес возврата. А DestAddr=0xB0 - это прыжок по этому адресу.

В результате всего этого мы получаем такую картину в памяти:


Всё хорошо. Поехали дальше:


Исполняются вот эти команды:
1E  CDED                          X       Z       ;для дальнейшей работы (после MidCycle)... Чтобы мы не взялись за только что добавленную точку!
1F  E083                          [Z+1]       C       ;гориз. размер точки (будет расти при слияниях, а поначалу D, т.к мы сюда включаем и )                                                                                                                      
20  F3B8                          CALL        AcqQueue
21  B063                          JMP     @@MidCycle  ;пропускаем слияние точек, 


Выставляем X=0x1A, т.е он указывает на только что добавленную точку. Диаметр этой новой точки задаём равным 6, нашему "расчётному", и вызываем процедуру AcqQueue:

    AcqQueue proc   
49  A023              i         2   ;даже если не нужно обновить точку, не навредит... (это мы Hazard убираем мучительно!)
4A  B841              JL        @@queue
4B  C6F4  @@updCycle: [X+2j+i]  [SP+i]
4C  A85F              iLOOP     @@updCycle
4D  80CA  @@queue:    Acc       [X+2j+k]
4E  8FC0              DIV2S     [X+1]   ;теперь в аккмуляторе у нас X - Ceil(D/2)
4F  2080              ACQ       Acc     ;первый отрезок
50  82C0              ADD       [X+1]   ;а вот теперь X + Floor(D/2)
51  2080              ACQ       Acc     ;второй отрезок
52  B0FF              JMP       [--SP]
    AcqQueue endp 


Инициализируем поля только что добавленной точки: яркость 0x6BD, Y-координата 0x07, X-координата 0x15.

В аккумулятор заносим X-координату, 0x15, вычитаем половинку диаметра, 6, получаем:


Получаем 0x12 (логично), её и отправляем новым заданием в GPU. В нём продолжает "обрабатываться" синхроимпульс, затем два задания мы уже положили (0x0B и 0x11), вот и ещё одно появилось, на 0x12. Получается, мы ИНДИВИДУАЛЬНО один-единственный пиксель проверяем, затесавшийся между двумя точками :) Но в этом есть смысл: вдруг и он выше порога обнаружения, это будет означать, что это не две разные точки, а одно гигантское пятно!

Наконец, прибавляем к аккумулятору диаметр точки, 6, и получаем 0x18. Отправляем это значение как очередное задание в GPU - и застреваем всерьёз и надолго - буфер заполнен, ждём, пока освободится!

Ожидание занимает 74 такта. И когда видеопроцессор закончил с синхроимпульсом и начал обрабатывать новую строку, мы наконец-то кладём туда 0x18 - и сдвигаемся с мёртвой точки:



Выполняется вот этот код:
21  B063                          JMP     @@MidCycle  ;пропускаем слияние точек, 
22  E0FC              @@DoMerge:      [Z+1]       [SP]    ;пока так           
23  DDCD              @@MidCycle:     Y       X
24  CDC8                          X       [X+k]                               
25  8000                          Acc     0
26  83C8                          SUB     [X+k]
27  BC39                          JGE     @@FinalRange    ;наткнулись на NULL, значит, все точки обработали...                        


Идёт долгая чехарда (в прямом смысле - перепрыгивание через одинокую метку @@DoMerge), после чего мы продвигаемся по списку ярких точек на одну позицию. Мы сидели в 0x1A только что добавленной, сохраняем её в регистр Y (может пригодиться, если решим следующую точку удалить!), а сами "переходим по ссылке" на 0x12 (это наша "правая фиктивная точка"). Потом мучительно проверяем на NULL ссылку с неё, и действительно, это NULL, а значит, мы наконец-то обработали все точки на этой строке!

С чувством выполненного долга переходим на @@FinalRange:

37  207C          @@FinalRange:   ACQ     WholeRow
38  203A                          ACQ     HSync
39  80FE                          Acc     [SP+2j]
3A  830A                          SUB     ImgHeight
3B  824A                          ADD     ImgHeightP1
3C  B818                          JL      @@newRow


И когда мы выдаём ACQ WholeRow, видеопроцессор уже успел обработать строку сначала до 0x0B, затем до 0x11, затем и до 0x12, и у него "в загашнике" осталось последнее задание - обработать до 0x18. Как герои боевиков, мы успели "озадачить" GPU где-то за 6 тактов "до взрыва"!

Это также означает, что выходной буфер уже почти заполнился - там лежит 3 значения, и осталось последнее вакантное место.

Затем мы положили задание "дождаться синхроимпульса" - и начали обновлять Y-координату. Загрузили в аккумулятор текущее значение, 7, вычитаем 0x28, чтобы понять - не пора ли закругляться?

У нас проскакивает "комбинаторный выброс" на проводе OFLO (переполнение выходного буфера), это нам предостережение: GPU уже обработал отрезок до 0x18 и заполнил буфер до конца. "Выброс" возник из-за того, что запрос на запись в буфер, wrreq, сбросился в ноль чуть позже, чем заполнился буфер, и некоторое время они сформировали "единичку".

Смотрим дальше:


Теперь мы прибавляем 0x29, прыгаем в начало цикла по строкам, как всегда "отходняк" от прыжка - старые команды из конвейера выкидываются, и в этот самый момент приходит сигнал OFLO!. Он никак не завязан на QuatCore, он приходит НАМ, как символ толстой полярной лисички. Это реально ПЕСЕЦ: выдачу видеосигнала приостановить нельзя, GPU ОБЯЗАН сбросить свои сумматоры и продолжить обработку нового отрезка, а результаты старого тупо ПРОПАЛИ. Там не было ничего интересного, нулевая яркость и нулевая координата, но количество элементов уже нарушено. Когда мы попытаемся прочитать этот элемент - мы застрянем на пустом буфере, и всё пойдёт насмарку.

Самое обидное, до команды GPUH, которая бы освободила одну ячейку в буфере, мы не дотянули ВСЕГО 3 ТАКТА!

Тем не менее, завершим разбор этого скриншота. Когда мы оправились от прыжка, мы положили новую Y-координату, 8, куда надо, затем поставили X в начало нашего списка ярких точек, т.е 0x10.

Только теперь мы наконец-то прочитали яркость первого отрезка новой строки, 0xFFF, и координату этой "столь яркой точки", 0x00. Поняв, что здесь ловить нечего, мы сразу прыгнули в @@MidCycle и там в списке обнаруженных точек с "левой фиктивной точки" переместились в первую реально обнаруженную точку, и начали проверять, а может это уже "правая фиктивная" точка?


В целом, происходящее весьма осмысленно. Мы увидели "в работе" все строчки кода, кроме 9 (из 83), то есть "покрытие" составило 89%.

Сейчас нарастим длину выходного буфера GPU - и можно продолжить.

PS. Считаю абсолютно верным решением "разогнать" QuatCore до 25 МГц. Когда я понял, что всё взаимодействие будет идти через FIFO - немного взгрустнул: они вполне могли бы обеспечить Crossing clock domains, когда GPU работал бы на 25 МГц, а QuatCore на 4..6 МГц. Но сейчас вижу: он даже на 25 МГц на удивление долго ковыряется, а на 6 МГц, да ещё с цепями "защиты от метастабильности" (они нужны, когда работаем с несколькими тактовыми частотами) - совсем абзац...

Так что месяц, ушедший на "разгон", ушёл не зря.
Tags: ПЛИС, программки, работа, странные девайсы
Subscribe

  • Лестница для самых жадных

    В эти выходные побывал на даче, после 3-недельной "самоизоляции". Забавно, как будто зима началась! Особенно грязные галоши остались на улице, в…

  • Возвращаемся к макету

    Очень давно макетом видеоизмерителя параметров сближения не занимался: сначала "громко думал" по поводу измерения его положения на аппарате, а потом…

  • Минутка живописи

    В процессе разгребания содержимого квартиры (после нескольких ремонтов) дошёл, наконец, и до картин. В кои-то веки их повесил. Куда их вешать -…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 0 comments