nabbla (nabbla1) wrote,
nabbla
nabbla1

Category:

Сказка про белого бычка

Хотел сейчас "для порядку" быстренько прогнать на симуляторе программу нахождения максимально отдалённой точки (ту, что скомпилирована новым транслятором, да ещё и автоматически вставлены NOPы для устранения Hazard'ов), в полной уверенности, что всё сработает как надо. Увы, опять чего-то не то - застревает намертво в 3 вложенных циклах, хотя не должен. Придётся выяснять...

Проблема ещё и в том, что когда переделывал транслятор, решил "заодно" и систему команд слегка причесать, поменял местами один бит в командах JMP и JL/JGE/JO/JNO, чтобы упростить декодирование (тогда, чтобы принять решение, нужно ли нам условный переход по отн. адресу или безусловный по абсолютному, нужно одним битом меньше. Правда, к уменьшению ЛЭ это не привело, но то дело такое), и новый вариант не проверял пока. Может, именно там собака и зарыта.




Листинг программы:
main proc
00  FD47          SP      StackAdr
01  8900      NOP  0 ;AUTOMATICALLY INSERTED BY TRANSLATOR TO PREVENT HAZARD
02  FDFC          SP      [SP]        ;тут должен быть HAZARD (чтение по адресу из SP, который ещё не записался)
03  F3B0          CALL        AffineAlgorithm
04  B004  @@endless:  JMP     @@endless
main endp
AffineAlgorithm     proc
    Associate4Points    proc
        FindMDD3    proc
05  DD18                  Y       Points2D
06  F000                  [SP+1]  0   ;максимальная отдалённость, инициализируем нулём
07  A103                  j       3
08  A201          @@j_loop:   k       1   ;также от 0 до 3, чтобы все расстояния просуммировать
09  FC00                  [SP]        0   ;здесь будет храниться текущий максимум
0A  A003          @@k_loop:   i       3   ;от 0 до 1, т.е значения X и Y
0B  8900      NOP  0 ;AUTOMATICALLY INSERTED BY TRANSLATOR TO PREVENT HAZARD
0C  80D9          @@i_loop:   Acc     [Y+2i+k]    ;тут должен быть HAZARD (обращение по [Y+2i+k] происходит по предыдущему значению i)
0D  83DA                  SUB     [Y+2j+k]    ;вычитаем второе
0E  9C80                  SQRD2       Acc         ;возводим в квадрат
0F  82FC                  ADD     [SP]        ;прибавляем к пред. значению
10  FC80                  [SP]        Acc
11  A879                  iLOOP       @@i_loop        ;теперь то же самое для второй координаты
12  AA76                  kLOOP       @@k_loop
13  83F0                  SUB     [SP+1]  ;можно и "пожертвовать" значением в Acc,
14  B801                  JL      @@skip
15  F0FC                  [SP+1]  [SP]    ;двухпортовая память-это зашибись!
16  CDA1                  X       j
17  A96F          @@skip: jLOOP       @@j_loop
18  A0CD                  i       X
19  CD18                  X       Points2D
1A  ED18                  Z       Points2D
1B  F3B1                  CALL        SwapPoints  ;потёрли текущий максимум (лежал в [SP])-и хрен с ним
        FindMDD3    endp
    Associate4Points endp
1C  B0FF              JMP         [--SP]
AffineAlgorithm     endp
SwapPoints  proc
1D  A201              k       1
1E  8900      NOP  0 ;AUTOMATICALLY INSERTED BY TRANSLATOR TO PREVENT HAZARD
1F  8AEA  @@swap:     C       [Z+2j+k]    ;здесь будет HAZARD, использование старого значения k
20  EAC9              [Z+2j+k]    [X+2i+k]
21  C983              [X+2i+k]    C
22  AA7B              kLOOP       @@swap
23  B0FF              JMP     [--SP]
SwapPoints  endp    


Кроме того, StackAdr = 47, по адресу 0xC7 лежит значение 0x5F - именно там мы хотим разместить стек.

Поехали!



PC=1, на шину данных отправляется значение 0xFFC7 - это 0x47 с расширением знака от 7 бит до 16.

PC=2, на шину данных отправляется число 0 из команды "NOP 0". Тем временем, в SP должно поступить значение 0xFFC7, срезанное до 8 бит, т.е просто 0xC7.

PC=3, запрашиваем значение [SP]. Это обращение на чтение из памяти, занимает дополнительный такт. Эффективный адрес: 0xC7, всё верно. Тем временем, "выполняется" команда NOP, вполне успешно.

PC=4, вызываем процедуру AffineAlgorithm. "Так совпало", что он начинается с адреса 5, поэтому по ходу PC мы можем не заметить прыжка, но он есть. На шину данных поступает значение PC=4. Тем временем, в SP поступает значение из шины данных, 0x005F, то есть стек инициализируется правильно. (Напомним, значение 0x5F нельзя задать напрямую, не хватает разрядности. Поэтому мы поместили это значение в память, и загрузили "в два этапа").

PC=5, SrcDiscard=1, т.е получение числа 4 (метка @@endless: чтобы завершить выполнение программы бесконечным циклом) "запрещается", но оно всё равно поступает на шину данных, поскольку никому не навредит. Тем временем, значение с шины данных, 4, поступает в [SP++], по адресу 0x5F. К следующему такту должно получиться SP=0x60.

PC=6, на шину данных поступает "метка" Points2D - адрес в памяти данных, где лежат координаты наших точек на фотоприёмной матрице. Тем временем, должна была выполнится команда JMP, но не выполнится, поскольку DestDiscard=1. Не пришло ещё её время, всё верно.

PC=7, на шину данных поступает нолик. Тем временем, в регистр Y заносится значение Points2D=0x18.

PC=8, на шину данных поступает число 3. А ранее поступивший нолик отправляется в [SP+1], т.е во "вторую локальную переменную" (по [SP-1] лежит адрес возврата, а [SP] и [SP+1] можно с чистой совестью использовать как переменные). Поступает он на адрес 0x61, всё верно. Заметьте, что правильный адрес формируется аккурат к положительному фронту clk, т.е мы работаем на тактовой частоте, близкой к предельной. При симуляции, разумеется, берут "наихудший вариант" - самый медленный кристалл, ещё и разогретый до предельной температуры.

PC=9, заносим число 1, а предыдущее число 3 отправляется в регистр j. Это мы инициализируемся для трёх вложенных циклов. Цикл по j-внешний. j принимает значения от 3 до 0, пробегая по всем 4 точкам. k принимает значения от 1 (коорд. Y) до 0 (коорд. X).

PC=0x0A, единичка отправляется в регистр k, а на шину данных заносится число 0.

PC=0x0B, нолик отправляется в [SP], это текущая полусумма квадратов разностей, инициализируется нулём, разумеется. Это оказывается адрес 0x60, всё верно. А на шину данных заносится число 3.

PC=0x0C, число 3 отправляется в регистр i. Мы будем суммировать квадраты разностей координат между точками i,j, по координатам X,Y (т.е для k=0..1). На шину данных заносится число 0.

PC=0x0D, нолик отправляется на команду NOP, которая была вставлена транслятором, чтобы адрес [Y+2i+k] в следующей команде сформировался для правильного значения i=3. Мы-то сами догадались, что можно было поменять местами [Y+2i+k] и [Y+2j+k], потому что от них берётся разность и возводится в квадрат, но для транслятора делать такие умозаключения пока слишком круто, он просто вставляет NOP, и я его не виню. На шину данных заносится значение [Y+2i+k]. Как водится, на это уходит 2 такта, один, чтобы сформировать эффективный адрес (увы, мы не можем знать заранее, какой из 64 разных адресов у нас запросят!) и один на получение значения. Эффективный адрес получился 0x1F = 0x18 (базовый адрес Points2D) + 2*3 (точка с индексом 3) + 1 (координата Y), всё верно.

PC=0x0E, значение FC1B = -997 отправилось в аккумулятор. Тем временем, на шину данных заносится значение из [Y+2j+k], и по "странному совпадению", это по-прежнему адрес 0x1F, т.к вместо i=3 у нас j=3.

PC=0x0F, из аккумулятора вычитается значение из шины данных, что должно дать нолик. На шину данных хотим занести значение аккумулятора. Срабатывает блокировка АЛУ, которая заставляет дождаться получения результата на аккумуляторе, прежде чем отправлять его на шину данных, поэтому вместо 3 тактов уходит 4.

PC=0x10, значение из шины данных (действительно ноль) возводится в квадрат и делится на два. "По-честному". Вообще, зная, что мы НЕИЗБЕЖНО в этом алгоритме будем получать 4 нуля и все их возводить в квадраты, можно было бы рассмотреть этот "особый случай", и завидев ноль, сразу выдавать ноль. Но пока мы никуда не торопимся :) На шину данных заносится значение из [SP], текущая полусумма квадратов разностей, пока что равная нулю.

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


PC=0x11, к аккумулятору (там нолик) прибавляется значение из шины данных (тоже нолик). На шину данных собираемся поместить результат, поэтому срабатывает блокировка на 1 такт, итого выполняется 4 такта. Результат - ВНЕЗАПНО нолик.

PC=0x12, нолик заносится в [SP], это наша текущая полусумма квадратов разностей. Адрес 0x60, всё верно. На шину данных заносим число -7, это относительный адрес для прыжка.

PC=0x13, выполняем iLOOP, осуществляем прыжок к PC=0x0C, всё верно. На шину данных заносим число -10, хотя включается SrcDiscard=1, но не утянет.

PC=0x0C, команду kLOOP не выполняем, поскольку она случайно затесалась, DestDiscard=1. Значение [SP+1] также нам не нужно, т.к SrcDiscard=1, и, как видно, на эти "отменённые" команды уходит 1 такт, а не 2, если бы дожидлись получения [SP+1]. Всё хорошо.

PC=0x0D, команду SUB не выполняем, т.к DestDiscard=1. А значение [Y+2i+k] берём, лезем по адресу 0x1D = 0x18 (базовый адрес Points2D) + 2*2 (точка с индексом 2) + 1 (коорд. Y). Ага, значит, регистры у нас работают как надо пока что... На выборку данных уходит лишний такт, всё верно. Это оказывается число -302.

PC=0x0E, число -302 заносим в аккумулятор, а на шину данных заносим [Y+2j+k], это число -997. На всё про всё уходит 3 такта, всё верно.

PC=0x0F, из числа -302 (в аккумуляторе) вычитаем -997, получается 695. Именно его хотим занести на шину данных, поэтому ждём лишний такт.

PC=0x10, возводим 695 в квадрат и делим пополам. Получается 7 (точнее, (695/32768)2/2 = 7/32768). Тем временем, на шину данных заносим значение [SP], там пока ноль.

На внутреннем цикле всё хорошо. Мы просмотрели 2 итерации и увидели, что регистр уменьшился на 1, как и надо. Промотаем до выхода из внутреннего цикла.

Первый раз у нас были адреса 0x1F и 0x1F, во второй: 0x1D и 0x1F (результат 7), в третий: 0x1B (там число -30) и 0x1F (число -997, результат 14), в четвёртый: 0x19 (там число -7) и 0x1F (результат 15). А теперь внимательнее:



Последняя половинка от квадрата разности: 15, прибавляем всё вместе (0+7+14+15) и получаем 36, всё верно.

PC=0x12, результат 36 опять заносится в [SP], а на шину данных заносится относительный адрес прыжка, -7.

PC=0x13, выполняется команда iLOOP, но прыжка не следует, т.к i=0. Тем временем, на шину данных заносится значение -10.

PC=0x14, выполняется команда kLOOP, с прыжком на PC=0xA. Выборка данных из [SP+1] не проходит, т.к SrcDiscard=1.

PC=0x0A, команда SUB не выполняется, т.к DestDiscard=1. На шину данных заносится единичка, хоть и SrcDiscard=1.

PC=0x0B, команда JL не выполняется, т.к DestDiscard=1. На шину данных заносится тройка.

PC=0x0C, тройка из шины данных поступает в регистр i (опять сделаем цикл от 3 до 0). На шину данных заносится нолик.

PC=0x0D, нолик "используется" командой NOP, а на шину данных заносится [Y+2i+k]. На этот раз, адрес 0x1E = 0x18 (базовый адрес Points2D) + 2*3 (точка под индексом 3) + 0 (коорд. X), всё верно. Там лежит число 400.

Итак, мы видим, что и второй цикл работает корректно. Дождёмся теперь его окончания. У нас будут пары адресов 0x1E и 0x1E (результат 0), затем 0x1C (там лежит число -539) и 0x1E (результат 13), затем 0x1A (там лежит число 480) и 0x1E (результат 0) и, наконец, 0x18 (там лежит число 53) и 0x1E (результат 2).

Посмотрим, чем это всё заканчивается:


Действительно, к предыдущей сумме 36 мы ещё прибавили 0, затем 13, затем 0 и ещё 2, в результате получилось 51.

PC=0x12, результат заносится в [SP], а на шину данных поступает относительный адрес для прыжка, -7.

PC=0x13, iLOOP не выполняется, поскольку i=0, на шину данных поступает относительный адрес для прыжка, -10.

PC=0x14, kLOOP не выполняется, поскольку k=0, на шину данных поступает значение [SP+1] (максимальная полусумма квадратов, которую пока что удалось достичь), это нолик. На выборку уходит дополнительный такт, всё правильно.

PC=0x15, из аккумулятора вычитаем нолик, получая 51. На шину данных заносим относительный адрес для прыжка 1 (для пропуска 2 команд, если максимум обновлять не нужно).

PC=0x16. Сейчас должна исполниться команда условного перехода JL (Jump if Less). Поскольку результатом вычитания стало положительное число, прыжка быть не должно. Тем временем на шину данных должно прийти значение [SP], но ему не дали времени на выборку...

И вдруг PC=0x01. Всё понятно: мы переделали декодер команд условного перехода, упростили ему логику, а подправить декодер команды БЕЗУСЛОВНОГО перехода забыли. Вот у нас и выполнилась команда JMP, и перенесла нас на АБСОЛЮТНЫЙ адрес 0x01. Это как Reset, только ещё хуже - так у нас стек будет бегать по всей памяти.

Обидная ошибка. Вот говорили же, и я сам себе говорил, РАБОТАЕТ-НЕ ЛЕЗЬ! Нее, перфекционизм наше всё!

Нужно подправить маленький модулёк. Так было:

module QuatCorePCisJMP (input [7:0] DestAddr, input DestDiscard, output Q);

assign Q = (DestAddr[7:3] == 5'b10111)&(~DestDiscard);

endmodule


А так стало:

module QuatCorePCisJMP (input [7:0] DestAddr, input DestDiscard, output Q);

assign Q = (DestAddr[7:3] == 5'b1011_0)&(~DestDiscard);

endmodule


И внезапно, перестал работать place&route: COULD NOT FIND FIT, и всё тут. Даже если уменьшить ширину аккмулятора до 19 бит и ширину адреса ROM до 6 бит. Не понимаю его логики. Причём соединить он не может элементы в совершенно другом месте - я ковыряю QuatCorePC (program counter), а он застревает в QuatCoreMem. Какой-то "эффект бабочки" - что-то слегка меняешь - и это кардинально влияет на то, как он распихивает элементы по поверхности ПЛИС, и вдруг у него не получается их соединить.

В принципе, такая фигня только когда я "вывожу наружу" слишком много потрохов: PC, SrcAddr, DestAddr, MemAddr, DataBus, в общей сложности 48 бит, причём в том месте, где к ним серьёзные требования по производительности. Когда начинаю синтезировать уже весь "процессор в сборе", где все эти шины остаются внутри, а наружу выходит одинокий UART_tx и UART_rx, ну и ещё SPI какой-нибудь - сразу оно куда проще удаётся.

Сейчас попробовал поковыряться в fitter settings. Выставил
Auto global memory control signals = on (было off),
Auto packed registers: normal (было off),
Fitter aggressive routability optimizations: always (было automatically).

Не знаю, что из этого помогло - но ругаться всё-таки перестал.

Итак, торжественно запускаем ещё раз и проматываем в самый конец:


Видно, что перед завершением программы (путём входа в бесконечный цикл), процедура SwapPoints поменяла местами ячейки памяти 0x18 и 0x1E, а также 0x19 и 0x1F, т.е поменяла местами точки под номерами 0 и 3. Всё верно. А вот время выполнения не совпадает: было 48,68 мкс в прошлый раз, а сейчас 49 мкс, т.е потеряли 8 тактов.

Это произошло из-за того, что транслятор добавил NOP там, где мы просто поменяли местами i с j. И этот NOP исполнился ровно 8 раз, т.к сидел внутри цикла по j (от 3 до 0) и внутри цикла по k (от 1 до 0).


Ладно, по крайней мере, после трёхнедельного перерыва я снова вспомнил, как эта штуковина работает. Надеюсь, дальнейшая отладка пойдёт пошустрее.
Tags: ПЛИС, программки, работа, странные девайсы
Subscribe

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

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

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

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

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

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

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 0 comments