nabbla (nabbla1) wrote,
nabbla
nabbla1

Categories:

QuatCore начинает фурычить (часть 3)

Часть 1, часть 2.
Пока что прошло 19 команд - полёт нормальный. Мы увидели работу со стеком, в т.ч вызов процедуры, использование текущей позиции и следующей за ней в качестве локальных переменных, выполнение цикла kLOOP, но пока без выхода из него. Арифметические операции уже были, но "тривиальные частные случаи", дающие в итоге ноль.

Теперь начнётся чуть поинтереснее.

Напомним, мы сейчас выполняем следующую процедуру:
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

Первое число в листинге указывает адрес данной команды (где она лежит в ROM, то есть ПЗУ). Второе число - это непосредственно команда. Первый байт - адрес получателя (DestAddr), второй - адрес источника данных (SrcAddr), а больше нам ничего не надо.
Правее идут мнемонические коды этих адресов, в левой колонке DestAddr, в правой колонке SrcAddr. И наконец, в некоторых строках есть метки наподобие @@j_loop: и пр. Две "собаки" означают, что это локальные метки, действующие только внутри процедуры. В машинный код они попадают только в виде правильных относительных адресах в командах условного перехода.

Пока мы дошли до адреса 10, там вернулись в адрес 0B, и снова дошли до адреса 0D. По существу, мы продолжаем считать сумму квадратов расстояний точки номер 3 до всех остальных точек, но пока лишь сосчитали квадрат расстояния до СЕБЯ САМОЙ, и вполне ожидаемо получили ноль...




ADD [SP] (82FC)
к квадрату разности Y-компонент точки 3 и себя же (это НОЛЬ) прибавляем текущее значение суммы квадратов (тоже НОЛЬ).

[SP] Acc (FC80)
обновлённое значение суммы квадратов расстояний запихиваем назад, на стек. Это действительно ноль.

kLOOP -5 (AA7B)
Теперь индексный регистр k=0, т.е мы выполнили сколько надо итераций. Как видно, k так и остаётся нулевым, и мы спокойно переходим к следующей команде.

iLOOP -7 (A879)
А вот этот цикл мы ещё не завершили - пока что посчитали расстояние от точки j=3 до точки i=3, и это получился ноль. Теперь нужно ещё прибавить расстояние до точки i=2, i=1 и i=0. Так что уменьшаем i на единичку и прыгаем на метку @@i_loop.

k 1 (A201)
По-новой начинаем внутренний цикл, по k от единицы до нуля. Всё в порядке.

Acc [X+2i+k] (80C9)
Загружаем Y-значение 2-й точки. X=18 (как мы его присвоили, так оно и не менялось), i=2, k=1, всё в HEX. Эффективный адрес X+2i+k равен 1D. Сверяемся по MemAddr - так и есть. По этому адресу лежит значение FED2, соотв. числу -302. Именно оно должно быть загружено в аккумулятор.

SUB [X+2j+k] (83CA)
Вычитаем Y-значение 3-й точки. X=18, j=3, k=1. Эффективный адрес X+2j+k равен 1F - всё работает правильно. По этому адресу лежит значение FC1B, что соответствует числу -997. Получается, мы должны посчитать выражение -302 - (-997) и получить ответ 695, или 2B7 в Hex. Скоро проверим.

SQRD2 Acc (9C80)
Действительно, на шине данных сидит значение 2B7, т.е хотя бы вычитать мы умеем! Правда, через 1 такт оно зануляется, так и должно быть - аккумулятор (с которого коммутировалось значение) уже обнулился, но значение 2B7 уже было занесено в регистр B. Вот только вот незадача - в регистр C оно так и не попало, эту операцию мы в своей безграничной мудрости решили сделать немножечко позже! В итоге, вместо возведения в квадрат и деления на 2, мы получили операцию умножения на ноль - чрезвычайно полезно...

Можно переписать программу, чтобы источником данных было нечто чуть более постоянное (пусть даже регистр C - его мы не трогаем, пока не занесём в него значение из шины данных), но красивее, конечно, несколько подправить логику АЛУ. Глянем, что мы там насочинили:

assign Cmode[1] = (isIdle|isDiv2)? 	(DestAddr[7:3] == 5'b1000_1)&DestAddr[1] :
			(~isMult)?	isLongCommand & DestAddr[3] & DestAddr[2]:
					1'b1;
assign Cmode[0] = ~isMult;


Cmode[1] - это, по сути, "разрешение работы" регистра C, пока здесь 0 - он просто хранит своё значение.

Cmode[0] игнорируется, пока Cmode[1]=0, в противном случае Cmode[0]=0 означает циклический сдвиг, Cmode[1]-параллельную загрузку из шины данных.

Cmode[0] нам менять не надо - он работает как надо - предлагает параллельную загрузку на всех этапах, кроме 16 тактов умножения, где идёт циклический сдвиг. Всё, что надо - переиначить Cmode[1].

Сейчас Cmode[1] на первых двух тактах равен единице, если код команды (т.е DestAddr): 1000_1x1x. Как видно из "периодической таблицы команд":


мы селектируем одну-единственную корректную команду C (загрузить в регистр C), в противном случае регистр C остаётся неизменным первые 2 такта. Затем, когда мы выходим на такт перед началом умножения, мы устанавливаем Cmode[1]=1 для следующих команд с адресами xxx1_11xx, т.е:


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

assign Busy = isOurAddr & (DestAddr[4:2] != 3'b0_10)&(~isAdd)&(~finished);


Ладно, не будем открывать ящик Пандоры, в смысле, ещё раз перекраивать всю логику АЛУ, а сделаем по-простому:

assign Cmode[1] = isIdle? isOurAddr & DestAddr[3] & ((~DestAddr[4]&DestAddr[1])|(DestAddr[4]&DestAddr[2])) : isMult;			


Как ни странно, теперь модуль управления АЛУ даже ужался с 38 ЛЭ до 36 ЛЭ. Т.е сейчас закономерности даже чуть лучше:
- какие-либо действия регистр C предпринимает только на первом такте (isIdle), либо на 16 тактах умножения (isMult).
- на первом такте нужно загрузить регистр C, если это НАША команда (isOurAddr, проверяет старшие 3 бита), и если 3-й бит адреса единичный. Дальше, увы, идёт небольшое расхождение: мы должны выбрать самую нижнюю строку, а также предпоследнюю строку из "коротких" команд, но и из неё нужно вырезать команду ZAcc, т.к на ней мы обязаны хранить значение в C.

Ладно, опробуем обновлённый драндулет. Весь QuatCore с 32-битным аккумулятором, отключённым адресом ijk, 5-битной адресаций ПЗУ и 8-битной адресацией ОЗУ сейчас занимает 431 ЛЭ.



Предыдущие операции проверил - всё верно, нули получились где надо. Теперь снова смотрим возведение в квадрат и деление пополам для числа 0x2B7 = 695.

Здесь опять небольшая терминологическая путаница. На самом деле, это не 695, а 695/32768. Когда мы возводим его в квадрат и делим на два, то получаем примерно 7/32768 - именно эту семёрку мы и увидели в аккумуляторе. Можно и более подробно рассмотреть, как выполнялась эта операция. Поскольку 2B7 = 0000_0010_1011_0111, то первые 6 тактов прошли "вхолостую", и в аккумуляторе оставался ноль. При этом в регистр B сразу же занесли поделённое на два значение:
0000 0001 0101 1011 (в этот момент в рег. C: 0000_0010_1011_0111)
но посмотрели на старший разряд C и не стали прибавлять к результату. Сдвинули на единицу вправо:
0000 0000 1010 1101 (рег С: 000_0010_1011_0111_0)
но и его не прибавили. Снова сдвинули вправо:
0000 0000 0101 0110 (рег С: 00_0010_1011_0111_00)
и его не прибавили. Ещё сдвинули вправо:
0000 0000 0010 1011 (рег C: 0_0010_1011_0111_000)
снова не прибавили, снова сдвинули:
0000 0000 0001 0101 (рег C: 0010_1011_0111_0000)
снова не прибавили, снова сдвинули:
0000 0000 0000 1010 (рег C: 010_1011_0111_0000_0)
снова не прибавили, снова сдвинули:
0000 0000 0000 0101 (рег С: 10_1011_0111_0000_00)
ДАА, наконец-то старший разряд С единичный, прибавляем текущее значение B к результату, получаем 5. И сдвигаем вправо:
0000 0000 0000 0010 (рег С: 0_1011_0111_0000_001)
не прибавили, сдвигаем:
0000 0000 0000 0001 (рег С: 1011_0111_0000_0010)
прибавляем текущее значение к результату, получаем 6.

И далее регистр B зануляется, и значение на выходе так и остаётся неизменным. Но в рассмотренном нами примере использовался всего 16-битный регистр B и регистр результата. У нас же они 32-битные, поэтому совсем "задвинутые" вправо результаты всё ещё имеют шанс повлиять на результат. Кроме того, регистр результата (Acc) инициализируется одной второй "младшего разряда". Результатом всего этого становится корректный результат, 7, а не 6.

Поехали дальше.

ADD [SP] (82 FC)
На стеке всё еще лежит нолик - квадрат расстояния точки 3 до самой себя. Но сейчас наконец-то прибавится ненулевое значение!

[SP] Acc (FC 80)
На шине - число 7, именно оно отправляется в стек.

kLOOP -5 (AA 7B)
k больше нуля, поэтому вычитаем единицу (становится ноль) и прыгаем в начало цикла.

Acc [X+2i+k] (80 C9)
Эффективный адрес 1C, это X-компонента 2-й точки, значение 0xFDE5 = -539. Оно должно отправиться в аккумулятор.

SUB [X+2j+k] (83 CA)
Эффективный адрес 1E, это X-компонента 3-й точки, значение 0x0190 = 400.

SQRD2 Acc (9C 80)
В конце скриншота мы видим значение из аккумулятора: FC55. Это -939 в дополнительном коде.

Интрига дня - правильно ли работает умножение и деление на два с отрицательными числами?


После некоторой заминки корректно исполнилось ещё 14 команд.
Продолжение следует...
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