nabbla (nabbla1) wrote,
nabbla
nabbla1

Category:

Алгоритм обнаружения на симуляции (4)

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

Оказалось: нет, явно что-то не то!



Обведённое сверху число - номер строки изображения, согласно программе. Снизу - согласно генератору тестового изображения. Они отличаются на 3, и это нехорошо: они вообще-то должны совпадать, но мы с количеством верхних строчек чуть запутались, поэтому у нас с самого начала было расхождение на 2:



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

Надо в этом разобраться...


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

Да, сразу в нескольких местах вроде как "успешной работы" оказывалось, что заказывался пустой отрезок:


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

А ведь действительно, несколько вариантов пустого множества у нас допускалось и в видеопроцессоре. Мы могли дать координату X меньше нуля - и тогда сразу же срабатывал "критерий окончания", что тогда давало нам минимально возможную яркость (0) и X-координату 0 в результатах выполнения. Также мы могли дать X свыше 1023, и получить результат ровно по окончании строки (хоть она сама больше 1023 никогда не станет).

Сейчас, когда мы попытались привести GPU в соответствие с "высокоуровневой моделью" и убрали сброс как таковой (вместо этого заносится яркость и координата текущего пикселя), всё это может нам аукнуться ещё как!

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

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

Тем не менее, я влез в код QuatCoreFullGPUinput.v, и заменил условие "==" на "<=", чтобы заказ пустого множества не приводил к сдвижке на целую строку. Как ни странно, модуль не шибко из-за этого раздулся, весь тестовый проект целиком (QuatCore+GPU+генератор тестового изображения) занимал 1340 ЛЭ (после Place&Route) и имел максимально допустимую частоту 28,74 МГц, а стал занимать 1346 ЛЭ, а частота уменьшилась до 26,81 МГц, но это терпимо.

В первую очередь такое изменение влияет на отработку кадрового синхроимпульса. Он у нас приходит с некоторым запозданием относительно строчного (требуется время "проинтегрировать" более длительную просадку, чтобы убедиться - это именно он!), и ранее мы дожидались следующего строчного синхроимпульса, чтобы он сбросил счётчик пикселей, и наконец условие завершения задания (пришёл кадровый импульс и счётчик равен нулю) было выполнено. Сейчас, подозреваю, условие выполнится сразу (счётчик БОЛЬШЕ ИЛИ РАВЕН НУЛЮ), поэтому нас ждёт сдвижка на одну строку:



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


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

Чтобы найти её, ввёл ещё такой "отладочный регистр", DEmptySet ("пустое множество"):

always @(posedge clk)
	DEmptySet <= (BufOut[XregWidth-1:0] < X)&(~SyncOut[0])&(~SyncOut[1])&(~FrontPorch)? 1'b1 : DEmptySet;


Это по сути RS-триггер, поначалу он сброшен, но устанавливается в 1, если заказывается пустой отрезок, то есть к примеру предыдущий был до координаты 100, а этот - до координаты 90, поэтому в старой реализации с "==" мы пропускали целую строку, чтобы счётчик обнулился по синхроимпульсу и снова досчитал до 90 (попав на "полочку" после синхроимпульса), а в новой сразу же переходим к новому отрезку.

Непостижимым образом при синтезе число ЛЭ не увеличилось, а допустимая частота даже поднялась. И запускаем симуляцию...

И обнаруживаем, что случилось. Опять Quartus показал своё пренебрежение к инициализации. Не понял до конца его логики, но иногда, если он видит, что любое воздействие устанавливает регистр в единицу, он выкидывает этот регистр нафиг, даже если мы предполагали, что он инициализируется нулём и будет оставаться нулём некоторое время, пока какое-то событие не произойдёт. Ладно, сделаем чуть по-другому:

always @(posedge clk)
	DEmptySet <= Vsync? 1'b0 : (BufOut[XregWidth-1:0] < X)&(~SyncOut[0])&(~SyncOut[1])&(~FrontPorch)? 1'b1 : DEmptySet;


Да, в этот раз добавилось приличное количество ЛЭ. И запущу-ка я для разнообразия Functional simulation, вместо привычного Timing simulation. Предварительно нужно вызвать Processing - Generate Functional Simulation Netlist, а в Settings - Simulation Settings - Simulation Mode выбрать Functional вместо Timing.

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

И нам показали что-то нехорошее на строке 0x17 = 23. Если помните, мы "своим ходом", честно проверяя каждую команду, успели дойти лишь до строки 15. Что ж, рассмотрим строку 23 "от начала до конца":



И приведём листинг к этой части:
1F  FE80      @@newRow:         [SP+2j]     Acc
20  CD10                        X           ActivePoints    ;начинаем цикл по всем активным точкам
21  8A0A      @@ResetPending:   C           0           ;уж явно не 0x8000          
22  F288      @@ActPointsStart: [SP+2j+1]   GPUL            ;яркость самой яркой точки на отрезке
23  F08A                        [SP+1]      GPUH            ;соотв. координата
24  8011                        Acc         Threshold       ;поставили задом наперёд, чтобы избежать Hazard'а   
25  83F2                        SUB         [SP+2j+1]       ;вычитаем порог, положит. значение свидетельствует о новом найденном "пятне"                        
26  FC12                        [SP]        @@MidCycle
27  B813                        JL          TaskPending ;пятна нет, не торопимся заказывать отрезок
28  80CA                        Acc         [X+2j+k]
29  8E01                        DIV2A       D1
2A  8EC0                        DIV2A       [X+1]
2B  83F0                        SUB         [SP+1]
2C  EDC8                        Z           [X+k]


По первому отрезку получаем результаты: яркость 0 (т.е максимальная) и координата 0x25 = 37 - похоже на правду. Яркость сравнивается с порогом и признаётся большой, поэтому переход JL TaskPending не срабатывает.

Проверяется положение яркой точки относительно левого пятна, это "левое фиктивное пятно". Следующий слайд:


И листинг:
2C  EDC8                  Z       [X+k]
2D  BC14                  JGE     @@LeftMerge
2E  80EA                  Acc     [Z+2j+k]
2F  83F0                  SUB     [SP+1]
30  8F15                  DIV2S       D1p1
31  8FE0                  DIV2S       [Z+1]
32  B816                  JL      @@RightMerge
33  FC17                  [SP]        @@NewBlob
    TaskPending proc
34  8483          ABS     C


Тут обращаем внимание, что адрес правого пятна: 0xA8. Приведём листинг памяти (значения при инициализации):
ActivePoints:     09B  0x009D
AllPoints:        09C  0x8000
APHeadDummyX:     09D  0x8000
Heap:             09E  0x00A0
APTailDummyX:     09F  0x7FFF
Elem0Next:        0A0  0x00A4
Elem0D:           0A1  ????
Elem0X:           0A2  ????
Elem0Y:           0A3  ????
Elem1:            0A4  0x00A8
Elem1[1]:         0A5  ????
Elem1[2]:         0A6  ????
Elem1[3]:         0A7  ????
Elem2:            0A8  0x00AC
Elem2[1]:         0A9  ????
Elem2[2]:         0AA  ????
Elem2[3]:         0AB  ????
Elem3:            0AC  0x00B0
Elem3[1]:         0AD  ????
Elem3[2]:         0AE  ????
Elem3[3]:         0AF  ????


То есть, это уже третье пятно, судьба первых двух пока неизвестна.

Переход на @@LeftMerge не осуществляется, что логично, к фиктивным пятнам "ничего не прилипает". Поэтому проверяем теперь пятно справа. Это нам очень интересно, мы же всё пропустили :)

В аккумулятор заносится координата X центра пятна, 0x27 = 39, ну допустим. Вычитается текущая, 0x25 = 37, получается 2. Вычитается (D1+1)/2, то есть 2, получается 0. Вычитается половинка диаметра пятна, 3, выходит -1,5. Ага, пятно было совсем маленьким, только-только добавлено!

Условие JL @@RightMerge выполняется, приведём листинг:

64  82E0  @@RightMerge:   ADD         [Z+1]   ;в аккумуляторе лежало x-X-D/2-D1/2. Теперь прибавили D, получили x-X+D/2-D1/2
65  8E1D                  DIV2A       D1p2    ;прибавили D1/2, получили x-X+D/2 - новый диаметр пятна
66  E080                  [Z+1]       Acc     ;ага, записали.
67  8C80                  DIV2        Acc
68  82F0                  ADD         [SP+1]
69  8311                  SUB         1
6A  EA80                  [Z+2j+k]    Acc
6B  B013                  JMP         TaskPending


Прибавляется диаметр правого пятна, 3, что должно дать 1,5.
Прибавляется (D1+2)/2 = 2,5, что должно дать 4. Всё верно, эта 4 записывается как новый диаметр пятна, а затем делится пополам, давая 2.

Следующий слайд:


Прибавляем текущую координату, 0x25 = 37, что даёт 39. Вычитаем единичку, что должно дать 38. И заносим как новую X-координату пятна. Действительно, мы занесли значение 0x26 = 38. И прыгаем на TaskPending:

    TaskPending proc
34  8483          ABS     C
35  BEFC          JNO     [SP]        ;вот в чём прелесть стека без инкремента!
...


Быстро оказывается, что долга за нами нет, и мы выпрыгиваем на @@MidCycle:
71  CDC8  @@MidCycle:     X           [X+k]
72  DDCD                  Y           X
73  84C8                  ABS         [X+k]
74  BA0F                  JO          @@FinalRange
75  F288  @@EndOfCycle:   [SP+2j+1]   GPUL            ;яркость
76  F08A                  [SP+1]      GPUH            ;пока не забыли - сразу второе слово запрашиваем (коорд точки), чтобы не сбить весь FIFO 
77  8A0B                  C           Nil         ;по умолчанию ставим метку "за нами должок!"                
78  80C2                  Acc         [X+2j+1]
79  8EC0                  DIV2A       [X+1]
7A  83FA                  SUB         [SP+2j+k]
7B  BC1E                  JGE         @@ActPointsStart


Регистр X указывал на левое фиктивное пятно, теперь указывает на следующее пятно на строке, 0xA8, с ним мы уже знакомы :)
В регистр Y заносится старое значение X, 0x9B, если вдруг будем текущее пятно переносить в другой список. Проверяем на Nil указатель на следующее пятно, но там 0xA0, это ПЕРВОЕ обнаруженное пятно. Подробностей пока не знаем (будем наблюдать). Переполнение не происходит, поэтому получаем результаты по следующему отрезку:

яркость 0 (т.е максимальная), координата 0x26 = 38. Если помните, центр пятна был 0x27 = 39, пока мы его не передвинули, и диаметр 3. Поэтому мы должны были заказать отрезок как раз-таки [38;40], он весь равномерно "закрашен", и взята была левая его точка, 38. Пока всё логично.

Делаем "зарубку" в регистр C, что по этому пятну надо выдать задание на обработку, а сами разбираемся с координатой Y.

Загружаем в аккумулятор значение Y центра пятна, 0x15. Да, сейчас мы на строке 0x16, значит, пятно было добавлено на предыдущей строке, мы так и поняли. Следующий слайд:



Прибавляем половинку диаметра, причём он уже был увеличен до 4. Значит, должно получиться 21+4/2=23. Вычитаем текущий номер строки изображения, 0x16 = 22, получаем 1. Результат неотрицателен, поэтому прыгаем в @@ActPointsStart, т.е решили точку не удалять пока!

Тем временем текущая строка уже закончилась, прошёл синхроимпульс, а мы на новую строку ещё ни одного задания не выдали! Правда, пока ещё длится "полочка" между синхроимпульсом и началом полезной части строки, так что время пока есть, примерно 200 тактов...

А мы запрашиваем результаты по следующему отрезку, и получаем яркость 0 (то есть, максимум) и координату 0x29 = 41. И снова это логично, ведь прошлый отрезок был [38;40], и "закраска" продолжается.

Мы проверяем, не нужно ли провести слияние с пятном слева от нас. Берём его X-координату, 0x26 = 38. Прибавляем половинку от "минимального диаметра" D1, получаем 39,5. Прибавляем половинку от диаметра пятна, 4, получаем 41,5. Вычитаем текущую координату, 41, и получаем 0,5. Следующий слайд:


В регистр Z кладём адрес следующего пятна, 0xA0 (это по логике ПЕРВОЕ обнаруженное нами пятно), после чего срабатывает переход JGE @@LeftMerge. Приведём листинг:

48  80EA  @@LeftMerge:    Acc     [Z+2j+k]        ;взяли X-координату правого пятна
49  83CA                  SUB     [X+2j+k]        ;вычитаем X-координату левого
4A  8FE0                  DIV2S   [Z+1]           ;вычитаем радиус правого пятна
4B  8FC0                  DIV2S   [X+1]           ;и радиус левого пятна
4C  8315                  SUB     D1p1            ;и минимально допустимый диаметр пятна
4D  B804                  JL      @@MergeBlobs    ;делаем слияние ПЯТЕН


В аккумулятор заносим X-координату правого пятна, 0x2C = 44. Вычитаем координату левого, 0x26 = 38, получается 6. Вычитаем половинку диаметра правого пятна, 5, получается 3,5. Вычитаем половинку диаметра левого пятна, 4, получается 1,5. Наконец, вычитаем (D1+1), и получается -2,5. Переход JL @@MergeBlobs выполняется - начинаем слияние двух пятен:

58  80C0  @@MergeBlobs:   Acc         [X+1]
59  82E0                  ADD         [Z+1]
5A  C080                  [X+1]       Acc ;диаметр равен сумме диаметров
5B  8801                  ZAcc        RoundZero
5C  8ECA                  DIV2A       [X+2j+k]
5D  8EEA                  DIV2A       [Z+2j+k]
5E  CA80                  [X+2j+k]    Acc ;среднее арифметическое от центров точек
5F  DDCD                  Y           X
60  CD18                  X           Heap
61  FCB5                  [SP]        CALL(MergeBlobCase)
62  CDDD                  X           Y
63  B01C                  JMP         @@EndOfCycle


Начинаем с диаметров. 4 + 5 = 9 - это и становится новым диаметром пятна. Следующий слайд:


Да, 9 мы получили, всё верно. Далее, закладываем в аккумулятор 1/2. Добавляем половинку от X-координаты левого пятна, 0x26 = 38. И половинку от X-координаты правого пятна, 0x2C = 44. В итоге в аккумуляторе лежит 41,5. Но в память отправится только целая часть. И действительно, мы заносим 0x29 = 41. Всё верно.
Далее, в регистр Y заносится адрес левого пятна, который сейчас лежал в X, 0xA8. Затем в X заносится указатель на "кучу", 0x9E. И затем мы заносим в [SP] "адрес возврата", 0x62, и прыгаем в MergeBlobCase, это метка внутри ListOp:

    ListOp proc         
40  EDD8                  Z       [Y+k]
41  84D8                  ABS     [Y+k]
42  D8E8  MergeBlobCase:  [Y+k]   [Z+k]
43  BA1A                  JO      OutOfMemory                                 
44  E8C8                  [Z+k]   [X+k]
45  C8ED                  [X+k]   Z
46  8900                  NOP     0       ;избежать Memory hazard (одновременный доступ на чтение и запись)
47  B0FC                  JMP     [SP]


У нас Z указывает на правое пятно, то самое, которое мы сейчас должны удалить, т.е перенести в список Heap. Берём [Z+k]=[Z] - здесь лежит указатель на следующее пятно, 0xA4. Ага, это третье пятно, пока утечек памяти у нас нет :). И присваиваем [Y]=0xA4, т.е список пошёл "в обход" правого пятна. Верно.

Прыжок на OutOfMemory не срабатывает, и это логично, мы же освобождаем память, а не выделяем её :)

Далее, берём [X+k]=[X] - первый элемент в "куче", 0xAC. И заносим этот адрес в [Z+k]=[Z] - указатель на следующий элемент в "повисшем воздухе" правом пятне. Да, теперь он указывает на все свободные элементы.

И наконец, значение Z=0xA0 (адрес "правого пятна") помещается в [X], т.е в указатель на кучу (Heap), так что удаление элемента вроде бы успешно завершено.

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

Присваиваем X = Y = 0xA8, это "левое пятно", теперь уже "объединённое пятно", особенно отожранное!

И наконец, прыгаем в @@EndOfCycle. Следующий слайд:


Этот кусочек мы уже выполняли:
75  F288  @@EndOfCycle:   [SP+2j+1]   GPUL            ;яркость
76  F08A                  [SP+1]      GPUH            ;пока не забыли - сразу второе слово запрашиваем (коорд точки), чтобы не сбить весь FIFO 
77  8A0B                  C           Nil         ;по умолчанию ставим метку "за нами должок!"                
78  80C2                  Acc         [X+2j+1]
79  8EC0                  DIV2A       [X+1]
7A  83FA                  SUB         [SP+2j+k]
7B  BC1E                  JGE         @@ActPointsStart


Сейчас мы получаем отрезок, который располагался под "правым пятном". У него был диаметр 5 и координата центра: 44. Значит, мы должны были тогда заказать отрезок [42;46]. Смотрим, что за результаты мы получили: яркость 0 (самая высокая) и координата 0x2A = 42 - логично, левая кромка.

Заносим флажок в регистр C, после чего проверяем Y-координату.

Начинаем с Y-координаты центра пятна, 0x15 = 21. Прибавляем половинку диаметра, сейчас это уже 9, что даёт 25,5. Вычитаем текущий номер строки, 0x16=22, получаем 3,5 - неотрицательное число, поэтому прыгаем на @@ActPointsStart. Заметим, что до сих пор ни одного задания на обработку мы не отправили! И это правильно, так уж вышло, что мы к этому пятну сначала добавили кусочек слева, потом объединили его с пятном справа, а сейчас ещё посмотрим на отрезок справа, вдруг его надо ещё сильнее расширить!

Запрашиваем ещё один результат обработки: яркость 0 (самая большая), координата 0x2F = 47 - логично, сразу за отрезком [42;46]. Традиционно убеждаемся, что точка яркая, после чего определяем, стоит ли её добавить к пятну слева.

Заносим в аккумулятор X-координату пятна слева, 0x29 = 41. Прибавляем половинку D1, должны получить 42,5. Следующий слайд:


Прибавляем половинку от диаметра пятна, 9, должны получить 47. Вычитаем текущую координату, 47 - и должны получить 0, неотрицательное число, поэтому прыжок на @@LeftMerge срабатывает. Только перед этим заносим в Z адрес правого пятна, 0xA4.

Начало @@LeftMerge мы тоже уже проходили, там мы проверяем - а не пора ли объединить два пятна, слева и справа от только что обнаруженной точки? Загружаем в аккумулятор X-координату правого пятна, 0x3E = 62. Вычитаем X-координату левого, 0x29 = 41, должны получить 21. Вычитаем половинку диаметра правого пятна, 0x16 = 22 (нехило оно у нас отожралось!), должно выйти 10. Вычитаем половинку диаметра левого пятна, 9, должно выйти 5,5. Наконец, вычитаем (D1+1), и должно выйти 1,5 - неотрицательное число, поэтому переход JL @@MergeBlobs не срабатывает. Вместо этого, мы пока что уширяем пятно слева от нас, вот листинг:

4E  80F0                  Acc         [SP+1]  ;Acc = x, т.е координата обнаруженной точки
4F  83CA                  SUB         [X+2j+k]    ;вычитаем X, т.е Acc = x - X - разность между обнаруженной точкой и центром левого пятна
50  8EC0                  DIV2A       [X+1]       ;прибавили D/2, получаем Acc = x-X+D/2 
51  8211                  ADD         1
52  C080                  [X+1]       Acc     ;обновили диаметр левого пятна.
53  8011                  Acc         1
54  82F0                  ADD         [SP+1]  ;берём координату обнаруженной точки
55  8FC0                  DIV2S       [X+1]       ;вычитаем половинку диаметра
56  CA80                  [X+2j+k]    Acc
57  B01B                  JMP         AcqNoCheck  ;если слева от нас есть РЕАЛЬНОЕ пятно (фиктивное мы трогать не можем, его X-координата "-32768", условие заведомо не выполнится),


Помещаем в аккумулятор текущую координату, 0x2F = 47. Следующий слайд:


Вычитаем координату левого пятна, 0x29 = 41, что должно дать 6. Прибавляем половинку от диаметра пятна, 9, что должно дать 10,5. Затем прибавляем ещё единичку, что даст 11,5. Действительно, целая часть от этого числа, 0x0B = 11, становится новым диаметром пятна.

Далее, заносим в аккумулятор число 1. Добавляем текущую координату 0x2F = 47, что должно дать 48. Далее вычитаем половинку обновлённого диаметра, 0x0B = 11, что должно дать 42,5. Целая часть, 0x2A = 42, заносится как новая X-координата пятна. Выглядит неплохо.

И теперь прыгаем в AcqNoCheck - НАКОНЕЦ-ТО ПОСЫЛАЕМ ЗАДАНИЯ НА ОБРАБОТКУ! Приведём листинг:

    TaskPending proc
34  8483              ABS     C
35  BEFC              JNO     [SP]        ;вот в чём прелесть стека без инкремента!
36  80CA  AcqNoCheck: Acc     [X+2j+k]
37  8FC0              DIV2S   [X+1]   ;теперь в аккмуляторе у нас X - Ceil(D/2)
38  8E11              DIV2A   1   ;чтобы всё-таки было X-Floor(D/2)
39  2080              ACQ     Acc     ;первый отрезок
3A  82C0              ADD     [X+1]   ;а вот теперь X + Floor(D/2)
3B  2080              ACQ     Acc     ;второй отрезок
3C  B0FC              JMP     [SP]
    TaskPending endp    


Мы пропустили проверку, "есть ли за нами должок", поскольку точно знаем - есть! В аккумулятор заносим X-координату центра пятна, 0x2A. Кстати, мы могли бы сдвинуть метку AcqNoCheck на строку ниже, ведь в аккумуляторе ИМЕННО ЭТО ЗНАЧЕНИЕ ЛЕЖИТ И ТАК! Но ошибки как таковой я не вижу.
Вычитаем половинку диаметра точки, 0x0B = 11, что должно дать 36,5. Следующий слайд:


Прибавляем 1/2, что должно дать 37. И действительно, на GPU мы отправляем значение 0x25 = 37. То есть, первый отрезок: [0;36].

Прибавляем диаметр 11, что должно дать 48. И действительно, на GPU отправляется значение 0x30 = 48. То есть, второй отрезок: [37;47].

И мы выпрыгиваем в @@MidCycle, наконец-то разобравшись с этим оочень большим пятном :)

В регистр X заносится следующее пятно, 0xA4, а в регистр Y - старое значение, 0xA8. Берём абсолютное значение от следующего указателя, на 0x9D (это "правая фиктивная точка") - переполнения не происходит, поэтому в @@FinalRange мы не выпрыгиваем.

Запрашиваем ещё один отрезок, и получаем результаты: яркость 0 (максимальная) и координата 0x33 = 51. Действительно, сейчас у нас пятно с центром X=62 и диаметром 22, поэтому мы должны были запросить отрезок [51;72]. Да, именно левая кромка этого пятна, всё логично.

Ставим метку в регистр C, после чего начинаем проверять Y-координату. Загружаем Y-коорд. центра пятна, 0x10 = 16. Прибавляем половинку диаметра, 0x16 = 22, что должно дать 27. Вычитаем номер текущей строки, тоже ВНЕЗАПНО 0x16 = 22, что должно дать 5. Следующий слайд:


Результат неотрицательный, поэтому прыгаем в @@ActPointsStart.

Запрашиваем последний отрезок на этой длинной строке. Получаем яркость 0 (максимальная) и координата 0x49 = 73. И опять всё логично: предыдущий отрезок был [51;72], а это следующая точка. Проверяем яркость: подходит. Проверяем на слияние с пятном слева. Загружаем в аккумулятор X-координату левого пятна, 0x3E = 62. Прибавляем половинку D1, должно получиться 63,5. Прибавляем половинку диаметра, 22, должно получиться 74,5. Вычитаем текущую кординату, 73 - получаем 1,5, неотрицательное значение. В регистр Z заносим адрес правого пятна, 0x9D, это правое ФИКТИВНОЕ пятно. И прыгаем в @@LeftMerge. Следующий слайд:


Загружаем X-координату правого пятна, 0x7FFF = 32767 (это ФИКТИВНОЕ пятно, у него специально координата огроменная, чтобы к нему ничего не прилипало). Вычитаем координату левого пятна 0x3E = 62, должно выйти 32705. Вычитаем половинку диаметра правого пятна, 0xA0 = 10, должно выйти 32700. Вычитаем половинку диаметра левого пятна, 0x16 = 22, должно выйти 32694. Значение это неотрицательное (ВНЕЗАПНО), и переход JL @@MergeBlobs не выполняется.

Вместо этого просто расширяем левое пятно. В аккумулятор заносим текущую координату, 0x49 = 73. Вычитаем центр пятна слева, 0x3E = 62, должно получиться 11. Прибавляем половинку диаметра пятна, 0x16 = 22, должно получиться 22. Прибавляем ещё единичку, должно получиться 23. Это и есть обновлённый диаметр пятна, 0x17, заносим его в память. Кстати, у нас уже вовсю пошла следующая строка! Но пока ещё мы не отстали, мы уже выдали задания до 0x30, а строка дошла до 0x26. Оох, это рискованно!

Следующий слайд:


В аккумулятор заносим число 1, прибавляем текущую координату, 0x49 = 73, что должно дать 74. Вычитаем половинку только обновлённого диаметра, 0x17 = 23, что должно дать 62,5. Целая часть, 0x3E = 62, отправляется в память, как новая координата центра пятна.

Но мы уже опоздали: GPU уже выполнил все выданные ему задания, а что делать со следующим отрезком - не знает.

И это ни о чём особенно не говорит: мы возились с предельно укороченным кадром, где вместо 53 мкс на одну строку (как в 720p 25 fps), или 1325 тактов на 25 МГц, мы имеем 402 такта.

С другой стороны, у нас было на строке всего одно пятно (которое мы восприняли как 3). Если же посмотреть на большой кадр, там, если нам не повезёт, может таких пятен быть сразу 4, поэтому на самом деле и там мы можем наткнуться ровно на такую ситуацию!



Вот так вот, времени тупо не хватило!

Да, мы молодцы - сразу же начали с наихудшего случая - дистанции 0,5 метра, и такое "удачное" расположение мишени, что она влезает вся. В результате у нас самые крупные пятна, чуть ли не 120х120 пикселей, из-за чего и приходится довольно много пикселей "перемалывать" силами QuatCore.

Сейчас, наверное, на симуляции увеличу время back porch, чтобы посмотреть - "а если тактов хватает - то всё будет правильно". А там видно будет. Но сначала придётся вернуться к бумажкам...
Tags: ПЛИС, программки, работа, странные девайсы
Subscribe

  • Мышки плакали, кололись,

    но продолжали смотреть Доктора Кто... Что-то не то, всё-таки. Какая-то бессмыссленность происходящего, простые сюжеты. Расизм - это плохо, экология…

  • И ещё о 13-й докторе

    В воскресенье вышла первая серия, посмотрел это дело. Да в общем, нормально, вполне себе "Доктор Кто". Вот она, компаньон моей мечты - ЯЯЯЯЗЬ!…

  • Великая Октябрьская резня бензопилой

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

  • Очередная несуразность в единицах измерения

    Когда-то я написал программу PhysUnitCalc - калькулятор, умеющий работать с размерностями. Мне казалось, что я наступил уже на все грабли, которые…

  • Big Data, чтоб их... (3)

    "В предыдущих сериях": мой прибор выдаёт 6 значений: 3 координаты и 3 угла, т.е все 6 степеней свободы твёрдого тела. Причём ошибки измерения этих 6…

  • Покрышки с взрывным характером

    Продолжаю кататься на велосипеде на работу и назад, а также время от времени в РКК Энергию. С 17 мая (когда решил записывать, сколько проехал,…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 0 comments