nabbla (nabbla1) wrote,
nabbla
nabbla1

Category:

Продолжаем тормозить (ABS подвела)

Худо-бедно поборол квартус, чтобы он всё-таки докомпилировал проект, воспользовался обычной "заменой" в текстовом редакторе, чтобы заменить адреса B0, B1, B2, B3 на B4, BE, BA, B7, потому что тогда модулю QuatCoreCallTable логические элементы вообще становятся не нужны. (повезло ещё, что начальные и конечные адреса не совпадают)

Запустил на симуляции свою "бортовую программу", увидел, что процедуры вызываются корректно, и первые 4 части (поиск самой отдалённой точки, сортировка против часовой стрелки, нахождение матрицы преобразования и нахождение крена) проходят как надо с новыми исходными данными отсюда (когда смотрим на мишень "сбоку").

А вот на 5-й части, нахождении масштаба, вышел прокол.

Команда ABS (взять абсолютное значение, т.е модуль числа) внезапно затупила - число 72 превратилось в "-73", а 16 - в "-17".



Так что надо снова открывать капот и разбираться, где там в АЛУ мы напортачили. Впрочем, подозрения сразу появились - именно здесь полная команда выглядит так:
ABS Acc


То есть источником данных является аккумулятор всё того же АЛУ, поэтому может быть ситуация спиливания сука, на котором сидишь, "циклической ссылки"...


С момента публикации поста "не столь монструозное АЛУ", оно ещё претерпело небольшие изменения, и сейчас общая схема такова:



Тут самое важное - какие сигналы подаются на вход модуля управления, слева. Мы видим сигнал Bypass, чтобы передать с аккумулятора на вход полное число, а не только 16 бит. Кстати, до сих пор мы это не проверили - пока вычисления таковы, что даже когда байпас работает, он не способен повлиять на результат. Поступает тактовая частота clk - куда же без неё.

Разумеется, поступает шина DestAddr, в которой содержится код команды для АЛУ или для кого-нибудь ещё. CSenior - старший бит регистра циклического сдвига C, для реализации умножения.
Вход D - это уже ближе. Мы загоняем сюда ШИНУ ДАННЫХ. Отчасти это нужно для реализации команды ZAcc - в ней именно два младших бита шины данных выбирают одну из 4 "длинных" констант, которые очень сложно было бы загнать в аккумулятор другими способами.

Но дальше случился ляп - знак числа, необходимый для команды ABS, мы тоже извлекаем ИЗ ШИНЫ ДАННЫХ. И до сих пор такой фокус прокатывал - пока данные мы брали из памяти, работа АЛУ не оказывала на них влияния, одно и то же число оставалось на входе до окончания выполнения.

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

assign AccMode =  isIdle? ((isOurAddr & (PlusMinusMode == 2'b00))? {1'b1, D[1:0]} : 3'b010) : ...

(другие значения мы здесь не привели, они сейчас не важны)

Режим работы аккумулятора "по умолчанию" - это 3'b010, или idle, когда он хранит своё значение.
Мы ОБЯЗАНЫ хранить его везде, кроме команд, которым его содержание безразлично - это команды из "первого столбца периодической таблицы". И там есть лишь одна команда, которая выполняется за 1 такт - это ZAcc. Остальные выполняются как минимум за 3 такта, у них ещё есть время (и даже "обязанность") обнулиться позже. Поэтому на первом такте мы не вдаёмся в подробности и исполняем ZAcc.

На следующем такте происходит обнуление аккумулятора - это абсолютно необходимо, поскольку мы не можем НАПРЯМУЮ занести в него произвольное значение - только ПРИБАВИТЬ к нему, поэтому для начала надо его сбросить.

И только после этого на нём появляется финальное значение, к сожалению, неправильное. И все эти значения через мультиплексор передаются на шину данных...

Теперь разбираемся, в какой момент нам нужен знак числа. На первом такте - ещё нет, мы только-только заносим данные в регистр B. На втором такте - нужен, чтобы узнать, нужно ли инвертировать содержимое. Наконец, и на третьем такте мы должны помнить знак, чтобы, если инверсия произошла, прибавить единичку.

Сейчас, ошибочно восприняв знак от "нового" числа -3/2 или -2, мы делали инверсию регистра B, а затем, когда аккумулятор обнулялся, уже не делали прибавления единички, из-за чего отрицательное число получалось ещё и на единицу меньше, чем при смене знака.

Кто виноват - понятно. Осталось понять - что делать, опровергнув принцип неопределённости Герцена-Чернышевского.

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

Может показаться, что мы можем вместо шины данных взять знак из регистра B. На втором такте так оно и есть, но затем число инвертируется - и там заведомо лежит положительное число, а ведь нам ещё нужно решить, прибавлять ли единичку!

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

Заставлять аккумулятор не сбрасываться в этом конкретном случае, а хранить исходное значение - тоже мимо. Мы можем избежать исполнения ZAcc, тогда хотя бы решение об инверсии будет принято верное. Но в этот же самый такт мы обязаны ОБНУЛИТЬ аккумулятор, и решение о прибавлении единички опять повисает в воздухе...

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

Собственно, вот:
reg SeniorDataBit = 1'b0;
always @(posedge clk) if (isIdle)
	SeniorDataBit <= D[15];


И в строке, задающей инверсию, меняем D[15] на SeniorDataBit:

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


Под спойлером - полный код модуля QuatCoreALUControl.

[Всего 169 строчек, половина - комментарии]
[Там конечный автомат есть. Всё, козёл Фрэнк точно запишет в Авто]
//most complicated thing so far. Should support commands:
//NOP (smth else is active) 
//0x80: ACC
//0x81: PM
//0x82: ADD
//0x83: SUB

//0x84: ABS
//0x85: ???
//0x86: ???
//0x87: ???

//0x88: ZACC
//0x89: ???
//0x8A: C
//0x8B: ???

//0x8C: DIV2
//0x8D: ???
//0x8E: ???
//0x8F: ???

//0x90: MUL
//0x91: FMPM
//0x92: FMA
//0x93: FMS

//0x94: MULSU
//0x95: SUFMPM
//0x96: SUFMA
//0x97: SUFMS

//0x98: MULU
//0x99: UFMPM
//0x9A: UFMA
//0x9B: UFMS

//0x9C: SQRD2
//0x9D: SQRPMD2
//0x9E: SQRAD2
//0x9F: SQRSD2

//100xxxxx = ALU commands
//1000xxxx = 'short' commands
//1001xxxx = 'long' commands

//100xxx00 = 'clear ACC'
//100xxx10 = 'add to ACC'
//100xxx11 = 'sub from ACC'
//100xxx01 = 'plus-minus' (depends on PM input)

//100x00xx = 'signed commands'
//100x01xx = 'signed B / unsigned C commands / special command (ABS)'
//100x10xx = 'unsigned commands / special commands (ZAcc, C)'
//100x11xx = 'div2 commands (DIV2, SQRD2, SQRAD2 etc)'

//AccMode = 	000 - load
//		001 - FORBIDDEN
//         	010 - idle
//		011 - clear
//		100 - set -3/2
//		101 - set 2
//		110 - set 3/2
//		111 - set 1/2lsb

//Bmode   =	00 - shift right
//		01 - shift right and invert
//		10 - load 16 bit
//		11 - load AccWidth bit (bypass)

//Cmode   = 	0x - idle
//		10 - shift
//		11 - load
module QuatCoreALUcontrol (input Bypass, input clk, input [7:0] DestAddr, input Csenior, input PM, input [15:0] D,
			output [2:0] AccMode, output cin, output BSigned, output [1:0] Bmode, output [1:0] Cmode, output Sena, output Busy);
							
wire isOurAddr	   = (DestAddr[7:5] == 3'b100);
wire isLongCommand = DestAddr[4];
wire [1:0] PlusMinusMode = DestAddr[1:0];

wire OpSign = PlusMinusMode[0] & (PlusMinusMode[1] | PM); //0 is '+', 1 is '-'
wire isSignedC = (DestAddr[3] == DestAddr[2]);

reg SeniorDataBit = 1'b0;
always @(posedge clk) if (isIdle)
	SeniorDataBit <= D[15];

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


//first of all, 'state machine'
localparam sIdle = 3'b000;
localparam sDiv2 = 3'b001;
localparam sFirstDiv = 3'b010;
localparam sAdd  = 3'b011;
localparam sMult = 3'b100;

reg [2:0] State = sIdle;


wire isIdle = (State == sIdle);
wire isFirstDiv = (State == sFirstDiv);
wire isDiv2 = (State == sDiv2);
wire isAdd = (State == sAdd);
wire isMult = (State == sMult);

reg MulFirst = 1'b0;
always @(posedge clk)
	MulFirst <= isFirstDiv;

	

//also, 4-bit counter for multiplier
wire finished; //when multiplication is all over
 lpm_counter	counter (
			.clock (clk),
//			.cnt_en (IsIdle),
			.sclr (~isMult),
			.cout (finished) );
  defparam
    counter.lpm_direction = "UP",
    counter.lpm_port_updown = "PORT_UNUSED",
    counter.lpm_type = "LPM_COUNTER",
    counter.lpm_width = 4;


//now combinatorial logic which depends on current state, DestAddr and other inputs
assign AccMode = 	isIdle?	((isOurAddr & (PlusMinusMode == 2'b00))? {1'b1, D[1:0]} : 3'b010) :
			(isFirstDiv | isDiv2)?	{isLongCommand & (PlusMinusMode == 2'b00), 1'b1, (PlusMinusMode == 2'b00)}:
			isAdd?	3'b000:
				{1'b0, ~Csenior, 1'b0};
//in 'clear' commands we either don't care of contents at first step (will clear it anyway),
//or this is ZAcc command which sets one of consts. So we seek for this command
//otherwise AccMode is idle.
					
assign cin = FirstDivSign; //don't care at idle/FirstDiv/Div2, care at sAdd so far

assign BSigned = 	(DestAddr[3:2] != 2'b10); //this row equals fully unsigned operation
							
assign Bmode = 	isIdle? 	{1'b1, Bypass}:
		isFirstDiv?	{1'b0, FirstDivSign} :
				{1'b0, isSignedC & MulFirst};
					
assign Cmode[1] = isIdle? isOurAddr & DestAddr[3] & ((~DestAddr[4]&DestAddr[1])|(DestAddr[4]&DestAddr[2])) : isMult;
									
assign Cmode[0] = ~isMult;

assign Sena    = 	isOurAddr & DestAddr[0] | isMult;

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

always @(posedge clk)
	State <= 	isIdle? 		(Busy? ((DestAddr[3:2] == 2'b11)? sDiv2 : sFirstDiv) : sIdle):
			isDiv2?			sFirstDiv:
			isFirstDiv? 	(isLongCommand? sMult : sAdd):
			(isAdd | finished)?	sIdle : State;
endmodule




Размер этого модуля увеличился с 36 до 38 ЛЭ, хотя в составе полной схемы соотношение может немножко поменяться.


Осталось испытать новый модуль (во всех смыслах)...
Tags: ПЛИС, математика, программки, работа, странные девайсы
Subscribe

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 5 comments