[an error occurred while processing this directive]
.цв
4. Форт-ассемблер
4.1. Введение
.нф
Изложение данной главы предполагает знакомство с машинными командами процессора 6502, в то же время, совершенно не требуется какого-либо знакомства с программированием на Ассемблере.
Хотя Форт обеспечивает высокую скорость программ, но иногда и она становится недостаточной. Некоторые проблемы возникают также при разработке своих программ обработки прерываний, которые не всегда можно эффективно написать на Форте. В таких случаях необходимо обратиться к машинному языку.
Короткие машинные подпрограммы можно ассемблировать вручную, помещая последовательность машинных команд непосредственно в словарь. Такой прием не требует наличия каких-либо инструментальных средств, но имеет массу недостатков: это очень утомительный процесс даже для очень коротких программ, а результирующий код иногда практически нечитаем.
Форт-ассемблер устраняет эти недостатки, так как коренным образом упрощает разработку безошибочного машинного кода. Во многих случаях программа может быть разработана в высокоуровневом Форте и лишь критичные по времени исполнения части ее впоследствии заменяются их эквивалентами в машинном коде. Такая разработка потребует не на много времени больше, чем чисто высокоуровневый подход.
Использование Форт-ассемблера может показаться странным для тех, кто привык к обычному Ассемблеру. Здесь никогда не используюются в явном виде команды ветвления, мнемоника Ассемблера и операнды пишутся в обратном порядке и очень редко применяются метки. Однако, это - очень мощный однопроходный ассемблер с развитой системой диагностики ошибок. Более того, вся мощь самого Форта проявляется в процессе ассемблирования. Скорость, мощь и простота Форт-ассемблера далеко перевешивают его кажущуюся непривычность.
.цв
4.2. Пример
.нф
Проиллюстрируем некоторые особенности Форт-ассемблера на прмере создания простого оределения слова в машинном коде. Для этого выберем слово DROP. На обычном Ассемблере это слово можно описать так:
JMP POP
где POP - точка входа в существующую машинную подпрограмму, которая удаляет число с вершины стека и передает управление Форт-системе. На Форт-Ассемблере определение DROP выглядит следующим:
CODE DROP POP JMP, END-CODE
Слова CODE и END-CODE используются для начала и конца машинного определения, аналогично : и ; в определении через двоеточие.
Слово CODE создает заголовок словарной статьи с именем, стоящим вслед за CODE, и запускает процедуру диагностики ошибок. Далее оно исполняет слово assembler, подключая к словарю список со словами Форт-Ассемблера, и оставляет систему в состоянии исполнения. Каждая машинная команда помещается в словарь (т.е. компилируется) во время исполнения слов мнемоники Ассемблера. Каждое такое слово действует как мини-компилятор, чтобы поместить свой код в создаваемую словарную статью.
В целом процесс ассемблирования, поэтому, представляет собой обычную интерпретацию текста, но со списком assembler в качестве первого списка поиска. Это означает, что все слова словаря forth также доступны для модификации или манипулирования содержимым стека, делая процесс в целом чрезвычайно гибким.
Отметим 2 момента, которые могут помочь при написании ассемблерных слов.
Во-первых, мнемоника Ассемблера всегда оканчивается запятой: это удобно, так как (1) помечает конец группы слов, формирующих одну машинную команду, (2) по аналогии со словом "," запятая означает место, в котором байт машинной команды действительно оказывается скомпилированным, (3) устраняет путаницу между мнемоникой Ассемблера и 16-ричными числами (ABC) или другими Ассемблерными словами.
Во-вторых, операнд (напр., POP) помещается перед мнемоникой. Это вседа так, является ли он числом или символическим словом. Операнд в любом случае кладется на стек, откуда он легко компилируется совместно с кодом машинной команды при исполнении мнемоники Ассемблера.
Определение завершается словом END-CODE, которое в дополнение к проверкам на отсутствие ошибок, восстанавливает первоначальный контекстный список.
.цв
4.3. Метки машинного кода
.нф
Каждая Ассемблерная словарная статья должна оканчиваться безусловным переходом к подпрограмме, которая прямо или косвенно исполняет подпрограмму NEXT. NEXT используется в конце любого слова Форта, ее основной функцией является переход к исполнению следующего слова в списке компиляций словарной статьи. Завершающий переход к этой подпрограмме реализован в следующих словах Форт-Ассемблера:
.кс-8
Слово Описание NEXT Передает управление следующему слову PUSH Кладет на стек данных одинарное число из аккумулятора (старший байт) и из стека возвратов (младший байт) и исполняет NEXT PUT Заменяет число на вершине стека данных на число из аккумулятора и стека возвратов (как в PUSH) и исполняет NEXT PUSH0A Кладет на стек данных байт из аккумулятора как младший байт и ноль как старший байт и исполняет NEXT POP Снимает со стека одинарное число и исполняет NEXT POPTWO Снимает со стека двойное число и исполняет NEXT
.нф
В Форт-Ассемблере эти слова имеют смысл системных меток (отсюда команда NEXT JMP,).
В дополнение к этим завершающим словам имеется подпрограмма SETUP (тоже в виде метки), которое переносит до четырех чисел со стека данных во временный буфер на нулевой странице. На входе аккумулятор должен содержать число пересылаемых чисел. На выходе из подпрограммы в регистре Y будет 0, а значение в аккумуляторе будет удвоено, иными словами в нем будет число байтов, пересланных из стека. В байте, предшествующем временному буферу, будет записано это же число.
Абсолютные адреса всех этих слов-меток могут потребоваться при ручном аасемблировании. Вот эти адреса для Форт-Агат v. 1.0 (в 16-ричных числах):
PUSH D09F PUT D0A1 POPTWO D219 NEXT D0A6 SETUP D0C6 POP D21B PUSH0A D350
Вызов каких-либо подпрограмм Операционной системы (ОС) отладчика проще всего реализовать по абсолютным адресам входов в них, указанным в Руководстве по операционной системе Best. Можно также оформить их как константы, напр.:
hex 262 constant input Ввод строки с клавиатуры
27d constant rdkey Чтение с устройства 283 constant hexo Вывод 16-ричного числа 286 constant hexo1 Вывод 16-ричной цифры 2C8 constant prchar Передача символа в принтер 2CB constant prbyte Передача байта в принтер 2CE constant prinit Инициализация принтера
и т.д.
Более подробно о вызовах ОС можно прочесть в Руководстве по ОС Best 5.3.
.цв
4.4. Регистры
.нф
Хотя Форт использует все регистры процессора (далее регистры), их все же можно использовать и в определениях ассемблерных слов, соблюдая определенные соглашения.
Всякий раз, когда ассемблерная словарная статья исполняется, вход в нее происходит из подпрограммы NEXT. Поэтому регистры будут всегда иметь вполне определенное содержание, а именно:
.кс-3
1) Содержимое аккумулятора произвольное. Этот регистр можно свободно использовать в машинной программе. 2) Регистр Y всегда содержит 0 и тоже может применяться без ограничений. 3) Регистр X содержит адрес вершины стека данных, в которой хранится младший байт числа на этом стеке. Этот регистр можно использовать только после сохранения его содержимого, напр., в переменной XSAVE, и восстановления его значения в конце словарной статьи. 4) Аппаратный стек 6502 используется Фортом как стек возвратов. Указатель стека содержит адрес первого неиспользуемого байта за концом стека. Его содержимое обычно изменять нельзя, иначе можно утратить адрес возврата в Форт. 5) Содержимое регистра состояния процессора, за исключением флага D десятичного режима, неопределенно и может свободно использоваться. Флаг D - обнулен и должен быть в этом же состоянии после выхода из определения.
.нф
В дополнение к регистрам процессора Форт имеет ряд специальных регистров на нулевой странице. Ассемблер содержит, снова в виде констант, адреса всех этих регистров. Вот они:
.кс-1
Имя Адрес Размер Формат Назначение N E0 1+8 xXXXXXXXX Временный буфер XSAVE E8 2 XX Сохранение регистра X W EA 1+2 xXX Указатель поля кода IP EC 2 XX Указатель адреса интерпретации UP EE 2 XX Указатель области пользовательских переменных
.нф
Заметим, что константы N и W используют 1 лишний байт перед их установленным адресом.
Временный буфер N, как описано выше, используется SETUP. В общем, его применяют для хранения данных, которыми удобно пользоваться в определении. Этот буфер не следует использовать для обмена данными между словами Форта.
Однобайтный регистр XSAVE резервируется для сохранения X-регистра, если он нужен в определении.
Указатель UP области пользовательских переменных содержит адрес начала последовательности ячеек, предназначенных для хранения значений ряда системных переменных, а также переменных, определяемых Вами для своих нужд. Эти переменные описаны в Основном словаре Форт-Агата.
Регистры W и IP являются фундаментальными для работы Форта и должны использоваться с большой осторожностью. Небрежное их изменение приводит к порче системы. Эти регистры используются и модифицируются словом NEXT.
Указатель W поля кода используется для хранения адреса поля кода текущего исполняемого слова, а указатель IP - следующего за исполняемым слова.
.цв
4.5. Мнемоники кодов машинных команд
.нф
Мнемоники Ассемблера разделяются на 2 группы по типу адресации. К первой группе относятся однобайтные безадресные команды. Во вторую группу входят двух и трехбайтные команды с различными типами адресации. Все мнемоники команд соответствуют стандарту для процессора 6502, к ним только добавлена запятая справа.
.цв
4.5.1. Однобайтные мнемоники
.нф
К ним относятся:
.кс+5
BRK, CLC, CLD, CLI, CLV, DEX, DEY, INX, INY, NOP, PHA, PHP, PLA, PLP, RTI, RTS, SEC, SED, SEI, TAX, TAY TSX, TXA, TXS, TYA,
.нф
Когда такое слово исполняется, то соответствующий код машинной команды просто компилируется в словарную статью.
Следующий пример при исполнении слова ZERO кладет число 0 на стек:
CODE ZERO TYA, PHA PUSH JMP, END-CODE
В нем использован тот факт, что в регистре Y всегда содержится 0 перед входом в слово. Аккумулятор А и вершина аппаратного стека - обнуляются, затем оба байта (из А - вначале) кладутся на стек данных словом PUSH.
.цв
4.5.2. Многобайтные мнемоники
.нф
К ним относятся:
.кс+5
ADC, AND, ASL, BIT, CMP, CPX, CPY, DEC, EOR, INC, JMP, JSR, LDA, LDX, LDY, LSR, ORA, ROL, ROR, SBC, STA, STX, STY,
.нф
Каждая из этих мнемоник обычно требует, чтобы в стеке был операнд. Если перед мемоникой не указан никакой тип адресации, то предполагается, что операнд является адресом - абсолютным (если 2 байта) или для нулевой страницы (если 1 байт) - в зависимости от типа машинной команды.
Когда такое слово исполняется оно компилирует в словарь соответствуюший код команды и операнд.
.цв
4.5.3. Типы адресации
.нф
Ниже представлены символы, обозначающие тип используемой адресации операнда:
Символ Тип адресации Ожидаемый операнд нет абсолютная двухбайтный абсолютный адрес нет нулевой страницы однобайтный адрес .A аккумуляторная нет # непосредственная байт ,X индексная по X одно- или двухбайтный адрес ,Y индексная по Y то же X) предындексная косвен. однобайтный адрес адреса )Y постиндексная косвен. то же ) косвенная абсолютный адрес адреса
Ниже приведены примеры использования различных типов адресации операндов в Форте и в обычном Ассемблере:
Форт-Ассемблер Ассемблер Агат ADDR JSR, JSR ADDR .A ASL, ASL A 4 # CMP, CMP #4 ADDR ,X LDA, LDA ADDR,X ADDR ,Y STA, STA ADDR,Y ADDR X) ADC, ADC (ADDR,X) ADDR )Y LDA, LDA (ADDR),Y ADDR ) JMP, JMP (ADDR)
Следующие 2 примера используют подпрограмму ОС cout1, которая выводит на экран символ, код которого лежит на стеке. Так как эта подпрограмма не изменяет содержимое регистров ( за исключением флагов C, N, V и Z), то никаких мер по их защите мы не предпринимаем. Слово CHAR выдает на экран символ из ячейки 128 (&80):
hex
CODE CHAR 80 LDA, 2BC JSR, NEXT JMP, END-CODE
decimal
После компиляции CHAR используют так:
65 128 C! CHAR
Эта строка выводит A.
Модификация CHAR с использованием постындекцной адресации через Y позволит выводить символ, адрес которого записан по адресу &80:
hex
CODE (CHAR) 80 )Y LDA, 2BC JSR, NEXT JMP, END-CODE
decimal
Здесь учитывется то, что всегда перед входом в слово Y=0.
.цв
4.6. Доступ к стекам Форта
.нф
В большинстве кодовых программ требуется доступ к стеку данных, или стеку возвратов, или к обоим стекам. Этот доступ реализуется по-разному для каждого стека.
Напомним, что стеки в Форте растут вниз, к меньшим адресам. В высокоуровневом Форте эта особенность не имела значения, и мы говорили о текущем значении указателя стека как о его "вершине". В случае машинных программ мы не можем позволить себе забыть реальную структуру стеков. Поэтому в Форт-Ассемблере "вершина" стека называется "дном". В обоих стеках старший байт числа всегда находится в старшем байте ячейки.
.цв
4.6.1. Стек данных
.нф
В Форт-Агате стек данных называют стеком вычислений, так как в нем действительно вычисляются основные арифметические действия. Он расположен на нулевой странице памяти и адрессуется по индексному по X типу адресации с регистром X в качестве указателя стека. Регистр X нормально содержит адрес младшего байта ячейки дна стека. Две донные ячейки так часто требуются в программах, что имеют специальные слова для их адресации, а именно:
Стек Адресация Второй от дна, ст.байт 3 ,X или SEC 1+ Второй от дна, мл.байт 2 ,X или SEC Дно стека, старший байт 1 ,X или BOT 1+ Дно стека, младший байт 0 ,X или BOT В качестве примера предложим определение слова 4* для быстрого умножения на 4: CODE 4* ( n -- 4*n) BOT ASL, BOT 1+ ROL, BOT ASL, BOT 1+ ROL, NEXT JMP, END-CODE
.цв
4.6.2. Стек возвратов
.нф
Стек возвратов расположен в аппаратном стеке процессора на странице 1 (&100..&1FF). В отличие от стека данных, его указательвсегда содержит адрес первого свободного байта ниже дна стека. Этот указатель однобайтовый, поэтому он содержит только младший байт адреса в этом стеке. Когда исполняются машинные команды PHA или PLA, процессор автоматически добавляет старший байт для доступа к нужной странице и устанавливает текущий адрес в указателе стека на 1 ниже его дна. Поэтому команда
PLA, PLA,
просто удалит донное число из стека (причем младший байт - первым).
Если необходимо манипулировать стеком возвратов, аппаратный указатель стека можно перевести в регистр X. Конечно предварительно сохранив содержимое последнего в переменной XSAVE. Поправки к указателю аппаратного стека, вносимые процессором, автоматически не добавляются к другим регистрам и должны вноситься отдельно. Специальный модификатор адреса стека возвратов - слово RP) - обеспечивает доступ к донному байту стека возвратов. Оно эквивалентно команде 101 ,X. Так что выражение
RP) LDA,
загружает в аккумулятор нижний байт стека возвратов.
Следующий пример, напр., будет загружать аккумулятор младшим байтом второго от низа числа стека возвратов (или третьим байтом от от дна аппаратного стека):
XSAVE STX, TSX, RP) 2+ LDA, XSAVE LDX,
Здесь первая строка сохраняет содержимое X в XSAVE и переводит в X содержимое указателя аппаратного стека, а третья строка восстанавливает первоначальное состояние регистра X. Эти 2 строки обязательны во всех случаях непосредственного обращения к стеку возвратов.
.цв
4.7. Условные ветвления
.нф
Структуры условных ветвлений обеспечиваются Форт-Ассемблером аналогично высокоуровневому Форту. Различия - только в конечных запятых:
.кс+10
IF, ... ELSE, ... THEN, BEGIN, ... AGAIN, BEGIN, ... UNTIL, BEGIN, ... WHILE, ... REPEAT,
.нф
Главное отличие состоит в том, что в ассемблере проверяется состояние битов признаков, а не содержимое стека. Эти проверки выполняются следующими словами:
Слово Значение Условие Соответствует команде истинности обычного ассемблера CS Перенос есть C=1 BCS или ¤B0 VS Переполнение есть V=1 BVS ¤70 0< Меньше нуля N=1 BMI ¤30 0= Равно нулю Z=1 BEQ ¤F0 CS NOT Переноса нет C=0 BCC ¤90 VS NOT Переполнения нет V=0 BVC ¤50 0< NOT Больше или равно 0 N=0 BPL ¤10 0= NOT Не равно 0 Z=0 BNE ¤D0
Два примера иллюстрируют некоторые идеи условных ветвлений. Первый просто прибавляет 3 к числу на дне стека:
CODE 3+ ( n -- n+3) BOT LDA, CLC, 3 # ADC, 0= IF, BOT 1+ INC, THEN, NEXT JMP, END-CODE
Второй пример иллюстрирует использование IF, ... ELSE, ... THEN, и вложенных условных ветвлений. Он прибавляет или вычитает 1 из второго числа в стеке в зависимости от знака числа на дне стека:
CODE INC/DEC ( n n1 -- n2 ) BOT 1+ LDA, 0< IF, SEC LDA, 0= IF, SEC 1+ DEC, THEN, SEC DEC, ELSE, SEC INC, 0= IF, SEC 1+ INC, THEN, THEN, POP JMP, END-CODE
Следующие 2 примера вводят слова 2@ и 2! для разыменования двойного числа и записи его по заданному адресу:
CODE 2@ ( A -- d) 1 # LDA, SETUP JSR, 3 # LDY, BEGIN, N )Y LDA, DEX, BOT STA, DEY, 0= UNTIL, NEXT JMP, END-CODE CODE 2! ( d A -- ) 1 # LDA, SETUP JSR, BEGIN BOT LDA, N )Y STA, INX, INY, 4 # CPY, 0= UNTIL, NEXT JMP, END-CODE
.цв
4.8. Использование слова ;CODE
.нф
Как известно, новые структуры данных создаются в Форте посредством слов CREATE и DOES>. Высокоуровневый код, стоящий после DOES>, определяет действие каждого члена совокупности слов, созданных данным определяющим словом. Недостаток этих слов, в особенности для данных со сложной структурой, медленность исполнения высокоуровневого кода. Было бы полезно иметь возможность определить исполняемую часть определяющего слова в машинных командах. Форт располагает именно таким средством. Это слово ;CODE, которое применяют в форме:
: NAME CREATE ... ;CODE ... END-CODE
Общие принципы этой структуры те же, что и при использовании CREATE ... DOES>. Слово CREATE компилирует параметры, присущие всем членам данного семейства, и каждый член семейства исполняет кодовую подпрограмму, стоящую вслед за ;CODE в определяющем слове.
Во время компиляции слово ;CODE оканчивает компиляцию высокоуровневой части определения через двоеточие и запускает ассемблирование кодовой части словарной статьи. Тем самым это слово выполняет многие из функций слов ; и CODE. Когда же определяющее слово (такое как NAME) исполняется и создает новый член семейства, то начинает действовать третий аспект ;CODE. Содержимое поля кода нового члена изменяется: теперь оно содержит адрес машинного кода, стоящего вслед за ;CODE.
Пара примеров может помочь прояснить использование ;CODE. Первый из них создает переменные двойной длины:
: 2VARIABLE ( -- A) CREATE 0 , 0 , ;CODE ( кладет PFA на стек ) CLC, W LDA, 2 # ADC, PHA, TYA, W 1+ ADC, PUSH JMP, END-CODE
Это определяющее слово может определять так
2VARIABLE DOUBLE
создавая двойную переменную DOUBLE со значением 0. Когда DOUBLE исполняется, она использует машинный код за ;CODE в слове 2VARIABLE и оставляет на стеке адрес первого байта поля параметров слова DOUBLE. Это значение затем могут использовать слова 2@ и 2!, описанные выше.
Код, использованный словом 2VARIABLE, идентичен коду слова VARIABLE, так как возвращает один и тот же адрес. Так что определение без потери скорости исполнения можно упростить:
: 2VARIABLE VARIABLE 0 , ;
хотя в нем и не видна роль ;CODE.
А вот во втором примере, где дается определение слова 2CONSTANT, которое создает двойную константу, вышприведенный упрощенный вариант не применим:
: 2CONSTANT ( -- d ) CREATE , , ;CODE DEX, DEX, CLC, W LDA, 2 # ADC, BOT STA, TYA, W 1+ ADC, BOT 1+ STA, ' 2@ JMP, END-CODE
Предлагаемая версия в целях ясности определения и экономии памяти представлена не в самом быстродействующем варианте. Кодовая часть ее очень похожа на определение 2VARIABLE, за исключением того, что PFA непосредственно кладется на стек, а не передается через PUSH. Коды заканчиваются обращением к слову 2@, которое заменяет адрес на стеке двойным числом и исполняет NEXT.
.цв
4.9. Макросы
.нф
Макрос - это часть машинного кода, которую можно использовать несколько раз в программе. Макрос используется аналогично подпрограмме, но, в отличие от нее, не вызывается на исполнение, а непосредственно вставляется в нужное место программы. Преимущество макроса состоит в увеличении скорости исполнения, так как отсутствуют вызов подпрограммы и последующий возврат в основную программу. Хотя это достигается ценой увеличения длины программы (если длина макроса превышает 4 байта).
Макрос создается словом MACRO аналогично определению через двоеточие. В нем команды аасемблера не исполняются, а лишь компилируются. Они исполняются при вызове макроса, и тогда вместо имени макроса в данное место программы будут включены машинные команды, составляющие макроопределение.
Главная особенность макросов - они обязательно должны быть созданы в списке assembler, как в следующем примере:
assembler definitions hex MACRO COUT1 ( -- ) 2BC JSR, ; MACRO RDKEY ( -- ) 280 JSR, ;
Эти 2 макросы ассемблируют вызов подпрограмм ОС ОНИКС. Они удобнее в применении, чем приведенные выше константы.
Следующая пара макросов ассемблирует цикл, аналогичный Фортовскому циклу DO .. LOOP:
MACRO TIMES, # LDY, BEGIN, ; MACRO LOOP, DEY, 0= UNTIL, ;
Эти маркосы используются в форме
n TIMES, ... LOOP,
Завершив определения одного или нескольких макросов, Вы должны восстановить первоначальное значение переменной CURRENT, напрмер, исполнив команду
forth definitions decimal
В заключительном примере использованы описанные выше маркосы при определении слова ALPHABET, которое выводит на экран латинский алфавит:
CODE ALPHABET ( --) 64 # LDA, 26 TIMES, CLC, 1 # ADC, COUT1 LOOP, NEXT JMP, END-CODE
.цв
10.10. Ошибки
.нф
В ассемблер Форта включена мощная система диагностики ошибок. Она ,конечно, не в состоянии гарантировать корректность работы кодовых определений, но может предотвратить ошибки в их синтаксисе.
Система диагностики включает 5 групп проверок:
.ло+3
1. Каждая мнемоника проверяется на правильность типа адресации. При любой ошибке выдается сообщение.
2. В условных ветвлениях проверяется правильность применения парных слов IF, ... THEN, BEGIN, ... UNTIL, и т.п.
3. Внутри условных структур проверяется, чтобы любая ветвь была в пределах границ. Так, сообщение об ошибке выдается ,если исполняется ... IF, 256 ALLOT THEN, (здесь переход от IF, до THEN, слишком длинен).
4. Число чисел на стеке, как правило, не должно изменяться в ходе кодового определения. Это может произойти, напр., если на стеке останется неиспользованный адрес или условная структура не будет завершена. лжно быть включено в список assembler. Если это не так, будет выдано сообщение об ошибке и макрос не будет принят в программу.
.стФортАгат.Инстр3
[an error occurred while processing this directive]