nabbla (nabbla1) wrote,
nabbla
nabbla1

Category:

Модуль управления АЛУ зафурычил!

Сегодня дописал этот модуль. Он получился относительно маленьким, 38 ЛЭ. Наверное, можно было и лучше, но и так я весьма доволен, всё-таки он реализует 24 команд АЛУ, может даже больше - не успел разобраться, есть ли среди 8 оставшихся "недокументированных" команд что-нибудь приличное, что могло бы пригодиться. Всё АЛУ занимает 189 ЛЭ при 32-битном аккумуляторе, и 150 ЛЭ при 19-битном (меньше нельзя-начнёт ругаться, да нам наверное и не хочется).

Пока что я подключил к входу DestAddr 8-битный счётчик, который перебирает все 256 адресов ("команд"), из них только 32 относятся к АЛУ, но нужно было убедиться, что и на "чужие" команды оно не откликается и свои регистры не портит.



И затем полдня срисовывал "осциллограму" на бумажку и сравнивал с тем, что там должно быть. Всё совпало!




Вот он код модуля:

//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 BSign, 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]);
wire FirstDivSign = OpSign ^ (isLongCommand & isSignedC & (isFirstDiv | MulFirst)) | (BSign & (~isLongCommand) & (~DestAddr[3]) & DestAddr[2]);


//first of all, 'state machine'
//short commands may be 1,2 or 3 clk long, long commands: 17 clk
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 isMultFirstStep = 1'b1;
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};
						
assign cin = FirstDivSign; 

assign BSigned = 	(DestAddr[3:2] != 2'b10);
							
assign Bmode = 	isIdle? 	{1'b1, Bypass}:
		isFirstDiv?	{1'b0, FirstDivSign} :
				{1'b0, isSignedC & MulFirst};
					
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;

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


В этот раз хитрючий синтезатор смог обнаружить в этом коде конечный автомат, после чего заменил 3-битный регистр State на 5 бит, в кодировке One-hot (выбранное состояние "зажигается" единичкой, остальные - нулями).

Все "чужие" команды, а также две "свои" - ZAcc и C - выполняются за один такт, при этом мы остаёмся в состоянии Idle.

Чтобы сразу "отбраковать" команды, не принадлежащие АЛУ, введен "провод" isOurAddr:

wire isOurAddr = (DestAddr[7:5] == 3'b100);

Проверяем первые 3 бита адреса.

Вот обновлённая "периодическая таблица команд":



Чтобы узнать, что команда выполняется всего за такт, достаточно выбрать 3-ю строку, т.е адрес должен записываться как 1000_10xx. Старшие 3 бита мы уже проверили через isOurAddr, остаётся проверить ещё 3.

Именно это делает первая часть строки
assign Busy = isOurAddr & (DestAddr[4:2] != 3'b0_10)&(~isAdd)&(~finished);


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

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

Из состояния Idle мы можем перейти либо в FirstDiv, либо в Div2. Последняя дополнительно сдвигает регистр B вправо, после чего мы возвращаемся в FirstDiv.

В состоянии FirstDiv также регистр B сдвигается вправо, в некоторых случаях с инверсией, в некоторых - нет. Именно выбор знака - один из самых запутанных моментов в этом модуле. Оно происходит примерно так:

- узнаём, с каким знаком вообще должен поступить операнд. Если младшие два бита адреса: 00 или 10 (первый и второй столбец в таблице), то заведомо знак "+", если 11 (третий столбец) - то заведомо знак "-", а вот если 01, то знак определяется сигналом PM (Plus-Minus), который приходит извне. В виде кода оно записывается так:

wire OpSign = PlusMinusMode[0] & (PlusMinusMode[1] | PM);


- одно исключение - операция взятия модуля, ABS. Тогда знак старшим разрядом на шине данных (или в регистре B). Поскольку вся вторая строка таблицы имеет лишь одну команду, то и здесь не проверяем все 8 бит адреса, а только 3 бита:

(D[15] & (~isLongCommand) & (~DestAddr[3]) & DestAddr[2])


(isLongCommand - просто "переименованный" DestAddr[4], чтобы было хоть чуточку понятнее).
И поскольку при выборе команды ABS мы по первому правилу поставим OpSign=0, то можно эту добавку объединить по "ИЛИ".

- если у нас происходит знаковое умножение, то нужно один раз поменять знак операнда при загрузке (точнее, при первом сдвиге вправо), т.к старший разряд регистра C будет иметь вес "-1" (дополнительный код), а затем - при следующем такте вернуть знак "как было", и затем 15 тактов его не трогать.

В итоге, получаем такое монструозное выражение:
wire FirstDivSign = OpSign ^ (isLongCommand & isSignedC & (isFirstDiv | MulFirst)) | (D[15] & (~isLongCommand) & (~DestAddr[3]) & DestAddr[2]);


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

Остальные воздействия, пожалуй, попроще будут. Регистр C совсем "отдыхает" на верхних ("коротких") командах, за исключением команды C (собственно, занести в него новое значение). Также в него заносится значение на командах самой нижней строки, т.к это возведение в квадрат - одно и то же значение у нас поступает и в B, и в C одновременно! И затем он проявляет активность на умножении, а именно, выполняет циклический сдвиг влево. Через 16 тактов он возвращается в исходное состояние, зато старший разряд с него управлял аккумулятором - принимать ли прибавленное значение, или оставить как есть?

С аккумулятором тоже хитрость - для команд из первого столбца он обнуляется, причем для "коротких" команд в него заносится конкретно НОЛЬ (не считая ZACC, который заносит константу), а вот для длинных - заносится половинка младшего разряда, чтобы автоматически обеспечивалось правильное округление.

В общем, есть надежда, что самое страшное позади:




Теперь "стряхну пыль" с модуля PC (Program Counter), а из них двоих уже можно построить интересные вещи.
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 

  • 12 comments

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

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

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

    Вчера я чуть поторопился отсинтезировать проект,параметры не поменял: RomWidth = 8 вместо 7, RamWidth = 9 вместо 8, и ещё EnableByteAccess=1, чтобы…

  • Балансируем конвейер QuatCore

    В пятницу у нас всё замечательно сработало на симуляции, первые 16 миллисекунд полёт нормальный. А вот прошить весь проект на ПЛИС и попробовать "в…