nabbla (nabbla1) wrote,
nabbla
nabbla1

Categories:

Проблемы с UFMS

По счастью, не с управлением федеральной миграционной службы, а с Unsigned Fused Multiply-Subtract. Сейчас наконец-то начал отлаживать часть программы, которая использует данную команду для нахождения обратной величины методом Ньютона - и напоролся - выдаёт полнейший бред.



Мы должны были посчитать выражение
2 - (37555/32768)(27979/32768)
и получить ответ: 33470/32768. Вместо этого у нас получилось 43044/32768 - ну вообще ни к селу ни к городу!

Чуть ранее мы считали выражение
2 - (32767/32768)(27979/32768)
и получили почти что правильный ответ: 37556, тогда как должны были получить 37557...


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

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

Именно в команде UFMS наложилось два "взаимоисключающих" параграфа - число беззнаковое, но мы его ВЫЧИТАЕМ.

Поскольку нужно сделать вычитание - то сразу же после загрузки числа в регистр, производится его сдвиг С ИНВЕРСИЕЙ, "в беззнаковом режиме", то есть старший бит остаётся нулевым.

К примеру, число 37555 = 92B3 = 1001_0010_1011_0011, когда заносится в регистр B, справа дополняется нулями до 32 бит:

1001_0010_1011_0011_0000_0000_0000_0000


Теперь делается сдвиг вправо с инверсией. Давайте сделаем его в два этапа. Сначала инверсия:
0110_1101_0100_1100_1111_1111_1111_1111

и теперь сдвиг:
0011_0110_1010_0110_0111_1111_1111_1111


И вот уже здесь проблема: мы прибавляем вполне себе положительное число. В аккумуляторе, мы считаем, число всегда знаковое, способное принимать значения от -2 до +2. Поэтому на самом деле, даже если операция у нас "беззнаковая", нам стоило бы правильным образом превратить 32-битное беззнаковое число в 32-битное знаковое, но вдвое меньшего масштаба. В нашем случае, поставить в старший бит единицу!

И дальше мы продолжаем совершать ошибки: теперь продолжаются сдвиги без инверсии, и каждый раз слева образуются нули, т.е вместо вычитания мы прибавляем числа.

Всё это у нас правильно работает в команде MULU (там инверсия не нужна, и нолик в старшем бите корректен, как и все последующие), и никаких проблем не было в знаковых операциях, включая MULSU (MULtiply Signed-Unsigned), где число в регистре B полагается беззнаковым, а вот число в регистре C - беззнаковым. И только вот с UFMS вышел прокол.

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

assign BSigned = 	(DestAddr[3:2] != 2'b10);


BSigned=1 заставляет регистр B делать "арифметический сдвиг вправо", а нолик - "логический", т.е просто кладёт в старший бит нолик.

Оказывается, именно самый первый сдвиг - самый ответственный. В случае знакового числа мы всё делаем правильно - к завершению сдвига два старших бита должны оказываться одинаковыми (как бы сначала арифметический сдвиг, а потом, если надо - инверсия, или сначала инверсия - потом арифметический сдвиг). А при беззнаковом числе мы должны "искусственно" задать знак числа - 0 (плюс), если мы делали сложение, и 1 (минус) - если вычитание.

А вот все остальные сдвиги всегда будут знаковыми! Беззнаковое число уже "обрело знак" на первом сдвиге!

Так что первым делом мы должны подправить регистр B, вот его нынешний код:

//mode = 00 - shift right
//mode = 01 - shift right and negate
//mode = 10 - load 16 bit (no bypass)
//mode = 11 - load AccWidth bit (bypass enabled)

module QuatCoreALUBreg (input clk, input[AccWidth-18:0] Dsmall, input [15:0] D, input DoSigned, input [1:0] mode,
						output [AccWidth-1:0] Q);

parameter AccWidth = 32;
localparam SmallPartWidth = AccWidth - 16;

reg seniorBit = 1'b0;
reg [14:0] largeQ = 1'b0;
reg [SmallPartWidth-1:0] smallQ = 1'b0;

assign Q = {seniorBit, largeQ, smallQ};

always @(posedge clk) begin
	seniorBit 	<= mode[1]? D[15] : DoSigned? (seniorBit^mode[0]) : 1'b0;
	largeQ 		<= mode[1]? D[14:0] : mode[0]? {~seniorBit, ~largeQ[14:1]} : {seniorBit, largeQ[14:1]};
	smallQ		<= mode[1]? (mode[0]? {Dsmall, 1'b0} : {SmallPartWidth{1'b0}}) : mode[0]? {~largeQ[0], ~smallQ[SmallPartWidth-1:1]} : {largeQ[0], smallQ[SmallPartWidth-1:1]};
end

endmodule


Такое ощущение, что мы только вчера здесь были! А мы и были здесь вчера, ремонтировали байпас.

Теперь ещё маленькая правка:
  seniorBit 	<= mode[1]? D[15] : (DoSigned & seniorBit)^mode[0];


Если DoSigned=1, то поведение остаётся как и раньше - при обычном сдвиге сохраняем бит как есть, при сдвиге с инверсией - инвертируем.
А если DoSigned=0, то при сложении защёлкиваем нолик, а при вычитании - единичку.

Но этого мало. Теперь нужно ещё "нейтрализовать" DoSigned, чтобы он мог становиться нулевым только при самом первом сдвиге. Правим соответствующую строку в модуле управления АЛУ, QuatCoreALUcontrol:

assign BSigned = 	(DestAddr[3:2] != 2'b10) | (~isFirstDiv);


т.е только в состоянии FirstDiv (первый сдвиг вправо) правая часть выражения обращается в ноль, так что BSigned определяется левой частью. А во всех остальных состояниях, правая часть единичная, так что очевидно операции "со знаком".

Ну что ж, глянем, что из этого вышло. Теперь весь процессор синтезируется в 446 ЛЭ - НА ОДИН МЕНЬШЕ, чем перед данными правками! Меня всегда радует, когда исправление ошибок приводит к более компактному исполнению :) Уж лучше доработать напильником (убрать лишнее), чем клеить заплатки :)

Теперь запустим нашу "бортовую программу":



Тот факт, что у нас спустя миллисекунду после начала работы (т.е после исполнения всех предыдущих этапов) всё то же самое число 27979, означает, что мы не "поломали" другие команды. Второй операнд - число 37557 - это корректный (почти) результат, в отличие от 37555, которое было ранее.

И наконец, результат операции
2 - (37557/32768)(27979/32768) = 33468/32768 - тоже абсолютно верный!

Так что UFMS заработал как надо.

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

Но теперь, при выполнении итерации метода Ньютона для взятия обратной величины:
x <  (2-a*x)*x


после выполнения команды
UFMS [SP]

в аккумуляторе лежит значение скобки, в регистре "С" по-прежнему лежит число x (старое приближение), поэтому умножение выполнится командой

MULU UAC

и в нём будет задействован байпас, а значит, на вход отправится не 16 бит, а 31 бит аккумулятора (самый старший игнорируется). И при таком раскладе округление оказывается во вред, а не на пользу.

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

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 4 comments