nabbla (nabbla1) wrote,
nabbla
nabbla1

Categories:

Новый АЛУ - работа над ошибками

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

Корректно работала команда Acc (положить значение в аккумулятор) и SUB (вычесть из аккумулятора), а вот с SQRD2 (возвести в квадрат и поделить на 2) возникла проблема - откуда-то возник знак "минус". Это заставляет повнимательнее приглядеться к самой длинной строке в модуле QuatCorePipelineALUcontrol.v:

FirstDivSign <= OpSign ^ ((DestAddr[4] & CisSignedC & (isFirstDiv | MulFirst)) | (D[15] & (~DestAddr[4]) & (~DestAddr[3]) & DestAddr[2]));


Которую мы "механически" получили из такой строки:

wire FirstDivSign = OpSign ^ ((isLongCommand & isSignedC & (isFirstDiv | MulFirst)) | (SeniorDataBit & (~isLongCommand) & (~DestAddr[3]) & DestAddr[2]));


Дело определённо в ней!


Первая часть, OpSign - наиболее понятная. Это мы декодируем из команды (т.е DestAddr), с каким знаком должно быть следующее слагаемое. Иногда для этого нужно воспользоваться входом PM (Plus-Minus) для одноимённых команд: PM (прибавить или отнять, в зависимости от регистра Inv), FMPM (Fused Multiply - Plus-Minus) и пр.

Самая последняя скобочка отвечает за взятие модуля числа, т.е команды ABS (просто положить в аккумулятор модуль числа), ABSA (прибавить к акк. модуль числа), ABSS (вычесть модуль числа) и ABSPM (либо прибавить, либо вычесть в зависимости от Inv). Их адреса: 0x84, 0x85, 0x86 и 0x87 соответственно.

Регистры защёлкиваются только если команда принадлежит АЛУ, там проверяются старшие 3 бита. Всего в АЛУ 32 команды. Нам нужно сузить их до конкретных 4, для чего нужно проверить ещё 3 бита: DestAddr[4] и DestAddr[3] должны быть нулевыми, а DestAddr[2] - единичным. И наконец, знак числа нужно поменять, если число отрицательное, что мы проверяем по его старшему биту, D[15].

Мы ещё не проверяли работу этих команд, но пока всё выглядит корректным.

А вот в средней скобочке:
(DestAddr[4] & CisSignedC & (isFirstDiv | MulFirst))


творится что-то очень странное...

Первое условие, DestAddr[4], означает: мы рассматриваем команды умножения, коих у нас 16. Из них 4 - со знаком, 4 - без знака, 4 - "смешанные" (регистр B со знаком, C без знака) и ещё 4 - возведение в квадрат и деление на 2, также со знаком.

Второе условие сужает круг до 8 команд, в которых регистр C воспринимается как знаковый. Команда SQRD2 - одна из этих 8.

А вот дальше мы проверяем состояние конечного автомата, мы должны находиться либо в sFirstDiv, либо в FirstMul - это конкретно первый такт умножения.

Смысл проглядывается: на первом такте умножения мы умножаем на старший разряд регистра C, и если это число интерпретируется как знаковое, старший бит будет браться со знаком "-". Поэтому перед этим тактом мы инвертируем значение в регистре B, а затем - возвращаем как было, т.к все остальные разряды уже будут браться со знаком "+".

В старом модуле, QuatCoreFastALUcontrol, где это выражение было комбинаторным (считалось "сразу же"), регистром B оно управляло только в состоянии sFirstDiv:
assign Bmode = 	isIdle?		{1'b1, Bypass}:
		isFirstDiv?	{1'b0, FirstDivSign} :
				{1'b0, isSignedC & MulFirst};


Но ещё ровно это выражение подавалось на вход переноса (cin, Carry In) сумматора:
assign cin = FirstDivSign;


В состояниях sIdle, sDiv2 и sFirstDiv нам вообще наплевать, что подавать на вход переноса - всё равно результат суммирования никуда не сохраняется.

Сохраняется он в состояниях sAdd и sMult и нужен как ВТОРАЯ ЧАСТЬ ОБРАЩЕНИЯ ЗНАКА. Скажем, если на вход у нас подавалась единичка, и мы решили её вычесть, то инверсия даст 0xFFFE.FFFF (добавилось ещё 16 младших разрядов, для точного умножения с накоплением), и нужно ещё прибавить 1 в самом младшем разряде, чтобы получить правильное 0xFFFF.0000.

Похоже, вся эта штука тянется ещё с 27 декабря 2019 года: Модуль управления АЛУ зафурычил! И эта логика получилась путём пристального взгляда на бумажку, где изображены все управляющие воздействия на каждой из команд :)

Что ж, регистр FirstDivSign может поменяться по окончании первого умножения (FirstMul) на противоположный, если регистр C воспринимается как знаковый. Поэтому поменяем код вот так:

always @(posedge clk)
	FirstDivSign <= LatchNewCmd? 	OpSign ^ ((DestAddr[4] & CisSignedC) | (D[15] & (~DestAddr[4]) & (~DestAddr[3]) & DestAddr[2])) :
					FirstDivSign ^ (MulFirst & isSignedC);


Тут заметим, что в верхней строке используется комбинаторное выражение CisSignedC (C в начале как раз от "Comb"), а в нижней - уже регистр isSignedC. Только так: в верхней строке в этот регистр значение ещё не защёлкнулось, а в нижней уже да, тогда как комбинаторное выражение могло "сбиться" из-за изменившегося DestAddr.

Как ни странно, такое исправление УМЕНЬШАЕТ размер модуля с 43 ЛЭ до его старых 42 ЛЭ. Не сказать, что это так важно, но мне очень нравится, когда исправление ошибок приводит к уменьшению размера, значит мы действительно ДОРАБОТАЛИ НАПИЛЬНИКОМ, а не налепили заплаток и прибили костылей, лишь бы не рухнуло.

Посмотрим, как оно заработает:


УРА, теперь ответ правильный: 7.

Теперь понаблюдаем следующие 6 итераций побыстрее. На первой итерации мы брали (Y3-Y3)2/2 и получили ВНЕЗАПНО нолик :)

На второй итерации было (Y3 - Y2)2/2 = (-997 - (-302))2 / 2 ≈ 7

точнее, (-997/32768 - (-302/32768))2/ 2 ≈ 7/32768, это у нас формат с фиксированной запятой 1.15 так работает.

Семёрку добавили в [SP] и пошли дальше.

На третьей итерации (Y3 - Y1)2/2 = (-997 - (-30))2 / 2 ≈ 14, и прибавление сюда семёрки должно дать 21 = 0x15. Смотрим:

Ага!

На четвёртой итерации (Y3 - Y0)2/2 = (-997 - (-7))2 / 2 ≈ 15, и прибавление сюда 21 должно дать 36 = 0x24. Смотрим:

Хехе!

Дальше мы должны перейти на кординату X, и первым будет (X3 - X3)2/2 = (400 - 400)2 / 2 = 0, так что должна остаться сумма 0x24, так и есть.

Затем (X3 - X2)2/2 = (400 - (-539))2 / 2 ≈ 13, и прибавление к 36 должно дать 49 = 0x31, так и есть.

Затем (X3 - X1)2/2 = (400 - 480)2 / 2 ≈ 0, и у нас должно сохраниться значение 0x31 - да.

И наконец, (X3 - X0)2/2 = (400 - 53)2 / 2 ≈ 2, прибавление к 49 должно дать 51 = 0x33. Посмотрим:



ШИКАРНО! Разные комбинации знаков прошли как надо, и в том числе округление выполнилось "до ближайшего целого", иначе сумма получилась бы на 2 меньше. Правда, такое совсем автоматическое округление может сыграть злую шутку, если результат умножения снова пустим на вход АЛУ через байпас, но здесь не этот случай

Наконец мы должны выйти из двух внутренних циклов и сравнить найденную сумму, 0x33 с текущим максимумом, вот такой листинг:

0C  A805           iLOOP   @@i_loop        ;теперь то же самое для второй координаты
0D  AA06           kLOOP   @@k_loop
0E  83F0           SUB     [SP+1]
0F  ED01           Z       Points2D    ;вообще, оно перед SwapPoints, но здесь всё равно ждём окончания SUB        
10  B807           JL      @@skip
11  F0FC           [SP+1]  [SP]    ;двухпортовая память-это зашибись!
12  CDA1           X       j
13  A908   @@skip: jLOOP   @@j_loop
14  A0CD           i       X
15  CD01           X       Points2D


Да, условный переход iLOOP не выполняется. kLOOP также не выполняется, но "застревает" на 1 такт, поскольку параллельно идёт выборка [SP+1] - это текущий максимум, равный нулю.

Команда SUB как будто бы выполняется за 1 такт (не останавливает счётчик инструкций), параллельно на шину загружается адрес Points2D = 0x18.

Далее, этот адрес помещается в регистр Z, а параллельно на шину поступает адрес @@skip = 0x13 для прыжка. (тем временем, АЛУ выполняет второй такт по SUB).

И вот наступает команда JL (Jump if Less - перейти если результат работы АЛУ меньше нуля), и "зажигает" SrcStall=1, поскольку сейчас идёт третий такт работы АЛУ - результата пока нет! Тем временем, идёт выборка [SP]. На второй такт принимается решение НЕ ПРЫГАТЬ (всё верно), и как раз выборка заканчивается, на шину поступает значение 0x33, наша сумма.

Помещаем её в [SP+1], т.е обновляем максимум, и тем временем помещаем на шину регистр j = 3.

Помещаем эту тройку в регистр X (это индекс точки, которая оказалась самой отдалённой, его тоже обновили), загружаем адрес @@j_loop - и осуществляем условный переход jLOOP.

УРА, команды условного перехода также работают адекватно с новым АЛУ!

Что ж, давайте тогда домотаем до конца работы FindMDD3 (до прыжка в процедуру SwapPoints):


Тут можно наблюдать, как мы сравнили сумму квадратов дальностей для точки с индексом 3, 0x1A, с текущим максимумом, 0x33, в смысле вычитаем 0x33 из 0x1A, ответ получился отрицательным - и условный переход JL выполнился, хотя по PC это незаметно из-за того что он и так всегда бежит "впереди паровоза". По сути, мы прошли две эти команды "в своём темпе" но не стали их выполнять.


Ура, процедура поиска самой отдалённой точки выполнилась верно (её индекс оказался 3), и на выполнение ушло 44,71 мкс. На старом АЛУ уходило 47,36 мкс, т.е мы сэкономили 66 тактов (5,6%). Мы ожидали 65 тактов. Один лишний такт пришёлся на невыполнившийся переход JL, когда во время работы АЛУ у нас также шла выборка из памяти [SP].

Ещё и размер модуля ALUcontrol вернулся к своим родным 42 ЛЭ.

Жизнь начинает удаваться :) Сейчас, наверное, быстренько поглядим остальные этапы работы (сортировка точек против часовой стрелки, нахождение матрицы аффинного преобразования, нахождение крена, масштаба и вектора), и если всё хорошо - вернёмся к злополучному "обнаружению точек".
Tags: ПЛИС, программки, работа, странные девайсы
Subscribe

Recent Posts from This Journal

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

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

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

    Вчера я чуть поторопился отсинтезировать проект,параметры не поменял: 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 

  • 2 comments