nabbla (nabbla1) wrote,
nabbla
nabbla1

Categories:

QuatCore+GPU, строка 9 (дубль 2)

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

Увы, уже по началу строки 10 было видно, что "что-то пошло не так" ещё ранее. Проматывая назад, удалось найти серьёзную проблему: почему-то координата X первой обнаруженной точки в какой-то момент стала нулевой, и мы не мудрствуя лукаво заказали первый отрезок до координаты "-3" (и кстати, видеопроцессор на это и глазом не моргнул - в первый же такт выдал нулевую координату и нулевую яркость, хоть аппаратную часть проверили, и то радость), затем до координаты "3".

Наконец, наблюдая начало 9-й строки, мы увидели, что на ней ещё всё было правильно, первый отрезок шёл до 0x0B. Так что сейчас придётся ещё раз внимательно изучить, что там творится...




Пока всё хорошо: мы дожидались отправки последних двух заданий на 9-ю строку, одно отправили только когда началась её полезная область, а второе - когда уже был обработан отрезок до 0x0B. В этот момент мы проверили, что номер строки (8) ещё маленький, заканчивать работу рано, прибавили единицу, прыгнули в начало цикла, занесли обновлённый номер строки (9) в [SP+2], после чего запросили GPU. Получили ответ: яркость 0xFFF (нулевая), координата 0. Загрузили в аккумулятор пороговое значение яркости, вычитаем текущее:


По результату, прыгнули на @@MidCycle, т.е добавлять новую точку не пожелали (её там и нет). В регистр Y положили адрес текущей точки в списке, 0x10, а X "прыгнул" на следующую точку, 0x15.

Теперь пошла команда "ZAcc RoundZero". Сначала SrcAddr = 0x79 - непосредственное значение, которое превратилось в 0xCC0F. И затем, DestAddr = 0x88 (ZAcc). Ей нужны только младшие два бита, они здесь единичные, как и должно быть.

Затем вычитается адрес точки, следующей за 0x15, а именно 0x1A. Ответ получается отрицательным, и условный переход JGE @@FinalRange не происходит. Взамен мы опрашиваем GPU: яркость 0xFFF, то есть нулевая. Эта яркость заносится в [SP+3] и ещё в аккумулятор. Затем из неё вычитается яркость точки, обнаруженная ранее: 0xCF4. И тут уже видна проблема: вообще-то самая большая яркость у нас была 0x39A, тогда как 0xCF4 - это яркость с предыдущей строки, когда она уже пошла на спад! Похоже, вот-вот разберёмся, в чём дело, и ошибка окажется пустяковой.

Запрашиваем у GPU координату точки, получаем 0, записываем его в [SP+1]. Почему бы и нет...
После этого должен произойти прыжок на @@NotSoBright, ведь мы из 0xFFF вычли 0xCF4, результат положительный, поэтому JGE должен сработать. Но это тот самый "скрытный" прыжок на 1 позицию (для PC), который без отображения SrcDiscard/DestDiscard не так-то просто заметить!

Видим SrcAddr = 0x19, который превращается в 0xC00C - если взять младшие 7 бит, это в точности метка @@ActPointsStart - её мы хотели занести в стек в качестве адреса возврата.
И наконец, идёт вызов процедуры, но раз мы не прыгнули, значит он не сработал :) Значит, всё-таки прыгнули куда хотели!

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


Прибавляем половинку диаметра (6), вычитаем номер текущей строки (9), и должны получить +2. И действительно, JGE @@QueueAnyway срабатывает. Пока что всё правильно. Заносим-таки в стек "адрес возврата" 0xC00C (целиком, слова в памяти у нас 16-битные!) - и прыгаем в процедуру AcqQueue.

А там, внезапно, JGE не срабатывает!

Чудеса, да и только. Что-то поменяло нам флаг знака. Вот код, который управляет его "защёлкиванием" внутри AluControl:
assign Sena = isOurAddr & DestAddr[0] | isMult;


Выходит, что знак меняется, если команда "принадлежит" АЛУ, и если её последний бит единичный. Ещё раз взглянем на "периодическую таблицу" АЛУ (все её команды):


Единичный последний бит имеют все команды из правых двух столбцов (а за счёт isMult - и все длинные команды тоже выставят знак!), и В ИХ ЧИСЛО ВХОДИТ NOP!!!
Да, NOP, как это ни странно, принадлежит к АЛУ, он "сам собой сложился" - выходило, что и аккумулятор он не обнуляет, и в регистр C ничего не заносит, и "конечный автомат" не запускает - как будто бы всё тихо и мирно. А вот про изменение флага S (Sign) как-то и забыл совсем, и до сих пор всё работало правильно. В сущности, это было везение, и перфекционизм, который заставлял меня так переиначить код, чтобы команд NOP там было 1-2 на всю программу. Поэтому вероятность, что NOP попадёт между выставлением знака и его проверкой с помощью JL/JGE, была практически нулевой.

Чтобы это случилось, нужно было "придумать", что знак станет одним из "аргументов" процедуры AcqQueue, из-за чего растянется цепочка команд между его выставлением и проверкой, а потом ещё и наворотить дел со стеком, что потребовало "выкинуть" адрес возврата PC (вместо него подставить другой), для чего мы и подсунули команду NOP:
NOP CALL(AcqQueue)


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

Кстати, пока рылся, нашёл ещё один небольшой баг, в простеньком модуле FastQuatCoreEnableBypass:
module FastQuatCoreEnableBypass (input clk, input [7:0] SrcAddr, output reg Q = 1'b0);

always @(posedge clk)
	Q <= (SrcAddr[7:4] == 4'b1000); //source is within ALU as well

endmodule


Этот код слегка устарел, ведь мы "отожрали" у адресного пространства АЛУ в SrcAddr ещё одну строку (это теперь GPUL, GPUH, GPUPL и GPUPH), оставив ему лишь адреса 0x80..0x87.

Из-за того, что этот модулёк всё ещё считает, что адреса 0x88..0x8F принадлежат ему, если мы напишем что-то наподобие

Acc GPUL


В 32-битный аккумулятор старшие 16 бит придут откуда надо, из GPU, но вот младшие не обнулятся! Для текущих дел, где мы только складываем и вычитаем, это вообще несущественно, но когда-нибудь могло бы сыграть злую шутку.

Давайте его подправим:
module FastQuatCoreEnableBypass (input clk, input [7:0] SrcAddr, output reg Q = 1'b0);

always @(posedge clk)
	Q <= (SrcAddr[7:3] == 5'b1000_0); //source is within ALU as well

endmodule


Ну и самое главное, нужно NOP сделать действительно "пустой" командой:
assign Sena = isOurAddr & DestAddr[0] & DestAddr[1] | isMult;


Так у нас из "коротких" команд, меняющих знак, останется только SUB, DIV2S (вычитание значения, делёного на два) и ABSS (вычитание модуля от значения), которое я ни разу ещё не применил, штука всё же специфическая...

При синтезе число ЛЭ не увеличилось, что не может не радовать.

Посмотрим на результаты симуляции, с того же места:


В этот раз мы действительно "перепрыгнули" через цикл по обновлению параметров точки в списке.
Затем мы загрузили X-координату этой точки, 0x0E, вычитаем половинку диаметра, получается 0x0B - даём это как задание в GPU. Затем прибавляем целый диаметр, выходит 0x11 - и это идёт вторым заданием. Наконец, мы возвращаемся процедуры аккурат на метку @@ActPointsStart, т.е наша идея со стеком сработала!


Нда, "побочный эффект" команды NOP - надо ж было умудриться такое сделать! Но вроде отремонтировали, причём безболезненно, без увеличения ЛЭ или изменения кода.

Теперь надо вернуться к строке 10...
Tags: ПЛИС, программки, работа, странные девайсы
Subscribe

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 0 comments