Запустил на симуляции свою "бортовую программу", увидел, что процедуры вызываются корректно, и первые 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 ЛЭ, хотя в составе полной схемы соотношение может немножко поменяться.
Осталось испытать новый модуль (во всех смыслах)...