nabbla (nabbla1) wrote,
nabbla
nabbla1

Category:

QuatCore: о стеке и вызове процедур

В продолжение темы о "кощунстве", когда мы применили SP просто как удобный инкрементирующийся регистр при выводе строки на экран.

А ведь нигде в QuatCore стек НЕ ЗАВЯЗАН на вызов процедур!


Когда мы пишем в ассемблере:

CALL print


CALL - это не команда QuatCore, это директива ассемблера, или скорее макрос, которая заносит метку print в таблицу вызова процедур, QuatCoreCallTable, а саму строку превращает уже в команду, наподобие:

[SP++]  Call0


Это уже команда, состоящая, как всегда, из двух частей, из адреса получателя (DestAddr), в данном случае [SP++], и адреса источника данных (SrcAddr), в данном случае Call0.

Адреса Call0, Call1, ..., CallF (всего их 16) принадлежат модулю QuatCorePC (program counter), т.е счётчику инструкций. При выборе любого из этих адресов, на шину данных поступает значение PC+1, т.е адрес следующей команды, которая бы выполнилась, не будь прыжка. Проще говоря, АДРЕС ВОЗВРАТА. И ещё происходит "побочный эффект" - мы переходим в одну из 16 процедур, адреса которых задаются "аппаратно" прямо внутри QuatCorePC, за счёт файлика QuatCoreCallTable.v, который формирует компилятор, и который нужно положить в папку с QuatCore, прежде чем его синтезировать.

Адрес [SP++] принадлежит модулю QuatCoreMem. Он знать не знает, что за данные такие поступили на шину данных. Он просто заносит их в стек!

У меня была идея сделать также "макрос" RET, который сразу же заменяется командой

JMP   [--SP]


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

А QuatCorePC, завидев адрес JMP, осуществляет переход по абсолютному адресу, взятому из шины данных.

На самом деле, для хранения адреса возврата мы могли бы использовать что угодно!

Если мы знаем, что регистр Z пока не используется, мы вполне могли бы вызвать процедуру так:
Z   Call0


а вернуться из процедуры так:

JMP  Z


Понятно, вызывать таким же способом другую процедуру из первой категорически нельзя, включая и рекурсивный вызов - затрём исходный адрес возврата. Но если мы знаем, что процедура "концевая" и других не вызывает - это метод абсолютно законный :) надо только ещё убедиться, что ширина регистра Z достаточна для хранения адреса возврата. Вообще говоря, ширина X,Y,Z,SP задаётся шириной адреса RAM, а PC - шириной адреса ROM, и они могут не совпадать. Но можно ширину увеличить "ручками", никаких проблем

Или даже так:
C  Call0


и возврат:
JMP  C

регистр C всегда 16-битный, так что это даже предпочтительнее :)

Заносить адрес возврата в аккумулятор и производить над ним арифметические действия не предлагаю, хотя и такое имеет право на существование. Какой-нибудь switch/case мы можем реализовать - сначала положить адрес возврата, а потом к нему что-нибудь прибавить, чтобы в зависимости от полученного числа (символа) мы перешли в разные части программы.

Такая свобода выбора вызвана тем, что у нас здесь начисто отсутствуют прерывания. Как только они появляются - нам тут же хочется унифицировать доступ к "свободному месту", куда можно сохранить все текущие регистры и потом вернуть их все на места, поскольку огромные куски программы пишутся с таким расчётом, что на любой команде управление может ВНЕЗАПНО отойти обработчику прерываний, и тот должен аккуратно замести за собой все следы вмешательства!

Но наш процессор пока что совсем для прерываний не готов: у него в принципе отсутствует возможность целиком сохранить текущее состояние и вернуться к нему. Доступ к самому старшему и младшим битам аккумулятора попросту отсутствует, хотя это и можно реализовать на "костылях" - старший бит определить по флагу переполнения, а затем пустить в цикле команду
ADD Acc


которая по сути осуществляет сдвиг влево на единичку. Таким образом, вытолкаем все младшие биты и сохраним, наконец, куда-нибудь в стек. Отдельная головная боль - сохранить бит знака (опять же, с помощью JL / JGE, прямого доступа нет) и ещё смешнее - вернуть его на место, проведя некие "фиктивные" вычисления.

Да и наш способ хранения локальных переменных, просто "на стеке", по [SP] и [SP+1] - категорически противопоказан в случае прерываний, т.к при сохранении регистров и адреса возврата первым же делом они будут потёрты. То есть, у нас поднос без бортиков. Если хочется локальные переменные сохранять во время прерываний, скорее всего нам нужен ещё один регистр, BP, как в x86. Можно обойтись и без него, но тогда вместо [SP] и [SP+1] нам нужны будут [SP-1] и [SP-2], а первым делом после вызова процедуры мы должны смещать стек на 1-2 позиции под хранение этих переменных, и не забыть в конце вернуть всё как было.

Впрочем, я думаю, что в этих задачах лучше обходиться без прерываний вообще. Всё достаточно детерминировано - матрица опрашивается по известному закону, параметры информационного обмена тоже оговариваются довольно строго. Как мне это представляется, максимум, что будет - это начальный вектор из нескольких адресов. Сейчас у нас работа начинается с reset, что выставляет PC по нулевому адресу, и программа выполняется, пока сама себя не загонит в бесконечный цикл. А кроме reset, можно ввести ещё несколько проводов, наподобие "frame_ready" или "MilStd1553_request", которые безусловно загоняют PC по нужным адресам, в полной уверенности, что к тому времени предыдущая операция уже была выполнена и процессор "прохлаждается" :)

А там посмотрим, куда разработка заведёт, может и передумаю в итоге.


UPD. Если хочется сохранять локальные переменные при вызове процедуры - НИЧЕГО МЕНЯТЬ В СИСТЕМЕ КОМАНД НЕ НУЖНО! Просто меняем направление, куда "растёт" стек. Сейчас он у нас растёт "вверх", в сторону увеличения адреса, т.е помещаем мы командой [SP++], а извлекаем через [--SP]. Тогда [SP] и [SP+1] - позиции НА стеке.

А если сделать помещение в стек командой [--SP], а извлечение через [SP++], то [SP] и [SP+1] будет указывать на два значения В стеке. Тогда процедуры будут выглядеть примерно так:

Foo proc
  [--SP] 0 ;выделяем память под одну локальную переменную
  [--SP] 0 ;под вторую, инициализируем заодно :)
  ... работа с [SP] и [SP+1]
  NOP    [SP++]
  NOP    [SP++]
  JMP    [SP++]
Foo endp


Poll #2098864 Прерывания

Использование прерываний в задачах реального времени

Необходимо
4(57.1%)
Пригодится
1(14.3%)
ЗЛО!!!
2(28.6%)
Tags: ПЛИС, программки, работа, странные девайсы
Subscribe

Recent Posts from This Journal

  • Великая Октябрьская резня бензопилой

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

  • Очередная несуразность в единицах измерения

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

  • Big Data, чтоб их... (3)

    "В предыдущих сериях": мой прибор выдаёт 6 значений: 3 координаты и 3 угла, т.е все 6 степеней свободы твёрдого тела. Причём ошибки измерения этих 6…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 11 comments

Recent Posts from This Journal

  • Великая Октябрьская резня бензопилой

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

  • Очередная несуразность в единицах измерения

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

  • Big Data, чтоб их... (3)

    "В предыдущих сериях": мой прибор выдаёт 6 значений: 3 координаты и 3 угла, т.е все 6 степеней свободы твёрдого тела. Причём ошибки измерения этих 6…