Пока что я подключил к входу 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), а из них двоих уже можно построить интересные вещи.