Пока что на симуляторе в Quartus'e, но если он там заработал - то высоки шансы, что и на кристалле заработает. Это всё-таки нормальный Timing analysis.
Reset и первые 4 команды - пока всё хорошо :)
Пару слов о программе, которую этот драндулет исполняет. Это первый кусочек аффинного алгоритма, подправленный под наш нынешний набор команд. Сначала приведём сегмент кода:
.code main proc SP StackAdr C [SP] SP C
CALL AffineAlgorithm
@@endless: JMP @@endless main endp
AffineAlgorithm proc
Associate4Points proc
;состояние регистров неизвестно (за искл. PC и SP) FindMDD3 proc ;перво-наперво, находим МДД3 ;как точка, сумма квадратов расстояний до которой максимальна (т.е самая отдалённая) ;для каждой точки считаем сумму квадр. расстояний до всех остальных, в т.ч до самой себя (это 0, зато код упрощается) ;внешний цикл (по строкам) - перем. j ;внутр. цикл (по столбцам) - перем. i ;самый-самый внутр. цикл - по индексу перем (X или Y) - перем k X Points2D [SP+1] 0 ;максимальная отдалённость, инициализируем нулём j 3 @@j_loop: i 3 ;также от 0 до 3, чтобы все расстояния просуммировать [SP] 0 ;здесь будет храниться текущий максимум @@i_loop: k 1 ;от 0 до 1, т.е значения X и Y @@k_loop: Acc [X+2i+k] ;загрузили одно значение SUB [X+2j+k] ;вычитаем второе SQRD2 Acc ;возводим в квадрат ADD [SP] ;прибавляем к пред. значению [SP] Acc kLOOP @@k_loop ;теперь то же самое для второй координаты iLOOP @@i_loop SUB [SP+1] JL @@skip C [SP] [SP+1] C Y j @@skip: jLOOP @@j_loop i Y k 1 @@swap: C [X+k] Acc [X+2i+k] [X+k] Acc [X+2i+k] C kLOOP @@swap
;на этом первая часть марлезонского балета закончена. FindMDD3 endp
SortCCW proc SortCCW endp
Associate4Points endp
Compute4PointAffine proc Compute4PointAffine endp
FindRoll proc FindRoll endp
FindScale proc FindScale endp
FindVector proc FindVector endp
JMP [--SP] AffineAlgorithm endp
Мы начинаем с мучительной инициализации стека. Предположительно, стек мы засунем по такому адресу, к которому не будет "прямого доступа" (это первые 64 и последние 64 слова данных), т.к он и нужен всего один раз, при инициализации. Из-за текущего запрета на операции "из памяти в память", которые распространяются даже на занесение в регистры X/Y/Z/SP значения из памяти (или наоборот), приходится воспользоваться ещё и регистром C из АЛУ. Возможно, в дальнейшем можно будет чуть улучшить модуль QuatCoreMem, но пока попробуем обойтись.
Под спойлером - объявление сегмента данных. Не стал его укорачивать, пущай уж будет "боевым".
;Бортовая программа ВИПС для QuatCore ;версия от 2.01.2020 ;необходимо подрихтовать весь код в соответствии ;с изменившимися командами QuatCore ;и запретом на пересылки "память-память" ;также изменения коснутся представления данных в памяти, ;т.к мы наконец-то созрели до треугольной адресации!
.data
;значения для ZAcc ;возможно, мы решим их переставить местами, ;или раскидать по доступным 128 значениям, ;если это упростит реализацию ThreeHalf EQU 0 MinusThreeHalf EQU 1 RoundZero EQU 2 Two EQU 3
ORG 0
;целочисленные координаты на фотопр. матрице ;либо сюда заносится во время захвата, ;либо мы сами задаём на стадии сопровождения RoughPoints: RoughX0 Int16 0 RoughY0 Int16 0 RoughX1 Int16 0 RoughY1 Int16 0 RoughX2 Int16 0 RoughY2 Int16 0 RoughX3 Int16 0 RoughY3 Int16 0 ;первые 4
RoughX4 Int16 0 RoughY4 Int16 0 RoughX5 Int16 0 RoughY5 Int16 0 RoughX6 Int16 0 RoughY6 Int16 0 RoughX7 Int16 0 RoughY7 Int16 0 ;следующие 4, для них всех Radius0
RoughX8 Int16 0 RoughY8 Int16 0 RoughX9 Int16 0 RoughY9 Int16 0 RoughX10 Int16 0 RoughY10 Int16 0 ;последние 3, для них задаётся Radius1 Radius0 Int16 0 ;сюда наш алгоритм помещает для видеопроцессора: какой должен быть радиус "основных" точек (первых 8) и какой радиус последних 3. Radius1 Int16 0
;сумма X * Pixel[X,Y], Y * Pixel[X,Y], для нахождения ярк. центра ;их выдаёт "видеопроцессор" ;но сейчас имеем дело с уже обработанными - готовыми коорд. ярк центра, ;нули в середине матрицы, 64 отсчёта на пиксель Points2d: ;дистанция 300 метров, все углы нулевые Fx0 Int16 53 ;адр 33 Fy0 Int16 -7
AfMat20 Int16 1199 ;адр 3Bh AfMat21 Int16 -5752 AfMat22 Int16 -3495 AfMat23 Int16 -5807 ;адр 3Eh ;решили все-все знаки здесь обратить, чтобы не связываться с X<0, который меня уже порядком достал! ;для этого и коэф. нижней строки аф. матрицы обратили, ибо нефиг.
;стек, решили, наконец-то, что он растёт вверх! ;зарезервируем память для него, адр. 5Fh Stack dw ? ;1 ячейка для адреса возврата в main st[1] dw ? ;1 ячейка для адреса возврата из RotateVecByQuat ;адр 5Fh st[2] dw ? ;для хранения X внутри RotateVecByQuat st[3] dw ? ;1 ячейка для адреса возврата из QuatMultiply st[4] dw ? ;для хранения ijk внутри QuatMultiply st[5] dw ? ;промежут. значение кват (0) st[6] dw ? ;(1) st[7] dw ? ;(2) st[8] dw ? ;(3) ;адр 67
.rodata ORG 163 ;A3h
;координаты отдельных отражателей мишени ближней дистанции, в увеличенном масштабе ;единица измерения - метры, ;диапазон представимых значений - от -0,125 до примерно +0,125 метров ;пользуемся тем фактом, что ракурс не более чем на 30 градусов, поэтому ;"есть где развернуться"
;координаты мишеней дальней дистанции (т.е когда смотрим издалека, и отдельные точки сливаются в одну) ;единица измерения - метры, ;диапазон представимых значений: от -4 до примерно +4 метров ;(хватило бы и -2..+2, но развернуться немножко негде, в прямом смысле) MDD3x Int16 0 ;BBh MDD3y Int16 -5554 ;BCh MDD3z Int16 15794 ;здесь же MDD1a ;BDh
MDD1x Int16 0 ;BEh MDD1y Int16 -6832 ;BFh ShortRangeRefPoints: ;и здесь же MDD2a. MDD1z Int16 410 ;здесь же MBDa ;C0h - первый адресуемый по Imm, он же -64
MDD2x Int16 0 ;C1h MDD2y Int16 9339 ;C2h LongRangeRefPoints: ;и здесь же MBDa MDD2z Int16 4743 ;C3h
.data ;выходные данные ;"в максимальной комплектации" - ковариационная матрица шума измерений Matrix (комп. a11, a12, a22, и т.д., 21 элем.), ;кватернион взаимной ориентации Quat (QuatA, QuatX, QuatY, QuatZ, 4 слова), ;вектор параллельного переноса (TxOut, Ty, Tz, 3 слова), ;значение скорости (Vel, 1 слово) ;сюда же вклинивается вектор K 1х6 (для алгоритма сопровождения), ;и остаются вакантные места на 6 слов и 5 слов. ;плюс дофига "флагов", которые пока что занимают по 16 бит каждый, но можно будет их и покомпактнее Matrix: a11 Int16 ? ;адр. C8h
state dw 1 ;0-поиск (подбор экспозиции, грубое определение дистанции), 1-захват, 2-сопровождение HasData dw 0 ;0-мы не выдаём данных, 1-выдаём по крайней мере дистанцию и углы с требуемой точностью QValid dw 0 ;0-только данные дистанции и 2 углов (кватернион выдаётся, но точность не обеспечивается). 1-точность кватерниона обеспечена VValid dw 0 ;0-показания скорости не обеспечивают точности. 1-точность, указанная в ТЗ, обеспечена Vel Int16 ? ;скорость сближения (положительная-отдаляемся, отрицательная-сближаемся, т.е производная от дальности) ErrCode dw 0 ;0: всё хорошо, 1: точка, которая ожидалась на экране, при проецировании вышла за пределы (скорее всего, зацепились за блик) OneHalf dw 16384 ;для нахождения кватерниона крена.
K1 Int16 0 ;вектор правой части при решении системы линейных уравнений K2 Int16 0 K3 Int16 0 K4 Int16 0 K5 Int16 0 K6 Int16 0
TxOut dw ? ;Tx вместе с экспонентой Exp dw ? ;отдельно экспонента Tx dw ? ;Tx отдельно Ty Int16 ? ;другие значения Tz Int16 ?
TargetPTR dw LongRangeRefPoints ;когда в дальней зоне, на 3 меньше когда в ближней TargetCnt dw 3 ;кол-во мишеней, минус 1: 3 в дальней зоне, 10 в ближней pad85 dw ?
;временные переменные. Не знаю, может и в стек их можно было, ;но мы там вызываем другие проц., нужно сдвигать SP,лениво честно говоря :) AfTransf: Tx': Txx Int16 ? ;F6h Ty': Tyx Int16 ? ;F7h Tz': Txy Int16 ? ;F8h B0: Tyy Int16 ? ;F9h Bx: Rx Int16 ? ;FAh By: Ry Int16 ? ;FBh Bz Int16 ? ;коэф. афинного преобр. на этапе захвата ;временная, сдвинутая копия вектора T, если Exp=0..3, на ComputeRoughPoints. ;весь этот бред-чтобы ткнуть носом видеопроцессор "здесь мы ожидаем точку, она нам нахрен не нужна, ;но не пугайся пожалуйста!"
Local0 Int16 ? ;место для локальной переменной (после вычистки системы команд, мы остались без [SP+2], нужно что-то взамен) Local1 Int16 ? ;для второй!
Также есть листинг, как оно будет распределено по адресам (результат работы транслятора):
main proc 00 FD47 SP StackAdr 01 8AFC C [SP] 02 FD83 SP C 03 F3B0 CALL AffineAlgorithm 04 B804 @@endless: JMP @@endless main endp AffineAlgorithm proc Associate4Points proc FindMDD3 proc 05 CD18 X Points2D 06 F000 [SP+1] 0 ;максимальная отдалённость, инициализируем нулём 07 A103 j 3 08 A003 @@j_loop: i 3 ;также от 0 до 3, чтобы все расстояния просуммировать 09 FC00 [SP] 0 ;здесь будет храниться текущий максимум 0A A201 @@i_loop: k 1 ;от 0 до 1, т.е значения X и Y 0B 80C9 @@k_loop: Acc [X+2i+k] ;загрузили одно значение 0C 83CA SUB [X+2j+k] ;вычитаем второе 0D 9C80 SQRD2 Acc ;возводим в квадрат 0E 82FC ADD [SP] ;прибавляем к пред. значению 0F FC80 [SP] Acc 10 AA7B kLOOP @@k_loop ;теперь то же самое для второй координаты 11 A879 iLOOP @@i_loop 12 83F0 SUB [SP+1] ;можно и "пожертвовать" значением в Acc, 13 B004 JL @@skip 14 8AFC C [SP] 15 F083 [SP+1] C 16 DDA1 Y j 17 A971 @@skip: jLOOP @@j_loop 18 A0DD i Y 19 A201 k 1 1A 8AC8 @@swap: C [X+k] 1B 80C9 Acc [X+2i+k] 1C C880 [X+k] Acc 1D C983 [X+2i+k] C 1E AA7C kLOOP @@swap ;а теперь и обе FindMDD3 endp SortCCW proc SortCCW endp Associate4Points endp Compute4PointAffine proc Compute4PointAffine endp FindRoll proc FindRoll endp FindScale proc FindScale endp FindVector proc FindVector endp 1F B8FF JMP [--SP] AffineAlgorithm endp
Теперь можно отследить, шаг за шагом, что же там происходит.
Первая команда, SP StackAdr (FD 47) Мы загружаем в указатель стека адрес константы StackAdr. Если глянуть листинг данных, она лежит по адресу C7. Мы же указали в поле SrcAddr число 47. Если мы посмотрим на скриншот, то после сброса (когда сигнал Reset стал низкого уровня), на SrcAddr действительно лежит 47, а на 16-битной шине данных DataBus - значение FFC7. Так происходит из-за расширения знака, который происходит в модуле QuatCoreImm, вот его код:
Нам очень хочется уметь представлять маленькие знаковые числа, но на них выделено всего 7 бит в поле команды (первый ноль означает, что мы из 4 модулей мы выбрали QuatCoreIMM, т.е Immediate-значения). Поэтому мы присоединяем к 7-му биту все более старшие, чтобы число -64 осталось таковым и в 16-битной записи.
А дальше происходит ещё одна метаморфоза - модуль памяти сейчас использует 8-битные адреса, поэтому старшие FF начисто игнорируются, и в SP поступает значение C7. Обо всём этом заботится компилятор. Если до значения в памяти можно "достучаться" напрямую, он сформирует правильный адрес, если нет - громко выругается. В этот раз всё пошло как надо.
Теперь смотрим на DestAddr. Как и задумано, там значение FD. Напомним, что: адреса 00..7F принадлежат модулю Imm (по Src) / Null (по Dest), адреса 80..9F принадлежат модулю ALU, адреса A0..BF принадлежат модулю PC, адреса C0..FF принадлежат модулю MEM.
И приведём в качестве шпаргалки таблицу адресов MEM:
Адрес FD здесь называется: SP. Всё верно.
Мы видим, что выборка адресов произведена верно, на шине данных лежит то, что надо. Если всё пойдёт правильно, то в SP должно прийти значение C7. На этом такте мы этого узнать не можем, а все-все "потроха" высовывать наружу мне не хотелось. Поехали дальше.
Вторая команда, C [SP] (8A FC) Видим, что за PC=0 последовало PC=1 (никаких переходов не было). Теперь SrcAddr = FC, DestAddr = 8A - из ПЗУ пришли правильные значения.
На MemAdr по-прежнему сидит значение C7 но теперь оно поступает не с шины данных, а из SP. В ответ на этот адрес, ОЗУ выдаёт значение 005F - адрес, по которому мы хотим разместить стек. Это значение коммутируется на шину данных, и если всё правильно выполнится, должно быть занесено в регистр C а АЛУ. Это операция, выполняющаяся за 1 такт. И действительно, по фронту тактовой частоты PC переключается с 1 на 2. Пока всё хорошо.
Третья команда, SP C (FD 83) На шине данных по-прежнему значение 005F, но в этот раз поступившее из регистра C. Получателем должен стать регистр SP. Эти 3 команды немного раздражают - будь наши модули чуточку поумнее, можно было бы всё сделать за 1 команду, а может и вовсе научить SP принимать правильное значение по reset'у - и делов с концом! Но пущай будет...
Четвёртая команда, Call AffineAlgorithm, т.е [SP++] Call0 (F3 B0) А вот это уже куда интереснее!
Модуль PC, при обращении по SrcAddr = B0, должен выдать на шину данных значение PC+1, т.е адрес возврата (по какому адресу надо прыгнуть по выходу из процедуры), и действительно, там мы видим значение 4. В качестве "побочного эффекта" должен произойти прыжок по нулевому адресу из таблицы CallTable. Сейчас компилятор немножечко поумнел и генерит такой код:
А именно, в случае, когда в таблице всего одна запись, она выдаётся "безальтернативно" и должна превратить вход "синхронной загрузки" в обычный "вход установки". Но самое главное - прыжок идёт по адресу 5. И как видно по сигналу PC, так оно и будет.
Теперь смотрим, что происходит на стороне Dest. Адрес возврата мы должны занести в стек. И действительно, в MemAdr поступает адрес 5F, который мы указали как начало стека. По MemContent пока идут нули - все "неинициализированные" области мы всё же инициализировали нулями.
Пока всё идёт как надо. Приведём следующий скриншот:
X Points2D (CD 18) На шину данных поступает "непосредственное значение" и, вероятно, заносится в регистр X. Скоро проверим...
[SP+1] 0 (F0 00) Мы "разместили" на стеке две локальные переменные. По адресу [SP] будем хранить текущую сумму, а по [SP+1] - максимальную отдалённости точки до всех остальных. Перед началом цикла заносим туда ноль.
Начало стека у нас было: 5F. Там сейчас лежит адрес возврата, а SP прибавил единичку, став 60. Значит, [SP+1] должен обратиться к адресу в памяти 61. Ага, так и есть! И эта область памяти тоже пока нулевая.
j 3 (A1 03) На шине данных непосредственное значение 3 - всё верно. Из интересного - в память ОЗУ сформировался адрес 19. В этом нет криминала, и можно расшифровать, что это значит. Когда DestAddr не начинается с двух единиц, QuatCoreMemDecoder считает, что обращение к памяти идёт по SrcAddr. Там у нас значение 00 00 00 11. Старшие 2 бита игнорируются, следующие 2 бита выбирают базовый регистр - X. Следующие 2 бита выбирают второй индекс - это 1. И младшие 2 бита выбирают первый индекс - это 4j. Итого, формируется адрес [X+4j+1], и это действительно обращение по адресу 19, т.е запись в регистр X прошла! Другое дело, значение [X+1] не коммутируется на шину данных - так и должно быть.
К следующему такту мы видим, что действительно j=3. Всё работает.
i 3 (A0 03) На шине данных значение 3. К следующему такту действительно i=3 - присвоение работает. В модуле памяти по-прежнему формируется адрес [X+4j+1], но теперь, поскольку j=3, вместо 19 получается 25 (это всё hex!), всё верно.
[SP] 0 (FC 00) Сформировался адрес в ОЗУ 60 - так и надо. Пока содержимое по этому адресу нулевое. Всё верно.
k 1 (A2 01) На шине данных "непосредственное значение" 1. В модуле MEM "на всякий случай" сформировался адрес [X+2i+1], и действительно, значение в скобках равно 1F.
10 команд, полёт нормальный :) Продолжение следует. Там начинается работа с АЛУ, и что-то странное происходит, нужно что-то подрихтовать. А вот остальные модули - IMM, MEM и PC - меня пока что радуют, всё чётко :)