nabbla (nabbla1) wrote,
nabbla
nabbla1

Category:

Разгоняем QuatCore до 25 МГц, часть 2

Начинается самое интересное - организация слаженной работы процессора "в конвейере".

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

Всё интересное происходит, если на какой-то из трёх ступеней случается "неполадка". На этапе выборки команды может оказаться, что был выполнен прыжок, и только что поступившие SrcAddr и ждущие своего часа DestAddr нужно ВЫКИНУТЬ, ни в коем случае не выполняя!

На этапе выборки данных может возникнуть заминка - либо мы читаем из памяти, а не из регистра, и нужно лишний такт подождать. А может, мы вообще читаем из SPI или UART, и тогда ожидание может затянуться и подольше (мы бы не стали СПЕЦИАЛЬНО усложнять логику процессора ради SPI/UART, если бы, к примеру, выборка ВСЕГДА занимала один такт, но раз уж есть исключение - теперь их будет 3).

И наконец, может затянуться запись/обработка данных, от 3 до 18 тактов в случае АЛУ, и даже больше в случае устройств ввода-вывода, тех же SPI, UART и LCD.

Поехали...


Начнём с основных модулей: QuatCoreMem (работа с памятью, базовые регистры X/Y/Z/SP), QuatCoreALU (арифметическое устройство, не знаю, стоит ли его называть логическим) и QuatCorePC (счётчик инструкций, отработка переходов, вызова процедур, индексные регистры). Модуль QuatCoreImm можно не рассматривать, уж больно он простой (транслирует 7 бит из шины адреса SrcAddr на шину данных, с расширением знака), а ввод-вывод пока что оставим. Сначала отладим "внутреннюю работу", а там и до ввода-вывода доберёмся.

Итак, всего 3 модуля, и 3 варианта "проблем". Но важно помнить ещё одну вещь: каждый модуль как бы поделён на две части. Почему-то мы слабо акцентировали на это внимание, но часть, реагирующая на SrcAddr, предоставляющая данные на выход, и часть, реагирующая на DestAddr и получающая данные на вход - должны оказываться практически независимыми! По этой причине сразу несколько проблем может возникнуть одновременно, и такие варианты тоже надо просчитать.

QuatCoreMem - на запись
Он сам никогда не потребует остановки конвейера - данные подаются в память на том же такте, и к следующему они уже на месте. Впрочем, из-за того, что запись происходит на один такт позже, чем чтение, у нас возникает HAZARD - если предыдущая команда осуществляет запись в некий регистр или память, а следующая пытается тут же его прочитать, то она прочитает предыдущее, "устаревшее" значение. Бороться с этим "аппаратно" очень трудно, именно эту проблему я хочу возложить на транслятор и, отчасти, на программиста.

Что должен делать этот модуль, если кто-то другой запросит остановку конвейера? Мы уже знаем, что DestAddr в этом случае зафиксируется, нам его менять не нужно. А вот на шине данных будут происходить подвижки - скорее всего сейчас там неверное значение, а когда действия возобновятся, нам дадут верные данные.

Отсюда вывод: мы можем разрешить запись данных, будь то запись в регистр или в память, поскольку на следующем такте мы перезапишем уже верное значение. А вот что мы должны пресечь на корню - это инкремент/декремент стека! Эту операцию он должен выполнить ровно один раз, когда остановка будет снята. Мы должны стараться сделать так, чтобы сигналы остановки конвейера / сброса команды оказывали минимальное влияние на модули, иначе очень скоро обнаружим возникновение циклических ссылок!

В случае сброса команды (когда стало ясно, что это команда, идущая ПОСЛЕ строки перехода, и выполнять её не надо), мы ни в коем случае не должны осуществлять ни запись, ни инкремент/декремент SP.

QuatCoreMem - на чтение
Команды обращения к регистрам (X,Y,Z,SP) занимают один такт и остановки конвейера не требуют. Операции чтения из памяти, то есть все, обозначенные квадратными скобками, требуют остановки конвейера на один такт, пока адрес не будет защёлкнут в ОЗУ, и на выход поступят корректные данные. При этом надо не забыть остановить также инкремент/декремент стека на первом такте, иначе он прибавится дважды.

Если кто-то другой запросит остановку конвейера, то мы опять же должны остановить инкремент/декремент стека. Выдавать данные можем абсолютно с чистой совестью - они уже будут верны и не будут меняться. Тут мы должны аккуратно формировать команду "занятости", так, чтобы она всё равно сбрасывалась уже после первого такта, вне зависимости от того, кто ещё запросил остановку.

В случае сброса команды мы вполне можем подавать на выход что хотим, поскольку точно знаем - команда записи также будет сброшена. Главное - затормозить инкремент/декремент стека.

QuatCoreALU - на запись
Есть три команды, которые выполняются за один такт и не требуют остановки конвейера. Это ZAcc (занести в аккумулятор одну из 4 заранее определённых "длинных" констант), C (занести данные в регистр C) и NOP (ничего не делать). Все остальные команды при текущей реализации вызывают остановку процессора на срок от 2 до 18 тактов. Можно было бы сделать чуть красивее, как мы делали с UART, SPI и LCD - арифметическая операция ЗАПУСКАЕТСЯ НА ИСПОЛНЕНИЕ - и процессор может заниматься своими делами. Но если он попробует запустить ещё одну арифметическую операцию, или обратится к аккумулятору, или захочет осуществить условный переход, т.е запросит ФЛАГИ АЛУ - вот тогда процессор остановится и дождётся завершения операции. Но именно из-за того, что АЛУ так сильно "мусорит" (куда не обратись -в него упрёшься!), пока оно сразу тормозит процессор, "дёшево и сердито".

Если кто-то другой запросит остановку конвейера, а у нас самые короткие команды, в 1 такт, нам глубоко наплевать. Ничто не мешает нам лишний раз записать неверные данные в Acc (через ZAcc) или в C, и уж точно никто не помешает нам НИЧЕГО НЕ ДЕЛАТЬ на NOP. Остановка конвейера интересна тем, что DestAddr и SrcAddr фиксируются, и только шина данных может поменяться (в этом зачастую и смысл - дождаться, когда на ней будут верные данные). Для всех прочих команд мы действительно должны остановиться и дождаться корректных данных.

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

QuatCoreALU - на чтение
У нас всего 3 команды - Acc (результат трудов "с насыщением"), UAC (Unsigned Acc, без знака и насыщения) и C (прочитать регистр C). Все они, очевидно, выполняются за один такт, и НЕ ДАЮТ ПОБОЧНЫХ ЭФФЕКТОВ, как было с инкрементом/декрементом стека.

Если кто-то другой запросит остановку конвейера, нам плевать - мы всё равно выдадим на шину данных запрошенный результат, он никому не навредит.

И на сброс команды нам глубоко наплевать - мы всё равно выдадим результат на шину, зная, что на запись этот результат заведомо не уйдёт.

QuatCorePC - на запись
Есть команды записи в регистры i,j,k,Inv, а также инкремента i++,j++,k++, все они выполняются за такт, то есть не требуют остановки процессора. Все остальные - это команды перехода, условного и безусловного. Если перехода не происходит, то они также выполняются за такт безо всяких последствий. Если же переход наступил, то НАМ НУЖНО СБРОСИТЬ ДВЕ ПОСЛЕДУЮЩИЕ КОМАНДЫ! Так получается, поскольку пока DestAddr продвигался сквозь защёлки, у нас уже набралось целых две команды, идущие уже после команды прыжка - они исполниться не должны!

Если кто-то другой запросит остановку конвейера, то мы можем разрешить запись в регистры, но должны не допустить инкремента и точно не должны выполнять команды перехода - пока что ждём. (хотя, осуществить переход по АБСОЛЮТНОМУ АДРЕСУ можно, главное по ОТНОСИТЕЛЬНОМУ не переходить, а то несколько раз прибавим смещение - будет грустно)

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

QuatCorePC - на чтение
У нас есть команды чтения i,j,k, Inv - они безобидны и выполняются за один такт. И есть команды вызова процедуры, их может быть 16, отличаются они только адресами процедур, записанных в QuatCoreCallTable.v. Если выполняется такая команда, НАМ НУЖНО СБРОСИТЬ ПОСЛЕДУЮЩУЮ КОМАНДУ! В этот раз - всего одну, поскольку SrcAddr обрабатывается тактом раньше. Так что у QuatCore получается забавная особенность - из всех команд перехода именно вызов процедур вносит в конвейер наименьшие разрушения!

Если кто-то другой запросит остановку конвейера, то в случае чтения i,j,k,Inv нет никаких проблем. А вот с вызовом процедуры надо всё-таки повременить! Хотя ничего страшного не будет, если произведём переход - мы продолжим его производить раз за разом...

То же самое нужно выполнить в случае сброса команды.


Вроде разобрались. Теперь осталось весь этот текст "оттранслировать" в код или схемы и надеяться, что комбинаторных обратных связей и самозаблокирования не возникнет. Когда я это сделал "по наитию", тут же возникло и одно, и другое :)
Tags: ПЛИС, программки, работа, странные девайсы
Subscribe

  • Формулы приведения, что б их... (и atan на ТРЁХ умножениях)

    Формулу арктангенса на 4 умножениях ещё немножко оптимизировал с помощью алгоритма Ремеза: Ошибка уменьшилась с 4,9 до 4,65 угловой секунды, и…

  • Алгоритм Ремеза в экселе

    Вот и до него руки дошли, причина станет ясна в следующем посте. Изучать чужие библиотеки было лениво (в том же BOOSTе сам чёрт ногу сломит), писать…

  • atan на ЧЕТЫРЁХ умножениях

    Мишка такой человек — ему обязательно надо, чтоб от всего была польза. Когда у него бывают лишние деньги, он идёт в магазин и покупает какую-нибудь…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 2 comments