Ввёл посылку CMD55 - работает, ответ корректный. Но в конце строки какой-то мусор, и мы входим в бесконечный цикл:

(скриншот сделан чуть позже, когда я попытался локализовать проблему и выкинул львиную долю информационного обмена)
После не столь долгих разбирательств оказалось, что ошибка опять "аппаратная", в смысле, что не в ассемблере, а в верилоге. Очень обидная ошибка - она появилась, когда я делал модуль QuatCoreMem более "сговорчивым" - одно исправил, а другое поломал, причём ошибка затаилась на довольно длительный срок, никак не выявляя себя...
С самого начала мне очень подозрительным показалось следующее в ассемблерном коде:
OKCMD55 dw 'CMD55 success',13,0x800A .data Stack dw ?,?,?,?,?
То есть, запоролись ПОСЛЕДНИЕ СИМВОЛЫ строки, вплотную прилегающей к стеку!
Собственно, поэтому и случился бесконечный цикл - потёрлась единичка в старшем разряде, которая должна нас останавливать, поэтому так и крутимся по ближайшим 32 словам.
Следующий вопрос - а почему стек уполз не в ту сторону? Первым подозреваемым была процедура SendSD, где я опять играюсь со стеком, но потом, когда я вообще перестал её вызывать, проблема сохранилась. Та же история с "обработкой исключений" - до них выполнение попросту не доходило!
Симулятор позволил пролить свет на проблему: из указателя стека ВНЕЗАПНО вычиталась единичка на команде
X CMD0strAdr
причём CMD0strAdr транслируется в литерал 0x77.
То есть мы просим поместить вполне конкретное значение в регистр X, а "попутно" вдруг из стека вычитается единица.
Вот код нашего модуля QuatCoreMemDecoder:
module QuatCoreMemDecoder (input [7:0] DestAddr, input [7:0] SrcAddr, input stall, output MemWrite, output SrcSquareBrac, output WriteX, output WriteY, output WriteZ, output WriteSP, output CountSP, output SPup, output [1:0] BaseAddr, output [1:0] FirstIndex, output [1:0] SecondIndex ); wire isDest = (DestAddr[7:6] == 2'b11); wire DestSquareBrac = (DestAddr[3:0] != 4'b1101); assign MemWrite = isDest & DestSquareBrac; assign BaseAddr = MemWrite? DestAddr[5:4] : SrcAddr[5:4]; assign FirstIndex = MemWrite? DestAddr[1:0] : SrcAddr[1:0]; assign SecondIndex = MemWrite? DestAddr[3:2] : SrcAddr[3:2]; assign SrcSquareBrac = (SrcAddr[3:0] != 4'b1101); assign WriteX = isDest & (~DestSquareBrac) & (DestAddr[5:4] == 2'b00); assign WriteY = isDest & (~DestSquareBrac) & (DestAddr[5:4] == 2'b01); assign WriteZ = isDest & (~DestSquareBrac) & (DestAddr[5:4] == 2'b10); assign WriteSP = isDest & (~DestSquareBrac) & (DestAddr[5:4] == 2'b11); wire isSource = (SrcAddr[7:6] == 2'b11); assign CountSP = (~stall) & (isDest | isSource) & (BaseAddr == 2'b11) & (FirstIndex == 2'b11); assign SPup = ~SecondIndex[0]; endmodule
И ошибка в выражении для CountSP - если здесь появляется единичка, то реверсивный счётчик, в котором хранится SP, начинает счёт вверх или вниз, в зависимости от выражения SPup (нужно ли считать ВВЕРХ?).
Для команды, которая вызвала ошибку, DestAddr = 0xCD = 1100_1101, а SrcAddr = 0x77 = 0111_0111.
Смотрим, как идёт "декодирование". isDest = 1 (проверяются старшие два бита DestAddr), то есть назначение данных - модуль памяти.
DestSquareBrac = 0, т.е в мнемоническом коде команды нет квадратных скобок, т.е запись надо делать не в память, а в регистр.
MemWrite = 0, т.е запись в память не делается.
И вот дальше возникает проблема. Провода BaseAddr / FirstIndex / SecondIndex управляют формирователем эффективного адреса. Раньше там стояли такие выражения:
assign BaseAddr = isDest? DestAddr[5:4] : SrcAddr[5:4]; assign FirstIndex = isDest? DestAddr[1:0] : SrcAddr[1:0]; assign SecondIndex = isDest? DestAddr[3:2] : SrcAddr[3:2];
И хотя это сильно ограничивало наши возможности - нельзя было поместить в регистр значение из памяти, но по крайней мере стек работал правильно :)
А сейчас, раз мы не ведём запись в память, то от щедрот своих передаём формирователь эффективного адреса для чтения из памяти. Причём мы не проверяем, нужно ли действительно читать из памяти, если не нужно, то мультиплексор QuatCoreSrcMux попросту проигнорирует наши данные.
Вот и получается, что BaseAddr = 11, что соответствует SP;
FirstIndex = 11, что в случае стека соответствует вычитанию единички для SP;
SecondIndex = 01, что в случае стека соответствует прибавлению нуля.
И теперь, при нахождении выражения CountSP, мы имеем:
(~stall)=1 (процессор никого не ждёт!),
(isDest | isSrc) = 1 (в нашем случае isDest=1),
BaseAddr == 2'b11 и
FirstIndex = 2'b11.
То есть, выполнены все условия для счёта SP.
Раньше это не срабатывало, поскольку мы не забирались в "отрицательную" область памяти, где сплошные единички в адресах. Но вот залезли наконец - и нарвались.
Исправление довольно простое, в одну строку:
assign CountSP = (~stall) & ((isDest & DestAddr[5] & DestAddr[4] & DestAddr[1] & DestAddr[0]) | (isSource & SrcAddr[5] & SrcAddr[4] & SrcAddr[1] & SrcAddr[0]));
С ним декодер с 17 ЛЭ разбухает аж до 18 ЛЭ, не нравится мне, когда так получается, но это запоздалое следствие расширения функциональности, когда мы разрешили перемещать данные из памяти в базовые регистры и наоборот.
Кажись, исправили:

Осталось ещё 2 команды выполнить, одна запросит High Capacity, вторая узнает, эта карточка High Capacity (свыше 2 Гб) или обычная. Если ещё багов в процессоре не вылезет - сделаем наконец-то...
UPD. Ага, и с размаху налетел на ещё одну "особенность". Хотел сделать, чтобы входные данные можно было сразу в аккумулятор запихивать или даже какие-то действия с ними делать, для чего добавил вход stall в АЛУ, и думал, этого хватит.
А вот добавить вход stall в устройства ввода-вывода забыл! В итоге, АЛУ честно дожидалось, когда мы примем очередной байт, а когда приняли его - начало упихивать в аккумулятор, при этом SrcAddr оставался тот же самый, и мы напрочь зависли, ожидая, когда же будет принят ещё один байт, а он не будет принят, потому что мы не запустили передачу! Лаадно, ща подправим...